Routing
Transitions
The individual states of a state machine are usually designed to perform specific tasks. For example, a state might be responsible for reading a file from the disk or requesting the data on a web page from a web server. Even these simple tasks might require several actions. For example, a state should probably check that a file exists before starting a filesystem process to read it. Therefore, the collection of individual states which make up a state machine will quite possibly contain a large number of actions. When the machine completes an action and moves on to process the next then a transition is said to have occurred. The overall behaviour of the machine can then easily be observed by the sequence of transitions it makes.
To keep the sequence continuous, whenever an action is completed the state machine makes a transition and the next action in the sequence is performed. The obvious exception to this rule is when the state machine transitions to one of the terminal states, i.e. either @exit or @error. At this point the machine stops and no further actions or transitions are made. The converse of the above rule also holds, i.e. all actions are initiated by a transition.
When the state machine is started an initial transition is made. This causes the machine to transition from its default state to a specific state within the machine (see: startstate). In general when the machine makes a transition from one state to another like this then the transition is called an Inter-State Transition.
The sequence of actions involved in an Inter-State Transition actually begins with an action defined on the state initiating the transition. Once the transition has been initiated by the departing state, and as long as the initiation did not originate in the guard action (see guard), then the states exit action is performed (see: exit). This is followed by either the guard action of the target state being performed, or if this is not defined, then its entry action.
In the context of ignite.js, the guard, entry and exit actions are all functions defined as properties on the State Behaviour Object. Performing an action is analogous with calling the function. Once the action is complete, i.e. the function call returns, the returned value dictates which transition is next in the sequence.
As well as the three default actions guard, entry and exit, a state can define actions used to respond to events. This is done by adding properties to the actions object. The response can either be to simply initiate a transition, in which case the property should be a string value indicating the destination of the next transition, or to perform an action followed by a transition. In the later case the function is called, and its return value (also a string) is used to dictate the destination of the transition.
There are some transitions where the state machine remains in its current state. These can either be Intra-State Transitions or Internal Transitions. Intra-State transitions occur when the state gains entry to a state by satisfying a guard condition and therefore transitions from the guard to entry functions of the state. Internal Transitions are made after the state has performed some action and then remains in the state to listen for additional events to respond to.
The following table shows a summary of how to interpret the possible transitions associated with the completion of each type of action. The Value column contains the possible set of values which can be returned by action functions or defined as action properties. These are very important to understand as they lead directly to the type of transition initiated. For example, the data in row one shows that if a guard action is completed (i.e. the guard function is called and returns) and the Value is null (i.e. the guard function return value is null) then an Intra-State Transition occurs.
| Value | Action | Type | Description |
|---|---|---|---|
null, undefined | guard | Intra-State | The state machine enters the state and the entry function is called. |
null, undefined | entry, actions | Internal | The state machine remains in the current state, no additional action is taken. |
String: target | guard | Inter-State | The guard function (or entry function if the guard function is undefined) is called on the target state target. |
String: target | entry, actions | Inter-State | The exit function (if defined) is called on the departing state followed by the guard function (or entry function if the guard function is undefined) on the target state target. |
'@defer' | actions | Internal | The event associated with the action is defered. The state machine remains in the current state, no additional action is taken. |
'@ignore' | actions | Internal | The event associated with the action is ignored and discarded. The state machine remains in the current state, no additional action is taken. |
'@self' | guard, entry, actions | Re-Enter | The state machine makes a transition out of the current state calling the exit function (if defined) and re-enters the state calling the guard function or entry function if the guard is missing. |
'@exit' | guard, entry, actions | Exit | The state machine makes a transition out of the current state calling the exit function (if defined) and then enters the terminal @exit state. |
'@error' | guard, entry, actions | Error | The state machine makes a transition out of the current state calling the exit function (if defined) and then enters the terminal @error state. |
In the above table the data in the Value column is shown as a String. However in practice it could also be defined using an Array where the first element of the array is the String value shown. The subsequent elements of the array are used to set new values for the transition arguments when the transition occurs.
Events / Actions
In ignite.js the completion of any asynchronous process results in the generation of an event. Once generated, the event is injected into the current state of the state machine. Each state can define actions to perform on receipt of specific events. Actions are associated with specific events using a pattern matching process based on the event name and in some cases the origin of the event.
Typically there are two sources which generate events within the state machine. These are event emitters (see: node.js EventEmmitter) and the callback functions generated for asynchronous functions calls (see: Function Proxies and Auto-Gen callbacks). It is worth making a distinction between these two as the events they generate are handled in slightly different ways.
The events generated by event emitters are called State Uncoupled Events. These events have no particular connection to, or knowledge of, the state their emitter was registered in (see: EventEmitters). By contrast, events generated by the callback functions are local to the state the respective asynchronous function call resides in. Given this locality, these events are called State Coupled Events. It should be noted that any State Coupled Events that are not received and injected into the state machine before an Inter-State transition occurs are lost. This is even true if the state machine has transitioned back to the originating state by the time the events are injected.
The flow chart below follows the life cycle of each event once it has been received by ignite.js and injected into the current state of the state machine. There are several things worth noting. The first is that State Coupled Events are only matched against actions if the machine has not left the state originating the event, otherwise they are ignored. The second is that if no matching action is found and the current state belongs to a sub-machine, then the event is injected into the parent machine in an attempt to find a match there.

The event / action matching process
Events are associated with actions by matching the event name with patterns defined in the actions object. The matching process aims to find the most specific match first and then looks for successively more general matches if no match is found. At each step in the matching process a prefix match is performed first, followed by a postfix match if the prefix match fails.
For Example, the event name 'fs.readFile.done' would cause the following order of strings to be matched against patterns defined in the actions object.
'fs.readFile.done', prefix'.readFile.done', postfix'fs.readFile', prefix'.done', postfix'fs', prefix'', catch-all
Deferring events
It is quite possible that when events are injected into a state machine the current state is not the intended recipient. In this case, the current state can define an action to indicate that the event should be deferred until the machine is in a different state, and hence the event is not lost. When an event is deferred, it is added to the end of a list containing all events that have been deferred by states. Each time the state machine enters a new state the oldest event in the deferred event list is removed and injected into the state. Depending on the actions defined in the new state, the event can then either be consumed, ignored or deferred again.
For example, the following State Machine Generator Function describes a simple web server returning the text 'Hello World!' to incoming connection requests. Notice that the 'Message' state defers any 'http.request' connection events, this way they are not lost if the machine is busy responding to a request when a new request is received.
var helloWorldServer = function (fire, port) {
var httpServer = require('http').createServer();
this.stateState = 'Init';
this.states = {
Init: {
entry: function (port) {
fire.$regEmitter('httpServer', httpServer, true);
httpServer.listen(port);
return 'Listen';
}
},
Listen: {
actions: {
'httpServer.request': 'Message'
}
},
Message: {
entry: function (request, response) {
response.writeHead(200, {
'Content-Type': 'text/html'
});
response.write('Hello World!');
response.end();
return 'Listen';
}
}
};
};
Default actions
When many states within a state machine need to respond in an identical way to specific events then it is convenient to use default actions. Default actions are defined at the State Machine Generator Function level and apply to all states within the defined machine. The actions only act as a defaults and do not take precedence over the actions explicitly defined at the state level.