Skip to content

perf: cut per-node and per-die work in the evaluator #336 - #337

Merged
edloidas merged 1 commit into
masterfrom
issue-336
Aug 19, 2026
Merged

perf: cut per-node and per-die work in the evaluator #336#337
edloidas merged 1 commit into
masterfrom
issue-336

Conversation

@edloidas

Copy link
Copy Markdown
Owner

Collapsed EvalContext's expression/rendered fragment arrays into single strings, then moved both onto EvalResult. Every eval* runs against a context of its own and pushed each array exactly once, so the arrays never held more than one element and every parent joined a one-element array. Returning them instead makes a forgotten write a type error rather than an empty string in a user-visible RollResult.expression — it caught a missing evalFunctionCall field on the first compile.

EvalContext now holds only what flows up unchanged or merged (rolls, versusMetadata) plus the parent-set suppressRender; everything a wrapper reformats rides EvalResult.

Rebuilt rewriteFlags as one loop instead of a closure, a filter array and a spread. It still always returns a fresh array — a variant returning the input when it already equalled the result was rejected because mergeMetaRolls clones a die as { ...die, modifiers: rewriteFlags(...) } and would alias flags that later passes mutate.

Suppressed the discarded pool render in a modifier's target context. Each pool modifier rebuilds the breakdown from its target's expression plus a renderDice pass of its own, so the target's own render was thrown away; in a chain each modifier's context is the next one out's discarded target, so every level above the innermost was also rendering into the void.

Also swapped keptIndices.includes for a Set in renderGroup, added 100d10ro<3 and 100d10kh50sd cs>8 benchmark cases for the two shapes with no coverage, and recorded why toSeedString stays on decimal.

Measured, medians of 5, A/B against master:

notation before after
1d20 0.353 us 0.277 us -22%
2d6+3 0.692 us 0.530 us -23%
4d6kh3 1.256 us 1.034 us -18%
floor((1d4+1)*2/3) 1.823 us 1.273 us -30%
2d20kh1 vs 15 1.440 us 1.098 us -24%
10d10sd>=6f1 3.185 us 2.612 us -18%
100d10kh50sd cs>8 47.393 us 39.184 us -17%
60-term sum 21.192 us 14.080 us -34%

No behavior change: rendered, expression and the RollResult shape are byte-identical, and the renderBreakdown parity property test still holds. Sizes fell rather than rose, so no budget change: index.js 13.16 -> 13.09 kB, { roll } 12.91 -> 12.85 kB.

Closes #336

Collapsed `EvalContext`'s `expression`/`rendered` fragment arrays into single
strings, then moved both onto `EvalResult` so no branch can omit one.
Dropped `ctx` from `evalLiteral` and `evalVariable`, which no longer need it.
Rebuilt `rewriteFlags` as one loop rather than a closure, a filter array and a
spread, keeping the fresh-array guarantee `mergeMetaRolls` depends on.
Suppressed the discarded pool render in a modifier's target context, routing
every pool render through `renderedPool`.
Replaced `keptIndices.includes` with a `Set` in `renderGroup`.
Added `100d10ro<3` and `100d10kh50sd cs>8` benchmark cases.
Recorded why `toSeedString` stays on decimal.

Co-Authored-By: Mikita Taukachou <edloidas@gmail.com>
@edloidas edloidas self-assigned this Aug 19, 2026
@edloidas
edloidas merged commit c275ebe into master Aug 19, 2026
47 checks passed
@edloidas
edloidas deleted the issue-336 branch August 19, 2026 21:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: cut per-node and per-die work in the evaluator

1 participant