無限再帰への対処

Issue #117 resolved
k4nagatsuki repo owner created an issue
  1. 自分自身を呼び出すパッケージを作成する
  2. そのパッケージを実行する

これで無限再帰という現象が起こり、エンジンが落ちる(リンク系では起こらない)。

具体的には、次のような事が起こっています。

スタートやパッケージの呼び出しコンテントは、実際に呼び出し先のイベントを実行した後、処理の流れを元の位置へ戻す必要があります。例えば「メッセージ1→パッケージ1の呼び出し→メッセージ2→…」というイベントツリーでは、呼び出しの後でメッセージ2以降のイベントを実行する必要があるため、パッケージ1の実行後、メッセージ2の直前まで処理の流れが戻ってきます。

これを実現するには、パッケージの実行前に「どこへ戻るのか」を記録しておかなければなりません。単純に1つだけ記録するのは上手くいきません。パッケージ1の中にパッケージ2への呼び出しがあり、さらにその中にパッケージ3の呼び出しがある……というような場合、3の実行から2の位置へ戻り、さらに1の位置へ戻らなければならないからです。つまり戻り位置は何個も記憶できるようにする必要があります。そして、記憶した順番とは逆の順番で戻り位置を思い出さなければなりません。

呼び出しの順序: 元のイベント → パッケージ1 → パッケージ2 → パッケージ3

戻り順序: 元のイベント ← パッケージ1 ← パッケージ2 ← パッケージ3

これを実現するにはスタックという仕組みを使います。スタックはリストの一種で、中身を取り出す時、後から入れたものほど先に出てくるという性質を持っています。

追加の順序は次のようになります。1、2、3、4の順です。

[戻り位置1][戻り位置2][戻り位置3] ← [戻り位置4]…

取り出しの順序は逆に4、3、2、1です。

[戻り位置1][戻り位置2] … [戻り位置3] …[戻り位置4] →

プログラムにおける関数のコールでは、このようにして戻り位置をスタックへ溜めていき、実行が終わるたびに戻り位置を取り出します。ここで、自分自身を呼び出す(これを再帰呼び出しといいます)関数があると、途中で状態が変わって自分自身の呼び出しが無くならない限り、無限に自分自身を呼び出し続け、従って戻り位置も無限に溜まっていきます。

def func():
    func()

戻り位置のスタック: [funcの1行目][funcの1行目][funcの1行目] ← [funcの1行目]… (以後無限に続く)

もちろん無限に溜める事は不可能なので、途中でスタックが一杯になってエラーになります。これをスタックオーバーフローといいます。

CardWirthでは、冒頭に書いたように、自分自身を呼び出すパッケージを用意すれば、容易にこのスタックオーバーフローを発生させるシナリオを作れます。バグとして作ってしまう場合もあります。しかしゲームのプレイ中にエンジンを落としてしまうわけにはいかないので、(ここからは推測ですが)呼び出し系のイベントコンテントの実行の際、ある程度スタックの内容が溜まっていたら呼び出しの前にウェイトを入れるようになっています。1.28で100回程度スタートやパッケージの呼び出しが重なると実行速度が極端に遅くなるのはこのためです。1.50では遅くならなくなりましたが、原理上問題の回避は不可能なので、おそらくこの制限を緩和しただけだと思われます。

CWPyでは、現在のところこれに一切対処していません(従ってエラーで落ちます)。対処する方法は次の二つが考えられます。

  1. 実行速度を制限してスタックが溜まる速度を落とし、一杯になる前にプレイヤーがF9等で対処できるようにする
  2. 呼び出し時にある程度スタックが溜まっていたらエラーメッセージを表示してイベントを中止する

実際には数百万回も再帰呼び出しが重なるのは明らかにシナリオのバグなので、次のようなメッセージを出してエラーにしてしまうのがよいのではないかと考えています。

「パッケージ・リンクの呼び出し回数がn回を超えたので処理を中止します。イベントが無限ループになっていないか確認してください」

これについてもっとよい対処方法やメッセージ等ありましたらご意見ください。

Comments (5)

  1. k4nagatsuki reporter

    パッケージに関するイベントフローを変更すればすぐ限界の来る再帰を避けられそうです。戻り点をシステムのスタックではなくPythonランタイム側の配列に記録する事で、少なくとも現実的な時間では到達可能でない程度には限界を伸ばす事ができます。警告メッセージも追加しますが、見ることはほとんど不可能となるでしょう。

    これはリスクの大きい変更なので、β6のリリースを行った後に実施します。

  2. takuto_cw

    あわわわ…ずっと放置してました、ごめんなさい! せっかく丁寧な解説もつけてくださっていたというのに……

    まずはテストに良さそうなシナリオ探してきます。


    自分のパソコン、OS が変わりました。XP + 7(64bit) のデュアルブート環境です。テストプレイにはまだ XP を使います。サポート終了後の XP 起動時には、モデムの電源切って余計なファイルに触らないようにして。自己責任で使い続けようと思っています。

    XP を手放すときは一声かけます。そのとき他に XP 使いがいなかったら、readme の「次のOSで動作確認を行っています」を書き換える、ということで。

    構築にかなり手間取ってしまいましたが(次のクリーンインストール時のためにメモをとっていたら、ちっとも進まない……)、大方終わったので、そろそろまた開発に戻れそうです。

  3. k4nagatsuki reporter

    よろしくお願いします。テストケースとしてパターンを作ってのテストなら行いましたので、後は適当にシナリオを遊んで問題が出なければいいくらいの気持ちで試していただけるとありがたいです。

    対応OSの事で気を遣っていただきありがとうございます。XPについては、私のところにXPModeもあるのでもうしばらくは大丈夫ですよ(それもサポート切れ時期には変わりないですが)。

  4. Log in to comment