renderer mailbox, focus message

This commit is contained in:
Mitchell Hashimoto
2022-11-05 18:51:39 -07:00
parent 9a44e45785
commit e2d8ffc3c1
7 changed files with 77 additions and 12 deletions

View File

@ -394,7 +394,6 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo
.renderer_thread = render_thread, .renderer_thread = render_thread,
.renderer_state = .{ .renderer_state = .{
.mutex = mutex, .mutex = mutex,
.focused = true,
.resize_screen = screen_size, .resize_screen = screen_size,
.cursor = .{ .cursor = .{
.style = .blinking_block, .style = .blinking_block,
@ -917,6 +916,11 @@ fn focusCallback(window: glfw.Window, focused: bool) void {
const win = window.getUserPointer(Window) orelse return; const win = window.getUserPointer(Window) orelse return;
// Notify our render thread of the new state
_ = win.renderer_thread.mailbox.push(.{
.focus = focused,
}, .{ .forever = {} });
// We have to schedule a render because no matter what we're changing // We have to schedule a render because no matter what we're changing
// the cursor. If we're focused its reappearing, if we're not then // the cursor. If we're focused its reappearing, if we're not then
// its changing to hollow and not blinking. // its changing to hollow and not blinking.
@ -926,11 +930,6 @@ fn focusCallback(window: glfw.Window, focused: bool) void {
win.terminal_cursor.startTimer() catch unreachable win.terminal_cursor.startTimer() catch unreachable
else else
win.terminal_cursor.stopTimer() catch unreachable; win.terminal_cursor.stopTimer() catch unreachable;
// We are modifying renderer state from here on out
win.renderer_state.mutex.lock();
defer win.renderer_state.mutex.unlock();
win.renderer_state.focused = focused;
} }
fn refreshCallback(window: glfw.Window) void { fn refreshCallback(window: glfw.Window) void {

View File

@ -10,6 +10,7 @@
const builtin = @import("builtin"); const builtin = @import("builtin");
pub usingnamespace @import("renderer/cursor.zig"); pub usingnamespace @import("renderer/cursor.zig");
pub usingnamespace @import("renderer/message.zig");
pub usingnamespace @import("renderer/size.zig"); pub usingnamespace @import("renderer/size.zig");
pub const Metal = @import("renderer/Metal.zig"); pub const Metal = @import("renderer/Metal.zig");
pub const OpenGL = @import("renderer/OpenGL.zig"); pub const OpenGL = @import("renderer/OpenGL.zig");

View File

@ -33,6 +33,9 @@ alloc: std.mem.Allocator,
/// Current cell dimensions for this grid. /// Current cell dimensions for this grid.
cell_size: renderer.CellSize, cell_size: renderer.CellSize,
/// True if the window is focused
focused: bool,
/// Whether the cursor is visible or not. This is used to control cursor /// Whether the cursor is visible or not. This is used to control cursor
/// blinking. /// blinking.
cursor_visible: bool, cursor_visible: bool,
@ -205,6 +208,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !Metal {
.cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height }, .cell_size = .{ .width = metrics.cell_width, .height = metrics.cell_height },
.background = .{ .r = 0, .g = 0, .b = 0 }, .background = .{ .r = 0, .g = 0, .b = 0 },
.foreground = .{ .r = 255, .g = 255, .b = 255 }, .foreground = .{ .r = 255, .g = 255, .b = 255 },
.focused = true,
.cursor_visible = true, .cursor_visible = true,
.cursor_style = .box, .cursor_style = .box,
@ -290,6 +294,11 @@ pub fn threadExit(self: *const Metal) void {
// Metal requires no per-thread state. // Metal requires no per-thread state.
} }
/// Callback when the focus changes for the terminal this is rendering.
pub fn setFocus(self: *Metal, focus: bool) !void {
self.focused = focus;
}
/// The primary render callback that is completely thread-safe. /// The primary render callback that is completely thread-safe.
pub fn render( pub fn render(
self: *Metal, self: *Metal,
@ -315,7 +324,7 @@ pub fn render(
defer state.resize_screen = null; defer state.resize_screen = null;
// Setup our cursor state // Setup our cursor state
if (state.focused) { if (self.focused) {
self.cursor_visible = state.cursor.visible and !state.cursor.blink; self.cursor_visible = state.cursor.visible and !state.cursor.blink;
self.cursor_style = renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box; self.cursor_style = renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box;
} else { } else {

View File

@ -74,6 +74,9 @@ foreground: terminal.color.RGB,
/// Default background color /// Default background color
background: terminal.color.RGB, background: terminal.color.RGB,
/// True if the window is focused
focused: bool,
/// The raw structure that maps directly to the buffer sent to the vertex shader. /// The raw structure that maps directly to the buffer sent to the vertex shader.
/// This must be "extern" so that the field order is not reordered by the /// This must be "extern" so that the field order is not reordered by the
/// Zig compiler. /// Zig compiler.
@ -292,6 +295,7 @@ pub fn init(alloc: Allocator, font_group: *font.GroupCache) !OpenGL {
.cursor_style = .box, .cursor_style = .box,
.background = .{ .r = 0, .g = 0, .b = 0 }, .background = .{ .r = 0, .g = 0, .b = 0 },
.foreground = .{ .r = 255, .g = 255, .b = 255 }, .foreground = .{ .r = 255, .g = 255, .b = 255 },
.focused = true,
}; };
} }
@ -432,6 +436,11 @@ pub fn threadExit(self: *const OpenGL) void {
glfw.makeContextCurrent(null) catch {}; glfw.makeContextCurrent(null) catch {};
} }
/// Callback when the focus changes for the terminal this is rendering.
pub fn setFocus(self: *OpenGL, focus: bool) !void {
self.focused = focus;
}
/// The primary render callback that is completely thread-safe. /// The primary render callback that is completely thread-safe.
pub fn render( pub fn render(
self: *OpenGL, self: *OpenGL,
@ -455,7 +464,7 @@ pub fn render(
defer state.resize_screen = null; defer state.resize_screen = null;
// Setup our cursor state // Setup our cursor state
if (state.focused) { if (self.focused) {
self.cursor_visible = state.cursor.visible and !state.cursor.blink; self.cursor_visible = state.cursor.visible and !state.cursor.blink;
self.cursor_style = renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box; self.cursor_style = renderer.CursorStyle.fromTerminal(state.cursor.style) orelse .box;
} else { } else {

View File

@ -12,9 +12,6 @@ const renderer = @import("../renderer.zig");
/// state (i.e. the terminal, devmode, etc. values). /// state (i.e. the terminal, devmode, etc. values).
mutex: *std.Thread.Mutex, mutex: *std.Thread.Mutex,
/// True if the window is focused
focused: bool,
/// A new screen size if the screen was resized. /// A new screen size if the screen was resized.
resize_screen: ?renderer.ScreenSize, resize_screen: ?renderer.ScreenSize,

View File

@ -7,11 +7,16 @@ const builtin = @import("builtin");
const glfw = @import("glfw"); const glfw = @import("glfw");
const libuv = @import("libuv"); const libuv = @import("libuv");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const gl = @import("../opengl.zig"); const BlockingQueue = @import("../blocking_queue.zig").BlockingQueue;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const log = std.log.scoped(.renderer_thread); const log = std.log.scoped(.renderer_thread);
/// The type used for sending messages to the IO thread. For now this is
/// hardcoded with a capacity. We can make this a comptime parameter in
/// the future if we want it configurable.
const Mailbox = BlockingQueue(renderer.Message, 64);
/// The main event loop for the application. The user data of this loop /// 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 /// is always the allocator used to create the loop. This is a convenience
/// so that users of the loop always have an allocator. /// so that users of the loop always have an allocator.
@ -36,6 +41,10 @@ renderer: *renderer.Renderer,
/// Pointer to the shared state that is used to generate the final render. /// Pointer to the shared state that is used to generate the final render.
state: *renderer.State, state: *renderer.State,
/// 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 thread. This does not START the thread. This only sets /// Initialize the thread. This does not START the thread. This only sets
/// up all the internal state necessary prior to starting the thread. It /// up all the internal state necessary prior to starting the thread. It
/// is up to the caller to start the thread with the threadMain entrypoint. /// is up to the caller to start the thread with the threadMain entrypoint.
@ -83,6 +92,10 @@ pub fn init(
} }
}).callback); }).callback);
// The mailbox for messaging this thread
var mailbox = try Mailbox.create(alloc);
errdefer mailbox.destroy(alloc);
return Thread{ return Thread{
.loop = loop, .loop = loop,
.wakeup = wakeup_h, .wakeup = wakeup_h,
@ -91,6 +104,7 @@ pub fn init(
.window = window, .window = window,
.renderer = renderer_impl, .renderer = renderer_impl,
.state = state, .state = state,
.mailbox = mailbox,
}; };
} }
@ -127,6 +141,9 @@ pub fn deinit(self: *Thread) void {
_ = self.loop.run(.default) catch |err| _ = self.loop.run(.default) catch |err|
log.err("error finalizing event loop: {}", .{err}); log.err("error finalizing event loop: {}", .{err});
// Nothing can possibly access the mailbox anymore, destroy it.
self.mailbox.destroy(alloc);
// Dealloc our allocator copy // Dealloc our allocator copy
alloc.destroy(alloc_ptr); alloc.destroy(alloc_ptr);
@ -164,6 +181,23 @@ fn threadMain_(self: *Thread) !void {
_ = try self.loop.run(.default); _ = try self.loop.run(.default);
} }
/// Drain the mailbox.
fn drainMailbox(self: *Thread) !void {
// This holds the mailbox lock for the duration of the drain. The
// expectation is that all our message handlers will be non-blocking
// ENOUGH to not mess up throughput on producers.
var drain = self.mailbox.drain();
defer drain.deinit();
while (drain.next()) |message| {
log.debug("mailbox message={}", .{message});
switch (message) {
.focus => |v| try self.renderer.setFocus(v),
}
}
}
fn wakeupCallback(h: *libuv.Async) void { fn wakeupCallback(h: *libuv.Async) void {
const t = h.getData(Thread) orelse { const t = h.getData(Thread) orelse {
// This shouldn't happen so we log it. // This shouldn't happen so we log it.
@ -171,6 +205,11 @@ fn wakeupCallback(h: *libuv.Async) void {
return; return;
}; };
// When we wake up, we check the mailbox. Mailbox producers should
// wake up our thread after publishing.
t.drainMailbox() catch |err|
log.err("error draining mailbox err={}", .{err});
// If the timer is already active then we don't have to do anything. // If the timer is already active then we don't have to do anything.
const active = t.render_h.isActive() catch true; const active = t.render_h.isActive() catch true;
if (active) return; if (active) return;

11
src/renderer/message.zig Normal file
View File

@ -0,0 +1,11 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
/// The messages that can be sent to a renderer thread.
pub const Message = union(enum) {
/// A change in state in the window focus that this renderer is
/// rendering within. This is only sent when a change is detected so
/// the renderer is expected to handle all of these.
focus: bool,
};