diff --git a/Sources/FirebladeECS/ComponentIdentifier.swift b/Sources/FirebladeECS/ComponentIdentifier.swift index 52433bc..928c398 100644 --- a/Sources/FirebladeECS/ComponentIdentifier.swift +++ b/Sources/FirebladeECS/ComponentIdentifier.swift @@ -18,7 +18,12 @@ public struct ComponentIdentifier { extension ComponentIdentifier { @usableFromInline init(_ componentType: C.Type) where C: Component { - self.hash = Nexus.makeOrGetComponentId(componentType) + self.hash = Self.makeRuntimeHash(componentType) + } + + /// object identifier hash (only stable during runtime) - arbitrary hash is ok. + internal static func makeRuntimeHash(_ componentType: C.Type) -> Hash where C: Component { + ObjectIdentifier(componentType).hashValue } } diff --git a/Sources/FirebladeECS/EntityIdentifier.swift b/Sources/FirebladeECS/EntityIdentifier.swift index 9eb7dc7..04ca439 100644 --- a/Sources/FirebladeECS/EntityIdentifier.swift +++ b/Sources/FirebladeECS/EntityIdentifier.swift @@ -21,4 +21,3 @@ public struct EntityIdentifier { extension EntityIdentifier: Equatable { } extension EntityIdentifier: Hashable { } -extension EntityIdentifier: Codable { } diff --git a/Sources/FirebladeECS/EntityIdentifierGenerator.swift b/Sources/FirebladeECS/EntityIdentifierGenerator.swift new file mode 100644 index 0000000..5cd9ee6 --- /dev/null +++ b/Sources/FirebladeECS/EntityIdentifierGenerator.swift @@ -0,0 +1,35 @@ +// +// EntityIdentifierGenerator.swift +// +// +// Created by Christian Treffs on 26.06.20. +// + +internal final class EntityIdentifierGenerator { + private var stack: [UInt32] + + var count: Int { + stack.count + } + + convenience init() { + self.init([EntityIdentifier(0)]) + } + + init(_ entityIds: [EntityIdentifier]) { + stack = entityIds.reversed().map { UInt32($0.id) } + } + + func nextId() -> EntityIdentifier { + if stack.count == 1 { + defer { stack[0] += 1 } + return EntityIdentifier(stack[0]) + } else { + return EntityIdentifier(stack.removeLast()) + } + } + + func freeId(_ entityId: EntityIdentifier) { + stack.append(UInt32(entityId.id)) + } +} diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index 663d927..bc5c637 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -6,20 +6,12 @@ // extension Nexus { - @inlinable - internal func nextEntityId() -> EntityIdentifier { - guard let nextReused: EntityIdentifier = freeEntities.popLast() else { - return EntityIdentifier(UInt32(entityStorage.count)) - } - return nextReused - } - @discardableResult public func createEntity() -> Entity { - let newEntityIdentifier: EntityIdentifier = nextEntityId() - entityStorage.insert(newEntityIdentifier, at: newEntityIdentifier.id) - delegate?.nexusEvent(EntityCreated(entityId: newEntityIdentifier)) - return Entity(nexus: self, id: newEntityIdentifier) + let entityId: EntityIdentifier = entityIdGenerator.nextId() + entityStorage.insert(entityId, at: entityId.id) + delegate?.nexusEvent(EntityCreated(entityId: entityId)) + return Entity(nexus: self, id: entityId) } @discardableResult @@ -67,7 +59,7 @@ extension Nexus { update(familyMembership: entityId) } - freeEntities.append(entityId) + entityIdGenerator.freeId(entityId) delegate?.nexusEvent(EntityDestroyed(entityId: entityId)) return true diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index 75dd9ac..a15385e 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -11,7 +11,7 @@ public final class Nexus { @usableFromInline final var entityStorage: UnorderedSparseSet /// Entity ids that are currently not used. - @usableFromInline final var freeEntities: [EntityIdentifier] + let entityIdGenerator: EntityIdentifierGenerator /// - Key: ComponentIdentifier aka component type. /// - Value: Array of component instances of same type (uniform). @@ -37,7 +37,7 @@ public final class Nexus { self.init(entityStorage: UnorderedSparseSet(), componentsByType: [:], componentsByEntity: [:], - freeEntities: [], + entityIdGenerator: EntityIdentifierGenerator(), familyMembersByTraits: [:], childrenByParentEntity: [:]) } @@ -45,15 +45,15 @@ public final class Nexus { internal init(entityStorage: UnorderedSparseSet, componentsByType: [ComponentIdentifier: ManagedContiguousArray], componentsByEntity: [EntityIdentifier: Set], - freeEntities: [EntityIdentifier], + entityIdGenerator: EntityIdentifierGenerator, familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet], childrenByParentEntity: [EntityIdentifier: Set]) { self.entityStorage = entityStorage self.componentsByType = componentsByType self.componentIdsByEntity = componentsByEntity - self.freeEntities = freeEntities self.familyMembersByTraits = familyMembersByTraits self.childrenByParentEntity = childrenByParentEntity + self.entityIdGenerator = entityIdGenerator } deinit { @@ -63,34 +63,11 @@ public final class Nexus { public final func clear() { entityStorage.forEach { destroy(entityId: $0) } entityStorage.removeAll() - freeEntities.removeAll() componentsByType.removeAll() componentIdsByEntity.removeAll() familyMembersByTraits.removeAll() childrenByParentEntity.removeAll() } - - @available(swift, deprecated: 0.12.0) - public static var knownUniqueComponentTypes: Set { - Set(stableComponentIdentifierMap.keys.map { ComponentIdentifier(hash: $0) }) - } -} - -// MARK: - centralized component identifier mapping -extension Nexus { - internal static var stableComponentIdentifierMap: [ComponentIdentifier.Hash: ComponentIdentifier.StableId] = [:] - - internal static func makeOrGetComponentId(_ componentType: C.Type) -> ComponentIdentifier.Hash where C: Component { - /// object identifier hash (only stable during runtime) - arbitrary hash is ok. - let objIdHash = ObjectIdentifier(componentType).hashValue - // if we do not know this component type yet - we register a stable identifier generator for it. - if stableComponentIdentifierMap[objIdHash] == nil { - let string = String(describing: C.self) - let stableHash = StringHashing.singer_djb2(string) - stableComponentIdentifierMap[objIdHash] = stableHash - } - return objIdHash - } } // MARK: - CustomDebugStringConvertible diff --git a/Tests/FirebladeECSTests/EntityTests.swift b/Tests/FirebladeECSTests/EntityTests.swift index d4cb2c6..878e173 100644 --- a/Tests/FirebladeECSTests/EntityTests.swift +++ b/Tests/FirebladeECSTests/EntityTests.swift @@ -31,14 +31,56 @@ class EntityTests: XCTestCase { let entity = nexus.createEntity() entity.assign(pos) - entity.assign(name) - entity.assign(vel) + entity.assign(name, vel) let expectedComponents: [Component] = [pos, name, vel] let allComponents = entity.allComponents() XCTAssertTrue(allComponents.elementsEqualUnordered(expectedComponents) { $0 === $1 }) } + + func testEntityEquality() { + let nexus = Nexus() + + let entityA = nexus.createEntity() + let entityB = nexus.createEntity() + + XCTAssertEqual(entityA, entityA) + XCTAssertNotEqual(entityA, entityB) + } + + func testRemoveAllComponentsFromEntity() { + let nexus = Nexus() + + let entity = nexus.createEntity(with: Position(x: 1, y: 2), Name(name: "MyEntity")) + XCTAssertEqual(entity.numComponents, 2) + entity.removeAll() + XCTAssertEqual(entity.numComponents, 0) + } + + func testEntityIdGenerator() { + let generator = EntityIdentifierGenerator() + + XCTAssertEqual(generator.count, 1) + + for _ in 0..<100 { + _ = generator.nextId() + } + + XCTAssertEqual(generator.count, 1) + + for i in 10..<60 { + generator.freeId(EntityIdentifier(UInt32(i))) + } + + XCTAssertEqual(generator.count, 51) + + for _ in 0..<50 { + _ = generator.nextId() + } + + XCTAssertEqual(generator.count, 1) + } } extension Sequence { diff --git a/Tests/FirebladeECSTests/SystemsTests.swift b/Tests/FirebladeECSTests/SystemsTests.swift index ac815bd..59005b2 100644 --- a/Tests/FirebladeECSTests/SystemsTests.swift +++ b/Tests/FirebladeECSTests/SystemsTests.swift @@ -38,7 +38,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.numEntities, 0) XCTAssertEqual(colorSystem.colors.memberIds.count, 0) XCTAssertEqual(positionSystem.positions.memberIds.count, 0) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, 0) batchCreateEntities(count: num) @@ -47,7 +47,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) colorSystem.update() positionSystem.update() @@ -56,7 +56,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) batchCreateEntities(count: num) @@ -64,7 +64,7 @@ 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.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) colorSystem.update() positionSystem.update() @@ -73,12 +73,12 @@ 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.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) batchDestroyEntities(count: num) XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num) - XCTAssertEqual(nexus.freeEntities.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 +90,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.numEntities, num) XCTAssertEqual(colorSystem.colors.memberIds.count, num) XCTAssertEqual(positionSystem.positions.memberIds.count, num) - XCTAssertEqual(nexus.freeEntities.count, num) + XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num) batchCreateEntities(count: num) @@ -98,7 +98,7 @@ class SystemsTests: XCTestCase { XCTAssertEqual(nexus.numEntities, num * 2) XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2) XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2) - XCTAssertEqual(nexus.freeEntities.count, 0) + XCTAssertEqual(nexus.entityIdGenerator.count, 1) } func createDefaultEntity() { diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index ba4266a..600fc6b 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -26,7 +26,10 @@ extension EntityTests { // to regenerate. static let __allTests__EntityTests = [ ("testAllComponentsOfEntity", testAllComponentsOfEntity), - ("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex) + ("testEntityEquality", testEntityEquality), + ("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex), + ("testEntityIdGenerator", testEntityIdGenerator), + ("testRemoveAllComponentsFromEntity", testRemoveAllComponentsFromEntity) ] }