123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Text;
- using UnityEngine;
- namespace Animancer
- {
- /// <summary>A dictionary of <see cref="AnimancerState"/>s mapped to their <see cref="AnimancerState.Key"/>.</summary>
- /// <remarks>
- /// <strong>Documentation:</strong>
- /// <see href="https://kybernetik.com.au/animancer/docs/manual/playing/states">
- /// States</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerStateDictionary
- public class AnimancerStateDictionary :
- IAnimationClipCollection,
- IEnumerable<AnimancerState>
- {
- /************************************************************************************************************************/
- /// <summary>The <see cref="AnimancerGraph"/> at the root of the graph.</summary>
- private readonly AnimancerGraph Graph;
- /************************************************************************************************************************/
- /// <summary><see cref="AnimancerState.Key"/> mapped to <see cref="AnimancerState"/>.</summary>
- private readonly Dictionary<object, AnimancerState>
- States = new();
- /************************************************************************************************************************/
- /// <summary>[Internal] Creates a new <see cref="AnimancerStateDictionary"/>.</summary>
- internal AnimancerStateDictionary(AnimancerGraph graph)
- => Graph = graph;
- /************************************************************************************************************************/
- /// <summary>The number of states that have been registered with a <see cref="AnimancerState.Key"/>.</summary>
- public int Count
- => States.Count;
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public void AppendDescriptionOrOrphans(
- StringBuilder text,
- string separator = "\n")
- {
- string stateSeparator = null;
- foreach (var state in States.Values)
- {
- if (state.Parent != null)
- continue;
- if (stateSeparator is null)
- {
- text.Append(separator)
- .Append("Orphan States:");
- separator += Strings.Indent;
- stateSeparator = separator + Strings.Indent;
- }
- text.Append(separator);
- state.AppendDescription(text, stateSeparator);
- }
- }
- /************************************************************************************************************************/
- #region Create
- /************************************************************************************************************************/
- /// <summary>Creates and returns a new <see cref="ClipState"/> to play the `clip`.</summary>
- /// <remarks>
- /// To create a state on a specific layer, use <c>animancer.Layers[x].CreateState(clip)</c> instead.
- /// <para></para>
- /// <see cref="AnimancerGraph.GetKey"/> is used to determine the <see cref="AnimancerState.Key"/>.
- /// </remarks>
- public ClipState Create(AnimationClip clip)
- => Create(Graph.GetKey(clip), clip);
- /// <summary>
- /// Creates and returns a new <see cref="ClipState"/> to play the `clip` and registers it with the `key`.
- /// </summary>
- /// <remarks>
- /// To create a state on a specific layer, use <c>animancer.Layers[x].CreateState(key, clip)</c> instead.
- /// </remarks>
- public ClipState Create(object key, AnimationClip clip)
- {
- var state = new ClipState(clip);
- state.SetGraph(Graph);
- state._Key = key;
- Register(state);
- return state;
- }
- /************************************************************************************************************************/
- /// <summary>Calls <see cref="GetOrCreate(AnimationClip, bool)"/> for each of the specified clips.</summary>
- public void CreateIfNew(AnimationClip clip0, AnimationClip clip1)
- {
- GetOrCreate(clip0);
- GetOrCreate(clip1);
- }
- /// <summary>Calls <see cref="GetOrCreate(AnimationClip, bool)"/> for each of the specified clips.</summary>
- public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2)
- {
- GetOrCreate(clip0);
- GetOrCreate(clip1);
- GetOrCreate(clip2);
- }
- /// <summary>Calls <see cref="GetOrCreate(AnimationClip, bool)"/> for each of the specified clips.</summary>
- public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2, AnimationClip clip3)
- {
- GetOrCreate(clip0);
- GetOrCreate(clip1);
- GetOrCreate(clip2);
- GetOrCreate(clip3);
- }
- /// <summary>Calls <see cref="GetOrCreate(AnimationClip, bool)"/> for each of the specified `clips`.</summary>
- public void CreateIfNew(params AnimationClip[] clips)
- {
- if (clips == null)
- return;
- var count = clips.Length;
- for (int i = 0; i < count; i++)
- {
- var clip = clips[i];
- if (clip != null)
- GetOrCreate(clip);
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Access
- /************************************************************************************************************************/
- /// <summary>
- /// The <see cref="AnimancerLayer.CurrentState"/> on layer 0.
- /// <para></para>
- /// Specifically, this is the state that was most recently started using any of the Play methods on that layer.
- /// States controlled individually via methods in the <see cref="AnimancerState"/> itself will not register in
- /// this property.
- /// </summary>
- public AnimancerState Current
- => Graph.Layers[0].CurrentState;
- /************************************************************************************************************************/
- /// <summary>Calls <see cref="AnimancerGraph.GetKey"/> then returns the state registered with that key.</summary>
- /// <exception cref="ArgumentNullException">The key is null.</exception>
- /// <exception cref="KeyNotFoundException">No state is registered with the key.</exception>
- public AnimancerState this[AnimationClip clip]
- => States[Graph.GetKey(clip)];
- /// <summary>Returns the state registered with the <see cref="IHasKey.Key"/>.</summary>
- /// <exception cref="ArgumentNullException">The `key` is null.</exception>
- /// <exception cref="KeyNotFoundException">No state is registered with the `key`.</exception>
- public AnimancerState this[IHasKey hasKey]
- => States[hasKey.Key];
- /// <summary>Returns the state registered with the `key`.</summary>
- /// <exception cref="ArgumentNullException">The `key` is null.</exception>
- /// <exception cref="KeyNotFoundException">No state is registered with the `key`.</exception>
- public AnimancerState this[object key]
- => States[key];
- /************************************************************************************************************************/
- /// <summary>
- /// Calls <see cref="AnimancerGraph.GetKey"/> then passes the key to
- /// <see cref="TryGet(object, out AnimancerState)"/> and returns the result.
- /// </summary>
- public bool TryGet(AnimationClip clip, out AnimancerState state)
- {
- if (clip == null)
- {
- state = null;
- return false;
- }
- return TryGet(Graph.GetKey(clip), out state);
- }
- /// <summary>
- /// Passes the <see cref="IHasKey.Key"/> into <see cref="TryGet(object, out AnimancerState)"/>
- /// and returns the result.
- /// </summary>
- public bool TryGet(IHasKey hasKey, out AnimancerState state)
- {
- if (hasKey == null)
- {
- state = null;
- return false;
- }
- return TryGet(hasKey.Key, out state);
- }
- /// <summary>
- /// If a `state` is registered with the `key`, this method outputs it and returns true. Otherwise the
- /// `state` is set to null and this method returns false.
- /// </summary>
- public bool TryGet(object key, out AnimancerState state)
- {
- if (key == null)
- {
- state = null;
- return false;
- }
- return States.TryGetValue(key, out state);
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Calls <see cref="AnimancerGraph.GetKey"/> and returns the state registered with that key
- /// or creates one if it doesn't exist.
- /// </summary>
- /// <remarks>
- /// If the state already exists but has the wrong <see cref="AnimancerState.Clip"/>, the `allowSetClip`
- /// parameter determines what will happen. False causes it to throw an <see cref="ArgumentException"/> while
- /// true allows it to change the <see cref="AnimancerState.Clip"/>. Note that the change is somewhat costly to
- /// performance so use with caution.
- /// </remarks>
- /// <exception cref="ArgumentException"/>
- public AnimancerState GetOrCreate(AnimationClip clip, bool allowSetClip = false)
- => GetOrCreate(Graph.GetKey(clip), clip, allowSetClip);
- /// <summary>
- /// Returns the state registered with the `transition`s <see cref="IHasKey.Key"/> if there is one.
- /// Otherwise this method uses <see cref="ITransition.CreateState"/> to create a new one
- /// and registers it with that key before returning it.
- /// </summary>
- public AnimancerState GetOrCreate(ITransition transition)
- {
- var key = transition.Key;
- if (!TryGet(key, out var state))
- {
- state = transition.CreateState();
- state._Key = key;
- state.SetGraph(Graph);
- }
- return state;
- }
- /// <summary>
- /// Returns the state which registered with the `key` or creates one if it doesn't exist.
- /// <para></para>
- /// If the state already exists but has the wrong <see cref="AnimancerState.Clip"/>, the `allowSetClip`
- /// parameter determines what will happen. False causes it to throw an <see cref="ArgumentException"/>
- /// while true allows it to change the <see cref="AnimancerState.Clip"/>.
- /// Note that the change is somewhat costly to performance so use with caution.
- /// </summary>
- /// <exception cref="ArgumentException"/>
- /// <remarks>See also: <see cref="AnimancerLayer.GetOrCreateState(object, AnimationClip, bool)"/></remarks>
- public AnimancerState GetOrCreate(object key, AnimationClip clip, bool allowSetClip = false)
- {
- if (TryGet(key, out var state))
- {
- // If a state exists with the 'key' but has the wrong clip, either change it or complain.
- if (!ReferenceEquals(state.Clip, clip))
- {
- if (allowSetClip)
- {
- state.Clip = clip;
- }
- else
- {
- throw new ArgumentException(GetClipMismatchError(key, state.Clip, clip));
- }
- }
- }
- else
- {
- state = Create(key, clip);
- }
- return state;
- }
- /************************************************************************************************************************/
- /// <summary>Returns an error message explaining that a state already exists with the specified `key`.</summary>
- public static string GetClipMismatchError(object key, AnimationClip oldClip, AnimationClip newClip)
- => $"A state already exists using the specified '{nameof(key)}', but has a different {nameof(AnimationClip)}:" +
- $"\n• Key: {key}" +
- $"\n• Old Clip: {oldClip}" +
- $"\n• New Clip: {newClip}";
- /************************************************************************************************************************/
- /// <summary>[Internal]
- /// Registers the `state` in this dictionary so the <see cref="AnimancerState.Key"/> can be used to get it
- /// later on using any of the lookup methods such as <see cref="this[object]"/> or
- /// <see cref="TryGet(object, out AnimancerState)"/>.
- /// </summary>
- /// <remarks>Does nothing if the <see cref="AnimancerState.Key"/> is <c>null</c>.</remarks>
- internal void Register(AnimancerState state)
- {
- var key = state._Key;
- if (key != null)
- {
- #if UNITY_ASSERTIONS
- if (state.Graph != Graph)
- throw new ArgumentException(
- $"{nameof(AnimancerStateDictionary)} cannot register a state with a different {nameof(Graph)}: " + state);
- #endif
- States.Add(key, state);
- }
- }
- /// <summary>[Internal] Removes the `state` from this dictionary (the opposite of <see cref="Register"/>).</summary>
- internal void Unregister(AnimancerState state)
- {
- var key = state._Key;
- if (key != null)
- States.Remove(key);
- }
- /************************************************************************************************************************/
- #region Enumeration
- /************************************************************************************************************************/
- // IEnumerable for 'foreach' statements.
- /************************************************************************************************************************/
- /// <summary>Returns an enumerator that will iterate through all registered states.</summary>
- public Dictionary<object, AnimancerState>.ValueCollection.Enumerator GetEnumerator()
- => States.Values.GetEnumerator();
- /// <inheritdoc/>
- IEnumerator<AnimancerState> IEnumerable<AnimancerState>.GetEnumerator()
- => GetEnumerator();
- /// <inheritdoc/>
- IEnumerator IEnumerable.GetEnumerator()
- => GetEnumerator();
- /************************************************************************************************************************/
- /// <summary>[<see cref="IAnimationClipCollection"/>]
- /// Adds all the animations of states with a <see cref="AnimancerState.Key"/> to the `clips`.
- /// </summary>
- public void GatherAnimationClips(ICollection<AnimationClip> clips)
- {
- foreach (var state in States.Values)
- clips.GatherFromSource(state);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Destroy
- /************************************************************************************************************************/
- /// <summary>
- /// Calls <see cref="AnimancerState.Destroy"/> on the state associated with the `clip` (if any).
- /// Returns true if the state existed.
- /// </summary>
- public bool Destroy(AnimationClip clip)
- {
- if (clip == null)
- return false;
- return Destroy(Graph.GetKey(clip));
- }
- /// <summary>
- /// Calls <see cref="AnimancerState.Destroy"/> on the state associated with the <see cref="IHasKey.Key"/>
- /// (if any). Returns true if the state existed.
- /// </summary>
- public bool Destroy(IHasKey hasKey)
- {
- if (hasKey == null)
- return false;
- return Destroy(hasKey.Key);
- }
- /// <summary>
- /// Calls <see cref="AnimancerState.Destroy"/> on the state associated with the `key` (if any).
- /// Returns true if the state existed.
- /// </summary>
- public bool Destroy(object key)
- {
- if (!TryGet(key, out var state))
- return false;
- state.Destroy();
- return true;
- }
- /************************************************************************************************************************/
- /// <summary>Calls <see cref="Destroy(AnimationClip)"/> on each of the `clips`.</summary>
- public void DestroyAll(IList<AnimationClip> clips)
- {
- if (clips == null)
- return;
- for (int i = clips.Count - 1; i >= 0; i--)
- Destroy(clips[i]);
- }
- /// <summary>Calls <see cref="Destroy(AnimationClip)"/> on each of the `clips`.</summary>
- public void DestroyAll(IEnumerable<AnimationClip> clips)
- {
- if (clips == null)
- return;
- foreach (var clip in clips)
- Destroy(clip);
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Calls <see cref="Destroy(AnimationClip)"/> on all states gathered by
- /// <see cref="IAnimationClipSource.GetAnimationClips"/>.
- /// </summary>
- public void DestroyAll(IAnimationClipSource source)
- {
- if (source == null)
- return;
- var clips = ListPool.Acquire<AnimationClip>();
- source.GetAnimationClips(clips);
- DestroyAll(clips);
- ListPool.Release(clips);
- }
- /// <summary>
- /// Calls <see cref="Destroy(AnimationClip)"/> on all states gathered by
- /// <see cref="IAnimationClipCollection.GatherAnimationClips"/>.
- /// </summary>
- public void DestroyAll(IAnimationClipCollection source)
- {
- if (source == null)
- return;
- var clips = SetPool.Acquire<AnimationClip>();
- source.GatherAnimationClips(clips);
- DestroyAll(clips);
- SetPool.Release(clips);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Key Error Methods
- #if UNITY_EDITOR
- /************************************************************************************************************************/
- // These are overloads of other methods that take a System.Object key to ensure the user doesn't try to use an
- // AnimancerState as a key, since the whole point of a key is to identify a state in the first place.
- /************************************************************************************************************************/
- /// <summary>[Warning]
- /// You should not use an <see cref="AnimancerState"/> as a key.
- /// The whole point of a key is to identify a state in the first place.
- /// </summary>
- [Obsolete("You should not use an AnimancerState as a key. The whole point of a key is to identify a state in the first place.", true)]
- public AnimancerState this[AnimancerState key]
- => key;
- /// <summary>[Warning]
- /// You should not use an <see cref="AnimancerState"/> as a key.
- /// The whole point of a key is to identify a state in the first place.
- /// </summary>
- [Obsolete("You should not use an AnimancerState as a key. The whole point of a key is to identify a state in the first place.", true)]
- public bool TryGet(AnimancerState key, out AnimancerState state)
- {
- state = key;
- return true;
- }
- /// <summary>[Warning]
- /// You should not use an <see cref="AnimancerState"/> as a key.
- /// The whole point of a key is to identify a state in the first place.
- /// </summary>
- [Obsolete("You should not use an AnimancerState as a key. The whole point of a key is to identify a state in the first place.", true)]
- public AnimancerState GetOrCreate(AnimancerState key, AnimationClip clip)
- => key;
- /// <summary>[Warning]
- /// You should not use an <see cref="AnimancerState"/> as a key.
- /// Just call <see cref="AnimancerState.Destroy"/>.
- /// </summary>
- [Obsolete("You should not use an AnimancerState as a key. Just call AnimancerState.Destroy.", true)]
- public bool Destroy(AnimancerState key)
- {
- key.Destroy();
- return true;
- }
- /************************************************************************************************************************/
- #endif
- #endregion
- /************************************************************************************************************************/
- }
- }
|