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
478b0d
#include <cassert></cassert>
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
	};
478b0d
	
478b0d
	class guard {
478b0d
	private:
478b0d
		bool *flag;
478b0d
	public:
478b0d
		explicit guard(bool &flag): flag() {
478b0d
			assert(!flag);
478b0d
			if (!flag) { flag = true; this->flag = &flag; }
478b0d
		}
478b0d
		~guard()
478b0d
			{ if (flag) *flag = false;}
478b0d
		operator bool() const
478b0d
			{ return flag != nullptr; }
478b0d
	};
478b0d
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;
478b0d
	
478b0d
	bool changing_state;
478b0d
	bool changing_default_state;
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
	{
478b0d
		guard lock(changing_default_state);
478b0d
		if (!lock) return false;
478b0d
		
darco b3016b
		// Keep track of the current state unless
darco b3016b
		// the state switch fails
478b0d
		const state_base *prev_state = default_state;
478b0d
		void *prev_context = default_context;
478b0d
478b0d
		default_state = 0;
478b0d
		default_context = 0;
478b0d
		
478b0d
		// If we are already in a state, leave it
478b0d
		if (prev_state && prev_context)
478b0d
			prev_state->leave_state(prev_context);
478b0d
		
darco b3016b
		// Attempt to enter the state
478b0d
		default_state=nextstate;
478b0d
		if (default_state) {
478b0d
			default_context = default_state->enter_state(machine_context);
478b0d
			if (default_context)
darco b3016b
				return true;
darco b3016b
		}
darco b3016b
darco b3016b
		// We failed, so attempt to return to previous state
478b0d
		default_state = prev_state;
478b0d
		if (default_state) {
478b0d
			default_context = default_state->enter_state(machine_context);
478b0d
			if (!default_context)
478b0d
				default_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
	}
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
	{
478b0d
		// Try to pop all states off the state stack, before lock
478b0d
		while(states_on_stack)
478b0d
			if (!pop_state()) return false;
darco b3016b
478b0d
		guard lock(changing_state);
478b0d
		if (!lock) return false;
478b0d
		
darco b3016b
		// If we are not in a state, then I guess
dooglus f9cb27
		// we were successful.
478b0d
		if (!curr_state)
darco b3016b
			return true;
darco b3016b
478b0d
		const state_base* old_state = curr_state;
478b0d
		void *old_context = state_context;
darco b3016b
dooglus bbf05c
		// Clear out the current state and its state_context
478b0d
		curr_state = 0;
478b0d
		state_context = 0;
darco b3016b
darco b3016b
		// Leave the state
ee269a
		if (old_state && old_context)
478b0d
			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
	{
478b0d
		guard lock(changing_state);
478b0d
		if (!lock) return false;
478b0d
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
478b0d
		changing_state = false;
478b0d
		egress();
478b0d
		assert(!state_context);
478b0d
		state_context = 0;
478b0d
		changing_state = true;
dooglus cee594
darco b3016b
		// Attempt to enter the state
478b0d
		curr_state = nextstate;
478b0d
		if (curr_state) {
478b0d
			state_context = curr_state->enter_state(machine_context);
478b0d
			if (state_context)
478b0d
				return true;
478b0d
		}
478b0d
		
darco b3016b
		// If we had a previous state, enter it
478b0d
		curr_state = prev_state;
478b0d
		if (curr_state) {
478b0d
			state_context = curr_state->enter_state(machine_context);
478b0d
			if (!state_context)
478b0d
				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
	{
478b0d
		// If there is no current state,
darco b3016b
		// just go ahead and enter the given state.
478b0d
		// before locking
478b0d
		if (!curr_state)
darco b3016b
			return enter(nextstate);
478b0d
		
478b0d
		guard lock(changing_state);
478b0d
		if (!lock) return false;
darco b3016b
478b0d
		// If there are not enough slots, then throw something.
478b0d
		if (states_on_stack==SMACH_STATE_STACK_SIZE)
478b0d
			throw(std::overflow_error("smach<>::push_state(): state stack overflow!"));
darco b3016b
478b0d
		// Push the current state onto the stack
478b0d
		state_stack[states_on_stack] = curr_state;
478b0d
		state_context_stack[states_on_stack++] = state_context;
darco b3016b
darco b3016b
		// Try to enter the next state
478b0d
		state_context = 0;
478b0d
		curr_state = nextstate;
478b0d
		if (curr_state) {
478b0d
			state_context = curr_state->enter_state(machine_context);
478b0d
			if (state_context)
478b0d
				return true;
478b0d
		}
darco b3016b
darco b3016b
		// Unable to push state, return to old one
478b0d
		curr_state = state_stack[--states_on_stack];
478b0d
		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 */
478b0d
	bool
darco b3016b
	pop_state()
darco b3016b
	{
478b0d
		{ // scope for locking
478b0d
			guard lock(changing_state);
478b0d
			if (!lock) return false;
478b0d
			
478b0d
			// If we aren't in a state, then there is nothing
478b0d
			// to do.
478b0d
			if (!curr_state && !states_on_stack)
478b0d
				throw(std::underflow_error("smach<>::pop_state(): stack is empty!"));
478b0d
478b0d
			if (states_on_stack) {
478b0d
				const state_base* old_state = curr_state;
478b0d
				void *old_context = state_context;
478b0d
478b0d
				// Pop previous state off of stack
478b0d
				--states_on_stack;
478b0d
				curr_state = state_stack[states_on_stack];
478b0d
				state_context = state_context_stack[states_on_stack];
478b0d
478b0d
				if (old_state && old_context)
478b0d
					old_state->leave_state(old_context);
478b0d
				return true;
478b0d
			}
darco b3016b
		}
478b0d
		
478b0d
		// call egress with changing_state unlocked
478b0d
		return 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),
478b0d
		states_on_stack(0),
478b0d
		changing_state(false),
478b0d
		changing_default_state(false)
darco b3016b
	{ }
dooglus cee594
darco b3016b
	//! The destructor
darco b3016b
	~smach()
darco b3016b
	{
478b0d
		assert(!changing_state);
478b0d
		changing_state = false;
darco b3016b
		egress();
darco b3016b
478b0d
		// reset default state
478b0d
		assert(!changing_default_state);
478b0d
		changing_default_state = false;
478b0d
		const state_base *prev_state = default_state;
478b0d
		void* prev_context = default_context;
478b0d
		default_state=0;
478b0d
		default_context=0;
478b0d
		if (prev_state && prev_context)
478b0d
			prev_state->leave_state(prev_context);
478b0d
478b0d
		assert(!default_state && !default_context);
478b0d
		assert(!curr_state && !state_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
		}
Rodolfo Ribeiro Gomes f89643
		catch(const 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
		}
Rodolfo Ribeiro Gomes f89643
		catch(const 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