vt: decouple row iterator allocation from population

Change row_iterator_new to only allocate with undefined fields,
matching the pattern used by row_cells_new. The iterator is now
populated via the render state get API with a new .row_iterator
data kind, which slices the row data and resets y to null.

This separates the lifetime of the opaque handle from the render
state it iterates, letting callers allocate once and re-populate
from different states without reallocating.
This commit is contained in:
Mitchell Hashimoto
2026-03-20 07:29:21 -07:00
parent 75b49051a3
commit ecd1d0d1e1
2 changed files with 55 additions and 61 deletions

View File

@@ -119,6 +119,13 @@ typedef enum {
/** Current dirty state (GhosttyRenderStateDirty). */
GHOSTTY_RENDER_STATE_DATA_DIRTY = 3,
/** Populate a pre-allocated GhosttyRenderStateRowIterator with row data
* from the render state (GhosttyRenderStateRowIterator). Row data is
* only valid as long as the underlying render state is not updated.
* It is unsafe to use row data after updating the render state.
* */
GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR = 4,
} GhosttyRenderStateData;
/**
@@ -147,7 +154,9 @@ typedef enum {
GHOSTTY_RENDER_STATE_ROW_DATA_RAW = 2,
/** Populate a pre-allocated GhosttyRenderStateRowCells with cell data for
* the current row (GhosttyRenderStateRowCells). */
* the current row (GhosttyRenderStateRowCells). Cell data is only
* valid as long as the underlying render state is not updated.
* It is unsafe to use cell data after updating the render state. */
GHOSTTY_RENDER_STATE_ROW_DATA_CELLS = 3,
} GhosttyRenderStateRowData;
@@ -298,21 +307,21 @@ GhosttyResult ghostty_render_state_colors_get(GhosttyRenderState state,
GhosttyRenderStateColors* out_colors);
/**
* Create a row iterator for a render state.
* Create a new row iterator instance.
*
* The iterator borrows from `state`; `state` must outlive the iterator.
* All fields except the allocator are left undefined until populated
* via ghostty_render_state_get() with
* GHOSTTY_RENDER_STATE_DATA_ROW_ITERATOR.
*
* @param allocator Pointer to allocator, or NULL to use the default allocator
* @param state The render state handle to iterate (NULL returns GHOSTTY_INVALID_VALUE)
* @param[out] out_iterator On success, receives the created iterator handle
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if `state` is
* NULL, GHOSTTY_OUT_OF_MEMORY on allocation failure
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY on allocation
* failure
*
* @ingroup render
*/
GhosttyResult ghostty_render_state_row_iterator_new(
const GhosttyAllocator* allocator,
GhosttyRenderState state,
GhosttyRenderStateRowIterator* out_iterator);
/**

View File

@@ -58,6 +58,7 @@ pub const Data = enum(c_int) {
cols = 1,
rows = 2,
dirty = 3,
row_iterator = 4,
/// Output type expected for querying the data of the given kind.
pub fn OutType(comptime self: Data) type {
@@ -65,6 +66,7 @@ pub const Data = enum(c_int) {
.invalid => void,
.cols, .rows => size.CellCountInt,
.dirty => Dirty,
.row_iterator => RowIterator,
};
}
};
@@ -163,6 +165,17 @@ fn getTyped(
.cols => out.* = state.state.cols,
.rows => out.* = state.state.rows,
.dirty => out.* = state.state.dirty,
.row_iterator => {
const it = out.* orelse return .invalid_value;
const row_data = state.state.row_data.slice();
it.* = .{
.alloc = it.alloc,
.y = null,
.raws = row_data.items(.raw),
.cells = row_data.items(.cells),
.dirty = row_data.items(.dirty),
};
},
}
return .success;
@@ -266,43 +279,22 @@ pub fn colors_get(
pub fn row_iterator_new(
alloc_: ?*const CAllocator,
state_: RenderState,
out_iterator_: ?*RowIterator,
result: *RowIterator,
) callconv(.c) Result {
const state = state_ orelse return .invalid_value;
const out_iterator = out_iterator_ orelse return .invalid_value;
const alloc = lib_alloc.default(alloc_);
out_iterator.* = row_iterator_new_(
alloc,
state,
) catch |err| {
out_iterator.* = null;
switch (err) {
error.OutOfMemory => return .out_of_memory,
}
const ptr = alloc.create(RowIteratorWrapper) catch {
result.* = null;
return .out_of_memory;
};
return .success;
}
fn row_iterator_new_(
alloc: Allocator,
state: *RenderStateWrapper,
) !*RowIteratorWrapper {
const it = try alloc.create(RowIteratorWrapper);
errdefer alloc.destroy(it);
const row_data = state.state.row_data.slice();
it.* = .{
ptr.* = .{
.alloc = alloc,
.y = null,
.raws = row_data.items(.raw),
.cells = row_data.items(.cells),
.dirty = row_data.items(.dirty),
.y = undefined,
.raws = undefined,
.cells = undefined,
.dirty = undefined,
};
return it;
result.* = ptr;
return .success;
}
pub fn row_iterator_free(iterator_: RowIterator) callconv(.c) void {
@@ -559,25 +551,15 @@ test "render: set null value" {
try testing.expectEqual(Result.invalid_value, set(state, .dirty, null));
}
test "render: row iterator new invalid value" {
var state: RenderState = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&state,
));
defer free(state);
test "render: row iterator get invalid value" {
var iterator: RowIterator = null;
try testing.expectEqual(Result.invalid_value, row_iterator_new(
try testing.expectEqual(Result.success, row_iterator_new(
&lib_alloc.test_allocator,
null,
&iterator,
));
try testing.expectEqual(Result.invalid_value, row_iterator_new(
&lib_alloc.test_allocator,
state,
null,
));
defer row_iterator_free(iterator);
try testing.expectEqual(Result.invalid_value, get(null, .row_iterator, @ptrCast(&iterator)));
}
test "render: row iterator new/free" {
@@ -605,12 +587,14 @@ test "render: row iterator new/free" {
var iterator: RowIterator = null;
try testing.expectEqual(Result.success, row_iterator_new(
&lib_alloc.test_allocator,
state,
&iterator,
));
defer row_iterator_free(iterator);
try testing.expect(iterator != null);
try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&iterator)));
const iterator_ptr = iterator.?;
const row_data = state.?.state.row_data.slice();
@@ -658,11 +642,11 @@ test "render: row get invalid data" {
var iterator: RowIterator = null;
try testing.expectEqual(Result.success, row_iterator_new(
&lib_alloc.test_allocator,
state,
&iterator,
));
defer row_iterator_free(iterator);
try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&iterator)));
try testing.expect(row_iterator_next(iterator));
try testing.expectEqual(Result.invalid_value, row_get(iterator, .invalid, null));
}
@@ -697,11 +681,11 @@ test "render: row set before iteration" {
var iterator: RowIterator = null;
try testing.expectEqual(Result.success, row_iterator_new(
&lib_alloc.test_allocator,
state,
&iterator,
));
defer row_iterator_free(iterator);
try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&iterator)));
const dirty = false;
try testing.expectEqual(Result.invalid_value, row_set(iterator, .dirty, @ptrCast(&dirty)));
}
@@ -731,11 +715,11 @@ test "render: row get before iteration" {
var iterator: RowIterator = null;
try testing.expectEqual(Result.success, row_iterator_new(
&lib_alloc.test_allocator,
state,
&iterator,
));
defer row_iterator_free(iterator);
try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&iterator)));
var dirty: bool = undefined;
try testing.expectEqual(Result.invalid_value, row_get(iterator, .dirty, @ptrCast(&dirty)));
}
@@ -770,11 +754,11 @@ test "render: row get/set dirty" {
var it: RowIterator = null;
try testing.expectEqual(Result.success, row_iterator_new(
&lib_alloc.test_allocator,
state,
&it,
));
defer row_iterator_free(it);
try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&it)));
try testing.expect(row_iterator_next(it));
var dirty: bool = undefined;
try testing.expectEqual(Result.success, row_get(it, .dirty, @ptrCast(&dirty)));
@@ -788,11 +772,11 @@ test "render: row get/set dirty" {
var it2: RowIterator = null;
try testing.expectEqual(Result.success, row_iterator_new(
&lib_alloc.test_allocator,
state,
&it2,
));
defer row_iterator_free(it2);
try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&it2)));
try testing.expect(row_iterator_next(it2));
try testing.expectEqual(Result.success, row_get(it2, .dirty, @ptrCast(&dirty)));
try testing.expect(!dirty);
@@ -823,11 +807,12 @@ test "render: row iterator next" {
var iterator: RowIterator = null;
try testing.expectEqual(Result.success, row_iterator_new(
&lib_alloc.test_allocator,
state,
&iterator,
));
defer row_iterator_free(iterator);
try testing.expectEqual(Result.success, get(state, .row_iterator, @ptrCast(&iterator)));
const rows = state.?.state.rows;
if (rows == 0) {
try testing.expect(!row_iterator_next(iterator));