using System.Collections; namespace HyperBooru.Util; public class MemoryCache : IEnumerable> where TKey : struct { /// Maximum number of items this cache may hold (unlimited if null) public int? MaxItems { get; init; } /// Maximum amount of time an item may stay in cache before it is removed (unlimited if null) public TimeSpan? MaxAge { get; init; } /// Function that will be called to populate the cache in the event of a cache-miss public Func? DataSource { get; init; } private Dictionary cache = new(); public TValue this[TKey key] { get => GetValue(key); set { Prune(); cache[key] = (DateTime.Now, value); } } public TValue GetValue(TKey key) { bool success = cache.TryGetValue(key, out var result); if(success) { if(MaxAge is null) return result.value; if(result.createTime > DateTime.Now - MaxAge) return result.value; } if(DataSource is null) throw new KeyNotFoundException(); TValue? value = DataSource(key); if(value is null) throw new KeyNotFoundException(); Prune(); cache[key] = (DateTime.Now, value); return value; } public bool TryGetValue(TKey key, out TValue? value) { try { value = GetValue(key); return true; } catch(KeyNotFoundException) { value = default; return false; } } public bool Remove(TKey key) => cache.Remove(key); public IEnumerator> GetEnumerator() { DateTime? expiry = MaxAge is null ? null : DateTime.Now - MaxAge; foreach(var kv in cache) { // Don't return expired cache items if(expiry is not null) if(kv.Value.createTime < expiry) continue; yield return new KeyValuePair(kv.Key, kv.Value.value); } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private void Prune() { DateTime? expiry = MaxAge is null ? null : DateTime.Now - MaxAge; // If an expiry time for cache items was // specified, remove expired cache items. if(expiry is not null) { foreach(var kv in cache) { if(kv.Value.createTime < expiry) cache.Remove(kv.Key); } } // If this cache was created with a maximum size, // remove elements until that size is reached. if(MaxItems is null || cache.Count() < MaxItems) return; var toRemove = cache .OrderBy(kv => kv.Value.createTime) .Take(cache.Count() - (int) MaxItems + 1) .Select(kv => kv.Key) .ToArray(); foreach(var key in toRemove) cache.Remove(key); } }