UniRx - Reactive Extensions for Unity

31
UniRx - Reactive Extensions for Unity 2014/04/19 Yoshifumi Kawai - @neuecc

description

Reactive Extensions for Unity https://github.com/neuecc/UniRx

Transcript of UniRx - Reactive Extensions for Unity

Page 1: UniRx - Reactive Extensions for Unity

UniRx - Reactive Extensions for Unity

2014/04/19Yoshifumi Kawai - @neuecc

Page 2: UniRx - Reactive Extensions for Unity

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

Page 3: UniRx - Reactive Extensions for Unity

UnityGame Engine but Not Only for Game Use

Page 4: UniRx - Reactive Extensions for Unity

Unityとは

押しも押されぬゲームエンジン

http://japan.unity3d.com/

最近は公式に3Dだけじゃなく2Dもサポートされた

クロスプラットフォーム(PC/iOS/Android/Windows Phone/

Windows Store/etc...)

国内でも事例多数(iOS版ドラクエ8などなど)

C#で書ける!※但しバージョン的には3.0相当

Page 5: UniRx - Reactive Extensions for Unity

ゲーム以外の用途にも

映像の出力先としてのUnity

VR(Oculus Rift)やNUI(Kinect, Leap Motion)などデバイスがあっ

ても、何に表示するの?何を表現するの?

そこの最有力候補としてのUnity

強力な開発環境、3D表現、AssetStore、そしてシェア

多くのデバイスがUnity用のSDKを用意している

メディアアート

ProcessingやopenFrameworksなどのフィールドへ (期待)

Page 6: UniRx - Reactive Extensions for Unity

Async in UnityCoroutine is not good practice for asynchronous operation

Page 7: UniRx - Reactive Extensions for Unity

Unityの非同期

通信処理はyield returnで待機できる

コールバック地獄にならない!

すばら!すばら!

と思ったことは一度もありません:)

IEnumerator OnMouseDown(){

var www = new WWW("http://bing.com/");yield return www; // これで待機

Debug.Log(www.text);}

Page 8: UniRx - Reactive Extensions for Unity

yieldの問題点

戻り値が返せない

例外処理ができない

IEnumerator GetGoogle(){

var www = new WWW("http://google.com/");yield return www;

// 戻り値を返せない!!! www.text is どこ

}

IEnumerator OnMouseDown(){

// このコードはコンパイルエラー// yieldはtry-catchで囲めないので例外処理できない!

try{

yield return StartCoroutine(GetGoogle());}catch{}

}

これにより処理が分離できないという問題が発生する。一つのIEnumeratorにダラダラと書

き連ねるしかなくなってしまう

Page 9: UniRx - Reactive Extensions for Unity

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

Page 10: UniRx - Reactive Extensions for Unity

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;

}}

C# 5.0(async/await)なら?

yield(await)が戻り値を返して受け取れる

例外がtry-catchできる

非同期メソッドが戻り値を返せる

Page 11: UniRx - Reactive Extensions for Unity

Unity 5.0

2014年秋ぐらいに多分リリース!

Monoのバージョンは新しくなりません!

つまりC#も古い(3.0)のままです!

asyncは来ない!知ってた!この先も期待できない!

The future of scripting in Unity

http://blogs.unity3d.com/2014/05/20/the-future-of-scripting-in-

unity/

今まで出遅れていたのを一気に取り戻そう作戦

Future、って感じでまだまだかなり時間かかりそうだ

Page 12: UniRx - Reactive Extensions for Unity

Reactive Programming

Page 13: UniRx - Reactive Extensions for Unity

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)

Page 14: UniRx - Reactive Extensions for Unity

Gartner’s Hype Cycle

2013 Application Architecture/Application Development

On the Rise - Reactive Programming

Page 15: UniRx - Reactive Extensions for Unity

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

Page 16: UniRx - Reactive Extensions for Unity

UniRx - Reactive Extensions for Unity

というのを作りました、本日公開!

ほとんど(8割ぐらい)は自前で書いた

公式のRxはヘヴィすぎてUnityの古いC#で動かしきれない

iOSはAOTの問題もあって回避頑張れる気がしない

ので自前で書くことに。

Available Now(?)

GitHub - https://github.com/neuecc/UniRx/

AssetStore(無料)

http://u3d.as/content/neuecc/uni-rx-reactive-extensions-for-

unity/7tT

Page 17: UniRx - Reactive Extensions for Unity

Curing Your Asynchronous Programming BluesRx saves your life & codes

Page 18: UniRx - Reactive Extensions for Unity

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

