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 std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const COMPONENT_NUMBER = 2;
|
|
||||||
|
|
||||||
pub const Position = packed struct {
|
pub const Position = packed struct {
|
||||||
x: f32,
|
x: f32,
|
||||||
y: f32,
|
y: f32,
|
||||||
|
|
@ -13,6 +11,4 @@ pub const Position = packed struct {
|
||||||
|
|
||||||
pub const Speed = packed struct {
|
pub const Speed = packed struct {
|
||||||
speed: f32,
|
speed: f32,
|
||||||
|
|
||||||
pub const id: usize = 1;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
pub const components = @import("components.zig");
|
pub const components = @import("components.zig");
|
||||||
const entities = @import("entities.zig");
|
pub const entities = @import("entities.zig");
|
||||||
|
|
||||||
pub const Pool = entities.Pool;
|
pub const Pool = entities.Pool;
|
||||||
pub const Resources = entities.Resources;
|
pub const Resources = entities.Resources;
|
||||||
|
|
|
||||||
|
|
@ -12,32 +12,29 @@ pub const Resources = struct {
|
||||||
renderer: Renderer,
|
renderer: Renderer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Human = struct {
|
||||||
|
position: components.Position,
|
||||||
|
speed: components.Speed,
|
||||||
|
};
|
||||||
|
|
||||||
pub const Pool = struct {
|
pub const Pool = struct {
|
||||||
// Components
|
humans: std.MultiArrayList(Human),
|
||||||
position: sparse.SparseSet(components.Position),
|
|
||||||
speed: sparse.SparseSet(components.Speed),
|
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
|
allocator: Allocator,
|
||||||
system_groups: std.ArrayList(SystemGroup),
|
system_groups: std.ArrayList(SystemGroup),
|
||||||
thread_pool: *std.Thread.Pool,
|
thread_pool: *std.Thread.Pool,
|
||||||
wait_group: std.Thread.WaitGroup,
|
wait_group: std.Thread.WaitGroup,
|
||||||
mutex: std.Thread.Mutex,
|
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() {
|
pub fn init(allocator: Allocator, resources: Resources) !@This() {
|
||||||
var pool = @This(){
|
var pool = @This(){
|
||||||
.position = sparse.SparseSet(components.Position).init(allocator),
|
.humans = .{},
|
||||||
.speed = sparse.SparseSet(components.Speed).init(allocator),
|
|
||||||
.resources = resources,
|
.resources = resources,
|
||||||
.system_groups = std.ArrayList(SystemGroup).init(allocator),
|
.system_groups = std.ArrayList(SystemGroup).init(allocator),
|
||||||
.thread_pool = try allocator.create(std.Thread.Pool),
|
.thread_pool = try allocator.create(std.Thread.Pool),
|
||||||
.wait_group = .{},
|
.wait_group = .{},
|
||||||
.mutex = .{},
|
.mutex = .{},
|
||||||
.last_entity = 0,
|
.allocator = allocator,
|
||||||
.free_ids = std.ArrayList(usize).init(allocator),
|
|
||||||
.component_flags = std.AutoHashMap(usize, usize).init(allocator),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try pool.thread_pool.init(.{
|
try pool.thread_pool.init(.{
|
||||||
|
|
@ -48,63 +45,16 @@ pub const Pool = struct {
|
||||||
return pool;
|
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 {
|
pub fn addSystemGroup(self: *@This(), group: SystemGroup) !void {
|
||||||
try self.system_groups.append(group);
|
try self.system_groups.append(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *@This(), allocator: Allocator) void {
|
pub fn deinit(self: *@This()) void {
|
||||||
self.position.deinit();
|
self.humans.deinit(self.allocator);
|
||||||
self.speed.deinit();
|
|
||||||
|
|
||||||
self.system_groups.deinit();
|
self.system_groups.deinit();
|
||||||
self.thread_pool.deinit();
|
self.thread_pool.deinit();
|
||||||
allocator.destroy(self.thread_pool);
|
self.allocator.destroy(self.thread_pool);
|
||||||
self.free_ids.deinit();
|
|
||||||
self.component_flags.deinit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(self: *@This()) void {
|
pub fn tick(self: *@This()) void {
|
||||||
|
|
@ -120,43 +70,21 @@ pub const Pool = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createEntity(self: *@This()) !usize {
|
fn getEntities(self: *@This(), T: type) *std.MultiArrayList(T) {
|
||||||
const id = self.free_ids.pop() orelse self.last_entity;
|
return switch (T) {
|
||||||
self.last_entity += 1;
|
Human => &self.humans,
|
||||||
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,
|
|
||||||
else => unreachable,
|
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 {
|
pub fn createEntity(self: *@This(), entity: anytype) !usize {
|
||||||
const set = switch (component_id) {
|
var list = self.getEntities(@TypeOf(entity));
|
||||||
components.Speed.id => self.speed,
|
const index = list.len;
|
||||||
components.Position.id => self.position,
|
try list.append(self.allocator, entity);
|
||||||
};
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
self.component_flags.put(entity, self.component_flags.get(entity) & ~(0x1 << component_id));
|
pub fn destroyEntity(self: *@This(), comptime T: type, entity: usize) void {
|
||||||
set.removeEntity(entity);
|
self.getEntities(T).swapRemove(entity);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
17
src/main.zig
17
src/main.zig
|
|
@ -1,5 +1,4 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const config = @import("config");
|
const config = @import("config");
|
||||||
const math = @import("sideros").math;
|
const math = @import("sideros").math;
|
||||||
const mods = @import("mods");
|
const mods = @import("mods");
|
||||||
|
|
@ -7,11 +6,10 @@ const ecs = @import("ecs/ecs.zig");
|
||||||
pub const Renderer = @import("renderer");
|
pub const Renderer = @import("renderer");
|
||||||
|
|
||||||
fn testSystem2(pool: *ecs.Pool) void {
|
fn testSystem2(pool: *ecs.Pool) void {
|
||||||
for (pool.getQuery(ecs.components.Position), 0..) |position, i| {
|
const slice = pool.humans.slice();
|
||||||
const entity = pool.getEntity(i, ecs.components.Position);
|
|
||||||
if (pool.getComponent(entity, ecs.components.Speed)) |speed| {
|
for (slice.items(.position), slice.items(.speed)) |position, speed| {
|
||||||
std.debug.print("entity{d}: {any},{any},{any} {any}\n", .{ i, position.x, position.y, position.z, speed.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);
|
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{
|
try pool.addSystemGroup(&[_]ecs.System{
|
||||||
testSystem2,
|
testSystem2,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue