Cleanup and refactor tests

This commit is contained in:
Christian Treffs 2019-02-14 12:34:42 +01:00
parent c1a68300de
commit fee1c2e52e
14 changed files with 312 additions and 248 deletions

View File

@ -19,6 +19,9 @@ let package = Package(
dependencies: []),
.testTarget(
name: "FirebladeECSTests",
dependencies: ["FirebladeECS"]),
.testTarget(
name: "FirebladeECSPerformanceTests",
dependencies: ["FirebladeECS"])
]
)

View File

@ -52,11 +52,7 @@ public struct FamilyEntitiesAndComponents1<A>: 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 {

View File

@ -5,7 +5,7 @@
// Created by Christian Treffs on 30.10.17.
//
public class UnorderedSparseSet<Element> {
open class UnorderedSparseSet<Element> {
public typealias Index = Int
public typealias Key = Int
@ -14,8 +14,8 @@ public class UnorderedSparseSet<Element> {
public let element: Element
}
internal var dense: ContiguousArray<Entry>
internal var sparse: [Index: Key]
public private(set) var dense: ContiguousArray<Entry>
public private(set) var sparse: [Index: Key]
public init() {
sparse = [Index: Key]()
@ -30,6 +30,7 @@ public class UnorderedSparseSet<Element> {
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<Element> {
///
/// - 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<Element> {
return element
}
@inlinable
public func get(unsafeAt key: Key) -> Element {
return find(at: key).unsafelyUnwrapped.1
}
@ -108,7 +111,8 @@ public class UnorderedSparseSet<Element> {
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
}

View File

@ -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<Position, Velocity>
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
}

View File

@ -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..<number {
let id = Position.identifier
_ = id
}
}
}
func testMeasureComponentIdentifier() {
let number: Int = 10_000
let pos = Position(x: 1, y: 2)
measure {
for _ in 0..<number {
let id = pos.identifier
_ = id
}
}
}
}

View File

@ -0,0 +1,42 @@
//
// HashingPerformanceTests.swift
// FirebladeECSPerformanceTests
//
// Created by Christian Treffs on 14.02.19.
//
import XCTest
import FirebladeECS
class HashingPerformanceTests: XCTestCase {
func testMeasureCombineHash() {
let a: Set<Int> = Set<Int>([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812])
let b: Set<Int> = Set<Int>([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234])
let c: Set<Int> = Set<Int>([3_410_346_899_765, 90_000_002, 12_212_321, 71, 6_123_345_676_543])
let input: ContiguousArray<Int> = ContiguousArray<Int>(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<Int> = Set<Int>([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812])
let b: Set<Int> = Set<Int>([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234])
let c: Set<Int> = Set<Int>([3_410_346_899_765, 90_000_002, 12_212_321, 71, 6_123_345_676_543])
let input = Set<Set<Int>>(arrayLiteral: a, b, c)
measure {
for _ in 0..<1_000_000 {
let hash: Int = input.hashValue
_ = hash
}
}
}
}

View File

@ -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)

View File

@ -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<Position, Velocity>
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<Position, Velocity>
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<Position>
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
}
}
}

View File

@ -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..<number {
let id = Position.identifier
_ = id
}
}
}
func testMeasureComponentIdentifier() {
let number: Int = 10_000
let pos = Position(x: 1, y: 2)
measure {
for _ in 0..<number {
let id = pos.identifier
_ = id
}
}
}
}

View File

@ -10,16 +10,6 @@ import XCTest
class EntityTests: 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 testEntityIdentifierAndIndex() {
let min: EntityIndex = EntityIdentifier(EntityIdentifier.min).index

View File

@ -22,6 +22,12 @@ class FamilyTests: XCTestCase {
super.tearDown()
}
func createDefaultEntity(name: String?) {
let e = nexus.create(entity: name)
e.assign(Position(x: 1, y: 2))
e.assign(Color())
}
func testFamilyCreation() {
let family = nexus.family(requires: Position.self,
@ -53,47 +59,30 @@ class FamilyTests: XCTestCase {
func testFamilyAbandoned() {
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 0)
_ = nexus.family(requires: Position.self)
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
let entity = nexus.create(entity: "eimer")
entity.assign(Position(x: 1, y: 1))
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
entity.remove(Position.self)
// FIXME: the family trait should vanish when no entity with revlevant component is present anymore
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
nexus.destroy(entity: entity)
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
}
func testFamilyLateMember() {
let eEarly = nexus.create(entity: "eary").assign(Position(x: 1, y: 2))
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 0)
let family = nexus.family(requires: Position.self)
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
let eLate = nexus.create(entity: "late").assign(Position(x: 1, y: 2))
XCTAssertTrue(family.isMember(eEarly))
XCTAssertTrue(family.isMember(eLate))
}
func testFamilyExchange() {
let number: Int = 10
for i in 0..<number {
@ -153,12 +142,6 @@ class FamilyTests: XCTestCase {
}
}
func createDefaultEntity(name: String?) {
let e = nexus.create(entity: name)
e.assign(Position(x: 1, y: 2))
e.assign(Color())
}
func testFamilyBulkDestroy() {
let count = 10_000

View File

@ -54,23 +54,4 @@ class FamilyTraitsTests: XCTestCase {
}
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)
}
}
}
}

View File

@ -9,78 +9,47 @@
import XCTest
class HashingTests: XCTestCase {
func testCollisionsInCritialRange() {
var hashSet: Set<Int> = Set<Int>()
var range: CountableRange<EntityIdentifier> = 0 ..< 1_000_000
let maxComponents: Int = 1000
let components: [Int] = (0..<maxComponents).map { _ in makeComponent() }
var index: Int = 0
while let eId: EntityIdentifier = range.popLast() {
let entityId: EntityIdentifier = eId
let c = (index % maxComponents)
index += 1
let cH: ComponentTypeHash = components[c]
let h: Int = EntityComponentHash.compose(entityId: entityId, componentTypeHash: cH)
let (collisionFree, _) = hashSet.insert(h)
XCTAssert(collisionFree)
XCTAssert(EntityComponentHash.decompose(h, with: cH) == entityId)
XCTAssert(EntityComponentHash.decompose(h, with: entityId) == cH)
}
}
func testMeasureCombineHash() {
let a: Set<Int> = Set<Int>([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812])
let b: Set<Int> = Set<Int>([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234])
let c: Set<Int> = Set<Int>([3_410_346_899_765, 90_000_002, 12_212_321, 71, 6_123_345_676_543])
let input: ContiguousArray<Int> = ContiguousArray<Int>(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<Int> = Set<Int>([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812])
let b: Set<Int> = Set<Int>([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234])
let c: Set<Int> = Set<Int>([3_410_346_899_765, 90_000_002, 12_212_321, 71, 6_123_345_676_543])
let input = Set<Set<Int>>(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<Int> = Set<Int>()
var range: CountableRange<EntityIdentifier> = 0 ..< 1_000_000
let maxComponents: Int = 1000
let components: [Int] = (0..<maxComponents).map { _ in makeComponent() }
var index: Int = 0
while let eId: EntityIdentifier = range.popLast() {
let entityId: EntityIdentifier = eId
let c = (index % maxComponents)
index += 1
let cH: ComponentTypeHash = components[c]
let h: Int = EntityComponentHash.compose(entityId: entityId, componentTypeHash: cH)
let (collisionFree, _) = hashSet.insert(h)
XCTAssert(collisionFree)
XCTAssert(EntityComponentHash.decompose(h, with: cH) == entityId)
XCTAssert(EntityComponentHash.decompose(h, with: entityId) == cH)
}
}
}
// MARK: - helper
extension HashingTests {
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
}
}

View File

@ -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<Position>
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
}
}
}