ecs: third major redesign/rethink of implementation
In the past: * hexops/mach#156 was the initial ECS implementation detailed in https://devlog.hexops.com/2022/lets-build-ecs-part-1 * hexops/mach#157 was the second major redesign in which we: * Eliminated major limitations (e.g. inability to add/remove components at runtime) * Investigated sparse sets * Began thinking in terms of databases * Enabled runtime introspection Our second revision of the ECS, however, still had _archetypes_ exposed as a public-facing user concern. When a new component was added to an entity, say a weapon, the table storing entities of that archetype changed to effectively have a new column `?Weapon` with a null value for _all existing entities of that archetype_. We can say that our ECS had archetypes as a user-facing concern AND this made performance worse: when iterating all entities with a weapon, we needed to check if the component value was `null` or not because every column was `?Weapon` instead of a guaranteed non-null value like `Weapon`. This was a key learning that I got from [discussing ECS tradeoffs with the Bevy team](https://github.com/hexops/mach/pull/157#issuecomment-1022916117). This third revision of our ECS has some big benefits: * Entities are now just IDs proper, you can add/remove arbitrary components at runtime. * You don't have an "entity which always belongs to one archetype table which changes" * Rather, you have an "entity of one archetype" and adding a component means that entity _moves_ from one archetype table to another. * Archetypes are now an implementation detail, not something you worry about as a consumer of the API. * Performance * We benefit from the fact that we no longer need check if a component on an entity is `null` or not. * Introspection * Previously iterating the component names/values an entity had was not possible, now it is. * Querying & multi-threading * Very very early stages into this, but we now have a general plan for how querying will work and multi-threading. * Effectively, it will look much like interfacing with a database: you have a connection (we call it an adapter) and you can ask for information through that. More work to be done here. * Systems, we now have a (very) basic starting point for how systems will work. Some examples of how the API looks today: *979240135b/ecs/src/main.zig (L49)*979240135b/ecs/src/entities.zig (L625-L656)Much more work to do, I will do a blog post detailing this step-by-step first though. Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
1428569e66
commit
0ef13eb1cc
3 changed files with 792 additions and 517 deletions
55
ecs/src/systems.zig
Normal file
55
ecs/src/systems.zig
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
const testing = std.testing;
|
||||
|
||||
const Entities = @import("entities.zig").Entities;
|
||||
const Iterator = Entities.Iterator;
|
||||
|
||||
pub const Adapter = struct {
|
||||
world: *World,
|
||||
|
||||
pub fn query(adapter: *Adapter, components: []const []const u8) Iterator {
|
||||
return adapter.world.entities.query(components);
|
||||
}
|
||||
};
|
||||
|
||||
pub const System = fn (adapter: *Adapter) void;
|
||||
|
||||
pub const World = struct {
|
||||
allocator: Allocator,
|
||||
systems: std.StringArrayHashMapUnmanaged(System) = .{},
|
||||
entities: Entities,
|
||||
|
||||
pub fn init(allocator: Allocator) !World {
|
||||
return World{
|
||||
.allocator = allocator,
|
||||
.entities = try Entities.init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(world: *World) void {
|
||||
world.systems.deinit(world.allocator);
|
||||
world.entities.deinit();
|
||||
}
|
||||
|
||||
pub fn register(world: *World, name: []const u8, system: System) !void {
|
||||
try world.systems.put(world.allocator, name, system);
|
||||
}
|
||||
|
||||
pub fn unregister(world: *World, name: []const u8) void {
|
||||
world.systems.orderedRemove(name);
|
||||
}
|
||||
|
||||
pub fn tick(world: *World) void {
|
||||
var i: usize = 0;
|
||||
while (i < world.systems.count()) : (i += 1) {
|
||||
const system = world.systems.entries.get(i).value;
|
||||
|
||||
var adapter = Adapter{
|
||||
.world = world,
|
||||
};
|
||||
system(&adapter);
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue