diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index fe7ddaf..09d37a0 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -3,7 +3,11 @@ const warn = std.debug.warn; const utils = @import("utils.zig"); const SparseSet = @import("sparse_set.zig").SparseSet; +const Signal = @import("../signals/signal.zig").Signal; +const Sink = @import("../signals/sink.zig").Sink; +/// Stores an ArrayList of components along with a SparseSet of entities. The max amount that can be stored is +/// based on the max value of DenseT pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime DenseT: type) type { std.debug.assert(!utils.isComptime(CompT)); @@ -25,6 +29,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D instances: std.ArrayList(CompOrAlmostEmptyT), allocator: ?*std.mem.Allocator, safe_deinit: fn (*Self) void, + construction: Signal(EntityT), + update: Signal(EntityT), + destruction: Signal(EntityT), pub fn init(allocator: *std.mem.Allocator) Self { var store = Self{ @@ -37,6 +44,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D } }.deinit, .allocator = null, + .construction = Signal(EntityT).init(allocator), + .update = Signal(EntityT).init(allocator), + .destruction = Signal(EntityT).init(allocator), }; if (!is_empty_struct) @@ -51,6 +61,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D if (!is_empty_struct) store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); store.allocator = allocator; + store.construction = Signal(EntityT).init(allocator); + store.update = Signal(EntityT).init(allocator); + store.destruction = Signal(EntityT).init(allocator); // since we are stored as a pointer, we need to catpure this store.safe_deinit = struct { @@ -69,11 +82,26 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D // will allways be false here so we have to deinit the instances no matter what. self.safe_deinit(self); self.set.deinit(); + self.construction.deinit(); + self.update.deinit(); + self.destruction.deinit(); if (self.allocator) |allocator| allocator.destroy(self); } + pub fn onConstruct(self: *Self) Sink(EntityT) { + return self.construction.sink(); + } + + pub fn onUpdate(self: *Self) Sink(EntityT) { + return self.update.sink(); + } + + pub fn onDestruct(self: *Self) Sink(EntityT) { + return self.destruction.sink(); + } + /// Increases the capacity of a component storage pub fn reserve(self: *Self, cap: usize) void { self.set.reserve(cap); @@ -81,11 +109,20 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D self.instances.items.reserve(cap); } - /// Assigns an entity to a storage and constructs its object + /// Assigns an entity to a storage and assigns its object pub fn add(self: *Self, entity: EntityT, value: CompT) void { if (!is_empty_struct) _ = self.instances.append(value) catch unreachable; self.set.add(entity); + self.construction.publish(entity); + } + + /// Removes an entity from a storage + pub fn remove(self: *Self, entity: EntityT) void { + if (!is_empty_struct) + _ = self.instances.swapRemove(self.set.index(entity)); + self.destruction.publish(entity); + self.set.remove(entity); } /// Checks if a view contains an entity @@ -106,6 +143,12 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D return self.instances.items; } + /// Replaces the given component for an entity + pub fn replace(self: *Self, entity: EntityT, value: CompT) void { + self.get(entity).* = value; + self.update.publish(entity); + } + /// Returns the object associated with an entity pub fn get(self: *Self, entity: EntityT) *CompT { std.debug.assert(self.contains(entity)); @@ -131,13 +174,6 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D return self.set.data(); } - /// Removes an entity from a storage - pub fn remove(self: *Self, entity: EntityT) void { - if (!is_empty_struct) - _ = self.instances.swapRemove(self.set.index(entity)); - self.set.remove(entity); - } - /// Swaps entities and objects in the internal packed arrays pub fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { if (!is_empty_struct) @@ -208,3 +244,34 @@ test "empty component" { store.add(3, Empty{}); store.remove(3); } + +fn construct(e: u32) void { + std.debug.assert(e == 3); +} +fn update(e: u32) void { + std.debug.assert(e == 3); +} +fn destruct(e: u32) void { + std.debug.assert(e == 3); +} + +test "signals" { + var store = ComponentStorage(f32, u32, u8).init(std.testing.allocator); + defer store.deinit(); + + store.onConstruct().connect(construct); + store.onUpdate().connect(update); + store.onDestruct().connect(destruct); + + store.add(3, 66.45); + store.replace(3, 45.64); + store.remove(3); + + store.onConstruct().disconnect(construct); + store.onUpdate().disconnect(update); + store.onDestruct().disconnect(destruct); + + store.add(4, 66.45); + store.replace(4, 45.64); + store.remove(4); +} diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 3dd68c7..96d8944 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -70,20 +70,29 @@ pub const Registry = struct { return @intToPtr(*Storage(T), ptr); } + /// Prepares a pool for the given type if required pub fn prepare(self: *Registry, comptime T: type) void { - unreachable; + _ = self.assure(T); } + /// Returns the number of existing components of the given type pub fn len(self: *Registry, comptime T: type) usize { self.assure(T).len(); } + /// Increases the capacity of the registry or of the pools for the given component + pub fn reserve(self: *Self, comptime T: type, cap: usize) void { + self.assure(T).reserve(cap); + } + + /// Direct access to the list of components of a given pool pub fn raw(self: Registry, comptime T: type) []T { return self.assure(T).raw(); } - pub fn reserve(self: *Self, comptime T: type, cap: usize) void { - self.assure(T).reserve(cap); + /// Direct access to the list of entities of a given pool + pub fn data(self: Registry, comptime T: type) []Entity { + return self.assure(T).data(); } pub fn valid(self: *Registry, entity: Entity) bool { @@ -122,10 +131,10 @@ pub const Registry = struct { self.add(entity, value); } + /// Replaces the given component for an entity pub fn replace(self: *Registry, entity: Entity, value: var) void { assert(self.valid(entity)); - var ptr = self.assure(@TypeOf(value)).get(entity); - ptr.* = value; + self.assure(@TypeOf(value)).replace(entity, value); } /// shortcut for replacing raw comptime_int/float without having to @as cast @@ -165,7 +174,13 @@ pub const Registry = struct { /// Removes all the components from an entity and makes it orphaned pub fn removeAll(self: *Registry, entity: Entity) void { assert(self.valid(entity)); - // unreachable; + + var it = self.components.iterator(); + while (it.next()) |ptr| { + // HACK: we dont know the Type here but we need to be able to call methods on the Storage(T) + var store = @intToPtr(*Storage(u128), ptr.value); + if (store.contains(entity)) store.remove(entity); + } } pub fn has(self: *Registry, comptime T: type, entity: Entity) bool { @@ -194,6 +209,21 @@ pub const Registry = struct { return self.assure(T).tryGet(entity); } + /// Returns a Sink object for the given component to add/remove listeners with + pub fn onConstruct(self: *Self, comptime T: type) Sink(Entity) { + return self.assure(T).onConstruct(); + } + + /// Returns a Sink object for the given component to add/remove listeners with + pub fn onUpdate(self: *Self, comptime T: type) Sink(Entity) { + return self.assure(T).onUpdate(); + } + + /// Returns a Sink object for the given component to add/remove listeners with + pub fn onDestruct(self: *Self, comptime T: type) Sink(Entity) { + return self.assure(T).onDestruct(); + } + /// Binds an object to the context of the registry pub fn setContext(self: *Registry, context: var) void { std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); @@ -291,3 +321,40 @@ test "component context get/set/unset" { ctx = reg.getContext(SomeType); std.testing.expectEqual(ctx, null); } + +test "destroy" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var i = @as(u8, 0); + while (i < 255) : (i += 1) { + const e = reg.create(); + reg.add(e, Position{ .x = @intToFloat(f32, i), .y = @intToFloat(f32, i) }); + } + + reg.destroy(3); + reg.destroy(4); + + i = 0; + while (i < 6) : (i += 1) { + if (i != 3 and i != 4) + std.testing.expectEqual(Position{ .x = @intToFloat(f32, i), .y = @intToFloat(f32, i)}, reg.getConst(Position, i)); + } +} + +test "remove all" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var e = reg.create(); + reg.add(e, Position{.x = 1, .y = 1}); + reg.addTyped(u32, e, 666); + + std.testing.expect(reg.has(Position, e)); + std.testing.expect(reg.has(u32, e)); + + reg.removeAll(e); + + std.testing.expect(!reg.has(Position, e)); + std.testing.expect(!reg.has(u32, e)); +} \ No newline at end of file diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index bc0fc28..fcf2967 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -85,7 +85,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { fn assure(self: *Self, pos: usize) []DenseT { // TODO: support paging - if (self.sparse.capacity < pos or self.sparse.capacity == 0) { + if (self.sparse.capacity <= pos or self.sparse.capacity == 0) { const amount = pos + 1 - self.sparse.capacity; // expand and fill with maxInt as an identifier diff --git a/zig-ecs/src/signals/signal.zig b/zig-ecs/src/signals/signal.zig index f8b923f..c14bf8b 100644 --- a/zig-ecs/src/signals/signal.zig +++ b/zig-ecs/src/signals/signal.zig @@ -10,7 +10,7 @@ pub fn Signal(comptime Event: type) type { allocator: ?*std.mem.Allocator = null, pub fn init(allocator: *std.mem.Allocator) Self { - // we purposely do not store the allocator locally in this case! + // we purposely do not store the allocator locally in this case so we know not to destroy ourself in deint! return Self{ .calls = std.ArrayList(Delegate(Event)).init(allocator), };