Family iteration working
This commit is contained in:
parent
9ffcb3d4f7
commit
2386fab4c3
|
|
@ -5,7 +5,7 @@
|
||||||
// Created by Christian Treffs on 08.10.17.
|
// Created by Christian Treffs on 08.10.17.
|
||||||
//
|
//
|
||||||
|
|
||||||
public protocol Component: UniqueComponentIdentifiable {}
|
public protocol Component: class, UniqueComponentIdentifiable {}
|
||||||
|
|
||||||
// MARK: UCI
|
// MARK: UCI
|
||||||
extension Component {
|
extension Component {
|
||||||
|
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
//
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
@ -66,9 +66,17 @@ public extension Entity {
|
||||||
// MARK: - add component(s)
|
// MARK: - add component(s)
|
||||||
public extension Entity {
|
public extension Entity {
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public final func assign(_ components: Component...) -> Entity {
|
||||||
|
components.forEach { (comp: Component) in
|
||||||
|
nexus.assign(component: comp, to: self)
|
||||||
|
}
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public final func assign<C>(_ component: C) -> Entity where C: Component {
|
public final func assign<C>(_ component: C) -> Entity where C: Component {
|
||||||
nexus.assign(component: component, to: identifier)
|
nexus.assign(component: component, to: self)
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,46 +147,50 @@ extension Entity {
|
||||||
// MARK: - component tuple access
|
// MARK: - component tuple access
|
||||||
public extension Entity {
|
public extension Entity {
|
||||||
|
|
||||||
public func component<A>(_: A.Type) -> A where A: Component {
|
public func component<A>() -> () -> A? where A: Component {
|
||||||
guard let a: A = nexus.get(component: A.identifier, for: identifier) else {
|
func getComponent() -> A? {
|
||||||
fatalError("Component Mapping Error: '\(A.self)' component was not found in entity '\(self)'")
|
return component(A.self)
|
||||||
}
|
}
|
||||||
return a
|
return getComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
public func component<A>(_ compType: A.Type = A.self) -> A? where A: Component {
|
||||||
|
return nexus.get(component: A.identifier, for: identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func components<A, B>(_: A.Type, _: B.Type) -> (A, B) where A: Component, B: Component {
|
public func components<A, B>(_: A.Type, _: B.Type) -> (A, B) where A: Component, B: Component {
|
||||||
let a: A = component(A.self)
|
let a: A! = component(A.self)
|
||||||
let b: B = component(B.self)
|
let b: B! = component(B.self)
|
||||||
return (a, b)
|
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 {
|
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 a: A! = component(A.self)
|
||||||
let b: B = component(B.self)
|
let b: B! = component(B.self)
|
||||||
let c: C = component(C.self)
|
let c: C! = component(C.self)
|
||||||
return (a, b, c)
|
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 {
|
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 a: A! = component(A.self)
|
||||||
let b: B = component(B.self)
|
let b: B! = component(B.self)
|
||||||
let c: C = component(C.self)
|
let c: C! = component(C.self)
|
||||||
let d: D = component(D.self)
|
let d: D! = component(D.self)
|
||||||
return (a, b, c, d)
|
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 {
|
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 a: A! = component(A.self)
|
||||||
let b: B = component(B.self)
|
let b: B! = component(B.self)
|
||||||
let c: C = component(C.self)
|
let c: C! = component(C.self)
|
||||||
let d: D = component(D.self)
|
let d: D! = component(D.self)
|
||||||
let e: E = component(E.self)
|
let e: E! = component(E.self)
|
||||||
return (a, b, c, d, e)
|
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 {
|
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 a: A! = component(A.self)
|
||||||
let b: B = component(B.self)
|
let b: B! = component(B.self)
|
||||||
let c: C = component(C.self)
|
let c: C! = component(C.self)
|
||||||
let d: D = component(D.self)
|
let d: D! = component(D.self)
|
||||||
let e: E = component(E.self)
|
let e: E! = component(E.self)
|
||||||
let f: F = component(F.self)
|
let f: F! = component(F.self)
|
||||||
return (a, b, c, d, e, f)
|
return (a, b, c, d, e, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
//
|
|
||||||
// EntityStorage.swift
|
|
||||||
// FirebladeECS
|
|
||||||
//
|
|
||||||
// Created by Christian Treffs on 10.10.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
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: EntityIdentifier) -> Bool
|
|
||||||
func has(_ named: String) -> Bool
|
|
||||||
|
|
||||||
func get(_ id: EntityIdentifier) -> Entity?
|
|
||||||
subscript(_ id: EntityIdentifier) -> Entity? { get }
|
|
||||||
|
|
||||||
func get(_ named: String) -> Entity?
|
|
||||||
subscript(_ named: String) -> Entity? { get }
|
|
||||||
|
|
||||||
@discardableResult func remove(_ id: EntityIdentifier) -> Bool
|
|
||||||
|
|
||||||
func clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
class GeneralEntityStorage: EntityStorage {
|
|
||||||
|
|
||||||
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 {
|
|
||||||
assert(!entities.contains(entity), "entity already present")
|
|
||||||
entities.append(entity)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func has(_ entity: Entity) -> Bool {
|
|
||||||
return entities.contains(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
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: EntityIdentifier) -> Entity? {
|
|
||||||
guard let index = index(id) else { return nil }
|
|
||||||
return entities[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
subscript(id: EntityIdentifier) -> Entity? { return get(id) }
|
|
||||||
|
|
||||||
func get(_ named: String) -> Entity? {
|
|
||||||
guard let index: Index = index(named) else { return nil }
|
|
||||||
return entities[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
subscript(named: String) -> Entity? { return get(named) }
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
func remove(_ id: EntityIdentifier) -> Bool {
|
|
||||||
guard let index: Index = index(id) else { return false }
|
|
||||||
entities.remove(at: index)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func clear() {
|
|
||||||
entities.removeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - internal
|
|
||||||
|
|
||||||
fileprivate func index(_ id: EntityIdentifier) -> Index? {
|
|
||||||
return Index(id)
|
|
||||||
//return entities.index { $0.uei == id }
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func index(_ named: String) -> Index? {
|
|
||||||
return entities.index { $0.name == named }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -29,18 +29,18 @@ public struct ComponentRemoved: Event {
|
||||||
|
|
||||||
struct FamilyMemberAdded: Event {
|
struct FamilyMemberAdded: Event {
|
||||||
let member: EntityIdentifier
|
let member: EntityIdentifier
|
||||||
let to: FamilyTraits
|
let to: FamilyTraitSet
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FamilyMemberRemoved: Event {
|
struct FamilyMemberRemoved: Event {
|
||||||
let member: EntityIdentifier
|
let member: EntityIdentifier
|
||||||
let from: FamilyTraits
|
let from: FamilyTraitSet
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FamilyCreated: Event {
|
struct FamilyCreated: Event {
|
||||||
let family: FamilyTraits
|
let family: FamilyTraitSet
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FamilyDestroyed: Event {
|
struct FamilyDestroyed: Event {
|
||||||
let family: FamilyTraits
|
let family: FamilyTraitSet
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// Family+Members.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 20.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
public protocol FamilyIterable {
|
||||||
|
func forEachMember(_ applyToMember: (Entity) -> Void)
|
||||||
|
func forEachMember<A>(_ applyToMember: (Entity, A) -> Void) where A: Component
|
||||||
|
func forEachMember<A, B>(_ applyToMember: (Entity, A, B) -> Void) where A: Component, B: Component
|
||||||
|
func forEachMember<A, B, C>(_ applyToMember: (Entity, A, B, C) -> Void) where A: Component, B: Component, C: Component
|
||||||
|
func forEachMember<A, B, C, D>(_ applyToMember: (Entity, A, B, C, D) -> Void) where A: Component, B: Component, C: Component, D: Component
|
||||||
|
func forEachMember<A, B, C, D, E>(_ applyToMember: (Entity, A, B, C, D, E) -> Void) where A: Component, B: Component, C: Component, D: Component, E: Component
|
||||||
|
func forEachMember<A, B, C, D, E, F>(_ applyToMember: (Entity, A, B, C, D, E, F) -> Void) where A: Component, B: Component, C: Component, D: Component, E: Component, F: Component
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Family/*: FamilyIterable*/ {
|
||||||
|
public func forEachMember(_ applyToMember: (Entity) -> Void) {
|
||||||
|
members.forEach { applyToMember(nexus.get(entity: $0)!) }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func forEachMember<A>(_ applyToMember: (Entity, () -> A?) -> Void) where A: Component {
|
||||||
|
forEachMember { (entity: Entity) in
|
||||||
|
applyToMember(entity, entity.component())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func forEachMember<A, B>(_ applyToMember: (Entity, () -> A?, () -> B?) -> Void) where A: Component, B: Component {
|
||||||
|
forEachMember { (entity: Entity) in
|
||||||
|
applyToMember(entity, entity.component(), entity.component())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func forEachMember<A, B, C>(_ applyToMember: (Entity, () -> A?, () -> B?, () -> C?) -> Void) where A: Component, B: Component, C: Component {
|
||||||
|
forEachMember { (entity: Entity) in
|
||||||
|
applyToMember(entity, entity.component(), entity.component(), entity.component())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func forEachMember<A, B, C, D>(_ applyToMember: (Entity, () -> A?, () -> B?, () -> C?, () -> D?) -> Void) where A: Component, B: Component, C: Component, D: Component {
|
||||||
|
forEachMember { (entity: Entity) in
|
||||||
|
applyToMember(entity, entity.component(), entity.component(), entity.component(), entity.component())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func forEachMember<A, B, C, D, E>(_ applyToMember: (Entity, () -> A?, () -> B?, () -> C?, () -> D?, () -> E?) -> Void) where A: Component, B: Component, C: Component, D: Component, E: Component {
|
||||||
|
forEachMember { (entity: Entity) in
|
||||||
|
applyToMember(entity, entity.component(), entity.component(), entity.component(), entity.component(), entity.component())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func forEachMember<A, B, C, D, E, F>(_ applyToMember: (Entity, () -> A?, () -> B?, () -> C?, () -> D?, () -> E?, () -> F?) -> Void) where A: Component, B: Component, C: Component, D: Component, E: Component, F: Component {
|
||||||
|
forEachMember { (entity: Entity) in
|
||||||
|
applyToMember(entity, entity.component(), entity.component(), entity.component(), entity.component(), entity.component(), entity.component())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -7,100 +7,37 @@
|
||||||
|
|
||||||
// MARK: - family
|
// MARK: - family
|
||||||
public final class Family {
|
public final class Family {
|
||||||
|
internal var nexus: Nexus
|
||||||
public var nexus: Nexus
|
|
||||||
|
|
||||||
// members of this Family must conform to these traits
|
// members of this Family must conform to these traits
|
||||||
public let traits: FamilyTraits
|
public let traits: FamilyTraitSet
|
||||||
|
|
||||||
public private(set) var members: ContiguousArray<EntityIdentifier>
|
|
||||||
|
|
||||||
public init(_ nexus: Nexus, traits: FamilyTraits) {
|
|
||||||
|
|
||||||
members = ContiguousArray<EntityIdentifier>()
|
|
||||||
members.reserveCapacity(64)
|
|
||||||
|
|
||||||
self.traits = traits
|
|
||||||
|
|
||||||
|
internal init(_ nexus: Nexus, traits: FamilyTraitSet) {
|
||||||
self.nexus = nexus
|
self.nexus = nexus
|
||||||
|
self.traits = traits
|
||||||
/*subscribe(event: handleComponentAddedToEntity)
|
|
||||||
subscribe(event: handleComponentUpdatedAtEntity)
|
|
||||||
subscribe(event: handleComponentRemovedFromEntity)
|
|
||||||
*/
|
|
||||||
defer {
|
|
||||||
//notifyCreated()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|
||||||
members.removeAll()
|
|
||||||
|
|
||||||
/*unsubscribe(event: handleComponentAddedToEntity)
|
|
||||||
unsubscribe(event: handleComponentUpdatedAtEntity)
|
|
||||||
unsubscribe(event: handleComponentRemovedFromEntity)*/
|
|
||||||
|
|
||||||
defer {
|
|
||||||
// notifyDestroyed()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - update family membership
|
|
||||||
extension Family {
|
extension Family {
|
||||||
|
public final func canBecomeMember(_ entity: Entity) -> Bool {
|
||||||
func update(membership entityIds: AnyIterator<EntityIdentifier>) {
|
return nexus.canBecomeMember(entity, in: self)
|
||||||
while let entityId: EntityIdentifier = entityIds.next() {
|
|
||||||
update(membership: entityId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(membership entities: AnyIterator<Entity>) {
|
public final func isMember(_ entity: Entity) -> Bool {
|
||||||
while let entity: Entity = entities.next() {
|
return nexus.isMember(entity, in: self)
|
||||||
update(membership: entity)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func update(membership entityId: EntityIdentifier) {
|
internal var members: EntitySet {
|
||||||
guard let entity = nexus.get(entity: entityId) else {
|
return nexus.members(of: self)
|
||||||
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.identifier)
|
|
||||||
case false:
|
|
||||||
remove(entity.identifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(_ 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: EntityIdentifier = removed {
|
|
||||||
// TODO: notify(removed: removedEntity)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - member iterator
|
// MARK: - member iterator
|
||||||
extension Family {
|
extension Family {
|
||||||
|
/*
|
||||||
func makeIterator<A>() -> AnyIterator<(Entity, A)> where A: Component {
|
func makeIterator<A>() -> AnyIterator<(Entity, A)> where A: Component {
|
||||||
var members = self.members.makeIterator()
|
var members = self.members.makeIterator()
|
||||||
return AnyIterator<(Entity, A)> { [unowned self] in
|
return AnyIterator<(Entity, A)> { [unowned self] in
|
||||||
|
|
@ -133,8 +70,10 @@ extension Family {
|
||||||
return (entity, a, b, c)
|
return (entity, a, b, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
// MARK: - Equatable
|
// MARK: - Equatable
|
||||||
extension Family: Equatable {
|
extension Family: Equatable {
|
||||||
public static func ==(lhs: Family, rhs: Family) -> Bool {
|
public static func ==(lhs: Family, rhs: Family) -> Bool {
|
||||||
|
|
@ -148,7 +87,7 @@ extension Family: Hashable {
|
||||||
return traits.hashValue
|
return traits.hashValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
/*
|
/*
|
||||||
// MARK: - event dispatcher
|
// MARK: - event dispatcher
|
||||||
extension Family: EventDispatcher {
|
extension Family: EventDispatcher {
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
//
|
|
||||||
// FamilyStorage.swift
|
|
||||||
// FirebladeECS
|
|
||||||
//
|
|
||||||
// Created by Christian Treffs on 10.10.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
public protocol FamilyStorage {
|
|
||||||
@discardableResult func add(_ family: Family) -> Bool
|
|
||||||
|
|
||||||
var iterator: AnyIterator<(FamilyTraits, Family)> { get }
|
|
||||||
|
|
||||||
func has(_ family: Family) -> Bool
|
|
||||||
func has(_ traits: FamilyTraits) -> Bool
|
|
||||||
|
|
||||||
func get(_ traits: FamilyTraits) -> Family?
|
|
||||||
subscript(_ traits: FamilyTraits) -> Family? { get }
|
|
||||||
|
|
||||||
@discardableResult func remove(_ family: Family) -> Bool
|
|
||||||
@discardableResult func remove(_ traits: FamilyTraits) -> Bool
|
|
||||||
|
|
||||||
func clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
class GeneralFamilyStorage: FamilyStorage {
|
|
||||||
|
|
||||||
fileprivate typealias Index = Dictionary<FamilyTraits, Family>.Index
|
|
||||||
fileprivate var families: [FamilyTraits: Family] = [:]
|
|
||||||
|
|
||||||
var iterator: AnyIterator<(FamilyTraits, Family)> {
|
|
||||||
// see: https://www.raywenderlich.com/139591/building-custom-collection-swift
|
|
||||||
var iter = families.makeIterator()
|
|
||||||
return AnyIterator<(FamilyTraits, Family)> {
|
|
||||||
return iter.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func add(_ family: Family) -> Bool {
|
|
||||||
let replaced: Family? = families.updateValue(family, forKey: family.traits)
|
|
||||||
let success: Bool = replaced == nil
|
|
||||||
assert(success)
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
|
|
||||||
func has(_ family: Family) -> Bool {
|
|
||||||
return index(family) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func has(_ traits: FamilyTraits) -> Bool {
|
|
||||||
return index(traits) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func get(_ traits: FamilyTraits) -> Family? {
|
|
||||||
return families[traits]
|
|
||||||
}
|
|
||||||
|
|
||||||
subscript(_ traits: FamilyTraits) -> Family? {
|
|
||||||
return get(traits)
|
|
||||||
}
|
|
||||||
|
|
||||||
func remove(_ family: Family) -> Bool {
|
|
||||||
guard let index = index(family) else { return false }
|
|
||||||
families.remove(at: index)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func remove(_ traits: FamilyTraits) -> Bool {
|
|
||||||
guard let index = index(traits) else { return false }
|
|
||||||
families.remove(at: index)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func clear() {
|
|
||||||
families.removeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - private
|
|
||||||
private func index(_ traits: FamilyTraits) -> Index? {
|
|
||||||
return families.index(forKey: traits)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func index(_ family: Family) -> Index? {
|
|
||||||
return index(family.traits)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
//
|
||||||
|
// FamilyTraitSet.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 09.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
public struct FamilyTraitSet {
|
||||||
|
|
||||||
|
fileprivate let requiresAll: ComponentSet
|
||||||
|
fileprivate let excludesAll: ComponentSet
|
||||||
|
fileprivate let needsAtLeastOne: ComponentSet
|
||||||
|
fileprivate let _hash: Int
|
||||||
|
fileprivate let isEmptyAny: Bool
|
||||||
|
|
||||||
|
public init(requiresAll: [Component.Type], excludesAll: [Component.Type], needsAtLeastOne: [Component.Type] = []) {
|
||||||
|
|
||||||
|
let all = ComponentSet(requiresAll.map { $0.identifier })
|
||||||
|
let none = ComponentSet(excludesAll.map { $0.identifier })
|
||||||
|
let one = ComponentSet(needsAtLeastOne.map { $0.identifier })
|
||||||
|
|
||||||
|
let valid: Bool = FamilyTraitSet.isValid(requiresAll: all, excludesAll: none, atLeastOne: one)
|
||||||
|
assert(valid, "invalid family trait created - requiresAll: \(all), excludesAll: \(none), atLeastOne: \(one)")
|
||||||
|
|
||||||
|
isEmptyAny = one.isEmpty
|
||||||
|
|
||||||
|
_hash = hash(combine: [all, one, none])
|
||||||
|
|
||||||
|
self.requiresAll = all
|
||||||
|
self.needsAtLeastOne = one
|
||||||
|
self.excludesAll = none
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - match
|
||||||
|
extension FamilyTraitSet {
|
||||||
|
public func isMatch(components: ComponentSet) -> Bool {
|
||||||
|
return hasAll(components) && hasNone(components) && hasOne(components)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func hasAll(_ components: ComponentSet) -> Bool {
|
||||||
|
return requiresAll.isSubset(of: components)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func hasNone(_ components: ComponentSet) -> Bool {
|
||||||
|
return excludesAll.isDisjoint(with: components)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func hasOne(_ components: ComponentSet) -> Bool {
|
||||||
|
if needsAtLeastOne.isEmpty { return true }
|
||||||
|
return !needsAtLeastOne.intersection(components).isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - valid
|
||||||
|
extension FamilyTraitSet {
|
||||||
|
fileprivate static func isValid(requiresAll: ComponentSet,
|
||||||
|
excludesAll: ComponentSet,
|
||||||
|
atLeastOne: ComponentSet) -> Bool {
|
||||||
|
return validAtLeastOneNonEmpty(requiresAll, atLeastOne) &&
|
||||||
|
requiresAll.isDisjoint(with: atLeastOne) &&
|
||||||
|
requiresAll.isDisjoint(with: excludesAll) &&
|
||||||
|
atLeastOne.isDisjoint(with: excludesAll)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate static func validAtLeastOneNonEmpty(_ requiresAll: ComponentSet, _ atLeastOne: ComponentSet) -> Bool {
|
||||||
|
return !requiresAll.isEmpty || !atLeastOne.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FamilyTraitSet {
|
||||||
|
|
||||||
|
/*/// needs to have all of the given components
|
||||||
|
fileprivate func matches(all entity: Entity) -> Bool {
|
||||||
|
var all = requiresAll
|
||||||
|
while let compId: ComponentIdentifier = all.next() {
|
||||||
|
guard entity.has(compId) else { return false }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// needs to have none of the given (excluded) components
|
||||||
|
fileprivate func matches(none entity: Entity) -> Bool {
|
||||||
|
var none = excludesAll
|
||||||
|
while let compId: ComponentIdentifier = none.next() {
|
||||||
|
guard !entity.has(compId) else { return false }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// needs to have at lease one of the given components
|
||||||
|
fileprivate func matches(any entity: Entity) -> Bool {
|
||||||
|
guard !isEmptyAny else { return true }
|
||||||
|
var any = needsAtLeastOne
|
||||||
|
while let compId: ComponentIdentifier = any.next() {
|
||||||
|
if entity.has(compId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMatch(_ entity: Entity) -> Bool {
|
||||||
|
guard matches(all: entity) else { return false }
|
||||||
|
guard matches(none: entity) else { return false }
|
||||||
|
guard matches(any: entity) else { return false }
|
||||||
|
return true
|
||||||
|
}*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Equatable
|
||||||
|
extension FamilyTraitSet: Equatable {
|
||||||
|
public static func ==(lhs: FamilyTraitSet, rhs: FamilyTraitSet) -> Bool {
|
||||||
|
return lhs._hash == rhs._hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Hashable
|
||||||
|
extension FamilyTraitSet: Hashable {
|
||||||
|
public var hashValue: Int {
|
||||||
|
return _hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - description
|
||||||
|
/*extension FamilyTraits: CustomStringConvertible {
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
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))"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
//
|
|
||||||
// FamilyPredicate.swift
|
|
||||||
// FirebladeECS
|
|
||||||
//
|
|
||||||
// Created by Christian Treffs on 09.10.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
// trait/predicate/characteristic
|
|
||||||
public struct FamilyTraits {
|
|
||||||
let hasAll: Set<ComponentIdentifier>
|
|
||||||
let hasAny: Set<ComponentIdentifier>
|
|
||||||
let hasNone: Set<ComponentIdentifier>
|
|
||||||
|
|
||||||
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<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 {
|
|
||||||
return (!hasAll.isEmpty || !hasAny.isEmpty) &&
|
|
||||||
hasAll.isDisjoint(with: hasAny) &&
|
|
||||||
hasAll.isDisjoint(with: hasNone) &&
|
|
||||||
hasAny.isDisjoint(with: hasNone)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension FamilyTraits {
|
|
||||||
|
|
||||||
fileprivate func matches(all entity: Entity) -> Bool {
|
|
||||||
var all = iteratorAll
|
|
||||||
while let uct: ComponentIdentifier = all.next() {
|
|
||||||
guard entity.has(uct) else { return false }
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func matches(none entity: Entity) -> Bool {
|
|
||||||
var none = iteratorNone
|
|
||||||
while let uct: ComponentIdentifier = none.next() {
|
|
||||||
guard !entity.has(uct) else { return false }
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fileprivate func matches(any entity: Entity) -> Bool {
|
|
||||||
guard !hasAny.isEmpty else { return true }
|
|
||||||
var any = iteratorAny
|
|
||||||
while let uct: ComponentIdentifier = any.next() {
|
|
||||||
if entity.has(uct) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isMatch(_ entity: Entity) -> Bool {
|
|
||||||
guard matches(all: entity) else { return false }
|
|
||||||
guard matches(none: entity) else { return false }
|
|
||||||
guard matches(any: entity) else { return false }
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Equatable
|
|
||||||
extension FamilyTraits: Equatable {
|
|
||||||
|
|
||||||
fileprivate var xorHash: Int {
|
|
||||||
return hasAll.hashValue ^ hasNone.hashValue ^ hasAny.hashValue
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func ==(lhs: FamilyTraits, rhs: FamilyTraits) -> Bool {
|
|
||||||
return lhs.xorHash == rhs.xorHash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Hashable
|
|
||||||
extension FamilyTraits: Hashable {
|
|
||||||
public var hashValue: Int {
|
|
||||||
return xorHash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - description
|
|
||||||
extension FamilyTraits: CustomStringConvertible {
|
|
||||||
|
|
||||||
public var description: String {
|
|
||||||
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))"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -25,11 +25,11 @@ extension Nexus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func assign<C>(component: C, to entityId: EntityIdentifier) where C: Component {
|
public func assign(component: Component, to entity: Entity) {
|
||||||
let componentId = C.identifier
|
let componentId = component.identifier
|
||||||
let entityIdx = entityId.index
|
let entityIdx = entity.identifier.index
|
||||||
let hash: EntityComponentHash = componentId.hashValue(using: entityIdx)
|
let hash: EntityComponentHash = componentId.hashValue(using: entityIdx)
|
||||||
assert(!has(hash), "ComponentAdd collision: \(entityId) already has a component \(component)")
|
assert(!has(hash), "ComponentAdd collision: \(entityIdx) already has a component \(component)")
|
||||||
var newComponentIndex: ComponentIndex = ComponentIndex.invalid
|
var newComponentIndex: ComponentIndex = ComponentIndex.invalid
|
||||||
if componentsByType[componentId] != nil {
|
if componentsByType[componentId] != nil {
|
||||||
newComponentIndex = componentsByType[componentId]!.count // TODO: get next free index
|
newComponentIndex = componentsByType[componentId]!.count // TODO: get next free index
|
||||||
|
|
@ -41,11 +41,17 @@ extension Nexus {
|
||||||
|
|
||||||
// assigns the component id to the entity id
|
// assigns the component id to the entity id
|
||||||
if componentIdsByEntity[entityIdx] != nil {
|
if componentIdsByEntity[entityIdx] != nil {
|
||||||
|
let (inserted, _) = componentIdsSetByEntity[entityIdx]!.insert(componentId)
|
||||||
|
assert(inserted)
|
||||||
let newIndex = componentIdsByEntity[entityIdx]!.count
|
let newIndex = componentIdsByEntity[entityIdx]!.count
|
||||||
componentIdsByEntity[entityIdx]!.insert(componentId, at: newIndex)
|
componentIdsByEntity[entityIdx]!.insert(componentId, at: newIndex)
|
||||||
componentIdsByEntityLookup[hash] = newIndex
|
componentIdsByEntityLookup[hash] = newIndex
|
||||||
} else {
|
} else {
|
||||||
|
componentIdsSetByEntity[entityIdx] = Set<ComponentIdentifier>(minimumCapacity: 2)
|
||||||
|
let (inserted, _) = componentIdsSetByEntity[entityIdx]!.insert(componentId)
|
||||||
|
assert(inserted)
|
||||||
componentIdsByEntity[entityIdx] = ComponentIdentifiers()
|
componentIdsByEntity[entityIdx] = ComponentIdentifiers()
|
||||||
|
componentIdsByEntity.reserveCapacity(1)
|
||||||
componentIdsByEntity[entityIdx]!.insert(componentId, at: 0)
|
componentIdsByEntity[entityIdx]!.insert(componentId, at: 0)
|
||||||
componentIdsByEntityLookup[hash] = 0
|
componentIdsByEntityLookup[hash] = 0
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +59,11 @@ extension Nexus {
|
||||||
// assign entity / component to index
|
// assign entity / component to index
|
||||||
componentIndexByEntityComponentHash[hash] = newComponentIndex
|
componentIndexByEntityComponentHash[hash] = newComponentIndex
|
||||||
|
|
||||||
notify(ComponentAdded(component: componentId, to: entityId))
|
notify(ComponentAdded(component: componentId, to: entity.identifier))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func assign<C>(component: C, to entity: Entity) where C: Component {
|
||||||
|
assign(component: component, to: entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func get<C>(component componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> C? where C: Component {
|
public func get<C>(component componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> C? where C: Component {
|
||||||
|
|
@ -62,6 +72,7 @@ extension Nexus {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func get<C>(_ hash: EntityComponentHash) -> C? where C: Component {
|
fileprivate func get<C>(_ hash: EntityComponentHash) -> C? where C: Component {
|
||||||
|
Log.info("GETTING: \(C.self)")
|
||||||
let componentId: ComponentIdentifier = C.identifier
|
let componentId: ComponentIdentifier = C.identifier
|
||||||
guard let componentIdx: ComponentIndex = componentIndexByEntityComponentHash[hash] else { return nil }
|
guard let componentIdx: ComponentIndex = componentIndexByEntityComponentHash[hash] else { return nil }
|
||||||
guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil }
|
guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil }
|
||||||
|
|
@ -90,6 +101,13 @@ extension Nexus {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: unassign component
|
// MARK: unassign component
|
||||||
|
|
||||||
|
guard componentIdsSetByEntity[entityId.index]?.remove(componentId) != nil else {
|
||||||
|
assert(false, "ComponentRemove failure: no component found to be removed in set")
|
||||||
|
report("ComponentRemove failure: no component found to be removed in set")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
guard let removeIndex: ComponentIdsByEntityIndex = componentIdsByEntityLookup.removeValue(forKey: hash) else {
|
guard let removeIndex: ComponentIdsByEntityIndex = componentIdsByEntityLookup.removeValue(forKey: hash) else {
|
||||||
assert(false, "ComponentRemove failure: no component found to be removed")
|
assert(false, "ComponentRemove failure: no component found to be removed")
|
||||||
report("ComponentRemove failure: no component found to be removed")
|
report("ComponentRemove failure: no component found to be removed")
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ extension Nexus {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func get(entity entityId: EntityIdentifier) -> Entity? {
|
public func get(entity entityId: EntityIdentifier) -> Entity? {
|
||||||
|
Log.info("GETTING ENTITY: \(entityId)")
|
||||||
guard has(entity: entityId) else { return nil }
|
guard has(entity: entityId) else { return nil }
|
||||||
return entities[entityId.index]
|
return entities[entityId.index]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,30 +7,99 @@
|
||||||
|
|
||||||
extension Nexus {
|
extension Nexus {
|
||||||
|
|
||||||
/*@discardableResult
|
/// Gets or creates (new) family with given traits.
|
||||||
public func family(with traits: FamilyTraits) -> (new: Bool, family: Family) {
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - allComponents: all component types are required in this family.
|
||||||
|
/// - noneComponents: none component type may appear in this family.
|
||||||
|
/// - oneComponents: at least one of component types must appear in this family.
|
||||||
|
/// - Returns: family with given traits.
|
||||||
|
public func family(requiresAll allComponents: [Component.Type], excludesAll noneComponents: [Component.Type], needsAtLeastOne oneComponents: [Component.Type] = []) -> Family {
|
||||||
|
|
||||||
if let existingFamily: Family = families.get(traits) {
|
let traits = FamilyTraitSet(requiresAll: allComponents, excludesAll: noneComponents, needsAtLeastOne: oneComponents)
|
||||||
return (new: false, family: existingFamily)
|
|
||||||
|
guard let family: Family = get(family: traits) else {
|
||||||
|
return create(family: traits)
|
||||||
|
}
|
||||||
|
return family
|
||||||
}
|
}
|
||||||
|
|
||||||
let newFamily = Family(self, traits: traits)
|
public func canBecomeMember(_ entity: Entity, in family: Family) -> Bool {
|
||||||
// ^ dispatches family creation event here ^
|
let entityIdx: EntityIndex = entity.identifier.index
|
||||||
let success = families.add(newFamily)
|
guard let componentSet: ComponentSet = componentIdsSetByEntity[entityIdx] else {
|
||||||
assert(success, "Family with the exact traits already exists")
|
assert(false, "no component set defined for entity: \(entity)")
|
||||||
|
return false
|
||||||
refreshFamilyCache()
|
}
|
||||||
|
return family.traits.isMatch(components: componentSet)
|
||||||
return (new: true, family: newFamily)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func onFamilyCreated(_ newFamily: Family) {
|
public func members(of family: Family) -> EntitySet {
|
||||||
newFamily.update(membership: entities.iterator)
|
let traitHash: FamilyTraitSetHash = family.traits.hashValue
|
||||||
|
return familyMembersByTraitHash[traitHash] ?? [] // FIXME: fail?
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshFamilyCache() {
|
public func isMember(_ entity: Entity, in family: Family) -> Bool {
|
||||||
// TODO:
|
let traitHash: FamilyTraitSetHash = family.traits.hashValue
|
||||||
|
let entityId = entity.identifier
|
||||||
|
return familyMembersByTraitHash[traitHash]?.contains(entityId) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func get(family traits: FamilyTraitSet) -> Family? {
|
||||||
|
let traitHash: FamilyTraitSetHash = traits.hashValue
|
||||||
|
return familiyByTraitHash[traitHash]
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func create(family traits: FamilyTraitSet) -> Family {
|
||||||
|
let traitHash: FamilyTraitSetHash = traits.hashValue
|
||||||
|
let family = Family(self, traits: traits)
|
||||||
|
let replaced = familiyByTraitHash.updateValue(family, forKey: traitHash)
|
||||||
|
assert(replaced == nil, "Family with exact trait hash already exists: \(traitHash)")
|
||||||
|
notify(FamilyCreated(family: traits))
|
||||||
|
// FIXME: update memberships for prior entites
|
||||||
|
return family
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: remove families?!
|
||||||
|
|
||||||
|
// MARK: - update family membership
|
||||||
|
|
||||||
|
func update(membership family: Family, for entity: Entity) {
|
||||||
|
let entityIdx: EntityIndex = entity.identifier.index
|
||||||
|
guard let componentsSet: ComponentSet = componentIdsSetByEntity[entityIdx] else { return }
|
||||||
|
let isMatch: Bool = family.traits.isMatch(components: componentsSet)
|
||||||
|
switch isMatch {
|
||||||
|
case true:
|
||||||
|
add(to: family, entity: entity)
|
||||||
|
case false:
|
||||||
|
remove(from: family, entity: entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func add(to family: Family, entity: Entity) {
|
||||||
|
let traitHash: FamilyTraitSetHash = family.traits.hashValue
|
||||||
|
let entityId: EntityIdentifier = entity.identifier
|
||||||
|
if familyMembersByTraitHash[traitHash] != nil {
|
||||||
|
let (inserted, _) = familyMembersByTraitHash[traitHash]!.insert(entityId)
|
||||||
|
assert(inserted, "entity with id \(entityId) already in family")
|
||||||
|
} else {
|
||||||
|
familyMembersByTraitHash[traitHash] = EntitySet(minimumCapacity: 2)
|
||||||
|
familyMembersByTraitHash[traitHash]!.insert(entityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(FamilyMemberAdded(member: entityId, to: family.traits))
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func remove(from family: Family, entity: Entity) {
|
||||||
|
let traitHash: FamilyTraitSetHash = family.traits.hashValue
|
||||||
|
let entityId: EntityIdentifier = entity.identifier
|
||||||
|
|
||||||
|
guard let removed = familyMembersByTraitHash[traitHash]?.remove(entityId) else {
|
||||||
|
assert(false, "removing entity id \(entityId) that is not in family \(family)")
|
||||||
|
report("removing entity id \(entityId) that is not in family \(family)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(FamilyMemberRemoved(member: removed, from: family.traits))
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@ public typealias ComponentTypeHash = Int // component object identifier hash val
|
||||||
|
|
||||||
public typealias UniformComponents = ContiguousArray<Component>
|
public typealias UniformComponents = ContiguousArray<Component>
|
||||||
public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier>
|
public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier>
|
||||||
|
public typealias ComponentSet = Set<ComponentIdentifier>
|
||||||
public typealias Entities = ContiguousArray<Entity>
|
public typealias Entities = ContiguousArray<Entity>
|
||||||
|
public typealias EntitySet = Set<EntityIdentifier>
|
||||||
|
public typealias FamilyTraitSetHash = Int
|
||||||
|
|
||||||
public class Nexus {
|
public class Nexus {
|
||||||
|
|
||||||
|
|
@ -43,13 +46,20 @@ public class Nexus {
|
||||||
/// - Values: entity ids that are currently not used
|
/// - Values: entity ids that are currently not used
|
||||||
var freeEntities: ContiguousArray<EntityIdentifier>
|
var freeEntities: ContiguousArray<EntityIdentifier>
|
||||||
|
|
||||||
|
var familiyByTraitHash: [FamilyTraitSetHash: Family]
|
||||||
|
var familyMembersByTraitHash: [FamilyTraitSetHash: EntitySet]
|
||||||
|
var componentIdsSetByEntity: [EntityIndex: ComponentSet]
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
entities = Entities()
|
entities = Entities()
|
||||||
componentsByType = [:]
|
componentsByType = [:]
|
||||||
componentIndexByEntityComponentHash = [:]
|
componentIndexByEntityComponentHash = [:]
|
||||||
componentIdsByEntity = [:]
|
componentIdsByEntity = [:]
|
||||||
|
componentIdsSetByEntity = [:]
|
||||||
componentIdsByEntityLookup = [:]
|
componentIdsByEntityLookup = [:]
|
||||||
freeEntities = ContiguousArray<EntityIdentifier>()
|
freeEntities = ContiguousArray<EntityIdentifier>()
|
||||||
|
familiyByTraitHash = [:]
|
||||||
|
familyMembersByTraitHash = [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,33 @@
|
||||||
|
|
||||||
import FirebladeECS
|
import FirebladeECS
|
||||||
|
|
||||||
struct EmptyComponent: Component { }
|
class EmptyComponent: Component { }
|
||||||
|
|
||||||
struct Name: Component {
|
class Name: Component {
|
||||||
var name: String
|
var name: String
|
||||||
|
init(name: String) {
|
||||||
|
self.name = name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Position: Component {
|
class Position: Component {
|
||||||
var x: Int
|
var x: Int
|
||||||
var y: Int
|
var y: Int
|
||||||
|
init(x: Int, y: Int) {
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Velocity: Component {
|
class Velocity: Component {
|
||||||
var a: Float
|
var a: Float
|
||||||
|
init(a: Float) {
|
||||||
|
self.a = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Party: Component {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DebugEventHandler: EventHandler {
|
class DebugEventHandler: EventHandler {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,99 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
/*@testable */import FirebladeECS
|
@testable import FirebladeECS
|
||||||
|
|
||||||
class FamilyTests: XCTestCase {
|
class FamilyTests: XCTestCase {
|
||||||
|
|
||||||
|
func testFamilyCreation() {
|
||||||
|
let nexus = Nexus()
|
||||||
|
|
||||||
|
let family: Family = nexus.family(requiresAll: [Position.self],
|
||||||
|
excludesAll: [Name.self],
|
||||||
|
needsAtLeastOne: [Velocity.self])
|
||||||
|
_ = family
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTraitCommutativity() {
|
||||||
|
|
||||||
|
let t1 = FamilyTraitSet(requiresAll: [Position.self, Velocity.self], excludesAll: [Name.self], needsAtLeastOne: [])
|
||||||
|
let t2 = FamilyTraitSet(requiresAll: [Velocity.self, Position.self], excludesAll: [Name.self], needsAtLeastOne: [])
|
||||||
|
|
||||||
|
XCTAssert(t1 == t2)
|
||||||
|
XCTAssert(t1.hashValue == t2.hashValue)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTraitMatching() {
|
||||||
|
let nexus = Nexus()
|
||||||
|
let a = nexus.create(entity: "a")
|
||||||
|
a.assign(Position(x: 1, y: 2))
|
||||||
|
a.assign(Name(name: "myName"))
|
||||||
|
a.assign(Velocity(a: 3.14))
|
||||||
|
a.assign(EmptyComponent())
|
||||||
|
|
||||||
|
let noMatch = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Name.self])
|
||||||
|
let isMatch = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||||
|
|
||||||
|
XCTAssertFalse(noMatch.canBecomeMember(a))
|
||||||
|
XCTAssertTrue(isMatch.canBecomeMember(a))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMeasureTraitMatching() {
|
||||||
|
let nexus = Nexus()
|
||||||
|
let a = nexus.create(entity: "a")
|
||||||
|
a.assign(Position(x: 1, y: 2))
|
||||||
|
a.assign(Name(name: "myName"))
|
||||||
|
a.assign(Velocity(a: 3.14))
|
||||||
|
a.assign(EmptyComponent())
|
||||||
|
|
||||||
|
let isMatch = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||||
|
|
||||||
|
measure {
|
||||||
|
for _ in 0..<1_000_000 {
|
||||||
|
let success = isMatch.canBecomeMember(a)
|
||||||
|
XCTAssert(success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIterateFamilyMembers() {
|
||||||
|
|
||||||
|
let nexus = Nexus()
|
||||||
|
|
||||||
|
let a = nexus.create(entity: "a").assign(Position(x: 1, y: 2), Name(name: "myName"), Velocity(a: 3.14), EmptyComponent())
|
||||||
|
let b = nexus.create(entity: "b").assign(Position(x: 3, y: 4), Velocity(a: 5.23), EmptyComponent())
|
||||||
|
|
||||||
|
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||||
|
|
||||||
|
nexus.update(membership: family, for: a)
|
||||||
|
nexus.update(membership: family, for: b)
|
||||||
|
|
||||||
|
var index: Int = 0
|
||||||
|
|
||||||
|
family.forEachMember { (e: Entity, p: () -> Position!, v: () -> Velocity!, n: () -> Name?) in
|
||||||
|
|
||||||
|
p()!.x = 10
|
||||||
|
|
||||||
|
print(e, p(), n())
|
||||||
|
if index == 0 {
|
||||||
|
print(v())
|
||||||
|
}
|
||||||
|
// bla
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
family.forEachMember { (e: Entity, p: () -> Position!, v: () -> Velocity!, n: () -> Name?) in
|
||||||
|
|
||||||
|
print(e, p().x, n())
|
||||||
|
if index == 0 {
|
||||||
|
print(v())
|
||||||
|
}
|
||||||
|
// bla
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@ class HashingTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMeasureCombineHash() {
|
func testMeasureCombineHash() {
|
||||||
let a: Set<Int> = Set<Int>.init([1, 2, 3, 4, 5, 6])
|
let a: Set<Int> = Set<Int>.init([14561291, 26451562, 34562182, 488972556, 5128426962, 68211812])
|
||||||
let b: Set<Int> = Set<Int>.init([10, 9, 8, 7, 6])
|
let b: Set<Int> = Set<Int>.init([1083838, 912312, 83333, 71234555, 4343234])
|
||||||
let c: Set<Int> = Set<Int>.init([10, 9, 12, 7, 6])
|
let c: Set<Int> = Set<Int>.init([3410346899765, 90000002, 12212321, 71, 6123345676543])
|
||||||
|
|
||||||
let input: ContiguousArray<Int> = ContiguousArray<Int>(arrayLiteral: a.hashValue, b.hashValue, c.hashValue)
|
let input: ContiguousArray<Int> = ContiguousArray<Int>(arrayLiteral: a.hashValue, b.hashValue, c.hashValue)
|
||||||
measure {
|
measure {
|
||||||
|
|
@ -54,9 +54,9 @@ class HashingTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMeasureSetOfSetHash() {
|
func testMeasureSetOfSetHash() {
|
||||||
let a: Set<Int> = Set<Int>.init([1, 2, 3, 4, 5, 6])
|
let a: Set<Int> = Set<Int>.init([14561291, 26451562, 34562182, 488972556, 5128426962, 68211812])
|
||||||
let b: Set<Int> = Set<Int>.init([10, 9, 8, 7, 6])
|
let b: Set<Int> = Set<Int>.init([1083838, 912312, 83333, 71234555, 4343234])
|
||||||
let c: Set<Int> = Set<Int>.init([10, 9, 12, 7, 6])
|
let c: Set<Int> = Set<Int>.init([3410346899765, 90000002, 12212321, 71, 6123345676543])
|
||||||
|
|
||||||
let input = Set<Set<Int>>(arrayLiteral: a, b, c)
|
let input = Set<Set<Int>>(arrayLiteral: a, b, c)
|
||||||
measure {
|
measure {
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ class NexusTests: XCTestCase {
|
||||||
XCTAssert(e0.hasComponents)
|
XCTAssert(e0.hasComponents)
|
||||||
XCTAssert(e0.numComponents == 1)
|
XCTAssert(e0.numComponents == 1)
|
||||||
|
|
||||||
let rP0: Position = e0.component(Position.self)
|
let rP0: Position = e0.component(Position.self)!
|
||||||
XCTAssert(rP0.x == 1)
|
XCTAssert(rP0.x == 1)
|
||||||
XCTAssert(rP0.y == 2)
|
XCTAssert(rP0.y == 2)
|
||||||
}
|
}
|
||||||
|
|
@ -162,14 +162,12 @@ class NexusTests: XCTestCase {
|
||||||
|
|
||||||
XCTAssert(nexus.count == 3)
|
XCTAssert(nexus.count == 3)
|
||||||
|
|
||||||
let p = Position(x: 0, y: 0)
|
a.assign(Position(x: 0, y: 0))
|
||||||
|
b.assign(Position(x: 0, y: 0))
|
||||||
|
c.assign(Position(x: 0, y: 0))
|
||||||
|
|
||||||
a.assign(p)
|
let pA: Position = a.component()!
|
||||||
b.assign(p)
|
let pB: Position = b.component()!
|
||||||
c.assign(p)
|
|
||||||
|
|
||||||
var pA: Position = a.component(Position.self)
|
|
||||||
let pB: Position = b.component(Position.self)
|
|
||||||
|
|
||||||
pA.x = 23
|
pA.x = 23
|
||||||
pA.y = 32
|
pA.y = 32
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue