Reworked Entitiy storage as SparseSet

This commit is contained in:
Christian Treffs 2017-11-01 07:44:24 +01:00
parent 555e2088bb
commit 19cab7af5d
4 changed files with 45 additions and 68 deletions

View File

@ -40,10 +40,7 @@ extension Family {
return nexus.isMember(entityId, in: self) return nexus.isMember(entityId, in: self)
} }
/*public var members: LazyMapCollection<LazyFilterCollection<LazyMapCollection<EntityIdSet, Entity?>>, Entity> { internal var memberIds: UniformEntityIdentifiers {
return nexus.members(of: self)
}*/
internal var memberIds: [EntityIdentifier] {
return nexus.members(of: self) return nexus.members(of: self)
} }
} }

View File

@ -39,24 +39,19 @@ extension Nexus {
return family.traits.isMatch(components: componentSet) 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 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 { public func isMember(_ entity: Entity, in family: Family) -> Bool {
return isMember(entity.identifier, in: family) 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 { public func isMember(_ entityId: EntityIdentifier, in family: Family) -> Bool {
let traitHash: FamilyTraitSetHash = family.traits.hashValue let traitHash: FamilyTraitSetHash = family.traits.hashValue
// FIXME: this is costly! guard let members: UniformEntityIdentifiers = familyMembersByTraitHash[traitHash] else { return false }
guard let members: [EntityIdentifier] = familyMembersByTraitHash[traitHash] else { return false } return members.has(entityId.index)
return members.contains(entityId)
} }
fileprivate func get(family traits: FamilyTraitSet) -> Family? { fileprivate func get(family traits: FamilyTraitSet) -> Family? {
@ -89,52 +84,35 @@ extension Nexus {
func update(membership family: Family, for entityId: EntityIdentifier) { func update(membership family: Family, for entityId: EntityIdentifier) {
let entityIdx: EntityIndex = entityId.index 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 } guard let componentIds: ComponentIdentifiers = componentIdsByEntity[entityIdx] else { return }
let trash: TraitEntityIdHash = calculateTraitEntityIdHash(traitHash: traitHash, entityIdx: entityIdx) let is_Member: Bool = isMember(entityId, in: family)
let is_Member: Bool = isMember(byHash: trash)
let componentsSet: ComponentSet = ComponentSet.init(componentIds) 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) { switch (isMatch, is_Member) {
case (true, false): 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): case (false, true):
remove(from: family, entityId: entityId, with: trash) remove(from: traitHash, entityId: entityId, entityIdx: entityIdx)
notify(FamilyMemberRemoved(member: entityId, from: traits))
default: default:
break break
} }
} }
fileprivate func add(to family: Family, entityId: EntityIdentifier, with traitEntityIdHash: TraitEntityIdHash) { fileprivate func add(to traitHash: FamilyTraitSetHash, entityId: EntityIdentifier, entityIdx: EntityIndex) {
let traitHash: FamilyTraitSetHash = family.traits.hashValue if familyMembersByTraitHash[traitHash] == nil {
if familyMembersByTraitHash[traitHash] != nil { familyMembersByTraitHash[traitHash] = UniformEntityIdentifiers()
// here we already checked if entity is a member }
familyMembersByTraitHash[traitHash]!.append(entityId) familyMembersByTraitHash[traitHash]!.add(entityId, at: entityIdx)
} else {
familyMembersByTraitHash[traitHash] = [EntityIdentifier].init(arrayLiteral: entityId)
familyMembersByTraitHash.reserveCapacity(4096)
} }
familyContainsEntityId[traitEntityIdHash] = true fileprivate func remove(from traitHash: FamilyTraitSetHash, entityId: EntityIdentifier, entityIdx: EntityIndex) {
familyMembersByTraitHash[traitHash]?.remove(at: entityIdx)
notify(FamilyMemberAdded(member: entityId, to: family.traits))
}
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))
} }
} }

View File

@ -11,6 +11,7 @@ public typealias ComponentIdsByEntityIndex = Int
public typealias ComponentTypeHash = Int // component object identifier hash value public typealias ComponentTypeHash = Int // component object identifier hash value
//public typealias UniformComponents = SparseComponentSet //public typealias UniformComponents = SparseComponentSet
public typealias UniformComponents = ContiguousComponentArray public typealias UniformComponents = ContiguousComponentArray
public typealias UniformEntityIdentifiers = SparseEntityIdentifierSet
public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier> public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier>
public typealias ComponentSet = Set<ComponentIdentifier> public typealias ComponentSet = Set<ComponentIdentifier>
public typealias Entities = ContiguousArray<Entity> public typealias Entities = ContiguousArray<Entity>
@ -42,8 +43,7 @@ public class Nexus {
var freeEntities: ContiguousArray<EntityIdentifier> var freeEntities: ContiguousArray<EntityIdentifier>
var familiyByTraitHash: [FamilyTraitSetHash: Family] var familiyByTraitHash: [FamilyTraitSetHash: Family]
var familyMembersByTraitHash: [FamilyTraitSetHash: [EntityIdentifier]] // SparseSet for EntityIdentifier var familyMembersByTraitHash: [FamilyTraitSetHash: UniformEntityIdentifiers] // SparseSet for EntityIdentifier
var familyContainsEntityId: [TraitEntityIdHash: Bool]
public init() { public init() {
entityStorage = Entities() entityStorage = Entities()
@ -53,7 +53,7 @@ public class Nexus {
freeEntities = ContiguousArray<EntityIdentifier>() freeEntities = ContiguousArray<EntityIdentifier>()
familiyByTraitHash = [:] familiyByTraitHash = [:]
familyMembersByTraitHash = [:] familyMembersByTraitHash = [:]
familyContainsEntityId = [:]
} }
} }

View File

@ -5,19 +5,17 @@
// Created by Christian Treffs on 30.10.17. // Created by Christian Treffs on 30.10.17.
// //
public class SparseSet: UniformStorage { public class SparseSet<Element>: UniformStorage, Sequence {
public typealias Element = Any
public typealias Index = Int public typealias Index = Int
fileprivate typealias ComponentIdx = Int fileprivate typealias DenseIndex = Int
fileprivate var size: Int = 0 fileprivate var size: Int = 0
fileprivate var dense: ContiguousArray<Pair?> fileprivate var dense: ContiguousArray<Pair?>
fileprivate var sparse: [EntityIndex: ComponentIdx] fileprivate var sparse: [Index: DenseIndex]
fileprivate typealias Pair = (key: EntityIndex, value: Element) fileprivate typealias Pair = (key: Index, value: Element)
public init() { public init() {
dense = ContiguousArray<Pair?>() dense = ContiguousArray<Pair?>()
sparse = [EntityIndex: ComponentIdx]() sparse = [Index: DenseIndex]()
} }
deinit { deinit {
@ -28,12 +26,12 @@ public class SparseSet: UniformStorage {
internal var capacitySparse: Int { return sparse.capacity } internal var capacitySparse: Int { return sparse.capacity }
internal var capacityDense: Int { return dense.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 && return sparse[index] ?? Int.max < count &&
dense[sparse[index]!] != nil dense[sparse[index]!] != nil
} }
public func add(_ element: Element, at index: EntityIndex) { public func add(_ element: Element, at index: Index) {
if has(index) { return } if has(index) { return }
sparse[index] = count sparse[index] = count
let entry: Pair = Pair(key: index, value: element) let entry: Pair = Pair(key: index, value: element)
@ -41,15 +39,15 @@ public class SparseSet: UniformStorage {
size += 1 size += 1
} }
public func get(at entityIdx: EntityIndex) -> Element? { public func get(at entityIdx: Index) -> Element? {
guard has(entityIdx) else { return nil } guard has(entityIdx) else { return nil }
return dense[sparse[entityIdx]!]!.value return dense[sparse[entityIdx]!]!.value
} }
public func remove(at index: EntityIndex) { public func remove(at index: Index) {
guard has(index) else { return } guard has(index) else { return }
let compIdx: ComponentIdx = sparse[index]! let compIdx: DenseIndex = sparse[index]!
let lastIdx: ComponentIdx = count-1 let lastIdx: DenseIndex = count-1
dense.swapAt(compIdx, lastIdx) dense.swapAt(compIdx, lastIdx)
sparse[index] = nil sparse[index] = nil
let swapped: Pair = dense[compIdx]! let swapped: Pair = dense[compIdx]!
@ -67,19 +65,23 @@ public class SparseSet: UniformStorage {
sparse.removeAll(keepingCapacity: keepingCapacity) sparse.removeAll(keepingCapacity: keepingCapacity)
} }
}
extension SparseSet: Sequence {
public func makeIterator() -> AnyIterator<Element> { public func makeIterator() -> AnyIterator<Element> {
var iterator = dense.makeIterator() var iter = dense.makeIterator()
return AnyIterator<Element> { return AnyIterator<Element> {
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 typealias Index = EntityIndex public class SparseComponentSet: SparseSet<Component> {
public typealias Index = EntityIndex
}
public class SparseEntityIdentifierSet: SparseSet<EntityIdentifier> {
public typealias Index = EntityIndex
} }