A Beginners Guide to Reactive Extensions with UniRx
どうも始めましての人は始めまして、@neueccといいます。この記事はUnity アセット真夏のアドベントカレンダー 2014 Summer!というイベントの23日目です。クリスマスのアレ!真夏に……!しかしクリスマスのアレは比較的脱落も少なくのないのですが、これは見事ーに続いてます。しかも日付が変わった瞬間に公開されることの多いこと多いこと。〆切というのは23:59:59だと思っている私には辛い話です……。さて、前日はnaichiさんの【うに部屋】Unityのゲーム投稿サイトにアセット検索機能を付けてみたでした。便利でいいですねー、UniRxも使ったアセットとして沢山並ぶ日が来ると、いいなぁ。
Reactive Programming
とは。と、ここで7/30に行われた【第1回】UnityアセットまみれのLT大会で使ったスライドが!
LTということで制限時間5分だったんですが当然終わるわけなくて凄まじく早口でまくしたてて強引に終わらせたせいで、全くワカラン!という感想を頂きましたありがとうございますごめんなさい。簡単にかいつまみますと、
Reactive Programmingはガートナーのハイプサイクル(記事では2013ですがこないだ出た2014年版のApplication DevelopmentでもOn the Riseに入っています)やThought Works Technology Raderといった有名な技術指標にもラインナップされるほど、注目を浴びている技術です。Scala周辺からもThe Reactive Manifestoといった文章が出ていますし、JavaでReactive Programmingを実現するRxJavaはGitHubのStarが2995、Objective-C用のReactiveCocoaは5341、JavaScriptでもRxJSが1792、bacon.jsが2864と、知名度・注目度、使われている度は非常に大きくなっています。
Reactive Programming自体は別に近年始まったわけでもなく、昔からたまに盛り上がっては消え、って感じなので、「へぇ~この技術2年前くらいに流行ってたよね 2年前くらい前に見たわ」って思う人もいるかもですが、大事なのは、ちゃんと実用に乗った、ということです。実験的なライブラリの段階はとうに超えて、RxJavaやReactiveCocoaの知名度が示す通り、完全に実用レベルに乗りました。
UniRxはReactive Extensions(Rx)というMicrosoftの開発した.NET用のライブラリ(現在はOSS化)を私がUnity用に移植したものです。現在のReactive(Rx)Hogeの源流は、この.NETのRxにあります。ほとんどのライブラリから言及され、原理原則や用語はRx.NETに従っていることが多い。というわけでRx.NETは革命的に素晴らしいわけなのです、が、しかし、Unityでは動きません。それはRx.NETが本来のC#の機能を全面的に使いすぎてUnityのC#では動かせないから……。少なくともiOSのAOT問題を全く突破できない……。
が、どうしてもUnityで使いたいので移植(といってもソースコードレベルではほとんど自前で書いてるのでインターフェイスと挙動を合わせているという感じで割と書き下ろしです、一部は純粋に移植してますが)+UnityはUnityで.NETとは異なるところもあるので、Unityで使って自然になるような改良を施したのがUniRxになります。
- GitHub - neuecc/UniRx
- AssetStore - UniRx - Reactive Extensions for Unity (無料)
ムリョーですよ、ムリョー。FREE!。私はLINQやRxの大ファンなんで、とにかく使ってもらいたい欲求のほうが強くて。そして勿論、自分で使えない状態にも耐えられなくて!気になったらGitHubでStarつけてください(Star乞食)、勿論AssetStoreのほうでもいいですよ:)
UniRxは非同期やイベント処理をReactive Programmingの概念を元に大きく簡単にします。Unityにも非同期ライブラリ、イベントライブラリは沢山あります。それらと比べたUniRxの強みは「Rxであるということ」です。Reactive Programmingは現状かなりブームになっているとおりに、その手法の正しさ、威力に関しては実証済みです。また、手法やメソッド名などが同一であるということは、既に普及しているRx系のライブラリのドキュメントがまんま使えます。そしてUniRxで学んだやり方は他のプラットフォームに移っても同じように使えるでしょう。ネイティブAndroid(RxJava)でもネイティブiOS(ReactiveCocoa)でも.NET(Rx.NET)でもJavaScript(RxJS)でも、そういった先々への応用性もまた、選ぶべき理由になると思います。
Introduction
前置きが長い!さて、この記事を出すちょっと前に【翻訳】あなたが求めていたリアクティブプログラミング入門という素晴らしい記事が翻訳されました!はてブでも500以上集まってましたし、実際めっちゃ良い記事です。この記事はRxJSを用いて具体的な説明を行っていますが、勿論UniRxでも同様のことができます(というわけで、結論んとしては↑の記事読んでもらえればいいんでさー、とか投げてみたい)。ちょっとやってみましょう。最初の例はダブルクリックの検出です。
using System; using UniRx; using UnityEngine; public class Intro : MonoBehaviour { void Start() { // 左クリックのストリーム var clickStream = Observable.EveryUpdate() .Where(_ => Input.GetMouseButtonDown(0)); // Buffer:250ミリ秒以内に連続してクリックされたものをまとめる clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250))) .Select(xs => xs.Count) // 250ミリ秒以内検出したクリック数 .Where(x => x >= 2) // 2個以上のみにフィルタ .Subscribe(_ => Debug.Log("ダブルクリックされた!")); // foreachみたいな } }
このスクリプト(Intro.cs)をMainCameraでもなんでも適当な何かに貼り付けてもらえれば、画面のどこでもダブルクリックされればログに流れます。
この例は6行しかない単純なものですが、多くの要素が詰め込まれています!そして、実際、詰め込まれすぎていてかなり難しい!わからん!というわけで真面目に分解していきます。
(ところで実は↑のコードは今AssetStoreに公開されてるバージョンでは動きません!何故かというと、これに対応させたバージョン(ver.4.5)が現在まだ審査中だからです……!まぁ大丈夫だろうと思ったんですが間に合わなかった、無念。GitHubのバージョンでは使えるので、実際試す場合は今のところはGitHubでDownload Zipしてきてください……。はやく審査終わって欲すぃ)
Rx is LINQ
Rxについてまとめると「時間軸に乗るストリームに対するLINQ」です。時魔法です。先の記事でも「FRPは非同期データストリームを用いるプログラミングである」といってました。(ドヤァするわけですが、私は遥か昔、2011年の時点で同じこと言ってましたからね!)。このことさえピンと来れば割としっくり来るんですが、同時にこれがしっくり来るというところまでが敷居となる。ところが、RxはこのことをLINQとして表現することによりグッと敷居を下げました。なんだ、LINQと一緒じゃん!って。え、LINQがワカラナイ?それは、普通にC#の必須技術なので是非学んでください!(色々種類ありますがLINQ to Objectsだけでいいです。以前に@ITでLINQの仕組み&遅延評価の正しい基礎知識を書いたりAn Internal of LINQ to Objectsというスライドで発表したりしてるんで読んでください)
簡単に同じように見れることを説明すると、LINQ to Objectsでは
new[] { 1, 2, 3, 4, 5 } .Where(x => x % 2 == 0) .Select(x => x * x);
のように、配列をフィルタリングして別の形に射影できます。
配列、int[]の横軸は当然ながら長さです。Rxは時間を横軸に取ることができます。どういうことか、というと、例えば何かをタップするというイベントは図にしたらこういう表現ができます。
ということは、同様にWhereしたりSelectしたりできる。
少しピンと来ました?さて、配列はnew[]{}やGetComponentsなどで手に入れることができますが、Rxで扱うためのイベントストリーム(IObservable<T>、ちなみにLINQ to Objectsは配列をIEnumerable<T>として扱う)はどこに転がっているのか。UniRxにおいて一番お手軽なのはObservable.EveryUpdateです。ゲームループは、ループというぐらいに1フレーム毎にUpdateが毎回呼ばれるサイクルなわけですが(参考:Script Lifecycle Flowchart)、つまり60fpsなら1/60秒毎に発生する時間軸にのったイベントとみなせられます。一度、時間軸上に乗るイベントとみなせられれば、それは全て無条件にRxで取り扱えます。
実際のところ、イベントに限らずあらゆるものがRxに見せかけることができます。非同期だってx秒後に一度だけ発生するイベントと考えれば?配列は0秒で沢山の値が発行されるイベントと考えれば?Unityのコルーチン(IEnumerator)だって乗せられる。
全てのものをRx化(IObservable化)すれば、あとは好きなようにLINQのメソッドで合成してしまえる。あらゆる素材(イベント・非同期・配列・コルーチン)を鍋(IObservable)に突っ込んで、料理(Where, Select, etc…)して食べる(Subscribe)。というのがRxの基本の基です。料理方法は色々あるので、そこが次のキモですね。
Composable
というわけでclickStreamがマウスの左クリックのストリームになったということは分かったでしょうか!?
var clickStream = Observable.EveryUpdate() .Where(_ => Input.GetMouseButtonDown(0));
左クリックがあったフレームだけにフィルタリングしているということですねー。では次は?
clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))
Rxの利点として、イベントが変数として扱えることです。どういうことか、戻り値にすることもできるし、フィールドやプロパティとして公開することもできるし、自分自身と結合することもできる。というわけで、clickStreamが二個出てるのは、自分との合成なんですね(わっかりづらい!)
そこまではいいとしてThrottleが分かりづらい!Throttleは一定時間毎(この場合は250ミリ秒)に値が観測できなかったら値を流す、という挙動です。
スロットル、流れてくる値を絞り込んでいるんですね。Rxは時間軸上に乗っているので、こうした時間関係を扱うメソッドが豊富です。Sample(一定時間毎のもののみを流す)、Delay(一定時間後に流す)、Timeout(一定時間たっても値がなければエラーにする)、Buffer(一定時間の間値を溜めてから流す)などなど。一見分かりづらいですが、そもそも普通にも書きづらい、そういったものがメソッド一発で書けるというのもRxの魅力です。
じゃあどういうことかというと、もうまだるっこしいので全部のせますが
ThrottleしたものをBufferしているのは、値が流れてくるまで値を溜める(そして配列にして後続に流す)、ということです。あとはSelectで配列の個数、つまり250ミリ秒の間にクリックされた回数に変形して、Whereでフィルタリング(ダブルクリック、つまり2個以上ならばOK)。なるほどねー?
ちなみにコレは(250ミリ秒以内に)クリックされ続けてるとずっと実行されません。最後にクリックされてから250ミリ秒後に、その間に2回以上クリックされてると実行される。が正しい表現でしょうか。この動作が望ましい場合もあれば望ましくない場合もある、ダブルクリックと一口でいっても定義は案外複雑なので、その辺はチューンしてみてください。その辺もclickStream.Timestamp()や.TimeInterval()でその時刻や前との差分なんかが簡単に取れます。
非同期について
時間切れなのであとで書く(ひどい!)、かも。
var parallel = Observable.WhenAll( ObservableWWW.Get("http://google.com/"), ObservableWWW.Get("http://bing.com/"), ObservableWWW.Get("http://unity3d.com/")); parallel.Subscribe(xs => { Debug.Log(xs[0]); // google Debug.Log(xs[1]); // bing Debug.Log(xs[2]); // unity });
とりあえずこんな風に並行処理できます。
コルーチンについて
時間切れなのでいつか書く(ひどい!)、かも。
// two coroutines IEnumerator AsyncA() { Debug.Log("a start"); yield return new WaitForSeconds(1); Debug.Log("a end"); } IEnumerator AsyncB() { Debug.Log("b start"); yield return new WaitForEndOfFrame(); Debug.Log("b end"); } // main code // Observable.FromCoroutine convert IEnumerator to Observable<Unit>. // other shorthand, AsyncA().ToObservable() // after completed AsyncA, run AsyncB as continuous routine. // UniRx expands SelectMany(IEnumerator) as SelectMany(IEnumerator.ToObservable()) var cancel = Observable.FromCoroutine(AsyncA) .SelectMany(_ => AsyncB()) .Subscribe(); // you can stop coroutine use subscription's Dispose. cancel.Dispose();
うーん、手抜きがすごくなってきた。
uGUI
iOS用のReactiveCocoaが非常に受け入れられているように、リアクティブプログラミングはGUIとの相性が非常に良いです。そこでUniRx ver.4.5では既にUnityの新GUIシステムに対応!.AsObservable()と書くだけで変換できます。例えば
GetComponentInChildren<Button>().AsObservable() .Subscribe(x => Debug.Log("クリックされた!"));
のように書けます。詳しい話はいつか!
学習リソース
UniRxの特徴として、Reactive Extensions系の学習リソースを流用できることが上げられますが、実際RxJavaのWikiは非常にお薦めです。沢山あるメソッドの説明が図入りで説明されていて非常に分かりやすい、例えばFiltering Observablesとか。
また、Introduction to Rxは非常に充実したチュートリアルを提供し、RxMarbles: Interactive diagrams of Rx Observables ではインタラクティブにメソッドの挙動を確認できます。
こうしたリソースにひたすらタダ乗り出来るのが強い!
更新履歴。
さて、いまさらですが、UniRxの最初の発表日は2014/04/19に開催されたすまべん特別編「Xamarin 2.0であそぼう!」@関東での発表でした。
この時にGitHubにリポジトリを公開して、アセットストアに審査出し。そこから審査に3回ほどこけて、一月後に2014年05月28日に公開されました、わーぱちぱち。ってしかしブログに解説書く書く詐欺で解説を書かないでいました、ほげえ。だからこの記事が最初の解説記事なんですねー、えー……。なんとなく書かないでいたのは、次のバージョンではもっと良くなってるから!を延々と繰り返してたから説。特に大きく変わったのがver.4.3(ちなみにUniRxのバージョンが4始まりなのは、審査にこける度に間違ってメジャーバージョンを上げちゃってたからです、気付いた時には戻せず……)
ver 4.3 - 2014/7/2 Fix iOS AOT Safe totally MainThreadSchedule's schedule(dueTime) acquired time accuracy MainThreadDispatcher avoid deadlock at recursive call Add Observable.Buffer(count, skip) Change OfType, Cast definition Change IScheduler definition Add AotSafe Utilities(AsSafeEnumerable, WrapValueToClass) Change Unit, TimeInterval and Timestamped to class(for iOS AOT) Add Examples/Sample7_OrchestratIEnumerator.cs
Unity + iOSのAOTでの例外の発生パターンと対処法という記事を書いて、そこそこ反響あったのですが、その成果を突っ込んでます。というわけで、このバージョンでiOSのAOT問題を大きく解決しました。そしてver4.4。
ver 4.4 - 2014/7/30 Add : Observable.FromEvent Add : Observable.Merge Overload(params IObservable[TSource][] / IEnumerable[IObserable[TSource]]) Add : Observable.Buffer Overload(timeSpan, timeShift) Add : IDisposable.AddTo Add : ObservableLogger(UniRx.Diagnostics) Add : Observable.StartAsCoroutine Add : MainThreadDispatcher.RegisterUnhandledExceptionCallback Add : Examples/Sample08, Sample09, Sample10, Sample11 Performance Improvment : Subject[T], OnNext avoids copy and lock Performance Improvment : MainThreadDispatcher, avoids copy on every update Change : Observable.ToCoroutine -> ToAwaitableEnumerator Fix : ObservableMonoBehaviour's OnTriggerStay2D doesn't pass Collider2D
機能的にはObservableLoggerを入れたのとIDisposable.AddToでリソース管理のガイドを示したのが大きいんですが、一番大きいのはパフォーマンス改善かなあ、と。ガチで使うと大量に出てくるSubject[T]や、絶対経由することになるMainThreadScheduler/Disptacherの性能を限界まで向上させたので、あまりネックになることはないのではかな、と。で、公開まだなver4.5。
ver 4.5 - 2014/8/19(アセットストアへは審査中) Add : ObservableWWW Overload(byte[] postData) Add : Observable.Buffer Overload(windowBoundaries) Add : LazyTask - yieldable value container like Task Add : Observable.StartWith Add : Observable.Distinct Add : Observable.DelaySubscription Add : UnityEvent.AsObservable - only for Unity 4.6 uGUI Add : UniRx.UI.ObserveEveryValueChanged(Extension Method) Add : RefCountDisposable Add : Scheduler.MainThreadIgnoreTimeScale - difference with MainThreadScheduler, not follow Unity Timescale Add : Scheduler.DefaultSchedulers - can configure default scheduler for any operation Fix : DistinctUntilChanged iOS AOT issue. Fix : Remove IObservable/IObserver/ISubject's covariance/contravariance(Unity is not support) Fix : UnityDebugSink throws exception when called from other thread Fix : Remove compiler error for Windows Phone 8/Windows Store App Breaking Change : MainThreadSchduler follow Unity Timescale Breaking Change : All Timebased operator's default scheduler changed to MainThreadScheduler Breaking Change : Remove TypedMonoBehaviour.OnGUI for performance improvment Performance Improvment : AsyncSubject[T] Performance Improvment : CurrentThreadScheduler Performance Improvment : MainThreadScheduler
本当はこの記事と同時にアセットストアでも公開!と行きたかったんですが審査がまだー……。uGUI対応のイベントハンドリングを足したり、UIで使うの見越したUniRx.UI.ObserveEveryValueChangedの追加とか、uGUIへの対応を見越した基礎部分を足してっていってる感じですね。こうしたUIでの利用法はuGUIのノウハウと共に貯めていきたい/公開していきたいと思っています。
あと凄く大きいのが時間ベースのメソッドで使われるデフォルトのスケジューラをScheduler.MainThreadに変えたことで、ふつーに使う分には全てがUnityのTimescaleの影響下にあるシングルスレッドで動く状態になるので、違和感というかハマりどころ(ObserverOnMainThreadしなかったから死んだ!オマジナイにObserveOnMainThreadって書きまくったせいで性能が!)を消せたのかなー、と思います。ここは本家Rxとは当然デフォルトが違うことになりますが、Unityネイティブに寄せるべきだろう、という判断です。
あと地味にWindows Phone 8やWindows Store Appにも対応しました。いや、最初のバージョンでは対応してたんですが機能足してるうちに、どうやらコンパイル通らなくなってしまっていて……。Platform切り替えないと気づけないのが辛いですねえ、Unity Cloud BuildのWindows Phone対応はよ!いちおう「We’ll be adding more platforms as the service matures.」ってあるので、適当に待ちましょう。そもそもBetaの現状は重すぎてそういう次元ですらないですしね。
次回更新では、現状ExecuteInEditModeでは動かないので、それに対応したものを出す予定です。
最後に
と、いうわけでどうでしょう?使ってみたくなってもらえれば幸いです。怒涛の更新のとおりにやる気はかなりあります、というか私はグラニという会社のCTOをしているんですが(CM放送などをした「神獄のヴァルハラゲート」が代表作です)、開発中の次のプロダクトに投下していて、実プロダクトで使う気満々というかドッグフーディングというか、ともあれ現状「枯れてない」というのは否定出来ないのですが、基本的なバグは既に概ね殺せているのではかなぁ、と、そこは信頼してくれると嬉しいですね。今回はUniRxの初めての記事だったので基礎の基礎的な話になりましたが(そうか?)、今後は応用的な記事などもどんどん出していきます。
Reactive Extensions自体は、私は2011年には@ITに連載:Reactive Extensions(Rx)入門という記事を書いていたり、そもそも2009年に最初のベータ版が出た時から追っかけて記事を書き続けていたりとneue.cc/category/programming/rx、5年間延々とRxを触っているので、さすがにかなり詳しいのではないかと自負するところです(ちなみに最初の記事は.NET Reactive Framework メソッド探訪第一回:FromEventでした。そう、当初はReactive Frameworkって名前だったんですね、更にもっと源流はMicrosoft Live Labs Voltaになります)。
繰り返しますが日本ではReactive Programmingのブームは定期的に起こっては消え(最初のほうでバズったのは2010年のやさしいFunctional reactive programming(概要編)でしょうか)、って感じですが、Microsoftは理論やプロトタイプに留まらず延々と改良を続け、完全に実用ベースに載せ、本物のブームを作り上げたことには本当に感嘆します。勿論、ブーム自体はMicrosoftよりは、そこから波及していったRxJavaやReactiveCocoaの貢献が非常に大きいです。私もUniRxで、Reactive Programingの強力さをUnityでも示し、大きなブームが巻き起こせればなあ、なんて野望は抱いていますね!
UniRxへの質問があれば、GitHubのIssuesやUnityのForumに立ててあるスレッドでもぜひぜひですが、そこでは英語でお願いしたいのでちょっと敷居がー、ということであれば、普通にTwitterの@neuecc宛てに気楽に言ってください。またはTwitterで「UniRx」で常時検索してますんで独り言みたいな感じでポストしてもらえればチェックします。
あと会社単位でも、会社間交流ということで共催の社内勉強会などできれば嬉しいかなー、と思ってますので、是非グラニと勉強会やりたいという方いらっしゃいましたらお声がけください。今までもKLabさん - 2014年3月度ALMレポート(株式会社グラニ様との合同開催)やドリコムさん等と行ってきています。グラニのUnity以外の技術、というか現状はそちらがメインなのですが、というのはgihyo.jpのグラニがC#にこだわる理由という記事を見て頂ければなのですが、サーバーサイドをPHPやPython、RubyじゃなくてC#(Windows Server + ASP.NET)でやる、というのが強みです。勿論、今後クライアントサイドもC#(Unity)でやっていきます。
さて、明日は野生の男さんの「無料アセットで簡単IBL!」です。楽しみ楽しみー、ではでは!
Comment (0)
Trackback(1) | http://neue.cc/2014/08/23_476.html/trackback
- neue cc – A Beginners Guide to Reactive Extensions with UniRx | 簡単料理レシピ : (08/24 02:13)
[…] neue cc – A Beginners Guide to Reactive Extensions with UniRx 2014年8月24日 12:01 AM / recipe この記事の所要時間: 約 0分33秒 neue cc – A Beginners Guide to Reactive Extensions with UniRx […]