diff --git a/examples/glyphs/main.zig b/examples/glyphs/main.zig index b5764514..f45e71d7 100644 --- a/examples/glyphs/main.zig +++ b/examples/glyphs/main.zig @@ -3,8 +3,7 @@ const mach = @import("mach"); // The global list of Mach modules registered for use in our application. pub const modules = .{ mach.Core, - mach.gfx.Sprite, - mach.gfx.SpritePipeline, + mach.gfx.sprite_modules, @import("App.zig"), @import("Glyphs.zig"), }; diff --git a/examples/sprite/main.zig b/examples/sprite/main.zig index acc8509d..7732839f 100644 --- a/examples/sprite/main.zig +++ b/examples/sprite/main.zig @@ -3,8 +3,7 @@ const mach = @import("mach"); // The global list of Mach modules registered for use in our application. pub const modules = .{ mach.Core, - mach.gfx.Sprite, - mach.gfx.SpritePipeline, + mach.gfx.sprite_modules, @import("App.zig"), }; diff --git a/examples/text/main.zig b/examples/text/main.zig index c5f962f8..6f7407d4 100644 --- a/examples/text/main.zig +++ b/examples/text/main.zig @@ -3,9 +3,7 @@ const mach = @import("mach"); // The global list of Mach modules registered for use in our application. pub const modules = .{ mach.Core, - mach.gfx.Text, - mach.gfx.TextPipeline, - mach.gfx.TextStyle, + mach.gfx.text_modules, @import("App.zig"), }; diff --git a/src/gfx/main.zig b/src/gfx/main.zig index 8653626b..97a8d56f 100644 --- a/src/gfx/main.zig +++ b/src/gfx/main.zig @@ -8,6 +8,15 @@ pub const Text = @import("Text.zig"); pub const TextPipeline = @import("TextPipeline.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 pub const Font = @import("font/main.zig").Font; pub const TextRun = @import("font/main.zig").TextRun; diff --git a/src/main.zig b/src/main.zig index fb270cc0..1554151b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -24,7 +24,10 @@ pub const modules = blk: { if (!@hasDecl(@import("root"), "modules")) { @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 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 EventID = @import("module/main.zig").EventID; 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: /// diff --git a/src/module/entities.zig b/src/module/entities.zig index 47a01ca9..d61540d8 100644 --- a/src/module/entities.zig +++ b/src/module/entities.zig @@ -7,6 +7,8 @@ const query_mod = @import("query.zig"); const Archetype = @import("Archetype.zig"); const StringTable = @import("StringTable.zig"); 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. pub const EntityID = u64; @@ -699,7 +701,10 @@ pub fn ArchetypeIterator(comptime component_types_by_name: anytype) type { } 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 @@ -749,7 +754,8 @@ test "example" { const Rotation = struct { degrees: f32 }; - const component_types_by_name = ComponentTypesByName(.{ + const component_types_by_name = ComponentTypesByName(merge(.{ + builtin_modules, struct { pub const name = .game; pub const components = .{ @@ -758,7 +764,7 @@ test "example" { .rotation = .{ .type = Rotation }, }; }, - }){}; + })){}; //------------------------------------------------------------------------- // Create a world. diff --git a/src/module/main.zig b/src/module/main.zig index 5c0f7998..7b4497f6 100644 --- a/src/module/main.zig +++ b/src/module/main.zig @@ -10,6 +10,19 @@ pub const Modules = @import("module.zig").Modules; pub const ModuleID = @import("module.zig").ModuleID; pub const EventID = @import("module.zig").EventID; 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 { std.testing.refAllDeclsRecursive(@This()); @@ -23,7 +36,7 @@ test "entities DB" { const allocator = testing.allocator; const root = struct { - pub const modules = .{ Renderer, Physics }; + pub const modules = merge(.{ builtin_modules, Renderer, Physics }); const Physics = struct { pointer: u8, diff --git a/src/module/module.zig b/src/module/module.zig index c8cdcdba..903b5cae 100644 --- a/src/module/module.zig +++ b/src/module/module.zig @@ -102,6 +102,78 @@ pub const AnyEvent = struct { 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. pub fn Modules(comptime modules: anytype) type { // 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{}; for (modules) |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. // This can be the case if e.g. a Mod() parameter is specified, but that module is // 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); } @@ -914,9 +991,6 @@ fn validateEvents(comptime error_prefix: anytype, comptime events: anytype) void /// /// ``` /// struct { -/// builtin: struct { -/// id: @TypeOf() = .{ .type = EntityID, .description = "Entity ID" }, -/// }, /// physics: struct { /// location: @TypeOf() = .{ .type = Vec3, .description = null }, /// rotation: @TypeOf() = .{ .type = Vec2, .description = "rotation component" }, @@ -938,23 +1012,6 @@ pub fn ComponentTypesByName(comptime modules: anytype) type { .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(.{ .Struct = .{ .layout = .Auto, diff --git a/src/module/query.zig b/src/module/query.zig index d3d5964f..60eeec55 100644 --- a/src/module/query.zig +++ b/src/module/query.zig @@ -37,7 +37,7 @@ pub fn Query(comptime component_types_by_name: anytype) type { const namespaces = std.meta.fields(Namespace); var fields: [namespaces.len]std.builtin.Type.UnionField = undefined; for (namespaces, 0..) |namespace, i| { - const ns = std.meta.stringToEnum(Namespace, namespace.name).?; + const ns = stringToEnum(Namespace, namespace.name).?; fields[i] = .{ .name = namespace.name, .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" { const Location = struct { x: f32 = 0,