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.
This commit is contained in:
parent
11f6bc2b04
commit
ff84d6ac53
4 changed files with 34 additions and 107 deletions
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
17
src/main.zig
17
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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue