diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig new file mode 100644 index 0000000..b703402 --- /dev/null +++ b/zig-ecs/src/ecs/groups.zig @@ -0,0 +1,37 @@ +const std = @import("std"); +const utils = @import("utils.zig"); + +const Registry = @import("registry.zig").Registry; +const Storage = @import("registry.zig").Storage; +const Entity = @import("registry.zig").Entity; + +pub fn NonOwningGroup(comptime n_includes: usize, comptime n_excludes: usize) type { + return struct { + const Self = @This(); + + registry: *Registry, + type_ids: [n_includes]u32, + exclude_type_ids: [n_excludes]u32, + + pub fn init(registry: *Registry, type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32) Self { + return Self{ + .registry = registry, + .type_ids = type_ids, + .exclude_type_ids = exclude_type_ids, + }; + } + }; +} + +test "group creation" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var e0 = reg.create(); + reg.add(e0, @as(i32, -0)); + reg.add(e0, @as(u32, 0)); + + var group = reg.group(.{}, .{i32}, .{}); + var group2 = reg.group(.{}, .{u32}, .{}); + var group23 = reg.group(.{}, .{i32}, .{}); +} \ No newline at end of file diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 4de5f06..32d7fb9 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -15,8 +15,9 @@ const entity_traits = if (@hasDecl(root, "EntityTraits")) root.EntityTraits.init const EntityHandles = Handles(entity_traits.entity_type, entity_traits.index_type, entity_traits.version_type); pub const Entity = entity_traits.entity_type; -pub const BasicView = @import("view.zig").BasicView; -pub const BasicMultiView = @import("view.zig").BasicMultiView; +pub const BasicView = @import("views.zig").BasicView; +pub const BasicMultiView = @import("views.zig").BasicMultiView; +pub const NonOwningGroup = @import("groups.zig").NonOwningGroup; /// 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 { @@ -31,14 +32,51 @@ pub const Registry = struct { handles: EntityHandles, components: std.AutoHashMap(u8, usize), contexts: std.AutoHashMap(u8, usize), + groups: std.ArrayList(GroupData), allocator: *std.mem.Allocator, + const GroupData = struct { + hash: u32, + entity_set: SparseSet(Entity, u16) = undefined, + owned: []u32, + include: []u32, + exclude: []u32, + + pub fn init(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); + std.debug.assert(std.mem.indexOfAny(u32, owned, exclude) == null); + std.debug.assert(std.mem.indexOfAny(u32, include, exclude) == null); + + const group_data = GroupData{ + .hash = hash, + .entity_set = SparseSet(Entity, u16).init(allocator), + .owned = std.mem.dupe(allocator, u32, owned) catch unreachable, + .include = std.mem.dupe(allocator, u32, include) catch unreachable, + .exclude = std.mem.dupe(allocator, u32, exclude) catch unreachable, + }; + + return group_data; + } + + pub fn deinit(self: *GroupData, allocator: *std.mem.Allocator) void { + self.entity_set.deinit(); + allocator.free(self.owned); + allocator.free(self.include); + allocator.free(self.exclude); + } + + pub fn hasSameConstraints(self: *GroupData, owned: []u32, include: []u32, exclude: []u32) bool { + return std.mem.eql(u32, self.owned, owned) and std.mem.eql(u32, self.include, include) and std.mem.eql(u32, self.exclude, exclude); + } + }; + pub fn init(allocator: *std.mem.Allocator) Registry { return Registry{ .typemap = TypeMap.init(allocator), .handles = EntityHandles.init(allocator), .components = std.AutoHashMap(u8, usize).init(allocator), .contexts = std.AutoHashMap(u8, usize).init(allocator), + .groups = std.ArrayList(GroupData).init(allocator), .allocator = allocator, }; } @@ -51,8 +89,13 @@ pub const Registry = struct { storage.deinit(); } + for (self.groups.items) |*grp| { + grp.deinit(self.allocator); + } + self.components.deinit(); self.contexts.deinit(); + self.groups.deinit(); self.typemap.deinit(); self.handles.deinit(); } @@ -276,95 +319,73 @@ pub const Registry = struct { excludes_arr[i] = @as(u32, self.typemap.get(t)); } - return BasicMultiView(includes.len, excludes.len).init(includes_arr, excludes_arr, self); + return BasicMultiView(includes.len, excludes.len).init(self, includes_arr, excludes_arr); } + /// returns the Type that a view will be based on the includes and excludes fn ViewType(comptime includes: var, comptime excludes: var) type { if (includes.len == 1 and excludes.len == 0) return BasicView(includes[0]); return BasicMultiView(includes.len, excludes.len); } -}; - -const Position = struct { x: f32, y: f32 }; - -test "context get/set/unset" { - var reg = Registry.init(std.testing.allocator); - defer reg.deinit(); - var ctx = reg.getContext(Position); - std.testing.expectEqual(ctx, null); - - var pos = Position{ .x = 5, .y = 5 }; - reg.setContext(&pos); - ctx = reg.getContext(Position); - std.testing.expectEqual(ctx.?, &pos); - - reg.unsetContext(Position); - ctx = reg.getContext(Position); - std.testing.expectEqual(ctx, null); -} + pub fn group(self: *Registry, comptime owned: var, comptime includes: var, comptime excludes: var) GroupType(owned, includes, excludes) { + if (@typeInfo(@TypeOf(owned)) != .Struct) + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(owned))); + if (@typeInfo(@TypeOf(includes)) != .Struct) + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(includes))); + if (@typeInfo(@TypeOf(excludes)) != .Struct) + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(excludes))); + std.debug.assert(includes.len + owned.len > 0); + std.debug.assert(includes.len + owned.len + excludes.len >= 1); -// this test should fail -test "context not pointer" { - var reg = Registry.init(std.testing.allocator); - defer reg.deinit(); + var owned_arr: [owned.len]u32 = undefined; + inline for (owned) |t, i| { + _ = self.assure(t); + owned_arr[i] = @as(u32, self.typemap.get(t)); + } - var pos = Position{ .x = 5, .y = 5 }; - // reg.setContext(pos); -} + var includes_arr: [includes.len]u32 = undefined; + inline for (includes) |t, i| { + _ = self.assure(t); + includes_arr[i] = @as(u32, self.typemap.get(t)); + } -test "component context get/set/unset" { - const SomeType = struct { dummy: u1 }; + var excludes_arr: [excludes.len]u32 = undefined; + inline for (excludes) |t, i| { + _ = self.assure(t); + excludes_arr[i] = @as(u32, self.typemap.get(t)); + } - var reg = Registry.init(std.testing.allocator); - defer reg.deinit(); + // create a unique hash to identify the group + var group_data: ?*GroupData = null; + comptime const hash = owned.len + (31 * includes.len) + (31 * 31 * excludes.len); - var ctx = reg.getContext(SomeType); - std.testing.expectEqual(ctx, null); + for (self.groups.items) |*grp| { + if (grp.hash == hash and grp.hasSameConstraints(owned_arr[0..], includes_arr[0..], excludes_arr[0..])) { + group_data = grp; + break; + } + } - var pos = SomeType{ .dummy = 0 }; - reg.setContext(&pos); - ctx = reg.getContext(SomeType); - std.testing.expectEqual(ctx.?, &pos); - reg.unsetContext(SomeType); - ctx = reg.getContext(SomeType); - std.testing.expectEqual(ctx, null); -} + // non-owning groups + if (owned.len == 0) { + if (group_data != null) { + return NonOwningGroup(includes.len, excludes.len).init(self, includes_arr, excludes_arr); + } -test "destroy" { - var reg = Registry.init(std.testing.allocator); - defer reg.deinit(); + var new_group_data = GroupData.init(self.allocator, self, hash, &[_]u32{}, includes_arr[0..], excludes_arr[0..]); + new_group_data.entity_set.reserve(5); + self.groups.append(new_group_data) catch unreachable; + return NonOwningGroup(includes.len, excludes.len).init(self, includes_arr, excludes_arr); + } - var i = @as(u8, 0); - while (i < 255) : (i += 1) { - const e = reg.create(); - reg.add(e, Position{ .x = @intToFloat(f32, i), .y = @intToFloat(f32, i) }); + @compileLog("owned groups not implemented"); } - reg.destroy(3); - reg.destroy(4); - - i = 0; - while (i < 6) : (i += 1) { - if (i != 3 and i != 4) - std.testing.expectEqual(Position{ .x = @intToFloat(f32, i), .y = @intToFloat(f32, i)}, reg.getConst(Position, i)); + /// 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 NonOwningGroup(includes.len, excludes.len); + unreachable; } -} - -test "remove all" { - var reg = Registry.init(std.testing.allocator); - defer reg.deinit(); - - var e = reg.create(); - reg.add(e, Position{.x = 1, .y = 1}); - reg.addTyped(u32, e, 666); - - std.testing.expect(reg.has(Position, e)); - std.testing.expect(reg.has(u32, e)); - - reg.removeAll(e); - - std.testing.expect(!reg.has(Position, e)); - std.testing.expect(!reg.has(u32, e)); -} \ No newline at end of file +}; diff --git a/zig-ecs/src/ecs/view.zig b/zig-ecs/src/ecs/views.zig similarity index 92% rename from zig-ecs/src/ecs/view.zig rename to zig-ecs/src/ecs/views.zig index bb21caa..ed623da 100644 --- a/zig-ecs/src/ecs/view.zig +++ b/zig-ecs/src/ecs/views.zig @@ -47,9 +47,9 @@ pub fn BasicMultiView(comptime n_includes: usize, comptime n_excludes: usize) ty return struct { const Self = @This(); + registry: *Registry, type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32, - registry: *Registry, pub const Iterator = struct { view: *Self, @@ -99,26 +99,20 @@ pub fn BasicMultiView(comptime n_includes: usize, comptime n_excludes: usize) ty } }; - pub fn init(type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32, registry: *Registry) Self { + pub fn init(registry: *Registry, type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32) Self { return Self{ + .registry = registry, .type_ids = type_ids, .exclude_type_ids = exclude_type_ids, - .registry = registry, }; } pub fn get(self: *Self, comptime T: type, entity: Entity) *T { - const type_id = self.registry.typemap.get(T); - const ptr = self.registry.components.getValue(type_id).?; - const store = @intToPtr(*Storage(T), ptr); - return store.get(entity); + return self.registry.assure(T).get(entity); } pub fn getConst(self: *Self, comptime T: type, entity: Entity) T { - const type_id = self.registry.typemap.get(T); - const ptr = self.registry.components.getValue(type_id).?; - const store = @intToPtr(*Storage(T), ptr); - return store.getConst(entity); + return self.registry.assure(T).getConst(entity); } fn sort(self: *Self) void {