mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
embedded libuv loop. still some issues:
1. 100% CPU if no handles/requests 2. slow to exit cause it waits for the next tick
This commit is contained in:
@ -26,6 +26,7 @@ pub fn build(b: *std.build.Builder) !void {
|
|||||||
ftlib.link(exe);
|
ftlib.link(exe);
|
||||||
|
|
||||||
const libuv = try uv.create(b, target, mode);
|
const libuv = try uv.create(b, target, mode);
|
||||||
|
libuv.link(exe);
|
||||||
|
|
||||||
// stb if we need it
|
// stb if we need it
|
||||||
// exe.addIncludeDir("vendor/stb");
|
// exe.addIncludeDir("vendor/stb");
|
||||||
|
50
src/App.zig
50
src/App.zig
@ -5,7 +5,11 @@ const App = @This();
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const glfw = @import("glfw");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
|
const libuv = @import("libuv/main.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.app);
|
||||||
|
|
||||||
/// General purpose allocator
|
/// General purpose allocator
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
@ -14,24 +18,66 @@ alloc: Allocator,
|
|||||||
/// single window operations.
|
/// single window operations.
|
||||||
window: *Window,
|
window: *Window,
|
||||||
|
|
||||||
|
// The main event loop for the application.
|
||||||
|
loop: libuv.Loop,
|
||||||
|
|
||||||
/// Initialize the main app instance. This creates the main window, sets
|
/// Initialize the main app instance. This creates the main window, sets
|
||||||
/// up the renderer state, compiles the shaders, etc. This is the primary
|
/// up the renderer state, compiles the shaders, etc. This is the primary
|
||||||
/// "startup" logic.
|
/// "startup" logic.
|
||||||
pub fn init(alloc: Allocator) !App {
|
pub fn init(alloc: Allocator) !App {
|
||||||
// Create the window
|
// Create the window
|
||||||
const window = try Window.create(alloc);
|
var window = try Window.create(alloc);
|
||||||
|
errdefer window.destroy();
|
||||||
|
|
||||||
|
// Create the event loop
|
||||||
|
var loop = try libuv.Loop.init(alloc);
|
||||||
|
errdefer loop.deinit(alloc);
|
||||||
|
|
||||||
return App{
|
return App{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.window = window,
|
.window = window,
|
||||||
|
.loop = loop,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *App) void {
|
pub fn deinit(self: *App) void {
|
||||||
self.window.destroy();
|
self.window.destroy();
|
||||||
|
self.loop.deinit(self.alloc);
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(self: App) !void {
|
pub fn run(self: App) !void {
|
||||||
try self.window.run();
|
// We are embedding two event loops: glfw and libuv. To do this, we
|
||||||
|
// create a separate thread that watches for libuv events and notifies
|
||||||
|
// glfw to wake up so we can run the libuv tick.
|
||||||
|
var embed = try libuv.Embed.init(self.alloc, self.loop, (struct {
|
||||||
|
fn callback() void {
|
||||||
|
glfw.postEmptyEvent() catch unreachable;
|
||||||
|
}
|
||||||
|
}).callback);
|
||||||
|
defer embed.deinit(self.alloc);
|
||||||
|
try embed.start();
|
||||||
|
errdefer embed.stop() catch unreachable;
|
||||||
|
|
||||||
|
// We need at least one handle in the event loop at all times
|
||||||
|
var timer = try libuv.Timer.init(self.alloc, self.loop);
|
||||||
|
defer timer.deinit(self.alloc);
|
||||||
|
try timer.start((struct {
|
||||||
|
fn callback(_: libuv.Timer) void {
|
||||||
|
log.info("timer tick", .{});
|
||||||
|
}
|
||||||
|
}).callback, 5000, 5000);
|
||||||
|
|
||||||
|
while (!self.window.shouldClose()) {
|
||||||
|
try self.window.run();
|
||||||
|
|
||||||
|
// Block for any glfw events. This may also be an "empty" event
|
||||||
|
// posted by the libuv watcher so that we trigger a libuv loop tick.
|
||||||
|
try glfw.waitEvents();
|
||||||
|
|
||||||
|
// Run the libuv loop
|
||||||
|
try embed.loopRun();
|
||||||
|
}
|
||||||
|
|
||||||
|
try embed.stop();
|
||||||
}
|
}
|
||||||
|
@ -119,19 +119,20 @@ pub fn destroy(self: *Window) void {
|
|||||||
self.alloc.destroy(self);
|
self.alloc.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn shouldClose(self: Window) bool {
|
||||||
|
return self.window.shouldClose();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(self: Window) !void {
|
pub fn run(self: Window) !void {
|
||||||
while (!self.window.shouldClose()) {
|
// Set our background
|
||||||
// Set our background
|
gl.clearColor(0.2, 0.3, 0.3, 1.0);
|
||||||
gl.clearColor(0.2, 0.3, 0.3, 1.0);
|
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
||||||
gl.clear(gl.c.GL_COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
// Render the grid
|
// Render the grid
|
||||||
try self.grid.render();
|
try self.grid.render();
|
||||||
|
|
||||||
// Swap
|
// Swap
|
||||||
try self.window.swapBuffers();
|
try self.window.swapBuffers();
|
||||||
try glfw.waitEvents();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
|
fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
|
||||||
|
109
src/libuv/Embed.zig
Normal file
109
src/libuv/Embed.zig
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
//! This has a helper for embedding libuv in another event loop.
|
||||||
|
//! This is an extension of libuv and not a helper built-in to libuv
|
||||||
|
//! itself, although it uses official APIs of libuv to enable the
|
||||||
|
//! functionality.
|
||||||
|
|
||||||
|
const Embed = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const testing = std.testing;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const Loop = @import("Loop.zig");
|
||||||
|
const Sem = @import("Sem.zig");
|
||||||
|
const Thread = @import("Thread.zig");
|
||||||
|
|
||||||
|
const TerminateAtomic = std.atomic.Atomic(bool);
|
||||||
|
|
||||||
|
loop: Loop,
|
||||||
|
sem: Sem,
|
||||||
|
terminate: TerminateAtomic,
|
||||||
|
callback: fn () void,
|
||||||
|
thread: ?Thread,
|
||||||
|
|
||||||
|
/// Initialize a new embedder. The callback is called when libuv should
|
||||||
|
/// tick. The callback should be as fast as possible.
|
||||||
|
pub fn init(alloc: Allocator, loop: Loop, callback: fn () void) !Embed {
|
||||||
|
return Embed{
|
||||||
|
.loop = loop,
|
||||||
|
.sem = try Sem.init(alloc, 0),
|
||||||
|
.terminate = TerminateAtomic.init(false),
|
||||||
|
.callback = callback,
|
||||||
|
.thread = null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deinit the embed struct. This will not automatically terminate
|
||||||
|
/// the embed thread. You must call stop manually.
|
||||||
|
pub fn deinit(self: *Embed, alloc: Allocator) void {
|
||||||
|
std.debug.assert(self.thread == null);
|
||||||
|
self.sem.deinit(alloc);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the thread that runs the embed logic and calls callback
|
||||||
|
/// when the libuv loop should tick. This must only be called once.
|
||||||
|
pub fn start(self: *Embed) !void {
|
||||||
|
self.thread = try Thread.initData(self, Embed.threadMain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop stops the embed thread and blocks until the thread joins.
|
||||||
|
pub fn stop(self: *Embed) !void {
|
||||||
|
var thread = self.thread orelse return;
|
||||||
|
|
||||||
|
// Mark that we want to terminate
|
||||||
|
self.terminate.store(true, .SeqCst);
|
||||||
|
|
||||||
|
// Post to the semaphore to ensure that any waits are processed.
|
||||||
|
self.sem.post();
|
||||||
|
|
||||||
|
// Wait
|
||||||
|
try thread.join();
|
||||||
|
self.thread = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// loopRun runs the next tick of the libuv event loop. This should be
|
||||||
|
/// called by the main loop thread as a result of callback making some
|
||||||
|
/// signal. This should NOT be called from callback.
|
||||||
|
pub fn loopRun(self: Embed) !void {
|
||||||
|
_ = try self.loop.run(.nowait);
|
||||||
|
self.sem.post();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn threadMain(self: *Embed) void {
|
||||||
|
while (self.terminate.load(.SeqCst) == false) {
|
||||||
|
const fd = self.loop.backendFd() catch unreachable;
|
||||||
|
const timeout = self.loop.backendTimeout();
|
||||||
|
switch (builtin.os.tag) {
|
||||||
|
// epoll
|
||||||
|
.linux => {
|
||||||
|
std.log.info("FOO {} {}", .{ fd, timeout });
|
||||||
|
var ev: [1]std.os.linux.epoll_event = undefined;
|
||||||
|
while (std.os.epoll_wait(fd, &ev, timeout) == -1) {}
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("unsupported libuv Embed platform"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call our trigger
|
||||||
|
self.callback();
|
||||||
|
|
||||||
|
// Wait for libuv to run a tick
|
||||||
|
self.sem.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Embed" {
|
||||||
|
var loop = try Loop.init(testing.allocator);
|
||||||
|
defer loop.deinit(testing.allocator);
|
||||||
|
|
||||||
|
var embed = try init(testing.allocator, loop, (struct {
|
||||||
|
fn callback() void {}
|
||||||
|
}).callback);
|
||||||
|
defer embed.deinit(testing.allocator);
|
||||||
|
|
||||||
|
// This just tests that the thread can start and then stop.
|
||||||
|
// It doesn't do much else at the moment
|
||||||
|
try embed.start();
|
||||||
|
try embed.stop();
|
||||||
|
}
|
@ -40,7 +40,7 @@ pub fn alive(self: Loop) !bool {
|
|||||||
/// This function runs the event loop. See RunMode for mode documentation.
|
/// This function runs the event loop. See RunMode for mode documentation.
|
||||||
///
|
///
|
||||||
/// This is not reentrant. It must not be called from a callback.
|
/// This is not reentrant. It must not be called from a callback.
|
||||||
pub fn run(self: *Loop, mode: RunMode) !u32 {
|
pub fn run(self: Loop, mode: RunMode) !u32 {
|
||||||
const res = c.uv_run(self.loop, @enumToInt(mode));
|
const res = c.uv_run(self.loop, @enumToInt(mode));
|
||||||
try errors.convertError(res);
|
try errors.convertError(res);
|
||||||
return @intCast(u32, res);
|
return @intCast(u32, res);
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
const Loop = @import("Loop.zig");
|
pub const Loop = @import("Loop.zig");
|
||||||
const Timer = @import("Timer.zig");
|
pub const Timer = @import("Timer.zig");
|
||||||
const Sem = @import("Sem.zig");
|
pub const Sem = @import("Sem.zig");
|
||||||
const Thread = @import("Thread.zig");
|
pub const Thread = @import("Thread.zig");
|
||||||
const Error = @import("error.zig").Error;
|
pub const Error = @import("error.zig").Error;
|
||||||
|
|
||||||
|
pub const Embed = @import("Embed.zig");
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = Loop;
|
_ = Loop;
|
||||||
@ -10,4 +12,6 @@ test {
|
|||||||
_ = Sem;
|
_ = Sem;
|
||||||
_ = Thread;
|
_ = Thread;
|
||||||
_ = Error;
|
_ = Error;
|
||||||
|
|
||||||
|
_ = Embed;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user