StateMachine2.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. namespace Animancer.FSM
  7. {
  8. /// <summary>Interface for accessing <see cref="StateMachine{TKey, TState}"/> without the <c>TState</c>.</summary>
  9. /// <remarks>
  10. /// <strong>Documentation:</strong>
  11. /// <see href="https://kybernetik.com.au/animancer/docs/manual/fsm/keys">
  12. /// Keyed State Machines</see>
  13. /// </remarks>
  14. /// https://kybernetik.com.au/animancer/api/Animancer.FSM/IKeyedStateMachine_1
  15. ///
  16. public interface IKeyedStateMachine<TKey>
  17. {
  18. /************************************************************************************************************************/
  19. /// <summary>The key which identifies the <see cref="StateMachine{TState}.CurrentState"/>.</summary>
  20. TKey CurrentKey { get; }
  21. /// <summary>The <see cref="KeyChange{TKey}.PreviousKey"/>.</summary>
  22. TKey PreviousKey { get; }
  23. /// <summary>The <see cref="KeyChange{TKey}.NextKey"/>.</summary>
  24. TKey NextKey { get; }
  25. /// <summary>Attempts to enter the state registered with the specified `key` and returns it if successful.</summary>
  26. /// <remarks>
  27. /// This method returns true immediately if the specified `key` is already the <see cref="CurrentKey"/>. To
  28. /// allow directly re-entering the same state, use <see cref="TryResetState(TKey)"/> instead.
  29. /// </remarks>
  30. object TrySetState(TKey key);
  31. /// <summary>Attempts to enter the state registered with the specified `key` and returns it if successful.</summary>
  32. /// <remarks>
  33. /// This method does not check if the `key` is already the <see cref="CurrentKey"/>. To do so, use
  34. /// <see cref="TrySetState(TKey)"/> instead.
  35. /// </remarks>
  36. object TryResetState(TKey key);
  37. /// <summary>
  38. /// Uses <see cref="StateMachine{TKey, TState}.ForceSetState(TKey, TState)"/> to change to the state registered
  39. /// with the `key`. If nothing is registered, it changes to <c>default(TState)</c>.
  40. /// </summary>
  41. object ForceSetState(TKey key);
  42. /************************************************************************************************************************/
  43. }
  44. /// <summary>A simple Finite State Machine system that registers each state with a particular key.</summary>
  45. /// <remarks>
  46. /// This class allows states to be registered with a particular key upfront and then accessed later using that key.
  47. /// See <see cref="StateMachine{TState}"/> for a system that does not bother keeping track of any states other than
  48. /// the active one.
  49. /// <para></para>
  50. /// See <see cref="InitializeAfterDeserialize"/> if using this class in a serialized field.
  51. /// <para></para>
  52. /// <strong>Documentation:</strong>
  53. /// <see href="https://kybernetik.com.au/animancer/docs/manual/fsm/keys">
  54. /// Keyed State Machines</see>
  55. /// </remarks>
  56. /// https://kybernetik.com.au/animancer/api/Animancer.FSM/StateMachine_2
  57. ///
  58. [HelpURL(StateExtensions.APIDocumentationURL + nameof(StateMachine<TState>) + "_2")]
  59. [Serializable]
  60. public partial class StateMachine<TKey, TState> : StateMachine<TState>, IKeyedStateMachine<TKey>, IDictionary<TKey, TState>
  61. where TState : class, IState
  62. {
  63. /************************************************************************************************************************/
  64. /// <summary>The collection of states mapped to a particular key.</summary>
  65. public IDictionary<TKey, TState> Dictionary { get; set; }
  66. /************************************************************************************************************************/
  67. [SerializeField]
  68. private TKey _CurrentKey;
  69. /// <summary>The key which identifies the <see cref="StateMachine{TState}.CurrentState"/>.</summary>
  70. public TKey CurrentKey => _CurrentKey;
  71. /************************************************************************************************************************/
  72. /// <summary>The <see cref="KeyChange{TKey}.PreviousKey"/>.</summary>
  73. public TKey PreviousKey => KeyChange<TKey>.PreviousKey;
  74. /// <summary>The <see cref="KeyChange{TKey}.NextKey"/>.</summary>
  75. public TKey NextKey => KeyChange<TKey>.NextKey;
  76. /************************************************************************************************************************/
  77. /// <summary>
  78. /// Creates a new <see cref="StateMachine{TKey, TState}"/> with a new <see cref="Dictionary"/>, leaving the
  79. /// <see cref="CurrentState"/> null.
  80. /// </summary>
  81. public StateMachine()
  82. {
  83. Dictionary = new Dictionary<TKey, TState>();
  84. }
  85. /// <summary>
  86. /// Creates a new <see cref="StateMachine{TKey, TState}"/> which uses the specified `dictionary`, leaving the
  87. /// <see cref="CurrentState"/> null.
  88. /// </summary>
  89. public StateMachine(IDictionary<TKey, TState> dictionary)
  90. {
  91. Dictionary = dictionary;
  92. }
  93. /// <summary>
  94. /// Constructs a new <see cref="StateMachine{TKey, TState}"/> with a new <see cref="Dictionary"/> and
  95. /// immediately uses the `defaultKey` to enter the `defaultState`.
  96. /// </summary>
  97. /// <remarks>This calls <see cref="IState.OnEnterState"/> but not <see cref="IState.CanEnterState"/>.</remarks>
  98. public StateMachine(TKey defaultKey, TState defaultState)
  99. {
  100. Dictionary = new Dictionary<TKey, TState>
  101. {
  102. { defaultKey, defaultState }
  103. };
  104. ForceSetState(defaultKey, defaultState);
  105. }
  106. /// <summary>
  107. /// Constructs a new <see cref="StateMachine{TKey, TState}"/> which uses the specified `dictionary` and
  108. /// immediately uses the `defaultKey` to enter the `defaultState`.
  109. /// </summary>
  110. /// <remarks>This calls <see cref="IState.OnEnterState"/> but not <see cref="IState.CanEnterState"/>.</remarks>
  111. public StateMachine(IDictionary<TKey, TState> dictionary, TKey defaultKey, TState defaultState)
  112. {
  113. Dictionary = dictionary;
  114. dictionary.Add(defaultKey, defaultState);
  115. ForceSetState(defaultKey, defaultState);
  116. }
  117. /************************************************************************************************************************/
  118. /// <inheritdoc/>
  119. public override void InitializeAfterDeserialize()
  120. {
  121. if (CurrentState != null)
  122. {
  123. using (new KeyChange<TKey>(this, default, _CurrentKey))
  124. using (new StateChange<TState>(this, null, CurrentState))
  125. CurrentState.OnEnterState();
  126. }
  127. else if (Dictionary.TryGetValue(_CurrentKey, out var state))
  128. {
  129. ForceSetState(_CurrentKey, state);
  130. }
  131. // Don't call the base method.
  132. }
  133. /************************************************************************************************************************/
  134. /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
  135. /// <remarks>
  136. /// This method returns true immediately if the specified `state` is already the
  137. /// <see cref="StateMachine{TState}.CurrentState"/>. To allow directly re-entering the same state, use
  138. /// <see cref="TryResetState(TKey, TState)"/> instead.
  139. /// </remarks>
  140. public bool TrySetState(TKey key, TState state)
  141. {
  142. if (CurrentState == state)
  143. return true;
  144. else
  145. return TryResetState(key, state);
  146. }
  147. /// <summary>Attempts to enter the state registered with the specified `key` and returns it if successful.</summary>
  148. /// <remarks>
  149. /// This method returns true immediately if the specified `key` is already the <see cref="CurrentKey"/>. To
  150. /// allow directly re-entering the same state, use <see cref="TryResetState(TKey)"/> instead.
  151. /// </remarks>
  152. public TState TrySetState(TKey key)
  153. {
  154. if (EqualityComparer<TKey>.Default.Equals(_CurrentKey, key))
  155. return CurrentState;
  156. else
  157. return TryResetState(key);
  158. }
  159. /// <inheritdoc/>
  160. object IKeyedStateMachine<TKey>.TrySetState(TKey key) => TrySetState(key);
  161. /************************************************************************************************************************/
  162. /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
  163. /// <remarks>
  164. /// This method does not check if the `state` is already the <see cref="StateMachine{TState}.CurrentState"/>.
  165. /// To do so, use <see cref="TrySetState(TKey, TState)"/> instead.
  166. /// </remarks>
  167. public bool TryResetState(TKey key, TState state)
  168. {
  169. using (new KeyChange<TKey>(this, _CurrentKey, key))
  170. {
  171. if (!CanSetState(state))
  172. return false;
  173. _CurrentKey = key;
  174. ForceSetState(state);
  175. return true;
  176. }
  177. }
  178. /// <summary>Attempts to enter the state registered with the specified `key` and returns it if successful.</summary>
  179. /// <remarks>
  180. /// This method does not check if the `key` is already the <see cref="CurrentKey"/>. To do so, use
  181. /// <see cref="TrySetState(TKey)"/> instead.
  182. /// </remarks>
  183. public TState TryResetState(TKey key)
  184. {
  185. if (Dictionary.TryGetValue(key, out var state) &&
  186. TryResetState(key, state))
  187. return state;
  188. else
  189. return null;
  190. }
  191. /// <inheritdoc/>
  192. object IKeyedStateMachine<TKey>.TryResetState(TKey key) => TryResetState(key);
  193. /************************************************************************************************************************/
  194. /// <summary>
  195. /// Calls <see cref="IState.OnExitState"/> on the <see cref="StateMachine{TState}.CurrentState"/> then changes
  196. /// to the specified `key` and `state` and calls <see cref="IState.OnEnterState"/> on it.
  197. /// </summary>
  198. /// <remarks>
  199. /// This method does not check <see cref="IState.CanExitState"/> or <see cref="IState.CanEnterState"/>. To do
  200. /// that, you should use <see cref="TrySetState(TKey, TState)"/> instead.
  201. /// </remarks>
  202. public void ForceSetState(TKey key, TState state)
  203. {
  204. using (new KeyChange<TKey>(this, _CurrentKey, key))
  205. {
  206. _CurrentKey = key;
  207. ForceSetState(state);
  208. }
  209. }
  210. /// <summary>
  211. /// Uses <see cref="ForceSetState(TKey, TState)"/> to change to the state registered with the `key`. If nothing
  212. /// is registered, it use <c>null</c> and will throw an exception unless
  213. /// <see cref="StateMachine{TState}.AllowNullStates"/> is enabled.
  214. /// </summary>
  215. public TState ForceSetState(TKey key)
  216. {
  217. Dictionary.TryGetValue(key, out var state);
  218. ForceSetState(key, state);
  219. return state;
  220. }
  221. /// <inheritdoc/>
  222. object IKeyedStateMachine<TKey>.ForceSetState(TKey key) => ForceSetState(key);
  223. /************************************************************************************************************************/
  224. #region Dictionary Wrappers
  225. /************************************************************************************************************************/
  226. /// <summary>The state registered with the `key` in the <see cref="Dictionary"/>.</summary>
  227. public TState this[TKey key] { get => Dictionary[key]; set => Dictionary[key] = value; }
  228. /// <summary>Gets the state registered with the specified `key` in the <see cref="Dictionary"/>.</summary>
  229. public bool TryGetValue(TKey key, out TState state) => Dictionary.TryGetValue(key, out state);
  230. /************************************************************************************************************************/
  231. /// <summary>Gets an <see cref="ICollection{T}"/> containing the keys of the <see cref="Dictionary"/>.</summary>
  232. public ICollection<TKey> Keys => Dictionary.Keys;
  233. /// <summary>Gets an <see cref="ICollection{T}"/> containing the state of the <see cref="Dictionary"/>.</summary>
  234. public ICollection<TState> Values => Dictionary.Values;
  235. /************************************************************************************************************************/
  236. /// <summary>Gets the number of states contained in the <see cref="Dictionary"/>.</summary>
  237. public int Count => Dictionary.Count;
  238. /************************************************************************************************************************/
  239. /// <summary>Adds a state to the <see cref="Dictionary"/>.</summary>
  240. public void Add(TKey key, TState state) => Dictionary.Add(key, state);
  241. /// <summary>Adds a state to the <see cref="Dictionary"/>.</summary>
  242. public void Add(KeyValuePair<TKey, TState> item) => Dictionary.Add(item);
  243. /************************************************************************************************************************/
  244. /// <summary>Removes a state from the <see cref="Dictionary"/>.</summary>
  245. public bool Remove(TKey key) => Dictionary.Remove(key);
  246. /// <summary>Removes a state from the <see cref="Dictionary"/>.</summary>
  247. public bool Remove(KeyValuePair<TKey, TState> item) => Dictionary.Remove(item);
  248. /************************************************************************************************************************/
  249. /// <summary>Removes all state from the <see cref="Dictionary"/>.</summary>
  250. public void Clear() => Dictionary.Clear();
  251. /************************************************************************************************************************/
  252. /// <summary>Determines whether the <see cref="Dictionary"/> contains a specific value.</summary>
  253. public bool Contains(KeyValuePair<TKey, TState> item) => Dictionary.Contains(item);
  254. /// <summary>Determines whether the <see cref="Dictionary"/> contains a state with the specified `key`.</summary>
  255. public bool ContainsKey(TKey key) => Dictionary.ContainsKey(key);
  256. /************************************************************************************************************************/
  257. /// <summary>Returns an enumerator that iterates through the <see cref="Dictionary"/>.</summary>
  258. public IEnumerator<KeyValuePair<TKey, TState>> GetEnumerator() => Dictionary.GetEnumerator();
  259. /// <summary>Returns an enumerator that iterates through the <see cref="Dictionary"/>.</summary>
  260. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
  261. /************************************************************************************************************************/
  262. /// <summary>Copies the contents of the <see cref="Dictionary"/> to the `array` starting at the `arrayIndex`.</summary>
  263. public void CopyTo(KeyValuePair<TKey, TState>[] array, int arrayIndex) => Dictionary.CopyTo(array, arrayIndex);
  264. /************************************************************************************************************************/
  265. /// <summary>Indicates whether the <see cref="Dictionary"/> is read-only.</summary>
  266. bool ICollection<KeyValuePair<TKey, TState>>.IsReadOnly => Dictionary.IsReadOnly;
  267. /************************************************************************************************************************/
  268. #endregion
  269. /************************************************************************************************************************/
  270. /// <summary>Returns the state registered with the specified `key`, or null if none is present.</summary>
  271. public TState GetState(TKey key)
  272. {
  273. TryGetValue(key, out var state);
  274. return state;
  275. }
  276. /************************************************************************************************************************/
  277. /// <summary>Adds the specified `keys` and `states`. Both arrays must be the same size.</summary>
  278. public void AddRange(TKey[] keys, TState[] states)
  279. {
  280. Debug.Assert(keys.Length == states.Length,
  281. $"The '{nameof(keys)}' and '{nameof(states)}' arrays must be the same size.");
  282. for (int i = 0; i < keys.Length; i++)
  283. {
  284. Dictionary.Add(keys[i], states[i]);
  285. }
  286. }
  287. /************************************************************************************************************************/
  288. /// <summary>
  289. /// Sets the <see cref="CurrentKey"/> without changing the <see cref="StateMachine{TState}.CurrentState"/>.
  290. /// </summary>
  291. public void SetFakeKey(TKey key) => _CurrentKey = key;
  292. /************************************************************************************************************************/
  293. /// <summary>
  294. /// Returns a string describing the type of this state machine and its <see cref="CurrentKey"/> and
  295. /// <see cref="StateMachine{TState}.CurrentState"/>.
  296. /// </summary>
  297. public override string ToString()
  298. => $"{GetType().FullName} -> {_CurrentKey} -> {(CurrentState != null ? CurrentState.ToString() : "null")}";
  299. /************************************************************************************************************************/
  300. #if UNITY_EDITOR && UNITY_IMGUI
  301. /************************************************************************************************************************/
  302. /// <inheritdoc/>
  303. public override int GUILineCount => 2;
  304. /************************************************************************************************************************/
  305. /// <inheritdoc/>
  306. public override void DoGUI(ref Rect area)
  307. {
  308. area.height = UnityEditor.EditorGUIUtility.singleLineHeight;
  309. UnityEditor.EditorGUI.BeginChangeCheck();
  310. var key = StateMachineUtilities.DoGenericField(area, "Current Key", _CurrentKey);
  311. if (UnityEditor.EditorGUI.EndChangeCheck())
  312. SetFakeKey(key);
  313. StateMachineUtilities.NextVerticalArea(ref area);
  314. base.DoGUI(ref area);
  315. }
  316. /************************************************************************************************************************/
  317. #endif
  318. /************************************************************************************************************************/
  319. }
  320. }