add basic replay
This commit is contained in:
parent
407da9e009
commit
83ce69a794
@ -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 statically_linked = opts.statically_linked orelse !hot_reload;
|
||||
const statically_linked = opts.statically_linked orelse true;
|
||||
|
||||
var build_options = b.addOptions();
|
||||
build_options.addOption(bool, "has_imgui", has_imgui);
|
||||
|
||||
@ -286,7 +286,7 @@ pub const Frame = struct {
|
||||
screen_size: Vec2,
|
||||
clear_color: Vec4,
|
||||
|
||||
pub fn init(gpa: std.mem.Allocator) Frame {
|
||||
pub fn init(gpa: std.mem.Allocator, screen_size: Vec2) Frame {
|
||||
return Frame{
|
||||
.arena = std.heap.ArenaAllocator.init(gpa),
|
||||
.time_ns = 0,
|
||||
@ -300,7 +300,7 @@ pub const Frame = struct {
|
||||
.clear_color = rgb(0, 0, 0),
|
||||
.audio_commands = .empty,
|
||||
.hide_cursor = false,
|
||||
.screen_size = .init(0, 0),
|
||||
.screen_size = screen_size,
|
||||
};
|
||||
}
|
||||
|
||||
@ -490,7 +490,8 @@ pub fn exportCallbacksIfNeeded(
|
||||
comptime deinit: Callbacks.DeinitFn,
|
||||
comptime debug: Callbacks.DebugFn
|
||||
) void {
|
||||
if (build_options.statically_linked) {
|
||||
const builtin = @import("builtin");
|
||||
if (builtin.output_mode != .Lib) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ const GameCallbacks = Lib.Callbacks;
|
||||
pub const Math = Lib.Math;
|
||||
pub const Vec2 = Math.Vec2;
|
||||
const rgb = Math.rgb;
|
||||
const Vec4 = Math.Vec4;
|
||||
|
||||
const GameLinking = struct {
|
||||
kind: union(enum) {
|
||||
@ -246,37 +247,219 @@ const GameLinking = struct {
|
||||
};
|
||||
|
||||
const GameState = struct {
|
||||
state: GameCallbacks.State,
|
||||
gpa: std.mem.Allocator,
|
||||
|
||||
input: Input,
|
||||
state: ?GameCallbacks.State,
|
||||
|
||||
mouse_position: ?Vec2,
|
||||
frame: Lib.Frame,
|
||||
started_at: std.time.Instant,
|
||||
last_frame_at: Lib.Nanoseconds,
|
||||
previous_tick_canvas_size: ?Vec2,
|
||||
seed: u64,
|
||||
|
||||
const Options = struct {
|
||||
gpa: std.mem.Allocator,
|
||||
const Initial = struct {
|
||||
screen_size: Vec2,
|
||||
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{
|
||||
.input = .initial,
|
||||
.frame = .init(opts.gpa),
|
||||
.gpa = gpa,
|
||||
.mouse_position = null,
|
||||
.frame = .init(gpa, initial.screen_size),
|
||||
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
|
||||
.last_frame_at = 0,
|
||||
.previous_tick_canvas_size = null,
|
||||
.seed = opts.seed,
|
||||
.state = undefined
|
||||
.seed = initial.seed,
|
||||
.state = null
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
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();
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
@ -285,31 +468,58 @@ graphics: Graphics,
|
||||
game_linking: GameLinking,
|
||||
game_state: GameState,
|
||||
|
||||
queued_events: std.ArrayList(Input.Event),
|
||||
is_mouse_inside: bool,
|
||||
|
||||
seed: u64,
|
||||
|
||||
show_debug: bool,
|
||||
restart_game: bool,
|
||||
recording: ?Recording,
|
||||
|
||||
play_recording: bool,
|
||||
recording_tick: u64,
|
||||
|
||||
const RunOptions = struct {
|
||||
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 {
|
||||
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{
|
||||
.allocator = opts.allocator,
|
||||
.graphics = undefined,
|
||||
.game_state = undefined,
|
||||
.game_linking = undefined,
|
||||
.game_state = game_state,
|
||||
.game_linking = game_linking,
|
||||
.show_debug = 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| {
|
||||
self.game_linking = try .initDynamic(self.allocator, game_library_path);
|
||||
} else {
|
||||
self.game_linking = try .initStatic();
|
||||
if (opts.do_recording) {
|
||||
self.recording = .init(opts.allocator, initial);
|
||||
}
|
||||
|
||||
tracy.setThreadName("Main");
|
||||
@ -350,8 +560,8 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
.cleanup_userdata_cb = sokolCleanupCallback,
|
||||
.event_userdata_cb = sokolEventCallback,
|
||||
.user_data = self,
|
||||
.width = 640,
|
||||
.height = 480,
|
||||
.width = @intCast(opts.screen_width),
|
||||
.height = @intCast(opts.screen_height),
|
||||
.icon = icon,
|
||||
.window_title = "Game",
|
||||
.logger = .{ .func = sokolLogCallback },
|
||||
@ -378,24 +588,18 @@ fn sokolInit(self: *Engine) !void {
|
||||
.allocator = self.allocator,
|
||||
.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 {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
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();
|
||||
Audio.deinit();
|
||||
self.graphics.deinit(self.allocator);
|
||||
@ -408,105 +612,79 @@ fn sokolFrame(self: *Engine) !void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
const frame = &self.game_state.frame;
|
||||
|
||||
try self.game_linking.check();
|
||||
|
||||
const screen_size = Vec2.init(sapp.widthf(), sapp.heightf());
|
||||
const now = std.time.Instant.now() catch @panic("Instant.now() unsupported");
|
||||
const time_passed = now.since(self.game_state.started_at);
|
||||
const dt_ns = time_passed - self.game_state.last_frame_at;
|
||||
self.game_state.last_frame_at = time_passed;
|
||||
|
||||
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);
|
||||
if (self.play_recording) {
|
||||
assert(self.recording != null);
|
||||
if (self.recording_tick >= self.recording.?.ticks.items.len) {
|
||||
self.play_recording = false;
|
||||
}
|
||||
}
|
||||
|
||||
var tick: GameState.Tick = undefined;
|
||||
if (self.play_recording) {
|
||||
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 (self.recording) |*recording| {
|
||||
try recording.appendTick(tick);
|
||||
}
|
||||
}
|
||||
|
||||
const tick_result = try self.game_state.tick(self.game_linking.callbacks.tick, tick);
|
||||
|
||||
self.queued_events.clearRetainingCapacity();
|
||||
|
||||
{
|
||||
const now = std.time.Instant.now() catch @panic("Instant.now() unsupported");
|
||||
const time_passed = now.since(self.game_state.started_at);
|
||||
defer self.game_state.last_frame_at = time_passed;
|
||||
sapp.showMouse(!tick_result.cursor_hidden);
|
||||
|
||||
_ = frame.arena.reset(.retain_capacity);
|
||||
const arena = frame.arena.allocator();
|
||||
{
|
||||
self.graphics.beginFrame();
|
||||
defer self.graphics.endFrame(tick_result.clear_color);
|
||||
|
||||
const audio_commands_capacity = frame.audio_commands.capacity;
|
||||
frame.audio_commands = .empty;
|
||||
try frame.audio_commands.ensureTotalCapacity(arena, audio_commands_capacity);
|
||||
self.graphics.drawCommands(tick_result.graphics);
|
||||
|
||||
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 (self.show_debug and build_options.has_imgui) {
|
||||
try self.showDebugWindow(&self.game_state.frame);
|
||||
}
|
||||
}
|
||||
|
||||
if (debug_keybinds) {
|
||||
if (frame.isKeyPressed(.F3)) {
|
||||
self.show_debug = !self.show_debug;
|
||||
}
|
||||
|
||||
if (frame.isKeyPressed(.F5)) {
|
||||
self.restart_game = true;
|
||||
}
|
||||
for (tick_result.audio) |command| {
|
||||
try Audio.mixer.commands.push(command);
|
||||
}
|
||||
|
||||
self.game_linking.callbacks.tick(self.game_state.state, frame);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 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();
|
||||
defer self.graphics.endFrame(frame.clear_color);
|
||||
|
||||
self.graphics.drawCommands(frame.graphics_commands.items);
|
||||
|
||||
if (self.show_debug and build_options.has_imgui) {
|
||||
try self.showDebugWindow(frame);
|
||||
}
|
||||
}
|
||||
|
||||
for (frame.audio_commands.items) |command| {
|
||||
try Audio.mixer.commands.push(command);
|
||||
}
|
||||
|
||||
if (self.restart_game) {
|
||||
self.restart_game = false;
|
||||
|
||||
self.game_linking.callbacks.deinit(self.game_state.state);
|
||||
|
||||
self.game_state.deinit();
|
||||
self.game_state = .init(.{
|
||||
.gpa = self.allocator,
|
||||
.seed = self.seed
|
||||
});
|
||||
|
||||
const opts = Lib.Init{
|
||||
.gpa = self.allocator,
|
||||
.seed = self.game_state.seed,
|
||||
const initial = GameState.Initial{
|
||||
.seed = self.seed,
|
||||
.screen_size = .init(sapp.widthf(), sapp.heightf())
|
||||
};
|
||||
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);
|
||||
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| {
|
||||
switch (cmd) {
|
||||
.text => |str| imgui.text(str)
|
||||
@ -543,6 +722,22 @@ fn showDebugWindow(self: *Engine, frame: *Lib.Frame) !void {
|
||||
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";
|
||||
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 input = &self.game_state.input;
|
||||
const frame = &self.game_state.frame;
|
||||
|
||||
if (imgui.handleEvent(e)) {
|
||||
if (input.mouse_position != null) {
|
||||
input.processEvent(frame, .{
|
||||
if (self.is_mouse_inside) {
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
self.is_mouse_inside = false;
|
||||
}
|
||||
input.mouse_position = null;
|
||||
|
||||
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) {
|
||||
.MOUSE_DOWN => {
|
||||
const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk;
|
||||
|
||||
input.processEvent(frame, .{
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.mouse_pressed = .{
|
||||
.button = mouse_button,
|
||||
.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 => {
|
||||
const mouse_button = Input.getMouseButtonFromSokol(e.mouse_button) orelse break :blk;
|
||||
|
||||
input.processEvent(frame, .{
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.mouse_released = .{
|
||||
.button = mouse_button,
|
||||
.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;
|
||||
},
|
||||
.MOUSE_MOVE => {
|
||||
if (input.mouse_position == null) {
|
||||
input.processEvent(frame, .{
|
||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
} else {
|
||||
input.processEvent(frame, .{
|
||||
if (self.is_mouse_inside) {
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.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;
|
||||
},
|
||||
.MOUSE_ENTER => {
|
||||
if (input.mouse_position == null) {
|
||||
input.processEvent(frame, .{
|
||||
if (!self.is_mouse_inside) {
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
self.is_mouse_inside = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
.RESIZED => {
|
||||
if (input.mouse_position != null) {
|
||||
input.processEvent(frame, .{
|
||||
if (self.is_mouse_inside) {
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
self.is_mouse_inside = false;
|
||||
}
|
||||
|
||||
input.processEvent(frame, .{
|
||||
.window_resize = {}
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.window_resize = .{
|
||||
.width = @intCast(e.window_width),
|
||||
.height = @intCast(e.window_height)
|
||||
}
|
||||
});
|
||||
return true;
|
||||
},
|
||||
.MOUSE_LEAVE => {
|
||||
if (input.mouse_position != null) {
|
||||
input.processEvent(frame, .{
|
||||
if (self.is_mouse_inside) {
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
|
||||
self.is_mouse_inside = false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
.MOUSE_SCROLL => {
|
||||
input.processEvent(frame, .{
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.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 => {
|
||||
const key_code = Input.getKeyCodeFromSokol(e.key_code) orelse break :blk;
|
||||
|
||||
input.processEvent(frame, .{
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.key_pressed = .{
|
||||
.code = key_code,
|
||||
.repeat = e.key_repeat
|
||||
@ -668,14 +886,14 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
|
||||
.KEY_UP => {
|
||||
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
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.CHAR => {
|
||||
input.processEvent(frame, .{
|
||||
try self.queued_events.append(self.allocator, .{
|
||||
.char = @intCast(e.char_code)
|
||||
});
|
||||
|
||||
|
||||
@ -251,9 +251,6 @@ pub fn drawCommands(self: *Graphics, commands: []const Command) void {
|
||||
}
|
||||
|
||||
pub fn beginFrame(self: *Graphics) void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
imgui.newFrame(.{
|
||||
.width = sapp.width(),
|
||||
.height = sapp.height(),
|
||||
@ -275,9 +272,6 @@ pub fn beginFrame(self: *Graphics) void {
|
||||
}
|
||||
|
||||
pub fn endFrame(self: *Graphics, clear_color: Vec4) void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
var pass_action: sg.PassAction = .{};
|
||||
|
||||
pass_action.colors[0] = sg.ColorAttachmentAction{
|
||||
|
||||
@ -7,8 +7,6 @@ const Frame = lib.Frame;
|
||||
const Nanoseconds = lib.Nanoseconds;
|
||||
const Vec2 = lib.Math.Vec2;
|
||||
|
||||
const Input = @This();
|
||||
|
||||
pub const Event = union(enum) {
|
||||
mouse_pressed: struct {
|
||||
button: lib.MouseButton,
|
||||
@ -27,54 +25,14 @@ pub const Event = union(enum) {
|
||||
repeat: bool
|
||||
},
|
||||
key_released: lib.KeyCode,
|
||||
window_resize,
|
||||
window_resize: struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
},
|
||||
char: u21,
|
||||
};
|
||||
|
||||
mouse_position: ?Vec2,
|
||||
|
||||
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 const EventEnum = @typeInfo(Event).@"union".tag_type.?;
|
||||
|
||||
pub fn getKeyCodeFromSokol(key_code: sokol.app.Keycode) ?lib.KeyCode {
|
||||
return switch (key_code) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user