// MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 -- use this if you're getting that odd crash on mac b: *std.Build, nw_builder: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.Mode, nw_mod: *std.Build.Module, spirvReflect: SpirvReflect.SpirvGenerator2, gltf2ozz: ozz.GltfToOzz, options: *std.Build.Step.Options, cookShaders: bool, backlogRoot: []const u8, // list of all shaders discovered under // content/_shaders/def reflectShaderPathList: [][]u8 = undefined, staticBuild: bool = false, nwdep: *std.Build.Dependency, apigen: *std.Build.Step.Compile, const engineDepList = [_][]const u8{ "assets", "audio", "core", "papyrus", "platform", "rend", "imgui", "physics", "ui", }; const BuildSystem = @This(); const std = @import("std"); const Build = std.Build; const LazyPath = Build.LazyPath; const SpirvReflect = @import("SpirvReflect"); const ozz = @import("ozz"); pub const InitOptions = struct { import_name: []const u8 = "Backlog", backlogRoot: []const u8 = "./BacklogEngine", staticBuild: bool = false, target: Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, }; pub fn init(b: *std.Build, opts: InitOptions) BuildSystem { var buildOpts = declareBuildOptions(b); if (opts.target.result.os.tag == .linux) { // using a static build for linux... object loading hell is not fun std.debug.print("Important! only supporting static builds for linux", .{}); buildOpts.static_build = true; } const nwdep = b.dependency(opts.import_name, .{ .target = opts.target, .optimize = opts.optimize, .static_build = buildOpts.static_build, }); var self = BuildSystem{ .b = b, .nw_builder = nwdep.builder, .target = opts.target, .optimize = opts.optimize, .nw_mod = nwdep.module("Backlog"), .backlogRoot = opts.backlogRoot, .spirvReflect = SpirvReflect.SpirvGenerator2.init(nwdep.builder, .{}), .options = createGameOptions(b, buildOpts), .gltf2ozz = ozz.GltfToOzz.init(nwdep.builder, .{}), .cookShaders = b.option(bool, "cookShaders", "generates shaders and updates .json files before running the build. (needs to be done whenever shaders are updated, this just runs tools/scripts/cook-shaders.py)") orelse false, .staticBuild = buildOpts.static_build, .nwdep = nwdep, .apigen = nwdep.artifact("backlog-apigen"), }; const exeList = [2]*std.Build.Step.Compile{ self.gltf2ozz.exe, self.spirvReflect.reflect }; const install_tools = b.step("tools", "installs tools needed to generate outputs for the engine"); for (exeList) |exe| { const toolsInstall = b.addInstallArtifact(exe, .{ .dest_dir = .{ .override = .{ .custom = "tools" } }, }); install_tools.dependOn(&toolsInstall.step); } { const runArtifact = b.addRunArtifact(self.gltf2ozz.exe); if (b.args) |args| { runArtifact.addArgs(args); } const run_exe = b.step("gltf2ozz", "runs the gltf animation converter."); run_exe.dependOn(&runArtifact.step); } { const runArtifact = b.addRunArtifact(self.spirvReflect.reflect); if (b.args) |args| { runArtifact.addArgs(args); } const run_exe = b.step("spv-reflect", "runs the gltf animation converter."); run_exe.dependOn(&runArtifact.step); } self.addDependencyInstalls(self.b, .ReleaseFast); return self; } pub const AddProgramOptions = struct { name: []const u8, desc: []const u8, root_source_file: LazyPath, imports: []const Build.Module.Import = &.{}, }; pub fn addProgram(self: *BuildSystem, opts: AddProgramOptions) *std.Build.Module { const b = self.b; const exe = self.nw_builder.addExecutable(.{ .name = opts.name, .target = self.target, .optimize = self.optimize, .root_source_file = self.nw_builder.path("engine/main.zig"), }); b.installArtifact(exe); const runArtifact = b.addRunArtifact(exe); if (b.args) |args| { runArtifact.addArgs(args); } const run_exe = b.step(self.b.fmt("run-{s}", .{opts.name}), opts.desc); run_exe.dependOn(&runArtifact.step); // main path = name/main.zig const mod = b.addModule(opts.name, .{ .target = self.target, .optimize = self.optimize, .root_source_file = opts.root_source_file, .imports = opts.imports, }); exe.root_module.addImport("main", mod); exe.root_module.addImport("Backlog", self.nw_mod); mod.addImport("Backlog", self.nw_mod); exe.root_module.addOptions("BacklogOptions", self.options); if (self.cookShaders) { const cookShadersScript = b.fmt("{s}/tools/scripts/cookShaders.py", .{self.backlogRoot}); const cookShadersCommand = b.addSystemCommand(&[_][]const u8{"python"}); cookShadersCommand.addArg(cookShadersScript); run_exe.dependOn(&cookShadersCommand.step); } b.getInstallStep().dependOn(self.nw_builder.getInstallStep()); // run_exe.dependOn(b.getInstallStep()); return mod; } pub const BuildOptions = struct { mutex_job_queue: bool, static_build: bool, zero_logging: bool, slow_logging: bool, force_mailbox: bool, }; pub fn declareBuildOptions(b: *std.Build) BuildOptions { return .{ .mutex_job_queue = b.option(bool, "mutex_job_queue", "temporary test, reverts to old mutex based queue behaviour in jobs.zig:JobManager") orelse false, .static_build = b.option(bool, "static_build", "builds the entire game as a single executable") orelse false, .zero_logging = b.option(bool, "zero_logging", "disables all logging, only intended for use on job dispatch testing") orelse false, .slow_logging = b.option(bool, "slow_logging", "Disables buffered logging, takes a hit to performance but gain timing information on logging") orelse false, .force_mailbox = b.option(bool, "force_mailbox", "forces mailbox mode for present mode. unlocks framerate to irresponsible levels") orelse false, }; } pub fn createGameOptions(b: *std.Build, options: BuildOptions) *std.Build.Step.Options { const opts = b.addOptions(); inline for (@typeInfo(BuildOptions).@"struct".fields) |field| { opts.addOption(bool, field.name, @field(options, field.name)); } // build options for core.zig // opts.addOption( // bool, // "mutex_job_queue", // ); // opts.addOption( // bool, // "static_build", // ); // opts.addOption( // bool, // "zero_logging", // ); // opts.addOption( // bool, // "slow_logging", // ); // opts.addOption( // bool, // "force_mailbox", // ); return opts; } pub fn addExtraModule(self: *BuildSystem, mod: *std.Build.Module, moduleName: []const u8) void { const dep = self.b.dependency(moduleName, .{ .target = self.target, .optimize = self.optimize, .static_build = self.staticBuild }); mod.addImport(moduleName, dep.module(moduleName)); } pub fn addDependencyInstalls(self: *BuildSystem, b: *std.Build, optimize: std.builtin.OptimizeMode) void { if (self.staticBuild) { return; } inline for (DynamicDepList) |d| { b.installArtifact(b.dependency( d.dep, .{ .target = self.target, .optimize = optimize, .static_build = self.staticBuild }, ).artifact(d.artifact)); } // b.installArtifact(b.dependency( // "sdl3", // .{ .target = self.target, .optimize = optimize, .static_build = self.staticBuild }, // ).artifact("SDL3")); } const DynamicDepList: []const struct { dep: []const u8, artifact: []const u8 } = &.{ .{ .dep = "sdl3", .artifact = "SDL3" }, .{ .dep = "spng", .artifact = "spng_c" }, .{ .dep = "lua", .artifact = "luac" }, .{ .dep = "miniaudio", .artifact = "miniaudio_c" }, .{ .dep = "zphysics", .artifact = "joltc" }, .{ .dep = "ozz", .artifact = "ozz_cpp" }, }; // all other modules are disabled by default pub const defaultEnabledModules: []const []const u8 = &.{ "core", "assets", "platform", "rend", }; pub const moduleOrder: []const []const u8 = &.{ "core", "assets", "platform", "physics", "audio", "rend", "imgui", "papyrus", "ui", }; pub const GameModule = struct { name: []const u8, enabled: bool = true, }; pub const Program = struct { allocator: std.mem.Allocator, opts: AddProgramOptions, gameModules: std.ArrayListUnmanaged(GameModule) = .{}, buildSystem: *BuildSystem, pub fn setModuleEnabled(self: *@This(), module: []const u8, enable: bool) void { for (self.gameModules.items) |*m| { if (std.mem.eql(u8, m.name, module)) { m.enabled = enable; return; } } // module not found, add it here. self.gameModules.append(self.allocator, .{ .name = module, .enabled = enable }) catch unreachable; } pub fn compileInstall(self: *@This()) *std.Build.Module { const exe = self.buildSystem.addProgram(self.opts); var modlist = std.ArrayList([]const u8).init(self.buildSystem.b.allocator); for (self.gameModules.items) |mod| { if (mod.enabled) { modlist.append(mod.name) catch unreachable; } } exe.addImport("backlog", self.buildSystem.generateApi(self.opts.name, modlist.items)); return exe; } }; pub fn program(self: *BuildSystem, opts: AddProgramOptions) *Program { const prog = self.b.allocator.create(Program) catch unreachable; prog.* = .{ .allocator = self.b.allocator, .opts = opts, .buildSystem = self, }; for (moduleOrder) |module| { prog.setModuleEnabled(module, false); } for (defaultEnabledModules) |module| { prog.setModuleEnabled(module, true); } return prog; } // ========= standalone build instance ======= // maybe the default should be like an engine launcher/project launcher or something pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false; const spirvDep = b.dependency("SpirvReflect", .{ .target = target, .optimize = optimize, .static_build = static_build, }); _ = spirvDep; const fwdGeneratorExe = b.addExecutable(.{ .name = "backlog-generate-fwd", .target = b.graph.host, .optimize = .Debug, .root_source_file = b.path("build/generateFwd.zig"), }); const generateExe = b.addExecutable(.{ .name = "backlog-apigen", .target = b.graph.host, .optimize = .Debug, .root_source_file = b.path("build/generateApi.zig"), }); b.installArtifact(generateExe); { const mod = b.addModule("Backlog", .{ .target = target, .optimize = optimize, .root_source_file = b.path("engine/backlog.zig"), }); for (engineDepList) |depName| { const dep = b.dependency( depName, .{ .target = target, .optimize = optimize, .static_build = static_build, }, ); const run = b.addRunArtifact(fwdGeneratorExe); const output = run.addOutputFileArg(b.fmt("{s}_fwd.zig", .{depName})); const modFwd = b.addModule(depName, .{ .target = target, .optimize = optimize, .root_source_file = output, }); mod.addImport(b.fmt("{s}", .{depName}), modFwd); modFwd.addImport("module", dep.module(depName)); // mod.addImport(depName, dep.module(depName)); } // link in large platform support functions... special case. { const dep = b.dependency("sdl3", .{ .target = target, .optimize = optimize, .static_build = static_build, }); const lib = dep.module("SDL3"); mod.addImport("sdl3_fwd", lib); } } } pub fn generateApi(self: *@This(), programName: []const u8, moduleList: []const []const u8) *std.Build.Module { const run = self.b.addRunArtifact(self.apigen); const pfile = self.b.fmt("{s}Api.zig", .{programName}); const output = run.addOutputFileArg(pfile); for (moduleList) |modName| { run.addArg(modName); } const mod = self.b.addModule(pfile, .{ .target = self.target, .optimize = self.optimize, .root_source_file = output, }); for (moduleList) |modName| { mod.addImport(modName, self.nwdep.module(modName)); } return mod; }