123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- // 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
|