public StateMachineContainer() { // // The underlying finite state machine engine needs a list of all // the states. It gets them as we "Add" them here. // OpeningDoor = engine.AddState("OpeningDoor"); ClosingDoor = engine.AddState("ClosingDoor"); Stopping = engine.AddState("Stopping"); // // Alternatively //OpeningDoor = engine.AddState(new FiniteState("OpeningDoor")); //ClosingDoor = engine.AddState(new FiniteState("ClosingDoor")); // Alternatively (more) //Stopping = new FiniteState("Stopping"); //engine.AddState(Stopping); // // Events are not "added" to the state machine directly. // However, they are effectively added to each state when state transitions are defined. // DoorOpened = new FiniteStateEvent("DoorOpened"); DoorClosed = new FiniteStateEvent("DoorClosed"); ActionFailed = new FiniteStateEvent("ActionFailed"); }
///// <summary> ///// RunStateMachine is the heart of the engine that runs the finite state machine. It is executed in a ///// separate thread that is started when you <see cref="Start(FiniteState)"/> the state machine. ///// All events that have been queued by <see cref="RaiseEvent(FiniteStateEvent)"/>, are processed here. ///// The method runs in an infinite loop that only exits when <see cref="Stop()"/> is called. ///// When it has processed all of the events in the queue, it will sleep until it is pulsed ///// by either <see cref="RaiseEvent(FiniteStateEvent)"/> or <see cref="Stop()"/> ///// </summary> //public void RunStateMachine() //{ // string methodName = System.Reflection.MethodBase.GetCurrentMethod().Name; // if (log.IsDebugEnabled) // log.Debug($"{methodName}: enter"); // // try // { // while (!stopRequested) // { // HandlePendingEvents(); // if (!stopRequested) // { // lock (locker) // { // if (log.IsDebugEnabled) // log.Debug($"{methodName}: Putting to sleep the state machine thread."); // // Monitor.Wait(locker); // Nothing to do, so go to sleep until we are pulsed by RaiseEvent or Stop // // if (log.IsDebugEnabled) // log.Debug($"{methodName}: Just woke up :-)"); // } // } // } // } // catch(Exception e) // { // log.Error($"{e.Message}"); // //OnExceptionAction?.Invoke(e); // } // finally // { // if (log.IsDebugEnabled) // log.Debug($"{methodName}: exit"); // } //} // /// <summary> /// This function is called during the first call to RaiseEvent() in Start() because at that point, /// we are not handling a previous event. From then on, every event handler raises an event. /// </summary> public void HandlePendingEvents() { string methodName = System.Reflection.MethodBase.GetCurrentMethod().Name; if (log.IsDebugEnabled) { log.Debug($"{methodName}: enter"); } try { while (PendingEvents.Count > 0) { if (log.IsDebugEnabled) { log.Debug($"{methodName}: PendingEvents.Count={PendingEvents.Count}"); } FiniteStateEvent ev = null; lock (PendingEvents) { ev = PendingEvents.Dequeue(); } if (CurrentState.Transitions.TryGetValue(ev.Name, out FiniteStateTransition transition)) { if (log.IsDebugEnabled) { log.Debug($"{methodName}: transition {CurrentState.Name} -> {ev.Name} ->{transition.ToState.Name}"); } SetCurrentState(transition.ToState); if (log.IsDebugEnabled) { log.Debug($"{methodName}: before invoke, PendingEvents.Count={PendingEvents.Count}"); } CurrentState.OnEnterAction?.Invoke(); if (log.IsDebugEnabled) { log.Debug($"{methodName}: after invoke, PendingEvents.Count={PendingEvents.Count}"); } } else { throw new ArgumentException($"Failed to find a transition for event '{ev.Name}' from state '{CurrentState.Name}'."); } } } finally { if (log.IsDebugEnabled) { log.Debug($"{methodName}: exit"); } } }
/// <summary> /// Raises an event in the finite state machine. /// </summary> /// <param name="e">The instance of the event to raise</param> public void RaiseEvent(FiniteStateEvent e) { string methodName = System.Reflection.MethodBase.GetCurrentMethod().Name; if (log.IsDebugEnabled) { log.Debug($"{methodName}: enter"); } engine.RaiseEvent(e); if (log.IsDebugEnabled) { log.Debug($"{methodName}: exit"); } }
/// <summary> /// Raises an event in the finite state machine. /// </summary> /// <param name="e">The instance of the event to raise</param> public void RaiseEvent(FiniteStateEvent e) { string methodName = System.Reflection.MethodBase.GetCurrentMethod().Name; if (log.IsDebugEnabled) { log.Debug($"{methodName}: enter"); } try { if (log.IsInfoEnabled) { log.Info($"{methodName}: [{e.Name}]"); } // // Use a queue so that event handlers can raise events. // lock (PendingEvents) { PendingEvents.Enqueue(e); if (log.IsDebugEnabled) { log.Debug($"{methodName}: handlingEvent = {handlingEvent}"); } if (handlingEvent) { return; } handlingEvent = true; } HandlePendingEvents(); // eliminate race for the flag by using the same lock object lock (PendingEvents) { handlingEvent = false; } } finally { if (log.IsDebugEnabled) { log.Debug($"{methodName}: exit"); } } }
/// <summary> /// Starts the state machine. /// /// Every state diagram has a start state with a single transition to some initial state in the diagram via a start event. /// This method performs the following actions: /// * creates the start state /// * creates the start event /// * adds a transition from the start state, via the start event to InitialState, the finite state passed as a parameter /// * sets the current state to be the start state /// * raises the start event /// The OnEntry event handler for InitialState will be executed. /// The state machine is now sitting in it's initial state and waiting for an event to occur. /// /// On a technical note, the underlying Thread, in which the event handlers are run, is also created and started here. /// </summary> /// <param name="InitialState">The finite state in which this state machine starts.</param> /// <exception cref="ArgumentException">Thrown if the parameter InitialState is a finite state that has not been added by <see cref="AddState(FiniteState)"/></exception> public void Start(FiniteState InitialState) { string methodName = System.Reflection.MethodBase.GetCurrentMethod().Name; if (log.IsDebugEnabled) { log.Debug($"{methodName}: enter"); } //if (OnExceptionAction == null) //{ // throw new ArgumentException($"The state machine must have an OnExceptionAction before you can start it."); //} // // Perform sanity checks // foreach (KeyValuePair <string, FiniteState> kvp in ManagedStates) { // // 1. Make sure each finite state has an OnEnterAction // FiniteState finiteState = kvp.Value; if (finiteState.OnEnterAction == null) { throw new Exception($"The finite state, '{finiteState.Name}' does not have an OnEnterAction."); } // // 2. Make sure the target state in every transition has been added to the state machine. // foreach (KeyValuePair <string, FiniteStateTransition> transitionKvp in finiteState.Transitions) { FiniteStateTransition fst = transitionKvp.Value; string stateName = fst.ToState.Name; if (!ManagedStates.ContainsKey(stateName)) { throw new Exception($"The finite state, '{stateName}' has not been added to the state machine."); } } } FiniteState startState = new FiniteState(START_STATE_NAME); FiniteStateEvent startEvent = new FiniteStateEvent(START_EVENT_NAME); startState.OnEnterAction = () => { }; // needs an empty handler AddState(startState); startState.AddTransition(startEvent, InitialState); SetCurrentState(startState); RaiseEvent(startEvent); //Thread runner = new Thread(RunStateMachine) //{ // Name = Name //}; //runner.Start(); if (log.IsDebugEnabled) { log.Debug($"{methodName}: exit"); } }
/// <summary> /// Add a new transition to the collection of transitions from this state that is created from an event and a state. /// </summary> /// <param name="ViaEvent">The event that will trigger this transition.</param> /// <param name="ToState">The state that is the destination of the transition.</param> /// <exception cref="ArgumentException">Thrown if the event in TransitionToAdd has already been used in a transition from this state.</exception> public void AddTransition(FiniteStateEvent ViaEvent, FiniteState ToState) { AddTransition(new FiniteStateTransition(ViaEvent, ToState)); }
public void despatch(FiniteStateEvent e) { engine.RaiseEvent(e); }