diff --git a/Package.swift b/Package.swift index 6e1ffe4..7355d60 100644 --- a/Package.swift +++ b/Package.swift @@ -19,6 +19,9 @@ let package = Package( dependencies: []), .testTarget( name: "FirebladeECSTests", + dependencies: ["FirebladeECS"]), + .testTarget( + name: "FirebladeECSPerformanceTests", dependencies: ["FirebladeECS"]) ] ) diff --git a/Sources/FirebladeECS/TypedFamily1.swift b/Sources/FirebladeECS/TypedFamily1.swift index 301525c..e91603f 100644 --- a/Sources/FirebladeECS/TypedFamily1.swift +++ b/Sources/FirebladeECS/TypedFamily1.swift @@ -52,11 +52,7 @@ public struct FamilyEntitiesAndComponents1: EntityComponentsSequenceProtocol } public mutating func next() -> (Entity, A)? { - guard let entityId = memberIdsIterator.next() else { - return nil - } - - guard + guard let entityId = memberIdsIterator.next(), let entity = nexus.get(entity: entityId), let compA: A = nexus.get(for: entityId) else { diff --git a/Sources/FirebladeECS/UnorderedSparseSet.swift b/Sources/FirebladeECS/UnorderedSparseSet.swift index 489d797..1f98579 100644 --- a/Sources/FirebladeECS/UnorderedSparseSet.swift +++ b/Sources/FirebladeECS/UnorderedSparseSet.swift @@ -5,7 +5,7 @@ // Created by Christian Treffs on 30.10.17. // -public class UnorderedSparseSet { +open class UnorderedSparseSet { public typealias Index = Int public typealias Key = Int @@ -14,8 +14,8 @@ public class UnorderedSparseSet { public let element: Element } - internal var dense: ContiguousArray - internal var sparse: [Index: Key] + public private(set) var dense: ContiguousArray + public private(set) var sparse: [Index: Key] public init() { sparse = [Index: Key]() @@ -30,6 +30,7 @@ public class UnorderedSparseSet { public var isEmpty: Bool { return dense.isEmpty } public var capacity: Int { return sparse.count } + @inlinable public func contains(_ key: Key) -> Bool { return find(at: key) != nil } @@ -58,6 +59,7 @@ public class UnorderedSparseSet { /// /// - Parameter key: the key /// - Returns: the element or nil of key not found. + @inlinable public func get(at key: Key) -> Element? { guard let (_, element) = find(at: key) else { return nil @@ -66,6 +68,7 @@ public class UnorderedSparseSet { return element } + @inlinable public func get(unsafeAt key: Key) -> Element { return find(at: key).unsafelyUnwrapped.1 } @@ -108,7 +111,8 @@ public class UnorderedSparseSet { return dense.removeLast() } - private func find(at key: Key) -> (Int, Element)? { + @inlinable + public func find(at key: Key) -> (Int, Element)? { guard let denseIndex = sparse[key], denseIndex < count else { return nil } diff --git a/Tests/FirebladeECSPerformanceTests/Base.swift b/Tests/FirebladeECSPerformanceTests/Base.swift new file mode 100644 index 0000000..63db0ff --- /dev/null +++ b/Tests/FirebladeECSPerformanceTests/Base.swift @@ -0,0 +1,71 @@ +// +// Base.swift +// FirebladeECSTests +// +// Created by Christian Treffs on 09.10.17. +// + +import FirebladeECS + +class EmptyComponent: Component { } + +class Name: Component { + var name: String + init(name: String) { + self.name = name + } +} + +class Position: Component { + var x: Int + var y: Int + init(x: Int, y: Int) { + self.x = x + self.y = y + } +} + +class Velocity: Component { + var a: Float + init(a: Float) { + self.a = a + } +} + +class Party: Component { + var partying: Bool + init(partying: Bool) { + self.partying = partying + } +} + +class Color: Component { + var r: UInt8 = 0 + var g: UInt8 = 0 + var b: UInt8 = 0 +} + +class ExampleSystem { + private let family: TypedFamily2 + + init(nexus: Nexus) { + family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: EmptyComponent.self) + } + + func update(deltaT: Double) { + family.forEach { (position: Position, velocity: Velocity) in + position.x *= 2 + velocity.a *= 2 + + } + + } + +} + + +final class SingleGameState: SingleComponent { + var shouldQuit: Bool = false + var playerHealth: Int = 67 + +} diff --git a/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift b/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift new file mode 100644 index 0000000..dea2bb8 --- /dev/null +++ b/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift @@ -0,0 +1,33 @@ +// +// ComponentPerformanceTests.swift +// FirebladeECSPerformanceTests +// +// Created by Christian Treffs on 14.02.19. +// + +@testable import FirebladeECS +import XCTest + +class ComponentTests: XCTestCase { + + func testMeasureStaticComponentIdentifier() { + let number: Int = 10_000 + measure { + for _ in 0.. = Set([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812]) + let b: Set = Set([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234]) + let c: Set = Set([3_410_346_899_765, 90_000_002, 12_212_321, 71, 6_123_345_676_543]) + + let input: ContiguousArray = ContiguousArray(arrayLiteral: a.hashValue, b.hashValue, c.hashValue) + measure { + for _ in 0..<1_000_000 { + let hashRes: Int = FirebladeECS.hash(combine: input) + _ = hashRes + } + } + } + + func testMeasureSetOfSetHash() { + let a: Set = Set([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812]) + let b: Set = Set([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234]) + let c: Set = Set([3_410_346_899_765, 90_000_002, 12_212_321, 71, 6_123_345_676_543]) + + let input = Set>(arrayLiteral: a, b, c) + measure { + for _ in 0..<1_000_000 { + let hash: Int = input.hashValue + _ = hash + } + } + } + + +} diff --git a/Tests/FirebladeECSTests/TypedFamilyPerformanceTests.swift b/Tests/FirebladeECSPerformanceTests/TypedFamilyPerformanceTests.swift similarity index 94% rename from Tests/FirebladeECSTests/TypedFamilyPerformanceTests.swift rename to Tests/FirebladeECSPerformanceTests/TypedFamilyPerformanceTests.swift index abada10..517fefe 100644 --- a/Tests/FirebladeECSTests/TypedFamilyPerformanceTests.swift +++ b/Tests/FirebladeECSPerformanceTests/TypedFamilyPerformanceTests.swift @@ -31,6 +31,25 @@ class TypedFamilyPerformanceTests: XCTestCase { super.tearDown() } + func testMeasureTraitMatching() { + + let a = nexus.create(entity: "a") + a.assign(Position(x: 1, y: 2)) + a.assign(Name(name: "myName")) + a.assign(Velocity(a: 3.14)) + a.assign(EmptyComponent()) + + let isMatch = nexus.family(requiresAll: Position.self, Velocity.self, + excludesAll: Party.self) + + measure { + for _ in 0..<10_000 { + let success = isMatch.canBecomeMember(a) + XCTAssert(success) + } + } + } + func testPerformanceTypedFamilyEntities() { let family = nexus.family(requires: Position.self, excludesAll: Party.self) diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index 63db0ff..0c122a7 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -10,33 +10,33 @@ import FirebladeECS class EmptyComponent: Component { } class Name: Component { - var name: String - init(name: String) { - self.name = name - } + var name: String + init(name: String) { + self.name = name + } } class Position: Component { - var x: Int - var y: Int - init(x: Int, y: Int) { - self.x = x - self.y = y - } + var x: Int + var y: Int + init(x: Int, y: Int) { + self.x = x + self.y = y + } } class Velocity: Component { - var a: Float - init(a: Float) { - self.a = a - } + var a: Float + init(a: Float) { + self.a = a + } } class Party: Component { - var partying: Bool - init(partying: Bool) { - self.partying = partying - } + var partying: Bool + init(partying: Bool) { + self.partying = partying + } } class Color: Component { @@ -45,27 +45,78 @@ class Color: Component { var b: UInt8 = 0 } -class ExampleSystem { - private let family: TypedFamily2 - - init(nexus: Nexus) { - family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: EmptyComponent.self) - } - - func update(deltaT: Double) { - family.forEach { (position: Position, velocity: Velocity) in - position.x *= 2 - velocity.a *= 2 - - } - - } - -} - final class SingleGameState: SingleComponent { var shouldQuit: Bool = false var playerHealth: Int = 67 } + + +class ExampleSystem { + private let family: TypedFamily2 + + init(nexus: Nexus) { + family = nexus.family(requiresAll: Position.self, Velocity.self, excludesAll: EmptyComponent.self) + } + + func update(deltaT: Double) { + family.forEach { (position: Position, velocity: Velocity) in + position.x *= 2 + velocity.a *= 2 + + } + + } + +} + + +class ColorSystem { + + let nexus: Nexus + lazy var colors = nexus.family(requires: Color.self) + + init(nexus: Nexus) { + self.nexus = nexus + } + + func update() { + colors + .forEach { (color: Color) in + color.r = 1 + color.g = 2 + color.b = 3 + } + } +} + +class PositionSystem { + let positions: TypedFamily1 + + var velocity: Double = 4.0 + + init(nexus: Nexus) { + positions = nexus.family(requires: Position.self) + } + + func randNorm() -> Double { + return 4.0 + } + + func update() { + positions + .forEach { [unowned self](pos: Position) in + + let deltaX: Double = self.velocity * ((self.randNorm() * 2) - 1) + let deltaY: Double = self.velocity * ((self.randNorm() * 2) - 1) + let x = pos.x + Int(deltaX) + let y = pos.y + Int(deltaY) + + pos.x = x + pos.y = y + } + } + +} + diff --git a/Tests/FirebladeECSTests/ComponentTests.swift b/Tests/FirebladeECSTests/ComponentTests.swift index c1b9a39..be1868f 100644 --- a/Tests/FirebladeECSTests/ComponentTests.swift +++ b/Tests/FirebladeECSTests/ComponentTests.swift @@ -10,16 +10,6 @@ import XCTest class ComponentTests: XCTestCase { - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - func testComponentIdentifier() { let p1 = Position(x: 1, y: 2) XCTAssert(p1.identifier == Position.identifier) @@ -30,25 +20,5 @@ class ComponentTests: XCTestCase { XCTAssert(Velocity.identifier != Position.identifier) } - func testMeasureStaticComponentIdentifier() { - let number: Int = 10_000 - measure { - for _ in 0.. = Set() - - var range: CountableRange = 0 ..< 1_000_000 - - let maxComponents: Int = 1000 - let components: [Int] = (0.. = Set([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812]) - let b: Set = Set([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234]) - let c: Set = Set([3_410_346_899_765, 90_000_002, 12_212_321, 71, 6_123_345_676_543]) - - let input: ContiguousArray = ContiguousArray(arrayLiteral: a.hashValue, b.hashValue, c.hashValue) - measure { - for _ in 0..<1_000_000 { - let hashRes: Int = FirebladeECS.hash(combine: input) - _ = hashRes - } - } - } - - func testMeasureSetOfSetHash() { - let a: Set = Set([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812]) - let b: Set = Set([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234]) - let c: Set = Set([3_410_346_899_765, 90_000_002, 12_212_321, 71, 6_123_345_676_543]) - - let input = Set>(arrayLiteral: a, b, c) - measure { - for _ in 0..<1_000_000 { - let hash: Int = input.hashValue - _ = hash - } - } - } - + + func makeComponent() -> Int { + let upperBound: Int = 44 + let high = UInt(arc4random()) << UInt(upperBound) + let low = UInt(arc4random()) + assert(high.leadingZeroBitCount < 64 - upperBound) + assert(high.trailingZeroBitCount >= upperBound) + assert(low.leadingZeroBitCount >= 32) + assert(low.trailingZeroBitCount <= 32) + let rand: UInt = high | low + let cH = Int(bitPattern: rand) + return cH + } + + func testCollisionsInCritialRange() { + + var hashSet: Set = Set() + + var range: CountableRange = 0 ..< 1_000_000 + + let maxComponents: Int = 1000 + let components: [Int] = (0.. Int { - let upperBound: Int = 44 - let high = UInt(arc4random()) << UInt(upperBound) - let low = UInt(arc4random()) - assert(high.leadingZeroBitCount < 64 - upperBound) - assert(high.trailingZeroBitCount >= upperBound) - assert(low.leadingZeroBitCount >= 32) - assert(low.trailingZeroBitCount <= 32) - let rand: UInt = high | low - let cH = Int(bitPattern: rand) - return cH - } -} diff --git a/Tests/FirebladeECSTests/SystemsTests.swift b/Tests/FirebladeECSTests/SystemsTests.swift index 38f5424..0006205 100644 --- a/Tests/FirebladeECSTests/SystemsTests.swift +++ b/Tests/FirebladeECSTests/SystemsTests.swift @@ -128,51 +128,3 @@ class SystemsTests: XCTestCase { } } - -class ColorSystem { - - let nexus: Nexus - lazy var colors = nexus.family(requires: Color.self) - - init(nexus: Nexus) { - self.nexus = nexus - } - - func update() { - colors - .forEach { (color: Color) in - color.r = 1 - color.g = 2 - color.b = 3 - } - } -} - -class PositionSystem { - let positions: TypedFamily1 - - var velocity: Double = 4.0 - - init(nexus: Nexus) { - positions = nexus.family(requires: Position.self) - } - - func randNorm() -> Double { - return 4.0 - } - - func update() { - positions - .forEach { [unowned self](pos: Position) in - - let deltaX: Double = self.velocity * ((self.randNorm() * 2) - 1) - let deltaY: Double = self.velocity * ((self.randNorm() * 2) - 1) - let x = pos.x + Int(deltaX) - let y = pos.y + Int(deltaY) - - pos.x = x - pos.y = y - } - } - -}