By Gary Dahl

One of the exciting things about working with computers is that they provide endless opportunities for working smarter rather than harder.  These opportunities have lead to evolutions in game development tools, game engine architectures, and game programming paradigms.  Most of these advances have been focused on modeling what happens in games rather than describing when those things happen.  Consider how you would express the following rules to a computer:

  • Earn a power-up after three consecutive deaths without scoring.
  • Throw fireballs by pressing a button after rocking the joystick forward.
  • Gain extra points by killing enemies within a short time after collecting a power-up.

Describing when rules like these take effect is often more difficult than describing their impact.  Reactive programming is a programming paradigm that focuses on how programs react to change.  This change might come in the form of new user input, the passage of time, or assignments to internal variable.  When changes like these trigger reactions in code, those reactions often result in further changes that cascade into subsequent reactions.  In a sense, changes flow through our programs.  This is what the reactive programming paradigm encourages us to model.  Instead of treating events as discrete isolated moments, reactive programming is focused on modeling and processing streams of interdependent events.

Reactive programming isn’t new, but it has primarily been used within functional programming languages in the past.  Traces of these functional roots are evident in Microsoft’s new Reactive Extensions (Rx) library, even though it’s available for popular object-oriented and hybrid languages like C#, JavaScript, F#, and VisualBasic.  After an introduction to reactive programming using Rx with C#, we’ll reflect on some of the key benefits of reactive programming for game development.  In particular, we’ll look for better ways of expressing when things happen in games.

Reactive Programming with Rx

In Microsoft’s words, “Rx is a library to compose asynchronous and event-based programs using observable collections and LINQ-style query operators”.  Observable collections serve as the foundation of this library.  The IObservable interface represents a stream of event notification messages that follow the well documented “Observer Design Pattern” used in many object oriented applications.  Of the many ways to create new observables, here is one of the simplest:

IObservable<long> whenSecondsElapse = Observable.Interval( TimeSpan.FromSeconds( 1 ) );

This simple observable creates a new event every second.  The payload with each of these events is a simple sequence number.  There are many ways to define observers (that implement the IObserver interface) to process these events.  The following example simply passes every new whenSecondsElapse event to the WriteLine method to be printed on the screen.  There is also a sleep instruction to prevent this asynchronous observable from being prematurely terminated.  We’ll further discuss this asynchronous nature of observables below.

whenSecondsElapse.Subscribe( Console.WriteLine );
Thread.Sleep( Timeout.Infinite );

Subjects seem to provide one of the most general shortcuts for converting existing game events into observable streams.  They are observables that you can post new events to at any time.  Every time an event is posted to the following subject, it will be printed to the screen with the prefix “RX>”.

Subject<string> whenNewInputArrives = new Subject<string>();
whenNewInputArrives.Subscribe( newInput => Console.WriteLine( "RX> " + newInput ) );

We can now post new event messages to this subject at any time using the subject’s OnNext method.  Here’s some code that simply echoes the user’s input back to the screen by posting it to the whenNewInputArrives subject.

while ( true )
{
  Console.Write( "YOU> " );
  string input = Console.ReadLine();
  whenNewInputArrives.OnNext( input );
}

When an observable event stream ends or errors out, all of its observers are notified and unsubscribed.  To demonstrate this, we can add a conditional statement to the loop above that ends the event stream as soon as the user enters “STOP”.  After you type the word “STOP”, your input will stop being echoed to the screen through the whenNewInputArrives observable.

if ( input == "STOP" ) whenNewInputArrives.OnCompleted();

Observable Operations

Now that we can create simple observables, let’s look at some of the high-level operations we can perform on them.  Processing and composing observable streams represents an expressive new way of describing when things happen in our games.  The three basic kinds of operations that we’ll look at below include filtering, projecting, and combining.

For the purpose of this article, we’ll make use of four subjects created in a simple XNA game.  There are static keyPress and keyRelease subjects that all GameObjects share access to.  And non-static collisionEnter and collisionExit subjects within each GameObject.

Filtering

Filtering allows us to create new Observables that contain only a subset of the events of another observable.  Consider creating an observable of firing commands by filtering only enter key presses out of the general keyPresses subject.  The Where-method is used below to create a new observable that is filtered in this way.

