From 6589ac9c58a682e9617c568dedde966a02563215 Mon Sep 17 00:00:00 2001 From: Paul Montag Date: Fri, 17 Apr 2026 22:31:45 -0500 Subject: [PATCH] restructuring --- src/container/matrix.zig | 110 ++++++++++ src/container/point.zig | 30 +++ src/container/rect.zig | 9 + src/container/root.zig | 16 ++ src/container/row_iterator.zig | 56 +++++ src/container/rowable.zig | 14 ++ src/container/sub_matrix.zig | 73 +++++++ src/grome/matrix.zig | 266 ----------------------- src/main.zig | 4 +- src/root.zig | 4 +- src/view/cell.zig | 17 ++ src/view/{draw.zig => double_buffer.zig} | 23 +- src/view/root.zig | 6 + src/view/viewport.zig | 8 + src/view/widget.zig | 8 + 15 files changed, 356 insertions(+), 288 deletions(-) create mode 100644 src/container/matrix.zig create mode 100644 src/container/point.zig create mode 100644 src/container/rect.zig create mode 100644 src/container/root.zig create mode 100644 src/container/row_iterator.zig create mode 100644 src/container/rowable.zig create mode 100644 src/container/sub_matrix.zig delete mode 100644 src/grome/matrix.zig create mode 100644 src/view/cell.zig rename src/view/{draw.zig => double_buffer.zig} (86%) create mode 100644 src/view/root.zig create mode 100644 src/view/viewport.zig create mode 100644 src/view/widget.zig diff --git a/src/container/matrix.zig b/src/container/matrix.zig new file mode 100644 index 0000000..c7f68de --- /dev/null +++ b/src/container/matrix.zig @@ -0,0 +1,110 @@ +const std = @import("std"); +const mem = std.mem; +const container = @import("root.zig"); + +const Allocator = mem.Allocator; +const Rect = container.Rect; +const Rowable = container.Rowable; +const Point = container.Point; +const SubMatrix = container.SubMatrix; +const Error = container.Error; +const RowIterator = container.RowIterator; + +/// Matrix is a part of the game board +pub fn Matrix(comptime T: type) type { + return struct { + /// data is the game board itself. + data: []T, + + /// 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() { + 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); + 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: *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 { + (try self.getPtr(loc)).* = val; + } + + // set sets the value of the specified location in memory + 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]; + } + + pub fn get(self: *const @This(), loc: Point) !T { + return (try self.getPtr(loc)).*; + } + + /// submatrix returns a [SubMatrix] + pub fn submatrix(self: *@This(), loc: Point, area: Rect) SubMatrix(T) { + return SubMatrix(T).init(self, loc, area); + } + + // TODO: you were in the middle of debugging this + pub fn row(interface: *Rowable(T), line: usize) ![]T { + var self: *@This() = @fieldParentPtr("rowable", interface); + const index = line * self.size.width; + if (index >= self.data.len) { + return Error.OutOfBounds; + } + + return self.data[index .. index + self.size.width]; + } + + pub fn row_iterator(self: *@This()) RowIterator(T) { + return .init(&self.rowable); + } + }; +} + +test "TestMatrix" { + const testing = std.testing; + const tests = [_]struct { + name: []const u8, + size: Rect, + point: Point, + value: u64, + }{ + .{ .name = "small", .size = .{ .width = 10, .height = 10 }, .point = .{ .x = 1, .y = 1 }, .value = 100 }, + .{ .name = "big", .size = .{ .width = 1000, .height = 1000 }, .point = .{ .x = 546, .y = 45 }, .value = 3472834 }, + }; + + inline for (tests) |case| { + var mat = try Matrix(u64).initZero(testing.allocator, case.size); + defer mat.deinit(testing.allocator); + try mat.set(case.point, case.value); + try testing.expectEqual(case.value, try mat.get(case.point)); + } +} diff --git a/src/container/point.zig b/src/container/point.zig new file mode 100644 index 0000000..f0f978d --- /dev/null +++ b/src/container/point.zig @@ -0,0 +1,30 @@ +const container = @import("root.zig"); + +const Rect = container.Rect; + +/// Point is a location within the matrix, (x,y) +pub const Point = struct { + x: usize, + y: usize, + + /// index returns the index in a flat array of the point givent he + /// provided width. + pub fn index(self: *const Point, width: usize) usize { + return (self.y * width) + self.x; + } + + // add two points together and return the subsiquent point + pub fn add(self: *const Point, b: Point) Point { + return .{ + .x = self.x + b.x, + .y = self.y + b.y, + }; + } + + pub fn bottom_right(self: *const Point, area: Rect) Point { + return .{ + .x = self.x + area.width, + .y = self.y + area.height, + }; + } +}; diff --git a/src/container/rect.zig b/src/container/rect.zig new file mode 100644 index 0000000..c06ba39 --- /dev/null +++ b/src/container/rect.zig @@ -0,0 +1,9 @@ +/// Rect is a rectangle +pub const Rect = struct { + width: usize, + height: usize, + + pub fn area(self: *const @This()) usize { + return self.width * self.height; + } +}; diff --git a/src/container/root.zig b/src/container/root.zig new file mode 100644 index 0000000..61167b1 --- /dev/null +++ b/src/container/root.zig @@ -0,0 +1,16 @@ +pub const Point = @import("point.zig").Point; +pub const Rect = @import("rect.zig").Rect; +pub const Rowable = @import("rowable.zig").Rowable; +pub const Matrix = @import("matrix.zig").Matrix; +pub const SubMatrix = @import("sub_matrix.zig").SubMatrix; +pub const RowIterator = @import("row_iterator.zig").RowIterator; + +// Error are all the errors that can exist for Container +// types. +pub const Error = error{ + OutOfBounds, +}; + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/src/container/row_iterator.zig b/src/container/row_iterator.zig new file mode 100644 index 0000000..7261cab --- /dev/null +++ b/src/container/row_iterator.zig @@ -0,0 +1,56 @@ +const std = @import("std"); +const mem = std.mem; +const container = @import("root.zig"); + +const Rowable = container.Rowable; +const Matrix = container.Matrix; +const Rect = container.Rect; + +pub fn RowIterator(comptime T: type) type { + return struct { + current: usize, + /// data is a reference to the board game data, now we will return it as + /// each row + rows: *Rowable(T), + + pub fn init(rowable: *Rowable(T)) @This() { + return .{ .rows = rowable, .current = 0 }; + } + + /// next returns the next row until we have exhausted the Rowable + pub fn next(self: *@This()) ?[]T { + defer self.current += 1; + return self.rows.row(self.current) catch null; + } + }; +} + +test "TestRowIterator" { + const testing = std.testing; + + var m = try Matrix(usize).initZero(testing.allocator, Rect{ .height = 10, .width = 10 }); + defer m.deinit(testing.allocator); + + for (0..10) |x| { + try m.set(.{ .x = x, .y = x }, x); + } + + var iter = m.row_iterator(); + var x: usize = 0; + while (iter.next()) |row| { + const v: @Vector(10, usize) = row[0..10].*; + try testing.expectEqual(x, @reduce(.Add, v)); + x += 1; + } + try testing.expectEqual(10, x); + + var sub = m.submatrix(.{ .x = 5, .y = 5 }, .{ .width = 5, .height = 5 }); + var sub_iter = sub.row_iterator(); + x = 0; + while (sub_iter.next()) |row| { + const v: @Vector(5, usize) = row[0..5].*; + try testing.expectEqual(x + 5, @reduce(.Add, v)); + x += 1; + } + try testing.expectEqual(5, x); +} diff --git a/src/container/rowable.zig b/src/container/rowable.zig new file mode 100644 index 0000000..4d3b3f1 --- /dev/null +++ b/src/container/rowable.zig @@ -0,0 +1,14 @@ +const container = @import("root.zig"); + +const Error = container.Error; + +/// Rowable is a type which can return a row at a time. +pub fn Rowable(comptime T: type) type { + return struct { + rowFn: *const fn (self: *@This(), line: usize) Error![]T, + + pub fn row(self: *@This(), line: usize) Error![]T { + return self.rowFn(self, line); + } + }; +} diff --git a/src/container/sub_matrix.zig b/src/container/sub_matrix.zig new file mode 100644 index 0000000..00d46c8 --- /dev/null +++ b/src/container/sub_matrix.zig @@ -0,0 +1,73 @@ +const std = @import("std"); +const mem = std.mem; +const container = @import("root.zig"); + +const Rect = container.Rect; +const Rowable = container.Rowable; +const Point = container.Point; +const Matrix = container.Matrix; +const Error = container.Error; +const RowIterator = container.RowIterator; + +// SubMatrix is a subset of a matrix which you can traverse to +pub fn SubMatrix(comptime T: type) type { + return struct { + // Data is made up of different sections of the + _m: *Matrix(T), + _offset: Point, + _mask: Rect, + //_width: usize, + rowable: Rowable(T), + + /// init takes the underlying data from the upstream matrix and creates + /// a submatrix of it + pub fn init(m: *Matrix(T), loc: Point, size: Rect) @This() { + return .{ ._m = m, ._offset = loc, ._mask = size, .rowable = Rowable(T){ .rowFn = SubMatrix(T).row } }; + } + + 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; + } + + return self._m.getPtr(self._offset.add(loc)); + } + + pub fn set(self: *@This(), loc: Point, val: T) !void { + (try self.getPtr(loc)).* = val; + } + + pub fn row(interface: *Rowable(T), line: usize) ![]T { + var self: *@This() = @fieldParentPtr("rowable", interface); + if (line >= self._mask.height) { + return Error.OutOfBounds; + } + + // TODO: This can index out of bound + // we need to checkt to make sure everything is copesedic + var r = try self._m.rowable.row(line + self._offset.y); + return r[self._offset.x .. self._offset.x + self._mask.width]; + } + + pub fn row_iterator(self: *@This()) RowIterator(T) { + return .init(&self.rowable); + } + }; +} + +test "SubMatrix" { + const testing = std.testing; + + // create our matrix and submatrix + var m = try Matrix(u8).initZero(testing.allocator, .{ .height = 10, .width = 10 }); + defer m.deinit(testing.allocator); + var sub = m.submatrix(.{ .x = 4, .y = 4 }, .{ .height = 5, .width = 5 }); + try sub.set(.{ .x = 0, .y = 0 }, 'a'); + + // we should see the value updated in the Matrix + try testing.expectEqual('a', try m.get(.{ .x = 4, .y = 4 })); +} diff --git a/src/grome/matrix.zig b/src/grome/matrix.zig deleted file mode 100644 index a6e004f..0000000 --- a/src/grome/matrix.zig +++ /dev/null @@ -1,266 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const Allocator = mem.Allocator; - -pub const Error = error{ - OutOfBounds, -}; - -/// Rowable is a type which can return a row at a time. -fn Rowable(comptime T: type) type { - return struct { - rowFn: *const fn (self: *@This(), line: usize) Error![]T, - - pub fn row(self: *@This(), line: usize) Error![]T { - return self.rowFn(self, line); - } - }; -} - -/// Point is a location within the matrix, (x,y) -pub const Point = struct { - x: usize, - y: usize, - - /// index returns the index in a flat array of the point givent he - /// provided width. - pub fn index(self: *const Point, width: usize) usize { - return (self.y * width) + self.x; - } - - // add two points together and return the subsiquent point - pub fn add(self: *const Point, b: Point) Point { - return .{ - .x = self.x + b.x, - .y = self.y + b.y, - }; - } - - pub fn bottom_right(self: *const Point, area: Rect) Point { - return .{ - .x = self.x + area.width, - .y = self.y + area.height, - }; - } -}; - -/// Rect is a rectangle -pub const Rect = struct { - width: usize, - height: usize, - - pub fn area(self: *const @This()) usize { - return self.width * self.height; - } -}; - -/// Matrix is a part of the game board -pub fn Matrix(comptime T: type) type { - return struct { - /// data is the game board itself. - data: []T, - - /// 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() { - 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); - 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: *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 { - (try self.getPtr(loc)).* = val; - } - - // set sets the value of the specified location in memory - 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]; - } - - pub fn get(self: *const @This(), loc: Point) !T { - return (try self.getPtr(loc)).*; - } - - /// submatrix returns a [SubMatrix] - pub fn submatrix(self: *@This(), loc: Point, area: Rect) SubMatrix(T) { - return SubMatrix(T).init(self, loc, area); - } - - // TODO: you were in the middle of debugging this - pub fn row(interface: *Rowable(T), line: usize) ![]T { - var self: *@This() = @fieldParentPtr("rowable", interface); - const index = line * self.size.width; - if (index >= self.data.len) { - return Error.OutOfBounds; - } - - return self.data[index .. index + self.size.width]; - } - - pub fn row_iterator(self: *@This()) RowIterator(T) { - return .init(&self.rowable); - } - }; -} - -test "TestMatrix" { - const testing = std.testing; - const tests = [_]struct { - name: []const u8, - size: Rect, - point: Point, - value: u64, - }{ - .{ .name = "small", .size = .{ .width = 10, .height = 10 }, .point = .{ .x = 1, .y = 1 }, .value = 100 }, - .{ .name = "big", .size = .{ .width = 1000, .height = 1000 }, .point = .{ .x = 546, .y = 45 }, .value = 3472834 }, - }; - - inline for (tests) |case| { - var mat = try Matrix(u64).initZero(testing.allocator, case.size); - defer mat.deinit(testing.allocator); - try mat.set(case.point, case.value); - try testing.expectEqual(case.value, try mat.get(case.point)); - } -} - -// SubMatrix is a subset of a matrix which you can traverse to -pub fn SubMatrix(comptime T: type) type { - return struct { - // Data is made up of different sections of the - _m: *Matrix(T), - _offset: Point, - _mask: Rect, - //_width: usize, - rowable: Rowable(T), - - /// init takes the underlying data from the upstream matrix and creates - /// a submatrix of it - pub fn init(m: *Matrix(T), loc: Point, size: Rect) @This() { - return .{ ._m = m, ._offset = loc, ._mask = size, .rowable = Rowable(T){ .rowFn = SubMatrix(T).row } }; - } - - 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; - } - - return self._m.getPtr(self._offset.add(loc)); - } - - pub fn set(self: *@This(), loc: Point, val: T) !void { - (try self.getPtr(loc)).* = val; - } - - pub fn row(interface: *Rowable(T), line: usize) ![]T { - var self: *@This() = @fieldParentPtr("rowable", interface); - if (line >= self._mask.height) { - return Error.OutOfBounds; - } - - // TODO: This can index out of bound - // we need to checkt to make sure everything is copesedic - var r = try self._m.rowable.row(line + self._offset.y); - return r[self._offset.x .. self._offset.x + self._mask.width]; - } - - pub fn row_iterator(self: *@This()) RowIterator(T) { - return .init(&self.rowable); - } - }; -} - -test "SubMatrix" { - const testing = std.testing; - - // create our matrix and submatrix - var m = try Matrix(u8).initZero(testing.allocator, .{ .height = 10, .width = 10 }); - defer m.deinit(testing.allocator); - var sub = m.submatrix(.{ .x = 4, .y = 4 }, .{ .height = 5, .width = 5 }); - try sub.set(.{ .x = 0, .y = 0 }, 'a'); - - // we should see the value updated in the Matrix - try testing.expectEqual('a', try m.get(.{ .x = 4, .y = 4 })); -} - -pub fn RowIterator(comptime T: type) type { - return struct { - current: usize, - /// data is a reference to the board game data, now we will return it as - /// each row - rows: *Rowable(T), - - fn init(rowable: *Rowable(T)) @This() { - return .{ .rows = rowable, .current = 0 }; - } - - /// next returns the next row until we have exhausted the Rowable - pub fn next(self: *@This()) ?[]T { - defer self.current += 1; - return self.rows.row(self.current) catch null; - } - }; -} - -test "TestRowIterator" { - const testing = std.testing; - - var m = try Matrix(usize).initZero(testing.allocator, Rect{ .height = 10, .width = 10 }); - defer m.deinit(testing.allocator); - - for (0..10) |x| { - try m.set(.{ .x = x, .y = x }, x); - } - - var iter = m.row_iterator(); - var x: usize = 0; - while (iter.next()) |row| { - const v: @Vector(10, usize) = row[0..10].*; - try testing.expectEqual(x, @reduce(.Add, v)); - x += 1; - } - try testing.expectEqual(10, x); - - var sub = m.submatrix(.{ .x = 5, .y = 5 }, .{ .width = 5, .height = 5 }); - var sub_iter = sub.row_iterator(); - x = 0; - while (sub_iter.next()) |row| { - const v: @Vector(5, usize) = row[0..5].*; - try testing.expectEqual(x + 5, @reduce(.Add, v)); - x += 1; - } - try testing.expectEqual(5, x); -} diff --git a/src/main.zig b/src/main.zig index ecc1cf3..9364099 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,8 +2,8 @@ const std = @import("std"); const grome = @import("root.zig"); const Io = std.Io; -const Rect = grome.matrix.Rect; -const DoubleBuffer = grome.draw.DoubleBuffer; +const Rect = grome.container.Rect; +const DoubleBuffer = grome.view.DoubleBuffer; const Duration = Io.Duration; const Clock = Io.Clock; diff --git a/src/root.zig b/src/root.zig index 1262543..8376d09 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub const matrix = @import("grome/matrix.zig"); -pub const draw = @import("view/draw.zig"); +pub const container = @import("container/root.zig"); +pub const view = @import("view/root.zig"); test { std.testing.refAllDecls(@This()); diff --git a/src/view/cell.zig b/src/view/cell.zig new file mode 100644 index 0000000..03fa7b6 --- /dev/null +++ b/src/view/cell.zig @@ -0,0 +1,17 @@ +const std = @import("std"); +const mem = std.mem; + +// Cell is a single cell in the [DoubleBuffer]. +pub const Cell = struct { + char: [8]u8, + width: usize, + + /// 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; + } + + pub fn render(self: *const @This(), w: *std.Io.Writer) !void { + _ = try w.write(self.char[0..self.width]); + } +}; diff --git a/src/view/draw.zig b/src/view/double_buffer.zig similarity index 86% rename from src/view/draw.zig rename to src/view/double_buffer.zig index 543843f..9eb2b09 100644 --- a/src/view/draw.zig +++ b/src/view/double_buffer.zig @@ -4,10 +4,11 @@ const mem = std.mem; const grome = @import("../root.zig"); const Allocator = mem.Allocator; -const Matrix = grome.matrix.Matrix; -const SubMatrix = grome.matrix.SubMatrix; -const Point = grome.matrix.Point; -const Rect = grome.matrix.Rect; +const Matrix = grome.container.Matrix; +const SubMatrix = grome.container.SubMatrix; +const Point = grome.container.Point; +const Rect = grome.container.Rect; +const Cell = grome.view.Cell; /// DoubleBuffer manages two buffers for efficient rendering: a display buffer /// showing the current screen state, and a write buffer for accumulating changes @@ -98,17 +99,3 @@ pub const DoubleBuffer = struct { self.swap(); } }; - -pub const Cell = struct { - char: [8]u8, - width: usize, - - /// 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; - } - - pub fn render(self: *const @This(), w: *std.Io.Writer) !void { - _ = try w.write(self.char[0..self.width]); - } -}; diff --git a/src/view/root.zig b/src/view/root.zig new file mode 100644 index 0000000..2bb80ee --- /dev/null +++ b/src/view/root.zig @@ -0,0 +1,6 @@ +pub const DoubleBuffer = @import("double_buffer.zig").DoubleBuffer; +pub const Cell = @import("cell.zig").Cell; + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/src/view/viewport.zig b/src/view/viewport.zig new file mode 100644 index 0000000..16b77d1 --- /dev/null +++ b/src/view/viewport.zig @@ -0,0 +1,8 @@ +const std = @import("std"); +const grome = @import("../root.zig"); + +const DoubleBuffer = grome.draw.DoubleBuffer; + +pub const Viewport = struct { + buffer: DoubleBuffer, +}; diff --git a/src/view/widget.zig b/src/view/widget.zig new file mode 100644 index 0000000..e2ed6bb --- /dev/null +++ b/src/view/widget.zig @@ -0,0 +1,8 @@ +const std = @import("std"); +const mem = std.mem; + +const grome = @import("../root.zig"); + +/// A widget is an intrusive interface that allows the implementor to draw +/// a specific item to a frame. +pub const Widget = struct {};