編成を解結する話・その3
2006/7/11 | 投稿者: ghost
またこれの続き。今回が最後。
これまでにも何度か書いたことだが、ボクがこうしてVRMスクリプトについて書いていることは、全てブラックボックス的な解析に拠っている。つまり、I.MAGiCから何かを特別に教えてもらっているワケではないし(VRM4EGの出版後は、I.MAGiCと直接会話することは敢えて避けている)、ましてやVRMを逆コンパイルして調べているワケでもない。なので、どうしてもわからないこと、だって少なからず存在する。今日はそんなお話。
編成の連結に際して、妙な挙動が気になっている。[編成A]を[編成B]にぶつけて連結し、数秒待って再び分割する、というケースを想定して欲しい。で、以下のようなスクリプトを書くとする。
以上のスクリプトを組み込んだレイアウトで、[編成A]を走行させて、停止している[編成B]にぶつけると、以下のような流れで処理が進行する。
で。
実際に動かすと、なぜか「分割します」が2つ表示される。しかも、きっかり3秒の間を空けて、だ。つまりメソッド「分割」は、メソッド「分割準備」の実行から数えて3秒後と6秒後の2回実行されていることになる。全体の動作が意図通りなのは、2回目に実行されるUncouple命令が空振り(既にその位置で切り離されているので無効)に終わるからである。
なぜこんなことになるのか。ヒントになるのは、どうもこの現象を引き起こすのはUncouple命令らしい、という点だ。メソッド「分割」のUncouple命令を、たとえばSetVoltage命令に置き換えてやる、つまり連結後3秒後に走り出す、に改めると「分割します」のメッセージ(は既にこの時点で嘘になっているが)は1回しか表示されなくなる。つまり、SetEventAfter命令の本来の仕様通り、指定時間後に1回だけメソッドを実行する、が守られるのである。
ここから考えられるのは、SetEventAfter命令が「何を以って自分の動作が完了したと判断するか」と、Uncouple命令が成功した場合の内部的な動作に相性の悪い「何か」があるのではないか、という仮説だ。たとえば、SetEventAfter命令は自分が実行したメソッドの処理が「EndFunc」に至ったことを以って自分の為すべきことを終えた、と判断するのだが、Uncoupleによってメソッド「分割」を含む[編成B]がメモリ内で複製されることにより、これを見失ってしまう。で、SetEventAfterからすると、まだ自分がやるべきことをやっていないつもりで、再び3秒待って「分割」を実行するのだが、今度はUncoupleが空振りするので正常に終了する、つまり、これが最後の実行になる、という寸法。しかし、この仮説を検証する手立てが思い浮かばない。
まぁ、大半のユーザーにとってはどうでもいい話だとは思うが、メソッド「分割」に、Uncouple以外の他の命令も含まれていて、かつそれが2回実行されると具合が悪い場合、問題になるかも知れないので書いておくことにする。
ちなみに回避は比較的簡単で、天狗亭氏もここのコメント欄に書いてくれているが、ifzero命令で変数TmpTrainを評価し、分割の成否に応じて処理を続行するか止めておくかを決めればいい。
ただし、この方法は2回目のUncoupleが失敗するのが自明である場合のみ有効だ。つまり、切り離し位置が固定値でなく変動値であれば、2回目以降も分割が成功する場合もあり得る。幸いにして、この現象の最中にあってもその他のスクリプトの挙動はまともなように見えるので、編成にメソッド「分割」の実行回数を記録するグローバル変数を用意し、「分割」の冒頭でこれをaddして、1回目ならば実行、2回目以降ならば何もせず終了、とすれば副作用の発生を抑えられるだろう。
っつーことで、この件で何か新しい知見を得た人はボクにも教えてね。
0
これまでにも何度か書いたことだが、ボクがこうしてVRMスクリプトについて書いていることは、全てブラックボックス的な解析に拠っている。つまり、I.MAGiCから何かを特別に教えてもらっているワケではないし(VRM4EGの出版後は、I.MAGiCと直接会話することは敢えて避けている)、ましてやVRMを逆コンパイルして調べているワケでもない。なので、どうしてもわからないこと、だって少なからず存在する。今日はそんなお話。
* * *
編成の連結に際して、妙な挙動が気になっている。[編成A]を[編成B]にぶつけて連結し、数秒待って再び分割する、というケースを想定して欲しい。で、以下のようなスクリプトを書くとする。
[[編成B]スクリプト]
//call用にレイアウトオブジェクトを取得
VarLayout ObjLayout
getlayout ObjLayout
//連結イベント設定
Var EventID
SetEventCouple 連結 EventID
//連結イベントで実行されるメソッド
BeginFunc 連結
//分割のタイマーを設定する
call ObjLayout 分割準備
//連結イベントを解除する
KillEvent EventID
EndFunc
//分割をおこなうメソッド
BeginFunc 分割
//デバッグメッセージ
DrawMessage "分割します"
//編成を分割し、新編成をレイアウトオブジェクトの変数に収める
VarTrain TmpTrain
Uncouple {分割位置} TmpTrain
get LAYOUT.NewTrain TmpTrain
EndFunc
[レイアウトスクリプト]
VarTrain NewTrain
//3秒後に[編成B]の「分割」メソッドを実行する
BeginFunc 分割準備
Var TimerID
SetEventAfter [編成B] 分割 TimerID 3000
EndFunc
以上のスクリプトを組み込んだレイアウトで、[編成A]を走行させて、停止している[編成B]にぶつけると、以下のような流れで処理が進行する。
(1) [編成A]と[編成B]が連結され、[編成A]は消失する。
(2) 連結イベントが[編成B]で発動し、メソッド「連結」が実行される。
(3) (2)によってレイアウトオブジェクトののメソッド「分割準備」が実行される。
(4) (3)によって3秒後に[編成B]のメソッド「分割」が実行されるイベントが設定される。
(5) 3秒後、(4)のイベントによってメソッド「分割」が実行され、[編成B]から[編成B_1]が生まれる。
(6) [編成B_1]の編成IDがレイアウトオブジェクトのグローバル変数「NewTrain」に記録され、利用可能となる。
で。
実際に動かすと、なぜか「分割します」が2つ表示される。しかも、きっかり3秒の間を空けて、だ。つまりメソッド「分割」は、メソッド「分割準備」の実行から数えて3秒後と6秒後の2回実行されていることになる。全体の動作が意図通りなのは、2回目に実行されるUncouple命令が空振り(既にその位置で切り離されているので無効)に終わるからである。
ちなみに。当然2回目のUncouple実行時の変数TmpTrainの中身は、分割に失敗するのでヌル(0)である。となると、続くgetでレイアウトオブジェクトの変数NewTrainもヌルで上書きされるのではないか、と思うところだが、実はそうならない。どうも、get命令は第二引数のオブジェクト参照がヌルの場合、動作しないという仕様らしい。
さて。なぜこんなことになるのか。ヒントになるのは、どうもこの現象を引き起こすのはUncouple命令らしい、という点だ。メソッド「分割」のUncouple命令を、たとえばSetVoltage命令に置き換えてやる、つまり連結後3秒後に走り出す、に改めると「分割します」のメッセージ(は既にこの時点で嘘になっているが)は1回しか表示されなくなる。つまり、SetEventAfter命令の本来の仕様通り、指定時間後に1回だけメソッドを実行する、が守られるのである。
ここから考えられるのは、SetEventAfter命令が「何を以って自分の動作が完了したと判断するか」と、Uncouple命令が成功した場合の内部的な動作に相性の悪い「何か」があるのではないか、という仮説だ。たとえば、SetEventAfter命令は自分が実行したメソッドの処理が「EndFunc」に至ったことを以って自分の為すべきことを終えた、と判断するのだが、Uncoupleによってメソッド「分割」を含む[編成B]がメモリ内で複製されることにより、これを見失ってしまう。で、SetEventAfterからすると、まだ自分がやるべきことをやっていないつもりで、再び3秒待って「分割」を実行するのだが、今度はUncoupleが空振りするので正常に終了する、つまり、これが最後の実行になる、という寸法。しかし、この仮説を検証する手立てが思い浮かばない。
まぁ、大半のユーザーにとってはどうでもいい話だとは思うが、メソッド「分割」に、Uncouple以外の他の命令も含まれていて、かつそれが2回実行されると具合が悪い場合、問題になるかも知れないので書いておくことにする。
ちなみに回避は比較的簡単で、天狗亭氏もここのコメント欄に書いてくれているが、ifzero命令で変数TmpTrainを評価し、分割の成否に応じて処理を続行するか止めておくかを決めればいい。
ただし、この方法は2回目のUncoupleが失敗するのが自明である場合のみ有効だ。つまり、切り離し位置が固定値でなく変動値であれば、2回目以降も分割が成功する場合もあり得る。幸いにして、この現象の最中にあってもその他のスクリプトの挙動はまともなように見えるので、編成にメソッド「分割」の実行回数を記録するグローバル変数を用意し、「分割」の冒頭でこれをaddして、1回目ならば実行、2回目以降ならば何もせず終了、とすれば副作用の発生を抑えられるだろう。
っつーことで、この件で何か新しい知見を得た人はボクにも教えてね。

