create engine frame struct

This commit is contained in:
Rokas Puzonas 2026-01-19 23:49:01 +02:00
parent d6e4575a7d
commit e7e91f3cef
4 changed files with 208 additions and 299 deletions

106
src/engine/frame.zig Normal file
View File

@ -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)
};
}

View File

@ -1,170 +1,23 @@
const std = @import("std"); const std = @import("std");
const sokol = @import("sokol"); const sokol = @import("sokol");
const Engine = @import("./root.zig"); const Frame = @import("./frame.zig");
const Nanoseconds = Engine.Nanoseconds; const Nanoseconds = Frame.Nanoseconds;
const Math = @import("./math.zig"); const Math = @import("./math.zig");
const Vec2 = Math.Vec2; const Vec2 = Math.Vec2;
const Input = @This(); const Input = @This();
pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) { const SokolKeyCodeInfo = @typeInfo(sokol.app.Keycode);
SPACE = 32, pub const KeyCode = @Type(.{
APOSTROPHE = 39, .@"enum" = .{
COMMA = 44, .tag_type = std.math.IntFittingRange(0, sokol.app.max_keycodes-1),
MINUS = 45, .decls = SokolKeyCodeInfo.@"enum".decls,
PERIOD = 46, .fields = SokolKeyCodeInfo.@"enum".fields,
SLASH = 47, .is_exhaustive = false
_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;
}
};
pub const Mouse = struct { pub const Mouse = struct {
pub const Button = enum { pub const Button = enum {
@ -191,49 +44,73 @@ pub const Mouse = struct {
}; };
}; };
down_keys: std.EnumSet(KeyCode), pub const Event = union(enum) {
pressed_keys: std.EnumSet(KeyCode), mouse_pressed: struct {
released_keys: std.EnumSet(KeyCode), button: Mouse.Button,
pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds), position: Vec2,
},
mouse: Mouse, mouse_released: struct {
button: Mouse.Button,
pub const empty = Input{ position: Vec2,
.down_keys = .initEmpty(), },
.pressed_keys = .initEmpty(), mouse_move: Vec2,
.released_keys = .initEmpty(), mouse_enter: Vec2,
.pressed_keys_at = .init(.{}), mouse_leave,
.mouse = .empty 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 { pub fn processEvent(frame: *Frame, event: Event) void {
return self.down_keys.contains(key_code); const input = &frame.input;
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 => {}
}
} }
pub fn getKeyDownDuration(self: *Input, frame: Engine.Frame, key_code: KeyCode) ?f64 { pub fn beginFrame(frame: *Frame) void {
if (!self.isKeyDown(key_code)) { frame.input.pressed_keys = .initEmpty();
return null; frame.input.released_keys = .initEmpty();
}
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)
};
} }

View File

@ -10,6 +10,8 @@ pub const Vec2 = Math.Vec2;
pub const Input = @import("./input.zig"); pub const Input = @import("./input.zig");
pub const Frame = @import("./frame.zig");
const ScreenScalar = @import("./screen_scaler.zig"); const ScreenScalar = @import("./screen_scaler.zig");
pub const imgui = @import("./imgui.zig"); pub const imgui = @import("./imgui.zig");
pub const Graphics = @import("./graphics.zig"); pub const Graphics = @import("./graphics.zig");
@ -27,43 +29,14 @@ const Engine = @This();
pub const Nanoseconds = u64; 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, allocator: std.mem.Allocator,
started_at: std.time.Instant, started_at: std.time.Instant,
mouse_inside: bool, mouse_inside: bool,
last_frame_at: Nanoseconds, last_frame_at: Nanoseconds,
input: Input,
game: Game, game: Game,
assets: Assets, assets: Assets,
frame: Frame,
const RunOptions = struct { const RunOptions = struct {
window_title: [*:0]const u8 = "Game", window_title: [*:0]const u8 = "Game",
@ -75,11 +48,11 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
self.* = Engine{ self.* = Engine{
.allocator = undefined, .allocator = undefined,
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"), .started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
.input = .empty,
.mouse_inside = false, .mouse_inside = false,
.last_frame_at = 0, .last_frame_at = 0,
.assets = undefined, .assets = undefined,
.game = undefined .game = undefined,
.frame = .empty
}; };
var debug_allocator: std.heap.DebugAllocator(.{}) = .init; var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
@ -188,18 +161,14 @@ fn sokolFrame(self: *Engine) !void {
Graphics.font_resolution_scale = ctx.scale; Graphics.font_resolution_scale = ctx.scale;
try self.game.tick(Frame{ self.frame.time_ns = time_passed;
.time_ns = time_passed, self.frame.dt_ns = dt_ns;
.dt_ns = dt_ns, try self.game.tick(&self.frame);
.dt = @as(f32, @floatFromInt(dt_ns)) / std.time.ns_per_s,
.input = &self.input
});
} }
try self.game.debug(); try self.game.debug();
self.input.pressed_keys = .initEmpty(); Input.beginFrame(&self.frame);
self.input.released_keys = .initEmpty();
} }
fn timePassed(self: *Engine) Nanoseconds { fn timePassed(self: *Engine) Nanoseconds {
@ -207,50 +176,6 @@ fn timePassed(self: *Engine) Nanoseconds {
return now.since(self.started_at); 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 { fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
const zone = tracy.initZone(@src(), .{ }); const zone = tracy.initZone(@src(), .{ });
defer zone.deinit(); defer zone.deinit();
@ -260,7 +185,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
if (imgui.handleEvent(e)) { if (imgui.handleEvent(e)) {
if (self.mouse_inside) { if (self.mouse_inside) {
try self.event(Event{ Input.processEvent(&self.frame, .{
.mouse_leave = {} .mouse_leave = {}
}); });
} }
@ -272,7 +197,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
.MOUSE_DOWN => { .MOUSE_DOWN => {
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk; const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
try self.event(Event{ Input.processEvent(&self.frame, .{
.mouse_pressed = .{ .mouse_pressed = .{
.button = mouse_button, .button = mouse_button,
.position = Vec2.init(e.mouse_x, e.mouse_y) .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 => { .MOUSE_UP => {
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk; const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
try self.event(Event{ Input.processEvent(&self.frame, .{
.mouse_released = .{ .mouse_released = .{
.button = mouse_button, .button = mouse_button,
.position = Vec2.init(e.mouse_x, e.mouse_y) .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 => { .MOUSE_MOVE => {
if (!self.mouse_inside) { if (!self.mouse_inside) {
try self.event(Event{ Input.processEvent(&self.frame, .{
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y) .mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
}); });
} else { } else {
try self.event(Event{ Input.processEvent(&self.frame, .{
.mouse_move = Vec2.init(e.mouse_x, e.mouse_y) .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 => { .MOUSE_ENTER => {
if (!self.mouse_inside) { if (!self.mouse_inside) {
try self.event(Event{ Input.processEvent(&self.frame, .{
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y) .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 => { .RESIZED => {
if (self.mouse_inside) { if (self.mouse_inside) {
try self.event(Event{ Input.processEvent(&self.frame, .{
.mouse_leave = {} .mouse_leave = {}
}); });
} }
try self.event(Event{ Input.processEvent(&self.frame, .{
.window_resize = {} .window_resize = {}
}); });
@ -333,7 +258,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
}, },
.MOUSE_LEAVE => { .MOUSE_LEAVE => {
if (self.mouse_inside) { if (self.mouse_inside) {
try self.event(Event{ Input.processEvent(&self.frame, .{
.mouse_leave = {} .mouse_leave = {}
}); });
} }
@ -342,14 +267,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true; return true;
}, },
.MOUSE_SCROLL => { .MOUSE_SCROLL => {
try self.event(Event{ Input.processEvent(&self.frame, .{
.mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y) .mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y)
}); });
return true; return true;
}, },
.KEY_DOWN => { .KEY_DOWN => {
try self.event(Event{ Input.processEvent(&self.frame, .{
.key_pressed = .{ .key_pressed = .{
.code = @enumFromInt(@intFromEnum(e.key_code)), .code = @enumFromInt(@intFromEnum(e.key_code)),
.repeat = e.key_repeat .repeat = e.key_repeat
@ -359,14 +284,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true; return true;
}, },
.KEY_UP => { .KEY_UP => {
try self.event(Event{ Input.processEvent(&self.frame, .{
.key_released = @enumFromInt(@intFromEnum(e.key_code)) .key_released = @enumFromInt(@intFromEnum(e.key_code))
}); });
return true; return true;
}, },
.CHAR => { .CHAR => {
try self.event(Event{ Input.processEvent(&self.frame, .{
.char = @intCast(e.char_code) .char = @intCast(e.char_code)
}); });

View File

@ -32,19 +32,20 @@ pub fn deinit(self: *Game) void {
_ = self; // autofix _ = 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); var dir = Vec2.init(0, 0);
if (frame.input.isKeyDown(.W)) { if (frame.isKeyDown(.W)) {
dir.y -= 1; dir.y -= 1;
} }
if (frame.input.isKeyDown(.S)) { if (frame.isKeyDown(.S)) {
dir.y += 1; dir.y += 1;
} }
if (frame.input.isKeyDown(.A)) { if (frame.isKeyDown(.A)) {
dir.x -= 1; dir.x -= 1;
} }
if (frame.input.isKeyDown(.D)) { if (frame.isKeyDown(.D)) {
dir.x += 1; dir.x += 1;
} }
dir = dir.normalized(); 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); const regular_font = self.assets.font_id.get(.regular);