Maps, maps and more maps

Over the last few months I’ve knocked up a few specialised ‘maps’ for working with robotlegs. I like the ethos behind these kinds of utilities – abstracting the meta-logic (the nature of the problem being solved) away from the detail-logic (the specific solution).

I’d planned to blog each one in turn, but the time runs away so quickly doesn’t it?

And so – all in one fell swoop – I offer you 3 variations on the CommandMap, a SignalMap and a race-condition beating EventMap. All on github and created through Read-Me-Driven-Test-Driven-Development, so you should find the ReadMe in each case sufficient to get going, but do check out the diagrams in this post as well:

1. GuardedCommandMap – separating conditional logic from action

Sometimes you want to map behaviour to an event, but only if certain other conditions are met.

It could be that the condition is relevant to some other property of the event – say the specific key being pressed in a KeyboardEvent – or it could be something else, for example whether the user already has local account details in a SOL.

Usually we wind up implementing this kind of logic using if() statements and early bails in the execution of the Command.

The GuardedCommandMap abstracts the conditions from the actions.

Like how?

As well as mapping a Command, you also map one or more Guards. The Command is only executed if all the Guards agree to it.

This has the advantage of allowing you to map a Command as oneShot, but know that it won’t be executed and unmapped unless all the Guards are passed. It also avoids duplication of condition-checking logic – for example if you had multiple commands that you only want to kick in after the ‘training’ phase of a game. You can map the same Guard (or Guards) to more than one Command.

The Guarded CommandMap

Halt! Nobody executes this baby without my say so.

What’s a Guard?

A Guard is very similar to a Command. It has only one public method:
function approve():Boolean;
The Guard Classes are instantiated in the same way as Command classes – so they can have injections in the same way as the Command, and can receive the Event class that triggered the CommandMap, just as the Command eventually will.

The approve() method returns true or false. If all the approve() methods return true then the Command will be instantiated and will run. If any approve() method returns false then the process is aborted.

Read more about the GuardedCommandMap, and get the code, on github.


2. OptionCommandMap – a generic ‘user choice’ solution

This is a commandMap which allows you to map against a common event type, and which automatically cleans up (removing all mappings) once one of the options has been selected and actioned.

Why would you need this?

OptionCommandMap is designed to streamline the following use case:

  • A user is offered multiple options
  • Based on which option they select, a different command (or combination of commands) should be run
  • Once the appropriate command is run, the previous option-command pairings are no longer required

Note: The use case happens repeatedly at runtime, with variations in the options and the commands, but a persistence of the overall pattern – the user is making a choice, and based on that choice a command should be executed. (Note that ‘user’ could be substituted with an external service or client). For example:

  • Your dope-wars clone game pops up a user decision dialog “You’ve run out of cash! What would you like to do?” with options “Beg”, “Borrow” or “Steal”
  • Depending on which option the user chooses, a different command runs to update models within the game to reflect the user’s choice
  • Once the user has made their decision, the other options are no longer viable

Why not just use the ordinary CommandMap?

You could achieve the above by doing the following mappings:

commandMap.mapEvent(OutOfCashEvent.SELECTED_BEG, TryBeggingCommand, OutOfCashEvent);
commandMap.mapEvent(OutOfCashEvent.SELECTED_BORROW, TryBorrowingCommand, OutOfCashEvent);
commandMap.mapEvent(OutOfCashEvent.SELECTED_STEAL, TryStealingCommand, OutOfCashEvent);

but that would result in a lot of bespoke events for each possible option offered to the player. Generally I’m in favour of strong typed events, but in the case of a game where there might be hundreds of randomly generated options, we can do better.

If we decide to use a common OptionEvent and make mappings at runtime such as:

commandMap.mapEvent(OptionEvent.OPTION_A, TryBeggingCommand, OptionEvent);
commandMap.mapEvent(OptionEvent.OPTION_B, TryBorrowingCommand, OptionEvent);
commandMap.mapEvent(OptionEvent.OPTION_C, TryStealingCommand, OptionEvent);

then we need to also keep unmapping these events, as once an option has been selected, the rest of the mappings (which may be many, there could be several options) need to be unmapped, so that when we next map options – perhaps for ‘Hide stash’ or ‘Flush stash’ options in a raid – we don’t also kick off the Command for the relevant Beg / Borrow / Steal options.

