unirx - reactive extensions for unity
DESCRIPTION
Reactive Extensions for Unity https://github.com/neuecc/UniRxTRANSCRIPT
UniRx - Reactive Extensions for Unity
2014/04/19Yoshifumi Kawai - @neuecc
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/とか作ってます
UnityGame Engine but Not Only for Game Use
Unityとは
押しも押されぬゲームエンジン
http://japan.unity3d.com/
最近は公式に3Dだけじゃなく2Dもサポートされた
クロスプラットフォーム(PC/iOS/Android/Windows Phone/
Windows Store/etc...)
国内でも事例多数(iOS版ドラクエ8などなど)
C#で書ける!※但しバージョン的には3.0相当
ゲーム以外の用途にも
映像の出力先としてのUnity
VR(Oculus Rift)やNUI(Kinect, Leap Motion)などデバイスがあっ
ても、何に表示するの?何を表現するの?
そこの最有力候補としてのUnity
強力な開発環境、3D表現、AssetStore、そしてシェア
多くのデバイスがUnity用のSDKを用意している
メディアアート
ProcessingやopenFrameworksなどのフィールドへ (期待)
Async in UnityCoroutine is not good practice for asynchronous operation
Unityの非同期
通信処理はyield returnで待機できる
コールバック地獄にならない!
すばら!すばら!
と思ったことは一度もありません:)
IEnumerator OnMouseDown(){
var www = new WWW("http://bing.com/");yield return www; // これで待機
Debug.Log(www.text);}
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にダラダラと書
き連ねるしかなくなってしまう
あるいはコールバック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で完了待機できるので多少マシだけどとはいえ酷い)
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できる
非同期メソッドが戻り値を返せる
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、って感じでまだまだかなり時間かかりそうだ
Reactive Programming
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)
Gartner’s Hype Cycle
2013 Application Architecture/Application Development
On the Rise - Reactive Programming
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
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
Curing Your Asynchronous Programming BluesRx saves your life & codes
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());
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クラスで通信可能なメソッドです
Orchestrate MultiThreadingand LINQ to MonoBehaviour Message Events
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するだけでキャンセル可能、などなど、多種のメソッドがマルチスレッド処理を支援する
AsyncA AsyncB
SelectMany -直列の結合
AsyncA
Zip -並列の結合
AsyncB
Result
AsyncA AsyncB
AsyncC
Result
AsyncA().SelectMany(a => AsyncB(a)).Zip(AsyncC(), (b, c) => new { b, c });
SelectMany + Zip -合成の例
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
Extra Gems
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());
}}
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>に変換するとかなりイケテル!
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();
センサー is IObservable<T>
IObservable<T>は時間軸上に並ぶシーケンス
詳しくはググッて私の適当な記事読んでください
http://www.atmarkit.co.jp/fdotnet/introrx/index/
Kinect, Leap Motionなどなど
は、イベントシーケンスとみなせてRxと相性が非常にいい!
というわけでUniRxで扱ってみてください
まだメソッド足りないかもなのでIssueに入れてもらうとうれすぃ
Conclusion
まとめ
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