Cleanups and small refactorings

This commit is contained in:
Christian Treffs 2017-11-16 21:53:47 +01:00
parent 181c8023bd
commit 525fb31724
14 changed files with 94 additions and 109 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ public final class Family {
self.nexus = nexus
self.traits = traits
defer {
nexus.onFamilyInit(family: self)
self.nexus?.onFamilyInit(family: self)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -77,7 +77,7 @@ class FamilyTests: XCTestCase {
XCTAssert(nexus.numEntities == number)
measure {
family.iterateMembers({ (entityId) in
family.iterate(entities: { (entityId) in
_ = entityId
})
}