The Normal CommandMap can't deal with repeated options

If you want to reuse common events for option consequences you're normally left with an unmapping headache.

The OptionCommandMap is intended to streamline this process and make the cleanup automatic, as well as simplifying the setup.

The OptionCommandMap

The OptionCommandMap knows how to clean up after each option is selected

Read more about the OptionCommandMap, and get the code, on github.


3. CompoundCommandMap – execute based on a combination of events

Sometimes we only want our application to respond after a number of processes have completed, but we can’t be sure that they’ll happen in any particular order.

Maybe your startup cycle involves several different asynchronous process. You only want your application to move into its ‘active’ state once all of those processes are complete.

Maybe your command needs the payload from more than one of those events – and it’s a bit of a PIA to have to store event values in an intermediate state solely for this purpose.

The CompoundCommandMap encapsulates the ‘checking that everything has happened and hanging on to any values’ logic, allowing you to map a Command to a combination of Events – in a specific order or in any order. All the events are available to be injected into your Command when it is triggered.

The Compound CommandMap

The CompoundCommandMap waits for all the required events to have fired before executing

It even supports named injections so that you can inject multiple instances of the same Event class.

// params: Command Class, isOneShot, requiredInOrder
compoundCommandMap.mapToEvents(SomeAwesomeCommand, true, true)
    .addRequiredEvent(SomeEvent.SOMETHING_HAPPENED, SomeEvent)
    .addRequiredEvent(SomeOtherEvent.SOMETHING_ELSE_HAPPENED, SomeOtherEvent, 'somethingElseHappened')
    .addRequiredEvent(SomeOtherEvent.STUFF_HAPPENED, SomeOtherEvent, 'stuffHappened');

Read more about the CompoundCommandMap, and get the code, on github


4. SignalMap (and SignalMediator)

The trouble with listening for events and signals is that it’s damn easy to forget to unlisten!

The SignalMap was originally intended to replicate the robotlegs EventMap, allowing the Mediator to do auto-magic clean-up of your signals when the view leaves the stage.  But the SignalMap has no dependencies, so you can exploit it anywhere that you want to be able to clean up a number of Signal listeners in one go.

Read more about the SignalMap, and get the code, on github


5. RelaxedEventMap – what race conditions?

Mediator: “Ok, I’m ready, my view’s on the stage, can I get updates on any data changes?”

EventMap: “Sure.”

Mediator: “Uh… come on then, it’s been like 5 seconds, and nothing’s happening! I need some data to pass to my view.”

EventMap: “Really? I’m sure I passed some data on a while back. You didn’t hear that?”

Mediator: “No! Are you sure? I’ve been listening *really* carefully.”

EventMap: “Yup. I’m sure. It was right before you said you were ready.”

Mediator: “Before? Wait! No! But I wasn’t ready then – I wasn’t even around… can you send it again?”

EventMap: “Sorry dude. You’ll have to wait for an update – I didn’t keep any notes, I’ve no idea what the message said.”

The RelaxedEventMap is an EventMap with built in time-traveling capabilities. At the time of registering a listener, it looks back in time for the last instance of the relevant event, and if it finds one then it relays it to the listener immediately.

Race-conditions be gone!

Read more about the RelaxedEventMap, and get the code, on github

About the Author

I'm an actionscript programmer living and working in a tiny village in the Yorkshire Dales, UK. I used to be a TV reporter, but my inner (and often outer) geek won. I also write stuff. Most recently Head First 2D Geometry.

Visit Stray's Website

Share the post

Delicious It Digg this! Stumble this! Share on Reddit Share on Buzz Share on FriendFeed
  • http://twitter.com/wrobel221 Michał Wróblewski

    Neat stuff! As always. Thanks.
    PS. Love that Mediator – EventDispatcher chat ;)

  • http://twitter.com/janders223 janders223

    Excellent post Stray. Love, love the commandMaps. I can already see ways of implementing them in my current project.

  • vitaLee

    nice collection of extensions presented in detailed and funny way :)
    LOVE :)

  • http://twitter.com/neilmanuell neil manuell

    Yep, very nice… I especially like the guards… may have to look deeper :)

  • http://twitter.com/neilmanuell neil manuell

    Me again… Have you any plans for extending @jhooks ‘s SignalCommandMap with any of this functionality?

    • http://www.xxcoder.net Stray

      Hi Neil – I do have a plan that there should be a Signals version of each of these, but it’s a case of finding the time, so anyone who has the inclination should just go ahead and mix the two (with tests obviously).

      The guard and option versions should be fairly simple, but the compound one has more complications as you could easily wind up with value type clashes (say two signals both dispatched a String).

      Named injection in that situation seems like a very weak solution – I think named injection is generally best avoided. So – you’d have to start thinking about whether you actually inject a dictionary of the properties – referenced against the signal that dispatched *and* the type of the value, but then that gets gnarly as well because you might have 2 vanilla signals.

      So – I haven’t come up with a convincing answer to that one yet.

      But the other two are fair game for anyone with the time to do it before it reaches the top of my to-do list!

      Stray

      • http://twitter.com/neilmanuell neil manuell

        Mmmm may try a GuardedSignalCommandMap… if I ever have the time. This is exactly the functionality I have been looking for.

        Just to clarify, the Guards are injected into the same childInjector as the commands, yes? So that the Guards can receive injections of the event object, and query it?

        • http://www.xxcoder.net Stray

          Yup, that’s exactly it. The guard can query the event and/or any models that are available for injection etc.

          I’ll have a look – I expect it wouldn’t take me *that* long.

          • Anonymous

            well I’ve written a GuardedSignalCommandMap extending SignalCommandMap
            just need to write tests for it ( may request a pull @jhooks, as a slight restructuring of SignalCommandMap would mean that some code could be shared

          • http://www.xxcoder.net Stray

            Perfect! Because I’ve pulled Joel’s fork and based on that have started writing tests – I’m a strictly tests-first person – so whack your fork up and send me a link and I’ll run my tests on your code tomorrow. Nice one!

  • Anonymous

    By the way, I have compiled a swc with only the bits you need.
    Basically, I compiled with a custom compiler config file against the robotlegs.swc
    how do you want it?

  • Pingback: Guarded Answers

  • System Programer

    Great Job!

  • http://twitter.com/markfoxisadj Mark Fox

    I’ve been bouncing around the web looking for a utility/approach that is like RelaxedEventMap but works with Signals instead of Events. I’m pretty new to Robotlegs and Signals and super excited to utilize them well, however I’m barely out of the gate and I’m already running into a race condition where my Mediator is onRegistering AFTER a very important Signal has been dispatched from a model. I don’t know if my design is weak (largely I’m just following Joel Hooks SignalCafe example) but my app is for mobile, so I have a feeling I could be running into this problem a lot as I’ll be creating Views as they are needed. I guess this is partially a plea/request to implement RelaxedSignals… or any pointers/tutorials to consider. I’ve kind of read about some other Asynchronous libraries but it all seemed a little heavy for the basic problem of “use the last dispatch payload, if one is available”.And before I finish just wanted to say thanks for all the great work/writing, Stray. I’ve been well informed by your posts on the Robotlegs forums!

    • http://twitter.com/markfoxisadj Mark Fox

      Ah yes, Signals History: http://knowledge.robotlegs.org/discussions/problems/213-no-subject — is there presently a utility implementation, or should I expect to roll my own?

      • http://www.xxcoder.net Stray

        https://github.com/creynders/RelaxedSignalsDemo

        Hi Mark – there’s a relaxedSignal all ready to use – check out this demo, and let me know if that doesn’t meet your need.

        • http://twitter.com/markfoxisadj Mark Fox

          RelaxedSignal, that does the trick nicely! I missed this the first time around but I’m breaking RobotLegs back out and am definitely enjoying RelaxedSignal ability to alleviate timing issues of mediators onRegistering after a signal dispatch.

          Much more elegant than my initial solution which involved having the Mediator request an initial payload via a signal command that pulled from the model. A really tedious step that generated code that was pretty redundant.

  • Pingback: bulupe » Extensions to Robotlegs, yes you will definetely need these :)