move debug related code to separate struct

This commit is contained in:
Rokas Puzonas 2026-04-04 20:47:20 +03:00
parent 7954a8915a
commit 39c311e225
7 changed files with 450 additions and 430 deletions

View File

@ -21,7 +21,7 @@ pub fn build(b: *std.Build) !void {
.has_tracy = b.option(bool, "tracy", "Tracy integration"),
.win32_has_console = b.option(bool, "console", "Show console (Window only)"),
.win32_png_icon = b.path("src/assets/icon.png"),
.code_hot_reload = b.option(bool, "code-hot-reload", "Dynamically load game code at runtime"),
.code_dynamic_linking = b.option(bool, "code-hot-reload", "Dynamically load game code at runtime"),
.asset_hot_reload = b.option(bool, "asset-hot-reload", "Load assets at runtime"),
.asset_dir = b.path("src/assets/"),
});

View File

@ -14,8 +14,8 @@ const InitOptions = struct {
dep_engine: *std.Build.Dependency,
statically_linked: ?bool = null,
code_hot_reload: ?bool = null,
code_static_linking: ?bool = null,
code_dynamic_linking: ?bool = null,
has_imgui: ?bool = null,
has_tracy: ?bool = null,
win32_has_console: ?bool = null,
@ -31,22 +31,22 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
const isDebug = (opts.optimize == .Debug);
var has_tracy = false;
var code_hot_reload = false;
var code_dynamic_linking = false;
var asset_hot_reload = false;
if (!isWasm) {
code_hot_reload = opts.code_hot_reload orelse isDebug;
code_dynamic_linking = opts.code_dynamic_linking orelse isDebug;
has_tracy = opts.has_tracy orelse isDebug;
asset_hot_reload = opts.asset_hot_reload orelse isDebug;
}
const has_imgui = opts.has_imgui orelse isDebug;
const statically_linked = opts.statically_linked orelse true;
const code_static_linking = opts.code_static_linking orelse true;
var build_options = b.addOptions();
build_options.addOption(bool, "has_imgui", has_imgui);
build_options.addOption(bool, "has_tracy", has_tracy);
build_options.addOption(bool, "code_hot_reload", code_hot_reload);
build_options.addOption(bool, "statically_linked", statically_linked);
build_options.addOption(bool, "code_dynamic_linking", code_dynamic_linking);
build_options.addOption(bool, "code_static_linking", code_static_linking);
build_options.addOption(bool, "asset_hot_reload", asset_hot_reload);
if (asset_hot_reload) {
build_options.addOptionPath("asset_dir", opts.asset_dir);
@ -69,7 +69,7 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
.has_imgui = has_imgui
});
engine_module.root_module.addImport("engine_lib", engine_lib);
if (statically_linked) {
if (code_static_linking) {
engine_module.root_module.addImport("game", opts.root_module);
}
@ -130,7 +130,7 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
if (outer_builder.args) |args| {
run_cmd.addArgs(args);
}
if (code_hot_reload) {
if (code_dynamic_linking) {
opts.root_module.resolved_target = opts.target;
opts.root_module.optimize = opts.optimize;
const game_lib = b.addLibrary(.{

View File

@ -0,0 +1,66 @@
const std = @import("std");
const Environment = @import("./environment.zig");
const GameLinking = @import("./game_linking.zig");
const Input = @import("./input.zig");
const DebugSystem = @This();
allocator: std.mem.Allocator,
is_mouse_inside_imgui: bool,
show_debug: bool,
restart_game: bool,
recording: ?Recording,
play_recording: bool,
recording_tick: u64,
dynamic_linking: ?GameLinking.Dynamic,
const Recording = struct {
arena: std.heap.ArenaAllocator,
initial: Environment.Init,
ticks: std.ArrayList(Environment.Tick),
pub fn init(gpa: std.mem.Allocator, initial: Environment.Init) Recording {
return Recording{
.arena = .init(gpa),
.ticks = .empty,
.initial = initial
};
}
pub fn appendTick(self: *Recording, tick: Environment.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();
}
};
pub fn init(allocator: std.mem.Allocator) DebugSystem {
return DebugSystem{
.allocator = allocator,
.is_mouse_inside_imgui = false,
.show_debug = false,
.restart_game = false,
.recording = null,
.play_recording = false,
.recording_tick = 0,
.dynamic_linking = null
};
}
pub fn deinit(self: *DebugSystem) void {
if (self.dynamic_linking) |*dynamic| {
dynamic.deinit(self.allocator);
}
}

View File

@ -0,0 +1,205 @@
const std = @import("std");
const log = std.log.scoped(.engine);
const assert = std.debug.assert;
const Input = @import("./input.zig");
const ScreenScalar = @import("./screen_scaler.zig");
pub const Lib = @import("engine_lib");
const GameCallbacks = Lib.Callbacks;
const Math = Lib.Math;
const Vec2 = Math.Vec2;
const rgb = Math.rgb;
const Vec4 = Math.Vec4;
const Environment = @This();
gpa: std.mem.Allocator,
callbacks: GameCallbacks,
state: ?GameCallbacks.State,
frame: ?Lib.Frame,
mouse_position: ?Vec2,
started_at: std.time.Instant,
last_frame_at: Lib.Nanoseconds,
pub const Init = struct {
screen_size: Vec2,
seed: u64
};
pub 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, callbacks: GameCallbacks) Environment {
return Environment{
.gpa = gpa,
.mouse_position = null,
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
.last_frame_at = 0,
.state = null,
.frame = null,
.callbacks = callbacks
};
}
pub fn deinit(self: *Environment) void {
if (self.frame) |*frame| {
frame.deinit();
}
}
fn processEvent(self: *Environment, event: Input.Event) void {
assert(self.frame != null);
const frame = &self.frame.?;
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);
},
.window_resize => |opts| {
frame.screen_size = .initFromInt(u32, opts.width, opts.height);
},
else => {}
}
}
pub fn stateInit(self: *Environment, initial: Init) void {
assert(self.frame == null);
const opts = Lib.Init{
.gpa = self.gpa,
.seed = initial.seed,
};
self.state = self.callbacks.init(&opts);
self.frame = .init(self.gpa, initial.screen_size);
}
pub fn stateDeinit(self: *Environment) void {
assert(self.state != null);
self.callbacks.deinit(self.state.?);
self.state = null;
}
pub fn stateTick(self: *Environment, opts: Environment.Tick) !TickResult {
assert(self.frame != null);
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);
}
}
assert(self.state != null);
self.callbacks.tick(self.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();
return TickResult{
.graphics = frame.graphics_commands.items,
.audio = frame.audio_commands.items,
.clear_color = frame.clear_color,
.cursor_hidden = frame.hide_cursor
};
}
pub fn stateDebug(self: *Environment, imgui_ctx: *Lib.ImGui) void {
assert(self.state != null);
self.callbacks.debug(self.state.?, imgui_ctx);
}

