Skip to content

perf: dirty-flag rendering + synchronized output (DEC PM 2026) - #19

Open
wasabeef wants to merge 1 commit into
mainfrom
perf/no-flicker-rendering
Open

perf: dirty-flag rendering + synchronized output (DEC PM 2026)#19
wasabeef wants to merge 1 commit into
mainfrom
perf/no-flicker-rendering

Conversation

@wasabeef

@wasabeef wasabeef commented May 8, 2026

Copy link
Copy Markdown
Owner

Summary

Inspired by Claude Code's NO_FLICKER mode investigation — emu already uses alt-screen rendering (the core of NO_FLICKER), but two lower-level optimizations were missing.

1. Dirty-flag rendering (needs_render)

Add pub needs_render: bool to AppState (default true for initial paint).

Before: terminal.draw() was called unconditionally on every event-loop iteration — roughly 125 fps even when the screen was completely static.

After: The main loop skips terminal.draw() unless either:

  • events_processed > 0 (user pressed a key), or
  • state.needs_render == true (a background task updated visible state)

Coverage across all background mutation paths:

File How dirty is set
state/logs.rs add_log(), update_single_*_device_status() call self.needs_render = true
state/details.rs update_cached_device_details()
state/mod.rs add_notification(), dismiss_expired_notifications() (conditional)
background.rs device list loads call mark_dirty()
refresh.rs both full and status-only refresh paths call mark_dirty()
api_levels.rs install/uninstall progress callbacks, result handlers, and post-operation refresh spawns
create_device.rs "Creating…" / "Finalizing…" status messages in the background spawn

2. Synchronized Output (DEC PM 2026)

Wrap each terminal.draw() with:

crossterm::queue!(terminal.backend_mut(), BeginSynchronizedUpdate)?;
terminal.draw(|f| ui::render::draw_app(f, &mut state, &ui::Theme::dark()))?;
crossterm::execute!(terminal.backend_mut(), EndSynchronizedUpdate)?;

The terminal receives \x1b[?2026h, then all cell-diff writes from ratatui (in a single flush), then \x1b[?2026l. Supporting terminals (iTerm2, kitty, WezTerm, Windows Terminal, …) buffer everything between the two sequences and paint atomically — no partial-frame flicker.

crossterm 0.29 already exposes BeginSynchronizedUpdate / EndSynchronizedUpdate natively, so no new dependency is required. Terminals that do not support the protocol silently ignore the sequences.

Test plan

  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • RUST_TEST_THREADS=1 cargo test --bins --tests --features test-utils — 790 tests pass (pre-push hook verified)
  • Existing flaky test (error_recovery_test::test_resource_exhaustion_recovery) confirmed pre-existing env-var race; passes in isolation and on main
  • 5 self-review passes: correctness, mutation-site coverage, borrow/ordering analysis, test compatibility, final lint+test

Two independent rendering optimizations:

## Dirty-flag rendering (needs_render)
Add `needs_render: bool` to `AppState` (default true).  Background tasks
and state-mutation methods set the flag; the main event loop skips
`terminal.draw()` when neither user input nor a state change occurred.

Eliminates the unconditional ~125 fps redraw when the app is idle:
- `add_log()` / device-status helpers / `update_cached_device_details()`
  set the flag directly inside the existing mutation methods.
- Background spawns in `api_levels`, `background`, `create_device`,
  `refresh` set `state.needs_render = true` (or call `mark_dirty()`)
  after direct field mutations that bypass the helper methods.
- `add_notification()` / `dismiss_expired_notifications()` set the flag
  so notification appearance and auto-dismiss trigger renders.

## Synchronized Output (DEC PM 2026)
Wrap every `terminal.draw()` call with
`crossterm::queue!(BeginSynchronizedUpdate)` before and
`crossterm::execute!(EndSynchronizedUpdate)` after.

The terminal buffers all display writes between the two escape sequences
(`\x1b[?2026h` / `\x1b[?2026l`) and paints them atomically, preventing
partial-frame flicker on supporting terminals (iTerm2, kitty, WezTerm,
Windows Terminal ...).  Terminals that do not support the protocol silently
ignore the sequences - no behaviour change on unsupported terminals.

crossterm 0.29 exposes `BeginSynchronizedUpdate` / `EndSynchronizedUpdate`
natively so no extra dependency is needed.
@github-actions

github-actions Bot commented May 8, 2026

Copy link
Copy Markdown

Code Metrics Report

Coverage Test Execution Time
68.5% 1m32s

Code coverage of files in pull request scope (50.5%)

Files Coverage
src/app/api_levels.rs 57.4%
src/app/background.rs 53.0%
src/app/create_device.rs 19.0%
src/app/mod.rs 25.0%
src/app/refresh.rs 39.9%
src/app/state/details.rs 83.3%
src/app/state/logs.rs 73.2%
src/app/state/mod.rs 86.7%

Reported by octocov

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