Family iteration working

This commit is contained in:
Christian Treffs 2017-10-20 16:25:29 +02:00
parent 9ffcb3d4f7
commit 2386fab4c3
18 changed files with 500 additions and 538 deletions

View File

@ -5,7 +5,7 @@
// Created by Christian Treffs on 08.10.17.
//
public protocol Component: UniqueComponentIdentifiable {}
public protocol Component: class, UniqueComponentIdentifiable {}
// MARK: UCI
extension Component {

View File

@ -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()
}
}
*/

View File

@ -66,9 +66,17 @@ public extension Entity {
// MARK: - add component(s)
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
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
}
@ -139,46 +147,50 @@ extension Entity {
// 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)'")
public func component<A>() -> () -> A? where A: Component {
func getComponent() -> A? {
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 {
let a: A = component(A.self)
let b: B = component(B.self)
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)
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)
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)
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)
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)
}
}

View File

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

View File

@ -29,18 +29,18 @@ public struct ComponentRemoved: Event {
struct FamilyMemberAdded: Event {
let member: EntityIdentifier
let to: FamilyTraits
let to: FamilyTraitSet
}
struct FamilyMemberRemoved: Event {
let member: EntityIdentifier
let from: FamilyTraits
let from: FamilyTraitSet
}
struct FamilyCreated: Event {
let family: FamilyTraits
let family: FamilyTraitSet
}
struct FamilyDestroyed: Event {
let family: FamilyTraits
let family: FamilyTraitSet
}

View File

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

View File

@ -7,100 +7,37 @@
// MARK: - family
public final class Family {
public var nexus: Nexus
internal var nexus: Nexus
// members of this Family must conform to these traits
public let traits: FamilyTraits
public private(set) var members: ContiguousArray<EntityIdentifier>
public init(_ nexus: Nexus, traits: FamilyTraits) {
members = ContiguousArray<EntityIdentifier>()
members.reserveCapacity(64)
self.traits = traits
public let traits: FamilyTraitSet
internal init(_ nexus: Nexus, traits: FamilyTraitSet) {
self.nexus = nexus
/*subscribe(event: handleComponentAddedToEntity)
subscribe(event: handleComponentUpdatedAtEntity)
subscribe(event: handleComponentRemovedFromEntity)
*/
defer {
//notifyCreated()
}
self.traits = traits
}
deinit {
members.removeAll()
/*unsubscribe(event: handleComponentAddedToEntity)
unsubscribe(event: handleComponentUpdatedAtEntity)
unsubscribe(event: handleComponentRemovedFromEntity)*/
defer {
// notifyDestroyed()
}
}
}
// MARK: - update family membership
extension Family {
func update(membership entityIds: AnyIterator<EntityIdentifier>) {
while let entityId: EntityIdentifier = entityIds.next() {
update(membership: entityId)
}
public final func canBecomeMember(_ entity: Entity) -> Bool {
return nexus.canBecomeMember(entity, in: self)
}
func update(membership entities: AnyIterator<Entity>) {
while let entity: Entity = entities.next() {
update(membership: entity)
}
public final func isMember(_ entity: Entity) -> Bool {
return nexus.isMember(entity, in: self)
}
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.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)
}
internal var members: EntitySet {
return nexus.members(of: self)
}
}
// MARK: - member iterator
extension Family {
/*
func makeIterator<A>() -> AnyIterator<(Entity, A)> where A: Component {
var members = self.members.makeIterator()
return AnyIterator<(Entity, A)> { [unowned self] in
@ -133,8 +70,10 @@ extension Family {
return (entity, a, b, c)
}
}
*/
}
/*
// MARK: - Equatable
extension Family: Equatable {
public static func ==(lhs: Family, rhs: Family) -> Bool {
@ -148,7 +87,7 @@ extension Family: Hashable {
return traits.hashValue
}
}
*/
/*
// MARK: - event dispatcher
extension Family: EventDispatcher {

View File

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

View File

@ -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))"
}
}
*/

View File

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

View File

@ -25,11 +25,11 @@ extension Nexus {
}
}
public func assign<C>(component: C, to entityId: EntityIdentifier) where C: Component {
let componentId = C.identifier
let entityIdx = entityId.index
public func assign(component: Component, to entity: Entity) {
let componentId = component.identifier
let entityIdx = entity.identifier.index
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
if componentsByType[componentId] != nil {
newComponentIndex = componentsByType[componentId]!.count // TODO: get next free index
@ -41,11 +41,17 @@ extension Nexus {
// assigns the component id to the entity id
if componentIdsByEntity[entityIdx] != nil {
let (inserted, _) = componentIdsSetByEntity[entityIdx]!.insert(componentId)
assert(inserted)
let newIndex = componentIdsByEntity[entityIdx]!.count
componentIdsByEntity[entityIdx]!.insert(componentId, at: newIndex)
componentIdsByEntityLookup[hash] = newIndex
} else {
componentIdsSetByEntity[entityIdx] = Set<ComponentIdentifier>(minimumCapacity: 2)
let (inserted, _) = componentIdsSetByEntity[entityIdx]!.insert(componentId)
assert(inserted)
componentIdsByEntity[entityIdx] = ComponentIdentifiers()
componentIdsByEntity.reserveCapacity(1)
componentIdsByEntity[entityIdx]!.insert(componentId, at: 0)
componentIdsByEntityLookup[hash] = 0
}
@ -53,7 +59,11 @@ extension Nexus {
// assign entity / component to index
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 {
@ -62,6 +72,7 @@ extension Nexus {
}
fileprivate func get<C>(_ hash: EntityComponentHash) -> C? where C: Component {
Log.info("GETTING: \(C.self)")
let componentId: ComponentIdentifier = C.identifier
guard let componentIdx: ComponentIndex = componentIndexByEntityComponentHash[hash] else { return nil }
guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil }
@ -90,6 +101,13 @@ extension Nexus {
}
// 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 {
assert(false, "ComponentRemove failure: no component found to be removed")
report("ComponentRemove failure: no component found to be removed")

View File

@ -52,6 +52,7 @@ extension Nexus {
}
public func get(entity entityId: EntityIdentifier) -> Entity? {
Log.info("GETTING ENTITY: \(entityId)")
guard has(entity: entityId) else { return nil }
return entities[entityId.index]
}

View File

@ -7,30 +7,99 @@
extension Nexus {
/*@discardableResult
public func family(with traits: FamilyTraits) -> (new: Bool, family: Family) {
/// Gets or creates (new) family with given traits.
///
/// - 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) {
return (new: false, family: existingFamily)
let traits = FamilyTraitSet(requiresAll: allComponents, excludesAll: noneComponents, needsAtLeastOne: oneComponents)
guard let family: Family = get(family: traits) else {
return create(family: traits)
}
return family
}
public func canBecomeMember(_ entity: Entity, in family: Family) -> Bool {
let entityIdx: EntityIndex = entity.identifier.index
guard let componentSet: ComponentSet = componentIdsSetByEntity[entityIdx] else {
assert(false, "no component set defined for entity: \(entity)")
return false
}
return family.traits.isMatch(components: componentSet)
}
public func members(of family: Family) -> EntitySet {
let traitHash: FamilyTraitSetHash = family.traits.hashValue
return familyMembersByTraitHash[traitHash] ?? [] // FIXME: fail?
}
public func isMember(_ entity: Entity, in family: Family) -> Bool {
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)
}
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)
notify(FamilyMemberAdded(member: entityId, to: family.traits))
}
func onFamilyCreated(_ newFamily: Family) {
newFamily.update(membership: entities.iterator)
}
fileprivate func remove(from family: Family, entity: Entity) {
let traitHash: FamilyTraitSetHash = family.traits.hashValue
let entityId: EntityIdentifier = entity.identifier
func refreshFamilyCache() {
// TODO:
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))
}
*/
}

View File

@ -16,7 +16,10 @@ public typealias ComponentTypeHash = Int // component object identifier hash val
public typealias UniformComponents = ContiguousArray<Component>
public typealias ComponentIdentifiers = ContiguousArray<ComponentIdentifier>
public typealias ComponentSet = Set<ComponentIdentifier>
public typealias Entities = ContiguousArray<Entity>
public typealias EntitySet = Set<EntityIdentifier>
public typealias FamilyTraitSetHash = Int
public class Nexus {
@ -43,13 +46,20 @@ public class Nexus {
/// - Values: entity ids that are currently not used
var freeEntities: ContiguousArray<EntityIdentifier>
var familiyByTraitHash: [FamilyTraitSetHash: Family]
var familyMembersByTraitHash: [FamilyTraitSetHash: EntitySet]
var componentIdsSetByEntity: [EntityIndex: ComponentSet]
public init() {
entities = Entities()
componentsByType = [:]
componentIndexByEntityComponentHash = [:]
componentIdsByEntity = [:]
componentIdsSetByEntity = [:]
componentIdsByEntityLookup = [:]
freeEntities = ContiguousArray<EntityIdentifier>()
familiyByTraitHash = [:]
familyMembersByTraitHash = [:]
}
}

View File

@ -7,19 +7,33 @@
import FirebladeECS
struct EmptyComponent: Component { }
class EmptyComponent: Component { }
struct Name: Component {
class Name: Component {
var name: String
init(name: String) {
self.name = name
}
}
struct Position: Component {
class Position: Component {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
struct Velocity: Component {
class Velocity: Component {
var a: Float
init(a: Float) {
self.a = a
}
}
class Party: Component {
}
class DebugEventHandler: EventHandler {

View File

@ -6,8 +6,99 @@
//
import XCTest
/*@testable */import FirebladeECS
@testable import FirebladeECS
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
}
}
}

View File

@ -40,9 +40,9 @@ class HashingTests: XCTestCase {
}
func testMeasureCombineHash() {
let a: Set<Int> = Set<Int>.init([1, 2, 3, 4, 5, 6])
let b: Set<Int> = Set<Int>.init([10, 9, 8, 7, 6])
let c: Set<Int> = Set<Int>.init([10, 9, 12, 7, 6])
let a: Set<Int> = Set<Int>.init([14561291, 26451562, 34562182, 488972556, 5128426962, 68211812])
let b: Set<Int> = Set<Int>.init([1083838, 912312, 83333, 71234555, 4343234])
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)
measure {
@ -54,9 +54,9 @@ class HashingTests: XCTestCase {
}
func testMeasureSetOfSetHash() {
let a: Set<Int> = Set<Int>.init([1, 2, 3, 4, 5, 6])
let b: Set<Int> = Set<Int>.init([10, 9, 8, 7, 6])
let c: Set<Int> = Set<Int>.init([10, 9, 12, 7, 6])
let a: Set<Int> = Set<Int>.init([14561291, 26451562, 34562182, 488972556, 5128426962, 68211812])
let b: Set<Int> = Set<Int>.init([1083838, 912312, 83333, 71234555, 4343234])
let c: Set<Int> = Set<Int>.init([3410346899765, 90000002, 12212321, 71, 6123345676543])
let input = Set<Set<Int>>(arrayLiteral: a, b, c)
measure {

View File

@ -100,7 +100,7 @@ class NexusTests: XCTestCase {
XCTAssert(e0.hasComponents)
XCTAssert(e0.numComponents == 1)
let rP0: Position = e0.component(Position.self)
let rP0: Position = e0.component(Position.self)!
XCTAssert(rP0.x == 1)
XCTAssert(rP0.y == 2)
}
@ -162,14 +162,12 @@ class NexusTests: XCTestCase {
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)
b.assign(p)
c.assign(p)
var pA: Position = a.component(Position.self)
let pB: Position = b.component(Position.self)
let pA: Position = a.component()!
let pB: Position = b.component()!
pA.x = 23
pA.y = 32