この記事へのトラックバックURLはありません
トラックバック一覧とは、この記事にリンクしている関連ページの一覧です。あなたの記事をここに掲載したいときは、「記事を投稿してこのページにお知らせする」ボタンを押して記事を投稿するか(AutoPageを持っている方のみ)、記事の投稿のときに上のトラックバックURLを送信して投稿してください。
→トラックバックのより詳しい説明へ
→トラックバックのより詳しい説明へ
2008/11/8 21:15
タイトルは最後に紹介する記事のパクリです(笑)。
さて、USO800鉄道氏から「西濃鉄道ホッキホキ(仮称)」の修正変更依頼がありまして、その中の一部がちょっと無理そうだなぁと思う部分がありました。そこで、現状の「西濃鉄道ホッキホキ(仮称)」のスクリプトを
さて、USO800鉄道氏から「西濃鉄道ホッキホキ(仮称)」の修正変更依頼がありまして、その中の一部がちょっと無理そうだなぁと思う部分がありました。そこで、現状の「西濃鉄道ホッキホキ(仮称)」のスクリプトを
2006/7/18 19:04
結論から言うと、SetEventAfter命令(おそらくは、オブジェクト指定可能なSetEvent系命令すべて)の仕様について、誤解していたらしいことに気づいたので報告しておきます。
×誤解・SetEvent〜命令によって設定されたイベント(ある条件が整うと指定したメソッドを実行...
×誤解・SetEvent〜命令によって設定されたイベント(ある条件が整うと指定したメソッドを実行...
2006/7/13 16:29
VRM入道の「編成を解結する話・その3」を読んで、自分も記事に載っていたスクリプトをコピペして試してみました。 この記事の内容は大まかには、Uncouple命令を含むメソッドをSetEventAfter命令で一度実行するようにスクリプトを書くと、そのメソッドが一度でなく繰り...