summaryrefslogtreecommitdiff
path: root/src/termio/Thread.zig
diff options
context:
space:
mode:
authorMitchell Hashimoto <mitchell.hashimoto@gmail.com>2023-03-08 08:43:42 -0800
committerMitchell Hashimoto <mitchell.hashimoto@gmail.com>2023-03-08 08:43:42 -0800
commit15b7e7fcd783459834015597aa651232631c8a7f (patch)
tree2f1c2c1324012fb04159a8d52a63d328626b9082 /src/termio/Thread.zig
parenta754fe8c304675ade2496ddb0e253f5c15c7a4bd (diff)
termio: coalesce resize events
On macOS, we were seeing resize events dropped by child processes if too many SIGWNCH events were generated.
Diffstat (limited to 'src/termio/Thread.zig')
-rw-r--r--src/termio/Thread.zig68
1 files changed, 67 insertions, 1 deletions
diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig
index 49ae661cc..b1c03cc6e 100644
--- a/src/termio/Thread.zig
+++ b/src/termio/Thread.zig
@@ -18,6 +18,15 @@ const log = std.log.scoped(.io_thread);
/// the future if we want it configurable.
pub const Mailbox = BlockingQueue(termio.Message, 64);
+/// This stores the information that is coalesced.
+const Coalesce = struct {
+ /// The number of milliseconds to coalesce certain messages like resize for.
+ /// Not all message types are coalesced.
+ const min_ms = 25;
+
+ resize: ?termio.Message.Resize = null,
+};
+
/// Allocator used for some state
alloc: std.mem.Allocator,
@@ -34,6 +43,12 @@ wakeup_c: xev.Completion = .{},
stop: xev.Async,
stop_c: xev.Completion = .{},
+/// This is used to coalesce resize events.
+coalesce: xev.Timer,
+coalesce_c: xev.Completion = .{},
+coalesce_cancel_c: xev.Completion = .{},
+coalesce_data: Coalesce = .{},
+
/// The underlying IO implementation.
impl: *termio.Impl,
@@ -60,6 +75,10 @@ pub fn init(
var stop_h = try xev.Async.init();
errdefer stop_h.deinit();
+ // This timer is used to coalesce resize events.
+ var coalesce_h = try xev.Timer.init();
+ errdefer coalesce_h.deinit();
+
// The mailbox for messaging this thread
var mailbox = try Mailbox.create(alloc);
errdefer mailbox.destroy(alloc);
@@ -69,6 +88,7 @@ pub fn init(
.loop = loop,
.wakeup = wakeup_h,
.stop = stop_h,
+ .coalesce = coalesce_h,
.impl = impl,
.mailbox = mailbox,
};
@@ -77,6 +97,7 @@ pub fn init(
/// Clean up the thread. This is only safe to call once the thread
/// completes executing; the caller must join prior to this.
pub fn deinit(self: *Thread) void {
+ self.coalesce.deinit();
self.stop.deinit();
self.wakeup.deinit();
self.loop.deinit();
@@ -129,7 +150,7 @@ fn drainMailbox(self: *Thread) !void {
log.debug("mailbox message={}", .{message});
switch (message) {
- .resize => |v| try self.impl.resize(v.grid_size, v.screen_size, v.padding),
+ .resize => |v| self.handleResize(v),
.clear_screen => |v| try self.impl.clearScreen(v.history),
.write_small => |v| try self.impl.queueWrite(v.data[0..v.len]),
.write_stable => |v| try self.impl.queueWrite(v),
@@ -147,6 +168,51 @@ fn drainMailbox(self: *Thread) !void {
}
}
+fn handleResize(self: *Thread, resize: termio.Message.Resize) void {
+ self.coalesce_data.resize = resize;
+
+ // If the timer is already active we just return. In the future we want
+ // to reset the timer up to a maximum wait time but for now this ensures
+ // relatively smooth resizing.
+ if (self.coalesce_c.state() == .active) return;
+
+ self.coalesce.reset(
+ &self.loop,
+ &self.coalesce_c,
+ &self.coalesce_cancel_c,
+ Coalesce.min_ms,
+ Thread,
+ self,
+ coalesceCallback,
+ );
+}
+
+fn coalesceCallback(
+ self_: ?*Thread,
+ _: *xev.Loop,
+ _: *xev.Completion,
+ r: xev.Timer.RunError!void,
+) xev.CallbackAction {
+ _ = r catch |err| switch (err) {
+ error.Canceled => {},
+ else => {
+ log.warn("error during coalesce callback err={}", .{err});
+ return .disarm;
+ },
+ };
+
+ const self = self_ orelse return .disarm;
+
+ if (self.coalesce_data.resize) |v| {
+ self.coalesce_data.resize = null;
+ self.impl.resize(v.grid_size, v.screen_size, v.padding) catch |err| {
+ log.warn("error during resize err={}", .{err});
+ };
+ }
+
+ return .disarm;
+}
+
fn wakeupCallback(
self_: ?*Thread,
_: *xev.Loop,