About RMC & Unity3D Rivello Multimedia Consulting (RMC) provides consulting services for applications and games. RMC specializes in Unity3D development (see our work here). Please contact us today with any questions, comments, and project quotes. As always, RivelloMultimediaConsulting.com/unity/ will be the central location for deep articles and tutorials, Facebook.com/RivelloMultimediaConsulting (like us!) will engage the growing RMC+Unity community, and for the latest opinions and cool links follow me at Twitter.com/srivello.
Why RX?
General
Obviously fantastic software can be created without RX. We work without it every day. So why consider it? Well, essentially because asynchonous programming is hard to do, brittle, and is a challenge to scale. Well as the Reactive Manifesto explains;
- The Need To Go Reactive – Each day applications are getting bigger, users expect more to happen in less time, and our projects are deployed to increasingly diverse set of (smaller) devices. To compete, old dogs need new tricks.
- Reactive Applications – They react to events; loading data, user gestures, system failures.
- Event-Driven – Event driven systems feature loose coupling between components and subsystems; a prerequisite for scalability and resilience.
- Scalable – With RX our data runs ‘as if’ it is synchronous. Even the currently synchronous stuff. So as complexity comes and delays ensue, it can flex to be asynchronous. Secondly, the decoupled nature allows for location transparency. Benefits include multi-threading (where possible).
- Resilient – In a reactive application, resilience is not an afterthought but part of the design from the beginning (see ‘onError’). Making failure a first class construct in the programming model provides the means to react to and manage it, which leads to applications that are highly tolerant to failure by being able to heal and repair themselves at run-time.
- Responsive – The philosophies and practices of RX programming seek minimal latency to the user experience.
- Conclusion – We expect that a rapidly increasing number of systems will follow this blueprint in the years ahead.
NOTE: While RX is designed to work with asynchronous data streams, it really doesn’t care. RX blindly accepts synchronous data within the same data flow.
Games
My first exposure to RX was regarding both UI behaviors (e.g. mouse events) and asynchronous data (e.g. loading from server). Gaming is inherently an asynchronous experience and the UI behaviors (e.g. user input) alone warrant a deep look at RX.
What is RX?
Iterables You are probably familiar with the concept of Iterables. In Unity we are used to using a List of values and looping through them and doing something synchronously. Observables In RX, it is observables that are a similar concept except the list may or may not be populated yet (i.e. it could start out empty) and may or may not be fully populated yet (i.e. ‘complete’). Still you iterate through the values, however asynchronously. Consider the following table;
Same Concept, Opposite Direction (Pull vs Push of Data) |
Iterable (Pull) |
Observable (Push) |
getDataFromLocalMemory()
.skip(10)
.take(5)
.map({ s -> return s + " changed" })
.forEach({ println "next => " + it })
|
getDataFromNetwork()
.skip(10)
.take(5)
.map({ s -> return s + " changed" })
.subscribe({ println "onNext => " + it })
|
Reactive applications use observable models, event streams and stateful clients.
Reactive extensions (RX) is the library designed for Functional Reactive Programming (FRP). It is “a library for composing asynchronous and event-based programs by using observable sequences.” What is FRP? The name comes from the language features used and the philosophy of the data exchange (See Figure 1).
Figure 1. Functional Reactive defined.
According to Haskell.org, Functional Reactive Programming (FRP) integrates time flow and compositional events into functional programming. This provides an elegant way to express computation in domains such as interactive animations, robotics, computer vision, user interfaces, and simulation. The major parts of RX;
- Observables – Source of event stream & Subscriber – Observer of event stream.
- Linq – Query the event stream (e.g. filter, map, zip)
- Schedulers – Define the timing (inc. frequency) of concurrency.
As a quick primer, consider the following Observer->Subscriber event stream (See Figure 2).
Figure 2. Observer-to-Subscriber Event Stream.
Here the Observable emits an integer, it goes through an operation that adds 2 to it, and is finally received by the Subscriber (Observer). All steps are asynchronous, which means the subscriber does not actually know that the original value had been changed.
Operators
Between the Observable and the Subscriber the event stream can be operated upon. Here is a partial list of operators;
- Transforming
- map( ) — transform the items emitted by an Observable by applying a function to each of them
- scan( ) — apply a function to each item emitted by an Observable, sequentially, and emit each successive value
- Filtering
- filter( ) — filter items emitted by an Observable
- takeLast( ) — only emit the last n items emitted by an Observable
- skip( ) — ignore the first n items emitted by an Observable
- takeFirst( ) — emit only the first item emitted by an Observable, or the first item that meets some condition
- Combining
- startWith( ) — emit a specified sequence of items before beginning to emit the items from the Observable
- merge( ) — combine multiple Observables into one
- zip( ) — combine sets of items emitted by two or more Observables together via a specified function and emit items based on the results of this function
How to do RX?
Let’s consider a very typical operation; using mouse input to perform a simple operation. Compare the non-RX and RX approaches to the following (academic) exercise;
Exercise: Dispatch a double click event based on custom timing parameters.
Reactive Extensions (RX) Solution
You can see the code is easy to read and powerful. While this algorithm requires state management, we don’t have to handle it directly. The internals of RX do it for us. You can imagine with a more complex feature, the code would be dramatically more than the RX solution. You can read an in depth analysis of this code in my next article “Unity3D Reactive Extensions 2“.
[actionscript3 collapse=”true”]
//————————————–
// Methods
//————————————–
///<summary>
/// Use this for initialization
///</summary>
void Start ()
{
// VARIABLES
float maxTimeAllowedBetweenSingleCLicks_float = 1;
float delayBetweenAllowingADoubleClick_float = 5;
int clicksRequiredForADoubleClick_int = 2;
// CREATE OBSERVABLE
//(ORDER IS NOT IMPORTANT, BUT SUBSCRIBE MUST BE LAST)
var mouseDoubleClickObservable = Observable
//RUN IT EVERY FRAME (INTERNALLY THAT MEANS Update()
.everyFrame
//FILTER RESULTS OF THE FRAME
//WE CARE ONLY ‘DID USER CLICK MOUSE BUTTON?’
.filter (
_ =>
Input.GetMouseButtonDown (0)
)
//DID WE FIND X RESULTS WITHIN Y SECONDS?
.withinTimeframe (clicksRequiredForADoubleClick_int, maxTimeAllowedBetweenSingleCLicks_float)
//REQUIRE SOME ‘COOL-DOWN-TIME’ BETWEEN SUCCESSES
.onceEvery (delayBetweenAllowingADoubleClick_float);
//FOR EVERY EVENT THAT MEETS THOSE CRITERIA, CALL A METHOD
var subscription = mouseDoubleClickObservable
.subscribe (
_ =>
_onMouseEvent (MouseEventType.DoubleClick)
);
Debug.Log ("Subscription Setup : " + subscription);
}
//————————————–
// Events
//————————————–
/// <summary>
/// SUCCESS: Double click
/// </summary>
private void _onMouseEvent (MouseEventType aMouseEventType)
{
Debug.Log ("RX._onMouseDoubleClick() " + aMouseEventType);
}
[/actionscript3]
Traditional Solution (Non RX)
This code requires us to ‘manually’ maintain state. You can imagine with a more complex feature, the code here would be even more long-winded compared to the RX equivalent. Thankfully we can use Coroutines, otherwise there would be even more state-specific variable setup to write and maintain.
[actionscript3 collapse=”true”]
/// <summary>
/// KEEPING STATE: Timing information
/// </summary>
private int _state_clicksInLastXSeconds_int = 0;
/// <summary>
/// KEEPING STATE: Timing information
/// </summary>
private bool _wasLastEventTooRecent_boolean = false;
// PRIVATE STATIC
//————————————–
// Methods
//————————————–
/// <summary>
/// Update this instance.
/// </summary>
void Update ()
{
// VARIABLES
int clicksRequiredForADoubleClick_int = 2;
//
if (Input.GetMouseButtonDown (0)) {
if (!_wasLastEventTooRecent_boolean) {
if (++_state_clicksInLastXSeconds_int >= clicksRequiredForADoubleClick_int) {
//SUCCESS!
_onMouseEvent (MouseEventType.DoubleClick);
//STATE MANAGMENT
_wasLastEventTooRecent_boolean = true;
_state_clicksInLastXSeconds_int = 0;
StartCoroutine ("DelayBetweenAllowingADoubleClick_Coroutine");
StopCoroutine ("MaxTimeAllowedBetweenSingleCLicks_Coroutine");
} else {
//STATE MANAGMENT
StartCoroutine ("MaxTimeAllowedBetweenSingleCLicks_Coroutine");
StopCoroutine ("DelayBetweenAllowingADoubleClick_Coroutine");
}
}
}
}
//————————————–
// Coroutines
//————————————–
/// <summary>
/// HANDLES TIMING: Between clicks
/// </summary>
private IEnumerator MaxTimeAllowedBetweenSingleCLicks_Coroutine ()
{
// VARIABLES
float maxTimeAllowedBetweenSingleCLicks_float = 1;
// TIMING
yield return new WaitForSeconds (maxTimeAllowedBetweenSingleCLicks_float);
// STATE MANAGMENT
_state_clicksInLastXSeconds_int = 0;
}
/// <summary>
/// HANDLES TIMING: Between clicks
/// </summary>
private IEnumerator DelayBetweenAllowingADoubleClick_Coroutine ()
{
// VARIABLES
float delayBetweenAllowingADoubleClick_float = 5;
// TIMING
yield return new WaitForSeconds (delayBetweenAllowingADoubleClick_float);
// STATE MANAGMENT
_wasLastEventTooRecent_boolean = false;
}
//————————————–
// Events
//————————————–
/// <summary>
/// _ons the mouse event.
/// </summary>
/// <param name="aMouseEventType">A mouse event type.</param>
private void _onMouseEvent (MouseEventType aMouseEventType)
{
Debug.Log ("NonRX._onMouseDoubleClick() " + aMouseEventType);
}
[/actionscript3]
NOTE: The Unity source-code for this article is available (See ‘Member Resources’ below).
RX & Unity
Figure 3. RX & Unity
RX is a library that can be theoretically ported to any language. It currently exists in many languages; prominently Java and JavaScript. There are C# ports, but unfortunately not all are Unity-compatible. Unity’s (latest version 4.3.4) C# is compatible with an OLD version of Mono – Mono v2.65 (versus the latest available v3.2.7). Mono is the open source version of .NET which powers the C# language features of Unity. So, while RX libraries in C# are plentiful, there is not yet a Unity-compatible library which contains all the features we would like to see. All examples covered int his article are from the amazing people at Tiny Lab Production (TLP). Their RX library is available for free download (See ‘Member Resources’ below). The TLP team actively encourage use of and contribution to their library
RX Integration
Uses of RX
My first experimentation includes creating mostly non-RX games with few distinct RX elements (particularly mouse/key/gesture input scenarios). RX can be peppered into your project selectively. With more experience and awareness I expect the community to embrace more systemic usage of RX in Unity games. Ideas for RX In Gaming;
- User Input (Mouse/Keyboard/Touch)
- Constraints (keep player character onscreen)
- Loading Assets
- Loading From Backend Services
- Multiplayer game state synchronization (e.g. ‘Did every player in this game room click start?’)
- And much more… see Reactive Game Architectures
Choosing an RX Library
The RX library you choose must match your platform of choice. For me that is Unity & C#. Due to the “RX & Unity” issues above I can’t simply choose any C# library. The library chosen for this article was created by the fine developers at Tiny Lab Productions (TLP). I continue to search for a Unity-friendly library that has the scope and syntax exactly matching Netflix’s fantastic RxJava implementation.
NOTE: The TLP Library used in this article is a work in progress. It features only limited functionality and does not conform to common RX standards in the naming, signature, and structure of major elements. The TLP team invites other developers to fork the library and contribute to it. Still it is a fantastic learning tool and quite useful as-is.
Resources
While RX is new to the Unity Community, there are tons of non-Unity resources and tutorials available. Its important to note that until a robust Unity-compatible RX library is available all of the functionality may not be available. Here are some resources;
NEXT STEPS: RX Syntax & Marble Diagrams
The RX example above uses combinators to empower the event stream filtration and modification. Deeper explanation is available in my next article “Unity3D Reactive Extensions 2“.
Member Resources
[private_Free member]Enjoy this members-only content!
[/private_Free member]