scrap current asset manager
This commit is contained in:
parent
9becd7d56b
commit
7954a8915a
@ -1,172 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Lib = @import("engine_lib");
|
||||
|
||||
const AssetBundle = @This();
|
||||
|
||||
fonts: std.ArrayList(Font),
|
||||
sounds: std.ArrayList(Sound),
|
||||
textures: std.ArrayList(Texture),
|
||||
data: std.ArrayList(u8),
|
||||
|
||||
const DataSegment = extern struct {
|
||||
offset: u64,
|
||||
len: u64,
|
||||
};
|
||||
|
||||
const Font = extern struct {
|
||||
asset_id: Lib.Asset.Id,
|
||||
info: Lib.Font.Info,
|
||||
data: DataSegment
|
||||
};
|
||||
|
||||
const Sound = extern struct {
|
||||
asset_id: Lib.Asset.Id,
|
||||
info: Lib.Sound.Info,
|
||||
data: DataSegment
|
||||
};
|
||||
|
||||
const Texture = extern struct {
|
||||
asset_id: Lib.Asset.Id,
|
||||
info: Lib.Texture.Info,
|
||||
data: DataSegment
|
||||
};
|
||||
|
||||
const Header = extern struct {
|
||||
magic: Magic,
|
||||
fonts_len: u32,
|
||||
sounds_len: u32,
|
||||
textures_len: u32,
|
||||
data_len: u32,
|
||||
|
||||
pub const Magic = enum(u32) {
|
||||
v1 = 0x961C8C65,
|
||||
_,
|
||||
};
|
||||
};
|
||||
|
||||
const endianness: std.builtin.Endian = .little;
|
||||
|
||||
pub fn init() AssetBundle {
|
||||
return AssetBundle{
|
||||
.fonts = .empty,
|
||||
.sounds = .empty,
|
||||
.textures = .empty,
|
||||
.data = .empty,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *AssetBundle, gpa: std.mem.Allocator) void {
|
||||
self.sounds.deinit(gpa);
|
||||
self.textures.deinit(gpa);
|
||||
self.fonts.deinit(gpa);
|
||||
self.data.deinit(gpa);
|
||||
}
|
||||
|
||||
pub fn getData(self: AssetBundle, data: DataSegment) []const u8 {
|
||||
return self.data.items[data.offset..][0..data.len];
|
||||
}
|
||||
|
||||
fn insertData(
|
||||
self: *AssetBundle,
|
||||
gpa: std.mem.Allocator,
|
||||
data: []const u8
|
||||
) !DataSegment {
|
||||
const data_offset = self.data.items.len;
|
||||
try self.data.appendSlice(gpa, data);
|
||||
|
||||
return DataSegment{
|
||||
.offset = data_offset,
|
||||
.len = data.len
|
||||
};
|
||||
}
|
||||
|
||||
pub fn appendSound(
|
||||
self: *AssetBundle,
|
||||
gpa: std.mem.Allocator,
|
||||
id: Lib.Asset.Id,
|
||||
data: []const u8,
|
||||
info: Lib.Sound.Info,
|
||||
) !void {
|
||||
try self.sounds.append(gpa, Sound{
|
||||
.asset_id = id,
|
||||
.data = try self.insertData(gpa, data),
|
||||
.info = info
|
||||
});
|
||||
}
|
||||
|
||||
pub fn appendFont(
|
||||
self: *AssetBundle,
|
||||
gpa: std.mem.Allocator,
|
||||
id: Lib.Asset.Id,
|
||||
data: []const u8,
|
||||
info: Lib.Font.Info,
|
||||
) !void {
|
||||
try self.fonts.append(gpa, Font{
|
||||
.asset_id = id,
|
||||
.data = try self.insertData(gpa, data),
|
||||
.info = info
|
||||
});
|
||||
}
|
||||
|
||||
pub fn appendTexture(
|
||||
self: *AssetBundle,
|
||||
gpa: std.mem.Allocator,
|
||||
id: Lib.Asset.Id,
|
||||
data: []const u8,
|
||||
info: Lib.Texture.Info
|
||||
) !void {
|
||||
try self.textures.append(gpa, Texture{
|
||||
.asset_id = id,
|
||||
.info = info,
|
||||
.data = try self.insertData(gpa, data)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn write(self: AssetBundle, writer: *std.Io.Writer) !void {
|
||||
try writer.writeStruct(Header{
|
||||
.magic = .v1,
|
||||
.fonts_len = @intCast(self.fonts.items.len),
|
||||
.sounds_len = @intCast(self.sounds.items.len),
|
||||
.textures_len = @intCast(self.textures.items.len),
|
||||
.data_len = @intCast(self.data.items.len)
|
||||
}, endianness);
|
||||
|
||||
try writer.writeSliceEndian(Font, self.fonts.items, endianness);
|
||||
try writer.writeSliceEndian(Sound, self.sounds.items, endianness);
|
||||
try writer.writeSliceEndian(Texture, self.textures.items, endianness);
|
||||
try writer.writeAll(self.data.items);
|
||||
}
|
||||
|
||||
pub fn read(self: *AssetBundle, gpa: std.mem.Allocator, reader: *std.Io.Reader) !void {
|
||||
const header = try reader.takeStruct(Header, endianness);
|
||||
if (header.magic != .v1) {
|
||||
return error.InvalidMagic;
|
||||
}
|
||||
|
||||
try self.fonts.ensureTotalCapacity(gpa, header.fonts_len);
|
||||
try self.sounds.ensureTotalCapacity(gpa, header.sounds_len);
|
||||
try self.textures.ensureTotalCapacity(gpa, header.textures_len);
|
||||
try self.data.ensureTotalCapacity(gpa, header.data_len);
|
||||
|
||||
self.fonts.clearRetainingCapacity();
|
||||
self.sounds.clearRetainingCapacity();
|
||||
self.textures.clearRetainingCapacity();
|
||||
self.data.clearRetainingCapacity();
|
||||
|
||||
try reader.readSliceEndian(
|
||||
Font,
|
||||
self.fonts.addManyAsSliceAssumeCapacity(header.fonts_len),
|
||||
endianness
|
||||
);
|
||||
try reader.readSliceEndian(
|
||||
Sound,
|
||||
self.sounds.addManyAsSliceAssumeCapacity(header.sounds_len),
|
||||
endianness
|
||||
);
|
||||
try reader.readSliceEndian(
|
||||
Texture,
|
||||
self.textures.addManyAsSliceAssumeCapacity(header.textures_len),
|
||||
endianness
|
||||
);
|
||||
try reader.readSliceAll(self.data.addManyAsSliceAssumeCapacity(header.data_len));
|
||||
}
|
||||
@ -35,7 +35,6 @@ const KindDynamic = struct {
|
||||
.init = try ensureLookup(lib, GameCallbacks.InitFn, "init"),
|
||||
.tick = try ensureLookup(lib, GameCallbacks.TickFn, "tick"),
|
||||
.deinit = try ensureLookup(lib, GameCallbacks.DeinitFn, "deinit"),
|
||||
.loadAssets = if (build_options.asset_hot_reload) try ensureLookup(lib, GameCallbacks.LoadAssetsFn, "loadAssets") else {},
|
||||
.debug = if (build_options.has_imgui) try ensureLookup(lib, GameCallbacks.DebugFn, "debug") else {},
|
||||
};
|
||||
}
|
||||
@ -212,7 +211,6 @@ const KindStatic = struct {
|
||||
.init = game.init,
|
||||
.deinit = game.deinit,
|
||||
.tick = game.tick,
|
||||
.loadAssets = if (build_options.asset_hot_reload) game.loadAssets else {},
|
||||
.debug = if (build_options.has_imgui) game.debug else {},
|
||||
};
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@ const STBImage = @import("stb_image");
|
||||
const Gfx = Graphics;
|
||||
|
||||
pub const Lib = @import("engine_lib");
|
||||
pub const AssetBundle = @import("./asset_bundle.zig");
|
||||
|
||||
const build_options = Lib.build_options;
|
||||
const debug_keybinds = (builtin.mode == .Debug);
|
||||
@ -124,18 +123,15 @@ const GameState = struct {
|
||||
self: *GameState,
|
||||
init_fn: GameCallbacks.InitFn,
|
||||
initial: Init,
|
||||
asset_registry: Lib.Asset.Registry
|
||||
) void {
|
||||
assert(self.frame == null);
|
||||
const opts = Lib.Init{
|
||||
.gpa = self.gpa,
|
||||
.seed = initial.seed,
|
||||
.assets = asset_registry
|
||||
};
|
||||
self.state = init_fn(&opts);
|
||||
|
||||
self.frame = .init(self.gpa, initial.screen_size);
|
||||
self.frame.?.assets = asset_registry;
|
||||
}
|
||||
|
||||
fn runGameDeinit(self: *GameState, deinit_fn: GameCallbacks.DeinitFn) void {
|
||||
@ -254,157 +250,12 @@ const Recording = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const AssetManager = struct {
|
||||
const LoadAsset = union(enum) {
|
||||
font: struct {
|
||||
id: Lib.Font.Id,
|
||||
path: []const u8
|
||||
},
|
||||
sound: struct {
|
||||
id: Lib.Sound.Id,
|
||||
path: []const u8
|
||||
},
|
||||
texture: struct {
|
||||
id: Lib.Texture.Id,
|
||||
path: []const u8
|
||||
},
|
||||
|
||||
pub fn clone(self: LoadAsset, arena: std.mem.Allocator) !LoadAsset {
|
||||
return switch (self) {
|
||||
.font => |opts| LoadAsset{
|
||||
.font = .{
|
||||
.id = opts.id,
|
||||
.path = try arena.dupe(u8, opts.path)
|
||||
}
|
||||
},
|
||||
.sound => |opts| LoadAsset{
|
||||
.sound = .{
|
||||
.id = opts.id,
|
||||
.path = try arena.dupe(u8, opts.path)
|
||||
}
|
||||
},
|
||||
.texture => |opts| LoadAsset{
|
||||
.texture = .{
|
||||
.id = opts.id,
|
||||
.path = try arena.dupe(u8, opts.path)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
gpa: std.mem.Allocator,
|
||||
assets: std.ArrayList(Lib.Asset),
|
||||
textures: std.ArrayList(Lib.Texture),
|
||||
|
||||
dir: ?std.fs.Dir,
|
||||
queued_loads_arena: std.heap.ArenaAllocator,
|
||||
queued_loads: std.ArrayList(LoadAsset),
|
||||
|
||||
pub fn init(gpa: std.mem.Allocator) AssetManager {
|
||||
return AssetManager{
|
||||
.gpa = gpa,
|
||||
.assets = .empty,
|
||||
.textures = .empty,
|
||||
.dir = null,
|
||||
.queued_loads_arena = .init(gpa),
|
||||
.queued_loads = .empty
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *AssetManager) void {
|
||||
self.queued_loads_arena.deinit();
|
||||
self.assets.deinit(self.gpa);
|
||||
self.textures.deinit(self.gpa);
|
||||
}
|
||||
|
||||
pub fn initRegistry(self: AssetManager) Lib.Asset.Registry {
|
||||
return Lib.Asset.Registry{
|
||||
.assets = self.assets.items,
|
||||
.textures = self.textures.items
|
||||
};
|
||||
}
|
||||
|
||||
pub fn enqueueLoad(self: *AssetManager, load: LoadAsset) !void {
|
||||
const arena = self.queued_loads_arena.allocator();
|
||||
|
||||
try self.queued_loads.append(arena, try load.clone(arena));
|
||||
}
|
||||
|
||||
pub fn performQueuedLoads(self: *AssetManager, graphics: *Graphics, audio: *Audio) !void {
|
||||
if (self.dir == null) {
|
||||
if (self.queued_loads.items.len > 0) {
|
||||
return error.MissingAssetDirectory;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const dir = self.dir.?;
|
||||
for (self.queued_loads.items) |queued_load| {
|
||||
switch (queued_load) {
|
||||
.font => |opts| {
|
||||
const font_data = try readFileAll(self.gpa, dir, opts.path);
|
||||
defer self.gpa.free(font_data);
|
||||
try graphics.setFontData(opts.id, font_data);
|
||||
},
|
||||
.sound => |opts| {
|
||||
const sound_data = try readFileAll(self.gpa, dir, opts.path);
|
||||
defer self.gpa.free(sound_data);
|
||||
try audio.setSoundData(opts.id, .{
|
||||
.data = sound_data,
|
||||
.format = .vorbis
|
||||
});
|
||||
},
|
||||
.texture => |opts| {
|
||||
const image_data = try readFileAll(self.gpa, dir, opts.path);
|
||||
defer self.gpa.free(image_data);
|
||||
|
||||
const image = try STBImage.load(image_data);
|
||||
defer image.deinit();
|
||||
|
||||
// TODO: Assert that loaded data matches precomputed texture info
|
||||
try graphics.setTextureData(opts.id, .{
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
.rgba = image.rgba8_pixels,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = self.queued_loads_arena.reset(.free_all);
|
||||
self.queued_loads = .empty;
|
||||
}
|
||||
|
||||
fn hasTexture(self: AssetManager, texture_id: Lib.Texture.Id) bool {
|
||||
for (self.textures.items) |texture| {
|
||||
if (texture.id == texture_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn assertAssetsLoaded(self: AssetManager) void {
|
||||
for (self.assets.items) |asset| {
|
||||
switch (asset.kind) {
|
||||
.texture => |texture_id| {
|
||||
assert(self.hasTexture(texture_id));
|
||||
},
|
||||
else => {} // TODO:
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Engine = @This();
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
graphics: Graphics,
|
||||
audio: Audio,
|
||||
|
||||
asset_bundle: ?AssetBundle = null,
|
||||
asset_manager: AssetManager,
|
||||
game_linking: GameLinking,
|
||||
game_state: GameState,
|
||||
|
||||
@ -473,123 +324,9 @@ pub fn run(self: *Engine, opts: RunOptions) !void {
|
||||
.play_recording = false,
|
||||
.recording_tick = 0,
|
||||
.seed = initial.seed,
|
||||
.asset_manager = .init(opts.allocator),
|
||||
.asset_bundle = null,
|
||||
};
|
||||
|
||||
switch (opts.asset_loading) {
|
||||
.bundle_bytes => |bundle_bytes| {
|
||||
var asset_bundle = AssetBundle.init();
|
||||
errdefer asset_bundle.deinit(opts.allocator);
|
||||
|
||||
var reader = std.Io.Reader.fixed(bundle_bytes);
|
||||
asset_bundle.read(opts.allocator, &reader) catch |e| {
|
||||
log.err("Failed to load asset bundle: {}", .{e});
|
||||
return error.FailedToLoadAssetBundle;
|
||||
};
|
||||
|
||||
for (asset_bundle.fonts.items) |font| {
|
||||
const font_id = try self.graphics.addFont();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = font.asset_id,
|
||||
.kind = .{ .font = font_id }
|
||||
});
|
||||
}
|
||||
|
||||
for (asset_bundle.textures.items) |texture| {
|
||||
const texture_id = try self.graphics.addTexture();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = texture.asset_id,
|
||||
.kind = .{ .texture = texture_id }
|
||||
});
|
||||
|
||||
try self.asset_manager.textures.append(self.asset_manager.gpa, .{
|
||||
.id = texture_id,
|
||||
.info = texture.info
|
||||
});
|
||||
}
|
||||
|
||||
for (asset_bundle.sounds.items) |sound| {
|
||||
const sound_id = try self.audio.addSound();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = sound.asset_id,
|
||||
.kind = .{ .sound = sound_id }
|
||||
});
|
||||
}
|
||||
|
||||
self.asset_bundle = asset_bundle;
|
||||
},
|
||||
.dir_path => |asset_dir_path| {
|
||||
if (!build_options.asset_hot_reload) {
|
||||
return error.RuntimeAssetLoadingDisabled;
|
||||
}
|
||||
|
||||
const dir = try std.fs.cwd().openDir(asset_dir_path, .{ .iterate = true });
|
||||
|
||||
var loader: Lib.Asset.Loader = .init(opts.allocator, dir);
|
||||
defer loader.deinit();
|
||||
game_linking.callbacks.loadAssets(&loader);
|
||||
|
||||
for (loader.assets.items) |asset_desc| {
|
||||
switch (asset_desc.kind) {
|
||||
.font => |path| {
|
||||
const font_id = try self.graphics.addFont();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = asset_desc.id,
|
||||
.kind = .{ .font = font_id }
|
||||
});
|
||||
|
||||
try self.asset_manager.enqueueLoad(.{
|
||||
.font = .{
|
||||
.id = font_id,
|
||||
.path = path
|
||||
}
|
||||
});
|
||||
},
|
||||
.texture => |texture| {
|
||||
const texture_id = try self.graphics.addTexture();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = asset_desc.id,
|
||||
.kind = .{ .texture = texture_id }
|
||||
});
|
||||
|
||||
try self.asset_manager.enqueueLoad(.{
|
||||
.texture = .{
|
||||
.id = texture_id,
|
||||
.path = texture.path
|
||||
}
|
||||
});
|
||||
try self.asset_manager.textures.append(self.asset_manager.gpa, .{
|
||||
.id = texture_id,
|
||||
.info = texture.info
|
||||
});
|
||||
},
|
||||
.sound => |path| {
|
||||
const sound_id = try self.audio.addSound();
|
||||
try self.asset_manager.assets.append(self.allocator, .{
|
||||
.id = asset_desc.id,
|
||||
.kind = .{ .sound = sound_id }
|
||||
});
|
||||
|
||||
try self.asset_manager.enqueueLoad(.{
|
||||
.sound = .{
|
||||
.id = sound_id,
|
||||
.path = path
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
self.asset_manager.dir = dir;
|
||||
},
|
||||
.bundle_path => {
|
||||
// TODO:
|
||||
unreachable;
|
||||
},
|
||||
}
|
||||
|
||||
self.game_state.runGameInit(game_linking.callbacks.init, initial, self.asset_manager.initRegistry());
|
||||
self.game_state.runGameInit(game_linking.callbacks.init, initial);
|
||||
|
||||
if (opts.do_recording) {
|
||||
self.recording = .init(opts.allocator, initial);
|
||||
@ -669,45 +406,6 @@ fn sokolInit(self: *Engine) !void {
|
||||
|
||||
try self.graphics.setupSokol();
|
||||
self.audio.setupSokol();
|
||||
|
||||
try self.asset_manager.performQueuedLoads(&self.graphics, &self.audio);
|
||||
if (self.asset_bundle) |*asset_bundle| {
|
||||
const registry = self.asset_manager.initRegistry();
|
||||
|
||||
for (asset_bundle.fonts.items) |font| {
|
||||
const font_id = registry.getFont(font.asset_id);
|
||||
assert(font_id != Lib.Font.Id.nil);
|
||||
try self.graphics.setFontData(font_id, asset_bundle.getData(font.data));
|
||||
}
|
||||
|
||||
for (asset_bundle.textures.items) |texture| {
|
||||
const texture_id = registry.getTexture(texture.asset_id);
|
||||
assert(texture_id != Lib.Texture.Id.nil);
|
||||
|
||||
const image = try STBImage.load(asset_bundle.getData(texture.data));
|
||||
defer image.deinit();
|
||||
|
||||
try self.graphics.setTextureData(texture_id, .{
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
.rgba = image.rgba8_pixels
|
||||
});
|
||||
}
|
||||
|
||||
for (asset_bundle.sounds.items) |sound| {
|
||||
const sound_id = registry.getSound(sound.asset_id);
|
||||
assert(sound_id != Lib.Sound.Id.nil);
|
||||
try self.audio.setSoundData(sound_id, .{
|
||||
.data = asset_bundle.getData(sound.data),
|
||||
.format = .vorbis
|
||||
});
|
||||
}
|
||||
|
||||
asset_bundle.deinit(self.allocator);
|
||||
self.asset_bundle = null;
|
||||
}
|
||||
|
||||
self.asset_manager.assertAssetsLoaded();
|
||||
}
|
||||
|
||||
fn sokolCleanup(self: *Engine) void {
|
||||
@ -724,7 +422,6 @@ fn sokolCleanup(self: *Engine) void {
|
||||
self.audio.deinit();
|
||||
self.graphics.deinit(self.allocator);
|
||||
self.game_linking.deinit(self.allocator);
|
||||
self.asset_manager.deinit();
|
||||
}
|
||||
|
||||
fn sokolFrame(self: *Engine) !void {
|
||||
@ -799,7 +496,7 @@ fn sokolFrame(self: *Engine) !void {
|
||||
self.game_state.deinit();
|
||||
|
||||
self.game_state = .init(self.allocator);
|
||||
self.game_state.runGameInit(self.game_linking.callbacks.init, initial, self.asset_manager.initRegistry());
|
||||
self.game_state.runGameInit(self.game_linking.callbacks.init, initial);
|
||||
|
||||
if (self.recording) |recording| {
|
||||
recording.deinit();
|
||||
@ -851,7 +548,7 @@ fn showDebugWindow(self: *Engine) !void {
|
||||
self.game_state.deinit();
|
||||
|
||||
self.game_state = .init(self.allocator);
|
||||
self.game_state.runGameInit(self.game_linking.callbacks.init, recording.initial, self.asset_manager.initRegistry());
|
||||
self.game_state.runGameInit(self.game_linking.callbacks.init, recording.initial);
|
||||
|
||||
self.play_recording = true;
|
||||
self.recording_tick = 0;
|
||||
|
||||
@ -1,207 +0,0 @@
|
||||
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;
|
||||
}
|
||||
};
|
||||
@ -146,7 +146,6 @@ pub const MouseButton = enum {
|
||||
pub const Texture = @import("./texture.zig");
|
||||
pub const Font = @import("./font.zig");
|
||||
pub const Sound = @import("./sound.zig");
|
||||
pub const Asset = @import("./asset.zig");
|
||||
|
||||
pub const Sprite = struct {
|
||||
texture: Texture.Id,
|
||||
@ -289,8 +288,6 @@ pub const Frame = struct {
|
||||
screen_size: Vec2,
|
||||
clear_color: Vec4,
|
||||
|
||||
assets: Asset.Registry,
|
||||
|
||||
pub fn init(
|
||||
gpa: std.mem.Allocator,
|
||||
screen_size: Vec2
|
||||
@ -309,7 +306,6 @@ pub const Frame = struct {
|
||||
.audio_commands = .empty,
|
||||
.hide_cursor = false,
|
||||
.screen_size = screen_size,
|
||||
.assets = .empty
|
||||
};
|
||||
}
|
||||
|
||||
@ -477,8 +473,6 @@ pub const Frame = struct {
|
||||
pub const Init = struct {
|
||||
gpa: std.mem.Allocator,
|
||||
seed: u64,
|
||||
|
||||
assets: Asset.Registry,
|
||||
};
|
||||
|
||||
pub const Callbacks = struct {
|
||||
@ -488,12 +482,10 @@ pub const Callbacks = struct {
|
||||
pub const TickFn = *const fn(state: State, frame: *Frame) callconv(.c) void;
|
||||
pub const DeinitFn = *const fn(state: State) callconv(.c) void;
|
||||
pub const DebugFn = *const fn(state: State, imgui: *ImGui) callconv(.c) void;
|
||||
pub const LoadAssetsFn = *const fn(assets: *Asset.Loader) callconv(.c) void;
|
||||
|
||||
init: InitFn,
|
||||
tick: TickFn,
|
||||
deinit: DeinitFn,
|
||||
loadAssets: if (build_options.asset_hot_reload) LoadAssetsFn else void,
|
||||
debug: if (build_options.has_imgui) DebugFn else void
|
||||
};
|
||||
|
||||
@ -501,8 +493,7 @@ pub fn exportCallbacksIfNeeded(
|
||||
comptime init: Callbacks.InitFn,
|
||||
comptime tick: Callbacks.TickFn,
|
||||
comptime deinit: Callbacks.DeinitFn,
|
||||
comptime debug: Callbacks.DebugFn,
|
||||
comptime assets: Callbacks.LoadAssetsFn,
|
||||
comptime debug: Callbacks.DebugFn
|
||||
) void {
|
||||
const builtin = @import("builtin");
|
||||
if (builtin.output_mode != .Lib) {
|
||||
@ -512,9 +503,6 @@ pub fn exportCallbacksIfNeeded(
|
||||
@export(init, .{ .name = "init", .linkage = .strong });
|
||||
@export(tick, .{ .name = "tick", .linkage = .strong });
|
||||
@export(deinit, .{ .name = "deinit", .linkage = .strong });
|
||||
if (build_options.asset_hot_reload) {
|
||||
@export(assets, .{ .name = "loadAssets", .linkage = .strong });
|
||||
}
|
||||
if (build_options.has_imgui) {
|
||||
@export(debug, .{ .name = "debug", .linkage = .strong });
|
||||
}
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Game = @import("game");
|
||||
const Engine = @import("engine");
|
||||
const Lib = Engine.Lib;
|
||||
const AssetBundle = Engine.AssetBundle;
|
||||
|
||||
fn readFileAll(gpa: std.mem.Allocator, dir: std.fs.Dir, sub_path: []const u8) ![]u8 {
|
||||
const f = try dir.openFile(sub_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 main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
|
||||
if (args.len != 3) {
|
||||
std.debug.print("Usage: ./asset-bundler <asset-dir> <output>", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
const cwd = std.fs.cwd();
|
||||
const asset_dir_path = args[1];
|
||||
const output_path = args[2];
|
||||
|
||||
const asset_dir = try cwd.openDir(asset_dir_path, .{ .iterate = true });
|
||||
|
||||
var loader: Lib.Asset.Loader = .init(allocator, asset_dir);
|
||||
defer loader.deinit();
|
||||
Game.loadAssets(&loader);
|
||||
|
||||
var bundle = AssetBundle.init();
|
||||
defer bundle.deinit(allocator);
|
||||
|
||||
for (loader.assets.items) |asset_desc| {
|
||||
const id = asset_desc.id;
|
||||
switch (asset_desc.kind) {
|
||||
.font => |path| {
|
||||
const font_data = try readFileAll(allocator, asset_dir, path);
|
||||
defer allocator.free(font_data);
|
||||
|
||||
// TODO:
|
||||
try bundle.appendFont(allocator, id, font_data, .{ .format = .ttf });
|
||||
},
|
||||
.sound => |path| {
|
||||
const sound_data = try readFileAll(allocator, asset_dir, path);
|
||||
defer allocator.free(sound_data);
|
||||
|
||||
try bundle.appendSound(allocator, id, sound_data, .{
|
||||
// TODO:
|
||||
.format = .vorbis,
|
||||
.playback_style = .decode_once
|
||||
});
|
||||
},
|
||||
.texture => |opts| {
|
||||
const texture_data = try readFileAll(allocator, asset_dir, opts.path);
|
||||
defer allocator.free(texture_data);
|
||||
|
||||
try bundle.appendTexture(allocator, id, texture_data, opts.info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const output_file = try std.fs.cwd().createFile(output_path, .{ });
|
||||
defer output_file.close();
|
||||
|
||||
var buffer: [4096 * 4]u8 = undefined;
|
||||
var writer = output_file.writer(&buffer);
|
||||
|
||||
try bundle.write(&writer.interface);
|
||||
try writer.interface.flush();
|
||||
}
|
||||
61
src/game.zig
61
src/game.zig
@ -9,20 +9,6 @@ const rgb = Engine.Math.rgb;
|
||||
|
||||
const Game = @This();
|
||||
|
||||
const FontName = enum {
|
||||
regular,
|
||||
bold,
|
||||
italic,
|
||||
};
|
||||
|
||||
const AssetId = enum {
|
||||
font_regular,
|
||||
font_bold,
|
||||
font_italic,
|
||||
sound_wood,
|
||||
texture_icon,
|
||||
};
|
||||
|
||||
const State = struct {
|
||||
gpa: Allocator,
|
||||
player: Vec2,
|
||||
@ -40,25 +26,6 @@ pub fn init(opts: *const Engine.Init) callconv(.c) *anyopaque {
|
||||
return state;
|
||||
}
|
||||
|
||||
fn getFont(assets: Engine.Asset.Registry, font_name: FontName) FontId {
|
||||
const asset_id = switch (font_name) {
|
||||
.regular => AssetId.font_regular,
|
||||
.bold => AssetId.font_bold,
|
||||
.italic => AssetId.font_italic,
|
||||
};
|
||||
return assets.getFont(@intFromEnum(asset_id));
|
||||
}
|
||||
|
||||
pub fn loadAssets(assets: *Engine.Asset.Loader) callconv(.c) void {
|
||||
assets.addFont(@intFromEnum(AssetId.font_regular), "roboto-font/Roboto-Regular.ttf");
|
||||
assets.addFont(@intFromEnum(AssetId.font_bold), "roboto-font/Roboto-Bold.ttf");
|
||||
assets.addFont(@intFromEnum(AssetId.font_italic), "roboto-font/Roboto-Italic.ttf");
|
||||
|
||||
assets.addSound(@intFromEnum(AssetId.sound_wood), "wood01.ogg");
|
||||
|
||||
assets.addTexture(@intFromEnum(AssetId.texture_icon), "icon.png");
|
||||
}
|
||||
|
||||
pub fn deinit(state_ptr: *anyopaque) callconv(.c) void {
|
||||
const state: *State = @alignCast(@ptrCast(state_ptr));
|
||||
const gpa = state.gpa;
|
||||
@ -89,10 +56,10 @@ pub fn tick(state_ptr: *anyopaque, frame: *Engine.Frame) callconv(.c) void {
|
||||
dir = dir.normalized();
|
||||
|
||||
if (dir.x != 0 or dir.y != 0) {
|
||||
frame.playAudio(.{
|
||||
.id = frame.assets.getSound(@intFromEnum(AssetId.sound_wood)),
|
||||
.volume = 0.1
|
||||
});
|
||||
// frame.playAudio(.{
|
||||
// .id = frame.assets.getSound(@intFromEnum(AssetId.sound_wood)),
|
||||
// .volume = 0.1
|
||||
// });
|
||||
}
|
||||
|
||||
state.player = state.player.add(dir.multiplyScalar(50 * dt));
|
||||
@ -102,7 +69,7 @@ pub fn tick(state_ptr: *anyopaque, frame: *Engine.Frame) callconv(.c) void {
|
||||
.color = rgb(20, 20, 20)
|
||||
});
|
||||
|
||||
const icon = frame.assets.getTexture(@intFromEnum(AssetId.texture_icon));
|
||||
// const icon = frame.assets.getTexture(@intFromEnum(AssetId.texture_icon));
|
||||
|
||||
const size = Vec2.init(20, 20);
|
||||
frame.drawRectangle(.{
|
||||
@ -111,10 +78,10 @@ pub fn tick(state_ptr: *anyopaque, frame: *Engine.Frame) callconv(.c) void {
|
||||
.size = size
|
||||
},
|
||||
.color = rgb(200, 2, 200),
|
||||
.sprite = .{
|
||||
.texture = icon,
|
||||
.uv = .unit
|
||||
}
|
||||
// .sprite = .{
|
||||
// .texture = icon,
|
||||
// .uv = .unit
|
||||
// }
|
||||
});
|
||||
if (dir.x != 0 or dir.y != 0) {
|
||||
frame.drawRectanglOutline(state.player.sub(size.divideScalar(2)), size, rgb(20, 20, 20), 3);
|
||||
@ -131,10 +98,10 @@ pub fn tick(state_ptr: *anyopaque, frame: *Engine.Frame) callconv(.c) void {
|
||||
});
|
||||
}
|
||||
|
||||
frame.drawText(state.player, "Player", .{
|
||||
.font = getFont(frame.assets, .regular),
|
||||
.size = 10
|
||||
});
|
||||
// frame.drawText(state.player, "Player", .{
|
||||
// .font = getFont(frame.assets, .regular),
|
||||
// .size = 10
|
||||
// });
|
||||
}
|
||||
|
||||
pub fn debug(state_ptr: *anyopaque, imgui: *Engine.ImGui) callconv(.c) void {
|
||||
@ -144,5 +111,5 @@ pub fn debug(state_ptr: *anyopaque, imgui: *Engine.ImGui) callconv(.c) void {
|
||||
}
|
||||
|
||||
comptime {
|
||||
Engine.exportCallbacksIfNeeded(init, tick, deinit, debug, loadAssets);
|
||||
Engine.exportCallbacksIfNeeded(init, tick, deinit, debug);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user