ConversionCache.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. // Inspector Gadgets // https://kybernetik.com.au/inspector-gadgets // Copyright 2017-2024 Kybernetik //
  3. #if UNITY_EDITOR
  4. //#define LOG_CONVERSION_CACHE
  5. using System;
  6. using System.Collections.Generic;
  7. using UnityEngine;
  8. // Shared File Last Modified: 2023-09-02
  9. namespace Animancer.Editor
  10. //namespace InspectorGadgets.Editor
  11. {
  12. /// <summary>[Editor-Only]
  13. /// A simple system for converting objects and storing the results so they can be reused to minimise the need for
  14. /// garbage collection, particularly for string construction.
  15. /// </summary>
  16. /// <remarks>This class doesn't use any Editor-Only functionality, but it's unlikely to be useful at runtime.</remarks>
  17. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/ConversionCache_2
  18. /// https://kybernetik.com.au/inspector-gadgets/api/InspectorGadgets.Editor/ConversionCache_2
  19. ///
  20. public class ConversionCache<TKey, TValue>
  21. {
  22. /************************************************************************************************************************/
  23. private class CachedValue
  24. {
  25. public int lastFrameAccessed;
  26. public TValue value;
  27. }
  28. /************************************************************************************************************************/
  29. private readonly Dictionary<TKey, CachedValue>
  30. Cache = new();
  31. private readonly List<TKey>
  32. Keys = new();
  33. private readonly Func<TKey, TValue>
  34. Converter;
  35. private int _LastCleanupFrame;
  36. /************************************************************************************************************************/
  37. /// <summary>
  38. /// Creates a new <see cref="ConversionCache{TKey, TValue}"/> which uses the specified delegate to convert values.
  39. /// </summary>
  40. public ConversionCache(Func<TKey, TValue> converter)
  41. => Converter = converter;
  42. /************************************************************************************************************************/
  43. /// <summary>
  44. /// If a value has already been cached for the specified `key`, return it. Otherwise create a new one using
  45. /// the delegate provided in the constructor and cache it.
  46. /// <para></para>
  47. /// If the `key` is <c>null</c>, this method returns the default <typeparamref name="TValue"/>.
  48. /// </summary>
  49. /// <remarks>This method also periodically removes values that have not been used recently.</remarks>
  50. public TValue Convert(TKey key)
  51. {
  52. if (key == null)
  53. return default;
  54. CachedValue cached;
  55. // The next time a value is retrieved after at least 100 frames, clear out any old ones.
  56. var frame = Time.frameCount;
  57. if (_LastCleanupFrame + 100 < frame)
  58. {
  59. for (int i = Keys.Count - 1; i >= 0; i--)
  60. {
  61. var checkKey = Keys[i];
  62. if (!Cache.TryGetValue(checkKey, out cached) ||
  63. cached.lastFrameAccessed <= _LastCleanupFrame)
  64. {
  65. Cache.Remove(checkKey);
  66. Keys.RemoveAt(i);
  67. }
  68. }
  69. _LastCleanupFrame = frame;
  70. }
  71. if (!Cache.TryGetValue(key, out cached))
  72. {
  73. Cache.Add(key, cached = new() { value = Converter(key) });
  74. Keys.Add(key);
  75. }
  76. cached.lastFrameAccessed = frame;
  77. return cached.value;
  78. }
  79. /************************************************************************************************************************/
  80. }
  81. /// <summary>[Editor-Only] Utilities for <see cref="ConversionCache{TKey, TValue}"/>.</summary>
  82. /// <remarks>This class doesn't use any Editor-Only functionality, but it's unlikely to be useful at runtime.</remarks>
  83. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/ConversionCache
  84. /// https://kybernetik.com.au/inspector-gadgets/api/InspectorGadgets.Editor/ConversionCache
  85. ///
  86. public static class ConversionCache
  87. {
  88. /************************************************************************************************************************/
  89. /// <summary>
  90. /// Creates a <see cref="ConversionCache{TKey, TValue}"/> for calculating the GUI width occupied by text using
  91. /// the specified `style`.
  92. /// </summary>
  93. public static ConversionCache<string, float> CreateWidthCache(GUIStyle style)
  94. => new(style.CalculateWidth);
  95. /************************************************************************************************************************/
  96. // The "g" format gives a lower case 'e' for exponentials instead of upper case 'E'.
  97. private static readonly ConversionCache<float, string>
  98. FloatToString = new((value) => $"{value:g}");
  99. /// <summary>[Animancer Extension]
  100. /// Calls <see cref="float.ToString(string)"/> using <c>"g"</c> as the format and caches the result.
  101. /// </summary>
  102. public static string ToStringCached(this float value)
  103. => FloatToString.Convert(value);
  104. /************************************************************************************************************************/
  105. }
  106. }
  107. #endif