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