AnimationGatherer.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Reflection;
  7. using UnityEngine;
  8. using Object = UnityEngine.Object;
  9. namespace Animancer.Editor
  10. {
  11. /// <summary>[Editor-Only]
  12. /// A system that procedurally gathers animations throughout the hierarchy without needing explicit references.
  13. /// </summary>
  14. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimationGatherer
  15. ///
  16. public class AnimationGatherer : IAnimationClipCollection
  17. {
  18. /************************************************************************************************************************/
  19. #region Recursion Guard
  20. /************************************************************************************************************************/
  21. private const int MaxFieldDepth = 7;
  22. /************************************************************************************************************************/
  23. private static readonly HashSet<object>
  24. RecursionGuard = new();
  25. private static int _CallCount;
  26. private static bool BeginRecursionGuard(object obj)
  27. {
  28. if (RecursionGuard.Contains(obj))
  29. return false;
  30. RecursionGuard.Add(obj);
  31. return true;
  32. }
  33. private static void EndCall()
  34. {
  35. if (_CallCount == 0)
  36. RecursionGuard.Clear();
  37. }
  38. /************************************************************************************************************************/
  39. #endregion
  40. /************************************************************************************************************************/
  41. #region Fields and Accessors
  42. /************************************************************************************************************************/
  43. /// <summary>All the <see cref="AnimationClip"/>s that have been gathered.</summary>
  44. public readonly HashSet<AnimationClip> Clips = new();
  45. /// <summary>All the <see cref="ITransition"/>s that have been gathered.</summary>
  46. public readonly HashSet<ITransition> Transitions = new();
  47. /************************************************************************************************************************/
  48. /// <inheritdoc/>
  49. public void GatherAnimationClips(ICollection<AnimationClip> clips)
  50. {
  51. try
  52. {
  53. foreach (var clip in Clips)
  54. clips.Add(clip);
  55. foreach (var transition in Transitions)
  56. clips.GatherFromSource(transition);
  57. }
  58. catch (Exception exception)
  59. {
  60. HandleException(exception);
  61. }
  62. }
  63. /************************************************************************************************************************/
  64. #endregion
  65. /************************************************************************************************************************/
  66. #region Cache
  67. /************************************************************************************************************************/
  68. private static readonly Dictionary<GameObject, AnimationGatherer>
  69. ObjectToGatherer = new();
  70. /************************************************************************************************************************/
  71. static AnimationGatherer()
  72. {
  73. UnityEditor.EditorApplication.hierarchyChanged += ClearCache;
  74. UnityEditor.Selection.selectionChanged += ClearCache;
  75. }
  76. /************************************************************************************************************************/
  77. /// <summary>Clears all cached gatherers.</summary>
  78. public static void ClearCache() => ObjectToGatherer.Clear();
  79. /************************************************************************************************************************/
  80. #endregion
  81. /************************************************************************************************************************/
  82. /// <summary>Should exceptions thrown while gathering animations be logged? Default is false to ignore them.</summary>
  83. public static bool logExceptions;
  84. /// <summary>Logs the `exception` if <see cref="logExceptions"/> is true. Otherwise does nothing.</summary>
  85. private static void HandleException(Exception exception)
  86. {
  87. if (logExceptions)
  88. Debug.LogException(exception);
  89. }
  90. /************************************************************************************************************************/
  91. /// <summary>
  92. /// Returns a cached <see cref="AnimationGatherer"/> containing any <see cref="AnimationClip"/>s referenced by
  93. /// components in the same hierarchy as the `gameObject`. See <see cref="ICharacterRoot"/> for details.
  94. /// </summary>
  95. public static AnimationGatherer GatherFromGameObject(GameObject gameObject)
  96. {
  97. if (!BeginRecursionGuard(gameObject))
  98. return null;
  99. try
  100. {
  101. _CallCount++;
  102. if (!ObjectToGatherer.TryGetValue(gameObject, out var gatherer))
  103. {
  104. gatherer = new();
  105. ObjectToGatherer.Add(gameObject, gatherer);
  106. gatherer.GatherFromComponents(gameObject);
  107. }
  108. return gatherer;
  109. }
  110. catch (Exception exception)
  111. {
  112. HandleException(exception);
  113. return null;
  114. }
  115. finally
  116. {
  117. _CallCount--;
  118. EndCall();
  119. }
  120. }
  121. /// <summary>
  122. /// Fills the `clips` with any <see cref="AnimationClip"/>s referenced by components in the same hierarchy as
  123. /// the `gameObject`. See <see cref="ICharacterRoot"/> for details.
  124. /// </summary>
  125. public static void GatherFromGameObject(GameObject gameObject, ICollection<AnimationClip> clips)
  126. {
  127. var gatherer = GatherFromGameObject(gameObject);
  128. gatherer?.GatherAnimationClips(clips);
  129. }
  130. /// <summary>
  131. /// Fills the `clips` with any <see cref="AnimationClip"/>s referenced by components in the same hierarchy as
  132. /// the `gameObject`. See <see cref="ICharacterRoot"/> for details.
  133. /// </summary>
  134. public static void GatherFromGameObject(GameObject gameObject, ref AnimationClip[] clips, bool sort)
  135. {
  136. var gatherer = GatherFromGameObject(gameObject);
  137. if (gatherer == null)
  138. return;
  139. using (SetPool<AnimationClip>.Instance.Acquire(out var clipSet))
  140. {
  141. gatherer.GatherAnimationClips(clipSet);
  142. AnimancerUtilities.SetLength(ref clips, clipSet.Count);
  143. clipSet.CopyTo(clips);
  144. }
  145. if (sort)
  146. Array.Sort(clips, (a, b) => a.GetCachedName().CompareTo(b.GetCachedName()));
  147. }
  148. /************************************************************************************************************************/
  149. private void GatherFromComponents(GameObject gameObject)
  150. {
  151. var root = AnimancerUtilities.FindRoot(gameObject);
  152. using (ListPool<MonoBehaviour>.Instance.Acquire(out var components))
  153. {
  154. root.GetComponentsInChildren(true, components);
  155. GatherFromComponents(components);
  156. }
  157. }
  158. /************************************************************************************************************************/
  159. private void GatherFromComponents(List<MonoBehaviour> components)
  160. {
  161. var i = components.Count;
  162. GatherClips:
  163. try
  164. {
  165. while (--i >= 0)
  166. {
  167. GatherFromObject(components[i], 0);
  168. }
  169. }
  170. catch (Exception exception)
  171. {
  172. HandleException(exception);
  173. goto GatherClips;
  174. }
  175. }
  176. /************************************************************************************************************************/
  177. /// <summary>Gathers all animations from the `source`s fields.</summary>
  178. private void GatherFromObject(object source, int depth)
  179. {
  180. if (source == null)
  181. return;
  182. if (source is AnimationClip clip)
  183. {
  184. Clips.Add(clip);
  185. return;
  186. }
  187. if (!MightContainAnimations(source.GetType()))
  188. return;
  189. if (!BeginRecursionGuard(source))
  190. return;
  191. try
  192. {
  193. if (Clips.GatherFromSource(source))
  194. return;
  195. }
  196. catch (Exception exception)
  197. {
  198. HandleException(exception);
  199. }
  200. finally
  201. {
  202. RecursionGuard.Remove(source);
  203. }
  204. GatherFromFields(source, depth);
  205. }
  206. /************************************************************************************************************************/
  207. /// <summary>Types mapped to a delegate that can quickly gather their clips.</summary>
  208. private static readonly Dictionary<Type, Action<object, AnimationGatherer>>
  209. TypeToGathererDelegate = new();
  210. /// <summary>
  211. /// Uses reflection to gather <see cref="AnimationClip"/>s from fields on the `source` object.
  212. /// </summary>
  213. private void GatherFromFields(object source, int depth)
  214. {
  215. if (depth >= MaxFieldDepth ||
  216. source == null ||
  217. !BeginRecursionGuard(source))
  218. return;
  219. var type = source.GetType();
  220. if (!TypeToGathererDelegate.TryGetValue(type, out var gatherClips))
  221. {
  222. gatherClips = BuildClipGathererDelegate(type, depth);
  223. TypeToGathererDelegate.Add(type, gatherClips);
  224. }
  225. gatherClips?.Invoke(source, this);
  226. }
  227. /************************************************************************************************************************/
  228. /// <summary>
  229. /// Creates a delegate to gather <see cref="AnimationClip"/>s from all relevant fields in a given `type`.
  230. /// </summary>
  231. private static Action<object, AnimationGatherer> BuildClipGathererDelegate(Type type, int depth)
  232. {
  233. if (!MightContainAnimations(type))
  234. return null;
  235. Action<object, AnimationGatherer> gathererDelegate = null;
  236. while (type != null)
  237. {
  238. var fields = type.GetFields(AnimancerReflection.InstanceBindings | BindingFlags.DeclaredOnly);
  239. for (int i = 0; i < fields.Length; i++)
  240. {
  241. var field = fields[i];
  242. var fieldType = field.FieldType;
  243. if (!MightContainAnimations(fieldType))
  244. continue;
  245. if (fieldType == typeof(AnimationClip))
  246. {
  247. gathererDelegate += (obj, gatherer) =>
  248. {
  249. var clip = (AnimationClip)field.GetValue(obj);
  250. gatherer.Clips.Gather(clip);
  251. };
  252. }
  253. else if (typeof(IAnimationClipSource).IsAssignableFrom(fieldType) ||
  254. typeof(IAnimationClipCollection).IsAssignableFrom(fieldType))
  255. {
  256. gathererDelegate += (obj, gatherer) =>
  257. {
  258. var source = field.GetValue(obj);
  259. gatherer.Clips.GatherFromSource(source);
  260. };
  261. }
  262. else if (typeof(ICollection).IsAssignableFrom(fieldType))
  263. {
  264. gathererDelegate += (obj, gatherer) =>
  265. {
  266. var collection = (ICollection)field.GetValue(obj);
  267. if (collection != null)
  268. {
  269. foreach (var item in collection)
  270. {
  271. gatherer.GatherFromObject(item, depth + 1);
  272. }
  273. }
  274. };
  275. }
  276. else
  277. {
  278. gathererDelegate += (obj, gatherer) =>
  279. {
  280. var source = field.GetValue(obj);
  281. if (source == null ||
  282. (source is Object sourceObject && sourceObject == null))
  283. return;
  284. gatherer.GatherFromObject(source, depth + 1);
  285. };
  286. }
  287. }
  288. type = type.BaseType;
  289. }
  290. return gathererDelegate;
  291. }
  292. /************************************************************************************************************************/
  293. private static bool MightContainAnimations(Type type)
  294. => !type.IsPrimitive
  295. && !type.IsEnum
  296. && !type.IsAutoClass
  297. && !type.IsPointer;
  298. /************************************************************************************************************************/
  299. }
  300. }
  301. #endif