ecs: simplify how modules are written

Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
Stephen Gutekanst 2023-03-26 19:22:11 -07:00 committed by Stephen Gutekanst
parent 85ffb37156
commit 4575080ca9
2 changed files with 64 additions and 80 deletions

View file

@ -21,7 +21,6 @@ pub const Modules = @import("systems.zig").Modules;
pub const Messages = @import("systems.zig").Messages;
pub const MessagesTag = @import("systems.zig").MessagesTag;
pub const World = @import("systems.zig").World;
pub const View = @import("systems.zig").View;
// TODO:
// * Iteration
@ -37,33 +36,34 @@ test "inclusion" {
test "example" {
const allocator = testing.allocator;
const PhysicsMsg = Messages(.{
.tick = void,
});
const physicsUpdate = (struct {
pub fn physicsUpdate(msg: PhysicsMsg) void {
const Physics2D = Module(struct {
pointer: u8,
pub const name = .physics;
pub const components = .{
.id = u32,
};
pub const Message = .{
.tick = void,
};
pub fn update(msg: Message) void {
switch (msg) {
.tick => std.debug.print("\nphysics tick!\n", .{}),
}
}
}).physicsUpdate;
});
const Renderer = Module(struct {
pub const name = .renderer;
pub const components = .{
.id = u16,
};
});
const modules = Modules(.{
.physics = Module(.{
.components = .{
.id = u32,
},
.globals = struct {
pointer: u8,
},
.messages = PhysicsMsg,
.update = physicsUpdate,
}),
.renderer = Module(.{
.components = .{
.id = u16,
},
}),
Physics2D,
Renderer,
});
//-------------------------------------------------------------------------

View file

@ -9,15 +9,25 @@ const UnionField = std.builtin.Type.UnionField;
const Entities = @import("entities.zig").Entities;
/// An ECS module can provide components, systems, and global values.
pub fn Module(comptime Params: anytype) @TypeOf(Params) {
// TODO: validate the type
return Params;
/// Validates that a module matches the expected type layout.
///
/// An ECS module has components, systems, global values & more.
pub fn Module(comptime M: anytype) type {
if (@hasDecl(M, "name")) {
_ = @tagName(M.name);
} else @compileError("Module missing `pub const name = .foobar;`");
if (@hasDecl(M, "Message")) _ = Messages(M.Message);
// TODO(ecs): validate M.components decl signature, if present.
// TODO(ecs): validate M.update method signature, if present.
return M;
}
/// Describes a set of ECS modules, each of which can provide components, systems, and more.
/// Validates that a list of module matches the expected type layout.
///
/// ECS modules have components, systems, global values & more.
pub fn Modules(comptime modules: anytype) @TypeOf(modules) {
// TODO: validate the type
inline for (modules) |m| _ = Module(m);
return modules;
}
@ -95,9 +105,10 @@ fn NamespacedComponents(comptime modules: anytype) type {
var fields: []const StructField = &[0]StructField{};
inline for (std.meta.fields(@TypeOf(modules))) |module_field| {
const module = @field(modules, module_field.name);
if (@hasField(@TypeOf(module), "components")) {
const module_name = @tagName(@field(module, "name"));
if (@hasDecl(module, "components")) {
fields = fields ++ [_]std.builtin.Type.StructField{.{
.name = module_field.name,
.name = module_name,
.type = @TypeOf(module.components),
.default_value = null,
.is_comptime = false,
@ -115,28 +126,9 @@ fn NamespacedComponents(comptime modules: anytype) type {
});
}
/// Extracts namespaces components from modules like this:
///
/// ```
/// .{
/// .renderer = .{
/// .components = .{
/// .location = Vec3,
/// .rotation = Vec3,
/// },
/// ...
/// },
/// .physics2d = .{
/// .components = .{
/// .location = Vec2
/// .velocity = Vec2,
/// },
/// ...
/// },
/// }
/// ```
///
/// Returning a namespaced components value like this:
/// Extracts namespaces components from modules like this. A module is said to have components if
/// the struct has a `pub const components`. This function returns a namespaced components value
/// like e.g.:
///
/// ```
/// .{
@ -155,34 +147,16 @@ fn namespacedComponents(comptime modules: anytype) NamespacedComponents(modules)
var x: NamespacedComponents(modules) = undefined;
inline for (std.meta.fields(@TypeOf(modules))) |module_field| {
const module = @field(modules, module_field.name);
if (@hasField(@TypeOf(module), "components")) {
@field(x, module_field.name) = module.components;
const module_name = @tagName(@field(module, "name"));
if (@hasDecl(module, "components")) {
@field(x, module_name) = module.components;
}
}
return x;
}
/// Extracts namespaced globals from modules like this:
///
/// ```
/// .{
/// .renderer = .{
/// .globals = struct{
/// foo: *Bar,
/// baz: Bam,
/// },
/// ...
/// },
/// .physics2d = .{
/// .globals = struct{
/// foo: *Instance,
/// },
/// ...
/// },
/// }
/// ```
///
/// Into a namespaced global type like this:
/// Extracts namespaced globals from modules (a module is said to have globals if the struct has
/// any fields), returning a type like e.g.:
///
/// ```
/// struct{
@ -200,13 +174,23 @@ fn NamespacedGlobals(comptime modules: anytype) type {
var fields: []const StructField = &[0]StructField{};
inline for (std.meta.fields(@TypeOf(modules))) |module_field| {
const module = @field(modules, module_field.name);
if (@hasField(@TypeOf(module), "globals")) {
const module_name = @tagName(@field(module, "name"));
const global_fields = std.meta.fields(module);
if (global_fields.len > 0) {
const Globals = @Type(.{
.Struct = .{
.layout = .Auto,
.is_tuple = false,
.fields = global_fields,
.decls = &[_]std.builtin.Type.Declaration{},
},
});
fields = fields ++ [_]std.builtin.Type.StructField{.{
.name = module_field.name,
.type = module.globals,
.name = module_name,
.type = Globals,
.default_value = null,
.is_comptime = false,
.alignment = @alignOf(module.globals),
.alignment = @alignOf(Globals),
}};
}
}
@ -272,7 +256,7 @@ pub fn World(comptime modules: anytype) type {
pub fn send(world: *Self, comptime msg_tag: anytype) !void {
inline for (std.meta.fields(@TypeOf(modules))) |module_field| {
const module = @field(modules, module_field.name);
if (@hasField(@TypeOf(module), "messages")) {
if (@hasDecl(module, "messages")) {
if (@hasField(module.messages, @tagName(msg_tag))) try module.update(world, msg_tag);
}
}