Merge branch 'feature/entity-identifier' into develop
This commit is contained in:
commit
391e16a374
|
|
@ -18,7 +18,12 @@ public struct ComponentIdentifier {
|
||||||
extension ComponentIdentifier {
|
extension ComponentIdentifier {
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
init<C>(_ componentType: C.Type) where C: Component {
|
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: Equatable { }
|
||||||
extension EntityIdentifier: Hashable { }
|
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 {
|
extension Nexus {
|
||||||
@inlinable
|
|
||||||
internal func nextEntityId() -> EntityIdentifier {
|
|
||||||
guard let nextReused: EntityIdentifier = freeEntities.popLast() else {
|
|
||||||
return EntityIdentifier(UInt32(entityStorage.count))
|
|
||||||
}
|
|
||||||
return nextReused
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func createEntity() -> Entity {
|
public func createEntity() -> Entity {
|
||||||
let newEntityIdentifier: EntityIdentifier = nextEntityId()
|
let entityId: EntityIdentifier = entityIdGenerator.nextId()
|
||||||
entityStorage.insert(newEntityIdentifier, at: newEntityIdentifier.id)
|
entityStorage.insert(entityId, at: entityId.id)
|
||||||
delegate?.nexusEvent(EntityCreated(entityId: newEntityIdentifier))
|
delegate?.nexusEvent(EntityCreated(entityId: entityId))
|
||||||
return Entity(nexus: self, id: newEntityIdentifier)
|
return Entity(nexus: self, id: entityId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
|
@ -67,7 +59,7 @@ extension Nexus {
|
||||||
update(familyMembership: entityId)
|
update(familyMembership: entityId)
|
||||||
}
|
}
|
||||||
|
|
||||||
freeEntities.append(entityId)
|
entityIdGenerator.freeId(entityId)
|
||||||
|
|
||||||
delegate?.nexusEvent(EntityDestroyed(entityId: entityId))
|
delegate?.nexusEvent(EntityDestroyed(entityId: entityId))
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ public final class Nexus {
|
||||||
@usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>
|
@usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>
|
||||||
|
|
||||||
/// Entity ids that are currently not used.
|
/// Entity ids that are currently not used.
|
||||||
@usableFromInline final var freeEntities: [EntityIdentifier]
|
let entityIdGenerator: EntityIdentifierGenerator
|
||||||
|
|
||||||
/// - Key: ComponentIdentifier aka component type.
|
/// - Key: ComponentIdentifier aka component type.
|
||||||
/// - Value: Array of component instances of same type (uniform).
|
/// - Value: Array of component instances of same type (uniform).
|
||||||
|
|
@ -37,7 +37,7 @@ public final class Nexus {
|
||||||
self.init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>(),
|
self.init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>(),
|
||||||
componentsByType: [:],
|
componentsByType: [:],
|
||||||
componentsByEntity: [:],
|
componentsByEntity: [:],
|
||||||
freeEntities: [],
|
entityIdGenerator: EntityIdentifierGenerator(),
|
||||||
familyMembersByTraits: [:],
|
familyMembersByTraits: [:],
|
||||||
childrenByParentEntity: [:])
|
childrenByParentEntity: [:])
|
||||||
}
|
}
|
||||||
|
|
@ -45,15 +45,15 @@ public final class Nexus {
|
||||||
internal init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>,
|
internal init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>,
|
||||||
componentsByType: [ComponentIdentifier: ManagedContiguousArray<Component>],
|
componentsByType: [ComponentIdentifier: ManagedContiguousArray<Component>],
|
||||||
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
||||||
freeEntities: [EntityIdentifier],
|
entityIdGenerator: EntityIdentifierGenerator,
|
||||||
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>],
|
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>],
|
||||||
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>]) {
|
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>]) {
|
||||||
self.entityStorage = entityStorage
|
self.entityStorage = entityStorage
|
||||||
self.componentsByType = componentsByType
|
self.componentsByType = componentsByType
|
||||||
self.componentIdsByEntity = componentsByEntity
|
self.componentIdsByEntity = componentsByEntity
|
||||||
self.freeEntities = freeEntities
|
|
||||||
self.familyMembersByTraits = familyMembersByTraits
|
self.familyMembersByTraits = familyMembersByTraits
|
||||||
self.childrenByParentEntity = childrenByParentEntity
|
self.childrenByParentEntity = childrenByParentEntity
|
||||||
|
self.entityIdGenerator = entityIdGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|
@ -63,34 +63,11 @@ public final class Nexus {
|
||||||
public final func clear() {
|
public final func clear() {
|
||||||
entityStorage.forEach { destroy(entityId: $0) }
|
entityStorage.forEach { destroy(entityId: $0) }
|
||||||
entityStorage.removeAll()
|
entityStorage.removeAll()
|
||||||
freeEntities.removeAll()
|
|
||||||
componentsByType.removeAll()
|
componentsByType.removeAll()
|
||||||
componentIdsByEntity.removeAll()
|
componentIdsByEntity.removeAll()
|
||||||
familyMembersByTraits.removeAll()
|
familyMembersByTraits.removeAll()
|
||||||
childrenByParentEntity.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
|
// MARK: - CustomDebugStringConvertible
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,56 @@ class EntityTests: XCTestCase {
|
||||||
|
|
||||||
let entity = nexus.createEntity()
|
let entity = nexus.createEntity()
|
||||||
entity.assign(pos)
|
entity.assign(pos)
|
||||||
entity.assign(name)
|
entity.assign(name, vel)
|
||||||
entity.assign(vel)
|
|
||||||
|
|
||||||
let expectedComponents: [Component] = [pos, name, vel]
|
let expectedComponents: [Component] = [pos, name, vel]
|
||||||
let allComponents = entity.allComponents()
|
let allComponents = entity.allComponents()
|
||||||
|
|
||||||
XCTAssertTrue(allComponents.elementsEqualUnordered(expectedComponents) { $0 === $1 })
|
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 {
|
extension Sequence {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class SystemsTests: XCTestCase {
|
||||||
XCTAssertEqual(nexus.numEntities, 0)
|
XCTAssertEqual(nexus.numEntities, 0)
|
||||||
XCTAssertEqual(colorSystem.colors.memberIds.count, 0)
|
XCTAssertEqual(colorSystem.colors.memberIds.count, 0)
|
||||||
XCTAssertEqual(positionSystem.positions.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)
|
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, 0)
|
||||||
|
|
||||||
batchCreateEntities(count: num)
|
batchCreateEntities(count: num)
|
||||||
|
|
@ -47,7 +47,7 @@ class SystemsTests: XCTestCase {
|
||||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
||||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
||||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||||
|
|
||||||
colorSystem.update()
|
colorSystem.update()
|
||||||
positionSystem.update()
|
positionSystem.update()
|
||||||
|
|
@ -56,7 +56,7 @@ class SystemsTests: XCTestCase {
|
||||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
||||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
||||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||||
|
|
||||||
batchCreateEntities(count: num)
|
batchCreateEntities(count: num)
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ class SystemsTests: XCTestCase {
|
||||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
|
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
|
||||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
|
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
|
||||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
|
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
|
||||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||||
|
|
||||||
colorSystem.update()
|
colorSystem.update()
|
||||||
positionSystem.update()
|
positionSystem.update()
|
||||||
|
|
@ -73,12 +73,12 @@ class SystemsTests: XCTestCase {
|
||||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
|
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num * 2)
|
||||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
|
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
|
||||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
|
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
|
||||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||||
|
|
||||||
batchDestroyEntities(count: num)
|
batchDestroyEntities(count: num)
|
||||||
|
|
||||||
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
XCTAssertEqual(nexus.familyMembersByTraits[posTraits]?.count, num)
|
||||||
XCTAssertEqual(nexus.freeEntities.count, num)
|
XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num)
|
||||||
XCTAssertEqual(nexus.numEntities, num)
|
XCTAssertEqual(nexus.numEntities, num)
|
||||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
||||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
||||||
|
|
@ -90,7 +90,7 @@ class SystemsTests: XCTestCase {
|
||||||
XCTAssertEqual(nexus.numEntities, num)
|
XCTAssertEqual(nexus.numEntities, num)
|
||||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
XCTAssertEqual(colorSystem.colors.memberIds.count, num)
|
||||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
XCTAssertEqual(positionSystem.positions.memberIds.count, num)
|
||||||
XCTAssertEqual(nexus.freeEntities.count, num)
|
XCTAssertEqual(nexus.entityIdGenerator.count, 1 + num)
|
||||||
|
|
||||||
batchCreateEntities(count: num)
|
batchCreateEntities(count: num)
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ class SystemsTests: XCTestCase {
|
||||||
XCTAssertEqual(nexus.numEntities, num * 2)
|
XCTAssertEqual(nexus.numEntities, num * 2)
|
||||||
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
|
XCTAssertEqual(colorSystem.colors.memberIds.count, num * 2)
|
||||||
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
|
XCTAssertEqual(positionSystem.positions.memberIds.count, num * 2)
|
||||||
XCTAssertEqual(nexus.freeEntities.count, 0)
|
XCTAssertEqual(nexus.entityIdGenerator.count, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultEntity() {
|
func createDefaultEntity() {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,10 @@ extension EntityTests {
|
||||||
// to regenerate.
|
// to regenerate.
|
||||||
static let __allTests__EntityTests = [
|
static let __allTests__EntityTests = [
|
||||||
("testAllComponentsOfEntity", testAllComponentsOfEntity),
|
("testAllComponentsOfEntity", testAllComponentsOfEntity),
|
||||||
("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex)
|
("testEntityEquality", testEntityEquality),
|
||||||
|
("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex),
|
||||||
|
("testEntityIdGenerator", testEntityIdGenerator),
|
||||||
|
("testRemoveAllComponentsFromEntity", testRemoveAllComponentsFromEntity)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue