- []
- [以前のリビジョン]
KAGシナリオの処理
KAGシナリオの文法
KAGシナリオでは、(文章用のレイヤに)描画させたい通常の文字と、文字の描画速度の変更や画像の描画などの各種命令を意味するタグとで構成されています。
タグは[tagname attr1="value1" attr2="value2" attr3="value3"]といったような形式で記述され、タグの種類と属性(attribute)・値(value)のセットを半角スペースで区切りながら大括弧記号で囲みます。タグの種類は、描画速度の変更(delay)や画像の描画(image)などの大まかな命令の種類を表します。どのくらいの速度にするかとか、どのレイヤにどの画像を描画させるかといったようなことは属性と値のセットで指定していきます。
; kag3/templete/scenario/first.ks 0001: [wait time=200] 0002: *start|スタート 0003: [cm] 0004: こんにちは。
シナリオの実行
system/Initialize.tjsの末尾でfirst.ksを実行していました。
// kag3/templete/system/Initialize.tjs
275: /*
276: first.ks の実行
277: */
278:
279: kag.process("first.ks");
このkag.processはKAGWindow.tjsの2379行目から定義されています。KAGWindow.processメソッドは指定されたファイルの指定されたラベルからKAGシナリオを実行するメソッドです。2388行目と2394行目で指定のラベルまで移動しました。ここで出てきたconductorとはKAGシステムでKAGシナリオの処理を管理しているオブジェクトです。吉里吉里組み込みのKAGParserクラスを祖母に、それを継承したBaseConductorクラスを母にもっているConductorクラスのインスタンスです。よって、KAGParserクラスから引き継いだメソッドもあれば、BaseConductorクラスで追加された機能もあります。
2405, 2406行目でKAGWindowとしてシナリオを処理中に対応する状態にしておき、2407行目でシナリオの処理を開始しています。(既に走行状態なら処理開始のメソッドは呼ばない。)
// kag3/templete/system/MainWindow.tjs
2379: function process(file, label, countpage = true, immediate = false)
2380: {
2381: // 指定ファイル、指定ラベルから実行を開始する
2382: if(!usingExtraConductor) incRecordLabel(countpage);
2383: setUserSpeed();
2384:
2385: if(file != '')
2386: {
2387: // ファイルを読み込み
2388: conductor.loadScenario(file);
2389: }
2390:
2391: if(label != '')
2392: {
2393: // ラベルに移動する
2394: conductor.goToLabel(label);
2395: }
2396:
2397: if(isFirstProcess)
2398: {
2399: storeFlags(); // 一番最初の状態をストア
2400: isFirstProcess = false;
2401: }
2402:
2403:
2404: dm("処理を開始します");
2405: inSleep = false;
2406: notifyRun();
2407: if(conductor.status != conductor.mRun) conductor.run(immediate); // 実行開始
2408: }
conductor.runの実装はsystem/Conductor.tjsの363行目にあります。ここでさらにstartProcessメソッドを呼んでいます。こちらで注目してほしいのはtimerのinterval(間隔)を0にしてenabled(使用可能)にしています。Timerオブジェクトはinterval周期でメソッドを繰り返し呼ぶオブジェクトです。このインスタンスはtimerCallbackを呼ぶタイマです。
// kag3/templete/system/Conductor.tjs
0363 function run(immediate = false)
0364 {
0365 // 実行の開始
0366 // immediate=true の場合は、
0367 // このメソッドを実行したらすぐに吉里吉里に制御を戻す
0368 // (すべての関数から戻る)こと
0369 status = mRun;
0370 startProcess(immediate);
0371 }
0204: function startProcess(immediate = false)
0205: {
0206: // シナリオ進行開始
0207: // immediate = false の場合は非同期で実行を開始するので、
0208: // このメソッド内でタグハンドラが呼ばれることはない
0209: // 次のイベント配信のタイミングで最初のタグハンドラが呼ばれる。
0210: // immediate = true の場合は、このメソッド内で初回のタグハンドラが
0211: // 処理されるため、呼び出し側はこのメソッドの実行が終わったら
0212: // すぐに吉里吉里に制御を戻す(すべての関数から抜ける)ようにするべき。
0213: resetInterrupt();
0214: timer.interval = 0; // 初期インターバル
0215: timerEnabled = true;
0216: if(!_interrupted)
0217: {
0218: timer.enabled = true; // タイマー開始
0219: if(immediate)
0220: {
0221: timerCallback();
0222: }
0223: else
0224: {
0225: oneshot.mode = atmExclusive;
0226: // イベントが配信されるまで他の非同期イベントをブロック
0227: oneshot.trigger(); // トリガ
0228: }
0229: }
0230: }
timerCallbackメソッドは簡単にいうと、次に処理するタグを取得して(81行目)、onTagでタグを実行して(104行目)、その返り値によって以降の処理を変更して(105-166行目)います。
// kag3/templete/system/Conductor.tjs
0055: function timerCallback()
0056: {
0057: // 次の要素を得る
0058: nextTimerTick = timer.interval + System.getTickCount();
0059: var obj;
0060: try
0061: {
0062: if(inProcessing)
0063: {
0064: // 再入
0065: reentered = true;
0066: timer.interval = 0;
0067: return;
0068: }
0069: inProcessing = true;
0070: for(;;)
0071: {
0072: if(pendings.count > 0)
0073: {
0074: // 後回しにされたタグがある場合
0075: obj = pendings[0];
0076: pendings.erase(0);
0077: }
0078: else
0079: {
0080: // 後回しにされたタグがないので次のタグを得る
0081: obj = getNextTag(); // 次のタグを得る
0082:
0083: // getNextTag() の中で、pendings に追加された (iscript など)
0084: if(pendings.count > 0)
0085: {
0086: pendings.add(obj);
0087: continue;
0088: }
0089: }
0090:
0091: if(obj === void)
0092: {
0093: // シナリオ終了
0094: timer.enabled = false;
0095: timerEnabled =false;
0096: onStop();
0097: inProcessing = false;
0098: reentered = false;
0099: return;
0100: }
0101: else
0102: {
0103: // onTag を呼ぶ
0104: var step = onTag(obj);
0188: }
0189: }
まとめると、走行状態になると次々とタグを取得・実行していき、sタグなどでシナリオの実行が終了します。
タグハンドラ
getNextTagは次のタグを得るメソッドです。たとえば、次のタグが[image layer=2 page="fore" storage="まき 笑顔" visible=true]というタグなら、getNextTagは%["tagname" => "image", "layer" => "2", "page" => "fore", "storage" => "まき 笑顔", "visible" => "true"]という辞書配列にして返します。このとき、数値も真偽値も文字列として格納されます。
// kag3/templete/system/Conductor.tjs
0080: // 後回しにされたタグがないので次のタグを得る
0081: obj = getNextTag(); // 次のタグを得る
0103: // onTag を呼ぶ
0104: var step = onTag(obj);
0433: function onTag(elm)
0434: {
0435: // タグの処理
0436: var tagname = elm.tagname;
0437: var handler = handlers[tagname];
0438: if(handler !== void)
0439: {
0440: var ret = handler(elm);
0441: lastTagName = tagname;
0442: return ret;
0443: }
0444: return onUnknownTag(tagname, elm);
0445: }
handlersはKAGWindow.getHandlersメソッドが返す辞書配列を有しており、handlers['image']などでタグの処理の定義にアクセスできます。if, endif, ignore, endignore, else, emb, macro, endmacro, erasemacro, jump, call, returnの各タグはKAGParserに組み込まれています(C++で実装されています)が、それ以外のタグはKAGWindowクラス内のタグハンドラで処理を定義されています。
タグハンドラ群で使われているfunctionは通常の定義と違って(tjs2)式中関数と呼ばれ、直接メソッドへの参照を返します。式中関数の場合、クラス定義内で使われていてもコンテキストはglobalになるので、コンテキストをthisにしたものにします。
// kag3/templete/system/MainWindow.tjs
4551: function getHandlers()
4552: {
4553: return %[ // 辞書配列オブジェクト
5175: image : function(elm)
5176: {
5177: // 画像読み込み
5178: updateBeforeCh = 1;
5179: var start = System.getTickCount();
5180: getLayerFromElm(elm).loadImages(elm);
5181: dm(elm.storage + " の読み込みに " + (System.getTickCount() - start) + "ms かかりました");
5182: return 0;
5183: } incontextof this,
タグハンドラの返り値が0のときは、continue(再びループ)して再びgetNextTagですぐに次のタグを取得し実行されます。多くのタグハンドラが0を返します。
// kag3/templete/system/Conductor.tjs
0103 // onTag を呼ぶ
0104 var step = onTag(obj);
0105 if(step === void)
0106 throw new Exception("onTag が void を返しました (" + obj.tagname + ")"
0107 "( おそらくタグハンドラの戻り値を返し忘れた )");
0108 step = int step; // step を数値に
0109 if(step == 0)
0110 {
0111 // ウェイトを掛けずに次へ
0112 timer.interval = 0;
0113 continue;
0114 }

