]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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 | { | |
47 | 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 | ||
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> | |
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> | |
83 | void on_entry(Event const&,FSM& ) {++entry_counter;} | |
84 | template <class Event,class FSM> | |
85 | 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> | |
92 | 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> | |
101 | void on_entry(Event const&,FSM& ) {++entry_counter;} | |
102 | template <class Event,class FSM> | |
103 | 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> | |
112 | void on_entry(Event const&,FSM& ) {++entry_counter;} | |
113 | template <class Event,class FSM> | |
114 | 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> | |
122 | void on_entry(Event const&,FSM& ) {++entry_counter;} | |
123 | template <class Event,class FSM> | |
124 | 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> | |
133 | void on_entry(Event const&,FSM& ) {++entry_counter;} | |
134 | template <class Event,class FSM> | |
135 | 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 | |
144 | void start_playback(play const&) {++start_playback_counter; } | |
145 | void open_drawer(open_close const&) { } | |
146 | void store_cd_info(cd_detected const&) { } | |
147 | void stop_playback(stop const&) { } | |
148 | void pause_playback(pause const&) { } | |
149 | void resume_playback(end_pause const&) { } | |
150 | void stop_and_open(open_close const&) { } | |
151 | void stopped_again(stop const&){} | |
152 | // guard conditions | |
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 | } | |
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> | |
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> | |
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 | ||
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 |