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") objects
root.Descendants()
.Where(x => x.tag == "foobar")
.Destroy();
// get FooScript under self childer objects and self
var 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
MainThreadScheduler
ObservableTriggers
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 access
public 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 value
return 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 IObservable
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)) // 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? etc
return Observable.Throw<HogeResponse>(error);
})
.Catch((WWWErrorException error) =>
{
return Observable.Empty<HogeResponse>();
});
}
}
// complex retyable edition
public class ObservableClient
{
public IObservable<HogeResponse> GetHogeAsync(int huga)
{
//for retry
IObservable<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 .Connect
return asyncRequest.PublishLast().RefCount();
}
}
public class ObservableClient
{
// outside in method
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 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 example
public 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
ToReactiveProperty
ReactiveProperty
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; // serializable
public 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; // serializable
public 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
// Parent
public 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 initialzied
ChildPresenter.IsDead.SubscribeToText(IsDeadDisplay);
}
// Child
[Serializable]
public class ChildPresenter : PresenterBase<int>
{
public IntReactiveProperty Hp; // serializable
public ReadOnlyReactiveProperty<bool> IsDead { get; set; }
protected override IPresenter[] Children
{
get { return EmptyChildren; }
}
protected override void BeforeInitialize(int argument) { }
// argument from parent
protected 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 operation
button.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

History & Practices for UniRx(EN)

  • 2.
  • 3.
    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") objects root.Descendants() .Where(x => x.tag == "foobar") .Destroy(); // get FooScript under self childer objects and self var fooScripts = root.ChildrenAndSelf().OfComponent<FooScript>();
  • 4.
  • 5.
  • 6.
    Push Event Stream EventProcessing Interactive/Visualize
  • 7.
    CoreLibrary Framework Adapter Port ofRx.NET FromCoroutine MainThreadScheduler ObservableTriggers ReactiveProperty
  • 8.
    2014/04/19 - UniRx1.0 http://www.slideshare.net/neuecc/unityrx-reactive-extensions-for-unity 2014/05/28 - UniRx 4.0
  • 9.
    2014/07/10 - UniRx4.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
  • 10.
  • 11.
    2015/03/10 - UniRx4.7 2015/04/10 - UniRx 4.8
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
    // Wrap theObservableWWW and aggregate network access public class ObservableClient { public IObservable<WWW> GetHogeAsync(int huga) { return ObservableWWW.GetWWW("http://hogehoge.com/Hoge?huga=" + huga); } }
  • 17.
    public class ObservableClient { publicIObservable<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 value return JsonConvert.DeserializeObject<HogeResponse>(www.text); }) } }
  • 18.
    public class ObservableClient { publicIObservable<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 IObservable return Observable.Empty<HogeResponse>(); }); } }
  • 19.
    public class ObservableClient { publicIObservable<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>(); }); } }
  • 20.
    public class ObservableClient { publicIObservable<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? etc return Observable.Throw<HogeResponse>(error); }) .Catch((WWWErrorException error) => { return Observable.Empty<HogeResponse>(); }); } }
  • 21.
    // complex retyableedition public class ObservableClient { public IObservable<HogeResponse> GetHogeAsync(int huga) { //for retry IObservable<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 .Connect return asyncRequest.PublishLast().RefCount(); } }
  • 22.
    public class ObservableClient { //outside in method 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)); } }
  • 23.
    WhenAll makes easyfor 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 => { });
  • 24.
  • 25.
  • 26.
  • 28.
    Completed callback asIObservable<T> // for example public 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(); } }
  • 29.
    Represents Value+Event [Serializable] public classCharacter : 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
  • 30.
    Unity + Rx'sUI Pattern Passive View Presenter (Supervising Controller) Model updates view state-change events user events update model UIControl.XxxAsObservable UnityEvent.AsObservable ObservableEventTrigger Subscribe ToReactiveProperty ReactiveProperty Subscribe SubscribeToText SubscribeToInteractable
  • 31.
    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); } }
  • 32.
    Issue of Pand serializable value // Child [Serializable] public class ChildPresenter : MonoBehaviour { public IntReactiveProperty Hp; // serializable public 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); } }
  • 33.
    Issue of Pand serializable value // Child [Serializable] public class ChildPresenter : MonoBehaviour { public IntReactiveProperty Hp; // serializable public 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); } }
  • 34.
  • 35.
    // Parent public classParentPresenter : 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 initialzied ChildPresenter.IsDead.SubscribeToText(IsDeadDisplay); }
  • 36.
    // Child [Serializable] public classChildPresenter : PresenterBase<int> { public IntReactiveProperty Hp; // serializable public ReadOnlyReactiveProperty<bool> IsDead { get; set; } protected override IPresenter[] Children { get { return EmptyChildren; } } protected override void BeforeInitialize(int argument) { } // argument from parent protected override void Initialize(int argument) { Hp.Value = argument; IsDead = Hp.Select(x => x <= 0).ToReadOnlyReactiveProperty(); } }
  • 37.
  • 38.
    Doesn't fire(I forgetSubscribe) // Return type is IObservable<Unit> // Nothing happens... new ObservableClient().SetNewData(100);
  • 39.
  • 40.
    // button clickin uGUI to asynchronous operation button.OnClickAsObservable() .SelectMany(_ => new ObservableClient().FooBarAsync(100)) .Subscribe(_ => { Debug.Log("done"); });
  • 41.
    button.OnClickAsObservable() .SelectMany(_ => { throw newException("something happen"); return new ObservableClient(). FooBarAsync(100); }) .Subscribe(_ => { Debug.Log("done"); });
  • 42.
    button.OnClickAsObservable() .SelectMany(_ => { throw newException("something happen"); return new ObservableClient(). FooBarAsync(100); }) .OnErrorRetry() .Subscribe(_ => { Debug.Log("done"); });
  • 43.
    button.OnClickAsObservable().Subscribe(_ => { // Subscribein Subscribe...! new ObservableClient(). FooBarAsync(100).Subscribe(__ => { Debug.Log("done"); }); });
  • 44.
    Can't avoid errorand Retry is dangerous button.OnClickAsObservable().Subscribe(_ => { // Subscribe in Subscribe...! new ObservableClient().NanikaHidoukiAsync(100).Subscribe(__ => { Debug.Log("done"); }); });
  • 45.
  • 46.
    We use UniRx RealWorld UniRx