Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

58,500 views

Published on

UniRx Meetup #1 at 2015/06/19

Published in: Technology
  • Be the first to comment

History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

  1. 1. Work http://grani.jp/ C# Unity Private http://neue.cc/ @neuecc https://github.com/neuecc
  2. 2. LINQ to GameOject https://github.com/neuecc/LINQ-to-GameObject-for-Unity https://www.assetstore.unity3d.com/jp/#!/content/24256 // destroy all filtered(tag == "foobar") objects root.Descendants() .Where(x => x.tag == "foobar") .Destroy(); // get FooScript under self childer objects and self var fooScripts = root.ChildrenAndSelf().OfComponent<FooScript>();
  3. 3. 46 39 71 0 10 20 30 40 50 60 70 80 使っている 使う予定がある 使っていない UniRxを使っていますか?(計156)
  4. 4. History
  5. 5. 神獄のヴァルハラゲート モンスタハンターロアオブカード http://grani.jp/
  6. 6. using
  7. 7. サーバーサイドからUnityまで全てをC#で Isomorphic Architecture
  8. 8. http://www.slideshare.net/neuecc/reactive-extensions-8049041
  9. 9. https://github.com/neuecc/UniRx
  10. 10. Push Event Stream Event Processing Interactive/Visualize
  11. 11. CoreLibrary Framework Adapter Port of Rx.NET FromCoroutine MainThreadScheduler ObservableTriggers ReactiveProperty
  12. 12. 2014/04/19 - UniRx 1.0 http://www.slideshare.net/neuecc/unityrx-reactive-extensions-for-unity 2014/05/28 - UniRx 4.0
  13. 13. 2014/07/10 - UniRx 4.3 http://neue.cc/2014/07/01_474.html 2014/07/30 http://www.slideshare.net/neuecc/reactive-programming-by-unirxfor-asynchronous-event-processing
  14. 14. 2015/01/22 - UniRx 4.4~4.6 http://neue.cc/2014/09/08_477.html
  15. 15. 2015/03/10 - UniRx 4.7 2015/04/10 - UniRx 4.8
  16. 16. 2015/04/16 http://www.slideshare.net/neuecc/observable-everywhere-rxuni-rx 2015/05/26 - UniRx 4.8.1
  17. 17. Practices & Pitfalls
  18. 18. https://github.com/neuecc/LightNode
  19. 19. ObservableWWW + 頻出メソッド
  20. 20. // ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良い public class ObservableClient { public IObservable<WWW> GetHogeAsync(int huga) { return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga); } }
  21. 21. // ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良い public class ObservableClient { public IObservable<HogeResponse> GetHogeAsync(int huga) { return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga) .Select(www => { // これはJSON.NET for Unity(有料アセット)を使ってますが // LitJsonなりMiniJsonなりでもなんでも、とにかくデシリアライズして返す return JsonConvert.DeserializeObject<HogeResponse>(www.text); }) } }
  22. 22. // ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良い public class ObservableClient { public IObservable<HogeResponse> GetHogeAsync(int huga) { return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga) .Select(www => { // これはJSON.NET for Unity(有料アセット)を使ってますが // LitJsonなりMiniJsonなりでもなんでも、とにかくデシリアライズして返す return JsonConvert.DeserializeObject<HogeResponse>(www.text); }) .Catch((WWWErrorException error) => { // WWW通信が失敗した場合の処理を書く // 例えばダイアログ出してユーザーの入力を待つとかも // (入力結果の待ちがIObservableにしていれば)可能 return Observable.Empty<HogeResponse>(); }); } }
  23. 23. // ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良い public class ObservableClient { public IObservable<HogeResponse> GetHogeAsync(int huga) { return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga) .Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト .Select(www => { // これはJSON.NET for Unity(有料アセット)を使ってますが // LitJsonなりMiniJsonなりでもなんでも、とにかくデシリアライズして返す return JsonConvert.DeserializeObject<HogeResponse>(www.text); }) .Catch((WWWErrorException error) => { // WWW通信が失敗した場合の処理を書く // 例えばダイアログ出してユーザーの入力を待つとかも可能 return Observable.Empty<HogeResponse>(); }); } }
  24. 24. // ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良い public class ObservableClient { public IObservable<HogeResponse> GetHogeAsync(int huga) { return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga) .Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト .Select(www => { // これはJSON.NET for Unity(有料アセット)を使ってますが // LitJsonなりMiniJsonなりでもなんでも、とにかくデシリアライズして返す return JsonConvert.DeserializeObject<HogeResponse>(www.text); }) .Catch((TimeoutException error) => { // タイムアウトした場合の処理を書く // その処理が正常終了なら Observable.Empty<T> を、ダメならThrowを返す return Observable.Throw<HogeResponse>(error); }) .Catch((WWWErrorException error) => { // WWW通信が失敗した場合の処理を書く
  25. 25. // 更にリトライを考慮したエディション(複雑!!!) public class ObservableClient { public IObservable<HogeResponse> GetHogeAsync(int huga) { // リトライ耐性をつけるために自身の変数を用意する IObservable<HogeResponse> asyncRequest = null; asyncRequest = ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga) .Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト .Select(www => JsonConvert.DeserializeObject<HogeResponse>(www.text)) .Catch((TimeoutException error) => Observable.Throw<HogeResponse>(error)) .Catch((WWWErrorException error) => { // WWW通信が失敗した場合の処理を書く // 例えばダイアログ出してユーザーの入力を待つとかも(入力結果の待ちがIObservableにしていれば)可能 // リトライするならreturn asyncRequest のように自分自身を返せばいい // ちなみに3秒後にリトライ、といったようにしたければ // asyncRequest.DelaySubscription(TimeSpan.FromSeconds(3)) といったように書けばOK return Observable.Empty<HogeResponse>(); }); // PublishLastによりColdの性質が消え、何度呼ばれても一回しか結果を返さないようになる // RefCountによりPublishLastの.ConnectがSubscribe時に自動でかかるようになる return asyncRequest.PublishLast().RefCount(); } }
  26. 26. // 更にリトライを考慮したエディション(複雑!!!) public class ObservableClient { public IObservable<HogeResponse> GetHogeAsync(int huga) { // リトライ耐性をつけるために自身の変数を用意する IObservable<HogeResponse> asyncRequest = null; asyncRequest = ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga) .Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト .Select(www => JsonConvert.DeserializeObject<HogeResponse>(www.text)) .Catch((TimeoutException error) => Observable.Throw<HogeResponse>(error)) .Catch((WWWErrorException error) => { // WWW通信が失敗した場合の処理を書く // 例えばダイアログ出してユーザーの入力を待つとかも(入力結果の待ちがIObservableにしていれば)可能 // リトライするならreturn asyncRequest のように自分自身を返せばいい // ちなみに3秒後にリトライ、といったようにしたければ // asyncRequest.DelaySubscription(TimeSpan.FromSeconds(3)) といったように書けばOK return Observable.Empty<HogeResponse>(); }); // PublishLastによりColdの性質が消え、何度呼ばれても一回しか結果を返さないようになる // RefCountによりPublishLastの.ConnectがSubscribe時に自動でかかるようになる return asyncRequest.PublishLast().RefCount(); } }
  27. 27. // 更にリトライを考慮したエディション(複雑!!!) public class ObservableClient { public IObservable<HogeResponse> GetHogeAsync(int huga) { // リトライ耐性をつけるために自身の変数を用意する IObservable<HogeResponse> asyncRequest = null; asyncRequest = ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga) .Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト .Select(www => JsonConvert.DeserializeObject<HogeResponse>(www.text)) .Catch((TimeoutException error) => Observable.Throw<HogeResponse>(error)) .Catch((WWWErrorException error) => { // WWW通信が失敗した場合の処理を書く // 例えばダイアログ出してユーザーの入力を待つとかも(入力結果の待ちがIObservableにしていれば)可能 // リトライするならreturn asyncRequest のように自分自身を返せばいい // ちなみに3秒後にリトライ、といったようにしたければ // asyncRequest.DelaySubscription(TimeSpan.FromSeconds(3)) といったように書けばOK return Observable.Empty<HogeResponse>(); }); // PublishLastによりColdの性質が消え、何度呼ばれても一回しか結果を返さないようになる // RefCountによりPublishLastの.ConnectがSubscribe時に自動でかかるようになる return asyncRequest.PublishLast().RefCount(); } }
  28. 28. public class ObservableClient { // 色々なのペタペタ付ける部分は外出し IObservable<T> WithErrorHandling<T>(IObservable<WWW> source) { IObservable<T> asyncRequest = null; asyncRequest = source .Timeout(TimeSpan.FromSeconds(30)) .Select(www => JsonConvert.DeserializeObject<T>(www.text)) .Catch((TimeoutException error) => Observable.Throw<T>(error)) .Catch((WWWErrorException error) => Observable.Throw<T>(error)) .Catch((Exception error) => Observable.Throw<T>(error)); return asyncRequest.PublishLast().RefCount(); } // 以下量産 public IObservable<HogeResponse> GetHogeAsync(int huga) { return WithErrorHandling<HogeResponse>(ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga)); } public IObservable<HugaResponse> GetHugaAsync(int huga) { return WithErrorHandling<HugaResponse>(ObservableWWW.GetWWW("http://hogehoge.com/Huga?huga=" + huga)); } }
  29. 29. WhenAllで並列リクエストがめっちゃ身近に var client = new ObservableClient(); Observable.WhenAll( client.GetFooAsync(), client.GetFooAsync(), client.GetFooAsync()) .Subscribe(xs => { }); // 戻り値が違うとコンパイルエラーで使えない!ダメぢゃん!!! Observable.WhenAll( client.GetFooAsync(), client.GetBarAsync(), client.GetBazAsync()) .Subscribe(xs => { });
  30. 30. Castを駆使する // 強引に型を合わせてから取り出す:) Observable.WhenAll( client.GetFooAsync().Cast(default(object)), client.GetBarAsync().Cast(default(object)), client.GetBazAsync().Cast(default(object))) .Subscribe(xs => { var foo = xs[0] as FooResponse; var bar = xs[1] as BarResponse; var baz = xs[2] as BazResponse; });
  31. 31. WhenAllに無限シーケンスが混ざってたら?
  32. 32. 要素(1)のメソッドは末尾Asyncの命名規約で統一
  33. 33. SignalRによるC#/WebSocketによるRPCを採用
  34. 34. 完了コールバック as IObservable<T>
  35. 35. 値+イベントの表現 [Serializable] public class Character : MonoBehaviour { public Action<int> HpChanged; [SerializeField] int hp = 0; public int Hp { get { return hp; } set { hp = value; var h = HpChanged; if (h != null) { h.Invoke(hp); } } [Serializable] public class Character : MonoBehaviour { public IntReactiveProperty Hp; }
  36. 36. // これでトグルオンオフがIObservable<bool>として取ってこれる、とか MyToggle.OnValueChangedAsObservable().Subscribe(x => { })
  37. 37. Unity + Rxに適したUIパターン Passive View Presenter (Supervising Controller) Model updates view state-change events user events update model UIControl.XxxAsObservable UnityEvent.AsObservable ObservableEventTrigger Subscribe ToReactiveProperty ReactiveProperty Subscribe SubscribeToText SubscribeToInteractable
  38. 38. public class CalculatorPresenter : MonoBehaviour { public InputField Left; public InputField Right; public Text Result; void Start() { var left = this.Left .OnValueChangeAsObservable() .Select(x => int.Parse(x)); var right = this.Right .OnValueChangeAsObservable() .Select(x => int.Parse(x)); left.CombineLatest(right, (l, r) => l + r) .SubscribeToText(this.Result); } }
  39. 39. インスペクタで貼り付けるPの抱える問題 // 子供。これはまぁ普通 [Serializable] public class ChildPresenter : MonoBehaviour { public IntReactiveProperty Hp; // serializable public ReadOnlyReactiveProperty<bool> IsDead { get; private set; } void Start() { IsDead = Hp.Select(x => x <= 0) .ToReadOnlyReactiveProperty(); } } // 親 [Serializable] public class ParentPresenter : MonoBehaviour { public ChildPresenter ChildPresenter; public Text IsDeadDisplay; void Start() { // IsDeadは触れるか? ChildPresenter.IsDead .SubscribeToText(IsDeadDisplay); } }
  40. 40. インスペクタで貼り付けるPの抱える問題 // 子供。これはまぁ普通 [Serializable] public class ChildPresenter : MonoBehaviour { public IntReactiveProperty Hp; // serializable public ReadOnlyReactiveProperty<bool> IsDead { get; private set; } void Start() { IsDead = Hp.Select(x => x <= 0) .ToReadOnlyReactiveProperty(); } } // 親 [Serializable] public class ParentPresenter : MonoBehaviour { public ChildPresenter ChildPresenter; public Text IsDeadDisplay; void Start() { // IsDeadは触れるか? ChildPresenter.IsDead .SubscribeToText(IsDeadDisplay); } }
  41. 41. MonoBehaviour同士の初期化順は不定
  42. 42. // 親。 public class ParentPresenter : PresenterBase { public ChildPresenter ChildPresenter; public Text IsDeadDisplay; protected override IPresenter[] Children { get { return new IPresenter[] { ChildPresenter }; } // 子供を指定する } protected override void BeforeInitialize() { ChildPresenter.PropagateArgument(1000); // 子供に初期値を渡す } protected override void Initialize() { // 子供の初期化は終わってるので問題なく触れる ChildPresenter.IsDead.SubscribeToText(IsDeadDisplay); }
  43. 43. // 子供。 [Serializable] public class ChildPresenter : PresenterBase<int> { public IntReactiveProperty Hp; // serializable public ReadOnlyReactiveProperty<bool> IsDead { get; set; } protected override IPresenter[] Children { get { return EmptyChildren; } } protected override void BeforeInitialize(int argument) { } // 親から引数が渡ってくるので初期値として使える protected override void Initialize(int argument) { Hp.Value = argument; IsDead = Hp.Select(x => x <= 0).ToReadOnlyReactiveProperty(); } }
  44. 44. Subscribe忘れで発火しない // 通信して新しいデータを登録するとする // 戻り値は IObservable<Unit> で、SetだしということでSubscribe忘れ // →何も起こらない→クソが! new ObservableClient().SetNewData(100);
  45. 45. Subscribe忘れで発火しない // 通信して新しいデータを登録するとする // 戻り値は IObservable<Unit> で、SetだしということでSubscribe忘れ // →何も起こらない→クソが! new ObservableClient().SetNewData(100);
  46. 46. // uGUIのボタンクリックから非同期通信(とか)に繋げる button.OnClickAsObservable() .SelectMany(_ => new ObservableClient().NanikaHidoukiAsync(100)) .Subscribe(_ => { Debug.Log("Nanika Hidouki sita"); });
  47. 47. // uGUIのボタンクリックから非同期通信(とか)に繋げる button.OnClickAsObservable() .SelectMany(_ => { throw new Exception("なにか例外がおきた"); return new ObservableClient().NanikaHidoukiAsync(100); }) .Subscribe(_ => { Debug.Log("Nanika Hidouki sita"); });
  48. 48. // uGUIのボタンクリックから非同期通信(とか)に繋げる button.OnClickAsObservable() .SelectMany(_ => { throw new Exception("なにか例外がおきた"); return new ObservableClient().NanikaHidoukiAsync(100); }) .OnErrorRetry() .Subscribe(_ => { Debug.Log("Nanika Hidouki sita"); });
  49. 49. button.OnClickAsObservable().Subscribe(_ => { // Subscribe in Subscribe...! new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ => { Debug.Log("Nanika Hidouki sita"); }); });
  50. 50. 例外は避け得ない、そしてRetryは…… button.OnClickAsObservable().Subscribe(_ => { // Subscribe in Subscribe...! new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ => { Debug.Log("Nanika Hidouki sita"); }); });
  51. 51. 例外は避け得ない、そしてRetryは…… button.OnClickAsObservable().Subscribe(_ => { // Subscribe in Subscribe...! new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ => { Debug.Log("Nanika Hidouki sita"); }); });
  52. 52. Conclusion
  53. 53. え、これだけ? Real World UniRx

×