Refactor component and entity identifier

This commit is contained in:
Christian Treffs 2019-10-05 22:38:28 +02:00
parent fa1ebe0193
commit c3d84b4f12
8 changed files with 104 additions and 37 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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