Skip to content

perf(ui): repaint only the Mission Control card that moves - #72

Merged
ShriD5 merged 1 commit into
mainfrom
perf/mission-control
Oct 30, 2026
Merged

ShriD5 merged 1 commit into
mainfrom
perf/mission-control

Conversation

@ShriD5

@ShriD5 ShriD5 commented Oct 30, 2026

Copy link
Copy Markdown
Owner

Mission Control re-rendered the whole board on every animation frame. Each working card's travelling current asked the shared pulse clock for frames on the board's behalf, so the whole view, every card included, re-rendered at the 30 Hz pulse. The header speedster added its own 8 to 12 Hz on top, and every render started from a fresh clone of the Board.

Changes

  • Cards are cached child views (the MiniSpinner / zoomie-mascot pattern). The board passes a card new props only when they differ, comparing in place so nothing is cloned. A pulse frame re-renders only that card. The cards around it reuse last frame's layout and paint.
  • The header speedster is a cached sprite view. Frame steps notify only the sprite. It now follows the Dock speedster's rules from perf(ui): idle-zero Dock speedster, zoomie mascots and usage probe #70 (Speedster::plan):
    • It holds still while asleep and once an alert has settled.
    • Working moods cap at 4 fps on battery or in Low Power Mode.
    • Nothing moves while Reduce Motion is on or no window is visible (AppVisibility).
  • The board is an Arc<Board>, so a render no longer clones it.
  • GODSPEED_FRAME_STATS=1 now also logs mission-control and mission-control-card render cadence, next to the existing shell and sidebar counters.

Measured

This is a gpui test with 4 working cards and 2 finished cards. It counts one second of pulse frames, and the test platform draws a window whenever it is dirty. The "before" numbers come from the same harness run on origin/main with a render counter added.

per second before after
Board (MissionControl) notifications 41 0
Board render passes 41, each re-rendering all 6 cards 41, each a thin pass over cached cards
Card renders 246 (6 × 41) 120 (4 working × 30), finished cards 0
Header speedster 11 board renders 11 renders of its own 76 pt view
Board clones 41 0

The test a_pulse_frame_repaints_only_the_working_cards asserts that no pulse or speedster frame notifies the board and that finished cards never re-render. The test the_header_speedster_holds_still_when_nobody_can_see_it covers the visibility rule.

Notes

  • gpui still runs the board's render on each frame. When a view is notified, gpui marks its ancestors dirty, so a card's pulse re-runs the parent's render. That pass is now thin. It builds the header, the group headers and one cached placeholder per card, and it rebuilds the board at most once a second for the elapsed labels, as before. The number of window frames is unchanged: 30 Hz while a card works, plus the speedster's steps.
  • The working current stays on the 30 Hz clock. It moves continuously, and at 15 Hz its bright core would jump about 12 px per frame. Waiting and settling cards were already on 15 Hz.
  • Blur: the overlay's backdrop blur (MENU_BLUR * 1.5) is a scene primitive, so every window frame recomputes it. This PR leaves the frame count and the blur unchanged.
  • Grid virtualization was skipped. A board holds live chats plus those finished in the last day, so a few dozen cards. Cards outside the viewport are cached leaves that paint nothing and request no frames. Moving to list / uniform_list would mean flattening the device and project groups into rows, and reworking focus-reveal scrolling and the edge fade. That is not worth the risk at this size.

Check by hand

  • Open Mission Control with a few working agents. The currents should glide as before. The speedster should jog or sprint, and hold its sleeping frame after about 45 s of quiet.
  • Hover, focus (arrow keys), A and Enter on cards should behave as before.
  • To see the cadence, run with GODSPEED_FRAME_STATS=1. The logs show view render cadence with view=mission-control-card.

Every working card's travelling current asked the pulse clock for frames
on behalf of the whole board, so with four agents working the board was
notified about 30 times a second (41 with the header speedster) and each
time re-rendered every card from a fresh clone of the board.

- Each card is its own cached view (the MiniSpinner pattern). The board
  hands a card new props only when they differ (compared in place, no
  clone), so a pulse frame re-renders that card alone and its
  neighbours reuse last frame's layout and paint.
- The header speedster is a cached sprite view. Its frame steps no
  longer notify the board, and it follows the Dock's rules: asleep and a
  settled alert hold still, working moods cap at 4 fps on battery or in
  Low Power Mode, and nothing moves while no window is visible.
- The board is held as Arc<Board> instead of being cloned per render.

The working current stays on the 30 Hz clock: it is continuous motion,
and at 15 Hz its bright core would jump about 12 px per frame. Waiting
and settling cards already run at 15 Hz.

Measured in a gpui test (4 working + 2 finished cards, one second of
frames): board notifies 41/s -> 0, card renders 246/s (6 per board
render) -> 120/s (4 x 30, finished cards 0), speedster frames on its
own 11/s view. gpui still re-runs the board's render on each frame
(ancestors of a dirty view re-render), now as a thin pass over cached
cards. GODSPEED_FRAME_STATS=1 logs the mission-control and
mission-control-card cadence.
@ShriD5
ShriD5 merged commit 7d9176a into main Oct 30, 2026
21 checks passed
@ShriD5
ShriD5 deleted the perf/mission-control branch October 30, 2026 14:53
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