Advertisement
Ameisen

Untitled

Jun 16th, 2022
1,550
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 7.62 KB | None | 0 0
  1. using Microsoft.Toolkit.HighPerformance;
  2. using SpriteMaster.Extensions;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Runtime.CompilerServices;
  7. using System.Runtime.InteropServices;
  8. using System.Threading;
  9.  
  10. namespace SpriteMaster.Types;
  11.  
  12. internal class TypedMemoryCache<TKey, TValue> : AbstractTypedMemoryCache<TKey, TValue> where TKey : notnull where TValue : class {
  13.     [StructLayout(LayoutKind.Auto)]
  14.     private readonly struct CacheEntry {
  15.         internal readonly TValue Value;
  16.         internal readonly ConcurrentLinkedListSlim<TKey>.NodeRef Node;
  17.         internal readonly long Size;
  18.  
  19.         internal readonly bool IsValid => Node.IsValid;
  20.  
  21.         [MethodImpl(Runtime.MethodImpl.Inline)]
  22.         internal CacheEntry(TValue value, ConcurrentLinkedListSlim<TKey>.NodeRef node, long size) {
  23.             Value = value;
  24.             Node = node;
  25.             Size = size;
  26.         }
  27.  
  28.         [MethodImpl(Runtime.MethodImpl.Inline)]
  29.         internal readonly void UpdateValue(TValue value, long size) {
  30.             Unsafe.AsRef(in Value) = value;
  31.             Unsafe.AsRef(in Size) = size;
  32.         }
  33.     }
  34.  
  35.     private readonly Dictionary<TKey, CacheEntry> Cache = new();
  36.     private readonly ConcurrentLinkedListSlim<TKey> RecentAccessList = new();
  37.     private readonly SharedLock CacheLock = new();
  38.  
  39.     private readonly CancellationTokenSource ThreadCancelSource = new();
  40.     private readonly Condition TrimEvent = new();
  41.     private readonly Thread TrimThread;
  42.  
  43.     private void CacheTrimLoop(in CancellationToken cancellationToken) {
  44.         while (!cancellationToken.IsCancellationRequested) {
  45.             TrimEvent.Wait();
  46.  
  47.             if (cancellationToken.IsCancellationRequested) {
  48.                 return;
  49.             }
  50.  
  51.             TrimToSize(MaxSizeHysteresis);
  52.         }
  53.     }
  54.  
  55.     internal TypedMemoryCache(string name, long? maxSize, RemovalCallbackDelegate? removalAction = null) :
  56.         base(name, maxSize ?? long.MaxValue, removalAction) {
  57.         TrimThread = ThreadExt.Run(() => CacheTrimLoop(ThreadCancelSource.Token), background: true, name: $"Cache '{name}' Trim Thread");
  58.     }
  59.  
  60.     internal override int Count => Cache.Count;
  61.  
  62.     [MethodImpl(Runtime.MethodImpl.Inline)]
  63.     internal override TValue? Get(TKey key) {
  64.         using (CacheLock.Read) {
  65.             var entry = Cache.GetValueOrDefault(key);
  66.             if (entry.IsValid) {
  67.                 RecentAccessList.MoveToFront(entry.Node);
  68.             }
  69.  
  70.             return default;
  71.         }
  72.     }
  73.  
  74.     [MethodImpl(Runtime.MethodImpl.Inline)]
  75.     internal override bool TryGet(TKey key, [NotNullWhen(true)] out TValue? value) {
  76.         using (CacheLock.Read) {
  77.             if (Cache.TryGetValue(key, out var entry)) {
  78.                 RecentAccessList.MoveToFront(entry.Node);
  79.                 value = entry.Value;
  80.                 return true;
  81.             }
  82.         }
  83.  
  84.         value = default;
  85.         return false;
  86.     }
  87.  
  88.     internal override TValue Set(TKey key, TValue value) {
  89.         TValue? original = null;
  90.         long size = GetSizeBytes(value);
  91.         long sizeDelta;
  92.  
  93.         using (CacheLock.Write) {
  94.             if (Cache.TryGetValue(key, out var entry)) {
  95.                 RecentAccessList.MoveToFront(entry.Node);
  96.                 original = entry.Value;
  97.                 if (ReferenceEquals(original, value)) {
  98.                     return original;
  99.                 }
  100.  
  101.                 long originalSize = entry.Size;
  102.                 sizeDelta = size - originalSize;
  103.                 entry.UpdateValue(value, size);
  104.                 Cache[key] = entry;
  105.             }
  106.             else {
  107.                 Cache.Add(key, new(value, RecentAccessList.AddFront(key), size));
  108.                 sizeDelta = size;
  109.             }
  110.  
  111.             Interlocked.Add(ref CurrentSize, sizeDelta);
  112.         }
  113.  
  114.         if (sizeDelta != 0L) {
  115.             if (Interlocked.Read(ref CurrentSize) > MaxSize) {
  116.                 TrimEvent.Set();
  117.             }
  118.         }
  119.  
  120.         if (original is not null) {
  121.             OnEntryRemoved(key, original, EvictionReason.Replaced);
  122.         }
  123.  
  124.         return value;
  125.     }
  126.  
  127.     [MethodImpl(Runtime.MethodImpl.Inline)]
  128.     internal override TValue? Update(TKey key, TValue value) {
  129.         TValue? original = null;
  130.         long size = GetSizeBytes(value);
  131.         long sizeDelta;
  132.  
  133.         using (CacheLock.ReadWrite) {
  134.             var entry = Cache.GetValueOrDefault(key, default);
  135.             using (CacheLock.Write) {
  136.                 if (entry.IsValid) {
  137.                     RecentAccessList.MoveToFront(entry.Node);
  138.                     original = entry.Value;
  139.                     if (ReferenceEquals(original, value)) {
  140.                         return original;
  141.                     }
  142.  
  143.                     long originalSize = entry.Size;
  144.                     sizeDelta = size - originalSize;
  145.                     entry.UpdateValue(value, size);
  146.                     Cache[key] = entry;
  147.                 }
  148.                 else {
  149.                     Cache.Add(key, new(value, RecentAccessList.AddFront(key), size));
  150.                     sizeDelta = size;
  151.                 }
  152.  
  153.                 Interlocked.Add(ref CurrentSize, sizeDelta);
  154.             }
  155.         }
  156.  
  157.         if (sizeDelta != 0L) {
  158.             if (Interlocked.Read(ref CurrentSize) > MaxSize) {
  159.                 TrimEvent.Set();
  160.             }
  161.         }
  162.  
  163.         if (original is not null) {
  164.             OnEntryRemoved(key, original, EvictionReason.Replaced);
  165.         }
  166.  
  167.         return original;
  168.     }
  169.  
  170.     [MethodImpl(Runtime.MethodImpl.Inline)]
  171.     internal override TValue? Remove(TKey key) {
  172.         TValue? result = null;
  173.  
  174.         using (CacheLock.Write) {
  175.             if (Cache.Remove(key, out var entry)) {
  176.                 result = entry.Value;
  177.                 Interlocked.Add(ref CurrentSize, -entry.Size);
  178.                 RecentAccessList.Release(entry.Node);
  179.             }
  180.         }
  181.  
  182.         if (result is not null) {
  183.             OnEntryRemoved(key, result, EvictionReason.Removed);
  184.         }
  185.  
  186.         return result;
  187.     }
  188.  
  189.     [MethodImpl(Runtime.MethodImpl.Inline)]
  190.     internal override void Trim(int count) {
  191.         count.AssertPositiveOrZero();
  192.  
  193.         if (count == 0) {
  194.             return;
  195.         }
  196.  
  197.         var trimArray = GC.AllocateUninitializedArray<KeyValuePair<TKey, TValue>>(count);
  198.  
  199.         using (CacheLock.Write) {
  200.             if (count > Cache.Count) {
  201.                 count = Cache.Count;
  202.             }
  203.  
  204.             if (count == 0) {
  205.                 return;
  206.             }
  207.  
  208.             for (int i = 0; i < count; i++) {
  209.                 var removeKey = RecentAccessList.RemoveLast();
  210.                 bool result = Cache.Remove(removeKey, out var entry);
  211.                 Interlocked.Add(ref CurrentSize, -entry.Size);
  212.                 trimArray[i] = new(removeKey, entry.Value);
  213.                 result.AssertTrue();
  214.             }
  215.         }
  216.  
  217.         ReportTrimmed(trimArray.AsReadOnlySpan(0, count));
  218.     }
  219.  
  220.     [MethodImpl(Runtime.MethodImpl.Inline)]
  221.     internal override void TrimTo(int count) {
  222.         count.AssertPositiveOrZero();
  223.  
  224.         Trim(Cache.Count - count);
  225.     }
  226.  
  227.     private void TrimToSize(long size) {
  228.         CacheLock.IsWriteLock.AssertFalse();
  229.         size.AssertPositiveOrZero();
  230.  
  231.         if (size == 0L) {
  232.             Clear();
  233.         }
  234.         else {
  235.             var trimmed = new List<KeyValuePair<TKey, TValue>>();
  236.  
  237.             using (CacheLock.Write) {
  238.                 long currentSize = Interlocked.Read(ref CurrentSize);
  239.                
  240.                 while (currentSize >= size) {
  241.                     var removeKey = RecentAccessList.RemoveLast();
  242.                     bool result = Cache.Remove(removeKey, out var entry);
  243.                     currentSize -= entry.Size;
  244.                     trimmed.Add(new(removeKey, entry.Value));
  245.                     result.AssertTrue();
  246.                 }
  247.  
  248.                 CurrentSize = currentSize;
  249.             }
  250.  
  251.             ReportTrimmed(trimmed.AsSpan());
  252.         }
  253.     }
  254.  
  255.     private void ReportTrimmed(ReadOnlySpan<KeyValuePair<TKey, TValue>> trimmed) {
  256.         CacheLock.IsWriteLock.AssertFalse();
  257.  
  258.         foreach (var pair in trimmed) {
  259.             OnEntryRemoved(pair.Key, pair.Value, EvictionReason.Expired);
  260.         }
  261.     }
  262.  
  263.     [MethodImpl(Runtime.MethodImpl.Inline)]
  264.     internal override void Clear() {
  265.         int removedCount;
  266.         var removedPairs = GC.AllocateUninitializedArray<KeyValuePair<TKey, TValue>>(Count);
  267.  
  268.         using (CacheLock.Write) {
  269.             if (Count > removedPairs.Length) {
  270.                 removedPairs = GC.AllocateUninitializedArray<KeyValuePair<TKey, TValue>>(Count);
  271.             }
  272.  
  273.             removedCount = Count;
  274.  
  275.             int removedIndex = 0;
  276.             foreach (var (key, entry) in Cache) {
  277.                 removedPairs[removedIndex++] = new(key, entry.Value);
  278.             }
  279.  
  280.             CurrentSize = 0;
  281.             Cache.Clear();
  282.             RecentAccessList.Clear();
  283.         }
  284.  
  285.         ReportTrimmed(removedPairs.AsReadOnlySpan(0, removedCount));
  286.     }
  287.  
  288.     public override void Dispose() {
  289.         ThreadCancelSource.Cancel();
  290.         TrimEvent.Set();
  291.  
  292.         using (CacheLock.Write) {
  293.             Clear();
  294.         }
  295.         CacheLock.Dispose();
  296.  
  297.         TrimThread.Join();
  298.  
  299.         ThreadCancelSource.Dispose();
  300.         TrimEvent.Dispose();
  301.     }
  302. }
  303.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement