perf(ui): drop unconditional per-frame repaint — idle window goes to ~0% CPU - #86
Merged
Conversation
…~0% CPU update() ended with an unconditional ctx.request_repaint(), forcing egui to render a full frame every vsync. On a 165 Hz display that pegs the UI thread (~65% of one core idle; 353% under unthrottled Xvfb) even when the window sits untouched. Replace it with a scoped repaint policy. All four channel drains in update() now report whether they drained anything, and repaints are only requested when the UI actually has new state to show: - any drained message (cover art, download progress, play result, async op) -> render immediately - active downloads -> request_repaint_after(250 ms) so the passive ProgressBar and the Instant-based ETA countdown keep ticking between channel messages (egui::Spinner self-repaints while visible, so cover loading / account / proton / steamguard spinners cover themselves) - pending play result -> request_repaint_after(1 s) so a game exit or login-required event is picked up while the window is idle With nothing pending the UI schedules no repaint and the main thread blocks in the event loop until the next input event (which egui repaints on itself). Measured idle (10 s samples, Xvfb :99, fresh config, no Steam session): before: 353% of one core (UI thread), 331% whole-process (top-style) after: 0% of one core (UI thread), 1.6% whole-process Input wake: 40 XTEST key events -> 288% burst, then settles back to 0%. Same binary pair, same display, only the repaint policy differs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
update()ended with an unconditionalctx.request_repaint(), forcing egui to render a full frame every vsync. On a 165 Hz display that pegs the UI thread even when the window sits untouched — the other half of the idle-CPU burn alongside #85's cover-fetch gate (this stacks on top of it).What changed
The four channel drains in
update()now return whether they drained anything; the unconditional repaint is replaced by a scoped policy:image_rxdrain (cover art)Spinnerself-repaints while fetch pendingrxdrainrequest_repaint_after(250ms)while tasks active (ProgressBar is passive; ETA is Instant-based and counts by wall-clock)play_result_rxdrainrequest_repaint_after(1s)while rx is Some (game exit / login-required can arrive while idle)operation_rxdrain (45 async ops)Spinnerself-requests repaint while visible — no change neededWith nothing pending, no repaint is scheduled and the main thread blocks in the event loop until the next input event (egui repaints on input itself).
Measured verification (idle window, not just compile)
Same binary pair, same Xvfb display, fresh config (no Steam session), 10 s samples:
The window was verified alive and input-responsive after the change (focus + synthetic input → repaint burst → returns to idle), not assumed.
Test Plan
cargo check --all-targetscleancargo test --lib— 141 passed