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