IObservable<Keys> whenToShoot = keyPresses
  .Where( key => key == Keys.Enter );

We can also filter these events by any number of other criteria, such as whether there is actually ammo available for the player to shoot when this key is pressed.

IObservable<Keys> whenToShoot = keyPresses.Where( key => key == Keys.Enter && ammoCount > 0 );

Once we’ve created a suitable observable, we can subscribe our bullet shooting method to observe it.

whenToShoot.Subscribe( fireKey => shoot() );

Projecting

Projection involves mapping the payloads in an event stream to other values.  For example, we might want to map the left and right key press events to -0.05 and +0.05 radians of rotation respectively.  After filtering the key press stream down to these two events, the Select-method below performs this projection.

IObservable<float> whenToSteer = keyPresses
  .Where( key => key == Keys.Left || key == Keys.Right )
  .Select( key => key == Keys.Left ? -0.05f : +0.05f );
whenToSteer.Subscribe( steer );

The keyPresses observable above only contains a single event as each key is pressed.  If we want our steering handling code to be called repeatedly while a key is being held down, we can project each key press event into a stream of multiple events that don’t stop until that same key is released.  SelectMany projects each individual event into a new observable stream of events.  The observable it returns is composed of all of the events from each of those projected streams merged together.

IObservable<Keys> keyHolds = keyPresses.SelectMany( keyDown =>
  Observable.Interval( TimeSpan.FromMilliseconds( 10 ) )
    .Select( tick => keyDown )
    .TakeUntil( keyReleases.Where( keyUp => keyUp == keyDown ) ) );

To break this composition down, every keyPresses event creates a new observable using SelectMany.  That observable creates a new event every ten milliseconds.  The payload of these ten millisecond events is projected to the value of the key that is being held down using Select.  Finally, these projected observables are terminated as soon as the held key is released.  The TakeUntil-method is taking care of this termination.

The resulting keyHolds observable can be used to capture events every ten milliseconds for as long as any key is being held down.  You can replace the keyPresses observable in the steering example above with this new keyHolds observable to implement more continuous steering controls.  This same keyHolds observable can also be used to add thrust to propel our ship forward for as long as the up arrow key is help down.

IObservable<Keys> whenToThrust = keyHolds.Where( key => key == Keys.Up );
whenToThrust.Subscribe( thrustKey => thrust() );

This ability to compose and reuse new observable event streams presents a breakthrough in more expressively describing when things happen in a game.

Combining

Rx contains a large collection of observable stream processing methods.  There are many more methods available than I could hope to cover here.  I can however demonstrate a couple of patterns that appear more generally useful in processing game events.  I’ll call these patterns serial and concurrent event combiners.

The serial combiner responds to specific sequences of serial events.  As an example, this might be used to implement a combo attack that is triggered only by a specific sequence of input.  The observable defined below will only trigger a combo attack after the user presses the sequence: up, down, enter.

IObservable<Keys> whenToFireCombo = keyPresses.Where( key => key == Keys.Up ).SelectMany(
  keyPresses.Take( 1 ).Where( key => key == Keys.Down ).SelectMany(
  keyPresses.Take( 1 ).Where( key => key == Keys.Enter )));
whenToFireCombo.Subscribe( combo => fireCombo() );

In this example, the SelectMany-methods cascade new observables to check each subsequent serial requirement.  Whenever the next key entered does not match the required sequence, the nested observables are terminated.  The only events that the resulting whenToFireCombo observable returns occur when the final enter press in this sequence is matched.

The concurrent combiner responds to events that are occurring at the same time.  As an example, consider picking up ammo only when the player is both colliding with an ammo pickup and pressing the space bar key at the same time.  The Join-method provides a convenient way of detecting overlap between events with duration.

IObservable<GameObject> whenToPickup = Observable.Join(
  collisionEnter.Where( ammo => ammo is Ammo ),
  keyPresses.Where( key => key == Keys.Space ),
  ammoEnter => collisionExit.Where( ammoExit => ammoExit == ammoEnter ),
  keyDown => keyReleases.Where( keyUp => keyUp == keyDown ),
  ( ammo, key ) => ammo );
whenToPickup.Subscribe( pickupAmmo );

