a73x

b0070dd0

Harden FrameLoop per code-quality review

a73x   2026-04-16 11:03

- commitRender: convert canRender precondition from std.debug.assert
  (compiled out in ReleaseFast) to a runtime error.CannotRender, so
  caller logic bugs are observable in release builds.
- waitForWork: propagate only .revents back to the caller's pollfd
  slice; .events and .fd are caller-owned and should be preserved.
- waitForWork doc: make the 8-extra cap rationale explicit on the
  public API.

Follow-up to dec56ed.

diff --git a/src/frame_loop.zig b/src/frame_loop.zig
index d3e84c3..640872b 100644
--- a/src/frame_loop.zig
+++ b/src/frame_loop.zig
@@ -64,7 +64,7 @@ pub const FrameLoop = struct {
    }

    pub fn commitRender(self: *FrameLoop) !void {
        std.debug.assert(self.canRender());
        if (!self.canRender()) return error.CannotRender;
        if (self.pending_token) |t| self.ops.destroyCallbackFn(self.ops.ctx, t);
        self.pending_token = try self.ops.requestFrameFn(self.ops.ctx, self);
        self.armed = false;
@@ -95,6 +95,11 @@ pub const FrameLoop = struct {

    /// Blocks on wl_fd + extra pollfds with `timeout_ms`, then reads + dispatches
    /// any pending Wayland events. Safe to call in any state.
    ///
    /// `extra.len` must not exceed 8. waystty's production callers never poll
    /// more than wl_fd + one extra fd (pty); the cap of 8 is a 4x margin and
    /// keeps the internal pollfd array stack-allocated. Increase requires a
    /// review of the stack frame size in this function.
    pub fn waitForWork(
        self: *FrameLoop,
        extra: []std.posix.pollfd,
@@ -113,8 +118,9 @@ pub const FrameLoop = struct {

        _ = std.posix.poll(all[0..total], timeout_ms) catch {};

        // Propagate revents back into caller's extra slice.
        for (extra, 0..) |*fd, i| fd.* = all[i + 1];
        // Propagate only revents back into caller's extra slice; events/fd are
        // caller-owned and must be preserved across calls.
        for (extra, 0..) |*fd, i| fd.revents = all[i + 1].revents;

        if (all[0].revents & std.posix.POLL.IN != 0) {
            if (self.ops.prepareReadFn(self.ops.ctx)) {