AnimancerState.EventDispatcher.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. using System;
  3. using UnityEngine;
  4. using Object = UnityEngine.Object;
  5. namespace Animancer
  6. {
  7. /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerState
  8. partial class AnimancerState
  9. {
  10. /************************************************************************************************************************/
  11. /// <summary>The <see cref="IUpdatable"/> that manages the events of this state.</summary>
  12. /// <remarks>
  13. /// This field is null by default, acquires its reference from an <see cref="ObjectPool"/> when accessed, and
  14. /// if it contains no events at the end of an update it releases the reference back to the pool.
  15. /// </remarks>
  16. private EventDispatcher _EventDispatcher;
  17. /************************************************************************************************************************/
  18. /// <summary>
  19. /// A list of <see cref="AnimancerEvent"/>s that will occur while this state plays as well as one that
  20. /// specifically defines when this state ends.
  21. /// </summary>
  22. /// <remarks>
  23. /// Accessing this property will acquire a spare <see cref="AnimancerEvent.Sequence"/> from the
  24. /// <see cref="ObjectPool"/> if none was already assigned. You can use <see cref="HasEvents"/> to check
  25. /// beforehand.
  26. /// <para></para>
  27. /// These events will automatically be cleared by <see cref="Play"/>, <see cref="Stop"/>, and
  28. /// <see cref="OnStartFade"/> (unless <see cref="AutomaticallyClearEvents"/> is disabled).
  29. /// <para></para>
  30. /// <em>Animancer Lite does not allow the use of events in runtime builds, except for
  31. /// <see cref="AnimancerEvent.Sequence.OnEnd"/>.</em>
  32. /// <para></para>
  33. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Events</see>
  34. /// </remarks>
  35. public AnimancerEvent.Sequence Events
  36. {
  37. get
  38. {
  39. EventDispatcher.Acquire(this);
  40. return _EventDispatcher.Events;
  41. }
  42. set
  43. {
  44. if (value != null)
  45. {
  46. EventDispatcher.Acquire(this);
  47. _EventDispatcher.Events = value;
  48. }
  49. else if (_EventDispatcher != null)
  50. {
  51. _EventDispatcher.Events = null;
  52. }
  53. }
  54. }
  55. /************************************************************************************************************************/
  56. /// <summary>Does this state have an <see cref="AnimancerEvent.Sequence"/>?</summary>
  57. /// <remarks>Accessing <see cref="Events"/> would automatically get one from the <see cref="ObjectPool"/>.</remarks>
  58. public bool HasEvents => _EventDispatcher != null && _EventDispatcher.HasEvents;
  59. /************************************************************************************************************************/
  60. /// <summary>
  61. /// Should the <see cref="Events"/> be cleared automatically whenever <see cref="Play"/>, <see cref="Stop"/>,
  62. /// or <see cref="OnStartFade"/> are called? Default true.
  63. /// </summary>
  64. /// <remarks>
  65. /// Disabling this property is not usually recommended since it would allow events to continue being triggered
  66. /// while a state is fading out. For example, if a <em>Flinch</em> animation interrupts an <em>Attack</em>, you
  67. /// probably don't want the <em>Attack</em>'s <em>Hit</em> event to still get triggered while it's fading out.
  68. /// <para></para>
  69. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer#clear-automatically">
  70. /// Clear Automatically</see>
  71. /// </remarks>
  72. public static bool AutomaticallyClearEvents { get; set; } = true;
  73. /************************************************************************************************************************/
  74. #if UNITY_ASSERTIONS
  75. /// <summary>[Assert-Only]
  76. /// Returns <c>null</c> if Animancer Events will work properly on this type of state, or a message explaining
  77. /// why they might not work.
  78. /// </summary>
  79. protected virtual string UnsupportedEventsMessage => null;
  80. #endif
  81. /************************************************************************************************************************/
  82. /// <summary>An <see cref="IUpdatable"/> which triggers events in an <see cref="AnimancerEvent.Sequence"/>.</summary>
  83. /// https://kybernetik.com.au/animancer/api/Animancer/EventDispatcher
  84. ///
  85. public class EventDispatcher : Key, IUpdatable
  86. {
  87. /************************************************************************************************************************/
  88. #region Pooling
  89. /************************************************************************************************************************/
  90. /// <summary>
  91. /// If the `state` has no <see cref="EventDispatcher"/>, this method gets one from the
  92. /// <see cref="ObjectPool"/>.
  93. /// </summary>
  94. internal static void Acquire(AnimancerState state)
  95. {
  96. ref var dispatcher = ref state._EventDispatcher;
  97. if (dispatcher != null)
  98. return;
  99. ObjectPool.Acquire(out dispatcher);
  100. #if UNITY_ASSERTIONS
  101. dispatcher._LoggedEndEventInterrupt = false;
  102. OptionalWarning.UnsupportedEvents.Log(state.UnsupportedEventsMessage, state.Root?.Component);
  103. if (dispatcher._State != null)
  104. Debug.LogError($"{dispatcher} already has a state even though it was in the list of spares.",
  105. state.Root?.Component as Object);
  106. if (dispatcher._Events != null)
  107. Debug.LogError($"{dispatcher} has event sequence even though it was in the list of spares.",
  108. state.Root?.Component as Object);
  109. if (dispatcher._GotEventsFromPool)
  110. Debug.LogError($"{dispatcher} is marked as having pooled events even though it has no events.",
  111. state.Root?.Component as Object);
  112. if (dispatcher._NextEventIndex != RecalculateEventIndex)
  113. Debug.LogError($"{dispatcher} has a {nameof(_NextEventIndex)} even though it was pooled.",
  114. state.Root?.Component as Object);
  115. if (IsInList(dispatcher))
  116. Debug.LogError($"{dispatcher} is currently in a Keyed List even though it was also in the list of spares.",
  117. state.Root?.Component as Object);
  118. #endif
  119. dispatcher._IsLooping = state.IsLooping;
  120. dispatcher._PreviousTime = state.NormalizedTime;
  121. dispatcher._State = state;
  122. state.Root?.RequirePostUpdate(dispatcher);
  123. }
  124. /************************************************************************************************************************/
  125. /// <summary>Returns this <see cref="EventDispatcher"/> to the <see cref="ObjectPool"/>.</summary>
  126. private void Release()
  127. {
  128. if (_State == null)
  129. return;
  130. _State.Root?.CancelPostUpdate(this);
  131. _State._EventDispatcher = null;
  132. _State = null;
  133. Events = null;
  134. ObjectPool.Release(this);
  135. }
  136. /************************************************************************************************************************/
  137. /// <summary>
  138. /// If the <see cref="AnimancerEvent.Sequence"/> was acquired from the <see cref="ObjectPool"/>, this
  139. /// method clears it. Otherwise it simply discards the reference.
  140. /// </summary>
  141. internal static void TryClear(EventDispatcher events)
  142. {
  143. if (events != null)
  144. events.Events = null;
  145. }
  146. /************************************************************************************************************************/
  147. #endregion
  148. /************************************************************************************************************************/
  149. private AnimancerState _State;
  150. private AnimancerEvent.Sequence _Events;
  151. private bool _GotEventsFromPool;
  152. private bool _IsLooping;
  153. private float _PreviousTime;
  154. private int _NextEventIndex = RecalculateEventIndex;
  155. private int _SequenceVersion;
  156. private bool _WasPlayingForwards;
  157. /// <summary>
  158. /// A special value used by the <see cref="_NextEventIndex"/> to indicate that it needs to be recalculated.
  159. /// </summary>
  160. private const int RecalculateEventIndex = int.MinValue;
  161. /// <summary>
  162. /// This system accounts for external modifications to the sequence, but modifying it while checking which
  163. /// of its events to update is not allowed because it would be impossible to efficiently keep track of
  164. /// which events have been checked/invoked and which still need to be checked.
  165. /// </summary>
  166. private const string SequenceVersionException =
  167. nameof(AnimancerState) + "." + nameof(AnimancerState.Events) + " sequence was modified while iterating through it." +
  168. " Events in a sequence must not modify that sequence.";
  169. /************************************************************************************************************************/
  170. /// <summary>Does this dispatcher have an <see cref="AnimancerEvent.Sequence"/>?</summary>
  171. /// <remarks>Accessing <see cref="Events"/> would automatically get one from the <see cref="ObjectPool"/>.</remarks>
  172. public bool HasEvents => _Events != null;
  173. /************************************************************************************************************************/
  174. /// <summary>The events managed by this dispatcher.</summary>
  175. /// <remarks>If <c>null</c>, a new sequence will be acquired from the <see cref="ObjectPool"/>.</remarks>
  176. internal AnimancerEvent.Sequence Events
  177. {
  178. get
  179. {
  180. if (_Events == null)
  181. {
  182. ObjectPool.Acquire(out _Events);
  183. _GotEventsFromPool = true;
  184. #if UNITY_ASSERTIONS
  185. if (!_Events.IsEmpty)
  186. Debug.LogError(_Events + " is not in its default state even though it was in the list of spares.",
  187. _State?.Root?.Component as Object);
  188. #endif
  189. }
  190. return _Events;
  191. }
  192. set
  193. {
  194. if (_GotEventsFromPool)
  195. {
  196. _Events.Clear();
  197. ObjectPool.Release(_Events);
  198. _GotEventsFromPool = false;
  199. }
  200. _Events = value;
  201. _NextEventIndex = RecalculateEventIndex;
  202. }
  203. }
  204. /************************************************************************************************************************/
  205. void IUpdatable.Update()
  206. {
  207. if (_Events == null || _Events.IsEmpty)
  208. {
  209. Release();
  210. return;
  211. }
  212. var length = _State.Length;
  213. if (length == 0)
  214. {
  215. UpdateZeroLength();
  216. return;
  217. }
  218. var currentTime = _State.Time / length;
  219. if (_PreviousTime == currentTime)
  220. return;
  221. // General events are triggered on the frame when their time passes.
  222. // This happens either once or repeatedly depending on whether the animation is looping or not.
  223. CheckGeneralEvents(currentTime);
  224. if (_Events == null)
  225. {
  226. Release();
  227. return;
  228. }
  229. // End events are triggered every frame after their time passes. This ensures that assigning the event
  230. // after the time has passed will still trigger it rather than leaving it playing indefinitely.
  231. var endEvent = _Events.EndEvent;
  232. if (endEvent.callback != null)
  233. {
  234. if (currentTime > _PreviousTime)// Playing Forwards.
  235. {
  236. var eventTime = float.IsNaN(endEvent.normalizedTime)
  237. ? 1
  238. : endEvent.normalizedTime;
  239. if (currentTime > eventTime)
  240. {
  241. ValidateBeforeEndEvent();
  242. endEvent.Invoke(_State);
  243. ValidateAfterEndEvent(endEvent.callback);
  244. }
  245. }
  246. else// Playing Backwards.
  247. {
  248. var eventTime = float.IsNaN(endEvent.normalizedTime)
  249. ? 0
  250. : endEvent.normalizedTime;
  251. if (currentTime < eventTime)
  252. {
  253. ValidateBeforeEndEvent();
  254. endEvent.Invoke(_State);
  255. ValidateAfterEndEvent(endEvent.callback);
  256. }
  257. }
  258. }
  259. // Store the current time as the previous for the next frame unless OnTimeChanged was called.
  260. if (_NextEventIndex != RecalculateEventIndex)
  261. _PreviousTime = currentTime;
  262. }
  263. /************************************************************************************************************************/
  264. #region End Event Validation
  265. /************************************************************************************************************************/
  266. #if UNITY_ASSERTIONS
  267. private bool _LoggedEndEventInterrupt;
  268. private static AnimancerLayer _BeforeEndLayer;
  269. private static int _BeforeEndCommandCount;
  270. #endif
  271. /************************************************************************************************************************/
  272. /// <summary>[Assert-Conditional]
  273. /// Called after the <see cref="AnimancerEvent.Sequence.EndEvent"/> is triggered to log a warning if the
  274. /// <see cref="_State"/> was not interrupted or the `callback` contains multiple calls to the same method.
  275. /// </summary>
  276. /// <remarks>
  277. /// It would be better if we could validate the callback when it is assigned to get a useful stack trace,
  278. /// but that is unfortunately not possible since <see cref="AnimancerEvent.Sequence.EndEvent"/> needs to be
  279. /// a field for efficiency.
  280. /// </remarks>
  281. [System.Diagnostics.Conditional(Strings.Assertions)]
  282. private void ValidateBeforeEndEvent()
  283. {
  284. #if UNITY_ASSERTIONS
  285. _BeforeEndLayer = _State.Layer;
  286. _BeforeEndCommandCount = _BeforeEndLayer.CommandCount;
  287. #endif
  288. }
  289. /************************************************************************************************************************/
  290. /// <summary>[Assert-Conditional]
  291. /// Called after the <see cref="AnimancerEvent.Sequence.EndEvent"/> is triggered to log a warning if the
  292. /// <see cref="_State"/> was not interrupted or the `callback` contains multiple calls to the same method.
  293. /// </summary>
  294. /// <remarks>
  295. /// It would be better if we could validate the callback when it is assigned to get a useful stack trace,
  296. /// but that is unfortunately not possible since <see cref="AnimancerEvent.Sequence.EndEvent"/> needs to be
  297. /// a field for efficiency.
  298. /// </remarks>
  299. [System.Diagnostics.Conditional(Strings.Assertions)]
  300. private void ValidateAfterEndEvent(Action callback)
  301. {
  302. #if UNITY_ASSERTIONS
  303. if (ShouldLogEndEventInterrupt(callback))
  304. {
  305. _LoggedEndEventInterrupt = true;
  306. if (OptionalWarning.EndEventInterrupt.IsEnabled())
  307. OptionalWarning.EndEventInterrupt.Log(
  308. "An End Event did not actually end the animation:" +
  309. $"\n - State: {_State}" +
  310. $"\n - Callback: {callback.Method.DeclaringType.Name}.{callback.Method.Name}" +
  311. "\n\nEnd Events are triggered every frame after their time has passed," +
  312. " so if that is not desired behaviour then it might be necessary to explicitly set the" +
  313. $" state.{nameof(AnimancerState.Events)}.{nameof(AnimancerEvent.Sequence.OnEnd)} = null" +
  314. " or simply use a regular event instead.",
  315. _State.Root?.Component);
  316. }
  317. if (OptionalWarning.DuplicateEvent.IsDisabled())
  318. return;
  319. if (!AnimancerUtilities.TryGetInvocationListNonAlloc(callback, out var delegates) ||
  320. delegates == null)
  321. return;
  322. var count = delegates.Length;
  323. for (int iA = 0; iA < count; iA++)
  324. {
  325. var a = delegates[iA];
  326. for (int iB = iA + 1; iB < count; iB++)
  327. {
  328. var b = delegates[iB];
  329. if (a == b)
  330. {
  331. OptionalWarning.DuplicateEvent.Log(
  332. $"The {nameof(AnimancerEvent)}.{nameof(AnimancerEvent.Sequence)}.{nameof(AnimancerEvent.Sequence.OnEnd)}" +
  333. " callback being invoked contains multiple identical delegates which may mean" +
  334. " that they are being unintentionally added multiple times." +
  335. $"\n - State: {_State}" +
  336. $"\n - Method: {a.Method.Name}",
  337. _State.Root?.Component);
  338. }
  339. else if (a?.Method == b?.Method)
  340. {
  341. OptionalWarning.DuplicateEvent.Log(
  342. $"The {nameof(AnimancerEvent)}.{nameof(AnimancerEvent.Sequence)}.{nameof(AnimancerEvent.Sequence.OnEnd)}" +
  343. " callback being invoked contains multiple delegates using the same method with different targets." +
  344. " This often happens when a Transition is shared by multiple objects," +
  345. " in which case it can be avoided by giving each object its own" +
  346. $" {nameof(AnimancerEvent)}.{nameof(AnimancerEvent.Sequence)} as explained in the documentation:" +
  347. $" {Strings.DocsURLs.SharedEventSequences}" +
  348. $"\n - State: {_State}" +
  349. $"\n - Method: {a.Method.Name}",
  350. _State.Root?.Component);
  351. }
  352. }
  353. }
  354. #endif
  355. }
  356. /************************************************************************************************************************/
  357. #if UNITY_ASSERTIONS
  358. /// <summary>Should <see cref="OptionalWarning.EndEventInterrupt"/> be logged?</summary>
  359. private bool ShouldLogEndEventInterrupt(Action callback)
  360. {
  361. if (_LoggedEndEventInterrupt ||
  362. _Events == null ||
  363. _Events.OnEnd != callback)
  364. return false;
  365. var layer = _State.Layer;
  366. if (_BeforeEndLayer != layer ||
  367. _BeforeEndCommandCount != layer.CommandCount ||
  368. !_State.Root.IsGraphPlaying ||
  369. !_State.IsPlaying)
  370. return false;
  371. var speed = _State.EffectiveSpeed;
  372. if (speed > 0)
  373. {
  374. return _State.NormalizedTime > _State.NormalizedEndTime;
  375. }
  376. else if (speed < 0)
  377. {
  378. return _State.NormalizedTime < _State.NormalizedEndTime;
  379. }
  380. else return false;// Speed 0.
  381. }
  382. #endif
  383. /************************************************************************************************************************/
  384. #endregion
  385. /************************************************************************************************************************/
  386. /// <summary>Notifies this dispatcher that the target's <see cref="Time"/> has changed.</summary>
  387. internal void OnTimeChanged()
  388. {
  389. _PreviousTime = _State.NormalizedTime;
  390. _NextEventIndex = RecalculateEventIndex;
  391. }
  392. /************************************************************************************************************************/
  393. /// <summary>If the state has zero length, trigger its end event every frame.</summary>
  394. private void UpdateZeroLength()
  395. {
  396. var speed = _State.EffectiveSpeed;
  397. if (speed == 0)
  398. return;
  399. if (_Events.Count > 0)
  400. {
  401. var sequenceVersion = _Events.Version;
  402. int playDirectionInt;
  403. if (speed < 0)
  404. {
  405. playDirectionInt = -1;
  406. if (_NextEventIndex == RecalculateEventIndex ||
  407. _SequenceVersion != sequenceVersion ||
  408. _WasPlayingForwards)
  409. {
  410. _NextEventIndex = Events.Count - 1;
  411. _SequenceVersion = sequenceVersion;
  412. _WasPlayingForwards = false;
  413. }
  414. }
  415. else
  416. {
  417. playDirectionInt = 1;
  418. if (_NextEventIndex == RecalculateEventIndex ||
  419. _SequenceVersion != sequenceVersion ||
  420. !_WasPlayingForwards)
  421. {
  422. _NextEventIndex = 0;
  423. _SequenceVersion = sequenceVersion;
  424. _WasPlayingForwards = true;
  425. }
  426. }
  427. if (!InvokeAllEvents(1, playDirectionInt))
  428. return;
  429. }
  430. var endEvent = _Events.EndEvent;
  431. if (endEvent.callback != null)
  432. endEvent.Invoke(_State);
  433. }
  434. /************************************************************************************************************************/
  435. private void CheckGeneralEvents(float currentTime)
  436. {
  437. var count = _Events.Count;
  438. if (count == 0)
  439. {
  440. _NextEventIndex = 0;
  441. return;
  442. }
  443. ValidateNextEventIndex(ref currentTime, out var playDirectionFloat, out var playDirectionInt);
  444. if (_IsLooping)// Looping.
  445. {
  446. var animancerEvent = _Events[_NextEventIndex];
  447. var eventTime = animancerEvent.normalizedTime * playDirectionFloat;
  448. var loopDelta = GetLoopDelta(_PreviousTime, currentTime, eventTime);
  449. if (loopDelta == 0)
  450. return;
  451. // For each additional loop, invoke all events without needing to check their times.
  452. if (!InvokeAllEvents(loopDelta - 1, playDirectionInt))
  453. return;
  454. var loopStartIndex = _NextEventIndex;
  455. Invoke:
  456. animancerEvent.Invoke(_State);
  457. if (!NextEventLooped(playDirectionInt) ||
  458. _NextEventIndex == loopStartIndex)
  459. return;
  460. animancerEvent = _Events[_NextEventIndex];
  461. eventTime = animancerEvent.normalizedTime * playDirectionFloat;
  462. if (loopDelta == GetLoopDelta(_PreviousTime, currentTime, eventTime))
  463. goto Invoke;
  464. }
  465. else// Non-Looping.
  466. {
  467. while ((uint)_NextEventIndex < (uint)count)
  468. {
  469. var animancerEvent = _Events[_NextEventIndex];
  470. var eventTime = animancerEvent.normalizedTime * playDirectionFloat;
  471. if (currentTime <= eventTime)
  472. return;
  473. animancerEvent.Invoke(_State);
  474. if (!NextEvent(playDirectionInt))
  475. return;
  476. }
  477. }
  478. }
  479. /************************************************************************************************************************/
  480. private void ValidateNextEventIndex(ref float currentTime,
  481. out float playDirectionFloat, out int playDirectionInt)
  482. {
  483. var sequenceVersion = _Events.Version;
  484. if (currentTime < _PreviousTime)// Playing Backwards.
  485. {
  486. var previousTime = _PreviousTime;
  487. _PreviousTime = -previousTime;
  488. currentTime = -currentTime;
  489. playDirectionFloat = -1;
  490. playDirectionInt = -1;
  491. if (_NextEventIndex == RecalculateEventIndex ||
  492. _SequenceVersion != sequenceVersion ||
  493. _WasPlayingForwards)
  494. {
  495. _NextEventIndex = _Events.Count - 1;
  496. _SequenceVersion = sequenceVersion;
  497. _WasPlayingForwards = false;
  498. if (_IsLooping)
  499. previousTime = AnimancerUtilities.Wrap01(previousTime);
  500. while (_Events[_NextEventIndex].normalizedTime > previousTime)
  501. {
  502. _NextEventIndex--;
  503. if (_NextEventIndex < 0)
  504. {
  505. if (_IsLooping)
  506. _NextEventIndex = _Events.Count - 1;
  507. break;
  508. }
  509. }
  510. _Events.AssertNormalizedTimes(_State, _IsLooping);
  511. }
  512. }
  513. else// Playing Forwards.
  514. {
  515. playDirectionFloat = 1;
  516. playDirectionInt = 1;
  517. if (_NextEventIndex == RecalculateEventIndex ||
  518. _SequenceVersion != sequenceVersion ||
  519. !_WasPlayingForwards)
  520. {
  521. _NextEventIndex = 0;
  522. _SequenceVersion = sequenceVersion;
  523. _WasPlayingForwards = true;
  524. var previousTime = _PreviousTime;
  525. if (_IsLooping)
  526. previousTime = AnimancerUtilities.Wrap01(previousTime);
  527. var max = _Events.Count - 1;
  528. while (_Events[_NextEventIndex].normalizedTime < previousTime)
  529. {
  530. _NextEventIndex++;
  531. if (_NextEventIndex > max)
  532. {
  533. if (_IsLooping)
  534. _NextEventIndex = 0;
  535. break;
  536. }
  537. }
  538. _Events.AssertNormalizedTimes(_State, _IsLooping);
  539. }
  540. }
  541. // This method could be slightly optimised for playback direction changes by using the current index
  542. // as the starting point instead of iterating from the edge of the sequence, but that would make it
  543. // significantly more complex for something that shouldn't happen very often and would only matter if
  544. // there are lots of events (in which case the optimisation would be tiny compared to the cost of
  545. // actually invoking all those events and running the rest of the application).
  546. }
  547. /************************************************************************************************************************/
  548. /// <summary>
  549. /// Calculates the number of times an event at `eventTime` should be invoked when the
  550. /// <see cref="NormalizedTime"/> goes from `previousTime` to `nextTime` on a looping animation.
  551. /// </summary>
  552. private static int GetLoopDelta(float previousTime, float nextTime, float eventTime)
  553. {
  554. previousTime -= eventTime;
  555. nextTime -= eventTime;
  556. var previousLoopCount = Mathf.FloorToInt(previousTime);
  557. var nextLoopCount = Mathf.FloorToInt(nextTime);
  558. var loopCount = nextLoopCount - previousLoopCount;
  559. // Previous time must be inclusive.
  560. // And next time must be exclusive.
  561. // So if the previous time is exactly on a looped increment of the event time, count one more.
  562. // And if the next time is exactly on a looped increment of the event time, count one less.
  563. if (previousTime == previousLoopCount)
  564. loopCount++;
  565. if (nextTime == nextLoopCount)
  566. loopCount--;
  567. return loopCount;
  568. }
  569. /************************************************************************************************************************/
  570. private bool InvokeAllEvents(int count, int playDirectionInt)
  571. {
  572. var loopStartIndex = _NextEventIndex;
  573. while (count-- > 0)
  574. {
  575. do
  576. {
  577. _Events[_NextEventIndex].Invoke(_State);
  578. if (!NextEventLooped(playDirectionInt))
  579. return false;
  580. }
  581. while (_NextEventIndex != loopStartIndex);
  582. }
  583. return true;
  584. }
  585. /************************************************************************************************************************/
  586. private bool NextEvent(int playDirectionInt)
  587. {
  588. if (_NextEventIndex == RecalculateEventIndex)
  589. return false;
  590. if (_Events.Version != _SequenceVersion)
  591. throw new InvalidOperationException(SequenceVersionException);
  592. _NextEventIndex += playDirectionInt;
  593. return true;
  594. }
  595. /************************************************************************************************************************/
  596. private bool NextEventLooped(int playDirectionInt)
  597. {
  598. if (!NextEvent(playDirectionInt))
  599. return false;
  600. var count = _Events.Count;
  601. if (_NextEventIndex >= count)
  602. _NextEventIndex = 0;
  603. else if (_NextEventIndex < 0)
  604. _NextEventIndex = count - 1;
  605. return true;
  606. }
  607. /************************************************************************************************************************/
  608. /// <summary>Returns "<see cref="EventDispatcher"/> (Target State)".</summary>
  609. public override string ToString()
  610. {
  611. return _State != null ?
  612. $"{nameof(EventDispatcher)} ({_State})" :
  613. $"{nameof(EventDispatcher)} (No Target State)";
  614. }
  615. /************************************************************************************************************************/
  616. }
  617. /************************************************************************************************************************/
  618. }
  619. }