2006/3/29

自分自身をコールするメソッドの危険性  VRMスクリプト禅問答
「VRM侍」に連載されていた「スクリプト禅問答」を一から辿っているのだが、これがま〜、見事なまでにはまるはまる。

はまってくださるのは嬉しい限りなのだが、油断している間に「連載されていた」と過去形で語られてしまったので、慌てて書いてみるテスト(笑)。

【怎麼生】
I.MAGiCスクリプト会議室メッセージNo.220で言及している「メソッドの中でそのメソッド自身をcallする」ことの危うさとは、実のところどういうことですか?

【説破】 4.0.3.2
危ないと言っても、それでPCが壊れたりレイアウトデータが消えてしまったりするような話ではありません。
一方で、この手法は注意して使わないと、心臓に悪いトラブルの元になったり、VRM4の評判を落とすことにもなり兼ねないので、少し説明しておくことにします。
なお、以下の説明は、一般的なVRM4ユーザーはあまり気にする必要はありません。アクロバティックにVRM4スクリプトを使いこなすことに喜びを感じる方のみ、気をつければ済む話です。


まず「メソッド自身をcallする」ことの意味と意図をおさらいしておきましょう。以下のサンプルコードを読んでください。
[編成スクリプト]
Var VarResourceNo
//何かキーを押すと以下のメソッドが実行されると思ってね
BeginFunc MtdChangeTexture
add VarResourceNo 1 //VarReourceNoを1つ増やす
//VarReourceNoが1だったら
ifeq VarResourceNo 1
CallCar MtdResourceNo1
endif
//VarReourceNoが2だったら
ifeq VarResourceNo 2
CallCar MtdResourceNo2
endif
//VarReourceNoが3だったら
ifeq VarResourceNo 3
CallCar MtdResourceNo3
endif
//VarReourceNoが4になったら、0に戻してメソッドを再実行
ifeq VarResourceNo 4
set VarResourceNo 0
call this MtdChangeTexture
endif
EndFunc
上掲のサンプルコードでメソッドMtdChangeTextureがキーイベント等から実行されると、変数VarResourceNoが1ずつ増えます。これをifeq命令でチェックして、変数の値に応じた車両スクリプトのメソッドを実行します。車両スクリプト側にはSetSignTexture命令があると思えば、これが順に方向幕を切り替えるスクリプトの制御部に当たることがわかります。
問題の部分は ifeq VarResourceNo 4、つまり、メソッド冒頭のadd命令で変数の中身が4に至った場合(直前まで3だった場合)の動作です。ここで、変数に0をセットして、自分自身(メソッドMtdChangeTexture)をcallしています。
この結果、再び冒頭のaddによって変数の値は1になります。つまり、このメソッドを実行する都度、VarResourceNoの中身が1,2,3,1,2,3・・・と変化を繰り返し、その値に応じた車両スクリプトのメソッドMtdResourceNo1〜3がCallCarされることになります。

実は、ifeq VarResourceNo 4以降を・・・
//自分自身をcallしない場合の例
ifeq VarResourceNo 4
set VarResourceNo 1
CallCar MtdResourceNo1
endif
と書いても、動作はまったく同じになります。敢えて上の例のような書き方をしているのは、読みやすさを優先してのことです。つまり、ifeq VarResourceNo 1〜3を数字の順番に並べて書いた方が、後で読んだときに意図がわかりやすい、と思ってこういう書き方をしているワケです。下の書き方だと、値が1に戻る場合のみ書き方が異なるので、スクリプトを書いた直後ならばともかく、しばらく経ってから読み直すと、何を意図して書いたコードなのかわかりにくくなりがちです。

ただし、この書き方には特有の怖さがあります。
上の例ではcall this MtdChangeTextureする直前にVarResourceNoの中身を0にしていますから、次にメソッドが実行されたとき、冒頭のaddで値が1になり、繰り返してcall this MtdChangeTextureが実行されないことが保障されます。これなら大丈夫です(厳密には言い切れません、後述)。
が、これが保障されない場合、例えば冒頭のaddが無条件に実行されるのではなく、何らかの条件(if系命令)を伴っていて、その条件が満たされない場合がありえたり、あるいは他のオブジェクトからVarResourceNoの値が変更されることがある場合、call this MtdChangeTextureが何度も繰り返して実行されてしまう「無限ループ」に陥る可能性が生じます。

で、このときのVRM内部での動作が・・・

クリックすると元のサイズで表示します

こうであれば、さして害はなくて(いや、これも害はありますが)なんかビュワーが重いな、で済むはずなのですが、実際には、

クリックすると元のサイズで表示します

メモリ上でメソッドの構造(少なくともローカル変数については間違いなく)が複製されて、そちらに制御が移り、その実行が終わってから元のメソッドに戻ってくるんですな。
したがって、これが無限ループすると・・・

クリックすると元のサイズで表示します

こうなります。マニュアルの記載を信じると「メソッドの呼び出しの深さ(上図における積み重ねの数)」に最大15の制約があるとされていますが、おそらくそれに達するか、VRMビュワーが確保しているスクリプト実行用の領域を食い尽くした時点で、何か嫌なコトが起きます。私自身は敢えて試していません、この実験がトラウマになっているので(笑)。試してみたい方は自己責任でどうぞ。

