C/C++

State Machine Design in C++

By David Lafreniere, May 01, 2000

It's not all that hard to implement a finite-state machine, unless it's very large, and you have to worry about multithreading, and ...

StateMachine Usage

StateMachine is used as an inherited base class. The Motor class is
an example of how to use it (see Listings Four and Five).
Motor implements our hypothetical motor-control state machine, where
clients can start the motor, at a specific speed, and stop the motor. The SetSpeed
and Halt public functions are the external events into the Motor
state machine. Note that to the caller an external event is just a function
call. The state machine implementation details are hidden from the client's
view of this class. SetSpeed takes event data, as evidenced by the pointer
to the MotorData structure, which contains the motor speed. This data
structure will be deleted upon completion of the state processing, so it is
imperative that the structure inherit from EventData and be created using
operator new before the function call is made.

When the Motor class is created, its initial state is ST_Idle. The first call to SetSpeed transitions the state machine to the ST_Start state, where the motor is initially set into motion. Subsequent SetSpeed events transition to the ST_ChangeSpeed state, where the speed of an already moving motor is adjusted. The Halt event transitions to ST_Stop, where, during state execution, an internal event is generated to transition back to the ST_Idle state.

The state-machine engine knows which state function to call by using the state map. The state map maps the currentState variable to a specific state function. For instance, if currentState is 2, then the third state-map function pointer entry will be called (counting from zero). The state map is created using these three macros:

BEGIN_STATE_MAP
STATE_MAP_ENTRY
END_STATE_MAP

BEGIN_STATE_MAP starts the state map sequence. Each STATE_MAP_ENTRY that follows has as an argument a state function, which is added to the state map. END_STATE_MAP terminates the map. The completed state map just implements the pure virtual function GetStateMap defined within the StateMachine base class. Now the StateMachine base class can ask for all the state function pointers via this call.

Notice that we have to use the dastardly reinterpret_cast<> operator within the STATE_MAP_ENTRY macro to cast the derived class member function pointer to a StateMachine member function pointer. It is necessary to perform this upcast since the StateMachine base class has no idea what the derived class is. So, it is imperative that the entries provided to STATE_MAP_ENTRY are really member functions of an inheriting class and that they conform to the state function signature discussed earlier. Otherwise bad things will happen.

Each state function must have an enumeration associated with it. These enumerations are used to store the current state of the state machine. In Motor, E_States provides these enumerations, which are used later in the transition map. It is important that the enumeration order matches the order provided within the state map. This way, we can tie a state enumeration to a particular state function call. EVENT_IGNORED and CANNOT_HAPPEN are two other constants used in conjunction with these state enumerations. EVENT_IGNORED tells the state engine not to execute any state, just return and do nothing. CANNOT_HAPPEN tells the state engine to fault. This is an abnormal catastrophic failure condition that is never suppose to occur.

The last detail to attend to are the state transition rules. How does the state machine know what transitions should occur? The answer is the transition map, which is created using these macros:

BEGIN_TRANSITION_MAP
TRANSITION_MAP_ENTRY
END_TRANSITION_MAP

Each external event function has a transition map. BEGIN_TRANSITION_MAP starts the map. Each TRANSITION_MAP_ENTRY that follows indicates what the state machine should do based upon the current state. The number of entries in each transition map must match the number of state functions exactly. In our example we have four state functions, so we need four entries. The location of each entry matches the order of state functions defined within the state map. Thus, the first entry within the Halt function indicates an EVENT_IGNORED. This is interpreted to mean, "If a Halt event occurs while the current state is state Idle, just ignore the event." Similarly, the third entry means, "If a Halt event occurs while current is state Start, then transition to state Stop." END_TRANSITION_MAP terminates the map. The argument to this end macro is the event data, if any. Halt has no event data so the argument is a null pointer, but ChangeSpeed has data so it is passed in here.

The transition map is basically a lookup table indexed by the currentState variable to determine the course of action. This information is passed to the ExternalEvent function as an argument along with the event data. When the StateEngine function executes, it looks up the correct state function pointer by calling GetStateMap:

const StateStruct*
pStateMap = GetStateMap();

Then, based upon the currentState variable, it calls one of the state functions in the map:

(this->*pStateMap[currentState].pStateFunc)(pDataTemp);

After the state function has a chance to execute, it deletes the event data, if any, before checking to see if any internal events were generated.

Generating Events

At this point we have a working state machine class. Let's see how to generate events to it. An external event is generated by creating the event data structure on the heap using new, assigning the structure member variables, and calling the external event function. Obviously, if the external event doesn't take event data then a data structure is not created. The following code fragment shows how a synchronous call is made. You could, of course, send the pointer to the data structure, along with some means of identifying the event, through an operating system message queue to be handled asynchronously by the destination task:

To generate an internal event from within a state function, call InternalEvent. If the destination doesn't accept event data, then the function is called with only the state you want to transition to:

InternalEvent(ST_IDLE);

In the example above, once the state function completes execution the state machine will transition to the ST_Idle state. If, on the other hand, event data needs to be sent to the destination state, then the data structure needs to be created on the heap and passed in as an argument:

Multithread Safety

To prevent preemption by another thread when the state machine is in the process of execution, the StateMachine class uses semaphores within the StateEngine function. Before the external event is allowed to execute, the semaphore is locked. When the external event and all internal events have been processed, the semaphore is unlocked, allowing another external event to enter the state machine instance.

Comments indicate where the semaphore lock and unlock should be placed if the application is multithreaded. Note that each StateMachine object should have its own instance of a semaphore. This prevents a single instance from locking a semaphore and preventing all other StateMachine objects from executing.

Benefits

Implementing a state machine using this method as opposed to the old switch statement style may seem like extra effort. However, the payoff is in a more robust design that is capable of being employed uniformly over an entire multithreaded system. Having each state in its own function provides easier reading than a single huge switch statement, and allows unique event data to be sent to each state. In addition, validating state transitions prevents client misuse by eliminating the side effects caused by unwanted state transitions.

This implementation offers easy use for the inheriting classes. With the macros it lets you just "turn the crank" without much thought given to the underlying mechanics of how the state engine operates. This allows you more time to concentrate on more important things, like the design of the state transitions and state function implementation.

Reference

David Lafreniere has designed hardware and software over the past ten years. He currently works at PropHead Development, Inc., a software-consulting firm, designing software for a variety of embedded and PC-based applications. He can be reached at lafreni@pacbell.net.

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!