// 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
/************************************************************************************************************************/
}
}
}