UniRx: Debug() 拡張メソッド

ObservableDebugExtensions.cs に Debug() 拡張メソッドが定義されています。 この拡張メソッドを使うと、OnNextOnCompleted などのイベント時にログが出力されるようになります。 (これはデバッグ機能として提供されており、DEBUG シンボルが定義されている場合にかぎり有効となります)

using System;
using UniRx;
using UniRx.Diagnostics;
using UnityEngine;

public class Main : MonoBehaviour
{
    void Start()
    {
        var s = Observable.Range(10, 3);
        s.Debug().Subscribe();
    }
}

実行結果です。

[22:32:55:471] OnSubscribe
[22:32:55:475] OnNext(10)
[22:32:55:477] OnNext(11)
[22:32:55:478] OnNext(12)
[22:32:55:479] OnCompleted()

これまでログを出力するときは、いつも以下のようなコードを書いていましたが、Debug() 拡張メソッドを使うと不要になりますね。

void Start()
{
  var s = Observable.Range(10, 3);
  s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
}

UniRx: Delay

Observable.Delay() は通知データを好きな期間だけ遅らせることができるオペレータです。

using System;
using UniRx;
using UnityEngine;

public class Main : MonoBehaviour {
    void Start()
    {
        Debug.Log("Start");
        
        var s = Observable.Range(10, 3).Delay(TimeSpan.FromSeconds(3));
        s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
    }
}

実行結果です。

[23:37:05:728] Start
[23:37:08:996] e: 10
[23:37:09:010] e: 11
[23:37:09:027] e: 12
[23:37:09:043] OnCompleted

通知される 3 つのデータはほぼ同時刻に通知されていますが、 Start() 実行後に 3 秒遅れてからログ出力されています。

Observable.Range の要素ごとに Delay を入れてみる

void Start()
{
  var s = Observable.Range(1, 3)
    .SelectMany(e => Observable.Return(e).Delay(TimeSpan.FromSeconds(e)));

  s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
}

実行結果です。

[22:37:58:674] e: 1
[22:37:59:668] e: 2
[22:38:00:679] e: 3
[22:38:00:695] OnCompleted

1 秒感覚で通知されていますね。

以下のように delay time を変更すると、出力が逆順になります。

void Start()
{
  var s = Observable.Range(1, 3)
    .SelectMany(e => Observable.Return(e).Delay(TimeSpan.FromSeconds(5 - e)));
  s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnComplete"));
}

実行結果です。

[20:49:47:409] e: 3
[20:49:48:398] e: 2
[20:49:49:393] e: 1
[20:49:49:409] OnComplete

参考

UniRx: Empty

Observable.Empty() は、Subscribe() するとすぐに完了を通知します。

using UniRx;
using UnityEngine;

public class Main : MonoBehaviour
{   
    void Start()
    {
        var s = Observable.Empty<Unit>();
        s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
    }
}

実行結果です。

OnCompleted

Observable.Empty を Take する

void Start()
{
  var s = Observable.Empty<Unit>().Take(5);
  s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
}

実行結果です。

OnCompleted

Take するときの不足分に対する挙動は、LinqTake と同じですね。

Observable.Empty を Skip する

void Start()
{
  var s = Observable.Empty<Unit>().Skip(5);
  s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
}

実行結果です。

OnCompleted

Take と同様の振る舞いです。

Observable.Empty を First する

void Start()
{
  var s = Observable.Empty<Unit>().First();
  s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
}

実行結果です。

InvalidOperationException: sequence is empty

シーケンスが空のため、InvalidOperationException 例外が生成されました。

Observable.Empty を FirstOrDefault する

void Start()
{
  var s = Observable.Empty<int>().FirstOrDefault();
  s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
}

実行結果です。

e: 0
OnCompleted

default(int) が通知されます。

参考

.NET Core API リファレンスと .NET Core Source Browser のURLのメモ

UniRx: Concat

Observable.Concat() は複数の IObservable<T> を順番に実行するオペレーターです。

using System;
using UniRx;
using UnityEngine;

public class Main : MonoBehaviour
{
    void Start()
    {
        // 1 秒ごとに通知
        var a = Observable.Interval(TimeSpan.FromSeconds(1)).Take(3);
        // 5 秒ごとに通知        
        var b = Observable.Interval(TimeSpan.FromSeconds(5)).Take(2);
        var c = a.Concat(b);

        c.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));     
    }
}

実行結果です。

[21:00:05] e: 0
[21:00:06] e: 1
[21:00:07] e: 2
[21:00:12] e: 0
[21:00:17] e: 1
[21:00:17] OnCompleted

先頭の数値は、ログの出力時刻です。IObservablea から 3 つ通知を受け取った後に b の処理が開始されています。

参考

UniRx: 購読をキャンセルする

IObservable<T>.Subscribe() から返却される IDisposableDispose() を呼び出すと購読がキャンセルされます。

using System;
using UniRx;
using UnityEngine;

public class Main : MonoBehaviour
{
    private IDisposable _disposable;
    
