new implementation - first test working

This commit is contained in:
Christian Treffs 2017-10-15 01:36:18 +02:00
parent e16759f9f1
commit 4e0522aa49
26 changed files with 866 additions and 697 deletions

View File

@ -10,12 +10,34 @@ public protocol Component: UniqueComponentIdentifiable {}
// MARK: UCI
extension Component {
/// Uniquely identifies the component by its meta type
public static var uct: UCT { return UCT(Self.self) }
public static var identifier: ComponentIdentifier { return ComponentIdentifier(Self.self) }
/// Uniquely identifies the component by its meta type
public var uct: UCT { return Self.uct }
public var identifier: ComponentIdentifier { return Self.identifier }
}
// MARK: Equatable
public func ==<A: Component, B: Component>(lhs: A, rhs: B) -> Bool {
return A.uct == B.uct
return A.identifier == B.identifier // FIXME: this may be wrong
}
// MARK: - entity component hashable
public extension Component {
/// 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
static func hashValue(using entityIdx: EntityIndex) -> EntityComponentHash {
return Self.identifier.hashValue(using: entityIdx)
}
/// 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 Self.hashValue(using: entityIdx)
}
}

View File

@ -0,0 +1,27 @@
//
// 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 self.hashValue ^ entityIdx
}
}
// MARK: Unique Component Identifiable
public protocol UniqueComponentIdentifiable {
static var identifier: ComponentIdentifier { get }
var identifier: ComponentIdentifier { get }
}

View File

@ -0,0 +1,102 @@
//
// ComponentStorage.swift
// FirebladeECS
//
// Created by Christian Treffs on 10.10.17.
//
/*
public typealias ComponentIndex = Int
public protocol ComponentStorage {
@discardableResult func add<C: Component>(_ component: C) -> Bool
func makeIterator<C: Component>(_ componentType: C.Type) -> AnyIterator<C>
func makeIterator(_ componentId: ComponentIdentifier) -> AnyIterator<Component>
func has<C: Component>(_ component: C) -> Bool
func has<C: Component>(_ componentType: C.Type) -> Bool
func has(_ componentId: ComponentIdentifier) -> Bool
func get<C: Component>(_ componentType: C.Type) -> Component?
subscript<C: Component>(_ componentType: C.Type) -> Component? { get }
func get(_ componentId: ComponentIdentifier) -> Component?
subscript(_ componentId: ComponentIdentifier) -> Component? { get }
@discardableResult func remove<C: Component>(_ component: C) -> Bool
@discardableResult func remove<C: Component>(_ componentType: C.Type) -> Bool
@discardableResult func remove(_ componentId: ComponentIdentifier) -> Bool
func clear()
}
class GeneralComponentStorage: ComponentStorage {
fileprivate var componentMap: [ComponentIdentifier: ContiguousArray<Component>] = [:]
func add<C>(_ component: C) -> Bool where C : Component {
if var comps: ContiguousArray<Component> = componentMap[component.uct] {
comps.append(component)
} else {
componentMap[component.uct] = ContiguousArray<Component>()
componentMap[component.uct]!.append(component)
}
return true
}
func makeIterator<C>(_ componentType: C.Type) -> AnyIterator<C> where C : Component {
fatalError()
}
func makeIterator(_ componentId: ComponentIdentifier) -> AnyIterator<Component> {
fatalError()
}
func has<C>(_ component: C) -> Bool where C : Component {
fatalError()
}
func has<C>(_ componentType: C.Type) -> Bool where C : Component {
fatalError()
}
func has(_ componentId: ComponentIdentifier) -> Bool {
fatalError()
}
func get<C>(_ componentType: C.Type) -> Component? where C : Component {
fatalError()
}
subscript<C>(componentType: C.Type) -> Component? where C: Component {
fatalError()
}
func get(_ componentId: ComponentIdentifier) -> Component? {
fatalError()
}
subscript(componentId: ComponentIdentifier) -> Component? {
fatalError()
}
func remove<C>(_ component: C) -> Bool where C : Component {
fatalError()
}
func remove<C>(_ componentType: C.Type) -> Bool where C : Component {
fatalError()
}
func remove(_ componentId: ComponentIdentifier) -> Bool {
fatalError()
}
func clear() {
fatalError()
}
}
*/

View File

