知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UniRx】Observableが完了時に処理を行うオペレータ #103

前回の成果

エラーハンドリングのオペレータをまとめた。

soramamenatan.hatenablog.com


今回やること

Observableが完了時に処理を行うオペレータをまとめます。



ストリームのOnCompletedが呼ばれたら、同じストリームを生成する

Repeat

OnCompletedが呼ばれた際に、同じストリームを生成してSubscribeします。
ただし、このオペレータは無限ループが発生するので注意してください。
可能ならば、下記で紹介するRepeatUntilDisableやRepeatUntilDestoryを使用した方が良いケースが多いです。

ソースコード
/// <summary>
/// ストリームのOnCompletedが呼ばれたら、同じストリームを生成する
/// </summary>
private void ExcuteRepeat() {
    Observable
        .Timer(TimeSpan.FromSeconds(1))
        .Do(_ => Debug.Log("Do"))
        .DoOnCompleted(() => Debug.Log("DoOnCompleted"))
        .Repeat()
        .Subscribe();
}
結果

Timerで1秒後にメッセージを発行し、OnCompletedが発行されています。
OnCompletedが発行されたので、再びSubscribeをしています。
こちらの例では、無限ループしています。

f:id:soramamenatan:20210513091617p:plain


同じストリームを指定回数生成する

Repeat

Repeatの第一引数に発行する値を、第二引数に回数を指定することが出来ます。
定義は以下のようになっています。

/// <summary>
/// 回数指定のRepeat
/// </summary>
/// <param name="value">発行する値</param>
/// <param name="repeatCount">Repeatの回数</param>
public static IObservable<T> Repeat<T>(T value, int repeatCount)
{
    return Repeat(value, repeatCount, Scheduler.DefaultSchedulers.Iteration);
}
ソースコード
/// <summary>
/// 同じストリームを指定回数生成する
/// </summary>
private void ExcuteRepeatCount() {
    Observable
        .Repeat("Repeat", 3)
        .Subscribe(x => {
            Debug.Log("OnNext : " + x);
        }, () => {
            // OnCompletedは1回しか呼ばれない
            Debug.Log("OnCompleted");
        });
}

結果

OnNextが第二引数で指定した回数呼ばれ、その後OnCompletedが呼ばれています。
引数無しのRepeatと異なり、OnCompletedは一度しか呼ばれません。

f:id:soramamenatan:20210513092050p:plain


短時間にOnCompletedが呼ばれた場合、Repeatを止める

RepeatSafe

OnCompletedが短時間で呼ばれるRepeatは無限ループが挙げられます。
そのようなケースが発生した場合に、このオペレータを使用することで防止することができます。
ただし、意図的に制御することができません。
ですので、以下で紹介するRepeatUntilDisable やRepeatUntilDestoryを使用するのをおすすめします。

ソースコード
/// <summary>
/// 短時間にOnCompletedが呼ばれた場合、Repeatを止める
/// 意図的に制御できない
/// </summary>
private void ExcuteRepeatSafe() {
    Subject<Unit> subject = new Subject<Unit>();
    subject
        .Do(_ => {
            Debug.Log("Do");
        })
        .DoOnCompleted(() => {
            Debug.Log("DoOnCompleted");
        })
        // Repeatだと無限ループしてしまう
        //.Repeat()
        .RepeatSafe()
        .Subscribe(x => {
            Debug.Log("OnNext : " + x);
        }, () => {
            Debug.Log("OnCompleted");
        });
    subject.OnCompleted();
}
結果

Repeatですと無限ループしてしまうのを防ぐことができます。

f:id:soramamenatan:20210515132244p:plain


ストリームのOnCompletedが呼ばれたら、同じストリームを生成し、指定したGameObjectが非表示になったら、Repeatを中止する

RepeatUntilDisable

引数で指定したGameObjectが非表示になった際に、Repeatを中止することができます。
RepeatSafeと違い、Repeatを中止するタイミングを制御できます。

ソースコード
/// <summary>
/// ストリームのOnCompletedが呼ばれたら、同じストリームを生成する
/// 指定したGameObjectが非表示になったら、Repeatを中止する
/// </summary>
private void ExcuteRepeatUntilDisable() {
    this.UpdateAsObservable()
        .Where(_ => Input.anyKeyDown)
        .Subscribe(_ => {
            Debug.Log("anyKeyDown");
            gameObject.SetActive(false);
        });

    Observable
        .Timer(TimeSpan.FromSeconds(1))
        .Do(_ => Debug.Log("Do"))
        .RepeatUntilDisable(gameObject)
        .Subscribe(x => {
            Debug.Log("OnNext : " + x);
        }, () => {
            Debug.Log("OnCompleted");
        });
}
結果

指定したGameObject(今回の場合、ObservableCompleted)が非表示になった場合にRepeatが中止されています。

f:id:soramamenatan:20210515132627p:plain


ストリームのOnCompletedが呼ばれたら、同じストリームを生成し、指定したGameObjectが破棄されたら、Repeatを中止する

RepeatUntilDestroy

引数で指定したGameObjectが破棄された際に、Repeatを中止することができます。 RepeatSafeと違い、Repeatを中止するタイミングを制御できます。

ソースコード
/// <summary>
/// ストリームのOnCompletedが呼ばれたら、同じストリームを生成する
/// 指定したGameObjectが破棄されたら、Repeatを中止する
/// </summary>
private void ExcuteRepeatUntilDestory() {
    this.UpdateAsObservable()
        .Where(_ => Input.anyKeyDown)
        .Subscribe(_ => {
            Debug.Log("anyKeyDown");
            Destroy(gameObject);
        });

    Observable
        .Timer(TimeSpan.FromSeconds(1))
        .Do(_ => Debug.Log("Do"))
        .RepeatUntilDestroy(gameObject)
        .Subscribe(x => {
            Debug.Log("OnNext : " + x);
        }, () => {
            Debug.Log("OnCompleted");
        });
}
結果

指定したGameObject(今回の場合、ObservableCompleted)が破棄された場合にRepeatが中止されています。

f:id:soramamenatan:20210515132912p:plain


ストリームが完了時、例外発生時、破棄時に処理を行う

DoOnTerminate / Finally

ストリームのOnCompleted、OnError、Dispose時に処理を行うことができます。
DoOnTerminateとFinallyの違いは以下になります。

\ 完了時 例外時 破棄時
DoOnTerminate 呼ばれる 呼ばれる 呼ばれない
Finally 呼ばれる 呼ばれる 呼ばれる
ソースコード
/// <summary>
/// ストリーム完了時のDoOnTerminateとFinallyの違い
/// </summary>
private void ExcuteDiffCompleted() {
    // DoOnTerminateとFinallyの両方が呼ばれる
    CreateSubject().OnCompleted();
}

/// <summary>
/// 例外発生時のDoOnTerminateとFinallyの違い
/// </summary>
private void ExcuteDiffError() {
    // DoOnTerminateとFinallyの両方が呼ばれる
    CreateSubject().OnError(new Exception());
}

/// <summary>
/// ストリーム破棄時のDoOnTerminateとFinallyの違い
/// </summary>
private void ExcuteDiffDispose() {
    // Finallyのみ呼ばれる
    CreateDisposable().Dispose();
}

/// <summary>
/// DoOnTerminateとFinallyの違いのSubject生成
/// </summary>
private Subject<Unit> CreateSubject() {
    Subject<Unit> subject = new Subject<Unit>();
    subject
        // 例外発生時
        .DoOnError    (e  => Debug.Log("DoOnError"))
        // ストリーム完了時
        .DoOnCompleted(() => Debug.Log("DoOnCompleted"))
        // ストリーム破棄時
        .DoOnCancel   (() => Debug.Log("DoOnCancel"))
        // ストリーム完了、例外発生時
        .DoOnTerminate(() => Debug.Log("DoOnTerminate"))
        // ストリーム完了、ストリーム破棄、例外発生時
        .Finally      (() => Debug.Log("Finally")).Subscribe();
    return subject;
}

/// <summary>
/// DoOnTerminateとFinallyの違いのDisposable生成
/// </summary>
private IDisposable CreateDisposable() {
    Subject<Unit> subject = new Subject<Unit>();
    return subject
                // 例外発生時
                .DoOnError    (e  => Debug.Log("DoOnError"))
                // ストリーム完了時
                .DoOnCompleted(() => Debug.Log("DoOnCompleted"))
                // ストリーム破棄時
                .DoOnCancel   (() => Debug.Log("DoOnCancel"))
                // ストリーム完了、例外発生時
                .DoOnTerminate(() => Debug.Log("DoOnTerminate"))
                // ストリーム完了、ストリーム破棄、例外発生時
                .Finally      (() => Debug.Log("Finally")).Subscribe();
}
ストリーム完了時の結果

DoOnTerminateもFinallyも呼ばれています。

f:id:soramamenatan:20210515133553p:plain

ストリーム例外発生時の結果

DoOnTerminateもFinallyも呼ばれています。

f:id:soramamenatan:20210515133610p:plain

ストリーム破棄時の結果

Finallyのみ呼ばれています。

f:id:soramamenatan:20210515133627p:plain


今回は以上となります。
ここまでご視聴ありがとうございました。

参考サイト様

light11.hatenadiary.com

light11.hatenadiary.com