C++20にコルーチンが提案されており、Visual Studio 2015以上で使うことが出来るため試してみました。この記事のコードはVisual Studio 2017 Update8で動作を確認しました。コンパイラオプションに /await
が必要です。
コルーチン自体の説明は省略します。検索すれば素晴らしい記事がいくつも見つかります。以前、 以下のようなRxの記事を書きました。
今回はコルーチンとRxを比較するためこれと同じ動作をするプログラムをコルーチンで書いてみました。
Appクラスでゲームの毎フレーム行わなければならないゲームのコアロジック外の処理を実行します。
そしてゲームのコアロジックを記述する場所で co_yield
し、コアロジックの実装を移譲します。(L8-L14)
その時に、キーのインプットなどアプリケーションが持っているコアロジック実装に必要な情報を渡します。 今回はRxの記事と対照にするためにZキーの入力情報のみ渡しています。普通はキーの入力をすべて渡して良いでしょうし毎フレーム変わる情報は渡してしまって良いと思います。
std::experimental::generator
はコルーチンの一種で、イテレータを返す begin()
メンバ関数と end()
メンバ関数を持っているためrange-based-forで呼び出すことが出来ます。(L28)
サンプルコードとは少し逸れますが、軽く std::experimental::generator
の実装の話をします。generatorのイテレータは operator*
で現在保持している値(最後にyieldした値)を返し、 operator++()
でresumeしています(co_yieldの次の行に戻って次のco_yieldまで処理を続けます)。 operator!=
は コルーチンが最後まで到達したか判定しています。
forの state
には co_yield
の値が入っています。今回のサンプルではZキーの入力情報のみになります。
そして、その情報をもとに前回のフレームの入力と比較して離れたか判定したり、カウンターを増やして描画しています。(L29-L46)
比較
Rxを使ったサンプルコードでは、operatorsを使って宣言的にロジックを記述しました。
// キーを押している間 input.onZInput() .filter([](int value) { return value != 0; }) .subscribe([&counter](int) { ++counter; }); // キーが離された瞬間 input.onZInput() .buffer(2, 1) .subscribe([&released](std::vector<int> input) { released = input[0] != 0 && input[1] == 0; });
コルーチンでは手続き的に記述しています。
// キーを押している間 if (current_hit != 0) { ++counter; } // キーが離された瞬間 auto const released = prev_hit != 0 && current_hit == 0; ... prev_hit = current_hit;
この部分がこの2つの一番比較ポイントなのではないかと思います。 可読性だけでなく、テストの書きやすさも気になります。まだテストについては未検証のため今後の自分の課題にします。
以上です。
参考
余談ですが、仕事で2つのプロジェクトでredux-observableとredux-sagaを使っているためC++でもRxとジェネレータ(コルーチン)の比較がしたくなったのでした。