From 25afe5ccf4236357f786a68907e0faf56a3b249c Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Fri, 10 Jun 2022 20:37:25 -0700 Subject: [PATCH] ecs: make [set/remove/get]Component globally type safe Signed-off-by: Stephen Gutekanst --- ecs/src/entities.zig | 66 +++++++++++++++++++++++++++++--------------- ecs/src/main.zig | 13 +++++---- 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/ecs/src/entities.zig b/ecs/src/entities.zig index 2a431133..ab5c50cb 100644 --- a/ecs/src/entities.zig +++ b/ecs/src/entities.zig @@ -497,7 +497,14 @@ pub fn Entities(all_components: anytype) type { /// Sets the named component to the specified value for the given entity, /// moving the entity from it's current archetype table to the new archetype /// table if required. - pub fn setComponent(entities: *Self, entity: EntityID, comptime name: []const u8, component: anytype) !void { + pub fn setComponent( + entities: *Self, + entity: EntityID, + comptime component_name: std.meta.FieldEnum(@TypeOf(all_components)), + component: @field(all_components, std.meta.tagName(component_name)), + ) !void { + const name = std.meta.tagName(component_name); + var archetype = entities.archetypeByID(entity); // Determine the old hash for the archetype. @@ -595,7 +602,13 @@ pub fn Entities(all_components: anytype) type { /// gets the named component of the given type (which must be correct, otherwise undefined /// behavior will occur). Returns null if the component does not exist on the entity. - pub fn getComponent(entities: *Self, entity: EntityID, name: []const u8, comptime Component: type) ?Component { + pub fn getComponent( + entities: *Self, + entity: EntityID, + comptime component_name: std.meta.FieldEnum(@TypeOf(all_components)), + ) ?@field(all_components, std.meta.tagName(component_name)) { + const Component = comptime @field(all_components, std.meta.tagName(component_name)); + const name = std.meta.tagName(component_name); var archetype = entities.archetypeByID(entity); const ptr = entities.entities.get(entity).?; @@ -603,7 +616,12 @@ pub fn Entities(all_components: anytype) type { } /// Removes the named component from the entity, or noop if it doesn't have such a component. - pub fn removeComponent(entities: *Self, entity: EntityID, name: []const u8) !void { + pub fn removeComponent( + entities: *Self, + entity: EntityID, + comptime component_name: std.meta.FieldEnum(@TypeOf(all_components)), + ) !void { + const name = std.meta.tagName(component_name); var archetype = entities.archetypeByID(entity); if (!archetype.hasComponent(name)) return; @@ -712,45 +730,47 @@ test "entity ID size" { test "example" { const allocator = testing.allocator; - const all_components = .{}; + const Location = struct { + x: f32 = 0, + y: f32 = 0, + z: f32 = 0, + }; + + const Rotation = struct { degrees: f32 }; + + const all_components = .{ + .location = Location, + .name = []const u8, + .rotation = Rotation, + }; //------------------------------------------------------------------------- // Create a world. var world = try Entities(all_components).init(allocator); defer world.deinit(); - //------------------------------------------------------------------------- - // Define component types, any Zig type will do! - // A location component. - const Location = struct { - x: f32 = 0, - y: f32 = 0, - z: f32 = 0, - }; - //------------------------------------------------------------------------- // Create first player entity. var player1 = try world.new(); - try world.setComponent(player1, "name", "jane"); // add Name component - try world.setComponent(player1, "location", Location{}); // add Location component + try world.setComponent(player1, .name, "jane"); // add Name component + try world.setComponent(player1, .location, .{}); // add Location component // Create second player entity. var player2 = try world.new(); - try testing.expect(world.getComponent(player2, "location", Location) == null); - try testing.expect(world.getComponent(player2, "name", []const u8) == null); + try testing.expect(world.getComponent(player2, .location) == null); + try testing.expect(world.getComponent(player2, .name) == null); //------------------------------------------------------------------------- // We can add new components at will. - const Rotation = struct { degrees: f32 }; - try world.setComponent(player2, "rotation", Rotation{ .degrees = 90 }); - try testing.expect(world.getComponent(player1, "rotation", Rotation) == null); // player1 has no rotation + try world.setComponent(player2, .rotation, .{ .degrees = 90 }); + try testing.expect(world.getComponent(player1, .rotation) == null); // player1 has no rotation //------------------------------------------------------------------------- // Remove a component from any entity at will. // TODO: add a way to "cleanup" truly unused archetypes - try world.removeComponent(player1, "name"); - try world.removeComponent(player1, "location"); - try world.removeComponent(player1, "location"); // doesn't exist? no problem. + try world.removeComponent(player1, .name); + try world.removeComponent(player1, .location); + try world.removeComponent(player1, .location); // doesn't exist? no problem. //------------------------------------------------------------------------- // Introspect things. diff --git a/ecs/src/main.zig b/ecs/src/main.zig index 1410ba08..6b311d70 100644 --- a/ecs/src/main.zig +++ b/ecs/src/main.zig @@ -46,7 +46,10 @@ test "inclusion" { test "example" { const allocator = testing.allocator; - const all_components = .{}; + const all_components = .{ + .physics = u16, + .geometry = u16, + }; //------------------------------------------------------------------------- // Create a world. @@ -56,11 +59,11 @@ test "example" { const player1 = try world.entities.new(); const player2 = try world.entities.new(); const player3 = try world.entities.new(); - try world.entities.setComponent(player1, "physics", @as(u16, 1234)); - try world.entities.setComponent(player1, "geometry", @as(u16, 1234)); + try world.entities.setComponent(player1, .physics, 1234); + try world.entities.setComponent(player1, .geometry, 1234); - try world.entities.setComponent(player2, "physics", @as(u16, 1234)); - try world.entities.setComponent(player3, "physics", @as(u16, 1234)); + try world.entities.setComponent(player2, .physics, 1234); + try world.entities.setComponent(player3, .physics, 1234); const physics = (struct { pub fn physics(adapter: *Adapter(all_components)) void {