Generalize entity identifier generator

This commit is contained in:
Christian Treffs 2020-08-06 22:45:55 +02:00
parent d210fe378b
commit 552665edd0
No known key found for this signature in database
GPG Key ID: 49A4B4B460BE3ED4
6 changed files with 83 additions and 37 deletions

View File

@ -6,15 +6,15 @@
// //
public struct EntityIdentifier { public struct EntityIdentifier {
static let invalid = EntityIdentifier(.max) public static let invalid = EntityIdentifier(.max)
public typealias Idx = Int public typealias Idx = Int
/// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid. /// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid.
@usableFromInline let id: Idx @usableFromInline let id: Idx
@usableFromInline @inlinable
init(_ uint32: UInt32) { public init(_ uint32: UInt32) {
self.id = Idx(uint32) self.id = Idx(uint32)
} }
} }

View File

@ -5,31 +5,78 @@
// Created by Christian Treffs on 26.06.20. // Created by Christian Treffs on 26.06.20.
// //
internal final class EntityIdentifierGenerator { /// An entity identifier generator provides new entity
private var stack: [UInt32] /// identifiers on entity creation.
/// It also allows entity ids to be marked for re-use.
/// Entity identifiers must be unique.
public protocol EntityIdentifierGenerator {
/// Initialize the generator with entity ids already in use.
/// - Parameter entityIds: The entity ids already in use. Default should be an empty array.
init(inUse entityIds: [EntityIdentifier])
var count: Int { /// Provides the next unused entity identifier.
stack.count ///
} /// The provided entity identifier is at least unique during runtime.
func nextId() -> EntityIdentifier
convenience init() { /// Marks the given entity identifier as free and ready for re-use.
self.init([EntityIdentifier(0)]) ///
} /// Unused entity identifiers will again be provided with `nextId()`.
/// - Parameter entityId: The entity id to be marked as unused.
func markUnused(entityId: EntityIdentifier)
}
init(_ entityIds: [EntityIdentifier]) { /// A default entity identifier generator implementation.
stack = entityIds.reversed().map { UInt32($0.id) } ///
} /// Provides entity ids starting at `0` incrementing until `UInt32.max`.
public struct DefaultEntityIdGenerator: EntityIdentifierGenerator {
@usableFromInline
final class Storage {
@usableFromInline var stack: [UInt32]
@usableFromInline var count: Int { stack.count }
func nextId() -> EntityIdentifier { @usableFromInline
if stack.count == 1 { init(inUse entityIds: [EntityIdentifier]) {
defer { stack[0] += 1 } stack = entityIds.reversed().map { UInt32($0.id) }
return EntityIdentifier(stack[0]) }
} else {
return EntityIdentifier(stack.removeLast()) @usableFromInline
func nextId() -> EntityIdentifier {
if stack.count == 1 {
defer { stack[0] += 1 }
return EntityIdentifier(stack[0])
} else {
return EntityIdentifier(stack.removeLast())
}
}
@usableFromInline
func markUnused(entityId: EntityIdentifier) {
stack.append(UInt32(entityId.id))
} }
} }
func freeId(_ entityId: EntityIdentifier) { @usableFromInline let storage: Storage
stack.append(UInt32(entityId.id))
@usableFromInline var count: Int { storage.count }
@inlinable
public init() {
self.init(inUse: [EntityIdentifier(0)])
}
@inlinable
public init(inUse entityIds: [EntityIdentifier]) {
self.storage = Storage(inUse: entityIds)
}
@inlinable
public func nextId() -> EntityIdentifier {
storage.nextId()
}
@inlinable
public func markUnused(entityId: EntityIdentifier) {
storage.markUnused(entityId: entityId)
} }
} }

View File

@ -64,7 +64,7 @@ extension Nexus {
update(familyMembership: entityId) update(familyMembership: entityId)
} }
entityIdGenerator.freeId(entityId) entityIdGenerator.markUnused(entityId: entityId)
delegate?.nexusEvent(EntityDestroyed(entityId: entityId)) delegate?.nexusEvent(EntityDestroyed(entityId: entityId))
return true return true

View File

