Upgrade to Pro — share decks privately, control downloads, hide ads and more …

UniRx - Reactive Extensions for Unity

Yoshifumi Kawai
April 19, 2014
58

UniRx - Reactive Extensions for Unity

Yoshifumi Kawai

April 19, 2014
Tweet

More Decks by Yoshifumi Kawai

Transcript

  1. Self Introduction @仕事 株式会社グラニ 取締役CTO C# 5.0 + .NET Framework

    4.5 + ASP.NET MVC 5 最先端C#によるハイパフォーマンスWebアプリケーション @個人活動 Microsoft MVP for Visual C# Web http://neue.cc/ Twitter @neuecc linq.js - http://linqjs.codeplex.com/ とか作ってます
  2. yieldの問題点 戻り値が返せない 例外処理ができない IEnumerator GetGoogle() { var www = new

    WWW("http://google.com/"); yield return www; // 戻り値を返せない!!! www.text is どこ } void OnMouseDown() { // このコードはコンパイルエラー // yieldはtry-catchで囲めないので例外処理できない! try { yield return StartCoroutine(GetGoogle()); } catch { } } これにより処理が分離できないという問題が 発生する。一つのIEnumeratorにダラダラと書 き連ねるしかなくなってしまう
  3. あるいはコールバック IEnumerator GetGoogle(Action<string> onCompleted, Action<Exception> onError) { var www =

    new WWW("http://google.com/"); yield return www; if (!www.error) onError(new Exception(www.error)); else onCompleted(www.text); } IEnumerator OnMouseDown() { string result; Exception error; yield return StartCoroutine(GetGoogle(x => result = x, x => error = x)); string result2; Exception error2; yield return StartCoroutine(GetGoogle(x => result2 = x, x => error2 = x)); } 結局コールバック地獄かよ! (yieldで完了待機できるので多少マシだけどとはいえ酷い)
  4. C# 5.0(async/await)なら? async Task<string> RunAsync() { try { var v

    = await new HttpClient().GetStringAsync("http://google.co.jp/"); var v2 = await new HttpClient().GetStringAsync("http://google.co.jp/"); return v + v2; } catch (Exception ex) { Debug.WriteLine(ex); throw; } } yield(await)が戻り値を返して受け取れる 例外がtry-catchできる 非同期メソッドが戻り値を返せる
  5. Unity 5.0 2014年秋ぐらいに多分リリース! Monoのバージョンは新しくなりません! つまりC#も古い(3.0)のままです! asyncは来ない!知ってた!この先も期待できない! Unity Feedback – Upgrade

    to Mono 3.0 http://feedback.unity3d.com/suggestions/upgrade-to-mono-30 Mono 3.0でC# 5.0フィーチャー入る。Voteしよう。 Vote数的に、どーもUnityコミュニティ的にも中の人達的にも、言語 あんま重要視してない気配がする……
  6. Reactive Programming #とは 近年注目のアーキテクチャスタイル 昔からあったといえばあったけど、原理原則やライブラリが充実し てきたので、最近一気に華開いた感ある The Rective Manifesto http://www.reactivemanifesto.org/

    Reactive Streams http://www.reactive-streams.org/ Principles of Reactive Programming https://www.coursera.org/course/reactive Martin Odersky(Creator of Scala) Eric Meijer(Creator of Reactive Extensions) Roland Kuhn(Akka Tech Lead)
  7. RxによるUnityの非同期通信 // xが完了したらそれでy、完了したらzのダウンロードの連鎖のフローをLINQクエリ式で var query = from x in ObservableWWW.Get("http://google.co.jp/")

    from y in ObservableWWW.Get(x) from z in ObservableWWW.Get(y) select new { x, y, z }; // Subscribe = "最後に全部まとまったあとの"コールバック(ネストしないから処理が楽) query.Subscribe(x => Debug.Log(x), ex => Debug.LogException(ex)); // もしくはCoroutineに変換して待機も可能(ToCoroutine is yieldable!) yield return StartCoroutine(query.Do(x => Debug.Log(x)).ToCoroutine());
  8. etc, etc.... // AとBを同時並列に走らせて一個にまとめる var query = Observable.Zip( ObservableWWW.Get("http://google.co.jp/"), ObservableWWW.Get("http://bing.com/"),

    (google, bing) => new { google, bing }); // エラーが起きたらリトライ処理を入れる var cancel = ObservableWWW.Get("http://hogehgoe") .OnErrorRetry((Exception ex) => Debug.LogException(ex), retryCount: 3, delay: TimeSpan.FromSeconds(1)) .Subscribe(Debug.Log); // キャンセルしたい場合は戻り値のDisposeを呼ぶだけ cancel.Dispose(); // などなど、100近くの演算子をメソッドチェーン形式で繋げることができる // あらゆる実行フローを完全にコントロールできる ところでObservableWWWは、UnityRxに同梱しているRxの形式 にラップ済みのWWWクラスで通信可能なメソッドです
  9. UnityRx solves MultiThreading problems // なんか重たい処理を別スレッドで開始するStartメソッド var heavyMethod1 = Observable.Start(()

    => { Thread.Sleep(TimeSpan.FromSeconds(3)); return 1; }); var heavyMethod2 = Observable.Start(() => { Thread.Sleep(TimeSpan.FromSeconds(3)); return 1; }); // Zipで両方並列に走らせながら連結して heavyMethod1.Zip(heavyMethod2, (x, y) => new { x, y }) .ObserveOnMainThread() // MainThreadに戻す! .Subscribe(x => { (GameObject.Find("myGuiText")).guiText.text = x.ToString(); }); 他スレッドと待ち合わせ MainThreadに戻して、そ れ以降のフローは GameObjectにアクセス可 能にする ObserveOnMainThread Subscribe後の戻り値を Disposeするだけでキャン セル可能、などなど、多種 のメソッドがマルチスレッ ド処理を支援する
  10. var asyncQuery = from a in AsyncA() from b in

    AsyncB(a) from c in AsyncC(a, b) select new { a, b, c }; 多重from(SelectMany) AsyncA AsyncB AsyncC Result
  11. Unity用の各支援メソッド // ObservableMonoBehaviour public class Hoge : ObservableMonoBehaviour { public

    override void Awake() { // 全てのMessageEventがRxで扱う形式で取得できる var query = this.OnMouseDownAsObservable() .SelectMany(_ => this.OnMouseDragAsObservable()) .TakeUntil(this.OnMouseUpAsObservable()); } } // Observable.EveryFrame // 毎フレーム毎に値を発行する Observable.EveryFrame() .Subscribe(_ => Debug.Log(DateTime.Now.ToString()));
  12. Unity用の各支援メソッド // 入れ物を用意して public class LogCallback { public string Condition;

    public string StackTrace; public UnityEngine.LogType LogType; } public static class LogHelper { public static IObservable<LogCallback> LogCallbackAsObservable() { var subject = new Subject<LogCallback>(); // callback内でSubjectに発行してあげるように作ることでRxにコンバート可能 UnityEngine.Application.RegisterLogCallback((condition, stackTrace, type) => { subject.OnNext(new LogCallback { Condition = condition, StackTrace = stackTrace, Log }); return subject; } } Unityに多くあるデリゲートによるコールバックを IObservable<T>に変換するとかなりイケテル!
  13. Unity用の各支援メソッド // 入れ物を用意して public class LogCallback { public string Condition;

    public string StackTrace; public UnityEngine.LogType LogType; } public static class LogHelper { public static IObservable<LogCallback> LogCallbackAsObservable() { var subject = new Subject<LogCallback>(); // callback内でSubjectに発行してあげるように作ることでRxにコンバート可能 UnityEngine.Application.RegisterLogCallback((condition, stackTrace, type) => { subject.OnNext(new LogCallback { Condition = condition, StackTrace = stackTrace, Log }); return subject; } } // 各処理が分離して記述可能になる、また合成処理が可能、などのメリットあり LogHelper.LogCallbackAsObservable() .Where(x => x.LogType == LogType.Warning) .Subscribe(); LogHelper.LogCallbackAsObservable() .Where(x => x.LogType == LogType.Error) .Subscribe(); Unityに多くあるデリゲートによるコールバックを IObservable<T>に変換するとかなりイケテル!