history & practices for unirx(en)
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>();
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/04/16
http://www.slideshare.net/neuecc/observable-everywhere-rxuni-rx
2015/05/26 - UniRx 4.8.1
// 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;
});
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);
}}
// 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();
}}
Doesn't fire(I forget Subscribe)// Return type is IObservable<Unit>// Nothing happens...new ObservableClient().SetNewData(100);
// 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");});
});