module: support merging module lists

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-05-04 18:08:04 -07:00
parent b37ece1b9a
commit 95c9ae5278
9 changed files with 130 additions and 34 deletions

View file

@ -3,8 +3,7 @@ const mach = @import("mach");
// The global list of Mach modules registered for use in our application. // The global list of Mach modules registered for use in our application.
pub const modules = .{ pub const modules = .{
mach.Core, mach.Core,
mach.gfx.Sprite, mach.gfx.sprite_modules,
mach.gfx.SpritePipeline,
@import("App.zig"), @import("App.zig"),
@import("Glyphs.zig"), @import("Glyphs.zig"),
}; };

View file

@ -3,8 +3,7 @@ const mach = @import("mach");
// The global list of Mach modules registered for use in our application. // The global list of Mach modules registered for use in our application.
pub const modules = .{ pub const modules = .{
mach.Core, mach.Core,
mach.gfx.Sprite, mach.gfx.sprite_modules,
mach.gfx.SpritePipeline,
@import("App.zig"), @import("App.zig"),
}; };

View file

@ -3,9 +3,7 @@ const mach = @import("mach");
// The global list of Mach modules registered for use in our application. // The global list of Mach modules registered for use in our application.
pub const modules = .{ pub const modules = .{
mach.Core, mach.Core,
mach.gfx.Text, mach.gfx.text_modules,
mach.gfx.TextPipeline,
mach.gfx.TextStyle,
@import("App.zig"), @import("App.zig"),
}; };

View file

@ -8,6 +8,15 @@ pub const Text = @import("Text.zig");
pub const TextPipeline = @import("TextPipeline.zig"); pub const TextPipeline = @import("TextPipeline.zig");
pub const TextStyle = @import("TextStyle.zig"); pub const TextStyle = @import("TextStyle.zig");
/// All Sprite rendering modules
pub const sprite_modules = .{ Sprite, SpritePipeline };
/// All Text rendering modules
pub const text_modules = .{ Text, TextPipeline, TextStyle };
/// All graphics modules
pub const modules = .{ sprite_modules, text_modules };
// Fonts // Fonts
pub const Font = @import("font/main.zig").Font; pub const Font = @import("font/main.zig").Font;
pub const TextRun = @import("font/main.zig").TextRun; pub const TextRun = @import("font/main.zig").TextRun;

View file

@ -24,7 +24,10 @@ pub const modules = blk: {
if (!@hasDecl(@import("root"), "modules")) { if (!@hasDecl(@import("root"), "modules")) {
@compileError("expected `pub const modules = .{};` in root file"); @compileError("expected `pub const modules = .{};` in root file");
} }
break :blk @import("root").modules; break :blk merge(.{
builtin_modules,
@import("root").modules,
});
}; };
pub const ModSet = @import("module/main.zig").ModSet; pub const ModSet = @import("module/main.zig").ModSet;
pub const Modules = @import("module/main.zig").Modules(modules); pub const Modules = @import("module/main.zig").Modules(modules);
@ -35,6 +38,9 @@ pub const Archetype = @import("module/main.zig").Archetype;
pub const ModuleID = @import("module/main.zig").ModuleID; pub const ModuleID = @import("module/main.zig").ModuleID;
pub const EventID = @import("module/main.zig").EventID; pub const EventID = @import("module/main.zig").EventID;
pub const AnyEvent = @import("module/main.zig").AnyEvent; pub const AnyEvent = @import("module/main.zig").AnyEvent;
pub const merge = @import("module/main.zig").merge;
pub const builtin_modules = @import("module/main.zig").builtin_modules;
pub const EntityModule = @import("module/main.zig").EntityModule;
/// To use experimental sysgpu graphics API, you can write this in your main.zig: /// To use experimental sysgpu graphics API, you can write this in your main.zig:
/// ///

View file

