Skip to content

Language Reference

github-actions[bot] edited this page Sep 30, 2026 · 7 revisions

Language Reference

🌐 English · 日本語

Contents

Mana Language Reference

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.

Basics

  1. Source code structure
  2. Types
  3. Variables
  4. Constants
  5. Expressions
  6. Operators
  7. Statements

Functions and data types

  1. Function
  2. Struct

The Actor execution model

  1. Actor
  2. Action
  3. Request
  4. Execution control

Structure and integration

  1. Module
  2. Phantom
  3. Namespace and using
  4. Native Function
  5. Source files and import / include
  6. Predefined symbols
  7. CLI

How the pages are written

Where possible, each page explains things in this order:

  1. Overview
  2. Syntax
  3. Behaviour
  4. Constraints
  5. Examples
  6. 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.

Source code structure

Mana source code is written in text files. The standard extension is .mn.

hello.mn

Comments

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.

Whitespace and line breaks

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;

Blocks

{ and } group several declarations or statements.

if (count > 0)
{
    print("positive\n");
}

Actors, Actions, Functions, namespaces and so on also use blocks.

Identifiers

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.

Integer literals

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 literals

Floating-point numbers are written with a decimal point.

1.0
0.5
12.25
1.0e3

String literals

A string is enclosed in ".

"Hello"
"Hello\n"

Escape sequences such as \n can be used inside a string.

Boolean values

Boolean values are written with these reserved words.

true
false

Nil

In the current implementation, the reserved word for an empty reference is Nil.

Nil

The N is upper case.

Name qualification and Action references

:: 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

Related topics

Types

Mana is a statically typed language. Variables, arguments, return values and so on have a type.

Basic types

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

void is not an ordinary variable type that holds a value; it mainly expresses "no return value".

void reset()
{
}

bool

bool holds true or false.

bool enabled = true;

In conditions you can use the results of comparisons and logical operations.

string

string holds a string of text.

string message = "Hello";

String literals are enclosed in ".

The Actor type

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.

Predefined compound types

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.

Struct types

You can create user-defined types with struct.

struct Position
{
    float x;
    float y;
}

Position p;

Structs are covered in detail in Struct.

pointer

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.

Type checking

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.

Related topics

Variables

A variable is a name for holding a value.

Declaration

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;

Assignment

count = 10;
speed = 2.0;

Compound assignment can also be used.

count += 1;
speed *= 2.0;

Global variables

Declared outside any Actor or Function.

int gScore = 0;

Global variables are initialised before the init of ordinary Actors runs.

Actor variables

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;
    }
}

Local variables

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 members

struct Position
{
    float x;
    float y;
}

actor PositionExample
{
    action main()
    {
        Position p;
        p.x = 10.0;
        p.y = 20.0;
    }
}

Fixed-length arrays

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.

allocate and static

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

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

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++.

Main scopes

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.

Related topics

Constants

Use const for values that don't change.

Syntax

const type name = constant-expression;

Examples:

const int kMaxCount = 10;
const float kSpeed = 2.5;
const bool kDebug = false;
const string kMessage = "Hello";

They cannot be assigned

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 is a constant expression

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.

Naming Priorities

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.

Using them as array sizes

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];
    }
}

About define / undef

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;.

Related topics

Expressions

An expression is syntax that makes, refers to, calculates or assigns a value.

Literals

10
1.5
true
"Hello"
Nil

Variable references

count
speed
mOpened

Arithmetic expressions

count + 1
speed * 2.0
(a + b) * c

Comparison expressions

count == 0
count != 0
count < 10
count >= 1

The result of a comparison can be used in conditions and so on.

Logical expressions

enabled && visible
ready || force
!finished

Assignment expressions

count = 10
count += 1
speed *= 2.0

The target of an assignment is something writable, such as a variable or a member.

The conditional operator

?: can be used.

int value = enabled ? 1 : 0;

Function calls

calculate(10, 20)

A Struct's member Function is called with ..

