Async APIs and State Machines

Continuing my posts on dealing with async APIs, I’ve been hinting that there options beyond rafts of callbacks functions, be they anonymous or not. So let’s get on to it–I’ve been promising this post for a while.

Briefly, state machines model the behavior of a system as a set of discrete states, events which are accepted in each state, and actions–including changing states–which occur when inputs are received. State machines turn out to be particularly well-suited to asynchronous programming because they don’t force you to organize your logic in sequential steps.

For example, imagine programming a controller for a sensored traffic light. The events it deals with–the arrival of a car, or a push on the crosswalk button–are inherently asynchronous. It’s difficult to write code that, given the arrival of an event, then examines which lights are red, which are green, determines which to change, and so on. It’s much easier to organize your code around the state of the light–which are green, which are red–and then, for every state, specify how each event is handled.

Now there’s more than one way to implement state machines in code, but I’m going to use the implementation proposed by [Samek]. Let’s start translating this to code:


class TrafficLight {
var m_state:Function = stateEastWestGreen;

function stateEastWestGreen( event:Event ):void ...
function stateNorthSouthGreen( event:Event ):void ...

Note that I’m modeling each state as a function and the events that can occur are modeled as, well, events. (You see how well this fits together!) Now a real traffic light probably has more states then this, but it’ll do as an example.

Each state has to deal with all possible events that it might receive and want to react to. For example, suppose I have traffic sensors that dispatch CarEvent each time a sensor is triggered:


function stateEastWestGreen( event:Event ):void {
if( event.type == CarEvent.CAR ) {
var carEvent:CarEvent = event as CarEvent;
if( carEvent.direction == CarEvent.NORTH_SOUTH ) {
// switch to stateNorthSouthGreen
} else {
// We're already green in this direction; nothing to do
}
} else {
// Some other type of event...
}
}

Now I’m not a traffic engineer, but it’s easy for me to read this code and realize that yes, when the light is green in one direction, if a car arrives in the other direction I’m going to want to eventually change the lights for it. For now I’m skipping over the details of turning the lights yellow, etc. but we’ll get back to that.

One more snippet for today, and that’s to get the events from our sensors into our state function. Still inside the TrafficLight class:


function dispatch( event:Event ):void {
m_state( event );
}

function TrafficLight() {
northSouthSensor.addEventListener( CarEvent.CAR, dispatch );
eastWestSensor.addEventListener( CarEvent.CAR, dispatch );
}

Note that I register the same callback function for all events I listen for, and that it in turn simply forwards them to the function that represents the current state. Et voila.

References

[Samek] Practical Statecharts in C/C++: Quantum Programming for Embedded Systems, Miro Samek, CMP Books, 2002.