Cleanups and small refactorings
This commit is contained in:
parent
181c8023bd
commit
525fb31724
|
|
@ -6,18 +6,13 @@
|
|||
//
|
||||
|
||||
public protocol Component: class, TypeIdentifiable {}
|
||||
public typealias ComponentIdentifier = ObjectIdentifier
|
||||
|
||||
extension Component {
|
||||
public var identifier: ComponentIdentifier { return typeObjectIdentifier }
|
||||
public static var identifier: ComponentIdentifier { return typeObjectIdentifier }
|
||||
}
|
||||
|
||||
// MARK: - activatable protocol
|
||||
extension Component {
|
||||
public func activate() { /* default does nothing */ }
|
||||
public func deactivate() { /* default does nothing */ }
|
||||
}
|
||||
|
||||
// MARK: - entity component hashable
|
||||
extension Component {
|
||||
|
||||
|
|
@ -39,3 +34,20 @@ extension Component {
|
|||
return Self.hashValue(using: entityIdx)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - component identifier hashable
|
||||
extension ComponentIdentifier {
|
||||
|
||||
/// Provides XOR hash value from component identifier (aka type) and entity index.
|
||||
/// Is only stable for app runtime.
|
||||
///
|
||||
/// - Parameter entityIdx: entity index
|
||||
/// - Returns: combinded entity component hash
|
||||
func hashValue(using entityIdx: EntityIndex) -> EntityComponentHash {
|
||||
return hashValue(using: entityIdx.identifier)
|
||||
}
|
||||
|
||||
func hashValue(using entityId: EntityIdentifier) -> EntityComponentHash {
|
||||
return EntityComponentHash.compose(entityId: entityId, componentTypeHash: hashValue)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// ComponentIdentifier.swift
|
||||
// FirebladeECS
|
||||
//
|
||||
// Created by Christian Treffs on 08.10.17.
|
||||
//
|
||||
|
||||
public typealias ComponentIdentifier = ObjectIdentifier
|
||||
|
||||
extension ComponentIdentifier {
|
||||
|
||||
/// Provides XOR hash value from component identifier (aka type) and entity index.
|
||||
/// Is only stable for app runtime.
|
||||
///
|
||||
/// - Parameter entityIdx: entity index
|
||||
/// - Returns: combinded entity component hash
|
||||
func hashValue(using entityIdx: EntityIndex) -> EntityComponentHash {
|
||||
return hashValue(using: entityIdx.identifier)
|
||||
}
|
||||
|
||||
func hashValue(using entityId: EntityIdentifier) -> EntityComponentHash {
|
||||
return EntityComponentHash.compose(entityId: entityId, componentTypeHash: hashValue)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,18 +6,14 @@
|
|||
//
|
||||
|
||||
public final class Entity: UniqueEntityIdentifiable {
|
||||
|
||||
internal(set) public var identifier: EntityIdentifier = EntityIdentifier.invalid
|
||||
public var name: String?
|
||||
|
||||
unowned let nexus: Nexus
|
||||
|
||||
init(nexus: Nexus, id: EntityIdentifier, name: String? = nil) {
|
||||
self.nexus = nexus
|
||||
self.identifier = id
|
||||
self.name = name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Invalidate
|
||||
|
|
@ -52,8 +48,8 @@ public extension Entity {
|
|||
return has(type.identifier)
|
||||
}
|
||||
|
||||
public final func has(_ uct: ComponentIdentifier) -> Bool {
|
||||
return nexus.has(componentId: uct, entityIdx: identifier.index)
|
||||
public final func has(_ compId: ComponentIdentifier) -> Bool {
|
||||
return nexus.has(componentId: compId, entityIdx: identifier.index)
|
||||
}
|
||||
|
||||
public final var hasComponents: Bool {
|
||||
|
|
@ -105,13 +101,13 @@ public extension Entity {
|
|||
}
|
||||
|
||||
@discardableResult
|
||||
public final func remove<C>(_ componentType: C.Type) -> Entity where C: Component {
|
||||
return remove(componentType.identifier)
|
||||
public final func remove<C>(_ compType: C.Type) -> Entity where C: Component {
|
||||
return remove(compType.identifier)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public final func remove(_ uct: ComponentIdentifier) -> Entity {
|
||||
nexus.remove(component: uct, from: identifier)
|
||||
public final func remove(_ compId: ComponentIdentifier) -> Entity {
|
||||
nexus.remove(component: compId, from: identifier)
|
||||
return self
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,43 +4,43 @@
|
|||
//
|
||||
// Created by Christian Treffs on 08.10.17.
|
||||
//
|
||||
protocol Event { }
|
||||
public struct EntityCreated: Event {
|
||||
public protocol ECSEvent {}
|
||||
public struct EntityCreated: ECSEvent {
|
||||
let entityId: EntityIdentifier
|
||||
}
|
||||
|
||||
public struct EntityDestroyed: Event {
|
||||
public struct EntityDestroyed: ECSEvent {
|
||||
let entityId: EntityIdentifier
|
||||
}
|
||||
|
||||
public struct ComponentAdded: Event {
|
||||
public struct ComponentAdded: ECSEvent {
|
||||
let component: ComponentIdentifier
|
||||
let to: EntityIdentifier
|
||||
}
|
||||
|
||||
public struct ComponentUpdated: Event {
|
||||
public struct ComponentUpdated: ECSEvent {
|
||||
let at: EntityIdentifier
|
||||
}
|
||||
|
||||
public struct ComponentRemoved: Event {
|
||||
public struct ComponentRemoved: ECSEvent {
|
||||
let component: ComponentIdentifier
|
||||
let from: EntityIdentifier
|
||||
}
|
||||
|
||||
struct FamilyMemberAdded: Event {
|
||||
struct FamilyMemberAdded: ECSEvent {
|
||||
let member: EntityIdentifier
|
||||
let to: FamilyTraitSet
|
||||
}
|
||||
|
||||
struct FamilyMemberRemoved: Event {
|
||||
struct FamilyMemberRemoved: ECSEvent {
|
||||
let member: EntityIdentifier
|
||||
let from: FamilyTraitSet
|
||||
}
|
||||
|
||||
struct FamilyCreated: Event {
|
||||
struct FamilyCreated: ECSEvent {
|
||||
let family: FamilyTraitSet
|
||||
}
|
||||
|
||||
struct FamilyDestroyed: Event {
|
||||
struct FamilyDestroyed: ECSEvent {
|
||||
let family: FamilyTraitSet
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,19 +6,16 @@
|
|||
//
|
||||
|
||||
extension Family {
|
||||
public func iterateMembers(_ apply: @escaping (EntityIdentifier) -> Void) {
|
||||
public func iterate(entities apply: @escaping (EntityIdentifier) -> Void) {
|
||||
for entityId in memberIds {
|
||||
apply(entityId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Family {
|
||||
|
||||
public func iterate<A>(components _: A.Type, _ apply: @escaping (EntityIdentifier, A?) -> Void)
|
||||
where A: Component {
|
||||
for entityId in memberIds {
|
||||
let a: A? = self.nexus?.get(for: entityId)
|
||||
let a: A? = nexus?.get(for: entityId)
|
||||
apply(entityId, a)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,8 +23,8 @@ extension Family {
|
|||
public func iterate<A, B>(components _: A.Type, _: B.Type, _ apply: @escaping (EntityIdentifier, A?, B?) -> Void)
|
||||
where A: Component, B: Component {
|
||||
for entityId in memberIds {
|
||||
let a: A? = self.nexus?.get(for: entityId)
|
||||
let b: B? = self.nexus?.get(for: entityId)
|
||||
let a: A? = nexus?.get(for: entityId)
|
||||
let b: B? = nexus?.get(for: entityId)
|
||||
apply(entityId, a, b)
|
||||
}
|
||||
}
|
||||
|
|
@ -35,9 +32,9 @@ extension Family {
|
|||
public func iterate<A, B, C>(components _: A.Type, _: B.Type, _: C.Type, _ apply: @escaping (EntityIdentifier, A?, B?, C?) -> Void)
|
||||
where A: Component, B: Component, C: Component {
|
||||
for entityId in memberIds {
|
||||
let a: A? = self.nexus?.get(for: entityId)
|
||||
let b: B? = self.nexus?.get(for: entityId)
|
||||
let c: C? = self.nexus?.get(for: entityId)
|
||||
let a: A? = nexus?.get(for: entityId)
|
||||
let b: B? = nexus?.get(for: entityId)
|
||||
let c: C? = nexus?.get(for: entityId)
|
||||
apply(entityId, a, b, c)
|
||||
}
|
||||
}
|
||||
|
|
@ -45,21 +42,21 @@ extension Family {
|
|||
public func iterate<A, B, C, D>(components _: A.Type, _: B.Type, _: C.Type, _: D.Type, _ apply: @escaping (EntityIdentifier, A?, B?, C?, D?) -> Void)
|
||||
where A: Component, B: Component, C: Component, D: Component {
|
||||
for entityId in memberIds {
|
||||
let a: A? = self.nexus?.get(for: entityId)
|
||||
let b: B? = self.nexus?.get(for: entityId)
|
||||
let c: C? = self.nexus?.get(for: entityId)
|
||||
let d: D? = self.nexus?.get(for: entityId)
|
||||
let a: A? = nexus?.get(for: entityId)
|
||||
let b: B? = nexus?.get(for: entityId)
|
||||
let c: C? = nexus?.get(for: entityId)
|
||||
let d: D? = nexus?.get(for: entityId)
|
||||
apply(entityId, a, b, c, d)
|
||||
}
|
||||
}
|
||||
public func iterate<A, B, C, D, E>(components _: A.Type, _: B.Type, _: C.Type, _: D.Type, _: E.Type, _ apply: @escaping (EntityIdentifier, A?, B?, C?, D?, E?) -> Void)
|
||||
where A: Component, B: Component, C: Component, D: Component, E: Component {
|
||||
for entityId in memberIds {
|
||||
let a: A? = self.nexus?.get(for: entityId)
|
||||
let b: B? = self.nexus?.get(for: entityId)
|
||||
let c: C? = self.nexus?.get(for: entityId)
|
||||
let d: D? = self.nexus?.get(for: entityId)
|
||||
let e: E? = self.nexus?.get(for: entityId)
|
||||
let a: A? = nexus?.get(for: entityId)
|
||||
let b: B? = nexus?.get(for: entityId)
|
||||
let c: C? = nexus?.get(for: entityId)
|
||||
let d: D? = nexus?.get(for: entityId)
|
||||
let e: E? = nexus?.get(for: entityId)
|
||||
apply(entityId, a, b, c, d, e)
|
||||
}
|
||||
}
|
||||
|
|
@ -68,12 +65,12 @@ extension Family {
|
|||
_ apply: @escaping (EntityIdentifier, A?, B?, C?, D?, E?, F?) -> Void)
|
||||
where A: Component, B: Component, C: Component, D: Component, E: Component, F: Component {
|
||||
for entityId in memberIds {
|
||||
let a: A? = self.nexus?.get(for: entityId)
|
||||
let b: B? = self.nexus?.get(for: entityId)
|
||||
let c: C? = self.nexus?.get(for: entityId)
|
||||
let d: D? = self.nexus?.get(for: entityId)
|
||||
let e: E? = self.nexus?.get(for: entityId)
|
||||
let f: F? = self.nexus?.get(for: entityId)
|
||||
let a: A? = nexus?.get(for: entityId)
|
||||
let b: B? = nexus?.get(for: entityId)
|
||||
let c: C? = nexus?.get(for: entityId)
|
||||
let d: D? = nexus?.get(for: entityId)
|
||||
let e: E? = nexus?.get(for: entityId)
|
||||
let f: F? = nexus?.get(for: entityId)
|
||||
apply(entityId, a, b, c, d, e, f)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public final class Family {
|
|||
self.nexus = nexus
|
||||
self.traits = traits
|
||||
defer {
|
||||
nexus.onFamilyInit(family: self)
|
||||
self.nexus?.onFamilyInit(family: self)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ public struct FamilyTraitSet {
|
|||
fileprivate let requiresAll: ComponentSet
|
||||
fileprivate let excludesAll: ComponentSet
|
||||
fileprivate let needsAtLeastOne: ComponentSet
|
||||
fileprivate let _hash: Int
|
||||
fileprivate let setHash: Int
|
||||
fileprivate let isEmptyAny: Bool
|
||||
|
||||
public init(requiresAll: [Component.Type], excludesAll: [Component.Type], needsAtLeastOne: [Component.Type] = []) {
|
||||
|
|
@ -24,7 +24,7 @@ public struct FamilyTraitSet {
|
|||
|
||||
isEmptyAny = one.isEmpty
|
||||
|
||||
_hash = hash(combine: [all, one, none])
|
||||
setHash = hash(combine: [all, one, none])
|
||||
|
||||
self.requiresAll = all
|
||||
self.needsAtLeastOne = one
|
||||
|
|
@ -72,13 +72,13 @@ extension FamilyTraitSet {
|
|||
// MARK: - Equatable
|
||||
extension FamilyTraitSet: Equatable {
|
||||
public static func ==(lhs: FamilyTraitSet, rhs: FamilyTraitSet) -> Bool {
|
||||
return lhs._hash == rhs._hash
|
||||
return lhs.setHash == rhs.setHash
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
extension FamilyTraitSet: Hashable {
|
||||
public var hashValue: Int {
|
||||
return _hash
|
||||
return setHash
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,6 @@ extension TypeHashable where Self: InstanceHashable {
|
|||
lhs.instanceObjectIdentifier == rhs.instanceObjectIdentifier
|
||||
}
|
||||
public var hashValue: Int {
|
||||
return typeObjectIdentifier.hashValue ^ instanceObjectIdentifier.hashValue // TODO: this might not be best - use hash combine?
|
||||
return hash(combine: typeObjectIdentifier.hashValue, instanceObjectIdentifier.hashValue)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ public protocol UniformStorage: class {
|
|||
|
||||
public class ManagedContiguousArray: UniformStorage {
|
||||
public static var chunkSize: Int = 4096
|
||||
|
||||
public typealias Index = Int
|
||||
public typealias Element = Any
|
||||
var _size: Int = 0
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@
|
|||
extension Nexus {
|
||||
|
||||
public var numComponents: Int {
|
||||
var count = 0
|
||||
for (_, uniformComps) in componentsByType {
|
||||
count += uniformComps.count
|
||||
}
|
||||
return count
|
||||
return componentsByType.reduce(0) { return $0 + $1.value.count }
|
||||
}
|
||||
|
||||
public func has(componentId: ComponentIdentifier, entityIdx: EntityIndex) -> Bool {
|
||||
|
|
@ -35,8 +31,11 @@ extension Nexus {
|
|||
let hash: EntityComponentHash = componentId.hashValue(using: entityIdx)
|
||||
/// test if component is already assigned
|
||||
guard !has(componentId: componentId, entityIdx: entityIdx) else {
|
||||
// FIXME: this is still open to debate
|
||||
// a) we replace the component
|
||||
// b) we copy the properties
|
||||
// c) we assert fail
|
||||
report("ComponentAdd collision: \(entityIdx) already has a component \(component)")
|
||||
// TODO: replace component?! copy properties?!
|
||||
return
|
||||
}
|
||||
if componentsByType[componentId] == nil {
|
||||
|
|
@ -54,7 +53,7 @@ extension Nexus {
|
|||
componentIdsByEntityLookup[hash] = 0
|
||||
}
|
||||
|
||||
// FIXME: this is costly for many families
|
||||
// FIXME: iterating all families is costly for many families
|
||||
let entityId: EntityIdentifier = entity.identifier
|
||||
for (_, family) in familiesByTraitHash {
|
||||
update(membership: family, for: entityId)
|
||||
|
|
@ -91,10 +90,10 @@ extension Nexus {
|
|||
let entityIdx: EntityIndex = entityId.index
|
||||
let hash: EntityComponentHash = componentId.hashValue(using: entityIdx)
|
||||
|
||||
// MARK: delete component instance
|
||||
// delete component instance
|
||||
componentsByType[componentId]?.remove(at: entityIdx)
|
||||
|
||||
// MARK: unassign component
|
||||
// unassign component
|
||||
guard let removeIndex: ComponentIdsByEntityIndex = componentIdsByEntityLookup.removeValue(forKey: hash) else {
|
||||
report("ComponentRemove failure: no component found to be removed")
|
||||
return false
|
||||
|
|
@ -108,7 +107,8 @@ extension Nexus {
|
|||
|
||||
// relocate remaining indices pointing in the componentsByEntity map
|
||||
if let remainingComponents: ComponentIdentifiers = componentIdsByEntity[entityIdx] {
|
||||
// FIXME: may be expensive but is cheap for small entities -> may be fixed with a sparse set?!
|
||||
// FIXME: enumerated iteration of remaning components is costly
|
||||
// solution: we fix it by using a sparse set for components per entity
|
||||
for (index, compId) in remainingComponents.enumerated() {
|
||||
let cHash: EntityComponentHash = compId.hashValue(using: entityIdx)
|
||||
assert(cHash != hash)
|
||||
|
|
@ -116,7 +116,7 @@ extension Nexus {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: this is costly for many families
|
||||
// FIXME: iterating all families is costly for many families
|
||||
for (_, family) in familiesByTraitHash {
|
||||
update(membership: family, for: entityId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
extension Nexus {
|
||||
|
||||
public var entities: [Entity] {
|
||||
// FIXME: we do not want this kind of access to the underlying entity store
|
||||
return entityStorage.filter { isValid(entity: $0.identifier) }
|
||||
}
|
||||
|
||||
|
|
@ -62,6 +63,7 @@ extension Nexus {
|
|||
@discardableResult
|
||||
public func destroy(entity: Entity) -> Bool {
|
||||
let entityId: EntityIdentifier = entity.identifier
|
||||
// FIXME: we can make this cheaper by eliminating the need to ask if entity is present
|
||||
guard has(entity: entityId) else {
|
||||
report("EntityRemove failure: no entity \(entityId) to remove")
|
||||
return false
|
||||
|
|
@ -78,6 +80,7 @@ extension Nexus {
|
|||
|
||||
freeEntities.append(entityId)
|
||||
|
||||
// FIXME: iterating all families is costly for many families
|
||||
for (_, family) in familiesByTraitHash {
|
||||
update(membership: family, for: entityId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,15 @@ public typealias TraitEntityIdHash = Int
|
|||
public typealias EntityIdInFamilyIndex = Int
|
||||
public typealias TraitEntityIdHashSet = [TraitEntityIdHash: EntityIdInFamilyIndex]
|
||||
|
||||
public protocol NexusDelegate: class {
|
||||
func nexusEventOccurred(_ event: ECSEvent)
|
||||
func nexusRecoverableErrorOccurred(_ message: String)
|
||||
}
|
||||
|
||||
public class Nexus {
|
||||
|
||||
weak var delegate: NexusDelegate?
|
||||
|
||||
/// - Index: index value matching entity identifier shifted to Int
|
||||
/// - Value: each element is a entity instance
|
||||
var entityStorage: Entities
|
||||
|
|
@ -80,15 +87,14 @@ public class Nexus {
|
|||
|
||||
}
|
||||
|
||||
// MARK: - nexus delegate
|
||||
extension Nexus {
|
||||
|
||||
func notify(_ event: Event) {
|
||||
//Log.debug(event)
|
||||
// TODO: implement
|
||||
|
||||
func notify(_ event: ECSEvent) {
|
||||
delegate?.nexusEventOccurred(event)
|
||||
}
|
||||
|
||||
func report(_ message: String) {
|
||||
// TODO: implement
|
||||
delegate?.nexusRecoverableErrorOccurred(message)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,18 +65,12 @@ public class SparseSet<Element>: UniformStorage, Sequence {
|
|||
sparse.removeAll(keepingCapacity: keepingCapacity)
|
||||
}
|
||||
|
||||
public func makeIterator() -> SparseIterator<Element> {
|
||||
return SparseIterator<Element>(self)
|
||||
/*
|
||||
// NOTE: was optimized by using a dedicated iterator implementation
|
||||
var iter = dense.makeIterator()
|
||||
return AnyIterator<Element>.init {
|
||||
guard let next: Pair = iter.next() as? Pair else { return nil }
|
||||
return next.value
|
||||
}*/
|
||||
public func makeIterator() -> SparseSetIterator<Element> {
|
||||
return SparseSetIterator<Element>(self)
|
||||
}
|
||||
|
||||
public struct SparseIterator<Element>: IteratorProtocol {
|
||||
// MARK: - SparseIterator
|
||||
public struct SparseSetIterator<Element>: IteratorProtocol {
|
||||
private let sparseSet: SparseSet<Element>
|
||||
private var iterator: IndexingIterator<ContiguousArray<(key: Index, value: Element)?>>
|
||||
init(_ sparseSet: SparseSet<Element>) {
|
||||
|
|
@ -92,6 +86,8 @@ public class SparseSet<Element>: UniformStorage, Sequence {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - specialized sparse sets
|
||||
|
||||
public class SparseComponentSet: SparseSet<Component> {
|
||||
public typealias Index = EntityIndex
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class FamilyTests: XCTestCase {
|
|||
XCTAssert(nexus.numEntities == number)
|
||||
|
||||
measure {
|
||||
family.iterateMembers({ (entityId) in
|
||||
family.iterate(entities: { (entityId) in
|
||||
_ = entityId
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue