Repository navigation
Skip unneeded Text layouts for unchanged values and with blocks - #2921
Merged
Merged
Conversation
- Every Text setter now returns early when the value is unchanged. Setting font_size, bold, italic, width, align, and similar to their current value used to run a full pyglet layout (about 165 us). - `with text:` only begins a pyglet update when a change inside it needs a new layout. pyglet's end_update() always lays out the text again, so an empty or unchanged block cost a full layout, and TextPool.get() did this for every line, every frame. - font_name compares with the last requested name, since the label holds the resolved one. - Link the italic workaround to pyglet issue #1508. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
PR 4 of the text improvements:
arcade.Textno longer lays out its text again when nothing changed.The problem
text,x,yandzdid. Settingfont_size,font_name,bold,italic,width,height,align,multilineortrackingto the value it already had ran a full pyglet layout, about 165–195 µs each. Code that sets properties every frame, likeTextPoolor a UI refresh, paid that cost even when nothing changed.with text:always laid out the text. pyglet'send_update()always runs a full layout, so even an empty block cost 165 µs.TextPool.get()wraps every call inwith cached_text:, so each line in a pool was laid out again every frame.The fix
font_namecompares with the last requested name, because the label holds the resolved name (e.g."arial"for("calibri", "arial")). It also checks that the label's font wasn't changed directly, so setting the name again still works in that case.with text:begins a pyglet update only when needed. That happens at the first change in the block that needs a new layout: text, font, size, weight, style, width, height, align, multiline, tracking, group, or a gradient color. Position, solid color, rotation, anchors and visibility apply right away, as they already did in pyglet. An empty or unchanged block now costs nothing, and several changes are still laid out once. Nested blocks work, and an exception inside a block still ends the update.Label.italic,Label.fontsetter, andIncrementalTextLayoutstill use the old"italic"style key, so italic changes don't render pyglet/pyglet#1508.Measurements
µs, pyglet 3.0.dev11, best of 5:
font_size,width,align,multilineset to the same valuebold,italicset to the same valuefont_nameset to the same valuecolorset to the same valuepositionset to the same valuewith text:blockTextPool, 5 unchanged lines, per frameTextPool, 4 unchanged lines + 1 changing, per frameA real change costs the same as before: one layout.
UILabel.update_font()also useswith label:, so it benefits too.Tests
New
tests/unit/text/test_text_updates.py(30 tests). It counts the label's full layouts by wrapping its_updateand counting only the calls that actually lay out.with text:block, or one that only sets unchanged values, does no layout.Textwith the same settings.get()calls do no layout, and a changed one does one.On
development, 16 of the new tests fail. The other 14 check behavior that already worked (correct drawing, nesting, exceptions, cheap setters) and guard it.Full suite on pyglet 3.0.dev11: 1543 passed, no warnings, plus the same 3 render tests that fail only on my machine. Ruff is clean, and mypy has no new errors.
One thing I noticed: pyglet replaces a label's text by deleting it and then inserting the new text, so a text change runs two layouts. The first one is of empty text, and wrapping both in one update measured the same (about 205 µs), so I left it alone.
The changelog has an entry under Unreleased → Misc Changes.
🤖 Generated with Claude Code