はじめに
みなさんこんにちは!株式会社グレンジでクライアントエンジニアをしている24新卒の原島と申します。
本投稿はグレンジアドベントカレンダーの4日目の記事です。
背景
社内のUnityで開発されているプロジェクトでは今までCysharp社のライブラリであるUniRXを用いてイベントの購読処理を行っていましたが、同じくCysharp社製のRxライブラリであるR3への移行をすることになりました。
R3にはUniRXの互換機能が多く備わっているためほとんどの処理は問題なく置換できたのですが、一部躓いた部分があったので備忘録として共有します!
困ったこと
今まではゲーム内で使うボタンのイベント購読処理をUniRXを用いて以下のように行っていました
/// <summary>
/// タップイベント
/// </summary>
private IObservable<CustomButton> OnTapAsObservableInternal() {
return _onPointerUpSubject
.SkipUntil(_onPointerDownSubject)
.TakeUntil(_onPointerExitSubject)
.TakeUntil(OnStartLongTapAsObservableInternal())
.Where(data => !data.dragging)
.Repeat()
.Select(_ => this);
}
R3からSubjectがIObservableを実装しなくなったので注意するのはそれくらいかと思い移行してみたところ…
なんとストリームが完了したあと再度ストリームを生成するRepeat()オペレーターが廃止されていました!困った!
このままではゲーム内の挙動が変わってしまうので代替策を見つける旅へ出ることに。
対策①
少し調べると様々な記事でRepeatオペレーターの代替として購読する側でForEachAsync + await + ループを使用する例が紹介されていました。
https://x.com/toRisouP/status/1762496292121674176
while (!destroyCancellationToken.IsCancellationRequested)
{
await _button.OnTapAsObservableInternal()
.ForEachAsync(x =>
{
}, destroyCancellationToken);
}
挙動的には問題はないのですが、ボタンイベント処理はかなり多くの箇所から使用されており、購読している側すべてを置換するのが中々しんどい…
OnTapAsObservableInternal()関数内で完結させて購読する側は今まで通りである必要があるので断念。
仕様が限定されている実装であればこれで問題ないと思います。
対策②
オペレーターがCompleateしたらもう一度購読処理をするオペレーターを組めたら解決するのでは!ということでやってみました。
private Observable<CustomButton> OnTapAsObservableInternal()
{
return Observable.Defer(() =>
Observable.Create<CustomButton>(observer =>
{
IDisposable subscription = null;
Action subscribeAgain = null;
subscribeAgain = () =>
{
subscription = CreateTapSequence().Subscribe(
onNext: value => observer.OnNext(value),
onCompleted: result =>
{
subscription.Dispose();
subscribeAgain();
}
);
};
subscribeAgain();
return Disposable.Create(() =>
{
if (subscription != null)
{
subscription.Dispose();
}
});
})
);
}
private Observable<CustomButton> CreateTapSequence()
{
return _button.OnClickAsObservable().Select(_ => this);
}
ローカルで定義したsubscribeAgain内ではCreateTapSequence()で定義したボタンのタップをそのまま購読しています。onCompletedでは現在のサブスクリプションを破棄し再起的に購読処理を行っています。
これにより繰り返し購読がされるためRepeat()と同じような挙動を再現できました。
ボタンのコンポーネント内で完結し購読する側に影響がないため今回はこちらを採用しました。
まとめ
今までの挙動を崩さずに置換するのはかなり骨が折れました…今回はプロジェクトの事情に沿った対処療法でしたが対策①のように他のアプローチも考えられると思います。
UniRX→R3移行の手助けになったら幸いです。