summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/gui.zig35
1 files changed, 34 insertions, 1 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 6bde8b60..8d9c29ab 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -4273,7 +4273,13 @@ fn waitInput(ctx: ?*anyopaque, timeout_ms: u32) void {
if (core.continuous()) {
const display = virtual.* + std.time.ns_per_s / s.test_hz;
virtual.* = if (core.nextWake()) |due| @max(virtual.*, @min(due, display)) else display;
- } else if (!got and timeout_ms != 0) virtual.* = @max(virtual.*, core.nextWake() orelse virtual.*);
+ } else if (timeout_ms != 0 and (!got or !core.needs_frame)) {
+ // A wait that ran out, or one a stray event cut short while
+ // nothing owes a frame: time moves to the core's wake all
+ // the same, so a steady trickle of events (a pty's, the
+ // test feed's) cannot hold a virtual clock still forever.
+ virtual.* = @max(virtual.*, core.nextWake() orelse virtual.*);
+ }
}
if (got) {
// A run of queued motions is one move to its last position (the
@@ -4348,6 +4354,7 @@ fn pollFrame(ctx: ?*anyopaque) void {
if (updateCoreResize(core, geom.cols, geom.rows, g.cell_w, g.cell_h, g.tagline_width, g.tagline_height)) resetScroll(g);
stepScroll(g, core, s.gpa);
g.post.sync(s.gpa, s.io, g.device, g.swapchain_format, core);
+ if (s.test_mode) writeTestStatus(g, core, s.presented);
// Between the core's frames, a chain that moves on its own redraws
// alone, once a refresh of the display the window is on (a millisecond
// early is on time: the wait that paces it is in whole milliseconds).
@@ -8235,6 +8242,32 @@ fn captureFrame(g: *Gui, gpa: std.mem.Allocator, cmd: *c.SDL_GPUCommandBuffer, t
g.captured +%= 1;
}
+/// A test window's state for a harness (test/gui_golden.py), in the
+/// capture directory's `status`: "<loop step> <frames captured> <idle>".
+/// Idle is 1 once the frame last captured is the one that stays: nothing
+/// owes a frame, nothing moves, nothing waits on a clock. A harness that
+/// sees a step past the one it acted on, idle, has its frame, however slow
+/// the machine is.
+var test_status_step: u64 = 0;
+fn writeTestStatus(g: *const Gui, core: *const pardes.Pardes, presented: bool) void {
+ test_status_step +%= 1;
+ if (g.capture_dir.len == 0) return;
+ const idle = presented and !core.needs_frame and !core.continuous() and core.nextWake() == null and
+ !g.glide.moving() and g.scroll_lag == 0 and g.scroll_delta == 0 and g.scroll_notch == 0;
+ var line_buf: [64]u8 = undefined;
+ // What keeps it busy, after: owes a frame, moves, waits, not shown.
+ const line = std.fmt.bufPrint(&line_buf, "{d} {d} {d} {d}{d}{d}{d}\n", .{ test_status_step, g.captured, @intFromBool(idle), @intFromBool(core.needs_frame), @intFromBool(core.continuous()), @intFromBool(core.nextWake() != null), @intFromBool(!presented) }) catch return;
+ var tmp_buf: [4096]u8 = undefined;
+ var final_buf: [4096]u8 = undefined;
+ const tmp_path = std.fmt.bufPrintSentinel(&tmp_buf, "{s}/status.tmp", .{g.capture_dir}, 0) catch return;
+ const final_path = std.fmt.bufPrintSentinel(&final_buf, "{s}/status", .{g.capture_dir}, 0) catch return;
+ const fd = libc.open(tmp_path, .{ .ACCMODE = .WRONLY, .CREAT = true, .TRUNC = true }, @as(libc.mode_t, 0o644));
+ if (fd < 0) return;
+ _ = libc.write(fd, line.ptr, line.len);
+ _ = libc.close(fd);
+ _ = libc.rename(tmp_path, final_path);
+}
+
fn writeCapturePpm(g: *Gui, gpa: std.mem.Allocator, pixels: []const u8, width: u32, height: u32, name: []const u8) !void {
// A latency trace times frames, and an 8 MB file write per frame would
// stall the loop it is timing.