2025-05-09 15:40:34 +08:00

170 lines
7.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
{
/// <summary>[Pro-Only] An object that can be updated during Animancer's animation updates.</summary>
///
/// <remarks>
/// <strong>Example:</strong>
/// Register to receive updates using <see cref="AnimancerGraph.RequirePreUpdate"/> or
/// <see cref="AnimancerGraph.RequirePostUpdate"/> and stop
/// receiving updates using <see cref="AnimancerGraph.CancelPreUpdate"/> or
/// <see cref="AnimancerGraph.CancelPostUpdate"/>.
/// <para></para><code>
/// public sealed class MyUpdatable : IUpdatable
/// {
/// // Implement IUpdatable.
/// // You can avoid this by inheriting from Updatable instead.
/// int IUpdatable.UpdatableIndex { get; set; } = IUpdatable.List.NotInList;
///
/// private AnimancerComponent _Animancer;
///
/// public void StartUpdating(AnimancerComponent animancer)
/// {
/// _Animancer = animancer;
///
/// // If you want Update to be called before the playables get updated.
/// _Animancer.Graph.RequirePreUpdate(this);
///
/// // If you want Update to be called after the playables get updated.
/// _Animancer.Graph.RequirePostUpdate(this);
/// }
///
/// public void StopUpdating()
/// {
/// // If you used RequirePreUpdate.
/// _Animancer.Graph.CancelPreUpdate(this);
///
/// // If you used RequirePostUpdate.
/// _Animancer.Graph.CancelPostUpdate(this);
/// }
///
/// void IUpdatable.Update()
/// {
/// // Called during every animation update.
///
/// // AnimancerGraph.Current can be used to access the system it is being updated by.
/// }
/// }
/// </code></remarks>
///
/// https://kybernetik.com.au/animancer/api/Animancer/IUpdatable
///
public interface IUpdatable
{
/************************************************************************************************************************/
/// <summary>The index of this object in its <see cref="IndexedList{TItem, TIndexer}"/>.</summary>
/// <remarks>Should be initialized to -1 to indicate that this object is not yet in a list.</remarks>
int UpdatableIndex { get; set; }
/// <summary>Updates this object.</summary>
void Update();
/************************************************************************************************************************/
/// <summary>An <see cref="IIndexer{T}"/> for <see cref="IUpdatable"/>.</summary>
public readonly struct Indexer : IIndexer<IUpdatable>
{
/************************************************************************************************************************/
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly int GetIndex(IUpdatable item)
=> item.UpdatableIndex;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void SetIndex(IUpdatable item, int index)
=> item.UpdatableIndex = index;
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void ClearIndex(IUpdatable item)
=> item.UpdatableIndex = -1;
/************************************************************************************************************************/
}
/************************************************************************************************************************/
/// <summary>An <see cref="IndexedList{TItem, TAccessor}"/> of <see cref="IUpdatable"/>.</summary>
public class List : IndexedList<IUpdatable, Indexer>
{
/************************************************************************************************************************/
/// <summary>The default <see cref="IndexedList{TItem, TIndexer}.Capacity"/> for newly created lists.</summary>
/// <remarks>Default value is 4.</remarks>
public static new int DefaultCapacity { get; set; } = 4;
/************************************************************************************************************************/
/// <summary>Creates a new <see cref="List"/> with the <see cref="DefaultCapacity"/>.</summary>
public List()
: base(DefaultCapacity, new())
{ }
/************************************************************************************************************************/
/// <summary>Calls <see cref="Update"/> on all items in this list.</summary>
/// <remarks>
/// Uses <see cref="Debug.LogException(Exception, Object)"/> to handle exceptions and continues executing
/// the remaining items if any occur.
/// </remarks>
public void UpdateAll()
{
BeginEnumeraton();
ContinueEnumeration:
try
{
while (TryEnumerateNext())
{
Current.Update();
}
}
catch (Exception exception)
{
Debug.LogException(exception, AnimancerGraph.Current?.Component as Object);
goto ContinueEnumeration;
}
}
/************************************************************************************************************************/
/// <summary>Clones any <see cref="ICloneable{T}"/> items.</summary>
public void CloneFrom(
List copyFrom,
CloneContext context)
{
var count = copyFrom.Count;
for (int i = 0; i < count; i++)
if (copyFrom[i] is ICloneable<object> cloneable &&
context.GetOrCreateClone(cloneable) is IUpdatable clone)
Add(clone);
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
}
/// https://kybernetik.com.au/animancer/api/Animancer/AnimancerUtilities
public static partial class AnimancerUtilities
{
/************************************************************************************************************************/
/// <summary>Is the `updatable` currently in a list?</summary>
public static bool IsInList(this IUpdatable updatable)
=> updatable.UpdatableIndex >= 0;
/************************************************************************************************************************/
}
}