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というクラスが追加された。
実装方法自体は上記記事にあるように、CustomYieldInstructionを継承してkeepWaitingをoverrideするだけでいい。
本題
実は今回のCustomYieldInstructionの追加で、ドキュメントにさりげなく記載されている、わりかし重大な変更がある。(公式Blogに記事出すなら、このことにも触れてほしかった。。。)
実は、IEnumerator自体もCoroutineとしてみなして実行するようになっているのだ。
つまり、今までは
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吐くようになってマジで面食らったので、一応共有。
対処は簡単ではあるんだけど、もうちょっと大々的に告知して欲しかったなあ。。。