void fsmbDebugPrint (string s) { #ifdef FSM_DEBUG PrintToRPT("" + s); // comment/uncomment to hide/see debug logs #else //Print("" + s); // comment/uncomment to hide/see debug logs #endif } void fsmbDebugSpam (string s) { #ifdef FSM_DEBUG_SPAM PrintToRPT("" + s); // comment/uncomment to hide/see debug logs #else //Print("" + s); // comment/uncomment to hide/see debug logs #endif } /**@class FSMTransition * @brief represents transition src ---- event[guard]/action ----|> dst **/ class FSMTransition { ref FSMStateBase m_srcState; ref FSMEventBase m_event; // @NOTE: NULL event means "completion transition" in UML speak ref FSMStateBase m_dstState; // @NOTE: NULL dst state == UML terminate pseudonode ref FSMActionBase m_action; ref FSMGuardBase m_guard; void FSMTransition (FSMStateBase src, FSMEventBase e, FSMStateBase dst, FSMActionBase a = NULL, FSMGuardBase g = NULL) { m_srcState = src; m_event = e; m_dstState = dst; m_action = a; m_guard = g; } }; enum ProcessEventResult { FSM_OK, FSM_TERMINATED, FSM_ABORTED, FSM_NO_TRANSITION, }; /**@class FSMBase * @brief base class for finite state machine * * stores current state (m_state) and transition table with possible transitions from each state * to another state via event **/ class FSMBase { protected ref FSMStateBase m_state; /// current fsm state protected ref FSMStateBase m_initialState; /// configurable initial state of the machine protected ref FSMEventBase m_initialEvent; /// configurable initial event to start the machine (null by default) protected ref array> m_transitions; /// fsm transition table void FSMBase () { m_transitions = new array>; } /**@fn GetCurrentState * @brief returns currently active state * @return current state the FSM is in (or NULL) **/ FSMStateBase GetCurrentState () { return m_state; } /**@fn SetInitialState * @brief sets the initial_state for starting the machine **/ void SetInitialState (FSMStateBase initial_state) { m_initialState = initial_state; } /**@fn Start * @brief starts the state machine by entering the initial_state (using intial_event as argument to initial state's onEntry) * @param[in] e \p optional event for starting the machind **/ void Start (FSMEventBase initial_event = NULL) { if (LogManager.IsInventoryHFSMLogEnable()) fsmbDebugPrint("[fsm] " + this.ToString() + "::Start(" + initial_event.ToString() + "), init_state=" + m_initialState.ToString()); m_state = m_initialState; m_state.OnEntry(initial_event); } /**@fn IsRunning * @brief returns true if machine is in running state **/ bool IsRunning () { return m_state != NULL; } /**@fn Terminate * @brief terminates the state machine **/ void Terminate (FSMEventBase terminal_event = NULL) { if (IsRunning()) { m_state.OnExit(terminal_event); m_state = NULL; } } /**@fn Update * @brief if machine running, call OnUpdate() on current state **/ void Update (float dt) { if (IsRunning()) m_state.OnUpdate(dt); } /**@fn AddTransition * @brief adds transition into transition table **/ void AddTransition (FSMTransition t) { m_transitions.Insert(t); } /**@fn ProcessEvent * @brief instructs the state machine to process the event e * @param[in] e \p event that will be used to find suitable transition from current state * @return FSM_OK if transition found and allowed by guard (if any) **/ ProcessEventResult ProcessEvent (FSMEventBase e) { FSMStateBase curr_state = m_state; int count = m_transitions.Count(); for (int i = 0; i < count; ++i) { FSMTransition row = m_transitions.Get(i); if (row.m_srcState.Type() == curr_state.Type() && row.m_event.Type() == e.Type()) { FSMTransition t = m_transitions.Get(i); bool hasGuard = t.m_guard != NULL; if (!hasGuard || (hasGuard && t.m_guard.GuardCondition(e))) // 1) exec guard (if any) { ProcessLocalTransition(t, e); // 2) process transition allowed by guard } } } return ProcessEventResult.FSM_NO_TRANSITION; } /**@fn ProcessLocalTransition * @brief instructs the state machine to process the event locally - no hierarchy is crossed * @param[in] t \p the transition in m_transitions * @param[in] e \p event that will be used to process transition from current state * @return FSM_OK or FSM_TERMINATED **/ protected ProcessEventResult ProcessLocalTransition (FSMTransition t, FSMEventBase e) { if (LogManager.IsInventoryHFSMLogEnable()) fsmbDebugPrint("[fsm] (local) state=" + t.m_srcState.ToString() + "-------- event=" + e.ToString() + "[G=" + t.m_guard.ToString() +"]/A=" + t.m_action.ToString() + " --------|> dst=" + t.m_dstState.ToString()); m_state.OnExit(e); // 1) call onExit on old state if (t.m_action) t.m_action.Action(e); // 2) execute transition action (if any) m_state = t.m_dstState; // 3) change state to new if (t.m_dstState != NULL) { m_state.OnEntry(e); // 4a) call onEntry on new state return ProcessEventResult.FSM_OK; } else { if (LogManager.IsInventoryHFSMLogEnable()) fsmbDebugPrint("[fsm] terminating fsm: state=" + t.m_srcState.ToString() + " event=" + e.ToString()); return ProcessEventResult.FSM_TERMINATED; // 4b) or terminate } } };