const std = @import("std"); const component = @import("component.zig"); const ComponentType = component.ComponentType; pub const EntityComponentSystem = struct { allocator: std.mem.Allocator, components: std.MultiArrayList(component.Components), available_ids: std.ArrayList(usize), next_id: usize, fn nullEntity(self: *EntityComponentSystem, entity: usize) void { inline for (std.enums.valuesFromFields(ComponentType, std.meta.fields(ComponentType))) |comp| { self.componentRemoveEntity(comp, entity); } } pub fn init(allocator: std.mem.Allocator) EntityComponentSystem { return EntityComponentSystem{ .allocator = allocator, .components = .{}, .available_ids = std.ArrayList(usize).init(allocator), .next_id = 0, }; } pub fn deinit(self: *EntityComponentSystem) void { self.available_ids.deinit(); self.components.deinit(self.allocator); } pub fn createEntity(self: *EntityComponentSystem) !usize { const id = self.available_ids.popOrNull() orelse noroom: { const id = self.next_id; try self.components.append(self.allocator, undefined); self.next_id += 1; break :noroom id; }; self.nullEntity(id); return id; } pub fn componentAddEntity( self: *EntityComponentSystem, comp: component.ComponentType, entity: usize, args: anytype, ) !void { switch (comp) { ComponentType.component_stub => self.components.items(.component_stub)[entity] = component.ComponentStub.init(args), } } pub fn deleteEntity(self: *EntityComponentSystem, entity: usize) !void { self.nullEntity(entity); try self.available_ids.append(entity); } fn componentRemoveEntityGeneric(comp: anytype, entity: usize) void { var fixed_comp = comp[entity] orelse return; fixed_comp.deinit(); comp[entity] = null; } pub fn componentRemoveEntity( self: *EntityComponentSystem, comp: component.ComponentType, entity: usize, ) void { var components = self.components; switch (comp) { ComponentType.component_stub => componentRemoveEntityGeneric(components.items(.component_stub), entity), } } }; test "add entities to components" { 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 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.deleteEntity(entity5); try ecs.deleteEntity(entity4); try ecs.deleteEntity(entity3); try std.testing.expectEqual(3, ecs.available_ids.items.len); }