Skip to content

Concepts

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

Concepts

🌐 English · 日本語

Contents

Mana Concepts

In the Tutorial, you learned how to use Mana by writing real code.

This section goes one step deeper. It explains why Mana is shaped around Actor / Action / Request, and how several Actions cooperate while they run.

To look up syntax, see the Language Reference. To learn by writing code, see the Tutorial.

Reading order

  1. Thinking actor-oriented
  2. The roles of Actor and Action
  3. Request and Priority
  4. Mana's execution model
  5. Module
  6. Phantom
  7. Namespace
  8. Compiler and VM

Once you have read these, you will have a grasp of Mana's main execution concepts and of the whole path from source code to running on the VM.

Next, the Language Reference lets you look up each construct and feature precisely.

Thinking actor-oriented

Mana is a scripting language for thinking of the many processes in a game as independent units of execution called Actors.

Rather than packing NPCs, doors, event flow, enemy AI, effects and so on into one huge routine, you give each of them a role and have them cooperate by Requesting Actions when needed.

What problem it solves

In a game, many processes appear to run at the same time.

For example:

  • An NPC talks
  • An enemy chases the player
  • A door opens
  • Event management remembers how far things have got
  • An effect starts

If you gather all of these into one long routine, the needs of one process easily affect another, and managing state gets complicated.

In Mana, you split Actors by role.

EventController
 ├─ Guide
 ├─ Gate
 └─ Guard

Each Actor has its own state and Actions.

Actors are not only characters

The name Actor may suggest that it only represents game characters.

But an Actor in Mana is the basic unit of execution that runs Actions independently.

For example, all of these can be Actors:

  • NPCs
  • Enemies
  • Gimmicks such as doors and switches
  • Event flow management
  • Scene control
  • Conversation management
  • Effect management

What matters is not whether it is a visible object, but whether you want to give it its own responsibility and behaviour.

Actors cooperate through Requests

When an Actor wants another Actor to do something, in Mana it does not call the Action directly like a function; it Requests it.

request(3, Guard->move());

This way, the side that asks does not need to know the details of how Guard works inside.

EventController
      │
      │ Request
      ▼
    Guard
      │
      └─ move Action

The central idea of Mana is to separate "who does what" into Actors and Actions, and to arrange "when it runs" with Requests and Priority.

Independent, in shared time

The Mana VM advances several Actors in turn.

So it does not create an operating-system thread for each Actor. Each Actor has its own execution state and moves a little at a time as the VM advances, so from the game's point of view several processes can be treated as progressing in parallel.

This documentation describes this property as cooperative pseudo-parallel execution.

Keep it apart from real CPU parallelism or multithreading.

How it relates to the general Actor Model

Mana is designed around Actors, but it is not a direct implementation of the academic Actor Model.

Mana has mechanisms of its own, for example:

  • Action
  • Request
  • Priority
  • Interrupting and resuming Actions
  • Global variables
  • Cooperative execution by the Mana VM

So rather than thinking of Mana's actor as "exactly the same as an actor in the general Actor Model", it is more accurate to think of it as Mana's own Actor, for splitting game processing into independent units of execution.

When to make something an Actor

Splitting everything into Actors is not the goal.

Actors suit processing such as:

  • It has its own state
  • Other code asks it to do things
  • It moves at different times from other processing
  • You want to handle interruption by Priority
  • It is easy to name as a responsibility in the game

Simple calculations and shared routines, on the other hand, are more natural as Functions.

Actor / Action
    "Who does what" in the game

Function
    Calculations and shared routines used inside Actions

Why think actor-oriented

Splitting responsibilities by Actor makes it easier to see game processing like this:

The NPC talks
The Door opens
The Guard moves
The EventController Requests them

What sets Mana apart is that you can see code not as "a long list of instructions" but as a structure in which several units of execution move the game forward by asking each other for work.

What to remember so far

  • Mana splits processing around Actors
  • Actors are not limited to characters
  • An Actor has state and Actions
  • Actors cooperate through Requests
  • Mana's Actor is not identical to the academic Actor Model
  • The VM advances several Actors cooperatively

Read next

Next, we look closely at how the Actions an Actor has differ from ordinary Functions.

The roles of Actor and Action

The roles of Actor and Action

Mana thinks of Actors and Actions separately.

An Actor is a unit of execution that holds state, and an Action is a behaviour that Actor can perform.

actor Guard
{
    bool mAlert;

    action watch()
    {
    }

    action move()
    {
    }
}

In this example, Guard is the Actor, and watch and move are Actions.

An Actor holds state

An Actor's member variables represent the state the Actor holds. Actions belonging to that Actor can refer to those variables directly.

actor Guide
{
    int mTalkCount;

    action init()
    {
        mTalkCount = 0;
    }
}

The Actor itself remains after an Action ends, so the state can still be referred to when another Action runs next.

This lets you represent, for example:

  • How many times someone has talked
  • Whether a door is open
  • Whether an NPC is on alert
  • How far an event has progressed

An Action is a behaviour others can request

An Action is what a Request targets.

request(3, Guard->move());

Here Guard->move() is a reference to an Action.

Putting the Action inside the Actor makes it clear in the code whose responsibility that behaviour is.

Guard
 ├─ watch
 ├─ move
 └─ talk

How it differs from a Function

Actions and Functions can both group work, but their roles differ.

A Function is called normally from the current work, and returns to the caller when it ends.

int clampHp(int hp)
{
    if (hp < 0)
        return 0;
    return hp;
}

An Action, on the other hand, takes part in the Actor's execution model and is affected by Request and Priority.

request(5, Enemy->damage());

Roughly, they divide like this:

Function Action
Calculations and shared routines A behaviour of an Actor
Called normally Asked for with a Request
Returns to the caller Can be interrupted or held back by Priority
Also usable outside Actors Belongs to an Actor

It helps to think of "calculate HP" as a Function and "the enemy takes damage" as an Action.

Does only one Action exist at a time?

One Actor can have several Action definitions.

Also, at run time, Requests with different Priorities can arrive at the same Actor.

For example, with this state:

priority 1 : patrol
priority 5 : damage

damage interrupts patrol.

After damage ends, patrol can go back to the execution position that was saved and carry on.

So an Action is not just "a member function of the Actor"; it is also a unit of the Actor's execution state, including suspension and resumption.

Sharing an Actor's state between Actions

Actions can access the same Actor's member variables.

actor Door
{
    bool mOpened;

    action init()
    {
        mOpened = false;
    }

    action open()
    {
        if (mOpened)
            return;

        mOpened = true;
        print("Door opened\n");
    }
}

Keeping an Actor's state close to its Actions like this lets you describe "the thing that holds state" and "the behaviour that changes that state" as one unit.

init and main

For an ordinary Actor, the VM automatically Requests init and main after loading the program.

init can be used to set up the initial state, and main to start the Actor's basic behaviour.

actor NPC
{
    int mState;

    action init()
    {
        mState = 0;
    }

    action main()
    {
        print("NPC started\n");
    }
}

However, not every Actor has to have both. You define only the Actions you need.

Don't give an Action too much responsibility

If you pack the whole game's processing into one Action, you lose the benefit of splitting things into Actors.

For example, rather than doing all of this directly:

EventController.main
    The NPC's conversation
    The Door's animation
    The Guard's movement
    Playing sound effects
    Saving state

splitting the responsibilities like this suits Mana's design better:

EventController
    ├─ Request Guide->talk()
    ├─ Request Gate->open()
    └─ Request Guard->move()

How to think when designing Actors and Actions

First, put the responsibilities in the game into words.

Who?          Does what?
Guide         talk
Gate          open
Guard         move
Event         start

Making "who" the Actor and "does what" the Action tends to give a natural structure.

