MRが楽しい

MRやVRについて学習したことを書き残す

UniRxでHotとColdの性質のObservableの動作を比較する

本日はUniRxの小ネタ枠です。
UniRxでHotとColdの性質のObservableの動作を比較してみます。

Hot/Cold

UniRxのObservableにはHotとColdの2種類の性質があります。
・Cold:購読されるまで通知が行われない。複数の購読が行われた場合は別々のストリームに分岐する。
・Hot:購読に関わらず通知が行われる。複数の購読が行われた場合は同じストリームから通知を受け取る。

ColdのObservable

凡そのオペレータはColdの性質を持ちます。
以下のRangeオペレータを使ったサンプルスクリプトを作成しました。
・ColdObservableTest.cs

using UniRx;
using UnityEngine;

public class ColdObservableTest : MonoBehaviour
{
    // Start is called before the first frame update
    async void Start()
    {
        // RangeオペレータはColdのObservableになる
        // Coldのため、この時点では 1, 2, 3 の通知が発行されない
        var rangeObservable = Observable.Range(1, 3);
        
        // Firstで最初の値を取得する
        // この時点で購読が行われ、通知が発行される
        // 1, 2, 3 のうち、最初の 1 が取得されてログに表示される
        Debug.Log("Start First()");
        int oneValue = await rangeObservable.First();
        Debug.Log($"oneValue: {oneValue}");
        
        // 再びFirstで最初の値を取得する
        // Coldは購読ごとに別々のストリームが生成されるため、
        // 再び 1, 2, 3 が通知され、最初の 1 が取得されてログに表示される
        Debug.Log("Start First()");
        int twoValue = await rangeObservable.First();
        Debug.Log($"twoValue: {twoValue}");
    }
}

ログから購読のタイミングで通知が開始していること、購読ごとに別のストリームが生成されていることが分かります。

HotのObservable

基本的にSubjectクラスまたは任意にHot変換されたObservableがHotの性質を持ちます。
以下のサンプルスクリプトを作成しました。
・HotObservableTest.cs

using System;
using Cysharp.Threading.Tasks;
using UniRx;
using UnityEngine;

public class HotObservableTest : MonoBehaviour
{
    // Start is called before the first frame update
    async void Start()
    {
        // SubjectをIObservableに変換するとHotのObservableになる
        Subject<int> subject = new Subject<int>();
        IObservable<int> asObservable = subject.AsObservable();
        subject.OnNext(1); // Hotのため、OnNextで通知を送ったタイミングで通知が発行されている
        subject.OnNext(2); // Hotのため、OnNextで通知を送ったタイミングで通知が発行されている
        subject.OnNext(3); // Hotのため、OnNextで通知を送ったタイミングで通知が発行されている
        
        // この後、firstで待機しても既に通知が完了されているため、何も受け取れない
        // このため、firstの待機1フレーム後に通知を送る処理を用意しておく
        UniTask.Create(async () =>
        {
            await UniTask.DelayFrame(1);
            subject.OnNext(4);
        });
        
        // Firstで最初の値を取得する
        // 次のフレームで 4 の通知が発行されるため、4 が取得されてログに表示される
        Debug.Log("Start First()");
        int oneValue = await asObservable.First();
        Debug.Log($"oneValue: {oneValue}");
        
        // 再びFirstで最初の値を取得する
        // firstの待機1フレーム後に通知を送る処理を用意しておく
        UniTask.Create(async () =>
        {
            await UniTask.DelayFrame(1);
            subject.OnNext(5);
        });

        // HotのObservableは複数回購読を行っても同じストリームを共有するため、
        // 次のフレームで発行される 5 の通知が取得されてログに表示される
        Debug.Log("Start First()");
        int twoValue = await asObservable.First();
        Debug.Log($"oneValue: {twoValue}");
    }
}

ログから購読のタイミングに関わらず通知が開始していることが分かります。