起こった問題
UniRxのFromCoroutineValueはコルーチンから戻り値を受け取ってRxのストリームに流せるので便利なのですが、同一フレームで実行されるという罠がありました。FromCoroutineValueを使わずにFromCoroutineを使うことで解決できました。
普通のコルーチン
public class CoroutineTest : MonoBehaviour
{
void Start()
{
StartCoroutine(MyCoroutine());
}
IEnumerator<int> MyCoroutine()
{
for(int i = 0; i < 10; i++)
{
Debug.Log(Time.frameCount); // コルーチン内で何フレーム目か表示
int someValue = 10;
yield return someValue;
}
}
}
Unityではyield return以降は次フレームで実行される(Unityのyield returnの仕様と言うよりStartCoroutineの仕様?)ので適当なGameObjectに貼り付けて実行すると以下のような結果になります。
FromCoroutineValue
コルーチン内でなにか処理を行ってその結果someValue
を返したいといったシチュエーションは結構あると思うのですが、通常のコルーチンでは厄介です。そんなとき便利なのがUniRxのFromCoroutineValueなのですがこれには同一フレームで実行されるという罠があります。
void Start()
{
Observable.FromCoroutineValue<int>(MyCoroutine)
.Subscribe(v => {});
}
これを実行すると以下のようなログが出ます。
yield returnで次フレームに移っておらず同一フレームで実行されてしまっています。ですので無限ストリームが作りたいからといって以下のようなコードを書くと永遠に次フレームに移れないのでUnity Editorがフリーズします(悲しい世界)
public class CoroutineTest : MonoBehaviour
{
void Start()
{
Observable.FromCoroutineValue<int>(MyEndlessCoroutine)
.Subscribe(v =>
{
Debug.Log(v);
});
}
IEnumerator<int> MyEndlessCoroutine()
{
while (true)
{
yield return Random.Range(0, 10);
}
}
}
じゃあどうするか
FromCoroutineを使ってIObserver経由で渡します。どういうわけか、FromCoroutineではyield returnで次フレームに移る仕様のようです。先程の無限ストリームは以下のように実現できます。
public class CoroutineTest : MonoBehaviour
{
void Start()
{
Observable.FromCoroutine<int>(observer => MyEndlessCoroutine(observer))
.Subscribe(v =>
{
Debug.Log(v);
});
}
IEnumerator MyEndlessCoroutine(IObserver<int> observer)
{
while(true)
{
yield return null;
observer.OnNext(Random.Range(0, 10));
}
}
}