namespace Age.Engine.Text; /// A small thread-safe LRU used by glyph masks and future backend-owned font handles. public sealed class BoundedLruCache where TKey : notnull { private sealed record Entry(TKey Key, TValue Value); private readonly object _gate = new(); private readonly int _capacity; private readonly Action? _onEvicted; private readonly Dictionary> _entries = new(); private readonly LinkedList _recency = new(); public BoundedLruCache(int capacity, Action? onEvicted = null) { if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); _capacity = capacity; _onEvicted = onEvicted; } public int Capacity => _capacity; public int Count { get { lock (_gate) return _entries.Count; } } public bool TryGetValue(TKey key, out TValue value) { lock (_gate) { if (!_entries.TryGetValue(key, out LinkedListNode? node)) { value = default!; return false; } _recency.Remove(node); _recency.AddFirst(node); value = node.Value.Value; return true; } } public void Set(TKey key, TValue value) { List? evicted = null; lock (_gate) { if (_entries.Remove(key, out LinkedListNode? existing)) { _recency.Remove(existing); (evicted ??= new List(2)).Add(existing.Value.Value); } var node = new LinkedListNode(new Entry(key, value)); _recency.AddFirst(node); _entries.Add(key, node); if (_entries.Count > _capacity) { LinkedListNode oldest = _recency.Last!; _recency.RemoveLast(); _entries.Remove(oldest.Value.Key); (evicted ??= new List(1)).Add(oldest.Value.Value); } } if (_onEvicted != null && evicted != null) foreach (TValue removed in evicted) _onEvicted(removed); } public void Clear() { TValue[] removed; lock (_gate) { removed = _recency.Select(entry => entry.Value).ToArray(); _entries.Clear(); _recency.Clear(); } if (_onEvicted != null) foreach (TValue value in removed) _onEvicted(value); } } /// Bounded caching decorator shared by exact and portable glyph-mask backends. public sealed class CachedGlyphMaskRasterizer : IGlyphMaskRasterizer { private readonly IGlyphMaskRasterizer _inner; private readonly BoundedLruCache _cache; private long _hits; private long _misses; public CachedGlyphMaskRasterizer(IGlyphMaskRasterizer inner, int capacity) { _inner = inner ?? throw new ArgumentNullException(nameof(inner)); _cache = new BoundedLruCache(capacity); } public int Count => _cache.Count; public int Capacity => _cache.Capacity; public long Hits => Interlocked.Read(ref _hits); public long Misses => Interlocked.Read(ref _misses); public GlyphMask Rasterize(GlyphRasterRequest request) { ArgumentNullException.ThrowIfNull(request); if (_cache.TryGetValue(request, out GlyphMask? cached)) { Interlocked.Increment(ref _hits); return cached; } GlyphMask result = _inner.Rasterize(request) ?? throw new InvalidOperationException("The glyph rasterizer returned null."); _cache.Set(request, result); Interlocked.Increment(ref _misses); return result; } public void Clear() => _cache.Clear(); }