diary

日記です

Unity 5.3のCustomYieldInstructionの話

前提

Unityでは古来よりフレームを跨ぐ処理にCoroutineと呼ばれる仕組みが用いられている。 実態としてはC#に備わっているyield構文による、値の列挙を目的としたコルーチンの生成機能を利用している形になる。

仕組みとしては、上記構文を利用して生成したIEnumeratorをStartCoroutineを用いてUnityのシステム側に渡すと、毎フレーム1回、UnityがそのIEnumeratorのMoveNextを呼んでくれる。

yield returnした値はCurrentに入るわけだが、それがYieldInstructionのサブクラスであった場合のみ、そのYieldInstructionが表す非同期処理が終わるまで次のMoveNextの呼び出しを行わないようにしてくれる。

StartCoroutineはIEnumeratorを引数に取り、Coroutineを返す。CoroutineはYieldInstructionのサブクラスなので、Coroutineをyield returnすればCoroutineの中でCoroutineを待てるようになっている。

ただし、YieldInstructionは内部実装に利用されているクラスで、C#スクリプト層から独自のYieldInstructionを実装するようなことは、今まではできなかった。(型の定義のみで何も定義されていないという、割とC#の常識破りなクラスになっている...)

CustomYieldInstruction

Unity 5.3では、独自のYieldInstructionを実装する手段として、CustomYieldInstructionというクラスが追加された。

blogs.unity3d.com

実装方法自体は上記記事にあるように、CustomYieldInstructionを継承してkeepWaitingをoverrideするだけでいい。

本題

実は今回のCustomYieldInstructionの追加で、ドキュメントにさりげなく記載されている、わりかし重大な変更がある。(公式Blogに記事出すなら、このことにも触れてほしかった。。。)

実は、IEnumerator自体もCoroutineとしてみなして実行するようになっているのだ。

docs.unity3d.com

つまり、今までは

IEnumerator IncludeNestedCoroutine()
{
    yield return StartCoroutine(SomeCoroutine());
    Debug.Log("4th Frame");
}

IEnumerator SomeCoroutine()
{
    Debug.Log("1st Frame");
    yield return null;
    Debug.Log("2nd Frame");
    yield return null;
    Debug.Log("3rd Frame");
    yield return null;
}

と描いていた処理が

IEnumerator IncludeNestedCoroutine()
{
    yield return SomeCoroutine();
    Debug.Log("4th Frame");
}

IEnumerator SomeCoroutine()
{
    //省略
}

このように、StartCoroutineなしで実現できるようになった。

今まででもIEnumeratorを実装して、StartCoroutineを挟みさえすれば(冗長だが)独自YieldInstructionらしきものは作れはしていたのだが、 うっかりCurrentとして自身を返す実装をしてしまっていたりすると、StackOverflowExceptionを延々と吐くようになる。

試しにアップデートしたらいきなりStackOverflowException吐くようになってマジで面食らったので、一応共有。

対処は簡単ではあるんだけど、もうちょっと大々的に告知して欲しかったなあ。。。