5e9ae29b
Fix render cache resize invariants
a73x 2026-04-08 18:58
diff --git a/src/main.zig b/src/main.zig index 5455e3c..c98a9d1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -769,6 +769,57 @@ test "RenderCache deinit resets fields after releasing row storage" { try std.testing.expect(cache.layout_dirty); } test "RenderCache resizeRows to zero clears derived state after live row data" { var cache = RenderCache.empty; try cache.resizeRows(std.testing.allocator, 2); cache.rows[0].instances = try makeTestInstances(std.testing.allocator, 1); cache.rows[1].instances = try makeTestInstances(std.testing.allocator, 2); 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; try cache.resizeRows(std.testing.allocator, 0); 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); cache.deinit(std.testing.allocator); } test "RenderCache resizeRows truncates populated tail rows and preserves prefix only" { var cache = RenderCache.empty; defer cache.deinit(std.testing.allocator); try cache.resizeRows(std.testing.allocator, 3); cache.rows[0].instances = try makeTestInstances(std.testing.allocator, 1); cache.rows[1].instances = try makeTestInstances(std.testing.allocator, 2); cache.rows[2].instances = try makeTestInstances(std.testing.allocator, 3); cache.rows[0].gpu_offset_instances = 10; cache.rows[1].gpu_offset_instances = 20; cache.rows[2].gpu_offset_instances = 30; 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, 10), cache.rows[0].gpu_offset_instances); 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, @@ -791,8 +842,8 @@ const RenderCache = struct { fn resizeRows(self: *RenderCache, alloc: std.mem.Allocator, row_count: usize) !void { if (self.rows.len == row_count) return; const old_rows = self.rows; 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) { @@ -801,23 +852,22 @@ const RenderCache = struct { alloc.free(old_rows); } self.rows = &.{}; self.invalidateAfterResize(); 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]); var row_idx: usize = 0; while (row_idx < copy_len) : (row_idx += 1) { // Preserve only the surviving prefix by moving ownership row-by-row. new_rows[row_idx] = old_rows[row_idx]; old_rows[row_idx] = .{}; } 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); } while (row_idx < old_rows.len) : (row_idx += 1) { old_rows[row_idx].deinit(alloc); } if (old_rows.len > 0) { @@ -825,6 +875,7 @@ const RenderCache = struct { } self.rows = new_rows; self.invalidateAfterResize(); } fn deinit(self: *RenderCache, alloc: std.mem.Allocator) void { @@ -836,6 +887,13 @@ const RenderCache = struct { self.packed_instances.deinit(alloc); self.* = .{}; } fn invalidateAfterResize(self: *RenderCache) void { self.cursor_instances.clearRetainingCapacity(); self.packed_instances.clearRetainingCapacity(); self.total_instance_count = 0; self.layout_dirty = true; } }; const RowPackResult = struct {