diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 20:20:27 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 7dc3891ed574d6e96548f592b09b0f8abc87ae2a (patch) | |
| tree | 70cddb9ce4a7e9039f8167e806b7e996f94eefb0 /src/ninep/screen.zig | |
| parent | acd9e032a147f4aa7ad49110dc2f4fb945e7a9bb (diff) | |
| download | pardes-7dc3891ed574d6e96548f592b09b0f8abc87ae2a.tar.gz pardes-7dc3891ed574d6e96548f592b09b0f8abc87ae2a.zip | |
Keep what each open holds in one table of open records
Snapshots (and /log's cursor), runs, and the reader_handle constants for
event and pty/data each reused the open handle and each validated
handle and node on its own. Now p.fs.opens is one table of 64 records,
each the node it was opened on and a tagged union of what it holds, like
lib9p's per-fid aux and acme's Fid (editors/acme/dat.h:373-385): one
lookup (openOf), one release, ENFILE when full. A held read lives in
its open's record, so it goes with the release. Opens that hold nothing
answer handle 0 and take no record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/screen.zig')
| -rw-r--r-- | src/ninep/screen.zig | 79 |
1 files changed, 19 insertions, 60 deletions
diff --git a/src/ninep/screen.zig b/src/ninep/screen.zig index 89ca369b..f72043d4 100644 --- a/src/ninep/screen.zig +++ b/src/ninep/screen.zig @@ -1,5 +1,5 @@ -//! /screen, /listeners and the snapshot handles that freeze a screen or a -//! terminal history between open and close. +//! /screen, /listeners and the snapshots that freeze a screen or a terminal +//! history between open and close (tree.zig's open records hold them). const std = @import("std"); const pardes = @import("../pardes.zig"); const panes = @import("../panes.zig"); @@ -11,21 +11,6 @@ const Req = tree.Req; const Reply = tree.Reply; const E = tree.E; -pub const Snapshot = struct { - node: u64 = 0, - bytes: ?[]const u8 = null, - /// /log only: this open waits for records after `bytes`, from `next`. - follow: bool = false, - next: u64 = 0, - /// How much of record `next` a short read already took. - part: u32 = 0, - /// /log only: how much of `bytes` this open has read. Reads walk it as a - /// stream and ignore their offset, since `exec 3<>log; echo follow >&3` - /// leaves the shared offset past the word it wrote. - pos: u64 = 0, -}; -pub const snapshot_slots = 32; - pub fn render(p: *Pardes) ![]u8 { var arena: std.heap.ArenaAllocator = .init(p.gpa); defer arena.deinit(); @@ -58,26 +43,13 @@ pub fn render(p: *Pardes) ![]u8 { return out.toOwnedSlice(); } -/// A screen freezes at open; a terminal body freezes at its first read. -pub fn openSnapshot(p: *Pardes, req: Req, of_screen: bool) Reply { - for (&p.fs.snapshots, 0..) |*slot, i| { - if (slot.node != 0) continue; - const bytes = if (of_screen) render(p) catch return Reply.fail(req.tag, E.NOMEM) else null; - slot.* = .{ .node = req.node, .bytes = bytes }; - return .{ .tag = req.tag, .handle = @intCast(i + 1) }; - } - return Reply.fail(req.tag, E.NFILE); -} - pub fn readSnapshot(p: *Pardes, req: Req, pane: ?*Pane) Reply { - if (req.handle == 0 or req.handle > p.fs.snapshots.len) return Reply.fail(req.tag, E.INVAL); - const snapshot = &p.fs.snapshots[req.handle - 1]; - if (snapshot.node == 0 or snapshot.node != req.node) return Reply.fail(req.tag, E.INVAL); - if (snapshot.bytes == null) { + const snapshot = &(tree.openOf(p, req) orelse return Reply.fail(req.tag, E.INVAL)).what.snapshot; + if (snapshot.* == null) { const terminal = pane orelse return Reply.fail(req.tag, E.INVAL); - snapshot.bytes = panes.Terminal.screenTextAlloc(terminal, p.gpa) catch return Reply.fail(req.tag, E.NOMEM); + snapshot.* = panes.Terminal.screenTextAlloc(terminal, p.gpa) catch return Reply.fail(req.tag, E.NOMEM); } - const bytes = snapshot.bytes.?; + const bytes = snapshot.*.?; const off = @min(req.off, bytes.len); const len = @min(bytes.len - off, req.size); const out = p.fs.stage(p.gpa); @@ -85,16 +57,6 @@ pub fn readSnapshot(p: *Pardes, req: Req, pane: ?*Pane) Reply { return .{ .tag = req.tag, .payload = .{ .staged = @intCast(len) } }; } -/// True when the handle named a snapshot, which is now released. -pub fn releaseSnapshot(p: *Pardes, req: Req) bool { - if (req.handle == 0 or req.handle > p.fs.snapshots.len) return false; - const snapshot = &p.fs.snapshots[req.handle - 1]; - if (snapshot.node != req.node) return false; - if (snapshot.bytes) |bytes| p.gpa.free(bytes); - snapshot.* = .{}; - return true; -} - const listeners_cap = 512; fn listenersText(p: *Pardes, buf: []u8) []const u8 { @@ -199,7 +161,7 @@ test "an empty terminal body snapshot stays empty while output continues" { const node = Node.of(serialOf(p), .body); const opened = call(p, .{ .tag = 1, .op = .open, .node = node }); try testing.expectEqualStrings("", call(p, .{ .tag = 2, .op = .read, .node = node, .handle = opened.reply.handle, .size = 32 }).bytes); - try testing.expect(p.fs.snapshots[opened.reply.handle - 1].bytes != null); + try testing.expect(p.fs.opens[opened.reply.handle - 1].what.snapshot != null); p.update(.{ .output = .{ .pane = 0, .bytes = "new output" } }); while (p.nextEffect()) |_| {} try testing.expectEqualStrings("", call(p, .{ .tag = 3, .op = .read, .node = node, .handle = opened.reply.handle, .size = 32 }).bytes); @@ -214,15 +176,15 @@ test "terminal body snapshots are lazy bounded and released after the pane close const p = try withTerm(gpa); defer p.deinit(); const node = Node.of(serialOf(p), .body); - var handles: [snapshot_slots]u32 = undefined; + var handles: [tree.open_slots]u32 = undefined; for (&handles) |*opened_handle| { const opened = call(p, .{ .tag = 1, .op = .open, .node = node }); try testing.expectEqual(Status.ok, opened.reply.status); opened_handle.* = opened.reply.handle; } - for (p.fs.snapshots) |snapshot| { - try testing.expectEqual(node, snapshot.node); - try testing.expect(snapshot.bytes == null); + for (p.fs.opens) |o| { + try testing.expectEqual(node, o.node); + try testing.expect(o.what.snapshot == null); } try testing.expectEqual(E.NFILE, call(p, .{ .tag = 2, .op = .open, .node = node }).errno()); const scratch = try th.newPane(p); @@ -235,26 +197,23 @@ test "terminal body snapshots are lazy bounded and released after the pane close const failed = call(p, .{ .tag = 3, .op = .read, .node = node, .handle = handles[0], .size = 32 }); p.gpa = gpa; try testing.expectEqual(E.NOMEM, failed.errno()); - try testing.expect(p.fs.snapshots[handles[0] - 1].bytes == null); + try testing.expect(p.fs.opens[handles[0] - 1].what.snapshot == null); const first = call(p, .{ .tag = 4, .op = .read, .node = node, .handle = handles[0], .size = 32 }); try testing.expectEqualStrings("latest output", first.bytes); - const saved = p.fs.snapshots[handles[0] - 1].bytes.?; + const saved = p.fs.opens[handles[0] - 1].what.snapshot.?; failing = std.testing.FailingAllocator.init(gpa, .{ .fail_index = 0 }); p.gpa = failing.allocator(); const second = call(p, .{ .tag = 5, .op = .read, .node = node, .handle = handles[0], .off = 7, .size = 32 }); p.gpa = gpa; try testing.expectEqualStrings("output", second.bytes); try testing.expect(!failing.has_induced_failure); - try testing.expectEqual(saved.ptr, p.fs.snapshots[handles[0] - 1].bytes.?.ptr); + try testing.expectEqual(saved.ptr, p.fs.opens[handles[0] - 1].what.snapshot.?.ptr); try testing.expectEqual(E.INVAL, call(p, .{ .tag = 6, .op = .read, .node = screen_node, .handle = handles[0], .size = 32 }).errno()); _ = call(p, .{ .tag = 7, .op = .release, .node = screen_node, .handle = handles[0] }); - try testing.expectEqual(node, p.fs.snapshots[handles[0] - 1].node); + try testing.expectEqual(node, p.fs.opens[handles[0] - 1].node); try p.removePane(0, null); for (handles) |opened_handle| _ = call(p, .{ .tag = 8, .op = .release, .node = node, .handle = opened_handle }); - for (p.fs.snapshots) |snapshot| { - try testing.expectEqual(@as(u64, 0), snapshot.node); - try testing.expect(snapshot.bytes == null); - } + for (p.fs.opens) |o| try testing.expectEqual(@as(u64, 0), o.node); } test "screen snapshots preserve rendered cells and styles until their handle is released" { @@ -263,7 +222,7 @@ test "screen snapshots preserve rendered cells and styles until their handle is defer p.deinit(); const opened = call(p, .{ .tag = 1, .op = .open, .node = screen_node }); try testing.expectEqual(Status.ok, opened.reply.status); - const snapshot = p.fs.snapshots[opened.reply.handle - 1].bytes.?; + const snapshot = p.fs.opens[opened.reply.handle - 1].what.snapshot.?; const original = try gpa.dupe(u8, snapshot); defer gpa.free(original); const parsed = try std.json.parseFromSlice(std.json.Value, gpa, original, .{}); @@ -276,7 +235,7 @@ test "screen snapshots preserve rendered cells and styles until their handle is _ = wr(p, Node.of(serialOf(p), .body), "changed\n"); const newer = call(p, .{ .tag = 2, .op = .open, .node = screen_node }); try testing.expectEqual(Status.ok, newer.reply.status); - try testing.expect(!std.mem.eql(u8, original, p.fs.snapshots[newer.reply.handle - 1].bytes.?)); + try testing.expect(!std.mem.eql(u8, original, p.fs.opens[newer.reply.handle - 1].what.snapshot.?)); var off: usize = 0; while (off < original.len) { const result = call(p, .{ .tag = 3, .op = .read, .node = screen_node, .handle = opened.reply.handle, .off = off, .size = 13 }); @@ -286,7 +245,7 @@ test "screen snapshots preserve rendered cells and styles until their handle is } _ = call(p, .{ .tag = 4, .op = .release, .node = screen_node, .handle = opened.reply.handle }); _ = call(p, .{ .tag = 5, .op = .release, .node = screen_node, .handle = newer.reply.handle }); - for (p.fs.snapshots) |slot| try testing.expect(slot.node == 0); + for (p.fs.opens) |o| try testing.expect(o.node == 0); } test "screen inspection preserves acknowledged presentation and the next real frame" { |
