From 7ce5902ec94376837a1a287878bcc2caa678dcbc Mon Sep 17 00:00:00 2001 From: Lorenzo Torres Date: Thu, 20 Mar 2025 23:40:20 +0100 Subject: preliminary work on ecs --- src/ecs/components.zig | 107 ++++--------------------------------------------- src/ecs/entities.zig | 96 ++++++++++++++++++++++++++++++++++++++++++++ src/ecs/sparse.zig | 36 +++++++++++++++++ src/main.zig | 8 +++- 4 files changed, 146 insertions(+), 101 deletions(-) create mode 100644 src/ecs/entities.zig create mode 100644 src/ecs/sparse.zig (limited to 'src') diff --git a/src/ecs/components.zig b/src/ecs/components.zig index bb333c9..5c35cbf 100644 --- a/src/ecs/components.zig +++ b/src/ecs/components.zig @@ -1,111 +1,18 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -pub const Position = struct { +const COMPONENT_NUMBER = 2; + +pub const Position = packed struct { x: f32, y: f32, z: f32, -}; -pub const Speed = struct { - speed: f32, + pub const id: usize = 0; }; -pub const Pool = struct { - comptime sets_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(allocator), - speed_set: SparseSet(Speed), - allocator: Allocator, - free_ids: std.ArrayList(usize), - entities: usize, - - pub fn init(allocator: Allocator) !Pool { - try sets_map.put(@typeName(Speed), "speed_set"); - - return Pool{ - .speed_set = try SparseSet(Speed).init(allocator), - .allocator = allocator, - .free_ids = try std.ArrayList(usize).initCapacity(allocator, 100), - .entities = 0, - }; - } - - pub fn deinit(self: *Pool) void { - self.sets_map.deinit(); - self.speed_set.deinit(); - } - - pub fn addComponent(self: *Pool, comptime T: type, id: usize, component: T) void { - var set = @field(self, try self.sets_map.get(@typeName(T))); - - set.insert(id, component); - } - - pub fn insert(self: *Pool) !usize { - const id = self.free_ids.pop() orelse self.entities; - - self.entities += 1; - return id; - } - - pub fn remove(self: *Pool, id: usize) !usize { - if (self.speed_set.hasComponent(id)) { - self.speed_set.remove(id); - } +pub const Speed = packed struct { + speed: f32, - self.entities -= 1; - self.free_ids.append(id); - } + pub const id: usize = 1; }; - -pub fn SparseSet(comptime T: type) type { - return struct { - sparse: std.ArrayList(usize), - dense: std.ArrayList(usize), - components: std.ArrayList(T), - - pub fn init(allocator: Allocator) !@This() { - return @This(){ - .sparse = try std.ArrayList(usize).initCapacity(allocator, 10), - .dense = try std.ArrayList(usize).initCapacity(allocator, 10), - .components = try std.ArrayList(T).initCapacity(allocator, 10), - }; - } - - pub fn deinit(self: *@This()) void { - self.sparse.deinit(); - self.dense.deinit(); - self.components.deinit(); - } - - pub fn hasComponent(self: *@This(), id: usize) bool { - return self.dense.items[self.sparse.items[id]] == id; - } - - pub fn insert(self: *@This(), id: usize, component: T) !void { - const dense_index = self.dense.items.len; - try self.dense.append(id); - try self.components.append(component); - try self.sparse.append(dense_index); - } - - pub fn remove(self: *@This(), id: usize) !void { - const index = self.sparse.items[id]; - const last = self.dense.getLast(); - self.sparse.items[last] = index; - _ = self.dense.swapRemove(index); - _ = self.components.swapRemove(index); - } - }; -} - -pub fn test_sparse() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - - var pool = try Pool.init(allocator); - defer pool.deinit(); - - const entity = try pool.insert(); - std.debug.print("new entity: {d}\n", .{entity}); - pool.addComponent(Speed, entity, .{ .speed = 5.0 }); -} diff --git a/src/ecs/entities.zig b/src/ecs/entities.zig new file mode 100644 index 0000000..6d091e2 --- /dev/null +++ b/src/ecs/entities.zig @@ -0,0 +1,96 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const components = @import("components.zig"); +const sparse = @import("sparse.zig"); + +const System = *const fn (Pool) void; +const SystemGroup = std.ArrayList(System); + +pub const Pool = struct { + // Components + position: sparse.SparseSet(components.Position), + speed: sparse.SparseSet(components.Speed), + + 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) !@This() { + var thread_pool: std.Thread.Pool = undefined; + try thread_pool.init(.{ + .allocator = allocator, + .n_jobs = 4, + }); + + return @This(){ + .position = sparse.SparseSet(components.Position).init(allocator), + .speed = sparse.SparseSet(components.Speed).init(allocator), + + .system_groups = std.ArrayList(SystemGroup).init(allocator), + .thread_pool = thread_pool, + .wait_group = .{}, + .mutex = .{}, + .last_entity = 0, + .free_ids = std.ArrayList(usize).init(allocator), + .component_flags = std.AutoHashMap(usize, usize).init(allocator), + }; + } + + pub fn tick(self: *@This()) void { + for (self.system_groups) |group| { + self.thread_pool.spawnWg(&self.wait_group, struct { + fn run(pool: *Pool) void { + for (group) |system| { + system(pool); + } + } + }.run, .{self}); + } + self.wait_group.wait(); + } + + 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(2, 0x2); + + 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, + 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, + }; + + self.component_flags.put(entity, self.component_flags.get(entity) & ~(0x1 << component_id)); + set.removeEntity(entity); + } +}; diff --git a/src/ecs/sparse.zig b/src/ecs/sparse.zig new file mode 100644 index 0000000..28915a5 --- /dev/null +++ b/src/ecs/sparse.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +pub fn SparseSet(comptime T: type) type { + return struct { + sparse: std.ArrayList(usize), + dense: std.ArrayList(usize), + components: std.ArrayList(T), + + pub fn init(allocator: Allocator) @This() { + return @This(){ + .sparse = std.ArrayList(usize).init(allocator), + .dense = std.ArrayList(usize).init(allocator), + .components = std.ArrayList(T).init(allocator), + }; + } + + pub fn addEntity(self: *@This(), entity: usize, component: T) !void { + if (entity >= self.sparse.items.len) { + try self.sparse.resize(entity + 10); + } + + self.sparse.items[entity] = self.dense.items.len; + try self.dense.append(entity); + try self.components.append(component); + } + + pub fn removeEntity(self: *@This(), entity: usize) void { + const last_index = self.dense.getLast(); + const dense_index = self.sparse.items[entity]; + self.dense.swapRemove(dense_index); + self.components.swapRemove(dense_index); + self.sparse.items[last_index] = dense_index; + } + }; +} diff --git a/src/main.zig b/src/main.zig index 636dbff..9c3e59d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -8,6 +8,8 @@ const math = @import("math.zig"); const Parser = @import("mods/parse.zig"); const vm = @import("mods/vm.zig"); const wasm = @import("mods/wasm.zig"); +const components = @import("ecs/components.zig"); +const entities = @import("ecs/entities.zig"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; @@ -23,10 +25,14 @@ pub fn main() !void { //defer runtime.deinit(allocator); //var parameters = [_]usize{}; - //try runtime.callExternal(allocator, "fibonacci", ¶meters); + //try runtime.callExternal(allocator, "calculate_fibonacci", ¶meters); const w = try window.Window.create(800, 600, "sideros"); defer w.destroy(); + var pool = try entities.Pool.init(allocator); + _ = try pool.createEntity(); + //try pool.addComponent(entity, components.Speed{ .speed = 0.0 }); + // TODO(luccie-cmd): Renderer.create shouldn't return an error var r = try Renderer.create(allocator, w); defer r.destroy(); -- cgit v1.2.3