Read Part One for a refresher first. We left that article with the hazy outline of how we can implement a state machine from an application's perspective by deferring state transitions and behavior to State subclass objects (which we still need to write) and have these objects swapped in/out depending on our requirements. We now get into some more detail on this and show a full (yet trivial) example.

As a reminder, we have a State base class that will provide some common functionality that all states should have and we have an Application class that maintains a pointer to the current state. The App's run() method is called and this starts the main loop in which the current state's update() method is continuously called. Every loop we also listen and perform state transitions as required.

The ability to transition from one state to another is actually handled by the current state calling the App's queueStateTransition() function, so we need to give all State objects the ability access the App object. We do this in State's constructor. Here is our updated State.h:


class State
{
protected:
  App* pApp;
  
  virtual void handleMouse(); // get mouse input and act
  virtual void handleKeyboard(); // get keyboard input and act
  virtual void draw(); // draw screen if needed
public:
  State(App* _app) : pApp(_app) {   }
 
  virtual void update(); // updated every tick
  // functions used to transition between states
  virtual bool transitionInto(State* fromState);
  virtual void transitionOutOf(State* toState);
};

Our State base class has only three public functions: update(), transitionInto() and transitionOutOf() and we now show the implementation of those virtual functions here:


void State::update()
{
  updateKeyboard();
  updateMouse();
  draw();
}
void State::transitionInto(State* fromState) { }
void State::transitionOutOf(State* toState) { }

As you can see, our transition functions are empty as these functions will be specific to each subclass' needs. There is no functionality that needs to be performed at the base class level (though if we wanted to log all state transitions we could certainly add this to the base State class). For our update() function, we defer to protected member functions updateKeyboard(), updateMouse() and draw().

The protected draw() method is also an empty function at the base State level as each subclass will have their own requirements for drawing to the screen. However, there is an opportunity to commonize polling the keyboard and mouse input at the State level and forwarding these input events to specific handlers. Because the implementation of these two functions are very platform-specific, I will leave them transparent for now.

Based on the above, we now have everything we need to start implementing specific State objects except one: How does the application and State objects know and identify the state subclasses? As an example of this problem, consider a simple example:

Our application starts with a Title Screen then waits utnil the user presses a key before transitioning into the Main Menu. Candidates for states are: TitleScreen and MainMenu. So we create two subclasses:


class TitleScreen : public State { ... }
class MainMenu : public State { ... }

Now that we've created the classes, we need to instantiate objects of the above types. We want our App to start in the TitleScreen object. When the user presses a key, we want the TitleScreen state to initiate a transition to the MainMenu state. The questions are: a) How does the App object acquire a pointer to the TitleScreen object? and b) How does the TitleScreen object acquire a pointer to the MainMenu state?

There are several possible solutions for this problem:

  • Put all subclass declarations into a specific header and include this header in the App module and in all state subclass modules. This has the disadvantage of forcing a recompile of all your state subclasses and App class when we add new states but it has the advantage of being the simplest.
  • Put all subclass declarations into their own header file. Include all headers in the App module and include only those state subclass headers that are specifically needed in each state subclass module. This has the advantage of reducing compile time for the subclasses (we can never transition from the Title Screen directly into the Game, so the Title Screen state does not need to know the InGame class), however we still are forcing our App module to do a recompile every time we add a state.
  • Put all subclass declarations into their own header file and create a StateManager class whose job it is to manage state subclasses and "shield" the App class from state subclass changes. This is an example of the Proxy Design Pattern. It has the advantage of reducing compile time even more but it requires more work to set up the proxy class.
  • For simplicity's sake we will choose the first option of just including all the subclasses into the App class and letting the App class be the "State Manager". However, use of the Proxy Design Pattern would probably be wisest if you have a lot of states.

    Let's put in that infrastructure using the example of above of TitleScreen and MainMenu states:

    We prepend State.h with an enumeration:


    enum EStateType {
      State_TitleScreen = 0,
      State_MainMenu // 1
      NUMBER_OF_STATES
    };

    Sidenote: The NUMBER_OF_STATES identifier is a nice little enum trick so we always know the total number of states available.

    And we add a EStateType member to the State class and constructor:

    class State {
    protected:
      EStateType id; // newly added
      App* pApp;
    // ...
    public:
      State(App* _app, EStateType _id) : pApp(_app), id(_id) { }
    // ...

    And now we flesh out our state subclasses. Below we show how to construct the TitleScreen class...


    #include "State.h";
    class TitleScreen : public State
    {
    public:
      TitleScreen(App* pApp) : State(pApp, State_TitleScreen) { ... }
    };

    We do a similar thing in MainMenu...

    And now inside App we add a collection of State objects and initialize things in the App constructor:


    #include <map>
    #include "State.h"
    #include "TitleScreen.h"
    #include "MainMenu.h"
    class App
    {
      std::map<EStateType, State*> stateMap;
    public:
      App()
      {
        stateMap[State_TitleScreen] = new TitleScreen(this);
        stateMap[State_MainMenu] = new MainMenu(this);
        
        pCurrentState = stateMap[State_TitleScreen];
      }
      State* getState(EStateType type) { return stateMap[type]; }
    };

    Of course in App's destructor we will make sure and delete all those State objects so we don't cause a memory leak, right? Right. Use your imagination (and your in-depth knowledge of the std::map template) for this...

    Note that we don't really need to keep track of the enumeration values because that is each subclass' responsibility (as shown above in the StateTitleScreen constructor). Also note that we provided an accessor function in the App class called getState() that will allow anyone to get a pointer to a specific State object.

    Ok, now that we've fleshed out in total how the state machine will work, we're ready to put all the pieces together with a full code example. As stated above, since we don't want to get into any platform-specific dependencies at this stage, the example will have to be really trivial but hopefully it will properly show how everything fits.

    Let's say our application has the following requirements: Print out "Hello, world!" ten times and then print out "Goodbye cruel world" once. Let's also say that instead of a 4-line C++ program we want to use the above State Design Pattern and we choose our states this way:
    Simple Hello/Goodbye State Diagram

    I decided I'd put the code on its own page because I haven't figured out a good way to embed code in a blog entry (I have to manually format indentations and special characters like < and >). The following four files show the code to implement the simple state machine above.

    State.h : defines the base State class and its two subclasses (HelloWorld and GoodbyeWorld)
    State.cpp: shows implementation of HelloWorld and GoodbyeWorld
    App.h: defines the App class
    App.cpp: shows implementation of App class and includes the main() function

    Of course the trivial nature of the example (partly attributable to the fact that we haven't supplied any implementation for keyboard or mouse handling) highlights the fact that the State Design Pattern should really only be used when there are a larger number of states with a variety of transitions that need to be properly separated. The above application could have been done in just a few lines of C/C++ code. However, I hope that the usefulness of the State Design Pattern is not lost completely...If someone has a better suggestion for an example (that doesn't involve mouse/keyboard input) please let me know.

    This really concludes the section on the State Design Pattern. In subsequent articles I'll be focusing on an SDL implementation of mouse/keyboard binding using the above State Pattern and getting into designing a user interface with simple buttons.

    §24 · January 24, 2005 · C++, Software, Technology · · [Print]

    Comments are closed.