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

commite147d6017a6281be30d72fcbdd88a9d81d28c901
parente77149bd7b
authorIsaac Freund <[email protected]>
date2024-07-28 15:56
WindowManager: state machine transitions

 river/WindowManager.zig | 100 ++++++++++++++++++++++++++++++++----------------
 1 file changed, 68 insertions(+), 32 deletions(-)

diff --git a/river/WindowManager.zig b/river/WindowManager.zig
index bc0aeef..229f86b 100644
--- a/river/WindowManager.zig
+++ b/river/WindowManager.zig
@@ -43,8 +43,8 @@ state: union(enum) {
     update_sent: u32,
     /// An update event was sent to the window manager and has been acked but not yet committed.
     update_acked,
-    /// The number of configures sent that have not yet been ack'd
-    configures_inflight: u32,
+    /// The number of configures sent that have not yet been acked
+    inflight_configures: u32,
 } = .idle,
 
 /// Pending state from windows to be sent to the wm in the next update sequence.
@@ -73,14 +73,12 @@ inflight: struct {
 
 dirty_idle: ?*wl.EventSource = null,
 
-/// Number of inflight configures sent to windows in the current transaction.
-inflight_configures: u32 = 0,
-transaction_timeout: *wl.EventSource,
+timeout: *wl.EventSource,
 
 pub fn init(wm: *WindowManager) !void {
     const event_loop = server.wl_server.getEventLoop();
-    const transaction_timeout = try event_loop.addTimer(*WindowManager, handleTransactionTimeout, wm);
-    errdefer transaction_timeout.remove();
+    const timeout = try event_loop.addTimer(*WindowManager, handleTimeout, wm);
+    errdefer timeout.remove();
 
     wm.* = .{
         .global = try wl.Global.create(server.wl_server, river.WindowManagerV1, 1, *WindowManager, wm, bind),
@@ -97,7 +95,7 @@ pub fn init(wm: *WindowManager) !void {
         .inflight = .{
             .render_list = undefined,
         },
-        .transaction_timeout = transaction_timeout,
+        .timeout = timeout,
     };
     wm.windows.init();
     wm.pending.dirty_windows.init();
@@ -112,7 +110,7 @@ fn handleServerDestroy(listener: *wl.Listener(*wl.Server), _: *wl.Server) void {
     const wm: *WindowManager = @fieldParentPtr("server_destroy", listener);
 
     wm.global.destroy();
-    wm.transaction_timeout.remove();
+    wm.timeout.remove();
 }
 
 fn bind(client: *wl.Client, wm: *WindowManager, version: u32, id: u32) void {
@@ -156,8 +154,23 @@ fn handleRequest(
         .destroy => {
             // XXX send protocol error
         },
-        .ack_update => |_| {},
-        .commit => {},
+        .ack_update => |args| {
+            switch (wm.state) {
+                .update_sent => |serial| {
+                    if (args.serial == serial) {
+                        wm.state = .update_acked;
+                    }
+                },
+                .idle, .update_acked, .inflight_configures => {},
+            }
+        },
+        .commit => {
+            wm.committed.dirty = true;
+            switch (wm.state) {
+                .idle, .update_acked => wm.sendConfigures(),
+                .update_sent, .inflight_configures => {},
+            }
+        },
         .get_seat => |_| {},
         .get_shell_surface => |_| {},
     }
@@ -184,12 +197,13 @@ fn handleDirty(wm: *WindowManager) void {
                 wm.sendUpdate();
             }
         },
-        .update_sent, .update_acked, .configures_inflight => {},
+        .update_sent, .update_acked, .inflight_configures => {},
     }
 }
 
 fn sendUpdate(wm: *WindowManager) void {
     assert(wm.state == .idle);
+    assert(wm.pending.dirty);
 
     const wm_v1 = wm.object orelse return;
 
@@ -210,10 +224,21 @@ fn sendUpdate(wm: *WindowManager) void {
     const serial = server.wl_server.nextSerial();
     wm_v1.sendUpdate(serial);
     wm.state = .{ .update_sent = serial };
+
+    wm.startTimeoutTimer();
 }
 
 fn sendConfigures(wm: *WindowManager) void {
-    assert(wm.inflight_configures == 0);
+    switch (wm.state) {
+        .idle, .update_acked => {},
+        .update_sent, .inflight_configures => unreachable,
+    }
+    assert(wm.committed.dirty);
+
+    // XXX apply committed to inflight
+    wm.committed.dirty = false;
+
+    wm.state = .{ .inflight_configures = 0 };
 
     {
         var it = wm.inflight.render_list.iterator(.forward);
@@ -226,7 +251,7 @@ fn sendConfigures(wm: *WindowManager) void {
             if (!window.mapped) continue;
 
             if (window.configure()) {
-                wm.inflight_configures += 1;
+                wm.state.inflight_configures += 1;
 
                 window.saveSurfaceTree();
                 window.sendFrameDone();
@@ -234,34 +259,42 @@ fn sendConfigures(wm: *WindowManager) void {
         }
     }
 
-    if (wm.inflight_configures > 0) {
-        std.log.scoped(.transaction).debug("started transaction with {} pending configure(s)", .{
-            wm.inflight_configures,
-        });
+    log.debug("started transaction with {} configure(s)", .{wm.state.inflight_configures});
 
-        wm.transaction_timeout.timerUpdate(100) catch {
-            std.log.scoped(.transaction).err("failed to update timer", .{});
-            wm.commitTransaction();
-        };
+    if (wm.state.inflight_configures > 0) {
+        wm.startTimeoutTimer();
     } else {
         wm.commitTransaction();
     }
 }
 
-fn handleTransactionTimeout(wm: *WindowManager) c_int {
-    std.log.scoped(.transaction).err("timeout occurred, some imperfect frames may be shown", .{});
+fn startTimeoutTimer(wm: *WindowManager) void {
+    wm.timeout.timerUpdate(100) catch {
+        log.err("failed to start timer", .{});
+        _ = wm.handleTimeout();
+    };
+}
+
+fn handleTimeout(wm: *WindowManager) c_int {
+    log.err("timeout occurred, some imperfect frames may be shown", .{});
 
-    wm.inflight_configures = 0;
-    wm.commitTransaction();
+    switch (wm.state) {
+        .idle => unreachable,
+        .update_sent, .update_acked => wm.state = .idle,
+        .inflight_configures => {
+            wm.state.inflight_configures = 0;
+            wm.commitTransaction();
+        },
+    }
 
     return 0;
 }
 
 pub fn notifyConfigured(wm: *WindowManager) void {
-    wm.inflight_configures -= 1;
-    if (wm.inflight_configures == 0) {
+    wm.state.inflight_configures -= 1;
+    if (wm.state.inflight_configures == 0) {
         // Disarm the timer, as we didn't timeout
-        wm.transaction_timeout.timerUpdate(0) catch std.log.scoped(.transaction).err("error disarming timer", .{});
+        wm.timeout.timerUpdate(0) catch log.err("error disarming timer", .{});
         wm.commitTransaction();
     }
 }
@@ -271,9 +304,10 @@ pub fn notifyConfigured(wm: *WindowManager) void {
 /// layout. Should only be called after all clients have configured for
 /// the new layout. If called early imperfect frames may be drawn.
 fn commitTransaction(wm: *WindowManager) void {
-    assert(wm.inflight_configures == 0);
+    assert(wm.state.inflight_configures == 0);
+    wm.state = .idle;
 
-    std.log.scoped(.transaction).debug("commiting transaction", .{});
+    log.debug("commiting transaction", .{});
 
     {
         var it = wm.inflight.render_list.iterator(.forward);
@@ -301,7 +335,9 @@ fn commitTransaction(wm: *WindowManager) void {
 
     server.idle_inhibit_manager.checkActive();
 
-    if (wm.pending.dirty) {
+    if (wm.committed.dirty) {
+        wm.sendConfigures();
+    } else if (wm.pending.dirty) {
         wm.dirtyPending();
     }
 }