chore: initial commit

This commit is contained in:
2026-05-08 11:04:00 +08:00
commit f55d2a57c3
6278 changed files with 866081 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace Animancer.Editor
{
/// <summary>[Editor-Only]
/// A <see cref="PropertyDrawer"/> for <see cref="IPolymorphic"/> and <see cref="PolymorphicAttribute"/>.
/// </summary>
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/PolymorphicDrawer
[CustomPropertyDrawer(typeof(IPolymorphic), true)]
[CustomPropertyDrawer(typeof(PolymorphicAttribute), true)]
public sealed class PolymorphicDrawer : PropertyDrawer
{
/************************************************************************************************************************/
private bool _DrawerThrewException;
/************************************************************************************************************************/
/// <inheritdoc/>
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var height = 0f;
if (property.propertyType == SerializedPropertyType.ManagedReference)
{
GetDetails(property, out var value, out var drawer, out var details);
if (value == null)
return AnimancerGUI.LineHeight;
if (drawer != null)
{
if (details.SeparateHeader)
{
if (!property.isExpanded)
return AnimancerGUI.LineHeight;
height += AnimancerGUI.LineHeight + AnimancerGUI.StandardSpacing;
}
try
{
height += drawer.GetPropertyHeight(property, label);
return height;
}
catch (Exception exception)
{
_DrawerThrewException = true;
Debug.LogException(exception, property.serializedObject.targetObject);
// Continue to the regular calculation.
}
}
}
height += EditorGUI.GetPropertyHeight(property, label, true);
return height;
}
/************************************************************************************************************************/
/// <inheritdoc/>
public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
{
if (property.propertyType != SerializedPropertyType.ManagedReference)
{
EditorGUI.PropertyField(area, property, label, true);
return;
}
GetDetails(property, out _, out var drawer, out var details);
var drawTypeSelectionButton = drawer == null || drawer is not IPolymorphic;
var button = drawTypeSelectionButton
? new TypeSelectionButton(area, property, true)
: default;
if (drawer != null)
{
if (details.SeparateHeader)
{
var foldoutArea = area;
foldoutArea.width = EditorGUIUtility.labelWidth;
foldoutArea.height = AnimancerGUI.LineHeight;
label = EditorGUI.BeginProperty(foldoutArea, label, property);
property.isExpanded = EditorGUI.Foldout(foldoutArea, property.isExpanded, label, true);
EditorGUI.EndProperty();
area.yMin += AnimancerGUI.LineHeight + AnimancerGUI.StandardSpacing;
}
// If drawing a separate header, don't draw the body if it's collapsed.
if (!details.SeparateHeader || property.isExpanded)
{
try
{
#pragma warning disable UNT0027 // Do not call PropertyDrawer.OnGUI(). Should only apply to calling base.OnGUI.
drawer.OnGUI(area, property, label);
#pragma warning restore UNT0027 // Do not call PropertyDrawer.OnGUI().
}
catch (Exception exception)
{
_DrawerThrewException = true;
Debug.LogException(exception, property.serializedObject.targetObject);
// Continue to PropertyField.
}
}
}
else
{
EditorGUI.PropertyField(area, property, label, true);
}
if (drawTypeSelectionButton)
button.DoGUI();
}
/************************************************************************************************************************/
private void GetDetails(
SerializedProperty property,
out object value,
out PropertyDrawer drawer,
out PolymorphicDrawerDetails details)
{
value = property.managedReferenceValue;
if (_DrawerThrewException || value == null)
{
drawer = null;
details = PolymorphicDrawerDetails.Default;
return;
}
if (PropertyDrawers.TryGetDrawer(value.GetType(), fieldInfo, attribute, out drawer) &&
drawer is PolymorphicDrawer)
drawer = null;
details = PolymorphicDrawerDetails.Get(value);
}
/************************************************************************************************************************/
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3302cfeb4df49034abb712f96989e2ec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,97 @@
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
namespace Animancer.Editor
{
/// <summary>[Editor-Only]
/// An assembly attribute for configuring how the <see cref="PolymorphicDrawer"/>
/// displays a particular type.
/// </summary>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public sealed class PolymorphicDrawerDetails : Attribute
{
/************************************************************************************************************************/
/// <summary>A default instance.</summary>
public static readonly PolymorphicDrawerDetails
Default = new(null);
/************************************************************************************************************************/
/// <summary>The <see cref="System.Type"/> this attribute applies to.</summary>
public readonly Type Type;
/// <summary>Creates a new <see cref="PolymorphicDrawerDetails"/>.</summary>
public PolymorphicDrawerDetails(Type type)
=> Type = type;
/************************************************************************************************************************/
/// <summary>
/// Should the label and <see cref="TypeSelectionButton"/>
/// be drawn on a separate line before the field's regular GUI?
/// </summary>
public bool SeparateHeader { get; set; }
/************************************************************************************************************************/
private static readonly Dictionary<Type, PolymorphicDrawerDetails>
TypeToDetails = new();
/// <summary>Gathers all instances of this attribute in all currently loaded assemblies.</summary>
static PolymorphicDrawerDetails()
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int iAssembly = 0; iAssembly < assemblies.Length; iAssembly++)
{
var assembly = assemblies[iAssembly];
if (!assembly.IsDefined(typeof(PolymorphicDrawerDetails), false))
continue;
var attributes = assemblies[iAssembly].GetCustomAttributes(typeof(PolymorphicDrawerDetails), false);
for (int iAttribute = 0; iAttribute < attributes.Length; iAttribute++)
{
var attribute = (PolymorphicDrawerDetails)attributes[iAttribute];
TypeToDetails.Add(attribute.Type, attribute);
}
}
}
/************************************************************************************************************************/
/// <summary>
/// Returns the <see cref="PolymorphicDrawerDetails"/> associated with the `type` or any of its base types.
/// Returns <c>null</c> if none of them have any details.
/// </summary>
public static PolymorphicDrawerDetails Get(Type type)
{
if (TypeToDetails.TryGetValue(type, out var details))
return details;
if (type.BaseType != null)
details = Get(type.BaseType);
else
details = Default;
TypeToDetails.Add(type, details);
return details;
}
/// <summary>
/// Returns the <see cref="PolymorphicDrawerDetails"/> associated with the `obj` or any of its base types.
/// Returns <c>null</c> if none of them have any details.
/// </summary>
public static PolymorphicDrawerDetails Get(object obj)
=> obj == null
? Default
: Get(obj.GetType());
/************************************************************************************************************************/
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ef6fe20da82c4840b0ed0bc86153e54
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,457 @@
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEditor;
using UnityEngine;
namespace Animancer.Editor
{
/// <summary>[Editor-Only]
/// A button that allows the user to select an object type for a [<see cref="SerializeReference"/>] field.
/// </summary>
///
/// <remarks>
/// <strong>Example:</strong>
/// <code>
/// public override void OnGUI(Rect area, SerializedProperty property, GUIContent label)
/// {
/// using (new TypeSelectionButton(area, property, label, true))
/// {
/// EditorGUI.PropertyField(area, property, label, true);
/// }
/// }
/// </code></remarks>
///
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/TypeSelectionButton
///
public readonly struct TypeSelectionButton : IDisposable
{
/************************************************************************************************************************/
/// <summary>The pixel area occupied by the button.</summary>
public readonly Rect Area;
/// <summary>The <see cref="SerializedProperty"/> representing the attributed field.</summary>
public readonly SerializedProperty Property;
/// <summary>The original <see cref="Event.type"/> from when this button was initialized.</summary>
public readonly EventType EventType;
/************************************************************************************************************************/
/// <summary>Creates a new <see cref="TypeSelectionButton"/>.</summary>
public TypeSelectionButton(
Rect area,
SerializedProperty property,
bool hasLabel)
{
area.height = AnimancerGUI.LineHeight;
if (hasLabel)
area.xMin += EditorGUIUtility.labelWidth + AnimancerGUI.StandardSpacing;
var currentEvent = Event.current;
Area = area;
Property = property;
EventType = currentEvent.type;
if (Property.propertyType != SerializedPropertyType.ManagedReference)
return;
switch (currentEvent.type)
{
case EventType.MouseDown:
case EventType.MouseUp:
if (area.Contains(currentEvent.mousePosition))
currentEvent.type = EventType.Ignore;
break;
}
}
/************************************************************************************************************************/
void IDisposable.Dispose()
=> DoGUI();
/// <summary>Draws this button's GUI.</summary>
/// <remarks>Run this method after drawing the target property so the button draws on top of its label.</remarks>
public void DoGUI()
{
if (Property.propertyType != SerializedPropertyType.ManagedReference)
return;
var currentEvent = Event.current;
var eventType = currentEvent.type;
var area = Area;
PrepareSharedReferenceArea(ref area, out var sharedButtonArea, out var value, out var references);
using (var label = PooledGUIContent.Acquire())
{
switch (EventType)
{
case EventType.MouseDown:
case EventType.MouseUp:
currentEvent.type = EventType;
break;
case EventType.Layout:
break;
// Only Repaint events actually care what the label is.
case EventType.Repaint:
var valueType = Property.managedReferenceValue?.GetType();
if (valueType == null)
{
label.text = "Null";
label.tooltip = "Nothing is assigned";
}
else
{
label.text = valueType.GetNameCS(false);
label.tooltip = valueType.GetNameCS(true);
}
break;
default:
return;
}
if (GUI.Button(area, label, EditorStyles.popup))
TypeSelectionMenu.Show(Property);
}
DoSharedReferenceGUI(sharedButtonArea, value, references, currentEvent.type);
if (currentEvent.type == EventType)
currentEvent.type = eventType;
}
/************************************************************************************************************************/
/// <summary>Allocates an area for <see cref="DoSharedReferenceGUI"/> if the `value` is shared.</summary>
private void PrepareSharedReferenceArea(
ref Rect remainingArea,
out Rect sharedButtonArea,
out object value,
out List<SharedReferenceCache.Field> references)
{
sharedButtonArea = default;
value = default;
references = default;
if (!TypeSelectionMenu.VisualiseSharedReferences)
return;
value = Property.managedReferenceValue;
if (value == null)
return;
var referenceCache = SharedReferenceCache.Get(Property.serializedObject);
if (!referenceCache.TryGetInfo(value, out references) ||
references.Count <= 1)
return;
sharedButtonArea = AnimancerGUI.StealFromRight(
ref remainingArea,
remainingArea.height + AnimancerGUI.StandardSpacing * 2,
AnimancerGUI.StandardSpacing);
}
/************************************************************************************************************************/
private static ConditionalWeakTable<object, object>
_VisualiseLinks;
private static GUIStyle
_SharedReferenceStyle;
private static Texture
_SharedReferenceIcon;
/// <summary>Draws a toggle to enable/disable visualisation of the `value`'s shared references.</summary>
private void DoSharedReferenceGUI(
Rect area,
object value,
List<SharedReferenceCache.Field> references,
EventType eventType)
{
if (area.width == 0)
return;
var wasVisualising = _VisualiseLinks != null && _VisualiseLinks.TryGetValue(value, out _);
var color = eventType == EventType.Repaint
? AnimancerGUI.GetHashColor(value.GetHashCode(), 0.5f, 1, 0.7f)
: Color.white;
if (wasVisualising)
new LinkLine(area, Property.GetFriendlyPath(), references, color);
using (var label = PooledGUIContent.Acquire(null, GetTooltip(references, eventType)))
{
if (_SharedReferenceStyle == null)
{
_SharedReferenceStyle ??= new(EditorStyles.miniButton)
{
padding = new RectOffset(0, 0, -2, 0),
overflow = new RectOffset(),
};
_SharedReferenceIcon = AnimancerIcons.Load(EditorGUIUtility.isProSkin
? "d_Linked@2x"
: "Linked@2x");
}
label.image = _SharedReferenceIcon;
var oldColor = GUI.color;
GUI.color = color;
var isVisualising = GUI.Toggle(area, wasVisualising, label, _SharedReferenceStyle);
GUI.color = oldColor;
if (isVisualising != wasVisualising)
{
if (isVisualising)
{
_VisualiseLinks ??= new();
_VisualiseLinks.Add(value, null);
}
else
{
_VisualiseLinks.Remove(value);
}
}
label.image = null;
}
}
/************************************************************************************************************************/
/// <summary>Builds a tooltip describing the `references`.</summary>
private static string GetTooltip(
List<SharedReferenceCache.Field> references,
EventType eventType)
{
if (eventType != EventType.Repaint)
return null;
var text = StringBuilderPool.Instance.Acquire();
text.Append("This reference is shared by:");
for (int i = 0; i < references.Count; i++)
text.Append("\n• ").Append(ObjectNames.NicifyVariableName(references[i].path));
text.Append("\nClick to visualise");
return text.ReleaseToString();
}
/************************************************************************************************************************/
#region Link Lines
/************************************************************************************************************************/
private static readonly List<LinkLine>
LinkLines = new();
private static int _DelayLinkLines;
/************************************************************************************************************************/
/// <summary>
/// Any shared reference link lines which would be drawn after this call are instead
/// delayed until the corresponding <see cref="EndDelayingLinkLines"/> call.
/// </summary>
public static void BeginDelayingLinkLines()
{
_DelayLinkLines++;
}
/// <summary>
/// Ends a block started by <see cref="BeginDelayingLinkLines"/>.
/// When all such blocks are cancelled, this method draws all delayed links between shared reference fields.
/// </summary>
public static void EndDelayingLinkLines()
{
_DelayLinkLines--;
if (_DelayLinkLines <= 0)
{
for (int i = LinkLines.Count - 1; i >= 0; i--)
LinkLines[i].Draw();
LinkLines.Clear();
}
}
/************************************************************************************************************************/
/// <summary>The details needed to draw a line between fields which share the same reference.</summary>
private readonly struct LinkLine
{
/************************************************************************************************************************/
/// <summary>The area of the button which toggles visibility of link lines.</summary>
public readonly Rect SharedButtonArea;
/// <summary>The property path of the target field.</summary>
public readonly string Path;
/// <summary>The shared reference cache for the target field.</summary>
public readonly List<SharedReferenceCache.Field> References;
/// <summary>The color of the link line.</summary>
public readonly Color Color;
/************************************************************************************************************************/
/// <summary>Creates a new <see cref="LinkLine"/>.</summary>
public LinkLine(
Rect sharedButtonArea,
string path,
List<SharedReferenceCache.Field> references,
Color color)
{
sharedButtonArea.position += AnimancerGUI.GuiOffset;
SharedButtonArea = sharedButtonArea;
Path = path;
References = references;
Color = color;
if (_DelayLinkLines <= 0)
Draw();
else
LinkLines.Add(this);
}
/************************************************************************************************************************/
/// <summary>Draws a line between the current field and the the previous field referencing the same value.</summary>
public void Draw()
{
var currentEvent = Event.current;
if (currentEvent.type != EventType.Repaint)
return;
var index = SetArea(References, Path, SharedButtonArea);
Handles.DrawLine(default, default);// Necessary for DrawCurve to work.
AnimancerGUI.BeginTriangles(Color);
var position = GetLeftCenter(SharedButtonArea);
for (int i = index - 1; i >= 0; i--)
{
var otherArea = References[i].area;
if (otherArea == default)
continue;
var otherPosition = GetLeftCenter(otherArea);
DrawCurve(position, otherPosition);
break;
}
AnimancerGUI.EndTriangles();
}
/************************************************************************************************************************/
/// <summary>Sets the <see cref="SharedReferenceCache.Field.area"/> of the current field.</summary>
private static int SetArea(
List<SharedReferenceCache.Field> references,
string path,
Rect area)
{
for (int i = 0; i < references.Count; i++)
{
var reference = references[i];
if (reference.path != path)
continue;
reference.area = area;
references[i] = reference;
return i;
}
return -1;
}
/************************************************************************************************************************/
/// <summary>Returns the center point of the left edge of the `rect`.</summary>
private static Vector2 GetLeftCenter(Rect rect)
=> new(rect.x, rect.y + rect.height * 0.5f);
/************************************************************************************************************************/
/// <summary>Draws a line between `a` and `b` curved towards x = 0.</summary>
private static void DrawCurve(
Vector2 a,
Vector2 b)
{
const int Segments = 16;
const float Increment = 1f / (Segments - 1);
var width = CalculateCurveWidth(Math.Abs(a.y - b.y));
var previous = a;
for (int i = 0; i < Segments; i++)
{
var t = i * Increment;
var next = Vector2.LerpUnclamped(a, b, t);
var curve = 0.5f - t;
curve *= 2;
curve *= curve;
curve = 1 - curve;
next.x *= 1 - curve * width;
AnimancerGUI.DrawLineBatched(previous, next, 2);
previous = next;
}
}
/************************************************************************************************************************/
/// <summary>
/// Calculates the desired width for a curve with the given `height`
/// as a portion of the total available width.
/// </summary>
private static float CalculateCurveWidth(float height)
{
const float
MinWidth = 0.05f,
MaxWidth = 0.8f;
var maxHeight = AnimancerGUI.LineHeight * 100;
if (height > maxHeight)
return MaxWidth;
var t = height / maxHeight;
t = 1 - t;
t *= t;
return Mathf.Lerp(MaxWidth, MinWidth, t);
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 311697a696013e64fbab8f44f876867e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,352 @@
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Animancer.Editor
{
/// <summary>[Editor-Only] A context menu for selecting a <see cref="Type"/>.</summary>
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/TypeSelectionMenu
public static class TypeSelectionMenu
{
/************************************************************************************************************************/
private const string
PrefKeyPrefix = nameof(TypeSelectionMenu) + ".",
PrefMenuPrefix = "Display Options/";
/// <summary>Should shared references be shown in the GUI?</summary>
public static readonly BoolPref
VisualiseSharedReferences = new(
PrefKeyPrefix + nameof(VisualiseSharedReferences),
PrefMenuPrefix + "Visualise Shared References",
true);
/// <summary>Should full type names be displayed?</summary>
public static readonly BoolPref
UseFullNames = new(
PrefKeyPrefix + nameof(UseFullNames),
PrefMenuPrefix + "Show Full Names");
/// <summary>Should options be grouped in sub menus based on their inheritance hierarchy?</summary>
public static readonly BoolPref
UseTypeHierarchy = new(
PrefKeyPrefix + nameof(UseTypeHierarchy),
PrefMenuPrefix + "Show Type Hierarchy");
/************************************************************************************************************************/
/// <summary>Shows a menu to select which type of object to assign to the `property`.</summary>
public static void Show(SerializedProperty property)
{
var value = property.managedReferenceValue;
var accessor = property.GetAccessor();
var fieldType = accessor.GetFieldElementType(property);
var selectedType = value?.GetType();
var menu = new GenericMenu();
AddPrefs(menu);
AddDocumentation(menu, fieldType);
menu.AddSeparator("");
menu.AddDisabledItem(new(ObjectNames.NicifyVariableName(property.GetFriendlyPath())));
menu.AddSeparator("");
AddTypeSelector(menu, property, fieldType, selectedType, null);
AddSharedReferences(menu, property, fieldType, value);
var inheritors = GetDerivedTypes(fieldType);
for (int i = 0; i < inheritors.Count; i++)
AddTypeSelector(menu, property, fieldType, selectedType, inheritors[i]);
menu.ShowAsContext();
}
/************************************************************************************************************************/
/// <summary>Adds items for toggling the display options.</summary>
private static void AddPrefs(GenericMenu menu)
{
VisualiseSharedReferences.AddToggleFunction(menu);
UseFullNames.AddToggleFunction(menu);
UseTypeHierarchy.AddToggleFunction(menu);
}
/************************************************************************************************************************/
/// <summary>Adds an itme for opening the documentation if a <see cref="HelpURLAttribute"/> is present.</summary>
private static void AddDocumentation(
GenericMenu menu,
Type fieldType)
{
var help = fieldType.GetCustomAttribute<HelpURLAttribute>();
if (help == null ||
string.IsNullOrWhiteSpace(help.URL))
return;
var label = $"Documentation: {help.URL.Replace('/', '\\')}";
menu.AddItem(new(label), false, () => Application.OpenURL(help.URL));
}
/************************************************************************************************************************/
/// <summary>Adds items for selecting shared references.</summary>
private static void AddSharedReferences(
GenericMenu menu,
SerializedProperty property,
Type fieldType,
object currentValue)
{
foreach (var item in GetObjectsAndPaths(property.serializedObject, fieldType))
{
var label = $"Shared Reference/{ObjectNames.NicifyVariableName(item.Value)}";
var state = item.Key == currentValue ? MenuFunctionState.Selected : MenuFunctionState.Normal;
menu.AddPropertyModifierFunction(property, label, state, targetProperty =>
{
targetProperty.managedReferenceValue = item.Key;
});
}
}
/************************************************************************************************************************/
/// <summary>Gathers all potential references that could be shared.</summary>
private static List<KeyValuePair<object, string>> GetObjectsAndPaths(
SerializedObject serializedObject,
Type fieldType)
{
var objectsAndPaths = new List<KeyValuePair<object, string>>();
var referenceCache = SharedReferenceCache.Get(serializedObject);
referenceCache.GatherReferences();
foreach (var item in referenceCache)
{
if (!fieldType.IsAssignableFrom(item.Key.GetType()))
continue;
foreach (var info in item.Value)
{
objectsAndPaths.Add(new(item.Key, info.path));
}
}
objectsAndPaths.Sort(
(a, b) => Comparer<string>.Default.Compare(a.Value, b.Value));
return objectsAndPaths;
}
/************************************************************************************************************************/
/// <summary>Adds a menu function to assign a new instance of the `newType` to the `property`.</summary>
private static void AddTypeSelector(
GenericMenu menu,
SerializedProperty property,
Type fieldType,
Type selectedType,
Type newType)
{
var label = GetSelectorLabel(fieldType, newType);
var state = selectedType == newType ? MenuFunctionState.Selected : MenuFunctionState.Normal;
menu.AddPropertyModifierFunction(property, label, state, targetProperty =>
{
var oldValue = property.GetValue();
var newValue = AnimancerReflection.CreateDefaultInstance(newType);
CopyCommonFields(oldValue, newValue);
if (newValue is IPolymorphicReset reset)
reset.Reset(oldValue);
targetProperty.managedReferenceValue = newValue;
targetProperty.isExpanded = true;
});
}
/************************************************************************************************************************/
private static string GetSelectorLabel(Type fieldType, Type newType)
{
if (newType == null)
return "Null";
if (!UseTypeHierarchy)
return newType.GetNameCS(UseFullNames);
var label = StringBuilderPool.Instance.Acquire();
if (fieldType.IsInterface)// Interface.
{
while (true)
{
if (label.Length > 0)
label.Insert(0, '/');
var displayType = newType.IsGenericType ?
newType.GetGenericTypeDefinition() :
newType;
label.Insert(0, displayType.GetNameCS(UseFullNames));
newType = newType.BaseType;
if (newType == null ||
!fieldType.IsAssignableFrom(newType))
break;
}
}
else// Base Class.
{
while (true)
{
if (label.Length > 0)
label.Insert(0, '/');
label.Insert(0, newType.GetNameCS(UseFullNames));
newType = newType.BaseType;
if (newType == null)
break;
if (fieldType.IsAbstract)
{
if (newType == fieldType)
break;
}
else
{
if (newType == fieldType.BaseType)
break;
}
}
}
return label.ReleaseToString();
}
/************************************************************************************************************************/
private static readonly List<Type>
AllTypes = new(1024);
private static readonly Dictionary<Type, List<Type>>
TypeToDerived = new();
/// <summary>Returns a list of all types that inherit from the `baseType`.</summary>
public static List<Type> GetDerivedTypes(Type baseType)
{
if (!TypeToDerived.TryGetValue(baseType, out var derivedTypes))
{
if (AllTypes.Count == 0)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int iAssembly = 0; iAssembly < assemblies.Length; iAssembly++)
{
var assembly = assemblies[iAssembly];
if (assembly.IsDynamic)
continue;
var types = assembly.GetExportedTypes();
for (int iType = 0; iType < types.Length; iType++)
{
var type = types[iType];
if (IsViableType(type))
AllTypes.Add(type);
}
}
AllTypes.Sort((a, b) => a.FullName.CompareTo(b.FullName));
}
derivedTypes = new();
for (int i = 0; i < AllTypes.Count; i++)
{
var type = AllTypes[i];
if (baseType.IsAssignableFrom(type))
derivedTypes.Add(type);
}
TypeToDerived.Add(baseType, derivedTypes);
}
return derivedTypes;
}
/************************************************************************************************************************/
/// <summary>Is the `type` supported by <see cref="SerializeReference"/> fields?</summary>
public static bool IsViableType(Type type)
=> !type.IsAbstract
&& !type.IsEnum
&& !type.IsGenericTypeDefinition
&& !type.IsInterface
&& !type.IsPrimitive
&& !type.IsSpecialName
&& type.Name[0] != '<'
&& type.IsDefined(typeof(SerializableAttribute), false)
&& !type.IsDefined(typeof(ObsoleteAttribute), true)
&& !typeof(Object).IsAssignableFrom(type)
&& type.GetConstructor(AnimancerReflection.InstanceBindings, null, Type.EmptyTypes, null) != null;
/************************************************************************************************************************/
/// <summary>
/// Copies the values of all fields in `from` into corresponding fields in `to` as long as they have the same
/// name and compatible types.
/// </summary>
public static void CopyCommonFields(object from, object to)
{
if (from == null ||
to == null)
return;
var nameToFromField = new Dictionary<string, FieldInfo>();
var fromType = from.GetType();
do
{
var fromFields = fromType.GetFields(AnimancerReflection.InstanceBindings | BindingFlags.DeclaredOnly);
for (int i = 0; i < fromFields.Length; i++)
{
var field = fromFields[i];
nameToFromField[field.Name] = field;
}
fromType = fromType.BaseType;
}
while (fromType != null);
var toType = to.GetType();
do
{
var toFields = toType.GetFields(AnimancerReflection.InstanceBindings | BindingFlags.DeclaredOnly);
for (int i = 0; i < toFields.Length; i++)
{
var toField = toFields[i];
if (nameToFromField.TryGetValue(toField.Name, out var fromField))
{
var fromValue = fromField.GetValue(from);
if (fromValue == null || toField.FieldType.IsAssignableFrom(fromValue.GetType()))
toField.SetValue(to, fromValue);
}
}
toType = toType.BaseType;
}
while (toType != null);
}
/************************************************************************************************************************/
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32cf6628710d067439dd6c78226a58a2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: