From 2875fc285d986b5961e7678b7d98d23bc6e3223d Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 4 Dec 2020 23:37:07 -0800 Subject: [PATCH] initial commit --- .gitignore | 1 + build.zig | 18 ++++ src/component_store.zig | 185 ++++++++++++++++++++++++++++++++++++++++ src/encompass.zig | 2 + src/entity.zig | 18 ++++ 5 files changed, 224 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 src/component_store.zig create mode 100644 src/encompass.zig create mode 100644 src/entity.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2040c29 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +zig-cache diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..677886c --- /dev/null +++ b/build.zig @@ -0,0 +1,18 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + const lib = b.addStaticLibrary("encompass", "src/encompass.zig"); + lib.setBuildMode(mode); + lib.install(); + + var entity_test = b.addTest("src/entity.zig"); + entity_test.setBuildMode(mode); + + var component_store_test = b.addTest("src/component_store.zig"); + component_store_test.setBuildMode(mode); + + const test_step = b.step("test", "Run library tests"); + test_step.dependOn(&entity_test.step); + test_step.dependOn(&component_store_test.step); +} diff --git a/src/component_store.zig b/src/component_store.zig new file mode 100644 index 0000000..7cd57d0 --- /dev/null +++ b/src/component_store.zig @@ -0,0 +1,185 @@ +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), + indexToEntityID: std.ArrayList(usize), + components: std.ArrayList(TComponent), + + pub fn init(allocator: *Allocator) Self { + return Self { + .allocator = allocator, + .entityIDToIndex = std.AutoArrayHashMap(usize, usize).init(allocator), + .indexToEntityID = std.ArrayList(usize).init(allocator), + .components = std.ArrayList(TComponent).init(allocator), + }; + } + + pub fn deinit(self: *Self) void { + self.entityIDToIndex.deinit(); + self.indexToEntityID.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.indexToEntityID.append(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.indexToEntityID.items[self.nextIndex - 1]; + + self.entityIDToIndex.put(lastEntity, storageIndex) catch unreachable; + + var removedComponent = self.components.swapRemove(storageIndex); + var removedEntityID = self.indexToEntityID.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) []usize { + return self.indexToEntityID.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); +} diff --git a/src/encompass.zig b/src/encompass.zig new file mode 100644 index 0000000..dcd593f --- /dev/null +++ b/src/encompass.zig @@ -0,0 +1,2 @@ +pub const Entity = @import("entity.zig").Entity; +pub const ComponentStore = @import("component_store.zig").ComponentStore; diff --git a/src/entity.zig b/src/entity.zig new file mode 100644 index 0000000..6011fde --- /dev/null +++ b/src/entity.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const testing = std.testing; + +pub const Entity = struct { + ID: u32, + + pub fn init(id: u32) Entity { + return .{ .ID = id }; + } +}; + +test "entity init" { + const e1 = Entity.init(12); + const e2 = Entity.init(21); + + testing.expect(e1.ID == 12); + testing.expect(e2.ID == 21); +}