diff --git a/Sources/FirebladeECS/Component.swift b/Sources/FirebladeECS/Component.swift index 74c1087..1217bde 100644 --- a/Sources/FirebladeECS/Component.swift +++ b/Sources/FirebladeECS/Component.swift @@ -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 { diff --git a/Sources/FirebladeECS/ComponentStorage.swift b/Sources/FirebladeECS/ComponentStorage.swift deleted file mode 100644 index f599103..0000000 --- a/Sources/FirebladeECS/ComponentStorage.swift +++ /dev/null @@ -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(_ component: C) -> Bool - - func makeIterator(_ componentType: C.Type) -> AnyIterator - func makeIterator(_ componentId: ComponentIdentifier) -> AnyIterator - - func has(_ component: C) -> Bool - func has(_ componentType: C.Type) -> Bool - func has(_ componentId: ComponentIdentifier) -> Bool - - func get(_ componentType: C.Type) -> Component? - subscript(_ componentType: C.Type) -> Component? { get } - - func get(_ componentId: ComponentIdentifier) -> Component? - subscript(_ componentId: ComponentIdentifier) -> Component? { get } - - @discardableResult func remove(_ component: C) -> Bool - @discardableResult func remove(_ componentType: C.Type) -> Bool - @discardableResult func remove(_ componentId: ComponentIdentifier) -> Bool - - func clear() -} - -class GeneralComponentStorage: ComponentStorage { - - fileprivate var componentMap: [ComponentIdentifier: ContiguousArray] = [:] - - - func add(_ component: C) -> Bool where C : Component { - if var comps: ContiguousArray = componentMap[component.uct] { - comps.append(component) - } else { - componentMap[component.uct] = ContiguousArray() - componentMap[component.uct]!.append(component) - } - return true - } - - func makeIterator(_ componentType: C.Type) -> AnyIterator where C : Component { - fatalError() - } - - func makeIterator(_ componentId: ComponentIdentifier) -> AnyIterator { - fatalError() - } - - func has(_ component: C) -> Bool where C : Component { - fatalError() - } - - func has(_ componentType: C.Type) -> Bool where C : Component { - fatalError() - } - - func has(_ componentId: ComponentIdentifier) -> Bool { - fatalError() - } - - func get(_ componentType: C.Type) -> Component? where C : Component { - fatalError() - } - - subscript(componentType: C.Type) -> Component? where C: Component { - fatalError() - } - - func get(_ componentId: ComponentIdentifier) -> Component? { - fatalError() - } - - subscript(componentId: ComponentIdentifier) -> Component? { - fatalError() - } - - func remove(_ component: C) -> Bool where C : Component { - fatalError() - } - - func remove(_ componentType: C.Type) -> Bool where C : Component { - fatalError() - } - - func remove(_ componentId: ComponentIdentifier) -> Bool { - fatalError() - } - - func clear() { - fatalError() - } - -} -*/ diff --git a/Sources/FirebladeECS/Entity.swift b/Sources/FirebladeECS/Entity.swift index 9b6d66d..4822215 100644 --- a/Sources/FirebladeECS/Entity.swift +++ b/Sources/FirebladeECS/Entity.swift @@ -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(_ 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.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? where A: Component { + func getComponent() -> A? { + return component(A.self) } - return a + return getComponent + } + + public func component(_ compType: A.Type = A.self) -> A? where A: Component { + return nexus.get(component: A.identifier, for: identifier) } public func components(_: 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.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.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.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.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) } } diff --git a/Sources/FirebladeECS/EntityStorage.swift b/Sources/FirebladeECS/EntityStorage.swift deleted file mode 100644 index 42424c0..0000000 --- a/Sources/FirebladeECS/EntityStorage.swift +++ /dev/null @@ -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 { 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.Index - fileprivate var entities: ContiguousArray = ContiguousArray() - - var iterator: AnyIterator { - 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 } - } - -} diff --git a/Sources/FirebladeECS/Events.swift b/Sources/FirebladeECS/Events.swift index ae110fa..50e090d 100644 --- a/Sources/FirebladeECS/Events.swift +++ b/Sources/FirebladeECS/Events.swift @@ -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 } diff --git a/Sources/FirebladeECS/Family+Members.swift b/Sources/FirebladeECS/Family+Members.swift new file mode 100644 index 0000000..b305b86 --- /dev/null +++ b/Sources/FirebladeECS/Family+Members.swift @@ -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(_ applyToMember: (Entity, A) -> Void) where A: Component + func forEachMember(_ applyToMember: (Entity, A, B) -> Void) where A: Component, B: Component + func forEachMember(_ applyToMember: (Entity, A, B, C) -> Void) where A: Component, B: Component, C: Component + func forEachMember(_ applyToMember: (Entity, A, B, C, D) -> Void) where A: Component, B: Component, C: Component, D: Component + func forEachMember(_ applyToMember: (Entity, A, B, C, D, E) -> Void) where A: Component, B: Component, C: Component, D: Component, E: Component + func forEachMember(_ 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(_ applyToMember: (Entity, () -> A?) -> Void) where A: Component { + forEachMember { (entity: Entity) in + applyToMember(entity, entity.component()) + } + } + + public func forEachMember(_ applyToMember: (Entity, () -> A?, () -> B?) -> Void) where A: Component, B: Component { + forEachMember { (entity: Entity) in + applyToMember(entity, entity.component(), entity.component()) + } + } + + public func forEachMember(_ 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(_ 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(_ 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(_ 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()) + } + } + +} diff --git a/Sources/FirebladeECS/Family.swift b/Sources/FirebladeECS/Family.swift index bc52609..c524428 100644 --- a/Sources/FirebladeECS/Family.swift +++ b/Sources/FirebladeECS/Family.swift @@ -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 - - public init(_ nexus: Nexus, traits: FamilyTraits) { - - members = ContiguousArray() - 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) { - 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) { - 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() -> 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 { diff --git a/Sources/FirebladeECS/FamilyStorage.swift b/Sources/FirebladeECS/FamilyStorage.swift deleted file mode 100644 index 1091ed0..0000000 --- a/Sources/FirebladeECS/FamilyStorage.swift +++ /dev/null @@ -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.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) - } - -} diff --git a/Sources/FirebladeECS/FamilyTraitSet.swift b/Sources/FirebladeECS/FamilyTraitSet.swift new file mode 100644 index 0000000..0b939de --- /dev/null +++ b/Sources/FirebladeECS/FamilyTraitSet.swift @@ -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))" + } + +} +*/ diff --git a/Sources/FirebladeECS/FamilyTraits.swift b/Sources/FirebladeECS/FamilyTraits.swift deleted file mode 100644 index eacb00e..0000000 --- a/Sources/FirebladeECS/FamilyTraits.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// FamilyPredicate.swift -// FirebladeECS -// -// Created by Christian Treffs on 09.10.17. -// - -// trait/predicate/characteristic -public struct FamilyTraits { - let hasAll: Set - let hasAny: Set - let hasNone: Set - - public init(hasAll: Set, hasAny: Set, hasNone: Set) { - self.hasAll = hasAll - self.hasAny = hasAny - self.hasNone = hasNone - assert(isValid) - } - - fileprivate var iteratorAll: SetIterator { return hasAll.makeIterator() } - fileprivate var iteratorAny: SetIterator { return hasAny.makeIterator() } - fileprivate var iteratorNone: SetIterator { 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))" - } - -} diff --git a/Sources/FirebladeECS/Nexus+Component.swift b/Sources/FirebladeECS/Nexus+Component.swift index e784e52..d18e3ff 100644 --- a/Sources/FirebladeECS/Nexus+Component.swift +++ b/Sources/FirebladeECS/Nexus+Component.swift @@ -25,11 +25,11 @@ extension Nexus { } } - public func assign(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(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(component: C, to entity: Entity) where C: Component { + assign(component: component, to: entity) } public func get(component componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> C? where C: Component { @@ -62,6 +72,7 @@ extension Nexus { } fileprivate func get(_ 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") diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index 3a0f08e..fb5ecec 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -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] } diff --git a/Sources/FirebladeECS/Nexus+Family.swift b/Sources/FirebladeECS/Nexus+Family.swift index 74577c3..f265539 100644 --- a/Sources/FirebladeECS/Nexus+Family.swift +++ b/Sources/FirebladeECS/Nexus+Family.swift @@ -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)) } - */ } diff --git a/Sources/FirebladeECS/Nexus.swift b/Sources/FirebladeECS/Nexus.swift index b4bd9a4..04de58d 100644 --- a/Sources/FirebladeECS/Nexus.swift +++ b/Sources/FirebladeECS/Nexus.swift @@ -16,7 +16,10 @@ public typealias ComponentTypeHash = Int // component object identifier hash val public typealias UniformComponents = ContiguousArray public typealias ComponentIdentifiers = ContiguousArray +public typealias ComponentSet = Set public typealias Entities = ContiguousArray +public typealias EntitySet = Set +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 + var familiyByTraitHash: [FamilyTraitSetHash: Family] + var familyMembersByTraitHash: [FamilyTraitSetHash: EntitySet] + var componentIdsSetByEntity: [EntityIndex: ComponentSet] + public init() { entities = Entities() componentsByType = [:] componentIndexByEntityComponentHash = [:] componentIdsByEntity = [:] + componentIdsSetByEntity = [:] componentIdsByEntityLookup = [:] freeEntities = ContiguousArray() + familiyByTraitHash = [:] + familyMembersByTraitHash = [:] } } diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index c6e490c..8225f33 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -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 { diff --git a/Tests/FirebladeECSTests/FamilyTests.swift b/Tests/FirebladeECSTests/FamilyTests.swift index e2c8e20..aed2203 100644 --- a/Tests/FirebladeECSTests/FamilyTests.swift +++ b/Tests/FirebladeECSTests/FamilyTests.swift @@ -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 + } + + } + } diff --git a/Tests/FirebladeECSTests/HashingTests.swift b/Tests/FirebladeECSTests/HashingTests.swift index 9f7e575..771d1b0 100644 --- a/Tests/FirebladeECSTests/HashingTests.swift +++ b/Tests/FirebladeECSTests/HashingTests.swift @@ -40,9 +40,9 @@ class HashingTests: XCTestCase { } func testMeasureCombineHash() { - let a: Set = Set.init([1, 2, 3, 4, 5, 6]) - let b: Set = Set.init([10, 9, 8, 7, 6]) - let c: Set = Set.init([10, 9, 12, 7, 6]) + let a: Set = Set.init([14561291, 26451562, 34562182, 488972556, 5128426962, 68211812]) + let b: Set = Set.init([1083838, 912312, 83333, 71234555, 4343234]) + let c: Set = Set.init([3410346899765, 90000002, 12212321, 71, 6123345676543]) let input: ContiguousArray = ContiguousArray(arrayLiteral: a.hashValue, b.hashValue, c.hashValue) measure { @@ -54,9 +54,9 @@ class HashingTests: XCTestCase { } func testMeasureSetOfSetHash() { - let a: Set = Set.init([1, 2, 3, 4, 5, 6]) - let b: Set = Set.init([10, 9, 8, 7, 6]) - let c: Set = Set.init([10, 9, 12, 7, 6]) + let a: Set = Set.init([14561291, 26451562, 34562182, 488972556, 5128426962, 68211812]) + let b: Set = Set.init([1083838, 912312, 83333, 71234555, 4343234]) + let c: Set = Set.init([3410346899765, 90000002, 12212321, 71, 6123345676543]) let input = Set>(arrayLiteral: a, b, c) measure { diff --git a/Tests/FirebladeECSTests/NexusTests.swift b/Tests/FirebladeECSTests/NexusTests.swift index 284d020..bb6d829 100644 --- a/Tests/FirebladeECSTests/NexusTests.swift +++ b/Tests/FirebladeECSTests/NexusTests.swift @@ -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