// --- LIBS --- const std = @import("std"); const Archetype = @import("archetype.zig"); const Signature = @import("signature.zig"); const EntityRecord = @import("entity_record.zig"); const QueryFilters = @import("query_filters.zig"); const Query = @import("query.zig").Query; const TypeID = @import("../template/type_id.zig").of; const Self = @This(); // // FIELDS // archetypes: std.ArrayList(Archetype), entity_map: std.AutoHashMap(u64, EntityRecord), signature_map: std.AutoHashMap(u64, usize), next_entity: u64, alloc: std.mem.Allocator, /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn init(alloc: std.mem.Allocator) Self { return .{ .archetypes = .empty, .entity_map = .init(alloc), .signature_map = .init(alloc), .next_entity = 1, .alloc = alloc, }; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn deinit(self: *Self) void { // --- ARCHETYPES --- for (self.archetypes.items) |*arch| { arch.deinit(); } self.entity_map.deinit(); self.archetypes.deinit(self.alloc); self.signature_map.deinit(); } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn spawn( self: *Self, components: anytype, ) !void { _ = try self.spawnEntity(components); } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn addComponents( self: *Self, entity: u64, components: anytype, ) !void { const record = self.entity_map.get(entity) orelse return; const new_fields = std.meta.fields(@TypeOf(components)); const old_count = self.archetypes.items[record.archetype_idx].signature.ids.len; // --- BUILD COMBINED SIGNATURE (dedup against existing) --- var ids = try self.alloc.alloc(u64, old_count + new_fields.len); defer self.alloc.free(ids); for (self.archetypes.items[record.archetype_idx].signature.ids, 0..) |id, i| { ids[i] = id; } var total: usize = old_count; inline for (new_fields) |field| { const component = @field(components, field.name); const id = TypeID(@TypeOf(component)); var dominated = false; for (ids[0..total]) |existing| { if (existing == id) { dominated = true; break; } } if (!dominated) { ids[total] = id; total += 1; } } if (total == old_count) return; std.mem.sort(u64, ids[0..total], {}, std.sort.asc(u64)); const signature_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(ids[0..total])); // --- FIND OR CREATE NEW ARCHETYPE --- const new_arch_idx = self.signature_map.get(signature_hash) orelse blk: { const old = &self.archetypes.items[record.archetype_idx]; const sig_ids = try self.alloc.dupe(u64, ids[0..total]); var arch: Archetype = .init(.{ .ids = sig_ids }, self.alloc); // --- CLONE OLD COLUMNS --- for (old.columns.items) |*col| { try arch.columns.append(self.alloc, try col.cloneEmpty()); } // --- COPY OLD TYPE MAPPINGS --- var old_iter = old.type_map.iterator(); while (old_iter.next()) |entry| { try arch.type_map.put(entry.key_ptr.*, entry.value_ptr.*); } // --- ADD NEW COLUMNS --- inline for (new_fields) |field| { const component = @field(components, field.name); const T = @TypeOf(component); var already = false; for (old.signature.ids) |old_id| { if (old_id == TypeID(T)) { already = true; break; } } if (!already) try arch.addColumn(T); } try self.archetypes.append(self.alloc, arch); const idx = self.archetypes.items.len - 1; try self.signature_map.put(signature_hash, idx); break :blk idx; }; // --- RE-FETCH --- const old_arch = &self.archetypes.items[record.archetype_idx]; const new_arch = &self.archetypes.items[new_arch_idx]; // --- COPY OLD COMPONENT VALUES --- try new_arch.entities.append( self.alloc, entity, ); for (old_arch.columns.items, 0..) |*old_col, i| { if (old_col.getPtr(record.row)) |ptr| { try new_arch.columns.items[i].append(ptr); } } // --- ADD NEW COMPONENT VALUES --- inline for (new_fields) |field| { const component = @field(components, field.name); const T = @TypeOf(component); var already = false; for (old_arch.signature.ids) |old_id| { if (old_id == TypeID(T)) { already = true; break; } } if (!already) { const idx = new_arch.type_map.get(TypeID(T)) orelse return error.ComponentNotFound; try new_arch.columns.items[idx].append(&component); } } // --- REMOVE FROM OLD ARCHETYPE --- if (old_arch.remove(record.row)) |moved| { self.entity_map.getPtr(moved).?.row = record.row; } // --- UPDATE ENTITY MAP --- self.entity_map.getPtr(entity).?.archetype_idx = new_arch_idx; self.entity_map.getPtr(entity).?.row = new_arch.entities.items.len - 1; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn getComponent( self: *Self, comptime T: type, entity: u64, ) ?*T { const record = self.entity_map.get(entity) orelse return null; const arch = &self.archetypes.items[record.archetype_idx]; return arch.get(T, record.row); } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn getComponents( self: *Self, comptime Result: type, entity: u64, ) error{ComponentNotRegistered}!Result { // --- RESULT --- var result: Result = undefined; // --- GET FIELDS --- inline for (std.meta.fields(Result)) |field| { // --- FIELD INFO --- const FieldType = field.type; switch (@typeInfo(FieldType)) { .optional => |opt| { const ptr_info = @typeInfo(opt.child).pointer; @field(result, field.name) = self.getComponent(ptr_info.child, entity); }, .pointer => |ptr| { @field(result, field.name) = self.getComponent(ptr.child, entity) orelse return error.ComponentNotRegistered; }, else => @compileError( "getComponents fields must be *T or ?*T; field '" ++ field.name ++ "' has type '" ++ @typeName(FieldType) ++ "'", ), } } return result; } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn despawn( self: *Self, entity: u64, ) void { const record = self.entity_map.get(entity) orelse return; if (self.archetypes.items[record.archetype_idx].remove(record.row)) |moved| { self.entity_map.getPtr(moved).?.row = record.row; } _ = self.entity_map.remove(entity); } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn spawnEntity( self: *Self, components: anytype, ) !u64 { const Components = @TypeOf(components); const fields = std.meta.fields(Components); // --- SIGNATURE --- var ids: [fields.len]u64 = undefined; inline for (fields, 0..) |field, i| { const component = @field( components, field.name, ); ids[i] = TypeID(@TypeOf(component)); } // --- SORT TYPE ID'S --- std.mem.sort( u64, &ids, {}, std.sort.asc(u64), ); const signature_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(&ids)); // --- FIND OR CREATE ARCHETYPE --- const archetype_idx = self.signature_map.get(signature_hash) orelse blk: { const sig_ids = try self.alloc.dupe(u64, &ids); var arch: Archetype = .init(.{ .ids = sig_ids }, self.alloc); // --- NEW COLUMNS --- inline for (fields) |field| { const component = @field(components, field.name); try arch.addColumn(@TypeOf(component)); } // --- ADD ARCHETYPE --- try self.archetypes.append( self.alloc, arch, ); // --- ADD SIGNATURE --- const idx = self.archetypes.items.len - 1; try self.signature_map.put( signature_hash, idx, ); break :blk idx; }; // --- NEW ENTITY --- const entity = self.next_entity; self.next_entity += 1; // --- ADD ROW --- var arch = &self.archetypes.items[archetype_idx]; const row = arch.entities.items.len; try arch.add( entity, components, ); // --- STORE ENTITY LOCATION --- try self.entity_map.put(entity, .{ .archetype_idx = archetype_idx, .row = row, }); return entity; } /// ---------------------------------------------------- /// ---------------------------------------------------- fn createEntity(self: *Self) u64 { const entity = self.next_entity; self.next_entity += 1; return entity; } /// ---------------------------------------------------- /// ---------------------------------------------------- fn createArchetype( self: *Self, comptime Components: type, ) !usize { const fields = std.meta.fields(Components); var ids = try self.alloc.alloc( u64, fields.len, ); inline for (fields, 0..) |field, i| { ids[i] = TypeID(field.type); } const sig: Signature = .{ .ids = ids, }; var arch: Archetype = .init( sig, self.alloc, ); inline for (fields) |field| { try arch.addColumn( field.type, ); } try self.archetypes.append( self.alloc, arch, ); return self.archetypes.items.len - 1; } /// ---------------------------------------------------- /// Example: /// ``` /// var query = world.query(struct { /// player: ?*Player, /// pos: *Position, /// vel: *Velocity, /// }, .{}); /// while (query.next()) |entity| { /// if (entity.player) |player| {} /// } /// ``` /// ---------------------------------------------------- pub fn query( self: *Self, comptime Result: type, comptime Filters: QueryFilters, ) Query(Result, Filters) { return .init(self); }