第13回 蛙の子は蛙

今回は、マイクロスレッドの中からマイクロスレッドを起動した場合について話していきたいと思います。

要旨

それではいってみましょう。

マイクロスレッド内でのマイクロスレッドの起動

今回は東方っぽく、最初に、敵が開始位置へと徐々に登場するようにしてみます。これには、前回使った SetMovePosition02 が使えます。これは、指定した座標へ指定したフレーム数をかけて敵を移動させる関数でしたね。SetX, SetY を使う代わりに

SetMovePosition02(GetCenterX, GetClipMinY + 128, 120);

と書いておけば、120 フレームかけて敵が徐々に登場するようになります。

しかし、こう変更してから実行してみると、敵が登場しながら弾を出すことが分かります。なぜでしょうか? それは、登場のために動いている間、弾を撃つのを待つようにしていなかったからです。つまり、TShot の最初で、loop(120) { yield; } しておく必要があるわけです。これはもちろん TMove でも同様ですね。

以上のように変更したプログラムをここに置いておきます。ただし、移動フレーム数は wIni という変数で置いています。

このプログラムを見て、1つ気付くことがあります。それは、待つ処理をいちいち TShotTMove の2箇所に書いている点です。待ちフレーム数を変数で置いてあるものの、このように同じ処理が2箇所にあるのは良くありません。できる限り同じ処理は1箇所にまとめるべきです。これは待ちフレーム数を変数で置いたのと理由は同じで、修正箇所を減らすことにより、バグの発生を未然に防げるからです。

では、この部分を関数かサブルーチン(第10回参照)にすればいいのでしょうか? 実は、それもあまり良い解決法とは言えません。そもそも、この「待つ」という処理は「弾の発射」や「敵の移動」を担当する TShotTMove というマイクロスレッドにおいては本質的な処理ではありません。この「待つ」という処理は、可能ならば TShotTMoveに追い出してやった方がいいでしょう。

しかし、「これより外って一体どこなの?」と思われるかもしれません。確かに、今のところは外に相当する部分がありませんが、なければ作ってしまえばいいのです。つまり、敵が登場し終えるまで待つマイクロスレッド を作って、待ち終わった後に TShotTMove を起動すればいいのです。おそらく、こんな感じになるでしょう。

task TMain {
    yield;
    loop(120) { yield; }

    TShot;
    TMove;
}

ただ、ここで1つ問題があります。マイクロスレッドAの中でマイクロスレッドBを起動するとどうなるのでしょうか? これには次の2通り考えられます。

  1. マイクロスレッドBにとっては、マイクロスレッドAがメインスレッドとなる。
  2. メインスレッドは唯一であり、マイクロスレッドAとマイクロスレッドBでメインスレッドは同じ。

ところが、1 の可能性は即座に否定されます。これは、yield を使おうとしてみるとすぐ分かると思います。マイクロスレッドAがメインスレッドとなるなら、マイクロスレッドBはマイクロスレッドAの中で yield が実行された時に復帰されるはずです。しかし、マイクロスレッドAの中で yield を実行すると、マイクロスレッドAの処理が中断され、メインスレッドに戻ってしまうのでした。この2つの処理を両立することはできないので、1 の可能性はあり得ないのです。

というわけで、2 が正しいことになります。つまり、どこでマイクロスレッドを起動したかに関係なく、全てのマイクロスレッドは同一のメインスレッドを持ち、同一のメインスレッドから復帰されるのです。言い換えると、どんな場合でも前回の図 3 が成り立つわけです。

もう少し詳しく見ていきましょう。その前に、TMain を使ったプログラムをここに置いておきます。

では、今回の処理がどのような流れになるかを追ってみましょう。

先ず、@InitializeTMain が起動されます。すぐに yield があるので、TMain の処理を中断し、起動地点へ戻ります。

そして、@MainLoopyield に到達すると、中断していたマイクロスレッドを復帰します。現在起動されているマイクロスレッドは

  1. TMain

ただ1つなので、TMain が復帰されます。またすぐに yield があるので、やはり即座に TMain の処理を中断します。他に起動しているマイクロスレッドがもうないので、@MainLoop に処理が戻ります。これを 120 回続けます。

問題は、yield ループが終了して、TShot を起動するところです。TShot を起動すると、メインスレッドで起動した時と同様に yield に到達するまで処理を進めます。そして、yield に到達すると、TShot を中断し、TShot を起動した地点へ戻ります。メインスレッドで起動した時と何も変わりませんね。同じ事を、TMove でも行います。

そして、マイクロスレッドの終端に達したので、TMain を終了します。

それでは、この後どうなるのでしょうか? 現在マイクロスレッド TShotTMove が起動していますが、既に最初の1回を実行しているので、同じフレームでこれらが復帰されてはたまりません。なので、これらは復帰されず、メインスレッドに戻るはずです。

そして、次のフレームに移り、メインスレッドで yield が実行されると、マイクロスレッドが復帰されます。現在のところマイクロスレッドは

  1. TShot
  2. TMove

の2つなので、これらが順番に復帰されます。あとは、前回と同じ状況になります。

長々と処理を追ってみましたが、要するにメインスレッド内でマイクロスレッドを起動しても、マイクロスレッド内でマイクロスレッドを起動しても、大して何も変わらないということです。どこでマイクロスレッドを起動したかというのは、特に意識する必要はないのです。

マイクロスレッドの復帰順

最後に、マイクロスレッドの復帰順について補足しておきます。繰り返しますが、復帰順は処理の順番が重要になる場合にしか意識する必要はありません

先ず、メインスレッドでのみマイクロスレッドを起動した場合は、起動した順に復帰されるのでしたね。

これに対し、マイクロスレッドAで動されたマイクロスレッドBは、マイクロスレッドAの直前に復帰されます。複数起動した場合は、やはり先に起動した方が先に復帰されます。

例えば、上の例で考えて見ましょう。TShot を起動すると、その時点での復帰順は次のようになります。

  1. TShot
  2. TMain

TShotTMain から起動されたので、TMain の前に復帰されるわけです。

そして、TMove を起動すると、復帰順は次のようになります。

  1. TShot
  2. TMove
  3. TMain

TMoveTMain の前に差し込まれることにより、TMoveTShot の後に復帰されることが保証されます。

で、TMain が終わることにより、復帰順は

  1. TShot
  2. TMove

となるわけです。TMain のあった場所の後には復帰すべきマイクロスレッドがないので、このフレームでの復帰はここで終了されます。TMain より前に TShotTMove が差し込まれることによって、自然に TShotTMove が実行されないようになっています。

要旨

ここまでの知識を使っただけでも、それなりの弾幕が作れると思います。そこで、次回からは実際に何か1つスペルカードを作ってみることにしましょう。

第12回 | 第14回 | 目次へ戻る

この講座はロベールが作成しました。
inserted by FC2 system