Created
June 13, 2022 14:58
-
-
Save garettbass/df745569ce8b1311300668cf46113836 to your computer and use it in GitHub Desktop.
Assigns a unique monotonically increasing integer to each registered type, and allows retrieval of runtime-accessible TypeInfo using the returned ComponentIndex.
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 type_info = @import("type_info.zig"); | |
const component_index = @import("component_index.zig"); | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
pub const ComponentRegistryError = error{ | |
ComponentIndexIsOutOfBounds, | |
ComponentIndexIsUnregistered, | |
}; | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
/// Maps concrete types to monotonically increasing `ComponentIndex` values. | |
pub fn ComponentRegistry(comptime EntityContextType: type) type { | |
return struct { | |
pub const EntityContext = EntityContextType; | |
pub const max_component_count = EntityContext.max_component_count; | |
pub const max_component_index = EntityContext.max_component_index; | |
pub const Index = component_index.ComponentIndex(EntityContextType); | |
pub const TypeInfo = type_info.TypeInfo; | |
pub const Error = ComponentRegistryError; | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
const void_type_info = type_info.typeInfo(void); | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
var mutex = std.Thread.Mutex{}; | |
var count = @as(u32, 0); | |
var type_info_table: [max_component_count]TypeInfo = | |
// zig fmt: off | |
[_]TypeInfo{void_type_info} | |
** max_component_count; | |
// zig fmt: on | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
/// The first call to `register(Foo)` returns a unique `ComponentIndex` | |
/// and associates the `TypeInfo` for `Foo` is associated with the | |
/// returned `ComponentIndex`. | |
/// | |
/// Subsequent calls to `register(Foo)` return the same `ComponentIndex` | |
/// as the first call and do not change the state of the registry. | |
/// | |
/// It is safe to call `register()` from different threads. | |
/// State changes made by `register()` are guarded by a mutex. | |
pub fn register(comptime Component: type) Error!Index { | |
const R = Registration(Component); | |
const info = type_info.typeInfo(Component); | |
const i = try initOnce(&R.index, info); | |
return Index.init(i); | |
} | |
/// Returns `true` if `index` is less than `max_component_count` and | |
/// corresponds to a component type that has already been registered | |
/// by a call to `register()`, otherwise `false`. | |
pub fn isValid(index: Index) bool { | |
validate(index) catch return false; | |
return true; | |
} | |
/// If `index` is not valid, returns one of: | |
/// * `ComponentRegistryError.ComponentIndexIsOutOfBounds` | |
/// * `ComponentRegistryError.ComponentIndexIsUnregistered` | |
pub fn validate(index: Index) Error!void { | |
const i = index.toInt(); | |
if (i > max_component_index) | |
return Error.ComponentIndexIsOutOfBounds; | |
if (type_info_table[i] == void_type_info) | |
return Error.ComponentIndexIsUnregistered; | |
} | |
/// Returns `TypeInfo` associated with `index` if valid, otherwise | |
/// returns one of: | |
/// * `ComponentRegistryError.ComponentIndexIsOutOfBounds` | |
/// * `ComponentRegistryError.ComponentIndexIsUnregistered` | |
pub fn typeInfo(index: Index) Error!TypeInfo { | |
try validate(index); | |
return type_info_table[index.toInt()]; | |
} | |
/// Returns `TypeInfo` associated with `index` if valid, otherwise | |
/// returns `null`. | |
pub fn typeInfoIfValid(index: Index) ?TypeInfo { | |
validate(index) catch return null; | |
return type_info_table[index.toInt()]; | |
} | |
/// Returns a `[]TypeInfo` including all of the currently registered | |
/// component types in the order in which they were registered. | |
/// Indexing the returned slice with `ComponentIndex.toInt()` is | |
/// equivalent to calling `ComponentRegistry.typeInfo()`. | |
pub fn typeInfoTable() []TypeInfo { | |
return type_info_table[0..count]; | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
/// Returns an iterator that enumerates each registered `ComponentIndex`. | |
pub fn indexIterator() IndexIterator { | |
return .{ .count = count }; | |
} | |
const IndexIterator = struct { | |
index: u32 = 0, | |
count: u32 = 0, | |
pub fn next(self: *@This()) ?Index { | |
const i = self.index; | |
if (i < self.count) { | |
self.index += 1; | |
return Index.init(i); | |
} | |
return null; | |
} | |
}; | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
const unregistered: u32 = ~@as(u32, 0); | |
fn Registration(comptime T: type) type { | |
return struct { | |
const ComponentType = T; | |
var index: u32 = unregistered; | |
}; | |
} | |
fn initOnce(outdex: *u32, info: TypeInfo) Error!u32 { | |
var i = @atomicLoad(u32, outdex, .Acquire); | |
if (i == unregistered) { | |
i = try initOnceSlow(outdex, info); | |
} | |
return i; | |
} | |
fn initOnceSlow(outdex: *u32, info: TypeInfo) Error!u32 { | |
@setCold(true); | |
mutex.lock(); | |
defer mutex.unlock(); | |
var n = @atomicLoad(u32, &count, .Acquire); | |
if (n > max_component_index) | |
return Error.ComponentIndexIsOutOfBounds; | |
var i = @atomicLoad(u32, outdex, .Acquire); | |
if (i == unregistered) { | |
i = @atomicRmw(u32, &count, .Add, 1, .Release); | |
@atomicStore(u32, outdex, i, .Release); | |
@atomicStore(TypeInfo, &type_info_table[i], info, .Monotonic); | |
} | |
return i; | |
} | |
}; | |
} | |
////////////////////////////////// T E S T S /////////////////////////////////// | |
test "ComponentRegistry basic operation" { | |
const TestContext = struct { | |
const max_component_count = 3; | |
const max_component_index = 2; | |
}; | |
const R = ComponentRegistry(TestContext); | |
const E = R.Error; | |
const I = R.Index; | |
const T = R.TypeInfo; | |
const typeInfo = type_info.typeInfo; | |
const OutOfBounds = E.ComponentIndexIsOutOfBounds; | |
const Unregistered = E.ComponentIndexIsUnregistered; | |
const Position = struct { | |
v: @Vector(3, f32), | |
}; | |
const LinearVelocity = struct { | |
v: @Vector(3, f32), | |
}; | |
const LinearAcceleration = struct { | |
v: @Vector(3, f32), | |
}; | |
const Orientation = struct { | |
q: @Vector(4, f32), | |
}; | |
const expectEqual = std.testing.expectEqual; | |
const expectError = std.testing.expectError; | |
// typeInfoTable is empty prior to registration | |
try expectEqual(@as(usize, 0), R.typeInfoTable().len); | |
{ // component indices are invalid prior to registration | |
try expectEqual(false, R.isValid(I.init(0))); | |
try expectEqual(false, R.isValid(I.init(1))); | |
try expectEqual(false, R.isValid(I.init(2))); | |
try expectEqual(false, R.isValid(I.init(3))); | |
try expectError(Unregistered, R.validate(I.init(0))); | |
try expectError(Unregistered, R.validate(I.init(1))); | |
try expectError(Unregistered, R.validate(I.init(2))); | |
try expectError(OutOfBounds, R.validate(I.init(3))); | |
} | |
{ // Registry.register() is idempotent | |
try expectEqual(I.init(0), try R.register(Position)); | |
try expectEqual(I.init(1), try R.register(LinearVelocity)); | |
try expectEqual(I.init(2), try R.register(LinearAcceleration)); | |
try expectError(OutOfBounds, R.register(Orientation)); | |
try expectEqual(I.init(0), try R.register(Position)); | |
try expectEqual(I.init(1), try R.register(LinearVelocity)); | |
try expectEqual(I.init(2), try R.register(LinearAcceleration)); | |
try expectError(OutOfBounds, R.register(Orientation)); | |
} | |
{ // component indices are valid after registration | |
try expectEqual(true, R.isValid(I.init(0))); | |
try expectEqual(true, R.isValid(I.init(1))); | |
try expectEqual(true, R.isValid(I.init(2))); | |
try expectEqual(false, R.isValid(I.init(3))); | |
try R.validate(I.init(0)); | |
try R.validate(I.init(1)); | |
try R.validate(I.init(2)); | |
try expectError(OutOfBounds, R.validate(I.init(3))); | |
} | |
{ // component indices map to component type info | |
try expectEqual(typeInfo(Position), try R.typeInfo(I.init(0))); | |
try expectEqual(typeInfo(LinearVelocity), try R.typeInfo(I.init(1))); | |
try expectEqual(typeInfo(LinearAcceleration), try R.typeInfo(I.init(2))); | |
try expectError(OutOfBounds, R.typeInfo(I.init(3))); | |
try expectEqual(typeInfo(Position), R.typeInfoIfValid(I.init(0)).?); | |
try expectEqual(typeInfo(LinearVelocity), R.typeInfoIfValid(I.init(1)).?); | |
try expectEqual(typeInfo(LinearAcceleration), R.typeInfoIfValid(I.init(2)).?); | |
try expectEqual(@as(?T, null), R.typeInfoIfValid(I.init(3))); | |
} | |
// typeInfoTable is full after registration | |
try expectEqual(@as(usize, 3), R.typeInfoTable().len); | |
var itr = R.indexIterator(); | |
try expectEqual(I.init(0), itr.next().?); | |
try expectEqual(I.init(1), itr.next().?); | |
try expectEqual(I.init(2), itr.next().?); | |
try expectEqual(I.nil, itr.next()); | |
} | |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | |
test "ComponentRegistry with a different Context is isolated." { | |
const TestContext = struct { | |
const max_component_count = 3; | |
const max_component_index = 2; | |
}; | |
const R = ComponentRegistry(TestContext); | |
const E = R.Error; | |
const I = R.Index; | |
const OutOfBounds = E.ComponentIndexIsOutOfBounds; | |
const Foo = struct {}; | |
const Bar = struct {}; | |
const Baz = struct {}; | |
const Bun = struct {}; | |
const expectEqual = std.testing.expectEqual; | |
const expectError = std.testing.expectError; | |
// typeInfoTable is empty prior to registration | |
try expectEqual(@as(usize, 0), R.typeInfoTable().len); | |
try expectEqual(I.init(0), try R.register(Foo)); | |
try expectEqual(I.init(1), try R.register(Bar)); | |
try expectEqual(I.init(2), try R.register(Baz)); | |
try expectError(OutOfBounds, R.register(Bun)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment