git.lucas.co / cce-status-interface
status bar
git clone https://git.lucas.co/cce-status-interface.git

commit111dd552d8ec329f39ce8ddd4bd6ec671d067132
parentb9cee16c34
authorLucas Galante <[email protected]>
date2026-07-17 14:47
perf: only redraw a status module when its displayed value changes

The system-stats poller pushes a full SystemStatsUpdated every second regardless
of change, and update() unconditionally set needs_rebuild for every message. So
each of the ~6 stats modules redrew once a second forever — even when its own
value was unchanged (the clock shows HH:MM yet redrew 59 needless times a minute)
— and the three non-stats modules (window/tray/light_source) redrew on every
stats push despite ignoring stats entirely. Each redraw commits a new buffer,
which the compositor answers with a full-screen backdrop blur re-bake.

Dedup per module: gate the redraw on whether this module's painted subset of the
stats (or the pushed viewport/layout/title string) actually changed. stats_signature
returns None for modules that ignore stats, so a stats push never redraws them.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/main.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 45 insertions(+), 2 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index e3e7a1b..22fffaf 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -84,6 +84,30 @@ pub struct SystemStats {
     pub brightness: String,
 }
 
+/// The subset of [`SystemStats`] a given module actually paints, as a comparable
+/// signature. The stats poller pushes a full `SystemStatsUpdated` every second
+/// regardless of change; deduping on this signature lets a module skip the redraw
+/// (and the compositor's whole-backdrop blur re-bake it triggers) when its own value
+/// is unchanged — e.g. the clock shows HH:MM and only changes once a minute.
+/// `None` means the module ignores stats entirely (window/tray/light_source), so a
+/// stats push never redraws it. Unknown / combined-bar modules compare everything.
+fn stats_signature(module: Option<&str>, s: &SystemStats) -> Option<String> {
+    match module {
+        Some("clock") => Some(s.clock.clone()),
+        Some("cpu") => Some(s.cpu.clone()),
+        Some("memory") => Some(s.memory.clone()),
+        Some("battery") => Some(format!("{}|{}|{}", s.battery, s.battery_capacity, s.battery_charging)),
+        Some("volume") => Some(format!("{}|{}", s.volume, s.volume_muted)),
+        Some("brightness") => Some(s.brightness.clone()),
+        Some("window") | Some("tray") | Some("light_source") => None,
+        _ => Some(format!(
+            "{}|{}|{}|{}|{}|{}|{}|{}|{}",
+            s.clock, s.memory, s.cpu, s.battery, s.battery_capacity,
+            s.battery_charging, s.volume, s.volume_muted, s.brightness
+        )),
+    }
+}
+
 #[derive(Debug, Clone)]
 pub(crate) enum CustomEvent {
     ViewportUpdated(String),
@@ -1008,18 +1032,35 @@ impl cce_ui::engine::Application for StatusApp {
     }
 
     fn update(&mut self, msg: Self::Message, needs_rebuild: &mut bool, _exit: &mut bool) {
+        // Default to redrawing; the high-frequency push events below clear this when
+        // their value is unchanged, so a once-a-second stats poll (or a repeated
+        // viewport/title push) no longer forces a redraw — and the compositor's
+        // whole-backdrop blur re-bake — every time.
+        let mut changed = true;
         match msg {
             CustomEvent::ViewportUpdated(t) => {
+                changed = self.viewport != t;
                 self.viewport = t;
             }
             CustomEvent::LayoutUpdated(l) => {
+                changed = self.layout != l;
                 self.layout = l;
             }
             CustomEvent::TitleUpdated(t) => {
+                changed = self.title != t;
                 self.title = t;
             }
             CustomEvent::SystemStatsUpdated(s) => {
                 log::debug!("[module-{}] stats updated, current width={}", self.selected_module_name.as_deref().unwrap_or("none"), self.width);
+                let module = self.selected_module_name.as_deref();
+                match stats_signature(module, &s) {
+                    // Module ignores stats (window/tray/light_source): never redraw here.
+                    None => changed = false,
+                    Some(new_sig) => {
+                        let old_sig = self.stats.as_ref().and_then(|o| stats_signature(module, o));
+                        changed = old_sig.as_deref() != Some(new_sig.as_str());
+                    }
+                }
                 self.stats = Some(s);
             }
             CustomEvent::TrayUpdated(item) => {
@@ -1080,8 +1121,10 @@ impl cce_ui::engine::Application for StatusApp {
                 *needs_rebuild = true;
             }
         }
-        self.needs_rebuild = true;
-        *needs_rebuild = true;
+        if changed {
+            self.needs_rebuild = true;
+            *needs_rebuild = true;
+        }
     }
 
     fn tick(&mut self, _dt: f32, needs_rebuild: &mut bool) {