-
-
Save slimsag/477f9f4c68667e71fbe584a700cfd87d to your computer and use it in GitHub Desktop.
"Let's build an Entity Component System (part 2): databases" PARTIAL code (creating entities only)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const testing = std.testing; | |
const Allocator = std.mem.Allocator; | |
/// An entity ID uniquely identifies an entity globally within an Entities set. | |
pub const EntityID = u64; | |
pub const void_archetype_hash = std.math.maxInt(u64); | |
/// Represents the storage for a single type of component within a single type of entity. | |
/// | |
/// Database equivalent: a column within a table. | |
pub fn ComponentStorage(comptime Component: type) type { | |
return struct { | |
/// A reference to the total number of entities with the same type as is being stored here. | |
total_rows: *usize, | |
/// The actual densely stored component data. | |
data: std.ArrayListUnmanaged(Component) = .{}, | |
const Self = @This(); | |
pub fn deinit(storage: *Self, allocator: Allocator) void { | |
storage.data.deinit(allocator); | |
} | |
}; | |
} | |
/// A type-erased representation of ComponentStorage(T) (where T is unknown). | |
pub const ErasedComponentStorage = struct { | |
ptr: *anyopaque, | |
deinit: fn (erased: *anyopaque, allocator: Allocator) void, | |
// Casts this `ErasedComponentStorage` into `*ComponentStorage(Component)` with the given type | |
// (unsafe). | |
pub fn cast(ptr: *anyopaque, comptime Component: type) *ComponentStorage(Component) { | |
var aligned = @alignCast(@alignOf(*ComponentStorage(Component)), ptr); | |
return @ptrCast(*ComponentStorage(Component), aligned); | |
} | |
}; | |
pub const ArchetypeStorage = struct { | |
allocator: Allocator, | |
/// The hash of every component name in this archetype, i.e. the name of this archetype. | |
hash: u64, | |
/// A mapping of rows in the table to entity IDs. | |
/// | |
/// Doubles as the counter of total number of rows that have been reserved within this | |
/// archetype table. | |
entity_ids: std.ArrayListUnmanaged(EntityID) = .{}, | |
/// A string hashmap of component_name -> type-erased *ComponentStorage(Component) | |
components: std.StringArrayHashMapUnmanaged(ErasedComponentStorage), | |
pub fn deinit(storage: *ArchetypeStorage) void { | |
for (storage.components.values()) |erased| { | |
erased.deinit(erased.ptr, storage.allocator); | |
} | |
storage.entity_ids.deinit(storage.allocator); | |
storage.components.deinit(storage.allocator); | |
} | |
/// New reserves a row for storing an entity within this archetype table. | |
pub fn new(storage: *ArchetypeStorage, entity: EntityID) !u32 { | |
// Return a new row index | |
const new_row_index = storage.entity_ids.items.len; | |
try storage.entity_ids.append(storage.allocator, entity); | |
return @intCast(u32, new_row_index); | |
} | |
/// Undoes the last call to the new() operation, effectively unreserving the row that was last | |
/// reserved. | |
pub fn undoNew(storage: *ArchetypeStorage) void { | |
_ = storage.entity_ids.pop(); | |
} | |
}; | |
pub const Entities = struct { | |
allocator: Allocator, | |
/// TODO! | |
counter: EntityID = 0, | |
/// A mapping of entity IDs (array indices) to where an entity's component values are actually | |
/// stored. | |
entities: std.AutoHashMapUnmanaged(EntityID, Pointer) = .{}, | |
/// A mapping of archetype hash to their storage. | |
/// | |
/// Database equivalent: table name -> tables representing entities. | |
archetypes: std.AutoArrayHashMapUnmanaged(u64, ArchetypeStorage) = .{}, | |
/// Points to where an entity is stored, specifically in which archetype table and in which row | |
/// of that table. That is, the entity's component values are stored at: | |
/// | |
/// ``` | |
/// Entities.archetypes[ptr.archetype_index].rows[ptr.row_index] | |
/// ``` | |
/// | |
pub const Pointer = struct { | |
archetype_index: u16, | |
row_index: u32, | |
}; | |
pub fn init(allocator: Allocator) !Entities { | |
var entities = Entities{ .allocator = allocator }; | |
try entities.archetypes.put(allocator, void_archetype_hash, ArchetypeStorage{ | |
.allocator = allocator, | |
.components = .{}, | |
.hash = void_archetype_hash, | |
}); | |
return entities; | |
} | |
pub fn deinit(entities: *Entities) void { | |
entities.entities.deinit(entities.allocator); | |
var iter = entities.archetypes.iterator(); | |
while (iter.next()) |entry| { | |
entry.value_ptr.deinit(); | |
} | |
entities.archetypes.deinit(entities.allocator); | |
} | |
pub fn initErasedStorage(entities: *const Entities, total_rows: *usize, comptime Component: type) !ErasedComponentStorage { | |
var new_ptr = try entities.allocator.create(ComponentStorage(Component)); | |
new_ptr.* = ComponentStorage(Component){ .total_rows = total_rows }; | |
return ErasedComponentStorage{ | |
.ptr = new_ptr, | |
.deinit = (struct { | |
pub fn deinit(erased: *anyopaque, allocator: Allocator) void { | |
var ptr = ErasedComponentStorage.cast(erased, Component); | |
ptr.deinit(allocator); | |
allocator.destroy(ptr); | |
} | |
}).deinit, | |
}; | |
} | |
/// Returns a new entity. | |
pub fn new(entities: *Entities) !EntityID { | |
const new_id = entities.counter; | |
entities.counter += 1; | |
var void_archetype = entities.archetypes.getPtr(void_archetype_hash).?; | |
const new_row = try void_archetype.new(new_id); | |
const void_pointer = Pointer{ | |
.archetype_index = 0, // void archetype is guaranteed to be first index | |
.row_index = new_row, | |
}; | |
entities.entities.put(entities.allocator, new_id, void_pointer) catch |err| { | |
void_archetype.undoNew(); | |
return err; | |
}; | |
return new_id; | |
} | |
}; | |
test "ecs" { | |
const allocator = testing.allocator; | |
var world = try Entities.init(allocator); | |
defer world.deinit(); | |
const player = try world.new(); | |
_ = player; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment