diff --git a/packages/core/scripts/build.ts b/packages/core/scripts/build.ts index 6500e69bf..5acf81743 100644 --- a/packages/core/scripts/build.ts +++ b/packages/core/scripts/build.ts @@ -78,11 +78,13 @@ if (missingRequired.length > 0) { process.exit(1) } +const zigExe = process.env.ZIG ?? "zig" + if (buildNative) { console.log(`Building native ${isDev ? "dev" : "prod"} binaries...`) const zigBuild: SpawnSyncReturns = spawnSync( - "zig", + zigExe, ["build", `-Doptimize=${isDev ? "Debug" : "ReleaseFast"}`], { cwd: join(rootDir, "src", "zig"), diff --git a/packages/core/src/zig/ansi.zig b/packages/core/src/zig/ansi.zig index d8db3bc7a..21dcb37b2 100644 --- a/packages/core/src/zig/ansi.zig +++ b/packages/core/src/zig/ansi.zig @@ -19,15 +19,15 @@ pub const ANSI = struct { // Direct writing to any writer - the most efficient option pub fn moveToOutput(writer: anytype, x: u32, y: u32) AnsiError!void { - std.fmt.format(writer, "\x1b[{d};{d}H", .{ y, x }) catch return AnsiError.WriteFailed; + writer.print("\x1b[{d};{d}H", .{ y, x }) catch return AnsiError.WriteFailed; } pub fn fgColorOutput(writer: anytype, r: u8, g: u8, b: u8) AnsiError!void { - std.fmt.format(writer, "\x1b[38;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed; + writer.print("\x1b[38;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed; } pub fn bgColorOutput(writer: anytype, r: u8, g: u8, b: u8) AnsiError!void { - std.fmt.format(writer, "\x1b[48;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed; + writer.print("\x1b[48;2;{d};{d};{d}m", .{ r, g, b }) catch return AnsiError.WriteFailed; } // Text attribute constants @@ -49,7 +49,7 @@ pub const ANSI = struct { pub const cursorUnderlineBlink = "\x1b[3 q"; pub fn cursorColorOutputWriter(writer: anytype, r: u8, g: u8, b: u8) AnsiError!void { - std.fmt.format(writer, "\x1b]12;#{x:0>2}{x:0>2}{x:0>2}\x07", .{ r, g, b }) catch return AnsiError.WriteFailed; + writer.print("\x1b]12;#{x:0>2}{x:0>2}{x:0>2}\x07", .{ r, g, b }) catch return AnsiError.WriteFailed; } pub const resetCursorColor = "\x1b]12;default\x07"; @@ -73,7 +73,7 @@ pub const ANSI = struct { // This approach is more compatible across different terminals var i: u32 = height; while (i > 0) : (i -= 1) { - std.fmt.format(writer, "\x1b[{d};1H\x1b[2K", .{i}) catch return AnsiError.WriteFailed; + writer.print("\x1b[{d};1H\x1b[2K", .{i}) catch return AnsiError.WriteFailed; } } }; diff --git a/packages/core/src/zig/buffer.zig b/packages/core/src/zig/buffer.zig index 453d21337..da893d1f1 100644 --- a/packages/core/src/zig/buffer.zig +++ b/packages/core/src/zig/buffer.zig @@ -367,7 +367,7 @@ pub const OptimizedBuffer = struct { while (utf8_it.nextCodepoint()) |codepoint| : (i += 1) { const charX = x + i; if (charX >= self.width) break; - + // Use provided background or get existing background var bgColor: RGBA = undefined; if (bg) |b| { diff --git a/packages/core/src/zig/build.zig b/packages/core/src/zig/build.zig index 4a46ab217..97a42e735 100644 --- a/packages/core/src/zig/build.zig +++ b/packages/core/src/zig/build.zig @@ -8,9 +8,7 @@ const SupportedZigVersion = struct { }; const SUPPORTED_ZIG_VERSIONS = [_]SupportedZigVersion{ - .{ .major = 0, .minor = 14, .patch = 0 }, - .{ .major = 0, .minor = 14, .patch = 1 }, - // .{ .major = 0, .minor = 15, .patch = 0 }, + .{ .major = 0, .minor = 15, .patch = 1 }, }; const SupportedTarget = struct { diff --git a/packages/core/src/zig/renderer.zig b/packages/core/src/zig/renderer.zig index d8f0fd487..e6501cf81 100644 --- a/packages/core/src/zig/renderer.zig +++ b/packages/core/src/zig/renderer.zig @@ -77,18 +77,18 @@ pub const CliRenderer = struct { frameCallbackTime: ?f64, }, statSamples: struct { - lastFrameTime: std.ArrayList(f64), - renderTime: std.ArrayList(f64), - overallFrameTime: std.ArrayList(f64), - bufferResetTime: std.ArrayList(f64), - stdoutWriteTime: std.ArrayList(f64), - cellsUpdated: std.ArrayList(u32), - frameCallbackTime: std.ArrayList(f64), + lastFrameTime: std.ArrayListUnmanaged(f64), + renderTime: std.ArrayListUnmanaged(f64), + overallFrameTime: std.ArrayListUnmanaged(f64), + bufferResetTime: std.ArrayListUnmanaged(f64), + stdoutWriteTime: std.ArrayListUnmanaged(f64), + cellsUpdated: std.ArrayListUnmanaged(u32), + frameCallbackTime: std.ArrayListUnmanaged(f64), }, lastRenderTime: i64, allocator: Allocator, renderThread: ?std.Thread = null, - stdoutWriter: std.io.BufferedWriter(4096, std.fs.File.Writer), + stdoutBuffer: [4096]u8, debugOverlay: struct { enabled: bool, corner: DebugOverlayCorner, @@ -137,7 +137,10 @@ pub const CliRenderer = struct { return data.len; } - pub fn writer() std.io.Writer(void, error{BufferFull}, write) { + // TODO: std.io.GenericWriter is deprecated, however the "correct" option seems to be much more involved + // So I have simply used GenericWriter here, and then the proper migration can be done later + // As GenericWriter still exists and works here in 0.15.1 + pub fn writer() std.io.GenericWriter(void, error{BufferFull}, write) { return .{ .context = {} }; } }; @@ -178,25 +181,22 @@ pub const CliRenderer = struct { const currentBuffer = try OptimizedBuffer.init(allocator, width, height, .{}); const nextBuffer = try OptimizedBuffer.init(allocator, width, height, .{}); - const stdout = std.io.getStdOut(); - const stdoutWriter = std.io.BufferedWriter(4096, std.fs.File.Writer){ .unbuffered_writer = stdout.writer() }; - // stat sample arrays - var lastFrameTime = std.ArrayList(f64).init(allocator); - var renderTime = std.ArrayList(f64).init(allocator); - var overallFrameTime = std.ArrayList(f64).init(allocator); - var bufferResetTime = std.ArrayList(f64).init(allocator); - var stdoutWriteTime = std.ArrayList(f64).init(allocator); - var cellsUpdated = std.ArrayList(u32).init(allocator); - var frameCallbackTimes = std.ArrayList(f64).init(allocator); - - try lastFrameTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try renderTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try overallFrameTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try bufferResetTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try stdoutWriteTime.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try cellsUpdated.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); - try frameCallbackTimes.ensureTotalCapacity(STAT_SAMPLE_CAPACITY); + var lastFrameTime = std.ArrayListUnmanaged(f64){}; + var renderTime = std.ArrayListUnmanaged(f64){}; + var overallFrameTime = std.ArrayListUnmanaged(f64){}; + var bufferResetTime = std.ArrayListUnmanaged(f64){}; + var stdoutWriteTime = std.ArrayListUnmanaged(f64){}; + var cellsUpdated = std.ArrayListUnmanaged(u32){}; + var frameCallbackTimes = std.ArrayListUnmanaged(f64){}; + + try lastFrameTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try renderTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try overallFrameTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try bufferResetTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try stdoutWriteTime.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try cellsUpdated.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); + try frameCallbackTimes.ensureTotalCapacity(allocator, STAT_SAMPLE_CAPACITY); const hitGridSize = width * height; const currentHitGrid = try allocator.alloc(u32, hitGridSize); @@ -238,7 +238,7 @@ pub const CliRenderer = struct { }, .lastRenderTime = std.time.microTimestamp(), .allocator = allocator, - .stdoutWriter = stdoutWriter, + .stdoutBuffer = undefined, .currentHitGrid = currentHitGrid, .nextHitGrid = nextHitGrid, .hitGridWidth = width, @@ -274,13 +274,13 @@ pub const CliRenderer = struct { self.nextRenderBuffer.deinit(); // Free stat sample arrays - self.statSamples.lastFrameTime.deinit(); - self.statSamples.renderTime.deinit(); - self.statSamples.overallFrameTime.deinit(); - self.statSamples.bufferResetTime.deinit(); - self.statSamples.stdoutWriteTime.deinit(); - self.statSamples.cellsUpdated.deinit(); - self.statSamples.frameCallbackTime.deinit(); + self.statSamples.lastFrameTime.deinit(self.allocator); + self.statSamples.renderTime.deinit(self.allocator); + self.statSamples.overallFrameTime.deinit(self.allocator); + self.statSamples.bufferResetTime.deinit(self.allocator); + self.statSamples.stdoutWriteTime.deinit(self.allocator); + self.statSamples.cellsUpdated.deinit(self.allocator); + self.statSamples.frameCallbackTime.deinit(self.allocator); self.allocator.free(self.currentHitGrid); self.allocator.free(self.nextHitGrid); @@ -289,13 +289,14 @@ pub const CliRenderer = struct { } fn performShutdownSequence(self: *CliRenderer, useAlternateScreen: bool, splitHeight: u32) void { - const direct = self.stdoutWriter.writer(); - self.disableMouse(); + var writer = std.fs.File.stdout().writer(&self.stdoutBuffer); + const direct = &writer.interface; + if (useAlternateScreen) { direct.writeAll(ansi.ANSI.switchToMainScreen) catch {}; - self.stdoutWriter.flush() catch {}; + direct.flush() catch {}; } else if (splitHeight == 0) { ansi.ANSI.clearRendererSpaceOutput(direct, self.height) catch {}; } else if (splitHeight > 0) { @@ -311,22 +312,22 @@ pub const CliRenderer = struct { direct.writeAll(ansi.ANSI.showCursor) catch {}; // Workaround for Ghostty not showing the cursor after shutdown for some reason - self.stdoutWriter.flush() catch {}; - std.time.sleep(10 * std.time.ns_per_ms); + direct.flush() catch {}; + std.Thread.sleep(10 * std.time.ns_per_ms); direct.writeAll(ansi.ANSI.showCursor) catch {}; - self.stdoutWriter.flush() catch {}; - std.time.sleep(10 * std.time.ns_per_ms); + direct.flush() catch {}; + std.Thread.sleep(10 * std.time.ns_per_ms); } - fn addStatSample(comptime T: type, samples: *std.ArrayList(T), value: T) void { - samples.append(value) catch return; + fn addStatSample(self: *CliRenderer, comptime T: type, samples: *std.ArrayListUnmanaged(T), value: T) void { + samples.append(self.allocator, value) catch return; if (samples.items.len > MAX_STAT_SAMPLES) { _ = samples.orderedRemove(0); } } - fn getStatAverage(comptime T: type, samples: *const std.ArrayList(T)) T { + fn getStatAverage(comptime T: type, samples: *const std.ArrayListUnmanaged(T)) T { if (samples.items.len == 0) { return 0; } @@ -369,8 +370,8 @@ pub const CliRenderer = struct { self.renderStats.fps = fps; self.renderStats.frameCallbackTime = frameCallbackTime; - addStatSample(f64, &self.statSamples.overallFrameTime, time); - addStatSample(f64, &self.statSamples.frameCallbackTime, frameCallbackTime); + self.addStatSample(f64, &self.statSamples.overallFrameTime, time); + self.addStatSample(f64, &self.statSamples.frameCallbackTime, frameCallbackTime); } pub fn updateMemoryStats(self: *CliRenderer, heapUsed: u32, heapTotal: u32, arrayBuffers: u32) void { @@ -440,9 +441,10 @@ pub const CliRenderer = struct { const writeStart = std.time.microTimestamp(); if (outputLen > 0) { - var bufferedWriter = &self.stdoutWriter; - bufferedWriter.writer().writeAll(outputData[0..outputLen]) catch {}; - bufferedWriter.flush() catch {}; + var writer = std.fs.File.stdout().writer(&self.stdoutBuffer); + const w = &writer.interface; + w.writeAll(outputData[0..outputLen]) catch {}; + w.flush() catch {}; } // Signal that rendering is complete @@ -486,26 +488,27 @@ pub const CliRenderer = struct { self.renderMutex.unlock(); } else { const writeStart = std.time.microTimestamp(); - var bufferedWriter = &self.stdoutWriter; - bufferedWriter.writer().writeAll(outputBuffer[0..outputBufferLen]) catch {}; - bufferedWriter.flush() catch {}; + var writer = std.fs.File.stdout().writer(&self.stdoutBuffer); + const w = &writer.interface; + w.writeAll(outputBuffer[0..outputBufferLen]) catch {}; + w.flush() catch {}; self.renderStats.stdoutWriteTime = @as(f64, @floatFromInt(std.time.microTimestamp() - writeStart)); } self.renderStats.lastFrameTime = deltaTime * 1000.0; self.renderStats.frameCount += 1; - addStatSample(f64, &self.statSamples.lastFrameTime, deltaTime * 1000.0); + self.addStatSample(f64, &self.statSamples.lastFrameTime, deltaTime * 1000.0); if (self.renderStats.renderTime) |rt| { - addStatSample(f64, &self.statSamples.renderTime, rt); + self.addStatSample(f64, &self.statSamples.renderTime, rt); } if (self.renderStats.bufferResetTime) |brt| { - addStatSample(f64, &self.statSamples.bufferResetTime, brt); + self.addStatSample(f64, &self.statSamples.bufferResetTime, brt); } if (self.renderStats.stdoutWriteTime) |swt| { - addStatSample(f64, &self.statSamples.stdoutWriteTime, swt); + self.addStatSample(f64, &self.statSamples.stdoutWriteTime, swt); } - addStatSample(u32, &self.statSamples.cellsUpdated, self.renderStats.cellsUpdated); + self.addStatSample(u32, &self.statSamples.cellsUpdated, self.renderStats.cellsUpdated); } pub fn getNextBuffer(self: *CliRenderer) *OptimizedBuffer { @@ -719,9 +722,10 @@ pub const CliRenderer = struct { } pub fn clearTerminal(self: *CliRenderer) void { - var bufferedWriter = &self.stdoutWriter; - bufferedWriter.writer().writeAll(ansi.ANSI.clearAndHome) catch {}; - bufferedWriter.flush() catch {}; + var writer = std.fs.File.stdout().writer(&self.stdoutBuffer); + const w = &writer.interface; + w.writeAll(ansi.ANSI.clearAndHome) catch {}; + w.flush() catch {}; } pub fn addToHitGrid(self: *CliRenderer, x: i32, y: i32, width: u32, height: u32, id: u32) void { @@ -763,7 +767,9 @@ pub const CliRenderer = struct { const file = std.fs.cwd().createFile(filename, .{}) catch return; defer file.close(); - const writer = file.writer(); + var fileBuffer: [4096]u8 = undefined; + var fileWriter = file.writer(&fileBuffer); + const writer = &fileWriter.interface; for (0..self.hitGridHeight) |y| { for (0..self.hitGridWidth) |x| { @@ -775,6 +781,7 @@ pub const CliRenderer = struct { } writer.writeByte('\n') catch return; } + writer.flush() catch {}; } fn dumpSingleBuffer(self: *CliRenderer, buffer: *OptimizedBuffer, buffer_name: []const u8, timestamp: i64) void { @@ -789,7 +796,9 @@ pub const CliRenderer = struct { const file = std.fs.cwd().createFile(filename, .{}) catch return; defer file.close(); - const writer = file.writer(); + var fileBuffer: [4096]u8 = undefined; + var fileWriter = file.writer(&fileBuffer); + const writer = &fileWriter.interface; writer.print("{s} Buffer ({d}x{d}):\n", .{ buffer_name, self.width, self.height }) catch return; writer.writeAll("Characters:\n") catch return; @@ -807,6 +816,7 @@ pub const CliRenderer = struct { } writer.writeByte('\n') catch return; } + writer.flush() catch {}; } pub fn dumpStdoutBuffer(self: *CliRenderer, timestamp: i64) void { @@ -822,7 +832,9 @@ pub const CliRenderer = struct { const file = std.fs.cwd().createFile(filename, .{}) catch return; defer file.close(); - const writer = file.writer(); + var fileBuffer: [4096]u8 = undefined; + var fileWriter = file.writer(&fileBuffer); + const writer = &fileWriter.interface; writer.print("Stdout Buffer Output (timestamp: {d}):\n", .{timestamp}) catch return; writer.writeAll("Last Rendered ANSI Output:\n") catch return; @@ -840,6 +852,7 @@ pub const CliRenderer = struct { writer.writeAll("\n================\n") catch return; writer.print("Buffer size: {d} bytes\n", .{lastLen}) catch return; writer.print("Active buffer: {s}\n", .{if (activeBuffer == .A) "A" else "B"}) catch return; + writer.flush() catch {}; } pub fn dumpBuffers(self: *CliRenderer, timestamp: i64) void { @@ -852,8 +865,8 @@ pub const CliRenderer = struct { self.mouseEnabled = true; self.mouseMovementEnabled = enableMovement; - var bufferedWriter = &self.stdoutWriter; - const writer = bufferedWriter.writer(); + var bufferedWriter = std.fs.File.stdout().writer(&self.stdoutBuffer); + const writer = &bufferedWriter.interface; writer.writeAll(ansi.ANSI.enableSGRMouseMode) catch {}; writer.writeAll(ansi.ANSI.enableMouseTracking) catch {}; @@ -863,7 +876,7 @@ pub const CliRenderer = struct { writer.writeAll(ansi.ANSI.enableAnyEventTracking) catch {}; } - bufferedWriter.flush() catch {}; + writer.flush() catch {}; } pub fn disableMouse(self: *CliRenderer) void { @@ -872,15 +885,15 @@ pub const CliRenderer = struct { self.mouseEnabled = false; self.mouseMovementEnabled = false; - var bufferedWriter = &self.stdoutWriter; - const writer = bufferedWriter.writer(); + var writer = std.fs.File.stdout().writer(&self.stdoutBuffer); + const w = &writer.interface; - writer.writeAll(ansi.ANSI.disableAnyEventTracking) catch {}; - writer.writeAll(ansi.ANSI.disableButtonEventTracking) catch {}; - writer.writeAll(ansi.ANSI.disableMouseTracking) catch {}; - writer.writeAll(ansi.ANSI.disableSGRMouseMode) catch {}; + w.writeAll(ansi.ANSI.disableAnyEventTracking) catch {}; + w.writeAll(ansi.ANSI.disableButtonEventTracking) catch {}; + w.writeAll(ansi.ANSI.disableMouseTracking) catch {}; + w.writeAll(ansi.ANSI.disableSGRMouseMode) catch {}; - bufferedWriter.flush() catch {}; + w.flush() catch {}; } fn renderDebugOverlay(self: *CliRenderer) void { diff --git a/packages/core/src/zig/text-buffer.zig b/packages/core/src/zig/text-buffer.zig index 8e9564574..805761c42 100644 --- a/packages/core/src/zig/text-buffer.zig +++ b/packages/core/src/zig/text-buffer.zig @@ -31,8 +31,8 @@ pub const TextBuffer = struct { default_attributes: ?u8, allocator: Allocator, - line_starts: std.ArrayList(u32), - line_widths: std.ArrayList(u32), + line_starts: std.ArrayListUnmanaged(u32), + line_widths: std.ArrayListUnmanaged(u32), current_line_width: u32, pub fn init(allocator: Allocator, length: u32) TextBufferError!*TextBuffer { @@ -53,8 +53,8 @@ pub const TextBuffer = struct { .default_bg = null, .default_attributes = null, .allocator = allocator, - .line_starts = std.ArrayList(u32).init(allocator), - .line_widths = std.ArrayList(u32).init(allocator), + .line_starts = std.ArrayListUnmanaged(u32){}, + .line_widths = std.ArrayListUnmanaged(u32){}, .current_line_width = 0, }; @@ -63,7 +63,7 @@ pub const TextBuffer = struct { @memset(self.bg, .{ 0.0, 0.0, 0.0, 0.0 }); @memset(self.attributes, 0); - self.line_starts.append(0) catch return TextBufferError.OutOfMemory; + self.line_starts.append(allocator, 0) catch return TextBufferError.OutOfMemory; return self; } @@ -73,8 +73,8 @@ pub const TextBuffer = struct { self.allocator.free(self.fg); self.allocator.free(self.bg); self.allocator.free(self.attributes); - self.line_starts.deinit(); - self.line_widths.deinit(); + self.line_starts.deinit(self.allocator); + self.line_widths.deinit(self.allocator); self.allocator.destroy(self); } @@ -131,17 +131,17 @@ pub const TextBuffer = struct { result.line_starts.clearRetainingCapacity(); result.line_widths.clearRetainingCapacity(); for (self.line_starts.items) |start| { - result.line_starts.append(start) catch {}; + result.line_starts.append(result.allocator, start) catch {}; } for (self.line_widths.items) |width| { - result.line_widths.append(width) catch {}; + result.line_widths.append(result.allocator, width) catch {}; } for (other.line_starts.items[1..]) |start| { - result.line_starts.append(start + self.cursor) catch {}; + result.line_starts.append(result.allocator, start + self.cursor) catch {}; } for (other.line_widths.items) |width| { - result.line_widths.append(width) catch {}; + result.line_widths.append(result.allocator, width) catch {}; } result.current_line_width = other.current_line_width; @@ -154,7 +154,7 @@ pub const TextBuffer = struct { self.line_starts.clearRetainingCapacity(); self.line_widths.clearRetainingCapacity(); self.current_line_width = 0; - self.line_starts.append(0) catch {}; + self.line_starts.append(self.allocator, 0) catch {}; } pub fn setSelection(self: *TextBuffer, start: u32, end: u32, bgColor: ?RGBA, fgColor: ?RGBA) void { @@ -244,8 +244,8 @@ pub const TextBuffer = struct { try self.setCell(self.cursor, codepoint, useFg, useBg, attrValue); if (codepoint == '\n') { - self.line_widths.append(self.current_line_width) catch {}; - self.line_starts.append(self.cursor + 1) catch {}; + self.line_widths.append(self.allocator, self.current_line_width) catch {}; + self.line_starts.append(self.allocator, self.cursor + 1) catch {}; self.current_line_width = 0; } else { self.current_line_width += 1; @@ -261,7 +261,7 @@ pub const TextBuffer = struct { pub fn finalizeLineInfo(self: *TextBuffer) void { if (self.current_line_width > 0 or self.cursor == 0) { - self.line_widths.append(self.current_line_width) catch {}; + self.line_widths.append(self.allocator, self.current_line_width) catch {}; } }