history & practices for unirx(en)

46

Upload: yoshifumi-kawai

Post on 03-Aug-2015

357 views

Category:

Technology


4 download

TRANSCRIPT

Work

http://grani.jp/

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>();

History

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

https://github.com/neuecc/UniRx/wiki/AOT-Exception-Patterns-and-Hacks

2014/07/30

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

2015/01/22 - UniRx 4.4~4.6

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 + Frequent methods

// Wrap the ObservableWWW and aggregate network accesspublic class ObservableClient{

public IObservable<WWW> GetHogeAsync(int huga){

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

}

public class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

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

// This is used JSON.NET for Unity(payed asset)// LitJson or MiniJson etc, return the deserialized valuereturn JsonConvert.DeserializeObject<HogeResponse>(www.text);

})}

}

public class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

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

return JsonConvert.DeserializeObject<HogeResponse>(www.text);}).Catch((WWWErrorException error) =>{

// Failed in WWW// For example shows dialog and await user input,// you can do even if input result is IObservablereturn Observable.Empty<HogeResponse>();

});}

}

public class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga).Timeout(TimeSpan.FromSeconds(30)) // 30 Seconds timeout.Select(www =>{

return JsonConvert.DeserializeObject<HogeResponse>(www.text);}).Catch((WWWErrorException error) =>{

return Observable.Empty<HogeResponse>();});

}}

public class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

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

return JsonConvert.DeserializeObject<HogeResponse>(www.text);}).Catch((TimeoutException error) =>{

// Observable.Empty<T>? Observable.Throw? etcreturn Observable.Throw<HogeResponse>(error);

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

return Observable.Empty<HogeResponse>();});

}}

// complex retyable editionpublic class ObservableClient{

public IObservable<HogeResponse> GetHogeAsync(int huga){

//for retryIObservable<HogeResponse> asyncRequest = null;asyncRequest = ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga).Timeout(TimeSpan.FromSeconds(30)).Select(www => JsonConvert.DeserializeObject<HogeResponse>(www.text)).Catch((TimeoutException error) => Observable.Throw<HogeResponse>(error)).Catch((WWWErrorException error) =>{

// If retry, you can return self such as "return asyncRequest"// If retry after 3 seconds you can write// asyncRequest.DelaySubscription(TimeSpan.FromSeconds(3))return Observable.Empty<HogeResponse>();

});// PublishLast, remove Cold behaivour, returns one result when called multiple// RefCount automate subscribable for PublishLast's .Connectreturn asyncRequest.PublishLast().RefCount();

}}

public class ObservableClient{

// outside in methodIObservable<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 makes easy for parallel request

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

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

.Subscribe(xs => { });

// Compile error detected when each return type is not same!Observable.WhenAll(

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

.Subscribe(xs => { });

Use 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 in Infinite Sequence?

Single(1) for "-Async" suffix

Completed callback as IObservable<T>

// for examplepublic static class DoTweenExtensions{

public static IObservable<Sequence> CompletedAsObservable(this Sequence sequence){

var subject = new AsyncSubject<Sequence>();sequence.AppendCallback(() =>{

subject.OnNext(sequence);subject.OnCompleted();

});return subject.AsObservable();

}}

Represents Value+Event[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;}

+ notify when their value is changed even

when it is changed in the inspector

Unity + Rx's UI Pattern

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);

}}

Issue of P and serializable value

// Child[Serializable]public class ChildPresenter : MonoBehaviour{

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

{ get; private set; }

void Start(){

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

}}

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

public ChildPresenter ChildPresenter;public Text IsDeadDisplay;

void Start(){

// Can you touch IsDead?ChildPresenter.IsDead.SubscribeToText(IsDeadDisplay);

}}

Issue of P and serializable value

// Child[Serializable]public class ChildPresenter : MonoBehaviour{

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

{ get; private set; }

void Start(){

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

}}

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

public ChildPresenter ChildPresenter;public Text IsDeadDisplay;

void Start(){

// Can you touch IsDead?ChildPresenter.IsDead.SubscribeToText(IsDeadDisplay);

}}

MonoBehaviour's order is uncontrollable

// Parentpublic class ParentPresenter : PresenterBase{

public ChildPresenter ChildPresenter;public Text IsDeadDisplay;

protected override IPresenter[] Children{

get { return new IPresenter[] { ChildPresenter }; } // Children}

protected override void BeforeInitialize(){

ChildPresenter.PropagateArgument(1000); // Pass initial argument to child}

protected override void Initialize(){

// After initialziedChildPresenter.IsDead.SubscribeToText(IsDeadDisplay);

}}

// Child[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) { }

// argument from parentprotected override void Initialize(int argument){

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

}}

https://github.com/neuecc/UniRx#presenterbase

Doesn't fire(I forget Subscribe)// Return type is IObservable<Unit>// Nothing happens...new ObservableClient().SetNewData(100);

Doesn't fire(I forget Subscribe)

// button click in uGUI to asynchronous operationbutton.OnClickAsObservable()

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

.Subscribe(_ =>{

Debug.Log("done");});

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

throw new Exception("something happen");return new ObservableClient(). FooBarAsync(100);

}).Subscribe(_ =>{

Debug.Log("done");});

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

throw new Exception("something happen");return new ObservableClient(). FooBarAsync(100);

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

Debug.Log("done");});

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

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

Debug.Log("done");});

});

Can't avoid error and Retry is dangerous

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

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

Debug.Log("done");});

});

Conclusion

We use UniRx

Real World UniRx