    void Start()
    {
        var s = Observable.Interval(TimeSpan.FromSeconds(1));
        _disposable = s.Subscribe(e => Debug.Log("e: " + e), () => Debug.Log("OnCompleted"));
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Debug.Log("キャンセルします");
            _disposable.Dispose();          
        }
    }
}

実行結果です。

e: 0
e: 1
w: 2
キャンセルします。 // ←スペースキーを押した

Dispose() を呼び出してキャンセルすると、それ以降のデータは通知されなくなります。

UniRx: AddTo() 拡張メソッドのソースを読む

この記事では、AddTo() 拡張メソッドを用いると、ゲームオブジェクトの破棄タイミングで購読を破棄する仕組みを眺めていきます。

次のサンプルコードでは、Observable.Interval() で一定時間ごとにログを出力します。 また、スペースキーを押すとシーンが切り替わるようにしています。

public class Main : MonoBehaviour
{   
    void Start()
    {
        var s = Observable.Interval(TimeSpan.FromSeconds(1));
        s.Subscribe(e => Debug.Log("e: " + e));
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // スペースキーでシーンを切り替える
            SceneManager.LoadScene("SampleScene2");
        }
    }
}

実行結果です。スペースキーを押してシーンが切り替わると "SampleScene2 Start" とログを出力するようにしています。

e: 0
e: 1
e: 2
SampleScene2 Start // ←シーンが切り替わった
e: 3
e: 4

このようにシーンが切り替わっても、Observable.Interval() は通知し続けます。 AddTo() 拡張メソッドを使うと、指定のゲームオブジェクトが破棄されたタイミングで購読を破棄することができます。

AddTo() 拡張メソッドのサンプルコード

public class Main : MonoBehaviour
{   
    void Start()
    {
        var s = Observable.Interval(TimeSpan.FromSeconds(1));
        s.Subscribe(e => Debug.Log("e: " + e)).AddTo(this); // AddTo を追加
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            SceneManager.LoadScene("SampleScene2");
        }
    }
}

実行結果です。

e: 0
e: 1
e: 2
SampleScene2 Start

今度はシーン遷移後は、Observable.Interval() の通知がログには出ておらず購読が破棄されていることが確認できます。

AddTo() 拡張メソッドのソースを読む

AddTo() は次のような拡張メソッドとして定義されています。

public static partial class DisposableExtensions
{
    /// <summary>Dispose self on target gameObject has been destroyed. Return value is self disposable.</summary>
    public static T AddTo<T>(this T disposable, GameObject gameObject)
        where T : IDisposable
    {
        if (gameObject == null)
        {
            disposable.Dispose();
            return disposable;
        }

        var trigger = gameObject.GetComponent<ObservableDestroyTrigger>();
        if (trigger == null)
        {
            trigger = gameObject.AddComponent<ObservableDestroyTrigger>();
        }

#pragma warning disable 618

        // If gameObject is deactive, does not raise OnDestroy, watch and invoke trigger.
        if (!trigger.IsActivated && !trigger.IsMonitoredActivate && !trigger.gameObject.activeInHierarchy)
        {
            trigger.IsMonitoredActivate = true;
            MainThreadDispatcher.StartEndOfFrameMicroCoroutine(MonitorTriggerHealth(trigger, gameObject));
        }

#pragma warning restore 618

        trigger.AddDisposableOnDestroy(disposable);
        return disposable;
    }
}    

gameObject に対して ObservableDestroyTriggerコンポーネント追加しているようです。  ObservableDestroyTrigger.AddDisposableOnDestroy() の中身を見てみましょう。 (gameObject が deactive のときの処理は、ここでは省略します)

[DisallowMultipleComponent]
public class ObservableDestroyTrigger : MonoBehaviour
{
    bool calledDestroy = false;
    Subject<Unit> onDestroy;
    CompositeDisposable disposablesOnDestroy;
    
    // (コード一部省略)

    /// <summary>This function is called when the MonoBehaviour will be destroyed.</summary>
    void OnDestroy()
    {
        if (!calledDestroy)
        {
            calledDestroy = true;
            if (disposablesOnDestroy != null) disposablesOnDestroy.Dispose();
            if (onDestroy != null) { onDestroy.OnNext(Unit.Default); onDestroy.OnCompleted(); }
        }
    }
    
    // (コード一部省略)

    public void AddDisposableOnDestroy(IDisposable disposable)
    {
        if (calledDestroy)
        {
            disposable.Dispose();
            return;
        }

        if (disposablesOnDestroy == null) disposablesOnDestroy = new CompositeDisposable();
        disposablesOnDestroy.Add(disposable);
    }
}
  1. ObservableDestroyTrigger.AddDisposableOnDestroy() で disposable を追加
  2. OnDestroy() のタイミングで 1. で登録した disposable を破棄

以上より、MonoBehaviour の OnDestroy() が呼び出されるタイミングで購読を解除されることが確認できました。

  • UniRx のバージョン: Ver 6.1.2

参考