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 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);
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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{
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user