pub const std = @import("std"); pub const core = @import("core"); fn __debugPrint(comptime fmt: []const u8, args: anytype) void { std.debug.print(fmt ++ "\n", args); } fn noprint(comptime fmt: []const u8, args: anytype) void { _ = fmt; _ = args; } const debugPrint = __debugPrint; pub const SubprocessTask = struct { // any and all string values added to this subprocesstask must exist and be usable across threads. // i reeccomend converting strings to a name via core.MakeName before passing them over. if they are freed // by the time the thread uses it, it will crash . mutex: std.Thread.Mutex = .{}, allocator: std.mem.Allocator, child: ?std.process.Child = null, completed: bool = false, success: bool = false, workingDir: ?[]const u8 = null, stdout: std.ArrayListUnmanaged(u8) = .{}, stderr: std.ArrayListUnmanaged(u8) = .{}, argsOwned: std.ArrayListUnmanaged([]const u8) = .{}, argsArena: std.heap.ArenaAllocator, pub fn run(task: *@This()) !void { const L = struct { self: *SubprocessTask, pub fn func(ctx: @This(), _: *core.JobContext) void { const self = ctx.self; self.mutex.lock(); self.stderr = .{}; self.stdout = .{}; core.engine_log("running task", .{}); core.engine_log("cwd = {?s}", .{self.workingDir}); self.child = std.process.Child.init(self.argsOwned.items, self.allocator); self.child.?.stdout_behavior = .Inherit; self.child.?.stderr_behavior = .Inherit; self.child.?.cwd = self.workingDir; _ = self.child.?.spawn() catch unreachable; self.mutex.unlock(); // close and cleanup everything self.waitInner() catch {}; } }; try core.dispatchJob(L{ .self = task }); } pub fn create(allocator: std.mem.Allocator, argv: []const []const u8) !*@This() { const self = try allocator.create(@This()); self.* = .{ .child = null, .allocator = allocator, .argsArena = std.heap.ArenaAllocator.init(allocator), }; for (argv) |a| { const v = try self.argsArena.allocator().dupe(u8, a); try self.argsOwned.append(self.allocator, v); } return self; } pub fn destroy(self: *@This()) void { self.mutex.lock(); self.argsArena.deinit(); self.argsOwned.deinit(self.allocator); if (self.child) |*child| { _ = child; core.engine_log("destroying child process", .{}); } self.mutex.unlock(); self.stdout.deinit(self.allocator); self.stderr.deinit(self.allocator); self.allocator.destroy(self); } pub fn checkComplete(self: *@This()) bool { return self.completed; } pub fn wait(self: *@This()) void { var completed: bool = self.completed; while (completed == false) { std.Thread.sleep(1 * 1000 * 1000); self.mutex.lock(); completed = self.completed; self.mutex.unlock(); } debugPrint("task completed", .{}); } pub fn waitInner(self: *@This()) !void { self.mutex.lock(); if (self.child.?.stdout_behavior == .Pipe) { try self.child.?.collectOutput(self.allocator, &self.stdout, &self.stderr, 1 * 1024 * 1024); } debugPrint("task completed stdout: {s}", .{self.stdout.items}); const term = try self.child.?.wait(); debugPrint("wait completed", .{}); self.mutex.unlock(); switch (term) { .Exited => |m| { if (m == 0) self.success = true; }, .Signal => |m| { core.engine_log("process Signaled {d}", .{m}); }, .Stopped => |m| { core.engine_log("process Stopped {d}", .{m}); }, .Unknown => |m| { core.engine_log("process Unknown {d}", .{m}); }, } self.mutex.lock(); self.child = null; self.completed = true; self.mutex.unlock(); } pub fn runCommand(allocator: std.mem.Allocator, argv: []const []const u8, cwd: ?[]const u8) !*@This() { const task = try @This().create(allocator, argv); task.workingDir = cwd; try task.run(); return task; } }; 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){}; defer path_components.deinit(allocator); // 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, "."); } pub fn start_CompileShaders(context: ?*anyopaque, callback: ?*const fn (?*anyopaque) void) !void { const self = core.EngineObject(SystemRunner).get(); 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 = .{ .onComplete = callback, .context = context, .task = try SubprocessTask.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 = .{ .task = try SubprocessTask.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 = .{ .context = context, .onComplete = callback, .task = try SubprocessTask.runCommand( self.allocator, &.{"zig-out/bin/compileShaders.exe"}, compile_shaders_name.utf8(), ), }, }); } } const Task = union(enum(u8)) { subprocess: struct { task: *SubprocessTask, onComplete: ?*const fn (?*anyopaque) void = null, context: ?*anyopaque = null, }, }; pub const SystemRunner = struct { pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "sys.SystemRunner"); allocator: std.mem.Allocator, commandQueue: core.RingQueue(Task), activeCommand: ?Task = null, pub fn create(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); self.* = .{ .allocator = allocator, .commandQueue = try core.RingQueue(Task).init(self.allocator, 256), }; return self; } pub fn tick(self: *@This(), dt: f64) void { _ = dt; if (self.activeCommand == null) { if (self.commandQueue.popFromLocked()) |task| { switch (task) { .subprocess => |t| { self.activeCommand = task; // core.engine_log("starting task {any}", .{t.args}); t.task.run() catch { self.activeCommand = null; return; }; }, //.func => |f| { //f(self); // }, } } } if (self.activeCommand) |active| { if (active.subprocess.task.checkComplete()) { if (active.subprocess.onComplete) |onComplete| { onComplete(active.subprocess.context); } active.subprocess.task.destroy(); core.engine_logs("task complete"); self.activeCommand = null; } } } pub fn destroy(self: *@This()) void { self.commandQueue.deinit(); self.allocator.destroy(self); } };