What to remember so far

  • An Actor is a unit of execution that holds state
  • An Action is a behaviour an Actor can perform
  • An Action is what a Request targets
  • A Function is ordinary processing; an Action takes part in the Actor's execution model
  • An Action can be suspended and resumed because of Priority
  • An Actor's member variables can be shared by several Actions

Read next

The mechanism for having an Actor run an Action is the Request. Next, we sort out how it behaves together with Priority.

Request and Priority

Request and Priority

In Mana, you use a Request when you want an Actor to run an Action.

request(3, Guard->move());

This expression asks the Guard Actor to run its move Action at Priority 3.

A Request is not a function call

An ordinary function call waits on the spot until the called work finishes.

A Request is different: it registers the Action in the target Actor's execution state.

EventController
      │
      │ request(3, Guard->move())
      ▼
    Guard
      │
      └─ priority 3 : move

With a plain request, the side that issued the Request does not wait for the target Action to finish.

When you need to wait for the target Action to start or finish, use awaitStart or await.

Priority expresses how important an Action is

In Mana, the larger the number, the higher the Priority.

For example, suppose Guard is running patrol at Priority 1, and damage is Requested at Priority 5.

priority 5 : damage   ← runs
priority 1 : patrol   ← suspended

The higher-Priority Action runs first, and the original Action is suspended, keeping its state partway through.

When damage ends, patrol can go back to the saved position and carry on.

A lower-Priority Request

A Request with a lower Priority than the Action currently running does not run right away.

Current
priority 5 : battle

New Request
priority 2 : talk

Here talk is kept as the candidate to run at Priority 2, and becomes ready to run after the Priority 5 work has finished.

So Priority is not just a number for sorting; it is the mechanism that decides which Action runs now and which Action waits, within an Actor.

Only one per Priority

In the current Mana implementation, one Actor cannot hold several Requests at the same Priority.

If a Request at Priority 3 is already registered and you Request a different Action at Priority 3, the new Request is not accepted.

Guard
priority 3 : talk

request(3, Guard->move())
        ↓
Not accepted, because the same Priority is in use

So a Priority acts not only as "importance" but also as something like an execution slot inside the Actor.

Rather than giving Priorities very fine-grained numbers, it is easier to manage if the game decides on levels that mean something and uses those.

For example, you can decide on uses like these:

1 : Normal behaviour
3 : Conversations and events
5 : Reacting to damage
8 : Forced cutscenes

What matters is not the numbers themselves but agreeing on what they mean within the project.

When a Request is not accepted

A Request does not always succeed.

In the current implementation, a Request is not accepted in cases such as these:

  • The Priority given is at or below the lowest Priority that can be used
  • The Actor is halted
  • The Actor is refusing Requests
  • The same Priority is already in use
  • The Action given does not exist

An ordinary Mana script has no syntax for receiving whether a Request succeeded as a return value. But to understand the execution model, it helps to remember that "a Request is a request; it does not always start a new Action".

sender

In an Action that received a Request, sender refers to who sent that Request.

actor Guard
{
    action talk()
    {
        if (sender == Guide)
        {
            print("Guide requested talk\n");
        }
    }
}

This lets the same Action behave differently depending on who Requested it.

Requests and waiting

Choose among the Request instructions according to the purpose.

Syntax What the caller does
request Carries on after asking
awaitStart Waits until the Action at the given Priority has reached the point where it can run
await Waits until the Action at the given Priority completes
join Waits until the target Actor's Priority is the given value or lower

request suits loosely coupling Actors, and await suits making the order of an event explicit. However, the await instructions carry on without waiting if the request is not accepted. Even after it is accepted, they wait on a condition about the target Actor's Priority, so check the exact release conditions in the Request reference.

Priority is managed per Actor

Priority does not create one table of execution order for the whole game.

Each Actor manages the Requests it has received and its current Priority.

Guide
priority 3 : talk

Guard
priority 5 : damage
priority 1 : patrol

Gate
priority 2 : open

