From e7e91f3cefd1897237d63febdd58806fe5d0a586 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Mon, 19 Jan 2026 23:49:01 +0200 Subject: [PATCH] create engine frame struct --- src/engine/frame.zig | 106 +++++++++++++++++ src/engine/input.zig | 269 ++++++++++++------------------------------- src/engine/root.zig | 119 ++++--------------- src/game.zig | 13 ++- 4 files changed, 208 insertions(+), 299 deletions(-) create mode 100644 src/engine/frame.zig diff --git a/src/engine/frame.zig b/src/engine/frame.zig new file mode 100644 index 0000000..e2e827e --- /dev/null +++ b/src/engine/frame.zig @@ -0,0 +1,106 @@ +const std = @import("std"); + +const InputSystem = @import("./input.zig"); +const KeyCode = InputSystem.KeyCode; +const Mouse = InputSystem.Mouse; + +pub const Nanoseconds = u64; + +const Frame = @This(); + +pub const Input = struct { + down_keys: std.EnumSet(KeyCode), + pressed_keys: std.EnumSet(KeyCode), + released_keys: std.EnumSet(KeyCode), + pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds), + + mouse: Mouse, + + pub const empty = Input{ + .down_keys = .initEmpty(), + .pressed_keys = .initEmpty(), + .released_keys = .initEmpty(), + .pressed_keys_at = .init(.{}), + .mouse = .empty + }; +}; + +pub const KeyState = struct { + down: bool, + pressed: bool, + released: bool, + down_duration: ?f64, + + pub const RepeatOptions = struct { + first_at: f64 = 0, + period: f64 + }; + + pub fn repeat(self: KeyState, last_repeat_at: *?f64, opts: RepeatOptions) bool { + if (!self.down) { + last_repeat_at.* = null; + return false; + } + + const down_duration = self.down_duration.?; + if (last_repeat_at.* != null) { + if (down_duration >= last_repeat_at.*.? + opts.period) { + last_repeat_at.* = last_repeat_at.*.? + opts.period; + return true; + } + } else { + if (down_duration >= opts.first_at) { + last_repeat_at.* = opts.first_at; + return true; + } + } + + return false; + } +}; + +time_ns: Nanoseconds, +dt_ns: Nanoseconds, +input: Input, + +pub const empty = Frame{ + .time_ns = 0, + .dt_ns = 0, + .input = .empty +}; + +pub fn deltaTime(self: Frame) f32 { + return @as(f32, @floatFromInt(self.dt_ns)) / std.time.ns_per_s; +} + +pub fn isKeyDown(self: Frame, key_code: KeyCode) bool { + return self.input.down_keys.contains(key_code); +} + +pub fn getKeyDownDuration(self: Frame, key_code: KeyCode) ?f32 { + if (!self.isKeyDown(key_code)) { + return null; + } + + const pressed_at_ns = self.input.pressed_keys_at.get(key_code).?; + const duration_ns = self.time_ns - pressed_at_ns; + + return @as(f32, @floatFromInt(duration_ns)) / std.time.ns_per_s; +} + +pub fn isKeyPressed(self: Frame, key_code: KeyCode) bool { + return self.input.pressed_keys.contains(key_code); +} + +pub fn isKeyReleased(self: Frame, key_code: KeyCode) bool { + return self.input.released_keys.contains(key_code); +} + +pub fn getKeyState(self: Frame, key_code: KeyCode) KeyState { + return KeyState{ + .down = self.isKeyDown(key_code), + .released = self.isKeyReleased(key_code), + .pressed = self.isKeyPressed(key_code), + .down_duration = self.getKeyDownDuration(key_code) + }; +} diff --git a/src/engine/input.zig b/src/engine/input.zig index 0cf6518..99195f4 100644 --- a/src/engine/input.zig +++ b/src/engine/input.zig @@ -1,170 +1,23 @@ const std = @import("std"); const sokol = @import("sokol"); -const Engine = @import("./root.zig"); -const Nanoseconds = Engine.Nanoseconds; +const Frame = @import("./frame.zig"); +const Nanoseconds = Frame.Nanoseconds; const Math = @import("./math.zig"); const Vec2 = Math.Vec2; const Input = @This(); -pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) { - SPACE = 32, - APOSTROPHE = 39, - COMMA = 44, - MINUS = 45, - PERIOD = 46, - SLASH = 47, - _0 = 48, - _1 = 49, - _2 = 50, - _3 = 51, - _4 = 52, - _5 = 53, - _6 = 54, - _7 = 55, - _8 = 56, - _9 = 57, - SEMICOLON = 59, - EQUAL = 61, - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - LEFT_BRACKET = 91, - BACKSLASH = 92, - RIGHT_BRACKET = 93, - GRAVE_ACCENT = 96, - WORLD_1 = 161, - WORLD_2 = 162, - ESCAPE = 256, - ENTER = 257, - TAB = 258, - BACKSPACE = 259, - INSERT = 260, - DELETE = 261, - RIGHT = 262, - LEFT = 263, - DOWN = 264, - UP = 265, - PAGE_UP = 266, - PAGE_DOWN = 267, - HOME = 268, - END = 269, - CAPS_LOCK = 280, - SCROLL_LOCK = 281, - NUM_LOCK = 282, - PRINT_SCREEN = 283, - PAUSE = 284, - F1 = 290, - F2 = 291, - F3 = 292, - F4 = 293, - F5 = 294, - F6 = 295, - F7 = 296, - F8 = 297, - F9 = 298, - F10 = 299, - F11 = 300, - F12 = 301, - F13 = 302, - F14 = 303, - F15 = 304, - F16 = 305, - F17 = 306, - F18 = 307, - F19 = 308, - F20 = 309, - F21 = 310, - F22 = 311, - F23 = 312, - F24 = 313, - F25 = 314, - KP_0 = 320, - KP_1 = 321, - KP_2 = 322, - KP_3 = 323, - KP_4 = 324, - KP_5 = 325, - KP_6 = 326, - KP_7 = 327, - KP_8 = 328, - KP_9 = 329, - KP_DECIMAL = 330, - KP_DIVIDE = 331, - KP_MULTIPLY = 332, - KP_SUBTRACT = 333, - KP_ADD = 334, - KP_ENTER = 335, - KP_EQUAL = 336, - LEFT_SHIFT = 340, - LEFT_CONTROL = 341, - LEFT_ALT = 342, - LEFT_SUPER = 343, - RIGHT_SHIFT = 344, - RIGHT_CONTROL = 345, - RIGHT_ALT = 346, - RIGHT_SUPER = 347, - MENU = 348, -}; - -pub const KeyState = struct { - down: bool, - pressed: bool, - released: bool, - down_duration: ?f64, - - pub const RepeatOptions = struct { - first_at: f64 = 0, - period: f64 - }; - - pub fn repeat(self: KeyState, last_repeat_at: *?f64, opts: RepeatOptions) bool { - if (!self.down) { - last_repeat_at.* = null; - return false; - } - - const down_duration = self.down_duration.?; - if (last_repeat_at.* != null) { - if (down_duration >= last_repeat_at.*.? + opts.period) { - last_repeat_at.* = last_repeat_at.*.? + opts.period; - return true; - } - } else { - if (down_duration >= opts.first_at) { - last_repeat_at.* = opts.first_at; - return true; - } - } - - return false; +const SokolKeyCodeInfo = @typeInfo(sokol.app.Keycode); +pub const KeyCode = @Type(.{ + .@"enum" = .{ + .tag_type = std.math.IntFittingRange(0, sokol.app.max_keycodes-1), + .decls = SokolKeyCodeInfo.@"enum".decls, + .fields = SokolKeyCodeInfo.@"enum".fields, + .is_exhaustive = false } -}; +}); pub const Mouse = struct { pub const Button = enum { @@ -191,49 +44,73 @@ pub const Mouse = struct { }; }; -down_keys: std.EnumSet(KeyCode), -pressed_keys: std.EnumSet(KeyCode), -released_keys: std.EnumSet(KeyCode), -pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds), - -mouse: Mouse, - -pub const empty = Input{ - .down_keys = .initEmpty(), - .pressed_keys = .initEmpty(), - .released_keys = .initEmpty(), - .pressed_keys_at = .init(.{}), - .mouse = .empty +pub const Event = union(enum) { + mouse_pressed: struct { + button: Mouse.Button, + position: Vec2, + }, + mouse_released: struct { + button: Mouse.Button, + position: Vec2, + }, + mouse_move: Vec2, + mouse_enter: Vec2, + mouse_leave, + mouse_scroll: Vec2, + key_pressed: struct { + code: KeyCode, + repeat: bool + }, + key_released: Input.KeyCode, + window_resize, + char: u21, }; -pub fn isKeyDown(self: *Input, key_code: KeyCode) bool { - return self.down_keys.contains(key_code); -} +pub fn processEvent(frame: *Frame, event: Event) void { + const input = &frame.input; -pub fn getKeyDownDuration(self: *Input, frame: Engine.Frame, key_code: KeyCode) ?f64 { - if (!self.isKeyDown(key_code)) { - return null; + switch (event) { + .key_pressed => |opts| { + if (!opts.repeat) { + input.pressed_keys_at.put(opts.code, frame.time_ns); + input.pressed_keys.insert(opts.code); + input.down_keys.insert(opts.code); + } + }, + .key_released => |key_code| { + input.down_keys.remove(key_code); + input.released_keys.insert(key_code); + input.pressed_keys_at.remove(key_code); + }, + .mouse_leave => { + var iter = input.down_keys.iterator(); + while (iter.next()) |key_code| { + input.released_keys.insert(key_code); + } + input.down_keys = .initEmpty(); + input.pressed_keys_at = .init(.{}); + + input.mouse = .empty; + }, + .mouse_enter => |pos| { + input.mouse.position = pos; + }, + .mouse_move => |pos| { + input.mouse.position = pos; + }, + .mouse_pressed => |opts| { + input.mouse.position = opts.position; + input.mouse.buttons.insert(opts.button); + }, + .mouse_released => |opts| { + input.mouse.position = opts.position; + input.mouse.buttons.remove(opts.button); + }, + else => {} } - - const pressed_at_ns = self.pressed_keys_at.get(key_code).?; - const duration_ns = frame.time_ns - pressed_at_ns; - - return @as(f64, @floatFromInt(duration_ns)) / std.time.ns_per_s; } -pub fn isKeyPressed(self: *Input, key_code: KeyCode) bool { - return self.pressed_keys.contains(key_code); -} - -pub fn isKeyReleased(self: *Input, key_code: KeyCode) bool { - return self.released_keys.contains(key_code); -} - -pub fn getKeyState(self: *Input, frame: Engine.Frame, key_code: KeyCode) KeyState { - return KeyState{ - .down = self.isKeyDown(key_code), - .released = self.isKeyReleased(key_code), - .pressed = self.isKeyPressed(key_code), - .down_duration = self.getKeyDownDuration(frame, key_code) - }; +pub fn beginFrame(frame: *Frame) void { + frame.input.pressed_keys = .initEmpty(); + frame.input.released_keys = .initEmpty(); } diff --git a/src/engine/root.zig b/src/engine/root.zig index 1a7bfe2..ddc7472 100644 --- a/src/engine/root.zig +++ b/src/engine/root.zig @@ -10,6 +10,8 @@ pub const Vec2 = Math.Vec2; pub const Input = @import("./input.zig"); +pub const Frame = @import("./frame.zig"); + const ScreenScalar = @import("./screen_scaler.zig"); pub const imgui = @import("./imgui.zig"); pub const Graphics = @import("./graphics.zig"); @@ -27,43 +29,14 @@ const Engine = @This(); pub const Nanoseconds = u64; -pub const Event = union(enum) { - mouse_pressed: struct { - button: Input.Mouse.Button, - position: Vec2, - }, - mouse_released: struct { - button: Input.Mouse.Button, - position: Vec2, - }, - mouse_move: Vec2, - mouse_enter: Vec2, - mouse_leave, - mouse_scroll: Vec2, - key_pressed: struct { - code: Input.KeyCode, - repeat: bool - }, - key_released: Input.KeyCode, - window_resize, - char: u21, -}; - -pub const Frame = struct { - time_ns: Nanoseconds, - dt_ns: Nanoseconds, - dt: f32, - input: *Input -}; - allocator: std.mem.Allocator, started_at: std.time.Instant, mouse_inside: bool, last_frame_at: Nanoseconds, -input: Input, game: Game, assets: Assets, +frame: Frame, const RunOptions = struct { window_title: [*:0]const u8 = "Game", @@ -75,11 +48,11 @@ pub fn run(self: *Engine, opts: RunOptions) !void { self.* = Engine{ .allocator = undefined, .started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"), - .input = .empty, .mouse_inside = false, .last_frame_at = 0, .assets = undefined, - .game = undefined + .game = undefined, + .frame = .empty }; var debug_allocator: std.heap.DebugAllocator(.{}) = .init; @@ -188,18 +161,14 @@ fn sokolFrame(self: *Engine) !void { Graphics.font_resolution_scale = ctx.scale; - try self.game.tick(Frame{ - .time_ns = time_passed, - .dt_ns = dt_ns, - .dt = @as(f32, @floatFromInt(dt_ns)) / std.time.ns_per_s, - .input = &self.input - }); + self.frame.time_ns = time_passed; + self.frame.dt_ns = dt_ns; + try self.game.tick(&self.frame); } try self.game.debug(); - self.input.pressed_keys = .initEmpty(); - self.input.released_keys = .initEmpty(); + Input.beginFrame(&self.frame); } fn timePassed(self: *Engine) Nanoseconds { @@ -207,50 +176,6 @@ fn timePassed(self: *Engine) Nanoseconds { return now.since(self.started_at); } -fn event(self: *Engine, e: Event) !void { - const input = &self.input; - - switch (e) { - .key_pressed => |opts| { - if (!opts.repeat) { - input.pressed_keys_at.put(opts.code, self.timePassed()); - input.pressed_keys.insert(opts.code); - input.down_keys.insert(opts.code); - } - }, - .key_released => |key_code| { - input.down_keys.remove(key_code); - input.released_keys.insert(key_code); - input.pressed_keys_at.remove(key_code); - }, - .mouse_leave => { - var iter = input.down_keys.iterator(); - while (iter.next()) |key_code| { - input.released_keys.insert(key_code); - } - input.down_keys = .initEmpty(); - input.pressed_keys_at = .init(.{}); - - input.mouse = .empty; - }, - .mouse_enter => |pos| { - input.mouse.position = pos; - }, - .mouse_move => |pos| { - input.mouse.position = pos; - }, - .mouse_pressed => |opts| { - input.mouse.position = opts.position; - input.mouse.buttons.insert(opts.button); - }, - .mouse_released => |opts| { - input.mouse.position = opts.position; - input.mouse.buttons.remove(opts.button); - }, - else => {} - } -} - fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { const zone = tracy.initZone(@src(), .{ }); defer zone.deinit(); @@ -260,7 +185,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { if (imgui.handleEvent(e)) { if (self.mouse_inside) { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .mouse_leave = {} }); } @@ -272,7 +197,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { .MOUSE_DOWN => { const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk; - try self.event(Event{ + Input.processEvent(&self.frame, .{ .mouse_pressed = .{ .button = mouse_button, .position = Vec2.init(e.mouse_x, e.mouse_y) @@ -284,7 +209,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { .MOUSE_UP => { const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk; - try self.event(Event{ + Input.processEvent(&self.frame, .{ .mouse_released = .{ .button = mouse_button, .position = Vec2.init(e.mouse_x, e.mouse_y) @@ -295,11 +220,11 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { }, .MOUSE_MOVE => { if (!self.mouse_inside) { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .mouse_enter = Vec2.init(e.mouse_x, e.mouse_y) }); } else { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .mouse_move = Vec2.init(e.mouse_x, e.mouse_y) }); } @@ -309,7 +234,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { }, .MOUSE_ENTER => { if (!self.mouse_inside) { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .mouse_enter = Vec2.init(e.mouse_x, e.mouse_y) }); } @@ -319,12 +244,12 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { }, .RESIZED => { if (self.mouse_inside) { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .mouse_leave = {} }); } - try self.event(Event{ + Input.processEvent(&self.frame, .{ .window_resize = {} }); @@ -333,7 +258,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { }, .MOUSE_LEAVE => { if (self.mouse_inside) { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .mouse_leave = {} }); } @@ -342,14 +267,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { return true; }, .MOUSE_SCROLL => { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y) }); return true; }, .KEY_DOWN => { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .key_pressed = .{ .code = @enumFromInt(@intFromEnum(e.key_code)), .repeat = e.key_repeat @@ -359,14 +284,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { return true; }, .KEY_UP => { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .key_released = @enumFromInt(@intFromEnum(e.key_code)) }); return true; }, .CHAR => { - try self.event(Event{ + Input.processEvent(&self.frame, .{ .char = @intCast(e.char_code) }); diff --git a/src/game.zig b/src/game.zig index a3a1836..597e65f 100644 --- a/src/game.zig +++ b/src/game.zig @@ -32,19 +32,20 @@ pub fn deinit(self: *Game) void { _ = self; // autofix } -pub fn tick(self: *Game, frame: Engine.Frame) !void { +pub fn tick(self: *Game, frame: *Engine.Frame) !void { + const dt = frame.deltaTime(); var dir = Vec2.init(0, 0); - if (frame.input.isKeyDown(.W)) { + if (frame.isKeyDown(.W)) { dir.y -= 1; } - if (frame.input.isKeyDown(.S)) { + if (frame.isKeyDown(.S)) { dir.y += 1; } - if (frame.input.isKeyDown(.A)) { + if (frame.isKeyDown(.A)) { dir.x -= 1; } - if (frame.input.isKeyDown(.D)) { + if (frame.isKeyDown(.D)) { dir.x += 1; } dir = dir.normalized(); @@ -55,7 +56,7 @@ pub fn tick(self: *Game, frame: Engine.Frame) !void { }); } - self.player = self.player.add(dir.multiplyScalar(50 * frame.dt)); + self.player = self.player.add(dir.multiplyScalar(50 * dt)); const regular_font = self.assets.font_id.get(.regular);