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.

Observable Everywhere - Rxの原則とUniRxにみるデータソースの見つけ方

63,976 views

Published on

Published in: Software
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Observable Everywhere - Rxの原則とUniRxにみるデータソースの見つけ方

  1. 1. Observable Everywhere Rxの原則とUniRxにみるデータソースの見つけ方 2015/04/16 Yoshifumi Kawai - @neuecc
  2. 2. @neuecc - Who, Where, When 2009/04 linq.js – LINQ to Objects for JavaScript https://linqjs.codeplex.com/ この頃からずっとLINQを追いかけてる 2009/09/04 最初のRx記事、以降現在まで70以上は書いてる http://neue.cc/category/programming/rx/ 2011/04~ Microsoft MVP for .NET(C#) 野良C#エヴァンジェリスト(現在も継続中)
  3. 3. @Work, @LINQ 2012/10~ 株式会社グラニ 取締役CTO 「神獄のヴァルハラゲート」「モンスターハンターロアオブカード」 サーバー/クライアント共に「最先端 C# 技術を使った」ゲーム開発 C# 5.0 + .NET Framework 4.5 + ASP.NET MVC 5 + Unity 2014/04/19 UniRx最初の公開 - https://github.com/neuecc/UniRx 現在☆286, ver 4.8.0 2014/09/24 LINQ to BigQuery - https://github.com/neuecc/LINQ-to-BigQuery/ 未だにLINQへの関心は衰えずといったところ
  4. 4. Agenda
  5. 5. せつめい 基本的には言語非依存の説明です RxJavaの人もReactiveCocoaの人もWelcome サンプルコードはC#になりますので適宜補完してください Rxの基本原則などを紹介 衒学趣味を蹴っ飛ばして実用的な意味合いを探る UniRxを例にRx利用時のデータソースの実例を紹介 自分の言語、自分のプラットフォームでも応用効くはず
  6. 6. OnNext* (OnError | OnCompleted)?
  7. 7. The grammar of messages OnNext* (OnError | OnCompleted)? 日本語でおk OnNextがゼロ回以上の後に OnErrorもしくはOnCompletedが一回来る(もしくはどちらも来ない) public interface IObserver<in T> { void OnCompleted(); void OnError(Exception error); void OnNext(T value); }
  8. 8. foreachで考えてみる; OnNext foreach (var x in sequence) { OnNext(x); } OnNext* 空の場合はOnNextは一度 も呼ばれないのでOnNext*
  9. 9. foreachで考えてみる; OnCompleted foreach (var x in sequence) { OnNext(x); } OnCompleted(); OnNext* (OnCompleted)? もしsequneceが無限シーケンス (例えばunfold)だったら OnCompletedまで行かないので OnNext * (OnCompleted)?
  10. 10. foreachで考えてみる; OnError try { foreach (var x in sequence) { OnNext(x); } } catch (Exception ex) { OnError(ex); // RxでonErrorをハンドルした場合 // 再スローはない、これはあくまで再現 throw; } OnCompleted(); OnNext* (OnError | OnCompleted)? もし途中で例外が起きたら OnCompletedは呼ばれずOnErrorだ けが呼ばれることになるので OnNext * (OnError | OnCompleted)?
  11. 11. 意味が通る!でも、なんで?
  12. 12. IEnumeratorから考える public interface IEnumerator<out T> { T Current { get; } // 現在の要素 bool MoveNext(); // 次の要素があるか確認しCurrentにセット }
  13. 13. 分解 public interface IEnumerator<out T> { T Current { get; } bool MoveNext() throws Exception; // (C#にthrowsはないけどネ) } public interface IEnumerator<out T> { T Current { get; } true|false|Exception MoveNext(); } MoveNextを分解すると true/false/Exceptionのどれかを返す
  14. 14. 集約 public interface IEnumerator<out T> { T Current { get; } bool MoveNext() throws Exception; // (C#にthrowsはないけどネ) } public interface IEnumerator<out T> { T Current { get; } T|void|Exception GetNext(); } trueの時Tを、falseの時voidを返すと考 えてCurrentも集約
  15. 15. そして(何故か)反転させる public interface IEnumeratorDual<in T> { void GotNext(T|void|Exception); } public interface IEnumerator<out T> { T|void|Exception GetNext(); }
  16. 16. 3つにちゃんとバラして public interface IEnumeratorDual<in T> { void GotNext(T); void GotNothing(void); void GotException(Exception); } public interface IEnumeratorDual<in T> { void GotNext(T|void|Exception); }
  17. 17. あら不思議、IObserver<T>が! public interface IEnumeratorDual<in T> { void GotNext(T); void GotNothing(void); void GotException(Exception); } 名前を変えれば出来上がり public interface IObserver<in T> { void OnNext(T value); void OnCompleted(); void OnError(Exception error); }
  18. 18. IEnumerableのほうも勿論 public interface IEnumerable<out T> { IEnumerator<T> GetEnumerator(); }
  19. 19. IEnumerator<T> : IDisposable public interface IEnumerable<out T> { IEnumerator<T> GetEnumerator(); } public interface IEnumerable<out T> { IDisposable|IEnumerator<T> GetEnumerator(); } IEnumerator<T> : IDisposable
  20. 20. public interface IEnumerableDual<out T> { IDisposable SetEnumeratorDual(IEnumeratorDual<T>); } public interface IEnumerable<out T> { IDisposable|IEnumerator<T> GetEnumerator(); } 当然のように(何故か)逆にする
  21. 21. public interface IEnumerableDual<out T> { IDisposable SetEnumeratorDual(IEnumeratorDual<T>); } あら不思議、IObservable<T>が! public interface IObservable<out T> { IDisposable Subscribe(IObserver<T> observer); } 名前を整えて出来上がり
  22. 22. Mathematical Dual 双対 なるほどわからん ふかーい関連性がある、ぐらいでいいんじゃまいか 実用的な意味では、OnNext* (OnError | OnCompleted)?だけ覚えればいい 同じように見なすことができる、という発見 IEnumerable<T> = イテレータパターン IObservable<T> = オブザーバーパターン 一見無関係の2つのデザインパターンに見つけた深い関連性 イテレータのためのLINQがオブザーバーにも同じように適用できる発見 Reactive Extensions= LINQ to Events / LINQ to Asynchronous
  23. 23. Pull(Interactive) vs Push(Reactive) DataSource (IEnumerable<T>) Action 値の要求(Pull) DataSource (IObservable<T>) Action 値の送信(Push)
  24. 24. Length or Time IEnumerable<T> length IObservable<T> time event async IE<T> イベント(や非同期)を時間軸に乗った シーケンスと見なすことができる
  25. 25. Same Atmosphere var query = from person in sequence where person.Age >= 20 select person.Name; foreach (var item in query) { OnNext(item); } var query = from person in sequence where person.Age >= 20 select person.Name; query.Subscribe(item => { OnNext(item); }); IEnumerble<T> IObservable<T> コレクション処理(filter/map)が超強力で有益なのは既知の話。それと同じように 書ける、同じような挙動を取るということが注意深くデザインされている。 これがRxの革命的なポイントであり普及の一助となった
  26. 26. Asynchronous Futures
  27. 27. Synchronous Asynchronous Single(1)Multiple(*) var x = f(); var x = await f(); var query = from person in sequence where person.Age >= 20 select person.Name; foreach (var item in query) { OnNext(item); } var query = from person in sequence where person.Age >= 20 select person.Name; query.Subscribe(item => { OnNext(item); }); IEnumerble<T> IObservable<T> Func<T> Task<T>
  28. 28. Synchronous Asynchronous Single(1)Multiple(*) var x = f(); var x = await f(); var query = from person in sequence where person.Age >= 20 select person.Name; foreach (var item in query) { OnNext(item); } var query = from person in sequence where person.Age >= 20 select person.Name; query.Subscribe(item => { OnNext(item); }); IEnumerble<T> IObservable<T> Func<T> Task<T> FutureとかPromise とか言われるアレの C#での表現
  29. 29. C# 5.0での非同期事情 C# 5.0では単発の非同期処理は全てTask<T>を返す async/awaitという言語構文でのサポートが圧倒的に大きい Task<T>にawaitするとTが取れるし、.NET Frameworkで全面的に非同期APIが追加された Task is Observable, Observable is Awaitable 相互変換可能(Taskは長さ1のIObservable、IObservableはLastを取得してTaskに変 換できる)なので、適宜必要な局面で変換 Before async/await 専用構文がないTaskだけとの比較だとRxのほうが圧倒的に使いやすかった というわけで、他言語においても事情は同じになるかな、と 専用構文が入ればそちらに寄って、それまではRxが使う上では強いはず
  30. 30. What is UniRx?
  31. 31. Welcome to the Reactive Game Architecture! https://github.com/neuecc/UniRx Unity用のReactive Extensions実装 (.NET用のRxはUnityで動かないので自前で実装)
  32. 32. Official(?) ReactiveX Family http://reactivex.io/ ReactiveX公式サイトのLanguagesにラインナップ されています(現状、非公式Rxでは唯一)
  33. 33. CoreLibrary Framework Adapter Rx.NET RxJS RxJava RxAndroid ReactiveUI ReactiveProperty
  34. 34. CoreLibrary Framework Adapter UniRxは全部入り (多分ReactiveCocoaとか もそうでしょうけど) Port of Rx.NET MainThreadSchedulerObservableTriggers ReactiveProperty
  35. 35. Observable Everywhere
  36. 36. データソースを見つけよう 何はともあれObservableの源流が必要 使い方は後からついてくる 通常のLINQ(to Objects)も何がシーケンスとみなせるか(DOMの Traverseはシーケンスになる、DatabaseのRowはシーケンスとみな せる、などなど)がまず最初にある シーケンスを作ろう シーケンスのように見なせるものがあれば、そこにLINQはある 見なせそうで見なせない、ありそうでないものがあれば、作りだ そう、そうすればそこにLINQ(to Objects/to Events = Rx)が現れる
  37. 37. Push Event Stream Event Processing Interactive/Visualize
  38. 38. Statndard Static Generators Range, Repeat, ToObservable OnNext{n} (OnError | OnCompleted)? Return OnNext (OnError | OnCompleted)? Empty OnCompleted Never () Throw OnError 単体ではそう使うこともな い感じですが、何かと組み 合わせていく感じ .Concat(Observable.Never)で、終 了を抑止する(OnCompletedは飛 ばさない、あるいは他のオペレー タが飛ばす)、的な使い方ができる
  39. 39. AsyncCallback Converter IEnumerator GetStringAsync(string url, Action<string> onCompleted, Action<Exception> onError); Callback Hell! 謎のIEnumerator(Unity固有事情) IObservable<string> Get(string url); Callback to IObservable<T> UniRxは組み込みの通信メソッドとして ObservableWWW.Get/Postメソッドなどを予め用意して あるので、ユーザーはすぐに非同期をRxで扱える
  40. 40. But... あれ、一緒……? ObservableWWW.Get("http://hogemoge").Subscribe(Action<string>, Action<Exception>) StartCoroutine(GetStringAsync("http://hogemoge", Action<string>, Action<Exception>))
  41. 41. GetStringAsync("http://hogemoge", nextUrl => { GetStringsAsync(nextUrl, finalUrl => { GetStringsAsync(finalUrl, result => { /* result handling */ }, ex => { /* third error handling */ }); }, ex => { /* second error handling */ }); }, ex => { /* first error handling */ }); Callback Hell ネスト地獄による圧倒的 な見通しの悪さ エラーハンドリングの困難(try- catchで囲んでも意味が無い) ObservableWWW.Get("http://hogemoge") .SelectMany(nextUrl => ObservableWWW.Get(nextUrl)) .SelectMany(finalUrl => ObservableWWW.Get(finalUrl)) .Subscribe( result => { /*result handling */ }, ex => { /* error handling */ }); ネストを一段に抑える パイプライン統合のエラー処理
  42. 42. Composability var query = from nextUrl in ObservableWWW.Get("http://hogemoge") from finalUrl in ObservableWWW.Get(nextUrl) from result in ObservableWWW.Get(finalUrl) select new { nextUrl, finalUrl, result }; var parallelQuery = Observable.WhenAll( ObservableWWW.Get("http//hogehoge"), ObservableWWW.Get("http//hugahuga"), ObservableWWW.Get("http//takotako")); var parallelQuery = Observable.WhenAll( ObservableWWW.Get("http//hogehoge"), ObservableWWW.Get("http//hugahuga"), ObservableWWW.Get("http//takotako")) .Timeout(TimeSpan.FromSeconds(15)); var incrementalSearch = userInput .Throttle(TimeSpan.FromMilliseconds(250)) .Select(keyword => SearchAsync(keyword)) .Switch(); 前の結果を保持したまま の直列処理 並べるだけで済む並列処理 メソッドを足すだけのTimeout付与 ユーザー入力など他のIObservableと の自然な融和と、複雑なPruning
  43. 43. UIEvent Converter Button IObservable<Unit> InputField IObservable<string> Toggle IObservable<bool> Slider IObservable<float> var incrementalSearch = input.OnValueChangeAsObservable() .Throttle(TimeSpan.FromMilliseconds(250)) .Select(keyword => SearchAsync(keyword)) .Switch(); from InputField toggle.OnValueChangedAsObservable() .Subscribe(x => button.interactable = x); ToggleのON/OFFでボタン の有効/非有効切り替え
  44. 44. ObservableTriggers 衝突 = IObservable<Collision> cube.OnCollisionEnterAsObservable().Subscribe(); カメラに入る = OnBecameVisibleAsObservable() = IObservable<Unit> カメラから出る = OnBecameInvisibleAsObservable() = IObservable<Unit> などなど、あらゆるインタラクションが全てIObservableになる
  45. 45. Lifecycle as Observable frame(time) OnDestroyAsObservable = (OnNext{1} OnCompleted) ゲームループは毎フレーム(60fpsなら1/60秒)毎 にメソッドが呼びだされる Observable.EveryUpdate().Subscribe(); // IObservable<long>
  46. 46. MultiThreading Rx = Observable + LINQ + Scheduler Observable.Start(() => { /* なにか重い処理 */ return "hogemoge"; }, Scheduler.ThreadPool) .ObserveOn(Scheduler.MainThread) // MainThreadに戻す .DelaySubscription(TimeSpan.FromSeconds(3), Scheduler.MainThreadIgnoreTimeScale) .Subscribe(x => ui.text = x); // UIパーツはメインスレッドからじゃないと触れない Schedulerは時空魔法の、時(AbsoluteTime/RelativeTime)と 空間(実行スレッド/SynchronizationContext)をコントロール
  47. 47. Polling ポーリングはIObservableと「見なせる」 IObservable<string> WatchFile(string path) { return Observable.Interval(TimeSpan.FromSeconds(5)) .Select(_ => File.ReadAllText(path)); } IObservable<Notification<Unit>> Ping(string path) { return Observable.Interval(TimeSpan.FromSeconds(5)) .SelectMany(_ => ObservableWWW.Get("http://ping").Timeout(TimeSpan.FromSeconds(1))) .AsUnitObservable() .Materialize(); }
  48. 48. EveryValueChanged 更に踏み込んで、値変更を常時監視する黒魔法化 原理はゲームループのUpdate監視による毎フレームチェック 常に必ず呼ばれるから監視コストが相対的に低い(ゲームエンジンならでは) 生存期間管理 無限監視は危険なので、監視対象の生存具合をチェック GameObjectなら破壊されていたら監視終了 C#オブジェクトなら、WeakReferenceで参照保持し、GCされてたら監視終了 // Position(x,y,z)が変化したら通知するIObservable<Vector3> this.ObserveEveryValueChanged(x => x.transform.position) .Subscribe(x => Debug.Log("poistion changed:" + x));
  49. 49. Logs are EventStream ログはInProcessにおいてもStreamとして扱えるべき logger(“Player”) logger(“Monster”) logger(“Network”) logger(“ItemResolver”) ObservableLogger.Listener (IObservable<LogEntry>) SubscribeToLogConsole SubscribeToInGameDebugWindow SubscribeToNetWorkLogStorage
  50. 50. EventAggregator/MessageBroker InProcessのMessageBus(名前はなんでもいいや) 先のObservableLoggerと同じ図式 中央がIObservable<T>なら 購読 = IObservable<T>.Subscribe() 解除 = subscription.Dispose()
  51. 51. 最も単純な(しかし便利な)実装 public static class MessageBroker { public static void Publish<T>(T message) { Notifier<T>.Instance.OnNext(message); } public static IObservable<T> Receive<T>() { return Notifier<T>.Instance.AsObservable(); } static class Notifier<T> { public static readonly Subject<T> Instance = new Subject<T>(); } } 型(<T>)でフィルタされるグローバル に通知可能な何か、と考える 送信側: MessageBroker.Publish(new HogeEvent()) 受信側: MessageBroker.Receive<HogeEvent>()
  52. 52. Model-View-Whatever
  53. 53. M-V-Hell MVVVVVV
  54. 54. M-V-VM DataBinding!
  55. 55. バインディングに必要なもの バインディングエンジン Reactive Extensionsはバインディングエンジン「ではない」 RxでViewに何か処理するにはViewを知らなければならない Unityにバインディングエンジンはない 作るか諦めるか どうしても必要なら作るしかない 私はMVVMを選ばないことを選んだ Unityにおいては(Rxがあれば)そこまで必要ではないと判断した
  56. 56. Model-View-Presenter DataBindingがないので ViewModelのかわりに PresenterがViewを直接弄り、 イベントを受け取る Rxで各レイヤーを繋げてしまえば見通 しよく構築可能
  57. 57. NotificationModel Modelは通知可能でなければならない // 変更通知付きなモデル public class Enemy { // HPは変更あったら通知して他のところでなんか変化を起こすよね? public IReactiveProperty<long> CurrentHp { get; private set; } // 死んだら通知起こすよね? public IReadOnlyReactiveProperty<bool> IsDead { get; private set; } public Enemy(int initialHp) { // 死んだかどうかというのはHPが0以下になったら、で表現できる CurrentHp = new ReactiveProperty<long>(initialHp); IsDead = CurrentHp.Select(x => x <= 0).ToReadOnlyReactiveProperty(); } }
  58. 58. ReactiveProperty public class ReactivePresenter : MonoBehaviour { // ViewからのイベントをUIEventAsObservableで購読 Button button; Text textView; // ModelからのイベントをReactivePropertyで購読、SubscribeでViewにUpdate Enemy enemy; // 宣言的なUI記述 void Start() { // ボタンクリックでHPを減らす button.OnClickAsObservable().Subscribe(_ => enemy.CurrentHp.Value -= 99); // その変更を受けてUIに変更を戻す enemy.CurrentHp.SubscribeToText(textView); // とりあえず現在HPをTextに表示 // もし死んだらボタンクリックできないようにする enemy.IsDead.Select(isDead => !isDead).SubscribeToInteractable(button); } } ReactivePropertyは通知付きモデルの作成を補助 値自体が変更をIObservable<T>として通知
  59. 59. Model-View-(Reactive)Presenter
  60. 60. not databinding but databinding データバインディングっぽい Rxの力で、かなりそれらしい感触になる マクロがあればもっと整形できそうだけどC#はマクロないので 別にそこまでは必要じゃない 無理しない やはりゲームなのでパフォーマンスの読みやすさは必須 バインディングエンジン作成は一朝一夕でできない、それよりMV(R)Pはシンプ ルな、というか中間レイヤーはなにもないので挙動も読みやすくオーバヘッド もない 既に実績あるバインディングエンジンがあるなら、それに乗っかればいい ReactivePropertyはModelの作成を補助するのでMVVMのMやVMのレイヤーで十分使える
  61. 61. Conclusion
  62. 62. 資料紹介 Event processing at all scales with Reactive Extensions http://channel9.msdn.com/events/TechDays/Techdays-2014-the- Netherlands/Event-processing-at-all-scales-with-Reactive-Extensions 現時点でのRx入門の決定版といえる素晴らすぃVideo Bart氏は現在はMicrosoftのPrincipal Software Engineer(偉い) Cloud-Scale Event Processing Using Rx http://buildstuff14.sched.org/event/174c265e4ec350bd2e005b59bce0275e 激烈にAdvancedな内容 IReactiveProcessingModel, SubjectのDuality, 次世代のRx(3.0)などなど 更なる未来を見据えた内容なので是非一読を
  63. 63. まとめ LINQ to Everywhere Reactive Extensions is not only Asynchronous Reactive Extensions is not only Data-Binding Observable Everywhere データソースさえ見つかれば、そこにRxはある あとは無限の合成可能性を活かせばいい! まず、データソースを探しだしてください

×