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 <stephen@hexops.com>
This commit is contained in:
parent
b497a1bbeb
commit
40351f85ba
4 changed files with 142 additions and 134 deletions
|
|
@ -24,12 +24,16 @@ pub fn deinit(core: *Core) void {
|
||||||
return core.internal.deinit();
|
return core.internal.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasEvent(core: *Core) bool {
|
pub const EventIterator = struct {
|
||||||
return core.internal.hasEvent();
|
internal: platform.Core.EventIterator,
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pollEvents(core: *Core) ?Event {
|
pub inline fn next(self: *EventIterator) ?Event {
|
||||||
return core.internal.pollEvents();
|
return self.internal.next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub inline fn pollEvents(core: *Core) EventIterator {
|
||||||
|
return .{ .internal = core.internal.pollEvents() };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the framebuffer size, in subpixel units.
|
/// Returns the framebuffer size, in subpixel units.
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ comptime {
|
||||||
// Core
|
// Core
|
||||||
assertHasDecl(@This().Core, "init");
|
assertHasDecl(@This().Core, "init");
|
||||||
assertHasDecl(@This().Core, "deinit");
|
assertHasDecl(@This().Core, "deinit");
|
||||||
assertHasDecl(@This().Core, "hasEvent");
|
|
||||||
assertHasDecl(@This().Core, "pollEvents");
|
assertHasDecl(@This().Core, "pollEvents");
|
||||||
assertHasDecl(@This().Core, "framebufferSize");
|
assertHasDecl(@This().Core, "framebufferSize");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,15 @@ cursors_tried: [@typeInfo(CursorShape).Enum.fields.len]bool,
|
||||||
|
|
||||||
linux_gamemode: ?bool,
|
linux_gamemode: ?bool,
|
||||||
|
|
||||||
const EventQueue = std.TailQueue(Event);
|
const EventQueue = std.fifo.LinearFifo(Event, .Dynamic);
|
||||||
const EventNode = EventQueue.Node;
|
|
||||||
|
pub const EventIterator = struct {
|
||||||
|
queue: *EventQueue,
|
||||||
|
|
||||||
|
pub inline fn next(self: *EventIterator) ?Event {
|
||||||
|
return self.queue.readItem();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const UserPtr = struct {
|
const UserPtr = struct {
|
||||||
self: *Core,
|
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);
|
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.* = .{
|
core.* = .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.window = window,
|
.window = window,
|
||||||
|
|
@ -166,7 +181,7 @@ pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void {
|
||||||
.swap_chain = swap_chain,
|
.swap_chain = swap_chain,
|
||||||
.swap_chain_desc = swap_chain_desc,
|
.swap_chain_desc = swap_chain_desc,
|
||||||
|
|
||||||
.events = .{},
|
.events = events,
|
||||||
.wait_timeout = 0.0,
|
.wait_timeout = 0.0,
|
||||||
|
|
||||||
.last_size = window.getSize(),
|
.last_size = window.getSize(),
|
||||||
|
|
@ -292,9 +307,8 @@ fn initCallbacks(self: *Core) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pushEvent(self: *Core, event: Event) void {
|
fn pushEvent(self: *Core, event: Event) void {
|
||||||
const node = self.allocator.create(EventNode) catch unreachable;
|
// TODO(core): handle OOM via error flag
|
||||||
node.* = .{ .data = event };
|
self.events.writeItem(event) catch unreachable;
|
||||||
self.events.append(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Core) void {
|
pub fn deinit(self: *Core) void {
|
||||||
|
|
@ -303,10 +317,7 @@ pub fn deinit(self: *Core) void {
|
||||||
cur.destroy();
|
cur.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.events.deinit();
|
||||||
while (self.events.popFirst()) |ev| {
|
|
||||||
self.allocator.destroy(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builtin.os.tag == .linux and
|
if (builtin.os.tag == .linux and
|
||||||
self.linux_gamemode != null and
|
self.linux_gamemode != null and
|
||||||
|
|
@ -314,11 +325,7 @@ pub fn deinit(self: *Core) void {
|
||||||
deinitLinuxGamemode();
|
deinitLinuxGamemode();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasEvent(self: *Core) bool {
|
pub inline fn pollEvents(self: *Core) EventIterator {
|
||||||
return self.events.first != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pollEvents(self: *Core) ?Event {
|
|
||||||
if (self.wait_timeout > 0.0) {
|
if (self.wait_timeout > 0.0) {
|
||||||
if (self.wait_timeout == std.math.inf(f64)) {
|
if (self.wait_timeout == std.math.inf(f64)) {
|
||||||
// Wait for an event
|
// Wait for an event
|
||||||
|
|
@ -361,13 +368,7 @@ pub fn pollEvents(self: *Core) ?Event {
|
||||||
self.pushEvent(.close);
|
self.pushEvent(.close);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.events.popFirst()) |n| {
|
return EventIterator{ .queue = &self.events };
|
||||||
const data = n.data;
|
|
||||||
self.allocator.destroy(n);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shouldClose(self: *Core) bool {
|
pub fn shouldClose(self: *Core) bool {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,114 @@ id: js.CanvasId,
|
||||||
last_cursor_position: Position,
|
last_cursor_position: Position,
|
||||||
last_key_mods: KeyMods,
|
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 {
|
pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void {
|
||||||
_ = options;
|
_ = options;
|
||||||
var selector = [1]u8{0} ** 15;
|
var selector = [1]u8{0} ** 15;
|
||||||
|
|
@ -54,112 +162,8 @@ pub fn deinit(self: *Core) void {
|
||||||
js.machCanvasDeinit(self.id);
|
js.machCanvasDeinit(self.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasEvent(_: *Core) bool {
|
pub inline fn pollEvents(self: *Core) EventIterator {
|
||||||
return js.machHasEvent();
|
return EventIterator{ .core = self };
|
||||||
}
|
|
||||||
|
|
||||||
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 fn framebufferSize(self: *Core) Size {
|
pub fn framebufferSize(self: *Core) Size {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue