From 40351f85bac27111b099309e64365f79a210cc47 Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Wed, 25 Jan 2023 10:23:28 -0700 Subject: [PATCH] core: make Core.pollEvents return an iterator, remove Core.hasEvent After this change: * `Core.pollEvents` returns an iterator. At the time of polling events, Mach core will perform work to poll for events, handle resizing of the framebuffer, etc. and the iterator allows the caller to consume all available events. * The event queue is now baced by a `std.fifo.LinearFifo`, which removes the need for dynamic allocation of each event. Instead, the event queue starts with a generous size suitable for most high-end gaming setups (high-precision mouse, etc.) and can grow, but never shrink, up to the maximum event queue size experienced by the app within any given frame. Effectively, this means we find the maximum capacity needed to store events and avoid runtime allocations. * `Core.hasEvent` is removed. Signed-off-by: Stephen Gutekanst --- libs/core/src/Core.zig | 14 +- libs/core/src/platform.zig | 1 - libs/core/src/platform/native/Core.zig | 45 +++--- libs/core/src/platform/wasm/Core.zig | 216 +++++++++++++------------ 4 files changed, 142 insertions(+), 134 deletions(-) diff --git a/libs/core/src/Core.zig b/libs/core/src/Core.zig index fa8719f0..6ddaa40d 100644 --- a/libs/core/src/Core.zig +++ b/libs/core/src/Core.zig @@ -24,12 +24,16 @@ pub fn deinit(core: *Core) void { return core.internal.deinit(); } -pub fn hasEvent(core: *Core) bool { - return core.internal.hasEvent(); -} +pub const EventIterator = struct { + internal: platform.Core.EventIterator, -pub fn pollEvents(core: *Core) ?Event { - return core.internal.pollEvents(); + pub inline fn next(self: *EventIterator) ?Event { + return self.internal.next(); + } +}; + +pub inline fn pollEvents(core: *Core) EventIterator { + return .{ .internal = core.internal.pollEvents() }; } /// Returns the framebuffer size, in subpixel units. diff --git a/libs/core/src/platform.zig b/libs/core/src/platform.zig index 0c85cd27..e0c22973 100644 --- a/libs/core/src/platform.zig +++ b/libs/core/src/platform.zig @@ -14,7 +14,6 @@ comptime { // Core assertHasDecl(@This().Core, "init"); assertHasDecl(@This().Core, "deinit"); - assertHasDecl(@This().Core, "hasEvent"); assertHasDecl(@This().Core, "pollEvents"); assertHasDecl(@This().Core, "framebufferSize"); diff --git a/libs/core/src/platform/native/Core.zig b/libs/core/src/platform/native/Core.zig index f27136c6..b61e3836 100644 --- a/libs/core/src/platform/native/Core.zig +++ b/libs/core/src/platform/native/Core.zig @@ -45,8 +45,15 @@ cursors_tried: [@typeInfo(CursorShape).Enum.fields.len]bool, linux_gamemode: ?bool, -const EventQueue = std.TailQueue(Event); -const EventNode = EventQueue.Node; +const EventQueue = std.fifo.LinearFifo(Event, .Dynamic); + +pub const EventIterator = struct { + queue: *EventQueue, + + pub inline fn next(self: *EventIterator) ?Event { + return self.queue.readItem(); + } +}; const UserPtr = struct { self: *Core, @@ -153,6 +160,14 @@ pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void { }; const swap_chain = gpu_device.createSwapChain(surface, &swap_chain_desc); + // The initial capacity we choose for the event queue is 2x our maximum expected event rate per + // frame. Specifically, 1000hz mouse updates are likely the maximum event rate we will encounter + // so we anticipate 2x that. If the event rate is higher than this per frame, it will grow to + // that maximum (we never shrink the event queue capacity in order to avoid allocations causing + // any stutter.) + var events = EventQueue.init(allocator); + try events.ensureTotalCapacity(2048); + core.* = .{ .allocator = allocator, .window = window, @@ -166,7 +181,7 @@ pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void { .swap_chain = swap_chain, .swap_chain_desc = swap_chain_desc, - .events = .{}, + .events = events, .wait_timeout = 0.0, .last_size = window.getSize(), @@ -292,9 +307,8 @@ fn initCallbacks(self: *Core) void { } fn pushEvent(self: *Core, event: Event) void { - const node = self.allocator.create(EventNode) catch unreachable; - node.* = .{ .data = event }; - self.events.append(node); + // TODO(core): handle OOM via error flag + self.events.writeItem(event) catch unreachable; } pub fn deinit(self: *Core) void { @@ -303,10 +317,7 @@ pub fn deinit(self: *Core) void { cur.destroy(); } } - - while (self.events.popFirst()) |ev| { - self.allocator.destroy(ev); - } + self.events.deinit(); if (builtin.os.tag == .linux and self.linux_gamemode != null and @@ -314,11 +325,7 @@ pub fn deinit(self: *Core) void { deinitLinuxGamemode(); } -pub fn hasEvent(self: *Core) bool { - return self.events.first != null; -} - -pub fn pollEvents(self: *Core) ?Event { +pub inline fn pollEvents(self: *Core) EventIterator { if (self.wait_timeout > 0.0) { if (self.wait_timeout == std.math.inf(f64)) { // Wait for an event @@ -361,13 +368,7 @@ pub fn pollEvents(self: *Core) ?Event { self.pushEvent(.close); } - if (self.events.popFirst()) |n| { - const data = n.data; - self.allocator.destroy(n); - return data; - } - - return null; + return EventIterator{ .queue = &self.events }; } pub fn shouldClose(self: *Core) bool { diff --git a/libs/core/src/platform/wasm/Core.zig b/libs/core/src/platform/wasm/Core.zig index dad19367..e46f7fd5 100644 --- a/libs/core/src/platform/wasm/Core.zig +++ b/libs/core/src/platform/wasm/Core.zig @@ -25,6 +25,114 @@ id: js.CanvasId, last_cursor_position: Position, last_key_mods: KeyMods, +pub const EventIterator = struct { + core: *Core, + + pub inline fn next(self: *EventIterator) ?Event { + const event_int = js.machEventShift(); + if (event_int == -1) return null; + + const event_type = @intToEnum(std.meta.Tag(Event), event_int); + return switch (event_type) { + .key_press, .key_repeat => blk: { + const key = @intToEnum(Key, js.machEventShift()); + switch (key) { + .left_shift, .right_shift => self.last_key_mods.shift = true, + .left_control, .right_control => self.last_key_mods.control = true, + .left_alt, .right_alt => self.last_key_mods.alt = true, + .left_super, .right_super => self.last_key_mods.super = true, + .caps_lock => self.last_key_mods.caps_lock = true, + .num_lock => self.last_key_mods.num_lock = true, + else => {}, + } + break :blk switch (event_type) { + .key_press => Event{ + .key_press = .{ + .key = key, + .mods = self.last_key_mods, + }, + }, + .key_repeat => Event{ + .key_repeat = .{ + .key = key, + .mods = self.last_key_mods, + }, + }, + else => unreachable, + }; + }, + .key_release => blk: { + const key = @intToEnum(Key, js.machEventShift()); + switch (key) { + .left_shift, .right_shift => self.last_key_mods.shift = false, + .left_control, .right_control => self.last_key_mods.control = false, + .left_alt, .right_alt => self.last_key_mods.alt = false, + .left_super, .right_super => self.last_key_mods.super = false, + .caps_lock => self.last_key_mods.caps_lock = false, + .num_lock => self.last_key_mods.num_lock = false, + else => {}, + } + break :blk Event{ + .key_release = .{ + .key = key, + .mods = self.last_key_mods, + }, + }; + }, + .mouse_motion => blk: { + const x = @intToFloat(f64, js.machEventShift()); + const y = @intToFloat(f64, js.machEventShift()); + self.last_cursor_position = .{ + .x = x, + .y = y, + }; + break :blk Event{ + .mouse_motion = .{ + .pos = .{ + .x = x, + .y = y, + }, + }, + }; + }, + .mouse_press => Event{ + .mouse_press = .{ + .button = toMachButton(js.machEventShift()), + .pos = self.last_cursor_position, + .mods = self.last_key_mods, + }, + }, + .mouse_release => Event{ + .mouse_release = .{ + .button = toMachButton(js.machEventShift()), + .pos = self.last_cursor_position, + .mods = self.last_key_mods, + }, + }, + .mouse_scroll => Event{ + .mouse_scroll = .{ + .xoffset = @floatCast(f32, std.math.sign(js.machEventShiftFloat())), + .yoffset = @floatCast(f32, std.math.sign(js.machEventShiftFloat())), + }, + }, + .framebuffer_resize => blk: { + const width = @intCast(u32, js.machEventShift()); + const height = @intCast(u32, js.machEventShift()); + const pixel_ratio = @intCast(u32, js.machEventShift()); + break :blk Event{ + .framebuffer_resize = .{ + .width = width * pixel_ratio, + .height = height * pixel_ratio, + }, + }; + }, + .focus_gained => Event.focus_gained, + .focus_lost => Event.focus_lost, + else => null, + }; + } +}; + pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void { _ = options; var selector = [1]u8{0} ** 15; @@ -54,112 +162,8 @@ pub fn deinit(self: *Core) void { js.machCanvasDeinit(self.id); } -pub fn hasEvent(_: *Core) bool { - return js.machHasEvent(); -} - -pub fn pollEvents(self: *Core) ?Event { - const event_int = js.machEventShift(); - if (event_int == -1) return null; - - const event_type = @intToEnum(std.meta.Tag(Event), event_int); - return switch (event_type) { - .key_press, .key_repeat => blk: { - const key = @intToEnum(Key, js.machEventShift()); - switch (key) { - .left_shift, .right_shift => self.last_key_mods.shift = true, - .left_control, .right_control => self.last_key_mods.control = true, - .left_alt, .right_alt => self.last_key_mods.alt = true, - .left_super, .right_super => self.last_key_mods.super = true, - .caps_lock => self.last_key_mods.caps_lock = true, - .num_lock => self.last_key_mods.num_lock = true, - else => {}, - } - break :blk switch (event_type) { - .key_press => Event{ - .key_press = .{ - .key = key, - .mods = self.last_key_mods, - }, - }, - .key_repeat => Event{ - .key_repeat = .{ - .key = key, - .mods = self.last_key_mods, - }, - }, - else => unreachable, - }; - }, - .key_release => blk: { - const key = @intToEnum(Key, js.machEventShift()); - switch (key) { - .left_shift, .right_shift => self.last_key_mods.shift = false, - .left_control, .right_control => self.last_key_mods.control = false, - .left_alt, .right_alt => self.last_key_mods.alt = false, - .left_super, .right_super => self.last_key_mods.super = false, - .caps_lock => self.last_key_mods.caps_lock = false, - .num_lock => self.last_key_mods.num_lock = false, - else => {}, - } - break :blk Event{ - .key_release = .{ - .key = key, - .mods = self.last_key_mods, - }, - }; - }, - .mouse_motion => blk: { - const x = @intToFloat(f64, js.machEventShift()); - const y = @intToFloat(f64, js.machEventShift()); - self.last_cursor_position = .{ - .x = x, - .y = y, - }; - break :blk Event{ - .mouse_motion = .{ - .pos = .{ - .x = x, - .y = y, - }, - }, - }; - }, - .mouse_press => Event{ - .mouse_press = .{ - .button = toMachButton(js.machEventShift()), - .pos = self.last_cursor_position, - .mods = self.last_key_mods, - }, - }, - .mouse_release => Event{ - .mouse_release = .{ - .button = toMachButton(js.machEventShift()), - .pos = self.last_cursor_position, - .mods = self.last_key_mods, - }, - }, - .mouse_scroll => Event{ - .mouse_scroll = .{ - .xoffset = @floatCast(f32, std.math.sign(js.machEventShiftFloat())), - .yoffset = @floatCast(f32, std.math.sign(js.machEventShiftFloat())), - }, - }, - .framebuffer_resize => blk: { - const width = @intCast(u32, js.machEventShift()); - const height = @intCast(u32, js.machEventShift()); - const pixel_ratio = @intCast(u32, js.machEventShift()); - break :blk Event{ - .framebuffer_resize = .{ - .width = width * pixel_ratio, - .height = height * pixel_ratio, - }, - }; - }, - .focus_gained => Event.focus_gained, - .focus_lost => Event.focus_lost, - else => null, - }; +pub inline fn pollEvents(self: *Core) EventIterator { + return EventIterator{ .core = self }; } pub fn framebufferSize(self: *Core) Size {