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