3

I want to implement the GUI as a state machine. I think there are some benefits and some drawbacks of doing this, but this is not the topic of this questions.

After some reading about this I found several ways of modeling a state machine in C++ and I stuck on 2, but I don't know what method may fit better for GUI modeling.

  1. Represent the State Machine as a list of states with following methods:

    • OnEvent(...);
    • OnEnterState(...);
    • OnExitState(...);

    From StateMachine::OnEvent(...) I forward the event to CurrentState::OnEvent(...) and here the decision to make a transition or not is made. On transition I call CurrentState::OnExitState(...), NewState::OnEnterState() and CurrentState = NewState;

    With this approach the state will be tightly coupled with actions, but State might get complicated when from one state I can go to multiple states and I have to take different actions for different transitions.

  2. Represent the state machine as list of transitions with following properties:

    • InitialState
    • FinalState
    • OnEvent(...)
    • DoTransition(...)

    From StateMachine::OnEvent(...) I forward the event to all transitions where InitialState has same value as CurrentState in the state machine. If the transition condition is met the loop is stopped, DoTransition method is called and CurrentState set to Transition::FinalState.

    With this approach Transition will be very simple, but the number of transition count might get very high. Also it will become harder to track what actions will be done when one state receives an event.

What approach do you think is better for GUI modeling. Do you know other representations that may be better for my problem?

Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
  • 1
    Have you considered using a state-machine library like [Boost.Statechart](http://www.boost.org/doc/libs/release/libs/statechart/doc/index.html) or [Boost.Meta State Machine](http://www.boost.org/doc/libs/release/libs/msm/doc/HTML/index.html)? – Björn Pollex Dec 07 '12 at 13:52
  • I assume you mean `current_state->OnEvent()`, not `CurrentState::OnEvent()`? – Ben Voigt Dec 07 '12 at 13:52
  • @BjörnPollex: What do those libraries offer that makes it worth having to step through Boost code when you debug? – Ben Voigt Dec 07 '12 at 13:53
  • @BjörnPollex I checked Boost.Statechar and the states are bound with the state machine at compile time. I don't want this. Please correct me if I'm wrong – Mircea Ispas Dec 07 '12 at 13:58
  • @BenVoigt: You can [selectively ignore](http://blogs.msdn.com/b/andypennell/archive/2004/02/06/69004.aspx) classes matching a specific pattern, i.e. "boost::*" – MSalters Dec 07 '12 at 14:00
  • @MSalters: What about this question led you to believe Felics is using Visual Studio? Because I can't find any indication whatsoever. – Ben Voigt Dec 07 '12 at 14:02
  • @BenVoigt: That was just an example; GDB has [similar functionality](http://stackoverflow.com/a/1454460/15416) – MSalters Dec 07 '12 at 14:12
  • @BenVoigt: For one things they offer a nice syntax for defining a state-machine (at least MSM does so, I haven't actually used Statechart). – Björn Pollex Dec 07 '12 at 14:15
  • @Felics: This might be true, I haven't actually used Statechart though, so I can't say for sure. – Björn Pollex Dec 07 '12 at 14:16

5 Answers5

3

Here is a third option:

  • Represent the state machine as a transition matrix
    • Matrix column index represents a state
    • Matrix row index represents a symbol (see below)
    • Matrix cell represents the state machihe should transit to. This could be both new state or the same state
    • Every state has OnEvent method which returns a symbol

From StateMachine::OnEvent(...) events are forwarded to State::OnEvent which returns a symbol - a result of execution. StateMachine then based on current state and returned symbol decides whether

  • Transition to different state must be made, or
  • Current state is preserved
  • Optionally, if transition is made, OnExitState and OnEnterState is called for a corresponsing states

Example matrix for 3 states and 3 symbols

0 1 2
1 2 0
2 0 1

In this example if if machine is in any od the states (0,1,2) and State::OnEvent returns symbol 0 (first row in the matrix) - it stays in the same state

Second row says, that if current state is 0 and returned symbol is 1 transition is made to state 1. For state 1 -> state 2 and for state 2 -> state 0.

Similary third row says that for symbol 2, state 0-> state 2, state 1 -> state 0, state 2 -> state 1

The point of this being:

  1. Number of symbols will likely be much lower than that of states.
  2. States are not aware of each other
  3. All transition are controlled from one point, so the moment you want to handle symbol DB_ERROR differently to NETWORK_ERROR you just change the transition table and don't touch states implementation.
1

I don't know if this is the kind of answer you are expecting, but I use to deal with such state machines in a straightforward way.

Use a state variable of an enumerated type (the possible states). In every event handler of the GUI, test the state value, for instance using a switch statement. Do whatever processing there needs to be accordingly and set the next value of the state.

Lightweight and flexible. Keeping the code regular makes it readable and "formal".

  • This is one possible implementation for first version - for one state you evaluate the input and take actions. I want to avoid huge "evaluate input/take actions" function when from one state I can go to many states based on input. – Mircea Ispas Dec 07 '12 at 13:46
  • @Felics: More often it ends up as "for one input, I can go to many states depending on current state". – Ben Voigt Dec 07 '12 at 13:49
  • The full decision table can have NI x NS entries (number of inputs times the number of states). If you want to avoid huge function, the table must be sparse, i.e. numerous entries being trivial. You can compress the table on the inputs or on the states, depending on what compresses best. –  Dec 07 '12 at 21:23
1

I'd personally prefer the first method you said. I find the second one to be quite counter-intuitive and overly complicated. Having one class for each state is simple and easy, if then you set the correct event handlers in OnEnterState and remove them in OnExitState your code will be clean and everything will be self contained in the corresponding state, allowing for an easy read.

You will also avoid having huge switch statements to select the right event handler or procedure to call as everything a state does is perfectly visible inside the state itself thus making the state machine code short and simple.

Last but not least, this way of coding is an exact translation from the state machine draw to whatever language you'll use.

BlackBear
  • 22,411
  • 10
  • 48
  • 86
  • The only problem here is that it becomes difficult to keep additional persistent data. You can't keep it in member variables of the state, even inherited ones, because each state has to be a different object in order to get virtual dispatch. So you end up needing to pass a helper structure to every method. It is really easy to have per-state data, however, such as counters. – Ben Voigt Dec 07 '12 at 13:51
  • @BenVoigt: Persistend data belongs to the state machine itself, and every state will receive a reference to its owner state machine when constructed. – BlackBear Dec 07 '12 at 13:53
  • Exactly, you need to go through another object to get to your data. Not a show-stopper, but certainly does make the code much more verbose. – Ben Voigt Dec 07 '12 at 13:55
  • @BenVoigt Not really. You can add a template state and fill it out with methods from the state machine. You will have same object in all states - the state machine itself. – Mircea Ispas Dec 07 '12 at 13:56
  • @Felics: That sounds a little different from what BlackBear proposed. In his design every state is a separate class. Since you can't change the class type of an object after construction, that means each state needs to be a separate object also. – Ben Voigt Dec 07 '12 at 14:05
  • @BenVoigt In my case every state is a different object too. But this doesn't stops me to call a member function callback from state object and these functions to be in the same object for all states. The template mechanism will generate separate classes for every state. – Mircea Ispas Dec 07 '12 at 14:09
0

I prefer a really simple approach for this kind of code.

  • An enumeration of states.
  • Each event handler checks the current state before deciding what action to take. Actions are just composite blocks inside a switch statement or if chain, and set the next state.
  • When actions become more than a couple lines long or need to be reused, refactor as calls to separate helper methods.

This way there's no extra state machine management metadata structures and no code to manage that metadata. Just your business data and transition logic. And actions can directly inspect and modify all member variables, including the current state.

The downside is that you can't add additional data members localized to one single state. Which is not a real problem unless you have a really large number of states.

I find it also leads to more robust design if you always configure all UI attributes on entry to each state, instead of making assumptions about the previous setting and creating state-exit behaviors to restore invariants before state transitions. This applies regardless of what scheme you use for implementing transitions.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
0

You can also consider modelling the desired behaviour using a Petri net. This would be preferable if you want to implement a more complex behaviour, since it allows you to determine exactly all possible scenarios and prevent deadlocks.

This library might be useful to implement a state machine to control your GUI: PTN Engine

Alejandro Montilla
  • 2,626
  • 3
  • 31
  • 35
vldtecno
  • 11
  • 4