module: write components using a struct pattern

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2024-04-05 05:35:58 -07:00 committed by Stephen Gutekanst
parent 17db5498ee
commit 2115f5832a
8 changed files with 45 additions and 62 deletions

View file

@ -17,7 +17,7 @@ spawning: bool = false,
spawn_timer: mach.Timer, spawn_timer: mach.Timer,
pub const components = .{ pub const components = .{
.{ .name = .follower, .type = void }, .follower = .{ .type = void },
}; };
pub const global_events = .{ pub const global_events = .{

View file

@ -21,9 +21,9 @@ pub const name = .renderer;
pub const Mod = mach.Mod(@This()); pub const Mod = mach.Mod(@This());
pub const components = .{ pub const components = .{
.{ .name = .location, .type = Vec3 }, .location = .{ .type = Vec3 },
.{ .name = .rotation, .type = Vec3 }, .rotation = .{ .type = Vec3 },
.{ .name = .scale, .type = f32 }, .scale = .{ .type = f32 },
}; };
pub const global_events = .{ pub const global_events = .{

View file

@ -754,9 +754,9 @@ test "example" {
struct { struct {
pub const name = .game; pub const name = .game;
pub const components = .{ pub const components = .{
.{ .name = .name, .type = []const u8 }, .name = .{ .type = []const u8 },
.{ .name = .location, .type = Location }, .location = .{ .type = Location },
.{ .name = .rotation, .type = Rotation }, .rotation = .{ .type = Rotation },
}; };
}, },
}){}; }){};
@ -858,9 +858,9 @@ test "many entities" {
struct { struct {
pub const name = .game; pub const name = .game;
pub const components = .{ pub const components = .{
.{ .name = .name, .type = []const u8 }, .name = .{ .type = []const u8 },
.{ .name = .location, .type = Location }, .location = .{ .type = Location },
.{ .name = .rotation, .type = Rotation }, .rotation = .{ .type = Rotation },
}; };
}, },
}){}; }){};

View file

@ -47,7 +47,7 @@ test "example" {
pub const name = .physics; pub const name = .physics;
pub const components = .{ pub const components = .{
.{ .name = .id, .type = u32 }, .id = .{ .type = u32 },
}; };
pub const global_events = .{ pub const global_events = .{
.tick = .{ .handler = tick }, .tick = .{ .handler = tick },
@ -61,7 +61,7 @@ test "example" {
const Renderer = struct { const Renderer = struct {
pub const name = .renderer; pub const name = .renderer;
pub const components = .{ pub const components = .{
.{ .name = .id, .type = u16 }, .id = .{ .type = u16 },
}; };
pub const global_events = .{ pub const global_events = .{
.tick = .{ .handler = tick }, .tick = .{ .handler = tick },

View file

@ -74,14 +74,14 @@ test "query" {
struct { struct {
pub const name = .game; pub const name = .game;
pub const components = .{ pub const components = .{
.{ .name = .name, .type = []const u8 }, .name = .{ .type = []const u8 },
}; };
}, },
struct { struct {
pub const name = .physics; pub const name = .physics;
pub const components = .{ pub const components = .{
.{ .name = .location, .type = Location }, .location = .{ .type = Location },
.{ .name = .rotation, .type = Rotation }, .rotation = .{ .type = Rotation },
}; };
}, },
struct { struct {

View file

@ -19,13 +19,13 @@ pub const name = .mach_gfx_sprite;
pub const Mod = mach.Mod(@This()); pub const Mod = mach.Mod(@This());
pub const components = .{ pub const components = .{
.{ .name = .pipeline, .type = u8, .description = .pipeline = .{ .type = u8, .description =
\\ The ID of the pipeline this sprite belongs to. By default, zero. \\ The ID of the pipeline this sprite belongs to. By default, zero.
\\ \\
\\ This determines which shader, textures, etc. are used for rendering the sprite. \\ This determines which shader, textures, etc. are used for rendering the sprite.
}, },
.{ .name = .transform, .type = Mat4x4, .description = .transform = .{ .type = Mat4x4, .description =
\\ The sprite model transformation matrix. A sprite is measured in pixel units, starting from \\ The sprite model transformation matrix. A sprite is measured in pixel units, starting from
\\ (0, 0) at the top-left corner and extending to the size of the sprite. By default, the world \\ (0, 0) at the top-left corner and extending to the size of the sprite. By default, the world
\\ origin (0, 0) lives at the center of the window. \\ origin (0, 0) lives at the center of the window.
@ -34,11 +34,11 @@ pub const components = .{
\\ cover the top-right hand corner of the window. \\ cover the top-right hand corner of the window.
}, },
.{ .name = .uv_transform, .type = Mat3x3, .description = .uv_transform = .{ .type = Mat3x3, .description =
\\ UV coordinate transformation matrix describing top-left corner / origin of sprite, in pixels. \\ UV coordinate transformation matrix describing top-left corner / origin of sprite, in pixels.
}, },
.{ .name = .size, .type = Vec2, .description = .size = .{ .type = Vec2, .description =
\\ The size of the sprite, in pixels. \\ The size of the sprite, in pixels.
}, },
}; };

View file

@ -28,52 +28,52 @@ pub const Mod = mach.Mod(@This());
// TODO: allow user to specify projection matrix (3d-space flat text etc.) // TODO: allow user to specify projection matrix (3d-space flat text etc.)
pub const components = .{ pub const components = .{
.{ .name = .pipeline, .type = u8, .description = .pipeline = .{ .type = u8, .description =
\\ The ID of the pipeline this text belongs to. By default, zero. \\ The ID of the pipeline this text belongs to. By default, zero.
\\ \\
\\ This determines which shader, textures, etc. are used for rendering the text. \\ This determines which shader, textures, etc. are used for rendering the text.
}, },
.{ .name = .transform, .type = Mat4x4, .description = .transform = .{ .type = Mat4x4, .description =
\\ The text model transformation matrix. Text is measured in pixel units, starting from \\ The text model transformation matrix. Text is measured in pixel units, starting from
\\ (0, 0) at the top-left corner and extending to the size of the text. By default, the world \\ (0, 0) at the top-left corner and extending to the size of the text. By default, the world
\\ origin (0, 0) lives at the center of the window. \\ origin (0, 0) lives at the center of the window.
}, },
.{ .name = .text, .type = []const []const u8, .description = .text = .{ .type = []const []const u8, .description =
\\ String segments of UTF-8 encoded text to render. \\ String segments of UTF-8 encoded text to render.
\\ \\
\\ Expected to match the length of the style component. \\ Expected to match the length of the style component.
}, },
.{ .name = .style, .type = []const mach.ecs.EntityID, .description = .style = .{ .type = []const mach.ecs.EntityID, .description =
\\ The style to apply to each segment of text. \\ The style to apply to each segment of text.
\\ \\
\\ Expected to match the length of the text component. \\ Expected to match the length of the text component.
}, },
// TODO: ship a default font // TODO: ship a default font
.{ .name = .font_name, .type = []const u8, .description = .font_name = .{ .type = []const u8, .description =
\\ Style component: desired font to render text with. \\ Style component: desired font to render text with.
}, },
// e.g. 12 * mach.gfx.px_per_pt // 12pt // e.g. 12 * mach.gfx.px_per_pt // 12pt
.{ .name = .font_size, .type = f32, .description = .font_size = .{ .type = f32, .description =
\\ Style component: font size in pixels \\ Style component: font size in pixels
}, },
// e.g. mach.gfx.font_weight_normal // e.g. mach.gfx.font_weight_normal
.{ .name = .font_weight, .type = u16, .description = .font_weight = .{ .type = u16, .description =
\\ Style component: font weight \\ Style component: font weight
}, },
// e.g. false // e.g. false
.{ .name = .italic, .type = bool, .description = .italic = .{ .type = bool, .description =
\\ Style component: italic text \\ Style component: italic text
}, },
// e.g. vec4(0, 0, 0, 1.0) // e.g. vec4(0, 0, 0, 1.0)
.{ .name = .color, .type = Vec4, .description = .color = .{ .type = Vec4, .description =
\\ Style component: fill color \\ Style component: fill color
}, },
}; };

View file

@ -744,7 +744,7 @@ pub fn ComponentTypesByName(comptime modules: anytype) type {
const BuiltinMC = ComponentTypesM(struct { const BuiltinMC = ComponentTypesM(struct {
pub const name = .builtin; pub const name = .builtin;
pub const components = .{ pub const components = .{
.{ .name = .id, .type = EntityID, .description = "Entity ID" }, .id = .{ .type = EntityID, .description = "Entity ID" },
}; };
}); });
fields = fields ++ [_]std.builtin.Type.StructField{.{ fields = fields ++ [_]std.builtin.Type.StructField{.{
@ -779,42 +779,33 @@ fn ComponentTypesM(comptime M: anytype) type {
if (!@hasDecl(M, "components")) { if (!@hasDecl(M, "components")) {
return struct {}; return struct {};
} }
if (@typeInfo(@TypeOf(M.components)) != .Struct or !@typeInfo(@TypeOf(M.components)).Struct.is_tuple) { if (@typeInfo(@TypeOf(M.components)) != .Struct or @typeInfo(@TypeOf(M.components)).Struct.is_tuple) {
@compileError(error_prefix ++ "expected a tuple of structs, found: " ++ @typeName(@TypeOf(M.components))); @compileError(error_prefix ++ "expected a struct .{}, found: " ++ @typeName(@TypeOf(M.components)));
} }
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{}; var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
inline for (M.components, 0..) |component, i| { inline for (@typeInfo(@TypeOf(M.components)).Struct.fields) |field| {
const Component = @TypeOf(component); const Component = field.type;
if (@typeInfo(Component) != .Struct) @compileError(std.fmt.comptimePrint( if (@typeInfo(Component) != .Struct) @compileError(std.fmt.comptimePrint(
error_prefix ++ "expected a tuple of structs, found tuple element ({}): {s}", error_prefix ++ "expected .{s} = .{{}}, found type: {s}",
.{ i, @typeName(Component) }, .{ field.name, @typeName(Component) },
));
// Verify .name = .foo component name field
const name_tag = if (@hasField(Component, "name")) component.name else @compileError(std.fmt.comptimePrint(
error_prefix ++ "tuple element ({}) missing field `.name = .foo` (component name)",
.{i},
));
if (@typeInfo(@TypeOf(name_tag)) != .EnumLiteral) @compileError(std.fmt.comptimePrint(
error_prefix ++ "tuple element ({}) expected field `.name = .foo`, found: {s}",
.{ i, @typeName(@TypeOf(name_tag)) },
)); ));
const component = @field(M.components, field.name);
// Verify .type = Foo, field // Verify .type = Foo, field
if (!@hasField(Component, "type")) @compileError(std.fmt.comptimePrint( if (!@hasField(Component, "type")) @compileError(std.fmt.comptimePrint(
error_prefix ++ "tuple element ({}) missing field `.type = Foo`", error_prefix ++ ".{s} missing field `.type = T`",
.{i}, .{field.name},
)); ));
if (@typeInfo(@TypeOf(component.type)) != .Type) @compileError(std.fmt.comptimePrint( if (@typeInfo(@TypeOf(component.type)) != .Type) @compileError(std.fmt.comptimePrint(
error_prefix ++ "tuple element ({}) expected field `.type = Foo`, found: {s}", error_prefix ++ ".{s} expected field `.type = T`, found: {s}",
.{ i, @typeName(@TypeOf(component.type)) }, .{ field.name, @typeName(@TypeOf(component.type)) },
)); ));
const description = blk: { const description = blk: {
if (@hasField(Component, "description")) { if (@hasField(Component, "description")) {
if (!isString(@TypeOf(component.description))) @compileError(std.fmt.comptimePrint( if (!isString(@TypeOf(component.description))) @compileError(std.fmt.comptimePrint(
error_prefix ++ "tuple element ({}) expected (optional) field `.description = \"foo\"`, found: {s}", error_prefix ++ ".{s} expected (optional) field `.description = \"foo\"`, found: {s}",
.{ i, @typeName(@TypeOf(component.description)) }, .{ field.name, @typeName(@TypeOf(component.description)) },
)); ));
break :blk component.description; break :blk component.description;
} else break :blk null; } else break :blk null;
@ -826,7 +817,7 @@ fn ComponentTypesM(comptime M: anytype) type {
}; };
const ns_component = NSComponent{ .type = component.type, .description = description }; const ns_component = NSComponent{ .type = component.type, .description = description };
fields = fields ++ [_]std.builtin.Type.StructField{.{ fields = fields ++ [_]std.builtin.Type.StructField{.{
.name = @tagName(name_tag), .name = field.name,
.type = NSComponent, .type = NSComponent,
.default_value = &ns_component, .default_value = &ns_component,
.is_comptime = true, .is_comptime = true,
@ -888,7 +879,7 @@ test ModuleInterface {
/// Physics module components /// Physics module components
pub const components = .{ pub const components = .{
.{ .name = .location, .type = @Vector(3, f32), .description = "A location component" }, .location = .{ .type = @Vector(3, f32), .description = "A location component" },
}; };
pub const global_events = .{ pub const global_events = .{
@ -909,7 +900,7 @@ test Modules {
/// Physics module components /// Physics module components
pub const components = .{ pub const components = .{
.{ .name = .location, .type = @Vector(3, f32), .description = "A location component" }, .location = .{ .type = @Vector(3, f32), .description = "A location component" },
}; };
pub const global_events = .{ pub const global_events = .{
@ -925,9 +916,6 @@ test Modules {
.tick = .{ .handler = tick }, .tick = .{ .handler = tick },
}; };
/// Renderer module components
pub const components = .{};
fn tick() !void {} fn tick() !void {}
}); });
@ -950,7 +938,6 @@ test Modules {
test "event name" { test "event name" {
const Physics = ModuleInterface(struct { const Physics = ModuleInterface(struct {
pub const name = .engine_physics; pub const name = .engine_physics;
pub const components = .{};
pub const global_events = .{ pub const global_events = .{
.foo = .{ .handler = foo }, .foo = .{ .handler = foo },
.bar = .{ .handler = bar }, .bar = .{ .handler = bar },
@ -968,7 +955,6 @@ test "event name" {
const Renderer = ModuleInterface(struct { const Renderer = ModuleInterface(struct {
pub const name = .engine_renderer; pub const name = .engine_renderer;
pub const components = .{};
pub const global_events = .{ pub const global_events = .{
.foo_unused = .{ .handler = fn (f32, i32) void }, .foo_unused = .{ .handler = fn (f32, i32) void },
.bar_unused = .{ .handler = fn (i32, f32) void }, .bar_unused = .{ .handler = fn (i32, f32) void },
@ -1162,7 +1148,6 @@ test "event name calling" {
}; };
const Physics = ModuleInterface(struct { const Physics = ModuleInterface(struct {
pub const name = .engine_physics; pub const name = .engine_physics;
pub const components = .{};
pub const global_events = .{ pub const global_events = .{
.tick = .{ .handler = tick }, .tick = .{ .handler = tick },
}; };
@ -1185,7 +1170,6 @@ test "event name calling" {
}); });
const Renderer = ModuleInterface(struct { const Renderer = ModuleInterface(struct {
pub const name = .engine_renderer; pub const name = .engine_renderer;
pub const components = .{};
pub const global_events = .{ pub const global_events = .{
.tick = .{ .handler = tick }, .tick = .{ .handler = tick },
}; };
@ -1295,7 +1279,6 @@ test "dispatch" {
}); });
const Renderer = ModuleInterface(struct { const Renderer = ModuleInterface(struct {
pub const name = .engine_renderer; pub const name = .engine_renderer;
pub const components = .{};
pub const global_events = .{ pub const global_events = .{
.tick = .{ .handler = tick }, .tick = .{ .handler = tick },
.frame_done = .{ .handler = fn (i32) void }, .frame_done = .{ .handler = fn (i32) void },