View File

@ -8,13 +8,7 @@ const build_options = Lib.build_options;
const GameLinking = @This();
kind: union(enum) {
static,
dynamic: KindDynamic,
},
callbacks: GameCallbacks,
const KindDynamic = struct {
pub const Dynamic = struct {
fan_fd: std.os.linux.fd_t,
path: []const u8,
dir_path: [:0]const u8,
@ -39,7 +33,7 @@ const KindDynamic = struct {
};
}
fn init(gpa: std.mem.Allocator, path: []const u8) !KindDynamic {
pub fn init(gpa: std.mem.Allocator, path: []const u8) !Dynamic {
const dir_path_z = try gpa.dupeZ(u8, std.fs.path.dirname(path) orelse ".");
errdefer gpa.free(dir_path_z);
@ -77,7 +71,7 @@ const KindDynamic = struct {
const lib = try std.DynLib.open(path);
return KindDynamic{
return Dynamic{
.fan_fd = fan_fd,
.dir_path = dir_path_z,
.path = path_dupe,
@ -88,18 +82,18 @@ const KindDynamic = struct {
};
}
fn deinit(self: *KindDynamic, gpa: std.mem.Allocator) void {
pub fn deinit(self: *Dynamic, gpa: std.mem.Allocator) void {
self.lib.close();
gpa.free(self.dir_path);
gpa.free(self.path);
std.posix.close(self.fan_fd);
}
fn getCallbacks(self: *KindDynamic) !GameCallbacks {
pub fn getCallbacks(self: *Dynamic) !GameCallbacks {
return getCallbacksFromLibrary(&self.lib);
}
fn checkForChanges(self: *KindDynamic) !?GameCallbacks {
pub fn checkForChanges(self: *Dynamic) !bool {
const fanotify = std.os.linux.fanotify;
const M = fanotify.event_metadata;
const fan_fd = self.fan_fd;
@ -187,6 +181,8 @@ const KindDynamic = struct {
var new_lib = try std.DynLib.open(tmp_path);
const new_callbacks = try getCallbacksFromLibrary(&new_lib);
// TODO:
_ = new_callbacks; // autofix
try tmp_dir.deleteFile(tmp_filename);
@ -196,16 +192,15 @@ const KindDynamic = struct {
self.reload_index += 1;
self.ignore_next_event = true;
return new_callbacks;
return true;
}
}
return null;
return false;
}
};
const KindStatic = struct {
fn getCallbacks() GameCallbacks {
pub fn getStatic() ?GameCallbacks {
const game = @import("game");
return GameCallbacks{
.init = game.init,
@ -213,52 +208,4 @@ const KindStatic = struct {
.tick = game.tick,
.debug = if (build_options.has_imgui) game.debug else {},
};
}
};
pub fn initDynamic(gpa: std.mem.Allocator, path: []const u8) !GameLinking {
if (!build_options.code_hot_reload) {
return error.NotSupported;
}
var dynamic = try KindDynamic.init(gpa, path);
errdefer dynamic.deinit(gpa);
const callbacks = try dynamic.getCallbacks();
return GameLinking{
.kind = .{ .dynamic = dynamic },
.callbacks = callbacks
};
}
pub fn initStatic() !GameLinking {
if (!build_options.statically_linked) {
return error.NotStaticallyLinked;
}
return GameLinking{
.kind = .static,
.callbacks = KindStatic.getCallbacks()
};
}
pub fn deinit(self: *GameLinking, gpa: std.mem.Allocator) void {
if (build_options.code_hot_reload and self.kind == .dynamic) {
self.kind.dynamic.deinit(gpa);
}
}
pub fn check(self: *GameLinking) !void {
if (self.kind == .static) {
return;
}
if (!build_options.code_hot_reload) {
return;
}
if (try self.kind.dynamic.checkForChanges()) |new_callbacks| {
self.callbacks = new_callbacks;
}
}

View File

@ -28,227 +28,11 @@ pub const Vec2 = Math.Vec2;
const rgb = Math.rgb;
const Vec4 = Math.Vec4;
const DebugSystem = @import("./debug_system.zig");
const GameLinking = @import("game_linking.zig");
const GameState = struct {
gpa: std.mem.Allocator,
state: ?GameCallbacks.State,
frame: ?Lib.Frame,
mouse_position: ?Vec2,
started_at: std.time.Instant,
last_frame_at: Lib.Nanoseconds,
const Init = struct {
screen_size: Vec2,
seed: u64
};
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) GameState {
return GameState{
.gpa = gpa,
.mouse_position = null,
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
.last_frame_at = 0,
.state = null,
.frame = null,
};
}
pub fn deinit(self: *GameState) void {
if (self.frame) |*frame| {
frame.deinit();
}
}
fn processEvent(self: *GameState, event: Input.Event) void {
assert(self.frame != null);
const frame = &self.frame.?;
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);
},
.window_resize => |opts| {
frame.screen_size = .initFromInt(u32, opts.width, opts.height);
},
else => {}
}
}
fn runGameInit(
self: *GameState,
init_fn: GameCallbacks.InitFn,
initial: Init,
) void {
assert(self.frame == null);
const opts = Lib.Init{
.gpa = self.gpa,
.seed = initial.seed,
};
self.state = init_fn(&opts);
self.frame = .init(self.gpa, initial.screen_size);
}
fn runGameDeinit(self: *GameState, deinit_fn: GameCallbacks.DeinitFn) void {
assert(self.state != null);
deinit_fn(self.state.?);
self.state = null;
}
fn tick(self: *GameState, tick_fn: GameCallbacks.TickFn, opts: GameState.Tick) !TickResult {
assert(self.frame != null);
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);
}
}
assert(self.state != null);
tick_fn(self.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();
return TickResult{
.graphics = frame.graphics_commands.items,
.audio = frame.audio_commands.items,
.clear_color = frame.clear_color,
.cursor_hidden = 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);
}
};
const Recording = struct {
arena: std.heap.ArenaAllocator,
initial: GameState.Init,
ticks: std.ArrayList(GameState.Tick),
pub fn init(gpa: std.mem.Allocator, initial: GameState.Init) 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 Environment = @import("environment.zig");
const Engine = @This();
@ -256,46 +40,49 @@ allocator: std.mem.Allocator,
graphics: Graphics,
audio: Audio,
game_linking: GameLinking,
game_state: GameState,
seed: u64,
// game_linking: GameLinking,
environment: Environment,
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,
debug: DebugSystem,
pub const RunOptions = struct {
pub const AssetLoading = union(enum) {
bundle_bytes: []const u8,
bundle_path: []const u8,
dir_path: []const u8
};
allocator: std.mem.Allocator,
game_library_path: ?[]const u8 = null,
do_recording: bool = (builtin.mode == .Debug),
screen_width: u32 = 640,
screen_height: u32 = 480,
asset_loading: AssetLoading
};
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();
// 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();
// }
var debug_system = DebugSystem.init(opts.allocator);
var maybe_callbacks: ?GameCallbacks = null;
if (build_options.code_static_linking) {
maybe_callbacks = GameLinking.getStatic();
}
if (build_options.code_dynamic_linking and opts.game_library_path != null) {
var dynamic = try GameLinking.Dynamic.init(opts.allocator, opts.game_library_path.?);
maybe_callbacks = try dynamic.getCallbacks();
debug_system.dynamic_linking = dynamic;
}
const initial = GameState.Init{
if (maybe_callbacks == null) {
return error.NotLinked;
}
const callbacks = maybe_callbacks.?;
const initial = Environment.Init{
.seed = @bitCast(std.time.milliTimestamp()),
.screen_size = .initFromInt(u32, opts.screen_width, opts.screen_height)
};
@ -314,23 +101,19 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
.allocator = opts.allocator,
.logger = .{ .func = sokolLogCallback },
}),
.game_state = .init(opts.allocator),
.game_linking = game_linking,
.show_debug = false,
.restart_game = false,
.recording = null,
.environment = .init(opts.allocator, callbacks),
.queued_events = .empty,
.is_mouse_inside = false,
.play_recording = false,
.recording_tick = 0,
.seed = initial.seed,
.is_mouse_inside = false,
.debug = debug_system
};
self.game_state.runGameInit(game_linking.callbacks.init, initial);
self.environment.stateInit(initial);
if (opts.do_recording) {
self.recording = .init(opts.allocator, initial);
}
// TODO:
// if (opts.do_recording) {
// self.recording = .init(opts.allocator, initial);
// }
tracy.setThreadName("Main");
@ -412,16 +195,19 @@ fn sokolCleanup(self: *Engine) void {
const zone = tracy.initZone(@src(), .{ });
defer zone.deinit();
if (self.recording) |recording| {
recording.deinit();
}
// TODO:
// 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.environment.stateDeinit();
self.environment.deinit();
self.audio.deinit();
self.graphics.deinit(self.allocator);
self.game_linking.deinit(self.allocator);
self.debug.deinit();
// TODO:
// self.game_linking.deinit(self.allocator);
}
fn sokolFrame(self: *Engine) !void {
@ -430,38 +216,44 @@ fn sokolFrame(self: *Engine) !void {
const zone = tracy.initZone(@src(), .{ });
defer zone.deinit();
try self.game_linking.check();
if (self.debug.dynamic_linking) |*dynamic| {
if (try dynamic.checkForChanges()) {
self.environment.callbacks = try dynamic.getCallbacks();
}
}
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;
const time_passed = now.since(self.environment.started_at);
const dt_ns = time_passed - self.environment.last_frame_at;
self.environment.last_frame_at = time_passed;
if (self.play_recording) {
assert(self.recording != null);
if (self.recording_tick >= self.recording.?.ticks.items.len) {
self.play_recording = false;
}
}
// TODO:
// 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{
// TODO:
var tick: Environment.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 = Environment.Tick{
.dt_ns = dt_ns,
.events = self.queued_events.items
};
//
// if (self.recording) |*recording| {
// try recording.appendTick(tick);
// }
// }
if (self.recording) |*recording| {
try recording.appendTick(tick);
}
}
const tick_result = try self.game_state.tick(self.game_linking.callbacks.tick, tick);
const tick_result = try self.environment.stateTick(tick);
self.queued_events.clearRetainingCapacity();
@ -474,9 +266,10 @@ fn sokolFrame(self: *Engine) !void {
self.graphics.drawCommands(tick_result.graphics);
if (self.show_debug and build_options.has_imgui) {
try self.showDebugWindow();
}
// TODO:
// if (self.show_debug and build_options.has_imgui) {
// try self.showDebugWindow();
// }
}
for (tick_result.audio) |command| {
@ -484,25 +277,27 @@ fn sokolFrame(self: *Engine) !void {
}
}
if (self.restart_game) {
self.restart_game = false;
const initial = GameState.Init{
.seed = self.seed,
.screen_size = .init(sapp.widthf(), sapp.heightf())
};
self.game_state.runGameDeinit(self.game_linking.callbacks.deinit);
self.game_state.deinit();
self.game_state = .init(self.allocator);
self.game_state.runGameInit(self.game_linking.callbacks.init, initial);
if (self.recording) |recording| {
recording.deinit();
self.recording = .init(self.allocator, initial);
}
}
// TODO:
// if (self.restart_game) {
// self.restart_game = false;
//
// const initial = Environment.Init{
// .seed = self.seed,
// .screen_size = .init(sapp.widthf(), sapp.heightf())
// };
//
// self.environment.runGameDeinit(self.game_linking.callbacks.deinit);
// self.environment.deinit();
//
// self.environment = .init(self.allocator);
// self.environment.runGameInit(self.game_linking.callbacks.init, initial);
//
// // TODO:
// // if (self.recording) |recording| {
// // recording.deinit();
// // self.recording = .init(self.allocator, initial);
// // }
// }
}
fn showDebugWindow(self: *Engine) !void {
@ -524,7 +319,7 @@ fn showDebugWindow(self: *Engine) !void {
var imgui_ctx = Lib.ImGui.init(self.allocator);
defer imgui_ctx.deinit();
self.game_state.runGameDebug(self.game_linking.callbacks.debug, &imgui_ctx);
self.environment.runGameDebug(self.game_linking.callbacks.debug, &imgui_ctx);
for (imgui_ctx.commands.items) |cmd| {
switch (cmd) {
.text => |str| imgui.text(str)
@ -535,31 +330,34 @@ fn showDebugWindow(self: *Engine) !void {
if (imgui.beginTabItem("Engine")) {
defer imgui.endTabItem();
if (imgui.button("Restart")) {
self.restart_game = true;
}
// TODO:
// if (imgui.button("Restart")) {
// self.restart_game = true;
// }
if (self.recording) |recording| {
imgui.beginDisabled(self.play_recording);
defer imgui.endDisabled();
// TODO:
// if (self.recording) |recording| {
// imgui.beginDisabled(self.play_recording);
// defer imgui.endDisabled();
//
// if (imgui.button("Replay")) {
// self.environment.runGameDeinit(self.game_linking.callbacks.deinit);
// self.environment.deinit();
//
// self.environment = .init(self.allocator);
// self.environment.runGameInit(self.game_linking.callbacks.init, recording.initial);
//
// self.play_recording = true;
// self.recording_tick = 0;
// }
// }
if (imgui.button("Replay")) {
self.game_state.runGameDeinit(self.game_linking.callbacks.deinit);
self.game_state.deinit();
self.game_state = .init(self.allocator);
self.game_state.runGameInit(self.game_linking.callbacks.init, recording.initial);
self.play_recording = true;
self.recording_tick = 0;
}
}
if (self.game_linking.kind == .dynamic) {
imgui.textFmt("Linking: dynamic (from {s})", .{ self.game_linking.kind.dynamic.path });
} else {
imgui.textFmt("Linking: static", .{ });
}
// TODO:
// if (self.game_linking.kind == .dynamic) {
// imgui.textFmt("Linking: dynamic (from {s})", .{ self.game_linking.kind.dynamic.path });
// } else {
// imgui.textFmt("Linking: static", .{ });
// }
if (build_options.asset_hot_reload) {
imgui.textFmt("Asset loading: runtime (from {s})", .{build_options.asset_dir});
@ -569,7 +367,7 @@ fn showDebugWindow(self: *Engine) !void {
imgui.textFmt("Seed: 0x{x:08}", .{ self.seed });
const time_ms: f64 = @floatFromInt(@divFloor(self.game_state.last_frame_at, std.time.ns_per_ms));
const time_ms: f64 = @floatFromInt(@divFloor(self.environment.last_frame_at, std.time.ns_per_ms));
imgui.textFmt("Time: {:.2}", .{ time_ms / 1000 });
// imgui.textFmt("Draw commands: {}\n", .{
@ -602,19 +400,23 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
if (debug_keybinds and e.type == .KEY_DOWN) {
if (e.key_code == .F4) {
self.show_debug = !self.show_debug;
// TODO:
// self.show_debug = !self.show_debug;
return true;
}
if (e.key_code == .F5) {
self.restart_game = true;
// TODO:
// self.restart_game = true;
return true;
}
}
if (self.play_recording) {
return false;
}
// TODO:
// if (self.play_recording) {
// return false;
// }
blk: switch (e.type) {
.MOUSE_DOWN => {

View File

@ -29,16 +29,16 @@ pub fn main() !void {
var game_library_path: ?[]const u8 = null;
defer if (game_library_path) |str| allocator.free(str);
var asset_loading: Engine.RunOptions.AssetLoading = undefined;
if (!build_options.asset_hot_reload) {
asset_loading = .{
.bundle_bytes = @embedFile("asset_bundle")
};
} else {
asset_loading = .{
.dir_path = build_options.asset_dir
};
}
// var asset_loading: Engine.RunOptions.AssetLoading = undefined;
// if (!build_options.asset_hot_reload) {
// asset_loading = .{
// .bundle_bytes = @embedFile("asset_bundle")
// };
// } else {
// asset_loading = .{
// .dir_path = build_options.asset_dir
// };
// }
if (!isWasm) {
var cli: CLI = undefined;
@ -51,7 +51,7 @@ pub fn main() !void {
});
var opt_game_lib_path: ?CLI.Option.Id = null;
if (build_options.code_hot_reload) {
if (build_options.code_dynamic_linking) {
opt_game_lib_path = try cli.addOption(.{
.long_name = "game-lib-path",
.description = "Load game code from dynamic library",
@ -103,7 +103,7 @@ pub fn main() !void {
try engine.run(.{
.allocator = allocator,
.game_library_path = game_library_path,
.asset_loading = asset_loading
// .asset_loading = asset_loading
});
}