58

History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

Embed Size (px)

Citation preview

Page 1: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
Page 2: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

Work

http://grani.jp/

C#

Unity

Private

http://neue.cc/

@neuecc

https://github.com/neuecc

Page 3: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

LINQ to GameOject

https://github.com/neuecc/LINQ-to-GameObject-for-Unity

https://www.assetstore.unity3d.com/jp/#!/content/24256

// destroy all filtered(tag == "foobar") objectsroot.Descendants()

.Where(x => x.tag == "foobar")

.Destroy();

// get FooScript under self childer objects and selfvar fooScripts = root.ChildrenAndSelf().OfComponent<FooScript>();

Page 4: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

46

39

71

0

10

20

30

40

50

60

70

80

使っている 使う予定がある 使っていない

UniRxを使っていますか?(計156)

Page 5: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

History

Page 6: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

神獄のヴァルハラゲート

モンスタハンターロアオブカード

http://grani.jp/

Page 7: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

using

Page 8: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

サーバーサイドからUnityまで全てをC#で

Isomorphic Architecture

Page 9: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

http://www.slideshare.net/neuecc/reactive-extensions-8049041

Page 10: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

https://github.com/neuecc/UniRx

Page 11: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

Push Event Stream

Event Processing

Interactive/Visualize

Page 12: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

CoreLibrary

Framework Adapter

Port of Rx.NET

FromCoroutine

MainThreadSchedulerObservableTriggers

ReactiveProperty

Page 13: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

2014/04/19 - UniRx 1.0

http://www.slideshare.net/neuecc/unityrx-reactive-extensions-for-unity

2014/05/28 - UniRx 4.0

Page 14: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

2014/07/10 - UniRx 4.3

http://neue.cc/2014/07/01_474.html

2014/07/30

http://www.slideshare.net/neuecc/reactive-programming-by-unirxfor-asynchronous-event-processing

Page 15: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

2015/01/22 - UniRx 4.4~4.6

http://neue.cc/2014/09/08_477.html

Page 16: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

2015/03/10 - UniRx 4.7

2015/04/10 - UniRx 4.8

Page 17: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

2015/04/16

http://www.slideshare.net/neuecc/observable-everywhere-rxuni-rx

2015/05/26 - UniRx 4.8.1

Page 18: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

Practices & Pitfalls

Page 19: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
Page 20: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

https://github.com/neuecc/LightNode

Page 21: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

ObservableWWW + 頻出メソッド

Page 22: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良いpublic class ObservableClient{

public IObservable<WWW> GetHogeAsync(int huga){

return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga);}

}

Page 23: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良いpublic class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga).Select(www =>{

// これはJSON.NET for Unity(有料アセット)を使ってますが// LitJsonなりMiniJsonなりでもなんでも、とにかくデシリアライズして返すreturn JsonConvert.DeserializeObject<HogeResponse>(www.text);

})}

}

Page 24: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良いpublic class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga).Select(www =>{

// これはJSON.NET for Unity(有料アセット)を使ってますが// LitJsonなりMiniJsonなりでもなんでも、とにかくデシリアライズして返すreturn JsonConvert.DeserializeObject<HogeResponse>(www.text);

}).Catch((WWWErrorException error) =>{

// WWW通信が失敗した場合の処理を書く// 例えばダイアログ出してユーザーの入力を待つとかも// (入力結果の待ちがIObservableにしていれば)可能return Observable.Empty<HogeResponse>();

});}

}

Page 25: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良いpublic class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga).Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト.Select(www =>{

// これはJSON.NET for Unity(有料アセット)を使ってますが// LitJsonなりMiniJsonなりでもなんでも、とにかくデシリアライズして返すreturn JsonConvert.DeserializeObject<HogeResponse>(www.text);

}).Catch((WWWErrorException error) =>{

// WWW通信が失敗した場合の処理を書く// 例えばダイアログ出してユーザーの入力を待つとかも可能return Observable.Empty<HogeResponse>();

});}

}

Page 26: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良いpublic class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga).Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト.Select(www =>{

// これはJSON.NET for Unity(有料アセット)を使ってますが// LitJsonなりMiniJsonなりでもなんでも、とにかくデシリアライズして返すreturn JsonConvert.DeserializeObject<HogeResponse>(www.text);

}).Catch((TimeoutException error) =>{

// タイムアウトした場合の処理を書く// その処理が正常終了なら Observable.Empty<T> を、ダメならThrowを返すreturn Observable.Throw<HogeResponse>(error);

}).Catch((WWWErrorException error) =>{

// WWW通信が失敗した場合の処理を書く// 例えばダイアログ出してユーザーの入力を待つとかも可能

Page 27: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// 更にリトライを考慮したエディション(複雑!!!)public class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

// リトライ耐性をつけるために自身の変数を用意するIObservable<HogeResponse> asyncRequest = null;asyncRequest = ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga).Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト.Select(www => JsonConvert.DeserializeObject<HogeResponse>(www.text)).Catch((TimeoutException error) => Observable.Throw<HogeResponse>(error)).Catch((WWWErrorException error) =>{

// WWW通信が失敗した場合の処理を書く// 例えばダイアログ出してユーザーの入力を待つとかも(入力結果の待ちがIObservableにしていれば)可能// リトライするならreturn asyncRequest のように自分自身を返せばいい// ちなみに3秒後にリトライ、といったようにしたければ// asyncRequest.DelaySubscription(TimeSpan.FromSeconds(3)) といったように書けばOKreturn Observable.Empty<HogeResponse>();

});// PublishLastによりColdの性質が消え、何度呼ばれても一回しか結果を返さないようになる// RefCountによりPublishLastの.ConnectがSubscribe時に自動でかかるようになるreturn asyncRequest.PublishLast().RefCount();

}}

Page 28: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// 更にリトライを考慮したエディション(複雑!!!)public class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

// リトライ耐性をつけるために自身の変数を用意するIObservable<HogeResponse> asyncRequest = null;asyncRequest = ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga).Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト.Select(www => JsonConvert.DeserializeObject<HogeResponse>(www.text)).Catch((TimeoutException error) => Observable.Throw<HogeResponse>(error)).Catch((WWWErrorException error) =>{

// WWW通信が失敗した場合の処理を書く// 例えばダイアログ出してユーザーの入力を待つとかも(入力結果の待ちがIObservableにしていれば)可能// リトライするならreturn asyncRequest のように自分自身を返せばいい// ちなみに3秒後にリトライ、といったようにしたければ// asyncRequest.DelaySubscription(TimeSpan.FromSeconds(3)) といったように書けばOKreturn Observable.Empty<HogeResponse>();

});// PublishLastによりColdの性質が消え、何度呼ばれても一回しか結果を返さないようになる// RefCountによりPublishLastの.ConnectがSubscribe時に自動でかかるようになるreturn asyncRequest.PublishLast().RefCount();

}}

Page 29: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// 更にリトライを考慮したエディション(複雑!!!)public class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

// リトライ耐性をつけるために自身の変数を用意するIObservable<HogeResponse> asyncRequest = null;asyncRequest = ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga).Timeout(TimeSpan.FromSeconds(30)) // 30秒タイムアウト.Select(www => JsonConvert.DeserializeObject<HogeResponse>(www.text)).Catch((TimeoutException error) => Observable.Throw<HogeResponse>(error)).Catch((WWWErrorException error) =>{

// WWW通信が失敗した場合の処理を書く// 例えばダイアログ出してユーザーの入力を待つとかも(入力結果の待ちがIObservableにしていれば)可能// リトライするならreturn asyncRequest のように自分自身を返せばいい// ちなみに3秒後にリトライ、といったようにしたければ// asyncRequest.DelaySubscription(TimeSpan.FromSeconds(3)) といったように書けばOKreturn Observable.Empty<HogeResponse>();

});// PublishLastによりColdの性質が消え、何度呼ばれても一回しか結果を返さないようになる// RefCountによりPublishLastの.ConnectがSubscribe時に自動でかかるようになるreturn asyncRequest.PublishLast().RefCount();

}}

Page 30: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

