From 5053a3ab5de0b89d564ba8fffa98c3b0c2502443 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 18 Feb 2024 15:37:17 -0800 Subject: [PATCH] terminal/new: page init --- src/terminal/new/page.zig | 161 ++++++++++++++++++++++++++----------- src/terminal/new/style.zig | 14 +++- 2 files changed, 125 insertions(+), 50 deletions(-) diff --git a/src/terminal/new/page.zig b/src/terminal/new/page.zig index d37661f94..080979689 100644 --- a/src/terminal/new/page.zig +++ b/src/terminal/new/page.zig @@ -1,12 +1,15 @@ const std = @import("std"); +const Allocator = std.mem.Allocator; const assert = std.debug.assert; const color = @import("../color.zig"); const sgr = @import("../sgr.zig"); const style = @import("style.zig"); const size = @import("size.zig"); const Offset = size.Offset; +const OffsetBuf = size.OffsetBuf; const hash_map = @import("hash_map.zig"); const AutoOffsetHashMap = hash_map.AutoOffsetHashMap; +const alignForward = std.mem.alignForward; /// A page represents a specific section of terminal screen. The primary /// idea of a page is that it is a fully self-contained unit that can be @@ -24,6 +27,16 @@ const AutoOffsetHashMap = hash_map.AutoOffsetHashMap; /// thoughtfully laid out to optimize primarily for terminal IO (VT streams) /// and to minimize memory usage. pub const Page = struct { + comptime { + // The alignment of our members. We want to ensure that the page + // alignment is always divisible by this. + assert(std.mem.page_size % @max( + @alignOf(Row), + @alignOf(Cell), + style.Set.base_align, + ) == 0); + } + /// The backing memory for the page. A page is always made up of a /// a single contiguous block of memory that is aligned on a page /// boundary and is a multiple of the system page size. @@ -41,6 +54,78 @@ pub const Page = struct { /// to row, you must use the `rows` field. From the pointer to the /// first column, all cells in that row are laid out in column order. cells: Offset(Cell), + + /// The available set of styles in use on this page. + styles: style.Set, + + /// Capacity of this page. + pub const Capacity = struct { + /// Number of columns and rows we can know about. + cols: usize, + rows: usize, + + /// Number of unique styles that can be used on this page. + styles: u16, + }; + + /// Initialize a new page, allocating the required backing memory. + /// It is HIGHLY RECOMMENDED you use a page_allocator as the allocator + /// but any allocator is allowed. + pub fn init(alloc: Allocator, cap: Capacity) !Page { + const l = layout(cap); + const backing = try alloc.alignedAlloc(u8, std.mem.page_size, l.total_size); + errdefer alloc.free(backing); + + const buf = OffsetBuf.init(backing); + + return .{ + .memory = backing, + .rows = buf.member(Row, l.rows_start), + .cells = buf.member(Cell, l.cells_start), + .styles = style.Set.init( + buf.add(l.styles_start), + l.styles_layout, + ), + }; + } + + pub fn deinit(self: *Page, alloc: Allocator) void { + alloc.free(self.memory); + self.* = undefined; + } + + pub const Layout = struct { + total_size: usize, + rows_start: usize, + cells_start: usize, + styles_start: usize, + styles_layout: style.Set.Layout, + }; + + /// The memory layout for a page given a desired minimum cols + /// and rows size. + pub fn layout(cap: Capacity) Layout { + const rows_start = 0; + const rows_end = rows_start + (cap.rows * @sizeOf(Row)); + + const cells_count = cap.cols * cap.rows; + const cells_start = alignForward(usize, rows_end, @alignOf(Cell)); + const cells_end = cells_start + (cells_count * @sizeOf(Cell)); + + const styles_layout = style.Set.layout(cap.styles); + const styles_start = alignForward(usize, cells_end, style.Set.base_align); + const styles_end = styles_start + styles_layout.total_size; + + const total_size = styles_end; + + return .{ + .total_size = total_size, + .rows_start = rows_start, + .cells_start = cells_start, + .styles_start = styles_start, + .styles_layout = styles_layout, + }; + } }; pub const Row = packed struct { @@ -54,56 +139,34 @@ pub const Row = packed struct { /// since we zero initialize the backing memory for a page. pub const Cell = packed struct(u32) { codepoint: u21 = 0, + _padding: u11 = 0, }; -/// The style attributes for a cell. -pub const Style = struct { - /// Various colors, all self-explanatory. - fg_color: Color = .none, - bg_color: Color = .none, - underline_color: Color = .none, - - /// On/off attributes that don't require much bit width so we use - /// a packed struct to make this take up significantly less space. - flags: packed struct { - bold: bool = false, - italic: bool = false, - faint: bool = false, - blink: bool = false, - inverse: bool = false, - invisible: bool = false, - strikethrough: bool = false, - underline: sgr.Attribute.Underline = .none, - } = .{}, - - /// The color for an SGR attribute. A color can come from multiple - /// sources so we use this to track the source plus color value so that - /// we can properly react to things like palette changes. - pub const Color = union(enum) { - none: void, - palette: u8, - rgb: color.RGB, - }; - - test { - // The size of the struct so we can be aware of changes. - const testing = std.testing; - try testing.expectEqual(@as(usize, 14), @sizeOf(Style)); - } -}; - -test { - _ = Page; - _ = Style; -} - -// test { -// const testing = std.testing; -// const cap = try std.math.ceilPowerOfTwo(usize, 350); -// const StyleIdMap = AutoOffsetHashMap(size.CellCountInt, style.Style); -// const StyleMetadataMap = AutoOffsetHashMap(style.Style, style.Metadata); +// Uncomment this when you want to do some math. +// test "Page size calculator" { +// const total_size = alignForward( +// usize, +// Page.layout(.{ +// .cols = 333, +// .rows = 81, +// .styles = 32, +// }).total_size, +// std.mem.page_size, +// ); // -// var len = StyleIdMap.bufferSize(@intCast(cap)); -// len += StyleMetadataMap.bufferSize(@intCast(cap)); -// try testing.expectEqual(@as(usize, 0), len); +// std.log.warn("total_size={} pages={}", .{ +// total_size, +// total_size / std.mem.page_size, +// }); // } + +test "Page" { + const testing = std.testing; + const alloc = testing.allocator; + var page = try Page.init(alloc, .{ + .cols = 120, + .rows = 80, + .styles = 32, + }); + defer page.deinit(alloc); +} diff --git a/src/terminal/new/style.zig b/src/terminal/new/style.zig index aab12aea7..668e9f7c8 100644 --- a/src/terminal/new/style.zig +++ b/src/terminal/new/style.zig @@ -132,13 +132,25 @@ pub const Set = struct { }; } + /// Possible errors for upsert. + pub const UpsertError = error{ + /// No more space in the backing buffer. Remove styles or + /// grow and reinitialize. + OutOfMemory, + + /// No more available IDs. Perform a garbage collection + /// operation to compact ID space. + /// TODO: implement gc operation + Overflow, + }; + /// Upsert a style into the set and return a pointer to the metadata /// for that style. The pointer is valid for the lifetime of the set /// so long as the style is not removed. /// /// The ref count for new styles is initialized to zero and /// for existing styles remains unmodified. - pub fn upsert(self: *Set, base: anytype, style: Style) !*Metadata { + pub fn upsert(self: *Set, base: anytype, style: Style) UpsertError!*Metadata { // If we already have the style in the map, this is fast. var map = self.styles.map(base); const gop = try map.getOrPut(style);