// MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 -- use this if you're getting that odd crash on mac b: *std.Build, bEngine: *std.Build, // used to resolve executable paths from within the engine's root directory opts: bh.Options, nw_mod: *std.Build.Module, 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, nwdep: *std.Build.Dependency, // apigen: *std.Build.Step.Compile, shaderEmbedGen: *std.Build.Step.Compile, loadDynamicsGen: *std.Build.Step.Compile, rcGen: *std.Build.Step.Compile, specGen: *std.Build.Step.Compile, generatedApis: std.StringHashMapUnmanaged(*std.Build.Module) = .{}, pub const BuildSystem = @This(); const std = @import("std"); const Build = std.Build; const LazyPath = Build.LazyPath; const ozz = @import("ozz"); pub const InitOptions = struct { import_name: []const u8 = "Backlog", backlogRoot: []const u8 = "./BacklogEngine", target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, }; pub fn init(b: *std.Build, initOptions: InitOptions) BuildSystem { const buildOpts = declareBuildOptions(b); var opts = bh.Options{ .target = initOptions.target, .optimize = initOptions.optimize, .static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false, .tracy = b.option(bool, "tracy", "builds with tracy support") orelse false, }; if (opts.target.result.os.tag == .linux) { // using a static build for linux... object loading hell is not fun... // i have skill issues in that realm right now std.debug.print("Important! only supporting static builds for linux", .{}); opts.static_build = true; } const nwdep = b.dependency(initOptions.import_name, .{ .target = opts.target, .optimize = opts.optimize, .static_build = opts.static_build, }); var self = BuildSystem{ .b = b, .bEngine = nwdep.builder, .opts = opts, .nw_mod = nwdep.module("Backlog"), .backlogRoot = initOptions.backlogRoot, .options = createGameOptions(b, buildOpts, opts), .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, .nwdep = nwdep, // .apigen = nwdep.artifact("backlog-apigen"), .loadDynamicsGen = nwdep.artifact("backlog-generate-loadDynamics"), .specGen = nwdep.artifact("backlog-generate-spec"), .rcGen = nwdep.artifact("backlog-generate-rc"), .shaderEmbedGen = nwdep.artifact("backlog-shaderEmbedGen"), }; const exeList = [1]*std.Build.Step.Compile{self.gltf2ozz.exe}; 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); } self.addDependencyInstalls(self.b, .ReleaseFast); return self; } // Build Options for the engine, // should generally be written as false = default // true = enabling something const BuildOptions = struct { mutex_job_queue: bool, zero_logging: bool, slow_logging: bool, force_mailbox: bool, }; 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, .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, }; } fn createGameOptions(b: *std.Build, options: BuildOptions, bhopts: bh.Options) *std.Build.Step.Options { const opts = b.addOptions(); inline for (@typeInfo(BuildOptions).@"struct".fields) |field| { opts.addOption(bool, field.name, @field(options, field.name)); } // forward the options to the build options opts.addOption(bool, "tracy", bhopts.tracy); opts.addOption(bool, "static_build", bhopts.static_build); return opts; } pub fn addExtraModule(self: *BuildSystem, mod: *std.Build.Module, moduleName: []const u8) void { const dep = self.b.dependency(moduleName, .{ .target = self.opts.target, .optimize = self.opts.optimize, .static_build = self.opts.static_build }); mod.addImport(moduleName, dep.module(moduleName)); } fn addDependencyInstalls(self: *BuildSystem, b: *std.Build, optimize: std.builtin.OptimizeMode) void { if (self.opts.static_build) { return; } inline for (DynamicDepList) |d| { b.installArtifact(b.dependency( d.dep, .{ .target = self.opts.target, .optimize = optimize, .static_build = self.opts.static_build }, ).artifact(d.artifact)); } } 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 = "enet", .artifact = "enet_c" }, .{ .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", "sys", "assets", "platform", "net", "physics", "audio", "rend", "ui", "imgui", "papyrus", }; pub fn addProgram( self: *@This(), name: []const u8, ) *Program { const p = self.b.allocator.create(Program) catch unreachable; p.* = .{ .name = name, .opts = self.opts, .buildSystem = self, .allocator = self.b.allocator, }; return p; } pub fn generateRc(self: *@This(), programName: []const u8, iconPath: []const u8) !std.Build.LazyPath { const run = self.b.addRunArtifact(self.rcGen); const pfile = self.b.fmt("{s}.rc", .{programName}); const output = run.addOutputFileArg(pfile); run.addArg(iconPath); return output; } pub fn generateInstallStaticResources(self: *@This(), programName: []const u8, shadersPath: []const u8) !*std.Build.Module { const run = self.b.addRunArtifact(self.shaderEmbedGen); const pfile = self.b.fmt("{s}ShaderGen.zig", .{programName}); const output = run.addOutputFileArg(pfile); run.addArg(shadersPath); const b = self.b; const mod = self.b.addModule(pfile, .{ .target = self.opts.target, .optimize = self.opts.optimize, .root_source_file = output, }); mod.addImport("core", self.nwdep.module("core")); var dir = try std.fs.cwd().openDir("content/_shaders", .{ .iterate = true }); defer dir.close(); var walker = dir.iterate(); while (try walker.next()) |shaderType| { if (shaderType.kind == .directory) { var d2 = try dir.openDir(shaderType.name, .{ .iterate = true }); defer d2.close(); var w2 = d2.iterate(); while (try w2.next()) |shaderName| { const shaderPath = b.fmt("content/_shaders/{s}/{s}", .{ shaderType.name, shaderName.name }); mod.addAnonymousImport(shaderName.name, .{ .root_source_file = b.path(shaderPath), }); } } } return mod; } // ========= standalone build instance ======= // maybe the default should be like an engine launcher/project launcher or something pub fn build(b: *std.Build) void { const opts = bh.declareOptions(b); engineDeps.buildGenerators(b, opts); } const bh = @import("bh"); const engineDeps = @import("build/engineDeps.zig"); const program = @import("build/program.zig"); const Program = program.Program;