Backlog/engine/assets/src/cook.zig

287 lines
8.9 KiB
Zig

pub const CookResultEnum = enum {
Success,
Failure,
};
pub const FileBytes = struct {
path: []const u8,
bytes: []u8,
};
pub const ExtraFiles = std.ArrayList(FileBytes);
pub const CookResult = struct {
bytes: std.ArrayList(u8),
result: CookResultEnum = .Success,
extraFiles: ?ExtraFiles = null,
pub fn deinit(self: *@This()) void {
self.bytes.deinit();
}
};
pub const CookParams = struct {
noSave: bool = false,
fileName: []const u8,
cookFileName: []const u8,
};
pub const CookInfo = struct {
assetType: []const u8 = "none",
};
pub const CookRegistry = struct {
allocator: std.mem.Allocator,
cooks: std.StringHashMapUnmanaged(CookFunction) = .{},
generates: std.StringHashMapUnmanaged(CookGenerateFunction) = .{},
typesByExt: std.StringHashMapUnmanaged([]const u8) = .{},
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
};
gCookRegistry = self;
return self;
}
pub fn install(self: *@This(), typeName: []const u8, generate: CookGenerateFunction, cook: CookFunction, extensions: []const []const u8) !void {
try self.generates.put(self.allocator, typeName, generate);
try self.cooks.put(self.allocator, typeName, cook);
for (extensions) |ext| {
try self.typesByExt.put(self.allocator, ext, typeName);
}
}
pub fn destroy(self: *@This()) void {
self.cooks.deinit(self.allocator);
self.typesByExt.deinit(self.allocator);
self.generates.deinit(self.allocator);
self.allocator.destroy(self);
}
};
var gCookRegistry: *CookRegistry = undefined;
var gCooking: bool = false;
pub fn getRegistry() *CookRegistry {
return gCookRegistry;
}
pub fn startup(allocator: std.mem.Allocator) !void {
gCooking = true;
_ = try CookRegistry.create(allocator);
}
pub fn shutdown() void {
if (gCooking) {
gCookRegistry.destroy();
gCooking = false;
}
}
pub const GenerateError = error{UnableToGenerate};
pub const LoadError = error{BadFile};
pub const CookGenerateFunction = *const fn (allocator: std.mem.Allocator, filePath: []const u8, out: *std.ArrayList(u8)) GenerateError!void;
pub const CookFunction = *const fn (allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8, params: CookParams) CookResult;
pub fn loadFileAlloc(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) ![]align(8) u8 {
var file = try dir.openFile(path, .{});
defer file.close();
const filesize = (try file.stat()).size + 1; // add null byte
const buffer: []align(8) 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;
}
pub fn getExtension(path: []const u8) ?[]const u8 {
var extension: []const u8 = path;
var i: i32 = @intCast(path.len - 1);
while (i > 0) : (i -= 1) {
if (path[@intCast(i)] == '.') {
extension = path[@intCast(i)..];
return extension;
}
}
return null;
}
pub fn generateCookFile(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) !void {
if (getExtension(path)) |extension| {
if (gCookRegistry.typesByExt.get(extension)) |assetType| {
var cookFilePath = std.ArrayList(u8){};
defer cookFilePath.deinit(allocator);
const generateFunction = gCookRegistry.generates.get(assetType).?;
var outPath: std.ArrayListUnmanaged(u8) = .{};
defer outPath.deinit(allocator);
try outPath.appendSlice(allocator, path);
try outPath.appendSlice(allocator, ".cook");
try generateFunction(allocator, path, &cookFilePath);
const file = try dir.createFile(outPath.items, .{});
defer file.close();
var writeBuf: [4096]u8 = undefined;
var writer = file.writer(&writeBuf);
try writer.interface.writeAll(cookFilePath.items);
}
}
}
pub fn cookFile(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) !void {
std.debug.print("{s}\n", .{path});
const extension = getExtension(path).?;
if (std.mem.eql(u8, extension, ".cook")) {
return;
}
var cookFilePath = std.ArrayList(u8).init(allocator);
defer cookFilePath.deinit();
try cookFilePath.appendSlice(path);
try cookFilePath.appendSlice(".cook");
if (gCookRegistry.typesByExt.get(extension)) |assetType| {
const cookFunction = gCookRegistry.cooks.get(assetType).?;
dir.access(cookFilePath.items, .{}) catch return;
var results = cookFunction(allocator, dir, path, .{
.cookFileName = cookFilePath.items,
.fileName = path,
});
defer results.deinit();
var outPath = std.ArrayList(u8).init(allocator);
defer outPath.deinit();
try outPath.appendSlice("_cooked/");
try outPath.appendSlice(path);
try outPath.appendSlice(".");
try outPath.appendSlice(assetType);
{
var basePath: []const u8 = outPath.items;
var index: usize = basePath.len - 1;
while (basePath[index] != '/' and basePath[index] != '\\') : (index -= 1) {}
basePath = basePath[0..index];
try dir.makePath(basePath);
}
if (results.result == .Success) {
const file = try dir.createFile(outPath.items, .{});
defer file.close();
try file.writeAll(results.bytes.items);
}
return;
} else {
unreachable;
}
}
pub fn generateAllCookFiles(allocator: std.mem.Allocator, dir: std.fs.Dir) !void {
var iter = dir.iterate();
while (try iter.next()) |next| {
switch (next.kind) {
.file => {
try generateCookFile(allocator, dir, next.name);
},
.directory => {
if (std.mem.eql(u8, "_cooked", next.name)) {
continue;
}
if (std.mem.eql(u8, ".gitignore", next.name)) {}
var subDir = try dir.openDir(next.name, .{ .iterate = true });
defer subDir.close();
try generateAllCookFiles(allocator, subDir);
},
else => {},
}
}
}
pub fn cookAllFiles(allocator: std.mem.Allocator, dir: std.fs.Dir) !void {
var walker = try dir.walk(allocator);
defer walker.deinit();
while (try walker.next()) |next| {
switch (next.kind) {
.file => {
if (std.mem.startsWith(u8, next.path, "_cooked")) {
continue;
}
if (std.mem.startsWith(u8, next.path, ".gitignore")) {
continue;
}
try cookFile(allocator, dir, next.path);
},
else => {},
}
}
}
test "testing cooking" {
const alloc = std.testing.allocator;
const Test = struct {
const TestConfig = struct {
info: CookInfo = .{ .assetType = "Texture" }, // there must always be a CookInfo field
sourceType: []const u8 = "png",
};
pub fn generateFunction(allocator: std.mem.Allocator, path: []const u8, out: *std.ArrayList(u8)) GenerateError!void {
_ = allocator;
_ = path;
out.clearRetainingCapacity();
std.json.stringify(TestConfig{}, .{ .whitespace = .indent_4 }, out.writer()) catch return GenerateError.UnableToGenerate;
}
pub fn cookFunction(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8, params: CookParams) CookResult {
_ = params;
const rawFileBytes = loadFileAlloc(allocator, dir, path) catch unreachable;
defer allocator.free(rawFileBytes);
const rv = std.ArrayList(u8).init(allocator);
return .{
.bytes = rv,
.result = .Success,
};
}
};
const dir = try std.fs.cwd().openDir("test", .{ .iterate = true });
const registry = try CookRegistry.create(alloc);
defer registry.destroy();
try registry.generates.put(registry.allocator, "Texture", Test.generateFunction);
try registry.cooks.put(registry.allocator, "Texture", Test.cookFunction);
try registry.typesByExt.put(registry.allocator, ".png", "Texture");
// scan and generate all texture files
try generateAllCookFiles(alloc, dir);
try cookAllFiles(alloc, dir);
// try generateCookFile(alloc, dir, "icon.png");
// try cookFile(alloc, dir, "icon.png");
}
const std = @import("std");