The NPF State Engine

Introduction

Applications have state. State is the condition of the app: are we in edit mode or drawing mode? Are we in the midst of executing an SQL query and waiting for results to come back? Have we loaded a document to edit yet?

Many applications represent this state explicitly, mostly in the form of the assumption: if we are executing this line of code, we must be in this state. As applications age, it is likely that more and more entry points will be added to blocks of code, so that the assumptions about state will become invalid, and explicit state representation will have to be added in the form of if-blocks that check conditions to see what should be done.

The purpose of the NPF state engine is to make it easy to represent state in the design, to generate code that makes it easy to tell what state the application is in at all times, and to make it easy to separate the user interface from the core code. How this is done is the tale told in this chapter.

It is important to realize what the NPF state engine is not: it is not a state machine in the formal sense. There is much more too it than just an intial state, a terminal state and a transition table. NPF states are active objects, with event handling methods that serve as the "brain" of the application. To an extent, the NPF state engine can be seen as an application-level instantiation of the State Design Pattern: what state is current will determine how the application responds to all events.

Overview

There are two main classes and several helper classes and structs that make up the state engine. The first main class is the state engine itself, which is a class utility called NPFStateEngine. It has four important methods:

a) static void init(const std::string& strInitialStateName);

init() must be called to set the initial state before any other methods are called. The argument is just the name of the element type is to be used for the initial state.

b) static NPFEventStatus handleEvent(NPFEvent* pEvent)

handleEvent() is used for all event handling. The typical way to use this, which is discussed in more detail below, is in a UI event handling routine. Any important data (such as mouse position, what button was pushed, etc.) are pulled out of the UI event and an appropriate NPFEvent object is built and passed in to the state engine. This may result in a change of the current state. The details of the NPFEvent struct are discussed below.

c) static NPFState* getCurrentState()

This is just a way of getting at the current state, should you need it. It should be rarely used.

d) static void final()

This should be called when the application is shutting down to clean up memory held by the state engine.

The state engine maintains a stack of states: it is possible to make a state transition such that the old current state is kept around as the parent of the new current state. If a stack of states is built up in this way, it is possible to "roll back" along the stack and restore any of the stacked states as the current state, which destroys the states that have been rolled back over. State transitions where the parent is kept around are called transitions to "successor" states. State transitions where the parent is replaced by the new current state are called transitions to "replacement" states.

The other main class is the state base class, NPFState. The code generator produces special derived classes when it sees and element with NPFState as a base class. The important information that states contain is:

  1. A list of successor states
  2. A list of replacement states
  3. Event handlers
There are two things a state can do: make a state transition, and handle an event. It is possible for an event handler to trigger a state transition, so that after the state's event handler has returned, but before the state engine's event handler has returned, a state transition occurs.

As noted above, state transitions can be of various kinds: successors, replacements, and roll-backs. Whether a transition is to a successor or a replacement is determined entirely by the type name of the new state: if it is on the replaement list it is a replacement transition, and not otherwise.

The details of how all this works in practice are described in the examples below.

Syntax

The markup for states is quite simple:

<!ELEMENT NPFState.state1 ((successor1, successor2),(repl1, repl2))>
<!ELEMENT NPFState.state1
NPFEvent (Event1 | Event2 | Event3) "Event1"
>
Naming a state as a successor or replacement to a particular parent type is
based on its place in the content model of the parent: the first sub-group
contains the names of one or more successor states, the second sub-group
contains the names of one or more replacement states.  If there are only
replacement states, you put the string NPF_DUMMY_STATE in the first
sub-group. If there are only successor states, just leave the replacements
sub-group out entirely, and put the successors sub-group at the top level
(that is, don't nest it in double parentheses.)

The special element name NPFEvent is used to generate event handlers. In the above example, the methods OnEvent1(), OnEvent2() and OnEvent3() would be generated, as well as the code to call them. Event types are identified by enumerations that are generated for each state, so event lookup is efficient, as it involves numeric rather than string comparison, but user code can still refer to events by a sensibly named constant.

Generated Code

The code generated for the above example looks like:


Implementation:
  State Conversion
  Event handling

Tricks for Transitions:

  Rollback states
  Interally generated transitions
  Keep current flag

Summary: what we can do