Backlog/projects/tools/newProjectMaker.zig

522 lines
15 KiB
Zig

pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "main");
const GameContext = @This();
allocator: std.mem.Allocator,
pathBuffer: [8192]u8,
projectNameBuffer: [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,
.pathBuffer = std.mem.zeroes([8192]u8),
.projectNameBuffer = 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"),
};
std.mem.copyForwards(u8, &self.projectNameBuffer, "newProject");
return self;
}
pub fn prepare(self: *@This()) !void {
_ = self;
core.engine_log("program ready", .{});
}
pub fn tick(self: *@This(), dt: f64) void {
_ = dt;
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.textf("outstanding tasks: {d}", .{self.commandQueue.count() + 1});
if (self.activeCommand) |cmd| {
ig.textf("current command: ", .{});
for (cmd.argsOwned.items) |arg| {
ig.textf("{s}", .{arg});
ig.sameLine(0, 4);
}
}
}
ig.end();
return;
}
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,
})) {
if (ig.inputText(
"##ProjectName",
&self.projectNameBuffer,
self.projectNameBuffer.len,
.{},
null,
null,
)) {
// core.engine_log("huh", .{});
}
ig.setNextItemWidth(600);
if (ig.inputText(
"##Path",
&self.pathBuffer,
self.pathBuffer.len,
.{},
null,
null,
)) {
// core.engine_log("huh", .{});
}
ig.sameLine(0, 3);
if (ig.button("...", .{})) {
core.engine_log("opening NFD", .{});
platform.file_dialogue.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.pathBuffer)));
core.engine_log("creating project: {s}", .{self.pathBuffer});
self.createProject(asSlice) catch {};
}
}
ig.end();
if (self.showInstructions) {
ig.setNextWindowPos(.{ .x = 100, .y = 100 }, .{}, .{});
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();
}
}
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);
{
const asSlice = std.mem.span(@as([*c]const u8, @ptrCast(&self.projectNameBuffer)));
self.targetName = try self.allocator.dupe(u8, asSlice);
}
try self.commandQueue.pushLocked(.{ .subprocess = try sys.runCommand(
self.allocator,
&.{ "zig", "init" },
targetDir,
) });
// const deployShaderDir = try std.fmt.allocPrint(self.allocator, "{s}/content/_shaders", .{self.targetDir.?});
if (builtin.os.tag == .windows) {
// os copy over the entire directory
} else {}
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 = copyShaders });
try self.commandQueue.pushLocked(.{ .func = displayInstructions });
}
fn copyShaders(self: *@This()) void {
self.copyShadersWrap() catch {};
}
fn copyShadersWrap(self: *@This()) !void {
var srcDir = try std.fs.cwd().openDir("content/_shaders", .{ .iterate = true });
defer srcDir.close();
var targetDir = try std.fs.cwd().openDir(self.targetDir.?, .{});
defer targetDir.close();
try targetDir.makeDir("content");
try targetDir.makeDir("content/_shaders");
var dstDir = try targetDir.openDir("content/_shaders", .{});
defer dstDir.close();
try copyRecursiveDir(self.allocator, &srcDir, &dstDir);
}
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.read(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);
core.engine_log("{s}", .{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.?, .{ .iterate = true });
defer workingDir.close();
core.engine_logs("deleting 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){};
defer content.deinit(self.allocator);
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 content.print(self.allocator, 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 content.print(self.allocator, template2, .{self.targetName.?});
try content.append(self.allocator, 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){};
defer content.deinit(self.allocator);
try content.print(self.allocator, ".{{\n", .{});
try content.print(self.allocator,
\\ .name = .{s},
\\ .version = "0.0.0",
\\ .dependencies = .{{
\\ .Backlog = .{{ .path = "{s}" }},
, .{ std.fs.path.basename(self.targetDir.?), self.engineRelative.? });
for (entries) |entry| {
try content.print(self.allocator, " .{s} = .{{ .path = \"{s}/{s}\" }},\n", .{ entry.name, self.engineRelative.?, entry.path });
}
try content.print(self.allocator,
\\ }},
\\
, .{});
try content.print(self.allocator,
\\ .paths = .{{ "" }},
\\
, .{});
try content.print(self.allocator,
\\ .fingerprint = {s},
\\
, .{self.fingerprint.?});
try content.print(self.allocator, "}}", .{});
const file = try workingDir.createFile("build.zig.zon", .{});
defer file.close();
var writer = file.writer(&wbuf);
try writer.interface.writeAll(content.items);
}
fn writeMainZig(self: *@This(), workingDir: *std.fs.Dir) !void {
var content = std.ArrayList(u8){};
defer content.deinit(self.allocator);
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;
\\
;
const allocator = self.allocator;
try content.appendSlice(allocator, template);
try content.appendSlice(allocator, self.targetName.?);
try content.appendSlice(allocator, template2);
try content.append(allocator, 0);
const out_path = "src/main.zig";
std.debug.print("{s}", .{content.items});
try self.writeZigFile(workingDir, out_path, content.items);
}
var wbuf: [4096]u8 = undefined;
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.renderAlloc(self.allocator);
const file = try workingDir.createFile(out_path, .{});
defer file.close();
var fw = file.writer(&wbuf);
try fw.interface.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]});
self.fingerprint = zonEntry[left..zonEntry.len];
}
pub fn onFolderSelected(
ctx: ?*anyopaque,
path: ?[]const u8,
) void {
const self: *@This() = @ptrCast(@alignCast(ctx));
if (path) |p| {
std.mem.copyForwards(u8, &self.pathBuffer, p);
self.pathBuffer[p.len] = 0;
}
}
pub fn deinit(self: *@This()) void {
if (self.targetDir) |t| {
self.allocator.free(t);
}
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 });
_ = api.startEngine(&NeonObjectTable, &spec);
}
const builtin = @import("builtin");
const std = @import("std");
const api = @import("backlog");
const ig = api.imgui.api;
const platform = api.platform;
const core = api.core;
const sys = api.sys;