From 19cab7af5d6a1d34fb9e14b59945c5477b9e81ee Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 1 Nov 2017 07:44:24 +0100 Subject: [PATCH] Reworked Entitiy storage as SparseSet --- Sources/FirebladeECS/Family.swift | 5 +-- Sources/FirebladeECS/Nexus+Family.swift | 58 ++++++++----------------- Sources/FirebladeECS/Nexus.swift | 6 +-- Sources/FirebladeECS/SparseSet.swift | 44 ++++++++++--------- 4 files changed, 45 insertions(+), 68 deletions(-) diff --git a/Sources/FirebladeECS/Family.swift b/Sources/FirebladeECS/Family.swift index 1608dd6..db7dca6 100644 --- a/Sources/FirebladeECS/Family.swift +++ b/Sources/FirebladeECS/Family.swift @@ -40,10 +40,7 @@ extension Family { return nexus.isMember(entityId, in: self) } - /*public var members: LazyMapCollection>, Entity> { - return nexus.members(of: self) - }*/ - internal var memberIds: [EntityIdentifier] { + internal var memberIds: UniformEntityIdentifiers { return nexus.members(of: self) } } diff --git a/Sources/FirebladeECS/Nexus+Family.swift b/Sources/FirebladeECS/Nexus+Family.swift index 0ee1c2e..e1fb4bb 100644 --- a/Sources/FirebladeECS/Nexus+Family.swift +++ b/Sources/FirebladeECS/Nexus+Family.swift @@ -39,24 +39,19 @@ extension Nexus { return family.traits.isMatch(components: componentSet) } - public func members(of family: Family) -> [EntityIdentifier] { + public func members(of family: Family) -> UniformEntityIdentifiers { let traitHash: FamilyTraitSetHash = family.traits.hashValue - return familyMembersByTraitHash[traitHash] ?? [] // FIXME: fail? + return familyMembersByTraitHash[traitHash] ?? UniformEntityIdentifiers() // FIXME: fail? } public func isMember(_ entity: Entity, in family: Family) -> Bool { return isMember(entity.identifier, in: family) } - public func isMember(byHash traitSetEntityIdHash: TraitEntityIdHash) -> Bool { - return familyContainsEntityId[traitSetEntityIdHash] ?? false - } - public func isMember(_ entityId: EntityIdentifier, in family: Family) -> Bool { let traitHash: FamilyTraitSetHash = family.traits.hashValue - // FIXME: this is costly! - guard let members: [EntityIdentifier] = familyMembersByTraitHash[traitHash] else { return false } - return members.contains(entityId) + guard let members: UniformEntityIdentifiers = familyMembersByTraitHash[traitHash] else { return false } + return members.has(entityId.index) } fileprivate func get(family traits: FamilyTraitSet) -> Family? { @@ -89,52 +84,35 @@ extension Nexus { func update(membership family: Family, for entityId: EntityIdentifier) { let entityIdx: EntityIndex = entityId.index - let traitHash: FamilyTraitSetHash = family.traits.hashValue + let traits: FamilyTraitSet = family.traits + let traitHash: FamilyTraitSetHash = traits.hashValue guard let componentIds: ComponentIdentifiers = componentIdsByEntity[entityIdx] else { return } - let trash: TraitEntityIdHash = calculateTraitEntityIdHash(traitHash: traitHash, entityIdx: entityIdx) - let is_Member: Bool = isMember(byHash: trash) + let is_Member: Bool = isMember(entityId, in: family) let componentsSet: ComponentSet = ComponentSet.init(componentIds) - let isMatch: Bool = family.traits.isMatch(components: componentsSet) + let isMatch: Bool = traits.isMatch(components: componentsSet) switch (isMatch, is_Member) { case (true, false): - add(to: family, entityId: entityId, with: trash) + add(to: traitHash, entityId: entityId, entityIdx: entityIdx) + notify(FamilyMemberAdded(member: entityId, to: traits)) case (false, true): - remove(from: family, entityId: entityId, with: trash) + remove(from: traitHash, entityId: entityId, entityIdx: entityIdx) + notify(FamilyMemberRemoved(member: entityId, from: traits)) default: break } } - fileprivate func add(to family: Family, entityId: EntityIdentifier, with traitEntityIdHash: TraitEntityIdHash) { - let traitHash: FamilyTraitSetHash = family.traits.hashValue - if familyMembersByTraitHash[traitHash] != nil { - // here we already checked if entity is a member - familyMembersByTraitHash[traitHash]!.append(entityId) - } else { - familyMembersByTraitHash[traitHash] = [EntityIdentifier].init(arrayLiteral: entityId) - familyMembersByTraitHash.reserveCapacity(4096) + fileprivate func add(to traitHash: FamilyTraitSetHash, entityId: EntityIdentifier, entityIdx: EntityIndex) { + if familyMembersByTraitHash[traitHash] == nil { + familyMembersByTraitHash[traitHash] = UniformEntityIdentifiers() } - - familyContainsEntityId[traitEntityIdHash] = true - - notify(FamilyMemberAdded(member: entityId, to: family.traits)) + familyMembersByTraitHash[traitHash]!.add(entityId, at: entityIdx) } - fileprivate func remove(from family: Family, entityId: EntityIdentifier, with traitEntityIdHash: TraitEntityIdHash) { - let traitHash: FamilyTraitSetHash = family.traits.hashValue - - // FIXME: index of is not cheep - guard let indexInFamily = familyMembersByTraitHash[traitHash]?.index(of: entityId) else { - assert(false, "removing entity id \(entityId) that is not in family \(family)") - report("removing entity id \(entityId) that is not in family \(family)") - return - } - - let removed: EntityIdentifier = familyMembersByTraitHash[traitHash]!.remove(at: indexInFamily) - familyContainsEntityId[traitEntityIdHash] = false - notify(FamilyMemberRemoved(member: removed, from: family.traits)) + fileprivate func remove(from traitHash: FamilyTraitSetHash, entityId: EntityIdentifier, entityIdx: EntityIndex) { + familyMembersByTraitHash[traitHash]?.remove(at: entityIdx) } } diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index 2279b22..0358731 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -11,6 +11,7 @@ public typealias ComponentIdsByEntityIndex = Int public typealias ComponentTypeHash = Int // component object identifier hash value //public typealias UniformComponents = SparseComponentSet public typealias UniformComponents = ContiguousComponentArray +public typealias UniformEntityIdentifiers = SparseEntityIdentifierSet public typealias ComponentIdentifiers = ContiguousArray public typealias ComponentSet = Set public typealias Entities = ContiguousArray @@ -42,8 +43,7 @@ public class Nexus { var freeEntities: ContiguousArray var familiyByTraitHash: [FamilyTraitSetHash: Family] - var familyMembersByTraitHash: [FamilyTraitSetHash: [EntityIdentifier]] // SparseSet for EntityIdentifier - var familyContainsEntityId: [TraitEntityIdHash: Bool] + var familyMembersByTraitHash: [FamilyTraitSetHash: UniformEntityIdentifiers] // SparseSet for EntityIdentifier public init() { entityStorage = Entities() @@ -53,7 +53,7 @@ public class Nexus { freeEntities = ContiguousArray() familiyByTraitHash = [:] familyMembersByTraitHash = [:] - familyContainsEntityId = [:] + } } diff --git a/Sources/FirebladeECS/SparseSet.swift b/Sources/FirebladeECS/SparseSet.swift index e7fe8f6..957f9fa 100644 --- a/Sources/FirebladeECS/SparseSet.swift +++ b/Sources/FirebladeECS/SparseSet.swift @@ -5,19 +5,17 @@ // Created by Christian Treffs on 30.10.17. // -public class SparseSet: UniformStorage { - - public typealias Element = Any +public class SparseSet: UniformStorage, Sequence { public typealias Index = Int - fileprivate typealias ComponentIdx = Int + fileprivate typealias DenseIndex = Int fileprivate var size: Int = 0 fileprivate var dense: ContiguousArray - fileprivate var sparse: [EntityIndex: ComponentIdx] - fileprivate typealias Pair = (key: EntityIndex, value: Element) + fileprivate var sparse: [Index: DenseIndex] + fileprivate typealias Pair = (key: Index, value: Element) public init() { dense = ContiguousArray() - sparse = [EntityIndex: ComponentIdx]() + sparse = [Index: DenseIndex]() } deinit { @@ -28,12 +26,12 @@ public class SparseSet: UniformStorage { internal var capacitySparse: Int { return sparse.capacity } internal var capacityDense: Int { return dense.capacity } - public func has(_ index: EntityIndex) -> Bool { + public func has(_ index: Index) -> Bool { return sparse[index] ?? Int.max < count && dense[sparse[index]!] != nil } - public func add(_ element: Element, at index: EntityIndex) { + public func add(_ element: Element, at index: Index) { if has(index) { return } sparse[index] = count let entry: Pair = Pair(key: index, value: element) @@ -41,15 +39,15 @@ public class SparseSet: UniformStorage { size += 1 } - public func get(at entityIdx: EntityIndex) -> Element? { + public func get(at entityIdx: Index) -> Element? { guard has(entityIdx) else { return nil } return dense[sparse[entityIdx]!]!.value } - public func remove(at index: EntityIndex) { + public func remove(at index: Index) { guard has(index) else { return } - let compIdx: ComponentIdx = sparse[index]! - let lastIdx: ComponentIdx = count-1 + let compIdx: DenseIndex = sparse[index]! + let lastIdx: DenseIndex = count-1 dense.swapAt(compIdx, lastIdx) sparse[index] = nil let swapped: Pair = dense[compIdx]! @@ -67,19 +65,23 @@ public class SparseSet: UniformStorage { sparse.removeAll(keepingCapacity: keepingCapacity) } -} - -extension SparseSet: Sequence { - public func makeIterator() -> AnyIterator { - var iterator = dense.makeIterator() + var iter = dense.makeIterator() return AnyIterator { - iterator.next()??.value + guard let next: Pair? = iter.next() else { return nil } + guard let pair: Pair = next else { return nil } + return pair.value } } + } -public class SparseComponentSet: SparseSet { - public typealias Element = Component +public class SparseComponentSet: SparseSet { public typealias Index = EntityIndex + +} + +public class SparseEntityIdentifierSet: SparseSet { + public typealias Index = EntityIndex + }