/*! ======================================================================== ** Extended Template and Library ** State Machine Abstraction Class Implementation ** $Id$ ** ** Copyright (c) 2002 Robert B. Quattlebaum Jr. ** Copyright (c) 2008 Chris Moore ** ** This package is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public License as ** published by the Free Software Foundation; either version 2 of ** the License, or (at your option) any later version. ** ** This package is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** ** === N O T E S =========================================================== ** ** ========================================================================= */ /* === S T A R T =========================================================== */ #ifndef __ETL__SMACH_H_ #define __ETL__SMACH_H_ /* === H E A D E R S ======================================================= */ #include #include #include #include "_mutex_null.h" #include "_misc.h" /* === M A C R O S ========================================================= */ #define SMACH_STATE_STACK_SIZE (32) #ifdef _MSC_VER #pragma warning (disable:4786) #pragma warning (disable:4290) // MSVC6 doesn't like function declarations with exception specs #endif //#define ETL_MUTEX_LOCK() _mutex::lock lock(mutex) #define ETL_MUTEX_LOCK() /* === T Y P E D E F S ===================================================== */ /* === C L A S S E S & S T R U C T S ======================================= */ _ETL_BEGIN_NAMESPACE /*! ======================================================================== ** \class smach ** \brief Templatized State Machine ** ** A more detailed description needs to be written. */ template class smach { public: typedef K event_key; typedef M _mutex; typedef CON context_type; struct egress_exception { }; struct pop_exception { }; //! Result type for event processing enum event_result { // These values are returned by the event // handlers cast to state pointers. RESULT_ERROR, //!< General error or malfunction RESULT_OK, //!< Event has been processed RESULT_ACCEPT, //!< The event has been explicitly accepted. RESULT_REJECT, //!< The event has been explicitly rejected. RESULT_END //!< Not a valid result }; //template class state; //! Event base class struct event { event_key key; event() { } event(const event_key& key):key(key) { } operator event_key()const { return key; } }; //! Event definition class template class event_def_internal { // List our friends friend class smach; //friend class state; public: typedef T state_context_type; //! Event function type typedef event_result (T::*funcptr)(const event&); //private: event_key id; // class state : public state_base { // Our parent is our friend friend class smach; public: typedef event_def_internal event_def; typedef T state_context_type; private: std::vector event_list; smach *nested; //! Nested machine event_key low,high; //! Lowest and Highest event values const char *name; //! Name of the state typename event_def::funcptr default_handler; //! Default handler for unknown key public: //! Constructor state(const char *n, smach* nest=0): nested(nest),name(n),default_handler(NULL) { } virtual ~state() { } //! Setup a nested state machine /*! A more detailed explanation needs to be written */ void set_nested_machine(smach *sm) { nested=sm; } //! Sets the default handler void set_default_handler(const typename event_def::funcptr &x) { default_handler=x; } //! Returns given the name of the state virtual const char *get_name() const { return name; } state_context_type& get_context(smach& machine) { state_context_type *context(dynamic_cast(machine.state_context)); if(context) return context; } //! Adds an event_def onto the list and then make sure it is sorted correctly. void insert(const event_def &x) { // If this is our first event_def, // setup the high and low values. if(!event_list.size()) low=high=x.id; // Sort the event_def onto the list event_list.push_back(x); sort(event_list.begin(),event_list.end()); // Update the low and high markers if(x.id::iterator find(const event_key &x) { return binary_find(event_list.begin(),event_list.end(),x); } typename std::vector::const_iterator find(const event_key &x)const { return binary_find(event_list.begin(),event_list.end(),x); } protected: virtual void* enter_state(context_type* machine_context)const { return new state_context_type(machine_context); } virtual bool leave_state(void* x)const { state_context_type* state_context(reinterpret_cast(x)); delete state_context; return true; } virtual event_result process_event(void* x,const event& id)const { state_context_type* state_context(reinterpret_cast(x)); // Check for nested machine in state if(nested) { const event_result ret(nested->process_event(id)); if(ret!=RESULT_OK) return ret; } // Quick test to make sure that the // given event is in the state if(id.key::const_iterator iter(find(id.key)); // If search results were negative, fail. if(iter->id!=id.key) return RESULT_OK; // Execute event function event_result ret((state_context->*(iter->handler))(id)); if(ret==RESULT_OK && default_handler) ret=(state_context->*(default_handler))(id); return ret; } }; private: // Machine data const state_base* curr_state; //!< Current state of the machine smach* child; //!< Child machine public: // this really should be private void* state_context; //!< State Context private: context_type* machine_context; //!< Machine Context const state_base* default_state; void* default_context; #ifdef ETL_MUTEX_LOCK _mutex mutex; #endif //! State stack data const state_base* state_stack[SMACH_STATE_STACK_SIZE]; void* state_context_stack[SMACH_STATE_STACK_SIZE]; int states_on_stack; public: //! Gets the name of the currently active state const char * get_state_name()const { #ifdef ETL_MUTEX_LOCK ETL_MUTEX_LOCK(); #endif if(curr_state) return curr_state->get_name(); if(default_state) return default_state->get_name(); return 0; } //! Determines if a given event result is an error /*! This function allows us to quickly see if an event_result contained an error */ static bool event_error(const event_result &rhs) { return rhs<=RESULT_ERROR; } bool set_default_state(const state_base *nextstate) { #ifdef ETL_MUTEX_LOCK ETL_MUTEX_LOCK(); #endif // Keep track of the current state unless // the state switch fails const state_base *prev_state=default_state; // If we are already in a state, leave it and // collapse the state stack if(default_state) default_state->leave_state(default_context); // Set this as our current state default_state=nextstate; default_context=0; // Attempt to enter the state if(default_state) { default_context=default_state->enter_state(machine_context); if(default_context) return true; } else return true; // We failed, so attempt to return to previous state default_state=prev_state; // If we had a previous state, enter it if(default_state) default_context=default_state->enter_state(machine_context); // At this point we are not in the // requested state, so return failure return false; } //! Leaves the current state /*! Effectively makes the state_depth() function return zero. */ bool egress() { #ifdef ETL_MUTEX_LOCK ETL_MUTEX_LOCK(); #endif // Pop all states off the state stack while(states_on_stack) pop_state(); // If we are not in a state, then I guess // we were successful. if(!curr_state) return true; // Grab the return value from the exit function bool ret=true; const state_base* old_state=curr_state; void *old_context=state_context; // Clear out the current state and its state_context curr_state=0;state_context=0; // Leave the state return old_state->leave_state(old_context); return ret; } //! State entry function /*! Attempts to enter the given state, popping off all states on the stack in the process. */ bool enter(const state_base *nextstate) { #ifdef ETL_MUTEX_LOCK ETL_MUTEX_LOCK(); #endif // Keep track of the current state unless // the state switch fails const state_base *prev_state=curr_state; // If we are already in a state, leave it and // collapse the state stack if(curr_state) egress(); // Set this as our current state curr_state=nextstate; state_context=0; // Attempt to enter the state state_context=curr_state->enter_state(machine_context); if(state_context) return true; // We failed, so attempt to return to previous state curr_state=prev_state; // If we had a previous state, enter it if(curr_state) state_context=curr_state->enter_state(machine_context); // At this point we are not in the // requested state, so return failure return false; } //! Pushes state onto state stack /*! This allows you to enter a state without leaving your current state. \param nextstate Pointer to the state to enter \sa pop_state() */ bool push_state(const state_base *nextstate) { #ifdef ETL_MUTEX_LOCK ETL_MUTEX_LOCK(); #endif // If there are not enough slots, then throw something. if(states_on_stack==SMACH_STATE_STACK_SIZE) throw(std::overflow_error("smach<>::push_state(): state stack overflow!")); // If there is no current state, nor anything on stack, // just go ahead and enter the given state. if(!curr_state && !states_on_stack) return enter(nextstate); // Push the current state onto the stack state_stack[states_on_stack]=curr_state; state_context_stack[states_on_stack++]=state_context; // Make the next state the current state curr_state=nextstate; // Try to enter the next state state_context=curr_state->enter_state(machine_context); if(state_context) return true; // Unable to push state, return to old one curr_state=state_stack[--states_on_stack]; state_context=state_context_stack[states_on_stack]; return false; } //! Pops state off of state stack /*! Decreases state depth */ void pop_state() { #ifdef ETL_MUTEX_LOCK ETL_MUTEX_LOCK(); #endif // If we aren't in a state, then there is nothing // to do. if(!curr_state) throw(std::underflow_error("smach<>::pop_state(): stack is empty!")); if(states_on_stack) { const state_base* old_state=curr_state; void *old_context=state_context; // Pop previous state off of stack --states_on_stack; curr_state=state_stack[states_on_stack]; state_context=state_context_stack[states_on_stack]; old_state->leave_state(old_context); } else // If there are no states on stack, just egress egress(); } //! State Machine Constructor /*! A more detailed description needs to be written */ smach(context_type* machine_context=0): curr_state(0), child(0), state_context(0), machine_context(machine_context), default_state(0), default_context(0), states_on_stack(0) { } //! The destructor ~smach() { egress(); if(default_state) default_state->leave_state(default_context); } //! Sets up a child state machine /*! A child state machine runs in parallel with its parent, and gets event priority. This mechanism is useful in cases where an inherited object has its own state machine. */ void set_child(smach *x) { #ifdef ETL_MUTEX_LOCK ETL_MUTEX_LOCK(); #endif child=x; } //! Returns the number states currently active int state_depth() { return curr_state?states_on_stack+1:0; } event_result process_event(const event_key& id) { return process_event(event(id)); } //! Process an event event_result process_event(const event& id) { #ifdef ETL_MUTEX_LOCK ETL_MUTEX_LOCK(); #endif event_result ret(RESULT_OK); // Check for child machine if(child) { ret=child->process_event(id); if(ret!=RESULT_OK) return ret; } try { if(curr_state) ret=curr_state->process_event(state_context,id); if(ret==RESULT_OK) return default_state->process_event(default_context,id); return ret; } catch(egress_exception) { if (egress()) { RESULT_ACCEPT; } else { RESULT_ERROR; } } catch(pop_exception) { pop_state(); return RESULT_ACCEPT; } catch(const state_base* state) { return enter(state)?RESULT_ACCEPT:RESULT_ERROR; } } }; // END of template class smach _ETL_END_NAMESPACE /* === E X T E R N S ======================================================= */ /* === E N D =============================================================== */ #endif