@ -7,6 +7,8 @@ const query_mod = @import("query.zig");
const Archetype = @import("Archetype.zig"); const Archetype = @import("Archetype.zig");
const StringTable = @import("StringTable.zig"); const StringTable = @import("StringTable.zig");
const ComponentTypesByName = @import("module.zig").ComponentTypesByName; const ComponentTypesByName = @import("module.zig").ComponentTypesByName;
const merge = @import("main.zig").merge;
const builtin_modules = @import("main.zig").builtin_modules;
/// An entity ID uniquely identifies an entity globally within an Entities set. /// An entity ID uniquely identifies an entity globally within an Entities set.
pub const EntityID = u64; pub const EntityID = u64;
@ -699,7 +701,10 @@ pub fn ArchetypeIterator(comptime component_types_by_name: anytype) type {
} }
test { test {
std.testing.refAllDeclsRecursive(Entities(.{})); const modules = ComponentTypesByName(merge(.{
builtin_modules,
})){};
std.testing.refAllDeclsRecursive(Entities(modules));
} }
// TODO: require "one big registration of components" even when using dynamic API? Would alleviate // TODO: require "one big registration of components" even when using dynamic API? Would alleviate
@ -749,7 +754,8 @@ test "example" {
const Rotation = struct { degrees: f32 }; const Rotation = struct { degrees: f32 };
const component_types_by_name = ComponentTypesByName(.{ const component_types_by_name = ComponentTypesByName(merge(.{
builtin_modules,
struct { struct {
pub const name = .game; pub const name = .game;
pub const components = .{ pub const components = .{
@ -758,7 +764,7 @@ test "example" {
.rotation = .{ .type = Rotation }, .rotation = .{ .type = Rotation },
}; };
}, },
}){}; })){};
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Create a world. // Create a world.

View file

@ -10,6 +10,19 @@ pub const Modules = @import("module.zig").Modules;
pub const ModuleID = @import("module.zig").ModuleID; pub const ModuleID = @import("module.zig").ModuleID;
pub const EventID = @import("module.zig").EventID; pub const EventID = @import("module.zig").EventID;
pub const AnyEvent = @import("module.zig").AnyEvent; pub const AnyEvent = @import("module.zig").AnyEvent;
pub const Merge = @import("module.zig").Merge;
pub const merge = @import("module.zig").merge;
pub const builtin_modules = .{EntityModule};
/// Builtin .entity module
pub const EntityModule = struct {
pub const name = .entity;
pub const components = .{
.id = .{ .type = EntityID, .description = "Entity ID" },
};
};
test { test {
std.testing.refAllDeclsRecursive(@This()); std.testing.refAllDeclsRecursive(@This());
@ -23,7 +36,7 @@ test "entities DB" {
const allocator = testing.allocator; const allocator = testing.allocator;
const root = struct { const root = struct {
pub const modules = .{ Renderer, Physics }; pub const modules = merge(.{ builtin_modules, Renderer, Physics });
const Physics = struct { const Physics = struct {
pointer: u8, pointer: u8,

View file

@ -102,6 +102,78 @@ pub const AnyEvent = struct {
event_id: EventID, event_id: EventID,
}; };
/// Type-returning variant of merge()
pub fn Merge(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| {
@setEvalBranchQuota(10_000);
if (@typeInfo(@TypeOf(elem)) == .Type and @typeInfo(elem) == .Struct) {
// Struct type
validateModule(elem, false);
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 (Merge(elem){}) |Nested| {
validateModule(Nested, false);
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 a tuple of module structs or module struct tuples:
///
/// ```
/// .{
/// .{ Baz, .{ Bar, Foo, .{ Fam } }, Bar },
/// Foo,
/// Bam,
/// .{ Foo, Bam },
/// }
/// ```
///
/// Returns a single tuple type with the struct types deduplicated:
///
/// .{ Baz, Bar, Foo, Fam, Bar, Bam }
///
pub fn merge(comptime tuple: anytype) Merge(tuple) {
return Merge(tuple){};
}
/// Manages comptime .{A, B, C} modules and runtime modules. /// Manages comptime .{A, B, C} modules and runtime modules.
pub fn Modules(comptime modules: anytype) type { pub fn Modules(comptime modules: anytype) type {
// Verify that each module is valid. // Verify that each module is valid.
@ -462,7 +534,7 @@ pub fn Modules(comptime modules: anytype) type {
}; };
} }
pub fn ModsByName(comptime modules: anytype) type { fn ModsByName(comptime modules: anytype) type {
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{}; var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
for (modules) |M| { for (modules) |M| {
const ModT = ModSet(modules).Mod(M); const ModT = ModSet(modules).Mod(M);
@ -647,6 +719,11 @@ inline fn injectArgs(comptime Function: type, comptime Injectable: type, injecta
// Argument is declared as injectable, but we do not have a value to inject for it. // Argument is declared as injectable, but we do not have a value to inject for it.
// This can be the case if e.g. a Mod() parameter is specified, but that module is // This can be the case if e.g. a Mod() parameter is specified, but that module is
// not registered. // not registered.
//
// TODO: we could make this error message less verbose, currently it reads e.g.
//
// src/module/module.zig:736:13: error: mach: cannot inject argument of type: *module.module.ModSet(.{Core, gfx.Sprite, gfx.SpritePipeline, App, Glyphs}).Mod(Core) - is it registered in your program's top-level `pub const modules = .{};`? used by mach_core.start
//
@compileError("mach: cannot inject argument of type: " ++ @typeName(arg.type) ++ " - is it registered in your program's top-level `pub const modules = .{};`? used by " ++ debug_name); @compileError("mach: cannot inject argument of type: " ++ @typeName(arg.type) ++ " - is it registered in your program's top-level `pub const modules = .{};`? used by " ++ debug_name);
} }
@ -914,9 +991,6 @@ fn validateEvents(comptime error_prefix: anytype, comptime events: anytype) void
/// ///
/// ``` /// ```
/// struct { /// struct {
/// builtin: struct {
/// id: @TypeOf() = .{ .type = EntityID, .description = "Entity ID" },
/// },
/// physics: struct { /// physics: struct {
/// location: @TypeOf() = .{ .type = Vec3, .description = null }, /// location: @TypeOf() = .{ .type = Vec3, .description = null },
/// rotation: @TypeOf() = .{ .type = Vec2, .description = "rotation component" }, /// rotation: @TypeOf() = .{ .type = Vec2, .description = "rotation component" },
@ -938,23 +1012,6 @@ pub fn ComponentTypesByName(comptime modules: anytype) type {
.alignment = @alignOf(MC), .alignment = @alignOf(MC),
}}; }};
} }
// Builtin components
// TODO: better method of injecting builtin module?
const BuiltinMC = ComponentTypesM(struct {
pub const name = .builtin;
pub const components = .{
.id = .{ .type = EntityID, .description = "Entity ID" },
};
});
fields = fields ++ [_]std.builtin.Type.StructField{.{
.name = "entity",
.type = BuiltinMC,
.default_value = &BuiltinMC{},
.is_comptime = true,
.alignment = @alignOf(BuiltinMC),
}};
return @Type(.{ return @Type(.{
.Struct = .{ .Struct = .{
.layout = .Auto, .layout = .Auto,

View file

@ -37,7 +37,7 @@ pub fn Query(comptime component_types_by_name: anytype) type {
const namespaces = std.meta.fields(Namespace); const namespaces = std.meta.fields(Namespace);
var fields: [namespaces.len]std.builtin.Type.UnionField = undefined; var fields: [namespaces.len]std.builtin.Type.UnionField = undefined;
for (namespaces, 0..) |namespace, i| { for (namespaces, 0..) |namespace, i| {
const ns = std.meta.stringToEnum(Namespace, namespace.name).?; const ns = stringToEnum(Namespace, namespace.name).?;
fields[i] = .{ fields[i] = .{
.name = namespace.name, .name = namespace.name,
.type = ComponentList(ns), .type = ComponentList(ns),
@ -61,6 +61,15 @@ pub fn Query(comptime component_types_by_name: anytype) type {
}; };
} }
// TODO: cannot use std.meta.stringToEnum for some reason; an issue with its internal comptime map and u0 values
pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
inline for (@typeInfo(T).Enum.fields) |enumField| {
if (std.mem.eql(u8, str, enumField.name)) {
return @field(T, enumField.name);
}
}
}
test "query" { test "query" {
const Location = struct { const Location = struct {
x: f32 = 0, x: f32 = 0,