GithubHelp home page GithubHelp logo

neuecc / unirx Goto Github PK

View Code? Open in Web Editor NEW
6.9K 375.0 887.0 8.22 MB

Reactive Extensions for Unity

License: MIT License

C# 99.84% PowerShell 0.16% Batchfile 0.01%
unirx unity c-sharp rx reactive-extensions linq

unirx's Introduction

Important

I have started distributing an evolved version of UniRx in Cysharp/R3, please use it instead of UniRx.

UniRx - Reactive Extensions for Unity

Created by Yoshifumi Kawai(neuecc)

What is UniRx?

UniRx (Reactive Extensions for Unity) is a reimplementation of the .NET Reactive Extensions. The Official Rx implementation is great but doesn't work on Unity and has issues with iOS IL2CPP compatibility. This library fixes those issues and adds some specific utilities for Unity. Supported platforms are PC/Mac/Android/iOS/WebGL/WindowsStore/etc and the library.

UniRx is available on the Unity Asset Store (FREE) - http://u3d.as/content/neuecc/uni-rx-reactive-extensions-for-unity/7tT

Blog for update info - https://medium.com/@neuecc

Support thread on the Unity Forums: Ask me any question - http://forum.unity3d.com/threads/248535-UniRx-Reactive-Extensions-for-Unity

Release Notes, see UniRx/releases

UniRx is Core Library (Port of Rx) + Platform Adaptor (MainThreadScheduler/FromCoroutine/etc) + Framework (ObservableTriggers/ReactiveProeperty/etc).

Note: async/await integration(UniRx.Async) is separated to Cysharp/UniTask after ver. 7.0.

Why Rx?

Ordinarily, Network operations in Unity require the use of WWW and Coroutine. That said, using Coroutine is not good practice for asynchronous operations for the following (and other) reasons:

  1. Coroutines can't return any values, since its return type must be IEnumerator.
  2. Coroutines can't handle exceptions, because yield return statements cannot be surrounded with a try-catch construction.

This kind of lack of composability causes operations to be close-coupled, which often results in huge monolithic IEnumerators.

Rx cures that kind of "asynchronous blues". Rx is a library for composing asynchronous and event-based programs using observable collections and LINQ-style query operators.

The game loop (every Update, OnCollisionEnter, etc), sensor data (Kinect, Leap Motion, VR Input, etc.) are all types of events. Rx represents events as reactive sequences which are both easily composable and support time-based operations by using LINQ query operators.

Unity is generally single threaded but UniRx facilitates multithreading for joins, cancels, accessing GameObjects, etc.

UniRx helps UI programming with uGUI. All UI events (clicked, valuechanged, etc) can be converted to UniRx event streams.

Unity supports async/await from 2017 with C# upgrades, UniRx family prjects provides more lightweight, more powerful async/await integration with Unity. Please see Cysharp/UniTask.

Introduction

Great introduction to Rx article: The introduction to Reactive Programming you've been missing.

The following code implements the double click detection example from the article in UniRx:

var clickStream = Observable.EveryUpdate()
    .Where(_ => Input.GetMouseButtonDown(0));

clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))
    .Where(xs => xs.Count >= 2)
    .Subscribe(xs => Debug.Log("DoubleClick Detected! Count:" + xs.Count));

This example demonstrates the following features (in only five lines!):

  • The game loop (Update) as an event stream
  • Composable event streams
  • Merging self stream
  • Easy handling of time based operations

Network operations

Use ObservableWWW for asynchronous network operations. Its Get/Post functions return subscribable IObservables:

ObservableWWW.Get("http://google.co.jp/")
    .Subscribe(
        x => Debug.Log(x.Substring(0, 100)), // onSuccess
        ex => Debug.LogException(ex)); // onError

Rx is composable and cancelable. You can also query with LINQ expressions:

// composing asynchronous sequence with LINQ query expressions
var query = from google in ObservableWWW.Get("http://google.com/")
            from bing in ObservableWWW.Get("http://bing.com/")
            from unknown in ObservableWWW.Get(google + bing)
            select new { google, bing, unknown };

var cancel = query.Subscribe(x => Debug.Log(x));

// Call Dispose is cancel.
cancel.Dispose();

Use Observable.WhenAll for parallel requests:

// Observable.WhenAll is for parallel asynchronous operation
// (It's like Observable.Zip but specialized for single async operations like Task.WhenAll)
var parallel = Observable.WhenAll(
    ObservableWWW.Get("http://google.com/"),
    ObservableWWW.Get("http://bing.com/"),
    ObservableWWW.Get("http://unity3d.com/"));

parallel.Subscribe(xs =>
{
    Debug.Log(xs[0].Substring(0, 100)); // google
    Debug.Log(xs[1].Substring(0, 100)); // bing
    Debug.Log(xs[2].Substring(0, 100)); // unity
});

Progress information is available:

// notifier for progress use ScheduledNotifier or new Progress<float>(/* action */)
var progressNotifier = new ScheduledNotifier<float>();
progressNotifier.Subscribe(x => Debug.Log(x)); // write www.progress

// pass notifier to WWW.Get/Post
ObservableWWW.Get("http://google.com/", progress: progressNotifier).Subscribe();

Error handling:

// If WWW has .error, ObservableWWW throws WWWErrorException to onError pipeline.
// WWWErrorException has RawErrorMessage, HasResponse, StatusCode, ResponseHeaders
ObservableWWW.Get("http://www.google.com/404")
    .CatchIgnore((WWWErrorException ex) =>
    {
        Debug.Log(ex.RawErrorMessage);
        if (ex.HasResponse)
        {
            Debug.Log(ex.StatusCode);
        }
        foreach (var item in ex.ResponseHeaders)
        {
            Debug.Log(item.Key + ":" + item.Value);
        }
    })
    .Subscribe();

Using with IEnumerators (Coroutines)

IEnumerator (Coroutine) is Unity's primitive asynchronous tool. UniRx integrates coroutines and IObservables. You can write asynchronious code in coroutines, and orchestrate them using UniRx. This is best way to control asynchronous flow.

// two coroutines

IEnumerator AsyncA()
{
    Debug.Log("a start");
    yield return new WaitForSeconds(1);
    Debug.Log("a end");
}

IEnumerator AsyncB()
{
    Debug.Log("b start");
    yield return new WaitForEndOfFrame();
    Debug.Log("b end");
}

// main code
// Observable.FromCoroutine converts IEnumerator to Observable<Unit>.
// You can also use the shorthand, AsyncA().ToObservable()
        
// after AsyncA completes, run AsyncB as a continuous routine.
// UniRx expands SelectMany(IEnumerator) as SelectMany(IEnumerator.ToObservable())
var cancel = Observable.FromCoroutine(AsyncA)
    .SelectMany(AsyncB)
    .Subscribe();

// you can stop a coroutine by calling your subscription's Dispose.
cancel.Dispose();

If in Unity 5.3, you can use ToYieldInstruction for Observable to Coroutine.

IEnumerator TestNewCustomYieldInstruction()
{
    // wait Rx Observable.
    yield return Observable.Timer(TimeSpan.FromSeconds(1)).ToYieldInstruction();

    // you can change the scheduler(this is ignore Time.scale)
    yield return Observable.Timer(TimeSpan.FromSeconds(1), Scheduler.MainThreadIgnoreTimeScale).ToYieldInstruction();

    // get return value from ObservableYieldInstruction
    var o = ObservableWWW.Get("http://unity3d.com/").ToYieldInstruction(throwOnError: false);
    yield return o;

    if (o.HasError) { Debug.Log(o.Error.ToString()); }
    if (o.HasResult) { Debug.Log(o.Result); }

    // other sample(wait until transform.position.y >= 100) 
    yield return this.transform.ObserveEveryValueChanged(x => x.position).FirstOrDefault(p => p.y >= 100).ToYieldInstruction();
}

