examples/core: building without ECS

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-09-22 13:25:49 -07:00 committed by Emi Gutekanst
parent 2a13c07d9e
commit 0e12857154
35 changed files with 1365 additions and 4176 deletions

368
src/module.zig Normal file
View file

@ -0,0 +1,368 @@
const std = @import("std");
/// Unique identifier for every module in the program, including those only known at runtime.
pub const ModuleID = u32;
/// Unique identifier for a function within a single module, including those only known at runtime.
pub const ModuleFunctionID = u16;
/// Unique identifier for a function within a module, including those only known at runtime.
pub const FunctionID = struct { module_id: ModuleID, fn_id: ModuleFunctionID };
pub fn Call(module_tag_or_type: anytype, fn_name_tag: anytype) type {
return struct {
pub const IsMachCall = void;
pub const module_name = module_tag_or_type;
pub const fn_name = fn_name_tag;
id: FunctionID,
};
}
pub const Runner = struct {
pub const IsMachRunner = void;
ctx: *anyopaque,
_run: *const fn (ctx: *anyopaque, fn_id: FunctionID) void,
pub fn run(r: *const @This(), fn_id: FunctionID) void {
r._run(r.ctx, fn_id);
}
};
pub fn Modules(module_lists: anytype) type {
inline for (moduleTuple(module_lists)) |module| {
validate(module);
}
return struct {
/// All modules
pub const modules = moduleTuple(module_lists);
/// Enum describing every module name compiled into the program.
pub const ModuleName = NameEnum(modules);
mods: ModulesByName(modules),
/// Enum describing all declarations for a given comptime-known module.
fn ModuleFunctionName(comptime module_name: ModuleName) type {
const module = @field(ModuleTypesByName(modules){}, @tagName(module_name));
validate(module);
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
var i: u32 = 0;
inline for (module.mach_systems) |fn_tag| {
// TODO: verify decls are Fn or mach.schedule() decl
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = @tagName(fn_tag), .value = i }};
i += 1;
}
return @Type(.{
.Enum = .{
.tag_type = if (enum_fields.len > 0) std.math.IntFittingRange(0, enum_fields.len - 1) else u0,
.fields = enum_fields,
.decls = &[_]std.builtin.Type.Declaration{},
.is_exhaustive = true,
},
});
}
pub fn init(allocator: std.mem.Allocator) @This() {
var m: @This() = .{
.mods = undefined,
};
inline for (@typeInfo(@TypeOf(m.mods)).Struct.fields) |field| {
// TODO(objects): module-state-init
var mod: @TypeOf(@field(m.mods, field.name)) = undefined;
inline for (@typeInfo(@TypeOf(mod)).Struct.fields) |mod_field| {
if (@typeInfo(mod_field.type) == .Struct and @hasDecl(mod_field.type, "IsMachObjects")) {
@field(mod, mod_field.name).internal = .{
.allocator = allocator,
};
}
}
@field(m.mods, field.name) = mod;
}
return m;
}
pub fn deinit(m: *@This(), allocator: std.mem.Allocator) void {
// TODO
_ = m;
_ = allocator;
}
pub fn Module(module_tag_or_type: anytype) type {
const module_name: ModuleName = blk: {
if (@typeInfo(@TypeOf(module_tag_or_type)) == .EnumLiteral or @typeInfo(@TypeOf(module_tag_or_type)) == .Enum) break :blk @as(ModuleName, module_tag_or_type);
validate(module_tag_or_type);
break :blk module_tag_or_type.mach_module;
};
const module = @field(ModuleTypesByName(modules){}, @tagName(module_name));
validate(module);
return struct {
mods: *ModulesByName(modules),
modules: *Modules(module_lists),
pub const mod_name: ModuleName = module_name;
pub fn getFunction(fn_name: ModuleFunctionName(mod_name)) FunctionID {
return .{
.module_id = @intFromEnum(mod_name),
.fn_id = @intFromEnum(fn_name),
};
}
pub fn run(
m: *const @This(),
comptime fn_name: ModuleFunctionName(module_name),
) void {
const debug_name = @tagName(module_name) ++ "." ++ @tagName(fn_name);
const f = @field(module, @tagName(fn_name));
const F = @TypeOf(f);
if (@typeInfo(F) == .Struct and @typeInfo(F).Struct.is_tuple) {
// Run a list of functions instead of a single function
// TODO: verify this is a mach.schedule() decl
if (module_name != .app) @compileLog(module_name);
inline for (f) |schedule_entry| {
// TODO: unify with Modules(modules).get(M)
const callMod: Module(schedule_entry.@"0") = .{ .mods = m.mods, .modules = m.modules };
const callFn = @as(ModuleFunctionName(@TypeOf(callMod).mod_name), schedule_entry.@"1");
callMod.run(callFn);
}
return;
}
// Inject arguments
var args: std.meta.ArgsTuple(F) = undefined;
outer: inline for (@typeInfo(std.meta.ArgsTuple(F)).Struct.fields) |arg| {
if (@typeInfo(arg.type) == .Pointer and
@typeInfo(std.meta.Child(arg.type)) == .Struct and
comptime isValid(std.meta.Child(arg.type)))
{
// *Module argument
// TODO: better error if @field(m.mods, ...) fails ("module not registered")
@field(args, arg.name) = &@field(m.mods, @tagName(std.meta.Child(arg.type).mach_module));
continue :outer;
}
if (@typeInfo(arg.type) == .Struct and @hasDecl(arg.type, "IsMachCall")) {
const machCall = arg.type;
@field(args, arg.name) = .{
.id = Module(@field(machCall, "module_name")).getFunction(@field(machCall, "fn_name")),
};
continue :outer;
}
if (@typeInfo(arg.type) == .Struct and @hasDecl(arg.type, "IsMachRunner")) {
@field(args, arg.name) = Runner{
.ctx = m.modules,
._run = (struct {
pub fn run(ctx: *anyopaque, fn_id: FunctionID) void {
const modules2: *Modules(module_lists) = @ptrCast(@alignCast(ctx));
modules2.callDynamic(fn_id);
}
}).run,
};
continue :outer;
}
@compileError("mach: function " ++ debug_name ++ " has an invalid argument(" ++ arg.name ++ ") type: " ++ @typeName(arg.type));
}
const Ret = @typeInfo(F).Fn.return_type orelse void;
switch (@typeInfo(Ret)) {
// TODO: define error handling of runnable functions
.ErrorUnion => @call(.auto, f, args) catch |err| std.debug.panic("error: {s}", .{@errorName(err)}),
else => @call(.auto, f, args),
}
}
};
}
pub fn get(m: *@This(), module_tag_or_type: anytype) Module(module_tag_or_type) {
return .{ .mods = &m.mods, .modules = m };
}
pub fn callDynamic(m: *@This(), f: FunctionID) void {
const module_name: ModuleName = @enumFromInt(f.module_id);
switch (module_name) {
inline else => |mod_name| {
const module_fn_name: ModuleFunctionName(mod_name) = @enumFromInt(f.fn_id);
const mod: Module(mod_name) = .{ .mods = &m.mods, .modules = m };
const module = @field(ModuleTypesByName(modules){}, @tagName(mod_name));
validate(module);
switch (module_fn_name) {
inline else => |fn_name| mod.run(fn_name),
}
},
}
}
};
}
/// Validates that the given struct is a Mach module.
fn validate(comptime module: anytype) void {
if (!@hasDecl(module, "mach_module")) @compileError("mach: invalid module, missing `pub const mach_module = .foo_name;` declaration: " ++ @typeName(@TypeOf(module)));
if (@typeInfo(@TypeOf(module.mach_module)) != .EnumLiteral) @compileError("mach: invalid module, expected `pub const mach_module = .foo_name;` declaration, found: " ++ @typeName(@TypeOf(module.mach_module)));
}
fn isValid(comptime module: anytype) bool {
if (!@hasDecl(module, "mach_module")) return false;
if (@typeInfo(@TypeOf(module.mach_module)) != .EnumLiteral) return false;
return true;
}
/// Given a tuple of Mach module structs, returns an enum which has every possible comptime-known
/// module name.
fn NameEnum(comptime mods: anytype) type {
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
for (mods, 0..) |module, i| {
validate(module);
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = @tagName(module.mach_module), .value = i }};
}
return @Type(.{
.Enum = .{
.tag_type = std.math.IntFittingRange(0, enum_fields.len - 1),
.fields = enum_fields,
.decls = &[_]std.builtin.Type.Declaration{},
.is_exhaustive = true,
},
});
}
/// Given a tuple of module structs or module struct tuples:
///
/// ```
/// .{
/// .{ Baz, .{ Bar, Foo, .{ Fam } }, Bar },
/// Foo,
/// Bam,
/// .{ Foo, Bam },
/// }
/// ```
///
/// Returns a flat tuple, deduplicated:
///
/// .{ Baz, Bar, Foo, Fam, Bar, Bam }
///
fn moduleTuple(comptime tuple: anytype) ModuleTuple(tuple) {
return ModuleTuple(tuple){};
}
/// Type-returning variant of merge()
fn ModuleTuple(comptime tuple: anytype) type {
if (@typeInfo(@TypeOf(tuple)) != .Struct or !@typeInfo(@TypeOf(tuple)).Struct.is_tuple) {
@compileError("Expected to find a tuple, found: " ++ @typeName(@TypeOf(tuple)));
}
var tuple_fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
loop: inline for (tuple) |elem| {
if (@typeInfo(@TypeOf(elem)) == .Type and @typeInfo(elem) == .Struct) {
// Struct type
validate(elem);
for (tuple_fields) |field| if (@as(*const type, @ptrCast(field.default_value.?)).* == elem)
continue :loop;
var num_buf: [128]u8 = undefined;
tuple_fields = tuple_fields ++ [_]std.builtin.Type.StructField{.{
.name = std.fmt.bufPrintZ(&num_buf, "{d}", .{tuple_fields.len}) catch unreachable,
.type = type,
.default_value = &elem,
.is_comptime = false,
.alignment = if (@sizeOf(elem) > 0) @alignOf(elem) else 0,
}};
} else if (@typeInfo(@TypeOf(elem)) == .Struct and @typeInfo(@TypeOf(elem)).Struct.is_tuple) {
// Nested tuple
inline for (moduleTuple(elem)) |nested| {
validate(nested);
for (tuple_fields) |field| if (@as(*const type, @ptrCast(field.default_value.?)).* == nested)
continue :loop;
var num_buf: [128]u8 = undefined;
tuple_fields = tuple_fields ++ [_]std.builtin.Type.StructField{.{
.name = std.fmt.bufPrintZ(&num_buf, "{d}", .{tuple_fields.len}) catch unreachable,
.type = type,
.default_value = &nested,
.is_comptime = false,
.alignment = if (@sizeOf(nested) > 0) @alignOf(nested) else 0,
}};
}
} else {
@compileError("Expected to find a tuple or struct type, found: " ++ @typeName(@TypeOf(elem)));
}
}
return @Type(.{
.Struct = .{
.is_tuple = true,
.layout = .auto,
.decls = &.{},
.fields = tuple_fields,
},
});
}
/// Given .{Foo, Bar, Baz} Mach modules, returns .{.foo = Foo, .bar = Bar, .baz = Baz} with field
/// names corresponding to each module's `pub const mach_module = .foo;` name.
fn ModuleTypesByName(comptime modules: anytype) type {
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
for (modules) |M| {
fields = fields ++ [_]std.builtin.Type.StructField{.{
.name = @tagName(M.mach_module),
.type = type,
.default_value = &M,
.is_comptime = true,
.alignment = @alignOf(type),
}};
}
return @Type(.{
.Struct = .{
.layout = .auto,
.is_tuple = false,
.fields = fields,
.decls = &[_]std.builtin.Type.Declaration{},
},
});
}
/// Given .{Foo, Bar, Baz} Mach modules, returns .{.foo: Foo = undefined, .bar: Bar = undefined, .baz: Baz = undefined}
/// with field names corresponding to each module's `pub const mach_module = .foo;` name, and each Foo type.
fn ModulesByName(comptime modules: anytype) type {
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
for (modules) |M| {
fields = fields ++ [_]std.builtin.Type.StructField{.{
.name = @tagName(M.mach_module),
.type = M,
.default_value = &@as(M, undefined),
.is_comptime = false,
.alignment = @alignOf(M),
}};
}
return @Type(.{
.Struct = .{
.layout = .auto,
.is_tuple = false,
.fields = fields,
.decls = &[_]std.builtin.Type.Declaration{},
},
});
}
// // Returns true if a and b are both functions and are equal
// fn isFnAndEqual(comptime a: anytype, comptime b: anytype) bool {
// const A = @TypeOf(a);
// const B = @TypeOf(b);
// if (@typeInfo(A) != .Fn or @typeInfo(B) != .Fn) return false;
// const x = @typeInfo(A).Fn;
// const y = @typeInfo(B).Fn;
// if (x.calling_convention != y.calling_convention) return false;
// if (x.is_generic != y.is_generic) return false;
// if (x.is_var_args != y.is_var_args) return false;
// if ((x.return_type != null) != (y.return_type != null)) return false;
// if (x.return_type != null) if (x.return_type.? != y.return_type.?) return false;
// if (x.params.len != y.params.len) return false;
// if (x.params.ptr != y.params.ptr) return false;
// if (A != B) return false;
// if (a != b) return false;
// return true;
// }