Remove ancient trunk folder from svn repository
[synfig.git] / ETL / ETL / _smach.h
diff --git a/ETL/ETL/_smach.h b/ETL/ETL/_smach.h
new file mode 100644 (file)
index 0000000..99a7320
--- /dev/null
@@ -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 <vector>
+#include <algorithm>
+#include <stdexcept>
+#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 <typename CON, typename K=int, typename M=mutex_null>
+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<typename T> 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<typename T>
+       class event_def_internal
+       {
+               // List our friends
+               friend class smach;
+               //friend class state<T>;
+
+       public:
+               typedef T state_context_type;
+
+               //! Event function type
+               typedef event_result (T::*funcptr)(const event&);
+
+       //private:
+
+               event_key id;           //<! Event ID
+               funcptr handler;        //<! Pointer event handler
+
+       public:
+
+               //! Less-than operator for sorting. Based on event_key value.
+               bool operator<(const event_def_internal &rhs)const
+                       { return id<rhs.id; }
+
+               //! Equal-to operator. Based on event_key value.
+               bool operator==(const event_def_internal &rhs)const
+                       { return id==rhs.id; }
+
+               //! Less-than operator for finding.
+               bool operator<(const event_key &rhs)const
+                       { return id<rhs; }
+
+               //! Equal-to operator. Based on event_key value.
+               bool operator==(const event_key &rhs)const
+                       { return id==rhs; }
+
+               //! Trivial Constructor
+               event_def_internal() { }
+
+               //! Constructor for creating an event_def_internal from the given key and function reference.
+               event_def_internal(event_key a, funcptr b):id(a),handler(b) { }
+
+               //! Copy constructor
+               event_def_internal(const event_def_internal &x):id(x.id),handler(x.handler) { }
+
+       };
+
+       class state_base
+       {
+               // Our parent is our friend
+               friend class smach;
+       public:
+               virtual ~state_base() { }
+
+               virtual void* enter_state(context_type* machine_context)const=0;
+
+               virtual bool leave_state(void* state_context)const=0;
+
+               virtual event_result process_event(void* state_context,const event& id)const=0;
+
+               virtual const char *get_name() const=0;
+       };
+
+       //! State class
+       template<typename T>
+       class state : public state_base
+       {
+               // Our parent is our friend
+               friend class smach;
+
+       public:
+               typedef event_def_internal<T> event_def;
+               typedef T state_context_type;
+
+
+       private:
+
+               std::vector<event_def> 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<state_context_type*>(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<low)
+                               low=x.id;
+                       if(high<x.id)
+                               high=x.id;
+               }
+
+               typename std::vector<event_def>::iterator find(const event_key &x) { return binary_find(event_list.begin(),event_list.end(),x); }
+               typename std::vector<event_def>::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<state_context_type*>(x));
+                       delete state_context;
+                       return true;
+               }
+
+               virtual event_result
+               process_event(void* x,const event& id)const
+               {
+                       state_context_type* state_context(reinterpret_cast<state_context_type*>(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<low || high<id.key)
+                               return RESULT_OK;
+
+                       // Look for the event
+                       typename std::vector<event_def>::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