diff --git a/Sources/FirebladeECS/Family.swift b/Sources/FirebladeECS/Family.swift index 1959da4..a64dfd2 100644 --- a/Sources/FirebladeECS/Family.swift +++ b/Sources/FirebladeECS/Family.swift @@ -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 { diff --git a/Sources/FirebladeECS/Nexus+Family.swift b/Sources/FirebladeECS/Nexus+Family.swift index a75e760..4d8d594 100644 --- a/Sources/FirebladeECS/Nexus+Family.swift +++ b/Sources/FirebladeECS/Nexus+Family.swift @@ -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) } } diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index 0dde283..c47e112 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -44,17 +44,15 @@ public class Nexus: Equatable { /// - Values: entity ids that are currently not used var freeEntities: ContiguousArray - var familiesByTraitHash: [FamilyTraitSetHash: Family] - var familyMembersByTraitHash: [FamilyTraitSetHash: UniformEntityIdentifiers] + //var familiesByTraitHash: [FamilyTraitSetHash: Family] + var familyMembersByTraits: [FamilyTraitSet: UniformEntityIdentifiers] public init() { entityStorage = Entities() componentsByType = [:] componentIdsByEntity = [:] freeEntities = ContiguousArray() - 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 } diff --git a/Tests/FirebladeECSTests/FamilyPerformanceTests.swift b/Tests/FirebladeECSTests/FamilyPerformanceTests.swift new file mode 100644 index 0000000..aa0b818 --- /dev/null +++ b/Tests/FirebladeECSTests/FamilyPerformanceTests.swift @@ -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..