using System; using System.Collections.Generic; using System.Xml.Linq; namespace DiscordTranslator.Bot.Logic { /// /// A Least Recently Used cache with optional forced expiry/eviction. /// public class LruCache where K : notnull where V : notnull { private readonly int _capacity; private readonly Dictionary> _map = []; private readonly LinkedList _lru = new(); private readonly Lock _sync = new(); public int Count { get { lock (_sync) { return _lru.Count; } } } public LruCache(int capacity) { if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be positive."); _capacity = capacity; } /// /// Try to get a value by key and cast it to T (where T : V). /// Returns true and sets 'val' when found and castable; otherwise false and val = default. /// public bool TryGetValue(K key, out T? val) where T : V { lock (_sync) { if (_map.TryGetValue(key, out var node)) { // Move to back (most recently used) _lru.Remove(node); _lru.AddLast(node); if (node.Value.Value is T t) { val = t; return true; } } val = default; return false; } } public void Set(K key, V value) { lock (_sync) { if (_map.TryGetValue(key, out var existing)) { existing.Value = existing.Value with { Value = value }; // Refresh LRU position _lru.Remove(existing); _lru.AddLast(existing); } else { if (_map.Count >= _capacity) RemoveFirst(); // evict LRU var item = new LRUCacheItem(key, value); var node = new LinkedListNode(item); _lru.AddLast(node); _map[key] = node; } } } public void Remove(K key) { lock (_sync) { if (_map.TryGetValue(key, out var node)) { _lru.Remove(node); _map.Remove(key); if (node.Value is IDisposable dispVal) { dispVal.Dispose(); } } } } private void RemoveFirst() { // Evict least recently used (front) if (_lru.First is { } first) { _lru.RemoveFirst(); // Remove from map using the evicted key _map.Remove(first.Value.Key); if (first.Value is IDisposable dispVal) { dispVal.Dispose(); } } // If list was empty, nothing to evict (shouldn't happen with capacity checks) } internal sealed record LRUCacheItem(K Key, V Value) { public override string ToString() => $"{Key} => {Value}"; } } }