文書の過去の版を表示しています。
組み込み時、Miniscriptから取得した変数を保存する方法
概要
MiniScript組み込み時に関数経由で変数を取得したい時があります。 少なくとも管理者は以下の理由により必要がありました。
* 敵スクリプトを書いてる時に特定タイミングで次に行動を指定したい * プレイヤーの行動に合わせて敵やボスの行動を変更したい
上記内容について、実装がうまく行ってるのでその内容を記載します。 なお、C#/C++について記載していますが、C++については大分内容が込み入ったものになります。
C#での実装
public class SampleClass: MonoBehaviour { //サンプルのノート。 public Value play_list; void intrinsic_define(){ f = Intrinsic.Create("get_list"); f.AddParam("note"); //リスト想定 f.code = (context, partialResult) => { var rs = context.interpreter.hostData as SampleClass; //ノート Value temp_value = context.GetVar("note"); rs.play_list = temp_value; return Intrinsic.Result.Null; } }
だいぶ簡単です。メンバ変数にValueを保管して、デリゲート処理で指定を間違わないようにすれば、楽に変数を格納可能です。 また、C#は値渡しがメインの機能で提供されているので、勝手に参照先が開放される等がなく管理は楽になっているかと思います。
つまりC#での実装においては以下の理由からあまり難易度が高くないと言えます。
- デリゲート機能があるため、クラス内のメンバ変数にアクセスができる
- 値渡しが主の機能になっているので参照先が開放される等のリスクを考慮しなくていい(auto使う場合はその限りではありません)
C++での実装
ではC++の実装でMiniScriptを使う場合はどうでしょうか。
結論から言うと、以下の理由でそう簡単にはいかないことが分かっています。
- C++にはデリゲート機能がなく、Valueについては起動しているVM内で保存する変数の「参照渡しになる」
- ValueクラスがVMへの参照しか持っておらず、実変数・文字列は全部VM上に持っている
- Context(VMがスクリプトを読んで計算する1回の処理)上での変数定義は保存しているが、基本的にはContext上でのローカル変数は開放されてしまう
※Contextについては以下解釈が近い模様。
https://e-words.jp/w/%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88.html
現在ツールを開発していますが、上記問題に遭遇したため、色々資料を集めながらなんとか対応しました。 以下にその対応方法を記載します。
1.関数から引数を受け取るための実装
これは楽で、以下のような実装になります。 組み込み関数の実装が色々でてきますがご了承ください。
static IntrinsicResult intrinsic_test(Context* context, IntrinsicResult partialResult) { Value x = context->GetVar("value"); context->vm->GetGlobalContext()->variables.SetValue("_sequence1", x); //staticなフラグ管理する。 seq[1] = true; return IntrinsicResult::Null; }
上記は第1引数を取得する組み込み関数の実装ですが、単純にValueをContextから取得します。 これによってContext上の変数を参照する、という状況が発生するわけですね。
次に、vm上の「グローバル変数を保存しておく領域」となるGetGlobalContext()に変数を保管します。 これはContext外やstatic変数で保存しようとする場合、あくまでVM上の一時的な参照領域にアクセスする事になってしまうため、処理中ならまだしも結果を受け取りたい場合処理終了後になることが多く、その場合VM上ですべて開放されているため、アクセスができない事になってしまいます。
結果、組み込み関数上でstatic変数に値を格納しようとしても、後でアクセスしようとしたときには変数がすべて初期化されていることになります。 ただこのおかげで