git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit5843deb9a4fba5356afb7780ab540c9d95de09eb
parent71ad9a530b
authorLucas Galante <[email protected]>
date2026-08-13 00:30
perf: only write state.json when the serialized state actually changed

save_state runs at the end of every transaction commit, and a 1 Hz status-bar
clock tick is enough to run a transaction — so an idle desktop rewrote ~21KB of
window state to disk every second (measured: state.json mtime advancing every
~2s with nothing on screen changing, ~10MB/hour of writes for state that never
moved).

Keeps the last successfully written JSON and skips the write when the new
serialization is byte-identical, so the file stays exactly as current as before.
The cache is only updated after a successful write, so a failed write retries on
the next transaction instead of latching. The "Saving state to" log moved to the
write site, so it now reports actual writes rather than attempts.

This does not touch the once-per-second manage transaction itself, which is the
likelier source of the ~17% idle CPU and remains open.

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

 src/server/window_manager.rs | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index aa95d77..5076563 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -114,6 +114,14 @@ pub struct WindowManager {
     pub injected_key_mods: u32,
     pub restore_queue: Vec<SavedWindowState>,
     pub last_window_states: Vec<SavedWindowState>,
+    /// The JSON last successfully written to `state.json`. `save_state` runs at
+    /// the end of every transaction commit, and a 1 Hz status-bar clock tick is
+    /// enough to run a transaction — so an idle desktop rewrote ~21KB to disk
+    /// every second for state that never changed. Skipping the write when the
+    /// serialization is byte-identical keeps the file exactly as current as
+    /// before while making an idle session silent on disk. `None` until the
+    /// first write, so a fresh start always writes once.
+    last_saved_state_json: Option<String>,
     /// One-shot placement hints (`place-next <app_id> <x> <y>` over IPC):
     /// the next map of a floating toplevel with this app_id lands near the
     /// given layout position instead of its remembered spot — widget-spawned
@@ -574,8 +582,6 @@ impl WindowManager {
             log::error!("Could not resolve state file path");
             return;
         };
-        log::debug!("Saving state to {}", path_str);
-        
         let focused_win = self.focused_window();
         let mut saved_wins = Vec::new();
         let mut last_states = self.last_window_states.clone();
@@ -698,12 +704,19 @@ impl WindowManager {
         };
         
         if let Ok(json_str) = serde_json::to_string_pretty(&state) {
+            if self.last_saved_state_json.as_deref() == Some(json_str.as_str()) {
+                return;
+            }
+            log::debug!("Saving state to {}", path_str);
             let path = std::path::Path::new(&path_str);
             if let Some(parent) = path.parent() {
                 let _ = std::fs::create_dir_all(parent);
             }
-            if let Err(e) = std::fs::write(path, json_str) {
-                log::error!("Failed to write state file: {}", e);
+            match std::fs::write(path, &json_str) {
+                // Only remember it once it is actually on disk, so a failed
+                // write is retried on the next transaction rather than latched.
+                Ok(()) => self.last_saved_state_json = Some(json_str),
+                Err(e) => log::error!("Failed to write state file: {}", e),
             }
         }
     }