Blame ETL/ETL/_smach.h

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