public class ObservableClient{

// 色々なのペタペタ付ける部分は外出しIObservable<T> WithErrorHandling<T>(IObservable<WWW> source){

IObservable<T> asyncRequest = null;asyncRequest = source

.Timeout(TimeSpan.FromSeconds(30))

.Select(www => JsonConvert.DeserializeObject<T>(www.text))

.Catch((TimeoutException error) => Observable.Throw<T>(error))

.Catch((WWWErrorException error) => Observable.Throw<T>(error))

.Catch((Exception error) => Observable.Throw<T>(error));

return asyncRequest.PublishLast().RefCount();}

// 以下量産

public IObservable<HogeResponse> GetHogeAsync(int huga){

return WithErrorHandling<HogeResponse>(ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga));}

public IObservable<HugaResponse> GetHugaAsync(int huga){

return WithErrorHandling<HugaResponse>(ObservableWWW.GetWWW("http://hogehoge.com/Huga?huga=" + huga));}

}

Page 31: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

WhenAllで並列リクエストがめっちゃ身近に

var client = new ObservableClient();Observable.WhenAll(

client.GetFooAsync(),client.GetFooAsync(),client.GetFooAsync())

.Subscribe(xs => { });

// 戻り値が違うとコンパイルエラーで使えない!ダメぢゃん!!!Observable.WhenAll(

client.GetFooAsync(),client.GetBarAsync(),client.GetBazAsync())

.Subscribe(xs => { });

Page 32: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

Castを駆使する

// 強引に型を合わせてから取り出す:)Observable.WhenAll(

client.GetFooAsync().Cast(default(object)),client.GetBarAsync().Cast(default(object)),client.GetBazAsync().Cast(default(object)))

.Subscribe(xs =>{

var foo = xs[0] as FooResponse;var bar = xs[1] as BarResponse;var baz = xs[2] as BazResponse;

});

Page 33: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

WhenAllに無限シーケンスが混ざってたら?

Page 34: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

要素(1)のメソッドは末尾Asyncの命名規約で統一

Page 35: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
Page 36: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

SignalRによるC#/WebSocketによるRPCを採用

Page 37: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

完了コールバック as IObservable<T>

Page 38: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

値+イベントの表現[Serializable]public class Character : MonoBehaviour{

public Action<int> HpChanged;

[SerializeField]int hp = 0;public int Hp{

get{

return hp;}set{

hp = value;var h = HpChanged;if (h != null){

h.Invoke(hp);}

}}

[Serializable]public class Character : MonoBehaviour{

public IntReactiveProperty Hp;}

Page 39: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// これでトグルオンオフがIObservable<bool>として取ってこれる、とかMyToggle.OnValueChangedAsObservable().Subscribe(x => { })

Page 40: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

Unity + Rxに適したUIパターン

Passive View

Presenter(Supervising Controller)

Model

updates view

state-change

events

user events

update model

UIControl.XxxAsObservable

UnityEvent.AsObservable

ObservableEventTrigger

Subscribe

ToReactivePropertyReactiveProperty

Subscribe

SubscribeToText

SubscribeToInteractable

Page 41: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

public class CalculatorPresenter : MonoBehaviour{

public InputField Left;public InputField Right;public Text Result;

void Start(){

var left = this.Left.OnValueChangeAsObservable().Select(x => int.Parse(x));

var right = this.Right.OnValueChangeAsObservable().Select(x => int.Parse(x));

left.CombineLatest(right, (l, r) => l + r).SubscribeToText(this.Result);

}}

Page 42: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

インスペクタで貼り付けるPの抱える問題

// 子供。これはまぁ普通[Serializable]public class ChildPresenter : MonoBehaviour{

public IntReactiveProperty Hp; // serializablepublic ReadOnlyReactiveProperty<bool> IsDead

{ get; private set; }

void Start(){

IsDead = Hp.Select(x => x <= 0).ToReadOnlyReactiveProperty();

}}

// 親[Serializable]public class ParentPresenter : MonoBehaviour{

public ChildPresenter ChildPresenter;public Text IsDeadDisplay;

void Start(){

// IsDeadは触れるか?ChildPresenter.IsDead.SubscribeToText(IsDeadDisplay);

}}

Page 43: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

