123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
- using System;
- using UnityEngine;
- namespace Animancer
- {
- /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerEvent
- partial struct AnimancerEvent
- {
- /// https://kybernetik.com.au/animancer/api/Animancer/Sequence
- partial class Sequence
- {
- /// <summary>
- /// Serializable data which can be used to construct an <see cref="Sequence"/> using
- /// <see cref="StringAsset"/>s and <see cref="IInvokable"/>s.
- /// </summary>
- /// <remarks>
- /// <strong>Documentation:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer/serialization">
- /// Serialized Events</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/Serializable
- [Serializable]
- public class Serializable : ICloneable<Serializable>
- #if UNITY_EDITOR
- , ISerializationCallbackReceiver
- #endif
- {
- /************************************************************************************************************************/
- #region Fields and Properties
- /************************************************************************************************************************/
- [SerializeField]
- private float[] _NormalizedTimes;
- /// <summary>[<see cref="SerializeField"/>] The serialized <see cref="normalizedTime"/>s.</summary>
- /// <remarks>The last item is used for the <see cref="EndEvent"/>.</remarks>
- public ref float[] NormalizedTimes => ref _NormalizedTimes;
- /************************************************************************************************************************/
- [SerializeReference, Polymorphic]
- private IInvokable[] _Callbacks;
- /// <summary>[<see cref="SerializeField"/>] The serialized <see cref="callback"/>s.</summary>
- /// <remarks>
- /// This array only needs to be large enough to hold the last item that isn't null.
- /// <para></para>
- /// If this array is larger than the <see cref="NormalizedTimes"/>, the first item
- /// with no corresponding time will be used as the <see cref="OnEnd"/> callback
- /// and any others after that will be ignored.
- /// </remarks>
- public ref IInvokable[] Callbacks => ref _Callbacks;
- /************************************************************************************************************************/
- [SerializeField]
- private StringAsset[] _Names;
- /// <summary>[<see cref="SerializeField"/>] The serialized <see cref="Sequence.Names"/>.</summary>
- public ref StringAsset[] Names => ref _Names;
- /************************************************************************************************************************/
- #if UNITY_EDITOR
- /************************************************************************************************************************/
- /// <summary>[Editor-Only] [Internal]
- /// The name of the array field which stores the <see cref="normalizedTime"/>s.
- /// </summary>
- internal const string NormalizedTimesField = nameof(_NormalizedTimes);
- /// <summary>[Editor-Only] [Internal]
- /// The name of the array field which stores the serialized <see cref="Callbacks"/>.
- /// </summary>
- internal const string CallbacksField = nameof(_Callbacks);
- /// <summary>[Editor-Only] [Internal]
- /// The name of the array field which stores the serialized <see cref="Names"/>.
- /// </summary>
- internal const string NamesField = nameof(_Names);
- /************************************************************************************************************************/
- #endif
- /************************************************************************************************************************/
- private Sequence _Events;
- /// <summary>Returns the <see cref="Events"/> or <c>null</c> if it wasn't yet initialized.</summary>
- public Sequence InitializedEvents
- => _Events;
- /// <summary>
- /// The runtime <see cref="Sequence"/> compiled from this <see cref="Serializable"/>.
- /// Each call after the first will return the same reference.
- /// </summary>
- /// <remarks>
- /// Unlike <see cref="GetEventsOptional"/>, this property will create an empty
- /// <see cref="Sequence"/> instead of returning null if there are no events.
- /// </remarks>
- public Sequence Events
- {
- get
- {
- if (_Events == null)
- {
- GetEventsOptional();
- _Events ??= new();
- }
- return _Events;
- }
- set => _Events = value;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Initialization
- /************************************************************************************************************************/
- /// <summary>
- /// Returns the runtime <see cref="Sequence"/> compiled from this <see cref="Serializable"/>.
- /// Each call after the first will return the same reference.
- /// </summary>
- /// <remarks>
- /// This method returns null if the sequence would be empty anyway and is used by the implicit
- /// conversion from <see cref="Serializable"/> to <see cref="Sequence"/>.
- /// </remarks>
- public Sequence GetEventsOptional()
- {
- if (_Events != null ||
- _NormalizedTimes == null)
- return _Events;
- var timeCount = _NormalizedTimes.Length;
- if (timeCount == 0)
- return null;
- var callbackCount = _Callbacks != null
- ? _Callbacks.Length
- : 0;
- var callback = callbackCount >= timeCount--
- ? GetInvoke(_Callbacks[timeCount])
- : null;
- var endEvent = new AnimancerEvent(_NormalizedTimes[timeCount], callback);
- _Events = new(timeCount)
- {
- EndEvent = endEvent,
- Count = timeCount,
- Names = StringAsset.ToStringReferences(_Names),
- };
- var events = _Events._Events;
- for (int i = 0; i < timeCount; i++)
- {
- callback = i < callbackCount
- ? GetInvoke(_Callbacks[i])
- : InvokeBoundCallback;
- events[i] = new(_NormalizedTimes[i], callback);
- }
- return _Events;
- }
- /// <summary>Calls <see cref="GetEventsOptional"/>.</summary>
- public static implicit operator Sequence(Serializable serializable)
- => serializable?.GetEventsOptional();
- /************************************************************************************************************************/
- /// <summary>
- /// Returns the <see cref="IInvokable.Invoke"/> if the `invokable` isn't <c>null</c>.
- /// Otherwise, returns <c>null</c>.
- /// </summary>
- public static Action GetInvoke(IInvokable invokable)
- => invokable != null
- ? invokable.Invoke
- : InvokeBoundCallback;
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region End Event
- /************************************************************************************************************************/
- /// <summary>Returns the <see cref="normalizedTime"/> of the <see cref="EndEvent"/>.</summary>
- /// <remarks>If the value is not set, the value is determined by <see cref="GetDefaultNormalizedEndTime"/>.</remarks>
- public float GetNormalizedEndTime(float speed = 1)
- {
- return _NormalizedTimes.IsNullOrEmpty()
- ? GetDefaultNormalizedEndTime(speed)
- : _NormalizedTimes[^1];
- }
- /************************************************************************************************************************/
- /// <summary>Sets the <see cref="normalizedTime"/> of the <see cref="EndEvent"/>.</summary>
- public void SetNormalizedEndTime(float normalizedTime)
- {
- if (_NormalizedTimes.IsNullOrEmpty())
- _NormalizedTimes = new float[] { normalizedTime };
- else
- _NormalizedTimes[^1] = normalizedTime;
- }
- /************************************************************************************************************************/
- /// <summary>Sets the <see cref="callback"/> of the <see cref="EndEvent"/>.</summary>
- public void SetEndCallback(IInvokable callback = null)
- {
- if (_NormalizedTimes.IsNullOrEmpty())
- _NormalizedTimes = new float[] { float.NaN };
- InsertOptionalItem(ref _Callbacks, _NormalizedTimes.Length - 1, callback);
- }
- /************************************************************************************************************************/
- /// <summary>Sets the data of the <see cref="EndEvent"/>.</summary>
- public void SetEndEvent(float normalizedTime = float.NaN, IInvokable callback = null)
- {
- if (_NormalizedTimes.IsNullOrEmpty())
- _NormalizedTimes = new float[] { normalizedTime };
- else
- _NormalizedTimes[^1] = normalizedTime;
- InsertOptionalItem(ref _Callbacks, _NormalizedTimes.Length - 1, callback);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Other Events
- /************************************************************************************************************************/
- /// <summary>Adds an event to the serialized fields.</summary>
- public int AddEvent(float normalizedTime, IInvokable callback = null, StringAsset name = null)
- {
- int index;
- if (_NormalizedTimes.IsNullOrEmpty())
- {
- _NormalizedTimes = new float[] { normalizedTime, float.NaN };
- index = 0;
- }
- else
- {
- index = _NormalizedTimes.Length - 1;
- for (int i = 0; i < _NormalizedTimes.Length - 1; i++)
- {
- if (_NormalizedTimes[i] > normalizedTime)
- {
- index = i;
- break;
- }
- }
- AnimancerUtilities.InsertAt(ref _NormalizedTimes, index, normalizedTime);
- }
- InsertOptionalItem(ref _Callbacks, index, callback);
- InsertOptionalItem(ref _Names, index, name);
- return index;
- }
- /************************************************************************************************************************/
- /// <summary>Inserts an `item` at the specified `index` in an optional `array`.</summary>
- /// <remarks>
- /// If the `item` is <c>null</c> then the array only needs
- /// to be expanded if it was already larger than the `index`.
- /// </remarks>
- private static void InsertOptionalItem<T>(ref T[] array, int index, T item)
- where T : class
- {
- if (item == null &&
- (array == null || array.Length < index))
- return;
- AnimancerUtilities.InsertAt(ref array, index, item);
- }
- /************************************************************************************************************************/
- /// <summary>Removes an event from the serialized fields.</summary>
- public void RemoveEvent(int index)
- {
- if (_NormalizedTimes.IsNullOrEmpty())
- return;
- AnimancerUtilities.RemoveAt(ref _NormalizedTimes, index);
- if (_Callbacks != null && _Callbacks.Length > index)
- AnimancerUtilities.RemoveAt(ref _Callbacks, index);
- if (_Names != null && _Names.Length > index)
- AnimancerUtilities.RemoveAt(ref _Names, index);
- }
- /************************************************************************************************************************/
- /// <summary>Removes all events.</summary>
- public void Clear(bool keepEndEvent = false)
- {
- if (keepEndEvent)
- {
- if (_NormalizedTimes != null && _NormalizedTimes.Length > 0)
- _NormalizedTimes = new float[] { _NormalizedTimes[^1] };
- else
- _NormalizedTimes = null;
- if (_Callbacks != null && _Callbacks.Length > 0)
- _Callbacks = new IInvokable[] { _Callbacks[^1] };
- else
- _Callbacks = null;
- }
- else
- {
- _NormalizedTimes = null;
- _Callbacks = null;
- }
- _Names = null;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Copying
- /************************************************************************************************************************/
- /// <summary>Creates a new <see cref="Serializable"/> and copies the contents of <c>this</c> into it.</summary>
- /// <remarks>To copy into an existing sequence, use <see cref="CopyFrom"/> instead.</remarks>
- public Serializable Clone()
- {
- var clone = new Serializable();
- clone.CopyFrom(this);
- return clone;
- }
- /// <inheritdoc/>
- public Serializable Clone(CloneContext context)
- => Clone();
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public void CopyFrom(Serializable copyFrom)
- {
- if (copyFrom == null)
- {
- _NormalizedTimes = default;
- _Callbacks = default;
- _Names = default;
- return;
- }
- AnimancerUtilities.CopyExactArray(copyFrom._NormalizedTimes, ref _NormalizedTimes);
- AnimancerUtilities.CopyExactArray(copyFrom._Callbacks, ref _Callbacks);
- AnimancerUtilities.CopyExactArray(copyFrom._Names, ref _Names);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Serialization
- /************************************************************************************************************************/
- #if UNITY_EDITOR
- /************************************************************************************************************************/
- /// <summary>[Editor-Only] Does nothing.</summary>
- void ISerializationCallbackReceiver.OnAfterDeserialize() { }
- /************************************************************************************************************************/
- /// <summary>[Editor-Only] [Internal]
- /// Called by <see cref="ISerializationCallbackReceiver.OnBeforeSerialize"/>.
- /// </summary>
- internal static event Action<Serializable> OnBeforeSerialize;
- /// <summary>[Editor-Only] Ensures that the events are sorted by time (excluding the end event).</summary>
- void ISerializationCallbackReceiver.OnBeforeSerialize()
- => OnBeforeSerialize?.Invoke(this);
- /************************************************************************************************************************/
- /// <summary>[Editor-Only] [Internal]
- /// Should the arrays be prevented from reducing their size when their last elements are unused?
- /// </summary>
- internal static bool DisableCompactArrays { get; set; }
- /// <summary>[Editor-Only] [Internal]
- /// Removes empty data from the ends of the arrays to reduce the serialized data size.
- /// </summary>
- internal void CompactArrays()
- {
- if (DisableCompactArrays)
- return;
- // If there is only one time and it is NaN, we don't need to store anything.
- if (_NormalizedTimes == null ||
- (_NormalizedTimes.Length == 1 &&
- (_Callbacks == null || _Callbacks.Length == 0) &&
- (_Names == null || _Names.Length == 0) &&
- float.IsNaN(_NormalizedTimes[0])))
- {
- _NormalizedTimes = Array.Empty<float>();
- _Callbacks = Array.Empty<IInvokable>();
- _Names = Array.Empty<StringAsset>();
- return;
- }
- Trim(ref _Callbacks, _NormalizedTimes.Length, callback => callback != null);
- Trim(ref _Names, _NormalizedTimes.Length, name => name != null);
- }
- /************************************************************************************************************************/
- /// <summary>[Editor-Only] Removes unimportant values from the end of the `array`.</summary>
- private static void Trim<T>(ref T[] array, int maxLength, Func<T, bool> isImportant)
- {
- if (array == null)
- return;
- var count = Math.Min(array.Length, maxLength);
- while (count >= 1)
- {
- var item = array[count - 1];
- if (isImportant(item))
- break;
- else
- count--;
- }
- Array.Resize(ref array, count);
- }
- /************************************************************************************************************************/
- #endif
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
- }
- }
|