// Animancer // Copyright 2020 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] /// Allows animations to be procedurally gathered throughout the hierarchy without needing explicit references. /// </summary> /// <remarks> /// This class is [Editor-Only] because it uses reflection and is not particularly efficient, but it does not /// actually use any Editor Only functionality so it could be made usable at runtime by simply removing the /// <c>#if UNITY_EDITOR</c> at the top of the file and <c>#endif</c> at the bottom. /// </remarks> public static class AnimationGatherer { /************************************************************************************************************************/ private const int MaxFieldDepth = 7; /************************************************************************************************************************/ private static readonly HashSet<object> RecursionGuard = new HashSet<object>(); 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(); } /************************************************************************************************************************/ /// <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) { if (!BeginRecursionGuard(gameObject)) return; try { _CallCount++; var clipSet = clips as HashSet<AnimationClip>; if (clipSet == null) clipSet = ObjectPool.AcquireSet<AnimationClip>(); GatherFromComponents(gameObject, clipSet); if (clipSet != clips) { clips.Gather(clipSet); ObjectPool.Release(clipSet); } } 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, ref AnimationClip[] clips, bool sort) { if (!BeginRecursionGuard(gameObject)) return; try { _CallCount++; var clipSet = ObjectPool.AcquireSet<AnimationClip>(); GatherFromComponents(gameObject, clipSet); if (clips == null || clips.Length != clipSet.Count) clips = new AnimationClip[clipSet.Count]; clipSet.CopyTo(clips); ObjectPool.Release(clipSet); if (sort) Array.Sort(clips, (a, b) => a.name.CompareTo(b.name)); } finally { _CallCount--; EndCall(); } } /************************************************************************************************************************/ private static void GatherFromComponents(GameObject gameObject, HashSet<AnimationClip> clips) { var root = AnimancerEditorUtilities.FindRoot(gameObject); var components = ObjectPool.AcquireList<MonoBehaviour>(); root.GetComponentsInChildren(true, components); GatherFromComponents(components, clips); ObjectPool.Release(components); } /************************************************************************************************************************/ private static void GatherFromComponents(List<MonoBehaviour> components, HashSet<AnimationClip> clips) { var i = components.Count; GatherClips: try { while (--i >= 0) { GatherFromObject(components[i], clips, 0); } } catch (Exception ex) { // If something throws an exception, log it and go to the next object. Debug.LogException(ex); goto GatherClips; } } /************************************************************************************************************************/ /// <summary> /// Gathers all animations from the `source`s fields. /// </summary> private static void GatherFromObject(object source, ICollection<AnimationClip> clips, int depth) { if (!BeginRecursionGuard(source)) return; try { if (clips.GatherFromSource(source)) return; } finally { RecursionGuard.Remove(source); } GatherFromFields(source, clips, depth); } /************************************************************************************************************************/ /// <summary>Types mapped to a delegate that can quickly gather their clips.</summary> private static readonly Dictionary<Type, Action<object, ICollection<AnimationClip>>> TypeToGatherer = new Dictionary<Type, Action<object, ICollection<AnimationClip>>>(); /// <summary> /// Uses reflection to gather <see cref="AnimationClip"/>s from fields on the `source` object. /// </summary> private static void GatherFromFields(object source, ICollection<AnimationClip> clips, int depth) { if (depth >= MaxFieldDepth || source == null || !BeginRecursionGuard(source)) return; var type = source.GetType(); Action<object, ICollection<AnimationClip>> gatherClips; if (!TypeToGatherer.TryGetValue(type, out gatherClips)) { gatherClips = BuildClipGatherer(type, depth); TypeToGatherer.Add(type, gatherClips); } if (gatherClips != null) gatherClips(source, clips); } /************************************************************************************************************************/ /// <summary> /// Creates a delegate to gather <see cref="AnimationClip"/>s from all relevant fields in a given `type`. /// </summary> private static Action<object, ICollection<AnimationClip>> BuildClipGatherer(Type type, int depth) { if (type.IsPrimitive || type.IsEnum || type.IsAutoClass || type.IsPointer) return null; Action<object, ICollection<AnimationClip>> gatherer = null; while (type != null) { var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); for (int i = 0; i < fields.Length; i++) { var field = fields[i]; var fieldType = field.FieldType; if (fieldType.IsPrimitive || fieldType.IsEnum || fieldType.IsAutoClass || fieldType.IsPointer) continue; if (fieldType == typeof(AnimationClip)) { gatherer += (obj, clips) => { var clip = (AnimationClip)field.GetValue(obj); clips.Gather(clip); }; } else if (typeof(IAnimationClipSource).IsAssignableFrom(fieldType) || typeof(IAnimationClipCollection).IsAssignableFrom(fieldType)) { gatherer += (obj, clips) => { var source = field.GetValue(obj); clips.GatherFromSource(source); }; } else if (typeof(ICollection).IsAssignableFrom(fieldType)) { gatherer += (obj, clips) => { var collection = (ICollection)field.GetValue(obj); if (collection != null) { foreach (var item in collection) { GatherFromObject(item, clips, depth + 1); } } }; } else { gatherer += (obj, clips) => { var source = field.GetValue(obj); if (source == null) return; var sourceObject = source as Object; if (!ReferenceEquals(sourceObject, null) && sourceObject == null) return; GatherFromObject(source, clips, depth + 1); }; } } type = type.BaseType; } return gatherer; } /************************************************************************************************************************/ } } #endif