したかみ ぶろぐ

Unity成分多め

UniRxのオペレータCombineLatestとWithLatestFrom

始めに

2つのストリームを合成する際に、CombineLatestWithLatestFromを混同していたので一度自分なりに調べてみようと思います。



CombineLatest

こちらのオペレータの特徴はこのようになっています。

  1. 複数のIObservableをまとめられる
  2. すべてのIObservableから1回以上、値が流れたら後続に流し始める
  3. 値を流し始めたあと、どれか1つのIObservableから値を受け取ったらそのIObservableからの値だけを更新して後続に流す。


1. 複数のIObservableをまとめられる

引数に複数のIObservableを渡せますし、IEnumerable<IObservable<T>>の後続に引数なしでCombineLatestをつけることですべてのIObservable<T>をまとめることができます。

また、7つまでの合成であれば合成するためのラムダ式を記述して無名関数として渡すことができます。

UniRx/Observable.Concatenate.cs at 284d5c50d3f1ddd9fa7df3d382ea904732a9c2ff · neuecc/UniRx · GitHub より引用

        public static IObservable<TResult> CombineLatest<TLeft, TRight, TResult>(this IObservable<TLeft> left, IObservable<TRight> right, Func<TLeft, TRight, TResult> selector)
        {
            return new CombineLatestObservable<TLeft, TRight, TResult>(left, right, selector);
        }


        public static IObservable<IList<T>> CombineLatest<T>(this IEnumerable<IObservable<T>> sources)
        {
            return CombineLatest(sources.ToArray());
        }


        public static IObservable<IList<TSource>> CombineLatest<TSource>(params IObservable<TSource>[] sources)
        {
            return new CombineLatestObservable<TSource>(sources);
        }


        public static IObservable<TR> CombineLatest<T1, T2, T3, TR>(this IObservable<T1> source1, IObservable<T2> source2, IObservable<T3> source3, CombineLatestFunc<T1, T2, T3, TR> resultSelector)
        {
            return new CombineLatestObservable<T1, T2, T3, TR>(source1, source2, source3, resultSelector);
        }

. . . . .
. . . . .



2. すべてのIObservableから1回以上、値が流れたら後続に流し始める

下の図のように合成しているIObservableのすべてから1回以上値が流れないとCombineLatestから値が流れ始めません。

f:id:vxd-naoshi-19961205-maro:20210916233420p:plain



3. 値を流し始めたあと、どれか1つのIObservableから値を受け取ったらそのIObservableからの値だけを更新して後続に流す。

値を流し始めたあとはどれか1つから値が流れたらその値だけを更新し、それ以外からの値は最新のものを使って後続に値を流します。

f:id:vxd-naoshi-19961205-maro:20210916234107p:plain

WithLatestFrom

こちらのオペレータの特徴は次のようになっています。

  1. 2つのIObservable(メイン、サブ)をまとめられる
  2. サブのIObservableから1回以上値が流されている状態でメインから流れた時に値を流し始める
  3. 値を流し始めたあと、メインのIObservableから値が渡されたときのみ後続に値を流す。



1. 2つのObservable(メイン、サブ)をまとめられる

WithLatestFromではメインとなるIObservableとサブとなるIObservableの2つをまとめられます。 それ以上のObservableをまとめることはできません。

UniRx/Observable.Concatenate.cs at 284d5c50d3f1ddd9fa7df3d382ea904732a9c2ff · neuecc/UniRx · GitHub より引用

        public static IObservable<TResult> WithLatestFrom<TLeft, TRight, TResult>(this IObservable<TLeft> left, IObservable<TRight> right, Func<TLeft, TRight, TResult> selector)
        {
            return new WithLatestFromObservable<TLeft, TRight, TResult>(left, right, selector);
        }



2. サブのObservableから1つ以上値が流されている状態でメインから流れた時に値を流し始める

サブから1つ以上の値が流れた状態でないと、メインからいくら値が流されても値が流れません。

f:id:vxd-naoshi-19961205-maro:20210916234519p:plain



3. 値を流し始めたあと、メインのObservableから値が渡されたときのみ後続に値を流す。

値が流れている状態ではメインから値が流れたときのみ、後続に値を流します。 値を流すときは最新のサブからの値とメインからの値を合成します。

f:id:vxd-naoshi-19961205-maro:20210916234731p:plain



2つの比較

CombineLatest WithLatstFrom
合成できる個数 2つ以上 2つ(メインとサブのIObservable)のみ
値を流し始める基準 すべてのIObservableから1回以上値が流れる サブから1回以上値が流れている状態でメインから値が流れる
値を流す基準 どれか一つのIObservableから値が流れる メインから値が流れる


使い分け

言わずもがな2つより多いIObservableを合成する場合はCombineLatestを使うことになるでしょう。

2つのObservableを合成するときでも、両方のIObservableで発火したい場合はCombineLatest、1つのIObservableからのみ発火したい場合はWithLatestFromになるでしょう。

2つのIObservableから1回だけ値が流れる場合は、順番関係なく2つから流れたときに後続に流し始めるCombineLatestの方が良いでしょう。もし、順番が明確であればWithLatestFromでも問題ないかもしれません。


ただ、使い分けが難しいと感じたのが順番が明確ではなく、1つのIObservableのみ発火してほしい場合だと感じています。その場合は、どうにかして決まった順で流れるようにするかもう一方から値が流れて発火した場合を許容するかになるかもしれません。

参考

Rxのマーブルを自由に移動させて、オペレータに通すとどうなるか動的にわかるサイトになっているので参考になります。

また、いくつかの画像作成で使用しました。

rxmarbles.com