mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-01 05:09:01 +00:00
This commit represents the majority of the work necessary to upgrade Ghostty to use Zig 0.16.0. Key parts: * In addition to its previous responsibilities, the global state now houses state for global I/O implementations and the process environment. It is now also utilized in the main application along with the C library. Where necessary, global state is isolated from key parts of the implementation (e.g., in libghostty subsystems), and it's expected that this list will grow. * We currently manage our own C translation layer where necessary. In these cases, cImport has been removed in favor of the new external translate-c package. Due to fixes that have needed be made to properly translate the dependencies that were swapped out, as mentioned, we have had to backport fixes from the current translate-c package (and the upstream Arocc dependency). We will host this ourselves until Zig 0.17.0 is released with these fixes. * Where necessary (only a small number of cases), some stdlib code from 0.15.2 (and even from 0.17.0) has been taken, adopted, and vendored in lib/compat. Co-authored-by: Leah Amelia Chen <hi@pluie.me>
108 lines
4.3 KiB
Zig
108 lines
4.3 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const global = @import("../global.zig");
|
|
const xev = global.xev;
|
|
const renderer = @import("../renderer.zig");
|
|
const termio = @import("../termio.zig");
|
|
const BlockingQueue = @import("../datastruct/main.zig").BlockingQueue;
|
|
|
|
const log = std.log.scoped(.io_writer);
|
|
|
|
/// A queue used for storing messages that is periodically drained.
|
|
/// Typically used by a multi-threaded application. The capacity is
|
|
/// hardcoded to a value that empirically has made sense for Ghostty usage
|
|
/// but I'm open to changing it with good arguments.
|
|
const Queue = BlockingQueue(termio.Message, 64);
|
|
|
|
/// The location to where write-related messages are sent.
|
|
pub const Mailbox = union(enum) {
|
|
// /// Write messages to an unbounded list backed by an allocator.
|
|
// /// This is useful for single-threaded applications where you're not
|
|
// /// afraid of running out of memory. You should be careful that you're
|
|
// /// processing this in a timely manner though since some heavy workloads
|
|
// /// will produce a LOT of messages.
|
|
// ///
|
|
// /// At the time of authoring this, the primary use case for this is
|
|
// /// testing more than anything, but it probably will have a use case
|
|
// /// in libghostty eventually.
|
|
// unbounded: std.ArrayList(termio.Message),
|
|
|
|
/// Write messages to a SPSC queue for multi-threaded applications.
|
|
spsc: struct {
|
|
queue: *Queue,
|
|
wakeup: xev.Async,
|
|
},
|
|
|
|
/// Init the SPSC writer.
|
|
pub fn initSPSC(alloc: Allocator) !Mailbox {
|
|
var queue = try Queue.create(alloc);
|
|
errdefer queue.destroy(alloc);
|
|
|
|
var wakeup = try xev.Async.init();
|
|
errdefer wakeup.deinit();
|
|
|
|
return .{ .spsc = .{ .queue = queue, .wakeup = wakeup } };
|
|
}
|
|
|
|
pub fn deinit(self: *Mailbox, alloc: Allocator) void {
|
|
switch (self.*) {
|
|
.spsc => |*v| {
|
|
v.queue.destroy(alloc);
|
|
v.wakeup.deinit();
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Sends the given message without notifying there are messages.
|
|
///
|
|
/// If the optional mutex is given, it must already be LOCKED. If the
|
|
/// send would block, we'll unlock this mutex, resend the message, and
|
|
/// lock it again. This handles an edge case where queues are full.
|
|
/// This may not apply to all writer types.
|
|
pub fn send(
|
|
self: *Mailbox,
|
|
msg: termio.Message,
|
|
mutex: ?*std.Io.Mutex,
|
|
) void {
|
|
switch (self.*) {
|
|
.spsc => |*mb| send: {
|
|
// Try to write to the queue with an instant timeout. This is the
|
|
// fast path because we can queue without a lock.
|
|
if (mb.queue.push(global.io(), msg, .{ .instant = {} }) > 0) break :send;
|
|
|
|
// If we enter this conditional, the queue is full. We wake up
|
|
// the writer thread so that it can process messages to clear up
|
|
// space. However, the writer thread may require the renderer
|
|
// lock so we need to unlock.
|
|
mb.wakeup.notify() catch |err| {
|
|
log.warn("failed to wake up writer, data will be dropped err={}", .{err});
|
|
return;
|
|
};
|
|
|
|
// Unlock the renderer state so the writer thread can acquire it.
|
|
// Then try to queue our message before continuing. This is a very
|
|
// slow path because we are having a lot of contention for data.
|
|
// But this only gets triggered in certain pathological cases.
|
|
//
|
|
// Note that writes themselves don't require a lock, but there
|
|
// are other messages in the writer queue (resize, focus) that
|
|
// could acquire the lock. This is why we have to release our lock
|
|
// here.
|
|
if (mutex) |m| m.unlock(global.io());
|
|
defer if (mutex) |m| m.lockUncancelable(global.io());
|
|
_ = mb.queue.push(global.io(), msg, .{ .forever = {} });
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Notify that there are new messages. This may be a noop depending
|
|
/// on the writer type.
|
|
pub fn notify(self: *Mailbox) void {
|
|
switch (self.*) {
|
|
.spsc => |*v| v.wakeup.notify() catch |err| {
|
|
log.warn("failed to notify writer, data will be dropped err={}", .{err});
|
|
},
|
|
}
|
|
}
|
|
};
|