今回は、マイクロスレッドの中からマイクロスレッドを起動した場合について話していきたいと思います。
yield
すると、マイクロスレッド内で起動したマイクロスレッドを含め、全てのマイクロスレッドが順番に復帰されます。それではいってみましょう。
今回は東方っぽく、最初に、敵が開始位置へと徐々に登場するようにしてみます。これには、前回使った SetMovePosition02
が使えます。これは、指定した座標へ指定したフレーム数をかけて敵を移動させる関数でしたね。SetX
, SetY
を使う代わりに
SetMovePosition02(GetCenterX, GetClipMinY + 128, 120);
と書いておけば、120 フレームかけて敵が徐々に登場するようになります。
しかし、こう変更してから実行してみると、敵が登場しながら弾を出すことが分かります。なぜでしょうか? それは、登場のために動いている間、弾を撃つのを待つようにしていなかったからです。つまり、TShot
の最初で、loop(120) { yield; }
しておく必要があるわけです。これはもちろん TMove
でも同様ですね。
以上のように変更したプログラムをここに置いておきます。ただし、移動フレーム数は wIni
という変数で置いています。
このプログラムを見て、1つ気付くことがあります。それは、待つ処理をいちいち TShot
と TMove
の2箇所に書いている点です。待ちフレーム数を変数で置いてあるものの、このように同じ処理が2箇所にあるのは良くありません。できる限り同じ処理は1箇所にまとめるべきです。これは待ちフレーム数を変数で置いたのと理由は同じで、修正箇所を減らすことにより、バグの発生を未然に防げるからです。
では、この部分を関数かサブルーチン(第10回参照)にすればいいのでしょうか? 実は、それもあまり良い解決法とは言えません。そもそも、この「待つ」という処理は「弾の発射」や「敵の移動」を担当する TShot
と TMove
というマイクロスレッドにおいては本質的な処理ではありません。この「待つ」という処理は、可能ならば TShot
と TMove
の外に追い出してやった方がいいでしょう。
しかし、「これより外って一体どこなの?」と思われるかもしれません。確かに、今のところは外に相当する部分がありませんが、なければ作ってしまえばいいのです。つまり、敵が登場し終えるまで待つマイクロスレッド を作って、待ち終わった後に TShot
と TMove
を起動すればいいのです。おそらく、こんな感じになるでしょう。
task TMain { yield; loop(120) { yield; } TShot; TMove; }
ただ、ここで1つ問題があります。マイクロスレッドAの中でマイクロスレッドBを起動するとどうなるのでしょうか? これには次の2通り考えられます。
ところが、1 の可能性は即座に否定されます。これは、yield
を使おうとしてみるとすぐ分かると思います。マイクロスレッドAがメインスレッドとなるなら、マイクロスレッドBはマイクロスレッドAの中で yield
が実行された時に復帰されるはずです。しかし、マイクロスレッドAの中で yield
を実行すると、マイクロスレッドAの処理が中断され、メインスレッドに戻ってしまうのでした。この2つの処理を両立することはできないので、1 の可能性はあり得ないのです。
というわけで、2 が正しいことになります。つまり、どこでマイクロスレッドを起動したかに関係なく、全てのマイクロスレッドは同一のメインスレッドを持ち、同一のメインスレッドから復帰されるのです。言い換えると、どんな場合でも前回の図 3 が成り立つわけです。
もう少し詳しく見ていきましょう。その前に、TMain
を使ったプログラムをここに置いておきます。
では、今回の処理がどのような流れになるかを追ってみましょう。
先ず、@Initialize
で TMain
が起動されます。すぐに yield
があるので、TMain
の処理を中断し、起動地点へ戻ります。
そして、@MainLoop
の yield に到達すると、中断していたマイクロスレッドを復帰します。現在起動されているマイクロスレッドは
TMain
ただ1つなので、TMain
が復帰されます。またすぐに yield
があるので、やはり即座に TMain
の処理を中断します。他に起動しているマイクロスレッドがもうないので、@MainLoop
に処理が戻ります。これを 120 回続けます。
問題は、yield
ループが終了して、TShot
を起動するところです。TShot
を起動すると、メインスレッドで起動した時と同様に yield
に到達するまで処理を進めます。そして、yield
に到達すると、TShot
を中断し、TShot
を起動した地点へ戻ります。メインスレッドで起動した時と何も変わりませんね。同じ事を、TMove
でも行います。
そして、マイクロスレッドの終端に達したので、TMain
を終了します。
それでは、この後どうなるのでしょうか? 現在マイクロスレッド TShot
と TMove
が起動していますが、既に最初の1回を実行しているので、同じフレームでこれらが復帰されてはたまりません。なので、これらは復帰されず、メインスレッドに戻るはずです。
そして、次のフレームに移り、メインスレッドで yield
が実行されると、マイクロスレッドが復帰されます。現在のところマイクロスレッドは
TShot
TMove
の2つなので、これらが順番に復帰されます。あとは、前回と同じ状況になります。
長々と処理を追ってみましたが、要するにメインスレッド内でマイクロスレッドを起動しても、マイクロスレッド内でマイクロスレッドを起動しても、大して何も変わらないということです。どこでマイクロスレッドを起動したかというのは、特に意識する必要はないのです。
最後に、マイクロスレッドの復帰順について補足しておきます。繰り返しますが、復帰順は処理の順番が重要になる場合にしか意識する必要はありません。
先ず、メインスレッドでのみマイクロスレッドを起動した場合は、起動した順に復帰されるのでしたね。
これに対し、マイクロスレッドAで動されたマイクロスレッドBは、マイクロスレッドAの直前に復帰されます。複数起動した場合は、やはり先に起動した方が先に復帰されます。
例えば、上の例で考えて見ましょう。TShot
を起動すると、その時点での復帰順は次のようになります。
TShot
TMain
TShot
は TMain
から起動されたので、TMain
の前に復帰されるわけです。
そして、TMove
を起動すると、復帰順は次のようになります。
TShot
TMove
TMain
TMove
が TMain
の前に差し込まれることにより、TMove
が TShot
の後に復帰されることが保証されます。
で、TMain
が終わることにより、復帰順は
TShot
TMove
となるわけです。TMain
のあった場所の後には復帰すべきマイクロスレッドがないので、このフレームでの復帰はここで終了されます。TMain
より前に TShot
や TMove
が差し込まれることによって、自然に TShot
や TMove
が実行されないようになっています。
yield
すると、マイクロスレッド内で起動したマイクロスレッドを含め、全てのマイクロスレッドが順番に復帰されます。ここまでの知識を使っただけでも、それなりの弾幕が作れると思います。そこで、次回からは実際に何か1つスペルカードを作ってみることにしましょう。