Repository navigation
perf(binding)!: materialize measured styles on demand - #46
Merged
Merged
Conversation
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.
Summary
MeasureArgs.stylefield withgetStyle(): StylegetStyle()This is a breaking API change:
Why
Taffy supplies
&Styleto every Rust measure call, but mechanically converting that large value at the JavaScript boundary made every callback pay for a complete Style object even when measurement only used constraints or context. The public capability remains complete and normalized, while conversion is now explicit and demand-driven.Implementation and lifetime
The exact-request cache from #45 is checked first. On the first uncached callback request for a node, the measure session clones the borrowed Rust Style into an owned snapshot and creates a native
getStylefunction that captures it. Later requests for that node in the same compute reuse the same function.Calling the function runs
style::output()and returns a fresh detached JavaScript object. Not calling it never runsstyle::output()and never creates a JavaScript Style object. A retained function remains safe after the callback or compute returns because the JavaScript function finalizer owns the Rust snapshot; it does not retain a tree borrow, a callback-scope pointer, or re-enter the busy tree.The fixed safe-lifetime cost is one Rust Style clone and one native provider for each callback-reached node per compute, whether or not the function is called. No JavaScript Style mirror, numeric slot, compact constraints ABI, per-node measure callback, or upstream algorithm change is included.
Performance
Base:
d73e3351d8ce9571faa65b10a855f428f73e896a, which includes #38 through merged PR #45. Linux x64, Node 24.19.0, Intel i5-13500H. Each cell is the median of three independent local runs of the unchanged benchmark scenario. The omit and always-call variants were temporary diagnostics and were fully reverted;benchmarks/results/published.jsonwas not changed.The unused lazy path recovers 98.6% / 98.2% of the removable initial-layout cost in Native / WASI, and 95.4% / 91.3% for resize. When Style is requested every time, initial Native is effectively the eager cost; the resize probe is 6.2% slower in Native and 8.5% slower in WASI, reflecting provider and clone overhead on top of full conversion.
The bounded review changed the internal lifetime witness without changing the ownership model. The final commit was then rerun three times with the ordinary unused callback and still showed the same direction: 7.3256 / 50.1378 ms for initial Native / WASI and 2.9261 / 20.9033 ms for resize Native / WASI in a noisier follow-up round.
Allocation counts
A temporary probe compared provider identity by node and called
getStyle()once per JavaScript callback. Native and WASI produced the same counts:Verification
vp run checkvp run check:wasm@taffyjs/nodebehavior suite@taffyjs/yoga, Yoga Wasm, browser bundles, packed consumers, type tests, Rust tests/clippy, formatting, and website build passThe public API and Guide/reference documentation now describe the breaking migration. Because this explicitly supersedes the earlier eager callback direction, the conflicting PCR vouch was removed; Yunfei has vouched the new on-demand decision entry.
Fixes #39