From 98f2ff5d20a0d6e1fb97e97ab1362d46222991e7 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 14:54:15 +0200 Subject: [PATCH 01/24] Optimize ManagedContiguousArray --- .../FirebladeECS/ManagedContiguousArray.swift | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/Sources/FirebladeECS/ManagedContiguousArray.swift b/Sources/FirebladeECS/ManagedContiguousArray.swift index b6fbdb7..a8b7938 100644 --- a/Sources/FirebladeECS/ManagedContiguousArray.swift +++ b/Sources/FirebladeECS/ManagedContiguousArray.swift @@ -5,27 +5,26 @@ // Created by Christian Treffs on 28.10.17. // -public class ManagedContiguousArray { +public struct ManagedContiguousArray { public typealias Index = Int - private let chunkSize: Int - private var size: Int = 0 - private var store: ContiguousArray = [] + + @usableFromInline let chunkSize: Int + @usableFromInline var size: Int = 0 + @usableFromInline var store: ContiguousArray = [] public init(minCount: Int = 4096) { chunkSize = minCount store = ContiguousArray(repeating: nil, count: minCount) } - deinit { - clear() - } - + @inline(__always) public var count: Int { return size } @discardableResult - public func insert(_ element: Element, at index: Int) -> Bool { + @inlinable + public mutating func insert(_ element: Element, at index: Int) -> Bool { if needsToGrow(index) { grow(to: index) } @@ -35,6 +34,8 @@ public class ManagedContiguousArray { store[index] = element return true } + + @inlinable public func contains(_ index: Index) -> Bool { if store.count <= index { return false @@ -42,16 +43,19 @@ public class ManagedContiguousArray { return store[index] != nil } + @inline(__always) public func get(at index: Index) -> Element? { return store[index] } + @inline(__always) public func get(unsafeAt index: Index) -> Element { return store[index].unsafelyUnwrapped } @discardableResult - public func remove(at index: Index) -> Bool { + @inlinable + public mutating func remove(at index: Index) -> Bool { if store[index] != nil { size -= 1 } @@ -62,22 +66,26 @@ public class ManagedContiguousArray { return true } - public func clear(keepingCapacity: Bool = false) { + @inlinable + public mutating func clear(keepingCapacity: Bool = false) { size = 0 store.removeAll(keepingCapacity: keepingCapacity) } - private func needsToGrow(_ index: Index) -> Bool { + @inlinable + func needsToGrow(_ index: Index) -> Bool { return index > store.count - 1 } - private func grow(to index: Index) { + @inlinable + mutating func grow(to index: Index) { let newCapacity: Int = calculateCapacity(to: index) let newCount: Int = newCapacity - store.count store += ContiguousArray(repeating: nil, count: newCount) } - private func calculateCapacity(to index: Index) -> Int { + @inlinable + func calculateCapacity(to index: Index) -> Int { let delta = Float(index) / Float(chunkSize) let multiplier = Int(delta.rounded(.up)) + 1 return multiplier * chunkSize @@ -90,3 +98,6 @@ extension ManagedContiguousArray: Equatable where Element: Equatable { return lhs.store == rhs.store } } + +// MARK: - Codable +extension ManagedContiguousArray: Codable where Element: Codable { } From 589a8c2ec133b722a843fa16b9e404e96c03e88a Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 14:54:33 +0200 Subject: [PATCH 02/24] Optimize UnorderedSparseSet --- Sources/FirebladeECS/UnorderedSparseSet.swift | 19 ++++++++++--------- Tests/FirebladeECSTests/SparseSetTests.swift | 8 ++++---- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Sources/FirebladeECS/UnorderedSparseSet.swift b/Sources/FirebladeECS/UnorderedSparseSet.swift index 8d07370..8f58734 100644 --- a/Sources/FirebladeECS/UnorderedSparseSet.swift +++ b/Sources/FirebladeECS/UnorderedSparseSet.swift @@ -5,7 +5,7 @@ // Created by Christian Treffs on 30.10.17. // -open class UnorderedSparseSet { +public struct UnorderedSparseSet { public typealias Index = Int public typealias Key = Int @@ -22,10 +22,6 @@ open class UnorderedSparseSet { dense = ContiguousArray() } - deinit { - removeAll() - } - public var count: Int { return dense.count } public var isEmpty: Bool { return dense.isEmpty } @@ -42,7 +38,7 @@ open class UnorderedSparseSet { /// - key: the key /// - Returns: true if new, false if replaced. @discardableResult - public func insert(_ element: Element, at key: Key) -> Bool { + public mutating func insert(_ element: Element, at key: Key) -> Bool { if let (denseIndex, _) = find(at: key) { dense[denseIndex] = Entry(key: key, element: element) return false @@ -77,7 +73,7 @@ open class UnorderedSparseSet { /// - Parameter key: the key /// - Returns: removed value or nil if key not found. @discardableResult - public func remove(at key: Key) -> Entry? { + public mutating func remove(at key: Key) -> Entry? { guard let (denseIndex, _) = find(at: key) else { return nil } @@ -92,7 +88,7 @@ open class UnorderedSparseSet { } @inlinable - public func removeAll(keepingCapacity: Bool = false) { + public mutating func removeAll(keepingCapacity: Bool = false) { sparse.removeAll(keepingCapacity: keepingCapacity) dense.removeAll(keepingCapacity: keepingCapacity) } @@ -107,7 +103,7 @@ open class UnorderedSparseSet { /// /// - Parameter denseIndex: the dense index /// - Returns: the element entry - private func swapRemove(at denseIndex: Int) -> Entry { + private mutating func swapRemove(at denseIndex: Int) -> Entry { dense.swapAt(denseIndex, dense.count - 1) return dense.removeLast() } @@ -145,6 +141,7 @@ open class UnorderedSparseSet { } } +// MARK: - Equatable extension UnorderedSparseSet.Entry: Equatable where Element: Equatable { } extension UnorderedSparseSet: Equatable where Element: Equatable { public static func == (lhs: UnorderedSparseSet, rhs: UnorderedSparseSet) -> Bool { @@ -152,6 +149,10 @@ extension UnorderedSparseSet: Equatable where Element: Equatable { } } +// MARK: - Codable +extension UnorderedSparseSet.Entry: Codable where Element: Codable { } +extension UnorderedSparseSet: Codable where Element: Codable { } + // MARK: - UnorderedSparseSetIterator public struct UnorderedSparseSetIterator: IteratorProtocol { public private(set) var iterator: IndexingIterator.Entry>> diff --git a/Tests/FirebladeECSTests/SparseSetTests.swift b/Tests/FirebladeECSTests/SparseSetTests.swift index 7351f42..4800376 100644 --- a/Tests/FirebladeECSTests/SparseSetTests.swift +++ b/Tests/FirebladeECSTests/SparseSetTests.swift @@ -387,7 +387,7 @@ class SparseSetTests: XCTestCase { func testSparseSetDoubleRemove() { class AClass { } - let set = UnorderedSparseSet() + var set = UnorderedSparseSet() let a = AClass() let b = AClass() set.insert(a, at: 0) @@ -471,7 +471,7 @@ class SparseSetTests: XCTestCase { } func testSparseSetReduce() { - let characters = UnorderedSparseSet() + var characters = UnorderedSparseSet() characters.insert("H", at: 4) characters.insert("e", at: 13) @@ -497,7 +497,7 @@ class SparseSetTests: XCTestCase { } func testSubscript() { - let characters = UnorderedSparseSet() + var characters = UnorderedSparseSet() characters[4] = "H" characters[13] = "e" @@ -528,7 +528,7 @@ class SparseSetTests: XCTestCase { } func testStartEndIndex() { - let set = UnorderedSparseSet() + var set = UnorderedSparseSet() set.insert("C", at: 33) set.insert("A", at: 11) From 61d407e5dbdf166b2ffbf7c99df45d1b205e05a4 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 14:55:52 +0200 Subject: [PATCH 03/24] Optimize and cleanup Nexus --- Sources/FirebladeECS/Family.swift | 2 +- Sources/FirebladeECS/FamilyTraitSet.swift | 4 ++- Sources/FirebladeECS/Nexus+Component.swift | 2 +- Sources/FirebladeECS/Nexus+FamilyUpdate.swift | 4 +-- Sources/FirebladeECS/Nexus+SceneGraph.swift | 14 ++++---- Sources/FirebladeECS/Nexus.swift | 32 +++++++++---------- 6 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Sources/FirebladeECS/Family.swift b/Sources/FirebladeECS/Family.swift index eded766..2200402 100644 --- a/Sources/FirebladeECS/Family.swift +++ b/Sources/FirebladeECS/Family.swift @@ -161,7 +161,7 @@ extension Family { } mutating func aggregateRelativesBreathFirst(_ parent: EntityIdentifier) { - guard let children = nexus.parentChildrenMap[parent] else { + guard let children = nexus.childrenByParentEntity[parent] else { return } children diff --git a/Sources/FirebladeECS/FamilyTraitSet.swift b/Sources/FirebladeECS/FamilyTraitSet.swift index bf1220c..685a76c 100644 --- a/Sources/FirebladeECS/FamilyTraitSet.swift +++ b/Sources/FirebladeECS/FamilyTraitSet.swift @@ -61,11 +61,13 @@ extension FamilyTraitSet: Hashable { } } -extension FamilyTraitSet: CustomStringConvertible, CustomDebugStringConvertible { +extension FamilyTraitSet: CustomStringConvertible { @inlinable public var description: String { return "" } +} +extension FamilyTraitSet: CustomDebugStringConvertible { @inlinable public var debugDescription: String { return "" } diff --git a/Sources/FirebladeECS/Nexus+Component.swift b/Sources/FirebladeECS/Nexus+Component.swift index 4a0ec73..9f1f053 100644 --- a/Sources/FirebladeECS/Nexus+Component.swift +++ b/Sources/FirebladeECS/Nexus+Component.swift @@ -34,7 +34,7 @@ extension Nexus { // add component instances to uniform component stores if componentsByType[componentId] == nil { - componentsByType[componentId] = ManagedContiguousArray() + componentsByType[componentId] = UnorderedSparseSet() } componentsByType[componentId]?.insert(component, at: entityId.id) diff --git a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift b/Sources/FirebladeECS/Nexus+FamilyUpdate.swift index 2c1ab51..9b92e72 100644 --- a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift +++ b/Sources/FirebladeECS/Nexus+FamilyUpdate.swift @@ -64,11 +64,11 @@ extension Nexus { final func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) { precondition(familyMembersByTraits[traits] != nil) - familyMembersByTraits[traits].unsafelyUnwrapped.insert(entityId, at: entityId.id) + familyMembersByTraits[traits]!.insert(entityId, at: entityId.id) } final func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) { precondition(familyMembersByTraits[traits] != nil) - familyMembersByTraits[traits].unsafelyUnwrapped.remove(at: entityId.id) + familyMembersByTraits[traits]!.remove(at: entityId.id) } } diff --git a/Sources/FirebladeECS/Nexus+SceneGraph.swift b/Sources/FirebladeECS/Nexus+SceneGraph.swift index d8576a4..24e8497 100644 --- a/Sources/FirebladeECS/Nexus+SceneGraph.swift +++ b/Sources/FirebladeECS/Nexus+SceneGraph.swift @@ -8,11 +8,11 @@ extension Nexus { public final func addChild(_ child: Entity, to parent: Entity) -> Bool { let inserted: Bool - if parentChildrenMap[parent.identifier] == nil { - parentChildrenMap[parent.identifier] = [child.identifier] + if childrenByParentEntity[parent.identifier] == nil { + childrenByParentEntity[parent.identifier] = [child.identifier] inserted = true } else { - let (isNewMember, _) = parentChildrenMap[parent.identifier]!.insert(child.identifier) + let (isNewMember, _) = childrenByParentEntity[parent.identifier]!.insert(child.identifier) inserted = isNewMember } if inserted { @@ -27,7 +27,7 @@ extension Nexus { @discardableResult public final func removeChild(_ child: EntityIdentifier, from parent: EntityIdentifier) -> Bool { - let removed: Bool = parentChildrenMap[parent]?.remove(child) != nil + let removed: Bool = childrenByParentEntity[parent]?.remove(child) != nil if removed { delegate?.nexusEvent(ChildRemoved(parent: parent, child: child)) } @@ -35,11 +35,11 @@ extension Nexus { } public final func removeAllChildren(from parent: Entity) { - parentChildrenMap[parent.identifier]?.forEach { removeChild($0, from: parent.identifier) } - return parentChildrenMap[parent.identifier] = nil + childrenByParentEntity[parent.identifier]?.forEach { removeChild($0, from: parent.identifier) } + return childrenByParentEntity[parent.identifier] = nil } public final func numChildren(for entity: Entity) -> Int { - return parentChildrenMap[entity.identifier]?.count ?? 0 + return childrenByParentEntity[entity.identifier]?.count ?? 0 } } diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index ffe17d2..5fdf2fb 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -6,40 +6,44 @@ // public final class Nexus { - public final weak var delegate: NexusEventDelegate? - /// Main entity storage. /// Entities are tightly packed by EntityIdentifier. @usableFromInline final var entityStorage: UnorderedSparseSet + /// Entity ids that are currently not used. + @usableFromInline final var freeEntities: [EntityIdentifier] + /// - Key: ComponentIdentifier aka component type. /// - Value: Array of component instances of same type (uniform). /// New component instances are appended. - @usableFromInline final var componentsByType: [ComponentIdentifier: ManagedContiguousArray] + @usableFromInline final var componentsByType: [ComponentIdentifier: UnorderedSparseSet] /// - Key: EntityIdentifier aka entity index /// - Value: Set of unique component types (ComponentIdentifier). /// Each element is a component identifier associated with this entity. @usableFromInline final var componentIdsByEntity: [EntityIdentifier: Set] - /// Entity ids that are currently not used. - @usableFromInline final var freeEntities: ContiguousArray + /// - Key: A parent entity id. + /// - Value: Adjacency Set of all associated children. + @usableFromInline final var childrenByParentEntity: [EntityIdentifier: Set] /// - Key: FamilyTraitSet aka component types that make up one distinct family. /// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family. @usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet] - /// - Key: A parent entity id. - /// - Value: Adjacency Set of all associated children. - @usableFromInline final var parentChildrenMap: [EntityIdentifier: Set] + public final weak var delegate: NexusEventDelegate? public init() { entityStorage = UnorderedSparseSet() componentsByType = [:] componentIdsByEntity = [:] - freeEntities = ContiguousArray() + freeEntities = [] familyMembersByTraits = [:] - parentChildrenMap = [:] + childrenByParentEntity = [:] + } + + deinit { + clear() } public final func clear() { @@ -60,11 +64,7 @@ public final class Nexus { componentsByType.removeAll() componentIdsByEntity.removeAll() familyMembersByTraits.removeAll() - parentChildrenMap.removeAll() - } - - deinit { - clear() + childrenByParentEntity.removeAll() } } @@ -77,7 +77,7 @@ extension Nexus: Equatable { lhs.freeEntities == rhs.freeEntities && lhs.familyMembersByTraits == rhs.familyMembersByTraits && lhs.componentsByType.keys == rhs.componentsByType.keys && - lhs.parentChildrenMap == rhs.parentChildrenMap + lhs.childrenByParentEntity == rhs.childrenByParentEntity // NOTE: components are not equatable (yet) } } From d3ab0d96beb83067191935e242120587c260c0b0 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 14:56:28 +0200 Subject: [PATCH 04/24] Make components codable --- Sources/FirebladeECS/Component.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/FirebladeECS/Component.swift b/Sources/FirebladeECS/Component.swift index 6764e89..c87e7a5 100644 --- a/Sources/FirebladeECS/Component.swift +++ b/Sources/FirebladeECS/Component.swift @@ -7,9 +7,8 @@ /// **Component** /// -/// A component represents the raw data for one aspect of the object, -/// and how it interacts with the world. -public protocol Component: class { +/// A component represents the raw data for one aspect of an object. +public protocol Component: class, Codable { static var identifier: ComponentIdentifier { get } var identifier: ComponentIdentifier { get } } From 893cfaee74c9aa7ebdd105e391bed9a008a662d6 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 15:07:15 +0200 Subject: [PATCH 05/24] Conform UnorderedSparseSet to Sequence --- Sources/FirebladeECS/UnorderedSparseSet.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/FirebladeECS/UnorderedSparseSet.swift b/Sources/FirebladeECS/UnorderedSparseSet.swift index 8f58734..dfb1bad 100644 --- a/Sources/FirebladeECS/UnorderedSparseSet.swift +++ b/Sources/FirebladeECS/UnorderedSparseSet.swift @@ -93,11 +93,6 @@ public struct UnorderedSparseSet { dense.removeAll(keepingCapacity: keepingCapacity) } - @inlinable - public func makeIterator() -> UnorderedSparseSetIterator { - return UnorderedSparseSetIterator(self) - } - /// Removes an element from the set and retuns it in O(1). /// The removed element is replaced with the last element of the set. /// @@ -141,6 +136,13 @@ public struct UnorderedSparseSet { } } +// MARK: - Sequence +extension UnorderedSparseSet: Sequence { + public __consuming func makeIterator() -> UnorderedSparseSetIterator { + return UnorderedSparseSetIterator(self) + } +} + // MARK: - Equatable extension UnorderedSparseSet.Entry: Equatable where Element: Equatable { } extension UnorderedSparseSet: Equatable where Element: Equatable { From a2bb251e80b7f24d0e5a3bb197fe8d80e9d7ca2d Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 22:35:03 +0200 Subject: [PATCH 06/24] Update swiftlint config --- .swiftlint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 5aa8fe1..f7c8e45 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -57,7 +57,7 @@ opt_in_rules: - multiline_arguments - multiline_function_chains - multiline_parameters - - multiline_parameters_brackets + #- multiline_parameters_brackets - nimble_operator - no_extension_access_modifier - number_separator From bba9ba46f8f8a68a65a25f820024a38477598755 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 22:36:50 +0200 Subject: [PATCH 07/24] Add identifier tests --- .../TypeIdentifierPerformanceTests.swift | 73 +++++++++++++++++++ .../ComponentIdentifierTests.swift | 23 ++++++ 2 files changed, 96 insertions(+) create mode 100644 Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift create mode 100644 Tests/FirebladeECSTests/ComponentIdentifierTests.swift diff --git a/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift b/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift new file mode 100644 index 0000000..022a4cc --- /dev/null +++ b/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift @@ -0,0 +1,73 @@ +// +// TypeIdentifierPerformanceTests.swift +// +// +// Created by Christian Treffs on 05.10.19. +// + +import XCTest + +final class TypeIdentifierPerformanceTests: XCTestCase { + let maxIterations: Int = 100_000 + + // 0.056 sec + func testPerformanceObjectIdentifier() { + measure { + for _ in 0.. Date: Sat, 5 Oct 2019 22:37:11 +0200 Subject: [PATCH 08/24] Update sparse set --- Sources/FirebladeECS/Family.swift | 6 +-- Sources/FirebladeECS/UnorderedSparseSet.swift | 38 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Sources/FirebladeECS/Family.swift b/Sources/FirebladeECS/Family.swift index 2200402..6370bd5 100644 --- a/Sources/FirebladeECS/Family.swift +++ b/Sources/FirebladeECS/Family.swift @@ -59,7 +59,7 @@ extension Family: LazySequenceProtocol { } // MARK: - components iterator extension Family { public struct ComponentsIterator: IteratorProtocol { - @usableFromInline var memberIdsIterator: UnorderedSparseSetIterator + @usableFromInline var memberIdsIterator: UnorderedSparseSet.ElementIterator @usableFromInline unowned let nexus: Nexus public init(family: Family) { @@ -86,7 +86,7 @@ extension Family { } public struct EntityIterator: IteratorProtocol { - @usableFromInline var memberIdsIterator: UnorderedSparseSetIterator + @usableFromInline var memberIdsIterator: UnorderedSparseSet.ElementIterator @usableFromInline unowned let nexus: Nexus public init(family: Family) { @@ -112,7 +112,7 @@ extension Family { } public struct EntityComponentIterator: IteratorProtocol { - @usableFromInline var memberIdsIterator: UnorderedSparseSetIterator + @usableFromInline var memberIdsIterator: UnorderedSparseSet.ElementIterator @usableFromInline unowned let nexus: Nexus public init(family: Family) { diff --git a/Sources/FirebladeECS/UnorderedSparseSet.swift b/Sources/FirebladeECS/UnorderedSparseSet.swift index dfb1bad..41d5c2f 100644 --- a/Sources/FirebladeECS/UnorderedSparseSet.swift +++ b/Sources/FirebladeECS/UnorderedSparseSet.swift @@ -18,8 +18,12 @@ public struct UnorderedSparseSet { @usableFromInline var sparse: [Index: Key] public init() { - sparse = [Index: Key]() - dense = ContiguousArray() + self.init(sparse: [:], dense: []) + } + + init(sparse: [Index: Key], dense: ContiguousArray) { + self.sparse = sparse + self.dense = dense } public var count: Int { return dense.count } @@ -138,8 +142,21 @@ public struct UnorderedSparseSet { // MARK: - Sequence extension UnorderedSparseSet: Sequence { - public __consuming func makeIterator() -> UnorderedSparseSetIterator { - return UnorderedSparseSetIterator(self) + public __consuming func makeIterator() -> ElementIterator { + return ElementIterator(self) + } + + // MARK: - UnorderedSparseSetIterator + public struct ElementIterator: IteratorProtocol { + public private(set) var iterator: IndexingIterator.Entry>> + + public init(_ sparseSet: UnorderedSparseSet) { + iterator = sparseSet.dense.makeIterator() + } + + public mutating func next() -> Element? { + return iterator.next()?.element + } } } @@ -154,16 +171,3 @@ extension UnorderedSparseSet: Equatable where Element: Equatable { // MARK: - Codable extension UnorderedSparseSet.Entry: Codable where Element: Codable { } extension UnorderedSparseSet: Codable where Element: Codable { } - -// MARK: - UnorderedSparseSetIterator -public struct UnorderedSparseSetIterator: IteratorProtocol { - public private(set) var iterator: IndexingIterator.Entry>> - - public init(_ sparseSet: UnorderedSparseSet) { - iterator = sparseSet.dense.makeIterator() - } - - public mutating func next() -> Element? { - return iterator.next()?.element - } -} From c3d84b4f12b2066c52e10bfc5e79514785c5878a Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 22:38:28 +0200 Subject: [PATCH 09/24] Refactor component and entity identifier --- Sources/FirebladeECS/Component.swift | 1 - .../FirebladeECS/ComponentIdentifier.swift | 14 +++- Sources/FirebladeECS/Nexus+Entity.swift | 19 ++++-- Sources/FirebladeECS/Nexus+FamilyUpdate.swift | 4 +- Sources/FirebladeECS/Nexus+SceneGraph.swift | 8 ++- Sources/FirebladeECS/Nexus.swift | 68 +++++++++++++------ Tests/FirebladeECSPerformanceTests/Base.swift | 16 ++++- Tests/FirebladeECSTests/Base.swift | 11 ++- 8 files changed, 104 insertions(+), 37 deletions(-) diff --git a/Sources/FirebladeECS/Component.swift b/Sources/FirebladeECS/Component.swift index c87e7a5..a6ea2df 100644 --- a/Sources/FirebladeECS/Component.swift +++ b/Sources/FirebladeECS/Component.swift @@ -14,6 +14,5 @@ public protocol Component: class, Codable { } extension Component { - public static var identifier: ComponentIdentifier { return ComponentIdentifier(Self.self) } @inlinable public var identifier: ComponentIdentifier { return Self.identifier } } diff --git a/Sources/FirebladeECS/ComponentIdentifier.swift b/Sources/FirebladeECS/ComponentIdentifier.swift index d3ae5d7..33e564a 100644 --- a/Sources/FirebladeECS/ComponentIdentifier.swift +++ b/Sources/FirebladeECS/ComponentIdentifier.swift @@ -7,12 +7,20 @@ /// Identifies a component by it's meta type public struct ComponentIdentifier: Identifiable { - public let id: ObjectIdentifier + public let id: String - init(_ type: T.Type) where T: Component { - self.id = ObjectIdentifier(type) + public init(_ componentType: T.Type) where T: Component { + defer { Nexus.register(component: T.self, using: self) } + + self.id = String(reflecting: componentType) } } extension ComponentIdentifier: Equatable { } extension ComponentIdentifier: Hashable { } +extension ComponentIdentifier: Codable { } +extension ComponentIdentifier: Comparable { + public static func < (lhs: ComponentIdentifier, rhs: ComponentIdentifier) -> Bool { + return lhs.id < rhs.id + } +} diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index b40308b..3578bfe 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -17,10 +17,9 @@ extension Nexus { @discardableResult public func createEntity() -> Entity { let newEntityIdentifier: EntityIdentifier = nextEntityId() - let newEntity = Entity(nexus: self, id: newEntityIdentifier) - entityStorage.insert(newEntity, at: newEntityIdentifier.id) + entityStorage.insert(newEntityIdentifier, at: newEntityIdentifier.id) delegate?.nexusEvent(EntityCreated(entityId: newEntityIdentifier)) - return newEntity + return Entity(nexus: self, id: newEntityIdentifier) } @discardableResult @@ -40,23 +39,29 @@ extension Nexus { } public func get(entity entityId: EntityIdentifier) -> Entity? { - return entityStorage.get(at: entityId.id) + guard let id = entityStorage.get(at: entityId.id) else { + return nil + } + return Entity(nexus: self, id: id) } public func get(unsafeEntity entityId: EntityIdentifier) -> Entity { - return entityStorage.get(unsafeAt: entityId.id) + return Entity(nexus: self, id: entityStorage.get(unsafeAt: entityId.id)) } @discardableResult public func destroy(entity: Entity) -> Bool { - let entityId: EntityIdentifier = entity.identifier + return self.destroy(entityId: entity.identifier) + } + @discardableResult + public func destroy(entityId: EntityIdentifier) -> Bool { guard entityStorage.remove(at: entityId.id) != nil else { delegate?.nexusNonFatalError("EntityRemove failure: no entity \(entityId) to remove") return false } - removeAllChildren(from: entity) + removeAllChildren(from: entityId) if removeAll(componentes: entityId) { update(familyMembership: entityId) diff --git a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift b/Sources/FirebladeECS/Nexus+FamilyUpdate.swift index 9b92e72..cd7d78d 100644 --- a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift +++ b/Sources/FirebladeECS/Nexus+FamilyUpdate.swift @@ -19,8 +19,8 @@ extension Nexus { final func update(familyMembership traits: FamilyTraitSet) { // FIXME: iterating all entities is costly for many entities var iter = entityStorage.makeIterator() - while let entity = iter.next() { - update(membership: traits, for: entity.identifier) + while let entityId = iter.next() { + update(membership: traits, for: entityId) } } diff --git a/Sources/FirebladeECS/Nexus+SceneGraph.swift b/Sources/FirebladeECS/Nexus+SceneGraph.swift index 24e8497..4203f4e 100644 --- a/Sources/FirebladeECS/Nexus+SceneGraph.swift +++ b/Sources/FirebladeECS/Nexus+SceneGraph.swift @@ -35,8 +35,12 @@ extension Nexus { } public final func removeAllChildren(from parent: Entity) { - childrenByParentEntity[parent.identifier]?.forEach { removeChild($0, from: parent.identifier) } - return childrenByParentEntity[parent.identifier] = nil + self.removeAllChildren(from: parent.identifier) + } + + public final func removeAllChildren(from parentId: EntityIdentifier) { + childrenByParentEntity[parentId]?.forEach { removeChild($0, from: parentId) } + return childrenByParentEntity[parentId] = nil } public final func numChildren(for entity: Entity) -> Int { diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index 5fdf2fb..bbffe12 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -6,9 +6,12 @@ // public final class Nexus { + /// Static version string. + public static let version: String = "1.0.0" + /// Main entity storage. /// Entities are tightly packed by EntityIdentifier. - @usableFromInline final var entityStorage: UnorderedSparseSet + @usableFromInline final var entityStorage: UnorderedSparseSet /// Entity ids that are currently not used. @usableFromInline final var freeEntities: [EntityIdentifier] @@ -33,13 +36,27 @@ public final class Nexus { public final weak var delegate: NexusEventDelegate? - public init() { - entityStorage = UnorderedSparseSet() - componentsByType = [:] - componentIdsByEntity = [:] - freeEntities = [] - familyMembersByTraits = [:] - childrenByParentEntity = [:] + public convenience init() { + self.init(entityStorage: UnorderedSparseSet(), + componentsByType: [:], + componentsByEntity: [:], + freeEntities: [], + familyMembersByTraits: [:], + childrenByParentEntity: [:]) + } + + internal init(entityStorage: UnorderedSparseSet, + componentsByType: [ComponentIdentifier: UnorderedSparseSet], + componentsByEntity: [EntityIdentifier: Set], + freeEntities: [EntityIdentifier], + familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet], + childrenByParentEntity: [EntityIdentifier: Set]) { + self.entityStorage = entityStorage + self.componentsByType = componentsByType + self.componentIdsByEntity = componentsByEntity + self.freeEntities = freeEntities + self.familyMembersByTraits = familyMembersByTraits + self.childrenByParentEntity = childrenByParentEntity } deinit { @@ -47,25 +64,36 @@ public final class Nexus { } public final func clear() { - var iter = entityStorage.makeIterator() - while let entity = iter.next() { - destroy(entity: entity) - } - + entityStorage.forEach { destroy(entityId: $0) } entityStorage.removeAll() freeEntities.removeAll() - - assert(entityStorage.isEmpty) - assert(componentsByType.values.reduce(0) { $0 + $1.count } == 0) - assert(componentIdsByEntity.values.reduce(0) { $0 + $1.count } == 0) - assert(freeEntities.isEmpty) - assert(familyMembersByTraits.values.reduce(0) { $0 + $1.count } == 0) - componentsByType.removeAll() componentIdsByEntity.removeAll() familyMembersByTraits.removeAll() childrenByParentEntity.removeAll() } + + public static var knownUniqueComponentTypes: Set { + return Set(componentDecoderMap.keys) + } + + internal static var componentDecoderMap: [ComponentIdentifier: (Decoder) throws -> Component] = [:] + + /// Register a component type uniquely with the Nexus implementation. + /// - Parameters: + /// - componentType: The component meta type. + /// - identifier: The unique identifier. + internal static func register(component componentType: C.Type, using identifier: ComponentIdentifier) where C: Component { + precondition(componentDecoderMap[identifier] == nil, "Component type collision: \(identifier) already in use.") + componentDecoderMap[identifier] = { try C(from: $0) } + } +} + +// MARK: - Errors +extension Nexus { + public enum Error: Swift.Error { + case versionMismatch(required: String, provided: String) + } } // MARK: - Equatable diff --git a/Tests/FirebladeECSPerformanceTests/Base.swift b/Tests/FirebladeECSPerformanceTests/Base.swift index 3b5834d..c9bbc33 100644 --- a/Tests/FirebladeECSPerformanceTests/Base.swift +++ b/Tests/FirebladeECSPerformanceTests/Base.swift @@ -7,9 +7,13 @@ import FirebladeECS -class EmptyComponent: Component { } +class EmptyComponent: Component { + static let identifier: ComponentIdentifier = .init(EmptyComponent.self) +} class Name: Component { + static let identifier: ComponentIdentifier = .init(Name.self) + var name: String init(name: String) { self.name = name @@ -17,6 +21,8 @@ class Name: Component { } class Position: Component { + static var identifier: ComponentIdentifier = .init(Position.self) + var x: Int var y: Int init(x: Int, y: Int) { @@ -26,6 +32,8 @@ class Position: Component { } class Velocity: Component { + static var identifier: ComponentIdentifier = .init(Velocity.self) + var a: Float init(a: Float) { self.a = a @@ -33,6 +41,8 @@ class Velocity: Component { } class Party: Component { + static var identifier: ComponentIdentifier = .init(Party.self) + var partying: Bool init(partying: Bool) { self.partying = partying @@ -40,6 +50,8 @@ class Party: Component { } class Color: Component { + static var identifier: ComponentIdentifier = .init(Color.self) + var r: UInt8 = 0 var g: UInt8 = 0 var b: UInt8 = 0 @@ -61,6 +73,8 @@ class ExampleSystem { } final class SingleGameState: SingleComponent { + static var identifier: ComponentIdentifier = .init(SingleGameState.self) + var shouldQuit: Bool = false var playerHealth: Int = 67 } diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index eb9e589..f7bc120 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -7,9 +7,12 @@ import FirebladeECS -class EmptyComponent: Component { } +class EmptyComponent: Component { + static let identifier: ComponentIdentifier = .init(EmptyComponent.self) +} class Name: Component { + static let identifier: ComponentIdentifier = .init(Name.self) var name: String init(name: String) { self.name = name @@ -17,6 +20,7 @@ class Name: Component { } class Position: Component { + static let identifier: ComponentIdentifier = .init(Position.self) var x: Int var y: Int init(x: Int, y: Int) { @@ -26,6 +30,7 @@ class Position: Component { } class Velocity: Component { + static let identifier: ComponentIdentifier = .init(Velocity.self) var a: Float init(a: Float) { self.a = a @@ -33,6 +38,7 @@ class Velocity: Component { } class Party: Component { + static let identifier: ComponentIdentifier = .init(Party.self) var partying: Bool init(partying: Bool) { self.partying = partying @@ -40,12 +46,14 @@ class Party: Component { } class Color: Component { + static let identifier: ComponentIdentifier = .init(Color.self) var r: UInt8 = 0 var g: UInt8 = 0 var b: UInt8 = 0 } class Index: Component { + static let identifier: ComponentIdentifier = .init(Index.self) var index: Int init(index: Int) { @@ -54,6 +62,7 @@ class Index: Component { } final class SingleGameState: SingleComponent { + static let identifier: ComponentIdentifier = .init(SingleGameState.self) var shouldQuit: Bool = false var playerHealth: Int = 67 } From 53621234d7c0896b5e9928ffb2273463b30d688b Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 22:38:49 +0200 Subject: [PATCH 10/24] Make Nexus fully codable --- Sources/FirebladeECS/FamilyTraitSet.swift | 3 + Sources/FirebladeECS/Nexus+Codable.swift | 107 ++++++++++++++++++ .../FirebladeECSTests/NexusCodingTests.swift | 76 +++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 Sources/FirebladeECS/Nexus+Codable.swift create mode 100644 Tests/FirebladeECSTests/NexusCodingTests.swift diff --git a/Sources/FirebladeECS/FamilyTraitSet.swift b/Sources/FirebladeECS/FamilyTraitSet.swift index 685a76c..eca5158 100644 --- a/Sources/FirebladeECS/FamilyTraitSet.swift +++ b/Sources/FirebladeECS/FamilyTraitSet.swift @@ -72,3 +72,6 @@ extension FamilyTraitSet: CustomDebugStringConvertible { return "" } } + +// MARK: - Codable +extension FamilyTraitSet: Codable { } diff --git a/Sources/FirebladeECS/Nexus+Codable.swift b/Sources/FirebladeECS/Nexus+Codable.swift new file mode 100644 index 0000000..8ed3e51 --- /dev/null +++ b/Sources/FirebladeECS/Nexus+Codable.swift @@ -0,0 +1,107 @@ +// +// Nexus+Codable.swift +// +// +// Created by Christian Treffs on 05.10.19. +// + +extension Nexus: Codable { + public enum CodingKeys: String, CodingKey { + case version + case entities + case freeEntities + case childrenByParent + case componentsByType + case familyMembersByTraits + case componentIdsByEntity + + public enum Components: String, CodingKey { + case componentId + case components + } + + public enum SparseSet: String, CodingKey { + case dense + case sparse + + public enum Entry: String, CodingKey { + case key + case element + } + } + } +} +// MARK: - Encodable +extension Nexus { + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(Nexus.version, forKey: .version) + try container.encode(entityStorage, forKey: .entities) + try container.encode(freeEntities, forKey: .freeEntities) + try container.encode(childrenByParentEntity, forKey: .childrenByParent) + + try container.encode(familyMembersByTraits, forKey: .familyMembersByTraits) + try container.encode(componentIdsByEntity, forKey: .componentIdsByEntity) + + // encode componentsByType + var contComponentsByType = container.nestedUnkeyedContainer(forKey: .componentsByType) + for (stableId, components) in componentsByType { + var contComponents = contComponentsByType.nestedContainer(keyedBy: CodingKeys.Components.self) + try contComponents.encode(stableId, forKey: .componentId) + var compSparseSet = contComponents.nestedContainer(keyedBy: CodingKeys.SparseSet.self, forKey: .components) + try compSparseSet.encode(components.sparse, forKey: .sparse) + var denseContainer = compSparseSet.nestedUnkeyedContainer(forKey: .dense) + try components.dense.forEach { (entry: UnorderedSparseSet.Entry) in + var entryContainer = denseContainer.nestedContainer(keyedBy: CodingKeys.SparseSet.Entry.self) + try entryContainer.encode(entry.key, forKey: .key) + try entry.element.encode(to: entryContainer.superEncoder(forKey: .element)) + } + } + } +} + +// MARK: - Decodable +extension Nexus { + public convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let version = try container.decode(String.self, forKey: .version) + if version != Nexus.version { + throw Error.versionMismatch(required: Nexus.version, provided: version) + } + + let entityStorage = try container.decode(UnorderedSparseSet.self, forKey: .entities) + let freeEntities = try container.decode([EntityIdentifier].self, forKey: .freeEntities) + let childrenByParentEntity = try container.decode([EntityIdentifier: Set].self, forKey: .childrenByParent) + let familyMembersByTraits = try container.decode([FamilyTraitSet: UnorderedSparseSet].self, forKey: .familyMembersByTraits) + let componentIdsByEntity = try container.decode([EntityIdentifier: Set].self, forKey: .componentIdsByEntity) + + // decode componentsByType + var contComponentsByType = try container.nestedUnkeyedContainer(forKey: .componentsByType) + var componentsByType = [ComponentIdentifier: UnorderedSparseSet]() + for _ in 0..<(contComponentsByType.count ?? 0) { + let contComponents = try contComponentsByType.nestedContainer(keyedBy: CodingKeys.Components.self) + let stableId = try contComponents.decode(ComponentIdentifier.self, forKey: .componentId) + + let compSparseSet = try contComponents.nestedContainer(keyedBy: CodingKeys.SparseSet.self, forKey: .components) + let sparse = try compSparseSet.decode([Int: Int].self, forKey: .sparse) + var denseContainer = try compSparseSet.nestedUnkeyedContainer(forKey: .dense) + var dense = ContiguousArray.Entry>() + for _ in 0..<(denseContainer.count ?? 0) { + let entryContainer = try denseContainer.nestedContainer(keyedBy: CodingKeys.SparseSet.Entry.self) + let key = try entryContainer.decode(UnorderedSparseSet.Key.self, forKey: .key) + let element: Component = try Nexus.componentDecoderMap[stableId]!(entryContainer.superDecoder(forKey: .element)) + dense.append(UnorderedSparseSet.Entry(key: key, element: element)) + } + + componentsByType[stableId] = UnorderedSparseSet(sparse: sparse, dense: dense) + } + + self.init(entityStorage: entityStorage, + componentsByType: componentsByType, + componentsByEntity: componentIdsByEntity, + freeEntities: freeEntities, + familyMembersByTraits: familyMembersByTraits, + childrenByParentEntity: childrenByParentEntity) + } +} diff --git a/Tests/FirebladeECSTests/NexusCodingTests.swift b/Tests/FirebladeECSTests/NexusCodingTests.swift new file mode 100644 index 0000000..af977ce --- /dev/null +++ b/Tests/FirebladeECSTests/NexusCodingTests.swift @@ -0,0 +1,76 @@ +// +// NexusCodingTests.swift +// FirebladeECSTests +// +// Created by Christian Treffs on 05.10.19. +// + +import XCTest +import FirebladeECS + +@available(OSX 10.12, *) +class NexusCodingTests: XCTestCase { + + lazy var tmpDir: URL = { + let dir = FileManager.default.temporaryDirectory.appendingPathComponent("NexusCodingTests", isDirectory: true) + try! FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + return dir + }() + + var nexus: Nexus! + + override func setUp() { + super.setUp() + nexus = Nexus() + + let e1 = nexus.createEntity(with: Position(x: 1, y: 2), Name(name: "Entity1")) + let e2 = nexus.createEntity(with: Velocity(a: 2.34), Name(name: "Entity1")) + let e3 = nexus.createEntity(with: Velocity(a: 5.67), Name(name: "Entity3")) + e1.addChild(e2) + e1.addChild(e3) + } + + override func tearDown() { + super.tearDown() + nexus = nil + } + + func testEncodeNexusJSON() { + let file = tmpDir.appendingPathComponent("Nexus.json") + let encoder = JSONEncoder() + var data: Data! + XCTAssertNoThrow(data = try encoder.encode(nexus)) + XCTAssertNoThrow(try data.write(to: file, options: .atomicWrite)) + print(file) + } + + func testDecodeNexusJSON() { + let file = tmpDir.appendingPathComponent("Nexus.json") + testEncodeNexusJSON() + + let decoder = JSONDecoder() + var data: Data! + XCTAssertNoThrow(data = try Data.init(contentsOf: file)) + + var restoredNexus: Nexus! + XCTAssertNoThrow(restoredNexus = try decoder.decode(Nexus.self, from: data)) + XCTAssertEqual(restoredNexus, nexus) + + let family1 = restoredNexus.family(requires: Name.self) + let family2 = restoredNexus.family(requires: Position.self) + let family3 = restoredNexus.family(requires: Velocity.self) + + family1.forEach { name in + print(name.name) + } + family2.forEach { pos in + print(pos.x, pos.y) + } + family3.forEach { vel in + print(vel.a) + } + + + } + +} From 43f14eb891d2d9647c12476a8bff103c1a056860 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Sat, 5 Oct 2019 22:59:59 +0200 Subject: [PATCH 11/24] Add tests --- Tests/FirebladeECSPerformanceTests/Base.swift | 12 ++-- .../TypeIdentifierPerformanceTests.swift | 12 ++-- .../XCTestManifests.swift | 13 ++++ .../ComponentIdentifierTests.swift | 7 +- .../FirebladeECSTests/NexusCodingTests.swift | 66 ++++++------------- Tests/FirebladeECSTests/XCTestManifests.swift | 21 ++++++ 6 files changed, 69 insertions(+), 62 deletions(-) diff --git a/Tests/FirebladeECSPerformanceTests/Base.swift b/Tests/FirebladeECSPerformanceTests/Base.swift index c9bbc33..6aacb43 100644 --- a/Tests/FirebladeECSPerformanceTests/Base.swift +++ b/Tests/FirebladeECSPerformanceTests/Base.swift @@ -13,7 +13,7 @@ class EmptyComponent: Component { class Name: Component { static let identifier: ComponentIdentifier = .init(Name.self) - + var name: String init(name: String) { self.name = name @@ -22,7 +22,7 @@ class Name: Component { class Position: Component { static var identifier: ComponentIdentifier = .init(Position.self) - + var x: Int var y: Int init(x: Int, y: Int) { @@ -33,7 +33,7 @@ class Position: Component { class Velocity: Component { static var identifier: ComponentIdentifier = .init(Velocity.self) - + var a: Float init(a: Float) { self.a = a @@ -42,7 +42,7 @@ class Velocity: Component { class Party: Component { static var identifier: ComponentIdentifier = .init(Party.self) - + var partying: Bool init(partying: Bool) { self.partying = partying @@ -51,7 +51,7 @@ class Party: Component { class Color: Component { static var identifier: ComponentIdentifier = .init(Color.self) - + var r: UInt8 = 0 var g: UInt8 = 0 var b: UInt8 = 0 @@ -74,7 +74,7 @@ class ExampleSystem { final class SingleGameState: SingleComponent { static var identifier: ComponentIdentifier = .init(SingleGameState.self) - + var shouldQuit: Bool = false var playerHealth: Int = 67 } diff --git a/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift b/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift index 022a4cc..26d1c07 100644 --- a/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift +++ b/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift @@ -1,6 +1,6 @@ // // TypeIdentifierPerformanceTests.swift -// +// // // Created by Christian Treffs on 05.10.19. // @@ -9,7 +9,7 @@ import XCTest final class TypeIdentifierPerformanceTests: XCTestCase { let maxIterations: Int = 100_000 - + // 0.056 sec func testPerformanceObjectIdentifier() { measure { @@ -24,7 +24,7 @@ final class TypeIdentifierPerformanceTests: XCTestCase { } } } - + // 1.451 sec func testPerformanceStringDescribing() { measure { @@ -39,7 +39,7 @@ final class TypeIdentifierPerformanceTests: XCTestCase { } } } - + // 1.587 sec func testPerformanceStringReflecting() { measure { @@ -54,7 +54,7 @@ final class TypeIdentifierPerformanceTests: XCTestCase { } } } - + // 2.817 sec func testPerformanceMirrorReflectingDescription() { measure { @@ -69,5 +69,5 @@ final class TypeIdentifierPerformanceTests: XCTestCase { } } } - + } diff --git a/Tests/FirebladeECSPerformanceTests/XCTestManifests.swift b/Tests/FirebladeECSPerformanceTests/XCTestManifests.swift index d5164ba..13a6948 100644 --- a/Tests/FirebladeECSPerformanceTests/XCTestManifests.swift +++ b/Tests/FirebladeECSPerformanceTests/XCTestManifests.swift @@ -21,6 +21,18 @@ extension HashingPerformanceTests { ] } +extension TypeIdentifierPerformanceTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__TypeIdentifierPerformanceTests = [ + ("testPerformanceMirrorReflectingDescription", testPerformanceMirrorReflectingDescription), + ("testPerformanceObjectIdentifier", testPerformanceObjectIdentifier), + ("testPerformanceStringDescribing", testPerformanceStringDescribing), + ("testPerformanceStringReflecting", testPerformanceStringReflecting) + ] +} + extension TypedFamilyPerformanceTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -46,6 +58,7 @@ public func __allTests() -> [XCTestCaseEntry] { return [ testCase(ComponentTests.__allTests__ComponentTests), testCase(HashingPerformanceTests.__allTests__HashingPerformanceTests), + testCase(TypeIdentifierPerformanceTests.__allTests__TypeIdentifierPerformanceTests), testCase(TypedFamilyPerformanceTests.__allTests__TypedFamilyPerformanceTests) ] } diff --git a/Tests/FirebladeECSTests/ComponentIdentifierTests.swift b/Tests/FirebladeECSTests/ComponentIdentifierTests.swift index 922d6cf..6cc5e61 100644 --- a/Tests/FirebladeECSTests/ComponentIdentifierTests.swift +++ b/Tests/FirebladeECSTests/ComponentIdentifierTests.swift @@ -1,23 +1,22 @@ // // ComponentIdentifierTests.swift -// +// // // Created by Christian Treffs on 05.10.19. // import XCTest final class ComponentIdentifierTests: XCTestCase { - + func testMirrorAsStableIdentifier() { let m = String(reflecting: Position.self) let identifier: String = m XCTAssertEqual(identifier, "FirebladeECSTests.Position") } - + func testStringDescribingAsStableIdentifier() { let s = String(describing: Position.self) let identifier: String = s XCTAssertEqual(identifier, "Position") } } - diff --git a/Tests/FirebladeECSTests/NexusCodingTests.swift b/Tests/FirebladeECSTests/NexusCodingTests.swift index af977ce..d122e19 100644 --- a/Tests/FirebladeECSTests/NexusCodingTests.swift +++ b/Tests/FirebladeECSTests/NexusCodingTests.swift @@ -8,69 +8,43 @@ import XCTest import FirebladeECS -@available(OSX 10.12, *) class NexusCodingTests: XCTestCase { - - lazy var tmpDir: URL = { - let dir = FileManager.default.temporaryDirectory.appendingPathComponent("NexusCodingTests", isDirectory: true) - try! FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) - return dir - }() - + var nexus: Nexus! - + override func setUp() { super.setUp() nexus = Nexus() - + + let e0 = nexus.createEntity(with: Position(x: 1, y: 2), Name(name: "Entity0")) let e1 = nexus.createEntity(with: Position(x: 1, y: 2), Name(name: "Entity1")) let e2 = nexus.createEntity(with: Velocity(a: 2.34), Name(name: "Entity1")) + nexus.destroy(entity: e0) let e3 = nexus.createEntity(with: Velocity(a: 5.67), Name(name: "Entity3")) e1.addChild(e2) e1.addChild(e3) + + _ = nexus.family(requiresAll: Position.self, Name.self, excludesAll: EmptyComponent.self) } - + override func tearDown() { super.tearDown() nexus = nil } - - func testEncodeNexusJSON() { - let file = tmpDir.appendingPathComponent("Nexus.json") + + func testEncodeDecodeNexusJSON() { let encoder = JSONEncoder() - var data: Data! - XCTAssertNoThrow(data = try encoder.encode(nexus)) - XCTAssertNoThrow(try data.write(to: file, options: .atomicWrite)) - print(file) - } - - func testDecodeNexusJSON() { - let file = tmpDir.appendingPathComponent("Nexus.json") - testEncodeNexusJSON() - let decoder = JSONDecoder() var data: Data! - XCTAssertNoThrow(data = try Data.init(contentsOf: file)) - - var restoredNexus: Nexus! - XCTAssertNoThrow(restoredNexus = try decoder.decode(Nexus.self, from: data)) - XCTAssertEqual(restoredNexus, nexus) - - let family1 = restoredNexus.family(requires: Name.self) - let family2 = restoredNexus.family(requires: Position.self) - let family3 = restoredNexus.family(requires: Velocity.self) - - family1.forEach { name in - print(name.name) - } - family2.forEach { pos in - print(pos.x, pos.y) - } - family3.forEach { vel in - print(vel.a) - } - - + XCTAssertNoThrow(data = try encoder.encode(nexus)) + XCTAssertGreaterThan(data.count, 0) + + var nexus2: Nexus! + XCTAssertNoThrow(nexus2 = try decoder.decode(Nexus.self, from: data)) + XCTAssertEqual(nexus2, nexus) + + var data2: Data! + XCTAssertNoThrow(data2 = try encoder.encode(nexus2)) + XCTAssertGreaterThan(data2.count, 0) } - } diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 2ae99ac..4fdd5ef 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -1,6 +1,16 @@ #if !canImport(ObjectiveC) import XCTest +extension ComponentIdentifierTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__ComponentIdentifierTests = [ + ("testMirrorAsStableIdentifier", testMirrorAsStableIdentifier), + ("testStringDescribingAsStableIdentifier", testStringDescribingAsStableIdentifier) + ] +} + extension ComponentTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -54,6 +64,15 @@ extension HashingTests { ] } +extension NexusCodingTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__NexusCodingTests = [ + ("testEncodeDecodeNexusJSON", testEncodeDecodeNexusJSON) + ] +} + extension NexusTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -124,11 +143,13 @@ extension SystemsTests { public func __allTests() -> [XCTestCaseEntry] { return [ + testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests), testCase(ComponentTests.__allTests__ComponentTests), testCase(EntityTests.__allTests__EntityTests), testCase(FamilyTests.__allTests__FamilyTests), testCase(FamilyTraitsTests.__allTests__FamilyTraitsTests), testCase(HashingTests.__allTests__HashingTests), + testCase(NexusCodingTests.__allTests__NexusCodingTests), testCase(NexusTests.__allTests__NexusTests), testCase(SceneGraphTests.__allTests__SceneGraphTests), testCase(SingleTests.__allTests__SingleTests), From c8dfbee47f2f55329a1a87c474e4de7b0c88ea8a Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Wed, 30 Oct 2019 09:21:08 +0100 Subject: [PATCH 12/24] Fix README links --- Makefile | 3 +++ README.md | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index b868cea..58164fa 100644 --- a/Makefile +++ b/Makefile @@ -32,3 +32,6 @@ genXcodeOpen: genXcode open *.xcodeproj precommit: lint genLinuxTests + +testReadme: + markdown-link-check -p -v ./README.md diff --git a/README.md b/README.md index 53db762..d3eb348 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Fireblade ECS (Entity-Component-System) +# Fireblade ECS (Entity-Component System) [![Build Status](https://travis-ci.com/fireblade-engine/ecs.svg?branch=master)](https://travis-ci.com/fireblade-engine/ecs) [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) [![swift version](https://img.shields.io/badge/swift-5.0+-brightgreen.svg)](https://swift.org/download) [![platforms](https://img.shields.io/badge/platforms-%20macOS%20|%20iOS%20|%20tvOS%20|%20watchOS-brightgreen.svg)](#) [![platforms](https://img.shields.io/badge/platforms-linux-brightgreen.svg)](#) -This is a **dependency free**, **lightweight**, **fast** and **easy to use** [Entity-Component-System](https://en.wikipedia.org/wiki/Entity–component–system) implementation in Swift. It is developed and maintained as part of the [Fireblade Game Engine project](https://github.com/fireblade-engine). +This is a **dependency free**, **lightweight**, **fast** and **easy to use** [Entity-Component System](https://en.wikipedia.org/wiki/Entity_component_system) implementation in Swift. It is developed and maintained as part of the [Fireblade Game Engine project](https://github.com/fireblade-engine). See the [Fireblade ECS Demo App](https://github.com/fireblade-engine/ecs-demo) to get started. @@ -219,16 +219,16 @@ nexus.family(requires: Position.self) See the [Fireblade ECS Demo App](https://github.com/fireblade-engine/ecs-demo) to get started. -## Versioning +## 🏷️ Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/fireblade-engine/ecs/tags). ## ✍️ Authors * [Christian Treffs](https://github.com/ctreffs) - *Initial work* * [Manuel Weidmann](https://github.com/vyo) -See also the list of [contributors](https://github.com/fireblade-engine/ecs/blob/master/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/fireblade-engine/ecs/contributors) who participated in this project. ## 🔏 License From 2dfe0162ceee5b70f33710795d785828e24a2818 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 19:44:01 +0200 Subject: [PATCH 13/24] Update Makefile --- Makefile | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 58164fa..aaa0d8b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,23 @@ +# Version 1.0.0 +UNAME_S := $(shell uname -s) + +# Lint lint: swiftlint autocorrect --format swiftlint lint --quiet +lintErrorOnly: + @swiftlint autocorrect --format --quiet + @swiftlint lint --quiet | grep error + +# Git +precommit: lint genLinuxTests + +submodule: + git submodule init + git submodule update --recursive + +# Tests genLinuxTests: swift test --generate-linuxmain swiftlint autocorrect --format --path Tests/ @@ -9,6 +25,21 @@ genLinuxTests: test: genLinuxTests swift test +# Package +latest: + swift package update + +resolve: + swift package resolve + +# Xcode +genXcode: + swift package generate-xcodeproj --enable-code-coverage --skip-extra-files + +genXcodeOpen: genXcode + open *.xcodeproj + +# Clean clean: swift package reset rm -rdf .swiftpm/xcode @@ -19,19 +50,7 @@ clean: cleanArtifacts: swift package clean -genXcode: - swift package generate-xcodeproj --enable-code-coverage --skip-extra-files - -latest: - swift package update - -resolve: - swift package resolve - -genXcodeOpen: genXcode - open *.xcodeproj - -precommit: lint genLinuxTests - +# Test links in README +# requires testReadme: - markdown-link-check -p -v ./README.md + markdown-link-check -p -v ./README.md \ No newline at end of file From e1ac76513c65296755f9ab803fa76ac7ff791973 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 19:50:46 +0200 Subject: [PATCH 14/24] Remove abandoned code --- Sources/FirebladeECS/Component+Access.swift | 37 ------ Sources/FirebladeECS/Identifiable.swift | 34 ------ .../FirebladeECS/ManagedContiguousArray.swift | 103 ----------------- Sources/FirebladeECS/Nexus+Codable.swift | 107 ------------------ Tests/FirebladeECSTests/AccessTests.swift | 41 ------- .../FirebladeECSTests/NexusCodingTests.swift | 50 -------- 6 files changed, 372 deletions(-) delete mode 100644 Sources/FirebladeECS/Component+Access.swift delete mode 100644 Sources/FirebladeECS/Identifiable.swift delete mode 100644 Sources/FirebladeECS/ManagedContiguousArray.swift delete mode 100644 Sources/FirebladeECS/Nexus+Codable.swift delete mode 100644 Tests/FirebladeECSTests/AccessTests.swift delete mode 100644 Tests/FirebladeECSTests/NexusCodingTests.swift diff --git a/Sources/FirebladeECS/Component+Access.swift b/Sources/FirebladeECS/Component+Access.swift deleted file mode 100644 index ebced3f..0000000 --- a/Sources/FirebladeECS/Component+Access.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// Component+Access.swift -// -// -// Created by Christian Treffs on 25.06.19. -// - -#if swift(>=5.1) -@dynamicMemberLookup -public struct ReadableOnly where Comp: Component { - @usableFromInline let component: Comp - - public init(_ component: Comp) { - self.component = component - } - - @inlinable - public subscript(dynamicMember keyPath: KeyPath) -> C { - return component[keyPath: keyPath] - } -} - -@dynamicMemberLookup -public struct Writable where Comp: Component { - @usableFromInline let component: Comp - - public init(_ component: Comp) { - self.component = component - } - - @inlinable - public subscript(dynamicMember keyPath: ReferenceWritableKeyPath) -> C { - nonmutating get { return component[keyPath: keyPath] } - nonmutating set { component[keyPath: keyPath] = newValue } - } -} -#endif diff --git a/Sources/FirebladeECS/Identifiable.swift b/Sources/FirebladeECS/Identifiable.swift deleted file mode 100644 index bd0f924..0000000 --- a/Sources/FirebladeECS/Identifiable.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// Identifiable.swift -// -// -// Created by Christian Treffs on 05.10.19. -// - -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2019 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// -#if swift(<5.1) -/// A class of types whose instances hold the value of an entity with stable identity. -public protocol Identifiable { - /// A type representing the stable identity of the entity associated with `self`. - associatedtype ID: Hashable - - /// The stable identity of the entity associated with `self`. - var id: ID { get } -} - -extension Identifiable where Self: AnyObject { - public var id: ObjectIdentifier { - return ObjectIdentifier(self) - } -} -#endif diff --git a/Sources/FirebladeECS/ManagedContiguousArray.swift b/Sources/FirebladeECS/ManagedContiguousArray.swift deleted file mode 100644 index a8b7938..0000000 --- a/Sources/FirebladeECS/ManagedContiguousArray.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// ManagedContiguousArray.swift -// FirebladeECS -// -// Created by Christian Treffs on 28.10.17. -// - -public struct ManagedContiguousArray { - public typealias Index = Int - - @usableFromInline let chunkSize: Int - @usableFromInline var size: Int = 0 - @usableFromInline var store: ContiguousArray = [] - - public init(minCount: Int = 4096) { - chunkSize = minCount - store = ContiguousArray(repeating: nil, count: minCount) - } - - @inline(__always) - public var count: Int { - return size - } - - @discardableResult - @inlinable - public mutating func insert(_ element: Element, at index: Int) -> Bool { - if needsToGrow(index) { - grow(to: index) - } - if store[index] == nil { - size += 1 - } - store[index] = element - return true - } - - @inlinable - public func contains(_ index: Index) -> Bool { - if store.count <= index { - return false - } - return store[index] != nil - } - - @inline(__always) - public func get(at index: Index) -> Element? { - return store[index] - } - - @inline(__always) - public func get(unsafeAt index: Index) -> Element { - return store[index].unsafelyUnwrapped - } - - @discardableResult - @inlinable - public mutating func remove(at index: Index) -> Bool { - if store[index] != nil { - size -= 1 - } - store[index] = nil - if size == 0 { - clear() - } - return true - } - - @inlinable - public mutating func clear(keepingCapacity: Bool = false) { - size = 0 - store.removeAll(keepingCapacity: keepingCapacity) - } - - @inlinable - func needsToGrow(_ index: Index) -> Bool { - return index > store.count - 1 - } - - @inlinable - mutating func grow(to index: Index) { - let newCapacity: Int = calculateCapacity(to: index) - let newCount: Int = newCapacity - store.count - store += ContiguousArray(repeating: nil, count: newCount) - } - - @inlinable - func calculateCapacity(to index: Index) -> Int { - let delta = Float(index) / Float(chunkSize) - let multiplier = Int(delta.rounded(.up)) + 1 - return multiplier * chunkSize - } -} - -// MARK: - Equatable -extension ManagedContiguousArray: Equatable where Element: Equatable { - public static func == (lhs: ManagedContiguousArray, rhs: ManagedContiguousArray) -> Bool { - return lhs.store == rhs.store - } -} - -// MARK: - Codable -extension ManagedContiguousArray: Codable where Element: Codable { } diff --git a/Sources/FirebladeECS/Nexus+Codable.swift b/Sources/FirebladeECS/Nexus+Codable.swift deleted file mode 100644 index 8ed3e51..0000000 --- a/Sources/FirebladeECS/Nexus+Codable.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// Nexus+Codable.swift -// -// -// Created by Christian Treffs on 05.10.19. -// - -extension Nexus: Codable { - public enum CodingKeys: String, CodingKey { - case version - case entities - case freeEntities - case childrenByParent - case componentsByType - case familyMembersByTraits - case componentIdsByEntity - - public enum Components: String, CodingKey { - case componentId - case components - } - - public enum SparseSet: String, CodingKey { - case dense - case sparse - - public enum Entry: String, CodingKey { - case key - case element - } - } - } -} -// MARK: - Encodable -extension Nexus { - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(Nexus.version, forKey: .version) - try container.encode(entityStorage, forKey: .entities) - try container.encode(freeEntities, forKey: .freeEntities) - try container.encode(childrenByParentEntity, forKey: .childrenByParent) - - try container.encode(familyMembersByTraits, forKey: .familyMembersByTraits) - try container.encode(componentIdsByEntity, forKey: .componentIdsByEntity) - - // encode componentsByType - var contComponentsByType = container.nestedUnkeyedContainer(forKey: .componentsByType) - for (stableId, components) in componentsByType { - var contComponents = contComponentsByType.nestedContainer(keyedBy: CodingKeys.Components.self) - try contComponents.encode(stableId, forKey: .componentId) - var compSparseSet = contComponents.nestedContainer(keyedBy: CodingKeys.SparseSet.self, forKey: .components) - try compSparseSet.encode(components.sparse, forKey: .sparse) - var denseContainer = compSparseSet.nestedUnkeyedContainer(forKey: .dense) - try components.dense.forEach { (entry: UnorderedSparseSet.Entry) in - var entryContainer = denseContainer.nestedContainer(keyedBy: CodingKeys.SparseSet.Entry.self) - try entryContainer.encode(entry.key, forKey: .key) - try entry.element.encode(to: entryContainer.superEncoder(forKey: .element)) - } - } - } -} - -// MARK: - Decodable -extension Nexus { - public convenience init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let version = try container.decode(String.self, forKey: .version) - if version != Nexus.version { - throw Error.versionMismatch(required: Nexus.version, provided: version) - } - - let entityStorage = try container.decode(UnorderedSparseSet.self, forKey: .entities) - let freeEntities = try container.decode([EntityIdentifier].self, forKey: .freeEntities) - let childrenByParentEntity = try container.decode([EntityIdentifier: Set].self, forKey: .childrenByParent) - let familyMembersByTraits = try container.decode([FamilyTraitSet: UnorderedSparseSet].self, forKey: .familyMembersByTraits) - let componentIdsByEntity = try container.decode([EntityIdentifier: Set].self, forKey: .componentIdsByEntity) - - // decode componentsByType - var contComponentsByType = try container.nestedUnkeyedContainer(forKey: .componentsByType) - var componentsByType = [ComponentIdentifier: UnorderedSparseSet]() - for _ in 0..<(contComponentsByType.count ?? 0) { - let contComponents = try contComponentsByType.nestedContainer(keyedBy: CodingKeys.Components.self) - let stableId = try contComponents.decode(ComponentIdentifier.self, forKey: .componentId) - - let compSparseSet = try contComponents.nestedContainer(keyedBy: CodingKeys.SparseSet.self, forKey: .components) - let sparse = try compSparseSet.decode([Int: Int].self, forKey: .sparse) - var denseContainer = try compSparseSet.nestedUnkeyedContainer(forKey: .dense) - var dense = ContiguousArray.Entry>() - for _ in 0..<(denseContainer.count ?? 0) { - let entryContainer = try denseContainer.nestedContainer(keyedBy: CodingKeys.SparseSet.Entry.self) - let key = try entryContainer.decode(UnorderedSparseSet.Key.self, forKey: .key) - let element: Component = try Nexus.componentDecoderMap[stableId]!(entryContainer.superDecoder(forKey: .element)) - dense.append(UnorderedSparseSet.Entry(key: key, element: element)) - } - - componentsByType[stableId] = UnorderedSparseSet(sparse: sparse, dense: dense) - } - - self.init(entityStorage: entityStorage, - componentsByType: componentsByType, - componentsByEntity: componentIdsByEntity, - freeEntities: freeEntities, - familyMembersByTraits: familyMembersByTraits, - childrenByParentEntity: childrenByParentEntity) - } -} diff --git a/Tests/FirebladeECSTests/AccessTests.swift b/Tests/FirebladeECSTests/AccessTests.swift deleted file mode 100644 index 0c51137..0000000 --- a/Tests/FirebladeECSTests/AccessTests.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// AccessTests.swift -// -// -// Created by Christian Treffs on 25.06.19. -// - -import FirebladeECS -import XCTest - -#if swift(>=5.1) -class AccessTests: XCTestCase { - func testReadOnly() { - let pos = Position(x: 1, y: 2) - - let readable = ReadableOnly(pos) - - XCTAssertEqual(readable.x, 1) - XCTAssertEqual(readable.y, 2) - - // readable.x = 3 // does not work and that's correct! - } - - func testWrite() { - let pos = Position(x: 1, y: 2) - - let writable = Writable(pos) - - XCTAssertEqual(writable.x, 1) - XCTAssertEqual(writable.y, 2) - - writable.x = 3 - - XCTAssertEqual(writable.x, 3) - XCTAssertEqual(pos.x, 3) - - XCTAssertEqual(writable.y, 2) - XCTAssertEqual(pos.y, 2) - } -} -#endif diff --git a/Tests/FirebladeECSTests/NexusCodingTests.swift b/Tests/FirebladeECSTests/NexusCodingTests.swift deleted file mode 100644 index d122e19..0000000 --- a/Tests/FirebladeECSTests/NexusCodingTests.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// NexusCodingTests.swift -// FirebladeECSTests -// -// Created by Christian Treffs on 05.10.19. -// - -import XCTest -import FirebladeECS - -class NexusCodingTests: XCTestCase { - - var nexus: Nexus! - - override func setUp() { - super.setUp() - nexus = Nexus() - - let e0 = nexus.createEntity(with: Position(x: 1, y: 2), Name(name: "Entity0")) - let e1 = nexus.createEntity(with: Position(x: 1, y: 2), Name(name: "Entity1")) - let e2 = nexus.createEntity(with: Velocity(a: 2.34), Name(name: "Entity1")) - nexus.destroy(entity: e0) - let e3 = nexus.createEntity(with: Velocity(a: 5.67), Name(name: "Entity3")) - e1.addChild(e2) - e1.addChild(e3) - - _ = nexus.family(requiresAll: Position.self, Name.self, excludesAll: EmptyComponent.self) - } - - override func tearDown() { - super.tearDown() - nexus = nil - } - - func testEncodeDecodeNexusJSON() { - let encoder = JSONEncoder() - let decoder = JSONDecoder() - var data: Data! - XCTAssertNoThrow(data = try encoder.encode(nexus)) - XCTAssertGreaterThan(data.count, 0) - - var nexus2: Nexus! - XCTAssertNoThrow(nexus2 = try decoder.decode(Nexus.self, from: data)) - XCTAssertEqual(nexus2, nexus) - - var data2: Data! - XCTAssertNoThrow(data2 = try encoder.encode(nexus2)) - XCTAssertGreaterThan(data2.count, 0) - } -} From 344b0465dd86ca1379e2de9a7b79a71ae02d9b75 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 19:52:25 +0200 Subject: [PATCH 15/24] Make tests run in release mode --- .../XCTestManifests.swift | 13 +++++++++---- Tests/FirebladeECSTests/EntityTests.swift | 4 +++- Tests/FirebladeECSTests/FamilyTests.swift | 2 ++ Tests/FirebladeECSTests/HashingTests.swift | 2 ++ Tests/FirebladeECSTests/NexusTests.swift | 4 +++- Tests/FirebladeECSTests/SingleTests.swift | 2 ++ Tests/FirebladeECSTests/SparseSetTests.swift | 2 ++ Tests/FirebladeECSTests/SystemsTests.swift | 2 ++ Tests/FirebladeECSTests/XCTestManifests.swift | 16 +++------------- 9 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Tests/FirebladeECSPerformanceTests/XCTestManifests.swift b/Tests/FirebladeECSPerformanceTests/XCTestManifests.swift index 13a6948..d6f0ba2 100644 --- a/Tests/FirebladeECSPerformanceTests/XCTestManifests.swift +++ b/Tests/FirebladeECSPerformanceTests/XCTestManifests.swift @@ -1,11 +1,11 @@ #if !canImport(ObjectiveC) import XCTest -extension ComponentTests { +extension ComponentIdentifierTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` // to regenerate. - static let __allTests__ComponentTests = [ + static let __allTests__ComponentIdentifierTests = [ ("testMeasureComponentIdentifier", testMeasureComponentIdentifier), ("testMeasureStaticComponentIdentifier", testMeasureStaticComponentIdentifier) ] @@ -16,8 +16,12 @@ extension HashingPerformanceTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__HashingPerformanceTests = [ + ("testMeasureBernsteinDjb2", testMeasureBernsteinDjb2), ("testMeasureCombineHash", testMeasureCombineHash), - ("testMeasureSetOfSetHash", testMeasureSetOfSetHash) + ("testMeasureSDBM", testMeasureSDBM), + ("testMeasureSetOfSetHash", testMeasureSetOfSetHash), + ("testMeasureSingerDjb2", testMeasureSingerDjb2), + ("testMeasureSwiftHasher", testMeasureSwiftHasher) ] } @@ -26,6 +30,7 @@ extension TypeIdentifierPerformanceTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__TypeIdentifierPerformanceTests = [ + ("testPerformanceHash", testPerformanceHash), ("testPerformanceMirrorReflectingDescription", testPerformanceMirrorReflectingDescription), ("testPerformanceObjectIdentifier", testPerformanceObjectIdentifier), ("testPerformanceStringDescribing", testPerformanceStringDescribing), @@ -56,7 +61,7 @@ extension TypedFamilyPerformanceTests { public func __allTests() -> [XCTestCaseEntry] { return [ - testCase(ComponentTests.__allTests__ComponentTests), + testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests), testCase(HashingPerformanceTests.__allTests__HashingPerformanceTests), testCase(TypeIdentifierPerformanceTests.__allTests__TypeIdentifierPerformanceTests), testCase(TypedFamilyPerformanceTests.__allTests__TypedFamilyPerformanceTests) diff --git a/Tests/FirebladeECSTests/EntityTests.swift b/Tests/FirebladeECSTests/EntityTests.swift index 9a6803d..d355997 100644 --- a/Tests/FirebladeECSTests/EntityTests.swift +++ b/Tests/FirebladeECSTests/EntityTests.swift @@ -5,7 +5,8 @@ // Created by Christian Treffs on 22.10.17. // -import FirebladeECS +#if DEBUG +@testable import FirebladeECS import XCTest class EntityTests: XCTestCase { @@ -27,3 +28,4 @@ class EntityTests: XCTestCase { XCTAssertTrue(EntityIdentifier(23) > EntityIdentifier(4)) } } +#endif diff --git a/Tests/FirebladeECSTests/FamilyTests.swift b/Tests/FirebladeECSTests/FamilyTests.swift index fb7fb50..a4f2c8c 100644 --- a/Tests/FirebladeECSTests/FamilyTests.swift +++ b/Tests/FirebladeECSTests/FamilyTests.swift @@ -5,6 +5,7 @@ // Created by Christian Treffs on 09.10.17. // +#if DEBUG @testable import FirebladeECS import XCTest @@ -186,3 +187,4 @@ class FamilyTests: XCTestCase { XCTAssertEqual(family.memberIds.count, count + (count / 2)) } } +#endif diff --git a/Tests/FirebladeECSTests/HashingTests.swift b/Tests/FirebladeECSTests/HashingTests.swift index 66ce2b3..49fc231 100644 --- a/Tests/FirebladeECSTests/HashingTests.swift +++ b/Tests/FirebladeECSTests/HashingTests.swift @@ -5,6 +5,7 @@ // Created by Christian Treffs on 16.10.17. // +#if DEBUG @testable import FirebladeECS import XCTest @@ -51,3 +52,4 @@ class HashingTests: XCTestCase { } } } +#endif diff --git a/Tests/FirebladeECSTests/NexusTests.swift b/Tests/FirebladeECSTests/NexusTests.swift index f334879..48af091 100644 --- a/Tests/FirebladeECSTests/NexusTests.swift +++ b/Tests/FirebladeECSTests/NexusTests.swift @@ -5,7 +5,8 @@ // Created by Christian Treffs on 09.10.17. // -import FirebladeECS +#if DEBUG +@testable import FirebladeECS import XCTest class NexusTests: XCTestCase { @@ -158,3 +159,4 @@ class NexusTests: XCTestCase { XCTAssert(pB.y != pA.y) } } +#endif diff --git a/Tests/FirebladeECSTests/SingleTests.swift b/Tests/FirebladeECSTests/SingleTests.swift index f0919c9..8341485 100644 --- a/Tests/FirebladeECSTests/SingleTests.swift +++ b/Tests/FirebladeECSTests/SingleTests.swift @@ -5,6 +5,7 @@ // Created by Christian Treffs on 13.02.19. // +#if DEBUG @testable import FirebladeECS import XCTest @@ -63,3 +64,4 @@ class SingleTests: XCTestCase { XCTAssertTrue(singleGame === single.component) } } +#endif diff --git a/Tests/FirebladeECSTests/SparseSetTests.swift b/Tests/FirebladeECSTests/SparseSetTests.swift index 4800376..033695e 100644 --- a/Tests/FirebladeECSTests/SparseSetTests.swift +++ b/Tests/FirebladeECSTests/SparseSetTests.swift @@ -5,6 +5,7 @@ // Created by Christian Treffs on 31.10.17. // +#if DEBUG @testable import FirebladeECS import XCTest @@ -539,3 +540,4 @@ class SparseSetTests: XCTestCase { XCTAssertEqual(mapped, ["C", "A", "B"]) } } +#endif diff --git a/Tests/FirebladeECSTests/SystemsTests.swift b/Tests/FirebladeECSTests/SystemsTests.swift index ac815bd..4c762ca 100644 --- a/Tests/FirebladeECSTests/SystemsTests.swift +++ b/Tests/FirebladeECSTests/SystemsTests.swift @@ -5,6 +5,7 @@ // Created by Christian Treffs on 10.05.18. // +#if DEBUG @testable import FirebladeECS import XCTest @@ -124,3 +125,4 @@ class SystemsTests: XCTestCase { } } } +#endif diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 4fdd5ef..cf77a10 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -25,8 +25,7 @@ extension EntityTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__EntityTests = [ - ("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex), - ("testEntityIdentifierComparison", testEntityIdentifierComparison) + ("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex) ] } @@ -60,16 +59,8 @@ extension HashingTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__HashingTests = [ - ("testCollisionsInCritialRange", testCollisionsInCritialRange) - ] -} - -extension NexusCodingTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__NexusCodingTests = [ - ("testEncodeDecodeNexusJSON", testEncodeDecodeNexusJSON) + ("testCollisionsInCritialRange", testCollisionsInCritialRange), + ("testStringHashes", testStringHashes) ] } @@ -149,7 +140,6 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(FamilyTests.__allTests__FamilyTests), testCase(FamilyTraitsTests.__allTests__FamilyTraitsTests), testCase(HashingTests.__allTests__HashingTests), - testCase(NexusCodingTests.__allTests__NexusCodingTests), testCase(NexusTests.__allTests__NexusTests), testCase(SceneGraphTests.__allTests__SceneGraphTests), testCase(SingleTests.__allTests__SingleTests), From 81cbb0f2b4212abdd28e4f522a7af3535bdf3ea6 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 19:53:38 +0200 Subject: [PATCH 16/24] Rework component identifier handling --- Sources/FirebladeECS/Component.swift | 11 +++-- .../FirebladeECS/ComponentIdentifier.swift | 22 ++++----- Sources/FirebladeECS/Hashing.swift | 49 +++++++++++++++++++ Sources/FirebladeECS/Nexus.swift | 46 ++++++----------- 4 files changed, 81 insertions(+), 47 deletions(-) diff --git a/Sources/FirebladeECS/Component.swift b/Sources/FirebladeECS/Component.swift index a6ea2df..23adf94 100644 --- a/Sources/FirebladeECS/Component.swift +++ b/Sources/FirebladeECS/Component.swift @@ -7,12 +7,17 @@ /// **Component** /// -/// A component represents the raw data for one aspect of an object. -public protocol Component: class, Codable { +/// A component represents the raw data for one aspect of an entity. +public protocol Component: AnyObject { + /// Unique, immutable identifier of this component type. static var identifier: ComponentIdentifier { get } + + /// Unique, immutable identifier of this component type. var identifier: ComponentIdentifier { get } } extension Component { - @inlinable public var identifier: ComponentIdentifier { return Self.identifier } + public static var identifier: ComponentIdentifier { ComponentIdentifier(Self.self) } + @inline(__always) + public var identifier: ComponentIdentifier { Self.identifier } } diff --git a/Sources/FirebladeECS/ComponentIdentifier.swift b/Sources/FirebladeECS/ComponentIdentifier.swift index 33e564a..ca39096 100644 --- a/Sources/FirebladeECS/ComponentIdentifier.swift +++ b/Sources/FirebladeECS/ComponentIdentifier.swift @@ -6,21 +6,19 @@ // /// Identifies a component by it's meta type -public struct ComponentIdentifier: Identifiable { - public let id: String +public struct ComponentIdentifier { + @usableFromInline + typealias Hash = Int + @usableFromInline + typealias StableId = UInt + @usableFromInline let hash: Hash +} - public init(_ componentType: T.Type) where T: Component { - defer { Nexus.register(component: T.self, using: self) } - - self.id = String(reflecting: componentType) +extension ComponentIdentifier { + @usableFromInline init(_ componentType: C.Type) where C: Component { + self.hash = Nexus.makeOrGetComponentId(componentType) } } extension ComponentIdentifier: Equatable { } extension ComponentIdentifier: Hashable { } -extension ComponentIdentifier: Codable { } -extension ComponentIdentifier: Comparable { - public static func < (lhs: ComponentIdentifier, rhs: ComponentIdentifier) -> Bool { - return lhs.id < rhs.id - } -} diff --git a/Sources/FirebladeECS/Hashing.swift b/Sources/FirebladeECS/Hashing.swift index 104e530..2bcbe00 100644 --- a/Sources/FirebladeECS/Hashing.swift +++ b/Sources/FirebladeECS/Hashing.swift @@ -83,3 +83,52 @@ extension EntityComponentHash { return EntityIdentifier(UInt32(truncatingIfNeeded: entityId)) } } + +// MARK: - string hashing +/// https://stackoverflow.com/a/52440609 +public enum StringHashing { + /// *Waren Singer djb2* + /// + /// + public static func singer_djb2(_ utf8String: String) -> UInt { + var hash = UInt(5381) + var iter = utf8String.unicodeScalars.makeIterator() + while let char = iter.next() { + hash = 127 * (hash & 0x00ffffffffffffff) + UInt(char.value) + } + return hash + } + + /// *Dan Bernstein djb2* + /// + /// This algorithm (k=33) was first reported by dan bernstein many years ago in comp.lang.c. + /// Another version of this algorithm (now favored by bernstein) uses xor: hash(i) = hash(i - 1) * 33 ^ str[i]; + /// The magic of number 33 (why it works better than many other constants, prime or not) has never been adequately explained. + /// + /// + public static func bernstein_djb2(_ string: String) -> UInt { + var hash: UInt = 5381 + var iter = string.unicodeScalars.makeIterator() + while let char = iter.next() { + hash = (hash << 5) &+ hash &+ UInt(char.value) + //hash = ((hash << 5) + hash) + UInt(c.value) + } + return hash + } + + /// *sdbm* + /// + /// This algorithm was created for sdbm (a public-domain reimplementation of ndbm) database library. + /// It was found to do well in scrambling bits, causing better distribution of the keys and fewer splits. + /// It also happens to be a good general hashing function with good distribution. + /// + /// + public static func sdbm(_ string: String) -> UInt { + var hash: UInt = 0 + var iter = string.unicodeScalars.makeIterator() + while let char = iter.next() { + hash = (UInt(char.value) &+ (hash << 6) &+ (hash << 16)) + } + return hash + } +} diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index bbffe12..4559e30 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -6,9 +6,6 @@ // public final class Nexus { - /// Static version string. - public static let version: String = "1.0.0" - /// Main entity storage. /// Entities are tightly packed by EntityIdentifier. @usableFromInline final var entityStorage: UnorderedSparseSet @@ -74,45 +71,30 @@ public final class Nexus { } public static var knownUniqueComponentTypes: Set { - return Set(componentDecoderMap.keys) - } - - internal static var componentDecoderMap: [ComponentIdentifier: (Decoder) throws -> Component] = [:] - - /// Register a component type uniquely with the Nexus implementation. - /// - Parameters: - /// - componentType: The component meta type. - /// - identifier: The unique identifier. - internal static func register(component componentType: C.Type, using identifier: ComponentIdentifier) where C: Component { - precondition(componentDecoderMap[identifier] == nil, "Component type collision: \(identifier) already in use.") - componentDecoderMap[identifier] = { try C(from: $0) } + Set(stableComponentIdentifierMap.keys.map { ComponentIdentifier(hash: $0) }) } } -// MARK: - Errors +// MARK: - centralized component identifier mapping extension Nexus { - public enum Error: Swift.Error { - case versionMismatch(required: String, provided: String) - } -} + internal static var stableComponentIdentifierMap: [ComponentIdentifier.Hash: ComponentIdentifier.StableId] = [:] -// MARK: - Equatable -extension Nexus: Equatable { - @inlinable - public static func == (lhs: Nexus, rhs: Nexus) -> Bool { - return lhs.entityStorage == rhs.entityStorage && - lhs.componentIdsByEntity == rhs.componentIdsByEntity && - lhs.freeEntities == rhs.freeEntities && - lhs.familyMembersByTraits == rhs.familyMembersByTraits && - lhs.componentsByType.keys == rhs.componentsByType.keys && - lhs.childrenByParentEntity == rhs.childrenByParentEntity - // NOTE: components are not equatable (yet) + internal static func makeOrGetComponentId(_ componentType: C.Type) -> ComponentIdentifier.Hash where C: Component { + /// object identifier hash (only stable during runtime) - arbitrary hash is ok. + let objIdHash = ObjectIdentifier(componentType).hashValue + // if we do not know this component type yet - we register a stable identifier generator for it. + if stableComponentIdentifierMap[objIdHash] == nil { + let string = String(describing: C.self) + let stableHash = StringHashing.singer_djb2(string) + stableComponentIdentifierMap[objIdHash] = stableHash + } + return objIdHash } } // MARK: - CustomDebugStringConvertible extension Nexus: CustomDebugStringConvertible { public var debugDescription: String { - return "" + "" } } From 6600cba5ae55c65f765f8d311d84f5c80df2502f Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 19:56:30 +0200 Subject: [PATCH 17/24] Rework conformances --- Sources/FirebladeECS/Entity.swift | 16 +++++++++++++--- Sources/FirebladeECS/EntityIdentifier.swift | 19 ++++++------------- Sources/FirebladeECS/FamilyTraitSet.swift | 7 ------- Sources/FirebladeECS/Single.swift | 8 +++++++- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Sources/FirebladeECS/Entity.swift b/Sources/FirebladeECS/Entity.swift index d7796ea..b85a1a9 100644 --- a/Sources/FirebladeECS/Entity.swift +++ b/Sources/FirebladeECS/Entity.swift @@ -127,10 +127,20 @@ public struct Entity { } } -// MARK: - Equatable extension Entity: Equatable { public static func == (lhs: Entity, rhs: Entity) -> Bool { - return lhs.nexus == rhs.nexus && - lhs.identifier == rhs.identifier + lhs.nexus === rhs.nexus && lhs.identifier == rhs.identifier + } +} + +extension Entity: CustomStringConvertible { + public var description: String { + "" + } +} + +extension Entity: CustomDebugStringConvertible { + public var debugDescription: String { + "" } } diff --git a/Sources/FirebladeECS/EntityIdentifier.swift b/Sources/FirebladeECS/EntityIdentifier.swift index 3bf35d1..d716270 100644 --- a/Sources/FirebladeECS/EntityIdentifier.swift +++ b/Sources/FirebladeECS/EntityIdentifier.swift @@ -5,24 +5,17 @@ // Created by Christian Treffs on 08.10.17. // -public struct EntityIdentifier: Identifiable { - /// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid. - public let id: Int +public struct EntityIdentifier { + static let invalid = EntityIdentifier(.max) - public init(_ uint32: UInt32) { + /// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid. + @usableFromInline let id: Int + + @usableFromInline init(_ uint32: UInt32) { self.id = Int(uint32) } } -extension EntityIdentifier { - public static let invalid = EntityIdentifier(.max) -} extension EntityIdentifier: Equatable { } extension EntityIdentifier: Hashable { } extension EntityIdentifier: Codable { } -extension EntityIdentifier: Comparable { - @inlinable - public static func < (lhs: EntityIdentifier, rhs: EntityIdentifier) -> Bool { - return lhs.id < rhs.id - } -} diff --git a/Sources/FirebladeECS/FamilyTraitSet.swift b/Sources/FirebladeECS/FamilyTraitSet.swift index eca5158..f530daa 100644 --- a/Sources/FirebladeECS/FamilyTraitSet.swift +++ b/Sources/FirebladeECS/FamilyTraitSet.swift @@ -23,7 +23,6 @@ public struct FamilyTraitSet { self.setHash = FirebladeECS.hash(combine: [requiresAll, excludesAll]) } - // MARK: - match @inlinable public func isMatch(components: Set) -> Bool { return hasAll(components) && hasNone(components) @@ -39,7 +38,6 @@ public struct FamilyTraitSet { return excludesAll.isDisjoint(with: components) } - // MARK: - valid @inlinable public static func isValid(requiresAll: Set, excludesAll: Set) -> Bool { return !requiresAll.isEmpty && @@ -47,14 +45,12 @@ public struct FamilyTraitSet { } } -// MARK: - Equatable extension FamilyTraitSet: Equatable { public static func == (lhs: FamilyTraitSet, rhs: FamilyTraitSet) -> Bool { return lhs.setHash == rhs.setHash } } -// MARK: - Hashable extension FamilyTraitSet: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(setHash) @@ -72,6 +68,3 @@ extension FamilyTraitSet: CustomDebugStringConvertible { return "" } } - -// MARK: - Codable -extension FamilyTraitSet: Codable { } diff --git a/Sources/FirebladeECS/Single.swift b/Sources/FirebladeECS/Single.swift index bc0a50e..ab09d1f 100644 --- a/Sources/FirebladeECS/Single.swift +++ b/Sources/FirebladeECS/Single.swift @@ -29,7 +29,13 @@ public struct Single where A: SingleComponent { public let entityId: EntityIdentifier } -extension Single: Equatable { } +extension Single: Equatable { + public static func == (lhs: Single, rhs: Single) -> Bool { + lhs.traits == rhs.traits && + lhs.entityId == rhs.entityId && + lhs.nexus === rhs.nexus + } +} extension Single where A: SingleComponent { @inlinable public var component: A { From a79df79bf9fa9b527d17bc762b9671cb6504302f Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 19:58:09 +0200 Subject: [PATCH 18/24] Delete empty families & fix event delegate --- Sources/FirebladeECS/Nexus+FamilyUpdate.swift | 12 +++++++----- Sources/FirebladeECS/NexusEvents.swift | 4 ---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift b/Sources/FirebladeECS/Nexus+FamilyUpdate.swift index cd7d78d..7424b27 100644 --- a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift +++ b/Sources/FirebladeECS/Nexus+FamilyUpdate.swift @@ -13,6 +13,7 @@ extension Nexus { } familyMembersByTraits[traits] = UnorderedSparseSet() + defer { delegate?.nexusEvent(FamilyCreated(family: traits)) } update(familyMembership: traits) } @@ -50,25 +51,26 @@ extension Nexus { case (true, false): add(entityWithId: entityId, toFamilyWithTraits: traits) delegate?.nexusEvent(FamilyMemberAdded(member: entityId, toFamily: traits)) - return case (false, true): remove(entityWithId: entityId, fromFamilyWithTraits: traits) delegate?.nexusEvent(FamilyMemberRemoved(member: entityId, from: traits)) - return default: - return + break } } final func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) { - precondition(familyMembersByTraits[traits] != nil) familyMembersByTraits[traits]!.insert(entityId, at: entityId.id) } final func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) { - precondition(familyMembersByTraits[traits] != nil) familyMembersByTraits[traits]!.remove(at: entityId.id) + if familyMembersByTraits[traits]!.isEmpty { + // delete family if no more entities are present + familyMembersByTraits[traits] = nil + delegate?.nexusEvent(FamilyDestroyed(family: traits)) + } } } diff --git a/Sources/FirebladeECS/NexusEvents.swift b/Sources/FirebladeECS/NexusEvents.swift index e686e6d..d82d1e1 100644 --- a/Sources/FirebladeECS/NexusEvents.swift +++ b/Sources/FirebladeECS/NexusEvents.swift @@ -20,10 +20,6 @@ public struct ComponentAdded: NexusEvent { public let toEntity: EntityIdentifier } -public struct ComponentUpdated: NexusEvent { - public let atEnity: EntityIdentifier -} - public struct ComponentRemoved: NexusEvent { public let component: ComponentIdentifier public let from: EntityIdentifier From 66f5bfc0c6dbc1d2d4cabe2cd8472df4f3dd859a Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 20:00:19 +0200 Subject: [PATCH 19/24] Lint --- Sources/FirebladeECS/Entity+Component.swift | 4 ++-- Sources/FirebladeECS/Entity.swift | 20 +++++++++--------- Sources/FirebladeECS/Family.swift | 21 +++++++++---------- Sources/FirebladeECS/Family1.swift | 6 +++--- Sources/FirebladeECS/Family2.swift | 2 +- Sources/FirebladeECS/Family3.swift | 2 +- Sources/FirebladeECS/Family4.swift | 2 +- Sources/FirebladeECS/Family5.swift | 2 +- Sources/FirebladeECS/FamilyTraitSet.swift | 14 ++++++------- Sources/FirebladeECS/Nexus+Component.swift | 10 ++++----- Sources/FirebladeECS/Nexus+Entity.swift | 8 +++---- Sources/FirebladeECS/Nexus+Family.swift | 10 ++++----- Sources/FirebladeECS/Nexus+SceneGraph.swift | 4 ++-- Sources/FirebladeECS/Single.swift | 8 +++---- Sources/FirebladeECS/UnorderedSparseSet.swift | 20 +++++++++--------- 15 files changed, 66 insertions(+), 67 deletions(-) diff --git a/Sources/FirebladeECS/Entity+Component.swift b/Sources/FirebladeECS/Entity+Component.swift index 991a491..900f785 100644 --- a/Sources/FirebladeECS/Entity+Component.swift +++ b/Sources/FirebladeECS/Entity+Component.swift @@ -8,12 +8,12 @@ extension Entity { @inlinable public func get() -> C? where C: Component { - return nexus.get(for: identifier) + nexus.get(for: identifier) } @inlinable public func get(component compType: A.Type = A.self) -> A? where A: Component { - return nexus.get(for: identifier) + nexus.get(for: identifier) } @inlinable diff --git a/Sources/FirebladeECS/Entity.swift b/Sources/FirebladeECS/Entity.swift index b85a1a9..29bc66f 100644 --- a/Sources/FirebladeECS/Entity.swift +++ b/Sources/FirebladeECS/Entity.swift @@ -24,24 +24,24 @@ public struct Entity { /// Returns the number of components for this entity. public var numComponents: Int { - return nexus.count(components: identifier) + nexus.count(components: identifier) } /// Checks if a component with given type is assigned to this entity. /// - Parameter type: the component type. public func has(_ type: C.Type) -> Bool where C: Component { - return has(type.identifier) + has(type.identifier) } /// Checks if a component with a given component identifier is assigned to this entity. /// - Parameter compId: the component identifier. public func has(_ compId: ComponentIdentifier) -> Bool { - return nexus.has(componentId: compId, entityId: identifier) + nexus.has(componentId: compId, entityId: identifier) } /// Checks if this entity has any components. public var hasComponents: Bool { - return nexus.count(components: identifier) > 0 + nexus.count(components: identifier) > 0 } /// Add one or more components to this entity. @@ -74,14 +74,14 @@ public struct Entity { /// - Parameter component: the component. @discardableResult public func remove(_ component: C) -> Entity where C: Component { - return remove(component.identifier) + remove(component.identifier) } /// Remove a component by type from this entity. /// - Parameter compType: the component type. @discardableResult public func remove(_ compType: C.Type) -> Entity where C: Component { - return remove(compType.identifier) + remove(compType.identifier) } /// Remove a component by id from this entity. @@ -106,24 +106,24 @@ public struct Entity { /// - Parameter entity: The child entity. @discardableResult public func addChild(_ entity: Entity) -> Bool { - return nexus.addChild(entity, to: self) + nexus.addChild(entity, to: self) } /// Remove entity as child. /// - Parameter entity: The child entity. @discardableResult public func removeChild(_ entity: Entity) -> Bool { - return nexus.removeChild(entity, from: self) + nexus.removeChild(entity, from: self) } /// Removes all children from this entity. public func removeAllChildren() { - return nexus.removeAllChildren(from: self) + nexus.removeAllChildren(from: self) } /// Returns the number of children for this entity. public var numChildren: Int { - return nexus.numChildren(for: self) + nexus.numChildren(for: self) } } diff --git a/Sources/FirebladeECS/Family.swift b/Sources/FirebladeECS/Family.swift index 6370bd5..dca646f 100644 --- a/Sources/FirebladeECS/Family.swift +++ b/Sources/FirebladeECS/Family.swift @@ -18,39 +18,38 @@ public struct Family where R: FamilyRequirementsManaging { } @inlinable public var memberIds: UnorderedSparseSet { - return nexus.members(withFamilyTraits: traits) + nexus.members(withFamilyTraits: traits) } @inlinable public var count: Int { - return memberIds.count + memberIds.count } @inlinable public var isEmpty: Bool { - return memberIds.isEmpty + memberIds.isEmpty } @inlinable public func canBecomeMember(_ entity: Entity) -> Bool { - return nexus.canBecomeMember(entity, in: traits) + nexus.canBecomeMember(entity, in: traits) } @inlinable public func isMember(_ entity: Entity) -> Bool { - return nexus.isMember(entity, in: traits) + nexus.isMember(entity, in: traits) } } -// MARK: - Equatable extension Family: Equatable { public static func == (lhs: Family, rhs: Family) -> Bool { - return lhs.nexus == rhs.nexus && + lhs.nexus === rhs.nexus && lhs.traits == rhs.traits } } extension Family: Sequence { __consuming public func makeIterator() -> ComponentsIterator { - return ComponentsIterator(family: self) + ComponentsIterator(family: self) } } @@ -82,7 +81,7 @@ extension Family.ComponentsIterator: LazySequenceProtocol { } // MARK: - entity iterator extension Family { @inlinable public var entities: EntityIterator { - return EntityIterator(family: self) + EntityIterator(family: self) } public struct EntityIterator: IteratorProtocol { @@ -108,7 +107,7 @@ extension Family.EntityIterator: LazySequenceProtocol { } // MARK: - entity component iterator extension Family { @inlinable public var entityAndComponents: EntityComponentIterator { - return EntityComponentIterator(family: self) + EntityComponentIterator(family: self) } public struct EntityComponentIterator: IteratorProtocol { @@ -136,7 +135,7 @@ extension Family.EntityComponentIterator: LazySequenceProtocol { } extension Family { @inlinable public func descendRelatives(from root: Entity) -> RelativesIterator { - return RelativesIterator(family: self, root: root) + RelativesIterator(family: self, root: root) } public struct RelativesIterator: IteratorProtocol { diff --git a/Sources/FirebladeECS/Family1.swift b/Sources/FirebladeECS/Family1.swift index 8e0f42c..b32409d 100644 --- a/Sources/FirebladeECS/Family1.swift +++ b/Sources/FirebladeECS/Family1.swift @@ -37,8 +37,8 @@ extension Nexus { requires componentA: A.Type, excludesAll excludedComponents: Component.Type... ) -> Family1 where A: Component { - return Family1(nexus: self, - requiresAll: componentA, - excludesAll: excludedComponents) + Family1(nexus: self, + requiresAll: componentA, + excludesAll: excludedComponents) } } diff --git a/Sources/FirebladeECS/Family2.swift b/Sources/FirebladeECS/Family2.swift index 8d33200..388002f 100644 --- a/Sources/FirebladeECS/Family2.swift +++ b/Sources/FirebladeECS/Family2.swift @@ -43,7 +43,7 @@ extension Nexus { _ componentB: B.Type, excludesAll excludedComponents: Component.Type... ) -> Family2 where A: Component, B: Component { - return Family2( + Family2( nexus: self, requiresAll: (componentA, componentB), excludesAll: excludedComponents diff --git a/Sources/FirebladeECS/Family3.swift b/Sources/FirebladeECS/Family3.swift index 74446e2..bc3b941 100644 --- a/Sources/FirebladeECS/Family3.swift +++ b/Sources/FirebladeECS/Family3.swift @@ -49,7 +49,7 @@ extension Nexus { _ componentC: C.Type, excludesAll excludedComponents: Component.Type... ) -> Family3 where A: Component, B: Component, C: Component { - return Family3( + Family3( nexus: self, requiresAll: (componentA, componentB, componentC), excludesAll: excludedComponents diff --git a/Sources/FirebladeECS/Family4.swift b/Sources/FirebladeECS/Family4.swift index 88050f7..f6ab1a6 100644 --- a/Sources/FirebladeECS/Family4.swift +++ b/Sources/FirebladeECS/Family4.swift @@ -55,7 +55,7 @@ extension Nexus { _ componentD: D.Type, excludesAll excludedComponents: Component.Type... ) -> Family4 where A: Component, B: Component, C: Component, D: Component { - return Family4( + Family4( nexus: self, requiresAll: (componentA, componentB, componentC, componentD), excludesAll: excludedComponents diff --git a/Sources/FirebladeECS/Family5.swift b/Sources/FirebladeECS/Family5.swift index 0d76248..02ee245 100644 --- a/Sources/FirebladeECS/Family5.swift +++ b/Sources/FirebladeECS/Family5.swift @@ -62,7 +62,7 @@ extension Nexus { _ componentE: E.Type, excludesAll excludedComponents: Component.Type... ) -> Family5 where A: Component, B: Component, C: Component, D: Component, E: Component { - return Family5( + Family5( nexus: self, requiresAll: (componentA, componentB, componentC, componentD, componentE), excludesAll: excludedComponents diff --git a/Sources/FirebladeECS/FamilyTraitSet.swift b/Sources/FirebladeECS/FamilyTraitSet.swift index f530daa..be195aa 100644 --- a/Sources/FirebladeECS/FamilyTraitSet.swift +++ b/Sources/FirebladeECS/FamilyTraitSet.swift @@ -25,29 +25,29 @@ public struct FamilyTraitSet { @inlinable public func isMatch(components: Set) -> Bool { - return hasAll(components) && hasNone(components) + hasAll(components) && hasNone(components) } @inlinable public func hasAll(_ components: Set) -> Bool { - return requiresAll.isSubset(of: components) + requiresAll.isSubset(of: components) } @inlinable public func hasNone(_ components: Set) -> Bool { - return excludesAll.isDisjoint(with: components) + excludesAll.isDisjoint(with: components) } @inlinable public static func isValid(requiresAll: Set, excludesAll: Set) -> Bool { - return !requiresAll.isEmpty && + !requiresAll.isEmpty && requiresAll.isDisjoint(with: excludesAll) } } extension FamilyTraitSet: Equatable { public static func == (lhs: FamilyTraitSet, rhs: FamilyTraitSet) -> Bool { - return lhs.setHash == rhs.setHash + lhs.setHash == rhs.setHash } } @@ -59,12 +59,12 @@ extension FamilyTraitSet: Hashable { extension FamilyTraitSet: CustomStringConvertible { @inlinable public var description: String { - return "" + "" } } extension FamilyTraitSet: CustomDebugStringConvertible { @inlinable public var debugDescription: String { - return "" + "" } } diff --git a/Sources/FirebladeECS/Nexus+Component.swift b/Sources/FirebladeECS/Nexus+Component.swift index 9f1f053..664f6bd 100644 --- a/Sources/FirebladeECS/Nexus+Component.swift +++ b/Sources/FirebladeECS/Nexus+Component.swift @@ -7,7 +7,7 @@ extension Nexus { public final var numComponents: Int { - return componentsByType.reduce(0) { $0 + $1.value.count } + componentsByType.reduce(0) { $0 + $1.value.count } } public final func has(componentId: ComponentIdentifier, entityId: EntityIdentifier) -> Bool { @@ -18,14 +18,14 @@ extension Nexus { } public final func count(components entityId: EntityIdentifier) -> Int { - return componentIdsByEntity[entityId]?.count ?? 0 + componentIdsByEntity[entityId]?.count ?? 0 } 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 + // 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)") @@ -76,13 +76,13 @@ extension Nexus { @inlinable public final func get(unsafeComponentFor entityId: EntityIdentifier) -> C where C: Component { let component: Component = get(unsafeComponent: C.identifier, for: entityId) - /// components are guaranteed to be reference tyes so unsafeDowncast is applicable here + // components are guaranteed to be reference tyes so unsafeDowncast is applicable here return unsafeDowncast(component, to: C.self) } @inlinable public final func get(components entityId: EntityIdentifier) -> Set? { - return componentIdsByEntity[entityId] + componentIdsByEntity[entityId] } @discardableResult diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index 3578bfe..6422b13 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -31,11 +31,11 @@ extension Nexus { /// Number of entities in nexus. public var numEntities: Int { - return entityStorage.count + entityStorage.count } public func exists(entity entityId: EntityIdentifier) -> Bool { - return entityStorage.contains(entityId.id) + entityStorage.contains(entityId.id) } public func get(entity entityId: EntityIdentifier) -> Entity? { @@ -46,12 +46,12 @@ extension Nexus { } public func get(unsafeEntity entityId: EntityIdentifier) -> Entity { - return Entity(nexus: self, id: entityStorage.get(unsafeAt: entityId.id)) + Entity(nexus: self, id: entityStorage.get(unsafeAt: entityId.id)) } @discardableResult public func destroy(entity: Entity) -> Bool { - return self.destroy(entityId: entity.identifier) + self.destroy(entityId: entity.identifier) } @discardableResult diff --git a/Sources/FirebladeECS/Nexus+Family.swift b/Sources/FirebladeECS/Nexus+Family.swift index 3895b4d..5b5f9a4 100644 --- a/Sources/FirebladeECS/Nexus+Family.swift +++ b/Sources/FirebladeECS/Nexus+Family.swift @@ -7,7 +7,7 @@ extension Nexus { public final var numFamilies: Int { - return familyMembersByTraits.keys.count + familyMembersByTraits.keys.count } public func canBecomeMember(_ entity: Entity, in traits: FamilyTraitSet) -> Bool { @@ -19,18 +19,18 @@ extension Nexus { } public func members(withFamilyTraits traits: FamilyTraitSet) -> UnorderedSparseSet { - return familyMembersByTraits[traits] ?? UnorderedSparseSet() + familyMembersByTraits[traits] ?? UnorderedSparseSet() } public func isMember(_ entity: Entity, in family: FamilyTraitSet) -> Bool { - return isMember(entity.identifier, in: family) + isMember(entity.identifier, in: family) } public func isMember(_ entityId: EntityIdentifier, in family: FamilyTraitSet) -> Bool { - return isMember(entity: entityId, inFamilyWithTraits: family) + isMember(entity: entityId, inFamilyWithTraits: family) } public func isMember(entity entityId: EntityIdentifier, inFamilyWithTraits traits: FamilyTraitSet) -> Bool { - return members(withFamilyTraits: traits).contains(entityId.id) + members(withFamilyTraits: traits).contains(entityId.id) } } diff --git a/Sources/FirebladeECS/Nexus+SceneGraph.swift b/Sources/FirebladeECS/Nexus+SceneGraph.swift index 4203f4e..d806b18 100644 --- a/Sources/FirebladeECS/Nexus+SceneGraph.swift +++ b/Sources/FirebladeECS/Nexus+SceneGraph.swift @@ -22,7 +22,7 @@ extension Nexus { } public final func removeChild(_ child: Entity, from parent: Entity) -> Bool { - return removeChild(child.identifier, from: parent.identifier) + removeChild(child.identifier, from: parent.identifier) } @discardableResult @@ -44,6 +44,6 @@ extension Nexus { } public final func numChildren(for entity: Entity) -> Int { - return childrenByParentEntity[entity.identifier]?.count ?? 0 + childrenByParentEntity[entity.identifier]?.count ?? 0 } } diff --git a/Sources/FirebladeECS/Single.swift b/Sources/FirebladeECS/Single.swift index ab09d1f..014a99a 100644 --- a/Sources/FirebladeECS/Single.swift +++ b/Sources/FirebladeECS/Single.swift @@ -39,13 +39,13 @@ extension Single: Equatable { extension Single where A: SingleComponent { @inlinable public var component: A { - /// Since we guarantee that the component will always be present by managing the complete lifecycle of the entity - /// and component assignment we may unsafelyUnwrap here. - /// Since components will allways be of reference type (class) we may use unsafeDowncast here for performance reasons. + // Since we guarantee that the component will always be present by managing the complete lifecycle of the entity + // and component assignment we may unsafelyUnwrap here. + // Since components will allways be of reference type (class) we may use unsafeDowncast here for performance reasons. return nexus.get(unsafeComponentFor: entityId) } public var entity: Entity { - return nexus.get(entity: entityId).unsafelyUnwrapped + nexus.get(entity: entityId).unsafelyUnwrapped } } diff --git a/Sources/FirebladeECS/UnorderedSparseSet.swift b/Sources/FirebladeECS/UnorderedSparseSet.swift index 41d5c2f..ad13dde 100644 --- a/Sources/FirebladeECS/UnorderedSparseSet.swift +++ b/Sources/FirebladeECS/UnorderedSparseSet.swift @@ -26,12 +26,12 @@ public struct UnorderedSparseSet { self.dense = dense } - public var count: Int { return dense.count } - public var isEmpty: Bool { return dense.isEmpty } + public var count: Int { dense.count } + public var isEmpty: Bool { dense.isEmpty } @inlinable public func contains(_ key: Key) -> Bool { - return find(at: key) != nil + find(at: key) != nil } /// Inset an element for a given key into the set in O(1). @@ -69,7 +69,7 @@ public struct UnorderedSparseSet { @inlinable public func get(unsafeAt key: Key) -> Element { - return find(at: key).unsafelyUnwrapped.1 + find(at: key).unsafelyUnwrapped.1 } /// Removes the element entry for given key in O(1). @@ -123,7 +123,7 @@ public struct UnorderedSparseSet { @inlinable public subscript(position: Index) -> Element { get { - return get(unsafeAt: position) + get(unsafeAt: position) } set(newValue) { @@ -132,18 +132,18 @@ public struct UnorderedSparseSet { } @inlinable public var first: Element? { - return dense.first?.element + dense.first?.element } @inlinable public var last: Element? { - return dense.last?.element + dense.last?.element } } // MARK: - Sequence extension UnorderedSparseSet: Sequence { public __consuming func makeIterator() -> ElementIterator { - return ElementIterator(self) + ElementIterator(self) } // MARK: - UnorderedSparseSetIterator @@ -155,7 +155,7 @@ extension UnorderedSparseSet: Sequence { } public mutating func next() -> Element? { - return iterator.next()?.element + iterator.next()?.element } } } @@ -164,7 +164,7 @@ extension UnorderedSparseSet: Sequence { extension UnorderedSparseSet.Entry: Equatable where Element: Equatable { } extension UnorderedSparseSet: Equatable where Element: Equatable { public static func == (lhs: UnorderedSparseSet, rhs: UnorderedSparseSet) -> Bool { - return lhs.dense == rhs.dense && lhs.sparse == rhs.sparse + lhs.dense == rhs.dense && lhs.sparse == rhs.sparse } } From 4adfd85fb1a99a6dd8cdfd40c4644373b5d68a06 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 20:00:34 +0200 Subject: [PATCH 20/24] Update test base --- Tests/FirebladeECSPerformanceTests/Base.swift | 9 +-------- Tests/FirebladeECSTests/Base.swift | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/Tests/FirebladeECSPerformanceTests/Base.swift b/Tests/FirebladeECSPerformanceTests/Base.swift index 6aacb43..b50f07b 100644 --- a/Tests/FirebladeECSPerformanceTests/Base.swift +++ b/Tests/FirebladeECSPerformanceTests/Base.swift @@ -8,11 +8,10 @@ import FirebladeECS class EmptyComponent: Component { - static let identifier: ComponentIdentifier = .init(EmptyComponent.self) + } class Name: Component { - static let identifier: ComponentIdentifier = .init(Name.self) var name: String init(name: String) { @@ -21,7 +20,6 @@ class Name: Component { } class Position: Component { - static var identifier: ComponentIdentifier = .init(Position.self) var x: Int var y: Int @@ -32,7 +30,6 @@ class Position: Component { } class Velocity: Component { - static var identifier: ComponentIdentifier = .init(Velocity.self) var a: Float init(a: Float) { @@ -41,7 +38,6 @@ class Velocity: Component { } class Party: Component { - static var identifier: ComponentIdentifier = .init(Party.self) var partying: Bool init(partying: Bool) { @@ -50,7 +46,6 @@ class Party: Component { } class Color: Component { - static var identifier: ComponentIdentifier = .init(Color.self) var r: UInt8 = 0 var g: UInt8 = 0 @@ -73,8 +68,6 @@ class ExampleSystem { } final class SingleGameState: SingleComponent { - static var identifier: ComponentIdentifier = .init(SingleGameState.self) - var shouldQuit: Bool = false var playerHealth: Int = 67 } diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index f7bc120..ded9b86 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -8,11 +8,10 @@ import FirebladeECS class EmptyComponent: Component { - static let identifier: ComponentIdentifier = .init(EmptyComponent.self) + } class Name: Component { - static let identifier: ComponentIdentifier = .init(Name.self) var name: String init(name: String) { self.name = name @@ -20,7 +19,6 @@ class Name: Component { } class Position: Component { - static let identifier: ComponentIdentifier = .init(Position.self) var x: Int var y: Int init(x: Int, y: Int) { @@ -30,7 +28,6 @@ class Position: Component { } class Velocity: Component { - static let identifier: ComponentIdentifier = .init(Velocity.self) var a: Float init(a: Float) { self.a = a @@ -38,7 +35,6 @@ class Velocity: Component { } class Party: Component { - static let identifier: ComponentIdentifier = .init(Party.self) var partying: Bool init(partying: Bool) { self.partying = partying @@ -46,14 +42,12 @@ class Party: Component { } class Color: Component { - static let identifier: ComponentIdentifier = .init(Color.self) var r: UInt8 = 0 var g: UInt8 = 0 var b: UInt8 = 0 } class Index: Component { - static let identifier: ComponentIdentifier = .init(Index.self) var index: Int init(index: Int) { @@ -62,7 +56,6 @@ class Index: Component { } final class SingleGameState: SingleComponent { - static let identifier: ComponentIdentifier = .init(SingleGameState.self) var shouldQuit: Bool = false var playerHealth: Int = 67 } From 6effdbb836987f01218040d635d20dddd8222476 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 20:01:00 +0200 Subject: [PATCH 21/24] Extend performance tests --- .../ComponentPerformanceTests.swift | 11 ++-- .../HashingPerformanceTests.swift | 65 +++++++++++++++++++ .../TypeIdentifierPerformanceTests.swift | 29 +++++++-- .../TypedFamilyPerformanceTests.swift | 18 +++++ 4 files changed, 115 insertions(+), 8 deletions(-) diff --git a/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift b/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift index 14721d5..8dfebc8 100644 --- a/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift +++ b/Tests/FirebladeECSPerformanceTests/ComponentPerformanceTests.swift @@ -1,5 +1,5 @@ // -// ComponentPerformanceTests.swift +// ComponentIdentifierTests.swift // FirebladeECSPerformanceTests // // Created by Christian Treffs on 14.02.19. @@ -8,9 +8,11 @@ import FirebladeECS import XCTest -class ComponentTests: XCTestCase { +class ComponentIdentifierTests: XCTestCase { + + /// debug: 0.456 sec func testMeasureStaticComponentIdentifier() { - let number: Int = 10_000 + let number: Int = 1_000_000 measure { for _ in 0.. = Set([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812]) let b: Set = Set([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234]) @@ -23,6 +32,8 @@ class HashingPerformanceTests: XCTestCase { } } + /// release: 0.494 sec + /// debug: 1.026 sec func testMeasureSetOfSetHash() { let a: Set = Set([14_561_291, 26_451_562, 34_562_182, 488_972_556, 5_128_426_962, 68_211_812]) let b: Set = Set([1_083_838, 912_312, 83_333, 71_234_555, 4_343_234]) @@ -36,4 +47,58 @@ class HashingPerformanceTests: XCTestCase { } } } + + /// release: 0.098 sec + /// debug: 16.702 sec + func testMeasureBernsteinDjb2() throws { + try XCTSkipIf(isDebug) + let string = "The quick brown fox jumps over the lazy dog" + measure { + for _ in 0..<1_000_000 { + let hash = StringHashing.bernstein_djb2(string) + _ = hash + } + } + } + + /// release: 0.087 sec + /// debug: 2.613 sec + func testMeasureSingerDjb2() throws { + let string = "The quick brown fox jumps over the lazy dog" + measure { + for _ in 0..<1_000_000 { + let hash = StringHashing.singer_djb2(string) + _ = hash + } + } + } + + /// release: 0.088 sec + /// debug: 30.766 sec + func testMeasureSDBM() throws { + try XCTSkipIf(isDebug) + let string = "The quick brown fox jumps over the lazy dog" + measure { + for _ in 0..<1_000_000 { + let hash = StringHashing.sdbm(string) + _ = hash + } + } + } + + /// release: 0.036 sec + /// debug: 0.546 sec + func testMeasureSwiftHasher() throws { + try XCTSkipIf(isDebug) + let string = "The quick brown fox jumps over the lazy dog" + measure { + for _ in 0..<1_000_000 { + var hasher = Hasher() + hasher.combine(string) + let hash = hasher.finalize() + _ = hash + } + } + } + } diff --git a/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift b/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift index 26d1c07..49c895f 100644 --- a/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift +++ b/Tests/FirebladeECSPerformanceTests/TypeIdentifierPerformanceTests.swift @@ -5,12 +5,14 @@ // Created by Christian Treffs on 05.10.19. // +import FirebladeECS import XCTest final class TypeIdentifierPerformanceTests: XCTestCase { let maxIterations: Int = 100_000 - // 0.056 sec + // release: 0.000 sec + // debug: 0.051 sec func testPerformanceObjectIdentifier() { measure { for _ in 0.. Date: Thu, 30 Apr 2020 20:01:16 +0200 Subject: [PATCH 22/24] Update tests --- Tests/FirebladeECSTests/EntityTests.swift | 5 ----- Tests/FirebladeECSTests/FamilyTests.swift | 7 +++---- Tests/FirebladeECSTests/HashingTests.swift | 13 +++++++++++++ Tests/FirebladeECSTests/NexusTests.swift | 3 --- Tests/FirebladeECSTests/SingleTests.swift | 1 - 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Tests/FirebladeECSTests/EntityTests.swift b/Tests/FirebladeECSTests/EntityTests.swift index d355997..ab2ae46 100644 --- a/Tests/FirebladeECSTests/EntityTests.swift +++ b/Tests/FirebladeECSTests/EntityTests.swift @@ -22,10 +22,5 @@ class EntityTests: XCTestCase { XCTAssertEqual(max, EntityIdentifier.invalid) XCTAssertEqual(max.id, Int(UInt32.max)) } - - func testEntityIdentifierComparison() { - XCTAssertTrue(EntityIdentifier(1) < EntityIdentifier(2)) - XCTAssertTrue(EntityIdentifier(23) > EntityIdentifier(4)) - } } #endif diff --git a/Tests/FirebladeECSTests/FamilyTests.swift b/Tests/FirebladeECSTests/FamilyTests.swift index a4f2c8c..e80cd7e 100644 --- a/Tests/FirebladeECSTests/FamilyTests.swift +++ b/Tests/FirebladeECSTests/FamilyTests.swift @@ -32,7 +32,6 @@ class FamilyTests: XCTestCase { let family = nexus.family(requires: Position.self, excludesAll: Name.self) - XCTAssertEqual(family.nexus, self.nexus) XCTAssertTrue(family.nexus === self.nexus) XCTAssertEqual(nexus.numFamilies, 1) XCTAssertEqual(nexus.numComponents, 0) @@ -74,11 +73,11 @@ class FamilyTests: XCTestCase { XCTAssertEqual(nexus.numComponents, 1) XCTAssertEqual(nexus.numEntities, 1) entity.remove(Position.self) - XCTAssertEqual(nexus.numFamilies, 1) + XCTAssertEqual(nexus.numFamilies, 0) XCTAssertEqual(nexus.numComponents, 0) XCTAssertEqual(nexus.numEntities, 1) nexus.destroy(entity: entity) - XCTAssertEqual(nexus.numFamilies, 1) + XCTAssertEqual(nexus.numFamilies, 0) XCTAssertEqual(nexus.numComponents, 0) XCTAssertEqual(nexus.numEntities, 0) } @@ -133,7 +132,7 @@ class FamilyTests: XCTestCase { entity.remove(velocity) } - XCTAssertEqual(familyA.count, 10) + XCTAssertEqual(familyA.count, 0) XCTAssertEqual(familyB.count, 0) } diff --git a/Tests/FirebladeECSTests/HashingTests.swift b/Tests/FirebladeECSTests/HashingTests.swift index 49fc231..4aad5f0 100644 --- a/Tests/FirebladeECSTests/HashingTests.swift +++ b/Tests/FirebladeECSTests/HashingTests.swift @@ -51,5 +51,18 @@ class HashingTests: XCTestCase { XCTAssert(EntityComponentHash.decompose(h, with: entityId) == cH) } } + + func testStringHashes() throws { + let string = "EiMersaufEn1" + + XCTAssertEqual(StringHashing.bernstein_djb2(string), 13447802024599246090) + XCTAssertEqual(StringHashing.singer_djb2(string), 5428736256651916664) + XCTAssertEqual(StringHashing.sdbm(string), 15559770072020577201) + + XCTAssertEqual(StringHashing.bernstein_djb2("gamedev"), 229466792000542) + XCTAssertEqual(StringHashing.singer_djb2("gamedev"), 2867840411746895486) + XCTAssertEqual(StringHashing.sdbm("gamedev"), 2761443862055442870) + + } } #endif diff --git a/Tests/FirebladeECSTests/NexusTests.swift b/Tests/FirebladeECSTests/NexusTests.swift index 48af091..733d20d 100644 --- a/Tests/FirebladeECSTests/NexusTests.swift +++ b/Tests/FirebladeECSTests/NexusTests.swift @@ -34,9 +34,6 @@ class NexusTests: XCTestCase { XCTAssert(e1.identifier.id == 1) XCTAssert(nexus.numEntities == 2) - - //FIXME: XCTAssertNil(e0.name) - //FIXME: XCTAssertEqual(e1.name, "Entity 1") } func testEntityDestroy() { diff --git a/Tests/FirebladeECSTests/SingleTests.swift b/Tests/FirebladeECSTests/SingleTests.swift index 8341485..c485228 100644 --- a/Tests/FirebladeECSTests/SingleTests.swift +++ b/Tests/FirebladeECSTests/SingleTests.swift @@ -24,7 +24,6 @@ class SingleTests: XCTestCase { func testSingleCreation() { let single = nexus.single(SingleGameState.self) - XCTAssertEqual(single.nexus, self.nexus) XCTAssertTrue(single.nexus === self.nexus) XCTAssertEqual(single.traits.requiresAll.count, 1) XCTAssertEqual(single.traits.excludesAll.count, 0) From a070b907c299132199909a1f8014ff48b0e3212f Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 20:04:07 +0200 Subject: [PATCH 23/24] Lint --- Sources/FirebladeECS/ComponentIdentifier.swift | 4 +++- Sources/FirebladeECS/EntityIdentifier.swift | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/FirebladeECS/ComponentIdentifier.swift b/Sources/FirebladeECS/ComponentIdentifier.swift index ca39096..b3ef562 100644 --- a/Sources/FirebladeECS/ComponentIdentifier.swift +++ b/Sources/FirebladeECS/ComponentIdentifier.swift @@ -11,11 +11,13 @@ public struct ComponentIdentifier { typealias Hash = Int @usableFromInline typealias StableId = UInt + @usableFromInline let hash: Hash } extension ComponentIdentifier { - @usableFromInline init(_ componentType: C.Type) where C: Component { + @usableFromInline + init(_ componentType: C.Type) where C: Component { self.hash = Nexus.makeOrGetComponentId(componentType) } } diff --git a/Sources/FirebladeECS/EntityIdentifier.swift b/Sources/FirebladeECS/EntityIdentifier.swift index d716270..0ccbade 100644 --- a/Sources/FirebladeECS/EntityIdentifier.swift +++ b/Sources/FirebladeECS/EntityIdentifier.swift @@ -11,7 +11,8 @@ public struct EntityIdentifier { /// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid. @usableFromInline let id: Int - @usableFromInline init(_ uint32: UInt32) { + @usableFromInline + init(_ uint32: UInt32) { self.id = Int(uint32) } } From 7ebcd99ad765df98493627efa66a79c79b3fa886 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 30 Apr 2020 20:07:42 +0200 Subject: [PATCH 24/24] Update CI + cleanups --- .gitignore | 1 - .gitlab-ci.yml | 2 +- .swift-version | 2 +- .travis.yml | 12 +----------- README.md | 4 ++-- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 3572c28..58f4b72 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,6 @@ fastlane/test_output Gemfile* Icon Network Trash Folder -Package.resolved Packages playground.xcworkspace Temporary Items diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5770440..863f3c0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: swift:5.0 +image: swift:5.2.2 #before_script: #- eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" diff --git a/.swift-version b/.swift-version index 50e2274..ce7f2b4 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.0.3 +5.2.2 diff --git a/.travis.yml b/.travis.yml index c989ccd..d17894a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,14 +11,4 @@ script: - swift package reset - swift build - swift test -env: - - BADGE=linux - - BADGE=osx -# hack to get some OS-specific badges -# see: https://github.com/travis-ci/travis-ci/issues/9579 -matrix: - exclude: - - os: linux - env: BADGE=osx - - os: osx - env: BADGE=linux + \ No newline at end of file diff --git a/README.md b/README.md index d3eb348..584e821 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Fireblade ECS (Entity-Component System) [![Build Status](https://travis-ci.com/fireblade-engine/ecs.svg?branch=master)](https://travis-ci.com/fireblade-engine/ecs) [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) -[![swift version](https://img.shields.io/badge/swift-5.0+-brightgreen.svg)](https://swift.org/download) +[![swift version](https://img.shields.io/badge/swift-5+-brightgreen.svg)](https://swift.org/download) [![platforms](https://img.shields.io/badge/platforms-%20macOS%20|%20iOS%20|%20tvOS%20|%20watchOS-brightgreen.svg)](#) [![platforms](https://img.shields.io/badge/platforms-linux-brightgreen.svg)](#) @@ -21,7 +21,7 @@ These instructions will get you a copy of the project up and running on your loc ### 💻 Installing -Fireblade ECS is available for all platforms that support [Swift 5.0](https://swift.org/) and higher and the [Swift Package Manager (SPM)](https://github.com/apple/swift-package-manager). +Fireblade ECS is available for all platforms that support [Swift 5](https://swift.org/) and higher and the [Swift Package Manager (SPM)](https://github.com/apple/swift-package-manager). Extend the following lines in your `Package.swift` file or use it to create a new project.