Refactor component and entity identifier
This commit is contained in:
parent
fa1ebe0193
commit
c3d84b4f12
|
|
@ -14,6 +14,5 @@ public protocol Component: class, Codable {
|
|||
}
|
||||
|
||||
extension Component {
|
||||
public static var identifier: ComponentIdentifier { return ComponentIdentifier(Self.self) }
|
||||
@inlinable public var identifier: ComponentIdentifier { return Self.identifier }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,20 @@
|
|||
|
||||
/// Identifies a component by it's meta type
|
||||
public struct ComponentIdentifier: Identifiable {
|
||||
public let id: ObjectIdentifier
|
||||
public let id: String
|
||||
|
||||
init<T>(_ type: T.Type) where T: Component {
|
||||
self.id = ObjectIdentifier(type)
|
||||
public init<T>(_ componentType: T.Type) where T: Component {
|
||||
defer { Nexus.register(component: T.self, using: self) }
|
||||
|
||||
self.id = String(reflecting: componentType)
|
||||
}
|
||||
}
|
||||
|
||||
extension ComponentIdentifier: Equatable { }
|
||||
extension ComponentIdentifier: Hashable { }
|
||||
extension ComponentIdentifier: Codable { }
|
||||
extension ComponentIdentifier: Comparable {
|
||||
public static func < (lhs: ComponentIdentifier, rhs: ComponentIdentifier) -> Bool {
|
||||
return lhs.id < rhs.id
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,9 @@ extension Nexus {
|
|||
@discardableResult
|
||||
public func createEntity() -> Entity {
|
||||
let newEntityIdentifier: EntityIdentifier = nextEntityId()
|
||||
let newEntity = Entity(nexus: self, id: newEntityIdentifier)
|
||||
entityStorage.insert(newEntity, at: newEntityIdentifier.id)
|
||||
entityStorage.insert(newEntityIdentifier, at: newEntityIdentifier.id)
|
||||
delegate?.nexusEvent(EntityCreated(entityId: newEntityIdentifier))
|
||||
return newEntity
|
||||
return Entity(nexus: self, id: newEntityIdentifier)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
|
|
@ -40,23 +39,29 @@ extension Nexus {
|
|||
}
|
||||
|
||||
public func get(entity entityId: EntityIdentifier) -> Entity? {
|
||||
return entityStorage.get(at: entityId.id)
|
||||
guard let id = entityStorage.get(at: entityId.id) else {
|
||||
return nil
|
||||
}
|
||||
return Entity(nexus: self, id: id)
|
||||
}
|
||||
|
||||
public func get(unsafeEntity entityId: EntityIdentifier) -> Entity {
|
||||
return entityStorage.get(unsafeAt: entityId.id)
|
||||
return Entity(nexus: self, id: entityStorage.get(unsafeAt: entityId.id))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func destroy(entity: Entity) -> Bool {
|
||||
let entityId: EntityIdentifier = entity.identifier
|
||||
return self.destroy(entityId: entity.identifier)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func destroy(entityId: EntityIdentifier) -> Bool {
|
||||
guard entityStorage.remove(at: entityId.id) != nil else {
|
||||
delegate?.nexusNonFatalError("EntityRemove failure: no entity \(entityId) to remove")
|
||||
return false
|
||||
}
|
||||
|
||||
removeAllChildren(from: entity)
|
||||
removeAllChildren(from: entityId)
|
||||
|
||||
if removeAll(componentes: entityId) {
|
||||
update(familyMembership: entityId)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ extension Nexus {
|
|||
final func update(familyMembership traits: FamilyTraitSet) {
|
||||
// FIXME: iterating all entities is costly for many entities
|
||||
var iter = entityStorage.makeIterator()
|
||||
while let entity = iter.next() {
|
||||
update(membership: traits, for: entity.identifier)
|
||||
while let entityId = iter.next() {
|
||||
update(membership: traits, for: entityId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,8 +35,12 @@ extension Nexus {
|
|||
}
|
||||
|
||||
public final func removeAllChildren(from parent: Entity) {
|
||||
childrenByParentEntity[parent.identifier]?.forEach { removeChild($0, from: parent.identifier) }
|
||||
return childrenByParentEntity[parent.identifier] = nil
|
||||
self.removeAllChildren(from: parent.identifier)
|
||||
}
|
||||
|
||||
public final func removeAllChildren(from parentId: EntityIdentifier) {
|
||||
childrenByParentEntity[parentId]?.forEach { removeChild($0, from: parentId) }
|
||||
return childrenByParentEntity[parentId] = nil
|
||||
}
|
||||
|
||||
public final func numChildren(for entity: Entity) -> Int {
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@
|
|||
//
|
||||
|
||||
public final class Nexus {
|
||||
/// Static version string.
|
||||
public static let version: String = "1.0.0"
|
||||
|
||||
/// Main entity storage.
|
||||
/// Entities are tightly packed by EntityIdentifier.
|
||||
@usableFromInline final var entityStorage: UnorderedSparseSet<Entity>
|
||||
@usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier>
|
||||
|
||||
/// Entity ids that are currently not used.
|
||||
@usableFromInline final var freeEntities: [EntityIdentifier]
|
||||
|
|
@ -33,13 +36,27 @@ public final class Nexus {
|
|||
|
||||
public final weak var delegate: NexusEventDelegate?
|
||||
|
||||
public init() {
|
||||
entityStorage = UnorderedSparseSet<Entity>()
|
||||
componentsByType = [:]
|
||||
componentIdsByEntity = [:]
|
||||
freeEntities = []
|
||||
familyMembersByTraits = [:]
|
||||
childrenByParentEntity = [:]
|
||||
public convenience init() {
|
||||
self.init(entityStorage: UnorderedSparseSet<EntityIdentifier>(),
|
||||
componentsByType: [:],
|
||||
componentsByEntity: [:],
|
||||
freeEntities: [],
|
||||
familyMembersByTraits: [:],
|
||||
childrenByParentEntity: [:])
|
||||
}
|
||||
|
||||
internal init(entityStorage: UnorderedSparseSet<EntityIdentifier>,
|
||||
componentsByType: [ComponentIdentifier: UnorderedSparseSet<Component>],
|
||||
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
||||
freeEntities: [EntityIdentifier],
|
||||
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier>],
|
||||
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>]) {
|
||||
self.entityStorage = entityStorage
|
||||
self.componentsByType = componentsByType
|
||||
self.componentIdsByEntity = componentsByEntity
|
||||
self.freeEntities = freeEntities
|
||||
self.familyMembersByTraits = familyMembersByTraits
|
||||
self.childrenByParentEntity = childrenByParentEntity
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -47,25 +64,36 @@ public final class Nexus {
|
|||
}
|
||||
|
||||
public final func clear() {
|
||||
var iter = entityStorage.makeIterator()
|
||||
while let entity = iter.next() {
|
||||
destroy(entity: entity)
|
||||
}
|
||||
|
||||
entityStorage.forEach { destroy(entityId: $0) }
|
||||
entityStorage.removeAll()
|
||||
freeEntities.removeAll()
|
||||
|
||||
assert(entityStorage.isEmpty)
|
||||
assert(componentsByType.values.reduce(0) { $0 + $1.count } == 0)
|
||||
assert(componentIdsByEntity.values.reduce(0) { $0 + $1.count } == 0)
|
||||
assert(freeEntities.isEmpty)
|
||||
assert(familyMembersByTraits.values.reduce(0) { $0 + $1.count } == 0)
|
||||
|
||||
componentsByType.removeAll()
|
||||
componentIdsByEntity.removeAll()
|
||||
familyMembersByTraits.removeAll()
|
||||
childrenByParentEntity.removeAll()
|
||||
}
|
||||
|
||||
public static var knownUniqueComponentTypes: Set<ComponentIdentifier> {
|
||||
return Set<ComponentIdentifier>(componentDecoderMap.keys)
|
||||
}
|
||||
|
||||
internal static var componentDecoderMap: [ComponentIdentifier: (Decoder) throws -> Component] = [:]
|
||||
|
||||
/// Register a component type uniquely with the Nexus implementation.
|
||||
/// - Parameters:
|
||||
/// - componentType: The component meta type.
|
||||
/// - identifier: The unique identifier.
|
||||
internal static func register<C>(component componentType: C.Type, using identifier: ComponentIdentifier) where C: Component {
|
||||
precondition(componentDecoderMap[identifier] == nil, "Component type collision: \(identifier) already in use.")
|
||||
componentDecoderMap[identifier] = { try C(from: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Errors
|
||||
extension Nexus {
|
||||
public enum Error: Swift.Error {
|
||||
case versionMismatch(required: String, provided: String)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
|
|
|||
|
|
@ -7,9 +7,13 @@
|
|||
|
||||
import FirebladeECS
|
||||
|
||||
class EmptyComponent: Component { }
|
||||
class EmptyComponent: Component {
|
||||
static let identifier: ComponentIdentifier = .init(EmptyComponent.self)
|
||||
}
|
||||
|
||||
class Name: Component {
|
||||
static let identifier: ComponentIdentifier = .init(Name.self)
|
||||
|
||||
var name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
|
|
@ -17,6 +21,8 @@ class Name: Component {
|
|||
}
|
||||
|
||||
class Position: Component {
|
||||
static var identifier: ComponentIdentifier = .init(Position.self)
|
||||
|
||||
var x: Int
|
||||
var y: Int
|
||||
init(x: Int, y: Int) {
|
||||
|
|
@ -26,6 +32,8 @@ class Position: Component {
|
|||
}
|
||||
|
||||
class Velocity: Component {
|
||||
static var identifier: ComponentIdentifier = .init(Velocity.self)
|
||||
|
||||
var a: Float
|
||||
init(a: Float) {
|
||||
self.a = a
|
||||
|
|
@ -33,6 +41,8 @@ class Velocity: Component {
|
|||
}
|
||||
|
||||
class Party: Component {
|
||||
static var identifier: ComponentIdentifier = .init(Party.self)
|
||||
|
||||
var partying: Bool
|
||||
init(partying: Bool) {
|
||||
self.partying = partying
|
||||
|
|
@ -40,6 +50,8 @@ class Party: Component {
|
|||
}
|
||||
|
||||
class Color: Component {
|
||||
static var identifier: ComponentIdentifier = .init(Color.self)
|
||||
|
||||
var r: UInt8 = 0
|
||||
var g: UInt8 = 0
|
||||
var b: UInt8 = 0
|
||||
|
|
@ -61,6 +73,8 @@ class ExampleSystem {
|
|||
}
|
||||
|
||||
final class SingleGameState: SingleComponent {
|
||||
static var identifier: ComponentIdentifier = .init(SingleGameState.self)
|
||||
|
||||
var shouldQuit: Bool = false
|
||||
var playerHealth: Int = 67
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@
|
|||
|
||||
import FirebladeECS
|
||||
|
||||
class EmptyComponent: Component { }
|
||||
class EmptyComponent: Component {
|
||||
static let identifier: ComponentIdentifier = .init(EmptyComponent.self)
|
||||
}
|
||||
|
||||
class Name: Component {
|
||||
static let identifier: ComponentIdentifier = .init(Name.self)
|
||||
var name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
|
|
@ -17,6 +20,7 @@ class Name: Component {
|
|||
}
|
||||
|
||||
class Position: Component {
|
||||
static let identifier: ComponentIdentifier = .init(Position.self)
|
||||
var x: Int
|
||||
var y: Int
|
||||
init(x: Int, y: Int) {
|
||||
|
|
@ -26,6 +30,7 @@ class Position: Component {
|
|||
}
|
||||
|
||||
class Velocity: Component {
|
||||
static let identifier: ComponentIdentifier = .init(Velocity.self)
|
||||
var a: Float
|
||||
init(a: Float) {
|
||||
self.a = a
|
||||
|
|
@ -33,6 +38,7 @@ class Velocity: Component {
|
|||
}
|
||||
|
||||
class Party: Component {
|
||||
static let identifier: ComponentIdentifier = .init(Party.self)
|
||||
var partying: Bool
|
||||
init(partying: Bool) {
|
||||
self.partying = partying
|
||||
|
|
@ -40,12 +46,14 @@ class Party: Component {
|
|||
}
|
||||
|
||||
class Color: Component {
|
||||
static let identifier: ComponentIdentifier = .init(Color.self)
|
||||
var r: UInt8 = 0
|
||||
var g: UInt8 = 0
|
||||
var b: UInt8 = 0
|
||||
}
|
||||
|
||||
class Index: Component {
|
||||
static let identifier: ComponentIdentifier = .init(Index.self)
|
||||
var index: Int
|
||||
|
||||
init(index: Int) {
|
||||
|
|
@ -54,6 +62,7 @@ class Index: Component {
|
|||
}
|
||||
|
||||
final class SingleGameState: SingleComponent {
|
||||
static let identifier: ComponentIdentifier = .init(SingleGameState.self)
|
||||
var shouldQuit: Bool = false
|
||||
var playerHealth: Int = 67
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue