diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 8fe80ef..f1de8e0 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -6,35 +6,14 @@ const Storage = @import("registry.zig").Storage; const SparseSet = @import("sparse_set.zig").SparseSet; const Entity = @import("registry.zig").Entity; -/// BasicGroups do not own any components +/// BasicGroups do not own any components. Internally, they keep a SparseSet that is always kept up-to-date with the matching +/// entities. pub const BasicGroup = struct { const Self = @This(); registry: *Registry, group_data: *Registry.GroupData, - // TODO: do we even need an iterator for a group? - pub const Iterator = struct { - index: usize = 0, - entities: []const Entity, // TODO: should this be a pointer to the slice? - - pub fn init(entities: []const Entity) Iterator { - return .{ .entities = entities }; - } - - pub fn next(it: *Iterator) ?Entity { - if (it.index >= it.entities.len) return null; - - it.index += 1; - return it.entities[it.index - 1]; - } - - // Reset the iterator to the initial index - pub fn reset(it: *Iterator) void { - it.index = 0; - } - }; - pub fn init(registry: *Registry, group_data: *Registry.GroupData) Self { return Self{ .registry = registry, @@ -59,8 +38,10 @@ pub const BasicGroup = struct { return self.registry.assure(T).getConst(entity); } - pub fn iterator(self: *Self) Iterator { - return Iterator.init(self.group_data.entity_set.data()); + /// iterates the matched entities backwards, so the current entity can always be removed safely + /// and newly added entities wont affect it. + pub fn iterator(self: Self) utils.ReverseSliceIterator(Entity) { + return self.group_data.entity_set.reverseIterator(); } }; @@ -69,10 +50,13 @@ pub const OwningGroup = struct { group_data: *Registry.GroupData, super: *usize, + /// iterator the provides the data from all the requested owned components in a single struct. Access to the current Entity + /// being iterated is available via the entity() method, useful for accessing non-owned component data. The get() method can + /// also be used to fetch non-owned component data for the currently iterated Entity. fn Iterator(comptime Components: var) type { return struct { - index: usize = 0, group: OwningGroup, + index: usize, storage: *Storage(u1), component_ptrs: [@typeInfo(Components).Struct.fields.len][*]u8, @@ -87,17 +71,18 @@ pub const OwningGroup = struct { return .{ .group = group, + .index = group.group_data.current, .storage = group.firstOwnedStorage(), .component_ptrs = component_ptrs, }; } pub fn next(it: *@This()) ?Components { - if (it.index >= it.group.group_data.current) return null; + if (it.index == 0) return null; + it.index -= 1; const ent = it.storage.set.dense.items[it.index]; const entity_index = it.storage.set.index(ent); - it.index += 1; // fill and return the struct var comps: Components = undefined; @@ -109,13 +94,17 @@ pub const OwningGroup = struct { } pub fn entity(it: @This()) Entity { - std.debug.assert(it.index > 0 and it.index <= it.group.group_data.current); - return it.storage.set.dense.items[it.index - 1]; + std.debug.assert(it.index >= 0 and it.index < it.group.group_data.current); + return it.storage.set.dense.items[it.index]; + } + + pub fn get(it: @This(), comptime T: type) *T { + return it.group.registry.get(T, it.entity()); } // Reset the iterator to the initial index pub fn reset(it: *@This()) void { - it.index = 0; + it.index = it.group.group_data.current; } }; } @@ -204,8 +193,11 @@ pub const OwningGroup = struct { } var storage = self.firstOwnedStorage(); - var index: usize = 0; - while (index < self.group_data.current) : (index += 1) { + var index: usize = self.group_data.current; + while (true) { + if (index == 0) return; + index -= 1; + const ent = storage.set.dense.items[index]; const entity_index = storage.set.index(ent); @@ -236,12 +228,16 @@ pub const OwningGroup = struct { return self.group_data.super == self.group_data.size; } - /// returns an iterator with optimized access to the Components. Note that Components should be a struct with + /// returns an iterator with optimized access to the owend Components. Note that Components should be a struct with /// fields that are pointers to the component types that you want to fetch. Only types that are owned are valid! pub fn iterator(self: OwningGroup, comptime Components: var) Iterator(Components) { self.validate(Components); return Iterator(Components).init(self); } + + pub fn entityIterator(self: OwningGroup) utils.ReverseSliceIterator(Entity) { + return utils.ReverseSliceIterator(Entity).init(self.firstOwnedStorage().set.dense.items[0..self.group_data.current]); + } }; test "BasicGroup creation/iteration" { @@ -344,7 +340,7 @@ test "OwningGroup add/remove" { reg.add(e0, @as(u32, 55)); std.testing.expectEqual(group.len(), 1); - reg.remove(i32, e0); + reg.remove(u32, e0); std.testing.expectEqual(group.len(), 0); } @@ -355,20 +351,24 @@ test "OwningGroup iterate" { var e0 = reg.create(); reg.add(e0, @as(i32, 44)); reg.add(e0, @as(u32, 55)); + reg.add(e0, @as(u8, 11)); var e1 = reg.create(); reg.add(e1, @as(i32, 666)); reg.add(e1, @as(u32, 999)); + reg.add(e1, @as(f32, 55.5)); var group = reg.group(.{ i32, u32 }, .{}, .{}); var iter = group.iterator(struct { int: *i32, uint: *u32 }); while (iter.next()) |item| { - if (iter.entity() == 0) { + if (iter.entity() == e0) { std.testing.expectEqual(item.int.*, 44); std.testing.expectEqual(item.uint.*, 55); + std.testing.expectEqual(iter.get(u8).*, 11); } else { std.testing.expectEqual(item.int.*, 666); std.testing.expectEqual(item.uint.*, 999); + std.testing.expectEqual(iter.get(f32).*, 55.5); } } } @@ -421,11 +421,11 @@ test "multiple OwningGroups" { var group3 = reg.group(.{Sprite}, .{Renderable}, .{}); var group4 = reg.group(.{ Sprite, Transform }, .{Renderable}, .{}); + // ensure groups are ordered correctly internally var last_size: u8 = 0; for (reg.groups.items) |grp| { std.testing.expect(last_size <= grp.size); last_size = grp.size; - std.debug.warn("grp: {}\n", .{grp.size}); } std.testing.expect(!reg.sortable(Sprite)); diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 91d3cbb..e39571c 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -1,5 +1,6 @@ const std = @import("std"); const warn = std.debug.warn; +const ReverseSliceIterator = @import("utils.zig").ReverseSliceIterator; // TODO: fix entity_mask. it should come from EntityTraitsDefinition. pub fn SparseSet(comptime SparseT: type) type { @@ -168,6 +169,10 @@ pub fn SparseSet(comptime SparseT: type) type { self.sparse.items.len = 0; self.dense.items.len = 0; } + + pub fn reverseIterator(self: *Self) ReverseSliceIterator(SparseT) { + return ReverseSliceIterator(SparseT).init(self.dense.items); + } }; } @@ -245,7 +250,24 @@ test "data() synced" { std.testing.expectEqual(set.len(), set.data().len); } -test "respect" { +test "iterate" { + var set = SparseSet(u32).initPtr(std.testing.allocator); + defer set.deinit(); + + set.add(0); + set.add(1); + set.add(2); + set.add(3); + + var i: u32 = @intCast(u32, set.len()) - 1; + var iter = set.reverseIterator(); + while (iter.next()) |entity| { + std.testing.expectEqual(i, entity); + if (i > 0) i -= 1; + } +} + +test "respect 1" { var set1 = SparseSet(u32).initPtr(std.testing.allocator); defer set1.deinit(); @@ -268,7 +290,7 @@ test "respect" { std.testing.expectEqual(set1.dense.items[1], set2.dense.items[2]); } -test "respect" { +test "respect 2" { var set = SparseSet(u32).initPtr(std.testing.allocator); defer set.deinit(); diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 0f7cb33..0f45efc 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -23,6 +23,31 @@ pub const ErasedPtr = struct { } }; +pub fn ReverseSliceIterator(comptime T: type) type { + return struct { + slice: []T, + index: usize, + + pub fn init(slice: []T) @This() { + return .{ + .slice = slice, + .index = slice.len, + }; + } + + pub fn next(self: *@This()) ?T { + if (self.index == 0) return null; + self.index -= 1; + + return self.slice[self.index]; + } + + pub fn reset(self: *@This()) void { + self.index = slice.len; + } + }; +} + /// sorts items using lessThan and keeps sub_items with the same sort pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, lessThan: fn (lhs: T1, rhs: T1) bool) void { var i: usize = 1; @@ -80,4 +105,20 @@ pub fn isComptime(comptime T: type) bool { .ComptimeInt, .ComptimeFloat => true, else => false, }; -} \ No newline at end of file +} + +test "ReverseSliceIterator" { + var slice = std.testing.allocator.alloc(usize, 10) catch unreachable; + defer std.testing.allocator.free(slice); + + for (slice) |*item, i| { + item.* = i; + } + + var iter = ReverseSliceIterator(usize).init(slice); + var i: usize = 9; + while (iter.next()) |val| { + std.testing.expectEqual(i, val); + if (i > 0) i -= 1; + } +} diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index 0901b73..505973d 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -5,7 +5,9 @@ const Registry = @import("registry.zig").Registry; const Storage = @import("registry.zig").Storage; const Entity = @import("registry.zig").Entity; -/// single item view. Iterating raw() directly is the fastest way to get at the data. +/// single item view. Iterating raw() directly is the fastest way to get at the data. An iterator is also available to iterate +/// either the Entities or the Components. If T is sorted note that raw() will be in the reverse order so it should be looped +/// backwards. The iterators will return data in the sorted order though. pub fn BasicView(comptime T: type) type { return struct { const Self = @This(); @@ -37,9 +39,17 @@ pub fn BasicView(comptime T: type) type { return self.storage.get(entity); } - pub fn getConst(self: *Self, comptime T: type, entity: Entity) T { + pub fn getConst(self: *Self, entity: Entity) T { return self.storage.getConst(entity); } + + pub fn entityIterator(self: Self) utils.ReverseSliceIterator(Entity) { + return self.storage.set.reverseIterator(); + } + + pub fn componentIterator(self: Self) utils.ReverseSliceIterator(T) { + return utils.ReverseSliceIterator(T).init(self.storage.instances.items); + } }; } @@ -53,21 +63,24 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { pub const Iterator = struct { view: *Self, - index: usize = 0, + index: usize, entities: *const []Entity, pub fn init(view: *Self) Iterator { const ptr = view.registry.components.getValue(view.type_ids[0]).?; + const entities = @intToPtr(*Storage(u8), ptr).dataPtr(); return .{ .view = view, - .entities = @intToPtr(*Storage(u8), ptr).dataPtr(), + .index = entities.len, + .entities = entities, }; } pub fn next(it: *Iterator) ?Entity { - if (it.index >= it.entities.len) return null; + while (true) blk: { + if (it.index == 0) return null; + it.index -= 1; - blk: while (it.index < it.entities.len) : (it.index += 1) { const entity = it.entities.*[it.index]; // entity must be in all other Storages @@ -86,16 +99,13 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { } } - it.index += 1; return entity; } - - return null; } // Reset the iterator to the initial index pub fn reset(it: *Iterator) void { - it.index = 0; + it.index = it.entities.len; } }; @@ -147,6 +157,28 @@ test "single basic view" { store.remove(7); std.testing.expectEqual(view.len(), 2); + + var i: usize = 0; + var iter = view.componentIterator(); + while (iter.next()) |comp| { + if (i == 0) std.testing.expectEqual(comp, 50); + if (i == 1) std.testing.expectEqual(comp, 30); + i += 1; + } + + i = 0; + var entIter = view.entityIterator(); + while (entIter.next()) |ent| { + if (i == 0) { + std.testing.expectEqual(ent, 5); + std.testing.expectEqual(view.getConst(ent), 50); + } + if (i == 1) { + std.testing.expectEqual(ent, 3); + std.testing.expectEqual(view.getConst(ent), 30); + } + i += 1; + } } test "single basic view data" { diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index fda7110..f8c5d55 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -66,11 +66,11 @@ test "nested OwningGroups entity order" { var sprite_store = reg.assure(Sprite); var transform_store = reg.assure(Transform); - printStore(sprite_store, "Sprite"); + // printStore(sprite_store, "Sprite"); reg.add(1, Transform{.x = 1}); - printStore(sprite_store, "Sprite"); - printStore(transform_store, "Transform"); - warn("group2.current: {}\n", .{group2.group_data.current}); + // printStore(sprite_store, "Sprite"); + // printStore(transform_store, "Transform"); + // warn("group2.current: {}\n", .{group2.group_data.current}); }