123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
- #if UNITY_EDITOR
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Reflection;
- using UnityEngine;
- using Object = UnityEngine.Object;
- namespace Animancer.Editor
- {
- /// <summary>[Editor-Only]
- /// A system that procedurally gathers animations throughout the hierarchy without needing explicit references.
- /// </summary>
- /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimationGatherer
- ///
- public class AnimationGatherer : IAnimationClipCollection
- {
- /************************************************************************************************************************/
- #region Recursion Guard
- /************************************************************************************************************************/
- private const int MaxFieldDepth = 7;
- /************************************************************************************************************************/
- private static readonly HashSet<object>
- RecursionGuard = new();
- private static int _CallCount;
- private static bool BeginRecursionGuard(object obj)
- {
- if (RecursionGuard.Contains(obj))
- return false;
- RecursionGuard.Add(obj);
- return true;
- }
- private static void EndCall()
- {
- if (_CallCount == 0)
- RecursionGuard.Clear();
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Fields and Accessors
- /************************************************************************************************************************/
- /// <summary>All the <see cref="AnimationClip"/>s that have been gathered.</summary>
- public readonly HashSet<AnimationClip> Clips = new();
- /// <summary>All the <see cref="ITransition"/>s that have been gathered.</summary>
- public readonly HashSet<ITransition> Transitions = new();
- /************************************************************************************************************************/
- /// <inheritdoc/>
- public void GatherAnimationClips(ICollection<AnimationClip> clips)
- {
- try
- {
- foreach (var clip in Clips)
- clips.Add(clip);
- foreach (var transition in Transitions)
- clips.GatherFromSource(transition);
- }
- catch (Exception exception)
- {
- HandleException(exception);
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Cache
- /************************************************************************************************************************/
- private static readonly Dictionary<GameObject, AnimationGatherer>
- ObjectToGatherer = new();
- /************************************************************************************************************************/
- static AnimationGatherer()
- {
- UnityEditor.EditorApplication.hierarchyChanged += ClearCache;
- UnityEditor.Selection.selectionChanged += ClearCache;
- }
- /************************************************************************************************************************/
- /// <summary>Clears all cached gatherers.</summary>
- public static void ClearCache() => ObjectToGatherer.Clear();
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- /// <summary>Should exceptions thrown while gathering animations be logged? Default is false to ignore them.</summary>
- public static bool logExceptions;
- /// <summary>Logs the `exception` if <see cref="logExceptions"/> is true. Otherwise does nothing.</summary>
- private static void HandleException(Exception exception)
- {
- if (logExceptions)
- Debug.LogException(exception);
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Returns a cached <see cref="AnimationGatherer"/> containing any <see cref="AnimationClip"/>s referenced by
- /// components in the same hierarchy as the `gameObject`. See <see cref="ICharacterRoot"/> for details.
- /// </summary>
- public static AnimationGatherer GatherFromGameObject(GameObject gameObject)
- {
- if (!BeginRecursionGuard(gameObject))
- return null;
- try
- {
- _CallCount++;
- if (!ObjectToGatherer.TryGetValue(gameObject, out var gatherer))
- {
- gatherer = new();
- ObjectToGatherer.Add(gameObject, gatherer);
- gatherer.GatherFromComponents(gameObject);
- }
- return gatherer;
- }
- catch (Exception exception)
- {
- HandleException(exception);
- return null;
- }
- finally
- {
- _CallCount--;
- EndCall();
- }
- }
- /// <summary>
- /// Fills the `clips` with any <see cref="AnimationClip"/>s referenced by components in the same hierarchy as
- /// the `gameObject`. See <see cref="ICharacterRoot"/> for details.
- /// </summary>
- public static void GatherFromGameObject(GameObject gameObject, ICollection<AnimationClip> clips)
- {
- var gatherer = GatherFromGameObject(gameObject);
- gatherer?.GatherAnimationClips(clips);
- }
- /// <summary>
- /// Fills the `clips` with any <see cref="AnimationClip"/>s referenced by components in the same hierarchy as
- /// the `gameObject`. See <see cref="ICharacterRoot"/> for details.
- /// </summary>
- public static void GatherFromGameObject(GameObject gameObject, ref AnimationClip[] clips, bool sort)
- {
- var gatherer = GatherFromGameObject(gameObject);
- if (gatherer == null)
- return;
- using (SetPool<AnimationClip>.Instance.Acquire(out var clipSet))
- {
- gatherer.GatherAnimationClips(clipSet);
- AnimancerUtilities.SetLength(ref clips, clipSet.Count);
- clipSet.CopyTo(clips);
- }
- if (sort)
- Array.Sort(clips, (a, b) => a.GetCachedName().CompareTo(b.GetCachedName()));
- }
- /************************************************************************************************************************/
- private void GatherFromComponents(GameObject gameObject)
- {
- var root = AnimancerUtilities.FindRoot(gameObject);
- using (ListPool<MonoBehaviour>.Instance.Acquire(out var components))
- {
- root.GetComponentsInChildren(true, components);
- GatherFromComponents(components);
- }
- }
- /************************************************************************************************************************/
- private void GatherFromComponents(List<MonoBehaviour> components)
- {
- var i = components.Count;
- GatherClips:
- try
- {
- while (--i >= 0)
- {
- GatherFromObject(components[i], 0);
- }
- }
- catch (Exception exception)
- {
- HandleException(exception);
- goto GatherClips;
- }
- }
- /************************************************************************************************************************/
- /// <summary>Gathers all animations from the `source`s fields.</summary>
- private void GatherFromObject(object source, int depth)
- {
- if (source == null)
- return;
- if (source is AnimationClip clip)
- {
- Clips.Add(clip);
- return;
- }
- if (!MightContainAnimations(source.GetType()))
- return;
- if (!BeginRecursionGuard(source))
- return;
- try
- {
- if (Clips.GatherFromSource(source))
- return;
- }
- catch (Exception exception)
- {
- HandleException(exception);
- }
- finally
- {
- RecursionGuard.Remove(source);
- }
- GatherFromFields(source, depth);
- }
- /************************************************************************************************************************/
- /// <summary>Types mapped to a delegate that can quickly gather their clips.</summary>
- private static readonly Dictionary<Type, Action<object, AnimationGatherer>>
- TypeToGathererDelegate = new();
- /// <summary>
- /// Uses reflection to gather <see cref="AnimationClip"/>s from fields on the `source` object.
- /// </summary>
- private void GatherFromFields(object source, int depth)
- {
- if (depth >= MaxFieldDepth ||
- source == null ||
- !BeginRecursionGuard(source))
- return;
- var type = source.GetType();
- if (!TypeToGathererDelegate.TryGetValue(type, out var gatherClips))
- {
- gatherClips = BuildClipGathererDelegate(type, depth);
- TypeToGathererDelegate.Add(type, gatherClips);
- }
- gatherClips?.Invoke(source, this);
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Creates a delegate to gather <see cref="AnimationClip"/>s from all relevant fields in a given `type`.
- /// </summary>
- private static Action<object, AnimationGatherer> BuildClipGathererDelegate(Type type, int depth)
- {
- if (!MightContainAnimations(type))
- return null;
- Action<object, AnimationGatherer> gathererDelegate = null;
- while (type != null)
- {
- var fields = type.GetFields(AnimancerReflection.InstanceBindings | BindingFlags.DeclaredOnly);
- for (int i = 0; i < fields.Length; i++)
- {
- var field = fields[i];
- var fieldType = field.FieldType;
- if (!MightContainAnimations(fieldType))
- continue;
- if (fieldType == typeof(AnimationClip))
- {
- gathererDelegate += (obj, gatherer) =>
- {
- var clip = (AnimationClip)field.GetValue(obj);
- gatherer.Clips.Gather(clip);
- };
- }
- else if (typeof(IAnimationClipSource).IsAssignableFrom(fieldType) ||
- typeof(IAnimationClipCollection).IsAssignableFrom(fieldType))
- {
- gathererDelegate += (obj, gatherer) =>
- {
- var source = field.GetValue(obj);
- gatherer.Clips.GatherFromSource(source);
- };
- }
- else if (typeof(ICollection).IsAssignableFrom(fieldType))
- {
- gathererDelegate += (obj, gatherer) =>
- {
- var collection = (ICollection)field.GetValue(obj);
- if (collection != null)
- {
- foreach (var item in collection)
- {
- gatherer.GatherFromObject(item, depth + 1);
- }
- }
- };
- }
- else
- {
- gathererDelegate += (obj, gatherer) =>
- {
- var source = field.GetValue(obj);
- if (source == null ||
- (source is Object sourceObject && sourceObject == null))
- return;
- gatherer.GatherFromObject(source, depth + 1);
- };
- }
- }
- type = type.BaseType;
- }
- return gathererDelegate;
- }
- /************************************************************************************************************************/
- private static bool MightContainAnimations(Type type)
- => !type.IsPrimitive
- && !type.IsEnum
- && !type.IsAutoClass
- && !type.IsPointer;
- /************************************************************************************************************************/
- }
- }
- #endif
|