【Unity】【UniRx】わかった気になるHotとCold

RxにはHotとColdという重要な概念がありますが、
調べると結構いろいろな説明があって少し混乱します。

調べるたびに混乱しそうなので具体例を使って考えてみました。

HotとCold

HotなObservableはそれを購読しているすべてのObserverに同じ値を流します。
たとえばFromEventなどで作ったObservableはHotです。

これに対してColdなObservableはそれぞれに別の値を流します。
オペレータはほとんどColdです。

よくわからないので具体例を見ていきます。

具体例1

次のtimerObservableはColdなObservableで構成されています。
Coldなので、購読したタイミングにより違う値が流れてきます。

// 1秒ずつカウントアップするタイマー
var timerObservable = Observable
    .Interval(System.TimeSpan.FromSeconds(1.0f))
    .Select(x => ++x);

// 何かしらのキーの押下を監視するストリーム
Observable
    .EveryUpdate()
    .Where(_ => Input.anyKeyDown)
    .Select(_ => 1)
    .Scan((a, b) => a + b)
    .Subscribe(x => 
    {
        // キーが押されるたびに購読開始する
        // 購読した時間によりタイマーの時間が異なる = 別のタイマーが稼働している
        timerObservable.Subscribe(c => Debug.Log("購読" + x + " : " + c + "秒経過"));
    });

同じObservableから同じ値を流してほしい場合にはこの実装では困るのでHot変換をします。
下記のようにPublish()とRefCount()を使うことでHot変換が行われ、同じ値が取得できます。

// Publish()とRefCount()によりHot変換
var timerObservable = Observable
    .Interval(System.TimeSpan.FromSeconds(1.0f))
    .Select(x => ++x)
    .Publish()
    .RefCount();
        
Observable
    .EveryUpdate()
    .Where(_ => Input.anyKeyDown)
    .Select(_ => 1)
    .Scan((a, b) => a + b)
    .Subscribe(x => 
    {
        // 最初に購読した時点からタイマーが始まり、複数回購読しても同じ値が流れてくる
        timerObservable.Subscribe(c => Debug.Log("購読" + x + " : " + c + "秒経過"));
    });

また、上記の例では最初に購読開始したときにタイマーが始まりますが、
購読よりも前にタイマーだけ開始したいときにはPublish()とConnect()を使います。

var timerObservable    = Observable
    .Interval(System.TimeSpan.FromSeconds(1.0f))
    .Select(x => ++x)
    .Publish();

// この時点でタイマーが開始する
timerObservable.Connect();
        
Observable
    .EveryUpdate()
    .Where(_ => Input.anyKeyDown)
    .Select(_ => 1)
    .Scan((a, b) => a + b)
    .Subscribe(x => 
    {
        // 複数回購読しても同じ値が流れてくる
        timerObservable.Subscribe(c => Debug.Log("購読" + x + " : " + c + "秒経過"));
    });

これでConnect()時からタイマーが開始するようになりました。

具体例2

いま、Startから今までのクリック数を流すObservableを作るとします。
HotとColdのことを知らずに実装すると次のようになりそうです。

using UniRx;
using UnityEngine;

public class UniRxExample : MonoBehaviour
{
    private void Start()
    {
        // クリックされた数を流すObservable
        // このままだとColdなので購読したタイミングからのクリック数が流れてくる
        var clickCountObservable    = Observable
            .EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Select(_ => 1)
            .Scan((a, b) => a + b);
    }
}

しかしこれではStartからのクリック数ではなく購読時点からのクリック数が流れてきてしまいます。
もちろん複数回購読すれば各Observerに別の値が流れます。

これを修正するためには前節と同じようにPublish()とConnect()を使用します。

using UniRx;
using UnityEngine;

public class UniRxExample : MonoBehaviour
{
    private void Start()
    {
        // クリックされた数を流すObservable
        // このままだとColdなので購読したタイミングからのクリック数が流れてくる
        var clickCountObservable    = Observable
            .EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0))
            .Select(_ => 1)
            .Scan((a, b) => a + b)
            .Publish();

        clickCountObservable.Connect();
    }
}

これによりHot変換され、何回購読しても同じ値が流れるようになりました。

参考サイト

qiita.com

blog.xin9le.net

qiita.com