core: move input state tracking from Platform to Core

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-08-25 18:50:32 -07:00
parent 1c0434a948
commit 8447654311
4 changed files with 100 additions and 135 deletions

View file

@ -156,6 +156,8 @@ descriptor: gpu.SwapChain.Descriptor,
// Internal state // Internal state
events: EventQueue, events: EventQueue,
input_state: InputState,
oom: std.Thread.ResetEvent = .{},
// TODO: this needs to be removed. // TODO: this needs to be removed.
pub const InitOptions = struct { pub const InitOptions = struct {
@ -208,6 +210,7 @@ fn init(core: *Mod, entities: *mach.Entities.Mod) !void {
.allocator = allocator, .allocator = allocator,
.main_window = main_window, .main_window = main_window,
.events = events, .events = events,
.input_state = .{},
// TODO: remove undefined initialization (disgusting!) // TODO: remove undefined initialization (disgusting!)
.platform = undefined, .platform = undefined,
@ -617,6 +620,41 @@ pub inline fn nextEvent(core: *@This()) ?Event {
return core.events.readItem(); return core.events.readItem();
} }
/// Push an event onto the event queue, or set OOM if no space is available.
///
/// Updates the input_state tracker.
pub inline fn pushEvent(core: *@This(), event: Event) void {
// Write event
core.events.writeItem(event) catch {
core.oom.set();
return;
};
// Update input state
switch (event) {
.key_press => |ev| core.input_state.keys.setValue(@intFromEnum(ev.key), true),
.key_release => |ev| core.input_state.keys.setValue(@intFromEnum(ev.key), false),
.mouse_press => |ev| core.input_state.mouse_buttons.setValue(@intFromEnum(ev.button), true),
.mouse_release => |ev| core.input_state.mouse_buttons.setValue(@intFromEnum(ev.button), false),
.mouse_motion => |ev| core.input_state.mouse_position = ev.pos,
.focus_lost => {
// Clear input state that may be 'stuck' when focus is regained.
core.input_state.keys = InputState.KeyBitSet.initEmpty();
core.input_state.mouse_buttons = InputState.MouseButtonSet.initEmpty();
},
else => {},
}
}
/// Reports whether mach.Core ran out of memory, indicating events may have been dropped.
///
/// Once called, the OOM flag is reset and mach.Core will continue operating normally.
pub fn outOfMemory(core: *@This()) bool {
if (!core.oom.isSet()) return false;
core.oom.reset();
return true;
}
/// Sets the window title. The string must be owned by Core, and will not be copied or freed. It is /// Sets the window title. The string must be owned by Core, and will not be copied or freed. It is
/// advised to use the `core.title` buffer for this purpose, e.g.: /// advised to use the `core.title` buffer for this purpose, e.g.:
/// ///
@ -672,6 +710,26 @@ pub inline fn headless(core: *@This()) bool {
return core.platform.headless; return core.platform.headless;
} }
pub fn keyPressed(core: *@This(), key: Key) bool {
return core.input_state.isKeyPressed(key);
}
pub fn keyReleased(core: *@This(), key: Key) bool {
return core.input_state.isKeyReleased(key);
}
pub fn mousePressed(core: *@This(), button: MouseButton) bool {
return core.input_state.isMouseButtonPressed(button);
}
pub fn mouseReleased(core: *@This(), button: MouseButton) bool {
return core.input_state.isMouseButtonReleased(button);
}
pub fn mousePosition(core: *@This()) Position {
return core.input_state.mouse_position;
}
pub const VSyncMode = enum { pub const VSyncMode = enum {
/// Potential screen tearing. /// Potential screen tearing.
/// No synchronization with monitor, render frames as fast as possible. /// No synchronization with monitor, render frames as fast as possible.
@ -784,31 +842,11 @@ pub inline fn cursorShape(core: *@This()) CursorShape {
return core.platform.cursorShape(); return core.platform.cursorShape();
} }
pub inline fn keyPressed(core: *@This(), key: Key) bool {
return core.platform.keyPressed(key);
}
pub inline fn keyReleased(core: *@This(), key: Key) bool {
return core.platform.keyReleased(key);
}
pub inline fn mousePressed(core: *@This(), button: MouseButton) bool {
return core.platform.mousePressed(button);
}
pub inline fn mouseReleased(core: *@This(), button: MouseButton) bool {
return core.platform.mouseReleased(button);
}
pub const Position = struct { pub const Position = struct {
x: f64, x: f64,
y: f64, y: f64,
}; };
pub inline fn mousePosition(core: *@This()) Position {
return core.platform.mousePosition();
}
/// Sets the minimum target frequency of the input handling thread. /// Sets the minimum target frequency of the input handling thread.
/// ///
/// Input handling (the main thread) runs at a variable frequency. The thread blocks until there are /// Input handling (the main thread) runs at a variable frequency. The thread blocks until there are
@ -1100,12 +1138,6 @@ comptime {
assertHasDecl(Platform, "setCursorShape"); assertHasDecl(Platform, "setCursorShape");
assertHasField(Platform, "cursor_shape"); assertHasField(Platform, "cursor_shape");
assertHasDecl(Platform, "keyPressed");
assertHasDecl(Platform, "keyReleased");
assertHasDecl(Platform, "mousePressed");
assertHasDecl(Platform, "mouseReleased");
assertHasDecl(Platform, "mousePosition");
} }
fn assertHasDecl(comptime T: anytype, comptime decl_name: []const u8) void { fn assertHasDecl(comptime T: anytype, comptime decl_name: []const u8) void {

View file

@ -23,7 +23,6 @@ pub const Darwin = @This();
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
core: *Core, core: *Core,
input_state: Core.InputState,
title: [:0]const u8, title: [:0]const u8,
display_mode: DisplayMode, display_mode: DisplayMode,
vsync_mode: VSyncMode, vsync_mode: VSyncMode,
@ -109,7 +108,6 @@ pub fn init(
darwin.* = .{ darwin.* = .{
.allocator = options.allocator, .allocator = options.allocator,
.core = @fieldParentPtr("platform", darwin), .core = @fieldParentPtr("platform", darwin),
.input_state = .{},
.title = options.title, .title = options.title,
.display_mode = options.display_mode, .display_mode = options.display_mode,
.vsync_mode = .none, .vsync_mode = .none,
@ -168,23 +166,3 @@ pub fn setCursorMode(_: *Darwin, _: CursorMode) void {
pub fn setCursorShape(_: *Darwin, _: CursorShape) void { pub fn setCursorShape(_: *Darwin, _: CursorShape) void {
return; return;
} }
pub fn keyPressed(_: *Darwin, _: Key) bool {
return false;
}
pub fn keyReleased(_: *Darwin, _: Key) bool {
return true;
}
pub fn mousePressed(_: *Darwin, _: MouseButton) bool {
return false;
}
pub fn mouseReleased(_: *Darwin, _: MouseButton) bool {
return true;
}
pub fn mousePosition(_: *Darwin) Position {
return Position{ .x = 0, .y = 0 };
}

View file

@ -25,7 +25,6 @@ pub const Null = @This();
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
core: *Core, core: *Core,
input_state: Core.InputState,
modifiers: KeyMods, modifiers: KeyMods,
title: [:0]u8, title: [:0]u8,
display_mode: DisplayMode, display_mode: DisplayMode,
@ -92,23 +91,3 @@ pub fn setCursorMode(_: *Null, _: CursorMode) void {
pub fn setCursorShape(_: *Null, _: CursorShape) void { pub fn setCursorShape(_: *Null, _: CursorShape) void {
return; return;
} }
pub fn keyPressed(_: *Null, _: Key) bool {
return false;
}
pub fn keyReleased(_: *Null, _: Key) bool {
return true;
}
pub fn mousePressed(_: *Null, _: MouseButton) bool {
return false;
}
pub fn mouseReleased(_: *Null, _: MouseButton) bool {
return true;
}
pub fn mousePosition(_: *Null) Position {
return Position{ .x = 0, .y = 0 };
}

View file

@ -45,8 +45,6 @@ dinput: *w.IDirectInput8W,
saved_window_rect: w.RECT, saved_window_rect: w.RECT,
surface_descriptor_from_hwnd: gpu.Surface.DescriptorFromWindowsHWND, surface_descriptor_from_hwnd: gpu.Surface.DescriptorFromWindowsHWND,
state: *Core, state: *Core,
input_state: Core.InputState,
oom: std.Thread.ResetEvent = .{},
// ------------------------------ // ------------------------------
// Platform interface // Platform interface
@ -60,7 +58,6 @@ pub fn init(
self.allocator = options.allocator; self.allocator = options.allocator;
self.core = @fieldParentPtr("platform", self); self.core = @fieldParentPtr("platform", self);
self.size = options.size; self.size = options.size;
self.input_state = .{};
self.saved_window_rect = .{ .top = 0, .left = 0, .right = 0, .bottom = 0 }; self.saved_window_rect = .{ .top = 0, .left = 0, .right = 0, .bottom = 0 };
const hInstance = w.GetModuleHandleW(null); const hInstance = w.GetModuleHandleW(null);
@ -153,7 +150,7 @@ pub fn update(self: *Win32) !void {
pub fn setTitle(self: *Win32, title: [:0]const u8) void { pub fn setTitle(self: *Win32, title: [:0]const u8) void {
const wtitle = std.unicode.utf8ToUtf16LeAllocZ(self.allocator, title) catch { const wtitle = std.unicode.utf8ToUtf16LeAllocZ(self.allocator, title) catch {
self.oom.set(); self.state.oom.set();
return; return;
}; };
defer self.allocator.free(wtitle); defer self.allocator.free(wtitle);
@ -258,26 +255,6 @@ pub fn setCursorShape(self: *Win32, shape: CursorShape) void {
self.cursor_shape = shape; self.cursor_shape = shape;
} }
pub fn keyPressed(self: *Win32, key: Key) bool {
return self.input_state.isKeyPressed(key);
}
pub fn keyReleased(self: *Win32, key: Key) bool {
return self.input_state.isKeyReleased(key);
}
pub fn mousePressed(self: *Win32, button: MouseButton) bool {
return self.input_state.isMouseButtonPressed(button);
}
pub fn mouseReleased(self: *Win32, button: MouseButton) bool {
return self.input_state.isMouseButtonReleased(button);
}
pub fn mousePosition(self: *Win32) Position {
return self.input_state.mouse_position;
}
pub fn nativeWindowWin32(self: *Win32) w.HWND { pub fn nativeWindowWin32(self: *Win32) w.HWND {
return self.window; return self.window;
} }
@ -303,18 +280,6 @@ fn restoreWindowPosition(self: *Win32) void {
} }
} }
pub fn outOfMemory(self: *Win32) bool {
if (self.oom.isSet()) {
self.oom.reset();
return true;
}
return false;
}
fn pushEvent(self: *Win32, event: Event) void {
self.state.events.writeItem(event) catch self.oom.set();
}
fn getKeyboardModifiers() mach.Core.KeyMods { fn getKeyboardModifiers() mach.Core.KeyMods {
return .{ return .{
.shift = w.GetKeyState(@as(i32, @intFromEnum(w.VK_SHIFT))) < 0, //& 0x8000 == 0x8000, .shift = w.GetKeyState(@as(i32, @intFromEnum(w.VK_SHIFT))) < 0, //& 0x8000 == 0x8000,
@ -336,7 +301,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
switch (msg) { switch (msg) {
w.WM_CLOSE => { w.WM_CLOSE => {
self.pushEvent(.close); self.state.pushEvent(.close);
return 0; return 0;
}, },
w.WM_SIZE => { w.WM_SIZE => {
@ -358,7 +323,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
if (vkey == w.VK_PROCESSKEY) return 0; if (vkey == w.VK_PROCESSKEY) return 0;
if (msg == w.WM_SYSKEYDOWN and vkey == w.VK_F4) { if (msg == w.WM_SYSKEYDOWN and vkey == w.VK_F4) {
self.pushEvent(.close); self.state.pushEvent(.close);
return 0; return 0;
} }
@ -381,16 +346,26 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
const mods = getKeyboardModifiers(); const mods = getKeyboardModifiers();
const key = keyFromScancode(scancode); const key = keyFromScancode(scancode);
if (msg == w.WM_KEYDOWN or msg == w.WM_SYSKEYDOWN) { if (msg == w.WM_KEYDOWN or msg == w.WM_SYSKEYDOWN) {
if (flags & w.KF_REPEAT == 0) { if (flags & w.KF_REPEAT == 0)
self.pushEvent(.{ .key_press = .{ .key = key, .mods = mods } }); self.state.pushEvent(.{
self.input_state.keys.setValue(@intFromEnum(key), true); .key_press = .{
} else { .key = key,
self.pushEvent(.{ .key_repeat = .{ .key = key, .mods = mods } }); .mods = mods,
} },
} else { })
self.pushEvent(.{ .key_release = .{ .key = key, .mods = mods } }); else
self.input_state.keys.setValue(@intFromEnum(key), false); self.state.pushEvent(.{
} .key_repeat = .{
.key = key,
.mods = mods,
},
});
} else self.state.pushEvent(.{
.key_release = .{
.key = key,
.mods = mods,
},
});
return 0; return 0;
}, },
@ -408,7 +383,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
} }
var iter = std.unicode.Utf16LeIterator.init(chars); var iter = std.unicode.Utf16LeIterator.init(chars);
if (iter.nextCodepoint()) |codepoint| { if (iter.nextCodepoint()) |codepoint| {
self.pushEvent(.{ .char_input = .{ .codepoint = codepoint.? } }); self.state.pushEvent(.{ .char_input = .{ .codepoint = codepoint.? } });
} else |err| { } else |err| {
err catch {}; err catch {};
} }
@ -439,14 +414,20 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
w.WM_MBUTTONDOWN, w.WM_MBUTTONDOWN,
w.WM_RBUTTONDOWN, w.WM_RBUTTONDOWN,
w.WM_XBUTTONDOWN, w.WM_XBUTTONDOWN,
=> { => self.state.pushEvent(.{
self.pushEvent(.{ .mouse_press = .{ .button = button, .mods = mods, .pos = .{ .x = x, .y = y } } }); .mouse_press = .{
self.input_state.mouse_buttons.setValue(@intFromEnum(button), true); .button = button,
}, .mods = mods,
else => { .pos = .{ .x = x, .y = y },
self.pushEvent(.{ .mouse_release = .{ .button = button, .mods = mods, .pos = .{ .x = x, .y = y } } }); },
self.input_state.mouse_buttons.setValue(@intFromEnum(button), false); }),
}, else => self.state.pushEvent(.{
.mouse_release = .{
.button = button,
.mods = mods,
.pos = .{ .x = x, .y = y },
},
}),
} }
return if (msg == w.WM_XBUTTONDOWN or msg == w.WM_XBUTTONUP) w.TRUE else 0; return if (msg == w.WM_XBUTTONDOWN or msg == w.WM_XBUTTONUP) w.TRUE else 0;
@ -454,8 +435,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
w.WM_MOUSEMOVE => { w.WM_MOUSEMOVE => {
const x: f64 = @floatFromInt(@as(i16, @truncate(lParam & 0xFFFF))); const x: f64 = @floatFromInt(@as(i16, @truncate(lParam & 0xFFFF)));
const y: f64 = @floatFromInt(@as(i16, @truncate((lParam >> 16) & 0xFFFF))); const y: f64 = @floatFromInt(@as(i16, @truncate((lParam >> 16) & 0xFFFF)));
self.state.pushEvent(.{
self.pushEvent(.{
.mouse_motion = .{ .mouse_motion = .{
.pos = .{ .pos = .{
.x = x, .x = x,
@ -463,8 +443,6 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
}, },
}, },
}); });
self.input_state.mouse_position = .{ .x = x, .y = y };
return 0; return 0;
}, },
w.WM_MOUSEWHEEL => { w.WM_MOUSEWHEEL => {
@ -472,7 +450,7 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
const wheel_high_word: u16 = @truncate((wParam >> 16) & 0xffff); const wheel_high_word: u16 = @truncate((wParam >> 16) & 0xffff);
const delta_y: f32 = @as(f32, @floatFromInt(@as(i16, @bitCast(wheel_high_word)))) / WHEEL_DELTA; const delta_y: f32 = @as(f32, @floatFromInt(@as(i16, @bitCast(wheel_high_word)))) / WHEEL_DELTA;
self.pushEvent(.{ self.state.pushEvent(.{
.mouse_scroll = .{ .mouse_scroll = .{
.xoffset = 0, .xoffset = 0,
.yoffset = delta_y, .yoffset = delta_y,
@ -481,13 +459,11 @@ fn wndProc(wnd: w.HWND, msg: u32, wParam: w.WPARAM, lParam: w.LPARAM) callconv(w
return 0; return 0;
}, },
w.WM_SETFOCUS => { w.WM_SETFOCUS => {
self.pushEvent(.{ .focus_gained = {} }); self.state.pushEvent(.{ .focus_gained = {} });
return 0; return 0;
}, },
w.WM_KILLFOCUS => { w.WM_KILLFOCUS => {
self.pushEvent(.{ .focus_lost = {} }); self.state.pushEvent(.{ .focus_lost = {} });
// Clear input state when focus is lost to avoid "stuck" button when focus is regained.
self.input_state = .{};
return 0; return 0;
}, },
else => return w.DefWindowProcW(wnd, msg, wParam, lParam), else => return w.DefWindowProcW(wnd, msg, wParam, lParam),