AnimancerEvent.Invocation.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. using System;
  3. using System.Runtime.CompilerServices;
  4. using UnityEngine;
  5. using Object = UnityEngine.Object;
  6. namespace Animancer
  7. {
  8. /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerEvent
  9. partial struct AnimancerEvent
  10. {
  11. /// <summary>An <see cref="AnimancerEvent"/> and other associated details used to invoke it.</summary>
  12. /// https://kybernetik.com.au/animancer/api/Animancer/Invocation
  13. public readonly struct Invocation
  14. {
  15. /************************************************************************************************************************/
  16. /// <summary>The details of the event currently being triggered.</summary>
  17. /// <remarks>Cleared after the event is invoked.</remarks>
  18. public static Invocation Current { get; private set; }
  19. /************************************************************************************************************************/
  20. /// <summary>The <see cref="AnimancerEvent"/>.</summary>
  21. public readonly AnimancerEvent Event;
  22. /// <summary>The name of the <see cref="Event"/>.</summary>
  23. public readonly StringReference Name;
  24. /// <summary>The <see cref="AnimancerState"/> triggering the <see cref="Event"/>.</summary>
  25. public readonly AnimancerState State;
  26. /************************************************************************************************************************/
  27. /// <summary>Creates a new <see cref="Invocation"/>.</summary>
  28. public Invocation(
  29. AnimancerEvent animancerEvent,
  30. StringReference eventName,
  31. AnimancerState state)
  32. {
  33. Event = animancerEvent;
  34. State = state;
  35. Name = eventName;
  36. }
  37. /************************************************************************************************************************/
  38. /// <summary>
  39. /// Sets the <see cref="Current"/>, invokes the <see cref="callback"/>,
  40. /// then reverts the <see cref="Current"/>.
  41. /// </summary>
  42. /// <remarks>This method catches and logs any exception thrown by the <see cref="callback"/>.</remarks>
  43. /// <exception cref="NullReferenceException">The <see cref="callback"/> is null.</exception>
  44. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  45. public readonly void Invoke()
  46. {
  47. #if UNITY_ASSERTIONS
  48. var oldLayer = State.Layer;
  49. var oldCommandCount = oldLayer.CommandCount;
  50. #endif
  51. var previous = Current;
  52. var parameter = CurrentParameter;
  53. Current = this;
  54. CurrentParameter = null;
  55. try
  56. {
  57. Event.callback();
  58. }
  59. catch (Exception exception)
  60. {
  61. Debug.LogException(exception, State?.Graph?.Component as Object);
  62. }
  63. Current = previous;
  64. CurrentParameter = parameter;
  65. #if UNITY_ASSERTIONS
  66. if (Name == EndEventName)
  67. AssertEndEventInvoked(oldLayer, oldCommandCount);
  68. #endif
  69. }
  70. /************************************************************************************************************************/
  71. /// <summary>
  72. /// Returns the callback registered in the <see cref="AnimancerGraph.Events"/>
  73. /// with the <see cref="Name"/> (or null if there isn't one).
  74. /// </summary>
  75. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  76. public readonly Action GetBoundCallback()
  77. => Name.IsNullOrEmpty()
  78. ? null
  79. : State.Graph._Events?.Get(Name);
  80. /************************************************************************************************************************/
  81. /// <summary>Returns a string describing the contents of this invocation.</summary>
  82. public override string ToString()
  83. => $"{nameof(AnimancerEvent)}.{nameof(Invocation)}(" +
  84. $"{nameof(Name)}={AnimancerUtilities.ToStringOrNull(Name)}, " +
  85. $"NormalizedTime={Event.normalizedTime:0.##}, " +
  86. $"Callback=({AnimancerReflection.ToStringDetailed(Event.callback)}), " +
  87. $"{nameof(State)}={AnimancerUtilities.ToStringOrNull(State)})";
  88. /************************************************************************************************************************/
  89. /// <summary>
  90. /// Invokes the callback bound to the <see cref="Name"/> in the <see cref="AnimancerGraph.Events"/>.
  91. /// </summary>
  92. /// <remarks>
  93. /// Logs <see cref="OptionalWarning.UselessEvent"/> if no callback is bound.
  94. /// </remarks>
  95. public void InvokeBoundCallback()
  96. {
  97. if (Name != null &&
  98. State.Graph._Events != null &&
  99. State.Graph._Events.TryGetValue(Name, out var callback))
  100. {
  101. callback();
  102. }
  103. #if UNITY_ASSERTIONS
  104. else if (OptionalWarning.UselessEvent.IsEnabled())
  105. {
  106. OptionalWarning.UselessEvent.Log(
  107. $"An {nameof(AnimancerEvent)} which does nothing was invoked." +
  108. $" Most likely it wasn't configured correctly." +
  109. $" Unused events should be removed to avoid wasting performance checking them." +
  110. $"\n• Name: {AnimancerUtilities.ToStringOrNull(Name)}" +
  111. $"\n• Normalized Time: {Event.normalizedTime}" +
  112. $"\n• State: {State}" +
  113. $"\n• Object: {AnimancerUtilities.ToStringOrNull(State.Graph?.Component)}",
  114. State.Graph?.Component);
  115. }
  116. #endif
  117. }
  118. /************************************************************************************************************************/
  119. #if UNITY_ASSERTIONS
  120. /************************************************************************************************************************/
  121. /// <summary>[Assert-Only]
  122. /// Call after invoking an end event to assert <see cref="OptionalWarning.EndEventInterrupt"/>.
  123. /// </summary>
  124. private readonly void AssertEndEventInvoked(AnimancerLayer oldLayer, int oldCommandCount)
  125. {
  126. if (ShouldLogEndEventInterrupt(oldLayer, oldCommandCount))
  127. {
  128. OptionalWarning.EndEventInterrupt.Log(
  129. $"An End Event callback didn't stop the animation." +
  130. $" Animancer doesn't handle End Events automatically," +
  131. $" so the controlling script is responsible for stopping the animation," +
  132. $" often by playing a different one." +
  133. $"\n• State: {State}" +
  134. $"\n• Callback: {Event.callback.ToStringDetailed()}" +
  135. $"\n• End Events are triggered every frame after their time has passed: {Strings.DocsURLs.EndEvents}" +
  136. $"\n• To avoid this behaviour, use a regular Animancer Event instead: {Strings.DocsURLs.AnimancerEvents}",
  137. State.Graph?.Component);
  138. OptionalWarning.EndEventInterrupt.Disable();
  139. }
  140. }
  141. /************************************************************************************************************************/
  142. /// <summary>[Assert-Only] Should <see cref="OptionalWarning.EndEventInterrupt"/> be logged?</summary>
  143. private readonly bool ShouldLogEndEventInterrupt(AnimancerLayer oldLayer, int oldCommandCount)
  144. {
  145. if (!OptionalWarning.EndEventInterrupt.IsEnabled())
  146. return false;
  147. var events = State.SharedEvents;
  148. if (events == null ||
  149. events.OnEnd != Event.callback)
  150. return false;
  151. var newLayer = State.Layer;
  152. if (oldLayer != newLayer ||
  153. oldCommandCount != newLayer.CommandCount ||
  154. !State.Graph.IsGraphPlaying ||
  155. !State.IsPlaying)
  156. return false;
  157. var speed = State.EffectiveSpeed;
  158. if (speed > 0)
  159. return State.NormalizedTime > State.NormalizedEndTime;
  160. else if (speed < 0)
  161. return State.NormalizedTime < State.NormalizedEndTime;
  162. else// Speed 0.
  163. return false;
  164. }
  165. /************************************************************************************************************************/
  166. #endif
  167. /************************************************************************************************************************/
  168. }
  169. }
  170. }