Fix bug in onFamilyDeinit
This commit is contained in:
parent
d274b3e719
commit
e0fd2bbeb1
|
|
@ -5,13 +5,14 @@
|
|||
// Created by Christian Treffs on 09.10.17.
|
||||
//
|
||||
|
||||
public struct FamilyTraitSet {
|
||||
public struct FamilyTraitSet: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
|
||||
public let requiresAll: ComponentSet
|
||||
public let excludesAll: ComponentSet
|
||||
public let needsAtLeastOne: ComponentSet
|
||||
private let setHash: Int
|
||||
private let isEmptyAny: Bool
|
||||
private let stringRespresentation: String
|
||||
|
||||
public init(requiresAll: [Component.Type], excludesAll: [Component.Type], needsAtLeastOne: [Component.Type] = []) {
|
||||
|
||||
|
|
@ -29,6 +30,12 @@ public struct FamilyTraitSet {
|
|||
self.requiresAll = all
|
||||
self.needsAtLeastOne = one
|
||||
self.excludesAll = none
|
||||
|
||||
let allString: String = requiresAll.map { "\($0)" }.joined(separator: ",")
|
||||
let excludedString: String = excludesAll.map { "\($0)" }.joined(separator: ",")
|
||||
let oneString: String = needsAtLeastOne.map { "\($0)" }.joined(separator: ",")
|
||||
|
||||
stringRespresentation = "[all:\(allString) excluded:\(excludedString) one:\(oneString)]"
|
||||
}
|
||||
|
||||
// MARK: - match
|
||||
|
|
@ -63,6 +70,14 @@ public struct FamilyTraitSet {
|
|||
return !requiresAll.isEmpty || !atLeastOne.isEmpty
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return stringRespresentation
|
||||
}
|
||||
|
||||
public var debugDescription: String {
|
||||
return stringRespresentation
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ extension Nexus {
|
|||
|
||||
@discardableResult
|
||||
public func remove(component componentId: ComponentIdentifier, from entityId: EntityIdentifier) -> Bool {
|
||||
|
||||
let entityIdx: EntityIndex = entityId.index
|
||||
//let hash: EntityComponentHash = componentId.hashValue(using: entityIdx)
|
||||
|
||||
// delete component instance
|
||||
componentsByType[componentId]?.remove(at: entityIdx)
|
||||
|
|
@ -99,6 +99,7 @@ extension Nexus {
|
|||
|
||||
@discardableResult
|
||||
public func clear(componentes entityId: EntityIdentifier) -> Bool {
|
||||
|
||||
guard let allComponents: SparseComponentIdentifierSet = get(components: entityId) else {
|
||||
report("clearing components form entity \(entityId) with no components")
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -57,26 +57,6 @@ public extension Nexus {
|
|||
// MARK: - internal extensions
|
||||
extension Nexus {
|
||||
|
||||
/// will be called on family init defer
|
||||
func onFamilyInit(traits: FamilyTraitSet) {
|
||||
createTraitsIfNeccessary(traits: traits)
|
||||
|
||||
// FIXME: this is costly for many entities
|
||||
for entity: Entity in entityStorage {
|
||||
update(membership: traits, for: entity.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
func onFamilyDeinit(traits: FamilyTraitSet) {
|
||||
guard let members: UniformEntityIdentifiers = members(of: traits) else {
|
||||
return
|
||||
}
|
||||
|
||||
for member: EntityIdentifier in members {
|
||||
remove(from: traits, entityId: member, entityIdx: member.index)
|
||||
}
|
||||
}
|
||||
|
||||
func update(familyMembership entityId: EntityIdentifier) {
|
||||
// FIXME: iterating all families is costly for many families
|
||||
for (familyTraits, _) in familyMembersByTraits {
|
||||
|
|
@ -84,16 +64,25 @@ extension Nexus {
|
|||
}
|
||||
}
|
||||
|
||||
func update(membership traits: FamilyTraitSet, for entityId: EntityIdentifier) {
|
||||
enum UpdateState {
|
||||
case noComponents(id: EntityIdentifier, traits: FamilyTraitSet)
|
||||
case added(id: EntityIdentifier, traits: FamilyTraitSet)
|
||||
case removedDeleted(id: EntityIdentifier, traits: FamilyTraitSet)
|
||||
case removed(id: EntityIdentifier, traits: FamilyTraitSet)
|
||||
case unchanged(id: EntityIdentifier, traits: FamilyTraitSet)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func update(membership traits: FamilyTraitSet, for entityId: EntityIdentifier) -> UpdateState {
|
||||
let entityIdx: EntityIndex = entityId.index
|
||||
guard let componentIds: SparseComponentIdentifierSet = componentIdsByEntity[entityIdx] else {
|
||||
return
|
||||
return .noComponents(id: entityId, traits: traits)
|
||||
}
|
||||
|
||||
let isMember: Bool = self.isMember(entityId, in: traits)
|
||||
if !has(entity: entityId) && isMember {
|
||||
remove(from: traits, entityId: entityId, entityIdx: entityIdx)
|
||||
return
|
||||
return .removedDeleted(id: entityId, traits: traits)
|
||||
}
|
||||
|
||||
let componentsSet: ComponentSet = ComponentSet(componentIds)
|
||||
|
|
@ -102,14 +91,33 @@ extension Nexus {
|
|||
case (true, false):
|
||||
add(to: traits, entityId: entityId, entityIdx: entityIdx)
|
||||
notify(FamilyMemberAdded(member: entityId, toFamily: traits))
|
||||
return .added(id: entityId, traits: traits)
|
||||
case (false, true):
|
||||
remove(from: traits, entityId: entityId, entityIdx: entityIdx)
|
||||
notify(FamilyMemberRemoved(member: entityId, from: traits))
|
||||
return .removed(id: entityId, traits: traits)
|
||||
default:
|
||||
break
|
||||
return .unchanged(id: entityId, traits: traits)
|
||||
}
|
||||
}
|
||||
|
||||
/// will be called on family init defer
|
||||
func onFamilyInit(traits: FamilyTraitSet) {
|
||||
|
||||
if familyMembersByTraits[traits] == nil {
|
||||
familyMembersByTraits[traits] = UniformEntityIdentifiers()
|
||||
}
|
||||
|
||||
// FIXME: this is costly for many entities
|
||||
for entity: Entity in entityStorage {
|
||||
update(membership: traits, for: entity.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
func onFamilyDeinit(traits: FamilyTraitSet) {
|
||||
// nothing todo here
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - fileprivate extensions
|
||||
|
|
@ -124,19 +132,15 @@ private extension Nexus {
|
|||
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 traits: FamilyTraitSet, entityId: EntityIdentifier, entityIdx: EntityIndex) {
|
||||
createTraitsIfNeccessary(traits: traits)
|
||||
if familyMembersByTraits[traits] == nil {
|
||||
familyMembersByTraits[traits] = UniformEntityIdentifiers()
|
||||
}
|
||||
|
||||
familyMembersByTraits[traits]?.insert(entityId, at: entityIdx)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ public class UnorderedSparseSet<Element>: Sequence {
|
|||
@discardableResult
|
||||
public func remove(at key: Key) -> Entry? {
|
||||
guard let (denseIndex, _) = find(at: key) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,8 +57,7 @@ class FamilyTests: XCTestCase {
|
|||
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 0)
|
||||
|
||||
nexus.family(requiresAll: [Position.self],
|
||||
excludesAll: [])
|
||||
_ = nexus.family(requiresAll: [Position.self], excludesAll: [])
|
||||
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,15 @@ class SparseSetTests: XCTestCase {
|
|||
XCTAssertEqual(set.get(at: 6)?.x, 6)
|
||||
XCTAssertEqual(set.get(at: 7)?.x, nil)
|
||||
|
||||
XCTAssertTrue(set.contains(0))
|
||||
XCTAssertTrue(set.contains(1))
|
||||
XCTAssertTrue(set.contains(2))
|
||||
XCTAssertTrue(set.contains(3))
|
||||
XCTAssertTrue(set.contains(4))
|
||||
XCTAssertTrue(set.contains(5))
|
||||
XCTAssertTrue(set.contains(6))
|
||||
XCTAssertFalse(set.contains(7))
|
||||
|
||||
XCTAssertEqual(set.sparse[0], 0)
|
||||
XCTAssertEqual(set.sparse[1], 1)
|
||||
XCTAssertEqual(set.sparse[2], 2)
|
||||
|
|
@ -129,6 +138,15 @@ class SparseSetTests: XCTestCase {
|
|||
XCTAssertEqual(set.get(at: 6)?.x, 6)
|
||||
XCTAssertEqual(set.get(at: 7)?.x, nil)
|
||||
|
||||
XCTAssertTrue(set.contains(0))
|
||||
XCTAssertTrue(set.contains(1))
|
||||
XCTAssertTrue(set.contains(2))
|
||||
XCTAssertFalse(set.contains(3))
|
||||
XCTAssertTrue(set.contains(4))
|
||||
XCTAssertTrue(set.contains(5))
|
||||
XCTAssertTrue(set.contains(6))
|
||||
XCTAssertFalse(set.contains(7))
|
||||
|
||||
XCTAssertEqual(set.sparse[0], 0)
|
||||
XCTAssertEqual(set.sparse[1], 1)
|
||||
XCTAssertEqual(set.sparse[2], 2)
|
||||
|
|
@ -157,6 +175,15 @@ class SparseSetTests: XCTestCase {
|
|||
XCTAssertEqual(set.get(at: 6)?.x, 6)
|
||||
XCTAssertEqual(set.get(at: 7)?.x, nil)
|
||||
|
||||
XCTAssertTrue(set.contains(0))
|
||||
XCTAssertTrue(set.contains(1))
|
||||
XCTAssertFalse(set.contains(2))
|
||||
XCTAssertFalse(set.contains(3))
|
||||
XCTAssertTrue(set.contains(4))
|
||||
XCTAssertTrue(set.contains(5))
|
||||
XCTAssertTrue(set.contains(6))
|
||||
XCTAssertFalse(set.contains(7))
|
||||
|
||||
XCTAssertEqual(set.sparse[0], 0)
|
||||
XCTAssertEqual(set.sparse[1], 1)
|
||||
XCTAssertEqual(set.sparse[2], nil)
|
||||
|
|
@ -185,6 +212,15 @@ class SparseSetTests: XCTestCase {
|
|||
XCTAssertEqual(set.get(at: 6)?.x, 6)
|
||||
XCTAssertEqual(set.get(at: 7)?.x, nil)
|
||||
|
||||
XCTAssertFalse(set.contains(0))
|
||||
XCTAssertTrue(set.contains(1))
|
||||
XCTAssertFalse(set.contains(2))
|
||||
XCTAssertFalse(set.contains(3))
|
||||
XCTAssertTrue(set.contains(4))
|
||||
XCTAssertTrue(set.contains(5))
|
||||
XCTAssertTrue(set.contains(6))
|
||||
XCTAssertFalse(set.contains(7))
|
||||
|
||||
XCTAssertEqual(set.sparse[0], nil)
|
||||
XCTAssertEqual(set.sparse[1], 1)
|
||||
XCTAssertEqual(set.sparse[2], nil)
|
||||
|
|
@ -212,6 +248,15 @@ class SparseSetTests: XCTestCase {
|
|||
XCTAssertEqual(set.get(at: 6)?.x, 6)
|
||||
XCTAssertEqual(set.get(at: 7)?.x, nil)
|
||||
|
||||
XCTAssertFalse(set.contains(0))
|
||||
XCTAssertFalse(set.contains(1))
|
||||
XCTAssertFalse(set.contains(2))
|
||||
XCTAssertFalse(set.contains(3))
|
||||
XCTAssertTrue(set.contains(4))
|
||||
XCTAssertTrue(set.contains(5))
|
||||
XCTAssertTrue(set.contains(6))
|
||||
XCTAssertFalse(set.contains(7))
|
||||
|
||||
XCTAssertEqual(set.sparse[0], nil)
|
||||
XCTAssertEqual(set.sparse[1], nil)
|
||||
XCTAssertEqual(set.sparse[2], nil)
|
||||
|
|
@ -237,6 +282,15 @@ class SparseSetTests: XCTestCase {
|
|||
XCTAssertEqual(set.get(at: 6)?.x, nil)
|
||||
XCTAssertEqual(set.get(at: 7)?.x, nil)
|
||||
|
||||
XCTAssertFalse(set.contains(0))
|
||||
XCTAssertFalse(set.contains(1))
|
||||
XCTAssertFalse(set.contains(2))
|
||||
XCTAssertFalse(set.contains(3))
|
||||
XCTAssertTrue(set.contains(4))
|
||||
XCTAssertTrue(set.contains(5))
|
||||
XCTAssertFalse(set.contains(6))
|
||||
XCTAssertFalse(set.contains(7))
|
||||
|
||||
XCTAssertEqual(set.sparse[0], nil)
|
||||
XCTAssertEqual(set.sparse[1], nil)
|
||||
XCTAssertEqual(set.sparse[2], nil)
|
||||
|
|
@ -262,6 +316,15 @@ class SparseSetTests: XCTestCase {
|
|||
XCTAssertEqual(set.get(at: 6)?.x, nil)
|
||||
XCTAssertEqual(set.get(at: 7)?.x, nil)
|
||||
|
||||
XCTAssertFalse(set.contains(0))
|
||||
XCTAssertFalse(set.contains(1))
|
||||
XCTAssertFalse(set.contains(2))
|
||||
XCTAssertFalse(set.contains(3))
|
||||
XCTAssertTrue(set.contains(4))
|
||||
XCTAssertFalse(set.contains(5))
|
||||
XCTAssertFalse(set.contains(6))
|
||||
XCTAssertFalse(set.contains(7))
|
||||
|
||||
XCTAssertEqual(set.sparse[0], nil)
|
||||
XCTAssertEqual(set.sparse[1], nil)
|
||||
XCTAssertEqual(set.sparse[2], nil)
|
||||
|
|
@ -288,6 +351,15 @@ class SparseSetTests: XCTestCase {
|
|||
XCTAssertEqual(set.get(at: 6)?.x, nil)
|
||||
XCTAssertEqual(set.get(at: 7)?.x, nil)
|
||||
|
||||
XCTAssertFalse(set.contains(0))
|
||||
XCTAssertFalse(set.contains(1))
|
||||
XCTAssertFalse(set.contains(2))
|
||||
XCTAssertFalse(set.contains(3))
|
||||
XCTAssertFalse(set.contains(4))
|
||||
XCTAssertFalse(set.contains(5))
|
||||
XCTAssertFalse(set.contains(6))
|
||||
XCTAssertFalse(set.contains(7))
|
||||
|
||||
XCTAssertEqual(set.sparse[0], nil)
|
||||
XCTAssertEqual(set.sparse[1], nil)
|
||||
XCTAssertEqual(set.sparse[2], nil)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
//
|
||||
|
||||
import XCTest
|
||||
import FirebladeECS
|
||||
@testable import FirebladeECS
|
||||
|
||||
class SystemsTests: XCTestCase {
|
||||
|
||||
|
|
@ -31,57 +31,78 @@ class SystemsTests: XCTestCase {
|
|||
|
||||
func testSystemsUpdate() {
|
||||
|
||||
let num: Int = 10_000
|
||||
|
||||
colorSystem.update()
|
||||
positionSystem.update()
|
||||
|
||||
let posTraits = positionSystem.family.traits
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 0)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, 0)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, 0)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, 0)
|
||||
|
||||
batchCreateEntities(count: 10_000)
|
||||
batchCreateEntities(count: num)
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 10_000)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, 10_000)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, 10_000)
|
||||
XCTAssertEqual(nexus.numEntities, num)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, num)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, num)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
|
||||
colorSystem.update()
|
||||
positionSystem.update()
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 10_000)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, 10_000)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, 10_000)
|
||||
XCTAssertEqual(nexus.numEntities, num)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, num)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, num)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
|
||||
batchCreateEntities(count: 10_000)
|
||||
batchCreateEntities(count: num)
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 20_000)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, 20_000)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, 20_000)
|
||||
XCTAssertEqual(nexus.numEntities, num * 2)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, num * 2)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, num * 2)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
|
||||
colorSystem.update()
|
||||
positionSystem.update()
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 20_000)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, 20_000)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, 20_000)
|
||||
XCTAssertEqual(nexus.numEntities, num * 2)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, num * 2)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, num * 2)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
|
||||
batchDestroyEntities(count: 10_000)
|
||||
batchDestroyEntities(count: num)
|
||||
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||
XCTAssertEqual(nexus.freeEntities.count, num)
|
||||
XCTAssertEqual(nexus.numEntities, num)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, num)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, num)
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 10_000)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, 10_000)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, 10_000)
|
||||
|
||||
colorSystem.update()
|
||||
positionSystem.update()
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 10_000)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, 10_000)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, 10_000)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||
XCTAssertEqual(nexus.numEntities, num)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, num)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, num)
|
||||
XCTAssertEqual(nexus.freeEntities.count, num)
|
||||
|
||||
batchCreateEntities(count: 10_000)
|
||||
batchCreateEntities(count: num)
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 20_000)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, 20_000)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, 20_000)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
|
||||
XCTAssertEqual(nexus.numEntities, num * 2)
|
||||
XCTAssertEqual(colorSystem.family.memberIds.count, num * 2)
|
||||
XCTAssertEqual(positionSystem.family.memberIds.count, num * 2)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
}
|
||||
|
||||
func createDefaultEntity(name: String?) {
|
||||
|
|
@ -99,6 +120,7 @@ class SystemsTests: XCTestCase {
|
|||
func batchDestroyEntities(count: Int) {
|
||||
let family = nexus.family(requiresAll: [Position.self], excludesAll: [])
|
||||
var currentCount = count
|
||||
|
||||
family.iterate { (entity: Entity!) in
|
||||
if currentCount > 0 {
|
||||
entity.destroy()
|
||||
|
|
@ -150,8 +172,8 @@ class PositionSystem {
|
|||
|
||||
let deltaX: Double = self.velocity*((self.randNorm() * 2) - 1)
|
||||
let deltaY: Double = self.velocity*((self.randNorm() * 2) - 1)
|
||||
var x = pos.x + Int(deltaX)
|
||||
var y = pos.y + Int(deltaY)
|
||||
let x = pos.x + Int(deltaX)
|
||||
let y = pos.y + Int(deltaY)
|
||||
|
||||
pos.x = x
|
||||
pos.y = y
|
||||
|
|
|
|||
Loading…
Reference in New Issue