From d30a3bec3b792416e9413685e50dbcc8b285abe4 Mon Sep 17 00:00:00 2001 From: Liam Don Date: Wed, 8 Jul 2020 23:31:30 -0700 Subject: [PATCH 1/5] Fix key-index in UnorderedSparseSet --- Sources/FirebladeECS/UnorderedSparseSet.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FirebladeECS/UnorderedSparseSet.swift b/Sources/FirebladeECS/UnorderedSparseSet.swift index ead3769..431c153 100644 --- a/Sources/FirebladeECS/UnorderedSparseSet.swift +++ b/Sources/FirebladeECS/UnorderedSparseSet.swift @@ -15,7 +15,7 @@ public struct UnorderedSparseSet { } @usableFromInline var dense: ContiguousArray - @usableFromInline var sparse: [Index: Key] + @usableFromInline var sparse: [Key: Index] public init() { self.init(sparse: [:], dense: []) From 63c09cf7fa7b5cbdddd53e8581e75fbfa9c2341b Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 9 Jul 2020 17:10:00 +0200 Subject: [PATCH 2/5] Refine UnorderedSparseSet --- Sources/FirebladeECS/UnorderedSparseSet.swift | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/Sources/FirebladeECS/UnorderedSparseSet.swift b/Sources/FirebladeECS/UnorderedSparseSet.swift index 431c153..74b3d61 100644 --- a/Sources/FirebladeECS/UnorderedSparseSet.swift +++ b/Sources/FirebladeECS/UnorderedSparseSet.swift @@ -5,23 +5,36 @@ // Created by Christian Treffs on 30.10.17. // -public struct UnorderedSparseSet { - public typealias Index = Int - public typealias Key = Int +/// An (unordered) sparse set. +/// +/// - `Element`: the element (instance) to store. +/// - `Key`: the unique, hashable datastructure to use as a key to retrieve +/// an element from the sparse set. +/// +/// See for a reference implementation. +public struct UnorderedSparseSet { + /// An index into the dense store. + public typealias DenseIndex = Int + + /// A sparse store holding indices into the dense mapped to key. + public typealias SparseStore = [Key: DenseIndex] + + /// A dense store holding all the entries. + public typealias DenseStore = ContiguousArray public struct Entry { public let key: Key public let element: Element } - @usableFromInline var dense: ContiguousArray - @usableFromInline var sparse: [Key: Index] + @usableFromInline var dense: DenseStore + @usableFromInline var sparse: SparseStore public init() { self.init(sparse: [:], dense: []) } - init(sparse: [Index: Key], dense: ContiguousArray) { + init(sparse: SparseStore, dense: DenseStore) { self.sparse = sparse self.dense = dense } @@ -120,8 +133,18 @@ public struct UnorderedSparseSet { return (denseIndex, entry.element) } + @inlinable public var first: Element? { + dense.first?.element + } + + @inlinable public var last: Element? { + dense.last?.element + } +} + +extension UnorderedSparseSet where Key == Int { @inlinable - public subscript(position: Index) -> Element { + public subscript(position: DenseIndex) -> Element { get { get(unsafeAt: position) } @@ -130,14 +153,6 @@ public struct UnorderedSparseSet { insert(newValue, at: position) } } - - @inlinable public var first: Element? { - dense.first?.element - } - - @inlinable public var last: Element? { - dense.last?.element - } } // MARK: - Sequence @@ -148,9 +163,9 @@ extension UnorderedSparseSet: Sequence { // MARK: - UnorderedSparseSetIterator public struct ElementIterator: IteratorProtocol { - public private(set) var iterator: IndexingIterator.Entry>> + public private(set) var iterator: IndexingIterator.Entry>> - public init(_ sparseSet: UnorderedSparseSet) { + public init(_ sparseSet: UnorderedSparseSet) { iterator = sparseSet.dense.makeIterator() } @@ -163,7 +178,7 @@ extension UnorderedSparseSet: Sequence { // MARK: - Equatable extension UnorderedSparseSet.Entry: Equatable where Element: Equatable { } extension UnorderedSparseSet: Equatable where Element: Equatable { - public static func == (lhs: UnorderedSparseSet, rhs: UnorderedSparseSet) -> Bool { + public static func == (lhs: UnorderedSparseSet, rhs: UnorderedSparseSet) -> Bool { lhs.dense == rhs.dense && lhs.sparse == rhs.sparse } } From 6875159593a30722781770f0d6d97ebb96ac9de7 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 9 Jul 2020 17:10:25 +0200 Subject: [PATCH 3/5] Fix and extend tests --- Tests/FirebladeECSTests/SparseSetTests.swift | 30 +++++++++++++++---- Tests/FirebladeECSTests/XCTestManifests.swift | 1 + 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Tests/FirebladeECSTests/SparseSetTests.swift b/Tests/FirebladeECSTests/SparseSetTests.swift index 4800376..f02f4ec 100644 --- a/Tests/FirebladeECSTests/SparseSetTests.swift +++ b/Tests/FirebladeECSTests/SparseSetTests.swift @@ -9,11 +9,11 @@ import XCTest class SparseSetTests: XCTestCase { - var set: UnorderedSparseSet! + var set: UnorderedSparseSet! override func setUp() { super.setUp() - set = UnorderedSparseSet() + set = UnorderedSparseSet() } override func tearDown() { @@ -387,7 +387,7 @@ class SparseSetTests: XCTestCase { func testSparseSetDoubleRemove() { class AClass { } - var 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() { - var characters = UnorderedSparseSet() + var characters = UnorderedSparseSet() characters.insert("H", at: 4) characters.insert("e", at: 13) @@ -497,7 +497,7 @@ class SparseSetTests: XCTestCase { } func testSubscript() { - var characters = UnorderedSparseSet() + var characters = UnorderedSparseSet() characters[4] = "H" characters[13] = "e" @@ -528,7 +528,7 @@ class SparseSetTests: XCTestCase { } func testStartEndIndex() { - var set = UnorderedSparseSet() + var set = UnorderedSparseSet() set.insert("C", at: 33) set.insert("A", at: 11) @@ -538,4 +538,22 @@ class SparseSetTests: XCTestCase { XCTAssertEqual(mapped, ["C", "A", "B"]) } + + func testAlternativeKey() { + + var set = UnorderedSparseSet() + + set.insert("A", at: "a") + set.insert("C", at: "c") + set.insert("B", at: "b") + + let mapped = set.dense.map { $0.element } + XCTAssertEqual(mapped, ["A", "C", "B"]) + let keyValues = set.sparse.sorted(by: { $0.value < $1.value }).map { ($0.key, $0.value) } + for (a, b) in zip(keyValues, [("a", 0), ("c", 1), ("b", 2)]) { + XCTAssertEqual(a.0, b.0) + XCTAssertEqual(a.1, b.1) + } + + } } diff --git a/Tests/FirebladeECSTests/XCTestManifests.swift b/Tests/FirebladeECSTests/XCTestManifests.swift index 9244a1c..ba4266a 100644 --- a/Tests/FirebladeECSTests/XCTestManifests.swift +++ b/Tests/FirebladeECSTests/XCTestManifests.swift @@ -109,6 +109,7 @@ extension SparseSetTests { // `swift test --generate-linuxmain` // to regenerate. static let __allTests__SparseSetTests = [ + ("testAlternativeKey", testAlternativeKey), ("testSparseSetAdd", testSparseSetAdd), ("testSparseSetAddAndReplace", testSparseSetAddAndReplace), ("testSparseSetClear", testSparseSetClear), From 1ad4d883f5219b9d37b2259d3391258e6f0dca29 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 9 Jul 2020 17:15:06 +0200 Subject: [PATCH 4/5] Type entity id --- Sources/FirebladeECS/EntityIdentifier.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/FirebladeECS/EntityIdentifier.swift b/Sources/FirebladeECS/EntityIdentifier.swift index 0ccbade..9eb7dc7 100644 --- a/Sources/FirebladeECS/EntityIdentifier.swift +++ b/Sources/FirebladeECS/EntityIdentifier.swift @@ -8,12 +8,14 @@ public struct EntityIdentifier { static let invalid = EntityIdentifier(.max) + public typealias Id = Int + /// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid. - @usableFromInline let id: Int + @usableFromInline let id: Id @usableFromInline init(_ uint32: UInt32) { - self.id = Int(uint32) + self.id = Id(uint32) } } From 4a995e90831d5516a34306f76c6c93f38223d440 Mon Sep 17 00:00:00 2001 From: Christian Treffs Date: Thu, 9 Jul 2020 17:15:49 +0200 Subject: [PATCH 5/5] Fix type requirements of UnorderedSparseSet --- Sources/FirebladeECS/Family.swift | 8 ++++---- Sources/FirebladeECS/Nexus+Family.swift | 4 ++-- Sources/FirebladeECS/Nexus+FamilyUpdate.swift | 2 +- Sources/FirebladeECS/Nexus.swift | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/FirebladeECS/Family.swift b/Sources/FirebladeECS/Family.swift index deac1a1..b9df771 100644 --- a/Sources/FirebladeECS/Family.swift +++ b/Sources/FirebladeECS/Family.swift @@ -17,7 +17,7 @@ public struct Family where R: FamilyRequirementsManaging { nexus.onFamilyInit(traits: traits) } - @inlinable public var memberIds: UnorderedSparseSet { + @inlinable public var memberIds: UnorderedSparseSet { nexus.members(withFamilyTraits: traits) } @@ -58,7 +58,7 @@ extension Family: LazySequenceProtocol { } // MARK: - components iterator extension Family { public struct ComponentsIterator: IteratorProtocol { - @usableFromInline var memberIdsIterator: UnorderedSparseSet.ElementIterator + @usableFromInline var memberIdsIterator: UnorderedSparseSet.ElementIterator @usableFromInline unowned let nexus: Nexus public init(family: Family) { @@ -85,7 +85,7 @@ extension Family { } public struct EntityIterator: IteratorProtocol { - @usableFromInline var memberIdsIterator: UnorderedSparseSet.ElementIterator + @usableFromInline var memberIdsIterator: UnorderedSparseSet.ElementIterator @usableFromInline unowned let nexus: Nexus public init(family: Family) { @@ -111,7 +111,7 @@ extension Family { } public struct EntityComponentIterator: IteratorProtocol { - @usableFromInline var memberIdsIterator: UnorderedSparseSet.ElementIterator + @usableFromInline var memberIdsIterator: UnorderedSparseSet.ElementIterator @usableFromInline unowned let nexus: Nexus public init(family: Family) { diff --git a/Sources/FirebladeECS/Nexus+Family.swift b/Sources/FirebladeECS/Nexus+Family.swift index 5b5f9a4..95f3b30 100644 --- a/Sources/FirebladeECS/Nexus+Family.swift +++ b/Sources/FirebladeECS/Nexus+Family.swift @@ -18,8 +18,8 @@ extension Nexus { return traits.isMatch(components: componentIds) } - public func members(withFamilyTraits traits: FamilyTraitSet) -> UnorderedSparseSet { - familyMembersByTraits[traits] ?? UnorderedSparseSet() + public func members(withFamilyTraits traits: FamilyTraitSet) -> UnorderedSparseSet { + familyMembersByTraits[traits] ?? UnorderedSparseSet() } public func isMember(_ entity: Entity, in family: FamilyTraitSet) -> Bool { diff --git a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift b/Sources/FirebladeECS/Nexus+FamilyUpdate.swift index 24c6c81..a669077 100644 --- a/Sources/FirebladeECS/Nexus+FamilyUpdate.swift +++ b/Sources/FirebladeECS/Nexus+FamilyUpdate.swift @@ -12,7 +12,7 @@ extension Nexus { return } - familyMembersByTraits[traits] = UnorderedSparseSet() + familyMembersByTraits[traits] = UnorderedSparseSet() update(familyMembership: traits) } diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index cbc630f..17fdf66 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -8,7 +8,7 @@ public final class Nexus { /// 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] @@ -29,12 +29,12 @@ public final class Nexus { /// - 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] + @usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet] public final weak var delegate: NexusEventDelegate? public convenience init() { - self.init(entityStorage: UnorderedSparseSet(), + self.init(entityStorage: UnorderedSparseSet(), componentsByType: [:], componentsByEntity: [:], freeEntities: [], @@ -42,11 +42,11 @@ public final class Nexus { childrenByParentEntity: [:]) } - internal init(entityStorage: UnorderedSparseSet, + internal init(entityStorage: UnorderedSparseSet, componentsByType: [ComponentIdentifier: ManagedContiguousArray], componentsByEntity: [EntityIdentifier: Set], freeEntities: [EntityIdentifier], - familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet], + familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet], childrenByParentEntity: [EntityIdentifier: Set]) { self.entityStorage = entityStorage self.componentsByType = componentsByType