diff --git a/Sources/FirebladeECS/ManagedContiguousArray.swift b/Sources/FirebladeECS/ManagedContiguousArray.swift new file mode 100644 index 0000000..5d627b1 --- /dev/null +++ b/Sources/FirebladeECS/ManagedContiguousArray.swift @@ -0,0 +1,102 @@ +// +// ManagedContiguousArray.swift +// FirebladeECS +// +// Created by Christian Treffs on 28.10.17. +// +public struct ManagedContiguousArray { + public typealias Index = Int + + @usableFromInline let chunkSize: Int + @usableFromInline var size: Int = 0 + @usableFromInline var store: ContiguousArray = [] + + public init(minCount: Int = 4096) { + chunkSize = minCount + store = ContiguousArray(repeating: nil, count: minCount) + } + + @inline(__always) + public var count: Int { + size + } + + @discardableResult + @inlinable + public mutating func insert(_ element: Element, at index: Int) -> Bool { + if needsToGrow(index) { + grow(to: index) + } + if store[index] == nil { + size += 1 + } + store[index] = element + return true + } + + @inlinable + public func contains(_ index: Index) -> Bool { + if store.count <= index { + return false + } + return store[index] != nil + } + + @inline(__always) + public func get(at index: Index) -> Element? { + store[index] + } + + @inline(__always) + public func get(unsafeAt index: Index) -> Element { + store[index].unsafelyUnwrapped + } + + @discardableResult + @inlinable + public mutating func remove(at index: Index) -> Bool { + if store[index] != nil { + size -= 1 + } + store[index] = nil + if size == 0 { + clear() + } + return true + } + + @inlinable + public mutating func clear(keepingCapacity: Bool = false) { + size = 0 + store.removeAll(keepingCapacity: keepingCapacity) + } + + @inlinable + func needsToGrow(_ index: Index) -> Bool { + index > store.count - 1 + } + + @inlinable + mutating func grow(to index: Index) { + let newCapacity: Int = calculateCapacity(to: index) + let newCount: Int = newCapacity - store.count + store += ContiguousArray(repeating: nil, count: newCount) + } + + @inlinable + func calculateCapacity(to index: Index) -> Int { + let delta = Float(index) / Float(chunkSize) + let multiplier = Int(delta.rounded(.up)) + 1 + return multiplier * chunkSize + } +} + +// MARK: - Equatable +extension ManagedContiguousArray: Equatable where Element: Equatable { + public static func == (lhs: ManagedContiguousArray, rhs: ManagedContiguousArray) -> Bool { + lhs.store == rhs.store + } +} + +// MARK: - Codable +extension ManagedContiguousArray: Codable where Element: Codable { } diff --git a/Sources/FirebladeECS/Nexus+Component.swift b/Sources/FirebladeECS/Nexus+Component.swift index 664f6bd..170a677 100644 --- a/Sources/FirebladeECS/Nexus+Component.swift +++ b/Sources/FirebladeECS/Nexus+Component.swift @@ -34,7 +34,7 @@ extension Nexus { // add component instances to uniform component stores if componentsByType[componentId] == nil { - componentsByType[componentId] = UnorderedSparseSet() + componentsByType[componentId] = ManagedContiguousArray() } componentsByType[componentId]?.insert(component, at: entityId.id) diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index 4559e30..cbc630f 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -16,7 +16,7 @@ public final class Nexus { /// - Key: ComponentIdentifier aka component type. /// - Value: Array of component instances of same type (uniform). /// New component instances are appended. - @usableFromInline final var componentsByType: [ComponentIdentifier: UnorderedSparseSet] + @usableFromInline final var componentsByType: [ComponentIdentifier: ManagedContiguousArray] /// - Key: EntityIdentifier aka entity index /// - Value: Set of unique component types (ComponentIdentifier). @@ -43,7 +43,7 @@ public final class Nexus { } internal init(entityStorage: UnorderedSparseSet, - componentsByType: [ComponentIdentifier: UnorderedSparseSet], + componentsByType: [ComponentIdentifier: ManagedContiguousArray], componentsByEntity: [EntityIdentifier: Set], freeEntities: [EntityIdentifier], familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet], diff --git a/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift b/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift index 8dfebc8..ac2b9bb 100644 --- a/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift +++ b/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift @@ -10,7 +10,8 @@ import XCTest class ComponentIdentifierTests: XCTestCase { - /// debug: 0.456 sec + /// release: 0.034 sec + /// debug: 0.456 sec func testMeasureStaticComponentIdentifier() { let number: Int = 1_000_000 measure { @@ -21,7 +22,8 @@ class ComponentIdentifierTests: XCTestCase { } } - /// debug: 0.413 sec + /// release: 0.036 sec + /// debug: 0.413 sec func testMeasureComponentIdentifier() { let number: Int = 1_000_000 let pos = Position(x: 1, y: 2) diff --git a/Tests/FirebladeECSPerformanceTests/TypedFamilyPerformanceTests.swift b/Tests/FirebladeECSPerformanceTests/TypedFamilyPerformanceTests.swift index 920602f..f4b7bd7 100644 --- a/Tests/FirebladeECSPerformanceTests/TypedFamilyPerformanceTests.swift +++ b/Tests/FirebladeECSPerformanceTests/TypedFamilyPerformanceTests.swift @@ -30,7 +30,7 @@ class TypedFamilyPerformanceTests: XCTestCase { super.tearDown() } - /// release: 0.011 sec + /// release: 0.007 sec /// debug: 0.017 sec func testMeasureTraitMatching() { let a = nexus.createEntity() @@ -92,7 +92,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, numEntities * 10) } - /// release: 0.003 sec + /// release: 0.002 sec /// debug: 0.010 sec func testPerformanceTypedFamilyOneComponent() { let family = nexus.family(requires: Position.self, excludesAll: Party.self) @@ -116,7 +116,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, family.count * 10) } - /// release: 0.004 sec + /// release: 0.002 sec /// debug: 0.016 sec func testPerformanceTypedFamilyEntityOneComponent() { let family = nexus.family(requires: Position.self, excludesAll: Party.self) @@ -142,7 +142,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, family.count * 10) } - /// release: 0.005 sec + /// release: 0.002 sec /// debug: 0.016 sec func testPerformanceTypedFamilyTwoComponents() { let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self) @@ -167,7 +167,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, family.count * 10) } - /// release: 0.006 sec + /// release: 0.003 sec func testPerformanceTypedFamilyEntityTwoComponents() { let family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: Party.self) @@ -193,7 +193,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, family.count * 10) } - /// release: 0.007 sec + /// release: 0.004 sec func testPerformanceTypedFamilyThreeComponents() { let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self) @@ -218,7 +218,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, family.count * 10) } - /// release: 0.008 sec + /// release: 0.004 sec func testPerformanceTypedFamilyEntityThreeComponents() { let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, excludesAll: Party.self) @@ -245,7 +245,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, family.count * 10) } - /// release: 0.009 sec + /// release: 0.004 sec func testPerformanceTypedFamilyFourComponents() { let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self) @@ -271,7 +271,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, family.count * 10) } - /// release: 0.010 sec + /// release: 0.005 sec func testPerformanceTypedFamilyEntityFourComponents() { let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, excludesAll: Party.self) @@ -299,7 +299,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, family.count * 10) } - /// release: 0.012 sec + /// release: 0.005 sec func testPerformanceTypedFamilyFiveComponents() { let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self) @@ -325,7 +325,7 @@ class TypedFamilyPerformanceTests: XCTestCase { XCTAssertEqual(loopCount, family.count * 10) } - /// release: 0.012 sec + /// release: 0.006 sec func testPerformanceTypedFamilyEntityFiveComponents() { let family = nexus.family(requiresAll: Position.self, Velocity.self, Name.self, Color.self, EmptyComponent.self, excludesAll: Party.self)