diff --git a/Sources/FirebladeECS/Nexus+Component.swift b/Sources/FirebladeECS/Nexus+Component.swift index 11da234..4635a97 100644 --- a/Sources/FirebladeECS/Nexus+Component.swift +++ b/Sources/FirebladeECS/Nexus+Component.swift @@ -22,31 +22,9 @@ extension Nexus { } public final func assign(component: Component, to entity: Entity) { - let componentId: ComponentIdentifier = component.identifier let entityId: EntityIdentifier = entity.identifier - - // test if component is already assigned - guard !has(componentId: componentId, entityId: entityId) else { - delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)") - assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)") - return - } - - // add component instances to uniform component stores - if componentsByType[componentId] == nil { - componentsByType[componentId] = ManagedContiguousArray() - } - componentsByType[componentId]?.insert(component, at: entityId.id) - - // assigns the component id to the entity id - if componentIdsByEntity[entityId] == nil { - componentIdsByEntity[entityId] = Set() - } - componentIdsByEntity[entityId]?.insert(componentId) //, at: componentId.hashValue) - - update(familyMembership: entityId) - - delegate?.nexusEvent(ComponentAdded(component: componentId, toEntity: entity.identifier)) + assign(component: component, entityId: entityId) + delegate?.nexusEvent(ComponentAdded(component: component.identifier, toEntity: entity.identifier)) } public final func assign(component: C, to entity: Entity) where C: Component { diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index 389883f..7e12e80 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -17,14 +17,14 @@ extension Nexus { @discardableResult public func createEntity(with components: Component...) -> Entity { let newEntity = createEntity() - components.forEach { newEntity.assign($0) } + assign(components: components, to: newEntity.identifier) return newEntity } @discardableResult public func createEntity(with components: C) -> Entity where C: Collection, C.Element == Component { let entity = self.createEntity() - components.forEach { entity.assign($0) } + assign(components: components, to: entity.identifier) return entity } diff --git a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift b/Sources/FirebladeECS/Nexus+FamilyUpdate.swift deleted file mode 100644 index a669077..0000000 --- a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// Nexus+FamilyUpdate.swift -// FirebladeECS -// -// Created by Christian Treffs on 14.02.19. -// - -extension Nexus { - /// will be called on family init - final func onFamilyInit(traits: FamilyTraitSet) { - guard familyMembersByTraits[traits] == nil else { - return - } - - familyMembersByTraits[traits] = UnorderedSparseSet() - update(familyMembership: traits) - } - - final func update(familyMembership traits: FamilyTraitSet) { - // FIXME: iterating all entities is costly for many entities - var iter = entityStorage.makeIterator() - while let entityId = iter.next() { - update(membership: traits, for: entityId) - } - } - - final func update(familyMembership entityId: EntityIdentifier) { - // FIXME: iterating all families is costly for many families - var iter = familyMembersByTraits.keys.makeIterator() - while let traits = iter.next() { - update(membership: traits, for: entityId) - } - } - - final func update(membership traits: FamilyTraitSet, for entityId: EntityIdentifier) { - guard let componentIds = componentIdsByEntity[entityId] else { - // no components - so skip - return - } - - let isMember: Bool = self.isMember(entity: entityId, inFamilyWithTraits: traits) - if !exists(entity: entityId) && isMember { - remove(entityWithId: entityId, fromFamilyWithTraits: traits) - return - } - - let isMatch: Bool = traits.isMatch(components: componentIds) - - switch (isMatch, isMember) { - case (true, false): - add(entityWithId: entityId, toFamilyWithTraits: traits) - delegate?.nexusEvent(FamilyMemberAdded(member: entityId, toFamily: traits)) - - case (false, true): - remove(entityWithId: entityId, fromFamilyWithTraits: traits) - delegate?.nexusEvent(FamilyMemberRemoved(member: entityId, from: traits)) - - default: - break - } - } - - final func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) { - familyMembersByTraits[traits]!.insert(entityId, at: entityId.id) - } - - final func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) { - familyMembersByTraits[traits]!.remove(at: entityId.id) - } -} diff --git a/Sources/FirebladeECS/Nexus+Internal.swift b/Sources/FirebladeECS/Nexus+Internal.swift new file mode 100644 index 0000000..2ba5263 --- /dev/null +++ b/Sources/FirebladeECS/Nexus+Internal.swift @@ -0,0 +1,136 @@ +// +// Nexus+Internal.swift +// FirebladeECS +// +// Created by Christian Treffs on 14.02.19. +// + +extension Nexus { + func assign(components: C, to entityId: EntityIdentifier) where C: Collection, C.Element == Component { + var iter = components.makeIterator() + while let component = iter.next() { + let componentId = component.identifier + // test if component is already assigned + guard !has(componentId: componentId, entityId: entityId) else { + delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)") + assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)") + return + } + + // add component instances to uniform component stores + insertComponentInstance(component, componentId, entityId) + + // assigns the component id to the entity id + assign(componentId, entityId) + } + + // Update entity membership + update(familyMembership: entityId) + } + + func assign(component: Component, entityId: EntityIdentifier) { + let componentId = component.identifier + + // test if component is already assigned + guard !has(componentId: componentId, entityId: entityId) else { + delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)") + assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)") + return + } + + // add component instances to uniform component stores + insertComponentInstance(component, componentId, entityId) + + // assigns the component id to the entity id + assign(componentId, entityId) + + // Update entity membership + update(familyMembership: entityId) + } + + func insertComponentInstance(_ component: Component, _ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) { + if componentsByType[componentId] == nil { + componentsByType[componentId] = ManagedContiguousArray() + } + componentsByType[componentId]?.insert(component, at: entityId.id) + } + + func assign(_ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) { + if componentIdsByEntity[entityId] == nil { + componentIdsByEntity[entityId] = Set(arrayLiteral: componentId) + } else { + componentIdsByEntity[entityId]?.insert(componentId) + } + } + + func assign(_ componentIds: Set, _ entityId: EntityIdentifier) { + if componentIdsByEntity[entityId] == nil { + componentIdsByEntity[entityId] = componentIds + } else { + componentIdsByEntity[entityId]?.formUnion(componentIds) + } + } + + func update(familyMembership entityId: EntityIdentifier) { + // FIXME: iterating all families is costly for many families + // FIXME: this could be parallelized + var iter = familyMembersByTraits.keys.makeIterator() + while let traits = iter.next() { + update(membership: traits, for: entityId) + } + } + + /// will be called on family init + func onFamilyInit(traits: FamilyTraitSet) { + guard familyMembersByTraits[traits] == nil else { + return + } + + familyMembersByTraits[traits] = UnorderedSparseSet() + update(familyMembership: traits) + } + + func update(familyMembership traits: FamilyTraitSet) { + // FIXME: iterating all entities is costly for many entities + var iter = entityStorage.makeIterator() + while let entityId = iter.next() { + update(membership: traits, for: entityId) + } + } + + func update(membership traits: FamilyTraitSet, for entityId: EntityIdentifier) { + guard let componentIds = componentIdsByEntity[entityId] else { + // no components - so skip + return + } + + let isMember: Bool = self.isMember(entity: entityId, inFamilyWithTraits: traits) + if !exists(entity: entityId) && isMember { + remove(entityWithId: entityId, fromFamilyWithTraits: traits) + return + } + + let isMatch: Bool = traits.isMatch(components: componentIds) + + switch (isMatch, isMember) { + case (true, false): + add(entityWithId: entityId, toFamilyWithTraits: traits) + delegate?.nexusEvent(FamilyMemberAdded(member: entityId, toFamily: traits)) + + case (false, true): + remove(entityWithId: entityId, fromFamilyWithTraits: traits) + delegate?.nexusEvent(FamilyMemberRemoved(member: entityId, from: traits)) + + default: + break + } + } + + func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) { + familyMembersByTraits[traits]!.insert(entityId, at: entityId.id) + } + + func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) { + familyMembersByTraits[traits]!.remove(at: entityId.id) + } +} diff --git a/Sources/FirebladeECS/UnorderedSparseSet.swift b/Sources/FirebladeECS/UnorderedSparseSet.swift index 74b3d61..d01d30f 100644 --- a/Sources/FirebladeECS/UnorderedSparseSet.swift +++ b/Sources/FirebladeECS/UnorderedSparseSet.swift @@ -12,7 +12,7 @@ /// an element from the sparse set. /// /// See for a reference implementation. -public struct UnorderedSparseSet { +public final class UnorderedSparseSet { /// An index into the dense store. public typealias DenseIndex = Int @@ -30,7 +30,7 @@ public struct UnorderedSparseSet { @usableFromInline var dense: DenseStore @usableFromInline var sparse: SparseStore - public init() { + public convenience init() { self.init(sparse: [:], dense: []) } @@ -44,7 +44,7 @@ public struct UnorderedSparseSet { @inlinable public func contains(_ key: Key) -> Bool { - find(at: key) != nil + findIndex(at: key) != nil } /// Inset an element for a given key into the set in O(1). @@ -55,15 +55,15 @@ public struct UnorderedSparseSet { /// - key: the key /// - Returns: true if new, false if replaced. @discardableResult - public mutating func insert(_ element: Element, at key: Key) -> Bool { - if let (denseIndex, _) = find(at: key) { + public func insert(_ element: Element, at key: Key) -> Bool { + if let denseIndex = findIndex(at: key) { dense[denseIndex] = Entry(key: key, element: element) return false } let nIndex = dense.count dense.append(Entry(key: key, element: element)) - sparse[key] = nIndex + sparse.updateValue(nIndex, forKey: key) return true } @@ -73,16 +73,12 @@ public struct UnorderedSparseSet { /// - 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 - } - - return element + findElement(at: key) } @inlinable public func get(unsafeAt key: Key) -> Element { - find(at: key).unsafelyUnwrapped.1 + findElement(at: key).unsafelyUnwrapped } /// Removes the element entry for given key in O(1). @@ -90,8 +86,8 @@ public struct UnorderedSparseSet { /// - Parameter key: the key /// - Returns: removed value or nil if key not found. @discardableResult - public mutating func remove(at key: Key) -> Entry? { - guard let (denseIndex, _) = find(at: key) else { + public func remove(at key: Key) -> Entry? { + guard let denseIndex = findIndex(at: key) else { return nil } @@ -105,7 +101,7 @@ public struct UnorderedSparseSet { } @inlinable - public mutating func removeAll(keepingCapacity: Bool = false) { + public func removeAll(keepingCapacity: Bool = false) { sparse.removeAll(keepingCapacity: keepingCapacity) dense.removeAll(keepingCapacity: keepingCapacity) } @@ -115,22 +111,30 @@ public struct UnorderedSparseSet { /// /// - Parameter denseIndex: the dense index /// - Returns: the element entry - private mutating func swapRemove(at denseIndex: Int) -> Entry { + private func swapRemove(at denseIndex: Int) -> Entry { dense.swapAt(denseIndex, dense.count - 1) return dense.removeLast() } @inlinable - public func find(at key: Key) -> (Int, Element)? { + func findIndex(at key: Key) -> Int? { guard let denseIndex = sparse[key], denseIndex < count else { return nil } + return denseIndex + } + + @inlinable + func findElement(at key: Key) -> Element? { + guard let denseIndex = findIndex(at: key) else { + return nil + } let entry = self.dense[denseIndex] guard entry.key == key else { return nil } - return (denseIndex, entry.element) + return entry.element } @inlinable public var first: Element? { diff --git a/Tests/FirebladeECSTests/SparseSetTests.swift b/Tests/FirebladeECSTests/SparseSetTests.swift index f02f4ec..5bf6d42 100644 --- a/Tests/FirebladeECSTests/SparseSetTests.swift +++ b/Tests/FirebladeECSTests/SparseSetTests.swift @@ -387,7 +387,7 @@ class SparseSetTests: XCTestCase { func testSparseSetDoubleRemove() { class AClass { } - var set = UnorderedSparseSet() + let set = UnorderedSparseSet() let a = AClass() let b = AClass() set.insert(a, at: 0) @@ -471,7 +471,7 @@ class SparseSetTests: XCTestCase { } func testSparseSetReduce() { - var characters = UnorderedSparseSet() + let characters = UnorderedSparseSet() characters.insert("H", at: 4) characters.insert("e", at: 13) @@ -497,7 +497,7 @@ class SparseSetTests: XCTestCase { } func testSubscript() { - var characters = UnorderedSparseSet() + let characters = UnorderedSparseSet() characters[4] = "H" characters[13] = "e" @@ -528,7 +528,7 @@ class SparseSetTests: XCTestCase { } func testStartEndIndex() { - var set = UnorderedSparseSet() + let set = UnorderedSparseSet() set.insert("C", at: 33) set.insert("A", at: 11) @@ -541,7 +541,7 @@ class SparseSetTests: XCTestCase { func testAlternativeKey() { - var set = UnorderedSparseSet() + let set = UnorderedSparseSet() set.insert("A", at: "a") set.insert("C", at: "c")