Each Actor has its own execution state, and the VM advances them in turn.

So there is no simple global ranking such as "Guard's Priority 5 runs before Guide's Priority 3".

Priority decides how Actions interrupt and are held back within the same Actor.

Why use Priority

In a game, some work should take precedence even if it means interrupting what is happening now.

For example:

Walk
  ↓
Spot an enemy
  ↓
Fight
  ↓
Take damage
  ↓
Return to fighting

Expressing this with nothing but lots of state branches makes it complicated to manage returning to the original work.

In Mana, keeping the execution state per Priority gives a structure that can return to the Action that was interrupted.

What to remember so far

  • A Request asks an Actor to run an Action
  • A plain request does not wait for it to finish
  • The larger the Priority, the higher it is
  • A higher Priority can interrupt the current Action
  • A lower Priority is kept to run later
  • The same Priority on the same Actor cannot hold several Requests
  • Priority is managed per Actor
  • The Requester can be referred to through sender

Read next

Next, we put together the overall picture of how these Requests and Priorities run inside the Mana VM.

Mana's execution model

Mana's execution model

Mana's execution model is based on several Actors, each with its own independent execution state, advanced cooperatively by the Mana VM.

It does not create an operating-system thread for each Actor.

From the game's point of view several Actors can be treated as running at the same time, but the VM runs the Actors in turn.

The overall picture

flowchart LR
    VM["Mana VM"] --> A["Actor A"]
    VM --> B["Actor B"]
    VM --> C["Actor C"]

    A --> A1["Action / Priority state"]
    B --> B1["Action / Priority state"]
    C --> C1["Action / Priority state"]
Loading

Each Actor holds its own state: its Actions, Priorities, execution position, stack and so on.

The VM makes several processes cooperate by advancing each Actor.

The VM advances Actors in turn

The Mana VM's Run() runs the registered Actors in turn.

Conceptually, you can think of it like this:

VM Tick
 ├─ Advance Actor A
 ├─ Advance Actor B
 ├─ Advance Actor C
 └─ If needed, advance Actors that were newly Requested

This does not mean a CPU thread per Actor.

So Mana's concurrency is cooperative pseudo-parallel execution.

The game engine only needs to advance one VM at regular intervals, while on the Mana side the state of several Actors can be described independently.

An Actor has an execution position

When an Action has run partway and is interrupted by a higher-Priority Action, Mana keeps the original Action's execution position.

priority 1 : patrol
    line A
    line B   ← has run up to here

priority 5 : damage interrupts

At the interrupt, the original Action's execution position and stack state are saved.

Then, when damage ends, execution returns to the saved state.

priority 5 : damage
    ends
       ↓
priority 1 : patrol
    line C   ← carries on from where it stopped

This mechanism is the core of interruption by Priority in Mana.

Execution state is kept per Priority

An Actor does not hold just one "current Action"; it can keep an execution state for each Priority.

For example, it can hold this state:

priority 5 : damage   ← running now
priority 3 : talk     ← held back
priority 1 : patrol   ← suspended

When the higher-Priority Action ends, execution returns to the highest remaining Priority that can run.

This lets you split complex game behaviour into combinations of Actions and Priorities, instead of expressing it only as one giant state machine.

Ending and resuming Actions

When an Action reaches its end or executes return, that Action's Priority is released.

If there is a suspended Action below it, execution returns to its saved position.

flowchart TD
    A["priority 1: patrol"] -->|"priority 5 request"| B["priority 5: damage"]
    B -->|"damage ends"| C["priority 1: patrol resumes"]
Loading

If, on the other hand, no Action is left to return to, that Actor has nothing to run.

request adds execution state

request is not just a jump instruction.

request(5, Enemy->damage());

This is an operation that adds an execution state for an Action at a new Priority to the target Actor.

If the Priority is higher than the current one it interrupts immediately; if it is lower it is kept to run later. If an execution state already exists at the same Priority, the new Request is not accepted.

This is the big difference from an ordinary Function call.

The side that waits is also an Actor

With awaitStart, await and join, the calling Actor waits by re-evaluating the same instruction until its condition is met.

In other words, it does not "stop the whole VM and wait".

EventController : waiting for Guard to finish
Guard           : running move
Guide           : can get on with another Action

Even while one Actor is waiting, the other Actors can advance.

This property suits event flow and synchronising several characters.

The role of yield

yield() hands over execution of the current Action once, at that point.

Use it when you don't want to push a long piece of work through all at once, and want to pass it on to the VM's next step.

action update()
{
    // Some work
    yield();

    // Continue on the next step
}

The detailed scheduling rules are covered in the Language Reference; in Concepts, think of it as "the mechanism by which an Actor gives up its turn by itself".

rollback winds Priority back

Normally, when the current Action ends, execution returns to the previous Action one level at a time.

With rollback, you can discard all execution states above a given Priority at once and return to the state at a lower Priority.

You can use this for control such as:

  • Cancelling a behaviour
  • Ending a chain of interrupts all at once
  • Forcing a return to the basic behaviour

The exact boundary conditions and syntax are covered in the Language Reference.

refuse and lock

refuse() controls whether an Actor accepts new Requests. comply() resumes accepting them.

lock is somewhat different. The current compiler generates instructions that switch the synchronised execution state before and after a lock block, and the VM turns the Synchronized flag of the current Priority on and off.

However, the current Actor::Request does not directly use this flag when deciding whether to accept a Request or whether a Priority interrupts.

So don't think of the current lock as a mutex or as "an atomic section that can never be interrupted". The exact current behaviour is covered in the execution control reference.

init and main at startup

When a program is loaded, the VM creates the ordinary Actors, does the initialisation work, and then Requests each Actor's init and main.

Conceptually, the flow is:

Load the Program Image
    ↓
Create the Actors
    ↓
Global initialisation
    ↓
Queue each Actor's main at Priority 0
    ↓
Request each Actor's init at the highest Priority (2147483647)
    ↓
Normal VM execution

This lets an Actor describe its startup initialisation and its normal behaviour as Actions. However, there is no collective wait in which all Actors' init finish before any Actor's main starts. Each Actor runs its queued Actions in Priority order after its own init finishes. Where cooperation needs initialisation to have happened, design the order explicitly.

Mana's execution model in a nutshell

What makes Mana distinctive is not simply that "there are several Actors".

What matters is that the VM cooperatively advances this structure:

Actor
  ├─ Holds its own state
  ├─ Has Actions
  ├─ Receives Requests
  ├─ Holds execution state per Priority
  └─ Interrupts, waits and resumes

With this model, game processing such as "walk", "talk", "attack", "take damage" and "wait for an event" can be combined as independent Actions.

What to remember so far

  • The Mana VM advances several Actors in turn
  • Each Actor has its own independent execution state
  • Mana's concurrency is not parallel execution with operating-system threads
  • Action state can be kept per Priority
  • A higher-Priority Action can interrupt a lower-Priority Action
  • A new Request at a Priority is not accepted if that Priority already exists
  • After an Action ends, the suspended Action can resume
  • Waiting with the await instructions does not stop the whole VM
  • refuse controls whether new Requests are accepted
  • In the current implementation, lock switches a synchronisation flag, but does not directly prevent Requests from interrupting

Read next

This completes the overall picture of Mana's core: Actor / Action / Request / Priority and the VM's execution model.

Next come the concepts that support larger script structures: Module, Phantom and Namespace.

Module

A module groups Actions and member definitions that you want to reuse in several Actors.

In Mana, rather than writing similar Actions over and over in each Actor, you can define the shared part as a Module and extend it from the Actors that need it.

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

actor Villager
{
    extend CommonActions;
}

In this example, Villager takes in the definitions of CommonActions.

What a Module is for

A Module is not itself a unit of execution.

Unlike an actor, it is not instantiated when the VM starts and does not run Actions by itself; it is a unit of reuse that gives Actors shared features.

Conceptually, you can think of it like this:

module CommonActions
        |
        | extend
        v
actor Villager

module CommonActions
        |
        | extend
        v
actor Guard

One Module can be used by several Actors.

Think "reusing parts" rather than inheritance

The name extend may make you think of class inheritance in C++ or Java.

But rather than a mechanism for building class hierarchies, it is more accurate to understand a Mana Module as a part that adds shared definitions to an Actor.

For example, you can group these into Modules:

  • Actions for conversation
  • Shared reactions
  • Shared waiting routines
  • Behaviour shared by several kinds of NPC

It combines with namespace

A Module can also be defined inside a namespace.

namespace Game::NPC
{
    module Talkable
    {
        action talk()
        {
            print("Hello\n");
        }
    }
}

You can use it by its fully qualified name.

actor Villager
{
    extend Game::NPC::Talkable;
}

If a namespace is made searchable with using, you can also refer to it by its short name.

using Game::NPC;

actor Villager
{
    extend Talkable;
}

The current compiler resolves Module names through namespaces and using like this.

How Actor and Module differ

Actor Module
Is a unit of execution Yes No
Created as an Actor when the VM starts Yes No
Can define Actions Yes Yes
Is the one that gets extended Not normally Yes
Main purpose An independent unit of execution Reusing shared definitions

When to use a Module

A Module is useful when several Actors have behaviour that means the same thing.

On the other hand, if code merely looks a little similar, you don't necessarily need to split it into a Module.

It helps to judge by asking: "is this behaviour one role shared by several Actors?"

Definitions with the same name

If both the Actor itself and a Module define an Action or member with the same name, the compiler's symbol resolution rules apply.

We recommend not designing around overriding or precedence between same-named definitions, and instead choosing names that don't clash for each role you reuse.

For the syntax and constraints guaranteed by the current specification, see the Module reference.

Summary

  • A Module is a unit of reuse that gives Actors shared features
  • A Module is not itself a unit of execution
  • An Actor uses it with extend ModuleName;
  • It is easier to understand as a "part" added to an Actor than as class inheritance
  • It can be organised together with namespace / using

Next, we explain the Phantom, for creating Actors from a definition at run time.

Phantom

A phantom is a template for an Actor definition, for creating Actors at run time when they are needed.

Its syntax is very similar to an Actor's.

phantom EnemyTemplate
{
    action appear()
    {
        print("Enemy appeared\n");
    }
}

However, actor and phantom are handled differently when loaded into the VM.

Actors are created at startup

An ordinary actor is instantiated when the Program Image is loaded into the VM, and registered in the VM's list of Actors.

actor Guide
{
    action main()
    {
    }
}

Guide is treated as a unit of execution that lives for the whole program.

Phantoms are not created at startup

A phantom does not create an Actor instance when the VM starts.

The VM keeps the Phantom's definition and can create an Actor from it when the C++ side needs one.

Conceptually, the flow is:

Mana source
    |
    | phantom EnemyTemplate
    v
Program Image
    |
    v
Mana VM
    |
    | keeps only the definition
    |
    | C++: CreateActorFromPhantom(...)
    v
Run-time Actor

Creating it from C++

The current VM has an API for creating an Actor from a Phantom.

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

This can be developed into uses such as creating several run-time Actors from the same definition.

When to use it

Think of things created when the game needs them, for example:

  • Enemy characters
  • Temporary event Actors
  • Gimmicks placed dynamically
  • Spawned NPCs

It suits units of execution that don't need to exist from startup, defined in advance on the Mana side.

How Actor and Phantom differ

Actor Phantom
Definition can have Actions Yes Yes
Created automatically when the VM loads Yes No
Runs at normal startup Yes No
Created from C++ when needed Not required Its main use
Main purpose A resident unit of execution A template for dynamic creation

What about init / main?

