// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // using System; using System.Runtime.CompilerServices; using UnityEngine; using Object = UnityEngine.Object; namespace Animancer { /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerEvent partial struct AnimancerEvent { /// An and other associated details used to invoke it. /// https://kybernetik.com.au/animancer/api/Animancer/Invocation public readonly struct Invocation { /************************************************************************************************************************/ /// The details of the event currently being triggered. /// Cleared after the event is invoked. public static Invocation Current { get; private set; } /************************************************************************************************************************/ /// The . public readonly AnimancerEvent Event; /// The name of the . public readonly StringReference Name; /// The triggering the . public readonly AnimancerState State; /************************************************************************************************************************/ /// Creates a new . public Invocation( AnimancerEvent animancerEvent, StringReference eventName, AnimancerState state) { Event = animancerEvent; State = state; Name = eventName; } /************************************************************************************************************************/ /// /// Sets the , invokes the , /// then reverts the . /// /// This method catches and logs any exception thrown by the . /// The is null. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly void Invoke() { #if UNITY_ASSERTIONS var oldLayer = State.Layer; var oldCommandCount = oldLayer.CommandCount; #endif var previous = Current; var parameter = CurrentParameter; Current = this; CurrentParameter = null; try { Event.callback(); } catch (Exception exception) { Debug.LogException(exception, State?.Graph?.Component as Object); } Current = previous; CurrentParameter = parameter; #if UNITY_ASSERTIONS if (Name == EndEventName) AssertEndEventInvoked(oldLayer, oldCommandCount); #endif } /************************************************************************************************************************/ /// /// Returns the callback registered in the /// with the (or null if there isn't one). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Action GetBoundCallback() => Name.IsNullOrEmpty() ? null : State.Graph._Events?.Get(Name); /************************************************************************************************************************/ /// Returns a string describing the contents of this invocation. public override string ToString() => $"{nameof(AnimancerEvent)}.{nameof(Invocation)}(" + $"{nameof(Name)}={AnimancerUtilities.ToStringOrNull(Name)}, " + $"NormalizedTime={Event.normalizedTime:0.##}, " + $"Callback=({AnimancerReflection.ToStringDetailed(Event.callback)}), " + $"{nameof(State)}={AnimancerUtilities.ToStringOrNull(State)})"; /************************************************************************************************************************/ /// /// Invokes the callback bound to the in the . /// /// /// Logs if no callback is bound. /// public void InvokeBoundCallback() { if (Name != null && State.Graph._Events != null && State.Graph._Events.TryGetValue(Name, out var callback)) { callback(); } #if UNITY_ASSERTIONS else if (OptionalWarning.UselessEvent.IsEnabled()) { OptionalWarning.UselessEvent.Log( $"An {nameof(AnimancerEvent)} which does nothing was invoked." + $" Most likely it wasn't configured correctly." + $" Unused events should be removed to avoid wasting performance checking them." + $"\n• Name: {AnimancerUtilities.ToStringOrNull(Name)}" + $"\n• Normalized Time: {Event.normalizedTime}" + $"\n• State: {State}" + $"\n• Object: {AnimancerUtilities.ToStringOrNull(State.Graph?.Component)}", State.Graph?.Component); } #endif } /************************************************************************************************************************/ #if UNITY_ASSERTIONS /************************************************************************************************************************/ /// [Assert-Only] /// Call after invoking an end event to assert . /// private readonly void AssertEndEventInvoked(AnimancerLayer oldLayer, int oldCommandCount) { if (ShouldLogEndEventInterrupt(oldLayer, oldCommandCount)) { OptionalWarning.EndEventInterrupt.Log( $"An End Event callback didn't stop the animation." + $" Animancer doesn't handle End Events automatically," + $" so the controlling script is responsible for stopping the animation," + $" often by playing a different one." + $"\n• State: {State}" + $"\n• Callback: {Event.callback.ToStringDetailed()}" + $"\n• End Events are triggered every frame after their time has passed: {Strings.DocsURLs.EndEvents}" + $"\n• To avoid this behaviour, use a regular Animancer Event instead: {Strings.DocsURLs.AnimancerEvents}", State.Graph?.Component); OptionalWarning.EndEventInterrupt.Disable(); } } /************************************************************************************************************************/ /// [Assert-Only] Should be logged? private readonly bool ShouldLogEndEventInterrupt(AnimancerLayer oldLayer, int oldCommandCount) { if (!OptionalWarning.EndEventInterrupt.IsEnabled()) return false; var events = State.SharedEvents; if (events == null || events.OnEnd != Event.callback) return false; var newLayer = State.Layer; if (oldLayer != newLayer || oldCommandCount != newLayer.CommandCount || !State.Graph.IsGraphPlaying || !State.IsPlaying) return false; var speed = State.EffectiveSpeed; if (speed > 0) return State.NormalizedTime > State.NormalizedEndTime; else if (speed < 0) return State.NormalizedTime < State.NormalizedEndTime; else// Speed 0. return false; } /************************************************************************************************************************/ #endif /************************************************************************************************************************/ } } }