Cache key: a font handle does not identify a font - #176
Merged
Merged
Conversation
text() put the resolved FontHandle in its CSG params. A handle numbers
faces WITHIN one FontProvider, and every Evaluator makes its own, so two
evaluators sharing a ManifoldCache -- which is exactly what two GUI tabs
are -- handed the cache the same number for different fonts. The second
render was served the first one's glyphs.
Measured, not deduced: two providers, and
providerA.resolveFont("Arial:style=Regular") -> 4
providerB.resolveFont("Arial:style=Bold") -> 4
End to end through one shared cache, before:
Arial Regular 204 tris width 9.196
Arial Bold 204 tris width 9.196 <- Regular's glyph
Courier Regular 204 tris width 9.196 <- Regular's glyph
Courier Bold 204 tris width 9.196 <- Regular's glyph
after, each matches what it renders in a cache of its own (204/9.196,
164/9.298, 320/6.978, 296/7.819).
The fix is the SPEC alongside the handle in the params, so the key tells
them apart. The handle still does the lookup within its own evaluator. Two
specs that resolve to one face merely miss the cache rather than collide,
which is the safe direction.
Tests here pin the two properties that are provable here: the handle
collision across providers, and cacheKey distinguishing two nodes that
differ only by spec. The end-to-end regression test lives in BelfrySCAD --
written against these C++ helpers the scenario does not collide, so a test
here would have passed either way, and a green test that cannot fail is
worse than none.
Reported as BelfrySCAD#434.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JFTVMTH6yEktHDPF5csksb
The test claimed a fresh provider gives the first font it resolves the same handle the previous provider gave ITS first font. That is what macOS does (both 4, which is the collision this change is about) but not what the Linux runner does: with only the bundled faces present the numbering lines up instead, 0 and 1 either way, and the test failed there on aReg=0 vs bBold=1. What is universally true is the part that stays: within ONE provider two fonts get two handles. That a second provider CAN reuse a number is the motivation, is recorded as a comment with both platforms' numbers, and is covered end to end by BelfrySCAD's own test -- where the collision reproduces in both directions rather than depending on which fonts the machine has. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JFTVMTH6yEktHDPF5csksb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported as BelfrySCAD#434 — "fonts seem to be cached between objects and tabs".
Cause
text()put the resolvedFontHandlein its CSG params. A handle numbers faces within oneFontProvider, and everyEvaluatormakes its own — so two evaluators sharing aManifoldCache, which is exactly what two GUI tabs are, hand the cache the same number for different fonts. The second render is served the first one's glyphs.Measured rather than deduced:
End to end through one shared cache:
Fix
The spec goes into the params alongside the handle, so the key tells two fonts apart. The handle still does the lookup within its own evaluator. Two specs that resolve to the same face merely miss the cache rather than collide — the safe direction.
On the tests
The tests here pin the two properties provable here: the handle colliding across providers, and
cacheKeydistinguishing nodes that differ only by spec.The end-to-end regression test lives in BelfrySCAD, not here. I wrote one against these C++ helpers first and it passed with the fix reverted — the scenario doesn't collide through
evalSrcWithCache, so it would have been a green test that can't fail. It's in the repo where the reproduction is verified in both directions instead.1260 C++ tests, 42 binding tests.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JFTVMTH6yEktHDPF5csksb