a73x

1c051843

Fix cursor cache invalidation contract

a73x   2026-04-08 19:24


diff --git a/src/main.zig b/src/main.zig
index 36cb8a9..08690d5 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -711,12 +711,21 @@ 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 "rebuildCursorInstances emits visible cursor instance without changing layout state when count is unchanged" {
test "rebuildCursorInstances invalidates packed cursor state when count is unchanged" {
    var cache = RenderCache.empty;
    defer cache.deinit(std.testing.allocator);

    cache.cursor_instances = try makeTestInstances(std.testing.allocator, 1);
    cache.cursor_instances.items[0].cell_pos = .{ 99.0, 99.0 };
    try cache.packed_instances.append(std.testing.allocator, .{
        .cell_pos = .{ 11.0, 11.0 },
        .glyph_size = .{ 1.0, 1.0 },
        .glyph_bearing = .{ 0.0, 0.0 },
        .uv_rect = .{ 0.0, 0.0, 0.0, 0.0 },
        .fg = .{ 0.0, 0.0, 0.0, 0.0 },
        .bg = .{ 0.0, 0.0, 0.0, 0.0 },
    });
    cache.total_instance_count = 4;
    cache.layout_dirty = false;

    var cursor_instances = try makeTestInstances(std.testing.allocator, 1);
@@ -726,22 +735,37 @@ test "rebuildCursorInstances emits visible cursor instance without changing layo
    const rebuilt = try cache.rebuildCursorInstances(std.testing.allocator, cursor_instances.items);

    try std.testing.expect(!rebuilt.len_changed);
    try std.testing.expect(rebuilt.packed_invalidated);
    try std.testing.expectEqual(@as(usize, 1), cache.cursor_instances.items.len);
    try std.testing.expectEqualDeep([2]f32{ 4.0, 7.0 }, cache.cursor_instances.items[0].cell_pos);
    try std.testing.expect(!cache.layout_dirty);
    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);
}

test "rebuildCursorInstances marks layout dirty when cursor instance count changes" {
test "rebuildCursorInstances invalidates packed cursor state when cursor instance count changes" {
    var cache = RenderCache.empty;
    defer cache.deinit(std.testing.allocator);

    cache.cursor_instances = try makeTestInstances(std.testing.allocator, 1);
    try cache.packed_instances.append(std.testing.allocator, .{
        .cell_pos = .{ 13.0, 13.0 },
        .glyph_size = .{ 1.0, 1.0 },
        .glyph_bearing = .{ 0.0, 0.0 },
        .uv_rect = .{ 0.0, 0.0, 0.0, 0.0 },
        .fg = .{ 0.0, 0.0, 0.0, 0.0 },
        .bg = .{ 0.0, 0.0, 0.0, 0.0 },
    });
    cache.total_instance_count = 4;
    cache.layout_dirty = false;

    const rebuilt = try cache.rebuildCursorInstances(std.testing.allocator, &.{});

    try std.testing.expect(rebuilt.len_changed);
    try std.testing.expect(rebuilt.packed_invalidated);
    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);
}

@@ -1051,16 +1075,18 @@ const RenderCache = struct {
        self: *RenderCache,
        alloc: std.mem.Allocator,
        cursor_instances: []const renderer.Instance,
    ) !RowRebuildResult {
    ) !CursorRebuildResult {
        const old_len = self.cursor_instances.items.len;
        self.cursor_instances.clearRetainingCapacity();
        try self.cursor_instances.appendSlice(alloc, cursor_instances);

        const len_changed = markLayoutDirtyOnLenChange(old_len, self.cursor_instances.items.len);
        if (len_changed) self.layout_dirty = true;
        self.packed_instances.clearRetainingCapacity();
        self.total_instance_count = 0;
        self.layout_dirty = true;

        return .{
            .len_changed = len_changed,
            .len_changed = markLayoutDirtyOnLenChange(old_len, self.cursor_instances.items.len),
            .packed_invalidated = true,
        };
    }
};
@@ -1075,6 +1101,11 @@ const RowRebuildResult = struct {
    len_changed: bool,
};

const CursorRebuildResult = struct {
    len_changed: bool,
    packed_invalidated: bool,
};

fn repackRowCaches(
    alloc: std.mem.Allocator,
    packed_instances: *std.ArrayListUnmanaged(renderer.Instance),