diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 217a9a4..0e3647c 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -29,6 +29,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D instances: std.ArrayList(CompOrAlmostEmptyT), allocator: ?*std.mem.Allocator, safe_deinit: fn (*Self) void, + safe_swap: fn (*Self, EntityT, EntityT) void, construction: Signal(EntityT), update: Signal(EntityT), destruction: Signal(EntityT), @@ -43,6 +44,13 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D self.instances.deinit(); } }.deinit, + .safe_swap = struct { + fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { + if (!is_empty_struct) + std.mem.swap(CompT, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]); + self.set.swap(lhs, rhs); + } + }.swap, .allocator = null, .construction = Signal(EntityT).init(allocator), .update = Signal(EntityT).init(allocator), @@ -73,6 +81,14 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D } }.deinit; + store.safe_swap = struct { + fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { + if (!is_empty_struct) + std.mem.swap(CompT, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]); + self.set.swap(lhs, rhs); + } + }.swap; + return store; } @@ -119,9 +135,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D /// Removes an entity from a storage pub fn remove(self: *Self, entity: EntityT) void { + self.destruction.publish(entity); if (!is_empty_struct) _ = self.instances.swapRemove(self.set.index(entity)); - self.destruction.publish(entity); self.set.remove(entity); } @@ -176,9 +192,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D /// Swaps entities and objects in the internal packed arrays pub fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { - if (!is_empty_struct) - std.mem.swap(CompT, &self.instances[self.set.index(lhs)], &self.instances[self.set.index(rhs)]); - self.set.swap(lhs, rhs); + self.safe_swap(self, lhs, rhs); } pub fn clear(self: *Self) void { diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 02428d4..f378e7a 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -73,6 +73,32 @@ pub fn BasicGroup(comptime n_includes: usize, comptime n_excludes: usize) type { }; } +pub fn OwningGroup(comptime n_owned: usize, comptime n_includes: usize, comptime n_excludes: usize) type { + return struct { + const Self = @This(); + + current: *usize, + registry: *Registry, + owned_type_ids: [n_owned]u32, + include_type_ids: [n_includes]u32, + exclude_type_ids: [n_excludes]u32, + + pub fn init(current: *usize, registry: *Registry, owned_type_ids: [n_owned]u32, include_type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32) Self { + return Self{ + .current = current, + .registry = registry, + .owned_type_ids = owned_type_ids, + .include_type_ids = include_type_ids, + .exclude_type_ids = exclude_type_ids, + }; + } + + pub fn len(self: Self) usize { + return self.current.*; + } + }; +} + test "BasicGroup creation" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); @@ -130,4 +156,31 @@ test "BasicGroup create late" { var group = reg.group(.{}, .{ i32, u32 }, .{}); std.testing.expectEqual(group.len(), 1); +} + +test "OwningGroup" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var group = reg.group(.{i32, u32}, .{}, .{}); + + var e0 = reg.create(); + reg.add(e0, @as(i32, 44)); + reg.add(e0, @as(u32, 55)); + std.testing.expectEqual(group.len(), 1); +} + +test "OwningGroup add/remove" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var group = reg.group(.{i32, u32}, .{}, .{}); + + var e0 = reg.create(); + reg.add(e0, @as(i32, 44)); + reg.add(e0, @as(u32, 55)); + std.testing.expectEqual(group.len(), 1); + + reg.remove(i32, e0); + std.testing.expectEqual(group.len(), 0); } \ No newline at end of file diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 827b9a3..7f81ad9 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -19,6 +19,7 @@ pub const Entity = entity_traits.entity_type; pub const BasicView = @import("views.zig").BasicView; pub const MultiView = @import("views.zig").MultiView; pub const BasicGroup = @import("groups.zig").BasicGroup; +pub const OwningGroup = @import("groups.zig").OwningGroup; /// Stores an ArrayList of components. The max amount that can be stored is based on the type below pub fn Storage(comptime CompT: type) type { @@ -39,11 +40,12 @@ pub const Registry = struct { /// internal, persistant data structure to manage the entities in a group const GroupData = struct { hash: u32, - entity_set: SparseSet(Entity, u16), // TODO: dont hardcode this. put it in EntityTraits maybe. All SparseSets would need to use the value. + entity_set: SparseSet(Entity, u16) = undefined, // TODO: dont hardcode this. put it in EntityTraits maybe. All SparseSets would need to use the value. owned: []u32, include: []u32, exclude: []u32, registry: *Registry, + current: usize, pub fn initPtr(allocator: *std.mem.Allocator, registry: *Registry, hash: u32, owned: []u32, include: []u32, exclude: []u32) *GroupData { std.debug.assert(std.mem.indexOfAny(u32, owned, include) == null); @@ -52,17 +54,23 @@ pub const Registry = struct { var group_data = allocator.create(GroupData) catch unreachable; group_data.hash = hash; - group_data.entity_set = SparseSet(Entity, u16).init(allocator); + if (owned.len == 0) { + group_data.entity_set = SparseSet(Entity, u16).init(allocator); + } group_data.owned = std.mem.dupe(allocator, u32, owned) catch unreachable; group_data.include = std.mem.dupe(allocator, u32, include) catch unreachable; group_data.exclude = std.mem.dupe(allocator, u32, exclude) catch unreachable; group_data.registry = registry; + group_data.current = 0; return group_data; } pub fn deinit(self: *GroupData, allocator: *std.mem.Allocator) void { - self.entity_set.deinit(); + // only deinit th SparseSet for non-owning groups + if (self.owned.len == 0) { + self.entity_set.deinit(); + } allocator.free(self.owned); allocator.free(self.include); allocator.free(self.exclude); @@ -95,6 +103,17 @@ pub const Registry = struct { if (isValid and !self.entity_set.contains(entity)) self.entity_set.add(entity); } else { + if (isValid) { + const ptr = self.registry.components.getValue(@intCast(u8, self.owned[0])).?; + if (!(@intToPtr(*Storage(u1), ptr).set.index(entity) < self.current)) { + for (self.owned) |tid| { + const store_ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + var store = @intToPtr(*Storage(u1), store_ptr); + store.swap(store.data().*[self.current], entity); + } + self.current += 1; + } + } std.debug.assert(self.owned.len >= 0); } } @@ -104,7 +123,17 @@ pub const Registry = struct { if (self.entity_set.contains(entity)) self.entity_set.remove(entity); } else { - std.debug.assert(self.owned.len == 0); + const ptr = self.registry.components.getValue(@intCast(u8, self.owned[0])).?; + var store = @intToPtr(*Storage(u1), ptr); + if (store.contains(entity) and store.set.index(entity) < self.current) { + self.current -= 1; + for (self.owned) |tid| { + const store_ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + store = @intToPtr(*Storage(u1), store_ptr); + std.debug.warn("\n-------- len: {}, curr: {}, ent: {} \n", .{store.data().*.len, self.current, entity}); + store.swap(store.data().*[self.current], entity); + } + } } } }; @@ -336,6 +365,11 @@ pub const Registry = struct { null; } + /// Checks whether the given component belongs to any group + pub fn sortable(self: Registry, comptime T: type) bool { + return true; + } + pub fn view(self: *Registry, comptime includes: var, comptime excludes: var) ViewType(includes, excludes) { if (@typeInfo(@TypeOf(includes)) != .Struct) @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); @@ -412,12 +446,12 @@ pub const Registry = struct { if (owned.len == 0) { return BasicGroup(includes.len, excludes.len).init(&group_data.entity_set, self, includes_arr, excludes_arr); } else { - @compileLog("owned groups not implemented"); + return OwningGroup(owned.len, includes.len, excludes.len).init(&group_data.current, self, owned_arr, includes_arr, excludes_arr); } } // we need to create a new GroupData - var new_group_data = GroupData.initPtr(self.allocator, self, hash, &[_]u32{}, includes_arr[0..], excludes_arr[0..]); + var new_group_data = GroupData.initPtr(self.allocator, self, hash, owned_arr[0..], includes_arr[0..], excludes_arr[0..]); self.groups.append(new_group_data) catch unreachable; // wire up our listeners @@ -437,15 +471,19 @@ pub const Registry = struct { new_group_data.entity_set.add(entity); } } else { - unreachable; + } - return BasicGroup(includes.len, excludes.len).init(&new_group_data.entity_set, self, includes_arr, excludes_arr); + if (owned.len == 0) { + return BasicGroup(includes.len, excludes.len).init(&new_group_data.entity_set, self, includes_arr, excludes_arr); + } else { + return OwningGroup(owned.len, includes.len, excludes.len).init(&new_group_data.current, self, owned_arr, includes_arr, excludes_arr); + } } /// returns the Type that a view will be based on the includes and excludes fn GroupType(comptime owned: var, comptime includes: var, comptime excludes: var) type { if (owned.len == 0) return BasicGroup(includes.len, excludes.len); - unreachable; + return OwningGroup(owned.len, includes.len, excludes.len); } };