QP/C
5. Elaborating State Machines of Active Objects

This QP/C Tutorial is adapted from Chapter 1 of Practical UML Statecharts in C/C++, Second Edition
by Miro Samek, the founder and president of Quantum Leaps, LLC.

qp_tutorial.jpg

Prev: 4. Designing an Event-Driven Application
Next: 6. Defining Event Signals and Event Parameters

I hope that the analysis of the sequence diagram in Figure 4-1 makes it clear that actions performed by an active object depend as much on the events it receives, as on the internal mode of the object. For example, the Missile active object handles the TIME_TICK event very differently when the Missile is in flight (Figure 4-1(12)) compared to the time when it is not (Figure 4-1(3)). The best known mechanism of handling such modal behavior is through state machines because a state machine makes the behavior explicitly dependent on both the event and the state of an object. In Chapter 2 of Practical UML Statecharts in C/C++, Second Edition I introduce UML state machine concepts more thoroughly. In this section, I give a cursory explanation of the state machines associated with each object in the "Fly 'n' Shoot" game.

5.1 The Missile Active Object

I start with the Missile state machine shown in Figure 5-1, because it turns out to be the simplest one. The explanation section immediately following the diagram illuminates the interesting points.

Note:
A UML state diagram like Figure 5-1 preserves the general form of the traditional state transition diagrams, where states are represented as nodes and transitions as arcs connecting the nodes. In the UML notation the state nodes are represented as rectangles with rounded corners. The name of the state appears in bold type in the name compartment at the top of the state. Optionally, right below the name, a state can have an internal transition compartment separated from the name by a horizontal line. The internal transition compartment can contain entry actions (actions following the reserved symbol "entry"), exit actions (actions following the reserved symbol "exit"), and other internal transitions (e.g., those triggered by TIME_TICK in Figure 5-1(3)). State transitions are represented as arrows originating at the boundary of the source state and pointing to the boundary of the target state. At a minimum, a transition must be labeled with the triggering event. Optionally, the trigger can be followed by event parameters, a guard, and a list of actions.

Fig1.05.jpg
Figure 5-1 Missile state machine diagram.
Note:
The UML intentionally does not specify the notation for actions. In practice, the actions are often written in the programming language used for coding the particular state machine. In all state diagrams in this book, I assume the C programming language. Furthermore, in the C expressions I refer to the data members associated with the state machine object through the me-> prefix and to the event parameters through the e-> prefix. For example, the action me->x = e->x; means that the internal data member x of the Missile active object is assigned the value of the event parameter x.

5.2 The Ship Active Object

The state machine of the Ship active object is shown in Figure 5-2. This state machine introduces the profound concept of hierarchical state nesting. The power of state nesting derives from the fact that it is designed to eliminate repetitions that otherwise would have to occur.

One of the main responsibilities of the Ship active object is to maintain the current position of the Ship. On the original LM3S811 board, this position is determined by the potentiometer wheel (see Figure 2-2). The PLAYER_SHIP_MOVE(x, y) event is generated whenever the wheel position changes, as shown in the sequence diagram (Figure 4-1). The Ship object must always keep track of the wheel position, which means that all states of the Ship state machine must handle the PLAYER_SHIP_MOVE(x, y) event.

