diff options
author | Lorenzo Torres <torres@sideros.org> | 2025-03-28 15:34:36 +0100 |
---|---|---|
committer | Lorenzo Torres <torres@sideros.org> | 2025-03-28 15:34:36 +0100 |
commit | ff84d6ac5375587428f279c06ead111301a332ab (patch) | |
tree | a26f32397f66693494ab17187a212cb52729ff3f /src | |
parent | 11f6bc2b04fae03a6e81735d4bcebe9505c5d76d (diff) |
the ECS is now using a more data oriented approach.
By defining archetypes using SOAs (Zig has this data structure in `std`,
called std.MultiArrayList), the engine can iterate faster over commonly
defined entities avoiding cache misses since each component is aligned
with other components of the same entity.
Diffstat (limited to 'src')
-rw-r--r-- | src/ecs/components.zig | 4 | ||||
-rw-r--r-- | src/ecs/ecs.zig | 2 | ||||
-rw-r--r-- | src/ecs/entities.zig | 118 | ||||
-rw-r--r-- | src/main.zig | 17 |
4 files changed, 34 insertions, 107 deletions
diff --git a/src/ecs/components.zig b/src/ecs/components.zig index 5c35cbf..1309e70 100644 --- a/src/ecs/components.zig +++ b/src/ecs/components.zig @@ -1,8 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const COMPONENT_NUMBER = 2; - pub const Position = packed struct { x: f32, y: f32, @@ -13,6 +11,4 @@ pub const Position = packed struct { pub const Speed = packed struct { speed: f32, - - pub const id: usize = 1; }; diff --git a/src/ecs/ecs.zig b/src/ecs/ecs.zig index 9efc044..e389223 100644 --- a/src/ecs/ecs.zig +++ b/src/ecs/ecs.zig @@ -1,5 +1,5 @@ pub const components = @import("components.zig"); -const entities = @import("entities.zig"); +pub const entities = @import("entities.zig"); pub const Pool = entities.Pool; pub const Resources = entities.Resources; diff --git a/src/ecs/entities.zig b/src/ecs/entities.zig index 9337d6b..0f1c58d 100644 --- a/src/ecs/entities.zig +++ b/src/ecs/entities.zig @@ -12,32 +12,29 @@ pub const Resources = struct { renderer: Renderer, }; +pub const Human = struct { + position: components.Position, + speed: components.Speed, +}; + pub const Pool = struct { - // Components - position: sparse.SparseSet(components.Position), - speed: sparse.SparseSet(components.Speed), + humans: std.MultiArrayList(Human), resources: Resources, + allocator: Allocator, system_groups: std.ArrayList(SystemGroup), thread_pool: *std.Thread.Pool, wait_group: std.Thread.WaitGroup, mutex: std.Thread.Mutex, - last_entity: usize, - free_ids: std.ArrayList(usize), - - component_flags: std.AutoHashMap(usize, usize), pub fn init(allocator: Allocator, resources: Resources) !@This() { var pool = @This(){ - .position = sparse.SparseSet(components.Position).init(allocator), - .speed = sparse.SparseSet(components.Speed).init(allocator), + .humans = .{}, .resources = resources, .system_groups = std.ArrayList(SystemGroup).init(allocator), .thread_pool = try allocator.create(std.Thread.Pool), .wait_group = .{}, .mutex = .{}, - .last_entity = 0, - .free_ids = std.ArrayList(usize).init(allocator), - .component_flags = std.AutoHashMap(usize, usize).init(allocator), + .allocator = allocator, }; try pool.thread_pool.init(.{ @@ -48,63 +45,16 @@ pub const Pool = struct { return pool; } - pub fn getQuery(self: *@This(), comptime T: type) []T { - const set = switch (T) { - components.Speed => &self.speed, - components.Position => &self.position, - else => unreachable, - }; - - return set.components.items; - } - - pub fn getEntity(self: *@This(), component: usize, comptime T: type) usize { - const set = switch (T) { - components.Speed => &self.speed, - components.Position => &self.position, - else => unreachable, - }; - - return set.dense.items[component]; - } - - pub fn getComponent(self: *@This(), entity: usize, comptime T: type) ?T { - const set = switch (T) { - components.Speed => &self.speed, - components.Position => &self.position, - else => unreachable, - }; - - if (self.hasComponent(entity, T)) { - return set.components.items[set.sparse.items[entity]]; - } else { - return null; - } - } - - pub fn hasComponent(self: *@This(), entity: usize, component: type) bool { - const set = switch (component) { - components.Speed => &self.speed, - components.Position => &self.position, - else => unreachable, - }; - - return set.dense.items[set.sparse.items[entity]] == entity; - } - pub fn addSystemGroup(self: *@This(), group: SystemGroup) !void { try self.system_groups.append(group); } - pub fn deinit(self: *@This(), allocator: Allocator) void { - self.position.deinit(); - self.speed.deinit(); + pub fn deinit(self: *@This()) void { + self.humans.deinit(self.allocator); self.system_groups.deinit(); self.thread_pool.deinit(); - allocator.destroy(self.thread_pool); - self.free_ids.deinit(); - self.component_flags.deinit(); + self.allocator.destroy(self.thread_pool); } pub fn tick(self: *@This()) void { @@ -120,43 +70,21 @@ pub const Pool = struct { } } - pub fn createEntity(self: *@This()) !usize { - const id = self.free_ids.pop() orelse self.last_entity; - self.last_entity += 1; - try self.component_flags.put(id, 0x0); - - return id; - } - - pub fn destroyEntity(self: *@This(), entity: usize) void { - self.free_ids.append(entity); - - const flags = self.component_flags.get(entity); - for (0..components.COMPONENT_NUMBER) |i| { - if (((flags >> i) & 0x1) != 0x0) { - self.removeComponent(entity, i); - } - } - } - - pub fn addComponent(self: *@This(), entity: usize, component: anytype) !void { - var set = switch (@TypeOf(component)) { - components.Speed => &self.speed, - components.Position => &self.position, + fn getEntities(self: *@This(), T: type) *std.MultiArrayList(T) { + return switch (T) { + Human => &self.humans, else => unreachable, }; - - try self.component_flags.put(entity, self.component_flags.get(entity).? | (0x1 << @TypeOf(component).id)); - try set.addEntity(entity, component); } - pub fn removeComponent(self: *@This(), entity: usize, component_id: usize) void { - const set = switch (component_id) { - components.Speed.id => self.speed, - components.Position.id => self.position, - }; + pub fn createEntity(self: *@This(), entity: anytype) !usize { + var list = self.getEntities(@TypeOf(entity)); + const index = list.len; + try list.append(self.allocator, entity); + return index; + } - self.component_flags.put(entity, self.component_flags.get(entity) & ~(0x1 << component_id)); - set.removeEntity(entity); + pub fn destroyEntity(self: *@This(), comptime T: type, entity: usize) void { + self.getEntities(T).swapRemove(entity); } }; diff --git a/src/main.zig b/src/main.zig index 4bbe084..5bcff43 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,4 @@ const std = @import("std"); - const config = @import("config"); const math = @import("sideros").math; const mods = @import("mods"); @@ -7,11 +6,10 @@ const ecs = @import("ecs/ecs.zig"); pub const Renderer = @import("renderer"); fn testSystem2(pool: *ecs.Pool) void { - for (pool.getQuery(ecs.components.Position), 0..) |position, i| { - const entity = pool.getEntity(i, ecs.components.Position); - if (pool.getComponent(entity, ecs.components.Speed)) |speed| { - std.debug.print("entity{d}: {any},{any},{any} {any}\n", .{ i, position.x, position.y, position.z, speed.speed }); - } + const slice = pool.humans.slice(); + + for (slice.items(.position), slice.items(.speed)) |position, speed| { + std.debug.print("entity: {any} {any} {any}: {any}\n", .{ position.x, position.y, position.z, speed.speed }); } } @@ -53,7 +51,12 @@ pub fn main() !void { }; var pool = try ecs.Pool.init(allocator, resources); - defer pool.deinit(allocator); + defer pool.deinit(); + + _ = try pool.createEntity(ecs.entities.Human{ + .position = .{ .x = 0.0, .y = 1.0, .z = 0.0 }, + .speed = .{ .speed = 5.0 }, + }); try pool.addSystemGroup(&[_]ecs.System{ testSystem2, |