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 | // back-end |
12 | #include <boost/msm/back/state_machine.hpp> |
13 | //front-end |
14 | #include <boost/msm/front/state_machine_def.hpp> |
15 | #include <boost/msm/front/functor_row.hpp> |
16 | #include <boost/msm/front/euml/common.hpp> |
17 | #ifndef BOOST_MSM_NONSTANDALONE_TEST |
18 | #define BOOST_TEST_MODULE MyTest |
19 | #endif |
20 | #include <boost/test/unit_test.hpp> |
21 | |
22 | using namespace std; |
23 | namespace msm = boost::msm; |
24 | using namespace msm::front; |
25 | namespace mpl = boost::mpl; |
26 | |
27 | namespace |
28 | { |
29 | // events |
30 | struct play {}; |
31 | struct end_pause {}; |
32 | struct stop {}; |
33 | struct pause {}; |
34 | struct open_close {}; |
35 | struct internal_evt {}; |
36 | struct to_ignore {}; |
37 | |
38 | // A "complicated" event type that carries some data. |
39 | enum DiskTypeEnum |
40 | { |
41 | DISK_CD=0, |
42 | DISK_DVD=1 |
43 | }; |
44 | struct cd_detected |
45 | { |
46 | cd_detected(std::string name, DiskTypeEnum diskType) |
47 | : name(name), |
48 | disc_type(diskType) |
49 | {} |
50 | |
51 | std::string name; |
52 | DiskTypeEnum disc_type; |
53 | }; |
54 | |
55 | // front-end: define the FSM structure |
56 | struct player_ : public msm::front::state_machine_def<player_> |
57 | { |
58 | unsigned int start_playback_counter; |
59 | unsigned int can_close_drawer_counter; |
60 | unsigned int internal_action_counter; |
61 | unsigned int internal_guard_counter; |
62 | |
63 | player_(): |
64 | start_playback_counter(0), |
65 | can_close_drawer_counter(0), |
66 | internal_action_counter(0), |
67 | internal_guard_counter(0) |
68 | {} |
69 | |
70 | // The list of FSM states |
71 | struct Empty : public msm::front::state<> |
72 | { |
73 | template <class Event,class FSM> |
74 | void on_entry(Event const&,FSM& ) {++entry_counter;} |
75 | template <class Event,class FSM> |
76 | void on_exit(Event const&,FSM& ) {++exit_counter;} |
77 | int entry_counter; |
78 | int exit_counter; |
79 | unsigned int empty_internal_guard_counter; |
80 | unsigned int empty_internal_action_counter; |
81 | struct internal_guard_fct |
82 | { |
83 | template <class EVT,class FSM,class SourceState,class TargetState> |
84 | bool operator()(EVT const& ,FSM&,SourceState& src,TargetState& ) |
85 | { |
86 | ++src.empty_internal_guard_counter; |
87 | return false; |
88 | } |
89 | }; |
90 | struct internal_action_fct |
91 | { |
92 | template <class EVT,class FSM,class SourceState,class TargetState> |
93 | void operator()(EVT const& ,FSM& ,SourceState& src,TargetState& ) |
94 | { |
95 | ++src.empty_internal_action_counter; |
96 | } |
97 | }; |
98 | // Transition table for Empty |
99 | struct internal_transition_table : mpl::vector< |
100 | // Start Event Next Action Guard |
101 | Internal < internal_evt , internal_action_fct ,internal_guard_fct > |
102 | // +---------+-------------+---------+---------------------+----------------------+ |
103 | > {}; |
104 | }; |
105 | struct Open : public msm::front::state<> |
106 | { |
107 | template <class Event,class FSM> |
108 | void on_entry(Event const&,FSM& ) {++entry_counter;} |
109 | template <class Event,class FSM> |
110 | void on_exit(Event const&,FSM& ) {++exit_counter;} |
111 | int entry_counter; |
112 | int exit_counter; |
113 | }; |
114 | |
115 | // sm_ptr still supported but deprecated as functors are a much better way to do the same thing |
116 | struct Stopped : public msm::front::state<> |
117 | { |
118 | template <class Event,class FSM> |
119 | void on_entry(Event const&,FSM& ) {++entry_counter;} |
120 | template <class Event,class FSM> |
121 | void on_exit(Event const&,FSM& ) {++exit_counter;} |
122 | int entry_counter; |
123 | int exit_counter; |
124 | }; |
125 | |
126 | struct Playing : public msm::front::state<> |
127 | { |
128 | template <class Event,class FSM> |
129 | void on_entry(Event const&,FSM& ) {++entry_counter;} |
130 | template <class Event,class FSM> |
131 | void on_exit(Event const&,FSM& ) {++exit_counter;} |
132 | int entry_counter; |
133 | int exit_counter; |
134 | }; |
135 | |
136 | // state not defining any entry or exit |
137 | struct Paused : public msm::front::state<> |
138 | { |
139 | template <class Event,class FSM> |
140 | void on_entry(Event const&,FSM& ) {++entry_counter;} |
141 | template <class Event,class FSM> |
142 | void on_exit(Event const&,FSM& ) {++exit_counter;} |
143 | int entry_counter; |
144 | int exit_counter; |
145 | }; |
146 | |
147 | // the initial state of the player SM. Must be defined |
148 | typedef Empty initial_state; |
149 | |
150 | // transition actions |
151 | void start_playback(play const&) {++start_playback_counter; } |
152 | void open_drawer(open_close const&) { } |
153 | void store_cd_info(cd_detected const&) { } |
154 | void stop_playback(stop const&) { } |
155 | void pause_playback(pause const&) { } |
156 | void resume_playback(end_pause const&) { } |
157 | void stop_and_open(open_close const&) { } |
158 | void stopped_again(stop const&){} |
159 | struct internal_action |
160 | { |
161 | template <class EVT,class FSM,class SourceState,class TargetState> |
162 | void operator()(EVT const& ,FSM& fsm,SourceState& ,TargetState& ) |
163 | { |
164 | ++fsm.internal_action_counter; |
165 | } |
166 | }; |
167 | struct internal_guard |
168 | { |
169 | template <class EVT,class FSM,class SourceState,class TargetState> |
170 | bool operator()(EVT const& ,FSM& fsm,SourceState& ,TargetState& ) |
171 | { |
172 | ++fsm.internal_guard_counter; |
173 | return false; |
174 | } |
175 | }; |
176 | struct internal_guard2 |
177 | { |
178 | template <class EVT,class FSM,class SourceState,class TargetState> |
179 | bool operator()(EVT const& ,FSM& fsm,SourceState& ,TargetState& ) |
180 | { |
181 | ++fsm.internal_guard_counter; |
182 | return true; |
183 | } |
184 | }; |
185 | // guard conditions |
186 | bool good_disk_format(cd_detected const& evt) |
187 | { |
188 | // to test a guard condition, let's say we understand only CDs, not DVD |
189 | if (evt.disc_type != DISK_CD) |
190 | { |
191 | return false; |
192 | } |
193 | return true; |
194 | } |
195 | bool can_close_drawer(open_close const&) |
196 | { |
197 | ++can_close_drawer_counter; |
198 | return true; |
199 | } |
200 | |
201 | typedef player_ p; // makes transition table cleaner |
202 | |
203 | // Transition table for player |
204 | struct transition_table : mpl::vector< |
205 | // Start Event Next Action Guard |
206 | // +---------+-------------+---------+---------------------+----------------------+ |
207 | a_row < Stopped , play , Playing , &p::start_playback >, |
208 | a_row < Stopped , open_close , Open , &p::open_drawer >, |
209 | _row < Stopped , stop , Stopped >, |
210 | // +---------+-------------+---------+---------------------+----------------------+ |
211 | g_row < Open , open_close , Empty , &p::can_close_drawer >, |
212 | // +---------+-------------+---------+---------------------+----------------------+ |
213 | a_row < Empty , open_close , Open , &p::open_drawer >, |
214 | row < Empty , cd_detected , Stopped , &p::store_cd_info ,&p::good_disk_format >, |
215 | Row < Empty , internal_evt, none , internal_action ,internal_guard2 >, |
216 | Row < Empty , to_ignore , none , none , none >, |
217 | Row < Empty , cd_detected , none , none ,internal_guard >, |
218 | // +---------+-------------+---------+---------------------+----------------------+ |
219 | a_row < Playing , stop , Stopped , &p::stop_playback >, |
220 | a_row < Playing , pause , Paused , &p::pause_playback >, |
221 | a_row < Playing , open_close , Open , &p::stop_and_open >, |
222 | // +---------+-------------+---------+---------------------+----------------------+ |
223 | a_row < Paused , end_pause , Playing , &p::resume_playback >, |
224 | a_row < Paused , stop , Stopped , &p::stop_playback >, |
225 | a_row < Paused , open_close , Open , &p::stop_and_open > |
226 | // +---------+-------------+---------+---------------------+----------------------+ |
227 | > {}; |
228 | // Replaces the default no-transition response. |
229 | template <class FSM,class Event> |
230 | void no_transition(Event const&, FSM&,int) |
231 | { |
232 | BOOST_FAIL("no_transition called!" ); |
233 | } |
234 | // init counters |
235 | template <class Event,class FSM> |
236 | void on_entry(Event const&,FSM& fsm) |
237 | { |
238 | fsm.template get_state<player_::Stopped&>().entry_counter=0; |
239 | fsm.template get_state<player_::Stopped&>().exit_counter=0; |
240 | fsm.template get_state<player_::Open&>().entry_counter=0; |
241 | fsm.template get_state<player_::Open&>().exit_counter=0; |
242 | fsm.template get_state<player_::Empty&>().entry_counter=0; |
243 | fsm.template get_state<player_::Empty&>().exit_counter=0; |
244 | fsm.template get_state<player_::Empty&>().empty_internal_guard_counter=0; |
245 | fsm.template get_state<player_::Empty&>().empty_internal_action_counter=0; |
246 | fsm.template get_state<player_::Playing&>().entry_counter=0; |
247 | fsm.template get_state<player_::Playing&>().exit_counter=0; |
248 | fsm.template get_state<player_::Paused&>().entry_counter=0; |
249 | fsm.template get_state<player_::Paused&>().exit_counter=0; |
250 | } |
251 | |
252 | }; |
253 | // Pick a back-end |
254 | typedef msm::back::state_machine<player_> player; |
255 | |
256 | // static char const* const state_names[] = { "Stopped", "Open", "Empty", "Playing", "Paused" }; |
257 | |
258 | |
259 | BOOST_AUTO_TEST_CASE( simple_internal_functors_test ) |
260 | { |
261 | player p; |
262 | |
263 | p.start(); |
264 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 1,"Empty entry not called correctly" ); |
265 | // internal events |
266 | p.process_event(evt: to_ignore()); |
267 | p.process_event(evt: internal_evt()); |
268 | BOOST_CHECK_MESSAGE(p.internal_action_counter == 1,"Internal action not called correctly" ); |
269 | BOOST_CHECK_MESSAGE(p.internal_guard_counter == 1,"Internal guard not called correctly" ); |
270 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().empty_internal_action_counter == 0,"Empty internal action not called correctly" ); |
271 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().empty_internal_guard_counter == 1,"Empty internal guard not called correctly" ); |
272 | |
273 | p.process_event(evt: open_close()); |
274 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 1,"Open should be active" ); //Open |
275 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 1,"Empty exit not called correctly" ); |
276 | BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().entry_counter == 1,"Open entry not called correctly" ); |
277 | |
278 | p.process_event(evt: open_close()); |
279 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active" ); //Empty |
280 | BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly" ); |
281 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly" ); |
282 | BOOST_CHECK_MESSAGE(p.can_close_drawer_counter == 1,"guard not called correctly" ); |
283 | |
284 | p.process_event( |
285 | evt: cd_detected("louie, louie" ,DISK_DVD)); |
286 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active" ); //Empty |
287 | BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly" ); |
288 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly" ); |
289 | BOOST_CHECK_MESSAGE(p.internal_guard_counter == 2,"Internal guard not called correctly" ); |
290 | |
291 | p.process_event( |
292 | evt: cd_detected("louie, louie" ,DISK_CD)); |
293 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active" ); //Stopped |
294 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 2,"Empty exit not called correctly" ); |
295 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 1,"Stopped entry not called correctly" ); |
296 | BOOST_CHECK_MESSAGE(p.internal_guard_counter == 3,"Internal guard not called correctly" ); |
297 | |
298 | p.process_event(evt: play()); |
299 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active" ); //Playing |
300 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 1,"Stopped exit not called correctly" ); |
301 | BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 1,"Playing entry not called correctly" ); |
302 | BOOST_CHECK_MESSAGE(p.start_playback_counter == 1,"action not called correctly" ); |
303 | |
304 | p.process_event(evt: pause()); |
305 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active" ); //Paused |
306 | BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 1,"Playing exit not called correctly" ); |
307 | BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 1,"Paused entry not called correctly" ); |
308 | |
309 | // go back to Playing |
310 | p.process_event(evt: end_pause()); |
311 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active" ); //Playing |
312 | BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 1,"Paused exit not called correctly" ); |
313 | BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 2,"Playing entry not called correctly" ); |
314 | |
315 | p.process_event(evt: pause()); |
316 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active" ); //Paused |
317 | BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 2,"Playing exit not called correctly" ); |
318 | BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 2,"Paused entry not called correctly" ); |
319 | |
320 | p.process_event(evt: stop()); |
321 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active" ); //Stopped |
322 | BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 2,"Paused exit not called correctly" ); |
323 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 2,"Stopped entry not called correctly" ); |
324 | |
325 | p.process_event(evt: stop()); |
326 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active" ); //Stopped |
327 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 2,"Stopped exit not called correctly" ); |
328 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 3,"Stopped entry not called correctly" ); |
329 | } |
330 | } |
331 | |
332 | |