/**@class OFSMBase * @brief base class for Orthogonal Finite State Machine * * stores current states (m_states) and transition table with possible transitions from each state * to another state via event **/ class OFSMBase { protected ref array m_States; /// current fsm state protected ref array m_InitialStates; /// configurable initial state of the machine protected ref array> m_Transitions; /// fsm transition table void OFSMBase () { m_States = new array; m_InitialStates = new array; m_Transitions = new array>; } /**@fn GetCurrentStates * @brief returns currently active state * @return current state the FSM is in (or NULL) **/ array GetCurrentState () { return m_States; } /**@fn SetInitialState * @brief sets the initial_state for starting the machine **/ void SetInitialStates (array initial_states) { m_InitialStates = initial_states; for (int s = 0; s < initial_states.Count(); ++s) m_States.Insert(initial_states[s]); } /**@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 (array initial_events = null) { if (LogManager.IsInventoryHFSMLogEnable()) fsmbDebugPrint("[ofsm] " + this.ToString() + "::Start(" + initial_events.ToString() + "), init_state=" + m_InitialStates.ToString()); for (int s = 0; s < m_States.Count(); ++s) { m_States[s] = m_InitialStates[s]; if (initial_events) m_States[s].OnEntry(initial_events[s]); else m_States[s].OnEntry(null); } } /**@fn IsRunning * @brief returns true if machine is in running state **/ bool IsRunning () { int sc = m_States.Count(); if (sc) { for (int s = 0; s < sc; ++s) if (m_States[s] != null) return true; } return false; } /**@fn Terminate * @brief terminates the state machine **/ void Terminate (array terminal_events = null) { if (IsRunning()) { for (int s = 0; s < m_States.Count(); ++s) { if (terminal_events) m_States[s].OnExit(terminal_events[s]); else m_States[s].OnExit(null); m_States[s] = null; } } } /**@fn Update * @brief if machine running, call OnUpdate() on current state **/ void Update (float dt) { if (IsRunning()) { for (int s = 0; s < m_States.Count(); ++s) { m_States[s].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) { int count = m_Transitions.Count(); for (int i = 0; i < count; ++i) { FSMTransition row = m_Transitions.Get(i); if (row.m_event.Type() == e.Type()) { for (int s = 0; s < m_States.Count(); ++s) { if (row.m_srcState.Type() == m_States[s].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(s, 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 (int s, FSMTransition t, FSMEventBase e) { if (LogManager.IsInventoryHFSMLogEnable()) fsmbDebugPrint("[ofsm] (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_States[s].OnExit(e); // 1) call onExit on old state if (t.m_action) t.m_action.Action(e); // 2) execute transition action (if any) m_States[s] = t.m_dstState; // 3) change state to new if (t.m_dstState != NULL) { m_States[s].OnEntry(e); // 4a) call onEntry on new state return ProcessEventResult.FSM_OK; } else { if (LogManager.IsInventoryHFSMLogEnable()) fsmbDebugPrint("[ofsm] terminating fsm: state=" + t.m_srcState.ToString() + " event=" + e.ToString()); return ProcessEventResult.FSM_TERMINATED; // 4b) or terminate } } };