a73x

b1c9ad85

main: thread optional ScenarioState through runTerminal

a73x   2026-04-19 13:26

Commit message
main: thread optional ScenarioState through runTerminal

Signature change only — tick_ctx parameter is unused this commit
beyond the loopShouldRun guard. Loop guard relaxed via loopShouldRun
helper so scenario mode can exit on state.isDone().

Existing entry points pass null — no behavior change for the
normal non-scenario path.

Stub src/scenario_runtime.zig added so the import resolves;
Task 3 replaces it with real TickIO callbacks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

build.zig
Old New
@@ -391,6 +391,17 @@ pub fn build(b: *std.Build) void {
391 scenario_mod.addImport("png", png_mod); 391 scenario_mod.addImport("png", png_mod);
392 scenario_mod.addImport("imgdiff", imgdiff_lib_mod); 392 scenario_mod.addImport("imgdiff", imgdiff_lib_mod);
393 393
394 // scenario_runtime stub module (Task 3 populates)
395 const scenario_runtime_mod = b.createModule(.{
396 .root_source_file = b.path("src/scenario_runtime.zig"),
397 .target = target,
398 .optimize = optimize,
399 });
400 scenario_runtime_mod.addImport("scenario", scenario_mod);
401
402 exe_mod.addImport("scenario", scenario_mod);
403 exe_mod.addImport("scenario_runtime", scenario_runtime_mod);
404
394 const scenario_test_mod = b.createModule(.{ 405 const scenario_test_mod = b.createModule(.{
395 .root_source_file = b.path("src/scenario.zig"), 406 .root_source_file = b.path("src/scenario.zig"),
396 .target = target, 407 .target = target,
src/main.zig
Old New
@@ -1,6 +1,8 @@
1 const std = @import("std"); 1 const std = @import("std");
2 const vt = @import("vt"); 2 const vt = @import("vt");
3 const pty = @import("pty"); 3 const pty = @import("pty");
4 const scenario = @import("scenario");
5 const scenario_runtime = @import("scenario_runtime");
4 const wayland_client = @import("wayland-client"); 6 const wayland_client = @import("wayland-client");
5 const frame_loop_mod = @import("frame_loop"); 7 const frame_loop_mod = @import("frame_loop");
6 const renderer = @import("renderer"); 8 const renderer = @import("renderer");
@@ -126,6 +128,16 @@ fn writeBenchJson(alloc: std.mem.Allocator, stats: FrameTimingStats, workload: ?
126 try out.writeAll(json_bytes); 128 try out.writeAll(json_bytes);
127 } 129 }
128 130
131 fn loopShouldRun(p: *pty.Pty, tick_ctx: ?*scenario_runtime.RunContext) bool {
132 if (tick_ctx) |rc| {
133 // Scenario mode — check state inside RunContext. Task 3 populates this field.
134 if (rc.state) |s| {
135 if (s.isDone()) return false;
136 }
137 }
138 return p.isChildAlive();
139 }
140
129 pub fn main() !void { 141 pub fn main() !void {
130 var gpa: std.heap.DebugAllocator(.{}) = .init; 142 var gpa: std.heap.DebugAllocator(.{}) = .init;
131 defer _ = gpa.deinit(); 143 defer _ = gpa.deinit();
@@ -167,10 +179,10 @@ pub fn main() !void {
167 return capture.run(alloc, args[1..]); 179 return capture.run(alloc, args[1..]);
168 } 180 }
169 181
170 return runTerminal(alloc); 182 return runTerminal(alloc, null);
171 } 183 }
172 184
173 fn runTerminal(alloc: std.mem.Allocator) !void { 185 fn runTerminal(alloc: std.mem.Allocator, tick_ctx: ?*scenario_runtime.RunContext) !void {
174 // === font first, to know cell size === 186 // === font first, to know cell size ===
175 var font_lookup = try font.lookupConfiguredFont(alloc); 187 var font_lookup = try font.lookupConfiguredFont(alloc);
176 defer font_lookup.deinit(alloc); 188 defer font_lookup.deinit(alloc);
@@ -341,7 +353,7 @@ fn runTerminal(alloc: std.mem.Allocator) !void {
341 var blink_state: BlinkState = .{}; 353 var blink_state: BlinkState = .{};
342 var previous_has_focus: bool = keyboard.has_focus; 354 var previous_has_focus: bool = keyboard.has_focus;
343 355
344 while (!window.should_close and p.isChildAlive()) { 356 while (!window.should_close and loopShouldRun(&p, tick_ctx)) {
345 const repeat_timeout_ms = remainingRepeatTimeoutMs(keyboard.nextRepeatDeadlineNs()); 357 const repeat_timeout_ms = remainingRepeatTimeoutMs(keyboard.nextRepeatDeadlineNs());
346 const blink_timeout_ms = remainingRepeatTimeoutMs(blink_state.next_deadline_ns); 358 const blink_timeout_ms = remainingRepeatTimeoutMs(blink_state.next_deadline_ns);
347 const timeout = computePollTimeoutMs(repeat_timeout_ms, blink_timeout_ms, render_pending); 359 const timeout = computePollTimeoutMs(repeat_timeout_ms, blink_timeout_ms, render_pending);
@@ -3527,5 +3539,5 @@ fn runHiddenFreezeRegression(alloc: std.mem.Allocator) !void {
3527 var buf: [1]u8 = undefined; 3539 var buf: [1]u8 = undefined;
3528 _ = try stdin_file.read(&buf); 3540 _ = try stdin_file.read(&buf);
3529 3541
3530 return runTerminal(alloc); 3542 return runTerminal(alloc, null);
3531 } 3543 }
src/scenario_runtime.zig
Old New
@@ -0,0 +1,5 @@
1 // src/scenario_runtime.zig (STUB — Task 3 replaces)
2 const scenario = @import("scenario");
3 pub const RunContext = struct {
4 state: ?*scenario.ScenarioState = null,
5 };