const std = @import("std"); const log = std.log.scoped(.engine); const STBImage = @import("stb_image"); const Font = @import("./font.zig"); const Sound = @import("./sound.zig"); const Texture = @import("./texture.zig"); const Asset = @This(); id: Id, kind: union(Type) { font: Font.Id, sound: Sound.Id, texture: Texture.Id, }, pub const Id = u32; pub const Type = enum { font, sound, texture }; pub const Loader = struct { const Descriptor = struct { const KindTexture = struct { path: []const u8, info: Texture.Info }; id: Id, kind: union(Type) { font: []const u8, sound: []const u8, texture: KindTexture } }; arena: std.heap.ArenaAllocator, dir: std.fs.Dir, assets: std.ArrayList(Descriptor), add_failed: bool, pub fn init(gpa: std.mem.Allocator, dir: std.fs.Dir) Loader { return Loader{ .arena = .init(gpa), .dir = dir, .assets = .empty, .add_failed = false }; } pub fn deinit(self: *Loader) void { self.arena.deinit(); self.* = undefined; } fn readFile(self: *Loader, gpa: std.mem.Allocator, path: []const u8) ![]u8 { const f = try self.dir.openFile(path, .{}); defer f.close(); var wa: std.Io.Writer.Allocating = .init(gpa); defer wa.deinit(); var buffer: [4 * 4096]u8 = undefined; var reader = f.reader(&buffer); _ = try reader.interface.stream(&wa.writer, .unlimited); return try wa.toOwnedSlice(); } pub fn addFont(self: *Loader, id: Asset.Id, path: []const u8) void { const allocator = self.arena.allocator(); const path_dupe = allocator.dupe(u8, path) catch |e| { log.err("Failed to duplicate path: {}", .{e}); return; }; self.assets.append(allocator, Descriptor{ .id = id, .kind = .{ .font = path_dupe } }) catch |e| { log.err("Failed add sound: {}", .{e}); }; } pub fn addSound(self: *Loader, id: Asset.Id, path: []const u8) void { const allocator = self.arena.allocator(); const path_dupe = allocator.dupe(u8, path) catch |e| { log.err("Failed to duplicate path: {}", .{e}); return; }; self.assets.append(allocator, Descriptor{ .id = id, .kind = .{ .sound = path_dupe } }) catch |e| { log.err("Failed add sound: {}", .{e}); }; } fn addTextureFallible(self: *Loader, id: Asset.Id, path: []const u8) !void { const allocator = self.arena.allocator(); var image_info: Texture.Info = undefined; { const file_contents = try self.readFile(allocator, path); defer allocator.free(file_contents); const image = try STBImage.load(file_contents); defer image.deinit(); image_info = .{ .width = image.width, .height = image.height, .format = .png }; } const path_dupe = allocator.dupe(u8, path) catch |e| { log.err("Failed to duplicate path: {}", .{e}); return; }; const texture = Descriptor.KindTexture{ .path = path_dupe, .info = image_info }; self.assets.append(allocator, Descriptor{ .id = id, .kind = .{ .texture = texture } }) catch |e| { log.err("Failed add sound: {}", .{e}); }; } pub fn addTexture(self: *Loader, id: Asset.Id, path: []const u8) void { self.addTextureFallible(id, path) catch |e| { log.err("Failed to add texture: {}", .{e}); self.add_failed = true; }; } }; pub const Registry = struct { assets: []const Asset, textures: []const Texture, pub const empty = Registry{ .assets = &.{}, .textures = &.{} }; pub fn getAsset(self: Registry, id: Asset.Id) ?Asset { // TODO: Make this O(1)? for (self.assets) |asset| { if (asset.id == id) { return asset; } } return null; } pub fn getSound(self: Registry, id: Asset.Id) Sound.Id { const asset = self.getAsset(id) orelse return Sound.Id.nil; if (asset.kind != .sound) { return Sound.Id.nil; } return asset.kind.sound; } pub fn getFont(self: Registry, id: Asset.Id) Font.Id { const asset = self.getAsset(id) orelse return Font.Id.nil; if (asset.kind != .font) { return Font.Id.nil; } return asset.kind.font; } pub fn getTexture(self: Registry, id: Asset.Id) Texture.Id { const asset = self.getAsset(id) orelse return Texture.Id.nil; if (asset.kind != .texture) { return Texture.Id.nil; } return asset.kind.texture; } pub fn getTextureInfo(self: Registry, id: Texture.Id) Texture.Info { if (id == .nil) { log.warn("Failed to access .nil texture info", .{}); return .nil; } // TODO: Make this O(1)? for (self.textures) |texture| { if (texture.id == id) { return texture.info; } } log.warn("Failed to find texture info, id={}", .{id}); return .nil; } };