![]() |
![]() |
|
|
Design of a Finite State Machine C++ Template. Part Two.
Author / Автор: Sergey SatskiyPublication date / Опубликовано: 06.08.2004
|
...
#define FSMStateType string
#define FSMEventType Events
SStateMachine< StateType,
EventType,
SEmptyFunctor<StateType,EventType>,
SThrowStrategy<EventType>
>
MyMachine(
FSM_BEGIN( "empty" )
FSM_STATES "empty" << "number" << "identifier" << "unknown"
FSM_EVENT(letter) "identifier" << "unknown" << "identifier" << "unknown"
FSM_EVENT(digit) "number" << "number" << "identifier" << "unknown"
FSM_END
);
#undef FSMStateType
#undef FSMEventType
...
|
It is possible to note that as soon as the machine comes to the state "unknown" any event will be ignored. Here the same state "unknown" is used. The code can be rewritten using a manipulator as follows.
MyMachine(
FSM_BEGIN( "empty" )
FSM_STATES "empty" << "number" << "identifier" << "unknown"
FSM_EVENT(letter) "identifier" << "unknown" << "identifier" << NONE
FSM_EVENT(digit) "number" << "number" << "identifier" << NONE
FSM_END
);
|
The NONE manipulator indicates that nothing should be done.
It is necessary to note that there is still a tiny difference between usage of the same state in the transition table and NONE manipulator. In case of manipulator there will be no actions at all - an event is ignored. In case of specifying the same state the functions OnEnter(. . .) and OnExit(. . .) will be called if they are specified. The manipulator usage is better in my opinion. It makes the code author intention clearer.
In addition to NONE manipulator the template supports one more: EXCEPTION. The EXCEPTION manipulator will lead to an exception generation instead of a transition and resetting a machine to the start state.
Let us come back to function calls in the template described in the first part of the article. Functions are linked to states and it was required that a state type had OnEnter(. . .) and OnExit(. . .) member functions defined with the proper prototypes. It could be depicted as follows.

Sometimes it is convenient to bind function calls to states but not to transitions though. That could be depicted as follows.

Bearing in mind that the variable length list processing problem has already been solved there is only a question of a good notation for binding function calls to transitions. The operator [ ] could be used in C++. The figure 2 depicted state machine could be written as follows using the [ ] notation.
...
MyMachine(
FUNCFSM_BEGIN( Start )
FUNCFSM_STATES Start << S1 << S2 << S3
FUNCFSM_EVENT(E1) S1[f1] << NONE << S2[f3] << NONE
FUNCFSM_EVENT(E2) S2[f2] << NONE << NONE << NONE
FUNCFSM_EVENT(E3) NONE << S3[f4] << S3[f4] << NONE
FUNCFSM_END
);
...
|
A function, which should be called for a particular transition, is given in square brackets together with a new state. Surely a question appears what kind of functions could be specified here. There is a variety of options: free function, class member function, virtual class member function etc. A good solution to store a function object is proposed by one of the boost libraries (http://www.boost.org). The boost::function object is a template which allows to store and then to call anything which uses () notation. The following definition is suitable for the template.
typedef boost::function< void ( StateType & From, EventType &, StateType & To ) > CallbackType; |
Now we need to store a new state, callback function and a manipulator for each of the transitions. Two last elements are optional. A structure is suitable to store the mentioned elements.
// A bundle of the state related information
template < typename SState, typename SEvent >
struct SFuncBundle
{
SState NewState;
int Manipulator;
boost::function< void ( SState &,
SEvent &,
SState & ) > Callback;
SFuncBundle() {}
};
|
The operator [ ] implementation is not complicated. The only important detail that should be kept in mind is that there are two options: a new state with or without a callback function. In both cases the overloaded for transitions operator << expects a reference to a state. In order to simplify developers' life a template could be introduced.
template < typename Child, typename Event > class StateBase { // Should all of them be const? - No. It is easy to imagine that // some fields in the states and events should be changed typedef boost::function< void ( Child &, Event &, Child & ) > CallbackType; private: CallbackType Callback; protected: StateBase() : Callback( 0 ) {} public: inline Child & operator [] ( const CallbackType & NewCallback ) { Callback = NewCallback; return static_cast< Child & >( *this ); } inline CallbackType GetCallback( void ) { CallbackType Temporary( Callback ); Callback = 0; return Temporary; } }; |
There are some interesting details here. The GetCallback(. . .) function cannot be const. A Proxy: class, which collects information about transitions, may have many transitions into the same state with different callback functions. So as soon as information about a necessary callback is collected a function object should be cleaned. Moreover the operator [ ] cannot be const either because it memorizes a callback function and therefore cannot be applied to a const reference to a state. That circumstatnces dictate to create instances of states before its usage in a template constructor. In other words a real code will be similar to the following.
. . . class MyEvent { . . . }; class MyState : public StateBase< MyState, MyEvent > { . . . }; MyEvent E1(. . .), E2(. . .); // Could be also instaniated // in the MyMachine constructor MyState Start(. . .), S1(. . .), S2(. . .), S3(. . .); MyMachine( FUNCFSM_BEGIN( Start ) FUNCFSM_STATES Start << S1 << S2 << S3 FUNCFSM_EVENT(E1) S1[f1] << NONE << S2[f3] << NONE FUNCFSM_EVENT(E2) S2[f2] << NONE << NONE << NONE FUNCFSM_EVENT(E3) NONE << S3[f4] << S3[f4] << NONE FUNCFSM_END ); . . . |
Implementation of all the rest is similar to what is done for the first version of the template. The final prototype of the second template version is as follows.
template < typename SState,
typename SEvent,
typename SUnknownEventStrategy = SThrowStrategy<SEvent> >
|
Here there are no strategies of calling functions, which are bound to states.
New macroses are introduced to simplify a finite state machine description in a constructor. The macroses purpose and names are the same as for the first version except the added prefix FUNC.
Getting of the finite state machine as well as passing an event to a state machine is done in the same manner as for the first version of a template.
The drawbacks list of the first template version can be extended with the following:
Advantagies are the same as for the first template version.
The given drawbacks list does not lead to large overheads for the "decorative" part of code while in some cases the model of bounding callbacks to transitions seems much better.
It is possible to implement a template, which allows both models (callbacks are linked to states and to transitions) at the same time. There is a chance that it is implemented one day.
Examples source code could be found on the author's web site: (http://satsky.spb.ru).