a73x

7a8bf92e

Add render cache data structures

a73x   2026-04-08 18:52


diff --git a/src/main.zig b/src/main.zig
index 39c40e4..5455e3c 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -719,6 +719,56 @@ test "repackRowCaches keeps cursor span explicit for empty and non-empty cursor 
    try std.testing.expectEqualDeep([2]f32{ 9.0, 10.0 }, packed_instances.items[2].cell_pos);
}

test "RenderCache resizeRows preserves surviving row caches" {
    var cache = RenderCache.empty;
    defer cache.deinit(std.testing.allocator);

    try cache.resizeRows(std.testing.allocator, 2);
    cache.rows[0].instances = try makeTestInstances(std.testing.allocator, 1);
    cache.rows[0].gpu_offset_instances = 17;
    cache.rows[0].gpu_len_instances = 1;

    try cache.resizeRows(std.testing.allocator, 3);
    try std.testing.expectEqual(@as(usize, 3), cache.rows.len);
    try std.testing.expectEqual(@as(usize, 1), cache.rows[0].instances.items.len);
    try std.testing.expectEqual(@as(u32, 17), cache.rows[0].gpu_offset_instances);
    try std.testing.expectEqual(@as(u32, 1), cache.rows[0].gpu_len_instances);
    try std.testing.expectEqual(@as(usize, 0), cache.rows[2].instances.items.len);

    try cache.resizeRows(std.testing.allocator, 1);
    try std.testing.expectEqual(@as(usize, 1), cache.rows.len);
    try std.testing.expectEqual(@as(usize, 1), cache.rows[0].instances.items.len);
    try std.testing.expectEqual(@as(u32, 17), cache.rows[0].gpu_offset_instances);
    try std.testing.expectEqual(@as(u32, 1), cache.rows[0].gpu_len_instances);
}

test "RenderCache deinit resets fields after releasing row storage" {
    var cache = RenderCache.empty;

    try cache.resizeRows(std.testing.allocator, 2);
    cache.rows[0].instances = try makeTestInstances(std.testing.allocator, 2);
    cache.rows[1].instances = try makeTestInstances(std.testing.allocator, 1);

    var cursor_instances = try makeTestInstances(std.testing.allocator, 1);
    defer cursor_instances.deinit(std.testing.allocator);
    try cache.cursor_instances.append(std.testing.allocator, cursor_instances.items[0]);

    var packed_instances = try makeTestInstances(std.testing.allocator, 1);
    defer packed_instances.deinit(std.testing.allocator);
    try cache.packed_instances.append(std.testing.allocator, packed_instances.items[0]);

    cache.total_instance_count = 3;
    cache.layout_dirty = false;

    cache.deinit(std.testing.allocator);

    try std.testing.expectEqual(@as(usize, 0), cache.rows.len);
    try std.testing.expectEqual(@as(usize, 0), cache.cursor_instances.items.len);
    try std.testing.expectEqual(@as(usize, 0), cache.packed_instances.items.len);
    try std.testing.expectEqual(@as(u32, 0), cache.total_instance_count);
    try std.testing.expect(cache.layout_dirty);
}

const RowInstanceCache = struct {
    instances: std.ArrayListUnmanaged(renderer.Instance) = .empty,
    gpu_offset_instances: u32 = 0,
@@ -726,6 +776,65 @@ const RowInstanceCache = struct {

    fn deinit(self: *RowInstanceCache, alloc: std.mem.Allocator) void {
        self.instances.deinit(alloc);
        self.* = .{};
    }
};

const RenderCache = struct {
    rows: []RowInstanceCache = &.{},
    cursor_instances: std.ArrayListUnmanaged(renderer.Instance) = .empty,
    packed_instances: std.ArrayListUnmanaged(renderer.Instance) = .empty,
    total_instance_count: u32 = 0,
    layout_dirty: bool = true,

    const empty: RenderCache = .{};

    fn resizeRows(self: *RenderCache, alloc: std.mem.Allocator, row_count: usize) !void {
        if (self.rows.len == row_count) return;
        if (row_count == 0) {
            const old_rows = self.rows;
            if (old_rows.len > 0) {
                var row_idx: usize = 0;
                while (row_idx < old_rows.len) : (row_idx += 1) {
                    old_rows[row_idx].deinit(alloc);
                }
                alloc.free(old_rows);
            }
            self.rows = &.{};
            return;
        }

        const old_rows = self.rows;
        var new_rows = try alloc.alloc(RowInstanceCache, row_count);
        for (new_rows) |*row| row.* = .{};

        const copy_len = @min(old_rows.len, row_count);
        if (copy_len > 0) {
            @memcpy(new_rows[0..copy_len], old_rows[0..copy_len]);
        }

        if (row_count < old_rows.len) {
            var row_idx = row_count;
            while (row_idx < old_rows.len) : (row_idx += 1) {
                old_rows[row_idx].deinit(alloc);
            }
        }

        if (old_rows.len > 0) {
            alloc.free(old_rows);
        }

        self.rows = new_rows;
    }

    fn deinit(self: *RenderCache, alloc: std.mem.Allocator) void {
        for (self.rows) |*row| row.deinit(alloc);
        if (self.rows.len > 0) {
            alloc.free(self.rows);
        }
        self.cursor_instances.deinit(alloc);
        self.packed_instances.deinit(alloc);
        self.* = .{};
    }
};