summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLorenzo Torres <torres@sideros.org>2025-03-28 15:34:36 +0100
committerLorenzo Torres <torres@sideros.org>2025-03-28 15:34:36 +0100
commitff84d6ac5375587428f279c06ead111301a332ab (patch)
treea26f32397f66693494ab17187a212cb52729ff3f /src
parent11f6bc2b04fae03a6e81735d4bcebe9505c5d76d (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.zig4
-rw-r--r--src/ecs/ecs.zig2
-rw-r--r--src/ecs/entities.zig118
-rw-r--r--src/main.zig17
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,