summaryrefslogtreecommitdiff
path: root/src/tty.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-06 13:50:54 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commita43dc216f03c0bfed51ca539d027a90adc689ac8 (patch)
treeeb9be20342c4af9773100f3fa0082a4ad30b2c26 /src/tty.zig
parentde4def3548a6729b0dfd2120495a61beef8c8c2c (diff)
downloadpardes-a43dc216f03c0bfed51ca539d027a90adc689ac8.tar.gz
pardes-a43dc216f03c0bfed51ca539d027a90adc689ac8.zip
tty: coalesce queued input events into one frame; tracy zones behind -Dtracy
The tty shell rendered (core.render + full vaxis repaint + tree-sitter re-highlight of the visible window) after EVERY event, so a wheel flick over a highlighted file was 150 re-parses and 150 terminal repaints; the gui shell drains the SDL queue before drawing and never had the issue. Now the tty loop applies every queued input event, then renders once (batch cap 64; pty output keeps a frame per chunk so progress paints and transient-state waits behave exactly as before). Measured with tracy on a 2400-line .zig scroll script: 150 highlightFileRange calls at 1.85ms mean -> 4; render pipeline total 730ms -> 64ms; 364 frames -> 39. Snap suite 18/18. Tracy: -Dtracy=<path-to-tracy-checkout> compiles TracyClient.cpp into the binary and turns src/tracy.zig zones live (default off, all no-ops). Sampling, callstacks, system tracing and the crash handler are disabled: tracy's symbol worker SIGSEGVs on this binary's debug info and its crash handler then parks every thread before the first frame. Capture: tracy-capture -o t.tracy; export: tracy-csvexport t.tracy.
Diffstat (limited to 'src/tty.zig')
-rw-r--r--src/tty.zig126
1 files changed, 75 insertions, 51 deletions
diff --git a/src/tty.zig b/src/tty.zig
index d9821343..3290cde9 100644
--- a/src/tty.zig
+++ b/src/tty.zig
@@ -7,6 +7,7 @@ const posix = std.posix;
const linux = std.os.linux;
const vaxis = @import("vaxis");
const pardes = @import("pardes.zig");
+const tracy = @import("tracy.zig");
extern "c" fn forkpty(amaster: *c_int, name: ?[*:0]u8, termp: ?*const anyopaque, winp: ?*const posix.winsize) c_int;
extern "c" fn execv(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8) c_int;
@@ -87,61 +88,77 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
var pending: ?@TypeOf(Command.value) = .tick;
while (!core.quit) {
- const event = if (pending) |ev| blk: {
+ var event = if (pending) |ev| blk: {
pending = null;
break :blk ev;
} else try loop.nextEvent();
- switch (event) {
- .nop, .tick => {},
- .quit => break,
- .winsize => |ws| {
- try vx.resize(gpa, tty.writer(), ws);
- core.update(.{ .resize = .{ .cols = @intCast(ws.cols), .rows = @intCast(ws.rows) } });
- },
- .pty_read => |pr| {
- core.update(.{ .output = .{ .pane = @intCast(pr.id), .bytes = pr.bytes } });
- gpa.free(pr.bytes);
- },
- .pty_eof => |id| {
- if (ptys[id]) |*pt| {
- _ = linux.close(pt.file.handle);
- ptys[id] = null;
- }
- core.update(.{ .eof = .{ .pane = @intCast(id) } });
- },
- .key_press => |key| core.update(.{ .key = .{
- .cp = mapKey(effCp(key)),
- .text = key.text orelse "",
- .ctrl = key.mods.ctrl,
- .alt = key.mods.alt,
- } }),
- .mouse => |m| {
- const button: ?pardes.Mouse.Button = switch (m.button) {
- .left => .left,
- .middle => .middle,
- .right => .right,
- .wheel_up => .wheel_up,
- .wheel_down => .wheel_down,
- .none => .none, // button-less motion: hover tracking
- else => null,
- };
- if (button) |b| core.update(.{ .mouse = .{
- .button = b,
- .kind = switch (m.type) {
- .press => .press,
- .release => .release,
- .motion => .motion,
- .drag => .drag,
- },
- .col = @intCast(m.col),
- .row = @intCast(m.row),
- } });
- },
- .paste => |bytes| {
- core.update(.{ .paste = bytes });
- gpa.free(@constCast(bytes));
- },
+ const tz_event = tracy.zone(@src(), "event");
+ // Apply every queued INPUT event, then render ONCE — the gui shell
+ // drains SDL's queue the same way. Without this a wheel flick is fifty
+ // full render+repaint (and re-highlight) cycles instead of one.
+ // pty output keeps a frame per chunk: progress paints as it arrives.
+ var stop = false;
+ var batch: usize = 0;
+ while (true) {
+ var output = false;
+ switch (event) {
+ .nop, .tick => {},
+ .quit => stop = true,
+ .winsize => |ws| {
+ try vx.resize(gpa, tty.writer(), ws);
+ core.update(.{ .resize = .{ .cols = @intCast(ws.cols), .rows = @intCast(ws.rows) } });
+ },
+ .pty_read => |pr| {
+ core.update(.{ .output = .{ .pane = @intCast(pr.id), .bytes = pr.bytes } });
+ gpa.free(pr.bytes);
+ output = true;
+ },
+ .pty_eof => |id| {
+ if (ptys[id]) |*pt| {
+ _ = linux.close(pt.file.handle);
+ ptys[id] = null;
+ }
+ core.update(.{ .eof = .{ .pane = @intCast(id) } });
+ },
+ .key_press => |key| core.update(.{ .key = .{
+ .cp = mapKey(effCp(key)),
+ .text = key.text orelse "",
+ .ctrl = key.mods.ctrl,
+ .alt = key.mods.alt,
+ } }),
+ .mouse => |m| {
+ const button: ?pardes.Mouse.Button = switch (m.button) {
+ .left => .left,
+ .middle => .middle,
+ .right => .right,
+ .wheel_up => .wheel_up,
+ .wheel_down => .wheel_down,
+ .none => .none, // button-less motion: hover tracking
+ else => null,
+ };
+ if (button) |b| core.update(.{ .mouse = .{
+ .button = b,
+ .kind = switch (m.type) {
+ .press => .press,
+ .release => .release,
+ .motion => .motion,
+ .drag => .drag,
+ },
+ .col = @intCast(m.col),
+ .row = @intCast(m.row),
+ } });
+ },
+ .paste => |bytes| {
+ core.update(.{ .paste = bytes });
+ gpa.free(@constCast(bytes));
+ },
+ }
+ batch += 1;
+ if (stop or output or batch >= 64) break;
+ event = (try loop.tryEvent()) orelse break;
}
+ tz_event.end();
+ if (stop) break;
drainEffects(core, &ptys, io, gpa, &loop, &vx, &tty, true);
if (core.quit) break;
@@ -158,7 +175,10 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
// ---- render: surface -> vaxis, cell for cell ----
_ = frame_arena.reset(.retain_capacity);
+ const tz_core = tracy.zone(@src(), "core.render");
const surface = try core.render(frame_arena.allocator());
+ tz_core.end();
+ const tz_cells = tracy.zone(@src(), "surface->vaxis");
const win = vx.window();
win.clear();
var y: u16 = 0;
@@ -173,6 +193,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
});
}
}
+ tz_cells.end();
// pixel attachments (kitty graphics): transmit once per pane, then
// re-place every frame (placements aren't persistent)
for (surface.images[0..surface.nimages]) |maybe| {
@@ -191,7 +212,10 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
}
}
if (surface.cursor) |cur| win.showCursor(cur.x, cur.y);
+ const tz_render = tracy.zone(@src(), "vx.render");
try vx.render(tty.writer());
+ tz_render.end();
+ tracy.frameMark();
}
}