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();
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -25,40 +25,10 @@ id: js.CanvasId,
|
|||
last_cursor_position: Position,
|
||||
last_key_mods: KeyMods,
|
||||
|
||||
pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void {
|
||||
_ = options;
|
||||
var selector = [1]u8{0} ** 15;
|
||||
const id = js.machCanvasInit(&selector[0]);
|
||||
pub const EventIterator = struct {
|
||||
core: *Core,
|
||||
|
||||
core.* = Core{
|
||||
.allocator = allocator,
|
||||
.id = id,
|
||||
|
||||
// TODO initialize these properly
|
||||
.last_cursor_position = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
.last_key_mods = .{
|
||||
.shift = false,
|
||||
.control = false,
|
||||
.alt = false,
|
||||
.super = false,
|
||||
.caps_lock = false,
|
||||
.num_lock = false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Core) void {
|
||||
js.machCanvasDeinit(self.id);
|
||||
}
|
||||
|
||||
pub fn hasEvent(_: *Core) bool {
|
||||
return js.machHasEvent();
|
||||
}
|
||||
|
||||
pub fn pollEvents(self: *Core) ?Event {
|
||||
pub inline fn next(self: *EventIterator) ?Event {
|
||||
const event_int = js.machEventShift();
|
||||
if (event_int == -1) return null;
|
||||
|
||||
|
|
@ -161,6 +131,40 @@ pub fn pollEvents(self: *Core) ?Event {
|
|||
else => null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(core: *Core, allocator: std.mem.Allocator, options: Options) !void {
|
||||
_ = options;
|
||||
var selector = [1]u8{0} ** 15;
|
||||
const id = js.machCanvasInit(&selector[0]);
|
||||
|
||||
core.* = Core{
|
||||
.allocator = allocator,
|
||||
.id = id,
|
||||
|
||||
// TODO initialize these properly
|
||||
.last_cursor_position = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
},
|
||||
.last_key_mods = .{
|
||||
.shift = false,
|
||||
.control = false,
|
||||
.alt = false,
|
||||
.super = false,
|
||||
.caps_lock = false,
|
||||
.num_lock = false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Core) void {
|
||||
js.machCanvasDeinit(self.id);
|
||||
}
|
||||
|
||||
pub inline fn pollEvents(self: *Core) EventIterator {
|
||||
return EventIterator{ .core = self };
|
||||
}
|
||||
|
||||
pub fn framebufferSize(self: *Core) Size {
|
||||
return .{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue