NamedEventDictionary.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. namespace Animancer
  6. {
  7. /// <summary>A dictionary which maps event names to callbacks.</summary>
  8. /// <remarks>
  9. /// <strong>Documentation:</strong>
  10. /// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
  11. /// Animancer Events</see>
  12. /// </remarks>
  13. /// https://kybernetik.com.au/animancer/api/Animancer/NamedEventDictionary
  14. public class NamedEventDictionary : IDictionary<StringReference, Action>
  15. {
  16. /************************************************************************************************************************/
  17. private readonly Dictionary<StringReference, Action>
  18. Dictionary = new();
  19. /************************************************************************************************************************/
  20. /// <summary>The number of items in this dictionary.</summary>
  21. public int Count
  22. => Dictionary.Count;
  23. /************************************************************************************************************************/
  24. #region Access
  25. /************************************************************************************************************************/
  26. /// <summary>Accesses a callback in this dictionary.</summary>
  27. public Action this[StringReference name]
  28. {
  29. get => Dictionary[name];
  30. set
  31. {
  32. AssertNotEndEvent(name);
  33. Dictionary[name] = value;
  34. }
  35. }
  36. /************************************************************************************************************************/
  37. /// <summary>Returns the callback registered using the `name`.</summary>
  38. /// <remarks>Returns <c>null</c> if nothing was registered.</remarks>
  39. public Action Get(StringReference name)
  40. => Dictionary.Get(name);
  41. /// <summary>Registers the callback using the `name`, replacing anything previously registered.</summary>
  42. public void Set(StringReference name, Action callback)
  43. {
  44. AssertNotEndEvent(name);
  45. Dictionary[name] = callback;
  46. }
  47. /************************************************************************************************************************/
  48. /// <summary>Are any callbacks registered for the `name`?</summary>
  49. /// <remarks>To get the registered callbacks at the same time, use <see cref="TryGetValue"/> instead.</remarks>
  50. public bool ContainsKey(StringReference name)
  51. => Dictionary.ContainsKey(name);
  52. /************************************************************************************************************************/
  53. /// <summary>Tries to get the `callback` registered with the `name` and returns <c>true</c> if successful.</summary>
  54. public bool TryGetValue(StringReference name, out Action callback)
  55. => Dictionary.TryGetValue(name, out callback);
  56. /************************************************************************************************************************/
  57. #endregion
  58. /************************************************************************************************************************/
  59. #region Add
  60. /************************************************************************************************************************/
  61. /// <summary>Adds the `callback` to any existing ones registered with the `name`.</summary>
  62. /// <remarks>
  63. /// If you want an exception to be thrown if something is already registered with the `name`,
  64. /// use <see cref="AddNew(StringReference, Action)"/> instead.
  65. /// </remarks>
  66. public void AddTo(StringReference name, Action callback)
  67. {
  68. AssertNotEndEvent(name);
  69. if (Dictionary.TryGetValue(name, out var existing))
  70. callback = existing + callback;
  71. Dictionary[name] = callback;
  72. }
  73. /************************************************************************************************************************/
  74. /// <summary>
  75. /// Registers the `callback` with the `name` but throws an <see cref="ArgumentException"/>
  76. /// if something was already registered with the same `name`.
  77. /// </summary>
  78. /// <remarks>
  79. /// This matches the standard <see cref="Dictionary{TKey, TValue}.Add(TKey, TValue)"/> behaviour,
  80. /// unlike <see cref="AddTo(StringReference, Action)"/>.
  81. /// </remarks>
  82. public void AddNew(StringReference name, Action callback)
  83. {
  84. AssertNotEndEvent(name);
  85. Dictionary.Add(name, callback);
  86. }
  87. void IDictionary<StringReference, Action>.Add(StringReference name, Action callback)
  88. => AddNew(name, callback);
  89. /************************************************************************************************************************/
  90. /// <summary>
  91. /// Adds the `callback` to any existing ones registered with the `name`.
  92. /// <para></para>
  93. /// It will be invoked using <see cref="AnimancerEvent.GetCurrentParameter{T}"/> to get its parameter.
  94. /// </summary>
  95. /// <remarks>
  96. /// If you want an exception to be thrown if something is already registered with the `name`,
  97. /// use <see cref="AddNew{T}(StringReference, Action{T})"/> instead.
  98. /// <para></para>
  99. /// If <typeparamref name="T"/> is <see cref="string"/>,
  100. /// consider using <see cref="AddTo(StringReference, Action{string})"/> instead of this overload.
  101. /// <para></para>
  102. /// If you want to later remove the `callback`,
  103. /// you need to store and remove the returned <see cref="Action"/>.
  104. /// </remarks>
  105. public Action AddTo<T>(StringReference name, Action<T> callback)
  106. {
  107. AssertNotEndEvent(name);
  108. var parametized = AnimancerEvent.Parametize(callback);
  109. if (Dictionary.TryGetValue(name, out var existing))
  110. parametized = existing + parametized;
  111. Dictionary[name] = parametized;
  112. return parametized;
  113. }
  114. /// <summary>
  115. /// Registers the `callback` with the `name` but throws an <see cref="ArgumentException"/>
  116. /// if something was already registered with the same `name`.
  117. /// <para></para>
  118. /// It will be invoked using <see cref="AnimancerEvent.GetCurrentParameter{T}"/> to get its parameter.
  119. /// </summary>
  120. /// <remarks>
  121. /// This matches the standard <see cref="Dictionary{TKey, TValue}.Add(TKey, TValue)"/> behaviour,
  122. /// unlike <see cref="AddTo{T}(StringReference, Action{T})"/>.
  123. /// If <typeparamref name="T"/> is <see cref="string"/>,
  124. /// consider using <see cref="AddTo(StringReference, Action{string})"/> instead of this overload.
  125. /// <para></para>
  126. /// If you want to later remove the `callback`,
  127. /// you need to store and remove the returned <see cref="Action"/>.
  128. /// </remarks>
  129. public Action AddNew<T>(StringReference name, Action<T> callback)
  130. {
  131. AssertNotEndEvent(name);
  132. var parametized = AnimancerEvent.Parametize(callback);
  133. Dictionary.Add(name, parametized);
  134. return parametized;
  135. }
  136. /************************************************************************************************************************/
  137. /// <summary>
  138. /// Adds the `callback` to any existing ones registered with the `name`.
  139. /// <para></para>
  140. /// It will be invoked using <see cref="object.ToString"/> on the
  141. /// <see cref="AnimancerEvent.CurrentParameter"/>.
  142. /// </summary>
  143. /// <remarks>
  144. /// If you want an exception to be thrown if something is already registered with the `name`,
  145. /// use <see cref="AddNew{T}(StringReference, Action{T})"/> instead.
  146. /// <para></para>
  147. /// If you want to later remove the `callback`,
  148. /// you need to store and remove the returned <see cref="Action"/>.
  149. /// </remarks>
  150. public Action AddTo(StringReference name, Action<string> callback)
  151. {
  152. AssertNotEndEvent(name);
  153. var parametized = AnimancerEvent.Parametize(callback);
  154. if (Dictionary.TryGetValue(name, out var existing))
  155. parametized = existing + parametized;
  156. Dictionary[name] = parametized;
  157. return parametized;
  158. }
  159. /// <summary>
  160. /// Registers the `callback` with the `name` but throws an <see cref="ArgumentException"/>
  161. /// if something was already registered with the same `name`.
  162. /// <para></para>
  163. /// It will be invoked using <see cref="object.ToString"/> on the
  164. /// <see cref="AnimancerEvent.CurrentParameter"/>.
  165. /// </summary>
  166. /// <remarks>
  167. /// This matches the standard <see cref="Dictionary{TKey, TValue}.Add(TKey, TValue)"/>
  168. /// behaviour, unlike <see cref="AddTo(StringReference, Action{string})"/>.
  169. /// <para></para>
  170. /// If you want to later remove the `callback`,
  171. /// you need to store and remove the returned <see cref="Action"/>.
  172. /// </remarks>
  173. public Action AddNew(StringReference name, Action<string> callback)
  174. {
  175. AssertNotEndEvent(name);
  176. var parametized = AnimancerEvent.Parametize(callback);
  177. Dictionary.Add(name, parametized);
  178. return parametized;
  179. }
  180. /************************************************************************************************************************/
  181. /// <summary>[Assert-Conditional]
  182. /// Throws an <see cref="ArgumentException"/> if the `name` is the <see cref="AnimancerEvent.EndEventName"/>.
  183. /// </summary>
  184. /// <remarks>
  185. /// In order to minimise the performance cost of End Events when there isn't one,
  186. /// the <see cref="AnimancerEvent.Dispatcher"/> won't even check the end time
  187. /// when there is no <see cref="AnimancerEvent.Sequence.OnEnd"/> callback.
  188. /// <para></para>
  189. /// That means if a callback was bound to the <see cref="AnimancerEvent.EndEventName"/>
  190. /// it would be triggered by any state with an <see cref="AnimancerEvent.Sequence.OnEnd"/>
  191. /// callback, but not by states without one. That would be very counterintuitive so it isn't allowed.
  192. /// </remarks>
  193. /// <exception cref="ArgumentException"/>
  194. [System.Diagnostics.Conditional(Strings.Assertions)]
  195. public static void AssertNotEndEvent(StringReference name)
  196. {
  197. if (name == AnimancerEvent.EndEventName)
  198. throw new ArgumentException(
  199. $"Binding event callbacks to the " +
  200. $"{nameof(AnimancerEvent)}.{nameof(AnimancerEvent.EndEventName)}" +
  201. $" is not supported for performance optimization reasons. See the documentation of" +
  202. $" {nameof(NamedEventDictionary)}.{nameof(AssertNotEndEvent)} for more details.",
  203. nameof(name));
  204. }
  205. /************************************************************************************************************************/
  206. #endregion
  207. /************************************************************************************************************************/
  208. #region Remove
  209. /************************************************************************************************************************/
  210. /// <summary>Removes all callbacks registered with the `name`.</summary>
  211. public bool Remove(StringReference name)
  212. => Dictionary.Remove(name);
  213. /// <summary>Removes a specific `callback` registered with the `name`.</summary>
  214. public bool Remove(StringReference name, Action callback)
  215. {
  216. if (!Dictionary.TryGetValue(name, out var callbacks))
  217. return false;
  218. if (callbacks == callback)
  219. Dictionary.Remove(name);
  220. else
  221. Dictionary[name] = callbacks - callback;
  222. return true;
  223. }
  224. /************************************************************************************************************************/
  225. /// <summary>Removes everything from this dictionary.</summary>
  226. public void Clear()
  227. => Dictionary.Clear();
  228. /************************************************************************************************************************/
  229. #endregion
  230. /************************************************************************************************************************/
  231. #region Enumeration
  232. /************************************************************************************************************************/
  233. /// <summary>Returns an enumerator to go through every item in this dictionary.</summary>
  234. public Dictionary<StringReference, Action>.Enumerator GetEnumerator()
  235. => Dictionary.GetEnumerator();
  236. IEnumerator<KeyValuePair<StringReference, Action>>
  237. IEnumerable<KeyValuePair<StringReference, Action>>.GetEnumerator()
  238. => Dictionary.GetEnumerator();
  239. IEnumerator IEnumerable.GetEnumerator()
  240. => Dictionary.GetEnumerator();
  241. /************************************************************************************************************************/
  242. /// <summary>The names in this dictionary.</summary>
  243. public Dictionary<StringReference, Action>.KeyCollection Keys
  244. => Dictionary.Keys;
  245. /// <summary>The values in this dictionary.</summary>
  246. public Dictionary<StringReference, Action>.ValueCollection Values
  247. => Dictionary.Values;
  248. ICollection<StringReference> IDictionary<StringReference, Action>.Keys
  249. => Dictionary.Keys;
  250. ICollection<Action> IDictionary<StringReference, Action>.Values
  251. => Dictionary.Values;
  252. /************************************************************************************************************************/
  253. #endregion
  254. /************************************************************************************************************************/
  255. #region Explicit Dictionary Wrappers
  256. /************************************************************************************************************************/
  257. void ICollection<KeyValuePair<StringReference, Action>>
  258. .Add(KeyValuePair<StringReference, Action> item)
  259. => AddTo(item.Key, item.Value);
  260. bool ICollection<KeyValuePair<StringReference, Action>>
  261. .Contains(KeyValuePair<StringReference, Action> item)
  262. => ((ICollection<KeyValuePair<StringReference, Action>>)Dictionary).Contains(item);
  263. void ICollection<KeyValuePair<StringReference, Action>>
  264. .CopyTo(KeyValuePair<StringReference, Action>[] array,
  265. int arrayIndex)
  266. => ((ICollection<KeyValuePair<StringReference, Action>>)Dictionary).CopyTo(array, arrayIndex);
  267. bool ICollection<KeyValuePair<StringReference, Action>>
  268. .Remove(KeyValuePair<StringReference, Action> item)
  269. => Dictionary.Remove(item.Key);
  270. bool ICollection<KeyValuePair<StringReference, Action>>.IsReadOnly
  271. => false;
  272. /************************************************************************************************************************/
  273. #endregion
  274. /************************************************************************************************************************/
  275. }
  276. }