@ -10,9 +10,6 @@ public final class Nexus {
/// Entities are tightly packed by EntityIdentifier. /// Entities are tightly packed by EntityIdentifier.
@usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx> @usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>
/// Entity ids that are currently not used.
let entityIdGenerator: EntityIdentifierGenerator
/// - Key: ComponentIdentifier aka component type. /// - Key: ComponentIdentifier aka component type.
/// - Value: Array of component instances of same type (uniform). /// - Value: Array of component instances of same type (uniform).
/// New component instances are appended. /// New component instances are appended.
@ -27,6 +24,16 @@ public final class Nexus {
/// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family. /// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family.
@usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>] @usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>]
/// The entity identifier generator responsible for providing unique ids for entities during runtime.
///
/// Provide a custom implementation prior to entity creation.
/// Defaults to `DefaultEntityIdGenerator`.
public final var entityIdGenerator: EntityIdentifierGenerator
/// The coding strategy used to encode/decode entities from/into families.
///
/// Provide a custom implementation prior to encoding/decoding.
/// Defaults to `DefaultCodingStrategy`.
public final var codingStrategy: CodingStrategy public final var codingStrategy: CodingStrategy
public final weak var delegate: NexusEventDelegate? public final weak var delegate: NexusEventDelegate?
@ -35,7 +42,7 @@ public final class Nexus {
self.init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>(), self.init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>(),
componentsByType: [:], componentsByType: [:],
componentsByEntity: [:], componentsByEntity: [:],
entityIdGenerator: EntityIdentifierGenerator(), entityIdGenerator: DefaultEntityIdGenerator(),
familyMembersByTraits: [:], familyMembersByTraits: [:],
codingStrategy: DefaultCodingStrategy()) codingStrategy: DefaultCodingStrategy())
} }

View File

@ -59,7 +59,7 @@ class EntityTests: XCTestCase {
} }
func testEntityIdGenerator() { func testEntityIdGenerator() {
let generator = EntityIdentifierGenerator() let generator = DefaultEntityIdGenerator()
XCTAssertEqual(generator.count, 1) XCTAssertEqual(generator.count, 1)
@ -70,7 +70,7 @@ class EntityTests: XCTestCase {
XCTAssertEqual(generator.count, 1) XCTAssertEqual(generator.count, 1)
for i in 10..<60 { for i in 10..<60 {
generator.freeId(EntityIdentifier(UInt32(i))) generator.markUnused(entityId: EntityIdentifier(UInt32(i)))
} }
XCTAssertEqual(generator.count, 51) XCTAssertEqual(generator.count, 51)

View File

@ -38,7 +38,6 @@ class SystemsTests: XCTestCase {
XCTAssertEqual(nexus.numEntities, 0) XCTAssertEqual(nexus.numEntities, 0)
XCTAssertEqual(colorSystem.colors.memberIds.count, 0) XCTAssertEqual(colorSystem.colors.memberIds.count, 0)
XCTAssertEqual(positionSystem.positions.memberIds.count, 0) XCTAssertEqual(positionSystem.positions.memberIds.count, 0)
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, 0) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, 0)
batchCreateEntities(count: num) batchCreateEntities(count: num)
@ -47,7 +46,6 @@ class SystemsTests: XCTestCase {
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num)
XCTAssertEqual(positionSystem.positions.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num)
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
colorSystem.update() colorSystem.update()
positionSystem.update() positionSystem.update()
@ -56,7 +54,6 @@ class SystemsTests: XCTestCase {
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num)
XCTAssertEqual(positionSystem.positions.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num)
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
batchCreateEntities(count: num) batchCreateEntities(count: num)
@ -64,7 +61,6 @@ class SystemsTests: XCTestCase {
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
colorSystem.update() colorSystem.update()
positionSystem.update() positionSystem.update()
@ -73,12 +69,10 @@ class SystemsTests: XCTestCase {
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
batchDestroyEntities(count: num) batchDestroyEntities(count: num)
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num)
XCTAssertEqual(nexus.numEntities, num) XCTAssertEqual(nexus.numEntities, num)
XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num)
XCTAssertEqual(positionSystem.positions.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num)
@ -90,7 +84,6 @@ class SystemsTests: XCTestCase {
XCTAssertEqual(nexus.numEntities, num) XCTAssertEqual(nexus.numEntities, num)
XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num)
XCTAssertEqual(positionSystem.positions.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num)
XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num)
batchCreateEntities(count: num) batchCreateEntities(count: num)
@ -98,7 +91,6 @@ class SystemsTests: XCTestCase {
XCTAssertEqual(nexus.numEntities, num * 2) XCTAssertEqual(nexus.numEntities, num * 2)
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
} }
func createDefaultEntity() { func createDefaultEntity() {