terminal: cache some selection state to make render state faster

pull/9662/head
Mitchell Hashimoto 2025-11-20 21:11:03 -08:00
parent 7728620ea8
commit c892599385
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
1 changed files with 43 additions and 11 deletions

View File

@ -8,6 +8,7 @@ const point = @import("point.zig");
const size = @import("size.zig");
const page = @import("page.zig");
const PageList = @import("PageList.zig");
const Selection = @import("Selection.zig");
const Screen = @import("Screen.zig");
const ScreenSet = @import("ScreenSet.zig");
const Style = @import("style.zig").Style;
@ -74,6 +75,10 @@ pub const RenderState = struct {
/// values for comparison.
viewport_pin: ?PageList.Pin = null,
/// The cached selection so we can avoid expensive selection calculations
/// if possible.
selection_cache: ?SelectionCache = null,
/// Initial state.
pub const empty: RenderState = .{
.rows = 0,
@ -193,6 +198,12 @@ pub const RenderState = struct {
full,
};
const SelectionCache = struct {
selection: Selection,
tl_pin: PageList.Pin,
br_pin: PageList.Pin,
};
pub fn deinit(self: *RenderState, alloc: Allocator) void {
for (
self.row_data.items(.arena),
@ -506,21 +517,42 @@ pub const RenderState = struct {
// There are performance improvements that can be made here, though.
// For example, `containedRow` recalculates a bunch of information
// we can cache.
if (s.selection) |*sel| {
if (s.selection) |*sel| selection: {
@branchHint(.unlikely);
// Go through each row and check for containment.
// Populate our selection cache to avoid some expensive
// recalculation.
const cache: *const SelectionCache = cache: {
if (self.selection_cache) |*c| cache_check: {
// If we're redrawing, we recalculate the cache just to
// be safe.
if (redraw) break :cache_check;
// TODO:
// - Cache the selection (untracked) so we can avoid redoing
// this expensive work every frame.
// If our selection isn't equal, we aren't cached!
if (!c.selection.eql(sel.*)) break :cache_check;
// If we have no dirty rows, we can not recalculate.
if (!any_dirty) break :selection;
// We have dirty rows, we can utilize the cache.
break :cache c;
}
// Create a new cache
const tl_pin = sel.topLeft(s);
const br_pin = sel.bottomRight(s);
self.selection_cache = .{
.selection = .init(tl_pin, br_pin, sel.rectangle),
.tl_pin = tl_pin,
.br_pin = br_pin,
};
break :cache &self.selection_cache.?;
};
// Grab the inefficient data we need from the selection. At
// least we can cache it.
const tl_pin = sel.topLeft(s);
const br_pin = sel.bottomRight(s);
const tl = s.pages.pointFromPin(.screen, tl_pin).?.screen;
const br = s.pages.pointFromPin(.screen, br_pin).?.screen;
const tl = s.pages.pointFromPin(.screen, cache.tl_pin).?.screen;
const br = s.pages.pointFromPin(.screen, cache.br_pin).?.screen;
// We need to determine if our selection is within the viewport.
// The viewport is generally very small so the efficient way to
@ -533,8 +565,8 @@ pub const RenderState = struct {
const p = s.pages.pointFromPin(.screen, pin).?.screen;
const row_sel = sel.containedRowCached(
s,
tl_pin,
br_pin,
cache.tl_pin,
cache.br_pin,
pin,
tl,
br,