terminal: render state dirty state

This commit is contained in:
Mitchell Hashimoto
2025-11-20 20:59:12 -08:00
parent 86fcf9ff4a
commit 7728620ea8
2 changed files with 45 additions and 24 deletions

View File

@@ -937,7 +937,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
/// Mark the full screen as dirty so that we redraw everything.
pub inline fn markDirty(self: *Self) void {
self.terminal_state.redraw = true;
self.terminal_state.dirty = .full;
}
/// Called when we get an updated display ID for our display link.
@@ -2265,7 +2265,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
links: *const terminal.RenderState.CellSet,
) !void {
const state: *terminal.RenderState = &self.terminal_state;
defer state.redraw = false;
defer state.dirty = .false;
self.draw_mutex.lock();
defer self.draw_mutex.unlock();
@@ -2317,7 +2317,7 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
self.uniforms.grid_size = .{ new_size.columns, new_size.rows };
}
const rebuild = state.redraw or grid_size_diff;
const rebuild = state.dirty == .full or grid_size_diff;
if (rebuild) {
// If we are doing a full rebuild, then we clear the entire cell buffer.
self.cells.reset();

View File

@@ -60,11 +60,10 @@ pub const RenderState = struct {
/// use cases.
row_data: std.MultiArrayList(Row),
/// This is set to true if the terminal state has changed in a way
/// that the renderer should do a full redraw of the grid. The renderer
/// should se this to false when it has done so. `update` will only
/// ever tick this to true.
redraw: bool,
/// The dirty state of the render state. This is set by the update method.
/// The renderer/caller should set this to false when it has handled
/// the dirty state.
dirty: Dirty,
/// The screen type that this state represents. This is used primarily
/// to detect changes.
@@ -93,7 +92,7 @@ pub const RenderState = struct {
.style = undefined,
},
.row_data = .empty,
.redraw = false,
.dirty = .false,
.screen = .primary,
};
@@ -179,6 +178,21 @@ pub const RenderState = struct {
style: Style,
};
// Dirty state
pub const Dirty = enum {
/// Not dirty at all. Can skip rendering if prior state was
/// already rendered.
false,
/// Partially dirty. Some rows changed but not all. None of the
/// global state changed such as colors.
partial,
/// Fully dirty. Global state changed or dimensions changed. All rows
/// should be redrawn.
full,
};
pub fn deinit(self: *RenderState, alloc: Allocator) void {
for (
self.row_data.items(.arena),
@@ -238,15 +252,6 @@ pub const RenderState = struct {
break :redraw false;
};
// Full redraw resets our state completely.
if (redraw) {
self.screen = t.screens.active_key;
self.redraw = true;
// Note: we don't clear any row_data here because our rebuild
// below is going to do that for us.
}
// Always set our cheap fields, its more expensive to compare
self.rows = s.pages.rows;
self.cols = s.pages.cols;
@@ -339,6 +344,7 @@ pub const RenderState = struct {
null,
);
var y: size.CellCountInt = 0;
var any_dirty: bool = false;
while (row_it.next()) |row_pin| : (y = y + 1) {
// Find our cursor if we haven't found it yet. We do this even
// if the row is not dirty because the cursor is unrelated.
@@ -390,6 +396,9 @@ pub const RenderState = struct {
continue;
}
// Set that at least one row was dirty.
any_dirty = true;
// Clear our row dirty, we'll clear our page dirty later.
// We can't clear it now because we have more rows to go through.
page_rac.row.dirty = false;
@@ -540,6 +549,18 @@ pub const RenderState = struct {
}
}
// Handle dirty state.
if (redraw) {
// Fully redraw resets some other state.
self.screen = t.screens.active_key;
self.dirty = .full;
// Note: we don't clear any row_data here because our rebuild
// above did this.
} else if (any_dirty and self.dirty == .false) {
self.dirty = .partial;
}
// Finalize our final dirty page
if (last_dirty_page) |last_p| last_p.dirty = false;
@@ -931,19 +952,19 @@ test "dirty state" {
// First update should trigger redraw due to resize
try state.update(alloc, &t);
try testing.expect(state.redraw);
try testing.expectEqual(.full, state.dirty);
// Reset redraw flag and dirty rows
state.redraw = false;
// Reset dirty flag and dirty rows
state.dirty = .false;
{
const row_data = state.row_data.slice();
const dirty = row_data.items(.dirty);
@memset(dirty, false);
}
// Second update with no changes - no redraw, no dirty rows
// Second update with no changes - no dirty rows
try state.update(alloc, &t);
try testing.expect(!state.redraw);
try testing.expectEqual(.false, state.dirty);
{
const row_data = state.row_data.slice();
const dirty = row_data.items(.dirty);
@@ -953,7 +974,7 @@ test "dirty state" {
// Write to first line
try s.nextSlice("A");
try state.update(alloc, &t);
try testing.expect(!state.redraw); // Should not trigger full redraw
try testing.expectEqual(.partial, state.dirty);
{
const row_data = state.row_data.slice();
const dirty = row_data.items(.dirty);