pub const AddProgramOptions = struct { name: []const u8, opt: bh.Options, root_source_file: std.Build.LazyPath, }; // 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 const GameModule = struct { name: []const u8, enabled: bool = true, }; pub const Program = struct { name: []const u8, opts: bh.Options, // root_path: not used yet, we will create a second module that dynamically loads in to that root_path gameModules: std.ArrayListUnmanaged(GameModule) = .{}, gameModulesMap: std.StringHashMapUnmanaged(bool) = .{}, buildSystem: *backlog.BuildSystem, iconPath: ?std.Build.LazyPath = null, allocator: std.mem.Allocator, // just set this to the build allocator pub fn setModuleEnabled(self: *@This(), name: []const u8, enabled: bool) void { const r = self.gameModulesMap.getOrPut(self.allocator, name) catch unreachable; r.value_ptr.* = enabled; } fn finalizeModules(self: *@This()) void { self.gameModules.clearRetainingCapacity(); for (moduleOrder) |mod| { var enabled: bool = false; for (defaultEnabledModules) |default| { if (std.mem.eql(u8, default, mod)) { enabled = true; } } if (self.gameModulesMap.get(mod)) |value| { enabled = value; } if (enabled) { self.gameModules.append(self.allocator, .{ .name = mod, .enabled = enabled }) catch unreachable; } } } pub fn setIconPath(self: *@This(), path: []const u8) void { if (self.iconPath != null) { return; } self.iconPath = self.buildSystem.generateRc(self.name, path) catch return; } pub fn makeSpec(self: *@This()) *std.Build.Module { const b = self.buildSystem.b; const run = b.addRunArtifact(self.buildSystem.specGen); const pfile = run.addOutputFileArg(b.fmt("{s}-spec.zig", .{self.name})); run.addArg(self.name); for (self.gameModules.items) |mod| { if (mod.enabled) run.addArg(mod.name); } const mod = b.createModule(.{ .target = self.opts.target, .optimize = self.opts.optimize, .root_source_file = pfile, }); const core = self.buildSystem.nwdep.module("core"); mod.addImport("core", core); return mod; } pub fn makeGameApi(self: *@This()) *std.Build.Module { const b = self.buildSystem.b; const apigen = self.buildSystem.nwdep.artifact("backlog-generate-modapi"); const run = b.addRunArtifact(apigen); const pfile = run.addOutputFileArg(b.fmt("{s}-api.zig", .{self.name})); run.addArg(self.name); for (self.gameModules.items) |mod| { if (mod.enabled) { run.addArg(mod.name); } } const api = b.createModule(.{ .target = self.opts.target, .optimize = self.opts.optimize, .root_source_file = pfile, }); for (self.gameModules.items) |mod| { if (mod.enabled) { api.addImport(mod.name, self.buildSystem.nwdep.module(mod.name)); } } return api; } pub fn makeGameModule(self: *@This()) *std.Build.Step.Compile { const b = self.buildSystem.b; const bEngine = self.buildSystem.bEngine; const opts = self.opts; // 1. generate the main launch codeset. const lib = b.addLibrary(.{ .name = b.fmt("{s}-mod", .{self.name}), .linkage = if (opts.static_build) .static else .dynamic, .root_module = b.createModule(.{ .target = opts.target, .optimize = opts.optimize, .root_source_file = bEngine.path("engine/modulelaunch.zig"), }), }); if (opts.static_build) { // b.installArtifact(lib); } else { const installExtern = b.addInstallArtifact(lib, .{ .dest_dir = .{ .override = .{ .custom = "modules" } }, }); b.getInstallStep().dependOn(&installExtern.step); } return lib; } pub fn generateStaticShaders(self: *@This()) *std.Build.Module { const loadStatics = self.buildSystem.generateInstallStaticResources(self.name, "content/_shaders") catch unreachable; return loadStatics; } // spec is linked in both the base trampoline pub fn compileInstall(self: *@This()) *std.Build.Step.Compile { const bEngine = self.buildSystem.bEngine; const b = self.buildSystem.b; const core = self.buildSystem.nwdep.module("core"); self.finalizeModules(); // 1. create the loader const exe = bEngine.addExecutable(.{ .name = self.name, .root_module = bEngine.createModule(.{ .target = self.opts.target, .optimize = self.opts.optimize, .root_source_file = bEngine.path("engine/main2.zig"), }), }); // link core const spec = self.makeSpec(); exe.root_module.addImport("gamespec", spec); exe.root_module.addImport("core", core); exe.root_module.addOptions("BacklogOptions", self.buildSystem.options); if (!self.opts.static_build) { const platform = self.buildSystem.nwdep.module("platform"); exe.root_module.addImport("platform", platform); } if (self.opts.static_build) { exe.root_module.addImport().addImport("staticShaderLoader", self.generateStaticShaders()); } const gameModule = self.makeGameModule(); const gameApi = self.makeGameApi(); gameModule.root_module.addImport("backlog", gameApi); gameModule.root_module.addImport("gamespec", spec); if (self.opts.static_build) { exe.root_module.addImport("backlog", gameApi); } // self.buildSystem.b.installArtifact(exe); const installed = self.buildSystem.b.addInstallArtifact(exe, .{}); { const run = b.addSystemCommand(&.{b.fmt("zig-out/bin/{s}", .{self.name})}); run.step.dependOn(&installed.step); if (b.args) |args| { run.addArgs(args); } const run_exe = b.step(b.fmt("run-{s}", .{self.name}), "runs the program"); run_exe.dependOn(&run.step); } return exe; } }; const std = @import("std"); const bh = @import("bh"); const backlog = @import("../build.zig");