const std = @import("std"); const Io = std.Io; const Msg = @import("root.zig").Msg; const Allocator = std.mem.Allocator; const Cmd = @This(); ctx: ?*anyopaque = null, run_fn: *const fn (allocator: Allocator, io: Io, ctx: ?*anyopaque) ?Msg, free_ctx: ?*const fn (allocator: Allocator, ctx: *anyopaque) void = null, pub fn run(self: Cmd, allocator: Allocator, io: Io) ?Msg { const result = self.run_fn(allocator, io, self.ctx); if (self.free_ctx) |free| if (self.ctx) |c| free(allocator, c); return result; } pub fn pure(comptime msg: Msg) Cmd { const Inner = struct { fn run(_: ?*anyopaque, _: Allocator, _: Io) ?Msg { return msg; } }; return .{ .run_fn = Inner.run }; } pub const quit: Cmd = .{ .run_fn = quitFn }; fn quitFn(_: Allocator, _: Io, _: ?*anyopaque) ?Msg { return .quit; } const TickCtx = struct { duration_ns: u64, msg: Msg, }; /// Returns a Cmd that sleeps for `duration_ns` and then emits `msg`. The /// caller's allocator (typically `model.gpa`) backs the ctx — the Cmd's /// `free_ctx` releases it after run completes. /// /// Sleep is performed via `Io.sleep`, so a shutdown that wakes the io /// can cancel it. pub fn tick(allocator: Allocator, duration_ns: u64, msg: Msg) Allocator.Error!Cmd { const ctx = try allocator.create(TickCtx); ctx.* = .{ .duration_ns = duration_ns, .msg = msg }; return .{ .run_fn = tickRun, .ctx = ctx, .free_ctx = tickFree, }; } fn tickRun(_: Allocator, io: Io, ctx: ?*anyopaque) ?Msg { const tc: *TickCtx = @ptrCast(@alignCast(ctx.?)); Io.sleep(io, .fromNanoseconds(@intCast(tc.duration_ns)), .awake) catch return null; return tc.msg; } fn tickFree(allocator: Allocator, ctx: *anyopaque) void { const tc: *TickCtx = @ptrCast(@alignCast(ctx)); allocator.destroy(tc); } const BatchCtx = struct { cmds: []Cmd, }; /// Returns a Cmd that, when run, dispatches every Cmd in `cmds` through /// the program's cmd queue. The slice is duped with `allocator`; the /// Program loop frees it after it pushes the children. pub fn batch(allocator: Allocator, cmds: []const Cmd) Allocator.Error!Cmd { const dup = try allocator.dupe(Cmd, cmds); const ctx = try allocator.create(BatchCtx); ctx.* = .{ .cmds = dup }; return .{ .run_fn = batchRun, .ctx = ctx, .free_ctx = batchFreeCtx, }; } fn batchRun(_: Allocator, _: Io, ctx: ?*anyopaque) ?Msg { const bc: *BatchCtx = @ptrCast(@alignCast(ctx.?)); return .{ .batch = bc.cmds }; } fn batchFreeCtx(allocator: Allocator, ctx: *anyopaque) void { // Only free the struct; the cmds slice is owned by the produced Msg // and is freed by `Program.run` after the loop pushes each child Cmd. const bc: *BatchCtx = @ptrCast(@alignCast(ctx)); allocator.destroy(bc); }