diff --git a/src/world/column.zig b/src/world/column.zig index 3e17281..8b1bce3 100644 --- a/src/world/column.zig +++ b/src/world/column.zig @@ -37,6 +37,10 @@ deinit_fn: *const fn ( alloc: std.mem.Allocator, ) void, +init_fn: *const fn ( + alloc: std.mem.Allocator, +) anyerror!Self, + /// ------------------------------------------ /// ------------------------------------------ pub fn init( @@ -101,6 +105,12 @@ pub fn init( allocator.destroy(col); } }.f, + + .init_fn = struct { + fn f(a: std.mem.Allocator) !Self { + return init(T, a); + } + }.f, }; } @@ -113,6 +123,27 @@ pub fn deinit(self: *Self) void { ); } +/// ------------------------------------------ +/// Create a new empty Column of the same type +/// ------------------------------------------ +pub fn cloneEmpty(self: *Self) !Self { + return self.init_fn(self.alloc); +} + +/// ------------------------------------------ +/// Copy a single element from another column +/// Both columns must store the same concrete type +/// ------------------------------------------ +pub fn copyElement( + self: *Self, + other: *Self, + idx: usize, +) !void { + if (other.getPtr(idx)) |ptr| { + try self.append(ptr); + } +} + /// ------------------------------------------ /// ------------------------------------------ pub fn append( diff --git a/src/world/world.zig b/src/world/world.zig index cdeb9a2..8c281b1 100644 --- a/src/world/world.zig +++ b/src/world/world.zig @@ -51,6 +51,180 @@ pub fn spawn( _ = 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(