AnimancerStateDictionary.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  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 System.Text;
  6. using UnityEngine;
  7. namespace Animancer
  8. {
  9. /// <summary>A dictionary of <see cref="AnimancerState"/>s mapped to their <see cref="AnimancerState.Key"/>.</summary>
  10. /// <remarks>
  11. /// <strong>Documentation:</strong>
  12. /// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/states">
  13. /// States</see>
  14. /// </remarks>
  15. /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerStateDictionary
  16. public class AnimancerStateDictionary :
  17. IAnimationClipCollection,
  18. IEnumerable<AnimancerState>
  19. {
  20. /************************************************************************************************************************/
  21. /// <summary>The <see cref="AnimancerGraph"/> at the root of the graph.</summary>
  22. private readonly AnimancerGraph Graph;
  23. /************************************************************************************************************************/
  24. /// <summary><see cref="AnimancerState.Key"/> mapped to <see cref="AnimancerState"/>.</summary>
  25. private readonly Dictionary<object, AnimancerState>
  26. States = new();
  27. /************************************************************************************************************************/
  28. /// <summary>[Internal] Creates a new <see cref="AnimancerStateDictionary"/>.</summary>
  29. internal AnimancerStateDictionary(AnimancerGraph graph)
  30. => Graph = graph;
  31. /************************************************************************************************************************/
  32. /// <summary>The number of states that have been registered with a <see cref="AnimancerState.Key"/>.</summary>
  33. public int Count
  34. => States.Count;
  35. /************************************************************************************************************************/
  36. /// <inheritdoc/>
  37. public void AppendDescriptionOrOrphans(
  38. StringBuilder text,
  39. string separator = "\n")
  40. {
  41. string stateSeparator = null;
  42. foreach (var state in States.Values)
  43. {
  44. if (state.Parent != null)
  45. continue;
  46. if (stateSeparator is null)
  47. {
  48. text.Append(separator)
  49. .Append("Orphan States:");
  50. separator += Strings.Indent;
  51. stateSeparator = separator + Strings.Indent;
  52. }
  53. text.Append(separator);
  54. state.AppendDescription(text, stateSeparator);
  55. }
  56. }
  57. /************************************************************************************************************************/
  58. #region Create
  59. /************************************************************************************************************************/
  60. /// <summary>Creates and returns a new <see cref="ClipState"/> to play the `clip`.</summary>
  61. /// <remarks>
  62. /// To create a state on a specific layer, use <c>animancer.Layers[x].CreateState(clip)</c> instead.
  63. /// <para></para>
  64. /// <see cref="AnimancerGraph.GetKey"/> is used to determine the <see cref="AnimancerState.Key"/>.
  65. /// </remarks>
  66. public ClipState Create(AnimationClip clip)
  67. => Create(Graph.GetKey(clip), clip);
  68. /// <summary>
  69. /// Creates and returns a new <see cref="ClipState"/> to play the `clip` and registers it with the `key`.
  70. /// </summary>
  71. /// <remarks>
  72. /// To create a state on a specific layer, use <c>animancer.Layers[x].CreateState(key, clip)</c> instead.
  73. /// </remarks>
  74. public ClipState Create(object key, AnimationClip clip)
  75. {
  76. var state = new ClipState(clip);
  77. state.SetGraph(Graph);
  78. state._Key = key;
  79. Register(state);
  80. return state;
  81. }
  82. /************************************************************************************************************************/
  83. /// <summary>Calls <see cref="GetOrCreate(AnimationClip, bool)"/> for each of the specified clips.</summary>
  84. public void CreateIfNew(AnimationClip clip0, AnimationClip clip1)
  85. {
  86. GetOrCreate(clip0);
  87. GetOrCreate(clip1);
  88. }
  89. /// <summary>Calls <see cref="GetOrCreate(AnimationClip, bool)"/> for each of the specified clips.</summary>
  90. public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2)
  91. {
  92. GetOrCreate(clip0);
  93. GetOrCreate(clip1);
  94. GetOrCreate(clip2);
  95. }
  96. /// <summary>Calls <see cref="GetOrCreate(AnimationClip, bool)"/> for each of the specified clips.</summary>
  97. public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2, AnimationClip clip3)
  98. {
  99. GetOrCreate(clip0);
  100. GetOrCreate(clip1);
  101. GetOrCreate(clip2);
  102. GetOrCreate(clip3);
  103. }
  104. /// <summary>Calls <see cref="GetOrCreate(AnimationClip, bool)"/> for each of the specified `clips`.</summary>
  105. public void CreateIfNew(params AnimationClip[] clips)
  106. {
  107. if (clips == null)
  108. return;
  109. var count = clips.Length;
  110. for (int i = 0; i < count; i++)
  111. {
  112. var clip = clips[i];
  113. if (clip != null)
  114. GetOrCreate(clip);
  115. }
  116. }
  117. /************************************************************************************************************************/
  118. #endregion
  119. /************************************************************************************************************************/
  120. #region Access
  121. /************************************************************************************************************************/
  122. /// <summary>
  123. /// The <see cref="AnimancerLayer.CurrentState"/> on layer 0.
  124. /// <para></para>
  125. /// Specifically, this is the state that was most recently started using any of the Play methods on that layer.
  126. /// States controlled individually via methods in the <see cref="AnimancerState"/> itself will not register in
  127. /// this property.
  128. /// </summary>
  129. public AnimancerState Current
  130. => Graph.Layers[0].CurrentState;
  131. /************************************************************************************************************************/
  132. /// <summary>Calls <see cref="AnimancerGraph.GetKey"/> then returns the state registered with that key.</summary>
  133. /// <exception cref="ArgumentNullException">The key is null.</exception>
  134. /// <exception cref="KeyNotFoundException">No state is registered with the key.</exception>
  135. public AnimancerState this[AnimationClip clip]
  136. => States[Graph.GetKey(clip)];
  137. /// <summary>Returns the state registered with the <see cref="IHasKey.Key"/>.</summary>
  138. /// <exception cref="ArgumentNullException">The `key` is null.</exception>
  139. /// <exception cref="KeyNotFoundException">No state is registered with the `key`.</exception>
  140. public AnimancerState this[IHasKey hasKey]
  141. => States[hasKey.Key];
  142. /// <summary>Returns the state registered with the `key`.</summary>
  143. /// <exception cref="ArgumentNullException">The `key` is null.</exception>
  144. /// <exception cref="KeyNotFoundException">No state is registered with the `key`.</exception>
  145. public AnimancerState this[object key]
  146. => States[key];
  147. /************************************************************************************************************************/
  148. /// <summary>
  149. /// Calls <see cref="AnimancerGraph.GetKey"/> then passes the key to
  150. /// <see cref="TryGet(object, out AnimancerState)"/> and returns the result.
  151. /// </summary>
  152. public bool TryGet(AnimationClip clip, out AnimancerState state)
  153. {
  154. if (clip == null)
  155. {
  156. state = null;
  157. return false;
  158. }
  159. return TryGet(Graph.GetKey(clip), out state);
  160. }
  161. /// <summary>
  162. /// Passes the <see cref="IHasKey.Key"/> into <see cref="TryGet(object, out AnimancerState)"/>
  163. /// and returns the result.
  164. /// </summary>
  165. public bool TryGet(IHasKey hasKey, out AnimancerState state)
  166. {
  167. if (hasKey == null)
  168. {
  169. state = null;
  170. return false;
  171. }
  172. return TryGet(hasKey.Key, out state);
  173. }
  174. /// <summary>
  175. /// If a `state` is registered with the `key`, this method outputs it and returns true. Otherwise the
  176. /// `state` is set to null and this method returns false.
  177. /// </summary>
  178. public bool TryGet(object key, out AnimancerState state)
  179. {
  180. if (key == null)
  181. {
  182. state = null;
  183. return false;
  184. }
  185. return States.TryGetValue(key, out state);
  186. }
  187. /************************************************************************************************************************/
  188. /// <summary>
  189. /// Calls <see cref="AnimancerGraph.GetKey"/> and returns the state registered with that key
  190. /// or creates one if it doesn't exist.
  191. /// </summary>
  192. /// <remarks>
  193. /// If the state already exists but has the wrong <see cref="AnimancerState.Clip"/>, the `allowSetClip`
  194. /// parameter determines what will happen. False causes it to throw an <see cref="ArgumentException"/> while
  195. /// true allows it to change the <see cref="AnimancerState.Clip"/>. Note that the change is somewhat costly to
  196. /// performance so use with caution.
  197. /// </remarks>
  198. /// <exception cref="ArgumentException"/>
  199. public AnimancerState GetOrCreate(AnimationClip clip, bool allowSetClip = false)
  200. => GetOrCreate(Graph.GetKey(clip), clip, allowSetClip);
  201. /// <summary>
  202. /// Returns the state registered with the `transition`s <see cref="IHasKey.Key"/> if there is one.
  203. /// Otherwise this method uses <see cref="ITransition.CreateState"/> to create a new one
  204. /// and registers it with that key before returning it.
  205. /// </summary>
  206. public AnimancerState GetOrCreate(ITransition transition)
  207. {
  208. var key = transition.Key;
  209. if (!TryGet(key, out var state))
  210. {
  211. state = transition.CreateState();
  212. state._Key = key;
  213. state.SetGraph(Graph);
  214. }
  215. return state;
  216. }
  217. /// <summary>
  218. /// Returns the state which registered with the `key` or creates one if it doesn't exist.
  219. /// <para></para>
  220. /// If the state already exists but has the wrong <see cref="AnimancerState.Clip"/>, the `allowSetClip`
  221. /// parameter determines what will happen. False causes it to throw an <see cref="ArgumentException"/>
  222. /// while true allows it to change the <see cref="AnimancerState.Clip"/>.
  223. /// Note that the change is somewhat costly to performance so use with caution.
  224. /// </summary>
  225. /// <exception cref="ArgumentException"/>
  226. /// <remarks>See also: <see cref="AnimancerLayer.GetOrCreateState(object, AnimationClip, bool)"/></remarks>
  227. public AnimancerState GetOrCreate(object key, AnimationClip clip, bool allowSetClip = false)
  228. {
  229. if (TryGet(key, out var state))
  230. {
  231. // If a state exists with the 'key' but has the wrong clip, either change it or complain.
  232. if (!ReferenceEquals(state.Clip, clip))
  233. {
  234. if (allowSetClip)
  235. {
  236. state.Clip = clip;
  237. }
  238. else
  239. {
  240. throw new ArgumentException(GetClipMismatchError(key, state.Clip, clip));
  241. }
  242. }
  243. }
  244. else
  245. {
  246. state = Create(key, clip);
  247. }
  248. return state;
  249. }
  250. /************************************************************************************************************************/
  251. /// <summary>Returns an error message explaining that a state already exists with the specified `key`.</summary>
  252. public static string GetClipMismatchError(object key, AnimationClip oldClip, AnimationClip newClip)
  253. => $"A state already exists using the specified '{nameof(key)}', but has a different {nameof(AnimationClip)}:" +
  254. $"\n• Key: {key}" +
  255. $"\n• Old Clip: {oldClip}" +
  256. $"\n• New Clip: {newClip}";
  257. /************************************************************************************************************************/
  258. /// <summary>[Internal]
  259. /// Registers the `state` in this dictionary so the <see cref="AnimancerState.Key"/> can be used to get it
  260. /// later on using any of the lookup methods such as <see cref="this[object]"/> or
  261. /// <see cref="TryGet(object, out AnimancerState)"/>.
  262. /// </summary>
  263. /// <remarks>Does nothing if the <see cref="AnimancerState.Key"/> is <c>null</c>.</remarks>
  264. internal void Register(AnimancerState state)
  265. {
  266. var key = state._Key;
  267. if (key != null)
  268. {
  269. #if UNITY_ASSERTIONS
  270. if (state.Graph != Graph)
  271. throw new ArgumentException(
  272. $"{nameof(AnimancerStateDictionary)} cannot register a state with a different {nameof(Graph)}: " + state);
  273. #endif
  274. States.Add(key, state);
  275. }
  276. }
  277. /// <summary>[Internal] Removes the `state` from this dictionary (the opposite of <see cref="Register"/>).</summary>
  278. internal void Unregister(AnimancerState state)
  279. {
  280. var key = state._Key;
  281. if (key != null)
  282. States.Remove(key);
  283. }
  284. /************************************************************************************************************************/
  285. #region Enumeration
  286. /************************************************************************************************************************/
  287. // IEnumerable for 'foreach' statements.
  288. /************************************************************************************************************************/
  289. /// <summary>Returns an enumerator that will iterate through all registered states.</summary>
  290. public Dictionary<object, AnimancerState>.ValueCollection.Enumerator GetEnumerator()
  291. => States.Values.GetEnumerator();
  292. /// <inheritdoc/>
  293. IEnumerator<AnimancerState> IEnumerable<AnimancerState>.GetEnumerator()
  294. => GetEnumerator();
  295. /// <inheritdoc/>
  296. IEnumerator IEnumerable.GetEnumerator()
  297. => GetEnumerator();
  298. /************************************************************************************************************************/
  299. /// <summary>[<see cref="IAnimationClipCollection"/>]
  300. /// Adds all the animations of states with a <see cref="AnimancerState.Key"/> to the `clips`.
  301. /// </summary>
  302. public void GatherAnimationClips(ICollection<AnimationClip> clips)
  303. {
  304. foreach (var state in States.Values)
  305. clips.GatherFromSource(state);
  306. }
  307. /************************************************************************************************************************/
  308. #endregion
  309. /************************************************************************************************************************/
  310. #endregion
  311. /************************************************************************************************************************/
  312. #region Destroy
  313. /************************************************************************************************************************/
  314. /// <summary>
  315. /// Calls <see cref="AnimancerState.Destroy"/> on the state associated with the `clip` (if any).
  316. /// Returns true if the state existed.
  317. /// </summary>
  318. public bool Destroy(AnimationClip clip)
  319. {
  320. if (clip == null)
  321. return false;
  322. return Destroy(Graph.GetKey(clip));
  323. }
  324. /// <summary>
  325. /// Calls <see cref="AnimancerState.Destroy"/> on the state associated with the <see cref="IHasKey.Key"/>
  326. /// (if any). Returns true if the state existed.
  327. /// </summary>
  328. public bool Destroy(IHasKey hasKey)
  329. {
  330. if (hasKey == null)
  331. return false;
  332. return Destroy(hasKey.Key);
  333. }
  334. /// <summary>
  335. /// Calls <see cref="AnimancerState.Destroy"/> on the state associated with the `key` (if any).
  336. /// Returns true if the state existed.
  337. /// </summary>
  338. public bool Destroy(object key)
  339. {
  340. if (!TryGet(key, out var state))
  341. return false;
  342. state.Destroy();
  343. return true;
  344. }
  345. /************************************************************************************************************************/
  346. /// <summary>Calls <see cref="Destroy(AnimationClip)"/> on each of the `clips`.</summary>
  347. public void DestroyAll(IList<AnimationClip> clips)
  348. {
  349. if (clips == null)
  350. return;
  351. for (int i = clips.Count - 1; i >= 0; i--)
  352. Destroy(clips[i]);
  353. }
  354. /// <summary>Calls <see cref="Destroy(AnimationClip)"/> on each of the `clips`.</summary>
  355. public void DestroyAll(IEnumerable<AnimationClip> clips)
  356. {
  357. if (clips == null)
  358. return;
  359. foreach (var clip in clips)
  360. Destroy(clip);
  361. }
  362. /************************************************************************************************************************/
  363. /// <summary>
  364. /// Calls <see cref="Destroy(AnimationClip)"/> on all states gathered by
  365. /// <see cref="IAnimationClipSource.GetAnimationClips"/>.
  366. /// </summary>
  367. public void DestroyAll(IAnimationClipSource source)
  368. {
  369. if (source == null)
  370. return;
  371. var clips = ListPool.Acquire<AnimationClip>();
  372. source.GetAnimationClips(clips);
  373. DestroyAll(clips);
  374. ListPool.Release(clips);
  375. }
  376. /// <summary>
  377. /// Calls <see cref="Destroy(AnimationClip)"/> on all states gathered by
  378. /// <see cref="IAnimationClipCollection.GatherAnimationClips"/>.
  379. /// </summary>
  380. public void DestroyAll(IAnimationClipCollection source)
  381. {
  382. if (source == null)
  383. return;
  384. var clips = SetPool.Acquire<AnimationClip>();
  385. source.GatherAnimationClips(clips);
  386. DestroyAll(clips);
  387. SetPool.Release(clips);
  388. }
  389. /************************************************************************************************************************/
  390. #endregion
  391. /************************************************************************************************************************/
  392. #region Key Error Methods
  393. #if UNITY_EDITOR
  394. /************************************************************************************************************************/
  395. // These are overloads of other methods that take a System.Object key to ensure the user doesn't try to use an
  396. // AnimancerState as a key, since the whole point of a key is to identify a state in the first place.
  397. /************************************************************************************************************************/
  398. /// <summary>[Warning]
  399. /// You should not use an <see cref="AnimancerState"/> as a key.
  400. /// The whole point of a key is to identify a state in the first place.
  401. /// </summary>
  402. [Obsolete("You should not use an AnimancerState as a key. The whole point of a key is to identify a state in the first place.", true)]
  403. public AnimancerState this[AnimancerState key]
  404. => key;
  405. /// <summary>[Warning]
  406. /// You should not use an <see cref="AnimancerState"/> as a key.
  407. /// The whole point of a key is to identify a state in the first place.
  408. /// </summary>
  409. [Obsolete("You should not use an AnimancerState as a key. The whole point of a key is to identify a state in the first place.", true)]
  410. public bool TryGet(AnimancerState key, out AnimancerState state)
  411. {
  412. state = key;
  413. return true;
  414. }
  415. /// <summary>[Warning]
  416. /// You should not use an <see cref="AnimancerState"/> as a key.
  417. /// The whole point of a key is to identify a state in the first place.
  418. /// </summary>
  419. [Obsolete("You should not use an AnimancerState as a key. The whole point of a key is to identify a state in the first place.", true)]
  420. public AnimancerState GetOrCreate(AnimancerState key, AnimationClip clip)
  421. => key;
  422. /// <summary>[Warning]
  423. /// You should not use an <see cref="AnimancerState"/> as a key.
  424. /// Just call <see cref="AnimancerState.Destroy"/>.
  425. /// </summary>
  426. [Obsolete("You should not use an AnimancerState as a key. Just call AnimancerState.Destroy.", true)]
  427. public bool Destroy(AnimancerState key)
  428. {
  429. key.Destroy();
  430. return true;
  431. }
  432. /************************************************************************************************************************/
  433. #endif
  434. #endregion
  435. /************************************************************************************************************************/
  436. }
  437. }