Refactor family management
This commit is contained in:
parent
7021e6eb30
commit
532741b126
|
|
@ -12,28 +12,25 @@ public final class Family: Equatable {
|
|||
// members of this Family must conform to these traits
|
||||
public let traits: FamilyTraitSet
|
||||
|
||||
// TODO: add family configuration feature
|
||||
// TODO: implemenet
|
||||
// a) define sort order of entities
|
||||
// b) define read/write access
|
||||
// c) set size and storage constraints
|
||||
// d) conform to collection
|
||||
// e) consider family to be a struct
|
||||
|
||||
// TODO: family unions
|
||||
// a) iterate family A and family B in pairs - i.e. zip
|
||||
// b) pair-wise comparison inside families or between families
|
||||
// f) iterate family A and family B in pairs - i.e. zip
|
||||
// g) pair-wise comparison inside families or between families
|
||||
|
||||
init(_ nexus: Nexus, traits: FamilyTraitSet) {
|
||||
self.nexus = nexus
|
||||
self.traits = traits
|
||||
defer {
|
||||
self.nexus?.onFamilyInit(family: self)
|
||||
self.nexus?.onFamilyInit(traits: self.traits)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
let hash: FamilyTraitSetHash = traits.hashValue
|
||||
nexus?.onFamilyDeinit(traitHash: hash)
|
||||
nexus?.onFamilyDeinit(traits: traits)
|
||||
}
|
||||
|
||||
public final var memberIds: UniformEntityIdentifiers {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,7 @@ public extension Nexus {
|
|||
}
|
||||
|
||||
func family(with traits: FamilyTraitSet) -> Family {
|
||||
guard let family: Family = get(family: traits) else {
|
||||
return create(family: traits)
|
||||
}
|
||||
return family
|
||||
return create(family: traits)
|
||||
}
|
||||
|
||||
func canBecomeMember(_ entity: Entity, in family: Family) -> Bool {
|
||||
|
|
@ -37,21 +34,24 @@ public extension Nexus {
|
|||
}
|
||||
|
||||
func members(of family: Family) -> UniformEntityIdentifiers? {
|
||||
let traitHash: FamilyTraitSetHash = family.traits.hashValue
|
||||
return members(of: traitHash)
|
||||
let traits: FamilyTraitSet = family.traits
|
||||
return members(of: traits)
|
||||
}
|
||||
|
||||
func members(of traitHash: FamilyTraitSetHash) -> UniformEntityIdentifiers? {
|
||||
return familyMembersByTraitHash[traitHash]
|
||||
func members(of traits: FamilyTraitSet) -> UniformEntityIdentifiers? {
|
||||
return familyMembersByTraits[traits]
|
||||
}
|
||||
|
||||
func isMember(_ entity: Entity, in family: Family) -> Bool {
|
||||
return isMember(entity.identifier, in: family)
|
||||
}
|
||||
|
||||
func isMember(_ entityId: EntityIdentifier, in family: Family) -> Bool {
|
||||
let traitHash: FamilyTraitSetHash = family.traits.hashValue
|
||||
guard let members: UniformEntityIdentifiers = members(of: traitHash) else {
|
||||
func isMember(_ entityId: EntityIdentifier, in family: Family) -> Bool {
|
||||
return isMember(entityId, in: family.traits)
|
||||
}
|
||||
|
||||
func isMember(_ entityId: EntityIdentifier, in traits: FamilyTraitSet) -> Bool {
|
||||
guard let members: UniformEntityIdentifiers = members(of: traits) else {
|
||||
return false
|
||||
}
|
||||
return members.has(entityId.index)
|
||||
|
|
@ -63,41 +63,41 @@ public extension Nexus {
|
|||
extension Nexus {
|
||||
|
||||
/// will be called on family init defer
|
||||
func onFamilyInit(family: Family) {
|
||||
func onFamilyInit(traits: FamilyTraitSet) {
|
||||
createTraitsIfNeccessary(traits: traits)
|
||||
|
||||
// FIXME: this is costly for many entities
|
||||
for entity: Entity in entityStorage {
|
||||
update(membership: family, for: entity.identifier)
|
||||
update(membership: traits, for: entity.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
func onFamilyDeinit(traitHash: FamilyTraitSetHash) {
|
||||
guard let members: UniformEntityIdentifiers = members(of: traitHash) else {
|
||||
func onFamilyDeinit(traits: FamilyTraitSet) {
|
||||
guard let members: UniformEntityIdentifiers = members(of: traits) else {
|
||||
return
|
||||
}
|
||||
|
||||
for member: EntityIdentifier in members {
|
||||
remove(from: traitHash, entityId: member, entityIdx: member.index)
|
||||
remove(from: traits, entityId: member, entityIdx: member.index)
|
||||
}
|
||||
}
|
||||
|
||||
func update(familyMembership entityId: EntityIdentifier) {
|
||||
// FIXME: iterating all families is costly for many families
|
||||
for family: Family in familiesByTraitHash.values {
|
||||
update(membership: family, for: entityId)
|
||||
for (familyTraits, _) in familyMembersByTraits {
|
||||
update(membership: familyTraits, for: entityId)
|
||||
}
|
||||
}
|
||||
|
||||
func update(membership family: Family, for entityId: EntityIdentifier) {
|
||||
func update(membership traits: FamilyTraitSet, for entityId: EntityIdentifier) {
|
||||
let entityIdx: EntityIndex = entityId.index
|
||||
let traits: FamilyTraitSet = family.traits
|
||||
let traitHash: FamilyTraitSetHash = traits.hashValue
|
||||
guard let componentIds: SparseComponentIdentifierSet = componentIdsByEntity[entityIdx] else {
|
||||
return
|
||||
}
|
||||
|
||||
let isMember: Bool = self.isMember(entityId, in: family)
|
||||
let isMember: Bool = self.isMember(entityId, in: traits)
|
||||
if !has(entity: entityId) && isMember {
|
||||
remove(from: traitHash, entityId: entityId, entityIdx: entityIdx)
|
||||
remove(from: traits, entityId: entityId, entityIdx: entityIdx)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -105,10 +105,10 @@ extension Nexus {
|
|||
let isMatch: Bool = traits.isMatch(components: componentsSet)
|
||||
switch (isMatch, isMember) {
|
||||
case (true, false):
|
||||
add(to: traitHash, entityId: entityId, entityIdx: entityIdx)
|
||||
add(to: traits, entityId: entityId, entityIdx: entityIdx)
|
||||
notify(FamilyMemberAdded(member: entityId, toFamily: traits))
|
||||
case (false, true):
|
||||
remove(from: traitHash, entityId: entityId, entityIdx: entityIdx)
|
||||
remove(from: traits, entityId: entityId, entityIdx: entityIdx)
|
||||
notify(FamilyMemberRemoved(member: entityId, from: traits))
|
||||
default:
|
||||
break
|
||||
|
|
@ -121,30 +121,31 @@ extension Nexus {
|
|||
private extension Nexus {
|
||||
|
||||
func get(family traits: FamilyTraitSet) -> Family? {
|
||||
let traitHash: FamilyTraitSetHash = traits.hashValue
|
||||
return familiesByTraitHash[traitHash]
|
||||
return create(family: traits)
|
||||
}
|
||||
|
||||
func create(family traits: FamilyTraitSet) -> Family {
|
||||
let traitHash: FamilyTraitSetHash = traits.hashValue
|
||||
let family: Family = Family(self, traits: traits)
|
||||
let replaced: Family? = familiesByTraitHash.updateValue(family, forKey: traitHash)
|
||||
assert(replaced == nil, "Family with exact trait hash already exists: \(traitHash)")
|
||||
notify(FamilyCreated(family: traits))
|
||||
return family
|
||||
}
|
||||
|
||||
func createTraitsIfNeccessary(traits: FamilyTraitSet) {
|
||||
guard familyMembersByTraits[traits] == nil else {
|
||||
return
|
||||
}
|
||||
familyMembersByTraits[traits] = UniformEntityIdentifiers()
|
||||
}
|
||||
|
||||
func calculateTraitEntityIdHash(traitHash: FamilyTraitSetHash, entityIdx: EntityIndex) -> TraitEntityIdHash {
|
||||
return hash(combine: traitHash, entityIdx)
|
||||
}
|
||||
func add(to traitHash: FamilyTraitSetHash, entityId: EntityIdentifier, entityIdx: EntityIndex) {
|
||||
if familyMembersByTraitHash[traitHash] == nil {
|
||||
familyMembersByTraitHash[traitHash] = UniformEntityIdentifiers()
|
||||
}
|
||||
familyMembersByTraitHash[traitHash]?.add(entityId, at: entityIdx)
|
||||
|
||||
func add(to traits: FamilyTraitSet, entityId: EntityIdentifier, entityIdx: EntityIndex) {
|
||||
createTraitsIfNeccessary(traits: traits)
|
||||
familyMembersByTraits[traits]?.add(entityId, at: entityIdx)
|
||||
}
|
||||
|
||||
func remove(from traitHash: FamilyTraitSetHash, entityId: EntityIdentifier, entityIdx: EntityIndex) {
|
||||
familyMembersByTraitHash[traitHash]?.remove(at: entityIdx)
|
||||
func remove(from traits: FamilyTraitSet, entityId: EntityIdentifier, entityIdx: EntityIndex) {
|
||||
familyMembersByTraits[traits]?.remove(at: entityIdx)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,17 +44,15 @@ public class Nexus: Equatable {
|
|||
/// - Values: entity ids that are currently not used
|
||||
var freeEntities: ContiguousArray<EntityIdentifier>
|
||||
|
||||
var familiesByTraitHash: [FamilyTraitSetHash: Family]
|
||||
var familyMembersByTraitHash: [FamilyTraitSetHash: UniformEntityIdentifiers]
|
||||
//var familiesByTraitHash: [FamilyTraitSetHash: Family]
|
||||
var familyMembersByTraits: [FamilyTraitSet: UniformEntityIdentifiers]
|
||||
|
||||
public init() {
|
||||
entityStorage = Entities()
|
||||
componentsByType = [:]
|
||||
componentIdsByEntity = [:]
|
||||
freeEntities = ContiguousArray<EntityIdentifier>()
|
||||
familiesByTraitHash = [:]
|
||||
familyMembersByTraitHash = [:]
|
||||
|
||||
familyMembersByTraits = [:]
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -70,13 +68,11 @@ public class Nexus: Equatable {
|
|||
assert(componentsByType.values.reduce(0) { $0 + $1.count } == 0)
|
||||
assert(componentIdsByEntity.values.reduce(0) { $0 + $1.count } == 0)
|
||||
assert(freeEntities.isEmpty)
|
||||
assert(familiesByTraitHash.values.reduce(0) { $0 + $1.count } == 0)
|
||||
assert(familyMembersByTraitHash.values.reduce(0) { $0 + $1.count } == 0)
|
||||
assert(familyMembersByTraits.values.reduce(0) { $0 + $1.count } == 0)
|
||||
|
||||
componentsByType.removeAll()
|
||||
componentIdsByEntity.removeAll()
|
||||
familiesByTraitHash.removeAll()
|
||||
familyMembersByTraitHash.removeAll()
|
||||
familyMembersByTraits.removeAll()
|
||||
}
|
||||
|
||||
// MARK: Equatable
|
||||
|
|
@ -84,8 +80,7 @@ public class Nexus: Equatable {
|
|||
return lhs.entityStorage == rhs.entityStorage &&
|
||||
lhs.componentIdsByEntity == rhs.componentIdsByEntity &&
|
||||
lhs.freeEntities == rhs.freeEntities &&
|
||||
lhs.familiesByTraitHash == rhs.familiesByTraitHash &&
|
||||
lhs.familyMembersByTraitHash == rhs.familyMembersByTraitHash
|
||||
lhs.familyMembersByTraits == rhs.familyMembersByTraits
|
||||
// TODO: components are not equatable yet
|
||||
//lhs.componentsByType == rhs.componentsByType
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// FamilyPerformanceTests.swift
|
||||
// FirebladeECSTests
|
||||
//
|
||||
// Created by Christian Treffs on 09.05.18.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import FirebladeECS
|
||||
|
||||
class FamilyPerformanceTests: XCTestCase {
|
||||
|
||||
var nexus: Nexus!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
nexus = Nexus()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
nexus = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
|
||||
func testMeasureIterateMembers() {
|
||||
|
||||
let number: Int = 10_000
|
||||
|
||||
for i in 0..<number {
|
||||
nexus.create(entity: "\(i)").assign(Position(x: 1 + i, y: 2 + i), Name(name: "myName\(i)"), Velocity(a: 3.14), EmptyComponent())
|
||||
}
|
||||
|
||||
let family = nexus.family(requiresAll: [Position.self, Velocity.self],
|
||||
excludesAll: [Party.self],
|
||||
needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
XCTAssertEqual(family.count, number)
|
||||
XCTAssertEqual(nexus.numEntities, number)
|
||||
|
||||
measure {
|
||||
family.iterate { (entityId: EntityIdentifier) in
|
||||
_ = entityId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testMeasureFamilyIterationOne() {
|
||||
|
||||
let number: Int = 10_000
|
||||
|
||||
for i in 0..<number {
|
||||
nexus.create(entity: "\(i)").assign(Position(x: 1 + i, y: 2 + i), Name(name: "myName\(i)"), Velocity(a: 3.14), EmptyComponent())
|
||||
}
|
||||
|
||||
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
XCTAssert(family.count == number)
|
||||
XCTAssert(nexus.numEntities == number)
|
||||
|
||||
measure {
|
||||
family.iterate { (velocity: Velocity!) in
|
||||
_ = velocity
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func testMeasureFamilyIterationThree() {
|
||||
|
||||
let number: Int = 10_000
|
||||
|
||||
for i in 0..<number {
|
||||
nexus.create(entity: "\(i)").assign(Position(x: 1 + i, y: 2 + i), Name(name: "myName\(i)"), Velocity(a: 3.14), EmptyComponent())
|
||||
}
|
||||
|
||||
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
XCTAssert(family.count == number)
|
||||
XCTAssert(nexus.numEntities == number)
|
||||
|
||||
measure {
|
||||
family.iterate { (entity: Entity!, position: Position!, velocity: Velocity!, name: Name?) in
|
||||
position.x += entity.identifier.index
|
||||
_ = velocity
|
||||
_ = name
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -30,8 +30,8 @@ class FamilyTests: XCTestCase {
|
|||
|
||||
XCTAssertEqual(family.nexus, self.nexus)
|
||||
XCTAssertTrue(family.nexus === self.nexus)
|
||||
XCTAssertEqual(nexus.familiesByTraitHash.count, 1)
|
||||
XCTAssertEqual(nexus.familiesByTraitHash.values.first!, family)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.values.count, 1)
|
||||
|
||||
let traits = FamilyTraitSet(requiresAll: [Position.self], excludesAll: [Name.self], needsAtLeastOne: [Velocity.self])
|
||||
XCTAssertEqual(family.traits, traits)
|
||||
|
|
@ -47,12 +47,55 @@ class FamilyTests: XCTestCase {
|
|||
excludesAll: [Name.self],
|
||||
needsAtLeastOne: [Velocity.self])
|
||||
|
||||
XCTAssertEqual(nexus.familiesByTraitHash.count, 1)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.values.count, 1)
|
||||
|
||||
XCTAssertEqual(familyA, familyB)
|
||||
XCTAssertTrue(familyA === familyB)
|
||||
}
|
||||
|
||||
func testFamilyAbandoned() {
|
||||
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 0)
|
||||
|
||||
nexus.family(requiresAll: [Position.self],
|
||||
excludesAll: [])
|
||||
|
||||
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(requiresAll: [Position.self],
|
||||
excludesAll: [])
|
||||
|
||||
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() {
|
||||
|
||||
|
|
@ -68,162 +111,50 @@ class FamilyTests: XCTestCase {
|
|||
let familyB = nexus.family(requiresAll: [Velocity.self],
|
||||
excludesAll: [Position.self])
|
||||
|
||||
var countA: Int = 0
|
||||
|
||||
XCTAssertEqual(familyA.count, 10)
|
||||
XCTAssertEqual(familyB.count, 0)
|
||||
|
||||
familyA.iterate { (entity: Entity!) in
|
||||
entity.assign(Velocity(a: 3.14))
|
||||
entity.remove(Position.self)
|
||||
countA += 1
|
||||
}
|
||||
|
||||
XCTAssert(countA == number)
|
||||
|
||||
var countB: Int = 0
|
||||
XCTAssertEqual(familyA.count, 0)
|
||||
XCTAssertEqual(familyB.count, 10)
|
||||
|
||||
familyB.iterate { (entity: Entity!, velocity: Velocity!) in
|
||||
entity.assign(Position(x: 1, y: 2))
|
||||
entity.remove(velocity)
|
||||
countB += 1
|
||||
}
|
||||
|
||||
XCTAssert(countB == number)
|
||||
|
||||
XCTAssertEqual(familyA.count, 10)
|
||||
XCTAssertEqual(familyB.count, 0)
|
||||
|
||||
}
|
||||
|
||||
|
||||
func testIterationSimple() {
|
||||
|
||||
|
||||
func testFamilyMemberBasicIteration() {
|
||||
|
||||
for i in 0..<1000 {
|
||||
nexus.create(entity: "\(i)").assign(Position(x: i + 1, y: i + 2))
|
||||
nexus.create(entity: "\(i)").assign(Velocity(a: Float(i)))
|
||||
}
|
||||
|
||||
let familyA = nexus.family(requiresAll: [Position.self], excludesAll: [Velocity.self])
|
||||
_ = nexus.family(requiresAll: [Velocity.self], excludesAll: [Position.self])
|
||||
let familyA = nexus.family(requiresAll: [Position.self],
|
||||
excludesAll: [Velocity.self])
|
||||
|
||||
familyA.iterate { (pos: Position!, vel: Velocity!) in
|
||||
_ = pos
|
||||
_ = vel
|
||||
let familyB = nexus.family(requiresAll: [Velocity.self],
|
||||
excludesAll: [Position.self])
|
||||
|
||||
familyA.iterate { (pos: Position?, vel: Velocity?) in
|
||||
XCTAssertNotNil(pos)
|
||||
XCTAssertNil(vel)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - family performance
|
||||
|
||||
|
||||
func testMeasureIterateMembers() {
|
||||
|
||||
let number: Int = 10_000
|
||||
|
||||
for i in 0..<number {
|
||||
nexus.create(entity: "\(i)").assign(Position(x: 1 + i, y: 2 + i), Name(name: "myName\(i)"), Velocity(a: 3.14), EmptyComponent())
|
||||
}
|
||||
|
||||
let family = nexus.family(requiresAll: [Position.self, Velocity.self],
|
||||
excludesAll: [Party.self],
|
||||
needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
XCTAssertEqual(family.count, number)
|
||||
XCTAssertEqual(nexus.numEntities, number)
|
||||
|
||||
measure {
|
||||
family.iterate { (entityId: EntityIdentifier) in
|
||||
_ = entityId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testMeasureFamilyIterationOne() {
|
||||
|
||||
let number: Int = 10_000
|
||||
|
||||
for i in 0..<number {
|
||||
nexus.create(entity: "\(i)").assign(Position(x: 1 + i, y: 2 + i), Name(name: "myName\(i)"), Velocity(a: 3.14), EmptyComponent())
|
||||
}
|
||||
|
||||
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
XCTAssert(family.count == number)
|
||||
XCTAssert(nexus.numEntities == number)
|
||||
|
||||
measure {
|
||||
family.iterate { (velocity: Velocity!) in
|
||||
_ = velocity
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func testMeasureFamilyIterationThree() {
|
||||
|
||||
let number: Int = 10_000
|
||||
|
||||
for i in 0..<number {
|
||||
nexus.create(entity: "\(i)").assign(Position(x: 1 + i, y: 2 + i), Name(name: "myName\(i)"), Velocity(a: 3.14), EmptyComponent())
|
||||
}
|
||||
|
||||
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
XCTAssert(family.count == number)
|
||||
XCTAssert(nexus.numEntities == number)
|
||||
|
||||
measure {
|
||||
family.iterate { (entity: Entity!, position: Position!, velocity: Velocity!, name: Name?) in
|
||||
position.x += entity.identifier.index
|
||||
_ = velocity
|
||||
_ = name
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - family traits
|
||||
|
||||
func testTraitCommutativity() {
|
||||
|
||||
let t1 = FamilyTraitSet(requiresAll: [Position.self, Velocity.self], excludesAll: [Name.self], needsAtLeastOne: [])
|
||||
let t2 = FamilyTraitSet(requiresAll: [Velocity.self, Position.self], excludesAll: [Name.self], needsAtLeastOne: [])
|
||||
|
||||
XCTAssertEqual(t1, t2)
|
||||
XCTAssertEqual(t1.hashValue, t2.hashValue)
|
||||
|
||||
}
|
||||
|
||||
func testTraitMatching() {
|
||||
|
||||
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 noMatch = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Name.self])
|
||||
let isMatch = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
XCTAssertFalse(noMatch.canBecomeMember(a))
|
||||
XCTAssertTrue(isMatch.canBecomeMember(a))
|
||||
|
||||
}
|
||||
|
||||
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],
|
||||
needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
measure {
|
||||
for _ in 0..<10_000 {
|
||||
let success = isMatch.canBecomeMember(a)
|
||||
XCTAssert(success)
|
||||
}
|
||||
familyB.iterate { (pos: Position?, vel: Velocity?) in
|
||||
XCTAssertNil(pos)
|
||||
XCTAssertNotNil(vel)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
//
|
||||
// FamilyTraitsTests.swift
|
||||
// FirebladeECSTests
|
||||
//
|
||||
// Created by Christian Treffs on 09.05.18.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import FirebladeECS
|
||||
|
||||
class FamilyTraitsTests: XCTestCase {
|
||||
|
||||
var nexus: Nexus!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
nexus = Nexus()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
nexus = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
|
||||
func testTraitCommutativity() {
|
||||
|
||||
let t1 = FamilyTraitSet(requiresAll: [Position.self, Velocity.self],
|
||||
excludesAll: [Name.self],
|
||||
needsAtLeastOne: [])
|
||||
let t2 = FamilyTraitSet(requiresAll: [Velocity.self, Position.self],
|
||||
excludesAll: [Name.self],
|
||||
needsAtLeastOne: [])
|
||||
|
||||
XCTAssertEqual(t1, t2)
|
||||
XCTAssertEqual(t1.hashValue, t2.hashValue)
|
||||
|
||||
}
|
||||
|
||||
func testTraitMatching() {
|
||||
|
||||
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 noMatch = nexus.family(requiresAll: [Position.self, Velocity.self],
|
||||
excludesAll: [Name.self])
|
||||
|
||||
let isMatch = nexus.family(requiresAll: [Position.self, Velocity.self],
|
||||
excludesAll: [],
|
||||
needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
XCTAssertFalse(noMatch.canBecomeMember(a))
|
||||
XCTAssertTrue(isMatch.canBecomeMember(a))
|
||||
|
||||
}
|
||||
|
||||
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],
|
||||
needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||
|
||||
measure {
|
||||
for _ in 0..<10_000 {
|
||||
let success = isMatch.canBecomeMember(a)
|
||||
XCTAssert(success)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue