const std = @import("std"); const terminal = @import("terminal.zig"); const Cell = @import("cell.zig"); const Position = @import("size.zig").Position; const Size = @import("size.zig").Size; /// Double-buffered intermediate rendering pipeline pub const Buffered = struct { const log = std.log.scoped(.renderer_buffered); // _ = log; allocator: std.mem.Allocator, created: bool, size: Size, screen: []Cell, virtual_screen: []Cell, pub fn init(allocator: std.mem.Allocator) @This() { return .{ .allocator = allocator, .created = false, .size = undefined, .screen = undefined, .virtual_screen = undefined, }; } pub fn deinit(this: *@This()) void { if (this.created) { this.allocator.free(this.screen); this.allocator.free(this.virtual_screen); } } pub fn resize(this: *@This(), size: Size) !void { log.debug("renderer::resize", .{}); defer this.size = size; if (!this.created) { this.screen = this.allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows)) catch @panic("render.zig: Out of memory."); @memset(this.screen, .{}); this.virtual_screen = this.allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows)) catch @panic("render.zig: Out of memory."); @memset(this.virtual_screen, .{}); this.created = true; } else { this.allocator.free(this.screen); this.screen = this.allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows)) catch @panic("render.zig: Out of memory."); @memset(this.screen, .{}); this.allocator.free(this.virtual_screen); this.virtual_screen = this.allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows)) catch @panic("render.zig: Out of memory."); @memset(this.virtual_screen, .{}); } try this.clear(); } /// Clear the entire screen and reset the screen buffer, to force a re-draw with the next `flush` call. pub fn clear(this: *@This()) !void { log.debug("renderer::clear", .{}); try terminal.clearScreen(); @memset(this.screen, .{}); } /// Render provided cells at size (anchor and dimension) into the *virtual screen*. pub fn render(this: *@This(), comptime T: type, container: *T) !void { const size: Size = container.size; const cells: []const Cell = try container.contents(); if (cells.len == 0) return; var idx: usize = 0; var vs = this.virtual_screen; const anchor: usize = (@as(usize, size.anchor.row) * @as(usize, this.size.cols)) + @as(usize, size.anchor.col); blk: for (0..size.rows) |row| { for (0..size.cols) |col| { const cell = cells[idx]; idx += 1; vs[anchor + (row * this.size.cols) + col].style = cell.style; vs[anchor + (row * this.size.cols) + col].cp = cell.cp; if (cells.len == idx) break :blk; } } // free immediately container.allocator.free(cells); for (container.elements.items) |*element| { try this.render(T, element); } } /// Write *virtual screen* to alternate screen (should be called once and last during each render loop iteration in the main loop). pub fn flush(this: *@This()) !void { // TODO: measure timings of rendered frames? log.debug("renderer::flush", .{}); const writer = terminal.writer(); const s = this.screen; const vs = this.virtual_screen; for (0..this.size.rows) |row| { for (0..this.size.cols) |col| { const idx = (row * this.size.cols) + col; const cs = s[idx]; const cvs = vs[idx]; if (cs.eql(cvs)) continue; // render differences found in virtual screen try terminal.setCursorPosition(.{ .row = @truncate(row + 1), .col = @truncate(col + 1) }); try cvs.value(writer); // update screen to be the virtual screen for the next frame s[idx] = vs[idx]; } } } };