move debug related code to separate struct
This commit is contained in:
parent
7954a8915a
commit
39c311e225
@ -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/"),
|
||||
});
|
||||
|
||||
@ -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(.{
|
||||
|
||||
66
engine/src/engine/debug_system.zig
Normal file
66
engine/src/engine/debug_system.zig
Normal 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);
|
||||
}
|
||||
}
|
||||
205
engine/src/engine/environment.zig
Normal file
205
engine/src/engine/environment.zig
Normal 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);
|
||||
}
|
||||
@ -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,69 +192,20 @@ 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 {
|
||||
const game = @import("game");
|
||||
return GameCallbacks{
|
||||
.init = game.init,
|
||||
.deinit = game.deinit,
|
||||
.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 getStatic() ?GameCallbacks {
|
||||
const game = @import("game");
|
||||
return GameCallbacks{
|
||||
.init = game.init,
|
||||
.deinit = game.deinit,
|
||||
.tick = game.tick,
|
||||
.debug = if (build_options.has_imgui) game.debug else {},
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
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;
|
||||
|
||||
if (self.play_recording) {
|
||||
assert(self.recording != null);
|
||||
if (self.recording_tick >= self.recording.?.ticks.items.len) {
|
||||
self.play_recording = false;
|
||||
if (self.debug.dynamic_linking) |*dynamic| {
|
||||
if (try dynamic.checkForChanges()) {
|
||||
self.environment.callbacks = try dynamic.getCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
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{
|
||||
const now = std.time.Instant.now() catch @panic("Instant.now() unsupported");
|
||||
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;
|
||||
|
||||
// TODO:
|
||||
// if (self.play_recording) {
|
||||
// assert(self.recording != null);
|
||||
// if (self.recording_tick >= self.recording.?.ticks.items.len) {
|
||||
// self.play_recording = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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 => {
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user