Normally, we have to use callbacks when we require a coroutine to return a value. Observable.FromCoroutine can convert coroutines to cancellable IObservable[T] instead.

// public method
public static IObservable<string> GetWWW(string url)
{
    // convert coroutine to IObservable
    return Observable.FromCoroutine<string>((observer, cancellationToken) => GetWWWCore(url, observer, cancellationToken));
}

// IObserver is a callback publisher
// Note: IObserver's basic scheme is "OnNext* (OnError | Oncompleted)?" 
static IEnumerator GetWWWCore(string url, IObserver<string> observer, CancellationToken cancellationToken)
{
    var www = new UnityEngine.WWW(url);
    while (!www.isDone && !cancellationToken.IsCancellationRequested)
    {
        yield return null;
    }

    if (cancellationToken.IsCancellationRequested) yield break;

    if (www.error != null)
    {
        observer.OnError(new Exception(www.error));
    }
    else
    {
        observer.OnNext(www.text);
        observer.OnCompleted(); // IObserver needs OnCompleted after OnNext!
    }
}

Here are some more examples. Next is a multiple OnNext pattern.

public static IObservable<float> ToObservable(this UnityEngine.AsyncOperation asyncOperation)
{
    if (asyncOperation == null) throw new ArgumentNullException("asyncOperation");

    return Observable.FromCoroutine<float>((observer, cancellationToken) => RunAsyncOperation(asyncOperation, observer, cancellationToken));
}

static IEnumerator RunAsyncOperation(UnityEngine.AsyncOperation asyncOperation, IObserver<float> observer, CancellationToken cancellationToken)
{
    while (!asyncOperation.isDone && !cancellationToken.IsCancellationRequested)
    {
        observer.OnNext(asyncOperation.progress);
        yield return null;
    }
    if (!cancellationToken.IsCancellationRequested)
    {
        observer.OnNext(asyncOperation.progress); // push 100%
        observer.OnCompleted();
    }
}

// usecase
Application.LoadLevelAsync("testscene")
    .ToObservable()
    .Do(x => Debug.Log(x)) // output progress
    .Last() // last sequence is load completed
    .Subscribe();

Using for MultiThreading

// Observable.Start is start factory methods on specified scheduler
// default is on ThreadPool
var heavyMethod = Observable.Start(() =>
{
    // heavy method...
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1));
    return 10;
});

var heavyMethod2 = Observable.Start(() =>
{
    // heavy method...
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(3));
    return 10;
});

// Join and await two other thread values
Observable.WhenAll(heavyMethod, heavyMethod2)
    .ObserveOnMainThread() // return to main thread
    .Subscribe(xs =>
    {
        // Unity can't touch GameObject from other thread
        // but use ObserveOnMainThread, you can touch GameObject naturally.
        (GameObject.Find("myGuiText")).guiText.text = xs[0] + ":" + xs[1];
    }); 

DefaultScheduler

UniRx's default time based operations (Interval, Timer, Buffer(timeSpan), etc) use Scheduler.MainThread as their scheduler. That means most operators (except for Observable.Start) work on a single thread, so ObserverOn isn't needed and thread safety measures can be ignored. This is differet from the standard RxNet implementation but better suited to the Unity environment.

Scheduler.MainThread runs under Time.timeScale's influence. If you want to ignore the time scale, use Scheduler.MainThreadIgnoreTimeScale instead.

MonoBehaviour triggers

UniRx can handle MonoBehaviour events with UniRx.Triggers:

using UniRx;
using UniRx.Triggers; // need UniRx.Triggers namespace

public class MyComponent : MonoBehaviour
{
    void Start()
    {
        // Get the plain object
        var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);

        // Add ObservableXxxTrigger for handle MonoBehaviour's event as Observable
        cube.AddComponent<ObservableUpdateTrigger>()
            .UpdateAsObservable()
            .SampleFrame(30)
            .Subscribe(x => Debug.Log("cube"), () => Debug.Log("destroy"));

        // destroy after 3 second:)
        GameObject.Destroy(cube, 3f);
    }
}

Supported triggers are listed in UniRx.wiki#UniRx.Triggers.

These can also be handled more easily by directly subscribing to observables returned by extension methods on Component/GameObject. These methods inject ObservableTrigger automaticaly (except for ObservableEventTrigger and ObservableStateMachineTrigger):

using UniRx;
using UniRx.Triggers; // need UniRx.Triggers namespace for extend gameObejct

public class DragAndDropOnce : MonoBehaviour
{
    void Start()
    {
        // All events can subscribe by ***AsObservable
        this.OnMouseDownAsObservable()
            .SelectMany(_ => this.UpdateAsObservable())
            .TakeUntil(this.OnMouseUpAsObservable())
            .Select(_ => Input.mousePosition)
            .Subscribe(x => Debug.Log(x));
    }
}

Previous versions of UniRx provided ObservableMonoBehaviour. This is a legacy interface that is no longer supported. Please use UniRx.Triggers instead.

Creating custom triggers

Converting to Observable is the best way to handle Unity events. If the standard triggers supplied by UniRx are not enough, you can create custom triggers. To demonstrate, here's a LongTap trigger for uGUI:

public class ObservableLongPointerDownTrigger : ObservableTriggerBase, IPointerDownHandler, IPointerUpHandler
{
    public float IntervalSecond = 1f;

    Subject<Unit> onLongPointerDown;

    float? raiseTime;

    void Update()
    {
        if (raiseTime != null && raiseTime <= Time.realtimeSinceStartup)
        {
            if (onLongPointerDown != null) onLongPointerDown.OnNext(Unit.Default);
            raiseTime = null;
        }
    }

    void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
    {
        raiseTime = Time.realtimeSinceStartup + IntervalSecond;
    }

    void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
    {
        raiseTime = null;
    }

    public IObservable<Unit> OnLongPointerDownAsObservable()
    {
        return onLongPointerDown ?? (onLongPointerDown = new Subject<Unit>());
    }

    protected override void RaiseOnCompletedOnDestroy()
    {
        if (onLongPointerDown != null)
        {
            onLongPointerDown.OnCompleted();
        }
    }
}

It can be used as easily as the standard triggers:

var trigger = button.AddComponent<ObservableLongPointerDownTrigger>();

trigger.OnLongPointerDownAsObservable().Subscribe();

Observable Lifecycle Management

When is OnCompleted called? Subscription lifecycle management is very important to consider when using UniRx. ObservableTriggers call OnCompleted when the GameObject they are attached to is destroyed. Other static generator methods (Observable.Timer, Observable.EveryUpdate, etc...) do not stop automatically, and their subscriptions should be managed manually.

Rx provides some helper methods, such as IDisposable.AddTo which allows you to dispose of several subscriptions at once:

// CompositeDisposable is similar with List<IDisposable>, manage multiple IDisposable
CompositeDisposable disposables = new CompositeDisposable(); // field

void Start()
{
    Observable.EveryUpdate().Subscribe(x => Debug.Log(x)).AddTo(disposables);
}

