EventBufferingRootStateMachine.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. using System;
  2. using System.Collections.Generic;
  3. using Wayne.Lib.Log;
  4. namespace Wayne.Lib.StateEngine
  5. {
  6. //[System.Diagnostics.DebuggerNonUserCode()]
  7. abstract class EventBufferingRootStateMachine : RootStateMachine
  8. {
  9. #region Fields
  10. private readonly List<StateEngineEvent> eventList = new List<StateEngineEvent>();
  11. private readonly object eventQueueSyncObj = new object();
  12. private readonly Queue<StateEngineEvent> resendQueue = new Queue<StateEngineEvent>();
  13. private uint nextSequenceNumber;
  14. private readonly object nextSequenceNumberLock = new object();
  15. private bool stateChanged;
  16. #endregion
  17. #region Constants
  18. private const uint sequenceNumberWindowSize = 200;
  19. private const uint sequenceNumberWindowStart = uint.MaxValue - sequenceNumberWindowSize / 2;
  20. private const uint sequenceNumberWindowEnd = uint.MinValue + sequenceNumberWindowSize / 2;
  21. #endregion
  22. #region Construction
  23. protected EventBufferingRootStateMachine(string name, IDebugLogger debugLogger, object logCategory)
  24. : base(name, debugLogger, logCategory)
  25. {
  26. }
  27. #endregion
  28. #region Protected Methods
  29. /// <summary>
  30. /// Recursively hook on the OnStateChanged for the state machine and all sub composite states.
  31. /// </summary>
  32. /// <param name="stateMachine"></param>
  33. protected internal void CreateOnStateChangeEventHandler(StateMachine stateMachine)
  34. {
  35. stateMachine.OnStateChanged += new EventHandler<StateChangedEventArgs>(StateChangedNotification);
  36. foreach (State state in stateMachine.CreatedStates)
  37. {
  38. CompositeState compositeState = state as CompositeState;
  39. if (compositeState != null)
  40. {
  41. CreateOnStateChangeEventHandler(compositeState.StateMachine);
  42. }
  43. }
  44. }
  45. /// <summary>
  46. /// Processes all queued events.
  47. /// </summary>
  48. protected internal void HandleQueuedEvents(ReentrancyMutex mutexToReleaseWhenEventProcessingIsDone)
  49. {
  50. int eventCount;
  51. lock (eventQueueSyncObj)
  52. {
  53. eventCount = eventList.Count;
  54. if ((eventCount == 0) && (mutexToReleaseWhenEventProcessingIsDone != null))
  55. mutexToReleaseWhenEventProcessingIsDone.Release();
  56. }
  57. if (eventCount > 0)
  58. {
  59. do
  60. {
  61. //Pick out the new event.
  62. StateEngineEvent stateEngineEvent;
  63. lock (eventQueueSyncObj)
  64. {
  65. stateEngineEvent = eventList[0];
  66. eventList.RemoveAt(0);
  67. }
  68. //Reset the state changed flag. This flag is set in the OnStateChange event handler
  69. stateChanged = false;
  70. if (disposed)
  71. return;
  72. //Handle the event
  73. HandleEventInCurrentState(stateEngineEvent);
  74. //The state changed during the event handling.
  75. if (stateChanged)
  76. {
  77. lock (eventQueueSyncObj)
  78. {
  79. //Move all the events waiting for resend to the normal event queue.
  80. while (resendQueue.Count > 0)
  81. eventList.Add(resendQueue.Dequeue());
  82. SortEventList();
  83. }
  84. }
  85. lock (eventQueueSyncObj)
  86. {
  87. eventCount = eventList.Count;
  88. if ((eventCount == 0) && (mutexToReleaseWhenEventProcessingIsDone != null))
  89. mutexToReleaseWhenEventProcessingIsDone.Release();
  90. }
  91. } while ((eventCount > 0) && !disposed);
  92. }
  93. }
  94. /// <summary>
  95. /// Handle the event in the current state, and if the event is returned unhandled, it
  96. /// is put on the resend queue, to be sent to the next state we enter.
  97. /// </summary>
  98. /// <param name="stateEngineEvent"></param>
  99. protected override void HandleEventInCurrentState(StateEngineEvent stateEngineEvent)
  100. {
  101. base.HandleEventInCurrentState(stateEngineEvent);
  102. //If the event was not handled, it should be queued to be re-sent in the next state.
  103. if (!stateEngineEvent.Handled)
  104. {
  105. lock (eventQueueSyncObj)
  106. resendQueue.Enqueue(stateEngineEvent);
  107. }
  108. }
  109. #endregion
  110. /// <summary>
  111. /// Sorts the event list.
  112. /// </summary>
  113. private void SortEventList()
  114. {
  115. eventList.Sort(CompareEvents);
  116. }
  117. /// <summary>
  118. /// Compares two state engine event objects.
  119. /// </summary>
  120. /// <param name="ev1"></param>
  121. /// <param name="ev2"></param>
  122. /// <returns></returns>
  123. private static int CompareEvents(StateEngineEvent ev1, StateEngineEvent ev2)
  124. {
  125. if (ev1.Priority != ev2.Priority)
  126. return ev1.Priority.CompareTo(ev2.Priority);
  127. else
  128. {
  129. //Get the result through a normal comparison.
  130. int result = ev1.SequenceNumber.CompareTo(ev2.SequenceNumber);
  131. //Check, we leave a window for 200 events between uint.MaxValue and uint.MinValue
  132. if ((ev1.SequenceNumber >= sequenceNumberWindowStart) && (ev2.SequenceNumber <= sequenceNumberWindowEnd) ||
  133. (ev2.SequenceNumber >= sequenceNumberWindowStart) && (ev1.SequenceNumber <= sequenceNumberWindowEnd))
  134. {
  135. result *= -1;
  136. }
  137. return result;
  138. }
  139. }
  140. /// <summary>
  141. /// Generates a new sequence number for incoming events.
  142. /// </summary>
  143. /// <returns></returns>
  144. private uint GetNextEventSequenceNumber()
  145. {
  146. lock (nextSequenceNumberLock)
  147. {
  148. unchecked
  149. {
  150. return nextSequenceNumber++;
  151. }
  152. }
  153. }
  154. /// <summary>
  155. /// Event hanler for state changes.
  156. /// </summary>
  157. /// <param name="sender"></param>
  158. /// <param name="e"></param>
  159. void StateChangedNotification(object sender, StateChangedEventArgs e)
  160. {
  161. stateChanged = true;
  162. }
  163. /// <summary>
  164. /// 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
  165. /// to add functionality on how to handle this.
  166. /// </summary>
  167. /// <param name="stateEngineEvent"></param>
  168. public override void IncomingEvent(StateEngineEvent stateEngineEvent)
  169. {
  170. if (!disposed)
  171. {
  172. if (stateEngineEvent.ArrivalTime != DateTime.MinValue) //The arrival time is always set to minvalue when it is created.
  173. //If it has another value, it has been added to another statemachine.
  174. 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()));
  175. stateEngineEvent.ArrivalTime = DateTime.Now;
  176. stateEngineEvent.SequenceNumber = GetNextEventSequenceNumber();
  177. lock (eventQueueSyncObj)
  178. {
  179. //Add the event to the list.
  180. eventList.Add(stateEngineEvent);
  181. //Sort the event list.
  182. SortEventList();
  183. }
  184. }
  185. }
  186. /// <summary>
  187. /// Removes all pending event that matches the supplied predicate.
  188. /// </summary>
  189. /// <typeparam name="TComparisonObject">Type of the comparison object</typeparam>
  190. /// <param name="predicate">The predicate that is used to match the event.</param>
  191. /// <param name="comparisonObject">The comparison object that is used in the StateEngineEventPredicate.</param>
  192. public override void RemovePendingEvents<TComparisonObject>(StateEngineEventPredicate<TComparisonObject> predicate, TComparisonObject comparisonObject)
  193. {
  194. lock (eventQueueSyncObj)
  195. {
  196. if ((debugLogger != null) && (debugLogger.IsActive(logCategory, DebugLogLevel.Maximized)))
  197. debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, "ClearPendingEvents using predicate {0} using comparisonObject {1}", predicate, comparisonObject), logCategory, DebugLogLevel.Maximized);
  198. //Remove the matching events in the resend queue.
  199. Queue<StateEngineEvent> tempQueue = new Queue<StateEngineEvent>(resendQueue);
  200. resendQueue.Clear();
  201. while (tempQueue.Count > 0)
  202. {
  203. StateEngineEvent tempEvent = tempQueue.Dequeue();
  204. if (!predicate(tempEvent, comparisonObject)) //Call the predicate delegate to evaluate the event.
  205. {
  206. resendQueue.Enqueue(tempEvent); //Put back the event in the queue
  207. }
  208. else
  209. {
  210. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  211. debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " Removed event [{0}] from resend queue", tempEvent), logCategory);
  212. }
  213. }
  214. //Remove the matching events in the event queue.
  215. List<int> indicesToRemove = new List<int>();
  216. for (int i = 0; i < eventList.Count; i++)
  217. {
  218. if (predicate(eventList[i], comparisonObject))
  219. {
  220. indicesToRemove.Add(i);
  221. }
  222. }
  223. foreach (int i in indicesToRemove)
  224. {
  225. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  226. debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " Removed event [{0}] from event list", eventList[i]), logCategory);
  227. eventList[i] = null;
  228. }
  229. while (eventList.Contains(null))
  230. eventList.Remove(null); //Removes only one instance of null, we may have several. So continue remove until there is no more nulls.
  231. }
  232. }
  233. /// <summary>
  234. /// DO NOT USE for any isfs based event!
  235. /// Removes all pending events that matches the specified event type. .Equals method is
  236. /// used to compare.
  237. /// </summary>
  238. /// <param name="eventType"></param>
  239. public override void RemovePendingEventsOfType(object eventType)
  240. {
  241. RemovePendingEvents(MatchEventOnType, eventType);
  242. }
  243. /// <summary>
  244. /// Gets the pending envents from the StateMachine.
  245. /// </summary>
  246. /// <param name="eventType"></param>
  247. /// <returns>null or an IEnumberable</returns>
  248. public override IEnumerable<StateEngineEvent> GetPendingEventsOfType(object eventType)
  249. {
  250. return GetPendingEvents(MatchEventOnType, eventType);
  251. }
  252. /// <summary>
  253. /// Gets the pending StateEngineEvents of the type
  254. /// </summary>
  255. /// <typeparam name="TComparisonObject">Type of the comparison object</typeparam>
  256. /// <param name="predicate">The predicate that is used to match the event.</param>
  257. /// <param name="comparisonObject">The comparison object that is used in the StateEngineEventPredicate.</param>
  258. /// <returns>null or an IEnumerable</returns>
  259. public override IEnumerable<StateEngineEvent> GetPendingEvents<TComparisonObject>(
  260. StateEngineEventPredicate<TComparisonObject> predicate, TComparisonObject comparisonObject)
  261. {
  262. var result = new List<StateEngineEvent>();
  263. lock (eventQueueSyncObj)
  264. {
  265. var tempQueue = new Queue<StateEngineEvent>(resendQueue);
  266. while (tempQueue.Count > 0)
  267. {
  268. var tempEvent = tempQueue.Dequeue();
  269. if (predicate(tempEvent, comparisonObject)) //Call the predicate delegate to evaluate the event.
  270. {
  271. result.Add(tempEvent);
  272. }
  273. }
  274. }
  275. return result;
  276. }
  277. /// <summary>
  278. /// Predicate method that is used to make the event type comparison in the RemovePendingEventsOfType method.
  279. /// </summary>
  280. /// <param name="stateEngineEvent"></param>
  281. /// <param name="eventType"></param>
  282. /// <returns></returns>
  283. private static bool MatchEventOnType(StateEngineEvent stateEngineEvent, object eventType)
  284. {
  285. if (stateEngineEvent != null)
  286. return stateEngineEvent.Type.Equals(eventType);
  287. else
  288. return false;
  289. }
  290. /// <summary>
  291. /// Clear all pending events.
  292. /// </summary>
  293. internal override void ClearPendingEvents()
  294. {
  295. lock (eventQueueSyncObj)
  296. {
  297. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  298. {
  299. debugLogger.Add("ClearPendingEvents");
  300. foreach (StateEngineEvent tempEvent in resendQueue)
  301. debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " Removed event [{0}] from resend queue ", tempEvent), logCategory);
  302. }
  303. resendQueue.Clear();
  304. if ((debugLogger != null) && (debugLogger.IsActive(logCategory)))
  305. {
  306. foreach (StateEngineEvent tempEvent in eventList)
  307. debugLogger.Add(string.Format(System.Globalization.CultureInfo.InvariantCulture, " Removed event [{0}] from event list", tempEvent), logCategory);
  308. }
  309. eventList.Clear();
  310. }
  311. }
  312. }
  313. }