UniRxのFirstオペレータを使ったらInvalidOperationException: sequence is empty
経緯
UIに一定時間触れないと自動で消えてくれるようにしてくれ!と言われたのでこんなコードを書いた。
private float _lastTime;
private float lifespan;
private void OnEnable()
{
this.UpdateAsObservable()
.First(_ => Time.unscaledTime - _lastTime > lifespan)
.Subscribe(_ =>
{
// UIが消える処理
});
}
できたできたと思っていたら、シーンから離れるとエラーになる
と言われた。
InvalidOperationException: sequence is empty
AddTo忘れてた!
ちゃんと購読中止するの忘れてた、と思ったので.AddTo(this)
を追加
private float _lastTime;
private float lifespan;
private void OnEnable()
{
this.UpdateAsObservable()
.First(_ => Time.unscaledTime - _lastTime > lifespan)
.Subscribe(_ =>
{
// UIが消える処理
})
.AddTo(this);
}
それでもエラーは解消されない。
InvalidOperationException: sequence is empty
そもそもUpdateAsObservable
はオブジェクトの破棄時に自動でDisposeしてくれるらしい
参考
めっちゃ便利ですね。
FirstはOnNextを発行しなければならない
どうやらFirst
オペレータは値を発行する前にOnCompleted()
しようとすると怒るらしい。
UniRxのソースコードは次のようになっている。
public override void OnCompleted()
{
if (parent.useDefault)
{
if (notPublished)
{
observer.OnNext(default(T));
}
try { observer.OnCompleted(); }
finally { Dispose(); }
}
else
{
if (notPublished)
{
try { observer.OnError(new InvalidOperationException("sequence is empty")); }
finally { Dispose(); }
}
else
{
try { observer.OnCompleted(); }
finally { Dispose(); }
}
}
}
useDefault
がfalse
かつnotPublished
がtrue
だと冒頭のエラーを吐くようになっていることがわかる。
notPublished
はOnNext()
の中でfalse
にセットされる。
First
オペレータはuseDefault
がfalse
となってしまうので、値を発行する前にDispose
しようとする(今回はシーン移動によるオブジェクトの破棄がきっかけ)とエラーとなってしまう。
対策
First
ではなくFirstOrDefault
を使用する!
これでuseDefault
がtrueとなり、OnNext()
を呼ぶ前であってもDefaultの値を発行した後、正しく購読中止される。
↓が完成形のコード
private void OnEnable()
{
_lastTime = CurrentTime();
this.UpdateAsObservable()
.FirstOrDefault(_ => Time.unscaledTime - _lastTime > lifespan)
.Subscribe(_ =>
{
// UIを消す処理
});
}