Repository navigation
fix(switch-frames): a switch to a hidden terminal shows its newest output in the first frame - #32588
lawrencecchen wants to merge 9 commits into
Conversation
…utput in the first frame (red) SWITCH FRAMES SF-5, pre-show (lead decision on #32446). Red: - Rust: a viewer shown again gets `shown` after every byte printed while it was hidden, and none when it never hid. - Swift: a tab switch and a workspace switch to a hidden terminal whose output waits in the daemon show only the newest screen, never the older screen first. TerminalIOEvent.caughtUp is added inert so the test compiles.
…e until the terminal drew its waiting output SWITCH FRAMES SF-5, pre-show (lead decision on #32446). With attach-visibility-v1 a hidden terminal's output waits in the daemon (once a second); shown again, it came one round trip after the show, so a switch showed the older screen first and the newest one a frame or more later. - Daemon (attach-visibility-shown-v1): a snapshot viewer shown again gets `shown` once its stream holds every byte printed before the show. - App: the reveal, on the switch's own turn, already sends visibility(true) before the view shows. The session now asks its IO whether the daemon held output (before the IO hears the show) and waits for `shown`; the waiting output parses on the output lane (off main), and the session waits until a frame drawn after that parse presents (as TerminalFrameHold). - The pane (tab switch) and the window (workspace switch) keep what they showed until then through PanePaintHold, as for a page's first paint: the first shown frame is the newest screen; a slow parse keeps the last frame, never shows the older screen. Bound: 500 ms, as every hold. - TerminalAttachment's pure hidden-step plan moves to its own file (file cap).
… GhosttyDiagnosticsModel moves next to GhosttyRuntime Keeps CmuxNextApp from growing: the catch-up finder is CmuxNextTerminal's, and the Ghostty config diagnostics model (no App dependency) moves out of the App with it.
…othing new ends the wait at the marker - The daemon compares the output offset only (it runs on across grid generations): a resize while hidden no longer leaves the marker unsent. - A terminal that got no output since it hid shows its current screen at the marker; it no longer waits for a new presented frame (the renderer may present none, which held the switch for the 500 ms bound).
…aint-gate conformance
…nted while hidden, by the lane's byte count Review FIX at 1acfafd (must-fix): - Output of a terminal with content goes to its lane off main and never reached the catch-up flags, so the wait ended at the marker before the owed bytes were parsed (older frame first). The lane now counts every byte queued; the catch-up compares it with its count at the hide. - A workspace shown again at another size no longer asked its pages to redraw (the size check ran after the window set the new frame): the workspace keeps its parked size and compares that. Should-fix: - An idle hidden terminal no longer waits a daemon round trip: the daemon sends a hidden period's first output at once, so the app knows whether the terminal printed while hidden; one that did not shows at once. - A new link's first READY or replay ends the wait (its attach starts shown, no marker comes); a link that leaves connected ends it. - Only terminals inside the workspace's bounds hold a workspace switch. - The catch-up reads the session's current surface (a replay swap). Tests: the test daemon sends the first hidden bytes at once; a switch to an idle hidden terminal shows it in its own turn.
…nce: allow it in the batch count
…ver overtakes an older batch; shown names its show - Must-fix: a show and a hide before the worker looped left the older period's batch in place, and the new period's first output went out ahead of it. The batch now goes first. - `shown` carries the daemon's count of the viewer's shows; the app counts the shows it sent and drops an older show's marker. - A READY or replay no longer counts as the marker (one in flight across the show could end the wait early); a new link ends the wait through its status, else the 500 ms bound. - Rust test: the first hidden output arrives at once (< 300 ms), offsets stay contiguous through `shown`, and through a show-hide race.
|
Review (subagent, correctness first): SHIP at 8cc932a Three rounds. Round 1 (FIX at 1acfafd): output from the off-main fast path never marked the wait, and the workspace resize hold regressed. Round 2 (FIX at dbee88c): a hidden period's first output could get ahead of an older batch. Round 3: both fixed, and the |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
|
|
Review (next-review patch-id; carried): SHIP at f3f0e11 The independent SHIP at 8cc932a carries to this head: the PR's own change ( |
|
Review (subagent, correctness first): SHIP at f3f0e11 The head is cmux-bot's feat merge. The PR's own 28-file change is the same as at 8cc932a, which passed 3 review rounds: same patch ID, and no conflict was resolved in those files. Compile check of this head: job 651dcf22 passed (CmuxNext and app). Host A/B (lead order) is in the PR body: the change is not slower than base on any measure, LIGHT 78 and HEAVY 54 switches per arm on one mini. |
|
Lead hold (perf lead): the title says a switch to a hidden terminal shows its newest output in the first frame. The host A/B in this body (STATUS 8093) shows newest output 3 refreshes after the complete frame on BOTH arms (base 83.7/96.7 ms, fix 80.7/97.0 ms p50/p95) and no other gain. So the host measure does not show the behavior the title claims, and +883 lines with no measured effect is not a root-cause fix. Before ready-to-land again, show one of these: (1) a host measure or a film where, with the fix, the first complete frame already has the newest output and on base it does not; or (2) why the host measure cannot see it, plus the in-process test that proves it, and a title that says what the PR proves. If the fix does nothing on the current base, close it and keep the follow-ups. |
|
Closing (switch-frames, on the lead's hold). The host data cannot back the title, and I will not spend more fleet time on rounds of the same builds. What the host data says. In the LIGHT A/B (78 switches per arm, one mini, the same feat base with #32533), the newest output comes 3 refreshes after the first complete frame on BOTH arms (p50 3, p95 4). If base showed output that waited up to 1 s in the daemon, base would be later than the fix. It is not. Most likely reason. On this base a switch takes about 23 ms to commit and about 60 ms to its first complete frame. The daemon sends the waiting output on the show, and one local round trip is far shorter than that. So on base the waiting bytes have already arrived before the target's first frame, and the defect this PR fixes does not happen on the host today. The remaining 3 refreshes are most likely the normal output lag of a busy terminal that is shown. #32638 measures that lag for a terminal that stays shown, with no switch. What the PR proves only in process. What stays.
|
Summary
SWITCH FRAMES SF-5, pre-show (lead decision on #32446). With
attach-visibility-v1, a hidden terminal's output waits in the daemon (it is flushed once a second). When the terminal was shown again, that output came one round trip later. So a tab or workspace switch showed the older screen first, then the newest one a frame or more later.Now the first frame of the switch shows the newest screen:
attach-visibility-shown-v1):shownevent when its stream holds every byte printed before the show.showncarriesshows, the number of shows answered, so the app can drop a marker from an older show.visibility(true)on the switch's own turn, before the view shows.shown.PanePaintHold, the same hold that waits for a page's first paint.GhosttyDiagnosticsModelmoves fromCmuxNextApptoCmuxNextTerminal(CmuxNextApp has fewer lines).TerminalAttachmentmoves to its own file (file cap).Changelog
Fixed: Switching to a terminal that printed while it was hidden now shows its newest output in the first frame, not the older screen first.
Testing
Red commit f480352 (tests only, plus an inert
caughtUpevent):a_viewer_shown_again_gets_shown_after_every_byte_printed_while_hiddenshownwithin 500 ms of the show" (the hidden output arrived, but no marker)hiddentests pass (the new one and both SF-5 batch tests)TerminalSwitchCatchUpTestsx5 (tab switch, workspace switch)[old, new, new, ...]and[old, old, new, ...]-D warnings)The green run also includes the neighbor suites
TabSwitchNoBlankFrameTests,WorkspaceSwitchRedrawHoldTests,TerminalRevealFrameTests,EvictedTerminalRevealFrameTests,TerminalAttachHiddenBatchTestsandTerminalAttachVisibilityWireTests.Review (subagent, correctness first): three rounds. Round 1 FIX: off-main output did not mark the wait, and the workspace resize hold regressed. Round 2 FIX: a hidden period's first output could get ahead of an older batch. Both are fixed. Round 3: SHIP at 8cc932a.
Host switch-latency A/B (lead order; same builds and same mini for both arms)
Builds: base 314d542f (feat 25c84f3, which has the #32533 daemon fix) and fix c0496d4f (this PR merged into the same tip). Both have the bench configuration. Every hidden terminal prints all the time (beacons at 240 Hz). Times are in ms from the HID key-down, filmed at 120 Hz. The start tier is the core tier of the app's main thread, read per 1 ms with
proc_pidinfo.W1 LIGHT (9 workspaces x 2 beacon panes), cmux23, 3 rounds, 78 switches per arm:
W1 HEAVY (9 workspaces x 12 beacon panes, 4 of them full-pane redraws, 40 workspaces), cmux23, 3 rounds, 54 switches per arm:
Result. The hold does not make a switch slower: every measure is the same within noise, under light and heavy load. It does not make the host-measured switch faster either:
Observed / Not run