mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
terminal/new: page capacity can be adjusted while retaining byte size
This commit is contained in:
@ -42,7 +42,7 @@ const List = std.DoublyLinkedList(Page);
|
|||||||
/// The memory pool we get page nodes from.
|
/// The memory pool we get page nodes from.
|
||||||
const Pool = std.heap.MemoryPool(List.Node);
|
const Pool = std.heap.MemoryPool(List.Node);
|
||||||
|
|
||||||
const std_layout = Page.layout(Page.std_capacity);
|
const std_layout = Page.layout(pagepkg.std_capacity);
|
||||||
const PagePool = std.heap.MemoryPoolAligned([std_layout.total_size]u8, std.mem.page_size);
|
const PagePool = std.heap.MemoryPoolAligned([std_layout.total_size]u8, std.mem.page_size);
|
||||||
|
|
||||||
/// The allocator to use for pages.
|
/// The allocator to use for pages.
|
||||||
|
@ -14,6 +14,18 @@ const hash_map = @import("hash_map.zig");
|
|||||||
const AutoOffsetHashMap = hash_map.AutoOffsetHashMap;
|
const AutoOffsetHashMap = hash_map.AutoOffsetHashMap;
|
||||||
const alignForward = std.mem.alignForward;
|
const alignForward = std.mem.alignForward;
|
||||||
|
|
||||||
|
/// The allocator to use for multi-codepoint grapheme data. We use
|
||||||
|
/// a chunk size of 4 codepoints. It'd be best to set this empirically
|
||||||
|
/// but it is currently set based on vibes. My thinking around 4 codepoints
|
||||||
|
/// is that most skin-tone emoji are <= 4 codepoints, letter combiners
|
||||||
|
/// are usually <= 4 codepoints, and 4 codepoints is a nice power of two
|
||||||
|
/// for alignment.
|
||||||
|
const grapheme_chunk = 4 * @sizeOf(u21);
|
||||||
|
const GraphemeAlloc = BitmapAllocator(grapheme_chunk);
|
||||||
|
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);
|
||||||
|
|
||||||
/// A page represents a specific section of terminal screen. The primary
|
/// 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
|
/// 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
|
/// serialized, copied, etc. as a convenient way to represent a section
|
||||||
@ -80,47 +92,6 @@ pub const Page = struct {
|
|||||||
/// memory and is fixed at page creation time.
|
/// memory and is fixed at page creation time.
|
||||||
capacity: Capacity,
|
capacity: Capacity,
|
||||||
|
|
||||||
/// The allocator to use for multi-codepoint grapheme data. We use
|
|
||||||
/// a chunk size of 4 codepoints. It'd be best to set this empirically
|
|
||||||
/// but it is currently set based on vibes. My thinking around 4 codepoints
|
|
||||||
/// is that most skin-tone emoji are <= 4 codepoints, letter combiners
|
|
||||||
/// are usually <= 4 codepoints, and 4 codepoints is a nice power of two
|
|
||||||
/// for alignment.
|
|
||||||
const grapheme_chunk = 4 * @sizeOf(u21);
|
|
||||||
const GraphemeAlloc = BitmapAllocator(grapheme_chunk);
|
|
||||||
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 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.
|
|
||||||
pub const std_capacity: Capacity = .{
|
|
||||||
.cols = 250,
|
|
||||||
.rows = 250,
|
|
||||||
.styles = 128,
|
|
||||||
.grapheme_bytes = 1024,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The size of this page.
|
|
||||||
pub const Size = struct {
|
|
||||||
cols: size.CellCountInt,
|
|
||||||
rows: size.CellCountInt,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Capacity of this page.
|
|
||||||
pub const Capacity = struct {
|
|
||||||
/// Number of columns and rows we can know about.
|
|
||||||
cols: size.CellCountInt,
|
|
||||||
rows: size.CellCountInt,
|
|
||||||
|
|
||||||
/// Number of unique styles that can be used on this page.
|
|
||||||
styles: u16 = 16,
|
|
||||||
|
|
||||||
/// Number of bytes to allocate for grapheme data.
|
|
||||||
grapheme_bytes: usize = grapheme_bytes_default,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Initialize a new page, allocating the required backing memory.
|
/// Initialize a new page, allocating the required backing memory.
|
||||||
/// The size of the initialized page defaults to the full capacity.
|
/// The size of the initialized page defaults to the full capacity.
|
||||||
///
|
///
|
||||||
@ -188,6 +159,9 @@ pub const Page = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deinitialize the page, freeing any backing memory. Do NOT call
|
||||||
|
/// this if you allocated the backing memory yourself (i.e. you used
|
||||||
|
/// initBuf).
|
||||||
pub fn deinit(self: *Page) void {
|
pub fn deinit(self: *Page) void {
|
||||||
std.os.munmap(self.memory);
|
std.os.munmap(self.memory);
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
@ -230,7 +204,9 @@ pub const Page = struct {
|
|||||||
pub const Layout = struct {
|
pub const Layout = struct {
|
||||||
total_size: usize,
|
total_size: usize,
|
||||||
rows_start: usize,
|
rows_start: usize,
|
||||||
|
rows_size: usize,
|
||||||
cells_start: usize,
|
cells_start: usize,
|
||||||
|
cells_size: usize,
|
||||||
styles_start: usize,
|
styles_start: usize,
|
||||||
styles_layout: style.Set.Layout,
|
styles_layout: style.Set.Layout,
|
||||||
grapheme_alloc_start: usize,
|
grapheme_alloc_start: usize,
|
||||||
@ -243,8 +219,9 @@ pub const Page = struct {
|
|||||||
/// The memory layout for a page given a desired minimum cols
|
/// The memory layout for a page given a desired minimum cols
|
||||||
/// and rows size.
|
/// and rows size.
|
||||||
pub fn layout(cap: Capacity) Layout {
|
pub fn layout(cap: Capacity) Layout {
|
||||||
|
const rows_count: usize = @intCast(cap.rows);
|
||||||
const rows_start = 0;
|
const rows_start = 0;
|
||||||
const rows_end = rows_start + (cap.rows * @sizeOf(Row));
|
const rows_end: usize = rows_start + (rows_count * @sizeOf(Row));
|
||||||
|
|
||||||
const cells_count: usize = @intCast(cap.cols * cap.rows);
|
const cells_count: usize = @intCast(cap.cols * cap.rows);
|
||||||
const cells_start = alignForward(usize, rows_end, @alignOf(Cell));
|
const cells_start = alignForward(usize, rows_end, @alignOf(Cell));
|
||||||
@ -268,7 +245,9 @@ pub const Page = struct {
|
|||||||
return .{
|
return .{
|
||||||
.total_size = total_size,
|
.total_size = total_size,
|
||||||
.rows_start = rows_start,
|
.rows_start = rows_start,
|
||||||
|
.rows_size = rows_end - rows_start,
|
||||||
.cells_start = cells_start,
|
.cells_start = cells_start,
|
||||||
|
.cells_size = cells_end - cells_start,
|
||||||
.styles_start = styles_start,
|
.styles_start = styles_start,
|
||||||
.styles_layout = styles_layout,
|
.styles_layout = styles_layout,
|
||||||
.grapheme_alloc_start = grapheme_alloc_start,
|
.grapheme_alloc_start = grapheme_alloc_start,
|
||||||
@ -280,6 +259,74 @@ 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.
|
||||||
|
pub const std_capacity: Capacity = .{
|
||||||
|
.cols = 250,
|
||||||
|
.rows = 250,
|
||||||
|
.styles = 128,
|
||||||
|
.grapheme_bytes = 1024,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The size of this page.
|
||||||
|
pub const Size = struct {
|
||||||
|
cols: size.CellCountInt,
|
||||||
|
rows: size.CellCountInt,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Capacity of this page.
|
||||||
|
pub const Capacity = struct {
|
||||||
|
/// Number of columns and rows we can know about.
|
||||||
|
cols: size.CellCountInt,
|
||||||
|
rows: size.CellCountInt,
|
||||||
|
|
||||||
|
/// Number of unique styles that can be used on this page.
|
||||||
|
styles: u16 = 16,
|
||||||
|
|
||||||
|
/// Number of bytes to allocate for grapheme data.
|
||||||
|
grapheme_bytes: usize = grapheme_bytes_default,
|
||||||
|
|
||||||
|
pub const Adjustment = struct {
|
||||||
|
cols: ?size.CellCountInt = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Adjust the capacity parameters while retaining the same total size.
|
||||||
|
/// Adjustments always happen by limiting the rows in the page. Everything
|
||||||
|
/// else can grow. If it is impossible to achieve the desired adjustment,
|
||||||
|
/// OutOfMemory is returned.
|
||||||
|
pub fn adjust(self: Capacity, req: Adjustment) Allocator.Error!Capacity {
|
||||||
|
var adjusted = self;
|
||||||
|
if (req.cols) |cols| {
|
||||||
|
// The calculations below only work if cells/rows match size.
|
||||||
|
assert(@sizeOf(Cell) == @sizeOf(Row));
|
||||||
|
|
||||||
|
// total_size = (Nrows * sizeOf(Row)) + (Nrows * Ncells * sizeOf(Cell))
|
||||||
|
// with some algebra:
|
||||||
|
// Nrows = total_size / (sizeOf(Row) + (Ncells * sizeOf(Cell)))
|
||||||
|
const layout = Page.layout(self);
|
||||||
|
const total_size = layout.rows_size + layout.cells_size;
|
||||||
|
const denom = @sizeOf(Row) + (@sizeOf(Cell) * @as(usize, @intCast(cols)));
|
||||||
|
const new_rows = @divFloor(total_size, denom);
|
||||||
|
|
||||||
|
// If our rows go to zero then we can't fit any row metadata
|
||||||
|
// for the desired number of columns.
|
||||||
|
if (new_rows == 0) return error.OutOfMemory;
|
||||||
|
|
||||||
|
adjusted.cols = cols;
|
||||||
|
adjusted.rows = @intCast(new_rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comptime std.debug.runtime_safety) {
|
||||||
|
const old_size = Page.layout(self).total_size;
|
||||||
|
const new_size = Page.layout(adjusted).total_size;
|
||||||
|
assert(new_size == old_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjusted;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Row = packed struct(u64) {
|
pub const Row = packed struct(u64) {
|
||||||
_padding: u29 = 0,
|
_padding: u29 = 0,
|
||||||
|
|
||||||
@ -357,11 +404,43 @@ test "Page std size" {
|
|||||||
// We want to ensure that the standard capacity is what we
|
// We want to ensure that the standard capacity is what we
|
||||||
// expect it to be. Changing this is fine but should be done with care
|
// expect it to be. Changing this is fine but should be done with care
|
||||||
// so we fail a test if it changes.
|
// so we fail a test if it changes.
|
||||||
const total_size = Page.layout(Page.std_capacity).total_size;
|
const total_size = Page.layout(std_capacity).total_size;
|
||||||
try testing.expectEqual(@as(usize, 524_288), total_size); // 512 KiB
|
try testing.expectEqual(@as(usize, 524_288), total_size); // 512 KiB
|
||||||
//const pages = total_size / std.mem.page_size;
|
//const pages = total_size / std.mem.page_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Page capacity adjust cols down" {
|
||||||
|
const original = std_capacity;
|
||||||
|
const original_size = Page.layout(original).total_size;
|
||||||
|
const adjusted = try original.adjust(.{ .cols = original.cols / 2 });
|
||||||
|
const adjusted_size = Page.layout(adjusted).total_size;
|
||||||
|
try testing.expectEqual(original_size, adjusted_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Page capacity adjust cols down to 1" {
|
||||||
|
const original = std_capacity;
|
||||||
|
const original_size = Page.layout(original).total_size;
|
||||||
|
const adjusted = try original.adjust(.{ .cols = 1 });
|
||||||
|
const adjusted_size = Page.layout(adjusted).total_size;
|
||||||
|
try testing.expectEqual(original_size, adjusted_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Page capacity adjust cols up" {
|
||||||
|
const original = std_capacity;
|
||||||
|
const original_size = Page.layout(original).total_size;
|
||||||
|
const adjusted = try original.adjust(.{ .cols = original.cols * 2 });
|
||||||
|
const adjusted_size = Page.layout(adjusted).total_size;
|
||||||
|
try testing.expectEqual(original_size, adjusted_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Page capacity adjust cols too high" {
|
||||||
|
const original = std_capacity;
|
||||||
|
try testing.expectError(
|
||||||
|
error.OutOfMemory,
|
||||||
|
original.adjust(.{ .cols = std.math.maxInt(size.CellCountInt) }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
test "Page init" {
|
test "Page init" {
|
||||||
var page = try Page.init(.{
|
var page = try Page.init(.{
|
||||||
.cols = 120,
|
.cols = 120,
|
||||||
|
Reference in New Issue
Block a user