add game code loading

This commit is contained in:
Rokas Puzonas 2026-02-08 01:15:46 +02:00
parent 6407263540
commit 9d733902b5
4 changed files with 113 additions and 37 deletions

View File

@ -20,7 +20,7 @@ pub fn build(b: *std.Build) !void {
.has_imgui = b.option(bool, "imgui", "ImGui integration"),
.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")
.win32_png_icon = b.path("src/assets/icon.png"),
});
{

View File

@ -14,6 +14,7 @@ const InitOptions = struct {
dep_engine: *std.Build.Dependency,
hot_reload: ?bool = null,
has_imgui: ?bool = null,
has_tracy: ?bool = null,
win32_has_console: ?bool = null,
@ -23,12 +24,19 @@ const InitOptions = struct {
pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
const b = opts.dep_engine.builder;
const isWasm = opts.target.result.cpu.arch.isWasm();
var hot_reload = false;
if (!isWasm) {
hot_reload = opts.hot_reload orelse (opts.optimize == .Debug);
}
opts.root_module.resolved_target = opts.target;
opts.root_module.optimize = opts.optimize;
const game_lib = b.addLibrary(.{
.name = "game",
.root_module = opts.root_module,
.linkage = .static,
.linkage = if (hot_reload) .dynamic else .static,
});
const engine_lib = b.createModule(.{
.root_source_file = b.path("src/lib/root.zig")
@ -39,7 +47,8 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
.target = opts.target,
.optimize = opts.optimize,
.has_tracy = opts.has_tracy,
.has_imgui = opts.has_imgui
.has_imgui = opts.has_imgui,
.hot_reload = hot_reload
});
const mod_main = b.createModule(.{
@ -50,10 +59,12 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
});
mod_main.addImport("lib", engine_lib);
mod_main.addImport("engine", engine_module.root_module);
mod_main.linkLibrary(game_lib);
if (!hot_reload) {
mod_main.linkLibrary(game_lib);
}
var run_cmd_step: *std.Build.Step = undefined;
if (opts.target.result.cpu.arch.isWasm()) {
if (isWasm) {
const build_step = buildWasm(b, .{
.name = "index",
.root_module = mod_main,
@ -83,6 +94,9 @@ pub fn init(outer_builder: *std.Build, opts: InitOptions) void {
if (outer_builder.args) |args| {
run_cmd.addArgs(args);
}
if (hot_reload) {
run_cmd.addFileArg(game_lib.getEmittedBin());
}
run_cmd_step = &run_cmd.step;
}
}
@ -101,6 +115,7 @@ const EngineModule = struct {
has_imgui: ?bool = null,
has_tracy: ?bool = null,
hot_reload: bool
};
};
@ -197,6 +212,7 @@ fn createEngineModule(b: *std.Build, opts: EngineModule.Options) EngineModule {
var options = b.addOptions();
options.addOption(bool, "has_imgui", has_imgui);
options.addOption(bool, "has_tracy", has_tracy);
options.addOption(bool, "hot_reload", opts.hot_reload);
mod.addOptions("build_options", options);
return EngineModule{

View File

@ -1,8 +1,34 @@
const std = @import("std");
const builtin = @import("builtin");
const Engine = @import("engine");
var engine: Engine = undefined;
pub fn main() !void {
try engine.run(.{});
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug_allocator.deinit();
// TODO: Use tracy TracingAllocator
var allocator: std.mem.Allocator = undefined;
if (builtin.cpu.arch.isWasm()) {
allocator = std.heap.wasm_allocator;
} else if (builtin.mode == .Debug) {
allocator = debug_allocator.allocator();
} else {
allocator = std.heap.smp_allocator;
}
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
var game_library_path: ?[]const u8 = null;
if (args.len >= 2) {
game_library_path = args[1];
}
try engine.run(.{
.allocator = allocator,
.game_library_path = game_library_path
});
}

View File

@ -17,39 +17,72 @@ const STBImage = @import("stb_image");
const Gfx = Graphics;
const build_options = @import("build_options");
const Lib = @import("lib");
pub const Math = Lib.Math;
pub const Vec2 = Math.Vec2;
const rgb = Math.rgb;
const GameCallbacks = struct {
init: *const fn(opts: *const Lib.Init) callconv(.c) void,
tick: *const fn(frame: *Lib.Frame) callconv(.c) void,
deinit: *const fn() callconv(.c) void,
debug: *const fn(imgui: *Lib.ImGui) callconv(.c) void,
const Init = *const fn(opts: *const Lib.Init) callconv(.c) void;
const Tick = *const fn(frame: *Lib.Frame) callconv(.c) void;
const Deinit = *const fn() callconv(.c) void;
const Debug = *const fn(imgui: *Lib.ImGui) callconv(.c) void;
init: Init,
tick: Tick,
deinit: Deinit,
debug: if (build_options.has_imgui) Debug else void
};
extern fn init(opts: *const Lib.Init) void;
extern fn tick(frame: *Lib.Frame) void;
extern fn deinit() void;
extern fn debug(imgui: *Lib.ImGui) void;
const static_game_callbacks = GameCallbacks{
.init = init,
.deinit = deinit,
.tick = tick,
.debug = debug,
};
const GameLinking = union(enum) {
static,
dynamic: struct {
lib: std.DynLib
},
pub fn getCallbacks(self: *GameLinking) !GameCallbacks {
switch (self.*) {
.static => {
if (build_options.hot_reload) {
return error.StaticLinkingDisabled;
}
return GameCallbacks{
.init = init,
.deinit = deinit,
.tick = tick,
.debug = if (build_options.has_imgui) debug else {},
};
},
.dynamic => |*opts| {
const lib = &opts.lib;
return GameCallbacks{
.init = lib.lookup(GameCallbacks.Init, "init") orelse return error.MissingFunction,
.tick = lib.lookup(GameCallbacks.Tick, "tick") orelse return error.MissingFunction,
.deinit = lib.lookup(GameCallbacks.Deinit, "deinit") orelse return error.MissingFunction,
.debug = if (build_options.has_imgui) lib.lookup(GameCallbacks.Debug, "debug") orelse return error.MissingFunction else {},
};
}
}
}
};
const Engine = @This();
pub const Nanoseconds = u64;
allocator: std.mem.Allocator,
started_at: std.time.Instant,
last_frame_at: Nanoseconds,
last_frame_at: Lib.Nanoseconds,
graphics: Graphics,
input: Input,
game_linking: GameLinking,
game_callbacks: GameCallbacks,
frame: Lib.Frame,
@ -57,34 +90,34 @@ frame: Lib.Frame,
previous_tick_canvas_size: ?Vec2 = null,
const RunOptions = struct {
window_title: [*:0]const u8 = "Game",
window_width: u31 = 640,
window_height: u31 = 480,
allocator: std.mem.Allocator,
game_library_path: ?[]const u8 = null
};
pub fn run(self: *Engine, opts: RunOptions) !void {
self.* = Engine{
.allocator = undefined,
.allocator = opts.allocator,
.started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"),
.last_frame_at = 0,
.frame = undefined,
.graphics = undefined,
.game_callbacks = static_game_callbacks,
.game_linking = undefined,
.game_callbacks = undefined,
.input = .{}
};
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug_allocator.deinit();
// TODO: Use tracy TracingAllocator
if (builtin.cpu.arch.isWasm()) {
self.allocator = std.heap.wasm_allocator;
} else if (builtin.mode == .Debug) {
self.allocator = debug_allocator.allocator();
if (opts.game_library_path) |game_library_path| {
self.game_linking = .{
.dynamic = .{
.lib = try std.DynLib.open(game_library_path)
}
};
} else {
self.allocator = std.heap.smp_allocator;
self.game_linking = .static;
}
self.game_callbacks = try self.game_linking.getCallbacks();
self.frame.init(self.allocator);
tracy.setThreadName("Main");
@ -120,10 +153,10 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
.cleanup_userdata_cb = sokolCleanupCallback,
.event_userdata_cb = sokolEventCallback,
.user_data = self,
.width = opts.window_width,
.height = opts.window_height,
.width = 640,
.height = 480,
.icon = icon,
.window_title = opts.window_title,
.window_title = "Game",
.logger = .{ .func = sokolLogCallback },
.win32 = .{
.console_utf8 = true
@ -238,7 +271,7 @@ fn sokolFrame(self: *Engine) !void {
self.graphics.drawCommands(frame.graphics_commands.items);
if (frame.show_debug) {
if (frame.show_debug and build_options.has_imgui) {
try self.showDebugWindow(frame);
}
}
@ -259,6 +292,7 @@ fn showDebugWindow(self: *Engine, frame: *Lib.Frame) !void {
defer imgui.endWindow();
_ = imgui.beginTabBar("debug");
defer imgui.endTabBar();
if (imgui.beginTabItem("Game")) {
defer imgui.endTabItem();