AssetManager with HashMaps has disappering keys

Hey everyone,

I am trying to build a simple asset loader for my games. I’ve tried loading some assets from a folder and automatically fetch the basename of a file and use it as the key for my Hashmap entry.

Whenever I am trying to access my data via key I get a null reference error.
I’ve checked the key entries and they’re just empty strings.
Do I have to allocate memory for my keys and later solve free them or is there maybe a better solution?
I came across this issue already twice I think but I couldn’t come up with an elegant solution to my problem.

const std = @import("std");
const rl = @import("raylib");

const Self = @This();

allocator: std.mem.Allocator,
fonts: std.StringArrayHashMapUnmanaged(rl.Font),
shaders: std.StringArrayHashMapUnmanaged(rl.Shader),
textures: std.StringArrayHashMapUnmanaged(rl.Texture2D),
sounds: std.StringArrayHashMapUnmanaged(rl.Sound),
models: std.StringArrayHashMapUnmanaged(rl.Model),

// TODO(MO): does this need to be a heap allocated struct?
pub fn createInit(allocator: std.mem.Allocator) !*Self {
    const self = try allocator.create(Self);

    const fonts = std.StringArrayHashMapUnmanaged(rl.Font){};
    const shaders = std.StringArrayHashMapUnmanaged(rl.Shader){};
    const textures = std.StringArrayHashMapUnmanaged(rl.Texture2D){};
    const sounds = std.StringArrayHashMapUnmanaged(rl.Sound){};
    const models = std.StringArrayHashMapUnmanaged(rl.Model){};

    self.* = .{
        .allocator = allocator,
        .fonts = fonts,
        .shaders = shaders,
        .textures = textures,
        .sounds = sounds,
        .models = models,
    };

    return self;
}

pub fn deinit(self: *Self) void {
    self.fonts.deinit(self.allocator);
    self.models.deinit(self.allocator);
    self.shaders.deinit(self.allocator);
    self.textures.deinit(self.allocator);
    self.textures.deinit(self.allocator);
    self.sounds.deinit(self.allocator);

    self.allocator.destroy(self);
}

pub fn loadFromFolder(self: *Self, folder: []const u8) !void {
    const dir = try std.fs.openDirAbsolute(folder, .{ .iterate = true });
    var walker = try dir.walk(self.allocator);
    defer walker.deinit();

    while (try walker.next()) |entry| {
        // we only care about files
        // NOTE: don't iterate depper!
        if (entry.kind != .file)
            continue;

        const basename = entry.basename;
        const path = entry.path;

        if (std.mem.endsWith(u8, basename, ".ttf")) {
            const key = basename;
            const font_path = try std.fs.path.joinZ(self.allocator, &[_][]const u8{ folder, path });
            defer self.allocator.free(font_path);

            const font = rl.loadFont(font_path);
            try self.fonts.put(self.allocator, key, font);
        } else if (std.mem.endsWith(u8, basename, ".png")) {
            const key = basename;
            const texture_path = try std.fs.path.joinZ(self.allocator, &[_][]const u8{ folder, path });
            defer self.allocator.free(texture_path);

            const texture = rl.loadTexture(texture_path);
            try self.textures.put(self.allocator, key, texture);
        } else if (std.mem.endsWith(u8, basename, ".ogg")) {
            const key = basename;

            const sound_path = try std.fs.path.joinZ(self.allocator, &[_][]const u8{ folder, path });
            defer self.allocator.free(sound_path);

            const sound = rl.loadSound(sound_path);
            try self.sounds.put(self.allocator, key, sound);
        }
    }
}

I’m assuming that my entry in the loadFromFile function is just stack allocated and gets free’d after leaving the scope.
I just don’t know how I could solve this in a “cleaner” way.
It might also not even be stack allocated but cleaned up after the deinit function for the walker is called which in this case I assume is after the function parent function returns.

Ok, so I do have to allocate and free the keys but I don’t have to write my own generic if there’s one in the std already.
Thank you for the confirmation!

1 Like