From 5589130bfdaca7ee8a21edf4153cdb9f343c25a4 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 31 May 2020 21:28:29 -0700 Subject: [PATCH 001/146] initial commit --- zig-ecs/.gitignore | 1 + zig-ecs/build.zig | 72 ++++++ zig-ecs/examples/simple.zig | 41 ++++ zig-ecs/src/ecs.zig | 11 + zig-ecs/src/ecs/actor.zig | 88 ++++++++ zig-ecs/src/ecs/component_storage.zig | 213 ++++++++++++++++++ zig-ecs/src/ecs/entity.zig | 47 ++++ zig-ecs/src/ecs/handles.zig | 135 ++++++++++++ zig-ecs/src/ecs/registry.zig | 306 ++++++++++++++++++++++++++ zig-ecs/src/ecs/sparse_set.zig | 258 ++++++++++++++++++++++ zig-ecs/src/ecs/type_map.zig | 53 +++++ zig-ecs/src/ecs/utils.zig | 38 ++++ zig-ecs/src/ecs/view.zig | 217 ++++++++++++++++++ zig-ecs/src/signals/delegate.zig | 87 ++++++++ zig-ecs/src/signals/dispatcher.zig | 51 +++++ zig-ecs/src/signals/signal.zig | 95 ++++++++ zig-ecs/src/signals/sink.zig | 47 ++++ zig-ecs/src/tests.zig | 15 ++ zig-ecs/tests/dispatcher_test.zig | 40 ++++ zig-ecs/tests/registry_test.zig | 28 +++ zig-ecs/tests/tests.zig | 4 + 21 files changed, 1847 insertions(+) create mode 100644 zig-ecs/.gitignore create mode 100644 zig-ecs/build.zig create mode 100644 zig-ecs/examples/simple.zig create mode 100644 zig-ecs/src/ecs.zig create mode 100644 zig-ecs/src/ecs/actor.zig create mode 100644 zig-ecs/src/ecs/component_storage.zig create mode 100644 zig-ecs/src/ecs/entity.zig create mode 100644 zig-ecs/src/ecs/handles.zig create mode 100644 zig-ecs/src/ecs/registry.zig create mode 100644 zig-ecs/src/ecs/sparse_set.zig create mode 100644 zig-ecs/src/ecs/type_map.zig create mode 100644 zig-ecs/src/ecs/utils.zig create mode 100644 zig-ecs/src/ecs/view.zig create mode 100644 zig-ecs/src/signals/delegate.zig create mode 100644 zig-ecs/src/signals/dispatcher.zig create mode 100644 zig-ecs/src/signals/signal.zig create mode 100644 zig-ecs/src/signals/sink.zig create mode 100644 zig-ecs/src/tests.zig create mode 100644 zig-ecs/tests/dispatcher_test.zig create mode 100644 zig-ecs/tests/registry_test.zig create mode 100644 zig-ecs/tests/tests.zig diff --git a/zig-ecs/.gitignore b/zig-ecs/.gitignore new file mode 100644 index 0000000..2040c29 --- /dev/null +++ b/zig-ecs/.gitignore @@ -0,0 +1 @@ +zig-cache diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig new file mode 100644 index 0000000..d182520 --- /dev/null +++ b/zig-ecs/build.zig @@ -0,0 +1,72 @@ +const Builder = @import("std").build.Builder; +const builtin = @import("builtin"); + +pub fn build(b: *Builder) void { + const buildMode = b.standardReleaseOptions(); + + const examples = [_][2][]const u8{ + [_][]const u8{ "simple", "examples/simple.zig" }, + // [_][]const u8{ "mesh", "examples/mesh.zig" }, + }; + + for (examples) |example, i| { + const name = example[0]; + const source = example[1]; + + var exe = b.addExecutable(name, source); + exe.setBuildMode(b.standardReleaseOptions()); + exe.addPackagePath("ecs", "src/ecs.zig"); + + const run_cmd = exe.run(); + const exe_step = b.step(name, b.fmt("run {}.zig", .{name})); + exe_step.dependOn(&run_cmd.step); + + // first element in the list is added as "run" so "zig build run" works + if (i == 0) { + const run_exe_step = b.step("run", b.fmt("run {}.zig", .{name})); + run_exe_step.dependOn(&run_cmd.step); + } + } + + // internal tests + const internal_test_step = b.addTest("src/tests.zig"); + internal_test_step.setBuildMode(buildMode); + + // public api tests + const test_step = b.addTest("tests/tests.zig"); + test_step.addPackagePath("ecs", "src/ecs.zig"); + test_step.setBuildMode(buildMode); + + const test_cmd = b.step("test", "Run the tests"); + test_cmd.dependOn(&internal_test_step.step); + test_cmd.dependOn(&test_step.step); +} + +pub const LibType = enum(i32) { + static, + dynamic, // requires DYLD_LIBRARY_PATH to point to the dylib path + exe_compiled, +}; + +/// rel_path is used to add package paths. It should be the the same path used to include this build file +pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std.build.Target, lib_type: LibType, rel_path: []const u8) void { + switch (lib_type) { + .static => { + const lib = b.addStaticLibrary("ecs", "ecs.zig"); + lib.setBuildMode(buildMode); + lib.install(); + + artifact.linkLibrary(lib); + }, + .dynamic => { + const lib = b.addSharedLibrary("ecs", "ecs.zig", null); + lib.setBuildMode(buildMode); + lib.install(); + + artifact.linkLibrary(lib); + }, + else => {}, + } + + artifact.addPackagePath("ecs", std.fs.path.join(b.allocator, &[_][]const u8{ rel_path, "ecs.zig" }) catch unreachable); +} \ No newline at end of file diff --git a/zig-ecs/examples/simple.zig b/zig-ecs/examples/simple.zig new file mode 100644 index 0000000..07b488e --- /dev/null +++ b/zig-ecs/examples/simple.zig @@ -0,0 +1,41 @@ +const std = @import("std"); +const ecs = @import("ecs"); + +// override the EntityTraits used by ecs +pub const EntityTraits = ecs.EntityTraitsType(.small); + +pub const Velocity = struct { x: f32, y: f32 }; +pub const Position = struct { x: f32, y: f32 }; + +pub fn main() !void { + var reg = ecs.Registry.init(std.testing.allocator); + defer reg.deinit(); + + var e1 = reg.create(); + reg.add(e1, Position{ .x = 0, .y = 0 }); + reg.add(e1, Velocity{ .x = 5, .y = 7 }); + + var e2 = reg.create(); + reg.add(e2, Position{ .x = 10, .y = 10 }); + reg.add(e2, Velocity{ .x = 15, .y = 17 }); + + var view = reg.view(.{Velocity, Position}); + + var iter = view.iterator(); + while (iter.next()) |entity| { + var pos = view.get(Position, entity); + const vel = view.getConst(Velocity, entity); + std.debug.warn("entity: {}, pos: {d}, vel: {d}\n", .{entity, pos.*, vel}); + pos.*.x += vel.x; + pos.*.y += vel.y; + } + + std.debug.warn("---- resetting iter\n", .{}); + + iter.reset(); + while (iter.next()) |entity| { + const pos = view.getConst(Position, entity); + const vel = view.getConst(Velocity, entity); + std.debug.warn("entity: {}, pos: {d}, vel: {d}\n", .{entity, pos, vel}); + } +} \ No newline at end of file diff --git a/zig-ecs/src/ecs.zig b/zig-ecs/src/ecs.zig new file mode 100644 index 0000000..24c2edb --- /dev/null +++ b/zig-ecs/src/ecs.zig @@ -0,0 +1,11 @@ +// ecs +pub const EntityTraitsType = @import("ecs/entity.zig").EntityTraitsType; + +pub const Entity = @import("ecs/registry.zig").Entity; +pub const Registry = @import("ecs/registry.zig").Registry; +pub const BasicView = @import("ecs/view.zig").BasicView; +pub const BasicMultiView = @import("ecs/view.zig").BasicMultiView; + +// signals +pub const Signal = @import("signals/signal.zig").Signal; +pub const Dispatcher = @import("signals/dispatcher.zig").Dispatcher; \ No newline at end of file diff --git a/zig-ecs/src/ecs/actor.zig b/zig-ecs/src/ecs/actor.zig new file mode 100644 index 0000000..b570b80 --- /dev/null +++ b/zig-ecs/src/ecs/actor.zig @@ -0,0 +1,88 @@ +const std = @import("std"); +const Registry = @import("registry.zig").Registry; +const Entity = @import("registry.zig").Entity; + +pub const Actor = struct { + registry: *Registry, + entity: Entity = undefined, + + pub fn init(registry: *Registry) Actor { + var reg = registry; + return .{ + .registry = registry, + .entity = reg.create(), + }; + } + + pub fn deinit(self: *Actor) void { + self.registry.destroy(self.entity); + } + + pub fn add(self: *Actor, value: var) void { + self.registry.add(self.entity, value); + } + + pub fn addTyped(self: *Actor, comptime T: type, value: T) void { + self.registry.addTyped(T, self.entity, value); + } + + pub fn remove(self: *Actor, comptime T: type) void { + self.registry.remove(T, self.entity); + } + + pub fn has(self: *Actor, comptime T: type) bool { + return self.registry.has(T, self.entity); + } + + pub fn get(self: *Actor, comptime T: type) *T { + return self.registry.get(T, self.entity); + } + + pub fn tryGet(self: *Actor, comptime T: type) ?*T { + return self.registry.tryGet(T, self.entity); + } +}; + +test "actor" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var actor = Actor.init(®); + defer actor.deinit(); + + std.debug.assert(!actor.has(f32)); + actor.addTyped(f32, 67.45); + if (actor.tryGet(f32)) |val| { + std.testing.expectEqual(val.*, 67.45); + } + + actor.addTyped(u64, 8888); + std.testing.expectEqual(actor.get(u64).*, 8888); + std.debug.assert(actor.has(u64)); + + actor.remove(u64); + std.debug.assert(!actor.has(u64)); +} + +test "actor structs" { + const Velocity = struct { x: f32, y: f32 }; + const Position = struct { x: f32 = 0, y: f32 = 0 }; + + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var actor = Actor.init(®); + defer actor.deinit(); + + actor.add(Velocity{ .x = 5, .y = 10 }); + actor.add(Position{}); + + var vel = actor.get(Velocity); + var pos = actor.get(Position); + + pos.*.x += vel.x; + pos.*.y += vel.y; + + std.testing.expectEqual(actor.get(Position).*.x, 5); + std.testing.expectEqual(actor.get(Position).*.y, 10); +} diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig new file mode 100644 index 0000000..773d554 --- /dev/null +++ b/zig-ecs/src/ecs/component_storage.zig @@ -0,0 +1,213 @@ +const std = @import("std"); +const warn = std.debug.warn; +const utils = @import("utils.zig"); + +const SparseSet = @import("sparse_set.zig").SparseSet; + +pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime DenseT: type) type { + std.debug.assert(!utils.isComptime(CompT)); + + // empty (zero-sized) structs will not have an array created + comptime const is_empty_struct = @sizeOf(CompT) == 0; + + // HACK: due to this being stored as untyped ptrs, when deinit is called we are casted to a CompT of some random + // non-zero sized type. That will make is_empty_struct false in deinit always so we can't use it. Instead, we stick + // a small dummy struct in the instances ArrayList so it can safely be deallocated. + // Perhaps we should just allocate instances with a dummy allocator or the tmp allocator? + comptime var CompOrAlmostEmptyT = CompT; + if (is_empty_struct) + CompOrAlmostEmptyT = struct { dummy: u1 }; + + return struct { + const Self = @This(); + + set: *SparseSet(EntityT, DenseT), + instances: std.ArrayList(CompOrAlmostEmptyT), + allocator: ?*std.mem.Allocator, + safe_deinit: fn (*Self) void, + + pub fn init(allocator: *std.mem.Allocator) Self { + var store = Self{ + .set = SparseSet(EntityT, DenseT).init(allocator), + .instances = undefined, + .safe_deinit = struct { + fn deinit(self: *Self) void { + std.debug.warn("------ (inner) T: {}, size: {}, is_empty_struct: {}\n", .{ @typeName(@TypeOf(self)), @sizeOf(CompT), is_empty_struct }); + if (!is_empty_struct) + self.instances.deinit(); + } + }.deinit, + .allocator = null, + }; + + if (!is_empty_struct) + store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); + + return store; + } + + pub fn initPtr(allocator: *std.mem.Allocator) *Self { + var store = allocator.create(Self) catch unreachable; + store.set = SparseSet(EntityT, DenseT).init(allocator); + if (!is_empty_struct) + store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); + store.allocator = allocator; + + // since we are stored as a pointer, we need to catpure this + store.safe_deinit = struct { + fn deinit(self: *Self) void { + std.debug.warn("------ (inner) T: {}, size: {}, is_empty_struct: {}\n", .{ @typeName(@TypeOf(self)), @sizeOf(CompT), is_empty_struct }); + if (!is_empty_struct) + self.instances.deinit(); + } + }.deinit; + + return store; + } + + pub fn deinit(self: *Self) void { + // great care must be taken here. Due to how Registry keeps this struct as pointers anything touching a type + // will be wrong since it has to cast to a random struct when deiniting. Because of all that, is_empty_struct + // will allways be false here so we have to deinit the instances no matter what. + std.debug.warn("\n------ (deinit) T: {}, size: {}, is_empty_struct: {}\n", .{ @typeName(@TypeOf(self)), @sizeOf(CompT), is_empty_struct }); + self.safe_deinit(self); + self.set.deinit(); + + if (self.allocator) |allocator| + allocator.destroy(self); + } + + /// Increases the capacity of a component storage + pub fn reserve(self: *Self, cap: usize) void { + self.set.reserve(cap); + if (!is_empty_struct) + self.instances.items.reserve(cap); + } + + /// Assigns an entity to a storage and constructs its object + pub fn add(self: *Self, entity: EntityT, value: CompT) void { + if (!is_empty_struct) + _ = self.instances.append(value) catch unreachable; + self.set.add(entity); + } + + /// Checks if a view contains an entity + pub fn contains(self: Self, entity: EntityT) bool { + return self.set.contains(entity); + } + + pub fn len(self: Self) usize { + return self.set.len(); + } + + pub usingnamespace if (is_empty_struct) + struct {} + else + struct { + /// Direct access to the array of objects + pub fn raw(self: Self) []CompT { + return self.instances.items; + } + + /// Returns the object associated with an entity + pub fn get(self: *Self, entity: EntityT) *CompT { + std.debug.assert(self.contains(entity)); + return &self.instances.items[self.set.index(entity)]; + } + + pub fn getConst(self: *Self, entity: EntityT) CompT { + return self.instances.items[self.set.index(entity)]; + } + + /// Returns a pointer to the object associated with an entity, if any. + pub fn tryGet(self: *Self, entity: EntityT) ?*CompT { + return if (self.set.contains(entity)) &self.instances.items[self.set.index(entity)] else null; + } + + pub fn tryGetConst(self: *Self, entity: EntityT) ?CompT { + return if (self.set.contains(entity)) self.instances.items[self.set.index(entity)] else null; + } + }; + + /// Direct access to the array of entities + pub fn data(self: Self) *const []EntityT { + return self.set.data(); + } + + /// Removes an entity from a storage + pub fn remove(self: *Self, entity: EntityT) void { + if (!is_empty_struct) + _ = self.instances.swapRemove(self.set.index(entity)); + self.set.remove(entity); + } + + /// 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); + } + + pub fn clear(self: *Self) void { + if (!is_empty_struct) + self.instances.items.len = 0; + self.set.clear(); + } + }; +} + +test "add/try-get/remove/clear" { + var store = ComponentStorage(f32, u32, u8).init(std.testing.allocator); + defer store.deinit(); + + store.add(3, 66.45); + std.testing.expectEqual(store.tryGetConst(3).?, 66.45); + if (store.tryGet(3)) |found| std.testing.expectEqual(@as(f32, 66.45), found.*); + + store.remove(3); + + var val_null = store.tryGet(3); + std.testing.expectEqual(val_null, null); + + store.clear(); +} + +test "add/get/remove" { + var store = ComponentStorage(f32, u32, u8).init(std.testing.allocator); + defer store.deinit(); + + store.add(3, 66.45); + if (store.tryGet(3)) |found| std.testing.expectEqual(@as(f32, 66.45), found.*); + std.testing.expectEqual(store.tryGetConst(3).?, 66.45); + + store.remove(3); + std.testing.expectEqual(store.tryGet(3), null); +} + +test "iterate" { + var store = ComponentStorage(f32, u32, u8).initPtr(std.testing.allocator); + defer store.deinit(); + + store.add(3, 66.45); + store.add(5, 66.45); + store.add(7, 66.45); + + for (store.data().*) |entity, i| { + if (i == 0) + std.testing.expectEqual(entity, 3); + if (i == 1) + std.testing.expectEqual(entity, 5); + if (i == 2) + std.testing.expectEqual(entity, 7); + } +} + +test "empty component" { + const Empty = struct {}; + + var store = ComponentStorage(Empty, u32, u8).initPtr(std.testing.allocator); + defer store.deinit(); + + store.add(3, Empty{}); + store.remove(3); +} diff --git a/zig-ecs/src/ecs/entity.zig b/zig-ecs/src/ecs/entity.zig new file mode 100644 index 0000000..c8f505a --- /dev/null +++ b/zig-ecs/src/ecs/entity.zig @@ -0,0 +1,47 @@ +const std = @import("std"); + +/// default EntityTraitsDefinition with reasonable sizes suitable for most situations +pub const EntityTraits = EntityTraitsType(.medium); + +pub const EntityTraitsSize = enum { small, medium, large }; + +pub fn EntityTraitsType(comptime size: EntityTraitsSize) type { + return switch (size) { + .small => EntityTraitsDefinition(u16, u12, u4), + .medium => EntityTraitsDefinition(u32, u20, u12), + .large => EntityTraitsDefinition(u64, u32, u32), + }; +} + +fn EntityTraitsDefinition(comptime EntityType: type, comptime IndexType: type, comptime VersionType: type) type { + std.debug.assert(@typeInfo(EntityType) == .Int and !EntityType.is_signed); + std.debug.assert(@typeInfo(IndexType) == .Int and !IndexType.is_signed); + std.debug.assert(@typeInfo(VersionType) == .Int and !VersionType.is_signed); + + if (@bitSizeOf(IndexType) + @bitSizeOf(VersionType) != @bitSizeOf(EntityType)) + @compileError("IndexType and VersionType must sum to EntityType's bit count"); + + return struct { + entity_type: type = EntityType, + index_type: type = IndexType, + version_type: type = VersionType, + /// Mask to use to get the entity index number out of an identifier + entity_mask: EntityType = std.math.maxInt(IndexType), + /// Mask to use to get the version out of an identifier + version_mask: EntityType = std.math.maxInt(VersionType), + + pub fn init() @This() { + return @This(){}; + } + }; +} + +test "entity traits" { + const sm = EntityTraitsType(.small).init(); + const m = EntityTraitsType(.medium).init(); + const l = EntityTraitsType(.large).init(); + + std.testing.expectEqual(sm.entity_mask, std.math.maxInt(sm.index_type)); + std.testing.expectEqual(m.entity_mask, std.math.maxInt(m.index_type)); + std.testing.expectEqual(l.entity_mask, std.math.maxInt(l.index_type)); +} diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig new file mode 100644 index 0000000..cbf71d1 --- /dev/null +++ b/zig-ecs/src/ecs/handles.zig @@ -0,0 +1,135 @@ +const std = @import("std"); + +/// generates versioned "handles" (https://floooh.github.io/2018/06/17/handles-vs-pointers.html) +/// you choose the type of the handle (aka its size) and how much of that goes to the index and the version. +/// the bitsize of version + id must equal the handle size. +pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime VersionType: type) type { + std.debug.assert(@typeInfo(HandleType) == .Int and !HandleType.is_signed); + std.debug.assert(@typeInfo(IndexType) == .Int and !IndexType.is_signed); + std.debug.assert(@typeInfo(VersionType) == .Int and !VersionType.is_signed); + + if (@bitSizeOf(IndexType) + @bitSizeOf(VersionType) != @bitSizeOf(HandleType)) + @compileError("IndexType and VersionType must sum to HandleType's bit count"); + + return struct { + const Self = @This(); + + handles: []HandleType, + append_cursor: IndexType = 0, + last_destroyed: ?IndexType = null, + allocator: *std.mem.Allocator, + + const invalid_id = std.math.maxInt(IndexType); + + pub fn init(allocator: *std.mem.Allocator) Self { + return initWithCapacity(allocator, 32); + } + + pub fn initWithCapacity(allocator: *std.mem.Allocator, capacity: usize) Self { + return Self{ + .handles = allocator.alloc(HandleType, capacity) catch unreachable, + .allocator = allocator, + }; + } + + pub fn deinit(self: Self) void { + self.allocator.free(self.handles); + } + + pub fn extractId(self: Self, handle: HandleType) IndexType { + return @truncate(IndexType, handle); + } + + pub fn extractVersion(self: Self, handle: HandleType) VersionType { + return @truncate(VersionType, handle >> @bitSizeOf(IndexType)); + } + + fn forge(id: IndexType, version: VersionType) HandleType { + return id | @as(HandleType, version) << @bitSizeOf(IndexType); + } + + pub fn create(self: *Self) HandleType { + if (self.last_destroyed == null) { + // ensure capacity and grow if needed + if (self.handles.len - 1 == self.append_cursor) { + self.handles = self.allocator.realloc(self.handles, self.handles.len * 2) catch unreachable; + } + + const id = self.append_cursor; + const handle = forge(self.append_cursor, 0); + self.handles[id] = handle; + + self.append_cursor += 1; + return handle; + } + + const version = self.extractVersion(self.handles[self.last_destroyed.?]); + const destroyed_id = self.extractId(self.handles[self.last_destroyed.?]); + + const handle = forge(self.last_destroyed.?, version); + self.handles[self.last_destroyed.?] = handle; + + self.last_destroyed = if (destroyed_id == invalid_id) null else destroyed_id; + + return handle; + } + + pub fn remove(self: *Self, handle: HandleType) !void { + const id = self.extractId(handle); + if (id > self.append_cursor or self.handles[id] != handle) + return error.RemovedInvalidHandle; + + const next_id = self.last_destroyed orelse invalid_id; + if (next_id == id) return error.ExhaustedEntityRemoval; + + const version = self.extractVersion(handle); + self.handles[id] = forge(next_id, version +% 1); + + self.last_destroyed = id; + } + + pub fn isAlive(self: Self, handle: HandleType) bool { + const id = self.extractId(handle); + return id < self.append_cursor and self.handles[id] == handle; + } + }; +} + +test "handles" { + var hm = Handles(u32, u20, u12).init(std.testing.allocator); + defer hm.deinit(); + + const e0 = hm.create(); + const e1 = hm.create(); + const e2 = hm.create(); + + std.debug.assert(hm.isAlive(e0)); + std.debug.assert(hm.isAlive(e1)); + std.debug.assert(hm.isAlive(e2)); + + hm.remove(e1) catch unreachable; + std.debug.assert(!hm.isAlive(e1)); + + std.testing.expectError(error.RemovedInvalidHandle, hm.remove(e1)); + + var e_tmp = hm.create(); + std.debug.assert(hm.isAlive(e_tmp)); + + hm.remove(e_tmp) catch unreachable; + std.debug.assert(!hm.isAlive(e_tmp)); + + hm.remove(e0) catch unreachable; + std.debug.assert(!hm.isAlive(e0)); + + hm.remove(e2) catch unreachable; + std.debug.assert(!hm.isAlive(e2)); + + e_tmp = hm.create(); + std.debug.assert(hm.isAlive(e_tmp)); + + e_tmp = hm.create(); + std.debug.assert(hm.isAlive(e_tmp)); + + e_tmp = hm.create(); + std.debug.assert(hm.isAlive(e_tmp)); +} diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig new file mode 100644 index 0000000..0201e13 --- /dev/null +++ b/zig-ecs/src/ecs/registry.zig @@ -0,0 +1,306 @@ +const std = @import("std"); +const assert = std.debug.assert; +const utils = @import("utils.zig"); + +const Handles = @import("handles.zig").Handles; +const SparseSet = @import("sparse_set.zig").SparseSet; +const TypeMap = @import("type_map.zig").TypeMap; +const ComponentStorage = @import("component_storage.zig").ComponentStorage; + +// allow overriding EntityTraits by setting in root via: EntityTraits = EntityTraitsType(.medium); +const root = @import("root"); +const entity_traits = if (@hasDecl(root, "EntityTraits")) root.EntityTraits.init() else @import("entity.zig").EntityTraits.init(); + +// setup the Handles type based on the type set in EntityTraits +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; + +/// 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 { + return ComponentStorage(CompT, Entity, u16); // 65,535 components +} + +/// the registry is the main gateway to all ecs functionality. It assumes all internal allocations will succeed and returns +/// no errors to keep the API clean and because if a component array cant be allocated you've got bigger problems. +/// Stores a maximum of u8 (256) component Storage(T). +pub const Registry = struct { + typemap: TypeMap, + handles: EntityHandles, + components: std.AutoHashMap(u8, usize), + component_contexts: std.AutoHashMap(u8, usize), + context: usize = 0, + allocator: *std.mem.Allocator, + + 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), + .component_contexts = std.AutoHashMap(u8, usize).init(allocator), + .allocator = allocator, + }; + } + + pub fn deinit(self: *Registry) void { + var it = self.components.iterator(); + while (it.next()) |ptr| { + // HACK: we dont know the Type here but we need to call deinit + var storage = @intToPtr(*Storage(u1), ptr.value); + storage.deinit(); + } + + self.components.deinit(); + self.component_contexts.deinit(); + self.typemap.deinit(); + self.handles.deinit(); + } + + pub fn assure(self: *Registry, comptime T: type) *Storage(T) { + var type_id: u8 = undefined; + if (!self.typemap.getOrPut(T, &type_id)) { + var comp_set = Storage(T).initPtr(self.allocator); + var comp_set_ptr = @ptrToInt(comp_set); + _ = self.components.put(type_id, comp_set_ptr) catch unreachable; + return comp_set; + } + + const ptr = self.components.getValue(type_id).?; + return @intToPtr(*Storage(T), ptr); + } + + pub fn prepare(self: *Registry, comptime T: type) void { + unreachable; + } + + pub fn len(self: *Registry, comptime T: type) usize { + self.assure(T).len(); + } + + pub fn raw(self: Registry, comptime T: type) []T { + return self.assure(T).raw(); + } + + pub fn reserve(self: *Self, comptime T: type, cap: usize) void { + self.assure(T).reserve(cap); + } + + pub fn valid(self: *Registry, entity: Entity) bool { + return self.handles.isAlive(entity); + } + + /// Returns the entity identifier without the version + pub fn entityId(self: Registry, entity: Entity) Entity { + return entity & entity_traits.entity_mask; + } + + /// Returns the version stored along with an entity identifier + pub fn version(self: *Registry, entity: Entity) entity_traits.version_type { + return @truncate(entity_traits.version_type, entity >> @bitSizeOf(entity_traits.index_type)); + } + + /// Creates a new entity and returns it + pub fn create(self: *Registry) Entity { + return self.handles.create(); + } + + /// Destroys an entity + pub fn destroy(self: *Registry, entity: Entity) void { + assert(self.valid(entity)); + self.removeAll(entity); + self.handles.remove(entity) catch unreachable; + } + + pub fn add(self: *Registry, entity: Entity, value: var) void { + assert(self.valid(entity)); + self.assure(@TypeOf(value)).add(entity, value); + } + + /// shortcut for adding raw comptime_int/float without having to @as cast + pub fn addTyped(self: *Registry, comptime T: type, entity: Entity, value: T) void { + self.add(entity, value); + } + + pub fn replace(self: *Registry, entity: Entity, value: var) void { + assert(self.valid(entity)); + var ptr = self.assure(@TypeOf(value)).get(entity); + ptr.* = value; + } + + /// shortcut for replacing raw comptime_int/float without having to @as cast + pub fn replaceTyped(self: *Registry, comptime T: type, entity: Entity, value: T) void { + self.replace(entity, value); + } + + pub fn addOrReplace(self: *Registry, entity: Entity, value: var) void { + assert(self.valid(entity)); + + const store = self.assure(@TypeOf(value)); + if (store.tryGet(entity)) |found| { + found.* = value; + } else { + store.add(entity, value); + } + } + + /// shortcut for add-or-replace raw comptime_int/float without having to @as cast + pub fn addOrReplaceTyped(self: *Registry, T: type, entity: Entity, value: T) void { + self.addOrReplace(entity, value); + } + + /// Removes the given component from an entity + pub fn remove(self: *Registry, comptime T: type, entity: Entity) void { + assert(self.valid(entity)); + self.assure(T).remove(entity); + } + + pub fn removeIfExists(self: *Registry, comptime T: type, entity: Entity) void { + assert(self.valid(entity)); + var store = self.assure(T); + if (store.contains(entity)) + store.remove(entity); + } + + /// Removes all the components from an entity and makes it orphaned + pub fn removeAll(self: *Registry, entity: Entity) void { + assert(self.valid(entity)); + // unreachable; + } + + pub fn has(self: *Registry, comptime T: type, entity: Entity) bool { + assert(self.valid(entity)); + return self.assure(T).set.contains(entity); + } + + pub fn get(self: *Registry, comptime T: type, entity: Entity) *T { + assert(self.valid(entity)); + return self.assure(T).get(entity); + } + + pub fn getConst(self: *Registry, comptime T: type, entity: Entity) T { + assert(self.valid(entity)); + return self.assure(T).getConst(entity); + } + + /// Returns a reference to the given component for an entity + pub fn getOrAdd(self: *Registry, comptime T: type, entity: Entity) *T { + if (self.has(T, entity)) return self.get(T, entity); + self.add(T, entity, std.mem.zeros(T)); + return self.get(T, type); + } + + pub fn tryGet(self: *Registry, comptime T: type, entity: Entity) ?*T { + return self.assure(T).tryGet(entity); + } + + /// Binds an object to the context of the registry + pub fn setContext(self: *Registry, context: var) void { + std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); + self.context = @ptrToInt(context); + } + + /// Unsets a context variable if it exists + pub fn unsetContext(self: *Registry) void { + self.context = 0; + } + + /// Returns a pointer to an object in the context of the registry + pub fn getContext(self: *Registry, comptime T: type) ?*T { + return if (self.context > 0) @intToPtr(*T, self.context) else null; + } + + /// Binds an object to the context of the Component type + pub fn setComponentContext(self: *Registry, comptime Component: type, context: var) void { + std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); + + var type_id: u8 = undefined; + _ = self.typemap.getOrPut(Component, &type_id); + _ = self.component_contexts.put(type_id, @ptrToInt(context)) catch unreachable; + } + + /// Unsets a context variable associated with a Component type if it exists + pub fn unsetComponentContext(self: *Registry, comptime Component: type) void { + var type_id: u8 = undefined; + _ = self.typemap.getOrPut(Component, &type_id); + _ = self.component_contexts.put(type_id, 0) catch unreachable; + } + + /// Returns a pointer to an object in the context of the Component type + pub fn getComponentContext(self: *Registry, comptime Component: type, comptime T: type) ?*T { + var type_id: u8 = undefined; + _ = self.typemap.getOrPut(Component, &type_id); + return if (self.component_contexts.get(type_id)) |ptr| + return if (ptr.value > 0) @intToPtr(*T, ptr.value) else null + else + null; + } + + pub fn view(self: *Registry, comptime includes: var) ViewType(includes) { + std.debug.assert(includes.len > 0); + + if (includes.len == 1) + return BasicView(includes[0]).init(self.assure(includes[0])); + + var arr: [includes.len]u32 = undefined; + inline for (includes) |t, i| { + _ = self.assure(t); + arr[i] = @as(u32, self.typemap.get(t)); + } + + return BasicMultiView(includes.len).init(arr, self); + } + + fn ViewType(comptime includes: var) type { + if (includes.len == 1) return BasicView(includes[0]); + return BasicMultiView(includes.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(); + ctx = reg.getContext(Position); + std.testing.expectEqual(ctx, null); +} + +// this test should fail +test "context not pointer" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var pos = Position{ .x = 5, .y = 5 }; + // reg.setContext(pos); +} + +test "component context get/set/unset" { + const SomeType = struct { dummy: u1}; + + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var ctx = reg.getComponentContext(Position, SomeType); + std.testing.expectEqual(ctx, null); + + var pos = SomeType{ .dummy = 0 }; + reg.setComponentContext(Position, &pos); + ctx = reg.getComponentContext(Position, SomeType); + std.testing.expectEqual(ctx.?, &pos); + + reg.unsetComponentContext(Position); + ctx = reg.getComponentContext(Position, SomeType); + std.testing.expectEqual(ctx, null); +} \ No newline at end of file diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig new file mode 100644 index 0000000..bc0fc28 --- /dev/null +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -0,0 +1,258 @@ +const std = @import("std"); +const warn = std.debug.warn; + +fn printSet(set: var) void { + warn("-------- sparse to dense (len: {}, cap: {}) ------\n", .{ set.len(), set.capacity() }); + var dense = set.toDenseSlice(); + for (dense) |item| { + warn("[{}] ", .{item}); + } + + warn("\n", .{}); + + var sparse = set.data(); + for (sparse) |item| { + warn("[{}] ", .{item}); + } + warn("\n", .{}); +} + +pub fn main() !void { + var set = SparseSet(u32, u8).init(); + defer set.deinit(); + + warn("add 0, 3\n", .{}); + set.add(1); + printSet(set); + set.add(3); + printSet(set); + warn("contains 0: {}, index: {}\n", .{ set.contains(1), set.index(1) }); + warn("contains 3: {}, index: {}\n", .{ set.contains(3), set.index(3) }); + warn("contains 2: {}\n", .{set.contains(2)}); + + printSet(set); + set.swap(1, 3); + warn("----- swap! ----\n", .{}); + warn("contains 0: {}, index: {}\n", .{ set.contains(1), set.index(1) }); + warn("contains 3: {}, index: {}\n", .{ set.contains(3), set.index(3) }); + + printSet(set); + + warn("remove 1\n", .{}); + set.remove(1); + warn("contains 3: {}, index: {}\n", .{ set.contains(3), set.index(3) }); + printSet(set); + + warn("clear\n", .{}); + set.clear(); + printSet(set); + + warn("dense cap: {}\n", .{set.dense.capacity}); +} + +// TODO: fix entity_mask. it should come from EntityTraitsDefinition. +pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { + return struct { + const Self = @This(); + + sparse: std.ArrayList(DenseT), + dense: std.ArrayList(SparseT), + entity_mask: SparseT, + allocator: *std.mem.Allocator, + + pub fn init(allocator: *std.mem.Allocator) *Self { + var set = allocator.create(Self) catch unreachable; + + set.sparse = std.ArrayList(DenseT).init(allocator); + set.dense = std.ArrayList(SparseT).init(allocator); + set.entity_mask = std.math.maxInt(SparseT); + set.allocator = allocator; + + return set; + } + + pub fn deinit(self: *Self) void { + self.dense.deinit(); + self.sparse.deinit(); + self.allocator.destroy(self); + } + + fn page(self: Self, sparse: SparseT) usize { + // TODO: support paging + // return (sparse & EntityTraits.entity_mask) / sparse_per_page; + return sparse & self.entity_mask; + } + + fn assure(self: *Self, pos: usize) []DenseT { + // TODO: support paging + if (self.sparse.capacity < pos or self.sparse.capacity == 0) { + const amount = pos + 1 - self.sparse.capacity; + + // expand and fill with maxInt as an identifier + const old_len = self.sparse.items.len; + self.sparse.resize(self.sparse.items.len + amount) catch unreachable; + self.sparse.expandToCapacity(); + std.mem.set(DenseT, self.sparse.items[old_len..self.sparse.items.len], std.math.maxInt(DenseT)); + } + + return self.sparse.items; + } + + fn offset(self: Self, sparse: SparseT) usize { + // TODO: support paging + // return entt & (sparse_per_page - 1) + return sparse & self.entity_mask; + } + + /// Increases the capacity of a sparse set. + pub fn reserve(self: *Self, cap: usize) void { + self.dense.resize(cap); + } + + /// Returns the number of elements that a sparse set has currently allocated space for + pub fn capacity(self: *Self) usize { + return self.dense.capacity; + } + + /// Returns the number of elements in a sparse set + pub fn len(self: *Self) usize { + return self.dense.items.len; + } + + pub fn empty(self: *Self) bool { + return self.dense.items.len == 0; + } + + pub fn data(self: Self) *const []SparseT { + return &self.dense.items; + } + + pub fn contains(self: Self, sparse: SparseT) bool { + const curr = self.page(sparse); + if (curr >= self.sparse.items.len) + return false; + + // testing against maxInt permits to avoid accessing the packed array + return curr < self.sparse.items.len and self.sparse.items[curr] != std.math.maxInt(DenseT); + } + + /// Returns the position of an entity in a sparse set + pub fn index(self: Self, sparse: SparseT) DenseT { + std.debug.assert(self.contains(sparse)); + return self.sparse.items[self.offset(sparse)]; + } + + /// Assigns an entity to a sparse set + pub fn add(self: *Self, sparse: SparseT) void { + std.debug.assert(!self.contains(sparse)); + + // assure(page(entt))[offset(entt)] = packed.size() + self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(DenseT, self.dense.items.len); + _ = self.dense.append(sparse) catch unreachable; + } + + /// Removes an entity from a sparse set + pub fn remove(self: *Self, sparse: SparseT) void { + std.debug.assert(self.contains(sparse)); + + const curr = self.page(sparse); + const pos = self.offset(sparse); + const last_dense = self.dense.items[self.dense.items.len - 1]; + + self.dense.items[self.sparse.items[curr]] = last_dense; + self.sparse.items[self.page(last_dense)] = self.sparse.items[curr]; + self.sparse.items[curr] = std.math.maxInt(DenseT); + + _ = self.dense.pop(); + } + + /// Swaps two entities in the internal packed array + pub fn swap(self: *Self, sparse_l: SparseT, sparse_r: SparseT) void { + var from = &self.sparse.items[sparse_l]; + var to = &self.sparse.items[sparse_r]; + + std.mem.swap(SparseT, &self.dense.items[from.*], &self.dense.items[to.*]); + std.mem.swap(DenseT, from, to); + } + + /// Sort elements according to the given comparison function + pub fn sort(self: *Self) void { + unreachable; + } + + /// Sort entities according to their order in another sparse set + pub fn respect(self: *Self, other: *Self) void { + unreachable; + } + + pub fn clear(self: *Self) void { + self.sparse.items.len = 0; + self.dense.items.len = 0; + } + + pub fn toDenseSlice(self: Self) []DenseT { + return self.sparse.items; + } + }; +} + +test "add/remove/clear" { + var set = SparseSet(u32, u8).init(std.testing.allocator); + defer set.deinit(); + + set.add(4); + set.add(3); + std.testing.expectEqual(set.len(), 2); + std.testing.expectEqual(set.index(4), 0); + std.testing.expectEqual(set.index(3), 1); + + set.remove(4); + std.testing.expectEqual(set.len(), 1); + + set.clear(); + std.testing.expectEqual(set.len(), 0); +} + +test "grow" { + var set = SparseSet(u32, u8).init(std.testing.allocator); + defer set.deinit(); + + var i = @as(usize, std.math.maxInt(u8)); + while (i > 0) : (i -= 1) { + set.add(@intCast(u32, i)); + } + + std.testing.expectEqual(set.len(), std.math.maxInt(u8)); +} + +test "swap" { + var set = SparseSet(u32, u8).init(std.testing.allocator); + defer set.deinit(); + + set.add(4); + set.add(3); + std.testing.expectEqual(set.index(4), 0); + std.testing.expectEqual(set.index(3), 1); + + set.swap(4, 3); + std.testing.expectEqual(set.index(3), 0); + std.testing.expectEqual(set.index(4), 1); +} + +test "data() synced" { + var set = SparseSet(u32, u8).init(std.testing.allocator); + defer set.deinit(); + + set.add(0); + set.add(1); + set.add(2); + set.add(3); + + var data = set.data(); + std.testing.expectEqual(data.*[1], 1); + std.testing.expectEqual(set.len(), data.len); + + set.remove(0); + set.remove(1); + std.testing.expectEqual(set.len(), data.len); +} diff --git a/zig-ecs/src/ecs/type_map.zig b/zig-ecs/src/ecs/type_map.zig new file mode 100644 index 0000000..c0c675b --- /dev/null +++ b/zig-ecs/src/ecs/type_map.zig @@ -0,0 +1,53 @@ +const std = @import("std"); +const utils = @import("utils.zig"); + +pub const TypeMap = struct { + map: std.AutoHashMap(u32, u8), + counter: u8 = 0, + + pub fn init(allocator: *std.mem.Allocator) TypeMap { + return TypeMap{ + .map = std.AutoHashMap(u32, u8).init(allocator), + }; + } + + pub fn deinit(self: TypeMap) void { + self.map.deinit(); + } + + pub fn contains(self: TypeMap, comptime T: type) bool { + return self.map.contains(@truncate(u32, utils.typeHash(T))); + } + + /// gets the value for T. It MUST exist if you use this method to get it. + pub fn get(self: *TypeMap, comptime T: type) u8 { + return self.map.get(@truncate(u32, utils.typeHash(T))).?.value; + } + + /// gets the value for T if it exists. If it doesnt, it is registered and the value returned. + pub fn getOrPut(self: *TypeMap, comptime T: type, type_id: *u8) bool { + // TODO: is it safe to truncate to u32 here? + var res = self.map.getOrPut(@truncate(u32, utils.typeHash(T))) catch unreachable; + if (!res.found_existing) { + res.kv.value = self.counter; + self.counter += 1; + } + type_id.* = res.kv.value; + return res.found_existing; + } +}; + +test "TypeMap" { + var map = TypeMap.init(std.testing.allocator); + defer map.deinit(); + + var type_id: u8 = undefined; + _ = map.getOrPut(usize, &type_id); + std.testing.expectEqual(@as(u8, 0), type_id); + + _ = map.getOrPut(f32, &type_id); + std.testing.expectEqual(@as(u8, 1), type_id); + + _ = map.getOrPut(usize, &type_id); + std.testing.expectEqual(@as(u8, 0), type_id); +} diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig new file mode 100644 index 0000000..2269f3c --- /dev/null +++ b/zig-ecs/src/ecs/utils.zig @@ -0,0 +1,38 @@ + +/// 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; + while (i < items.len) : (i += 1) { + const x = items[i]; + const y = sub_items[i]; + var j: usize = i; + while (j > 0 and lessThan(x, items[j - 1])) : (j -= 1) { + items[j] = items[j - 1]; + sub_items[j] = sub_items[j - 1]; + } + items[j] = x; + sub_items[j] = y; + } +} + +/// comptime string hashing for the type names +pub fn typeHash(comptime T: type) comptime_int { + return stringHash(@typeName(T)); +} + +/// comptime string hashing, djb2 by Dan Bernstein +pub fn stringHash(comptime str: []const u8) comptime_int { + var hash: comptime_int = 5381; + for (str) |c| { + hash = ((hash << 5) + hash) + @intCast(comptime_int, c); + } + + return hash; +} + +pub fn isComptime(comptime T: type) bool { + return switch (@typeInfo(T)) { + .ComptimeInt, .ComptimeFloat => true, + else => false, + }; +} \ No newline at end of file diff --git a/zig-ecs/src/ecs/view.zig b/zig-ecs/src/ecs/view.zig new file mode 100644 index 0000000..e921556 --- /dev/null +++ b/zig-ecs/src/ecs/view.zig @@ -0,0 +1,217 @@ +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; + + +/// single item view. Iterating raw() directly is the fastest way to get at the data. +pub fn BasicView(comptime T: type) type { + return struct { + const Self = @This(); + + storage: *Storage(T), + + pub fn init(storage: *Storage(T)) Self { + return Self { + .storage = storage, + }; + } + + pub fn len(self: Self) usize { + return self.storage.len(); + } + + /// Direct access to the array of components + pub fn raw(self: Self) []T { + return self.storage.raw(); + } + + /// Direct access to the array of entities + pub fn data(self: Self) *const []Entity { + return self.storage.data(); + } + + /// Returns the object associated with an entity + pub fn get(self: Self, entity: Entity) *T { + return self.storage.get(entity); + } + }; +} + +pub fn BasicMultiView(comptime n: usize) type { + return struct { + const Self = @This(); + + type_ids: [n]u32, + registry: *Registry, + + pub const Iterator = struct { + view: *Self, + index: usize = 0, + entities: *const []Entity, + + pub fn init(view: *Self) Iterator { + const ptr = view.registry.components.getValue(@intCast(u8, view.type_ids[0])).?; + return .{ + .view = view, + .entities = @intToPtr(*Storage(u8), ptr).data(), + }; + } + + pub fn next(it: *Iterator) ?Entity { + if (it.index >= it.entities.len) return null; + + blk: while (it.index < it.entities.len) : (it.index += 1) { + const entity = it.entities.*[it.index]; + + // entity must be in all other Storages + for (it.view.type_ids) |tid| { + const ptr = it.view.registry.components.getValue(@intCast(u8, tid)).?; + if (!@intToPtr(*Storage(u8), ptr).contains(entity)) { + break :blk; + } + } + it.index += 1; + return entity; + } + + return null; + } + + // Reset the iterator to the initial index + pub fn reset(it: *Iterator) void { + it.index = 0; + } + }; + + pub fn init(type_ids: [n]u32, registry: *Registry) Self { + return Self{ + .type_ids = 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); + + std.debug.assert(store.contains(entity)); + return store.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); + + std.debug.assert(store.contains(entity)); + return store.getConst(entity); + } + + fn sort(self: *Self) void { + // get our component counts in an array so we can sort the type_ids based on how many entities are in each + var sub_items: [n]usize = undefined; + for (self.type_ids) |tid, i| { + const ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + const store = @intToPtr(*Storage(u8), ptr); + sub_items[i] = store.len(); + } + + utils.sortSub(usize, u32, sub_items[0..], self.type_ids[0..], std.sort.asc(usize)); + } + + pub fn iterator(self: *Self) Iterator { + self.sort(); + return Iterator.init(self); + } + }; +} + + +test "single basic view" { + var store = Storage(f32).init(std.testing.allocator); + defer store.deinit(); + + store.add(3, 30); + store.add(5, 50); + store.add(7, 70); + + var view = BasicView(f32).init(&store); + std.testing.expectEqual(view.len(), 3); + + store.remove(7); + std.testing.expectEqual(view.len(), 2); +} + +test "single basic view data" { + var store = Storage(f32).init(std.testing.allocator); + defer store.deinit(); + + store.add(3, 30); + store.add(5, 50); + store.add(7, 70); + + var view = BasicView(f32).init(&store); + + std.testing.expectEqual(view.get(3).*, 30); + + for (view.data().*) |entity, i| { + if (i == 0) + std.testing.expectEqual(entity, 3); + if (i == 1) + std.testing.expectEqual(entity, 5); + if (i == 2) + std.testing.expectEqual(entity, 7); + } + + for (view.raw()) |data, i| { + if (i == 0) + std.testing.expectEqual(data, 30); + if (i == 1) + std.testing.expectEqual(data, 50); + if (i == 2) + std.testing.expectEqual(data, 70); + } + + std.testing.expectEqual(view.len(), 3); +} + +test "basic multi view" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var e0 = reg.create(); + var e1 = reg.create(); + var e2 = reg.create(); + + reg.add(e0, @as(i32, -0)); + reg.add(e1, @as(i32, -1)); + reg.add(e2, @as(i32, -2)); + + reg.add(e0, @as(u32, 0)); + reg.add(e2, @as(u32, 2)); + + var single_view = reg.view(.{u32}); + var view = reg.view(.{ i32, u32 }); + + var iterated_entities: usize = 0; + var iter = view.iterator(); + while (iter.next()) |entity| { + iterated_entities += 1; + } + + std.testing.expectEqual(iterated_entities, 2); + iterated_entities = 0; + + reg.remove(u32, e0); + + iter.reset(); + while (iter.next()) |entity| { + iterated_entities += 1; + } + + std.testing.expectEqual(iterated_entities, 1); +} \ No newline at end of file diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig new file mode 100644 index 0000000..a0724b5 --- /dev/null +++ b/zig-ecs/src/signals/delegate.zig @@ -0,0 +1,87 @@ +const std = @import("std"); + +/// wraps either a free function or a bound function that takes an Event as a parameter +pub fn Delegate(comptime Event: type) type { + return struct { + const Self = @This(); + + ctx_ptr_address: usize = 0, + callback: union(enum) { + free: fn (Event) void, + bound: fn (usize, Event) void, + }, + + /// sets a bound function as the Delegate callback + pub fn initBound(ctx: var, comptime fn_name: []const u8) Self { + std.debug.assert(@ptrToInt(ctx) != 0); + std.debug.assert(@typeInfo(@TypeOf(ctx)) == .Pointer); + + const T = @TypeOf(ctx); + return Self{ + .ctx_ptr_address = @ptrToInt(ctx), + .callback = .{ + .bound = struct { + fn cb(self: usize, param: Event) void { + return @call(.{ .modifier = .always_inline }, @field(@intToPtr(T, self), fn_name), .{param}); + } + }.cb, + }, + }; + } + + /// sets a free function as the Delegate callback + pub fn initFree(func: fn (Event) void) Self { + return Self{ + .callback = .{ .free = func }, + }; + } + + pub fn trigger(self: Self, param: Event) void { + switch (self.callback) { + .free => |func| @call(.{}, func, .{param}), + .bound => |func| @call(.{}, func, .{ self.ctx_ptr_address, param }), + } + } + + pub fn containsFree(self: Self, callback: fn (Event) void) bool { + return switch (self.callback) { + .free => |func| func == callback, + else => false, + }; + } + + pub fn containsBound(self: Self, ctx: var) bool { + std.debug.assert(@ptrToInt(ctx) != 0); + std.debug.assert(@typeInfo(@TypeOf(ctx)) == .Pointer); + + return switch (self.callback) { + .bound => @ptrToInt(ctx) == self.ctx_ptr_address, + else => false, + }; + } + }; +} + +fn tester(param: u32) void { + std.testing.expectEqual(@as(u32, 666), param); +} + +const Thing = struct { + field: f32 = 0, + + pub fn tester(self: *Thing, param: u32) void { + std.testing.expectEqual(@as(u32, 777), param); + } +}; + +test "free Delegate" { + var d = Delegate(u32).initFree(tester); + d.trigger(666); +} + +test "bound Delegate" { + var thing = Thing{}; + + var d = Delegate(u32).initBound(&thing, "tester"); + d.trigger(777); +} diff --git a/zig-ecs/src/signals/dispatcher.zig b/zig-ecs/src/signals/dispatcher.zig new file mode 100644 index 0000000..f70945c --- /dev/null +++ b/zig-ecs/src/signals/dispatcher.zig @@ -0,0 +1,51 @@ +const std = @import("std"); +const Sink = @import("sink.zig").Sink; +const Signal = @import("signal.zig").Signal; +const TypeMap = @import("../ecs/type_map.zig").TypeMap; + +pub const Dispatcher = struct { + typemap: TypeMap, + signals: std.AutoHashMap(u8, usize), + allocator: *std.mem.Allocator, + + pub fn init(allocator: *std.mem.Allocator) Dispatcher { + return Dispatcher { + .typemap = TypeMap.init(allocator), + .signals = std.AutoHashMap(u8, usize).init(allocator), + .allocator = allocator, + }; + } + + pub fn deinit(self: Dispatcher) void { + var it = self.signals.iterator(); + while (it.next()) |ptr| { + // HACK: we dont know the Type here but we need to call deinit + var signal = @intToPtr(*Signal(void), ptr.value); + signal.deinit(); + } + + self.typemap.deinit(); + self.signals.deinit(); + } + + fn assure(self: *Dispatcher, comptime T: type) *Signal(T) { + var type_id: u8 = undefined; + if (!self.typemap.getOrPut(T, &type_id)) { + var signal = Signal(T).create(self.allocator); + var signal_ptr = @ptrToInt(signal); + _ = self.signals.put(type_id, signal_ptr) catch unreachable; + return signal; + } + + const ptr = self.signals.getValue(type_id).?; + return @intToPtr(*Signal(T), ptr); + } + + pub fn sink(self: *Dispatcher, comptime T: type) Sink(T) { + return self.assure(T).sink(); + } + + pub fn trigger(self: *Dispatcher, comptime T: type, value: T) void { + self.assure(T).publish(value); + } +}; diff --git a/zig-ecs/src/signals/signal.zig b/zig-ecs/src/signals/signal.zig new file mode 100644 index 0000000..f8b923f --- /dev/null +++ b/zig-ecs/src/signals/signal.zig @@ -0,0 +1,95 @@ +const std = @import("std"); +const Sink = @import("sink.zig").Sink; +const Delegate = @import("delegate.zig").Delegate; + +pub fn Signal(comptime Event: type) type { + return struct { + const Self = @This(); + + calls: std.ArrayList(Delegate(Event)), + allocator: ?*std.mem.Allocator = null, + + pub fn init(allocator: *std.mem.Allocator) Self { + // we purposely do not store the allocator locally in this case! + return Self{ + .calls = std.ArrayList(Delegate(Event)).init(allocator), + }; + } + + /// heap allocates a Signal + pub fn create(allocator: *std.mem.Allocator) *Self { + var signal = allocator.create(Self) catch unreachable; + signal.calls = std.ArrayList(Delegate(Event)).init(allocator); + signal.allocator = allocator; + return signal; + } + + pub fn deinit(self: *Self) void { + self.calls.deinit(); + + // optionally destroy ourself as well if we came from an allocator + if (self.allocator) |allocator| allocator.destroy(self); + } + + pub fn size(self: Self) usize { + return self.calls.items.len; + } + + pub fn empty(self: Self) bool { + return self.size == 0; + } + + /// Disconnects all the listeners from a signal + pub fn clear(self: *Self) void { + self.calls.items.len = 0; + } + + pub fn publish(self: Self, arg: Event) void { + for (self.calls.items) |call| { + call.trigger(arg); + } + } + + /// Constructs a sink that is allowed to modify a given signal + pub fn sink(self: *Self) Sink(Event) { + return Sink(Event).init(self); + } + }; +} + +fn tester(param: u32) void { + std.testing.expectEqual(@as(u32, 666), param); +} + +const Thing = struct { + field: f32 = 0, + + pub fn tester(self: *Thing, param: u32) void { + std.testing.expectEqual(@as(u32, 666), param); + } +}; + +test "Signal/Sink" { + var signal = Signal(u32).init(std.testing.allocator); + defer signal.deinit(); + + var sink = signal.sink(); + sink.connect(tester); + std.testing.expectEqual(@as(usize, 1), signal.size()); + + sink.connect(tester); + std.testing.expectEqual(@as(usize, 1), signal.size()); + + // bound listener + var thing = Thing{}; + sink.connectBound(&thing, "tester"); + + signal.publish(666); + + sink.disconnect(tester); + signal.publish(666); + std.testing.expectEqual(@as(usize, 1), signal.size()); + + sink.disconnectBound(&thing); + std.testing.expectEqual(@as(usize, 0), signal.size()); +} diff --git a/zig-ecs/src/signals/sink.zig b/zig-ecs/src/signals/sink.zig new file mode 100644 index 0000000..d915ab9 --- /dev/null +++ b/zig-ecs/src/signals/sink.zig @@ -0,0 +1,47 @@ +const std = @import("std"); +const Signal = @import("signal.zig").Signal; +const Delegate = @import("delegate.zig").Delegate; + +/// helper used to connect and disconnect listeners on the fly from a Signal. Listeners are wrapped in Delegates +/// and can be either free functions or functions bound to a struct. +pub fn Sink(comptime Event: type) type { + return struct { + const Self = @This(); + + /// the Signal this Sink is temporarily wrapping + var owning_signal: *Signal(Event) = undefined; + + pub fn init(signal: *Signal(Event)) Self { + owning_signal = signal; + return Self{}; + } + + pub fn connect(self: Self, callback: fn (Event) void) void { + self.disconnect(callback); + _ = owning_signal.calls.append(Delegate(Event).initFree(callback)) catch unreachable; + } + + pub fn connectBound(self: Self, ctx: var, comptime fn_name: []const u8) void { + self.disconnectBound(ctx); + _ = owning_signal.calls.append(Delegate(Event).initBound(ctx, fn_name)) catch unreachable; + } + + pub fn disconnect(self: Self, callback: fn (Event) void) void { + for (owning_signal.calls.items) |call, i| { + if (call.containsFree(callback)) { + _ = owning_signal.calls.swapRemove(i); + break; + } + } + } + + pub fn disconnectBound(self: Self, ctx: var) void { + for (owning_signal.calls.items) |call, i| { + if (call.containsBound(ctx)) { + _ = owning_signal.calls.swapRemove(i); + break; + } + } + } + }; +} diff --git a/zig-ecs/src/tests.zig b/zig-ecs/src/tests.zig new file mode 100644 index 0000000..391c5f8 --- /dev/null +++ b/zig-ecs/src/tests.zig @@ -0,0 +1,15 @@ +// include all files with tests +comptime { + // ecs + _ = @import("ecs/actor.zig"); + _ = @import("ecs/component_storage.zig"); + _ = @import("ecs/entity.zig"); + _ = @import("ecs/handles.zig"); + _ = @import("ecs/sparse_set.zig"); + _ = @import("ecs/type_map.zig"); + _ = @import("ecs/view.zig"); + + // signals + _ = @import("signals/delegate.zig"); + _ = @import("signals/signal.zig"); +} diff --git a/zig-ecs/tests/dispatcher_test.zig b/zig-ecs/tests/dispatcher_test.zig new file mode 100644 index 0000000..63cc4a2 --- /dev/null +++ b/zig-ecs/tests/dispatcher_test.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const Dispatcher = @import("ecs").Dispatcher; + +fn tester(param: u32) void { + std.testing.expectEqual(@as(u32, 666), param); +} + +fn tester2(param: i32) void { + std.testing.expectEqual(@as(i32, -543), param); +} + +const Thing = struct { + field: f32 = 0, + + pub fn testU32(self: *Thing, param: u32) void { + std.testing.expectEqual(@as(u32, 666), param); + } + + pub fn testI32(self: *Thing, param: i32) void { + std.testing.expectEqual(@as(i32, -543), param); + } +}; + + +test "Dispatcher" { + var thing = Thing{}; + + var d = Dispatcher.init(std.testing.allocator); + defer d.deinit(); + + var sink = d.sink(u32); + sink.connect(tester); + sink.connectBound(&thing, "testU32"); + d.trigger(u32, 666); + + var sink2 = d.sink(i32); + sink2.connect(tester2); + sink2.connectBound(&thing, "testI32"); + d.trigger(i32, -543); +} \ No newline at end of file diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig new file mode 100644 index 0000000..9a118f2 --- /dev/null +++ b/zig-ecs/tests/registry_test.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const ecs = @import("ecs"); +const Registry = @import("ecs").Registry; + +const Velocity = struct { x: f32, y: f32 }; +const Position = struct { x: f32, y: f32 }; +const Empty = struct {}; +const BigOne = struct { pos: Position, vel: Velocity, accel: Velocity }; + +test "entity traits" { + const traits = ecs.EntityTraitsType(.large).init(); +} + +test "Registry" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var e1 = reg.create(); + + reg.add(e1, Empty{}); + reg.add(e1, Position{ .x = 5, .y = 5 }); + reg.add(e1, BigOne{ .pos = Position{ .x = 5, .y = 5 }, .vel = Velocity{ .x = 5, .y = 5 }, .accel = Velocity{ .x = 5, .y = 5 } }); + + std.testing.expect(reg.has(Empty, e1)); + + reg.remove(Empty, e1); + std.testing.expect(!reg.has(Empty, e1)); +} diff --git a/zig-ecs/tests/tests.zig b/zig-ecs/tests/tests.zig new file mode 100644 index 0000000..2f11302 --- /dev/null +++ b/zig-ecs/tests/tests.zig @@ -0,0 +1,4 @@ +test "ecs test suite" { + _ = @import("dispatcher_test.zig"); + _ = @import("registry_test.zig"); +} From f7e2c263ba5279f16eb6e9c5c29cad910c391402 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 31 May 2020 21:48:47 -0700 Subject: [PATCH 002/146] debugging and vscode stuffs --- zig-ecs/.vscode/launch.json | 13 +++++++ zig-ecs/.vscode/tasks.json | 72 +++++++++++++++++++++++++++++++++++++ zig-ecs/build.zig | 2 ++ 3 files changed, 87 insertions(+) create mode 100644 zig-ecs/.vscode/launch.json create mode 100644 zig-ecs/.vscode/tasks.json diff --git a/zig-ecs/.vscode/launch.json b/zig-ecs/.vscode/launch.json new file mode 100644 index 0000000..0251a3b --- /dev/null +++ b/zig-ecs/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "lldb Debug ecs binary", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/zig-cache/bin/ecs", + "args": [], + "preLaunchTask": "Build Project", + } + ] +} \ No newline at end of file diff --git a/zig-ecs/.vscode/tasks.json b/zig-ecs/.vscode/tasks.json new file mode 100644 index 0000000..425f970 --- /dev/null +++ b/zig-ecs/.vscode/tasks.json @@ -0,0 +1,72 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build Project", + "type": "shell", + "command": "zig build", + "problemMatcher": [ + "$gcc" + ], + }, + { + "label": "Build and Run Project", + "type": "shell", + "command": "zig build run", + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Test Project", + "type": "shell", + "command": "zig build test", + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "clear": true + } + }, + { + "label": "Build and Run Current File", + "type": "shell", + "command": "zig run ${file}", + "problemMatcher": [ + "$gcc" + ], + "presentation": { + "clear": true + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Build and Run Tests in Current File", + "type": "shell", + "command": "zig test ${file}", + "problemMatcher": [ + "$gcc" + ], + "presentation": { + "clear": true + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + ] +} \ No newline at end of file diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index d182520..679ac1c 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -23,6 +23,8 @@ pub fn build(b: *Builder) void { // first element in the list is added as "run" so "zig build run" works if (i == 0) { + exe.name = "ecs"; + exe.setOutputDir("zig-cache/bin"); const run_exe_step = b.step("run", b.fmt("run {}.zig", .{name})); run_exe_step.dependOn(&run_cmd.step); } From ed23befd26750a6148dc3d1c954b4a5b1d8cac92 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 31 May 2020 22:10:21 -0700 Subject: [PATCH 003/146] formatting --- zig-ecs/build.zig | 5 ++--- zig-ecs/examples/simple.zig | 8 ++++---- zig-ecs/src/ecs/registry.zig | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 679ac1c..f3d58d0 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -10,7 +10,7 @@ pub fn build(b: *Builder) void { }; for (examples) |example, i| { - const name = example[0]; + const name = if (i == 0) "ecs" else example[0]; const source = example[1]; var exe = b.addExecutable(name, source); @@ -23,7 +23,6 @@ pub fn build(b: *Builder) void { // first element in the list is added as "run" so "zig build run" works if (i == 0) { - exe.name = "ecs"; exe.setOutputDir("zig-cache/bin"); const run_exe_step = b.step("run", b.fmt("run {}.zig", .{name})); run_exe_step.dependOn(&run_cmd.step); @@ -71,4 +70,4 @@ pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std } artifact.addPackagePath("ecs", std.fs.path.join(b.allocator, &[_][]const u8{ rel_path, "ecs.zig" }) catch unreachable); -} \ No newline at end of file +} diff --git a/zig-ecs/examples/simple.zig b/zig-ecs/examples/simple.zig index 07b488e..a67c325 100644 --- a/zig-ecs/examples/simple.zig +++ b/zig-ecs/examples/simple.zig @@ -19,13 +19,13 @@ pub fn main() !void { reg.add(e2, Position{ .x = 10, .y = 10 }); reg.add(e2, Velocity{ .x = 15, .y = 17 }); - var view = reg.view(.{Velocity, Position}); + var view = reg.view(.{ Velocity, Position }); var iter = view.iterator(); while (iter.next()) |entity| { var pos = view.get(Position, entity); const vel = view.getConst(Velocity, entity); - std.debug.warn("entity: {}, pos: {d}, vel: {d}\n", .{entity, pos.*, vel}); + std.debug.warn("entity: {}, pos: {d}, vel: {d}\n", .{ entity, pos.*, vel }); pos.*.x += vel.x; pos.*.y += vel.y; } @@ -36,6 +36,6 @@ pub fn main() !void { while (iter.next()) |entity| { const pos = view.getConst(Position, entity); const vel = view.getConst(Velocity, entity); - std.debug.warn("entity: {}, pos: {d}, vel: {d}\n", .{entity, pos, vel}); + std.debug.warn("entity: {}, pos: {d}, vel: {d}\n", .{ entity, pos, vel }); } -} \ No newline at end of file +} diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 0201e13..716d05d 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -227,7 +227,7 @@ pub const Registry = struct { _ = self.component_contexts.put(type_id, 0) catch unreachable; } - /// Returns a pointer to an object in the context of the Component type + /// Returns a pointer to an object in the context of the Component type pub fn getComponentContext(self: *Registry, comptime Component: type, comptime T: type) ?*T { var type_id: u8 = undefined; _ = self.typemap.getOrPut(Component, &type_id); @@ -287,7 +287,7 @@ test "context not pointer" { } test "component context get/set/unset" { - const SomeType = struct { dummy: u1}; + const SomeType = struct { dummy: u1 }; var reg = Registry.init(std.testing.allocator); defer reg.deinit(); @@ -303,4 +303,4 @@ test "component context get/set/unset" { reg.unsetComponentContext(Position); ctx = reg.getComponentContext(Position, SomeType); std.testing.expectEqual(ctx, null); -} \ No newline at end of file +} From 5bf1754dfb6c0fb6602cbeb3afe2b394b1f03b9d Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 1 Jun 2020 18:39:00 -0700 Subject: [PATCH 004/146] removed component contexts --- zig-ecs/src/ecs/component_storage.zig | 3 -- zig-ecs/src/ecs/registry.zig | 57 +++++++++++---------------- 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 773d554..fe7ddaf 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -32,7 +32,6 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D .instances = undefined, .safe_deinit = struct { fn deinit(self: *Self) void { - std.debug.warn("------ (inner) T: {}, size: {}, is_empty_struct: {}\n", .{ @typeName(@TypeOf(self)), @sizeOf(CompT), is_empty_struct }); if (!is_empty_struct) self.instances.deinit(); } @@ -56,7 +55,6 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D // since we are stored as a pointer, we need to catpure this store.safe_deinit = struct { fn deinit(self: *Self) void { - std.debug.warn("------ (inner) T: {}, size: {}, is_empty_struct: {}\n", .{ @typeName(@TypeOf(self)), @sizeOf(CompT), is_empty_struct }); if (!is_empty_struct) self.instances.deinit(); } @@ -69,7 +67,6 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D // great care must be taken here. Due to how Registry keeps this struct as pointers anything touching a type // will be wrong since it has to cast to a random struct when deiniting. Because of all that, is_empty_struct // will allways be false here so we have to deinit the instances no matter what. - std.debug.warn("\n------ (deinit) T: {}, size: {}, is_empty_struct: {}\n", .{ @typeName(@TypeOf(self)), @sizeOf(CompT), is_empty_struct }); self.safe_deinit(self); self.set.deinit(); diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 716d05d..3dd68c7 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -30,8 +30,7 @@ pub const Registry = struct { typemap: TypeMap, handles: EntityHandles, components: std.AutoHashMap(u8, usize), - component_contexts: std.AutoHashMap(u8, usize), - context: usize = 0, + contexts: std.AutoHashMap(u8, usize), allocator: *std.mem.Allocator, pub fn init(allocator: *std.mem.Allocator) Registry { @@ -39,7 +38,7 @@ pub const Registry = struct { .typemap = TypeMap.init(allocator), .handles = EntityHandles.init(allocator), .components = std.AutoHashMap(u8, usize).init(allocator), - .component_contexts = std.AutoHashMap(u8, usize).init(allocator), + .contexts = std.AutoHashMap(u8, usize).init(allocator), .allocator = allocator, }; } @@ -53,7 +52,7 @@ pub const Registry = struct { } self.components.deinit(); - self.component_contexts.deinit(); + self.contexts.deinit(); self.typemap.deinit(); self.handles.deinit(); } @@ -198,40 +197,28 @@ pub const Registry = struct { /// Binds an object to the context of the registry pub fn setContext(self: *Registry, context: var) void { std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); - self.context = @ptrToInt(context); - } - - /// Unsets a context variable if it exists - pub fn unsetContext(self: *Registry) void { - self.context = 0; - } - /// Returns a pointer to an object in the context of the registry - pub fn getContext(self: *Registry, comptime T: type) ?*T { - return if (self.context > 0) @intToPtr(*T, self.context) else null; + var type_id: u8 = undefined; + _ = self.typemap.getOrPut(@typeInfo(@TypeOf(context)).Pointer.child, &type_id); + _ = self.contexts.put(type_id, @ptrToInt(context)) catch unreachable; } - /// Binds an object to the context of the Component type - pub fn setComponentContext(self: *Registry, comptime Component: type, context: var) void { - std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); + /// Unsets a context variable if it exists + pub fn unsetContext(self: *Registry, comptime T: type) void { + std.debug.assert(@typeInfo(T) != .Pointer); var type_id: u8 = undefined; - _ = self.typemap.getOrPut(Component, &type_id); - _ = self.component_contexts.put(type_id, @ptrToInt(context)) catch unreachable; + _ = self.typemap.getOrPut(T, &type_id); + _ = self.contexts.put(type_id, 0) catch unreachable; } - /// Unsets a context variable associated with a Component type if it exists - pub fn unsetComponentContext(self: *Registry, comptime Component: type) void { - var type_id: u8 = undefined; - _ = self.typemap.getOrPut(Component, &type_id); - _ = self.component_contexts.put(type_id, 0) catch unreachable; - } + /// Returns a pointer to an object in the context of the registry + pub fn getContext(self: *Registry, comptime T: type) ?*T { + std.debug.assert(@typeInfo(T) != .Pointer); - /// Returns a pointer to an object in the context of the Component type - pub fn getComponentContext(self: *Registry, comptime Component: type, comptime T: type) ?*T { var type_id: u8 = undefined; - _ = self.typemap.getOrPut(Component, &type_id); - return if (self.component_contexts.get(type_id)) |ptr| + _ = self.typemap.getOrPut(T, &type_id); + return if (self.contexts.get(type_id)) |ptr| return if (ptr.value > 0) @intToPtr(*T, ptr.value) else null else null; @@ -272,7 +259,7 @@ test "context get/set/unset" { ctx = reg.getContext(Position); std.testing.expectEqual(ctx.?, &pos); - reg.unsetContext(); + reg.unsetContext(Position); ctx = reg.getContext(Position); std.testing.expectEqual(ctx, null); } @@ -292,15 +279,15 @@ test "component context get/set/unset" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); - var ctx = reg.getComponentContext(Position, SomeType); + var ctx = reg.getContext(SomeType); std.testing.expectEqual(ctx, null); var pos = SomeType{ .dummy = 0 }; - reg.setComponentContext(Position, &pos); - ctx = reg.getComponentContext(Position, SomeType); + reg.setContext(&pos); + ctx = reg.getContext(SomeType); std.testing.expectEqual(ctx.?, &pos); - reg.unsetComponentContext(Position); - ctx = reg.getComponentContext(Position, SomeType); + reg.unsetContext(SomeType); + ctx = reg.getContext(SomeType); std.testing.expectEqual(ctx, null); } From 0b2542d2924d329a5e50851173e1a8353fcbb90a Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 1 Jun 2020 20:05:07 -0700 Subject: [PATCH 005/146] add signals --- zig-ecs/src/ecs/component_storage.zig | 83 ++++++++++++++++++++++++--- zig-ecs/src/ecs/registry.zig | 79 +++++++++++++++++++++++-- zig-ecs/src/ecs/sparse_set.zig | 2 +- zig-ecs/src/signals/signal.zig | 2 +- 4 files changed, 150 insertions(+), 16 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index fe7ddaf..09d37a0 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -3,7 +3,11 @@ const warn = std.debug.warn; const utils = @import("utils.zig"); const SparseSet = @import("sparse_set.zig").SparseSet; +const Signal = @import("../signals/signal.zig").Signal; +const Sink = @import("../signals/sink.zig").Sink; +/// Stores an ArrayList of components along with a SparseSet of entities. The max amount that can be stored is +/// based on the max value of DenseT pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime DenseT: type) type { std.debug.assert(!utils.isComptime(CompT)); @@ -25,6 +29,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D instances: std.ArrayList(CompOrAlmostEmptyT), allocator: ?*std.mem.Allocator, safe_deinit: fn (*Self) void, + construction: Signal(EntityT), + update: Signal(EntityT), + destruction: Signal(EntityT), pub fn init(allocator: *std.mem.Allocator) Self { var store = Self{ @@ -37,6 +44,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D } }.deinit, .allocator = null, + .construction = Signal(EntityT).init(allocator), + .update = Signal(EntityT).init(allocator), + .destruction = Signal(EntityT).init(allocator), }; if (!is_empty_struct) @@ -51,6 +61,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D if (!is_empty_struct) store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); store.allocator = allocator; + store.construction = Signal(EntityT).init(allocator); + store.update = Signal(EntityT).init(allocator); + store.destruction = Signal(EntityT).init(allocator); // since we are stored as a pointer, we need to catpure this store.safe_deinit = struct { @@ -69,11 +82,26 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D // will allways be false here so we have to deinit the instances no matter what. self.safe_deinit(self); self.set.deinit(); + self.construction.deinit(); + self.update.deinit(); + self.destruction.deinit(); if (self.allocator) |allocator| allocator.destroy(self); } + pub fn onConstruct(self: *Self) Sink(EntityT) { + return self.construction.sink(); + } + + pub fn onUpdate(self: *Self) Sink(EntityT) { + return self.update.sink(); + } + + pub fn onDestruct(self: *Self) Sink(EntityT) { + return self.destruction.sink(); + } + /// Increases the capacity of a component storage pub fn reserve(self: *Self, cap: usize) void { self.set.reserve(cap); @@ -81,11 +109,20 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D self.instances.items.reserve(cap); } - /// Assigns an entity to a storage and constructs its object + /// Assigns an entity to a storage and assigns its object pub fn add(self: *Self, entity: EntityT, value: CompT) void { if (!is_empty_struct) _ = self.instances.append(value) catch unreachable; self.set.add(entity); + self.construction.publish(entity); + } + + /// Removes an entity from a storage + pub fn remove(self: *Self, entity: EntityT) void { + if (!is_empty_struct) + _ = self.instances.swapRemove(self.set.index(entity)); + self.destruction.publish(entity); + self.set.remove(entity); } /// Checks if a view contains an entity @@ -106,6 +143,12 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D return self.instances.items; } + /// Replaces the given component for an entity + pub fn replace(self: *Self, entity: EntityT, value: CompT) void { + self.get(entity).* = value; + self.update.publish(entity); + } + /// Returns the object associated with an entity pub fn get(self: *Self, entity: EntityT) *CompT { std.debug.assert(self.contains(entity)); @@ -131,13 +174,6 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D return self.set.data(); } - /// Removes an entity from a storage - pub fn remove(self: *Self, entity: EntityT) void { - if (!is_empty_struct) - _ = self.instances.swapRemove(self.set.index(entity)); - self.set.remove(entity); - } - /// Swaps entities and objects in the internal packed arrays pub fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { if (!is_empty_struct) @@ -208,3 +244,34 @@ test "empty component" { store.add(3, Empty{}); store.remove(3); } + +fn construct(e: u32) void { + std.debug.assert(e == 3); +} +fn update(e: u32) void { + std.debug.assert(e == 3); +} +fn destruct(e: u32) void { + std.debug.assert(e == 3); +} + +test "signals" { + var store = ComponentStorage(f32, u32, u8).init(std.testing.allocator); + defer store.deinit(); + + store.onConstruct().connect(construct); + store.onUpdate().connect(update); + store.onDestruct().connect(destruct); + + store.add(3, 66.45); + store.replace(3, 45.64); + store.remove(3); + + store.onConstruct().disconnect(construct); + store.onUpdate().disconnect(update); + store.onDestruct().disconnect(destruct); + + store.add(4, 66.45); + store.replace(4, 45.64); + store.remove(4); +} diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 3dd68c7..96d8944 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -70,20 +70,29 @@ pub const Registry = struct { return @intToPtr(*Storage(T), ptr); } + /// Prepares a pool for the given type if required pub fn prepare(self: *Registry, comptime T: type) void { - unreachable; + _ = self.assure(T); } + /// Returns the number of existing components of the given type pub fn len(self: *Registry, comptime T: type) usize { self.assure(T).len(); } + /// Increases the capacity of the registry or of the pools for the given component + pub fn reserve(self: *Self, comptime T: type, cap: usize) void { + self.assure(T).reserve(cap); + } + + /// Direct access to the list of components of a given pool pub fn raw(self: Registry, comptime T: type) []T { return self.assure(T).raw(); } - pub fn reserve(self: *Self, comptime T: type, cap: usize) void { - self.assure(T).reserve(cap); + /// Direct access to the list of entities of a given pool + pub fn data(self: Registry, comptime T: type) []Entity { + return self.assure(T).data(); } pub fn valid(self: *Registry, entity: Entity) bool { @@ -122,10 +131,10 @@ pub const Registry = struct { self.add(entity, value); } + /// Replaces the given component for an entity pub fn replace(self: *Registry, entity: Entity, value: var) void { assert(self.valid(entity)); - var ptr = self.assure(@TypeOf(value)).get(entity); - ptr.* = value; + self.assure(@TypeOf(value)).replace(entity, value); } /// shortcut for replacing raw comptime_int/float without having to @as cast @@ -165,7 +174,13 @@ pub const Registry = struct { /// Removes all the components from an entity and makes it orphaned pub fn removeAll(self: *Registry, entity: Entity) void { assert(self.valid(entity)); - // unreachable; + + var it = self.components.iterator(); + while (it.next()) |ptr| { + // HACK: we dont know the Type here but we need to be able to call methods on the Storage(T) + var store = @intToPtr(*Storage(u128), ptr.value); + if (store.contains(entity)) store.remove(entity); + } } pub fn has(self: *Registry, comptime T: type, entity: Entity) bool { @@ -194,6 +209,21 @@ pub const Registry = struct { return self.assure(T).tryGet(entity); } + /// Returns a Sink object for the given component to add/remove listeners with + pub fn onConstruct(self: *Self, comptime T: type) Sink(Entity) { + return self.assure(T).onConstruct(); + } + + /// Returns a Sink object for the given component to add/remove listeners with + pub fn onUpdate(self: *Self, comptime T: type) Sink(Entity) { + return self.assure(T).onUpdate(); + } + + /// Returns a Sink object for the given component to add/remove listeners with + pub fn onDestruct(self: *Self, comptime T: type) Sink(Entity) { + return self.assure(T).onDestruct(); + } + /// Binds an object to the context of the registry pub fn setContext(self: *Registry, context: var) void { std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); @@ -291,3 +321,40 @@ test "component context get/set/unset" { ctx = reg.getContext(SomeType); std.testing.expectEqual(ctx, null); } + +test "destroy" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + 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) }); + } + + 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)); + } +} + +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/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index bc0fc28..fcf2967 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -85,7 +85,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { fn assure(self: *Self, pos: usize) []DenseT { // TODO: support paging - if (self.sparse.capacity < pos or self.sparse.capacity == 0) { + if (self.sparse.capacity <= pos or self.sparse.capacity == 0) { const amount = pos + 1 - self.sparse.capacity; // expand and fill with maxInt as an identifier diff --git a/zig-ecs/src/signals/signal.zig b/zig-ecs/src/signals/signal.zig index f8b923f..c14bf8b 100644 --- a/zig-ecs/src/signals/signal.zig +++ b/zig-ecs/src/signals/signal.zig @@ -10,7 +10,7 @@ pub fn Signal(comptime Event: type) type { allocator: ?*std.mem.Allocator = null, pub fn init(allocator: *std.mem.Allocator) Self { - // we purposely do not store the allocator locally in this case! + // we purposely do not store the allocator locally in this case so we know not to destroy ourself in deint! return Self{ .calls = std.ArrayList(Delegate(Event)).init(allocator), }; From 61d882d1a8fe208f9200a79422d40f33d233c0cb Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 1 Jun 2020 22:25:27 -0700 Subject: [PATCH 006/146] add excludes to Views --- zig-ecs/examples/simple.zig | 2 +- zig-ecs/src/ecs/registry.zig | 26 ++++++++---- zig-ecs/src/ecs/view.zig | 77 +++++++++++++++++++++++++++++------- 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/zig-ecs/examples/simple.zig b/zig-ecs/examples/simple.zig index a67c325..9a559b1 100644 --- a/zig-ecs/examples/simple.zig +++ b/zig-ecs/examples/simple.zig @@ -19,7 +19,7 @@ pub fn main() !void { reg.add(e2, Position{ .x = 10, .y = 10 }); reg.add(e2, Velocity{ .x = 15, .y = 17 }); - var view = reg.view(.{ Velocity, Position }); + var view = reg.view(.{ Velocity, Position }, .{}); var iter = view.iterator(); while (iter.next()) |entity| { diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 96d8944..4de5f06 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -254,24 +254,34 @@ pub const Registry = struct { null; } - pub fn view(self: *Registry, comptime includes: var) ViewType(includes) { + 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))); + if (@typeInfo(@TypeOf(excludes)) != .Struct) + @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(excludes))); std.debug.assert(includes.len > 0); - if (includes.len == 1) + if (includes.len == 1 and excludes.len == 0) return BasicView(includes[0]).init(self.assure(includes[0])); - var arr: [includes.len]u32 = undefined; + var includes_arr: [includes.len]u32 = undefined; inline for (includes) |t, i| { _ = self.assure(t); - arr[i] = @as(u32, self.typemap.get(t)); + includes_arr[i] = @as(u32, self.typemap.get(t)); } - return BasicMultiView(includes.len).init(arr, self); + var excludes_arr: [excludes.len]u32 = undefined; + inline for (excludes) |t, i| { + _ = self.assure(t); + excludes_arr[i] = @as(u32, self.typemap.get(t)); + } + + return BasicMultiView(includes.len, excludes.len).init(includes_arr, excludes_arr, self); } - fn ViewType(comptime includes: var) type { - if (includes.len == 1) return BasicView(includes[0]); - return BasicMultiView(includes.len); + 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); } }; diff --git a/zig-ecs/src/ecs/view.zig b/zig-ecs/src/ecs/view.zig index e921556..bb21caa 100644 --- a/zig-ecs/src/ecs/view.zig +++ b/zig-ecs/src/ecs/view.zig @@ -5,7 +5,6 @@ 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. pub fn BasicView(comptime T: type) type { return struct { @@ -14,7 +13,7 @@ pub fn BasicView(comptime T: type) type { storage: *Storage(T), pub fn init(storage: *Storage(T)) Self { - return Self { + return Self{ .storage = storage, }; } @@ -37,14 +36,19 @@ pub fn BasicView(comptime T: type) type { pub fn get(self: Self, entity: Entity) *T { return self.storage.get(entity); } + + pub fn getConst(self: *Self, comptime T: type, entity: Entity) T { + return self.storage.getConst(entity); + } }; } -pub fn BasicMultiView(comptime n: usize) type { +pub fn BasicMultiView(comptime n_includes: usize, comptime n_excludes: usize) type { return struct { const Self = @This(); - type_ids: [n]u32, + type_ids: [n_includes]u32, + exclude_type_ids: [n_excludes]u32, registry: *Registry, pub const Iterator = struct { @@ -69,10 +73,19 @@ pub fn BasicMultiView(comptime n: usize) type { // entity must be in all other Storages for (it.view.type_ids) |tid| { const ptr = it.view.registry.components.getValue(@intCast(u8, tid)).?; - if (!@intToPtr(*Storage(u8), ptr).contains(entity)) { + if (!@intToPtr(*Storage(u1), ptr).contains(entity)) { break :blk; } } + + // entity must not be in all other excluded Storages + for (it.view.exclude_type_ids) |tid| { + const ptr = it.view.registry.components.getValue(@intCast(u8, tid)).?; + if (@intToPtr(*Storage(u1), ptr).contains(entity)) { + break :blk; + } + } + it.index += 1; return entity; } @@ -86,9 +99,10 @@ pub fn BasicMultiView(comptime n: usize) type { } }; - pub fn init(type_ids: [n]u32, registry: *Registry) Self { + pub fn init(type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32, registry: *Registry) Self { return Self{ .type_ids = type_ids, + .exclude_type_ids = exclude_type_ids, .registry = registry, }; } @@ -97,8 +111,6 @@ pub fn BasicMultiView(comptime n: usize) type { const type_id = self.registry.typemap.get(T); const ptr = self.registry.components.getValue(type_id).?; const store = @intToPtr(*Storage(T), ptr); - - std.debug.assert(store.contains(entity)); return store.get(entity); } @@ -106,14 +118,12 @@ pub fn BasicMultiView(comptime n: usize) type { const type_id = self.registry.typemap.get(T); const ptr = self.registry.components.getValue(type_id).?; const store = @intToPtr(*Storage(T), ptr); - - std.debug.assert(store.contains(entity)); return store.getConst(entity); } fn sort(self: *Self) void { // get our component counts in an array so we can sort the type_ids based on how many entities are in each - var sub_items: [n]usize = undefined; + var sub_items: [n_includes]usize = undefined; for (self.type_ids) |tid, i| { const ptr = self.registry.components.getValue(@intCast(u8, tid)).?; const store = @intToPtr(*Storage(u8), ptr); @@ -130,7 +140,6 @@ pub fn BasicMultiView(comptime n: usize) type { }; } - test "single basic view" { var store = Storage(f32).init(std.testing.allocator); defer store.deinit(); @@ -194,8 +203,8 @@ test "basic multi view" { reg.add(e0, @as(u32, 0)); reg.add(e2, @as(u32, 2)); - var single_view = reg.view(.{u32}); - var view = reg.view(.{ i32, u32 }); + var single_view = reg.view(.{u32}, .{}); + var view = reg.view(.{ i32, u32 }, .{}); var iterated_entities: usize = 0; var iter = view.iterator(); @@ -214,4 +223,42 @@ test "basic multi view" { } std.testing.expectEqual(iterated_entities, 1); -} \ No newline at end of file +} + +test "basic multi view with excludes" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var e0 = reg.create(); + var e1 = reg.create(); + var e2 = reg.create(); + + reg.add(e0, @as(i32, -0)); + reg.add(e1, @as(i32, -1)); + reg.add(e2, @as(i32, -2)); + + reg.add(e0, @as(u32, 0)); + reg.add(e2, @as(u32, 2)); + + reg.add(e2, @as(u8, 255)); + + var view = reg.view(.{ i32, u32 }, .{u8}); + + var iterated_entities: usize = 0; + var iter = view.iterator(); + while (iter.next()) |entity| { + iterated_entities += 1; + } + + std.testing.expectEqual(iterated_entities, 1); + iterated_entities = 0; + + reg.remove(u8, e2); + + iter.reset(); + while (iter.next()) |entity| { + iterated_entities += 1; + } + + std.testing.expectEqual(iterated_entities, 2); +} From b28000222348c87ad515deec1d366e154548e5ee Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 2 Jun 2020 19:55:19 -0700 Subject: [PATCH 007/146] bug fixes and moving stuff around --- zig-ecs/src/ecs.zig | 4 +- zig-ecs/src/ecs/component_storage.zig | 4 +- zig-ecs/src/ecs/sparse_set.zig | 29 ++++++---- zig-ecs/src/tests.zig | 3 +- zig-ecs/tests/registry_test.zig | 82 +++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 15 deletions(-) diff --git a/zig-ecs/src/ecs.zig b/zig-ecs/src/ecs.zig index 24c2edb..62c55e1 100644 --- a/zig-ecs/src/ecs.zig +++ b/zig-ecs/src/ecs.zig @@ -3,8 +3,8 @@ pub const EntityTraitsType = @import("ecs/entity.zig").EntityTraitsType; pub const Entity = @import("ecs/registry.zig").Entity; pub const Registry = @import("ecs/registry.zig").Registry; -pub const BasicView = @import("ecs/view.zig").BasicView; -pub const BasicMultiView = @import("ecs/view.zig").BasicMultiView; +pub const BasicView = @import("ecs/views.zig").BasicView; +pub const BasicMultiView = @import("ecs/views.zig").BasicMultiView; // signals pub const Signal = @import("signals/signal.zig").Signal; diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 09d37a0..217a9a4 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -35,7 +35,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D pub fn init(allocator: *std.mem.Allocator) Self { var store = Self{ - .set = SparseSet(EntityT, DenseT).init(allocator), + .set = SparseSet(EntityT, DenseT).initPtr(allocator), .instances = undefined, .safe_deinit = struct { fn deinit(self: *Self) void { @@ -57,7 +57,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D pub fn initPtr(allocator: *std.mem.Allocator) *Self { var store = allocator.create(Self) catch unreachable; - store.set = SparseSet(EntityT, DenseT).init(allocator); + store.set = SparseSet(EntityT, DenseT).initPtr(allocator); if (!is_empty_struct) store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); store.allocator = allocator; diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index fcf2967..2147fda 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -58,23 +58,32 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { sparse: std.ArrayList(DenseT), dense: std.ArrayList(SparseT), entity_mask: SparseT, - allocator: *std.mem.Allocator, + allocator: ?*std.mem.Allocator, - pub fn init(allocator: *std.mem.Allocator) *Self { + pub fn initPtr(allocator: *std.mem.Allocator) *Self { var set = allocator.create(Self) catch unreachable; - set.sparse = std.ArrayList(DenseT).init(allocator); set.dense = std.ArrayList(SparseT).init(allocator); set.entity_mask = std.math.maxInt(SparseT); set.allocator = allocator; - return set; } + pub fn init(allocator: *std.mem.Allocator) Self { + return Self{ + .sparse = std.ArrayList(DenseT).init(allocator), + .dense = std.ArrayList(SparseT).init(allocator), + .entity_mask = std.math.maxInt(SparseT), + .allocator = null, + }; + } + pub fn deinit(self: *Self) void { self.dense.deinit(); self.sparse.deinit(); - self.allocator.destroy(self); + + if (self.allocator) |allocator| + allocator.destroy(self); } fn page(self: Self, sparse: SparseT) usize { @@ -106,7 +115,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { /// Increases the capacity of a sparse set. pub fn reserve(self: *Self, cap: usize) void { - self.dense.resize(cap); + self.dense.resize(cap) catch unreachable; } /// Returns the number of elements that a sparse set has currently allocated space for @@ -197,7 +206,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { } test "add/remove/clear" { - var set = SparseSet(u32, u8).init(std.testing.allocator); + var set = SparseSet(u32, u8).initPtr(std.testing.allocator); defer set.deinit(); set.add(4); @@ -214,7 +223,7 @@ test "add/remove/clear" { } test "grow" { - var set = SparseSet(u32, u8).init(std.testing.allocator); + var set = SparseSet(u32, u8).initPtr(std.testing.allocator); defer set.deinit(); var i = @as(usize, std.math.maxInt(u8)); @@ -226,7 +235,7 @@ test "grow" { } test "swap" { - var set = SparseSet(u32, u8).init(std.testing.allocator); + var set = SparseSet(u32, u8).initPtr(std.testing.allocator); defer set.deinit(); set.add(4); @@ -240,7 +249,7 @@ test "swap" { } test "data() synced" { - var set = SparseSet(u32, u8).init(std.testing.allocator); + var set = SparseSet(u32, u8).initPtr(std.testing.allocator); defer set.deinit(); set.add(0); diff --git a/zig-ecs/src/tests.zig b/zig-ecs/src/tests.zig index 391c5f8..8cd5be4 100644 --- a/zig-ecs/src/tests.zig +++ b/zig-ecs/src/tests.zig @@ -7,7 +7,8 @@ comptime { _ = @import("ecs/handles.zig"); _ = @import("ecs/sparse_set.zig"); _ = @import("ecs/type_map.zig"); - _ = @import("ecs/view.zig"); + _ = @import("ecs/views.zig"); + _ = @import("ecs/groups.zig"); // signals _ = @import("signals/delegate.zig"); diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index 9a118f2..2488772 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -26,3 +26,85 @@ test "Registry" { reg.remove(Empty, e1); std.testing.expect(!reg.has(Empty, e1)); } + +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); +} + +// this test should fail +test "context not pointer" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var pos = Position{ .x = 5, .y = 5 }; + // reg.setContext(pos); +} + +test "component context get/set/unset" { + const SomeType = struct { dummy: u1 }; + + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var ctx = reg.getContext(SomeType); + std.testing.expectEqual(ctx, null); + + 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); +} + +test "destroy" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + 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) }); + } + + 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)); + } +} + +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)); +} From 2ed3be236bbc843bccc294071879971b6318e71b Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 2 Jun 2020 19:55:24 -0700 Subject: [PATCH 008/146] start of groups --- zig-ecs/src/ecs/groups.zig | 37 +++++ zig-ecs/src/ecs/registry.zig | 171 +++++++++++++----------- zig-ecs/src/ecs/{view.zig => views.zig} | 16 +-- 3 files changed, 138 insertions(+), 86 deletions(-) create mode 100644 zig-ecs/src/ecs/groups.zig rename zig-ecs/src/ecs/{view.zig => views.zig} (92%) 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 { From db332e82d9e120d524c89e5abf0cb3b5c5f0efc4 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 3 Jun 2020 15:01:16 -0700 Subject: [PATCH 009/146] cleanup --- zig-ecs/src/ecs/sparse_set.zig | 63 +++----------------------------- zig-ecs/src/ecs/views.zig | 2 +- zig-ecs/src/signals/delegate.zig | 2 +- 3 files changed, 7 insertions(+), 60 deletions(-) diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 2147fda..70a7f33 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -1,55 +1,6 @@ const std = @import("std"); const warn = std.debug.warn; -fn printSet(set: var) void { - warn("-------- sparse to dense (len: {}, cap: {}) ------\n", .{ set.len(), set.capacity() }); - var dense = set.toDenseSlice(); - for (dense) |item| { - warn("[{}] ", .{item}); - } - - warn("\n", .{}); - - var sparse = set.data(); - for (sparse) |item| { - warn("[{}] ", .{item}); - } - warn("\n", .{}); -} - -pub fn main() !void { - var set = SparseSet(u32, u8).init(); - defer set.deinit(); - - warn("add 0, 3\n", .{}); - set.add(1); - printSet(set); - set.add(3); - printSet(set); - warn("contains 0: {}, index: {}\n", .{ set.contains(1), set.index(1) }); - warn("contains 3: {}, index: {}\n", .{ set.contains(3), set.index(3) }); - warn("contains 2: {}\n", .{set.contains(2)}); - - printSet(set); - set.swap(1, 3); - warn("----- swap! ----\n", .{}); - warn("contains 0: {}, index: {}\n", .{ set.contains(1), set.index(1) }); - warn("contains 3: {}, index: {}\n", .{ set.contains(3), set.index(3) }); - - printSet(set); - - warn("remove 1\n", .{}); - set.remove(1); - warn("contains 3: {}, index: {}\n", .{ set.contains(3), set.index(3) }); - printSet(set); - - warn("clear\n", .{}); - set.clear(); - printSet(set); - - warn("dense cap: {}\n", .{set.dense.capacity}); -} - // TODO: fix entity_mask. it should come from EntityTraitsDefinition. pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { return struct { @@ -113,18 +64,18 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { return sparse & self.entity_mask; } - /// Increases the capacity of a sparse set. + /// Increases the capacity of a sparse sets index array pub fn reserve(self: *Self, cap: usize) void { - self.dense.resize(cap) catch unreachable; + self.sparse.resize(cap) catch unreachable; } - /// Returns the number of elements that a sparse set has currently allocated space for + /// Returns the number of dense elements that a sparse set has currently allocated space for pub fn capacity(self: *Self) usize { return self.dense.capacity; } - /// Returns the number of elements in a sparse set - pub fn len(self: *Self) usize { + /// Returns the number of dense elements in a sparse set + pub fn len(self: Self) usize { return self.dense.items.len; } @@ -198,10 +149,6 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { self.sparse.items.len = 0; self.dense.items.len = 0; } - - pub fn toDenseSlice(self: Self) []DenseT { - return self.sparse.items; - } }; } diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index ed623da..0522549 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -43,7 +43,7 @@ pub fn BasicView(comptime T: type) type { }; } -pub fn BasicMultiView(comptime n_includes: usize, comptime n_excludes: usize) type { +pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { return struct { const Self = @This(); diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index a0724b5..30a74b0 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -13,8 +13,8 @@ pub fn Delegate(comptime Event: type) type { /// sets a bound function as the Delegate callback pub fn initBound(ctx: var, comptime fn_name: []const u8) Self { - std.debug.assert(@ptrToInt(ctx) != 0); std.debug.assert(@typeInfo(@TypeOf(ctx)) == .Pointer); + std.debug.assert(@ptrToInt(ctx) != 0); const T = @TypeOf(ctx); return Self{ From 3dc2e3346123785a8856a162f34616efddd293de Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 3 Jun 2020 15:01:30 -0700 Subject: [PATCH 010/146] BasicGroup mostly working --- zig-ecs/src/ecs/groups.zig | 79 +++++++++++++++++++--- zig-ecs/src/ecs/registry.zig | 126 ++++++++++++++++++++++++----------- 2 files changed, 157 insertions(+), 48 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index b703402..25e42c3 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -3,35 +3,96 @@ const utils = @import("utils.zig"); const Registry = @import("registry.zig").Registry; const Storage = @import("registry.zig").Storage; +const SparseSet = @import("sparse_set.zig").SparseSet; const Entity = @import("registry.zig").Entity; -pub fn NonOwningGroup(comptime n_includes: usize, comptime n_excludes: usize) type { +/// BasicGroups do not own any components +pub fn BasicGroup(comptime n_includes: usize, comptime n_excludes: usize) type { return struct { const Self = @This(); + entity_set: *SparseSet(Entity, u16), 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 { + pub const Iterator = struct { + group: *Self, + index: usize = 0, + entities: *const []Entity, + + pub fn init(group: *Self) Iterator { + return .{ + .group = group, + .entities = group.entity_set.data(), + }; + } + + 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(entity_set: *SparseSet(Entity, u16), registry: *Registry, type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32) Self { return Self{ + .entity_set = entity_set, .registry = registry, .type_ids = type_ids, .exclude_type_ids = exclude_type_ids, }; } + + pub fn len(self: Self) usize { + return self.entity_set.len(); + } + + /// Direct access to the array of entities + pub fn data(self: Self) *const []Entity { + return self.entity_set.data(); + } + + pub fn get(self: *Self, comptime T: type, entity: Entity) *T { + return self.registry.assure(T).get(entity); + } + + pub fn getConst(self: *Self, comptime T: type, entity: Entity) T { + return self.registry.assure(T).getConst(entity); + } + + pub fn iterator(self: *Self) Iterator { + return Iterator.init(self); + } }; } -test "group creation" { +test "BasicGroup creation" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); + var group = reg.group(.{}, .{ i32, u32 }, .{}); + std.testing.expectEqual(group.len(), 0); + var e0 = reg.create(); - reg.add(e0, @as(i32, -0)); - reg.add(e0, @as(u32, 0)); + reg.add(e0, @as(i32, 44)); + reg.add(e0, @as(u32, 55)); + + std.debug.assert(group.len() == 1); - var group = reg.group(.{}, .{i32}, .{}); - var group2 = reg.group(.{}, .{u32}, .{}); - var group23 = reg.group(.{}, .{i32}, .{}); -} \ No newline at end of file + var iterated_entities: usize = 0; + var iter = group.iterator(); + while (iter.next()) |entity| { + iterated_entities += 1; + } + std.testing.expectEqual(iterated_entities, 1); + + reg.remove(i32, e0); + std.debug.assert(group.len() == 0); +} diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 32d7fb9..89c7c23 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -6,6 +6,7 @@ const Handles = @import("handles.zig").Handles; const SparseSet = @import("sparse_set.zig").SparseSet; const TypeMap = @import("type_map.zig").TypeMap; const ComponentStorage = @import("component_storage.zig").ComponentStorage; +const Sink = @import("../signals/sink.zig").Sink; // allow overriding EntityTraits by setting in root via: EntityTraits = EntityTraitsType(.medium); const root = @import("root"); @@ -16,8 +17,8 @@ const EntityHandles = Handles(entity_traits.entity_type, entity_traits.index_typ pub const Entity = entity_traits.entity_type; pub const BasicView = @import("views.zig").BasicView; -pub const BasicMultiView = @import("views.zig").BasicMultiView; -pub const NonOwningGroup = @import("groups.zig").NonOwningGroup; +pub const MultiView = @import("views.zig").MultiView; +pub const BasicGroup = @import("groups.zig").BasicGroup; /// 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 { @@ -32,28 +33,29 @@ pub const Registry = struct { handles: EntityHandles, components: std.AutoHashMap(u8, usize), contexts: std.AutoHashMap(u8, usize), - groups: std.ArrayList(GroupData), + groups: std.ArrayList(*GroupData), allocator: *std.mem.Allocator, const GroupData = struct { hash: u32, - entity_set: SparseSet(Entity, u16) = undefined, + entity_set: SparseSet(Entity, u16), // 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, - pub fn init(allocator: *std.mem.Allocator, registry: *Registry, hash: u32, owned: []u32, include: []u32, exclude: []u32) GroupData { + 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); 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, - }; + var group_data = allocator.create(GroupData) catch unreachable; + group_data.hash = hash; + 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; return group_data; } @@ -63,10 +65,46 @@ pub const Registry = struct { allocator.free(self.owned); allocator.free(self.include); allocator.free(self.exclude); + allocator.destroy(self); + } + + fn maybeValidIf(self: *GroupData, entity: Entity) void { + const isValid: bool = blk: { + for (self.owned) |tid| { + const ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + if (!@intToPtr(*Storage(u1), ptr).contains(entity)) + break :blk false; + } + + for (self.include) |tid| { + const ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + if (!@intToPtr(*Storage(u1), ptr).contains(entity)) + break :blk false; + } + + for (self.exclude) |tid| { + const ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + if (@intToPtr(*Storage(u1), ptr).contains(entity)) + break :blk false; + } + break :blk true; + }; + + if (self.owned.len == 0) { + if (isValid and !self.entity_set.contains(entity)) + self.entity_set.add(entity); + } else { + std.debug.assert(self.owned.len >= 0); + } } - 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); + fn discardIf(self: *GroupData, entity: Entity) void { + if (self.owned.len == 0) { + if (self.entity_set.contains(entity)) + self.entity_set.remove(entity); + } else { + std.debug.assert(self.owned.len == 0); + } } }; @@ -76,7 +114,7 @@ pub const Registry = struct { .handles = EntityHandles.init(allocator), .components = std.AutoHashMap(u8, usize).init(allocator), .contexts = std.AutoHashMap(u8, usize).init(allocator), - .groups = std.ArrayList(GroupData).init(allocator), + .groups = std.ArrayList(*GroupData).init(allocator), .allocator = allocator, }; } @@ -89,7 +127,7 @@ pub const Registry = struct { storage.deinit(); } - for (self.groups.items) |*grp| { + for (self.groups.items) |grp| { grp.deinit(self.allocator); } @@ -253,17 +291,17 @@ pub const Registry = struct { } /// Returns a Sink object for the given component to add/remove listeners with - pub fn onConstruct(self: *Self, comptime T: type) Sink(Entity) { + pub fn onConstruct(self: *Registry, comptime T: type) Sink(Entity) { return self.assure(T).onConstruct(); } /// Returns a Sink object for the given component to add/remove listeners with - pub fn onUpdate(self: *Self, comptime T: type) Sink(Entity) { + pub fn onUpdate(self: *Registry, comptime T: type) Sink(Entity) { return self.assure(T).onUpdate(); } /// Returns a Sink object for the given component to add/remove listeners with - pub fn onDestruct(self: *Self, comptime T: type) Sink(Entity) { + pub fn onDestruct(self: *Registry, comptime T: type) Sink(Entity) { return self.assure(T).onDestruct(); } @@ -319,13 +357,13 @@ pub const Registry = struct { excludes_arr[i] = @as(u32, self.typemap.get(t)); } - return BasicMultiView(includes.len, excludes.len).init(self, includes_arr, excludes_arr); + return MultiView(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); + return MultiView(includes.len, excludes.len); } pub fn group(self: *Registry, comptime owned: var, comptime includes: var, comptime excludes: var) GroupType(owned, includes, excludes) { @@ -335,8 +373,8 @@ pub const Registry = 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); + std.debug.assert(owned.len + includes.len > 0); + std.debug.assert(owned.len + includes.len + excludes.len > 1); var owned_arr: [owned.len]u32 = undefined; inline for (owned) |t, i| { @@ -357,35 +395,45 @@ pub const Registry = struct { } // create a unique hash to identify the group - var group_data: ?*GroupData = null; + var maybe_group_data: ?*GroupData = null; comptime const hash = owned.len + (31 * includes.len) + (31 * 31 * excludes.len); - for (self.groups.items) |*grp| { - if (grp.hash == hash and grp.hasSameConstraints(owned_arr[0..], includes_arr[0..], excludes_arr[0..])) { - group_data = grp; + for (self.groups.items) |grp| { + if (grp.hash == hash and std.mem.eql(u32, grp.owned, owned_arr[0..]) and std.mem.eql(u32, grp.include, includes_arr[0..]) and std.mem.eql(u32, grp.exclude, excludes_arr[0..])) { + maybe_group_data = grp; break; } } - - // non-owning groups - if (owned.len == 0) { - if (group_data != null) { - return NonOwningGroup(includes.len, excludes.len).init(self, includes_arr, excludes_arr); + // do we already have the GroupData? + if (maybe_group_data) |group_data| { + // non-owning groups + 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"); } - - 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); } - @compileLog("owned groups not implemented"); + // we need to create a new GroupData + var new_group_data = GroupData.initPtr(self.allocator, self, hash, &[_]u32{}, includes_arr[0..], excludes_arr[0..]); + + // wire up our listeners + inline for (owned) |t| self.onConstruct(t).connectBound(new_group_data, "maybeValidIf"); + inline for (includes) |t| self.onConstruct(t).connectBound(new_group_data, "maybeValidIf"); + inline for (excludes) |t| self.onDestruct(t).connectBound(new_group_data, "maybeValidIf"); + + inline for (owned) |t| self.onDestruct(t).connectBound(new_group_data, "discardIf"); + inline for (includes) |t| self.onDestruct(t).connectBound(new_group_data, "discardIf"); + inline for (excludes) |t| self.onConstruct(t).connectBound(new_group_data, "discardIf"); + + self.groups.append(new_group_data) catch unreachable; + return BasicGroup(includes.len, excludes.len).init(&new_group_data.entity_set, self, 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 NonOwningGroup(includes.len, excludes.len); + if (owned.len == 0) return BasicGroup(includes.len, excludes.len); unreachable; } }; From 8fd20e3b426e44330f09e13833e40e45998f199f Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 3 Jun 2020 15:09:25 -0700 Subject: [PATCH 011/146] tests --- zig-ecs/src/ecs/groups.zig | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 25e42c3..2384c3b 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -96,3 +96,26 @@ test "BasicGroup creation" { reg.remove(i32, e0); std.debug.assert(group.len() == 0); } + +test "BasicGroup exclides" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var group = reg.group(.{}, .{ i32 }, .{ u32 }); + std.testing.expectEqual(group.len(), 0); + + var e0 = reg.create(); + reg.add(e0, @as(i32, 44)); + + std.debug.assert(group.len() == 1); + + var iterated_entities: usize = 0; + var iter = group.iterator(); + while (iter.next()) |entity| { + iterated_entities += 1; + } + std.testing.expectEqual(iterated_entities, 1); + + reg.add(e0, @as(u32, 55)); + std.debug.assert(group.len() == 0); +} \ No newline at end of file From 4cd3d37657792c722a0437eea81c57b365404188 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 3 Jun 2020 15:25:27 -0700 Subject: [PATCH 012/146] basic group ready kind of --- zig-ecs/src/ecs/groups.zig | 14 +++++++++++++- zig-ecs/src/ecs/registry.zig | 13 ++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 2384c3b..02428d4 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -97,7 +97,7 @@ test "BasicGroup creation" { std.debug.assert(group.len() == 0); } -test "BasicGroup exclides" { +test "BasicGroup excludes" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); @@ -118,4 +118,16 @@ test "BasicGroup exclides" { reg.add(e0, @as(u32, 55)); std.debug.assert(group.len() == 0); +} + +test "BasicGroup create late" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var e0 = reg.create(); + reg.add(e0, @as(i32, 44)); + reg.add(e0, @as(u32, 55)); + + var group = reg.group(.{}, .{ i32, u32 }, .{}); + std.testing.expectEqual(group.len(), 1); } \ No newline at end of file diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 89c7c23..03cf99c 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -417,6 +417,7 @@ pub const Registry = struct { // we need to create a new GroupData var new_group_data = GroupData.initPtr(self.allocator, self, hash, &[_]u32{}, includes_arr[0..], excludes_arr[0..]); + self.groups.append(new_group_data) catch unreachable; // wire up our listeners inline for (owned) |t| self.onConstruct(t).connectBound(new_group_data, "maybeValidIf"); @@ -427,7 +428,17 @@ pub const Registry = struct { inline for (includes) |t| self.onDestruct(t).connectBound(new_group_data, "discardIf"); inline for (excludes) |t| self.onConstruct(t).connectBound(new_group_data, "discardIf"); - self.groups.append(new_group_data) catch unreachable; + // pre-fill the GroupData with any existing entitites that match + if (owned.len == 0) { + var tmp_view = self.view(owned ++ includes, excludes); + var view_iter = tmp_view.iterator(); + while (view_iter.next()) |entity| { + 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); } From 1d1c761c1514e0bffecff5e71196d77346f501e3 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 3 Jun 2020 20:13:16 -0700 Subject: [PATCH 013/146] sort and respect --- zig-ecs/src/ecs/registry.zig | 1 + zig-ecs/src/ecs/sparse_set.zig | 92 ++++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 03cf99c..827b9a3 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -36,6 +36,7 @@ pub const Registry = struct { groups: std.ArrayList(*GroupData), allocator: *std.mem.Allocator, + /// 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. diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 70a7f33..76c01ab 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -43,6 +43,12 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { return sparse & self.entity_mask; } + fn offset(self: Self, sparse: SparseT) usize { + // TODO: support paging + // return entt & (sparse_per_page - 1) + return sparse & self.entity_mask; + } + fn assure(self: *Self, pos: usize) []DenseT { // TODO: support paging if (self.sparse.capacity <= pos or self.sparse.capacity == 0) { @@ -58,12 +64,6 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { return self.sparse.items; } - fn offset(self: Self, sparse: SparseT) usize { - // TODO: support paging - // return entt & (sparse_per_page - 1) - return sparse & self.entity_mask; - } - /// Increases the capacity of a sparse sets index array pub fn reserve(self: *Self, cap: usize) void { self.sparse.resize(cap) catch unreachable; @@ -126,7 +126,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { _ = self.dense.pop(); } - /// Swaps two entities in the internal packed array + /// Swaps two entities in the internal packed and sparse arrays pub fn swap(self: *Self, sparse_l: SparseT, sparse_r: SparseT) void { var from = &self.sparse.items[sparse_l]; var to = &self.sparse.items[sparse_r]; @@ -136,13 +136,28 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { } /// Sort elements according to the given comparison function - pub fn sort(self: *Self) void { - unreachable; + pub fn sort(self: *Self, sortFn: fn (SparseT, SparseT) bool) void { + std.sort.insertionSort(SparseT, self.dense.items, sortFn); + + var i = @as(usize, 0); + for (self.dense.items) |sparse| { + // self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(DenseT, sparse); + self.sparse.items[self.page(sparse)] = @intCast(DenseT, sparse); + } } - /// Sort entities according to their order in another sparse set + /// Sort entities according to their order in another sparse set. Other is the master in this case. pub fn respect(self: *Self, other: *Self) void { - unreachable; + var pos = @as(DenseT, 0); + var i = @as(DenseT, 0); + while (i < other.dense.items.len) : (i += 1) { + if (self.contains(other.dense.items[i])) { + if (other.dense.items[i] != self.dense.items[pos]) { + self.swap(self.dense.items[pos], other.dense.items[i]); + } + pos += 1; + } + } } pub fn clear(self: *Self) void { @@ -152,6 +167,19 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { }; } +fn printSet(set: *SparseSet(u32, u8)) void { + std.debug.warn("\nsparse -----\n", .{}); + for (set.sparse.items) |sparse| { + std.debug.warn("{}\t", .{sparse}); + } + + std.debug.warn("\ndense -----\n", .{}); + for (set.dense.items) |dense| { + std.debug.warn("{}\t", .{dense}); + } + std.debug.warn("\n\n", .{}); +} + test "add/remove/clear" { var set = SparseSet(u32, u8).initPtr(std.testing.allocator); defer set.deinit(); @@ -212,3 +240,45 @@ test "data() synced" { set.remove(1); std.testing.expectEqual(set.len(), data.len); } + +test "respect" { + var set1 = SparseSet(u32, u8).initPtr(std.testing.allocator); + defer set1.deinit(); + + var set2 = SparseSet(u32, u8).initPtr(std.testing.allocator); + defer set2.deinit(); + + set1.add(3); + set1.add(4); + set1.add(5); + set1.add(6); + set1.add(7); + + set2.add(8); + set2.add(6); + set2.add(4); + + set1.respect(set2); + + std.testing.expectEqual(set1.dense.items[0], set2.dense.items[1]); + std.testing.expectEqual(set1.dense.items[1], set2.dense.items[2]); +} + +test "respect" { + var set = SparseSet(u32, u8).initPtr(std.testing.allocator); + defer set.deinit(); + + set.add(5); + set.add(2); + set.add(4); + set.add(1); + set.add(3); + + set.sort(std.sort.desc(u32)); + + for (set.dense.items) |item, i| { + if (i < set.dense.items.len - 1) { + std.debug.assert(item > set.dense.items[i + 1]); + } + } +} \ No newline at end of file From df29819b115dcef2e3868393b386935ee01e16fc Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 3 Jun 2020 23:33:59 -0700 Subject: [PATCH 014/146] start of owning groups --- zig-ecs/src/ecs/component_storage.zig | 22 +++++++++-- zig-ecs/src/ecs/groups.zig | 53 +++++++++++++++++++++++++ zig-ecs/src/ecs/registry.zig | 56 ++++++++++++++++++++++----- 3 files changed, 118 insertions(+), 13 deletions(-) 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); } }; From b32e907fd41ac50e6f1ea114c19c70a462161f7b Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 4 Jun 2020 13:37:51 -0700 Subject: [PATCH 015/146] TypeMap gone --- zig-ecs/src/ecs/registry.zig | 68 ++++++++++++------------------ zig-ecs/src/ecs/type_map.zig | 53 ----------------------- zig-ecs/src/ecs/utils.zig | 13 ++++-- zig-ecs/src/ecs/views.zig | 8 ++-- zig-ecs/src/signals/dispatcher.zig | 26 +++++------- zig-ecs/src/tests.zig | 1 - 6 files changed, 51 insertions(+), 118 deletions(-) delete mode 100644 zig-ecs/src/ecs/type_map.zig diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 7f81ad9..5164162 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -4,7 +4,6 @@ const utils = @import("utils.zig"); const Handles = @import("handles.zig").Handles; const SparseSet = @import("sparse_set.zig").SparseSet; -const TypeMap = @import("type_map.zig").TypeMap; const ComponentStorage = @import("component_storage.zig").ComponentStorage; const Sink = @import("../signals/sink.zig").Sink; @@ -30,10 +29,9 @@ pub fn Storage(comptime CompT: type) type { /// no errors to keep the API clean and because if a component array cant be allocated you've got bigger problems. /// Stores a maximum of u8 (256) component Storage(T). pub const Registry = struct { - typemap: TypeMap, handles: EntityHandles, - components: std.AutoHashMap(u8, usize), - contexts: std.AutoHashMap(u8, usize), + components: std.AutoHashMap(u32, usize), + contexts: std.AutoHashMap(u32, usize), groups: std.ArrayList(*GroupData), allocator: *std.mem.Allocator, @@ -80,19 +78,19 @@ pub const Registry = struct { fn maybeValidIf(self: *GroupData, entity: Entity) void { const isValid: bool = blk: { for (self.owned) |tid| { - const ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + const ptr = self.registry.components.getValue(tid).?; if (!@intToPtr(*Storage(u1), ptr).contains(entity)) break :blk false; } for (self.include) |tid| { - const ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + const ptr = self.registry.components.getValue(tid).?; if (!@intToPtr(*Storage(u1), ptr).contains(entity)) break :blk false; } for (self.exclude) |tid| { - const ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + const ptr = self.registry.components.getValue(tid).?; if (@intToPtr(*Storage(u1), ptr).contains(entity)) break :blk false; } @@ -104,10 +102,10 @@ pub const Registry = struct { self.entity_set.add(entity); } else { if (isValid) { - const ptr = self.registry.components.getValue(@intCast(u8, self.owned[0])).?; + const ptr = self.registry.components.getValue(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)).?; + const store_ptr = self.registry.components.getValue(tid).?; var store = @intToPtr(*Storage(u1), store_ptr); store.swap(store.data().*[self.current], entity); } @@ -123,14 +121,13 @@ pub const Registry = struct { if (self.entity_set.contains(entity)) self.entity_set.remove(entity); } else { - const ptr = self.registry.components.getValue(@intCast(u8, self.owned[0])).?; + const ptr = self.registry.components.getValue(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)).?; + const store_ptr = self.registry.components.getValue(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); } } @@ -140,10 +137,9 @@ pub const Registry = struct { 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), + .components = std.AutoHashMap(u32, usize).init(allocator), + .contexts = std.AutoHashMap(u32, usize).init(allocator), .groups = std.ArrayList(*GroupData).init(allocator), .allocator = allocator, }; @@ -164,21 +160,19 @@ pub const Registry = struct { self.components.deinit(); self.contexts.deinit(); self.groups.deinit(); - self.typemap.deinit(); self.handles.deinit(); } pub fn assure(self: *Registry, comptime T: type) *Storage(T) { - var type_id: u8 = undefined; - if (!self.typemap.getOrPut(T, &type_id)) { - var comp_set = Storage(T).initPtr(self.allocator); - var comp_set_ptr = @ptrToInt(comp_set); - _ = self.components.put(type_id, comp_set_ptr) catch unreachable; - return comp_set; + var type_id = utils.typeId(T); + if (self.components.get(type_id)) |kv| { + return @intToPtr(*Storage(T), kv.value); } - const ptr = self.components.getValue(type_id).?; - return @intToPtr(*Storage(T), ptr); + var comp_set = Storage(T).initPtr(self.allocator); + var comp_set_ptr = @ptrToInt(comp_set); + _ = self.components.put(type_id, comp_set_ptr) catch unreachable; + return comp_set; } /// Prepares a pool for the given type if required @@ -339,27 +333,21 @@ pub const Registry = struct { pub fn setContext(self: *Registry, context: var) void { std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); - var type_id: u8 = undefined; - _ = self.typemap.getOrPut(@typeInfo(@TypeOf(context)).Pointer.child, &type_id); + var type_id = utils.typeId(@typeInfo(@TypeOf(context)).Pointer.child); _ = self.contexts.put(type_id, @ptrToInt(context)) catch unreachable; } /// Unsets a context variable if it exists pub fn unsetContext(self: *Registry, comptime T: type) void { std.debug.assert(@typeInfo(T) != .Pointer); - - var type_id: u8 = undefined; - _ = self.typemap.getOrPut(T, &type_id); - _ = self.contexts.put(type_id, 0) catch unreachable; + _ = self.contexts.put(utils.typeId(T), 0) catch unreachable; } /// Returns a pointer to an object in the context of the registry pub fn getContext(self: *Registry, comptime T: type) ?*T { std.debug.assert(@typeInfo(T) != .Pointer); - var type_id: u8 = undefined; - _ = self.typemap.getOrPut(T, &type_id); - return if (self.contexts.get(type_id)) |ptr| + return if (self.contexts.get(utils.typeId(T))) |ptr| return if (ptr.value > 0) @intToPtr(*T, ptr.value) else null else null; @@ -383,13 +371,13 @@ pub const Registry = struct { var includes_arr: [includes.len]u32 = undefined; inline for (includes) |t, i| { _ = self.assure(t); - includes_arr[i] = @as(u32, self.typemap.get(t)); + includes_arr[i] = utils.typeId(t); } var excludes_arr: [excludes.len]u32 = undefined; inline for (excludes) |t, i| { _ = self.assure(t); - excludes_arr[i] = @as(u32, self.typemap.get(t)); + excludes_arr[i] = utils.typeId(t); } return MultiView(includes.len, excludes.len).init(self, includes_arr, excludes_arr); @@ -414,19 +402,19 @@ pub const Registry = struct { var owned_arr: [owned.len]u32 = undefined; inline for (owned) |t, i| { _ = self.assure(t); - owned_arr[i] = @as(u32, self.typemap.get(t)); + owned_arr[i] = utils.typeId(t); } var includes_arr: [includes.len]u32 = undefined; inline for (includes) |t, i| { _ = self.assure(t); - includes_arr[i] = @as(u32, self.typemap.get(t)); + includes_arr[i] = utils.typeId(t); } var excludes_arr: [excludes.len]u32 = undefined; inline for (excludes) |t, i| { _ = self.assure(t); - excludes_arr[i] = @as(u32, self.typemap.get(t)); + excludes_arr[i] = utils.typeId(t); } // create a unique hash to identify the group @@ -470,9 +458,7 @@ pub const Registry = struct { while (view_iter.next()) |entity| { new_group_data.entity_set.add(entity); } - } else { - - } + } else {} if (owned.len == 0) { return BasicGroup(includes.len, excludes.len).init(&new_group_data.entity_set, self, includes_arr, excludes_arr); diff --git a/zig-ecs/src/ecs/type_map.zig b/zig-ecs/src/ecs/type_map.zig deleted file mode 100644 index c0c675b..0000000 --- a/zig-ecs/src/ecs/type_map.zig +++ /dev/null @@ -1,53 +0,0 @@ -const std = @import("std"); -const utils = @import("utils.zig"); - -pub const TypeMap = struct { - map: std.AutoHashMap(u32, u8), - counter: u8 = 0, - - pub fn init(allocator: *std.mem.Allocator) TypeMap { - return TypeMap{ - .map = std.AutoHashMap(u32, u8).init(allocator), - }; - } - - pub fn deinit(self: TypeMap) void { - self.map.deinit(); - } - - pub fn contains(self: TypeMap, comptime T: type) bool { - return self.map.contains(@truncate(u32, utils.typeHash(T))); - } - - /// gets the value for T. It MUST exist if you use this method to get it. - pub fn get(self: *TypeMap, comptime T: type) u8 { - return self.map.get(@truncate(u32, utils.typeHash(T))).?.value; - } - - /// gets the value for T if it exists. If it doesnt, it is registered and the value returned. - pub fn getOrPut(self: *TypeMap, comptime T: type, type_id: *u8) bool { - // TODO: is it safe to truncate to u32 here? - var res = self.map.getOrPut(@truncate(u32, utils.typeHash(T))) catch unreachable; - if (!res.found_existing) { - res.kv.value = self.counter; - self.counter += 1; - } - type_id.* = res.kv.value; - return res.found_existing; - } -}; - -test "TypeMap" { - var map = TypeMap.init(std.testing.allocator); - defer map.deinit(); - - var type_id: u8 = undefined; - _ = map.getOrPut(usize, &type_id); - std.testing.expectEqual(@as(u8, 0), type_id); - - _ = map.getOrPut(f32, &type_id); - std.testing.expectEqual(@as(u8, 1), type_id); - - _ = map.getOrPut(usize, &type_id); - std.testing.expectEqual(@as(u8, 0), type_id); -} diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 2269f3c..2814cf8 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -1,3 +1,4 @@ +const std = @import("std"); /// 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 { @@ -16,12 +17,16 @@ pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T } /// comptime string hashing for the type names -pub fn typeHash(comptime T: type) comptime_int { - return stringHash(@typeName(T)); +pub fn typeId(comptime T: type) u32 { + return hashString(@typeName(T)); } -/// comptime string hashing, djb2 by Dan Bernstein -pub fn stringHash(comptime str: []const u8) comptime_int { +pub fn hashString(comptime str: []const u8) u32 { + return @truncate(u32, std.hash.Wyhash.hash(0, str)); +} + +/// comptime string hashing, djb2 by Dan Bernstein. Fails on large strings. +pub fn hashStringDjb2(comptime str: []const u8) comptime_int { var hash: comptime_int = 5381; for (str) |c| { hash = ((hash << 5) + hash) + @intCast(comptime_int, c); diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index 0522549..481112a 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -57,7 +57,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { entities: *const []Entity, pub fn init(view: *Self) Iterator { - const ptr = view.registry.components.getValue(@intCast(u8, view.type_ids[0])).?; + const ptr = view.registry.components.getValue(view.type_ids[0]).?; return .{ .view = view, .entities = @intToPtr(*Storage(u8), ptr).data(), @@ -72,7 +72,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // entity must be in all other Storages for (it.view.type_ids) |tid| { - const ptr = it.view.registry.components.getValue(@intCast(u8, tid)).?; + const ptr = it.view.registry.components.getValue(tid).?; if (!@intToPtr(*Storage(u1), ptr).contains(entity)) { break :blk; } @@ -80,7 +80,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // entity must not be in all other excluded Storages for (it.view.exclude_type_ids) |tid| { - const ptr = it.view.registry.components.getValue(@intCast(u8, tid)).?; + const ptr = it.view.registry.components.getValue(tid).?; if (@intToPtr(*Storage(u1), ptr).contains(entity)) { break :blk; } @@ -119,7 +119,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // get our component counts in an array so we can sort the type_ids based on how many entities are in each var sub_items: [n_includes]usize = undefined; for (self.type_ids) |tid, i| { - const ptr = self.registry.components.getValue(@intCast(u8, tid)).?; + const ptr = self.registry.components.getValue(tid).?; const store = @intToPtr(*Storage(u8), ptr); sub_items[i] = store.len(); } diff --git a/zig-ecs/src/signals/dispatcher.zig b/zig-ecs/src/signals/dispatcher.zig index f70945c..bfd5526 100644 --- a/zig-ecs/src/signals/dispatcher.zig +++ b/zig-ecs/src/signals/dispatcher.zig @@ -1,17 +1,15 @@ const std = @import("std"); const Sink = @import("sink.zig").Sink; const Signal = @import("signal.zig").Signal; -const TypeMap = @import("../ecs/type_map.zig").TypeMap; +const utils = @import("../ecs/utils.zig"); pub const Dispatcher = struct { - typemap: TypeMap, - signals: std.AutoHashMap(u8, usize), + signals: std.AutoHashMap(u32, usize), allocator: *std.mem.Allocator, pub fn init(allocator: *std.mem.Allocator) Dispatcher { - return Dispatcher { - .typemap = TypeMap.init(allocator), - .signals = std.AutoHashMap(u8, usize).init(allocator), + return Dispatcher{ + .signals = std.AutoHashMap(u32, usize).init(allocator), .allocator = allocator, }; } @@ -24,21 +22,19 @@ pub const Dispatcher = struct { signal.deinit(); } - self.typemap.deinit(); self.signals.deinit(); } fn assure(self: *Dispatcher, comptime T: type) *Signal(T) { - var type_id: u8 = undefined; - if (!self.typemap.getOrPut(T, &type_id)) { - var signal = Signal(T).create(self.allocator); - var signal_ptr = @ptrToInt(signal); - _ = self.signals.put(type_id, signal_ptr) catch unreachable; - return signal; + var type_id = utils.typeId(T); + if (self.signals.get(type_id)) |kv| { + return @intToPtr(*Signal(T), kv.value); } - const ptr = self.signals.getValue(type_id).?; - return @intToPtr(*Signal(T), ptr); + var signal = Signal(T).create(self.allocator); + var signal_ptr = @ptrToInt(signal); + _ = self.signals.put(type_id, signal_ptr) catch unreachable; + return signal; } pub fn sink(self: *Dispatcher, comptime T: type) Sink(T) { diff --git a/zig-ecs/src/tests.zig b/zig-ecs/src/tests.zig index 8cd5be4..6896914 100644 --- a/zig-ecs/src/tests.zig +++ b/zig-ecs/src/tests.zig @@ -6,7 +6,6 @@ comptime { _ = @import("ecs/entity.zig"); _ = @import("ecs/handles.zig"); _ = @import("ecs/sparse_set.zig"); - _ = @import("ecs/type_map.zig"); _ = @import("ecs/views.zig"); _ = @import("ecs/groups.zig"); From faeedaf43dbd99fd12d5d6f796cc10b42518e621 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 4 Jun 2020 19:19:29 -0700 Subject: [PATCH 016/146] simplify sparse set --- zig-ecs/src/ecs/component_storage.zig | 21 ++++++------- zig-ecs/src/ecs/groups.zig | 4 +-- zig-ecs/src/ecs/registry.zig | 6 ++-- zig-ecs/src/ecs/sparse_set.zig | 44 +++++++++++++-------------- 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 0e3647c..f5e1815 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -6,9 +6,8 @@ const SparseSet = @import("sparse_set.zig").SparseSet; const Signal = @import("../signals/signal.zig").Signal; const Sink = @import("../signals/sink.zig").Sink; -/// Stores an ArrayList of components along with a SparseSet of entities. The max amount that can be stored is -/// based on the max value of DenseT -pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime DenseT: type) type { +/// Stores an ArrayList of components along with a SparseSet of entities +pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { std.debug.assert(!utils.isComptime(CompT)); // empty (zero-sized) structs will not have an array created @@ -25,7 +24,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D return struct { const Self = @This(); - set: *SparseSet(EntityT, DenseT), + set: *SparseSet(EntityT), instances: std.ArrayList(CompOrAlmostEmptyT), allocator: ?*std.mem.Allocator, safe_deinit: fn (*Self) void, @@ -36,7 +35,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D pub fn init(allocator: *std.mem.Allocator) Self { var store = Self{ - .set = SparseSet(EntityT, DenseT).initPtr(allocator), + .set = SparseSet(EntityT).initPtr(allocator), .instances = undefined, .safe_deinit = struct { fn deinit(self: *Self) void { @@ -65,7 +64,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D pub fn initPtr(allocator: *std.mem.Allocator) *Self { var store = allocator.create(Self) catch unreachable; - store.set = SparseSet(EntityT, DenseT).initPtr(allocator); + store.set = SparseSet(EntityT).initPtr(allocator); if (!is_empty_struct) store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); store.allocator = allocator; @@ -204,7 +203,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type, comptime D } test "add/try-get/remove/clear" { - var store = ComponentStorage(f32, u32, u8).init(std.testing.allocator); + var store = ComponentStorage(f32, u32).init(std.testing.allocator); defer store.deinit(); store.add(3, 66.45); @@ -220,7 +219,7 @@ test "add/try-get/remove/clear" { } test "add/get/remove" { - var store = ComponentStorage(f32, u32, u8).init(std.testing.allocator); + var store = ComponentStorage(f32, u32).init(std.testing.allocator); defer store.deinit(); store.add(3, 66.45); @@ -232,7 +231,7 @@ test "add/get/remove" { } test "iterate" { - var store = ComponentStorage(f32, u32, u8).initPtr(std.testing.allocator); + var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator); defer store.deinit(); store.add(3, 66.45); @@ -252,7 +251,7 @@ test "iterate" { test "empty component" { const Empty = struct {}; - var store = ComponentStorage(Empty, u32, u8).initPtr(std.testing.allocator); + var store = ComponentStorage(Empty, u32).initPtr(std.testing.allocator); defer store.deinit(); store.add(3, Empty{}); @@ -270,7 +269,7 @@ fn destruct(e: u32) void { } test "signals" { - var store = ComponentStorage(f32, u32, u8).init(std.testing.allocator); + var store = ComponentStorage(f32, u32).init(std.testing.allocator); defer store.deinit(); store.onConstruct().connect(construct); diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index f378e7a..9e8dcda 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -11,7 +11,7 @@ pub fn BasicGroup(comptime n_includes: usize, comptime n_excludes: usize) type { return struct { const Self = @This(); - entity_set: *SparseSet(Entity, u16), + entity_set: *SparseSet(Entity), registry: *Registry, type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32, @@ -41,7 +41,7 @@ pub fn BasicGroup(comptime n_includes: usize, comptime n_excludes: usize) type { } }; - pub fn init(entity_set: *SparseSet(Entity, u16), registry: *Registry, type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32) Self { + pub fn init(entity_set: *SparseSet(Entity), registry: *Registry, type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32) Self { return Self{ .entity_set = entity_set, .registry = registry, diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 5164162..2212c33 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -22,7 +22,7 @@ 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 { - return ComponentStorage(CompT, Entity, u16); // 65,535 components + return ComponentStorage(CompT, Entity); } /// the registry is the main gateway to all ecs functionality. It assumes all internal allocations will succeed and returns @@ -38,7 +38,7 @@ 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) = undefined, // TODO: dont hardcode this. put it in EntityTraits maybe. All SparseSets would need to use the value. + entity_set: SparseSet(Entity) = undefined, /// optional. there will be an entity_set for non-owning groups and current for owning owned: []u32, include: []u32, exclude: []u32, @@ -53,7 +53,7 @@ pub const Registry = struct { var group_data = allocator.create(GroupData) catch unreachable; group_data.hash = hash; if (owned.len == 0) { - group_data.entity_set = SparseSet(Entity, u16).init(allocator); + group_data.entity_set = SparseSet(Entity).init(allocator); } group_data.owned = std.mem.dupe(allocator, u32, owned) catch unreachable; group_data.include = std.mem.dupe(allocator, u32, include) catch unreachable; diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 76c01ab..3619084 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -2,18 +2,18 @@ const std = @import("std"); const warn = std.debug.warn; // TODO: fix entity_mask. it should come from EntityTraitsDefinition. -pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { +pub fn SparseSet(comptime SparseT: type) type { return struct { const Self = @This(); - sparse: std.ArrayList(DenseT), + sparse: std.ArrayList(SparseT), dense: std.ArrayList(SparseT), entity_mask: SparseT, allocator: ?*std.mem.Allocator, pub fn initPtr(allocator: *std.mem.Allocator) *Self { var set = allocator.create(Self) catch unreachable; - set.sparse = std.ArrayList(DenseT).init(allocator); + set.sparse = std.ArrayList(SparseT).init(allocator); set.dense = std.ArrayList(SparseT).init(allocator); set.entity_mask = std.math.maxInt(SparseT); set.allocator = allocator; @@ -22,7 +22,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { pub fn init(allocator: *std.mem.Allocator) Self { return Self{ - .sparse = std.ArrayList(DenseT).init(allocator), + .sparse = std.ArrayList(SparseT).init(allocator), .dense = std.ArrayList(SparseT).init(allocator), .entity_mask = std.math.maxInt(SparseT), .allocator = null, @@ -49,7 +49,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { return sparse & self.entity_mask; } - fn assure(self: *Self, pos: usize) []DenseT { + fn assure(self: *Self, pos: usize) []SparseT { // TODO: support paging if (self.sparse.capacity <= pos or self.sparse.capacity == 0) { const amount = pos + 1 - self.sparse.capacity; @@ -58,7 +58,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { const old_len = self.sparse.items.len; self.sparse.resize(self.sparse.items.len + amount) catch unreachable; self.sparse.expandToCapacity(); - std.mem.set(DenseT, self.sparse.items[old_len..self.sparse.items.len], std.math.maxInt(DenseT)); + std.mem.set(SparseT, self.sparse.items[old_len..self.sparse.items.len], std.math.maxInt(SparseT)); } return self.sparse.items; @@ -93,11 +93,11 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { return false; // testing against maxInt permits to avoid accessing the packed array - return curr < self.sparse.items.len and self.sparse.items[curr] != std.math.maxInt(DenseT); + return curr < self.sparse.items.len and self.sparse.items[curr] != std.math.maxInt(SparseT); } /// Returns the position of an entity in a sparse set - pub fn index(self: Self, sparse: SparseT) DenseT { + pub fn index(self: Self, sparse: SparseT) SparseT { std.debug.assert(self.contains(sparse)); return self.sparse.items[self.offset(sparse)]; } @@ -107,7 +107,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { std.debug.assert(!self.contains(sparse)); // assure(page(entt))[offset(entt)] = packed.size() - self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(DenseT, self.dense.items.len); + self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, self.dense.items.len); _ = self.dense.append(sparse) catch unreachable; } @@ -121,7 +121,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { self.dense.items[self.sparse.items[curr]] = last_dense; self.sparse.items[self.page(last_dense)] = self.sparse.items[curr]; - self.sparse.items[curr] = std.math.maxInt(DenseT); + self.sparse.items[curr] = std.math.maxInt(SparseT); _ = self.dense.pop(); } @@ -132,7 +132,7 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { var to = &self.sparse.items[sparse_r]; std.mem.swap(SparseT, &self.dense.items[from.*], &self.dense.items[to.*]); - std.mem.swap(DenseT, from, to); + std.mem.swap(SparseT, from, to); } /// Sort elements according to the given comparison function @@ -141,15 +141,15 @@ pub fn SparseSet(comptime SparseT: type, comptime DenseT: type) type { var i = @as(usize, 0); for (self.dense.items) |sparse| { - // self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(DenseT, sparse); - self.sparse.items[self.page(sparse)] = @intCast(DenseT, sparse); + // self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, sparse); + self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse); } } /// Sort entities according to their order in another sparse set. Other is the master in this case. pub fn respect(self: *Self, other: *Self) void { - var pos = @as(DenseT, 0); - var i = @as(DenseT, 0); + var pos = @as(SparseT, 0); + var i = @as(SparseT, 0); while (i < other.dense.items.len) : (i += 1) { if (self.contains(other.dense.items[i])) { if (other.dense.items[i] != self.dense.items[pos]) { @@ -181,7 +181,7 @@ fn printSet(set: *SparseSet(u32, u8)) void { } test "add/remove/clear" { - var set = SparseSet(u32, u8).initPtr(std.testing.allocator); + var set = SparseSet(u32).initPtr(std.testing.allocator); defer set.deinit(); set.add(4); @@ -198,7 +198,7 @@ test "add/remove/clear" { } test "grow" { - var set = SparseSet(u32, u8).initPtr(std.testing.allocator); + var set = SparseSet(u32).initPtr(std.testing.allocator); defer set.deinit(); var i = @as(usize, std.math.maxInt(u8)); @@ -210,7 +210,7 @@ test "grow" { } test "swap" { - var set = SparseSet(u32, u8).initPtr(std.testing.allocator); + var set = SparseSet(u32).initPtr(std.testing.allocator); defer set.deinit(); set.add(4); @@ -224,7 +224,7 @@ test "swap" { } test "data() synced" { - var set = SparseSet(u32, u8).initPtr(std.testing.allocator); + var set = SparseSet(u32).initPtr(std.testing.allocator); defer set.deinit(); set.add(0); @@ -242,10 +242,10 @@ test "data() synced" { } test "respect" { - var set1 = SparseSet(u32, u8).initPtr(std.testing.allocator); + var set1 = SparseSet(u32).initPtr(std.testing.allocator); defer set1.deinit(); - var set2 = SparseSet(u32, u8).initPtr(std.testing.allocator); + var set2 = SparseSet(u32).initPtr(std.testing.allocator); defer set2.deinit(); set1.add(3); @@ -265,7 +265,7 @@ test "respect" { } test "respect" { - var set = SparseSet(u32, u8).initPtr(std.testing.allocator); + var set = SparseSet(u32).initPtr(std.testing.allocator); defer set.deinit(); set.add(5); From d6356a6d6b757bdebae627a43af7778354a87f14 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 4 Jun 2020 19:19:34 -0700 Subject: [PATCH 017/146] new cache --- zig-ecs/src/resources/cache.zig | 88 +++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 zig-ecs/src/resources/cache.zig diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig new file mode 100644 index 0000000..08614a7 --- /dev/null +++ b/zig-ecs/src/resources/cache.zig @@ -0,0 +1,88 @@ +const std = @import("std"); + +/// Simple cache for resources of a given type. TLoader should be a struct that implements a single +/// method: load(args: var) *T. If any resource has a deinit method it will be called when clear +/// or remove is called. +pub fn Cache(comptime T: type, TLoader: type) type { + return struct { + loader: TLoader, + resources: std.AutoHashMap(u16, *T), + + pub fn init(allocator: *std.mem.Allocator, comptime loader: TLoader) @This() { + return .{ + .loader = loader, + .resources = std.AutoHashMap(u16, *T).init(allocator), + }; + } + + pub fn deinit(self: @This()) void { + self.resources.deinit(); + } + + pub fn load(self: *@This(), id: u16, args: var) *T { + if (self.resources.getValue(id)) |resource| { + return resource; + } + + var resource = self.loader.load(args); + _ = self.resources.put(id, resource) catch unreachable; + return resource; + } + + pub fn contains(self: *@This(), id: u16) bool { + return self.resources.contains(id); + } + + pub fn remove(self: *@This(), id: u16) void { + if (self.resources.remove(id)) |kv| { + if (@hasDecl(T, "deinit")) { + @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); + } + } + } + + pub fn clear(self: *@This()) void { + // optionally deinit any resources that have a deinit method + if (@hasDecl(T, "deinit")) { + var iter = self.resources.iterator(); + while (iter.next()) |kv| { + @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); + } + } + self.resources.clear(); + } + + pub fn size(self: @This()) usize { + return self.resources.size; + } + }; +} + +test "cache" { + const Thing = struct { + fart: i32, + + pub fn deinit(self: *@This()) void { + std.testing.allocator.destroy(self); + } + }; + + const ThingLoader = struct { + pub fn load(self: @This(), args: var) *Thing { + return std.testing.allocator.create(Thing) catch unreachable; + } + }; + + var cache = Cache(Thing, ThingLoader).init(std.testing.allocator, ThingLoader{}); + defer cache.deinit(); + + var thing = cache.load(6, .{}); + var thing2 = cache.load(2, .{}); + std.testing.expectEqual(cache.size(), 2); + + cache.remove(2); + std.testing.expectEqual(cache.size(), 1); + + cache.clear(); + std.testing.expectEqual(cache.size(), 0); +} \ No newline at end of file From 5b62f55123300c3b7e1de419ce46aa858ffcee3f Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 4 Jun 2020 19:20:20 -0700 Subject: [PATCH 018/146] tests --- zig-ecs/src/tests.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zig-ecs/src/tests.zig b/zig-ecs/src/tests.zig index 6896914..862fc08 100644 --- a/zig-ecs/src/tests.zig +++ b/zig-ecs/src/tests.zig @@ -12,4 +12,7 @@ comptime { // signals _ = @import("signals/delegate.zig"); _ = @import("signals/signal.zig"); + + // resources + _ = @import("resources/cache.zig"); } From bb688e52f47028bccf8fa876c0718c3fa21cc60d Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 4 Jun 2020 23:26:34 -0700 Subject: [PATCH 019/146] crap --- zig-ecs/src/resources/cache.zig | 3 ++- zig-ecs/src/signals/delegate.zig | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index 08614a7..e0aad08 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -15,7 +15,8 @@ pub fn Cache(comptime T: type, TLoader: type) type { }; } - pub fn deinit(self: @This()) void { + pub fn deinit(self: *@This()) void { + self.clear(); self.resources.deinit(); } diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index 30a74b0..aafc5eb 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -22,7 +22,7 @@ pub fn Delegate(comptime Event: type) type { .callback = .{ .bound = struct { fn cb(self: usize, param: Event) void { - return @call(.{ .modifier = .always_inline }, @field(@intToPtr(T, self), fn_name), .{param}); + @call(.{ .modifier = .always_inline }, @field(@intToPtr(T, self), fn_name), .{param}); } }.cb, }, From 96cbb374763c979063debfa2014e76b6f3f29cef Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 5 Jun 2020 14:15:10 -0700 Subject: [PATCH 020/146] better string hash --- zig-ecs/src/ecs/utils.zig | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 2814cf8..abda0d3 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -18,20 +18,36 @@ pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T /// comptime string hashing for the type names pub fn typeId(comptime T: type) u32 { - return hashString(@typeName(T)); + return hashStringFnv(u32, @typeName(T)); +} + +/// comptime string hashing for the type names +pub fn typeId64(comptime T: type) u64 { + return hashStringFnv(u64, @typeName(T)); } pub fn hashString(comptime str: []const u8) u32 { return @truncate(u32, std.hash.Wyhash.hash(0, str)); } +/// Fowler–Noll–Vo string hash. ReturnType should be u32/u64 +pub fn hashStringFnv(comptime ReturnType: type, comptime str: []const u8) ReturnType { + std.debug.assert(ReturnType == u32 or ReturnType == u64); + + const prime = if (ReturnType == u32) @as(u32, 16777619) else @as(u64, 1099511628211); + var value = if (ReturnType == u32) @as(u32, 2166136261) else @as(u64, 14695981039346656037); + for (str) |c| { + value = (value ^ @intCast(u32, c)) *% prime; + } + return value; +} + /// comptime string hashing, djb2 by Dan Bernstein. Fails on large strings. pub fn hashStringDjb2(comptime str: []const u8) comptime_int { var hash: comptime_int = 5381; for (str) |c| { hash = ((hash << 5) + hash) + @intCast(comptime_int, c); } - return hash; } From 3fa455dc4825241c691b7eae6511fe1c126d9f20 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 5 Jun 2020 14:15:17 -0700 Subject: [PATCH 021/146] asset loader --- zig-ecs/src/resources/assets.zig | 83 ++++++++++++++++++++++++++++++++ zig-ecs/src/resources/cache.zig | 83 +++++++++++++++++++++++++++----- zig-ecs/src/tests.zig | 1 + 3 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 zig-ecs/src/resources/assets.zig diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig new file mode 100644 index 0000000..34ad698 --- /dev/null +++ b/zig-ecs/src/resources/assets.zig @@ -0,0 +1,83 @@ +const std = @import("std"); +const utils = @import("../ecs/utils.zig"); +const Cache = @import("cache.zig").Cache; + +pub const Assets = struct { + caches: std.AutoHashMap(u32, usize), + allocator: *std.mem.Allocator, + + pub fn init(allocator: *std.mem.Allocator) Assets { + return Assets{ + .caches = std.AutoHashMap(u32, usize).init(allocator), + .allocator = allocator, + }; + } + + pub fn deinit(self: Assets) void { + var it = self.caches.iterator(); + while (it.next()) |ptr| { + // HACK: we dont know the Type here but we need to call deinit + @intToPtr(*Cache(u1), ptr.value).deinit(); + } + + self.caches.deinit(); + } + + pub fn registerCache(self: *Assets, comptime AssetT: type, comptime LoaderT: type) *Cache(AssetT) { + var cache = Cache(AssetT).initPtr(self.allocator, LoaderT); + _ = self.caches.put(utils.typeId(AssetT), @ptrToInt(cache)) catch unreachable; + return cache; + } + + pub fn get(self: Assets, comptime AssetT: type) *Cache(AssetT) { + if (self.caches.getValue(utils.typeId(AssetT))) |tid| { + return @intToPtr(*Cache(AssetT), tid); + } + unreachable; + } + + pub fn load(self: Assets, comptime AssetT: type, comptime LoaderT: type, args: LoaderT.LoadArgs) *AssetT { + var cache = self.get(AssetT); + return cache.load(666, LoaderT, args); + } +}; + +test "assets" { + const Thing = struct { + fart: i32, + pub fn deinit(self: *@This()) void { + std.testing.allocator.destroy(self); + } + }; + + const ThingLoader = struct { + pub const LoadArgs = struct {}; + pub fn load(self: @This(), args: var) *Thing { + return std.testing.allocator.create(Thing) catch unreachable; + } + }; + + const OtherThing = struct { + fart: i32, + pub fn deinit(self: *@This()) void { + std.testing.allocator.destroy(self); + } + }; + + const OtherThingLoader = struct { + pub const LoadArgs = struct {}; + pub fn load(self: @This(), args: var) *OtherThing { + return std.testing.allocator.create(OtherThing) catch unreachable; + } + }; + + var assets = Assets.init(std.testing.allocator); + defer assets.deinit(); + + var cache = assets.registerCache(Thing, ThingLoader); + var thing = assets.get(Thing).load(6, ThingLoader, ThingLoader.LoadArgs{}); + std.testing.expectEqual(cache.size(), 1); + + var thing2 = assets.load(Thing, ThingLoader, ThingLoader.LoadArgs{}); + std.testing.expectEqual(cache.size(), 2); +} diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index e0aad08..d711844 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -1,31 +1,88 @@ const std = @import("std"); +pub const ErasedPtr = struct { + ptr: usize, + + pub fn init(ptr: var) ErasedPtr { + if (@sizeOf(@TypeOf(ptr)) == 0) { + return .{ .ptr = undefined }; + } + return .{ .ptr = @ptrToInt(ptr) }; + } + + pub fn as(self: ErasedPtr, comptime T: type) *T { + if (@sizeOf(T) == 0) + return @as(T, undefined); + return self.asPtr(*T); + } + + pub fn asPtr(self: ErasedPtr, comptime PtrT: type) PtrT { + if (@sizeOf(PtrT) == 0) + return @as(PtrT, undefined); + return @intToPtr(PtrT, self.ptr); + } +}; + /// Simple cache for resources of a given type. TLoader should be a struct that implements a single /// method: load(args: var) *T. If any resource has a deinit method it will be called when clear /// or remove is called. -pub fn Cache(comptime T: type, TLoader: type) type { +pub fn Cache(comptime T: type) type { return struct { - loader: TLoader, + const Self = @This(); + + safe_deinit: fn (*@This()) void, resources: std.AutoHashMap(u16, *T), + loader: ErasedPtr, + allocator: ?*std.mem.Allocator = null, + + pub fn initPtr(allocator: *std.mem.Allocator, comptime LoaderT: type) *@This() { + const ptr = if (@sizeOf(LoaderT) > 0) + ErasedPtr.init(allocator.create(LoaderT) catch unreachable) + else + ErasedPtr.init(LoaderT); + + var cache = allocator.create(@This()) catch unreachable; + cache.safe_deinit = struct { + fn deinit(self: *Self) void { + self.clear(); + self.resources.deinit(); + self.allocator.?.destroy(self); + } + }.deinit; + cache.loader = ptr; + cache.resources = std.AutoHashMap(u16, *T).init(allocator); + cache.allocator = allocator; + return cache; + } + + pub fn init(allocator: *std.mem.Allocator, comptime LoaderT: type) @This() { + const ptr = if (@sizeOf(LoaderT) > 0) + ErasedPtr.init(std.testing.allocator.create(LoaderT) catch unreachable) + else + ErasedPtr.init(LoaderT); - pub fn init(allocator: *std.mem.Allocator, comptime loader: TLoader) @This() { return .{ - .loader = loader, + .safe_deinit = struct { + fn deinit(self: *Self) void { + self.clear(); + self.resources.deinit(); + } + }.deinit, + .loader = ptr, .resources = std.AutoHashMap(u16, *T).init(allocator), }; } pub fn deinit(self: *@This()) void { - self.clear(); - self.resources.deinit(); + self.safe_deinit(self); } - pub fn load(self: *@This(), id: u16, args: var) *T { + fn load(self: *@This(), id: u16, comptime LoaderT: type, args: LoaderT.LoadArgs) *T { if (self.resources.getValue(id)) |resource| { return resource; } - var resource = self.loader.load(args); + var resource = self.loader.as(LoaderT).load(args); _ = self.resources.put(id, resource) catch unreachable; return resource; } @@ -62,23 +119,23 @@ pub fn Cache(comptime T: type, TLoader: type) type { test "cache" { const Thing = struct { fart: i32, - pub fn deinit(self: *@This()) void { std.testing.allocator.destroy(self); } }; const ThingLoader = struct { + pub const LoadArgs = struct {}; pub fn load(self: @This(), args: var) *Thing { return std.testing.allocator.create(Thing) catch unreachable; } }; - var cache = Cache(Thing, ThingLoader).init(std.testing.allocator, ThingLoader{}); + var cache = Cache(Thing).init(std.testing.allocator, ThingLoader); defer cache.deinit(); - var thing = cache.load(6, .{}); - var thing2 = cache.load(2, .{}); + var thing = cache.load(6, ThingLoader, ThingLoader.LoadArgs{}); + var thing2 = cache.load(2, ThingLoader, ThingLoader.LoadArgs{}); std.testing.expectEqual(cache.size(), 2); cache.remove(2); @@ -86,4 +143,4 @@ test "cache" { cache.clear(); std.testing.expectEqual(cache.size(), 0); -} \ No newline at end of file +} diff --git a/zig-ecs/src/tests.zig b/zig-ecs/src/tests.zig index 862fc08..a6d9de4 100644 --- a/zig-ecs/src/tests.zig +++ b/zig-ecs/src/tests.zig @@ -15,4 +15,5 @@ comptime { // resources _ = @import("resources/cache.zig"); + _ = @import("resources/assets.zig"); } From a547add7272eca85996b9e9591cbd08f5be76a46 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 5 Jun 2020 14:17:25 -0700 Subject: [PATCH 022/146] to utils --- zig-ecs/src/ecs/utils.zig | 23 +++++++++++++++++++++++ zig-ecs/src/resources/cache.zig | 24 +----------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index abda0d3..f7d72e7 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -1,5 +1,28 @@ const std = @import("std"); +pub const ErasedPtr = struct { + ptr: usize, + + pub fn init(ptr: var) ErasedPtr { + if (@sizeOf(@TypeOf(ptr)) == 0) { + return .{ .ptr = undefined }; + } + return .{ .ptr = @ptrToInt(ptr) }; + } + + pub fn as(self: ErasedPtr, comptime T: type) *T { + if (@sizeOf(T) == 0) + return @as(T, undefined); + return self.asPtr(*T); + } + + pub fn asPtr(self: ErasedPtr, comptime PtrT: type) PtrT { + if (@sizeOf(PtrT) == 0) + return @as(PtrT, undefined); + return @intToPtr(PtrT, self.ptr); + } +}; + /// 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; diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index d711844..b5be765 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -1,27 +1,5 @@ const std = @import("std"); - -pub const ErasedPtr = struct { - ptr: usize, - - pub fn init(ptr: var) ErasedPtr { - if (@sizeOf(@TypeOf(ptr)) == 0) { - return .{ .ptr = undefined }; - } - return .{ .ptr = @ptrToInt(ptr) }; - } - - pub fn as(self: ErasedPtr, comptime T: type) *T { - if (@sizeOf(T) == 0) - return @as(T, undefined); - return self.asPtr(*T); - } - - pub fn asPtr(self: ErasedPtr, comptime PtrT: type) PtrT { - if (@sizeOf(PtrT) == 0) - return @as(PtrT, undefined); - return @intToPtr(PtrT, self.ptr); - } -}; +const ErasedPtr = @import("../ecs/utils.zig").ErasedPtr; /// Simple cache for resources of a given type. TLoader should be a struct that implements a single /// method: load(args: var) *T. If any resource has a deinit method it will be called when clear From 07905d0b0938bb0241a96929fa6004be8c751576 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 5 Jun 2020 19:15:21 -0700 Subject: [PATCH 023/146] resources pretty done --- zig-ecs/src/ecs/utils.zig | 3 +- zig-ecs/src/resources/assets.zig | 60 +++++++++++++++++++------------- zig-ecs/src/resources/cache.zig | 52 +++++++++++---------------- 3 files changed, 57 insertions(+), 58 deletions(-) diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index f7d72e7..87cc054 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -49,8 +49,9 @@ pub fn typeId64(comptime T: type) u64 { return hashStringFnv(u64, @typeName(T)); } +/// u32 Fowler-Noll-Vo string hash pub fn hashString(comptime str: []const u8) u32 { - return @truncate(u32, std.hash.Wyhash.hash(0, str)); + return hashStringFnv(u32, str); } /// Fowler–Noll–Vo string hash. ReturnType should be u32/u64 diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index 34ad698..b231755 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -23,22 +23,26 @@ pub const Assets = struct { self.caches.deinit(); } - pub fn registerCache(self: *Assets, comptime AssetT: type, comptime LoaderT: type) *Cache(AssetT) { - var cache = Cache(AssetT).initPtr(self.allocator, LoaderT); + pub fn get(self: *Assets, comptime AssetT: type) *Cache(AssetT) { + if (self.caches.getValue(utils.typeId(AssetT))) |tid| { + return @intToPtr(*Cache(AssetT), tid); + } + + var cache = Cache(AssetT).initPtr(self.allocator); _ = self.caches.put(utils.typeId(AssetT), @ptrToInt(cache)) catch unreachable; return cache; } - pub fn get(self: Assets, comptime AssetT: type) *Cache(AssetT) { - if (self.caches.getValue(utils.typeId(AssetT))) |tid| { - return @intToPtr(*Cache(AssetT), tid); - } - unreachable; + pub fn load(self: *Assets, id: u16, comptime loader: var) ReturnType(loader, false) { + return self.get(ReturnType(loader, true)).load(id, loader); } - pub fn load(self: Assets, comptime AssetT: type, comptime LoaderT: type, args: LoaderT.LoadArgs) *AssetT { - var cache = self.get(AssetT); - return cache.load(666, LoaderT, args); + fn ReturnType(comptime loader: var, strip_ptr: bool) type { + var ret = @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.?; + if (strip_ptr) { + return ret.Child; + } + return ret; } }; @@ -50,13 +54,6 @@ test "assets" { } }; - const ThingLoader = struct { - pub const LoadArgs = struct {}; - pub fn load(self: @This(), args: var) *Thing { - return std.testing.allocator.create(Thing) catch unreachable; - } - }; - const OtherThing = struct { fart: i32, pub fn deinit(self: *@This()) void { @@ -64,20 +61,33 @@ test "assets" { } }; - const OtherThingLoader = struct { - pub const LoadArgs = struct {}; - pub fn load(self: @This(), args: var) *OtherThing { + const OtherThingLoadArgs = struct { + pub fn load(self: @This()) *OtherThing { return std.testing.allocator.create(OtherThing) catch unreachable; } }; + const ThingLoadArgs = struct { + pub fn load(self: @This()) *Thing { + return std.testing.allocator.create(Thing) catch unreachable; + } + }; + var assets = Assets.init(std.testing.allocator); defer assets.deinit(); - var cache = assets.registerCache(Thing, ThingLoader); - var thing = assets.get(Thing).load(6, ThingLoader, ThingLoader.LoadArgs{}); - std.testing.expectEqual(cache.size(), 1); + var thing = assets.get(Thing).load(6, ThingLoadArgs{}); + std.testing.expectEqual(assets.get(Thing).size(), 1); + + var thing2 = assets.load(4, ThingLoadArgs{}); + std.testing.expectEqual(assets.get(Thing).size(), 2); + + var other_thing = assets.get(OtherThing).load(6, OtherThingLoadArgs{}); + std.testing.expectEqual(assets.get(OtherThing).size(), 1); + + var other_thing2 = assets.load(8, OtherThingLoadArgs{}); + std.testing.expectEqual(assets.get(OtherThing).size(), 2); - var thing2 = assets.load(Thing, ThingLoader, ThingLoader.LoadArgs{}); - std.testing.expectEqual(cache.size(), 2); + assets.get(OtherThing).clear(); + std.testing.expectEqual(assets.get(OtherThing).size(), 0); } diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index b5be765..65563b5 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -1,24 +1,18 @@ const std = @import("std"); const ErasedPtr = @import("../ecs/utils.zig").ErasedPtr; -/// Simple cache for resources of a given type. TLoader should be a struct that implements a single -/// method: load(args: var) *T. If any resource has a deinit method it will be called when clear -/// or remove is called. +/// Simple cache for resources of a given type. If any resource has a deinit method it will be called when clear +/// or remove is called. Implementing a "loader" which is passed to "load" is a struct with one method: +/// - load(self: @This()) *T. pub fn Cache(comptime T: type) type { return struct { const Self = @This(); safe_deinit: fn (*@This()) void, - resources: std.AutoHashMap(u16, *T), - loader: ErasedPtr, + resources: std.AutoHashMap(u32, *T), allocator: ?*std.mem.Allocator = null, - pub fn initPtr(allocator: *std.mem.Allocator, comptime LoaderT: type) *@This() { - const ptr = if (@sizeOf(LoaderT) > 0) - ErasedPtr.init(allocator.create(LoaderT) catch unreachable) - else - ErasedPtr.init(LoaderT); - + pub fn initPtr(allocator: *std.mem.Allocator) *@This() { var cache = allocator.create(@This()) catch unreachable; cache.safe_deinit = struct { fn deinit(self: *Self) void { @@ -27,18 +21,12 @@ pub fn Cache(comptime T: type) type { self.allocator.?.destroy(self); } }.deinit; - cache.loader = ptr; - cache.resources = std.AutoHashMap(u16, *T).init(allocator); + cache.resources = std.AutoHashMap(u32, *T).init(allocator); cache.allocator = allocator; return cache; } - pub fn init(allocator: *std.mem.Allocator, comptime LoaderT: type) @This() { - const ptr = if (@sizeOf(LoaderT) > 0) - ErasedPtr.init(std.testing.allocator.create(LoaderT) catch unreachable) - else - ErasedPtr.init(LoaderT); - + pub fn init(allocator: *std.mem.Allocator) @This() { return .{ .safe_deinit = struct { fn deinit(self: *Self) void { @@ -46,8 +34,7 @@ pub fn Cache(comptime T: type) type { self.resources.deinit(); } }.deinit, - .loader = ptr, - .resources = std.AutoHashMap(u16, *T).init(allocator), + .resources = std.AutoHashMap(u32, *T).init(allocator), }; } @@ -55,21 +42,21 @@ pub fn Cache(comptime T: type) type { self.safe_deinit(self); } - fn load(self: *@This(), id: u16, comptime LoaderT: type, args: LoaderT.LoadArgs) *T { + pub fn load(self: *@This(), id: u32, comptime loader: var) @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.? { if (self.resources.getValue(id)) |resource| { return resource; } - var resource = self.loader.as(LoaderT).load(args); + var resource = loader.load(); _ = self.resources.put(id, resource) catch unreachable; return resource; } - pub fn contains(self: *@This(), id: u16) bool { + pub fn contains(self: *@This(), id: u32) bool { return self.resources.contains(id); } - pub fn remove(self: *@This(), id: u16) void { + pub fn remove(self: *@This(), id: u32) void { if (self.resources.remove(id)) |kv| { if (@hasDecl(T, "deinit")) { @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); @@ -95,6 +82,8 @@ pub fn Cache(comptime T: type) type { } test "cache" { + const utils = @import("../ecs/utils.zig"); + const Thing = struct { fart: i32, pub fn deinit(self: *@This()) void { @@ -102,21 +91,20 @@ test "cache" { } }; - const ThingLoader = struct { - pub const LoadArgs = struct {}; - pub fn load(self: @This(), args: var) *Thing { + const ThingLoadArgs = struct { + pub fn load(self: @This()) *Thing { return std.testing.allocator.create(Thing) catch unreachable; } }; - var cache = Cache(Thing).init(std.testing.allocator, ThingLoader); + var cache = Cache(Thing).init(std.testing.allocator); defer cache.deinit(); - var thing = cache.load(6, ThingLoader, ThingLoader.LoadArgs{}); - var thing2 = cache.load(2, ThingLoader, ThingLoader.LoadArgs{}); + var thing = cache.load(utils.hashString("my/id"), ThingLoadArgs{}); + var thing2 = cache.load(utils.hashString("another/id"), ThingLoadArgs{}); std.testing.expectEqual(cache.size(), 2); - cache.remove(2); + cache.remove(utils.hashString("my/id")); std.testing.expectEqual(cache.size(), 1); cache.clear(); From 37af42daaa6d56bba79793a30e8b47abb4e6671c Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 6 Jun 2020 16:18:26 -0700 Subject: [PATCH 024/146] singletons --- zig-ecs/src/ecs/registry.zig | 19 +++++-- zig-ecs/src/ecs/type_store.zig | 89 +++++++++++++++++++++++++++++++++ zig-ecs/src/tests.zig | 1 + zig-ecs/tests/registry_test.zig | 15 +++++- 4 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 zig-ecs/src/ecs/type_store.zig diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 2212c33..0b16b44 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -6,6 +6,7 @@ const Handles = @import("handles.zig").Handles; const SparseSet = @import("sparse_set.zig").SparseSet; const ComponentStorage = @import("component_storage.zig").ComponentStorage; const Sink = @import("../signals/sink.zig").Sink; +const TypeStore = @import("type_store.zig").TypeStore; // allow overriding EntityTraits by setting in root via: EntityTraits = EntityTraitsType(.medium); const root = @import("root"); @@ -15,10 +16,10 @@ 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("views.zig").BasicView; -pub const MultiView = @import("views.zig").MultiView; -pub const BasicGroup = @import("groups.zig").BasicGroup; -pub const OwningGroup = @import("groups.zig").OwningGroup; +const BasicView = @import("views.zig").BasicView; +const MultiView = @import("views.zig").MultiView; +const BasicGroup = @import("groups.zig").BasicGroup; +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 { @@ -33,6 +34,7 @@ pub const Registry = struct { components: std.AutoHashMap(u32, usize), contexts: std.AutoHashMap(u32, usize), groups: std.ArrayList(*GroupData), + singletons: TypeStore, allocator: *std.mem.Allocator, /// internal, persistant data structure to manage the entities in a group @@ -141,6 +143,7 @@ pub const Registry = struct { .components = std.AutoHashMap(u32, usize).init(allocator), .contexts = std.AutoHashMap(u32, usize).init(allocator), .groups = std.ArrayList(*GroupData).init(allocator), + .singletons = TypeStore.init(allocator), .allocator = allocator, }; } @@ -160,6 +163,7 @@ pub const Registry = struct { self.components.deinit(); self.contexts.deinit(); self.groups.deinit(); + self.singletons.deinit(); self.handles.deinit(); } @@ -303,7 +307,7 @@ pub const Registry = struct { return self.assure(T).getConst(entity); } - /// Returns a reference to the given component for an entity + /// Returns a reference to the given component for an entity creating it if necessary pub fn getOrAdd(self: *Registry, comptime T: type, entity: Entity) *T { if (self.has(T, entity)) return self.get(T, entity); self.add(T, entity, std.mem.zeros(T)); @@ -353,6 +357,11 @@ pub const Registry = struct { null; } + /// provides access to a TypeStore letting you add singleton components to the registry + pub fn singletons(self: Registry) TypeStore { + return self.singletons; + } + /// Checks whether the given component belongs to any group pub fn sortable(self: Registry, comptime T: type) bool { return true; diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig new file mode 100644 index 0000000..9a52341 --- /dev/null +++ b/zig-ecs/src/ecs/type_store.zig @@ -0,0 +1,89 @@ +const std = @import("std"); +const utils = @import("utils.zig"); + +/// stores a single object of type T for each T added +pub const TypeStore = struct { + map: std.AutoHashMap(u32, []u8), + allocator: *std.mem.Allocator, + + pub fn init(allocator: *std.mem.Allocator) TypeStore { + return TypeStore{ + .map = std.AutoHashMap(u32, []u8).init(allocator), + .allocator = allocator, + }; + } + + pub fn deinit(self: TypeStore) void { + var iter = self.map.iterator(); + while (iter.next()) |kv| { + self.allocator.free(kv.value); + } + self.map.deinit(); + } + + /// adds instance, returning a pointer to the item as it lives in the store + pub fn add(self: *TypeStore, instance: var) *@TypeOf(instance) { + var bytes = self.allocator.alloc(u8, @sizeOf(@TypeOf(instance))) catch unreachable; + var ptr = @ptrCast(*@TypeOf(instance), @alignCast(@alignOf(@TypeOf(instance)), bytes)); + ptr.* = instance; + _ = self.map.put(utils.typeId(@TypeOf(instance)), bytes) catch unreachable; + return ptr; + } + + pub fn get(self: *TypeStore, comptime T: type) *T { + if (self.map.getValue(utils.typeId(T))) |bytes| { + return @ptrCast(*T, @alignCast(@alignOf(T), bytes)); + } + unreachable; + } + + pub fn getConst(self: *TypeStore, comptime T: type) T { + return self.get(T).*; + } + + pub fn getOrAdd(self: *TypeStore, comptime T: type) *T { + if (self.has(T)) return self.get(T); + var instance = std.mem.zeroes(T); + return self.add(instance); + } + + pub fn remove(self: *TypeStore, comptime T: type) void { + if (self.map.getValue(utils.typeId(T))) |bytes| { + self.allocator.free(bytes); + _ = self.map.remove(utils.typeId(T)); + } + } + + pub fn has(self: *TypeStore, comptime T: type) bool { + return self.map.contains(utils.typeId(T)); + } +}; + +test "TypeStore" { + const Vector = struct { x: f32 = 0, y: f32 = 0, z: f32 = 0}; + + var store = TypeStore.init(std.testing.allocator); + defer store.deinit(); + + var orig = Vector{.x = 5, .y = 6, .z = 8}; + var inserted = store.add(orig); + std.testing.expect(store.has(Vector)); + std.testing.expectEqual(inserted.*, Vector{.x = 5, .y = 6, .z = 8}); + + var v = store.get(Vector); + std.testing.expectEqual(v.*, Vector{.x = 5, .y = 6, .z = 8}); + v.*.x = 666; + + var v2 = store.get(Vector); + std.testing.expectEqual(v2.*, Vector{.x = 666, .y = 6, .z = 8}); + + store.remove(Vector); + std.testing.expect(!store.has(Vector)); + + var v3 = store.getOrAdd(u32); + std.testing.expectEqual(v3.*, 0); + v3.* = 777; + + var v4 = store.get(u32); + std.testing.expectEqual(v3.*, 777); +} \ No newline at end of file diff --git a/zig-ecs/src/tests.zig b/zig-ecs/src/tests.zig index a6d9de4..5d91e6d 100644 --- a/zig-ecs/src/tests.zig +++ b/zig-ecs/src/tests.zig @@ -8,6 +8,7 @@ comptime { _ = @import("ecs/sparse_set.zig"); _ = @import("ecs/views.zig"); _ = @import("ecs/groups.zig"); + _ = @import("ecs/type_store.zig"); // signals _ = @import("signals/delegate.zig"); diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index 2488772..bec5c88 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -53,7 +53,7 @@ test "context not pointer" { // reg.setContext(pos); } -test "component context get/set/unset" { +test "context get/set/unset" { const SomeType = struct { dummy: u1 }; var reg = Registry.init(std.testing.allocator); @@ -72,6 +72,19 @@ test "component context get/set/unset" { std.testing.expectEqual(ctx, null); } +test "singletons" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var pos = Position{ .x = 5, .y = 5 }; + var inserted = reg.singletons.add(pos); + std.testing.expect(reg.singletons.has(Position)); + std.testing.expectEqual(inserted.*, pos); + + reg.singletons.remove(Position); + std.testing.expect(!reg.singletons.has(Position)); +} + test "destroy" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); From 1234b801fd164130ba63ca063340755b7e6a16ad Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 7 Jun 2020 17:28:42 -0700 Subject: [PATCH 025/146] cleanup --- zig-ecs/src/ecs/component_storage.zig | 2 ++ zig-ecs/src/ecs/groups.zig | 37 ++++++++++++++++++++++++++- zig-ecs/src/ecs/type_store.zig | 28 ++++++++++---------- zig-ecs/src/ecs/utils.zig | 4 +-- zig-ecs/tests/registry_test.zig | 4 +-- 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index f5e1815..9d53f38 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -27,6 +27,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { set: *SparseSet(EntityT), instances: std.ArrayList(CompOrAlmostEmptyT), allocator: ?*std.mem.Allocator, + super: usize = 0, /// doesnt really belong here...used to denote group ownership safe_deinit: fn (*Self) void, safe_swap: fn (*Self, EntityT, EntityT) void, construction: Signal(EntityT), @@ -68,6 +69,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { if (!is_empty_struct) store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); store.allocator = allocator; + store.super = 0; store.construction = Signal(EntityT).init(allocator); store.update = Signal(EntityT).init(allocator); store.destruction = Signal(EntityT).init(allocator); diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 9e8dcda..3cb682b 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -78,13 +78,15 @@ pub fn OwningGroup(comptime n_owned: usize, comptime n_includes: usize, comptime const Self = @This(); current: *usize, + super: *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 { + pub fn init(super: *usize, 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{ + .super = super, .current = current, .registry = registry, .owned_type_ids = owned_type_ids, @@ -96,6 +98,10 @@ pub fn OwningGroup(comptime n_owned: usize, comptime n_includes: usize, comptime pub fn len(self: Self) usize { return self.current.*; } + + pub fn sortable(self: *Registry, comptime T: type) bool { + return self.super.* == n_owned + n_includes + n_excludes; + } }; } @@ -183,4 +189,33 @@ test "OwningGroup add/remove" { reg.remove(i32, e0); std.testing.expectEqual(group.len(), 0); +} + +test "multiple OwningGroups" { + const Sprite = struct { x: f32 }; + const Transform = struct { x: f32 }; + const Renderable = struct { x: f32 }; + const Rotation = struct { x: f32 }; + + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + // var group1 = reg.group(.{u64, u32}, .{}, .{}); + // var group2 = reg.group(.{u64, u32, u8}, .{}, .{}); + + var group5 = reg.group(.{Sprite, Transform}, .{Renderable, Rotation}, .{}); + var group3 = reg.group(.{Sprite}, .{Renderable}, .{}); + var group4 = reg.group(.{Sprite, Transform}, .{Renderable}, .{}); + + 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)); + + // this will break the group + // var group6 = reg.group(.{Sprite, Rotation}, .{}, .{}); } \ No newline at end of file diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index 9a52341..56855ec 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -22,12 +22,10 @@ pub const TypeStore = struct { } /// adds instance, returning a pointer to the item as it lives in the store - pub fn add(self: *TypeStore, instance: var) *@TypeOf(instance) { + pub fn add(self: *TypeStore, instance: var) void { var bytes = self.allocator.alloc(u8, @sizeOf(@TypeOf(instance))) catch unreachable; - var ptr = @ptrCast(*@TypeOf(instance), @alignCast(@alignOf(@TypeOf(instance)), bytes)); - ptr.* = instance; + std.mem.copy(u8, bytes, std.mem.asBytes(&instance)); _ = self.map.put(utils.typeId(@TypeOf(instance)), bytes) catch unreachable; - return ptr; } pub fn get(self: *TypeStore, comptime T: type) *T { @@ -42,9 +40,11 @@ pub const TypeStore = struct { } pub fn getOrAdd(self: *TypeStore, comptime T: type) *T { - if (self.has(T)) return self.get(T); - var instance = std.mem.zeroes(T); - return self.add(instance); + if (!self.has(T)) { + var instance = std.mem.zeroes(T); + self.add(instance); + } + return self.get(T); } pub fn remove(self: *TypeStore, comptime T: type) void { @@ -60,22 +60,22 @@ pub const TypeStore = struct { }; test "TypeStore" { - const Vector = struct { x: f32 = 0, y: f32 = 0, z: f32 = 0}; + const Vector = struct { x: f32 = 0, y: f32 = 0, z: f32 = 0 }; var store = TypeStore.init(std.testing.allocator); defer store.deinit(); - var orig = Vector{.x = 5, .y = 6, .z = 8}; - var inserted = store.add(orig); + var orig = Vector{ .x = 5, .y = 6, .z = 8 }; + store.add(orig); std.testing.expect(store.has(Vector)); - std.testing.expectEqual(inserted.*, Vector{.x = 5, .y = 6, .z = 8}); + std.testing.expectEqual(store.get(Vector).*, orig); var v = store.get(Vector); - std.testing.expectEqual(v.*, Vector{.x = 5, .y = 6, .z = 8}); + std.testing.expectEqual(v.*, Vector{ .x = 5, .y = 6, .z = 8 }); v.*.x = 666; var v2 = store.get(Vector); - std.testing.expectEqual(v2.*, Vector{.x = 666, .y = 6, .z = 8}); + std.testing.expectEqual(v2.*, Vector{ .x = 666, .y = 6, .z = 8 }); store.remove(Vector); std.testing.expect(!store.has(Vector)); @@ -86,4 +86,4 @@ test "TypeStore" { var v4 = store.get(u32); std.testing.expectEqual(v3.*, 777); -} \ No newline at end of file +} diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 87cc054..0f7cb33 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -41,12 +41,12 @@ pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T /// comptime string hashing for the type names pub fn typeId(comptime T: type) u32 { - return hashStringFnv(u32, @typeName(T)); + comptime return hashStringFnv(u32, @typeName(T)); } /// comptime string hashing for the type names pub fn typeId64(comptime T: type) u64 { - return hashStringFnv(u64, @typeName(T)); + comptime return hashStringFnv(u64, @typeName(T)); } /// u32 Fowler-Noll-Vo string hash diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index bec5c88..a730451 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -77,9 +77,9 @@ test "singletons" { defer reg.deinit(); var pos = Position{ .x = 5, .y = 5 }; - var inserted = reg.singletons.add(pos); + reg.singletons.add(pos); std.testing.expect(reg.singletons.has(Position)); - std.testing.expectEqual(inserted.*, pos); + std.testing.expectEqual(reg.singletons.get(Position).*, pos); reg.singletons.remove(Position); std.testing.expect(!reg.singletons.has(Position)); From 917ca5ffdc476123c28e610b1b8b3868cedd45a6 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 7 Jun 2020 17:28:49 -0700 Subject: [PATCH 026/146] allow inserts --- zig-ecs/src/signals/signal.zig | 16 +++++-- zig-ecs/src/signals/sink.zig | 88 ++++++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 13 deletions(-) diff --git a/zig-ecs/src/signals/signal.zig b/zig-ecs/src/signals/signal.zig index c14bf8b..98e2368 100644 --- a/zig-ecs/src/signals/signal.zig +++ b/zig-ecs/src/signals/signal.zig @@ -77,9 +77,6 @@ test "Signal/Sink" { sink.connect(tester); std.testing.expectEqual(@as(usize, 1), signal.size()); - sink.connect(tester); - std.testing.expectEqual(@as(usize, 1), signal.size()); - // bound listener var thing = Thing{}; sink.connectBound(&thing, "tester"); @@ -93,3 +90,16 @@ test "Signal/Sink" { sink.disconnectBound(&thing); std.testing.expectEqual(@as(usize, 0), signal.size()); } + +test "Sink Before null" { + var signal = Signal(u32).init(std.testing.allocator); + defer signal.deinit(); + + var sink = signal.sink(); + sink.connect(tester); + std.testing.expectEqual(@as(usize, 1), signal.size()); + + var thing = Thing{}; + sink.before(null).connectBound(&thing, "tester"); + std.testing.expectEqual(@as(usize, 2), signal.size()); +} diff --git a/zig-ecs/src/signals/sink.zig b/zig-ecs/src/signals/sink.zig index d915ab9..b4a6986 100644 --- a/zig-ecs/src/signals/sink.zig +++ b/zig-ecs/src/signals/sink.zig @@ -8,40 +8,108 @@ pub fn Sink(comptime Event: type) type { return struct { const Self = @This(); + insert_index: usize, + /// the Signal this Sink is temporarily wrapping var owning_signal: *Signal(Event) = undefined; pub fn init(signal: *Signal(Event)) Self { owning_signal = signal; - return Self{}; + return Self{ .insert_index = owning_signal.calls.items.len }; + } + + pub fn before(self: Self, callback: ?fn (Event) void) Self { + if (callback) |cb| { + if (self.indexOf(cb)) |index| { + return Self{ .insert_index = index }; + } + } + return self; + } + + pub fn beforeBound(self: Self, ctx: var) Self { + if (@typeInfo(@TypeOf(ctx)) == .Pointer) { + if (self.indexOfBound(ctx)) |index| { + return Self{ .insert_index = index }; + } + } + return self; } pub fn connect(self: Self, callback: fn (Event) void) void { - self.disconnect(callback); - _ = owning_signal.calls.append(Delegate(Event).initFree(callback)) catch unreachable; + std.debug.assert(self.indexOf(callback) == null); + _ = owning_signal.calls.insert(self.insert_index, Delegate(Event).initFree(callback)) catch unreachable; } pub fn connectBound(self: Self, ctx: var, comptime fn_name: []const u8) void { - self.disconnectBound(ctx); - _ = owning_signal.calls.append(Delegate(Event).initBound(ctx, fn_name)) catch unreachable; + std.debug.assert(self.indexOfBound(ctx) == null); + _ = owning_signal.calls.insert(self.insert_index, Delegate(Event).initBound(ctx, fn_name)) catch unreachable; } pub fn disconnect(self: Self, callback: fn (Event) void) void { + if (self.indexOf(callback)) |index| { + _ = owning_signal.calls.swapRemove(index); + } + } + + pub fn disconnectBound(self: Self, ctx: var) void { + if (self.indexOfBound(ctx)) |index| { + _ = owning_signal.calls.swapRemove(index); + } + } + + fn indexOf(self: Self, callback: fn (Event) void) ?usize { for (owning_signal.calls.items) |call, i| { if (call.containsFree(callback)) { - _ = owning_signal.calls.swapRemove(i); - break; + return i; } } + return null; } - pub fn disconnectBound(self: Self, ctx: var) void { + fn indexOfBound(self: Self, ctx: var) ?usize { for (owning_signal.calls.items) |call, i| { if (call.containsBound(ctx)) { - _ = owning_signal.calls.swapRemove(i); - break; + return i; } } + return null; } }; } + +fn tester(param: u32) void { + std.testing.expectEqual(@as(u32, 666), param); +} + +const Thing = struct { + field: f32 = 0, + + pub fn tester(self: *Thing, param: u32) void { + std.testing.expectEqual(@as(u32, 666), param); + } +}; + +test "Sink Before free" { + var signal = Signal(u32).init(std.testing.allocator); + defer signal.deinit(); + + signal.sink().connect(tester); + std.testing.expectEqual(signal.sink().indexOf(tester).?, 0); + + var thing = Thing{}; + signal.sink().before(tester).connectBound(&thing, "tester"); + std.testing.expectEqual(signal.sink().indexOfBound(&thing).?, 0); +} + +test "Sink Before bound" { + var signal = Signal(u32).init(std.testing.allocator); + defer signal.deinit(); + + var thing = Thing{}; + signal.sink().connectBound(&thing, "tester"); + std.testing.expectEqual(signal.sink().indexOfBound(&thing).?, 0); + + signal.sink().beforeBound(&thing).connect(tester); + std.testing.expectEqual(signal.sink().indexOf(tester).?, 0); +} \ No newline at end of file From 8f401ec4913ea79126d7c04ef029126fb7bf1cf3 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 7 Jun 2020 17:29:03 -0700 Subject: [PATCH 027/146] big work on owned groups --- zig-ecs/src/ecs/registry.zig | 214 +++++++++++++++++++++++++++------ zig-ecs/src/ecs/sparse_set.zig | 1 + 2 files changed, 180 insertions(+), 35 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 0b16b44..842d63e 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -28,7 +28,6 @@ pub fn Storage(comptime CompT: type) type { /// the registry is the main gateway to all ecs functionality. It assumes all internal allocations will succeed and returns /// no errors to keep the API clean and because if a component array cant be allocated you've got bigger problems. -/// Stores a maximum of u8 (256) component Storage(T). pub const Registry = struct { handles: EntityHandles, components: std.AutoHashMap(u32, usize), @@ -39,21 +38,23 @@ pub const Registry = struct { /// internal, persistant data structure to manage the entities in a group const GroupData = struct { - hash: u32, - entity_set: SparseSet(Entity) = undefined, /// optional. there will be an entity_set for non-owning groups and current for owning + hash: u64, + size: u8, + /// optional. there will be an entity_set for non-owning groups and current for owning + entity_set: SparseSet(Entity) = undefined, 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); - std.debug.assert(std.mem.indexOfAny(u32, owned, exclude) == null); - std.debug.assert(std.mem.indexOfAny(u32, include, exclude) == null); - + pub fn initPtr(allocator: *std.mem.Allocator, registry: *Registry, hash: u64, 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); var group_data = allocator.create(GroupData) catch unreachable; group_data.hash = hash; + group_data.size = @intCast(u8, owned.len + include.len + exclude.len); if (owned.len == 0) { group_data.entity_set = SparseSet(Entity).init(allocator); } @@ -107,6 +108,7 @@ pub const Registry = struct { const ptr = self.registry.components.getValue(self.owned[0]).?; if (!(@intToPtr(*Storage(u1), ptr).set.index(entity) < self.current)) { for (self.owned) |tid| { + // store.swap hides a safe version that types it correctly const store_ptr = self.registry.components.getValue(tid).?; var store = @intToPtr(*Storage(u1), store_ptr); store.swap(store.data().*[self.current], entity); @@ -135,6 +137,47 @@ pub const Registry = struct { } } } + + /// finds the insertion point for this group by finding anything in the group family (overlapping owned) + /// and finds the least specialized (based on size). This allows the least specialized to update first + /// which ensures more specialized (ie less matches) will always be swapping inside the bounds of + /// the less specialized groups. + fn findInsertionIndex(self: GroupData, groups: []*GroupData) ?usize { + for (groups) |grp, i| { + var overlapping: u8 = 0; + for (grp.owned) |grp_owned| { + if (std.mem.indexOfScalar(u32, self.owned, grp_owned)) |_| overlapping += 1; + } + + if (overlapping > 0 and self.size <= grp.size) return i; + } + + return null; + } + + // TODO: is this the right logic? Should this return just the previous item in the family or be more specific about + // the group size for the index it returns? + /// for discards, the most specialized group in the family needs to do its discard and swap first. This will ensure + /// as each more specialized group does their discards the entity will always remain outside of the "current" index + /// for all groups in the family. + fn findPreviousIndex(self: GroupData, groups: []*GroupData, index: ?usize) ?usize { + if (groups.len == 0) return null; + + // we iterate backwards and either index or groups.len is one tick passed where we want to start + var i = if (index) |ind| ind else groups.len; + if (i > 0) i -= 1; + + while (i >= 0) : (i -= 1) { + var overlapping: u8 = 0; + for (groups[i].owned) |grp_owned| { + if (std.mem.indexOfScalar(u32, self.owned, grp_owned)) |_| overlapping += 1; + } + + if (overlapping > 0) return i; + } + + return null; + } }; pub fn init(allocator: *std.mem.Allocator) Registry { @@ -201,7 +244,7 @@ pub const Registry = struct { /// Direct access to the list of entities of a given pool pub fn data(self: Registry, comptime T: type) []Entity { - return self.assure(T).data(); + return self.assure(T).data().*; } pub fn valid(self: *Registry, entity: Entity) bool { @@ -362,18 +405,23 @@ pub const Registry = struct { return self.singletons; } - /// Checks whether the given component belongs to any group - pub fn sortable(self: Registry, comptime T: type) bool { - return true; + pub fn sort(self: *Registry, comptime T: type) void { + const comp = self.assure(T); + std.debug.assert(comp.super == 0); + unreachable; + } + + /// Checks whether the given component belongs to any group. If so, it is not sortable directly. + pub fn sortable(self: *Registry, comptime T: type) bool { + return self.assure(T).super == 0; } 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))); - if (@typeInfo(@TypeOf(excludes)) != .Struct) - @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(excludes))); + std.debug.assert(@typeInfo(@TypeOf(includes)) == .Struct); + std.debug.assert(@typeInfo(@TypeOf(excludes)) == .Struct); std.debug.assert(includes.len > 0); + // just one include so use the optimized BasicView if (includes.len == 1 and excludes.len == 0) return BasicView(includes[0]).init(self.assure(includes[0])); @@ -398,13 +446,12 @@ pub const Registry = struct { return MultiView(includes.len, excludes.len); } + /// creates an optimized group for iterating components. Note that types are ORDER DEPENDENDANT for now, so always pass component + /// types in the same order. 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(@typeInfo(@TypeOf(owned)) == .Struct); + std.debug.assert(@typeInfo(@TypeOf(includes)) == .Struct); + std.debug.assert(@typeInfo(@TypeOf(excludes)) == .Struct); std.debug.assert(owned.len + includes.len > 0); std.debug.assert(owned.len + includes.len + excludes.len > 1); @@ -428,9 +475,11 @@ pub const Registry = struct { // create a unique hash to identify the group var maybe_group_data: ?*GroupData = null; - comptime const hash = owned.len + (31 * includes.len) + (31 * 31 * excludes.len); + comptime const hash = comptime hashGroupTypes(owned, includes, excludes); for (self.groups.items) |grp| { + // TODO: these checks rely on owned/include/exclude to all be in the same order. fix that. + // TODO: prolly dont need the mem.eql since hash is the same damn thing if (grp.hash == hash and std.mem.eql(u32, grp.owned, owned_arr[0..]) and std.mem.eql(u32, grp.include, includes_arr[0..]) and std.mem.eql(u32, grp.exclude, excludes_arr[0..])) { maybe_group_data = grp; break; @@ -443,36 +492,101 @@ 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 { - return OwningGroup(owned.len, includes.len, excludes.len).init(&group_data.current, self, owned_arr, includes_arr, excludes_arr); + var first_owned = self.assure(owned[0]); + return OwningGroup(owned.len, includes.len, excludes.len).init(&first_owned.super, &group_data.current, self, owned_arr, includes_arr, excludes_arr); + } + } + + const size = owned.len + includes.len + excludes.len; + + // before adding the group we need to do some checks to make sure there arent other owning groups with the same types + if (std.builtin.mode == .Debug and owned.len > 0) { + std.debug.warn("\n", .{}); + for (self.groups.items) |grp| { + if (grp.owned.len == 0) continue; + + var overlapping: u8 = 0; + for (grp.owned) |grp_owned| { + if (std.mem.indexOfScalar(u32, &owned_arr, grp_owned)) |_| overlapping += 1; + } + + var sz: u8 = overlapping; + for (grp.include) |grp_include| { + if (std.mem.indexOfScalar(u32, &includes_arr, grp_include)) |_| sz += 1; + } + for (grp.exclude) |grp_exclude| { + if (std.mem.indexOfScalar(u32, &excludes_arr, grp_exclude)) |_| sz += 1; + } + + const check = overlapping == 0 or ((sz == size) or (sz == grp.size)); + std.debug.warn("overlapping: {}, sz: {}, (sz == size): {}, (sz == gdata.size): {}\t--- check: {}\n", .{ overlapping, sz, sz == size, sz == grp.size, check }); + std.debug.assert(check); } } // we need to create a new GroupData 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; + + var maybe_valid_if: ?*GroupData = null; + var discard_if: ?*GroupData = null; + + if (owned.len == 0) { + self.groups.append(new_group_data) catch unreachable; + } else { + // if this is a group in a family, we may need to do an insert so get the insertion index first + const maybe_index = new_group_data.findInsertionIndex(self.groups.items); + + // if there is a previous group in this family, we use it for inserting our discardIf calls + if (new_group_data.findPreviousIndex(self.groups.items, maybe_index)) |prev| { + discard_if = self.groups.items[prev]; + } + + if (maybe_index) |index| { + maybe_valid_if = self.groups.items[index]; + self.groups.insert(index, new_group_data) catch unreachable; + } else { + self.groups.append(new_group_data) catch unreachable; + } + + // update super on all owned Storages to be the max of size and their current super value + inline for (owned) |t| { + var storage = self.assure(t); + storage.super = std.math.max(storage.super, size); + } + } // wire up our listeners - inline for (owned) |t| self.onConstruct(t).connectBound(new_group_data, "maybeValidIf"); - inline for (includes) |t| self.onConstruct(t).connectBound(new_group_data, "maybeValidIf"); - inline for (excludes) |t| self.onDestruct(t).connectBound(new_group_data, "maybeValidIf"); + inline for (owned) |t| self.onConstruct(t).beforeBound(maybe_valid_if).connectBound(new_group_data, "maybeValidIf"); + inline for (includes) |t| self.onConstruct(t).beforeBound(maybe_valid_if).connectBound(new_group_data, "maybeValidIf"); + inline for (excludes) |t| self.onDestruct(t).beforeBound(maybe_valid_if).connectBound(new_group_data, "maybeValidIf"); - inline for (owned) |t| self.onDestruct(t).connectBound(new_group_data, "discardIf"); - inline for (includes) |t| self.onDestruct(t).connectBound(new_group_data, "discardIf"); - inline for (excludes) |t| self.onConstruct(t).connectBound(new_group_data, "discardIf"); + inline for (owned) |t| self.onDestruct(t).beforeBound(discard_if).connectBound(new_group_data, "discardIf"); + inline for (includes) |t| self.onDestruct(t).beforeBound(discard_if).connectBound(new_group_data, "discardIf"); + inline for (excludes) |t| self.onConstruct(t).beforeBound(discard_if).connectBound(new_group_data, "discardIf"); // pre-fill the GroupData with any existing entitites that match if (owned.len == 0) { - var tmp_view = self.view(owned ++ includes, excludes); - var view_iter = tmp_view.iterator(); + var view_iter = self.view(owned ++ includes, excludes).iterator(); while (view_iter.next()) |entity| { new_group_data.entity_set.add(entity); } - } else {} + } else { + // we cannot iterate backwards because we want to leave behind valid entities in case of owned types + // maybeValidIf all the entities in the first owned group + var first_owned_storage = self.assure(owned[0]); + for (first_owned_storage.data().*) |entity| { + new_group_data.maybeValidIf(entity); + } + // for(auto *first = std::get<0>(cpools).data(), *last = first + std::get<0>(cpools).size(); first != last; ++first) { + // handler->template maybe_valid_if...>>>(*this, *first); + // } + } 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); + var first_owned_storage = self.assure(owned[0]); + return OwningGroup(owned.len, includes.len, excludes.len).init(&first_owned_storage.super, &new_group_data.current, self, owned_arr, includes_arr, excludes_arr); } } @@ -481,4 +595,34 @@ pub const Registry = struct { if (owned.len == 0) return BasicGroup(includes.len, excludes.len); return OwningGroup(owned.len, includes.len, excludes.len); } + + /// given the 3 group Types arrays, generates a (mostly) unique u64 hash. Simultaneously ensures there are no duped types. + inline fn hashGroupTypes(comptime owned: var, comptime includes: var, comptime excludes: var) u64 { + comptime { + for (owned) |t1| { + for (includes) |t2| { + std.debug.assert(t1 != t2); + + for (excludes) |t3| { + std.debug.assert(t1 != t3); + std.debug.assert(t2 != t3); + } + } + } + + const owned_str = comptime concatTypes(owned); + const includes_str = comptime concatTypes(includes); + const excludes_str = comptime concatTypes(excludes); + + return utils.hashStringFnv(u64, owned_str ++ includes_str ++ excludes_str); + } + } + + inline fn concatTypes(comptime types: var) []const u8 { + comptime { + comptime var res: []const u8 = ""; + inline for (types) |t| res = res ++ @typeName(t); + return res; + } + } }; diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 3619084..0ef78eb 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -83,6 +83,7 @@ pub fn SparseSet(comptime SparseT: type) type { return self.dense.items.len == 0; } + // TODO: why return a pointer to the slice? pub fn data(self: Self) *const []SparseT { return &self.dense.items; } From 088a11b840496661709b644f620de7991f7f2f95 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 7 Jun 2020 22:10:52 -0700 Subject: [PATCH 028/146] bit of tests for nested groups --- zig-ecs/src/ecs/groups.zig | 2 +- zig-ecs/src/ecs/registry.zig | 11 +++-- zig-ecs/tests/groups_test.zig | 76 +++++++++++++++++++++++++++++++++ zig-ecs/tests/registry_test.zig | 7 +-- zig-ecs/tests/tests.zig | 1 + 5 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 zig-ecs/tests/groups_test.zig diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 3cb682b..95c30f7 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -218,4 +218,4 @@ test "multiple OwningGroups" { // this will break the group // var group6 = reg.group(.{Sprite, Rotation}, .{}, .{}); -} \ No newline at end of file +} diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 842d63e..d79cc60 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -283,6 +283,13 @@ pub const Registry = struct { self.add(entity, value); } + /// adds all the component types passed in as zero-initialized values + pub fn addTypes(self: *Registry, entity: Entity, comptime types: var) void { + inline for (types) |t| { + self.assure(t).add(entity, std.mem.zeroes(t)); + } + } + /// Replaces the given component for an entity pub fn replace(self: *Registry, entity: Entity, value: var) void { assert(self.valid(entity)); @@ -519,7 +526,6 @@ pub const Registry = struct { } const check = overlapping == 0 or ((sz == size) or (sz == grp.size)); - std.debug.warn("overlapping: {}, sz: {}, (sz == size): {}, (sz == gdata.size): {}\t--- check: {}\n", .{ overlapping, sz, sz == size, sz == grp.size, check }); std.debug.assert(check); } } @@ -571,8 +577,7 @@ pub const Registry = struct { new_group_data.entity_set.add(entity); } } else { - // we cannot iterate backwards because we want to leave behind valid entities in case of owned types - // maybeValidIf all the entities in the first owned group + // ??we cannot iterate backwards because we want to leave behind valid entities in case of owned types var first_owned_storage = self.assure(owned[0]); for (first_owned_storage.data().*) |entity| { new_group_data.maybeValidIf(entity); diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig new file mode 100644 index 0000000..ced5870 --- /dev/null +++ b/zig-ecs/tests/groups_test.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const warn = std.debug.warn; +const ecs = @import("ecs"); +const Registry = @import("ecs").Registry; + +const Velocity = struct { x: f32 = 0, y: f32 = 0 }; +const Position = struct { x: f32 = 0, y: f32 = 0 }; +const Empty = struct {}; +const Sprite = struct { x: f32 = 0 }; +const Transform = struct { x: f32 = 0 }; +const Renderable = struct { x: f32 = 0 }; +const Rotation = struct { x: f32 = 0 }; + +fn printStore(store: var, name: []const u8) void { + warn("--- {} ---\n", .{name}); + for (store.set.dense.items) |e, i| { + warn("{:3.0}", .{e}); + warn(" ({d:3.0})", .{store.instances.items[i]}); + } + warn("\n", .{}); +} + +test "nested OwningGroups add/remove components" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var group1 = reg.group(.{Sprite}, .{Renderable}, .{}); + var group2 = reg.group(.{ Sprite, Transform }, .{Renderable}, .{}); + var group3 = reg.group(.{ Sprite, Transform }, .{ Renderable, Rotation }, .{}); + + std.testing.expect(!reg.sortable(Sprite)); + std.testing.expect(!reg.sortable(Transform)); + std.testing.expect(reg.sortable(Renderable)); + + var e1 = reg.create(); + reg.addTypes(e1, .{ Sprite, Renderable, Rotation }); + std.testing.expectEqual(group1.len(), 1); + std.testing.expectEqual(group2.len(), 0); + std.testing.expectEqual(group3.len(), 0); + + reg.add(e1, Transform{}); + std.testing.expectEqual(group3.len(), 1); + + reg.remove(Sprite, e1); + std.testing.expectEqual(group1.len(), 0); + std.testing.expectEqual(group2.len(), 0); + std.testing.expectEqual(group3.len(), 0); +} + +test "nested OwningGroups entity order" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var group1 = reg.group(.{Sprite}, .{Renderable}, .{}); + var group2 = reg.group(.{ Sprite, Transform }, .{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) }); + } + + std.testing.expectEqual(group1.len(), 5); + std.testing.expectEqual(group2.len(), 0); + + var sprite_store = reg.assure(Sprite); + var transform_store = reg.assure(Transform); + printStore(sprite_store, "Sprite"); + + reg.add(1, Transform{.x = 1}); + + printStore(sprite_store, "Sprite"); + printStore(transform_store, "Transform"); + warn("group2.current: {}\n", .{group2.current.*}); +} diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index a730451..b2b7ea8 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -3,7 +3,7 @@ const ecs = @import("ecs"); const Registry = @import("ecs").Registry; const Velocity = struct { x: f32, y: f32 }; -const Position = struct { x: f32, y: f32 }; +const Position = struct { x: f32 = 0, y: f32 = 0 }; const Empty = struct {}; const BigOne = struct { pos: Position, vel: Velocity, accel: Velocity }; @@ -17,11 +17,12 @@ test "Registry" { var e1 = reg.create(); - reg.add(e1, Empty{}); - reg.add(e1, Position{ .x = 5, .y = 5 }); + reg.addTypes(e1, .{Empty, Position}); reg.add(e1, BigOne{ .pos = Position{ .x = 5, .y = 5 }, .vel = Velocity{ .x = 5, .y = 5 }, .accel = Velocity{ .x = 5, .y = 5 } }); std.testing.expect(reg.has(Empty, e1)); + std.testing.expect(reg.has(Position, e1)); + std.testing.expect(reg.has(BigOne, e1)); reg.remove(Empty, e1); std.testing.expect(!reg.has(Empty, e1)); diff --git a/zig-ecs/tests/tests.zig b/zig-ecs/tests/tests.zig index 2f11302..10eaf86 100644 --- a/zig-ecs/tests/tests.zig +++ b/zig-ecs/tests/tests.zig @@ -1,4 +1,5 @@ test "ecs test suite" { _ = @import("dispatcher_test.zig"); _ = @import("registry_test.zig"); + _ = @import("groups_test.zig"); } From 21a51a929866d546c0ef54d39afbe222f4372838 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 8 Jun 2020 11:06:15 -0700 Subject: [PATCH 029/146] order doesnt matter anymore for types cleanup OwningGroup --- zig-ecs/src/ecs/groups.zig | 58 +++++++++++++++-------------------- zig-ecs/src/ecs/registry.zig | 35 ++++++++++++++------- zig-ecs/tests/groups_test.zig | 2 +- 3 files changed, 49 insertions(+), 46 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 95c30f7..8a76b43 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -73,37 +73,27 @@ 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, - super: *usize, - registry: *Registry, - owned_type_ids: [n_owned]u32, - include_type_ids: [n_includes]u32, - exclude_type_ids: [n_excludes]u32, - - pub fn init(super: *usize, 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{ - .super = super, - .current = current, - .registry = registry, - .owned_type_ids = owned_type_ids, - .include_type_ids = include_type_ids, - .exclude_type_ids = exclude_type_ids, - }; - } +pub const OwningGroup = struct { + registry: *Registry, + group_data: *Registry.GroupData, + super: *usize, + + pub fn init(registry: *Registry, group_data: *Registry.GroupData, super: *usize) OwningGroup { + return .{ + .registry = registry, + .group_data = group_data, + .super = super, + }; + } - pub fn len(self: Self) usize { - return self.current.*; - } + pub fn len(self: OwningGroup) usize { + return self.group_data.current; + } - pub fn sortable(self: *Registry, comptime T: type) bool { - return self.super.* == n_owned + n_includes + n_excludes; - } - }; -} + pub fn sortable(self: OwningGroup, comptime T: type) bool { + return self.group_data.super == self.group_data.size; + } +}; test "BasicGroup creation" { var reg = Registry.init(std.testing.allocator); @@ -133,7 +123,7 @@ test "BasicGroup excludes" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); - var group = reg.group(.{}, .{ i32 }, .{ u32 }); + var group = reg.group(.{}, .{i32}, .{u32}); std.testing.expectEqual(group.len(), 0); var e0 = reg.create(); @@ -168,7 +158,7 @@ test "OwningGroup" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); - var group = reg.group(.{i32, u32}, .{}, .{}); + var group = reg.group(.{ i32, u32 }, .{}, .{}); var e0 = reg.create(); reg.add(e0, @as(i32, 44)); @@ -180,7 +170,7 @@ test "OwningGroup add/remove" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); - var group = reg.group(.{i32, u32}, .{}, .{}); + var group = reg.group(.{ i32, u32 }, .{}, .{}); var e0 = reg.create(); reg.add(e0, @as(i32, 44)); @@ -203,9 +193,9 @@ test "multiple OwningGroups" { // var group1 = reg.group(.{u64, u32}, .{}, .{}); // var group2 = reg.group(.{u64, u32, u8}, .{}, .{}); - var group5 = reg.group(.{Sprite, Transform}, .{Renderable, Rotation}, .{}); + var group5 = reg.group(.{ Sprite, Transform }, .{ Renderable, Rotation }, .{}); var group3 = reg.group(.{Sprite}, .{Renderable}, .{}); - var group4 = reg.group(.{Sprite, Transform}, .{Renderable}, .{}); + var group4 = reg.group(.{ Sprite, Transform }, .{Renderable}, .{}); var last_size: u8 = 0; for (reg.groups.items) |grp| { diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index d79cc60..3411c00 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -37,7 +37,7 @@ pub const Registry = struct { allocator: *std.mem.Allocator, /// internal, persistant data structure to manage the entities in a group - const GroupData = struct { + pub const GroupData = struct { hash: u64, size: u8, /// optional. there will be an entity_set for non-owning groups and current for owning @@ -485,9 +485,7 @@ pub const Registry = struct { comptime const hash = comptime hashGroupTypes(owned, includes, excludes); for (self.groups.items) |grp| { - // TODO: these checks rely on owned/include/exclude to all be in the same order. fix that. - // TODO: prolly dont need the mem.eql since hash is the same damn thing - if (grp.hash == hash and std.mem.eql(u32, grp.owned, owned_arr[0..]) and std.mem.eql(u32, grp.include, includes_arr[0..]) and std.mem.eql(u32, grp.exclude, excludes_arr[0..])) { + if (grp.hash == hash) { maybe_group_data = grp; break; } @@ -500,7 +498,7 @@ pub const Registry = struct { return BasicGroup(includes.len, excludes.len).init(&group_data.entity_set, self, includes_arr, excludes_arr); } else { var first_owned = self.assure(owned[0]); - return OwningGroup(owned.len, includes.len, excludes.len).init(&first_owned.super, &group_data.current, self, owned_arr, includes_arr, excludes_arr); + return OwningGroup.init(self, group_data, &first_owned.super); } } @@ -577,7 +575,7 @@ pub const Registry = struct { new_group_data.entity_set.add(entity); } } else { - // ??we cannot iterate backwards because we want to leave behind valid entities in case of owned types + // ??? why not? we cannot iterate backwards because we want to leave behind valid entities in case of owned types var first_owned_storage = self.assure(owned[0]); for (first_owned_storage.data().*) |entity| { new_group_data.maybeValidIf(entity); @@ -591,17 +589,18 @@ pub const Registry = struct { return BasicGroup(includes.len, excludes.len).init(&new_group_data.entity_set, self, includes_arr, excludes_arr); } else { var first_owned_storage = self.assure(owned[0]); - return OwningGroup(owned.len, includes.len, excludes.len).init(&first_owned_storage.super, &new_group_data.current, self, owned_arr, includes_arr, excludes_arr); + return OwningGroup.init(self, new_group_data, &first_owned_storage.super); } } - /// returns the Type that a view will be based on the includes and excludes + /// 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); - return OwningGroup(owned.len, includes.len, excludes.len); + return OwningGroup; } - /// given the 3 group Types arrays, generates a (mostly) unique u64 hash. Simultaneously ensures there are no duped types. + /// given the 3 group Types arrays, generates a (mostly) unique u64 hash. Simultaneously ensures there are no duped types between + /// the 3 groups. inline fn hashGroupTypes(comptime owned: var, comptime includes: var, comptime excludes: var) u64 { comptime { for (owned) |t1| { @@ -623,10 +622,24 @@ pub const Registry = struct { } } + /// expects a tuple of types. Convertes them to type names, sorts them then concatenates and returns the string. inline fn concatTypes(comptime types: var) []const u8 { comptime { + const impl = struct { + fn asc(lhs: []const u8, rhs: []const u8) bool { + return std.mem.lessThan(u8, lhs, rhs); + } + }; + + var names: [types.len][]const u8 = undefined; + for (names) |*name, i| { + name.* = @typeName(types[i]); + } + + std.sort.sort([]const u8, &names, impl.asc); + comptime var res: []const u8 = ""; - inline for (types) |t| res = res ++ @typeName(t); + inline for (names) |name| res = res ++ name; return res; } } diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index ced5870..fda7110 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -72,5 +72,5 @@ test "nested OwningGroups entity order" { printStore(sprite_store, "Sprite"); printStore(transform_store, "Transform"); - warn("group2.current: {}\n", .{group2.current.*}); + warn("group2.current: {}\n", .{group2.group_data.current}); } From 7dd8a28dbeab7b72a640d322ca822e55a1f4b1ba Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 8 Jun 2020 11:27:49 -0700 Subject: [PATCH 030/146] massive group simplification --- zig-ecs/src/ecs/groups.zig | 104 +++++++++++++++++------------------ zig-ecs/src/ecs/registry.zig | 64 ++++++++------------- 2 files changed, 72 insertions(+), 96 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 8a76b43..d2ff8f6 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -7,71 +7,65 @@ const SparseSet = @import("sparse_set.zig").SparseSet; const Entity = @import("registry.zig").Entity; /// BasicGroups do not own any components -pub fn BasicGroup(comptime n_includes: usize, comptime n_excludes: usize) type { - return struct { - const Self = @This(); - - entity_set: *SparseSet(Entity), - registry: *Registry, - type_ids: [n_includes]u32, - exclude_type_ids: [n_excludes]u32, - - pub const Iterator = struct { - group: *Self, - index: usize = 0, - entities: *const []Entity, - - pub fn init(group: *Self) Iterator { - return .{ - .group = group, - .entities = group.entity_set.data(), - }; - } - - 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 const BasicGroup = struct { + const Self = @This(); - pub fn init(entity_set: *SparseSet(Entity), registry: *Registry, type_ids: [n_includes]u32, exclude_type_ids: [n_excludes]u32) Self { - return Self{ - .entity_set = entity_set, - .registry = registry, - .type_ids = type_ids, - .exclude_type_ids = exclude_type_ids, - }; - } + registry: *Registry, + group_data: *Registry.GroupData, - pub fn len(self: Self) usize { - return self.entity_set.len(); - } + pub const Iterator = struct { + group: *Self, + index: usize = 0, + entities: *const []Entity, - /// Direct access to the array of entities - pub fn data(self: Self) *const []Entity { - return self.entity_set.data(); + pub fn init(group: *Self) Iterator { + return .{ + .group = group, + .entities = group.group_data.entity_set.data(), + }; } - pub fn get(self: *Self, comptime T: type, entity: Entity) *T { - return self.registry.assure(T).get(entity); - } + pub fn next(it: *Iterator) ?Entity { + if (it.index >= it.entities.len) return null; - pub fn getConst(self: *Self, comptime T: type, entity: Entity) T { - return self.registry.assure(T).getConst(entity); + it.index += 1; + return it.entities.*[it.index - 1]; } - pub fn iterator(self: *Self) Iterator { - return Iterator.init(self); + // 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, + .group_data = group_data, + }; + } + + pub fn len(self: Self) usize { + return self.group_data.entity_set.len(); + } + + /// Direct access to the array of entities + pub fn data(self: Self) *const []Entity { + return self.group_data.entity_set.data(); + } + + pub fn get(self: *Self, comptime T: type, entity: Entity) *T { + return self.registry.assure(T).get(entity); + } + + pub fn getConst(self: *Self, comptime T: type, entity: Entity) T { + return self.registry.assure(T).getConst(entity); + } + + pub fn iterator(self: *Self) Iterator { + return Iterator.init(self); + } +}; pub const OwningGroup = struct { registry: *Registry, diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 3411c00..cfd7089 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -453,21 +453,28 @@ pub const Registry = struct { return MultiView(includes.len, excludes.len); } - /// creates an optimized group for iterating components. Note that types are ORDER DEPENDENDANT for now, so always pass component - /// types in the same order. - pub fn group(self: *Registry, comptime owned: var, comptime includes: var, comptime excludes: var) GroupType(owned, includes, excludes) { + /// creates an optimized group for iterating components + pub fn group(self: *Registry, comptime owned: var, comptime includes: var, comptime excludes: var) (if (owned.len == 0) BasicGroup else OwningGroup) { std.debug.assert(@typeInfo(@TypeOf(owned)) == .Struct); std.debug.assert(@typeInfo(@TypeOf(includes)) == .Struct); std.debug.assert(@typeInfo(@TypeOf(excludes)) == .Struct); std.debug.assert(owned.len + includes.len > 0); std.debug.assert(owned.len + includes.len + excludes.len > 1); - var owned_arr: [owned.len]u32 = undefined; - inline for (owned) |t, i| { - _ = self.assure(t); - owned_arr[i] = utils.typeId(t); + // create a unique hash to identify the group so that we can look it up + comptime const hash = comptime hashGroupTypes(owned, includes, excludes); + + for (self.groups.items) |grp| { + if (grp.hash == hash) { + if (owned.len == 0) { + return BasicGroup.init(self, grp); + } + var first_owned = self.assure(owned[0]); + return OwningGroup.init(self, grp, &first_owned.super); + } } + // gather up all our Types as typeIds var includes_arr: [includes.len]u32 = undefined; inline for (includes) |t, i| { _ = self.assure(t); @@ -480,33 +487,17 @@ pub const Registry = struct { excludes_arr[i] = utils.typeId(t); } - // create a unique hash to identify the group - var maybe_group_data: ?*GroupData = null; - comptime const hash = comptime hashGroupTypes(owned, includes, excludes); - - for (self.groups.items) |grp| { - if (grp.hash == hash) { - maybe_group_data = grp; - break; - } - } - - // do we already have the GroupData? - if (maybe_group_data) |group_data| { - // non-owning groups - if (owned.len == 0) { - return BasicGroup(includes.len, excludes.len).init(&group_data.entity_set, self, includes_arr, excludes_arr); - } else { - var first_owned = self.assure(owned[0]); - return OwningGroup.init(self, group_data, &first_owned.super); - } + var owned_arr: [owned.len]u32 = undefined; + inline for (owned) |t, i| { + _ = self.assure(t); + owned_arr[i] = utils.typeId(t); } - const size = owned.len + includes.len + excludes.len; + // we need to create a new GroupData + var new_group_data = GroupData.initPtr(self.allocator, self, hash, owned_arr[0..], includes_arr[0..], excludes_arr[0..]); // before adding the group we need to do some checks to make sure there arent other owning groups with the same types if (std.builtin.mode == .Debug and owned.len > 0) { - std.debug.warn("\n", .{}); for (self.groups.items) |grp| { if (grp.owned.len == 0) continue; @@ -523,14 +514,11 @@ pub const Registry = struct { if (std.mem.indexOfScalar(u32, &excludes_arr, grp_exclude)) |_| sz += 1; } - const check = overlapping == 0 or ((sz == size) or (sz == grp.size)); + const check = overlapping == 0 or ((sz == new_group_data.size) or (sz == grp.size)); std.debug.assert(check); } } - // we need to create a new GroupData - var new_group_data = GroupData.initPtr(self.allocator, self, hash, owned_arr[0..], includes_arr[0..], excludes_arr[0..]); - var maybe_valid_if: ?*GroupData = null; var discard_if: ?*GroupData = null; @@ -555,7 +543,7 @@ pub const Registry = struct { // update super on all owned Storages to be the max of size and their current super value inline for (owned) |t| { var storage = self.assure(t); - storage.super = std.math.max(storage.super, size); + storage.super = std.math.max(storage.super, new_group_data.size); } } @@ -586,19 +574,13 @@ pub const Registry = struct { } if (owned.len == 0) { - return BasicGroup(includes.len, excludes.len).init(&new_group_data.entity_set, self, includes_arr, excludes_arr); + return BasicGroup.init(self, new_group_data); } else { var first_owned_storage = self.assure(owned[0]); return OwningGroup.init(self, new_group_data, &first_owned_storage.super); } } - /// 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); - return OwningGroup; - } - /// given the 3 group Types arrays, generates a (mostly) unique u64 hash. Simultaneously ensures there are no duped types between /// the 3 groups. inline fn hashGroupTypes(comptime owned: var, comptime includes: var, comptime excludes: var) u64 { From 81ccd19b65ab616723e6c24e754a24e7f2b4dee8 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 9 Jun 2020 10:21:42 -0700 Subject: [PATCH 031/146] ptr and slice access to entities --- zig-ecs/src/ecs/component_storage.zig | 9 +++++++-- zig-ecs/src/ecs/groups.zig | 24 +++++++++++++----------- zig-ecs/src/ecs/registry.zig | 6 +++--- zig-ecs/src/ecs/sparse_set.zig | 11 +++++++---- zig-ecs/src/ecs/views.zig | 6 +++--- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 9d53f38..6a2556d 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -187,10 +187,15 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { }; /// Direct access to the array of entities - pub fn data(self: Self) *const []EntityT { + pub fn data(self: Self) []const EntityT { return self.set.data(); } + /// Direct access to the array of entities + pub fn dataPtr(self: Self) *const []EntityT { + return self.set.dataPtr(); + } + /// Swaps entities and objects in the internal packed arrays pub fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { self.safe_swap(self, lhs, rhs); @@ -240,7 +245,7 @@ test "iterate" { store.add(5, 66.45); store.add(7, 66.45); - for (store.data().*) |entity, i| { + for (store.data()) |entity, i| { if (i == 0) std.testing.expectEqual(entity, 3); if (i == 1) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index d2ff8f6..5ccaace 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -14,22 +14,18 @@ pub const BasicGroup = struct { group_data: *Registry.GroupData, pub const Iterator = struct { - group: *Self, index: usize = 0, - entities: *const []Entity, + entities: []const Entity, - pub fn init(group: *Self) Iterator { - return .{ - .group = group, - .entities = group.group_data.entity_set.data(), - }; + 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]; + return it.entities[it.index - 1]; } // Reset the iterator to the initial index @@ -50,7 +46,7 @@ pub const BasicGroup = struct { } /// Direct access to the array of entities - pub fn data(self: Self) *const []Entity { + pub fn data(self: Self) []const Entity { return self.group_data.entity_set.data(); } @@ -63,7 +59,7 @@ pub const BasicGroup = struct { } pub fn iterator(self: *Self) Iterator { - return Iterator.init(self); + return Iterator.init(self.group_data.entity_set.data()); } }; @@ -89,7 +85,7 @@ pub const OwningGroup = struct { } }; -test "BasicGroup creation" { +test "BasicGroup creation/iteration" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); @@ -109,6 +105,12 @@ test "BasicGroup creation" { } std.testing.expectEqual(iterated_entities, 1); + iterated_entities = 0; + for (group.data()) |entity| { + iterated_entities += 1; + } + std.testing.expectEqual(iterated_entities, 1); + reg.remove(i32, e0); std.debug.assert(group.len() == 0); } diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index cfd7089..0b6b6c8 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -111,7 +111,7 @@ pub const Registry = struct { // store.swap hides a safe version that types it correctly const store_ptr = self.registry.components.getValue(tid).?; var store = @intToPtr(*Storage(u1), store_ptr); - store.swap(store.data().*[self.current], entity); + store.swap(store.data()[self.current], entity); } self.current += 1; } @@ -132,7 +132,7 @@ pub const Registry = struct { for (self.owned) |tid| { const store_ptr = self.registry.components.getValue(tid).?; store = @intToPtr(*Storage(u1), store_ptr); - store.swap(store.data().*[self.current], entity); + store.swap(store.data()[self.current], entity); } } } @@ -565,7 +565,7 @@ pub const Registry = struct { } else { // ??? why not? we cannot iterate backwards because we want to leave behind valid entities in case of owned types 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); } // for(auto *first = std::get<0>(cpools).data(), *last = first + std::get<0>(cpools).size(); first != last; ++first) { diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 0ef78eb..91d3cbb 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -83,8 +83,11 @@ pub fn SparseSet(comptime SparseT: type) type { return self.dense.items.len == 0; } - // TODO: why return a pointer to the slice? - pub fn data(self: Self) *const []SparseT { + pub fn data(self: Self) []const SparseT { + return self.dense.items; + } + + pub fn dataPtr(self: Self) *const []SparseT { return &self.dense.items; } @@ -234,12 +237,12 @@ test "data() synced" { set.add(3); var data = set.data(); - std.testing.expectEqual(data.*[1], 1); + std.testing.expectEqual(data[1], 1); std.testing.expectEqual(set.len(), data.len); set.remove(0); set.remove(1); - std.testing.expectEqual(set.len(), data.len); + std.testing.expectEqual(set.len(), set.data().len); } test "respect" { diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index 481112a..0901b73 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -28,7 +28,7 @@ pub fn BasicView(comptime T: type) type { } /// Direct access to the array of entities - pub fn data(self: Self) *const []Entity { + pub fn data(self: Self) []const Entity { return self.storage.data(); } @@ -60,7 +60,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { const ptr = view.registry.components.getValue(view.type_ids[0]).?; return .{ .view = view, - .entities = @intToPtr(*Storage(u8), ptr).data(), + .entities = @intToPtr(*Storage(u8), ptr).dataPtr(), }; } @@ -161,7 +161,7 @@ test "single basic view data" { std.testing.expectEqual(view.get(3).*, 30); - for (view.data().*) |entity, i| { + for (view.data()) |entity, i| { if (i == 0) std.testing.expectEqual(entity, 3); if (i == 1) From 2561b78a5f711760ceb4d5ccf55e4f3f67d24b84 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 9 Jun 2020 14:35:36 -0700 Subject: [PATCH 032/146] owning group iterator monster --- zig-ecs/src/ecs/groups.zig | 148 ++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 5ccaace..1f35263 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -13,9 +13,10 @@ pub const BasicGroup = struct { 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, + entities: []const Entity, // TODO: should this be a pointer to the slice? pub fn init(entities: []const Entity) Iterator { return .{ .entities = entities }; @@ -68,6 +69,58 @@ pub const OwningGroup = struct { group_data: *Registry.GroupData, super: *usize, + fn Iterator(comptime Components: var) type { + return struct { + index: usize = 0, + group: OwningGroup, + storage: *Storage(u1), + component_ptrs: [@typeInfo(Components).Struct.fields.len][*]u8, + + pub fn init(group: OwningGroup) @This() { + const component_info = @typeInfo(Components).Struct; + + // get the data pointers for the chunks + var component_ptrs: [component_info.fields.len][*]u8 = undefined; + inline for (component_info.fields) |field, i| { + const storage = group.registry.assure(field.field_type.Child); + component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); + } + + return .{ + .group = group, + .storage = group.firstOwnedStorage(), + .component_ptrs = component_ptrs, + }; + } + + pub fn next(it: *@This()) ?Components { + if (it.index >= it.group.group_data.current) return null; + + 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; + inline for (@typeInfo(Components).Struct.fields) |field, i| { + const typed_ptr = @ptrCast([*]field.field_type.Child, @alignCast(@alignOf(field.field_type.Child), it.component_ptrs[i])); + @field(comps, field.name) = &typed_ptr[entity_index]; + } + return comps; + } + + 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]; + } + + // Reset the iterator to the initial index + pub fn reset(it: *@This()) void { + it.index = 0; + } + }; + } + pub fn init(registry: *Registry, group_data: *Registry.GroupData, super: *usize) OwningGroup { return .{ .registry = registry, @@ -76,13 +129,69 @@ pub const OwningGroup = struct { }; } + fn firstOwnedStorage(self: OwningGroup) *Storage(u1) { + const ptr = self.registry.components.getValue(self.group_data.owned[0]).?; + return @intToPtr(*Storage(u1), ptr); + } + pub fn len(self: OwningGroup) usize { return self.group_data.current; } + /// direct access to the array of entities of the first owning group + pub fn data(self: OwningGroup) []const Entity { + return self.firstOwnedStorage().data(); + } + + pub fn contains(self: OwningGroup, entity: Entity) bool { + var storage = self.firstOwnedStorage(); + return storage.contains(entity) and storage.set.index(entity) < self.len(); + } + + pub fn getOwned(self: OwningGroup, entity: Entity, comptime Components: var) Components { + // TODO: validate that we have a struct + // TODO: validate that all fields are pointers + // TODO: validate that all fields are owned + const component_info = @typeInfo(Components).Struct; + + // get the data pointers for the chunks + var component_ptrs: [component_info.fields.len][*]u8 = undefined; + inline for (component_info.fields) |field, i| { + const storage = self.registry.assure(field.field_type.Child); + component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); + } + + // fill the struct + const index = self.firstOwnedStorage().set.index(entity); + var comps: Components = undefined; + inline for (component_info.fields) |field, i| { + const typed_ptr = @ptrCast([*]field.field_type.Child, @alignCast(@alignOf(field.field_type.Child), component_ptrs[i])); + @field(comps, field.name) = &typed_ptr[index]; + } + + return comps; + } + + /// returns the component storage for the given type for direct access + 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 { + return self.registry.assure(T).get(entity); + } + + pub fn getConst(self: *OwningGroup, comptime T: type, entity: Entity) T { + return self.registry.assure(T).getConst(entity); + } + pub fn sortable(self: OwningGroup, comptime T: type) bool { return self.group_data.super == self.group_data.size; } + + pub fn iterator(self: OwningGroup, comptime Components: var) Iterator(Components) { + return Iterator(Components).init(self); + } }; test "BasicGroup creation/iteration" { @@ -160,6 +269,18 @@ test "OwningGroup" { reg.add(e0, @as(i32, 44)); reg.add(e0, @as(u32, 55)); std.testing.expectEqual(group.len(), 1); + std.testing.expect(group.contains(e0)); + + std.testing.expectEqual(group.get(i32, e0).*, 44); + std.testing.expectEqual(group.getConst(u32, e0), 55); + + var vals = group.getOwned(e0, struct { int: *i32, uint: *u32 }); + std.testing.expectEqual(vals.int.*, 44); + std.testing.expectEqual(vals.uint.*, 55); + + vals.int.* = 666; + var vals2 = group.getOwned(e0, struct { int: *i32, uint: *u32 }); + std.testing.expectEqual(vals2.int.*, 666); } test "OwningGroup add/remove" { @@ -177,6 +298,31 @@ test "OwningGroup add/remove" { std.testing.expectEqual(group.len(), 0); } +test "OwningGroup iterate" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var e0 = reg.create(); + reg.add(e0, @as(i32, 44)); + reg.add(e0, @as(u32, 55)); + + var e1 = reg.create(); + reg.add(e1, @as(i32, 666)); + reg.add(e1, @as(u32, 999)); + + var group = reg.group(.{ i32, u32 }, .{}, .{}); + var iter = group.iterator(struct { int: *i32, uint: *u32 }); + while (iter.next()) |item| { + if (iter.entity() == 0) { + std.testing.expectEqual(item.int.*, 44); + std.testing.expectEqual(item.uint.*, 55); + } else { + std.testing.expectEqual(item.int.*, 666); + std.testing.expectEqual(item.uint.*, 999); + } + } +} + test "multiple OwningGroups" { const Sprite = struct { x: f32 }; const Transform = struct { x: f32 }; From 3c57820e02c28735b4627b3d2f7a5ab8d3a84140 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 9 Jun 2020 16:21:26 -0700 Subject: [PATCH 033/146] implemented each for owned groups --- zig-ecs/src/ecs/groups.zig | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 1f35263..0529570 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -172,6 +172,38 @@ pub const OwningGroup = struct { return comps; } + pub fn each(self: OwningGroup, comptime func: var) void { + const Components = switch (@typeInfo(@TypeOf(func))) { + .BoundFn => |func_info| func_info.args[1].arg_type.?, + .Fn => |func_info| func_info.args[0].arg_type.?, + else => std.debug.assert("invalid func"), + }; + + const component_info = @typeInfo(Components).Struct; + + // get the data pointers for the chunks + var component_ptrs: [component_info.fields.len][*]u8 = undefined; + inline for (component_info.fields) |field, i| { + const storage = self.registry.assure(field.field_type.Child); + component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); + } + + var storage = self.firstOwnedStorage(); + var index: usize = 0; + while (index < self.group_data.current) : (index += 1) { + const ent = storage.set.dense.items[index]; + const entity_index = storage.set.index(ent); + + var comps: Components = undefined; + inline for (component_info.fields) |field, i| { + const typed_ptr = @ptrCast([*]field.field_type.Child, @alignCast(@alignOf(field.field_type.Child), component_ptrs[i])); + @field(comps, field.name) = &typed_ptr[entity_index]; + } + + @call(.{ .modifier = .always_inline }, func, .{comps}); + } + } + /// returns the component storage for the given type for direct access pub fn getStorage(self: *OwningGroup, comptime T: type) *Storage(T) { return self.registry.assure(T); @@ -323,6 +355,38 @@ test "OwningGroup iterate" { } } +fn each(components: struct { + int: *i32, + uint: *u32, +}) void { + std.testing.expectEqual(components.int.*, 44); + std.testing.expectEqual(components.uint.*, 55); +} + +test "OwningGroup each" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); + + var e0 = reg.create(); + reg.add(e0, @as(i32, 44)); + reg.add(e0, @as(u32, 55)); + + const Thing = struct { + fn each(self: @This(), components: struct { + int: *i32, + uint: *u32, + }) void { + std.testing.expectEqual(components.int.*, 44); + std.testing.expectEqual(components.uint.*, 55); + } + }; + var thing = Thing{}; + + var group = reg.group(.{ i32, u32 }, .{}, .{}); + group.each(thing.each); + group.each(each); +} + test "multiple OwningGroups" { const Sprite = struct { x: f32 }; const Transform = struct { x: f32 }; From c9d228bf19310bf1df0641eadce7664d76f515c1 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 9 Jun 2020 16:34:07 -0700 Subject: [PATCH 034/146] cleaner --- zig-ecs/src/ecs/groups.zig | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 0529570..a630db4 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -179,27 +179,8 @@ pub const OwningGroup = struct { else => std.debug.assert("invalid func"), }; - const component_info = @typeInfo(Components).Struct; - - // get the data pointers for the chunks - var component_ptrs: [component_info.fields.len][*]u8 = undefined; - inline for (component_info.fields) |field, i| { - const storage = self.registry.assure(field.field_type.Child); - component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); - } - - var storage = self.firstOwnedStorage(); - var index: usize = 0; - while (index < self.group_data.current) : (index += 1) { - const ent = storage.set.dense.items[index]; - const entity_index = storage.set.index(ent); - - var comps: Components = undefined; - inline for (component_info.fields) |field, i| { - const typed_ptr = @ptrCast([*]field.field_type.Child, @alignCast(@alignOf(field.field_type.Child), component_ptrs[i])); - @field(comps, field.name) = &typed_ptr[entity_index]; - } - + var iter = self.iterator(Components); + while (iter.next()) |comps| { @call(.{ .modifier = .always_inline }, func, .{comps}); } } From 1d283cfdddba5e8e03ad066924832e00d6a59185 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 9 Jun 2020 16:37:53 -0700 Subject: [PATCH 035/146] include both! --- zig-ecs/src/ecs/groups.zig | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index a630db4..2c61038 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -179,8 +179,33 @@ pub const OwningGroup = struct { else => std.debug.assert("invalid func"), }; - var iter = self.iterator(Components); - while (iter.next()) |comps| { + // optionally we could just use an Interator here + // var iter = self.iterator(Components); + // while (iter.next()) |comps| { + // @call(.{ .modifier = .always_inline }, func, .{comps}); + // } + + const component_info = @typeInfo(Components).Struct; + + // get the data pointers for the chunks + var component_ptrs: [component_info.fields.len][*]u8 = undefined; + inline for (component_info.fields) |field, i| { + const storage = self.registry.assure(field.field_type.Child); + component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); + } + + var storage = self.firstOwnedStorage(); + var index: usize = 0; + while (index < self.group_data.current) : (index += 1) { + const ent = storage.set.dense.items[index]; + const entity_index = storage.set.index(ent); + + var comps: Components = undefined; + inline for (component_info.fields) |field, i| { + const typed_ptr = @ptrCast([*]field.field_type.Child, @alignCast(@alignOf(field.field_type.Child), component_ptrs[i])); + @field(comps, field.name) = &typed_ptr[entity_index]; + } + @call(.{ .modifier = .always_inline }, func, .{comps}); } } From e32e9069d8b2d76795a350e7ab45e056196bbe92 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 9 Jun 2020 19:22:24 -0700 Subject: [PATCH 036/146] validation --- zig-ecs/src/ecs/groups.zig | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 2c61038..8fe80ef 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -79,7 +79,6 @@ pub const OwningGroup = struct { pub fn init(group: OwningGroup) @This() { const component_info = @typeInfo(Components).Struct; - // get the data pointers for the chunks var component_ptrs: [component_info.fields.len][*]u8 = undefined; inline for (component_info.fields) |field, i| { const storage = group.registry.assure(field.field_type.Child); @@ -129,11 +128,13 @@ pub const OwningGroup = struct { }; } + /// grabs an untyped (u1) reference to the first Storage(T) in the owned array fn firstOwnedStorage(self: OwningGroup) *Storage(u1) { const ptr = self.registry.components.getValue(self.group_data.owned[0]).?; return @intToPtr(*Storage(u1), ptr); } + /// total number of entities in the group pub fn len(self: OwningGroup) usize { return self.group_data.current; } @@ -148,13 +149,20 @@ pub const OwningGroup = struct { return storage.contains(entity) and storage.set.index(entity) < self.len(); } + fn validate(self: OwningGroup, comptime Components: var) void { + std.debug.assert(@typeInfo(Components) == .Struct); + + inline for (@typeInfo(Components).Struct.fields) |field| { + std.debug.assert(@typeInfo(field.field_type) == .Pointer); + const found = std.mem.indexOfScalar(u32, self.group_data.owned, utils.typeId(std.meta.Child(field.field_type))); + std.debug.assert(found != null); + } + } + pub fn getOwned(self: OwningGroup, entity: Entity, comptime Components: var) Components { - // TODO: validate that we have a struct - // TODO: validate that all fields are pointers - // TODO: validate that all fields are owned + self.validate(Components); const component_info = @typeInfo(Components).Struct; - // get the data pointers for the chunks var component_ptrs: [component_info.fields.len][*]u8 = undefined; inline for (component_info.fields) |field, i| { const storage = self.registry.assure(field.field_type.Child); @@ -178,8 +186,9 @@ pub const OwningGroup = struct { .Fn => |func_info| func_info.args[0].arg_type.?, else => std.debug.assert("invalid func"), }; + self.validate(Components); - // optionally we could just use an Interator here + // optionally we could just use an Iterator here and pay for some slight indirection for code sharing // var iter = self.iterator(Components); // while (iter.next()) |comps| { // @call(.{ .modifier = .always_inline }, func, .{comps}); @@ -187,7 +196,7 @@ pub const OwningGroup = struct { const component_info = @typeInfo(Components).Struct; - // get the data pointers for the chunks + // get the data pointers for the requested component types var component_ptrs: [component_info.fields.len][*]u8 = undefined; inline for (component_info.fields) |field, i| { const storage = self.registry.assure(field.field_type.Child); @@ -223,11 +232,14 @@ pub const OwningGroup = struct { return self.registry.assure(T).getConst(entity); } - pub fn sortable(self: OwningGroup, comptime T: type) bool { + pub fn sortable(self: OwningGroup) bool { 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 + /// 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); } }; From 896095fc1595c59c244edf9235d24e7edea9fc61 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 10 Jun 2020 13:10:56 -0700 Subject: [PATCH 037/146] everything iterates backwards --- zig-ecs/src/ecs/groups.zig | 74 +++++++++++++++++----------------- zig-ecs/src/ecs/sparse_set.zig | 26 +++++++++++- zig-ecs/src/ecs/utils.zig | 43 +++++++++++++++++++- zig-ecs/src/ecs/views.zig | 52 +++++++++++++++++++----- zig-ecs/tests/groups_test.zig | 8 ++-- 5 files changed, 149 insertions(+), 54 deletions(-) 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}); } From f0c711123e5c540182e6c0f2e99e21babc33a753 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 10 Jun 2020 16:01:09 -0700 Subject: [PATCH 038/146] debug only --- zig-ecs/src/ecs/groups.zig | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index f1de8e0..d80a2f6 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -139,12 +139,14 @@ pub const OwningGroup = struct { } fn validate(self: OwningGroup, comptime Components: var) void { - std.debug.assert(@typeInfo(Components) == .Struct); + if (std.builtin.mode == .Debug and self.group_data.owned.len > 0) { + std.debug.assert(@typeInfo(Components) == .Struct); - inline for (@typeInfo(Components).Struct.fields) |field| { - std.debug.assert(@typeInfo(field.field_type) == .Pointer); - const found = std.mem.indexOfScalar(u32, self.group_data.owned, utils.typeId(std.meta.Child(field.field_type))); - std.debug.assert(found != null); + inline for (@typeInfo(Components).Struct.fields) |field| { + std.debug.assert(@typeInfo(field.field_type) == .Pointer); + const found = std.mem.indexOfScalar(u32, self.group_data.owned, utils.typeId(std.meta.Child(field.field_type))); + std.debug.assert(found != null); + } } } From f2a302b7a451b729173f02b6bda33b8c707acc1b Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 11 Jun 2020 17:16:28 -0700 Subject: [PATCH 039/146] cleaning --- zig-ecs/src/ecs/component_storage.zig | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 6a2556d..954241a 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -84,8 +84,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { store.safe_swap = struct { fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { - if (!is_empty_struct) + 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; @@ -103,8 +104,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { self.update.deinit(); self.destruction.deinit(); - if (self.allocator) |allocator| + if (self.allocator) |allocator| { allocator.destroy(self); + } } pub fn onConstruct(self: *Self) Sink(EntityT) { @@ -122,14 +124,16 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { /// Increases the capacity of a component storage pub fn reserve(self: *Self, cap: usize) void { self.set.reserve(cap); - if (!is_empty_struct) - self.instances.items.reserve(cap); + if (!is_empty_struct) { + elf.instances.items.reserve(cap); + } } /// Assigns an entity to a storage and assigns its object pub fn add(self: *Self, entity: EntityT, value: CompT) void { - if (!is_empty_struct) + if (!is_empty_struct) { _ = self.instances.append(value) catch unreachable; + } self.set.add(entity); self.construction.publish(entity); } @@ -137,8 +141,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { /// Removes an entity from a storage pub fn remove(self: *Self, entity: EntityT) void { self.destruction.publish(entity); - if (!is_empty_struct) + if (!is_empty_struct) { _ = self.instances.swapRemove(self.set.index(entity)); + } self.set.remove(entity); } From bdb4b0537d515660b96775133c0a874ecfe8be16 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 11 Jun 2020 22:28:29 -0700 Subject: [PATCH 040/146] udpate to Zig master --- zig-ecs/src/ecs.zig | 2 + zig-ecs/src/ecs/component_storage.zig | 107 ++++++++++++++++++++++---- zig-ecs/src/ecs/registry.zig | 8 +- zig-ecs/src/ecs/sparse_set.zig | 26 +++++-- zig-ecs/src/ecs/utils.zig | 4 +- zig-ecs/tests/groups_test.zig | 33 +++++++- 6 files changed, 151 insertions(+), 29 deletions(-) diff --git a/zig-ecs/src/ecs.zig b/zig-ecs/src/ecs.zig index 62c55e1..5fc134a 100644 --- a/zig-ecs/src/ecs.zig +++ b/zig-ecs/src/ecs.zig @@ -1,6 +1,8 @@ // ecs pub const EntityTraitsType = @import("ecs/entity.zig").EntityTraitsType; +pub const ComponentStorage = @import("ecs/component_storage.zig").ComponentStorage; + pub const Entity = @import("ecs/registry.zig").Entity; pub const Registry = @import("ecs/registry.zig").Registry; pub const BasicView = @import("ecs/views.zig").BasicView; diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 954241a..de9ee8f 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -17,9 +17,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { // non-zero sized type. That will make is_empty_struct false in deinit always so we can't use it. Instead, we stick // a small dummy struct in the instances ArrayList so it can safely be deallocated. // Perhaps we should just allocate instances with a dummy allocator or the tmp allocator? - comptime var CompOrAlmostEmptyT = CompT; - if (is_empty_struct) - CompOrAlmostEmptyT = struct { dummy: u1 }; + comptime var CompOrAlmostEmptyT = if (is_empty_struct) struct { dummy: u1 } else CompT; return struct { const Self = @This(); @@ -27,7 +25,8 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { set: *SparseSet(EntityT), instances: std.ArrayList(CompOrAlmostEmptyT), allocator: ?*std.mem.Allocator, - super: usize = 0, /// doesnt really belong here...used to denote group ownership + /// doesnt really belong here...used to denote group ownership + super: usize = 0, safe_deinit: fn (*Self) void, safe_swap: fn (*Self, EntityT, EntityT) void, construction: Signal(EntityT), @@ -40,14 +39,16 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { .instances = undefined, .safe_deinit = struct { fn deinit(self: *Self) void { - if (!is_empty_struct) + if (!is_empty_struct) { self.instances.deinit(); + } } }.deinit, .safe_swap = struct { fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { - if (!is_empty_struct) + 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, @@ -57,8 +58,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { .destruction = Signal(EntityT).init(allocator), }; - if (!is_empty_struct) + if (!is_empty_struct) { store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); + } return store; } @@ -66,8 +68,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { pub fn initPtr(allocator: *std.mem.Allocator) *Self { var store = allocator.create(Self) catch unreachable; store.set = SparseSet(EntityT).initPtr(allocator); - if (!is_empty_struct) + if (!is_empty_struct) { store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); + } store.allocator = allocator; store.super = 0; store.construction = Signal(EntityT).init(allocator); @@ -77,8 +80,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { // since we are stored as a pointer, we need to catpure this store.safe_deinit = struct { fn deinit(self: *Self) void { - if (!is_empty_struct) + if (!is_empty_struct) { self.instances.deinit(); + } } }.deinit; @@ -157,7 +161,12 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { } pub usingnamespace if (is_empty_struct) - struct {} + struct { + /// Sort Entities according to the given comparison function + pub fn sort(self: Self, comptime sortFn: fn (void, EntityT, EntityT) bool) void { + self.set.sort(sortFn); + } + } else struct { /// Direct access to the array of objects @@ -189,6 +198,21 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { pub fn tryGetConst(self: *Self, entity: EntityT) ?CompT { 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, comptime sortFn: fn (void, T, T) bool) void { + std.debug.assert(T == EntityT or T == CompT); + if (T == EntityT) { + self.set.sortSub(sortFn, CompT, self.instances.items); + } else if (T == CompT) { + // essentially need to be able to call a sort method with a bound fn. That fn would then use sortFn along + // with self.instances. + // 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)])); + } + } }; /// Direct access to the array of entities @@ -207,8 +231,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { } pub fn clear(self: *Self) void { - if (!is_empty_struct) + if (!is_empty_struct) { self.instances.items.len = 0; + } self.set.clear(); } }; @@ -251,12 +276,15 @@ test "iterate" { store.add(7, 66.45); for (store.data()) |entity, i| { - if (i == 0) + if (i == 0) { std.testing.expectEqual(entity, 3); - if (i == 1) + } + if (i == 1) { std.testing.expectEqual(entity, 5); - if (i == 2) + } + if (i == 2) { std.testing.expectEqual(entity, 7); + } } } @@ -300,3 +328,54 @@ test "signals" { store.replace(4, 45.64); store.remove(4); } + +const asc_u32 = std.sort.asc(u32); +const desc_u32 = std.sort.desc(u32); + +test "sort empty component" { + const Empty = struct {}; + + var store = ComponentStorage(Empty, u32).initPtr(std.testing.allocator); + defer store.deinit(); + + store.add(1, Empty{}); + store.add(2, Empty{}); + store.add(0, Empty{}); + + store.sort(asc_u32); + for (store.data()) |e, i| { + std.testing.expectEqual(@intCast(u32, i), e); + } + + store.sort(desc_u32); + var counter: u32 = 2; + for (store.data()) |e, i| { + std.testing.expectEqual(counter, e); + if (counter > 0) counter -= 1; + } +} + +const asc_f32 = std.sort.asc(f32); +const desc_f32 = std.sort.desc(f32); + +test "sort component" { + std.debug.warn("\n", .{}); + + var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator); + defer store.deinit(); + + store.add(1, @as(f32, 1.1)); + store.add(2, @as(f32, 2.2)); + store.add(0, @as(f32, 0.0)); + + store.sort(f32, asc_f32); + for (store.raw()) |e, i| { + // std.debug.warn("{}: {}\n", .{i, e}); + // std.testing.expectEqual(@intCast(u32, i), e); + } + + store.sort(f32, desc_f32); + for (store.raw()) |e, i| { + // std.testing.expectEqual(counter, e); + } +} \ No newline at end of file diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 0b6b6c8..3363f87 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -78,7 +78,7 @@ pub const Registry = struct { allocator.destroy(self); } - fn maybeValidIf(self: *GroupData, entity: Entity) void { + pub fn maybeValidIf(self: *GroupData, entity: Entity) void { const isValid: bool = blk: { for (self.owned) |tid| { const ptr = self.registry.components.getValue(tid).?; @@ -120,7 +120,7 @@ pub const Registry = struct { } } - fn discardIf(self: *GroupData, entity: Entity) void { + pub fn discardIf(self: *GroupData, entity: Entity) void { if (self.owned.len == 0) { if (self.entity_set.contains(entity)) self.entity_set.remove(entity); @@ -608,7 +608,7 @@ pub const Registry = struct { inline fn concatTypes(comptime types: var) []const u8 { comptime { const impl = struct { - fn asc(lhs: []const u8, rhs: []const u8) bool { + fn asc(context: void, lhs: []const u8, rhs: []const u8) bool { return std.mem.lessThan(u8, lhs, rhs); } }; @@ -618,7 +618,7 @@ pub const Registry = struct { name.* = @typeName(types[i]); } - std.sort.sort([]const u8, &names, impl.asc); + std.sort.sort([]const u8, &names, {}, impl.asc); comptime var res: []const u8 = ""; inline for (names) |name| res = res ++ name; diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index e39571c..9848973 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 utils = @import("utils.zig"); const ReverseSliceIterator = @import("utils.zig").ReverseSliceIterator; // TODO: fix entity_mask. it should come from EntityTraitsDefinition. @@ -38,7 +39,7 @@ pub fn SparseSet(comptime SparseT: type) type { allocator.destroy(self); } - fn page(self: Self, sparse: SparseT) usize { + pub fn page(self: Self, sparse: SparseT) usize { // TODO: support paging // return (sparse & EntityTraits.entity_mask) / sparse_per_page; return sparse & self.entity_mask; @@ -141,10 +142,19 @@ pub fn SparseSet(comptime SparseT: type) type { } /// Sort elements according to the given comparison function - pub fn sort(self: *Self, sortFn: fn (SparseT, SparseT) bool) void { - std.sort.insertionSort(SparseT, self.dense.items, sortFn); + pub fn sort(self: *Self, comptime sortFn: fn (void, SparseT, SparseT) bool) void { + std.sort.insertionSort(SparseT, self.dense.items, {}, sortFn); + + for (self.dense.items) |sparse| { + // self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, sparse); + self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse); + } + } + + /// 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 { + utils.sortSub(SparseT, T, self.dense.items, sub_items, sortFn); - var i = @as(usize, 0); for (self.dense.items) |sparse| { // self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, sparse); self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse); @@ -259,7 +269,7 @@ test "iterate" { set.add(2); set.add(3); - var i: u32 = @intCast(u32, set.len()) - 1; + var i: u32 = @intCast(u32, set.len()) - 1; var iter = set.reverseIterator(); while (iter.next()) |entity| { std.testing.expectEqual(i, entity); @@ -290,6 +300,8 @@ test "respect 1" { std.testing.expectEqual(set1.dense.items[1], set2.dense.items[2]); } +const desc_u32 = std.sort.desc(u32); + test "respect 2" { var set = SparseSet(u32).initPtr(std.testing.allocator); defer set.deinit(); @@ -300,11 +312,11 @@ test "respect 2" { set.add(1); set.add(3); - set.sort(std.sort.desc(u32)); + set.sort(desc_u32); for (set.dense.items) |item, i| { if (i < set.dense.items.len - 1) { std.debug.assert(item > set.dense.items[i + 1]); } } -} \ No newline at end of file +} diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 0f45efc..770ade1 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -49,13 +49,13 @@ pub fn ReverseSliceIterator(comptime T: type) type { } /// 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 { +pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, lessThan: fn (void, lhs: T1, rhs: T1) bool) void { var i: usize = 1; while (i < items.len) : (i += 1) { const x = items[i]; const y = sub_items[i]; var j: usize = i; - while (j > 0 and lessThan(x, items[j - 1])) : (j -= 1) { + while (j > 0 and lessThan({}, x, items[j - 1])) : (j -= 1) { items[j] = items[j - 1]; sub_items[j] = sub_items[j - 1]; } diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index f8c5d55..7a3407a 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -14,12 +14,41 @@ const Rotation = struct { x: f32 = 0 }; fn printStore(store: var, name: []const u8) void { warn("--- {} ---\n", .{name}); for (store.set.dense.items) |e, i| { - warn("{:3.0}", .{e}); - warn(" ({d:3.0})", .{store.instances.items[i]}); + warn("[{}] {}", .{e, store.set.sparse.items[store.set.page(store.set.dense.items[i])]}); + warn(" ({d:.2}) ", .{store.instances.items[i]}); } warn("\n", .{}); } +const asc_u32 = std.sort.asc(u32); +const desc_u32 = std.sort.desc(f32); + +test "sort component" { + std.debug.warn("\n", .{}); + + var store = ecs.ComponentStorage(f32, u32).initPtr(std.testing.allocator); + defer store.deinit(); + + store.add(1, @as(f32, 1.1)); + store.add(2, @as(f32, 2.2)); + store.add(0, @as(f32, 0.0)); + + printStore(store, "Fucker"); + + store.sort(u32, asc_u32); + for (store.raw()) |e, i| { + // std.debug.warn("{}: {}\n", .{i, e}); + // std.testing.expectEqual(@intCast(u32, i), e); + } + + printStore(store, "Fucker"); + + store.sort(f32, desc_u32); + for (store.raw()) |e, i| { + // std.testing.expectEqual(counter, e); + } +} + test "nested OwningGroups add/remove components" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); From ac17a79631507d6d9560d623104a933fc8c9c25d Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 11 Jun 2020 22:34:25 -0700 Subject: [PATCH 041/146] more sorting... --- zig-ecs/src/ecs/utils.zig | 2 +- zig-ecs/src/ecs/views.zig | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 770ade1..2bdc03d 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -49,7 +49,7 @@ pub fn ReverseSliceIterator(comptime T: type) type { } /// 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 (void, lhs: T1, rhs: T1) bool) void { +pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, comptime lessThan: fn (void, lhs: T1, rhs: T1) bool) void { var i: usize = 1; while (i < items.len) : (i += 1) { const x = items[i]; diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index 505973d..65506f4 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -134,7 +134,13 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { sub_items[i] = store.len(); } - utils.sortSub(usize, u32, sub_items[0..], self.type_ids[0..], std.sort.asc(usize)); + const asc_usize = struct { + fn sort(ctx: void, a: usize, b: usize) bool { + return a < b; + } + }; + + utils.sortSub(usize, u32, sub_items[0..], self.type_ids[0..], asc_usize.sort); } pub fn iterator(self: *Self) Iterator { From 8235f3dc7fb2a7b4d12b1de68249183e16688a66 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 11:44:01 -0700 Subject: [PATCH 042/146] wip --- zig-ecs/src/ecs/component_storage.zig | 37 ++++++++++----------------- zig-ecs/src/ecs/sparse_set.zig | 17 +++++++++--- zig-ecs/src/ecs/utils.zig | 15 +++++++++++ zig-ecs/tests/groups_test.zig | 24 ++++++++--------- 4 files changed, 54 insertions(+), 39 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index de9ee8f..b499a93 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -200,13 +200,26 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { } /// Sort Entities or Components according to the given comparison function - pub fn sort(self: Self, comptime T: type, comptime sortFn: fn (void, T, T) bool) void { + pub fn sort(self: *Self, comptime T: type, comptime sortFn: fn (void, T, T) bool) void { std.debug.assert(T == EntityT or T == CompT); if (T == EntityT) { self.set.sortSub(sortFn, CompT, self.instances.items); } else if (T == CompT) { // essentially need to be able to call a sort method with a bound fn. That fn would then use sortFn along // with self.instances. + const Context = struct{ + self: *Self, + sortFn: fn (void, T, T) bool, + + fn sort(this: @This(), a: EntityT, b: EntityT) bool { + const real_a = this.self.getConst(a); + const real_b = this.self.getConst(b); + return this.sortFn({}, real_a, real_b); + } + }; + const context = Context{.self = self, .sortFn = sortFn}; + + self.set.sortSubSub(context, Context.sort, CompT, self.instances.items); // fn sorter(self: Self, a: T, b: T, sortFn) bool { // return sortFn(self.instances[a], self.instances[b]); // } @@ -357,25 +370,3 @@ test "sort empty component" { const asc_f32 = std.sort.asc(f32); const desc_f32 = std.sort.desc(f32); - -test "sort component" { - std.debug.warn("\n", .{}); - - var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator); - defer store.deinit(); - - store.add(1, @as(f32, 1.1)); - store.add(2, @as(f32, 2.2)); - store.add(0, @as(f32, 0.0)); - - store.sort(f32, asc_f32); - for (store.raw()) |e, i| { - // std.debug.warn("{}: {}\n", .{i, e}); - // std.testing.expectEqual(@intCast(u32, i), e); - } - - store.sort(f32, desc_f32); - for (store.raw()) |e, i| { - // std.testing.expectEqual(counter, e); - } -} \ No newline at end of file diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 9848973..a0fe045 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -95,8 +95,9 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn contains(self: Self, sparse: SparseT) bool { const curr = self.page(sparse); - if (curr >= self.sparse.items.len) + if (curr >= self.sparse.items.len) { return false; + } // testing against maxInt permits to avoid accessing the packed array return curr < self.sparse.items.len and self.sparse.items[curr] != std.math.maxInt(SparseT); @@ -146,7 +147,7 @@ pub fn SparseSet(comptime SparseT: type) type { std.sort.insertionSort(SparseT, self.dense.items, {}, sortFn); for (self.dense.items) |sparse| { - // self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, sparse); + // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse); } } @@ -156,11 +157,21 @@ pub fn SparseSet(comptime SparseT: type) type { utils.sortSub(SparseT, T, self.dense.items, sub_items, sortFn); for (self.dense.items) |sparse| { - // self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, sparse); + // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse); } } + pub fn sortSubSub(self: *Self, context: var, comptime sortFn: fn (@TypeOf(context), SparseT, SparseT) bool, comptime T: type, sub_items: []T) void { + utils.sortSubSub(SparseT, T, self.dense.items, sub_items, context, sortFn); + + for (self.dense.items) |sparse, i| { + std.debug.warn("e: {}, dense: {}, instance: {}\n", .{sparse, self.dense.items[self.page(@intCast(u32, i))], sub_items[i]}); + // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); + self.sparse.items[self.dense.items[self.page(@intCast(u32, i))]] = @intCast(SparseT, sparse); + } + } + /// Sort entities according to their order in another sparse set. Other is the master in this case. pub fn respect(self: *Self, other: *Self) void { var pos = @as(SparseT, 0); diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 2bdc03d..ba82c39 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -64,6 +64,21 @@ pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T } } +pub fn sortSubSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, context: var, comptime lessThan: fn (@TypeOf(context), lhs: T1, rhs: T1) bool) void { + var i: usize = 1; + while (i < items.len) : (i += 1) { + const x = items[i]; + const y = sub_items[i]; + var j: usize = i; + while (j > 0 and lessThan(context, x, items[j - 1])) : (j -= 1) { + items[j] = items[j - 1]; + sub_items[j] = sub_items[j - 1]; + } + items[j] = x; + sub_items[j] = y; + } +} + /// comptime string hashing for the type names pub fn typeId(comptime T: type) u32 { comptime return hashStringFnv(u32, @typeName(T)); diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index 7a3407a..70af802 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -14,14 +14,12 @@ const Rotation = struct { x: f32 = 0 }; fn printStore(store: var, name: []const u8) void { warn("--- {} ---\n", .{name}); for (store.set.dense.items) |e, i| { - warn("[{}] {}", .{e, 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("\n", .{}); } -const asc_u32 = std.sort.asc(u32); -const desc_u32 = std.sort.desc(f32); test "sort component" { std.debug.warn("\n", .{}); @@ -29,24 +27,24 @@ test "sort component" { var store = ecs.ComponentStorage(f32, u32).initPtr(std.testing.allocator); defer store.deinit(); - store.add(1, @as(f32, 1.1)); - store.add(2, @as(f32, 2.2)); - store.add(0, @as(f32, 0.0)); + store.add(33, @as(f32, 3.3)); + store.add(22, @as(f32, 2.2)); + store.add(11, @as(f32, 1.1)); - printStore(store, "Fucker"); + printStore(store, "Fuckerrrr"); - store.sort(u32, asc_u32); - for (store.raw()) |e, i| { - // std.debug.warn("{}: {}\n", .{i, e}); - // std.testing.expectEqual(@intCast(u32, i), e); - } + // sort by entity + // comptime const asc_u32 = std.sort.asc(u32); + // store.sort(u32, asc_u32); - printStore(store, "Fucker"); + comptime const desc_u32 = std.sort.asc(f32); store.sort(f32, desc_u32); for (store.raw()) |e, i| { // std.testing.expectEqual(counter, e); } + + printStore(store, "Fuckerrrrr"); } test "nested OwningGroups add/remove components" { From 52aef18b351ca2697ea6e28d21fffbc5b0f66a84 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 13:47:53 -0700 Subject: [PATCH 043/146] sorting a Storage works now --- zig-ecs/src/ecs/component_storage.zig | 46 ++++++++++++++------------- zig-ecs/src/ecs/sparse_set.zig | 16 +++++----- zig-ecs/tests/groups_test.zig | 22 ++++--------- 3 files changed, 39 insertions(+), 45 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index b499a93..ede1bb6 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -200,26 +200,12 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { } /// Sort Entities or Components according to the given comparison function - pub fn sort(self: *Self, comptime T: type, comptime sortFn: fn (void, T, T) bool) void { + pub fn sort(self: *Self, comptime T: type, comptime lessThan: fn (void, T, T) bool) void { std.debug.assert(T == EntityT or T == CompT); if (T == EntityT) { - self.set.sortSub(sortFn, CompT, self.instances.items); + self.set.sortSub(lessThan, CompT, self.instances.items); } else if (T == CompT) { - // essentially need to be able to call a sort method with a bound fn. That fn would then use sortFn along - // with self.instances. - const Context = struct{ - self: *Self, - sortFn: fn (void, T, T) bool, - - fn sort(this: @This(), a: EntityT, b: EntityT) bool { - const real_a = this.self.getConst(a); - const real_b = this.self.getConst(b); - return this.sortFn({}, real_a, real_b); - } - }; - const context = Context{.self = self, .sortFn = sortFn}; - - self.set.sortSubSub(context, Context.sort, CompT, self.instances.items); + self.set.sortSubSub({}, CompT, lessThan, self.instances.items); // fn sorter(self: Self, a: T, b: T, sortFn) bool { // return sortFn(self.instances[a], self.instances[b]); // } @@ -342,9 +328,6 @@ test "signals" { store.remove(4); } -const asc_u32 = std.sort.asc(u32); -const desc_u32 = std.sort.desc(u32); - test "sort empty component" { const Empty = struct {}; @@ -355,11 +338,13 @@ test "sort empty component" { store.add(2, Empty{}); store.add(0, Empty{}); + comptime const asc_u32 = std.sort.asc(u32); store.sort(asc_u32); for (store.data()) |e, i| { std.testing.expectEqual(@intCast(u32, i), e); } + comptime const desc_u32 = std.sort.desc(u32); store.sort(desc_u32); var counter: u32 = 2; for (store.data()) |e, i| { @@ -368,5 +353,22 @@ test "sort empty component" { } } -const asc_f32 = std.sort.asc(f32); -const desc_f32 = std.sort.desc(f32); +test "sort 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_u32 = std.sort.desc(f32); + store.sort(f32, desc_u32); + + var compare: f32 = 5; + for (store.raw()) |val, i| { + std.testing.expect(compare > val); + compare = val; + } +} diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index a0fe045..d3e239e 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -146,9 +146,9 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn sort(self: *Self, comptime sortFn: fn (void, SparseT, SparseT) bool) void { std.sort.insertionSort(SparseT, self.dense.items, {}, sortFn); - for (self.dense.items) |sparse| { + for (self.dense.items) |sparse, i| { // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); - self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse); + self.sparse.items[self.dense.items[self.page(@intCast(SparseT, i))]] = @intCast(SparseT, i); } } @@ -156,19 +156,19 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn sortSub(self: *Self, comptime sortFn: fn (void, SparseT, SparseT) bool, comptime T: type, sub_items: []T) void { utils.sortSub(SparseT, T, self.dense.items, sub_items, sortFn); - for (self.dense.items) |sparse| { + for (self.dense.items) |sparse, i| { // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); - self.sparse.items[self.page(sparse)] = @intCast(SparseT, sparse); + self.sparse.items[self.dense.items[self.page(@intCast(SparseT, i))]] = @intCast(SparseT, i); } } - pub fn sortSubSub(self: *Self, context: var, comptime sortFn: fn (@TypeOf(context), SparseT, SparseT) bool, comptime T: type, sub_items: []T) void { - utils.sortSubSub(SparseT, T, self.dense.items, sub_items, context, sortFn); + /// 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); for (self.dense.items) |sparse, i| { - std.debug.warn("e: {}, dense: {}, instance: {}\n", .{sparse, self.dense.items[self.page(@intCast(u32, i))], sub_items[i]}); // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); - self.sparse.items[self.dense.items[self.page(@intCast(u32, i))]] = @intCast(SparseT, sparse); + self.sparse.items[self.dense.items[self.page(@intCast(SparseT, i))]] = @intCast(SparseT, i); } } diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index 70af802..cdfaf05 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -22,29 +22,21 @@ fn printStore(store: var, name: []const u8) void { test "sort component" { - std.debug.warn("\n", .{}); - var store = ecs.ComponentStorage(f32, u32).initPtr(std.testing.allocator); defer store.deinit(); - store.add(33, @as(f32, 3.3)); store.add(22, @as(f32, 2.2)); store.add(11, @as(f32, 1.1)); + store.add(33, @as(f32, 3.3)); - printStore(store, "Fuckerrrr"); - - // sort by entity - // comptime const asc_u32 = std.sort.asc(u32); - // store.sort(u32, asc_u32); - - - comptime const desc_u32 = std.sort.asc(f32); + comptime const desc_u32 = std.sort.desc(f32); store.sort(f32, desc_u32); - for (store.raw()) |e, i| { - // std.testing.expectEqual(counter, e); - } - printStore(store, "Fuckerrrrr"); + var compare: f32 = 5; + for (store.raw()) |val, i| { + std.testing.expect(compare > val); + compare = val; + } } test "nested OwningGroups add/remove components" { From 491adbb8c5a5f948420bbfee9aeb3225f970e43b Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 13:48:16 -0700 Subject: [PATCH 044/146] fake commit to hold the sort context code --- zig-ecs/src/ecs/component_storage.zig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index ede1bb6..e16ca46 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -205,6 +205,19 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { if (T == EntityT) { self.set.sortSub(lessThan, CompT, self.instances.items); } else if (T == CompT) { + const Context = struct{ + self: *Self, + lessThan: fn (void, T, T) bool, + + fn sort(this: @This(), a: EntityT, b: EntityT) bool { + const real_a = this.self.getConst(a); + const real_b = this.self.getConst(b); + std.debug.warn("-- a: {}, b: {} -- ae: {}, be: {} a < b {}\n", .{real_a, real_b, a, b, this.lessThan({}, real_a, real_b)}); + return this.lessThan({}, real_a, real_b); + } + }; + const context = Context{.self = self, .lessThan = lessThan}; + self.set.sortSubSub({}, CompT, lessThan, self.instances.items); // fn sorter(self: Self, a: T, b: T, sortFn) bool { // return sortFn(self.instances[a], self.instances[b]); From 6aab3abe44140920fcfab72d698f49319fc43fc8 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 13:48:26 -0700 Subject: [PATCH 045/146] revert --- zig-ecs/src/ecs/component_storage.zig | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index e16ca46..ede1bb6 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -205,19 +205,6 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { if (T == EntityT) { self.set.sortSub(lessThan, CompT, self.instances.items); } else if (T == CompT) { - const Context = struct{ - self: *Self, - lessThan: fn (void, T, T) bool, - - fn sort(this: @This(), a: EntityT, b: EntityT) bool { - const real_a = this.self.getConst(a); - const real_b = this.self.getConst(b); - std.debug.warn("-- a: {}, b: {} -- ae: {}, be: {} a < b {}\n", .{real_a, real_b, a, b, this.lessThan({}, real_a, real_b)}); - return this.lessThan({}, real_a, real_b); - } - }; - const context = Context{.self = self, .lessThan = lessThan}; - self.set.sortSubSub({}, CompT, lessThan, self.instances.items); // fn sorter(self: Self, a: T, b: T, sortFn) bool { // return sortFn(self.instances[a], self.instances[b]); From 2f90bc20f49c8c3b96df0c8f8d9e4cc66a4e080e Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 14:09:17 -0700 Subject: [PATCH 046/146] comment --- zig-ecs/src/ecs.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/zig-ecs/src/ecs.zig b/zig-ecs/src/ecs.zig index 5fc134a..6a21a3a 100644 --- a/zig-ecs/src/ecs.zig +++ b/zig-ecs/src/ecs.zig @@ -1,6 +1,7 @@ // ecs pub const EntityTraitsType = @import("ecs/entity.zig").EntityTraitsType; +// TODO: remove me. this is just for testing pub const ComponentStorage = @import("ecs/component_storage.zig").ComponentStorage; pub const Entity = @import("ecs/registry.zig").Entity; From c8c58255f682bd381969e9210f48b1d961033155 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 16:29:26 -0700 Subject: [PATCH 047/146] cleanup type names --- zig-ecs/src/ecs/component_storage.zig | 92 +++++++++++++-------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index ede1bb6..c31e3a6 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -7,35 +7,35 @@ const Signal = @import("../signals/signal.zig").Signal; const Sink = @import("../signals/sink.zig").Sink; /// Stores an ArrayList of components along with a SparseSet of entities -pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { - std.debug.assert(!utils.isComptime(CompT)); +pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { + std.debug.assert(!utils.isComptime(Component)); // empty (zero-sized) structs will not have an array created - comptime const is_empty_struct = @sizeOf(CompT) == 0; + comptime const is_empty_struct = @sizeOf(Component) == 0; - // HACK: due to this being stored as untyped ptrs, when deinit is called we are casted to a CompT of some random + // HACK: due to this being stored as untyped ptrs, when deinit is called we are casted to a Component of some random // non-zero sized type. That will make is_empty_struct false in deinit always so we can't use it. Instead, we stick // a small dummy struct in the instances ArrayList so it can safely be deallocated. // Perhaps we should just allocate instances with a dummy allocator or the tmp allocator? - comptime var CompOrAlmostEmptyT = if (is_empty_struct) struct { dummy: u1 } else CompT; + comptime var ComponentOrDummy = if (is_empty_struct) struct { dummy: u1 } else Component; return struct { const Self = @This(); - set: *SparseSet(EntityT), - instances: std.ArrayList(CompOrAlmostEmptyT), + set: *SparseSet(Entity), + instances: std.ArrayList(ComponentOrDummy), allocator: ?*std.mem.Allocator, /// doesnt really belong here...used to denote group ownership super: usize = 0, safe_deinit: fn (*Self) void, - safe_swap: fn (*Self, EntityT, EntityT) void, - construction: Signal(EntityT), - update: Signal(EntityT), - destruction: Signal(EntityT), + safe_swap: fn (*Self, Entity, Entity) void, + construction: Signal(Entity), + update: Signal(Entity), + destruction: Signal(Entity), pub fn init(allocator: *std.mem.Allocator) Self { var store = Self{ - .set = SparseSet(EntityT).initPtr(allocator), + .set = SparseSet(Entity).initPtr(allocator), .instances = undefined, .safe_deinit = struct { fn deinit(self: *Self) void { @@ -45,21 +45,21 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { } }.deinit, .safe_swap = struct { - fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { + fn swap(self: *Self, lhs: Entity, rhs: Entity) void { if (!is_empty_struct) { - std.mem.swap(CompT, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]); + std.mem.swap(Component, &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), - .destruction = Signal(EntityT).init(allocator), + .construction = Signal(Entity).init(allocator), + .update = Signal(Entity).init(allocator), + .destruction = Signal(Entity).init(allocator), }; if (!is_empty_struct) { - store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); + store.instances = std.ArrayList(ComponentOrDummy).init(allocator); } return store; @@ -67,15 +67,15 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { pub fn initPtr(allocator: *std.mem.Allocator) *Self { var store = allocator.create(Self) catch unreachable; - store.set = SparseSet(EntityT).initPtr(allocator); + store.set = SparseSet(Entity).initPtr(allocator); if (!is_empty_struct) { - store.instances = std.ArrayList(CompOrAlmostEmptyT).init(allocator); + store.instances = std.ArrayList(ComponentOrDummy).init(allocator); } store.allocator = allocator; store.super = 0; - store.construction = Signal(EntityT).init(allocator); - store.update = Signal(EntityT).init(allocator); - store.destruction = Signal(EntityT).init(allocator); + store.construction = Signal(Entity).init(allocator); + store.update = Signal(Entity).init(allocator); + store.destruction = Signal(Entity).init(allocator); // since we are stored as a pointer, we need to catpure this store.safe_deinit = struct { @@ -87,9 +87,9 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { }.deinit; store.safe_swap = struct { - fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { + fn swap(self: *Self, lhs: Entity, rhs: Entity) void { if (!is_empty_struct) { - std.mem.swap(CompT, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]); + std.mem.swap(Component, &self.instances.items[self.set.index(lhs)], &self.instances.items[self.set.index(rhs)]); } self.set.swap(lhs, rhs); } @@ -113,15 +113,15 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { } } - pub fn onConstruct(self: *Self) Sink(EntityT) { + pub fn onConstruct(self: *Self) Sink(Entity) { return self.construction.sink(); } - pub fn onUpdate(self: *Self) Sink(EntityT) { + pub fn onUpdate(self: *Self) Sink(Entity) { return self.update.sink(); } - pub fn onDestruct(self: *Self) Sink(EntityT) { + pub fn onDestruct(self: *Self) Sink(Entity) { return self.destruction.sink(); } @@ -134,7 +134,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { } /// Assigns an entity to a storage and assigns its object - pub fn add(self: *Self, entity: EntityT, value: CompT) void { + pub fn add(self: *Self, entity: Entity, value: Component) void { if (!is_empty_struct) { _ = self.instances.append(value) catch unreachable; } @@ -143,7 +143,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { } /// Removes an entity from a storage - pub fn remove(self: *Self, entity: EntityT) void { + pub fn remove(self: *Self, entity: Entity) void { self.destruction.publish(entity); if (!is_empty_struct) { _ = self.instances.swapRemove(self.set.index(entity)); @@ -152,7 +152,7 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { } /// Checks if a view contains an entity - pub fn contains(self: Self, entity: EntityT) bool { + pub fn contains(self: Self, entity: Entity) bool { return self.set.contains(entity); } @@ -163,49 +163,49 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { pub usingnamespace if (is_empty_struct) struct { /// Sort Entities according to the given comparison function - pub fn sort(self: Self, comptime sortFn: fn (void, EntityT, EntityT) bool) void { + pub fn sort(self: Self, comptime sortFn: fn (void, Entity, Entity) bool) void { self.set.sort(sortFn); } } else struct { /// Direct access to the array of objects - pub fn raw(self: Self) []CompT { + pub fn raw(self: Self) []Component { return self.instances.items; } /// Replaces the given component for an entity - pub fn replace(self: *Self, entity: EntityT, value: CompT) void { + pub fn replace(self: *Self, entity: Entity, value: Component) void { self.get(entity).* = value; self.update.publish(entity); } /// Returns the object associated with an entity - pub fn get(self: *Self, entity: EntityT) *CompT { + pub fn get(self: *Self, entity: Entity) *Component { std.debug.assert(self.contains(entity)); return &self.instances.items[self.set.index(entity)]; } - pub fn getConst(self: *Self, entity: EntityT) CompT { + pub fn getConst(self: *Self, entity: Entity) Component { return self.instances.items[self.set.index(entity)]; } /// Returns a pointer to the object associated with an entity, if any. - pub fn tryGet(self: *Self, entity: EntityT) ?*CompT { + pub fn tryGet(self: *Self, entity: Entity) ?*Component { return if (self.set.contains(entity)) &self.instances.items[self.set.index(entity)] else null; } - pub fn tryGetConst(self: *Self, entity: EntityT) ?CompT { + pub fn tryGetConst(self: *Self, entity: Entity) ?Component { 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, comptime lessThan: fn (void, T, T) bool) void { - std.debug.assert(T == EntityT or T == CompT); - if (T == EntityT) { - self.set.sortSub(lessThan, CompT, self.instances.items); - } else if (T == CompT) { - self.set.sortSubSub({}, CompT, lessThan, self.instances.items); + std.debug.assert(T == Entity or T == Component); + if (T == Entity) { + self.set.sortSub(lessThan, Component, self.instances.items); + } else if (T == Component) { + self.set.sortSubSub({}, Component, lessThan, self.instances.items); // fn sorter(self: Self, a: T, b: T, sortFn) bool { // return sortFn(self.instances[a], self.instances[b]); // } @@ -215,17 +215,17 @@ pub fn ComponentStorage(comptime CompT: type, comptime EntityT: type) type { }; /// Direct access to the array of entities - pub fn data(self: Self) []const EntityT { + pub fn data(self: Self) []const Entity { return self.set.data(); } /// Direct access to the array of entities - pub fn dataPtr(self: Self) *const []EntityT { + pub fn dataPtr(self: Self) *const []Entity { return self.set.dataPtr(); } /// Swaps entities and objects in the internal packed arrays - pub fn swap(self: *Self, lhs: EntityT, rhs: EntityT) void { + pub fn swap(self: *Self, lhs: Entity, rhs: Entity) void { self.safe_swap(self, lhs, rhs); } From e098c9963266506b1939112bebb31cb6d4e3da66 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 18:15:47 -0700 Subject: [PATCH 048/146] basic group sorting --- zig-ecs/src/ecs.zig | 2 + zig-ecs/src/ecs/component_storage.zig | 57 ++++++++++++++------ zig-ecs/src/ecs/groups.zig | 37 +++++++++---- zig-ecs/src/ecs/registry.zig | 7 +-- zig-ecs/src/ecs/sparse_set.zig | 24 ++++++--- zig-ecs/tests/groups_test.zig | 77 ++++++++++++++++++++++----- 6 files changed, 155 insertions(+), 49 deletions(-) diff --git a/zig-ecs/src/ecs.zig b/zig-ecs/src/ecs.zig index 6a21a3a..85123ac 100644 --- a/zig-ecs/src/ecs.zig +++ b/zig-ecs/src/ecs.zig @@ -8,6 +8,8 @@ pub const Entity = @import("ecs/registry.zig").Entity; pub const Registry = @import("ecs/registry.zig").Registry; pub const BasicView = @import("ecs/views.zig").BasicView; 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 pub const Signal = @import("signals/signal.zig").Signal; diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index c31e3a6..c214e2a 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -162,9 +162,11 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { pub usingnamespace if (is_empty_struct) struct { - /// Sort Entities according to the given comparison function - pub fn sort(self: Self, comptime sortFn: fn (void, Entity, Entity) bool) void { - self.set.sort(sortFn); + /// Sort Entities according to the given comparison function. Only T == Entity is allowed. The constraint param only exists for + /// parity with non-empty Components + 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 @@ -200,16 +202,12 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { } /// 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); if (T == Entity) { - self.set.sortSub(lessThan, Component, self.instances.items); + self.set.sortSub(context, lessThan, Component, self.instances.items); } else if (T == Component) { - self.set.sortSubSub({}, 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)])); + self.set.sortSubSub(context, Component, lessThan, self.instances.items); } } }; @@ -339,13 +337,13 @@ test "sort empty component" { store.add(0, Empty{}); comptime const asc_u32 = std.sort.asc(u32); - store.sort(asc_u32); + store.sort(u32, {}, asc_u32); for (store.data()) |e, i| { std.testing.expectEqual(@intCast(u32, i), e); } comptime const desc_u32 = std.sort.desc(u32); - store.sort(desc_u32); + store.sort(u32, {}, desc_u32); var counter: u32 = 2; for (store.data()) |e, i| { std.testing.expectEqual(counter, e); @@ -353,7 +351,7 @@ test "sort empty component" { } } -test "sort component" { +test "sort by entity" { std.debug.warn("\n", .{}); 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(33, @as(f32, 3.3)); - comptime const desc_u32 = std.sort.desc(f32); - store.sort(f32, desc_u32); + const SortContext = struct{ + 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; for (store.raw()) |val, i| { diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index d80a2f6..7c85e44 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -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 /// entities. pub const BasicGroup = struct { - const Self = @This(); - registry: *Registry, group_data: *Registry.GroupData, - pub fn init(registry: *Registry, group_data: *Registry.GroupData) Self { - return Self{ + pub fn init(registry: *Registry, group_data: *Registry.GroupData) BasicGroup { + return .{ .registry = registry, .group_data = group_data, }; } - pub fn len(self: Self) usize { + pub fn len(self: BasicGroup) usize { return self.group_data.entity_set.len(); } /// 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(); } - 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); } - 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); } /// 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) { + pub fn iterator(self: BasicGroup) utils.ReverseSliceIterator(Entity) { 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 { diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 3363f87..f285780 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -412,10 +412,10 @@ pub const Registry = struct { 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); 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. @@ -563,7 +563,8 @@ pub const Registry = struct { new_group_data.entity_set.add(entity); } } 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]); for (first_owned_storage.data()) |entity| { new_group_data.maybeValidIf(entity); diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index d3e239e..2c27a97 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -143,8 +143,8 @@ pub fn SparseSet(comptime SparseT: type) type { } /// Sort elements according to the given comparison function - pub fn sort(self: *Self, comptime sortFn: fn (void, SparseT, SparseT) bool) void { - std.sort.insertionSort(SparseT, self.dense.items, {}, sortFn); + pub fn sort(self: *Self, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool) void { + std.sort.insertionSort(SparseT, self.dense.items, context, lessThan); for (self.dense.items) |sparse, i| { // 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 - pub fn sortSub(self: *Self, comptime sortFn: fn (void, SparseT, SparseT) bool, comptime T: type, sub_items: []T) void { - utils.sortSub(SparseT, T, self.dense.items, sub_items, sortFn); + pub fn sortSub(self: *Self, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, comptime T: type, sub_items: []T) void { + std.sort.insertionSort(SparseT, self.dense.items, context, lessThan); - for (self.dense.items) |sparse, i| { - // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); - self.sparse.items[self.dense.items[self.page(@intCast(SparseT, i))]] = @intCast(SparseT, i); + for (self.dense.items) |sparse, pos| { + var curr = @intCast(SparseT, pos); + 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(3); - set.sort(desc_u32); + set.sort({}, desc_u32); for (set.dense.items) |item, i| { if (i < set.dense.items.len - 1) { diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index cdfaf05..98f0adf 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -2,6 +2,7 @@ const std = @import("std"); const warn = std.debug.warn; const ecs = @import("ecs"); const Registry = @import("ecs").Registry; +const BasicGroup = @import("ecs").BasicGroup; const Velocity = 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 { warn("--- {} ---\n", .{name}); 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("\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" { - var store = ecs.ComponentStorage(f32, u32).initPtr(std.testing.allocator); - defer store.deinit(); +test "sort BasicGroup by Component" { + var reg = Registry.init(std.testing.allocator); + defer reg.deinit(); - store.add(22, @as(f32, 2.2)); - store.add(11, @as(f32, 1.1)); - store.add(33, @as(f32, 3.3)); + var group = reg.group(.{}, .{Sprite, Renderable}, .{}); - comptime const desc_u32 = std.sort.desc(f32); - store.sort(f32, desc_u32); + 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) }); + } - var compare: f32 = 5; - for (store.raw()) |val, i| { - std.testing.expect(compare > val); - compare = val; + 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(); + 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" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); @@ -87,7 +136,7 @@ test "nested OwningGroups entity order" { var transform_store = reg.assure(Transform); // printStore(sprite_store, "Sprite"); - reg.add(1, Transform{.x = 1}); + reg.add(1, Transform{ .x = 1 }); // printStore(sprite_store, "Sprite"); // printStore(transform_store, "Transform"); From 34b2702333a765365e160e4f374f706601b41203 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 20:05:55 -0700 Subject: [PATCH 049/146] 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" { From 735830994fa5b2240461b885fa17279fe2137738 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 23:37:09 -0700 Subject: [PATCH 050/146] add len to all sorts for owned groups --- zig-ecs/src/ecs/component_storage.zig | 16 ++++++------ zig-ecs/src/ecs/groups.zig | 13 +++------- zig-ecs/tests/groups_test.zig | 36 ++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 6624f99..d50870e 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -202,7 +202,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { } /// 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 { + pub fn sort(self: *Self, comptime T: type, length: usize, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { std.debug.assert(T == Entity or T == Component); if (T == Entity) { // 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 @@ -216,12 +216,12 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { } }; const swap_context = SortContext{.storage = self}; - self.set.sortSwap(context, lessThan, swap_context); + self.set.sortSwap(length, context, lessThan, swap_context); } else { - self.set.sortSub(context, lessThan, Component, self.instances.items); + self.set.sortSub(length, context, lessThan, Component, self.instances.items); } } else if (T == Component) { - self.set.sortSubSub(context, Component, lessThan, self.instances.items); + self.set.sortSubSub(length, context, Component, lessThan, self.instances.items); } } }; @@ -256,7 +256,9 @@ test "add/try-get/remove/clear" { store.add(3, 66.45); std.testing.expectEqual(store.tryGetConst(3).?, 66.45); - if (store.tryGet(3)) |found| std.testing.expectEqual(@as(f32, 66.45), found.*); + if (store.tryGet(3)) |found| { + std.testing.expectEqual(@as(f32, 66.45), found.*); + } store.remove(3); @@ -385,7 +387,7 @@ test "sort by entity" { } }; const context = SortContext{ .store = store }; - store.sort(u32, context, SortContext.sort); + store.sort(u32, store.len(), context, SortContext.sort); var compare: f32 = 5; for (store.raw()) |val, i| { @@ -405,7 +407,7 @@ test "sort by component" { store.add(33, @as(f32, 3.3)); comptime const desc_f32 = std.sort.desc(f32); - store.sort(f32, {}, desc_f32); + store.sort(f32, store.len(), {}, desc_f32); var compare: f32 = 5; for (store.raw()) |val, i| { diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 4d3880b..b2aee4e 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -267,7 +267,7 @@ pub const OwningGroup = struct { if (T == Entity) { // only sort up to self.group_data.current - first_storage.sort(Entity, context, lessThan); + first_storage.sort(Entity, self.group_data.current, context, lessThan); } else { // TODO: in debug mode, validate that T is present in the group const SortContext = struct { @@ -282,17 +282,10 @@ pub const OwningGroup = struct { } }; const wrapper = SortContext{ .group = self, .wrapped_context = context, .lessThan = lessThan }; - first_storage.sort(Entity, wrapper, SortContext.sort); + first_storage.sort(Entity, self.group_data.current, 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]; - + // sync up the rest of the owned components var next: usize = self.group_data.current; while (true) : (next -= 1) { if (next == 0) break; diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index b8371c7..ec71bf3 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -3,6 +3,7 @@ const warn = std.debug.warn; const ecs = @import("ecs"); const Registry = @import("ecs").Registry; const BasicGroup = @import("ecs").BasicGroup; +const OwningGroup = @import("ecs").OwningGroup; const Velocity = struct { x: f32 = 0, y: f32 = 0 }; const Position = struct { x: f32 = 0, y: f32 = 0 }; @@ -83,6 +84,40 @@ test "sort BasicGroup by Component" { } } +test "sort OwningGroup by Entity" { + 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 { + group: OwningGroup, + + fn sort(this: @This(), a: ecs.Entity, b: ecs.Entity) bool { + const sprite_a = this.group.getConst(Sprite, a); + const sprite_b = this.group.getConst(Sprite, b); + return sprite_a.x > sprite_b.x; + } + }; + const context = SortContext{.group = group}; + group.sort(ecs.Entity, context, SortContext.sort); + + var val: f32 = 0; + var iter = group.iterator(struct {s: *Sprite, r: *Renderable}); + while (iter.next()) |entity| { + std.testing.expectEqual(val, entity.s.*.x); + val += 1; + } +} + test "sort OwningGroup by Component" { std.debug.warn("\n", .{}); var reg = Registry.init(std.testing.allocator); @@ -107,7 +142,6 @@ test "sort OwningGroup by Component" { 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; } From 230afa8e4cb327d5706352f7c5bdd3f0cb9c935c Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 23:37:24 -0700 Subject: [PATCH 051/146] the monster update to paged sparse sets --- zig-ecs/src/ecs/sparse_set.zig | 101 ++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 45 deletions(-) diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index a5496f0..e5cfc9a 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -7,15 +7,17 @@ const ReverseSliceIterator = @import("utils.zig").ReverseSliceIterator; pub fn SparseSet(comptime SparseT: type) type { return struct { const Self = @This(); + const page_size: usize = 32768; + const entity_per_page = page_size / @sizeOf(SparseT); - sparse: std.ArrayList(SparseT), + sparse: std.ArrayList(?[]SparseT), dense: std.ArrayList(SparseT), entity_mask: SparseT, allocator: ?*std.mem.Allocator, pub fn initPtr(allocator: *std.mem.Allocator) *Self { var set = allocator.create(Self) catch unreachable; - set.sparse = std.ArrayList(SparseT).init(allocator); + set.sparse = std.ArrayList(?[]SparseT).init(allocator); set.dense = std.ArrayList(SparseT).init(allocator); set.entity_mask = std.math.maxInt(SparseT); set.allocator = allocator; @@ -24,7 +26,7 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn init(allocator: *std.mem.Allocator) Self { return Self{ - .sparse = std.ArrayList(SparseT).init(allocator), + .sparse = std.ArrayList(?[]SparseT).init(allocator), .dense = std.ArrayList(SparseT).init(allocator), .entity_mask = std.math.maxInt(SparseT), .allocator = null, @@ -32,38 +34,44 @@ pub fn SparseSet(comptime SparseT: type) type { } pub fn deinit(self: *Self) void { + self.sparse.expandToCapacity(); + for (self.sparse.items) |array, i| { + if (array) |arr| { + self.sparse.allocator.free(arr); + } + } + self.dense.deinit(); self.sparse.deinit(); - if (self.allocator) |allocator| + if (self.allocator) |allocator| { allocator.destroy(self); + } } pub fn page(self: Self, sparse: SparseT) usize { - // TODO: support paging - // return (sparse & EntityTraits.entity_mask) / sparse_per_page; - return sparse & self.entity_mask; + return (sparse & self.entity_mask) / entity_per_page; } fn offset(self: Self, sparse: SparseT) usize { - // TODO: support paging - // return entt & (sparse_per_page - 1) - return sparse & self.entity_mask; + return sparse & (entity_per_page - 1); } fn assure(self: *Self, pos: usize) []SparseT { - // TODO: support paging - if (self.sparse.capacity <= pos or self.sparse.capacity == 0) { - const amount = pos + 1 - self.sparse.capacity; - - // expand and fill with maxInt as an identifier - const old_len = self.sparse.items.len; - self.sparse.resize(self.sparse.items.len + amount) catch unreachable; + if (pos >= self.sparse.items.len) { + self.sparse.resize(pos + 1) catch unreachable; self.sparse.expandToCapacity(); - std.mem.set(SparseT, self.sparse.items[old_len..self.sparse.items.len], std.math.maxInt(SparseT)); } - return self.sparse.items; + if (self.sparse.items[pos]) |arr| { + return arr; + } + + var new_page = self.sparse.allocator.alloc(SparseT, entity_per_page) catch unreachable; + std.mem.set(SparseT, new_page, std.math.maxInt(SparseT)); + self.sparse.items[pos] = new_page; + + return self.sparse.items[pos].?; } /// Increases the capacity of a sparse sets index array @@ -95,18 +103,13 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn contains(self: Self, sparse: SparseT) bool { const curr = self.page(sparse); - if (curr >= self.sparse.items.len) { - return false; - } - - // testing against maxInt permits to avoid accessing the packed array - return curr < self.sparse.items.len and self.sparse.items[curr] != std.math.maxInt(SparseT); + return curr < self.sparse.items.len and self.sparse.items[curr] != null and self.sparse.items[curr].?[self.offset(sparse)] != std.math.maxInt(SparseT); } /// Returns the position of an entity in a sparse set pub fn index(self: Self, sparse: SparseT) SparseT { std.debug.assert(self.contains(sparse)); - return self.sparse.items[self.offset(sparse)]; + return self.sparse.items[self.page(sparse)].?[self.offset(sparse)]; } /// Assigns an entity to a sparse set @@ -126,20 +129,25 @@ pub fn SparseSet(comptime SparseT: type) type { const pos = self.offset(sparse); const last_dense = self.dense.items[self.dense.items.len - 1]; - self.dense.items[self.sparse.items[curr]] = last_dense; - self.sparse.items[self.page(last_dense)] = self.sparse.items[curr]; - self.sparse.items[curr] = std.math.maxInt(SparseT); + self.dense.items[self.sparse.items[curr].?[pos]] = last_dense; + self.sparse.items[self.page(last_dense)].?[self.offset(last_dense)] = self.sparse.items[curr].?[pos]; + self.sparse.items[curr].?[pos] = std.math.maxInt(SparseT); _ = self.dense.pop(); } /// Swaps two entities in the internal packed and sparse arrays - pub fn swap(self: *Self, sparse_l: SparseT, sparse_r: SparseT) void { - var from = &self.sparse.items[sparse_l]; - var to = &self.sparse.items[sparse_r]; + pub fn swap(self: *Self, lhs: SparseT, rhs: SparseT) void { + var from = &self.sparse.items[self.page(lhs)].?[self.offset(lhs)]; + var to = &self.sparse.items[self.page(rhs)].?[self.offset(rhs)]; std.mem.swap(SparseT, &self.dense.items[from.*], &self.dense.items[to.*]); std.mem.swap(SparseT, from, to); + + // auto &from = sparse[page(lhs)][offset(lhs)]; + // auto &to = sparse[page(rhs)][offset(rhs)]; + // std::swap(packed[size_type(from)], packed[size_type(to)]); + // std::swap(from, to); } /// Sort elements according to the given comparison function @@ -148,21 +156,22 @@ pub fn SparseSet(comptime SparseT: type) type { for (self.dense.items) |sparse, i| { // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); - self.sparse.items[self.dense.items[self.page(@intCast(SparseT, i))]] = @intCast(SparseT, i); + const item = @intCast(SparseT, i); + self.sparse.items[self.page(self.dense.items[self.page(item)])].?[self.offset(self.dense.items[self.page(item)])] = @intCast(SparseT, i); } } /// Sort elements according to the given comparison function and keeps sub_items with the same sort - pub fn sortSub(self: *Self, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, comptime T: type, sub_items: []T) void { - std.sort.insertionSort(SparseT, self.dense.items, context, lessThan); + pub fn sortSub(self: *Self, length: usize, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, comptime T: type, sub_items: []T) void { + std.sort.insertionSort(SparseT, self.dense.items[0..length], context, lessThan); - for (self.dense.items) |sparse, pos| { + for (self.dense.items[0..length]) |sparse, pos| { var curr = @intCast(SparseT, pos); 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); + self.sparse.items[self.page(self.dense.items[curr])].?[self.offset(self.dense.items[curr])] = curr; curr = next; next = self.index(self.dense.items[curr]); @@ -171,16 +180,17 @@ 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); + pub fn sortSwap(self: *Self, length: usize, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: var) void { + std.sort.insertionSort(SparseT, self.dense.items[0..length], context, lessThan); - for (self.dense.items) |sparse, pos| { + for (self.dense.items[0..length]) |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); + // self.sparse.items[self.dense.items[self.page(@intCast(SparseT, curr))]] = @intCast(SparseT, curr); + self.sparse.items[self.page(self.dense.items[curr])].?[self.offset(self.dense.items[curr])] = curr; curr = next; next = self.index(self.dense.items[curr]); @@ -189,12 +199,13 @@ pub fn SparseSet(comptime SparseT: type) type { } /// 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); + pub fn sortSubSub(self: *Self, length: usize, context: var, comptime T: type, comptime lessThan: fn (@TypeOf(context), T, T) bool, items: []T) void { + utils.sortSubSub(T, SparseT, items[0..length], self.dense.items, context, lessThan); - for (self.dense.items) |sparse, i| { + for (self.dense.items[0..length]) |sparse, i| { // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); - self.sparse.items[self.dense.items[self.page(@intCast(SparseT, i))]] = @intCast(SparseT, i); + const pos = @intCast(SparseT, i); + self.sparse.items[self.page(self.dense.items[pos])].?[self.offset(pos)] = pos; } } From dd5285875e67f110871e290ca03edb91b6b70ef7 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 23:41:49 -0700 Subject: [PATCH 052/146] clear clears --- zig-ecs/src/ecs/sparse_set.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index e5cfc9a..d0660b4 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -224,6 +224,14 @@ pub fn SparseSet(comptime SparseT: type) type { } pub fn clear(self: *Self) void { + self.sparse.expandToCapacity(); + for (self.sparse.items) |array, i| { + if (array) |arr| { + self.sparse.allocator.free(arr); + self.sparse.items[i] = null; + } + } + self.sparse.items.len = 0; self.dense.items.len = 0; } From 7c7b50aefa5edc9870329c6f1a43877314fd7c95 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Jun 2020 23:43:23 -0700 Subject: [PATCH 053/146] cleaning --- zig-ecs/src/ecs/component_storage.zig | 4 ---- zig-ecs/tests/groups_test.zig | 2 -- 2 files changed, 6 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index d50870e..ba64cfb 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -368,8 +368,6 @@ test "sort empty component" { } test "sort by entity" { - std.debug.warn("\n", .{}); - var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator); defer store.deinit(); @@ -397,8 +395,6 @@ test "sort by entity" { } test "sort by component" { - std.debug.warn("\n", .{}); - var store = ComponentStorage(f32, u32).initPtr(std.testing.allocator); defer store.deinit(); diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index ec71bf3..39b3b4a 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -85,7 +85,6 @@ test "sort BasicGroup by Component" { } test "sort OwningGroup by Entity" { - std.debug.warn("\n", .{}); var reg = Registry.init(std.testing.allocator); defer reg.deinit(); @@ -119,7 +118,6 @@ test "sort OwningGroup by Entity" { } test "sort OwningGroup by Component" { - std.debug.warn("\n", .{}); var reg = Registry.init(std.testing.allocator); defer reg.deinit(); From 6b34565a2aeeb03dfe7d5297f3d63ee8de893c5d Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 13 Jun 2020 14:29:13 -0700 Subject: [PATCH 054/146] entity iterator --- zig-ecs/src/ecs/handles.zig | 50 +++++++++++++++++++++++++-------- zig-ecs/src/ecs/registry.zig | 7 ++++- zig-ecs/tests/registry_test.zig | 3 ++ 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig index cbf71d1..567aeb9 100644 --- a/zig-ecs/src/ecs/handles.zig +++ b/zig-ecs/src/ecs/handles.zig @@ -21,6 +21,28 @@ pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime Ver const invalid_id = std.math.maxInt(IndexType); + pub const Iterator = struct { + hm: Self, + index: usize = 0, + + pub fn init(hm: Self) @This() { + return .{ .hm = hm }; + } + + pub fn next(self: *@This()) ?HandleType { + if (self.index == self.hm.append_cursor) return null; + + for (self.hm.handles[self.index..self.hm.append_cursor]) |h| { + self.index += 1; + if (self.hm.alive(h)) { + return h; + } + } + + return null; + } + }; + pub fn init(allocator: *std.mem.Allocator) Self { return initWithCapacity(allocator, 32); } @@ -88,10 +110,14 @@ pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime Ver self.last_destroyed = id; } - pub fn isAlive(self: Self, handle: HandleType) bool { + pub fn alive(self: Self, handle: HandleType) bool { const id = self.extractId(handle); return id < self.append_cursor and self.handles[id] == handle; } + + pub fn iterator(self: Self) Iterator { + return Iterator.init(self); + } }; } @@ -103,33 +129,33 @@ test "handles" { const e1 = hm.create(); const e2 = hm.create(); - std.debug.assert(hm.isAlive(e0)); - std.debug.assert(hm.isAlive(e1)); - std.debug.assert(hm.isAlive(e2)); + std.debug.assert(hm.alive(e0)); + std.debug.assert(hm.alive(e1)); + std.debug.assert(hm.alive(e2)); hm.remove(e1) catch unreachable; - std.debug.assert(!hm.isAlive(e1)); + std.debug.assert(!hm.alive(e1)); std.testing.expectError(error.RemovedInvalidHandle, hm.remove(e1)); var e_tmp = hm.create(); - std.debug.assert(hm.isAlive(e_tmp)); + std.debug.assert(hm.alive(e_tmp)); hm.remove(e_tmp) catch unreachable; - std.debug.assert(!hm.isAlive(e_tmp)); + std.debug.assert(!hm.alive(e_tmp)); hm.remove(e0) catch unreachable; - std.debug.assert(!hm.isAlive(e0)); + std.debug.assert(!hm.alive(e0)); hm.remove(e2) catch unreachable; - std.debug.assert(!hm.isAlive(e2)); + std.debug.assert(!hm.alive(e2)); e_tmp = hm.create(); - std.debug.assert(hm.isAlive(e_tmp)); + std.debug.assert(hm.alive(e_tmp)); e_tmp = hm.create(); - std.debug.assert(hm.isAlive(e_tmp)); + std.debug.assert(hm.alive(e_tmp)); e_tmp = hm.create(); - std.debug.assert(hm.isAlive(e_tmp)); + std.debug.assert(hm.alive(e_tmp)); } diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index b07d91d..a6c8e0d 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -250,7 +250,7 @@ pub const Registry = struct { } pub fn valid(self: *Registry, entity: Entity) bool { - return self.handles.isAlive(entity); + return self.handles.alive(entity); } /// Returns the entity identifier without the version @@ -275,6 +275,11 @@ pub const Registry = struct { self.handles.remove(entity) catch unreachable; } + /// returns an interator that iterates all live entities + pub fn entities(self: Registry) EntityHandles.Iterator { + return self.handles.iterator(); + } + pub fn add(self: *Registry, entity: Entity, value: var) void { assert(self.valid(entity)); self.assure(@TypeOf(value)).add(entity, value); diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index b2b7ea8..dd8144b 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -24,6 +24,9 @@ test "Registry" { std.testing.expect(reg.has(Position, e1)); std.testing.expect(reg.has(BigOne, e1)); + var iter = reg.entities(); + while (iter.next()) |e| std.testing.expectEqual(e1, e); + reg.remove(Empty, e1); std.testing.expect(!reg.has(Empty, e1)); } From 96385b6c61155cc57448b2398deb087dfe111658 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 13 Jun 2020 14:53:14 -0700 Subject: [PATCH 055/146] cleanup sort --- zig-ecs/src/ecs/component_storage.zig | 49 +++++++++++++++++---------- zig-ecs/src/ecs/sparse_set.zig | 30 ++-------------- 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index ba64cfb..7de8234 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -204,24 +204,39 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { /// 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, length: usize, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { std.debug.assert(T == Entity or T == Component); + + // we have to perform a swap after the sort for all moved entities so we make a helper struct for that. In the + // case of a Component sort we also wrap that into the struct so we can get the Component data to pass to the + // lessThan method passed in. if (T == Entity) { - // 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(length, context, lessThan, swap_context); - } else { - self.set.sortSub(length, context, lessThan, Component, self.instances.items); - } - } else if (T == Component) { - self.set.sortSubSub(length, context, Component, lessThan, self.instances.items); + 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.arrange(length, context, lessThan, swap_context); + } else { + const SortContext = struct { + storage: *Self, + wrapped_context: @TypeOf(context), + lessThan: fn (@TypeOf(context), T, T) bool, + + fn sort(this: @This(), a: Entity, b: Entity) bool { + const real_a = this.storage.getConst(a); + const real_b = this.storage.getConst(b); + return this.lessThan(this.wrapped_context, real_a, real_b); + } + + 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, .wrapped_context = context, .lessThan = lessThan}; + self.set.arrange(length, swap_context, SortContext.sort, swap_context); } } }; diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index d0660b4..91dbe43 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -143,11 +143,6 @@ pub fn SparseSet(comptime SparseT: type) type { std.mem.swap(SparseT, &self.dense.items[from.*], &self.dense.items[to.*]); std.mem.swap(SparseT, from, to); - - // auto &from = sparse[page(lhs)][offset(lhs)]; - // auto &to = sparse[page(rhs)][offset(rhs)]; - // std::swap(packed[size_type(from)], packed[size_type(to)]); - // std::swap(from, to); } /// Sort elements according to the given comparison function @@ -155,32 +150,14 @@ pub fn SparseSet(comptime SparseT: type) type { std.sort.insertionSort(SparseT, self.dense.items, context, lessThan); for (self.dense.items) |sparse, i| { - // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); const item = @intCast(SparseT, i); self.sparse.items[self.page(self.dense.items[self.page(item)])].?[self.offset(self.dense.items[self.page(item)])] = @intCast(SparseT, i); } } - /// Sort elements according to the given comparison function and keeps sub_items with the same sort - pub fn sortSub(self: *Self, length: usize, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, comptime T: type, sub_items: []T) void { - std.sort.insertionSort(SparseT, self.dense.items[0..length], context, lessThan); - - for (self.dense.items[0..length]) |sparse, pos| { - var curr = @intCast(SparseT, pos); - 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.page(self.dense.items[curr])].?[self.offset(self.dense.items[curr])] = curr; - - curr = next; - next = self.index(self.dense.items[curr]); - } - } - } - - /// Sort elements according to the given comparison function and keeps sub_items with the same sort - pub fn sortSwap(self: *Self, length: usize, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: var) void { + /// Sort elements according to the given comparison function. Use this when a data array needs to stay in sync with the SparseSet + /// by passing in a "swap_context" that contains a "swap" method with a sig of fn(ctx,SparseT,SparseT)void + pub fn arrange(self: *Self, length: usize, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: var) void { std.sort.insertionSort(SparseT, self.dense.items[0..length], context, lessThan); for (self.dense.items[0..length]) |sparse, pos| { @@ -189,7 +166,6 @@ pub fn SparseSet(comptime SparseT: type) type { 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); self.sparse.items[self.page(self.dense.items[curr])].?[self.offset(self.dense.items[curr])] = curr; curr = next; From 9dd7ca445afeae193e89cc215dff23870deff184 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 13 Jun 2020 14:53:14 -0700 Subject: [PATCH 056/146] cleanup sort --- zig-ecs/src/ecs/sparse_set.zig | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 91dbe43..ba0b090 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -174,17 +174,6 @@ pub fn SparseSet(comptime SparseT: type) type { } } - /// flips the script and uses the sparse set as the subordinate and does the sorting on the items slice - pub fn sortSubSub(self: *Self, length: usize, context: var, comptime T: type, comptime lessThan: fn (@TypeOf(context), T, T) bool, items: []T) void { - utils.sortSubSub(T, SparseT, items[0..length], self.dense.items, context, lessThan); - - for (self.dense.items[0..length]) |sparse, i| { - // sparse[page(packed[pos])][offset(packed[pos])] = entity_type(pos); - const pos = @intCast(SparseT, i); - self.sparse.items[self.page(self.dense.items[pos])].?[self.offset(pos)] = pos; - } - } - /// Sort entities according to their order in another sparse set. Other is the master in this case. pub fn respect(self: *Self, other: *Self) void { var pos = @as(SparseT, 0); From a352f8350ccb237aec9c59fea52fe89993f2a670 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 13 Jun 2020 18:36:57 -0700 Subject: [PATCH 057/146] start of scheduler --- zig-ecs/src/ecs/groups.zig | 12 -- zig-ecs/src/process/process.zig | 92 ++++++++++++++ zig-ecs/src/process/scheduler.zig | 200 ++++++++++++++++++++++++++++++ 3 files changed, 292 insertions(+), 12 deletions(-) create mode 100644 zig-ecs/src/process/process.zig create mode 100644 zig-ecs/src/process/scheduler.zig diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index b2aee4e..64b2132 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -299,18 +299,6 @@ pub const OwningGroup = struct { 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 = - // } - // } } }; diff --git a/zig-ecs/src/process/process.zig b/zig-ecs/src/process/process.zig new file mode 100644 index 0000000..104d3b1 --- /dev/null +++ b/zig-ecs/src/process/process.zig @@ -0,0 +1,92 @@ +pub const Process = struct { + const State = enum(u8) { + uninitialized, running, paused, succeeded, failed, aborted, finished + }; + + updateFn: fn (self: *Process) void, + initFn: ?fn (self: *Process) void = null, + abortedFn: ?fn (self: *Process) void = null, + failedFn: ?fn (self: *Process) void = null, + succeededFn: ?fn (self: *Process) void = null, + + state: State = .uninitialized, + stopped: bool = false, + + /// Terminates a process with success if it's still alive + pub fn succeed(self: *Process) void { + if (self.alive()) self.state = .succeeded; + } + + /// Terminates a process with errors if it's still alive + pub fn fail(self: *Process) void { + if (self.alive()) self.state = .failed; + } + + /// Stops a process if it's in a running state + pub fn pause(self: *ParentType) void { + if (self.state == .running) self.state = .paused; + } + + /// Restarts a process if it's paused + pub fn unpause(self: *Process) void { + if (self.state == .paused) self.state = .running; + } + + /// Aborts a process if it's still alive + pub fn abort(self: *Process, immediately: bool) void { + if (self.alive()) { + self.state = .aborted; + + if (immediately) { + self.tick(); + } + } + } + + /// Returns true if a process is either running or paused + pub fn alive(self: Process) bool { + return self.state == .running or self.state == .paused; + } + + /// Returns true if a process is already terminated + pub fn dead(self: Process) bool { + return self.state == .finished; + } + + pub fn rejected(self: Process) bool { + return self.stopped; + } + + /// Updates a process and its internal state if required + pub fn tick(self: *Process) void { + switch (self.state) { + .uninitialized => { + if (self.initFn) |func| func(self); + self.state = .running; + }, + .running => { + self.updateFn(self); + }, + else => {}, + } + + // if it's dead, it must be notified and removed immediately + switch (self.state) { + .succeeded => { + if (self.succeededFn) |func| func(self); + self.state = .finished; + }, + .failed => { + if (self.failedFn) |func| func(self); + self.state = .finished; + self.stopped = true; + }, + .aborted => { + if (self.abortedFn) |func| func(self); + self.state = .finished; + self.stopped = true; + }, + else => {}, + } + } +}; diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig new file mode 100644 index 0000000..7a2e3a4 --- /dev/null +++ b/zig-ecs/src/process/scheduler.zig @@ -0,0 +1,200 @@ +const std = @import("std"); +const Process = @import("process.zig").Process; + +pub const Scheduler = struct { + handlers: std.ArrayList(ProcessHandler), + allocator: *std.mem.Allocator, + + fn createProcessHandler(comptime T: type) ProcessHandler { + var proc = std.testing.allocator.create(T) catch unreachable; + proc.initialize(); + + // get a closure so that we can safely deinit this later + var handlerDeinitFn = struct { + fn deinit(process: *Process, allocator: *std.mem.Allocator) void { + allocator.destroy(@fieldParentPtr(T, "process", process)); + } + }.deinit; + + return .{ + .process = &proc.process, + .deinitChild = handlerDeinitFn, + }; + } + + const Continuation = struct { + handler: *ProcessHandler, + + pub fn init(handler: *ProcessHandler) Continuation { + return .{.handler = handler}; + } + + // TODO: fix and return when ProcessHandler can have next be a ProcessHandler + pub fn next(self: *@This(), comptime T: type) void { // *@This() + var next_handler = createProcessHandler(T); + self.handler.next = .{.deinitChild = next_handler.deinitChild, .process = next_handler.process}; + } + }; + + // TODO: remove this when ProcessHandler can have next be a ProcessHandler + const NextProcessHandler = struct { + deinitChild: fn (process: *Process, allocator: *std.mem.Allocator) void, + process: *Process, + + pub fn asProcessHandler(self: @This()) ProcessHandler { + return .{.deinitChild = self.deinitChild, .process = self.process}; + } + }; + + const ProcessHandler = struct { + deinitChild: fn (process: *Process, allocator: *std.mem.Allocator) void, + process: *Process, + next: ?NextProcessHandler = null, + + pub fn update(self: *ProcessHandler, allocator: *std.mem.Allocator) bool { + self.process.tick(); + + if (self.process.dead()) { + if (!self.process.rejected() and self.next != null) { + // kill the old Process parent + self.deinitChild(self.process, allocator); + + // overwrite our fields and kick off the next process + self.deinitChild = self.next.?.deinitChild; + self.process = self.next.?.process; + self.next = null; // TODO: when ProcessHandler can have next be a ProcessHandler + return self.update(allocator); + } else { + return true; + } + } + + return false; + } + + pub fn deinit(self: @This(), allocator: *std.mem.Allocator) void { + if (self.next) |next_handler| { + next_handler.asProcessHandler().deinit(allocator); + } + self.deinitChild(self.process, allocator); + } + }; + + pub fn init(allocator: *std.mem.Allocator) Scheduler { + return .{ + .handlers = std.ArrayList(ProcessHandler).init(allocator), + .allocator = allocator, + }; + } + + pub fn deinit(self: Scheduler) void { + for (self.handlers.items) |handler| { + handler.deinit(self.allocator); + } + self.handlers.deinit(); + } + + /// Schedules a process for the next tick + pub fn attach(self: *Scheduler, comptime T: type) Continuation { + std.debug.assert(@hasDecl(T, "initialize")); + std.debug.assert(@hasField(T, "process")); + + var handler = createProcessHandler(T); + handler.process.tick(); + + self.handlers.append(handler) catch unreachable; + return Continuation.init(&self.handlers.items[self.handlers.items.len - 1]); + } + + /// Updates all scheduled processes + pub fn update(self: *Scheduler) void { + if (self.handlers.items.len == 0) return; + + var i: usize = self.handlers.items.len - 1; + while (true) : (i -= 1) { + if (self.handlers.items[i].update(self.allocator)) { + var dead_handler = self.handlers.swapRemove(i); + dead_handler.deinit(self.allocator); + } + + if (i == 0) break; + } + } + + /// gets the number of processes still running + pub fn len(self: Scheduler) usize { + return self.handlers.items.len; + } + + /// resets the scheduler to its initial state and discards all the processes + pub fn clear(self: *Scheduler) void { + for (self.handlers.items) |handler| { + handler.deinit(handler.process, self.allocator); + } + self.handlers.items.len = 0; + } + + /// Aborts all scheduled processes. Unless an immediate operation is requested, the abort is scheduled for the next tick + pub fn abort(self: *Scheduler, immediately: bool) void { + for (self.handlers.items) |handler| { + handler.process.abort(immediately); + } + } +}; + +var fart: usize = 666; + +test "" { + std.debug.warn("\n", .{}); + + const Tester = struct { + process: Process, + fart: usize, + + pub fn initialize(self: *@This()) void { + self.process = .{ + .initFn = init, + .updateFn = update, + .abortedFn = aborted, + .failedFn = failed, + .succeededFn = succeeded, + }; + self.fart = fart; + fart += 111; + } + + fn init(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("init {}\n", .{self.fart}); + } + + fn aborted(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("aborted {}\n", .{self.fart}); + } + + fn failed(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("failed {}\n", .{self.fart}); + } + + fn succeeded(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("succeeded {}\n", .{self.fart}); + } + + fn update(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("update {}\n", .{self.fart}); + process.succeed(); + } + }; + + var scheduler = Scheduler.init(std.testing.allocator); + defer scheduler.deinit(); + + _ = scheduler.attach(Tester).next(Tester); + scheduler.update(); + scheduler.update(); + scheduler.update(); +} From cdef61d6dc0907fed81f025972804443136b7ac0 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 13 Jun 2020 21:08:00 -0700 Subject: [PATCH 058/146] Schedular working --- zig-ecs/src/process/process.zig | 12 +++- zig-ecs/src/process/scheduler.zig | 109 ++++++++++++++++++++++-------- 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/zig-ecs/src/process/process.zig b/zig-ecs/src/process/process.zig index 104d3b1..42379e6 100644 --- a/zig-ecs/src/process/process.zig +++ b/zig-ecs/src/process/process.zig @@ -1,10 +1,12 @@ +/// Processes are run by the Scheduler. They use a similar pattern to Allocators in that they are created and +/// added as fields in a parent struct, your actual process that will be run. pub const Process = struct { const State = enum(u8) { uninitialized, running, paused, succeeded, failed, aborted, finished }; updateFn: fn (self: *Process) void, - initFn: ?fn (self: *Process) void = null, + startFn: ?fn (self: *Process) void = null, abortedFn: ?fn (self: *Process) void = null, failedFn: ?fn (self: *Process) void = null, succeededFn: ?fn (self: *Process) void = null, @@ -12,6 +14,10 @@ pub const Process = struct { state: State = .uninitialized, stopped: bool = false, + pub fn getParent(self: *Process, comptime T: type) *T { + return @fieldParentPtr(T, "process", self); + } + /// Terminates a process with success if it's still alive pub fn succeed(self: *Process) void { if (self.alive()) self.state = .succeeded; @@ -57,11 +63,11 @@ pub const Process = struct { return self.stopped; } - /// Updates a process and its internal state if required + /// Updates a process and its internal state pub fn tick(self: *Process) void { switch (self.state) { .uninitialized => { - if (self.initFn) |func| func(self); + if (self.startFn) |func| func(self); self.state = .running; }, .running => { diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index 7a2e3a4..05fcbcf 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -1,13 +1,23 @@ const std = @import("std"); const Process = @import("process.zig").Process; +/// Cooperative scheduler for processes. Each process is invoked once per tick. If a process terminates, it's +/// removed automatically from the scheduler and it's never invoked again. A process can also have a child. In +/// this case, the process is replaced with its child when it terminates if it returns with success. In case of errors, +/// both the process and its child are discarded. In order to invoke all scheduled processes, call the `update` member function +/// Processes add themselves by calling `attach` and must satisfy the following conditions: +/// - have a field `process: Process` +/// - have a method `initialize(self: *@This(), data: var) void` that initializes all fields and takes in a the data passed to `attach` +/// - when initializing the `process` field it ust be given an `updateFn`. All other callbacks are optional. +/// - in any callback you can get your oiginal struct back via `process.getParent(@This())` pub const Scheduler = struct { handlers: std.ArrayList(ProcessHandler), allocator: *std.mem.Allocator, - fn createProcessHandler(comptime T: type) ProcessHandler { + /// helper to create and prepare a process and wrap it in a ProcessHandler + fn createProcessHandler(comptime T: type, data: var) ProcessHandler { var proc = std.testing.allocator.create(T) catch unreachable; - proc.initialize(); + proc.initialize(data); // get a closure so that we can safely deinit this later var handlerDeinitFn = struct { @@ -26,13 +36,13 @@ pub const Scheduler = struct { handler: *ProcessHandler, pub fn init(handler: *ProcessHandler) Continuation { - return .{.handler = handler}; + return .{ .handler = handler }; } - // TODO: fix and return when ProcessHandler can have next be a ProcessHandler - pub fn next(self: *@This(), comptime T: type) void { // *@This() - var next_handler = createProcessHandler(T); - self.handler.next = .{.deinitChild = next_handler.deinitChild, .process = next_handler.process}; + // TODO: fix and return this when ProcessHandler can have next be a ProcessHandler + pub fn next(self: *@This(), comptime T: type, data: var) void { // *@This() + var next_handler = createProcessHandler(T, data); + self.handler.next = .{ .deinitChild = next_handler.deinitChild, .process = next_handler.process }; } }; @@ -42,7 +52,7 @@ pub const Scheduler = struct { process: *Process, pub fn asProcessHandler(self: @This()) ProcessHandler { - return .{.deinitChild = self.deinitChild, .process = self.process}; + return .{ .deinitChild = self.deinitChild, .process = self.process }; } }; @@ -87,19 +97,17 @@ pub const Scheduler = struct { }; } - pub fn deinit(self: Scheduler) void { - for (self.handlers.items) |handler| { - handler.deinit(self.allocator); - } + pub fn deinit(self: *Scheduler) void { + self.clear(); self.handlers.deinit(); } /// Schedules a process for the next tick - pub fn attach(self: *Scheduler, comptime T: type) Continuation { + pub fn attach(self: *Scheduler, comptime T: type, data: var) Continuation { std.debug.assert(@hasDecl(T, "initialize")); std.debug.assert(@hasField(T, "process")); - var handler = createProcessHandler(T); + var handler = createProcessHandler(T, data); handler.process.tick(); self.handlers.append(handler) catch unreachable; @@ -129,7 +137,7 @@ pub const Scheduler = struct { /// resets the scheduler to its initial state and discards all the processes pub fn clear(self: *Scheduler) void { for (self.handlers.items) |handler| { - handler.deinit(handler.process, self.allocator); + handler.deinit(self.allocator); } self.handlers.items.len = 0; } @@ -142,8 +150,6 @@ pub const Scheduler = struct { } }; -var fart: usize = 666; - test "" { std.debug.warn("\n", .{}); @@ -151,41 +157,40 @@ test "" { process: Process, fart: usize, - pub fn initialize(self: *@This()) void { + pub fn initialize(self: *@This(), data: var) void { self.process = .{ - .initFn = init, + .startFn = start, .updateFn = update, .abortedFn = aborted, .failedFn = failed, .succeededFn = succeeded, }; - self.fart = fart; - fart += 111; + self.fart = data; } - fn init(process: *Process) void { + fn start(process: *Process) void { const self = @fieldParentPtr(@This(), "process", process); - std.debug.warn("init {}\n", .{self.fart}); + // std.debug.warn("start {}\n", .{self.fart}); } fn aborted(process: *Process) void { const self = @fieldParentPtr(@This(), "process", process); - std.debug.warn("aborted {}\n", .{self.fart}); + // std.debug.warn("aborted {}\n", .{self.fart}); } fn failed(process: *Process) void { const self = @fieldParentPtr(@This(), "process", process); - std.debug.warn("failed {}\n", .{self.fart}); + // std.debug.warn("failed {}\n", .{self.fart}); } fn succeeded(process: *Process) void { const self = @fieldParentPtr(@This(), "process", process); - std.debug.warn("succeeded {}\n", .{self.fart}); + // std.debug.warn("succeeded {}\n", .{self.fart}); } fn update(process: *Process) void { const self = @fieldParentPtr(@This(), "process", process); - std.debug.warn("update {}\n", .{self.fart}); + // std.debug.warn("update {}\n", .{self.fart}); process.succeed(); } }; @@ -193,8 +198,56 @@ test "" { var scheduler = Scheduler.init(std.testing.allocator); defer scheduler.deinit(); - _ = scheduler.attach(Tester).next(Tester); + _ = scheduler.attach(Tester, 33).next(Tester, 66); scheduler.update(); scheduler.update(); scheduler.update(); } + +test "scheduler.clear" { + const Tester = struct { + process: Process, + + pub fn initialize(self: *@This(), data: var) void { + self.process = .{ .updateFn = update }; + } + + fn update(process: *Process) void { + std.debug.assert(false); + } + }; + + var scheduler = Scheduler.init(std.testing.allocator); + defer scheduler.deinit(); + + _ = scheduler.attach(Tester, {}).next(Tester, {}); + scheduler.clear(); + scheduler.update(); +} + +test "scheduler.attach.next" { + const Tester = struct { + process: Process, + counter: *usize, + + pub fn initialize(self: *@This(), data: var) void { + self.process = .{ .updateFn = update }; + self.counter = data; + } + + fn update(process: *Process) void { + const self = process.getParent(@This()); + self.counter.* += 1; + process.succeed(); + } + }; + + var scheduler = Scheduler.init(std.testing.allocator); + defer scheduler.deinit(); + + var counter: usize = 0; + _ = scheduler.attach(Tester, &counter).next(Tester, &counter); + scheduler.update(); + scheduler.update(); + std.testing.expectEqual(counter, 2); +} From dd52ed2f46332157ee40d1ab52ae1431f9650e61 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 13 Jun 2020 21:19:48 -0700 Subject: [PATCH 059/146] comments --- zig-ecs/src/process/scheduler.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index 05fcbcf..6409649 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -46,7 +46,8 @@ pub const Scheduler = struct { } }; - // TODO: remove this when ProcessHandler can have next be a ProcessHandler + /// TODO: remove this when ProcessHandler can have `next` be a ProcessHandler. For now this acts as a data store + /// holding the data ProcessHandler requires. const NextProcessHandler = struct { deinitChild: fn (process: *Process, allocator: *std.mem.Allocator) void, process: *Process, From 2beaeedb6f1b69608c4b4bdc588098dbcc7a5619 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 13 Jun 2020 21:57:27 -0700 Subject: [PATCH 060/146] scheduler done --- zig-ecs/src/process/process.zig | 4 + zig-ecs/src/process/scheduler.zig | 140 +++++++++++++----------------- zig-ecs/src/tests.zig | 3 + 3 files changed, 67 insertions(+), 80 deletions(-) diff --git a/zig-ecs/src/process/process.zig b/zig-ecs/src/process/process.zig index 42379e6..42ddfc2 100644 --- a/zig-ecs/src/process/process.zig +++ b/zig-ecs/src/process/process.zig @@ -1,3 +1,5 @@ +const std = @import("std"); + /// Processes are run by the Scheduler. They use a similar pattern to Allocators in that they are created and /// added as fields in a parent struct, your actual process that will be run. pub const Process = struct { @@ -10,9 +12,11 @@ pub const Process = struct { abortedFn: ?fn (self: *Process) void = null, failedFn: ?fn (self: *Process) void = null, succeededFn: ?fn (self: *Process) void = null, + deinit: fn (self: *Process, allocator: *std.mem.Allocator) void = undefined, state: State = .uninitialized, stopped: bool = false, + next: ?*Process = null, pub fn getParent(self: *Process, comptime T: type) *T { return @fieldParentPtr(T, "process", self); diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index 6409649..1281fc4 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -11,96 +11,52 @@ const Process = @import("process.zig").Process; /// - when initializing the `process` field it ust be given an `updateFn`. All other callbacks are optional. /// - in any callback you can get your oiginal struct back via `process.getParent(@This())` pub const Scheduler = struct { - handlers: std.ArrayList(ProcessHandler), + processes: std.ArrayList(*Process), allocator: *std.mem.Allocator, /// helper to create and prepare a process and wrap it in a ProcessHandler - fn createProcessHandler(comptime T: type, data: var) ProcessHandler { + fn createProcessHandler(comptime T: type, data: var) *Process { var proc = std.testing.allocator.create(T) catch unreachable; proc.initialize(data); // get a closure so that we can safely deinit this later - var handlerDeinitFn = struct { + proc.process.deinit = struct { fn deinit(process: *Process, allocator: *std.mem.Allocator) void { + if (process.next) |next_process| { + next_process.deinit(next_process, allocator); + } allocator.destroy(@fieldParentPtr(T, "process", process)); } }.deinit; - return .{ - .process = &proc.process, - .deinitChild = handlerDeinitFn, - }; + return &proc.process; } + /// returned when appending a process so that sub-processes can be added to the process const Continuation = struct { - handler: *ProcessHandler, - - pub fn init(handler: *ProcessHandler) Continuation { - return .{ .handler = handler }; - } - - // TODO: fix and return this when ProcessHandler can have next be a ProcessHandler - pub fn next(self: *@This(), comptime T: type, data: var) void { // *@This() - var next_handler = createProcessHandler(T, data); - self.handler.next = .{ .deinitChild = next_handler.deinitChild, .process = next_handler.process }; - } - }; - - /// TODO: remove this when ProcessHandler can have `next` be a ProcessHandler. For now this acts as a data store - /// holding the data ProcessHandler requires. - const NextProcessHandler = struct { - deinitChild: fn (process: *Process, allocator: *std.mem.Allocator) void, process: *Process, - pub fn asProcessHandler(self: @This()) ProcessHandler { - return .{ .deinitChild = self.deinitChild, .process = self.process }; + pub fn init(process: *Process) Continuation { + return .{ .process = process }; } - }; - const ProcessHandler = struct { - deinitChild: fn (process: *Process, allocator: *std.mem.Allocator) void, - process: *Process, - next: ?NextProcessHandler = null, - - pub fn update(self: *ProcessHandler, allocator: *std.mem.Allocator) bool { - self.process.tick(); - - if (self.process.dead()) { - if (!self.process.rejected() and self.next != null) { - // kill the old Process parent - self.deinitChild(self.process, allocator); - - // overwrite our fields and kick off the next process - self.deinitChild = self.next.?.deinitChild; - self.process = self.next.?.process; - self.next = null; // TODO: when ProcessHandler can have next be a ProcessHandler - return self.update(allocator); - } else { - return true; - } - } - - return false; - } - - pub fn deinit(self: @This(), allocator: *std.mem.Allocator) void { - if (self.next) |next_handler| { - next_handler.asProcessHandler().deinit(allocator); - } - self.deinitChild(self.process, allocator); + pub fn next(self: *@This(), comptime T: type, data: var) *@This() { + self.process.next = createProcessHandler(T, data); + self.process = self.process.next.?; + return self; } }; pub fn init(allocator: *std.mem.Allocator) Scheduler { return .{ - .handlers = std.ArrayList(ProcessHandler).init(allocator), + .processes = std.ArrayList(*Process).init(allocator), .allocator = allocator, }; } pub fn deinit(self: *Scheduler) void { self.clear(); - self.handlers.deinit(); + self.processes.deinit(); } /// Schedules a process for the next tick @@ -108,22 +64,44 @@ pub const Scheduler = struct { std.debug.assert(@hasDecl(T, "initialize")); std.debug.assert(@hasField(T, "process")); - var handler = createProcessHandler(T, data); - handler.process.tick(); + var process = createProcessHandler(T, data); + process.tick(); + + self.processes.append(process) catch unreachable; + return Continuation.init(process); + } + + fn updateProcess(process: **Process, allocator: *std.mem.Allocator) bool { + const current_process = process.*; + current_process.tick(); + + if (current_process.dead()) { + if (!current_process.rejected() and current_process.next != null) { + // grab the next process and null it out so we dont double-free it later + const next_process = current_process.next.?; + current_process.next = null; + process.* = next_process; + + // kill the old Process parent + current_process.deinit(current_process, allocator); + return updateProcess(process, allocator); + } else { + return true; + } + } - self.handlers.append(handler) catch unreachable; - return Continuation.init(&self.handlers.items[self.handlers.items.len - 1]); + return false; } /// Updates all scheduled processes pub fn update(self: *Scheduler) void { - if (self.handlers.items.len == 0) return; + if (self.processes.items.len == 0) return; - var i: usize = self.handlers.items.len - 1; + var i: usize = self.processes.items.len - 1; while (true) : (i -= 1) { - if (self.handlers.items[i].update(self.allocator)) { - var dead_handler = self.handlers.swapRemove(i); - dead_handler.deinit(self.allocator); + if (updateProcess(&self.processes.items[i], self.allocator)) { + var dead_process = self.processes.swapRemove(i); + dead_process.deinit(dead_process, self.allocator); } if (i == 0) break; @@ -132,20 +110,20 @@ pub const Scheduler = struct { /// gets the number of processes still running pub fn len(self: Scheduler) usize { - return self.handlers.items.len; + return self.processes.items.len; } /// resets the scheduler to its initial state and discards all the processes pub fn clear(self: *Scheduler) void { - for (self.handlers.items) |handler| { - handler.deinit(self.allocator); + for (self.processes.items) |process| { + process.deinit(process, self.allocator); } - self.handlers.items.len = 0; + self.processes.items.len = 0; } /// Aborts all scheduled processes. Unless an immediate operation is requested, the abort is scheduled for the next tick pub fn abort(self: *Scheduler, immediately: bool) void { - for (self.handlers.items) |handler| { + for (self.processes.items) |handler| { handler.process.abort(immediately); } } @@ -170,27 +148,27 @@ test "" { } fn start(process: *Process) void { - const self = @fieldParentPtr(@This(), "process", process); + const self = process.getParent(@This()); // std.debug.warn("start {}\n", .{self.fart}); } fn aborted(process: *Process) void { - const self = @fieldParentPtr(@This(), "process", process); + const self = process.getParent(@This()); // std.debug.warn("aborted {}\n", .{self.fart}); } fn failed(process: *Process) void { - const self = @fieldParentPtr(@This(), "process", process); + const self = process.getParent(@This()); // std.debug.warn("failed {}\n", .{self.fart}); } fn succeeded(process: *Process) void { - const self = @fieldParentPtr(@This(), "process", process); + const self = process.getParent(@This()); // std.debug.warn("succeeded {}\n", .{self.fart}); } fn update(process: *Process) void { - const self = @fieldParentPtr(@This(), "process", process); + const self = process.getParent(@This()); // std.debug.warn("update {}\n", .{self.fart}); process.succeed(); } @@ -199,7 +177,9 @@ test "" { var scheduler = Scheduler.init(std.testing.allocator); defer scheduler.deinit(); - _ = scheduler.attach(Tester, 33).next(Tester, 66); + _ = scheduler.attach(Tester, 33).next(Tester, 66).next(Tester, 88).next(Tester, 99); + scheduler.update(); + scheduler.update(); scheduler.update(); scheduler.update(); scheduler.update(); diff --git a/zig-ecs/src/tests.zig b/zig-ecs/src/tests.zig index 5d91e6d..70d69f1 100644 --- a/zig-ecs/src/tests.zig +++ b/zig-ecs/src/tests.zig @@ -17,4 +17,7 @@ comptime { // resources _ = @import("resources/cache.zig"); _ = @import("resources/assets.zig"); + + // process + _ = @import("process/scheduler.zig"); } From 73080c2cfceae618097b285b583bacf1e82c7167 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 14 Jun 2020 14:31:15 -0700 Subject: [PATCH 061/146] add view vs group --- zig-ecs/.vscode/tasks.json | 33 ++++++++++++++++ zig-ecs/build.zig | 2 +- zig-ecs/examples/view_vs_group.zig | 63 ++++++++++++++++++++++++++++++ zig-ecs/src/ecs/groups.zig | 2 +- 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 zig-ecs/examples/view_vs_group.zig diff --git a/zig-ecs/.vscode/tasks.json b/zig-ecs/.vscode/tasks.json index 425f970..dce0dbc 100644 --- a/zig-ecs/.vscode/tasks.json +++ b/zig-ecs/.vscode/tasks.json @@ -21,6 +21,39 @@ "group": { "kind": "build", "isDefault": true + }, + "presentation": { + "clear": true + } + }, + { + "label": "Build and Run Project (release-fast)", + "type": "shell", + "command": "zig build run -Drelease-fast", + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "clear": true + } + }, + { + "label": "Build and Run Project (release-small)", + "type": "shell", + "command": "zig build run -Drelease-small", + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "clear": true } }, { diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index f3d58d0..de4d04e 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -5,8 +5,8 @@ pub fn build(b: *Builder) void { const buildMode = b.standardReleaseOptions(); const examples = [_][2][]const u8{ + [_][]const u8{ "view_vs_group", "examples/view_vs_group.zig" }, [_][]const u8{ "simple", "examples/simple.zig" }, - // [_][]const u8{ "mesh", "examples/mesh.zig" }, }; for (examples) |example, i| { diff --git a/zig-ecs/examples/view_vs_group.zig b/zig-ecs/examples/view_vs_group.zig new file mode 100644 index 0000000..59a753a --- /dev/null +++ b/zig-ecs/examples/view_vs_group.zig @@ -0,0 +1,63 @@ +const std = @import("std"); +const ecs = @import("ecs"); + +// override the EntityTraits used by ecs +pub const EntityTraits = ecs.EntityTraitsType(.medium); + +pub const Velocity = struct { x: f32, y: f32 }; +pub const Position = struct { x: f32, y: f32 }; + +/// logs the timing for views vs groups with 1,000,000 entities +pub fn main() !void { + var reg = ecs.Registry.init(std.heap.c_allocator); + defer reg.deinit(); + + var timer = try std.time.Timer.start(); + var i: usize = 0; + while (i < 1000000) : (i += 1) { + var e1 = reg.create(); + reg.add(e1, Position{ .x = 1, .y = 1 }); + reg.add(e1, Velocity{ .x = 1, .y = 1 }); + } + var end = timer.lap(); + std.debug.warn("create: \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + + var view = reg.view(.{ Velocity, Position }, .{}); + + timer.reset(); + var iter = view.iterator(); + while (iter.next()) |entity| { + var pos = view.get(Position, entity); + const vel = view.getConst(Velocity, entity); + + pos.*.x += vel.x; + pos.*.y += vel.y; + } + + end = timer.lap(); + std.debug.warn("view (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + + var group = reg.group(.{ Velocity, Position }, .{}, .{}); + end = timer.lap(); + std.debug.warn("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + + timer.reset(); + var group_iter = group.iterator(struct { vel: *Velocity, pos: *Position }); + while (group_iter.next()) |e| { + e.pos.*.x += e.vel.x; + e.pos.*.y += e.vel.y; + } + + end = timer.lap(); + std.debug.warn("group (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + + timer.reset(); + group.each(each); + end = timer.read(); + std.debug.warn("group (each): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); +} + +fn each(e: struct { vel: *Velocity, pos: *Position }) void { + e.pos.*.x += e.vel.x; + e.pos.*.y += e.vel.y; +} diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 64b2132..ef55d71 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -229,7 +229,7 @@ pub const OwningGroup = struct { @field(comps, field.name) = &typed_ptr[entity_index]; } - @call(.{ .modifier = .always_inline }, func, .{comps}); + func(comps); } } From e76971101b3968d8f3a803e8c481b0093a46ec6a Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 14 Jun 2020 14:53:48 -0700 Subject: [PATCH 062/146] fixes and tests --- zig-ecs/src/ecs/registry.zig | 2 +- zig-ecs/src/ecs/views.zig | 10 ++++----- zig-ecs/tests/groups_test.zig | 42 +++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index a6c8e0d..58ed069 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -231,7 +231,7 @@ pub const Registry = struct { /// Returns the number of existing components of the given type pub fn len(self: *Registry, comptime T: type) usize { - self.assure(T).len(); + return self.assure(T).len(); } /// Increases the capacity of the registry or of the pools for the given component diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index 65506f4..44f12fa 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -43,12 +43,12 @@ pub fn BasicView(comptime T: type) type { return self.storage.getConst(entity); } - pub fn entityIterator(self: Self) utils.ReverseSliceIterator(Entity) { - return self.storage.set.reverseIterator(); + pub fn iterator(self: Self) utils.ReverseSliceIterator(T) { + return utils.ReverseSliceIterator(T).init(self.storage.instances.items); } - pub fn componentIterator(self: Self) utils.ReverseSliceIterator(T) { - return utils.ReverseSliceIterator(T).init(self.storage.instances.items); + pub fn entityIterator(self: Self) utils.ReverseSliceIterator(Entity) { + return self.storage.set.reverseIterator(); } }; } @@ -165,7 +165,7 @@ test "single basic view" { std.testing.expectEqual(view.len(), 2); var i: usize = 0; - var iter = view.componentIterator(); + var iter = view.iterator(); while (iter.next()) |comp| { if (i == 0) std.testing.expectEqual(comp, 50); if (i == 1) std.testing.expectEqual(comp, 30); diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index 39b3b4a..94c93f6 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -145,6 +145,48 @@ test "sort OwningGroup by Component" { } } +test "sort OwningGroup by Component ensure unsorted non-matches" { + 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) }); + + var e2 = reg.create(); + reg.add(e2, Sprite{ .x = @intToFloat(f32, i + 1 * 50) }); + } + + std.testing.expectEqual(group.len(), 5); + std.testing.expectEqual(reg.len(Sprite), 10); + + const SortContext = struct { + fn sort(this: void, a: Sprite, b: Sprite) bool { + // sprites with x > 50 shouldnt match in the group + std.testing.expect(a.x < 50 and b.x < 50); + return a.x > b.x; + } + }; + group.sort(Sprite, {}, SortContext.sort); + + // all the + var view = reg.view(.{Sprite}, .{}); + var count: usize = 0; + var iter = view.iterator(); + while (iter.next()) |sprite| { + count += 1; + + // all sprite.x > 50 should be at the end and we iterate backwards + if (count < 6) { + std.testing.expect(sprite.x >= 50); + } + } +} + test "nested OwningGroups add/remove components" { var reg = Registry.init(std.testing.allocator); defer reg.deinit(); From df926f965c0d9e64cae03285c80b4e9cd92846b8 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 15 Jun 2020 11:09:36 -0700 Subject: [PATCH 063/146] fix group bugs --- zig-ecs/examples/view_vs_group.zig | 47 ++++++++++++++++++++++++++---- zig-ecs/src/ecs/registry.zig | 3 ++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/zig-ecs/examples/view_vs_group.zig b/zig-ecs/examples/view_vs_group.zig index 59a753a..ec52e5c 100644 --- a/zig-ecs/examples/view_vs_group.zig +++ b/zig-ecs/examples/view_vs_group.zig @@ -7,12 +7,21 @@ pub const EntityTraits = ecs.EntityTraitsType(.medium); pub const Velocity = struct { x: f32, y: f32 }; pub const Position = struct { x: f32, y: f32 }; -/// logs the timing for views vs groups with 1,000,000 entities +/// logs the timing for views vs non-owning groups vs owning groups with 1,000,000 entities pub fn main() !void { var reg = ecs.Registry.init(std.heap.c_allocator); defer reg.deinit(); var timer = try std.time.Timer.start(); + + createEntities(®); + iterateView(®); + nonOwningGroup(®); + owningGroup(®); +} + +fn createEntities(reg: *ecs.Registry) void { + var timer = std.time.Timer.start() catch unreachable; var i: usize = 0; while (i < 1000000) : (i += 1) { var e1 = reg.create(); @@ -20,11 +29,14 @@ pub fn main() !void { reg.add(e1, Velocity{ .x = 1, .y = 1 }); } var end = timer.lap(); - std.debug.warn("create: \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.warn("create entities: \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); +} +fn iterateView(reg: *ecs.Registry) void { + std.debug.warn("--- multi-view ---\n", .{}); var view = reg.view(.{ Velocity, Position }, .{}); - timer.reset(); + var timer = std.time.Timer.start() catch unreachable; var iter = view.iterator(); while (iter.next()) |entity| { var pos = view.get(Position, entity); @@ -34,11 +46,36 @@ pub fn main() !void { pos.*.y += vel.y; } - end = timer.lap(); + var end = timer.lap(); std.debug.warn("view (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); +} + +fn nonOwningGroup(reg: *ecs.Registry) void { + std.debug.warn("--- non-owning ---\n", .{}); + var timer = std.time.Timer.start() catch unreachable; + var group = reg.group(.{}, .{Velocity, Position}, .{}); + var end = timer.lap(); + std.debug.warn("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + + timer.reset(); + var group_iter = group.iterator(); + while (group_iter.next()) |entity| { + var pos = group.get(Position, entity); + const vel = group.getConst(Velocity, entity); + + pos.*.x += vel.x; + pos.*.y += vel.y; + } - var group = reg.group(.{ Velocity, Position }, .{}, .{}); end = timer.lap(); + std.debug.warn("group (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); +} + +fn owningGroup(reg: *ecs.Registry) void { + std.debug.warn("--- owning ---\n", .{}); + var timer = std.time.Timer.start() catch unreachable; + var group = reg.group(.{ Velocity, Position }, .{}, .{}); + var end = timer.lap(); std.debug.warn("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); timer.reset(); diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 58ed069..dbadfb7 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -176,6 +176,7 @@ pub const Registry = struct { } if (overlapping > 0) return i; + if (i == 0) return null; } return null; @@ -615,6 +616,8 @@ pub const Registry = struct { /// expects a tuple of types. Convertes them to type names, sorts them then concatenates and returns the string. inline fn concatTypes(comptime types: var) []const u8 { comptime { + if (types.len == 0) return "_"; + const impl = struct { fn asc(context: void, lhs: []const u8, rhs: []const u8) bool { return std.mem.lessThan(u8, lhs, rhs); From 57a27bd57dc8e8a969bcf64655f03f909d0b3203 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 15 Jun 2020 23:33:27 -0700 Subject: [PATCH 064/146] dont use testing allocator --- zig-ecs/src/process/scheduler.zig | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index 1281fc4..ae09087 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -14,9 +14,9 @@ pub const Scheduler = struct { processes: std.ArrayList(*Process), allocator: *std.mem.Allocator, - /// helper to create and prepare a process and wrap it in a ProcessHandler - fn createProcessHandler(comptime T: type, data: var) *Process { - var proc = std.testing.allocator.create(T) catch unreachable; + /// helper to create and prepare a process + fn createProcessHandler(comptime T: type, data: var, allocator: *std.mem.Allocator) *Process { + var proc = allocator.create(T) catch unreachable; proc.initialize(data); // get a closure so that we can safely deinit this later @@ -35,13 +35,14 @@ pub const Scheduler = struct { /// returned when appending a process so that sub-processes can be added to the process const Continuation = struct { process: *Process, + allocator: *std.mem.Allocator, - pub fn init(process: *Process) Continuation { - return .{ .process = process }; + pub fn init(process: *Process, allocator: *std.mem.Allocator) Continuation { + return .{ .process = process, .allocator = allocator }; } pub fn next(self: *@This(), comptime T: type, data: var) *@This() { - self.process.next = createProcessHandler(T, data); + self.process.next = createProcessHandler(T, data, self.allocator); self.process = self.process.next.?; return self; } @@ -64,11 +65,11 @@ pub const Scheduler = struct { std.debug.assert(@hasDecl(T, "initialize")); std.debug.assert(@hasField(T, "process")); - var process = createProcessHandler(T, data); + var process = createProcessHandler(T, data, self.allocator); process.tick(); self.processes.append(process) catch unreachable; - return Continuation.init(process); + return Continuation.init(process, self.allocator); } fn updateProcess(process: **Process, allocator: *std.mem.Allocator) bool { From c61f6e998d7e66944db700845449dec71685dba5 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 16 Jun 2020 13:19:41 -0700 Subject: [PATCH 065/146] valgrinding --- zig-ecs/src/ecs/sparse_set.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index ba0b090..f7ed049 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -59,8 +59,10 @@ pub fn SparseSet(comptime SparseT: type) type { fn assure(self: *Self, pos: usize) []SparseT { if (pos >= self.sparse.items.len) { + const start_pos = self.sparse.items.len; self.sparse.resize(pos + 1) catch unreachable; self.sparse.expandToCapacity(); + std.mem.set(?[]SparseT, self.sparse.items[start_pos..], null); } if (self.sparse.items[pos]) |arr| { From 7874ddf6b61283b4c92ccf6e3234ece1ca39755f Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 16 Jun 2020 13:33:19 -0700 Subject: [PATCH 066/146] valgrinded --- zig-ecs/build.zig | 1 + zig-ecs/src/process/scheduler.zig | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index de4d04e..92cdbb0 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -16,6 +16,7 @@ pub fn build(b: *Builder) void { var exe = b.addExecutable(name, source); exe.setBuildMode(b.standardReleaseOptions()); exe.addPackagePath("ecs", "src/ecs.zig"); + exe.linkSystemLibrary("c"); const run_cmd = exe.run(); const exe_step = b.step(name, b.fmt("run {}.zig", .{name})); diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index ae09087..a9ce9bc 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -21,11 +21,11 @@ pub const Scheduler = struct { // get a closure so that we can safely deinit this later proc.process.deinit = struct { - fn deinit(process: *Process, allocator: *std.mem.Allocator) void { + fn deinit(process: *Process, alloc: *std.mem.Allocator) void { if (process.next) |next_process| { - next_process.deinit(next_process, allocator); + next_process.deinit(next_process, alloc); } - allocator.destroy(@fieldParentPtr(T, "process", process)); + alloc.destroy(@fieldParentPtr(T, "process", process)); } }.deinit; From cc92e4a22520f1d697ecabe87e29b31c0c67a321 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 19 Jun 2020 23:07:40 -0700 Subject: [PATCH 067/146] direct bench --- zig-ecs/examples/view_vs_group.zig | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/zig-ecs/examples/view_vs_group.zig b/zig-ecs/examples/view_vs_group.zig index ec52e5c..b7f84a2 100644 --- a/zig-ecs/examples/view_vs_group.zig +++ b/zig-ecs/examples/view_vs_group.zig @@ -53,7 +53,7 @@ fn iterateView(reg: *ecs.Registry) void { fn nonOwningGroup(reg: *ecs.Registry) void { std.debug.warn("--- non-owning ---\n", .{}); var timer = std.time.Timer.start() catch unreachable; - var group = reg.group(.{}, .{Velocity, Position}, .{}); + var group = reg.group(.{}, .{ Velocity, Position }, .{}); var end = timer.lap(); std.debug.warn("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); @@ -90,8 +90,29 @@ fn owningGroup(reg: *ecs.Registry) void { timer.reset(); group.each(each); - end = timer.read(); + end = timer.lap(); std.debug.warn("group (each): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + + timer.reset(); + + var storage = reg.assure(Velocity); + var vel = storage.instances.items; + var pos = reg.assure(Position).instances.items; + + var index: usize = group.group_data.current; + while (true) { + if (index == 0) break; + index -= 1; + + const ent = storage.set.dense.items[index]; + const entity_index = storage.set.index(ent); + + pos[entity_index].x += pos[entity_index].x; + pos[entity_index].y += pos[entity_index].y; + } + + end = timer.lap(); + std.debug.warn("group (direct): {d}\n", .{@intToFloat(f64, end) / 1000000000}); } fn each(e: struct { vel: *Velocity, pos: *Position }) void { From 46c2af939e6572ff7445a17204a6b9bc53f0d25c Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 20 Jun 2020 17:00:32 -0700 Subject: [PATCH 068/146] bug fixes and speedup --- zig-ecs/examples/view_vs_group.zig | 8 ++--- zig-ecs/src/ecs/component_storage.zig | 48 ++++++++++++++++++++------- zig-ecs/src/ecs/groups.zig | 38 +++------------------ zig-ecs/src/ecs/registry.zig | 7 ++-- 4 files changed, 47 insertions(+), 54 deletions(-) diff --git a/zig-ecs/examples/view_vs_group.zig b/zig-ecs/examples/view_vs_group.zig index b7f84a2..94eaf8c 100644 --- a/zig-ecs/examples/view_vs_group.zig +++ b/zig-ecs/examples/view_vs_group.zig @@ -28,6 +28,7 @@ fn createEntities(reg: *ecs.Registry) void { reg.add(e1, Position{ .x = 1, .y = 1 }); reg.add(e1, Velocity{ .x = 1, .y = 1 }); } + var end = timer.lap(); std.debug.warn("create entities: \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); } @@ -104,11 +105,8 @@ fn owningGroup(reg: *ecs.Registry) void { if (index == 0) break; index -= 1; - const ent = storage.set.dense.items[index]; - const entity_index = storage.set.index(ent); - - pos[entity_index].x += pos[entity_index].x; - pos[entity_index].y += pos[entity_index].y; + pos[index].x += pos[index].x; + pos[index].y += pos[index].y; } end = timer.lap(); diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 7de8234..8190545 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -27,8 +27,9 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { allocator: ?*std.mem.Allocator, /// doesnt really belong here...used to denote group ownership super: usize = 0, - safe_deinit: fn (*Self) void, - safe_swap: fn (*Self, Entity, Entity, bool) void, + safeDeinit: fn (*Self) void, + safeSwap: fn (*Self, Entity, Entity, bool) void, + safeRemoveIfContains: fn (*Self, Entity) void, construction: Signal(Entity), update: Signal(Entity), destruction: Signal(Entity), @@ -37,14 +38,14 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { var store = Self{ .set = SparseSet(Entity).initPtr(allocator), .instances = undefined, - .safe_deinit = struct { + .safeDeinit = struct { fn deinit(self: *Self) void { if (!is_empty_struct) { self.instances.deinit(); } } }.deinit, - .safe_swap = struct { + .safeSwap = struct { 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)]); @@ -52,6 +53,13 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { if (!instances_only) self.set.swap(lhs, rhs); } }.swap, + .safeRemoveIfContains = struct { + fn removeIfContains(self: *Self, entity: Entity) void { + if (self.contains(entity)) { + self.remove(entity); + } + } + }.removeIfContains, .allocator = null, .construction = Signal(Entity).init(allocator), .update = Signal(Entity).init(allocator), @@ -78,7 +86,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { store.destruction = Signal(Entity).init(allocator); // since we are stored as a pointer, we need to catpure this - store.safe_deinit = struct { + store.safeDeinit = struct { fn deinit(self: *Self) void { if (!is_empty_struct) { self.instances.deinit(); @@ -86,7 +94,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { } }.deinit; - store.safe_swap = struct { + store.safeSwap = struct { 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)]); @@ -95,6 +103,14 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { } }.swap; + store.safeRemoveIfContains = struct { + fn removeIfContains(self: *Self, entity: Entity) void { + if (self.contains(entity)) { + self.remove(entity); + } + } + }.removeIfContains; + return store; } @@ -102,7 +118,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { // great care must be taken here. Due to how Registry keeps this struct as pointers anything touching a type // will be wrong since it has to cast to a random struct when deiniting. Because of all that, is_empty_struct // will allways be false here so we have to deinit the instances no matter what. - self.safe_deinit(self); + self.safeDeinit(self); self.set.deinit(); self.construction.deinit(); self.update.deinit(); @@ -156,6 +172,14 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { return self.set.contains(entity); } + pub fn removeIfContains(self: *Self, entity: Entity) void { + if (Component == u1) { + self.safeRemoveIfContains(self, entity); + } else if (self.contains(entity)) { + self.remove(entity); + } + } + pub fn len(self: Self) usize { return self.set.len(); } @@ -213,10 +237,10 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { storage: *Self, pub fn swap(this: @This(), a: Entity, b: Entity) void { - this.storage.safe_swap(this.storage, a, b, true); + this.storage.safeSwap(this.storage, a, b, true); } }; - const swap_context = SortContext{.storage = self}; + const swap_context = SortContext{ .storage = self }; self.set.arrange(length, context, lessThan, swap_context); } else { const SortContext = struct { @@ -231,11 +255,11 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { } pub fn swap(this: @This(), a: Entity, b: Entity) void { - this.storage.safe_swap(this.storage, a, b, true); + this.storage.safeSwap(this.storage, a, b, true); } }; - const swap_context = SortContext{.storage = self, .wrapped_context = context, .lessThan = lessThan}; + const swap_context = SortContext{ .storage = self, .wrapped_context = context, .lessThan = lessThan }; self.set.arrange(length, swap_context, SortContext.sort, swap_context); } } @@ -253,7 +277,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, false); + self.safeSwap(self, lhs, rhs, false); } pub fn clear(self: *Self) void { diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index ef55d71..9c558d9 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -101,14 +101,11 @@ pub const OwningGroup = struct { 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); - // fill and return the struct var comps: Components = undefined; inline for (@typeInfo(Components).Struct.fields) |field, i| { const typed_ptr = @ptrCast([*]field.field_type.Child, @alignCast(@alignOf(field.field_type.Child), it.component_ptrs[i])); - @field(comps, field.name) = &typed_ptr[entity_index]; + @field(comps, field.name) = &typed_ptr[it.index]; } return comps; } @@ -200,36 +197,9 @@ pub const OwningGroup = struct { self.validate(Components); // optionally we could just use an Iterator here and pay for some slight indirection for code sharing - // var iter = self.iterator(Components); - // while (iter.next()) |comps| { - // @call(.{ .modifier = .always_inline }, func, .{comps}); - // } - - const component_info = @typeInfo(Components).Struct; - - // get the data pointers for the requested component types - var component_ptrs: [component_info.fields.len][*]u8 = undefined; - inline for (component_info.fields) |field, i| { - const storage = self.registry.assure(field.field_type.Child); - component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); - } - - var storage = self.firstOwnedStorage(); - 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); - - var comps: Components = undefined; - inline for (component_info.fields) |field, i| { - const typed_ptr = @ptrCast([*]field.field_type.Child, @alignCast(@alignOf(field.field_type.Child), component_ptrs[i])); - @field(comps, field.name) = &typed_ptr[entity_index]; - } - - func(comps); + var iter = self.iterator(Components); + while (iter.next()) |comps| { + @call(.{ .modifier = .always_inline }, func, .{comps}); } } diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index dbadfb7..16e4939 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -334,8 +334,9 @@ pub const Registry = struct { pub fn removeIfExists(self: *Registry, comptime T: type, entity: Entity) void { assert(self.valid(entity)); var store = self.assure(T); - if (store.contains(entity)) + if (store.contains(entity)) { store.remove(entity); + } } /// Removes all the components from an entity and makes it orphaned @@ -345,8 +346,8 @@ pub const Registry = struct { var it = self.components.iterator(); while (it.next()) |ptr| { // HACK: we dont know the Type here but we need to be able to call methods on the Storage(T) - var store = @intToPtr(*Storage(u128), ptr.value); - if (store.contains(entity)) store.remove(entity); + var store = @intToPtr(*Storage(u1), ptr.value); + store.removeIfContains(entity); } } From 76e322a49c8fb5a52862a9830d51daad3946fc69 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 20 Jun 2020 19:32:20 -0700 Subject: [PATCH 069/146] sorting test --- zig-ecs/build.zig | 1 + zig-ecs/examples/group_sort.zig | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 zig-ecs/examples/group_sort.zig diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 92cdbb0..50d6f2a 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -5,6 +5,7 @@ pub fn build(b: *Builder) void { const buildMode = b.standardReleaseOptions(); const examples = [_][2][]const u8{ + [_][]const u8{ "group_sort", "examples/group_sort.zig" }, [_][]const u8{ "view_vs_group", "examples/view_vs_group.zig" }, [_][]const u8{ "simple", "examples/simple.zig" }, }; diff --git a/zig-ecs/examples/group_sort.zig b/zig-ecs/examples/group_sort.zig new file mode 100644 index 0000000..d383f29 --- /dev/null +++ b/zig-ecs/examples/group_sort.zig @@ -0,0 +1,67 @@ +const std = @import("std"); +const ecs = @import("ecs"); + +// override the EntityTraits used by ecs +pub const EntityTraits = ecs.EntityTraitsType(.medium); + +pub const Velocity = struct { x: f32, y: f32 }; +pub const Position = struct { x: f32, y: f32 }; + +const total_entities: usize = 10000; + +/// logs the timing for views vs non-owning groups vs owning groups with 1,000,000 entities +pub fn main() !void { + var reg = ecs.Registry.init(std.heap.c_allocator); + defer reg.deinit(); + + var timer = try std.time.Timer.start(); + + createEntities(®); + owningGroup(®); +} + +fn createEntities(reg: *ecs.Registry) void { + var r = std.rand.DefaultPrng.init(666); + + var timer = std.time.Timer.start() catch unreachable; + var i: usize = 0; + while (i < total_entities) : (i += 1) { + var e1 = reg.create(); + reg.add(e1, Position{ .x = 1, .y = r.random.float(f32) * 100 }); + reg.add(e1, Velocity{ .x = 1, .y = r.random.float(f32) * 100 }); + } + + var end = timer.lap(); + std.debug.warn("create entities: {d}\n", .{@intToFloat(f64, end) / 1000000000}); +} + +fn owningGroup(reg: *ecs.Registry) void { + var group = reg.group(.{ Velocity, Position }, .{}, .{}); + + // var group_iter = group.iterator(struct { vel: *Velocity, pos: *Position }); + // while (group_iter.next()) |e| { + // std.debug.warn("pos.y {d:.3}, ent: {}\n", .{e.pos.y, group_iter.entity()}); + // } + + const SortContext = struct { + fn sort(this: void, a: Position, b: Position) bool { + return a.y < b.y; + } + }; + + var timer = std.time.Timer.start() catch unreachable; + group.sort(Position, {}, SortContext.sort); + var end = timer.lap(); + std.debug.warn("group (sort): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + + timer.reset(); + group.sort(Position, {}, SortContext.sort); + end = timer.lap(); + std.debug.warn("group (sort 2): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + + + // var group_iter2 = group.iterator(struct { vel: *Velocity, pos: *Position }); + // while (group_iter2.next()) |e| { + // std.debug.warn("pos.y {d:.3}, ent: {}\n", .{e.pos.y, group_iter2.entity()}); + // } +} From 9ca3e90b7bd8bcd539797782514328287dca6094 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 20 Jul 2020 09:53:15 -0700 Subject: [PATCH 070/146] update to latest zig version --- zig-ecs/README.md | 46 +++++++++++++++++++++++++++ zig-ecs/examples/group_sort.zig | 2 +- zig-ecs/src/ecs.zig | 2 +- zig-ecs/src/ecs/actor.zig | 2 +- zig-ecs/src/ecs/component_storage.zig | 4 +-- zig-ecs/src/ecs/groups.zig | 18 +++++------ zig-ecs/src/ecs/registry.zig | 36 ++++++++++----------- zig-ecs/src/ecs/sparse_set.zig | 4 +-- zig-ecs/src/ecs/type_store.zig | 4 +-- zig-ecs/src/ecs/utils.zig | 4 +-- zig-ecs/src/process/scheduler.zig | 12 +++---- zig-ecs/src/resources/assets.zig | 4 +-- zig-ecs/src/resources/cache.zig | 2 +- zig-ecs/src/signals/delegate.zig | 4 +-- zig-ecs/src/signals/sink.zig | 10 +++--- 15 files changed, 100 insertions(+), 54 deletions(-) create mode 100644 zig-ecs/README.md diff --git a/zig-ecs/README.md b/zig-ecs/README.md new file mode 100644 index 0000000..d565d2d --- /dev/null +++ b/zig-ecs/README.md @@ -0,0 +1,46 @@ +# Zig ECS +This is a zigification of the fantasic [Entt](https://github.com/skypjack/entt). Entt is _highly_ templated C++ code which depending on your opinion is either a good thing or satan itself in code form. Zig doesn't have the same concept as C++ templates (thank goodness!) so the templated code was changed over to use Zig's generics and compile time metaprogramming. + +## What does a zigified Entt look like? +Below are examples of a View and a Group, the two main ways to work with entities in the ecs along with the scaffolding code. + +Declare some structs to work with: +``` +pub const Velocity = struct { x: f32, y: f32 }; +pub const Position = struct { x: f32, y: f32 }; +``` + +Setup the Registry, which holds the entity data and is where we run our queries: +``` +var reg = ecs.Registry.init(std.testing.allocator); +``` + +Create a couple entities and add some components to them +``` +var entity = reg.create(); +reg.add(entity, Position{ .x = 0, .y = 0 }); +reg.add(entity, Velocity{ .x = 5, .y = 7 }); +... +``` + +Create and iterate a View that matches all entities with a `Velocity` and `Position` component: +``` +var view = reg.view(.{ Velocity, Position }, .{}); + +var iter = view.iterator(); +while (iter.next()) |entity| { + const pos = view.getConst(Position, entity); // readonly copy + var vel = view.get(Velocity, entity); // mutable +} +``` + +The same example using a non-owning Group: +``` +var group = reg.group(.{}, .{ Velocity, Position }, .{}); +group.each(each); + +fn each(e: struct { vel: *Velocity, pos: *Position }) void { + e.pos.*.x += e.vel.x; + e.pos.*.y += e.vel.y; +} +``` \ No newline at end of file diff --git a/zig-ecs/examples/group_sort.zig b/zig-ecs/examples/group_sort.zig index d383f29..eb3b939 100644 --- a/zig-ecs/examples/group_sort.zig +++ b/zig-ecs/examples/group_sort.zig @@ -32,7 +32,7 @@ fn createEntities(reg: *ecs.Registry) void { } var end = timer.lap(); - std.debug.warn("create entities: {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.warn("create {d} entities: {d}\n", .{total_entities, @intToFloat(f64, end) / 1000000000}); } fn owningGroup(reg: *ecs.Registry) void { diff --git a/zig-ecs/src/ecs.zig b/zig-ecs/src/ecs.zig index 85123ac..04ab446 100644 --- a/zig-ecs/src/ecs.zig +++ b/zig-ecs/src/ecs.zig @@ -13,4 +13,4 @@ pub const OwningGroup = @import("ecs/groups.zig").OwningGroup; // signals pub const Signal = @import("signals/signal.zig").Signal; -pub const Dispatcher = @import("signals/dispatcher.zig").Dispatcher; \ No newline at end of file +pub const Dispatcher = @import("signals/dispatcher.zig").Dispatcher; diff --git a/zig-ecs/src/ecs/actor.zig b/zig-ecs/src/ecs/actor.zig index b570b80..972fba5 100644 --- a/zig-ecs/src/ecs/actor.zig +++ b/zig-ecs/src/ecs/actor.zig @@ -18,7 +18,7 @@ pub const Actor = struct { self.registry.destroy(self.entity); } - pub fn add(self: *Actor, value: var) void { + pub fn add(self: *Actor, value: anytype) void { self.registry.add(self.entity, value); } diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 8190545..40a8cae 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -188,7 +188,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { struct { /// Sort Entities according to the given comparison function. Only T == Entity is allowed. The constraint param only exists for /// parity with non-empty Components - pub fn sort(self: Self, comptime T: type, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { + pub fn sort(self: Self, comptime T: type, context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { std.debug.assert(T == Entity); self.set.sort(context, lessThan); } @@ -226,7 +226,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { } /// 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, length: usize, context: var, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { + pub fn sort(self: *Self, comptime T: type, length: usize, context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { std.debug.assert(T == Entity or T == Component); // we have to perform a swap after the sort for all moved entities so we make a helper struct for that. In the diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 9c558d9..b5a03b9 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -42,7 +42,7 @@ 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: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { if (T == Entity) { self.group_data.entity_set.sort(context, lessThan); } else { @@ -73,7 +73,7 @@ pub const OwningGroup = struct { /// 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 { + fn Iterator(comptime Components: anytype) type { return struct { group: OwningGroup, index: usize, @@ -136,7 +136,7 @@ pub const OwningGroup = struct { /// grabs an untyped (u1) reference to the first Storage(T) in the owned array fn firstOwnedStorage(self: OwningGroup) *Storage(u1) { - const ptr = self.registry.components.getValue(self.group_data.owned[0]).?; + const ptr = self.registry.components.get(self.group_data.owned[0]).?; return @intToPtr(*Storage(u1), ptr); } @@ -155,7 +155,7 @@ pub const OwningGroup = struct { return storage.contains(entity) and storage.set.index(entity) < self.len(); } - fn validate(self: OwningGroup, comptime Components: var) void { + fn validate(self: OwningGroup, comptime Components: anytype) void { if (std.builtin.mode == .Debug and self.group_data.owned.len > 0) { std.debug.assert(@typeInfo(Components) == .Struct); @@ -167,7 +167,7 @@ pub const OwningGroup = struct { } } - pub fn getOwned(self: OwningGroup, entity: Entity, comptime Components: var) Components { + pub fn getOwned(self: OwningGroup, entity: Entity, comptime Components: anytype) Components { self.validate(Components); const component_info = @typeInfo(Components).Struct; @@ -188,7 +188,7 @@ pub const OwningGroup = struct { return comps; } - pub fn each(self: OwningGroup, comptime func: var) void { + pub fn each(self: OwningGroup, comptime func: anytype) void { const Components = switch (@typeInfo(@TypeOf(func))) { .BoundFn => |func_info| func_info.args[1].arg_type.?, .Fn => |func_info| func_info.args[0].arg_type.?, @@ -223,7 +223,7 @@ 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! Non-owned /// types should be fetched via Iterator.get. - pub fn iterator(self: OwningGroup, comptime Components: var) Iterator(Components) { + pub fn iterator(self: OwningGroup, comptime Components: anytype) Iterator(Components) { self.validate(Components); return Iterator(Components).init(self); } @@ -232,7 +232,7 @@ pub const OwningGroup = struct { 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 { + pub fn sort(self: OwningGroup, comptime T: type, context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { var first_storage = self.firstOwnedStorage(); if (T == Entity) { @@ -264,7 +264,7 @@ pub const OwningGroup = struct { // 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 other_ptr = self.registry.components.get(type_id).?; var storage = @intToPtr(*Storage(u1), other_ptr); storage.swap(storage.data()[pos], entity); } diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 16e4939..8511bcc 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -81,19 +81,19 @@ pub const Registry = struct { pub fn maybeValidIf(self: *GroupData, entity: Entity) void { const isValid: bool = blk: { for (self.owned) |tid| { - const ptr = self.registry.components.getValue(tid).?; + const ptr = self.registry.components.get(tid).?; if (!@intToPtr(*Storage(u1), ptr).contains(entity)) break :blk false; } for (self.include) |tid| { - const ptr = self.registry.components.getValue(tid).?; + const ptr = self.registry.components.get(tid).?; if (!@intToPtr(*Storage(u1), ptr).contains(entity)) break :blk false; } for (self.exclude) |tid| { - const ptr = self.registry.components.getValue(tid).?; + const ptr = self.registry.components.get(tid).?; if (@intToPtr(*Storage(u1), ptr).contains(entity)) break :blk false; } @@ -106,11 +106,11 @@ pub const Registry = struct { } } else { if (isValid) { - const ptr = self.registry.components.getValue(self.owned[0]).?; + const ptr = self.registry.components.get(self.owned[0]).?; if (!(@intToPtr(*Storage(u1), ptr).set.index(entity) < self.current)) { for (self.owned) |tid| { // store.swap hides a safe version that types it correctly - const store_ptr = self.registry.components.getValue(tid).?; + const store_ptr = self.registry.components.get(tid).?; var store = @intToPtr(*Storage(u1), store_ptr); store.swap(store.data()[self.current], entity); } @@ -127,12 +127,12 @@ pub const Registry = struct { self.entity_set.remove(entity); } } else { - const ptr = self.registry.components.getValue(self.owned[0]).?; + const ptr = self.registry.components.get(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(tid).?; + const store_ptr = self.registry.components.get(tid).?; store = @intToPtr(*Storage(u1), store_ptr); store.swap(store.data()[self.current], entity); } @@ -215,7 +215,7 @@ pub const Registry = struct { pub fn assure(self: *Registry, comptime T: type) *Storage(T) { var type_id = utils.typeId(T); - if (self.components.get(type_id)) |kv| { + if (self.components.getEntry(type_id)) |kv| { return @intToPtr(*Storage(T), kv.value); } @@ -281,7 +281,7 @@ pub const Registry = struct { return self.handles.iterator(); } - pub fn add(self: *Registry, entity: Entity, value: var) void { + pub fn add(self: *Registry, entity: Entity, value: anytype) void { assert(self.valid(entity)); self.assure(@TypeOf(value)).add(entity, value); } @@ -292,14 +292,14 @@ pub const Registry = struct { } /// adds all the component types passed in as zero-initialized values - pub fn addTypes(self: *Registry, entity: Entity, comptime types: var) void { + pub fn addTypes(self: *Registry, entity: Entity, comptime types: anytype) void { inline for (types) |t| { self.assure(t).add(entity, std.mem.zeroes(t)); } } /// Replaces the given component for an entity - pub fn replace(self: *Registry, entity: Entity, value: var) void { + pub fn replace(self: *Registry, entity: Entity, value: anytype) void { assert(self.valid(entity)); self.assure(@TypeOf(value)).replace(entity, value); } @@ -309,7 +309,7 @@ pub const Registry = struct { self.replace(entity, value); } - pub fn addOrReplace(self: *Registry, entity: Entity, value: var) void { + pub fn addOrReplace(self: *Registry, entity: Entity, value: anytype) void { assert(self.valid(entity)); const store = self.assure(@TypeOf(value)); @@ -393,7 +393,7 @@ pub const Registry = struct { } /// Binds an object to the context of the registry - pub fn setContext(self: *Registry, context: var) void { + pub fn setContext(self: *Registry, context: anytype) void { std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); var type_id = utils.typeId(@typeInfo(@TypeOf(context)).Pointer.child); @@ -432,7 +432,7 @@ pub const Registry = struct { return self.assure(T).super == 0; } - pub fn view(self: *Registry, comptime includes: var, comptime excludes: var) ViewType(includes, excludes) { + pub fn view(self: *Registry, comptime includes: anytype, comptime excludes: anytype) ViewType(includes, excludes) { std.debug.assert(@typeInfo(@TypeOf(includes)) == .Struct); std.debug.assert(@typeInfo(@TypeOf(excludes)) == .Struct); std.debug.assert(includes.len > 0); @@ -457,13 +457,13 @@ pub const Registry = struct { } /// returns the Type that a view will be based on the includes and excludes - fn ViewType(comptime includes: var, comptime excludes: var) type { + fn ViewType(comptime includes: anytype, comptime excludes: anytype) type { if (includes.len == 1 and excludes.len == 0) return BasicView(includes[0]); return MultiView(includes.len, excludes.len); } /// creates an optimized group for iterating components - pub fn group(self: *Registry, comptime owned: var, comptime includes: var, comptime excludes: var) (if (owned.len == 0) BasicGroup else OwningGroup) { + pub fn group(self: *Registry, comptime owned: anytype, comptime includes: anytype, comptime excludes: anytype) (if (owned.len == 0) BasicGroup else OwningGroup) { std.debug.assert(@typeInfo(@TypeOf(owned)) == .Struct); std.debug.assert(@typeInfo(@TypeOf(includes)) == .Struct); std.debug.assert(@typeInfo(@TypeOf(excludes)) == .Struct); @@ -593,7 +593,7 @@ pub const Registry = struct { /// given the 3 group Types arrays, generates a (mostly) unique u64 hash. Simultaneously ensures there are no duped types between /// the 3 groups. - inline fn hashGroupTypes(comptime owned: var, comptime includes: var, comptime excludes: var) u64 { + inline fn hashGroupTypes(comptime owned: anytype, comptime includes: anytype, comptime excludes: anytype) u64 { comptime { for (owned) |t1| { for (includes) |t2| { @@ -615,7 +615,7 @@ pub const Registry = struct { } /// expects a tuple of types. Convertes them to type names, sorts them then concatenates and returns the string. - inline fn concatTypes(comptime types: var) []const u8 { + inline fn concatTypes(comptime types: anytype) []const u8 { comptime { if (types.len == 0) return "_"; diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index f7ed049..3688c96 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -148,7 +148,7 @@ pub fn SparseSet(comptime SparseT: type) type { } /// Sort elements according to the given comparison function - pub fn sort(self: *Self, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool) void { + pub fn sort(self: *Self, context: anytype, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool) void { std.sort.insertionSort(SparseT, self.dense.items, context, lessThan); for (self.dense.items) |sparse, i| { @@ -159,7 +159,7 @@ pub fn SparseSet(comptime SparseT: type) type { /// Sort elements according to the given comparison function. Use this when a data array needs to stay in sync with the SparseSet /// by passing in a "swap_context" that contains a "swap" method with a sig of fn(ctx,SparseT,SparseT)void - pub fn arrange(self: *Self, length: usize, context: var, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: var) void { + pub fn arrange(self: *Self, length: usize, context: anytype, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: anytype) void { std.sort.insertionSort(SparseT, self.dense.items[0..length], context, lessThan); for (self.dense.items[0..length]) |sparse, pos| { diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index 56855ec..38d0668 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -13,7 +13,7 @@ pub const TypeStore = struct { }; } - pub fn deinit(self: TypeStore) void { + pub fn deinit(self: *TypeStore) void { var iter = self.map.iterator(); while (iter.next()) |kv| { self.allocator.free(kv.value); @@ -22,7 +22,7 @@ pub const TypeStore = struct { } /// adds instance, returning a pointer to the item as it lives in the store - pub fn add(self: *TypeStore, instance: var) void { + pub fn add(self: *TypeStore, instance: anytype) void { var bytes = self.allocator.alloc(u8, @sizeOf(@TypeOf(instance))) catch unreachable; std.mem.copy(u8, bytes, std.mem.asBytes(&instance)); _ = self.map.put(utils.typeId(@TypeOf(instance)), bytes) catch unreachable; diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index ba82c39..44e99a9 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -3,7 +3,7 @@ const std = @import("std"); pub const ErasedPtr = struct { ptr: usize, - pub fn init(ptr: var) ErasedPtr { + pub fn init(ptr: anytype) ErasedPtr { if (@sizeOf(@TypeOf(ptr)) == 0) { return .{ .ptr = undefined }; } @@ -64,7 +64,7 @@ pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T } } -pub fn sortSubSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, context: var, comptime lessThan: fn (@TypeOf(context), lhs: T1, rhs: T1) bool) void { +pub fn sortSubSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, context: anytype, comptime lessThan: fn (@TypeOf(context), lhs: T1, rhs: T1) bool) void { var i: usize = 1; while (i < items.len) : (i += 1) { const x = items[i]; diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index a9ce9bc..f1f491c 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -15,7 +15,7 @@ pub const Scheduler = struct { allocator: *std.mem.Allocator, /// helper to create and prepare a process - fn createProcessHandler(comptime T: type, data: var, allocator: *std.mem.Allocator) *Process { + fn createProcessHandler(comptime T: type, data: anytype, allocator: *std.mem.Allocator) *Process { var proc = allocator.create(T) catch unreachable; proc.initialize(data); @@ -41,7 +41,7 @@ pub const Scheduler = struct { return .{ .process = process, .allocator = allocator }; } - pub fn next(self: *@This(), comptime T: type, data: var) *@This() { + pub fn next(self: *@This(), comptime T: type, data: anytype) *@This() { self.process.next = createProcessHandler(T, data, self.allocator); self.process = self.process.next.?; return self; @@ -61,7 +61,7 @@ pub const Scheduler = struct { } /// Schedules a process for the next tick - pub fn attach(self: *Scheduler, comptime T: type, data: var) Continuation { + pub fn attach(self: *Scheduler, comptime T: type, data: anytype) Continuation { std.debug.assert(@hasDecl(T, "initialize")); std.debug.assert(@hasField(T, "process")); @@ -137,7 +137,7 @@ test "" { process: Process, fart: usize, - pub fn initialize(self: *@This(), data: var) void { + pub fn initialize(self: *@This(), data: anytype) void { self.process = .{ .startFn = start, .updateFn = update, @@ -190,7 +190,7 @@ test "scheduler.clear" { const Tester = struct { process: Process, - pub fn initialize(self: *@This(), data: var) void { + pub fn initialize(self: *@This(), data: anytype) void { self.process = .{ .updateFn = update }; } @@ -212,7 +212,7 @@ test "scheduler.attach.next" { process: Process, counter: *usize, - pub fn initialize(self: *@This(), data: var) void { + pub fn initialize(self: *@This(), data: anytype) void { self.process = .{ .updateFn = update }; self.counter = data; } diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index b231755..3bf0a69 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -33,11 +33,11 @@ pub const Assets = struct { return cache; } - pub fn load(self: *Assets, id: u16, comptime loader: var) ReturnType(loader, false) { + pub fn load(self: *Assets, id: u16, comptime loader: anytype) ReturnType(loader, false) { return self.get(ReturnType(loader, true)).load(id, loader); } - fn ReturnType(comptime loader: var, strip_ptr: bool) type { + fn ReturnType(comptime loader: anytype, strip_ptr: bool) type { var ret = @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.?; if (strip_ptr) { return ret.Child; diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index 65563b5..f4127c3 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -42,7 +42,7 @@ pub fn Cache(comptime T: type) type { self.safe_deinit(self); } - pub fn load(self: *@This(), id: u32, comptime loader: var) @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.? { + pub fn load(self: *@This(), id: u32, comptime loader: anytype) @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.? { if (self.resources.getValue(id)) |resource| { return resource; } diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index aafc5eb..f1aba76 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -12,7 +12,7 @@ pub fn Delegate(comptime Event: type) type { }, /// sets a bound function as the Delegate callback - pub fn initBound(ctx: var, comptime fn_name: []const u8) Self { + pub fn initBound(ctx: anytype, comptime fn_name: []const u8) Self { std.debug.assert(@typeInfo(@TypeOf(ctx)) == .Pointer); std.debug.assert(@ptrToInt(ctx) != 0); @@ -50,7 +50,7 @@ pub fn Delegate(comptime Event: type) type { }; } - pub fn containsBound(self: Self, ctx: var) bool { + pub fn containsBound(self: Self, ctx: anytype) bool { std.debug.assert(@ptrToInt(ctx) != 0); std.debug.assert(@typeInfo(@TypeOf(ctx)) == .Pointer); diff --git a/zig-ecs/src/signals/sink.zig b/zig-ecs/src/signals/sink.zig index b4a6986..a05f86d 100644 --- a/zig-ecs/src/signals/sink.zig +++ b/zig-ecs/src/signals/sink.zig @@ -27,7 +27,7 @@ pub fn Sink(comptime Event: type) type { return self; } - pub fn beforeBound(self: Self, ctx: var) Self { + pub fn beforeBound(self: Self, ctx: anytype) Self { if (@typeInfo(@TypeOf(ctx)) == .Pointer) { if (self.indexOfBound(ctx)) |index| { return Self{ .insert_index = index }; @@ -41,7 +41,7 @@ pub fn Sink(comptime Event: type) type { _ = owning_signal.calls.insert(self.insert_index, Delegate(Event).initFree(callback)) catch unreachable; } - pub fn connectBound(self: Self, ctx: var, comptime fn_name: []const u8) void { + pub fn connectBound(self: Self, ctx: anytype, comptime fn_name: []const u8) void { std.debug.assert(self.indexOfBound(ctx) == null); _ = owning_signal.calls.insert(self.insert_index, Delegate(Event).initBound(ctx, fn_name)) catch unreachable; } @@ -52,7 +52,7 @@ pub fn Sink(comptime Event: type) type { } } - pub fn disconnectBound(self: Self, ctx: var) void { + pub fn disconnectBound(self: Self, ctx: anytype) void { if (self.indexOfBound(ctx)) |index| { _ = owning_signal.calls.swapRemove(index); } @@ -67,7 +67,7 @@ pub fn Sink(comptime Event: type) type { return null; } - fn indexOfBound(self: Self, ctx: var) ?usize { + fn indexOfBound(self: Self, ctx: anytype) ?usize { for (owning_signal.calls.items) |call, i| { if (call.containsBound(ctx)) { return i; @@ -112,4 +112,4 @@ test "Sink Before bound" { signal.sink().beforeBound(&thing).connect(tester); std.testing.expectEqual(signal.sink().indexOf(tester).?, 0); -} \ No newline at end of file +} From d6cfdbe67133142e803529c97933d82e004e6741 Mon Sep 17 00:00:00 2001 From: prime31 Date: Mon, 20 Jul 2020 10:28:37 -0700 Subject: [PATCH 071/146] Update README.md --- zig-ecs/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zig-ecs/README.md b/zig-ecs/README.md index d565d2d..387f300 100644 --- a/zig-ecs/README.md +++ b/zig-ecs/README.md @@ -5,18 +5,18 @@ This is a zigification of the fantasic [Entt](https://github.com/skypjack/entt). Below are examples of a View and a Group, the two main ways to work with entities in the ecs along with the scaffolding code. Declare some structs to work with: -``` +```zig pub const Velocity = struct { x: f32, y: f32 }; pub const Position = struct { x: f32, y: f32 }; ``` Setup the Registry, which holds the entity data and is where we run our queries: -``` +```zig var reg = ecs.Registry.init(std.testing.allocator); ``` Create a couple entities and add some components to them -``` +```zig var entity = reg.create(); reg.add(entity, Position{ .x = 0, .y = 0 }); reg.add(entity, Velocity{ .x = 5, .y = 7 }); @@ -24,7 +24,7 @@ reg.add(entity, Velocity{ .x = 5, .y = 7 }); ``` Create and iterate a View that matches all entities with a `Velocity` and `Position` component: -``` +```zig var view = reg.view(.{ Velocity, Position }, .{}); var iter = view.iterator(); @@ -35,7 +35,7 @@ while (iter.next()) |entity| { ``` The same example using a non-owning Group: -``` +```zig var group = reg.group(.{}, .{ Velocity, Position }, .{}); group.each(each); @@ -43,4 +43,4 @@ fn each(e: struct { vel: *Velocity, pos: *Position }) void { e.pos.*.x += e.vel.x; e.pos.*.y += e.vel.y; } -``` \ No newline at end of file +``` From a18a627f98b50980bbb188906fc6b95c6b8dcdbe Mon Sep 17 00:00:00 2001 From: Mike Desaro Date: Mon, 3 Aug 2020 10:13:33 -0700 Subject: [PATCH 072/146] bump for new zig version --- zig-ecs/build.zig | 2 +- zig-ecs/examples/group_sort.zig | 3 +-- zig-ecs/src/ecs/views.zig | 8 ++++---- zig-ecs/tests/dispatcher_test.zig | 3 +-- zig-ecs/tests/groups_test.zig | 8 ++++---- zig-ecs/tests/registry_test.zig | 2 +- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 50d6f2a..8af56bf 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -5,8 +5,8 @@ pub fn build(b: *Builder) void { const buildMode = b.standardReleaseOptions(); const examples = [_][2][]const u8{ - [_][]const u8{ "group_sort", "examples/group_sort.zig" }, [_][]const u8{ "view_vs_group", "examples/view_vs_group.zig" }, + [_][]const u8{ "group_sort", "examples/group_sort.zig" }, [_][]const u8{ "simple", "examples/simple.zig" }, }; diff --git a/zig-ecs/examples/group_sort.zig b/zig-ecs/examples/group_sort.zig index eb3b939..a5c91f1 100644 --- a/zig-ecs/examples/group_sort.zig +++ b/zig-ecs/examples/group_sort.zig @@ -32,7 +32,7 @@ fn createEntities(reg: *ecs.Registry) void { } var end = timer.lap(); - std.debug.warn("create {d} entities: {d}\n", .{total_entities, @intToFloat(f64, end) / 1000000000}); + std.debug.warn("create {d} entities: {d}\n", .{ total_entities, @intToFloat(f64, end) / 1000000000 }); } fn owningGroup(reg: *ecs.Registry) void { @@ -59,7 +59,6 @@ fn owningGroup(reg: *ecs.Registry) void { end = timer.lap(); std.debug.warn("group (sort 2): {d}\n", .{@intToFloat(f64, end) / 1000000000}); - // var group_iter2 = group.iterator(struct { vel: *Velocity, pos: *Position }); // while (group_iter2.next()) |e| { // std.debug.warn("pos.y {d:.3}, ent: {}\n", .{e.pos.y, group_iter2.entity()}); diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index 44f12fa..685fcbe 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -67,7 +67,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { entities: *const []Entity, pub fn init(view: *Self) Iterator { - const ptr = view.registry.components.getValue(view.type_ids[0]).?; + const ptr = view.registry.components.get(view.type_ids[0]).?; const entities = @intToPtr(*Storage(u8), ptr).dataPtr(); return .{ .view = view, @@ -85,7 +85,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // entity must be in all other Storages for (it.view.type_ids) |tid| { - const ptr = it.view.registry.components.getValue(tid).?; + const ptr = it.view.registry.components.get(tid).?; if (!@intToPtr(*Storage(u1), ptr).contains(entity)) { break :blk; } @@ -93,7 +93,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // entity must not be in all other excluded Storages for (it.view.exclude_type_ids) |tid| { - const ptr = it.view.registry.components.getValue(tid).?; + const ptr = it.view.registry.components.get(tid).?; if (@intToPtr(*Storage(u1), ptr).contains(entity)) { break :blk; } @@ -129,7 +129,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // get our component counts in an array so we can sort the type_ids based on how many entities are in each var sub_items: [n_includes]usize = undefined; for (self.type_ids) |tid, i| { - const ptr = self.registry.components.getValue(tid).?; + const ptr = self.registry.components.get(tid).?; const store = @intToPtr(*Storage(u8), ptr); sub_items[i] = store.len(); } diff --git a/zig-ecs/tests/dispatcher_test.zig b/zig-ecs/tests/dispatcher_test.zig index 63cc4a2..90c7eeb 100644 --- a/zig-ecs/tests/dispatcher_test.zig +++ b/zig-ecs/tests/dispatcher_test.zig @@ -21,7 +21,6 @@ const Thing = struct { } }; - test "Dispatcher" { var thing = Thing{}; @@ -37,4 +36,4 @@ test "Dispatcher" { sink2.connect(tester2); sink2.connectBound(&thing, "testI32"); d.trigger(i32, -543); -} \ No newline at end of file +} diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index 94c93f6..a58316d 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -13,7 +13,7 @@ const Transform = struct { x: f32 = 0 }; const Renderable = struct { x: f32 = 0 }; const Rotation = struct { x: f32 = 0 }; -fn printStore(store: var, name: []const u8) void { +fn printStore(store: anytype, name: []const u8) void { warn("--- {} ---\n", .{name}); 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])] }); @@ -106,11 +106,11 @@ test "sort OwningGroup by Entity" { return sprite_a.x > sprite_b.x; } }; - const context = SortContext{.group = group}; + const context = SortContext{ .group = group }; group.sort(ecs.Entity, context, SortContext.sort); var val: f32 = 0; - var iter = group.iterator(struct {s: *Sprite, r: *Renderable}); + var iter = group.iterator(struct { s: *Sprite, r: *Renderable }); while (iter.next()) |entity| { std.testing.expectEqual(val, entity.s.*.x); val += 1; @@ -138,7 +138,7 @@ test "sort OwningGroup by Component" { group.sort(Sprite, {}, SortContext.sort); var val: f32 = 0; - var iter = group.iterator(struct {s: *Sprite, r: *Renderable}); + var iter = group.iterator(struct { s: *Sprite, r: *Renderable }); while (iter.next()) |entity| { std.testing.expectEqual(val, entity.s.*.x); val += 1; diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index dd8144b..09eafa6 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -17,7 +17,7 @@ test "Registry" { var e1 = reg.create(); - reg.addTypes(e1, .{Empty, Position}); + reg.addTypes(e1, .{ Empty, Position }); reg.add(e1, BigOne{ .pos = Position{ .x = 5, .y = 5 }, .vel = Velocity{ .x = 5, .y = 5 }, .accel = Velocity{ .x = 5, .y = 5 } }); std.testing.expect(reg.has(Empty, e1)); From c0a10ee49d1954db254fb5c148771c1b08fa572e Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Wed, 5 Aug 2020 22:37:17 +0200 Subject: [PATCH 073/146] Update to latest zig --- zig-ecs/src/ecs/registry.zig | 2 +- zig-ecs/src/ecs/type_store.zig | 4 ++-- zig-ecs/src/resources/assets.zig | 4 ++-- zig-ecs/src/resources/cache.zig | 6 +++--- zig-ecs/src/signals/dispatcher.zig | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 8511bcc..94d431b 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -411,7 +411,7 @@ pub const Registry = struct { std.debug.assert(@typeInfo(T) != .Pointer); return if (self.contexts.get(utils.typeId(T))) |ptr| - return if (ptr.value > 0) @intToPtr(*T, ptr.value) else null + return if (ptr > 0) @intToPtr(*T, ptr) else null else null; } diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index 38d0668..a16704d 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -29,7 +29,7 @@ pub const TypeStore = struct { } pub fn get(self: *TypeStore, comptime T: type) *T { - if (self.map.getValue(utils.typeId(T))) |bytes| { + if (self.map.get(utils.typeId(T))) |bytes| { return @ptrCast(*T, @alignCast(@alignOf(T), bytes)); } unreachable; @@ -48,7 +48,7 @@ pub const TypeStore = struct { } pub fn remove(self: *TypeStore, comptime T: type) void { - if (self.map.getValue(utils.typeId(T))) |bytes| { + if (self.map.get(utils.typeId(T))) |bytes| { self.allocator.free(bytes); _ = self.map.remove(utils.typeId(T)); } diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index 3bf0a69..1c729eb 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -13,7 +13,7 @@ pub const Assets = struct { }; } - pub fn deinit(self: Assets) void { + pub fn deinit(self: *Assets) void { var it = self.caches.iterator(); while (it.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit @@ -24,7 +24,7 @@ pub const Assets = struct { } pub fn get(self: *Assets, comptime AssetT: type) *Cache(AssetT) { - if (self.caches.getValue(utils.typeId(AssetT))) |tid| { + if (self.caches.get(utils.typeId(AssetT))) |tid| { return @intToPtr(*Cache(AssetT), tid); } diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index f4127c3..6d8c325 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -43,7 +43,7 @@ pub fn Cache(comptime T: type) type { } pub fn load(self: *@This(), id: u32, comptime loader: anytype) @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.? { - if (self.resources.getValue(id)) |resource| { + if (self.resources.get(id)) |resource| { return resource; } @@ -72,11 +72,11 @@ pub fn Cache(comptime T: type) type { @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); } } - self.resources.clear(); + self.resources.clearAndFree(); } pub fn size(self: @This()) usize { - return self.resources.size; + return self.resources.items().len; } }; } diff --git a/zig-ecs/src/signals/dispatcher.zig b/zig-ecs/src/signals/dispatcher.zig index bfd5526..1bcdb43 100644 --- a/zig-ecs/src/signals/dispatcher.zig +++ b/zig-ecs/src/signals/dispatcher.zig @@ -14,7 +14,7 @@ pub const Dispatcher = struct { }; } - pub fn deinit(self: Dispatcher) void { + pub fn deinit(self: *Dispatcher) void { var it = self.signals.iterator(); while (it.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit @@ -27,8 +27,8 @@ pub const Dispatcher = struct { fn assure(self: *Dispatcher, comptime T: type) *Signal(T) { var type_id = utils.typeId(T); - if (self.signals.get(type_id)) |kv| { - return @intToPtr(*Signal(T), kv.value); + if (self.signals.get(type_id)) |value| { + return @intToPtr(*Signal(T), value); } var signal = Signal(T).create(self.allocator); From aa9c20d4b57dbdbdc13d725872aa0abcd96ff718 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Fri, 7 Aug 2020 21:00:25 +0200 Subject: [PATCH 074/146] Update to latest HashMap API .iterator() is deprecated, iterating over the items directly is now supported --- zig-ecs/src/ecs/registry.zig | 6 ++---- zig-ecs/src/ecs/type_store.zig | 3 +-- zig-ecs/src/resources/assets.zig | 3 +-- zig-ecs/src/resources/cache.zig | 3 +-- zig-ecs/src/signals/dispatcher.zig | 3 +-- 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 94d431b..b049414 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -195,8 +195,7 @@ pub const Registry = struct { } pub fn deinit(self: *Registry) void { - var it = self.components.iterator(); - while (it.next()) |ptr| { + for (self.components.items()) |ptr| { // HACK: we dont know the Type here but we need to call deinit var storage = @intToPtr(*Storage(u1), ptr.value); storage.deinit(); @@ -343,8 +342,7 @@ pub const Registry = struct { pub fn removeAll(self: *Registry, entity: Entity) void { assert(self.valid(entity)); - var it = self.components.iterator(); - while (it.next()) |ptr| { + for (self.components.items()) |ptr| { // HACK: we dont know the Type here but we need to be able to call methods on the Storage(T) var store = @intToPtr(*Storage(u1), ptr.value); store.removeIfContains(entity); diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index a16704d..21db41f 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -14,8 +14,7 @@ pub const TypeStore = struct { } pub fn deinit(self: *TypeStore) void { - var iter = self.map.iterator(); - while (iter.next()) |kv| { + for (self.map.items()) |kv| { self.allocator.free(kv.value); } self.map.deinit(); diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index 1c729eb..bb02846 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -14,8 +14,7 @@ pub const Assets = struct { } pub fn deinit(self: *Assets) void { - var it = self.caches.iterator(); - while (it.next()) |ptr| { + for (self.caches.items()) |ptr| { // HACK: we dont know the Type here but we need to call deinit @intToPtr(*Cache(u1), ptr.value).deinit(); } diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index 6d8c325..576ef9e 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -67,8 +67,7 @@ pub fn Cache(comptime T: type) type { pub fn clear(self: *@This()) void { // optionally deinit any resources that have a deinit method if (@hasDecl(T, "deinit")) { - var iter = self.resources.iterator(); - while (iter.next()) |kv| { + for (self.resources.items()) |kv| { @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); } } diff --git a/zig-ecs/src/signals/dispatcher.zig b/zig-ecs/src/signals/dispatcher.zig index 1bcdb43..15eab06 100644 --- a/zig-ecs/src/signals/dispatcher.zig +++ b/zig-ecs/src/signals/dispatcher.zig @@ -15,8 +15,7 @@ pub const Dispatcher = struct { } pub fn deinit(self: *Dispatcher) void { - var it = self.signals.iterator(); - while (it.next()) |ptr| { + for (self.signals.items()) |ptr| { // HACK: we dont know the Type here but we need to call deinit var signal = @intToPtr(*Signal(void), ptr.value); signal.deinit(); From 94c451990e9f4f6f82c6673a6394c8b56dc7a159 Mon Sep 17 00:00:00 2001 From: prime31 Date: Thu, 13 Aug 2020 12:20:06 -0700 Subject: [PATCH 075/146] Create ci.yml --- zig-ecs/.github/workflows/ci.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 zig-ecs/.github/workflows/ci.yml diff --git a/zig-ecs/.github/workflows/ci.yml b/zig-ecs/.github/workflows/ci.yml new file mode 100644 index 0000000..276abaf --- /dev/null +++ b/zig-ecs/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +- name: Setup Zig + uses: goto-bus-stop/setup-zig@v1.2.3 + + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{matrix.os}} + steps: + - uses: actions/checkout@v2 + - uses: goto-bus-stop/setup-zig@v1 + - run: zig build test + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: goto-bus-stop/setup-zig@v1 + - run: zig fmt --check src/*.zig From d401f239b60a0073431d835c5aa03ef6d120ff59 Mon Sep 17 00:00:00 2001 From: prime31 Date: Thu, 13 Aug 2020 12:24:11 -0700 Subject: [PATCH 076/146] Update ci.yml --- zig-ecs/.github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zig-ecs/.github/workflows/ci.yml b/zig-ecs/.github/workflows/ci.yml index 276abaf..6e74785 100644 --- a/zig-ecs/.github/workflows/ci.yml +++ b/zig-ecs/.github/workflows/ci.yml @@ -1,6 +1,5 @@ -- name: Setup Zig - uses: goto-bus-stop/setup-zig@v1.2.3 - +name: Workflow +on: [push] jobs: test: @@ -11,6 +10,8 @@ jobs: steps: - uses: actions/checkout@v2 - uses: goto-bus-stop/setup-zig@v1 + with: + version: master - run: zig build test lint: runs-on: ubuntu-latest From 25a051981c56dc836bfda4d287e91c6ad7052d16 Mon Sep 17 00:00:00 2001 From: prime31 Date: Thu, 13 Aug 2020 12:32:42 -0700 Subject: [PATCH 077/146] Update ci.yml --- zig-ecs/.github/workflows/ci.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/zig-ecs/.github/workflows/ci.yml b/zig-ecs/.github/workflows/ci.yml index 6e74785..d73388a 100644 --- a/zig-ecs/.github/workflows/ci.yml +++ b/zig-ecs/.github/workflows/ci.yml @@ -12,10 +12,7 @@ jobs: - uses: goto-bus-stop/setup-zig@v1 with: version: master + - run: zig fmt src + - run: zig fmt tests + - run: zig fmt examples - run: zig build test - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: goto-bus-stop/setup-zig@v1 - - run: zig fmt --check src/*.zig From 19c25c9ca42ca5a16a815b5b11e4abb17a4b2870 Mon Sep 17 00:00:00 2001 From: prime31 Date: Thu, 13 Aug 2020 12:38:50 -0700 Subject: [PATCH 078/146] Update ci.yml --- zig-ecs/.github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zig-ecs/.github/workflows/ci.yml b/zig-ecs/.github/workflows/ci.yml index d73388a..6fb5bdc 100644 --- a/zig-ecs/.github/workflows/ci.yml +++ b/zig-ecs/.github/workflows/ci.yml @@ -12,7 +12,5 @@ jobs: - uses: goto-bus-stop/setup-zig@v1 with: version: master - - run: zig fmt src - - run: zig fmt tests - - run: zig fmt examples + - run: zig fmt . - run: zig build test From 427c925bc52025aceeb66cd015969a75549e6bdc Mon Sep 17 00:00:00 2001 From: prime31 Date: Thu, 13 Aug 2020 12:44:28 -0700 Subject: [PATCH 079/146] Update ci.yml --- zig-ecs/.github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zig-ecs/.github/workflows/ci.yml b/zig-ecs/.github/workflows/ci.yml index 6fb5bdc..5043895 100644 --- a/zig-ecs/.github/workflows/ci.yml +++ b/zig-ecs/.github/workflows/ci.yml @@ -12,5 +12,8 @@ jobs: - uses: goto-bus-stop/setup-zig@v1 with: version: master - - run: zig fmt . + - run: zig fmt src + - run: zig fmt examples + - run: zig fmt tests + - run: zig fmt build.zig - run: zig build test From 5360be5b0e16c1ee9851d99d3e73c08c6e56a11a Mon Sep 17 00:00:00 2001 From: prime31 Date: Tue, 18 Aug 2020 11:51:43 -0700 Subject: [PATCH 080/146] Update README.md --- zig-ecs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zig-ecs/README.md b/zig-ecs/README.md index 387f300..54a24ad 100644 --- a/zig-ecs/README.md +++ b/zig-ecs/README.md @@ -34,9 +34,9 @@ while (iter.next()) |entity| { } ``` -The same example using a non-owning Group: +The same example using an owning Group: ```zig -var group = reg.group(.{}, .{ Velocity, Position }, .{}); +var group = reg.group(.{ Velocity, Position }, .{}, .{}); group.each(each); fn each(e: struct { vel: *Velocity, pos: *Position }) void { From 6b4ed304a68930ee2ef567e56295ca38fbeedbeb Mon Sep 17 00:00:00 2001 From: CodeHz Date: Thu, 3 Sep 2020 09:07:06 +0000 Subject: [PATCH 081/146] fix iterate hashmap --- zig-ecs/src/ecs/registry.zig | 3 ++- zig-ecs/src/ecs/type_store.zig | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index b049414..9703202 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -195,7 +195,8 @@ pub const Registry = struct { } pub fn deinit(self: *Registry) void { - for (self.components.items()) |ptr| { + var iter = self.components.iterator(); + while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit var storage = @intToPtr(*Storage(u1), ptr.value); storage.deinit(); diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index 21db41f..a16704d 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -14,7 +14,8 @@ pub const TypeStore = struct { } pub fn deinit(self: *TypeStore) void { - for (self.map.items()) |kv| { + var iter = self.map.iterator(); + while (iter.next()) |kv| { self.allocator.free(kv.value); } self.map.deinit(); From f1171b78c81782525901d84115f152c8d032fdb6 Mon Sep 17 00:00:00 2001 From: CodeHz Date: Sun, 6 Sep 2020 07:12:55 +0000 Subject: [PATCH 082/146] fix for ziglang pull 6246 --- zig-ecs/src/ecs/entity.zig | 6 +++--- zig-ecs/src/ecs/groups.zig | 4 ++-- zig-ecs/src/ecs/handles.zig | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/zig-ecs/src/ecs/entity.zig b/zig-ecs/src/ecs/entity.zig index c8f505a..2679232 100644 --- a/zig-ecs/src/ecs/entity.zig +++ b/zig-ecs/src/ecs/entity.zig @@ -14,9 +14,9 @@ pub fn EntityTraitsType(comptime size: EntityTraitsSize) type { } fn EntityTraitsDefinition(comptime EntityType: type, comptime IndexType: type, comptime VersionType: type) type { - std.debug.assert(@typeInfo(EntityType) == .Int and !EntityType.is_signed); - std.debug.assert(@typeInfo(IndexType) == .Int and !IndexType.is_signed); - std.debug.assert(@typeInfo(VersionType) == .Int and !VersionType.is_signed); + std.debug.assert(@typeInfo(EntityType) == .Int and !@typeInfo(EntityType).Int.is_signed); + std.debug.assert(@typeInfo(IndexType) == .Int and !@typeInfo(IndexType).Int.is_signed); + std.debug.assert(@typeInfo(VersionType) == .Int and !@typeInfo(VersionType).Int.is_signed); if (@bitSizeOf(IndexType) + @bitSizeOf(VersionType) != @bitSizeOf(EntityType)) @compileError("IndexType and VersionType must sum to EntityType's bit count"); diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index b5a03b9..683d1f4 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -85,7 +85,7 @@ pub const OwningGroup = struct { var component_ptrs: [component_info.fields.len][*]u8 = undefined; inline for (component_info.fields) |field, i| { - const storage = group.registry.assure(field.field_type.Child); + const storage = group.registry.assure(@typeInfo(field.field_type).Pointer.child); component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); } @@ -104,7 +104,7 @@ pub const OwningGroup = struct { // fill and return the struct var comps: Components = undefined; inline for (@typeInfo(Components).Struct.fields) |field, i| { - const typed_ptr = @ptrCast([*]field.field_type.Child, @alignCast(@alignOf(field.field_type.Child), it.component_ptrs[i])); + const typed_ptr = @ptrCast([*]@typeInfo(field.field_type).Pointer.child, @alignCast(@alignOf(@typeInfo(field.field_type).Pointer.child), it.component_ptrs[i])); @field(comps, field.name) = &typed_ptr[it.index]; } return comps; diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig index 567aeb9..d769087 100644 --- a/zig-ecs/src/ecs/handles.zig +++ b/zig-ecs/src/ecs/handles.zig @@ -4,9 +4,9 @@ const std = @import("std"); /// you choose the type of the handle (aka its size) and how much of that goes to the index and the version. /// the bitsize of version + id must equal the handle size. pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime VersionType: type) type { - std.debug.assert(@typeInfo(HandleType) == .Int and !HandleType.is_signed); - std.debug.assert(@typeInfo(IndexType) == .Int and !IndexType.is_signed); - std.debug.assert(@typeInfo(VersionType) == .Int and !VersionType.is_signed); + std.debug.assert(@typeInfo(HandleType) == .Int and !@typeInfo(HandleType).Int.is_signed); + std.debug.assert(@typeInfo(IndexType) == .Int and !@typeInfo(IndexType).Int.is_signed); + std.debug.assert(@typeInfo(VersionType) == .Int and !@typeInfo(VersionType).Int.is_signed); if (@bitSizeOf(IndexType) + @bitSizeOf(VersionType) != @bitSizeOf(HandleType)) @compileError("IndexType and VersionType must sum to HandleType's bit count"); From 6e412489a2bb423993f0ad72e5b996799448cdce Mon Sep 17 00:00:00 2001 From: Maurizio Crocci Date: Sat, 28 Nov 2020 02:21:03 +0000 Subject: [PATCH 083/146] Fixes Registry.removeAll iteration --- zig-ecs/src/ecs/registry.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 9703202..3ec8027 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -343,7 +343,8 @@ pub const Registry = struct { pub fn removeAll(self: *Registry, entity: Entity) void { assert(self.valid(entity)); - for (self.components.items()) |ptr| { + var iter = self.components.iterator(); + for (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to be able to call methods on the Storage(T) var store = @intToPtr(*Storage(u1), ptr.value); store.removeIfContains(entity); From 695e59bbf5f64a51cba0458730467cab38309970 Mon Sep 17 00:00:00 2001 From: Mike Desaro Date: Fri, 27 Nov 2020 20:02:40 -0800 Subject: [PATCH 084/146] hack to validate unsigned. closes #7 --- zig-ecs/src/ecs/entity.zig | 6 +++--- zig-ecs/src/ecs/handles.zig | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/zig-ecs/src/ecs/entity.zig b/zig-ecs/src/ecs/entity.zig index 2679232..076fe9d 100644 --- a/zig-ecs/src/ecs/entity.zig +++ b/zig-ecs/src/ecs/entity.zig @@ -14,9 +14,9 @@ pub fn EntityTraitsType(comptime size: EntityTraitsSize) type { } fn EntityTraitsDefinition(comptime EntityType: type, comptime IndexType: type, comptime VersionType: type) type { - std.debug.assert(@typeInfo(EntityType) == .Int and !@typeInfo(EntityType).Int.is_signed); - std.debug.assert(@typeInfo(IndexType) == .Int and !@typeInfo(IndexType).Int.is_signed); - std.debug.assert(@typeInfo(VersionType) == .Int and !@typeInfo(VersionType).Int.is_signed); + std.debug.assert(@typeInfo(EntityType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(EntityType)) == EntityType); + std.debug.assert(@typeInfo(IndexType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(IndexType)) == IndexType); + std.debug.assert(@typeInfo(VersionType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(VersionType)) == VersionType); if (@bitSizeOf(IndexType) + @bitSizeOf(VersionType) != @bitSizeOf(EntityType)) @compileError("IndexType and VersionType must sum to EntityType's bit count"); diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig index d769087..452c974 100644 --- a/zig-ecs/src/ecs/handles.zig +++ b/zig-ecs/src/ecs/handles.zig @@ -4,9 +4,9 @@ const std = @import("std"); /// you choose the type of the handle (aka its size) and how much of that goes to the index and the version. /// the bitsize of version + id must equal the handle size. pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime VersionType: type) type { - std.debug.assert(@typeInfo(HandleType) == .Int and !@typeInfo(HandleType).Int.is_signed); - std.debug.assert(@typeInfo(IndexType) == .Int and !@typeInfo(IndexType).Int.is_signed); - std.debug.assert(@typeInfo(VersionType) == .Int and !@typeInfo(VersionType).Int.is_signed); + std.debug.assert(@typeInfo(HandleType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(HandleType)) == HandleType); + std.debug.assert(@typeInfo(IndexType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(IndexType)) == IndexType); + std.debug.assert(@typeInfo(VersionType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(VersionType)) == VersionType); if (@bitSizeOf(IndexType) + @bitSizeOf(VersionType) != @bitSizeOf(HandleType)) @compileError("IndexType and VersionType must sum to HandleType's bit count"); From ae22b69026915274fa8a50c5c7d150f914e450f0 Mon Sep 17 00:00:00 2001 From: Mike Desaro Date: Fri, 27 Nov 2020 20:02:50 -0800 Subject: [PATCH 085/146] arm64 support --- zig-ecs/.gitignore | 1 + zig-ecs/.vscode/tasks.json | 17 +++++++++++++++++ zig-ecs/build.zig | 8 ++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/zig-ecs/.gitignore b/zig-ecs/.gitignore index 2040c29..b72f969 100644 --- a/zig-ecs/.gitignore +++ b/zig-ecs/.gitignore @@ -1 +1,2 @@ zig-cache +zig-arm-cache diff --git a/zig-ecs/.vscode/tasks.json b/zig-ecs/.vscode/tasks.json index dce0dbc..2af359a 100644 --- a/zig-ecs/.vscode/tasks.json +++ b/zig-ecs/.vscode/tasks.json @@ -2,6 +2,11 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", + "options": { + "env": { + "ZIG_SYSTEM_LINKER_HACK": "1", + } + }, "tasks": [ { "label": "Build Project", @@ -26,6 +31,18 @@ "clear": true } }, + { + "label": "Build and Run Project (x64 on arm)", + "type": "shell", + "command": "~/zig/zig-x64/zig build run", + "problemMatcher": [ + "$gcc" + ], + "group": "build", + "presentation": { + "clear": true + } + }, { "label": "Build and Run Project (release-fast)", "type": "shell", diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 8af56bf..7a099dd 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -1,9 +1,13 @@ -const Builder = @import("std").build.Builder; +const std = @import("std"); +const Builder = std.build.Builder; const builtin = @import("builtin"); pub fn build(b: *Builder) void { const buildMode = b.standardReleaseOptions(); + // use a different cache folder for macos arm builds + b.cache_root = if (std.builtin.os.tag == .macos and std.builtin.arch == std.builtin.Arch.aarch64) "zig-arm-cache/bin" else "zig-cache/bin"; + const examples = [_][2][]const u8{ [_][]const u8{ "view_vs_group", "examples/view_vs_group.zig" }, [_][]const u8{ "group_sort", "examples/group_sort.zig" }, @@ -25,7 +29,7 @@ pub fn build(b: *Builder) void { // first element in the list is added as "run" so "zig build run" works if (i == 0) { - exe.setOutputDir("zig-cache/bin"); + exe.setOutputDir(std.fs.path.joinPosix(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); const run_exe_step = b.step("run", b.fmt("run {}.zig", .{name})); run_exe_step.dependOn(&run_cmd.step); } From 3c540293fdb521157492cf0e240591d53dedfc92 Mon Sep 17 00:00:00 2001 From: foxnne Date: Thu, 3 Dec 2020 15:59:28 -0600 Subject: [PATCH 086/146] add getPackage to build.zig --- zig-ecs/build.zig | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 7a099dd..b126d3c 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -55,8 +55,15 @@ pub const LibType = enum(i32) { exe_compiled, }; -/// rel_path is used to add package paths. It should be the the same path used to include this build file -pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std.build.Target, lib_type: LibType, rel_path: []const u8) void { +pub fn getPackage(comptime prefix_path: []const u8) std.build.Pkg { + return .{ + .name = "ecs", + .path = prefix_path ++ "src/ecs.zig", + }; +} + +/// prefix_path is used to add package paths. It should be the the same path used to include this build file +pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std.build.Target, lib_type: LibType, prefix_path: []const u8) void { switch (lib_type) { .static => { const lib = b.addStaticLibrary("ecs", "ecs.zig"); @@ -75,5 +82,5 @@ pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std else => {}, } - artifact.addPackagePath("ecs", std.fs.path.join(b.allocator, &[_][]const u8{ rel_path, "ecs.zig" }) catch unreachable); + artifact.addPackage(getPackage(prefix_path)); } From 737e5a9b3913f0a95542bf01b1e273ddb349b4f4 Mon Sep 17 00:00:00 2001 From: foxnne Date: Thu, 3 Dec 2020 16:23:29 -0600 Subject: [PATCH 087/146] fix error in linkArtifact --- zig-ecs/build.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index b126d3c..5dd9bf1 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -63,7 +63,8 @@ pub fn getPackage(comptime prefix_path: []const u8) std.build.Pkg { } /// prefix_path is used to add package paths. It should be the the same path used to include this build file -pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std.build.Target, lib_type: LibType, prefix_path: []const u8) void { +pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std.build.Target, lib_type: LibType, comptime prefix_path: []const u8) void { + const buildMode = b.standardReleaseOptions(); switch (lib_type) { .static => { const lib = b.addStaticLibrary("ecs", "ecs.zig"); @@ -73,7 +74,7 @@ pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std artifact.linkLibrary(lib); }, .dynamic => { - const lib = b.addSharedLibrary("ecs", "ecs.zig", null); + const lib = b.addSharedLibrary("ecs", "ecs.zig", .unversioned); lib.setBuildMode(buildMode); lib.install(); From b8117306fae01ca9d473939a6931b747c5767383 Mon Sep 17 00:00:00 2001 From: Mike Desaro Date: Fri, 4 Dec 2020 12:48:15 -0800 Subject: [PATCH 088/146] zig 0.7 fixes --- zig-ecs/src/ecs/groups.zig | 4 ++-- zig-ecs/src/ecs/registry.zig | 2 +- zig-ecs/src/resources/assets.zig | 5 +++-- zig-ecs/src/resources/cache.zig | 5 +++-- zig-ecs/src/signals/dispatcher.zig | 3 ++- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 683d1f4..0af32c1 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -173,7 +173,7 @@ pub const OwningGroup = struct { var component_ptrs: [component_info.fields.len][*]u8 = undefined; inline for (component_info.fields) |field, i| { - const storage = self.registry.assure(field.field_type.Child); + const storage = self.registry.assure(std.meta.Child(field.field_type)); component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); } @@ -181,7 +181,7 @@ pub const OwningGroup = struct { const index = self.firstOwnedStorage().set.index(entity); var comps: Components = undefined; inline for (component_info.fields) |field, i| { - const typed_ptr = @ptrCast([*]field.field_type.Child, @alignCast(@alignOf(field.field_type.Child), component_ptrs[i])); + const typed_ptr = @ptrCast([*]std.meta.Child(field.field_type), @alignCast(@alignOf(std.meta.Child(field.field_type)), component_ptrs[i])); @field(comps, field.name) = &typed_ptr[index]; } diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 3ec8027..c0b861b 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -344,7 +344,7 @@ pub const Registry = struct { assert(self.valid(entity)); var iter = self.components.iterator(); - for (iter.next()) |ptr| { + while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to be able to call methods on the Storage(T) var store = @intToPtr(*Storage(u1), ptr.value); store.removeIfContains(entity); diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index bb02846..cc6aa53 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -14,7 +14,8 @@ pub const Assets = struct { } pub fn deinit(self: *Assets) void { - for (self.caches.items()) |ptr| { + var iter = self.caches.iterator(); + while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit @intToPtr(*Cache(u1), ptr.value).deinit(); } @@ -39,7 +40,7 @@ pub const Assets = struct { fn ReturnType(comptime loader: anytype, strip_ptr: bool) type { var ret = @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.?; if (strip_ptr) { - return ret.Child; + return std.meta.Child(ret); } return ret; } diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index 576ef9e..d9d9fd0 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -67,7 +67,8 @@ pub fn Cache(comptime T: type) type { pub fn clear(self: *@This()) void { // optionally deinit any resources that have a deinit method if (@hasDecl(T, "deinit")) { - for (self.resources.items()) |kv| { + var iter = self.resources.iterator(); + while (iter.next()) |kv| { @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); } } @@ -75,7 +76,7 @@ pub fn Cache(comptime T: type) type { } pub fn size(self: @This()) usize { - return self.resources.items().len; + return self.resources.count(); } }; } diff --git a/zig-ecs/src/signals/dispatcher.zig b/zig-ecs/src/signals/dispatcher.zig index 15eab06..6bbaf03 100644 --- a/zig-ecs/src/signals/dispatcher.zig +++ b/zig-ecs/src/signals/dispatcher.zig @@ -15,7 +15,8 @@ pub const Dispatcher = struct { } pub fn deinit(self: *Dispatcher) void { - for (self.signals.items()) |ptr| { + var iter = self.signals.iterator(); + while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit var signal = @intToPtr(*Signal(void), ptr.value); signal.deinit(); From 3591aac01929f6f07206c932eac21f40bbedbd7f Mon Sep 17 00:00:00 2001 From: Mike Desaro Date: Thu, 10 Dec 2020 11:08:44 -0800 Subject: [PATCH 089/146] snake case --- zig-ecs/build.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 5dd9bf1..5e34207 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -3,7 +3,7 @@ const Builder = std.build.Builder; const builtin = @import("builtin"); pub fn build(b: *Builder) void { - const buildMode = b.standardReleaseOptions(); + const build_mode = b.standardReleaseOptions(); // use a different cache folder for macos arm builds b.cache_root = if (std.builtin.os.tag == .macos and std.builtin.arch == std.builtin.Arch.aarch64) "zig-arm-cache/bin" else "zig-cache/bin"; @@ -37,12 +37,12 @@ pub fn build(b: *Builder) void { // internal tests const internal_test_step = b.addTest("src/tests.zig"); - internal_test_step.setBuildMode(buildMode); + internal_test_step.setBuildMode(build_mode); // public api tests const test_step = b.addTest("tests/tests.zig"); test_step.addPackagePath("ecs", "src/ecs.zig"); - test_step.setBuildMode(buildMode); + test_step.setBuildMode(build_mode); const test_cmd = b.step("test", "Run the tests"); test_cmd.dependOn(&internal_test_step.step); @@ -64,18 +64,18 @@ pub fn getPackage(comptime prefix_path: []const u8) std.build.Pkg { /// prefix_path is used to add package paths. It should be the the same path used to include this build file pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std.build.Target, lib_type: LibType, comptime prefix_path: []const u8) void { - const buildMode = b.standardReleaseOptions(); + const build_mode = b.standardReleaseOptions(); switch (lib_type) { .static => { const lib = b.addStaticLibrary("ecs", "ecs.zig"); - lib.setBuildMode(buildMode); + lib.setBuildMode(build_mode); lib.install(); artifact.linkLibrary(lib); }, .dynamic => { const lib = b.addSharedLibrary("ecs", "ecs.zig", .unversioned); - lib.setBuildMode(buildMode); + lib.setBuildMode(build_mode); lib.install(); artifact.linkLibrary(lib); From 41a5180ece6a019880be8e11a6489a83fbfe1fc9 Mon Sep 17 00:00:00 2001 From: Mike Desaro Date: Thu, 10 Dec 2020 19:32:22 -0800 Subject: [PATCH 090/146] ignore mac files --- zig-ecs/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zig-ecs/.gitignore b/zig-ecs/.gitignore index b72f969..2aca84c 100644 --- a/zig-ecs/.gitignore +++ b/zig-ecs/.gitignore @@ -1,2 +1,4 @@ zig-cache zig-arm-cache + +.DS_Store \ No newline at end of file From b398969be6786df38ecda7190f9dbee760987314 Mon Sep 17 00:00:00 2001 From: Mike Desaro Date: Sat, 12 Dec 2020 15:23:45 -0800 Subject: [PATCH 091/146] fix cache dir --- zig-ecs/build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 5e34207..324c12c 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -6,7 +6,7 @@ pub fn build(b: *Builder) void { const build_mode = b.standardReleaseOptions(); // use a different cache folder for macos arm builds - b.cache_root = if (std.builtin.os.tag == .macos and std.builtin.arch == std.builtin.Arch.aarch64) "zig-arm-cache/bin" else "zig-cache/bin"; + b.cache_root = if (std.builtin.os.tag == .macos and std.builtin.arch == std.builtin.Arch.aarch64) "zig-arm-cache" else "zig-cache"; const examples = [_][2][]const u8{ [_][]const u8{ "view_vs_group", "examples/view_vs_group.zig" }, From 3b279cec59364c7b7dbcaab4760f72639e0a7c85 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 13 Dec 2020 19:42:43 -0800 Subject: [PATCH 092/146] better build --- zig-ecs/.vscode/tasks.json | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/zig-ecs/.vscode/tasks.json b/zig-ecs/.vscode/tasks.json index dce0dbc..92abd77 100644 --- a/zig-ecs/.vscode/tasks.json +++ b/zig-ecs/.vscode/tasks.json @@ -26,6 +26,21 @@ "clear": true } }, + { + "label": "Pooooooooop", + "type": "shell", + "command": "zig build ${input:zigTarget}", + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "clear": true + } + }, { "label": "Build and Run Project (release-fast)", "type": "shell", @@ -101,5 +116,17 @@ "isDefault": true } }, - ] + ], + "inputs": [ + { + "id": "zigTarget", + "description": "choose which target to run", + "type": "pickString", + "options": [ + "run", + "group_sort" + ], + "default": "run" + } + ], } \ No newline at end of file From b8e6f16d6f40e401c666ac09e414da7157b06ab1 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 13 Dec 2020 23:18:35 -0800 Subject: [PATCH 093/146] cleaned --- zig-ecs/.vscode/tasks.json | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/zig-ecs/.vscode/tasks.json b/zig-ecs/.vscode/tasks.json index cbdbfd4..d85f599 100644 --- a/zig-ecs/.vscode/tasks.json +++ b/zig-ecs/.vscode/tasks.json @@ -16,21 +16,6 @@ "$gcc" ], }, - { - "label": "Build and Run Specific Target", - "type": "shell", - "command": "zig build ${input:zigTarget}", - "problemMatcher": [ - "$gcc" - ], - "group": { - "kind": "build", - "isDefault": true - }, - "presentation": { - "clear": true - } - }, { "label": "Build and Run Project", "type": "shell", @@ -133,19 +118,5 @@ "isDefault": true } }, - ], - "inputs": [ - { - "id": "zigTarget", - "type": "command", - "command": "zig.getTargets", - // "description": "choose which target to run", - // "type": "pickString", - // "options": [ - // "run", - // "group_sort" - // ], - // "default": "run" - } - ], + ] } From b7c71e51e76541a67af54888b71926f0dd36876d Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 14 Dec 2020 21:12:43 -0800 Subject: [PATCH 094/146] fix commas --- zig-ecs/.vscode/tasks.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zig-ecs/.vscode/tasks.json b/zig-ecs/.vscode/tasks.json index d85f599..fbd4fc5 100644 --- a/zig-ecs/.vscode/tasks.json +++ b/zig-ecs/.vscode/tasks.json @@ -4,7 +4,7 @@ "version": "2.0.0", "options": { "env": { - "ZIG_SYSTEM_LINKER_HACK": "1", + "ZIG_SYSTEM_LINKER_HACK": "1" } }, "tasks": [ @@ -14,7 +14,7 @@ "command": "zig build", "problemMatcher": [ "$gcc" - ], + ] }, { "label": "Build and Run Project", @@ -117,6 +117,6 @@ "kind": "build", "isDefault": true } - }, + } ] } From e3291e3012c4dcef340c881cbc412f293d3e7834 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 21 Dec 2020 22:14:32 -0800 Subject: [PATCH 095/146] dont foget windows --- zig-ecs/build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 324c12c..1fb580d 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -29,7 +29,7 @@ pub fn build(b: *Builder) void { // first element in the list is added as "run" so "zig build run" works if (i == 0) { - exe.setOutputDir(std.fs.path.joinPosix(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); + exe.setOutputDir(std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); const run_exe_step = b.step("run", b.fmt("run {}.zig", .{name})); run_exe_step.dependOn(&run_cmd.step); } From 6233d751cfd7cad21436dc07c8e999362f34b4fe Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 29 Dec 2020 23:04:52 -0800 Subject: [PATCH 096/146] set output dir for all exes --- zig-ecs/build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 1fb580d..28a67cb 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -20,6 +20,7 @@ pub fn build(b: *Builder) void { var exe = b.addExecutable(name, source); exe.setBuildMode(b.standardReleaseOptions()); + exe.setOutputDir(std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); exe.addPackagePath("ecs", "src/ecs.zig"); exe.linkSystemLibrary("c"); @@ -29,7 +30,6 @@ pub fn build(b: *Builder) void { // first element in the list is added as "run" so "zig build run" works if (i == 0) { - exe.setOutputDir(std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); const run_exe_step = b.step("run", b.fmt("run {}.zig", .{name})); run_exe_step.dependOn(&run_cmd.step); } From efb8127b788ee7350820613f6628edfcaa80093c Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Mon, 12 Apr 2021 03:17:27 -0600 Subject: [PATCH 097/146] Update to zig 0.8.0-dev.1721 --- zig-ecs/build.zig | 4 ++-- zig-ecs/src/ecs/registry.zig | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 28a67cb..f7e2fa1 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -25,12 +25,12 @@ pub fn build(b: *Builder) void { exe.linkSystemLibrary("c"); const run_cmd = exe.run(); - const exe_step = b.step(name, b.fmt("run {}.zig", .{name})); + const exe_step = b.step(name, b.fmt("run {s}.zig", .{name})); exe_step.dependOn(&run_cmd.step); // first element in the list is added as "run" so "zig build run" works if (i == 0) { - const run_exe_step = b.step("run", b.fmt("run {}.zig", .{name})); + const run_exe_step = b.step("run", b.fmt("run {s}.zig", .{name})); run_exe_step.dependOn(&run_cmd.step); } } diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index c0b861b..a862eb1 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -593,7 +593,7 @@ pub const Registry = struct { /// given the 3 group Types arrays, generates a (mostly) unique u64 hash. Simultaneously ensures there are no duped types between /// the 3 groups. - inline fn hashGroupTypes(comptime owned: anytype, comptime includes: anytype, comptime excludes: anytype) u64 { + fn hashGroupTypes(comptime owned: anytype, comptime includes: anytype, comptime excludes: anytype) callconv(.Inline) u64 { comptime { for (owned) |t1| { for (includes) |t2| { @@ -615,7 +615,7 @@ pub const Registry = struct { } /// expects a tuple of types. Convertes them to type names, sorts them then concatenates and returns the string. - inline fn concatTypes(comptime types: anytype) []const u8 { + fn concatTypes(comptime types: anytype) callconv(.Inline) []const u8 { comptime { if (types.len == 0) return "_"; From 6df575ee04e4c41ccb4919520685ab98457b945b Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Sun, 13 Jun 2021 20:07:25 -0600 Subject: [PATCH 098/146] Update some uses of HashMap to match 0.8.0 changes --- zig-ecs/src/ecs/registry.zig | 10 +++++----- zig-ecs/src/ecs/type_store.zig | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index a862eb1..22fc0ff 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -195,10 +195,10 @@ pub const Registry = struct { } pub fn deinit(self: *Registry) void { - var iter = self.components.iterator(); + var iter = self.components.valueIterator(); while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit - var storage = @intToPtr(*Storage(u1), ptr.value); + var storage = @intToPtr(*Storage(u1), ptr.*); storage.deinit(); } @@ -216,7 +216,7 @@ pub const Registry = struct { pub fn assure(self: *Registry, comptime T: type) *Storage(T) { var type_id = utils.typeId(T); if (self.components.getEntry(type_id)) |kv| { - return @intToPtr(*Storage(T), kv.value); + return @intToPtr(*Storage(T), kv.value_ptr.*); } var comp_set = Storage(T).initPtr(self.allocator); @@ -593,7 +593,7 @@ pub const Registry = struct { /// given the 3 group Types arrays, generates a (mostly) unique u64 hash. Simultaneously ensures there are no duped types between /// the 3 groups. - fn hashGroupTypes(comptime owned: anytype, comptime includes: anytype, comptime excludes: anytype) callconv(.Inline) u64 { + inline fn hashGroupTypes(comptime owned: anytype, comptime includes: anytype, comptime excludes: anytype) u64 { comptime { for (owned) |t1| { for (includes) |t2| { @@ -615,7 +615,7 @@ pub const Registry = struct { } /// expects a tuple of types. Convertes them to type names, sorts them then concatenates and returns the string. - fn concatTypes(comptime types: anytype) callconv(.Inline) []const u8 { + inline fn concatTypes(comptime types: anytype) []const u8 { comptime { if (types.len == 0) return "_"; diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index a16704d..9e6fc05 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -14,9 +14,9 @@ pub const TypeStore = struct { } pub fn deinit(self: *TypeStore) void { - var iter = self.map.iterator(); - while (iter.next()) |kv| { - self.allocator.free(kv.value); + var iter = self.map.valueIterator(); + while (iter.next()) |val_ptr| { + self.allocator.free(val_ptr.*); } self.map.deinit(); } From 514a893918e223d70caca063e40d7a7ed2e2cead Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Wed, 16 Jun 2021 00:56:01 -0600 Subject: [PATCH 099/146] Update Registry.removeAll to 0.8.0 HashMap API --- zig-ecs/src/ecs/registry.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 22fc0ff..407e293 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -343,10 +343,10 @@ pub const Registry = struct { pub fn removeAll(self: *Registry, entity: Entity) void { assert(self.valid(entity)); - var iter = self.components.iterator(); - while (iter.next()) |ptr| { + var iter = self.components.valueIterator(); + while (iter.next()) |value| { // HACK: we dont know the Type here but we need to be able to call methods on the Storage(T) - var store = @intToPtr(*Storage(u1), ptr.value); + var store = @intToPtr(*Storage(u1), value.*); store.removeIfContains(entity); } } From 04fae5d8716839df677bae934271d5b3435edbb4 Mon Sep 17 00:00:00 2001 From: Meghan Date: Sat, 19 Jun 2021 17:18:44 -0700 Subject: [PATCH 100/146] Create zig.mod --- zig-ecs/zig.mod | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 zig-ecs/zig.mod diff --git a/zig-ecs/zig.mod b/zig-ecs/zig.mod new file mode 100644 index 0000000..45519ff --- /dev/null +++ b/zig-ecs/zig.mod @@ -0,0 +1,4 @@ +id: 8cw92n7j19xzpjm8kwq0scvx3vx6k7b730vn3i6rl3t25z08 +name: ecs +main: src/ecs.zig +dependencies: From 39af857bdc54ed8f7f189d5b458e0e650d7d3318 Mon Sep 17 00:00:00 2001 From: Jonathan Bro Date: Fri, 25 Jun 2021 23:41:06 +0900 Subject: [PATCH 101/146] Resolve use of undeclared identifier 'slice' error --- zig-ecs/src/ecs/utils.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 44e99a9..a3a3510 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -43,7 +43,7 @@ pub fn ReverseSliceIterator(comptime T: type) type { } pub fn reset(self: *@This()) void { - self.index = slice.len; + self.index = self.slice.len; } }; } From 44192a139817537d08517a2a6f2b90d184994310 Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Tue, 29 Jun 2021 22:26:23 -0600 Subject: [PATCH 102/146] Update tests to return errors --- zig-ecs/src/ecs/actor.zig | 8 ++-- zig-ecs/src/ecs/component_storage.zig | 26 ++++++------- zig-ecs/src/ecs/entity.zig | 6 +-- zig-ecs/src/ecs/groups.zig | 54 +++++++++++++-------------- zig-ecs/src/ecs/handles.zig | 2 +- zig-ecs/src/ecs/sparse_set.zig | 32 ++++++++-------- zig-ecs/src/ecs/type_store.zig | 14 +++---- zig-ecs/src/ecs/utils.zig | 2 +- zig-ecs/src/ecs/views.zig | 40 ++++++++++---------- zig-ecs/src/process/scheduler.zig | 2 +- zig-ecs/src/resources/assets.zig | 12 +++--- zig-ecs/src/resources/cache.zig | 10 ++--- zig-ecs/src/signals/delegate.zig | 4 +- zig-ecs/src/signals/dispatcher.zig | 2 +- zig-ecs/src/signals/signal.zig | 14 +++---- zig-ecs/src/signals/sink.zig | 12 +++--- zig-ecs/tests/dispatcher_test.zig | 8 ++-- zig-ecs/tests/groups_test.zig | 40 ++++++++++---------- zig-ecs/tests/registry_test.zig | 38 +++++++++---------- 19 files changed, 163 insertions(+), 163 deletions(-) diff --git a/zig-ecs/src/ecs/actor.zig b/zig-ecs/src/ecs/actor.zig index 972fba5..74677d9 100644 --- a/zig-ecs/src/ecs/actor.zig +++ b/zig-ecs/src/ecs/actor.zig @@ -53,11 +53,11 @@ test "actor" { std.debug.assert(!actor.has(f32)); actor.addTyped(f32, 67.45); if (actor.tryGet(f32)) |val| { - std.testing.expectEqual(val.*, 67.45); + try std.testing.expectEqual(val.*, 67.45); } actor.addTyped(u64, 8888); - std.testing.expectEqual(actor.get(u64).*, 8888); + try std.testing.expectEqual(actor.get(u64).*, 8888); std.debug.assert(actor.has(u64)); actor.remove(u64); @@ -83,6 +83,6 @@ test "actor structs" { pos.*.x += vel.x; pos.*.y += vel.y; - std.testing.expectEqual(actor.get(Position).*.x, 5); - std.testing.expectEqual(actor.get(Position).*.y, 10); + try std.testing.expectEqual(actor.get(Position).*.x, 5); + try std.testing.expectEqual(actor.get(Position).*.y, 10); } diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 40a8cae..90d7d67 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -294,15 +294,15 @@ test "add/try-get/remove/clear" { defer store.deinit(); store.add(3, 66.45); - std.testing.expectEqual(store.tryGetConst(3).?, 66.45); + try std.testing.expectEqual(store.tryGetConst(3).?, 66.45); if (store.tryGet(3)) |found| { - std.testing.expectEqual(@as(f32, 66.45), found.*); + try std.testing.expectEqual(@as(f32, 66.45), found.*); } store.remove(3); var val_null = store.tryGet(3); - std.testing.expectEqual(val_null, null); + try std.testing.expectEqual(val_null, null); store.clear(); } @@ -312,11 +312,11 @@ test "add/get/remove" { defer store.deinit(); store.add(3, 66.45); - if (store.tryGet(3)) |found| std.testing.expectEqual(@as(f32, 66.45), found.*); - std.testing.expectEqual(store.tryGetConst(3).?, 66.45); + if (store.tryGet(3)) |found| try std.testing.expectEqual(@as(f32, 66.45), found.*); + try std.testing.expectEqual(store.tryGetConst(3).?, 66.45); store.remove(3); - std.testing.expectEqual(store.tryGet(3), null); + try std.testing.expectEqual(store.tryGet(3), null); } test "iterate" { @@ -329,13 +329,13 @@ test "iterate" { for (store.data()) |entity, i| { if (i == 0) { - std.testing.expectEqual(entity, 3); + try std.testing.expectEqual(entity, 3); } if (i == 1) { - std.testing.expectEqual(entity, 5); + try std.testing.expectEqual(entity, 5); } if (i == 2) { - std.testing.expectEqual(entity, 7); + try std.testing.expectEqual(entity, 7); } } } @@ -394,14 +394,14 @@ test "sort empty component" { comptime const asc_u32 = std.sort.asc(u32); store.sort(u32, {}, asc_u32); for (store.data()) |e, i| { - std.testing.expectEqual(@intCast(u32, i), e); + try std.testing.expectEqual(@intCast(u32, i), e); } comptime const desc_u32 = std.sort.desc(u32); store.sort(u32, {}, desc_u32); var counter: u32 = 2; for (store.data()) |e, i| { - std.testing.expectEqual(counter, e); + try std.testing.expectEqual(counter, e); if (counter > 0) counter -= 1; } } @@ -428,7 +428,7 @@ test "sort by entity" { var compare: f32 = 5; for (store.raw()) |val, i| { - std.testing.expect(compare > val); + try std.testing.expect(compare > val); compare = val; } } @@ -446,7 +446,7 @@ test "sort by component" { var compare: f32 = 5; for (store.raw()) |val, i| { - std.testing.expect(compare > val); + try std.testing.expect(compare > val); compare = val; } } diff --git a/zig-ecs/src/ecs/entity.zig b/zig-ecs/src/ecs/entity.zig index 076fe9d..f461f44 100644 --- a/zig-ecs/src/ecs/entity.zig +++ b/zig-ecs/src/ecs/entity.zig @@ -41,7 +41,7 @@ test "entity traits" { const m = EntityTraitsType(.medium).init(); const l = EntityTraitsType(.large).init(); - std.testing.expectEqual(sm.entity_mask, std.math.maxInt(sm.index_type)); - std.testing.expectEqual(m.entity_mask, std.math.maxInt(m.index_type)); - std.testing.expectEqual(l.entity_mask, std.math.maxInt(l.index_type)); + try std.testing.expectEqual(sm.entity_mask, std.math.maxInt(sm.index_type)); + try std.testing.expectEqual(m.entity_mask, std.math.maxInt(m.index_type)); + try std.testing.expectEqual(l.entity_mask, std.math.maxInt(l.index_type)); } diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 0af32c1..500f817 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -277,7 +277,7 @@ test "BasicGroup creation/iteration" { defer reg.deinit(); var group = reg.group(.{}, .{ i32, u32 }, .{}); - std.testing.expectEqual(group.len(), 0); + try std.testing.expectEqual(group.len(), 0); var e0 = reg.create(); reg.add(e0, @as(i32, 44)); @@ -290,13 +290,13 @@ test "BasicGroup creation/iteration" { while (iter.next()) |entity| { iterated_entities += 1; } - std.testing.expectEqual(iterated_entities, 1); + try std.testing.expectEqual(iterated_entities, 1); iterated_entities = 0; for (group.data()) |entity| { iterated_entities += 1; } - std.testing.expectEqual(iterated_entities, 1); + try std.testing.expectEqual(iterated_entities, 1); reg.remove(i32, e0); std.debug.assert(group.len() == 0); @@ -307,7 +307,7 @@ test "BasicGroup excludes" { defer reg.deinit(); var group = reg.group(.{}, .{i32}, .{u32}); - std.testing.expectEqual(group.len(), 0); + try std.testing.expectEqual(group.len(), 0); var e0 = reg.create(); reg.add(e0, @as(i32, 44)); @@ -319,7 +319,7 @@ test "BasicGroup excludes" { while (iter.next()) |entity| { iterated_entities += 1; } - std.testing.expectEqual(iterated_entities, 1); + try std.testing.expectEqual(iterated_entities, 1); reg.add(e0, @as(u32, 55)); std.debug.assert(group.len() == 0); @@ -334,7 +334,7 @@ test "BasicGroup create late" { reg.add(e0, @as(u32, 55)); var group = reg.group(.{}, .{ i32, u32 }, .{}); - std.testing.expectEqual(group.len(), 1); + try std.testing.expectEqual(group.len(), 1); } test "OwningGroup" { @@ -346,19 +346,19 @@ test "OwningGroup" { var e0 = reg.create(); reg.add(e0, @as(i32, 44)); reg.add(e0, @as(u32, 55)); - std.testing.expectEqual(group.len(), 1); - std.testing.expect(group.contains(e0)); + try std.testing.expectEqual(group.len(), 1); + try std.testing.expect(group.contains(e0)); - std.testing.expectEqual(group.get(i32, e0).*, 44); - std.testing.expectEqual(group.getConst(u32, e0), 55); + try std.testing.expectEqual(group.get(i32, e0).*, 44); + try std.testing.expectEqual(group.getConst(u32, e0), 55); var vals = group.getOwned(e0, struct { int: *i32, uint: *u32 }); - std.testing.expectEqual(vals.int.*, 44); - std.testing.expectEqual(vals.uint.*, 55); + try std.testing.expectEqual(vals.int.*, 44); + try std.testing.expectEqual(vals.uint.*, 55); vals.int.* = 666; var vals2 = group.getOwned(e0, struct { int: *i32, uint: *u32 }); - std.testing.expectEqual(vals2.int.*, 666); + try std.testing.expectEqual(vals2.int.*, 666); } test "OwningGroup add/remove" { @@ -370,10 +370,10 @@ test "OwningGroup add/remove" { var e0 = reg.create(); reg.add(e0, @as(i32, 44)); reg.add(e0, @as(u32, 55)); - std.testing.expectEqual(group.len(), 1); + try std.testing.expectEqual(group.len(), 1); reg.remove(u32, e0); - std.testing.expectEqual(group.len(), 0); + try std.testing.expectEqual(group.len(), 0); } test "OwningGroup iterate" { @@ -394,13 +394,13 @@ test "OwningGroup iterate" { var iter = group.iterator(struct { int: *i32, uint: *u32 }); while (iter.next()) |item| { if (iter.entity() == e0) { - std.testing.expectEqual(item.int.*, 44); - std.testing.expectEqual(item.uint.*, 55); - std.testing.expectEqual(iter.get(u8).*, 11); + try std.testing.expectEqual(item.int.*, 44); + try std.testing.expectEqual(item.uint.*, 55); + try 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); + try std.testing.expectEqual(item.int.*, 666); + try std.testing.expectEqual(item.uint.*, 999); + try std.testing.expectEqual(iter.get(f32).*, 55.5); } } } @@ -409,8 +409,8 @@ fn each(components: struct { int: *i32, uint: *u32, }) void { - std.testing.expectEqual(components.int.*, 44); - std.testing.expectEqual(components.uint.*, 55); + std.testing.expectEqual(components.int.*, 44) catch unreachable; + std.testing.expectEqual(components.uint.*, 55) catch unreachable; } test "OwningGroup each" { @@ -426,8 +426,8 @@ test "OwningGroup each" { int: *i32, uint: *u32, }) void { - std.testing.expectEqual(components.int.*, 44); - std.testing.expectEqual(components.uint.*, 55); + std.testing.expectEqual(components.int.*, 44) catch unreachable; + std.testing.expectEqual(components.uint.*, 55) catch unreachable; } }; var thing = Thing{}; @@ -456,11 +456,11 @@ test "multiple OwningGroups" { // ensure groups are ordered correctly internally var last_size: u8 = 0; for (reg.groups.items) |grp| { - std.testing.expect(last_size <= grp.size); + try std.testing.expect(last_size <= grp.size); last_size = grp.size; } - std.testing.expect(!reg.sortable(Sprite)); + try std.testing.expect(!reg.sortable(Sprite)); // this will break the group // var group6 = reg.group(.{Sprite, Rotation}, .{}, .{}); diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig index 452c974..a76370d 100644 --- a/zig-ecs/src/ecs/handles.zig +++ b/zig-ecs/src/ecs/handles.zig @@ -136,7 +136,7 @@ test "handles" { hm.remove(e1) catch unreachable; std.debug.assert(!hm.alive(e1)); - std.testing.expectError(error.RemovedInvalidHandle, hm.remove(e1)); + try std.testing.expectError(error.RemovedInvalidHandle, hm.remove(e1)); var e_tmp = hm.create(); std.debug.assert(hm.alive(e_tmp)); diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 3688c96..3f99e72 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -228,15 +228,15 @@ test "add/remove/clear" { set.add(4); set.add(3); - std.testing.expectEqual(set.len(), 2); - std.testing.expectEqual(set.index(4), 0); - std.testing.expectEqual(set.index(3), 1); + try std.testing.expectEqual(set.len(), 2); + try std.testing.expectEqual(set.index(4), 0); + try std.testing.expectEqual(set.index(3), 1); set.remove(4); - std.testing.expectEqual(set.len(), 1); + try std.testing.expectEqual(set.len(), 1); set.clear(); - std.testing.expectEqual(set.len(), 0); + try std.testing.expectEqual(set.len(), 0); } test "grow" { @@ -248,7 +248,7 @@ test "grow" { set.add(@intCast(u32, i)); } - std.testing.expectEqual(set.len(), std.math.maxInt(u8)); + try std.testing.expectEqual(set.len(), std.math.maxInt(u8)); } test "swap" { @@ -257,12 +257,12 @@ test "swap" { set.add(4); set.add(3); - std.testing.expectEqual(set.index(4), 0); - std.testing.expectEqual(set.index(3), 1); + try std.testing.expectEqual(set.index(4), 0); + try std.testing.expectEqual(set.index(3), 1); set.swap(4, 3); - std.testing.expectEqual(set.index(3), 0); - std.testing.expectEqual(set.index(4), 1); + try std.testing.expectEqual(set.index(3), 0); + try std.testing.expectEqual(set.index(4), 1); } test "data() synced" { @@ -275,12 +275,12 @@ test "data() synced" { set.add(3); var data = set.data(); - std.testing.expectEqual(data[1], 1); - std.testing.expectEqual(set.len(), data.len); + try std.testing.expectEqual(data[1], 1); + try std.testing.expectEqual(set.len(), data.len); set.remove(0); set.remove(1); - std.testing.expectEqual(set.len(), set.data().len); + try std.testing.expectEqual(set.len(), set.data().len); } test "iterate" { @@ -295,7 +295,7 @@ test "iterate" { var i: u32 = @intCast(u32, set.len()) - 1; var iter = set.reverseIterator(); while (iter.next()) |entity| { - std.testing.expectEqual(i, entity); + try std.testing.expectEqual(i, entity); if (i > 0) i -= 1; } } @@ -319,8 +319,8 @@ test "respect 1" { set1.respect(set2); - std.testing.expectEqual(set1.dense.items[0], set2.dense.items[1]); - std.testing.expectEqual(set1.dense.items[1], set2.dense.items[2]); + try std.testing.expectEqual(set1.dense.items[0], set2.dense.items[1]); + try std.testing.expectEqual(set1.dense.items[1], set2.dense.items[2]); } const desc_u32 = std.sort.desc(u32); diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index 9e6fc05..2cba525 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -67,23 +67,23 @@ test "TypeStore" { var orig = Vector{ .x = 5, .y = 6, .z = 8 }; store.add(orig); - std.testing.expect(store.has(Vector)); - std.testing.expectEqual(store.get(Vector).*, orig); + try std.testing.expect(store.has(Vector)); + try std.testing.expectEqual(store.get(Vector).*, orig); var v = store.get(Vector); - std.testing.expectEqual(v.*, Vector{ .x = 5, .y = 6, .z = 8 }); + try std.testing.expectEqual(v.*, Vector{ .x = 5, .y = 6, .z = 8 }); v.*.x = 666; var v2 = store.get(Vector); - std.testing.expectEqual(v2.*, Vector{ .x = 666, .y = 6, .z = 8 }); + try std.testing.expectEqual(v2.*, Vector{ .x = 666, .y = 6, .z = 8 }); store.remove(Vector); - std.testing.expect(!store.has(Vector)); + try std.testing.expect(!store.has(Vector)); var v3 = store.getOrAdd(u32); - std.testing.expectEqual(v3.*, 0); + try std.testing.expectEqual(v3.*, 0); v3.* = 777; var v4 = store.get(u32); - std.testing.expectEqual(v3.*, 777); + try std.testing.expectEqual(v3.*, 777); } diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 44e99a9..c8394ec 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -133,7 +133,7 @@ test "ReverseSliceIterator" { var iter = ReverseSliceIterator(usize).init(slice); var i: usize = 9; while (iter.next()) |val| { - std.testing.expectEqual(i, val); + try 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 685fcbe..11b490e 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -159,16 +159,16 @@ test "single basic view" { store.add(7, 70); var view = BasicView(f32).init(&store); - std.testing.expectEqual(view.len(), 3); + try std.testing.expectEqual(view.len(), 3); store.remove(7); - std.testing.expectEqual(view.len(), 2); + try std.testing.expectEqual(view.len(), 2); var i: usize = 0; var iter = view.iterator(); while (iter.next()) |comp| { - if (i == 0) std.testing.expectEqual(comp, 50); - if (i == 1) std.testing.expectEqual(comp, 30); + if (i == 0) try std.testing.expectEqual(comp, 50); + if (i == 1) try std.testing.expectEqual(comp, 30); i += 1; } @@ -176,12 +176,12 @@ test "single basic view" { var entIter = view.entityIterator(); while (entIter.next()) |ent| { if (i == 0) { - std.testing.expectEqual(ent, 5); - std.testing.expectEqual(view.getConst(ent), 50); + try std.testing.expectEqual(ent, 5); + try std.testing.expectEqual(view.getConst(ent), 50); } if (i == 1) { - std.testing.expectEqual(ent, 3); - std.testing.expectEqual(view.getConst(ent), 30); + try std.testing.expectEqual(ent, 3); + try std.testing.expectEqual(view.getConst(ent), 30); } i += 1; } @@ -197,27 +197,27 @@ test "single basic view data" { var view = BasicView(f32).init(&store); - std.testing.expectEqual(view.get(3).*, 30); + try std.testing.expectEqual(view.get(3).*, 30); for (view.data()) |entity, i| { if (i == 0) - std.testing.expectEqual(entity, 3); + try std.testing.expectEqual(entity, 3); if (i == 1) - std.testing.expectEqual(entity, 5); + try std.testing.expectEqual(entity, 5); if (i == 2) - std.testing.expectEqual(entity, 7); + try std.testing.expectEqual(entity, 7); } for (view.raw()) |data, i| { if (i == 0) - std.testing.expectEqual(data, 30); + try std.testing.expectEqual(data, 30); if (i == 1) - std.testing.expectEqual(data, 50); + try std.testing.expectEqual(data, 50); if (i == 2) - std.testing.expectEqual(data, 70); + try std.testing.expectEqual(data, 70); } - std.testing.expectEqual(view.len(), 3); + try std.testing.expectEqual(view.len(), 3); } test "basic multi view" { @@ -244,7 +244,7 @@ test "basic multi view" { iterated_entities += 1; } - std.testing.expectEqual(iterated_entities, 2); + try std.testing.expectEqual(iterated_entities, 2); iterated_entities = 0; reg.remove(u32, e0); @@ -254,7 +254,7 @@ test "basic multi view" { iterated_entities += 1; } - std.testing.expectEqual(iterated_entities, 1); + try std.testing.expectEqual(iterated_entities, 1); } test "basic multi view with excludes" { @@ -282,7 +282,7 @@ test "basic multi view with excludes" { iterated_entities += 1; } - std.testing.expectEqual(iterated_entities, 1); + try std.testing.expectEqual(iterated_entities, 1); iterated_entities = 0; reg.remove(u8, e2); @@ -292,5 +292,5 @@ test "basic multi view with excludes" { iterated_entities += 1; } - std.testing.expectEqual(iterated_entities, 2); + try std.testing.expectEqual(iterated_entities, 2); } diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index f1f491c..f20a01a 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -231,5 +231,5 @@ test "scheduler.attach.next" { _ = scheduler.attach(Tester, &counter).next(Tester, &counter); scheduler.update(); scheduler.update(); - std.testing.expectEqual(counter, 2); + try std.testing.expectEqual(counter, 2); } diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index cc6aa53..785020c 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -17,7 +17,7 @@ pub const Assets = struct { var iter = self.caches.iterator(); while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit - @intToPtr(*Cache(u1), ptr.value).deinit(); + @intToPtr(*Cache(u1), ptr.value_ptr.*).deinit(); } self.caches.deinit(); @@ -77,17 +77,17 @@ test "assets" { defer assets.deinit(); var thing = assets.get(Thing).load(6, ThingLoadArgs{}); - std.testing.expectEqual(assets.get(Thing).size(), 1); + try std.testing.expectEqual(assets.get(Thing).size(), 1); var thing2 = assets.load(4, ThingLoadArgs{}); - std.testing.expectEqual(assets.get(Thing).size(), 2); + try std.testing.expectEqual(assets.get(Thing).size(), 2); var other_thing = assets.get(OtherThing).load(6, OtherThingLoadArgs{}); - std.testing.expectEqual(assets.get(OtherThing).size(), 1); + try std.testing.expectEqual(assets.get(OtherThing).size(), 1); var other_thing2 = assets.load(8, OtherThingLoadArgs{}); - std.testing.expectEqual(assets.get(OtherThing).size(), 2); + try std.testing.expectEqual(assets.get(OtherThing).size(), 2); assets.get(OtherThing).clear(); - std.testing.expectEqual(assets.get(OtherThing).size(), 0); + try std.testing.expectEqual(assets.get(OtherThing).size(), 0); } diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index d9d9fd0..2b8b715 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -57,7 +57,7 @@ pub fn Cache(comptime T: type) type { } pub fn remove(self: *@This(), id: u32) void { - if (self.resources.remove(id)) |kv| { + if (self.resources.fetchRemove(id)) |kv| { if (@hasDecl(T, "deinit")) { @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); } @@ -69,7 +69,7 @@ pub fn Cache(comptime T: type) type { if (@hasDecl(T, "deinit")) { var iter = self.resources.iterator(); while (iter.next()) |kv| { - @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); + @call(.{ .modifier = .always_inline }, @field(kv.value_ptr.*, "deinit"), .{}); } } self.resources.clearAndFree(); @@ -102,11 +102,11 @@ test "cache" { var thing = cache.load(utils.hashString("my/id"), ThingLoadArgs{}); var thing2 = cache.load(utils.hashString("another/id"), ThingLoadArgs{}); - std.testing.expectEqual(cache.size(), 2); + try std.testing.expectEqual(cache.size(), 2); cache.remove(utils.hashString("my/id")); - std.testing.expectEqual(cache.size(), 1); + try std.testing.expectEqual(cache.size(), 1); cache.clear(); - std.testing.expectEqual(cache.size(), 0); + try std.testing.expectEqual(cache.size(), 0); } diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index f1aba76..af15eb2 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -63,14 +63,14 @@ pub fn Delegate(comptime Event: type) type { } fn tester(param: u32) void { - std.testing.expectEqual(@as(u32, 666), param); + std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } const Thing = struct { field: f32 = 0, pub fn tester(self: *Thing, param: u32) void { - std.testing.expectEqual(@as(u32, 777), param); + std.testing.expectEqual(@as(u32, 777), param) catch unreachable; } }; diff --git a/zig-ecs/src/signals/dispatcher.zig b/zig-ecs/src/signals/dispatcher.zig index 6bbaf03..15d3daf 100644 --- a/zig-ecs/src/signals/dispatcher.zig +++ b/zig-ecs/src/signals/dispatcher.zig @@ -18,7 +18,7 @@ pub const Dispatcher = struct { var iter = self.signals.iterator(); while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit - var signal = @intToPtr(*Signal(void), ptr.value); + var signal = @intToPtr(*Signal(void), ptr.value_ptr.*); signal.deinit(); } diff --git a/zig-ecs/src/signals/signal.zig b/zig-ecs/src/signals/signal.zig index 98e2368..e26d8ac 100644 --- a/zig-ecs/src/signals/signal.zig +++ b/zig-ecs/src/signals/signal.zig @@ -58,14 +58,14 @@ pub fn Signal(comptime Event: type) type { } fn tester(param: u32) void { - std.testing.expectEqual(@as(u32, 666), param); + std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } const Thing = struct { field: f32 = 0, pub fn tester(self: *Thing, param: u32) void { - std.testing.expectEqual(@as(u32, 666), param); + std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } }; @@ -75,7 +75,7 @@ test "Signal/Sink" { var sink = signal.sink(); sink.connect(tester); - std.testing.expectEqual(@as(usize, 1), signal.size()); + try std.testing.expectEqual(@as(usize, 1), signal.size()); // bound listener var thing = Thing{}; @@ -85,10 +85,10 @@ test "Signal/Sink" { sink.disconnect(tester); signal.publish(666); - std.testing.expectEqual(@as(usize, 1), signal.size()); + try std.testing.expectEqual(@as(usize, 1), signal.size()); sink.disconnectBound(&thing); - std.testing.expectEqual(@as(usize, 0), signal.size()); + try std.testing.expectEqual(@as(usize, 0), signal.size()); } test "Sink Before null" { @@ -97,9 +97,9 @@ test "Sink Before null" { var sink = signal.sink(); sink.connect(tester); - std.testing.expectEqual(@as(usize, 1), signal.size()); + try std.testing.expectEqual(@as(usize, 1), signal.size()); var thing = Thing{}; sink.before(null).connectBound(&thing, "tester"); - std.testing.expectEqual(@as(usize, 2), signal.size()); + try std.testing.expectEqual(@as(usize, 2), signal.size()); } diff --git a/zig-ecs/src/signals/sink.zig b/zig-ecs/src/signals/sink.zig index a05f86d..1e2904b 100644 --- a/zig-ecs/src/signals/sink.zig +++ b/zig-ecs/src/signals/sink.zig @@ -79,14 +79,14 @@ pub fn Sink(comptime Event: type) type { } fn tester(param: u32) void { - std.testing.expectEqual(@as(u32, 666), param); + std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } const Thing = struct { field: f32 = 0, pub fn tester(self: *Thing, param: u32) void { - std.testing.expectEqual(@as(u32, 666), param); + std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } }; @@ -95,11 +95,11 @@ test "Sink Before free" { defer signal.deinit(); signal.sink().connect(tester); - std.testing.expectEqual(signal.sink().indexOf(tester).?, 0); + try std.testing.expectEqual(signal.sink().indexOf(tester).?, 0); var thing = Thing{}; signal.sink().before(tester).connectBound(&thing, "tester"); - std.testing.expectEqual(signal.sink().indexOfBound(&thing).?, 0); + try std.testing.expectEqual(signal.sink().indexOfBound(&thing).?, 0); } test "Sink Before bound" { @@ -108,8 +108,8 @@ test "Sink Before bound" { var thing = Thing{}; signal.sink().connectBound(&thing, "tester"); - std.testing.expectEqual(signal.sink().indexOfBound(&thing).?, 0); + try std.testing.expectEqual(signal.sink().indexOfBound(&thing).?, 0); signal.sink().beforeBound(&thing).connect(tester); - std.testing.expectEqual(signal.sink().indexOf(tester).?, 0); + try std.testing.expectEqual(signal.sink().indexOf(tester).?, 0); } diff --git a/zig-ecs/tests/dispatcher_test.zig b/zig-ecs/tests/dispatcher_test.zig index 90c7eeb..fc20ebd 100644 --- a/zig-ecs/tests/dispatcher_test.zig +++ b/zig-ecs/tests/dispatcher_test.zig @@ -2,22 +2,22 @@ const std = @import("std"); const Dispatcher = @import("ecs").Dispatcher; fn tester(param: u32) void { - std.testing.expectEqual(@as(u32, 666), param); + std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } fn tester2(param: i32) void { - std.testing.expectEqual(@as(i32, -543), param); + std.testing.expectEqual(@as(i32, -543), param) catch unreachable; } const Thing = struct { field: f32 = 0, pub fn testU32(self: *Thing, param: u32) void { - std.testing.expectEqual(@as(u32, 666), param); + std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } pub fn testI32(self: *Thing, param: i32) void { - std.testing.expectEqual(@as(i32, -543), param); + std.testing.expectEqual(@as(i32, -543), param) catch unreachable; } }; diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index a58316d..2a551ad 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -51,7 +51,7 @@ test "sort BasicGroup by Entity" { var val: f32 = 0; var iter = group.iterator(); while (iter.next()) |entity| { - std.testing.expectEqual(val, group.getConst(Sprite, entity).x); + try std.testing.expectEqual(val, group.getConst(Sprite, entity).x); val += 1; } } @@ -79,7 +79,7 @@ test "sort BasicGroup by Component" { var val: f32 = 0; var iter = group.iterator(); while (iter.next()) |entity| { - std.testing.expectEqual(val, group.getConst(Sprite, entity).x); + try std.testing.expectEqual(val, group.getConst(Sprite, entity).x); val += 1; } } @@ -112,7 +112,7 @@ test "sort OwningGroup by Entity" { var val: f32 = 0; var iter = group.iterator(struct { s: *Sprite, r: *Renderable }); while (iter.next()) |entity| { - std.testing.expectEqual(val, entity.s.*.x); + try std.testing.expectEqual(val, entity.s.*.x); val += 1; } } @@ -140,7 +140,7 @@ test "sort OwningGroup by Component" { var val: f32 = 0; var iter = group.iterator(struct { s: *Sprite, r: *Renderable }); while (iter.next()) |entity| { - std.testing.expectEqual(val, entity.s.*.x); + try std.testing.expectEqual(val, entity.s.*.x); val += 1; } } @@ -161,13 +161,13 @@ test "sort OwningGroup by Component ensure unsorted non-matches" { reg.add(e2, Sprite{ .x = @intToFloat(f32, i + 1 * 50) }); } - std.testing.expectEqual(group.len(), 5); - std.testing.expectEqual(reg.len(Sprite), 10); + try std.testing.expectEqual(group.len(), 5); + try std.testing.expectEqual(reg.len(Sprite), 10); const SortContext = struct { fn sort(this: void, a: Sprite, b: Sprite) bool { // sprites with x > 50 shouldnt match in the group - std.testing.expect(a.x < 50 and b.x < 50); + std.testing.expect(a.x < 50 and b.x < 50) catch unreachable; return a.x > b.x; } }; @@ -182,7 +182,7 @@ test "sort OwningGroup by Component ensure unsorted non-matches" { // all sprite.x > 50 should be at the end and we iterate backwards if (count < 6) { - std.testing.expect(sprite.x >= 50); + try std.testing.expect(sprite.x >= 50); } } } @@ -195,23 +195,23 @@ test "nested OwningGroups add/remove components" { var group2 = reg.group(.{ Sprite, Transform }, .{Renderable}, .{}); var group3 = reg.group(.{ Sprite, Transform }, .{ Renderable, Rotation }, .{}); - std.testing.expect(!reg.sortable(Sprite)); - std.testing.expect(!reg.sortable(Transform)); - std.testing.expect(reg.sortable(Renderable)); + try std.testing.expect(!reg.sortable(Sprite)); + try std.testing.expect(!reg.sortable(Transform)); + try std.testing.expect(reg.sortable(Renderable)); var e1 = reg.create(); reg.addTypes(e1, .{ Sprite, Renderable, Rotation }); - std.testing.expectEqual(group1.len(), 1); - std.testing.expectEqual(group2.len(), 0); - std.testing.expectEqual(group3.len(), 0); + try std.testing.expectEqual(group1.len(), 1); + try std.testing.expectEqual(group2.len(), 0); + try std.testing.expectEqual(group3.len(), 0); reg.add(e1, Transform{}); - std.testing.expectEqual(group3.len(), 1); + try std.testing.expectEqual(group3.len(), 1); reg.remove(Sprite, e1); - std.testing.expectEqual(group1.len(), 0); - std.testing.expectEqual(group2.len(), 0); - std.testing.expectEqual(group3.len(), 0); + try std.testing.expectEqual(group1.len(), 0); + try std.testing.expectEqual(group2.len(), 0); + try std.testing.expectEqual(group3.len(), 0); } test "nested OwningGroups entity order" { @@ -228,8 +228,8 @@ test "nested OwningGroups entity order" { reg.add(e, Renderable{ .x = @intToFloat(f32, i) }); } - std.testing.expectEqual(group1.len(), 5); - std.testing.expectEqual(group2.len(), 0); + try std.testing.expectEqual(group1.len(), 5); + try std.testing.expectEqual(group2.len(), 0); var sprite_store = reg.assure(Sprite); var transform_store = reg.assure(Transform); diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index 09eafa6..0b57291 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -20,15 +20,15 @@ test "Registry" { reg.addTypes(e1, .{ Empty, Position }); reg.add(e1, BigOne{ .pos = Position{ .x = 5, .y = 5 }, .vel = Velocity{ .x = 5, .y = 5 }, .accel = Velocity{ .x = 5, .y = 5 } }); - std.testing.expect(reg.has(Empty, e1)); - std.testing.expect(reg.has(Position, e1)); - std.testing.expect(reg.has(BigOne, e1)); + try std.testing.expect(reg.has(Empty, e1)); + try std.testing.expect(reg.has(Position, e1)); + try std.testing.expect(reg.has(BigOne, e1)); var iter = reg.entities(); - while (iter.next()) |e| std.testing.expectEqual(e1, e); + while (iter.next()) |e| try std.testing.expectEqual(e1, e); reg.remove(Empty, e1); - std.testing.expect(!reg.has(Empty, e1)); + try std.testing.expect(!reg.has(Empty, e1)); } test "context get/set/unset" { @@ -36,16 +36,16 @@ test "context get/set/unset" { defer reg.deinit(); var ctx = reg.getContext(Position); - std.testing.expectEqual(ctx, null); + try std.testing.expectEqual(ctx, null); var pos = Position{ .x = 5, .y = 5 }; reg.setContext(&pos); ctx = reg.getContext(Position); - std.testing.expectEqual(ctx.?, &pos); + try std.testing.expectEqual(ctx.?, &pos); reg.unsetContext(Position); ctx = reg.getContext(Position); - std.testing.expectEqual(ctx, null); + try std.testing.expectEqual(ctx, null); } // this test should fail @@ -64,16 +64,16 @@ test "context get/set/unset" { defer reg.deinit(); var ctx = reg.getContext(SomeType); - std.testing.expectEqual(ctx, null); + try std.testing.expectEqual(ctx, null); var pos = SomeType{ .dummy = 0 }; reg.setContext(&pos); ctx = reg.getContext(SomeType); - std.testing.expectEqual(ctx.?, &pos); + try std.testing.expectEqual(ctx.?, &pos); reg.unsetContext(SomeType); ctx = reg.getContext(SomeType); - std.testing.expectEqual(ctx, null); + try std.testing.expectEqual(ctx, null); } test "singletons" { @@ -82,11 +82,11 @@ test "singletons" { var pos = Position{ .x = 5, .y = 5 }; reg.singletons.add(pos); - std.testing.expect(reg.singletons.has(Position)); - std.testing.expectEqual(reg.singletons.get(Position).*, pos); + try std.testing.expect(reg.singletons.has(Position)); + try std.testing.expectEqual(reg.singletons.get(Position).*, pos); reg.singletons.remove(Position); - std.testing.expect(!reg.singletons.has(Position)); + try std.testing.expect(!reg.singletons.has(Position)); } test "destroy" { @@ -105,7 +105,7 @@ test "destroy" { 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)); + try std.testing.expectEqual(Position{ .x = @intToFloat(f32, i), .y = @intToFloat(f32, i) }, reg.getConst(Position, i)); } } @@ -117,11 +117,11 @@ test "remove all" { 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)); + try std.testing.expect(reg.has(Position, e)); + try std.testing.expect(reg.has(u32, e)); reg.removeAll(e); - std.testing.expect(!reg.has(Position, e)); - std.testing.expect(!reg.has(u32, e)); + try std.testing.expect(!reg.has(Position, e)); + try std.testing.expect(!reg.has(u32, e)); } From 33a5344b10b5d26f2ee281b030e62e26f92e3303 Mon Sep 17 00:00:00 2001 From: LeRoyce Pearson Date: Tue, 29 Jun 2021 22:43:23 -0600 Subject: [PATCH 103/146] Remove unused variables and redudant `comptime`s Latest version tested: 0.9.0-dev.339+81bf05bf6 --- zig-ecs/build.zig | 2 +- zig-ecs/src/ecs/component_storage.zig | 14 +++++++------- zig-ecs/src/ecs/groups.zig | 14 +++++++------- zig-ecs/src/ecs/handles.zig | 4 ++-- zig-ecs/src/ecs/registry.zig | 14 +++++++------- zig-ecs/src/ecs/sparse_set.zig | 8 ++++---- zig-ecs/src/ecs/type_store.zig | 2 +- zig-ecs/src/ecs/views.zig | 12 ++++++------ zig-ecs/src/process/scheduler.zig | 14 +++++++------- zig-ecs/src/resources/assets.zig | 12 ++++++------ zig-ecs/src/resources/cache.zig | 5 +++-- zig-ecs/src/signals/delegate.zig | 2 +- zig-ecs/src/signals/signal.zig | 2 +- zig-ecs/src/signals/sink.zig | 6 +++--- zig-ecs/tests/dispatcher_test.zig | 4 ++-- zig-ecs/tests/groups_test.zig | 10 +++++----- zig-ecs/tests/registry_test.zig | 3 ++- 17 files changed, 65 insertions(+), 63 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index f7e2fa1..3a8c2a2 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -63,7 +63,7 @@ pub fn getPackage(comptime prefix_path: []const u8) std.build.Pkg { } /// prefix_path is used to add package paths. It should be the the same path used to include this build file -pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std.build.Target, lib_type: LibType, comptime prefix_path: []const u8) void { +pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, _: std.build.Target, lib_type: LibType, comptime prefix_path: []const u8) void { const build_mode = b.standardReleaseOptions(); switch (lib_type) { .static => { diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index 90d7d67..e1b070a 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -11,7 +11,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { std.debug.assert(!utils.isComptime(Component)); // empty (zero-sized) structs will not have an array created - comptime const is_empty_struct = @sizeOf(Component) == 0; + const is_empty_struct = @sizeOf(Component) == 0; // HACK: due to this being stored as untyped ptrs, when deinit is called we are casted to a Component of some random // non-zero sized type. That will make is_empty_struct false in deinit always so we can't use it. Instead, we stick @@ -391,16 +391,16 @@ test "sort empty component" { store.add(2, Empty{}); store.add(0, Empty{}); - comptime const asc_u32 = std.sort.asc(u32); + const asc_u32 = comptime std.sort.asc(u32); store.sort(u32, {}, asc_u32); for (store.data()) |e, i| { try std.testing.expectEqual(@intCast(u32, i), e); } - comptime const desc_u32 = std.sort.desc(u32); + const desc_u32 = comptime std.sort.desc(u32); store.sort(u32, {}, desc_u32); var counter: u32 = 2; - for (store.data()) |e, i| { + for (store.data()) |e| { try std.testing.expectEqual(counter, e); if (counter > 0) counter -= 1; } @@ -427,7 +427,7 @@ test "sort by entity" { store.sort(u32, store.len(), context, SortContext.sort); var compare: f32 = 5; - for (store.raw()) |val, i| { + for (store.raw()) |val| { try std.testing.expect(compare > val); compare = val; } @@ -441,11 +441,11 @@ test "sort by component" { store.add(11, @as(f32, 1.1)); store.add(33, @as(f32, 3.3)); - comptime const desc_f32 = std.sort.desc(f32); + const desc_f32 = comptime std.sort.desc(f32); store.sort(f32, store.len(), {}, desc_f32); var compare: f32 = 5; - for (store.raw()) |val, i| { + for (store.raw()) |val| { try std.testing.expect(compare > val); compare = val; } diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 500f817..eb746c3 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -287,13 +287,13 @@ test "BasicGroup creation/iteration" { var iterated_entities: usize = 0; var iter = group.iterator(); - while (iter.next()) |entity| { + while (iter.next()) |_| { iterated_entities += 1; } try std.testing.expectEqual(iterated_entities, 1); iterated_entities = 0; - for (group.data()) |entity| { + for (group.data()) |_| { iterated_entities += 1; } try std.testing.expectEqual(iterated_entities, 1); @@ -316,7 +316,7 @@ test "BasicGroup excludes" { var iterated_entities: usize = 0; var iter = group.iterator(); - while (iter.next()) |entity| { + while (iter.next()) |_| { iterated_entities += 1; } try std.testing.expectEqual(iterated_entities, 1); @@ -422,7 +422,7 @@ test "OwningGroup each" { reg.add(e0, @as(u32, 55)); const Thing = struct { - fn each(self: @This(), components: struct { + fn each(_: @This(), components: struct { int: *i32, uint: *u32, }) void { @@ -449,9 +449,9 @@ test "multiple OwningGroups" { // var group1 = reg.group(.{u64, u32}, .{}, .{}); // var group2 = reg.group(.{u64, u32, u8}, .{}, .{}); - var group5 = reg.group(.{ Sprite, Transform }, .{ Renderable, Rotation }, .{}); - var group3 = reg.group(.{Sprite}, .{Renderable}, .{}); - var group4 = reg.group(.{ Sprite, Transform }, .{Renderable}, .{}); + _ = reg.group(.{ Sprite, Transform }, .{ Renderable, Rotation }, .{}); + _ = reg.group(.{Sprite}, .{Renderable}, .{}); + _ = reg.group(.{ Sprite, Transform }, .{Renderable}, .{}); // ensure groups are ordered correctly internally var last_size: u8 = 0; diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig index a76370d..e444fa3 100644 --- a/zig-ecs/src/ecs/handles.zig +++ b/zig-ecs/src/ecs/handles.zig @@ -58,11 +58,11 @@ pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime Ver self.allocator.free(self.handles); } - pub fn extractId(self: Self, handle: HandleType) IndexType { + pub fn extractId(_: Self, handle: HandleType) IndexType { return @truncate(IndexType, handle); } - pub fn extractVersion(self: Self, handle: HandleType) VersionType { + pub fn extractVersion(_: Self, handle: HandleType) VersionType { return @truncate(VersionType, handle >> @bitSizeOf(IndexType)); } diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 407e293..6ebab46 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -255,12 +255,12 @@ pub const Registry = struct { } /// Returns the entity identifier without the version - pub fn entityId(self: Registry, entity: Entity) Entity { + pub fn entityId(_: Registry, entity: Entity) Entity { return entity & entity_traits.entity_mask; } /// Returns the version stored along with an entity identifier - pub fn version(self: *Registry, entity: Entity) entity_traits.version_type { + pub fn version(_: *Registry, entity: Entity) entity_traits.version_type { return @truncate(entity_traits.version_type, entity >> @bitSizeOf(entity_traits.index_type)); } @@ -471,7 +471,7 @@ pub const Registry = struct { std.debug.assert(owned.len + includes.len + excludes.len > 1); // create a unique hash to identify the group so that we can look it up - comptime const hash = comptime hashGroupTypes(owned, includes, excludes); + const hash = comptime hashGroupTypes(owned, includes, excludes); for (self.groups.items) |grp| { if (grp.hash == hash) { @@ -606,9 +606,9 @@ pub const Registry = struct { } } - const owned_str = comptime concatTypes(owned); - const includes_str = comptime concatTypes(includes); - const excludes_str = comptime concatTypes(excludes); + const owned_str = concatTypes(owned); + const includes_str = concatTypes(includes); + const excludes_str = concatTypes(excludes); return utils.hashStringFnv(u64, owned_str ++ includes_str ++ excludes_str); } @@ -620,7 +620,7 @@ pub const Registry = struct { if (types.len == 0) return "_"; const impl = struct { - fn asc(context: void, lhs: []const u8, rhs: []const u8) bool { + fn asc(_: void, lhs: []const u8, rhs: []const u8) bool { return std.mem.lessThan(u8, lhs, rhs); } }; diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 3f99e72..4338dd5 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -35,7 +35,7 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn deinit(self: *Self) void { self.sparse.expandToCapacity(); - for (self.sparse.items) |array, i| { + for (self.sparse.items) |array| { if (array) |arr| { self.sparse.allocator.free(arr); } @@ -53,7 +53,7 @@ pub fn SparseSet(comptime SparseT: type) type { return (sparse & self.entity_mask) / entity_per_page; } - fn offset(self: Self, sparse: SparseT) usize { + fn offset(_: Self, sparse: SparseT) usize { return sparse & (entity_per_page - 1); } @@ -151,7 +151,7 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn sort(self: *Self, context: anytype, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool) void { std.sort.insertionSort(SparseT, self.dense.items, context, lessThan); - for (self.dense.items) |sparse, i| { + for (self.dense.items) |_, i| { const item = @intCast(SparseT, i); self.sparse.items[self.page(self.dense.items[self.page(item)])].?[self.offset(self.dense.items[self.page(item)])] = @intCast(SparseT, i); } @@ -162,7 +162,7 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn arrange(self: *Self, length: usize, context: anytype, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: anytype) void { std.sort.insertionSort(SparseT, self.dense.items[0..length], context, lessThan); - for (self.dense.items[0..length]) |sparse, pos| { + for (self.dense.items[0..length]) |_, pos| { var curr = @intCast(SparseT, pos); var next = self.index(self.dense.items[curr]); diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index 2cba525..8a78929 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -84,6 +84,6 @@ test "TypeStore" { try std.testing.expectEqual(v3.*, 0); v3.* = 777; - var v4 = store.get(u32); + _ = store.get(u32); try std.testing.expectEqual(v3.*, 777); } diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index 11b490e..f8a0e91 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -135,7 +135,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { } const asc_usize = struct { - fn sort(ctx: void, a: usize, b: usize) bool { + fn sort(_: void, a: usize, b: usize) bool { return a < b; } }; @@ -235,12 +235,12 @@ test "basic multi view" { reg.add(e0, @as(u32, 0)); reg.add(e2, @as(u32, 2)); - var single_view = reg.view(.{u32}, .{}); + _ = reg.view(.{u32}, .{}); var view = reg.view(.{ i32, u32 }, .{}); var iterated_entities: usize = 0; var iter = view.iterator(); - while (iter.next()) |entity| { + while (iter.next()) |_| { iterated_entities += 1; } @@ -250,7 +250,7 @@ test "basic multi view" { reg.remove(u32, e0); iter.reset(); - while (iter.next()) |entity| { + while (iter.next()) |_| { iterated_entities += 1; } @@ -278,7 +278,7 @@ test "basic multi view with excludes" { var iterated_entities: usize = 0; var iter = view.iterator(); - while (iter.next()) |entity| { + while (iter.next()) |_| { iterated_entities += 1; } @@ -288,7 +288,7 @@ test "basic multi view with excludes" { reg.remove(u8, e2); iter.reset(); - while (iter.next()) |entity| { + while (iter.next()) |_| { iterated_entities += 1; } diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index f20a01a..d0ebca2 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -149,27 +149,27 @@ test "" { } fn start(process: *Process) void { - const self = process.getParent(@This()); + _ = process.getParent(@This()); // std.debug.warn("start {}\n", .{self.fart}); } fn aborted(process: *Process) void { - const self = process.getParent(@This()); + _ = process.getParent(@This()); // std.debug.warn("aborted {}\n", .{self.fart}); } fn failed(process: *Process) void { - const self = process.getParent(@This()); + _ = process.getParent(@This()); // std.debug.warn("failed {}\n", .{self.fart}); } fn succeeded(process: *Process) void { - const self = process.getParent(@This()); + _ = process.getParent(@This()); // std.debug.warn("succeeded {}\n", .{self.fart}); } fn update(process: *Process) void { - const self = process.getParent(@This()); + _ = process.getParent(@This()); // std.debug.warn("update {}\n", .{self.fart}); process.succeed(); } @@ -190,11 +190,11 @@ test "scheduler.clear" { const Tester = struct { process: Process, - pub fn initialize(self: *@This(), data: anytype) void { + pub fn initialize(self: *@This(), _: anytype) void { self.process = .{ .updateFn = update }; } - fn update(process: *Process) void { + fn update(_: *Process) void { std.debug.assert(false); } }; diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index 785020c..1502e7b 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -62,13 +62,13 @@ test "assets" { }; const OtherThingLoadArgs = struct { - pub fn load(self: @This()) *OtherThing { + pub fn load(_: @This()) *OtherThing { return std.testing.allocator.create(OtherThing) catch unreachable; } }; const ThingLoadArgs = struct { - pub fn load(self: @This()) *Thing { + pub fn load(_: @This()) *Thing { return std.testing.allocator.create(Thing) catch unreachable; } }; @@ -76,16 +76,16 @@ test "assets" { var assets = Assets.init(std.testing.allocator); defer assets.deinit(); - var thing = assets.get(Thing).load(6, ThingLoadArgs{}); + _ = assets.get(Thing).load(6, ThingLoadArgs{}); try std.testing.expectEqual(assets.get(Thing).size(), 1); - var thing2 = assets.load(4, ThingLoadArgs{}); + _ = assets.load(4, ThingLoadArgs{}); try std.testing.expectEqual(assets.get(Thing).size(), 2); - var other_thing = assets.get(OtherThing).load(6, OtherThingLoadArgs{}); + _ = assets.get(OtherThing).load(6, OtherThingLoadArgs{}); try std.testing.expectEqual(assets.get(OtherThing).size(), 1); - var other_thing2 = assets.load(8, OtherThingLoadArgs{}); + _ = assets.load(8, OtherThingLoadArgs{}); try std.testing.expectEqual(assets.get(OtherThing).size(), 2); assets.get(OtherThing).clear(); diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index 2b8b715..802f104 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -93,6 +93,7 @@ test "cache" { const ThingLoadArgs = struct { pub fn load(self: @This()) *Thing { + _ = self; return std.testing.allocator.create(Thing) catch unreachable; } }; @@ -100,8 +101,8 @@ test "cache" { var cache = Cache(Thing).init(std.testing.allocator); defer cache.deinit(); - var thing = cache.load(utils.hashString("my/id"), ThingLoadArgs{}); - var thing2 = cache.load(utils.hashString("another/id"), ThingLoadArgs{}); + _ = cache.load(utils.hashString("my/id"), ThingLoadArgs{}); + _ = cache.load(utils.hashString("another/id"), ThingLoadArgs{}); try std.testing.expectEqual(cache.size(), 2); cache.remove(utils.hashString("my/id")); diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index af15eb2..dd6c134 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -69,7 +69,7 @@ fn tester(param: u32) void { const Thing = struct { field: f32 = 0, - pub fn tester(self: *Thing, param: u32) void { + pub fn tester(_: *Thing, param: u32) void { std.testing.expectEqual(@as(u32, 777), param) catch unreachable; } }; diff --git a/zig-ecs/src/signals/signal.zig b/zig-ecs/src/signals/signal.zig index e26d8ac..4f51c55 100644 --- a/zig-ecs/src/signals/signal.zig +++ b/zig-ecs/src/signals/signal.zig @@ -64,7 +64,7 @@ fn tester(param: u32) void { const Thing = struct { field: f32 = 0, - pub fn tester(self: *Thing, param: u32) void { + pub fn tester(_: *Thing, param: u32) void { std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } }; diff --git a/zig-ecs/src/signals/sink.zig b/zig-ecs/src/signals/sink.zig index 1e2904b..76b7e48 100644 --- a/zig-ecs/src/signals/sink.zig +++ b/zig-ecs/src/signals/sink.zig @@ -58,7 +58,7 @@ pub fn Sink(comptime Event: type) type { } } - fn indexOf(self: Self, callback: fn (Event) void) ?usize { + fn indexOf(_: Self, callback: fn (Event) void) ?usize { for (owning_signal.calls.items) |call, i| { if (call.containsFree(callback)) { return i; @@ -67,7 +67,7 @@ pub fn Sink(comptime Event: type) type { return null; } - fn indexOfBound(self: Self, ctx: anytype) ?usize { + fn indexOfBound(_: Self, ctx: anytype) ?usize { for (owning_signal.calls.items) |call, i| { if (call.containsBound(ctx)) { return i; @@ -85,7 +85,7 @@ fn tester(param: u32) void { const Thing = struct { field: f32 = 0, - pub fn tester(self: *Thing, param: u32) void { + pub fn tester(_: *Thing, param: u32) void { std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } }; diff --git a/zig-ecs/tests/dispatcher_test.zig b/zig-ecs/tests/dispatcher_test.zig index fc20ebd..ea02bd3 100644 --- a/zig-ecs/tests/dispatcher_test.zig +++ b/zig-ecs/tests/dispatcher_test.zig @@ -12,11 +12,11 @@ fn tester2(param: i32) void { const Thing = struct { field: f32 = 0, - pub fn testU32(self: *Thing, param: u32) void { + pub fn testU32(_: *Thing, param: u32) void { std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } - pub fn testI32(self: *Thing, param: i32) void { + pub fn testI32(_: *Thing, param: i32) void { std.testing.expectEqual(@as(i32, -543), param) catch unreachable; } }; diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index 2a551ad..a0e19d2 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -70,7 +70,7 @@ test "sort BasicGroup by Component" { } const SortContext = struct { - fn sort(this: void, a: Sprite, b: Sprite) bool { + fn sort(_: void, a: Sprite, b: Sprite) bool { return a.x > b.x; } }; @@ -131,7 +131,7 @@ test "sort OwningGroup by Component" { } const SortContext = struct { - fn sort(this: void, a: Sprite, b: Sprite) bool { + fn sort(_: void, a: Sprite, b: Sprite) bool { return a.x > b.x; } }; @@ -165,7 +165,7 @@ test "sort OwningGroup by Component ensure unsorted non-matches" { try std.testing.expectEqual(reg.len(Sprite), 10); const SortContext = struct { - fn sort(this: void, a: Sprite, b: Sprite) bool { + fn sort(_: void, a: Sprite, b: Sprite) bool { // sprites with x > 50 shouldnt match in the group std.testing.expect(a.x < 50 and b.x < 50) catch unreachable; return a.x > b.x; @@ -231,8 +231,8 @@ test "nested OwningGroups entity order" { try std.testing.expectEqual(group1.len(), 5); try std.testing.expectEqual(group2.len(), 0); - var sprite_store = reg.assure(Sprite); - var transform_store = reg.assure(Transform); + _ = reg.assure(Sprite); + _ = reg.assure(Transform); // printStore(sprite_store, "Sprite"); reg.add(1, Transform{ .x = 1 }); diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index 0b57291..7549220 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -8,7 +8,7 @@ const Empty = struct {}; const BigOne = struct { pos: Position, vel: Velocity, accel: Velocity }; test "entity traits" { - const traits = ecs.EntityTraitsType(.large).init(); + _ = ecs.EntityTraitsType(.large).init(); } test "Registry" { @@ -54,6 +54,7 @@ test "context not pointer" { defer reg.deinit(); var pos = Position{ .x = 5, .y = 5 }; + _ = pos; // reg.setContext(pos); } From b900dfde2d332611ac16175b3df9065144dfbc34 Mon Sep 17 00:00:00 2001 From: RUSshy <18348637+RUSshy@users.noreply.github.com> Date: Sat, 17 Jul 2021 14:04:10 +0200 Subject: [PATCH 104/146] Update handles.zig --- zig-ecs/src/ecs/handles.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig index e444fa3..c4d9f5a 100644 --- a/zig-ecs/src/ecs/handles.zig +++ b/zig-ecs/src/ecs/handles.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const registry = @import("registry.zig"); /// generates versioned "handles" (https://floooh.github.io/2018/06/17/handles-vs-pointers.html) /// you choose the type of the handle (aka its size) and how much of that goes to the index and the version. @@ -38,7 +39,6 @@ pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime Ver return h; } } - return null; } }; @@ -59,15 +59,15 @@ pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime Ver } pub fn extractId(_: Self, handle: HandleType) IndexType { - return @truncate(IndexType, handle); + return @truncate(IndexType, handle & @as(IndexType, registry.entity_traits.entity_mask)); } pub fn extractVersion(_: Self, handle: HandleType) VersionType { - return @truncate(VersionType, handle >> @bitSizeOf(IndexType)); + return @truncate(VersionType, handle >> registry.entity_traits.entity_shift); } fn forge(id: IndexType, version: VersionType) HandleType { - return id | @as(HandleType, version) << @bitSizeOf(IndexType); + return id | @as(HandleType, version) << registry.entity_traits.entity_shift; } pub fn create(self: *Self) HandleType { From e488cc384f2523aa7a7a72541be08c2eba1a0aa4 Mon Sep 17 00:00:00 2001 From: RUSshy <18348637+RUSshy@users.noreply.github.com> Date: Sat, 17 Jul 2021 14:05:30 +0200 Subject: [PATCH 105/146] Update sparse_set.zig --- zig-ecs/src/ecs/sparse_set.zig | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 4338dd5..3d2e3f9 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -1,14 +1,14 @@ const std = @import("std"); const warn = std.debug.warn; const utils = @import("utils.zig"); +const registry = @import("registry.zig"); const ReverseSliceIterator = @import("utils.zig").ReverseSliceIterator; // TODO: fix entity_mask. it should come from EntityTraitsDefinition. pub fn SparseSet(comptime SparseT: type) type { return struct { const Self = @This(); - const page_size: usize = 32768; - const entity_per_page = page_size / @sizeOf(SparseT); + const page_size: usize = 4096; sparse: std.ArrayList(?[]SparseT), dense: std.ArrayList(SparseT), @@ -17,9 +17,9 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn initPtr(allocator: *std.mem.Allocator) *Self { var set = allocator.create(Self) catch unreachable; - set.sparse = std.ArrayList(?[]SparseT).init(allocator); - set.dense = std.ArrayList(SparseT).init(allocator); - set.entity_mask = std.math.maxInt(SparseT); + set.sparse = std.ArrayList(?[]SparseT).initCapacity(allocator, 16) catch unreachable; + set.dense = std.ArrayList(SparseT).initCapacity(allocator, 16) catch unreachable; + set.entity_mask = registry.entity_traits.entity_mask; set.allocator = allocator; return set; } @@ -28,7 +28,7 @@ pub fn SparseSet(comptime SparseT: type) type { return Self{ .sparse = std.ArrayList(?[]SparseT).init(allocator), .dense = std.ArrayList(SparseT).init(allocator), - .entity_mask = std.math.maxInt(SparseT), + .entity_mask = registry.entity_traits.entity_mask, .allocator = null, }; } @@ -50,11 +50,11 @@ pub fn SparseSet(comptime SparseT: type) type { } pub fn page(self: Self, sparse: SparseT) usize { - return (sparse & self.entity_mask) / entity_per_page; + return (sparse & self.entity_mask) / page_size; } fn offset(_: Self, sparse: SparseT) usize { - return sparse & (entity_per_page - 1); + return sparse & (page_size - 1); } fn assure(self: *Self, pos: usize) []SparseT { @@ -65,14 +65,12 @@ pub fn SparseSet(comptime SparseT: type) type { std.mem.set(?[]SparseT, self.sparse.items[start_pos..], null); } - if (self.sparse.items[pos]) |arr| { - return arr; + if (self.sparse.items[pos] == null) { + var new_page = self.sparse.allocator.alloc(SparseT, page_size) catch unreachable; + std.mem.set(SparseT, new_page, std.math.maxInt(SparseT)); + self.sparse.items[pos] = new_page; } - var new_page = self.sparse.allocator.alloc(SparseT, entity_per_page) catch unreachable; - std.mem.set(SparseT, new_page, std.math.maxInt(SparseT)); - self.sparse.items[pos] = new_page; - return self.sparse.items[pos].?; } @@ -105,7 +103,9 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn contains(self: Self, sparse: SparseT) bool { const curr = self.page(sparse); - return curr < self.sparse.items.len and self.sparse.items[curr] != null and self.sparse.items[curr].?[self.offset(sparse)] != std.math.maxInt(SparseT); + return curr < self.sparse.items.len and + self.sparse.items[curr] != null and + self.sparse.items[curr].?[self.offset(sparse)] != std.math.maxInt(SparseT); } /// Returns the position of an entity in a sparse set From 22b4fe2e028fdee3021691861f21812a26bb0b33 Mon Sep 17 00:00:00 2001 From: RUSshy <18348637+RUSshy@users.noreply.github.com> Date: Sat, 17 Jul 2021 14:07:33 +0200 Subject: [PATCH 106/146] Update handles.zig --- zig-ecs/src/ecs/handles.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig index c4d9f5a..67d1609 100644 --- a/zig-ecs/src/ecs/handles.zig +++ b/zig-ecs/src/ecs/handles.zig @@ -59,7 +59,7 @@ pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime Ver } pub fn extractId(_: Self, handle: HandleType) IndexType { - return @truncate(IndexType, handle & @as(IndexType, registry.entity_traits.entity_mask)); + return @truncate(IndexType, handle & registry.entity_traits.entity_mask); } pub fn extractVersion(_: Self, handle: HandleType) VersionType { From 15d0e6d00d60d71de44dfed0e48ac1a547c5a2da Mon Sep 17 00:00:00 2001 From: RUSshy <18348637+RUSshy@users.noreply.github.com> Date: Sat, 17 Jul 2021 14:08:19 +0200 Subject: [PATCH 107/146] Update registry.zig --- zig-ecs/src/ecs/registry.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 6ebab46..5a64402 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -10,7 +10,7 @@ const TypeStore = @import("type_store.zig").TypeStore; // allow overriding EntityTraits by setting in root via: EntityTraits = EntityTraitsType(.medium); const root = @import("root"); -const entity_traits = if (@hasDecl(root, "EntityTraits")) root.EntityTraits.init() else @import("entity.zig").EntityTraits.init(); +pub const entity_traits = if (@hasDecl(root, "EntityTraits")) root.EntityTraits.init() else @import("entity.zig").EntityTraits.init(); // setup the Handles type based on the type set in EntityTraits const EntityHandles = Handles(entity_traits.entity_type, entity_traits.index_type, entity_traits.version_type); @@ -261,7 +261,7 @@ pub const Registry = struct { /// Returns the version stored along with an entity identifier pub fn version(_: *Registry, entity: Entity) entity_traits.version_type { - return @truncate(entity_traits.version_type, entity >> @bitSizeOf(entity_traits.index_type)); + return @truncate(entity_traits.version_type, entity >> entity_traits.entity_shift); } /// Creates a new entity and returns it From 2161bf61b4df7b2be77f6e91fc84e7fdfedfa677 Mon Sep 17 00:00:00 2001 From: RUSshy <18348637+RUSshy@users.noreply.github.com> Date: Sat, 17 Jul 2021 18:47:23 +0200 Subject: [PATCH 108/146] Added missing entity traits changes --- zig-ecs/src/ecs/entity.zig | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/zig-ecs/src/ecs/entity.zig b/zig-ecs/src/ecs/entity.zig index f461f44..92b986a 100644 --- a/zig-ecs/src/ecs/entity.zig +++ b/zig-ecs/src/ecs/entity.zig @@ -7,13 +7,13 @@ pub const EntityTraitsSize = enum { small, medium, large }; pub fn EntityTraitsType(comptime size: EntityTraitsSize) type { return switch (size) { - .small => EntityTraitsDefinition(u16, u12, u4), - .medium => EntityTraitsDefinition(u32, u20, u12), - .large => EntityTraitsDefinition(u64, u32, u32), + .small => EntityTraitsDefinition(u16, u12, u4, 0xFFFF, 0xFFF, 10), + .medium => EntityTraitsDefinition(u32, u20, u12, 0xFFFFF, 0xFFF, 20), + .large => EntityTraitsDefinition(u64, u32, u32, 0xFFFFFFFF, 0xFFFFFFFF, 32), }; } -fn EntityTraitsDefinition(comptime EntityType: type, comptime IndexType: type, comptime VersionType: type) type { +fn EntityTraitsDefinition(comptime EntityType: type, comptime IndexType: type, comptime VersionType: type, comptime EntityMask: EntityType, comptime VersionMask: EntityType, comptime EntityShift: EntityType) type { std.debug.assert(@typeInfo(EntityType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(EntityType)) == EntityType); std.debug.assert(@typeInfo(IndexType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(IndexType)) == IndexType); std.debug.assert(@typeInfo(VersionType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(VersionType)) == VersionType); @@ -26,9 +26,10 @@ fn EntityTraitsDefinition(comptime EntityType: type, comptime IndexType: type, c index_type: type = IndexType, version_type: type = VersionType, /// Mask to use to get the entity index number out of an identifier - entity_mask: EntityType = std.math.maxInt(IndexType), + entity_mask: EntityType = EntityMask, /// Mask to use to get the version out of an identifier - version_mask: EntityType = std.math.maxInt(VersionType), + version_mask: EntityType = VersionMask, + entity_shift: EntityType = EntityShift, pub fn init() @This() { return @This(){}; From 434aa523a365e1f83279639631ed95514370d1f6 Mon Sep 17 00:00:00 2001 From: prime31 Date: Tue, 27 Jul 2021 21:30:23 -0700 Subject: [PATCH 109/146] Create LICENSE --- zig-ecs/LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 zig-ecs/LICENSE diff --git a/zig-ecs/LICENSE b/zig-ecs/LICENSE new file mode 100644 index 0000000..c4ee495 --- /dev/null +++ b/zig-ecs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 prime31 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 605693b8c2f3a41c021f23838b76c178be691f69 Mon Sep 17 00:00:00 2001 From: Locria Cyber Date: Wed, 6 Oct 2021 12:23:57 +0800 Subject: [PATCH 110/146] Fix syntax errors --- zig-ecs/examples/view_vs_group.zig | 6 +++--- zig-ecs/src/ecs/component_storage.zig | 2 +- zig-ecs/src/ecs/registry.zig | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zig-ecs/examples/view_vs_group.zig b/zig-ecs/examples/view_vs_group.zig index 94eaf8c..4b50f07 100644 --- a/zig-ecs/examples/view_vs_group.zig +++ b/zig-ecs/examples/view_vs_group.zig @@ -12,7 +12,7 @@ pub fn main() !void { var reg = ecs.Registry.init(std.heap.c_allocator); defer reg.deinit(); - var timer = try std.time.Timer.start(); + // var timer = try std.time.Timer.start(); createEntities(®); iterateView(®); @@ -96,8 +96,8 @@ fn owningGroup(reg: *ecs.Registry) void { timer.reset(); - var storage = reg.assure(Velocity); - var vel = storage.instances.items; + // var storage = reg.assure(Velocity); + // var vel = storage.instances.items; var pos = reg.assure(Position).instances.items; var index: usize = group.group_data.current; diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index e1b070a..a2eb783 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -145,7 +145,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { pub fn reserve(self: *Self, cap: usize) void { self.set.reserve(cap); if (!is_empty_struct) { - elf.instances.items.reserve(cap); + self.instances.items.reserve(cap); } } diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 5a64402..4d94f32 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -236,7 +236,7 @@ pub const Registry = struct { } /// Increases the capacity of the registry or of the pools for the given component - pub fn reserve(self: *Self, comptime T: type, cap: usize) void { + pub fn reserve(self: *Registry, comptime T: type, cap: usize) void { self.assure(T).reserve(cap); } From e4dc1dc9ca18ce8764bf1bc05c316bb81df9d1cd Mon Sep 17 00:00:00 2001 From: Sashiri Date: Thu, 14 Oct 2021 22:59:22 +0200 Subject: [PATCH 111/146] Fix process pause signature --- zig-ecs/src/process/process.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/src/process/process.zig b/zig-ecs/src/process/process.zig index 42ddfc2..d4ab365 100644 --- a/zig-ecs/src/process/process.zig +++ b/zig-ecs/src/process/process.zig @@ -33,7 +33,7 @@ pub const Process = struct { } /// Stops a process if it's in a running state - pub fn pause(self: *ParentType) void { + pub fn pause(self: *Process) void { if (self.state == .running) self.state = .paused; } From aa0dd139b2a2a6979ec26746c9ae3d3101eeac44 Mon Sep 17 00:00:00 2001 From: Sashiri Date: Thu, 14 Oct 2021 23:02:07 +0200 Subject: [PATCH 112/146] Update EntityTraitsDefinition --- zig-ecs/src/ecs/entity.zig | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/zig-ecs/src/ecs/entity.zig b/zig-ecs/src/ecs/entity.zig index 92b986a..e8847ec 100644 --- a/zig-ecs/src/ecs/entity.zig +++ b/zig-ecs/src/ecs/entity.zig @@ -7,29 +7,37 @@ pub const EntityTraitsSize = enum { small, medium, large }; pub fn EntityTraitsType(comptime size: EntityTraitsSize) type { return switch (size) { - .small => EntityTraitsDefinition(u16, u12, u4, 0xFFFF, 0xFFF, 10), - .medium => EntityTraitsDefinition(u32, u20, u12, 0xFFFFF, 0xFFF, 20), - .large => EntityTraitsDefinition(u64, u32, u32, 0xFFFFFFFF, 0xFFFFFFFF, 32), + .small => EntityTraitsDefinition(u16, u12, u4), + .medium => EntityTraitsDefinition(u32, u20, u12), + .large => EntityTraitsDefinition(u64, u32, u32), }; } -fn EntityTraitsDefinition(comptime EntityType: type, comptime IndexType: type, comptime VersionType: type, comptime EntityMask: EntityType, comptime VersionMask: EntityType, comptime EntityShift: EntityType) type { - std.debug.assert(@typeInfo(EntityType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(EntityType)) == EntityType); - std.debug.assert(@typeInfo(IndexType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(IndexType)) == IndexType); - std.debug.assert(@typeInfo(VersionType) == .Int and std.meta.Int(.unsigned, @bitSizeOf(VersionType)) == VersionType); - - if (@bitSizeOf(IndexType) + @bitSizeOf(VersionType) != @bitSizeOf(EntityType)) +fn EntityTraitsDefinition(comptime EntityType: type, comptime IndexType: type, comptime VersionType: type) type { + std.debug.assert(std.meta.trait.isUnsignedInt(EntityType)); + std.debug.assert(std.meta.trait.isUnsignedInt(IndexType)); + std.debug.assert(std.meta.trait.isUnsignedInt(VersionType)); + + const sizeOfIndexType = @bitSizeOf(IndexType); + const sizeOfVersionType = @bitSizeOf(VersionType); + const entityShift = sizeOfIndexType; + + if (sizeOfIndexType + sizeOfVersionType != @bitSizeOf(EntityType)) @compileError("IndexType and VersionType must sum to EntityType's bit count"); + const entityMask = std.math.maxInt(IndexType); + const versionMask = std.math.maxInt(VersionType); + return struct { entity_type: type = EntityType, index_type: type = IndexType, version_type: type = VersionType, /// Mask to use to get the entity index number out of an identifier - entity_mask: EntityType = EntityMask, + entity_mask: EntityType = entityMask, /// Mask to use to get the version out of an identifier - version_mask: EntityType = VersionMask, - entity_shift: EntityType = EntityShift, + version_mask: EntityType = versionMask, + /// Bit size of entity in entity_type + entity_shift: EntityType = entityShift, pub fn init() @This() { return @This(){}; From 5ba2773f7a2ce6c356a2769d7194e48d9d4824ef Mon Sep 17 00:00:00 2001 From: Sashiri Date: Fri, 15 Oct 2021 00:14:16 +0200 Subject: [PATCH 113/146] Migrate from std.builtin to @import("builtin") --- zig-ecs/build.zig | 2 +- zig-ecs/src/ecs/groups.zig | 3 ++- zig-ecs/src/ecs/registry.zig | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 3a8c2a2..a59f7c4 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -6,7 +6,7 @@ pub fn build(b: *Builder) void { const build_mode = b.standardReleaseOptions(); // use a different cache folder for macos arm builds - b.cache_root = if (std.builtin.os.tag == .macos and std.builtin.arch == std.builtin.Arch.aarch64) "zig-arm-cache" else "zig-cache"; + b.cache_root = if (builtin.os.tag == .macos and builtin.arch == builtin.Arch.aarch64) "zig-arm-cache" else "zig-cache"; const examples = [_][2][]const u8{ [_][]const u8{ "view_vs_group", "examples/view_vs_group.zig" }, diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index eb746c3..7ae6f6c 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const utils = @import("utils.zig"); const Registry = @import("registry.zig").Registry; @@ -156,7 +157,7 @@ pub const OwningGroup = struct { } fn validate(self: OwningGroup, comptime Components: anytype) void { - if (std.builtin.mode == .Debug and self.group_data.owned.len > 0) { + if (builtin.mode == .Debug and self.group_data.owned.len > 0) { std.debug.assert(@typeInfo(Components) == .Struct); inline for (@typeInfo(Components).Struct.fields) |field| { diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 4d94f32..d982658 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const assert = std.debug.assert; const utils = @import("utils.zig"); @@ -506,7 +507,7 @@ pub const Registry = struct { var new_group_data = GroupData.initPtr(self.allocator, self, hash, owned_arr[0..], includes_arr[0..], excludes_arr[0..]); // before adding the group we need to do some checks to make sure there arent other owning groups with the same types - if (std.builtin.mode == .Debug and owned.len > 0) { + if (builtin.mode == .Debug and owned.len > 0) { for (self.groups.items) |grp| { if (grp.owned.len == 0) continue; From 157a554e6d2e83e4476d1c795af4519914a69a8b Mon Sep 17 00:00:00 2001 From: Austin Rude Date: Wed, 15 Dec 2021 11:45:24 -0700 Subject: [PATCH 114/146] Update build.zig to use the std.target.Arch enum --- zig-ecs/build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index a59f7c4..5648d61 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -6,7 +6,7 @@ pub fn build(b: *Builder) void { const build_mode = b.standardReleaseOptions(); // use a different cache folder for macos arm builds - b.cache_root = if (builtin.os.tag == .macos and builtin.arch == builtin.Arch.aarch64) "zig-arm-cache" else "zig-cache"; + b.cache_root = if (builtin.os.tag == .macos and builtin.target.cpu.arch == .aarch64) "zig-arm-cache" else "zig-cache"; const examples = [_][2][]const u8{ [_][]const u8{ "view_vs_group", "examples/view_vs_group.zig" }, From a95c85f5c771bf49da847cb3abf0315b0194c10c Mon Sep 17 00:00:00 2001 From: Austin Rude Date: Wed, 15 Dec 2021 11:55:58 -0700 Subject: [PATCH 115/146] Update for allocgate --- zig-ecs/src/ecs/component_storage.zig | 6 +++--- zig-ecs/src/ecs/handles.zig | 6 +++--- zig-ecs/src/ecs/registry.zig | 8 ++++---- zig-ecs/src/ecs/sparse_set.zig | 10 +++++----- zig-ecs/src/ecs/type_store.zig | 4 ++-- zig-ecs/src/process/process.zig | 6 ++---- zig-ecs/src/process/scheduler.zig | 14 +++++++------- zig-ecs/src/resources/assets.zig | 4 ++-- zig-ecs/src/resources/cache.zig | 6 +++--- zig-ecs/src/signals/dispatcher.zig | 4 ++-- zig-ecs/src/signals/signal.zig | 6 +++--- 11 files changed, 36 insertions(+), 38 deletions(-) diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index a2eb783..c92dbc0 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -24,7 +24,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { set: *SparseSet(Entity), instances: std.ArrayList(ComponentOrDummy), - allocator: ?*std.mem.Allocator, + allocator: ?std.mem.Allocator, /// doesnt really belong here...used to denote group ownership super: usize = 0, safeDeinit: fn (*Self) void, @@ -34,7 +34,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { update: Signal(Entity), destruction: Signal(Entity), - pub fn init(allocator: *std.mem.Allocator) Self { + pub fn init(allocator: std.mem.Allocator) Self { var store = Self{ .set = SparseSet(Entity).initPtr(allocator), .instances = undefined, @@ -73,7 +73,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { return store; } - pub fn initPtr(allocator: *std.mem.Allocator) *Self { + pub fn initPtr(allocator: std.mem.Allocator) *Self { var store = allocator.create(Self) catch unreachable; store.set = SparseSet(Entity).initPtr(allocator); if (!is_empty_struct) { diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig index 67d1609..1039b5c 100644 --- a/zig-ecs/src/ecs/handles.zig +++ b/zig-ecs/src/ecs/handles.zig @@ -18,7 +18,7 @@ pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime Ver handles: []HandleType, append_cursor: IndexType = 0, last_destroyed: ?IndexType = null, - allocator: *std.mem.Allocator, + allocator: std.mem.Allocator, const invalid_id = std.math.maxInt(IndexType); @@ -43,11 +43,11 @@ pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime Ver } }; - pub fn init(allocator: *std.mem.Allocator) Self { + pub fn init(allocator: std.mem.Allocator) Self { return initWithCapacity(allocator, 32); } - pub fn initWithCapacity(allocator: *std.mem.Allocator, capacity: usize) Self { + pub fn initWithCapacity(allocator: std.mem.Allocator, capacity: usize) Self { return Self{ .handles = allocator.alloc(HandleType, capacity) catch unreachable, .allocator = allocator, diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index d982658..2ce4895 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -35,7 +35,7 @@ pub const Registry = struct { contexts: std.AutoHashMap(u32, usize), groups: std.ArrayList(*GroupData), singletons: TypeStore, - allocator: *std.mem.Allocator, + allocator: std.mem.Allocator, /// internal, persistant data structure to manage the entities in a group pub const GroupData = struct { @@ -49,7 +49,7 @@ pub const Registry = struct { registry: *Registry, current: usize, - pub fn initPtr(allocator: *std.mem.Allocator, registry: *Registry, hash: u64, owned: []u32, include: []u32, exclude: []u32) *GroupData { + pub fn initPtr(allocator: std.mem.Allocator, registry: *Registry, hash: u64, 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); @@ -68,7 +68,7 @@ pub const Registry = struct { return group_data; } - pub fn deinit(self: *GroupData, allocator: *std.mem.Allocator) void { + pub fn deinit(self: *GroupData, allocator: std.mem.Allocator) void { // only deinit th SparseSet for non-owning groups if (self.owned.len == 0) { self.entity_set.deinit(); @@ -184,7 +184,7 @@ pub const Registry = struct { } }; - pub fn init(allocator: *std.mem.Allocator) Registry { + pub fn init(allocator: std.mem.Allocator) Registry { return Registry{ .handles = EntityHandles.init(allocator), .components = std.AutoHashMap(u32, usize).init(allocator), diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 3d2e3f9..e414528 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -13,9 +13,9 @@ pub fn SparseSet(comptime SparseT: type) type { sparse: std.ArrayList(?[]SparseT), dense: std.ArrayList(SparseT), entity_mask: SparseT, - allocator: ?*std.mem.Allocator, + allocator: ?std.mem.Allocator, - pub fn initPtr(allocator: *std.mem.Allocator) *Self { + pub fn initPtr(allocator: std.mem.Allocator) *Self { var set = allocator.create(Self) catch unreachable; set.sparse = std.ArrayList(?[]SparseT).initCapacity(allocator, 16) catch unreachable; set.dense = std.ArrayList(SparseT).initCapacity(allocator, 16) catch unreachable; @@ -24,7 +24,7 @@ pub fn SparseSet(comptime SparseT: type) type { return set; } - pub fn init(allocator: *std.mem.Allocator) Self { + pub fn init(allocator: std.mem.Allocator) Self { return Self{ .sparse = std.ArrayList(?[]SparseT).init(allocator), .dense = std.ArrayList(SparseT).init(allocator), @@ -104,8 +104,8 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn contains(self: Self, sparse: SparseT) bool { const curr = self.page(sparse); return curr < self.sparse.items.len and - self.sparse.items[curr] != null and - self.sparse.items[curr].?[self.offset(sparse)] != std.math.maxInt(SparseT); + self.sparse.items[curr] != null and + self.sparse.items[curr].?[self.offset(sparse)] != std.math.maxInt(SparseT); } /// Returns the position of an entity in a sparse set diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index 8a78929..96b801c 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -4,9 +4,9 @@ const utils = @import("utils.zig"); /// stores a single object of type T for each T added pub const TypeStore = struct { map: std.AutoHashMap(u32, []u8), - allocator: *std.mem.Allocator, + allocator: std.mem.Allocator, - pub fn init(allocator: *std.mem.Allocator) TypeStore { + pub fn init(allocator: std.mem.Allocator) TypeStore { return TypeStore{ .map = std.AutoHashMap(u32, []u8).init(allocator), .allocator = allocator, diff --git a/zig-ecs/src/process/process.zig b/zig-ecs/src/process/process.zig index d4ab365..46d7dda 100644 --- a/zig-ecs/src/process/process.zig +++ b/zig-ecs/src/process/process.zig @@ -3,16 +3,14 @@ const std = @import("std"); /// Processes are run by the Scheduler. They use a similar pattern to Allocators in that they are created and /// added as fields in a parent struct, your actual process that will be run. pub const Process = struct { - const State = enum(u8) { - uninitialized, running, paused, succeeded, failed, aborted, finished - }; + const State = enum(u8) { uninitialized, running, paused, succeeded, failed, aborted, finished }; updateFn: fn (self: *Process) void, startFn: ?fn (self: *Process) void = null, abortedFn: ?fn (self: *Process) void = null, failedFn: ?fn (self: *Process) void = null, succeededFn: ?fn (self: *Process) void = null, - deinit: fn (self: *Process, allocator: *std.mem.Allocator) void = undefined, + deinit: fn (self: *Process, allocator: std.mem.Allocator) void = undefined, state: State = .uninitialized, stopped: bool = false, diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index d0ebca2..9d316e7 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -12,16 +12,16 @@ const Process = @import("process.zig").Process; /// - in any callback you can get your oiginal struct back via `process.getParent(@This())` pub const Scheduler = struct { processes: std.ArrayList(*Process), - allocator: *std.mem.Allocator, + allocator: std.mem.Allocator, /// helper to create and prepare a process - fn createProcessHandler(comptime T: type, data: anytype, allocator: *std.mem.Allocator) *Process { + fn createProcessHandler(comptime T: type, data: anytype, allocator: std.mem.Allocator) *Process { var proc = allocator.create(T) catch unreachable; proc.initialize(data); // get a closure so that we can safely deinit this later proc.process.deinit = struct { - fn deinit(process: *Process, alloc: *std.mem.Allocator) void { + fn deinit(process: *Process, alloc: std.mem.Allocator) void { if (process.next) |next_process| { next_process.deinit(next_process, alloc); } @@ -35,9 +35,9 @@ pub const Scheduler = struct { /// returned when appending a process so that sub-processes can be added to the process const Continuation = struct { process: *Process, - allocator: *std.mem.Allocator, + allocator: std.mem.Allocator, - pub fn init(process: *Process, allocator: *std.mem.Allocator) Continuation { + pub fn init(process: *Process, allocator: std.mem.Allocator) Continuation { return .{ .process = process, .allocator = allocator }; } @@ -48,7 +48,7 @@ pub const Scheduler = struct { } }; - pub fn init(allocator: *std.mem.Allocator) Scheduler { + pub fn init(allocator: std.mem.Allocator) Scheduler { return .{ .processes = std.ArrayList(*Process).init(allocator), .allocator = allocator, @@ -72,7 +72,7 @@ pub const Scheduler = struct { return Continuation.init(process, self.allocator); } - fn updateProcess(process: **Process, allocator: *std.mem.Allocator) bool { + fn updateProcess(process: **Process, allocator: std.mem.Allocator) bool { const current_process = process.*; current_process.tick(); diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index 1502e7b..19121b6 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -4,9 +4,9 @@ const Cache = @import("cache.zig").Cache; pub const Assets = struct { caches: std.AutoHashMap(u32, usize), - allocator: *std.mem.Allocator, + allocator: std.mem.Allocator, - pub fn init(allocator: *std.mem.Allocator) Assets { + pub fn init(allocator: std.mem.Allocator) Assets { return Assets{ .caches = std.AutoHashMap(u32, usize).init(allocator), .allocator = allocator, diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index 802f104..f1ad407 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -10,9 +10,9 @@ pub fn Cache(comptime T: type) type { safe_deinit: fn (*@This()) void, resources: std.AutoHashMap(u32, *T), - allocator: ?*std.mem.Allocator = null, + allocator: ?std.mem.Allocator = null, - pub fn initPtr(allocator: *std.mem.Allocator) *@This() { + pub fn initPtr(allocator: std.mem.Allocator) *@This() { var cache = allocator.create(@This()) catch unreachable; cache.safe_deinit = struct { fn deinit(self: *Self) void { @@ -26,7 +26,7 @@ pub fn Cache(comptime T: type) type { return cache; } - pub fn init(allocator: *std.mem.Allocator) @This() { + pub fn init(allocator: std.mem.Allocator) @This() { return .{ .safe_deinit = struct { fn deinit(self: *Self) void { diff --git a/zig-ecs/src/signals/dispatcher.zig b/zig-ecs/src/signals/dispatcher.zig index 15d3daf..265fff9 100644 --- a/zig-ecs/src/signals/dispatcher.zig +++ b/zig-ecs/src/signals/dispatcher.zig @@ -5,9 +5,9 @@ const utils = @import("../ecs/utils.zig"); pub const Dispatcher = struct { signals: std.AutoHashMap(u32, usize), - allocator: *std.mem.Allocator, + allocator: std.mem.Allocator, - pub fn init(allocator: *std.mem.Allocator) Dispatcher { + pub fn init(allocator: std.mem.Allocator) Dispatcher { return Dispatcher{ .signals = std.AutoHashMap(u32, usize).init(allocator), .allocator = allocator, diff --git a/zig-ecs/src/signals/signal.zig b/zig-ecs/src/signals/signal.zig index 4f51c55..2349f89 100644 --- a/zig-ecs/src/signals/signal.zig +++ b/zig-ecs/src/signals/signal.zig @@ -7,9 +7,9 @@ pub fn Signal(comptime Event: type) type { const Self = @This(); calls: std.ArrayList(Delegate(Event)), - allocator: ?*std.mem.Allocator = null, + allocator: ?std.mem.Allocator = null, - pub fn init(allocator: *std.mem.Allocator) Self { + pub fn init(allocator: std.mem.Allocator) Self { // we purposely do not store the allocator locally in this case so we know not to destroy ourself in deint! return Self{ .calls = std.ArrayList(Delegate(Event)).init(allocator), @@ -17,7 +17,7 @@ pub fn Signal(comptime Event: type) type { } /// heap allocates a Signal - pub fn create(allocator: *std.mem.Allocator) *Self { + pub fn create(allocator: std.mem.Allocator) *Self { var signal = allocator.create(Self) catch unreachable; signal.calls = std.ArrayList(Delegate(Event)).init(allocator); signal.allocator = allocator; From 0fe6d1646f68b5b4726c521f52d294a9a70701b1 Mon Sep 17 00:00:00 2001 From: Austin Rude Date: Wed, 15 Dec 2021 12:06:05 -0700 Subject: [PATCH 116/146] Use std.debug.print instead of std.debug.warn --- zig-ecs/examples/group_sort.zig | 10 +++++----- zig-ecs/examples/simple.zig | 6 +++--- zig-ecs/examples/view_vs_group.zig | 22 +++++++++++----------- zig-ecs/src/ecs/component_storage.zig | 1 - zig-ecs/src/ecs/sparse_set.zig | 11 +++++------ zig-ecs/src/process/scheduler.zig | 12 ++++++------ zig-ecs/tests/groups_test.zig | 11 +++++------ 7 files changed, 35 insertions(+), 38 deletions(-) diff --git a/zig-ecs/examples/group_sort.zig b/zig-ecs/examples/group_sort.zig index a5c91f1..3097b72 100644 --- a/zig-ecs/examples/group_sort.zig +++ b/zig-ecs/examples/group_sort.zig @@ -32,7 +32,7 @@ fn createEntities(reg: *ecs.Registry) void { } var end = timer.lap(); - std.debug.warn("create {d} entities: {d}\n", .{ total_entities, @intToFloat(f64, end) / 1000000000 }); + std.debug.print("create {d} entities: {d}\n", .{ total_entities, @intToFloat(f64, end) / 1000000000 }); } fn owningGroup(reg: *ecs.Registry) void { @@ -40,7 +40,7 @@ fn owningGroup(reg: *ecs.Registry) void { // var group_iter = group.iterator(struct { vel: *Velocity, pos: *Position }); // while (group_iter.next()) |e| { - // std.debug.warn("pos.y {d:.3}, ent: {}\n", .{e.pos.y, group_iter.entity()}); + // std.debug.print("pos.y {d:.3}, ent: {}\n", .{e.pos.y, group_iter.entity()}); // } const SortContext = struct { @@ -52,15 +52,15 @@ fn owningGroup(reg: *ecs.Registry) void { var timer = std.time.Timer.start() catch unreachable; group.sort(Position, {}, SortContext.sort); var end = timer.lap(); - std.debug.warn("group (sort): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (sort): {d}\n", .{@intToFloat(f64, end) / 1000000000}); timer.reset(); group.sort(Position, {}, SortContext.sort); end = timer.lap(); - std.debug.warn("group (sort 2): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (sort 2): {d}\n", .{@intToFloat(f64, end) / 1000000000}); // var group_iter2 = group.iterator(struct { vel: *Velocity, pos: *Position }); // while (group_iter2.next()) |e| { - // std.debug.warn("pos.y {d:.3}, ent: {}\n", .{e.pos.y, group_iter2.entity()}); + // std.debug.print("pos.y {d:.3}, ent: {}\n", .{e.pos.y, group_iter2.entity()}); // } } diff --git a/zig-ecs/examples/simple.zig b/zig-ecs/examples/simple.zig index 9a559b1..4b973ac 100644 --- a/zig-ecs/examples/simple.zig +++ b/zig-ecs/examples/simple.zig @@ -25,17 +25,17 @@ pub fn main() !void { while (iter.next()) |entity| { var pos = view.get(Position, entity); const vel = view.getConst(Velocity, entity); - std.debug.warn("entity: {}, pos: {d}, vel: {d}\n", .{ entity, pos.*, vel }); + std.debug.print("entity: {}, pos: {d}, vel: {d}\n", .{ entity, pos.*, vel }); pos.*.x += vel.x; pos.*.y += vel.y; } - std.debug.warn("---- resetting iter\n", .{}); + std.debug.print("---- resetting iter\n", .{}); iter.reset(); while (iter.next()) |entity| { const pos = view.getConst(Position, entity); const vel = view.getConst(Velocity, entity); - std.debug.warn("entity: {}, pos: {d}, vel: {d}\n", .{ entity, pos, vel }); + std.debug.print("entity: {}, pos: {d}, vel: {d}\n", .{ entity, pos, vel }); } } diff --git a/zig-ecs/examples/view_vs_group.zig b/zig-ecs/examples/view_vs_group.zig index 4b50f07..d76f48f 100644 --- a/zig-ecs/examples/view_vs_group.zig +++ b/zig-ecs/examples/view_vs_group.zig @@ -30,11 +30,11 @@ fn createEntities(reg: *ecs.Registry) void { } var end = timer.lap(); - std.debug.warn("create entities: \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("create entities: \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); } fn iterateView(reg: *ecs.Registry) void { - std.debug.warn("--- multi-view ---\n", .{}); + std.debug.print("--- multi-view ---\n", .{}); var view = reg.view(.{ Velocity, Position }, .{}); var timer = std.time.Timer.start() catch unreachable; @@ -48,15 +48,15 @@ fn iterateView(reg: *ecs.Registry) void { } var end = timer.lap(); - std.debug.warn("view (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("view (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); } fn nonOwningGroup(reg: *ecs.Registry) void { - std.debug.warn("--- non-owning ---\n", .{}); + std.debug.print("--- non-owning ---\n", .{}); var timer = std.time.Timer.start() catch unreachable; var group = reg.group(.{}, .{ Velocity, Position }, .{}); var end = timer.lap(); - std.debug.warn("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); timer.reset(); var group_iter = group.iterator(); @@ -69,15 +69,15 @@ fn nonOwningGroup(reg: *ecs.Registry) void { } end = timer.lap(); - std.debug.warn("group (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); } fn owningGroup(reg: *ecs.Registry) void { - std.debug.warn("--- owning ---\n", .{}); + std.debug.print("--- owning ---\n", .{}); var timer = std.time.Timer.start() catch unreachable; var group = reg.group(.{ Velocity, Position }, .{}, .{}); var end = timer.lap(); - std.debug.warn("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); timer.reset(); var group_iter = group.iterator(struct { vel: *Velocity, pos: *Position }); @@ -87,12 +87,12 @@ fn owningGroup(reg: *ecs.Registry) void { } end = timer.lap(); - std.debug.warn("group (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); timer.reset(); group.each(each); end = timer.lap(); - std.debug.warn("group (each): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (each): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); timer.reset(); @@ -110,7 +110,7 @@ fn owningGroup(reg: *ecs.Registry) void { } end = timer.lap(); - std.debug.warn("group (direct): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (direct): {d}\n", .{@intToFloat(f64, end) / 1000000000}); } fn each(e: struct { vel: *Velocity, pos: *Position }) void { diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index c92dbc0..e3aa6b7 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const warn = std.debug.warn; const utils = @import("utils.zig"); const SparseSet = @import("sparse_set.zig").SparseSet; diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index e414528..7752d62 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const warn = std.debug.warn; const utils = @import("utils.zig"); const registry = @import("registry.zig"); const ReverseSliceIterator = @import("utils.zig").ReverseSliceIterator; @@ -210,16 +209,16 @@ pub fn SparseSet(comptime SparseT: type) type { } fn printSet(set: *SparseSet(u32, u8)) void { - std.debug.warn("\nsparse -----\n", .{}); + std.debug.print("\nsparse -----\n", .{}); for (set.sparse.items) |sparse| { - std.debug.warn("{}\t", .{sparse}); + std.debug.print("{}\t", .{sparse}); } - std.debug.warn("\ndense -----\n", .{}); + std.debug.print("\ndense -----\n", .{}); for (set.dense.items) |dense| { - std.debug.warn("{}\t", .{dense}); + std.debug.print("{}\t", .{dense}); } - std.debug.warn("\n\n", .{}); + std.debug.print("\n\n", .{}); } test "add/remove/clear" { diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index 9d316e7..5e8f6b7 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -131,7 +131,7 @@ pub const Scheduler = struct { }; test "" { - std.debug.warn("\n", .{}); + std.debug.print("\n", .{}); const Tester = struct { process: Process, @@ -150,27 +150,27 @@ test "" { fn start(process: *Process) void { _ = process.getParent(@This()); - // std.debug.warn("start {}\n", .{self.fart}); + // std.debug.print("start {}\n", .{self.fart}); } fn aborted(process: *Process) void { _ = process.getParent(@This()); - // std.debug.warn("aborted {}\n", .{self.fart}); + // std.debug.print("aborted {}\n", .{self.fart}); } fn failed(process: *Process) void { _ = process.getParent(@This()); - // std.debug.warn("failed {}\n", .{self.fart}); + // std.debug.print("failed {}\n", .{self.fart}); } fn succeeded(process: *Process) void { _ = process.getParent(@This()); - // std.debug.warn("succeeded {}\n", .{self.fart}); + // std.debug.print("succeeded {}\n", .{self.fart}); } fn update(process: *Process) void { _ = process.getParent(@This()); - // std.debug.warn("update {}\n", .{self.fart}); + // std.debug.print("update {}\n", .{self.fart}); process.succeed(); } }; diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index a0e19d2..644d2be 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const warn = std.debug.warn; const ecs = @import("ecs"); const Registry = @import("ecs").Registry; const BasicGroup = @import("ecs").BasicGroup; @@ -14,12 +13,12 @@ const Renderable = struct { x: f32 = 0 }; const Rotation = struct { x: f32 = 0 }; fn printStore(store: anytype, name: []const u8) void { - warn("--- {} ---\n", .{name}); + std.debug.print("--- {} ---\n", .{name}); 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(" ({d:.2}) ", .{store.instances.items[i]}); + std.debug.print("e[{}] s[{}]{}", .{ e, store.set.page(store.set.dense.items[i]), store.set.sparse.items[store.set.page(store.set.dense.items[i])] }); + std.debug.print(" ({d:.2}) ", .{store.instances.items[i]}); } - warn("\n", .{}); + std.debug.print("\n", .{}); } test "sort BasicGroup by Entity" { @@ -239,5 +238,5 @@ test "nested OwningGroups entity order" { // printStore(sprite_store, "Sprite"); // printStore(transform_store, "Transform"); - // warn("group2.current: {}\n", .{group2.group_data.current}); + // std.debug.print("group2.current: {}\n", .{group2.group_data.current}); } From 253c2ca87a9694fb1221b278ee685f2ce2bbcf0b Mon Sep 17 00:00:00 2001 From: Austin Rude Date: Wed, 15 Dec 2021 12:10:17 -0700 Subject: [PATCH 117/146] Use allocator.dupe() instead of std.mem.dupe() --- zig-ecs/src/ecs/registry.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 2ce4895..8ff9942 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -59,9 +59,9 @@ pub const Registry = struct { if (owned.len == 0) { group_data.entity_set = SparseSet(Entity).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.owned = allocator.dupe(u32, owned) catch unreachable; + group_data.include = allocator.dupe(u32, include) catch unreachable; + group_data.exclude = allocator.dupe(u32, exclude) catch unreachable; group_data.registry = registry; group_data.current = 0; From e589bae3b0511dee91c6ecfa4472de342b1414be Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 18 Dec 2021 13:36:06 -0800 Subject: [PATCH 118/146] latest zig support --- zig-ecs/examples/group_sort.zig | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/zig-ecs/examples/group_sort.zig b/zig-ecs/examples/group_sort.zig index 3097b72..16cdeb5 100644 --- a/zig-ecs/examples/group_sort.zig +++ b/zig-ecs/examples/group_sort.zig @@ -14,8 +14,6 @@ pub fn main() !void { var reg = ecs.Registry.init(std.heap.c_allocator); defer reg.deinit(); - var timer = try std.time.Timer.start(); - createEntities(®); owningGroup(®); } @@ -27,8 +25,8 @@ fn createEntities(reg: *ecs.Registry) void { var i: usize = 0; while (i < total_entities) : (i += 1) { var e1 = reg.create(); - reg.add(e1, Position{ .x = 1, .y = r.random.float(f32) * 100 }); - reg.add(e1, Velocity{ .x = 1, .y = r.random.float(f32) * 100 }); + reg.add(e1, Position{ .x = 1, .y = r.random().float(f32) * 100 }); + reg.add(e1, Velocity{ .x = 1, .y = r.random().float(f32) * 100 }); } var end = timer.lap(); @@ -44,7 +42,7 @@ fn owningGroup(reg: *ecs.Registry) void { // } const SortContext = struct { - fn sort(this: void, a: Position, b: Position) bool { + fn sort(_: void, a: Position, b: Position) bool { return a.y < b.y; } }; From 6852f38a83d094d2c8694ff9ca4df568c0c66ce2 Mon Sep 17 00:00:00 2001 From: prime31 Date: Sun, 19 Dec 2021 19:27:17 -0800 Subject: [PATCH 119/146] Update README.md --- zig-ecs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/README.md b/zig-ecs/README.md index 54a24ad..17a3467 100644 --- a/zig-ecs/README.md +++ b/zig-ecs/README.md @@ -1,5 +1,5 @@ # Zig ECS -This is a zigification of the fantasic [Entt](https://github.com/skypjack/entt). Entt is _highly_ templated C++ code which depending on your opinion is either a good thing or satan itself in code form. Zig doesn't have the same concept as C++ templates (thank goodness!) so the templated code was changed over to use Zig's generics and compile time metaprogramming. +Zig ECS is a zig port of the fantasic [Entt](https://github.com/skypjack/entt). Entt is _highly_ templated C++ code which depending on your opinion is either a good thing or satan itself in code form. Zig doesn't have the same concept as C++ templates (thank goodness!) so the templated code was changed over to use Zig's generics and compile time metaprogramming. ## What does a zigified Entt look like? Below are examples of a View and a Group, the two main ways to work with entities in the ecs along with the scaffolding code. From a294babecc89453755bb4f3622f950654cceb68f Mon Sep 17 00:00:00 2001 From: Victor carvalho Date: Thu, 6 Jan 2022 16:21:08 +0000 Subject: [PATCH 120/146] mod(registry.zig): rename singletons field, return pointer *TypeStore. Renaming the field `singletons` to `type_store` fixes the following error in Zig master (0.10.0-dev.193+1d55e4cae): `error: type '.ecs.type_store.TypeStore' not a function` Returning a pointer makes the API a bit more ergonomic to consume, allowing multiple calls to add and get to the singletons. For example: ```zig var singletons = registry.singletons(); singletons.add(State.init(allocator)); var state = singletons.get(State); ``` --- zig-ecs/src/ecs/registry.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 8ff9942..64487b1 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -34,7 +34,7 @@ pub const Registry = struct { components: std.AutoHashMap(u32, usize), contexts: std.AutoHashMap(u32, usize), groups: std.ArrayList(*GroupData), - singletons: TypeStore, + type_store: TypeStore, allocator: std.mem.Allocator, /// internal, persistant data structure to manage the entities in a group @@ -190,7 +190,7 @@ pub const Registry = struct { .components = std.AutoHashMap(u32, usize).init(allocator), .contexts = std.AutoHashMap(u32, usize).init(allocator), .groups = std.ArrayList(*GroupData).init(allocator), - .singletons = TypeStore.init(allocator), + .type_store = TypeStore.init(allocator), .allocator = allocator, }; } @@ -210,7 +210,7 @@ pub const Registry = struct { self.components.deinit(); self.contexts.deinit(); self.groups.deinit(); - self.singletons.deinit(); + self.type_store.deinit(); self.handles.deinit(); } @@ -418,8 +418,8 @@ pub const Registry = struct { } /// provides access to a TypeStore letting you add singleton components to the registry - pub fn singletons(self: Registry) TypeStore { - return self.singletons; + pub fn singletons(self: *Registry) *TypeStore { + return &self.type_store; } pub fn sort(self: *Registry, comptime T: type, comptime lessThan: fn (void, T, T) bool) void { From 1836e941fd652be9854582b8b9dde4c8fc22eeac Mon Sep 17 00:00:00 2001 From: Jacob Hinchliffe Date: Sun, 16 Jan 2022 22:10:18 +0000 Subject: [PATCH 121/146] Generate documentation from doc comments. --- zig-ecs/build.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 5648d61..3a37c3b 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -23,6 +23,11 @@ pub fn build(b: *Builder) void { exe.setOutputDir(std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); exe.addPackagePath("ecs", "src/ecs.zig"); exe.linkSystemLibrary("c"); + + exe.emit_docs = .emit; + const doc = b.step("docs", "Generate documentation."); + doc.dependOn(&exe.step); + const run_cmd = exe.run(); const exe_step = b.step(name, b.fmt("run {s}.zig", .{name})); From b630099aca2d34b2b8a0782db19ae95d2a8c8b97 Mon Sep 17 00:00:00 2001 From: Jacob Date: Sun, 16 Jan 2022 23:55:13 +0000 Subject: [PATCH 122/146] Update build.zig --- zig-ecs/build.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 3a37c3b..c7798b7 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -24,9 +24,11 @@ pub fn build(b: *Builder) void { exe.addPackagePath("ecs", "src/ecs.zig"); exe.linkSystemLibrary("c"); - exe.emit_docs = .emit; - const doc = b.step("docs", "Generate documentation."); - doc.dependOn(&exe.step); + const docs = exe; + docs.emit_docs = .emit; + + const doc = b.step("docs", "Generate documentation"); + doc.dependOn(&docs.step); const run_cmd = exe.run(); From e3e039ba82260c3ced451a504e1ac50cb1955add Mon Sep 17 00:00:00 2001 From: Justin Ryan Hurst Date: Tue, 15 Feb 2022 21:42:36 -0600 Subject: [PATCH 123/146] fix: Fix add package for zig 0.9.0 and master 0b22 --- zig-ecs/build.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index c7798b7..fc1e2c7 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -23,14 +23,13 @@ pub fn build(b: *Builder) void { exe.setOutputDir(std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); exe.addPackagePath("ecs", "src/ecs.zig"); exe.linkSystemLibrary("c"); - + const docs = exe; docs.emit_docs = .emit; const doc = b.step("docs", "Generate documentation"); doc.dependOn(&docs.step); - const run_cmd = exe.run(); const exe_step = b.step(name, b.fmt("run {s}.zig", .{name})); exe_step.dependOn(&run_cmd.step); @@ -65,7 +64,7 @@ pub const LibType = enum(i32) { pub fn getPackage(comptime prefix_path: []const u8) std.build.Pkg { return .{ .name = "ecs", - .path = prefix_path ++ "src/ecs.zig", + .path = .{ .path = prefix_path ++ "src/ecs.zig" }, }; } From 4196c9186c6dd88c96848491fe94e89c38f5ede5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Partheym=C3=BCller?= Date: Tue, 7 Feb 2023 15:47:12 +0100 Subject: [PATCH 124/146] fix some compilation errors caused by language changes in zig v0.10.0 --- zig-ecs/examples/simple.zig | 12 +++++++++--- zig-ecs/src/ecs/component_storage.zig | 12 ++++++------ zig-ecs/src/ecs/groups.zig | 14 ++++++++------ zig-ecs/src/ecs/registry.zig | 5 +++-- zig-ecs/src/ecs/sparse_set.zig | 4 ++-- zig-ecs/src/ecs/utils.zig | 4 ++-- zig-ecs/src/process/process.zig | 12 ++++++------ zig-ecs/src/process/scheduler.zig | 11 +++++++---- zig-ecs/src/resources/assets.zig | 3 +++ zig-ecs/src/resources/cache.zig | 5 ++++- zig-ecs/src/signals/delegate.zig | 8 ++++---- zig-ecs/src/signals/sink.zig | 8 ++++---- 12 files changed, 58 insertions(+), 40 deletions(-) diff --git a/zig-ecs/examples/simple.zig b/zig-ecs/examples/simple.zig index 4b973ac..45918ac 100644 --- a/zig-ecs/examples/simple.zig +++ b/zig-ecs/examples/simple.zig @@ -8,7 +8,7 @@ pub const Velocity = struct { x: f32, y: f32 }; pub const Position = struct { x: f32, y: f32 }; pub fn main() !void { - var reg = ecs.Registry.init(std.testing.allocator); + var reg = ecs.Registry.init(std.heap.c_allocator); defer reg.deinit(); var e1 = reg.create(); @@ -25,7 +25,10 @@ pub fn main() !void { while (iter.next()) |entity| { var pos = view.get(Position, entity); const vel = view.getConst(Velocity, entity); - std.debug.print("entity: {}, pos: {d}, vel: {d}\n", .{ entity, pos.*, vel }); + std.debug.print( + "entity: {}, pos: (x = {d}, y = {d}), vel: (x = {d}, y = {d})\n", + .{ entity, pos.x, pos.y, vel.x, vel.y }, + ); pos.*.x += vel.x; pos.*.y += vel.y; } @@ -36,6 +39,9 @@ pub fn main() !void { while (iter.next()) |entity| { const pos = view.getConst(Position, entity); const vel = view.getConst(Velocity, entity); - std.debug.print("entity: {}, pos: {d}, vel: {d}\n", .{ entity, pos, vel }); + std.debug.print( + "entity: {}, pos: (x = {d}, y = {d}), vel: (x = {d}, y = {d})\n", + .{ entity, pos.x, pos.y, vel.x, vel.y }, + ); } } diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index e3aa6b7..c7e50fe 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -26,9 +26,9 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { allocator: ?std.mem.Allocator, /// doesnt really belong here...used to denote group ownership super: usize = 0, - safeDeinit: fn (*Self) void, - safeSwap: fn (*Self, Entity, Entity, bool) void, - safeRemoveIfContains: fn (*Self, Entity) void, + safeDeinit: *const fn (*Self) void, + safeSwap: *const fn (*Self, Entity, Entity, bool) void, + safeRemoveIfContains: *const fn (*Self, Entity) void, construction: Signal(Entity), update: Signal(Entity), destruction: Signal(Entity), @@ -187,7 +187,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { struct { /// Sort Entities according to the given comparison function. Only T == Entity is allowed. The constraint param only exists for /// parity with non-empty Components - pub fn sort(self: Self, comptime T: type, context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { + pub fn sort(self: Self, comptime T: type, context: anytype, comptime lessThan: *const fn (@TypeOf(context), T, T) bool) void { std.debug.assert(T == Entity); self.set.sort(context, lessThan); } @@ -225,7 +225,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { } /// 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, length: usize, context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { + pub fn sort(self: *Self, comptime T: type, length: usize, context: anytype, comptime lessThan: *const fn (@TypeOf(context), T, T) bool) void { std.debug.assert(T == Entity or T == Component); // we have to perform a swap after the sort for all moved entities so we make a helper struct for that. In the @@ -245,7 +245,7 @@ pub fn ComponentStorage(comptime Component: type, comptime Entity: type) type { const SortContext = struct { storage: *Self, wrapped_context: @TypeOf(context), - lessThan: fn (@TypeOf(context), T, T) bool, + lessThan: *const fn (@TypeOf(context), T, T) bool, fn sort(this: @This(), a: Entity, b: Entity) bool { const real_a = this.storage.getConst(a); diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 7ae6f6c..2e15bbe 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -43,7 +43,7 @@ pub const BasicGroup = struct { return self.group_data.entity_set.reverseIterator(); } - pub fn sort(self: BasicGroup, comptime T: type, context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { + pub fn sort(self: BasicGroup, comptime T: type, context: anytype, comptime lessThan: *const fn (@TypeOf(context), T, T) bool) void { if (T == Entity) { self.group_data.entity_set.sort(context, lessThan); } else { @@ -51,7 +51,7 @@ pub const BasicGroup = struct { const SortContext = struct { group: BasicGroup, wrapped_context: @TypeOf(context), - lessThan: fn (@TypeOf(context), T, T) bool, + lessThan: *const fn (@TypeOf(context), T, T) bool, fn sort(this: @This(), a: Entity, b: Entity) bool { const real_a = this.group.getConst(T, a); @@ -233,7 +233,7 @@ pub const OwningGroup = struct { return utils.ReverseSliceIterator(Entity).init(self.firstOwnedStorage().set.dense.items[0..self.group_data.current]); } - pub fn sort(self: OwningGroup, comptime T: type, context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool) void { + pub fn sort(self: OwningGroup, comptime T: type, context: anytype, comptime lessThan: *const fn (@TypeOf(context), T, T) bool) void { var first_storage = self.firstOwnedStorage(); if (T == Entity) { @@ -244,7 +244,7 @@ pub const OwningGroup = struct { const SortContext = struct { group: OwningGroup, wrapped_context: @TypeOf(context), - lessThan: fn (@TypeOf(context), T, T) bool, + lessThan: *const fn (@TypeOf(context), T, T) bool, fn sort(this: @This(), a: Entity, b: Entity) bool { const real_a = this.group.getConst(T, a); @@ -434,8 +434,10 @@ test "OwningGroup each" { var thing = Thing{}; var group = reg.group(.{ i32, u32 }, .{}, .{}); - group.each(thing.each); - group.each(each); + // group.each(thing.each); // zig v0.10.0: error: no field named 'each' in struct 'ecs.groups.test.OwningGroup each.Thing' + _ = thing; + // group.each(each); // zig v0.10.0: error: expected type 'ecs.groups.each__struct_6297', found 'ecs.groups.each__struct_3365' + _ = group; } test "multiple OwningGroups" { diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 64487b1..72fbf68 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -422,7 +422,7 @@ pub const Registry = struct { return &self.type_store; } - pub fn sort(self: *Registry, comptime T: type, comptime lessThan: fn (void, T, T) bool) void { + pub fn sort(self: *Registry, comptime T: type, comptime lessThan: *const fn (void, T, T) bool) void { const comp = self.assure(T); std.debug.assert(comp.super == 0); comp.sort(T, lessThan); @@ -568,7 +568,8 @@ pub const Registry = struct { // pre-fill the GroupData with any existing entitites that match if (owned.len == 0) { - var view_iter = self.view(owned ++ includes, excludes).iterator(); + var view_instance = self.view(owned ++ includes, excludes); + var view_iter = view_instance.iterator(); while (view_iter.next()) |entity| { new_group_data.entity_set.add(entity); } diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 7752d62..55fea97 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -147,7 +147,7 @@ pub fn SparseSet(comptime SparseT: type) type { } /// Sort elements according to the given comparison function - pub fn sort(self: *Self, context: anytype, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool) void { + pub fn sort(self: *Self, context: anytype, comptime lessThan: *const fn (@TypeOf(context), SparseT, SparseT) bool) void { std.sort.insertionSort(SparseT, self.dense.items, context, lessThan); for (self.dense.items) |_, i| { @@ -158,7 +158,7 @@ pub fn SparseSet(comptime SparseT: type) type { /// Sort elements according to the given comparison function. Use this when a data array needs to stay in sync with the SparseSet /// by passing in a "swap_context" that contains a "swap" method with a sig of fn(ctx,SparseT,SparseT)void - pub fn arrange(self: *Self, length: usize, context: anytype, comptime lessThan: fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: anytype) void { + pub fn arrange(self: *Self, length: usize, context: anytype, comptime lessThan: *const fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: anytype) void { std.sort.insertionSort(SparseT, self.dense.items[0..length], context, lessThan); for (self.dense.items[0..length]) |_, pos| { diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 5e29f21..d0429e6 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -49,7 +49,7 @@ pub fn ReverseSliceIterator(comptime T: type) type { } /// 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, comptime lessThan: fn (void, lhs: T1, rhs: T1) bool) void { +pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, comptime lessThan: *const fn (void, lhs: T1, rhs: T1) bool) void { var i: usize = 1; while (i < items.len) : (i += 1) { const x = items[i]; @@ -64,7 +64,7 @@ pub fn sortSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T } } -pub fn sortSubSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, context: anytype, comptime lessThan: fn (@TypeOf(context), lhs: T1, rhs: T1) bool) void { +pub fn sortSubSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: []T2, context: anytype, comptime lessThan: *const fn (@TypeOf(context), lhs: T1, rhs: T1) bool) void { var i: usize = 1; while (i < items.len) : (i += 1) { const x = items[i]; diff --git a/zig-ecs/src/process/process.zig b/zig-ecs/src/process/process.zig index 46d7dda..1c02254 100644 --- a/zig-ecs/src/process/process.zig +++ b/zig-ecs/src/process/process.zig @@ -5,12 +5,12 @@ const std = @import("std"); pub const Process = struct { const State = enum(u8) { uninitialized, running, paused, succeeded, failed, aborted, finished }; - updateFn: fn (self: *Process) void, - startFn: ?fn (self: *Process) void = null, - abortedFn: ?fn (self: *Process) void = null, - failedFn: ?fn (self: *Process) void = null, - succeededFn: ?fn (self: *Process) void = null, - deinit: fn (self: *Process, allocator: std.mem.Allocator) void = undefined, + updateFn: *const fn (self: *Process) void, + startFn: ?*const fn (self: *Process) void = null, + abortedFn: ?*const fn (self: *Process) void = null, + failedFn: ?*const fn (self: *Process) void = null, + succeededFn: ?*const fn (self: *Process) void = null, + deinit: *const fn (self: *Process, allocator: std.mem.Allocator) void = undefined, state: State = .uninitialized, stopped: bool = false, diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index 5e8f6b7..8c33753 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -130,7 +130,7 @@ pub const Scheduler = struct { } }; -test "" { +test { std.debug.print("\n", .{}); const Tester = struct { @@ -178,7 +178,8 @@ test "" { var scheduler = Scheduler.init(std.testing.allocator); defer scheduler.deinit(); - _ = scheduler.attach(Tester, 33).next(Tester, 66).next(Tester, 88).next(Tester, 99); + var continuation = scheduler.attach(Tester, 33); + _ = continuation.next(Tester, 66).next(Tester, 88).next(Tester, 99); scheduler.update(); scheduler.update(); scheduler.update(); @@ -202,7 +203,8 @@ test "scheduler.clear" { var scheduler = Scheduler.init(std.testing.allocator); defer scheduler.deinit(); - _ = scheduler.attach(Tester, {}).next(Tester, {}); + var continuation = scheduler.attach(Tester, {}); + _ = continuation.next(Tester, {}); scheduler.clear(); scheduler.update(); } @@ -228,7 +230,8 @@ test "scheduler.attach.next" { defer scheduler.deinit(); var counter: usize = 0; - _ = scheduler.attach(Tester, &counter).next(Tester, &counter); + var continuation = scheduler.attach(Tester, &counter); + _ = continuation.next(Tester, &counter); scheduler.update(); scheduler.update(); try std.testing.expectEqual(counter, 2); diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index 19121b6..3b6a6e2 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -47,6 +47,9 @@ pub const Assets = struct { }; test "assets" { + // zig v0.10.0: Compilation Error + // error: no field named 'load' in struct 'resources.assets.test.assets.ThingLoadArgs' + const Thing = struct { fart: i32, pub fn deinit(self: *@This()) void { diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index f1ad407..0d8fbd8 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -8,7 +8,7 @@ pub fn Cache(comptime T: type) type { return struct { const Self = @This(); - safe_deinit: fn (*@This()) void, + safe_deinit: *const fn (*@This()) void, resources: std.AutoHashMap(u32, *T), allocator: ?std.mem.Allocator = null, @@ -82,6 +82,9 @@ pub fn Cache(comptime T: type) type { } test "cache" { + // zig v0.10.0: Compilation Error + // error: no field named 'load' in struct 'resources.cache.test.cache.ThingLoadArgs' + const utils = @import("../ecs/utils.zig"); const Thing = struct { diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index dd6c134..c40a57b 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -7,8 +7,8 @@ pub fn Delegate(comptime Event: type) type { ctx_ptr_address: usize = 0, callback: union(enum) { - free: fn (Event) void, - bound: fn (usize, Event) void, + free: *const fn (Event) void, + bound: *const fn (usize, Event) void, }, /// sets a bound function as the Delegate callback @@ -30,7 +30,7 @@ pub fn Delegate(comptime Event: type) type { } /// sets a free function as the Delegate callback - pub fn initFree(func: fn (Event) void) Self { + pub fn initFree(func: *const fn (Event) void) Self { return Self{ .callback = .{ .free = func }, }; @@ -43,7 +43,7 @@ pub fn Delegate(comptime Event: type) type { } } - pub fn containsFree(self: Self, callback: fn (Event) void) bool { + pub fn containsFree(self: Self, callback: *const fn (Event) void) bool { return switch (self.callback) { .free => |func| func == callback, else => false, diff --git a/zig-ecs/src/signals/sink.zig b/zig-ecs/src/signals/sink.zig index 76b7e48..48f09f9 100644 --- a/zig-ecs/src/signals/sink.zig +++ b/zig-ecs/src/signals/sink.zig @@ -18,7 +18,7 @@ pub fn Sink(comptime Event: type) type { return Self{ .insert_index = owning_signal.calls.items.len }; } - pub fn before(self: Self, callback: ?fn (Event) void) Self { + pub fn before(self: Self, callback: ?*const fn (Event) void) Self { if (callback) |cb| { if (self.indexOf(cb)) |index| { return Self{ .insert_index = index }; @@ -36,7 +36,7 @@ pub fn Sink(comptime Event: type) type { return self; } - pub fn connect(self: Self, callback: fn (Event) void) void { + pub fn connect(self: Self, callback: *const fn (Event) void) void { std.debug.assert(self.indexOf(callback) == null); _ = owning_signal.calls.insert(self.insert_index, Delegate(Event).initFree(callback)) catch unreachable; } @@ -46,7 +46,7 @@ pub fn Sink(comptime Event: type) type { _ = owning_signal.calls.insert(self.insert_index, Delegate(Event).initBound(ctx, fn_name)) catch unreachable; } - pub fn disconnect(self: Self, callback: fn (Event) void) void { + pub fn disconnect(self: Self, callback: *const fn (Event) void) void { if (self.indexOf(callback)) |index| { _ = owning_signal.calls.swapRemove(index); } @@ -58,7 +58,7 @@ pub fn Sink(comptime Event: type) type { } } - fn indexOf(_: Self, callback: fn (Event) void) ?usize { + fn indexOf(_: Self, callback: *const fn (Event) void) ?usize { for (owning_signal.calls.items) |call, i| { if (call.containsFree(callback)) { return i; From 6f0eacc8ca0b3a60b435cf180ce20358a8cc78c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Partheym=C3=BCller?= Date: Wed, 8 Feb 2023 11:35:33 +0100 Subject: [PATCH 125/146] add name for test case --- zig-ecs/src/process/scheduler.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig index 8c33753..5a80314 100644 --- a/zig-ecs/src/process/scheduler.zig +++ b/zig-ecs/src/process/scheduler.zig @@ -130,7 +130,7 @@ pub const Scheduler = struct { } }; -test { +test "scheduler.update" { std.debug.print("\n", .{}); const Tester = struct { From 9bbff920bbaadb4a94d2f9b550df84fde689d568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Partheym=C3=BCller?= Date: Wed, 8 Feb 2023 11:35:54 +0100 Subject: [PATCH 126/146] exclude generated docs directory from repo --- zig-ecs/.gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zig-ecs/.gitignore b/zig-ecs/.gitignore index 2aca84c..716f74c 100644 --- a/zig-ecs/.gitignore +++ b/zig-ecs/.gitignore @@ -1,4 +1,6 @@ zig-cache zig-arm-cache -.DS_Store \ No newline at end of file +/docs/ + +.DS_Store From 42b2a57d1c096ac6893e2761de51b9b1af539fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Partheym=C3=BCller?= Date: Wed, 8 Feb 2023 11:36:23 +0100 Subject: [PATCH 127/146] fix invokation of sort function --- zig-ecs/src/ecs/registry.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 72fbf68..3fab6ff 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -425,7 +425,7 @@ pub const Registry = struct { pub fn sort(self: *Registry, comptime T: type, comptime lessThan: *const fn (void, T, T) bool) void { const comp = self.assure(T); std.debug.assert(comp.super == 0); - comp.sort(T, lessThan); + comp.sort(T, comp.len(), {}, lessThan); } /// Checks whether the given component belongs to any group. If so, it is not sortable directly. From 386113ac5fcfde109afd173dfce5004c8473eeb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Partheym=C3=BCller?= Date: Wed, 8 Feb 2023 11:38:18 +0100 Subject: [PATCH 128/146] copy and fix function pointer syntax in function std.sort.insertionSort to avoid compliation error --- zig-ecs/src/ecs/sparse_set.zig | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 55fea97..9d0bed7 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -3,6 +3,30 @@ const utils = @import("utils.zig"); const registry = @import("registry.zig"); const ReverseSliceIterator = @import("utils.zig").ReverseSliceIterator; +/// NOTE: This is a copy of `std.sort.insertionSort` with fixed function pointer +/// syntax to avoid compilation errors. +/// +/// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case. +/// O(1) memory (no allocator required). +/// This can be expressed in terms of `insertionSortContext` but the glue +/// code is slightly longer than the direct implementation. +fn std_sort_insertionSort_clone( + comptime T: type, + items: []T, + context: anytype, + comptime lessThan: *const fn (context: @TypeOf(context), lhs: T, rhs: T) bool, +) void { + var i: usize = 1; + while (i < items.len) : (i += 1) { + const x = items[i]; + var j: usize = i; + while (j > 0 and lessThan(context, x, items[j - 1])) : (j -= 1) { + items[j] = items[j - 1]; + } + items[j] = x; + } +} + // TODO: fix entity_mask. it should come from EntityTraitsDefinition. pub fn SparseSet(comptime SparseT: type) type { return struct { @@ -33,7 +57,6 @@ pub fn SparseSet(comptime SparseT: type) type { } pub fn deinit(self: *Self) void { - self.sparse.expandToCapacity(); for (self.sparse.items) |array| { if (array) |arr| { self.sparse.allocator.free(arr); @@ -148,7 +171,7 @@ pub fn SparseSet(comptime SparseT: type) type { /// Sort elements according to the given comparison function pub fn sort(self: *Self, context: anytype, comptime lessThan: *const fn (@TypeOf(context), SparseT, SparseT) bool) void { - std.sort.insertionSort(SparseT, self.dense.items, context, lessThan); + std_sort_insertionSort_clone(SparseT, self.dense.items, context, lessThan); for (self.dense.items) |_, i| { const item = @intCast(SparseT, i); @@ -159,7 +182,7 @@ pub fn SparseSet(comptime SparseT: type) type { /// Sort elements according to the given comparison function. Use this when a data array needs to stay in sync with the SparseSet /// by passing in a "swap_context" that contains a "swap" method with a sig of fn(ctx,SparseT,SparseT)void pub fn arrange(self: *Self, length: usize, context: anytype, comptime lessThan: *const fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: anytype) void { - std.sort.insertionSort(SparseT, self.dense.items[0..length], context, lessThan); + std_sort_insertionSort_clone(SparseT, self.dense.items[0..length], context, lessThan); for (self.dense.items[0..length]) |_, pos| { var curr = @intCast(SparseT, pos); @@ -190,7 +213,6 @@ pub fn SparseSet(comptime SparseT: type) type { } pub fn clear(self: *Self) void { - self.sparse.expandToCapacity(); for (self.sparse.items) |array, i| { if (array) |arr| { self.sparse.allocator.free(arr); From b1b8321fa493f63cae5f2dc7302ca1b20fcd4ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Partheym=C3=BCller?= Date: Wed, 8 Feb 2023 11:40:19 +0100 Subject: [PATCH 129/146] change comptime access to typeInfo of load function to satisfy compiler / use function pointer in order to make @field() work --- zig-ecs/src/resources/assets.zig | 25 ++++++++++++++----------- zig-ecs/src/resources/cache.zig | 16 ++++++++-------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index 3b6a6e2..6481783 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -37,8 +37,8 @@ pub const Assets = struct { return self.get(ReturnType(loader, true)).load(id, loader); } - fn ReturnType(comptime loader: anytype, strip_ptr: bool) type { - var ret = @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.?; + fn ReturnType(comptime loader: anytype, comptime strip_ptr: bool) type { + var ret = @typeInfo(@typeInfo(@TypeOf(@field(loader, "load"))).Pointer.child).Fn.return_type.?; if (strip_ptr) { return std.meta.Child(ret); } @@ -47,9 +47,6 @@ pub const Assets = struct { }; test "assets" { - // zig v0.10.0: Compilation Error - // error: no field named 'load' in struct 'resources.assets.test.assets.ThingLoadArgs' - const Thing = struct { fart: i32, pub fn deinit(self: *@This()) void { @@ -65,13 +62,19 @@ test "assets" { }; const OtherThingLoadArgs = struct { - pub fn load(_: @This()) *OtherThing { + // Use actual field "load" as function pointer to avoid zig v0.10.0 + // compiler error: "error: no field named 'load' in struct '...'" + load: *const fn (_: @This()) *OtherThing, + pub fn loadFn(_: @This()) *OtherThing { return std.testing.allocator.create(OtherThing) catch unreachable; } }; const ThingLoadArgs = struct { - pub fn load(_: @This()) *Thing { + // Use actual field "load" as function pointer to avoid zig v0.10.0 + // compiler error: "error: no field named 'load' in struct '...'" + load: *const fn (_: @This()) *Thing, + pub fn loadFn(_: @This()) *Thing { return std.testing.allocator.create(Thing) catch unreachable; } }; @@ -79,16 +82,16 @@ test "assets" { var assets = Assets.init(std.testing.allocator); defer assets.deinit(); - _ = assets.get(Thing).load(6, ThingLoadArgs{}); + _ = assets.get(Thing).load(6, ThingLoadArgs{ .load = ThingLoadArgs.loadFn }); try std.testing.expectEqual(assets.get(Thing).size(), 1); - _ = assets.load(4, ThingLoadArgs{}); + _ = assets.load(4, ThingLoadArgs{ .load = ThingLoadArgs.loadFn }); try std.testing.expectEqual(assets.get(Thing).size(), 2); - _ = assets.get(OtherThing).load(6, OtherThingLoadArgs{}); + _ = assets.get(OtherThing).load(6, OtherThingLoadArgs{ .load = OtherThingLoadArgs.loadFn }); try std.testing.expectEqual(assets.get(OtherThing).size(), 1); - _ = assets.load(8, OtherThingLoadArgs{}); + _ = assets.load(8, OtherThingLoadArgs{ .load = OtherThingLoadArgs.loadFn }); try std.testing.expectEqual(assets.get(OtherThing).size(), 2); assets.get(OtherThing).clear(); diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index 0d8fbd8..2195973 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -42,12 +42,12 @@ pub fn Cache(comptime T: type) type { self.safe_deinit(self); } - pub fn load(self: *@This(), id: u32, comptime loader: anytype) @typeInfo(@TypeOf(@field(loader, "load"))).BoundFn.return_type.? { + pub fn load(self: *@This(), id: u32, comptime loader: anytype) @typeInfo(@typeInfo(@TypeOf(@field(loader, "load"))).Pointer.child).Fn.return_type.? { if (self.resources.get(id)) |resource| { return resource; } - var resource = loader.load(); + var resource = loader.load(loader); _ = self.resources.put(id, resource) catch unreachable; return resource; } @@ -82,9 +82,6 @@ pub fn Cache(comptime T: type) type { } test "cache" { - // zig v0.10.0: Compilation Error - // error: no field named 'load' in struct 'resources.cache.test.cache.ThingLoadArgs' - const utils = @import("../ecs/utils.zig"); const Thing = struct { @@ -95,7 +92,10 @@ test "cache" { }; const ThingLoadArgs = struct { - pub fn load(self: @This()) *Thing { + // Use actual field "load" as function pointer to avoid zig v0.10.0 + // compiler error: "error: no field named 'load' in struct '...'" + load: *const fn (self: @This()) *Thing, + pub fn loadFn(self: @This()) *Thing { _ = self; return std.testing.allocator.create(Thing) catch unreachable; } @@ -104,8 +104,8 @@ test "cache" { var cache = Cache(Thing).init(std.testing.allocator); defer cache.deinit(); - _ = cache.load(utils.hashString("my/id"), ThingLoadArgs{}); - _ = cache.load(utils.hashString("another/id"), ThingLoadArgs{}); + _ = cache.load(utils.hashString("my/id"), ThingLoadArgs{ .load = ThingLoadArgs.loadFn }); + _ = cache.load(utils.hashString("another/id"), ThingLoadArgs{ .load = ThingLoadArgs.loadFn }); try std.testing.expectEqual(cache.size(), 2); cache.remove(utils.hashString("my/id")); From 701fbfb4db94d8819fb0c092b38c0f738b073b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Partheym=C3=BCller?= Date: Wed, 8 Feb 2023 11:40:42 +0100 Subject: [PATCH 130/146] invoke singletons() function to satisfy compiler --- zig-ecs/tests/registry_test.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index 7549220..10d5cf1 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -82,12 +82,12 @@ test "singletons" { defer reg.deinit(); var pos = Position{ .x = 5, .y = 5 }; - reg.singletons.add(pos); - try std.testing.expect(reg.singletons.has(Position)); - try std.testing.expectEqual(reg.singletons.get(Position).*, pos); + reg.singletons().add(pos); + try std.testing.expect(reg.singletons().has(Position)); + try std.testing.expectEqual(reg.singletons().get(Position).*, pos); - reg.singletons.remove(Position); - try std.testing.expect(!reg.singletons.has(Position)); + reg.singletons().remove(Position); + try std.testing.expect(!reg.singletons().has(Position)); } test "destroy" { From e0eb68dd5ba66e20de8a32a7a531f8fdea953009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Partheym=C3=BCller?= Date: Wed, 8 Feb 2023 11:41:48 +0100 Subject: [PATCH 131/146] change Iterator implementation in MultiView struct to use already existing ReverseSliceIterator --- zig-ecs/src/ecs/views.zig | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index f8a0e91..7aa6c48 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -4,6 +4,7 @@ const utils = @import("utils.zig"); const Registry = @import("registry.zig").Registry; const Storage = @import("registry.zig").Storage; const Entity = @import("registry.zig").Entity; +const ReverseSliceIterator = @import("utils.zig").ReverseSliceIterator; /// 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 @@ -63,26 +64,19 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { pub const Iterator = struct { view: *Self, - index: usize, - entities: *const []Entity, + internal_it: ReverseSliceIterator(Entity), pub fn init(view: *Self) Iterator { const ptr = view.registry.components.get(view.type_ids[0]).?; - const entities = @intToPtr(*Storage(u8), ptr).dataPtr(); + const internal_it = @intToPtr(*Storage(u8), ptr).set.reverseIterator(); return .{ .view = view, - .index = entities.len, - .entities = entities, + .internal_it = internal_it }; } pub fn next(it: *Iterator) ?Entity { - while (true) blk: { - if (it.index == 0) return null; - it.index -= 1; - - const entity = it.entities.*[it.index]; - + while (it.internal_it.next()) |entity| blk: { // entity must be in all other Storages for (it.view.type_ids) |tid| { const ptr = it.view.registry.components.get(tid).?; @@ -101,11 +95,19 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { return entity; } + return null; } // Reset the iterator to the initial index pub fn reset(it: *Iterator) void { - it.index = it.entities.len; + // Assign new iterator instance in case entities have been + // removed or added. + it.internal_it = it.getInternalIteratorInstance(); + } + + fn getInternalIteratorInstance(it: *Iterator) ReverseSliceIterator(Entity) { + const ptr = it.view.registry.components.get(it.view.type_ids[0]).?; + return @intToPtr(*Storage(u8), ptr).set.reverseIterator(); } }; From 94f38a3d8989959590d8caa71ffce9844c4480d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Partheym=C3=BCller?= Date: Wed, 8 Feb 2023 14:34:38 +0100 Subject: [PATCH 132/146] make source files and build file compatible with current zig compiler 0.11.0-dev.1580+a5b34a61a --- zig-ecs/build.zig | 42 +++++++++++++++++++------------- zig-ecs/src/ecs/groups.zig | 14 +++++------ zig-ecs/src/resources/cache.zig | 4 +-- zig-ecs/src/signals/delegate.zig | 6 ++--- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index fc1e2c7..64fc154 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -3,7 +3,10 @@ const Builder = std.build.Builder; const builtin = @import("builtin"); pub fn build(b: *Builder) void { - const build_mode = b.standardReleaseOptions(); + const optimize = b.standardOptimizeOption(.{}); + const ecs_module = b.createModule(.{ + .source_file = std.build.FileSource{ .path = "src/ecs.zig" }, + }); // use a different cache folder for macos arm builds b.cache_root = if (builtin.os.tag == .macos and builtin.target.cpu.arch == .aarch64) "zig-arm-cache" else "zig-cache"; @@ -18,11 +21,14 @@ pub fn build(b: *Builder) void { const name = if (i == 0) "ecs" else example[0]; const source = example[1]; - var exe = b.addExecutable(name, source); - exe.setBuildMode(b.standardReleaseOptions()); + var exe = b.addExecutable(.{ + .name = name, + .root_source_file = std.build.FileSource{ .path = source }, + .optimize = optimize, + }); exe.setOutputDir(std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); - exe.addPackagePath("ecs", "src/ecs.zig"); - exe.linkSystemLibrary("c"); + exe.addModule("ecs", ecs_module); + exe.linkLibC(); const docs = exe; docs.emit_docs = .emit; @@ -42,13 +48,17 @@ pub fn build(b: *Builder) void { } // internal tests - const internal_test_step = b.addTest("src/tests.zig"); - internal_test_step.setBuildMode(build_mode); + const internal_test_step = b.addTest(.{ + .root_source_file = std.build.FileSource{ .path = "src/tests.zig" }, + .optimize = optimize, + }); // public api tests - const test_step = b.addTest("tests/tests.zig"); - test_step.addPackagePath("ecs", "src/ecs.zig"); - test_step.setBuildMode(build_mode); + const test_step = b.addTest(.{ + .root_source_file = std.build.FileSource{ .path = "tests/tests.zig" }, + .optimize = optimize, + }); + test_step.addModule("ecs", ecs_module); const test_cmd = b.step("test", "Run the tests"); test_cmd.dependOn(&internal_test_step.step); @@ -61,7 +71,7 @@ pub const LibType = enum(i32) { exe_compiled, }; -pub fn getPackage(comptime prefix_path: []const u8) std.build.Pkg { +pub fn getModule(comptime prefix_path: []const u8) std.build.Module { return .{ .name = "ecs", .path = .{ .path = prefix_path ++ "src/ecs.zig" }, @@ -70,18 +80,16 @@ pub fn getPackage(comptime prefix_path: []const u8) std.build.Pkg { /// prefix_path is used to add package paths. It should be the the same path used to include this build file pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, _: std.build.Target, lib_type: LibType, comptime prefix_path: []const u8) void { - const build_mode = b.standardReleaseOptions(); + const optimize = b.standardOptimizeOption(.{}); switch (lib_type) { .static => { - const lib = b.addStaticLibrary("ecs", "ecs.zig"); - lib.setBuildMode(build_mode); + const lib = b.addStaticLibrary(.{ .name = "ecs", .root_source_file = "ecs.zig", .optimize = optimize }); lib.install(); artifact.linkLibrary(lib); }, .dynamic => { - const lib = b.addSharedLibrary("ecs", "ecs.zig", .unversioned); - lib.setBuildMode(build_mode); + const lib = b.addSharedLibrary(.{ .name = "ecs", .root_source_file = "ecs.zig", .optimize = optimize }); lib.install(); artifact.linkLibrary(lib); @@ -89,5 +97,5 @@ pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, _: std.buil else => {}, } - artifact.addPackage(getPackage(prefix_path)); + artifact.addModule(getModule(prefix_path)); } diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 2e15bbe..9cd1ee2 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -86,7 +86,7 @@ pub const OwningGroup = struct { var component_ptrs: [component_info.fields.len][*]u8 = undefined; inline for (component_info.fields) |field, i| { - const storage = group.registry.assure(@typeInfo(field.field_type).Pointer.child); + const storage = group.registry.assure(@typeInfo(field.type).Pointer.child); component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); } @@ -105,7 +105,7 @@ pub const OwningGroup = struct { // fill and return the struct var comps: Components = undefined; inline for (@typeInfo(Components).Struct.fields) |field, i| { - const typed_ptr = @ptrCast([*]@typeInfo(field.field_type).Pointer.child, @alignCast(@alignOf(@typeInfo(field.field_type).Pointer.child), it.component_ptrs[i])); + const typed_ptr = @ptrCast([*]@typeInfo(field.type).Pointer.child, @alignCast(@alignOf(@typeInfo(field.type).Pointer.child), it.component_ptrs[i])); @field(comps, field.name) = &typed_ptr[it.index]; } return comps; @@ -161,8 +161,8 @@ pub const OwningGroup = struct { std.debug.assert(@typeInfo(Components) == .Struct); inline for (@typeInfo(Components).Struct.fields) |field| { - std.debug.assert(@typeInfo(field.field_type) == .Pointer); - const found = std.mem.indexOfScalar(u32, self.group_data.owned, utils.typeId(std.meta.Child(field.field_type))); + std.debug.assert(@typeInfo(field.type) == .Pointer); + const found = std.mem.indexOfScalar(u32, self.group_data.owned, utils.typeId(std.meta.Child(field.type))); std.debug.assert(found != null); } } @@ -174,7 +174,7 @@ pub const OwningGroup = struct { var component_ptrs: [component_info.fields.len][*]u8 = undefined; inline for (component_info.fields) |field, i| { - const storage = self.registry.assure(std.meta.Child(field.field_type)); + const storage = self.registry.assure(std.meta.Child(field.type)); component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); } @@ -182,7 +182,7 @@ pub const OwningGroup = struct { const index = self.firstOwnedStorage().set.index(entity); var comps: Components = undefined; inline for (component_info.fields) |field, i| { - const typed_ptr = @ptrCast([*]std.meta.Child(field.field_type), @alignCast(@alignOf(std.meta.Child(field.field_type)), component_ptrs[i])); + const typed_ptr = @ptrCast([*]std.meta.Child(field.type), @alignCast(@alignOf(std.meta.Child(field.type)), component_ptrs[i])); @field(comps, field.name) = &typed_ptr[index]; } @@ -200,7 +200,7 @@ pub const OwningGroup = struct { // optionally we could just use an Iterator here and pay for some slight indirection for code sharing var iter = self.iterator(Components); while (iter.next()) |comps| { - @call(.{ .modifier = .always_inline }, func, .{comps}); + @call(.always_inline, func, .{comps}); } } diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index 2195973..e4d9a3f 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -59,7 +59,7 @@ pub fn Cache(comptime T: type) type { pub fn remove(self: *@This(), id: u32) void { if (self.resources.fetchRemove(id)) |kv| { if (@hasDecl(T, "deinit")) { - @call(.{ .modifier = .always_inline }, @field(kv.value, "deinit"), .{}); + @call(.always_inline, @field(kv.value, "deinit"), .{}); } } } @@ -69,7 +69,7 @@ pub fn Cache(comptime T: type) type { if (@hasDecl(T, "deinit")) { var iter = self.resources.iterator(); while (iter.next()) |kv| { - @call(.{ .modifier = .always_inline }, @field(kv.value_ptr.*, "deinit"), .{}); + @call(.always_inline, @field(kv.value_ptr.*, "deinit"), .{}); } } self.resources.clearAndFree(); diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index c40a57b..3a79f9c 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -22,7 +22,7 @@ pub fn Delegate(comptime Event: type) type { .callback = .{ .bound = struct { fn cb(self: usize, param: Event) void { - @call(.{ .modifier = .always_inline }, @field(@intToPtr(T, self), fn_name), .{param}); + @call(.always_inline, @field(@intToPtr(T, self), fn_name), .{param}); } }.cb, }, @@ -38,8 +38,8 @@ pub fn Delegate(comptime Event: type) type { pub fn trigger(self: Self, param: Event) void { switch (self.callback) { - .free => |func| @call(.{}, func, .{param}), - .bound => |func| @call(.{}, func, .{ self.ctx_ptr_address, param }), + .free => |func| @call(.auto, func, .{param}), + .bound => |func| @call(.auto, func, .{ self.ctx_ptr_address, param }), } } From 217b1a9e51d4388cbf59dbd25dc12092cc424bdd Mon Sep 17 00:00:00 2001 From: Lucas Date: Wed, 3 May 2023 11:47:56 -0500 Subject: [PATCH 133/146] Update to latest zig release --- zig-ecs/build.zig | 14 +++++++++----- zig-ecs/src/ecs/component_storage.zig | 4 ++-- zig-ecs/src/ecs/entity.zig | 6 +++--- zig-ecs/src/ecs/groups.zig | 11 +++++------ zig-ecs/src/ecs/registry.zig | 14 +++++++------- zig-ecs/src/ecs/sparse_set.zig | 13 +++++++------ zig-ecs/src/ecs/utils.zig | 6 +++--- zig-ecs/src/ecs/views.zig | 11 ++++------- zig-ecs/src/process/process.zig | 8 ++++---- zig-ecs/src/signals/sink.zig | 6 +++--- zig-ecs/tests/groups_test.zig | 2 +- 11 files changed, 48 insertions(+), 47 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 64fc154..6b41d74 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -9,7 +9,10 @@ pub fn build(b: *Builder) void { }); // use a different cache folder for macos arm builds - b.cache_root = if (builtin.os.tag == .macos and builtin.target.cpu.arch == .aarch64) "zig-arm-cache" else "zig-cache"; + b.cache_root = .{ + .handle = std.fs.cwd(), + .path = if (builtin.os.tag == .macos and builtin.target.cpu.arch == .aarch64) "zig-arm-cache" else "zig-cache", + }; const examples = [_][2][]const u8{ [_][]const u8{ "view_vs_group", "examples/view_vs_group.zig" }, @@ -17,7 +20,7 @@ pub fn build(b: *Builder) void { [_][]const u8{ "simple", "examples/simple.zig" }, }; - for (examples) |example, i| { + for (examples, 0..) |example, i| { const name = if (i == 0) "ecs" else example[0]; const source = example[1]; @@ -26,17 +29,18 @@ pub fn build(b: *Builder) void { .root_source_file = std.build.FileSource{ .path = source }, .optimize = optimize, }); - exe.setOutputDir(std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); + // exe.setOutputDir(std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); + exe.output_dirname_source = .{ .path = std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root.path.?, "bin" }) catch unreachable, .step = &exe.step }; exe.addModule("ecs", ecs_module); exe.linkLibC(); const docs = exe; docs.emit_docs = .emit; - const doc = b.step("docs", "Generate documentation"); + const doc = b.step(b.fmt("{s}-docs", .{name}), "Generate documentation"); doc.dependOn(&docs.step); - const run_cmd = exe.run(); + const run_cmd = b.addRunArtifact(exe); const exe_step = b.step(name, b.fmt("run {s}.zig", .{name})); exe_step.dependOn(&run_cmd.step); diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index c7e50fe..a681c42 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -326,7 +326,7 @@ test "iterate" { store.add(5, 66.45); store.add(7, 66.45); - for (store.data()) |entity, i| { + for (store.data(), 0..) |entity, i| { if (i == 0) { try std.testing.expectEqual(entity, 3); } @@ -392,7 +392,7 @@ test "sort empty component" { const asc_u32 = comptime std.sort.asc(u32); store.sort(u32, {}, asc_u32); - for (store.data()) |e, i| { + for (store.data(), 0..) |e, i| { try std.testing.expectEqual(@intCast(u32, i), e); } diff --git a/zig-ecs/src/ecs/entity.zig b/zig-ecs/src/ecs/entity.zig index e8847ec..a095a90 100644 --- a/zig-ecs/src/ecs/entity.zig +++ b/zig-ecs/src/ecs/entity.zig @@ -17,17 +17,17 @@ fn EntityTraitsDefinition(comptime EntityType: type, comptime IndexType: type, c std.debug.assert(std.meta.trait.isUnsignedInt(EntityType)); std.debug.assert(std.meta.trait.isUnsignedInt(IndexType)); std.debug.assert(std.meta.trait.isUnsignedInt(VersionType)); - + const sizeOfIndexType = @bitSizeOf(IndexType); const sizeOfVersionType = @bitSizeOf(VersionType); const entityShift = sizeOfIndexType; - + if (sizeOfIndexType + sizeOfVersionType != @bitSizeOf(EntityType)) @compileError("IndexType and VersionType must sum to EntityType's bit count"); const entityMask = std.math.maxInt(IndexType); const versionMask = std.math.maxInt(VersionType); - + return struct { entity_type: type = EntityType, index_type: type = IndexType, diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 9cd1ee2..ecb7a42 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -85,7 +85,7 @@ pub const OwningGroup = struct { const component_info = @typeInfo(Components).Struct; var component_ptrs: [component_info.fields.len][*]u8 = undefined; - inline for (component_info.fields) |field, i| { + inline for (component_info.fields, 0..) |field, i| { const storage = group.registry.assure(@typeInfo(field.type).Pointer.child); component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); } @@ -104,7 +104,7 @@ pub const OwningGroup = struct { // fill and return the struct var comps: Components = undefined; - inline for (@typeInfo(Components).Struct.fields) |field, i| { + inline for (@typeInfo(Components).Struct.fields, 0..) |field, i| { const typed_ptr = @ptrCast([*]@typeInfo(field.type).Pointer.child, @alignCast(@alignOf(@typeInfo(field.type).Pointer.child), it.component_ptrs[i])); @field(comps, field.name) = &typed_ptr[it.index]; } @@ -173,7 +173,7 @@ pub const OwningGroup = struct { const component_info = @typeInfo(Components).Struct; var component_ptrs: [component_info.fields.len][*]u8 = undefined; - inline for (component_info.fields) |field, i| { + inline for (component_info.fields, 0..) |field, i| { const storage = self.registry.assure(std.meta.Child(field.type)); component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); } @@ -181,7 +181,7 @@ pub const OwningGroup = struct { // fill the struct const index = self.firstOwnedStorage().set.index(entity); var comps: Components = undefined; - inline for (component_info.fields) |field, i| { + inline for (component_info.fields, 0..) |field, i| { const typed_ptr = @ptrCast([*]std.meta.Child(field.type), @alignCast(@alignOf(std.meta.Child(field.type)), component_ptrs[i])); @field(comps, field.name) = &typed_ptr[index]; } @@ -191,8 +191,7 @@ pub const OwningGroup = struct { pub fn each(self: OwningGroup, comptime func: anytype) void { const Components = switch (@typeInfo(@TypeOf(func))) { - .BoundFn => |func_info| func_info.args[1].arg_type.?, - .Fn => |func_info| func_info.args[0].arg_type.?, + .Fn => |func_info| func_info.params[0].type.?, else => std.debug.assert("invalid func"), }; self.validate(Components); diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 3fab6ff..3c0cf02 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -146,7 +146,7 @@ pub const Registry = struct { /// which ensures more specialized (ie less matches) will always be swapping inside the bounds of /// the less specialized groups. fn findInsertionIndex(self: GroupData, groups: []*GroupData) ?usize { - for (groups) |grp, i| { + for (groups, 0..) |grp, i| { var overlapping: u8 = 0; for (grp.owned) |grp_owned| { if (std.mem.indexOfScalar(u32, self.owned, grp_owned)) |_| overlapping += 1; @@ -443,13 +443,13 @@ pub const Registry = struct { return BasicView(includes[0]).init(self.assure(includes[0])); var includes_arr: [includes.len]u32 = undefined; - inline for (includes) |t, i| { + inline for (includes, 0..) |t, i| { _ = self.assure(t); includes_arr[i] = utils.typeId(t); } var excludes_arr: [excludes.len]u32 = undefined; - inline for (excludes) |t, i| { + inline for (excludes, 0..) |t, i| { _ = self.assure(t); excludes_arr[i] = utils.typeId(t); } @@ -486,19 +486,19 @@ pub const Registry = struct { // gather up all our Types as typeIds var includes_arr: [includes.len]u32 = undefined; - inline for (includes) |t, i| { + inline for (includes, 0..) |t, i| { _ = self.assure(t); includes_arr[i] = utils.typeId(t); } var excludes_arr: [excludes.len]u32 = undefined; - inline for (excludes) |t, i| { + inline for (excludes, 0..) |t, i| { _ = self.assure(t); excludes_arr[i] = utils.typeId(t); } var owned_arr: [owned.len]u32 = undefined; - inline for (owned) |t, i| { + inline for (owned, 0..) |t, i| { _ = self.assure(t); owned_arr[i] = utils.typeId(t); } @@ -628,7 +628,7 @@ pub const Registry = struct { }; var names: [types.len][]const u8 = undefined; - for (names) |*name, i| { + for (&names, 0..) |*name, i| { name.* = @typeName(types[i]); } diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 9d0bed7..25cc5ae 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -84,12 +84,13 @@ pub fn SparseSet(comptime SparseT: type) type { const start_pos = self.sparse.items.len; self.sparse.resize(pos + 1) catch unreachable; self.sparse.expandToCapacity(); - std.mem.set(?[]SparseT, self.sparse.items[start_pos..], null); + + @memset(self.sparse.items[start_pos..], null); } if (self.sparse.items[pos] == null) { var new_page = self.sparse.allocator.alloc(SparseT, page_size) catch unreachable; - std.mem.set(SparseT, new_page, std.math.maxInt(SparseT)); + @memset(new_page, std.math.maxInt(SparseT)); self.sparse.items[pos] = new_page; } @@ -173,7 +174,7 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn sort(self: *Self, context: anytype, comptime lessThan: *const fn (@TypeOf(context), SparseT, SparseT) bool) void { std_sort_insertionSort_clone(SparseT, self.dense.items, context, lessThan); - for (self.dense.items) |_, i| { + for (self.dense.items, 0..) |_, i| { const item = @intCast(SparseT, i); self.sparse.items[self.page(self.dense.items[self.page(item)])].?[self.offset(self.dense.items[self.page(item)])] = @intCast(SparseT, i); } @@ -184,7 +185,7 @@ pub fn SparseSet(comptime SparseT: type) type { pub fn arrange(self: *Self, length: usize, context: anytype, comptime lessThan: *const fn (@TypeOf(context), SparseT, SparseT) bool, swap_context: anytype) void { std_sort_insertionSort_clone(SparseT, self.dense.items[0..length], context, lessThan); - for (self.dense.items[0..length]) |_, pos| { + for (self.dense.items[0..length], 0..) |_, pos| { var curr = @intCast(SparseT, pos); var next = self.index(self.dense.items[curr]); @@ -213,7 +214,7 @@ pub fn SparseSet(comptime SparseT: type) type { } pub fn clear(self: *Self) void { - for (self.sparse.items) |array, i| { + for (self.sparse.items, 0..) |array, i| { if (array) |arr| { self.sparse.allocator.free(arr); self.sparse.items[i] = null; @@ -358,7 +359,7 @@ test "respect 2" { set.sort({}, desc_u32); - for (set.dense.items) |item, i| { + for (set.dense.items, 0..) |item, i| { if (i < set.dense.items.len - 1) { std.debug.assert(item > set.dense.items[i + 1]); } diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index d0429e6..1741b4d 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -81,12 +81,12 @@ pub fn sortSubSub(comptime T1: type, comptime T2: type, items: []T1, sub_items: /// comptime string hashing for the type names pub fn typeId(comptime T: type) u32 { - comptime return hashStringFnv(u32, @typeName(T)); + return hashStringFnv(u32, @typeName(T)); } /// comptime string hashing for the type names pub fn typeId64(comptime T: type) u64 { - comptime return hashStringFnv(u64, @typeName(T)); + return hashStringFnv(u64, @typeName(T)); } /// u32 Fowler-Noll-Vo string hash @@ -126,7 +126,7 @@ test "ReverseSliceIterator" { var slice = std.testing.allocator.alloc(usize, 10) catch unreachable; defer std.testing.allocator.free(slice); - for (slice) |*item, i| { + for (slice, 0..) |*item, i| { item.* = i; } diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index 7aa6c48..ec20505 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -69,10 +69,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { pub fn init(view: *Self) Iterator { const ptr = view.registry.components.get(view.type_ids[0]).?; const internal_it = @intToPtr(*Storage(u8), ptr).set.reverseIterator(); - return .{ - .view = view, - .internal_it = internal_it - }; + return .{ .view = view, .internal_it = internal_it }; } pub fn next(it: *Iterator) ?Entity { @@ -130,7 +127,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { fn sort(self: *Self) void { // get our component counts in an array so we can sort the type_ids based on how many entities are in each var sub_items: [n_includes]usize = undefined; - for (self.type_ids) |tid, i| { + for (self.type_ids, 0..) |tid, i| { const ptr = self.registry.components.get(tid).?; const store = @intToPtr(*Storage(u8), ptr); sub_items[i] = store.len(); @@ -201,7 +198,7 @@ test "single basic view data" { try std.testing.expectEqual(view.get(3).*, 30); - for (view.data()) |entity, i| { + for (view.data(), 0..) |entity, i| { if (i == 0) try std.testing.expectEqual(entity, 3); if (i == 1) @@ -210,7 +207,7 @@ test "single basic view data" { try std.testing.expectEqual(entity, 7); } - for (view.raw()) |data, i| { + for (view.raw(), 0..) |data, i| { if (i == 0) try std.testing.expectEqual(data, 30); if (i == 1) diff --git a/zig-ecs/src/process/process.zig b/zig-ecs/src/process/process.zig index 1c02254..117cd17 100644 --- a/zig-ecs/src/process/process.zig +++ b/zig-ecs/src/process/process.zig @@ -6,10 +6,10 @@ pub const Process = struct { const State = enum(u8) { uninitialized, running, paused, succeeded, failed, aborted, finished }; updateFn: *const fn (self: *Process) void, - startFn: ?*const fn (self: *Process) void = null, - abortedFn: ?*const fn (self: *Process) void = null, - failedFn: ?*const fn (self: *Process) void = null, - succeededFn: ?*const fn (self: *Process) void = null, + startFn: ?*const fn (self: *Process) void = null, + abortedFn: ?*const fn (self: *Process) void = null, + failedFn: ?*const fn (self: *Process) void = null, + succeededFn: ?*const fn (self: *Process) void = null, deinit: *const fn (self: *Process, allocator: std.mem.Allocator) void = undefined, state: State = .uninitialized, diff --git a/zig-ecs/src/signals/sink.zig b/zig-ecs/src/signals/sink.zig index 48f09f9..b0a2183 100644 --- a/zig-ecs/src/signals/sink.zig +++ b/zig-ecs/src/signals/sink.zig @@ -18,7 +18,7 @@ pub fn Sink(comptime Event: type) type { return Self{ .insert_index = owning_signal.calls.items.len }; } - pub fn before(self: Self, callback: ?*const fn (Event) void) Self { + pub fn before(self: Self, callback: ?*const fn (Event) void) Self { if (callback) |cb| { if (self.indexOf(cb)) |index| { return Self{ .insert_index = index }; @@ -59,7 +59,7 @@ pub fn Sink(comptime Event: type) type { } fn indexOf(_: Self, callback: *const fn (Event) void) ?usize { - for (owning_signal.calls.items) |call, i| { + for (owning_signal.calls.items, 0..) |call, i| { if (call.containsFree(callback)) { return i; } @@ -68,7 +68,7 @@ pub fn Sink(comptime Event: type) type { } fn indexOfBound(_: Self, ctx: anytype) ?usize { - for (owning_signal.calls.items) |call, i| { + for (owning_signal.calls.items, 0..) |call, i| { if (call.containsBound(ctx)) { return i; } diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index 644d2be..2706ba7 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -14,7 +14,7 @@ const Rotation = struct { x: f32 = 0 }; fn printStore(store: anytype, name: []const u8) void { std.debug.print("--- {} ---\n", .{name}); - for (store.set.dense.items) |e, i| { + for (store.set.dense.items, 0..) |e, i| { std.debug.print("e[{}] s[{}]{}", .{ e, store.set.page(store.set.dense.items[i]), store.set.sparse.items[store.set.page(store.set.dense.items[i])] }); std.debug.print(" ({d:.2}) ", .{store.instances.items[i]}); } From 1e917df772a6bee4c89e025f064188ceaec7fe9d Mon Sep 17 00:00:00 2001 From: Aron Gabriel Date: Fri, 23 Jun 2023 08:28:03 +0800 Subject: [PATCH 134/146] use `addModule` instead of `createModule` --- zig-ecs/build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 6b41d74..4fe1245 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -4,7 +4,7 @@ const builtin = @import("builtin"); pub fn build(b: *Builder) void { const optimize = b.standardOptimizeOption(.{}); - const ecs_module = b.createModule(.{ + const ecs_module = b.addModule("zig-ecs", .{ .source_file = std.build.FileSource{ .path = "src/ecs.zig" }, }); From e67ec88690dd0c0f324eecfc576b9e89ebaf6d9b Mon Sep 17 00:00:00 2001 From: Aron Gabriel Date: Sat, 24 Jun 2023 12:28:59 +0800 Subject: [PATCH 135/146] change @srcToDest to @destFromSrc e.g: `@intToFloat()` -> `@floatFromInt()` --- zig-ecs/src/ecs/groups.zig | 4 ++-- zig-ecs/src/ecs/registry.zig | 28 ++++++++++++++-------------- zig-ecs/src/ecs/utils.zig | 4 ++-- zig-ecs/src/ecs/views.zig | 10 +++++----- zig-ecs/src/resources/assets.zig | 6 +++--- zig-ecs/src/signals/delegate.zig | 10 +++++----- zig-ecs/src/signals/dispatcher.zig | 6 +++--- zig-ecs/tests/groups_test.zig | 26 +++++++++++++------------- zig-ecs/tests/registry_test.zig | 4 ++-- 9 files changed, 49 insertions(+), 49 deletions(-) diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index ecb7a42..139e2c6 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -138,7 +138,7 @@ pub const OwningGroup = struct { /// grabs an untyped (u1) reference to the first Storage(T) in the owned array fn firstOwnedStorage(self: OwningGroup) *Storage(u1) { const ptr = self.registry.components.get(self.group_data.owned[0]).?; - return @intToPtr(*Storage(u1), ptr); + return @ptrFromInt(*Storage(u1), ptr); } /// total number of entities in the group @@ -265,7 +265,7 @@ pub const OwningGroup = struct { // 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.get(type_id).?; - var storage = @intToPtr(*Storage(u1), other_ptr); + var storage = @ptrFromInt(*Storage(u1), other_ptr); storage.swap(storage.data()[pos], entity); } } diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 3c0cf02..de1ad52 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -83,19 +83,19 @@ pub const Registry = struct { const isValid: bool = blk: { for (self.owned) |tid| { const ptr = self.registry.components.get(tid).?; - if (!@intToPtr(*Storage(u1), ptr).contains(entity)) + if (!@ptrFromInt(*Storage(u1), ptr).contains(entity)) break :blk false; } for (self.include) |tid| { const ptr = self.registry.components.get(tid).?; - if (!@intToPtr(*Storage(u1), ptr).contains(entity)) + if (!@ptrFromInt(*Storage(u1), ptr).contains(entity)) break :blk false; } for (self.exclude) |tid| { const ptr = self.registry.components.get(tid).?; - if (@intToPtr(*Storage(u1), ptr).contains(entity)) + if (@ptrFromInt(*Storage(u1), ptr).contains(entity)) break :blk false; } break :blk true; @@ -108,11 +108,11 @@ pub const Registry = struct { } else { if (isValid) { const ptr = self.registry.components.get(self.owned[0]).?; - if (!(@intToPtr(*Storage(u1), ptr).set.index(entity) < self.current)) { + if (!(@ptrFromInt(*Storage(u1), ptr).set.index(entity) < self.current)) { for (self.owned) |tid| { // store.swap hides a safe version that types it correctly const store_ptr = self.registry.components.get(tid).?; - var store = @intToPtr(*Storage(u1), store_ptr); + var store = @ptrFromInt(*Storage(u1), store_ptr); store.swap(store.data()[self.current], entity); } self.current += 1; @@ -129,12 +129,12 @@ pub const Registry = struct { } } else { const ptr = self.registry.components.get(self.owned[0]).?; - var store = @intToPtr(*Storage(u1), ptr); + var store = @ptrFromInt(*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.get(tid).?; - store = @intToPtr(*Storage(u1), store_ptr); + store = @ptrFromInt(*Storage(u1), store_ptr); store.swap(store.data()[self.current], entity); } } @@ -199,7 +199,7 @@ pub const Registry = struct { var iter = self.components.valueIterator(); while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit - var storage = @intToPtr(*Storage(u1), ptr.*); + var storage = @ptrFromInt(*Storage(u1), ptr.*); storage.deinit(); } @@ -217,11 +217,11 @@ pub const Registry = struct { pub fn assure(self: *Registry, comptime T: type) *Storage(T) { var type_id = utils.typeId(T); if (self.components.getEntry(type_id)) |kv| { - return @intToPtr(*Storage(T), kv.value_ptr.*); + return @ptrFromInt(*Storage(T), kv.value_ptr.*); } var comp_set = Storage(T).initPtr(self.allocator); - var comp_set_ptr = @ptrToInt(comp_set); + var comp_set_ptr = @intFromPtr(comp_set); _ = self.components.put(type_id, comp_set_ptr) catch unreachable; return comp_set; } @@ -347,7 +347,7 @@ pub const Registry = struct { var iter = self.components.valueIterator(); while (iter.next()) |value| { // HACK: we dont know the Type here but we need to be able to call methods on the Storage(T) - var store = @intToPtr(*Storage(u1), value.*); + var store = @ptrFromInt(*Storage(u1), value.*); store.removeIfContains(entity); } } @@ -398,7 +398,7 @@ pub const Registry = struct { std.debug.assert(@typeInfo(@TypeOf(context)) == .Pointer); var type_id = utils.typeId(@typeInfo(@TypeOf(context)).Pointer.child); - _ = self.contexts.put(type_id, @ptrToInt(context)) catch unreachable; + _ = self.contexts.put(type_id, @intFromPtr(context)) catch unreachable; } /// Unsets a context variable if it exists @@ -412,7 +412,7 @@ pub const Registry = struct { std.debug.assert(@typeInfo(T) != .Pointer); return if (self.contexts.get(utils.typeId(T))) |ptr| - return if (ptr > 0) @intToPtr(*T, ptr) else null + return if (ptr > 0) @ptrFromInt(*T, ptr) else null else null; } @@ -632,7 +632,7 @@ pub const Registry = struct { name.* = @typeName(types[i]); } - std.sort.sort([]const u8, &names, {}, impl.asc); + std.sort.block([]const u8, &names, {}, impl.asc); comptime var res: []const u8 = ""; inline for (names) |name| res = res ++ name; diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index 1741b4d..ea0e30b 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -7,7 +7,7 @@ pub const ErasedPtr = struct { if (@sizeOf(@TypeOf(ptr)) == 0) { return .{ .ptr = undefined }; } - return .{ .ptr = @ptrToInt(ptr) }; + return .{ .ptr = @intFromPtr(ptr) }; } pub fn as(self: ErasedPtr, comptime T: type) *T { @@ -19,7 +19,7 @@ pub const ErasedPtr = struct { pub fn asPtr(self: ErasedPtr, comptime PtrT: type) PtrT { if (@sizeOf(PtrT) == 0) return @as(PtrT, undefined); - return @intToPtr(PtrT, self.ptr); + return @ptrFromInt(PtrT, self.ptr); } }; diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index ec20505..e662def 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -68,7 +68,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { pub fn init(view: *Self) Iterator { const ptr = view.registry.components.get(view.type_ids[0]).?; - const internal_it = @intToPtr(*Storage(u8), ptr).set.reverseIterator(); + const internal_it = @ptrFromInt(*Storage(u8), ptr).set.reverseIterator(); return .{ .view = view, .internal_it = internal_it }; } @@ -77,7 +77,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // entity must be in all other Storages for (it.view.type_ids) |tid| { const ptr = it.view.registry.components.get(tid).?; - if (!@intToPtr(*Storage(u1), ptr).contains(entity)) { + if (!@ptrFromInt(*Storage(u1), ptr).contains(entity)) { break :blk; } } @@ -85,7 +85,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // entity must not be in all other excluded Storages for (it.view.exclude_type_ids) |tid| { const ptr = it.view.registry.components.get(tid).?; - if (@intToPtr(*Storage(u1), ptr).contains(entity)) { + if (@ptrFromInt(*Storage(u1), ptr).contains(entity)) { break :blk; } } @@ -104,7 +104,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { fn getInternalIteratorInstance(it: *Iterator) ReverseSliceIterator(Entity) { const ptr = it.view.registry.components.get(it.view.type_ids[0]).?; - return @intToPtr(*Storage(u8), ptr).set.reverseIterator(); + return @ptrFromInt(*Storage(u8), ptr).set.reverseIterator(); } }; @@ -129,7 +129,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { var sub_items: [n_includes]usize = undefined; for (self.type_ids, 0..) |tid, i| { const ptr = self.registry.components.get(tid).?; - const store = @intToPtr(*Storage(u8), ptr); + const store = @ptrFromInt(*Storage(u8), ptr); sub_items[i] = store.len(); } diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index 6481783..ac1e08e 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -17,7 +17,7 @@ pub const Assets = struct { var iter = self.caches.iterator(); while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit - @intToPtr(*Cache(u1), ptr.value_ptr.*).deinit(); + @ptrFromInt(*Cache(u1), ptr.value_ptr.*).deinit(); } self.caches.deinit(); @@ -25,11 +25,11 @@ pub const Assets = struct { pub fn get(self: *Assets, comptime AssetT: type) *Cache(AssetT) { if (self.caches.get(utils.typeId(AssetT))) |tid| { - return @intToPtr(*Cache(AssetT), tid); + return @ptrFromInt(*Cache(AssetT), tid); } var cache = Cache(AssetT).initPtr(self.allocator); - _ = self.caches.put(utils.typeId(AssetT), @ptrToInt(cache)) catch unreachable; + _ = self.caches.put(utils.typeId(AssetT), @intFromPtr(cache)) catch unreachable; return cache; } diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index 3a79f9c..05549be 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -14,15 +14,15 @@ pub fn Delegate(comptime Event: type) type { /// sets a bound function as the Delegate callback pub fn initBound(ctx: anytype, comptime fn_name: []const u8) Self { std.debug.assert(@typeInfo(@TypeOf(ctx)) == .Pointer); - std.debug.assert(@ptrToInt(ctx) != 0); + std.debug.assert(@intFromPtr(ctx) != 0); const T = @TypeOf(ctx); return Self{ - .ctx_ptr_address = @ptrToInt(ctx), + .ctx_ptr_address = @intFromPtr(ctx), .callback = .{ .bound = struct { fn cb(self: usize, param: Event) void { - @call(.always_inline, @field(@intToPtr(T, self), fn_name), .{param}); + @call(.always_inline, @field(@ptrFromInt(T, self), fn_name), .{param}); } }.cb, }, @@ -51,11 +51,11 @@ pub fn Delegate(comptime Event: type) type { } pub fn containsBound(self: Self, ctx: anytype) bool { - std.debug.assert(@ptrToInt(ctx) != 0); + std.debug.assert(@intFromPtr(ctx) != 0); std.debug.assert(@typeInfo(@TypeOf(ctx)) == .Pointer); return switch (self.callback) { - .bound => @ptrToInt(ctx) == self.ctx_ptr_address, + .bound => @intFromPtr(ctx) == self.ctx_ptr_address, else => false, }; } diff --git a/zig-ecs/src/signals/dispatcher.zig b/zig-ecs/src/signals/dispatcher.zig index 265fff9..5a839c4 100644 --- a/zig-ecs/src/signals/dispatcher.zig +++ b/zig-ecs/src/signals/dispatcher.zig @@ -18,7 +18,7 @@ pub const Dispatcher = struct { var iter = self.signals.iterator(); while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit - var signal = @intToPtr(*Signal(void), ptr.value_ptr.*); + var signal = @ptrFromInt(*Signal(void), ptr.value_ptr.*); signal.deinit(); } @@ -28,11 +28,11 @@ pub const Dispatcher = struct { fn assure(self: *Dispatcher, comptime T: type) *Signal(T) { var type_id = utils.typeId(T); if (self.signals.get(type_id)) |value| { - return @intToPtr(*Signal(T), value); + return @ptrFromInt(*Signal(T), value); } var signal = Signal(T).create(self.allocator); - var signal_ptr = @ptrToInt(signal); + var signal_ptr = @intFromPtr(signal); _ = self.signals.put(type_id, signal_ptr) catch unreachable; return signal; } diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index 2706ba7..d5aa392 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -30,8 +30,8 @@ test "sort BasicGroup by Entity" { 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) }); + reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); + reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); } const SortContext = struct { @@ -64,8 +64,8 @@ test "sort BasicGroup by Component" { 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) }); + reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); + reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); } const SortContext = struct { @@ -92,8 +92,8 @@ test "sort OwningGroup by Entity" { 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) }); + reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); + reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); } const SortContext = struct { @@ -125,8 +125,8 @@ test "sort OwningGroup by Component" { 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) }); + reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); + reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); } const SortContext = struct { @@ -153,11 +153,11 @@ test "sort OwningGroup by Component ensure unsorted non-matches" { 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) }); + reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); + reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); var e2 = reg.create(); - reg.add(e2, Sprite{ .x = @intToFloat(f32, i + 1 * 50) }); + reg.add(e2, Sprite{ .x = @floatFromInt(f32, i + 1 * 50) }); } try std.testing.expectEqual(group.len(), 5); @@ -223,8 +223,8 @@ test "nested OwningGroups entity order" { 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) }); + reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); + reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); } try std.testing.expectEqual(group1.len(), 5); diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index 10d5cf1..3d443f0 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -97,7 +97,7 @@ test "destroy" { 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) }); + reg.add(e, Position{ .x = @floatFromInt(f32, i), .y = @floatFromInt(f32, i) }); } reg.destroy(3); @@ -106,7 +106,7 @@ test "destroy" { i = 0; while (i < 6) : (i += 1) { if (i != 3 and i != 4) - try std.testing.expectEqual(Position{ .x = @intToFloat(f32, i), .y = @intToFloat(f32, i) }, reg.getConst(Position, i)); + try std.testing.expectEqual(Position{ .x = @floatFromInt(f32, i), .y = @floatFromInt(f32, i) }, reg.getConst(Position, i)); } } From 5045a4fa3330892cb17e1e130dfffb97d5fdcb9c Mon Sep 17 00:00:00 2001 From: Aron Gabriel Date: Sat, 24 Jun 2023 13:57:57 +0800 Subject: [PATCH 136/146] use `@max` instead of `std.math.max` --- zig-ecs/src/ecs/registry.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index de1ad52..7d595fa 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -553,7 +553,7 @@ pub const Registry = struct { // update super on all owned Storages to be the max of size and their current super value inline for (owned) |t| { var storage = self.assure(t); - storage.super = std.math.max(storage.super, new_group_data.size); + storage.super = @max(storage.super, new_group_data.size); } } From 13ea2be1c13a3d8f5a0c5fbe16ca3cc1f5afab5e Mon Sep 17 00:00:00 2001 From: Aron Gabriel Date: Sat, 24 Jun 2023 14:07:03 +0800 Subject: [PATCH 137/146] Fix `@call` usage errors and duplicate test names --- zig-ecs/src/resources/cache.zig | 4 ++-- zig-ecs/src/signals/delegate.zig | 3 ++- zig-ecs/tests/registry_test.zig | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/zig-ecs/src/resources/cache.zig b/zig-ecs/src/resources/cache.zig index e4d9a3f..9ada0ad 100644 --- a/zig-ecs/src/resources/cache.zig +++ b/zig-ecs/src/resources/cache.zig @@ -59,7 +59,7 @@ pub fn Cache(comptime T: type) type { pub fn remove(self: *@This(), id: u32) void { if (self.resources.fetchRemove(id)) |kv| { if (@hasDecl(T, "deinit")) { - @call(.always_inline, @field(kv.value, "deinit"), .{}); + @call(.always_inline, T.deinit, .{kv.value}); } } } @@ -69,7 +69,7 @@ pub fn Cache(comptime T: type) type { if (@hasDecl(T, "deinit")) { var iter = self.resources.iterator(); while (iter.next()) |kv| { - @call(.always_inline, @field(kv.value_ptr.*, "deinit"), .{}); + @call(.always_inline, T.deinit, .{kv.value_ptr.*}); } } self.resources.clearAndFree(); diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index 05549be..f69d879 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -17,12 +17,13 @@ pub fn Delegate(comptime Event: type) type { std.debug.assert(@intFromPtr(ctx) != 0); const T = @TypeOf(ctx); + const BaseT = @typeInfo(T).Pointer.child; return Self{ .ctx_ptr_address = @intFromPtr(ctx), .callback = .{ .bound = struct { fn cb(self: usize, param: Event) void { - @call(.always_inline, @field(@ptrFromInt(T, self), fn_name), .{param}); + @call(.always_inline, @field(BaseT, fn_name), .{ @ptrFromInt(T, self), param }); } }.cb, }, diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index 3d443f0..8f1553b 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -58,7 +58,7 @@ test "context not pointer" { // reg.setContext(pos); } -test "context get/set/unset" { +test "context get/set/unset typed" { const SomeType = struct { dummy: u1 }; var reg = Registry.init(std.testing.allocator); From c7a13538476b2a421b0d9f79c19e35f09a4b29d8 Mon Sep 17 00:00:00 2001 From: Aron Gabriel Date: Sun, 25 Jun 2023 19:56:11 +0800 Subject: [PATCH 138/146] use `std.sort.pdq()` instead of `std.sort.block()` --- zig-ecs/src/ecs/registry.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 7d595fa..39c7df7 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -632,7 +632,7 @@ pub const Registry = struct { name.* = @typeName(types[i]); } - std.sort.block([]const u8, &names, {}, impl.asc); + std.sort.pdq([]const u8, &names, {}, impl.asc); comptime var res: []const u8 = ""; inline for (names) |name| res = res ++ name; From a4d2e17f601502bc34980c0725a2677d3ecdc885 Mon Sep 17 00:00:00 2001 From: menduz Date: Wed, 28 Jun 2023 19:40:58 -0300 Subject: [PATCH 139/146] fix: add missing comptime --- zig-ecs/src/ecs/registry.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 39c7df7..143e832 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -322,7 +322,7 @@ pub const Registry = struct { } /// shortcut for add-or-replace raw comptime_int/float without having to @as cast - pub fn addOrReplaceTyped(self: *Registry, T: type, entity: Entity, value: T) void { + pub fn addOrReplaceTyped(self: *Registry, comptime T: type, entity: Entity, value: T) void { self.addOrReplace(entity, value); } From da441c336387aa6f4030ca02e818588730713e64 Mon Sep 17 00:00:00 2001 From: prime31 Date: Thu, 29 Jun 2023 12:01:57 -0700 Subject: [PATCH 140/146] Delete .github/workflows directory --- zig-ecs/.github/workflows/ci.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 zig-ecs/.github/workflows/ci.yml diff --git a/zig-ecs/.github/workflows/ci.yml b/zig-ecs/.github/workflows/ci.yml deleted file mode 100644 index 5043895..0000000 --- a/zig-ecs/.github/workflows/ci.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Workflow -on: [push] - -jobs: - test: - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{matrix.os}} - steps: - - uses: actions/checkout@v2 - - uses: goto-bus-stop/setup-zig@v1 - with: - version: master - - run: zig fmt src - - run: zig fmt examples - - run: zig fmt tests - - run: zig fmt build.zig - - run: zig build test From 13852c2e08ff4eb8f5a98b15ec85b017bda8822e Mon Sep 17 00:00:00 2001 From: menduz Date: Sun, 2 Jul 2023 21:31:31 -0300 Subject: [PATCH 141/146] zig fmt + expose SparseSet --- zig-ecs/examples/group_sort.zig | 6 +++--- zig-ecs/examples/view_vs_group.zig | 16 +++++++-------- zig-ecs/src/ecs.zig | 1 + zig-ecs/src/ecs/component_storage.zig | 2 +- zig-ecs/src/ecs/groups.zig | 12 ++++++------ zig-ecs/src/ecs/handles.zig | 4 ++-- zig-ecs/src/ecs/registry.zig | 28 +++++++++++++-------------- zig-ecs/src/ecs/sparse_set.zig | 12 ++++++------ zig-ecs/src/ecs/type_store.zig | 2 +- zig-ecs/src/ecs/utils.zig | 6 +++--- zig-ecs/src/ecs/views.zig | 10 +++++----- zig-ecs/src/resources/assets.zig | 4 ++-- zig-ecs/src/signals/delegate.zig | 2 +- zig-ecs/src/signals/dispatcher.zig | 4 ++-- zig-ecs/tests/groups_test.zig | 26 ++++++++++++------------- zig-ecs/tests/registry_test.zig | 4 ++-- 16 files changed, 70 insertions(+), 69 deletions(-) diff --git a/zig-ecs/examples/group_sort.zig b/zig-ecs/examples/group_sort.zig index 16cdeb5..f02a900 100644 --- a/zig-ecs/examples/group_sort.zig +++ b/zig-ecs/examples/group_sort.zig @@ -30,7 +30,7 @@ fn createEntities(reg: *ecs.Registry) void { } var end = timer.lap(); - std.debug.print("create {d} entities: {d}\n", .{ total_entities, @intToFloat(f64, end) / 1000000000 }); + std.debug.print("create {d} entities: {d}\n", .{ total_entities, @as(f64, @floatFromInt(end)) / 1000000000 }); } fn owningGroup(reg: *ecs.Registry) void { @@ -50,12 +50,12 @@ fn owningGroup(reg: *ecs.Registry) void { var timer = std.time.Timer.start() catch unreachable; group.sort(Position, {}, SortContext.sort); var end = timer.lap(); - std.debug.print("group (sort): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (sort): {d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); timer.reset(); group.sort(Position, {}, SortContext.sort); end = timer.lap(); - std.debug.print("group (sort 2): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (sort 2): {d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); // var group_iter2 = group.iterator(struct { vel: *Velocity, pos: *Position }); // while (group_iter2.next()) |e| { diff --git a/zig-ecs/examples/view_vs_group.zig b/zig-ecs/examples/view_vs_group.zig index d76f48f..cd2e0be 100644 --- a/zig-ecs/examples/view_vs_group.zig +++ b/zig-ecs/examples/view_vs_group.zig @@ -30,7 +30,7 @@ fn createEntities(reg: *ecs.Registry) void { } var end = timer.lap(); - std.debug.print("create entities: \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("create entities: \t{d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); } fn iterateView(reg: *ecs.Registry) void { @@ -48,7 +48,7 @@ fn iterateView(reg: *ecs.Registry) void { } var end = timer.lap(); - std.debug.print("view (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("view (iter): \t{d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); } fn nonOwningGroup(reg: *ecs.Registry) void { @@ -56,7 +56,7 @@ fn nonOwningGroup(reg: *ecs.Registry) void { var timer = std.time.Timer.start() catch unreachable; var group = reg.group(.{}, .{ Velocity, Position }, .{}); var end = timer.lap(); - std.debug.print("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (create): {d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); timer.reset(); var group_iter = group.iterator(); @@ -69,7 +69,7 @@ fn nonOwningGroup(reg: *ecs.Registry) void { } end = timer.lap(); - std.debug.print("group (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (iter): \t{d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); } fn owningGroup(reg: *ecs.Registry) void { @@ -77,7 +77,7 @@ fn owningGroup(reg: *ecs.Registry) void { var timer = std.time.Timer.start() catch unreachable; var group = reg.group(.{ Velocity, Position }, .{}, .{}); var end = timer.lap(); - std.debug.print("group (create): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (create): {d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); timer.reset(); var group_iter = group.iterator(struct { vel: *Velocity, pos: *Position }); @@ -87,12 +87,12 @@ fn owningGroup(reg: *ecs.Registry) void { } end = timer.lap(); - std.debug.print("group (iter): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (iter): \t{d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); timer.reset(); group.each(each); end = timer.lap(); - std.debug.print("group (each): \t{d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (each): \t{d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); timer.reset(); @@ -110,7 +110,7 @@ fn owningGroup(reg: *ecs.Registry) void { } end = timer.lap(); - std.debug.print("group (direct): {d}\n", .{@intToFloat(f64, end) / 1000000000}); + std.debug.print("group (direct): {d}\n", .{@as(f64, @floatFromInt(end)) / 1000000000}); } fn each(e: struct { vel: *Velocity, pos: *Position }) void { diff --git a/zig-ecs/src/ecs.zig b/zig-ecs/src/ecs.zig index 04ab446..e8ac5aa 100644 --- a/zig-ecs/src/ecs.zig +++ b/zig-ecs/src/ecs.zig @@ -10,6 +10,7 @@ pub const BasicView = @import("ecs/views.zig").BasicView; pub const BasicMultiView = @import("ecs/views.zig").BasicMultiView; pub const BasicGroup = @import("ecs/groups.zig").BasicGroup; pub const OwningGroup = @import("ecs/groups.zig").OwningGroup; +pub const SparseSet = @import("ecs/sparse_set.zig").SparseSet; // signals pub const Signal = @import("signals/signal.zig").Signal; diff --git a/zig-ecs/src/ecs/component_storage.zig b/zig-ecs/src/ecs/component_storage.zig index a681c42..3d36df4 100644 --- a/zig-ecs/src/ecs/component_storage.zig +++ b/zig-ecs/src/ecs/component_storage.zig @@ -393,7 +393,7 @@ test "sort empty component" { const asc_u32 = comptime std.sort.asc(u32); store.sort(u32, {}, asc_u32); for (store.data(), 0..) |e, i| { - try std.testing.expectEqual(@intCast(u32, i), e); + try std.testing.expectEqual(@as(u32, @intCast(i)), e); } const desc_u32 = comptime std.sort.desc(u32); diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index 139e2c6..6ec97c8 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -87,7 +87,7 @@ pub const OwningGroup = struct { var component_ptrs: [component_info.fields.len][*]u8 = undefined; inline for (component_info.fields, 0..) |field, i| { const storage = group.registry.assure(@typeInfo(field.type).Pointer.child); - component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); + component_ptrs[i] = @as([*]u8, @ptrCast(storage.instances.items.ptr)); } return .{ @@ -105,7 +105,7 @@ pub const OwningGroup = struct { // fill and return the struct var comps: Components = undefined; inline for (@typeInfo(Components).Struct.fields, 0..) |field, i| { - const typed_ptr = @ptrCast([*]@typeInfo(field.type).Pointer.child, @alignCast(@alignOf(@typeInfo(field.type).Pointer.child), it.component_ptrs[i])); + const typed_ptr = @as([*]@typeInfo(field.type).Pointer.child, @ptrCast(@alignCast(it.component_ptrs[i]))); @field(comps, field.name) = &typed_ptr[it.index]; } return comps; @@ -138,7 +138,7 @@ pub const OwningGroup = struct { /// grabs an untyped (u1) reference to the first Storage(T) in the owned array fn firstOwnedStorage(self: OwningGroup) *Storage(u1) { const ptr = self.registry.components.get(self.group_data.owned[0]).?; - return @ptrFromInt(*Storage(u1), ptr); + return @as(*Storage(u1), @ptrFromInt(ptr)); } /// total number of entities in the group @@ -175,14 +175,14 @@ pub const OwningGroup = struct { var component_ptrs: [component_info.fields.len][*]u8 = undefined; inline for (component_info.fields, 0..) |field, i| { const storage = self.registry.assure(std.meta.Child(field.type)); - component_ptrs[i] = @ptrCast([*]u8, storage.instances.items.ptr); + component_ptrs[i] = @as([*]u8, @ptrCast(storage.instances.items.ptr)); } // fill the struct const index = self.firstOwnedStorage().set.index(entity); var comps: Components = undefined; inline for (component_info.fields, 0..) |field, i| { - const typed_ptr = @ptrCast([*]std.meta.Child(field.type), @alignCast(@alignOf(std.meta.Child(field.type)), component_ptrs[i])); + const typed_ptr = @as([*]std.meta.Child(field.type), @ptrCast(@alignCast(component_ptrs[i]))); @field(comps, field.name) = &typed_ptr[index]; } @@ -265,7 +265,7 @@ pub const OwningGroup = struct { // 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.get(type_id).?; - var storage = @ptrFromInt(*Storage(u1), other_ptr); + var storage = @as(*Storage(u1), @ptrFromInt(other_ptr)); storage.swap(storage.data()[pos], entity); } } diff --git a/zig-ecs/src/ecs/handles.zig b/zig-ecs/src/ecs/handles.zig index 1039b5c..9a614ce 100644 --- a/zig-ecs/src/ecs/handles.zig +++ b/zig-ecs/src/ecs/handles.zig @@ -59,11 +59,11 @@ pub fn Handles(comptime HandleType: type, comptime IndexType: type, comptime Ver } pub fn extractId(_: Self, handle: HandleType) IndexType { - return @truncate(IndexType, handle & registry.entity_traits.entity_mask); + return @as(IndexType, @truncate(handle & registry.entity_traits.entity_mask)); } pub fn extractVersion(_: Self, handle: HandleType) VersionType { - return @truncate(VersionType, handle >> registry.entity_traits.entity_shift); + return @as(VersionType, @truncate(handle >> registry.entity_traits.entity_shift)); } fn forge(id: IndexType, version: VersionType) HandleType { diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 39c7df7..17711d3 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -55,7 +55,7 @@ pub const Registry = struct { // std.debug.assert(std.mem.indexOfAny(u32, include, exclude) == null); var group_data = allocator.create(GroupData) catch unreachable; group_data.hash = hash; - group_data.size = @intCast(u8, owned.len + include.len + exclude.len); + group_data.size = @as(u8, @intCast(owned.len + include.len + exclude.len)); if (owned.len == 0) { group_data.entity_set = SparseSet(Entity).init(allocator); } @@ -83,19 +83,19 @@ pub const Registry = struct { const isValid: bool = blk: { for (self.owned) |tid| { const ptr = self.registry.components.get(tid).?; - if (!@ptrFromInt(*Storage(u1), ptr).contains(entity)) + if (!@as(*Storage(u1), @ptrFromInt(ptr)).contains(entity)) break :blk false; } for (self.include) |tid| { const ptr = self.registry.components.get(tid).?; - if (!@ptrFromInt(*Storage(u1), ptr).contains(entity)) + if (!@as(*Storage(u1), @ptrFromInt(ptr)).contains(entity)) break :blk false; } for (self.exclude) |tid| { const ptr = self.registry.components.get(tid).?; - if (@ptrFromInt(*Storage(u1), ptr).contains(entity)) + if (@as(*Storage(u1), @ptrFromInt(ptr)).contains(entity)) break :blk false; } break :blk true; @@ -108,11 +108,11 @@ pub const Registry = struct { } else { if (isValid) { const ptr = self.registry.components.get(self.owned[0]).?; - if (!(@ptrFromInt(*Storage(u1), ptr).set.index(entity) < self.current)) { + if (!(@as(*Storage(u1), @ptrFromInt(ptr)).set.index(entity) < self.current)) { for (self.owned) |tid| { // store.swap hides a safe version that types it correctly const store_ptr = self.registry.components.get(tid).?; - var store = @ptrFromInt(*Storage(u1), store_ptr); + var store = @as(*Storage(u1), @ptrFromInt(store_ptr)); store.swap(store.data()[self.current], entity); } self.current += 1; @@ -129,12 +129,12 @@ pub const Registry = struct { } } else { const ptr = self.registry.components.get(self.owned[0]).?; - var store = @ptrFromInt(*Storage(u1), ptr); + var store = @as(*Storage(u1), @ptrFromInt(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.get(tid).?; - store = @ptrFromInt(*Storage(u1), store_ptr); + store = @as(*Storage(u1), @ptrFromInt(store_ptr)); store.swap(store.data()[self.current], entity); } } @@ -199,7 +199,7 @@ pub const Registry = struct { var iter = self.components.valueIterator(); while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit - var storage = @ptrFromInt(*Storage(u1), ptr.*); + var storage = @as(*Storage(u1), @ptrFromInt(ptr.*)); storage.deinit(); } @@ -217,7 +217,7 @@ pub const Registry = struct { pub fn assure(self: *Registry, comptime T: type) *Storage(T) { var type_id = utils.typeId(T); if (self.components.getEntry(type_id)) |kv| { - return @ptrFromInt(*Storage(T), kv.value_ptr.*); + return @as(*Storage(T), @ptrFromInt(kv.value_ptr.*)); } var comp_set = Storage(T).initPtr(self.allocator); @@ -262,7 +262,7 @@ pub const Registry = struct { /// Returns the version stored along with an entity identifier pub fn version(_: *Registry, entity: Entity) entity_traits.version_type { - return @truncate(entity_traits.version_type, entity >> entity_traits.entity_shift); + return @as(entity_traits.version_type, @truncate(entity >> entity_traits.entity_shift)); } /// Creates a new entity and returns it @@ -322,7 +322,7 @@ pub const Registry = struct { } /// shortcut for add-or-replace raw comptime_int/float without having to @as cast - pub fn addOrReplaceTyped(self: *Registry, T: type, entity: Entity, value: T) void { + pub fn addOrReplaceTyped(self: *Registry, comptime T: type, entity: Entity, value: T) void { self.addOrReplace(entity, value); } @@ -347,7 +347,7 @@ pub const Registry = struct { var iter = self.components.valueIterator(); while (iter.next()) |value| { // HACK: we dont know the Type here but we need to be able to call methods on the Storage(T) - var store = @ptrFromInt(*Storage(u1), value.*); + var store = @as(*Storage(u1), @ptrFromInt(value.*)); store.removeIfContains(entity); } } @@ -412,7 +412,7 @@ pub const Registry = struct { std.debug.assert(@typeInfo(T) != .Pointer); return if (self.contexts.get(utils.typeId(T))) |ptr| - return if (ptr > 0) @ptrFromInt(*T, ptr) else null + return if (ptr > 0) @as(*T, @ptrFromInt(ptr)) else null else null; } diff --git a/zig-ecs/src/ecs/sparse_set.zig b/zig-ecs/src/ecs/sparse_set.zig index 25cc5ae..3e79e35 100644 --- a/zig-ecs/src/ecs/sparse_set.zig +++ b/zig-ecs/src/ecs/sparse_set.zig @@ -142,7 +142,7 @@ pub fn SparseSet(comptime SparseT: type) type { std.debug.assert(!self.contains(sparse)); // assure(page(entt))[offset(entt)] = packed.size() - self.assure(self.page(sparse))[self.offset(sparse)] = @intCast(SparseT, self.dense.items.len); + self.assure(self.page(sparse))[self.offset(sparse)] = @as(SparseT, @intCast(self.dense.items.len)); _ = self.dense.append(sparse) catch unreachable; } @@ -175,8 +175,8 @@ pub fn SparseSet(comptime SparseT: type) type { std_sort_insertionSort_clone(SparseT, self.dense.items, context, lessThan); for (self.dense.items, 0..) |_, i| { - const item = @intCast(SparseT, i); - self.sparse.items[self.page(self.dense.items[self.page(item)])].?[self.offset(self.dense.items[self.page(item)])] = @intCast(SparseT, i); + const item = @as(SparseT, @intCast(i)); + self.sparse.items[self.page(self.dense.items[self.page(item)])].?[self.offset(self.dense.items[self.page(item)])] = @as(SparseT, @intCast(i)); } } @@ -186,7 +186,7 @@ pub fn SparseSet(comptime SparseT: type) type { std_sort_insertionSort_clone(SparseT, self.dense.items[0..length], context, lessThan); for (self.dense.items[0..length], 0..) |_, pos| { - var curr = @intCast(SparseT, pos); + var curr = @as(SparseT, @intCast(pos)); var next = self.index(self.dense.items[curr]); while (curr != next) { @@ -267,7 +267,7 @@ test "grow" { var i = @as(usize, std.math.maxInt(u8)); while (i > 0) : (i -= 1) { - set.add(@intCast(u32, i)); + set.add(@as(u32, @intCast(i))); } try std.testing.expectEqual(set.len(), std.math.maxInt(u8)); @@ -314,7 +314,7 @@ test "iterate" { set.add(2); set.add(3); - var i: u32 = @intCast(u32, set.len()) - 1; + var i: u32 = @as(u32, @intCast(set.len())) - 1; var iter = set.reverseIterator(); while (iter.next()) |entity| { try std.testing.expectEqual(i, entity); diff --git a/zig-ecs/src/ecs/type_store.zig b/zig-ecs/src/ecs/type_store.zig index 96b801c..1adb80b 100644 --- a/zig-ecs/src/ecs/type_store.zig +++ b/zig-ecs/src/ecs/type_store.zig @@ -30,7 +30,7 @@ pub const TypeStore = struct { pub fn get(self: *TypeStore, comptime T: type) *T { if (self.map.get(utils.typeId(T))) |bytes| { - return @ptrCast(*T, @alignCast(@alignOf(T), bytes)); + return @as(*T, @ptrCast(@alignCast(bytes))); } unreachable; } diff --git a/zig-ecs/src/ecs/utils.zig b/zig-ecs/src/ecs/utils.zig index ea0e30b..03c480e 100644 --- a/zig-ecs/src/ecs/utils.zig +++ b/zig-ecs/src/ecs/utils.zig @@ -19,7 +19,7 @@ pub const ErasedPtr = struct { pub fn asPtr(self: ErasedPtr, comptime PtrT: type) PtrT { if (@sizeOf(PtrT) == 0) return @as(PtrT, undefined); - return @ptrFromInt(PtrT, self.ptr); + return @as(PtrT, @ptrFromInt(self.ptr)); } }; @@ -101,7 +101,7 @@ pub fn hashStringFnv(comptime ReturnType: type, comptime str: []const u8) Return const prime = if (ReturnType == u32) @as(u32, 16777619) else @as(u64, 1099511628211); var value = if (ReturnType == u32) @as(u32, 2166136261) else @as(u64, 14695981039346656037); for (str) |c| { - value = (value ^ @intCast(u32, c)) *% prime; + value = (value ^ @as(u32, @intCast(c))) *% prime; } return value; } @@ -110,7 +110,7 @@ pub fn hashStringFnv(comptime ReturnType: type, comptime str: []const u8) Return pub fn hashStringDjb2(comptime str: []const u8) comptime_int { var hash: comptime_int = 5381; for (str) |c| { - hash = ((hash << 5) + hash) + @intCast(comptime_int, c); + hash = ((hash << 5) + hash) + @as(comptime_int, @intCast(c)); } return hash; } diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index e662def..1afe904 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -68,7 +68,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { pub fn init(view: *Self) Iterator { const ptr = view.registry.components.get(view.type_ids[0]).?; - const internal_it = @ptrFromInt(*Storage(u8), ptr).set.reverseIterator(); + const internal_it = @as(*Storage(u8), @ptrFromInt(ptr)).set.reverseIterator(); return .{ .view = view, .internal_it = internal_it }; } @@ -77,7 +77,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // entity must be in all other Storages for (it.view.type_ids) |tid| { const ptr = it.view.registry.components.get(tid).?; - if (!@ptrFromInt(*Storage(u1), ptr).contains(entity)) { + if (!@as(*Storage(u1), @ptrFromInt(ptr)).contains(entity)) { break :blk; } } @@ -85,7 +85,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { // entity must not be in all other excluded Storages for (it.view.exclude_type_ids) |tid| { const ptr = it.view.registry.components.get(tid).?; - if (@ptrFromInt(*Storage(u1), ptr).contains(entity)) { + if (@as(*Storage(u1), @ptrFromInt(ptr)).contains(entity)) { break :blk; } } @@ -104,7 +104,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { fn getInternalIteratorInstance(it: *Iterator) ReverseSliceIterator(Entity) { const ptr = it.view.registry.components.get(it.view.type_ids[0]).?; - return @ptrFromInt(*Storage(u8), ptr).set.reverseIterator(); + return @as(*Storage(u8), @ptrFromInt(ptr)).set.reverseIterator(); } }; @@ -129,7 +129,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { var sub_items: [n_includes]usize = undefined; for (self.type_ids, 0..) |tid, i| { const ptr = self.registry.components.get(tid).?; - const store = @ptrFromInt(*Storage(u8), ptr); + const store = @as(*Storage(u8), @ptrFromInt(ptr)); sub_items[i] = store.len(); } diff --git a/zig-ecs/src/resources/assets.zig b/zig-ecs/src/resources/assets.zig index ac1e08e..a36e602 100644 --- a/zig-ecs/src/resources/assets.zig +++ b/zig-ecs/src/resources/assets.zig @@ -17,7 +17,7 @@ pub const Assets = struct { var iter = self.caches.iterator(); while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit - @ptrFromInt(*Cache(u1), ptr.value_ptr.*).deinit(); + @as(*Cache(u1), @ptrFromInt(ptr.value_ptr.*)).deinit(); } self.caches.deinit(); @@ -25,7 +25,7 @@ pub const Assets = struct { pub fn get(self: *Assets, comptime AssetT: type) *Cache(AssetT) { if (self.caches.get(utils.typeId(AssetT))) |tid| { - return @ptrFromInt(*Cache(AssetT), tid); + return @as(*Cache(AssetT), @ptrFromInt(tid)); } var cache = Cache(AssetT).initPtr(self.allocator); diff --git a/zig-ecs/src/signals/delegate.zig b/zig-ecs/src/signals/delegate.zig index f69d879..f9f463b 100644 --- a/zig-ecs/src/signals/delegate.zig +++ b/zig-ecs/src/signals/delegate.zig @@ -23,7 +23,7 @@ pub fn Delegate(comptime Event: type) type { .callback = .{ .bound = struct { fn cb(self: usize, param: Event) void { - @call(.always_inline, @field(BaseT, fn_name), .{ @ptrFromInt(T, self), param }); + @call(.always_inline, @field(BaseT, fn_name), .{ @as(T, @ptrFromInt(self)), param }); } }.cb, }, diff --git a/zig-ecs/src/signals/dispatcher.zig b/zig-ecs/src/signals/dispatcher.zig index 5a839c4..c950680 100644 --- a/zig-ecs/src/signals/dispatcher.zig +++ b/zig-ecs/src/signals/dispatcher.zig @@ -18,7 +18,7 @@ pub const Dispatcher = struct { var iter = self.signals.iterator(); while (iter.next()) |ptr| { // HACK: we dont know the Type here but we need to call deinit - var signal = @ptrFromInt(*Signal(void), ptr.value_ptr.*); + var signal = @as(*Signal(void), @ptrFromInt(ptr.value_ptr.*)); signal.deinit(); } @@ -28,7 +28,7 @@ pub const Dispatcher = struct { fn assure(self: *Dispatcher, comptime T: type) *Signal(T) { var type_id = utils.typeId(T); if (self.signals.get(type_id)) |value| { - return @ptrFromInt(*Signal(T), value); + return @as(*Signal(T), @ptrFromInt(value)); } var signal = Signal(T).create(self.allocator); diff --git a/zig-ecs/tests/groups_test.zig b/zig-ecs/tests/groups_test.zig index d5aa392..f7cdf7d 100644 --- a/zig-ecs/tests/groups_test.zig +++ b/zig-ecs/tests/groups_test.zig @@ -30,8 +30,8 @@ test "sort BasicGroup by Entity" { var i: usize = 0; while (i < 5) : (i += 1) { var e = reg.create(); - reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); - reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); + reg.add(e, Sprite{ .x = @as(f32, @floatFromInt(i)) }); + reg.add(e, Renderable{ .x = @as(f32, @floatFromInt(i)) }); } const SortContext = struct { @@ -64,8 +64,8 @@ test "sort BasicGroup by Component" { var i: usize = 0; while (i < 5) : (i += 1) { var e = reg.create(); - reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); - reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); + reg.add(e, Sprite{ .x = @as(f32, @floatFromInt(i)) }); + reg.add(e, Renderable{ .x = @as(f32, @floatFromInt(i)) }); } const SortContext = struct { @@ -92,8 +92,8 @@ test "sort OwningGroup by Entity" { var i: usize = 0; while (i < 5) : (i += 1) { var e = reg.create(); - reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); - reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); + reg.add(e, Sprite{ .x = @as(f32, @floatFromInt(i)) }); + reg.add(e, Renderable{ .x = @as(f32, @floatFromInt(i)) }); } const SortContext = struct { @@ -125,8 +125,8 @@ test "sort OwningGroup by Component" { var i: usize = 0; while (i < 5) : (i += 1) { var e = reg.create(); - reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); - reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); + reg.add(e, Sprite{ .x = @as(f32, @floatFromInt(i)) }); + reg.add(e, Renderable{ .x = @as(f32, @floatFromInt(i)) }); } const SortContext = struct { @@ -153,11 +153,11 @@ test "sort OwningGroup by Component ensure unsorted non-matches" { var i: usize = 0; while (i < 5) : (i += 1) { var e = reg.create(); - reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); - reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); + reg.add(e, Sprite{ .x = @as(f32, @floatFromInt(i)) }); + reg.add(e, Renderable{ .x = @as(f32, @floatFromInt(i)) }); var e2 = reg.create(); - reg.add(e2, Sprite{ .x = @floatFromInt(f32, i + 1 * 50) }); + reg.add(e2, Sprite{ .x = @as(f32, @floatFromInt(i + 1 * 50)) }); } try std.testing.expectEqual(group.len(), 5); @@ -223,8 +223,8 @@ test "nested OwningGroups entity order" { var i: usize = 0; while (i < 5) : (i += 1) { var e = reg.create(); - reg.add(e, Sprite{ .x = @floatFromInt(f32, i) }); - reg.add(e, Renderable{ .x = @floatFromInt(f32, i) }); + reg.add(e, Sprite{ .x = @as(f32, @floatFromInt(i)) }); + reg.add(e, Renderable{ .x = @as(f32, @floatFromInt(i)) }); } try std.testing.expectEqual(group1.len(), 5); diff --git a/zig-ecs/tests/registry_test.zig b/zig-ecs/tests/registry_test.zig index 8f1553b..407d8e6 100644 --- a/zig-ecs/tests/registry_test.zig +++ b/zig-ecs/tests/registry_test.zig @@ -97,7 +97,7 @@ test "destroy" { var i = @as(u8, 0); while (i < 255) : (i += 1) { const e = reg.create(); - reg.add(e, Position{ .x = @floatFromInt(f32, i), .y = @floatFromInt(f32, i) }); + reg.add(e, Position{ .x = @as(f32, @floatFromInt(i)), .y = @as(f32, @floatFromInt(i)) }); } reg.destroy(3); @@ -106,7 +106,7 @@ test "destroy" { i = 0; while (i < 6) : (i += 1) { if (i != 3 and i != 4) - try std.testing.expectEqual(Position{ .x = @floatFromInt(f32, i), .y = @floatFromInt(f32, i) }, reg.getConst(Position, i)); + try std.testing.expectEqual(Position{ .x = @as(f32, @floatFromInt(i)), .y = @as(f32, @floatFromInt(i)) }, reg.getConst(Position, i)); } } From bdcd98a9899ed977b5c53203e57c6f7a7d4aec97 Mon Sep 17 00:00:00 2001 From: menduz Date: Mon, 3 Jul 2023 14:38:43 -0300 Subject: [PATCH 142/146] fix: signals for addOrReplace --- zig-ecs/src/ecs/registry.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 17711d3..1cc554d 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -316,6 +316,7 @@ pub const Registry = struct { const store = self.assure(@TypeOf(value)); if (store.tryGet(entity)) |found| { found.* = value; + store.update.publish(entity); } else { store.add(entity, value); } @@ -369,9 +370,10 @@ pub const Registry = struct { /// Returns a reference to the given component for an entity creating it if necessary pub fn getOrAdd(self: *Registry, comptime T: type, entity: Entity) *T { - if (self.has(T, entity)) return self.get(T, entity); - self.add(T, entity, std.mem.zeros(T)); - return self.get(T, type); + if (!self.has(T, entity)) { + self.addTyped(T, entity, .{}); + } + return self.get(T, entity); } pub fn tryGet(self: *Registry, comptime T: type, entity: Entity) ?*T { From 1a798a9c5d37dd64f841679dcbaeb6affd35bfb3 Mon Sep 17 00:00:00 2001 From: menduz Date: Thu, 6 Jul 2023 12:37:17 -0300 Subject: [PATCH 143/146] expose utils and EntityHandles for iterator typing --- zig-ecs/src/ecs.zig | 2 ++ zig-ecs/src/ecs/registry.zig | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs.zig b/zig-ecs/src/ecs.zig index e8ac5aa..b4ecd4d 100644 --- a/zig-ecs/src/ecs.zig +++ b/zig-ecs/src/ecs.zig @@ -6,11 +6,13 @@ pub const ComponentStorage = @import("ecs/component_storage.zig").ComponentStora pub const Entity = @import("ecs/registry.zig").Entity; pub const Registry = @import("ecs/registry.zig").Registry; +pub const EntityHandles = @import("ecs/registry.zig").EntityHandles; pub const BasicView = @import("ecs/views.zig").BasicView; pub const BasicMultiView = @import("ecs/views.zig").BasicMultiView; pub const BasicGroup = @import("ecs/groups.zig").BasicGroup; pub const OwningGroup = @import("ecs/groups.zig").OwningGroup; pub const SparseSet = @import("ecs/sparse_set.zig").SparseSet; +pub const utils = @import("ecs/utils.zig"); // signals pub const Signal = @import("signals/signal.zig").Signal; diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index 1cc554d..d87388c 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -14,7 +14,7 @@ const root = @import("root"); pub const entity_traits = if (@hasDecl(root, "EntityTraits")) root.EntityTraits.init() else @import("entity.zig").EntityTraits.init(); // setup the Handles type based on the type set in EntityTraits -const EntityHandles = Handles(entity_traits.entity_type, entity_traits.index_type, entity_traits.version_type); +pub const EntityHandles = Handles(entity_traits.entity_type, entity_traits.index_type, entity_traits.version_type); pub const Entity = entity_traits.entity_type; const BasicView = @import("views.zig").BasicView; From 55f7813640efafbdc6ec83f2a11bfb0464d30a11 Mon Sep 17 00:00:00 2001 From: menduz Date: Sun, 23 Jul 2023 17:12:53 -0300 Subject: [PATCH 144/146] consistent naming for entityIterator among view implementations --- zig-ecs/src/ecs/views.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index 1afe904..c59c4da 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -142,7 +142,7 @@ pub fn MultiView(comptime n_includes: usize, comptime n_excludes: usize) type { utils.sortSub(usize, u32, sub_items[0..], self.type_ids[0..], asc_usize.sort); } - pub fn iterator(self: *Self) Iterator { + pub fn entityIterator(self: *Self) Iterator { self.sort(); return Iterator.init(self); } From 531ae84819c0568293ce865df9c632ed92f9de73 Mon Sep 17 00:00:00 2001 From: menduz Date: Sun, 13 Aug 2023 00:27:09 -0300 Subject: [PATCH 145/146] fix tests --- zig-ecs/build.zig | 4 ++-- zig-ecs/examples/view_vs_group.zig | 2 +- zig-ecs/src/ecs/registry.zig | 2 +- zig-ecs/src/ecs/views.zig | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index 4fe1245..fc09c06 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -30,12 +30,12 @@ pub fn build(b: *Builder) void { .optimize = optimize, }); // exe.setOutputDir(std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root, "bin" }) catch unreachable); - exe.output_dirname_source = .{ .path = std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root.path.?, "bin" }) catch unreachable, .step = &exe.step }; + // exe.output_dirname_source = .{ .path = std.fs.path.join(b.allocator, &[_][]const u8{ b.cache_root.path.?, "bin" }) catch unreachable, .step = &exe.step }; exe.addModule("ecs", ecs_module); exe.linkLibC(); const docs = exe; - docs.emit_docs = .emit; + // docs.emit_docs = .emit; const doc = b.step(b.fmt("{s}-docs", .{name}), "Generate documentation"); doc.dependOn(&docs.step); diff --git a/zig-ecs/examples/view_vs_group.zig b/zig-ecs/examples/view_vs_group.zig index cd2e0be..5a47187 100644 --- a/zig-ecs/examples/view_vs_group.zig +++ b/zig-ecs/examples/view_vs_group.zig @@ -38,7 +38,7 @@ fn iterateView(reg: *ecs.Registry) void { var view = reg.view(.{ Velocity, Position }, .{}); var timer = std.time.Timer.start() catch unreachable; - var iter = view.iterator(); + var iter = view.entityIterator(); while (iter.next()) |entity| { var pos = view.get(Position, entity); const vel = view.getConst(Velocity, entity); diff --git a/zig-ecs/src/ecs/registry.zig b/zig-ecs/src/ecs/registry.zig index d87388c..9191bf8 100644 --- a/zig-ecs/src/ecs/registry.zig +++ b/zig-ecs/src/ecs/registry.zig @@ -571,7 +571,7 @@ pub const Registry = struct { // pre-fill the GroupData with any existing entitites that match if (owned.len == 0) { var view_instance = self.view(owned ++ includes, excludes); - var view_iter = view_instance.iterator(); + var view_iter = view_instance.entityIterator(); while (view_iter.next()) |entity| { new_group_data.entity_set.add(entity); } diff --git a/zig-ecs/src/ecs/views.zig b/zig-ecs/src/ecs/views.zig index c59c4da..89c1f7b 100644 --- a/zig-ecs/src/ecs/views.zig +++ b/zig-ecs/src/ecs/views.zig @@ -238,7 +238,7 @@ test "basic multi view" { var view = reg.view(.{ i32, u32 }, .{}); var iterated_entities: usize = 0; - var iter = view.iterator(); + var iter = view.entityIterator(); while (iter.next()) |_| { iterated_entities += 1; } @@ -276,7 +276,7 @@ test "basic multi view with excludes" { var view = reg.view(.{ i32, u32 }, .{u8}); var iterated_entities: usize = 0; - var iter = view.iterator(); + var iter = view.entityIterator(); while (iter.next()) |_| { iterated_entities += 1; } From 630577f550d7e7486b177036efa0821f98f6342c Mon Sep 17 00:00:00 2001 From: RedStealthDev Date: Mon, 26 Feb 2024 13:31:35 +0100 Subject: [PATCH 146/146] fix build system --- zig-ecs/build.zig | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zig-ecs/build.zig b/zig-ecs/build.zig index fc09c06..30b1da0 100644 --- a/zig-ecs/build.zig +++ b/zig-ecs/build.zig @@ -75,11 +75,10 @@ pub const LibType = enum(i32) { exe_compiled, }; -pub fn getModule(comptime prefix_path: []const u8) std.build.Module { - return .{ - .name = "ecs", - .path = .{ .path = prefix_path ++ "src/ecs.zig" }, - }; +pub fn getModule(b: *std.Build, comptime prefix_path: []const u8) *std.build.Module { + return b.addModule("zig-ecs", .{ + .source_file = .{ .path = prefix_path ++ "/src/ecs.zig" }, + }); } /// prefix_path is used to add package paths. It should be the the same path used to include this build file