-
-
Notifications
You must be signed in to change notification settings - Fork 4
Concepts
🌐 English · 日本語
- Mana Concepts
- Thinking actor-oriented
- The roles of Actor and Action
- Request and Priority
- Mana's execution model
- Module
- Phantom
- Namespace
- Compiler and VM
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.
- Thinking actor-oriented
- The roles of Actor and Action
- Request and Priority
- Mana's execution model
- Module
- Phantom
- Namespace
- 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.
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.
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.
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.
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.
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.
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.
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
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.
- 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
Next, we look closely at how the Actions an Actor has differ from ordinary Functions.
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'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 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
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.
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.
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.
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.
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()
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.
- 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
The mechanism for having an Actor run an Action is the Request. Next, we sort out how it behaves together with 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.
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.
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 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.
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.
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".
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.
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 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.
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.
- A Request asks an Actor to run an Action
- A plain
requestdoes 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
Next, we put together the overall picture of how these Requests and Priorities run inside the Mana VM.
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.
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"]
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 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.
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.
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.
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"]
If, on the other hand, no Action is left to return to, that Actor has nothing to run.
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.
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.
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".
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() 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.
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.
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.
- 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
-
refusecontrols whether new Requests are accepted - In the current implementation,
lockswitches a synchronisation flag, but does not directly prevent Requests from interrupting
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.
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.
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.
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
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.
| 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 |
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?"
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.
- 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.
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.
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.
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
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.
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.
| 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 |
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.
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.
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".
- 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.
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.
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.
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.
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.
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.
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.
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.
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 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.
- A namespace organises names logically
- It is a separate mechanism from splitting into files
-
::qualifies with a namespace -
->refers to an Actor's Action -
usinghelps 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.
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
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.
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.
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.
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.
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.
mana source.mncompiles 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.binA compiled file can be run like this.
mana --execute program.binIn 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
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.
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.
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.
- Mana is split into a Compiler and a VM
- The Compiler turns
.mnsource 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.
このマニュアルは 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.