a73x

917999fd

Fix dirty-row packing contract

a73x   2026-04-08 18:41

Commit message
Fix dirty-row packing contract

src/main.zig
Old New
@@ -640,11 +640,38 @@ test "repackRowCaches assigns contiguous offsets" {
640 var packed_instances: std.ArrayListUnmanaged(renderer.Instance) = .empty; 640 var packed_instances: std.ArrayListUnmanaged(renderer.Instance) = .empty;
641 defer packed_instances.deinit(std.testing.allocator); 641 defer packed_instances.deinit(std.testing.allocator);
642 642
643 const total = try repackRowCaches(std.testing.allocator, &packed_instances, &rows, &.{}); 643 var cursor_instances = try makeTestInstances(std.testing.allocator, 1);
644 defer cursor_instances.deinit(std.testing.allocator);
645
646 rows[0].instances.items[0].cell_pos[1] = 10.0;
647 rows[0].instances.items[1].cell_pos[1] = 10.0;
648 rows[1].instances.items[0].cell_pos[1] = 20.0;
649 rows[1].instances.items[1].cell_pos[1] = 20.0;
650 rows[1].instances.items[2].cell_pos[1] = 20.0;
651 cursor_instances.items[0].cell_pos[0] = 99.0;
652 cursor_instances.items[0].cell_pos[1] = 99.0;
653
654 const packed_result = try repackRowCaches(
655 std.testing.allocator,
656 &packed_instances,
657 &rows,
658 cursor_instances.items,
659 );
644 660
645 try std.testing.expectEqual(@as(u32, 0), rows[0].gpu_offset_instances); 661 try std.testing.expectEqual(@as(u32, 0), rows[0].gpu_offset_instances);
662 try std.testing.expectEqual(@as(u32, 2), rows[0].gpu_len_instances);
646 try std.testing.expectEqual(@as(u32, 2), rows[1].gpu_offset_instances); 663 try std.testing.expectEqual(@as(u32, 2), rows[1].gpu_offset_instances);
647 try std.testing.expectEqual(@as(u32, 5), total); 664 try std.testing.expectEqual(@as(u32, 3), rows[1].gpu_len_instances);
665 try std.testing.expectEqual(@as(u32, 6), packed_result.total_instances);
666 try std.testing.expectEqual(@as(u32, 5), packed_result.cursor_offset_instances);
667 try std.testing.expectEqual(@as(u32, 1), packed_result.cursor_len_instances);
668 try std.testing.expectEqual(@as(usize, 6), packed_instances.items.len);
669 try std.testing.expectEqualDeep([2]f32{ 0.0, 10.0 }, packed_instances.items[0].cell_pos);
670 try std.testing.expectEqualDeep([2]f32{ 1.0, 10.0 }, packed_instances.items[1].cell_pos);
671 try std.testing.expectEqualDeep([2]f32{ 0.0, 20.0 }, packed_instances.items[2].cell_pos);
672 try std.testing.expectEqualDeep([2]f32{ 1.0, 20.0 }, packed_instances.items[3].cell_pos);
673 try std.testing.expectEqualDeep([2]f32{ 2.0, 20.0 }, packed_instances.items[4].cell_pos);
674 try std.testing.expectEqualDeep([2]f32{ 99.0, 99.0 }, packed_instances.items[5].cell_pos);
648 } 675 }
649 676
650 test "markLayoutDirtyOnLenChange returns true when row length changes" { 677 test "markLayoutDirtyOnLenChange returns true when row length changes" {
@@ -652,6 +679,46 @@ test "markLayoutDirtyOnLenChange returns true when row length changes" {
652 try std.testing.expect(!markLayoutDirtyOnLenChange(3, 3)); 679 try std.testing.expect(!markLayoutDirtyOnLenChange(3, 3));
653 } 680 }
654 681
682 test "repackRowCaches keeps cursor span explicit for empty and non-empty cursor instances" {
683 var rows = [_]RowInstanceCache{
684 .{
685 .instances = try makeTestInstances(std.testing.allocator, 1),
686 },
687 };
688 defer for (&rows) |*row| row.instances.deinit(std.testing.allocator);
689
690 var packed_instances: std.ArrayListUnmanaged(renderer.Instance) = .empty;
691 defer packed_instances.deinit(std.testing.allocator);
692
693 const empty_cursor_result = try repackRowCaches(std.testing.allocator, &packed_instances, &rows, &.{});
694 try std.testing.expectEqual(@as(u32, 1), empty_cursor_result.total_instances);
695 try std.testing.expectEqual(@as(u32, 1), empty_cursor_result.cursor_offset_instances);
696 try std.testing.expectEqual(@as(u32, 0), empty_cursor_result.cursor_len_instances);
697
698 var cursor_instances = try makeTestInstances(std.testing.allocator, 2);
699 defer cursor_instances.deinit(std.testing.allocator);
700
701 cursor_instances.items[0].cell_pos[0] = 7.0;
702 cursor_instances.items[0].cell_pos[1] = 8.0;
703 cursor_instances.items[1].cell_pos[0] = 9.0;
704 cursor_instances.items[1].cell_pos[1] = 10.0;
705
706 const non_empty_cursor_result = try repackRowCaches(
707 std.testing.allocator,
708 &packed_instances,
709 &rows,
710 cursor_instances.items,
711 );
712
713 try std.testing.expectEqual(@as(u32, 3), non_empty_cursor_result.total_instances);
714 try std.testing.expectEqual(@as(u32, 1), non_empty_cursor_result.cursor_offset_instances);
715 try std.testing.expectEqual(@as(u32, 2), non_empty_cursor_result.cursor_len_instances);
716 try std.testing.expectEqual(@as(usize, 3), packed_instances.items.len);
717 try std.testing.expectEqualDeep([2]f32{ 0.0, 0.0 }, packed_instances.items[0].cell_pos);
718 try std.testing.expectEqualDeep([2]f32{ 7.0, 8.0 }, packed_instances.items[1].cell_pos);
719 try std.testing.expectEqualDeep([2]f32{ 9.0, 10.0 }, packed_instances.items[2].cell_pos);
720 }
721
655 const RowInstanceCache = struct { 722 const RowInstanceCache = struct {
656 instances: std.ArrayListUnmanaged(renderer.Instance) = .empty, 723 instances: std.ArrayListUnmanaged(renderer.Instance) = .empty,
657 gpu_offset_instances: u32 = 0, 724 gpu_offset_instances: u32 = 0,
@@ -662,24 +729,42 @@ const RowInstanceCache = struct {
662 } 729 }
663 }; 730 };
664 731
732 const RowPackResult = struct {
733 total_instances: u32,
734 cursor_offset_instances: u32,
735 cursor_len_instances: u32,
736 };
737
665 fn repackRowCaches( 738 fn repackRowCaches(
666 alloc: std.mem.Allocator, 739 alloc: std.mem.Allocator,
667 packed_instances: *std.ArrayListUnmanaged(renderer.Instance), 740 packed_instances: *std.ArrayListUnmanaged(renderer.Instance),
668 rows: []RowInstanceCache, 741 rows: []RowInstanceCache,
669 cursor_instances: []const renderer.Instance, 742 cursor_instances: []const renderer.Instance,
670 ) !u32 { 743 ) !RowPackResult {
671 packed_instances.clearRetainingCapacity(); 744 packed_instances.clearRetainingCapacity();
672 745
746 var total_instances: u32 = 0;
747 for (rows) |*row| {
748 try packed_instances.appendSlice(alloc, row.instances.items);
749 total_instances += @intCast(row.instances.items.len);
750 }
751
752 const cursor_offset_instances = total_instances;
753 try packed_instances.appendSlice(alloc, cursor_instances);
754 total_instances += @intCast(cursor_instances.len);
755
673 var offset: u32 = 0; 756 var offset: u32 = 0;
674 for (rows) |*row| { 757 for (rows) |*row| {
675 row.gpu_offset_instances = offset; 758 row.gpu_offset_instances = offset;
676 row.gpu_len_instances = @intCast(row.instances.items.len); 759 row.gpu_len_instances = @intCast(row.instances.items.len);
677 try packed_instances.appendSlice(alloc, row.instances.items);
678 offset += @intCast(row.instances.items.len); 760 offset += @intCast(row.instances.items.len);
679 } 761 }
680 762
681 try packed_instances.appendSlice(alloc, cursor_instances); 763 return .{
682 return offset + @as(u32, @intCast(cursor_instances.len)); 764 .total_instances = total_instances,
765 .cursor_offset_instances = cursor_offset_instances,
766 .cursor_len_instances = @intCast(cursor_instances.len),
767 };
683 } 768 }
684 769
685 fn markLayoutDirtyOnLenChange(old_len: usize, new_len: usize) bool { 770 fn markLayoutDirtyOnLenChange(old_len: usize, new_len: usize) bool {