value.reset()

Member references

A Struct's members are accessed with ..

position.x

Array elements

Array elements are referred to with [].

values[index]

Action references

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.

Predefined values

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.

sizeof

A sizeof operator is provided.

What it applies to and what it gives are covered in detail in the operators reference.

Type checking

The Compiler checks that types agree in operations and assignments within expressions.

Related topics

Operators

This page lists the main operators available in Mana.

Arithmetic operators

Operator Meaning
+ Addition
- Subtraction
* Multiplication
/ Division
% Remainder
** Power

Examples:

int a = 10 + 2;
int b = 10 % 3;

Comparison operators

Operator Meaning
== Equal
!= Not equal
< Less than
<= Less than or equal
> Greater than
>= Greater than or equal

Logical operators

Operator Meaning
&& Logical AND
`
! Logical NOT
if (enabled && visible)
{
}

Bitwise operators

Operator Meaning
& AND
` `
^ XOR
~ NOT
<< Left shift
>> Right shift

Assignment operators

=
+=  -=  *=  /=  %=
&=  |=  ^=
<<= >>=

Examples:

count += 1;
flags |= 0x10;

Increment / decrement

The current grammar accepts both the prefix and the postfix forms.

++count;
--count;
count++;
count--;

The conditional operator

condition ? trueValue : falseValue

Example:

int sign = value >= 0 ? 1 : -1;

sizeof

In the current grammar, sizeof takes a type in brackets.

sizeof(int)
sizeof(Position)

It is not syntax that accepts an arbitrary expression.

The Action reference operator ->

-> 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++.

Namespace qualification ::

:: qualifies a name that includes a namespace.

Game::AI::Enemy

To go as far as an Action, combine the two.

Game::AI::Enemy->think()

Main precedence

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)
{
}

Related topics

Statements

This page lists the basic statements used in Mana.

Expression statements

Put ; after an expression.

count = count + 1;
update();

Blocks

{
    int count = 0;
    count += 1;
}

if / else

if (condition)
{
    print("true\n");
}
else
{
    print("false\n");
}

while

while (condition)
{
    update();
}

do / while

do
{
    update();
}
while (condition);

for

for (int i = 0; i < 10; ++i)
{
    print("loop\n");
}

loop

For a loop with no exit condition written as an expression, use loop.

loop
{
    update();
}

break

Leaves the current loop or switch.

while (true)
{
    if (finished)
        break;
}

continue

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

switch (value)
{
case 0:
    print("zero\n");
    break;

case 1:
    print("one\n");
    break;

default:
    print("other\n");
    break;
}

return

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.

goto and labels

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

print("Hello\n");

The Request family

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.

Controlling how Actions run

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.

Related topics

Function

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.

Syntax

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

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.

Return values

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.

Struct member Functions

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.

How it differs from an Action

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.

Native Functions

A Function connected to the C++ side is declared with native.

native void playSound(string name);

For details, see Native Function.

Related topics

Struct

A struct groups several values, and the Functions that work with them, into one type.

Syntax

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;
    }
}

Member variables

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 ..

Member Functions

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 member Functions

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.

How Struct and Actor differ

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

Related topics

Actor

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.

Syntax

actor ActorName
{
    members
}

Example:

actor NPC
{
    int mTalkCount;

    action talk()
    {
        mTalkCount++;
        print("Hello\n");
    }
}

Actor members

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.

Creation when the VM starts

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.

init and main

After loading the program, the Mana VM requests special Actions from the Actors.

The current VM sends every Actor, in this order:

  1. A Request for main at Priority 0
  2. A Request for init at 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.

The Actor type

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.

Namespace

An Actor can be defined inside a namespace.

namespace Game::NPC
{
    actor Shopkeeper
    {
        action talk()
        {
        }
    }
}

Its fully qualified name is Game::NPC::Shopkeeper.

Actors are not limited to characters

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

Related topics

Action

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.

Syntax

action actionName()
{
    statements
}

Example:

actor NPC
{
    action talk()
    {
        print("Hello\n");
    }
}

Arguments and return values

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.

Running an Action

An Action can be run with request, awaitStart, await and so on.

request(1, NPC->talk());

NPC->talk() is an Action reference.

-> and ::

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

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.

Predefined values while an Action runs

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.

Priority and suspension

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.

How it differs from a Function

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

Related topics

Request

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.

request

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.

Action references

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.

How request behaves

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.

When a Request is not accepted

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

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

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());

When an await request is not accepted

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.

Awaiting yourself

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.

sender

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.

How it differs from join

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

Related topics

Execution control

This page gathers the statements that control waiting, suspending and resuming Actions, and whether Requests are accepted.

yield()

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

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

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()

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()

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()

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();

lock

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.

request / awaitStart / await

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.

Getting the Priority

The Priority currently running is available through the predefined value priority.

print("%d\n", priority);

Related topics

Module

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.

Syntax

module CommonActions
{
    action greet()
    {
        print("Hello\n");
    }
}

actor Villager
{
    extend CommonActions;
}

The basic form of extend is:

extend ModuleName;

What a Module can contain

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;
    }
}

Modules inside a namespace

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 not an Actor

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.

Name clashes

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.

Related topics

Phantom

A phantom is a template that creates no instance when the VM starts, from which the C++ side creates Actors when it needs them.

Syntax

phantom EnemyTemplate
{
    int mHp;

    action main()
    {
    }

    action damage()
    {
    }
}

Grammatically its body has the same form as an actor, and can define Actions and members.

How it differs from an Actor

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

Creating it from C++

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.

How it differs from automatic startup

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.

Creating one from a script

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.

Errors

If a Phantom name that doesn't exist is passed to CreateActorFromPhantom, the VM raises a Phantom not found runtime error.

Related topics

Namespace and using

A namespace organises the names of Actors, Modules, Structs, Functions, variables, constants and so on into a hierarchy.

namespace

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());

Adding a namespace to the search with using

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.

using an Actor / Module

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.

Scope

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.

Forward references

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()
        {
        }
    }
}

Ambiguous names

If several candidates are found for the same unqualified name, the compiler reports an ambiguous reference as an error.

Typical diagnostics include:

  • ambiguous using
  • ambiguous symbol reference
  • ambiguous type reference
  • ambiguous actor reference
  • unresolved using

When a name is ambiguous, use the fully qualified name.

Files and namespaces are different concepts

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.

Related topics

Native Function

native is a declaration for calling, from a Mana script, an external function registered on the C++ side.

Global native functions

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 methods on Structs

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.

Declaration syntax

native return-type functionName(arguments...);

Inside a Struct, the form is:

struct TypeName
{
    native return-type methodName(arguments...);
}

How it differs from an ordinary Function

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 registration type on the C++ side

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.

When the external function cannot be found

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.

Points to note

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.

Related topics

Source files and import / include

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

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

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.

Resolving paths

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.

Forward references

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()
    {
    }
}

Independent of namespaces

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.

Forced includes in the CLI

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.

Related topics

Predefined symbols

Mana has predefined symbols for referring to the context of the running Actor and Request.

List

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

self

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.

sender

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.

priority

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

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

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 / false

true and false can also be used as Boolean literals.

bool visible = true;
bool finished = false;

These are literals of type bool.

Related topics

CLI

mana is the command-line tool that compiles Mana source, writes out Program Images, and runs Program Images.

Basic form

mana [options] input

Compile and run

mana main.mn

If no output file is given, it compiles the source and then runs that Program Image on the Mana VM straight away.

Save the Program Image

mana main.mn -o game.mx

With -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 -o

In this case the output is main.mx.

Run a Program Image

mana --execute game.mx

With --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.

Generate a C++ type declaration header

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 -t

In this case main.h is generated.

Forced includes

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.mn

Showing information

mana --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

List of public options

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

Exit codes

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.

Related topics

Clone this wiki locally