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 struct internal_evt {}; 33 struct to_ignore {}; 34 35 // A "complicated" event type that carries some data. 36 enum DiskTypeEnum 37 { 38 DISK_CD=0, 39 DISK_DVD=1 40 }; 41 struct cd_detected 42 { cd_detected__anonaa5d9ad40111::cd_detected43 cd_detected(std::string name, DiskTypeEnum diskType) 44 : name(name), 45 disc_type(diskType) 46 {} 47 48 std::string name; 49 DiskTypeEnum disc_type; 50 }; 51 52 // front-end: define the FSM structure 53 struct player_ : public msm::front::state_machine_def<player_> 54 { 55 unsigned int start_playback_counter; 56 unsigned int can_close_drawer_counter; 57 unsigned int internal_action_counter; 58 unsigned int internal_guard_counter; 59 player___anonaa5d9ad40111::player_60 player_(): 61 start_playback_counter(0), 62 can_close_drawer_counter(0), 63 internal_action_counter(0), 64 internal_guard_counter(0) 65 {} 66 67 // The list of FSM states 68 struct Empty : public msm::front::state<> 69 { 70 template <class Event,class FSM> on_entry__anonaa5d9ad40111::player_::Empty71 void on_entry(Event const&,FSM& ) {++entry_counter;} 72 template <class Event,class FSM> on_exit__anonaa5d9ad40111::player_::Empty73 void on_exit(Event const&,FSM& ) {++exit_counter;} 74 int entry_counter; 75 int exit_counter; 76 }; 77 struct Open : public msm::front::state<> 78 { 79 template <class Event,class FSM> on_entry__anonaa5d9ad40111::player_::Open80 void on_entry(Event const&,FSM& ) {++entry_counter;} 81 template <class Event,class FSM> on_exit__anonaa5d9ad40111::player_::Open82 void on_exit(Event const&,FSM& ) {++exit_counter;} 83 int entry_counter; 84 int exit_counter; 85 }; 86 87 // sm_ptr still supported but deprecated as functors are a much better way to do the same thing 88 struct Stopped : public msm::front::state<> 89 { 90 template <class Event,class FSM> on_entry__anonaa5d9ad40111::player_::Stopped91 void on_entry(Event const&,FSM& ) {++entry_counter;} 92 template <class Event,class FSM> on_exit__anonaa5d9ad40111::player_::Stopped93 void on_exit(Event const&,FSM& ) {++exit_counter;} 94 int entry_counter; 95 int exit_counter; 96 }; 97 98 struct Playing : public msm::front::state<> 99 { 100 template <class Event,class FSM> on_entry__anonaa5d9ad40111::player_::Playing101 void on_entry(Event const&,FSM& ) {++entry_counter;} 102 template <class Event,class FSM> on_exit__anonaa5d9ad40111::player_::Playing103 void on_exit(Event const&,FSM& ) {++exit_counter;} 104 int entry_counter; 105 int exit_counter; 106 }; 107 108 // state not defining any entry or exit 109 struct Paused : public msm::front::state<> 110 { 111 template <class Event,class FSM> on_entry__anonaa5d9ad40111::player_::Paused112 void on_entry(Event const&,FSM& ) {++entry_counter;} 113 template <class Event,class FSM> on_exit__anonaa5d9ad40111::player_::Paused114 void on_exit(Event const&,FSM& ) {++exit_counter;} 115 int entry_counter; 116 int exit_counter; 117 }; 118 119 // the initial state of the player SM. Must be defined 120 typedef Empty initial_state; 121 122 // transition actions start_playback__anonaa5d9ad40111::player_123 void start_playback(play const&) {++start_playback_counter; } open_drawer__anonaa5d9ad40111::player_124 void open_drawer(open_close const&) { } store_cd_info__anonaa5d9ad40111::player_125 void store_cd_info(cd_detected const&) { } stop_playback__anonaa5d9ad40111::player_126 void stop_playback(stop const&) { } pause_playback__anonaa5d9ad40111::player_127 void pause_playback(pause const&) { } resume_playback__anonaa5d9ad40111::player_128 void resume_playback(end_pause const&) { } stop_and_open__anonaa5d9ad40111::player_129 void stop_and_open(open_close const&) { } stopped_again__anonaa5d9ad40111::player_130 void stopped_again(stop const&){} internal_action__anonaa5d9ad40111::player_131 void internal_action(internal_evt const&){++internal_action_counter; } internal_guard__anonaa5d9ad40111::player_132 bool internal_guard(cd_detected const&){++internal_guard_counter;return false;} internal_guard2__anonaa5d9ad40111::player_133 bool internal_guard2(internal_evt const&){++internal_guard_counter;return true;} 134 // guard conditions good_disk_format__anonaa5d9ad40111::player_135 bool good_disk_format(cd_detected const& evt) 136 { 137 // to test a guard condition, let's say we understand only CDs, not DVD 138 if (evt.disc_type != DISK_CD) 139 { 140 return false; 141 } 142 return true; 143 } can_close_drawer__anonaa5d9ad40111::player_144 bool can_close_drawer(open_close const&) 145 { 146 ++can_close_drawer_counter; 147 return true; 148 } 149 150 typedef player_ p; // makes transition table cleaner 151 152 // Transition table for player 153 struct transition_table : mpl::vector< 154 // Start Event Next Action Guard 155 // +---------+-------------+---------+---------------------+----------------------+ 156 a_row < Stopped , play , Playing , &p::start_playback >, 157 a_row < Stopped , open_close , Open , &p::open_drawer >, 158 _row < Stopped , stop , Stopped >, 159 // +---------+-------------+---------+---------------------+----------------------+ 160 g_row < Open , open_close , Empty , &p::can_close_drawer >, 161 // +---------+-------------+---------+---------------------+----------------------+ 162 a_row < Empty , open_close , Open , &p::open_drawer >, 163 row < Empty , cd_detected , Stopped , &p::store_cd_info ,&p::good_disk_format >, 164 irow < Empty , internal_evt, &p::internal_action ,&p::internal_guard2 >, 165 _irow < Empty , to_ignore >, 166 g_irow < Empty , cd_detected ,&p::internal_guard >, 167 // +---------+-------------+---------+---------------------+----------------------+ 168 a_row < Playing , stop , Stopped , &p::stop_playback >, 169 a_row < Playing , pause , Paused , &p::pause_playback >, 170 a_row < Playing , open_close , Open , &p::stop_and_open >, 171 // +---------+-------------+---------+---------------------+----------------------+ 172 a_row < Paused , end_pause , Playing , &p::resume_playback >, 173 a_row < Paused , stop , Stopped , &p::stop_playback >, 174 a_row < Paused , open_close , Open , &p::stop_and_open > 175 // +---------+-------------+---------+---------------------+----------------------+ 176 > {}; 177 // Replaces the default no-transition response. 178 template <class FSM,class Event> no_transition__anonaa5d9ad40111::player_179 void no_transition(Event const&, FSM&,int) 180 { 181 BOOST_FAIL("no_transition called!"); 182 } 183 // init counters 184 template <class Event,class FSM> on_entry__anonaa5d9ad40111::player_185 void on_entry(Event const&,FSM& fsm) 186 { 187 fsm.template get_state<player_::Stopped&>().entry_counter=0; 188 fsm.template get_state<player_::Stopped&>().exit_counter=0; 189 fsm.template get_state<player_::Open&>().entry_counter=0; 190 fsm.template get_state<player_::Open&>().exit_counter=0; 191 fsm.template get_state<player_::Empty&>().entry_counter=0; 192 fsm.template get_state<player_::Empty&>().exit_counter=0; 193 fsm.template get_state<player_::Playing&>().entry_counter=0; 194 fsm.template get_state<player_::Playing&>().exit_counter=0; 195 fsm.template get_state<player_::Paused&>().entry_counter=0; 196 fsm.template get_state<player_::Paused&>().exit_counter=0; 197 } 198 199 }; 200 // Pick a back-end 201 typedef msm::back::state_machine<player_> player; 202 203 // static char const* const state_names[] = { "Stopped", "Open", "Empty", "Playing", "Paused" }; 204 205 BOOST_AUTO_TEST_CASE(my_test)206 BOOST_AUTO_TEST_CASE( my_test ) 207 { 208 player p; 209 210 p.start(); 211 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 1,"Empty entry not called correctly"); 212 // internal events 213 p.process_event(to_ignore()); 214 p.process_event(internal_evt()); 215 BOOST_CHECK_MESSAGE(p.internal_action_counter == 1,"Internal action not called correctly"); 216 BOOST_CHECK_MESSAGE(p.internal_guard_counter == 1,"Internal guard not called correctly"); 217 218 p.process_event(open_close()); 219 BOOST_CHECK_MESSAGE(p.current_state()[0] == 1,"Open should be active"); //Open 220 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 1,"Empty exit not called correctly"); 221 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().entry_counter == 1,"Open entry not called correctly"); 222 223 p.process_event(open_close()); 224 BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active"); //Empty 225 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly"); 226 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly"); 227 BOOST_CHECK_MESSAGE(p.can_close_drawer_counter == 1,"guard not called correctly"); 228 229 p.process_event( 230 cd_detected("louie, louie",DISK_DVD)); 231 BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active"); //Empty 232 BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly"); 233 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly"); 234 BOOST_CHECK_MESSAGE(p.internal_guard_counter == 2,"Internal guard not called correctly"); 235 236 p.process_event( 237 cd_detected("louie, louie",DISK_CD)); 238 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 239 BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 2,"Empty exit not called correctly"); 240 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 1,"Stopped entry not called correctly"); 241 BOOST_CHECK_MESSAGE(p.internal_guard_counter == 3,"Internal guard not called correctly"); 242 243 p.process_event(play()); 244 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 245 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 1,"Stopped exit not called correctly"); 246 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 1,"Playing entry not called correctly"); 247 BOOST_CHECK_MESSAGE(p.start_playback_counter == 1,"action not called correctly"); 248 249 p.process_event(pause()); 250 BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active"); //Paused 251 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 1,"Playing exit not called correctly"); 252 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 1,"Paused entry not called correctly"); 253 254 // go back to Playing 255 p.process_event(end_pause()); 256 BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing 257 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 1,"Paused exit not called correctly"); 258 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 2,"Playing entry not called correctly"); 259 260 p.process_event(pause()); 261 BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active"); //Paused 262 BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 2,"Playing exit not called correctly"); 263 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 2,"Paused entry not called correctly"); 264 265 p.process_event(stop()); 266 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 267 BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 2,"Paused exit not called correctly"); 268 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 2,"Stopped entry not called correctly"); 269 270 p.process_event(stop()); 271 BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped 272 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 2,"Stopped exit not called correctly"); 273 BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 3,"Stopped entry not called correctly"); 274 } 275 } 276 277