diff --git a/src/App.zig b/src/App.zig index 89e96192e..c80665577 100644 --- a/src/App.zig +++ b/src/App.zig @@ -9,46 +9,111 @@ const glfw = @import("glfw"); const Window = @import("Window.zig"); const tracy = @import("tracy"); const Config = @import("config.zig").Config; +const BlockingQueue = @import("./blocking_queue.zig").BlockingQueue; const log = std.log.scoped(.app); +const WindowList = std.ArrayListUnmanaged(*Window); + +/// The type used for sending messages to the app thread. +pub const Mailbox = BlockingQueue(Message, 64); + /// General purpose allocator alloc: Allocator, -/// The primary window for the application. We currently support only -/// single window operations. -window: *Window, +/// The list of windows that are currently open +windows: WindowList, // The configuration for the app. config: *const Config, +/// 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 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, config: *const Config) !App { - // Create the window + // The mailbox for messaging this thread + var mailbox = try Mailbox.create(alloc); + errdefer mailbox.destroy(alloc); + + // Create the first window var window = try Window.create(alloc, config); errdefer window.destroy(); + // Create our windows list and add our initial window. + var windows: WindowList = .{}; + errdefer windows.deinit(alloc); + try windows.append(alloc, window); + return App{ .alloc = alloc, - .window = window, + .windows = windows, .config = config, + .mailbox = mailbox, }; } pub fn deinit(self: *App) void { - self.window.destroy(); + // Clean up all our windows + for (self.windows.items) |window| window.destroy(); + self.windows.deinit(self.alloc); + self.mailbox.destroy(self.alloc); self.* = undefined; } -pub fn run(self: App) !void { - while (!self.window.shouldClose()) { - // 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. +/// Wake up the app event loop. This should be called after any messages +/// are sent to the mailbox. +pub fn wakeup(self: App) void { + _ = self; + glfw.postEmptyEvent() catch {}; +} + +/// Run the main event loop for the application. This blocks until the +/// application quits or every window is closed. +pub fn run(self: *App) !void { + while (self.windows.items.len > 0) { + // Block for any glfw events. try glfw.waitEvents(); // Mark this so we're in a totally different "frame" tracy.frameMark(); + + // If any windows are closing, destroy them + var i: usize = 0; + while (i < self.windows.items.len) { + const window = self.windows.items[i]; + if (window.shouldClose()) { + window.destroy(); + _ = self.windows.swapRemove(i); + continue; + } + + i += 1; + } + + // Drain our mailbox + try self.drainMailbox(); } } + +/// Drain the mailbox. +fn drainMailbox(self: *App) !void { + var drain = self.mailbox.drain(); + defer drain.deinit(); + + while (drain.next()) |message| { + log.debug("mailbox message={}", .{message}); + switch (message) { + .new_window => unreachable, + } + } +} + +/// The message types that can be sent to the app thread. +pub const Message = union(enum) { + /// Create a new terminal window. + new_window: void, +}; diff --git a/src/Window.zig b/src/Window.zig index cd899c086..83237d391 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -136,6 +136,10 @@ pub fn create(alloc: Allocator, config: *const Config) !*Window { }; // Find all the fonts for this window + // + // Future: we can share the font group amongst all windows to save + // some new window init time and some memory. This will require making + // thread-safe changes to font structs. var font_lib = try font.Library.init(); errdefer font_lib.deinit(); var font_group = try alloc.create(font.GroupCache);