add signals

master
Mike 5 years ago
parent 5bf1754dfb
commit 0b2542d292

@ -3,7 +3,11 @@ const warn = std.debug.warn;
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const SparseSet = @import("sparse_set.zig").SparseSet; 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 { pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime DenseT: type) type {
std.debug.assert(!utils.isComptime(CompT)); 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), instances: std.ArrayList(CompOrAlmostEmptyT),
allocator: ?*std.mem.Allocator, allocator: ?*std.mem.Allocator,
safe_deinit: fn (*Self) void, safe_deinit: fn (*Self) void,
construction: Signal(EntityT),
update: Signal(EntityT),
destruction: Signal(EntityT),
pub fn init(allocator: *std.mem.Allocator) Self { pub fn init(allocator: *std.mem.Allocator) Self {
var store = Self{ var store = Self{
@ -37,6 +44,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D
} }
}.deinit, }.deinit,
.allocator = null, .allocator = null,
.construction = Signal(EntityT).init(allocator),
.update = Signal(EntityT).init(allocator),
.destruction = Signal(EntityT).init(allocator),
}; };
if (!is_empty_struct) if (!is_empty_struct)
@ -51,6 +61,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D
if (!is_empty_struct) if (!is_empty_struct)
store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator);
store.allocator = 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 // since we are stored as a pointer, we need to catpure this
store.safe_deinit = struct { 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. // will allways be false here so we have to deinit the instances no matter what.
self.safe_deinit(self); self.safe_deinit(self);
self.set.deinit(); self.set.deinit();
self.construction.deinit();
self.update.deinit();
self.destruction.deinit();
if (self.allocator) |allocator| if (self.allocator) |allocator|
allocator.destroy(self); 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 /// Increases the capacity of a component storage
pub fn reserve(self: *Self, cap: usize) void { pub fn reserve(self: *Self, cap: usize) void {
self.set.reserve(cap); self.set.reserve(cap);
@ -81,11 +109,20 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D
self.instances.items.reserve(cap); 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 { pub fn add(self: *Self, entity: EntityT, value: CompT) void {
if (!is_empty_struct) if (!is_empty_struct)
_ = self.instances.append(value) catch unreachable; _ = self.instances.append(value) catch unreachable;
self.set.add(entity); 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 /// 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; 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 /// Returns the object associated with an entity
pub fn get(self: *Self, entity: EntityT) *CompT { pub fn get(self: *Self, entity: EntityT) *CompT {
std.debug.assert(self.contains(entity)); 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(); 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 /// Swaps entities and objects in the internal packed arrays
pub fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { pub fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void {
if (!is_empty_struct) if (!is_empty_struct)
@ -208,3 +244,34 @@ test "empty component" {
store.add(3, Empty{}); store.add(3, Empty{});
store.remove(3); 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);
}

@ -70,20 +70,29 @@ pub const Registry = struct {
return @intToPtr(*Storage(T), ptr); return @intToPtr(*Storage(T), ptr);
} }
/// Prepares a pool for the given type if required
pub fn prepare(self: *Registry, comptime T: type) void { 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 { pub fn len(self: *Registry, comptime T: type) usize {
self.assure(T).len(); 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 { pub fn raw(self: Registry, comptime T: type) []T {
return self.assure(T).raw(); return self.assure(T).raw();
} }
pub fn reserve(self: *Self, comptime T: type, cap: usize) void { /// Direct access to the list of entities of a given pool
self.assure(T).reserve(cap); pub fn data(self: Registry, comptime T: type) []Entity {
return self.assure(T).data();
} }
pub fn valid(self: *Registry, entity: Entity) bool { pub fn valid(self: *Registry, entity: Entity) bool {
@ -122,10 +131,10 @@ pub const Registry = struct {
self.add(entity, value); self.add(entity, value);
} }
/// Replaces the given component for an entity
pub fn replace(self: *Registry, entity: Entity, value: var) void { pub fn replace(self: *Registry, entity: Entity, value: var) void {
assert(self.valid(entity)); assert(self.valid(entity));
var ptr = self.assure(@TypeOf(value)).get(entity); self.assure(@TypeOf(value)).replace(entity, value);
ptr.* = value;
} }
/// shortcut for replacing raw comptime_int/float without having to @as cast /// 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 /// Removes all the components from an entity and makes it orphaned
pub fn removeAll(self: *Registry, entity: Entity) void { pub fn removeAll(self: *Registry, entity: Entity) void {
assert(self.valid(entity)); 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 { 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); 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 /// Binds an object to the context of the registry
pub fn setContext(self: *Registry, context: var) void { pub fn setContext(self: *Registry, context: var) void {
std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer);
@ -291,3 +321,40 @@ test "component context get/set/unset" {
ctx = reg.getContext(SomeType); ctx = reg.getContext(SomeType);
std.testing.expectEqual(ctx, null); 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));
}

@ -85,7 +85,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type {
fn assure(self: *Self, pos: usize) []DenseT { fn assure(self: *Self, pos: usize) []DenseT {
// TODO: support paging // 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; const amount = pos + 1 - self.sparse.capacity;
// expand and fill with maxInt as an identifier // expand and fill with maxInt as an identifier

@ -10,7 +10,7 @@ pub fn Signal(comptime Event: type) type {
allocator: ?*std.mem.Allocator = null, allocator: ?*std.mem.Allocator = null,
pub fn init(allocator: *std.mem.Allocator) Self { 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{ return Self{
.calls = std.ArrayList(Delegate(Event)).init(allocator), .calls = std.ArrayList(Delegate(Event)).init(allocator),
}; };

Loading…
Cancel
Save