// Animancer // Copyright 2020 Kybernetik // using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Animancer { partial class AnimancerPlayable { /// /// A dictionary of s mapped to their . /// public sealed class StateDictionary : IEnumerable>, IAnimationClipCollection { /************************************************************************************************************************/ /// The at the root of the graph. private readonly AnimancerPlayable Root; /// mapped to . private readonly Dictionary States = new Dictionary(FastComparer.Instance); /************************************************************************************************************************/ /// [Internal] Constructs a new . internal StateDictionary(AnimancerPlayable root) { Root = root; } /************************************************************************************************************************/ /// The number of states that have been registered with a . public int Count { get { return States.Count; } } /************************************************************************************************************************/ internal void Clear() { States.Clear(); } /************************************************************************************************************************/ #region Create /************************************************************************************************************************/ /// /// Creates and returns a new to play the `clip`. /// /// This method uses to determine the . /// /// To create a state on a different layer, call animancer.Layers[x].CreateState(clip) instead. /// public ClipState Create(AnimationClip clip) { return Root.Layers[0].CreateState(clip); } /// /// Creates and returns a new to play the `clip` and registers it with the `key`. /// /// To create a state on a different layer, call animancer.Layers[x].CreateState(key, clip) instead. /// public ClipState Create(object key, AnimationClip clip) { return Root.Layers[0].CreateState(key, clip); } /************************************************************************************************************************/ /// /// Calls for each of the specified clips. /// /// If you only want to create a single state, use . /// public void CreateIfNew(AnimationClip clip0, AnimationClip clip1) { GetOrCreate(clip0); GetOrCreate(clip1); } /// /// Calls for each of the specified clips. /// /// If you only want to create a single state, use . /// public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2) { GetOrCreate(clip0); GetOrCreate(clip1); GetOrCreate(clip2); } /// /// Calls for each of the specified clips. /// /// If you only want to create a single state, use . /// public void CreateIfNew(AnimationClip clip0, AnimationClip clip1, AnimationClip clip2, AnimationClip clip3) { GetOrCreate(clip0); GetOrCreate(clip1); GetOrCreate(clip2); GetOrCreate(clip3); } /// /// Calls for each of the specified `clips`. /// /// If you only want to create a single state, use . /// 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 /************************************************************************************************************************/ /// /// The on layer 0. /// /// 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 itself will not register in /// this property. /// public AnimancerState Current { get { return Root.Layers[0].CurrentState; } } /************************************************************************************************************************/ /// /// Calls then returns the state registered with that key, or null if none exists. /// public AnimancerState this[AnimationClip clip] { get { if (clip != null) return this[Root.GetKey(clip)]; else return null; } } /// /// Returns the state registered with the , or null if none exists. /// public AnimancerState this[IHasKey hasKey] { get { return this[hasKey.Key]; } } /// /// Returns the state registered with the `key`, or null if none exists. /// public AnimancerState this[object key] { get { AnimancerState state; TryGet(key, out state); return state; } } /************************************************************************************************************************/ /// /// Calls then passes the key to /// and returns the result. /// public bool TryGet(AnimationClip clip, out AnimancerState state) { if (clip != null) { return States.TryGetValue(Root.GetKey(clip), out state); } else { state = null; return false; } } /// /// Passes the into /// and returns the result. /// public bool TryGet(IHasKey hasKey, out AnimancerState state) { if (hasKey != null) { return States.TryGetValue(hasKey.Key, out state); } else { state = null; return false; } } /// /// If a state is registered with the `key`, this method outputs it as the `state` and returns true. Otherwise /// `state` is set to null and this method returns false. /// public bool TryGet(object key, out AnimancerState state) { return States.TryGetValue(key, out state); } /************************************************************************************************************************/ /// /// Calls and returns the state which registered with that key or /// creates one if it doesn't exist. /// /// If the state already exists but has the wrong , the `allowSetClip` /// parameter determines what will happen. False causes it to throw an while /// true allows it to change the . Note that the change is somewhat costly to /// performance to use with caution. /// /// public AnimancerState GetOrCreate(AnimationClip clip, bool allowSetClip = false) { return GetOrCreate(Root.GetKey(clip), clip, allowSetClip); } /// /// Returns the state registered with the `transition`s if there is one. Otherwise /// this method uses to create a new one and registers it with /// that key before returning it. /// public AnimancerState GetOrCreate(ITransition transition) { var key = transition.Key; AnimancerState state; if (!States.TryGetValue(key, out state)) { state = transition.CreateState(Root.Layers[0]); Root.States.Register(key, state); } return state; } /// /// Returns the state which registered with the `key` or creates one if it doesn't exist. /// /// If the state already exists but has the wrong , the `allowSetClip` /// parameter determines what will happen. False causes it to throw an while /// true allows it to change the . Note that the change is somewhat costly to /// performance to use with caution. /// /// /// Thrown if the `key` is null. /// See also: public AnimancerState GetOrCreate(object key, AnimationClip clip, bool allowSetClip = false) { if (key == null) throw new ArgumentNullException("key"); AnimancerState state; if (States.TryGetValue(key, out 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(string.Concat( "A state already exists using the specified 'key', but has a different AnimationClip:", "\n - Key: ", key.ToString(), "\n - Existing Clip: ", state.Clip.ToString(), "\n - New Clip: ", clip.ToString())); } } } else { state = Root.Layers[0].CreateState(key, clip); } return state; } /************************************************************************************************************************/ /// [Internal] /// Registers the `state` in this dictionary so the `key` can be used to get it later on using /// . /// internal void Register(object key, AnimancerState state) { if (key != null) States.Add(key, state); state._Key = key; } /// [Internal] /// Removes the `state` from this dictionary. /// internal void Unregister(AnimancerState state) { if (state._Key == null) return; States.Remove(state._Key); state._Key = null; } /************************************************************************************************************************/ #region Enumeration /************************************************************************************************************************/ // IEnumerable for 'foreach' statements. /************************************************************************************************************************/ /// /// Returns an enumerator that will iterate through all states in each layer (not states inside mixers). /// public IEnumerator> GetEnumerator() { return States.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /************************************************************************************************************************/ /// [] /// Gathers all the animations in all layers. /// public void GatherAnimationClips(ICollection clips) { foreach (var state in States.Values) clips.GatherFromSource(state); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Destroy /************************************************************************************************************************/ /// /// Calls on the state associated with the `clip` (if any). /// Returns true if the state existed. /// public bool Destroy(AnimationClip clip) { if (clip == null) return false; return Destroy(Root.GetKey(clip)); } /// /// Calls on the state associated with the /// (if any). Returns true if the state existed. /// public bool Destroy(IHasKey hasKey) { if (hasKey == null) return false; return Destroy(hasKey.Key); } /// /// Calls on the state associated with the `key` (if any). /// Returns true if the state existed. /// public bool Destroy(object key) { if (key == null) return false; AnimancerState state; if (States.TryGetValue(key, out state)) { state.Destroy(); return true; } return false; } /************************************************************************************************************************/ /// /// Calls on each of the `clips`. /// public void DestroyAll(IList clips) { if (clips == null) return; for (int i = 0; i < clips.Count; i++) Destroy(clips[i]); } /// /// Calls on each of the `clips`. /// public void DestroyAll(IEnumerable clips) { if (clips == null) return; foreach (var clip in clips) Destroy(clip); } /************************************************************************************************************************/ /// /// Calls on all states gathered by /// . /// public void DestroyAll(IAnimationClipSource source) { if (source == null) return; var clips = ObjectPool.AcquireList(); for (int i = 0; i < clips.Count; i++) Destroy(clips[i]); ObjectPool.Release(clips); } /// /// Calls on all states gathered by /// . /// public void DestroyAll(IAnimationClipCollection source) { if (source == null) return; var clips = ObjectPool.AcquireSet(); foreach (var clip in clips) Destroy(clip); ObjectPool.Release(clips); } /************************************************************************************************************************/ /// /// Destroys all states connected to all layers (regardless of whether they are actually registered in this /// dictionary). /// public void DestroyAll() { var count = Root.Layers.Count; while (--count >= 0) Root.Layers._Layers[count].DestroyStates(); States.Clear(); } /************************************************************************************************************************/ #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. /************************************************************************************************************************/ /// [Warning] /// You should not use an as a key. /// The whole point of a key is to identify a state in the first place. /// [System.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] { get { return key; } } /// [Warning] /// You should not use an as a key. /// The whole point of a key is to identify a state in the first place. /// [System.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; } /// [Warning] /// You should not use an as a key. /// The whole point of a key is to identify a state in the first place. /// [System.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) { return key; } /// [Warning] /// You should not use an as a key. /// Just call . /// [System.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 /************************************************************************************************************************/ } } }