module: support merging module lists
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
b37ece1b9a
commit
95c9ae5278
9 changed files with 130 additions and 34 deletions
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue