ecs: refactor comptime.zig -> Archetype.zig
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
883a9a7807
commit
679a05faf4
5 changed files with 77 additions and 84 deletions
|
|
@ -10,7 +10,6 @@ const testing = std.testing;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const StringTable = @import("StringTable.zig");
|
const StringTable = @import("StringTable.zig");
|
||||||
const comp = @import("comptime.zig");
|
|
||||||
|
|
||||||
const Archetype = @This();
|
const Archetype = @This();
|
||||||
|
|
||||||
|
|
@ -75,7 +74,7 @@ pub fn appendUndefined(storage: *Archetype, gpa: Allocator) !u32 {
|
||||||
|
|
||||||
// TODO: comptime: missing a runtime variant of this function
|
// TODO: comptime: missing a runtime variant of this function
|
||||||
pub fn append(storage: *Archetype, gpa: Allocator, row: anytype) !u32 {
|
pub fn append(storage: *Archetype, gpa: Allocator, row: anytype) !u32 {
|
||||||
comp.debugAssertRowType(storage, row);
|
debugAssertRowType(storage, row);
|
||||||
|
|
||||||
try storage.ensureUnusedCapacity(gpa, 1);
|
try storage.ensureUnusedCapacity(gpa, 1);
|
||||||
assert(storage.len < storage.capacity);
|
assert(storage.len < storage.capacity);
|
||||||
|
|
@ -130,7 +129,7 @@ pub fn setCapacity(storage: *Archetype, gpa: Allocator, new_capacity: usize) !vo
|
||||||
// TODO: comptime: missing a runtime variant of this function
|
// TODO: comptime: missing a runtime variant of this function
|
||||||
/// Sets the entire row's values in the table.
|
/// Sets the entire row's values in the table.
|
||||||
pub fn setRow(storage: *Archetype, row_index: u32, row: anytype) void {
|
pub fn setRow(storage: *Archetype, row_index: u32, row: anytype) void {
|
||||||
comp.debugAssertRowType(storage, row);
|
debugAssertRowType(storage, row);
|
||||||
|
|
||||||
const fields = std.meta.fields(@TypeOf(row));
|
const fields = std.meta.fields(@TypeOf(row));
|
||||||
inline for (fields, 0..) |field, index| {
|
inline for (fields, 0..) |field, index| {
|
||||||
|
|
@ -147,18 +146,18 @@ pub fn setRow(storage: *Archetype, row_index: u32, row: anytype) void {
|
||||||
pub fn set(storage: *Archetype, row_index: u32, name: StringTable.Index, component: anytype) void {
|
pub fn set(storage: *Archetype, row_index: u32, name: StringTable.Index, component: anytype) void {
|
||||||
const ColumnType = @TypeOf(component);
|
const ColumnType = @TypeOf(component);
|
||||||
if (@sizeOf(ColumnType) == 0) return;
|
if (@sizeOf(ColumnType) == 0) return;
|
||||||
if (comp.is_debug) comp.debugAssertColumnType(storage, storage.columnByName(name).?, @TypeOf(component));
|
if (is_debug) debugAssertColumnType(storage, storage.columnByName(name).?, @TypeOf(component));
|
||||||
storage.setDynamic(
|
storage.setDynamic(
|
||||||
row_index,
|
row_index,
|
||||||
name,
|
name,
|
||||||
std.mem.asBytes(&component),
|
std.mem.asBytes(&component),
|
||||||
@alignOf(@TypeOf(component)),
|
@alignOf(@TypeOf(component)),
|
||||||
comp.typeId(@TypeOf(component)),
|
typeId(@TypeOf(component)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setDynamic(storage: *Archetype, row_index: u32, name: StringTable.Index, component: []const u8, alignment: u16, type_id: u32) void {
|
pub fn setDynamic(storage: *Archetype, row_index: u32, name: StringTable.Index, component: []const u8, alignment: u16, type_id: u32) void {
|
||||||
if (comp.is_debug) {
|
if (is_debug) {
|
||||||
// TODO: improve error messages
|
// TODO: improve error messages
|
||||||
assert(storage.len != 0 and storage.len >= row_index);
|
assert(storage.len != 0 and storage.len >= row_index);
|
||||||
assert(storage.columnByName(name).?.type_id == type_id);
|
assert(storage.columnByName(name).?.type_id == type_id);
|
||||||
|
|
@ -173,15 +172,15 @@ pub fn setDynamic(storage: *Archetype, row_index: u32, name: StringTable.Index,
|
||||||
|
|
||||||
pub fn get(storage: *Archetype, row_index: u32, name: StringTable.Index, comptime ColumnType: type) ?ColumnType {
|
pub fn get(storage: *Archetype, row_index: u32, name: StringTable.Index, comptime ColumnType: type) ?ColumnType {
|
||||||
if (@sizeOf(ColumnType) == 0) return {};
|
if (@sizeOf(ColumnType) == 0) return {};
|
||||||
if (comp.is_debug) comp.debugAssertColumnType(storage, storage.columnByName(name) orelse return null, ColumnType);
|
if (is_debug) debugAssertColumnType(storage, storage.columnByName(name) orelse return null, ColumnType);
|
||||||
|
|
||||||
const bytes = storage.getDynamic(row_index, name, @sizeOf(ColumnType), @alignOf(ColumnType), comp.typeId(ColumnType)) orelse return null;
|
const bytes = storage.getDynamic(row_index, name, @sizeOf(ColumnType), @alignOf(ColumnType), typeId(ColumnType)) orelse return null;
|
||||||
return @as(*ColumnType, @alignCast(@ptrCast(bytes.ptr))).*;
|
return @as(*ColumnType, @alignCast(@ptrCast(bytes.ptr))).*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDynamic(storage: *Archetype, row_index: u32, name: StringTable.Index, size: u32, alignment: u16, type_id: u32) ?[]u8 {
|
pub fn getDynamic(storage: *Archetype, row_index: u32, name: StringTable.Index, size: u32, alignment: u16, type_id: u32) ?[]u8 {
|
||||||
const values = storage.getColumnValuesRaw(name) orelse return null;
|
const values = storage.getColumnValuesRaw(name) orelse return null;
|
||||||
if (comp.is_debug) {
|
if (is_debug) {
|
||||||
// TODO: improve error messages
|
// TODO: improve error messages
|
||||||
assert(storage.columnByName(name).?.size == size);
|
assert(storage.columnByName(name).?.size == size);
|
||||||
assert(storage.columnByName(name).?.alignment == alignment);
|
assert(storage.columnByName(name).?.alignment == alignment);
|
||||||
|
|
@ -222,7 +221,7 @@ pub fn hasComponent(storage: *Archetype, name: StringTable.Index) bool {
|
||||||
|
|
||||||
pub fn getColumnValues(storage: *Archetype, name: StringTable.Index, comptime ColumnType: type) ?[]ColumnType {
|
pub fn getColumnValues(storage: *Archetype, name: StringTable.Index, comptime ColumnType: type) ?[]ColumnType {
|
||||||
const values = storage.getColumnValuesRaw(name) orelse return null;
|
const values = storage.getColumnValuesRaw(name) orelse return null;
|
||||||
if (comp.is_debug) comp.debugAssertColumnType(storage, storage.columnByName(name).?, ColumnType);
|
if (is_debug) debugAssertColumnType(storage, storage.columnByName(name).?, ColumnType);
|
||||||
var ptr = @as([*]ColumnType, @ptrCast(@alignCast(values.ptr)));
|
var ptr = @as([*]ColumnType, @ptrCast(@alignCast(values.ptr)));
|
||||||
const column_values = ptr[0..storage.capacity];
|
const column_values = ptr[0..storage.capacity];
|
||||||
return column_values;
|
return column_values;
|
||||||
|
|
@ -239,3 +238,64 @@ pub inline fn columnByName(storage: *Archetype, name: StringTable.Index) ?*Colum
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const is_debug = builtin.mode == .Debug;
|
||||||
|
|
||||||
|
/// Returns a unique comptime usize integer representing the type T. Value will change across
|
||||||
|
/// different compilations.
|
||||||
|
pub fn typeId(comptime T: type) u32 {
|
||||||
|
_ = T;
|
||||||
|
return @truncate(@intFromPtr(&struct {
|
||||||
|
var x: u8 = 0;
|
||||||
|
}.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that T matches the type of the column.
|
||||||
|
pub inline fn debugAssertColumnType(storage: *Archetype, column: *Archetype.Column, comptime T: type) void {
|
||||||
|
if (is_debug) {
|
||||||
|
if (typeId(T) != column.type_id) std.debug.panic("unexpected type: {s} expected: {s}", .{
|
||||||
|
@typeName(T),
|
||||||
|
storage.component_names.string(column.name),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that a tuple `row` to be e.g. appended to an archetype has values that actually match
|
||||||
|
/// all of the columns of the archetype table.
|
||||||
|
pub inline fn debugAssertRowType(storage: *Archetype, row: anytype) void {
|
||||||
|
if (is_debug) {
|
||||||
|
inline for (std.meta.fields(@TypeOf(row)), 0..) |field, index| {
|
||||||
|
debugAssertColumnType(storage, &storage.columns[index], field.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: comptime refactor
|
||||||
|
pub fn Slicer(comptime all_components: anytype) type {
|
||||||
|
return struct {
|
||||||
|
archetype: *Archetype,
|
||||||
|
|
||||||
|
pub fn slice(
|
||||||
|
slicer: @This(),
|
||||||
|
// TODO: cleanup comptime
|
||||||
|
comptime namespace_name: std.meta.FieldEnum(@TypeOf(all_components)),
|
||||||
|
comptime component_name: std.meta.FieldEnum(@TypeOf(@field(all_components, @tagName(namespace_name)))),
|
||||||
|
) []@field(
|
||||||
|
@field(all_components, @tagName(namespace_name)),
|
||||||
|
@tagName(component_name),
|
||||||
|
).type {
|
||||||
|
// TODO: cleanup comptime
|
||||||
|
const Type = @field(
|
||||||
|
@field(all_components, @tagName(namespace_name)),
|
||||||
|
@tagName(component_name),
|
||||||
|
).type;
|
||||||
|
if (namespace_name == .entity and component_name == .id) {
|
||||||
|
const name_id = slicer.archetype.component_names.index("id").?;
|
||||||
|
return slicer.archetype.getColumnValues(name_id, Type).?[0..slicer.archetype.len];
|
||||||
|
}
|
||||||
|
const name = @tagName(namespace_name) ++ "." ++ @tagName(component_name);
|
||||||
|
const name_id = slicer.archetype.component_names.index(name).?;
|
||||||
|
return slicer.archetype.getColumnValues(name_id, Type).?[0..slicer.archetype.len];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const Archetype = @import("Archetype.zig");
|
|
||||||
const StringTable = @import("StringTable.zig");
|
|
||||||
|
|
||||||
pub const is_debug = builtin.mode == .Debug;
|
|
||||||
|
|
||||||
/// Returns a unique comptime usize integer representing the type T. Value will change across
|
|
||||||
/// different compilations.
|
|
||||||
pub fn typeId(comptime T: type) u32 {
|
|
||||||
_ = T;
|
|
||||||
return @truncate(@intFromPtr(&struct {
|
|
||||||
var x: u8 = 0;
|
|
||||||
}.x));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Asserts that T matches the type of the column.
|
|
||||||
pub inline fn debugAssertColumnType(storage: *Archetype, column: *Archetype.Column, comptime T: type) void {
|
|
||||||
if (is_debug) {
|
|
||||||
if (typeId(T) != column.type_id) std.debug.panic("unexpected type: {s} expected: {s}", .{
|
|
||||||
@typeName(T),
|
|
||||||
storage.component_names.string(column.name),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Asserts that a tuple `row` to be e.g. appended to an archetype has values that actually match
|
|
||||||
/// all of the columns of the archetype table.
|
|
||||||
pub inline fn debugAssertRowType(storage: *Archetype, row: anytype) void {
|
|
||||||
if (is_debug) {
|
|
||||||
inline for (std.meta.fields(@TypeOf(row)), 0..) |field, index| {
|
|
||||||
debugAssertColumnType(storage, &storage.columns[index], field.type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: comptime refactor
|
|
||||||
pub fn ArchetypeSlicer(comptime all_components: anytype) type {
|
|
||||||
return struct {
|
|
||||||
archetype: *Archetype,
|
|
||||||
|
|
||||||
pub fn slice(
|
|
||||||
slicer: @This(),
|
|
||||||
// TODO: cleanup comptime
|
|
||||||
comptime namespace_name: std.meta.FieldEnum(@TypeOf(all_components)),
|
|
||||||
comptime component_name: std.meta.FieldEnum(@TypeOf(@field(all_components, @tagName(namespace_name)))),
|
|
||||||
) []@field(
|
|
||||||
@field(all_components, @tagName(namespace_name)),
|
|
||||||
@tagName(component_name),
|
|
||||||
).type {
|
|
||||||
// TODO: cleanup comptime
|
|
||||||
const Type = @field(
|
|
||||||
@field(all_components, @tagName(namespace_name)),
|
|
||||||
@tagName(component_name),
|
|
||||||
).type;
|
|
||||||
if (namespace_name == .entity and component_name == .id) {
|
|
||||||
const name_id = slicer.archetype.component_names.index("id").?;
|
|
||||||
return slicer.archetype.getColumnValues(name_id, Type).?[0..slicer.archetype.len];
|
|
||||||
}
|
|
||||||
const name = @tagName(namespace_name) ++ "." ++ @tagName(component_name);
|
|
||||||
const name_id = slicer.archetype.component_names.index(name).?;
|
|
||||||
return slicer.archetype.getColumnValues(name_id, Type).?[0..slicer.archetype.len];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,6 @@ const assert = std.debug.assert;
|
||||||
const query_mod = @import("query.zig");
|
const query_mod = @import("query.zig");
|
||||||
const Archetype = @import("Archetype.zig");
|
const Archetype = @import("Archetype.zig");
|
||||||
const StringTable = @import("StringTable.zig");
|
const StringTable = @import("StringTable.zig");
|
||||||
const comp = @import("comptime.zig");
|
|
||||||
const ComponentTypesByName = @import("../module.zig").ComponentTypesByName;
|
const ComponentTypesByName = @import("../module.zig").ComponentTypesByName;
|
||||||
|
|
||||||
/// An entity ID uniquely identifies an entity globally within an Entities set.
|
/// An entity ID uniquely identifies an entity globally within an Entities set.
|
||||||
|
|
@ -128,7 +127,7 @@ pub fn Entities(comptime all_components: anytype) type {
|
||||||
const columns = try allocator.alloc(Archetype.Column, 1);
|
const columns = try allocator.alloc(Archetype.Column, 1);
|
||||||
columns[0] = .{
|
columns[0] = .{
|
||||||
.name = entities.id_name,
|
.name = entities.id_name,
|
||||||
.type_id = comp.typeId(EntityID),
|
.type_id = Archetype.typeId(EntityID),
|
||||||
.size = @sizeOf(EntityID),
|
.size = @sizeOf(EntityID),
|
||||||
.alignment = @alignOf(EntityID),
|
.alignment = @alignOf(EntityID),
|
||||||
.values = undefined,
|
.values = undefined,
|
||||||
|
|
@ -220,7 +219,7 @@ pub fn Entities(comptime all_components: anytype) type {
|
||||||
const archetype_entry = try entities.archetypeOrPut(&.{
|
const archetype_entry = try entities.archetypeOrPut(&.{
|
||||||
.{
|
.{
|
||||||
.name = entities.id_name,
|
.name = entities.id_name,
|
||||||
.type_id = comp.typeId(EntityID),
|
.type_id = Archetype.typeId(EntityID),
|
||||||
.size = @sizeOf(EntityID),
|
.size = @sizeOf(EntityID),
|
||||||
.alignment = @alignOf(EntityID),
|
.alignment = @alignOf(EntityID),
|
||||||
.values = undefined,
|
.values = undefined,
|
||||||
|
|
@ -308,7 +307,7 @@ pub fn Entities(comptime all_components: anytype) type {
|
||||||
}
|
}
|
||||||
columns[columns.len - 1] = .{
|
columns[columns.len - 1] = .{
|
||||||
.name = name_id,
|
.name = name_id,
|
||||||
.type_id = comp.typeId(@TypeOf(component)),
|
.type_id = Archetype.typeId(@TypeOf(component)),
|
||||||
.size = @sizeOf(@TypeOf(component)),
|
.size = @sizeOf(@TypeOf(component)),
|
||||||
.alignment = if (@sizeOf(@TypeOf(component)) == 0) 1 else @alignOf(@TypeOf(component)),
|
.alignment = if (@sizeOf(@TypeOf(component)) == 0) 1 else @alignOf(@TypeOf(component)),
|
||||||
.values = undefined,
|
.values = undefined,
|
||||||
|
|
@ -656,11 +655,11 @@ pub fn ArchetypeIterator(comptime all_components: anytype) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: all_components is a superset of queried items, not type-safe.
|
// TODO: all_components is a superset of queried items, not type-safe.
|
||||||
pub fn next(iter: *Self) ?comp.ArchetypeSlicer(all_components) {
|
pub fn next(iter: *Self) ?Archetype.Slicer(all_components) {
|
||||||
while (iter.index < iter.entities.archetypes.items.len) {
|
while (iter.index < iter.entities.archetypes.items.len) {
|
||||||
const archetype = &iter.entities.archetypes.items[iter.index];
|
const archetype = &iter.entities.archetypes.items[iter.index];
|
||||||
iter.index += 1;
|
iter.index += 1;
|
||||||
if (iter.match(archetype)) return comp.ArchetypeSlicer(all_components){ .archetype = archetype };
|
if (iter.match(archetype)) return Archetype.Slicer(all_components){ .archetype = archetype };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ test "example" {
|
||||||
try testing.expectEqual(@as(usize, 1001), ids[0]);
|
try testing.expectEqual(@as(usize, 1001), ids[0]);
|
||||||
|
|
||||||
// TODO: can't write @as type here easily due to generic parameter, should be exposed
|
// TODO: can't write @as type here easily due to generic parameter, should be exposed
|
||||||
// ?comp.ArchetypeSlicer(all_components)
|
// ?Archetype.Slicer(all_components)
|
||||||
try testing.expectEqual(iter.next(), null);
|
try testing.expectEqual(iter.next(), null);
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ const testing = @import("testing.zig");
|
||||||
|
|
||||||
const Entities = @import("ecs/entities.zig").Entities;
|
const Entities = @import("ecs/entities.zig").Entities;
|
||||||
const EntityID = @import("ecs/entities.zig").EntityID;
|
const EntityID = @import("ecs/entities.zig").EntityID;
|
||||||
const is_debug = @import("ecs/comptime.zig").is_debug;
|
const is_debug = @import("ecs/Archetype.zig").is_debug;
|
||||||
|
|
||||||
/// Verifies that M matches the basic layout of a Mach module
|
/// Verifies that M matches the basic layout of a Mach module
|
||||||
fn ModuleInterface(comptime M: type) type {
|
fn ModuleInterface(comptime M: type) type {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue