From e5fccc21f2a43e392e4a998421c94ce30515ee5b Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sat, 7 Feb 2026 21:22:14 +0200 Subject: [PATCH] split engine to separate library --- build.zig | 299 +------------- build.zig.zon | 29 +- engine/build.zig | 385 ++++++++++++++++++ engine/build.zig.zon | 42 ++ {libs => engine/libs}/stb/build.zig | 0 {libs => engine/libs}/stb/build.zig.zon | 0 {libs => engine/libs}/stb/src/stb_image.zig | 0 .../libs}/stb/src/stb_image_impl.c | 0 {libs => engine/libs}/stb/src/stb_vorbis.zig | 0 .../libs}/stb/src/stb_vorbis_impl.c | 0 {src/engine => engine/src}/audio/data.zig | 0 {src/engine => engine/src}/audio/mixer.zig | 0 {src/engine => engine/src}/audio/root.zig | 0 {src/engine => engine/src}/audio/store.zig | 0 .../src}/fontstash/context.zig | 0 {src/engine => engine/src}/fontstash/font.zig | 0 {src/engine => engine/src}/fontstash/root.zig | 0 .../src}/fontstash/sokol_fontstash_impl.c | 0 {src/engine => engine/src}/frame.zig | 0 {src/engine => engine/src}/graphics.zig | 115 +++--- {src/engine => engine/src}/imgui.zig | 0 {src/engine => engine/src}/input.zig | 0 {src => engine/src}/main.zig | 2 +- {src/engine => engine/src}/math.zig | 0 {src/engine => engine/src}/root.zig | 121 ++++-- {src/engine => engine/src}/screen_scaler.zig | 0 {src/engine => engine/src}/shell.html | 0 {tools => engine/tools}/png-to-icon.zig | 0 src/assets.zig | 44 -- src/game.zig | 97 +++-- 30 files changed, 639 insertions(+), 495 deletions(-) create mode 100644 engine/build.zig create mode 100644 engine/build.zig.zon rename {libs => engine/libs}/stb/build.zig (100%) rename {libs => engine/libs}/stb/build.zig.zon (100%) rename {libs => engine/libs}/stb/src/stb_image.zig (100%) rename {libs => engine/libs}/stb/src/stb_image_impl.c (100%) rename {libs => engine/libs}/stb/src/stb_vorbis.zig (100%) rename {libs => engine/libs}/stb/src/stb_vorbis_impl.c (100%) rename {src/engine => engine/src}/audio/data.zig (100%) rename {src/engine => engine/src}/audio/mixer.zig (100%) rename {src/engine => engine/src}/audio/root.zig (100%) rename {src/engine => engine/src}/audio/store.zig (100%) rename {src/engine => engine/src}/fontstash/context.zig (100%) rename {src/engine => engine/src}/fontstash/font.zig (100%) rename {src/engine => engine/src}/fontstash/root.zig (100%) rename {src/engine => engine/src}/fontstash/sokol_fontstash_impl.c (100%) rename {src/engine => engine/src}/frame.zig (100%) rename {src/engine => engine/src}/graphics.zig (78%) rename {src/engine => engine/src}/imgui.zig (100%) rename {src/engine => engine/src}/input.zig (100%) rename {src => engine/src}/main.zig (65%) rename {src/engine => engine/src}/math.zig (100%) rename {src/engine => engine/src}/root.zig (82%) rename {src/engine => engine/src}/screen_scaler.zig (100%) rename {src/engine => engine/src}/shell.html (100%) rename {tools => engine/tools}/png-to-icon.zig (100%) delete mode 100644 src/assets.zig diff --git a/build.zig b/build.zig index 004e8b7..25b00a8 100644 --- a/build.zig +++ b/build.zig @@ -1,161 +1,32 @@ const std = @import("std"); -const sokol = @import("sokol"); -const builtin = @import("builtin"); +const Engine = @import("engine"); pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse (optimize == .Debug); - var has_tracy = b.option(bool, "tracy", "Tracy integration") orelse (optimize == .Debug); - const has_console = b.option(bool, "console", "Show console (Window only)") orelse (optimize == .Debug); - - const isWasm = target.result.cpu.arch.isWasm(); - - if (isWasm) { - has_tracy = false; - } - - const mod_main = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - .link_libc = true + const mod = b.createModule(.{ + .root_source_file = b.path("src/game.zig"), }); - - const dep_sokol = b.dependency("sokol", .{ - .target = target, - .optimize = optimize, - .with_sokol_imgui = has_imgui, - }); - mod_main.linkLibrary(dep_sokol.artifact("sokol_clib")); - mod_main.addImport("sokol", dep_sokol.module("sokol")); - - if (has_imgui) { - if (b.lazyDependency("cimgui", .{ - .target = target, - .optimize = optimize, - })) |dep_cimgui| { - const cimgui = b.lazyImport(@This(), "cimgui").?; - const cimgui_conf = cimgui.getConfig(false); - mod_main.addImport("cimgui", dep_cimgui.module(cimgui_conf.module_name)); - dep_sokol.artifact("sokol_clib").addIncludePath(dep_cimgui.path(cimgui_conf.include_dir)); - } - } - - const dep_tracy = b.dependency("tracy", .{ - .target = target, - .optimize = optimize, - .tracy_enable = has_tracy, - .tracy_only_localhost = true - }); - if (has_tracy) { - mod_main.linkLibrary(dep_tracy.artifact("tracy")); - } - mod_main.addImport("tracy", dep_tracy.module("tracy")); - const dep_tiled = b.dependency("tiled", .{}); - mod_main.addImport("tiled", dep_tiled.module("tiled")); + mod.addImport("tiled", dep_tiled.module("tiled")); - const dep_stb = b.dependency("stb", .{}); - mod_main.addImport("stb_image", dep_stb.module("stb_image")); - mod_main.addImport("stb_vorbis", dep_stb.module("stb_vorbis")); - - const dep_fontstash_c = b.dependency("fontstash_c", .{}); - mod_main.addIncludePath(dep_fontstash_c.path("src")); - - const dep_sokol_c = b.dependency("sokol_c", .{}); - { - var cflags_buffer: [64][]const u8 = undefined; - var cflags = std.ArrayListUnmanaged([]const u8).initBuffer(&cflags_buffer); - switch (sokol.resolveSokolBackend(.auto, target.result)) { - .d3d11 => try cflags.appendBounded("-DSOKOL_D3D11"), - .metal => try cflags.appendBounded("-DSOKOL_METAL"), - .gl => try cflags.appendBounded("-DSOKOL_GLCORE"), - .gles3 => try cflags.appendBounded("-DSOKOL_GLES3"), - .wgpu => try cflags.appendBounded("-DSOKOL_WGPU"), - else => @panic("unknown sokol backend"), - } - - mod_main.addIncludePath(dep_sokol_c.path("util")); - mod_main.addCSourceFile(.{ - .file = b.path("src/engine/fontstash/sokol_fontstash_impl.c"), - .flags = cflags.items - }); - } - - // TODO: - // const sdl = b.dependency("sdl", .{ - // .optimize = optimize, - // .target = target, - // .linkage = .static, - // .default_target_config = !isWasm - // }); - // mod_main.linkLibrary(sdl.artifact("SDL3")); - // if (isWasm) { - // // TODO: Define buid config for wasm - // } - - var options = b.addOptions(); - options.addOption(bool, "has_imgui", has_imgui); - options.addOption(bool, "has_tracy", has_tracy); - mod_main.addOptions("build_options", options); - - // from here on different handling for native vs wasm builds - if (target.result.cpu.arch.isWasm()) { - try buildWasm(b, .{ - .name = "sokol_template", - .mod_main = mod_main, - .dep_sokol = dep_sokol, - }); - } else { - try buildNative(b, "sokol_template", mod_main, has_console); - } -} - -fn buildNative(b: *std.Build, name: []const u8, mod: *std.Build.Module, has_console: bool) !void { - const exe = b.addExecutable(.{ - .name = name, - .root_module = mod + Engine.init(b, .{ + .name = "sokol_template", + .root_module = mod, + .target = target, + .optimize = optimize, + .dep_engine = b.dependency("engine", .{}), + .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") }); - const target = mod.resolved_target.?; - if (target.result.os.tag == .windows) { - exe.subsystem = if (has_console) .Console else .Windows; - - const png_to_icon_tool = b.addExecutable(.{ - .name = "png-to-icon", - .root_module = b.createModule(.{ - .target = b.graph.host, - .root_source_file = b.path("tools/png-to-icon.zig"), - }), - }); - const dep_stb_image = b.dependency("stb_image", .{}); - png_to_icon_tool.root_module.addImport("stb_image", dep_stb_image.module("stb_image")); - - const png_to_icon_step = b.addRunArtifact(png_to_icon_tool); - png_to_icon_step.addFileArg(b.path("src/assets/icon.png")); - const icon_file = png_to_icon_step.addOutputFileArg("icon.ico"); - - const add_icon_step = AddExecutableIcon.init(exe, icon_file); - exe.step.dependOn(&add_icon_step.step); - } - b.installArtifact(exe); - - { - const run_step = b.step("run", "Run game"); - - const run_cmd = b.addRunArtifact(exe); - run_step.dependOn(&run_cmd.step); - run_cmd.step.dependOn(b.getInstallStep()); - - if (b.args) |args| { - run_cmd.addArgs(args); - } - } { + mod.resolved_target = target; const exe_tests = b.addTest(.{ - .root_module = exe.root_module, + .root_module = mod, }); const run_exe_tests = b.addRunArtifact(exe_tests); @@ -164,141 +35,3 @@ fn buildNative(b: *std.Build, name: []const u8, mod: *std.Build.Module, has_cons test_step.dependOn(&run_exe_tests.step); } } - -const BuildWasmOptions = struct { - name: []const u8, - mod_main: *std.Build.Module, - dep_sokol: *std.Build.Dependency, -}; - -fn patchWasmIncludeDirs( - module: *std.Build.Module, - path: std.Build.LazyPath, - depend_step: *std.Build.Step -) void { - if (module.link_libc != null and module.link_libc.?) { - // need to inject the Emscripten system header include path into - // the cimgui C library otherwise the C/C++ code won't find - // C stdlib headers - module.addSystemIncludePath(path); - } - - for (module.import_table.values()) |imported_module| { - patchWasmIncludeDirs(imported_module, path, depend_step); - } - - for (module.link_objects.items) |link_object| { - if (link_object != .other_step) { - continue; - } - const lib = link_object.other_step; - if (&lib.step == depend_step) { - continue; - } - - if (lib.root_module.link_libc != null and lib.root_module.link_libc.?) { - // need to inject the Emscripten system header include path into - // the cimgui C library otherwise the C/C++ code won't find - // C stdlib headers - lib.root_module.addSystemIncludePath(path); - - // all C libraries need to depend on the sokol library, when building for - // WASM this makes sure that the Emscripten SDK has been setup before - // C compilation is attempted (since the sokol C library depends on the - // Emscripten SDK setup step) - lib.step.dependOn(depend_step); - } - patchWasmIncludeDirs(lib.root_module, path, depend_step); - } -} - -fn buildWasm(b: *std.Build, opts: BuildWasmOptions) !void { - opts.mod_main.sanitize_c = .off; - - // build the main file into a library, this is because the WASM 'exe' - // needs to be linked in a separate build step with the Emscripten linker - const main_lib = b.addLibrary(.{ - .name = "index", - .root_module = opts.mod_main, - }); - - const dep_emsdk = opts.dep_sokol.builder.dependency("emsdk", .{}); - - patchWasmIncludeDirs( - opts.mod_main, - dep_emsdk.path("upstream/emscripten/cache/sysroot/include"), - &(opts.dep_sokol.artifact("sokol_clib").step) - ); - - // create a build step which invokes the Emscripten linker - const link_step = try sokol.emLinkStep(b, .{ - .lib_main = main_lib, - .target = opts.mod_main.resolved_target.?, - .optimize = opts.mod_main.optimize.?, - .emsdk = dep_emsdk, - .use_webgl2 = true, - .use_emmalloc = true, - .use_filesystem = false, - .shell_file_path = b.path("src/engine/shell.html"), - }); - // attach to default target - b.getInstallStep().dependOn(&link_step.step); - // ...and a special run step to start the web build output via 'emrun' - const run = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk }); - run.step.dependOn(&link_step.step); - b.step("run", "Run game").dependOn(&run.step); - - // TODO: Create a zip archive of all of the files. Would be useful for easier itch.io upload -} - -const AddExecutableIcon = struct { - obj: *std.Build.Step.Compile, - step: std.Build.Step, - icon_file: std.Build.LazyPath, - resource_file: std.Build.LazyPath, - - fn init(obj: *std.Build.Step.Compile, icon_file: std.Build.LazyPath) *AddExecutableIcon { - const b = obj.step.owner; - const self = b.allocator.create(AddExecutableIcon) catch @panic("OOM"); - - self.obj = obj; - self.step = std.Build.Step.init(.{ - .id = .custom, - .name = "add executable icon", - .owner = b, - .makeFn = make - }); - - self.icon_file = icon_file; - icon_file.addStepDependencies(&self.step); - - const write_files = b.addWriteFiles(); - self.resource_file = write_files.add("resource-file.rc", ""); - self.step.dependOn(&write_files.step); - - self.obj.addWin32ResourceFile(.{ - .file = self.resource_file, - }); - - return self; - } - - fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { - const b = step.owner; - const self: *AddExecutableIcon = @fieldParentPtr("step", step); - - const resource_file = try std.fs.cwd().createFile(self.resource_file.getPath(b), .{ }); - defer resource_file.close(); - - const relative_icon_path = try std.fs.path.relative( - b.allocator, - self.resource_file.dirname().getPath(b), - self.icon_file.getPath(b) - ); - std.mem.replaceScalar(u8, relative_icon_path, '\\', '/'); - - try resource_file.writeAll("IDI_ICON ICON \""); - try resource_file.writeAll(relative_icon_path); - try resource_file.writeAll("\""); - } -}; diff --git a/build.zig.zon b/build.zig.zon index 0aee37a..0b8a83d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,37 +4,12 @@ .fingerprint = 0x60a8e079a691c8d9, // Changing this has security and trust implications. .minimum_zig_version = "0.15.2", .dependencies = .{ - .sokol = .{ - .url = "git+https://github.com/floooh/sokol-zig.git#aaf291ca2d3d1cedc05d65f5a1cacae0f53d934a", - .hash = "sokol-0.1.0-pb1HK4iDNgCom5dkY66eUBm_bYBHEl8KWFDAiqwWgpEy", - }, - .sokol_c = .{ - .url = "git+https://github.com/floooh/sokol.git#c66a1f04e6495d635c5e913335ab2308281e0492", - .hash = "N-V-__8AAC3eYABB1DVLb4dkcEzq_xVeEZZugVfQ6DoNQBDN", - }, - .fontstash_c = .{ - .url = "git+https://github.com/memononen/fontstash.git#b5ddc9741061343740d85d636d782ed3e07cf7be", - .hash = "N-V-__8AAA9xHgAxdLYPmlNTy6qzv9IYqiIePEHQUOPWYQ_6", - }, - .cimgui = .{ - .url = "git+https://github.com/floooh/dcimgui.git#33c99ef426b68030412b5a4b11487a23da9d4f13", - .hash = "cimgui-0.1.0-44ClkQRJlABdFMKRqIG8KDD6jy1eQbgPO335NziPYjmL", - .lazy = true, - }, - .tracy = .{ - .url = "git+https://github.com/sagehane/zig-tracy.git#80933723efe9bf840fe749b0bfc0d610f1db1669", - .hash = "zig_tracy-0.0.5-aOIqsX1tAACKaRRB-sraMLuNiMASXi_y-4FtRuw4cTpx", - }, .tiled = .{ .path = "./libs/tiled", }, - .stb = .{ - .path = "./libs/stb", + .engine = .{ + .path = "./engine/", }, - // .sdl = .{ - // .url = "git+https://github.com/allyourcodebase/SDL3.git#f85824b0db782b7d01c60aaad8bcb537892394e8", - // .hash = "sdl-0.0.0-i4QD0UuFqADRQysNyJ1OvCOZnq-clcVhq3BfPcBOf9zr", - // }, }, .paths = .{ "build.zig", diff --git a/engine/build.zig b/engine/build.zig new file mode 100644 index 0000000..b70a9fa --- /dev/null +++ b/engine/build.zig @@ -0,0 +1,385 @@ +const std = @import("std"); +const sokol = @import("sokol"); + +pub fn build(b: *std.Build) !void { + _ = b; // autofix +} + +const InitOptions = struct { + name: []const u8, + root_module: *std.Build.Module, + + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + + dep_engine: *std.Build.Dependency, + + has_imgui: ?bool = null, + has_tracy: ?bool = null, + win32_has_console: ?bool = null, + win32_png_icon: ?std.Build.LazyPath = null +}; + +pub fn init(outer_builder: *std.Build, opts: InitOptions) void { + const b = opts.dep_engine.builder; + + const engine_module = createEngineModule(b, .{ + .target = opts.target, + .optimize = opts.optimize, + .has_tracy = opts.has_tracy, + .has_imgui = opts.has_imgui + }); + + 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, + }); + opts.root_module.addImport("engine", engine_module.root_module); + + const mod_main = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = opts.target, + .optimize = opts.optimize, + .link_libc = true + }); + mod_main.addImport("engine", engine_module.root_module); + mod_main.linkLibrary(game_lib); + + var run_cmd_step: *std.Build.Step = undefined; + if (opts.target.result.cpu.arch.isWasm()) { + const build_step = buildWasm(b, .{ + .name = "index", + .root_module = mod_main, + .dep_sokol = engine_module.dep_sokol, + .outer_builder = outer_builder + }); + outer_builder.getInstallStep().dependOn(build_step); + + const dep_sokol = engine_module.dep_sokol; + const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{}); + const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk }); + emrun_step.step.dependOn(build_step); + run_cmd_step = &emrun_step.step; + + // TODO: Create a zip archive of all of the files. Would be useful for easier itch.io upload + } else { + const exe = buildNative(b, .{ + .name = opts.name, + .root_module = mod_main, + .win32_has_console = opts.win32_has_console, + .win32_png_icon = opts.win32_png_icon + }); + outer_builder.installArtifact(exe); + + { + const run_cmd = outer_builder.addRunArtifact(exe); + if (outer_builder.args) |args| { + run_cmd.addArgs(args); + } + run_cmd_step = &run_cmd.step; + } + } + + const run_step = outer_builder.step("run", "Run game"); + run_step.dependOn(run_cmd_step); +} + +const EngineModule = struct { + root_module: *std.Build.Module, + dep_sokol: *std.Build.Dependency, + + const Options = struct { + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + + has_imgui: ?bool = null, + has_tracy: ?bool = null, + }; +}; + +fn createEngineModule(b: *std.Build, opts: EngineModule.Options) EngineModule { + const has_imgui = opts.has_imgui orelse (opts.optimize == .Debug); + var has_tracy = opts.has_tracy orelse (opts.optimize == .Debug); + const isWasm = opts.target.result.cpu.arch.isWasm(); + + if (isWasm) { + has_tracy = false; + } + + const mod = b.createModule(.{ + .root_source_file = b.path("src/root.zig"), + .target = opts.target, + .optimize = opts.optimize, + .link_libc = true + }); + + const dep_sokol = b.dependency("sokol", .{ + .target = opts.target, + .optimize = opts.optimize, + .with_sokol_imgui = has_imgui, + }); + mod.linkLibrary(dep_sokol.artifact("sokol_clib")); + mod.addImport("sokol", dep_sokol.module("sokol")); + + if (has_imgui) { + if (b.lazyDependency("cimgui", .{ + .target = opts.target, + .optimize = opts.optimize, + })) |dep_cimgui| { + const cimgui = b.lazyImport(@This(), "cimgui").?; + const cimgui_conf = cimgui.getConfig(false); + mod.addImport("cimgui", dep_cimgui.module(cimgui_conf.module_name)); + dep_sokol.artifact("sokol_clib").addIncludePath(dep_cimgui.path(cimgui_conf.include_dir)); + } + } + + const dep_tracy = b.dependency("tracy", .{ + .target = opts.target, + .optimize = opts.optimize, + .tracy_enable = has_tracy, + .tracy_only_localhost = true + }); + if (has_tracy) { + mod.linkLibrary(dep_tracy.artifact("tracy")); + } + mod.addImport("tracy", dep_tracy.module("tracy")); + + const dep_stb = b.dependency("stb", .{}); + mod.addImport("stb_image", dep_stb.module("stb_image")); + mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis")); + + const dep_fontstash_c = b.dependency("fontstash_c", .{}); + mod.addIncludePath(dep_fontstash_c.path("src")); + + const dep_sokol_c = b.dependency("sokol_c", .{}); + { + var cflags_buffer: [64][]const u8 = undefined; + var cflags = std.ArrayListUnmanaged([]const u8).initBuffer(&cflags_buffer); + switch (sokol.resolveSokolBackend(.auto, opts.target.result)) { + .d3d11 => cflags.appendAssumeCapacity("-DSOKOL_D3D11"), + .metal => cflags.appendAssumeCapacity("-DSOKOL_METAL"), + .gl => cflags.appendAssumeCapacity("-DSOKOL_GLCORE"), + .gles3 => cflags.appendAssumeCapacity("-DSOKOL_GLES3"), + .wgpu => cflags.appendAssumeCapacity("-DSOKOL_WGPU"), + else => @panic("unknown sokol backend"), + } + + mod.addIncludePath(dep_sokol_c.path("util")); + mod.addCSourceFile(.{ + .file = b.path("src/fontstash/sokol_fontstash_impl.c"), + .flags = cflags.items + }); + } + + // TODO: + // const sdl = b.dependency("sdl", Run game.{ + // .optimize = optimize, + // .target = target, + // .linkage = .static, + // .default_target_config = !isWasm + // }); + // mod_main.linkLibrary(sdl.artifact("SDL3")); + // if (isWasm) { + // // TODO: Define buid config for wasm + // } + + var options = b.addOptions(); + options.addOption(bool, "has_imgui", has_imgui); + options.addOption(bool, "has_tracy", has_tracy); + mod.addOptions("build_options", options); + + return EngineModule{ + .root_module = mod, + .dep_sokol = dep_sokol + }; +} + +fn buildPngToIconTool( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) *std.Build.Step.Compile { + const mod = b.createModule(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("tools/png-to-icon.zig"), + }); + + const dep_stb = b.dependency("stb", .{}); + mod.addImport("stb_image", dep_stb.module("stb_image")); + + return b.addExecutable(.{ + .name = "png-to-icon", + .root_module = mod, + }); +} + +const BuildNativeOptions = struct { + name: []const u8, + root_module: *std.Build.Module, + win32_has_console: ?bool = null, + win32_png_icon: ?std.Build.LazyPath = null +}; + +fn buildNative(b: *std.Build, opts: BuildNativeOptions) *std.Build.Step.Compile { + const exe = b.addExecutable(.{ + .name = opts.name, + .root_module = opts.root_module + }); + + const target = opts.root_module.resolved_target.?; + if (target.result.os.tag == .windows) { + const optimize = opts.root_module.optimize.?; + const has_console = opts.win32_has_console orelse (optimize == .Debug); + exe.subsystem = if (has_console) .Console else .Windows; + + if (opts.win32_png_icon) |win32_png_icon| { + const png_to_icon_tool = buildPngToIconTool(b, b.graph.host, .ReleaseSafe); + + const png_to_icon_step = b.addRunArtifact(png_to_icon_tool); + png_to_icon_step.addFileArg(win32_png_icon); + const icon_file = png_to_icon_step.addOutputFileArg("icon.ico"); + + const add_icon_step = AddExecutableIcon.init(exe, icon_file); + exe.step.dependOn(&add_icon_step.step); + } + } + + return exe; +} + +const BuildWasmOptions = struct { + name: []const u8, + root_module: *std.Build.Module, + dep_sokol: *std.Build.Dependency, + outer_builder: *std.Build +}; + +fn buildWasm(b: *std.Build, opts: BuildWasmOptions) *std.Build.Step { + opts.root_module.sanitize_c = .off; + + // build the main file into a library, this is because the WASM 'exe' + // needs to be linked in a separate build step with the Emscripten linker + const main_lib = b.addLibrary(.{ + .name = opts.name, + .root_module = opts.root_module, + }); + + const dep_sokol = opts.dep_sokol; + const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{}); + + patchWasmIncludeDirs( + opts.root_module, + dep_emsdk.path("upstream/emscripten/cache/sysroot/include"), + &(dep_sokol.artifact("sokol_clib").step) + ); + + const link_step = sokol.emLinkStep(opts.outer_builder, .{ + .lib_main = main_lib, + .target = opts.root_module.resolved_target.?, + .optimize = opts.root_module.optimize.?, + .emsdk = dep_emsdk, + .use_webgl2 = true, + .use_emmalloc = true, + .use_filesystem = false, + .shell_file_path = b.path("src/shell.html"), + }) catch unreachable; + + return &link_step.step; +} + +fn patchWasmIncludeDirs( + module: *std.Build.Module, + path: std.Build.LazyPath, + depend_step: *std.Build.Step +) void { + if (module.link_libc != null and module.link_libc.?) { + // need to inject the Emscripten system header include path into + // the cimgui C library otherwise the C/C++ code won't find + // C stdlib headers + module.addSystemIncludePath(path); + } + + for (module.import_table.values()) |imported_module| { + patchWasmIncludeDirs(imported_module, path, depend_step); + } + + for (module.link_objects.items) |link_object| { + if (link_object != .other_step) { + continue; + } + const lib = link_object.other_step; + if (&lib.step == depend_step) { + continue; + } + + if (lib.root_module.link_libc != null and lib.root_module.link_libc.?) { + // need to inject the Emscripten system header include path into + // the cimgui C library otherwise the C/C++ code won't find + // C stdlib headers + lib.root_module.addSystemIncludePath(path); + + // all C libraries need to depend on the sokol library, when building for + // WASM this makes sure that the Emscripten SDK has been setup before + // C compilation is attempted (since the sokol C library depends on the + // Emscripten SDK setup step) + lib.step.dependOn(depend_step); + } + patchWasmIncludeDirs(lib.root_module, path, depend_step); + } +} + +const AddExecutableIcon = struct { + obj: *std.Build.Step.Compile, + step: std.Build.Step, + icon_file: std.Build.LazyPath, + resource_file: std.Build.LazyPath, + + fn init(obj: *std.Build.Step.Compile, icon_file: std.Build.LazyPath) *AddExecutableIcon { + const b = obj.step.owner; + const self = b.allocator.create(AddExecutableIcon) catch @panic("OOM"); + + self.obj = obj; + self.step = std.Build.Step.init(.{ + .id = .custom, + .name = "add executable icon", + .owner = b, + .makeFn = make + }); + + self.icon_file = icon_file; + icon_file.addStepDependencies(&self.step); + + const write_files = b.addWriteFiles(); + self.resource_file = write_files.add("resource-file.rc", ""); + self.step.dependOn(&write_files.step); + + self.obj.addWin32ResourceFile(.{ + .file = self.resource_file, + }); + + return self; + } + + fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { + const b = step.owner; + const self: *AddExecutableIcon = @fieldParentPtr("step", step); + + const resource_file = try std.fs.cwd().createFile(self.resource_file.getPath(b), .{ }); + defer resource_file.close(); + + const relative_icon_path = try std.fs.path.relative( + b.allocator, + self.resource_file.dirname().getPath(b), + self.icon_file.getPath(b) + ); + std.mem.replaceScalar(u8, relative_icon_path, '\\', '/'); + + try resource_file.writeAll("IDI_ICON ICON \""); + try resource_file.writeAll(relative_icon_path); + try resource_file.writeAll("\""); + } +}; diff --git a/engine/build.zig.zon b/engine/build.zig.zon new file mode 100644 index 0000000..1cf183b --- /dev/null +++ b/engine/build.zig.zon @@ -0,0 +1,42 @@ +.{ + .name = .engine, + .version = "0.0.0", + .fingerprint = 0xe8a81a8d6fb6f0fb, + .minimum_zig_version = "0.15.2", + .dependencies = .{ + .sokol = .{ + .url = "git+https://github.com/floooh/sokol-zig.git#aaf291ca2d3d1cedc05d65f5a1cacae0f53d934a", + .hash = "sokol-0.1.0-pb1HK4iDNgCom5dkY66eUBm_bYBHEl8KWFDAiqwWgpEy", + }, + .sokol_c = .{ + .url = "git+https://github.com/floooh/sokol.git#c66a1f04e6495d635c5e913335ab2308281e0492", + .hash = "N-V-__8AAC3eYABB1DVLb4dkcEzq_xVeEZZugVfQ6DoNQBDN", + }, + .fontstash_c = .{ + .url = "git+https://github.com/memononen/fontstash.git#b5ddc9741061343740d85d636d782ed3e07cf7be", + .hash = "N-V-__8AAA9xHgAxdLYPmlNTy6qzv9IYqiIePEHQUOPWYQ_6", + }, + .cimgui = .{ + .url = "git+https://github.com/floooh/dcimgui.git#33c99ef426b68030412b5a4b11487a23da9d4f13", + .hash = "cimgui-0.1.0-44ClkQRJlABdFMKRqIG8KDD6jy1eQbgPO335NziPYjmL", + .lazy = true, + }, + .tracy = .{ + .url = "git+https://github.com/sagehane/zig-tracy.git#80933723efe9bf840fe749b0bfc0d610f1db1669", + .hash = "zig_tracy-0.0.5-aOIqsX1tAACKaRRB-sraMLuNiMASXi_y-4FtRuw4cTpx", + }, + .stb = .{ + .path = "./libs/stb", + }, + // .sdl = .{ + // .url = "git+https://github.com/allyourcodebase/SDL3.git#f85824b0db782b7d01c60aaad8bcb537892394e8", + // .hash = "sdl-0.0.0-i4QD0UuFqADRQysNyJ1OvCOZnq-clcVhq3BfPcBOf9zr", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + "tools", + }, +} diff --git a/libs/stb/build.zig b/engine/libs/stb/build.zig similarity index 100% rename from libs/stb/build.zig rename to engine/libs/stb/build.zig diff --git a/libs/stb/build.zig.zon b/engine/libs/stb/build.zig.zon similarity index 100% rename from libs/stb/build.zig.zon rename to engine/libs/stb/build.zig.zon diff --git a/libs/stb/src/stb_image.zig b/engine/libs/stb/src/stb_image.zig similarity index 100% rename from libs/stb/src/stb_image.zig rename to engine/libs/stb/src/stb_image.zig diff --git a/libs/stb/src/stb_image_impl.c b/engine/libs/stb/src/stb_image_impl.c similarity index 100% rename from libs/stb/src/stb_image_impl.c rename to engine/libs/stb/src/stb_image_impl.c diff --git a/libs/stb/src/stb_vorbis.zig b/engine/libs/stb/src/stb_vorbis.zig similarity index 100% rename from libs/stb/src/stb_vorbis.zig rename to engine/libs/stb/src/stb_vorbis.zig diff --git a/libs/stb/src/stb_vorbis_impl.c b/engine/libs/stb/src/stb_vorbis_impl.c similarity index 100% rename from libs/stb/src/stb_vorbis_impl.c rename to engine/libs/stb/src/stb_vorbis_impl.c diff --git a/src/engine/audio/data.zig b/engine/src/audio/data.zig similarity index 100% rename from src/engine/audio/data.zig rename to engine/src/audio/data.zig diff --git a/src/engine/audio/mixer.zig b/engine/src/audio/mixer.zig similarity index 100% rename from src/engine/audio/mixer.zig rename to engine/src/audio/mixer.zig diff --git a/src/engine/audio/root.zig b/engine/src/audio/root.zig similarity index 100% rename from src/engine/audio/root.zig rename to engine/src/audio/root.zig diff --git a/src/engine/audio/store.zig b/engine/src/audio/store.zig similarity index 100% rename from src/engine/audio/store.zig rename to engine/src/audio/store.zig diff --git a/src/engine/fontstash/context.zig b/engine/src/fontstash/context.zig similarity index 100% rename from src/engine/fontstash/context.zig rename to engine/src/fontstash/context.zig diff --git a/src/engine/fontstash/font.zig b/engine/src/fontstash/font.zig similarity index 100% rename from src/engine/fontstash/font.zig rename to engine/src/fontstash/font.zig diff --git a/src/engine/fontstash/root.zig b/engine/src/fontstash/root.zig similarity index 100% rename from src/engine/fontstash/root.zig rename to engine/src/fontstash/root.zig diff --git a/src/engine/fontstash/sokol_fontstash_impl.c b/engine/src/fontstash/sokol_fontstash_impl.c similarity index 100% rename from src/engine/fontstash/sokol_fontstash_impl.c rename to engine/src/fontstash/sokol_fontstash_impl.c diff --git a/src/engine/frame.zig b/engine/src/frame.zig similarity index 100% rename from src/engine/frame.zig rename to engine/src/frame.zig diff --git a/src/engine/graphics.zig b/engine/src/graphics.zig similarity index 78% rename from src/engine/graphics.zig rename to engine/src/graphics.zig index 2ea0894..95a5439 100644 --- a/src/engine/graphics.zig +++ b/engine/src/graphics.zig @@ -23,6 +23,8 @@ pub const Font = fontstash.Font; const GraphicsFrame = @import("./frame.zig").Graphics; +const Graphics = @This(); + // TODO: Seems that there is a vertical jitter bug when resizing a window in OpenGL. Seems like a driver bug. // From other peoples research it seems that disabling vsync when a resize event occurs fixes it. // Maybe a patch for sokol could be made? @@ -97,20 +99,16 @@ pub const Sprite = struct { uv: Rect, }; -var gpa: std.mem.Allocator = undefined; +main_pipeline: sgl.Pipeline, +linear_sampler: sg.Sampler, +nearest_sampler: sg.Sampler, +font_context: fontstash.Context, +textures: std.ArrayList(Texture) = .empty, -var main_pipeline: sgl.Pipeline = .{}; -var linear_sampler: sg.Sampler = .{}; -var nearest_sampler: sg.Sampler = .{}; -var font_context: fontstash.Context = undefined; -var textures: std.ArrayList(Texture) = .empty; - -var scale_stack_buffer: [32]Vec2 = undefined; -var scale_stack: std.ArrayList(Vec2) = .empty; - -pub fn init(options: Options) !void { - gpa = options.allocator; +scale_stack_buffer: [32]Vec2 = undefined, +scale_stack: std.ArrayList(Vec2) = .empty, +pub fn init(self: *Graphics, options: Options) !void { sg.setup(.{ .logger = options.logger, .environment = sglue.environment(), @@ -123,7 +121,7 @@ pub fn init(options: Options) !void { } }); - main_pipeline = sgl.makePipeline(.{ + const main_pipeline = sgl.makePipeline(.{ .colors = init: { var colors: [8]sg.ColorTargetState = @splat(.{}); colors[0] = .{ @@ -158,14 +156,14 @@ pub fn init(options: Options) !void { imgui.addFont(imgui_font.ttf_data, imgui_font.size); } - linear_sampler = sg.makeSampler(.{ + const linear_sampler = sg.makeSampler(.{ .min_filter = .LINEAR, .mag_filter = .LINEAR, .mipmap_filter = .LINEAR, .label = "linear-sampler", }); - nearest_sampler = sg.makeSampler(.{ + const nearest_sampler = sg.makeSampler(.{ .min_filter = .NEAREST, .mag_filter = .NEAREST, .mipmap_filter = .NEAREST, @@ -175,31 +173,38 @@ pub fn init(options: Options) !void { const dpi_scale = sapp.dpiScale(); const atlas_size = 512; const atlas_dim = std.math.ceilPowerOfTwoAssert(u32, @intFromFloat(atlas_size * dpi_scale)); - font_context = try fontstash.Context.init(.{ + const font_context = try fontstash.Context.init(.{ .width = @intCast(atlas_dim), .height = @intCast(atlas_dim), }); + + self.* = Graphics{ + .main_pipeline = main_pipeline, + .linear_sampler = linear_sampler, + .nearest_sampler = nearest_sampler, + .font_context = font_context, + }; } -pub fn deinit() void { - textures.deinit(gpa); +pub fn deinit(self: *Graphics, gpa: std.mem.Allocator) void { + self.textures.deinit(gpa); imgui.shutdown(); - font_context.deinit(); + self.font_context.deinit(); sgl.shutdown(); sg.shutdown(); } -pub fn drawCommand(command: Command) void { +pub fn drawCommand(self: *Graphics, command: Command) void { switch(command) { .push_transformation => |opts| { - pushTransform(opts.translation, opts.scale); + self.pushTransform(opts.translation, opts.scale); // font_resolution_scale = font_resolution_scale.multiply(opts.scale); }, .pop_transformation => { - popTransform(); + self.popTransform(); }, .draw_rectangle => |opts| { - drawRectangle(opts); + self.drawRectangle(opts); }, .set_scissor => |opts| { sgl.scissorRectf( @@ -219,25 +224,25 @@ pub fn drawCommand(command: Command) void { ); }, .draw_text => |opts| { - const font_resolution_scale = scale_stack.getLast(); + const font_resolution_scale = self.scale_stack.getLast(); sgl.pushMatrix(); defer sgl.popMatrix(); sgl.scale(1/font_resolution_scale.x, 1/font_resolution_scale.y, 1); - font_context.setFont(opts.font); - font_context.setSize(opts.size * font_resolution_scale.y); - font_context.setAlign(.{ .x = .left, .y = .top }); - font_context.setSpacing(0); + self.font_context.setFont(opts.font); + self.font_context.setSize(opts.size * font_resolution_scale.y); + self.font_context.setAlign(.{ .x = .left, .y = .top }); + self.font_context.setSpacing(0); const r: u8 = @intFromFloat(opts.color.x * 255); const g: u8 = @intFromFloat(opts.color.y * 255); const b: u8 = @intFromFloat(opts.color.z * 255); const a: u8 = @intFromFloat(opts.color.w * 255); const color: u32 = r | (@as(u32, g) << 8) | (@as(u32, b) << 16) | (@as(u32, a) << 24); - font_context.setColor(color); - font_context.drawText( + self.font_context.setColor(color); + self.font_context.drawText( opts.pos.x * font_resolution_scale.x, opts.pos.y * font_resolution_scale.y, opts.text @@ -246,13 +251,13 @@ pub fn drawCommand(command: Command) void { } } -pub fn drawCommands(commands: []const Command) void { +pub fn drawCommands(self: *Graphics, commands: []const Command) void { for (commands) |command| { - drawCommand(command); + self.drawCommand(command); } } -pub fn beginFrame() void { +pub fn beginFrame(self: *Graphics) void { const zone = tracy.initZone(@src(), .{ }); defer zone.deinit(); @@ -263,17 +268,17 @@ pub fn beginFrame() void { .dpi_scale = sapp.dpiScale() }); - scale_stack = .initBuffer(&scale_stack_buffer); - scale_stack.appendAssumeCapacity(.init(1, 1)); + self.scale_stack = .initBuffer(&self.scale_stack_buffer); + self.scale_stack.appendAssumeCapacity(.init(1, 1)); - font_context.clearState(); + self.font_context.clearState(); sgl.defaults(); sgl.matrixModeProjection(); sgl.ortho(0, sapp.widthf(), sapp.heightf(), 0, -1, 1); - sgl.loadPipeline(main_pipeline); + sgl.loadPipeline(self.main_pipeline); } -pub fn endFrame(clear_color: Vec4) void { +pub fn endFrame(self: *Graphics, clear_color: Vec4) void { const zone = tracy.initZone(@src(), .{ }); defer zone.deinit(); @@ -289,7 +294,7 @@ pub fn endFrame(clear_color: Vec4) void { } }; - font_context.flush(); + self.font_context.flush(); { sg.beginPass(.{ @@ -305,17 +310,17 @@ pub fn endFrame(clear_color: Vec4) void { sg.commit(); } -fn pushTransform(translation: Vec2, scale: Vec2) void { +fn pushTransform(self: *Graphics, translation: Vec2, scale: Vec2) void { sgl.pushMatrix(); sgl.translate(translation.x, translation.y, 0); sgl.scale(scale.x, scale.y, 1); - scale_stack.appendAssumeCapacity(scale_stack.getLast().multiply(scale)); + self.scale_stack.appendAssumeCapacity(self.scale_stack.getLast().multiply(scale)); } -fn popTransform() void { +fn popTransform(self: *Graphics) void { sgl.popMatrix(); - _ = scale_stack.pop().?; + _ = self.scale_stack.pop().?; } const Vertex = struct { @@ -323,13 +328,13 @@ const Vertex = struct { uv: Vec2 }; -fn drawQuad(quad: [4]Vertex, color: Vec4, texture_id: TextureId) void { +fn drawQuad(self: *Graphics, quad: [4]Vertex, color: Vec4, texture_id: TextureId) void { sgl.enableTexture(); defer sgl.disableTexture(); - const view = textures.items[@intFromEnum(texture_id)].view; + const view = self.textures.items[@intFromEnum(texture_id)].view; // TODO: Make sampler configurable - sgl.texture(view, nearest_sampler); + sgl.texture(view, self.nearest_sampler); sgl.beginQuads(); defer sgl.end(); @@ -352,7 +357,7 @@ fn drawQuadNoUVs(quad: [4]Vec2, color: Vec4) void { } } -fn drawRectangle(opts: Command.DrawRectangle) void { +fn drawRectangle(self: *Graphics, opts: Command.DrawRectangle) void { const pos = opts.rect.pos; const size = opts.rect.size; @@ -381,7 +386,7 @@ fn drawRectangle(opts: Command.DrawRectangle) void { .uv = .init(uv.left(), uv.bottom()) } }; - drawQuad(quad, opts.color, sprite.texture); + self.drawQuad(quad, opts.color, sprite.texture); } else { const quad = .{ pos.add(top_left), @@ -407,8 +412,8 @@ fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void { ); } -pub fn addFont(name: [*c]const u8, data: []const u8) !Font.Id { - return try font_context.addFont(name, data); +fn addFont(self: *Graphics, name: [*c]const u8, data: []const u8) !Font.Id { + return try self.font_context.addFont(name, data); } fn makeView(image: sg.Image) !sg.View { @@ -453,7 +458,7 @@ fn makeImageWithMipMaps(mipmaps: []const Texture.Data) !sg.Image { return image; } -pub fn addTexture(mipmaps: []const Texture.Data) !TextureId { +pub fn addTexture(self: *Graphics, gpa: std.mem.Allocator, mipmaps: []const Texture.Data) !TextureId { const image = try makeImageWithMipMaps(mipmaps); errdefer sg.deallocImage(image); @@ -461,8 +466,8 @@ pub fn addTexture(mipmaps: []const Texture.Data) !TextureId { errdefer sg.deallocView(view); assert(mipmaps.len > 0); - const index = textures.items.len; - try textures.append(gpa, .{ + const index = self.textures.items.len; + try self.textures.append(gpa, .{ .image = image, .view = view, .info = .{ @@ -474,7 +479,7 @@ pub fn addTexture(mipmaps: []const Texture.Data) !TextureId { return @enumFromInt(index); } -pub fn getTextureInfo(id: TextureId) TextureInfo { - const texture = textures.items[@intFromEnum(id)]; +pub fn getTextureInfo(self: *Graphics, id: TextureId) TextureInfo { + const texture = self.textures.items[@intFromEnum(id)]; return texture.info; } diff --git a/src/engine/imgui.zig b/engine/src/imgui.zig similarity index 100% rename from src/engine/imgui.zig rename to engine/src/imgui.zig diff --git a/src/engine/input.zig b/engine/src/input.zig similarity index 100% rename from src/engine/input.zig rename to engine/src/input.zig diff --git a/src/main.zig b/engine/src/main.zig similarity index 65% rename from src/main.zig rename to engine/src/main.zig index 9a6aadc..6cf0834 100644 --- a/src/main.zig +++ b/engine/src/main.zig @@ -1,4 +1,4 @@ -const Engine = @import("./engine/root.zig"); +const Engine = @import("engine"); var engine: Engine = undefined; diff --git a/src/engine/math.zig b/engine/src/math.zig similarity index 100% rename from src/engine/math.zig rename to engine/src/math.zig diff --git a/src/engine/root.zig b/engine/src/root.zig similarity index 82% rename from src/engine/root.zig rename to engine/src/root.zig index 36b9e3e..314ecbe 100644 --- a/src/engine/root.zig +++ b/engine/src/root.zig @@ -23,20 +23,34 @@ const STBImage = @import("stb_image"); const Gfx = Graphics; -const Game = @import("../game.zig"); -const Assets = @import("../assets.zig"); +const GameCallbacks = struct { + init: *const fn(opts: *const Engine.InitOptions) callconv(.c) void, + tick: *const fn(frame: *Frame) callconv(.c) void, + deinit: *const fn() callconv(.c) void, + debug: *const fn() callconv(.c) void, +}; + +extern fn init(opts: *const Engine.InitOptions) void; +extern fn tick(frame: *Frame) void; +extern fn deinit() void; +extern fn debug() void; const Engine = @This(); pub const Nanoseconds = u64; +pub const InitOptions = struct { + gpa: std.mem.Allocator, + seed: u64 +}; + allocator: std.mem.Allocator, started_at: std.time.Instant, mouse_inside: bool, last_frame_at: Nanoseconds, +graphics: Graphics, +game_callbacks: GameCallbacks, -game: Game, -assets: Assets, frame: Frame, canvas_size: ?Vec2 = null, @@ -53,9 +67,14 @@ pub fn run(self: *Engine, opts: RunOptions) !void { .started_at = std.time.Instant.now() catch @panic("Instant.now() unsupported"), .mouse_inside = false, .last_frame_at = 0, - .assets = undefined, - .game = undefined, - .frame = undefined + .frame = undefined, + .graphics = undefined, + .game_callbacks = .{ + .init = init, + .deinit = deinit, + .tick = tick, + .debug = debug, + } }; var debug_allocator: std.heap.DebugAllocator(.{}) = .init; @@ -84,19 +103,20 @@ pub fn run(self: *Engine, opts: RunOptions) !void { } // TODO: Don't hard code icon path, allow changing through options - var icon_data = try STBImage.load(@embedFile("../assets/icon.png")); - defer icon_data.deinit(); + // var icon_data = try STBImage.load(@embedFile("../assets/icon.png")); + // defer icon_data.deinit(); var icon: sapp.IconDesc = .{}; - icon.sokol_default = false; - icon.images[0] = .{ - .width = @intCast(icon_data.width), - .height = @intCast(icon_data.height), - .pixels = .{ - .ptr = icon_data.rgba8_pixels, - .size = icon_data.width * icon_data.height * 4 - } - }; + icon.sokol_default = true; + // TODO: + // icon.images[0] = .{ + // .width = @intCast(icon_data.width), + // .height = @intCast(icon_data.height), + // .pixels = .{ + // .ptr = icon_data.rgba8_pixels, + // .size = icon_data.width * icon_data.height * 4 + // } + // }; sapp.run(.{ .init_userdata_cb = sokolInitCallback, @@ -119,12 +139,15 @@ fn sokolInit(self: *Engine) !void { const zone = tracy.initZone(@src(), .{ }); defer zone.deinit(); - try Gfx.init(.{ + log.debug("Init", .{}); + + try self.graphics.init(.{ .allocator = self.allocator, .logger = .{ .func = sokolLogCallback }, - .imgui_font = .{ - .ttf_data = @embedFile("../assets/roboto-font/Roboto-Regular.ttf"), - } + // TODO: + // .imgui_font = .{ + // .ttf_data = @embedFile("../assets/roboto-font/Roboto-Regular.ttf"), + // } }); try Audio.init(.{ @@ -134,8 +157,11 @@ fn sokolInit(self: *Engine) !void { const seed: u64 = @bitCast(std.time.milliTimestamp()); - self.assets = try Assets.init(self.allocator); - self.game = try Game.init(self.allocator, seed, &self.assets); + const opts = InitOptions{ + .gpa = self.allocator, + .seed = seed, + }; + self.game_callbacks.init(&opts); } fn sokolCleanup(self: *Engine) void { @@ -144,9 +170,8 @@ fn sokolCleanup(self: *Engine) void { self.frame.deinit(); Audio.deinit(); - self.game.deinit(); - self.assets.deinit(self.allocator); - Gfx.deinit(); + self.game_callbacks.deinit(); + self.graphics.deinit(self.allocator); } fn sokolFrame(self: *Engine) !void { @@ -193,7 +218,7 @@ fn sokolFrame(self: *Engine) !void { frame.dt_ns = time_passed - self.last_frame_at; frame.hide_cursor = false; - try self.game.tick(&self.frame); + self.game_callbacks.tick(&self.frame); frame.input.keyboard.pressed = .initEmpty(); frame.input.keyboard.released = .initEmpty(); @@ -220,14 +245,13 @@ fn sokolFrame(self: *Engine) !void { self.canvas_size = self.frame.graphics.canvas_size; { - Gfx.beginFrame(); - defer Gfx.endFrame(frame.graphics.clear_color); + self.graphics.beginFrame(); + defer self.graphics.endFrame(frame.graphics.clear_color); - Gfx.drawCommands(frame.graphics.commands.items); + self.graphics.drawCommands(frame.graphics.commands.items); if (frame.show_debug) { - try self.game.debug(); - try showDebugWindow(&self.frame); + try self.showDebugWindow(&self.frame); } } @@ -240,23 +264,34 @@ fn sokolFrame(self: *Engine) !void { } } -fn showDebugWindow(frame: *Frame) !void { +fn showDebugWindow(self: *Engine, frame: *Frame) !void { if (!imgui.beginWindow(.{ - .name = "Engine", - .pos = Vec2.init(240, 20), + .name = "Debug", + .pos = Vec2.init(20, 20), .size = Vec2.init(200, 200), })) { return; } defer imgui.endWindow(); - imgui.textFmt("Draw commands: {}\n", .{ - frame.graphics.commands.items.len, - }); - imgui.textFmt("Audio instances: {}/{}\n", .{ - Audio.mixer.instances.items.len, - Audio.mixer.instances.capacity - }); + if (imgui.beginTabBar("debug")) { + defer imgui.endTabBar(); + + if (imgui.beginTabItem("Game")) { + defer imgui.endTabItem(); + self.game_callbacks.debug(); + } + if (imgui.beginTabItem("Engine")) { + defer imgui.endTabItem(); + imgui.textFmt("Draw commands: {}\n", .{ + frame.graphics.commands.items.len, + }); + imgui.textFmt("Audio instances: {}/{}\n", .{ + Audio.mixer.instances.items.len, + Audio.mixer.instances.capacity + }); + } + } } fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool { diff --git a/src/engine/screen_scaler.zig b/engine/src/screen_scaler.zig similarity index 100% rename from src/engine/screen_scaler.zig rename to engine/src/screen_scaler.zig diff --git a/src/engine/shell.html b/engine/src/shell.html similarity index 100% rename from src/engine/shell.html rename to engine/src/shell.html diff --git a/tools/png-to-icon.zig b/engine/tools/png-to-icon.zig similarity index 100% rename from tools/png-to-icon.zig rename to engine/tools/png-to-icon.zig diff --git a/src/assets.zig b/src/assets.zig deleted file mode 100644 index 8ea3e31..0000000 --- a/src/assets.zig +++ /dev/null @@ -1,44 +0,0 @@ -const std = @import("std"); - -const Math = @import("./engine/math.zig"); -const Engine = @import("./engine/root.zig"); -const Gfx = Engine.Graphics; -const Audio = Engine.Audio; - -const Assets = @This(); - -const FontName = enum { - regular, - bold, - italic, - - const EnumArray = std.EnumArray(FontName, Gfx.Font.Id); -}; - -font_id: FontName.EnumArray, - -wood01: Audio.Data.Id, - -pub fn init(gpa: std.mem.Allocator) !Assets { - _ = gpa; // autofix - const font_id_array: FontName.EnumArray = .init(.{ - .regular = try Gfx.addFont("regular", @embedFile("assets/roboto-font/Roboto-Regular.ttf")), - .bold = try Gfx.addFont("bold", @embedFile("assets/roboto-font/Roboto-Bold.ttf")), - .italic = try Gfx.addFont("italic", @embedFile("assets/roboto-font/Roboto-Italic.ttf")), - }); - - const wood01 = try Audio.load(.{ - .format = .vorbis, - .data = @embedFile("assets/wood01.ogg"), - }); - - return Assets{ - .font_id = font_id_array, - .wood01 = wood01 - }; -} - -pub fn deinit(self: *Assets, gpa: std.mem.Allocator) void { - _ = self; // autofix - _ = gpa; // autofix -} diff --git a/src/game.zig b/src/game.zig index 4d47a66..9a6f136 100644 --- a/src/game.zig +++ b/src/game.zig @@ -2,9 +2,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const Assets = @import("./assets.zig"); - -const Engine = @import("./engine/root.zig"); +const Engine = @import("engine"); const imgui = Engine.imgui; const Vec2 = Engine.Vec2; const rgb = Engine.Math.rgb; @@ -13,25 +11,55 @@ const Audio = Engine.Audio; const Game = @This(); -gpa: Allocator, -assets: *Assets, +const FontName = enum { + regular, + bold, + italic, -player: Vec2, + const EnumArray = std.EnumArray(FontName, Gfx.Font.Id); +}; -pub fn init(gpa: Allocator, seed: u64, assets: *Assets) !Game { - _ = seed; // autofix - return Game{ +const State = struct { + gpa: Allocator, + player: Vec2, + + // font_id: FontName.EnumArray, + // wood01: Audio.Data.Id, +}; + +var g_state: *State = undefined; + +pub export fn init(opts: *const Engine.InitOptions) void { + const gpa = opts.gpa; + + // const font_id_array: FontName.EnumArray = .init(.{ + // .regular = try Gfx.addFont("regular", @embedFile("assets/roboto-font/Roboto-Regular.ttf")), + // .bold = try Gfx.addFont("bold", @embedFile("assets/roboto-font/Roboto-Bold.ttf")), + // .italic = try Gfx.addFont("italic", @embedFile("assets/roboto-font/Roboto-Italic.ttf")), + // }); + + // const wood01 = try Audio.load(.{ + // .format = .vorbis, + // .data = @embedFile("assets/wood01.ogg"), + // }); + + g_state = gpa.create(State) catch @panic("OOM"); + g_state.* = State{ .gpa = gpa, - .assets = assets, - .player = .init(50, 50) + .player = .init(50, 50), + // .font_id = font_id_array, + // .wood01 = wood01 }; } -pub fn deinit(self: *Game) void { - _ = self; // autofix +pub export fn deinit() void { + const gpa = g_state.gpa; + + gpa.destroy(g_state); + g_state = undefined; } -pub fn tick(self: *Game, frame: *Engine.Frame) !void { +pub export fn tick(frame: *Engine.Frame) void { const dt = frame.deltaTime(); const canvas_size = Vec2.init(100, 100); @@ -57,15 +85,13 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void { dir = dir.normalized(); if (dir.x != 0 or dir.y != 0) { - frame.playAudio(.{ - .id = self.assets.wood01, - .volume = 0.1 - }); + // frame.playAudio(.{ + // .id = g_state.wood01, + // .volume = 0.1 + // }); } - self.player = self.player.add(dir.multiplyScalar(50 * dt)); - - const regular_font = self.assets.font_id.get(.regular); + g_state.player = g_state.player.add(dir.multiplyScalar(50 * dt)); frame.drawRectangle(.{ .rect = .{ .pos = .init(0, 0), .size = canvas_size }, @@ -74,35 +100,22 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void { const size = Vec2.init(20, 20); frame.drawRectangle(.{ .rect = .{ - .pos = self.player.sub(size.divideScalar(2)), + .pos = g_state.player.sub(size.divideScalar(2)), .size = size }, .color = rgb(200, 20, 20) }); if (dir.x != 0 or dir.y != 0) { - frame.drawRectanglOutline(self.player.sub(size.divideScalar(2)), size, rgb(20, 200, 20), 3); + frame.drawRectanglOutline(g_state.player.sub(size.divideScalar(2)), size, rgb(20, 200, 20), 3); } - frame.drawText(self.player, "Player", .{ - .font = regular_font, - .size = 10 - }); + // const regular_font = g_state.font_id.get(.regular); + // frame.drawText(g_state.player, "Player", .{ + // .font = regular_font, + // .size = 10 + // }); } -pub fn debug(self: *Game) !void { - _ = self; // autofix - if (!imgui.beginWindow(.{ - .name = "Debug", - .pos = Vec2.init(20, 20), - .size = Vec2.init(400, 200), - })) { - return; - } - defer imgui.endWindow(); - +pub export fn debug() void { imgui.text("Hello World!\n"); - imgui.textFmt("Audio: {}/{}\n", .{ - Audio.mixer.instances.items.len, - Audio.mixer.instances.capacity - }); }