// Animancer // Copyright 2018-2024 Kybernetik // #if UNITY_EDITOR using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace Animancer.Editor { /// [Editor-Only] /// A system which gathers information about fields to detect when multiple fields /// are referencing the same object. /// /// https://kybernetik.com.au/animancer/api/Animancer.Editor/SharedReferenceCache public class SharedReferenceCache : IEnumerable>> { /************************************************************************************************************************/ #region Static Caching /************************************************************************************************************************/ private static readonly Dictionary SerializedObjectToCache = new(); /// Returns a cached for the `serializedObject`. 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 NotRecentlyUsed = new(); private const double FlushInterval = 5; private static double _LastFlushTime; /// Discards any caches not used during the last when it elapses. 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); } /************************************************************************************************************************/ /// The number of editor updates that have occurred since startup. public static ulong FrameCount { get; private set; } static SharedReferenceCache() { EditorApplication.update += () => FrameCount++; } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ /// Information about a field. public struct Field { /************************************************************************************************************************/ /// The of the field. public string path; /// The area where the field was last drawn. public Rect area; /************************************************************************************************************************/ /// Creates a new . public Field(string path) { this.path = path; area = default; } /************************************************************************************************************************/ } /************************************************************************************************************************/ private readonly SerializedObject SerializedObject; private readonly Dictionary> ObjectToReferences = new(); private ulong _LastGatherFrameCount; /************************************************************************************************************************/ /// Creates a new . public SharedReferenceCache(SerializedObject serializedObject) { SerializedObject = serializedObject; } /************************************************************************************************************************/ /// Should be called? public bool ShouldGather => _LastGatherFrameCount != FrameCount; /// Updates the cached reference info. 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())); } } /************************************************************************************************************************/ /// Tries to get the info about all fields containing the `reference`. public bool TryGetInfo(object reference, out List references) { if (ShouldGather) GatherReferences(); return ObjectToReferences.TryGetValue(reference, out references); } /************************************************************************************************************************/ /// Returns an enumerator for all references and their info. public Dictionary>.Enumerator GetEnumerator() { if (ShouldGather) GatherReferences(); return ObjectToReferences.GetEnumerator(); } /// Returns an enumerator for all references and their info. IEnumerator>> IEnumerable>>.GetEnumerator() => GetEnumerator(); /// Returns an enumerator for all references and their info. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /************************************************************************************************************************/ } } #endif