1 // Copyright 2010 Christophe Henry 2 // henry UNDERSCORE christophe AT hotmail DOT com 3 // This is an extended version of the state machine available in the boost::mpl library 4 // Distributed under the same license as the original. 5 // Copyright for the original version: 6 // Copyright 2005 David Abrahams and Aleksey Gurtovoy. Distributed 7 // under the Boost Software License, Version 1.0. (See accompanying 8 // file LICENSE_1_0.txt or copy at 9 // http://www.boost.org/LICENSE_1_0.txt) 10 11 #include <iostream> 12 // back-end 13 #include <boost/msm/back/state_machine.hpp> 14 //front-end 15 #include <boost/msm/front/state_machine_def.hpp> 16 #ifndef BOOST_MSM_NONSTANDALONE_TEST 17 #define BOOST_TEST_MODULE MyTest 18 #endif 19 #include <boost/test/unit_test.hpp> 20 21 namespace msm = boost::msm; 22 namespace mpl = boost::mpl; 23 24 namespace 25 { 26 // events 27 struct play {}; 28 struct end_pause {}; 29 struct stop {}; 30 struct pause {}; 31 struct open_close {}; 32 33 // A "complicated" event type that carries some data. 34 enum DiskTypeEnum 35 { 36 DISK_CD=0, 37 DISK_DVD=1 38 }; 39 struct cd_detected 40 { cd_detected__anon34264fed0111::cd_detected41 cd_detected(std::string name, DiskTypeEnum diskType) 42 : name(name), 43 disc_type(diskType) 44 {} 45 46 std::string name; 47 DiskTypeEnum disc_type; 48 }; 49 50 // front-end: define the FSM structure 51 struct player_ : public msm::front::state_machine_def<player_> 52 { 53 unsigned int start_playback_counter; 54 unsigned int can_close_drawer_counter; 55 player___anon34264fed0111::player_56 player_(): 57 start_playback_counter(0), 58 can_close_drawer_counter(0) 59 {} 60 61 // The list of FSM states 62 struct Empty : public msm::front::state<> 63 { 64 template <class Event,class FSM> on_entry__anon34264fed0111::player_::Empty65 void on_entry(Event const&,FSM& ) {++entry_counter;} 66 template <class Event,class FSM> on_exit__anon34264fed0111::player_::Empty67 void on_exit(Event const&,FSM& ) {++exit_counter;} 68 int entry_counter; 69 int exit_counter; 70 }; 71 struct Open : public msm::front::state<> 72 { 73 template <class Event,class FSM> on_entry__anon34264fed0111::player_::Open74 void on_entry(Event const&,FSM& ) {++entry_counter;} 75 template <class Event,class FSM> on_exit__anon34264fed0111::player_::Open76 void on_exit(Event const&,FSM& ) {++exit_counter;} 77 int entry_counter; 78 int exit_counter; 79 }; 80 81 // sm_ptr still supported but deprecated as functors are a much better way to do the same thing 82 struct Stopped : public msm::front::state<> 83 { 84 template <class Event,class FSM> on_entry__anon34264fed0111::player_::Stopped85 void on_entry(Event const&,FSM& ) {++entry_counter;} 86 template <class Event,class FSM> on_exit__anon34264fed0111::player_::Stopped87 void on_exit(Event const&,FSM& ) {++exit_counter;} 88 int entry_counter; 89 int exit_counter; 90 }; 91 92 struct Playing : public msm::front::state<> 93 { 94 template <class Event,class FSM> on_entry__anon34264fed0111::player_::Playing95 void on_entry(Event const&,FSM& ) {++entry_counter;} 96 template <class Event,class FSM> on_exit__anon34264fed0111::player_::Playing97 void on_exit(Event const&,FSM& ) {++exit_counter;} 98 int entry_counter; 99 int exit_counter; 100 }; 101 102 // state not defining any entry or exit 103 struct Paused : public msm::front::state<> 104 { 105 template <class Event,class FSM> on_entry__anon34264fed0111::player_::Paused106 void on_entry(Event const&,FSM& ) {++entry_counter;} 107 template <class Event,class FSM> on_exit__anon34264fed0111::player_::Paused108 void on_exit(Event const&,FSM& ) {++exit_counter;} 109 int entry_counter; 110 int exit_counter; 111 }; 112 113 // the initial state of the player SM. Must be defined 114 typedef Empty initial_state; 115 116 // transition actions start_playback__anon34264fed0111::player_117 void start_playback(play const&) {++start_playback_counter; } open_drawer__anon34264fed0111::player_118 void open_drawer(open_close const&) { } store_cd_info__anon34264fed0111::player_119 void store_cd_info(cd_detected const&) { } stop_playback__anon34264fed0111::player_120 void stop_playback(stop const&) { } pause_playback__anon34264fed0111::player_121 void pause_playback(pause const&) { } resume_playback__anon34264fed0111::player_122 void resume_playback(end_pause const&) { } stop_and_open__anon34264fed0111::player_123 void stop_and_open(open_close const&) { } stopped_again__anon34264fed0111::player_124 void stopped_again(stop const&){} 125 // guard conditions good_disk_format__anon34264fed0111::player_126 bool good_disk_format(cd_detected const& evt) 127 { 128 // to test a guard condition, let's say we understand only CDs, not DVD 129 if (evt.disc_type != DISK_CD) 130 { 131 return false; 132 } 133 return true; 134 } can_close_drawer__anon34264fed0111::player_135 bool can_close_drawer(open_close const&) 136 { 137 ++can_close_drawer_counter; 138 return true; 139 } 140 141 typedef player_ p; // makes transition table cleaner 142 143 // Transition table for player 144 struct transition_table : mpl::vector< 145 // Start Event Next Action Guard 146 // +---------+-------------+---------+---------------------+----------------------+ 147 a_row < Stopped , play , Playing , &p::start_playback >, 148 a_row < Stopped , open_close , Open , &p::open_drawer >, 149 _row < Stopped , stop , Stopped >, 150 // +---------+-------------+---------+---------------------+----------------------+ 151 g_row < Open , open_close , Empty , &p::can_close_drawer >, 152 // +---------+-------------+---------+---------------------+----------------------+ 153 a_row < Empty , open_close , Open , &p::open_drawer >, 154 row < Empty , cd_detected , Stopped , &p::store_cd_info ,&p::good_disk_format >, 155 // +---------+-------------+---------+---------------------+----------------------+ 156 a_row < Playing , stop , Stopped , &p::stop_playback >, 157 a_row < Playing , pause , Paused , &p::pause_playback >, 158 a_row < Playing , open_close , Open , &p::stop_and_open >, 159 // +---------+-------------+---------+---------------------+----------------------+ 160 a_row < Paused , end_pause , Playing , &p::resume_playback >, 161 a_row < Paused , stop , Stopped , &p::stop_playback >, 162 a_row < Paused , open_close , Open , &p::stop_and_open > 163 // +---------+-------------+---------+---------------------+----------------------+ 164 > {}; 165 // Replaces the default no-transition response. 166 template <class FSM,class Event> no_transition__anon34264fed0111::player_167 void no_transition(Event const& , FSM&,int) 168 { 169 BOOST_FAIL("no_transition called!"); 170 } 171 // init counters 172 template <class Event,class FSM> on_entry__anon34264fed0111::player_173 void on_entry(Event const&,FSM& fsm) 174 { 175 fsm.template get_state<player_::Stopped&>().entry_counter=0; 176 fsm.template get_state<player_::Stopped&>().exit_counter=0; 177 fsm.template get_state<player_::Open&>().entry_counter=0; 178 fsm.template get_state<player_::Open&>().exit_counter=0; 179 fsm.template get_state<player_::Empty&>().entry_counter=0; 180 fsm.template get_state<player_::Empty&>().exit_counter=0; 181 fsm.template get_state<player_::Playing&>().entry_counter=0; 182 fsm.template get_state<player_::Playing&>().exit_counter=0; 183 fsm.template get_state<player_::Paused&>().entry_counter=0; 184 fsm.template get_state<player_::Paused&>().exit_counter=0; 185 } 186 187 }; 188 // Pick a back-end 189 typedef msm::back::state_machine<player_> player; 190 191 // static char const* const state_names[] = { "Stopped", "Open", "Empty", "Playing", "Paused" }; 192 193 BOOST_AUTO_TEST_CASE(my_test)194 BOOST_AUTO_TEST_CASE( my_test ) 195 { 196 player p; 197 198 p.start(); 199 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 1,"Empty entry not called correctly"); 200 201 p.process_event(open_close()); 202 BOOST_CHECK_MESSAGE(p.current_state()[0] == 1,"Open should be active"); //Open 203 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 1,"Empty exit not called correctly"); 204 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().entry_counter == 1,"Open entry not called correctly"); 205 206 p.process_event(open_close()); 207 BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active"); //Empty 208 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly"); 209 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly"); 210 BOOST_CHECK_MESSAGE(p.can_close_drawer_counter == 1,"guard not called correctly"); 211 212 p.process_event( 213 cd_detected("louie, louie",DISK_DVD)); 214 BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active"); //Empty 215 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly"); 216 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly"); 217 218 p.process_event( 219 cd_detected("louie, louie",DISK_CD)); 220 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 221 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 2,"Empty exit not called correctly"); 222 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 1,"Stopped entry not called correctly"); 223 224 p.process_event(play()); 225 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 226 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 1,"Stopped exit not called correctly"); 227 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 1,"Playing entry not called correctly"); 228 BOOST_CHECK_MESSAGE(p.start_playback_counter == 1,"action not called correctly"); 229 230 p.process_event(pause()); 231 BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active"); //Paused 232 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 1,"Playing exit not called correctly"); 233 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 1,"Paused entry not called correctly"); 234 235 // go back to Playing 236 p.process_event(end_pause()); 237 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 238 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 1,"Paused exit not called correctly"); 239 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 2,"Playing entry not called correctly"); 240 241 p.process_event(pause()); 242 BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active"); //Paused 243 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 2,"Playing exit not called correctly"); 244 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 2,"Paused entry not called correctly"); 245 246 p.process_event(stop()); 247 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 248 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 2,"Paused exit not called correctly"); 249 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 2,"Stopped entry not called correctly"); 250 251 p.process_event(stop()); 252 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 253 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 2,"Stopped exit not called correctly"); 254 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 3,"Stopped entry not called correctly"); 255 } 256 } 257 258