C++と色々

主にC++やプログラムに関する記事を投稿します。

DxLibとCoroutineを合わせてみた

C++20にコルーチンが提案されており、Visual Studio 2015以上で使うことが出来るため試してみました。この記事のコードはVisual Studio 2017 Update8で動作を確認しました。コンパイラオプションに /await が必要です。

コルーチン自体の説明は省略します。検索すれば素晴らしい記事がいくつも見つかります。以前、 以下のようなRxの記事を書きました。

nekko1119.hatenablog.com

今回はコルーチンとRxを比較するためこれと同じ動作をするプログラムをコルーチンで書いてみました。

gist.github.com

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つの一番比較ポイントなのではないかと思います。 可読性だけでなく、テストの書きやすさも気になります。まだテストについては未検証のため今後の自分の課題にします。

以上です。

参考

qiita.com

余談ですが、仕事で2つのプロジェクトでredux-observableとredux-sagaを使っているためC++でもRxとジェネレータ(コルーチン)の比較がしたくなったのでした。