perf(ui): LRU-evict the shaped-run cache instead of clearing it (#123) #143
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Catcrafts/Crafter.Graphics!143
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "claude/issue-123"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
UIRenderer::ShapeTextmemoizes origin-relative shaped runs inshapedRuns_. At thekMaxShapedRuns(8192) soft cap the cache did a fullclear(). A stream of unique strings (FPS counters, timers) alongside many stable labels periodically nuked every stable entry, forcing a full-UI reshape (UTF-8 decode + per-glyph atlas lookup + layout) on the very next frame — a recurring latency spike.(Found by an adversarially-verified performance audit; distinct from the GPU-atlas eviction issue #55 — this is the CPU-side shaped-run cache.)
Fix
Replace clear-all with O(1) LRU eviction:
std::list<const ShapedRunKey*> shapedRunsLru_(front = most recently used) holding pointers to the keys owned by the node-basedunordered_map(stable across rehash). EachShapedRunstores its list position inlruIt.splicethe entry to the front (O(1), keeps all iterators valid).InvalidateFontnow drops matching entries from both the map and the LRU list.The hot set of labels, reshaped every frame, stays at the front and survives; only the cold tail of stale unique strings is recycled. Output is byte-identical to before — the glyph buffer is fully rewritten and re-flushed every frame regardless.
Net performance
splice(~3 pointer writes), negligible next to the full-string hash lookup the hit already performs. No measurable regression.Strict win in the pathological case, negligible cost otherwise.
Tests
crafter-build test→ 23 passed. ExtendedShapeTextCachewith introspection-backed checks (the eviction policy isn't observable through output oratlas.dirty):Adds
ShapedRunCacheSize()/IsShapedRunCached()accessors used by the test.Resolves #123