-
-
Notifications
You must be signed in to change notification settings - Fork 4
Integration ja
🌐 English · 日本語
- Mana Integration
- 組み込み概要
- Compiler
- VM
- Program Image
- Native Functions
- SourceResolver
- Diagnostics
- Error Handling
このセクションでは、Mana Compiler と Mana VM を C++ アプリケーションやゲームエンジンへ組み込む方法を説明します。
Mana source
|
v
mana::Compile()
|
v
CompileResult::mProgramImage
|
+--> mana::ProgramImage で内容を照会
|
v
mana::VM::LoadProgram()
|
+--> Native Functions でホスト機能と接続
|
v
VM::Run()
Compiler へソースを供給する経路は SourceResolver で差し替えられ、コンパイル時の問題は Diagnostic、実行時の問題は Trace / ScriptError / FatalError などでホスト側へ通知できます。
| やりたいこと | ページ |
|---|---|
| Mana をゲームへ組み込む全体像を知る | 組み込み概要 |
| C++ から Mana ソースをコンパイルする | Compiler |
| Program Image を実行する | VM |
| Actor / Action 一覧を実行せず取得する | Program Image |
| Mana からゲーム側 C++ を呼ぶ | Native Functions |
| エディタやアセットからソースを供給する | SourceResolver |
| コンパイルエラーをIDEやCIへ表示する | Diagnostics |
| 実行時エラーや内部Faultを扱う | Error Handling |
Language Reference が Mana スクリプトを書く人向けなのに対し、Integration は Mana をゲームやツールへ組み込む C++ 開発者向けです。
Mana の言語仕様そのものを調べたい場合は Language Reference を参照してください。
Mana は、コンパイラと VM をライブラリとして C++ アプリケーションへ組み込めます。
#include "compiler/Compiler.h"
#include "runner/Mana.h"
mana::CompileOptions options;
options.mSourceFilename = "main.mn";
const mana::CompileResult result = mana::Compile(options);
if (!result.mSucceeded)
return;
auto image = std::make_shared<std::vector<uint8_t>>(result.mProgramImage);
auto vm = std::make_shared<mana::VM>();
vm->LoadProgram(std::shared_ptr<const void>(image, image->data()));
while (vm->Run())
{
}組み込み側は大きく3段階に分かれます。
-
mana::Compile()でソースを Program Image へ変換する - 必要なら
mana::ProgramImageで Actor / Action を事前確認する -
mana::VMへ Program Image をロードして実行する
Mana Compiler はファイルを書き出すことを前提にしていません。Compile() は CompileResult を返し、Program Image は std::vector<uint8_t> として取得できます。
そのため、エディタ内コンパイル、アセットビルド、サーバー側ビルドなどにも組み込めます。
VM はコンパイル済み Program Image を受け取って実行します。実行環境に Compiler を含めず、ビルド済み Program Image だけを配布する構成も可能です。
CompileOptions::mSourceResolver を差し替えると、ファイルシステム以外からソースを供給できます。
例えば次のような用途があります。
- エディタの未保存バッファ
- ゲームエンジンのアセットシステム
- パッケージ内の仮想ファイル
- テスト用のメモリ上ソース
詳しくは後続の SourceResolver ページで扱います。
コンパイルエラーは CompileResult::mDiagnostics に格納されます。
Compile() は内部例外を捕捉し、C++ 呼び出し側へ例外を越境させない設計です。一方、VM 実行時の問題は Trace や実行状態として扱われます。
現行 Compile() はコンパイラ内部にグローバル状態を持つため、複数スレッドから同時実行できません。
複数アセットを並列ビルドする場合でも、Mana Compiler の呼び出し部分は直列化してください。
Mana Compiler を C++ から利用する入口は mana::Compile() です。
mana::CompileOptions options;
options.mSourceFilename = "main.mn";
mana::CompileResult result = mana::Compile(options);Compile() はファイルを書き出さず、生成物を CompileResult に格納して返します。
主な設定は次の通りです。
| メンバー | 内容 |
|---|---|
mSourceFilename |
エントリとなるソースファイル |
mForcedIncludeFiles |
ソースより先に読み込むファイル群。CLI の -I 相当 |
mGenerateDump |
シンボル表・構文木・中間コードのダンプを生成 |
mGeneratePublicTypeDecl |
C++ 型宣言ヘッダーを生成 |
mSourceResolver |
ソース供給方法を差し替える |
mDiagnosticHandler |
診断発生時のコールバック |
mSourceResolver を省略した場合は、標準のファイルシステムから読み込みます。
| メンバー | 内容 |
|---|---|
mSucceeded |
エラーがなければ true
|
mProgramImage |
生成された Program Image |
mPublicTypeDecl |
C++ 公開型宣言 |
mDump |
デバッグ用 Markdown ダンプ |
mDiagnostics |
発生した全診断 |
コンパイル失敗時、mProgramImage は空になります。
options.mDiagnosticHandler = [](const mana::Diagnostic& diagnostic)
{
std::cerr << diagnostic.ToString() << '\n';
};ハンドラを指定しても、診断は CompileResult::mDiagnostics にも保持されます。
現行実装では、診断ハンドラ自身が例外を送出した場合も Compile() の外へ例外を越境させないよう処理されています。
options.mForcedIncludeFiles.push_back("common.mn");
options.mForcedIncludeFiles.push_back("platform.mn");先に追加したファイルほど先に読み込まれます。
options.mGeneratePublicTypeDecl = true;
const mana::CompileResult result = mana::Compile(options);
if (result.mSucceeded)
{
const std::string& header = result.mPublicTypeDecl;
}CLI の -t と同じ生成機能を、ファイル出力せず文字列として取得できます。
options.mGenerateDump = true;成功・失敗の解析やコンパイラ開発用に、Markdown形式のダンプを mDump から取得できます。
Compile() は内部で発生した例外を捕捉し、致命的な診断へ変換する設計です。通常、ホスト側は mSucceeded と mDiagnostics を確認すればよく、Mana内部例外を前提にした制御を組む必要はありません。
現行コンパイラはグローバル状態を持つため、Compile() を複数スレッドから同時に呼び出すことはできません。
mana::VM は、Compiler が生成した Program Image を読み込み、Actor / Action を実行する実行環境です。
メモリ上の Program Image をロードできます。
auto image = std::make_shared<std::vector<uint8_t>>(result.mProgramImage);
auto vm = std::make_shared<mana::VM>();
vm->LoadProgram(std::shared_ptr<const void>(image, image->data()));ファイルとして保存済みなら、パスからロードすることもできます。
vm->LoadProgram("game.mx");while (vm->Run())
{
}Run() はVMの処理が継続している間 true を返します。
ゲームループへ組み込む場合は、ホスト側の更新単位に合わせて Run() を呼び出す構成にできます。
auto actor = vm->FindActor("Game::NPC::Guide");Actor名は namespace を含む完全名で扱えます。
VM全体へ名前指定で Request を送れます。
vm->Request(10, "Game::NPC::Guide", "talk", nullptr);また、Actor を取得してから直接呼び出す経路もあります。
既存Actorを複製するAPIと、Phantomから生成するAPIがあります。
auto clone = vm->CloneActor(actor, "GuideClone");
auto enemy = vm->CreateActorFromPhantom("EnemyTemplate", "Enemy01");Phantom は Program Image ロード時には通常Actorとして生成されず、CreateActorFromPhantom() で明示的に生成します。
vm->RegisterFunction("nativeAdd", &OnNativeAdd);Mana側で native 宣言した関数名と一致する名前を登録します。
現行VMはプログラムロード後、通常Actorに対して init を最高優先度(2147483647)、main を Priority 0 で Request します。
init : Priority 2147483647
main : Priority 0
このため、init は main より高いPriorityで初期化処理を実行できます。
FunctionInitialize(*vm) で組み込み関数を登録すると、Mana から native void delay(float seconds); を宣言して秒単位で待機できます。以前のフレーム数を受け取る delay とは互換性がありません。
ホストが時間を管理する場合は vm->Run(deltaSeconds) に有限かつ非負の経過秒数を渡します。Run(0.0) は命令を実行しますが時間を進めません。初回に Run(0.0) を呼ぶと、その時点から待機を開始できます。指定時間は呼び出しの冒頭で一度だけ加算されます。
引数なしの Run() は単調時計で前回の更新からの経過時間を計測します。ポーズや倍速を制御する場合は、引数付きの呼び出しに統一してください。GetElapsedSeconds() は VM の累積秒数、GetDeltaTime() は直前の更新の経過秒数を返します。ロードと Restart() で時間はリセットされます。Restart() 後の Action 起動には別途 Request が必要です。
待機期限は Action の割り込みごとに保持され、割り込み中も時間は進みます。期限に達した後、次にその Action が実行可能になった時点で再開します。
主な状態確認APIには次があります。
IsRunning()GetFrameCounter()GetDeltaTime()IsFrameChanged()
ゲームエンジンとの連携では、VMを独立したアプリケーションとしてではなく、ホスト側更新ループの一部として扱うことを想定できます。
mana::ProgramImage は、コンパイル済み Program Image を読み込み、含まれている Actor / Action / Phantom を実行前に調べるためのクラスです。
mana::VM が「実行する」ためのAPIなのに対し、ProgramImage は「中身を調べる」ためのAPIです。
auto bytes = std::make_shared<std::vector<uint8_t>>(result.mProgramImage);
mana::ProgramImage image;
const bool loaded = image.LoadProgram(
std::shared_ptr<const void>(bytes, bytes->data()),
bytes->size());ロード結果は bool で返ります。
if (!loaded)
{
std::cerr << image.GetLastError() << '\n';
}IsLoaded() でも状態を確認できます。
for (std::string_view name : image.GetActorNames())
{
std::cout << name << '\n';
}特定Actorの存在確認もできます。
if (image.HasActor("Game::NPC::Guide"))
{
}for (std::string_view action :
image.GetActorActionNames("Game::NPC::Guide"))
{
std::cout << action << '\n';
}if (image.HasActorAction("Game::NPC::Guide", "talk"))
{
}ゲーム側から名前でRequestする前の検証や、エディタUIの候補一覧生成に利用できます。
Phantomについても存在確認とAction一覧の取得ができます。
if (image.HasPhantom("EnemyTemplate"))
{
const auto actions =
image.GetPhantomActionNames("EnemyTemplate");
}image.HasPhantomAction("EnemyTemplate", "damage");ProgramImage はロードしたバイト列を shared_ptr<const void> として保持します。
Actor名やAction名の戻り値は std::string_view なので、これらのビューは Program Image が保持するデータの寿命と関係します。取得した string_view を、元の ProgramImage より長く保持しないようにしてください。
ProgramImage は特に次の用途に向いています。
- エディタでActor一覧を表示する
- Action名をコンボボックスから選択する
- C++側の設定に指定されたActor / Action名を事前検証する
- Phantomの生成候補を列挙する
- Program Imageを実行せず検査する
ProgramImage |
VM |
|
|---|---|---|
| Program Imageを読む | する | する |
| Actor / Action一覧を調べる | 主用途 | 一部検索APIあり |
| Actionを実行する | しない | する |
| Requestする | しない | する |
| Native Functionを登録する | しない | する |
Native Function は、Mana スクリプトからゲームやツール側の C++ 処理を呼び出すための境界です。
Mana 側では native を使って関数を宣言し、C++ 側では同じ名前の関数を mana::VM に登録します。
Mana 側で次のように宣言します。
native int add(int a, int b);
actor Main
{
action main()
{
int result = add(10, 20);
print("%d\n", result);
}
}
C++ 側では、外部関数を登録します。
void Add(const std::shared_ptr<mana::Actor>& actor, void*)
{
const int32_t a = actor->GetParameterInteger(0);
const int32_t b = actor->GetParameterInteger(1);
actor->SetReturnInteger(a + b);
}
std::shared_ptr<mana::VM> vm = std::make_shared<mana::VM>();
vm->RegisterFunction("add", &Add);登録した VM に Program Image を読み込み、通常どおり Run() すると、Mana の add() 呼び出しから C++ の Add() が実行されます。
現行 VM の外部関数型は次の形式です。
using ExternalFunctionType =
std::function<void(const std::shared_ptr<mana::Actor>& actor,
void* structPointer)>;第1引数の actor は、その native 関数を実行している Actor です。
第2引数の structPointer は Struct の native メソッドを呼び出した場合に、その Struct インスタンスを参照するために使用されます。
Native Function の引数は、実行中 Actor から取得します。
代表的な API は次の通りです。
actor->GetParameterInteger(index);
actor->GetParameterFloat(index);
actor->GetParameterString(index);
actor->GetParameterActor(index);
actor->GetParameterPointer(index);
actor->GetParameterAddress(index);引数数は次で取得できます。
const int32_t count = actor->GetArgumentCount();Mana 側の宣言と C++ 側で読み取る型・順番は、組み込み側の契約として一致させてください。
戻り値がある native 関数では SetReturn*() を使用します。
actor->SetReturnInteger(value);
actor->SetReturnFloat(value);
actor->SetReturnString(text);
actor->SetReturnActor(otherActor);
actor->SetReturnPointer(pointer);
actor->SetReturnData(data, size);例えば Mana 側が
native float getSpeed();
なら、C++ 側では次のように値を設定します。
void GetSpeed(const std::shared_ptr<mana::Actor>& actor, void*)
{
actor->SetReturnFloat(3.5f);
}Mana では Struct のメンバーとして native 関数を宣言できます。
struct Position
{
float x;
float y;
native void normalize();
}
この場合、C++ 側で解決される外部名は Struct名::メソッド名 です。
vm->RegisterFunction("Position::normalize", &NormalizePosition);コールバックの第2引数 structPointer には対象 Struct のアドレスが渡されます。
void NormalizePosition(const std::shared_ptr<mana::Actor>&,
void* structPointer)
{
// structPointer をホスト側で対応するレイアウトとして扱う
}Struct のメモリレイアウトを C++ 側で直接扱う場合は、Mana 側の型定義との一致を厳密に管理してください。
VM::RegisterMemberFunction() を使うと、C++ オブジェクトのメンバー関数をラッパーなしで登録できます。
class GameBridge
{
public:
void PlaySound(const std::shared_ptr<mana::Actor>& actor, void* structPointer)
{
// ゲーム側処理
}
};
auto bridge = std::make_shared<GameBridge>();
vm->RegisterMemberFunction("playSound", bridge, &GameBridge::PlaySound);現行 API には生ポインタ、shared_ptr、weak_ptr を利用するオーバーロードがあります。
shared_ptr を渡すオーバーロードは内部で弱参照として保持します。登録先オブジェクトが先に破棄された場合、呼び出し時にエラーが Trace へ出力されます。
VM は実行時に文字列名で外部関数を検索します。
Mana 側の宣言名と RegisterFunction() の登録名が一致していない場合、VM は外部関数が見つからないことを Error Trace として報告します。
組み込み時には、Program Image をロードして実行する前に必要な Native Function をすべて登録しておくことを推奨します。
Native Function は、Mana 自身へレンダリング、物理、サウンド、アセット管理などを持ち込むための仕組みではありません。
それらをホストアプリケーション側へ残したまま、Mana から必要な操作だけを公開する境界として設計すると、スクリプトとゲームエンジンの責務を分離しやすくなります。
mana::SourceResolver は、Mana Compiler へソースコードを供給するためのインターフェースです。
通常はファイルシステムから .mn を読み込みますが、エディタの未保存バッファ、アセットデータベース、パッケージ内データ、ネットワーク上の仮想ファイルなどからソースを供給することもできます。
CompileOptions::mSourceResolver に独自 Resolver を指定します。
mana::CompileOptions options;
options.mSourceFilename = "main.mn";
options.mSourceResolver = resolver;
mana::CompileResult result = mana::Compile(options);mSourceResolver を省略した場合は、既定の mana::FileSourceResolver が使用されます。
独自 Resolver では、次の2関数を実装します。
class SourceResolver
{
public:
virtual std::string Resolve(
std::string_view from,
std::string_view filename) const = 0;
virtual bool Read(
std::string_view path,
std::string& outText) const = 0;
};役割は明確に分かれています。
-
Resolve(): 参照元と指定名から、ソースを識別する位置を決める -
Read(): 解決済み位置から実際のソース文字列を取得する
標準の FileSourceResolver はファイルシステムから読み込みます。
project/
├─ main.mn
└─ actor/
└─ npc.mn
main.mn が次のように書かれている場合、
import "actor/npc.mn";
相対パスは main.mn のあるディレクトリを基準に解決されます。
さらに actor/npc.mn から別ファイルを読み込む場合は、今度は npc.mn の場所が基準になります。
最初のソースだけは現在の作業ディレクトリを基準にします。
例えばエディタの未保存内容を直接コンパイルしたい場合は、次のような Resolver を作れます。
class MemorySourceResolver final : public mana::SourceResolver
{
public:
std::map<std::string, std::string, std::less<>> files;
std::string Resolve(
std::string_view,
std::string_view filename) const override
{
return std::string(filename);
}
bool Read(
std::string_view path,
std::string& outText) const override
{
const auto it = files.find(path);
if (it == files.end())
return false;
outText = it->second;
return true;
}
};利用側は次のようになります。
auto resolver = std::make_shared<MemorySourceResolver>();
resolver->files["main.mn"] = R"(
actor Main
{
action main()
{
print("Hello\n");
}
}
)";
mana::CompileOptions options;
options.mSourceFilename = "main.mn";
options.mSourceResolver = resolver;
const mana::CompileResult result = mana::Compile(options);import は、同じ解決済みパスのソースを二度読み込まない仕組みです。
そのため独自 SourceResolver では、同じソースを表す指定に対して可能な限り同じ解決済み文字列を返すことが重要です。
例えば次の2つが同じデータを表すのに、
scripts/npc.mn
scripts/./npc.mn
異なる解決結果のまま返すと、Compiler からは別ソースとして見える可能性があります。
アセットIDや正規化済み仮想パスなど、安定した識別子へ正規化する設計を推奨します。
Resolve() が空文字列を返した場合、Compiler は指定位置を解決できなかったものとして診断します。
Resolve() が位置を返しても Read() が false を返した場合は、その解決済み位置を開けなかったものとして診断されます。
エディタ統合では、診断表示に使いやすい論理パスを Resolve() の結果として返しておくと、どの仮想ファイルで問題が起きたかを利用者へ示しやすくなります。
Read() が返す文字列の改行コードを Resolver 側で統一する必要はありません。
Compiler の Lexer が読み込み後に LF へ正規化します。
CompileOptions::mForcedIncludeFiles に指定したソースも、同じ SourceResolver を通して読み込まれます。
options.mForcedIncludeFiles.push_back("common.mn");CLI の -I common.mn に相当します。
SourceResolver を差し替えることで、次のような統合が可能です。
- ゲームエディタ上の未保存 Mana スクリプトをそのままコンパイルする
- Unreal Engine などのアセットからソースを供給する
- zip / pak などのパッケージ内から読む
- 単体テストでファイルI/Oを使わずソースを与える
- 論理パスと実ファイルの場所を分離する
Mana Compiler は、警告やエラーを単なる文字列として標準出力へ流すだけでなく、mana::Diagnostic として構造化して返します。
エディタ、IDE、CI、ゲーム内ツールへ組み込む場合は、CompileResult::mDiagnostics を利用すると、ファイル名や行番号を保ったまま独自表示できます。
最も基本的な方法は、コンパイル後に mDiagnostics を確認することです。
mana::CompileOptions options;
options.mSourceFilename = "main.mn";
const mana::CompileResult result = mana::Compile(options);
for (const mana::Diagnostic& diagnostic : result.mDiagnostics)
{
std::cout << diagnostic.ToString() << '\n';
}診断が存在しても、すべてがコンパイル失敗を意味するわけではありません。
- Warning: 警告。コンパイルは継続する
- Error: エラー。解析は可能な範囲で継続するが、成果物は生成しない
- Fatal: そのコンパイルを継続できない致命的エラー
CompileResult::mSucceeded を最終的な成功判定に使用してください。
mana::Diagnostic には次の情報があります。
struct Diagnostic
{
DiagnosticSeverity mSeverity;
DiagnosticPhase mPhase;
std::string mFilename;
int32_t mLineNo;
std::string mMessage;
};mana::DiagnosticSeverity::Warning
mana::DiagnosticSeverity::Error
mana::DiagnosticSeverity::Fatalmana::DiagnosticPhase::Compile
mana::DiagnosticPhase::LinkCompile は字句解析、構文解析、意味解析、コード生成などで発生した問題を表します。
Link はシンボル解決や Program Image 生成段階の問題を表します。
mFilename と mLineNo を使うと、エディタ上で該当ソースへジャンプできます。
for (const auto& diagnostic : result.mDiagnostics)
{
editor.ShowDiagnostic(
diagnostic.mFilename,
diagnostic.mLineNo,
diagnostic.mMessage);
}mLineNo == 0 は、診断に行情報がないことを表します。
include / import 先で発生した診断には、対象ソースのファイル名と行番号が保持されます。
独自 SourceResolver を使う場合、Resolve() が返す論理パスはそのまま診断上の位置として重要になります。
Diagnostic::ToString() は Mana の標準書式へ整形します。
const std::string text = diagnostic.ToString();行番号がある場合、プラットフォームに応じて例えば次のような形式になります。
main.mn(12): error: message
または、
main.mn:12 error: message
独自UIでは構造化フィールドを使い、CLIやログでは ToString() を使うという分け方ができます。
コンパイル完了後ではなく、診断が発生した時点で受け取りたい場合は CompileOptions::mDiagnosticHandler を使用します。
mana::CompileOptions options;
options.mSourceFilename = "main.mn";
options.mDiagnosticHandler = [](const mana::Diagnostic& diagnostic)
{
LogDiagnostic(diagnostic);
};
const mana::CompileResult result = mana::Compile(options);Handler を設定しても、診断は CompileResult::mDiagnostics にも残ります。
そのため、
- Handler: リアルタイム表示やログ
-
mDiagnostics: コンパイル後の一覧表示やテスト
という使い分けができます。
組み込み側の Diagnostic Handler は例外を送出しない設計を推奨します。
現行 Compile() はコンパイル境界から例外を外へ出さないよう実装されており、Handler 内で例外が発生した場合もホスト側へ越境しないことを回帰テストしています。
ただし、診断処理そのものが失敗すると本来のエラー表示を失う原因になるため、Handler はできるだけ単純な処理にしてください。
現在の Mana Compiler は診断収集を含めてグローバル状態を使用しています。
そのため、mana::Compile() を複数スレッドから同時に呼び出すことはできません。
エディタでバックグラウンドコンパイルを行う場合でも、Mana Compiler の呼び出し自体は1本に直列化してください。
CI では mSucceeded と mDiagnostics を組み合わせて扱うと便利です。
const mana::CompileResult result = mana::Compile(options);
if (!result.mSucceeded)
{
for (const auto& diagnostic : result.mDiagnostics)
std::cerr << diagnostic.ToString() << '\n';
return 1;
}警告を独自ポリシーでエラー扱いする場合も、DiagnosticSeverity を見てホスト側で判断できます。
このページで扱う Diagnostic は主に Compiler の診断です。
Program Image をロードした後に発生するスクリプト実行エラーや VM 内部エラーは、Trace、ScriptError、FatalError など別の経路で扱います。
それらは Error Handling を参照してください。
Mana をゲームやツールへ組み込む場合、エラーを一種類として扱うのではなく、コンパイル時 / Program Image 読み込み時 / スクリプト実行時 / Mana 内部不整合 に分けて考えると扱いやすくなります。
Mana source
|
| Compile diagnostics
v
mana::Compile()
|
| Program Image load error
v
mana::VM::LoadProgram()
|
| ScriptError / runtime trace
v
mana::VM::Run()
|
| FatalError / FaultHandler
v
Mana または組み込み側の不整合
それぞれで復帰単位と通知経路が異なります。
mana::Compile() は、失敗を CompileResult として返します。
const mana::CompileResult result = mana::Compile(options);
if (!result.mSucceeded)
{
for (const auto& diagnostic : result.mDiagnostics)
ShowError(diagnostic);
}Compiler 内部で発生した例外も Compile() の境界で捕捉され、致命的診断として mDiagnostics へ格納されます。
そのため通常の組み込みコードでは、コンパイルエラーのために Compile() 全体を例外で制御する必要はありません。
詳細は Diagnostics を参照してください。
VM::LoadProgram() は Compiler API と異なり、読み込みに失敗した場合に例外を送出する場合があります。
例えば現行実装では、次のような問題が検査されます。
- ファイルを開けない
- Mana Program Image のシグネチャではない
- Program Image のバージョンが一致しない
- 32bit / 64bit の形式が実行側と一致しない
ホスト側ではロード境界で例外を捕捉してください。
try
{
vm->LoadProgram("event.mx");
}
catch (const std::exception& e)
{
LogError(e.what());
return false;
}メモリから読み込む場合も、不正な Program Image に対する例外をホスト側で扱う設計にしてください。
実行せずに Program Image を調べたい場合は mana::ProgramImage を利用できます。
mana::ProgramImage image;
if (!image.LoadProgram(program, size))
{
LogError(image.GetLastError());
return false;
}ProgramImage::LoadProgram() は bool で成功を返し、失敗理由は GetLastError() から取得できます。
エディタやアセットインポータでは、VM に渡す前の検査として利用できます。
0除算、配列範囲外、不正な自己待機など、Mana スクリプトの実行中に継続できない問題は mana::ScriptError として扱われます。
VM::RunActor() は ScriptError を Actor 単位で捕捉します。
Actor A
ScriptError
|
v
Actor A を停止
Actor B / C
実行継続
エラーを起こした Actor は停止しますが、それだけを理由に VM 全体や他の Actor が停止する設計ではありません。
エラー内容は TraceLevel::Error として Trace に出力されます。
print() の出力、警告、実行時エラーなどは Trace 経由で受け取れます。
void OnTrace(
void* userData,
const mana::TraceLevel level,
const char* message,
const std::size_t length)
{
// ゲームエンジン側のログへ送る
}
mana::SetTraceHandler(&OnTrace, hostContext);重大度は次の3種類です。
mana::TraceLevel::Info
mana::TraceLevel::Warning
mana::TraceLevel::Error標準出力が利用しにくいゲームエンジンへ組み込む場合は、起動時に TraceHandler を設定してエンジン側ログへ接続することを推奨します。
TraceHandler は利用開始前に一度設定し、実行中に頻繁に差し替えないでください。
また、Handler 自身から例外を送出しないでください。
Trace は必ずしも1行単位でコールバックされるとは限らないため、行単位のログが必要ならホスト側で改行までバッファリングします。
スクリプトの誤りではなく、Mana 自身または組み込み側の不整合によって内部の前提が崩れた場合は mana::FatalError が使用されます。
この系統は RaiseFault() から報告され、Error Trace を出力した後に FatalError を送出します。
Actor 実行中に発生した std::exception は VM::RunActor() の境界で捕捉され、その Actor が停止します。他の Actor は継続できます。
内部不整合をデバッガーやクラッシュレポートへ接続したい場合は SetFaultHandler() を利用できます。
void OnFault(
void* userData,
const char* file,
int line,
const char* message)
{
// debugger break / crash reporter / telemetry など
}
mana::SetFaultHandler(&OnFault, hostContext);FaultHandler は巻き戻しが始まる前に呼ばれるため、Mana 開発中にその場でデバッガーを止めたい場合にも利用できます。
Handler から戻った後は FatalError が送出されます。
FaultHandler 自身から例外を送出してはいけません。
| 種類 | 意味 | FaultHandler | Actor |
|---|---|---|---|
ScriptError |
スクリプト側の実行時エラー | 呼ばれない | 該当Actorを停止 |
FatalError |
Manaまたは組み込み側の内部不整合 | 呼ばれる | Actor実行境界なら該当Actorを停止 |
スクリプト作者へ見せるエラーと、Mana/エンジン開発者が調査すべき内部不整合を分離することが重要です。
組み込み側では次のように責務を分けると扱いやすくなります。
- Compiler の問題は
Diagnosticとしてエディタへ表示する - Program Image のロード失敗はロード処理の例外として扱う
- Runtime の
ScriptErrorは Trace へ送り、該当Actorだけを停止する -
FatalErrorは Trace + FaultHandler で開発者へ通知する -
TraceHandlerとFaultHandlerはアプリケーション初期化時に設定する
これにより、1つの不正なスクリプトでゲームプロセス全体を即座に終了させるのではなく、問題の種類に応じた復帰単位を選べます。
このマニュアルは 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.