まだプレビュー版が外れていないR3ですが、もうそろそろ正式リリースされそうな雰囲気なので記事を書き始めようかと思います。
今回はUniRxのときと少しObservableに対する AddTo
のシグネチャの変更や追加があるので紹介します。
実行環境
Unity 2022.3.17
R3 0.1.12 → R3 1.0.0
RegisterTo(CancellationToken)
AddToにCancellationTokenを渡すのは UniTask でもありました。ですが、UniTaskのAddToと名前が競合するということで、R3ではCancellationTokenを渡す場合に限り RegisterTo
メソッドで対応します。
内部実装的にもUniTaskと大きな違いはありません。登録したCancellationTokenがキャンセル状態になったときDisposeされる紐付けができます。CancellationToken.Register
で IDisposable.Dispose()
を呼ぶようになっています。
void DoNanka(CancellationToken token)
{
Observable.Timer(TimeSpan.FromSeconds(10))
.Subscribe(_ => Debug.Log("Finish!"))
.RegisterTo(token);
}
返り値が CancellationTokenRegistration
になっているのでCancellationTokenとIDisposableの寿命を一度切り離すことができます。(購読自体を止められるわけではないので注意)
AddTo(MonoBehaviour)
UniRxでよく使われていた AddTo(GameObject)
と AddTo(Component)
は廃止されました。
代わりに MonoBehaviour を渡すようになっています。
内部ではUnity2022.2 から登場した MonoBehaviour.destroyCancellationToken
を呼び出すショートカットになっていて、これを渡しても同じ挙動になります。返り値も同じくCancellationTokenRegistration
です。
public class NewBehaviourScript : MonoBehaviour
{
private void Start()
{
Observable.Timer(TimeSpan.FromSeconds(10))
.Subscribe(_ => Debug.Log("Finish!"))
.AddTo(this);
// .AddTo(destroyCancellationToken); これと同じ
}
}
AddTo(ICollection<IDisposable>)
これはUniRxのときと変わりありません。
R3ではUniRxのときと同じく CompositeDisposable
が定義されていて これをよく使うことになると思います。
変わらず複数の購読を一括管理するのにとても便利です。
private NankaClass nanka = new();
private CompositeDisposable disposables = new();
void EventSubscribe()
{
// CompositeDisposableを生成する
EventDispose();
disposables = new();
nanka.OnNankaCompletedPrepare.Subscribe().AddTo(disposables);
nanka.OnNaknaStart.Subscribe().AddTo(disposables);
nanka.OnNaknaEnd.Subscribe().AddTo(disposables);
}
void EventDispose()
{
// AddされたIDisposableを全てDisposeする
disposables?.Dispose();
}
class NankaClass
{
private readonly Subject<Unit> onNankaCompletedPrepare = new();
private readonly Subject<Unit> onNaknaStart = new();
private readonly Subject<Unit> onNaknaEnd = new();
public Observable<Unit> OnNankaCompletedPrepare => onNankaCompletedPrepare;
public Observable<Unit> OnNaknaStart => onNaknaStart;
public Observable<Unit> OnNaknaEnd => onNaknaEnd;
}
AddTo(ref DisposableBuilder)
DisposableBuilder
は構造体のため、参照渡しをすることになります。
そして、ref構造体なので、通常メンバとして持つことはできません。DisposableBuilder.Build();
を呼ぶと、合成されたIDisposableを取得できます。(渡されたIDisposable群をメンバに持つだけのIDisposable)
こいつを持っておいてDisposeすればまとめて破棄処理ができて便利です。
private NankaClass nanka = new();
IDisposable disposable;
void EventSubscribe()
{
EventDispose();
// DisposableBuilderを生成する
var disposableBuilder = Disposable.CreateBuilder();
// var disposables = new DisposableBuilder(); 同じ
nanka.OnNankaCompletedPrepare.Subscribe().AddTo(ref disposableBuilder);
nanka.OnNaknaStart.Subscribe().AddTo(ref disposableBuilder);
nanka.OnNaknaEnd.Subscribe().AddTo(ref disposableBuilder);
disposable = disposableBuilder.Build();
// disposableBuilder.Dispose(); IDisposableではないが直接Disposeすることもできる
}
void EventDispose()
{
// AddされたIDisposableを全てDisposeする
disposable?.Dispose();
}
DisposableBuilder.AddTo(CancellationToken)
は、Build()
しつつ返り値のIDisposableをCancellationTokenに登録できるショートカットです。
挙動は disposableBuilder.Build().AddTo(CancellationToken)
と同じです。
var r = disposableBuilder.AddTo(destroyCancellationToken);
// var r = disposableBuilder.Build().AddTo(destroyCancellationToken); 同じ
ちなみに、disposableBuilder.Build()
をすると、その DisposableBuilder はその後再度 Add や Build できなくなるので注意が必要です。DisposableBuilderはIDisposableではありませんが、その振る舞いはCompositeDisposableに似ています。BuildやDispose後にAdd, Buildしようとすると ObjectDisposedException
がスローされます
var disposableBuilder = Disposable.CreateBuilder();
disposableBuilder.Build();
subject.Subscribe().AddTo(ref disposableBuilder); // ObjectDisposedExceptionがスローされる
disposableBuilder.Add(subject); // ObjectDisposedExceptionがスローされる
AddTo(ref DisposableBag)
DisposableBag は構造体で、Remove機能のない簡易的なCompositeDisposableみたいな感じです。Clearはできます。
これもまとめてDisposeするのに便利です。
private NankaClass nanka = new();
DisposableBag disposableBag;
void EventSubscribe()
{
EventDispose();
// DisposableBagを生成する
disposableBag = new DisposableBag();
nanka.OnNankaCompletedPrepare.Subscribe().AddTo(ref disposableBag);
nanka.OnNaknaStart.Subscribe().AddTo(ref disposableBag);
nanka.OnNaknaEnd.Subscribe().AddTo(ref disposableBag);
}
void EventDispose()
{
// AddされたIDisposableを全てDisposeする
disposableBag.Dispose();
}
DisposableBag は IDisposableを実装しているので CompositeDisposable や DisposableBuilder に Addすることもできます。Box化はしますが。
// CompositeDisposableに追加
compositeDisposable.Add(disposableBag);
disposableBag.AddTo(compositeDisposable);
// DisposableBuilderに追加
disposableBuilder.Add(disposableBag);
// DisposableBagに追加(これはBox化しない)
disposableBag.Add(compositeDisposable);
// DisposableBuilderはIDisposableじゃないのでできない。エラーになる
disposableBag.Add(disposableBuilder);
compositeDisposable.Add(disposableBuilder);
Disposable.Combine
を使うパターン
Disposable.Combine
を使えば、AddToを使わず合成を自分で行うこともできます。公式Readmeによればパフォーマンスも良いらしいです。
(公式より引用)
five types are available for use. In terms of performance advantages, the order isCombine(d1,...,d8) (>= CreateBuilder) > Combine(IDisposable[]) >= CreateBuilder > DisposableBag > CompositeDisposable
private NankaClass nanka = new();
IDisposable disposable;
void EventSubscribe()
{
EventDispose();
var d1 = nanka.OnNankaCompletedPrepare.Subscribe();
var d2 = nanka.OnNaknaStart.Subscribe();
var d3 = nanka.OnNaknaEnd.Subscribe();
// Disposable.Combineで複数のIDisposableを合成する
disposable = Disposable.Combine(d1, d2, d3);
}
void EventDispose()
{
// AddされたIDisposableを全てDisposeする
disposable?.Dispose();
}
Disposable.Combine
引数の数が8個以下かそれ以上かでパフォーマンスが変わります。内部実装を見るとわかりますが、8個まではメンバ変数としてそれぞれ持つのに対し、それ以上は配列として持つような実装になっています。
デメリットとしては、一度Combine
したらAddもRemoveもClearもできない のと、Combineメソッドへの渡し忘れの注意が必要なことでしょうか。
AddToと違って一度ローカル変数を介して合成するので、しっかり全ての購読で返り値を取得して Disposable.Combine
に入れ忘れないように注意が必要です。
バランスを考えたら個人的には DisposableBuilder
か 動的に追加したいならDisposableBag
がオススメ