Merge branch 'feature/entity-identifier' into develop

This commit is contained in:
Christian Treffs 2020-07-15 21:34:28 +02:00
commit 391e16a374
No known key found for this signature in database
GPG Key ID: 49A4B4B460BE3ED4
8 changed files with 106 additions and 53 deletions

View File

@ -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
}
}

View File

@ -21,4 +21,3 @@ public struct EntityIdentifier {
extension EntityIdentifier: Equatable { }
extension EntityIdentifier: Hashable { }
extension EntityIdentifier: Codable { }

View File

@ -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))
}
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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() {

View File

@ -26,7 +26,10 @@ extension EntityTests {
// to regenerate.
static let __allTests__EntityTests = [
("testAllComponentsOfEntity", testAllComponentsOfEntity),
("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex)
("testEntityEquality", testEntityEquality),
("testEntityIdentifierAndIndex", testEntityIdentifierAndIndex),
("testEntityIdGenerator", testEntityIdGenerator),
("testRemoveAllComponentsFromEntity", testRemoveAllComponentsFromEntity)
]
}