インスペクタで貼り付けるPの抱える問題

// 子供。これはまぁ普通[Serializable]public class ChildPresenter : MonoBehaviour{

public IntReactiveProperty Hp; // serializablepublic ReadOnlyReactiveProperty<bool> IsDead

{ get; private set; }

void Start(){

IsDead = Hp.Select(x => x <= 0).ToReadOnlyReactiveProperty();

}}

// 親[Serializable]public class ParentPresenter : MonoBehaviour{

public ChildPresenter ChildPresenter;public Text IsDeadDisplay;

void Start(){

// IsDeadは触れるか?ChildPresenter.IsDead.SubscribeToText(IsDeadDisplay);

}}

Page 44: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

MonoBehaviour同士の初期化順は不定

Page 45: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// 親。public class ParentPresenter : PresenterBase{

public ChildPresenter ChildPresenter;public Text IsDeadDisplay;

protected override IPresenter[] Children{

get { return new IPresenter[] { ChildPresenter }; } // 子供を指定する}

protected override void BeforeInitialize(){

ChildPresenter.PropagateArgument(1000); // 子供に初期値を渡す}

protected override void Initialize(){

// 子供の初期化は終わってるので問題なく触れるChildPresenter.IsDead.SubscribeToText(IsDeadDisplay);

}}

Page 46: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// 子供。[Serializable]public class ChildPresenter : PresenterBase<int>{

public IntReactiveProperty Hp; // serializablepublic ReadOnlyReactiveProperty<bool> IsDead { get; set; }

protected override IPresenter[] Children{

get { return EmptyChildren; }}

protected override void BeforeInitialize(int argument) { }

// 親から引数が渡ってくるので初期値として使えるprotected override void Initialize(int argument){

Hp.Value = argument;IsDead = Hp.Select(x => x <= 0).ToReadOnlyReactiveProperty();

}}

Page 47: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
Page 48: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

Subscribe忘れで発火しない// 通信して新しいデータを登録するとする// 戻り値は IObservable<Unit> で、SetだしということでSubscribe忘れ// →何も起こらない→クソが!new ObservableClient().SetNewData(100);

Page 49: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

Subscribe忘れで発火しない// 通信して新しいデータを登録するとする// 戻り値は IObservable<Unit> で、SetだしということでSubscribe忘れ// →何も起こらない→クソが!new ObservableClient().SetNewData(100);

Page 50: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// uGUIのボタンクリックから非同期通信(とか)に繋げるbutton.OnClickAsObservable()

.SelectMany(_ => new ObservableClient().NanikaHidoukiAsync(100))

.Subscribe(_ =>{

Debug.Log("Nanika Hidouki sita");});

Page 51: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// uGUIのボタンクリックから非同期通信(とか)に繋げるbutton.OnClickAsObservable()

.SelectMany(_ =>{

throw new Exception("なにか例外がおきた");return new ObservableClient().NanikaHidoukiAsync(100);

}).Subscribe(_ =>{

Debug.Log("Nanika Hidouki sita");});

Page 52: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

// uGUIのボタンクリックから非同期通信(とか)に繋げるbutton.OnClickAsObservable()

.SelectMany(_ =>{

throw new Exception("なにか例外がおきた");return new ObservableClient().NanikaHidoukiAsync(100);

}).OnErrorRetry().Subscribe(_ =>{

Debug.Log("Nanika Hidouki sita");});

Page 53: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

button.OnClickAsObservable().Subscribe(_ =>{

// Subscribe in Subscribe...!new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ =>{

Debug.Log("Nanika Hidouki sita");});

});

Page 54: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

例外は避け得ない、そしてRetryは……

button.OnClickAsObservable().Subscribe(_ =>{

// Subscribe in Subscribe...!new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ =>{

Debug.Log("Nanika Hidouki sita");});

});

Page 55: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

例外は避け得ない、そしてRetryは……

button.OnClickAsObservable().Subscribe(_ =>{

// Subscribe in Subscribe...!new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ =>{

Debug.Log("Nanika Hidouki sita");});

});

Page 56: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

Conclusion

Page 57: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

え、これだけ?

Real World UniRx

Page 58: History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法