encompass-zig/src/component_store.zig

188 lines
5.6 KiB
Zig

const Entity = @import("entity.zig").Entity;
const std = @import("std");
const mem = std.mem;
const testing = std.testing;
const Allocator = mem.Allocator;
pub fn ComponentStore(comptime TComponent: type) type {
return struct {
const Self = @This();
nextIndex: usize = 0,
allocator: *Allocator,
entityIDToIndex: std.AutoArrayHashMap(usize, usize),
indexToEntity: std.ArrayList(Entity),
components: std.ArrayList(TComponent),
pub fn init(allocator: *Allocator) Self {
return Self {
.allocator = allocator,
.entityIDToIndex = std.AutoArrayHashMap(usize, usize).init(allocator),
.indexToEntity = std.ArrayList(Entity).init(allocator),
.components = std.ArrayList(TComponent).init(allocator),
};
}
pub fn deinit(self: *Self) void {
self.entityIDToIndex.deinit();
self.indexToEntity.deinit();
self.components.deinit();
}
pub fn has(self: Self, entityID: usize) bool {
return self.entityIDToIndex.contains(entityID);
}
pub fn set(self: *Self, entityID: usize, component: TComponent) void {
if (!self.entityIDToIndex.contains(entityID)) {
const index = self.nextIndex;
self.nextIndex += 1;
// FIXME: actually handle OOM
self.components.append(component) catch unreachable;
self.entityIDToIndex.put(entityID, index) catch unreachable;
self.indexToEntity.append(Entity { .ID = entityID }) catch unreachable;
}
else
{
self.components.items[self.entityIDToIndex.get(entityID).?] = component;
}
}
pub fn count(self: Self) usize {
return self.components.items.len;
}
pub fn get(self: Self, entityID: usize) !TComponent {
var index = self.entityIDToIndex.get(entityID) orelse error.NoComponentError;
return self.components.items[try index];
}
pub fn remove(self: *Self, entityID: usize) void {
if (self.entityIDToIndex.contains(entityID)) {
var storageIndex = self.entityIDToIndex.get(entityID).?;
var lastEntity = self.indexToEntity.items[self.nextIndex - 1];
self.entityIDToIndex.put(lastEntity.ID, storageIndex) catch unreachable;
var removedComponent = self.components.swapRemove(storageIndex);
var removedEntityID = self.indexToEntity.swapRemove(storageIndex);
var removedEntry = self.entityIDToIndex.remove(entityID);
self.nextIndex -= 1;
}
}
pub fn all_components(self: Self) []TComponent {
return self.components.items;
}
pub fn all_entities(self: Self) []Entity {
return self.indexToEntity.items;
}
};
}
const TestComponent = struct {
number: i32,
};
test "component store set item" {
const component = TestComponent {
.number = 3
};
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = &arena.allocator;
var component_store = ComponentStore(TestComponent).init(allocator);
component_store.set(12, component);
testing.expect(component_store.count() == 1);
testing.expect(component_store.has(12));
testing.expect((try component_store.get(12)).number == component.number);
}
test "iterate items" {
const component = TestComponent {
.number = 3
};
const componentTwo = TestComponent {
.number = 10
};
const componentThree = TestComponent {
.number = 15
};
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = &arena.allocator;
var component_store = ComponentStore(TestComponent).init(allocator);
component_store.set(3, component);
component_store.set(5, componentTwo);
component_store.set(8, componentThree);
var all_components = component_store.all_components();
testing.expect(all_components[0].number == 3);
testing.expect(all_components[1].number == 10);
testing.expect(all_components[2].number == 15);
}
test "has item" {
const component = TestComponent {
.number = 3
};
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = &arena.allocator;
var component_store = ComponentStore(TestComponent).init(allocator);
component_store.set(5, component);
testing.expect(component_store.has(5));
testing.expect(!component_store.has(6));
}
test "remove item" {
const component = TestComponent {
.number = 5
};
const componentTwo = TestComponent {
.number = 87
};
const componentThree = TestComponent {
.number = 124
};
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = &arena.allocator;
var component_store = ComponentStore(TestComponent).init(allocator);
component_store.set(5, component);
component_store.set(10, componentTwo);
component_store.set(15, componentThree);
component_store.remove(10);
testing.expect(component_store.has(5));
testing.expect(component_store.has(15));
testing.expect(!component_store.has(10));
testing.expect((try component_store.get(5)).number == 5);
testing.expect((try component_store.get(15)).number == 124);
}