6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

まだプレビュー版が外れていない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.RegisterIDisposable.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 is Combine(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 がオススメ

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?