aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjjanzen <jjanzen@jjanzen.ca>2025-01-21 21:46:21 -0600
committerjjanzen <jjanzen@jjanzen.ca>2025-01-21 21:46:21 -0600
commiteb2e8d13fa7c81a5dc0714be4012b522280d49cc (patch)
tree0ff14aec06f3361a692521ca68207fd6e0b09bc0
parent56bb712c67e249ecb04c6752d55f562a87607e45 (diff)
rigorous ecs implementation
-rw-r--r--src/ecs/component.zig27
-rw-r--r--src/ecs/ecs.zig143
2 files changed, 105 insertions, 65 deletions
diff --git a/src/ecs/component.zig b/src/ecs/component.zig
index 4a10172..37956d0 100644
--- a/src/ecs/component.zig
+++ b/src/ecs/component.zig
@@ -11,29 +11,10 @@ pub const ComponentStub = struct {
}
};
-pub const ComponentType = enum(usize) {
- component_stub = 0,
+pub const ComponentType = enum {
+ component_stub,
};
-pub const Component = union(ComponentType) {
- component_stub: ComponentStub,
-
- pub fn init(t: ComponentType, args: anytype) Component {
- switch (t) {
- ComponentType.component_stub => return Component{
- .component_stub = ComponentStub.init(args),
- },
- }
- }
-
- pub fn deinit(self: *Component) void {
- switch (self.*) {
- .component_stub => |*comp| comp.deinit(),
- }
- }
+pub const Components = struct {
+ component_stub: ?ComponentStub,
};
-
-test "stub component" {
- var stub = Component.init(ComponentType.component_stub, .{});
- defer stub.deinit();
-}
diff --git a/src/ecs/ecs.zig b/src/ecs/ecs.zig
index 542de34..919587a 100644
--- a/src/ecs/ecs.zig
+++ b/src/ecs/ecs.zig
@@ -1,62 +1,37 @@
const std = @import("std");
const component = @import("component.zig");
-
-const ComponentList = struct {
- entities: std.AutoHashMap(usize, component.Component),
-};
+const ComponentType = component.ComponentType;
pub const EntityComponentSystem = struct {
- entities: std.ArrayList(usize),
- components: std.AutoHashMap(usize, ComponentList),
+ allocator: std.mem.Allocator,
+ components: std.MultiArrayList(component.Components),
available_ids: std.ArrayList(usize),
next_id: usize,
- pub fn init(allocator: std.mem.Allocator) !EntityComponentSystem {
- var ecs = EntityComponentSystem{
- .entities = std.ArrayList(usize).init(allocator),
- .components = std.AutoHashMap(usize, ComponentList).init(allocator),
+ pub fn init(allocator: std.mem.Allocator) EntityComponentSystem {
+ return EntityComponentSystem{
+ .allocator = allocator,
+ .components = .{},
.available_ids = std.ArrayList(usize).init(allocator),
.next_id = 0,
};
-
- inline for (std.meta.fields(component.ComponentType)) |comp| {
- try ecs.components.put(comp.value, ComponentList{
- .entities = std.AutoHashMap(usize, component.Component).init(allocator),
- });
- }
-
- return ecs;
}
pub fn deinit(self: *EntityComponentSystem) void {
- self.entities.deinit();
self.available_ids.deinit();
-
- var component_iterator = self.components.iterator();
- while (component_iterator.next()) |component_entry| {
- var comp = component_entry.value_ptr;
-
- var entity_iterator = comp.entities.iterator();
- while (entity_iterator.next()) |entity_entry| {
- var entity = entity_entry.value_ptr;
-
- entity.deinit();
- }
-
- comp.entities.deinit();
- }
- self.components.deinit();
+ self.components.deinit(self.allocator);
}
pub fn createEntity(self: *EntityComponentSystem) !usize {
const reuse_id = self.available_ids.popOrNull();
- if (reuse_id != null and reuse_id.? < self.entities.items.len) {
- self.entities.items[reuse_id.?] = reuse_id.?;
+ if (reuse_id != null and reuse_id.? < self.components.items(.component_stub).len) {
return reuse_id.?;
}
const id = self.next_id;
- try self.entities.append(id);
+ try self.components.append(self.allocator, .{
+ .component_stub = null,
+ });
self.next_id += 1;
return id;
}
@@ -67,18 +42,102 @@ pub const EntityComponentSystem = struct {
entity: usize,
args: anytype,
) !void {
- var comp_list = self.components.getPtr(@intFromEnum(comp)) orelse return;
+ switch (comp) {
+ ComponentType.component_stub => self.components.items(.component_stub)[entity] = component.ComponentStub.init(args),
+ }
+ }
+
+ pub fn deleteEntity(self: *EntityComponentSystem, entity: usize) !void {
+ inline for (std.enums.valuesFromFields(ComponentType, std.meta.fields(ComponentType))) |comp| {
+ self.componentRemoveEntity(comp, entity);
+ }
+ try self.available_ids.append(entity);
+ }
- try comp_list.entities.put(entity, component.Component.init(comp, args));
+ pub fn componentRemoveEntity(
+ self: *EntityComponentSystem,
+ comp: component.ComponentType,
+ entity: usize,
+ ) void {
+ switch (comp) {
+ ComponentType.component_stub => {
+ var comp_opt = self.components.items(.component_stub)[entity];
+ if (comp_opt != null) comp_opt.?.deinit();
+ self.components.items(.component_stub)[entity] = null;
+ },
+ }
}
};
test "add entities to components" {
- var ecs = try EntityComponentSystem.init(std.testing.allocator);
+ var ecs = EntityComponentSystem.init(std.testing.allocator);
+ defer ecs.deinit();
+
+ for (0..100) |i| {
+ const entity = try ecs.createEntity();
+ try std.testing.expectEqual(i, entity);
+ try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
+ try std.testing.expect(ecs.components.items(.component_stub)[entity] != null);
+ }
+}
+
+test "remove entities from components" {
+ var ecs = EntityComponentSystem.init(std.testing.allocator);
+ defer ecs.deinit();
+
+ const entity = try ecs.createEntity();
+ try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
+ const entity2 = try ecs.createEntity();
+ try ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});
+
+ try std.testing.expect(ecs.components.items(.component_stub)[entity2] != null);
+ ecs.componentRemoveEntity(ComponentType.component_stub, entity2);
+ try std.testing.expect(ecs.components.items(.component_stub)[entity2] == null);
+}
+
+test "delete entities" {
+ var ecs = EntityComponentSystem.init(std.testing.allocator);
defer ecs.deinit();
const entity = try ecs.createEntity();
- try std.testing.expectEqual(0, entity);
+ try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
+ const entity2 = try ecs.createEntity();
+ try ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});
+
+ try std.testing.expect(ecs.components.items(.component_stub)[entity2] != null);
+ try ecs.deleteEntity(entity2);
+ try std.testing.expect(ecs.components.items(.component_stub)[entity2] == null);
+ try std.testing.expectEqual(1, ecs.available_ids.items.len);
+}
+
+test "recycle entities" {
+ var ecs = EntityComponentSystem.init(std.testing.allocator);
+ defer ecs.deinit();
+
+ const entity = try ecs.createEntity();
+ try ecs.componentAddEntity(ComponentType.component_stub, entity, .{});
+ const entity2 = try ecs.createEntity();
+ try ecs.componentAddEntity(ComponentType.component_stub, entity2, .{});
+
+ try ecs.deleteEntity(entity2);
+ try std.testing.expectEqual(1, ecs.available_ids.items.len);
+
+ const entity3 = try ecs.createEntity();
+ try std.testing.expectEqual(1, entity3);
+ try std.testing.expectEqual(0, ecs.available_ids.items.len);
+
+ const entity4 = try ecs.createEntity();
+ try std.testing.expectEqual(2, entity4);
+
+ try ecs.deleteEntity(entity);
+ try std.testing.expectEqual(1, ecs.available_ids.items.len);
+
+ const entity5 = try ecs.createEntity();
+ try std.testing.expectEqual(0, entity5);
+ try std.testing.expectEqual(0, ecs.available_ids.items.len);
- try ecs.componentAddEntity(component.ComponentType.component_stub, entity, .{});
+ try ecs.deleteEntity(entity5);
+ try ecs.deleteEntity(entity4);
+ try ecs.deleteEntity(entity3);
+ try std.testing.expectEqual(3, ecs.available_ids.items.len);
}