summaryrefslogtreecommitdiff
path: root/src/tty/tty.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/tty/tty.zig')
-rw-r--r--src/tty/tty.zig110
1 files changed, 92 insertions, 18 deletions
diff --git a/src/tty/tty.zig b/src/tty/tty.zig
index f8f8cdc5..822c2d7f 100644
--- a/src/tty/tty.zig
+++ b/src/tty/tty.zig
@@ -76,7 +76,8 @@ pub const Command = struct {
pub var value: union(enum) {
nop,
quit,
- tick,
+ /// A timed wait ran out: the core's next wake has come.
+ wake,
key_press: vaxis.Key,
pty_read: struct { id: usize, gen: u32, bytes: []u8 },
pty_eof: struct { id: usize, gen: u32 },
@@ -737,6 +738,7 @@ fn localSession(
.paste_buf = paste_buf,
.inotify_fd = file_watch.init(false),
.fs = fs,
+ .test_clock = host_io.testClock(),
};
pardes.lsp.setStatusSink(&sh, lspStatusSink);
host_io.setExitWake(loop, wakeForExit);
@@ -786,7 +788,7 @@ fn localSession(
try startInput(loop, input_cache);
defer if (attached.* == null) stopInput(loop);
(try std.Thread.spawn(.{}, winchWatch, .{ loop, vx, tty })).detach();
- (try std.Thread.spawn(.{}, tickWatch, .{ io, loop })).detach();
+ (try std.Thread.spawn(.{}, wakeWatch, .{ io, loop })).detach();
if (fs) |f| try f.watch(loop, wakeFs);
try vx.queryTerminalSend(tty.writer());
@@ -911,7 +913,11 @@ const Shell = struct {
caps_pending: bool = true,
check_files: bool = false,
in_paste: bool = false,
- tracks: []const pardes.layout.Track = &.{},
+ tracks: []const pardes.animation.Track = &.{},
+ /// PARDES_TEST_CLOCK's virtual time (host_io.testClock), and where the
+ /// running-out wait moves it: the core's next wake.
+ test_clock: ?u64 = null,
+ wake_to: u64 = 0,
fn of(ctx: ?*anyopaque) *Shell {
return @ptrCast(@alignCast(ctx.?));
@@ -923,12 +929,14 @@ const Shell = struct {
const isolated_vtable: host_io.Host.VTable = .{
.wait_input = waitInput,
+ .now = clockNow,
.present = present,
.post_present = postPresent,
};
const vtable: host_io.Host.VTable = .{
.wait_input = waitInput,
+ .now = clockNow,
.present = present,
.post_present = postPresent,
.poll_frame = pollFrame,
@@ -951,12 +959,20 @@ const Shell = struct {
.pipe = pipe,
};
+ fn clockNow(ctx: ?*anyopaque) u64 {
+ const s = of(ctx);
+ return s.test_clock orelse host_io.monotonicNs();
+ }
+
fn waitInput(ctx: ?*anyopaque, timeout_ms: u32) void {
const s = of(ctx);
var batch: usize = 0;
- // The wait is the 9P connections' turn with the core. Animating, the
- // frame's tick arrives through the same queue (`tickWatch`).
- if (timeout_ms != 0) tick_request.set(s.io);
+ // The wait is the 9P connections' turn with the core. A timeout (the
+ // core's next wake) arrives through the same queue (`wakeWatch`).
+ if (timeout_ms != 0) {
+ s.wake_to = s.core.nextWake() orelse 0;
+ requestWake(s.io, timeout_ms);
+ }
pardes.turn.rest();
const first = s.loop.nextEvent() catch {
pardes.turn.wake();
@@ -999,7 +1015,9 @@ const Shell = struct {
defer tz_event.end();
switch (event) {
.nop => {},
- .tick => core.update(.tick),
+ .wake => if (s.test_clock) |*virtual| {
+ virtual.* = @max(virtual.*, s.wake_to);
+ },
.quit => {
core.quit = true;
return true;
@@ -1565,21 +1583,77 @@ fn winchWatch(loop: *Loop, vx: *vaxis.Vaxis, tty: *vaxis.Tty) void {
}
}
-/// Set by an animating frame's wait: post one `.tick` a frame from now. The
-/// tick joins the input queue, so a key or a click that lands inside the
-/// frame is handled at once instead of after a blind sleep.
-var tick_request: std.Io.Event = .unset;
+/// A wait with a timeout: post one `.wake` when it runs out. The wake joins
+/// the input queue, so a key or a click that lands first is handled at once
+/// instead of after a blind sleep; vaxis's queue has no timed wait. Each
+/// request bumps `wake_gen`, which cuts short a sleep still timing an
+/// earlier, longer request (an 800 ms linger interrupted by a key that
+/// starts a 16 ms fall) and times the new one instead.
+var wake_gen: std.atomic.Value(u32) = .init(0);
+var wake_ms: std.atomic.Value(u32) = .init(0);
-fn tickWatch(io: std.Io, loop: *Loop) void {
+fn requestWake(io: std.Io, ms: u32) void {
+ wake_ms.store(ms, .release);
+ _ = wake_gen.fetchAdd(1, .acq_rel);
+ io.futexWake(u32, &wake_gen.raw, 1);
+}
+
+fn wakeWatch(io: std.Io, loop: *Loop) void {
+ var seen = wake_gen.load(.acquire);
while (true) {
- tick_request.waitUncancelable(io);
- tick_request.reset();
- var ts: libc.timespec = .{ .sec = 0, .nsec = @as(isize, pardes.layout.Animation.frame_ms) * std.time.ns_per_ms };
- _ = libc.nanosleep(&ts, null);
- loop.postEvent(.tick) catch {};
+ // Asleep until a request.
+ while (wake_gen.load(.acquire) == seen) io.futexWaitUncancelable(u32, &wake_gen.raw, seen);
+ seen = wake_gen.load(.acquire);
+ if (timeWake(io, &seen)) loop.postEvent(.wake) catch {};
}
}
+/// Sleep out the latest request; true when it ran its course, false when
+/// none is pending. A newer request met on the way restarts the timing.
+fn timeWake(io: std.Io, seen: *u32) bool {
+ while (true) {
+ const ms = wake_ms.load(.acquire);
+ const deadline = host_io.monotonicNs() + @as(u64, ms) * std.time.ns_per_ms;
+ while (true) {
+ const now = host_io.monotonicNs();
+ if (now >= deadline) return true;
+ io.futexWaitTimeout(u32, &wake_gen.raw, seen.*, .{ .duration = .{
+ .raw = .fromNanoseconds(@intCast(deadline - now)),
+ .clock = .awake,
+ } }) catch {};
+ const gen = wake_gen.load(.acquire);
+ if (gen != seen.*) {
+ seen.* = gen;
+ break;
+ }
+ }
+ }
+}
+
+test "a short wake request cuts short a long one still sleeping" {
+ const io = std.testing.io;
+ var seen = wake_gen.load(.acquire);
+ requestWake(io, 800);
+ seen = wake_gen.load(.acquire);
+ const Timer = struct {
+ fn run(timer_io: std.Io, start_seen: u32, done: *std.atomic.Value(u64)) void {
+ var local = start_seen;
+ _ = timeWake(timer_io, &local);
+ done.store(host_io.monotonicNs(), .release);
+ }
+ };
+ var done: std.atomic.Value(u64) = .init(0);
+ const start = host_io.monotonicNs();
+ const thread = try std.Thread.spawn(.{}, Timer.run, .{ io, seen, &done });
+ var pause: std.c.timespec = .{ .sec = 0, .nsec = 50 * std.time.ns_per_ms };
+ _ = std.c.nanosleep(&pause, null);
+ requestWake(io, 16);
+ thread.join();
+ // The 16 ms request lands about 66 ms in: well before the 800 ms one
+ // would have, however loaded the machine.
+ try std.testing.expect(done.load(.acquire) - start < 700 * std.time.ns_per_ms);
+}
+
fn wakeFs(ctx: ?*anyopaque) void {
const loop: *Loop = @ptrCast(@alignCast(ctx.?));
_ = loop.tryPostEvent(.fs_ready) catch {};
@@ -1864,7 +1938,7 @@ const Attach = struct {
fn apply(a: *Attach, event: @TypeOf(Command.value)) ?AttachEnd {
switch (event) {
- .nop, .tick, .fs_ready, .lsp_done, .lsp_status, .pipe_done, .pty_read, .pty_eof, .files_changed => {},
+ .nop, .wake, .fs_ready, .lsp_done, .lsp_status, .pipe_done, .pty_read, .pty_eof, .files_changed => {},
.quit => return .none,
.focus_in => {},
.focus_out => return a.send(.pointer_leave),