mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-18 04:49:13 +00:00
fix(scroll): tie the frame hold to the scroll, not the plugin
The hold applied in configure() never reached the panel. Plugins share one
display manager, and set_scrolling_state(False) -- fired whenever ANY other
plugin finishes its scroll -- reset the hold to 1. A hold set once at plugin
construction was therefore always gone by the time that plugin rendered.
The symptom was a log line that lied. ledmatrix-stocks reported
Scroll configured: 50.0 px/s (1px every 2 refreshes = 50.0 fps, smooth)
while the panel measured 100.0 fps, median 10.00ms. Config, resolution and
snapping were all correct; only the pacing silently was not applied.
set_scrolling_state(is_scrolling, frame_hold=1) now carries it, so the hold
lives exactly as long as the scroll that asked for it. configure() reports the
value as ScrollSettings.frame_hold instead of applying it -- applying it behind
the caller's back could never have been right on a shared display manager.
Existing callers are unaffected; the default keeps one frame per refresh.
Verified on hardware: stocks at 50 px/s now measures
50.0 fps over 251 frames | median 20.00ms p95 20.09ms | stalls 0 skips 0
20.00ms being exactly two refreshes, with the panel still refreshing at 100Hz
underneath so flicker is unchanged.
test_another_plugin_stopping_does_not_strand_a_hold pins the interaction that
broke this.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -272,3 +272,43 @@ class TestFrameHold:
|
||||
def test_unusable_holds_are_ignored_or_floored(self, dm, bad, expected):
|
||||
dm.set_frame_hold(bad)
|
||||
assert dm._frame_hold == expected
|
||||
|
||||
|
||||
class TestFrameHoldLifetime:
|
||||
"""The hold must last exactly as long as the scroll that asked for it.
|
||||
|
||||
Plugins share one display manager. A hold applied at plugin construction is
|
||||
wiped the moment any *other* plugin finishes scrolling, so by the time the
|
||||
first plugin renders it is back to one pixel per refresh -- the speed reads
|
||||
correct in the log and is wrong on the panel.
|
||||
"""
|
||||
|
||||
def test_scrolling_state_carries_the_hold(self, dm):
|
||||
dm.set_scrolling_state(True, frame_hold=4)
|
||||
try:
|
||||
dm.draw.rectangle([0, 0, 7, 7], fill=(10, 200, 10))
|
||||
with _SwapSpy(dm.matrix) as spy:
|
||||
dm.update_display()
|
||||
assert spy.last_frame_hold == 4
|
||||
finally:
|
||||
dm.set_scrolling_state(False)
|
||||
|
||||
def test_another_plugin_stopping_does_not_strand_a_hold(self, dm):
|
||||
dm.set_scrolling_state(True, frame_hold=3)
|
||||
dm.set_scrolling_state(False) # some other plugin finishes
|
||||
dm.set_scrolling_state(True) # a plugin that wants no hold
|
||||
try:
|
||||
dm.draw.rectangle([0, 0, 6, 6], fill=(200, 10, 10))
|
||||
with _SwapSpy(dm.matrix) as spy:
|
||||
dm.update_display()
|
||||
assert spy.last_frame_hold == 1
|
||||
finally:
|
||||
dm.set_scrolling_state(False)
|
||||
|
||||
def test_default_keeps_previous_behaviour(self, dm):
|
||||
"""Callers that never heard of frame holds get one frame per refresh."""
|
||||
dm.set_scrolling_state(True)
|
||||
try:
|
||||
assert dm._frame_hold == 1
|
||||
finally:
|
||||
dm.set_scrolling_state(False)
|
||||
|
||||
Reference in New Issue
Block a user