1 /*! ========================================================================
2 ** Extended Template and Library
3 ** State Machine Abstraction Class Implementation
6 ** Copyright (c) 2002 Robert B. Quattlebaum Jr.
8 ** This package is free software; you can redistribute it and/or
9 ** modify it under the terms of the GNU General Public License as
10 ** published by the Free Software Foundation; either version 2 of
11 ** the License, or (at your option) any later version.
13 ** This package is distributed in the hope that it will be useful,
14 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 ** General Public License for more details.
18 ** === N O T E S ===========================================================
20 ** ========================================================================= */
22 /* === S T A R T =========================================================== */
24 #ifndef __ETL__SMACH_H_
25 #define __ETL__SMACH_H_
27 /* === H E A D E R S ======================================================= */
32 #include "_mutex_null.h"
35 /* === M A C R O S ========================================================= */
37 #define SMACH_STATE_STACK_SIZE (32)
40 #pragma warning (disable:4786)
41 #pragma warning (disable:4290) // MSVC6 doesn't like function declarations with exception specs
44 //#define ETL_MUTEX_LOCK() _mutex::lock lock(mutex)
45 #define ETL_MUTEX_LOCK()
47 /* === T Y P E D E F S ===================================================== */
49 /* === C L A S S E S & S T R U C T S ======================================= */
53 /*! ========================================================================
55 ** \brief Templatized State Machine
57 ** A more detailed description needs to be written.
59 template <typename CON, typename K=int, typename M=mutex_null>
66 typedef CON context_type;
69 struct egress_exception { };
70 struct pop_exception { };
73 //! Result type for event processing
76 // These values are returned by the event
77 // handlers cast to state pointers.
78 RESULT_ERROR, //!< General error or malfunction
79 RESULT_OK, //!< Event has been processed
80 RESULT_ACCEPT, //!< The event has been explicitly accepted.
81 RESULT_REJECT, //!< The event has been explicitly rejected.
83 RESULT_END //!< Not a valid result
86 //template<typename T> class state;
94 event(const event_key& key):key(key) { }
96 operator event_key()const { return key; }
99 //! Event definition class
105 //friend class state<T>;
108 typedef T state_context_type;
110 //! Event function type
111 typedef event_result (T::*funcptr)(const event&);
115 event_key id; //<! Event ID
116 funcptr handler; //<! Pointer event handler
120 //! Less-than operator for sorting. Based on event_key value.
121 bool operator<(const event_def &rhs)const
122 { return id<rhs.id; }
124 //! Equal-to operator. Based on event_key value.
125 bool operator==(const event_def &rhs)const
126 { return id==rhs.id; }
128 //! Less-than operator for finding.
129 bool operator<(const event_key &rhs)const
132 //! Equal-to operator. Based on event_key value.
133 bool operator==(const event_key &rhs)const
136 //! Trivial Constructor
139 //! Constructor for creating an event_def from the given key and function reference.
140 event_def(event_key a, funcptr b):id(a),handler(b) { }
143 event_def(const event_def &x):id(x.id),handler(x.handler) { }
149 // Our parent is our friend
152 virtual ~state_base() { }
154 virtual void* enter_state(context_type* machine_context)const=0;
156 virtual bool leave_state(void* state_context)const=0;
158 virtual event_result process_event(void* state_context,const event& id)const=0;
160 virtual const char *get_name() const=0;
165 class state : public state_base
167 // Our parent is our friend
171 typedef event_def<T> event_def;
172 typedef T state_context_type;
177 std::vector<event_def> event_list;
179 smach *nested; //! Nested machine
180 event_key low,high; //! Lowest and Highest event values
181 const char *name; //! Name of the state
182 typename event_def::funcptr default_handler; //! Default handler for unknown key
187 state(const char *n, smach* nest=0):
188 nested(nest),name(n),default_handler(NULL)
193 //! Setup a nested state machine
194 /*! A more detailed explanation needs to be written */
195 void set_nested_machine(smach *sm) { nested=sm; }
197 //! Sets the default handler
198 void set_default_handler(const typename event_def::funcptr &x) { default_handler=x; }
200 //! Returns given the name of the state
201 virtual const char *get_name() const { return name; }
203 state_context_type& get_context(smach& machine)
205 state_context_type *context(dynamic_cast<state_context_type*>(machine.state_context));
211 //! Adds an event_def onto the list and then make sure it is sorted correctly.
213 insert(const event_def &x)
215 // If this is our first event_def,
216 // setup the high and low values.
217 if(!event_list.size())
220 // Sort the event_def onto the list
221 event_list.push_back(x);
222 sort(event_list.begin(),event_list.end());
224 // Update the low and high markers
231 typename std::vector<event_def>::iterator find(const event_key &x) { return binary_find(event_list.begin(),event_list.end(),x); }
232 typename std::vector<event_def>::const_iterator find(const event_key &x)const { return binary_find(event_list.begin(),event_list.end(),x); }
236 virtual void* enter_state(context_type* machine_context)const
238 return new state_context_type(machine_context);
241 virtual bool leave_state(void* x)const
243 state_context_type* state_context(reinterpret_cast<state_context_type*>(x));
244 delete state_context;
249 process_event(void* x,const event& id)const
251 state_context_type* state_context(reinterpret_cast<state_context_type*>(x));
253 // Check for nested machine in state
256 const event_result ret(nested->process_event(id));
261 // Quick test to make sure that the
262 // given event is in the state
263 if(id.key<low || high<id.key)
266 // Look for the event
267 typename std::vector<event_def>::const_iterator iter(find(id.key));
269 // If search results were negative, fail.
273 // Execute event function
274 event_result ret((state_context->*(iter->handler))(id));
276 if(ret==RESULT_OK && default_handler)
277 ret=(state_context->*(default_handler))(id);
286 const state_base* curr_state; //!< Current state of the machine
287 smach* child; //!< Child machine
289 public: // this really should be private
290 void* state_context; //!< State Context
293 context_type* machine_context; //!< Machine Context
295 const state_base* default_state;
296 void* default_context;
298 #ifdef ETL_MUTEX_LOCK
303 const state_base* state_stack[SMACH_STATE_STACK_SIZE];
304 void* state_context_stack[SMACH_STATE_STACK_SIZE];
309 //! Gets the name of the currently active state
311 get_state_name()const
313 #ifdef ETL_MUTEX_LOCK
317 return curr_state->get_name();
319 return default_state->get_name();
323 //! Determines if a given event result is an error
324 /*! This function allows us to quickly see
325 if an event_result contained an error */
327 event_error(const event_result &rhs)
328 { return rhs<=RESULT_ERROR; }
331 set_default_state(const state_base *nextstate)
333 #ifdef ETL_MUTEX_LOCK
336 // Keep track of the current state unless
337 // the state switch fails
338 const state_base *prev_state=default_state;
340 // If we are already in a state, leave it and
341 // collapse the state stack
343 default_state->leave_state(default_context);
345 // Set this as our current state
346 default_state=nextstate;
349 // Attempt to enter the state
352 default_context=default_state->enter_state(machine_context);
359 // We failed, so attempt to return to previous state
360 default_state=prev_state;
362 // If we had a previous state, enter it
364 default_context=default_state->enter_state(machine_context);
366 // At this point we are not in the
367 // requested state, so return failure
371 //! Leaves the current state
372 /*! Effectively makes the state_depth() function return zero. */
376 #ifdef ETL_MUTEX_LOCK
380 // Pop all states off the state stack
381 while(states_on_stack) pop_state();
383 // If we are not in a state, then I guess
384 // we were successful.
388 // Grab the return value from the exit function
391 const state_base* old_state=curr_state;
392 void *old_context=state_context;
394 // Clear out the current state and its state_context
395 curr_state=0;state_context=0;
398 return old_state->leave_state(old_context);
403 //! State entry function
404 /*! Attempts to enter the given state,
405 popping off all states on the stack
408 enter(const state_base *nextstate)
410 #ifdef ETL_MUTEX_LOCK
414 // Keep track of the current state unless
415 // the state switch fails
416 const state_base *prev_state=curr_state;
418 // If we are already in a state, leave it and
419 // collapse the state stack
423 // Set this as our current state
424 curr_state=nextstate;
427 // Attempt to enter the state
428 state_context=curr_state->enter_state(machine_context);
432 // We failed, so attempt to return to previous state
433 curr_state=prev_state;
435 // If we had a previous state, enter it
437 state_context=curr_state->enter_state(machine_context);
439 // At this point we are not in the
440 // requested state, so return failure
444 //! Pushes state onto state stack
445 /*! This allows you to enter a state without
446 leaving your current state.
447 \param nextstate Pointer to the state to enter
451 push_state(const state_base *nextstate)
453 #ifdef ETL_MUTEX_LOCK
457 // If there are not enough slots, then throw something.
458 if(states_on_stack==SMACH_STATE_STACK_SIZE)
459 throw(std::overflow_error("smach<>::push_state(): state stack overflow!"));
461 // If there is no current state, nor anything on stack,
462 // just go ahead and enter the given state.
463 if(!curr_state && !states_on_stack)
464 return enter(nextstate);
466 // Push the current state onto the stack
467 state_stack[states_on_stack]=curr_state;
468 state_context_stack[states_on_stack++]=state_context;
470 // Make the next state the current state
471 curr_state=nextstate;
473 // Try to enter the next state
474 state_context=curr_state->enter_state(machine_context);
478 // Unable to push state, return to old one
479 curr_state=state_stack[--states_on_stack];
480 state_context=state_context_stack[states_on_stack];
484 //! Pops state off of state stack
485 /*! Decreases state depth */
489 #ifdef ETL_MUTEX_LOCK
493 // If we aren't in a state, then there is nothing
496 throw(std::underflow_error("smach<>::pop_state(): stack is empty!"));
500 const state_base* old_state=curr_state;
501 void *old_context=state_context;
503 // Pop previous state off of stack
505 curr_state=state_stack[states_on_stack];
506 state_context=state_context_stack[states_on_stack];
508 old_state->leave_state(old_context);
510 else // If there are no states on stack, just egress
514 //! State Machine Constructor
515 /*! A more detailed description needs to be written */
516 smach(context_type* machine_context=0):
520 machine_context(machine_context),
532 default_state->leave_state(default_context);
535 //! Sets up a child state machine
536 /*! A child state machine runs in parallel with
537 its parent, and gets event priority. This
538 mechanism is useful in cases where an inherited
539 object has its own state machine. */
540 void set_child(smach *x)
542 #ifdef ETL_MUTEX_LOCK
548 //! Returns the number states currently active
551 { return curr_state?states_on_stack+1:0; }
554 process_event(const event_key& id) { return process_event(event(id)); }
558 process_event(const event& id)
560 #ifdef ETL_MUTEX_LOCK
564 event_result ret(RESULT_OK);
566 // Check for child machine
569 ret=child->process_event(id);
577 ret=curr_state->process_event(state_context,id);
580 return default_state->process_event(default_context,id);
584 catch(egress_exception) { return egress()?RESULT_ACCEPT:RESULT_ERROR; }
585 catch(pop_exception) { pop_state(); return RESULT_ACCEPT; }
586 catch(const state_base* state) { return enter(state)?RESULT_ACCEPT:RESULT_ERROR; }
589 }; // END of template class smach
593 /* === E X T E R N S ======================================================= */
595 /* === E N D =============================================================== */