From f8b305df62205d1a704a6e5d0a2747b546d3a206 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 22 Apr 2022 13:56:39 -0700 Subject: [PATCH] pass around the event loop, setup a timer to prove it works --- src/App.zig | 28 +++++++++++++++++++++++----- src/Grid.zig | 17 ++++++++++++++++- src/Window.zig | 5 +++-- src/libuv/Loop.zig | 17 +++++++++++++++++ src/libuv/Timer.zig | 2 +- src/libuv/handle.zig | 6 +++--- 6 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/App.zig b/src/App.zig index 1fbc9591e..6f3d6fd31 100644 --- a/src/App.zig +++ b/src/App.zig @@ -18,21 +18,30 @@ alloc: Allocator, /// single window operations. window: *Window, -// The main event loop for the application. +// The main event loop for the application. 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, /// Initialize the main app instance. This creates the main window, sets /// up the renderer state, compiles the shaders, etc. This is the primary /// "startup" logic. pub fn init(alloc: Allocator) !App { - // Create the window - var window = try Window.create(alloc); - errdefer window.destroy(); - // Create the event loop var loop = try libuv.Loop.init(alloc); errdefer loop.deinit(alloc); + // 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; + loop.setData(allocPtr); + + // Create the window + var window = try Window.create(alloc, loop); + errdefer window.destroy(); + return App{ .alloc = alloc, .window = window, @@ -42,6 +51,15 @@ pub fn init(alloc: Allocator) !App { pub fn deinit(self: *App) void { self.window.destroy(); + + // 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 unreachable; + + // Dealloc our allocator copy + self.alloc.destroy(self.loop.getData(Allocator).?); + self.loop.deinit(self.alloc); self.* = undefined; } diff --git a/src/Grid.zig b/src/Grid.zig index b5d80378c..63fdbf58f 100644 --- a/src/Grid.zig +++ b/src/Grid.zig @@ -10,6 +10,7 @@ const FontAtlas = @import("FontAtlas.zig"); const Terminal = @import("terminal/Terminal.zig"); const gl = @import("opengl.zig"); const gb = @import("gb_math.zig"); +const libuv = @import("libuv/main.zig"); const log = std.log.scoped(.grid); @@ -34,6 +35,9 @@ texture: gl.Texture, /// The font atlas. font_atlas: FontAtlas, +/// The timer for cursor blining. +cursor_timer: libuv.Timer, + /// The raw structure that maps directly to the buffer sent to the vertex shader. const GPUCell = struct { /// vec2 grid_coord @@ -68,7 +72,11 @@ const GPUCell = struct { mode: u8, }; -pub fn init(alloc: Allocator) !Grid { +pub fn init(alloc: Allocator, loop: libuv.Loop) !Grid { + // Setup the timer for our cursor. + var timer = try libuv.Timer.init(alloc, loop); + errdefer timer.deinit(alloc); + // Initialize our font atlas. We will initially populate the // font atlas with all the visible ASCII characters since they are common. var atlas = try Atlas.init(alloc, 512); @@ -206,10 +214,17 @@ pub fn init(alloc: Allocator) !Grid { .vbo = vbo, .texture = tex, .font_atlas = font, + .cursor_timer = timer, }; } pub fn deinit(self: *Grid) void { + self.cursor_timer.close((struct { + fn callback(t: *libuv.Timer) void { + const alloc = t.loop().getData(Allocator).?; + t.deinit(alloc.*); + } + }).callback); self.font_atlas.atlas.deinit(self.alloc); self.font_atlas.deinit(self.alloc); self.texture.destroy(); diff --git a/src/Window.zig b/src/Window.zig index b93ab4b59..9a19fa376 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -11,6 +11,7 @@ const Allocator = std.mem.Allocator; const Grid = @import("Grid.zig"); const glfw = @import("glfw"); const gl = @import("opengl.zig"); +const libuv = @import("libuv/main.zig"); const Pty = @import("Pty.zig"); const Terminal = @import("terminal/Terminal.zig"); @@ -37,7 +38,7 @@ terminal: Terminal, /// Create a new window. This allocates and returns a pointer because we /// need a stable pointer for user data callbacks. Therefore, a stack-only /// initialization is not currently possible. -pub fn create(alloc: Allocator) !*Window { +pub fn create(alloc: Allocator, loop: libuv.Loop) !*Window { var self = try alloc.create(Window); errdefer alloc.destroy(self); @@ -77,7 +78,7 @@ pub fn create(alloc: Allocator) !*Window { // Create our terminal grid with the initial window size const window_size = try window.getSize(); - var grid = try Grid.init(alloc); + var grid = try Grid.init(alloc, loop); try grid.setScreenSize(.{ .width = window_size.width, .height = window_size.height }); // Create our pty diff --git a/src/libuv/Loop.zig b/src/libuv/Loop.zig index 564993895..4583b0959 100644 --- a/src/libuv/Loop.zig +++ b/src/libuv/Loop.zig @@ -70,6 +70,19 @@ pub fn backendTimeout(self: Loop) c_int { return c.uv_backend_timeout(self.loop); } +/// Sets loop->data to data. +pub fn setData(self: Loop, pointer: ?*anyopaque) void { + c.uv_loop_set_data(self.loop, pointer); +} + +/// Returns loop->data. +pub fn getData(self: Loop, comptime DT: type) ?*DT { + return if (c.uv_loop_get_data(self.loop)) |ptr| + @ptrCast(?*DT, @alignCast(@alignOf(DT), ptr)) + else + null; +} + /// Mode used to run the loop with uv_run(). pub const RunMode = enum(c.uv_run_mode) { default = c.UV_RUN_DEFAULT, @@ -81,6 +94,10 @@ test { var loop = try init(testing.allocator); defer loop.deinit(testing.allocator); + var data: u8 = 42; + loop.setData(&data); + try testing.expect(loop.getData(u8).?.* == 42); + try testing.expect((try loop.backendFd()) > 0); try testing.expectEqual(@as(u32, 0), try loop.run(.nowait)); } diff --git a/src/libuv/Timer.zig b/src/libuv/Timer.zig index 9d2195d28..5b0a288e9 100644 --- a/src/libuv/Timer.zig +++ b/src/libuv/Timer.zig @@ -85,7 +85,7 @@ test "Timer: close callback" { var data: u8 = 42; timer.setData(&data); timer.close((struct { - fn callback(v: Timer) void { + fn callback(v: *Timer) void { var dataPtr = v.getData(u8).?; dataPtr.* = 24; } diff --git a/src/libuv/handle.zig b/src/libuv/handle.zig index d44605ed4..ea23e62b0 100644 --- a/src/libuv/handle.zig +++ b/src/libuv/handle.zig @@ -27,15 +27,15 @@ pub fn Handle(comptime T: type) type { // // In-progress requests, like uv_connect_t or uv_write_t, are cancelled // and have their callbacks called asynchronously with status=UV_ECANCELED. - pub fn close(self: T, comptime cb: ?fn (T) void) void { + pub fn close(self: T, comptime cb: ?fn (*T) void) void { const cbParam = if (cb) |f| (struct { pub fn callback(handle: [*c]c.uv_handle_t) callconv(.C) void { // We get the raw handle, so we need to reconstruct // the T. This is mutable because a lot of the libuv APIs // are non-const but modifying it makes no sense. - const param: T = .{ .handle = @ptrCast(HandleType, handle) }; - @call(.{ .modifier = .always_inline }, f, .{param}); + var param: T = .{ .handle = @ptrCast(HandleType, handle) }; + @call(.{ .modifier = .always_inline }, f, .{¶m}); } }).callback else