zig-raylib-engine/zig-ecs/src/ecs/component_storage.zig

453 lines
16 KiB
Zig
Raw Normal View History

2020-05-31 21:28:29 -07:00
const std = @import("std");
const warn = std.debug.warn;
const utils = @import("utils.zig");
const SparseSet = @import("sparse_set.zig").SparseSet;
2020-06-01 20:05:07 -07:00
const Signal = @import("../signals/signal.zig").Signal;
const Sink = @import("../signals/sink.zig").Sink;
2020-05-31 21:28:29 -07:00
2020-06-04 19:19:29 -07:00
/// Stores an ArrayList of components along with a SparseSet of entities
2020-06-12 16:29:26 -07:00
pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type {
std.debug.assert(!utils.isComptime(Component));
2020-05-31 21:28:29 -07:00
// empty (zero-sized) structs will not have an array created
const is_empty_struct = @sizeOf(Component) == 0;
2020-05-31 21:28:29 -07:00
2020-06-12 16:29:26 -07:00
// HACK: due to this being stored as untyped ptrs, when deinit is called we are casted to a Component of some random
2020-05-31 21:28:29 -07:00
// non-zero sized type. That will make is_empty_struct false in deinit always so we can't use it. Instead, we stick
// a small dummy struct in the instances ArrayList so it can safely be deallocated.
// Perhaps we should just allocate instances with a dummy allocator or the tmp allocator?
2020-06-12 16:29:26 -07:00
comptime var ComponentOrDummy = if (is_empty_struct) struct { dummy: u1 } else Component;
2020-05-31 21:28:29 -07:00
return struct {
const Self = @This();
2020-06-12 16:29:26 -07:00
set: *SparseSet(Entity),
instances: std.ArrayList(ComponentOrDummy),
2020-05-31 21:28:29 -07:00
allocator: ?*std.mem.Allocator,
2020-06-11 22:28:29 -07:00
/// doesnt really belong here...used to denote group ownership
super: usize = 0,
2020-06-20 17:00:32 -07:00
safeDeinit: fn (*Self) void,
safeSwap: fn (*Self, Entity, Entity, bool) void,
safeRemoveIfContains: fn (*Self, Entity) void,
2020-06-12 16:29:26 -07:00
construction: Signal(Entity),
update: Signal(Entity),
destruction: Signal(Entity),
2020-05-31 21:28:29 -07:00
pub fn init(allocator: *std.mem.Allocator) Self {
var store = Self{
2020-06-12 16:29:26 -07:00
.set = SparseSet(Entity).initPtr(allocator),
2020-05-31 21:28:29 -07:00
.instances = undefined,
2020-06-20 17:00:32 -07:00
.safeDeinit = struct {
2020-05-31 21:28:29 -07:00
fn deinit(self: *Self) void {
2020-06-11 22:28:29 -07:00
if (!is_empty_struct) {
2020-05-31 21:28:29 -07:00
self.instances.deinit();
2020-06-11 22:28:29 -07:00
}
2020-05-31 21:28:29 -07:00
}
}.deinit,
2020-06-20 17:00:32 -07:00
.safeSwap = struct {
2020-06-12 20:05:55 -07:00
fn swap(self: *Self, lhs: Entity, rhs: Entity, instances_only: bool) void {
2020-06-11 22:28:29 -07:00
if (!is_empty_struct) {
2020-06-12 16:29:26 -07:00
std.mem.swap(Component, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]);
2020-06-11 22:28:29 -07:00
}
2020-06-12 20:05:55 -07:00
if (!instances_only) self.set.swap(lhs, rhs);
2020-06-03 23:33:59 -07:00
}
}.swap,
2020-06-20 17:00:32 -07:00
.safeRemoveIfContains = struct {
fn removeIfContains(self: *Self, entity: Entity) void {
if (self.contains(entity)) {
self.remove(entity);
}
}
}.removeIfContains,
2020-05-31 21:28:29 -07:00
.allocator = null,
2020-06-12 16:29:26 -07:00
.construction = Signal(Entity).init(allocator),
.update = Signal(Entity).init(allocator),
.destruction = Signal(Entity).init(allocator),
2020-05-31 21:28:29 -07:00
};
2020-06-11 22:28:29 -07:00
if (!is_empty_struct) {
2020-06-12 16:29:26 -07:00
store.instances = std.ArrayList(ComponentOrDummy).init(allocator);
2020-06-11 22:28:29 -07:00
}
2020-05-31 21:28:29 -07:00
return store;
}
pub fn initPtr(allocator: *std.mem.Allocator) *Self {
var store = allocator.create(Self) catch unreachable;
2020-06-12 16:29:26 -07:00
store.set = SparseSet(Entity).initPtr(allocator);
2020-06-11 22:28:29 -07:00
if (!is_empty_struct) {
2020-06-12 16:29:26 -07:00
store.instances = std.ArrayList(ComponentOrDummy).init(allocator);
2020-06-11 22:28:29 -07:00
}
2020-05-31 21:28:29 -07:00
store.allocator = allocator;
2020-06-07 17:28:42 -07:00
store.super = 0;
2020-06-12 16:29:26 -07:00
store.construction = Signal(Entity).init(allocator);
store.update = Signal(Entity).init(allocator);
store.destruction = Signal(Entity).init(allocator);
2020-05-31 21:28:29 -07:00
// since we are stored as a pointer, we need to catpure this
2020-06-20 17:00:32 -07:00
store.safeDeinit = struct {
2020-05-31 21:28:29 -07:00
fn deinit(self: *Self) void {
2020-06-11 22:28:29 -07:00
if (!is_empty_struct) {
2020-05-31 21:28:29 -07:00
self.instances.deinit();
2020-06-11 22:28:29 -07:00
}
2020-05-31 21:28:29 -07:00
}
}.deinit;
2020-06-20 17:00:32 -07:00
store.safeSwap = struct {
2020-06-12 20:05:55 -07:00
fn swap(self: *Self, lhs: Entity, rhs: Entity, instances_only: bool) void {
2020-06-11 17:16:28 -07:00
if (!is_empty_struct) {
2020-06-12 16:29:26 -07:00
std.mem.swap(Component, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]);
2020-06-11 17:16:28 -07:00
}
2020-06-12 20:05:55 -07:00
if (!instances_only) self.set.swap(lhs, rhs);
2020-06-03 23:33:59 -07:00
}
}.swap;
2020-06-20 17:00:32 -07:00
store.safeRemoveIfContains = struct {
fn removeIfContains(self: *Self, entity: Entity) void {
if (self.contains(entity)) {
self.remove(entity);
}
}
}.removeIfContains;
2020-05-31 21:28:29 -07:00
return store;
}
pub fn deinit(self: *Self) void {
// great care must be taken here. Due to how Registry keeps this struct as pointers anything touching a type
// will be wrong since it has to cast to a random struct when deiniting. Because of all that, is_empty_struct
// will allways be false here so we have to deinit the instances no matter what.
2020-06-20 17:00:32 -07:00
self.safeDeinit(self);
2020-05-31 21:28:29 -07:00
self.set.deinit();
2020-06-01 20:05:07 -07:00
self.construction.deinit();
self.update.deinit();
self.destruction.deinit();
2020-05-31 21:28:29 -07:00
2020-06-11 17:16:28 -07:00
if (self.allocator) |allocator| {
2020-05-31 21:28:29 -07:00
allocator.destroy(self);
2020-06-11 17:16:28 -07:00
}
2020-05-31 21:28:29 -07:00
}
2020-06-12 16:29:26 -07:00
pub fn onConstruct(self: *Self) Sink(Entity) {
2020-06-01 20:05:07 -07:00
return self.construction.sink();
}
2020-06-12 16:29:26 -07:00
pub fn onUpdate(self: *Self) Sink(Entity) {
2020-06-01 20:05:07 -07:00
return self.update.sink();
}
2020-06-12 16:29:26 -07:00
pub fn onDestruct(self: *Self) Sink(Entity) {
2020-06-01 20:05:07 -07:00
return self.destruction.sink();
}
2020-05-31 21:28:29 -07:00
/// Increases the capacity of a component storage
pub fn reserve(self: *Self, cap: usize) void {
self.set.reserve(cap);
2020-06-11 17:16:28 -07:00
if (!is_empty_struct) {
2021-10-06 12:23:57 +08:00
self.instances.items.reserve(cap);
2020-06-11 17:16:28 -07:00
}
2020-05-31 21:28:29 -07:00
}
2020-06-01 20:05:07 -07:00
/// Assigns an entity to a storage and assigns its object
2020-06-12 16:29:26 -07:00
pub fn add(self: *Self, entity: Entity, value: Component) void {
2020-06-11 17:16:28 -07:00
if (!is_empty_struct) {
2020-05-31 21:28:29 -07:00
_ = self.instances.append(value) catch unreachable;
2020-06-11 17:16:28 -07:00
}
2020-05-31 21:28:29 -07:00
self.set.add(entity);
2020-06-01 20:05:07 -07:00
self.construction.publish(entity);
}
/// Removes an entity from a storage
2020-06-12 16:29:26 -07:00
pub fn remove(self: *Self, entity: Entity) void {
2020-06-03 23:33:59 -07:00
self.destruction.publish(entity);
2020-06-11 17:16:28 -07:00
if (!is_empty_struct) {
2020-06-01 20:05:07 -07:00
_ = self.instances.swapRemove(self.set.index(entity));
2020-06-11 17:16:28 -07:00
}
2020-06-01 20:05:07 -07:00
self.set.remove(entity);
2020-05-31 21:28:29 -07:00
}
/// Checks if a view contains an entity
2020-06-12 16:29:26 -07:00
pub fn contains(self: Self, entity: Entity) bool {
2020-05-31 21:28:29 -07:00
return self.set.contains(entity);
}
2020-06-20 17:00:32 -07:00
pub fn removeIfContains(self: *Self, entity: Entity) void {
if (Component == u1) {
self.safeRemoveIfContains(self, entity);
} else if (self.contains(entity)) {
self.remove(entity);
}
}
2020-05-31 21:28:29 -07:00
pub fn len(self: Self) usize {
return self.set.len();
}
pub usingnamespace if (is_empty_struct)
2020-06-11 22:28:29 -07:00
struct {
2020-06-12 18:15:47 -07:00
/// Sort Entities according to the given comparison function. Only T == Entity is allowed. The constraint param only exists for
/// parity with non-empty Components
2020-07-20 09:53:15 -07:00
pub fn sort(self: Self, comptime T: type, context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void {
2020-06-12 18:15:47 -07:00
std.debug.assert(T == Entity);
self.set.sort(context, lessThan);
2020-06-11 22:28:29 -07:00
}
}
2020-05-31 21:28:29 -07:00
else
struct {
/// Direct access to the array of objects
2020-06-12 16:29:26 -07:00
pub fn raw(self: Self) []Component {
2020-05-31 21:28:29 -07:00
return self.instances.items;
}
2020-06-01 20:05:07 -07:00
/// Replaces the given component for an entity
2020-06-12 16:29:26 -07:00
pub fn replace(self: *Self, entity: Entity, value: Component) void {
2020-06-01 20:05:07 -07:00
self.get(entity).* = value;
self.update.publish(entity);
}
2020-05-31 21:28:29 -07:00
/// Returns the object associated with an entity
2020-06-12 16:29:26 -07:00
pub fn get(self: *Self, entity: Entity) *Component {
2020-05-31 21:28:29 -07:00
std.debug.assert(self.contains(entity));
return &self.instances.items[self.set.index(entity)];
}
2020-06-12 16:29:26 -07:00
pub fn getConst(self: *Self, entity: Entity) Component {
2020-05-31 21:28:29 -07:00
return self.instances.items[self.set.index(entity)];
}
/// Returns a pointer to the object associated with an entity, if any.
2020-06-12 16:29:26 -07:00
pub fn tryGet(self: *Self, entity: Entity) ?*Component {
2020-05-31 21:28:29 -07:00
return if (self.set.contains(entity)) &self.instances.items[self.set.index(entity)] else null;
}
2020-06-12 16:29:26 -07:00
pub fn tryGetConst(self: *Self, entity: Entity) ?Component {
2020-05-31 21:28:29 -07:00
return if (self.set.contains(entity)) self.instances.items[self.set.index(entity)] else null;
}
2020-06-11 22:28:29 -07:00
2020-06-12 20:05:55 -07:00
/// Sort Entities or Components according to the given comparison function. Valid types for T are Entity or Component.
2020-07-20 09:53:15 -07:00
pub fn sort(self: *Self, comptime T: type, length: usize, context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void {
2020-06-12 16:29:26 -07:00
std.debug.assert(T == Entity or T == Component);
2020-06-13 14:53:14 -07:00
// we have to perform a swap after the sort for all moved entities so we make a helper struct for that. In the
// case of a Component sort we also wrap that into the struct so we can get the Component data to pass to the
// lessThan method passed in.
2020-06-12 16:29:26 -07:00
if (T == Entity) {
2020-06-13 14:53:14 -07:00
const SortContext = struct {
storage: *Self,
pub fn swap(this: @This(), a: Entity, b: Entity) void {
2020-06-20 17:00:32 -07:00
this.storage.safeSwap(this.storage, a, b, true);
2020-06-13 14:53:14 -07:00
}
};
2020-06-20 17:00:32 -07:00
const swap_context = SortContext{ .storage = self };
2020-06-13 14:53:14 -07:00
self.set.arrange(length, context, lessThan, swap_context);
} else {
const SortContext = struct {
storage: *Self,
wrapped_context: @TypeOf(context),
lessThan: fn (@TypeOf(context), T, T) bool,
fn sort(this: @This(), a: Entity, b: Entity) bool {
const real_a = this.storage.getConst(a);
const real_b = this.storage.getConst(b);
return this.lessThan(this.wrapped_context, real_a, real_b);
}
pub fn swap(this: @This(), a: Entity, b: Entity) void {
2020-06-20 17:00:32 -07:00
this.storage.safeSwap(this.storage, a, b, true);
2020-06-13 14:53:14 -07:00
}
};
2020-06-20 17:00:32 -07:00
const swap_context = SortContext{ .storage = self, .wrapped_context = context, .lessThan = lessThan };
2020-06-13 14:53:14 -07:00
self.set.arrange(length, swap_context, SortContext.sort, swap_context);
2020-06-11 22:28:29 -07:00
}
}
2020-05-31 21:28:29 -07:00
};
/// Direct access to the array of entities
2020-06-12 16:29:26 -07:00
pub fn data(self: Self) []const Entity {
2020-05-31 21:28:29 -07:00
return self.set.data();
}
2020-06-09 10:21:42 -07:00
/// Direct access to the array of entities
2020-06-12 16:29:26 -07:00
pub fn dataPtr(self: Self) *const []Entity {
2020-06-09 10:21:42 -07:00
return self.set.dataPtr();
}
2020-05-31 21:28:29 -07:00
/// Swaps entities and objects in the internal packed arrays
2020-06-12 16:29:26 -07:00
pub fn swap(self: *Self, lhs: Entity, rhs: Entity) void {
2020-06-20 17:00:32 -07:00
self.safeSwap(self, lhs, rhs, false);
2020-05-31 21:28:29 -07:00
}
pub fn clear(self: *Self) void {
2020-06-11 22:28:29 -07:00
if (!is_empty_struct) {
2020-05-31 21:28:29 -07:00
self.instances.items.len = 0;
2020-06-11 22:28:29 -07:00
}
2020-05-31 21:28:29 -07:00
self.set.clear();
}
};
}
test "add/try-get/remove/clear" {
2020-06-04 19:19:29 -07:00
var store = ComponentStorage(f32, u32).init(std.testing.allocator);
2020-05-31 21:28:29 -07:00
defer store.deinit();
store.add(3, 66.45);
2021-06-29 22:26:23 -06:00
try std.testing.expectEqual(store.tryGetConst(3).?, 66.45);
2020-06-12 23:37:09 -07:00
if (store.tryGet(3)) |found| {
2021-06-29 22:26:23 -06:00
try std.testing.expectEqual(@as(f32, 66.45), found.*);
2020-06-12 23:37:09 -07:00
}
2020-05-31 21:28:29 -07:00
store.remove(3);
var val_null = store.tryGet(3);
2021-06-29 22:26:23 -06:00
try std.testing.expectEqual(val_null, null);
2020-05-31 21:28:29 -07:00
store.clear();
}
test "add/get/remove" {
2020-06-04 19:19:29 -07:00
var store = ComponentStorage(f32, u32).init(std.testing.allocator);
2020-05-31 21:28:29 -07:00
defer store.deinit();
store.add(3, 66.45);
2021-06-29 22:26:23 -06:00
if (store.tryGet(3)) |found| try std.testing.expectEqual(@as(f32, 66.45), found.*);
try std.testing.expectEqual(store.tryGetConst(3).?, 66.45);
2020-05-31 21:28:29 -07:00
store.remove(3);
2021-06-29 22:26:23 -06:00
try std.testing.expectEqual(store.tryGet(3), null);
2020-05-31 21:28:29 -07:00
}
test "iterate" {
2020-06-04 19:19:29 -07:00
var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator);
2020-05-31 21:28:29 -07:00
defer store.deinit();
store.add(3, 66.45);
store.add(5, 66.45);
store.add(7, 66.45);
2020-06-09 10:21:42 -07:00
for (store.data()) |entity, i| {
2020-06-11 22:28:29 -07:00
if (i == 0) {
2021-06-29 22:26:23 -06:00
try std.testing.expectEqual(entity, 3);
2020-06-11 22:28:29 -07:00
}
if (i == 1) {
2021-06-29 22:26:23 -06:00
try std.testing.expectEqual(entity, 5);
2020-06-11 22:28:29 -07:00
}
if (i == 2) {
2021-06-29 22:26:23 -06:00
try std.testing.expectEqual(entity, 7);
2020-06-11 22:28:29 -07:00
}
2020-05-31 21:28:29 -07:00
}
}
test "empty component" {
const Empty = struct {};
2020-06-04 19:19:29 -07:00
var store = ComponentStorage(Empty, u32).initPtr(std.testing.allocator);
2020-05-31 21:28:29 -07:00
defer store.deinit();
store.add(3, Empty{});
store.remove(3);
}
2020-06-01 20:05:07 -07:00
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" {
2020-06-04 19:19:29 -07:00
var store = ComponentStorage(f32, u32).init(std.testing.allocator);
2020-06-01 20:05:07 -07:00
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);
}
2020-06-11 22:28:29 -07:00
test "sort empty component" {
const Empty = struct {};
var store = ComponentStorage(Empty, u32).initPtr(std.testing.allocator);
defer store.deinit();
store.add(1, Empty{});
store.add(2, Empty{});
store.add(0, Empty{});
const asc_u32 = comptime std.sort.asc(u32);
2020-06-12 18:15:47 -07:00
store.sort(u32, {}, asc_u32);
2020-06-11 22:28:29 -07:00
for (store.data()) |e, i| {
2021-06-29 22:26:23 -06:00
try std.testing.expectEqual(@intCast(u32, i), e);
2020-06-11 22:28:29 -07:00
}
const desc_u32 = comptime std.sort.desc(u32);
2020-06-12 18:15:47 -07:00
store.sort(u32, {}, desc_u32);
2020-06-11 22:28:29 -07:00
var counter: u32 = 2;
for (store.data()) |e| {
2021-06-29 22:26:23 -06:00
try std.testing.expectEqual(counter, e);
2020-06-11 22:28:29 -07:00
if (counter > 0) counter -= 1;
}
}
2020-06-12 18:15:47 -07:00
test "sort by entity" {
2020-06-12 13:47:53 -07:00
var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator);
defer store.deinit();
store.add(22, @as(f32, 2.2));
store.add(11, @as(f32, 1.1));
store.add(33, @as(f32, 3.3));
2020-06-12 20:05:55 -07:00
const SortContext = struct {
2020-06-12 18:15:47 -07:00
store: *ComponentStorage(f32, u32),
fn sort(this: @This(), a: u32, b: u32) bool {
const real_a = this.store.getConst(a);
const real_b = this.store.getConst(b);
return real_a > real_b;
}
};
2020-06-12 20:05:55 -07:00
const context = SortContext{ .store = store };
2020-06-12 23:37:09 -07:00
store.sort(u32, store.len(), context, SortContext.sort);
2020-06-12 18:15:47 -07:00
var compare: f32 = 5;
for (store.raw()) |val| {
2021-06-29 22:26:23 -06:00
try std.testing.expect(compare > val);
2020-06-12 18:15:47 -07:00
compare = val;
}
}
test "sort by component" {
var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator);
defer store.deinit();
store.add(22, @as(f32, 2.2));
store.add(11, @as(f32, 1.1));
store.add(33, @as(f32, 3.3));
const desc_f32 = comptime std.sort.desc(f32);
2020-06-12 23:37:09 -07:00
store.sort(f32, store.len(), {}, desc_f32);
2020-06-12 13:47:53 -07:00
var compare: f32 = 5;
for (store.raw()) |val| {
2021-06-29 22:26:23 -06:00
try std.testing.expect(compare > val);
2020-06-12 13:47:53 -07:00
compare = val;
}
}