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