void OnTriggerEnter(Collider other)
{
    // .Clear() => Dispose is called for all inner disposables, and the list is cleared.
    // .Dispose() => Dispose is called for all inner disposables, and Dispose is called immediately after additional Adds.
    disposables.Clear();
}

If you want to automatically Dispose when a GameObjects is destroyed, use AddTo(GameObject/Component):

void Start()
{
    Observable.IntervalFrame(30).Subscribe(x => Debug.Log(x)).AddTo(this);
}

AddTo calls facilitate automatic Dispose. If you needs special OnCompleted handling in the pipeline, however, use TakeWhile, TakeUntil, TakeUntilDestroy and TakeUntilDisable instead:

Observable.IntervalFrame(30).TakeUntilDisable(this)
    .Subscribe(x => Debug.Log(x), () => Debug.Log("completed!"));

If you handle events, Repeat is an important but dangerous method. It may cause an infinite loop, so handle with care:

using UniRx;
using UniRx.Triggers;

public class DangerousDragAndDrop : MonoBehaviour
{
    void Start()
    {
        this.gameObject.OnMouseDownAsObservable()
            .SelectMany(_ => this.gameObject.UpdateAsObservable())
            .TakeUntil(this.gameObject.OnMouseUpAsObservable())
            .Select(_ => Input.mousePosition)
            .Repeat() // dangerous!!! Repeat cause infinite repeat subscribe at GameObject was destroyed.(If in UnityEditor, Editor is freezed)
            .Subscribe(x => Debug.Log(x));
    }
}

UniRx provides an additional safe Repeat method. RepeatSafe: if contiguous "OnComplete" are called repeat stops. RepeatUntilDestroy(gameObject/component), RepeatUntilDisable(gameObject/component) allows to stop when a target GameObject has been destroyed:

this.gameObject.OnMouseDownAsObservable()
    .SelectMany(_ => this.gameObject.UpdateAsObservable())
    .TakeUntil(this.gameObject.OnMouseUpAsObservable())
    .Select(_ => Input.mousePosition)
    .RepeatUntilDestroy(this) // safety way
    .Subscribe(x => Debug.Log(x));            

UniRx gurantees hot observable(FromEvent/Subject/ReactiveProperty/UnityUI.AsObservable..., there are like event) have unhandled exception durability. What is it? If subscribe in subcribe, does not detach event.

button.OnClickAsObservable().Subscribe(_ =>
{
    // If throws error in inner subscribe, but doesn't detached OnClick event.
    ObservableWWW.Get("htttp://error/").Subscribe(x =>
    {
        Debug.Log(x);
    });
});

This behaviour is sometimes useful such as user event handling.

All class instances provide an ObserveEveryValueChanged method, which watches for changing values every frame:

// watch position change
this.transform.ObserveEveryValueChanged(x => x.position).Subscribe(x => Debug.Log(x));

It's very useful. If the watch target is a GameObject, it will stop observing when the target is destroyed, and call OnCompleted. If the watch target is a plain C# Object, OnCompleted will be called on GC.

Converting Unity callbacks to IObservables

Use Subject (or AsyncSubject for asynchronious operations):

public class LogCallback
{
    public string Condition;
    public string StackTrace;
    public UnityEngine.LogType LogType;
}

public static class LogHelper
{
    static Subject<LogCallback> subject;

    public static IObservable<LogCallback> LogCallbackAsObservable()
    {
        if (subject == null)
        {
            subject = new Subject<LogCallback>();

            // Publish to Subject in callback
            UnityEngine.Application.RegisterLogCallback((condition, stackTrace, type) =>
            {
                subject.OnNext(new LogCallback { Condition = condition, StackTrace = stackTrace, LogType = type });
            });
        }

        return subject.AsObservable();
    }
}

// method is separatable and composable
LogHelper.LogCallbackAsObservable()
    .Where(x => x.LogType == LogType.Warning)
    .Subscribe();

LogHelper.LogCallbackAsObservable()
    .Where(x => x.LogType == LogType.Error)
    .Subscribe();

In Unity5, Application.RegisterLogCallback was removed in favor of Application.logMessageReceived, so we can now simply use Observable.FromEvent.

public static IObservable<LogCallback> LogCallbackAsObservable()
{
    return Observable.FromEvent<Application.LogCallback, LogCallback>(
        h => (condition, stackTrace, type) => h(new LogCallback { Condition = condition, StackTrace = stackTrace, LogType = type }),
        h => Application.logMessageReceived += h, h => Application.logMessageReceived -= h);
}

Stream Logger

// using UniRx.Diagnostics;

// logger is threadsafe, define per class with name.
static readonly Logger logger = new Logger("Sample11");

// call once at applicationinit
public static void ApplicationInitialize()
{
    // Log as Stream, UniRx.Diagnostics.ObservableLogger.Listener is IObservable<LogEntry>
    // You can subscribe and output to any place.
    ObservableLogger.Listener.LogToUnityDebug();

    // for example, filter only Exception and upload to web.
    // (make custom sink(IObserver<EventEntry>) is better to use)
    ObservableLogger.Listener
        .Where(x => x.LogType == LogType.Exception)
        .Subscribe(x =>
        {
            // ObservableWWW.Post("", null).Subscribe();
        });
}

// Debug is write only DebugBuild.
logger.Debug("Debug Message");

// or other logging methods
logger.Log("Message");
logger.Exception(new Exception("test exception"));

Debugging

Debug operator in UniRx.Diagnostics namespace helps debugging.

// needs Diagnostics using
using UniRx.Diagnostics;

---

// [DebugDump, Normal]OnSubscribe
// [DebugDump, Normal]OnNext(1)
// [DebugDump, Normal]OnNext(10)
// [DebugDump, Normal]OnCompleted()
{
    var subject = new Subject<int>();

    subject.Debug("DebugDump, Normal").Subscribe();

    subject.OnNext(1);
    subject.OnNext(10);
    subject.OnCompleted();
}

// [DebugDump, Cancel]OnSubscribe
// [DebugDump, Cancel]OnNext(1)
// [DebugDump, Cancel]OnCancel
{
    var subject = new Subject<int>();

    var d = subject.Debug("DebugDump, Cancel").Subscribe();

    subject.OnNext(1);
    d.Dispose();
}

// [DebugDump, Error]OnSubscribe
// [DebugDump, Error]OnNext(1)
// [DebugDump, Error]OnError(System.Exception)
{
    var subject = new Subject<int>();

    subject.Debug("DebugDump, Error").Subscribe();

    subject.OnNext(1);
    subject.OnError(new Exception());
}

shows sequence element on OnNext, OnError, OnCompleted, OnCancel, OnSubscribe timing to Debug.Log. It enables only #if DEBUG.

Unity-specific Extra Gems

// Unity's singleton UiThread Queue Scheduler
Scheduler.MainThreadScheduler 
ObserveOnMainThread()/SubscribeOnMainThread()

// Global StartCoroutine runner
MainThreadDispatcher.StartCoroutine(enumerator)

// convert Coroutine to IObservable
Observable.FromCoroutine((observer, token) => enumerator(observer, token)); 

// convert IObservable to Coroutine
yield return Observable.Range(1, 10).ToYieldInstruction(); // after Unity 5.3, before can use StartAsCoroutine()

// Lifetime hooks
Observable.EveryApplicationPause();
Observable.EveryApplicationFocus();
Observable.OnceApplicationQuit();

Framecount-based time operators

UniRx provides a few framecount-based time operators:

