UniRxを紐解く「Take(1)とFirst()の違い」
UniRxとは
大変便利なReactiveExtensionsのUnity版ライブラリ。
最近、業務でも業務外でもこちらを触ることが多く、色々な地雷を踏み倒してきました。
備忘録の意味も兼ねて今回は
- 「Take(1)とFirst()」の違い
をまとめようと思います。
入門者向きではないです、ご了承ください。
あとかなり長いです。
ソースコードなどはこちらの
を参考にさせていただいています。
TakeとかFirstってなんですか?という方は、こちらをまず読まれると良いと思います。
その他にも様々な分かりやすいサンプルがあり、入門に最適です。
Take(n)
nに指定した分だけ、OnNextを発行するオペレータです。
using UnityEngine; using System.Collections; using UniRx; using UniRx.Triggers; public class TakeSample : Base { // Use this for initialization void Start () { gameObject.transform.position = new Vector2(0, 0.5f); //Takeで最初の100回以外はカット this.UpdateAsObservable() .Take(100) .Subscribe(l => Move(0.01f, 0)); } }
このソースコード上で書かれたObservableを分解すると
- this.UpdateAsObservable()
毎フレーム、次のオペレータ[Take(100)]に対してOnNextを発行する
- .Take(100)
OnNextを100回だけ次のオペレータに発行する
- Subscribe(l => Move(0.01f,0));
OnNextが来たら、Move(0.01f,0)を叩く
という記述がされています。
ここでさらに、記述されていない中身を付け足したソースコードがこちらです
using UnityEngine; using System.Collections; using UniRx; using UniRx.Triggers; public class TakeSample : Base { // Use this for initialization void Start () { gameObject.transform.position = new Vector2(0, 0.5f); //Takeで最初の100回以外はカット this.UpdateAsObservable() .Take(100) .Subscribe(l => Move(0.01f, 0),
() => OnCompleted()); } }
() => OnCompleted()がSubscribeに追加されています。
これは、Subscribeまでのストリームで、OnCompletedが発行された際に実行されます。
そして、今回のObservableでOnCompletedを発行するのは
Take(100)が100回OnNextを通した時です。
つまるところ、100回Move()したら、OnCompletedが呼ばれるということです。
ちなみに、OnCompletedはだいたいどのオペレータでも
- try { observer.OnCompleted() } finally { Dispose(); } ;
という処理になっていますので、OnCompletedを発行してエラーを吐いてもDisposeされるようになっています。
まとめ
Take(n)はnに指定した回数だけOnNextを通して、終了時にOnCompletedを発行する
First(Func),First()
OnNextが発行された最初の一度だけOnNextを発行します
using UnityEngine; using System.Collections; using UniRx; using UniRx.Triggers; public class First : Base { // Use this for initialization void Start() { gameObject.transform.position = new Vector2(0, 1f); //クリックされたら右に1.5動かす(但し1回だけ) this.UpdateAsObservable() .First(l => Input.GetMouseButton(0)) .Subscribe(l => Move(1.5f, 0));
//こっちもやってること同じ
this.UpdateAsObservable()
.Where(_ => Input.GetMouseButton(0))
.First()
.Subscribe(l => Move(1.5f, 0));
} }
このソースコード上で書かれたObservableをまた分解していきます
- this.UpdateAsObservable()
毎フレーム、次のオペレータ[First(Func)]に対してOnNextを発行する
- .First(Func) .First()
OnNextをFuncがtrueの時に、一度発行する
FuncがないFirst()は、OnNextを無条件で発行する
- Subscribe(l => Move(0.01f,0));
OnNextが来たら、Move(0.01f,0)を叩く
という記述がされています。
First()は一度OnNextを発行すると、OnCompletedを発行します
単体では、Take(1)と同じ挙動をします
まとめ
First(),First(Func)は一回OnNextを発行し、その後OnCompletedを発行する
Take(1)とFirst()の違い
ここまで読んで
「Take()は回数指定できる、First()は条件指定できるのね、へ〜」
と思ったことでしょう。
それだけではないです、というかここからが重要です
端的に言うと
OnCompletedをオペレータ内で通す際の挙動に明確な違いがあります
TakeとFirstのOnCompletedメソッドをごらんください
- Take
public class Take : OperatorObserverbase<T,T>{
public override void OnCompleted(){
try {observer.OnCompleted();} finally { Dispose(); }
}
}
- First
public class First : OperatorObserverbase<T,T>{
public override void OnCompleted(){
if(notPublished){
try { observer.OnError(new InvalidOperationException("Sequence is empty"));}
finally { Dispose(); }
}
else{
try {observer.OnCompleted();} finally { Dispose(); }
}
}
}
こうなってます、全然違いますね。
一目見てなんだこれとなるのは、First()の方にあるnotPublishedというboolだと思います。
これはFirst()でOnNextが発行された時にfalseになるbool値です。
OnNextメソッドでもこれを使用していて、notPublishedの状態の時のみOnNextが次のオペレータに発行されるようになっています。
一度だけしか発行しない処理の中身はこうなっていました。
つまり
OnNextが一度も通っていないFirstオペレータはOnCompletedを通しません
こういう明確な差があるわけですね。
例として
public class First : Base { // Use this for initialization void Start() { gameObject.transform.position = new Vector2(0, 1f); //クリックされたら右に1.5動かす(但し1回だけ) this.UpdateAsObservable()
.TakeWhile(_ => isEnabled)
.Where(_ => Input.GetMouseButton(0)) .First() .Subscribe(l => Move(1.5f, 0));
} }
このコードで
一度もGetMouseButton(0)がtrueにならないまま、isEnabledがfalseになり
OnCompletedが発行されると、FirstくんはErrorを吐きます。
まとめ
OnNextが発行されていなくてもOnCompletedを次のオペレータに通知できるのが
Take(1)
OnNextが発行されていないと、OnCompletedを次のオペレータに通知できないのが
First()
です。
もしOnCompletedを発行できるオペレータが複数あるストリームを組むときは
Take(1)を使用することをオヌヌメします。
終わり。
間違いなどがあったら@YutaDevelopまでお願いします。
unirxすげー