diff --git a/Sources/FirebladeECS/EntityIdentifierGenerator.swift b/Sources/FirebladeECS/EntityIdentifierGenerator.swift index ef324a5..c371b10 100644 --- a/Sources/FirebladeECS/EntityIdentifierGenerator.swift +++ b/Sources/FirebladeECS/EntityIdentifierGenerator.swift @@ -17,7 +17,7 @@ public protocol EntityIdentifierGenerator { /// Entity ids provided should be passed to `nextId()` in last out order up until the collection is empty. /// The default is an empty collection. /// - Parameter initialEntityIds: The entity ids to start providing up until the collection is empty (in last out order). - init(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier + init(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection & MutableCollection, EntityIds.Element == EntityIdentifier /// Provides the next unused entity identifier. /// @@ -41,8 +41,19 @@ public struct DefaultEntityIdGenerator: EntityIdentifierGenerator { @usableFromInline var count: Int { stack.count } @usableFromInline - init(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier { - stack = initialEntityIds.map { $0.id } + init(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection & MutableCollection, EntityIds.Element == EntityIdentifier { + let initialInUse: [EntityIdentifier.Identifier] = initialEntityIds.map { $0.id } + let maxInUseValue = initialInUse.max() ?? 0 + let inUseSet = Set(initialInUse) // a set of all eIds in use + let allSet = Set(0...maxInUseValue) // all eIds from 0 to including maxInUseValue + let freeSet = allSet.subtracting(inUseSet) // all "holes" / unused / free eIds + let initialFree = Array(freeSet).sorted().reversed() // order them to provide them linear increasing after all initially used are provided. + stack = initialFree + initialInUse + } + + @usableFromInline + init() { + stack = [0] } @usableFromInline @@ -65,16 +76,16 @@ public struct DefaultEntityIdGenerator: EntityIdentifierGenerator { @usableFromInline var count: Int { storage.count } @inlinable - public init(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier { + public init(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection & MutableCollection, EntityIds.Element == EntityIdentifier { self.storage = Storage(startProviding: initialEntityIds) } @inlinable public init() { - self.init(startProviding: [EntityIdentifier(0)]) + self.storage = Storage() } - @inline(__always) + //@inline(__always) public func nextId() -> EntityIdentifier { storage.nextId() } diff --git a/Tests/FirebladeECSTests/EntityIdGenTests.swift b/Tests/FirebladeECSTests/EntityIdGenTests.swift new file mode 100644 index 0000000..bab14de --- /dev/null +++ b/Tests/FirebladeECSTests/EntityIdGenTests.swift @@ -0,0 +1,76 @@ +// +// EntityIdGenTests.swift +// +// +// Created by Christian Treffs on 21.08.20. +// + +import FirebladeECS +import XCTest + +final class EntityIdGenTests: XCTestCase { + var gen: EntityIdentifierGenerator! + + override func setUp() { + super.setUp() + gen = DefaultEntityIdGenerator() + } + + func testGeneratorDefaultInit() { + XCTAssertEqual(gen.nextId(), 0) + } + + func testLinearIncrement() { + for i in 0..<1_000_000 { + XCTAssertEqual(gen.nextId(), EntityIdentifier(EntityIdentifier.Identifier(i))) + } + } + + func testGenerateWithInitialIds() { + let initialIds: [EntityIdentifier] = [2, 4, 11, 3, 0, 304] + gen = DefaultEntityIdGenerator(startProviding: initialIds) + + let generatedIds: [EntityIdentifier] = (0.. [XCTestCaseEntry] { testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests), testCase(ComponentTests.__allTests__ComponentTests), testCase(EntityCreationTests.__allTests__EntityCreationTests), + testCase(EntityIdGenTests.__allTests__EntityIdGenTests), testCase(EntityTests.__allTests__EntityTests), testCase(Family1Tests.__allTests__Family1Tests), testCase(Family2Tests.__allTests__Family2Tests),