Backlog/engine/assets/src/asset_references.zig

216 lines
7.1 KiB
Zig

const std = @import("std");
const core = @import("core");
const tracy = core.tracy;
pub const AssetImportReference = struct {
assetRef: AssetRef,
properties: AssetPropertiesBag,
};
// MakeImportRef with defaults.
pub fn MakeImportRef(assetType: []const u8, name: []const u8, path: []const u8) AssetImportReference {
return .{
.assetRef = .{
.assetType = core.Name.MakeComptime(assetType),
.name = core.Name.MakeComptime(name),
},
.properties = .{ .path = path },
};
}
// Advanced version of MakeImportRef, allows user to explicitly construct the properties bag.
pub fn MakeImportRefOptions(assetType: []const u8, name: []const u8, properties: AssetPropertiesBag) AssetImportReference {
return .{
.assetRef = .{
.assetType = core.Name.MakeComptime(assetType),
.name = core.Name.MakeComptime(name),
},
.properties = properties,
};
}
// TODO, this needs to be replaced with some kind of polymorphic data bag I think,
// some kind of transmute is probably in order, maybe allocate 256 bytes of data
// for it.
pub const AssetPropertiesBag = struct {
path: []const u8 = "None",
soundVolume: f32 = 1.0,
soundLooping: bool = false,
textureUseBlockySampler: bool = true,
meshType: ?[]const u8 = null,
skeletonName: ?[]const u8 = null,
textureCube: bool = false,
textureList: ?[]const []const u8 = null, // if set, path texture will be loaded first, and this list of textures will be loaded together.
};
pub const AssetRef = struct {
name: core.Name,
assetType: core.Name,
};
// very minimal asset loading library.
pub const AssetLoaderError = error{
UnableToLoad,
};
pub const AssetLoaderStatus = enum {
AssetReady, // The asset was loaded and ready to be installed
LoadFailed, // The asset failed for one reason or another
};
pub const AssetLoaderMessage = struct {
assetRef: AssetRef,
status: AssetLoaderStatus,
message: ?[]u8,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator, assetRef: AssetRef, status: AssetLoaderStatus, maybeMessage: ?[]const u8) !@This() {
var m: ?[]u8 = null;
if (maybeMessage) |message| {
m = try core.dupeString(message);
}
return .{ .message = m, .allocator = allocator, .assetRef = assetRef, .status = status };
}
pub fn deinit(self: *@This()) void {
if (self.message) |message| {
self.allocator.free(message);
}
}
};
pub const AssetLoaderInterface = struct {
typeName: []const u8,
typeSize: usize,
typeAlign: usize,
assetType: []const u8,
loadAsset: *const fn (*anyopaque, AssetRef, ?AssetPropertiesBag) AssetLoaderError!void,
// destroy: *const fn (*anyopaque, std.mem.Allocator) void,
discardAll: *const fn (*anyopaque) void,
pub fn from(comptime assetType: []const u8, comptime TargetType: type) @This() {
const wrappedFuncs = struct {
pub fn loadAsset(
pointer: *anyopaque,
assetRef: AssetRef,
properties: ?AssetPropertiesBag,
) AssetLoaderError!void {
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
try ptr.loadAsset(assetRef, properties);
}
pub fn discardAll(pointer: *anyopaque) void {
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
ptr.discardAll();
}
pub fn destroy(
pointer: *anyopaque,
allocator: std.mem.Allocator,
) void {
var ptr = @as(*TargetType, @ptrCast(@alignCast(pointer)));
ptr.destroy(allocator);
}
};
if (!@hasDecl(TargetType, "loadAsset")) {
@compileLog("Tried to generate AssetLoaderInterface for type ", TargetType, "but it's missing func loadAsset");
unreachable;
}
if (!@hasDecl(TargetType, "destroy")) {
@compileLog("Tried to generate AssetLoaderInterface for type ", TargetType, "but it's missing func destroy.");
unreachable;
}
const self = @This(){
.typeName = @typeName(TargetType),
.typeSize = @sizeOf(TargetType),
.typeAlign = @alignOf(TargetType),
.loadAsset = wrappedFuncs.loadAsset,
.discardAll = wrappedFuncs.discardAll,
.assetType = assetType,
};
return self;
}
};
pub const AssetLoaderRef = struct {
target: *anyopaque,
vtable: *const AssetLoaderInterface,
size: usize,
pub fn loadAsset(self: *@This(), asset: AssetRef, propertiesBag: ?AssetPropertiesBag) !void {
try self.vtable.loadAsset(self.target, asset, propertiesBag);
}
pub fn discardAll(self: @This()) void {
self.vtable.discardAll(self.target);
}
};
pub const AssetReferenceSys = struct {
loaders: std.AutoHashMapUnmanaged(u32, AssetLoaderRef),
allocator: std.mem.Allocator,
outstandingAssetJobs: std.atomic.Value(i32),
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "AssetReference");
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = @This(){
.loaders = .{},
.allocator = allocator,
.outstandingAssetJobs = std.atomic.Value(i32).init(0),
};
return self;
}
pub fn registerLoader(self: *@This(), loader: anytype) !void {
const vtable = &@field(@TypeOf(loader.*), "LoaderInterfaceVTable");
var assetType = core.MakeName(vtable.assetType);
try self.loaders.put(self.allocator, assetType.handle(), .{
.vtable = vtable,
.target = loader,
.size = @sizeOf(@TypeOf(loader)),
});
core.engine_log("asset loader for asset type ({s}) registered", .{assetType.utf8()});
}
pub fn loadRef(self: *@This(), asset: AssetRef, propertiesBag: ?AssetPropertiesBag) !void {
if (core.getEngine().isShuttingDown())
return;
_ = self.outstandingAssetJobs.fetchAdd(1, .acquire);
var z = tracy.ZoneN(@src(), "AssetReferenceSys LoadRef");
var assetName = asset.name;
var assetType = asset.assetType;
if (propertiesBag) |props| {
core.engine_log("loading asset {s} ({s}) [{s}]", .{ assetName.utf8(), assetType.utf8(), props.path });
} else {
core.engine_log("loading asset {s} ({s})", .{ assetName.utf8(), assetType.utf8() });
}
var n = asset.assetType;
try self.loaders.getPtr(n.handle()).?.loadAsset(asset, propertiesBag);
z.End();
}
pub fn deinit(self: *@This()) void {
// the loader system shall no longer
// control lifetimes for assset loaders
// each asset loader will be responsible for its own lifetime
// var iter = self.loaders.valueIterator();
// while (iter.next()) |i| {
// i.destroy(self.allocator);
// }
self.loaders.deinit(self.allocator);
self.allocator.destroy(self);
}
};