f4135ed2
Fix dirty-row refresh planning helpers
a73x 2026-04-08 18:28
diff --git a/src/main.zig b/src/main.zig index e0b4313..a0b0ab3 100644 --- a/src/main.zig +++ b/src/main.zig @@ -372,10 +372,15 @@ const RowRefreshState = enum { partial, }; const CursorRefreshContext = struct { old_row: ?usize, new_row: ?usize, old_visible: bool, new_visible: bool, }; const RowRefreshContext = struct { cursor_changed: bool, old_cursor_row: ?usize, new_cursor_row: ?usize, cursor: CursorRefreshContext, }; const RowRefreshPlan = struct { @@ -391,22 +396,27 @@ fn planRowRefresh( ) RowRefreshPlan { var rows_to_rebuild = std.StaticBitSet(256).initEmpty(); const full_rebuild = state == .full; const full_rebuild = state == .full or dirty_rows.len > rows_to_rebuild.capacity(); const cursor_rebuild = full_rebuild or cursorNeedsRebuild(ctx.cursor); if (!full_rebuild) { const limit = @min(dirty_rows.len, rows_to_rebuild.capacity()); var row_idx: usize = 0; while (row_idx < limit) : (row_idx += 1) { while (row_idx < dirty_rows.len) : (row_idx += 1) { if (dirty_rows[row_idx]) rows_to_rebuild.set(row_idx); } } return .{ .full_rebuild = full_rebuild, .cursor_rebuild = ctx.cursor_changed, .cursor_rebuild = cursor_rebuild, .rows_to_rebuild = rows_to_rebuild, }; } fn cursorNeedsRebuild(cursor: CursorRefreshContext) bool { return cursor.old_row != cursor.new_row or cursor.old_visible != cursor.new_visible; } fn appendCellInstances( alloc: std.mem.Allocator, instances: *std.ArrayListUnmanaged(renderer.Instance), @@ -518,33 +528,46 @@ test "event loop redraws only when terminal or window state changed" { test "planRowRefresh requests full rebuild for full dirty state" { const plan = planRowRefresh(.full, &.{ false, true, false }, .{ .cursor_changed = false, .old_cursor_row = null, .new_cursor_row = null, .cursor = .{ .old_row = null, .new_row = null, .old_visible = false, .new_visible = false, }, }); try std.testing.expect(plan.full_rebuild); try std.testing.expect(plan.cursor_rebuild); try std.testing.expectEqual(@as(usize, 0), plan.rows_to_rebuild.count()); } test "planRowRefresh selects only dirty rows for partial state" { const plan = planRowRefresh(.partial, &.{ false, true, false, true }, .{ .cursor_changed = false, .old_cursor_row = null, .new_cursor_row = null, .cursor = .{ .old_row = null, .new_row = null, .old_visible = false, .new_visible = false, }, }); try std.testing.expect(!plan.full_rebuild); try std.testing.expect(!plan.cursor_rebuild); try std.testing.expectEqual(@as(usize, 2), plan.rows_to_rebuild.count()); try std.testing.expect(plan.rows_to_rebuild.isSet(1)); try std.testing.expect(plan.rows_to_rebuild.isSet(3)); try std.testing.expect(!plan.rows_to_rebuild.isSet(0)); try std.testing.expect(!plan.rows_to_rebuild.isSet(2)); } test "planRowRefresh handles cursor-only updates without unrelated rows" { const plan = planRowRefresh(.partial, &.{ false, false, false }, .{ .cursor_changed = true, .old_cursor_row = 1, .new_cursor_row = 2, .cursor = .{ .old_row = 1, .new_row = 2, .old_visible = true, .new_visible = true, }, }); try std.testing.expect(!plan.full_rebuild); @@ -552,6 +575,24 @@ test "planRowRefresh handles cursor-only updates without unrelated rows" { try std.testing.expectEqual(@as(usize, 0), plan.rows_to_rebuild.count()); } test "planRowRefresh forces full rebuild when dirty rows exceed fixed capacity" { var dirty_rows: [257]bool = .{false} ** 257; dirty_rows[256] = true; const plan = planRowRefresh(.partial, dirty_rows[0..], .{ .cursor = .{ .old_row = null, .new_row = null, .old_visible = true, .new_visible = true, }, }); try std.testing.expect(plan.full_rebuild); try std.testing.expect(plan.cursor_rebuild); try std.testing.expectEqual(@as(usize, 0), plan.rows_to_rebuild.count()); } fn runDrawSmokeTest(alloc: std.mem.Allocator) !void { var conn = try wayland_client.Connection.init(); defer conn.deinit();