diff --git a/Sources/FirebladeECS/Component.swift b/Sources/FirebladeECS/Component.swift index f7354c4..8c0a8bc 100644 --- a/Sources/FirebladeECS/Component.swift +++ b/Sources/FirebladeECS/Component.swift @@ -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) + } +} diff --git a/Sources/FirebladeECS/ComponentIdentifier.swift b/Sources/FirebladeECS/ComponentIdentifier.swift deleted file mode 100644 index a1967bb..0000000 --- a/Sources/FirebladeECS/ComponentIdentifier.swift +++ /dev/null @@ -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) - } -} diff --git a/Sources/FirebladeECS/Entity.swift b/Sources/FirebladeECS/Entity.swift index 7812bae..c0b1d6a 100644 --- a/Sources/FirebladeECS/Entity.swift +++ b/Sources/FirebladeECS/Entity.swift @@ -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(_ componentType: C.Type) -> Entity where C: Component { - return remove(componentType.identifier) + public final func remove(_ 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 } diff --git a/Sources/FirebladeECS/Events.swift b/Sources/FirebladeECS/Events.swift index db9e935..2b17041 100644 --- a/Sources/FirebladeECS/Events.swift +++ b/Sources/FirebladeECS/Events.swift @@ -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 } diff --git a/Sources/FirebladeECS/Family+Members.swift b/Sources/FirebladeECS/Family+Members.swift index 6b2ab53..c85f72c 100644 --- a/Sources/FirebladeECS/Family+Members.swift +++ b/Sources/FirebladeECS/Family+Members.swift @@ -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(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(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(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(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(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) } } diff --git a/Sources/FirebladeECS/Family.swift b/Sources/FirebladeECS/Family.swift index d7d9521..659a4d2 100644 --- a/Sources/FirebladeECS/Family.swift +++ b/Sources/FirebladeECS/Family.swift @@ -15,7 +15,7 @@ public final class Family { self.nexus = nexus self.traits = traits defer { - nexus.onFamilyInit(family: self) + self.nexus?.onFamilyInit(family: self) } } diff --git a/Sources/FirebladeECS/FamilyTraitSet.swift b/Sources/FirebladeECS/FamilyTraitSet.swift index 1fbaa21..a4a4b70 100644 --- a/Sources/FirebladeECS/FamilyTraitSet.swift +++ b/Sources/FirebladeECS/FamilyTraitSet.swift @@ -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 } } diff --git a/Sources/FirebladeECS/Hashable.swift b/Sources/FirebladeECS/Hashable.swift index 57733b8..254bf00 100644 --- a/Sources/FirebladeECS/Hashable.swift +++ b/Sources/FirebladeECS/Hashable.swift @@ -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) } } diff --git a/Sources/FirebladeECS/ManagedContiguousArray.swift b/Sources/FirebladeECS/ManagedContiguousArray.swift index fe947f2..bb38c52 100644 --- a/Sources/FirebladeECS/ManagedContiguousArray.swift +++ b/Sources/FirebladeECS/ManagedContiguousArray.swift @@ -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 diff --git a/Sources/FirebladeECS/Nexus+Component.swift b/Sources/FirebladeECS/Nexus+Component.swift index 825417c..305e25d 100644 --- a/Sources/FirebladeECS/Nexus+Component.swift +++ b/Sources/FirebladeECS/Nexus+Component.swift @@ -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) } diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index 7c83f32..1fefaa0 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -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) } diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index 4f5bf9b..ca499c8 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -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) } } diff --git a/Sources/FirebladeECS/SparseSet.swift b/Sources/FirebladeECS/SparseSet.swift index cfe5690..74dea8e 100644 --- a/Sources/FirebladeECS/SparseSet.swift +++ b/Sources/FirebladeECS/SparseSet.swift @@ -65,18 +65,12 @@ public class SparseSet: UniformStorage, Sequence { sparse.removeAll(keepingCapacity: keepingCapacity) } - public func makeIterator() -> SparseIterator { - return SparseIterator(self) - /* - // NOTE: was optimized by using a dedicated iterator implementation - var iter = dense.makeIterator() - return AnyIterator.init { - guard let next: Pair = iter.next() as? Pair else { return nil } - return next.value - }*/ + public func makeIterator() -> SparseSetIterator { + return SparseSetIterator(self) } - public struct SparseIterator: IteratorProtocol { + // MARK: - SparseIterator + public struct SparseSetIterator: IteratorProtocol { private let sparseSet: SparseSet private var iterator: IndexingIterator> init(_ sparseSet: SparseSet) { @@ -92,6 +86,8 @@ public class SparseSet: UniformStorage, Sequence { } } +// MARK: - specialized sparse sets + public class SparseComponentSet: SparseSet { public typealias Index = EntityIndex diff --git a/Tests/FirebladeECSTests/FamilyTests.swift b/Tests/FirebladeECSTests/FamilyTests.swift index cd92aa0..784a62a 100644 --- a/Tests/FirebladeECSTests/FamilyTests.swift +++ b/Tests/FirebladeECSTests/FamilyTests.swift @@ -77,7 +77,7 @@ class FamilyTests: XCTestCase { XCTAssert(nexus.numEntities == number) measure { - family.iterateMembers({ (entityId) in + family.iterate(entities: { (entityId) in _ = entityId }) }