で。
何が危ういかと言うと、一見して無限ループするスクリプトならばまだいいんですが、ある稀な条件が成立した場合に限り無限ループするスクリプトを含むレイアウトをネット等を介して他人に提供してしまうと、これを遊ぶ人にとっては「VRM4のバグ」にしか見えないんですね、どう考えても。普通の人は、ユーザーが書いたスクリプトでこういう現象を起こせることを知りませんから。これはPCでは何事にも通じる話ではありますが。

一方で、前述したようにこの書き方特有の「読みやすさ」というメリットもあるので、この書き方は絶対駄目!!というものでもないワケです。そういう意味で気をつけて使わないとね、という、有り体に言えばどうでもいいネタでした。
0

2006/3/29  10:58

投稿者:ghost
> VarResourceNoが4のままでMtdChangeTextureを呼んでしまうと(中略)無限ループと言うことですか?

このエントリに例示されているメソッドについて言うと、メソッド冒頭にadd VarResourceNo 1があるので、4のまま再帰呼び出しをしてもこのaddで値が5になりますから、すべてのifeqをスルーして処理の終了が保証されます。つまり、無限ループにはなりません。

VarResourceNoはグローバル変数ですから、このメソッド以外のメソッドからでもその値を変更することが出来ます。問題になるのは、他のメソッドがVarResourceNoの値を3にしてしまうことが有り得る場合でしょうか。これがこのメソッドの実行に併走すると、無限ループ化する可能性が出てきます。
外部要因については、明示的に3をsetしている場合はわかりやすいですが(これも意図せずにそう書いてしまった場合や、他のオブジェクトからやっている場合は、発見が困難です)これが何らかの計算の結果であったりすると、直感的な把握が極めて難しくなります。

2006/3/29  9:13

投稿者:moko
>>わからないままにわかった気になってしまう
確認させてもらって正解でした。(--;)

何度も申し訳ないですが、もう一度確認させてください。

VarResourceNoが4のままでMtdChangeTextureを呼んでしまうと、
呼ばれる度にcall this MtdChangeTextureを実行して、
結果無限ループと言うことですか?

2006/3/28  22:58

投稿者:ghost
問題となるのは自分自身を呼び出すcall命令です。>moko殿
つまり・・・

call this {このcall命令が含まれるメソッド}

という書き方。
これを一般に「再帰呼び出し」と言い、上手に使えば見た目に綺麗な(必ずしも高機能、と言うワケではない)コーディングが出来ます。が、このエントリに書いたような問題を孕んでいます。
再帰呼び出しを安全におこなうには、

・再帰しなくなる条件が明確に存在する(サンプルコードではifeq VarResourceNo 4にならないことがコレに当たる)
・その条件がかならず満たされることが保証される(同じく、VarResourceNoが外部から値を書き換えない限り、再帰呼び出し時に必ず1になること)

の2つが必要です。これが満たせないコードには予期せぬ無限ループ(その結果としてのメモリ資源の食い尽くし)の可能性があることになります。

まぁ、死にはしないし、逆に言うと無限ループを仮に起こしたとしても、ユーザーの大半はその責任がVRM4自身にあるのかレイアウト作者にあるのかがわからない、ということでもある(それ以前に「無限ループ」であるかどうかも判断できないでしょう)ので、何をやってもいいんじゃないでしょうか(ぉぃ!!)。

冗談はさておき。
ボクが言いたかったのは、危ないことをするな、ではなく、既存の他者の手になるコードを真似ていくときは、少しずつで良いので、その意味を自分なりに考えてみましょう、というお話です。わからないことがあれば何でも聞いてください。
本当の危険性は、VRMに限らず物事へ挑む姿勢として、わからないことをわからないままにわかった気になってしまう癖を、VRMスクリプトで養ってしまうことに潜んでいます。

ちょっと説教臭いな、我ながら。
ま、みんな楽しくVRMスクリプトをハックしましょうと。そういうことです(w

2006/3/28  22:01

投稿者:Kaoru
 無限ループですか……先日やってしまいました。
でも「このスクリプト。絶対、無限ループに陥るだろうな」と、確信した状態でやってましたから「思ったとおりフリーズしてしまったか」でした。

 ghostさんが危惧しているように、稀な条件が成立した場合無限ループに陥るのは、作者も実際にその現場に立ち会わなければ解らないでしょうし、不幸にも立ち会った人には「VRM4のバグ」としか見えないでしょうね。

 プログラムにはバグはつきものだと割り切るしかないのかもしれません。今後、複雑なスクリプトを用いたレイアウトが増えることにより、このような問題が起こる可能性が増えることが予想されます。

http://wind.ap.teacup.com/asakaze_blog/

2006/3/28  21:31

投稿者:moko
少なくとも「評判を落とす原因になり得る者」の一人はボクですね・・・。
気をつけなければ。(--;)

脳無な所為か完全に理解できていないので確認させて下さい。
call this メゾット名 で呼び出せば危険性は少ないと言うことですか?

2006/3/28  20:16

投稿者:天狗亭
>過去形で語られてしまったので
「スクリプト禅問答」を亡き者にし、その後釜を狙うべく(!)書いたわけではなくて、単に「目指せ完全自動運転(12)−前回の解答と連載終了宣言」を見て、手が無意識に動いちまいますた。

因みに、この回の検証は、精神衛生上「パス」させていただきます。^^;

http://blue.ap.teacup.com/tengutei/

コメントを書く

この記事にはコメントを投稿できません




teacup.ブログ “AutoPage”
AutoPage最新お知らせ