Backlog/engine/sys/src/systemProc.zig

307 lines
9.8 KiB
Zig

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);
}
};