mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 00:36:07 +03:00
termio: start the thread mailbox, hook up resize
This commit is contained in:
@ -473,7 +473,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
|
|||||||
.renderer_state = &self.renderer_state,
|
.renderer_state = &self.renderer_state,
|
||||||
.renderer_wakeup = render_thread.wakeup,
|
.renderer_wakeup = render_thread.wakeup,
|
||||||
});
|
});
|
||||||
errdefer io.deinit(alloc);
|
errdefer io.deinit();
|
||||||
|
|
||||||
// Create the IO thread
|
// Create the IO thread
|
||||||
var io_thread = try termio.Thread.init(alloc, &self.io);
|
var io_thread = try termio.Thread.init(alloc, &self.io);
|
||||||
@ -617,7 +617,7 @@ pub fn destroy(self: *Window) void {
|
|||||||
self.io_thread.deinit();
|
self.io_thread.deinit();
|
||||||
|
|
||||||
// Deinitialize our terminal IO
|
// Deinitialize our terminal IO
|
||||||
self.io.deinit(self.alloc);
|
self.io.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deinitialize the pty. This closes the pty handles. This should
|
// Deinitialize the pty. This closes the pty handles. This should
|
||||||
@ -744,13 +744,27 @@ fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
|
|||||||
|
|
||||||
const win = window.getUserPointer(Window) orelse return;
|
const win = window.getUserPointer(Window) orelse return;
|
||||||
|
|
||||||
// Resize usually forces a redraw
|
// TODO: if our screen size didn't change, then we should avoid the
|
||||||
win.queueRender() catch |err|
|
// overhead of inter-thread communication
|
||||||
log.err("error scheduling render timer in sizeCallback err={}", .{err});
|
|
||||||
|
|
||||||
// Recalculate our grid size
|
// Recalculate our grid size
|
||||||
win.grid_size.update(screen_size, win.renderer.cell_size);
|
win.grid_size.update(screen_size, win.renderer.cell_size);
|
||||||
|
|
||||||
|
// Mail the IO thread
|
||||||
|
_ = win.io_thread.mailbox.push(.{
|
||||||
|
.resize = .{
|
||||||
|
.grid_size = win.grid_size,
|
||||||
|
.screen_size = screen_size,
|
||||||
|
},
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
win.io_thread.wakeup.send() catch {};
|
||||||
|
|
||||||
|
// TODO: everything below here goes away with the IO thread
|
||||||
|
|
||||||
|
// Resize usually forces a redraw
|
||||||
|
win.queueRender() catch |err|
|
||||||
|
log.err("error scheduling render timer in sizeCallback err={}", .{err});
|
||||||
|
|
||||||
// Update the size of our pty
|
// Update the size of our pty
|
||||||
win.pty.setSize(.{
|
win.pty.setSize(.{
|
||||||
.ws_row = @intCast(u16, win.grid_size.rows),
|
.ws_row = @intCast(u16, win.grid_size.rows),
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
//! for taking the config, spinning up a child process, and handling IO
|
//! for taking the config, spinning up a child process, and handling IO
|
||||||
//! with the termianl.
|
//! with the termianl.
|
||||||
|
|
||||||
|
pub const message = @import("termio/message.zig");
|
||||||
pub const Exec = @import("termio/Exec.zig");
|
pub const Exec = @import("termio/Exec.zig");
|
||||||
pub const Options = @import("termio/Options.zig");
|
pub const Options = @import("termio/Options.zig");
|
||||||
pub const Thread = @import("termio/Thread.zig");
|
pub const Thread = @import("termio/Thread.zig");
|
||||||
|
@ -14,6 +14,9 @@ const renderer = @import("../renderer.zig");
|
|||||||
|
|
||||||
const log = std.log.scoped(.io_exec);
|
const log = std.log.scoped(.io_exec);
|
||||||
|
|
||||||
|
/// Allocator
|
||||||
|
alloc: Allocator,
|
||||||
|
|
||||||
/// This is the pty fd created for the subcommand.
|
/// This is the pty fd created for the subcommand.
|
||||||
pty: Pty,
|
pty: Pty,
|
||||||
|
|
||||||
@ -86,6 +89,7 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
|||||||
errdefer term.deinit(alloc);
|
errdefer term.deinit(alloc);
|
||||||
|
|
||||||
return Exec{
|
return Exec{
|
||||||
|
.alloc = alloc,
|
||||||
.pty = pty,
|
.pty = pty,
|
||||||
.command = cmd,
|
.command = cmd,
|
||||||
.terminal = term,
|
.terminal = term,
|
||||||
@ -95,7 +99,7 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Exec, alloc: Allocator) void {
|
pub fn deinit(self: *Exec) void {
|
||||||
// Deinitialize the pty. This closes the pty handles. This should
|
// Deinitialize the pty. This closes the pty handles. This should
|
||||||
// cause a close in the our subprocess so just wait for that.
|
// cause a close in the our subprocess so just wait for that.
|
||||||
self.pty.deinit();
|
self.pty.deinit();
|
||||||
@ -103,7 +107,7 @@ pub fn deinit(self: *Exec, alloc: Allocator) void {
|
|||||||
log.err("error waiting for command to exit: {}", .{err});
|
log.err("error waiting for command to exit: {}", .{err});
|
||||||
|
|
||||||
// Clean up our other members
|
// Clean up our other members
|
||||||
self.terminal.deinit(alloc);
|
self.terminal.deinit(self.alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn threadEnter(self: *Exec, loop: libuv.Loop) !ThreadData {
|
pub fn threadEnter(self: *Exec, loop: libuv.Loop) !ThreadData {
|
||||||
@ -148,6 +152,33 @@ pub fn threadExit(self: *Exec, data: ThreadData) void {
|
|||||||
_ = data;
|
_ = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resize the terminal.
|
||||||
|
pub fn resize(
|
||||||
|
self: *Exec,
|
||||||
|
grid_size: renderer.GridSize,
|
||||||
|
screen_size: renderer.ScreenSize,
|
||||||
|
) !void {
|
||||||
|
// Update the size of our pty
|
||||||
|
try self.pty.setSize(.{
|
||||||
|
.ws_row = @intCast(u16, grid_size.rows),
|
||||||
|
.ws_col = @intCast(u16, grid_size.columns),
|
||||||
|
.ws_xpixel = @intCast(u16, screen_size.width),
|
||||||
|
.ws_ypixel = @intCast(u16, screen_size.height),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enter the critical area that we want to keep small
|
||||||
|
{
|
||||||
|
self.renderer_state.mutex.lock();
|
||||||
|
defer self.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
|
// We need to setup our render state to store our new pending size
|
||||||
|
self.renderer_state.resize_screen = screen_size;
|
||||||
|
|
||||||
|
// Update the size of our terminal state
|
||||||
|
try self.terminal.resize(self.alloc, grid_size.columns, grid_size.rows);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ThreadData = struct {
|
const ThreadData = struct {
|
||||||
/// Allocator used for the event data
|
/// Allocator used for the event data
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
|
@ -6,10 +6,16 @@ const std = @import("std");
|
|||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const libuv = @import("libuv");
|
const libuv = @import("libuv");
|
||||||
const termio = @import("../termio.zig");
|
const termio = @import("../termio.zig");
|
||||||
|
const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const log = std.log.scoped(.io_thread);
|
const log = std.log.scoped(.io_thread);
|
||||||
|
|
||||||
|
/// The type used for sending messages to the IO thread. For now this is
|
||||||
|
/// hardcoded with a capacity. We can make this a comptime parameter in
|
||||||
|
/// the future if we want it configurable.
|
||||||
|
const Mailbox = BlockingQueue(termio.message.IO, 64);
|
||||||
|
|
||||||
/// The main event loop for the thread. The user data of this loop
|
/// 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
|
/// is always the allocator used to create the loop. This is a convenience
|
||||||
/// so that users of the loop always have an allocator.
|
/// so that users of the loop always have an allocator.
|
||||||
@ -24,6 +30,10 @@ stop: libuv.Async,
|
|||||||
/// The underlying IO implementation.
|
/// The underlying IO implementation.
|
||||||
impl: *termio.Impl,
|
impl: *termio.Impl,
|
||||||
|
|
||||||
|
/// The mailbox that can be used to send this thread messages. Note
|
||||||
|
/// this is a blocking queue so if it is full you will get errors (or block).
|
||||||
|
mailbox: *Mailbox,
|
||||||
|
|
||||||
/// Initialize the thread. This does not START the thread. This only sets
|
/// Initialize the thread. This does not START the thread. This only sets
|
||||||
/// up all the internal state necessary prior to starting the thread. It
|
/// 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.
|
/// is up to the caller to start the thread with the threadMain entrypoint.
|
||||||
@ -60,11 +70,16 @@ pub fn init(
|
|||||||
}
|
}
|
||||||
}).callback);
|
}).callback);
|
||||||
|
|
||||||
|
// The mailbox for messaging this thread
|
||||||
|
var mailbox = try Mailbox.create(alloc);
|
||||||
|
errdefer mailbox.destroy(alloc);
|
||||||
|
|
||||||
return Thread{
|
return Thread{
|
||||||
.loop = loop,
|
.loop = loop,
|
||||||
.wakeup = wakeup_h,
|
.wakeup = wakeup_h,
|
||||||
.stop = stop_h,
|
.stop = stop_h,
|
||||||
.impl = impl,
|
.impl = impl,
|
||||||
|
.mailbox = mailbox,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +110,9 @@ pub fn deinit(self: *Thread) void {
|
|||||||
_ = self.loop.run(.default) catch |err|
|
_ = self.loop.run(.default) catch |err|
|
||||||
log.err("error finalizing event loop: {}", .{err});
|
log.err("error finalizing event loop: {}", .{err});
|
||||||
|
|
||||||
|
// Nothing can possibly access the mailbox anymore, destroy it.
|
||||||
|
self.mailbox.destroy(alloc);
|
||||||
|
|
||||||
// Dealloc our allocator copy
|
// Dealloc our allocator copy
|
||||||
alloc.destroy(alloc_ptr);
|
alloc.destroy(alloc_ptr);
|
||||||
|
|
||||||
@ -127,13 +145,33 @@ fn threadMain_(self: *Thread) !void {
|
|||||||
_ = try self.loop.run(.default);
|
_ = try self.loop.run(.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drain the mailbox, handling all the messages in our terminal implementation.
|
||||||
|
fn drainMailbox(self: *Thread) !void {
|
||||||
|
// This holds the mailbox lock for the duration of the drain. The
|
||||||
|
// expectation is that all our message handlers will be non-blocking
|
||||||
|
// ENOUGH to not mess up throughput on producers.
|
||||||
|
var drain = self.mailbox.drain();
|
||||||
|
defer drain.deinit();
|
||||||
|
|
||||||
|
while (drain.next()) |message| {
|
||||||
|
log.debug("mailbox message={}", .{message});
|
||||||
|
switch (message) {
|
||||||
|
.resize => |v| try self.impl.resize(v.grid_size, v.screen_size),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn wakeupCallback(h: *libuv.Async) void {
|
fn wakeupCallback(h: *libuv.Async) void {
|
||||||
_ = h;
|
const t = h.getData(Thread) orelse {
|
||||||
// const t = h.getData(Thread) orelse {
|
// This shouldn't happen so we log it.
|
||||||
// // This shouldn't happen so we log it.
|
log.warn("wakeup callback fired without data set", .{});
|
||||||
// log.warn("render callback fired without data set", .{});
|
return;
|
||||||
// return;
|
};
|
||||||
// };
|
|
||||||
|
// 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});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stopCallback(h: *libuv.Async) void {
|
fn stopCallback(h: *libuv.Async) void {
|
||||||
|
17
src/termio/message.zig
Normal file
17
src/termio/message.zig
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const renderer = @import("../renderer.zig");
|
||||||
|
const terminal = @import("../terminal/main.zig");
|
||||||
|
|
||||||
|
/// The messages that can be sent to an IO thread.
|
||||||
|
pub const IO = union(enum) {
|
||||||
|
/// Resize the window.
|
||||||
|
resize: struct {
|
||||||
|
grid_size: renderer.GridSize,
|
||||||
|
screen_size: renderer.ScreenSize,
|
||||||
|
},
|
||||||
|
|
||||||
|
// /// Clear the selection
|
||||||
|
// clear_selection: void,
|
||||||
|
//
|
||||||
|
// /// Scroll the viewport
|
||||||
|
// scroll_viewport: terminal.Terminal.ScrollViewport,
|
||||||
|
};
|
Reference in New Issue
Block a user