diff options
| author | Mitchell Hashimoto <mitchell.hashimoto@gmail.com> | 2022-11-03 13:30:30 -0700 |
|---|---|---|
| committer | Mitchell Hashimoto <mitchell.hashimoto@gmail.com> | 2022-11-05 19:31:02 -0700 |
| commit | 35c1decd58af93efcd693d195ce5010706a6bf5a (patch) | |
| tree | f9c3455602e3ac4f356044c1832a53d2f26e3074 /src/termio/Thread.zig | |
| parent | 8dd67662b32464671352cf9fe46f178109455168 (diff) | |
Start pulling out IO thread and IO implementation
Diffstat (limited to 'src/termio/Thread.zig')
| -rw-r--r-- | src/termio/Thread.zig | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig new file mode 100644 index 000000000..ac30fb070 --- /dev/null +++ b/src/termio/Thread.zig @@ -0,0 +1,140 @@ +//! Represents the IO thread logic. The IO thread is responsible for +//! the child process and pty management. +pub const Thread = @This(); + +const std = @import("std"); +const builtin = @import("builtin"); +const libuv = @import("libuv"); +const termio = @import("../termio.zig"); + +const Allocator = std.mem.Allocator; +const log = std.log.scoped(.io_thread); + +/// 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, + +/// This can be used to wake up the thread. +wakeup: libuv.Async, + +/// This can be used to stop the thread on the next loop iteration. +stop: libuv.Async, + +/// The underlying IO implementation. +impl: *termio.Impl, + +/// Initialize the thread. This does not START the thread. This only sets +/// up all the internal state necessary prior to starting the thread. It +/// is up to the caller to start the thread with the threadMain entrypoint. +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 loop.deinit(alloc); + loop.setData(allocPtr); + + // 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); + + // 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); + + return Thread{ + .loop = loop, + .wakeup = wakeup_h, + .stop = stop_h, + .impl = impl, + }; +} + +/// 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}); + + // Dealloc our allocator copy + alloc.destroy(alloc_ptr); + + self.loop.deinit(alloc); +} + +/// The main entrypoint for the thread. +pub fn threadMain(self: *Thread) void { + // Call child function so we can use errors... + self.threadMain_() catch |err| { + // In the future, we should expose this on the thread struct. + log.warn("error in io thread err={}", .{err}); + }; +} + +fn threadMain_(self: *Thread) !void { + // Run our thread start/end callbacks. This allows the implementation + // to hook into the event loop as needed. + try self.impl.threadEnter(self.loop); + defer self.impl.threadExit(); + + // Set up our async handler to support rendering + self.wakeup.setData(self); + defer self.wakeup.setData(null); + + // Run + log.debug("starting IO thread", .{}); + defer log.debug("exiting IO thread", .{}); + _ = try self.loop.run(.default); +} + +fn wakeupCallback(h: *libuv.Async) void { + _ = h; + // const t = h.getData(Thread) orelse { + // // This shouldn't happen so we log it. + // log.warn("render callback fired without data set", .{}); + // return; + // }; +} + +fn stopCallback(h: *libuv.Async) void { + h.loop().stop(); +} |