In the Join that is called above, the first and third parameters describe when collision events start and end.  The second and fourth parameters describe when the space bar key is pressed and released.  Whenever an ammo collision overlaps with a space bar press, an event is spawned with its payload defined by the fifth parameter of this method: the specific ammo object that the player is colliding with.

Scheduling and Disposing of Subscriptions

Our examples so far have neglected a couple of somewhat clerical concerns.  First, where and when is the code that is asynchronously processing observable event streams running?  And second, what resources need to be released when observers are destroyed?

Rx uses schedulers to encapsulate work that needs to be done, and context it should be done in, with a clock to help regulate when that work is done.  All of the observables that we’ve worked with include default schedulers.  Microsoft has chosen these schedulers to introduce the least amount of concurrency reasonable for the observable type.  The ObserveOn method allows you to change the scheduler that an observable is running through.  In addition to the Immediate, CurrentThread, NewThread, ThreadPool, TaskPool and Dispatcher schedulers that are provided with Rx, you can create your own IScheduler implementation for more complete control.  The example code includes a scheduler that performs work within XNA’s GameComponent framework.  Here’s an example of subscribing to an observable using that custom scheduler.

whenToShoot.ObserveOn( rxGame.scheduler ).Subscribe( shootKey => shoot() );

Rx uses disposables to model observer subscriptions that can be terminated at any time.  Whenever an observable sequence is completed or errors-out, all related subscriptions are automatically disposed of.  However when an observer wishes to stop receiving event notifications from a live observable stream, they must manually dispose of their subscription.  This disposable subscription is returned from the Subscribe-method, and its Dispose-method prevents further events from being pushed to the observer.  The CompositeDisposable is a convenient collection type that allows groups of disposable subscriptions to be disposed of together.  The following code fragment adds a new subscription to a CompositeDisposable collection called subscriptions, and then disposes of all of the subscriptions in that collection.

subscriptions.Add( whenToShoot.Subscribe( shootKey => shoot() ) );
subscriptions.Dispose();

Syntactic Sugar

The Rx team is fond of pointing out a duality between IObservables and IEnumerables.  Pulling data from and IEnumerable collection is symmetric to having data pushed to you from an IObservable.  This observation appears to be the inspiration behind integrating LINQ and Rx.  LINQ is Microsoft’s Language Integrated Query syntax, and has traditionally been used to query data from object hierarchies, xml files, and databases.  It can now also be used to query data from observable event streams in Rx.  Here’s a quick LINQ example of the steering observable discussed above.

var whenToSteerContinuous = from key in keyHolds
                            where key == Keys.Left || key == Keys.Right
                            select key == Keys.Left ? -0.05f : +0.05f ;

Although I don’t personally find this syntactic sugar particularly advantageous, I do believe the underlying relationship between querying databases and event streams is important.  This shift from thinking about events as hook-able moments in time to streams of data that can be processed and queried is at the heart of what reactive programming has to offer game developers.

A Brief History of When

The thing that excites me most about reactive programming in games is the increased focus on expressively describing when things happen.  This different way of thinking about and organizing code is much more subtle than any new feature on a graphics card or in a game engine.  In order to better frame this advantage, I’d like to briefly review how descriptions of when-things-happen are commonly implemented in games.

Code is generally executed from top to bottom in imperative architectures.  When something happens is implicitly determined by the relative position of an instruction.  Basic control structures give us ways of conditionally skipping over some instructions or repeatedly executing them, but generally avoid directly addressing the question of when things should happen in a game.  These implied expressions of when things should happen are powerful enough to implement any Turing-compatible game feature or algorithm, but are extremely messy and difficult to maintain.

Modern games and simulations are commonly organized around a main loop that often runs multiple-tens of iterations per second.  This loop contains instructions implementing every possible change within a game.  However many of these instructions may be preceded by conditions that contains true or false expressions of whether those instructions should be run or skipped over.  These conditions are repeatedly evaluated within a loop, and serve as more explicit descriptions of when things should happen in a game.  Organizationally, this architecture is still quite a mess to deal with as the line between event detecting and event handling code is so blurry.

