UniRx: Debug() 拡張メソッド
ObservableDebugExtensions.cs に Debug()
拡張メソッドが定義されています。
この拡張メソッドを使うと、OnNext
や OnCompleted
などのイベント時にログが出力されるようになります。
(これはデバッグ機能として提供されており、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
するときの不足分に対する挙動は、Linq の Take
と同じですね。
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のメモ
- .NET API ブラウザー | Microsoft Docs
- Source Browser
- 例えば
System.Collections.Generic.List<T>
を検索するにはclass List<
まで入力すると検索候補にでてきます(List
で検索するとマッチ候補が多くて探しにくくなります)。
- 例えば
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
先頭の数値は、ログの出力時刻です。IObservable
の a
から 3 つ通知を受け取った後に b
の処理が開始されています。
参考
UniRx: 購読をキャンセルする
IObservable<T>.Subscribe()
から返却される IDisposable
の Dispose()
を呼び出すと購読がキャンセルされます。
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); } }
ObservableDestroyTrigger.AddDisposableOnDestroy()
で disposable を追加OnDestroy()
のタイミングで 1. で登録した disposable を破棄
以上より、MonoBehaviour の OnDestroy()
が呼び出されるタイミングで購読を解除されることが確認できました。
- UniRx のバージョン: Ver 6.1.2