SlideShare a Scribd company logo
Observable Everywhere
Rxの原則とUniRxにみるデータソースの見つけ方
2015/04/16
Yoshifumi Kawai - @neuecc
@neuecc - Who, Where, When
2009/04
linq.js – LINQ to Objects for JavaScript
https://linqjs.codeplex.com/
この頃からずっとLINQを追いかけてる
2009/09/04
最初のRx記事、以降現在まで70以上は書いてる
http://neue.cc/category/programming/rx/
2011/04~
Microsoft MVP for .NET(C#)
野良C#エヴァンジェリスト(現在も継続中)
@Work, @LINQ
2012/10~
株式会社グラニ 取締役CTO
「神獄のヴァルハラゲート」「モンスターハンターロアオブカード」
サーバー/クライアント共に「最先端 C# 技術を使った」ゲーム開発
C# 5.0 + .NET Framework 4.5 + ASP.NET MVC 5 + Unity
2014/04/19
UniRx最初の公開 - https://github.com/neuecc/UniRx
現在☆286, ver 4.8.0
2014/09/24
LINQ to BigQuery - https://github.com/neuecc/LINQ-to-BigQuery/
未だにLINQへの関心は衰えずといったところ
Agenda
せつめい
基本的には言語非依存の説明です
RxJavaの人もReactiveCocoaの人もWelcome
サンプルコードはC#になりますので適宜補完してください
Rxの基本原則などを紹介
衒学趣味を蹴っ飛ばして実用的な意味合いを探る
UniRxを例にRx利用時のデータソースの実例を紹介
自分の言語、自分のプラットフォームでも応用効くはず
OnNext* (OnError | OnCompleted)?
The grammar of messages
OnNext* (OnError | OnCompleted)?
日本語でおk
OnNextがゼロ回以上の後に
OnErrorもしくはOnCompletedが一回来る(もしくはどちらも来ない)
public interface IObserver<in T>
{
void OnCompleted();
void OnError(Exception error);
void OnNext(T value);
}
foreachで考えてみる; OnNext
foreach (var x in sequence)
{
OnNext(x);
}
OnNext*
空の場合はOnNextは一度
も呼ばれないのでOnNext*
foreachで考えてみる; OnCompleted
foreach (var x in sequence)
{
OnNext(x);
}
OnCompleted();
OnNext* (OnCompleted)?
もしsequneceが無限シーケンス
(例えばunfold)だったら
OnCompletedまで行かないので
OnNext * (OnCompleted)?
foreachで考えてみる; OnError
try
{
foreach (var x in sequence)
{
OnNext(x);
}
}
catch (Exception ex)
{
OnError(ex);
// RxでonErrorをハンドルした場合
// 再スローはない、これはあくまで再現
throw;
}
OnCompleted();
OnNext* (OnError | OnCompleted)?
もし途中で例外が起きたら
OnCompletedは呼ばれずOnErrorだ
けが呼ばれることになるので
OnNext * (OnError | OnCompleted)?
意味が通る!でも、なんで?
IEnumeratorから考える
public interface IEnumerator<out T>
{
T Current { get; } // 現在の要素
bool MoveNext(); // 次の要素があるか確認しCurrentにセット
}
分解
public interface IEnumerator<out T>
{
T Current { get; }
bool MoveNext() throws Exception; // (C#にthrowsはないけどネ)
}
public interface IEnumerator<out T>
{
T Current { get; }
true|false|Exception MoveNext();
}
MoveNextを分解すると
true/false/Exceptionのどれかを返す
集約
public interface IEnumerator<out T>
{
T Current { get; }
bool MoveNext() throws Exception; // (C#にthrowsはないけどネ)
}
public interface IEnumerator<out T>
{
T Current { get; }
T|void|Exception GetNext();
}
trueの時Tを、falseの時voidを返すと考
えてCurrentも集約
そして(何故か)反転させる
public interface IEnumeratorDual<in T>
{
void GotNext(T|void|Exception);
}
public interface IEnumerator<out T>
{
T|void|Exception GetNext();
}
3つにちゃんとバラして
public interface IEnumeratorDual<in T>
{
void GotNext(T);
void GotNothing(void);
void GotException(Exception);
}
public interface IEnumeratorDual<in T>
{
void GotNext(T|void|Exception);
}
あら不思議、IObserver<T>が!
public interface IEnumeratorDual<in T>
{
void GotNext(T);
void GotNothing(void);
void GotException(Exception);
}
名前を変えれば出来上がり
public interface IObserver<in T>
{
void OnNext(T value);
void OnCompleted();
void OnError(Exception error);
}
IEnumerableのほうも勿論
public interface IEnumerable<out T>
{
IEnumerator<T> GetEnumerator();
}
IEnumerator<T> : IDisposable
public interface IEnumerable<out T>
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerable<out T>
{
IDisposable|IEnumerator<T> GetEnumerator();
}
IEnumerator<T> : IDisposable
public interface IEnumerableDual<out T>
{
IDisposable SetEnumeratorDual(IEnumeratorDual<T>);
}
public interface IEnumerable<out T>
{
IDisposable|IEnumerator<T> GetEnumerator();
}
当然のように(何故か)逆にする
public interface IEnumerableDual<out T>
{
IDisposable SetEnumeratorDual(IEnumeratorDual<T>);
}
あら不思議、IObservable<T>が!
public interface IObservable<out T>
{
IDisposable Subscribe(IObserver<T> observer);
}
名前を整えて出来上がり
Mathematical Dual
双対
なるほどわからん
ふかーい関連性がある、ぐらいでいいんじゃまいか
実用的な意味では、OnNext* (OnError | OnCompleted)?だけ覚えればいい
同じように見なすことができる、という発見
IEnumerable<T> = イテレータパターン
IObservable<T> = オブザーバーパターン
一見無関係の2つのデザインパターンに見つけた深い関連性
イテレータのためのLINQがオブザーバーにも同じように適用できる発見
Reactive Extensions= LINQ to Events / LINQ to Asynchronous
Pull(Interactive) vs Push(Reactive)
DataSource
(IEnumerable<T>)
Action
値の要求(Pull)
DataSource
(IObservable<T>)
Action
値の送信(Push)
Length or Time
IEnumerable<T> length
IObservable<T> time
event
async
IE<T>
イベント(や非同期)を時間軸に乗った
シーケンスと見なすことができる
Same Atmosphere
var query = from person in sequence
where person.Age >= 20
select person.Name;
foreach (var item in query)
{
OnNext(item);
}
var query = from person in sequence
where person.Age >= 20
select person.Name;
query.Subscribe(item =>
{
OnNext(item);
});
IEnumerble<T> IObservable<T>
コレクション処理(filter/map)が超強力で有益なのは既知の話。それと同じように
書ける、同じような挙動を取るということが注意深くデザインされている。
これがRxの革命的なポイントであり普及の一助となった
Asynchronous Futures
Synchronous Asynchronous
Single(1)Multiple(*)
var x = f(); var x = await f();
var query = from person in sequence
where person.Age >= 20
select person.Name;
foreach (var item in query)
{
OnNext(item);
}
var query = from person in sequence
where person.Age >= 20
select person.Name;
query.Subscribe(item =>
{
OnNext(item);
});
IEnumerble<T> IObservable<T>
Func<T> Task<T>
Synchronous Asynchronous
Single(1)Multiple(*)
var x = f(); var x = await f();
var query = from person in sequence
where person.Age >= 20
select person.Name;
foreach (var item in query)
{
OnNext(item);
}
var query = from person in sequence
where person.Age >= 20
select person.Name;
query.Subscribe(item =>
{
OnNext(item);
});
IEnumerble<T> IObservable<T>
Func<T> Task<T>
FutureとかPromise
とか言われるアレの
C#での表現
C# 5.0での非同期事情
C# 5.0では単発の非同期処理は全てTask<T>を返す
async/awaitという言語構文でのサポートが圧倒的に大きい
Task<T>にawaitするとTが取れるし、.NET Frameworkで全面的に非同期APIが追加された
Task is Observable, Observable is Awaitable
相互変換可能(Taskは長さ1のIObservable、IObservableはLastを取得してTaskに変
換できる)なので、適宜必要な局面で変換
Before async/await
専用構文がないTaskだけとの比較だとRxのほうが圧倒的に使いやすかった
というわけで、他言語においても事情は同じになるかな、と
専用構文が入ればそちらに寄って、それまではRxが使う上では強いはず
What is UniRx?
Welcome to the Reactive Game Architecture!
https://github.com/neuecc/UniRx Unity用のReactive Extensions実装
(.NET用のRxはUnityで動かないので自前で実装)
Official(?) ReactiveX Family
http://reactivex.io/
ReactiveX公式サイトのLanguagesにラインナップ
されています(現状、非公式Rxでは唯一)
CoreLibrary
Framework Adapter
Rx.NET
RxJS
RxJava
RxAndroid
ReactiveUI
ReactiveProperty
CoreLibrary
Framework Adapter
UniRxは全部入り
(多分ReactiveCocoaとか
もそうでしょうけど)
Port of Rx.NET
MainThreadSchedulerObservableTriggers
ReactiveProperty
Observable Everywhere
データソースを見つけよう
何はともあれObservableの源流が必要
使い方は後からついてくる
通常のLINQ(to Objects)も何がシーケンスとみなせるか(DOMの
Traverseはシーケンスになる、DatabaseのRowはシーケンスとみな
せる、などなど)がまず最初にある
シーケンスを作ろう
シーケンスのように見なせるものがあれば、そこにLINQはある
見なせそうで見なせない、ありそうでないものがあれば、作りだ
そう、そうすればそこにLINQ(to Objects/to Events = Rx)が現れる
Push Event Stream
Event Processing
Interactive/Visualize
Statndard Static Generators
Range, Repeat, ToObservable
OnNext{n} (OnError | OnCompleted)?
Return
OnNext (OnError | OnCompleted)?
Empty
OnCompleted
Never
()
Throw
OnError
単体ではそう使うこともな
い感じですが、何かと組み
合わせていく感じ
.Concat(Observable.Never)で、終
了を抑止する(OnCompletedは飛
ばさない、あるいは他のオペレー
タが飛ばす)、的な使い方ができる
AsyncCallback Converter
IEnumerator GetStringAsync(string url, Action<string> onCompleted, Action<Exception> onError);
Callback Hell!
謎のIEnumerator(Unity固有事情)
IObservable<string> Get(string url);
Callback to IObservable<T>
UniRxは組み込みの通信メソッドとして
ObservableWWW.Get/Postメソッドなどを予め用意して
あるので、ユーザーはすぐに非同期をRxで扱える
But...
あれ、一緒……?
ObservableWWW.Get("http://hogemoge").Subscribe(Action<string>, Action<Exception>)
StartCoroutine(GetStringAsync("http://hogemoge", Action<string>, Action<Exception>))
GetStringAsync("http://hogemoge", nextUrl =>
{
GetStringsAsync(nextUrl, finalUrl =>
{
GetStringsAsync(finalUrl, result =>
{
/* result handling */
}, ex => { /* third error handling */ });
}, ex => { /* second error handling */ });
}, ex => { /* first error handling */ });
Callback Hell ネスト地獄による圧倒的
な見通しの悪さ
エラーハンドリングの困難(try-
catchで囲んでも意味が無い)
ObservableWWW.Get("http://hogemoge")
.SelectMany(nextUrl => ObservableWWW.Get(nextUrl))
.SelectMany(finalUrl => ObservableWWW.Get(finalUrl))
.Subscribe(
result => { /*result handling */ },
ex => { /* error handling */ });
ネストを一段に抑える
パイプライン統合のエラー処理
Composability
var query = from nextUrl in ObservableWWW.Get("http://hogemoge")
from finalUrl in ObservableWWW.Get(nextUrl)
from result in ObservableWWW.Get(finalUrl)
select new { nextUrl, finalUrl, result };
var parallelQuery = Observable.WhenAll(
ObservableWWW.Get("http//hogehoge"),
ObservableWWW.Get("http//hugahuga"),
ObservableWWW.Get("http//takotako"));
var parallelQuery = Observable.WhenAll(
ObservableWWW.Get("http//hogehoge"),
ObservableWWW.Get("http//hugahuga"),
ObservableWWW.Get("http//takotako"))
.Timeout(TimeSpan.FromSeconds(15));
var incrementalSearch = userInput
.Throttle(TimeSpan.FromMilliseconds(250))
.Select(keyword => SearchAsync(keyword))
.Switch();
前の結果を保持したまま
の直列処理
並べるだけで済む並列処理
メソッドを足すだけのTimeout付与
ユーザー入力など他のIObservableと
の自然な融和と、複雑なPruning
UIEvent Converter
Button
IObservable<Unit>
InputField
IObservable<string>
Toggle
IObservable<bool>
Slider
IObservable<float>
var incrementalSearch = input.OnValueChangeAsObservable()
.Throttle(TimeSpan.FromMilliseconds(250))
.Select(keyword => SearchAsync(keyword))
.Switch();
from InputField
toggle.OnValueChangedAsObservable()
.Subscribe(x => button.interactable = x);
ToggleのON/OFFでボタン
の有効/非有効切り替え
ObservableTriggers
衝突 = IObservable<Collision>
cube.OnCollisionEnterAsObservable().Subscribe();
カメラに入る = OnBecameVisibleAsObservable() = IObservable<Unit>
カメラから出る = OnBecameInvisibleAsObservable() = IObservable<Unit>
などなど、あらゆるインタラクションが全てIObservableになる
Lifecycle as Observable
frame(time)
OnDestroyAsObservable = (OnNext{1} OnCompleted)
ゲームループは毎フレーム(60fpsなら1/60秒)毎
にメソッドが呼びだされる
Observable.EveryUpdate().Subscribe(); // IObservable<long>
MultiThreading
Rx = Observable + LINQ + Scheduler
Observable.Start(() =>
{
/* なにか重い処理 */
return "hogemoge";
}, Scheduler.ThreadPool)
.ObserveOn(Scheduler.MainThread) // MainThreadに戻す
.DelaySubscription(TimeSpan.FromSeconds(3), Scheduler.MainThreadIgnoreTimeScale)
.Subscribe(x => ui.text = x); // UIパーツはメインスレッドからじゃないと触れない
Schedulerは時空魔法の、時(AbsoluteTime/RelativeTime)と
空間(実行スレッド/SynchronizationContext)をコントロール
Polling
ポーリングはIObservableと「見なせる」
IObservable<string> WatchFile(string path)
{
return Observable.Interval(TimeSpan.FromSeconds(5))
.Select(_ => File.ReadAllText(path));
}
IObservable<Notification<Unit>> Ping(string path)
{
return Observable.Interval(TimeSpan.FromSeconds(5))
.SelectMany(_ =>
ObservableWWW.Get("http://ping").Timeout(TimeSpan.FromSeconds(1)))
.AsUnitObservable()
.Materialize();
}
EveryValueChanged
更に踏み込んで、値変更を常時監視する黒魔法化
原理はゲームループのUpdate監視による毎フレームチェック
常に必ず呼ばれるから監視コストが相対的に低い(ゲームエンジンならでは)
生存期間管理
無限監視は危険なので、監視対象の生存具合をチェック
GameObjectなら破壊されていたら監視終了
C#オブジェクトなら、WeakReferenceで参照保持し、GCされてたら監視終了
// Position(x,y,z)が変化したら通知するIObservable<Vector3>
this.ObserveEveryValueChanged(x => x.transform.position)
.Subscribe(x => Debug.Log("poistion changed:" + x));
Logs are EventStream
ログはInProcessにおいてもStreamとして扱えるべき
logger(“Player”)
logger(“Monster”)
logger(“Network”)
logger(“ItemResolver”)
ObservableLogger.Listener
(IObservable<LogEntry>)
SubscribeToLogConsole
SubscribeToInGameDebugWindow
SubscribeToNetWorkLogStorage
EventAggregator/MessageBroker
InProcessのMessageBus(名前はなんでもいいや)
先のObservableLoggerと同じ図式
中央がIObservable<T>なら
購読 = IObservable<T>.Subscribe()
解除 = subscription.Dispose()
最も単純な(しかし便利な)実装
public static class MessageBroker
{
public static void Publish<T>(T message)
{
Notifier<T>.Instance.OnNext(message);
}
public static IObservable<T> Receive<T>()
{
return Notifier<T>.Instance.AsObservable();
}
static class Notifier<T>
{
public static readonly Subject<T> Instance = new Subject<T>();
}
}
型(<T>)でフィルタされるグローバル
に通知可能な何か、と考える
送信側:
MessageBroker.Publish(new HogeEvent())
受信側:
MessageBroker.Receive<HogeEvent>()
Model-View-Whatever
M-V-Hell
MVVVVVV
M-V-VM
DataBinding!
Observable Everywhere  - Rxの原則とUniRxにみるデータソースの見つけ方
バインディングに必要なもの
バインディングエンジン
Reactive Extensionsはバインディングエンジン「ではない」
RxでViewに何か処理するにはViewを知らなければならない
Unityにバインディングエンジンはない
作るか諦めるか
どうしても必要なら作るしかない
私はMVVMを選ばないことを選んだ
Unityにおいては(Rxがあれば)そこまで必要ではないと判断した
Model-View-Presenter
DataBindingがないので
ViewModelのかわりに
PresenterがViewを直接弄り、
イベントを受け取る
Rxで各レイヤーを繋げてしまえば見通
しよく構築可能
NotificationModel
Modelは通知可能でなければならない
// 変更通知付きなモデル
public class Enemy
{
// HPは変更あったら通知して他のところでなんか変化を起こすよね?
public IReactiveProperty<long> CurrentHp { get; private set; }
// 死んだら通知起こすよね?
public IReadOnlyReactiveProperty<bool> IsDead { get; private set; }
public Enemy(int initialHp)
{
// 死んだかどうかというのはHPが0以下になったら、で表現できる
CurrentHp = new ReactiveProperty<long>(initialHp);
IsDead = CurrentHp.Select(x => x <= 0).ToReadOnlyReactiveProperty();
}
}
ReactiveProperty
public class ReactivePresenter : MonoBehaviour
{
// ViewからのイベントをUIEventAsObservableで購読
Button button;
Text textView;
// ModelからのイベントをReactivePropertyで購読、SubscribeでViewにUpdate
Enemy enemy;
// 宣言的なUI記述
void Start()
{
// ボタンクリックでHPを減らす
button.OnClickAsObservable().Subscribe(_ => enemy.CurrentHp.Value -= 99);
// その変更を受けてUIに変更を戻す
enemy.CurrentHp.SubscribeToText(textView); // とりあえず現在HPをTextに表示
// もし死んだらボタンクリックできないようにする
enemy.IsDead.Select(isDead => !isDead).SubscribeToInteractable(button);
}
}
ReactivePropertyは通知付きモデルの作成を補助
値自体が変更をIObservable<T>として通知
Model-View-(Reactive)Presenter
not databinding but databinding
データバインディングっぽい
Rxの力で、かなりそれらしい感触になる
マクロがあればもっと整形できそうだけどC#はマクロないので
別にそこまでは必要じゃない
無理しない
やはりゲームなのでパフォーマンスの読みやすさは必須
バインディングエンジン作成は一朝一夕でできない、それよりMV(R)Pはシンプ
ルな、というか中間レイヤーはなにもないので挙動も読みやすくオーバヘッド
もない
既に実績あるバインディングエンジンがあるなら、それに乗っかればいい
ReactivePropertyはModelの作成を補助するのでMVVMのMやVMのレイヤーで十分使える
Conclusion
資料紹介
Event processing at all scales with Reactive Extensions
http://channel9.msdn.com/events/TechDays/Techdays-2014-the-
Netherlands/Event-processing-at-all-scales-with-Reactive-Extensions
現時点でのRx入門の決定版といえる素晴らすぃVideo
Bart氏は現在はMicrosoftのPrincipal Software Engineer(偉い)
Cloud-Scale Event Processing Using Rx
http://buildstuff14.sched.org/event/174c265e4ec350bd2e005b59bce0275e
激烈にAdvancedな内容
IReactiveProcessingModel, SubjectのDuality, 次世代のRx(3.0)などなど
更なる未来を見据えた内容なので是非一読を
まとめ
LINQ to Everywhere
Reactive Extensions is not only Asynchronous
Reactive Extensions is not only Data-Binding
Observable Everywhere
データソースさえ見つかれば、そこにRxはある
あとは無限の合成可能性を活かせばいい!
まず、データソースを探しだしてください

More Related Content

Observable Everywhere - Rxの原則とUniRxにみるデータソースの見つけ方