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 {