ゲームオーバー時の処理
ゲームオーバー状態に遷移してから3秒後にメニュー表示に戻るコードを考えていて、思いついたのが下記のコード。
struct GameOver : msmf::state<>{
template <class Event, class Fsm>
void on_entry(Event const& ev, Fsm& )
{
GameMain^ g = ev.getGameMain();
timer_task_ = Concurrency::create_task(
[=]() -> void {
Concurrency::wait(1000 * 3);
}
).then([=]{
g->GetStateMachine().process_event(ev::TimeOver(g));
});
}
private:
Concurrency::task<void> timer_task_;
};
3秒後にthen()のラムダ式が呼ばれ、イベントを発行する。しかしこのコード、うまく動作しない。イベント発効後、Platform::Collections::Vectorをクリアするのだけれども、そこで例外が発生するのだ。試しにtaskを使わず直接イベントを発行すると問題ない。
実行時のスレッドコンテキスト
taskを使うとおそらくUMSでスケジューリングされるんだろうけれども、おそらくthen()を実行するときのスレッドコンテキストがVectorでアイテムを挿入したときと異なるから例外が出ていそうな気がする。調べるとcreate_task()で作られたtaskの実行時のコンテキストは既定ではバックグラウンド・タスクに割り当てられるとのことである。
IAsyncAction または IAsyncOperation を返さないタスクは、アパートメントを認識しません。これらのタスクの後続タスクは、既定では、利用可能な最初のバックグラウンド スレッドで実行されます。
then()については実行時のコンテキストを指定できる。Concurrency::task_continuation_context::use_current()を指定してカレントでthen()節を実行させるとうまくいった。
struct GameOver : msmf::state<>{
template <class Event, class Fsm>
void on_entry(Event const& ev, Fsm& )
{
GameMain^ g = ev.getGameMain();
timer_task_ = Concurrency::create_task(
[=]() -> void {
Concurrency::wait(1000 * 3);
}
).then([=]{
g->GetStateMachine().process_event(ev::TimeOver(g));
},Concurrency::task_continuation_context::use_current());
}
private:
Concurrency::task<void> timer_task_;
};
しかしこのPPLによるtask、スレッドが巧みに隠ぺいされていてなかなか中身の動作がつかみづらい。ドキュメントをよく読む必要がある。でもほとんど英語なんだよね。。まあいずれにせよ「XX秒まである状態を維持する」というのはこの方法で実装していくことにする。
スレッド・アパートメントは今も現役である
スレッドに関するドキュメントを読んでいるとよくわかるが、COMの考え方、STA・MTAはまだ現役である。あとFTAなんていうのもあるね。さらにWindows8ではASTAちゅうのも追加されているのでこの辺よく理解しておく必要がある。私はまだよくわかっていないが。。。