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 // include headers that implement a archive in simple text format 21 #include <boost/archive/text_oarchive.hpp> 22 #include <boost/archive/text_iarchive.hpp> 23 #include <boost/serialization/tracking.hpp> 24 25 #include <fstream> 26 27 namespace msm = boost::msm; 28 namespace mpl = boost::mpl; 29 30 namespace 31 { 32 // events 33 struct play {}; 34 struct end_pause {}; 35 struct stop {}; 36 struct pause {}; 37 struct open_close {}; 38 struct NextSong {}; 39 struct PreviousSong {}; 40 struct cd_detected 41 { cd_detected__anon839e82250111::cd_detected42 cd_detected(std::string name) 43 : name(name) 44 {} 45 std::string name; 46 }; 47 48 // front-end: define the FSM structure 49 struct player_ : public msm::front::state_machine_def<player_> 50 { 51 unsigned int start_playback_counter; 52 unsigned int can_close_drawer_counter; 53 player___anon839e82250111::player_54 player_(): 55 start_playback_counter(0), 56 can_close_drawer_counter(0) 57 {} 58 // The list of FSM states 59 struct Empty : public msm::front::state<> 60 { 61 template <class Event,class FSM> on_entry__anon839e82250111::player_::Empty62 void on_entry(Event const&,FSM& ) {++entry_counter;} 63 template <class Event,class FSM> on_exit__anon839e82250111::player_::Empty64 void on_exit(Event const&,FSM& ) {++exit_counter;} 65 int entry_counter; 66 int exit_counter; 67 }; 68 struct Open : public msm::front::state<> 69 { 70 template <class Event,class FSM> on_entry__anon839e82250111::player_::Open71 void on_entry(Event const&,FSM& ) {++entry_counter;} 72 template <class Event,class FSM> on_exit__anon839e82250111::player_::Open73 void on_exit(Event const&,FSM& ) {++exit_counter;} 74 int entry_counter; 75 int exit_counter; 76 }; 77 78 // sm_ptr still supported but deprecated as functors are a much better way to do the same thing 79 struct Stopped : public msm::front::state<> 80 { 81 template <class Event,class FSM> on_entry__anon839e82250111::player_::Stopped82 void on_entry(Event const&,FSM& ) {++entry_counter;} 83 template <class Event,class FSM> on_exit__anon839e82250111::player_::Stopped84 void on_exit(Event const&,FSM& ) {++exit_counter;} 85 int entry_counter; 86 int exit_counter; 87 }; 88 89 struct Playing_ : public msm::front::state_machine_def<Playing_> 90 { 91 template <class Event,class FSM> on_entry__anon839e82250111::player_::Playing_92 void on_entry(Event const&,FSM& ) {++entry_counter;} 93 template <class Event,class FSM> on_exit__anon839e82250111::player_::Playing_94 void on_exit(Event const&,FSM& ) {++exit_counter;} 95 int entry_counter; 96 int exit_counter; 97 unsigned int start_next_song_counter; 98 unsigned int start_prev_song_guard_counter; 99 Playing___anon839e82250111::player_::Playing_100 Playing_(): 101 start_next_song_counter(0), 102 start_prev_song_guard_counter(0) 103 {} 104 105 // The list of FSM states 106 struct Song1 : public msm::front::state<> 107 { 108 template <class Event,class FSM> on_entry__anon839e82250111::player_::Playing_::Song1109 void on_entry(Event const&,FSM& ) {++entry_counter;} 110 template <class Event,class FSM> on_exit__anon839e82250111::player_::Playing_::Song1111 void on_exit(Event const&,FSM& ) {++exit_counter;} 112 int entry_counter; 113 int exit_counter; 114 }; 115 struct Song2 : public msm::front::state<> 116 { 117 template <class Event,class FSM> on_entry__anon839e82250111::player_::Playing_::Song2118 void on_entry(Event const&,FSM& ) {++entry_counter;} 119 template <class Event,class FSM> on_exit__anon839e82250111::player_::Playing_::Song2120 void on_exit(Event const&,FSM& ) {++exit_counter;} 121 int entry_counter; 122 int exit_counter; 123 }; 124 struct Song3 : public msm::front::state<> 125 { 126 template <class Event,class FSM> on_entry__anon839e82250111::player_::Playing_::Song3127 void on_entry(Event const&,FSM& ) {++entry_counter;} 128 template <class Event,class FSM> on_exit__anon839e82250111::player_::Playing_::Song3129 void on_exit(Event const&,FSM& ) {++exit_counter;} 130 int entry_counter; 131 int exit_counter; 132 }; 133 // the initial state. Must be defined 134 typedef Song1 initial_state; 135 // transition actions start_next_song__anon839e82250111::player_::Playing_136 void start_next_song(NextSong const&) {++start_next_song_counter; } start_prev_song__anon839e82250111::player_::Playing_137 void start_prev_song(PreviousSong const&) { } 138 // guard conditions start_prev_song_guard__anon839e82250111::player_::Playing_139 bool start_prev_song_guard(PreviousSong const&) {++start_prev_song_guard_counter;return true; } 140 141 typedef Playing_ pl; // makes transition table cleaner 142 // Transition table for Playing 143 struct transition_table : mpl::vector4< 144 // Start Event Next Action Guard 145 // +---------+-------------+---------+---------------------+----------------------+ 146 _row < Song1 , NextSong , Song2 >, 147 row < Song2 , PreviousSong, Song1 , &pl::start_prev_song,&pl::start_prev_song_guard>, 148 a_row < Song2 , NextSong , Song3 , &pl::start_next_song >, 149 g_row < Song3 , PreviousSong, Song2 ,&pl::start_prev_song_guard> 150 // +---------+-------------+---------+---------------------+----------------------+ 151 > {}; 152 // Replaces the default no-transition response. 153 template <class FSM,class Event> no_transition__anon839e82250111::player_::Playing_154 void no_transition(Event const&, FSM&,int) 155 { 156 BOOST_FAIL("no_transition called!"); 157 } 158 }; 159 // back-end 160 typedef msm::back::state_machine<Playing_,msm::back::ShallowHistory<mpl::vector<end_pause> > > Playing; 161 162 // state not defining any entry or exit 163 struct Paused : public msm::front::state<> 164 { 165 template <class Event,class FSM> on_entry__anon839e82250111::player_::Paused166 void on_entry(Event const&,FSM& ) {++entry_counter;} 167 template <class Event,class FSM> on_exit__anon839e82250111::player_::Paused168 void on_exit(Event const&,FSM& ) {++exit_counter;} 169 int entry_counter; 170 int exit_counter; 171 }; 172 173 // the initial state of the player SM. Must be defined 174 typedef Empty initial_state; 175 176 // transition actions start_playback__anon839e82250111::player_177 void start_playback(play const&) {++start_playback_counter; } open_drawer__anon839e82250111::player_178 void open_drawer(open_close const&) { } store_cd_info__anon839e82250111::player_179 void store_cd_info(cd_detected const&) { } stop_playback__anon839e82250111::player_180 void stop_playback(stop const&) { } pause_playback__anon839e82250111::player_181 void pause_playback(pause const&) { } resume_playback__anon839e82250111::player_182 void resume_playback(end_pause const&) { } stop_and_open__anon839e82250111::player_183 void stop_and_open(open_close const&) { } stopped_again__anon839e82250111::player_184 void stopped_again(stop const&) {} 185 //guards can_close_drawer__anon839e82250111::player_186 bool can_close_drawer(open_close const&) 187 { 188 ++can_close_drawer_counter; 189 return true; 190 } 191 192 193 typedef player_ p; // makes transition table cleaner 194 195 // Transition table for player 196 struct transition_table : mpl::vector< 197 // Start Event Next Action Guard 198 // +---------+-------------+---------+---------------------+----------------------+ 199 a_row < Stopped , play , Playing , &p::start_playback >, 200 a_row < Stopped , open_close , Open , &p::open_drawer >, 201 _row < Stopped , stop , Stopped >, 202 // +---------+-------------+---------+---------------------+----------------------+ 203 g_row < Open , open_close , Empty , &p::can_close_drawer >, 204 // +---------+-------------+---------+---------------------+----------------------+ 205 a_row < Empty , open_close , Open , &p::open_drawer >, 206 a_row < Empty , cd_detected , Stopped , &p::store_cd_info >, 207 // +---------+-------------+---------+---------------------+----------------------+ 208 a_row < Playing , stop , Stopped , &p::stop_playback >, 209 a_row < Playing , pause , Paused , &p::pause_playback >, 210 a_row < Playing , open_close , Open , &p::stop_and_open >, 211 // +---------+-------------+---------+---------------------+----------------------+ 212 a_row < Paused , end_pause , Playing , &p::resume_playback >, 213 a_row < Paused , stop , Stopped , &p::stop_playback >, 214 a_row < Paused , open_close , Open , &p::stop_and_open > 215 // +---------+-------------+---------+---------------------+----------------------+ 216 > {}; 217 // Replaces the default no-transition response. 218 template <class FSM,class Event> no_transition__anon839e82250111::player_219 void no_transition(Event const& e, FSM&,int state) 220 { 221 BOOST_FAIL("no_transition called!"); 222 } 223 // init counters 224 template <class Event,class FSM> on_entry__anon839e82250111::player_225 void on_entry(Event const&,FSM& fsm) 226 { 227 fsm.template get_state<player_::Stopped&>().entry_counter=0; 228 fsm.template get_state<player_::Stopped&>().exit_counter=0; 229 fsm.template get_state<player_::Open&>().entry_counter=0; 230 fsm.template get_state<player_::Open&>().exit_counter=0; 231 fsm.template get_state<player_::Empty&>().entry_counter=0; 232 fsm.template get_state<player_::Empty&>().exit_counter=0; 233 fsm.template get_state<player_::Playing&>().entry_counter=0; 234 fsm.template get_state<player_::Playing&>().exit_counter=0; 235 fsm.template get_state<player_::Playing&>().template get_state<player_::Playing::Song1&>().entry_counter=0; 236 fsm.template get_state<player_::Playing&>().template get_state<player_::Playing::Song1&>().exit_counter=0; 237 fsm.template get_state<player_::Playing&>().template get_state<player_::Playing::Song2&>().entry_counter=0; 238 fsm.template get_state<player_::Playing&>().template get_state<player_::Playing::Song2&>().exit_counter=0; 239 fsm.template get_state<player_::Playing&>().template get_state<player_::Playing::Song3&>().entry_counter=0; 240 fsm.template get_state<player_::Playing&>().template get_state<player_::Playing::Song3&>().exit_counter=0; 241 fsm.template get_state<player_::Paused&>().entry_counter=0; 242 fsm.template get_state<player_::Paused&>().exit_counter=0; 243 } 244 245 }; 246 // Pick a back-end 247 typedef msm::back::state_machine<player_> player; 248 249 // static char const* const state_names[] = { "Stopped", "Open", "Empty", "Playing", "Paused" }; 250 251 BOOST_AUTO_TEST_CASE(my_test)252 BOOST_AUTO_TEST_CASE( my_test ) 253 { 254 player p; 255 256 p.start(); 257 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 1,"Empty entry not called correctly"); 258 259 p.process_event(open_close()); 260 BOOST_CHECK_MESSAGE(p.current_state()[0] == 1,"Open should be active"); //Open 261 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 1,"Empty exit not called correctly"); 262 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().entry_counter == 1,"Open entry not called correctly"); 263 264 p.process_event(open_close()); 265 BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active"); //Empty 266 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly"); 267 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly"); 268 BOOST_CHECK_MESSAGE(p.can_close_drawer_counter == 1,"guard not called correctly"); 269 270 p.process_event(cd_detected("louie, louie")); 271 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 272 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 2,"Empty exit not called correctly"); 273 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 1,"Stopped entry not called correctly"); 274 275 p.process_event(play()); 276 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 277 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 1,"Stopped exit not called correctly"); 278 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 1,"Playing entry not called correctly"); 279 BOOST_CHECK_MESSAGE(p.start_playback_counter == 1,"action not called correctly"); 280 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().current_state()[0] == 0,"Song1 should be active"); 281 BOOST_CHECK_MESSAGE( 282 p.get_state<player_::Playing&>().get_state<player_::Playing::Song1&>().entry_counter == 1, 283 "Song1 entry not called correctly"); 284 285 p.process_event(NextSong()); 286 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 287 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().current_state()[0] == 1,"Song2 should be active"); 288 BOOST_CHECK_MESSAGE( 289 p.get_state<player_::Playing&>().get_state<player_::Playing::Song2&>().entry_counter == 1, 290 "Song2 entry not called correctly"); 291 BOOST_CHECK_MESSAGE( 292 p.get_state<player_::Playing&>().get_state<player_::Playing::Song1&>().exit_counter == 1, 293 "Song1 exit not called correctly"); 294 BOOST_CHECK_MESSAGE( 295 p.get_state<player_::Playing&>().start_next_song_counter == 0, 296 "submachine action not called correctly"); 297 298 p.process_event(NextSong()); 299 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 300 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().current_state()[0] == 2,"Song3 should be active"); 301 BOOST_CHECK_MESSAGE( 302 p.get_state<player_::Playing&>().get_state<player_::Playing::Song3&>().entry_counter == 1, 303 "Song3 entry not called correctly"); 304 BOOST_CHECK_MESSAGE( 305 p.get_state<player_::Playing&>().get_state<player_::Playing::Song2&>().exit_counter == 1, 306 "Song2 exit not called correctly"); 307 BOOST_CHECK_MESSAGE( 308 p.get_state<player_::Playing&>().start_next_song_counter == 1, 309 "submachine action not called correctly"); 310 311 p.process_event(PreviousSong()); 312 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 313 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().current_state()[0] == 1,"Song2 should be active"); 314 BOOST_CHECK_MESSAGE( 315 p.get_state<player_::Playing&>().get_state<player_::Playing::Song2&>().entry_counter == 2, 316 "Song2 entry not called correctly"); 317 BOOST_CHECK_MESSAGE( 318 p.get_state<player_::Playing&>().get_state<player_::Playing::Song3&>().exit_counter == 1, 319 "Song3 exit not called correctly"); 320 BOOST_CHECK_MESSAGE( 321 p.get_state<player_::Playing&>().start_prev_song_guard_counter == 1, 322 "submachine guard not called correctly"); 323 324 p.process_event(pause()); 325 BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active"); //Paused 326 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 1,"Playing exit not called correctly"); 327 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 1,"Paused entry not called correctly"); 328 329 std::ofstream ofs("fsm.txt"); 330 // save fsm to archive (current state is Pause, Playing is in Song2) 331 { 332 boost::archive::text_oarchive oa(ofs); 333 // write class instance to archive 334 oa << p; 335 } 336 // reload fsm in state Open 337 player p2; 338 { 339 // create and open an archive for input 340 std::ifstream ifs("fsm.txt"); 341 boost::archive::text_iarchive ia(ifs); 342 // read class state from archive 343 ia >> p2; 344 } 345 // go back to Playing 346 p2.process_event(end_pause()); 347 BOOST_CHECK_MESSAGE(p2.current_state()[0] == 3,"Playing should be active"); //Playing 348 BOOST_CHECK_MESSAGE(p2.get_state<player_::Playing&>().current_state()[0] == 1,"Song2 should be active"); 349 350 p2.process_event(pause()); 351 BOOST_CHECK_MESSAGE(p2.current_state()[0] == 4,"Paused should be active"); //Paused 352 353 p2.process_event(stop()); 354 BOOST_CHECK_MESSAGE(p2.current_state()[0] == 0,"Stopped should be active"); //Stopped 355 356 p2.process_event(stop()); 357 BOOST_CHECK_MESSAGE(p2.current_state()[0] == 0,"Stopped should be active"); //Stopped 358 359 p2.process_event(play()); 360 BOOST_CHECK_MESSAGE(p2.get_state<player_::Playing&>().current_state()[0] == 0,"Song1 should be active"); 361 } 362 } 363 // eliminate object tracking (even if serialized through a pointer) 364 // at the risk of a programming error creating duplicate objects. 365 // this is to get rid of warning because p is not const 366 BOOST_CLASS_TRACKING(player, boost::serialization::track_never) 367 368