In the traditional finite state machine (FSM) formalism, you would need to repeat the Ship position update from the PLAYER_SHIP_MOVE(x, y) event in every state. But such repetitions would bloat the state machine and, more importantly, would represent multiple points of maintenance both in the diagram and the code. Such repetitions go against the DRY principle (Don't Repeat Yourself), which is vital for flexible and maintainable code.

Hierarchical state nesting remedies the problem. Consider the state "active" that surrounds all other states in Figure 5-2. The high-level "active" state is called the superstate and is abstract in that the state machine cannot be in this state directly, but only in one of the states nested within, which are called the substates of "active". The UML semantics associated with state nesting prescribes that any event is first handled in the context of the currently active substate. If the substate cannot handle the event, the state machine attempts to handle the event in the context of the next-level superstate. Of course, state nesting in UML is not limited to just one level and the simple rule of processing events applies recursively to any level of nesting.

Specifically to the Ship state machine diagram shown in Figure 5-2, suppose that the event PLAYER_SHIP_MOVE(x, y) arrives when the state machine is in the "parked" state. The "parked" state does not handle the PLAYER_SHIP_MOVE(x, y) event. In the traditional finite state machine this would be the end of story—the PLAYER_SHIP_MOVE(x, y) event would be silently discarded. However, the state machine in Figure 5-2 has another layer of the "active" superstate. Per the semantics of state nesting, this higher-level superstate handles the PLAYER_SHIP_MOVE(x, y) event, which is exactly what's needed. The same exact argumentation applies for any other substate of the "active" superstate, such as "flying" or "exploding", because none of these substates handle the PLAYER_SHIP_MOVE(x, y) event. Instead, the "active" superstate handles the event in one single place, without repetitions.

Fig1.06.jpg
Figure 5-2 Ship state machine diagram.

5.3 The Tunnel Active Object

The Tunnel active object has the most complex state machine, which is shown in Figure 5-3. Unlike the previous state diagrams, the diagram in Figure 5-3 shows only the high-level of abstraction and omits a lot of details such as most entry/exit actions, internal transitions, guard conditions, or actions on transitions. Such a "zoomed out" view is always legal in the UML, because UML allows you to choose the level of detail that you want to include in your diagram.

The Tunnel state machine uses state hierarchy more extensively than the Ship state machine in Figure 5-2. The explanation section immediately following Figure 5-3 illuminates the new uses of state nesting as well as the new elements not explained yet in the other state diagrams.

Fig1.07.jpg
Figure 5-3 Tunnel state machine diagram.

5.4 The Mine Components

Mines are also modeled as hierarchical state machines, but are not active objects. Instead, Mines are components of the Tunnel active object and share its event queue and priority level. The Tunnel active object communicates with the Mine components synchronously by directly dispatching events to them via the function QHsm_dispatch(). Mines communicate with Tunnel and all other active objects asynchronously by posting events to their event queues via the function QActive_postFIFO().

Note:
Active objects exchange events asynchronously, meaning that the sender of the event merely posts the event to the event queue of the recipient active object without waiting for the completion of the event processing. In contrast, synchronous event processing corresponds to a function call (e.g., QHsm_dispatch()), which processes the event in the caller's thread of execution.

Fig1.08.jpg
Figure 5-4 The Table active object manages two types of Mines.

As shown in Figure 5-4, Tunnel maintains the data member mines[], which is an array of pointers to hierarchical state machines (QHsm *). Each of these pointers can point either to a Mine1 object, a Mine2 object, or NULL, if the entry is unused. Please note that Tunnel "knows" the Mines only as generic state machines (pointers to the QHsm structure defined in QP). Tunnel dispatches events to Mines uniformly, without differentiating between different types of Mines. Still, each Mine state machine handles the events it its specific way. For example, Mine type 2 checks for collision with the Missile differently than with the Ship while Mine type 1 handles both identically.

Note:
The last point is actually very interesting. Dispatching the same event to different Mine objects results in different behavior, specific to the type of the Mine, which in OOP is known as polymorphism. I'll have more to say about this in Chapter 3 of Practical UML Statecharts in C/C++, Second Edition.

Each Mine object is fairly autonomous. The Mine maintains its own position and is responsible for informing the Tunnel object whenever the Mine gets destroyed or scrolls out of the display. This information is vital for the Tunnel object so that it can keep track of the unused Mines.

Figure 5-5 shows a hierarchical state machine of Mine2 state machine. Mine1 is very similar, except that it uses the same bitmap for testing collisions with the Missile and the Ship.

Fig1.09.jpg
Figure 5-5 Mine2 state machine diagram.

Prev: 4. Designing an Event-Driven Application
Next: 6. Defining Event Signals and Event Parameters

logo_ql_TM.jpg

Copyright © 2002-2011 Quantum Leaps, LLC. All Rights Reserved.
http://www.state-machine.com