mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-31 17:11:22 +00:00
termio: use libxev (with TODOs)
This commit is contained in:
@@ -4,6 +4,7 @@ pub const Thread = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const xev = @import("xev");
|
||||
const libuv = @import("libuv");
|
||||
const termio = @import("../termio.zig");
|
||||
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
|
||||
@@ -18,16 +19,21 @@ const log = std.log.scoped(.io_thread);
|
||||
/// the future if we want it configurable.
|
||||
const Mailbox = BlockingQueue(termio.Message, 64);
|
||||
|
||||
/// Allocator used for some state
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
/// The main event loop for the thread. The user data of this loop
|
||||
/// is always the allocator used to create the loop. This is a convenience
|
||||
/// so that users of the loop always have an allocator.
|
||||
loop: libuv.Loop,
|
||||
loop: xev.Loop,
|
||||
|
||||
/// This can be used to wake up the thread.
|
||||
wakeup: libuv.Async,
|
||||
wakeup: xev.Async,
|
||||
wakeup_c: xev.Completion = .{},
|
||||
|
||||
/// This can be used to stop the thread on the next loop iteration.
|
||||
stop: libuv.Async,
|
||||
stop: xev.Async,
|
||||
stop_c: xev.Completion = .{},
|
||||
|
||||
/// The underlying IO implementation.
|
||||
impl: *termio.Impl,
|
||||
@@ -43,44 +49,24 @@ pub fn init(
|
||||
alloc: Allocator,
|
||||
impl: *termio.Impl,
|
||||
) !Thread {
|
||||
// We always store allocator pointer on the loop data so that
|
||||
// handles can use our global allocator.
|
||||
const allocPtr = try alloc.create(Allocator);
|
||||
errdefer alloc.destroy(allocPtr);
|
||||
allocPtr.* = alloc;
|
||||
|
||||
// Create our event loop.
|
||||
var loop = try libuv.Loop.init(alloc);
|
||||
errdefer {
|
||||
// Run the loop once to close any of our handles
|
||||
_ = loop.run(.nowait) catch 0;
|
||||
loop.deinit(alloc);
|
||||
}
|
||||
loop.setData(allocPtr);
|
||||
var loop = try xev.Loop.init(.{});
|
||||
errdefer loop.deinit();
|
||||
|
||||
// This async handle is used to "wake up" the renderer and force a render.
|
||||
var wakeup_h = try libuv.Async.init(alloc, loop, wakeupCallback);
|
||||
errdefer wakeup_h.close((struct {
|
||||
fn callback(h: *libuv.Async) void {
|
||||
const loop_alloc = h.loop().getData(Allocator).?.*;
|
||||
h.deinit(loop_alloc);
|
||||
}
|
||||
}).callback);
|
||||
var wakeup_h = try xev.Async.init();
|
||||
errdefer wakeup_h.deinit();
|
||||
|
||||
// This async handle is used to stop the loop and force the thread to end.
|
||||
var stop_h = try libuv.Async.init(alloc, loop, stopCallback);
|
||||
errdefer stop_h.close((struct {
|
||||
fn callback(h: *libuv.Async) void {
|
||||
const loop_alloc = h.loop().getData(Allocator).?.*;
|
||||
h.deinit(loop_alloc);
|
||||
}
|
||||
}).callback);
|
||||
var stop_h = try xev.Async.init();
|
||||
errdefer stop_h.deinit();
|
||||
|
||||
// The mailbox for messaging this thread
|
||||
var mailbox = try Mailbox.create(alloc);
|
||||
errdefer mailbox.destroy(alloc);
|
||||
|
||||
return Thread{
|
||||
.alloc = alloc,
|
||||
.loop = loop,
|
||||
.wakeup = wakeup_h,
|
||||
.stop = stop_h,
|
||||
@@ -92,37 +78,12 @@ 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 {
|
||||
// Get a copy to our allocator
|
||||
const alloc_ptr = self.loop.getData(Allocator).?;
|
||||
const alloc = alloc_ptr.*;
|
||||
|
||||
// Schedule our handles to close
|
||||
self.stop.close((struct {
|
||||
fn callback(h: *libuv.Async) void {
|
||||
const handle_alloc = h.loop().getData(Allocator).?.*;
|
||||
h.deinit(handle_alloc);
|
||||
}
|
||||
}).callback);
|
||||
self.wakeup.close((struct {
|
||||
fn callback(h: *libuv.Async) void {
|
||||
const handle_alloc = h.loop().getData(Allocator).?.*;
|
||||
h.deinit(handle_alloc);
|
||||
}
|
||||
}).callback);
|
||||
|
||||
// Run the loop one more time, because destroying our other things
|
||||
// like windows usually cancel all our event loop stuff and we need
|
||||
// one more run through to finalize all the closes.
|
||||
_ = self.loop.run(.default) catch |err|
|
||||
log.err("error finalizing event loop: {}", .{err});
|
||||
self.stop.deinit();
|
||||
self.wakeup.deinit();
|
||||
self.loop.deinit();
|
||||
|
||||
// Nothing can possibly access the mailbox anymore, destroy it.
|
||||
self.mailbox.destroy(alloc);
|
||||
|
||||
// Dealloc our allocator copy
|
||||
alloc.destroy(alloc_ptr);
|
||||
|
||||
self.loop.deinit(alloc);
|
||||
self.mailbox.destroy(self.alloc);
|
||||
}
|
||||
|
||||
/// The main entrypoint for the thread.
|
||||
@@ -139,18 +100,18 @@ fn threadMain_(self: *Thread) !void {
|
||||
|
||||
// Run our thread start/end callbacks. This allows the implementation
|
||||
// to hook into the event loop as needed.
|
||||
var data = try self.impl.threadEnter(self.loop);
|
||||
var data = try self.impl.threadEnter(&self.loop);
|
||||
defer data.deinit();
|
||||
defer self.impl.threadExit(data);
|
||||
|
||||
// Set up our async handler to support rendering
|
||||
self.wakeup.setData(self);
|
||||
defer self.wakeup.setData(null);
|
||||
// Start the async handlers
|
||||
self.wakeup.wait(&self.loop, &self.wakeup_c, Thread, self, wakeupCallback);
|
||||
self.stop.wait(&self.loop, &self.stop_c, Thread, self, stopCallback);
|
||||
|
||||
// Run
|
||||
log.debug("starting IO thread", .{});
|
||||
defer log.debug("exiting IO thread", .{});
|
||||
_ = try self.loop.run(.default);
|
||||
try self.loop.run(.until_done);
|
||||
}
|
||||
|
||||
/// Drain the mailbox, handling all the messages in our terminal implementation.
|
||||
@@ -185,22 +146,37 @@ fn drainMailbox(self: *Thread) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn wakeupCallback(h: *libuv.Async) void {
|
||||
fn wakeupCallback(
|
||||
self_: ?*Thread,
|
||||
_: *xev.Loop,
|
||||
_: *xev.Completion,
|
||||
r: xev.Async.WaitError!void,
|
||||
) xev.CallbackAction {
|
||||
_ = r catch |err| {
|
||||
log.err("error in wakeup err={}", .{err});
|
||||
return .rearm;
|
||||
};
|
||||
|
||||
const zone = trace(@src());
|
||||
defer zone.end();
|
||||
|
||||
const t = h.getData(Thread) orelse {
|
||||
// This shouldn't happen so we log it.
|
||||
log.warn("wakeup callback fired without data set", .{});
|
||||
return;
|
||||
};
|
||||
const t = self_.?;
|
||||
|
||||
// When we wake up, we check the mailbox. Mailbox producers should
|
||||
// wake up our thread after publishing.
|
||||
t.drainMailbox() catch |err|
|
||||
log.err("error draining mailbox err={}", .{err});
|
||||
|
||||
return .rearm;
|
||||
}
|
||||
|
||||
fn stopCallback(h: *libuv.Async) void {
|
||||
h.loop().stop();
|
||||
fn stopCallback(
|
||||
self_: ?*Thread,
|
||||
_: *xev.Loop,
|
||||
_: *xev.Completion,
|
||||
r: xev.Async.WaitError!void,
|
||||
) xev.CallbackAction {
|
||||
_ = r catch unreachable;
|
||||
self_.?.loop.stop();
|
||||
return .disarm;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user