This is a quick post about how I’ve been approaching state management in the Cinder applications I’ve been building lately. Typically an app will have multiple modes of operation. This is true for all sorts of platforms, from mobile and websites to installations and kiosks. The approach I’m going to share isn’t really specific to Cinder apps, but pretty much any case where there is a basic setup -> update <-> draw event loop happening. It solves a very basic problem where there is a need to perform a different set of logic or render different content to the screen based on the current mode or “state” of the application.
The first step is to construct a class that holds reference to the current state of the application. This class will provide methods for getting and setting the current state of the application. Below is an example of such a class. Notice that it provides a method called setNextState()
that doesn’t doesn’t actually set the state but sets a variable called next
. The reason for this is explained below. It also defines a method called getCurrentState()
and getPreviousState
. Nothing magical is happening with these two methods. They simply return values indicating the current and previous state values.
namespace state { static const int INIT = 0; static const int PLAY = 1; class State { public: State() { current = -1; } ~State(){ } // -- set the state to be committed in next call to commitState() void setNextState( int newState ) { if ( newState != current ) { if ( validateState( newState ) ) { next = newState; } } } // -- return true if there has been a change in state bool commitState() { if ( next != current ) { previous = current; current = next; return true; } else { return false; } } // -- return the current state int getCurrentState() { return current; } int getPreviousState() { return previous; } // -- return true if the new state is valid bool validateState( int newState ) { return newState == INIT || newState == PLAY; } private: int current; int previous; int next; }; }
We can use the value returned by getCurrentState()
to determine which state the app is in and update our app accordingly in the update()
and draw()
methods as demonstrated below.
class StateManagementApp : public AppBasic { public: void setup(); void update(); void draw(); State appState; }; void StateManagementApp::setup() { setFrameRate( 60.0f ); appState.setNextState( INIT ); } void StateManagementApp::update() { switch( appState.getCurrentState() ) { case INIT: // -- do something break; case PLAY: // -- do something else break; } } void StateManagementApp::draw() { gl::clear( Color( 0, 0, 0 ) ); switch( appState.getCurrentState() ) { case INIT: // -- draw for init state break; case PLAY: // -- draw for play state break; } }
This is all pretty simple. Now let’s look at how the setNextState()
and commitState()
are useful. It is often the case that when a change in state has occurred that you will want to perform some set of logic only once for that specific state and not every time the update
method is called. This is where the two previously mentioned methods come in handy. By calling commitState()
at the beginning of each update method and assigning a boolean variable to the returned boolean value of commitState()
you can determine whether or not a change occurred since the last update. Using this boolean value you can then perform a one time set of logic for a given state. The setNextState
method allows you to specify which state should become the current one next time commitState()
is called. Consider the following:
void StateManagementApp::update() { bool change = appState.commitState(); switch( appState.getCurrentState() ) { case INIT: // -- do something if( change ) { // -- set up something here once } else { // -- do the same thing for the INIT state each time update() is called } break; case PLAY: if( change ) { // -- set up something here once } else { // -- do the same thing for the PLAY state each time update() is called } break; } } void StateManagementApp::draw() { gl::clear( Color( 0, 0, 0 ) ); switch( appState.getCurrentState() ) { case INIT: // -- draw for init state break; case PLAY: // -- draw for play state break; } }
It could be argued that the one time change is it’s own specific state. This is a valid point however, I prefer to think of it like this. It reduces the number of state constants and I find it to be generally more clear than adding an extra constant just to define a change.
As I mentioned before, this approach is not at all specific to Cinder. I’ve used similar setups in Flash and Python applications. If you find it useful or flawed let me know by leaving a comment.