From cb2b60a3115e6f6b60e8ba0f4f85d096b278dfe3 Mon Sep 17 00:00:00 2001 From: Dan O'Donovan Date: Tue, 16 Dec 2025 12:38:01 +0000 Subject: [PATCH 1/4] build: zig 0.15 --- .zigversion | 2 +- build.tst | 21 +++++++++++++++------ build.zig | 12 ++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.zigversion b/.zigversion index c64601b..2856407 100644 --- a/.zigversion +++ b/.zigversion @@ -1 +1 @@ -0.14 \ No newline at end of file +0.15 diff --git a/build.tst b/build.tst index c777934..7e17947 100644 --- a/build.tst +++ b/build.tst @@ -13,12 +13,13 @@ pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "DOOM-fire", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), }); - - exe.linkLibC(); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); @@ -30,7 +31,15 @@ pub fn build(b: *std.Build) void { const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); - const exe_tests = b.addTest(.{ .name = "exe_tests", .root_source_file = b.path("src/main.zig"), .target = target }); + const exe_tests = b.addTest(.{ + .name = "exe_tests", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&exe_tests.step); diff --git a/build.zig b/build.zig index 34a4771..bf587b7 100644 --- a/build.zig +++ b/build.zig @@ -17,14 +17,14 @@ pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = "DOOM-fire", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, + }), }); - //libc linking - exe.linkLibC(); - // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default // step when running `zig build`). From a5599174e234ad8d00e2ef01d3c14e736d20fa57 Mon Sep 17 00:00:00 2001 From: Dan O'Donovan Date: Tue, 16 Dec 2025 12:42:25 +0000 Subject: [PATCH 2/4] fix: zig-0.15 API updates --- src/main.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main.zig b/src/main.zig index cceca94..8d1d87f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -8,8 +8,8 @@ const std = @import("std"); const allocator = std.heap.page_allocator; -var stdout: std.fs.File.Writer = undefined; -var stdin: std.fs.File.Reader = undefined; +var stdout: std.fs.File = undefined; +var stdin: std.fs.File = undefined; var g_tty_win: win32.HANDLE = undefined; /////////////////////////////////// @@ -576,11 +576,11 @@ pub fn scrollMarquee() !void { try emit(line_clear_to_eol); try emit(nl); - std.time.sleep(10 * std.time.ns_per_ms); + std.Thread.sleep(10 * std.time.ns_per_ms); } //let quote chill for a second - std.time.sleep(1000 * std.time.ns_per_ms); + std.Thread.sleep(1000 * std.time.ns_per_ms); //fade out fade_idx = fade_len - 1; @@ -598,7 +598,7 @@ pub fn scrollMarquee() !void { try emit(txt[txt_idx * 2 + 1]); try emit(line_clear_to_eol); try emit(nl); - std.time.sleep(10 * std.time.ns_per_ms); + std.Thread.sleep(10 * std.time.ns_per_ms); } try emit(nl); } @@ -835,8 +835,8 @@ pub fn showDoomFire() !void { /////////////////////////////////// pub fn main() anyerror!void { - stdout = std.io.getStdOut().writer(); - stdin = std.io.getStdIn().reader(); + stdout = std.fs.File.stdout(); + stdin = std.fs.File.stdin(); try initTerm(); defer complete() catch {}; From 267d567a7bafa12f3f7d8fb1618073fe21c9f5aa Mon Sep 17 00:00:00 2001 From: Dan O'Donovan Date: Tue, 16 Dec 2025 12:43:28 +0000 Subject: [PATCH 3/4] fix: buffer safety --- src/main.zig | 97 ++++++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/src/main.zig b/src/main.zig index 8d1d87f..da1f553 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,6 +11,7 @@ const allocator = std.heap.page_allocator; var stdout: std.fs.File = undefined; var stdin: std.fs.File = undefined; var g_tty_win: win32.HANDLE = undefined; +var stop_requested = std.atomic.Value(bool).init(false); /////////////////////////////////// // Tested on M1 osx15.3, Windows 10 on Intel + Artix Linux. @@ -117,11 +118,7 @@ pub fn emit(s: []const u8) !void { } return; } else { - const sz = try stdout.write(s); - if (sz == 0) { - return; - } // cauze I c - return; + try stdout.writeAll(s); } } @@ -144,6 +141,7 @@ const TIOCGWINSZ = std.c.T.IOCGWINSZ; // ioctl flag //term size const TermSz = struct { height: usize, width: usize }; var term_sz: TermSz = .{ .height = 0, .width = 0 }; // set via initTermSz +const sigint = std.posix.SIG.INT; //ansi escape codes const esc = "\x1B"; @@ -231,7 +229,7 @@ pub fn getTermSzLinux() !TermSz { //Linux-MacOS Case //base case - invoked from cmd line - const tty_nix = stdout.context.handle; + const tty_nix = stdout.handle; var winsz = std.c.winsize{ .col = 0, .row = 0, .xpixel = 0, .ypixel = 0 }; const rv = std.c.ioctl(tty_nix, TIOCGWINSZ, @intFromPtr(&winsz)); const err = std.posix.errno(rv); @@ -309,6 +307,8 @@ pub fn initTerm() !void { if (builtin.os.tag == .windows) { try initTermWin(); + } else { + try initSignalHandling(); } try initTermSize(); @@ -338,10 +338,11 @@ pub fn pause() !void { try emit(color_reset); try emit("Press return to continue..."); - var b: u8 = undefined; - b = stdin.readByte() catch undefined; + var buf: [1]u8 = undefined; + const read_sz = stdin.read(&buf) catch 0; + if (read_sz == 0) return; - if (b == 'q') { + if (buf[0] == 'q') { //exit cleanly try complete(); std.process.exit(0); @@ -625,27 +626,26 @@ const px = "▀"; //bs = buffer string var bs: []u8 = undefined; -var bs_idx: u64 = 0; -var bs_len: u64 = 0; -var bs_sz_min: u64 = 0; -var bs_sz_max: u64 = 0; -var bs_sz_avg: u64 = 0; -var bs_frame_tic: u64 = 0; +var bs_idx: usize = 0; +var bs_len: usize = 0; +var bs_sz_min: usize = 0; +var bs_sz_max: usize = 0; +var bs_sz_avg: usize = 0; +var bs_frame_tic: usize = 0; var t_start: i64 = 0; var t_now: i64 = 0; var t_dur: f64 = 0.0; var fps: f64 = 0.0; -pub fn initBuf() !void { - //some lazy guesswork to make sure we have enough of a buffer to render DOOM fire. - const px_char_sz = px.len; - const px_color_sz = bg[LAST_COLOR].len + fg[LAST_COLOR].len; - const px_sz = px_color_sz + px_char_sz; - const screen_sz: u64 = @as(u64, px_sz * term_sz.width * term_sz.width); - const overflow_sz: u64 = px_char_sz * 100; - const bs_sz: u64 = screen_sz + overflow_sz; +pub fn initBuf(fire_w: usize, fire_h: usize) !void { + //Allocate generously to avoid buffer overrun when painting frames. + //Worst case: every pixel writes bg + fg + px (no reuse), plus line breaks and reset sequences. + const max_bytes_per_px: usize = 128; // intentionally generous to avoid overruns + const pixel_count = try std.math.mul(usize, fire_w, fire_h); + const base_sz = try std.math.mul(usize, pixel_count, max_bytes_per_px); + const overhead = (fire_w + fire_h) * 64 + 16384; // cursor moves, reset, slop - bs = try allocator.alloc(u8, bs_sz * 2); + bs = try allocator.alloc(u8, try std.math.add(usize, base_sz, overhead)); t_start = std.time.milliTimestamp(); resetBuf(); } @@ -658,6 +658,9 @@ pub fn resetBuf() void { //copy input string to buffer string pub fn drawBuf(s: []const u8) void { + if (bs_idx + s.len > bs.len) { + return; // prevent overflow; frame will be partial but safe + } for (s) |b| { bs[bs_idx] = b; bs_idx += 1; @@ -667,7 +670,8 @@ pub fn drawBuf(s: []const u8) void { //print buffer to string...can be a decent amount of text! pub fn paintBuf() !void { - try emit(bs[0 .. bs_len - 1]); + if (bs_len == 0) return; + try emit(bs[0 .. bs_len]); t_now = std.time.milliTimestamp(); bs_frame_tic += 1; if (bs_sz_min == 0) { @@ -689,7 +693,7 @@ pub fn paintBuf() !void { fps = @as(f64, @floatFromInt(bs_frame_tic)) / t_dur; try emit(fg[0]); - try emitFmt("mem: {s:.2} min / {s:.2} avg / {s:.2} max [ {d:.2} fps ]", .{ std.fmt.fmtIntSizeBin(bs_sz_min), std.fmt.fmtIntSizeBin(bs_sz_avg), std.fmt.fmtIntSizeBin(bs_sz_max), fps }); + try emitFmt("mem: {d} min / {d} avg / {d} max [ {d:.2} fps ]", .{ bs_sz_min, bs_sz_avg, bs_sz_max, fps }); } // initBuf(); defer freeBuf(); @@ -710,21 +714,24 @@ pub fn showDoomFire() !void { const fire_white: u8 = fire_palette.len - 1; //screen buf default color is black - var screen_buf: []u8 = undefined; //{fire_black}**FIRE_SZ; - screen_buf = try allocator.alloc(u8, FIRE_SZ); - defer allocator.free(screen_buf); + var screen_buf: []u8 = undefined; - //init buffer - var buf_idx: u64 = 0; - while (buf_idx < FIRE_SZ) : (buf_idx += 1) { - screen_buf[buf_idx] = fire_black; - } + const initScreenBuf = struct { + pub fn fill(buf: []u8, fire_w: u64, fire_last_row: u64, white: u8, black: u8) void { + var i: u64 = 0; + while (i < buf.len) : (i += 1) { + buf[i] = black; + } + i = 0; + while (i < fire_w) : (i += 1) { + buf[fire_last_row + i] = white; + } + } + }.fill; - //last row is white...white is "fire source" - buf_idx = 0; - while (buf_idx < FIRE_W) : (buf_idx += 1) { - screen_buf[FIRE_LAST_ROW + buf_idx] = fire_white; - } + screen_buf = try allocator.alloc(u8, FIRE_SZ); + defer allocator.free(screen_buf); + initScreenBuf(screen_buf, FIRE_W, FIRE_LAST_ROW, fire_white, fire_black); //reset terminal try emit(cursor_home); @@ -756,13 +763,15 @@ pub fn showDoomFire() !void { var px_prev_lo = px_lo; //get to work! - try initBuf(); + try initBuf(@intCast(FIRE_W), @intCast(FIRE_H)); defer freeBuf(); - //when there is an ez way to poll for key stroke...do that. for now, ctrl+c! - const ok = true; - while (ok) { - + while (!stop_requested.load(.acquire)) { + if (screen_buf.len != FIRE_SZ) { + allocator.free(screen_buf); + screen_buf = try allocator.alloc(u8, FIRE_SZ); + initScreenBuf(screen_buf, FIRE_W, FIRE_LAST_ROW, fire_white, fire_black); + } //update fire buf doFire_x = 0; while (doFire_x < FIRE_W) : (doFire_x = doFire_x + 1) { From 12af71a6dda4257edfe47317a4124fee68680c6f Mon Sep 17 00:00:00 2001 From: Dan O'Donovan Date: Tue, 16 Dec 2025 12:43:49 +0000 Subject: [PATCH 4/4] fix: ctrl-c handling to restore TTY --- src/main.zig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main.zig b/src/main.zig index da1f553..b7069b7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -960,3 +960,17 @@ const win32 = struct { // //If not, see https://www.gnu.org/licenses/. //--------- +// basic Ctrl+C handling on POSIX; set a flag and let main loop exit gracefully +fn handleSigInt(sig: c_int) callconv(.c) void { + _ = sig; + stop_requested.store(true, .release); +} + +fn initSignalHandling() !void { + var act = std.posix.Sigaction{ + .handler = .{ .handler = handleSigInt }, + .mask = std.posix.sigemptyset(), + .flags = 0, + }; + std.posix.sigaction(sigint, &act, null); +}