diff --git a/src/grome/matrix.zig b/src/grome/matrix.zig index c66fcd9..a6e004f 100644 --- a/src/grome/matrix.zig +++ b/src/grome/matrix.zig @@ -63,46 +63,52 @@ pub fn Matrix(comptime T: type) type { /// defines the size of the matrix size: Rect, + /// Specifies the zero value to display when reseting the matrix + zero: T, + rowable: Rowable(T), /// init creates the chunk, storing the bytes in memory. pub fn initZero(gpa: Allocator, size: Rect) !@This() { - const data = try gpa.alloc(T, size.area()); - @memset(data, mem.zeroes(T)); - return .{ .data = data, .size = size, .rowable = .{ .rowFn = Matrix(T).row } }; + return @This().init(gpa, mem.zeroes(T), size); } /// init creates the chunk, storing the bytes in memory. pub fn init(gpa: Allocator, zero: T, size: Rect) !@This() { const data = try gpa.alloc(T, size.area()); @memset(data, zero); - return .{ .data = data, .size = size, .rowable = .{ .rowFn = Matrix(T).row } }; + var val = @This(){ .data = data, .size = size, .zero = zero, .rowable = .{ .rowFn = Matrix(T).row } }; + val.reset(); + return val; + } + + /// reset sets all values in the matrix back to the zero value. + pub fn reset(self: *@This()) void { + @memset(self.data, self.zero); } /// deinit frees the memory utilized to create the chunk - pub fn deinit(self: *@This(), gpa: Allocator) void { + pub fn deinit(self: *const @This(), gpa: Allocator) void { gpa.free(self.data); } // set sets the value of the specified location in memory pub fn set(self: *@This(), loc: Point, val: T) !void { - if (loc.x >= self.size.width or loc.y >= self.size.height) { - std.debug.print("loc.x ({d}) >= self.size.width ({d}) or loc.y ({d}) >= self.size.height ({d})", .{ loc.x, self.size.width, loc.y, self.size.height }); - return Error.OutOfBounds; - } - - const index = (self.size.width * loc.y) + loc.x; - self.data[index] = val; + (try self.getPtr(loc)).* = val; } // set sets the value of the specified location in memory - pub fn get(self: *const @This(), loc: Point) !T { + pub fn getPtr(self: *const @This(), loc: Point) !*T { if (loc.x >= self.size.width or loc.y >= self.size.height) { return Error.OutOfBounds; } const index = (self.size.width * loc.y) + loc.x; - return self.data[index]; + return &self.data[index]; + } + + pub fn get(self: *const @This(), loc: Point) !T { + return (try self.getPtr(loc)).*; } /// submatrix returns a [SubMatrix] @@ -164,19 +170,19 @@ pub fn SubMatrix(comptime T: type) type { } pub fn get(self: *const @This(), loc: Point) !T { + return (try self.getPtr(loc)).*; + } + + pub fn getPtr(self: *const @This(), loc: Point) !*T { if (loc.x >= self._mask.width or loc.y >= self._mask.height) { return Error.OutOfBounds; } - try self._m.get(self._offset.add(loc)); + return self._m.getPtr(self._offset.add(loc)); } pub fn set(self: *@This(), loc: Point, val: T) !void { - if (loc.x >= self._mask.width or loc.y >= self._mask.height) { - return Error.OutOfBounds; - } - - try self._m.set(self._offset.add(loc), val); + (try self.getPtr(loc)).* = val; } pub fn row(interface: *Rowable(T), line: usize) ![]T { diff --git a/src/main.zig b/src/main.zig index f4e1f94..ecc1cf3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -14,21 +14,38 @@ pub fn main(init: std.process.Init) !void { // Initialize the stdout writer var stdout_buffer: [1024]u8 = undefined; var stdout_file_writer: Io.File.Writer = .init(.stdout(), init.io, &stdout_buffer); + var w = &stdout_file_writer.interface; // alright lets play with the matrix - const size = Rect{ .height = 20, .width = 50 }; + const size = Rect{ .height = 70, .width = 120 }; var double_buf = try DoubleBuffer.init(arena, size); + defer double_buf.deinit(arena); + try double_buf.hard_refresh(w); - const density = size.area() / 100; while (true) { - var frame = double_buf.frame(.{ .x = 0, .y = 0 }, size); - for (0..density) |_| { - var buf: [3]u8 = undefined; - init.io.random(&buf); - try frame.set(.{ .x = @intCast(buf[0] % size.width), .y = @intCast(buf[1] % size.height) }, .{ .char = [_]u8{@intCast((buf[2] % 50) + 93)} ** 8, .width = 1 }); + var frame = double_buf.frame(.{ .x = 0, .y = 10 }, .{ .height = 1, .width = 120 }); + const row = try frame.rowable.row(0); + var fill_next: bool = false; + var unfilled: bool = true; + + for (row) |*cell| { + if (cell.char[0] == '>') { + fill_next = true; + cell.char[0] = ' '; + } else { + if (fill_next) { + fill_next = false; + cell.char[0] = '>'; + unfilled = false; + } + } + } + if (unfilled) { + row[0].char[0] = '>'; } - _ = try double_buf.render(&stdout_file_writer.interface); - try init.io.sleep(Duration.fromMilliseconds(100), Clock.real); + try double_buf.refresh(w); + try w.flush(); + try init.io.sleep(Duration.fromMilliseconds(10), Clock.real); } } diff --git a/src/view/draw.zig b/src/view/draw.zig index 2a405f9..543843f 100644 --- a/src/view/draw.zig +++ b/src/view/draw.zig @@ -15,52 +15,87 @@ const Rect = grome.matrix.Rect; /// approach minimizes unnecessary updates by only diffing changed cells. pub const DoubleBuffer = struct { // display holds the existing data. - a: Matrix(Cell), + front: Matrix(Cell), // write is where we are writing the active changes which will be commited // on the next rendering - b: Matrix(Cell), - - // Front and back hold pointers to a and b, they will switch back and forth - // based on what is needed. - front: *Matrix(Cell), - back: *Matrix(Cell), + back: Matrix(Cell), pub fn init(gpa: Allocator, size: Rect) !DoubleBuffer { - var a = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size); + const a = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size); errdefer a.deinit(gpa); - var b = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size); + const b = try Matrix(Cell).init(gpa, .{ .char = [_]u8{' '} ** 8, .width = 1 }, size); return .{ - .a = a, - .b = b, - .front = &a, - .back = &b, + .front = a, + .back = b, }; } pub fn deinit(self: *@This(), gpa: Allocator) void { - gpa.free(self.a); - gpa.free(self.b); + self.front.deinit(gpa); + self.back.deinit(gpa); } + // frame returns a subset of the backbuffer where `loc` is the top left point of the + // subsection, and `size` is the area or size of the subset. pub fn frame(self: *@This(), loc: Point, size: Rect) SubMatrix(Cell) { - return .init(self.back, loc, size); + return .init(&self.back, loc, size); } - pub fn render(self: *@This(), w: *std.Io.Writer) !usize { - std.mem.swap(*Matrix(Cell), &self.front, &self.back); + // swap the front and back. This should be called right before rendering + fn swap(self: *@This()) void { + std.mem.swap(Matrix(Cell), &self.front, &self.back); + } - var i = try w.write("\\033[H\\033[2J"); - var iter = self.front.row_iterator(); - while (iter.next()) |row| { - for (row) |cell| { - i += try cell.render(w); + // render outputs contents of the backbuffer to the writer. This function **does not** + // call `flush` so you should! + pub fn hard_refresh(self: *@This(), w: *std.Io.Writer) !void { + _ = try w.write("\x1b[H\x1b[2J"); + var back_iter = self.back.row_iterator(); + + while (back_iter.next()) |back_row| { + for (back_row) |cell| { + _ = try cell.render(w); } - i += try w.write("\n"); + _ = try w.write("\n"); } - return i; + self.swap(); + } + + pub fn refresh(self: *@This(), w: *std.Io.Writer) !void { + _ = try w.write("\x1b[H"); + + var back_iter = self.back.row_iterator(); + var front_iter = self.front.row_iterator(); + var skip_lines: usize = 0; + + while (true) { + var back_row = back_iter.next() orelse break; + const front_row = front_iter.next() orelse break; + + for (0..back_row.len) |x| { + if (back_row[x].eql(&front_row[x])) { + continue; + } + + // if it's a new character then write out the rest of the line and then + // continue + try w.print("\x1b[{d};{d}H", .{ skip_lines, x + 1 }); + for (back_row[x..]) |*cell| { + try cell.render(w); + } + _ = try w.write("\n"); + skip_lines = 0; + break; + } + + skip_lines += 1; + } + + _ = try w.write("\x1b[?25l"); + self.swap(); } }; @@ -70,10 +105,10 @@ pub const Cell = struct { /// eql returns pub fn eql(self: *const Cell, b: *const Cell) bool { - return mem.eql(u8, self.char, b.char) and self.width == b.width; + return mem.eql(u8, &self.char, &b.char) and self.width == b.width; } - pub fn render(self: *const @This(), w: *std.Io.Writer) !usize { - return w.write(self.char[0..self.width]); + pub fn render(self: *const @This(), w: *std.Io.Writer) !void { + _ = try w.write(self.char[0..self.width]); } };