pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "main"); const GameContext = @This(); allocator: std.mem.Allocator, textBuffer: [8192]u8, commandQueue: core.RingQueue(Task), activeCommand: ?*sys.SubprocessTask = null, targetName: ?[]u8 = null, targetDir: ?[]u8 = null, engineRelative: ?[]u8 = null, fingerprint: ?[]const u8 = null, showInstructions: bool = false, const Task = union(enum(u8)) { subprocess: *sys.SubprocessTask, func: *const fn (*GameContext) void, }; pub fn init(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); self.* = .{ .allocator = allocator, .textBuffer = std.mem.zeroes([8192]u8), .commandQueue = try core.RingQueue(Task).init(self.allocator, 4096), .targetName = try allocator.dupe(u8, "sample"), .engineRelative = try allocator.dupe(u8, "Backlog"), }; return self; } pub fn prepare(self: *@This()) !void { _ = self; core.engine_log("program ready", .{}); } pub fn tick(self: *@This(), dt: f64) void { _ = dt; ig.setNextWindowPos(.{ .x = 200, .y = 200 }, .{}, .{}); if (ig.begin("New Project...", null, .{ .always_auto_resize = true, .no_move = true, .no_resize = true, .no_collapse = true, })) { ig.setNextItemWidth(600); if (ig.inputText( "##Path", &self.textBuffer, self.textBuffer.len, .{ .no_blank = true }, null, null, )) { core.engine_log("huh", .{}); } ig.sameLine(0, 3); if (ig.button("...", .{})) { core.engine_log("opening NFD", .{}); core.asyncOpenFolder(.{ .callback = onFolderSelected, .callbackContext = self, }) catch @panic(" unable to open dialog"); } if (ig.button("Create", .{})) { const asSlice = std.mem.span(@as([*c]const u8, @ptrCast(&self.textBuffer))); core.engine_log("creating project: {s}", .{self.textBuffer}); self.createProject(asSlice) catch {}; } } ig.end(); if (self.activeCommand != null or self.commandQueue.count() > 0) { ig.setNextWindowPos(.{ .x = 20, .y = 20 }, .{}, .{}); if (ig.begin("tasks", null, .{ .always_auto_resize = true, .no_title_bar = true, .no_move = true, .no_resize = true, .no_collapse = true, })) { ig.textf("outstanding tasks: {d}", .{self.commandQueue.count() + 1}); } ig.end(); } if (self.showInstructions) { ig.setNextWindowPos(.{ .x = 20, .y = 20 }, .{}, .{}); ig.setNextWindowSize(.{ .x = 800, .y = 500 }, .{}); if (ig.begin("Instructions", null, .{ .always_auto_resize = true, .no_move = true, .no_resize = true, .no_collapse = true, })) { ig.textf("project generated and git repo cloned, run zig build install and you might need to update the fingerprint", .{}); if (ig.button("close..", .{})) { self.showInstructions = false; } } ig.end(); } self.tickTasks() catch {}; } fn tickTasks(self: *@This()) !void { if (self.activeCommand == null) { if (self.commandQueue.popFromLocked()) |task| { switch (task) { .subprocess => |t| { self.activeCommand = t; core.engine_log("starting task {any}", .{t.args}); try t.run(); }, .func => |f| { f(self); }, } } } if (self.activeCommand) |active| { if (active.checkComplete()) { active.destroy(); core.engine_logs("task complete"); self.activeCommand = null; } } } pub fn createProject(self: *@This(), targetDir: []const u8) !void { self.targetDir = try self.allocator.dupe(u8, targetDir); try self.commandQueue.pushLocked(.{ .subprocess = try sys.runCommand( self.allocator, &.{ "zig", "init" }, targetDir, ) }); try self.commandQueue.pushLocked(.{ .func = updateBuildZig }); try self.commandQueue.pushLocked(.{ .subprocess = try sys.runCommand( self.allocator, &.{ "git", "clone", "git@github.com:peterino2/BacklogEngine.git", "Backlog" }, targetDir, ) }); try self.commandQueue.pushLocked(.{ .func = displayInstructions }); } fn loadFileAllocFromCwd(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) ![]u8 { var file = try dir.openFile(path, .{}); defer file.close(); const filesize = (try file.stat()).size + 1; // add null byte const buffer: []u8 = try allocator.alignedAlloc(u8, 8, filesize); errdefer allocator.free(buffer); try file.reader().readNoEof(buffer[0 .. buffer.len - 1]); buffer[buffer.len - 1] = 0; return buffer; } fn displayInstructions(self: *@This()) void { self.showInstructions = true; } fn updateBuildZig(self: *@This()) void { core.engine_logs("updating build.zig.zon"); var workingDir = std.fs.cwd().openDir(self.targetDir.?, .{}) catch return; defer workingDir.close(); const buildZigZonPath = std.fmt.allocPrint(self.allocator, "{s}/build.zig.zon", .{self.targetDir.?}) catch return; defer self.allocator.free(buildZigZonPath); const buildZigContents = loadFileAllocFromCwd(self.allocator, workingDir, buildZigZonPath) catch return; defer self.allocator.free(buildZigContents); // walk forward until we find the fingerprint value var window: []const u8 = buildZigContents[0..]; var offset: usize = 0; while (offset < buildZigContents.len) : (offset += 1) { window = buildZigContents[offset..buildZigContents.len]; if (std.mem.startsWith(u8, window, ".fingerprint")) { // walk forward until i find the comma var windowEnd: usize = offset; while (windowEnd < buildZigContents.len) : (windowEnd += 1) { if (buildZigContents[windowEnd] == ',') { self.updateFingerPrint(buildZigContents[offset..windowEnd]); self.writeBuildZigFiles() catch {}; return; } } } } core.engine_logs("unable to update window"); } fn writeBuildZigFiles(self: *@This()) !void { // copy over the entire templates directory. // 1. make a content folder // 2. create src/main.zig // 3. build.zig // 4. build.zig.zon var workingDir = try std.fs.cwd().openDir(self.targetDir.?, .{}); defer workingDir.close(); core.engine_logs("deliting files..."); try workingDir.deleteFile("src/root.zig"); try workingDir.deleteFile("src/main.zig"); try workingDir.deleteFile("build.zig"); try workingDir.deleteFile("build.zig.zon"); // 1. write src/main.zig try self.writeMainZig(&workingDir); try self.writeBuildZig(&workingDir); try self.writeBuildZigZon(&workingDir); } fn writeBuildZig(self: *@This(), workingDir: *std.fs.Dir) !void { const out_path = "build.zig"; var content = std.ArrayList(u8).init(self.allocator); defer content.deinit(); var writer = content.writer(); const template = \\ const std = @import("std"); \\ const Backlog = @import("Backlog"); \\ \\ pub fn build(b: *std.Build) void {{ \\ const target = b.standardTargetOptions(.{{}}); \\ const optimize = b.standardOptimizeOption(.{{}}); \\ \\ var blbuild = Backlog.init(b, .{{ \\ .target = target, \\ .optimize = optimize, \\ .backlogRoot = "{s}", \\ }}); ; try writer.print(template, .{self.engineRelative.?}); const template2 = \\ const program = blbuild.program(.{{ \\ .name = "{s}", \\ .desc = "simple program built with backlog", \\ .root_source_file = b.path("src/main.zig"), \\ }}); \\ \\ program.setModuleEnabled("imgui", true); \\ \\ _ = program.compileInstall(); \\ }} ; try writer.print(template2, .{self.targetName.?}); try writer.writeByte(0); try self.writeZigFile(workingDir, out_path, content.items); } const entries: []const struct { name: []const u8, path: []const u8 } = &.{ .{ .name = "SpirvReflect", .path = "lib/spirv-reflect-zig" }, .{ .name = "zphysics", .path = "lib/zphysics" }, .{ .name = "spng", .path = "lib/spng" }, .{ .name = "ozz", .path = "lib/ozz" }, .{ .name = "sdl3", .path = "lib/sdl3" }, .{ .name = "miniaudio", .path = "lib/miniaudio" }, .{ .name = "lua", .path = "lib/lua" }, }; fn writeBuildZigZon(self: *@This(), workingDir: *std.fs.Dir) !void { var content = std.ArrayList(u8).init(self.allocator); defer content.deinit(); var writer = content.writer(); try writer.print(".{{\n", .{}); try writer.print( \\ .name = .MyProjects, \\ .version = "0.0.0", \\ .dependencies = .{{ \\ .Backlog = .{{ .path = "{s}" }}, , .{self.engineRelative.?}); for (entries) |entry| { try writer.print(" .{s} = .{{ .path = \"{s}/{s}\" }},\n", .{ entry.name, self.engineRelative.?, entry.path }); } try writer.print( \\ }}, \\ , .{}); try writer.print( \\ .paths = .{{ "" }}, \\ , .{}); try writer.print( \\ .fingerprint = {s}, \\ , .{self.fingerprint.?}); try writer.print("}}", .{}); const file = try workingDir.createFile("build.zig.zon", .{}); defer file.close(); try file.writeAll(content.items); } fn writeMainZig(self: *@This(), workingDir: *std.fs.Dir) !void { var content = std.ArrayList(u8).init(self.allocator); defer content.deinit(); const template = \\ pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "main"); \\ \\ allocator: std.mem.Allocator, \\ \\ pub fn init(allocator: std.mem.Allocator) !*@This() { \\ const self = try allocator.create(@This()); \\ self.* = .{ \\ .allocator = allocator, \\ }; \\ return self; \\ } \\ \\ pub fn prepare(self: *@This()) !void { \\ _ = self; \\ core.engine_log("program ready", .{}); \\ } \\ \\ pub fn tick(self: *@This(), dt: f64) void { \\ _ = self; \\ _ = dt; \\ ig.showDemoWindow(null); \\ } \\ \\ pub fn deinit(self: *@This()) void { \\ self.allocator.destroy(self); \\ } \\ \\ pub fn main() anyerror!void { \\ var spec = try api.getSpec(" ; const template2 = \\"); \\ _ = api.startEngine(&NeonObjectTable, &spec); \\ } \\ \\ const std = @import("std"); \\ const api = @import("backlog"); \\ const ig = api.imgui.api; \\ const core = api.core; \\ ; try content.appendSlice(template); try content.appendSlice(self.targetName.?); try content.appendSlice(template2); try content.append(0); const out_path = "src/main.zig"; std.debug.print("{s}", .{content.items}); try self.writeZigFile(workingDir, out_path, content.items); } fn writeZigFile(self: *@This(), workingDir: *std.fs.Dir, out_path: []const u8, contents: []const u8) !void { var ast = try std.zig.Ast.parse(self.allocator, @as([:0]const u8, @ptrCast(contents[0 .. contents.len - 1])), .zig); defer ast.deinit(self.allocator); const out = try ast.render(self.allocator); const file = try workingDir.createFile(out_path, .{}); defer file.close(); try file.writeAll(out); } fn updateFingerPrint(self: *@This(), zonEntry: []const u8) void { // find the first number var left: usize = 0; while (zonEntry[left] != '0') : (left += 1) {} core.engine_log(">> FINGERPRINT '{s}'", .{zonEntry[left .. zonEntry.len - 1]}); self.fingerprint = zonEntry[left .. zonEntry.len - 1]; } pub fn onFolderSelected( ctx: ?*anyopaque, path: ?[]const u8, ) void { const self: *@This() = @ptrCast(@alignCast(ctx)); if (path) |p| { std.mem.copyForwards(u8, &self.textBuffer, p); self.textBuffer[p.len] = 0; } } pub fn deinit(self: *@This()) void { if (self.targetDir) |t| { self.allocator.free(t); } self.allocator.destroy(self); } pub fn main() anyerror!void { var spec = try api.getSpec("New Project Dialogue"); _ = api.startEngine(&NeonObjectTable, &spec); } const std = @import("std"); const api = @import("backlog"); const ig = api.imgui.api; const core = api.core; const sys = api.sys;