Arma 3のスクリプトとかミッション作成とか

Arma3のミッション作成とスクリプティング

execVMとspawnとcall

スクリプトを学ぶ上で何も考えなければ簡単だけど、気にしだすとワケワカメになる3つの呼び出しコマンド。

とりあえず初学者はexecVMでスクリプトファイルを呼び出すのが安牌。

spawnやcallは主にFunctionを呼び出すときに使用され、公式WikiのFunctionのページにどちらを使うかは書いてあるので、それに従えばOK。

以下、気になる人向け

execVM

execVM - Bohemia Interactive Community

execVMとcall, spawnの決定的な違い

spawnとcallはファンクションなどのコードを呼び出しますが、exevVMはスクリプトファイル(ex. script.sqf)の呼び出しのみ可能です。

execVMはスクリプトファイルをコードに変換し呼び出しています。

execVM "someScript.sqf"

この呼出コマンドは

spawn compile preprocessFileLineNumbers "someScript.sqf"

と同じ働きをします。

同じファイルを連続して呼び出すのは良くない

execVMは一度のみ読み込まれるスクリプトならば問題ありませんが、何度も呼び出すようなスクリプトで使用すべきではありません。
なぜなら、execVMは使用する度に呼び出し(spawn)と2つの変換作業(compile, preprocessFileLineNumbers)をさせてしまっているからです。

何度も呼び出すスクリプトはファンクションとして変数に保存し、その変数を呼び出すのが効率的です。
あまり良くない例

[_argument1] execVM "someScript.sqf";
[_argument2] execVM "someScript.sqf";
[_argument3] execVM "someScript.sqf";

改善例

KS_fnc_someFunction =  compile preprocessFileLineNumbers "someScript.sqf";
[_argument1] call KS_fnc_someFunction;
[_argument2] call KS_fnc_someFunction;
[_argument3] call KS_fnc_someFunction;

someScript.sqfのファイル内容を変数KS_fnc_someFunctionに保存し、それをファンクションとしてcallから呼び出しています。
こうすることで、変換作業を一度で済ませることができるので、CPUへの負担を減らすことができます。
(本来ならforEachなどの繰り返し構文で記述するべきですが、簡潔さのために割愛しました)

また、execVMは変換作業を必要とするために動作に若干の遅れが生じることがあります。
一度Functionとして格納し、それを呼び出すことでスムーズに動作させることが期待できるでしょう。

Call と Spawn

call - Bohemia Interactive Community
spawn - Bohemia Interactive Community

ポイント① 遅延が許可されているかどうか

callとspawnの決定的な差は、sleepやwaitUntilといった待機コマンドを使用することができるかどうかです。

  • callは待機コマンドを使用することができない。
  • spawnは待機コマンドを使用することができる。

この差はなぜなのかを簡単に説明します。
callの動きとspawnの動きを簡単な図で表すと次のようになります。

main ------> call === ------> end
    └> spawn ===

call(赤の流れ)はコマンドが処理されるのを今か今かと待ち受ける待機列で、そこでは遅延(=待機コマンド)の使用は許されません。
spawn(青の流れ)は黒の流れと並行して処理される部分で、遅延が許可されています。

せやなら自由度の高いspawnの方が便利やんけ!ってなるかもしれませんが、欠点もあります。

  • 同時に処理する項目が増えることでCPUへの負荷が大きくなる。
  • 処理される順番が記述された順通りになるとは限らない。

同時処理数は意図的でない限り少ないほうが良いので、なるべくcallを使うようにしましょう。

ポイント② 返り値の種類が異なる

callは呼び出されたファンクションの最後の値が返ってくる。
spawnスクリプトハンドルが返ってくる。

例えば次ようなコードをデバッグコンソールで実行すると、hintでプレイヤーの名前が表示されます。

KS_fnc_getPlayerName = {
    params ["_unit"];
    private _name = name _unit;
    _name
};

private _returnValue = [player] call KS_fnc_getPlayerName;
hint str _returnValue;

このコードのcallをspawnに変えた次のコードを実行すると

KS_fnc_getPlayerName = {
    params ["_unit"];
    private _name = name _unit;
    _name
};

private _returnValue = [player] spawn KS_fnc_getPlayerName;
hint str _returnValue;

次ようなコードそのものが表示されてしまいます。

params ["_unit"]; private _name = name _unit; _name

これはScript Handleというもので、コードの識別に使用されるものです。
ScriptDoneやterminateなどのコマンドで使用されます。

つまり、callやspawnはコードを実行するだけでなく、返り値を利用するために使い分けるということもあります。

これを念頭に置いて考えると、スクリプトの記述例によくある

nul = [] execVM "someScript.sqf";

といった「nul =」というのは返り値を格納するための変数というのがよくわかりますね。

EDEN Ediforなどのinit欄に挿入するコマンドでは利用しない返り値を「nul」や「0」で処理します。
スクリプトファイルではこの処置は必要ありません

その他

EDEN Editorのinit欄やデバッグコンソールはcallで呼び出されるので、sleepやwaitUntilなどは使用できません。

nul = [] spawn { code };

のように記述しましょう。