@ -6,109 +6,78 @@
//
public final class Entity: UniqueEntityIdentifiable {
public let uei: UEI
public let identifier: EntityIdentifier
public var name: String?
@available(*, deprecated: 0.1, message: "replace this with core/context concept")
fileprivate var eventDispatcher: EventDispatcher
fileprivate let nexus: Nexus
public private(set) var componentMap: [UCT: Component]
init(uei: UEI, dispatcher: EventDispatcher) {
self.uei = uei
self.eventDispatcher = dispatcher
componentMap = [UCT: Component]()
componentMap.reserveCapacity(2)
defer {
notifyInit()
}
init(nexus: Nexus, id: EntityIdentifier, name: String? = nil) {
self.nexus = nexus
self.identifier = id
self.name = name
}
deinit {
defer {
destroy()
}
}
}
// MARK: - Equatable
public func ==(lhs: Entity, rhs: Entity) -> Bool {
return lhs.uei == rhs.uei
return lhs.identifier == rhs.identifier
}
// MARK: - number of components
public extension Entity {
public final var numComponents: Int { return componentMap.count }
public final var numComponents: Int {
return nexus.count(components: identifier)
}
}
// MARK: - has component(s)
public extension Entity {
public final func has(_ component: Component.Type) -> Bool {
return has(UCT(component))
public final func has<C>(_ type: C.Type) -> Bool where C: Component {
return has(type.identifier)
}
public final func has(_ component: UCT) -> Bool {
return componentMap[component] != nil
public final func has(_ uct: ComponentIdentifier) -> Bool {
return nexus.has(component: uct, entity: identifier)
}
public final var hasComponents: Bool { return !componentMap.isEmpty }
public final var hasComponents: Bool {
return nexus.count(components: identifier) > 0
}
}
// MARK: - add/push component(s)
// MARK: - add component(s)
public extension Entity {
@discardableResult
public final func push<C: Component>(component: C) -> Entity {
let previousComponent: C? = componentMap.updateValue(component, forKey: component.uct) as? C
if let pComp: C = previousComponent {
notify(update: component, previous: pComp)
} else {
notify(add: component)
}
public final func add<C>(_ component: C) -> Entity where C: Component {
nexus.add(component: component, to: identifier)
return self
}
@discardableResult
public final func add<C: Component>(component: C) -> Entity {
return push(component: component)
public static func += <C>(lhs: Entity, rhs: C) -> Entity where C: Component {
return lhs.add(rhs)
}
@discardableResult
public static func += <C: Component>(lhs: Entity, rhs: C) -> Entity {
return lhs.push(component: rhs)
}
@discardableResult
public static func << <C: Component>(lhs: Entity, rhs: C) -> Entity {
return lhs.push(component: rhs)
public static func << <C>(lhs: Entity, rhs: C) -> Entity where C: Component {
return lhs.add(rhs)
}
}
// MARK: - peek component
// MARK: - get components
public extension Entity {
public final func peekComponent<C: Component>() -> C? {
return componentMap[C.uct] as? C
public final func get<C>() -> C? where C: Component {
return nexus.get(component: C.identifier, for: identifier)
}
public final func peek<C: Component>(_ componentType: C.Type) -> C? {
return componentMap[componentType.uct] as? C
}
public final func peek<C: Component>(_ uct: UCT) -> C? {
return componentMap[uct] as? C
}
public final func peek<C: Component>(_ unwrapping: (C?) -> C) -> C {
return unwrapping(peekComponent())
}
@discardableResult
public final func peek<C: Component, Result>(_ applying: (C?) -> Result) -> Result {
return applying(peekComponent())
public final func get<C>(_ componentType: C.Type) -> C? where C: Component {
return get()
}
}
@ -116,40 +85,32 @@ public extension Entity {
public extension Entity {
@discardableResult
public final func remove(_ component: Component) -> Entity {
return remove(component.uct)
public final func remove<C>(_ component: C) -> Entity where C: Component {
return remove(component.identifier)
}
@discardableResult
public final func remove<C: Component>(_ componentType: C.Type) -> Entity {
let removedComponent: C? = componentMap.removeValue(forKey: C.uct) as? C
if let rComp: C = removedComponent {
notify(removed: rComp)
}
public final func remove<C>(_ componentType: C.Type) -> Entity where C: Component {
return remove(componentType.identifier)
}
@discardableResult
public final func remove(_ uct: ComponentIdentifier) -> Entity {
nexus.remove(component: uct, from: identifier)
return self
}
@discardableResult
public final func remove(_ uct: UCT) -> Entity {
let removedComponent = componentMap.removeValue(forKey: uct)
if let rComp = removedComponent {
assert(rComp.uct.type == uct.type)
notify(removed: rComp)
}
return self
}
public final func removeAll() {
componentMap.forEach { remove($0.key) }
public final func clear() {
nexus.clear(componentes: identifier)
}
@discardableResult
public static func -= <C: Component>(lhs: Entity, rhs: C) -> Entity {
public static func -= <C>(lhs: Entity, rhs: C) -> Entity where C: Component {
return lhs.remove(rhs)
}
@discardableResult
public static func -= <C: Component>(lhs: Entity, rhs: C.Type) -> Entity {
public static func -= <C>(lhs: Entity, rhs: C.Type) -> Entity where C: Component {
return lhs.remove(rhs)
}
}
@ -157,12 +118,61 @@ public extension Entity {
// MARK: - destroy/deinit entity
extension Entity {
final func destroy() {
removeAll()
UEI.free(uei)
notifyDestoryed()
clear()
//TODO: notifyDestoryed()
}
}
// MARK: - component tuple access
public extension Entity {
public func component<A>(_: A.Type) -> A where A: Component {
guard let a: A = nexus.get(component: A.identifier, for: identifier) else {
fatalError("Component Mapping Error: '\(A.self)' component was not found in entity '\(self)'")
}
return a
}
public func components<A, B>(_: A.Type, _: B.Type) -> (A, B) where A: Component, B: Component {
let a: A = component(A.self)
let b: B = component(B.self)
return (a, b)
}
public func components<A, B, C>(_: A.Type, _: B.Type, _: C.Type) -> (A, B, C) where A: Component, B: Component, C: Component {
let a: A = component(A.self)
let b: B = component(B.self)
let c: C = component(C.self)
return (a, b, c)
}
public func components<A, B, C, D>(_: A.Type, _: B.Type, _: C.Type, _: D.Type) -> (A, B, C, D) where A: Component, B: Component, C: Component, D: Component {
let a: A = component(A.self)
let b: B = component(B.self)
let c: C = component(C.self)
let d: D = component(D.self)
return (a, b, c, d)
}
public func components<A, B, C, D, E>(_: A.Type, _: B.Type, _: C.Type, _: D.Type, _: E.Type) -> (A, B, C, D, E) where A: Component, B: Component, C: Component, D: Component, E: Component {
let a: A = component(A.self)
let b: B = component(B.self)
let c: C = component(C.self)
let d: D = component(D.self)
let e: E = component(E.self)
return (a, b, c, d, e)
}
public func components<A, B, C, D, E, F>(_: A.Type, _: B.Type, _: C.Type, _: D.Type, _: E.Type, _: F.Type) -> (A, B, C, D, E, F) where A: Component, B: Component, C: Component, D: Component, E: Component, F: Component {
let a: A = component(A.self)
let b: B = component(B.self)
let c: C = component(C.self)
let d: D = component(D.self)
let e: E = component(E.self)
let f: F = component(F.self)
return (a, b, c, d, e, f)
}
}
/*
extension Entity: EventDispatcher {
public func dispatch<E>(_ event: E) where E: Event {
eventDispatcher.dispatch(event)
@ -188,25 +198,25 @@ extension Entity: EventDispatcher {
}
}
private func notify<C: Component>(add component: C) {
private func notify<C>(add component: C) {
unowned {
$0.dispatch(ComponentAdded(to: $0))
}
}
private func notify<C: Component>(update newComponent: C, previous previousComponent: C) {
private func notify<C>(update newComponent: C, previous previousComponent: C) {
unowned {
$0.dispatch(ComponentUpdated(at: $0))
}
}
private func notify<C: Component>(removed component: C) {
private func notify<C>(removed component: C) {
unowned {
$0.dispatch(ComponentRemoved(from: $0))
}
}
private func notify(removed component: Component) {
private func notify(removed component) {
//unowned { /* this keeps a reference since we need it */
dispatch(ComponentRemoved(from: self))
//}
@ -241,85 +251,4 @@ extension Entity: CustomPlaygroundQuickLookable {
return .text(self.description)
}
}
// MARK: - component tuple access
public extension Entity {
public func component<A: Component>(_: A.Type) -> A {
guard let a: A = componentMap[A.uct] as? A else {
fatalError("Component Mapping Error: '\(A.self)' component was not found in entity '\(self)'")
}
return a
}
public func components<A: Component, B: Component>(_: A.Type, _: B.Type) -> (A, B) {
let a: A = component(A.self)
let b: B = component(B.self)
return (a, b)
}
public func components<A: Component, B: Component, C: Component>(_: A.Type, _: B.Type, _: C.Type) -> (A, B, C) {
let a: A = component(A.self)
let b: B = component(B.self)
let c: C = component(C.self)
return (a, b, c)
}
public func components<A: Component, B: Component, C: Component, D: Component>(_: A.Type, _: B.Type, _: C.Type, _: D.Type) -> (A, B, C, D) {
let a: A = component(A.self)
let b: B = component(B.self)
let c: C = component(C.self)
let d: D = component(D.self)
return (a, b, c, d)
}
public func components<A: Component, B: Component, C: Component, D: Component, E: Component>(_: A.Type, _: B.Type, _: C.Type, _: D.Type, _: E.Type) -> (A, B, C, D, E) {
let a: A = component(A.self)
let b: B = component(B.self)
let c: C = component(C.self)
let d: D = component(D.self)
let e: E = component(E.self)
return (a, b, c, d, e)
}
public func components<A: Component, B: Component, C: Component, D: Component, E: Component, F: Component>(_: A.Type, _: B.Type, _: C.Type, _: D.Type, _: E.Type, _: F.Type) -> (A, B, C, D, E, F) {
let a: A = component(A.self)
let b: B = component(B.self)
let c: C = component(C.self)
let d: D = component(D.self)
let e: E = component(E.self)
let f: F = component(F.self)
return (a, b, c, d, e, f)
}
}
// MARK: - component closure access
public extension Entity {
public func component<R, A: Component>(_ closure: (A) -> R) -> R { return closure(component(A.self)) }
public func components<R, A: Component, B: Component>(_ closure: (A, B) -> R) -> R {
return closure(component(A.self), component(B.self))
}
public func components<R, A: Component, B: Component, C: Component>(_ closure: (A, B, C) -> R) -> R {
return closure(component(A.self), component(B.self), component(C.self))
}
public func components<R, A: Component, B: Component, C: Component, D: Component>(_ closure: (A, B, C, D) -> R) -> R {
return closure(component(A.self),
component(B.self),
component(C.self),
component(D.self))
}
public func components<R, A: Component, B: Component, C: Component, D: Component, E: Component>(_ closure: (A, B, C, D, E) -> R) -> R {
return closure(component(A.self),
component(B.self),
component(C.self),
component(D.self),
component(E.self))
}
public func components<R, A: Component, B: Component, C: Component, D: Component, E: Component, F: Component>(_ closure: (A, B, C, D, E, F) -> R) -> R {
return closure(component(A.self),
component(B.self),
component(C.self),
component(D.self),
component(E.self),
component(F.self) )
}
}
*/

View File

@ -1,118 +0,0 @@
//
// EntityHub.swift
// FirebladeECS
//
// Created by Christian Treffs on 09.10.17.
//
public class EntityHub: EventHandler {
public weak var delegate: EventHub?
public lazy var eventHub: DefaultEventHub = { return DefaultEventHub() }()
private(set) var entities: EntityStorage
private(set) var families: FamilyStorage
public init() {
entities = DefaultEntityStorage()
families = DefaultFamilyStorage()
self.delegate = eventHub
subscribe(event: handleEntityCreated)
subscribe(event: handleEntityDestroyed)
subscribe(event: handleComponentAdded)
subscribe(event: handleComponentUpdated)
subscribe(event: handleComponentRemoved)
subscribe(event: handleFamilyCreated)
subscribe(event: handleFamilyMemberAdded)
subscribe(event: handleFamilyMemberUpdated)
subscribe(event: handleFamilyMemberRemoved)
subscribe(event: handleFamilyDestroyed)
}
deinit {
unsubscribe(event: handleEntityCreated)
unsubscribe(event: handleEntityDestroyed)
unsubscribe(event: handleComponentAdded)
unsubscribe(event: handleComponentUpdated)
unsubscribe(event: handleComponentRemoved)
unsubscribe(event: handleFamilyCreated)
unsubscribe(event: handleFamilyMemberUpdated)
unsubscribe(event: handleFamilyMemberAdded)
unsubscribe(event: handleFamilyMemberRemoved)
unsubscribe(event: handleFamilyDestroyed)
}
}
// MARK: - creator entity
extension EntityHub {
public func createEntity() -> Entity {
let newEntity = Entity(uei: UEI.next, dispatcher: eventHub)
// ^ dispatches entity creation event here ^
let success: Bool = entities.add(newEntity)
assert(success == true, "Entity with the exact identifier already exists")
return newEntity
}
}
// MARK: - create/get family
extension EntityHub {
@discardableResult
public func family(with traits: FamilyTraits) -> (new: Bool, family: Family) {
if let existingFamily: Family = families[traits] {
return (new: false, family: existingFamily)
}
let newFamily = Family(traits: traits, eventHub: eventHub)
// ^ dispatches family creation event here ^
let success = families.add(newFamily)
assert(success, "Family with the exact traits already exists")
refreshFamilyCache()
return (new: true, family: newFamily)
}
fileprivate func onFamilyCreated(_ newFamily: Family) {
newFamily.update(membership: entities.iterator)
}
fileprivate func refreshFamilyCache() {
// TODO:
}
}
// MARK: - event handler
extension EntityHub {
func handleEntityCreated(_ e: EntityCreated) { print(e) }
func handleEntityDestroyed(_ e: EntityDestroyed) { print(e) }
func handleComponentAdded(_ e: ComponentAdded) { print(e) }
func handleComponentUpdated(_ e: ComponentUpdated) { print(e) }
func handleComponentRemoved(_ e: ComponentRemoved) { print(e) }
func handleFamilyCreated(_ e: FamilyCreated) {
print(e)
let newFamily: Family = e.family
onFamilyCreated(newFamily)
}
func handleFamilyMemberAdded(_ e: FamilyMemberAdded) { print(e) }
func handleFamilyMemberUpdated(_ e: FamilyMemberUpdated) { print(e) }
func handleFamilyMemberRemoved(_ e: FamilyMemberRemoved) { print(e) }
func handleFamilyDestroyed(_ e: FamilyDestroyed) { print(e) }
}

View File

@ -0,0 +1,96 @@
//
// EntityHub2.swift
// FirebladeECS
//
// Created by Christian Treffs on 11.10.17.
//
class EntityHub2 {
/*
fileprivate typealias CompType = ComponentIdentifier
fileprivate typealias CompIdxInCompArrayForType = Int
fileprivate typealias ComponentArray = ContiguousArray<Component>
fileprivate typealias EntityID = Int // aka EntityIdentifier
fileprivate var componentsByType: [CompType: ComponentArray] = [:]
fileprivate var entitiesById: [EntityID: Entity] = [:]
fileprivate var componentLookupByEntityId: [EntityID: [CompType:CompIdxInCompArrayForType]] = [:]
fileprivate var entityIdLookupByCompType: [CompType: [CompIdxInCompArrayForType:EntityID]] = [:]
fileprivate func query(_ compTypes: Component.Type...) -> [Entity] {
let types = compTypes.map{ $0.identifier }
fatalError()
}
fileprivate func queryTypes(_ types: [CompType]) -> [Entity] {
var types = types
var minSize: Int = Int.max
var minIndex: Int = 0
var index: Int = 0
for t in types {
guard let compArray = componentsByType[t] else {
fatalError("querying for non existing type \(t.type)")
}
let size: Int = compArray.count
if size < minSize {
minSize = size
minIndex = index
}
index += 1
}
let minType: CompType = types[minIndex]
if types.count >= 2 && minIndex != (types.count - 1) {
types.swapAt(minIndex, (types.count-1))
}
types.removeLast()
return iterate(minType, types)
}
fileprivate func iterate(_ minType: CompType, _ types: [CompType]) -> [Entity] {
var entitiesWithTypes: [Entity] = []
guard let compArray = componentsByType[minType] else {
fatalError("iterating non existing type \(minType.type)")
}
for i: CompIdxInCompArrayForType in 0..<compArray.count {
let component = compArray[i]
let compType = component.identifier
guard let ownerId: EntityID = entityIdLookupByCompType[compType]?[i]else {
fatalError("could not find owner id")
}
if has(allTypes: ownerId, types) {
guard let entity = entitiesById[ownerId] else {
fatalError("could not find entity")
}
entitiesWithTypes.append(entity)
}
}
return entitiesWithTypes
}
fileprivate func has(allTypes entityId: EntityID, _ types: [CompType]) -> Bool {
guard let entityTypes = componentLookupByEntityId[entityId]?.keys else { return false }
for requiredType: CompType in types {
if !entityTypes.contains(requiredType) { return false }
}
return true
}
*/
}

View File

@ -0,0 +1,30 @@
//
// EntityIdentifier.swift
// FirebladeECS
//
// Created by Christian Treffs on 08.10.17.
//
// MARK: Unique Entity Index
public typealias EntityIdentifier = UInt64 // provides 18446744073709551615 unique identifiers
public typealias EntityIndex = Int
public extension EntityIdentifier {
public var index: EntityIndex { return EntityIndex(self & 0xffffffff) } // shifts entity identifier by UInt32.max
}
public extension EntityIndex {
public var identifier: EntityIdentifier { return EntityIdentifier(self & -0xffffffff) } // shifts entity identifier by -UInt32.max
}
// MARK: Unique Entity Identifiable
public protocol UniqueEntityIdentifiable: Hashable {
var identifier: EntityIdentifier { get }
}
public extension UniqueEntityIdentifiable {
public var hashValue: Int { return identifier.hashValue }
}

View File

@ -6,59 +6,67 @@
//
public protocol EntityStorage {
func create(in nexus: Nexus) -> Entity
@discardableResult func add(_ entity: Entity) -> Bool
var iterator: AnyIterator<Entity> { get }
func has(_ entity: Entity) -> Bool
func has(_ id: UEI) -> Bool
func has(_ id: EntityIdentifier) -> Bool
func has(_ named: String) -> Bool
func get(_ id: UEI) -> Entity?
subscript(_ id: UEI) -> Entity? { get }
func get(_ id: EntityIdentifier) -> Entity?
subscript(_ id: EntityIdentifier) -> Entity? { get }
func get(_ named: String) -> Entity?
subscript(_ named: String) -> Entity? { get }
@discardableResult func remove(_ id: UEI) -> Bool
@discardableResult func remove(_ id: EntityIdentifier) -> Bool
func clear()
}
class DefaultEntityStorage: EntityStorage {
class GeneralEntityStorage: EntityStorage {
fileprivate typealias Index = Set<Entity>.Index
fileprivate var entities: Set<Entity> = Set<Entity>()
fileprivate typealias Index = ContiguousArray<Entity>.Index
fileprivate var entities: ContiguousArray<Entity> = ContiguousArray<Entity>()
var iterator: AnyIterator<Entity> {
return AnyIterator(entities.makeIterator())
}
func create(in nexus: Nexus) -> Entity {
let nextIndex = EntityIdentifier(entities.count) // TODO: should be next free index -> reuse
return Entity(nexus: nexus, id: nextIndex)
}
@discardableResult
func add(_ entity: Entity) -> Bool {
let (success, _) = entities.insert(entity)
return success
assert(!entities.contains(entity), "entity already present")
entities.append(entity)
return true
}
func has(_ entity: Entity) -> Bool {
return entities.contains(entity)
}
func has(_ id: UEI) -> Bool {
return entities.contains { $0.uei == id }
func has(_ id: EntityIdentifier) -> Bool {
return entities.contains { $0.identifier == id }
}
func has(_ named: String) -> Bool {
return entities.contains { $0.name == named }
}
func get(_ id: UEI) -> Entity? {
func get(_ id: EntityIdentifier) -> Entity? {
guard let index = index(id) else { return nil }
return entities[index]
}
subscript(id: UEI) -> Entity? { return get(id) }
subscript(id: EntityIdentifier) -> Entity? { return get(id) }
func get(_ named: String) -> Entity? {
guard let index: Index = index(named) else { return nil }
@ -68,7 +76,7 @@ class DefaultEntityStorage: EntityStorage {
subscript(named: String) -> Entity? { return get(named) }
@discardableResult
func remove(_ id: UEI) -> Bool {
func remove(_ id: EntityIdentifier) -> Bool {
guard let index: Index = index(id) else { return false }
entities.remove(at: index)
return true
@ -80,8 +88,9 @@ class DefaultEntityStorage: EntityStorage {
// MARK: - internal
fileprivate func index(_ id: UEI) -> Index? {
return entities.index { $0.uei == id }
fileprivate func index(_ id: EntityIdentifier) -> Index? {
return Index(id)
//return entities.index { $0.uei == id }
}
fileprivate func index(_ named: String) -> Index? {

View File

@ -6,49 +6,41 @@
//
public struct EntityCreated: Event {
unowned let entity: Entity
let entityId: EntityIdentifier
}
public struct EntityDestroyed: Event {
unowned let entity: Entity
let entityId: EntityIdentifier
}
public struct ComponentAdded: Event {
//let component: Component
unowned let to: Entity
let component: ComponentIdentifier
let to: EntityIdentifier
}
public struct ComponentUpdated: Event {
//let component: Component
//let previous: Component
unowned let at: Entity
let at: EntityIdentifier
}
public struct ComponentRemoved: Event {
//let component: Component
unowned let from: Entity
let component: ComponentIdentifier
let from: EntityIdentifier
}
struct FamilyMemberAdded: Event {
unowned let member: Entity
unowned let to: Family
}
struct FamilyMemberUpdated: Event {
unowned let newMember: Entity
unowned let oldMember: Entity
unowned let `in`: Family
let member: EntityIdentifier
let to: FamilyTraits
}
struct FamilyMemberRemoved: Event {
unowned let member: Entity
unowned let from: Family
let member: EntityIdentifier
let from: FamilyTraits
}
struct FamilyCreated: Event {
unowned let family: Family
let family: FamilyTraits
}
struct FamilyDestroyed: Event {
unowned let family: Family
let family: FamilyTraits
}

View File

@ -8,30 +8,28 @@
// MARK: - family
public final class Family {
public var delegate: EventHub?
fileprivate var dispatcher: EventDispatcher
public var nexus: Nexus
// members of this Family must conform to these traits
public let traits: FamilyTraits
public private(set) var members: Set<Entity>
public private(set) var members: ContiguousArray<EntityIdentifier>
public init(traits: FamilyTraits, eventHub: EventHub & EventDispatcher) {
public init(_ nexus: Nexus, traits: FamilyTraits) {
members = Set<Entity>()
members = ContiguousArray<EntityIdentifier>()
members.reserveCapacity(64)
self.traits = traits
delegate = eventHub
dispatcher = eventHub
self.nexus = nexus
subscribe(event: handleComponentAddedToEntity)
/*subscribe(event: handleComponentAddedToEntity)
subscribe(event: handleComponentUpdatedAtEntity)
subscribe(event: handleComponentRemovedFromEntity)
*/
defer {
notifyCreated()
//notifyCreated()
}
}
@ -40,12 +38,12 @@ public final class Family {
members.removeAll()
unsubscribe(event: handleComponentAddedToEntity)
/*unsubscribe(event: handleComponentAddedToEntity)
unsubscribe(event: handleComponentUpdatedAtEntity)
unsubscribe(event: handleComponentRemovedFromEntity)
unsubscribe(event: handleComponentRemovedFromEntity)*/
defer {
notifyDestroyed()
// notifyDestroyed()
}
}
@ -54,73 +52,87 @@ public final class Family {
// MARK: - update family membership
extension Family {
func update(membership entityIds: AnyIterator<EntityIdentifier>) {
while let entityId: EntityIdentifier = entityIds.next() {
update(membership: entityId)
}
}
func update(membership entities: AnyIterator<Entity>) {
while let entity: Entity = entities.next() {
update(membership: entity)
}
}
fileprivate func update(membership entityId: EntityIdentifier) {
guard let entity = nexus.get(entity: entityId) else {
fatalError("no entity with id \(entityId) in \(nexus)")
}
update(membership: entity)
}
fileprivate func update(membership entity: Entity) {
let isMatch: Bool = traits.isMatch(entity)
switch isMatch {
case true:
push(entity)
push(entity.identifier)
case false:
remove(entity)
remove(entity.identifier)
}
}
fileprivate func push(_ entity: Entity) {
let (added, member) = members.insert(entity)
switch added {
case true:
notify(added: member)
case false:
notify(update: entity, previous: member)
}
fileprivate func push(_ entityId: EntityIdentifier) {
assert(!members.contains(entityId), "entity with id \(entityId) already in family")
members.append(entityId)
// TODO: notify(added: entityId)
}
fileprivate func remove(_ entity: Entity) {
let removed: Entity? = members.remove(entity)
fileprivate func remove(_ entityId: EntityIdentifier) {
guard let index: Int = members.index(of: entityId) else {
fatalError("removing entity id \(entityId) that is not in family")
}
let removed: EntityIdentifier? = members.remove(at: index)
assert(removed != nil)
if let removedEntity: Entity = removed {
notify(removed: removedEntity)
if let removedEntity: EntityIdentifier = removed {
// TODO: notify(removed: removedEntity)
}
}
}
// MARK: - entity accessors
// MARK: - member iterator
extension Family {
public func entities(_ apply: (Entity) -> Void ) {
members.lazy.forEach(apply)
func makeIterator<A>() -> AnyIterator<(Entity, A)> where A: Component {
var members = self.members.makeIterator()
return AnyIterator<(Entity, A)> { [unowned self] in
let entityId: EntityIdentifier = members.next()!
let entity: Entity = self.nexus.get(entity: entityId)!
let a: A = entity.get()!
return (entity, a)
}
}
public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Entity) throws -> Result) rethrows -> Result {
return try members.lazy.reduce(initialResult, nextPartialResult)
}
}
// MARK: - component accessors
extension Family {
public func component<A: Component>(_ apply: (A) -> Void) {
members.lazy.forEach { $0.component(apply) }
}
public func components<A: Component, B: Component>(_ apply: (A, B) -> Void) {
members.lazy.forEach { $0.components(apply) }
}
public func components<A: Component, B: Component, C: Component>(_ apply: (A, B, C) -> Void) {
members.lazy.forEach { $0.components(apply) }
}
public func components<A: Component, B: Component, C: Component, D: Component>(_ apply: (A, B, C, D) -> Void) {
members.lazy.forEach { $0.components(apply) }
}
public func components<A: Component, B: Component, C: Component, D: Component, E: Component>(_ apply: (A, B, C, D, E) -> Void) {
members.lazy.forEach { $0.components(apply) }
}
public func components<A: Component, B: Component, C: Component, D: Component, E: Component, F: Component>(_ apply: (A, B, C, D, E, F) -> Void) {
members.lazy.forEach { $0.components(apply) }
func makeIterator<A, B>() -> AnyIterator<(Entity, A, B)> where A: Component, B: Component {
var members = self.members.makeIterator()
return AnyIterator<(Entity, A, B)> { [unowned self] in
let entityId: EntityIdentifier = members.next()!
let entity: Entity = self.nexus.get(entity: entityId)!
let a: A = entity.get()!
let b: B = entity.get()!
return (entity, a, b)
}
}
func makeIterator<A, B, C>() -> AnyIterator<(Entity, A, B, C)> where A: Component, B: Component, C: Component {
var members = self.members.makeIterator()
return AnyIterator<(Entity, A, B, C)> { [unowned self] in
let entityId: EntityIdentifier = members.next()!
let entity: Entity = self.nexus.get(entity: entityId)!
let a: A = entity.get()!
let b: B = entity.get()!
let c: C = entity.get()!
return (entity, a, b, c)
}
}
}
// MARK: - Equatable
@ -137,46 +149,27 @@ extension Family: Hashable {
}
}
/*
// MARK: - event dispatcher
extension Family: EventDispatcher {
public func dispatch<E>(_ event: E) where E: Event {
dispatcher.dispatch(event)
}
fileprivate func unowned(closure: @escaping (Family) -> Void) {
let unownedClosure = { [unowned self] in
closure(self)
}
unownedClosure()
}
fileprivate func notifyCreated() {
unowned {
$0.dispatch(FamilyCreated(family: $0))
}
dispatch(FamilyCreated(family: traits))
}
fileprivate func notify(added newEntity: Entity) {
unowned { [unowned newEntity] in
$0.dispatch(FamilyMemberAdded(member: newEntity, to: $0))
}
fileprivate func notify(added newEntity: EntityIdentifier) {
dispatch(FamilyMemberAdded(member: newEntity, to: traits))
}
fileprivate func notify(update newEntity: Entity, previous oldEntity: Entity) {
unowned { [unowned newEntity, unowned oldEntity] in
$0.dispatch(FamilyMemberUpdated(newMember: newEntity, oldMember: oldEntity, in: $0) )
}
}
fileprivate func notify(removed removedEntity: Entity) {
unowned { [unowned removedEntity] in
$0.dispatch(FamilyMemberRemoved(member: removedEntity, from: $0))
}
fileprivate func notify(removed removedEntity: EntityIdentifier) {
dispatch(FamilyMemberRemoved(member: removedEntity, from: traits))
}
fileprivate func notifyDestroyed() {
//dispatch(event: FamilyDestroyed())
// dispatch(FamilyDestroyed(family: self))
dispatch(FamilyDestroyed(family: traits))
}
}
@ -184,18 +177,16 @@ extension Family: EventDispatcher {
extension Family: EventHandler {
fileprivate final func handleComponentAddedToEntity(event: ComponentAdded) {
unowned let entity: Entity = event.to
update(membership: entity)
update(membership: event.to)
}
fileprivate final func handleComponentUpdatedAtEntity(event: ComponentUpdated) {
unowned let entity: Entity = event.at
update(membership: entity)
update(membership: event.at)
}
fileprivate final func handleComponentRemovedFromEntity(event: ComponentRemoved) {
unowned let entity: Entity = event.from
update(membership: entity)
update(membership: event.from)
}
}
*/

View File

@ -22,7 +22,7 @@ public protocol FamilyStorage {
func clear()
}
class DefaultFamilyStorage: FamilyStorage {
class GeneralFamilyStorage: FamilyStorage {
fileprivate typealias Index = Dictionary<FamilyTraits, Family>.Index
fileprivate var families: [FamilyTraits: Family] = [:]

View File

@ -7,20 +7,20 @@
// trait/predicate/characteristic
public struct FamilyTraits {
let hasAll: Set<UCT>
let hasAny: Set<UCT>
let hasNone: Set<UCT>
let hasAll: Set<ComponentIdentifier>
let hasAny: Set<ComponentIdentifier>
let hasNone: Set<ComponentIdentifier>
public init(hasAll: Set<UCT>, hasAny: Set<UCT>, hasNone: Set<UCT>) {
public init(hasAll: Set<ComponentIdentifier>, hasAny: Set<ComponentIdentifier>, hasNone: Set<ComponentIdentifier>) {
self.hasAll = hasAll
self.hasAny = hasAny
self.hasNone = hasNone
assert(isValid)
}
fileprivate var iteratorAll: SetIterator<UCT> { return hasAll.makeIterator() }
fileprivate var iteratorAny: SetIterator<UCT> { return hasAny.makeIterator() }
fileprivate var iteratorNone: SetIterator<UCT> { return hasNone.makeIterator() }
fileprivate var iteratorAll: SetIterator<ComponentIdentifier> { return hasAll.makeIterator() }
fileprivate var iteratorAny: SetIterator<ComponentIdentifier> { return hasAny.makeIterator() }
fileprivate var iteratorNone: SetIterator<ComponentIdentifier> { return hasNone.makeIterator() }
}
extension FamilyTraits {
var isValid: Bool {
@ -35,7 +35,7 @@ extension FamilyTraits {
fileprivate func matches(all entity: Entity) -> Bool {
var all = iteratorAll
while let uct: UCT = all.next() {
while let uct: ComponentIdentifier = all.next() {
guard entity.has(uct) else { return false }
}
return true
@ -43,7 +43,7 @@ extension FamilyTraits {
fileprivate func matches(none entity: Entity) -> Bool {
var none = iteratorNone
while let uct: UCT = none.next() {
while let uct: ComponentIdentifier = none.next() {
guard !entity.has(uct) else { return false }
}
return true
@ -52,7 +52,7 @@ extension FamilyTraits {
fileprivate func matches(any entity: Entity) -> Bool {
guard !hasAny.isEmpty else { return true }
var any = iteratorAny
while let uct: UCT = any.next() {
while let uct: ComponentIdentifier = any.next() {
if entity.has(uct) {
return true
}
@ -92,9 +92,9 @@ extension FamilyTraits: Hashable {
extension FamilyTraits: CustomStringConvertible {
public var description: String {
let all: String = hasAll.map { "\($0.type)" }.joined(separator: " AND ")
let any: String = hasAny.map { "\($0.type)" }.joined(separator: " OR ")
let none: String = hasNone.map { "!\($0.type)"}.joined(separator: " NOT ")
let all: String = hasAll.map { "\($0.self)" }.joined(separator: " AND ")
let any: String = hasAny.map { "\($0.self)" }.joined(separator: " OR ")
let none: String = hasNone.map { "!\($0.self)"}.joined(separator: " NOT ")
let out: String = ["\(all)", "\(any)", "\(none)"].joined(separator: " AND ")
//TODO: nicer
return "FamilyTraits(\(out))"

View File

@ -0,0 +1,107 @@
//
// Nexus+Component.swift
// FirebladeECS
//
// Created by Christian Treffs on 13.10.17.
//
extension Nexus {
public func has(component: ComponentIdentifier, entity: EntityIdentifier) -> Bool {
let hash: EntityComponentHash = component.hashValue(using: entity.index)
return has(hash)
}
fileprivate func has(_ hash: EntityComponentHash) -> Bool {
return componentIndexByEntityComponentHash[hash] != nil
}
public func count(components entityId: EntityIdentifier) -> Int {
switch componentIdsByEntityIdx[entityId.index] {
case .some(let componentIds):
return componentIds.count
case .none:
return 0
}
}
public func add<C>(component: C, to entityId: EntityIdentifier) where C: Component {
let componentId = C.identifier
let entityIdx = entityId.index
let hash: EntityComponentHash = componentId.hashValue(using: entityIdx)
assert(!has(hash), "ComponentAdd collision: \(entityId) already has a component \(component)")
var newComponentIndex: ComponentIndex = ComponentIndex.invalid
if componentsByType[componentId] != nil {
newComponentIndex = componentsByType[componentId]!.count // TODO: get next free index
componentsByType[componentId]!.insert(component, at: newComponentIndex)
} else {
newComponentIndex = 0
componentsByType[componentId] = UniformComponents(arrayLiteral: component)
}
if componentIdsByEntityIdx[entityIdx] != nil {
componentIdsByEntityIdx[entityIdx]!.append(componentId)
} else {
componentIdsByEntityIdx[entityIdx] = ComponentIdentifiers()
componentIdsByEntityIdx[entityIdx]!.append(componentId)
}
// assign entity / component to index
componentIndexByEntityComponentHash[hash] = newComponentIndex
notify(ComponentAdded(component: componentId, to: entityId))
}
public func get<C>(component componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> C? where C: Component {
let hash: EntityComponentHash = componentId.hashValue(using: entityId.index)
return get(hash)
}
fileprivate func get<C>(_ hash: EntityComponentHash) -> C? where C: Component {
let componentId: ComponentIdentifier = C.identifier
guard let componentIdx: ComponentIndex = componentIndexByEntityComponentHash[hash] else { return nil }
guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil }
guard let typedComponent: C = uniformComponents[componentIdx] as? C else { return nil }
return typedComponent
}
public func get(components entityId: EntityIdentifier) -> ComponentIdentifiers? {
return componentIdsByEntityIdx[entityId.index]
}
@discardableResult
public func remove(component componentId: ComponentIdentifier, from entityId: EntityIdentifier) -> Bool {
let hash: EntityComponentHash = componentId.hashValue(using: entityId.index)
guard let componentIdx: ComponentIndex = componentIndexByEntityComponentHash.removeValue(forKey: hash) else {
assert(false, "ComponentRemove failure: entity \(entityId) has no component \(componentId)")
return false
}
guard componentsByType[componentId]?.remove(at: componentIdx) != nil else {
assert(false, "ComponentRemove failure: no component instance for \(componentId) with the given index \(componentIdx)")
return false
}
// FIXME: this is expensive
if let removeIndex: Int = get(components: entityId)?.index(where: { $0 == componentId }) {
componentIdsByEntityIdx[entityId.index]?.remove(at: removeIndex)
}
notify(ComponentRemoved(component: componentId, from: entityId))
return true
}
public func clear(componentes entityId: EntityIdentifier) {
guard let componentIds = get(components: entityId) else { return }
componentIds.forEach { (componentId: ComponentIdentifier) in
remove(component: componentId, from: entityId)
}
}
}

View File

@ -0,0 +1,46 @@
//
// Nexus+Entity.swift
// FirebladeECS
//
// Created by Christian Treffs on 13.10.17.
//
extension Nexus {
public func create(entity name: String? = nil) -> Entity {
let newEntityIndex: EntityIndex = entities.count // TODO: use free entity index
let newEntityIdentifier: EntityIdentifier = newEntityIndex.identifier
let newEntity = Entity(nexus: self, id: newEntityIdentifier, name: name)
entities.insert(newEntity, at: newEntityIndex)
notify(EntityCreated(entityId: newEntityIdentifier))
return newEntity
}
public func has(entity entityId: EntityIdentifier) -> Bool {
return entities.count > entityId.index // TODO: reuse free index
}
public func get(entity entityId: EntityIdentifier) -> Entity? {
guard has(entity: entityId) else { return nil }
return entities[entityId.index]
}
@discardableResult
public func destroy(entity entityId: EntityIdentifier) -> Bool {
guard has(entity: entityId) else {
assert(false, "EntityRemove failure: no entity \(entityId) to remove")
return false
}
clear(componentes: entityId)
// FIXME: this is wrong since it removes the entity that should be reused
entities.remove(at: entityId.index)
notify(EntityDestroyed(entityId: entityId))
return true
}
}

View File

@ -0,0 +1,36 @@
//
// Nexus+Family.swift
// FirebladeECS
//
// Created by Christian Treffs on 13.10.17.
//
extension Nexus {
/*@discardableResult
public func family(with traits: FamilyTraits) -> (new: Bool, family: Family) {
if let existingFamily: Family = families.get(traits) {
return (new: false, family: existingFamily)
}
let newFamily = Family(self, traits: traits)
// ^ dispatches family creation event here ^
let success = families.add(newFamily)
assert(success, "Family with the exact traits already exists")
refreshFamilyCache()
return (new: true, family: newFamily)
}
func onFamilyCreated(_ newFamily: Family) {
newFamily.update(membership: entities.iterator)
}
func refreshFamilyCache() {
// TODO:
}
*/
}

View File

@ -0,0 +1,71 @@
//
// Nexus.swift
// FirebladeECS
//
// Created by Christian Treffs on 09.10.17.
//
public typealias EntityComponentHash = Int
public typealias ComponentIndex = Int
public extension ComponentIndex {
static let invalid: ComponentIndex = Int.min
}
public typealias UniformComponents = ContiguousArray<Component>
public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier>
public typealias Entities = ContiguousArray<Entity>
public class Nexus {
/// - Index: index value matching entity identifier shifted to Int
/// - Value: each element is a entity instance
var entities: Entities
/// - Key: component type identifier
/// - Value: each element is a component instance of the same type (uniform). New component instances are appended.
var componentsByType: [ComponentIdentifier: UniformComponents]
/// - Key: 'entity id' - 'component type' hash that uniquely links both
/// - Value: each element is an index pointing to the component instance index in the componentsByType map.
var componentIndexByEntityComponentHash: [EntityComponentHash: ComponentIndex]
var componentIdsByEntityIdx: [EntityIndex: ComponentIdentifiers]
public init() {
entities = Entities()
componentsByType = [:]
componentIndexByEntityComponentHash = [:]
componentIdsByEntityIdx = [:]
}
}
extension Nexus {
func notify(_ event: Event) {
Log.debug(event)
// TODO: implement
}
}
/*
// MARK: - event handler
extension Nexus {
func handleEntityCreated(_ e: EntityCreated) { print(e) }
func handleEntityDestroyed(_ e: EntityDestroyed) { print(e) }
func handleComponentAdded(_ e: ComponentAdded) { print(e) }
func handleComponentUpdated(_ e: ComponentUpdated) { print(e) }
func handleComponentRemoved(_ e: ComponentRemoved) { print(e) }
func handleFamilyCreated(_ e: FamilyCreated) {
print(e)
let newFamily: Family = e.family
onFamilyCreated(newFamily)
}
func handleFamilyMemberAdded(_ e: FamilyMemberAdded) { print(e) }
func handleFamilyMemberRemoved(_ e: FamilyMemberRemoved) { print(e) }
func handleFamilyDestroyed(_ e: FamilyDestroyed) { print(e) }
}
*/

View File

@ -14,7 +14,3 @@ public protocol System: class {
func startup()
func shutdown()
}
public protocol EntitySystem: System {
//TODO: var systemFamily: Family { get }
}

View File

@ -1,41 +0,0 @@
//
// UCT.swift
// FirebladeECS
//
// Created by Christian Treffs on 08.10.17.
//
// MARK: Unique Component Type
/// Unique Component Type
public struct UCT {
let objectIdentifier: ObjectIdentifier
let type: Component.Type
init(_ componentType: Component.Type) {
objectIdentifier = ObjectIdentifier(componentType)
type = componentType
}
init(_ component: Component) {
let componentType: Component.Type = component.uct.type
self.init(componentType)
}
}
extension UCT: Equatable {
public static func ==(lhs: UCT, rhs: UCT) -> Bool {
return lhs.objectIdentifier == rhs.objectIdentifier
}
}
extension UCT: Hashable {
public var hashValue: Int {
return objectIdentifier.hashValue
}
}
// MARK: Unique Component Identifiable
public protocol UniqueComponentIdentifiable {
static var uct: UCT { get }
var uct: UCT { get }
}

View File

@ -1,36 +0,0 @@
//
// UEI.swift
// FirebladeECS
//
// Created by Christian Treffs on 08.10.17.
//
// MARK: Unique Entity Index
public typealias UEI = UInt32 // provides 4294967295 unique identifiers
public extension UEI {
private static var currentUEI: UEI = UInt32.min // starts at 0
/// Provides the next (higher/free) unique entity identifer.
/// Minimum: 1, maximum: 4294967295.
/// - Returns: next higher unique entity identifer.
public static var next: UEI {
currentUEI += 1
return currentUEI
}
internal static func free(_ uei: UEI) {
// TODO: free used index
}
}
// MARK: Unique Entity Identifiable
public protocol UniqueEntityIdentifiable: Hashable {
var uei: UEI { get }
}
public extension UniqueEntityIdentifiable {
public var hashValue: Int { return uei.hashValue }
}

View File

@ -13,6 +13,15 @@ struct Name: Component {
let name: String
}
struct Position: Component {
let x: Int
let y: Int
}
struct Velocity: Component {
let a: Float
}
class DebugEventHandler: EventHandler {
let eventHub = DefaultEventHub()

View File

@ -0,0 +1,24 @@
//
// EntityComponentTests.swift
// FirebladeECS
//
// Created by Christian Treffs on 13.10.17.
//
import XCTest
/*@testable */import FirebladeECS
class EntityComponentTests: XCTestCase {
func testComponentAccess() {
}
}

View File

@ -1,58 +0,0 @@
//
// EntityHubTests.swift
// FirebladeECS
//
// Created by Christian Treffs on 09.10.17.
//
import XCTest
@testable import FirebladeECS
class EntityHubTests: XCTestCase {
let entityHub = EntityHub()
override func setUp() {
super.setUp()
entityHub.eventHub.sniffer = self
}
func testCreateEntity() {
let newEntity: Entity = entityHub.createEntity()
XCTAssert(newEntity.hasComponents == false)
//TODO: XCTAssert(entityHub.entities[newEntity.uei] == newEntity)
//TODO: XCTAssert(entityHub.entities[newEntity.uei] === newEntity)
}
func testCreateEntityAndAddComponent() {
let newEntity: Entity = entityHub.createEntity()
let emptyComp = EmptyComponent()
newEntity.add(component: emptyComp)
XCTAssert(newEntity.hasComponents)
XCTAssert(newEntity.numComponents == 1)
//TODO: XCTAssert(entityHub.entities[newEntity.uei] == newEntity)
//TODO: XCTAssert(entityHub.entities[newEntity.uei] === newEntity)
}
}
extension EntityHubTests: EventSniffer {
public func subscriber(subsribed to: UET) {
}
public func subscriber(unsubsribed from: UET) {
}
public func dispatched<E>(event: E) where E: Event {
}
}

View File

@ -10,40 +10,5 @@ import XCTest
class FamilyTests: XCTestCase {
let entityHub: EntityHub = EntityHub()
func testFamily() {
let e1 = entityHub.createEntity()
e1 += EmptyComponent()
let e2 = entityHub.createEntity()
e2 += EmptyComponent()
e1 += Name(name: "Sarah")
let (empty, name) = e1.components(EmptyComponent.self, Name.self)
print(empty, name)
e1.components { (empty: EmptyComponent, name: Name) in
print(empty, name)
}
let traits = FamilyTraits(hasAll: [EmptyComponent.uct], hasAny: [], hasNone: [])
let (new, family) = entityHub.family(with: traits)
XCTAssert(new == true)
let (new2, _) = entityHub.family(with: traits)
XCTAssert(new2 == false)
let e3 = entityHub.createEntity()
e3 += EmptyComponent()
e3 += Name(name: "Michael")
e2.remove(EmptyComponent.self)
family.components { (name: Name, empty: EmptyComponent) in
print(name, empty)
}
}
}

View File

@ -0,0 +1,44 @@
//
// NexusTests.swift
// FirebladeECS
//
// Created by Christian Treffs on 09.10.17.
//
import XCTest
@testable import FirebladeECS
class NexusTests: XCTestCase {
let nexus = Nexus()
func testNexus() {
let e0 = nexus.create(entity: "E0")
XCTAssert(e0.identifier == 0)
XCTAssert(e0.identifier.index == 0)
let p0 = Position(x: 1, y: 2)
let n0 = Name(name: "FirstName")
e0.add(p0)
e0.add(n0)
let rE1 = nexus.get(entity: e0.identifier)
let rN0: Name = rE1!.component(Name.self)
let rP0: Position = rE1!.component(Position.self)
XCTAssert(rN0.name == "FirstName")
XCTAssert(rP0.x == 1)
XCTAssert(rP0.y == 2)
let count = nexus.count(components: rE1!.identifier)
XCTAssert(count == 2)
}
}

View File

@ -10,98 +10,6 @@ import XCTest
class PerformanceTests: XCTestCase {
var eventHub: DefaultEventHub = DefaultEventHub()
var entityArray: [Entity] = [Entity]()
var entityContArray: ContiguousArray<Entity> = ContiguousArray<Entity>()
var entitySet: Set<Entity> = Set<Entity>()
var entityMap: [UEI: Entity] = [UEI: Entity]()
static let maxNum: Int = 65536
override func setUp() {
super.setUp()
entitySet.removeAll()
entitySet.reserveCapacity(0)
entityMap.removeAll()
entityMap.reserveCapacity(0)
entityArray.removeAll()
entityArray.reserveCapacity(0)
entityContArray.removeAll()
entityContArray.reserveCapacity(0)
}
func testEntitySetLinearInsert() {
measure {
for i in 0..<65536 {
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
entitySet.insert(newEntity)
}
}
}
func testEntityMapLinearInsert() {
entityMap.removeAll()
entityMap.reserveCapacity(0)
measure {
for i in 0..<65536 {
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
entityMap[newEntity.uei] = newEntity
}
}
}
func testEntityMapUpdateLinarInsert() {
entityMap.removeAll()
entityMap.reserveCapacity(0)
measure {
for i in 0..<65536 {
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
entityMap.updateValue(newEntity, forKey: newEntity.uei)
}
}
}
func testEntityContArrayLinearInsert() {
measure {
for i in 0..<65536 {
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
entityContArray.append(newEntity)
}
}
}
func testEntityContArrayFixPosInsert() {
let count: Int = Int(65536)
var entityContArray2 = ContiguousArray<Entity!>(repeating: nil, count: count)
entityContArray2.reserveCapacity(count)
measure {
for i: Int in 0..<count-1 {
let uei = UEI(i)
let newEntity = Entity(uei: uei, dispatcher: eventHub)
entityContArray2[i] = newEntity
}
}
}
func testEntityArrayLinearInsert() {
measure {
for i in 0..<65536 {
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
entityArray.append(newEntity)
}
}
}
}

View File

@ -0,0 +1,18 @@
//
// SystemTests.swift
// FirebladeECS
//
// Created by Christian Treffs on 11.10.17.
//
import XCTest
import FirebladeECS
class SystemTests: XCTestCase {
}