Boost.MSM: Exit orthogonal regions via a join pseu

2019-04-30 02:47发布

问题:

I intend to use boost.msm with the concept of composite containing orthogonal regions. I want to synchronize all orthogonal regions upon exit. In other words: the state following my composite shall be activated if and only if all regions have reached their last state.

UML 2.4 "Superstructure" proposes join pseudo states (i.e. chapter 15.3.8). In boost, there is a fork but I cannot find any implementation of its counterpart join.

Is there no join pseudo state in boost.msm? How would I apply the concept of join pseudo state with boost.msm?

回答1:

You could use a counter which will increment each time the join state is entered. When this counter equals the number of orthogonal regions, the state following the join state will be activated.

This could be done manually or in a generic way. Below I implemented a generic way where the joining logic is added to the submachine Sub by inheriting from the template JoinSM.

Sub has 3 orthogonal regions (which in this simple example just consist of one state each, namely Orthogonal1, Orthogonal2 and Orthogonal3). All of these orthogonal states are connected to the Join state but no direct connection to the Exit state from the Join state is specified within Sub.

This connection is implemented in JoinSM. Every time the Join state is reached from Sub, the Waiting state is activated and the counter is incremented. If the counter reaches the number of orthogonal regions, the Event AllJoined is fired and the transition to Exit is activated.

Since JoinSM queries the number of orthogonal regions through the size of initial_state, adding or removing regions in Sub will automatically be reflected in the joining logic.

#include <iostream>
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

template <class T>
std::string demangle()
{
    const char* name = typeid(T).name();
    int status = -1; 
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };
    return (status==0) ? res.get() : name ;
}


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

using namespace boost::msm;
using namespace boost::msm::front;


template <typename State>
struct BaseState : public boost::msm::front::state<>
{
    template <class Event,class FSM> void on_entry(Event const&,FSM& )
    {
        std::cout << "on_entry: " << demangle<State>()  << std::endl;
    }
    template <class Event,class FSM> void on_exit(Event const&,FSM& ) 
    {
        std::cout << "on_exit: " << demangle<State>() << std::endl;
    }
};


// EVENTS
struct EnterOrthogonal {};

struct Orthogonal1Finished{};
struct Orthogonal2Finished{};
struct Orthogonal3Finished{};



struct SubSM_ : state_machine_def<SubSM_>
{
    struct Started : BaseState<Started>{};
    struct Exit : exit_pseudo_state<none> {};

    struct Orthogonal1 : BaseState<Orthogonal1>{};
    struct Orthogonal2 : BaseState<Orthogonal2>{};
    struct Orthogonal3 : BaseState<Orthogonal3>{};

    struct Join : BaseState<Join>{};

    typedef boost::mpl::vector<Orthogonal1, Orthogonal2, Orthogonal3> initial_state;
    struct transition_table : boost::mpl::vector<
     Row<Orthogonal1, Orthogonal1Finished, Join, none, none>,
     Row<Orthogonal2, Orthogonal2Finished, Join, none, none>,
     Row<Orthogonal3, Orthogonal3Finished, Join, none, none>
     > {};
};


template <typename SM, typename JoinState = typename SM::Join, typename ExitState = typename SM::Exit>
struct JoinSM  : SM
{
    struct AllJoined{};

    constexpr static int num_regions = boost::mpl::size<typename SM::initial_state>::value;
    int count;

    template <class Event,class FSM>
    void on_entry(Event const& ,FSM&) 
    {
        // reset count
        count = 0;
    }

    struct Waiting : BaseState<Waiting>
    {
        template <class Event,class FSM>
        void on_entry(const Event& e,FSM& f)
        {
            BaseState<Waiting>::on_entry(e,f);
            f.count++; 
            if (f.count == FSM::num_regions)
            {
                f.process_event(AllJoined()); 
            } 
        }
    };

    typedef boost::mpl::vector<
        Row<JoinState, none, Waiting, none, none>,
        Row<Waiting, AllJoined, ExitState, none, none>
    > additional_transition_table;

    typedef boost::mpl::joint_view<
        typename SM::transition_table,
        additional_transition_table
    > transition_table;
};

// inherit from JoinSM to add the joining logic
using Sub = back::state_machine<JoinSM<SubSM_>>;

struct MainSM_ : state_machine_def<MainSM_>
{
    struct Started : BaseState<Started>{};
    struct AfterJoin : BaseState<AfterJoin>{};
    using initial_state = boost::mpl::vector<Started>;
    struct transition_table : boost::mpl::vector<
        Row<Started, EnterOrthogonal, Sub, none, none>,
        Row<Sub::exit_pt<SubSM_::Exit>, none, AfterJoin, none, none>
    > {};
};

struct MainSM_;
using Main = back::state_machine<MainSM_>;    


int main()
{

    Main main;
    main.start();
    main.process_event(EnterOrthogonal());
    main.process_event(Orthogonal3Finished());
    main.process_event(Orthogonal1Finished());
    main.process_event(Orthogonal2Finished());
}

Output:

on_entry: MainSM_::Started
on_exit: MainSM_::Started
on_entry: SubSM_::Orthogonal1
on_entry: SubSM_::Orthogonal2
on_entry: SubSM_::Orthogonal3
on_exit: SubSM_::Orthogonal3
on_entry: SubSM_::Join
on_exit: SubSM_::Join
on_entry: JoinSM<SubSM_, SubSM_::Join, SubSM_::Exit>::Waiting
on_exit: SubSM_::Orthogonal1
on_entry: SubSM_::Join
on_exit: SubSM_::Join
on_entry: JoinSM<SubSM_, SubSM_::Join, SubSM_::Exit>::Waiting
on_exit: SubSM_::Orthogonal2
on_entry: SubSM_::Join
on_exit: SubSM_::Join
on_entry: JoinSM<SubSM_, SubSM_::Join, SubSM_::Exit>::Waiting
on_exit: JoinSM<SubSM_, SubSM_::Join, SubSM_::Exit>::Waiting
on_exit: JoinSM<SubSM_, SubSM_::Join, SubSM_::Exit>::Waiting
on_exit: JoinSM<SubSM_, SubSM_::Join, SubSM_::Exit>::Waiting
on_entry: MainSM_::AfterJoin

Live example: http://coliru.stacked-crooked.com/a/6c060d032bc53573