summaryrefslogtreecommitdiff
path: root/src/ninep/screen.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-27 20:20:27 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit7dc3891ed574d6e96548f592b09b0f8abc87ae2a (patch)
tree70cddb9ce4a7e9039f8167e806b7e996f94eefb0 /src/ninep/screen.zig
parentacd9e032a147f4aa7ad49110dc2f4fb945e7a9bb (diff)
downloadpardes-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.zig79
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" {