started component_manager
parent
74e4abd8fd
commit
2a3d11702c
|
@ -6,13 +6,12 @@ pub fn build(b: *Builder) void {
|
||||||
lib.setBuildMode(mode);
|
lib.setBuildMode(mode);
|
||||||
lib.install();
|
lib.install();
|
||||||
|
|
||||||
var component_store_test = b.addTest("src/component_store.zig");
|
var entity_manager_test = b.addTest("src/encompass.zig");
|
||||||
component_store_test.setBuildMode(mode);
|
|
||||||
|
|
||||||
var entity_manager_test = b.addTest("src/entity_manager.zig");
|
|
||||||
entity_manager_test.setBuildMode(mode);
|
entity_manager_test.setBuildMode(mode);
|
||||||
|
|
||||||
|
var component_manager_test = b.addTest("src/component_manager.zig");
|
||||||
|
component_manager_test.setBuildMode(mode);
|
||||||
|
|
||||||
const test_step = b.step("test", "Run library tests");
|
const test_step = b.step("test", "Run library tests");
|
||||||
test_step.dependOn(&component_store_test.step);
|
|
||||||
test_step.dependOn(&entity_manager_test.step);
|
test_step.dependOn(&entity_manager_test.step);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
const ComponentStore = @import("component_store.zig").ComponentStore;
|
||||||
|
const TypeStore = @import("type_store.zig").TypeStore;
|
||||||
|
const Entity = @import("entity.zig").Entity;
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
const testing = std.testing;
|
||||||
|
const Allocator = mem.Allocator;
|
||||||
|
|
||||||
|
pub const ComponentManager = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
allocator: *Allocator,
|
||||||
|
component_stores: TypeStore,
|
||||||
|
|
||||||
|
pub fn init(allocator: *Allocator) Self {
|
||||||
|
return Self {
|
||||||
|
.allocator = allocator,
|
||||||
|
.component_stores = TypeStore.init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.component_stores.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(self: *Self, comptime TComponent: type) void {
|
||||||
|
self.component_stores.add(ComponentStore(TComponent).init(self.allocator));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_component(self: *Self, entityID: usize, component: anytype) void {
|
||||||
|
self.component_stores.get(ComponentStore(@TypeOf(component))).*.set(entityID, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_component(self: *Self, comptime TComponent: type, entityID: usize) !TComponent {
|
||||||
|
return self.component_stores.get(ComponentStore(TComponent)).*.get(entityID) catch |err| return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_components(self: *Self, comptime TComponent: type) []TComponent {
|
||||||
|
return self.component_stores.get(ComponentStore(TComponent)).*.all_components();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_entities(self: *Self, comptime TComponent: type) []Entity {
|
||||||
|
return self.component_stores.get(ComponentStore(TComponent)).*.all_entities();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestComponent = struct {
|
||||||
|
number: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
test "add component" {
|
||||||
|
const component = TestComponent {
|
||||||
|
.number = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
var component_manager = ComponentManager.init(std.testing.allocator);
|
||||||
|
defer component_manager.deinit();
|
||||||
|
|
||||||
|
component_manager.register(TestComponent);
|
||||||
|
|
||||||
|
component_manager.add_component(2, component);
|
||||||
|
var expectedComponent = try component_manager.get_component(TestComponent, 2);
|
||||||
|
expectedComponent.number = 5; // should be immutable!
|
||||||
|
|
||||||
|
expectedComponent = try component_manager.get_component(TestComponent, 2);
|
||||||
|
testing.expect(expectedComponent.number == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "read components" {
|
||||||
|
const component = TestComponent {
|
||||||
|
.number = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
const componentTwo = TestComponent {
|
||||||
|
.number = 8
|
||||||
|
};
|
||||||
|
|
||||||
|
const componentThree = TestComponent {
|
||||||
|
.number = 14
|
||||||
|
};
|
||||||
|
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
const allocator = &arena.allocator;
|
||||||
|
|
||||||
|
var component_manager = ComponentManager.init(allocator);
|
||||||
|
|
||||||
|
component_manager.register(TestComponent);
|
||||||
|
|
||||||
|
component_manager.add_component(2, component);
|
||||||
|
component_manager.add_component(5, componentTwo);
|
||||||
|
component_manager.add_component(52, componentThree);
|
||||||
|
|
||||||
|
var components = component_manager.read_components(TestComponent);
|
||||||
|
|
||||||
|
testing.expect(components[0].number == 3);
|
||||||
|
testing.expect(components[1].number == 8);
|
||||||
|
testing.expect(components[2].number == 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "read entities" {
|
||||||
|
const component = TestComponent {
|
||||||
|
.number = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
const componentTwo = TestComponent {
|
||||||
|
.number = 8
|
||||||
|
};
|
||||||
|
|
||||||
|
const componentThree = TestComponent {
|
||||||
|
.number = 14
|
||||||
|
};
|
||||||
|
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
const allocator = &arena.allocator;
|
||||||
|
|
||||||
|
var component_manager = ComponentManager.init(allocator);
|
||||||
|
|
||||||
|
component_manager.register(TestComponent);
|
||||||
|
|
||||||
|
component_manager.add_component(2, component);
|
||||||
|
component_manager.add_component(5, componentTwo);
|
||||||
|
component_manager.add_component(52, componentThree);
|
||||||
|
|
||||||
|
var entities = component_manager.read_entities(TestComponent);
|
||||||
|
|
||||||
|
testing.expect(entities[0].ID == 2);
|
||||||
|
testing.expect(entities[1].ID == 5);
|
||||||
|
testing.expect(entities[2].ID == 52);
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
const Entity = @import("entity.zig").Entity;
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
@ -10,21 +12,21 @@ pub fn ComponentStore(comptime TComponent: type) type {
|
||||||
nextIndex: usize = 0,
|
nextIndex: usize = 0,
|
||||||
allocator: *Allocator,
|
allocator: *Allocator,
|
||||||
entityIDToIndex: std.AutoArrayHashMap(usize, usize),
|
entityIDToIndex: std.AutoArrayHashMap(usize, usize),
|
||||||
indexToEntityID: std.ArrayList(usize),
|
indexToEntity: std.ArrayList(Entity),
|
||||||
components: std.ArrayList(TComponent),
|
components: std.ArrayList(TComponent),
|
||||||
|
|
||||||
pub fn init(allocator: *Allocator) Self {
|
pub fn init(allocator: *Allocator) Self {
|
||||||
return Self {
|
return Self {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.entityIDToIndex = std.AutoArrayHashMap(usize, usize).init(allocator),
|
.entityIDToIndex = std.AutoArrayHashMap(usize, usize).init(allocator),
|
||||||
.indexToEntityID = std.ArrayList(usize).init(allocator),
|
.indexToEntity = std.ArrayList(Entity).init(allocator),
|
||||||
.components = std.ArrayList(TComponent).init(allocator),
|
.components = std.ArrayList(TComponent).init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.entityIDToIndex.deinit();
|
self.entityIDToIndex.deinit();
|
||||||
self.indexToEntityID.deinit();
|
self.indexToEntity.deinit();
|
||||||
self.components.deinit();
|
self.components.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +42,7 @@ pub fn ComponentStore(comptime TComponent: type) type {
|
||||||
// FIXME: actually handle OOM
|
// FIXME: actually handle OOM
|
||||||
self.components.append(component) catch unreachable;
|
self.components.append(component) catch unreachable;
|
||||||
self.entityIDToIndex.put(entityID, index) catch unreachable;
|
self.entityIDToIndex.put(entityID, index) catch unreachable;
|
||||||
self.indexToEntityID.append(entityID) catch unreachable;
|
self.indexToEntity.append(Entity { .ID = entityID }) catch unreachable;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -60,12 +62,12 @@ pub fn ComponentStore(comptime TComponent: type) type {
|
||||||
pub fn remove(self: *Self, entityID: usize) void {
|
pub fn remove(self: *Self, entityID: usize) void {
|
||||||
if (self.entityIDToIndex.contains(entityID)) {
|
if (self.entityIDToIndex.contains(entityID)) {
|
||||||
var storageIndex = self.entityIDToIndex.get(entityID).?;
|
var storageIndex = self.entityIDToIndex.get(entityID).?;
|
||||||
var lastEntity = self.indexToEntityID.items[self.nextIndex - 1];
|
var lastEntity = self.indexToEntity.items[self.nextIndex - 1];
|
||||||
|
|
||||||
self.entityIDToIndex.put(lastEntity, storageIndex) catch unreachable;
|
self.entityIDToIndex.put(lastEntity.ID, storageIndex) catch unreachable;
|
||||||
|
|
||||||
var removedComponent = self.components.swapRemove(storageIndex);
|
var removedComponent = self.components.swapRemove(storageIndex);
|
||||||
var removedEntityID = self.indexToEntityID.swapRemove(storageIndex);
|
var removedEntityID = self.indexToEntity.swapRemove(storageIndex);
|
||||||
var removedEntry = self.entityIDToIndex.remove(entityID);
|
var removedEntry = self.entityIDToIndex.remove(entityID);
|
||||||
|
|
||||||
self.nextIndex -= 1;
|
self.nextIndex -= 1;
|
||||||
|
@ -76,8 +78,8 @@ pub fn ComponentStore(comptime TComponent: type) type {
|
||||||
return self.components.items;
|
return self.components.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_entities(self: Self) []usize {
|
pub fn all_entities(self: Self) []Entity {
|
||||||
return self.indexToEntityID.items();
|
return self.indexToEntity.items;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,9 @@
|
||||||
pub const Entity = @import("entity.zig").Entity;
|
pub const EntityManager = @import("entity_manager.zig").EntityManager;
|
||||||
pub const ComponentStore = @import("component_store.zig").ComponentStore;
|
pub const ComponentManager = @import("component_manager.zig").ComponentManager;
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
test "" {
|
||||||
|
testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
// adapted from https://github.com/prime31/zig-ecs/blob/master/src/ecs/utils.zig
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// stores a single object of type T for each T added
|
||||||
|
pub const TypeStore = struct {
|
||||||
|
map: std.AutoHashMap(u32, []u8),
|
||||||
|
allocator: *std.mem.Allocator,
|
||||||
|
|
||||||
|
pub fn init(allocator: *std.mem.Allocator) TypeStore {
|
||||||
|
return TypeStore{
|
||||||
|
.map = std.AutoHashMap(u32, []u8).init(allocator),
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *TypeStore) void {
|
||||||
|
var iter = self.map.iterator();
|
||||||
|
while (iter.next()) |kv| {
|
||||||
|
self.allocator.free(kv.value);
|
||||||
|
}
|
||||||
|
self.map.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// adds instance, returning a pointer to the item as it lives in the store
|
||||||
|
pub fn add(self: *TypeStore, instance: anytype) void {
|
||||||
|
var bytes = self.allocator.alloc(u8, @sizeOf(@TypeOf(instance))) catch unreachable;
|
||||||
|
std.mem.copy(u8, bytes, std.mem.asBytes(&instance));
|
||||||
|
_ = self.map.put(typeId(@TypeOf(instance)), bytes) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *TypeStore, comptime T: type) *T {
|
||||||
|
if (self.map.get(typeId(T))) |bytes| {
|
||||||
|
return @ptrCast(*T, @alignCast(@alignOf(T), bytes));
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getConst(self: *TypeStore, comptime T: type) T {
|
||||||
|
return self.get(T).*;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getOrAdd(self: *TypeStore, comptime T: type) *T {
|
||||||
|
if (!self.has(T)) {
|
||||||
|
var instance = std.mem.zeroes(T);
|
||||||
|
self.add(instance);
|
||||||
|
}
|
||||||
|
return self.get(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self: *TypeStore, comptime T: type) void {
|
||||||
|
if (self.map.get(typeId(T))) |bytes| {
|
||||||
|
self.allocator.free(bytes);
|
||||||
|
_ = self.map.remove(typeId(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has(self: *TypeStore, comptime T: type) bool {
|
||||||
|
return self.map.contains(typeId(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typeId(comptime T: type) u32 {
|
||||||
|
comptime return hashStringFnv(u32, @typeName(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hashStringFnv(comptime ReturnType: type, comptime str: []const u8) ReturnType {
|
||||||
|
std.debug.assert(ReturnType == u32 or ReturnType == u64);
|
||||||
|
|
||||||
|
const prime = if (ReturnType == u32) @as(u32, 16777619) else @as(u64, 1099511628211);
|
||||||
|
var value = if (ReturnType == u32) @as(u32, 2166136261) else @as(u64, 14695981039346656037);
|
||||||
|
for (str) |c| {
|
||||||
|
value = (value ^ @intCast(u32, c)) *% prime;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "TypeStore" {
|
||||||
|
const Vector = struct { x: f32 = 0, y: f32 = 0, z: f32 = 0 };
|
||||||
|
|
||||||
|
var store = TypeStore.init(std.testing.allocator);
|
||||||
|
defer store.deinit();
|
||||||
|
|
||||||
|
var orig = Vector{ .x = 5, .y = 6, .z = 8 };
|
||||||
|
store.add(orig);
|
||||||
|
std.testing.expect(store.has(Vector));
|
||||||
|
std.testing.expectEqual(store.get(Vector).*, orig);
|
||||||
|
|
||||||
|
var v = store.get(Vector);
|
||||||
|
std.testing.expectEqual(v.*, Vector{ .x = 5, .y = 6, .z = 8 });
|
||||||
|
v.*.x = 666;
|
||||||
|
|
||||||
|
var v2 = store.get(Vector);
|
||||||
|
std.testing.expectEqual(v2.*, Vector{ .x = 666, .y = 6, .z = 8 });
|
||||||
|
|
||||||
|
store.remove(Vector);
|
||||||
|
std.testing.expect(!store.has(Vector));
|
||||||
|
|
||||||
|
var v3 = store.getOrAdd(u32);
|
||||||
|
std.testing.expectEqual(v3.*, 0);
|
||||||
|
v3.* = 777;
|
||||||
|
|
||||||
|
var v4 = store.get(u32);
|
||||||
|
std.testing.expectEqual(v3.*, 777);
|
||||||
|
}
|
Loading…
Reference in New Issue