Fix bug in onFamilyDeinit

This commit is contained in:
Christian Treffs 2018-05-10 19:55:01 +02:00
parent d274b3e719
commit e0fd2bbeb1
7 changed files with 177 additions and 65 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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