/// <summary> /// Enters the state. /// </summary> /// <param name="context">The event arguments pertaining to the transition into the state.</param> /// <param name="useHistory">Used by history states to reconstitute state history.</param> /// <remarks> /// This method is for use only by the North State Framework's internal logic. /// </remarks> protected internal virtual void enter(NSFStateMachineContext context, bool useHistory) { active = true; if (parentRegion != null) { parentRegion.setActiveSubstate(this); if (!parentRegion.isActive()) { parentRegion.enter(context, false); } } if (TopStateMachine.LoggingEnabled && LogEntry) { NSFTraceLog.PrimaryTraceLog.addTrace(NSFTraceTags.StateEnteredTag, NSFTraceTags.StateMachineTag, TopStateMachine.Name, NSFTraceTags.StateTag, Name); } // Update context to indicate entering this state context.EnteringState = this; context.ExitingState = null; EntryActions.execute(context); NSFStateMachine parentStateMachine = ParentStateMachine; if (parentStateMachine != null) { parentStateMachine.executeStateChangeActions(context); } }
/// <summary> /// Handles exceptions caught by main event processing loop. /// </summary> /// <param name="exception">The exception caught.</param> protected void handleException(Exception exception) { NSFExceptionContext newContext = new NSFExceptionContext(this, new Exception(Name + " thread exception", exception)); ExceptionActions.execute(newContext); NSFExceptionHandler.handleException(newContext); }
/// <summary> /// Handles exceptions raised during state machine processing. /// </summary> /// <param name="exception">The exception thrown.</param> /// <remarks> /// By default, any exception actions are executed first, /// then the exception is forwarded to the global exception handler, NSFExceptionHandler.handleException(). /// </remarks> protected internal void handleException(Exception exception) { NSFExceptionContext newContext = new NSFExceptionContext(this, new Exception(Name + " state machine exception", exception)); ExceptionActions.execute(newContext); NSFExceptionHandler.handleException(newContext); }
/// <summary> /// Event handler action that executes the scheduled action. /// </summary> /// <param name="context">Additional contextual information.</param> private void executeActions(NSFEventContext context) { if ((Name != null) && (Name != String.Empty)) { NSFTraceLog.PrimaryTraceLog.addTrace(NSFTraceTags.ActionExecutedTag, NSFTraceTags.ActionTag, Name); } Actions.execute(context); }
/// <summary> /// Adds the specified event to the state machine's event queue. /// </summary> /// <param name="nsfEvent">The event to queue.</param> /// <param name="isPriorityEvent">Flag indicating if the event should be queued to the back of the queue (false) or the front of the queue (true).</param> /// <param name="logEventQueued">Flag indicating if an event queued trace should be added to the trace log.</param> private void queueEvent(NSFEvent nsfEvent, bool isPriorityEvent, bool logEventQueued) { if (!isTopStateMachine()) { TopStateMachine.queueEvent(nsfEvent, isPriorityEvent, logEventQueued); return; } lock (stateMachineMutex) { // Do not allow events to be queued if terminating or terminated, // except for run to completion event, which may be queued if terminating to allow proper semantics to continue until terminated. if ((TerminationStatus == NSFEventHandlerTerminationStatus.EventHandlerTerminated) || ((TerminationStatus == NSFEventHandlerTerminationStatus.EventHandlerTerminating) && (nsfEvent != runToCompletionEvent))) { return; } // Handle special case of terminate event by setting status and queuing a single terminate event. // Terminate event must be the last event queued to guarantee safe deletion when it is handled. if (nsfEvent == terminateEvent) { if (TerminationStatus == NSFEventHandlerTerminationStatus.EventHandlerReady) { TerminationStatus = NSFEventHandlerTerminationStatus.EventHandlerTerminating; } } // Event limit detection looks for too many events queued for the state machine. // If more than the specified number of events are queued, the state machine will remove // its queued events, call the event limit actions, and stop after executing any events queued // by the limit actions. if ((EventLimitDetectionEnabled) && (QueuedEvents == EventLimit)) { EventThread.removeEventsFor(this); QueuedEvents = 0; EventLimitActions.execute(new NSFStateMachineContext(this, null, null, null, nsfEvent)); // Stop the state machine so that no more event processing occurs until started again stopStateMachine(); NSFTraceLog.PrimaryTraceLog.addTrace(NSFTraceTags.ErrorTag, NSFTraceTags.SourceTag, Name, NSFTraceTags.MessageTag, "EventLimit"); return; } nsfEvent.Destination = this; EventThread.queueEvent(nsfEvent, isPriorityEvent, logEventQueued); ++QueuedEvents; } }
/// <summary> /// Executes the actions in the state change actions list. /// </summary> internal void executeStateChangeActions(NSFStateMachineContext context) { StateChangeActions.execute(context); NSFStateMachine parentStateMachine = ParentStateMachine; if (parentStateMachine != null) { parentStateMachine.executeStateChangeActions(context); } }
/// <summary> /// Global exception handling method. /// </summary> /// <param name="context">Additional contextual information.</param> /// <remarks> /// All exceptions are ultimately routed to this method. It logs the exception to the trace log, /// saves the trace log if a file name has been set, and executes any exception actions. /// </remarks> public static void handleException(NSFExceptionContext context) { try { NSFTraceLog.PrimaryTraceLog.addTrace(NSFTraceTags.ExceptionTag, NSFTraceTags.MessageTag, context.Exception.ToString()); } catch (Exception) { // Nothing to do } ExceptionActions.execute(context); }
/// <summary> /// Exits the state. /// </summary> /// <param name="context">The event arguments pertaining to the transition out of the state.</param> /// <remarks> /// This method is for use only by the North State Framework's internal logic. /// </remarks> protected internal virtual void exit(NSFStateMachineContext context) { active = false; // Update context to indicate exiting this state context.ExitingState = this; context.EnteringState = null; ExitActions.execute(context); if (parentRegion != null) { parentRegion.setActiveSubstate(NSFState.NullState); } }
/// <summary> /// Implements the main timer processing loop. /// </summary> protected override void threadLoop() { NSFTime currentTime = CurrentTime; List <NSFTimerAction> readyActions = new List <NSFTimerAction>(); while (true) { // Set up next timeout lock (threadMutex) { if (actions.Count != 0) { timer.setNextTimeout(actions.First.Value.ExecutionTime); } else { timer.setNextTimeout(Int32.MaxValue); } } timer.waitForNextTimeout(); // Clean up and return if terminating if (TerminationStatus != NSFThreadTerminationStatus.ThreadReady) { actions.Clear(); return; } currentTime = CurrentTime; lock (threadMutex) { // Create list of actions ready to execute foreach (NSFTimerAction action in actions) { if (action.ExecutionTime <= currentTime) { readyActions.Add(action); } else { break; } } // Reschedule repetitive actions foreach (NSFTimerAction action in readyActions) { if (action.RepeatTime != 0) { action.ExecutionTime = action.ExecutionTime + action.RepeatTime; insertAction(action); } else { actions.Remove(action); } } } // Check for excessive gap between current time and execution time if (readyActions.Count != 0) { NSFTime timeGap = currentTime - readyActions[0].ExecutionTime; if ((MaxAllowableTimeGap > 0) && (timeGap > MaxAllowableTimeGap) && (currentTime > nextTimeGapInterval)) { NSFTraceLog.PrimaryTraceLog.addTrace(NSFTraceTags.ErrorTag, NSFTraceTags.SourceTag, Name, NSFTraceTags.MessageTag, "TimeGap", NSFTraceTags.ValueTag, timeGap.ToString()); TimeGapActions.execute(new NSFContext(this)); // Set time when next time gap can be recorded // This prevents all gapped timer events from recording time gap trace nextTimeGapInterval = currentTime + MaxAllowableTimeGap; } if (timeGap > MaxObservedTimeGap) { MaxObservedTimeGap = timeGap; } } // Execute all ready actions while (readyActions.Count != 0) { executeAction(readyActions[0]); readyActions.RemoveAt(0); } } }
/// <summary> /// Handles an event. /// </summary> /// <param name="nsfEvent">The event to handle.</param> /// <returns>Status indicating if the event was handled or not.</returns> /// <remarks> /// This method is for use only by the North State Framework's internal logic. /// It processes the event using UML defined behavior, including run to completion. /// </remarks> public NSFEventStatus handleEvent(NSFEvent nsfEvent) { lock (stateMachineMutex) { --QueuedEvents; // This should only happen if events are queued without using the queueEvent method if (QueuedEvents < 0) { QueuedEvents = 0; } } // Handle status changing events if ((nsfEvent == startEvent)) { RunStatus = NSFEventHandlerRunStatus.EventHandlerStarted; } else if (nsfEvent == stopEvent) { RunStatus = NSFEventHandlerRunStatus.EventHandlerStopped; } else if (nsfEvent == terminateEvent) { TerminationStatus = NSFEventHandlerTerminationStatus.EventHandlerTerminated; EventThread.removeEventHandler(this); return(NSFEventStatus.NSFEventHandled); } else if (nsfEvent == resetEvent) { reset(); } // Don't process events if stopped if (RunStatus == NSFEventHandlerRunStatus.EventHandlerStopped) { return(NSFEventStatus.NSFEventUnhandled); } // If not already active, enter state machine at the root if (!active) { enter(new NSFStateMachineContext(this, this, null, null, startEvent), false); } // Process the event NSFEventStatus eventStatus = NSFEventStatus.NSFEventUnhandled; try { eventStatus = processEvent(nsfEvent); if (eventStatus == NSFEventStatus.NSFEventHandled) { runToCompletion(); } // Consecutive loop detection looks for too many events without the state machine pausing. // If more than the specified number of transitions occur without a pause, the state machine will remove // its queued events, call the consecutive loop limit actions, and stop after executing any events queued // by the actions. if (ConsecutiveLoopDetectionEnabled) { ++consecutiveLoopCount; if (consecutiveLoopCount == ConsecutiveLoopLimit) { lock (stateMachineMutex) { EventThread.removeEventsFor(this); QueuedEvents = 0; } ConsecutiveLoopLimitActions.execute(new NSFStateMachineContext(this, null, null, null, nsfEvent)); // Stop the state machine so that no more event processing occurs until started again stopStateMachine(); // Reset consecutive loop count in case state machine is started again consecutiveLoopCount = 0; NSFTraceLog.PrimaryTraceLog.addTrace(NSFTraceTags.ErrorTag, NSFTraceTags.SourceTag, Name, NSFTraceTags.MessageTag, "ConsecutiveLoopLimit"); } else if (QueuedEvents == 0) { // If no events are queued for this state machine, then it has paused, indicating it's not in an infinite loop. consecutiveLoopCount = 0; } } } catch (Exception exception) { handleException(new Exception(nsfEvent.Name + " event handling exception", exception)); } return(eventStatus); }