mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
terminal: PageSize adjustCapacity for non-standard pages
This commit is contained in:
@ -34,12 +34,13 @@ const List = std.DoublyLinkedList(Page);
|
|||||||
const NodePool = std.heap.MemoryPool(List.Node);
|
const NodePool = std.heap.MemoryPool(List.Node);
|
||||||
|
|
||||||
const std_capacity = pagepkg.std_capacity;
|
const std_capacity = pagepkg.std_capacity;
|
||||||
|
const std_size = Page.layout(std_capacity).total_size;
|
||||||
|
|
||||||
/// The memory pool we use for page memory buffers. We use a separate pool
|
/// The memory pool we use for page memory buffers. We use a separate pool
|
||||||
/// so we can allocate these with a page allocator. We have to use a page
|
/// so we can allocate these with a page allocator. We have to use a page
|
||||||
/// allocator because we need memory that is zero-initialized and page-aligned.
|
/// allocator because we need memory that is zero-initialized and page-aligned.
|
||||||
const PagePool = std.heap.MemoryPoolAligned(
|
const PagePool = std.heap.MemoryPoolAligned(
|
||||||
[Page.layout(std_capacity).total_size]u8,
|
[std_size]u8,
|
||||||
std.mem.page_size,
|
std.mem.page_size,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -234,6 +235,16 @@ pub fn deinit(self: *PageList) void {
|
|||||||
// Always deallocate our hashmap.
|
// Always deallocate our hashmap.
|
||||||
self.tracked_pins.deinit(self.pool.alloc);
|
self.tracked_pins.deinit(self.pool.alloc);
|
||||||
|
|
||||||
|
// Go through our linked list and deallocate all pages that are
|
||||||
|
// not standard size.
|
||||||
|
const page_alloc = self.pool.pages.arena.child_allocator;
|
||||||
|
var it = self.pages.first;
|
||||||
|
while (it) |node| : (it = node.next) {
|
||||||
|
if (node.data.memory.len > std_size) {
|
||||||
|
page_alloc.free(node.data.memory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Deallocate all the pages. We don't need to deallocate the list or
|
// Deallocate all the pages. We don't need to deallocate the list or
|
||||||
// nodes because they all reside in the pool.
|
// nodes because they all reside in the pool.
|
||||||
if (self.pool_owned) {
|
if (self.pool_owned) {
|
||||||
@ -1500,28 +1511,95 @@ pub fn grow(self: *PageList) !?*List.Node {
|
|||||||
return next_page;
|
return next_page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adjust the capacity of the given page in the list.
|
||||||
|
pub const AdjustCapacity = struct {
|
||||||
|
/// Adjust the number of styles in the page. This may be
|
||||||
|
/// rounded up if necessary to fit alignment requirements,
|
||||||
|
/// but it will never be rounded down.
|
||||||
|
styles: ?u16 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Adjust the capcaity of the given page in the list. This should
|
||||||
|
/// be used in cases where OutOfMemory is returned by some operation
|
||||||
|
/// i.e to increase style counts, grapheme counts, etc.
|
||||||
|
///
|
||||||
|
/// Adjustment works by increasing the capacity of the desired
|
||||||
|
/// dimension to a certain amount and increases the memory allocation
|
||||||
|
/// requirement for the backing memory of the page. We currently
|
||||||
|
/// never split pages or anything like that. Because increased allocation
|
||||||
|
/// has to happen outside our memory pool, its generally much slower
|
||||||
|
/// so pages should be sized to be large enough to handle all but
|
||||||
|
/// exceptional cases.
|
||||||
|
///
|
||||||
|
/// This can currently only INCREASE capacity size. It cannot
|
||||||
|
/// decrease capacity size. This limitation is only because we haven't
|
||||||
|
/// yet needed that use case. If we ever do, this can be added. Currently
|
||||||
|
/// any requests to decrease will be ignored.
|
||||||
|
pub fn adjustCapacity(
|
||||||
|
self: *PageList,
|
||||||
|
page: *List.Node,
|
||||||
|
adjustment: AdjustCapacity,
|
||||||
|
) !void {
|
||||||
|
// We always use our base capacity which is our standard
|
||||||
|
// adjusted for our column size.
|
||||||
|
var cap = try std_capacity.adjust(.{ .cols = self.cols });
|
||||||
|
|
||||||
|
// From there, we increase our capacity as required
|
||||||
|
if (adjustment.styles) |v| {
|
||||||
|
const aligned = try std.math.ceilPowerOfTwo(u16, v);
|
||||||
|
cap.styles = @max(cap.styles, aligned);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our new page and clone the old page into it.
|
||||||
|
const new_page = try self.createPage(cap);
|
||||||
|
errdefer self.destroyPage(new_page);
|
||||||
|
assert(new_page.data.capacity.rows >= page.data.capacity.rows);
|
||||||
|
new_page.data.size.rows = page.data.size.rows;
|
||||||
|
try new_page.data.cloneFrom(&page.data, 0, page.data.size.rows);
|
||||||
|
|
||||||
|
// Insert this page and destroy the old page
|
||||||
|
self.pages.insertBefore(page, new_page);
|
||||||
|
self.pages.remove(page);
|
||||||
|
self.destroyPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new page node. This does not add it to the list and this
|
/// Create a new page node. This does not add it to the list and this
|
||||||
/// does not do any memory size accounting with max_size/page_size.
|
/// does not do any memory size accounting with max_size/page_size.
|
||||||
fn createPage(self: *PageList, cap: Capacity) !*List.Node {
|
fn createPage(self: *PageList, cap: Capacity) !*List.Node {
|
||||||
var page = try self.pool.nodes.create();
|
var page = try self.pool.nodes.create();
|
||||||
errdefer self.pool.nodes.destroy(page);
|
errdefer self.pool.nodes.destroy(page);
|
||||||
|
|
||||||
const page_buf = try self.pool.pages.create();
|
const layout = Page.layout(cap);
|
||||||
errdefer self.pool.pages.destroy(page_buf);
|
const pooled = layout.total_size <= std_size;
|
||||||
|
const page_alloc = self.pool.pages.arena.child_allocator;
|
||||||
|
|
||||||
|
// Our page buffer comes from our standard memory pool if it
|
||||||
|
// is within our standard size since this is what the pool
|
||||||
|
// dispenses. Otherwise, we use the heap allocator to allocate.
|
||||||
|
const page_buf = if (pooled)
|
||||||
|
try self.pool.pages.create()
|
||||||
|
else
|
||||||
|
try page_alloc.alignedAlloc(
|
||||||
|
u8,
|
||||||
|
std.mem.page_size,
|
||||||
|
layout.total_size,
|
||||||
|
);
|
||||||
|
errdefer if (pooled)
|
||||||
|
self.pool.pages.destroy(page_buf)
|
||||||
|
else
|
||||||
|
page_alloc.free(page_buf);
|
||||||
|
|
||||||
|
// Required only with runtime safety because allocators initialize
|
||||||
|
// to undefined, 0xAA.
|
||||||
if (comptime std.debug.runtime_safety) @memset(page_buf, 0);
|
if (comptime std.debug.runtime_safety) @memset(page_buf, 0);
|
||||||
|
|
||||||
page.* = .{
|
page.* = .{ .data = Page.initBuf(OffsetBuf.init(page_buf), layout) };
|
||||||
.data = Page.initBuf(
|
|
||||||
OffsetBuf.init(page_buf),
|
|
||||||
Page.layout(cap),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
page.data.size.rows = 0;
|
page.data.size.rows = 0;
|
||||||
|
|
||||||
// Accumulate page size now. We don't assert or check max size because
|
// Accumulate page size now. We don't assert or check max size because
|
||||||
// we may exceed it here temporarily as we are allocating pages before
|
// we may exceed it here temporarily as we are allocating pages before
|
||||||
// destroy.
|
// destroy.
|
||||||
self.page_size += PagePool.item_size;
|
self.page_size += page_buf.len;
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
@ -1529,15 +1607,19 @@ fn createPage(self: *PageList, cap: Capacity) !*List.Node {
|
|||||||
/// Destroy the memory of the given page and return it to the pool. The
|
/// Destroy the memory of the given page and return it to the pool. The
|
||||||
/// page is assumed to already be removed from the linked list.
|
/// page is assumed to already be removed from the linked list.
|
||||||
fn destroyPage(self: *PageList, page: *List.Node) void {
|
fn destroyPage(self: *PageList, page: *List.Node) void {
|
||||||
|
// Update our accounting for page size
|
||||||
|
self.page_size -= page.data.memory.len;
|
||||||
|
|
||||||
|
if (page.data.memory.len <= std_size) {
|
||||||
// Reset the memory to zero so it can be reused
|
// Reset the memory to zero so it can be reused
|
||||||
@memset(page.data.memory, 0);
|
@memset(page.data.memory, 0);
|
||||||
|
|
||||||
// Put it back into the allocator pool
|
|
||||||
self.pool.pages.destroy(@ptrCast(page.data.memory.ptr));
|
self.pool.pages.destroy(@ptrCast(page.data.memory.ptr));
|
||||||
self.pool.nodes.destroy(page);
|
} else {
|
||||||
|
const page_alloc = self.pool.pages.arena.child_allocator;
|
||||||
|
page_alloc.free(page.data.memory);
|
||||||
|
}
|
||||||
|
|
||||||
// Update our accounting for page size
|
self.pool.nodes.destroy(page);
|
||||||
self.page_size -= PagePool.item_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Erase the rows from the given top to bottom (inclusive). Erasing
|
/// Erase the rows from the given top to bottom (inclusive). Erasing
|
||||||
@ -3000,6 +3082,49 @@ test "PageList grow prune scrollback" {
|
|||||||
try testing.expectEqual(page1_node, s.pages.last.?);
|
try testing.expectEqual(page1_node, s.pages.last.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "PageList adjustCapacity to increase styles" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var s = try init(alloc, 2, 2, 0);
|
||||||
|
defer s.deinit();
|
||||||
|
{
|
||||||
|
try testing.expect(s.pages.first == s.pages.last);
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
|
||||||
|
// Write all our data so we can assert its the same after
|
||||||
|
for (0..s.rows) |y| {
|
||||||
|
for (0..s.cols) |x| {
|
||||||
|
const rac = page.getRowAndCell(x, y);
|
||||||
|
rac.cell.* = .{
|
||||||
|
.content_tag = .codepoint,
|
||||||
|
.content = .{ .codepoint = @intCast(x) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase our styles
|
||||||
|
try s.adjustCapacity(
|
||||||
|
s.pages.first.?,
|
||||||
|
.{ .styles = std_capacity.styles * 2 },
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
try testing.expect(s.pages.first == s.pages.last);
|
||||||
|
const page = &s.pages.first.?.data;
|
||||||
|
for (0..s.rows) |y| {
|
||||||
|
for (0..s.cols) |x| {
|
||||||
|
const rac = page.getRowAndCell(x, y);
|
||||||
|
try testing.expectEqual(
|
||||||
|
@as(u21, @intCast(x)),
|
||||||
|
rac.cell.content.codepoint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "PageList pageIterator single page" {
|
test "PageList pageIterator single page" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
@ -4830,7 +4955,6 @@ test "PageList resize reflow less cols no reflow preserves semantic prompt" {
|
|||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
std.log.warn("GO", .{});
|
|
||||||
var s = try init(alloc, 4, 4, 0);
|
var s = try init(alloc, 4, 4, 0);
|
||||||
defer s.deinit();
|
defer s.deinit();
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user