Stewart
Stewart

Reputation: 4982

Boost MSM switching on current state

I have a real-time loop and I need to do work each iteration depending on which state I'm in.

I am using Boosts Meta State Machine library to implement my state machine. What's the best way to call a function based on which state I'm in?

Let's say that I have a super-simple state machine:

//Events
struct run {};
struct stop {}; 

struct MyStateMachine : public boost::msm::front::state_machine_def<MyStateMachine>
{
    // FSM states
    struct Idle : public boost::msm::front::state<> {};
    struct Running : public boost::msm::front::state<> {};

    typedef Idle initial_state;

    // transition actions
    void do_start(run const&)  { std::cout << "Starting\n"; }
    void do_stop(stop const&)  { std::cout << "Stopping\n"; }

    struct transition_table : boost::mpl::vector<
        //    Start     Event     Next     Action               
      a_row < Idle     , run    , Running, &MyStateMachine::do_start   >,
      a_row < Running  , stop   , Idle   , &MyStateMachine::do_stop    >
    > {};
};

I can see a few ways to do work based on which state we are in:

1) switch on the current state

class MyModule : RealTimeModule
{
    void Init() override  { // Called before realtime
        fsm_.start();
    }

    void Step() override {  // Called once each iteration during realtime

        switch(fsm_.current_state()[0])
        {
        case 0:      do_idle();     break;
        case 1:      do_run();      break;
        default:     assert();
        }
    }

    void DoEvent(Event e) override { // Called once per received event

        fsm_.process_event(e);
    }

private: 
    boost::msm::back::state_machine<MyStateMachine> fsm_;
};

This doesn't seem great because there is no intuitive way to know that case 0 is Idle or that case 1 is Running.

2) Add transitions and events to represent each iteration

//Events
struct run {}; 
struct stop {};
struct step {}; // !!!NEW EVENT!!!

struct MyStateMachine : public boost::msm::front::state_machine_def<MyStateMachine>
{
    ...
    // transition actions
    void do_start(run const&)  { std::cout << "Starting\n"; }
    void do_stop(stop const&)  { std::cout << "Stopping\n"; }
    void do_idle(step const&)  { std::cout << "Idling  \n";  } //!!!NEW ACTION!!!
    void do_run (step const&)  { std::cout << "Running \n";  } //!!!NEW ACTION!!!

    struct transition_table : boost::mpl::vector<
        //    Start     Event     Next     Action               
      a_row < Idle     , run    , Running, &MyStateMachine::do_start   >,
      a_row < Running  , stop   , Idle   , &MyStateMachine::do_stop    >,
      a_row < Idle     , step   , Idle   , &MyStateMachine::do_idle    >, //!!!NEW TRANSITION!!!
      a_row < Running  , step   , Running, &MyStateMachine::do_step    > //!!!NEW TRANSITION!!!
    > {};
};

class MyModule : RealTimeModule
{
    ...
    void Step() override {
        fsm_.process_event(step); //!!!SIMPLIFIED STEP!!!
    }
    ...
};

This seems a little better, but my issue here is that if I want to implement boost::msm::front::state<>::on_entry() or on_exit, then those functions will be called each iteration instead of on a transition to that state.


I've found that it is possible to call an action with an event without a transition using the functor front-end as used here. The row in the transition table would look like this:

msm::front::Row < Idle, step, msm::front::none, step_idle, msm::front::none >

However, in order to use that, I need to make my actions looks like this:

struct do_step {
    template <class Event, class Fsm, class SourceState, class TargetState>
    void operator()(Event const&, Fsm&, SourceState&, TargetState&) const {
        std::cout << "Idling" << std::endl;
    }
};

Perhaps it's a style thing, but that seems wayyy too verbose to be a good solution. Is there a way to add something like msm::front::none by using the simple front-end instead of the functor front-end?

Upvotes: 2

Views: 1411

Answers (1)

Stewart
Stewart

Reputation: 4982

Ok I found an answer. It irks me that I really need to read the uncommented boost headers instead of documentation or references to figure out what to do.

I needed to use the a_irow structure which does not involve a transition, but rather just calls the action:

struct transition_table : boost::mpl::vector<
    //    Start     Event     Next     Action               
  a_row < Idle     , run    , Running, &MyStateMachine::do_start   >,
  a_row < Running  , stop   , Idle   , &MyStateMachine::do_stop    >,
 a_irow < Idle     , step   ,          &MyStateMachine::do_idle    >,
 a_irow < Running  , step   ,          &MyStateMachine::do_step    >
> {};

In summary these are the types of rows that can be added:

 a_row<Source, Event, Target, Action       >
  _row<Source, Event, Target               >
   row<Source, Event, Target, Action, Guard>
 g_row<Source, Event, Target,         Guard>
a_irow<Source, Event,       , Action       >
  irow<Source, Event,         Action, Guard>
g_irow<Source, Event,                 Guard>
 _irow<Source, Event,                      >  // Forces events to be ignored

With:

typename Source;
class    Event;
typename Target;
typedef void (Derived::*action)(Event const&) Action;
typedef bool (Derived::*guard)(Event const&)  Guard;

Reference: boost/msm/front/state_machine_def.hpp

Upvotes: 2

Related Questions