git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit718d45cf8c48a8d8826eaebd751a4609fa722148
parent9344a826ea
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-06 15:22
perf(runner): an isolated update goes to sleep without a warm-down

Every genuine redraw armed the 200 ms WARM_DOWN, ~12 more turns at frame
rate with a bufferless keepalive commit each. That is for interaction and
for streams — commits close together, where a late frame callback would
delay the next one — but an app's own isolated update has no next frame
to delay. The status bar's stats module redraws once a second and so woke
~13 times a second for it.

The warm-down is now armed only when the redraw is part of a sequence:
an input event arrived within SEQUENCE_GAP (500 ms), or the previous
genuine redraw did. Driver stamps `last_input` in every input entry
point (pointer, scroll, touch, pinch, keyboard focus, keys, IME). A
terminal printing or a sync narrating progress keeps its warm-down; a
once-a-second stat or a once-a-minute clock does not.

The stats module alone in a scale-2 shadow, 10 s, three runs each: main
thread 99-164 -> 23-26 wakes, CPU 10-30 -> 10-20 ms. Pacer tests: an
isolated update goes straight to the idle sleep; an input-led redraw
and back-to-back redraws still warm down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/backend/driver.rs | 21 ++++++++++++++++++++
 src/backend/shell.rs  | 55 ++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 73 insertions(+), 3 deletions(-)

diff --git a/src/backend/driver.rs b/src/backend/driver.rs
index 982376f..c54da97 100644
--- a/src/backend/driver.rs
+++ b/src/backend/driver.rs
@@ -150,6 +150,10 @@ impl<A: Application> Turn<'_, A> {
 /// The input state a session carries, and the routing over it.
 pub struct Driver {
     pub mods: Modifiers,
+    /// When the last input event of any kind arrived — what makes a redraw
+    /// interactive, which is the redraw the runner's warm-down is for
+    /// (`shell::Pacer`).
+    pub last_input: Option<std::time::Instant>,
     pub pressed_key: Option<PressedKey>,
     /// The pointer's last position, window-logical (popup events translated).
     pub cursor_pos: (f32, f32),
@@ -215,6 +219,7 @@ impl Driver {
         Self {
             mods: Modifiers::default(),
             pressed_key: None,
+            last_input: None,
             cursor_pos: (0.0, 0.0),
             buttons_down: 0,
             last_pinch_scale: 1.0,
@@ -282,12 +287,18 @@ impl Driver {
     /// hover state is stale from enter to first move, and a press in that
     /// window can misroute (e.g. a divider press falling through to the
     /// movable-root plate window drag).
+    fn note_input(&mut self) {
+        self.last_input = Some(std::time::Instant::now());
+    }
+
     pub fn pointer_enter<A: Application>(&mut self, t: Turn<'_, A>, pos: LogicalPosition) {
+        self.note_input();
         self.pointer_motion(t, pos);
     }
 
     /// The pointer moved to `pos`.
     pub fn pointer_motion<A: Application>(&mut self, t: Turn<'_, A>, pos: LogicalPosition) {
+        self.note_input();
         let mut rebuild = false;
         t.app.handle_pointer_move(pos, &mut rebuild);
         if rebuild {
@@ -302,6 +313,7 @@ impl Driver {
     /// first; then clear hover with an off-screen move — safe now that no
     /// drag is held.
     pub fn pointer_leave<A: Application>(&mut self, mut t: Turn<'_, A>) {
+        self.note_input();
         if self.buttons_down != 0 {
             let (px, py) = self.cursor_pos;
             for btn in [MouseButton::Left, MouseButton::Right, MouseButton::Middle] {
@@ -336,6 +348,7 @@ impl Driver {
         pos: LogicalPosition,
         site: PressSite,
     ) -> Press {
+        self.note_input();
         self.buttons_down |= button_bit(btn);
         let (lx, ly) = (pos.x, pos.y);
 
@@ -389,6 +402,7 @@ impl Driver {
 
     /// A button came up at `pos`.
     pub fn pointer_release<A: Application>(&mut self, mut t: Turn<'_, A>, btn: MouseButton, pos: LogicalPosition) {
+        self.note_input();
         self.buttons_down &= !button_bit(btn);
         let mut rebuild = false;
         let msg = t.app.handle_mouse_input(btn, ElementState::Released, pos, &mut rebuild);
@@ -398,6 +412,7 @@ impl Driver {
     /// One coalesced frame of scrolling at `pos`. Publishes the frame's
     /// phase (`scroll_motion::set_scroll_phase`) before the app sees it.
     pub fn scroll<A: Application>(&mut self, t: Turn<'_, A>, frame: ScrollFrame, pos: LogicalPosition) {
+        self.note_input();
         let ScrollFrame { h, v, discrete_h, discrete_v, source, stop } = frame;
         // Per-app scroll factors from input.kdl (`<app>`/`cce-ui` domain
         // `input { }` blocks); the compositor's global device scaling has
@@ -438,6 +453,7 @@ impl Driver {
         actions: Vec<super::touch::TouchAction>,
         scroll_at: Option<(f32, f32)>,
     ) {
+        self.note_input();
         use super::touch::TouchAction;
         for action in actions {
             let mut rebuild = false;
@@ -514,6 +530,7 @@ impl Driver {
 
     /// The pinch's cumulative `scale` moved, with the pointer where it last was.
     pub fn pinch_update<A: Application>(&mut self, t: Turn<'_, A>, scale: f32) {
+        self.note_input();
         let factor = scale / self.last_pinch_scale;
         self.last_pinch_scale = scale;
 
@@ -564,6 +581,7 @@ impl Driver {
     /// The window gained (`true`) or lost keyboard focus. Losing it drops a
     /// held key and the held modifiers, whose releases go elsewhere.
     pub fn keyboard_focus<A: Application>(&mut self, t: Turn<'_, A>, focused: bool) {
+        self.note_input();
         if !focused {
             self.pressed_key = None;
             self.mods.ctrl = false;
@@ -587,6 +605,7 @@ impl Driver {
         text: Option<String>,
         state: ElementState,
     ) {
+        self.note_input();
         let event = KeyEvent {
             state,
             logical_key,
@@ -648,6 +667,7 @@ impl Driver {
     /// commits on its own keys), never repeated, and past the chords: a
     /// commit of "z" is a "z", not half of an undo.
     pub fn commit_text<A: Application>(&mut self, t: Turn<'_, A>, text: String) {
+        self.note_input();
         if text.is_empty() {
             return;
         }
@@ -678,6 +698,7 @@ impl Driver {
     /// commit, or the commit follows). The editing widget shows it from the
     /// next frame.
     pub fn preedit<A: Application>(&mut self, t: Turn<'_, A>, preedit: Option<crate::ime::Preedit>) {
+        self.note_input();
         crate::ime::set_preedit(preedit);
         *t.redraw = true;
     }
diff --git a/src/backend/shell.rs b/src/backend/shell.rs
index c2b7153..79672fc 100644
--- a/src/backend/shell.rs
+++ b/src/backend/shell.rs
@@ -38,6 +38,18 @@ pub const IDLE_DISPATCH: Duration = Duration::from_millis(1000);
 /// compositor.
 pub const WARM_DOWN: Duration = Duration::from_millis(200);
 
+/// A redraw this soon after the previous one, or after input, belongs to a
+/// sequence and gets the [`WARM_DOWN`]; one further from both is an
+/// isolated update and does not.
+///
+/// The warm-down is for interaction and for streams (a terminal printing, a
+/// sync narrating progress): a run of commits close together, where a late
+/// frame callback would delay the next one. An app's own isolated update —
+/// the status bar's stats once a second, a clock once a minute — has no next
+/// frame to delay, and each one used to buy twelve more wakes at frame rate:
+/// the stats module woke ~13 times a second for one redraw (2026-10-06).
+pub const SEQUENCE_GAP: Duration = Duration::from_millis(500);
+
 /// Upper bound on an idle sleep. The loop is woken early by any event the
 /// shell's window system delivers and by messages on the app's sender, so
 /// this only caps how long an app-side poll that bypasses both (see
@@ -117,13 +129,15 @@ pub struct Pacer {
     slept_idle: bool,
     /// Turns stay at frame rate until this instant (see [`WARM_DOWN`]).
     warm_until: Option<Instant>,
+    /// When the last genuine redraw was presented (see [`SEQUENCE_GAP`]).
+    last_redraw: Option<Instant>,
     last_title: String,
 }
 
 impl Pacer {
     /// Before the first turn the loop runs at [`ACTIVE_DISPATCH`].
     pub fn new(title: String) -> Self {
-        Self { last_tick: Instant::now(), slept_idle: false, warm_until: None, last_title: title }
+        Self { last_tick: Instant::now(), slept_idle: false, warm_until: None, last_redraw: None, last_title: title }
     }
 
     /// One turn of the loop. The cadence is ACTIVE while anything is in
@@ -177,9 +191,17 @@ impl Pacer {
         let pending = shell.frame_pending();
 
         if *shell.redraw() {
-            // Genuine dirt (input, app state, animation) extends the warm window;
+            // Genuine dirt that is part of a sequence — input just arrived, or
+            // the last redraw was moments ago — extends the warm window;
             // warm-down renders below do NOT, so idle decays in one window.
-            self.warm_until = Some(Instant::now() + WARM_DOWN);
+            // An isolated update gets none (see `SEQUENCE_GAP`).
+            let now = Instant::now();
+            let recent = |t: Option<Instant>| t.is_some_and(|t| now.duration_since(t) < SEQUENCE_GAP);
+            let input = shell.turn().0.last_input;
+            if recent(input) || recent(self.last_redraw) {
+                self.warm_until = Some(now + WARM_DOWN);
+            }
+            self.last_redraw = Some(now);
         }
         let mut rendered = false;
         if *shell.redraw() && !pending {
@@ -346,6 +368,8 @@ mod tests {
     #[test]
     fn a_redraw_presents_then_warms_down_at_frame_rate() {
         let (mut p, mut s) = (Pacer::new(String::new()), Mock::new());
+        // Interactive: an input event just arrived.
+        s.driver.last_input = Some(Instant::now());
         s.redraw = true;
         assert_eq!(p.turn(&mut s), Step::Sleep(ACTIVE_DISPATCH));
         assert_eq!(s.did, vec![Did::Present(true)]);
@@ -356,6 +380,31 @@ mod tests {
         assert_eq!(s.did.last(), Some(&Did::Present(false)));
     }
 
+    #[test]
+    fn an_isolated_update_presents_and_goes_idle_without_a_warm_down() {
+        let (mut p, mut s) = (Pacer::new(String::new()), Mock::new());
+        // No input, no recent redraw: the app's own once-a-second update.
+        s.redraw = true;
+        assert_eq!(p.turn(&mut s), Step::Sleep(ACTIVE_DISPATCH));
+        assert_eq!(s.did, vec![Did::Present(true)]);
+        // No warm-down re-render: straight to the idle sleep.
+        assert_eq!(p.turn(&mut s), idle());
+        assert_eq!(s.did, vec![Did::Present(true)], "an isolated update warmed down");
+    }
+
+    #[test]
+    fn a_redraw_soon_after_another_warms_down_without_input() {
+        let (mut p, mut s) = (Pacer::new(String::new()), Mock::new());
+        s.redraw = true;
+        p.turn(&mut s);
+        p.turn(&mut s);
+        // A stream: the next update lands well inside SEQUENCE_GAP.
+        s.redraw = true;
+        assert_eq!(p.turn(&mut s), Step::Sleep(ACTIVE_DISPATCH));
+        assert_eq!(p.turn(&mut s), Step::Sleep(ACTIVE_DISPATCH));
+        assert_eq!(s.did.last(), Some(&Did::Present(false)), "the stream lost its warm-down");
+    }
+
     #[test]
     fn a_pending_frame_withholds_the_present_and_keeps_the_redraw() {
         let (mut p, mut s) = (Pacer::new(String::new()), Mock::new());