const std = @import("std");

const Module = struct {
    init: *const fn (std.mem.Allocator) *anyopaque,
    deinit: *const fn (*anyopaque, std.mem.Allocator) void,
    update: *const fn (*anyopaque, std.mem.Allocator) void,
};

const Foo = struct {
    fn init(alloc: std.mem.Allocator) *@This() {
        return alloc.create(@This()) catch @panic("OOM");
    }

    fn deinit(self: *@This(), alloc: std.mem.Allocator) void {
        alloc.destroy(self);
    }

    fn update(self: *@This(), alloc: std.mem.Allocator) void {
        _ = self;
        _ = alloc;

        std.log.debug("Hello, {s}", .{@typeName(@This())});
    }
};

const Bar = struct {
    fn init(alloc: std.mem.Allocator) *@This() {
        return alloc.create(@This()) catch @panic("OOM");
    }

    fn deinit(self: *@This(), alloc: std.mem.Allocator) void {
        alloc.destroy(self);
    }

    fn update(self: *@This(), alloc: std.mem.Allocator) void {
        _ = self;
        _ = alloc;

        std.log.debug("Hello, {s}", .{@typeName(@This())});
    }
};

const Modules = .{
    Foo,
    Bar,
};

pub fn main() !void {
    // comptime var modules: [Modules.len]Module = undefined;
    comptime var modules: [Modules.len]type = undefined;

    inline for (Modules, &modules) |Mod, *mod| {
        mod.* = struct {
            pub fn init(alloc: std.mem.Allocator) void {
            }
        };
        // mod.* = .{
        //     .init = @ptrCast(&Mod.init),
        //     .deinit = @ptrCast(&Mod.deinit),
        //     .update = @ptrCast(&Mod.update),
        // };
    }

    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const alloc = gpa.allocator();

    var states: [Modules.len]*anyopaque = undefined;
    for (modules, &states) |mod, *state| {
        state.* = mod.init(alloc);
    }
    defer for (modules, states) |mod, state| {
        mod.deinit(state, alloc);
    };

    std.log.debug("Performing update...", .{});
    for (modules, states) |mod, state| {
        mod.update(state, alloc);
    }
}
