add basic replay

This commit is contained in:
Rokas Puzonas 2026-02-09 00:53:32 +02:00
parent 407da9e009
commit 83ce69a794
5 changed files with 367 additions and 196 deletions

View File

@ -35,7 +35,7 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
} }
const has_imgui = opts.has_imgui orelse (opts.optimize == .Debug); const has_imgui = opts.has_imgui orelse (opts.optimize == .Debug);
const statically_linked = opts.statically_linked orelse !hot_reload; const statically_linked = opts.statically_linked orelse true;
var build_options = b.addOptions(); var build_options = b.addOptions();
build_options.addOption(bool, "has_imgui", has_imgui); build_options.addOption(bool, "has_imgui", has_imgui);

View File

@ -286,7 +286,7 @@ pub const Frame = struct {
screen_size: Vec2, screen_size: Vec2,
clear_color: Vec4, clear_color: Vec4,
pub fn init(gpa: std.mem.Allocator) Frame { pub fn init(gpa: std.mem.Allocator, screen_size: Vec2) Frame {
return Frame{ return Frame{
.arena = std.heap.ArenaAllocator.init(gpa), .arena = std.heap.ArenaAllocator.init(gpa),
.time_ns = 0, .time_ns = 0,
@ -300,7 +300,7 @@ pub const Frame = struct {
.clear_color = rgb(0, 0, 0), .clear_color = rgb(0, 0, 0),
.audio_commands = .empty, .audio_commands = .empty,
.hide_cursor = false, .hide_cursor = false,
.screen_size = .init(0, 0), .screen_size = screen_size,
}; };
} }
@ -490,7 +490,8 @@ pub fn exportCallbacksIfNeeded(
comptime deinit: Callbacks.DeinitFn, comptime deinit: Callbacks.DeinitFn,
comptime debug: Callbacks.DebugFn comptime debug: Callbacks.DebugFn
) void { ) void {
if (build_options.statically_linked) { const builtin = @import("builtin");
if (builtin.output_mode != .Lib) {
return; return;
} }

View File

@ -24,6 +24,7 @@ const GameCallbacks = Lib.Callbacks;
pub const Math = Lib.Math; pub const Math = Lib.Math;
pub const Vec2 = Math.Vec2; pub const Vec2 = Math.Vec2;
const rgb = Math.rgb; const rgb = Math.rgb;
const Vec4 = Math.Vec4;
const GameLinking = struct { const GameLinking = struct {
kind: union(enum) { kind: union(enum) {
@ -246,37 +247,219 @@ const GameLinking = struct {
}; };
const GameState = struct { const GameState = struct {
state: GameCallbacks.State, gpa: std.mem.Allocator,
input: Input, state: ?GameCallbacks.State,
mouse_position: ?Vec2,
frame: Lib.Frame, frame: Lib.Frame,
started_at: std.time.Instant, started_at: std.time.Instant,
last_frame_at: Lib.Nanoseconds, last_frame_at: Lib.Nanoseconds,
previous_tick_canvas_size: ?Vec2,
seed: u64, seed: u64,
const Options = struct { const Initial = struct {
gpa: std.mem.Allocator, screen_size: Vec2,
seed: u64 seed: u64
}; };
pub fn init(opts: Options) GameState { const Tick = struct {
dt_ns: u64,
events: []Input.Event,
};
const TickResult = struct {
audio: []Lib.AudioCommand,
graphics: []Lib.GraphicsCommand,
clear_color: Vec4,
cursor_hidden: bool
};
pub fn init(gpa: std.mem.Allocator, initial: Initial) GameState {
return GameState{ return GameState{
.input = .initial, .gpa = gpa,
.frame = .init(opts.gpa), .mouse_position = null,
.frame = .init(gpa, initial.screen_size),
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"), .started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
.last_frame_at = 0, .last_frame_at = 0,
.previous_tick_canvas_size = null, .seed = initial.seed,
.seed = opts.seed, .state = null
.state = undefined
}; };
} }
fn processEvent(self: *GameState, event: Input.Event) void {
const frame = &self.frame;
switch (event) {
.key_pressed => |opts| {
if (!opts.repeat) {
frame.keyboard.press(opts.code, self.frame.time_ns);
}
},
.key_released => |key_code| {
frame.keyboard.release(key_code);
},
.mouse_leave => {
frame.keyboard.releaseAll();
assert(self.mouse_position != null);
self.mouse_position = null;
frame.mouse_button = .empty;
},
.mouse_enter => |pos| {
assert(self.mouse_position == null);
self.mouse_position = pos;
},
.mouse_move => |pos| {
assert(self.mouse_position != null);
self.mouse_position = pos;
},
.mouse_pressed => |opts| {
assert(self.mouse_position != null);
self.mouse_position = opts.position;
frame.mouse_button.press(opts.button, frame.time_ns);
},
.mouse_released => |opts| {
assert(self.mouse_position != null);
self.mouse_position = opts.position;
frame.mouse_button.release(opts.button);
},
.window_resize => |opts| {
frame.screen_size = .initFromInt(u32, opts.width, opts.height);
},
else => {}
}
}
fn runGameInit(self: *GameState, init_fn: GameCallbacks.InitFn) void {
const opts = Lib.Init{
.gpa = self.gpa,
.seed = self.seed,
};
self.state = init_fn(&opts);
}
fn runGameDeinit(self: *GameState, deinit_fn: GameCallbacks.DeinitFn) void {
assert(self.state != null);
deinit_fn(self.state.?);
self.state = null;
}
fn runGameTick(self: *GameState, tick_fn: GameCallbacks.TickFn) void {
assert(self.state != null);
tick_fn(self.state.?, &self.frame);
}
fn tick(self: *GameState, tick_fn: GameCallbacks.TickFn, opts: GameState.Tick) !TickResult {
const frame = &self.frame;
_ = frame.arena.reset(.retain_capacity);
const arena = frame.arena.allocator();
const audio_commands_capacity = frame.audio_commands.capacity;
frame.audio_commands = .empty;
try frame.audio_commands.ensureTotalCapacity(arena, audio_commands_capacity);
const graphics_commands_capacity = frame.graphics_commands.capacity;
frame.graphics_commands = .empty;
try frame.graphics_commands.ensureTotalCapacity(arena, graphics_commands_capacity);
frame.dt_ns = opts.dt_ns;
frame.time_ns += opts.dt_ns;
for (opts.events) |event| {
if (self.mouse_position == null) {
if (event == .mouse_leave) {
continue;
}
const enter_position = switch(event) {
.mouse_pressed => |e_opts| e_opts.position,
.mouse_released => |e_opts| e_opts.position,
.mouse_move => |pos| pos,
else => null
};
if (enter_position) |pos| {
self.processEvent(.{ .mouse_enter = pos });
}
} else {
if (event == .mouse_enter) {
continue;
}
}
self.processEvent(event);
}
frame.mouse_position = self.mouse_position;
var maybe_screen_scaler: ?ScreenScalar = null;
if (frame.canvas_size) |canvas_size| {
const screen_scaler = ScreenScalar.init(frame.screen_size, canvas_size);
maybe_screen_scaler = screen_scaler;
screen_scaler.push(frame);
if (frame.mouse_position) |mouse_position| {
frame.mouse_position = mouse_position.sub(screen_scaler.translation).divideScalar(screen_scaler.scale);
}
}
self.runGameTick(tick_fn);
if (maybe_screen_scaler) |screen_scaler| {
screen_scaler.pop(frame, frame.clear_color);
}
frame.keyboard.pressed = .initEmpty();
frame.keyboard.released = .initEmpty();
frame.mouse_button.pressed = .initEmpty();
frame.mouse_button.released = .initEmpty();
return TickResult{
.graphics = self.frame.graphics_commands.items,
.audio = self.frame.audio_commands.items,
.clear_color = self.frame.clear_color,
.cursor_hidden = self.frame.hide_cursor
};
}
fn runGameDebug(self: *GameState, debug_fn: GameCallbacks.DebugFn, imgui_ctx: *Lib.ImGui) void {
assert(self.state != null);
debug_fn(self.state.?, imgui_ctx);
}
pub fn deinit(self: *GameState) void { pub fn deinit(self: *GameState) void {
self.frame.deinit(); self.frame.deinit();
} }
}; };
const Recording = struct {
arena: std.heap.ArenaAllocator,
initial: GameState.Initial,
ticks: std.ArrayList(GameState.Tick),
pub fn init(gpa: std.mem.Allocator, initial: GameState.Initial) Recording {
return Recording{
.arena = .init(gpa),
.ticks = .empty,
.initial = initial
};
}
pub fn appendTick(self: *Recording, tick: GameState.Tick) !void {
const arena = self.arena.allocator();
try self.ticks.append(arena, .{
.dt_ns = tick.dt_ns,
.events = try arena.dupe(Input.Event, tick.events)
});
}
pub fn deinit(self: Recording) void {
self.arena.deinit();
}
};
const Engine = @This(); const Engine = @This();
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -285,31 +468,58 @@ graphics: Graphics,
game_linking: GameLinking, game_linking: GameLinking,
game_state: GameState, game_state: GameState,
queued_events: std.ArrayList(Input.Event),
is_mouse_inside: bool,
seed: u64, seed: u64,
show_debug: bool, show_debug: bool,
restart_game: bool, restart_game: bool,
recording: ?Recording,
play_recording: bool,
recording_tick: u64,
const RunOptions = struct { const RunOptions = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
game_library_path: ?[]const u8 = null game_library_path: ?[]const u8 = null,
do_recording: bool = (builtin.mode == .Debug),
screen_width: u32 = 640,
screen_height: u32 = 480,
}; };
pub fn run(self: *Engine, opts: RunOptions) !void { pub fn run(self: *Engine, opts: RunOptions) !void {
var game_linking: GameLinking = undefined;
if (opts.game_library_path) |game_library_path| {
game_linking = try .initDynamic(opts.allocator, game_library_path);
} else {
game_linking = try .initStatic();
}
const initial = GameState.Initial{
.seed = @bitCast(std.time.milliTimestamp()),
.screen_size = .initFromInt(u32, opts.screen_width, opts.screen_height)
};
var game_state: GameState = .init(opts.allocator, initial);
game_state.runGameInit(game_linking.callbacks.init);
self.* = Engine{ self.* = Engine{
.allocator = opts.allocator, .allocator = opts.allocator,
.graphics = undefined, .graphics = undefined,
.game_state = undefined, .game_state = game_state,
.game_linking = undefined, .game_linking = game_linking,
.show_debug = false, .show_debug = false,
.restart_game = false, .restart_game = false,
.seed = @bitCast(std.time.milliTimestamp()), .recording = null,
.queued_events = .empty,
.is_mouse_inside = false,
.play_recording = false,
.recording_tick = 0,
.seed = initial.seed,
}; };
if (opts.game_library_path) |game_library_path| { if (opts.do_recording) {
self.game_linking = try .initDynamic(self.allocator, game_library_path); self.recording = .init(opts.allocator, initial);
} else {
self.game_linking = try .initStatic();
} }
tracy.setThreadName("Main"); tracy.setThreadName("Main");
@ -350,8 +560,8 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
.cleanup_userdata_cb = sokolCleanupCallback, .cleanup_userdata_cb = sokolCleanupCallback,
.event_userdata_cb = sokolEventCallback, .event_userdata_cb = sokolEventCallback,
.user_data = self, .user_data = self,
.width = 640, .width = @intCast(opts.screen_width),
.height = 480, .height = @intCast(opts.screen_height),
.icon = icon, .icon = icon,
.window_title = "Game", .window_title = "Game",
.logger = .{ .func = sokolLogCallback }, .logger = .{ .func = sokolLogCallback },
@ -378,24 +588,18 @@ fn sokolInit(self: *Engine) !void {
.allocator = self.allocator, .allocator = self.allocator,
.logger = .{ .func = sokolLogCallback }, .logger = .{ .func = sokolLogCallback },
}); });
self.game_state = .init(.{
.gpa = self.allocator,
.seed = self.seed
});
const opts = Lib.Init{
.gpa = self.allocator,
.seed = self.game_state.seed,
};
self.game_state.state = self.game_linking.callbacks.init(&opts);
} }
fn sokolCleanup(self: *Engine) void { fn sokolCleanup(self: *Engine) void {
const zone = tracy.initZone(@src(), .{ }); const zone = tracy.initZone(@src(), .{ });
defer zone.deinit(); defer zone.deinit();
self.game_linking.callbacks.deinit(self.game_state.state); if (self.recording) |recording| {
recording.deinit();
}
self.queued_events.deinit(self.allocator);
self.game_state.runGameDeinit(self.game_linking.callbacks.deinit);
self.game_state.deinit(); self.game_state.deinit();
Audio.deinit(); Audio.deinit();
self.graphics.deinit(self.allocator); self.graphics.deinit(self.allocator);
@ -408,105 +612,79 @@ fn sokolFrame(self: *Engine) !void {
const zone = tracy.initZone(@src(), .{ }); const zone = tracy.initZone(@src(), .{ });
defer zone.deinit(); defer zone.deinit();
const frame = &self.game_state.frame;
try self.game_linking.check(); try self.game_linking.check();
const screen_size = Vec2.init(sapp.widthf(), sapp.heightf());
var maybe_screen_scaler: ?ScreenScalar = null;
if (self.game_state.previous_tick_canvas_size) |canvas_size| {
maybe_screen_scaler = ScreenScalar.init(screen_size, canvas_size);
}
{
const now = std.time.Instant.now() catch @panic("Instant.now() unsupported"); const now = std.time.Instant.now() catch @panic("Instant.now() unsupported");
const time_passed = now.since(self.game_state.started_at); const time_passed = now.since(self.game_state.started_at);
defer self.game_state.last_frame_at = time_passed; const dt_ns = time_passed - self.game_state.last_frame_at;
self.game_state.last_frame_at = time_passed;
_ = frame.arena.reset(.retain_capacity); if (self.play_recording) {
const arena = frame.arena.allocator(); assert(self.recording != null);
if (self.recording_tick >= self.recording.?.ticks.items.len) {
const audio_commands_capacity = frame.audio_commands.capacity; self.play_recording = false;
frame.audio_commands = .empty;
try frame.audio_commands.ensureTotalCapacity(arena, audio_commands_capacity);
const graphics_commands_capacity = frame.graphics_commands.capacity;
frame.graphics_commands = .empty;
try frame.graphics_commands.ensureTotalCapacity(arena, graphics_commands_capacity);
frame.screen_size = screen_size;
frame.time_ns = time_passed;
frame.dt_ns = time_passed - self.game_state.last_frame_at;
frame.mouse_position = self.game_state.input.mouse_position;
if (maybe_screen_scaler) |screen_scaler| {
screen_scaler.push(frame);
if (frame.mouse_position) |mouse_position| {
frame.mouse_position = mouse_position.sub(screen_scaler.translation).divideScalar(screen_scaler.scale);
} }
} }
if (debug_keybinds) { var tick: GameState.Tick = undefined;
if (frame.isKeyPressed(.F3)) { if (self.play_recording) {
self.show_debug = !self.show_debug; assert(self.recording != null);
} const ticks = self.recording.?.ticks.items;
tick = ticks[self.recording_tick];
self.recording_tick += 1;
} else {
tick = GameState.Tick{
.dt_ns = dt_ns,
.events = self.queued_events.items
};
if (frame.isKeyPressed(.F5)) { if (self.recording) |*recording| {
self.restart_game = true; try recording.appendTick(tick);
} }
} }
self.game_linking.callbacks.tick(self.game_state.state, frame); const tick_result = try self.game_state.tick(self.game_linking.callbacks.tick, tick);
if (maybe_screen_scaler) |screen_scaler| { self.queued_events.clearRetainingCapacity();
screen_scaler.pop(frame, frame.clear_color);
}
frame.keyboard.pressed = .initEmpty(); {
frame.keyboard.released = .initEmpty(); sapp.showMouse(!tick_result.cursor_hidden);
frame.mouse_button.pressed = .initEmpty();
frame.mouse_button.released = .initEmpty();
}
// Canvas size modification must always be applied a frame later.
// So that mouse coordinate transformations are consistent.
self.game_state.previous_tick_canvas_size = frame.canvas_size;
sapp.showMouse(!frame.hide_cursor);
{ {
self.graphics.beginFrame(); self.graphics.beginFrame();
defer self.graphics.endFrame(frame.clear_color); defer self.graphics.endFrame(tick_result.clear_color);
self.graphics.drawCommands(frame.graphics_commands.items); self.graphics.drawCommands(tick_result.graphics);
if (self.show_debug and build_options.has_imgui) { if (self.show_debug and build_options.has_imgui) {
try self.showDebugWindow(frame); try self.showDebugWindow(&self.game_state.frame);
} }
} }
for (frame.audio_commands.items) |command| { for (tick_result.audio) |command| {
try Audio.mixer.commands.push(command); try Audio.mixer.commands.push(command);
} }
}
if (self.restart_game) { if (self.restart_game) {
self.restart_game = false; self.restart_game = false;
self.game_linking.callbacks.deinit(self.game_state.state);
self.game_state.deinit(); const initial = GameState.Initial{
self.game_state = .init(.{ .seed = self.seed,
.gpa = self.allocator, .screen_size = .init(sapp.widthf(), sapp.heightf())
.seed = self.seed
});
const opts = Lib.Init{
.gpa = self.allocator,
.seed = self.game_state.seed,
}; };
self.game_state.state = self.game_linking.callbacks.init(&opts);
self.game_state.runGameDeinit(self.game_linking.callbacks.deinit);
self.game_state.deinit();
self.game_state = .init(self.allocator, initial);
self.game_state.runGameInit(self.game_linking.callbacks.init);
if (self.recording) |recording| {
recording.deinit();
self.recording = .init(self.allocator, initial);
}
} }
} }
@ -528,7 +706,8 @@ fn showDebugWindow(self: *Engine, frame: *Lib.Frame) !void {
var imgui_ctx = Lib.ImGui.init(self.allocator); var imgui_ctx = Lib.ImGui.init(self.allocator);
defer imgui_ctx.deinit(); defer imgui_ctx.deinit();
self.game_linking.callbacks.debug(self.game_state.state, &imgui_ctx);
self.game_state.runGameDebug(self.game_linking.callbacks.debug, &imgui_ctx);
for (imgui_ctx.commands.items) |cmd| { for (imgui_ctx.commands.items) |cmd| {
switch (cmd) { switch (cmd) {
.text => |str| imgui.text(str) .text => |str| imgui.text(str)
@ -543,6 +722,22 @@ fn showDebugWindow(self: *Engine, frame: *Lib.Frame) !void {
self.restart_game = true; self.restart_game = true;
} }
if (self.recording) |recording| {
imgui.beginDisabled(self.play_recording);
defer imgui.endDisabled();
if (imgui.button("Replay")) {
self.game_state.runGameDeinit(self.game_linking.callbacks.deinit);
self.game_state.deinit();
self.game_state = .init(self.allocator, recording.initial);
self.game_state.runGameInit(self.game_linking.callbacks.init);
self.play_recording = true;
self.recording_tick = 0;
}
}
const linking_label = if (self.game_linking.kind == .static) "static" else "dynamic"; const linking_label = if (self.game_linking.kind == .static) "static" else "dynamic";
imgui.textFmt("Linking: {s}", .{ linking_label }); imgui.textFmt("Linking: {s}", .{ linking_label });
@ -567,24 +762,38 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
const e = e_ptr.*; const e = e_ptr.*;
const input = &self.game_state.input;
const frame = &self.game_state.frame;
if (imgui.handleEvent(e)) { if (imgui.handleEvent(e)) {
if (input.mouse_position != null) { if (self.is_mouse_inside) {
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.mouse_leave = {} .mouse_leave = {}
}); });
self.is_mouse_inside = false;
} }
input.mouse_position = null;
return true; return true;
} }
if (debug_keybinds and e.type == .KEY_DOWN) {
if (e.key_code == .F4) {
self.show_debug = !self.show_debug;
return true;
}
if (e.key_code == .F5) {
self.restart_game = true;
return true;
}
}
if (self.play_recording) {
return false;
}
blk: switch (e.type) { blk: switch (e.type) {
.MOUSE_DOWN => { .MOUSE_DOWN => {
const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk; const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk;
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.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)
@ -596,7 +805,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
.MOUSE_UP => { .MOUSE_UP => {
const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk; const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk;
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.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)
@ -606,48 +815,57 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true; return true;
}, },
.MOUSE_MOVE => { .MOUSE_MOVE => {
if (input.mouse_position == null) { if (self.is_mouse_inside) {
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
});
} else {
input.processEvent(frame, .{
.mouse_move = Vec2.init(e.mouse_x, e.mouse_y) .mouse_move = Vec2.init(e.mouse_x, e.mouse_y)
}); });
} else {
try self.queued_events.append(self.allocator, .{
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
});
self.is_mouse_inside = true;
} }
return true; return true;
}, },
.MOUSE_ENTER => { .MOUSE_ENTER => {
if (input.mouse_position == null) { if (!self.is_mouse_inside) {
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y) .mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
}); });
self.is_mouse_inside = true;
} }
return true; return true;
}, },
.RESIZED => { .RESIZED => {
if (input.mouse_position != null) { if (self.is_mouse_inside) {
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.mouse_leave = {} .mouse_leave = {}
}); });
self.is_mouse_inside = false;
} }
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.window_resize = {} .window_resize = .{
.width = @intCast(e.window_width),
.height = @intCast(e.window_height)
}
}); });
return true; return true;
}, },
.MOUSE_LEAVE => { .MOUSE_LEAVE => {
if (input.mouse_position != null) { if (self.is_mouse_inside) {
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.mouse_leave = {} .mouse_leave = {}
}); });
self.is_mouse_inside = false;
} }
return true; return true;
}, },
.MOUSE_SCROLL => { .MOUSE_SCROLL => {
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y) .mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y)
}); });
@ -656,7 +874,7 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
.KEY_DOWN => { .KEY_DOWN => {
const key_code = Input.getKeyCodeFromSokol(e.key_code) orelse break :blk; const key_code = Input.getKeyCodeFromSokol(e.key_code) orelse break :blk;
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.key_pressed = .{ .key_pressed = .{
.code = key_code, .code = key_code,
.repeat = e.key_repeat .repeat = e.key_repeat
@ -668,14 +886,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
.KEY_UP => { .KEY_UP => {
const key_code = Input.getKeyCodeFromSokol(e.key_code) orelse break :blk; const key_code = Input.getKeyCodeFromSokol(e.key_code) orelse break :blk;
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.key_released = key_code .key_released = key_code
}); });
return true; return true;
}, },
.CHAR => { .CHAR => {
input.processEvent(frame, .{ try self.queued_events.append(self.allocator, .{
.char = @intCast(e.char_code) .char = @intCast(e.char_code)
}); });

View File

@ -251,9 +251,6 @@ pub fn drawCommands(self: *Graphics, commands: []const Command) void {
} }
pub fn beginFrame(self: *Graphics) void { pub fn beginFrame(self: *Graphics) void {
const zone = tracy.initZone(@src(), .{ });
defer zone.deinit();
imgui.newFrame(.{ imgui.newFrame(.{
.width = sapp.width(), .width = sapp.width(),
.height = sapp.height(), .height = sapp.height(),
@ -275,9 +272,6 @@ pub fn beginFrame(self: *Graphics) void {
} }
pub fn endFrame(self: *Graphics, clear_color: Vec4) void { pub fn endFrame(self: *Graphics, clear_color: Vec4) void {
const zone = tracy.initZone(@src(), .{ });
defer zone.deinit();
var pass_action: sg.PassAction = .{}; var pass_action: sg.PassAction = .{};
pass_action.colors[0] = sg.ColorAttachmentAction{ pass_action.colors[0] = sg.ColorAttachmentAction{

View File

@ -7,8 +7,6 @@ const Frame = lib.Frame;
const Nanoseconds = lib.Nanoseconds; const Nanoseconds = lib.Nanoseconds;
const Vec2 = lib.Math.Vec2; const Vec2 = lib.Math.Vec2;
const Input = @This();
pub const Event = union(enum) { pub const Event = union(enum) {
mouse_pressed: struct { mouse_pressed: struct {
button: lib.MouseButton, button: lib.MouseButton,
@ -27,54 +25,14 @@ pub const Event = union(enum) {
repeat: bool repeat: bool
}, },
key_released: lib.KeyCode, key_released: lib.KeyCode,
window_resize, window_resize: struct {
width: u32,
height: u32,
},
char: u21, char: u21,
}; };
mouse_position: ?Vec2, pub const EventEnum = @typeInfo(Event).@"union".tag_type.?;
pub const initial = Input{
.mouse_position = null
};
pub fn processEvent(self: *Input, frame: *Frame, event: Event) void {
switch (event) {
.key_pressed => |opts| {
if (!opts.repeat) {
frame.keyboard.press(opts.code, frame.time_ns);
}
},
.key_released => |key_code| {
frame.keyboard.release(key_code);
},
.mouse_leave => {
frame.keyboard.releaseAll();
assert(self.mouse_position != null);
self.mouse_position = null;
frame.mouse_button = .empty;
},
.mouse_enter => |pos| {
assert(self.mouse_position == null);
self.mouse_position = pos;
},
.mouse_move => |pos| {
assert(self.mouse_position != null);
self.mouse_position = pos;
},
.mouse_pressed => |opts| {
assert(self.mouse_position != null);
self.mouse_position = opts.position;
frame.mouse_button.press(opts.button, frame.time_ns);
},
.mouse_released => |opts| {
assert(self.mouse_position != null);
self.mouse_position = opts.position;
frame.mouse_button.release(opts.button);
},
else => {}
}
}
pub fn getKeyCodeFromSokol(key_code: sokol.app.Keycode) ?lib.KeyCode { pub fn getKeyCodeFromSokol(key_code: sokol.app.Keycode) ?lib.KeyCode {
return switch (key_code) { return switch (key_code) {