Boost logo

Boost :

Subject: [boost] Two FSM libraries, one interface
From: David Bergman (David.Bergman_at_[hidden])
Date: 2009-12-06 19:47:56


NO, this is not yet another attempt of mine to irritate people by claiming that the interfaces of Statechart and MSM are similar ;-)

When evaluating MSM, both from an interface and performance (read "abuse" ;-)) point of view, I decided to create a common wrapper for both libraries, since I host this awkward notion that the mapping from such a (much more abstract and less verbose) wrapper to the underlying libraries should not vary that much. Be that due to the interfacial commonalities between the two libraries or my great mapping skills :-) Anyway, I just got tired of being a grumpy old passive man, so decided to put some of my money (or time) where my mouth is. I will write more about this side effect of my analysis in a blog post later, as well as provide access to the actual header file. I just want to gauge the interest for such an abstraction layer *for non-complex FSM needs*. I do not claim any commutation, but at least we have a decent sub diagram with arrows from my abstract interface to each one of Statechart and MSM for non-complex cases.

I currently imaginatively call this common interface "abstract_fsm.h". It preprocesses a common description to either Statechart or MSM, decided by setting either "USE_SC" or "USE_MSM". It is currently limited to non-nested machines without guards, and only handles the specification, not execution. But, the end result is pretty cool. So, hold on to you LISP hats (there will be parentheses...) and see for yourself. This is a simple FSM app using that abstraction:

#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());
}

Everything is contained in that FSM_DEF construct, including events and transitions (including the source states). One cool thing is that the actions are right there. I have no clue as to the general usability of this abstract layer, but it serves the immediate purpose of testing runtime performance of the two libraries by merely using USE_SC or USE_MSM.

This application is preprocessed to this Statechart app (by using -E -DUSE_SC...):

#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 when preprocessing to an MSM app (by using -E -DUSE_MSM...):

#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());
}

It is worth noting that once I had wrestled with Boost.Preprocessor and created a mapping to Statechart, the MSM mapping took 14 minutes, and a lot of meta functions were common between the two cases.

/David


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk