StateTransitionLookup.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. namespace Wayne.Lib.StateEngine
  5. {
  6. public class StateLookupEntry
  7. {
  8. #region Fields
  9. public string NextStateFactoryName { get; internal set; }
  10. public HistoryType HistoryType { get; private set; }
  11. #endregion
  12. #region Construction
  13. public StateLookupEntry(string nextStateFactoryName, HistoryType historyType)
  14. {
  15. NextStateFactoryName = nextStateFactoryName;
  16. HistoryType = historyType;
  17. }
  18. #endregion
  19. }
  20. /// <summary>
  21. /// StateTransitionLookup is used by the State machine to find the state to
  22. /// change to when a transition occurs. It uses a dataset that contains the actual data.
  23. /// </summary>
  24. public class StateTransitionLookup
  25. {
  26. #region Fields
  27. private readonly StateTypeContainer stateTypeContainer;
  28. #endregion
  29. #region Construction
  30. /// <summary>
  31. /// Constructor
  32. /// </summary>
  33. public StateTransitionLookup(StateTypeContainer stateTypeContainer)
  34. {
  35. LookupTable = new Dictionary<string, Dictionary<object, StateLookupEntry>>();
  36. this.stateTypeContainer = stateTypeContainer;
  37. }
  38. public Dictionary<string, Dictionary<object, StateLookupEntry>> LookupTable { get; private set; }
  39. #endregion
  40. #region Method: GetNextState
  41. /// <summary>
  42. /// Performs a Lookup for the next state when in the source state, and
  43. /// are going to perform the specified transition
  44. /// </summary>
  45. /// <param name="sourceStateFactoryName">
  46. /// The state the state machine is in when performing transition
  47. /// </param>
  48. /// <param name="transition">
  49. /// The requested transition.
  50. /// </param>
  51. /// <returns>
  52. /// Returns the next state that should be entered based on the transition.
  53. /// </returns>
  54. public StateEntry GetNextState(string sourceStateFactoryName, Wayne.Lib.StateEngine.Transition transition)
  55. {
  56. if (transition == null)
  57. return null;
  58. StateLookupEntry stateLookupEntry;
  59. //Try look up with the current state name
  60. if (TryLookup(sourceStateFactoryName, transition.Type, out stateLookupEntry))
  61. {
  62. return new StateEntry(transition, stateLookupEntry.NextStateFactoryName, stateLookupEntry.HistoryType);
  63. }
  64. //If not matching with current state name, try lookup recursively with AnyState
  65. if (sourceStateFactoryName != AnyState.FactoryName) //Unless we are already in recursion.
  66. {
  67. return GetNextState(AnyState.FactoryName, transition);
  68. }
  69. return null;
  70. }
  71. public bool TryLookup(string sourceStateFactoryName, object transitionType, out StateLookupEntry stateLookupEntry)
  72. {
  73. stateLookupEntry = null;
  74. Dictionary<object, StateLookupEntry> lookup1;
  75. if (LookupTable.TryGetValue(sourceStateFactoryName, out lookup1))
  76. {
  77. return lookup1.TryGetValue(transitionType, out stateLookupEntry);
  78. }
  79. return false;
  80. }
  81. #endregion
  82. #region Method: AddTransition
  83. /// <summary>
  84. /// Internal add transition.
  85. /// </summary>
  86. /// <param name="sourceStateFactoryName"></param>
  87. /// <param name="transitionType"></param>
  88. /// <param name="nextStateType"></param>
  89. /// <param name="historyType"></param>
  90. /// <param name="replace"></param>
  91. private void InternalAddTransition(string sourceStateFactoryName, object transitionType, string nextStateType, HistoryType historyType, bool replace)
  92. {
  93. if (historyType == HistoryType.Explicit)
  94. throw new StateEngineException("Cannot configure a transition to have a explicit history type. It is only used internally when working with explicit transitions");
  95. var stateLookup = GetTransitionsForState(sourceStateFactoryName);
  96. var exists = stateLookup.ContainsKey(transitionType);
  97. if (exists && !replace)
  98. {
  99. throw new ArgumentException(string.Format("The State-Transition combination \"{0}-{1}\" has already been defined.", sourceStateFactoryName, Transition.GetTransitionName(transitionType)));
  100. }
  101. if (!exists && replace)
  102. {
  103. throw new ArgumentException(string.Format("The State-Transition combination \"{0}-{1}\" is not defined.", sourceStateFactoryName, Transition.GetTransitionName(transitionType)));
  104. }
  105. StateLookupEntry lookupEntry = new StateLookupEntry(nextStateType, historyType);
  106. stateLookup[transitionType] = lookupEntry;
  107. }
  108. /// <summary>
  109. /// Returns a transition dictionary for the specified state.
  110. /// </summary>
  111. /// <param name="sourceStateFactoryName"></param>
  112. /// <returns></returns>
  113. public Dictionary<object, StateLookupEntry> GetTransitionsForState(string sourceStateFactoryName)
  114. {
  115. Dictionary<object, StateLookupEntry> result;
  116. if (!LookupTable.TryGetValue(sourceStateFactoryName, out result))
  117. {
  118. LookupTable.Add(sourceStateFactoryName, result = new Dictionary<object, StateLookupEntry>());
  119. }
  120. return result;
  121. }
  122. /// <summary>
  123. /// Adds a transition from state of class TFromState to the class TToState, when the transitiontype
  124. /// is inserted.
  125. /// </summary>
  126. /// <typeparam name="TFromState">Class type of the source state.</typeparam>
  127. /// <typeparam name="TToState">Class type of target state.</typeparam>
  128. /// <param name="transitionType">Transition type.</param>
  129. public void AddTransition<TFromState, TToState>(object transitionType)
  130. where TFromState : State
  131. where TToState : State
  132. {
  133. stateTypeContainer.Register<TFromState>();
  134. stateTypeContainer.Register<TToState>();
  135. InternalAddTransition(typeof(TFromState).FullName, transitionType, typeof(TToState).FullName, HistoryType.None, false);
  136. }
  137. /// <summary>
  138. /// Adds a transition from state of class fromStateFactoryName to the class toStateFactoryName, when the transitiontype
  139. /// is inserted.
  140. /// </summary>
  141. /// <param name="fromStateFactoryName">String containig the factory name for the fromState.</param>
  142. /// <param name="toStateFactoryName">String containig the factory name for the toState.</param>
  143. /// <param name="transitionType">Transition type.</param>
  144. public void AddTransition(string fromStateFactoryName, string toStateFactoryName, object transitionType)
  145. {
  146. InternalAddTransition(fromStateFactoryName, transitionType, toStateFactoryName, HistoryType.None, false);
  147. }
  148. /// <summary>
  149. /// Adds a transition from state of class TFromState to the class TToState, when the transitiontype
  150. /// is inserted.
  151. /// </summary>
  152. /// <typeparam name="TFromState">Class type of the source state.</typeparam>
  153. /// <typeparam name="TToState">Class type of target state.</typeparam>
  154. /// <param name="transitionType">Transition type.</param>
  155. /// <param name="historyType"></param>
  156. public void AddTransition<TFromState, TToState>(object transitionType, HistoryType historyType)
  157. where TFromState : State
  158. where TToState : State
  159. {
  160. stateTypeContainer.Register<TFromState>();
  161. stateTypeContainer.Register<TToState>();
  162. InternalAddTransition(typeof(TFromState).FullName, transitionType, typeof(TToState).FullName, historyType, false);
  163. }
  164. #endregion
  165. #region Method: ReplaceTransition (Specifying both From and To state)
  166. /// <summary>
  167. /// Replaces the existing transition from a state and the given TransitionType, to another state.
  168. /// </summary>
  169. /// <typeparam name="TFromOldState">Class type of the source state.</typeparam>
  170. /// <typeparam name="TToNewState">Class type of new target state.</typeparam>
  171. /// <param name="transitionType">Transition type.</param>
  172. public void ReplaceTransition<TFromOldState, TToNewState>(object transitionType)
  173. where TFromOldState : State
  174. where TToNewState : State
  175. {
  176. stateTypeContainer.Register<TFromOldState>();
  177. stateTypeContainer.Register<TToNewState>();
  178. InternalAddTransition(typeof(TFromOldState).FullName, transitionType, typeof(TToNewState).FullName, HistoryType.None, true);
  179. }
  180. /// <summary>
  181. /// Replaces the existing transition from a state and the given TransitionType, to another state.
  182. /// </summary>
  183. /// <param name="fromOldStateFactoryName">String containig the factory name for the fromState.</param>
  184. /// <param name="toNewStateFactoryName">String containig the factory name for the new toState.</param>
  185. /// <param name="transitionType">Transition type.</param>
  186. public void ReplaceTransition(string fromOldStateFactoryName, string toNewStateFactoryName, object transitionType)
  187. {
  188. InternalAddTransition(fromOldStateFactoryName, transitionType, toNewStateFactoryName, HistoryType.None, true);
  189. }
  190. /// <summary>
  191. /// Replaces the existing transition from a state and the given TransitionType, to another state.
  192. /// </summary>
  193. /// <typeparam name="TFromOldState">Class type of the source state.</typeparam>
  194. /// <typeparam name="TToNewState">Class type of new target state.</typeparam>
  195. /// <param name="transitionType">Transition type.</param>
  196. /// <param name="historyType"></param>
  197. public void ReplaceTransition<TFromOldState, TToNewState>(object transitionType, HistoryType historyType)
  198. where TFromOldState : State
  199. where TToNewState : State
  200. {
  201. stateTypeContainer.Register<TFromOldState>();
  202. stateTypeContainer.Register<TToNewState>();
  203. InternalAddTransition(typeof(TFromOldState).FullName, transitionType, typeof(TToNewState).FullName, historyType, true);
  204. }
  205. #endregion
  206. #region Method: ReplaceTransitions (Specifying only To state)
  207. /// <summary>
  208. /// Replaces all the existing transitions to the given OldToState to the NewToState.
  209. /// </summary>
  210. /// <typeparam name="TOldToState">Class type of old target state.</typeparam>
  211. /// <typeparam name="TNewToState">Class type of new target state.</typeparam>
  212. public void ReplaceTransitions<TOldToState, TNewToState>()
  213. where TOldToState : State
  214. where TNewToState : State
  215. {
  216. stateTypeContainer.Register<TOldToState>();
  217. stateTypeContainer.Register<TNewToState>();
  218. ReplaceTransitions(typeof(TOldToState).FullName, typeof(TNewToState).FullName);
  219. }
  220. /// <summary>
  221. /// Replaces all the existing transitions to the given OldToState to the NewToState.
  222. /// </summary>
  223. /// <param name="oldToStateFactoryName">String containig the factory name for the old ToState.</param>
  224. /// <param name="newToStateFactoryName">String containig the factory name for the new ToState.</param>
  225. public void ReplaceTransitions(string oldToStateFactoryName, string newToStateFactoryName)
  226. {
  227. LookupTable.Values
  228. .SelectMany(x => x.Values)
  229. .Where(x => x.NextStateFactoryName == oldToStateFactoryName)
  230. .ForEach(x => x.NextStateFactoryName = newToStateFactoryName);
  231. }
  232. #endregion
  233. #region Method: IntersectTransition
  234. /// <summary>
  235. /// Puts a state in-between two states that has a transition between them.
  236. /// </summary>
  237. /// <typeparam name="TFromState"></typeparam>
  238. /// <typeparam name="TIntersectingState"></typeparam>
  239. /// <typeparam name="TOldToState"></typeparam>
  240. /// <param name="transitionType"></param>
  241. public void IntersectTransition<TFromState, TIntersectingState, TOldToState>(object transitionType)
  242. where TFromState : State
  243. where TIntersectingState : State
  244. where TOldToState : State
  245. {
  246. stateTypeContainer.Register<TFromState>();
  247. stateTypeContainer.Register<TIntersectingState>();
  248. stateTypeContainer.Register<TOldToState>();
  249. IntersectTransition<TFromState, TIntersectingState, TOldToState>(transitionType, transitionType);
  250. }
  251. /// <summary>
  252. /// Puts a state in-between two states that has a transition between them.
  253. /// </summary>
  254. /// <typeparam name="TFromState"></typeparam>
  255. /// <typeparam name="TIntersectingState"></typeparam>
  256. /// <typeparam name="TOldToState"></typeparam>
  257. /// <param name="fromTransitionType"></param>
  258. /// <param name="toTransitionType"></param>
  259. public void IntersectTransition<TFromState, TIntersectingState, TOldToState>(object fromTransitionType, object toTransitionType)
  260. where TFromState : State
  261. where TIntersectingState : State
  262. where TOldToState : State
  263. {
  264. stateTypeContainer.Register<TFromState>();
  265. stateTypeContainer.Register<TIntersectingState>();
  266. stateTypeContainer.Register<TOldToState>();
  267. StateLookupEntry dummy;
  268. if (!TryLookup(typeof(TFromState).FullName, fromTransitionType, out dummy))
  269. {
  270. throw new StateEngineException(string.Format("No previous Transition {0}, from {1} to {2} exists to intersect.", Transition.GetTransitionName(fromTransitionType), typeof(TFromState).FullName, typeof(TOldToState).FullName));
  271. }
  272. //If the Transition TFromState to TOldState with toTransitionType doesn't exist throw an Error.
  273. ReplaceTransition<TFromState, TIntersectingState>(fromTransitionType);
  274. AddTransition<TIntersectingState, TOldToState>(toTransitionType);
  275. }
  276. #endregion
  277. #region Method: ReplaceState
  278. /// <summary>
  279. /// Replaces all the existing transitions to the given OldToState to the NewToState.
  280. /// Also adds the transitions from the OldToState to also from the NewToState.
  281. /// </summary>
  282. /// <typeparam name="TOldToState">Class type of old target state.</typeparam>
  283. /// <typeparam name="TNewToState">Class type of new target state.</typeparam>
  284. public void ReplaceState<TOldToState, TNewToState>()
  285. where TOldToState : State
  286. where TNewToState : State
  287. {
  288. stateTypeContainer.Register<TOldToState>();
  289. stateTypeContainer.Register<TNewToState>();
  290. ReplaceState(typeof(TOldToState).FullName, typeof(TNewToState).FullName, false);
  291. }
  292. /// <summary>
  293. /// Replaces all the existing transitions to the given OldToState to the NewToState.
  294. /// Also adds the transitions from the OldToState to also from the NewToState.
  295. /// </summary>
  296. /// <param name="removeAllTransitionsFromState">Default=false. Should all existing transitions *From* this state be removed?</param>
  297. /// <typeparam name="TOldToState">Class type of old target state.</typeparam>
  298. /// <typeparam name="TNewToState">Class type of new target state.</typeparam>
  299. public void ReplaceState<TOldToState, TNewToState>(bool removeAllTransitionsFromState)
  300. where TOldToState : State
  301. where TNewToState : State
  302. {
  303. stateTypeContainer.Register<TOldToState>();
  304. stateTypeContainer.Register<TNewToState>();
  305. ReplaceState(typeof(TOldToState).FullName, typeof(TNewToState).FullName, removeAllTransitionsFromState);
  306. }
  307. /// <summary>
  308. /// Replaces all the existing transitions to the given OldToState to the NewToState.
  309. /// Also adds the transitions from the OldToState to also from the NewToState.
  310. /// </summary>
  311. /// <param name="oldToStateFactoryName">String containig the factory name for the old ToState.</param>
  312. /// <param name="newToStateFactoryName">String containig the factory name for the new ToState.</param>
  313. public void ReplaceState(string oldToStateFactoryName, string newToStateFactoryName)
  314. {
  315. ReplaceState(oldToStateFactoryName, newToStateFactoryName, false);
  316. }
  317. /// <summary>
  318. /// Replaces all the existing transitions to the given OldToState to the NewToState.
  319. /// Also adds the transitions from the OldToState to also from the NewToState.
  320. /// Note: 'OverrideState' also exists, to be used e.g. when replacing state that transists to itself
  321. /// </summary>
  322. /// <param name="oldToStateFactoryName">The factory name for the old ToState.</param>
  323. /// <param name="newToStateFactoryName">The factory name for the new ToState.</param>
  324. /// <param name="removeAllTransitionsFromState">Default=false. Should all existing transitions *From* this state be removed?</param>
  325. public void ReplaceState(string oldToStateFactoryName, string newToStateFactoryName, bool removeAllTransitionsFromState)
  326. {
  327. var transitionsForState = GetTransitionsForState(oldToStateFactoryName);
  328. //Get a list of the self-ref transitions
  329. var selfRefTransitions = transitionsForState
  330. .Where(x => x.Value.NextStateFactoryName == oldToStateFactoryName)
  331. .ToArray();
  332. //Update all transitions going FROM the old state
  333. var transitions = transitionsForState;
  334. LookupTable.Remove(oldToStateFactoryName);
  335. if (!removeAllTransitionsFromState)
  336. {
  337. LookupTable.Add(newToStateFactoryName, transitions);
  338. }
  339. //Update all transitions going TO the old state.
  340. LookupTable.Values
  341. .SelectMany(x => x.Values)
  342. .Where(x => x.NextStateFactoryName == oldToStateFactoryName)
  343. .ForEach(x => x.NextStateFactoryName = newToStateFactoryName);
  344. //Warning :
  345. //Add super-weird old broken self links for backwards compatibility
  346. selfRefTransitions.ForEach(t =>
  347. {
  348. transitionsForState.Remove(t.Key);
  349. InternalAddTransition(oldToStateFactoryName, t.Key, newToStateFactoryName, t.Value.HistoryType, false);
  350. });
  351. }
  352. #endregion
  353. #region Method: OverrideState
  354. /// <summary>
  355. /// Makes an override of a state.
  356. /// This could be used when you have made an inheritance of a state and want to replace the old one.
  357. /// </summary>
  358. /// <typeparam name="TOldState">The state to override.</typeparam>
  359. /// <typeparam name="TNewState">The overrider.</typeparam>
  360. public void OverrideState<TOldState, TNewState>()
  361. where TOldState : State
  362. where TNewState : State
  363. {
  364. stateTypeContainer.Register<TOldState>();
  365. stateTypeContainer.Register<TNewState>();
  366. string oldToStateFactoryName = typeof(TOldState).FullName;
  367. string newToStateFactoryName = typeof(TNewState).FullName;
  368. OverrideState(oldToStateFactoryName, newToStateFactoryName);
  369. }
  370. /// <summary>
  371. /// Makes an override of a state.
  372. /// This could be used when you have made an inheritance of a state and want to replace the old one.
  373. /// </summary>
  374. /// <param name="oldToStateFactoryName"></param>
  375. /// <param name="newToStateFactoryName"></param>
  376. public void OverrideState(string oldToStateFactoryName, string newToStateFactoryName)
  377. {
  378. //Update all transitions going FROM the old state
  379. var transitions = GetTransitionsForState(oldToStateFactoryName);
  380. LookupTable.Remove(oldToStateFactoryName);
  381. LookupTable.Add(newToStateFactoryName, transitions);
  382. //Update all transitions going TO the old state.
  383. LookupTable.Values
  384. .SelectMany(x => x.Values)
  385. .Where(x => x.NextStateFactoryName == oldToStateFactoryName)
  386. .ForEach(x => x.NextStateFactoryName = newToStateFactoryName);
  387. }
  388. #endregion
  389. #region Methods: GetLists
  390. /// <summary>
  391. /// Returns a list of state factory names that is in the lookup table.
  392. /// </summary>
  393. /// <param name="includeAnyStates">Should the AnyStates be included?</param>
  394. public string[] GetStateNameList(bool includeAnyStates)
  395. {
  396. var stateNames = LookupTable.Keys
  397. .Concat(LookupTable
  398. .Values
  399. .SelectMany(dict => dict.Values.Select(entry => entry.NextStateFactoryName)))
  400. .Distinct();
  401. if (!includeAnyStates)
  402. {
  403. stateNames = stateNames.Where(x => x != AnyState.FactoryName);
  404. }
  405. return stateNames.ToArray();
  406. }
  407. #endregion
  408. }
  409. }