naichi's lab

3日後の自分(他人)への書き置き

UniRx使用シーンの破棄時に詳細のわからないエラーが出た(Couldn't extract exception string from exception)

概要

  • UniRxを使っててStackTraceの表示されないエラーに遭遇した。
  • 原因はよくわかってないがRepeat()を使わないように書き換えたら出なくなった。
  • 備忘録として書いておく

正直UniRxよく分からず使ってるので、詳細わかる方いらっしゃいましたら教えていただきたいです。

状況

f:id:naichilab:20180722231230p:plain:w320

  • ドラッグ&ドロップで木や机などを配置できる編集用シーンがある
  • 下部にあるリストはuGUI
  • リストアイテムのドラッグ開始を検出するために、 UniRxにある ObservableEventTriggerOnDragAsObservable を使っている。
  • 編集用シーンは前のシーンから追加読み込みしており、編集後に破棄するタイミングで下記エラーが発生した
Couldn't extract exception string from exception of type StackOverflowException
(another exception of class 'NullReferenceException' was thrown while processing the stack trace)

f:id:naichilab:20180722230824p:plain:w320

スタックトレースを作るときにさらにエラーしているようで、何も詳細が分からない。。。

エラーする対象コード

リストアイテムが持つクラスは下記イメージ。 実際にはもう少し色々やってますが、これだけでもエラーすることは確認しました。

    public class StockNodePresenter : MonoBehaviour
    {
        void Start()
        {
            ObservableEventTrigger trigger = gameObject.AddComponent<ObservableEventTrigger>();
            trigger.OnDragAsObservable()
                .Take(1) //ドラッグ開始の瞬間を取得したい
                .Repeat()
                .Subscribe(
                    _ => Debug.Log($"ドラッグ開始!")
                    , ex => Debug.LogError(ex)
                ).AddTo(gameObject);
        }
    }

ちょっと脱線 最初は Take(1)First() で書いていたんですが、 そのときは System.InvalidOperationException: sequence is empty というエラーが出ていました。 これはFirst()をSubscribeした場合、OnNextが呼ばれずにOnCompleteした場合エラーになるからっぽいです。

エラーしなくなった変更後のコード

下記にように書き換えたらエラーしなくなりました。

    public class StockNodePresenter : MonoBehaviour
    {
        void Start()
        {
            ObservableEventTrigger trigger = gameObject.AddComponent<ObservableEventTrigger>();
            bool isDrag = false;

            trigger.OnDragAsObservable()
                .Where(_ => !isDrag)
                .Subscribe(
                    _ =>
                    {
                        Debug.Log($"ドラッグ開始!");
                        isDrag = true;
                    }, ex => Debug.LogError(ex)
                ).AddTo(gameObject);
        }
    }

まとめ

うーん動いたけど結局なぜエラーするのか全く理解できていない。

今の自分だとStreamの流れをイメージ仕切れてなくて、 SkipXXXTakeXXXRepeat 等を使うと大体深みにハマる。 XXXAsObservableWhere ぐらいでイベント検知だけして、あとは Subscribe 内で手続き的に書くぐらいがちょうどいいかもしれない。

修正方法2(追記)

Twitterでアドバイスいただけました。

イベントを「繰り返す」場合に、Repeatが通常使われますが、実は危険です。 源流がOnCompletedを発行すると無限ループ化するからです。 ObservableTriggersが終了するとOnCompletedを発行するため、安易なRepeatの使用は無限ループ行きとなります。 それを避けるには、RepeatUntilDestroy(gameObject/component), RepeatUntilDisable(gameObject/component), RepeatSafeが使えます。

引用元 neue cc - UniRx 4.8 - 軽量イベントフックとuGUI連携によるデータバインディング

コード

参考にして下記のように書き換えてみたら動きました!!!ありがとうございます〜。

        void Start()
        {
            ObservableEventTrigger trigger = gameObject.AddComponent<ObservableEventTrigger>();
            trigger.OnDragAsObservable()
                .Take(1)
                .RepeatUntilDestroy(gameObject)
                .Subscribe(
                    _ => Debug.Log($"ドラッグ開始!")
                    , ex => Debug.LogError(ex)
                ).AddTo(gameObject);
        }