using System; using System.Collections.Generic; using Wayne.Lib.Log; namespace Wayne.Lib.StateEngine { //[System.Diagnostics.DebuggerNonUserCode()] abstract class EventBufferingRootStateMachine : RootStateMachine { #region Fields private readonly List<StateEngineEvent> eventList = new List<StateEngineEvent>(); private readonly object eventQueueSyncObj = new object(); private readonly Queue<StateEngineEvent> resendQueue = new Queue<StateEngineEvent>(); private uint nextSequenceNumber; private readonly object nextSequenceNumberLock = new object(); private bool stateChanged; #endregion #region Constants private const uint sequenceNumberWindowSize = 200; private const uint sequenceNumberWindowStart = uint.MaxValue - sequenceNumberWindowSize / 2; private const uint sequenceNumberWindowEnd = uint.MinValue + sequenceNumberWindowSize / 2; #endregion #region Construction protected EventBufferingRootStateMachine(string name, IDebugLogger debugLogger, object logCategory) : base(name, debugLogger, logCategory) { } #endregion #region Protected Methods /// <summary> /// Recursively hook on the OnStateChanged for the state machine and all sub composite states. /// </summary> /// <param name="stateMachine"></param> protected internal void CreateOnStateChangeEventHandler(StateMachine stateMachine) { stateMachine.OnStateChanged += new EventHandler<StateChangedEventArgs>(StateChangedNotification); foreach (State state in stateMachine.CreatedStates) { CompositeState compositeState = state as CompositeState; if (compositeState != null) { CreateOnStateChangeEventHandler(compositeState.StateMachine); } } } /// <summary> /// Processes all queued events. /// </summary> protected internal void HandleQueuedEvents(ReentrancyMutex mutexToReleaseWhenEventProcessingIsDone) { int eventCount; lock (eventQueueSyncObj) { eventCount = eventList.Count; if ((eventCount == 0) && (mutexToReleaseWhenEventProcessingIsDone != null)) mutexToReleaseWhenEventProcessingIsDone.Release(); } if (eventCount > 0) { do { //Pick out the new event. StateEngineEvent stateEngineEvent; lock (eventQueueSyncObj) { stateEngineEvent = eventList[0]; eventList.RemoveAt(0); } //Reset the state changed flag. This flag is set in the OnStateChange event handler stateChanged = false; if (disposed) return; //Handle the event HandleEventInCurrentState(stateEngineEvent); //The state changed during the event handling. if (stateChanged) { lock (eventQueueSyncObj) { //Move all the events waiting for resend to the normal event queue. while (resendQueue.Count > 0) eventList.Add(resendQueue.Dequeue()); SortEventList(); } } lock (eventQueueSyncObj) { eventCount = eventList.Count; if ((eventCount == 0) && (mutexToReleaseWhenEventProcessingIsDone != null)) mutexToReleaseWhenEventProcessingIsDone.Release(); } } while ((eventCount > 0) && !disposed); } } /// <summary> /// Handle the event in the current state, and if the event is returned unhandled, it /// is put on the resend queue, to be sent to the next state we enter. /// </summary> /// <param name="stateEngineEvent"></param> protected override void HandleEventInCurrentState(StateEngineEvent stateEngineEvent) { base.HandleEventInCurrentState(stateEngineEvent); //If the event was not handled, it should be queued to be re-sent in the next state. if (!stateEngineEvent.Handled) { lock (eventQueueSyncObj) resendQueue.Enqueue(stateEngineEvent); } } #endregion /// <summary> /// Sorts the event list. /// </summary> private void SortEventList() { eventList.Sort(CompareEvents); } /// <summary> /// Compares two state engine event objects. /// </summary> /// <param name="ev1"></param> /// <param name="ev2"></param> /// <returns></returns> private static int CompareEvents(StateEngineEvent ev1, StateEngineEvent ev2) { if (ev1.Priority != ev2.Priority) return ev1.Priority.CompareTo(ev2.Priority); else { //Get the result through a normal comparison. int result = ev1.SequenceNumber.CompareTo(ev2.SequenceNumber); //Check, we leave a window for 200 events between uint.MaxValue and uint.MinValue if ((ev1.SequenceNumber >= sequenceNumberWindowStart) && (ev2.SequenceNumber <= sequenceNumberWindowEnd) || (ev2.SequenceNumber >= sequenceNumberWindowStart) && (ev1.SequenceNumber <= sequenceNumberWindowEnd)) { result *= -1; } return result; } } /// <summary> /// Generates a new sequence number for incoming events. /// </summary> /// <returns></returns> private uint GetNextEventSequenceNumber() { lock (nextSequenceNumberLock) { unchecked { return nextSequenceNumber++; } } } /// <summary> /// Event hanler for state changes. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void StateChangedNotification(object sender, StateChangedEventArgs e) { stateChanged = true; } /// <summary> /// Entry point for incoming events. Stores the event in the event list and sorts the list. Nothing more happens, it is up to descendant classes /// to add functionality on how to handle this. /// </summary> /// <param name="stateEngineEvent"></param> public override void IncomingEvent(StateEngineEvent stateEngineEvent) { if (!disposed) { if (stateEngineEvent.ArrivalTime != DateTime.MinValue) //The arrival time is always set to minvalue when it is created. //If it has another value, it has been added to another statemachine. throw new StateEngineException(string.Format("The event ({0}) has already been sent to another statemachine. It can not be sent to more than one.", stateEngineEvent.ToString())); stateEngineEvent.ArrivalTime = DateTime.Now; stateEngineEvent.SequenceNumber = GetNextEventSequenceNumber(); lock (eventQueueSyncObj) { //Add the event to the list. eventList.Add(stateEngineEvent); //Sort the event list. SortEventList(); } } } /// <summary> /// Removes all pending event that matches the supplied predicate. /// </summary> /// <typeparam name="TComparisonObject">Type of the comparison object</typeparam> /// <param name="predicate">The predicate that is used to match the event.</param> /// <param name="comparisonObject">The comparison object that is used in the StateEngineEventPredicate.</param> public override void RemovePendingEvents<TComparisonObject>(StateEngineEventPredicate<TComparisonObject> predicate, TComparisonObject comparisonObject) { lock (eventQueueSyncObj) { if ((debugLogger != null) && (debugLogger.IsActive(logCategory, DebugLogLevel.Maximized))) debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, "ClearPendingEvents using predicate {0} using comparisonObject {1}", predicate, comparisonObject), logCategory, DebugLogLevel.Maximized); //Remove the matching events in the resend queue. Queue<StateEngineEvent> tempQueue = new Queue<StateEngineEvent>(resendQueue); resendQueue.Clear(); while (tempQueue.Count > 0) { StateEngineEvent tempEvent = tempQueue.Dequeue(); if (!predicate(tempEvent, comparisonObject)) //Call the predicate delegate to evaluate the event. { resendQueue.Enqueue(tempEvent); //Put back the event in the queue } else { if ((debugLogger != null) && (debugLogger.IsActive(logCategory))) debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " Removed event [{0}] from resend queue", tempEvent), logCategory); } } //Remove the matching events in the event queue. List<int> indicesToRemove = new List<int>(); for (int i = 0; i < eventList.Count; i++) { if (predicate(eventList[i], comparisonObject)) { indicesToRemove.Add(i); } } foreach (int i in indicesToRemove) { if ((debugLogger != null) && (debugLogger.IsActive(logCategory))) debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " Removed event [{0}] from event list", eventList[i]), logCategory); eventList[i] = null; } while (eventList.Contains(null)) eventList.Remove(null); //Removes only one instance of null, we may have several. So continue remove until there is no more nulls. } } /// <summary> /// DO NOT USE for any isfs based event! /// Removes all pending events that matches the specified event type. .Equals method is /// used to compare. /// </summary> /// <param name="eventType"></param> public override void RemovePendingEventsOfType(object eventType) { RemovePendingEvents(MatchEventOnType, eventType); } /// <summary> /// Gets the pending envents from the StateMachine. /// </summary> /// <param name="eventType"></param> /// <returns>null or an IEnumberable</returns> public override IEnumerable<StateEngineEvent> GetPendingEventsOfType(object eventType) { return GetPendingEvents(MatchEventOnType, eventType); } /// <summary> /// Gets the pending StateEngineEvents of the type /// </summary> /// <typeparam name="TComparisonObject">Type of the comparison object</typeparam> /// <param name="predicate">The predicate that is used to match the event.</param> /// <param name="comparisonObject">The comparison object that is used in the StateEngineEventPredicate.</param> /// <returns>null or an IEnumerable</returns> public override IEnumerable<StateEngineEvent> GetPendingEvents<TComparisonObject>( StateEngineEventPredicate<TComparisonObject> predicate, TComparisonObject comparisonObject) { var result = new List<StateEngineEvent>(); lock (eventQueueSyncObj) { var tempQueue = new Queue<StateEngineEvent>(resendQueue); while (tempQueue.Count > 0) { var tempEvent = tempQueue.Dequeue(); if (predicate(tempEvent, comparisonObject)) //Call the predicate delegate to evaluate the event. { result.Add(tempEvent); } } } return result; } /// <summary> /// Predicate method that is used to make the event type comparison in the RemovePendingEventsOfType method. /// </summary> /// <param name="stateEngineEvent"></param> /// <param name="eventType"></param> /// <returns></returns> private static bool MatchEventOnType(StateEngineEvent stateEngineEvent, object eventType) { if (stateEngineEvent != null) return stateEngineEvent.Type.Equals(eventType); else return false; } /// <summary> /// Clear all pending events. /// </summary> internal override void ClearPendingEvents() { lock (eventQueueSyncObj) { if ((debugLogger != null) && (debugLogger.IsActive(logCategory))) { debugLogger.Add("ClearPendingEvents"); foreach (StateEngineEvent tempEvent in resendQueue) debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " Removed event [{0}] from resend queue ", tempEvent), logCategory); } resendQueue.Clear(); if ((debugLogger != null) && (debugLogger.IsActive(logCategory))) { foreach (StateEngineEvent tempEvent in eventList) debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " Removed event [{0}] from event list", tempEvent), logCategory); } eventList.Clear(); } } } }