#Advice regarding architecture and comptime

1 messages · Page 1 of 1 (latest)

west fox
#

I've been making a simple little Asteroids clone as a way to learn more about Zig and SDL.

I've been trying to seperate the Platform code (in this case SDL) from the game code so the platform stuff can be used without modification in other projects. I've separated a lot of it, but sprites continue to be an issue. I created a generic EnumMap that lets me map a SpriteID to a path to load the sprite from. My intent was to hand that to the platform and have it then map SpriteIDs to actual textures. It works, the game compiles and runs, but now I have this notion of a SpriteID that a) has to be known by both layers, and b) has to be known at compile time.

I've considered two options to fix this. The first would be to make the Platform code generic and take an ENUM to be used as part of the map, but making the entire thing generic just to cross this one boundary seems excessive. The other would be to hand an allocator in and have it create a slice large enough for all the sprites/textures it will need.

Here is a link to the code https://codeberg.org/scuff3d/the-game-2 the Platform stuff is in main.zig, and the EnumMap is in base.zig

Does one of thee approaches make more sense then the other? Anything I haven't considered. Any advice would be appreciated! 😁

Snippets of code in comments

#
// base.zig
pub fn EnumMap(comptime T: type, comptime E: type) type {
    const info = @typeInfo(T);
    const size = info.@"enum".fields.len;
    return struct {
        map: [size]E,

        /// Initializes all map entries with a default value
        pub fn initWithDefault(default: E) @This() {
            return .{ .map = [_]E{default} ** size };
...
}

//main.zig
const SpriteID = enum { NONE, SHIP_ID, THRUSTER_ID, LASER_ID };
const SpriteMap = base.EnumMap(SpriteID, []const u8);

const Platform = struct {
    const TextureMap = base.EnumMap(SpriteID, *c.SDL_Texture);

    ...
    texture_map: TextureMap,

    pub fn init(default_background_color: RGB, sprite_map: SpriteMap) !Platform {
...
        var texture_map = TextureMap.initWithDefault(undefined);
        const info = @typeInfo(SpriteID);

        inline for (info.@"enum".fields) |field| {
            const sprite_id: SpriteID = @enumFromInt(field.value);
            print("{s}\n", .{sprite_map.get(sprite_id)});
            if (sprite_id == .NONE) {
                continue;
            }
            const surface = c.IMG_Load(sprite_map.get(sprite_id).ptr);
            const texture: *c.SDL_Texture = c.SDL_CreateTextureFromSurface(renderer, surface) orelse return error.TextureLoadFailed;
            _ = c.SDL_SetTextureBlendMode(texture, c.SDL_BLENDMODE_BLEND);
            texture_map.set(sprite_id, texture);
            c.SDL_DestroySurface(surface);
        }
...
}

pub fn main(init: std.process.Init) !void {
    ...

    var sprite_id_map = SpriteMap.initWithDefault("");
    sprite_id_map.set(SpriteID.SHIP_ID, "images/the-ship.png");
    sprite_id_map.set(SpriteID.THRUSTER_ID, "images/the-ship-thrust2.png");
    sprite_id_map.set(SpriteID.LASER_ID, "images/laser.png");

    var pl: Platform = try Platform.init(.{ .b = 30, .g = 30, .r = 30 }, sprite_id_map);
    defer pl.deinit();
...
}
willow crown
#

you dont need to make the entire platform type generic, only the texture map stuff.

the issue is you are storing the texture map in the platform state, if you separate them then you can limit the genericness spreading.

#

if you want to keep the texture map in the platform state without spreading the genericness, then you'd need to add another layer of indirection.

west fox
willow crown
#

btw, your EnumMap looks like a std.enums.EnumArray
there is also a std.enums.EnumMap, the difference is the map has the concept of empty elements and the array does not