Page 19: UniRx - Reactive Extensions for Unity

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は、UniRxに同梱されている、Rxの形式にラップ済みのWWWクラスで通信可能なメソッドです

Page 20: UniRx - Reactive Extensions for Unity

Orchestrate MultiThreadingand LINQ to MonoBehaviour Message Events

Page 21: UniRx - Reactive Extensions for Unity

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

Page 22: UniRx - Reactive Extensions for Unity

AsyncA AsyncB

SelectMany -直列の結合

AsyncA

Zip -並列の結合

AsyncB

Result

Page 23: UniRx - Reactive Extensions for Unity

AsyncA AsyncB

AsyncC

Result

AsyncA().SelectMany(a => AsyncB(a)).Zip(AsyncC(), (b, c) => new { b, c });

SelectMany + Zip -合成の例

Page 24: UniRx - Reactive Extensions for Unity

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

Page 25: UniRx - Reactive Extensions for Unity

Extra Gems

Page 26: UniRx - Reactive Extensions for Unity

Unity用の各支援メソッド// Observable.EveryUpdate// 毎フレーム毎に値を発行する

Observable.EveryUpdate().Subscribe(_ =>

Debug.Log(DateTime.Now.ToString()));

// ObservableMonoBehaviourpublic class Hoge : ObservableMonoBehaviour{

public override void Awake(){

// 全てのMessageEventがRxで扱う形式で取得できる

var query = this.OnMouseDownAsObservable().SelectMany(_ => this.OnMouseDragAsObservable()).TakeUntil(this.OnMouseUpAsObservable());

}}

Page 27: UniRx - Reactive Extensions for Unity

Unity用の各支援メソッド// 入れ物を用意して

public class LogCallback{

public string Condition;public string StackTrace;public UnityEngine.LogType LogType;

}

public static class LogHelper{

static Subject<LogCallback> subject;

public static IObservable<LogCallback> LogCallbackAsObservable(){

if (subject == null){

subject = new Subject<LogCallback>();

// callback内でSubjectに発行してあげるように作ることでRxにコンバート可能

UnityEngine.Application.RegisterLogCallback((condition, stackTrace, type) =>{

subject.OnNext(new LogCallback { Condition = condition, StackTrace = stackTrace, LogType = type });});

}

return subject.AsObservable();}

}

Unityに多くあるデリゲートによるコールバックをIObservable<T>に変換するとかなりイケテル!

Page 28: UniRx - Reactive Extensions for Unity

Unity用の各支援メソッド// 入れ物を用意して

public class LogCallback{

public string Condition;public string StackTrace;public UnityEngine.LogType LogType;

}

public static class LogHelper{

static Subject<LogCallback> subject;

public static IObservable<LogCallback> LogCallbackAsObservable(){

if (subject == null){

subject = new Subject<LogCallback>();

// callback内でSubjectに発行してあげるように作ることでRxにコンバート可能

UnityEngine.Application.RegisterLogCallback((condition, stackTrace, type) =>{

subject.OnNext(new LogCallback { Condition = condition, StackTrace = stackTrace, LogType = type });});

}

return subject.AsObservable();}

}

Unityに多くあるデリゲートによるコールバックをIObservable<T>に変換するとかなりイケテル!

// 各処理が分離して記述可能になる、また合成処理が可能、などのメリットあり

LogHelper.LogCallbackAsObservable().Where(x => x.LogType == LogType.Warning).Subscribe();

LogHelper.LogCallbackAsObservable().Where(x => x.LogType == LogType.Error).Subscribe();

Page 29: UniRx - Reactive Extensions for Unity

センサー is IObservable<T>

IObservable<T>は時間軸上に並ぶシーケンス

詳しくはググッて私の適当な記事読んでください

http://www.atmarkit.co.jp/fdotnet/introrx/index/

Kinect, Leap Motionなどなど

は、イベントシーケンスとみなせてRxと相性が非常にいい!

というわけでUniRxで扱ってみてください

まだメソッド足りないかもなのでIssueに入れてもらうとうれすぃ

Page 30: UniRx - Reactive Extensions for Unity

Conclusion

Page 31: UniRx - Reactive Extensions for Unity

まとめ

IEnumeratorを非同期処理に使うのはいけてない

そしてC# 5.0(async/await)は来ない!

そこでUniRx

なぜTaskじゃなくてRxなのか?Taskはawaitがなければ機能的に弱くて使いやすくはない

マルチスレッド処理やイベント処理など多数の機能も!

Available Now(?)

GitHub - https://github.com/neuecc/UniRx/

AssetStore – http://u3d.as/content/neuecc/uni-rx-reactive-

extensions-for-unity/7tT