Skip to content

Zero-alloc fast path for single-line text wrapping (Part of #1934)#3538

Merged
vchelaru merged 1 commit into
mainfrom
1934-text-relayout-alloc
Jul 7, 2026
Merged

Zero-alloc fast path for single-line text wrapping (Part of #1934)#3538
vchelaru merged 1 commit into
mainfrom
1934-text-relayout-alloc

Conversation

@vchelaru

@vchelaru vchelaru commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Part of #1934 (runtime allocation pass). Continues the ratchet after the idle Draw pass (#3514); this one targets the full-relayout Text path the handoff comment flagged as next.

Finding

Profiling the realistic-Forms full-relayout scenario showed 1,504 of 1,584 bytes/frame (95%) came from text wrapping, from just 4 wrap calls/frame — all the same unchanged TextBox string, re-wrapped with its Width toggling ∞↔90 by the RelativeToChildren natural-size measurement (measure unconstrained, then restore). IWrappedTextExtensions.UpdateLines re-tokenized/Split/concatenated that string on every call even though it fits on one line and the result is the raw string itself.

Fix

A single-line fast path in UpdateLines: when a string has no explicit newline and already fits within the wrap width, add it as the one line and return — zero managed allocation. The word-by-word algorithm reproduces the raw string verbatim in that case (interior/leading/trailing spaces included, per #2617), so it's behavior-preserving. MeasureString is allocation-free for text without inline runs (it sums glyph advances).

Impact

Scenario Before After
Realistic Forms full relayout 1,584 B/f 240 B/f (−85%)
UpdateWrappedText on a fitting single line 288 B/call 0 B/call

The residual 240 B/f is the one genuinely-wrapping Text (the TextBox content re-wrapping at its constrained width) — the next ratchet target (making the word-by-word path itself allocation-free).

Tests (TDD, red→green)

  • New TextTests.UpdateWrappedText_WhenTextFitsOnOneLine_DoesNotAllocate — asserted 288 → 0 (failed red before the fast path).
  • Ratchet RealisticLayoutAllocationTests tightened 2,200 → 400 (pins 240).
  • Full suites green: MonoGameGum.Tests 1821, Integration, SkiaGum 197, RaylibGum 454 — the ~25 existing wrapping tests (fixed-width, mid-word break, zero-width space, trailing space Crash on multiline text boxes #2617, newlines, ellipsis, inline BBCode) pin the algorithm's output.

Boyscout: removed a dead stringToUse recompute (Replace ran twice) and an unused returnString local in the same method.

The RelativeToChildren natural-size measurement re-wraps a Text every layout frame by toggling its Width (unconstrained to read natural size, then back). IWrappedTextExtensions.UpdateLines re-tokenized/Split/concatenated the string on every call — allocating even when the text fits on one line and the wrapped result is the raw text itself.

Add a fast path: when a string has no explicit newline and already fits within the wrap width, add it as the single line directly and return, with zero managed allocation. The word-by-word algorithm reproduces the raw string verbatim in that case (interior/leading/trailing spaces included, per #2617), so this is behavior-preserving. MeasureString is allocation-free for text without inline runs.

Impact: realistic Forms full-relayout drops 1,584 -> 240 bytes/frame (85%); a single-line UpdateWrappedText drops 288 -> 0 bytes/call. Ratchet in RealisticLayoutAllocationTests tightened 2,200 -> 400; new red-green unit test TextTests.UpdateWrappedText_WhenTextFitsOnOneLine_DoesNotAllocate pins the zero-alloc contract. Boyscout: removed dead 'stringToUse' recompute and unused 'returnString' local.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vchelaru vchelaru merged commit 5e319a0 into main Jul 7, 2026
3 checks passed
@vchelaru vchelaru deleted the 1934-text-relayout-alloc branch July 7, 2026 23:54
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.

1 participant