-
Notifications
You must be signed in to change notification settings - Fork 0
/
ecs.zig
453 lines (367 loc) · 15 KB
/
ecs.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
const std = @import("std");
const IntegerBitSet = std.bit_set.IntegerBitSet;
pub const components = @import("components.zig");
pub const Queue = @import("../queue.zig").Queue;
const LOGGER = std.log;
const MAX_COMPONENTS = 1024;
const MAX_COMPONENTS_PER_ENTITY = 32;
pub const ComponentSignature = IntegerBitSet(MAX_COMPONENTS_PER_ENTITY);
pub const ComponentType = usize;
pub const EntityType = usize;
pub fn EntityIdMap(comptime T: type) type {
return std.AutoArrayHashMap(EntityType, T);
}
pub fn Ecs(comptime Types: anytype) type {
return struct {
const This = @This();
entityManager: EntityManager,
componentManager: ComponentManager(Types),
signatureIndexMap: std.AutoArrayHashMap(ComponentType, usize),
pub fn init(allocator: std.mem.Allocator) !This {
var ecs = This{
.entityManager = try EntityManager.init(allocator),
.componentManager = try ComponentManager(Types).init(allocator),
.signatureIndexMap = std.AutoArrayHashMap(ComponentType, usize).init(allocator),
};
// Map types to signature bitset indices.
inline for (Types, 0..) |T, idx| {
if (idx == MAX_COMPONENTS_PER_ENTITY) {
return error.MaxComponentsPerEntityExceeded;
}
try ecs.signatureIndexMap.put(typeid(T), idx);
}
return ecs;
}
pub fn deinit(self: *This) void {
self.entityManager.deinit();
self.componentManager.deinit();
self.signatureIndexMap.deinit();
}
pub fn registerEntity(self: *This) !EntityType {
return self.entityManager.registerEntity();
}
pub fn removeEntity(self: *This, entity: EntityType) void {
self.componentManager.removeAll(entity);
_ = self.entityManager.removeEntity(entity);
}
pub fn setComponent(self: *This, entity: EntityType, comptime T: type, component: T) !void {
try self.componentManager.set(entity, T, component);
// Cannot be null because otherwise error about unregistered would have already returned.
const index = self.signatureIndexMap.get(typeid(T)).?;
try self.entityManager.setSignature(entity, index);
}
pub fn hasComponent(self: *This, entity: EntityType, comptime T: type) bool {
const sig = self.entityManager.getSignature(entity) orelse return false;
const index = self.signatureIndexMap.get(typeid(T)) orelse return false;
return sig.isSet(index);
}
pub fn removeComponent(self: *This, entity: EntityType, comptime T: type) !void {
try self.componentManager.remove(entity, T);
const index = self.signatureIndexMap.get(typeid(T)).?;
try self.entityManager.unsetSignature(entity, index);
}
};
}
pub const EntityManager = struct {
const This = @This();
entities: EntityIdMap(ComponentSignature),
nextId: EntityType = 0,
pub fn init(allocator: std.mem.Allocator) !EntityManager {
return EntityManager{
.entities = EntityIdMap(ComponentSignature).init(allocator),
};
}
pub fn deinit(self: *EntityManager) void {
self.entities.deinit();
}
pub fn registerEntity(self: *EntityManager) !EntityType {
const id = self.nextId;
try self.entities.put(id, ComponentSignature.initEmpty());
self.nextId += 1;
return id;
}
pub fn getSignature(self: *EntityManager, entity: EntityType) ?ComponentSignature {
return self.entities.get(entity) orelse return null;
}
pub fn setSignature(self: *EntityManager, entity: EntityType, index: usize) !void {
var sig = self.getSignature(entity) orelse return error.NoSignatureForEntity;
sig.set(index);
try self.entities.put(entity, sig);
}
pub fn unsetSignature(self: *EntityManager, entity: EntityType, index: usize) !void {
var sig = self.getSignature(entity) orelse return error.NoSignatureForEntity;
sig.unset(index);
try self.entities.put(entity, sig);
}
pub fn removeEntity(self: *EntityManager, id: EntityType) bool {
return self.entities.swapRemove(id);
}
pub fn iterator(self: *EntityManager) EntityIdMap(ComponentSignature).Iterator {
return self.entities.iterator();
}
};
pub fn ComponentManager(comptime Types: anytype) type {
return struct {
/// Storage backend for component lists.
const This = @This();
const StorageType = ComponentFixedList;
componentTypes: @TypeOf(Types) = Types,
allocator: std.mem.Allocator,
/// Values are pointers to StorageType values
componentLists: std.AutoArrayHashMap(ComponentType, usize),
pub fn init(allocator: std.mem.Allocator) !This {
var manager = This{
.allocator = allocator,
.componentLists = std.AutoArrayHashMap(ComponentType, usize).init(allocator),
};
inline for (manager.componentTypes) |T| {
const id = typeid(T);
const list = try allocator.create(StorageType(T));
list.* = try StorageType(T).init(allocator);
try manager.componentLists.put(id, @as(usize, @intFromPtr(list)));
}
return manager;
}
pub fn deinitComponent(self: *This, comptime T: type) void {
const id = typeid(T);
const listAddr = self.componentLists.get(id) orelse unreachable;
const listPtr: *StorageType(T) = @ptrFromInt(listAddr);
listPtr.deinit();
self.allocator.destroy(listPtr);
}
pub fn deinit(self: *This) void {
inline for (self.componentTypes) |T| {
const list = self.getComponentList(T) catch unreachable;
list.deinit();
self.allocator.destroy(list);
}
self.componentLists.deinit();
}
pub fn set(self: *This, entity: EntityType, comptime T: type, component: T) !void {
var list = try self.getComponentList(T);
try list.add(entity, component);
}
pub fn get(self: *This, entity: EntityType, comptime T: type) ?*T {
var list = self.getComponentList(T) catch {
std.log.err("Failed to get {any} component list for entity {d}", .{ T, entity });
@panic("Failed to get component list");
};
return list.get(entity);
}
/// Unsafe version of get. Will panic if the component is not registered. Use with caution.
pub fn getKnown(self: *This, entity: EntityType, comptime T: type) *T {
var list = self.getComponentList(T) catch unreachable;
return list.get(entity).?;
}
pub fn remove(self: *This, entity: EntityType, comptime T: type) !void {
var list = try self.getComponentList(T);
try list.remove(entity);
}
pub fn removeAll(self: *This, entity: EntityType) void {
inline for (self.componentTypes) |T| {
const list = self.getComponentList(T) catch unreachable;
list.remove(entity) catch {}; // We don't care if there are no components of this type for this entity.
}
}
pub fn iterator(self: *This, comptime T: type) Iterator(StorageType(T), *T) {
var list = self.getComponentList(T) catch return .{};
return list.iterator();
}
fn getComponentList(self: *This, comptime T: type) !*StorageType(T) {
const addr = self.componentLists.get(typeid(T)) orelse return error.ComponentTypeNotRegistered;
return @as(*StorageType(T), @ptrFromInt(addr));
}
};
}
pub fn ComponentFixedList(comptime T: type) type {
return struct {
const This = @This();
const List = std.DoublyLinkedList(usize);
components: [MAX_COMPONENTS]T = undefined,
entityIndexMap: EntityIdMap(usize),
arena: std.heap.ArenaAllocator,
freelist: List = .{},
pub fn init(allocator: std.mem.Allocator) !This {
var arena = std.heap.ArenaAllocator.init(allocator);
var freelist = List{};
var idx: usize = 0;
while (idx < MAX_COMPONENTS) : (idx += 1) {
const node = try arena.allocator().create(List.Node);
node.* = .{ .data = idx };
freelist.append(node);
}
return .{
.entityIndexMap = EntityIdMap(usize).init(allocator),
.arena = arena,
.freelist = freelist,
};
}
pub fn deinit(self: *This) void {
// If components have a `fn deinit(...)` declaration, we need to call it.
for (self.entityIndexMap.keys()) |entity| {
const component = self.get(entity) orelse continue;
self.deinitComponent(component);
}
self.entityIndexMap.deinit();
self.arena.deinit();
}
pub fn add(self: *This, entity: EntityType, component: T) !void {
const node =
if (self.freelist.popFirst()) |free_node|
free_node
else
return error.SizeExceeded;
const idx = node.data;
const result = try self.entityIndexMap.getOrPutValue(entity, idx);
if (result.found_existing) {
std.log.err("Component type {} already set for entity {d}", .{ T, entity });
self.freelist.append(node);
return error.ComponentAlreadySet;
}
self.components[idx] = component;
}
pub fn get(self: *This, entity: EntityType) ?*T {
const idx = self.entityIndexMap.get(entity) orelse return null;
return &self.components[idx];
}
pub fn remove(self: *This, entity: EntityType) !void {
const idx = self.entityIndexMap.get(entity) orelse return error.NoSuchElement;
_ = self.entityIndexMap.swapRemove(entity);
self.deinitComponent(&self.components[idx]);
const node = try self.arena.allocator().create(List.Node);
node.* = .{
.data = idx,
};
self.freelist.append(node);
}
pub fn iterator(self: *This) Iterator(This, *T) {
return .{ .parent = self };
}
pub fn size(self: *const This) usize {
return MAX_COMPONENTS - self.freelist.len;
}
fn deinitComponent(self: *This, component: *T) void {
_ = self;
if (@hasDecl(T, "deinit")) component.deinit();
}
};
}
fn Iterator(comptime ParentType: type, comptime ValType: type) type {
return struct {
const This = @This();
parent: ?*ParentType = null,
idx: usize = 0,
pub fn next(self: *This) ?ValType {
if (self.parent == null or self.idx >= self.parent.?.size()) return null;
defer self.idx += 1;
return &self.parent.?.components[self.idx];
}
pub fn reset(self: *This) void {
self.idx = 0;
}
};
}
/// Returns a distinct ID for each type passed in. IDs are consistent across invocations.
fn typeid(comptime T: type) ComponentType {
// This exploits the zig compiler's memoization of globals defined for each comptime argument.
// We need to assign T to a field to make sure that each distinct type invocation of this function generates a new struct i.e. new address of the byte field.
const H = struct {
const _ = T;
const byte: u8 = 0;
};
return @as(usize, @intFromPtr(&H.byte));
}
const expect = std.testing.expect;
test "typeid" {
const id1 = typeid(usize);
const id2 = typeid(usize);
const s = struct {
x: f32,
y: f32,
};
const id3 = typeid(s);
try expect(id1 == id2);
try expect(id1 != id3);
}
test "ComponentFixedList" {
const TestComponent = struct {};
const entity = 0;
var list = try ComponentFixedList(TestComponent).init(std.testing.allocator);
defer list.deinit();
const component = TestComponent{};
try list.add(entity, component);
// Ensure access by entity id retrieves component.
try expect(list.size() == 1);
try expect(list.entityIndexMap.contains(entity));
try expect(std.meta.eql(component, list.get(entity).?.*));
// Ensure access by iterator retrieves component.
var it = list.iterator();
var count: usize = 0;
while (it.next()) |val| : (count += 1) {
try expect(std.meta.eql(component, val.*));
}
// Ensure iterator respects list size.
try expect(count == 1);
// Ensure removed elements are not accessible.
try list.remove(entity);
try expect(list.size() == 0);
it.reset();
try expect(it.next() == null);
}
test "component manager instantiation with types" {
const TestComponent1 = struct {};
const TestComponent2 = struct {};
var c = try ComponentManager(.{ TestComponent1, TestComponent2 }).init(std.testing.allocator);
defer c.deinit();
const list1 = try c.getComponentList(TestComponent1);
const list2 = try c.getComponentList(TestComponent2);
try expect(list1.size() == 0);
try expect(list2.size() == 0);
try c.set(0, TestComponent1, .{});
try expect(list1.size() == 1);
try expect(list2.size() == 0);
try c.set(0, TestComponent2, .{});
try expect(list1.size() == 1);
try expect(list2.size() == 1);
c.removeAll(0);
try expect(list1.size() == 0);
try expect(list2.size() == 0);
}
test "remove component from entity" {
const TestComponent = struct {};
var ecs = try Ecs(.{TestComponent}).init(std.testing.allocator);
defer ecs.deinit();
const entity = try ecs.registerEntity();
try ecs.setComponent(entity, TestComponent, .{});
try expect(ecs.hasComponent(entity, TestComponent));
try ecs.removeComponent(entity, TestComponent);
try expect(!ecs.hasComponent(entity, TestComponent));
}
test "iterate components" {
const T = struct {};
var ecs = try Ecs(.{T}).init(std.testing.allocator);
defer ecs.deinit();
var it = ecs.componentManager.iterator(T);
var count: usize = 0;
while (it.next()) |_| : (count += 1) {}
try expect(count == 0);
const e1 = try ecs.registerEntity();
try ecs.setComponent(e1, T, .{});
const e2 = try ecs.registerEntity();
try ecs.setComponent(e2, T, .{});
const e3 = try ecs.registerEntity();
try ecs.setComponent(e3, T, .{});
it.reset();
while (it.next()) |_| : (count += 1) {}
try expect(count == 3);
ecs.removeEntity(e2);
count = 0;
it.reset();
while (it.next()) |_| : (count += 1) {}
try expect(count == 2);
}
test "test Ecs instantiation" {
const TestComponent = struct {};
var ecs = try Ecs(.{TestComponent}).init(std.testing.allocator);
defer ecs.deinit();
}