ConversionCache.cs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. #if UNITY_EDITOR
  3. //#define ANIMANCER_LOG_CONVERSION_CACHE
  4. using System;
  5. using System.Collections.Generic;
  6. using UnityEngine;
  7. namespace Animancer.Editor
  8. {
  9. /// <summary>[Editor-Only]
  10. /// A simple system for converting objects and storing the results so they can be reused to minimise the need for
  11. /// garbage collection, particularly for string construction.
  12. /// </summary>
  13. /// <remarks>This class doesn't use any Editor-Only functionality, but it's unlikely to be useful at runtime.</remarks>
  14. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/ConversionCache_2
  15. ///
  16. public class ConversionCache<TKey, TValue>
  17. {
  18. /************************************************************************************************************************/
  19. private class CachedValue
  20. {
  21. public int lastFrameAccessed;
  22. public TValue value;
  23. }
  24. /************************************************************************************************************************/
  25. private readonly Dictionary<TKey, CachedValue>
  26. Cache = new Dictionary<TKey, CachedValue>();
  27. private readonly List<TKey>
  28. Keys = new List<TKey>();
  29. private readonly Func<TKey, TValue>
  30. Converter;
  31. private int _LastCleanupFrame;
  32. /************************************************************************************************************************/
  33. /// <summary>
  34. /// Creates a new <see cref="ConversionCache{TKey, TValue}"/> which uses the specified delegate to convert values.
  35. /// </summary>
  36. public ConversionCache(Func<TKey, TValue> converter) => Converter = converter;
  37. /************************************************************************************************************************/
  38. /// <summary>
  39. /// If a value has already been cached for the specified `key`, return it. Otherwise create a new one using
  40. /// the delegate provided in the constructor and cache it.
  41. /// <para></para>
  42. /// If the `key` is <c>null</c>, this method returns the default <typeparamref name="TValue"/>.
  43. /// </summary>
  44. /// <remarks>This method also periodically removes values that have not been used recently.</remarks>
  45. public TValue Convert(TKey key)
  46. {
  47. if (key == null)
  48. return default;
  49. CachedValue cached;
  50. // The next time a value is retrieved after at least 100 frames, clear out any old ones.
  51. var frame = Time.frameCount;
  52. if (_LastCleanupFrame + 100 < frame)
  53. {
  54. for (int i = Keys.Count - 1; i >= 0; i--)
  55. {
  56. var checkKey = Keys[i];
  57. if (!Cache.TryGetValue(checkKey, out cached) ||
  58. cached.lastFrameAccessed <= _LastCleanupFrame)
  59. {
  60. Cache.Remove(checkKey);
  61. Keys.RemoveAt(i);
  62. }
  63. }
  64. _LastCleanupFrame = frame;
  65. }
  66. if (!Cache.TryGetValue(key, out cached))
  67. {
  68. Cache.Add(key, cached = new CachedValue { value = Converter(key) });
  69. Keys.Add(key);
  70. }
  71. cached.lastFrameAccessed = frame;
  72. return cached.value;
  73. }
  74. /************************************************************************************************************************/
  75. }
  76. }
  77. #endif