a73x

8975cd18

Add dirty-row refresh planning helpers

a73x   2026-04-08 18:25

Commit message
Add dirty-row refresh planning helpers

src/main.zig
Old New
@@ -367,6 +367,46 @@ fn shouldRenderFrame(terminal_dirty: bool, window_dirty: bool, forced: bool) boo
367 return terminal_dirty or window_dirty or forced; 367 return terminal_dirty or window_dirty or forced;
368 } 368 }
369 369
370 const RowRefreshState = enum {
371 full,
372 partial,
373 };
374
375 const RowRefreshContext = struct {
376 cursor_changed: bool,
377 old_cursor_row: ?usize,
378 new_cursor_row: ?usize,
379 };
380
381 const RowRefreshPlan = struct {
382 full_rebuild: bool,
383 cursor_rebuild: bool,
384 rows_to_rebuild: std.StaticBitSet(256),
385 };
386
387 fn planRowRefresh(
388 state: RowRefreshState,
389 dirty_rows: []const bool,
390 ctx: RowRefreshContext,
391 ) RowRefreshPlan {
392 var rows_to_rebuild = std.StaticBitSet(256).initEmpty();
393
394 const full_rebuild = state == .full;
395 if (!full_rebuild) {
396 const limit = @min(dirty_rows.len, rows_to_rebuild.capacity());
397 var row_idx: usize = 0;
398 while (row_idx < limit) : (row_idx += 1) {
399 if (dirty_rows[row_idx]) rows_to_rebuild.set(row_idx);
400 }
401 }
402
403 return .{
404 .full_rebuild = full_rebuild,
405 .cursor_rebuild = ctx.cursor_changed,
406 .rows_to_rebuild = rows_to_rebuild,
407 };
408 }
409
370 fn appendCellInstances( 410 fn appendCellInstances(
371 alloc: std.mem.Allocator, 411 alloc: std.mem.Allocator,
372 instances: *std.ArrayListUnmanaged(renderer.Instance), 412 instances: *std.ArrayListUnmanaged(renderer.Instance),
@@ -476,6 +516,42 @@ test "event loop redraws only when terminal or window state changed" {
476 try std.testing.expect(!shouldRenderFrame(false, false, false)); 516 try std.testing.expect(!shouldRenderFrame(false, false, false));
477 } 517 }
478 518
519 test "planRowRefresh requests full rebuild for full dirty state" {
520 const plan = planRowRefresh(.full, &.{ false, true, false }, .{
521 .cursor_changed = false,
522 .old_cursor_row = null,
523 .new_cursor_row = null,
524 });
525
526 try std.testing.expect(plan.full_rebuild);
527 try std.testing.expectEqual(@as(usize, 0), plan.rows_to_rebuild.count());
528 }
529
530 test "planRowRefresh selects only dirty rows for partial state" {
531 const plan = planRowRefresh(.partial, &.{ false, true, false, true }, .{
532 .cursor_changed = false,
533 .old_cursor_row = null,
534 .new_cursor_row = null,
535 });
536
537 try std.testing.expect(!plan.full_rebuild);
538 try std.testing.expect(plan.rows_to_rebuild.isSet(1));
539 try std.testing.expect(plan.rows_to_rebuild.isSet(3));
540 try std.testing.expect(!plan.rows_to_rebuild.isSet(0));
541 }
542
543 test "planRowRefresh handles cursor-only updates without unrelated rows" {
544 const plan = planRowRefresh(.partial, &.{ false, false, false }, .{
545 .cursor_changed = true,
546 .old_cursor_row = 1,
547 .new_cursor_row = 2,
548 });
549
550 try std.testing.expect(!plan.full_rebuild);
551 try std.testing.expect(plan.cursor_rebuild);
552 try std.testing.expectEqual(@as(usize, 0), plan.rows_to_rebuild.count());
553 }
554
479 fn runDrawSmokeTest(alloc: std.mem.Allocator) !void { 555 fn runDrawSmokeTest(alloc: std.mem.Allocator) !void {
480 var conn = try wayland_client.Connection.init(); 556 var conn = try wayland_client.Connection.init();
481 defer conn.deinit(); 557 defer conn.deinit();