a73x

1c051843

Fix cursor cache invalidation contract

a73x   2026-04-08 19:24

Commit message
Fix cursor cache invalidation contract

src/main.zig
Old New
@@ -711,12 +711,21 @@ test "repackRowCaches keeps cursor span explicit for empty and non-empty cursor
711 try std.testing.expectEqualDeep([2]f32{ 9.0, 10.0 }, packed_instances.items[2].cell_pos); 711 try std.testing.expectEqualDeep([2]f32{ 9.0, 10.0 }, packed_instances.items[2].cell_pos);
712 } 712 }
713 713
714 test "rebuildCursorInstances emits visible cursor instance without changing layout state when count is unchanged" { 714 test "rebuildCursorInstances invalidates packed cursor state when count is unchanged" {
715 var cache = RenderCache.empty; 715 var cache = RenderCache.empty;
716 defer cache.deinit(std.testing.allocator); 716 defer cache.deinit(std.testing.allocator);
717 717
718 cache.cursor_instances = try makeTestInstances(std.testing.allocator, 1); 718 cache.cursor_instances = try makeTestInstances(std.testing.allocator, 1);
719 cache.cursor_instances.items[0].cell_pos = .{ 99.0, 99.0 }; 719 cache.cursor_instances.items[0].cell_pos = .{ 99.0, 99.0 };
720 try cache.packed_instances.append(std.testing.allocator, .{
721 .cell_pos = .{ 11.0, 11.0 },
722 .glyph_size = .{ 1.0, 1.0 },
723 .glyph_bearing = .{ 0.0, 0.0 },
724 .uv_rect = .{ 0.0, 0.0, 0.0, 0.0 },
725 .fg = .{ 0.0, 0.0, 0.0, 0.0 },
726 .bg = .{ 0.0, 0.0, 0.0, 0.0 },
727 });
728 cache.total_instance_count = 4;
720 cache.layout_dirty = false; 729 cache.layout_dirty = false;
721 730
722 var cursor_instances = try makeTestInstances(std.testing.allocator, 1); 731 var cursor_instances = try makeTestInstances(std.testing.allocator, 1);
@@ -726,22 +735,37 @@ test "rebuildCursorInstances emits visible cursor instance without changing layo
726 const rebuilt = try cache.rebuildCursorInstances(std.testing.allocator, cursor_instances.items); 735 const rebuilt = try cache.rebuildCursorInstances(std.testing.allocator, cursor_instances.items);
727 736
728 try std.testing.expect(!rebuilt.len_changed); 737 try std.testing.expect(!rebuilt.len_changed);
738 try std.testing.expect(rebuilt.packed_invalidated);
729 try std.testing.expectEqual(@as(usize, 1), cache.cursor_instances.items.len); 739 try std.testing.expectEqual(@as(usize, 1), cache.cursor_instances.items.len);
730 try std.testing.expectEqualDeep([2]f32{ 4.0, 7.0 }, cache.cursor_instances.items[0].cell_pos); 740 try std.testing.expectEqualDeep([2]f32{ 4.0, 7.0 }, cache.cursor_instances.items[0].cell_pos);
731 try std.testing.expect(!cache.layout_dirty); 741 try std.testing.expectEqual(@as(usize, 0), cache.packed_instances.items.len);
742 try std.testing.expectEqual(@as(u32, 0), cache.total_instance_count);
743 try std.testing.expect(cache.layout_dirty);
732 } 744 }
733 745
734 test "rebuildCursorInstances marks layout dirty when cursor instance count changes" { 746 test "rebuildCursorInstances invalidates packed cursor state when cursor instance count changes" {
735 var cache = RenderCache.empty; 747 var cache = RenderCache.empty;
736 defer cache.deinit(std.testing.allocator); 748 defer cache.deinit(std.testing.allocator);
737 749
738 cache.cursor_instances = try makeTestInstances(std.testing.allocator, 1); 750 cache.cursor_instances = try makeTestInstances(std.testing.allocator, 1);
751 try cache.packed_instances.append(std.testing.allocator, .{
752 .cell_pos = .{ 13.0, 13.0 },
753 .glyph_size = .{ 1.0, 1.0 },
754 .glyph_bearing = .{ 0.0, 0.0 },
755 .uv_rect = .{ 0.0, 0.0, 0.0, 0.0 },
756 .fg = .{ 0.0, 0.0, 0.0, 0.0 },
757 .bg = .{ 0.0, 0.0, 0.0, 0.0 },
758 });
759 cache.total_instance_count = 4;
739 cache.layout_dirty = false; 760 cache.layout_dirty = false;
740 761
741 const rebuilt = try cache.rebuildCursorInstances(std.testing.allocator, &.{}); 762 const rebuilt = try cache.rebuildCursorInstances(std.testing.allocator, &.{});
742 763
743 try std.testing.expect(rebuilt.len_changed); 764 try std.testing.expect(rebuilt.len_changed);
765 try std.testing.expect(rebuilt.packed_invalidated);
744 try std.testing.expectEqual(@as(usize, 0), cache.cursor_instances.items.len); 766 try std.testing.expectEqual(@as(usize, 0), cache.cursor_instances.items.len);
767 try std.testing.expectEqual(@as(usize, 0), cache.packed_instances.items.len);
768 try std.testing.expectEqual(@as(u32, 0), cache.total_instance_count);
745 try std.testing.expect(cache.layout_dirty); 769 try std.testing.expect(cache.layout_dirty);
746 } 770 }
747 771
@@ -1051,16 +1075,18 @@ const RenderCache = struct {
1051 self: *RenderCache, 1075 self: *RenderCache,
1052 alloc: std.mem.Allocator, 1076 alloc: std.mem.Allocator,
1053 cursor_instances: []const renderer.Instance, 1077 cursor_instances: []const renderer.Instance,
1054 ) !RowRebuildResult { 1078 ) !CursorRebuildResult {
1055 const old_len = self.cursor_instances.items.len; 1079 const old_len = self.cursor_instances.items.len;
1056 self.cursor_instances.clearRetainingCapacity(); 1080 self.cursor_instances.clearRetainingCapacity();
1057 try self.cursor_instances.appendSlice(alloc, cursor_instances); 1081 try self.cursor_instances.appendSlice(alloc, cursor_instances);
1058 1082
1059 const len_changed = markLayoutDirtyOnLenChange(old_len, self.cursor_instances.items.len); 1083 self.packed_instances.clearRetainingCapacity();
1060 if (len_changed) self.layout_dirty = true; 1084 self.total_instance_count = 0;
1085 self.layout_dirty = true;
1061 1086
1062 return .{ 1087 return .{
1063 .len_changed = len_changed, 1088 .len_changed = markLayoutDirtyOnLenChange(old_len, self.cursor_instances.items.len),
1089 .packed_invalidated = true,
1064 }; 1090 };
1065 } 1091 }
1066 }; 1092 };
@@ -1075,6 +1101,11 @@ const RowRebuildResult = struct {
1075 len_changed: bool, 1101 len_changed: bool,
1076 }; 1102 };
1077 1103
1104 const CursorRebuildResult = struct {
1105 len_changed: bool,
1106 packed_invalidated: bool,
1107 };
1108
1078 fn repackRowCaches( 1109 fn repackRowCaches(
1079 alloc: std.mem.Allocator, 1110 alloc: std.mem.Allocator,
1080 packed_instances: *std.ArrayListUnmanaged(renderer.Instance), 1111 packed_instances: *std.ArrayListUnmanaged(renderer.Instance),