Fix SpriteList pop with negative index, rescale, and lazy preload - #2906
Merged
Merged
Conversation
- pop(): the index buffer array is longer than the list (it has spare
capacity), so popping it with a negative index removed padding instead
of the sprite's entry. After pop(-2) the removed sprite stayed on
screen and the last sprite vanished. Make the index positive first,
and raise IndexError for out-of-range indexes before changing
anything.
- rescale(): self.center was recalculated for every sprite, after
earlier sprites had moved. Find the center once.
- preload_textures(): raised AttributeError on a list that wasn't
initialized yet, such as a lazy list. Use the default atlas it will
get, and raise the intended ValueError only if there's no window.
- Create the index array as unsigned ("I"), matching clear(), shuffle()
and the GPU buffer, and fix the DEFAULT_TEXTURE_FILTER docs example.
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
Fixes three
SpriteListbugs found while reviewing the sprite drawing code, plus two small consistency fixes. Each bug is reproduced by a new test that fails ondevelopment.1.
pop()with a negative index drew the wrong sprites_sprite_index_data(the draw order sent to the GPU) is longer than the list, because it has spare capacity at the end.pop(index)passed the same index to both the sprite list and that array, so a negative index removed padding from the array instead of the sprite's entry:The removed sprite stayed on screen,
cdisappeared, and the next sprite appended reusedb's slot.pop()andpop(-1)only worked by luck, since the entry being dropped happened to be the last one drawn.Fix: convert the index to a positive one before touching either list. Out-of-range indexes now raise
IndexErrorbefore anything changes. Before, a far-out-of-range negative index could have removed the wrong sprite after the check.2.
rescale()scaled around a moving centerrescale()calledself.centerinside its loop, after earlier sprites had already moved. Two sprites at x=0 and x=100 rescaled ×2 ended at -50 and 175, instead of -50 and 150. It also recalculated the center for every sprite, which is O(n²).Fix: find the center once, before the loop. An early return for empty lists is needed now, since finding the center of an empty list would divide by zero.
3.
preload_textures()on a lazy list raisedAttributeErrorIt checked
self.ctx, which only exists after the list is initialized, so a lazy list that hadn't been drawn yet raisedAttributeError: 'SpriteList' object has no attribute 'ctx'.Fix: if the list has no atlas yet, preload into the window's default atlas, which is the one the list gets when it initializes. Preloading doesn't initialize the list. Without a window, it raises the
ValueErrorthe code always intended.Smaller fixes
"i"(signed), butclear()andshuffle()recreate it as"I"(unsigned), matching the GPU's 32-bit unsigned index buffer. It's now"I"from the start.DEFAULT_TEXTURE_FILTERdocs example for "linear filtering (smooth)" showedgl.NEAREST, gl.NEAREST; it now showsgl.LINEAR, gl.LINEAR.Tests
The
pop()tests read the draw order back from the GPU index buffer, like the existing shuffle test, so they check what's actually drawn.test_pop_index: indexes 0–3 and -1 to -4 on a 4-sprite list. The draw order matches the list, including after a new sprite reuses the freed slot.test_pop_repeatedly: repeatedpop(-2)until the list is empty.test_pop_out_of_rangeandtest_pop_empty:IndexError, and nothing removed.test_rescale_around_center: positions after rescaling around the center (50, 20), plus an empty list.test_preload_textures_lazy: the texture is in the atlas, and the list isn't initialized.test_index_buffer_type:"I"after creation,shuffle(), andclear().On
development,pop()with -2/-3/-4, the repeated pops, rescale, lazy preload, and index type tests fail. The non-negative and-1pop cases pass there too, as expected.Full suite on pyglet 3.0.dev11: 1382 passed. The 3 failures are the render tests that only fail on my machine. Ruff is clean, and mypy reports no errors in
sprite_list.py.🤖 Generated with Claude Code