Just defining a Phantom does not create an instance, so init / main do not run right after loading as they do for an ordinary Actor.

The detailed lifecycle after creation and how to use the C++ API are covered in Integration.

Creating one directly from a script

The current specification defines no syntax for instantiating a phantom directly from a Mana script.

Creating Phantoms is the job of the VM API on the C++ side.

This shows that Phantom is a feature close to the boundary between Mana and the game engine.

How it differs from Module

Module and Phantom are both different from an Actor itself, but their purposes differ greatly.

Module
  -> Reuses shared definitions in existing Actors

Phantom
  -> A definition for creating new Actors at run time

It is easy to tell them apart if you think of a Module as "a feature part" and a Phantom as "a template for creation".

Summary

  • A Phantom is a template for an Actor definition
  • It is not instantiated automatically when the VM loads
  • It can be created from C++ with CreateActorFromPhantom()
  • It can be used for dynamic enemies, NPCs, gimmicks and so on
  • The current specification has no syntax for creating one directly from a script

Next, we sort out the ideas behind Namespace, which organises names in large Mana programs.

Namespace

A namespace organises the names of Actors, Modules, structs and so on logically, and keeps the same short name from clashing.

You learned how to write one in the Tutorial. This page sorts out the role Namespace plays in Mana.

Files and namespaces are different things

Splitting into files is a way of organising source code physically.

A namespace is a way of organising the names in a program logically.

File
  -> Where the code is written

namespace
  -> Which area the name belongs to

For example, code written in npc.mn does not automatically go into an NPC namespace.

Giving names a meaningful hierarchy

namespace Game::Town
{
    actor Guard
    {
    }
}

namespace Game::Dungeon
{
    actor Guard
    {
    }
}

Both have the short name Guard, but their full names differ.

Game::Town::Guard
Game::Dungeon::Guard

This way, you can safely use the same role name in different contexts.

:: shows where a name belongs

Mana uses :: to qualify with a namespace.

request(10, Game::Town::Guard->talk());

->, on the other hand, expresses the relationship between an Actor and an Action.

Game::Town::Guard -> talk
^^^^^^^^^^^^^^^^^    ^^^^
Actor's name         Action

The two have different roles.

using helps with name lookup

Instead of writing the fully qualified name every time, you can add places to search with using.

using Game::Town;

actor EventController
{
    action main()
    {
        request(10, Guard->talk());
    }
}

using Game::Town; makes Game::Town one of the places searched when an unqualified name is resolved.

You can also bring in a specific Actor or Module as a name.

using Game::Town::Guard;

In the current implementation, using can target a namespace or an Actor / Module.

Names are resolved after parsing

The Mana compiler resolves names in the semantic analysis stage, after reading the source.

So code that defines a namespace after the using, like this, can still be resolved.

using Game::Town;

actor Controller
{
    action main()
    {
        request(1, Guard->talk());
    }
}

namespace Game::Town
{
    actor Guard
    {
        action talk()
        {
        }
    }
}

This works because the compiler does not simply fix names one line at a time from the top; it analyses meaning by looking at the whole compilation unit.

The same namespace across several files

A namespace is not confined to one file.

By using the same logical namespace from several sources, you can split a large game by role.

character.mn  -> Game::Character
npc.mn        -> Game::Character::NPC
enemy.mn      -> Game::Character::Enemy
event.mn      -> Game::Event

Making the file layout resemble the namespace layout can make things easier to follow, but the two don't have to be the same.

Don't add too many using declarations

using can make code shorter, but if several namespaces have symbols with the same name, a name can become ambiguous.

In that case, make your intent explicit with the fully qualified name.

request(10, Game::Town::Guard->talk());

Prefer making it clear which Actor you refer to over keeping it short.

A namespace is not a unit of execution

A namespace itself does not run on the VM, and it does not hold state the way an Actor does.

A namespace is purely a compile-time mechanism for organising and resolving names.

This is an important difference from Actors and Modules.

Summary

  • A namespace organises names logically
  • It is a separate mechanism from splitting into files
  • :: qualifies with a namespace
  • -> refers to an Actor's Action
  • using helps with name lookup
  • Names are resolved during semantic analysis, so forward references are possible
  • When a name is ambiguous, write the fully qualified name

Next, we sort out how Mana source runs, in terms of the relationship between the Compiler and VM.

Compiler and VM

Mana does not run source code directly as it is.

The Mana Compiler analyses the source code and produces a Program Image, and the Mana VM runs that Program Image.

Mana source (.mn)
        |
        v
Mana Compiler
        |
        v
Program Image
        |
        v
Mana VM
        |
        v
Running Actors / Actions

A scripting language can be compiled too

Even a scripting language can turn its source code into another form before running it.

Compiling, in Mana's sense, does not mean producing native machine code that the CPU runs directly.

It means producing a Program Image for the Mana VM.

What the Compiler does

The Compiler reads the source, analyses its syntax and meaning, and turns it into a form the VM can run.

Conceptually, the flow is:

Read the source
    ↓
Analyse the syntax
    ↓
Analyse names and types
    ↓
Detect errors
    ↓
Produce the Program Image

Mana can handle namespaces and forward references because it analyses the whole source at the compile stage instead of simply running it one line at a time from the top.

What a Program Image is

A Program Image is the compiled data that connects the Compiler and the VM.

It holds what the VM needs to run the program: Actors, Actions, instructions, constants and so on.

Source Code
    ↓ Compiler
Program Image
    ↓ VM
Runtime State

Source code and the state of running Actors are different things.

What the VM does

The Mana VM loads the Program Image and runs Actors and Actions.

Its main jobs are:

  • Managing Actors
  • Running Actions
  • Processing Requests
  • Interrupting and resuming by Priority
  • Executing VM instructions
  • Working with the C++ side

The request and await you used in the Tutorial also work through this VM's execution model.

When a Program Image is loaded

Ordinary actors are created on the VM when the Program Image is loaded.

After that, initialisation runs, and then each Actor's init and then main are started.

A phantom, on the other hand, is kept as definition information; no Actor instance is created at load time.

The CLI can do it all at once

mana source.mn

compiles the source and runs it on the VM straight away.

You can also write the Program Image out to a file.

mana source.mn -o program.bin

A compiled file can be run like this.

mana --execute program.bin

In terms of the internal roles, the differences are:

mana source.mn
    = Compile + Execute

mana source.mn -o program.bin
    = Compile

mana --execute program.bin
    = Execute

The Compiler and VM can be embedded

Mana's Compiler and VM are not confined to the command-line tool.

The Compiler can be embedded in a game or editor as a library, and the VM as a runtime.

Editor / Game Engine
        |
        +--> Mana Compiler
        |        |
        |        v
        |   Program Image
        |        |
        +--> Mana VM

The C++ API is covered in detail in Integration.

Compile time and run time

Because the Compiler and VM are separate, problems are also detected at separate stages.

The Compiler detects problems that can be decided before running, such as syntax, name resolution and types.

The VM handles problems that can only be decided while Actors and Actions are actually running.

This separation lets you catch what mistakes you can before running, while leaving control during the game to the VM.

Program Image compatibility

A Program Image is the executable format the Mana VM reads.

The current VM checks the signature, version, bit width and so on when loading it.

So treat a Program Image as a compiled format to be used with a matching Mana VM.

Summary

  • Mana is split into a Compiler and a VM
  • The Compiler turns .mn source into a Program Image
  • A Program Image is an executable format for the Mana VM
  • The VM runs Actors / Actions / Requests
  • The Compiler is responsible for analysis and error detection before running
  • The CLI can compile and execute together or separately
  • The Compiler and VM can be embedded in a C++ application

That completes the basic topics of Concepts.

Next, the Language Reference organises Mana's syntax and each language feature so you can look them up precisely.

Clone this wiki locally