189 lines
7.3 KiB
C#
189 lines
7.3 KiB
C#
// Animancer // Copyright 2018-2024 Kybernetik //
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace Animancer.Editor
|
|
{
|
|
/// <summary>[Editor-Only]
|
|
/// A system which gathers information about <see cref="SerializeReference"/> fields to detect when multiple fields
|
|
/// are referencing the same object.
|
|
/// </summary>
|
|
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/SharedReferenceCache
|
|
public class SharedReferenceCache :
|
|
IEnumerable<KeyValuePair<object, List<SharedReferenceCache.Field>>>
|
|
{
|
|
/************************************************************************************************************************/
|
|
#region Static Caching
|
|
/************************************************************************************************************************/
|
|
|
|
private static readonly Dictionary<SerializedObject, SharedReferenceCache>
|
|
SerializedObjectToCache = new();
|
|
|
|
/// <summary>Returns a cached <see cref="SharedReferenceCache"/> for the `serializedObject`.</summary>
|
|
public static SharedReferenceCache Get(SerializedObject serializedObject)
|
|
{
|
|
CheckFlush(serializedObject);
|
|
|
|
if (!SerializedObjectToCache.TryGetValue(serializedObject, out var cache))
|
|
SerializedObjectToCache.Add(serializedObject, cache = new(serializedObject));
|
|
|
|
return cache;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private static readonly HashSet<SerializedObject>
|
|
NotRecentlyUsed = new();
|
|
|
|
private const double
|
|
FlushInterval = 5;
|
|
|
|
private static double
|
|
_LastFlushTime;
|
|
|
|
/// <summary>Discards any caches not used during the last <see cref="FlushInterval"/> when it elapses.</summary>
|
|
private static void CheckFlush(SerializedObject serializedObject)
|
|
{
|
|
var currentTime = EditorApplication.timeSinceStartup;
|
|
|
|
if (currentTime >= _LastFlushTime + FlushInterval)
|
|
{
|
|
_LastFlushTime = currentTime;
|
|
|
|
foreach (var unused in NotRecentlyUsed)
|
|
SerializedObjectToCache.Remove(unused);
|
|
|
|
NotRecentlyUsed.Clear();
|
|
NotRecentlyUsed.UnionWith(SerializedObjectToCache.Keys);
|
|
}
|
|
|
|
NotRecentlyUsed.Remove(serializedObject);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The number of editor updates that have occurred since startup.</summary>
|
|
public static ulong FrameCount { get; private set; }
|
|
|
|
static SharedReferenceCache()
|
|
{
|
|
EditorApplication.update += () => FrameCount++;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Information about a field.</summary>
|
|
public struct Field
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The <see cref="Serialization.GetFriendlyPath"/> of the field.</summary>
|
|
public string path;
|
|
|
|
/// <summary>The area where the field was last drawn.</summary>
|
|
public Rect area;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Creates a new <see cref="Field"/>.</summary>
|
|
public Field(string path)
|
|
{
|
|
this.path = path;
|
|
area = default;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private readonly SerializedObject
|
|
SerializedObject;
|
|
|
|
private readonly Dictionary<object, List<Field>>
|
|
ObjectToReferences = new();
|
|
|
|
private ulong
|
|
_LastGatherFrameCount;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Creates a new <see cref="SharedReferenceCache"/>.</summary>
|
|
public SharedReferenceCache(SerializedObject serializedObject)
|
|
{
|
|
SerializedObject = serializedObject;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Should <see cref="GatherReferences"/> be called?</summary>
|
|
public bool ShouldGather
|
|
=> _LastGatherFrameCount != FrameCount;
|
|
|
|
/// <summary>Updates the cached reference info.</summary>
|
|
public void GatherReferences()
|
|
{
|
|
_LastGatherFrameCount = FrameCount;
|
|
|
|
ObjectToReferences.Clear();
|
|
|
|
var property = SerializedObject.GetIterator();
|
|
while (property.Next(true))
|
|
{
|
|
if (property.propertyType != SerializedPropertyType.ManagedReference)
|
|
continue;
|
|
|
|
var reference = property.managedReferenceValue;
|
|
if (reference == null)
|
|
continue;
|
|
|
|
if (!ObjectToReferences.TryGetValue(reference, out var paths))
|
|
ObjectToReferences.Add(reference, paths = new());
|
|
|
|
paths.Add(new(property.GetFriendlyPath()));
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Tries to get the info about all fields containing the `reference`.</summary>
|
|
public bool TryGetInfo(object reference, out List<Field> references)
|
|
{
|
|
if (ShouldGather)
|
|
GatherReferences();
|
|
|
|
return ObjectToReferences.TryGetValue(reference, out references);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns an enumerator for all references and their info.</summary>
|
|
public Dictionary<object, List<Field>>.Enumerator GetEnumerator()
|
|
{
|
|
if (ShouldGather)
|
|
GatherReferences();
|
|
|
|
return ObjectToReferences.GetEnumerator();
|
|
}
|
|
|
|
/// <summary>Returns an enumerator for all references and their info.</summary>
|
|
IEnumerator<KeyValuePair<object, List<Field>>> IEnumerable<KeyValuePair<object, List<Field>>>.GetEnumerator()
|
|
=> GetEnumerator();
|
|
|
|
/// <summary>Returns an enumerator for all references and their info.</summary>
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
=> GetEnumerator();
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|
|
#endif
|