Search code examples
c++boosttemplate-meta-programmingboost-msm

How to exit a submachine in a hierarchial-SM that is split into multiple files? (using boost::MSM)


There is a good tutorial that explains how we can use the "exit-pseudo-state" to exit from a submachine using boost::MSM, here.

But I need to split my SM into multiple files, to keep it manageable, and here is where the problem arises.

When the sub-SM is defined in the main fsm file, everything works fine, i.e. exiting from the "exit-pseudo-state" results in a an exit from the sub-SM into the next state(sample code)

Implementing the sub-SM in a separate file, where I have to make an extra level of dummy inheritance, causes problems. This time the transition to the exit-pseudo-state in the internal sub-SM does not trigger an exit to the the next state in the parent-SM. Here is a sample code showing the problem.

As you can see from the output below, State2::on_exit() is missing at the end after exiting substate21

Testing boost::msm ...
State1::on_entry()
State1::on_exit()
State2::on_entry()
SubState21::on_entry()
SubState21::on_exit()

Thanks for your help in advance

Code included:

main file:

#include "myfsm.h"

int main()
{    
    std::cout << "Testing boost::msm ..." << std::endl;
    MyFsm fsm;
    fsm.start();

    fsm.process_event(Event1());
    fsm.process_event(Event3());
    //fsm.process_event(Event2());
}

main fsm:

#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>

#include "state2.h"
#include "events.h"

namespace msm = boost::msm;
namespace msmf = boost::msm::front;
namespace mpl = boost::mpl;

struct MyFsm_ : msmf::state_machine_def<MyFsm_>
{
    struct State1 : msmf::state<>{
        template<class Event, class Fsm> void on_entry(const Event&, Fsm&) const {std::cout << "State1::on_entry()" << std::endl;}
        template<class Event, class Fsm> void on_exit(const Event&, Fsm&) const {std::cout << "State1::on_exit()" << std::endl;}
    };    

    struct State2m : State2 {};

   // Set initial state
   typedef State1 initial_state;

   // Transition table
   struct transition_table:mpl::vector<
         msmf::Row < State1, Event1, State2m, msmf::none, msmf::none >,
         msmf::Row < State2m, Event2, State1, msmf::none, msmf::none >,
         msmf::Row < State2::exit_pt
                     <State2_::Exit2>, msmf::none, State1, msmf::none, msmf::none >
   >{};

  template<class Event, class Fsm>
   void no_transition(Event const&, Fsm&, int state){
       std::cout<<"no_transiton detected from state: "<< state << std::endl;
   }

};
// Pick a back-end
typedef msm::back::state_machine<MyFsm_> MyFsm;    

sub-SM i.e. state2.h:

#include <iostream>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
#include <boost/msm/front/functor_row.hpp>
#include "events.h"

namespace msm = boost::msm;
namespace msmf = boost::msm::front;
namespace mpl = boost::mpl;


struct State2_ : msmf::state_machine_def<State2_>{
        template<class Event, class Fsm> void on_entry(const Event&, Fsm&) const {std::cout << "State2::on_entry()" << std::endl;}
        template<class Event, class Fsm> void on_exit(const Event&, Fsm&) const {std::cout << "State2::on_exit()" << std::endl;}

        struct SubState21 : msmf::state<>{
            template<class Event, class Fsm> void on_entry(const Event&, Fsm&) const {std::cout << "SubState21::on_entry()" <<std::endl;}
            template<class Event, class Fsm> void on_exit(const Event&, Fsm&) const {std::cout << "SubState21::on_exit()" << std::endl;}
        };
        typedef mpl::vector<SubState21> initial_state;
        struct Exit2 : msmf::exit_pseudo_state<msmf::none> {};

        struct transition_table:mpl::vector<
            msmf::Row < SubState21, Event3, Exit2, msmf::none, msmf::none >
           >{};

    };
    typedef msm::back::state_machine<State2_> State2;

Solution

  • You need to define msm::back::state_machine at outside of the msmf::state_machine_def.

    Here is the updated version of your example:

    https://wandbox.org/permlink/DktAL169yjFNnmfl

    There are four updated points.

    In state2.h (1)

    // 1. move msm::back::state_machine to fsm
    // typedef msm::back::state_machine<State2_> State2;
    

    In myfsm.h (2, 3, 4)

    struct State2m : State2_ {}; // 2. replaced State2 to State2_
    typedef msm::back::state_machine<State2m> State2mm; // 3. define backend here
    
    
    // Transition table
    struct transition_table:mpl::vector<
         // 4. updated transition table using State2mm
         msmf::Row < State1, Event1, State2mm, msmf::none, msmf::none >,
         msmf::Row < State2mm, Event2, State1, msmf::none, msmf::none >,
         msmf::Row < State2mm::exit_pt
                     <State2mm::Exit2>, msmf::none, State1, msmf::none, msmf::none >
    >{};
    

    In this case, you can do more elegant way. You can remove struct State2m : State2_ {}; inheritance.

    Here is the code:

    https://wandbox.org/permlink/FO7lEELLtLhiismu

    You can define State2 as follows and use State2 in the transition table.

    typedef msm::back::state_machine<State2_> State2;
    
    // Transition table
    struct transition_table:mpl::vector<
          msmf::Row < State1, Event1, State2, msmf::none, msmf::none >,
          msmf::Row < State2, Event2, State1, msmf::none, msmf::none >,
          msmf::Row < State2::exit_pt
                      <State2::Exit2>, msmf::none, State1, msmf::none, msmf::none >
    >{};