|
![]() |
|
|
|
class QHsm // Quantum Hierarchical State Machine { public: typedef void (QHsm::*QPseudoState)(QEvent const*); // pseudostate[1] typedef QPseudoState (QHsm::*QState)(QEvent const*); // state[2] // state handler returns parent state[3] for implicit event handling void init(QEvent const *e = 0); // execute initial transition void dispatch(QEvent const *e); // dispatch event[4] int isIn(QState state); // "is-in-state" query static char const *getVersion(); protected: struct Tran // protected inner class Tran[5] { QState myChain[7]; unsigned short myActions; // action mask (2-bits for action) }; QHsm(QPseudoState initial); // ctor[6] virtual ~QHsm(); // virtual xtor QPseudoState top(QEvent const*) // the "top" state { return 0; } QState getState() const { return myState; } void tran(QState target); // dynamic state transition void tranStat(Tran *t, QState target); // static state transition void init_(QState target) { myState = target; } #define Q_INIT(target_) init_(Q_STATIC_CAST(QState, target_)) #define Q_TRAN(target_) if (1) { \ static Tran t_; \ tranStat(&t_, Q_STATIC_CAST(QState, target_));\ } else ((void)0) #define Q_TRAN_DYN(target_) tran(Q_STATIC_CAST(QState, target_)) private: void tranSetup(Tran *t, QState target); private: QState myState; // the active state QState mySource; // source state during a transition }; typedef QHsm::QPseudoState QSTATE; // state-handler return type
// compiler dependent static cast // preferred new style #define Q_STATIC_CAST(type_, expr_) static_cast<type_>(expr_) // older C style #define Q_STATIC_CAST(type_, expr_) (type)expr_
// compiler dependent dynamic cast // preferred new style #define Q_STATE_CAST(expr_) reinterpret_cast<QState>(expr_) // older C style #define Q_STATE_CAST(expr_) (QState)expr_
// calls init_() after cast #define Q_INIT(target_) init_(Q_STATIC_CAST(QState, target_))
#define Q_TRAN(target_) if (1) { \ static Tran t_; \ tranStat(&t_, Q_STATIC_CAST(QState, target_));\ } else ((void)0)Effect: substitute the code block
{ static Tran t_; tranStat(&t_, Q_STATIC_CAST(QState, target_)); }for Q_TRAN(target_)
// calls tran() after cast #define Q_TRAN_DYN(target_) tran(Q_STATIC_CAST(QState, target_))
typedef unsigned short QSignal; struct QEvent { QSignal sig; // signal of the event instance unsigned char poolId; // pool this event instance comes from unsigned char useNum; // # of times it has been used so far };
enum // standard signals { Q_INIT_SIG = 1, Q_ENTRY_SIG, Q_EXIT_SIG, Q_USER_SIG };
enum MySignals { mySig1 = Q_USER_SIG, mySig2, ... };
protected: QPseudoState top(QEvent const*) // the "top" state { return 0; }
|
![]() |
class QHsmTst : public QHsm { public: QHsmTst() : QHsm((QPseudoState)initial) {} protected: void initial(QEvent const *e); // initial pseudostate QSTATE s0(QEvent const *e); // state-handler QSTATE s1(QEvent const *e); // state-handler QSTATE s11(QEvent const *e); // state-handler QSTATE s2(QEvent const *e); // state-handler QSTATE s21(QEvent const *e); // state-handler QSTATE s211(QEvent const *e); // state-handler private: // extended state variables... int myFoo; };Note the use of indentation to self-document the state hierarchy
void QHsmTst::initial(QEvent const *) { printf("top-INIT;"); myFoo = 0; // initialize extended state variable Q_INIT(&QHsmTst::s0); // initial transition } |
QSTATE QHsmTst::s0(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: printf("s0-ENTRY;"); return 0; case Q_EXIT_SIG: printf("s0-EXIT;"); return 0; case Q_INIT_SIG: printf("s0-INIT;"); Q_INIT(&QHsmTst::s1); return 0; case E_SIG: printf("s0-E;"); Q_TRAN(&QHsmTst::s211); return 0; } return (QSTATE)&QHsmTst::top; } |
QSTATE QHsmTst::s1(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: printf("s1-ENTRY;"); return 0; case Q_EXIT_SIG: printf("s1-EXIT;"); return 0; case Q_INIT_SIG: printf("s1-INIT;"); Q_INIT(&QHsmTst::s11); return 0; case A_SIG: printf("s1-A;"); Q_TRAN(&QHsmTst::s1); return 0; case B_SIG: printf("s1-B;"); Q_TRAN(&QHsmTst::s11); return 0; case C_SIG: printf("s1-C;"); Q_TRAN(&QHsmTst::s2); return 0; case D_SIG: printf("s1-D;"); Q_TRAN(&QHsmTst::s0); return 0; case F_SIG: printf("s1-F;"); Q_TRAN(&QHsmTst::s211); return 0; } return (QSTATE)&QHsmTst::s0; } |
|
QSTATE QHsmTst::s11(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: printf("s11-ENTRY;"); return 0; case Q_EXIT_SIG: printf("s11-EXIT;"); return 0; case G_SIG: printf("s11-G;"); Q_TRAN(&QHsmTst::s211); return 0; case H_SIG: // internal transition with a guard if (myFoo) // test the guard condition { printf("s11-H;"); myFoo = 0; return 0; } break; // else return superstate } return (QSTATE)&QHsmTst::s1; } QSTATE QHsmTst::s2(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: printf("s2-ENTRY;"); return 0; case Q_EXIT_SIG: printf("s2-EXIT;"); return 0; case Q_INIT_SIG: printf("s2-INIT;"); Q_INIT(&QHsmTst::s21); return 0; case C_SIG: printf("s2-C;"); Q_TRAN(&QHsmTst::s1); return 0; case F_SIG: printf("s2-F;"); Q_TRAN(&QHsmTst::s11); return 0; } return (QSTATE)&QHsmTst::s0; } QSTATE QHsmTst::s21(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: printf("s21-ENTRY;"); return 0; case Q_EXIT_SIG: printf("s21-EXIT;"); return 0; case Q_INIT_SIG: printf("s21-INIT;"); Q_INIT(&QHsmTst::s211); return 0; case B_SIG: printf("s21-B;"); Q_TRAN(&QHsmTst::s211); return 0; case H_SIG: // self transition with a guard if (!myFoo) // test the guard condition { printf("s21-H;"); myFoo = 1; Q_TRAN(&QHsmTst::s21); // self transition return 0; } break; // else return the superstate } return (QSTATE)&QHsmTst::s2; // return the superstate } QSTATE QHsmTst::s211(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: printf("s211-ENTRY;"); return 0; case Q_EXIT_SIG: printf("s211-EXIT;"); return 0; case D_SIG: printf("s211-D;"); Q_TRAN(&QHsmTst::s21); return 0; case G_SIG: printf("s211-G;"); Q_TRAN(&QHsmTst::s0); return 0; } return (QSTATE)&QHsmTst::s21; } | |
static const QEvent testQEvt[] = { {A_SIG, 0, 0}, {B_SIG, 0, 0}, {C_SIG, 0, 0}, {D_SIG, 0, 0}, {E_SIG, 0, 0}, {F_SIG, 0, 0}, {G_SIG, 0, 0}, {H_SIG, 0, 0} }; static QHsmTst test; int main() { printf("QHsmTst example, version 1.00, libraries: %s\n", QHsm::getVersion()); test.init(); // take the initial transition for (;;) { printf("\nSignal<-"); char c = getc(stdin); getc(stdin); // discard '\n' if (c < 'a' || 'h' < c) // in range? { return 0; } test.dispatch(&testQEvt[c - 'a']); // dispatch event } return 0; } extern "C" void onAssert__(char const *file, unsigned line) { fprintf(stderr, "Assertion failed in %s, line %d", file, line); exit(-1); } |
Match code to model
Construct complete state handlers.
QSTATE MyHsm::stateAB (QEvent const * e) { switch { ... case MYSIG1_SIG: onMySig1(); // hides state transition return 0; ... } return (QSTATE)&MyHsm::stateA; } void MyHsm::onMySig1() { if (myA && !myB && myC > 0 ...) { Q_TRAN(&MyHsm::stateB); } } QSTATE MyHsm::stateAB (QEvent const * e) { switch { ... case MYSIG1_SIG: if (sig1Guard()) { Q_TRAN(&MyHsm::stateB); } return 0; ... } return (QSTATE)&MyHsm::stateA; } int MyHsm::sig1Guard() { if (myA && !myB && myC > 0 ...) { return 1; } return 0; } Keep HSM topology static
QSTATE MyHsm::stateAB (QEvent const * e) { switch { ... case MYSIG1_SIG: Q_TRAN((...) ? (QState)stateB : (QState)stateC); // NO! return 0; ... } return (QSTATE)&MyHsm::stateA; } QSTATE MyHsm::stateAB (QEvent const * e) { switch { ... case MYSIG1_SIG: if (...) Q_TRAN(stateB); else Q_TRAN(stateC); return 0; ... } return (QSTATE)&MyHsm::stateA; } Choose "right" signal granularity
Allow active composit states
![]() Inside The Event Processor
![]() Assumptions
extern QEvent const pkgStdEvt[]; // preallocated standard events ![]() The Is In State Query
#define TRIGGER(state_, sig_) \ Q_STATE_CAST((this->*(state_))(&pkgStdEvt[sig_])) int QHsm::isIn(QState state) { register QState s; for (s = myState; s != 0; s = TRIGGER(s, Q_EMPTY_SIG)) { if (s == state) // do the states match? { return !0; // match found, return true } } return 0; // no match found, return false }
QHsm::QHsm(QPseudoState initial) : myState(&QHsm::top), mySource(Q_STATE_CAST(initial)) {} void QHsm::init(QEvent const *e) { REQUIRE(myState == &QHsm::top && // HSM not executed yet mySource != 0); // we are about to dereference mySource register QState s = myState; // save myState in a temporary (this->*(QPseudoState)mySource)(e); // top-most initial transition // initial transition must go *one* level deep ASSERT(s == TRIGGER(myState, Q_EMPTY_SIG)); s = myState; // update the temporary TRIGGER(s, Q_ENTRY_SIG); // enter the state while (TRIGGER(s, Q_INIT_SIG) == 0) // init handled? { // initial transition must go *one* level deep ASSERT(s == TRIGGER(myState, Q_EMPTY_SIG)); s = myState; TRIGGER(s, Q_ENTRY_SIG); // enter the substate } } ![]() Dispatching Events
void QHsm::dispatch(QEvent const *e) { for (mySource = myState; mySource != 0; mySource = Q_STATE_CAST((this->*mySource)(e))) {} } ![]() Dynamic Transitions
#define Q_TRAN_DYN(target_) tran(Q_STATIC_CAST(QState, target_))
void QHsm::tran(QState target) { REQUIRE(target != &QHsm::top); // cannot target "top" state QState entry[7], p, q, s, *e, *lca; for (s = myState; s != mySource; ) { ASSERT(s != 0); // we are about to dereference s QState t = TRIGGER(s, Q_EXIT_SIG); if (t != 0) // exit action unhandled, t points to superstate { s = t; } else // exit action handled, elicit superstate { s = TRIGGER(s, Q_EMPTY_SIG); } } *(e = &entry[0]) = 0; *(++e) = target; // assume entry to target // (a) check mySource == target (transition to self) if (mySource == target) { TRIGGER(mySource, Q_EXIT_SIG); // exit source goto inLCA; } // (b) check mySource == target->super p = TRIGGER(target, Q_EMPTY_SIG); if (mySource == p) { goto inLCA; } // (c) check mySource->super == target->super (most common) q = TRIGGER(mySource, Q_EMPTY_SIG); if (q == p) { TRIGGER(mySource, Q_EXIT_SIG); // exit source goto inLCA; } // (d) check mySource->super == target if (q == target) { TRIGGER(mySource, Q_EXIT_SIG); // exit source --e; // do not enter the LCA goto inLCA; } // (e) check rest of mySource == target->super->super... hierarchy *(++e) = p; for (s = TRIGGER(p, Q_EMPTY_SIG); s != 0; s = TRIGGER(s, Q_EMPTY_SIG)) { if (mySource == s) { goto inLCA; } *(++e) = s; } TRIGGER(mySource, Q_EXIT_SIG); // exit source // (f) check rest of mySource->super == target->super->super... for (lca = e; *lca != 0; --lca) { if (q == *lca) { e = lca - 1; // do not enter the LCA goto inLCA; } } // (g) check each mySource->super->super..for each target... for (s = q; s != 0; s = TRIGGER(s, Q_EMPTY_SIG)) { for (lca = e; *lca != 0; --lca) { if (s == *lca) { e = lca - 1; // do not enter the LCA goto inLCA; } } TRIGGER(s, Q_EXIT_SIG); // exit s } ASSERT(0); // malformed HSM inLCA: // now we are in the LCA of mySource and target ASSERT(e < &entry[DIM(entry)]); // new entry e must fit in while ((s = *e--) != 0) //retrace the entry path in reverse order { TRIGGER(s, Q_ENTRY_SIG); // enter s } myState = target; // update current state while (TRIGGER(target, Q_INIT_SIG) == 0) { // initial transition must go *one* level deep ASSERT(target == TRIGGER(myState, Q_EMPTY_SIG)); target = myState; TRIGGER(target, Q_ENTRY_SIG); // enter target } } ![]() Static Transitions
#define Q_TRAN(target_) if (1) { \ static Tran t_; \ tranStat(&t_, Q_STATIC_CAST(QState, target_));\ } else ((void)0)Effect: substitute the code block { static Tran t_; tranStat(&t_, Q_STATIC_CAST(QState, target_)); }Note the subtlty: t_ is locally scoped in association with the particular transition, yet it is a persistent (static) data item.
void QHsm::tranStat(Tran *tran, QState target) { REQUIRE(target != &QHsm::top); // cannot target "top" state register QState s; // ascend to mySource, invoking exit actions for (s = myState; s != mySource; ) { ASSERT(s != 0); // we are about to dereference s QState t = TRIGGER(s, Q_EXIT_SIG); if (t != 0) // exit action unhandled, t points to superstate { s = t; } else exit action handled, elicit superstate { s = TRIGGER(s, Q_EMPTY_SIG); } } // check for transition object if (tran->myActions == 0) // is the tran object initialized? { tranSetup(tran, target); // setup the transition object } // transition object initialized, execute transition chain { register QState *c = &tran->myChain[0]; register unsigned short a; for (a = tran->myActions >> 1; a != 0; a >>= 2, ++c) { (this->*(*c))(&pkgStdEvt[a & 3]); } myState = *c; } } ![]() The Transition Path
void QHsm::tranSetup(Tran *tran, QState target) { QState entry[DIM(tran->myChain)], p, q, s, *c, *e, *lca; unsigned short a = 0; #define RECORD(state_, sig_) \ if (TRIGGER(state_, sig_) == 0)\ {\ a |= ((sig_) << 14); \ a >>= 2; \ *c++ = (state_); \ }\ else ((void)0) c = &tran->myChain[0]; *(e = &entry[0]) = 0; *(++e) = target; // assume entry to target // (a) check mySource == target (transition to self) if (mySource == target) { RECORD(mySource, Q_EXIT_SIG); // exit source goto inLCA; } // (b) check mySource == target->super p = TRIGGER(target, Q_EMPTY_SIG); if (mySource == p) { goto inLCA; } // (c) check mySource->super == target->super (most common) q = TRIGGER(mySource, Q_EMPTY_SIG); if (q == p) { RECORD(mySource, Q_EXIT_SIG); // exit source goto inLCA; } // (d) check mySource->super == target if (q == target) { RECORD(mySource, Q_EXIT_SIG); // exit source --e; // do not enter the LCA goto inLCA; } // (e) check rest of mySource == target->super->super... hierarchy *(++e) = p; for (s = TRIGGER(p, Q_EMPTY_SIG); s != 0; s = TRIGGER(s, Q_EMPTY_SIG)) { if (mySource == s) { goto inLCA; } *(++e) = s; } RECORD(mySource, Q_EXIT_SIG); // exit source // (f) check rest of mySource->super == target->super->super... for (lca = e; *lca != 0; --lca) { if (q == *lca) { e = lca - 1; // do not enter the LCA goto inLCA; } } // (g) check each mySource->super->super..for each target... for (s = q; s != 0; s = TRIGGER(s, Q_EMPTY_SIG)) { for (lca = e; *lca != 0; --lca) { if (s == *lca) { e = lca - 1; // do not enter the LCA goto inLCA; } } RECORD(s, Q_EXIT_SIG); // exit s } ASSERT(0); // malformed HSM inLCA: // now we are in the LCA of mySource and target ASSERT(e < &entry[DIM(entry)]); // new entry e must fit in while ((s = *e--) != 0) //retrace the entry path in reverse order { RECORD(s, Q_ENTRY_SIG); // enter s } myState = target; // update current state while (TRIGGER(target, Q_INIT_SIG) == 0) { // initial transition must go *one* level deep ASSERT(target == TRIGGER(myState, Q_EMPTY_SIG)); a |= (Q_INIT_SIG << 14); a >>= 2; *c++ = target; target = myState; RECORD(target, Q_ENTRY_SIG); // enter target } #undef RECORD *c = target; tran->myActions = (a >> (13 - (c - &tran->myChain[0])*2)) & 0x1; // transition initialized ENSURE(tran->myChain[0] != 0 && c < &tran->myChain[DIM(tran->myChain)]); // check overflow } ![]() Review
![]() Notes1. Definition of QPseudoState expanded for reading: typedef // defining signature of pseudostate handler method void // return type of method (QHsm::* // class membership of method QPseudoState // typename defined ) (QEvent const*) // argument list ; 2. Definition of QState expanded for reading: typedef // defining signature of state handler method QPseudoState // return type of method (QHsm::* // class membership of method QState // typename defined ) (QEvent const*) // argument list of method ; typedef QHsm::QPseudoState QSTATE; // state-handler return type
QSTATE Calc::operand1(QEvent const *e) // state-handler signature { switch (e->sig) { case Q_ENTRY_SIG: // handle entry-sig event (entry action) dispState("operand1"); return 0; case IDC_CE: // handle clear-entry event clear(); Q_TRAN(&Calc::begin); // transition to "begin" return 0; case IDC_OPER: // handle operator-entered event sscanf(myDisplay, "%lf", &myOperand1); myOperator = (static_cast<CalcEvt *>(e))->keyId; Q_TRAN(&Calc::opEntered); // transition to "opEntered" return 0; } return (QSTATE)&Calc::calc; // event not handled, return parent state } ![]() 4. Hierarchical event dispatching void QHsm::dispatch(QEvent const *e) { for (mySource = myState; mySource != 0; mySource = Q_STATE_CAST((this->*mySource)(e))) {} } class Tran { public: QState myChain[7]; unsigned short myActions; // action mask (2-bits for action) };
6. Protected constructor and destructor
class QHsm { protected: QHsm(QPseudoState initial); // ... }; class QHsmTst : public QHsm { public: QHsmTst() : QHsm ((QPseudoState)initial) // default constructor {} // ... }; QSTATE Calc::zero1(QEvent const *e) // state-handler signature { switch (e->sig) { case Q_ENTRY_SIG: // handle entry signal event (entry action) dispState("zero1"); return 0; case IDC_1_9: // handle digit(1..9) event insert(static_cast<CalcEvt *>(e)->keyId); Q_TRAN(&Calc::int1); // transition to "int1" return 0; case IDC_POINT: // handle "." event insert(((CalcEvt *)e)->keyId); Q_TRAN(&Calc::frac1); // transition to "frac1" return 0; } return (QSTATE)&Calc::operand1; // event not handled, return parent state } ![]() 8. Initial Transtion and Entry Action Example QSTATE Calc::calc(QEvent const *e) { switch (e->sig) { case Q_ENTRY_SIG: // no state transition dispState("calc"); return 0; case Q_INIT_SIG: // transition to initial via Q_INIT() clear(); Q_INIT(&Calc::ready); return 0; case IDC_C: clear(); Q_TRAN(&Calc::calc); return 0; case TERMINATE: Q_TRAN(&Calc::final); return 0; } if (e->sig >= Q_USER_SIG) // a form of exception handling { isHandled = FALSE; } return (QSTATE)&Calc::top; } 9. Initial transition for QCalc void Calc::initial(QEvent const *) { clear(); Q_INIT(&Calc::calc); } |