Event driven architectures break descriptions of what happens in a game apart from when those things happen.  Many modern game engines expose similar sets of event handlers for programmers to implement.  The convenience of implementing these event handlers without worrying about event detection is clearly limited to the set of events that an engine exposes.  Extending these sets of events is often so cumbersome that programmers typically prefer to filter overly general events with conditions.  For instance, a general update or tick event that is repeatedly triggered may contain multiple conditions checking for events related to each player’s health, the elapsed time of a race, and a submersed player’s depth.  Detecting game-specific events in this way leads event driven architectures to essentially degenerate into loop-style now or not-now expressions of when things need to happen.

There’s a variation of event driven architectures that carries an important advantage in describing when things happen in games.  Although this terminology is not completely standard, I prefer describing systems that serialize event messages for later processing: message based architectures.  This is in contrast to event driven systems that integrate event handling directly into the flow of a programs execution with constructs like delegates, virtual functions, or function pointers.

Message based architecture’s subtle shift from collecting event handling code to event notification data adds a valuable level of indirection.  Event notification messages can be sorted, filtered, or even deferred for later processing.  Beyond helping us more expressively describe when things happen in games, message based architectures also offer benefits in implementing game save and playback features, networking synchronization, and powerful diagnostic tools for developers.

The Future of When

Reactive game architectures are built on top of multiple observable event streams, each of which individually resemble message based architectures.  In addition to enabling us to sort, filter, and defer the processing of individual event messages, reactive architectures encourage us to compose entirely new observables.  In the previous example code, we composed a keyHolds observable relative to the pre-existing keyPresses and keyReleases observables.  We also created new observables that represented both serial and concurrent combinations of events from other observable event streams.

Although reactive programming is an important step in the evolution of describing when things happen in games, I believe that we are still near the beginning of this journey.  Many experienced programmers are so accustomed to implicitly describing when things happen in games, that it’s easy to overlook the problem and accompanying opportunities for improvement.  I’m not sure that I would have been as sensitive to this problem, if it weren’t for my experience helping students figure out how to convert polled key inputs into throttled fire commands.  The process of identifying mutable state to track and check is just not very intuitive to beginning programmers.  In fact, even more experienced programmers often prefer to avoid such messy mutable state, as can be seen in the design of more functional programming languages like Scheme, Haskell, and F#.

Reactive libraries like Rx are still a long way from fulfilling their exciting potential.  There are currently over a hundred operations available on observables.  Many of these operations are redundant, and it’s not entirely clear what subset of them will form a sufficiently expressive basis.  There may also be better ways of expressing common high-level processes like the serial and concurrent combiners discussed above.  As a final note, Rx represents a paradigm shift in processing observable event streams.  This requires time, both for programmers to become familiar with, and for tool developers to engineer specialized diagnostic and debugging environments around.

Final Reactions

Expressively describing when things happen in games is hard.  We have evolved from using imperative and event driven architectures to message based and now reactive game architectures.   But none of these are as expressive or intuitive in formulating description of when something happens as in English… yet.  The composable event streams of reactive architectures appear to be a promising direction forward, but only time and experience will tell.

Beyond introducing you to reactive programming and Microsoft’s Rx, I hope this article encourages you to experiment with a new ways of explicitly describing when things happen in your games.  It’s easy to take what we are used to for granted.  But resisting that complacency is essential to advancing the state of our art.  This is the programmer’s journey, to search for new ways to work smarter rather than harder.





3 Comments

Kevin J. says:

Truly inspiring article. I’ve never heard of Reactive Programming, or the Rx library for that matter. I have come from a background of event-driven development with the Flash platform. I’ve always felt that there was a more intuitive way to write responsive code, but always ended up pushing the rock uphill instead of using a simple machine such as Reactive Programming.

zloidooraque says:

unfortunately it’s pretty hard to accustom with all this.
more or less complex “streams” will take quite an effort to compose. but underlying power is immense.

also, there is an obvious need of some metalanguage, or, rather, metaoperators or at least set of rules to compose and manage routings. but i think the simpliest way to achieve it is to watch how it crystallize out of real world implementation endeavours

Phil says:

Very helpful article, especially alongside your description of the way games are currently programmed.


Leave a Comment






Related Posts

Thursday, January 26th, 2012


Tuesday, January 24th, 2012