mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 14:25:08 +00:00
perf(display): skip preview work nobody reads (lazy checksum, 1 Hz snapshot writer) (#717)
Mid-scroll, update_display() no longer checksums every frame: it asks is_currently_scrolling() once per frame and reuses the answer, hashes only when dirty tracking can skip a static frame, and the preview snapshot asks its policy first and hashes only when a write or touch could follow (decide() is monotone, pinned by a property test). With the preview open the snapshot is written at most once a second (VIEWER_INTERVAL 1.0 s, was 0.2 s; the SSE stream re-read it once a second, so four encodes in five went unread); the stream now polls its mtime every 0.25 s (VIEWER_POLL_INTERVAL), so the preview stays about as fresh. The PNG is written at compress_level=1. --preview soaks are not comparable across this change. Merged with #716: _scan_segments takes the frame's one scrolling answer and #716's static-handover pass-through, as soaked on ledpi (A B B A, 20 min each: main 0.118% / 0.113% late, with #716 and this 0.107% / 0.104%). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -231,7 +231,8 @@ rather than the `set_*` methods. Vegas mode reads a plugin's
|
||||
[`snapshot_policy.py`](snapshot_policy.py). Core-internal. `decide()`
|
||||
tells `DisplayManager` whether to write `/tmp/led_matrix_preview.png`, only
|
||||
touch its mtime, or skip, based on whether a browser is watching the preview.
|
||||
The web health check reads the file's age.
|
||||
The web health check reads the file's age, and the web preview stream checks
|
||||
its mtime every `VIEWER_POLL_INTERVAL`.
|
||||
|
||||
### sports_card
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@ Only intervals between two consecutive *scrolling* frames count: a static
|
||||
screen that changes once a second has no timing to get wrong, and the first
|
||||
frame of a scroll has no predecessor worth measuring against.
|
||||
|
||||
"Scrolling" is DisplayManager's scroll state when the frame is presented, and
|
||||
that state can go missing in the middle of a scroll. It expires after 2s
|
||||
"Scrolling" is the scroll state ``DisplayManager.update_display`` acted on for
|
||||
the frame, sampled once before the blit and swap, and that state can go missing
|
||||
in the middle of a scroll. It expires after 2s
|
||||
without scroll activity, which a long enough stall outlasts, and any thread can
|
||||
clear it: plugins call ``set_scrolling_state(False)`` from their own
|
||||
``display()``, and Vegas captures some of those on the render thread between
|
||||
@@ -393,7 +394,9 @@ class FrameTimingRecorder:
|
||||
:param blit: seconds spent copying the frame into the canvas.
|
||||
:param wait: seconds SwapOnVSync blocked.
|
||||
:param hold: the refreshes this frame was held for.
|
||||
:param scrolling: whether a scroll was running when it was presented.
|
||||
:param scrolling: whether a scroll was running for this frame: the
|
||||
scroll state ``update_display`` acted on, sampled once before the
|
||||
blit and swap.
|
||||
:param presented_at: ``time.perf_counter()`` when the swap returned.
|
||||
"""
|
||||
previous = self._previous
|
||||
|
||||
@@ -26,14 +26,33 @@ Policy:
|
||||
- Unchanged frames are never re-encoded; the mtime is touched every
|
||||
TOUCH_INTERVAL so the health check (60s threshold) never degrades.
|
||||
|
||||
The writer and the SSE reader (web_interface/app.py) have two periods, not
|
||||
one shared value. The reader sends each write it sees, so the preview shows
|
||||
at most one frame per VIEWER_INTERVAL. (That was 0.2 s while the reader
|
||||
slept 1 s between reads, so four encodes in five were overwritten unread.)
|
||||
The reader checks the file's mtime every VIEWER_POLL_INTERVAL, which is only
|
||||
a stat, and so sends each write within that long of it landing. Equal
|
||||
periods would alias: two unsynchronised 1 s clocks leave the preview up to a
|
||||
second stale, and now and then 2 s between frames.
|
||||
|
||||
decide() is monotone in frame_changed: SKIP for a changed frame means SKIP
|
||||
for an unchanged one. DisplayManager relies on that to skip hashing the
|
||||
frame when even a changed one would be skipped; test_snapshot_policy.py
|
||||
checks it.
|
||||
|
||||
If any constant here changes, re-check the health threshold in
|
||||
api_v3/misc.py (get_hardware_status) — TOUCH_INTERVAL must stay well under it.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
# Snapshot cadence with a browser preview open (seconds).
|
||||
VIEWER_INTERVAL = 0.2
|
||||
# Snapshot cadence with a browser preview open (seconds): the shortest gap
|
||||
# between two preview frames.
|
||||
VIEWER_INTERVAL = 1.0
|
||||
# How often the web SSE reader checks the snapshot's mtime (seconds). Must
|
||||
# stay well under VIEWER_INTERVAL -- half of it at most -- or the two clocks
|
||||
# alias (see above).
|
||||
VIEWER_POLL_INTERVAL = 0.25
|
||||
# Snapshot cadence with no viewers — cheap freshness for page-open (seconds).
|
||||
IDLE_INTERVAL = 30.0
|
||||
# Max age of the last write/touch before bumping mtime for the health
|
||||
|
||||
Reference in New Issue
Block a user