Merge branch 'feature/entity-identifier' into develop
This commit is contained in:
commit
391e16a374
|
|
@ -18,7 +18,12 @@ public struct ComponentIdentifier {
|
|||
extension ComponentIdentifier {
|
||||
@usableFromInline
|
||||
init<C>(_ componentType: C.Type) where C: Component {
|
||||
self.hash = Nexus.makeOrGetComponentId(componentType)
|
||||
self.hash = Self.makeRuntimeHash(componentType)
|
||||
}
|
||||
|
||||
/// object identifier hash (only stable during runtime) - arbitrary hash is ok.
|
||||
internal static func makeRuntimeHash<C>(_ componentType: C.Type) -> Hash where C: Component {
|
||||
ObjectIdentifier(componentType).hashValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,4 +21,3 @@ public struct EntityIdentifier {
|
|||
|
||||
extension EntityIdentifier: Equatable { }
|
||||
extension EntityIdentifier: Hashable { }
|
||||
extension EntityIdentifier: Codable { }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// EntityIdentifierGenerator.swift
|
||||
//
|
||||
//
|
||||
// Created by Christian Treffs on 26.06.20.
|
||||
//
|
||||
|
||||
internal final class EntityIdentifierGenerator {
|
||||
private var stack: [UInt32]
|
||||
|
||||
var count: Int {
|
||||
stack.count
|
||||
}
|
||||
|
||||
convenience init() {
|
||||
self.init([EntityIdentifier(0)])
|
||||
}
|
||||
|
||||
init(_ entityIds: [EntityIdentifier]) {
|
||||
stack = entityIds.reversed().map { UInt32($0.id) }
|
||||
}
|
||||
|
||||
func nextId() -> EntityIdentifier {
|
||||
if stack.count == 1 {
|
||||
defer { stack[0] += 1 }
|
||||
return EntityIdentifier(stack[0])
|
||||
} else {
|
||||
return EntityIdentifier(stack.removeLast())
|
||||
}
|
||||
}
|
||||
|
||||
func freeId(_ entityId: EntityIdentifier) {
|
||||
stack.append(UInt32(entityId.id))
|
||||
}
|
||||
}
|
||||
|
|
@ -6,20 +6,12 @@
|
|||
//
|
||||
|
||||
extension Nexus {
|
||||
@inlinable
|
||||
internal func nextEntityId() -> EntityIdentifier {
|
||||
guard let nextReused: EntityIdentifier = freeEntities.popLast() else {
|
||||
return EntityIdentifier(UInt32(entityStorage.count))
|
||||
}
|
||||
return nextReused
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func createEntity() -> Entity {
|
||||
let newEntityIdentifier: EntityIdentifier = nextEntityId()
|
||||
entityStorage.insert(newEntityIdentifier, at: newEntityIdentifier.id)
|
||||
delegate?.nexusEvent(EntityCreated(entityId: newEntityIdentifier))
|
||||
return Entity(nexus: self, id: newEntityIdentifier)
|
||||
let entityId: EntityIdentifier = entityIdGenerator.nextId()
|
||||
entityStorage.insert(entityId, at: entityId.id)
|
||||
delegate?.nexusEvent(EntityCreated(entityId: entityId))
|
||||
return Entity(nexus: self, id: entityId)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
|
|
@ -67,7 +59,7 @@ extension Nexus {
|
|||
update(familyMembership: entityId)
|
||||
}
|
||||
|
||||
freeEntities.append(entityId)
|
||||
entityIdGenerator.freeId(entityId)
|
||||
|
||||
delegate?.nexusEvent(EntityDestroyed(entityId: entityId))
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public final class Nexus {
|
|||
@usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>
|
||||
|
||||
/// Entity ids that are currently not used.
|
||||
@usableFromInline final var freeEntities: [EntityIdentifier]
|
||||
let entityIdGenerator: EntityIdentifierGenerator
|
||||
|
||||
/// - Key: ComponentIdentifier aka component type.
|
||||
/// - Value: Array of component instances of same type (uniform).
|
||||
|
|
@ -37,7 +37,7 @@ public final class Nexus {
|
|||
self.init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>(),
|
||||
componentsByType: [:],
|
||||
componentsByEntity: [:],
|
||||
freeEntities: [],
|
||||
entityIdGenerator: EntityIdentifierGenerator(),
|
||||
familyMembersByTraits: [:],
|
||||
childrenByParentEntity: [:])
|
||||
}
|
||||
|
|
@ -45,15 +45,15 @@ public final class Nexus {
|
|||
internal init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>,
|
||||
componentsByType: [ComponentIdentifier: ManagedContiguousArray<Component>],
|
||||
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
||||
freeEntities: [EntityIdentifier],
|
||||
entityIdGenerator: EntityIdentifierGenerator,
|
||||
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>],
|
||||
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>]) {
|
||||
self.entityStorage = entityStorage
|
||||
self.componentsByType = componentsByType
|
||||
self.componentIdsByEntity = componentsByEntity
|
||||
self.freeEntities = freeEntities
|
||||
self.familyMembersByTraits = familyMembersByTraits
|
||||
self.childrenByParentEntity = childrenByParentEntity
|
||||
self.entityIdGenerator = entityIdGenerator
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -63,34 +63,11 @@ public final class Nexus {
|
|||
public final func clear() {
|
||||
entityStorage.forEach { destroy(entityId: $0) }
|
||||
entityStorage.removeAll()
|
||||
freeEntities.removeAll()
|
||||
componentsByType.removeAll()
|
||||
componentIdsByEntity.removeAll()
|
||||
familyMembersByTraits.removeAll()
|
||||
childrenByParentEntity.removeAll()
|
||||
}
|
||||
|
||||
@available(swift, deprecated: 0.12.0)
|
||||
public static var knownUniqueComponentTypes: Set<ComponentIdentifier> {
|
||||
Set<ComponentIdentifier>(stableComponentIdentifierMap.keys.map { ComponentIdentifier(hash: $0) })
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - centralized component identifier mapping
|
||||
extension Nexus {
|
||||
internal static var stableComponentIdentifierMap: [ComponentIdentifier.Hash: ComponentIdentifier.StableId] = [:]
|
||||
|
||||
internal static func makeOrGetComponentId<C>(_ 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
|
||||
|
|
|
|||
|
|
@ -31,14 +31,56 @@ class EntityTests: XCTestCase {
|
|||
|
||||
let entity = nexus.createEntity()
|
||||
entity.assign(pos)
|
||||
entity.assign(name)
|
||||
entity.assign(vel)
|
||||
entity.assign(name, vel)
|
||||
|
||||
let expectedComponents: [Component] = [pos, name, vel]
|
||||
let allComponents = entity.allComponents()
|
||||
|
||||
XCTAssertTrue(allComponents.elementsEqualUnordered(expectedComponents) { $0 === $1 })
|
||||
}
|
||||
|
||||
func testEntityEquality() {
|
||||
let nexus = Nexus()
|
||||
|
||||
let entityA = nexus.createEntity()
|
||||
let entityB = nexus.createEntity()
|
||||
|
||||
XCTAssertEqual(entityA, entityA)
|
||||
XCTAssertNotEqual(entityA, entityB)
|
||||
}
|
||||
|
||||
func testRemoveAllComponentsFromEntity() {
|
||||
let nexus = Nexus()
|
||||
|
||||
let entity = nexus.createEntity(with: Position(x: 1, y: 2), Name(name: "MyEntity"))
|
||||
XCTAssertEqual(entity.numComponents, 2)
|
||||
entity.removeAll()
|
||||
XCTAssertEqual(entity.numComponents, 0)
|
||||
}
|
||||
|
||||
func testEntityIdGenerator() {
|
||||
let generator = EntityIdentifierGenerator()
|
||||
|
||||
XCTAssertEqual(generator.count, 1)
|
||||
|
||||
for _ in 0..<100 {
|
||||
_ = generator.nextId()
|
||||
}
|
||||
|
||||
XCTAssertEqual(generator.count, 1)
|
||||
|
||||
for i in 10..<60 {
|
||||
generator.freeId(EntityIdentifier(UInt32(i)))
|
||||
}
|
||||
|
||||
XCTAssertEqual(generator.count, 51)
|
||||
|
||||
for _ in 0..<50 {
|
||||
_ = generator.nextId()
|
||||
}
|
||||
|
||||
XCTAssertEqual(generator.count, 1)
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class SystemsTests: XCTestCase {
|
|||
XCTAssertEqual(nexus.numEntities, 0)
|
||||
XCTAssertEqual(colorSystem.colors.memberIds.count, 0)
|
||||
XCTAssertEqual(positionSystem.positions.memberIds.count, 0)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, 0)
|
||||
|
||||
batchCreateEntities(count: num)
|
||||
|
|
@ -47,7 +47,7 @@ class SystemsTests: XCTestCase {
|
|||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||
|
||||
colorSystem.update()
|
||||
positionSystem.update()
|
||||
|
|
@ -56,7 +56,7 @@ class SystemsTests: XCTestCase {
|
|||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||
|
||||
batchCreateEntities(count: num)
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ class SystemsTests: XCTestCase {
|
|||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
|
||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
|
||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||
|
||||
colorSystem.update()
|
||||
positionSystem.update()
|
||||
|
|
@ -73,12 +73,12 @@ class SystemsTests: XCTestCase {
|
|||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
|
||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
|
||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||
|
||||
batchDestroyEntities(count: num)
|
||||
|
||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||
XCTAssertEqual(nexus.freeEntities.count, num)
|
||||
XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num)
|
||||
XCTAssertEqual(nexus.numEntities, num)
|
||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
||||
|
|
@ -90,7 +90,7 @@ class SystemsTests: XCTestCase {
|
|||
XCTAssertEqual(nexus.numEntities, num)
|
||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
||||
XCTAssertEqual(nexus.freeEntities.count, num)
|
||||
XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num)
|
||||
|
||||
batchCreateEntities(count: num)
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ class SystemsTests: XCTestCase {
|
|||
XCTAssertEqual(nexus.numEntities, num * 2)
|
||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
|
||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
|
||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
||||
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||
}
|
||||
|
||||
func createDefaultEntity() {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ extension EntityTests {
|
|||
// to regenerate.
|
||||
static let __allTests__EntityTests = [
|
||||
("testAllComponentsOfEntity", testAllComponentsOfEntity),
|
||||
("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex)
|
||||
("testEntityEquality", testEntityEquality),
|
||||
("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex),
|
||||
("testEntityIdGenerator", testEntityIdGenerator),
|
||||
("testRemoveAllComponentsFromEntity", testRemoveAllComponentsFromEntity)
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue