-
-
Notifications
You must be signed in to change notification settings - Fork 4
Language Reference
🌐 English · 日本語
- Mana Language Reference
- Source code structure
- Types
- Variables
- Constants
- Expressions
- Operators
- Statements
- Function
- Struct
- Actor
- Action
- Request
- Execution control
- Module
- Phantom
- Namespace and using
- Native Function
- Source files and import / include
- Predefined symbols
- CLI
This section is a reference for looking up Mana's syntax and language features precisely.
The Tutorial is for "learning in order", and Concepts is for understanding "why it works that way". The Language Reference puts first being able to check syntax, constraints and examples by topic.
- Module
- Phantom
- Namespace and using
- Native Function
- Source files and import / include
- Predefined symbols
- CLI
Where possible, each page explains things in this order:
- Overview
- Syntax
- Behaviour
- Constraints
- Examples
- Related topics
This reference is based on the current compiler, VM and tests. Where old syntax remains in the former Primer, the current implementation takes precedence.
Mana source code is written in text files. The standard extension is .mn.
hello.mn
A single-line comment starts with //.
// A single-line comment
print("Hello\n");
A multi-line comment is enclosed in /* and */.
/*
A comment
over several
lines
*/
In the current Lexer, nesting block comments is an error.
Spaces and tabs separate tokens. Line breaks are also not normally significant in the grammar.
A statement is usually ended with ;.
int count = 0;
count = count + 1;
{ and } group several declarations or statements.
if (count > 0)
{
print("positive\n");
}
Actors, Actions, Functions, namespaces and so on also use blocks.
In the current Lexer, an identifier can start with a letter, _ or ?, and can contain digits after that.
Examples:
count
_count
Enemy01
?temporary
For readability, however, we recommend using mainly letters and _ in ordinary code.
Reserved words cannot be used as identifiers.
Decimal integers are written as they are.
0
10
1234
Hexadecimal integers start with 0x.
0x10
0xFF
Binary integers start with 0b.
0b1010
0b1111_0000
In the current Lexer, _ can be used as a separator in binary notation.
Floating-point numbers are written with a decimal point.
1.0
0.5
12.25
1.0e3
A string is enclosed in ".
"Hello"
"Hello\n"
Escape sequences such as \n can be used inside a string.
Boolean values are written with these reserved words.
true
false
In the current implementation, the reserved word for an empty reference is Nil.
Nil
The N is upper case.
:: separates namespaces.
Game::AI::Enemy
-> is used for Action references.
Enemy->think()
The two have different roles.
-
::: qualifies with a namespace -
->: refers to an Actor's Action
Mana is a statically typed language. Variables, arguments, return values and so on have a type.
The basic types the current Lexer recognises are:
| Type | Overview |
|---|---|
void |
Used for Functions and the like that return nothing |
char |
8-bit integer type |
short |
16-bit integer type |
bool |
Boolean value |
int |
32-bit integer type |
float |
32-bit floating-point type |
string |
String type |
pointer |
A type for low-level references |
Examples:
int count = 10;
float speed = 2.5;
bool opened = false;
string name = "Guard";
void is not an ordinary variable type that holds a value; it mainly expresses "no return value".
void reset()
{
}
bool holds true or false.
bool enabled = true;
In conditions you can use the results of comparisons and logical operations.
string holds a string of text.
string message = "Hello";
String literals are enclosed in ".
To pass a reference to an Actor as an argument of a Function or native Function, use the Actor type. Lowercase actor is reserved for Actor declarations.
void notify(Actor target)
{
}
self, sender and the like are also treated as Actor references.
The current compiler registers the following value types in advance, for common use.
| Type | Members |
|---|---|
vec2 |
float x, float y
|
vec3 |
float x, float y, float z
|
vec4 |
float x, float y, float z, float w
|
rotator |
float pitch, float yaw, float roll
|
color |
float r, float g, float b, float a
|
As with Structs, their members are referred to with ..
actor BuiltInTypeExample
{
action main()
{
vec3 position;
position.x = 10.0;
position.y = 20.0;
position.z = 30.0;
color tint;
tint.r = 1.0;
tint.g = 0.5;
tint.b = 0.25;
tint.a = 1.0;
}
}
transform is not registered as a predefined type in the current compiler.
You can create user-defined types with struct.
struct Position
{
float x;
float y;
}
Position p;
Structs are covered in detail in Struct.
pointer is a type used mainly for low-level purposes, such as the boundary between the VM and native Functions.
For ordinary game event scripting, we recommend using mainly int, float, bool, string, Actor, the predefined compound types and Struct types.
The Mana Compiler checks that types agree in assignments, Function calls, return values, operations and so on.
Code whose types do not match is, as a rule, diagnosed at compile time.
A variable is a name for holding a value.
int count;
float speed;
bool opened;
You can give an initial value at the same time.
int count = 0;
float speed = 1.5;
bool opened = false;
count = 10;
speed = 2.0;
Compound assignment can also be used.
count += 1;
speed *= 2.0;
Declared outside any Actor or Function.
int gScore = 0;
Global variables are initialised before the init of ordinary Actors runs.
Declare an Actor variable inside an Actor and outside its Actions. Every Action belonging to that Actor can read and write the variable, and its value remains part of that Actor's state between Actions.
actor Door
{
bool mOpened;
action init()
{
mOpened = false;
}
}
Declared inside the block of an Action or Function.
actor Counter
{
action main()
{
int count = 0;
count = count + 1;
}
}
A local variable is a temporary value used during that work.
struct Position
{
float x;
float y;
}
actor PositionExample
{
action main()
{
Position p;
p.x = 10.0;
p.y = 20.0;
}
}
Variable declarations can use fixed-length arrays.
actor ArrayExample
{
action main()
{
int values[4];
values[0] = 10;
values[1] = 20;
}
}
The array size can be a positive integer literal or the name of an integer constant.
const int kValueCount = 4;
actor ArrayWithConstant
{
action main()
{
int values[kValueCount];
}
}
A declarator can also have several [] in a row.
int grid[4][8];
Array elements are referred to with [].
values[index]
If an index decided at run time is outside the array, the current VM stops that Actor with a ScriptError and does not continue the out-of-range access.
At the top level, allocate and static can be used to lay out the VM's variable memory explicitly. These are lower-level features than ordinary game logic.
allocate 1024
{
int gReservedValue;
}
allocate N { ... } reserves an area of an explicit size in the global variable area and places the variables inside it. N is an integer literal giving the number of bytes.
static
{
float gStaticValue;
}
Variables in static { ... } are placed in the VM's static variable area, separate from ordinary global variables.
There is also a form that gives the size explicitly.
static allocate 512
{
int gStaticReservedValue;
}
The current compiler checks that the variables declared in an explicit area fit in it.
Mana's static is syntax for choosing the static variable area inside the VM. Don't read it as having the same meaning or linkage rules as static in C++.
| Where it is declared | Main use |
|---|---|
| Global | A value shared across the whole program |
| Inside an Actor | Per-Actor state |
| Inside an Action / Function | A temporary value |
| Inside a Struct | A member of the Struct |
A static block |
The VM's static variable area |
Use const for values that don't change.
Use const for values that don't change.
const type name = constant-expression;
Examples:
const int kMaxCount = 10;
const float kSpeed = 2.5;
const bool kDebug = false;
const string kMessage = "Hello";
A name declared with const cannot be assigned to later.
const int kMaxCount = 10;
// Error
kMaxCount = 20;
The Compiler diagnoses such assignments.
The initial value of a const must be an expression that can be evaluated at compile time.
const int kBase = 10;
const int kDouble = kBase * 2;
Things whose value is only decided at run time, such as Function calls, cannot be used as a constant's initial value.
Nil has a type of its own, and in the current compiler it cannot be used in a constant expression.
Constants suit giving meaningful names to numbers such as Priorities.
const int kTalkPriority = 10;
const int kMovePriority = 5;
request(kTalkPriority, NPC->talk());
The purpose is easier to see than with a number written directly.
Integer constants can also be used as the size of a fixed-length array.
const int kValueCount = 4;
actor ArrayExample
{
action main()
{
int values[kValueCount];
}
}
The current Lexer still has the old-style define / undef tokens, but they are not part of the declaration syntax in the current Parser.yy.
So the new documentation does not treat define / undef as current syntax for declaring constants. In new code, use const type name = value;.
An expression is syntax that makes, refers to, calculates or assigns a value.
10
1.5
true
"Hello"
Nil
count
speed
mOpened
count + 1
speed * 2.0
(a + b) * c
count == 0
count != 0
count < 10
count >= 1
The result of a comparison can be used in conditions and so on.
enabled && visible
ready || force
!finished
count = 10
count += 1
speed *= 2.0
The target of an assignment is something writable, such as a variable or a member.
?: can be used.
int value = enabled ? 1 : 0;
calculate(10, 20)
A Struct's member Function is called with ..
value.reset()
A Struct's members are accessed with ..
position.x
Array elements are referred to with [].
values[index]
An Actor's Action is referred to with ->.
Enemy->think()
Game::AI::Enemy->think()
Action references are used with request, awaitStart, await and so on.
request(10, Enemy->think());
:: qualifies a name with a namespace, and -> is an Action reference.
Mana has predefined symbols for referring to the execution situation.
priority
self
sender
this
Nil
Where each one is valid and exactly what it means are covered in the "Predefined symbols" reference.
A sizeof operator is provided.
What it applies to and what it gives are covered in detail in the operators reference.
The Compiler checks that types agree in operations and assignments within expressions.
This page lists the main operators available in Mana.
| Operator | Meaning |
|---|---|
+ |
Addition |
- |
Subtraction |
* |
Multiplication |
/ |
Division |
% |
Remainder |
** |
Power |
Examples:
int a = 10 + 2;
int b = 10 % 3;
| Operator | Meaning |
|---|---|
== |
Equal |
!= |
Not equal |
< |
Less than |
<= |
Less than or equal |
> |
Greater than |
>= |
Greater than or equal |
| Operator | Meaning |
|---|---|
&& |
Logical AND |
| ` | |
! |
Logical NOT |
if (enabled && visible)
{
}
| Operator | Meaning |
|---|---|
& |
AND |
| ` | ` |
^ |
XOR |
~ |
NOT |
<< |
Left shift |
>> |
Right shift |
=
+= -= *= /= %=
&= |= ^=
<<= >>=
Examples:
count += 1;
flags |= 0x10;
The current grammar accepts both the prefix and the postfix forms.
++count;
--count;
count++;
count--;
condition ? trueValue : falseValue
Example:
int sign = value >= 0 ? 1 : -1;
In the current grammar, sizeof takes a type in brackets.
sizeof(int)
sizeof(Position)
It is not syntax that accepts an arbitrary expression.
-> is a Mana-specific operator for referring to an Actor's Action.
Enemy->think()
request(10, Enemy->think());
Its meaning differs from pointer member access in C/C++.
:: qualifies a name that includes a namespace.
Game::AI::Enemy
To go as far as an Action, combine the two.
Game::AI::Enemy->think()
For typical expressions other than assignment, the current Parser binds more tightly roughly in this order, from loosest to tightest:
?:
&& ||
== !=
< <= > >=
| ^
&
<< >>
+ -
* / %
**
sizeof
! ~
Unary + -
++ --
Note in particular that && and || are declared with the same precedence in the current Parser.
When you want to make your intent clear, use brackets rather than relying on precedence alone.
if ((a || b) && c)
{
}
This page lists the basic statements used in Mana.
Put ; after an expression.
count = count + 1;
update();
{
int count = 0;
count += 1;
}
if (condition)
{
print("true\n");
}
else
{
print("false\n");
}
while (condition)
{
update();
}
do
{
update();
}
while (condition);
for (int i = 0; i < 10; ++i)
{
print("loop\n");
}
For a loop with no exit condition written as an expression, use loop.
loop
{
update();
}
Leaves the current loop or switch.
while (true)
{
if (finished)
break;
}
Skips the rest of the current iteration and moves on to the next.
for (int i = 0; i < 10; ++i)
{
if (i == 5)
continue;
print("run\n");
}
switch (value)
{
case 0:
print("zero\n");
break;
case 1:
print("one\n");
break;
default:
print("other\n");
break;
}
In a Function, it returns to the caller.
int add(int a, int b)
{
return a + b;
}
return; can also be used inside an Action. In that case it ends the current Action and releases its Priority. If there is a suspended Action at a lower Priority, the VM can return to it.
actor NPC
{
action talk()
{
if (sender == Nil)
return;
print("Hello\n");
}
}
An Action has no return value, so return expression; is not used in an Action.
A label is written identifier:, and a jump goto identifier;.
actor GotoExample
{
action main()
{
goto Done;
print("skip\n");
Done:
print("done\n");
}
}
A goto to a label that doesn't exist is a name resolution error at compile time.
Where ordinary branches and loops can express it, we recommend preferring structured control statements such as if, switch, while and for.
print("Hello\n");
Syntax for asking an Actor to run an Action.
request(10, Enemy->think());
awaitStart(10, Enemy->think());
await(10, Enemy->think());
For the detailed waiting conditions and how they relate to Priority, see Request and Execution control.
There are statements that control how an Action progresses, winding Priority back, whether Requests are accepted, and so on.
yield
join
rollback
halt
lock
refuse
comply
The exact syntax and behaviour are gathered in Execution control.
A Function is an ordinary subroutine that takes values, groups some work, and returns a value when needed.
Unlike an Action, a Function is not a Request target; it runs synchronously within the flow of the code that called it.
return_type functionName(arguments)
{
statements
}
Global and Struct member Function definitions with no arguments may omit empty
() as shorthand; definitions with arguments require parentheses. Calls always
require (), such as functionName() or counter.reset().
For example, Actor current() { ... } and Actor current { ... } define a global
Function returning an Actor, while actor current { ... } declares an Actor.
Example:
int add(int a, int b)
{
return a + b;
}
A Function can be called from an Action or from another Function.
actor FunctionExample
{
action main()
{
int value = add(2, 3);
print("%d\n", value);
}
}
Arguments are declared as a type followed by a name.
float distance(float x, float y)
{
return x + y;
}
The type can be a built-in type, including Actor, or a user-defined type.
A Function with a return value returns it with return expression;.
int getCount()
{
return 10;
}
If there is no return value, use void.
void reset()
{
return;
}
Returning a value from a void Function, or writing a bare return; in a Function that returns a value, is a compile error.
A Function can also be defined inside a struct.
struct Counter
{
int value;
void reset()
{
value = 0;
}
}
actor CounterExample
{
action main()
{
Counter counter;
counter.reset();
}
}
It is called with ..
For Struct member Functions, see Struct.
| Function | Action |
|---|---|
| Runs through an ordinary function call | Can be run with a Request |
| Can have arguments | Has no arguments in the current syntax |
| Can have a return value | Has no return value in the current syntax |
| Part of the caller's work | A unit of execution of an Actor |
It helps to use Actions for independent behaviour in the game, and Functions for work reused inside Actions.
A Function connected to the C++ side is declared with native.
native void playSound(string name);
For details, see Native Function.
A struct groups several values, and the Functions that work with them, into one type.
struct TypeName
{
members
}
Example:
struct Status
{
int hp;
int mp;
}
It is used as a variable.
actor StatusExample
{
action main()
{
Status status;
status.hp = 100;
status.mp = 20;
}
}
Variables can be declared inside a Struct.
struct CharacterData
{
string name;
int level;
float speed;
Actor owner;
}
A Struct can also have another Struct as a member.
struct Position
{
float x;
float y;
}
struct Unit
{
Position position;
int hp;
}
actor UnitExample
{
action main()
{
Unit unit;
unit.position.x = 10.0;
unit.hp = 100;
}
}
Members are referred to with ..
Ordinary Functions can be defined inside a Struct.
struct Counter
{
int value;
void reset()
{
value = 0;
}
}
actor CounterExample
{
action main()
{
Counter counter;
counter.reset();
}
}
In the current compiler, a Struct's member Function can contain ordinary Mana code too. For example, it can take an Actor as an argument and send it a Request.
struct Helper
{
void call(Actor target)
{
request(1, target->talk());
}
}
native Functions can also be declared inside a Struct.
struct Transform
{
native void reset();
}
actor TransformExample
{
action main()
{
Transform transform;
transform.reset();
}
}
The syntax on the calling side is the same as for an ordinary member Function.
For how it maps to the C++ side, see Native Function and Native Functions Integration.
A Struct is a data type that groups values. Unlike an Actor, it does not become an independent unit that runs Actions.
| Struct | Actor |
|---|---|
| A data type | A unit of execution |
| Can have Functions | Can have Actions and state |
| Not a Request target | Is a Request target |
| Held as a variable | The VM manages the Actor instance |
An actor is Mana's basic unit of execution that runs Actions independently.
An Actor holds state, has several Actions, and can receive Requests from other Actors.
actor ActorName
{
members
}
Example:
actor NPC
{
int mTalkCount;
action talk()
{
mTalkCount++;
print("Hello\n");
}
}
Inside an Actor you mainly write:
- Actions
- Variables
- Constants
- Modules brought in with
extend
Example:
actor Guard
{
int mAlertLevel;
const int kMaxAlert = 3;
action patrol()
{
}
}
An Actor's member variables are kept as the Actor's state, and their values remain after an Action ends.
The Mana VM instantiates ordinary actors when the Program Image is loaded.
This is the big difference from phantom. A Phantom is not instantiated at load time; it is created explicitly from the C++ side.
After loading the program, the Mana VM requests special Actions from the Actors.
The current VM sends every Actor, in this order:
- A Request for
mainat Priority 0 - A Request for
initat the highest Priority (2147483647)
If an Actor does not define that Action, the Request does not run.
actor Example
{
action init()
{
print("init\n");
}
action main()
{
print("main\n");
}
}
init can be used to initialise state, and main for normal startup work.
Startup does not wait for other Actors to initialize. Actors without init start with main. An init must not wait for a lower-Priority Action on the same Actor: that Action cannot run until init finishes.
Actor is the type that holds a reference to an Actor. The lowercase actor keyword is only used to declare an Actor. Actor is reserved and cannot be used as a user-defined name.
Actor target;
You can name an Action on an Actor reference.
request(1, target->talk());
For details on Action references, see Request.
An Actor can be defined inside a namespace.
namespace Game::NPC
{
actor Shopkeeper
{
action talk()
{
}
}
}
Its fully qualified name is Game::NPC::Shopkeeper.
An Actor is a unit of execution, not a concept only for game characters.
It can also be used for roles such as:
- Event flow
- UI control
- Gimmicks
- Scene management
- Battle flow
- Effect control
An action is a unit of work an Actor runs.
Unlike a Function, an Action is a Request target, and it is started, suspended and resumed according to Priority.
action actionName()
{
statements
}
Example:
actor NPC
{
action talk()
{
print("Hello\n");
}
}
An Action currently has no arguments and no return value. The form with ()
is canonical; omitting it is shorthand. action talk and action talk()
define the same Action.
action talk()
{
}
To share values between Actors, use the Actor's state, global data, Structs, Native Functions and so on, as suits the purpose.
An Action can be run with request, awaitStart, await and so on.
request(1, NPC->talk());
NPC->talk() is an Action reference.
Action references use ->.
NPC->talk()
Namespace qualification uses ::.
Game::NPC::Shopkeeper->talk()
The old form Actor::action() remains in the compiler as compatibility syntax, but it gives a deprecated warning. In new code, use Actor->action().
init and main are Action names that get special treatment when the VM starts.
actor Example
{
action init()
{
}
action main()
{
}
}
The current VM Requests init at the highest Priority (2147483647) and main at Priority 0 for every Actor. It does not wait for every Actor's init to finish before starting main. Each Actor runs its queued Actions in Priority order after its own init finishes.
Inside an Action you can use predefined values that describe the current execution state.
-
self: the current Actor -
sender: the Actor that Requested this Action -
priority: the Priority currently running
Example:
actor NPC
{
action talk()
{
print("priority = %d\n", priority);
}
}
sender holds the Request's sender, so it can be used to tell which Actor asked for the Action to run.
For details, see Predefined symbols.
When an Actor receives another Request while running an Action, Priority decides the order of execution.
- A higher Priority: interrupts the current Action
- A lower Priority: held back until the current Action ends
- The same Priority: in the current VM, if a Request at that Priority already exists, the new Request is not accepted
The larger the number, the higher the Priority.
For details, see Request and Execution control.
| Action | Function |
|---|---|
| A unit of execution of an Actor | An ordinary subroutine |
| A Request target | An ordinary function call |
| Has a Priority | Has no Priority |
| Can be suspended and resumed | Runs as part of the caller's work |
| No arguments or return value in the current syntax | Can have arguments and a return value |
A Request is the mechanism for asking an Actor to run an Action.
In Mana, cooperation between Actors is not expressed with ordinary Function calls alone, but with Request and Priority.
Syntax:
request(priority, actor_expression->actionName());
Example:
request(10, NPC->talk());
The first argument is the Priority, and the second is an Action reference.
The form with () is canonical, and the empty () may be omitted as shorthand:
Actor->action and Actor->action() both refer
to an Action with no arguments. Neither form runs the Action directly; request
sends the execution request. Action arguments are not supported yet.
The larger the value, the higher the Priority.
The recommended syntax is ->.
NPC->talk()
With a namespace:
Game::NPC::Shopkeeper->talk()
An expression of Actor type can also be used.
Actor target;
request(1, target->talk());
The Parser accepts expression->actionName() as an Action reference.
The old form:
NPC::talk()
is also recognised as compatibility syntax, but it gives a deprecated warning. Don't use it in new code.
A plain request does not wait for the Action to complete.
request(10, NPC->talk());
print("continue\n");
The Actor that sent the Request carries on with the work that follows.
On the target Actor's side, it is handled according to Priority.
- The requested Priority is higher than the current one: it interrupts the current work
- The requested Priority is lower than the current one: it is kept to run later
- The requested Priority is the same as the current one: an execution state at that Priority already exists, so the new Request is not accepted
It is not a mechanism that queues several Actions at the same Priority.
In the current VM's Actor::Request, a Request fails in at least these cases:
- The Priority is at or below the VM's lowest interrupt Priority
- The target Actor is halted
- The target Actor is in the
refuse()state - A Request at the same Priority already exists
- The Action given does not exist
The request statement in a script has no return value that reports success or failure.
awaitStart(priority, actor_expression->actionName());
Sends a Request and makes the caller wait until the target Actor is in a state where it can start that Priority.
If the request is accepted, the wait ends when the target Actor's current Priority becomes the requested Priority or lower. It does not guarantee that the first statement of the requested Action has already run.
awaitStart(10, NPC->talk());
await(priority, actor_expression->actionName());
Sends a Request and waits until the work at that Priority completes.
If the request is accepted, the wait ends when the target Actor's current Priority becomes lower than the requested Priority. It is not a mechanism that keeps a completion notice per request and waits for it.
await(10, NPC->talk());
If the initial Request is not accepted, awaitStart and await carry on without waiting. They do not keep requesting until the same Priority is free. Returning from the wait does not by itself guarantee that the requested Action ran, or that its behaviour succeeded.
If awaitStart or await targets self, the current VM raises a script error.
await(10, self->talk()); // error
This is because if an Actor waits on itself, the wait can only finish if the waiting Actor itself makes progress.
Sending a Request to yourself with a plain request is possible.
When a Request is accepted, the sending Actor is recorded as the sender of the target Action.
actor NPC
{
action talk()
{
// sender is the Actor that Requested this Action
}
}
In some cases, such as the system Requests made when the VM starts, there is no sending Actor.
join does not send a new Request.
join(0, NPC);
It watches the Priority of the work the target Actor is already running, and waits until it is the given Priority or lower.
| Instruction | New Request | Waits |
|---|---|---|
request |
Sends one | No |
awaitStart |
Sends one | Until it can start |
await |
Sends one | Until it completes |
join |
Doesn't send one | Until the existing Priority is the given value or lower |
This page gathers the statements that control waiting, suspending and resuming Actions, and whether Requests are accepted.
yield();
Without ending the current Action, it suspends execution at that point and hands control back to the Mana VM.
The next time it gets a chance to run, it resumes after the yield().
print("step 1\n");
yield();
print("step 2\n");
yield() is not a timed wait. It is not guaranteed to mean "wait one frame" or "wait one second", and when it runs again also depends on how the host updates the VM.
join(priority, actorExpression);
Does not send a new Request; it waits for a target Actor that is already running.
join(0, NPC);
In the current VM, it waits until the target Actor's current Priority is the given Priority or lower.
Whereas await "sends a Request and waits for it to complete", join is an instruction for "waiting on an existing execution state".
rollback priorityExpression;
Example:
rollback 1;
rollback ends the current Action's execution and winds the Priority execution states saved in the Actor back towards the given value.
The current VM's Actor::Rollback releases the current Priority, removes any execution states left above the given value as needed, and then restores a saved Action that can resume. If no Action can resume, the Actor goes back to the stopped state.
The VM also uses the same Rollback mechanism internally when an Action ends normally.
rollback has a large effect on control flow, so use it when you want to explicitly roll back Priority execution states, rather than for ordinary sequential work.
halt();
Stops execution of the current Actor.
The current VM puts the Actor into the halt state and clears the interrupt execution states it holds. Actor::Request does not accept new Requests to a halted Actor.
Restarting the Actor on the VM side clears the halt state.
refuse();
Puts the Actor into the state of refusing Requests.
The current VM sets the Actor's Refused flag, and from then on does not accept new Requests that arrive.
It is not an instruction that clears execution states already registered.
comply();
Clears the state of refusing Requests set by refuse().
The current VM clears the Refused flag.
refuse();
// A section in which new Requests are refused
comply();
Syntax:
lock statement
It is normally combined with a block.
lock
{
// statements
}
The current compiler generates a NonPreEmptive instruction at the start of a lock and a PreEmptive instruction at its end. In the current VM, these turn on and off the Synchronized flag in the current Priority execution state.
Importantly, the current Actor::Request implementation does not refer to this Synchronized flag directly when deciding whether to accept a Request or whether a Priority interrupts.
So don't treat lock in the current implementation as meaning the same as a C++ mutex or "an atomic section that is never interrupted". In this reference, it is treated as syntax that, in the current implementation, switches a synchronised execution flag.
These instructions combine asking for an Action to run with waiting.
request(10, NPC->talk());
awaitStart(10, NPC->talk());
await(10, NPC->talk());
For details, see Request.
The Priority currently running is available through the predefined value priority.
print("%d\n", priority);
A module groups Actions and member definitions reused by several Actors.
A Module is not itself a unit of execution. An actor uses the definitions a Module contains by extending it.
module CommonActions
{
action greet()
{
print("Hello\n");
}
}
actor Villager
{
extend CommonActions;
}
The basic form of extend is:
extend ModuleName;
In the current grammar, a Module's body uses the same actions grammar as an Actor.
So a Module can contain definitions such as:
- Actions
- Member variables
- Constants
extend
module Talkable
{
int mTalkCount;
const int kTalkPriority = 10;
action talk()
{
mTalkCount = mTalkCount + 1;
}
}
A Module can be defined inside a namespace.
namespace Game::NPC
{
module Talkable
{
action talk()
{
}
}
}
It can be named by its fully qualified name.
actor Villager
{
extend Game::NPC::Talkable;
}
If the namespace is added to the places searched with using, the short name also works.
using Game::NPC;
actor Villager
{
extend Talkable;
}
A Module is included in the Program Image as definition information, but it is not created as a unit of execution when the VM loads, the way an ordinary Actor is.
So it is not for sending a request to the Module itself and running it independently; it is used to add shared definitions to Actors.
If a definition brought in by extend and a definition on the Actor side have the same name, the compiler's symbol resolution rules apply.
For Modules meant for reuse, we recommend not relying on overriding by same-named definitions, and using names that clearly separate the roles.
A phantom is a template that creates no instance when the VM starts, from which the C++ side creates Actors when it needs them.
phantom EnemyTemplate
{
int mHp;
action main()
{
}
action damage()
{
}
}
Grammatically its body has the same form as an actor, and can define Actions and members.
For an ordinary actor, the VM creates an instance when the Program Image is loaded and registers it in the list of Actors.
A phantom is not created in the list of Actors at load time. The VM keeps the Phantom's definition information and creates an Actor instance when the C++ side explicitly asks for one.
actor |
phantom |
|
|---|---|---|
| Created when the VM loads | Yes | No |
| Defines Actions | Can | Can |
| Has Actor variables | Can | Can |
| Main use | A resident unit of execution | A template for dynamic creation |
The current VM API uses CreateActorFromPhantom.
std::shared_ptr<mana::Actor> enemy =
vm->CreateActorFromPhantom("EnemyTemplate", "Enemy01");The first argument is the Phantom's definition name, and the second is the name of the Actor to create.
The created Actor is registered in the VM's list of Actors, and has the Actions and Actor variable area defined in the Phantom.
When the VM loads a Program Image, it sends init and main Requests to the ordinary Actors.
A Phantom has not been created as an Actor at that point, so it is not part of these Requests made all at once at load time.
How a dynamically created Actor is initialised and started should be designed together with the C++ code that creates it and the way it uses the VM API.
The current language has no syntax for instantiating a Phantom directly from a Mana script.
Creating Phantoms is the responsibility of the host's C++ API.
If a Phantom name that doesn't exist is passed to CreateActorFromPhantom, the VM raises a Phantom not found runtime error.
A namespace organises the names of Actors, Modules, Structs, Functions, variables, constants and so on into a hierarchy.
namespace Game::AI
{
actor Enemy
{
action think()
{
}
}
}
The parts of a fully qualified name are separated with ::.
Game::AI::Enemy
:: is the operator that qualifies a name with a namespace. Its role differs from ->, used in Action references.
request(1, Game::AI::Enemy->think());
using Game::AI;
actor Controller
{
action main()
{
request(1, Enemy->think());
}
}
using Game::AI; adds Game::AI::Enemy as a candidate when the unqualified name Enemy is resolved.
In the current implementation, using can resolve not only a namespace but also an Actor / Module.
namespace Game::AI
{
actor Enemy
{
action think()
{
}
}
}
using Game::AI::Enemy;
In this case the last name, Enemy, is added to the current scope as an alias.
In the current implementation, the symbols using can target are limited to Actors / Modules. It is not treated as syntax for using Structs, ordinary Functions and so on in the same way.
using affects name resolution in the namespace scope its declaration belongs to.
When you leave the namespace, the using scope added inside it also ends.
Mana analyses all symbols and namespaces after parsing, so a using can refer to namespaces and Actors / Modules defined later.
using Game::AI;
actor Controller
{
action main()
{
request(1, Enemy->think());
}
}
namespace Game::AI
{
actor Enemy
{
action think()
{
}
}
}
If several candidates are found for the same unqualified name, the compiler reports an ambiguous reference as an error.
Typical diagnostics include:
ambiguous usingambiguous symbol referenceambiguous type referenceambiguous actor referenceunresolved using
When a name is ambiguous, use the fully qualified name.
Splitting source into files does not create namespaces automatically.
- File: a unit for organising source code physically
- namespace: a unit for organising names logically
You can combine several files into one Program Image and still avoid name clashes with namespaces.
native is a declaration for calling, from a Mana script, an external function registered on the C++ side.
native int nativeAdd(int a, int b);
A native function has only a declaration; you don't write a body on the Mana side.
actor Main
{
action main()
{
int value = nativeAdd(10, 20);
print("%d\n", value);
}
}
At run time, the VM looks up the registered C++ function by the function's name and calls it.
On the C++ side it can be registered with, for example, RegisterFunction.
vm->RegisterFunction("nativeAdd", &OnNativeAdd);native can also be declared as a Struct member.
struct Vec
{
float x;
float y;
native void normalize();
}
void update(Vec value)
{
value.normalize();
}
A Struct's native method is resolved as an external function named in the form StructName::methodName.
Vec::normalize
The VM's external function callback receives a pointer to the Struct instance, in addition to the running Actor.
native return-type functionName(arguments...);
Inside a Struct, the form is:
struct TypeName
{
native return-type methodName(arguments...);
}
An ordinary Mana Function runs by branching to the instructions the Mana Compiler generated.
A native Function has no body on the Mana side; at run time a registered external function is looked up by name.
The current VM's basic callback type has this form:
std::function<void(const std::shared_ptr<mana::Actor>& actor,
void* structPointer)>Arguments and return values are passed through the Actor's API for external functions and the VM stack. The details are covered in Native Functions in Integration.
If no external function with that name is registered with the VM, the VM reports that the external function cannot be found as an error.
Make the declaration on the script side match the name registered on the C++ side.
native is the boundary between C++ and Mana. The declaration on the Mana side and the way the C++ side handles arguments and return values must match.
In particular, a Struct native method receives a pointer to the Struct instance, unlike an ordinary global native function.
Mana source files normally use the .mn extension.
In a large program, you can bring several source files into one compilation with import or include.
import "npc.mn";
import reads the given source. If a source with the same resolved path has already been read, the current Lexer skips reading it a second time or more.
So import is recommended for ordinary splitting into files.
main.mn
├─ import "npc.mn"
└─ import "event.mn"
The definitions brought in are combined into the same compilation result, and one Program Image is produced.
include "common.mn";
include also reads the given source, but unlike import it does not prevent reading the same file more than once.
If you include the same file several times, the same declarations are analysed several times, which can cause duplicate definition errors.
Normally use import, and consider include only when you deliberately need to read the same source again.
The path of the file to read is resolved by the SourceResolver.
With the standard file-based use, relative paths can be resolved from the location of the source file currently being read.
project/
├─ main.mn
└─ actors/
└─ npc.mn
import "actors/npc.mn";
When a custom SourceResolver is used in an embedding, the actual path resolution rules depend on that implementation.
Mana builds the syntax tree including the sources brought in, and then performs semantic analysis of symbols and namespaces.
So even if Actors, Modules, namespaces and so on are split across files, the design does not stop you referring to a resolvable name just because of the order of definition.
// main.mn
import "enemy.mn";
actor Controller
{
action main()
{
request(1, Enemy->think());
}
}
// enemy.mn
actor Enemy
{
action think()
{
}
}
Namespaces are never generated automatically from file names or directory structure.
Creating a file named
actors/enemy.mn
does not automatically make it actors::Enemy.
To organise names logically, use namespace explicitly.
The mana command has -I filename, which forces a file into the compilation without changing the source code.
mana -I common.mn main.mn-I can be given several times.
Mana has predefined symbols for referring to the context of the running Actor and Request.
| Name | Type / kind | Meaning |
|---|---|---|
self |
Actor |
The Actor currently running |
sender |
Actor |
The Actor that Requested the current Action |
priority |
int |
The Priority of the Action currently running |
this |
The Struct receiver | A reserved word for referring to the current Struct instance in a Struct member Function |
Nil |
Nil |
A special value that represents an empty reference |
actor Worker
{
action main()
{
request(1, self->update());
}
action update()
{
}
}
self represents the current Actor.
It can be used, for example, to send a Request to yourself from an Action or from a Function running on the Actor.
actor Receiver
{
action receive()
{
request(1, sender->reply());
}
}
sender represents the Actor that Requested that Action.
However, for system Requests the VM itself sends at startup, such as init / main, there is no sending Actor. Don't assume that sender always points to a valid Actor.
actor Worker
{
action work()
{
print("%d\n", priority);
}
}
priority gets the Priority of the Action currently running, as an int.
Use it when you want to check the Priority given in the Request, and which interrupt level the code is currently running at.
this is a reserved word for referring to the current Struct instance in a Struct's member Function.
Inside the compiler it is resolved as the receiver identifier this. It is not for general self-reference to an Actor in ordinary global Functions or Actions. To refer to the Actor itself, use self.
Nil is a special value that represents an empty reference.
Nil
The leading N is upper case. The current Lexer recognises Nil as a reserved word, and does not treat nil as the same token.
Nil has a type of its own, different from ordinary numeric constants, and cannot be used in constant expressions.
true and false can also be used as Boolean literals.
bool visible = true;
bool finished = false;
These are literals of type bool.
mana is the command-line tool that compiles Mana source, writes out Program Images, and runs Program Images.
mana [options] input
mana main.mnIf no output file is given, it compiles the source and then runs that Program Image on the Mana VM straight away.
mana main.mn -o game.mxWith -o filename, the compiled Program Image is saved to a file and is not run automatically.
The file extension can be anything.
In the current CLI, if -o is given without a value, it generates a .mx file name with the same base name as the input source.
mana main.mn -oIn this case the output is main.mx.
mana --execute game.mxWith --execute, the input is read as a compiled Program Image rather than Mana source, and run on the VM.
The current driver/Main.cpp does not implement a short form -e.
mana main.mn -t public_types.h-t filename writes the public type declarations the compiler generates to a file.
If -t is given without a value, the output is a .h with the same base name as the input source.
mana main.mn -tIn this case main.h is generated.
mana -I common.mn main.mn-I filename forces the given file into the compilation.
It can be given several times.
mana -I common.mn -I platform.mn main.mnmana --help
mana --version
mana --copyright| Option | What it does |
|---|---|
--help |
Shows how to use it |
--version |
Shows Mana's version |
--copyright |
Shows the copyright notice |
| Option | What it does |
|---|---|
-o filename |
Where to write the Program Image |
-t filename |
Where to write the C++ type declaration header |
-I filename |
Forced include. Can be given several times |
--execute |
Runs the input as a Program Image |
--help |
Shows help |
--version |
Shows the version |
--copyright |
Shows the copyright notice |
If compilation fails or an output file cannot be saved, the CLI returns an exit code that signals failure.
In build scripts and CI, check the exit code and the compiler's diagnostics.
このマニュアルは shun126/Mana の documents/wiki/ から自動生成しています。Wiki を直接編集しても次の公開で上書きされるため、修正はリポジトリへの Pull Request でお願いします。
This manual is generated from documents/wiki/ in shun126/Mana. Edits made on the Wiki itself are overwritten on the next publish, so please send changes as pull requests to the repository.