mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-05 14:55:08 +00:00
feat(vegas): live elements update in place while they scroll
One background worker (src/vegas_mode/live_worker.py) redraws a plugin's live elements when its data epoch moves on (update listener) or on their refresh_hz, nearest the screen first, and hands changed pixels lock-free to the render thread, which copies them into the strip between frames (RenderPipeline.apply_live_patches, ScrollHelper.patch_columns): at most four patches or two screens of bytes a frame, no drawing or locks there. The worker takes over group prefetch once a live element is placed, runs inside the render gate, and is supervised. Update tick 1s while live elements exist. Web UI switch for live_refresh. OFFSCREEN_RENDERING.md describes what was built and why SegmentStrip was not needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,64 @@ def test_an_update_moves_the_plugins_epoch_and_new_elements_carry_it(ticker):
|
||||
assert metas and all(m.epoch == epoch for m in metas)
|
||||
|
||||
|
||||
def _columns(pipeline, record):
|
||||
x = record.abs_x - pipeline._strip_origin
|
||||
return pipeline.scroll_helper.cached_array[:, max(0, x):x + record.width].copy()
|
||||
|
||||
|
||||
def _upcoming(pipeline, key_prefix):
|
||||
"""A live record of this kind that is not yet behind the viewport."""
|
||||
left = pipeline._strip_origin + int(pipeline.scroll_helper.scroll_position)
|
||||
for record in pipeline.live_records():
|
||||
if record.key.startswith(key_prefix) and record.abs_x + record.width > left + 40:
|
||||
return record
|
||||
return None
|
||||
|
||||
|
||||
def test_an_update_changes_cards_already_in_the_strip(ticker):
|
||||
coordinator, plugin, pm = ticker
|
||||
assert coordinator.start()
|
||||
pipeline = coordinator.render_pipeline
|
||||
assert _run_until(coordinator, lambda: _upcoming(pipeline, "card:"))
|
||||
record = _upcoming(pipeline, "card:")
|
||||
before = _columns(pipeline, record)
|
||||
|
||||
plugin.update() # new data: every card's bars change
|
||||
pm._note_update_completed(PID) # what the update worker does after it
|
||||
assert _run_until(
|
||||
coordinator,
|
||||
lambda: not np.array_equal(_columns(pipeline, record), before), seconds=5.0)
|
||||
# The card changed in place: same columns, same width, new pixels.
|
||||
assert pipeline._record_by_seq[record.seq] == record
|
||||
assert pipeline._live_worker is not None and pipeline._live_worker.is_alive()
|
||||
|
||||
|
||||
def test_an_animated_element_moves_with_no_update_at_all(ticker):
|
||||
coordinator, _plugin, _pm = ticker
|
||||
assert coordinator.start()
|
||||
pipeline = coordinator.render_pipeline
|
||||
assert _run_until(coordinator, lambda: _upcoming(pipeline, "map"))
|
||||
record = _upcoming(pipeline, "map")
|
||||
before = _columns(pipeline, record)
|
||||
assert _run_until(
|
||||
coordinator,
|
||||
lambda: (pipeline._record_by_seq.get(record.seq) is not None
|
||||
and not np.array_equal(_columns(pipeline, record), before)),
|
||||
seconds=5.0)
|
||||
|
||||
|
||||
def test_stopping_vegas_stops_the_worker(ticker):
|
||||
coordinator, _plugin, _pm = ticker
|
||||
assert coordinator.start()
|
||||
pipeline = coordinator.render_pipeline
|
||||
assert _run_until(coordinator, lambda: pipeline._live_worker is not None)
|
||||
worker = pipeline._live_worker
|
||||
coordinator.stop()
|
||||
worker.join(3)
|
||||
assert not worker.is_alive()
|
||||
assert pipeline._live_worker is None
|
||||
|
||||
|
||||
def test_with_live_refresh_off_the_same_run_is_plain_content(dm, tmp_path, ticker):
|
||||
coordinator, _plugin, _pm = ticker
|
||||
coordinator.vegas_config.live_refresh = False
|
||||
|
||||
Reference in New Issue
Block a user