mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
fix(vegas): refresh scroll buffer on live score updates (#299)
* fix(vegas): refresh scroll buffer when plugins report live data updates should_recompose() only checked for cycle completion or staging buffer content, but plugin updates go to _pending_updates — not the staging buffer. The scroll display kept showing the old pre-rendered image until the full cycle ended, even though fresh scores were already fetched and logged. Add has_pending_updates() check so hot_swap_content() triggers immediately when plugins have new data. Fixes #230 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(vegas): scope hot-swap to visible segments; use monotonic clock 1. Replace has_pending_updates() with has_pending_updates_for_visible_segments() so hot_swap_content() only fires when a pending update affects a plugin that is actually in the active scroll buffer (with images). Avoids unnecessary recomposition when non-visible plugins report updates. 2. Switch all display-loop timing (start_time, elapsed, _next_live_priority_check) from time.time() to time.monotonic() to prevent clock-stepping issues from NTP adjustments on Raspberry Pi. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,7 +79,7 @@ class DisplayController:
|
|||||||
logger.info("Display modes initialized in %.3f seconds", time.time() - init_time)
|
logger.info("Display modes initialized in %.3f seconds", time.time() - init_time)
|
||||||
|
|
||||||
self.force_change = False
|
self.force_change = False
|
||||||
self._next_live_priority_check = 0.0 # timestamp for throttled live priority checks
|
self._next_live_priority_check = 0.0 # monotonic timestamp for throttled live priority checks
|
||||||
|
|
||||||
# All sports and content managers now handled via plugins
|
# All sports and content managers now handled via plugins
|
||||||
logger.info("All sports and content managers now handled via plugin system")
|
logger.info("All sports and content managers now handled via plugin system")
|
||||||
@@ -1727,7 +1727,7 @@ class DisplayController:
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_duration = max_duration
|
target_duration = max_duration
|
||||||
start_time = time.time()
|
start_time = time.monotonic()
|
||||||
|
|
||||||
def _should_exit_dynamic(elapsed_time: float) -> bool:
|
def _should_exit_dynamic(elapsed_time: float) -> bool:
|
||||||
if not dynamic_enabled:
|
if not dynamic_enabled:
|
||||||
@@ -1793,8 +1793,8 @@ class DisplayController:
|
|||||||
|
|
||||||
# Check for live priority every ~30s so live
|
# Check for live priority every ~30s so live
|
||||||
# games can interrupt long display durations
|
# games can interrupt long display durations
|
||||||
elapsed = time.time() - start_time
|
elapsed = time.monotonic() - start_time
|
||||||
now = time.time()
|
now = time.monotonic()
|
||||||
if not self.on_demand_active and now >= self._next_live_priority_check:
|
if not self.on_demand_active and now >= self._next_live_priority_check:
|
||||||
self._next_live_priority_check = now + 30.0
|
self._next_live_priority_check = now + 30.0
|
||||||
live_mode = self._check_live_priority()
|
live_mode = self._check_live_priority()
|
||||||
@@ -1843,7 +1843,7 @@ class DisplayController:
|
|||||||
time.sleep(display_interval)
|
time.sleep(display_interval)
|
||||||
self._tick_plugin_updates()
|
self._tick_plugin_updates()
|
||||||
|
|
||||||
elapsed = time.time() - start_time
|
elapsed = time.monotonic() - start_time
|
||||||
if elapsed >= target_duration:
|
if elapsed >= target_duration:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Reached standard target duration %.2fs for mode %s",
|
"Reached standard target duration %.2fs for mode %s",
|
||||||
@@ -1875,7 +1875,7 @@ class DisplayController:
|
|||||||
|
|
||||||
# Check for live priority every ~30s so live
|
# Check for live priority every ~30s so live
|
||||||
# games can interrupt long display durations
|
# games can interrupt long display durations
|
||||||
now = time.time()
|
now = time.monotonic()
|
||||||
if not self.on_demand_active and now >= self._next_live_priority_check:
|
if not self.on_demand_active and now >= self._next_live_priority_check:
|
||||||
self._next_live_priority_check = now + 30.0
|
self._next_live_priority_check = now + 30.0
|
||||||
live_mode = self._check_live_priority()
|
live_mode = self._check_live_priority()
|
||||||
@@ -1915,13 +1915,13 @@ class DisplayController:
|
|||||||
and not loop_completed
|
and not loop_completed
|
||||||
and not needs_high_fps
|
and not needs_high_fps
|
||||||
):
|
):
|
||||||
elapsed = time.time() - start_time
|
elapsed = time.monotonic() - start_time
|
||||||
remaining_sleep = max(0.0, max_duration - elapsed)
|
remaining_sleep = max(0.0, max_duration - elapsed)
|
||||||
if remaining_sleep > 0:
|
if remaining_sleep > 0:
|
||||||
self._sleep_with_plugin_updates(remaining_sleep)
|
self._sleep_with_plugin_updates(remaining_sleep)
|
||||||
|
|
||||||
if dynamic_enabled:
|
if dynamic_enabled:
|
||||||
elapsed_total = time.time() - start_time
|
elapsed_total = time.monotonic() - start_time
|
||||||
cycle_done = self._plugin_cycle_complete(manager_to_display)
|
cycle_done = self._plugin_cycle_complete(manager_to_display)
|
||||||
|
|
||||||
# Log cycle completion status and metrics
|
# Log cycle completion status and metrics
|
||||||
|
|||||||
@@ -265,6 +265,10 @@ class RenderPipeline:
|
|||||||
if buffer_status['staging_count'] > 0:
|
if buffer_status['staging_count'] > 0:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Trigger recompose when pending updates affect visible segments
|
||||||
|
if self.stream_manager.has_pending_updates_for_visible_segments():
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def hot_swap_content(self) -> bool:
|
def hot_swap_content(self) -> bool:
|
||||||
|
|||||||
@@ -199,6 +199,21 @@ class StreamManager:
|
|||||||
|
|
||||||
logger.debug("Plugin %s marked for update", plugin_id)
|
logger.debug("Plugin %s marked for update", plugin_id)
|
||||||
|
|
||||||
|
def has_pending_updates(self) -> bool:
|
||||||
|
"""Check if any plugins have pending updates awaiting processing."""
|
||||||
|
with self._buffer_lock:
|
||||||
|
return len(self._pending_updates) > 0
|
||||||
|
|
||||||
|
def has_pending_updates_for_visible_segments(self) -> bool:
|
||||||
|
"""Check if pending updates affect plugins currently in the active buffer."""
|
||||||
|
with self._buffer_lock:
|
||||||
|
if not self._pending_updates:
|
||||||
|
return False
|
||||||
|
active_ids = {
|
||||||
|
seg.plugin_id for seg in self._active_buffer if seg.images
|
||||||
|
}
|
||||||
|
return bool(active_ids & self._pending_updates.keys())
|
||||||
|
|
||||||
def process_updates(self) -> None:
|
def process_updates(self) -> None:
|
||||||
"""
|
"""
|
||||||
Process pending plugin updates.
|
Process pending plugin updates.
|
||||||
|
|||||||
Reference in New Issue
Block a user