basic group sorting

master
Mike 4 years ago
parent c8c58255f6
commit e098c99632

@ -8,6 +8,8 @@ pub const Entity = @import("ecs/registry.zig").Entity;
pub const Registry = @import("ecs/registry.zig").Registry; pub const Registry = @import("ecs/registry.zig").Registry;
pub const BasicView = @import("ecs/views.zig").BasicView; pub const BasicView = @import("ecs/views.zig").BasicView;
pub const BasicMultiView = @import("ecs/views.zig").BasicMultiView; pub const BasicMultiView = @import("ecs/views.zig").BasicMultiView;
pub const BasicGroup = @import("ecs/groups.zig").BasicGroup;
pub const OwningGroup = @import("ecs/groups.zig").OwningGroup;
// signals // signals
pub const Signal = @import("signals/signal.zig").Signal; pub const Signal = @import("signals/signal.zig").Signal;

@ -162,9 +162,11 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type {
pub usingnamespace if (is_empty_struct) pub usingnamespace if (is_empty_struct)
struct { struct {
/// Sort Entities according to the given comparison function /// Sort Entities according to the given comparison function. Only T == Entity is allowed. The constraint param only exists for
pub fn sort(self: Self, comptime sortFn: fn (void, Entity, Entity) bool) void { /// parity with non-empty Components
self.set.sort(sortFn); pub fn sort(self: Self, comptime T: type, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void {
std.debug.assert(T == Entity);
self.set.sort(context, lessThan);
} }
} }
else else
@ -200,16 +202,12 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type {
} }
/// Sort Entities or Components according to the given comparison function /// Sort Entities or Components according to the given comparison function
pub fn sort(self: *Self, comptime T: type, comptime lessThan: fn (void, T, T) bool) void { 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); std.debug.assert(T == Entity or T == Component);
if (T == Entity) { if (T == Entity) {
self.set.sortSub(lessThan, Component, self.instances.items); self.set.sortSub(context, lessThan, Component, self.instances.items);
} else if (T == Component) { } else if (T == Component) {
self.set.sortSubSub({}, Component, lessThan, self.instances.items); self.set.sortSubSub(context, Component, lessThan, self.instances.items);
// fn sorter(self: Self, a: T, b: T, sortFn) bool {
// return sortFn(self.instances[a], self.instances[b]);
// }
//return compare(std::as_const(instances[underlying_type::index(lhs)]), std::as_const(instances[underlying_type::index(rhs)]));
} }
} }
}; };
@ -339,13 +337,13 @@ test "sort empty component" {
store.add(0, Empty{}); store.add(0, Empty{});
comptime const asc_u32 = std.sort.asc(u32); comptime const asc_u32 = std.sort.asc(u32);
store.sort(asc_u32); store.sort(u32, {}, asc_u32);
for (store.data()) |e, i| { for (store.data()) |e, i| {
std.testing.expectEqual(@intCast(u32, i), e); std.testing.expectEqual(@intCast(u32, i), e);
} }
comptime const desc_u32 = std.sort.desc(u32); comptime const desc_u32 = std.sort.desc(u32);
store.sort(desc_u32); store.sort(u32, {}, desc_u32);
var counter: u32 = 2; var counter: u32 = 2;
for (store.data()) |e, i| { for (store.data()) |e, i| {
std.testing.expectEqual(counter, e); std.testing.expectEqual(counter, e);
@ -353,7 +351,7 @@ test "sort empty component" {
} }
} }
test "sort component" { test "sort by entity" {
std.debug.warn("\n", .{}); std.debug.warn("\n", .{});
var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator); var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator);
@ -363,8 +361,37 @@ test "sort component" {
store.add(11, @as(f32, 1.1)); store.add(11, @as(f32, 1.1));
store.add(33, @as(f32, 3.3)); store.add(33, @as(f32, 3.3));
comptime const desc_u32 = std.sort.desc(f32); const SortContext = struct{
store.sort(f32, desc_u32); 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;
}
};
const context = SortContext{.store = store};
store.sort(u32, context, SortContext.sort);
var compare: f32 = 5;
for (store.raw()) |val, i| {
std.testing.expect(compare > val);
compare = val;
}
}
test "sort by component" {
std.debug.warn("\n", .{});
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));
comptime const desc_f32 = std.sort.desc(f32);
store.sort(f32, {}, desc_f32);
var compare: f32 = 5; var compare: f32 = 5;
for (store.raw()) |val, i| { for (store.raw()) |val, i| {

@ -9,40 +9,59 @@ const Entity = @import("registry.zig").Entity;
/// BasicGroups do not own any components. Internally, they keep a SparseSet that is always kept up-to-date with the matching /// BasicGroups do not own any components. Internally, they keep a SparseSet that is always kept up-to-date with the matching
/// entities. /// entities.
pub const BasicGroup = struct { pub const BasicGroup = struct {
const Self = @This();
registry: *Registry, registry: *Registry,
group_data: *Registry.GroupData, group_data: *Registry.GroupData,
pub fn init(registry: *Registry, group_data: *Registry.GroupData) Self { pub fn init(registry: *Registry, group_data: *Registry.GroupData) BasicGroup {
return Self{ return .{
.registry = registry, .registry = registry,
.group_data = group_data, .group_data = group_data,
}; };
} }
pub fn len(self: Self) usize { pub fn len(self: BasicGroup) usize {
return self.group_data.entity_set.len(); return self.group_data.entity_set.len();
} }
/// Direct access to the array of entities /// Direct access to the array of entities
pub fn data(self: Self) []const Entity { pub fn data(self: BasicGroup) []const Entity {
return self.group_data.entity_set.data(); return self.group_data.entity_set.data();
} }
pub fn get(self: *Self, comptime T: type, entity: Entity) *T { pub fn get(self: *BasicGroup, comptime T: type, entity: Entity) *T {
return self.registry.assure(T).get(entity); return self.registry.assure(T).get(entity);
} }
pub fn getConst(self: *Self, comptime T: type, entity: Entity) T { pub fn getConst(self: *BasicGroup, comptime T: type, entity: Entity) T {
return self.registry.assure(T).getConst(entity); return self.registry.assure(T).getConst(entity);
} }
/// iterates the matched entities backwards, so the current entity can always be removed safely /// iterates the matched entities backwards, so the current entity can always be removed safely
/// and newly added entities wont affect it. /// and newly added entities wont affect it.
pub fn iterator(self: Self) utils.ReverseSliceIterator(Entity) { pub fn iterator(self: BasicGroup) utils.ReverseSliceIterator(Entity) {
return self.group_data.entity_set.reverseIterator(); 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 {
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,
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);
}
};
var wrapper = SortContext{.group = self, .wrapped_context = context, .lessThan = lessThan};
self.group_data.entity_set.sort(wrapper, SortContext.sort);
}
}
}; };
pub const OwningGroup = struct { pub const OwningGroup = struct {

@ -412,10 +412,10 @@ pub const Registry = struct {
return self.singletons; return self.singletons;
} }
pub fn sort(self: *Registry, comptime T: type) void { pub fn sort(self: *Registry, comptime T: type, comptime lessThan: fn (void, T, T) bool) void {
const comp = self.assure(T); const comp = self.assure(T);
std.debug.assert(comp.super == 0); std.debug.assert(comp.super == 0);
unreachable; comp.sort(T, lessThan);
} }
/// Checks whether the given component belongs to any group. If so, it is not sortable directly. /// Checks whether the given component belongs to any group. If so, it is not sortable directly.
@ -563,7 +563,8 @@ pub const Registry = struct {
new_group_data.entity_set.add(entity); new_group_data.entity_set.add(entity);
} }
} else { } else {
// ??? why not? we cannot iterate backwards because we want to leave behind valid entities in case of owned types // we cannot iterate backwards because we want to leave behind valid entities in case of owned types
// ??? why not?
var first_owned_storage = self.assure(owned[0]); var first_owned_storage = self.assure(owned[0]);
for (first_owned_storage.data()) |entity| { for (first_owned_storage.data()) |entity| {
new_group_data.maybeValidIf(entity); new_group_data.maybeValidIf(entity);

@ -143,8 +143,8 @@ pub fn SparseSet(comptime SparseT: type) type {
} }
/// Sort elements according to the given comparison function /// Sort elements according to the given comparison function
pub fn sort(self: *Self, comptime sortFn: fn (void, SparseT, SparseT) bool) void { pub fn sort(self: *Self, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool) void {
std.sort.insertionSort(SparseT, self.dense.items, {}, sortFn); std.sort.insertionSort(SparseT, self.dense.items, context, lessThan);
for (self.dense.items) |sparse, i| { for (self.dense.items) |sparse, i| {
// sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos);
@ -153,12 +153,20 @@ pub fn SparseSet(comptime SparseT: type) type {
} }
/// Sort elements according to the given comparison function and keeps sub_items with the same sort /// Sort elements according to the given comparison function and keeps sub_items with the same sort
pub fn sortSub(self: *Self, comptime sortFn: fn (void, SparseT, SparseT) bool, comptime T: type, sub_items: []T) void { pub fn sortSub(self: *Self, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, comptime T: type, sub_items: []T) void {
utils.sortSub(SparseT, T, self.dense.items, sub_items, sortFn); std.sort.insertionSort(SparseT, self.dense.items, context, lessThan);
for (self.dense.items) |sparse, i| { for (self.dense.items) |sparse, pos| {
// sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); var curr = @intCast(SparseT, pos);
self.sparse.items[self.dense.items[self.page(@intCast(SparseT, i))]] = @intCast(SparseT, i); var next = self.index(self.dense.items[curr]);
while (curr != next) {
std.mem.swap(T, &sub_items[self.index(self.dense.items[curr])], &sub_items[self.index(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]);
}
} }
} }
@ -323,7 +331,7 @@ test "respect 2" {
set.add(1); set.add(1);
set.add(3); set.add(3);
set.sort(desc_u32); set.sort({}, desc_u32);
for (set.dense.items) |item, i| { for (set.dense.items) |item, i| {
if (i < set.dense.items.len - 1) { if (i < set.dense.items.len - 1) {

@ -2,6 +2,7 @@ const std = @import("std");
const warn = std.debug.warn; const warn = std.debug.warn;
const ecs = @import("ecs"); const ecs = @import("ecs");
const Registry = @import("ecs").Registry; const Registry = @import("ecs").Registry;
const BasicGroup = @import("ecs").BasicGroup;
const Velocity = struct { x: f32 = 0, y: f32 = 0 }; const Velocity = struct { x: f32 = 0, y: f32 = 0 };
const Position = struct { x: f32 = 0, y: f32 = 0 }; const Position = struct { x: f32 = 0, y: f32 = 0 };
@ -14,31 +15,79 @@ const Rotation = struct { x: f32 = 0 };
fn printStore(store: var, name: []const u8) void { fn printStore(store: var, name: []const u8) void {
warn("--- {} ---\n", .{name}); warn("--- {} ---\n", .{name});
for (store.set.dense.items) |e, i| { for (store.set.dense.items) |e, i| {
warn("e[{}] s[{}]{}", .{e, store.set.page(store.set.dense.items[i]), store.set.sparse.items[store.set.page(store.set.dense.items[i])]}); warn("e[{}] s[{}]{}", .{ e, store.set.page(store.set.dense.items[i]), store.set.sparse.items[store.set.page(store.set.dense.items[i])] });
warn(" ({d:.2}) ", .{store.instances.items[i]}); warn(" ({d:.2}) ", .{store.instances.items[i]});
} }
warn("\n", .{}); warn("\n", .{});
} }
test "sort BasicGroup by Entity" {
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{
group: BasicGroup,
fn sort(this: *@This(), a: ecs.Entity, b: ecs.Entity) bool {
const real_a = this.group.getConst(Sprite, a);
const real_b = this.group.getConst(Sprite, b);
return real_a.x > real_b.x;
}
};
var context = SortContext{.group = group};
group.sort(ecs.Entity, &context, SortContext.sort);
var val: f32 = 0;
var iter = group.iterator();
while (iter.next()) |entity| {
std.testing.expectEqual(val, group.getConst(Sprite, entity).x);
val += 1;
}
}
test "sort component" { test "sort BasicGroup by Component" {
var store = ecs.ComponentStorage(f32, u32).initPtr(std.testing.allocator); var reg = Registry.init(std.testing.allocator);
defer store.deinit(); defer reg.deinit();
store.add(22, @as(f32, 2.2)); var group = reg.group(.{}, .{Sprite, Renderable}, .{});
store.add(11, @as(f32, 1.1));
store.add(33, @as(f32, 3.3));
comptime const desc_u32 = std.sort.desc(f32); var i: usize = 0;
store.sort(f32, desc_u32); 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) });
}
var compare: f32 = 5; const SortContext = struct{
for (store.raw()) |val, i| { fn sort(this: void, a: Sprite, b: Sprite) bool {
std.testing.expect(compare > val); return a.x > b.x;
compare = val; }
};
group.sort(Sprite, {}, SortContext.sort);
var val: f32 = 0;
var iter = group.iterator();
while (iter.next()) |entity| {
std.testing.expectEqual(val, group.getConst(Sprite, entity).x);
val += 1;
} }
} }
test "sort OwningGroup by Entity" {
var reg = Registry.init(std.testing.allocator);
defer reg.deinit();
}
test "nested OwningGroups add/remove components" { test "nested OwningGroups add/remove components" {
var reg = Registry.init(std.testing.allocator); var reg = Registry.init(std.testing.allocator);
defer reg.deinit(); defer reg.deinit();
@ -87,7 +136,7 @@ test "nested OwningGroups entity order" {
var transform_store = reg.assure(Transform); var transform_store = reg.assure(Transform);
// printStore(sprite_store, "Sprite"); // printStore(sprite_store, "Sprite");
reg.add(1, Transform{.x = 1}); reg.add(1, Transform{ .x = 1 });
// printStore(sprite_store, "Sprite"); // printStore(sprite_store, "Sprite");
// printStore(transform_store, "Transform"); // printStore(transform_store, "Transform");

Loading…
Cancel
Save