203 lines
9.2 KiB
C#
203 lines
9.2 KiB
C#
// 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
|
|
{
|
|
/// <summary>An <see cref="AnimancerEvent"/> and other associated details used to invoke it.</summary>
|
|
/// https://kybernetik.com.au/animancer/api/Animancer/Invocation
|
|
public readonly struct Invocation
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The details of the event currently being triggered.</summary>
|
|
/// <remarks>Cleared after the event is invoked.</remarks>
|
|
public static Invocation Current { get; private set; }
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The <see cref="AnimancerEvent"/>.</summary>
|
|
public readonly AnimancerEvent Event;
|
|
|
|
/// <summary>The name of the <see cref="Event"/>.</summary>
|
|
public readonly StringReference Name;
|
|
|
|
/// <summary>The <see cref="AnimancerState"/> triggering the <see cref="Event"/>.</summary>
|
|
public readonly AnimancerState State;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Creates a new <see cref="Invocation"/>.</summary>
|
|
public Invocation(
|
|
AnimancerEvent animancerEvent,
|
|
StringReference eventName,
|
|
AnimancerState state)
|
|
{
|
|
Event = animancerEvent;
|
|
State = state;
|
|
Name = eventName;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Sets the <see cref="Current"/>, invokes the <see cref="callback"/>,
|
|
/// then reverts the <see cref="Current"/>.
|
|
/// </summary>
|
|
/// <remarks>This method catches and logs any exception thrown by the <see cref="callback"/>.</remarks>
|
|
/// <exception cref="NullReferenceException">The <see cref="callback"/> is null.</exception>
|
|
[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
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Returns the callback registered in the <see cref="AnimancerGraph.Events"/>
|
|
/// with the <see cref="Name"/> (or null if there isn't one).
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public readonly Action GetBoundCallback()
|
|
=> Name.IsNullOrEmpty()
|
|
? null
|
|
: State.Graph._Events?.Get(Name);
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns a string describing the contents of this invocation.</summary>
|
|
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)})";
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Invokes the callback bound to the <see cref="Name"/> in the <see cref="AnimancerGraph.Events"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Logs <see cref="OptionalWarning.UselessEvent"/> if no callback is bound.
|
|
/// </remarks>
|
|
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
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Assert-Only]
|
|
/// Call after invoking an end event to assert <see cref="OptionalWarning.EndEventInterrupt"/>.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Assert-Only] Should <see cref="OptionalWarning.EndEventInterrupt"/> be logged?</summary>
|
|
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
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
}
|
|
|