概要
- UniRxを使っててStackTraceの表示されないエラーに遭遇した。
- 原因はよくわかってないがRepeat()を使わないように書き換えたら出なくなった。
- 備忘録として書いておく
正直UniRxよく分からず使ってるので、詳細わかる方いらっしゃいましたら教えていただきたいです。
状況
- ドラッグ&ドロップで木や机などを配置できる編集用シーンがある
- 下部にあるリストはuGUI
- リストアイテムのドラッグ開始を検出するために、 UniRxにある
ObservableEventTrigger
のOnDragAsObservable
を使っている。 - 編集用シーンは前のシーンから追加読み込みしており、編集後に破棄するタイミングで下記エラーが発生した
Couldn't extract exception string from exception of type StackOverflowException (another exception of class 'NullReferenceException' was thrown while processing the stack trace)
スタックトレースを作るときにさらにエラーしているようで、何も詳細が分からない。。。
エラーする対象コード
リストアイテムが持つクラスは下記イメージ。 実際にはもう少し色々やってますが、これだけでもエラーすることは確認しました。
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の流れをイメージ仕切れてなくて、 SkipXXX
や TakeXXX
、 Repeat
等を使うと大体深みにハマる。
XXXAsObservable
と Where
ぐらいでイベント検知だけして、あとは Subscribe
内で手続き的に書くぐらいがちょうどいいかもしれない。
修正方法2(追記)
Twitterでアドバイスいただけました。
https://t.co/C1JtKpGxNm このページにRepeatによる無限ループ発生の危険性とその回避方法が書いてますよ!
— tatsunoru (@tatsunoru) July 22, 2018
イベントを「繰り返す」場合に、Repeatが通常使われますが、実は危険です。 源流がOnCompletedを発行すると無限ループ化するからです。 ObservableTriggersが終了するとOnCompletedを発行するため、安易なRepeatの使用は無限ループ行きとなります。 それを避けるには、RepeatUntilDestroy(gameObject/component), RepeatUntilDisable(gameObject/component), RepeatSafeが使えます。
コード
参考にして下記のように書き換えてみたら動きました!!!ありがとうございます〜。
void Start() { ObservableEventTrigger trigger = gameObject.AddComponent<ObservableEventTrigger>(); trigger.OnDragAsObservable() .Take(1) .RepeatUntilDestroy(gameObject) .Subscribe( _ => Debug.Log($"ドラッグ開始!") , ex => Debug.LogError(ex) ).AddTo(gameObject); }