]>
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 | ||
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 | ||
33 | // A "complicated" event type that carries some data. | |
34 | enum DiskTypeEnum | |
35 | { | |
36 | DISK_CD=0, | |
37 | DISK_DVD=1 | |
38 | }; | |
39 | struct cd_detected | |
40 | { | |
41 | cd_detected(std::string name, DiskTypeEnum diskType) | |
42 | : name(name), | |
43 | disc_type(diskType) | |
44 | {} | |
45 | ||
46 | std::string name; | |
47 | DiskTypeEnum disc_type; | |
48 | }; | |
49 | ||
50 | // front-end: define the FSM structure | |
51 | struct player_ : public msm::front::state_machine_def<player_> | |
52 | { | |
53 | unsigned int start_playback_counter; | |
54 | unsigned int can_close_drawer_counter; | |
55 | ||
56 | player_(): | |
57 | start_playback_counter(0), | |
58 | can_close_drawer_counter(0) | |
59 | {} | |
60 | ||
61 | // The list of FSM states | |
62 | struct Empty : public msm::front::state<> | |
63 | { | |
64 | template <class Event,class FSM> | |
65 | void on_entry(Event const&,FSM& ) {++entry_counter;} | |
66 | template <class Event,class FSM> | |
67 | void on_exit(Event const&,FSM& ) {++exit_counter;} | |
68 | int entry_counter; | |
69 | int exit_counter; | |
70 | }; | |
71 | struct Open : 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 | }; | |
80 | ||
81 | // sm_ptr still supported but deprecated as functors are a much better way to do the same thing | |
82 | struct Stopped : public msm::front::state<> | |
83 | { | |
84 | template <class Event,class FSM> | |
85 | void on_entry(Event const&,FSM& ) {++entry_counter;} | |
86 | template <class Event,class FSM> | |
87 | void on_exit(Event const&,FSM& ) {++exit_counter;} | |
88 | int entry_counter; | |
89 | int exit_counter; | |
90 | }; | |
91 | ||
92 | struct Playing : public msm::front::state<> | |
93 | { | |
94 | template <class Event,class FSM> | |
95 | void on_entry(Event const&,FSM& ) {++entry_counter;} | |
96 | template <class Event,class FSM> | |
97 | void on_exit(Event const&,FSM& ) {++exit_counter;} | |
98 | int entry_counter; | |
99 | int exit_counter; | |
100 | }; | |
101 | ||
102 | // state not defining any entry or exit | |
103 | struct Paused : public msm::front::state<> | |
104 | { | |
105 | template <class Event,class FSM> | |
106 | void on_entry(Event const&,FSM& ) {++entry_counter;} | |
107 | template <class Event,class FSM> | |
108 | void on_exit(Event const&,FSM& ) {++exit_counter;} | |
109 | int entry_counter; | |
110 | int exit_counter; | |
111 | }; | |
112 | ||
113 | // the initial state of the player SM. Must be defined | |
114 | typedef Empty initial_state; | |
115 | ||
116 | // transition actions | |
117 | void start_playback(play const&) {++start_playback_counter; } | |
118 | void open_drawer(open_close const&) { } | |
119 | void store_cd_info(cd_detected const&) { } | |
120 | void stop_playback(stop const&) { } | |
121 | void pause_playback(pause const&) { } | |
122 | void resume_playback(end_pause const&) { } | |
123 | void stop_and_open(open_close const&) { } | |
124 | void stopped_again(stop const&){} | |
125 | // guard conditions | |
126 | bool good_disk_format(cd_detected const& evt) | |
127 | { | |
128 | // to test a guard condition, let's say we understand only CDs, not DVD | |
129 | if (evt.disc_type != DISK_CD) | |
130 | { | |
131 | return false; | |
132 | } | |
133 | return true; | |
134 | } | |
135 | bool can_close_drawer(open_close const&) | |
136 | { | |
137 | ++can_close_drawer_counter; | |
138 | return true; | |
139 | } | |
140 | ||
141 | typedef player_ p; // makes transition table cleaner | |
142 | ||
143 | // Transition table for player | |
144 | struct transition_table : mpl::vector< | |
145 | // Start Event Next Action Guard | |
146 | // +---------+-------------+---------+---------------------+----------------------+ | |
147 | a_row < Stopped , play , Playing , &p::start_playback >, | |
148 | a_row < Stopped , open_close , Open , &p::open_drawer >, | |
149 | _row < Stopped , stop , Stopped >, | |
150 | // +---------+-------------+---------+---------------------+----------------------+ | |
151 | g_row < Open , open_close , Empty , &p::can_close_drawer >, | |
152 | // +---------+-------------+---------+---------------------+----------------------+ | |
153 | a_row < Empty , open_close , Open , &p::open_drawer >, | |
154 | row < Empty , cd_detected , Stopped , &p::store_cd_info ,&p::good_disk_format >, | |
155 | // +---------+-------------+---------+---------------------+----------------------+ | |
156 | a_row < Playing , stop , Stopped , &p::stop_playback >, | |
157 | a_row < Playing , pause , Paused , &p::pause_playback >, | |
158 | a_row < Playing , open_close , Open , &p::stop_and_open >, | |
159 | // +---------+-------------+---------+---------------------+----------------------+ | |
160 | a_row < Paused , end_pause , Playing , &p::resume_playback >, | |
161 | a_row < Paused , stop , Stopped , &p::stop_playback >, | |
162 | a_row < Paused , open_close , Open , &p::stop_and_open > | |
163 | // +---------+-------------+---------+---------------------+----------------------+ | |
164 | > {}; | |
165 | // Replaces the default no-transition response. | |
166 | template <class FSM,class Event> | |
167 | void no_transition(Event const& , FSM&,int) | |
168 | { | |
169 | BOOST_FAIL("no_transition called!"); | |
170 | } | |
171 | // init counters | |
172 | template <class Event,class FSM> | |
173 | void on_entry(Event const&,FSM& fsm) | |
174 | { | |
175 | fsm.template get_state<player_::Stopped&>().entry_counter=0; | |
176 | fsm.template get_state<player_::Stopped&>().exit_counter=0; | |
177 | fsm.template get_state<player_::Open&>().entry_counter=0; | |
178 | fsm.template get_state<player_::Open&>().exit_counter=0; | |
179 | fsm.template get_state<player_::Empty&>().entry_counter=0; | |
180 | fsm.template get_state<player_::Empty&>().exit_counter=0; | |
181 | fsm.template get_state<player_::Playing&>().entry_counter=0; | |
182 | fsm.template get_state<player_::Playing&>().exit_counter=0; | |
183 | fsm.template get_state<player_::Paused&>().entry_counter=0; | |
184 | fsm.template get_state<player_::Paused&>().exit_counter=0; | |
185 | } | |
186 | ||
187 | }; | |
188 | // Pick a back-end | |
189 | typedef msm::back::state_machine<player_> player; | |
190 | ||
191 | // static char const* const state_names[] = { "Stopped", "Open", "Empty", "Playing", "Paused" }; | |
192 | ||
193 | ||
194 | BOOST_AUTO_TEST_CASE( my_test ) | |
195 | { | |
196 | player p; | |
197 | ||
198 | p.start(); | |
199 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 1,"Empty entry not called correctly"); | |
200 | ||
201 | p.process_event(open_close()); | |
202 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 1,"Open should be active"); //Open | |
203 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 1,"Empty exit not called correctly"); | |
204 | BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().entry_counter == 1,"Open entry not called correctly"); | |
205 | ||
206 | p.process_event(open_close()); | |
207 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active"); //Empty | |
208 | BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly"); | |
209 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly"); | |
210 | BOOST_CHECK_MESSAGE(p.can_close_drawer_counter == 1,"guard not called correctly"); | |
211 | ||
212 | p.process_event( | |
213 | cd_detected("louie, louie",DISK_DVD)); | |
214 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 2,"Empty should be active"); //Empty | |
215 | BOOST_CHECK_MESSAGE(p.get_state<player_::Open&>().exit_counter == 1,"Open exit not called correctly"); | |
216 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().entry_counter == 2,"Empty entry not called correctly"); | |
217 | ||
218 | p.process_event( | |
219 | cd_detected("louie, louie",DISK_CD)); | |
220 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped | |
221 | BOOST_CHECK_MESSAGE(p.get_state<player_::Empty&>().exit_counter == 2,"Empty exit not called correctly"); | |
222 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 1,"Stopped entry not called correctly"); | |
223 | ||
224 | p.process_event(play()); | |
225 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing | |
226 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 1,"Stopped exit not called correctly"); | |
227 | BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 1,"Playing entry not called correctly"); | |
228 | BOOST_CHECK_MESSAGE(p.start_playback_counter == 1,"action not called correctly"); | |
229 | ||
230 | p.process_event(pause()); | |
231 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active"); //Paused | |
232 | BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 1,"Playing exit not called correctly"); | |
233 | BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 1,"Paused entry not called correctly"); | |
234 | ||
235 | // go back to Playing | |
236 | p.process_event(end_pause()); | |
237 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 3,"Playing should be active"); //Playing | |
238 | BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 1,"Paused exit not called correctly"); | |
239 | BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().entry_counter == 2,"Playing entry not called correctly"); | |
240 | ||
241 | p.process_event(pause()); | |
242 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 4,"Paused should be active"); //Paused | |
243 | BOOST_CHECK_MESSAGE(p.get_state<player_::Playing&>().exit_counter == 2,"Playing exit not called correctly"); | |
244 | BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().entry_counter == 2,"Paused entry not called correctly"); | |
245 | ||
246 | p.process_event(stop()); | |
247 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped | |
248 | BOOST_CHECK_MESSAGE(p.get_state<player_::Paused&>().exit_counter == 2,"Paused exit not called correctly"); | |
249 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 2,"Stopped entry not called correctly"); | |
250 | ||
251 | p.process_event(stop()); | |
252 | BOOST_CHECK_MESSAGE(p.current_state()[0] == 0,"Stopped should be active"); //Stopped | |
253 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().exit_counter == 2,"Stopped exit not called correctly"); | |
254 | BOOST_CHECK_MESSAGE(p.get_state<player_::Stopped&>().entry_counter == 3,"Stopped entry not called correctly"); | |
255 | } | |
256 | } | |
257 |