Always having enjoyed structures with arrows and vertices, I was happy to learn that Boost was considering welcoming a new member to its family of libraries: a finite state machine library called MSM.
In my attempt to evaluate that library, it was natural to compare it with an existing Boost library for finite state machines, Statechart.
Since I do not like repeating myself, I took a few hours and created an abstract interface for finite state machines that gets translated, via the C++ preprocessor, into code for either one of these underlying finite state machine engines.
My interface, which I call abstract_fsm, is a much simpler way to define non-complex finite state machines than any other interface I have seen for C++. The main benefit is that one does not need to declare the events, states and actions, but they are automatically declared and defined from a state transition table. This avoids tedious empty declarations for the constituents and puts the focus where it should be: on the state transition table.
Enough talk. Let us see a simple example, of a machine going between the a Stopped and Playing state via Play and Stop events.
Using abstract_fsm:
- #include <iostream>
- #include"abstract_fsm.h"
- FSM_DEF(Machine, // The type of the FSM
- (Play)(Stop), // the events
- // The transitions (including the states)
- ((Stopped,
- ((Play, Playing, { std::cout << "starting to play" << std::endl; }))
- ((Stop, Stopped, { std::cout << "stopping" << std::endl; }))))
- ((Playing,
- ((Stop, Stopped, { std::cout << "staying in Stopped" << std::endl; }))))
- )
- int main(int argc, const char* argv[])
- {
- Machine mach;
- mach.initiate();
- mach.process_event(Stop());
- mach.process_event(Play());
- mach.process_event(Stop());
- }
Compiling this for the Statechart engine, via options -E -DUSE_SC for GCC, we get:
- #include <boost/statechart/event.hpp>
- #include <boost/statechart/state_machine.hpp>
- #include <boost/statechart/simple_state.hpp>
- #include <boost/statechart/transition.hpp>
- struct Play : boost::statechart::event<Play> {};
- struct Stop : boost::statechart::event<Stop> {};
- struct Stopped; struct Playing;
- struct Machine : boost::statechart::state_machine<Machine, Stopped> {
- void actStoppedPlayPlaying(const Play& evt) { std::cout << "starting to play" << std::endl; }
- void actStoppedStopStopped(const Stop& evt) { std::cout << "stopping" << std::endl; }
- void actPlayingStopStopped(const Stop& evt) { std::cout << "staying in Stopped" << std::endl; }
- };
- struct Stopped : boost::statechart::simple_state<Stopped, Machine> {
- typedef boost::mpl::list<
- boost::statechart::transition<Play, Playing, Machine, &Machine::actStoppedPlayPlaying>,
- boost::statechart::transition<Stop, Stopped, Machine, &Machine::actStoppedStopStopped>
- reactions;
- };
- struct Playing : boost::statechart::simple_state<Playing, Machine> {
- typedef boost::mpl::list< boost::statechart::transition<Stop, Stopped, Machine, &Machine::actPlayingStopStopped> > reactions;
- };
- int main(int argc, const char* argv[])
- {
- Machine mach;
- mach.initiate();
- mach.process_event(Stop());
- mach.process_event(Play());
- mach.process_event(Stop());
- }
And compiling for MSM, via options -E -DUSE_MSM on GCC, we get:
- #include <boost/msm/back/state_machine.hpp>
- #include <boost/msm/front/state_machine_def.hpp>
- struct Play {};
- struct Stop {};
- struct Stopped : boost::msm::front::state<> {};
- struct Playing : boost::msm::front::state<> {};
- struct Machine_ : boost::msm::front::state_machine_def<Machine_> {
- typedef Stopped initial_state;
- void actStoppedPlayPlaying(const Play& evt) {
- std::cout << "starting to play" << std::endl;
- }
- void actStoppedStopStopped(const Stop& evt) {
- std::cout << "stopping" << std::endl;
- }
- void actPlayingStopStopped(const Stop& evt) {
- std::cout << "staying in Stopped" << std::endl;
- }
- typedef boost::mpl::vector<
- a_row<Stopped, Play, Playing, &Machine_::actStoppedPlayPlaying>,
- a_row<Stopped, Stop, Stopped, &Machine_::actStoppedStopStopped>,
- a_row<Playing, Stop, Stopped, &Machine_::actPlayingStopStopped>
- transition_table;
- void initiate() {}
- };
- typedef boost::msm::back::state_machine<Machine_> Machine;
- int main(int argc, const char* argv[])
- {
- Machine mach;
- mach.initiate();
- mach.process_event(Stop());
- mach.process_event(Play());
- mach.process_event(Stop());
- }
The requirements to use my interface (which is one header file) are:
- Boost (my interface uses
Boost.Preprocessorin particular), which also includes the Statechart library - MSM (if you want to use that engine)
There are a lot of limitations and strangenesses with the current version of my interface:
- Introduces a lot of preprocessor symbols with quite generic names, thereby polluting the namespace of the compilation
- All states must have at least one outgoing transition, i.e., no truly terminal state is allowed
- Does not handle nested machines, which is quite common in UML statecharts
- Does not handle guards for transition
Furthermore, there are a lot of neat things in the underlying engines that are outside the scope of pure finite state machines (or DFA’s) that are not implemented by my interface.
You can download the latest version of the interface from Github
boost C++ dfa fsm msm preprocessor statechart