]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // Copyright 2005 David Abrahams and Aleksey Gurtovoy. Distributed | |
3 | // under the Boost Software License, Version 1.0. (See accompanying | |
4 | // file LICENSE_1_0.txt or copy at | |
5 | // http://www.boost.org/LICENSE_1_0.txt) | |
6 | // | |
7 | #include "boost/mpl/int.hpp" | |
8 | #include "boost/mpl/fold.hpp" | |
9 | #include "boost/mpl/prior.hpp" | |
10 | #include "boost/mpl/count.hpp" | |
11 | #include "boost/mpl/insert.hpp" | |
12 | #include <boost/mpl/greater.hpp> | |
13 | #include <boost/mpl/for_each.hpp> | |
14 | #include <boost/mpl/filter_view.hpp> | |
15 | #include "boost/mpl/vector/vector20.hpp" | |
16 | #include "boost/assert.hpp" | |
17 | #include <boost/type_traits/is_same.hpp> | |
18 | ||
19 | #include <vector> | |
20 | #include <ctime> | |
21 | #include <iostream> | |
22 | ||
23 | #if defined(BOOST_DINKUMWARE_STDLIB) && BOOST_DINKUMWARE_STDLIB < 310 | |
24 | namespace std { using ::clock_t; } | |
25 | #endif | |
26 | ||
27 | namespace mpl = boost::mpl; | |
28 | using namespace mpl::placeholders; | |
29 | ||
30 | // A metafunction that returns the Event associated with a transition. | |
31 | template <class Transition> | |
32 | struct transition_event | |
33 | { | |
34 | typedef typename Transition::event type; | |
35 | }; | |
36 | ||
37 | // A metafunction computing the maximum of a transition's source and | |
38 | // end states. | |
39 | template <class Transition> | |
40 | struct transition_max_state | |
41 | { | |
42 | typedef typename mpl::int_< | |
43 | (Transition::current_state > Transition::next_state) | |
44 | ? Transition::current_state | |
45 | : Transition::next_state | |
46 | > type; | |
47 | }; | |
48 | ||
49 | template<class Derived> | |
50 | class state_machine; | |
51 | ||
52 | // Generates a singleton runtime lookup table that maps current state | |
53 | // to a function that makes the FSM take its transition on the given | |
54 | // Event type. | |
55 | template <class Fsm, int initial_state, class Stt, class Event> | |
56 | struct dispatch_table | |
57 | { | |
58 | private: | |
59 | // This is a table of these function pointers. | |
60 | typedef int (*cell)(Fsm&, int, Event const&); | |
61 | ||
62 | // Compute the maximum state value in the Fsm so we know how big | |
63 | // to make the table | |
64 | BOOST_STATIC_CONSTANT( | |
65 | int, max_state = ( | |
66 | mpl::fold<Stt | |
67 | , mpl::int_<initial_state> | |
68 | , mpl::if_< | |
69 | mpl::greater<transition_max_state<_2>,_1> | |
70 | , transition_max_state<_2> | |
71 | , _1 | |
72 | > | |
73 | >::type::value | |
74 | ) | |
75 | ); | |
76 | ||
77 | // A function object for use with mpl::for_each that stuffs | |
78 | // transitions into cells. | |
79 | struct init_cell | |
80 | { | |
81 | init_cell(dispatch_table* self_) | |
82 | : self(self_) | |
83 | {} | |
84 | ||
85 | // Cell initializer function object, used with mpl::for_each | |
86 | template <class Transition> | |
87 | void operator()(Transition const&) const | |
88 | { | |
89 | self->entries[Transition::current_state] = &Transition::execute; | |
90 | } | |
91 | ||
92 | dispatch_table* self; | |
93 | }; | |
94 | ||
95 | public: | |
96 | // initialize the dispatch table for a given Event and Fsm | |
97 | dispatch_table() | |
98 | { | |
99 | // Initialize cells for no transition | |
100 | for (int i = 0; i <= max_state; ++i) | |
101 | { | |
102 | // VC7.1 seems to need the two-phase assignment. | |
103 | cell call_no_transition = &state_machine<Fsm>::call_no_transition; | |
104 | entries[i] = call_no_transition; | |
105 | } | |
106 | ||
107 | // Go back and fill in cells for matching transitions. | |
108 | mpl::for_each< | |
109 | mpl::filter_view< | |
110 | Stt | |
111 | , boost::is_same<transition_event<_>, Event> | |
112 | > | |
113 | >(init_cell(this)); | |
114 | } | |
115 | ||
116 | // The singleton instance. | |
117 | static const dispatch_table instance; | |
118 | ||
119 | public: // data members | |
120 | cell entries[max_state + 1]; | |
121 | }; | |
122 | ||
123 | // This declares the statically-initialized dispatch_table instance. | |
124 | template <class Fsm, int initial_state, class Stt, class Event> | |
125 | const dispatch_table<Fsm, initial_state, Stt, Event> | |
126 | dispatch_table<Fsm, initial_state, Stt, Event>::instance; | |
127 | ||
128 | // CRTP base class for state machines. Pass the actual FSM class as | |
129 | // the Derived parameter. | |
130 | template<class Derived> | |
131 | class state_machine | |
132 | { | |
133 | public: // Member functions | |
134 | ||
135 | // Main function used by clients of the derived FSM to make | |
136 | // transitions. | |
137 | template<class Event> | |
138 | int process_event(Event const& evt) | |
139 | { | |
140 | typedef typename Derived::transition_table stt; | |
141 | typedef dispatch_table<Derived, Derived::initial_state,stt,Event> table; | |
142 | ||
143 | // Call the action | |
144 | return this->m_state | |
145 | = table::instance.entries[this->m_state]( | |
146 | *static_cast<Derived*>(this), this->m_state, evt); | |
147 | } | |
148 | ||
149 | // Getter that returns the current state of the FSM | |
150 | int current_state() const | |
151 | { | |
152 | return this->m_state; | |
153 | } | |
154 | ||
155 | private: | |
156 | template <class Fsm, int initial_state, class Stt, class Event> | |
157 | friend class dispatch_table; | |
158 | ||
159 | template <class Event> | |
160 | static int call_no_transition(Derived& fsm, int state, Event const& e) | |
161 | { | |
162 | return fsm.no_transition(state, e); | |
163 | } | |
164 | ||
165 | // Default no-transition handler. Can be replaced in the Derived | |
166 | // FSM class. | |
167 | template <class Event> | |
168 | int no_transition(int state, Event const& e) | |
169 | { | |
170 | BOOST_ASSERT(false); | |
171 | return state; | |
172 | } | |
173 | ||
174 | protected: // interface for the derived class | |
175 | ||
176 | template<class State> | |
177 | state_machine(State state) // Construct with an initial state | |
178 | : m_state(state) | |
179 | { | |
180 | } | |
181 | ||
182 | state_machine() | |
183 | : m_state(Derived::initial_state) // Construct with the default initial_state | |
184 | { | |
185 | } | |
186 | ||
187 | // Template used to form rows in the transition table | |
188 | template< | |
189 | int CurrentState | |
190 | , class Event | |
191 | , int NextState | |
192 | , void (Derived::*action)(Event const&) | |
193 | > | |
194 | struct row | |
195 | { | |
196 | BOOST_STATIC_CONSTANT(int, current_state = CurrentState); | |
197 | BOOST_STATIC_CONSTANT(int, next_state = NextState); | |
198 | typedef Event event; | |
199 | ||
200 | // Take the transition action and return the next state. | |
201 | static int execute(Derived& fsm, int state, Event const& evt) | |
202 | { | |
203 | BOOST_ASSERT(state == current_state); | |
204 | (fsm.*action)(evt); | |
205 | return next_state; | |
206 | } | |
207 | }; | |
208 | ||
209 | private: // data members | |
210 | int m_state; | |
211 | }; | |
212 | ||
213 | namespace // Concrete FSM implementation | |
214 | { | |
215 | // events | |
216 | struct play {}; | |
217 | struct stop {}; | |
218 | struct pause {}; | |
219 | struct open_close {}; | |
220 | ||
221 | // A "complicated" event type that carries some data. | |
222 | struct cd_detected | |
223 | { | |
224 | cd_detected(std::string name, std::vector<std::clock_t> durations) | |
225 | : name(name) | |
226 | , track_durations(durations) | |
227 | {} | |
228 | ||
229 | std::string name; | |
230 | std::vector<std::clock_t> track_durations; | |
231 | }; | |
232 | ||
233 | // Concrete FSM implementation | |
234 | class player : public state_machine<player> | |
235 | { | |
236 | // The list of FSM states | |
237 | enum states { | |
238 | Empty, Open, Stopped, Playing, Paused | |
239 | , initial_state = Empty | |
240 | }; | |
241 | ||
242 | #ifdef __MWERKS__ | |
243 | public: // Codewarrior bug workaround. Tested at 0x3202 | |
244 | #endif | |
245 | // transition actions | |
246 | void start_playback(play const&) { std::cout << "player::start_playback\n"; } | |
247 | void open_drawer(open_close const&) { std::cout << "player::open_drawer\n"; } | |
248 | void close_drawer(open_close const&) { std::cout << "player::close_drawer\n"; } | |
249 | void store_cd_info(cd_detected const&) { std::cout << "player::store_cd_info\n"; } | |
250 | void stop_playback(stop const&) { std::cout << "player::stop_playback\n"; } | |
251 | void pause_playback(pause const&) { std::cout << "player::pause_playback\n"; } | |
252 | void resume_playback(play const&) { std::cout << "player::resume_playback\n"; } | |
253 | void stop_and_open(open_close const&) { std::cout << "player::stop_and_open\n"; } | |
254 | #ifdef __MWERKS__ | |
255 | private: | |
256 | #endif | |
257 | friend class state_machine<player>; | |
258 | typedef player p; // makes transition table cleaner | |
259 | ||
260 | // Transition table | |
261 | struct transition_table : mpl::vector11< | |
262 | // Start Event Next Action | |
263 | // +---------+-------------+---------+---------------------+ | |
264 | row < Stopped , play , Playing , &p::start_playback >, | |
265 | row < Stopped , open_close , Open , &p::open_drawer >, | |
266 | // +---------+-------------+---------+---------------------+ | |
267 | row < Open , open_close , Empty , &p::close_drawer >, | |
268 | // +---------+-------------+---------+---------------------+ | |
269 | row < Empty , open_close , Open , &p::open_drawer >, | |
270 | row < Empty , cd_detected , Stopped , &p::store_cd_info >, | |
271 | // +---------+-------------+---------+---------------------+ | |
272 | row < Playing , stop , Stopped , &p::stop_playback >, | |
273 | row < Playing , pause , Paused , &p::pause_playback >, | |
274 | row < Playing , open_close , Open , &p::stop_and_open >, | |
275 | // +---------+-------------+---------+---------------------+ | |
276 | row < Paused , play , Playing , &p::resume_playback >, | |
277 | row < Paused , stop , Stopped , &p::stop_playback >, | |
278 | row < Paused , open_close , Open , &p::stop_and_open > | |
279 | // +---------+-------------+---------+---------------------+ | |
280 | > {}; | |
281 | ||
282 | // Replaces the default no-transition response. | |
283 | template <class Event> | |
284 | int no_transition(int state, Event const& e) | |
285 | { | |
286 | std::cout << "no transition from state " << state | |
287 | << " on event " << typeid(e).name() << std::endl; | |
288 | return state; | |
289 | } | |
290 | }; | |
291 | ||
292 | // | |
293 | // Testing utilities. | |
294 | // | |
295 | static char const* const state_names[] = { "Empty", "Open", "Stopped", "Playing", "Paused" }; | |
296 | ||
297 | void pstate(player const& p) | |
298 | { | |
299 | std::cout << " -> " << state_names[p.current_state()] << std::endl; | |
300 | } | |
301 | ||
302 | void test() | |
303 | { | |
304 | player p; | |
305 | p.process_event(open_close()); pstate(p); | |
306 | p.process_event(open_close()); pstate(p); | |
307 | p.process_event( | |
308 | cd_detected( | |
309 | "louie, louie" | |
310 | , std::vector<std::clock_t>( /* track lengths */ ) | |
311 | ) | |
312 | ); | |
313 | pstate(p); | |
314 | ||
315 | p.process_event(play()); pstate(p); | |
316 | p.process_event(pause()); pstate(p); | |
317 | p.process_event(play()); pstate(p); | |
318 | p.process_event(stop()); pstate(p); | |
319 | } | |
320 | } | |
321 | ||
322 | int main() | |
323 | { | |
324 | test(); | |
325 | return 0; | |
326 | } |