diff --git a/Sources/FirebladeECS/EntityIdentifier.swift b/Sources/FirebladeECS/EntityIdentifier.swift index 03b8e79..f8a649a 100644 --- a/Sources/FirebladeECS/EntityIdentifier.swift +++ b/Sources/FirebladeECS/EntityIdentifier.swift @@ -6,15 +6,15 @@ // public struct EntityIdentifier { - static let invalid = EntityIdentifier(.max) + public static let invalid = EntityIdentifier(.max) public typealias Idx = Int /// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid. @usableFromInline let id: Idx - @usableFromInline - init(_ uint32: UInt32) { + @inlinable + public init(_ uint32: UInt32) { self.id = Idx(uint32) } } diff --git a/Sources/FirebladeECS/EntityIdentifierGenerator.swift b/Sources/FirebladeECS/EntityIdentifierGenerator.swift index 7e10e94..8a58673 100644 --- a/Sources/FirebladeECS/EntityIdentifierGenerator.swift +++ b/Sources/FirebladeECS/EntityIdentifierGenerator.swift @@ -5,31 +5,78 @@ // Created by Christian Treffs on 26.06.20. // -internal final class EntityIdentifierGenerator { - private var stack: [UInt32] +/// An entity identifier generator provides new entity +/// 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 { - stack.count - } + /// Provides the next unused entity identifier. + /// + /// The provided entity identifier is at least unique during runtime. + func nextId() -> EntityIdentifier - convenience init() { - self.init([EntityIdentifier(0)]) - } + /// Marks the given entity identifier as free and ready for re-use. + /// + /// 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]) { - stack = entityIds.reversed().map { UInt32($0.id) } - } +/// A default entity identifier generator implementation. +/// +/// 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 { - if stack.count == 1 { - defer { stack[0] += 1 } - return EntityIdentifier(stack[0]) - } else { - return EntityIdentifier(stack.removeLast()) + @usableFromInline + init(inUse entityIds: [EntityIdentifier]) { + stack = entityIds.reversed().map { UInt32($0.id) } + } + + @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) { - stack.append(UInt32(entityId.id)) + @usableFromInline let storage: Storage + + @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) } } diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index d6146bd..a8279e8 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -64,7 +64,7 @@ extension Nexus { update(familyMembership: entityId) } - entityIdGenerator.freeId(entityId) + entityIdGenerator.markUnused(entityId: entityId) delegate?.nexusEvent(EntityDestroyed(entityId: entityId)) return true diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index cd293e1..df77235 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -10,9 +10,6 @@ public final class Nexus { /// Entities are tightly packed by EntityIdentifier. @usableFromInline final var entityStorage: UnorderedSparseSet - /// Entity ids that are currently not used. - let entityIdGenerator: EntityIdentifierGenerator - /// - Key: ComponentIdentifier aka component type. /// - Value: Array of component instances of same type (uniform). /// 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. @usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet] + /// 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 weak var delegate: NexusEventDelegate? @@ -35,7 +42,7 @@ public final class Nexus { self.init(entityStorage: UnorderedSparseSet(), componentsByType: [:], componentsByEntity: [:], - entityIdGenerator: EntityIdentifierGenerator(), + entityIdGenerator: DefaultEntityIdGenerator(), familyMembersByTraits: [:], codingStrategy: DefaultCodingStrategy()) } diff --git a/Tests/FirebladeECSTests/EntityTests.swift b/Tests/FirebladeECSTests/EntityTests.swift index d977cd6..a6103d3 100644 --- a/Tests/FirebladeECSTests/EntityTests.swift +++ b/Tests/FirebladeECSTests/EntityTests.swift @@ -59,7 +59,7 @@ class EntityTests: XCTestCase { } func testEntityIdGenerator() { - let generator = EntityIdentifierGenerator() + let generator = DefaultEntityIdGenerator() XCTAssertEqual(generator.count, 1) @@ -70,7 +70,7 @@ class EntityTests: XCTestCase { XCTAssertEqual(generator.count, 1) for i in 10..<60 { - generator.freeId(EntityIdentifier(UInt32(i))) + generator.markUnused(entityId: EntityIdentifier(UInt32(i))) } XCTAssertEqual(generator.count, 51) diff --git a/Tests/FirebladeECSTests/SystemsTests.swift b/Tests/FirebladeECSTests/SystemsTests.swift index 59005b2..88d8ba8 100644 --- a/Tests/FirebladeECSTests/SystemsTests.swift +++ b/Tests/FirebladeECSTests/SystemsTests.swift @@ -38,7 +38,6 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.numEntities, 0) XCTAssertEqual(colorSystem.colors.memberIds.count, 0) XCTAssertEqual(positionSystem.positions.memberIds.count, 0) - XCTAssertEqual(nexus.entityIdGenerator.count, 1) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, 0) batchCreateEntities(count: num) @@ -47,7 +46,6 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) - XCTAssertEqual(nexus.entityIdGenerator.count, 1) colorSystem.update() positionSystem.update() @@ -56,7 +54,6 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) - XCTAssertEqual(nexus.entityIdGenerator.count, 1) batchCreateEntities(count: num) @@ -64,7 +61,6 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) - XCTAssertEqual(nexus.entityIdGenerator.count, 1) colorSystem.update() positionSystem.update() @@ -73,12 +69,10 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) - XCTAssertEqual(nexus.entityIdGenerator.count, 1) batchDestroyEntities(count: num) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) - XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num) XCTAssertEqual(nexus.numEntities, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) @@ -90,7 +84,6 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.numEntities, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) - XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num) batchCreateEntities(count: num) @@ -98,7 +91,6 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.numEntities, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) - XCTAssertEqual(nexus.entityIdGenerator.count, 1) } func createDefaultEntity() {