AnimancerEvent.Sequence.cs 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. namespace Animancer
  7. {
  8. partial struct AnimancerEvent
  9. {
  10. /// <summary>
  11. /// A variable-size list of <see cref="AnimancerEvent"/>s which keeps itself sorted according to their
  12. /// <see cref="normalizedTime"/>.
  13. /// </summary>
  14. /// <remarks>
  15. /// <em>Animancer Lite does not allow events (except for <see cref="OnEnd"/>) in runtime builds.</em>
  16. /// <para></para>
  17. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Events</see>
  18. /// </remarks>
  19. /// https://kybernetik.com.au/animancer/api/Animancer/Sequence
  20. ///
  21. public partial class Sequence : IEnumerable<AnimancerEvent>, ICopyable<Sequence>
  22. {
  23. /************************************************************************************************************************/
  24. #region Fields and Properties
  25. /************************************************************************************************************************/
  26. internal const string
  27. IndexOutOfRangeError = "index must be within the range of 0 <= index < " + nameof(Count);
  28. #if UNITY_ASSERTIONS
  29. private const string
  30. NullCallbackError = nameof(AnimancerEvent) + " callbacks can't be null (except for End Events)." +
  31. " The " + nameof(AnimancerEvent) + "." + nameof(DummyCallback) + " can be assigned to make an event do nothing.";
  32. #endif
  33. /************************************************************************************************************************/
  34. /// <summary>All of the <see cref="AnimancerEvent"/>s in this sequence (excluding the <see cref="EndEvent"/>).</summary>
  35. /// <remarks>This field should never be null. It should use <see cref="Array.Empty{T}"/> instead.</remarks>
  36. private AnimancerEvent[] _Events;
  37. /************************************************************************************************************************/
  38. /// <summary>[Pro-Only] The number of events in this sequence (excluding the <see cref="EndEvent"/>).</summary>
  39. public int Count { get; private set; }
  40. /************************************************************************************************************************/
  41. /// <summary>Indicates whether the sequence has any events in it (including the <see cref="EndEvent"/>).</summary>
  42. public bool IsEmpty
  43. {
  44. get
  45. {
  46. return
  47. _EndEvent.callback == null &&
  48. float.IsNaN(_EndEvent.normalizedTime) &&
  49. Count == 0;
  50. }
  51. }
  52. /************************************************************************************************************************/
  53. /// <summary>The initial <see cref="Capacity"/> that will be used if another value is not specified.</summary>
  54. public const int DefaultCapacity = 8;
  55. /// <summary>[Pro-Only] The size of the internal array used to hold events.</summary>
  56. /// <remarks>
  57. /// When set, the array is reallocated to the given size.
  58. /// <para></para>
  59. /// By default, the <see cref="Capacity"/> starts at 0 and increases to the <see cref="DefaultCapacity"/>
  60. /// when the first event is added.
  61. /// </remarks>
  62. public int Capacity
  63. {
  64. get => _Events.Length;
  65. set
  66. {
  67. if (value < Count)
  68. throw new ArgumentOutOfRangeException(nameof(value),
  69. $"{nameof(Capacity)} cannot be set lower than {nameof(Count)}");
  70. if (value == _Events.Length)
  71. return;
  72. if (value > 0)
  73. {
  74. var newEvents = new AnimancerEvent[value];
  75. if (Count > 0)
  76. Array.Copy(_Events, 0, newEvents, 0, Count);
  77. _Events = newEvents;
  78. }
  79. else
  80. {
  81. _Events = Array.Empty<AnimancerEvent>();
  82. }
  83. }
  84. }
  85. /************************************************************************************************************************/
  86. #region Modification Detection
  87. /************************************************************************************************************************/
  88. private int _Version;
  89. /// <summary>[Pro-Only]
  90. /// The number of times the contents of this sequence have been modified. This applies to general events,
  91. /// but not the <see cref="EndEvent"/>.
  92. /// </summary>
  93. public int Version
  94. {
  95. get => _Version;
  96. private set
  97. {
  98. _Version = value;
  99. OnSequenceModified();
  100. }
  101. }
  102. /************************************************************************************************************************/
  103. #if UNITY_ASSERTIONS
  104. /// <summary>[Assert-Only]
  105. /// If this property is set, any attempt to modify this sequence will trigger
  106. /// <see cref="OptionalWarning.LockedEvents"/> (which will include this value in its message).
  107. /// </summary>
  108. /// <remarks>This value can be set by <see cref="SetShouldNotModifyReason"/>.</remarks>
  109. public string ShouldNotModifyReason { get; private set; }
  110. #endif
  111. /************************************************************************************************************************/
  112. /// <summary>[Assert-Conditional]
  113. /// Sets the <see cref="ShouldNotModifyReason"/> for <see cref="OptionalWarning.LockedEvents"/>.
  114. /// </summary>
  115. /// <remarks>
  116. /// If the warning is triggered, the message is formatted as:
  117. /// "The <see cref="Sequence"/> being modified should not be modified because " +
  118. /// <see cref="ShouldNotModifyReason"/>.
  119. /// </remarks>
  120. [System.Diagnostics.Conditional(Strings.Assertions)]
  121. public void SetShouldNotModifyReason(string reason)
  122. {
  123. #if UNITY_ASSERTIONS
  124. ShouldNotModifyReason = reason;
  125. #endif
  126. }
  127. /************************************************************************************************************************/
  128. /// <summary>[Assert-Conditional] Logs <see cref="OptionalWarning.LockedEvents"/> if necessary.</summary>
  129. [System.Diagnostics.Conditional(Strings.Assertions)]
  130. public void OnSequenceModified()
  131. {
  132. #if UNITY_ASSERTIONS
  133. if (string.IsNullOrEmpty(ShouldNotModifyReason) ||
  134. OptionalWarning.LockedEvents.IsDisabled())
  135. return;
  136. OptionalWarning.LockedEvents.Log(
  137. $"The {nameof(AnimancerEvent)}.{nameof(Sequence)} being modified should not be modified because " +
  138. ShouldNotModifyReason);
  139. // Clear the reason so it doesn't trigger this warning again. No point in wasting performance.
  140. ShouldNotModifyReason = null;
  141. #endif
  142. }
  143. /************************************************************************************************************************/
  144. #endregion
  145. /************************************************************************************************************************/
  146. #region End Event
  147. /************************************************************************************************************************/
  148. private AnimancerEvent _EndEvent = new AnimancerEvent(float.NaN, null);
  149. /// <summary>
  150. /// A <see cref="callback "/> that will be triggered every frame after the <see cref="normalizedTime"/> has
  151. /// passed. If you want it to only get triggered once, you can either have the event clear itself or just
  152. /// use a regular event instead.
  153. /// </summary>
  154. ///
  155. /// <example><code>
  156. /// void PlayAnimation(AnimancerComponent animancer, AnimationClip clip)
  157. /// {
  158. /// var state = animancer.Play(clip);
  159. /// state.Events.NormalizedEndTime = 0.75f;
  160. /// state.Events.OnEnd = OnAnimationEnd;
  161. ///
  162. /// // Or set the time and callback at the same time:
  163. /// state.Events.EndEvent = new AnimancerEvent(0.75f, OnAnimationEnd);
  164. /// }
  165. ///
  166. /// void OnAnimationEnd()
  167. /// {
  168. /// Debug.Log("Animation ended");
  169. /// }
  170. /// </code></example>
  171. ///
  172. /// <remarks>
  173. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/events/end">End Events</see>
  174. /// <para></para>
  175. /// Interrupting the animation does not trigger this event.
  176. /// <para></para>
  177. /// By default, the <see cref="normalizedTime"/> will be <see cref="float.NaN"/> so that it can choose the
  178. /// correct value based on the current play direction: forwards ends at 1 and backwards ends at 0.
  179. /// <para></para>
  180. /// <em>Animancer Lite does not allow the <see cref="normalizedTime"/> to be changed in Runtime Builds.</em>
  181. /// </remarks>
  182. ///
  183. /// <seealso cref="OnEnd"/>
  184. /// <seealso cref="NormalizedEndTime"/>
  185. public AnimancerEvent EndEvent
  186. {
  187. get => _EndEvent;
  188. set
  189. {
  190. _EndEvent = value;
  191. OnSequenceModified();
  192. }
  193. }
  194. /************************************************************************************************************************/
  195. /// <summary>Shorthand for the <c>EndEvent.callback</c>.</summary>
  196. /// <seealso cref="EndEvent"/>
  197. /// <seealso cref="NormalizedEndTime"/>
  198. public Action OnEnd
  199. {
  200. get => _EndEvent.callback;
  201. set
  202. {
  203. _EndEvent.callback = value;
  204. OnSequenceModified();
  205. }
  206. }
  207. /************************************************************************************************************************/
  208. /// <summary>[Pro-Only] Shorthand for <c>EndEvent.normalizedTime</c>.</summary>
  209. /// <remarks>
  210. /// This value is <see cref="float.NaN"/> by default so that the actual time can be determined based on the
  211. /// <see cref="AnimancerNode.EffectiveSpeed"/>: positive speed ends at 1 and negative speed ends at 0.
  212. /// <para></para>
  213. /// Use <see cref="AnimancerState.NormalizedEndTime"/> to access that value.
  214. /// </remarks>
  215. /// <seealso cref="EndEvent"/>
  216. /// <seealso cref="OnEnd"/>
  217. public float NormalizedEndTime
  218. {
  219. get => _EndEvent.normalizedTime;
  220. set
  221. {
  222. _EndEvent.normalizedTime = value;
  223. OnSequenceModified();
  224. }
  225. }
  226. /************************************************************************************************************************/
  227. /// <summary>
  228. /// The default <see cref="AnimancerState.NormalizedTime"/> for an animation to start at when playing
  229. /// forwards is 0 (the start of the animation) and when playing backwards is 1 (the end of the animation).
  230. /// <para></para>
  231. /// `speed` 0 or <see cref="float.NaN"/> will also return 0.
  232. /// </summary>
  233. /// <remarks>
  234. /// This method has nothing to do with events, so it is only here because of
  235. /// <see cref="GetDefaultNormalizedEndTime"/>.
  236. /// </remarks>
  237. public static float GetDefaultNormalizedStartTime(float speed) => speed < 0 ? 1 : 0;
  238. /// <summary>
  239. /// The default <see cref="normalizedTime"/> for an <see cref="EndEvent"/> when playing forwards is 1 (the
  240. /// end of the animation) and when playing backwards is 0 (the start of the animation).
  241. /// <para></para>
  242. /// `speed` 0 or <see cref="float.NaN"/> will also return 1.
  243. /// </summary>
  244. public static float GetDefaultNormalizedEndTime(float speed) => speed < 0 ? 0 : 1;
  245. /************************************************************************************************************************/
  246. #endregion
  247. /************************************************************************************************************************/
  248. #region Names
  249. /************************************************************************************************************************/
  250. private string[] _Names;
  251. /// <summary>[Pro-Only] The names of the events (excluding the <see cref="EndEvent"/>).</summary>
  252. /// <remarks>This array can be <c>null</c>.</remarks>
  253. public ref string[] Names => ref _Names;
  254. /************************************************************************************************************************/
  255. /// <summary>[Pro-Only]
  256. /// Returns the name of the event at the specified `index` or <c>null</c> if it is outside of the
  257. /// <see cref="Names"/> array.
  258. /// </summary>
  259. public string GetName(int index)
  260. {
  261. if (_Names == null ||
  262. _Names.Length <= index)
  263. return null;
  264. else
  265. return _Names[index];
  266. }
  267. /************************************************************************************************************************/
  268. /// <summary>[Pro-Only]
  269. /// Sets the name of the event at the specified `index`. If the <see cref="Names"/> did not previously
  270. /// include that `index` it will be resized with a size equal to the <see cref="Count"/>.
  271. /// </summary>
  272. public void SetName(int index, string name)
  273. {
  274. AnimancerUtilities.Assert((uint)index < (uint)Count, IndexOutOfRangeError);
  275. // Capacity can't be 0 at this point.
  276. if (_Names == null)
  277. {
  278. _Names = new string[Capacity];
  279. }
  280. else if (_Names.Length <= index)
  281. {
  282. var names = new string[Capacity];
  283. Array.Copy(_Names, names, _Names.Length);
  284. _Names = names;
  285. }
  286. _Names[index] = name;
  287. }
  288. /************************************************************************************************************************/
  289. /// <summary>[Pro-Only]
  290. /// Returns the index of the event with the specified `name` or <c>-1</c> if there is no such event.
  291. /// </summary>
  292. /// <seealso cref="Names"/>
  293. /// <seealso cref="GetName"/>
  294. /// <seealso cref="SetName"/>
  295. /// <seealso cref="IndexOfRequired(string, int)"/>
  296. public int IndexOf(string name, int startIndex = 0)
  297. {
  298. if (_Names == null)
  299. return -1;
  300. var count = Mathf.Min(Count, _Names.Length);
  301. for (; startIndex < count; startIndex++)
  302. if (_Names[startIndex] == name)
  303. return startIndex;
  304. return -1;
  305. }
  306. /// <summary>[Pro-Only] Returns the index of the event with the specified `name`.</summary>
  307. /// <exception cref="ArgumentException">There is no such event.</exception>
  308. /// <seealso cref="IndexOf(string, int)"/>
  309. public int IndexOfRequired(string name, int startIndex = 0)
  310. {
  311. startIndex = IndexOf(name, startIndex);
  312. if (startIndex >= 0)
  313. return startIndex;
  314. throw new ArgumentException($"No event exists with the name '{name}'.");
  315. }
  316. /************************************************************************************************************************/
  317. #endregion
  318. /************************************************************************************************************************/
  319. #endregion
  320. /************************************************************************************************************************/
  321. #region Constructors
  322. /************************************************************************************************************************/
  323. /// <summary>
  324. /// Creates a new <see cref="Sequence"/> which starts at 0 <see cref="Capacity"/>.
  325. /// <para></para>
  326. /// Adding anything to the sequence will set the <see cref="Capacity"/> = <see cref="DefaultCapacity"/>
  327. /// and then double it whenever the <see cref="Count"/> would exceed the <see cref="Capacity"/>.
  328. /// </summary>
  329. public Sequence()
  330. {
  331. _Events = Array.Empty<AnimancerEvent>();
  332. }
  333. /************************************************************************************************************************/
  334. /// <summary>[Pro-Only]
  335. /// Creates a new <see cref="Sequence"/> which starts with the specified <see cref="Capacity"/>. It will be
  336. /// initially empty, but will have room for the given number of elements before any reallocations are
  337. /// required.
  338. /// </summary>
  339. public Sequence(int capacity)
  340. {
  341. _Events = capacity > 0 ? new AnimancerEvent[capacity] : Array.Empty<AnimancerEvent>();
  342. }
  343. /************************************************************************************************************************/
  344. /// <summary>Creates a new <see cref="Sequence"/> and copies the contents of `copyFrom` into it.</summary>
  345. /// <remarks>To copy into an existing sequence, use <see cref="CopyFrom"/> instead.</remarks>
  346. public Sequence(Sequence copyFrom)
  347. {
  348. _Events = Array.Empty<AnimancerEvent>();
  349. if (copyFrom != null)
  350. CopyFrom(copyFrom);
  351. }
  352. /************************************************************************************************************************/
  353. #endregion
  354. /************************************************************************************************************************/
  355. #region Iteration
  356. /************************************************************************************************************************/
  357. /// <summary>[Pro-Only] Returns the event at the specified `index`.</summary>
  358. public AnimancerEvent this[int index]
  359. {
  360. get
  361. {
  362. AnimancerUtilities.Assert((uint)index < (uint)Count, IndexOutOfRangeError);
  363. return _Events[index];
  364. }
  365. }
  366. /// <summary>[Pro-Only] Returns the event with the specified `name`.</summary>
  367. /// <exception cref="ArgumentException">There is no event with the specified `name`.</exception>
  368. public AnimancerEvent this[string name] => this[IndexOfRequired(name)];
  369. /************************************************************************************************************************/
  370. /// <summary>[Assert-Conditional]
  371. /// Throws an <see cref="ArgumentOutOfRangeException"/> if the <see cref="normalizedTime"/> of any events
  372. /// is less than 0 or greater than or equal to 1.
  373. /// <para></para>
  374. /// This does not include the <see cref="EndEvent"/> since it works differently to other events.
  375. /// </summary>
  376. [System.Diagnostics.Conditional(Strings.Assertions)]
  377. public void AssertNormalizedTimes(AnimancerState state)
  378. {
  379. if (Count == 0 ||
  380. (_Events[0].normalizedTime >= 0 && _Events[Count - 1].normalizedTime < 1))
  381. return;
  382. throw new ArgumentOutOfRangeException(nameof(normalizedTime),
  383. "Events on looping animations are triggered every loop and must be" +
  384. $" within the range of 0 <= {nameof(normalizedTime)} < 1.\n{state}\n{DeepToString()}");
  385. }
  386. /// <summary>[Assert-Conditional]
  387. /// Calls <see cref="AssertNormalizedTimes(AnimancerState)"/> if `isLooping` is true.
  388. /// </summary>
  389. [System.Diagnostics.Conditional(Strings.Assertions)]
  390. public void AssertNormalizedTimes(AnimancerState state, bool isLooping)
  391. {
  392. if (isLooping)
  393. AssertNormalizedTimes(state);
  394. }
  395. /************************************************************************************************************************/
  396. /// <summary>Returns a string containing the details of all events in this sequence.</summary>
  397. public string DeepToString(bool multiLine = true)
  398. {
  399. var text = ObjectPool.AcquireStringBuilder()
  400. .Append(ToString())
  401. .Append('[')
  402. .Append(Count)
  403. .Append(']');
  404. text.Append(multiLine ? "\n{" : " {");
  405. for (int i = 0; i < Count; i++)
  406. {
  407. if (multiLine)
  408. text.Append("\n ");
  409. else if (i > 0)
  410. text.Append(',');
  411. text.Append(" [");
  412. text.Append(i)
  413. .Append("] ");
  414. this[i].AppendDetails(text);
  415. var name = GetName(i);
  416. if (name != null)
  417. {
  418. text.Append(", Name: '")
  419. .Append(name)
  420. .Append('\'');
  421. }
  422. }
  423. if (multiLine)
  424. {
  425. text.Append("\n [End] ");
  426. }
  427. else
  428. {
  429. if (Count > 0)
  430. text.Append(',');
  431. text.Append(" [End] ");
  432. }
  433. _EndEvent.AppendDetails(text);
  434. if (multiLine)
  435. text.Append("\n}\n");
  436. else
  437. text.Append(" }");
  438. return text.ReleaseToString();
  439. }
  440. /************************************************************************************************************************/
  441. /// <summary>[Pro-Only]
  442. /// Returns a <see cref="FastEnumerator{T}"/> for the events in this sequence excluding the
  443. /// <see cref="EndEvent"/>.
  444. /// </summary>
  445. public FastEnumerator<AnimancerEvent> GetEnumerator()
  446. => new FastEnumerator<AnimancerEvent>(_Events, Count);
  447. IEnumerator<AnimancerEvent> IEnumerable<AnimancerEvent>.GetEnumerator()
  448. => GetEnumerator();
  449. IEnumerator IEnumerable.GetEnumerator()
  450. => GetEnumerator();
  451. /************************************************************************************************************************/
  452. /// <summary>[Pro-Only] Returns the index of the `animancerEvent` or <c>-1</c> if there is no such event.</summary>
  453. /// <seealso cref="IndexOfRequired(int, AnimancerEvent)"/>
  454. public int IndexOf(AnimancerEvent animancerEvent) => IndexOf(Count / 2, animancerEvent);
  455. /// <summary>[Pro-Only] Returns the index of the `animancerEvent`.</summary>
  456. /// <exception cref="ArgumentException">There is no such event.</exception>
  457. /// <seealso cref="IndexOf(AnimancerEvent)"/>
  458. public int IndexOfRequired(AnimancerEvent animancerEvent) => IndexOfRequired(Count / 2, animancerEvent);
  459. /// <summary>[Pro-Only] Returns the index of the `animancerEvent` or <c>-1</c> if there is no such event.</summary>
  460. /// <seealso cref="IndexOfRequired(int, AnimancerEvent)"/>
  461. public int IndexOf(int indexHint, AnimancerEvent animancerEvent)
  462. {
  463. if (Count == 0)
  464. return -1;
  465. if (indexHint >= Count)
  466. indexHint = Count - 1;
  467. var otherEvent = _Events[indexHint];
  468. if (otherEvent == animancerEvent)
  469. return indexHint;
  470. if (otherEvent.normalizedTime > animancerEvent.normalizedTime)
  471. {
  472. while (--indexHint >= 0)
  473. {
  474. otherEvent = _Events[indexHint];
  475. if (otherEvent.normalizedTime < animancerEvent.normalizedTime)
  476. return -1;
  477. else if (otherEvent.normalizedTime == animancerEvent.normalizedTime)
  478. if (otherEvent.callback == animancerEvent.callback)
  479. return indexHint;
  480. }
  481. }
  482. else
  483. {
  484. while (otherEvent.normalizedTime == animancerEvent.normalizedTime)
  485. {
  486. indexHint--;
  487. if (indexHint < 0)
  488. break;
  489. otherEvent = _Events[indexHint];
  490. }
  491. while (++indexHint < Count)
  492. {
  493. otherEvent = _Events[indexHint];
  494. if (otherEvent.normalizedTime > animancerEvent.normalizedTime)
  495. return -1;
  496. else if (otherEvent.normalizedTime == animancerEvent.normalizedTime)
  497. if (otherEvent.callback == animancerEvent.callback)
  498. return indexHint;
  499. }
  500. }
  501. return -1;
  502. }
  503. /// <summary>[Pro-Only] Returns the index of the `animancerEvent`.</summary>
  504. /// <exception cref="ArgumentException">There is no such event.</exception>
  505. /// <seealso cref="IndexOf(int, AnimancerEvent)"/>
  506. public int IndexOfRequired(int indexHint, AnimancerEvent animancerEvent)
  507. {
  508. indexHint = IndexOf(indexHint, animancerEvent);
  509. if (indexHint >= 0)
  510. return indexHint;
  511. throw new ArgumentException($"Event not found in {nameof(Sequence)} '{animancerEvent}'.");
  512. }
  513. /************************************************************************************************************************/
  514. #endregion
  515. /************************************************************************************************************************/
  516. #region Modification
  517. /************************************************************************************************************************/
  518. /// <summary>[Pro-Only]
  519. /// Adds the given event to this sequence. The <see cref="Count"/> is increased by one and if required, the
  520. /// <see cref="Capacity"/> is doubled to fit the new event.
  521. /// </summary>
  522. /// <remarks>
  523. /// This methods returns the index at which the event is added, which is determined by its
  524. /// <see cref="normalizedTime"/> to keep the sequence sorted in ascending order. If there are already any
  525. /// events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
  526. /// </remarks>
  527. /// <exception cref="ArgumentNullException">Use the <see cref="DummyCallback"/> instead of <c>null</c>.</exception>
  528. /// <seealso cref="OptionalWarning.DuplicateEvent"/>
  529. public int Add(AnimancerEvent animancerEvent)
  530. {
  531. #if UNITY_ASSERTIONS
  532. if (animancerEvent.callback == null)
  533. throw new ArgumentNullException($"{nameof(AnimancerEvent)}.{nameof(callback)}", NullCallbackError);
  534. #endif
  535. var index = Insert(animancerEvent.normalizedTime);
  536. AssertEventUniqueness(index, animancerEvent);
  537. _Events[index] = animancerEvent;
  538. return index;
  539. }
  540. /// <summary>[Pro-Only]
  541. /// Adds the given event to this sequence. The <see cref="Count"/> is increased by one and if required, the
  542. /// <see cref="Capacity"/> is doubled to fit the new event.
  543. /// </summary>
  544. /// <remarks>
  545. /// This methods returns the index at which the event is added, which is determined by its
  546. /// <see cref="normalizedTime"/> to keep the sequence sorted in ascending order. If there are already any
  547. /// events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
  548. /// </remarks>
  549. /// <seealso cref="OptionalWarning.DuplicateEvent"/>
  550. public int Add(float normalizedTime, Action callback)
  551. => Add(new AnimancerEvent(normalizedTime, callback));
  552. /// <summary>[Pro-Only]
  553. /// Adds the given event to this sequence. The <see cref="Count"/> is increased by one and if required, the
  554. /// <see cref="Capacity"/> is doubled to fit the new event.
  555. /// </summary>
  556. /// <remarks>
  557. /// This methods returns the index at which the event is added, which is determined by its
  558. /// <see cref="normalizedTime"/> to keep the sequence sorted in ascending order. If there are already any
  559. /// events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
  560. /// </remarks>
  561. /// <exception cref="ArgumentNullException">Use the <see cref="DummyCallback"/> instead of <c>null</c>.</exception>
  562. /// <seealso cref="OptionalWarning.DuplicateEvent"/>
  563. public int Add(int indexHint, AnimancerEvent animancerEvent)
  564. {
  565. #if UNITY_ASSERTIONS
  566. if (animancerEvent.callback == null)
  567. throw new ArgumentNullException($"{nameof(AnimancerEvent)}.{nameof(callback)}", NullCallbackError);
  568. #endif
  569. indexHint = Insert(indexHint, animancerEvent.normalizedTime);
  570. AssertEventUniqueness(indexHint, animancerEvent);
  571. _Events[indexHint] = animancerEvent;
  572. return indexHint;
  573. }
  574. /// <summary>[Pro-Only]
  575. /// Adds the given event to this sequence. The <see cref="Count"/> is increased by one and if required, the
  576. /// <see cref="Capacity"/> is doubled to fit the new event.
  577. /// </summary>
  578. /// <remarks>
  579. /// This methods returns the index at which the event is added, which is determined by its
  580. /// <see cref="normalizedTime"/> to keep the sequence sorted in ascending order. If there are already any
  581. /// events with the same <see cref="normalizedTime"/>, the new event is added immediately after them.
  582. /// </remarks>
  583. /// <seealso cref="OptionalWarning.DuplicateEvent"/>
  584. public int Add(int indexHint, float normalizedTime, Action callback)
  585. => Add(indexHint, new AnimancerEvent(normalizedTime, callback));
  586. /************************************************************************************************************************/
  587. /// <summary>[Pro-Only]
  588. /// Adds every event in the `enumerable` to this sequence. The <see cref="Count"/> is increased by one and if
  589. /// required, the <see cref="Capacity"/> is doubled to fit the new event.
  590. /// </summary>
  591. /// <seealso cref="OptionalWarning.DuplicateEvent"/>
  592. public void AddRange(IEnumerable<AnimancerEvent> enumerable)
  593. {
  594. foreach (var item in enumerable)
  595. Add(item);
  596. }
  597. /************************************************************************************************************************/
  598. /// <summary>[Pro-Only] Adds the specified `callback` to the event at the specified `index`.</summary>
  599. /// <seealso cref="OptionalWarning.DuplicateEvent"/>
  600. public void AddCallback(int index, Action callback)
  601. {
  602. ref var animancerEvent = ref _Events[index];
  603. AssertCallbackUniqueness(animancerEvent.callback, callback, $"{nameof(callback)} being added");
  604. animancerEvent.callback += callback;
  605. Version++;
  606. }
  607. /// <summary>[Pro-Only] Adds the specified `callback` to the event with the specified `name`.</summary>
  608. /// <exception cref="ArgumentException">There is no event with the specified `name`.</exception>
  609. /// <seealso cref="IndexOfRequired(string, int)"/>
  610. /// <seealso cref="OptionalWarning.DuplicateEvent"/>
  611. public void AddCallback(string name, Action callback) => AddCallback(IndexOfRequired(name), callback);
  612. /************************************************************************************************************************/
  613. /// <summary>[Pro-Only] Removes the specified `callback` from the event at the specified `index`.</summary>
  614. /// <remarks>
  615. /// If the <see cref="callback"/> would become null, it is instead set to the <see cref="DummyCallback"/>
  616. /// since they are not allowed to be null.
  617. /// </remarks>
  618. public void RemoveCallback(int index, Action callback)
  619. {
  620. ref var animancerEvent = ref _Events[index];
  621. animancerEvent.callback -= callback;
  622. if (animancerEvent.callback == null)
  623. animancerEvent.callback = DummyCallback;
  624. Version++;
  625. }
  626. /// <summary>[Pro-Only] Removes the specified `callback` from the event with the specified `name`.</summary>
  627. /// <remarks>
  628. /// If the <see cref="callback"/> would become null, it is instead set to the <see cref="DummyCallback"/>
  629. /// since they are not allowed to be null.
  630. /// </remarks>
  631. /// <exception cref="ArgumentException">There is no event with the specified `name`.</exception>
  632. /// <seealso cref="IndexOfRequired(string, int)"/>
  633. public void RemoveCallback(string name, Action callback) => RemoveCallback(IndexOfRequired(name), callback);
  634. /************************************************************************************************************************/
  635. /// <summary>[Pro-Only] Replaces the <see cref="callback"/> of the event at the specified `index`.</summary>
  636. /// <exception cref="ArgumentNullException">Use the <see cref="DummyCallback"/> instead of <c>null</c>.</exception>
  637. /// <seealso cref="OptionalWarning.DuplicateEvent"/>
  638. public void SetCallback(int index, Action callback)
  639. {
  640. #if UNITY_ASSERTIONS
  641. if (callback == null)
  642. throw new ArgumentNullException(nameof(callback), NullCallbackError);
  643. #endif
  644. ref var animancerEvent = ref _Events[index];
  645. AssertCallbackUniqueness(animancerEvent.callback, callback, $"{nameof(callback)} being assigned");
  646. animancerEvent.callback = callback;
  647. Version++;
  648. }
  649. /// <summary>[Pro-Only] Replaces the <see cref="callback"/> of the event with the specified `name`.</summary>
  650. /// <exception cref="ArgumentException">There is no event with the specified `name`.</exception>
  651. /// <seealso cref="IndexOfRequired(string, int)"/>
  652. /// <seealso cref="OptionalWarning.DuplicateEvent"/>
  653. public void SetCallback(string name, Action callback) => SetCallback(IndexOfRequired(name), callback);
  654. /************************************************************************************************************************/
  655. /// <summary>[Assert-Conditional]
  656. /// Logs <see cref="OptionalWarning.DuplicateEvent"/> if the `oldCallback` is identical to the
  657. /// `newCallback` or just has the same <see cref="Delegate.Method"/>.
  658. /// </summary>
  659. [System.Diagnostics.Conditional(Strings.Assertions)]
  660. private static void AssertCallbackUniqueness(Action oldCallback, Action newCallback, string target)
  661. {
  662. #if UNITY_ASSERTIONS
  663. if (oldCallback == DummyCallback ||
  664. OptionalWarning.DuplicateEvent.IsDisabled())
  665. return;
  666. if (oldCallback == newCallback)
  667. {
  668. OptionalWarning.DuplicateEvent.Log($"The {target}" +
  669. " is identical to an existing event in the sequence" +
  670. " which may mean that it is being unintentionally added multiple times." +
  671. $" If the {nameof(AnimancerEvent)}.{nameof(Sequence)} is owned by a Transition then it will not" +
  672. " be cleared each time it is played so the events should only be initialized once on startup." +
  673. $" See the documentation for more information: {Strings.DocsURLs.ClearAutomatically}");
  674. }
  675. else if (oldCallback?.Method == newCallback?.Method)
  676. {
  677. OptionalWarning.DuplicateEvent.Log($"The {target}" +
  678. " is identical to an existing event in the sequence except for the target object." +
  679. " This often happens when a Transition is shared by multiple objects," +
  680. " in which case it can be avoided by giving each object its own" +
  681. $" {nameof(AnimancerEvent)}.{nameof(Sequence)} as explained in the documentation:" +
  682. $" {Strings.DocsURLs.SharedEventSequences}");
  683. }
  684. #endif
  685. }
  686. /// <summary>[Assert-Conditional]
  687. /// Logs <see cref="OptionalWarning.DuplicateEvent"/> if the event at the specified `index` is identical to
  688. /// the `newEvent`.
  689. /// </summary>
  690. [System.Diagnostics.Conditional(Strings.Assertions)]
  691. private void AssertEventUniqueness(int index, AnimancerEvent newEvent)
  692. {
  693. #if UNITY_ASSERTIONS
  694. if (OptionalWarning.DuplicateEvent.IsDisabled() || index == 0)
  695. return;
  696. var previousEvent = _Events[index - 1];
  697. if (previousEvent.normalizedTime != newEvent.normalizedTime)
  698. return;
  699. AssertCallbackUniqueness(previousEvent.callback, newEvent.callback, $"{nameof(AnimancerEvent)} being added");
  700. #endif
  701. }
  702. /************************************************************************************************************************/
  703. /// <summary>[Pro-Only] Sets the <see cref="normalizedTime"/> of the event at the specified `index`.</summary>
  704. /// <remarks>
  705. /// If multiple events have the same <see cref="normalizedTime"/>, this method will avoid re-arranging them
  706. /// where calling <see cref="Remove(int)"/> then <see cref="Add(AnimancerEvent)"/> would always re-add the
  707. /// moved event as the last one with that time.
  708. /// </remarks>
  709. public int SetNormalizedTime(int index, float normalizedTime)
  710. {
  711. #if UNITY_ASSERTIONS
  712. if (!normalizedTime.IsFinite())
  713. throw new ArgumentOutOfRangeException(nameof(normalizedTime), normalizedTime,
  714. $"{nameof(normalizedTime)} {Strings.MustBeFinite}");
  715. #endif
  716. var animancerEvent = _Events[index];
  717. if (animancerEvent.normalizedTime == normalizedTime)
  718. return index;
  719. var moveTo = index;
  720. if (animancerEvent.normalizedTime < normalizedTime)
  721. {
  722. while (moveTo < Count - 1)
  723. {
  724. if (_Events[moveTo + 1].normalizedTime >= normalizedTime)
  725. break;
  726. else
  727. moveTo++;
  728. }
  729. }
  730. else
  731. {
  732. while (moveTo > 0)
  733. {
  734. if (_Events[moveTo - 1].normalizedTime <= normalizedTime)
  735. break;
  736. else
  737. moveTo--;
  738. }
  739. }
  740. if (index != moveTo)
  741. {
  742. var name = GetName(index);
  743. Remove(index);
  744. index = moveTo;
  745. Insert(index);
  746. if (!string.IsNullOrEmpty(name))
  747. SetName(index, name);
  748. }
  749. animancerEvent.normalizedTime = normalizedTime;
  750. _Events[index] = animancerEvent;
  751. Version++;
  752. return index;
  753. }
  754. /// <summary>[Pro-Only] Sets the <see cref="normalizedTime"/> of the event with the specified `name`.</summary>
  755. /// <remarks>
  756. /// If multiple events have the same <see cref="normalizedTime"/>, this method will avoid re-arranging them
  757. /// where calling <see cref="Remove(int)"/> then <see cref="Add(AnimancerEvent)"/> would always re-add the
  758. /// moved event as the last one with that time.
  759. /// </remarks>
  760. /// <exception cref="ArgumentException">There is no event with the specified `name`.</exception>
  761. /// <seealso cref="IndexOfRequired(string, int)"/>
  762. public int SetNormalizedTime(string name, float normalizedTime)
  763. => SetNormalizedTime(IndexOfRequired(name), normalizedTime);
  764. /// <summary>[Pro-Only] Sets the <see cref="normalizedTime"/> of the matching `animancerEvent`.</summary>
  765. /// <remarks>
  766. /// If multiple events have the same <see cref="normalizedTime"/>, this method will avoid re-arranging them
  767. /// where calling <see cref="Remove(int)"/> then <see cref="Add(AnimancerEvent)"/> would always re-add the
  768. /// moved event as the last one with that time.
  769. /// </remarks>
  770. /// <exception cref="ArgumentException">There is no event matching the `animancerEvent`.</exception>
  771. /// <seealso cref="IndexOfRequired(AnimancerEvent)"/>
  772. public int SetNormalizedTime(AnimancerEvent animancerEvent, float normalizedTime)
  773. => SetNormalizedTime(IndexOfRequired(animancerEvent), normalizedTime);
  774. /************************************************************************************************************************/
  775. /// <summary>[Pro-Only]
  776. /// Determines the index where a new event with the specified `normalizedTime` should be added in order to
  777. /// keep this sequence sorted, increases the <see cref="Count"/> by one, doubles the <see cref="Capacity"/>
  778. /// if required, moves any existing events to open up the chosen index, and returns that index.
  779. /// <para></para>
  780. /// This overload starts searching for the desired index from the end of the sequence, using the assumption
  781. /// that elements will usually be added in order.
  782. /// </summary>
  783. private int Insert(float normalizedTime)
  784. {
  785. var index = Count;
  786. while (index > 0 && _Events[index - 1].normalizedTime > normalizedTime)
  787. index--;
  788. Insert(index);
  789. return index;
  790. }
  791. /// <summary>[Pro-Only]
  792. /// Determines the index where a new event with the specified `normalizedTime` should be added in order to
  793. /// keep this sequence sorted, increases the <see cref="Count"/> by one, doubles the <see cref="Capacity"/>
  794. /// if required, moves any existing events to open up the chosen index, and returns that index.
  795. /// <para></para>
  796. /// This overload starts searching for the desired index from the `hint`.
  797. /// </summary>
  798. private int Insert(int indexHint, float normalizedTime)
  799. {
  800. if (Count == 0)
  801. {
  802. Count = 0;
  803. }
  804. else
  805. {
  806. if (indexHint >= Count)
  807. indexHint = Count - 1;
  808. if (_Events[indexHint].normalizedTime > normalizedTime)
  809. {
  810. while (indexHint > 0 && _Events[indexHint - 1].normalizedTime > normalizedTime)
  811. indexHint--;
  812. }
  813. else
  814. {
  815. while (indexHint < Count && _Events[indexHint].normalizedTime <= normalizedTime)
  816. indexHint++;
  817. }
  818. }
  819. Insert(indexHint);
  820. return indexHint;
  821. }
  822. /************************************************************************************************************************/
  823. /// <summary>[Pro-Only]
  824. /// Increases the <see cref="Count"/> by one, doubles the <see cref="Capacity"/> if required, and moves any
  825. /// existing events to open up the `index`.
  826. /// </summary>
  827. private void Insert(int index)
  828. {
  829. AnimancerUtilities.Assert((uint)index <= (uint)Count, IndexOutOfRangeError);
  830. var capacity = _Events.Length;
  831. if (Count == capacity)
  832. {
  833. if (capacity == 0)
  834. {
  835. capacity = DefaultCapacity;
  836. _Events = new AnimancerEvent[DefaultCapacity];
  837. }
  838. else
  839. {
  840. capacity *= 2;
  841. if (capacity < DefaultCapacity)
  842. capacity = DefaultCapacity;
  843. var events = new AnimancerEvent[capacity];
  844. Array.Copy(_Events, 0, events, 0, index);
  845. if (Count > index)
  846. Array.Copy(_Events, index, events, index + 1, Count - index);
  847. _Events = events;
  848. }
  849. }
  850. else if (Count > index)
  851. {
  852. Array.Copy(_Events, index, _Events, index + 1, Count - index);
  853. }
  854. if (_Names != null)
  855. {
  856. if (_Names.Length < capacity)
  857. {
  858. var names = new string[capacity];
  859. Array.Copy(_Names, 0, names, 0, Math.Min(_Names.Length, index));
  860. if (index <= Count &&
  861. index < _Names.Length)
  862. Array.Copy(_Names, index, names, index + 1, Count - index);
  863. _Names = names;
  864. }
  865. else
  866. {
  867. if (Count > index)
  868. Array.Copy(_Names, index, _Names, index + 1, Count - index);
  869. _Names[index] = null;
  870. }
  871. }
  872. Count++;
  873. Version++;
  874. }
  875. /************************************************************************************************************************/
  876. /// <summary>[Pro-Only]
  877. /// Removes the event at the specified `index` from this sequence by decrementing the <see cref="Count"/>
  878. /// and copying all events after the removed one down one place.
  879. /// </summary>
  880. public void Remove(int index)
  881. {
  882. AnimancerUtilities.Assert((uint)index < (uint)Count, IndexOutOfRangeError);
  883. Count--;
  884. if (index < Count)
  885. {
  886. Array.Copy(_Events, index + 1, _Events, index, Count - index);
  887. if (_Names != null)
  888. {
  889. var nameCount = Mathf.Min(Count + 1, _Names.Length);
  890. if (index + 1 < nameCount)
  891. Array.Copy(_Names, index + 1, _Names, index, nameCount - index - 1);
  892. _Names[nameCount - 1] = default;
  893. }
  894. }
  895. else if (_Names != null && index < _Names.Length)
  896. {
  897. _Names[index] = default;
  898. }
  899. _Events[Count] = default;
  900. Version++;
  901. }
  902. /// <summary>[Pro-Only]
  903. /// Removes the event with the specified `name` from this sequence by decrementing the <see cref="Count"/>
  904. /// and copying all events after the removed one down one place. Returns true if the event was found and
  905. /// removed.
  906. /// </summary>
  907. public bool Remove(string name)
  908. {
  909. var index = IndexOf(name);
  910. if (index >= 0)
  911. {
  912. Remove(index);
  913. return true;
  914. }
  915. else return false;
  916. }
  917. /// <summary>[Pro-Only]
  918. /// Removes the `animancerEvent` from this sequence by decrementing the <see cref="Count"/> and copying all
  919. /// events after the removed one down one place. Returns true if the event was found and removed.
  920. /// </summary>
  921. public bool Remove(AnimancerEvent animancerEvent)
  922. {
  923. var index = IndexOf(animancerEvent);
  924. if (index >= 0)
  925. {
  926. Remove(index);
  927. return true;
  928. }
  929. else return false;
  930. }
  931. /************************************************************************************************************************/
  932. /// <summary>Removes all events, including the <see cref="EndEvent"/>.</summary>
  933. public void Clear()
  934. {
  935. if (_Names != null)
  936. Array.Clear(_Names, 0, _Names.Length);
  937. Array.Clear(_Events, 0, Count);
  938. Count = 0;
  939. Version++;
  940. _EndEvent = new AnimancerEvent(float.NaN, null);
  941. }
  942. /************************************************************************************************************************/
  943. #endregion
  944. /************************************************************************************************************************/
  945. #region Copying
  946. /************************************************************************************************************************/
  947. /// <inheritdoc/>
  948. public void CopyFrom(Sequence copyFrom)
  949. {
  950. if (copyFrom == null)
  951. {
  952. if (_Names != null)
  953. Array.Clear(_Names, 0, _Names.Length);
  954. Array.Clear(_Events, 0, Count);
  955. Count = 0;
  956. Capacity = 0;
  957. _EndEvent = default;
  958. return;
  959. }
  960. AnimancerUtilities.CopyExactArray(copyFrom._Names, ref _Names);
  961. var sourceCount = copyFrom.Count;
  962. if (Count > sourceCount)
  963. Array.Clear(_Events, Count, sourceCount - Count);
  964. else if (_Events.Length < sourceCount)
  965. Capacity = sourceCount;
  966. Count = sourceCount;
  967. Array.Copy(copyFrom._Events, 0, _Events, 0, sourceCount);
  968. _EndEvent = copyFrom._EndEvent;
  969. }
  970. /************************************************************************************************************************/
  971. /// <summary>[Pro-Only] Copies the <see cref="AnimationClip.events"/> into this <see cref="Sequence"/>.</summary>
  972. /// <remarks>
  973. /// The <see cref="callback"/> of the new events will be empty and can be set by
  974. /// <see cref="SetCallback(string, Action)"/>.
  975. /// <para></para>
  976. /// If you are going to play the `animation`, consider disabling <see cref="Animator.fireEvents"/> so that
  977. /// the events copied by this method are not triggered as <see cref="AnimationEvent"/>s. Otherwise they
  978. /// would still trigger in addition to the <see cref="AnimancerEvent"/>s copied here.
  979. /// </remarks>
  980. public void AddAllEvents(AnimationClip animation)
  981. {
  982. if (animation == null)
  983. return;
  984. var length = animation.length;
  985. var animationEvents = animation.events;
  986. Capacity += animationEvents.Length;
  987. var index = -1;
  988. for (int i = 0; i < animationEvents.Length; i++)
  989. {
  990. var animationEvent = animationEvents[i];
  991. index = Add(index + 1, new AnimancerEvent(animationEvent.time / length, DummyCallback));
  992. SetName(index, animationEvent.functionName);
  993. }
  994. }
  995. /************************************************************************************************************************/
  996. /// <summary>[<see cref="ICollection{T}"/>] [Pro-Only]
  997. /// Copies all the events from this sequence into the `array`, starting at the `index`.
  998. /// </summary>
  999. public void CopyTo(AnimancerEvent[] array, int index)
  1000. {
  1001. Array.Copy(_Events, 0, array, index, Count);
  1002. }
  1003. /************************************************************************************************************************/
  1004. #endregion
  1005. /************************************************************************************************************************/
  1006. }
  1007. }
  1008. }