module: components are written in the same style as events
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
f50a27b83d
commit
7e0b9dde68
10 changed files with 265 additions and 123 deletions
|
|
@ -42,16 +42,18 @@ pub fn ArchetypeSlicer(comptime all_components: anytype) type {
|
|||
|
||||
pub fn slice(
|
||||
slicer: @This(),
|
||||
// TODO: cleanup comptime
|
||||
comptime namespace_name: std.meta.FieldEnum(@TypeOf(all_components)),
|
||||
comptime component_name: std.meta.DeclEnum(@field(all_components, @tagName(namespace_name))),
|
||||
comptime component_name: std.meta.FieldEnum(@TypeOf(@field(all_components, @tagName(namespace_name)))),
|
||||
) []@field(
|
||||
@field(all_components, @tagName(namespace_name)),
|
||||
@tagName(component_name),
|
||||
) {
|
||||
).type {
|
||||
// TODO: cleanup comptime
|
||||
const Type = @field(
|
||||
@field(all_components, @tagName(namespace_name)),
|
||||
@tagName(component_name),
|
||||
);
|
||||
).type;
|
||||
if (namespace_name == .entity and component_name == .id) {
|
||||
const name_id = slicer.archetype.component_names.index("id").?;
|
||||
return slicer.archetype.getColumnValues(name_id, Type).?[0..slicer.archetype.len];
|
||||
|
|
|
|||
|
|
@ -281,12 +281,13 @@ pub fn Entities(comptime all_components: anytype) type {
|
|||
pub fn setComponent(
|
||||
entities: *Self,
|
||||
entity: EntityID,
|
||||
// TODO: cleanup comptime
|
||||
comptime namespace_name: std.meta.FieldEnum(@TypeOf(all_components)),
|
||||
comptime component_name: std.meta.DeclEnum(@field(all_components, @tagName(namespace_name))),
|
||||
comptime component_name: std.meta.FieldEnum(@TypeOf(@field(all_components, @tagName(namespace_name)))),
|
||||
component: @field(
|
||||
@field(all_components, @tagName(namespace_name)),
|
||||
@tagName(component_name),
|
||||
),
|
||||
).type,
|
||||
) !void {
|
||||
const name_str = @tagName(namespace_name) ++ "." ++ @tagName(component_name);
|
||||
const name_id = try entities.component_names.indexOrPut(entities.allocator, name_str);
|
||||
|
|
@ -483,16 +484,19 @@ pub fn Entities(comptime all_components: anytype) type {
|
|||
pub fn getComponent(
|
||||
entities: *Self,
|
||||
entity: EntityID,
|
||||
// TODO: cleanup comptime
|
||||
comptime namespace_name: std.meta.FieldEnum(@TypeOf(all_components)),
|
||||
comptime component_name: std.meta.DeclEnum(@field(all_components, @tagName(namespace_name))),
|
||||
comptime component_name: std.meta.FieldEnum(@TypeOf(@field(all_components, @tagName(namespace_name)))),
|
||||
) ?@field(
|
||||
@field(all_components, @tagName(namespace_name)),
|
||||
@tagName(component_name),
|
||||
) {
|
||||
).type {
|
||||
// TODO: cleanup comptime
|
||||
const Component = comptime @field(
|
||||
@field(all_components, @tagName(namespace_name)),
|
||||
@tagName(component_name),
|
||||
);
|
||||
).type;
|
||||
|
||||
const name_str = @tagName(namespace_name) ++ "." ++ @tagName(component_name);
|
||||
const name_id = entities.component_names.index(name_str) orelse return null;
|
||||
|
||||
|
|
@ -523,8 +527,9 @@ pub fn Entities(comptime all_components: anytype) type {
|
|||
pub fn removeComponent(
|
||||
entities: *Self,
|
||||
entity: EntityID,
|
||||
// TODO: cleanup comptime
|
||||
comptime namespace_name: std.meta.FieldEnum(@TypeOf(all_components)),
|
||||
comptime component_name: std.meta.DeclEnum(@field(all_components, @tagName(namespace_name))),
|
||||
comptime component_name: std.meta.FieldEnum(@TypeOf(@field(all_components, @tagName(namespace_name)))),
|
||||
) !void {
|
||||
const name_str = @tagName(namespace_name) ++ "." ++ @tagName(component_name);
|
||||
const name_id = try entities.component_names.indexOrPut(entities.allocator, name_str);
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ test "example" {
|
|||
pointer: u8,
|
||||
|
||||
pub const name = .physics;
|
||||
pub const components = struct {
|
||||
pub const id = u32;
|
||||
pub const components = .{
|
||||
.{ .name = .id, .type = u32 },
|
||||
};
|
||||
pub const events = .{
|
||||
.{ .global = .tick, .handler = tick },
|
||||
|
|
@ -61,8 +61,8 @@ test "example" {
|
|||
|
||||
const Renderer = struct {
|
||||
pub const name = .renderer;
|
||||
pub const components = struct {
|
||||
pub const id = u16;
|
||||
pub const components = .{
|
||||
.{ .name = .ud, .type = u16 },
|
||||
};
|
||||
pub const events = .{
|
||||
.{ .global = .tick, .handler = tick },
|
||||
|
|
|
|||
|
|
@ -9,23 +9,27 @@ pub const QueryTag = enum {
|
|||
/// A complex query for entities matching a given criteria
|
||||
pub fn Query(comptime all_components: anytype) type {
|
||||
return union(QueryTag) {
|
||||
// TODO: cleanup comptime
|
||||
/// Enum matching a namespace. e.g. `.game` or `.physics2d`
|
||||
pub const Namespace = std.meta.FieldEnum(@TypeOf(all_components));
|
||||
|
||||
// TODO: cleanup comptime
|
||||
/// Enum matching a component within a namespace
|
||||
/// e.g. `var a: Component(.physics2d) = .location`
|
||||
pub fn Component(comptime namespace: Namespace) type {
|
||||
const components = @field(all_components, @tagName(namespace));
|
||||
if (@typeInfo(components).Struct.decls.len == 0) return enum {};
|
||||
return std.meta.DeclEnum(components);
|
||||
if (@typeInfo(@TypeOf(components)).Struct.fields.len == 0) return enum {};
|
||||
return std.meta.FieldEnum(@TypeOf(components));
|
||||
}
|
||||
|
||||
// TODO: cleanup comptime
|
||||
/// Slice of enums matching a component within a namespace
|
||||
/// e.g. `&.{.location, .rotation}`
|
||||
pub fn ComponentList(comptime namespace: Namespace) type {
|
||||
return []const Component(namespace);
|
||||
}
|
||||
|
||||
// TODO: cleanup comptime
|
||||
/// Tagged union of namespaces matching lists of components
|
||||
/// e.g. `.physics2d = &.{ .location, .rotation }`
|
||||
pub const NamespaceComponent = T: {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ const Entities = @import("entities.zig").Entities;
|
|||
const EntityID = @import("entities.zig").EntityID;
|
||||
const comp = @import("comptime.zig");
|
||||
const Module = @import("../module.zig").Module;
|
||||
const NamespacedComponents = @import("../module.zig").NamespacedComponents;
|
||||
|
||||
pub fn World(comptime mods: anytype) type {
|
||||
const StateT = NamespacedState(mods);
|
||||
|
|
@ -37,8 +38,9 @@ pub fn World(comptime mods: anytype) type {
|
|||
pub inline fn set(
|
||||
m: *@This(),
|
||||
entity: EntityID,
|
||||
comptime component_name: std.meta.DeclEnum(components),
|
||||
component: @field(components, @tagName(component_name)),
|
||||
// TODO: cleanup comptime
|
||||
comptime component_name: std.meta.FieldEnum(@TypeOf(components)),
|
||||
component: @field(components, @tagName(component_name)).type,
|
||||
) !void {
|
||||
const mod_ptr: *Mods = @alignCast(@fieldParentPtr(Mods, @tagName(module_tag), m));
|
||||
const world = @fieldParentPtr(WorldT, "mod", mod_ptr);
|
||||
|
|
@ -50,8 +52,9 @@ pub fn World(comptime mods: anytype) type {
|
|||
pub inline fn get(
|
||||
m: *@This(),
|
||||
entity: EntityID,
|
||||
comptime component_name: std.meta.DeclEnum(components),
|
||||
) ?@field(components, @tagName(component_name)) {
|
||||
// TODO: cleanup comptime
|
||||
comptime component_name: std.meta.FieldEnum(@TypeOf(components)),
|
||||
) ?@field(components, @tagName(component_name)).type {
|
||||
const mod_ptr: *Mods = @alignCast(@fieldParentPtr(Mods, @tagName(module_tag), m));
|
||||
const world = @fieldParentPtr(WorldT, "mod", mod_ptr);
|
||||
return world.entities.getComponent(entity, module_tag, component_name);
|
||||
|
|
@ -61,7 +64,8 @@ pub fn World(comptime mods: anytype) type {
|
|||
pub inline fn remove(
|
||||
m: *@This(),
|
||||
entity: EntityID,
|
||||
comptime component_name: std.meta.DeclEnum(components),
|
||||
// TODO: cleanup comptime
|
||||
comptime component_name: std.meta.FieldEnum(@TypeOf(components)),
|
||||
) !void {
|
||||
const mod_ptr: *Mods = @alignCast(@fieldParentPtr(Mods, @tagName(module_tag), m));
|
||||
const world = @fieldParentPtr(WorldT, "mod", mod_ptr);
|
||||
|
|
@ -80,6 +84,7 @@ pub fn World(comptime mods: anytype) type {
|
|||
world.modules.sendGlobal(module_tag, event_name, args);
|
||||
}
|
||||
|
||||
// TODO: eliminate this
|
||||
pub fn dispatchNoError(m: *@This()) void {
|
||||
const mod_ptr: *Mods = @alignCast(@fieldParentPtr(Mods, @tagName(module_tag), m));
|
||||
const world = @fieldParentPtr(WorldT, "mod", mod_ptr);
|
||||
|
|
@ -173,42 +178,6 @@ pub fn World(comptime mods: anytype) type {
|
|||
};
|
||||
}
|
||||
|
||||
// TODO: reconsider components concept
|
||||
fn NamespacedComponents(comptime modules: anytype) type {
|
||||
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
||||
inline for (modules) |M| {
|
||||
const components = if (@hasDecl(M, "components")) M.components else struct {};
|
||||
fields = fields ++ [_]std.builtin.Type.StructField{.{
|
||||
.name = @tagName(M.name),
|
||||
.type = type,
|
||||
.default_value = &components,
|
||||
.is_comptime = true,
|
||||
.alignment = @alignOf(@TypeOf(components)),
|
||||
}};
|
||||
}
|
||||
|
||||
// Builtin components
|
||||
const entity_components = struct {
|
||||
pub const id = EntityID;
|
||||
};
|
||||
fields = fields ++ [_]std.builtin.Type.StructField{.{
|
||||
.name = "entity",
|
||||
.type = type,
|
||||
.default_value = &entity_components,
|
||||
.is_comptime = true,
|
||||
.alignment = @alignOf(@TypeOf(entity_components)),
|
||||
}};
|
||||
|
||||
return @Type(.{
|
||||
.Struct = .{
|
||||
.layout = .Auto,
|
||||
.is_tuple = false,
|
||||
.fields = fields,
|
||||
.decls = &[_]std.builtin.Type.Declaration{},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: reconsider state concept
|
||||
fn NamespacedState(comptime modules: anytype) type {
|
||||
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
||||
|
|
|
|||
|
|
@ -18,25 +18,29 @@ pipelines: std.AutoArrayHashMapUnmanaged(u32, Pipeline),
|
|||
pub const name = .mach_gfx_sprite;
|
||||
pub const Mod = mach.Mod(@This());
|
||||
|
||||
pub const components = struct {
|
||||
/// The ID of the pipeline this sprite belongs to. By default, zero.
|
||||
///
|
||||
/// This determines which shader, textures, etc. are used for rendering the sprite.
|
||||
pub const pipeline = u8;
|
||||
pub const components = .{
|
||||
.{ .name = .pipeline, .type = u8, .description =
|
||||
\\ The ID of the pipeline this sprite belongs to. By default, zero.
|
||||
\\
|
||||
\\ This determines which shader, textures, etc. are used for rendering the sprite.
|
||||
},
|
||||
|
||||
/// 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
|
||||
/// origin (0, 0) lives at the center of the window.
|
||||
///
|
||||
/// Example: in a 500px by 500px window, a sprite located at (0, 0) with size (250, 250) will
|
||||
/// cover the top-right hand corner of the window.
|
||||
pub const transform = Mat4x4;
|
||||
.{ .name = .transform, .type = Mat4x4, .description =
|
||||
\\ 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
|
||||
\\ origin (0, 0) lives at the center of the window.
|
||||
\\
|
||||
\\ Example: in a 500px by 500px window, a sprite located at (0, 0) with size (250, 250) will
|
||||
\\ cover the top-right hand corner of the window.
|
||||
},
|
||||
|
||||
/// UV coordinate transformation matrix describing top-left corner / origin of sprite, in pixels.
|
||||
pub const uv_transform = Mat3x3;
|
||||
.{ .name = .uv_transform, .type = Mat3x3, .description =
|
||||
\\ UV coordinate transformation matrix describing top-left corner / origin of sprite, in pixels.
|
||||
},
|
||||
|
||||
/// The size of the sprite, in pixels.
|
||||
pub const size = Vec2;
|
||||
.{ .name = .size, .type = Vec2, .description =
|
||||
\\ The size of the sprite, in pixels.
|
||||
},
|
||||
};
|
||||
|
||||
pub const events = .{
|
||||
|
|
|
|||
|
|
@ -27,41 +27,55 @@ pub const Mod = mach.Mod(@This());
|
|||
//
|
||||
// TODO: allow user to specify projection matrix (3d-space flat text etc.)
|
||||
|
||||
pub const components = struct {
|
||||
/// The ID of the pipeline this text belongs to. By default, zero.
|
||||
///
|
||||
/// This determines which shader, textures, etc. are used for rendering the text.
|
||||
pub const pipeline = u8;
|
||||
pub const components = .{
|
||||
.{ .name = .pipeline, .type = u8, .description =
|
||||
\\ The ID of the pipeline this text belongs to. By default, zero.
|
||||
\\
|
||||
\\ This determines which shader, textures, etc. are used for rendering the text.
|
||||
},
|
||||
|
||||
/// 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
|
||||
/// origin (0, 0) lives at the center of the window.
|
||||
pub const transform = Mat4x4;
|
||||
.{ .name = .transform, .type = Mat4x4, .description =
|
||||
\\ 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
|
||||
\\ origin (0, 0) lives at the center of the window.
|
||||
},
|
||||
|
||||
/// String segments of UTF-8 encoded text to render.
|
||||
///
|
||||
/// Expected to match the length of the style component.
|
||||
pub const text = []const []const u8;
|
||||
.{ .name = .text, .type = []const []const u8, .description =
|
||||
\\ String segments of UTF-8 encoded text to render.
|
||||
\\
|
||||
\\ Expected to match the length of the style component.
|
||||
},
|
||||
|
||||
/// The style to apply to each segment of text.
|
||||
///
|
||||
/// Expected to match the length of the text component.
|
||||
pub const style = []const mach.ecs.EntityID;
|
||||
.{ .name = .style, .type = []const mach.ecs.EntityID, .description =
|
||||
\\ The style to apply to each segment of text.
|
||||
\\
|
||||
\\ Expected to match the length of the text component.
|
||||
},
|
||||
|
||||
/// Style component: desired font to render text with.
|
||||
pub const font_name = []const u8; // TODO: ship a default font
|
||||
// TODO: ship a default font
|
||||
.{ .name = .font_name, .type = []const u8, .description =
|
||||
\\ Style component: desired font to render text with.
|
||||
},
|
||||
|
||||
/// Style component: font size in pixels
|
||||
pub const font_size = f32; // e.g. 12 * mach.gfx.px_per_pt // 12pt
|
||||
// e.g. 12 * mach.gfx.px_per_pt // 12pt
|
||||
.{ .name = .font_size, .type = f32, .description =
|
||||
\\ Style component: font size in pixels
|
||||
},
|
||||
|
||||
/// Style component: font weight
|
||||
pub const font_weight = u16; // e.g. mach.gfx.font_weight_normal
|
||||
// e.g. mach.gfx.font_weight_normal
|
||||
.{ .name = .font_weight, .type = u16, .description =
|
||||
\\ Style component: font weight
|
||||
},
|
||||
|
||||
/// Style component: italic text
|
||||
pub const italic = bool; // e.g. false
|
||||
// e.g. false
|
||||
.{ .name = .italic, .type = bool, .description =
|
||||
\\ Style component: italic text
|
||||
},
|
||||
|
||||
/// Style component: fill color
|
||||
pub const color = Vec4; // e.g. vec4(0, 0, 0, 1.0),
|
||||
// e.g. vec4(0, 0, 0, 1.0)
|
||||
.{ .name = .color, .type = Vec4, .description =
|
||||
\\ Style component: fill color
|
||||
},
|
||||
};
|
||||
|
||||
pub const events = .{
|
||||
|
|
|
|||
180
src/module.zig
180
src/module.zig
|
|
@ -2,6 +2,8 @@ const builtin = @import("builtin");
|
|||
const std = @import("std");
|
||||
const testing = @import("testing.zig");
|
||||
|
||||
const EntityID = @import("./ecs/entities.zig").EntityID;
|
||||
|
||||
/// Verifies that M matches the basic layout of a Mach module
|
||||
pub fn Module(comptime M: type) type {
|
||||
if (@typeInfo(M) != .Struct) @compileError("mach: expected module struct, found: " ++ @typeName(M));
|
||||
|
|
@ -11,11 +13,7 @@ pub fn Module(comptime M: type) type {
|
|||
const prefix = "mach: module ." ++ @tagName(M.name) ++ " ";
|
||||
if (!@hasDecl(M, "events")) @compileError(prefix ++ "must have `pub const events = .{};`");
|
||||
validateEvents("mach: module ." ++ @tagName(M.name) ++ " ", M.events);
|
||||
|
||||
// TODO: move this to ecs
|
||||
if (@hasDecl(M, "components")) {
|
||||
if (@typeInfo(M.components) != .Struct) @compileError("Module.components must be `pub const components = struct { ... };`, found type:" ++ @typeName(M.components));
|
||||
}
|
||||
_ = MComponents(M);
|
||||
return M;
|
||||
}
|
||||
|
||||
|
|
@ -461,6 +459,7 @@ fn ModuleName(comptime mods: anytype) type {
|
|||
});
|
||||
}
|
||||
|
||||
// TODO: tests
|
||||
/// Struct like .{.foo = FooMod, .bar = BarMod}
|
||||
fn NamespacedModules(comptime modules: anytype) type {
|
||||
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
||||
|
|
@ -483,6 +482,7 @@ fn NamespacedModules(comptime modules: anytype) type {
|
|||
});
|
||||
}
|
||||
|
||||
// TODO: tests
|
||||
fn validateEvents(comptime error_prefix: anytype, comptime events: anytype) void {
|
||||
if (@typeInfo(@TypeOf(events)) != .Struct or !@typeInfo(@TypeOf(events)).Struct.is_tuple) {
|
||||
@compileError(error_prefix ++ "expected a tuple of structs, found: " ++ @typeName(@TypeOf(events)));
|
||||
|
|
@ -525,6 +525,152 @@ fn validateEvents(comptime error_prefix: anytype, comptime events: anytype) void
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: tests
|
||||
/// Returns a struct type defining all module's components by module name, e.g.:
|
||||
///
|
||||
/// ```
|
||||
/// 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" },
|
||||
/// },
|
||||
/// renderer: struct {
|
||||
/// location: @TypeOf() = .{ .type = Vec2, .description = null },
|
||||
/// },
|
||||
/// }
|
||||
/// ```
|
||||
pub fn NamespacedComponents(comptime modules: anytype) type {
|
||||
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
||||
inline for (modules) |M| {
|
||||
const MC = MComponents(M);
|
||||
fields = fields ++ [_]std.builtin.Type.StructField{.{
|
||||
.name = @tagName(M.name),
|
||||
.type = MC,
|
||||
.default_value = &MC{},
|
||||
.is_comptime = true,
|
||||
.alignment = @alignOf(MC),
|
||||
}};
|
||||
}
|
||||
|
||||
// Builtin components
|
||||
// TODO: better method of injecting builtin module?
|
||||
const BuiltinMC = MComponents(struct {
|
||||
pub const name = .builtin;
|
||||
pub const components = .{
|
||||
.{ .name = .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,
|
||||
.is_tuple = false,
|
||||
.fields = fields,
|
||||
.decls = &[_]std.builtin.Type.Declaration{},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: tests
|
||||
/// Returns a struct type defining the module's components, e.g.:
|
||||
///
|
||||
/// ```
|
||||
/// struct {
|
||||
/// location: @TypeOf() = .{ .type = Vec3, .description = null },
|
||||
/// rotation: @TypeOf() = .{ .type = Vec2, .description = "rotation component" },
|
||||
/// }
|
||||
/// ```
|
||||
fn MComponents(comptime M: anytype) type {
|
||||
const error_prefix = "mach: module ." ++ @tagName(M.name) ++ " .components ";
|
||||
if (!@hasDecl(M, "components")) {
|
||||
return struct {};
|
||||
}
|
||||
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)));
|
||||
}
|
||||
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
||||
inline for (M.components, 0..) |component, i| {
|
||||
const Component = @TypeOf(component);
|
||||
if (@typeInfo(Component) != .Struct) @compileError(std.fmt.comptimePrint(
|
||||
error_prefix ++ "expected a tuple of structs, found tuple element ({}): {s}",
|
||||
.{ i, @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)) },
|
||||
));
|
||||
|
||||
// Verify .type = Foo, field
|
||||
if (!@hasField(Component, "type")) @compileError(std.fmt.comptimePrint(
|
||||
error_prefix ++ "tuple element ({}) missing field `.type = Foo`",
|
||||
.{i},
|
||||
));
|
||||
if (@typeInfo(@TypeOf(component.type)) != .Type) @compileError(std.fmt.comptimePrint(
|
||||
error_prefix ++ "tuple element ({}) expected field `.type = Foo`, found: {s}",
|
||||
.{ i, @typeName(@TypeOf(component.type)) },
|
||||
));
|
||||
|
||||
const description = blk: {
|
||||
if (@hasField(Component, "description")) {
|
||||
if (!isString(@TypeOf(component.description))) @compileError(std.fmt.comptimePrint(
|
||||
error_prefix ++ "tuple element ({}) expected (optional) field `.description = \"foo\"`, found: {s}",
|
||||
.{ i, @typeName(@TypeOf(component.description)) },
|
||||
));
|
||||
break :blk component.description;
|
||||
} else break :blk null;
|
||||
};
|
||||
|
||||
const NSComponent = struct {
|
||||
type: type,
|
||||
description: ?[]const u8,
|
||||
};
|
||||
const ns_component = NSComponent{ .type = component.type, .description = description };
|
||||
fields = fields ++ [_]std.builtin.Type.StructField{.{
|
||||
.name = @tagName(name_tag),
|
||||
.type = NSComponent,
|
||||
.default_value = &ns_component,
|
||||
.is_comptime = true,
|
||||
.alignment = @alignOf(NSComponent),
|
||||
}};
|
||||
}
|
||||
return @Type(.{
|
||||
.Struct = .{
|
||||
.layout = .Auto,
|
||||
.is_tuple = false,
|
||||
.fields = fields,
|
||||
.decls = &[_]std.builtin.Type.Declaration{},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: tests
|
||||
// TODO: stricter enforcement
|
||||
fn isString(comptime S: type) bool {
|
||||
return switch (@typeInfo(S)) {
|
||||
.Pointer => |p| switch (@typeInfo(p.child)) {
|
||||
.Array => |a| a.child == u8,
|
||||
else => false,
|
||||
},
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
test {
|
||||
testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
|
|
@ -538,9 +684,8 @@ test Module {
|
|||
pub const name = .engine_physics;
|
||||
|
||||
/// Physics module components
|
||||
pub const components = struct {
|
||||
/// A location component
|
||||
pub const location = @Vector(3, f32);
|
||||
pub const components = .{
|
||||
.{ .name = .location, .type = @Vector(3, f32), .description = "A location component" },
|
||||
};
|
||||
|
||||
pub const events = .{
|
||||
|
|
@ -560,9 +705,8 @@ test Modules {
|
|||
pub const name = .engine_physics;
|
||||
|
||||
/// Physics module components
|
||||
pub const components = struct {
|
||||
/// A location component
|
||||
pub const location = @Vector(3, f32);
|
||||
pub const components = .{
|
||||
.{ .name = .location, .type = @Vector(3, f32), .description = "A location component" },
|
||||
};
|
||||
|
||||
pub const events = .{
|
||||
|
|
@ -579,7 +723,7 @@ test Modules {
|
|||
};
|
||||
|
||||
/// Renderer module components
|
||||
pub const components = struct {};
|
||||
pub const components = .{};
|
||||
|
||||
fn tick() !void {}
|
||||
});
|
||||
|
|
@ -604,7 +748,7 @@ test Modules {
|
|||
test "event name" {
|
||||
const Physics = Module(struct {
|
||||
pub const name = .engine_physics;
|
||||
pub const components = struct {};
|
||||
pub const components = .{};
|
||||
pub const events = .{
|
||||
.{ .global = .foo, .handler = foo },
|
||||
.{ .global = .bar, .handler = bar },
|
||||
|
|
@ -620,7 +764,7 @@ test "event name" {
|
|||
|
||||
const Renderer = Module(struct {
|
||||
pub const name = .engine_renderer;
|
||||
pub const components = struct {};
|
||||
pub const components = .{};
|
||||
pub const events = .{
|
||||
.{ .global = .foo_unused, .handler = fn (f32, i32) void },
|
||||
.{ .global = .bar_unused, .handler = fn (i32, f32) void },
|
||||
|
|
@ -817,7 +961,7 @@ test "event name calling" {
|
|||
};
|
||||
const Physics = Module(struct {
|
||||
pub const name = .engine_physics;
|
||||
pub const components = struct {};
|
||||
pub const components = .{};
|
||||
pub const events = .{
|
||||
.{ .global = .tick, .handler = tick },
|
||||
.{ .local = .update, .handler = update },
|
||||
|
|
@ -838,7 +982,7 @@ test "event name calling" {
|
|||
});
|
||||
const Renderer = Module(struct {
|
||||
pub const name = .engine_renderer;
|
||||
pub const components = struct {};
|
||||
pub const components = .{};
|
||||
pub const events = .{
|
||||
.{ .global = .tick, .handler = tick },
|
||||
.{ .local = .update, .handler = update },
|
||||
|
|
@ -925,7 +1069,7 @@ test "dispatch" {
|
|||
});
|
||||
const Physics = Module(struct {
|
||||
pub const name = .engine_physics;
|
||||
pub const components = struct {};
|
||||
pub const components = .{};
|
||||
pub const events = .{
|
||||
.{ .global = .tick, .handler = tick },
|
||||
.{ .local = .update, .handler = update },
|
||||
|
|
@ -946,7 +1090,7 @@ test "dispatch" {
|
|||
});
|
||||
const Renderer = Module(struct {
|
||||
pub const name = .engine_renderer;
|
||||
pub const components = struct {};
|
||||
pub const components = .{};
|
||||
pub const events = .{
|
||||
.{ .global = .tick, .handler = tick },
|
||||
.{ .global = .frame_done, .handler = fn (i32) void },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue