c++でコルーチンっぽい処理を書いてみる

#モチベーション
[boost::coroutines](http://www.boost.org/doc/libs/1_61_0/libs/coroutine/doc/html/index.html)でコルーチンを使えますが、ビルドが必要なライブラリで、ちょっと面倒。Fiberを使う方法もありますが、これは環境依存なのでポータビリティがありません。というわけで、標準ライブラリだけでコルーチンが書けないかと考えてみました。

std::functionとラムダでなんとかする。goto、longjmpは禁止！

#例１）C#のコルーチンを置き換えてみる

[Unityのコルーチンマニュアル](https://docs.unity3d.com/jp/current/Manual/Coroutines.html)にある下記のC#のコードをC++で置き換えてみます。

```csharp 
IEnumerator Fade() {
    for (float f = 1f; f >= 0; f -= 0.1f) {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        yield return null;
    }
}
```

Unityのコンポーネントとかをプリミティブな型に置き換えたり、Update関数で行われるであろう処理をforループにしてしまっていますが、c++で似たように書くとするとこんな感じ。

```cpp 
int main(int argc, const char * argv[]) {

    using CoroutineContext = std::deque<std::function<bool(void)>>;
    CoroutineContext context;
    float color = 1.0f;

    // ここからFade()に相当
    float f = 1.0f;
    context.push_back([&color, &f]() {
        if(f >= 0.0f) {
            color = f;
            f -= 0.1f;
            return false;
        }
        return true;
    });
    // ここまで

    for(;;) // ゲームだとUpdate関数になる
    {
        if(context.empty()) {
            break;
        }
        auto func = context.front();
        if(func()) {
            context.pop_front();
        }
        std::cout << "color : " << color << std::endl;
    }

    return 0;
}
```
処理を中断するところでfalseを返し、処理が完了したらtrueを返すラムダをキューに突っ込み、先頭にあるラムダを実行して、trueを返したらポップし、キューが空になるまで繰り返します。

#例２）決まった処理のシーケンス
ファイル読み込み待ち、アニメーション再生待ちなどを挟みつつ決まった処理を書くことを考えてみます。

1. アニメーションAを再生
1. アニメーションAの再生完了待ち
1. アニメーションBを再生
1. アニメーション再生完了待ち
1. エフェクト生成

これをｃ++で素直（？）に書くと

```cpp
enum class Phase {
    PlayA,
    WaitA,
    PlayB,
    WaitB,
    PlayEffect,
    End,
};

Phase phase;
///////////////
// 中略
///////////////

switch(phase) {
case Phase::PlayA:
    playAnimation(animationA);
    phase = Phase::WaitA;
    break;

case Phase::WaitA:
    if(isFinishedAnimation()) {
        phase = Phase::PlayB;        
    }
    break;

case Phase::PlayB:
    playAnimation(animationB);
    phase = Phase::WaitB;
    break;

case Phase::WaitB:
    if(isFinishedAnimation()) {
        phase = Phase::PlayEffect;
    }
    break;

case Phase::PlayEffect:
    playEffect();
    phase = Phase::End;
    break;
}
```

このように、状態変数とswitch文を駆使して状態遷移させる感じになると思います。これを前述のコルーチンっぽい書き方にしてみます。

```cpp
using CoroutineContext = std::deque<std::function<bool(void)>>;
CoroutineContext context;
///////////////
// 中略
///////////////
// メンバ関数内で書いているとします
context.push_back([this](){
        playAnimation(animationA)
        return true;
    });
context.push_back([this](){
        return isFinishedAnimation();
    });
context.push_back([this](){
        playAnimation(animationB);
        return true;
    });
context.push_back([this](){
        return isFinishedAnimation();
    });
context.push_back([this](){
        playEffect();
        return true;
    });
```

状態変数の定義が無いので、状態の追加削除が容易でしょうか。yieldで挟まれた部分をラムダにして、ループで待つ部分を条件式だけのラムダにし、それらをキューに順に入れたイメージです。

#考察
例ではCoroutineContextは単純にbooleanを返すファンクタのキューとしましたが、Contextをクラスにし状態を持たせ、booleanではなくオブジェクトを返すラムダを保持するようにすると、よりc#のコルーチンに近づけられそうです。（クラスにタイマー変数を持たせて、待ち時間を返せるラムダにすると、WaitForSecondsっぽくできる）

#まとめ
戻り値で処理の完了を表すラムダのキューを使うことで、コルーチンっぽい書き方ができそう。
Contextをクラスにして機能を追加していけば、よりc#に近づけられそう。

実際に比較用のコードを書いてみると、劇的に可読性が上がるとか、書きやすくなったとかはなさそうだけど、より直感的に書けるようになっていると思います。。。思いたい。