// 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 { /// [Editor-Only] /// Allows animations to be procedurally gathered throughout the hierarchy without needing explicit references. /// /// /// 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 /// #if UNITY_EDITOR at the top of the file and #endif at the bottom. /// public static class AnimationGatherer { /************************************************************************************************************************/ private const int MaxFieldDepth = 7; /************************************************************************************************************************/ private static readonly HashSet RecursionGuard = new HashSet(); 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(); } /************************************************************************************************************************/ /// /// Fills the `clips` with any s referenced by components in the same hierarchy as /// the `gameObject`. See for details. /// public static void GatherFromGameObject(GameObject gameObject, ICollection clips) { if (!BeginRecursionGuard(gameObject)) return; try { _CallCount++; var clipSet = clips as HashSet; if (clipSet == null) clipSet = ObjectPool.AcquireSet(); GatherFromComponents(gameObject, clipSet); if (clipSet != clips) { clips.Gather(clipSet); ObjectPool.Release(clipSet); } } finally { _CallCount--; EndCall(); } } /// /// Fills the `clips` with any s referenced by components in the same hierarchy as /// the `gameObject`. See for details. /// public static void GatherFromGameObject(GameObject gameObject, ref AnimationClip[] clips, bool sort) { if (!BeginRecursionGuard(gameObject)) return; try { _CallCount++; var clipSet = ObjectPool.AcquireSet(); 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 clips) { var root = AnimancerEditorUtilities.FindRoot(gameObject); var components = ObjectPool.AcquireList(); root.GetComponentsInChildren(true, components); GatherFromComponents(components, clips); ObjectPool.Release(components); } /************************************************************************************************************************/ private static void GatherFromComponents(List components, HashSet 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; } } /************************************************************************************************************************/ /// /// Gathers all animations from the `source`s fields. /// private static void GatherFromObject(object source, ICollection clips, int depth) { if (!BeginRecursionGuard(source)) return; try { if (clips.GatherFromSource(source)) return; } finally { RecursionGuard.Remove(source); } GatherFromFields(source, clips, depth); } /************************************************************************************************************************/ /// Types mapped to a delegate that can quickly gather their clips. private static readonly Dictionary>> TypeToGatherer = new Dictionary>>(); /// /// Uses reflection to gather s from fields on the `source` object. /// private static void GatherFromFields(object source, ICollection clips, int depth) { if (depth >= MaxFieldDepth || source == null || !BeginRecursionGuard(source)) return; var type = source.GetType(); Action> gatherClips; if (!TypeToGatherer.TryGetValue(type, out gatherClips)) { gatherClips = BuildClipGatherer(type, depth); TypeToGatherer.Add(type, gatherClips); } if (gatherClips != null) gatherClips(source, clips); } /************************************************************************************************************************/ /// /// Creates a delegate to gather s from all relevant fields in a given `type`. /// private static Action> BuildClipGatherer(Type type, int depth) { if (type.IsPrimitive || type.IsEnum || type.IsAutoClass || type.IsPointer) return null; Action> 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