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

UniRx - Reactive Extensions for Unity

Yoshifumi Kawai
April 19, 2014
48

UniRx - Reactive Extensions for Unity

Yoshifumi Kawai

April 19, 2014
Tweet

More Decks by Yoshifumi Kawai

Transcript

  1. UnityRx - Reactive Extensions for Unity
    2014/04/19
    Yoshifumi Kawai - @neuecc

    View full-size slide

  2. 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/ とか作ってます

    View full-size slide

  3. Unity
    Game Engine but Not Only for Game Use

    View full-size slide

  4. Unityとは
    押しも押されぬゲームエンジン
    http://japan.unity3d.com/
    最近は公式に3Dだけじゃなく2Dもサポートされた
    クロスプラットフォーム(PC/iOS/Android/Windows Phone/
    Windows Store/etc...)
    国内でも事例多数(iOS版ドラクエ8などなど)
    C#で書ける! ※但しバージョン的には3.0相当

    View full-size slide

  5. ゲーム以外の用途にも
    映像の出力先としてのUnity
    VR(Oculus Rift)やNUI(Kinect, Leap Motion)などデバイスがあっ
    ても、何に表示するの?何を表現するの?
    そこの最有力候補としてのUnity
    強力な開発環境、3D表現、AssetStore、そしてシェア
    多くのデバイスがUnity用のSDKを用意している
    メディアアート
    ProcessingやopenFrameworksなどのフィールドへ (期待)

    View full-size slide

  6. Async in Unity
    Coroutine is not good practice for asynchronous operation

    View full-size slide

  7. Unityの非同期
    通信処理はyield returnで待機できる
    コールバック地獄にならない!
    すばら!すばら!
    と思ったことは一度もありません:)
    IEnumerator OnMouseDown()
    {
    var www = new WWW("http://bing.com/");
    yield return www; // これで待機
    Debug.Log(www.text);
    }

    View full-size slide

  8. 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にダラダラと書
    き連ねるしかなくなってしまう

    View full-size slide

  9. あるいはコールバック
    IEnumerator GetGoogle(Action onCompleted, Action 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で完了待機できるので多少マシだけどとはいえ酷い)

    View full-size slide

  10. C# 5.0(async/await)なら?
    async Task 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できる
    非同期メソッドが戻り値を返せる

    View full-size slide

  11. 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コミュニティ的にも中の人達的にも、言語
    あんま重要視してない気配がする……

    View full-size slide

  12. Reactive Programming

    View full-size slide

  13. 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)

    View full-size slide

  14. Gartner’s Hype Cycle
    2013 Application Architecture/Application Development
    On the Rise - Reactive Programming

    View full-size slide

  15. Reactive Extensions
    .NETでのReactive Programmingライブラリ
    https://rx.codeplex.com
    Microsoftのプロジェクトだったけど現在はOSS化している
    LINQスタイルでイベントや非同期が処理できる
    他言語への移植
    RxJava(RxNetよりむしろ注目されてるぐらい)
    https://github.com/Netflix/RxJava 2070 Stars
    RxJS(Microsoft自身によるもの)
    https://github.com/Reactive-Extensions/RxJS 1021 Stars

    View full-size slide

  16. UnityRx - Reactive Extensions for Unity
    というのを作りました、本日公開!
    ほとんど(8割ぐらい)は自前で書いた
    公式のRxはヘヴィすぎてUnityの古いC#で動かしきれない
    iOSはAOTの問題もあって回避頑張れる気がしない
    ので自前で書くことに。
    Available Now(?)
    GitHub - https://github.com/neuecc/UnityRx/
    AssetStore – 準備中(数日中に出すので待ってて!) 無料!

    View full-size slide

  17. Curing Your Asynchronous
    Programming Blues
    Rx saves your life & codes

    View full-size slide

  18. 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());

    View full-size slide

  19. 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クラスで通信可能なメソッドです

    View full-size slide

  20. Orchestrate MultiThreading
    and LINQ to MonoBehaviour Message Events

    View full-size slide

  21. 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するだけでキャン
    セル可能、などなど、多種
    のメソッドがマルチスレッ
    ド処理を支援する

    View full-size slide

  22. AsyncA AsyncB
    SelectMany - 直列の結合
    AsyncA
    Zip - 並列の結合
    AsyncB
    Result

    View full-size slide

  23. AsyncA AsyncB
    AsyncC
    Result
    AsyncA().SelectMany(a => AsyncB(a))
    .Zip(AsyncC(), (b, c) => new { b, c });
    SelectMany + Zip - 合成の例

    View full-size slide

  24. 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

    View full-size slide

  25. 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()));

    View full-size slide

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

    View full-size slide

  27. Unity用の各支援メソッド
    // 入れ物を用意して
    public class LogCallback
    {
    public string Condition;
    public string StackTrace;
    public UnityEngine.LogType LogType;
    }
    public static class LogHelper
    {
    public static IObservable LogCallbackAsObservable()
    {
    var subject = new Subject();
    // 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に変換するとかなりイケテル!

    View full-size slide

  28. センサー is IObservable
    IObservableは時間軸上に並ぶシーケンス
    詳しくはググッて私の適当な記事読んでください
    http://www.atmarkit.co.jp/fdotnet/introrx/index/
    Kinect, Leap Motionなどなど
    は、イベントシーケンスとみなせてRxと相性が非常にいい!
    というわけでUnityRxで扱ってみてください
    まだメソッド足りないかもなのでIssueに入れてもらうとうれすぃ

    View full-size slide

  29. まとめ
    IEnumeratorを非同期処理に使うのはいけてない
    そしてC# 5.0(async/await)は来ない!
    そこでUnityRx
    なぜTaskじゃなくてRxなのか?
    Taskはawaitがなければ機能的に弱くて使いやすくはない
    マルチスレッド処理やイベント処理など多数の機能も!
    Available Now(?)
    GitHub - https://github.com/neuecc/UnityRx/
    Assets/UnityRx下のコードが本体なのでそれを持って来れば動く
    AssetStore – 準備中(数日中に出すので待ってて!) 無料!

    View full-size slide