1 /*! ========================================================================
2 ** Extended Template and Library
3 ** State Machine Abstraction Class Implementation
4 ** $Id$
5 **
6 ** Copyright (c) 2002 Robert B. Quattlebaum Jr.
7 ** Copyright (c) 2008 Chris Moore
8 **
9 ** This package is free software; you can redistribute it and/or
10 ** modify it under the terms of the GNU General Public License as
11 ** published by the Free Software Foundation; either version 2 of
12 ** the License, or (at your option) any later version.
13 **
14 ** This package is distributed in the hope that it will be useful,
15 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 ** General Public License for more details.
18 **
19 ** === N O T E S ===========================================================
20 **
21 ** ========================================================================= */
22 
23 /* === S T A R T =========================================================== */
24 
25 #ifndef __ETL__SMACH_H_
26 #define __ETL__SMACH_H_
27 
28 /* === H E A D E R S ======================================================= */
29 
30 #include <vector>
31 #include <algorithm>
32 #include <stdexcept>
33 #include "_mutex_null.h"
34 #include "_misc.h"
35 
36 /* === M A C R O S ========================================================= */
37 
38 #define SMACH_STATE_STACK_SIZE		(32)
39 
40 #ifdef _MSC_VER
41 #pragma warning (disable:4786)
42 #pragma warning (disable:4290) // MSVC6 doesn't like function declarations with exception specs
43 #endif
44 
45 //#define ETL_MUTEX_LOCK() 		_mutex::lock lock(mutex)
46 #define ETL_MUTEX_LOCK()
47 
48 /* === T Y P E D E F S ===================================================== */
49 
50 /* === C L A S S E S & S T R U C T S ======================================= */
51 
52 _ETL_BEGIN_NAMESPACE
53 
54 /*! ========================================================================
55 ** \class	smach
56 ** \brief	Templatized State Machine
57 **
58 ** A more detailed description needs to be written.
59 */
60 template <typename CON, typename K=int, typename M=mutex_null>
61 class smach
62 {
63 public:
64 
65 	typedef K event_key;
66 	typedef M _mutex;
67 	typedef CON context_type;
68 
69 
70 	struct egress_exception { };
71 	struct pop_exception { };
72 
73 
74 	//! Result type for event processing
75 	enum event_result
76 	{
77 		// These values are returned by the event
78 		// handlers cast to state pointers.
79 		RESULT_ERROR,		//!< General error or malfunction
80 		RESULT_OK,			//!< Event has been processed
81 		RESULT_ACCEPT,		//!< The event has been explicitly accepted.
82 		RESULT_REJECT,		//!< The event has been explicitly rejected.
83 
84 		RESULT_END			//!< Not a valid result
85 	};
86 
87 	//template<typename T> class state;
88 
89 	//! Event base class
90 	struct event
91 	{
92 		event_key key;
93 
eventevent94 		event() { }
eventevent95 		event(const event_key& key):key(key) { }
96 
event_keyevent97 		operator event_key()const { return key; }
98 	};
99 
100 	//! Event definition class
101 	template<typename T>
102 	class event_def_internal
103 	{
104 		// List our friends
105 		friend class smach;
106 		//friend class state<T>;
107 
108 	public:
109 		typedef T state_context_type;
110 
111 		//! Event function type
112 		typedef event_result (T::*funcptr)(const event&);
113 
114 	//private:
115 
116 		event_key id;		//<! Event ID
117 		funcptr handler;	//<! Pointer event handler
118 
119 	public:
120 
121 		//! Less-than operator for sorting. Based on event_key value.
122 		bool operator<(const event_def_internal &rhs)const
123 			{ return id<rhs.id; }
124 
125 		//! Equal-to operator. Based on event_key value.
126 		bool operator==(const event_def_internal &rhs)const
127 			{ return id==rhs.id; }
128 
129 		//! Less-than operator for finding.
130 		bool operator<(const event_key &rhs)const
131 			{ return id<rhs; }
132 
133 		//! Equal-to operator. Based on event_key value.
134 		bool operator==(const event_key &rhs)const
135 			{ return id==rhs; }
136 
137 		//! Trivial Constructor
event_def_internal()138 		event_def_internal() { }
139 
140 		//! Constructor for creating an event_def_internal from the given key and function reference.
event_def_internal(event_key a,funcptr b)141 		event_def_internal(event_key a, funcptr b):id(a),handler(b) { }
142 
143 		//! Copy constructor
event_def_internal(const event_def_internal & x)144 		event_def_internal(const event_def_internal &x):id(x.id),handler(x.handler) { }
145 
146 	};
147 
148 	class state_base
149 	{
150 		// Our parent is our friend
151 		friend class smach;
152 	public:
~state_base()153 		virtual ~state_base() { }
154 
155 		virtual void* enter_state(context_type* machine_context)const=0;
156 
157 		virtual bool leave_state(void* state_context)const=0;
158 
159 		virtual event_result process_event(void* state_context,const event& id)const=0;
160 
161 		virtual const char *get_name() const=0;
162 	};
163 
164 	//! State class
165 	template<typename T>
166 	class state : public state_base
167 	{
168 		// Our parent is our friend
169 		friend class smach;
170 
171 	public:
172 		typedef event_def_internal<T> event_def;
173 		typedef T state_context_type;
174 
175 
176 	private:
177 
178 		std::vector<event_def> event_list;
179 
180 		smach *nested;		//! Nested machine
181 		event_key low,high;	//! Lowest and Highest event values
182 		const char *name;	//! Name of the state
183 		typename event_def::funcptr default_handler;	//! Default handler for unknown key
184 
185 	public:
186 
187 		//! Constructor
188 		state(const char *n, smach* nest=0):
nested(nest)189 			nested(nest),name(n),default_handler(NULL)
190 		{ }
191 
~state()192 		virtual ~state() { }
193 
194 		//! Setup a nested state machine
195 		/*! A more detailed explanation needs to be written */
set_nested_machine(smach * sm)196 		void set_nested_machine(smach *sm) { nested=sm; }
197 
198 		//! Sets the default handler
set_default_handler(const typename event_def::funcptr & x)199 		void set_default_handler(const typename event_def::funcptr &x) { default_handler=x; }
200 
201 		//! Returns given the name of the state
get_name()202 		virtual const char *get_name() const { return name; }
203 
204 		//! Adds an event_def onto the list and then make sure it is sorted correctly.
205 		void
insert(const event_def & x)206 		insert(const event_def &x)
207 		{
208 			// If this is our first event_def,
209 			// setup the high and low values.
210 			if(!event_list.size())
211 				low=high=x.id;
212 
213 			// Sort the event_def onto the list
214 			event_list.push_back(x);
215 			sort(event_list.begin(),event_list.end());
216 
217 			// Update the low and high markers
218 			if(x.id<low)
219 				low=x.id;
220 			if(high<x.id)
221 				high=x.id;
222 		}
223 
find(const event_key & x)224 		typename std::vector<event_def>::iterator find(const event_key &x) { return binary_find(event_list.begin(),event_list.end(),x); }
find(const event_key & x)225 		typename std::vector<event_def>::const_iterator find(const event_key &x)const { return binary_find(event_list.begin(),event_list.end(),x); }
226 
227 	protected:
228 
enter_state(context_type * machine_context)229 		virtual void* enter_state(context_type* machine_context)const
230 		{
231 			return new state_context_type(machine_context);
232 		}
233 
leave_state(void * x)234 		virtual bool leave_state(void* x)const
235 		{
236 			state_context_type* state_context(reinterpret_cast<state_context_type*>(x));
237 			delete state_context;
238 			return true;
239 		}
240 
241 		virtual event_result
process_event(void * x,const event & id)242 		process_event(void* x,const event& id)const
243 		{
244 			state_context_type* state_context(reinterpret_cast<state_context_type*>(x));
245 
246 			// Check for nested machine in state
247 			if(nested)
248 			{
249 				const event_result ret(nested->process_event(id));
250 				if(ret!=RESULT_OK)
251 					return ret;
252 			}
253 
254 			// Quick test to make sure that the
255 			// given event is in the state
256 			if(id.key<low || high<id.key)
257 				return RESULT_OK;
258 
259 			// Look for the event
260 			typename std::vector<event_def>::const_iterator iter(find(id.key));
261 
262 			// If search results were negative, fail.
263 			if(iter->id!=id.key)
264 				return RESULT_OK;
265 
266 			// Execute event function
267 			event_result ret((state_context->*(iter->handler))(id));
268 
269 			if(ret==RESULT_OK && default_handler)
270 				ret=(state_context->*(default_handler))(id);
271 
272 			return ret;
273 		}
274 	};
275 
276 private:
277 
278 	// Machine data
279 	const state_base* curr_state;	//!< Current state of the machine
280 	smach* child;				   //!< Child machine
281 
282 public: // this really should be private
283 	void* state_context;		//!< State Context
284 private:
285 
286 	context_type* machine_context;		//!< Machine Context
287 
288 	const state_base* default_state;
289 	void*	default_context;
290 
291 #ifdef ETL_MUTEX_LOCK
292 	_mutex mutex;
293 #endif
294 
295 	//! State stack data
296 	const state_base* 	state_stack[SMACH_STATE_STACK_SIZE];
297 	void* 				state_context_stack[SMACH_STATE_STACK_SIZE];
298 	int 				states_on_stack;
299 
300 public:
301 
302 	//! Gets the name of the currently active state
303 	const char *
get_state_name()304 	get_state_name()const
305 	{
306 #ifdef ETL_MUTEX_LOCK
307 		ETL_MUTEX_LOCK();
308 #endif
309 		if(curr_state)
310 			return curr_state->get_name();
311 		if(default_state)
312 			return default_state->get_name();
313 		return 0;
314 	}
315 
316 	//! Determines if a given event result is an error
317 	/*! This function allows us to quickly see
318 		if an event_result contained an error */
319 	static bool
event_error(const event_result & rhs)320 	event_error(const event_result &rhs)
321 		{ return rhs<=RESULT_ERROR; }
322 
323 	bool
set_default_state(const state_base * nextstate)324 	set_default_state(const state_base *nextstate)
325 	{
326 #ifdef ETL_MUTEX_LOCK
327 		ETL_MUTEX_LOCK();
328 #endif
329 		// Keep track of the current state unless
330 		// the state switch fails
331 		const state_base *prev_state=default_state;
332 
333 		// If we are already in a state, leave it and
334 		// collapse the state stack
335 		if(default_state)
336 			default_state->leave_state(default_context);
337 
338 		// Set this as our current state
339 		default_state=nextstate;
340 		default_context=0;
341 
342 		// Attempt to enter the state
343 		if(default_state)
344 		{
345 			default_context=default_state->enter_state(machine_context);
346 			if(default_context)
347 				return true;
348 		}
349 		else
350 			return true;
351 
352 		// We failed, so attempt to return to previous state
353 		default_state=prev_state;
354 
355 		// If we had a previous state, enter it
356 		if(default_state)
357 			default_context=default_state->enter_state(machine_context);
358 
359 		// At this point we are not in the
360 		// requested state, so return failure
361 		return false;
362 	}
363 
364 	//! Leaves the current state
365 	/*! Effectively makes the state_depth() function return zero. */
366 	bool
egress()367 	egress()
368 	{
369 #ifdef ETL_MUTEX_LOCK
370 		ETL_MUTEX_LOCK();
371 #endif
372 
373 		// Pop all states off the state stack
374 		while(states_on_stack) pop_state();
375 
376 		// If we are not in a state, then I guess
377 		// we were successful.
378 		if(!curr_state)
379 			return true;
380 
381 		// Grab the return value from the exit function
382 		bool ret=true;
383 
384 		const state_base* old_state=curr_state;
385 		void *old_context=state_context;
386 
387 		// Clear out the current state and its state_context
388 		curr_state=0;state_context=0;
389 
390 		// Leave the state
391 		return old_state->leave_state(old_context);
392 
393 		return ret;
394 	}
395 
396 	//! State entry function
397 	/*! Attempts to enter the given state,
398 		popping off all states on the stack
399 		in the process. */
400 	bool
enter(const state_base * nextstate)401 	enter(const state_base *nextstate)
402 	{
403 #ifdef ETL_MUTEX_LOCK
404 		ETL_MUTEX_LOCK();
405 #endif
406 
407 		// Keep track of the current state unless
408 		// the state switch fails
409 		const state_base *prev_state=curr_state;
410 
411 		// If we are already in a state, leave it and
412 		// collapse the state stack
413 		if(curr_state)
414 			egress();
415 
416 		// Set this as our current state
417 		curr_state=nextstate;
418 		state_context=0;
419 
420 		// Attempt to enter the state
421 		state_context=curr_state->enter_state(machine_context);
422 		if(state_context)
423 			return true;
424 
425 		// We failed, so attempt to return to previous state
426 		curr_state=prev_state;
427 
428 		// If we had a previous state, enter it
429 		if(curr_state)
430 			state_context=curr_state->enter_state(machine_context);
431 
432 		// At this point we are not in the
433 		// requested state, so return failure
434 		return false;
435 	}
436 
437 	//! Pushes state onto state stack
438 	/*! This allows you to enter a state without
439 		leaving your current state.
440 		\param   nextstate Pointer to the state to enter
441 		\sa      pop_state()
442 	*/
443 	bool
push_state(const state_base * nextstate)444 	push_state(const state_base *nextstate)
445 	{
446 #ifdef ETL_MUTEX_LOCK
447 		ETL_MUTEX_LOCK();
448 #endif
449 
450 		// If there are not enough slots, then throw something.
451 		if(states_on_stack==SMACH_STATE_STACK_SIZE)
452 			throw(std::overflow_error("smach<>::push_state(): state stack overflow!"));
453 
454 		// If there is no current state, nor anything on stack,
455 		// just go ahead and enter the given state.
456 		if(!curr_state && !states_on_stack)
457 			return enter(nextstate);
458 
459 		// Push the current state onto the stack
460 		state_stack[states_on_stack]=curr_state;
461 		state_context_stack[states_on_stack++]=state_context;
462 
463 		// Make the next state the current state
464 		curr_state=nextstate;
465 
466 		// Try to enter the next state
467 		state_context=curr_state->enter_state(machine_context);
468 		if(state_context)
469 			return true;
470 
471 		// Unable to push state, return to old one
472 		curr_state=state_stack[--states_on_stack];
473 		state_context=state_context_stack[states_on_stack];
474 		return false;
475 	}
476 
477 	//! Pops state off of state stack
478 	/*! Decreases state depth */
479 	void
pop_state()480 	pop_state()
481 	{
482 #ifdef ETL_MUTEX_LOCK
483 		ETL_MUTEX_LOCK();
484 #endif
485 
486 		// If we aren't in a state, then there is nothing
487 		// to do.
488 		if(!curr_state)
489 			throw(std::underflow_error("smach<>::pop_state(): stack is empty!"));
490 
491 		if(states_on_stack)
492 		{
493 			const state_base* old_state=curr_state;
494 			void *old_context=state_context;
495 
496 			// Pop previous state off of stack
497 			--states_on_stack;
498 			curr_state=state_stack[states_on_stack];
499 			state_context=state_context_stack[states_on_stack];
500 
501 			old_state->leave_state(old_context);
502 		}
503 		else // If there are no states on stack, just egress
504 			egress();
505 	}
506 
507 	//! State Machine Constructor
508 	/*! A more detailed description needs to be written */
509 	smach(context_type* machine_context=0):
510 		curr_state(0),
511 		child(0),
512 		state_context(0),
513 		machine_context(machine_context),
514 		default_state(0),
515 		default_context(0),
516 		states_on_stack(0)
517 	{ }
518 
519 	//! The destructor
~smach()520 	~smach()
521 	{
522 		egress();
523 
524 		if(default_state)
525 			default_state->leave_state(default_context);
526 	}
527 
528 	//! Sets up a child state machine
529 	/*! A child state machine runs in parallel with
530 		its parent, and gets event priority. This
531 		mechanism is useful in cases where an inherited
532 		object has its own state machine. */
set_child(smach * x)533 	void set_child(smach *x)
534 	{
535 #ifdef ETL_MUTEX_LOCK
536 		ETL_MUTEX_LOCK();
537 #endif
538 		child=x;
539 	}
540 
541 	//! Returns the number states currently active
542 	int
state_depth()543 	state_depth()
544 		{ return curr_state?states_on_stack+1:0; }
545 
546 	event_result
process_event(const event_key & id)547 	process_event(const event_key& id) { return process_event(event(id)); }
548 
549 	//! Process an event
550 	event_result
process_event(const event & id)551 	process_event(const event& id)
552 	{
553 #ifdef ETL_MUTEX_LOCK
554 		ETL_MUTEX_LOCK();
555 #endif
556 
557 		event_result ret(RESULT_OK);
558 
559 		// Check for child machine
560 		if(child)
561 		{
562 			ret=child->process_event(id);
563 			if(ret!=RESULT_OK)
564 				return ret;
565 		}
566 
567 		try
568 		{
569 			if(curr_state)
570 				ret=curr_state->process_event(state_context,id);
571 
572 			if(ret==RESULT_OK)
573 				return default_state->process_event(default_context,id);
574 
575 			return ret;
576 		}
577 		catch(egress_exception) {
578 			if (egress()) {
579 				ret=RESULT_ACCEPT;
580 			} else {
581 				ret=RESULT_ERROR;
582 			}
583 		}
584 		catch(pop_exception) { pop_state(); return RESULT_ACCEPT; }
585 		catch(const state_base* state) { return enter(state)?RESULT_ACCEPT:RESULT_ERROR; }
586 		return ret;
587 	}
588 
589 }; // END of template class smach
590 
591 _ETL_END_NAMESPACE
592 
593 /* === E X T E R N S ======================================================= */
594 
595 /* === E N D =============================================================== */
596 
597 #endif
598