Cleanups and unification of storage protocol

This commit is contained in:
Christian Treffs 2017-10-31 12:18:07 +01:00
parent 4ada634130
commit 555e2088bb
7 changed files with 74 additions and 117 deletions

View File

@ -5,82 +5,82 @@
// Created by Christian Treffs on 28.10.17. // Created by Christian Treffs on 28.10.17.
// //
private let pow2: [Int] = [ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 4294967296 public protocol UniformStorage: class {
]
private func nearestToPow2(_ value: Int) -> Int {
let exp = (value.bitWidth-value.leadingZeroBitCount)
return pow2[exp]
}
public protocol ManagedContiguousArrayProtocol: class {
associatedtype Element associatedtype Element
static var chunkSize: Int { get } associatedtype Index
init(minCount: Int)
var count: Int { get } var count: Int { get }
func insert(_ element: Element, at index: Int) func add(_ element: Element, at index: Index)
func has(_ index: Int) -> Bool func has(_ index: Index) -> Bool
func get(at index: Int) -> Element? func get(at index: Index) -> Element?
func remove(at index: Int) func remove(at index: Index)
func clear(keepingCapacity: Bool)
} }
public class ManagedContiguousArray: ManagedContiguousArrayProtocol { public class ManagedContiguousArray: UniformStorage {
public static var chunkSize: Int = 4096 public static var chunkSize: Int = 4096
public typealias Index = Int
public typealias Element = Any public typealias Element = Any
var _count: Int = 0 var _size: Int = 0
var _store: ContiguousArray<Element?> = [] var _store: ContiguousArray<Element?> = []
public required init(minCount: Int = chunkSize) { public init(minCount: Int = chunkSize) {
_store = ContiguousArray<Element?>(repeating: nil, count: minCount) _store = ContiguousArray<Element?>(repeating: nil, count: minCount)
} }
deinit {
clear()
}
public var count: Int { public var count: Int {
return _count return _size
} }
public func insert(_ element: Element, at index: Int) { public func add(_ element: Element, at index: Index) {
if needsToGrow(index) { if needsToGrow(index) {
grow(including: index) grow(including: index)
} }
if _store[index] == nil { if _store[index] == nil {
_count += 1 _size += 1
} }
_store[index] = element _store[index] = element
} }
public func has(_ index: Int) -> Bool { public func has(_ index: Index) -> Bool {
if _store.count <= index { return false } if _store.count <= index { return false }
return _store[index] != nil return _store[index] != nil
} }
public func get(at index: Int) -> Element? { public func get(at index: Index) -> Element? {
return _store[index] return _store[index]
} }
public func remove(at index: Int) { public func remove(at index: Index) {
if _store[index] != nil { if _store[index] != nil {
_count -= 1 _size -= 1
}
_store[index] = nil
if _size == 0 {
clear()
} }
return _store[index] = nil
} }
internal func needsToGrow(_ index: Int) -> Bool { public func clear(keepingCapacity: Bool = false) {
_size = 0
_store.removeAll(keepingCapacity: keepingCapacity)
}
internal func needsToGrow(_ index: Index) -> Bool {
return index > _store.count - 1 return index > _store.count - 1
} }
internal func grow(including index: Int) { internal func grow(including index: Index) {
//var t = Timer()
//t.start()
let newCapacity: Int = nearest(to: index) let newCapacity: Int = nearest(to: index)
let count: Int = newCapacity-_store.count let newCount: Int = newCapacity-_store.count
//_store.reserveCapacity(newCapacity) for _ in 0..<newCount {
for _ in 0..<count {
_store.append(nil) _store.append(nil)
} }
//t.stop()
//print("did grow to \(newCapacity) in \(t.milliSeconds)ms")
} }
internal func nearest(to index: Int) -> Int { internal func nearest(to index: Index) -> Int {
let delta = Float(index) / Float(ManagedContiguousArray.chunkSize) let delta = Float(index) / Float(ManagedContiguousArray.chunkSize)
let multiplier = Int(delta) + 1 let multiplier = Int(delta) + 1
return multiplier * ManagedContiguousArray.chunkSize return multiplier * ManagedContiguousArray.chunkSize
@ -88,44 +88,10 @@ public class ManagedContiguousArray: ManagedContiguousArrayProtocol {
} }
public class ContiguousComponentArray: ManagedContiguousArray { public class ContiguousComponentArray: ManagedContiguousArray {
public typealias Element = Component public typealias Element = Component
public typealias Index = EntityIndex
} }
public class ContiguousEntityIdArray: ManagedContiguousArray { public class ContiguousEntityIdArray: ManagedContiguousArray {
public typealias Element = EntityIdentifier public typealias Element = EntityIdentifier
} }
/*
public func insert(_ element: Element, at entityIdx: EntityIndex) {
super.insert(element, at: entityIdx)
}
public func has(_ entityIdx: EntityIndex) -> Bool {
if _store.count <= entityIdx { return false }
return _store[entityIdx] != nil
}
public func get(at entityIdx: EntityIndex) -> Element? {
return _store[entityIdx]
}
public func remove(at entityIdx: EntityIndex) {
return _store[entityIdx] = nil
}
fileprivate func needsToGrow(_ entityId: EntityIndex) -> Bool {
return entityId > _store.count - 1
}
fileprivate func grow(to minIndex: Int) {
let newCapacity: Int = nearestToPow2(minIndex)
let count: Int = newCapacity-_store.count
let nilElements: ContiguousArray<Element?> = ContiguousArray<Element?>.init(repeating: nil, count: count)
_store.reserveCapacity(newCapacity)
_store.append(contentsOf: nilElements)
}
*/

View File

@ -17,7 +17,7 @@ extension Nexus {
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.contains(entityIdx) return uniforms.has(entityIdx)
} }
public func count(components entityId: EntityIdentifier) -> Int { public func count(components entityId: EntityIdentifier) -> Int {
@ -39,12 +39,10 @@ extension Nexus {
// TODO: replace component?! copy properties?! // TODO: replace component?! copy properties?!
return return
} }
if componentsByType[componentId] != nil { if componentsByType[componentId] == nil {
componentsByType[componentId]!.add(component, with: entityIdx) componentsByType[componentId] = UniformComponents()
} else {
componentsByType[componentId] = SparseComponentSet()
componentsByType[componentId]!.add(component, with: entityIdx)
} }
componentsByType[componentId]!.add(component, at: entityIdx)
// assigns the component id to the entity id // assigns the component id to the entity id
if componentIdsByEntity[entityIdx] != nil { if componentIdsByEntity[entityIdx] != nil {
@ -70,8 +68,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: SparseComponentSet = componentsByType[componentId] else { return nil } guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil }
return uniformComponents.get(at: entityId.index) return uniformComponents.get(at: entityId.index) as? Component
} }
public func get<C>(for entityId: EntityIdentifier) -> C? where C: Component { public func get<C>(for entityId: EntityIdentifier) -> C? where C: Component {
@ -80,7 +78,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: SparseComponentSet = componentsByType[componentId] else { return nil } guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil }
return uniformComponents.get(at: entityIdx) as? C return uniformComponents.get(at: entityIdx) as? C
} }
@ -94,7 +92,7 @@ extension Nexus {
let hash: EntityComponentHash = componentId.hashValue(using: entityIdx) let hash: EntityComponentHash = componentId.hashValue(using: entityIdx)
// MARK: delete component instance // MARK: delete component instance
componentsByType[componentId]?.remove(entityIdx) componentsByType[componentId]?.remove(at: 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 {

View File

@ -9,7 +9,7 @@
public typealias EntityComponentHash = Int public typealias EntityComponentHash = Int
public typealias ComponentIdsByEntityIndex = Int 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 = ContiguousComponentArray public typealias UniformComponents = ContiguousComponentArray
public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier> public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier>
public typealias ComponentSet = Set<ComponentIdentifier> public typealias ComponentSet = Set<ComponentIdentifier>
@ -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: SparseComponentSet] var componentsByType: [ComponentIdentifier: UniformComponents]
/// - 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

View File

@ -5,8 +5,10 @@
// Created by Christian Treffs on 30.10.17. // Created by Christian Treffs on 30.10.17.
// //
public class SparseComponentSet { public class SparseSet: UniformStorage {
public typealias Element = Component
public typealias Element = Any
public typealias Index = Int
fileprivate typealias ComponentIdx = Int fileprivate typealias ComponentIdx = Int
fileprivate var size: Int = 0 fileprivate var size: Int = 0
fileprivate var dense: ContiguousArray<Pair?> fileprivate var dense: ContiguousArray<Pair?>
@ -26,42 +28,37 @@ public class SparseComponentSet {
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 contains(_ entityIdx: EntityIndex ) -> Bool { public func has(_ index: EntityIndex) -> Bool {
return sparse[entityIdx] != nil && return sparse[index] ?? Int.max < count &&
sparse[entityIdx]! < count && dense[sparse[index]!] != nil
dense[sparse[entityIdx]!] != nil
} }
@discardableResult public func add(_ element: Element, at index: EntityIndex) {
public func add(_ element: Element, with entityIdx: EntityIndex ) -> Bool { if has(index) { return }
if contains(entityIdx) { return false } sparse[index] = count
sparse[entityIdx] = count let entry: Pair = Pair(key: index, value: element)
let entry: Pair = Pair(key: entityIdx, value: element)
dense.append(entry) dense.append(entry)
size += 1 size += 1
return true
} }
public func get(at entityIdx: EntityIndex) -> Element? { public func get(at entityIdx: EntityIndex) -> Element? {
guard contains(entityIdx) else { return nil } guard has(entityIdx) else { return nil }
return dense[sparse[entityIdx]!]!.value return dense[sparse[entityIdx]!]!.value
} }
@discardableResult public func remove(at index: EntityIndex) {
public func remove(_ entityIdx: EntityIndex ) -> Element? { guard has(index) else { return }
guard contains(entityIdx) else { return nil } let compIdx: ComponentIdx = sparse[index]!
let compIdx: ComponentIdx = sparse[entityIdx]!
let lastIdx: ComponentIdx = count-1 let lastIdx: ComponentIdx = count-1
dense.swapAt(compIdx, lastIdx) dense.swapAt(compIdx, lastIdx)
sparse[entityIdx] = nil sparse[index] = nil
let swapped: Pair = dense[compIdx]! let swapped: Pair = dense[compIdx]!
sparse[swapped.key] = compIdx sparse[swapped.key] = compIdx
let removed: Pair = dense.popLast()!! _ = dense.popLast()!!
size -= 1 size -= 1
if size == 0 { if size == 0 {
clear(keepingCapacity: false) clear(keepingCapacity: false)
} }
return removed.value
} }
public func clear(keepingCapacity: Bool = false) { public func clear(keepingCapacity: Bool = false) {
@ -72,7 +69,7 @@ public class SparseComponentSet {
} }
extension SparseComponentSet: Sequence { extension SparseSet: Sequence {
public func makeIterator() -> AnyIterator<Element> { public func makeIterator() -> AnyIterator<Element> {
var iterator = dense.makeIterator() var iterator = dense.makeIterator()
@ -81,3 +78,8 @@ extension SparseComponentSet: Sequence {
} }
} }
} }
public class SparseComponentSet: SparseSet {
public typealias Element = Component
public typealias Index = EntityIndex
}

View File

@ -47,11 +47,10 @@ class ExampleSystem {
} }
func update(deltaT: Double) { func update(deltaT: Double) {
family.iterate(components: Position.self, Velocity.self, Name.self) { (_, positionGetter, velocityGetter, nameGetter) in family.iterate(components: Position.self, Velocity.self, Name.self) { (_, positionGetter, velocityGetter, _) in
let position: Position = positionGetter! let position: Position = positionGetter!
let velocity: Velocity = velocityGetter! let velocity: Velocity = velocityGetter!
let name: Name? = nameGetter
position.x *= 2 position.x *= 2
velocity.a *= 2 velocity.a *= 2

View File

@ -162,12 +162,4 @@ class NexusTests: XCTestCase {
} }
func testComponentStorage() {
let nexus = Nexus()
let a = nexus.create()
let b = nexus.create()
let c = nexus.create()
}
} }

View File

@ -16,7 +16,7 @@ class SparseComponentSetTests: XCTestCase {
let num: Int = 100 let num: Int = 100
for i in 0..<num { for i in 0..<num {
s.add(Position(x: i, y: i), with: EntityIndex(i)) s.add(Position(x: i, y: i), at: EntityIndex(i))
} }
XCTAssert(s.count == num) XCTAssert(s.count == num)
@ -28,7 +28,7 @@ class SparseComponentSetTests: XCTestCase {
} }
for i in 0..<num { for i in 0..<num {
s.remove(EntityIndex(i)) s.remove(at: EntityIndex(i))
} }
XCTAssert(s.count == 0) XCTAssert(s.count == 0)