diff --git a/src/terminal/Screen.zig b/src/terminal/Screen.zig index 347a06ebf..02bb9c9b6 100644 --- a/src/terminal/Screen.zig +++ b/src/terminal/Screen.zig @@ -3306,13 +3306,6 @@ test "Screen: scrolling when viewport is pruned" { for (0..1000) |_| try s.testWriteString("1ABCD\n2EFGH\n3IJKL\n"); try s.testWriteString("1ABCD\n2EFGH\n3IJKL"); - { - // Test our contents rotated - const contents = try s.dumpStringAlloc(alloc, .{ .viewport = .{} }); - defer alloc.free(contents); - try testing.expectEqualStrings("1ABCD\n2EFGH\n3IJKL", contents); - } - { try testing.expectEqual(point.Point{ .screen = .{ .x = 0, diff --git a/src/terminal/bitmap_allocator.zig b/src/terminal/bitmap_allocator.zig index 550a50416..a13236217 100644 --- a/src/terminal/bitmap_allocator.zig +++ b/src/terminal/bitmap_allocator.zig @@ -65,6 +65,9 @@ pub fn BitmapAllocator(comptime chunk_size: comptime_int) type { /// Allocate n elements of type T. This will return error.OutOfMemory /// if there isn't enough space in the backing buffer. + /// + /// Use (size.zig).getOffset to get the base offset from the backing + /// memory for portable storage. pub fn alloc( self: *Self, comptime T: type, diff --git a/src/terminal/page.zig b/src/terminal/page.zig index 65334460f..8f895a82c 100644 --- a/src/terminal/page.zig +++ b/src/terminal/page.zig @@ -34,6 +34,24 @@ const grapheme_count_default = GraphemeAlloc.bitmap_bit_size; const grapheme_bytes_default = grapheme_count_default * grapheme_chunk; const GraphemeMap = AutoOffsetHashMap(Offset(Cell), Offset(u21).Slice); +/// The allocator used for shared utf8-encoded strings within a page. +/// Note the chunk size below is the minimum size of a single allocation +/// and requires a single bit of metadata in our bitmap allocator. Therefore +/// it should be tuned carefully (too small and we waste metadata, too large +/// and we have fragmentation). We can probably use a better allocation +/// strategy in the future. +/// +/// At the time of writing this, the strings table is only used for OSC8 +/// IDs and URIs. IDs are usually short and URIs are usually longer. I chose +/// 32 bytes as a compromise between these two since it represents single +/// domain links quite well and is not too wasteful for short IDs. We can +/// continue to tune this as we see how it's used. +const string_chunk_len = 32; +const string_chunk = string_chunk_len * @sizeOf(u8); +const StringAlloc = BitmapAllocator(string_chunk); +const string_count_default = StringAlloc.bitmap_bit_size; +const string_bytes_default = string_count_default * string_chunk; + /// 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 /// serialized, copied, etc. as a convenient way to represent a section @@ -75,6 +93,11 @@ pub const Page = struct { /// first column, all cells in that row are laid out in column order. cells: Offset(Cell), + /// The string allocator for this page used for shared utf-8 encoded + /// strings. Liveness of strings and memory management is deferred to + /// the individual use case. + string_alloc: StringAlloc, + /// The multi-codepoint grapheme data for this page. This is where /// any cell that has more than one codepoint will be stored. This is /// relatively rare (typically only emoji) so this defaults to a very small @@ -199,6 +222,10 @@ pub const Page = struct { l.styles_layout, .{}, ), + .string_alloc = StringAlloc.init( + buf.add(l.string_alloc_start), + l.string_alloc_layout, + ), .grapheme_alloc = GraphemeAlloc.init( buf.add(l.grapheme_alloc_start), l.grapheme_alloc_layout, @@ -977,6 +1004,8 @@ pub const Page = struct { grapheme_alloc_layout: GraphemeAlloc.Layout, grapheme_map_start: usize, grapheme_map_layout: GraphemeMap.Layout, + string_alloc_start: usize, + string_alloc_layout: StringAlloc.Layout, capacity: Capacity, }; @@ -1015,7 +1044,11 @@ pub const Page = struct { const grapheme_map_start = alignForward(usize, grapheme_alloc_end, GraphemeMap.base_align); const grapheme_map_end = grapheme_map_start + grapheme_map_layout.total_size; - const total_size = alignForward(usize, grapheme_map_end, std.mem.page_size); + const string_layout = StringAlloc.layout(cap.string_bytes); + const string_start = alignForward(usize, grapheme_map_end, StringAlloc.base_align); + const string_end = string_start + string_layout.total_size; + + const total_size = alignForward(usize, string_end, std.mem.page_size); return .{ .total_size = total_size, @@ -1031,6 +1064,8 @@ pub const Page = struct { .grapheme_alloc_layout = grapheme_alloc_layout, .grapheme_map_start = grapheme_map_start, .grapheme_map_layout = grapheme_map_layout, + .string_alloc_start = string_start, + .string_alloc_layout = string_layout, .capacity = cap, }; } @@ -1038,12 +1073,15 @@ pub const Page = struct { /// The standard capacity for a page that doesn't have special /// requirements. This is enough to support a very large number of cells. -/// The standard capacity is chosen as the fast-path for allocation. +/// The standard capacity is chosen as the fast-path for allocation since +/// pages of standard capacity use a pooled allocator instead of single-use +/// mmaps. pub const std_capacity: Capacity = .{ .cols = 215, .rows = 215, .styles = 128, .grapheme_bytes = 8192, + .string_bytes = 2048, }; /// The size of this page. @@ -1064,6 +1102,9 @@ pub const Capacity = struct { /// Number of bytes to allocate for grapheme data. grapheme_bytes: usize = grapheme_bytes_default, + /// Number of bytes to allocate for strings. + string_bytes: usize = string_bytes_default, + pub const Adjustment = struct { cols: ?size.CellCountInt = null, }; @@ -1089,7 +1130,8 @@ pub const Capacity = struct { // for rows & cells (which will allow us to calculate the number of // rows we can fit at a certain column width) we need to layout the // "meta" members of the page (i.e. everything else) from the end. - const grapheme_map_start = alignBackward(usize, layout.total_size - layout.grapheme_map_layout.total_size, GraphemeMap.base_align); + const string_alloc_start = alignBackward(usize, layout.total_size, StringAlloc.base_align); + const grapheme_map_start = alignBackward(usize, string_alloc_start - layout.grapheme_map_layout.total_size, GraphemeMap.base_align); const grapheme_alloc_start = alignBackward(usize, grapheme_map_start - layout.grapheme_alloc_layout.total_size, GraphemeAlloc.base_align); const styles_start = alignBackward(usize, grapheme_alloc_start - layout.styles_layout.total_size, style.Set.base_align);