You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CrowdControl/Assets/Plugins/Animancer/Internal/Editor Utilities/AnimationGatherer.cs

290 lines
10 KiB
C#

3 months ago
// 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