Method
EveryUpdate
EveryFixedUpdate
EveryEndOfFrame
EveryGameObjectUpdate
EveryLateUpdate
ObserveOnMainThread
NextFrame
IntervalFrame
TimerFrame
DelayFrame
SampleFrame
ThrottleFrame
ThrottleFirstFrame
TimeoutFrame
DelayFrameSubscription
FrameInterval
FrameTimeInterval
BatchFrame

For example, delayed invoke once:

Observable.TimerFrame(100).Subscribe(_ => Debug.Log("after 100 frame"));

Every* Method's execution order is

EveryGameObjectUpdate(in MainThreadDispatcher's Execution Order) ->
EveryUpdate -> 
EveryLateUpdate -> 
EveryEndOfFrame

EveryGameObjectUpdate invoke from same frame if caller is called before MainThreadDispatcher.Update(I recommend MainThreadDispatcher called first than others(ScriptExecutionOrder makes -32000)
EveryLateUpdate, EveryEndOfFrame invoke from same frame.
EveryUpdate, invoke from next frame.

MicroCoroutine

MicroCoroutine is memory efficient and fast coroutine worker. This implemantation is based on Unity blog's 10000 UPDATE() CALLS, avoid managed-unmanaged overhead so gets 10x faster iteration. MicroCoroutine is automaticaly used on Framecount-based time operators and ObserveEveryValueChanged.

If you want to use MicroCoroutine instead of standard unity coroutine, use MainThreadDispatcher.StartUpdateMicroCoroutine or Observable.FromMicroCoroutine.

int counter;

IEnumerator Worker()
{
    while(true)
    {
        counter++;
        yield return null;
    }
}

void Start()
{
    for(var i = 0; i < 10000; i++)
    {
        // fast, memory efficient
        MainThreadDispatcher.StartUpdateMicroCoroutine(Worker());

        // slow...
        // StartCoroutine(Worker());
    }
}

image

MicroCoroutine's limitation, only supports yield return null and update timing is determined start method(StartUpdateMicroCoroutine, StartFixedUpdateMicroCoroutine, StartEndOfFrameMicroCoroutine).

If you combine with other IObservable, you can check completed property like isDone.

IEnumerator MicroCoroutineWithToYieldInstruction()
{
    var www = ObservableWWW.Get("http://aaa").ToYieldInstruction();
    while (!www.IsDone)
    {
        yield return null;
    }

    if (www.HasResult)
    {
        UnityEngine.Debug.Log(www.Result);
    }
}

uGUI Integration

UniRx can handle UnityEvents easily. Use UnityEvent.AsObservable to subscribe to events:

public Button MyButton;
// ---
MyButton.onClick.AsObservable().Subscribe(_ => Debug.Log("clicked"));

Treating Events as Observables enables declarative UI programming.

public Toggle MyToggle;
public InputField MyInput;
public Text MyText;
public Slider MySlider;

// On Start, you can write reactive rules for declaretive/reactive ui programming
void Start()
{
    // Toggle, Input etc as Observable (OnValueChangedAsObservable is a helper providing isOn value on subscribe)
    // SubscribeToInteractable is an Extension Method, same as .interactable = x)
    MyToggle.OnValueChangedAsObservable().SubscribeToInteractable(MyButton);
    
    // Input is displayed after a 1 second delay
    MyInput.OnValueChangedAsObservable()
        .Where(x => x != null)
        .Delay(TimeSpan.FromSeconds(1))
        .SubscribeToText(MyText); // SubscribeToText is helper for subscribe to text
    
    // Converting for human readability
    MySlider.OnValueChangedAsObservable()
        .SubscribeToText(MyText, x => Math.Round(x, 2).ToString());
}

For more on reactive UI programming please consult Sample12, Sample13 and the ReactiveProperty section below.

ReactiveProperty, ReactiveCollection

Game data often requires notification. Should we use properties and events (callbacks)? That's often too complex. UniRx provides ReactiveProperty, a lightweight property broker.

// Reactive Notification Model
public class Enemy
{
    public ReactiveProperty<long> CurrentHp { get; private set; }

    public ReactiveProperty<bool> IsDead { get; private set; }

    public Enemy(int initialHp)
    {
        // Declarative Property
        CurrentHp = new ReactiveProperty<long>(initialHp);
        IsDead = CurrentHp.Select(x => x <= 0).ToReactiveProperty();
    }
}

// ---
// onclick, HP decrement
MyButton.OnClickAsObservable().Subscribe(_ => enemy.CurrentHp.Value -= 99);
// subscribe from notification model.
enemy.CurrentHp.SubscribeToText(MyText);
enemy.IsDead.Where(isDead => isDead == true)
    .Subscribe(_ =>
    {
        MyButton.interactable = false;
    });

You can combine ReactiveProperties, ReactiveCollections and observables returned by UnityEvent.AsObservable. All UI elements are observable.

Generic ReactiveProperties are not serializable or inspecatble in the Unity editor, but UniRx provides specialized subclasses of ReactiveProperty that are. These include classes such as Int/LongReactiveProperty, Float/DoubleReactiveProperty, StringReactiveProperty, BoolReactiveProperty and more (Browse them here: InspectableReactiveProperty.cs). All are fully editable in the inspector. For custom Enum ReactiveProperty, it's easy to write a custom inspectable ReactiveProperty[T].

If you needs [Multiline] or [Range] attach to ReactiveProperty, you can use MultilineReactivePropertyAttribute and RangeReactivePropertyAttribute instead of Multiline and Range.

The provided derived InpsectableReactiveProperties are displayed in the inspector naturally and notify when their value is changed even when it is changed in the inspector.

This functionality is provided by InspectorDisplayDrawer. You can supply your own custom specialized ReactiveProperties by inheriting from it:

public enum Fruit
{
    Apple, Grape
}

[Serializable]
public class FruitReactiveProperty : ReactiveProperty<Fruit>
{
    public FruitReactiveProperty()
    {
    }

    public FruitReactiveProperty(Fruit initialValue)
        :base(initialValue)
    {
    }
}

[UnityEditor.CustomPropertyDrawer(typeof(FruitReactiveProperty))]
[UnityEditor.CustomPropertyDrawer(typeof(YourSpecializedReactiveProperty2))] // and others...
public class ExtendInspectorDisplayDrawer : InspectorDisplayDrawer
{
}

If a ReactiveProperty value is only updated within a stream, you can make it read only by using from ReadOnlyReactiveProperty.

public class Person
{
    public ReactiveProperty<string> GivenName { get; private set; }
    public ReactiveProperty<string> FamilyName { get; private set; }
    public ReadOnlyReactiveProperty<string> FullName { get; private set; }

    public Person(string givenName, string familyName)
    {
        GivenName = new ReactiveProperty<string>(givenName);
        FamilyName = new ReactiveProperty<string>(familyName);
        // If change the givenName or familyName, notify with fullName!
        FullName = GivenName.CombineLatest(FamilyName, (x, y) => x + " " + y).ToReadOnlyReactiveProperty();
    }
}

Model-View-(Reactive)Presenter Pattern

UniRx makes it possible to implement the MVP(MVRP) Pattern.

Why should we use MVP instead of MVVM? Unity doesn't provide a UI binding mechanism and creating a binding layer is too complex and loss and affects performance. Still, Views need updating. Presenters are aware of their view's components and can update them. Although there is no real binding, Observables enables subscription to notification, which can act much like the real thing. This pattern is called a Reactive Presenter:

// Presenter for scene(canvas) root.
public class ReactivePresenter : MonoBehaviour
{
    // Presenter is aware of its View (binded in the inspector)
    public Button MyButton;
    public Toggle MyToggle;
    
    // State-Change-Events from Model by ReactiveProperty
    Enemy enemy = new Enemy(1000);

    void Start()
    {
        // Rx supplies user events from Views and Models in a reactive manner 
        MyButton.OnClickAsObservable().Subscribe(_ => enemy.CurrentHp.Value -= 99);
        MyToggle.OnValueChangedAsObservable().SubscribeToInteractable(MyButton);

        // Models notify Presenters via Rx, and Presenters update their views
        enemy.CurrentHp.SubscribeToText(MyText);
        enemy.IsDead.Where(isDead => isDead == true)
            .Subscribe(_ =>
            {
                MyToggle.interactable = MyButton.interactable = false;
            });
    }
}

// The Model. All property notify when their values change
public class Enemy
{
    public ReactiveProperty<long> CurrentHp { get; private set; }

    public ReactiveProperty<bool> IsDead { get; private set; }

    public Enemy(int initialHp)
    {
        // Declarative Property
        CurrentHp = new ReactiveProperty<long>(initialHp);
        IsDead = CurrentHp.Select(x => x <= 0).ToReactiveProperty();
    }
}

A View is a scene, that is a Unity hierarchy. Views are associated with Presenters by the Unity Engine on initialize. The XxxAsObservable methods make creating event signals simple, without any overhead. SubscribeToText and SubscribeToInteractable are simple binding-like helpers. These may be simple tools, but they are very powerful. They feel natural in the Unity environment and provide high performance and a clean architecture.

V -> RP -> M -> RP -> V completely connected in a reactive way. UniRx provides all of the adaptor methods and classes, but other MVVM(or MV*) frameworks can be used instead. UniRx/ReactiveProperty is only simple toolkit.

GUI programming also benefits from ObservableTriggers. ObservableTriggers convert Unity events to Observables, so the MV(R)P pattern can be composed using them. For example, ObservableEventTrigger converts uGUI events to Observable:

var eventTrigger = this.gameObject.AddComponent<ObservableEventTrigger>();
eventTrigger.OnBeginDragAsObservable()
    .SelectMany(_ => eventTrigger.OnDragAsObservable(), (start, current) => UniRx.Tuple.Create(start, current))
    .TakeUntil(eventTrigger.OnEndDragAsObservable())
    .RepeatUntilDestroy(this)
    .Subscribe(x => Debug.Log(x));

(Obsolete)PresenterBase

Note: PresenterBase works enough, but too complex.
You can use simple Initialize method and call parent to child, it works for most scenario.
So I don't recommend using PresenterBase, sorry.

ReactiveCommand, AsyncReactiveCommand

ReactiveCommand abstraction of button command with boolean interactable.

public class Player
{		
   public ReactiveProperty<int> Hp;		
   public ReactiveCommand Resurrect;		
		
   public Player()
   {		
        Hp = new ReactiveProperty<int>(1000);		
        		
        // If dead, can not execute.		
        Resurrect = Hp.Select(x => x <= 0).ToReactiveCommand();		
        // Execute when clicked		
        Resurrect.Subscribe(_ =>		
        {		
             Hp.Value = 1000;		
        }); 		
    }		
}		
		
public class Presenter : MonoBehaviour		
{		
    public Button resurrectButton;		
		
    Player player;		
		
    void Start()
    {		
      player = new Player();		
		
      // If Hp <= 0, can't press button.		
      player.Resurrect.BindTo(resurrectButton);		
    }		
}		

AsyncReactiveCommand is a variation of ReactiveCommand that CanExecute(in many cases bind to button's interactable) is changed to false until asynchronous execution was finished.

public class Presenter : MonoBehaviour		
{		
    public UnityEngine.UI.Button button;		
		
    void Start()
    {		
        var command = new AsyncReactiveCommand();		
		
        command.Subscribe(_ =>		
        {		
            // heavy, heavy, heavy method....		
            return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();		
        });		
		
        // after clicked, button shows disable for 3 seconds		
        command.BindTo(button);		
		
        // Note:shortcut extension, bind aync onclick directly		
        button.BindToOnClick(_ =>		
        {		
            return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();		
        });		
    }		
}		

AsyncReactiveCommand has three constructor.

  • () - CanExecute is changed to false until async execution finished
  • (IObservable<bool> canExecuteSource) - Mixed with empty, CanExecute becomes true when canExecuteSource send to true and does not executing
  • (IReactiveProperty<bool> sharedCanExecute) - share execution status between multiple AsyncReactiveCommands, if one AsyncReactiveCommand is executing, other AsyncReactiveCommands(with same sharedCanExecute property) becomes CanExecute false until async execution finished
public class Presenter : MonoBehaviour
{
    public UnityEngine.UI.Button button1;
    public UnityEngine.UI.Button button2;

    void Start()
    {
        // share canExecute status.
        // when clicked button1, button1 and button2 was disabled for 3 seconds.

        var sharedCanExecute = new ReactiveProperty<bool>();

        button1.BindToOnClick(sharedCanExecute, _ =>
        {
            return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();
        });

        button2.BindToOnClick(sharedCanExecute, _ =>
        {
            return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();
        });
    }
}

MessageBroker, AsyncMessageBroker

MessageBroker is Rx based in-memory pubsub system filtered by type.

public class TestArgs
{
    public int Value { get; set; }
}

---

// Subscribe message on global-scope.
MessageBroker.Default.Receive<TestArgs>().Subscribe(x => UnityEngine.Debug.Log(x));

// Publish message
MessageBroker.Default.Publish(new TestArgs { Value = 1000 });

AsyncMessageBroker is variation of MessageBroker, can await Publish call.

AsyncMessageBroker.Default.Subscribe<TestArgs>(x =>
{
    // show after 3 seconds.
    return Observable.Timer(TimeSpan.FromSeconds(3))
        .ForEachAsync(_ =>
        {
            UnityEngine.Debug.Log(x);
        });
});

AsyncMessageBroker.Default.PublishAsync(new TestArgs { Value = 3000 })
    .Subscribe(_ =>
    {
        UnityEngine.Debug.Log("called all subscriber completed");
    });

UniRx.Toolkit

UniRx.Toolkit includes serveral Rx-ish tools. Currently includes ObjectPool and AsyncObjectPool. It can Rent, Return and PreloadAsync for fill pool before rent operation.

// sample class
public class Foobar : MonoBehaviour
{
    public IObservable<Unit> ActionAsync()
    {
        // heavy, heavy, action...
        return Observable.Timer(TimeSpan.FromSeconds(3)).AsUnitObservable();
    }
}

public class FoobarPool : ObjectPool<Foobar>
{
    readonly Foobar prefab;
    readonly Transform hierarchyParent;

    public FoobarPool(Foobar prefab, Transform hierarchyParent)
    {
        this.prefab = prefab;
        this.hierarchyParent = hierarchyParent;
    }

    protected override Foobar CreateInstance()
    {
        var foobar = GameObject.Instantiate<Foobar>(prefab);
        foobar.transform.SetParent(hierarchyParent);

        return foobar;
    }

    // You can overload OnBeforeRent, OnBeforeReturn, OnClear for customize action.
    // In default, OnBeforeRent = SetActive(true), OnBeforeReturn = SetActive(false)

    // protected override void OnBeforeRent(Foobar instance)
    // protected override void OnBeforeReturn(Foobar instance)
    // protected override void OnClear(Foobar instance)
}

public class Presenter : MonoBehaviour
{
    FoobarPool pool = null;

    public Foobar prefab;
    public Button rentButton;

    void Start()
    {
        pool = new FoobarPool(prefab, this.transform);

        rentButton.OnClickAsObservable().Subscribe(_ =>
        {
            var foobar = pool.Rent();
            foobar.ActionAsync().Subscribe(__ =>
            {
                // if action completed, return to pool
                pool.Return(foobar);
            });
        });
    }
}

Visual Studio Analyzer

For Visual Studio 2015 users, a custom analyzer, UniRxAnalyzer, is provided. It can, for example, detect when streams aren't subscribed to.

ObservableWWW doesn't fire until it's subscribed to, so the analyzer warns about incorrect usage. It can be downloaded from NuGet.

Please submit new analyzer ideas on GitHub Issues!

Samples

See UniRx/Examples

The samples demonstrate how to do resource management (Sample09_EventHandling), what is the MainThreadDispatcher, among other things.

Windows Store/Phone App (NETFX_CORE)

Some interfaces, such as UniRx.IObservable<T> and System.IObservable<T>, cause conflicts when submitting to the Windows Store App. Therefore, when using NETFX_CORE, please refrain from using such constructs as UniRx.IObservable<T> and refer to the UniRx components by their short name, without adding the namespace. This solves the conflicts.

DLL Separation

If you want to pre-build UniRx, you can build own dll. clone project and open UniRx.sln, you can see UniRx, it is fullset separated project of UniRx. You should define compile symbol like UNITY;UNITY_5_4_OR_NEWER;UNITY_5_4_0;UNITY_5_4;UNITY_5; + UNITY_EDITOR, UNITY_IPHONE or other platform symbol. We can not provides pre-build binary to release page, asset store because compile symbol is different each other.

UPM Package

After Unity 2019.3.4f1, Unity 2020.1a21, that support path query parameter of git package. You can add https://github.com/neuecc/UniRx.git?path=Assets/Plugins/UniRx/Scripts to Package Manager

or add "com.neuecc.unirx": "https://github.com/neuecc/UniRx.git?path=Assets/Plugins/UniRx/Scripts" to Packages/manifest.json.

Reference

UniRx API documents.

The home of ReactiveX. Introduction, All operators are illustrated with graphical marble diagrams, there makes easy to understand. And UniRx is official ReactiveX Languages.

A great online tutorial and eBook.

Many videos, slides and documents for Rx.NET.

Intro slide by @torisoup

Intro slide and sample game by @Xerios

How to integrate with PlayFab API

Help & Contribute

Support thread on the Unity forum. Ask me any question - http://forum.unity3d.com/threads/248535-UniRx-Reactive-Extensions-for-Unity

We welcome any contributions, be they bug reports, requests or pull request.
Please consult and submit your reports or requests on GitHub issues.
Source code is available in Assets/Plugins/UniRx/Scripts.

Author's other Unity + LINQ Assets

LINQ to GameObject is a group of GameObject extensions for Unity that allows traversing the hierarchy and appending GameObject to it like LINQ to XML. It's free and opensource on GitHub.

Author Info

Yoshifumi Kawai(a.k.a. neuecc) is a software developer in Japan.
Currently founded consulting company New World, Inc.
He is awarding Microsoft MVP for Visual C# since 2011.

Blog: https://medium.com/@neuecc (English)
Blog: http://neue.cc/ (Japanese)
Twitter: https://twitter.com/neuecc (Japanese)

License

This library is under the MIT License.

Some code is borrowed from Rx.NET and mono/mcs.

unirx's People

Contributors

5argon avatar badoet avatar bezarius avatar denizpiri avatar deryew avatar etic avatar faidra avatar gableroux avatar grabacr07 avatar j6lim avatar jimmylovesiren avatar joshua-light avatar leonakasaka avatar liambaloh avatar micahosborne avatar mikanbako avatar movrajr avatar neuecc avatar orenro avatar pomle avatar ppcuni avatar rdeluxe avatar sbergen avatar sheepbeo avatar shiena avatar shiwano avatar silphid avatar skybladev2 avatar svermeulen avatar torisoup avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unirx's Issues

Using with Linq?

I'm having trouble using UniRx with Linq. For example:

using UniRx;
using System.Linq;

public class Distinct {
  private Subject<int> subject;

  Distinct() {
    subject = new Subject<int>();
    subject.Distinct().Subscribe();
  }
}

I get this compile error:

System.Linq.Enumerable.Distinct<TSource>(this System.Collections.Generic.IEnumerable<TSource>)' cannot be inferred from the usage. Try specifying the type arguments explicitly

Am I doing something wrong?

Asset Bundle support?

is there support for asset bundle?
or will it works out of the box with the current unirx library?

Timeout may not trigger

I use the Timeout method like
SomeObservable.Timeout(TimeSpan.FromSeconds(5)).Subscribe( ... ) but it didnot work,
I debug the source code, found this line if (scheduler.Now - beforeTime >= dueTime) not success.
That is (scheduler.Now - beforeTime) is 4.99, not greater than my value (5).
Is this a bug?
PS: UniRx is awesome

Can't load solution

Fails loading UnityVS.UniRx.CSharp in VS 2013 and 2012, and everything fails in 2010. In 12/13 I can load UnityVS.UniRx but it has missing references to System.Reactive.Core, Interfaces, Linq, PlatformService and UnityVS.UniRx.CSharp.

ObservableEventTrigger blocks events that aren't being observed

When attaching an ObservableEventTrigger to a gameobject, it will block events that aren't being observed from being passed down to other objects under the mouse, farther down in the Raycaster list.

This is particularly problematic when you want to place, for example, clickable objects over a scroll rect's content object. Even though we don't actively subscribe to the scrolling events, they are being blocked from the scroll rect itself and so the user can't scroll when the mouse or finger is over the clickable object.

There seem to be no solution other than breaking up ObservableEventTrigger into individual classes for each trigger.

ReactiveCollections - The name 'Items' does not exist in the current context

When my Unity build target is set to Web Player I see the following error being thrown by UniRx:

CompilerError: The name `Items' does not exist in the current context

StackTrace:
Compiler CompilerError
    Assets/UniRx/Scripts/UnityEngineBridge/ReactiveCollection.cs: line 123 column 25

It appears that the Web Player build target is using a modified version of Connection<T> that does not have the Items property. My temporary workaround is to surround the ReactiveCollection<T> and ReactiveCollectionExtensions classes with #if !UNITY_WEBPLAYER.

Unity 5.0.0f4, UniRx master branch.

Observable still runs once before truly disposed

I'm not sure if this is a bug or not, but I did something like this:

        IDisposable dis = Observable.EveryFixedUpdate().Subscribe(l =>
            {
                Debug.Log("Hello world");
            }
        );

And now console is logging "Hello World" continuously. Perfect. Now I dispose it:

        dis.Dispose();
        Debug.Log("Disposed");

The console log is now a bit weird:

...
Hello world
Hello world
Disposed
Hello world

Which means even though I disposed it, it still runs the task once before it truly disposes. Is this a bug or intended?

Windows Phone/Store: Ambiguous reference

Some people have reported "ambiguous reference between 'System.IObserver<UniRx.Unit>' and 'UniRx.IObserver<UniRx.Unit>". That happens when UniRx is compiled with the Microsoft compiler, ie.:

  • IObserver is used without UniRx namespace
  • build platform is set to Windows Phone or Windows Store
  • UniRx is not located in “Assets/Plugins”, “Assets/Standard Assets” and “Assets/Pro Standard Assets”

http://docs.unity3d.com/Manual/wp8-1-faq.html

I think this can be fixed by:

  • removing UniRx.IObserver from Observer.cs

  • creating a new file called IObserver.cs

  • put this in the file

    if !NETFX_CORE

    using System;

    namespace System
    {
    public interface IObserver
    {
    void OnNext(T value);
    void OnError(Exception error);
    void OnCompleted();
    }
    }

    endif

Drawback: any code code that referred to the interface with its fully qualified name "UniRx.IObserver" will break.

Missing Amb method

Greetings!
I'm learning Rx using your asset and by reading the book introtorx.com and noticed there're some basic extension methods are missing (particularly Amb) and I need it in my multitouch gestures recognition.
Do you plan to implement it?
Or could I express Amb using methods that you've already ported?

Fix ObservableWWW Sample

Debug.Log and too long string causes error.
There need cut long string.

var parallel = Observable.WhenAll(
    ObservableWWW.Get("http://google.com/"),
    ObservableWWW.Get("http://www.bing.com/"),
    ObservableWWW.Get("http://unity3d.com/"));

parallel.Subscribe(xs =>
{
    Debug.Log(xs[0].Substring(0, 100)); // google
    Debug.Log(xs[1].Substring(0, 100)); // bing
    Debug.Log(xs[2].Substring(0, 100)); // unity
});

OSX?

Is there anything to be wary of on OSX. I noticed it's not listed in the supported platforms. Thanks.

Application.isPlaying causing exception

Hello!

This is my code:

 //This code is invoked OUTSIDE OF MAIN THREAD            
 Scheduler.MainThread.Schedule(() =>
 {
       inputfield.text = "blbalbla"
 });

Issue is coming from:
MainThreadDispatcher.cs : 164

When I schedule actions from another thread to the Main Thread this line

 #if UNITY_EDITOR
             if (!Application.isPlaying) 
             { EditorThreadDispatcher.Instance.Enqueue(action); return; }
 #endif

causes exception
get_isPlaying cannot can only be called on the Main Thread.

Because Applicationn.isPlay is invoked from another thread.
Ofcourse, in the build it works as expected without exceptions

I wonder if it will be a good fix, to just remove this check

if (!Application.isPlaying)

And let everything work inside EditorThread while in UNITY_EDITOR?

Unity warns about OnMouse_ event handlers on Android build

Unity warns OnMouse_ event handlers on Android build. On Unity 4.6.2f1, the following warning is on its console when Unity builds Android application.

Game scripts or other custom code contains OnMouse_ event handlers. Presence of such handlers might impact performance on handheld devices.
UnityEditor.BuildPlayerWindow:BuildPlayerAndRun()

I will create a pull request.

Unity Windows Phone build error on using observableWWW

on the windows phone build will encounter error
when calling multiple observableWWW
the error message is something like must check for isDone property and yield
i think the yield is referring to the normal coroutine usage when doing http calls

is there some details that i missed out to make it work on windows phone?
or its just an error that no one encountered yet?

Blank compile error

Whenever I bring in UniRx from the Asset Store, I would get a blank compile error, that is a red cross without any information. This happens on a fresh install of Unity 5.0.1f1. Sorry I cannot give more info, but Unity is just not spitting it out. Can't tell for sure this is related to UniRx as the code seems fine and compiles without problems in MonoDevelop.

the new unirx PostWWW is giving error

in unity 4.6, this line : var contentHeaders = (Hash)(object)content.headers;
in the ObservableWWW.cs is throwing casting error

i need to change it to something like this to get it to work :
Hash contentHeaders = new Hash();
if(content.headers != null){
foreach (DictionaryEntry pair in content.headers)
{
contentHeaders[pair.Key.ToString()] = pair.Value.ToString();
}
}

is this intended? or did i do something wrong?

Windows phone 8.1 build error

Hello. I am trying to build this library to windows phone 8.1 and i have error "Assets\Libraries\UniRx\Scripts\UnityEngineBridge\Triggers\ObservableTriggerExtensions.cs(111,23): error CS0246: The type or namespace name 'IObservable' could not be found (are you missing a using directive or an assembly reference?)"
How can i solve it?

Added: I had removed directive from IObservable and builded project. But in line
Observable.Start(Foo1()).ObserveOnMainThread().Subscribe(un =>Foo2(un);}
I have got new error (runtime error).

Exception: Specified method is not supported.
Type: System.NotSupportedException
Module: mscorlib
InnerException:
AdditionalInfo:
at System.Action.BeginInvoke(AsyncCallback callback, Object object)
at UniRx.Scheduler.ThreadPoolScheduler.Schedule(Action action)
at UniRx.Observable.<>c__DisplayClass166.b__164()
at UniRx.Observable.Start(Action action, IScheduler scheduler)
at UniRx.Observable.Start(Action action)

Implements to 4/19

Memo - Methods

Scheduler

EventLoop
NewThread
VirtualTimeScheduler
HistoricalScheduler

Disposable

RefCountDisposable
ScheduledDisposable

Subject

BehaviorSubject
ReplaySubject

LINQ-Generation

Empty
Never
If
Generate
FromAsync
FromAsyncPattern
FromEvent
FromEventPattern
For
Case
Interval
Range
Repeat
Return
Start
Throw
Timer
Using
While

LINQ

Aggregate
All
Amb
And/Then/When
Any
AsObservable
Average
Buffer
Cast
Catch
Chunkify
Collect
CombineLatest
Concat
Contains
Count
Create
DefaultIfEmpty
Defer
Delay
DelaySubscription
Dematerialize
Distinct
DistinctUntilChanged
Do
DoWhile
ElementAt
ElementAtOrDefault
First
FirstOrDefault
ForEach
GroupBy
GetEnumerator
GroupByUntil
GroupJoin
IgnoreElements
IsEmpty
Join
Last
LastOrDefault
Latest
LongCount
Materialize
Max
MaxBy
Merge
Min
MinBy
MostRecent
Multicast
Next
ObserveOn
OfType
OnErrorResumeNext
Publish
PublishLast
RefCount
Replay
Retry
Sample
Scan
Select
SelectMany
SequenceEqual
Single
SingleOrDefault
Skip
SkipLast
SkipUntil
SkipWhile
StartWith
Subscribe
SubscribeOn
Sum
Switch
Synchronize
Take
TakeLast
TakeLastBuffer
TakeUntil
TakeWhile
Throttle
TimeInterval
Timeout
Timestamp
ToArray
ToAsync
ToDictionary
ToEvent
ToEventPattern
ToList
ToLookup
ToObservable
Wait
Where
Window
Zip

LINQ.ErrorHandling

Finally

Observable.Interval is affected by Time.timescale

how to use the Scheduler.MainThreadIgnoreTimeScale
so that my timer not get affected by the game pause.

because i will need to sync the game time with my server time.
so the time cannot be paused when the game is paused.

thx

ObservableWWW with accept-encoding gzip

tried to do the following :

    #if !(UNITY_METRO || UNITY_WP8)
        using Hash = System.Collections.Hashtable;
    #else
        using Hash = System.Collections.Generic.Dictionary<string, string>;
    #endif

    using Ionic.Zlib; // from http://dotnetzip.codeplex.com/

    Hash headers = new Hash();
    headers["Accept-Encoding"] = "gzip";

    ObservableWWW.Get(AppURL , headers)
     .Subscribe(x => {
         byte[] decompressed = Ionic.Zlib.GZipStream.UncompressBuffer(GetBytes(x));
         xmlText = System.Text.ASCIIEncoding.ASCII.GetString(decompressed);
         UnityEngine.Debug.Log(xmlText);
    }

    public byte[] GetBytes(string str)
    {
        byte[] bytes = new byte[str.Length * sizeof(char)];
        System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
        return bytes;
    }

I get a bad gzip header from the decompression process - i speculate that maybe there r some modification done during the return from the ObservableWWW??
maybe we can make the ObservableWWW to detect if the response is in gzip format, it will automatically decompress it and return as string.

plz convert structs to classes to prevent JIT exception on iOS

First of all, you did so much great job. :)

With Unity 4.5.0/4.5.1 I've recently been faced AOT/JIT issue on iOS. And finally I found out that value type may causes the problem, despite of AOT improvement in Unity4.5.

public interface IFoo
{
    void bar<T>(T value);
}

public class Foo : IFoo
{
    public void bar<T>(T value)
    {
    }
}

IFoo foo = new Foo();
foo.bar(UniRx.Unit.Default);  // JIT exception
foo.bar(1);                   // JIT exception
foo.bar("abc");               // no JIT exception

Foo foo2 = new Foo();
foo2.bar(UniRx.Unit.Default);  // no JIT exception

To raise JIT exception it needs to fulfill all of the following conditions:

  1. invoke C# generic method
  2. do it via variable of interface(not via of concrete class)
  3. resolve the generic type with value type

So, struct types in UniRx like Unit may scratch the bug.
And so now I'm here to suggest struct -> class replacement. m(~)m.

Compile errors on Unity5.0.0.f4

Hello.

I got compile errors on Unity5.0.0.f4, in both examples as well in other places where onButtonClick is used.


Assets/UniRx/Scripts/UnityEngineBridge/UnityUIComponentExtensions.cs(35,27): error CS1061: Type Button' does not contain a definition foronClick' and no extension method onClick' of typeButton' could be found (are you missing a using directive or an assembly reference?)


I just imported the package, its same in the package from asset store and from github directly.

is it possible to create a simple settimeout function?

IDisposable timeoutAction= null;
timeoutAction= Observable.Interval(TimeSpan.FromMilliseconds(1000))
                     .Subscribe(
            x =>
            {
                timeoutAction.Dispose(); // dispose the observable so it will only run one time...
                // perform timeout action
            });

currently im doing it in a super round about way...

How to Unit Test a method with a callback that is an argument?

We are trying to unit test this method. See unit test below

public void Login(String userName, String password, Action successCallback, Action errorCallback) {
try {
ObservableWWW.PostAndGetBytes(OAuthServiceUrl + "/token",
Encoding.UTF8.GetBytes("grant_type=password&username=" + userName + "&password=" + password))
.Subscribe(x => {
try {
String response = Encoding.UTF8.GetString(x);
var message = JsonConvert.DeserializeObject(response);
var obj = HtAccessTokenMapper.Map(message);
successCallback(obj);
} catch (Exception ex) {
errorCallback(ex);
}
}, errorCallback);
} catch (Exception ex) {
errorCallback(ex);
}
}

[TestFixture]
public class SecurityServiceTest {

This is our test method. No matter what we've tried, when running this test using Unity Unit Test Runner, it always fails because the Assert runs (after waiting 10 seconds) and then the method code runs.

How can we test the above method?

Thank you,

Karl

[Test]
public void Login_IntegrationTest() {
    // arrange
    var service = new SecurityService();
    String token = String.Empty;
    Exception exception = null;
    // act
    service.Login("user", "password", r => {
        token = r.AccessToken;
    }, ex => {
        exception = ex;
    });

    // assert

    Assert.That(token.Length > 0, Is.True.After(10000));

}

}

LogCallbackAsObservable

public class LogCallback
{
    public string Condition { get; private set; }
    public string StackTrace { get; private set; }
    public UnityEngine.LogType LogType { get; private set; }

    public LogCallback(string condition, string stackTrace, UnityEngine.LogType logType)
    {
        Condition = condition;
        StackTrace = stackTrace;
        LogType = logType;
    }
}

public static IObservable<LogCallback> LogCallbackAsObservable()
{
    var subject = new Subject<LogCallback>();

    UnityEngine.Application.RegisterLogCallback((condition, stackTrace, type) =>
    {
        subject.OnNext(new LogCallback(condition, stackTrace, type));
    });

    return subject;
}

Retry with Strategy

ExponentialBackoff and other retry strategy for Observable.Retry.

public interface IRetryStrategy
{
    TimeSpan GetNextDelay();
}

public class LinearRetry : IRetryStrategy
{
    readonly TimeSpan deltaBackoff;
    public LinearRetry(TimeSpan deltaBackoff)
    {
        this.deltaBackoff = deltaBackoff;
    }

    public TimeSpan GetNextDelay()
    {
        return deltaBackoff;
    }
}

public class ImmediateRetry : IRetryStrategy
{
    public TimeSpan GetNextDelay()
    {
        return TimeSpan.Zero;
    }
}

public class ExponentialBackoffRetry : IRetryStrategy
{
    readonly Random random = new Random();
    readonly double minBackoffMilliseconds;
    readonly double maxBackoffMilliseconds;
    readonly double deltaBackoffMilliseconds;

    int currentPower;

    public ExponentialBackoffRetry(TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff)
    {
        this.minBackoffMilliseconds = minBackoff.TotalMilliseconds;
        this.maxBackoffMilliseconds = maxBackoff.TotalMilliseconds;
        this.deltaBackoffMilliseconds = deltaBackoff.TotalMilliseconds;
    }

    public TimeSpan GetNextDelay()
    {
        int delta = (int)((Math.Pow(2.0, this.currentPower) - 1.0) * random.Next((int)(this.deltaBackoffMilliseconds * 0.8), (int)(this.deltaBackoffMilliseconds * 1.2)));
        int interval = (int)Math.Min(checked(this.minBackoffMilliseconds + delta), this.maxBackoffMilliseconds);

        if (interval < this.maxBackoffMilliseconds)
        {
            this.currentPower++;
        }

        return TimeSpan.FromMilliseconds(interval);
    }

    public void Reset()
    {
        this.currentPower = 0;
    }
}

Add Observable.Window

It doesn't seem like Observable.Window is implemented. Would it be possible to add it?

Thanks!

UNITY_IPHONE and Mouse observable triggers

Unity now allows *Mouse methods even on mobile (e.g., when UNITY_IPHONE is defined) as touches are mapped to these methods. Can the UNITY_IPHONE conditional guards around the *Mouse Observable triggers be removed?

  • Also, UNITY_IPHONE is deprecated in favor or UNITY_IOS but might be a good idea to use both for now for legacy user code.

Does not "When" call completed method in "Subscribe" ?

Hi there,
I'm quite impressed to use UniRx and trying things these days.

Let me ask a question that I couldn't get "complete" at this code.
Observable.EveryUpdate()
.Where(x => x < 10)
.Subscribe(x => Debug.Log(x),
ex => Debug.LogError(ex.Message),
() => Debug.Log("completed"));

Does not "When" call completed method in "Subscribe" ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.