diff --git a/Sources/FirebladeECS/Entity.swift b/Sources/FirebladeECS/Entity.swift index 4f217f5..43c1817 100644 --- a/Sources/FirebladeECS/Entity.swift +++ b/Sources/FirebladeECS/Entity.swift @@ -17,19 +17,6 @@ public final class Entity: UniqueEntityIdentifiable { } } -// MARK: - Invalidate -extension Entity { - - public var isValid: Bool { - return nexus.isValid(entity: self) - } - - func invalidate() { - identifier = EntityIdentifier.invalid - name = nil - } -} - // MARK: - Equatable public func == (lhs: Entity, rhs: Entity) -> Bool { return lhs.identifier == rhs.identifier diff --git a/Sources/FirebladeECS/ManagedContiguousArray.swift b/Sources/FirebladeECS/ManagedContiguousArray.swift index ddc8b3a..54a918b 100644 --- a/Sources/FirebladeECS/ManagedContiguousArray.swift +++ b/Sources/FirebladeECS/ManagedContiguousArray.swift @@ -14,7 +14,8 @@ public protocol UniformStorage: class { func add(_ element: Element, at index: Index) func has(_ index: Index) -> Bool func get(at index: Index) -> Element? - func remove(at index: Index) + @discardableResult + func remove(at index: Index) -> Bool func clear(keepingCapacity: Bool) } @@ -56,7 +57,8 @@ public class ManagedContiguousArray: UniformStorage { return store[index] } - public func remove(at index: Index) { + @discardableResult + public func remove(at index: Index) -> Bool { if store[index] != nil { size -= 1 } @@ -64,6 +66,7 @@ public class ManagedContiguousArray: UniformStorage { if size == 0 { clear() } + return true } public func clear(keepingCapacity: Bool = false) { diff --git a/Sources/FirebladeECS/Nexus+Component.swift b/Sources/FirebladeECS/Nexus+Component.swift index 2065c64..0363848 100644 --- a/Sources/FirebladeECS/Nexus+Component.swift +++ b/Sources/FirebladeECS/Nexus+Component.swift @@ -30,6 +30,8 @@ extension Nexus { public func assign(component: Component, to entity: Entity) { let componentId = component.identifier let entityIdx = entity.identifier.index + let entityId: EntityIdentifier = entity.identifier + /// test if component is already assigned guard !has(componentId: componentId, entityIdx: entityIdx) else { // FIXME: this is still open to debate @@ -53,7 +55,6 @@ extension Nexus { componentIdsByEntity[entityIdx]?.add(componentId, at: componentId.hashValue) // FIXME: iterating all families is costly for many families - let entityId: EntityIdentifier = entity.identifier for (_, family) in familiesByTraitHash { update(membership: family, for: entityId) } diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index e226aa3..79a6219 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -7,11 +7,6 @@ extension Nexus { - public var entities: [Entity] { - // FIXME: we do not want this kind of access to the underlying entity store - return entityStorage.filter { isValid(entity: $0.identifier) } - } - private func nextEntityIdx() -> EntityIndex { guard let nextReused: EntityIdentifier = freeEntities.popLast() else { return entityStorage.count @@ -22,49 +17,32 @@ extension Nexus { public func create(entity name: String? = nil) -> Entity { let newEntityIndex: EntityIndex = nextEntityIdx() let newEntityIdentifier: EntityIdentifier = newEntityIndex.identifier - if entityStorage.count > newEntityIndex { - let reusedEntity: Entity = entityStorage[newEntityIndex] - assert(reusedEntity.identifier == EntityIdentifier.invalid, "Stil valid entity \(reusedEntity)") - reusedEntity.identifier = newEntityIdentifier - reusedEntity.name = name - notify(EntityCreated(entityId: newEntityIdentifier)) - return reusedEntity - } else { - let newEntity = Entity(nexus: self, id: newEntityIdentifier, name: name) - entityStorage.insert(newEntity, at: newEntityIndex) - notify(EntityCreated(entityId: newEntityIdentifier)) - return newEntity - } + + let newEntity = Entity(nexus: self, id: newEntityIdentifier, name: name) + entityStorage.add(newEntity, at: newEntityIndex) + notify(EntityCreated(entityId: newEntityIdentifier)) + return newEntity } /// Number of entities in nexus. public var numEntities: Int { - return entityStorage.count - freeEntities.count - } - - func isValid(entity: Entity) -> Bool { - return isValid(entity: entity.identifier) - } - - func isValid(entity entitiyId: EntityIdentifier) -> Bool { - return entitiyId != EntityIdentifier.invalid && - entitiyId.index >= 0 && - entitiyId.index < entityStorage.count + return entityStorage.count } public func has(entity entityId: EntityIdentifier) -> Bool { - return isValid(entity: entityId) + return entityStorage.has(entityId.index) } - public func get(entity entityId: EntityIdentifier) -> Entity { - return entityStorage[entityId.index] + public func get(entity entityId: EntityIdentifier) -> Entity? { + return entityStorage.get(at: entityId.index) } @discardableResult public func destroy(entity: Entity) -> Bool { let entityId: EntityIdentifier = entity.identifier - // FIXME: we can make this cheaper by eliminating the need to ask if entity is present - guard has(entity: entityId) else { + let entityIdx: EntityIndex = entityId.index + + guard entityStorage.remove(at: entityIdx) else { report("EntityRemove failure: no entity \(entityId) to remove") return false } @@ -72,12 +50,6 @@ extension Nexus { let cleared: Bool = clear(componentes: entityId) assert(cleared, "Could not clear all components form entity \(entityId)") - entity.invalidate() - - // replace with "new" invalid entity to keep capacity of array - let invalidEntity = Entity(nexus: self, id: EntityIdentifier.invalid) - entityStorage[entityId.index] = invalidEntity - freeEntities.append(entityId) // FIXME: iterating all families is costly for many families diff --git a/Sources/FirebladeECS/Nexus+Family.swift b/Sources/FirebladeECS/Nexus+Family.swift index 1688188..65a62a5 100644 --- a/Sources/FirebladeECS/Nexus+Family.swift +++ b/Sources/FirebladeECS/Nexus+Family.swift @@ -66,7 +66,6 @@ extension Nexus { /// will be called on family init defer func onFamilyInit(family: Family) { // FIXME: this is costly for many entities - // FIXME: we iterate invalid entities here for entity: Entity in entityStorage { update(membership: family, for: entity.identifier) } @@ -87,7 +86,7 @@ extension Nexus { } let is_Member: Bool = isMember(entityId, in: family) - if !isValid(entity: entityId) && is_Member { + if !has(entity: entityId) && is_Member { remove(from: traitHash, entityId: entityId, entityIdx: entityIdx) return } diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index f26a200..9ad924d 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -13,7 +13,7 @@ public typealias UniformComponents = ContiguousComponentArray public typealias UniformEntityIdentifiers = SparseEntityIdentifierSet public typealias ComponentIdentifiers = ContiguousArray public typealias ComponentSet = Set -public typealias Entities = ContiguousArray +public typealias Entities = SparseEntitySet public typealias EntityIdSet = Set public typealias FamilyTraitSetHash = Int public typealias TraitEntityIdHash = Int @@ -31,7 +31,6 @@ public class Nexus { /// - Index: index value matching entity identifier shifted to Int /// - Value: each element is a entity instance - // FIXME: sparse set my be valuable var entityStorage: Entities /// - Key: component type identifier @@ -40,7 +39,6 @@ public class Nexus { /// - Key: entity id as index /// - Value: each element is a component identifier associated with this entity - // FIXME: this may be refactored to a uniform sparse set var componentIdsByEntity: [EntityIndex: SparseComponentIdentifierSet] /// - Values: entity ids that are currently not used @@ -61,11 +59,12 @@ public class Nexus { } deinit { - for e in entities { + + for e: Entity in entityStorage { destroy(entity: e) } - entityStorage.removeAll() + entityStorage.clear() freeEntities.removeAll() assert(entityStorage.isEmpty) diff --git a/Sources/FirebladeECS/SparseSet.swift b/Sources/FirebladeECS/SparseSet.swift index 7c1fcee..3e763ec 100644 --- a/Sources/FirebladeECS/SparseSet.swift +++ b/Sources/FirebladeECS/SparseSet.swift @@ -24,6 +24,7 @@ public class SparseSet: UniformStorage, Sequence { } public var count: Int { return size } + var isEmpty: Bool { return size == 0 } var capacitySparse: Int { return sparse.capacity } var capacityDense: Int { return dense.capacity } @@ -52,18 +53,19 @@ public class SparseSet: UniformStorage, Sequence { return dense[sIdx]?.value } - public func remove(at index: Index) { + @discardableResult + public func remove(at index: Index) -> Bool { guard has(index) else { - return + return false } guard let removeIdx: DenseIndex = sparse[index] else { - return + return false } let lastIdx: DenseIndex = count - 1 dense.swapAt(removeIdx, lastIdx) sparse[index] = nil guard let swapped: Pair = dense[removeIdx] else { - return + return false } sparse[swapped.key] = removeIdx dense.removeLast() @@ -71,6 +73,7 @@ public class SparseSet: UniformStorage, Sequence { if size == 0 { clear(keepingCapacity: false) } + return true } public func clear(keepingCapacity: Bool = false) { @@ -105,6 +108,10 @@ public class SparseSet: UniformStorage, Sequence { // MARK: - specialized sparse sets +public class SparseEntitySet: SparseSet { + public typealias Index = EntityIndex +} + public class SparseEntityIdentifierSet: SparseSet { public typealias Index = EntityIndex diff --git a/Tests/FirebladeECSTests/FamilyTests.swift b/Tests/FirebladeECSTests/FamilyTests.swift index 57ca96d..38da168 100644 --- a/Tests/FirebladeECSTests/FamilyTests.swift +++ b/Tests/FirebladeECSTests/FamilyTests.swift @@ -144,7 +144,7 @@ class FamilyTests: XCTestCase { var countA: Int = 0 familyA.iterate(components: Position.self) { (entityId, _) in - let e = nexus.get(entity: entityId) + let e = nexus.get(entity: entityId)! e.assign(Velocity(a: 3.14)) e.remove(Position.self) countA += 1 @@ -153,7 +153,7 @@ class FamilyTests: XCTestCase { var countB: Int = 0 familyB.iterate(components: Velocity.self) { eId, velocity in - let e = nexus.get(entity: eId) + let e = nexus.get(entity: eId)! e.assign(Position(x: 1, y: 2)) e.remove(velocity!) countB += 1 diff --git a/Tests/FirebladeECSTests/NexusTests.swift b/Tests/FirebladeECSTests/NexusTests.swift index 8917230..cb0d498 100644 --- a/Tests/FirebladeECSTests/NexusTests.swift +++ b/Tests/FirebladeECSTests/NexusTests.swift @@ -24,51 +24,20 @@ class NexusTests: XCTestCase { let e0 = nexus.create() XCTAssert(e0.identifier.index == 0) - XCTAssert(e0.isValid) XCTAssert(nexus.numEntities == 1) let e1 = nexus.create(entity: "Named e1") XCTAssert(e1.identifier.index == 1) - XCTAssert(e1.isValid) XCTAssert(nexus.numEntities == 2) XCTAssert(e0.name == nil) XCTAssert(e1.name == "Named e1") - let rE0 = nexus.get(entity: e0.identifier) + let rE0 = nexus.get(entity: e0.identifier)! XCTAssert(rE0.name == e0.name) XCTAssert(rE0.identifier == e0.identifier) } - func testDestroyAndReuseEntity() { - let nexus: Nexus = Nexus() - XCTAssert(nexus.numEntities == 0) - - let e0 = nexus.create(entity: "e0") - XCTAssert(e0.isValid) - XCTAssert(nexus.numEntities == 1) - - let e1 = nexus.create(entity: "e1") - XCTAssert(e1.isValid) - XCTAssert(nexus.numEntities == 2) - - e0.destroy() - - XCTAssert(!e0.isValid) - XCTAssert(e1.isValid) - XCTAssert(nexus.numEntities == 1) - - let e2 = nexus.create(entity: "e2") - XCTAssert(!e0.isValid) - XCTAssert(e1.isValid) - XCTAssert(e2.isValid) - - XCTAssert(nexus.numEntities == 2) - - XCTAssert(!(e0 == e2)) - XCTAssert(!(e0 === e2)) - } - func testComponentCreation() { let nexus: Nexus = Nexus() XCTAssert(nexus.numEntities == 0) @@ -80,7 +49,6 @@ class NexusTests: XCTestCase { e0.assign(p0) e0.assign(p0) - XCTAssert(e0.isValid) XCTAssert(e0.hasComponents) XCTAssert(e0.numComponents == 1) @@ -93,7 +61,7 @@ class NexusTests: XCTestCase { let nexus = Nexus() let identifier: EntityIdentifier = nexus.create(entity: "e0").identifier - let e0 = nexus.get(entity: identifier) + let e0 = nexus.get(entity: identifier)! XCTAssert(e0.numComponents == 0) e0.remove(Position.self) @@ -134,7 +102,6 @@ class NexusTests: XCTestCase { e0.destroy() XCTAssert(e0.numComponents == 0) - XCTAssert(!e0.isValid) }