Skip to content

perf(display): throttle the per-frame update tick; check strips without building them - #731

Merged
ChuckBuilds merged 3 commits into
mainfrom
perf/render-loop-overhead
Oct 3, 2026
Merged

ChuckBuilds merged 3 commits into
mainfrom
perf/render-loop-overhead

Conversation

@ChuckBuilds

Copy link
Copy Markdown
Owner

Summary

Two per-frame costs in the render loop:

  • Plugin update tick throttled to 4 Hz. The frame loops and the dwell sleep ran PluginManager.run_scheduled_updates() after every frame, ~125×/s on a scroller, almost always to find nothing due (~100 µs with 20 plugins on a Pi 4). They now run it at most every 0.25 s. No update interval is shorter than 5 s, and the top of each loop pass still ticks unthrottled.
  • Strip checks no longer build the PIL image. SportsScrollDisplay and the sync follower asked "is there a strip?" by reading ScrollHelper.cached_image, which builds and keeps the image from the array after a deferred rebuild (3.5 ms and ~1 MiB for a 4288x64 strip). They now ask has_strip(). A helper without has_strip is still asked the old way.

Type of change

  • Refactor (no functional change)

Test plan

  • Ran on a real Raspberry Pi with hardware: hdpi (Pi 4, 512x64), 40-min windows in an A/B/B/A soak, each from a fresh restart, Vegas mode, same music load. This branch vs the main window after it:

    this branch main
    process CPU (% of one core) 144.0 146.3
    Vegas fps, mean 81.6 81.7
    median p99 frame time 20.6 ms 20.9 ms
    late frames per 1,000 21.3 24.0
    RSS growth 1.8 MB/h 8.1 MB/h

    The CPU difference is about what the ~1% estimate predicted and is within run-to-run noise. The result is "no regression", not a measured win. The strip change mostly saves memory on sports scrollers, which these windows didn't spend much time in.

  • Ran the test suite (pytest): new test_plugin_update_tick_throttle.py (on the real run() through the golden-trace harness), test_sports_scroll_strip_check.py, test_follower_scroll_image_handoff.py. Rebased onto fix(ipc): a plugin reload no longer freezes the panel during Vegas #723 and re-ran its reload/IPC tests (261 passed). The full suite passes, apart from 8 psutil-dependent tests that fail the same way on main.

Plugin compatibility

  • No plugin breakage expected

Notes for reviewer

Since #723 a plugin reload can finish between frames rather than at the top of the loop. The reloaded plugin's first update then comes from the next throttled tick, at most 0.25 s later.

🤖 Generated with Claude Code

ChuckBuilds and others added 2 commits October 2, 2026 15:14
The frame loops and the dwell sleep called _tick_plugin_updates() after
every frame, about 125 times a second on a scroller. Each call ran
PluginManager.run_scheduled_updates(), which copies the plugin dict and
takes several locks per plugin to find, almost always, that nothing is
due: about 100 us with 20 plugins on a Pi 4, 1.2% of the render thread.
No update interval is shorter than 5 s.

They now call _tick_plugin_updates_if_due(), which runs the pass at most
every PLUGIN_UPDATE_TICK_INTERVAL (0.25 s, monotonic clock, like
_service_pending_changes). The top of each loop pass keeps calling
_tick_plugin_updates() unthrottled, because that is where a plugin just
loaded, reloaded or enabled for on-demand gets its first update. The
Vegas update thread calls the plugin manager directly and is unchanged.

Golden traces unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uc5DAbSwGGUTm3MrCtpC2m
SportsScrollDisplay.display_scroll_frame (every frame) and
has_cached_content, and the sync follower's per-frame check of the Vegas
strip, tested ScrollHelper.cached_image for truth. When the helper had
deferred the image (append_content, drop_scrolled_prefix, patch_columns),
that read built it from cached_array and kept it, so the strip was held
twice: 3.5 ms and about 1 MiB more for a 4288x64 strip on a Pi.

has_strip() is true exactly when cached_image would be truthy (a PIL
image is always truthy, the empty-content placeholder included), so the
answers are unchanged. A scroll_helper without has_strip (a plugin's own
helper, a test double) is still asked cached_image. Reads that need the
image itself (the leader's sync push, Vegas capture in plugin_adapter,
render_pipeline's image push, vegas_audit) are left alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uc5DAbSwGGUTm3MrCtpC2m
@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 46 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9244a5f9-8003-4034-8e30-b7be7299b8a6
📥 Commits

Reviewing files that changed from the base of the PR and between ee78977 and e8be4bc.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • src/common/sports_scroll.py
  • src/display_controller.py
  • test/test_follower_scroll_image_handoff.py
  • test/test_plugin_update_tick_throttle.py
  • test/test_sports_scroll_strip_check.py
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity

Metric Results
Complexity 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@ChuckBuilds
ChuckBuilds merged commit 0d179fd into main Oct 3, 2026
15 checks passed
@ChuckBuilds
ChuckBuilds deleted the perf/render-loop-overhead branch October 3, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant