SharedReferenceCache.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // Animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using UnityEditor;
  6. using UnityEngine;
  7. namespace Animancer.Editor
  8. {
  9. /// <summary>[Editor-Only]
  10. /// A system which gathers information about <see cref="SerializeReference"/> fields to detect when multiple fields
  11. /// are referencing the same object.
  12. /// </summary>
  13. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/SharedReferenceCache
  14. public class SharedReferenceCache :
  15. IEnumerable<KeyValuePair<object, List<SharedReferenceCache.Field>>>
  16. {
  17. /************************************************************************************************************************/
  18. #region Static Caching
  19. /************************************************************************************************************************/
  20. private static readonly Dictionary<SerializedObject, SharedReferenceCache>
  21. SerializedObjectToCache = new();
  22. /// <summary>Returns a cached <see cref="SharedReferenceCache"/> for the `serializedObject`.</summary>
  23. public static SharedReferenceCache Get(SerializedObject serializedObject)
  24. {
  25. CheckFlush(serializedObject);
  26. if (!SerializedObjectToCache.TryGetValue(serializedObject, out var cache))
  27. SerializedObjectToCache.Add(serializedObject, cache = new(serializedObject));
  28. return cache;
  29. }
  30. /************************************************************************************************************************/
  31. private static readonly HashSet<SerializedObject>
  32. NotRecentlyUsed = new();
  33. private const double
  34. FlushInterval = 5;
  35. private static double
  36. _LastFlushTime;
  37. /// <summary>Discards any caches not used during the last <see cref="FlushInterval"/> when it elapses.</summary>
  38. private static void CheckFlush(SerializedObject serializedObject)
  39. {
  40. var currentTime = EditorApplication.timeSinceStartup;
  41. if (currentTime >= _LastFlushTime + FlushInterval)
  42. {
  43. _LastFlushTime = currentTime;
  44. foreach (var unused in NotRecentlyUsed)
  45. SerializedObjectToCache.Remove(unused);
  46. NotRecentlyUsed.Clear();
  47. NotRecentlyUsed.UnionWith(SerializedObjectToCache.Keys);
  48. }
  49. NotRecentlyUsed.Remove(serializedObject);
  50. }
  51. /************************************************************************************************************************/
  52. /// <summary>The number of editor updates that have occurred since startup.</summary>
  53. public static ulong FrameCount { get; private set; }
  54. static SharedReferenceCache()
  55. {
  56. EditorApplication.update += () => FrameCount++;
  57. }
  58. /************************************************************************************************************************/
  59. #endregion
  60. /************************************************************************************************************************/
  61. /// <summary>Information about a field.</summary>
  62. public struct Field
  63. {
  64. /************************************************************************************************************************/
  65. /// <summary>The <see cref="Serialization.GetFriendlyPath"/> of the field.</summary>
  66. public string path;
  67. /// <summary>The area where the field was last drawn.</summary>
  68. public Rect area;
  69. /************************************************************************************************************************/
  70. /// <summary>Creates a new <see cref="Field"/>.</summary>
  71. public Field(string path)
  72. {
  73. this.path = path;
  74. area = default;
  75. }
  76. /************************************************************************************************************************/
  77. }
  78. /************************************************************************************************************************/
  79. private readonly SerializedObject
  80. SerializedObject;
  81. private readonly Dictionary<object, List<Field>>
  82. ObjectToReferences = new();
  83. private ulong
  84. _LastGatherFrameCount;
  85. /************************************************************************************************************************/
  86. /// <summary>Creates a new <see cref="SharedReferenceCache"/>.</summary>
  87. public SharedReferenceCache(SerializedObject serializedObject)
  88. {
  89. SerializedObject = serializedObject;
  90. }
  91. /************************************************************************************************************************/
  92. /// <summary>Should <see cref="GatherReferences"/> be called?</summary>
  93. public bool ShouldGather
  94. => _LastGatherFrameCount != FrameCount;
  95. /// <summary>Updates the cached reference info.</summary>
  96. public void GatherReferences()
  97. {
  98. _LastGatherFrameCount = FrameCount;
  99. ObjectToReferences.Clear();
  100. var property = SerializedObject.GetIterator();
  101. while (property.Next(true))
  102. {
  103. if (property.propertyType != SerializedPropertyType.ManagedReference)
  104. continue;
  105. var reference = property.managedReferenceValue;
  106. if (reference == null)
  107. continue;
  108. if (!ObjectToReferences.TryGetValue(reference, out var paths))
  109. ObjectToReferences.Add(reference, paths = new());
  110. paths.Add(new(property.GetFriendlyPath()));
  111. }
  112. }
  113. /************************************************************************************************************************/
  114. /// <summary>Tries to get the info about all fields containing the `reference`.</summary>
  115. public bool TryGetInfo(object reference, out List<Field> references)
  116. {
  117. if (ShouldGather)
  118. GatherReferences();
  119. return ObjectToReferences.TryGetValue(reference, out references);
  120. }
  121. /************************************************************************************************************************/
  122. /// <summary>Returns an enumerator for all references and their info.</summary>
  123. public Dictionary<object, List<Field>>.Enumerator GetEnumerator()
  124. {
  125. if (ShouldGather)
  126. GatherReferences();
  127. return ObjectToReferences.GetEnumerator();
  128. }
  129. /// <summary>Returns an enumerator for all references and their info.</summary>
  130. IEnumerator<KeyValuePair<object, List<Field>>> IEnumerable<KeyValuePair<object, List<Field>>>.GetEnumerator()
  131. => GetEnumerator();
  132. /// <summary>Returns an enumerator for all references and their info.</summary>
  133. IEnumerator IEnumerable.GetEnumerator()
  134. => GetEnumerator();
  135. /************************************************************************************************************************/
  136. }
  137. }
  138. #endif