pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "main"); const GameContext = @This(); allocator: std.mem.Allocator, commandQueue: core.RingQueue(Task), activeCommand: ?*sys.SubprocessTask = null, showInstructions: bool = false, dockingInitialized: bool = false, animationStore: *AnimationStore = undefined, 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, .commandQueue = try core.RingQueue(Task).init(self.allocator, 4096), }; return self; } pub fn prepare(self: *@This()) !void { core.engine_log("program ready", .{}); if (core.getEngineObject(imgui.utils.TopBar)) |topbar| { topbar.menuOpen = true; } self.animationStore = try AnimationStore.create(self.allocator); } pub fn tick(self: *@This(), dt: f64) void { _ = dt; std.time.sleep(10 * 1000 * 1000); self.tickTasks() catch {}; 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.end(); } // Initialize docking layout on first run if (!self.dockingInitialized) { // Get the main viewport dockspace ID (created by dockSpaceOverViewport) const main_dockspace_id = ig.getID_Str("##DockSpaceViewport_11111111"); // Dock our toolbox window to the center of the main dockspace ig.dockBuilderDockWindow("Toolbox Window", main_dockspace_id); ig.dockBuilderFinish(main_dockspace_id); self.dockingInitialized = true; } // Create a docked window if (ig.begin("Toolbox Window", null, .{})) { ig.textf("This is a docked toolbox window", .{}); if (ig.button("Compile Shaders", .{})) { start_CompileShaders() catch {}; } if (ig.button("Launch TrenchBroom", .{})) { start_TrenchBroom() catch {}; } ig.separator(); ig.textf("Additional toolbox content can go here", .{}); if (self.activeCommand != null or self.commandQueue.count() > 0) { ig.textf("outstanding tasks: {d}", .{self.commandQueue.count() + 1}); if (self.activeCommand) |cmd| { ig.textf("current command: ", .{}); for (cmd.args) |arg| { ig.textf("{s}", .{arg}); ig.sameLine(0, 4); } } } } ig.end(); } fn findRootDir(allocator: std.mem.Allocator) ![]u8 { var current_dir = std.fs.cwd(); // Start from current directory and walk up var path_components = std.ArrayList([]const u8).init(allocator); defer path_components.deinit(); // Try current directory first current_dir.access("content.txt", .{}) catch { // content.txt not found, need to walk up var temp_dir = current_dir; while (true) { // Try to go up one directory const parent_dir = temp_dir.openDir("..", .{}) catch return error.RootNotFound; temp_dir = parent_dir; // Check if content.txt exists in parent directory temp_dir.access("content.txt", .{}) catch { continue; }; // Found content.txt, get the absolute path return try temp_dir.realpathAlloc(allocator, "."); } }; // content.txt found in current directory return try current_dir.realpathAlloc(allocator, "."); } fn start_CompileShaders() !void { const self = core.getEngineObject(@This()) orelse return; const root_dir = findRootDir(self.allocator) catch { core.engine_log("Could not find BacklogEngine root directory (content.txt not found)", .{}); return; }; defer self.allocator.free(root_dir); core.engine_log("Found root directory: {s}", .{root_dir}); const compile_shaders_dir = try std.fs.path.join(self.allocator, &.{ root_dir, "tools", "compileShaders" }); defer self.allocator.free(compile_shaders_dir); var compile_shaders_name = core.MakeName(compile_shaders_dir); core.engine_log("CompileShaders directory: {s}", .{compile_shaders_dir}); const exe_path = try std.fs.path.join(self.allocator, &.{ compile_shaders_dir, "zig-out", "bin", "compileShaders.exe" }); defer self.allocator.free(exe_path); core.engine_log("Looking for executable at: {s}", .{exe_path}); // Check if compileShaders.exe exists if (std.fs.cwd().access(exe_path, .{})) |_| { // File exists, run it directly core.engine_log("CompileShaders executable found, running directly", .{}); try self.commandQueue.pushLocked(.{ .subprocess = try sys.runCommand( self.allocator, &.{"zig-out/bin/compileShaders.exe"}, compile_shaders_name.utf8(), ) }); } else |_| { // File doesn't exist, build it first core.engine_log("CompileShaders executable not found, building first", .{}); try self.commandQueue.pushLocked(.{ .subprocess = try sys.runCommand( self.allocator, &.{ "zig", "build", "install" }, compile_shaders_name.utf8(), ) }); // Then run it core.engine_log("Queuing compileShaders execution after build", .{}); try self.commandQueue.pushLocked(.{ .subprocess = try sys.runCommand( self.allocator, &.{"zig-out/bin/compileShaders.exe"}, compile_shaders_name.utf8(), ) }); } } pub fn launchDetachedProcess(allocator: std.mem.Allocator, argv: []const []const u8, cwd: ?[]const u8) !void { var child = std.process.Child.init(argv, allocator); child.cwd = cwd; child.stdin_behavior = .Ignore; child.stdout_behavior = .Ignore; child.stderr_behavior = .Ignore; try child.spawn(); } fn start_TrenchBroom() !void { const self = core.getEngineObject(@This()) orelse return; const root_dir = findRootDir(self.allocator) catch { core.engine_log("Could not find BacklogEngine root directory (content.txt not found)", .{}); return; }; defer self.allocator.free(root_dir); const trenchbroom_dir = try std.fs.path.join(self.allocator, &.{ root_dir, "tools", "trenchbroom" }); defer self.allocator.free(trenchbroom_dir); var trenchbroom_name = core.MakeName(trenchbroom_dir); core.engine_log("Launching TrenchBroom from: {s}", .{trenchbroom_dir}); try launchDetachedProcess( self.allocator, &.{"TrenchBroom.exe"}, trenchbroom_name.utf8(), ); } 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; } } } 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.alloc(u8, 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; } pub fn deinit(self: *@This()) void { self.animationStore.destroy(); self.allocator.destroy(self); } fn copyRecursiveDir( allocator: std.mem.Allocator, src_dir: *std.fs.Dir, dest_dir: *std.fs.Dir, ) !void { var walker = try src_dir.walk(allocator); defer walker.deinit(); while (try walker.next()) |entry| { switch (entry.kind) { .file => { try entry.dir.copyFile(entry.basename, dest_dir.*, entry.path, .{}); }, .directory => { try dest_dir.makeDir(entry.path); }, else => {}, } } } pub fn main() anyerror!void { var spec = try api.getSpec("New Project Dialogue"); try spec.put("useGPA", .{ .boolean = false }); try spec.put("configName", .{ .string = "toolbox" }); _ = api.startEngine(&NeonObjectTable, &spec); } const AnimationStore = @import("toolbox/animationStore.zig"); const builtin = @import("builtin"); const std = @import("std"); const api = @import("backlog"); const imgui = api.imgui; const ig = api.imgui.api; const core = api.core; const sys = api.sys;