-
-
Notifications
You must be signed in to change notification settings - Fork 4
Language Reference ja
🌐 English · 日本語
- Mana 言語リファレンス
- ソースコードの構造
- 型
- 変数
- 定数
- 式
- 演算子
- 文
- Function
- Struct
- Actor
- Action
- Request
- 実行制御
- Module
- Phantom
- Namespace と using
- Native Function
- ソースファイルと import / include
- 定義済みシンボル
- CLI
このセクションは、Mana の構文や言語機能を正確に調べるためのリファレンスです。
Tutorial は「順番に学ぶ」ため、Concepts は「なぜそう動くのか」を理解するための資料です。Language Reference では、用途ごとに構文・制約・例を確認できることを優先します。
各ページでは、可能な限り次の順で説明します。
- 概要
- 構文
- 動作
- 制約
- 例
- 関連項目
このリファレンスは現行コンパイラ、VM、テストを基準にします。旧 Primer に残っている古い構文より、現在の実装を優先します。
Mana のソースコードはテキストファイルに記述します。標準的な拡張子は .mn です。
hello.mn
1行コメントは // です。
// 1行コメント
print("Hello\n");
複数行コメントは /* と */ で囲みます。
/*
複数行の
コメント
*/
現在の Lexer では、ブロックコメントの入れ子はエラーになります。
空白とタブは、トークンを区切るために使います。改行も通常は文法上の区切りにはなりません。
文の終端には、多くの場合 ; を書きます。
int count = 0;
count = count + 1;
{ と } で複数の宣言や文をまとめます。
if (count > 0)
{
print("positive\n");
}
Actor、Action、Function、namespace などでもブロックを使います。
現在の Lexer では、識別子の先頭には英字、_、? を使用でき、その後には数字も使用できます。
例:
count
_count
Enemy01
?temporary
ただし、可読性のため通常のコードでは英字と _ を中心に使うことを推奨します。
予約語は識別子として使用できません。
10進整数はそのまま記述します。
0
10
1234
16進整数は 0x で始めます。
0x10
0xFF
2進整数は 0b で始めます。
0b1010
0b1111_0000
現在の Lexer では、2進表記中の _ を区切りとして使用できます。
浮動小数点数は小数点を含む形で記述します。
1.0
0.5
12.25
1.0e3
文字列は " で囲みます。
"Hello"
"Hello\n"
\n などのエスケープシーケンスを文字列内で使用できます。
真偽値は次の予約語で記述します。
true
false
現在の実装では、空の参照を表す予約語は Nil です。
Nil
N は大文字です。
namespace の区切りには :: を使います。
Game::AI::Enemy
Action 参照には -> を使います。
Enemy->think()
この2つは役割が異なります。
-
::: 名前空間の修飾 -
->: Actor の Action 参照
Mana は静的に型を持つ言語です。変数、引数、戻り値などには型を指定します。
現在の Lexer で認識される基本型は次のとおりです。
| 型 | 概要 |
|---|---|
void |
戻り値を持たない Function などで使用 |
char |
8 bit 整数型 |
short |
16 bit 整数型 |
bool |
真偽値 |
int |
32 bit 整数型 |
float |
32 bit 浮動小数点型 |
string |
文字列型 |
pointer |
低レベルな参照を扱う型 |
例:
int count = 10;
float speed = 2.5;
bool opened = false;
string name = "Guard";
void は値を保持する通常の変数型ではなく、主に「戻り値がない」ことを表します。
void reset()
{
}
bool は true または false を扱います。
bool enabled = true;
条件式では比較結果や論理演算の結果を使用できます。
string は文字列を扱います。
string message = "Hello";
文字列リテラルは " で囲みます。
Actor への参照を Function や native Function の引数として扱う場合、Actor 型を使用します。小文字の actor は Actor 宣言専用です。
void notify(Actor target)
{
}
self や sender なども Actor 参照として扱われます。
現行コンパイラは、よく使う値型として次の型をあらかじめ登録しています。
| 型 | メンバー |
|---|---|
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
|
これらは Struct と同様にメンバーを . で参照できます。
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 は現行コンパイラでは定義済み型として登録されていません。
struct でユーザー定義型を作成できます。
struct Position
{
float x;
float y;
}
Position p;
Struct の詳細は Struct で扱います。
pointer は主に VM と native Function の境界など、低レベルな用途で使用する型です。
通常のゲームイベント記述では、まず int、float、bool、string、Actor、定義済み複合型、Struct 型を中心に使用することを推奨します。
Mana Compiler は、代入、Function 呼び出し、戻り値、演算などで型の整合性を確認します。
型が合わないコードは、原則としてコンパイル時に診断されます。
変数は値を保持するための名前です。
int count;
float speed;
bool opened;
初期値を同時に指定できます。
int count = 0;
float speed = 1.5;
bool opened = false;
count = 10;
speed = 2.0;
複合代入も使用できます。
count += 1;
speed *= 2.0;
Actor や Function の外側で宣言します。
int gScore = 0;
グローバル変数の初期化は、通常の Actor の init より前に実行されます。
Actor 変数は Actor の中、Action の外側へ宣言します。その Actor に属するすべての Action から読み書きでき、値は Action 間で Actor の状態として保持されます。
actor Door
{
bool mOpened;
action init()
{
mOpened = false;
}
}
Action や Function のブロック内で宣言します。
actor Counter
{
action main()
{
int count = 0;
count = count + 1;
}
}
ローカル変数は、その処理中に使う一時的な値です。
struct Position
{
float x;
float y;
}
actor PositionExample
{
action main()
{
Position p;
p.x = 10.0;
p.y = 20.0;
}
}
変数宣言では固定長配列を使用できます。
actor ArrayExample
{
action main()
{
int values[4];
values[0] = 10;
values[1] = 20;
}
}
配列サイズには正の整数リテラル、または整数定数の名前を指定できます。
const int kValueCount = 4;
actor ArrayWithConstant
{
action main()
{
int values[kValueCount];
}
}
宣言子には複数の [] を続けて記述することもできます。
int grid[4][8];
配列要素は [] で参照します。
values[index]
実行時に決まる添字が配列範囲外になった場合、現行VMは ScriptError としてその Actor を停止し、範囲外アクセスを続行しません。
トップレベルでは、VM の変数メモリを明示的に構成するための allocate と static を使用できます。通常のゲームロジックより低レベルな機能です。
allocate 1024
{
int gReservedValue;
}
allocate N { ... } はグローバル変数領域にサイズを明示した領域を確保し、その中へ変数を配置します。N はバイト数を表す整数リテラルです。
static
{
float gStaticValue;
}
static { ... } の変数は、通常のグローバル変数とは別の VM static 変数領域へ配置されます。
サイズを明示する形式もあります。
static allocate 512
{
int gStaticReservedValue;
}
現行コンパイラは、明示した領域に宣言された変数が収まるかを検査します。
Mana の static は VM 内の static 変数領域を選ぶ構文です。C++ の static と同じ意味やリンケージ規則を持つものとして解釈しないでください。
| 宣言場所 | 主な用途 |
|---|---|
| グローバル | プログラム全体で共有する値 |
| Actor 内 | Actor ごとの状態 |
| Action / Function 内 | 一時的な値 |
| Struct 内 | Struct のメンバー |
static ブロック |
VM の static 変数領域 |
変更しない値には const を使用します。
変更しない値には const を使用します。
const 型 名前 = 定数式;
例:
const int kMaxCount = 10;
const float kSpeed = 2.5;
const bool kDebug = false;
const string kMessage = "Hello";
const で宣言した名前へ後から代入することはできません。
const int kMaxCount = 10;
// エラー
kMaxCount = 20;
Compiler はこのような代入を診断します。
const の初期値には、コンパイル時に評価できる式を使用します。
const int kBase = 10;
const int kDouble = kBase * 2;
実行時にしか値が決まらない Function 呼び出しなどは、定数初期値として使用できません。
Nil は専用型であり、現行コンパイラでは定数式として使用できません。
Priority のような数値に意味のある名前を付ける用途に向いています。
const int kTalkPriority = 10;
const int kMovePriority = 5;
request(kTalkPriority, NPC->talk());
数値を直接書くより、用途が分かりやすくなります。
整数定数は固定長配列のサイズにも使用できます。
const int kValueCount = 4;
actor ArrayExample
{
action main()
{
int values[kValueCount];
}
}
現在の Lexer には旧形式の define / undef トークンが残っていますが、現行 Parser.yy の宣言構文には組み込まれていません。
したがって、新しいドキュメントでは define / undef を現行の定数宣言構文として扱いません。新しいコードでは const 型 名前 = 値; を使用してください。
式は値を作る、参照する、計算する、または代入するための構文です。
10
1.5
true
"Hello"
Nil
count
speed
mOpened
count + 1
speed * 2.0
(a + b) * c
count == 0
count != 0
count < 10
count >= 1
比較結果は条件式などで使用できます。
enabled && visible
ready || force
!finished
count = 10
count += 1
speed *= 2.0
代入先には、書き込み可能な変数やメンバーなどを指定します。
?: を使用できます。
int value = enabled ? 1 : 0;
calculate(10, 20)
Struct のメンバー Function は . で呼び出します。
value.reset()
Struct のメンバーには . でアクセスします。
position.x
配列要素は [] で参照します。
values[index]
Actor の Action は -> で参照します。
Enemy->think()
Game::AI::Enemy->think()
Action 参照は request、awaitStart、await などで使用します。
request(10, Enemy->think());
:: は namespace の名前修飾、-> は Action 参照です。
Mana には実行状況を参照するための定義済みシンボルがあります。
priority
self
sender
this
Nil
それぞれの有効な位置と正確な意味は「定義済みシンボル」リファレンスで扱います。
sizeof 演算子が用意されています。
詳細な対象と結果は演算子リファレンスで扱います。
式の演算や代入について、Compiler が型の整合性を確認します。
Mana で使用できる主な演算子をまとめます。
| 演算子 | 意味 |
|---|---|
+ |
加算 |
- |
減算 |
* |
乗算 |
/ |
除算 |
% |
剰余 |
** |
べき乗 |
例:
int a = 10 + 2;
int b = 10 % 3;
| 演算子 | 意味 |
|---|---|
== |
等しい |
!= |
等しくない |
< |
より小さい |
<= |
以下 |
> |
より大きい |
>= |
以上 |
| 演算子 | 意味 |
|---|---|
&& |
論理 AND |
| ` | |
! |
論理 NOT |
if (enabled && visible)
{
}
| 演算子 | 意味 |
|---|---|
& |
AND |
| ` | ` |
^ |
XOR |
~ |
NOT |
<< |
左シフト |
>> |
右シフト |
=
+= -= *= /= %=
&= |= ^=
<<= >>=
例:
count += 1;
flags |= 0x10;
現行文法では、前置形式と後置形式の両方を記述できます。
++count;
--count;
count++;
count--;
condition ? trueValue : falseValue
例:
int sign = value >= 0 ? 1 : -1;
現行文法の sizeof は、型を括弧で指定します。
sizeof(int)
sizeof(Position)
任意の式を渡す構文ではありません。
-> は Actor の Action を参照するための Mana 固有の演算子です。
Enemy->think()
request(10, Enemy->think());
C/C++ のポインタメンバーアクセスとは意味が異なります。
:: は namespace を含む名前を修飾します。
Game::AI::Enemy
Action まで指定する場合は組み合わせます。
Game::AI::Enemy->think()
代入演算子を除く代表的な式について、現在の Parser では概ね次の順に結合が強くなります。
?:
&& ||
== !=
< <= > >=
| ^
&
<< >>
+ -
* / %
**
sizeof
! ~
単項 + -
++ --
特に && と || は現在の Parser では同じ優先順位として宣言されています。
意図を明確にしたい場合は、優先順位だけに頼らず括弧を使用してください。
if ((a || b) && c)
{
}
Mana で使用する基本的な文をまとめます。
式の後ろに ; を付けます。
count = count + 1;
update();
{
int count = 0;
count += 1;
}
if (condition)
{
print("true\n");
}
else
{
print("false\n");
}
while (condition)
{
update();
}
do
{
update();
}
while (condition);
for (int i = 0; i < 10; ++i)
{
print("loop\n");
}
終了条件を式として持たない繰り返しには loop を使用できます。
loop
{
update();
}
現在の繰り返しや switch から抜けます。
while (true)
{
if (finished)
break;
}
現在の反復の残りを飛ばし、次の反復へ進みます。
for (int i = 0; i < 10; ++i)
{
if (i == 5)
continue;
print("run\n");
}
switch (value)
{
case 0:
print("zero\n");
break;
case 1:
print("one\n");
break;
default:
print("other\n");
break;
}
Function では呼び出し元へ戻ります。
int add(int a, int b)
{
return a + b;
}
Action の中でも return; を使用できます。この場合は現在の Action を終了し、その Priority を解放します。下位 Priority に中断中の Action があれば、VM はそこへ復帰できます。
actor NPC
{
action talk()
{
if (sender == Nil)
return;
print("Hello\n");
}
}
Action は戻り値を持たないため、Action では return expression; を使用しません。
ラベルは identifier:、分岐は goto identifier; で記述できます。
actor GotoExample
{
action main()
{
goto Done;
print("skip\n");
Done:
print("done\n");
}
}
存在しないラベルへの goto はコンパイル時の名前解決エラーになります。
通常の条件分岐や繰り返しで表現できる場合は if、switch、while、for などの構造化された制御文を優先することを推奨します。
print("Hello\n");
Actor の Action を依頼する構文です。
request(10, Enemy->think());
awaitStart(10, Enemy->think());
await(10, Enemy->think());
詳細な待機条件や Priority との関係は Request と 実行制御 を参照してください。
Action の進行、Priority の巻き戻し、Request の受付状態などを制御する文があります。
yield
join
rollback
halt
lock
refuse
comply
正確な構文と動作は 実行制御 にまとめています。
Function は、値を受け取り、処理をまとめ、必要に応じて値を返す通常のサブルーチンです。
Action と違い、Function は Request の対象ではなく、呼び出した処理の流れの中で同期的に実行されます。
return_type functionName(arguments)
{
statements
}
グローバル Function と Struct のメンバー Function は、引数がない場合に限り空の () を糖衣構文として省略できます。引数がある定義には () が必要です。呼び出し時は functionName() や counter.reset() のように常に () が必要です。
たとえば Actor current() { ... } と Actor current { ... } は Actor を返すグローバル Function の定義で、actor current { ... } は Actor 宣言です。
例:
int add(int a, int b)
{
return a + b;
}
Function は Action や別の Function から呼び出せます。
actor FunctionExample
{
action main()
{
int value = add(2, 3);
print("%d\n", value);
}
}
引数は型と名前を並べて宣言します。
float distance(float x, float y)
{
return x + y;
}
型には Actor を含む組み込み型やユーザー定義型を使用できます。
戻り値がある Function は return expression; で値を返します。
int getCount()
{
return 10;
}
戻り値がない場合は void を使用します。
void reset()
{
return;
}
void Function から値を返すこと、または値を返す Function で return; のみを書くことはコンパイルエラーです。
Function は struct の中にも定義できます。
struct Counter
{
int value;
void reset()
{
value = 0;
}
}
actor CounterExample
{
action main()
{
Counter counter;
counter.reset();
}
}
呼び出しには . を使います。
Struct のメンバー Function については Struct を参照してください。
| Function | Action |
|---|---|
| 通常の関数呼び出しで実行 | Request で実行できる |
| 引数を持てる | 現行構文では引数を持たない |
| 戻り値を持てる | 現行構文では戻り値を持たない |
| 呼び出し元の処理の一部 | Actor の実行単位 |
ゲーム内の独立した行動は Action、Action の内部で再利用する処理は Function、と分けると整理しやすくなります。
C++ 側へ接続する Function は native で宣言します。
native void playSound(string name);
詳細は Native Function を参照してください。
struct は、複数の値と、それらを扱う Function を一つの型としてまとめるための機能です。
struct TypeName
{
members
}
例:
struct Status
{
int hp;
int mp;
}
変数として使います。
actor StatusExample
{
action main()
{
Status status;
status.hp = 100;
status.mp = 20;
}
}
Struct の中には変数を宣言できます。
struct CharacterData
{
string name;
int level;
float speed;
Actor owner;
}
別の Struct をメンバーに持つこともできます。
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;
}
}
メンバー参照には . を使います。
Struct 内には通常の Function を定義できます。
struct Counter
{
int value;
void reset()
{
value = 0;
}
}
actor CounterExample
{
action main()
{
Counter counter;
counter.reset();
}
}
現行コンパイラでは、Struct のメンバー Function からも通常の Mana の処理を記述できます。例えば Actor を引数として受け取り、Request を送ることもできます。
struct Helper
{
void call(Actor target)
{
request(1, target->talk());
}
}
Struct の中には native Function も宣言できます。
struct Transform
{
native void reset();
}
actor TransformExample
{
action main()
{
Transform transform;
transform.reset();
}
}
呼び出し側の構文は通常のメンバー Function と同じです。
C++ 側との対応方法は Native Function と Native Functions Integration を参照してください。
Struct は値をまとめるデータ型です。Actor のように独立した Action 実行主体にはなりません。
| Struct | Actor |
|---|---|
| データ型 | 実行主体 |
| Function を持てる | Action と状態を持てる |
| Request の対象ではない | Request の対象になる |
| 変数として保持する | VM が Actor インスタンスを管理する |
actor は、Mana で独立して Action を実行する基本的な実行単位です。
Actor は状態を保持し、複数の Action を持ち、他の Actor から Request を受け取れます。
actor ActorName
{
members
}
例:
actor NPC
{
int mTalkCount;
action talk()
{
mTalkCount++;
print("Hello\n");
}
}
Actor の中には、主に次のものを書けます。
- Action
- 変数
- 定数
-
extendによる Module の取り込み
例:
actor Guard
{
int mAlertLevel;
const int kMaxAlert = 3;
action patrol()
{
}
}
Actor のメンバー変数は Actor の状態として保持され、Action が終了しても値は残ります。
通常の actor は Program Image のロード時に Mana VM がインスタンス化します。
これは phantom との大きな違いです。Phantom はロード時にはインスタンス化されず、C++ 側から明示的に生成します。
Mana VM はプログラムをロードした後、Actor に対して特別な Action を要求します。
現行VMでは、
-
mainを Priority 0 で予約 -
initを最高優先度(2147483647)で Request
の順で全 Actor に送ります。
Actor がその Action を定義していない場合、その Request は実行されません。
actor Example
{
action init()
{
print("init\n");
}
action main()
{
print("main\n");
}
}
init は状態の初期化、main は通常の開始処理として使えます。全 Actor の初期化完了は待ちません。init がない Actor は main から開始します。init 内で自 Actor の低優先度 Action の完了を待つと、その Action を実行できず待機が続きます。
Actor への参照を保持する型は Actor です。小文字の actor は Actor 宣言専用のキーワードです。Actor は予約語のため、ユーザー定義名には使用できません。
Actor target;
Actor 参照に対して Action を指定できます。
request(1, target->talk());
Action 参照の詳細は Request を参照してください。
Actor は namespace 内に定義できます。
namespace Game::NPC
{
actor Shopkeeper
{
action talk()
{
}
}
}
完全修飾名では Game::NPC::Shopkeeper となります。
Actor は実行主体であり、ゲームキャラクター専用の概念ではありません。
例えば次のような役割にも使用できます。
- イベント進行
- UI 制御
- ギミック
- シーン管理
- バトル進行
- エフェクト制御
action は Actor が実行する処理単位です。
Function と異なり、Action は Request の対象になり、Priority に従って開始・中断・再開されます。
action actionName()
{
statements
}
例:
actor NPC
{
action talk()
{
print("Hello\n");
}
}
現在、Action は引数も戻り値も持ちません。() を付けた形が正式な構文で、省略形は糖衣構文です。action talk と action talk() は同じ Action を定義します。
action talk()
{
}
Actor 間で値を共有したい場合は、Actor の状態、グローバルデータ、Struct、Native Function などを用途に応じて使います。
Action は request、awaitStart、await などから実行できます。
request(1, NPC->talk());
NPC->talk() は Action 参照です。
Action 参照には -> を使います。
NPC->talk()
namespace の修飾には :: を使います。
Game::NPC::Shopkeeper->talk()
旧形式の Actor::action() はコンパイラに互換構文として残っていますが、deprecated warning が出ます。新しいコードでは Actor->action() を使用してください。
init と main は VM 起動時に特別扱いされる Action 名です。
actor Example
{
action init()
{
}
action main()
{
}
}
現行VMは全 Actor に対して、init を最高優先度(2147483647)、main を Priority 0 で Request します。全 Actor の init 完了を待ってから main を開始する仕組みではありません。各 Actor は自分の init が終了すると、予約済みの Action を優先度順に実行します。
Action では、現在の実行状態を表す定義済み値を利用できます。
-
self: 現在の Actor -
sender: この Action を Request した Actor -
priority: 現在実行中の Priority
例:
actor NPC
{
action talk()
{
print("priority = %d\n", priority);
}
}
sender は Request の送信元を保持するため、どの Actor から実行を依頼されたかを識別する用途に使えます。
詳細は 定義済みシンボル を参照してください。
Actor が Action を実行中に別の Request を受けると、Priority によって実行順が決まります。
- より高い Priority: 現在の Action に割り込む
- より低い Priority: 現在の Action が終わるまで保留される
- 同じ Priority: 現行VMでは同じ Priority の Request がすでに存在すると新しい Request は受理されない
Priority は数値が大きいほど高くなります。
| Action | Function |
|---|---|
| Actor の実行単位 | 通常のサブルーチン |
| Request の対象 | 通常の関数呼び出し |
| Priority を持つ | Priority を持たない |
| 中断・再開され得る | 呼び出し元の処理として実行 |
| 現行構文では引数・戻り値なし | 引数・戻り値を持てる |
Request は、Actor に対して Action の実行を要求する仕組みです。
Mana では、Actor 同士の協調を通常の Function 呼び出しだけで表すのではなく、Request と Priority を使って表現します。
構文:
request(priority, actor_expression->actionName());
例:
request(10, NPC->talk());
第1引数は Priority、第2引数は Action 参照です。
() を付けた形が正式な構文で、空の () は糖衣構文として省略できます。Actor->action と Actor->action() はどちらも引数なしの Action を参照します。これ自体が Action を実行するのではなく、request が実行を要求します。Action の引数はまだ使用できません。
Priority は値が大きいほど高くなります。
推奨構文は -> です。
NPC->talk()
namespace を含む場合:
Game::NPC::Shopkeeper->talk()
Actor 型の式も使用できます。
Actor target;
request(1, target->talk());
Parser は expression->actionName() を Action 参照として受け付けます。
旧形式:
NPC::talk()
も互換構文として認識されますが、deprecated warning が出ます。新しいコードでは使用しないでください。
通常の request は Action の完了を待ちません。
request(10, NPC->talk());
print("continue\n");
Request を送った Actor は、そのまま後続処理を続けます。
対象 Actor 側では Priority によって処理されます。
- 要求 Priority が現在より高い: 現在の処理へ割り込む
- 要求 Priority が現在より低い: 後で実行するため保持する
- 要求 Priority が現在と同じ: その Priority の実行状態がすでに存在するため、新しい Request は受理されない
同じ Priority に複数の Action をキューとして積む仕組みではありません。
現行VMの Actor::Request では、少なくとも次の場合に Request が失敗します。
- Priority が VM の最低割り込み Priority 以下
- 対象 Actor が halt 状態
- 対象 Actor が
refuse()状態 - 同じ Priority の Request がすでに存在する
- 指定した Action が存在しない
スクリプトの request 文自体には成否を返す戻り値はありません。
awaitStart(priority, actor_expression->actionName());
Request を送り、対象 Actor がその Priority を開始できる状態になるまで呼び出し側を待機させます。
要求が受理された場合、対象 Actor の現在 Priority が要求 Priority 以下になった時点で待機を解除します。要求した Action の最初の文が実行済みであることまでは保証しません。
awaitStart(10, NPC->talk());
await(priority, actor_expression->actionName());
Request を送り、その Priority の処理が完了するまで待機します。
要求が受理された場合、対象 Actor の現在 Priority が要求 Priority 未満になった時点で待機を解除します。要求ごとの完了通知を保持して待つ仕組みではありません。
await(10, NPC->talk());
awaitStart と await は、最初の Request が受理されなければ待たずに次へ進みます。同じ Priority が空くまで再要求する命令ではありません。待機から戻ったことだけでは、要求した Action が実行されたことや、その行動が成功したことを保証しません。
awaitStart と await で self を対象にすると、現行VMはスクリプトエラーにします。
await(10, self->talk()); // error
自分自身を待機対象にすると、待機中の Actor 自身が進まなければ完了できないためです。
通常の request で自分自身へ Request することは可能です。
Request が受理されると、送信元 Actor は対象 Action の sender として記録されます。
actor NPC
{
action talk()
{
// sender はこの Action を Request した Actor
}
}
VM 起動時のシステム Request など、送信元 Actor が存在しない場合もあります。
join は新しい Request を送りません。
join(0, NPC);
対象 Actor がすでに実行している処理の Priority を監視し、指定 Priority 以下になるまで待ちます。
| 命令 | 新しい Request | 待機 |
|---|---|---|
request |
送る | しない |
awaitStart |
送る | 開始可能になるまで |
await |
送る | 完了まで |
join |
送らない | 既存 Priority が指定値以下になるまで |
このページでは、Action の待機・中断・再開や Request の受付状態を制御する文をまとめます。
yield();
現在の Action を終了せず、その時点の実行をいったん中断して Mana VM に制御を返します。
次に実行機会が来たとき、yield() の後から再開します。
print("step 1\n");
yield();
print("step 2\n");
yield() は時間指定の待機ではありません。「1フレーム待つ」「1秒待つ」とは保証されず、再実行のタイミングはホスト側が VM をどのように更新するかにも依存します。
join(priority, actorExpression);
新しい Request は送らず、既に動作している対象 Actor を待ちます。
join(0, NPC);
現行VMでは、対象 Actor の現在 Priority が指定 Priority 以下になるまで待機します。
await が「Request を送ってその完了を待つ」のに対し、join は「既存の実行状態を待つ」ための命令です。
rollback priorityExpression;
例:
rollback 1;
rollback は現在の Action 実行を終了し、Actor に保存されている Priority の実行状態を指定値の方向へ巻き戻します。
現行VMの Actor::Rollback は、現在の Priority を解放し、必要に応じて指定値より上側に残っている実行状態を取り除いた後、再開可能な保存済み Action を復元します。再開できる Action がなければ Actor は停止状態へ戻ります。
通常の Action 終了時にも VM 内部では同じ Rollback 機構が使われています。
rollback は制御フローへの影響が大きいため、通常の順次処理よりも Priority の実行状態を明示的に戻したい場合に使用します。
halt();
現在の Actor の実行を停止します。
現行VMでは Actor を halt 状態にし、保持している割り込み実行状態をクリアします。halt 状態の Actor に対する新しい Request は Actor::Request で受理されません。
VM 側で Actor を Restart すると halt 状態は解除されます。
refuse();
Actor を Request 拒否状態にします。
現行VMでは Actor の Refused フラグを立て、それ以降に届く新しい Request を受理しなくします。
既に登録済みの実行状態をクリアする命令ではありません。
comply();
refuse() による Request 拒否状態を解除します。
現行VMでは Refused フラグをクリアします。
refuse();
// 新しい Request を拒否する区間
comply();
構文:
lock statement
通常はブロックと組み合わせます。
lock
{
// statements
}
現行コンパイラは lock の開始時に NonPreEmptive、終了時に PreEmptive 命令を生成します。現行VMでは、これらは現在の Priority 実行状態にある Synchronized フラグを ON / OFF します。
重要な点として、現在の Actor::Request 実装はこの Synchronized フラグを Request の受付判定や Priority の割り込み判定には直接参照していません。
そのため、現行実装の lock を C++ の mutex や「必ず割り込まれない atomic 区間」と同じ意味として扱わないでください。リファレンス上は、現在の実装では同期実行フラグを切り替える構文として扱います。
これらは Action の実行要求と待機を組み合わせた命令です。
request(10, NPC->talk());
awaitStart(10, NPC->talk());
await(10, NPC->talk());
詳細は Request を参照してください。
現在実行中の Priority は定義済み値 priority で取得できます。
print("%d\n", priority);
module は、複数の Actor で再利用する Action やメンバー定義をまとめるための仕組みです。
Module 自体は実行主体ではありません。actor から extend することで、Module に含まれる定義を Actor 側で利用します。
module CommonActions
{
action greet()
{
print("Hello\n");
}
}
actor Villager
{
extend CommonActions;
}
extend の基本形は次の通りです。
extend ModuleName;
現行文法では、Module の本体は Actor と同じ actions 文法を使用します。
そのため、Module には次のような定義を書けます。
- Action
- メンバー変数
- 定数
extend
module Talkable
{
int mTalkCount;
const int kTalkPriority = 10;
action talk()
{
mTalkCount = mTalkCount + 1;
}
}
Module は namespace 内に定義できます。
namespace Game::NPC
{
module Talkable
{
action talk()
{
}
}
}
完全修飾名で指定できます。
actor Villager
{
extend Game::NPC::Talkable;
}
using で namespace を探索対象へ追加した場合は、短い名前でも参照できます。
using Game::NPC;
actor Villager
{
extend Talkable;
}
Module は Program Image に定義情報として含まれますが、通常の Actor のように VM ロード時に実行主体として生成されません。
そのため、Module 自体へ request を送って独立実行させる用途ではなく、Actor へ共通定義を追加するために使用します。
extend によって取り込まれる定義と Actor 側の定義で同名シンボルが発生した場合、コンパイラのシンボル解決規則の対象になります。
再利用用 Module では、同名定義による上書きを前提にせず、役割が明確に分かれる名前を使用することを推奨します。
phantom は、VM 起動時にはインスタンスを作らず、C++ 側から必要なタイミングで Actor を生成するためのテンプレートです。
phantom EnemyTemplate
{
int mHp;
action main()
{
}
action damage()
{
}
}
文法上の本体は actor と同じ形式で、Action やメンバーを定義できます。
通常の actor は Program Image のロード時に VM がインスタンスを生成し、Actor 一覧へ登録します。
phantom はロード時には Actor 一覧へ生成されません。VM は Phantom の定義情報を保持し、C++ 側から明示的に生成されたときに Actor インスタンスを作ります。
actor |
phantom |
|
|---|---|---|
| VMロード時に生成 | される | されない |
| Actionを定義 | できる | できる |
| Actor変数を持つ | できる | できる |
| 主な用途 | 常駐する実行主体 | 動的生成用テンプレート |
現行 VM API では CreateActorFromPhantom を使用します。
std::shared_ptr<mana::Actor> enemy =
vm->CreateActorFromPhantom("EnemyTemplate", "Enemy01");第1引数は Phantom の定義名、第2引数は生成する Actor の名前です。
生成された Actor は VM の Actor 一覧へ登録され、Phantom に定義されていた Action と Actor 変数領域を持ちます。
VM が Program Image をロードすると、通常の Actor に対して init と main の Request を行います。
Phantom はその時点では Actor として生成されていないため、このロード時の一括 Request の対象にはなりません。
動的生成した Actor をどのように初期化・開始するかは、生成側の C++ コードと VM API の利用方法を含めて設計してください。
現行言語には、Mana スクリプトから Phantom を直接インスタンス化する構文はありません。
Phantom の生成はホスト側 C++ API の責務です。
存在しない Phantom 名を CreateActorFromPhantom に渡すと、VM は Phantom not found の実行時エラーを発生させます。
namespace は、Actor、Module、Struct、Function、変数、定数などの名前を階層化して整理するための仕組みです。
namespace Game::AI
{
actor Enemy
{
action think()
{
}
}
}
完全修飾名は :: で区切ります。
Game::AI::Enemy
:: は namespace の名前を修飾する演算子です。Action 参照で使う -> とは役割が異なります。
request(1, Game::AI::Enemy->think());
using Game::AI;
actor Controller
{
action main()
{
request(1, Enemy->think());
}
}
using Game::AI; により、未修飾名 Enemy の解決候補として Game::AI::Enemy が追加されます。
現行実装では using の対象として namespace だけでなく Actor / Module も解決できます。
namespace Game::AI
{
actor Enemy
{
action think()
{
}
}
}
using Game::AI::Enemy;
この場合、最後の名前 Enemy がエイリアスとして現在のスコープへ追加されます。
using のシンボル対象は現行実装では Actor / Module に限定されています。Struct や通常Functionなどを同じ方式で using する構文としては扱いません。
using は、その宣言が属する namespace スコープの名前解決へ影響します。
namespace を抜けると、その内側で追加した using スコープも終了します。
Mana はパース後に全体のシンボルと namespace を解析するため、後方で定義される namespace や Actor / Module を using から参照できます。
using Game::AI;
actor Controller
{
action main()
{
request(1, Enemy->think());
}
}
namespace Game::AI
{
actor Enemy
{
action think()
{
}
}
}
複数の候補が同じ未修飾名として見つかる場合、コンパイラは曖昧な参照としてエラーにします。
代表的な診断には次があります。
ambiguous usingambiguous symbol referenceambiguous type referenceambiguous actor referenceunresolved using
曖昧になる場合は完全修飾名を使用してください。
ソースファイルを分けても、自動的に namespace が作られるわけではありません。
- ファイル: ソースコードを物理的に整理する単位
- namespace: 名前を論理的に整理する単位
複数ファイルを一つの Program Image へまとめながら、namespace で名前の衝突を避けられます。
native は、Mana スクリプトから C++ 側に登録された外部関数を呼び出すための宣言です。
native int nativeAdd(int a, int b);
native 関数は宣言だけを持ち、Mana 側に本文を書きません。
actor Main
{
action main()
{
int value = nativeAdd(10, 20);
print("%d\n", value);
}
}
実行時には VM が関数名から C++ 側の登録済み関数を検索して呼び出します。
C++ 側では、例えば RegisterFunction で登録できます。
vm->RegisterFunction("nativeAdd", &OnNativeAdd);native は Struct メンバーとしても宣言できます。
struct Vec
{
float x;
float y;
native void normalize();
}
void update(Vec value)
{
value.normalize();
}
Struct の native メソッドは、外部関数名として Struct名::メソッド名 の形式で解決されます。
Vec::normalize
VM の外部関数コールバックには、実行中 Actor に加えて Struct インスタンスを指すポインタが渡されます。
native 戻り値型 関数名(引数...);
Struct 内では次の形式です。
struct TypeName
{
native 戻り値型 メソッド名(引数...);
}
通常の Mana Function は Mana Compiler が生成した命令列へ分岐して実行されます。
native Function は Mana 側に本文を持たず、実行時に名前で登録済み外部関数を検索します。
現行 VM の基本コールバック型は次の形式です。
std::function<void(const std::shared_ptr<mana::Actor>& actor,
void* structPointer)>引数と戻り値の受け渡しは Actor の外部関数向け API と VM スタックを通して行われます。詳細は Integration の Native Functions で扱います。
VM に同名の外部関数が登録されていない場合、VM は外部関数が見つからないことをエラーとして報告します。
Script側の宣言とC++側の登録名を一致させてください。
native は C++ と Mana の境界です。Mana 側の宣言と C++ 側の引数・戻り値の扱いを一致させる必要があります。
特に Struct native メソッドでは、通常のグローバル native 関数と異なり Struct インスタンスへのポインタが渡されます。
Mana のソースファイルは通常 .mn 拡張子を使用します。
大きなプログラムでは、import または include を使って複数のソースファイルを一つのコンパイルへ取り込めます。
import "npc.mn";
import は指定したソースを読み込みます。同じ解決済みパスのソースがすでに読み込まれている場合、現行Lexerは2回目以降の読み込みを省略します。
そのため、通常のファイル分割には import を推奨します。
main.mn
├─ import "npc.mn"
└─ import "event.mn"
取り込まれた定義は同じコンパイル結果にまとめられ、一つの Program Image が生成されます。
include "common.mn";
include も指定したソースを読み込みますが、import と異なり重複読み込みの抑止を行いません。
同じファイルを複数回 include すると、同じ宣言を複数回解析することになり、重複定義エラーの原因になる場合があります。
通常は import を使用し、同じソースを意図的に再読込する必要がある場合だけ include を検討してください。
読み込み対象のパスは SourceResolver によって解決されます。
標準のファイルベースの利用では、現在読み込んでいるソースファイルの場所を基準に相対パスを解決できます。
project/
├─ main.mn
└─ actors/
└─ npc.mn
import "actors/npc.mn";
組み込み時に独自 SourceResolver を使う場合、実際のパス解決規則はその実装に依存します。
Mana は取り込まれたソースを含む構文木を作成した後、シンボルと namespace のセマンティック解析を行います。
そのため、Actor、Module、namespace などは別ファイルに分かれていても、解析可能な名前であれば定義順だけを理由に参照できなくなる設計ではありません。
// main.mn
import "enemy.mn";
actor Controller
{
action main()
{
request(1, Enemy->think());
}
}
// enemy.mn
actor Enemy
{
action think()
{
}
}
ファイル名やディレクトリ構造から namespace が自動生成されることはありません。
actors/enemy.mn
というファイルを作っても、自動的に actors::Enemy になるわけではありません。
名前の論理的な整理には namespace を明示的に使用します。
mana コマンドには -I filename があり、ソースコードを書き換えずにコンパイル対象へファイルを強制追加できます。
mana -I common.mn main.mn-I は複数回指定できます。
Mana には、実行中の Actor や Request の文脈を参照するための定義済みシンボルがあります。
| 名前 | 型 / 種類 | 意味 |
|---|---|---|
self |
Actor |
現在実行している Actor |
sender |
Actor |
現在の Action を Request した Actor |
priority |
int |
現在実行中の Action の Priority |
this |
Struct の受信側 | Struct メンバーFunctionで現在の Struct インスタンスを参照するための予約語 |
Nil |
Nil |
空の参照を表す特殊値 |
actor Worker
{
action main()
{
request(1, self->update());
}
action update()
{
}
}
self は現在の Actor を表します。
Action や Actor 上で実行される Function から、自分自身へ Request を送る場合などに使用できます。
actor Receiver
{
action receive()
{
request(1, sender->reply());
}
}
sender は、その Action を Request した Actor を表します。
ただし VM 自身が起動時に送る init / main などのシステム Request では、送信元 Actor が存在しません。sender が常に有効な Actor を指すことを前提にしないでください。
actor Worker
{
action work()
{
print("%d\n", priority);
}
}
priority は現在実行している Action の Priority を int として取得します。
Request に指定した Priority と、現在どの割り込みレベルで動いているかを調べたい場合に使用します。
this は Struct のメンバーFunctionで、現在の Struct インスタンスを参照するための予約語です。
コンパイラ内部では this という受信側識別子として解決されます。通常のグローバルFunctionやActionで一般的な Actor 自己参照として使うものではありません。Actor 自身を参照する場合は self を使用します。
Nil は空の参照を表す特殊値です。
Nil
先頭の N は大文字です。 現行Lexerは Nil を予約語として認識し、nil は同じトークンとして扱いません。
Nil は通常の数値定数とは異なる専用型を持ち、定数式では使用できません。
真偽値リテラルとして true と false も使用できます。
bool visible = true;
bool finished = false;
これらは bool 型のリテラルです。
mana は、Mana ソースのコンパイル、Program Image の出力、Program Image の実行を行うコマンドラインツールです。
mana [options] input
mana main.mn出力ファイルを指定しない場合、ソースをコンパイルした後、その Program Image をそのまま Mana VM で実行します。
mana main.mn -o game.mx-o filename を指定すると、コンパイル結果の Program Image をファイルへ保存し、自動実行は行いません。
ファイル名の拡張子は任意です。
現行CLIでは、値を付けずに -o を指定した場合、入力ソースと同じベース名の .mx ファイル名を自動生成します。
mana main.mn -oこの場合は main.mx が出力先になります。
mana --execute game.mx--execute を指定すると、入力を Mana ソースではなくコンパイル済み Program Image として読み込み、VM で実行します。
現行 driver/Main.cpp では短縮形 -e は実装されていません。
mana main.mn -t public_types.h-t filename は、コンパイラが生成する公開型宣言をファイルへ出力します。
値を付けずに -t を指定した場合、入力ソースと同じベース名の .h が出力先になります。
mana main.mn -tこの場合は main.h が生成されます。
mana -I common.mn main.mn-I filename は、指定ファイルをコンパイル対象へ強制追加します。
複数回指定できます。
mana -I common.mn -I platform.mn main.mnmana --help
mana --version
mana --copyright| オプション | 内容 |
|---|---|
--help |
使用方法を表示 |
--version |
Mana のバージョンを表示 |
--copyright |
著作権表示 |
| オプション | 内容 |
|---|---|
-o filename |
Program Image の出力先 |
-t filename |
C++ 型宣言ヘッダーの出力先 |
-I filename |
強制インクルード。複数指定可 |
--execute |
入力を Program Image として実行 |
--help |
ヘルプ表示 |
--version |
バージョン表示 |
--copyright |
著作権表示 |
コンパイルに失敗した場合や出力ファイルを保存できない場合、CLI は失敗を表す終了コードを返します。
ビルドスクリプトやCIでは終了コードとコンパイラ診断を確認してください。
このマニュアルは 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.