Refactor EntityIdentifier
This commit is contained in:
parent
89f0a92557
commit
b62cf7464b
|
|
@ -11,7 +11,7 @@
|
|||
/// It only consists of a unique id (EntityIdentifier).
|
||||
/// Components can be assigned to an entity to give it behavior or functionality.
|
||||
/// An entity creates the relationship between all it's assigned components.
|
||||
public struct Entity: UniqueEntityIdentifiable {
|
||||
public struct Entity {
|
||||
@usableFromInline unowned let nexus: Nexus
|
||||
|
||||
/// The unique entity identifier.
|
||||
|
|
@ -36,7 +36,7 @@ public struct Entity: UniqueEntityIdentifiable {
|
|||
/// Checks if a component with a given component identifier is assigned to this entity.
|
||||
/// - Parameter compId: the component identifier.
|
||||
public func has(_ compId: ComponentIdentifier) -> Bool {
|
||||
return nexus.has(componentId: compId, entityIdx: identifier.index)
|
||||
return nexus.has(componentId: compId, entityId: identifier)
|
||||
}
|
||||
|
||||
/// Checks if this entity has any components.
|
||||
|
|
|
|||
|
|
@ -5,26 +5,27 @@
|
|||
// Created by Christian Treffs on 08.10.17.
|
||||
//
|
||||
|
||||
public typealias EntityIdentifier = UInt32 // provides 4294967295 unique identifiers
|
||||
public typealias EntityIndex = Int
|
||||
public struct EntityIdentifier {
|
||||
public static let invalid = EntityIdentifier(.max)
|
||||
|
||||
public extension EntityIdentifier {
|
||||
static let invalid = EntityIdentifier.max
|
||||
/// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid.
|
||||
public let index: Int
|
||||
|
||||
private init() {
|
||||
self = .invalid
|
||||
}
|
||||
|
||||
public init(_ uint32: UInt32) {
|
||||
self.index = Int(uint32)
|
||||
}
|
||||
}
|
||||
|
||||
public extension EntityIdentifier {
|
||||
var index: EntityIndex {
|
||||
return EntityIndex(self)
|
||||
}
|
||||
}
|
||||
extension EntityIdentifier: Equatable { }
|
||||
|
||||
public extension EntityIndex {
|
||||
var identifier: EntityIdentifier {
|
||||
return EntityIdentifier(truncatingIfNeeded: self)
|
||||
}
|
||||
}
|
||||
extension EntityIdentifier: Hashable { }
|
||||
|
||||
// MARK: Unique Entity Identifiable
|
||||
public protocol UniqueEntityIdentifiable {
|
||||
var identifier: EntityIdentifier { get }
|
||||
extension EntityIdentifier: Comparable {
|
||||
@inlinable public static func < (lhs: EntityIdentifier, rhs: EntityIdentifier) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,20 +60,20 @@ public func hash<H: Sequence>(combine hashables: H) -> Int where H.Element: Hash
|
|||
// MARK: - entity component hash
|
||||
extension EntityComponentHash {
|
||||
internal static func compose(entityId: EntityIdentifier, componentTypeHash: ComponentTypeHash) -> EntityComponentHash {
|
||||
let entityIdSwapped = UInt(entityId).byteSwapped // needs to be 64 bit
|
||||
let entityIdSwapped = UInt(entityId.index).byteSwapped // needs to be 64 bit
|
||||
let componentTypeHashUInt = UInt(bitPattern: componentTypeHash)
|
||||
let hashUInt: UInt = componentTypeHashUInt ^ entityIdSwapped
|
||||
return Int(bitPattern: hashUInt)
|
||||
}
|
||||
|
||||
internal static func decompose(_ hash: EntityComponentHash, with entityId: EntityIdentifier) -> ComponentTypeHash {
|
||||
let entityIdSwapped = UInt(entityId).byteSwapped
|
||||
let entityIdSwapped = UInt(entityId.index).byteSwapped
|
||||
let entityIdSwappedInt = Int(bitPattern: entityIdSwapped)
|
||||
return hash ^ entityIdSwappedInt
|
||||
}
|
||||
|
||||
internal static func decompose(_ hash: EntityComponentHash, with componentTypeHash: ComponentTypeHash) -> EntityIdentifier {
|
||||
let entityId: Int = (hash ^ componentTypeHash).byteSwapped
|
||||
return EntityIdentifier(truncatingIfNeeded: entityId)
|
||||
return EntityIdentifier(UInt32(truncatingIfNeeded: entityId))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,26 +10,25 @@ public extension Nexus {
|
|||
return componentsByType.reduce(0) { $0 + $1.value.count }
|
||||
}
|
||||
|
||||
final func has(componentId: ComponentIdentifier, entityIdx: EntityIndex) -> Bool {
|
||||
final func has(componentId: ComponentIdentifier, entityId: EntityIdentifier) -> Bool {
|
||||
guard let uniforms: UniformComponents = componentsByType[componentId] else {
|
||||
return false
|
||||
}
|
||||
return uniforms.contains(entityIdx)
|
||||
return uniforms.contains(entityId.index)
|
||||
}
|
||||
|
||||
final func count(components entityId: EntityIdentifier) -> Int {
|
||||
return componentIdsByEntity[entityId.index]?.count ?? 0
|
||||
return componentIdsByEntity[entityId]?.count ?? 0
|
||||
}
|
||||
|
||||
final func assign(component: Component, to entity: Entity) {
|
||||
let componentId: ComponentIdentifier = component.identifier
|
||||
let entityIdx: EntityIndex = entity.identifier.index
|
||||
let entityId: EntityIdentifier = entity.identifier
|
||||
|
||||
/// test if component is already assigned
|
||||
guard !has(componentId: componentId, entityIdx: entityIdx) else {
|
||||
report("ComponentAdd collision: \(entityIdx) already has a component \(component)")
|
||||
assertionFailure("ComponentAdd collision: \(entityIdx) already has a component \(component)")
|
||||
guard !has(componentId: componentId, entityId: entityId) else {
|
||||
report("ComponentAdd collision: \(entityId) already has a component \(component)")
|
||||
assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -37,13 +36,13 @@ public extension Nexus {
|
|||
if componentsByType[componentId] == nil {
|
||||
componentsByType[componentId] = UniformComponents()
|
||||
}
|
||||
componentsByType[componentId]?.insert(component, at: entityIdx)
|
||||
componentsByType[componentId]?.insert(component, at: entityId.index)
|
||||
|
||||
// assigns the component id to the entity id
|
||||
if componentIdsByEntity[entityIdx] == nil {
|
||||
componentIdsByEntity[entityIdx] = ComponentSet()
|
||||
if componentIdsByEntity[entityId] == nil {
|
||||
componentIdsByEntity[entityId] = ComponentSet()
|
||||
}
|
||||
componentIdsByEntity[entityIdx]?.insert(componentId) //, at: componentId.hashValue)
|
||||
componentIdsByEntity[entityId]?.insert(componentId) //, at: componentId.hashValue)
|
||||
|
||||
update(familyMembership: entityId)
|
||||
|
||||
|
|
@ -68,7 +67,7 @@ public extension Nexus {
|
|||
|
||||
final func get<C>(for entityId: EntityIdentifier) -> C? where C: Component {
|
||||
let componentId: ComponentIdentifier = C.identifier
|
||||
return get(componentId: componentId, entityIdx: entityId.index)
|
||||
return get(componentId: componentId, entityId: entityId)
|
||||
}
|
||||
|
||||
final func get<C>(unsafeComponentFor entityId: EntityIdentifier) -> C where C: Component {
|
||||
|
|
@ -78,17 +77,15 @@ public extension Nexus {
|
|||
}
|
||||
|
||||
final func get(components entityId: EntityIdentifier) -> ComponentSet? {
|
||||
return componentIdsByEntity[entityId.index]
|
||||
return componentIdsByEntity[entityId]
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
final func remove(component componentId: ComponentIdentifier, from entityId: EntityIdentifier) -> Bool {
|
||||
let entityIdx: EntityIndex = entityId.index
|
||||
|
||||
// delete component instance
|
||||
componentsByType[componentId]?.remove(at: entityIdx)
|
||||
componentsByType[componentId]?.remove(at: entityId.index)
|
||||
// unasign component from entity
|
||||
componentIdsByEntity[entityIdx]?.remove(componentId) //.hashValue)
|
||||
componentIdsByEntity[entityId]?.remove(componentId)
|
||||
|
||||
update(familyMembership: entityId)
|
||||
|
||||
|
|
@ -112,10 +109,10 @@ public extension Nexus {
|
|||
}
|
||||
|
||||
private extension Nexus {
|
||||
final func get<C>(componentId: ComponentIdentifier, entityIdx: EntityIndex) -> C? where C: Component {
|
||||
final func get<C>(componentId: ComponentIdentifier, entityId: EntityIdentifier) -> C? where C: Component {
|
||||
guard let uniformComponents: UniformComponents = componentsByType[componentId] else {
|
||||
return nil
|
||||
}
|
||||
return uniformComponents.get(at: entityIdx) as? C
|
||||
return uniformComponents.get(at: entityId.index) as? C
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,57 +6,54 @@
|
|||
//
|
||||
|
||||
extension Nexus {
|
||||
private func nextEntityIdx() -> EntityIndex {
|
||||
guard let nextReused: EntityIdentifier = freeEntities.popLast() else {
|
||||
return entityStorage.count
|
||||
}
|
||||
return nextReused.index
|
||||
}
|
||||
@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 newEntityIndex: EntityIndex = nextEntityIdx()
|
||||
let newEntityIdentifier: EntityIdentifier = newEntityIndex.identifier
|
||||
let newEntityIdentifier: EntityIdentifier = nextEntityId()
|
||||
let newEntity = Entity(nexus: self, id: newEntityIdentifier)
|
||||
entityStorage.insert(newEntity, at: newEntityIndex)
|
||||
entityStorage.insert(newEntity, at: newEntityIdentifier.index)
|
||||
notify(EntityCreated(entityId: newEntityIdentifier))
|
||||
return newEntity
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
@discardableResult
|
||||
public func createEntity(with components: Component...) -> Entity {
|
||||
let newEntity = createEntity()
|
||||
let newEntity = createEntity()
|
||||
components.forEach { newEntity.assign($0) }
|
||||
return newEntity
|
||||
}
|
||||
// swiftlint:enable function_default_parameter_at_end
|
||||
|
||||
/// Number of entities in nexus.
|
||||
public var numEntities: Int {
|
||||
return entityStorage.count
|
||||
}
|
||||
/// Number of entities in nexus.
|
||||
public var numEntities: Int {
|
||||
return entityStorage.count
|
||||
}
|
||||
|
||||
public func exists(entity entityId: EntityIdentifier) -> Bool {
|
||||
return entityStorage.contains(entityId.index)
|
||||
}
|
||||
public func exists(entity entityId: EntityIdentifier) -> Bool {
|
||||
return entityStorage.contains(entityId.index)
|
||||
}
|
||||
|
||||
public func get(entity entityId: EntityIdentifier) -> Entity? {
|
||||
return entityStorage.get(at: entityId.index)
|
||||
}
|
||||
public func get(entity entityId: EntityIdentifier) -> Entity? {
|
||||
return entityStorage.get(at: entityId.index)
|
||||
}
|
||||
|
||||
public func get(unsafeEntity entityId: EntityIdentifier) -> Entity {
|
||||
return entityStorage.get(unsafeAt: entityId.index)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func destroy(entity: Entity) -> Bool {
|
||||
let entityId: EntityIdentifier = entity.identifier
|
||||
let entityIdx: EntityIndex = entityId.index
|
||||
@discardableResult
|
||||
public func destroy(entity: Entity) -> Bool {
|
||||
let entityId: EntityIdentifier = entity.identifier
|
||||
|
||||
guard entityStorage.remove(at: entityIdx) != nil else {
|
||||
report("EntityRemove failure: no entity \(entityId) to remove")
|
||||
return false
|
||||
}
|
||||
guard entityStorage.remove(at: entityId.index) != nil else {
|
||||
report("EntityRemove failure: no entity \(entityId) to remove")
|
||||
return false
|
||||
}
|
||||
|
||||
if removeAll(componentes: entityId) {
|
||||
update(familyMembership: entityId)
|
||||
|
|
@ -64,7 +61,7 @@ extension Nexus {
|
|||
|
||||
freeEntities.append(entityId)
|
||||
|
||||
notify(EntityDestroyed(entityId: entityId))
|
||||
return true
|
||||
}
|
||||
notify(EntityDestroyed(entityId: entityId))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ public extension Nexus {
|
|||
}
|
||||
|
||||
func canBecomeMember(_ entity: Entity, in traits: FamilyTraitSet) -> Bool {
|
||||
let entityIdx: EntityIndex = entity.identifier.index
|
||||
guard let componentIds = componentIdsByEntity[entityIdx] else {
|
||||
let entityId = entity.identifier
|
||||
guard let componentIds = componentIdsByEntity[entityId] else {
|
||||
assertionFailure("no component set defined for entity: \(entity)")
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,15 +32,14 @@ extension Nexus {
|
|||
}
|
||||
|
||||
final func update(membership traits: FamilyTraitSet, for entityId: EntityIdentifier) {
|
||||
let entityIdx: EntityIndex = entityId.index
|
||||
guard let componentIds = componentIdsByEntity[entityIdx] else {
|
||||
guard let componentIds = componentIdsByEntity[entityId] else {
|
||||
// no components - so skip
|
||||
return
|
||||
}
|
||||
|
||||
let isMember: Bool = self.isMember(entity: entityId, inFamilyWithTraits: traits)
|
||||
if !exists(entity: entityId) && isMember {
|
||||
remove(entityWithId: entityId, andIndex: entityIdx, fromFamilyWithTraits: traits)
|
||||
remove(entityWithId: entityId, fromFamilyWithTraits: traits)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -48,12 +47,12 @@ extension Nexus {
|
|||
|
||||
switch (isMatch, isMember) {
|
||||
case (true, false):
|
||||
add(entityWithId: entityId, andIndex: entityIdx, toFamilyWithTraits: traits)
|
||||
add(entityWithId: entityId, toFamilyWithTraits: traits)
|
||||
notify(FamilyMemberAdded(member: entityId, toFamily: traits))
|
||||
return
|
||||
|
||||
case (false, true):
|
||||
remove(entityWithId: entityId, andIndex: entityIdx, fromFamilyWithTraits: traits)
|
||||
remove(entityWithId: entityId, fromFamilyWithTraits: traits)
|
||||
notify(FamilyMemberRemoved(member: entityId, from: traits))
|
||||
return
|
||||
|
||||
|
|
@ -62,13 +61,13 @@ extension Nexus {
|
|||
}
|
||||
}
|
||||
|
||||
final func add(entityWithId entityId: EntityIdentifier, andIndex entityIdx: EntityIndex, toFamilyWithTraits traits: FamilyTraitSet) {
|
||||
final func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) {
|
||||
precondition(familyMembersByTraits[traits] != nil)
|
||||
familyMembersByTraits[traits].unsafelyUnwrapped.insert(entityId, at: entityIdx)
|
||||
familyMembersByTraits[traits].unsafelyUnwrapped.insert(entityId, at: entityId.index)
|
||||
}
|
||||
|
||||
final func remove(entityWithId entityId: EntityIdentifier, andIndex entityIdx: EntityIndex, fromFamilyWithTraits traits: FamilyTraitSet) {
|
||||
final func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) {
|
||||
precondition(familyMembersByTraits[traits] != nil)
|
||||
familyMembersByTraits[traits].unsafelyUnwrapped.remove(at: entityIdx)
|
||||
familyMembersByTraits[traits].unsafelyUnwrapped.remove(at: entityId.index)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,21 +10,21 @@ public class Nexus {
|
|||
|
||||
/// - Index: index value matching entity identifier shifted to Int
|
||||
/// - Value: each element is a entity instance
|
||||
internal var entityStorage: Entities
|
||||
@usableFromInline internal var entityStorage: Entities
|
||||
|
||||
/// - Key: component type identifier
|
||||
/// - Value: each element is a component instance of the same type (uniform). New component instances are appended.
|
||||
internal var componentsByType: [ComponentIdentifier: UniformComponents]
|
||||
@usableFromInline internal var componentsByType: [ComponentIdentifier: UniformComponents]
|
||||
|
||||
/// - Key: entity id as index
|
||||
/// - Value: each element is a component identifier associated with this entity
|
||||
internal var componentIdsByEntity: [EntityIndex: ComponentSet]
|
||||
@usableFromInline internal var componentIdsByEntity: [EntityIdentifier: ComponentSet]
|
||||
|
||||
/// - Values: entity ids that are currently not used
|
||||
internal var freeEntities: ContiguousArray<EntityIdentifier>
|
||||
@usableFromInline internal var freeEntities: ContiguousArray<EntityIdentifier>
|
||||
|
||||
//var familiesByTraitHash: [FamilyTraitSetHash: Family]
|
||||
internal var familyMembersByTraits: [FamilyTraitSet: UniformEntityIdentifiers]
|
||||
@usableFromInline internal var familyMembersByTraits: [FamilyTraitSet: UniformEntityIdentifiers]
|
||||
|
||||
public init() {
|
||||
entityStorage = Entities()
|
||||
|
|
|
|||
|
|
@ -12,14 +12,16 @@ class EntityTests: XCTestCase {
|
|||
|
||||
func testEntityIdentifierAndIndex() {
|
||||
|
||||
let min: EntityIndex = EntityIdentifier(EntityIdentifier.min).index
|
||||
XCTAssert(EntityIndex(min).identifier == min)
|
||||
let min = EntityIdentifier(.min)
|
||||
XCTAssertEqual(min.index, Int(UInt32.min))
|
||||
|
||||
let rand: EntityIndex = EntityIdentifier(EntityIdentifier(arc4random())).index
|
||||
XCTAssert(EntityIndex(rand).identifier == rand)
|
||||
let uRand = UInt32.random(in: UInt32.min...UInt32.max)
|
||||
let rand = EntityIdentifier(uRand)
|
||||
XCTAssertEqual(rand.index, Int(uRand))
|
||||
|
||||
let max: EntityIndex = EntityIdentifier(EntityIdentifier.max).index
|
||||
XCTAssert(EntityIndex(max).identifier == max)
|
||||
let max = EntityIdentifier(.max)
|
||||
XCTAssertEqual(max, EntityIdentifier.invalid)
|
||||
XCTAssertEqual(max.index, Int(UInt32.max))
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,13 +27,14 @@ class HashingTests: XCTestCase {
|
|||
|
||||
var hashSet: Set<Int> = Set<Int>()
|
||||
|
||||
var range: CountableRange<EntityIdentifier> = 0 ..< 1_000_000
|
||||
var range: CountableRange<UInt32> = 0 ..< 1_000_000
|
||||
|
||||
let maxComponents: Int = 1000
|
||||
let components: [Int] = (0..<maxComponents).map { _ in makeComponent() }
|
||||
|
||||
var index: Int = 0
|
||||
while let eId: EntityIdentifier = range.popLast() {
|
||||
while let idx: UInt32 = range.popLast() {
|
||||
let eId = EntityIdentifier(idx)
|
||||
|
||||
let entityId: EntityIdentifier = eId
|
||||
let c = (index % maxComponents)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class NexusTests: XCTestCase {
|
|||
testEntityCreate()
|
||||
XCTAssertEqual(nexus.numEntities, 2)
|
||||
|
||||
let e1: Entity = nexus.get(entity: 1)!
|
||||
let e1: Entity = nexus.get(entity: EntityIdentifier(1))!
|
||||
XCTAssertEqual(e1.identifier.index, 1)
|
||||
|
||||
XCTAssertTrue(nexus.destroy(entity: e1))
|
||||
|
|
@ -51,7 +51,7 @@ class NexusTests: XCTestCase {
|
|||
|
||||
XCTAssertEqual(nexus.numEntities, 1)
|
||||
|
||||
let e1Again: Entity? = nexus.get(entity: 1)
|
||||
let e1Again: Entity? = nexus.get(entity: EntityIdentifier(1))
|
||||
XCTAssertNil(e1Again)
|
||||
|
||||
XCTAssertEqual(nexus.numEntities, 1)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@testable import FirebladeECSTests
|
||||
import FirebladeECSTests
|
||||
import XCTest
|
||||
|
||||
XCTMain([
|
||||
|
|
|
|||
Loading…
Reference in New Issue