Fixed SparseSet - needs optimizations
This commit is contained in:
parent
a491d457ec
commit
a559387891
|
|
@ -9,15 +9,15 @@ extension Nexus {
|
|||
|
||||
public var numComponents: Int {
|
||||
var count = 0
|
||||
for (_, value) in componentsByType {
|
||||
count += value._count
|
||||
for (_, uniformComps) in componentsByType {
|
||||
count += uniformComps.count
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
public func has(componentId: ComponentIdentifier, entityIdx: EntityIndex) -> Bool {
|
||||
guard let uniforms = componentsByType[componentId] else { return false }
|
||||
return uniforms.has(entityIdx)
|
||||
return uniforms.contains(entityIdx)
|
||||
}
|
||||
|
||||
public func count(components entityId: EntityIdentifier) -> Int {
|
||||
|
|
@ -40,10 +40,10 @@ extension Nexus {
|
|||
return
|
||||
}
|
||||
if componentsByType[componentId] != nil {
|
||||
componentsByType[componentId]!.insert(component, at: entityIdx)
|
||||
componentsByType[componentId]!.add(component, with: entityIdx)
|
||||
} else {
|
||||
componentsByType[componentId] = UniformComponents()
|
||||
componentsByType[componentId]!.insert(component, at: entityIdx)
|
||||
componentsByType[componentId] = SparseComponentSet()
|
||||
componentsByType[componentId]!.add(component, with: entityIdx)
|
||||
}
|
||||
|
||||
// assigns the component id to the entity id
|
||||
|
|
@ -70,8 +70,8 @@ extension Nexus {
|
|||
}
|
||||
|
||||
public func get(component componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component? {
|
||||
guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil }
|
||||
return uniformComponents.get(at: entityId.index) as? Component
|
||||
guard let uniformComponents: SparseComponentSet = componentsByType[componentId] else { return nil }
|
||||
return uniformComponents.get(at: entityId.index)
|
||||
}
|
||||
|
||||
public func get<C>(for entityId: EntityIdentifier) -> C? where C: Component {
|
||||
|
|
@ -80,7 +80,7 @@ extension Nexus {
|
|||
}
|
||||
|
||||
fileprivate func get<C>(componentId: ComponentIdentifier, entityIdx: EntityIndex) -> C? where C: Component {
|
||||
guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil }
|
||||
guard let uniformComponents: SparseComponentSet = componentsByType[componentId] else { return nil }
|
||||
return uniformComponents.get(at: entityIdx) as? C
|
||||
}
|
||||
|
||||
|
|
@ -90,10 +90,11 @@ extension Nexus {
|
|||
|
||||
@discardableResult
|
||||
public func remove(component componentId: ComponentIdentifier, from entityId: EntityIdentifier) -> Bool {
|
||||
let hash: EntityComponentHash = componentId.hashValue(using: entityId.index)
|
||||
let entityIdx: EntityIndex = entityId.index
|
||||
let hash: EntityComponentHash = componentId.hashValue(using: entityIdx)
|
||||
|
||||
// MARK: delete component instance
|
||||
componentsByType[componentId]?.remove(at: entityId.index)
|
||||
componentsByType[componentId]?.remove(entityIdx)
|
||||
|
||||
// MARK: unassign component
|
||||
guard let removeIndex: ComponentIdsByEntityIndex = componentIdsByEntityLookup.removeValue(forKey: hash) else {
|
||||
|
|
@ -101,17 +102,17 @@ extension Nexus {
|
|||
return false
|
||||
}
|
||||
|
||||
guard componentIdsByEntity[entityId.index]?.remove(at: removeIndex) != nil else {
|
||||
guard componentIdsByEntity[entityIdx]?.remove(at: removeIndex) != nil else {
|
||||
assert(false, "ComponentRemove failure: nothing was removed")
|
||||
report("ComponentRemove failure: nothing was removed")
|
||||
return false
|
||||
}
|
||||
|
||||
// relocate remaining indices pointing in the componentsByEntity map
|
||||
if let remainingComponents: ComponentIdentifiers = componentIdsByEntity[entityId.index] {
|
||||
if let remainingComponents: ComponentIdentifiers = componentIdsByEntity[entityIdx] {
|
||||
// FIXME: may be expensive but is cheap for small entities
|
||||
for (index, compId) in remainingComponents.enumerated() {
|
||||
let cHash: EntityComponentHash = compId.hashValue(using: entityId.index)
|
||||
let cHash: EntityComponentHash = compId.hashValue(using: entityIdx)
|
||||
assert(cHash != hash)
|
||||
componentIdsByEntityLookup[cHash] = index
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public class Nexus {
|
|||
|
||||
/// - Key: component type identifier
|
||||
/// - Value: each element is a component instance of the same type (uniform). New component instances are appended.
|
||||
var componentsByType: [ComponentIdentifier: UniformComponents]
|
||||
var componentsByType: [ComponentIdentifier: SparseComponentSet]
|
||||
|
||||
/// - Key: entity id as index
|
||||
/// - Value: each element is a component identifier associated with this entity
|
||||
|
|
|
|||
|
|
@ -5,55 +5,92 @@
|
|||
// Created by Christian Treffs on 30.10.17.
|
||||
//
|
||||
|
||||
public struct SparseComponentSet<Element: Component> {
|
||||
public class SparseComponentSet {
|
||||
public typealias Element = Component
|
||||
public let chunkSize: Int = 4096
|
||||
fileprivate typealias ComponentIdx = Int
|
||||
fileprivate typealias EntryTuple = (entityId: EntityIdentifier, component: Element)
|
||||
fileprivate var dense: ContiguousArray<EntryTuple?>
|
||||
fileprivate var sparse: [EntityIdentifier: ComponentIdx]
|
||||
fileprivate var size: Int = 0
|
||||
|
||||
public init(_ min: Int = 1024) {
|
||||
dense = ContiguousArray<EntryTuple?>()
|
||||
dense.reserveCapacity(min)
|
||||
|
||||
sparse = [EntityIdentifier: ComponentIdx](minimumCapacity: min)
|
||||
fileprivate class Pair {
|
||||
let key: EntityIndex
|
||||
let value: Element
|
||||
init(key: EntityIndex, value: Element) {
|
||||
self.key = key
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
public var count: Int { return dense.count }
|
||||
fileprivate var dense: ContiguousArray<Pair?>
|
||||
fileprivate var sparse: [EntityIndex: ComponentIdx]
|
||||
|
||||
public init() {
|
||||
dense = ContiguousArray<Pair?>()
|
||||
dense.reserveCapacity(chunkSize)
|
||||
sparse = [EntityIndex: ComponentIdx].init(minimumCapacity: chunkSize)
|
||||
}
|
||||
|
||||
public var count: Int { return size }
|
||||
internal var capacitySparse: Int { return sparse.count }
|
||||
internal var capacityDense: Int { return dense.count }
|
||||
|
||||
public func contains(_ entityId: EntityIdentifier) -> Bool {
|
||||
guard let compIdx: ComponentIdx = sparse[entityId] else { return false }
|
||||
public func contains(_ entityIdx: EntityIndex ) -> Bool {
|
||||
guard let compIdx: ComponentIdx = sparse[entityIdx] else { return false }
|
||||
return compIdx < count && dense[compIdx] != nil
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public mutating func add(_ element: Element, with entityId: EntityIdentifier) -> Bool {
|
||||
if contains(entityId) { return false }
|
||||
sparse[entityId] = count
|
||||
let entry: EntryTuple = EntryTuple(entityId: entityId, component: element)
|
||||
public func add(_ element: Element, with entityIdx: EntityIndex ) -> Bool {
|
||||
if contains(entityIdx) { return false }
|
||||
if needsToGrow(entityIdx) {
|
||||
grow(including: entityIdx)
|
||||
}
|
||||
sparse[entityIdx] = count
|
||||
let entry: Pair = Pair(key: entityIdx, value: element)
|
||||
dense.append(entry)
|
||||
size += 1
|
||||
return true
|
||||
}
|
||||
|
||||
public func get(_ entityId: EntityIdentifier) -> Element? {
|
||||
guard let compIdx: ComponentIdx = sparse[entityId] else { return nil }
|
||||
return dense[compIdx]?.component
|
||||
public func get(at entityIdx: EntityIndex) -> Element? {
|
||||
guard let compIdx: ComponentIdx = sparse[entityIdx] else { return nil }
|
||||
return dense[compIdx]!.value
|
||||
}
|
||||
|
||||
public mutating func remove(_ entityId: EntityIdentifier) -> Element? {
|
||||
guard let compIdx: ComponentIdx = sparse[entityId] else { return nil }
|
||||
dense.swapAt(compIdx, count-1)
|
||||
sparse[entityId] = nil
|
||||
let swapped: EntryTuple = dense[compIdx]!
|
||||
sparse[swapped.entityId] = compIdx
|
||||
let removed: EntryTuple = dense.popLast()!!
|
||||
return removed.component
|
||||
@discardableResult
|
||||
public func remove(_ entityIdx: EntityIndex ) -> Element? {
|
||||
guard let compIdx: ComponentIdx = sparse[entityIdx] else { return nil }
|
||||
let last: Int = count-1
|
||||
dense.swapAt(compIdx, last)
|
||||
sparse[entityIdx] = nil
|
||||
let swapped: Pair = dense[compIdx]!
|
||||
sparse[swapped.key] = compIdx
|
||||
let removed: Pair = dense.popLast()!!
|
||||
size -= 1
|
||||
return removed.value
|
||||
}
|
||||
|
||||
public mutating func clear(keepingCapacity: Bool = false) {
|
||||
public func clear(keepingCapacity: Bool = false) {
|
||||
dense.removeAll(keepingCapacity: keepingCapacity)
|
||||
}
|
||||
|
||||
fileprivate func needsToGrow(_ index: Int) -> Bool {
|
||||
return index > count - 1
|
||||
}
|
||||
|
||||
fileprivate func grow(including index: Int) {
|
||||
let newCapacity: Int = nearest(to: index)
|
||||
//let newCount: Int = newCapacity-count
|
||||
dense.reserveCapacity(newCapacity)
|
||||
/*for _ in 0..<newCount {
|
||||
dense.append(nil)
|
||||
}*/
|
||||
}
|
||||
|
||||
fileprivate func nearest(to index: Int) -> Int {
|
||||
let delta = Float(index) / Float(chunkSize)
|
||||
let multiplier = Int(delta) + 1
|
||||
return multiplier * chunkSize
|
||||
}
|
||||
}
|
||||
|
||||
extension SparseComponentSet: Sequence {
|
||||
|
|
@ -62,7 +99,7 @@ extension SparseComponentSet: Sequence {
|
|||
var iterator = dense.makeIterator()
|
||||
|
||||
return AnyIterator<Element> {
|
||||
iterator.next()??.component
|
||||
iterator.next()??.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,27 +6,31 @@
|
|||
//
|
||||
|
||||
import XCTest
|
||||
import FirebladeECS
|
||||
@testable import FirebladeECS
|
||||
|
||||
class SparseComponentSetTests: XCTestCase {
|
||||
|
||||
func testSet() {
|
||||
var s = SparseComponentSet<Position>()
|
||||
let s = SparseComponentSet()
|
||||
|
||||
s.add(Position(x: 1, y: 2), with: 0)
|
||||
s.add(Position(x: 13, y: 23), with: 13)
|
||||
s.add(Position(x: 123, y: 123), with: 123)
|
||||
let num: Int = 100
|
||||
|
||||
for p in s {
|
||||
print(p.x, p.y)
|
||||
for i in 0..<num {
|
||||
s.add(Position(x: i, y: i), with: EntityIndex(i))
|
||||
}
|
||||
|
||||
s.remove(13)
|
||||
XCTAssert(s.count == num)
|
||||
|
||||
for p in s {
|
||||
print(p.x, p.y)
|
||||
for i in 0..<num {
|
||||
let idx = num-i-1
|
||||
let p: Position = s.get(at: idx) as! Position
|
||||
XCTAssertEqual(idx, p.x)
|
||||
}
|
||||
|
||||
s.remove(234567890)
|
||||
for i in 0..<num {
|
||||
s.remove(EntityIndex(i))
|
||||
}
|
||||
|
||||
XCTAssert(s.count == 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue