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();
}