X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;ds=sidebyside;f=ETL%2FETL%2F_smach.h;fp=ETL%2FETL%2F_smach.h;h=99a7320472de952d09347ce018ec8806d29635cb;hb=a095981e18cc37a8ecc7cd237cc22b9c10329264;hp=0000000000000000000000000000000000000000;hpb=9459638ad6797b8139f1e9f0715c96076dbf0890;p=synfig.git diff --git a/ETL/ETL/_smach.h b/ETL/ETL/_smach.h new file mode 100644 index 0000000..99a7320 --- /dev/null +++ b/ETL/ETL/_smach.h @@ -0,0 +1,598 @@ +/*! ======================================================================== +** 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) { return egress()?RESULT_ACCEPT: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