Sub Machine State
Composite state and sub-machine state
Boost.Msm supports composite state and sub-machine state. Consider the following diagrams. State1 is a composite state that has two sub states. They are simply mapped into the code.Naming rule (example)
Before describing the code, I introduce the naming rule. Name_, any name with underscore, is a state's name that inherited state_machine_def class template.
struct State1_:msmf::state_machine_def<State1_>
Name, any name without underscore, is a state's name that doesn't inherited it.
struct State2:msmf::state<>
Name is also used Boost.Msm back-end templates that have the template argument Name_.
typedef msm::back::state_machine<State1_> State1;
Let's see the code corresponds to the above diagrams.
#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 <boost/static_assert.hpp> namespace { namespace msm = boost::msm; namespace msmf = boost::msm::front; namespace mpl = boost::mpl; // ----- Events struct Event1 {}; struct Event2 {}; struct Event3 {}; // ----- State machine struct OuterSm_:msmf::state_machine_def<OuterSm_> { struct State1_:msmf::state_machine_def<State1_> { template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State1::on_entry()" << std::endl; } template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State1::on_exit()" << std::endl; } struct SubState1:msmf::state<> { template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value)); std::cout << "SubState1::on_entry()" << std::endl; } template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value)); std::cout << "SubState1::on_exit()" << std::endl; } }; struct SubState2:msmf::state<> { template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value)); std::cout << "SubState2::on_entry()" << std::endl; } template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, State1_>::value)); std::cout << "SubState2::on_exit()" << std::endl; } }; // Set initial state typedef mpl::vector<SubState1> initial_state; // Transition table struct transition_table:mpl::vector< // Start Event Next Action Guard msmf::Row < SubState1, Event2, SubState2, msmf::none, msmf::none >, msmf::Row < SubState2, Event3, SubState1, msmf::none, msmf::none > > {}; }; struct State2:msmf::state<> { template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State2::on_entry()" << std::endl; } template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State2::on_exit()" << std::endl; } }; typedef msm::back::state_machine<State1_> State1; // Set initial state typedef State1 initial_state; // Transition table struct transition_table:mpl::vector< // Start Event Next Action Guard msmf::Row < State1, Event1, State2, msmf::none, msmf::none > > {}; }; // Pick a back-end typedef msm::back::state_machine<OuterSm_> Osm; void test() { Osm osm; osm.start(); std::cout << "> Send Event2()" << std::endl; osm.process_event(Event2()); std::cout << "> Send Event1()" << std::endl; osm.process_event(Event1()); } } int main() { test(); return 0; } // Output: // // State1::on_entry() // SubState1::on_entry() // > Send Event2() // SubState1::on_exit() // SubState2::on_entry() // > Send Event1() // SubState2::on_exit() // State1::on_exit() // State2::on_entry()
To implement composite states, inherits state_machine_def class template same as parent state machine.
struct State1_:msmf::state_machine_def<State1_>
And then, connect to back-end state_machine class template.
typedef msm::back::state_machine<State1_> State1;
In parent state machine's transition table, you can use back-end state name.
// Transition table struct transition_table:mpl::vector< // Start Event Next Action Guard msmf::Row < State1, Event1, State2, msmf::none, msmf::none > > {};
These are sub-machine state diagram examples. These diagrams have the same behavior as above diagram that uses composite state. Sub-machine states are more reusable than composite states. For example, you can reuse StateSub as State2's sub-machine state.
This code corresponds to above sub-machine state diagrams.
#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 <boost/static_assert.hpp> namespace { namespace msm = boost::msm; namespace msmf = boost::msm::front; namespace mpl = boost::mpl; // ----- Events struct Event1 {}; struct Event2 {}; struct Event3 {}; // ----- State machine struct StateSub_:msmf::state_machine_def<StateSub_> { struct SubState1:msmf::state<> { template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, StateSub_>::value)); std::cout << "SubState1::on_entry()" << std::endl; } template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, StateSub_>::value)); std::cout << "SubState1::on_exit()" << std::endl; } }; struct SubState2:msmf::state<> { template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, StateSub_>::value)); std::cout << "SubState2::on_entry()" << std::endl; } template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, StateSub_>::value)); std::cout << "SubState2::on_exit()" << std::endl; } }; // Set initial state typedef mpl::vector<SubState1> initial_state; // Transition table struct transition_table:mpl::vector< // Start Event Next Action Guard msmf::Row < SubState1, Event2, SubState2, msmf::none, msmf::none >, msmf::Row < SubState2, Event3, SubState1, msmf::none, msmf::none > > {}; }; typedef msm::back::state_machine<StateSub_> StateSub; struct OuterSm_:msmf::state_machine_def<OuterSm_> { struct State1_:msmf::state_machine_def<State1_> { template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State1::on_entry()" << std::endl; } template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State1::on_exit()" << std::endl; } struct Sub_:StateSub {}; typedef Sub_ initial_state; }; // Pick a back-end typedef msm::back::state_machine<State1_> State1; struct State2:msmf::state<> { template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State2::on_entry()" << std::endl; } template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State2::on_exit()" << std::endl; } }; // Set initial state typedef State1 initial_state; // Transition table struct transition_table:mpl::vector< // Start Event Next Action Guard msmf::Row < State1, Event1, State2, msmf::none, msmf::none > > {}; }; // Pick a back-end typedef msm::back::state_machine<OuterSm_> Osm; void test() { Osm osm; osm.start(); std::cout << "> Send Event2()" << std::endl; osm.process_event(Event2()); std::cout << "> Send Event1()" << std::endl; osm.process_event(Event1()); } } int main() { test(); return 0; } // Output: // // State1::on_entry() // SubState1::on_entry() // > Send Event2() // SubState1::on_exit() // SubState2::on_entry() // > Send Event1() // SubState2::on_exit() // State1::on_exit() // State2::on_entry()
In the definition of State1_, embed StateSub as Sub_ using inheritance. And set it as an initial_state.
struct State1_:msmf::state_machine_def<State1_> { template <class Event,class Fsm> void on_entry(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State1::on_entry()" << std::endl; } template <class Event,class Fsm> void on_exit(Event const&, Fsm&) const { BOOST_STATIC_ASSERT((boost::is_convertible<Fsm, OuterSm_>::value)); std::cout << "State1::on_exit()" << std::endl; } struct Sub_:StateSub {}; typedef Sub_ initial_state; };
Which fsm is accessible?
Event handlers can access a inner most Fsm that includes the state that has the event handler. Check BOOST_STATIC_ASSERT on above codes. Be careful that the Fsm of State1_::on_entry(Event const&, Fsm&) refers to OuterSm_, not State1_ because State1_ is included in OuterSm_.
page revision: 51, last edited: 17 Mar 2019 09:39
On all pictures there are 2 substates "SubState 1" and no any "SubState 2".
Thank you! I fixed the pictures.
This is a very good set of pages written very well. Only nit I have is that I would like to see a pdf version or a printable one; some stuff is worth highlighting but is buried within the doc.
Thanks!
ajaym