History & Practices for UniRx...
-
Upload
yoshifumi-kawai -
Category
Technology
-
view
7.212 -
download
0
Transcript of History & Practices for UniRx...
Work
http://grani.jp/
C#
Unity
Private
http://neue.cc/
@neuecc
https://github.com/neuecc
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>();
46
39
71
0
10
20
30
40
50
60
70
80
使っている 使う予定がある 使っていない
UniRxを使っていますか?(計156)
History
using
サーバーサイドからUnityまで全てをC#で
Isomorphic Architecture
http://www.slideshare.net/neuecc/reactive-extensions-8049041
https://github.com/neuecc/UniRx
Push Event Stream
Event Processing
Interactive/Visualize
CoreLibrary
Framework Adapter
Port of Rx.NET
FromCoroutine
MainThreadSchedulerObservableTriggers
ReactiveProperty
2014/04/19 - UniRx 1.0
http://www.slideshare.net/neuecc/unityrx-reactive-extensions-for-unity
2014/05/28 - UniRx 4.0
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
2015/03/10 - UniRx 4.7
2015/04/10 - UniRx 4.8
2015/04/16
http://www.slideshare.net/neuecc/observable-everywhere-rxuni-rx
2015/05/26 - UniRx 4.8.1
Practices & Pitfalls
https://github.com/neuecc/LightNode
ObservableWWW + 頻出メソッド
// ObservableWWWを一つラップしたものを用意して、プロジェクトの通信周りを集約化すると良いpublic class ObservableClient{
public IObservable<WWW> GetHogeAsync(int huga){
return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga);}
}
// 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);
})}
}
// 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>();
});}
}
// 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>();
});}
}
// 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通信が失敗した場合の処理を書く// 例えばダイアログ出してユーザーの入力を待つとかも可能
// 更にリトライを考慮したエディション(複雑!!!)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();
}}
// 更にリトライを考慮したエディション(複雑!!!)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();
}}
// 更にリトライを考慮したエディション(複雑!!!)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();
}}
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));}
}
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 => { });
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;
});
WhenAllに無限シーケンスが混ざってたら?
要素(1)のメソッドは末尾Asyncの命名規約で統一
SignalRによるC#/WebSocketによるRPCを採用
完了コールバック as IObservable<T>
値+イベントの表現[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;}
// これでトグルオンオフがIObservable<bool>として取ってこれる、とかMyToggle.OnValueChangedAsObservable().Subscribe(x => { })
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
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);
}}
インスペクタで貼り付ける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);
}}
インスペクタで貼り付ける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);
}}
MonoBehaviour同士の初期化順は不定
// 親。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);
}}
// 子供。[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();
}}
Subscribe忘れで発火しない// 通信して新しいデータを登録するとする// 戻り値は IObservable<Unit> で、SetだしということでSubscribe忘れ// →何も起こらない→クソが!new ObservableClient().SetNewData(100);
Subscribe忘れで発火しない// 通信して新しいデータを登録するとする// 戻り値は IObservable<Unit> で、SetだしということでSubscribe忘れ// →何も起こらない→クソが!new ObservableClient().SetNewData(100);
// uGUIのボタンクリックから非同期通信(とか)に繋げるbutton.OnClickAsObservable()
.SelectMany(_ => new ObservableClient().NanikaHidoukiAsync(100))
.Subscribe(_ =>{
Debug.Log("Nanika Hidouki sita");});
// uGUIのボタンクリックから非同期通信(とか)に繋げるbutton.OnClickAsObservable()
.SelectMany(_ =>{
throw new Exception("なにか例外がおきた");return new ObservableClient().NanikaHidoukiAsync(100);
}).Subscribe(_ =>{
Debug.Log("Nanika Hidouki sita");});
// uGUIのボタンクリックから非同期通信(とか)に繋げるbutton.OnClickAsObservable()
.SelectMany(_ =>{
throw new Exception("なにか例外がおきた");return new ObservableClient().NanikaHidoukiAsync(100);
}).OnErrorRetry().Subscribe(_ =>{
Debug.Log("Nanika Hidouki sita");});
button.OnClickAsObservable().Subscribe(_ =>{
// Subscribe in Subscribe...!new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ =>{
Debug.Log("Nanika Hidouki sita");});
});
例外は避け得ない、そしてRetryは……
button.OnClickAsObservable().Subscribe(_ =>{
// Subscribe in Subscribe...!new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ =>{
Debug.Log("Nanika Hidouki sita");});
});
例外は避け得ない、そしてRetryは……
button.OnClickAsObservable().Subscribe(_ =>{
// Subscribe in Subscribe...!new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ =>{
Debug.Log("Nanika Hidouki sita");});
});
Conclusion
え、これだけ?
Real World UniRx