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 39 // A "complicated" event type that carries some data. 40 enum DiskTypeEnum 41 { 42 DISK_CD=0, 43 DISK_DVD=1 44 }; 45 struct cd_detected 46 { cd_detected__anon5ecd1c550111::cd_detected47 cd_detected(std::string name, DiskTypeEnum diskType) 48 : name(name), 49 disc_type(diskType) 50 {} 51 52 std::string name; 53 DiskTypeEnum disc_type; 54 }; 55 56 // front-end: define the FSM structure 57 struct player_ : public msm::front::state_machine_def<player_> 58 { 59 unsigned int start_playback_counter; 60 unsigned int can_close_drawer_counter; 61 int front_end_data; 62 player___anon5ecd1c550111::player_63 player_(): 64 start_playback_counter(0), 65 can_close_drawer_counter(0), 66 front_end_data(4) 67 {} 68 69 //we want to serialize some data contained by the front-end 70 // to achieve this, ask for it 71 typedef int do_serialize; 72 // and provide a serialize 73 template<class Archive> serialize__anon5ecd1c550111::player_74 void serialize(Archive & ar, const unsigned int ) 75 { 76 ar & front_end_data; 77 } 78 79 // The list of FSM states 80 struct Empty : public msm::front::state<> 81 { 82 template <class Event,class FSM> on_entry__anon5ecd1c550111::player_::Empty83 void on_entry(Event const&,FSM& ) {++entry_counter;} 84 template <class Event,class FSM> on_exit__anon5ecd1c550111::player_::Empty85 void on_exit(Event const&,FSM& ) {++exit_counter;} 86 int entry_counter; 87 int exit_counter; 88 int some_dummy_data; 89 // we want Empty to be serialized 90 typedef int do_serialize; 91 template<class Archive> serialize__anon5ecd1c550111::player_::Empty92 void serialize(Archive & ar, const unsigned int ) 93 { 94 ar & some_dummy_data; 95 } 96 97 }; 98 struct Open : public msm::front::state<> 99 { 100 template <class Event,class FSM> on_entry__anon5ecd1c550111::player_::Open101 void on_entry(Event const&,FSM& ) {++entry_counter;} 102 template <class Event,class FSM> on_exit__anon5ecd1c550111::player_::Open103 void on_exit(Event const&,FSM& ) {++exit_counter;} 104 int entry_counter; 105 int exit_counter; 106 }; 107 108 // sm_ptr still supported but deprecated as functors are a much better way to do the same thing 109 struct Stopped : public msm::front::state<> 110 { 111 template <class Event,class FSM> on_entry__anon5ecd1c550111::player_::Stopped112 void on_entry(Event const&,FSM& ) {++entry_counter;} 113 template <class Event,class FSM> on_exit__anon5ecd1c550111::player_::Stopped114 void on_exit(Event const&,FSM& ) {++exit_counter;} 115 int entry_counter; 116 int exit_counter; 117 }; 118 119 struct Playing : public msm::front::state<> 120 { 121 template <class Event,class FSM> on_entry__anon5ecd1c550111::player_::Playing122 void on_entry(Event const&,FSM& ) {++entry_counter;} 123 template <class Event,class FSM> on_exit__anon5ecd1c550111::player_::Playing124 void on_exit(Event const&,FSM& ) {++exit_counter;} 125 int entry_counter; 126 int exit_counter; 127 }; 128 129 // state not defining any entry or exit 130 struct Paused : public msm::front::state<> 131 { 132 template <class Event,class FSM> on_entry__anon5ecd1c550111::player_::Paused133 void on_entry(Event const&,FSM& ) {++entry_counter;} 134 template <class Event,class FSM> on_exit__anon5ecd1c550111::player_::Paused135 void on_exit(Event const&,FSM& ) {++exit_counter;} 136 int entry_counter; 137 int exit_counter; 138 }; 139 140 // the initial state of the player SM. Must be defined 141 typedef Empty initial_state; 142 143 // transition actions start_playback__anon5ecd1c550111::player_144 void start_playback(play const&) {++start_playback_counter; } open_drawer__anon5ecd1c550111::player_145 void open_drawer(open_close const&) { } store_cd_info__anon5ecd1c550111::player_146 void store_cd_info(cd_detected const&) { } stop_playback__anon5ecd1c550111::player_147 void stop_playback(stop const&) { } pause_playback__anon5ecd1c550111::player_148 void pause_playback(pause const&) { } resume_playback__anon5ecd1c550111::player_149 void resume_playback(end_pause const&) { } stop_and_open__anon5ecd1c550111::player_150 void stop_and_open(open_close const&) { } stopped_again__anon5ecd1c550111::player_151 void stopped_again(stop const&){} 152 // guard conditions good_disk_format__anon5ecd1c550111::player_153 bool good_disk_format(cd_detected const& evt) 154 { 155 // to test a guard condition, let's say we understand only CDs, not DVD 156 if (evt.disc_type != DISK_CD) 157 { 158 return false; 159 } 160 return true; 161 } can_close_drawer__anon5ecd1c550111::player_162 bool can_close_drawer(open_close const&) 163 { 164 ++can_close_drawer_counter; 165 return true; 166 } 167 168 typedef player_ p; // makes transition table cleaner 169 170 // Transition table for player 171 struct transition_table : mpl::vector< 172 // Start Event Next Action Guard 173 // +---------+-------------+---------+---------------------+----------------------+ 174 a_row < Stopped , play , Playing , &p::start_playback >, 175 a_row < Stopped , open_close , Open , &p::open_drawer >, 176 _row < Stopped , stop , Stopped >, 177 // +---------+-------------+---------+---------------------+----------------------+ 178 g_row < Open , open_close , Empty , &p::can_close_drawer >, 179 // +---------+-------------+---------+---------------------+----------------------+ 180 a_row < Empty , open_close , Open , &p::open_drawer >, 181 row < Empty , cd_detected , Stopped , &p::store_cd_info ,&p::good_disk_format >, 182 // +---------+-------------+---------+---------------------+----------------------+ 183 a_row < Playing , stop , Stopped , &p::stop_playback >, 184 a_row < Playing , pause , Paused , &p::pause_playback >, 185 a_row < Playing , open_close , Open , &p::stop_and_open >, 186 // +---------+-------------+---------+---------------------+----------------------+ 187 a_row < Paused , end_pause , Playing , &p::resume_playback >, 188 a_row < Paused , stop , Stopped , &p::stop_playback >, 189 a_row < Paused , open_close , Open , &p::stop_and_open > 190 // +---------+-------------+---------+---------------------+----------------------+ 191 > {}; 192 // Replaces the default no-transition response. 193 template <class FSM,class Event> no_transition__anon5ecd1c550111::player_194 void no_transition(Event const& , FSM&,int) 195 { 196 BOOST_FAIL("no_transition called!"); 197 } 198 // init counters 199 template <class Event,class FSM> on_entry__anon5ecd1c550111::player_200 void on_entry(Event const&,FSM& fsm) 201 { 202 fsm.template get_state<player_::Stopped&>().entry_counter=0; 203 fsm.template get_state<player_::Stopped&>().exit_counter=0; 204 fsm.template get_state<player_::Open&>().entry_counter=0; 205 fsm.template get_state<player_::Open&>().exit_counter=0; 206 fsm.template get_state<player_::Empty&>().entry_counter=0; 207 fsm.template get_state<player_::Empty&>().exit_counter=0; 208 fsm.template get_state<player_::Empty&>().some_dummy_data=3; 209 fsm.template get_state<player_::Playing&>().entry_counter=0; 210 fsm.template get_state<player_::Playing&>().exit_counter=0; 211 fsm.template get_state<player_::Paused&>().entry_counter=0; 212 fsm.template get_state<player_::Paused&>().exit_counter=0; 213 } 214 215 }; 216 // Pick a back-end 217 typedef msm::back::state_machine<player_> player; 218 219 // static char const* const state_names[] = { "Stopped", "Open", "Empty", "Playing", "Paused" }; 220 221 BOOST_AUTO_TEST_CASE(my_test)222 BOOST_AUTO_TEST_CASE( my_test ) 223 { 224 player p; 225 226 p.start(); 227 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 1,"Empty entry not called correctly"); 228 229 p.process_event(open_close()); 230 BOOST_CHECK_MESSAGE(p.current_state()[0] == 1,"Open should be active"); //Open 231 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 1,"Empty exit not called correctly"); 232 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().entry_counter == 1,"Open entry not called correctly"); 233 234 // test the serialization 235 std::ofstream ofs("fsm.txt"); 236 // save fsm to archive (current state is Open) 237 { 238 boost::archive::text_oarchive oa(ofs); 239 // write class instance to archive 240 oa << p; 241 } 242 // reload fsm in state Open 243 player p2; 244 { 245 // create and open an archive for input 246 std::ifstream ifs("fsm.txt"); 247 boost::archive::text_iarchive ia(ifs); 248 // read class state from archive 249 ia >> p2; 250 } 251 // we now use p2 as it was loaded 252 // check that we kept Empty's data value 253 BOOST_CHECK_MESSAGE(p2.get_state<player_::Empty&>().some_dummy_data == 3,"Empty not deserialized correctly"); 254 BOOST_CHECK_MESSAGE(p2.front_end_data == 4,"Front-end not deserialized correctly"); 255 256 p.process_event(open_close()); 257 BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active"); //Empty 258 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly"); 259 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly"); 260 BOOST_CHECK_MESSAGE(p.can_close_drawer_counter == 1,"guard not called correctly"); 261 262 p.process_event( 263 cd_detected("louie, louie",DISK_DVD)); 264 BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active"); //Empty 265 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly"); 266 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly"); 267 268 p.process_event( 269 cd_detected("louie, louie",DISK_CD)); 270 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 271 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 2,"Empty exit not called correctly"); 272 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 1,"Stopped entry not called correctly"); 273 274 p.process_event(play()); 275 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 276 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 1,"Stopped exit not called correctly"); 277 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 1,"Playing entry not called correctly"); 278 BOOST_CHECK_MESSAGE(p.start_playback_counter == 1,"action not called correctly"); 279 280 p.process_event(pause()); 281 BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active"); //Paused 282 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 1,"Playing exit not called correctly"); 283 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 1,"Paused entry not called correctly"); 284 285 // go back to Playing 286 p.process_event(end_pause()); 287 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 288 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 1,"Paused exit not called correctly"); 289 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 2,"Playing entry not called correctly"); 290 291 p.process_event(pause()); 292 BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active"); //Paused 293 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 2,"Playing exit not called correctly"); 294 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 2,"Paused entry not called correctly"); 295 296 p.process_event(stop()); 297 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 298 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 2,"Paused exit not called correctly"); 299 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 2,"Stopped entry not called correctly"); 300 301 p.process_event(stop()); 302 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 303 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 2,"Stopped exit not called correctly"); 304 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 3,"Stopped entry not called correctly"); 305 } 306 } 307 // eliminate object tracking (even if serialized through a pointer) 308 // at the risk of a programming error creating duplicate objects. 309 // this is to get rid of warning because p is not const 310 BOOST_CLASS_TRACKING(player, boost::serialization::track_never) 311 312