From 34b2702333a765365e160e4f374f706601b41203 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 20:05:55 -0700 Subject: [PATCH] progress on sort --- zig-ecs/src/ecs/component_storage.zig | 36 ++++++++---- zig-ecs/src/ecs/groups.zig | 80 +++++++++++++++++++++++---- zig-ecs/src/ecs/registry.zig | 6 +- zig-ecs/src/ecs/sparse_set.zig | 18 ++++++ zig-ecs/tests/groups_test.zig | 37 +++++++++++-- 5 files changed, 148 insertions(+), 29 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index c214e2a..6624f99 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -28,7 +28,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { /// doesnt really belong here...used to denote group ownership super: usize = 0, safe_deinit: fn (*Self) void, - safe_swap: fn (*Self, Entity, Entity) void, + safe_swap: fn (*Self, Entity, Entity, bool) void, construction: Signal(Entity), update: Signal(Entity), destruction: Signal(Entity), @@ -45,11 +45,11 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { } }.deinit, .safe_swap = struct { - fn swap(self: *Self, lhs: Entity, rhs: Entity) void { + fn swap(self: *Self, lhs: Entity, rhs: Entity, instances_only: bool) void { if (!is_empty_struct) { std.mem.swap(Component, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]); } - self.set.swap(lhs, rhs); + if (!instances_only) self.set.swap(lhs, rhs); } }.swap, .allocator = null, @@ -87,11 +87,11 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { }.deinit; store.safe_swap = struct { - fn swap(self: *Self, lhs: Entity, rhs: Entity) void { + fn swap(self: *Self, lhs: Entity, rhs: Entity, instances_only: bool) void { if (!is_empty_struct) { std.mem.swap(Component, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]); } - self.set.swap(lhs, rhs); + if (!instances_only) self.set.swap(lhs, rhs); } }.swap; @@ -201,11 +201,25 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { return if (self.set.contains(entity)) self.instances.items[self.set.index(entity)] else null; } - /// Sort Entities or Components according to the given comparison function - pub fn sort(self: Self, comptime T: type, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { + /// Sort Entities or Components according to the given comparison function. Valid types for T are Entity or Component. + pub fn sort(self: *Self, comptime T: type, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { std.debug.assert(T == Entity or T == Component); if (T == Entity) { - self.set.sortSub(context, lessThan, Component, self.instances.items); + // wtf? When an OwningGroup calls us we are gonna be fake-typed and if we are fake-typed its not safe to pass our slice to + // the SparseSet and let it handle sorting. Instead, we'll use swap _without a set swap_ and do it ourselves. + if (Component == u1) { + const SortContext = struct { + storage: *Self, + + pub fn swap(this: @This(), a: Entity, b: Entity) void { + this.storage.safe_swap(this.storage, a, b, true); + } + }; + const swap_context = SortContext{.storage = self}; + self.set.sortSwap(context, lessThan, swap_context); + } else { + self.set.sortSub(context, lessThan, Component, self.instances.items); + } } else if (T == Component) { self.set.sortSubSub(context, Component, lessThan, self.instances.items); } @@ -224,7 +238,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { /// Swaps entities and objects in the internal packed arrays pub fn swap(self: *Self, lhs: Entity, rhs: Entity) void { - self.safe_swap(self, lhs, rhs); + self.safe_swap(self, lhs, rhs, false); } pub fn clear(self: *Self) void { @@ -361,7 +375,7 @@ test "sort by entity" { store.add(11, @as(f32, 1.1)); store.add(33, @as(f32, 3.3)); - const SortContext = struct{ + const SortContext = struct { store: *ComponentStorage(f32, u32), fn sort(this: @This(), a: u32, b: u32) bool { @@ -370,7 +384,7 @@ test "sort by entity" { return real_a > real_b; } }; - const context = SortContext{.store = store}; + const context = SortContext{ .store = store }; store.sort(u32, context, SortContext.sort); var compare: f32 = 5; diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 7c85e44..4d3880b 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -28,11 +28,11 @@ pub const BasicGroup = struct { return self.group_data.entity_set.data(); } - pub fn get(self: *BasicGroup, comptime T: type, entity: Entity) *T { + pub fn get(self: BasicGroup, comptime T: type, entity: Entity) *T { return self.registry.assure(T).get(entity); } - pub fn getConst(self: *BasicGroup, comptime T: type, entity: Entity) T { + pub fn getConst(self: BasicGroup, comptime T: type, entity: Entity) T { return self.registry.assure(T).getConst(entity); } @@ -42,13 +42,13 @@ pub const BasicGroup = struct { return self.group_data.entity_set.reverseIterator(); } - pub fn sort(self: *BasicGroup, comptime T: type, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { + pub fn sort(self: BasicGroup, comptime T: type, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { if (T == Entity) { self.group_data.entity_set.sort(context, lessThan); } else { // TODO: in debug mode, validate that T is present in the group - const SortContext = struct{ - group: *BasicGroup, + const SortContext = struct { + group: BasicGroup, wrapped_context: @TypeOf(context), lessThan: fn (@TypeOf(context), T, T) bool, @@ -58,7 +58,7 @@ pub const BasicGroup = struct { return this.lessThan(this.wrapped_context, real_a, real_b); } }; - var wrapper = SortContext{.group = self, .wrapped_context = context, .lessThan = lessThan}; + var wrapper = SortContext{ .group = self, .wrapped_context = context, .lessThan = lessThan }; self.group_data.entity_set.sort(wrapper, SortContext.sort); } } @@ -72,6 +72,7 @@ pub const OwningGroup = struct { /// 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. + /// TODO: support const types in the Components struct in addition to the current ptrs fn Iterator(comptime Components: var) type { return struct { group: OwningGroup, @@ -233,15 +234,15 @@ pub const OwningGroup = struct { } /// returns the component storage for the given type for direct access - pub fn getStorage(self: *OwningGroup, comptime T: type) *Storage(T) { + pub fn getStorage(self: OwningGroup, comptime T: type) *Storage(T) { return self.registry.assure(T); } - pub fn get(self: *OwningGroup, comptime T: type, entity: Entity) *T { + pub fn get(self: OwningGroup, comptime T: type, entity: Entity) *T { return self.registry.assure(T).get(entity); } - pub fn getConst(self: *OwningGroup, comptime T: type, entity: Entity) T { + pub fn getConst(self: OwningGroup, comptime T: type, entity: Entity) T { return self.registry.assure(T).getConst(entity); } @@ -250,7 +251,8 @@ pub const OwningGroup = struct { } /// 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! + /// fields that are pointers to the component types that you want to fetch. Only types that are owned are valid! Non-owned + /// types should be fetched via Iterator.get. pub fn iterator(self: OwningGroup, comptime Components: var) Iterator(Components) { self.validate(Components); return Iterator(Components).init(self); @@ -259,6 +261,64 @@ pub const OwningGroup = struct { pub fn entityIterator(self: OwningGroup) utils.ReverseSliceIterator(Entity) { return utils.ReverseSliceIterator(Entity).init(self.firstOwnedStorage().set.dense.items[0..self.group_data.current]); } + + pub fn sort(self: OwningGroup, comptime T: type, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { + var first_storage = self.firstOwnedStorage(); + + if (T == Entity) { + // only sort up to self.group_data.current + first_storage.sort(Entity, context, lessThan); + } else { + // TODO: in debug mode, validate that T is present in the group + const SortContext = struct { + group: OwningGroup, + wrapped_context: @TypeOf(context), + lessThan: fn (@TypeOf(context), T, T) bool, + + fn sort(this: @This(), a: Entity, b: Entity) bool { + const real_a = this.group.getConst(T, a); + const real_b = this.group.getConst(T, b); + return this.lessThan(this.wrapped_context, real_a, real_b); + } + }; + const wrapper = SortContext{ .group = self, .wrapped_context = context, .lessThan = lessThan }; + first_storage.sort(Entity, wrapper, SortContext.sort); + } + + // sync up the rest of the owned components. First get our Storages in + // var tmp_storages: [20]*Storage(u1) = undefined; + // for (self.group_data.owned[1..]) |type_id, i| { + // var other_ptr = self.registry.components.getValue(type_id).?; + // tmp_storages[i] = @intToPtr(*Storage(u1), other_ptr); + // } + // var storages = tmp_storages[0 .. self.group_data.owned.len - 1]; + + var next: usize = self.group_data.current; + while (true) : (next -= 1) { + if (next == 0) break; + const pos = next - 1; + const entity = first_storage.data()[pos]; + + // skip the first one since its what we are using to sort with + for (self.group_data.owned[1..]) |type_id| { + var other_ptr = self.registry.components.getValue(type_id).?; + var storage = @intToPtr(*Storage(u1), other_ptr); + storage.swap(storage.data()[pos], entity); + } + } + + // for (self.group_data.owned[1..]) |type_id| { + // var other_ptr = self.registry.components.getValue(type_id).?; + // var other = @intToPtr(*Storage(u1), other_ptr); + + // var i: usize = self.group_data.current - 1; + // while (true) : (i -= 1) { + // if (i == 0) break; + // const pos = i - 1; + // const entity = + // } + // } + } }; test "BasicGroup creation/iteration" { diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index f285780..b07d91d 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -101,8 +101,9 @@ pub const Registry = struct { }; if (self.owned.len == 0) { - if (isValid and !self.entity_set.contains(entity)) + if (isValid and !self.entity_set.contains(entity)) { self.entity_set.add(entity); + } } else { if (isValid) { const ptr = self.registry.components.getValue(self.owned[0]).?; @@ -122,8 +123,9 @@ pub const Registry = struct { pub fn discardIf(self: *GroupData, entity: Entity) void { if (self.owned.len == 0) { - if (self.entity_set.contains(entity)) + if (self.entity_set.contains(entity)) { self.entity_set.remove(entity); + } } else { const ptr = self.registry.components.getValue(self.owned[0]).?; var store = @intToPtr(*Storage(u1), ptr); diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 2c27a97..a5496f0 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -170,6 +170,24 @@ pub fn SparseSet(comptime SparseT: type) type { } } + /// Sort elements according to the given comparison function and keeps sub_items with the same sort + pub fn sortSwap(self: *Self, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: var) void { + std.sort.insertionSort(SparseT, self.dense.items, context, lessThan); + + for (self.dense.items) |sparse, pos| { + var curr = @intCast(SparseT, pos); + var next = self.index(self.dense.items[curr]); + + while (curr != next) { + swap_context.swap(self.dense.items[curr], self.dense.items[next]); + self.sparse.items[self.dense.items[self.page(@intCast(SparseT, curr))]] = @intCast(SparseT, curr); + + curr = next; + next = self.index(self.dense.items[curr]); + } + } + } + /// flips the script and uses the sparse set as the subordinate and does the sorting on the items slice pub fn sortSubSub(self: *Self, context: var, comptime T: type, comptime lessThan: fn (@TypeOf(context), T, T) bool, items: []T) void { utils.sortSubSub(T, SparseT, items, self.dense.items, context, lessThan); diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index 98f0adf..b8371c7 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -25,7 +25,7 @@ test "sort BasicGroup by Entity" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); - var group = reg.group(.{}, .{Sprite, Renderable}, .{}); + var group = reg.group(.{}, .{ Sprite, Renderable }, .{}); var i: usize = 0; while (i < 5) : (i += 1) { @@ -34,7 +34,7 @@ test "sort BasicGroup by Entity" { reg.add(e, Renderable{ .x = @intToFloat(f32, i) }); } - const SortContext = struct{ + const SortContext = struct { group: BasicGroup, fn sort(this: *@This(), a: ecs.Entity, b: ecs.Entity) bool { @@ -44,7 +44,7 @@ test "sort BasicGroup by Entity" { } }; - var context = SortContext{.group = group}; + var context = SortContext{ .group = group }; group.sort(ecs.Entity, &context, SortContext.sort); var val: f32 = 0; @@ -59,7 +59,7 @@ test "sort BasicGroup by Component" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); - var group = reg.group(.{}, .{Sprite, Renderable}, .{}); + var group = reg.group(.{}, .{ Sprite, Renderable }, .{}); var i: usize = 0; while (i < 5) : (i += 1) { @@ -68,7 +68,7 @@ test "sort BasicGroup by Component" { reg.add(e, Renderable{ .x = @intToFloat(f32, i) }); } - const SortContext = struct{ + const SortContext = struct { fn sort(this: void, a: Sprite, b: Sprite) bool { return a.x > b.x; } @@ -83,9 +83,34 @@ test "sort BasicGroup by Component" { } } -test "sort OwningGroup by Entity" { +test "sort OwningGroup by Component" { + std.debug.warn("\n", .{}); var reg = Registry.init(std.testing.allocator); defer reg.deinit(); + + var group = reg.group(.{ Sprite, Renderable }, .{}, .{}); + + var i: usize = 0; + while (i < 5) : (i += 1) { + var e = reg.create(); + reg.add(e, Sprite{ .x = @intToFloat(f32, i) }); + reg.add(e, Renderable{ .x = @intToFloat(f32, i) }); + } + + const SortContext = struct { + fn sort(this: void, a: Sprite, b: Sprite) bool { + return a.x > b.x; + } + }; + group.sort(Sprite, {}, SortContext.sort); + + var val: f32 = 0; + var iter = group.iterator(struct {s: *Sprite, r: *Renderable}); + while (iter.next()) |entity| { + std.debug.warn("e{}: {d}\n", .{iter.entity(), entity}); + std.testing.expectEqual(val, entity.s.*.x); + val += 1; + } } test "nested OwningGroups add/remove components" {