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:
Mitchell Hashimoto
2022-04-22 10:01:52 -07:00
parent 001aac26e0
commit cca32c4d1c
6 changed files with 179 additions and 18 deletions

View File

@ -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");

View File

@ -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();
} }

View File

@ -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
View 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();
}

View File

@ -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);

View File

@ -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;
} }