Add Family basics
This commit is contained in:
parent
6a045cd643
commit
e6cb5770e0
|
|
@ -47,7 +47,7 @@ public extension Entity {
|
|||
}
|
||||
|
||||
public final func has(_ component: UCT) -> Bool {
|
||||
fatalError()
|
||||
return componentMap[component] != nil
|
||||
}
|
||||
|
||||
public final var hasComponents: Bool { return !componentMap.isEmpty }
|
||||
|
|
@ -188,25 +188,25 @@ extension Entity: EventDispatcher {
|
|||
|
||||
private func notify<C: Component>(add component: C) {
|
||||
unowned {
|
||||
$0.dispatch(ComponentAdded(component: component, to: $0))
|
||||
$0.dispatch(ComponentAdded(to: $0))
|
||||
}
|
||||
}
|
||||
|
||||
private func notify<C: Component>(update newComponent: C, previous previousComponent: C) {
|
||||
unowned {
|
||||
$0.dispatch(ComponentUpdated(component: newComponent, previous: previousComponent, at: $0))
|
||||
$0.dispatch(ComponentUpdated(at: $0))
|
||||
}
|
||||
}
|
||||
|
||||
private func notify<C: Component>(removed component: C) {
|
||||
unowned {
|
||||
$0.dispatch(ComponentRemoved(component: component, from: $0))
|
||||
$0.dispatch(ComponentRemoved(from: $0))
|
||||
}
|
||||
}
|
||||
|
||||
private func notify(removed component: Component) {
|
||||
//unowned { /* this keeps a reference since we need it */
|
||||
dispatch(ComponentRemoved(component: component, from: self))
|
||||
dispatch(ComponentRemoved(from: self))
|
||||
//}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,41 +5,65 @@
|
|||
// Created by Christian Treffs on 09.10.17.
|
||||
//
|
||||
|
||||
class EntityHub: EventHandler {
|
||||
weak var delegate: EventHub?
|
||||
lazy var eventCenter: DefaultEventHub = { return DefaultEventHub() }()
|
||||
public class EntityHub: EventHandler {
|
||||
public weak var delegate: EventHub?
|
||||
|
||||
private(set) var entites: [UEI:Entity] = [:]
|
||||
public lazy var eventHub: DefaultEventHub = { return DefaultEventHub() }()
|
||||
|
||||
init() {
|
||||
self.delegate = eventCenter
|
||||
private(set) var entites: Set<Entity>
|
||||
//private(set) var entites: [UEI:Entity] = [:]
|
||||
private(set) var families: Set<Family>
|
||||
|
||||
public init() {
|
||||
entites = Set<Entity>()
|
||||
entites.reserveCapacity(512)
|
||||
|
||||
families = Set<Family>()
|
||||
families.reserveCapacity(64)
|
||||
|
||||
self.delegate = eventHub
|
||||
|
||||
subscribe(event: handleEntityCreated)
|
||||
subscribe(event: handleFamilyCreated)
|
||||
subscribe(event: handleComponentAdded)
|
||||
subscribe(event: handleFamilyMemberAdded)
|
||||
}
|
||||
deinit {
|
||||
unsubscribe(event: handleEntityCreated)
|
||||
unsubscribe(event: handleFamilyCreated)
|
||||
unsubscribe(event: handleComponentAdded)
|
||||
unsubscribe(event: handleFamilyMemberAdded)
|
||||
}
|
||||
|
||||
func createEntity() -> Entity {
|
||||
let newEntity = Entity(uei: UEI.next, dispatcher: eventCenter)
|
||||
}
|
||||
|
||||
// MARK: - creators
|
||||
extension EntityHub {
|
||||
public func createEntity() -> Entity {
|
||||
let newEntity = Entity(uei: UEI.next, dispatcher: eventHub)
|
||||
// ^ dispatches entity creation event here ^
|
||||
let prevEntity: Entity? = entites.updateValue(newEntity, forKey: newEntity.uei)
|
||||
assert(prevEntity == nil)
|
||||
let (success, _) = entites.insert(newEntity)
|
||||
assert(success == true, "Entity with the exact identifier already exists")
|
||||
|
||||
return newEntity
|
||||
}
|
||||
|
||||
public func createFamily(with traits: FamilyTraits) -> Family {
|
||||
let newFamily = Family(traits: traits, eventHub: eventHub)
|
||||
// ^ dispatches family creation event here ^
|
||||
let (success, _) = families.insert(newFamily)
|
||||
assert(success == true, "Family with the exact traits already exists")
|
||||
|
||||
return newFamily
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - event handler
|
||||
extension EntityHub {
|
||||
func handleEntityCreated(_ ec: EntityCreated) {
|
||||
print(ec)
|
||||
}
|
||||
func handleEntityCreated(_ e: EntityCreated) { print(e) }
|
||||
func handleFamilyCreated(_ e: FamilyCreated) { print(e) }
|
||||
|
||||
func handleComponentAdded(_ ca: ComponentAdded) {
|
||||
print(ca)
|
||||
}
|
||||
func handleComponentAdded(_ e: ComponentAdded) { print(e) }
|
||||
func handleFamilyMemberAdded(_ e: FamilyMemberAdded) { print(e) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,31 +14,41 @@ public struct EntityDestroyed: Event {
|
|||
}
|
||||
|
||||
public struct ComponentAdded: Event {
|
||||
let component: Component
|
||||
//let component: Component
|
||||
let to: Entity
|
||||
}
|
||||
|
||||
public struct ComponentUpdated: Event {
|
||||
let component: Component
|
||||
let previous: Component
|
||||
//let component: Component
|
||||
//let previous: Component
|
||||
let at: Entity
|
||||
}
|
||||
|
||||
public struct ComponentRemoved: Event {
|
||||
let component: Component
|
||||
//let component: Component
|
||||
let from: Entity
|
||||
}
|
||||
|
||||
/*
|
||||
public enum ECSEvent {
|
||||
|
||||
case entityCreated(Entity)
|
||||
case entityDestroyed(Entity)
|
||||
|
||||
case componentAdded(Component, to: Entity)
|
||||
case componentUpdated(Component, previous: Component, at: Entity)
|
||||
case componentRemoved(Component, from: Entity)
|
||||
|
||||
|
||||
struct FamilyMemberAdded: Event {
|
||||
let member: Entity
|
||||
let to: Family
|
||||
}
|
||||
|
||||
struct FamilyMemberUpdated: Event {
|
||||
let newMember: Entity
|
||||
let oldMember: Entity
|
||||
let `in`: Family
|
||||
}
|
||||
|
||||
struct FamilyMemberRemoved: Event {
|
||||
let member: Entity
|
||||
let from: Family
|
||||
}
|
||||
|
||||
struct FamilyCreated: Event {
|
||||
let family: Family
|
||||
}
|
||||
|
||||
struct FamilyDestroyed: Event {
|
||||
let family: Family
|
||||
}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,116 +4,159 @@
|
|||
//
|
||||
// Created by Christian Treffs on 08.10.17.
|
||||
//
|
||||
/*
|
||||
// TODO: is this needed?
|
||||
struct FamilyMemberAdded: Event {
|
||||
let entity: Entity
|
||||
let family: Family
|
||||
}
|
||||
// TODO: is this needed?
|
||||
struct FamilyMemberRemoved: Event {
|
||||
let entity: Entity
|
||||
let family: Family
|
||||
}
|
||||
|
||||
struct FamilyCreated: Event {
|
||||
let family: Family
|
||||
}
|
||||
// MARK: - family
|
||||
public final class Family {
|
||||
|
||||
struct FamilyDestroyed: Event {
|
||||
//TODO: family
|
||||
}
|
||||
public var delegate: EventHub?
|
||||
fileprivate var dispatcher: EventDispatcher
|
||||
|
||||
public final class Family: EventSender, EventHandler {
|
||||
// members of this Family must conform to these traits
|
||||
public let traits: FamilyTraits
|
||||
|
||||
// members of this Family must conform to:
|
||||
let required: Set<ComponentType>
|
||||
let excluded: Set<ComponentType>
|
||||
public private(set) var members: Set<Entity>
|
||||
|
||||
public private(set) var members: ContiguousArray<Entity>
|
||||
public init(traits: FamilyTraits, eventHub: EventHub & EventDispatcher) {
|
||||
|
||||
public convenience init(requiresAll required: ComponentType...) {
|
||||
self.init(requiresAll: required, excludesAll: [])
|
||||
}
|
||||
members = Set<Entity>()
|
||||
|
||||
public init(requiresAll required: [ComponentType], excludesAll excluded: [ComponentType]) {
|
||||
self.required = Set<ComponentType>(required)
|
||||
self.excluded = Set<ComponentType>(excluded)
|
||||
self.traits = traits
|
||||
|
||||
self.members = []
|
||||
delegate = eventHub
|
||||
dispatcher = eventHub
|
||||
|
||||
subscribe(event: handleComponentAddedToEntity)
|
||||
subscribe(event: handleComponentRemovedFromEntity)
|
||||
|
||||
dispatch(event: FamilyCreated(family: self))
|
||||
defer {
|
||||
notifyCreated()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
//TODO: optimize for large sets
|
||||
//TODO: dispatch entity removed event
|
||||
self.members.removeAll()
|
||||
members.removeAll()
|
||||
|
||||
unsubscribe(event: handleComponentAddedToEntity)
|
||||
unsubscribe(event: handleComponentRemovedFromEntity)
|
||||
|
||||
dispatch(event: FamilyDestroyed())
|
||||
defer {
|
||||
notifyDestroyed()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
final func handleComponentAddedToEntity(event: ComponentAdded) {
|
||||
//TODO: optimize by more specific comparison
|
||||
self.update(familyMembership: event.toEntity)
|
||||
}
|
||||
final func handleComponentRemovedFromEntity(event: ComponentRemoved) {
|
||||
//TODO: optimize by more specific comparison
|
||||
self.update(familyMembership: event.fromEntity)
|
||||
}
|
||||
// MARK: update family membership
|
||||
extension Family {
|
||||
|
||||
final func matches(familyRequirements entity: Entity) -> Bool {
|
||||
return entity.contains(all: required) && entity.contains(none: excluded)
|
||||
}
|
||||
|
||||
final func contains(entity: Entity) -> Bool {
|
||||
return self.members.contains(where: { $0.uid == entity.uid })
|
||||
}
|
||||
final func indexOf(entity: Entity) -> Int? {
|
||||
return self.members.index(where: { $0.uid == entity.uid })
|
||||
}
|
||||
|
||||
final func update(familyMemberships entities: ContiguousArray<Entity>) {
|
||||
//TODO: optimize for large sets
|
||||
entities.forEach { self.update(familyMembership:$0) }
|
||||
}
|
||||
|
||||
private final func update(familyMembership entity: Entity) {
|
||||
|
||||
let NEW: Int = -1
|
||||
let isMatch: Bool = matches(familyRequirements: entity)
|
||||
let index: Int = indexOf(entity: entity) ?? NEW
|
||||
let isNew: Bool = index == NEW
|
||||
|
||||
switch (isMatch, isNew) {
|
||||
case (true, true): // isMatch && new -> add
|
||||
add(toFamily: entity)
|
||||
return
|
||||
|
||||
case (false, false): // noMatch && isPart -> remove
|
||||
remove(entityAtIndex: index)
|
||||
return
|
||||
|
||||
default:
|
||||
return
|
||||
fileprivate func update(membership entity: Entity) {
|
||||
let isMatch: Bool = traits.isMatch(entity)
|
||||
switch isMatch {
|
||||
case true:
|
||||
push(entity)
|
||||
case false:
|
||||
remove(entity)
|
||||
}
|
||||
}
|
||||
|
||||
private final func add(toFamily entity: Entity) {
|
||||
self.members.append(entity)
|
||||
dispatch(event: FamilyMemberAdded(entity: entity, family: self))
|
||||
fileprivate func push(_ entity: Entity) {
|
||||
let (added, member) = members.insert(entity)
|
||||
switch added {
|
||||
case true:
|
||||
notify(added: member)
|
||||
case false:
|
||||
notify(update: entity, previous: member)
|
||||
}
|
||||
}
|
||||
|
||||
private final func remove(entityAtIndex index: Int) {
|
||||
let removedEntity: Entity = self.members.remove(at: index)
|
||||
dispatch(event: FamilyMemberRemoved(entity: removedEntity, family: self))
|
||||
fileprivate func remove(_ entity: Entity) {
|
||||
let removed: Entity? = members.remove(entity)
|
||||
assert(removed != nil)
|
||||
if let removedEntity: Entity = removed {
|
||||
notify(removed: removedEntity)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// MARK: - Equatable
|
||||
extension Family: Equatable {
|
||||
public static func ==(lhs: Family, rhs: Family) -> Bool {
|
||||
return lhs.traits == rhs.traits
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
extension Family: Hashable {
|
||||
public var hashValue: Int {
|
||||
return traits.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - event dispatcher
|
||||
extension Family: EventDispatcher {
|
||||
public func dispatch<E>(_ event: E) where E : Event {
|
||||
dispatcher.dispatch(event)
|
||||
}
|
||||
|
||||
fileprivate func unowned(closure: @escaping (Family) -> Void) {
|
||||
let unownedClosure = { [unowned self] in
|
||||
closure(self)
|
||||
}
|
||||
unownedClosure()
|
||||
}
|
||||
|
||||
fileprivate func notifyCreated() {
|
||||
unowned {
|
||||
$0.dispatch(FamilyCreated(family: $0))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func notify(added newEntity: Entity) {
|
||||
unowned { [unowned newEntity] in
|
||||
$0.dispatch(FamilyMemberAdded(member: newEntity, to: $0))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func notify(update newEntity: Entity, previous oldEntity: Entity) {
|
||||
unowned { [unowned newEntity, unowned oldEntity] in
|
||||
$0.dispatch(FamilyMemberUpdated(newMember: newEntity, oldMember: oldEntity, in: $0) )
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func notify(removed removedEntity: Entity) {
|
||||
unowned { [unowned removedEntity] in
|
||||
$0.dispatch(FamilyMemberRemoved(member: removedEntity, from: $0))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func notifyDestroyed() {
|
||||
//dispatch(event: FamilyDestroyed())
|
||||
// dispatch(FamilyDestroyed(family: self))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - event handler
|
||||
extension Family: EventHandler {
|
||||
|
||||
fileprivate final func handleComponentAddedToEntity(event: ComponentAdded) {
|
||||
//let newComponent: Component = event.component
|
||||
let entity: Entity = event.to
|
||||
update(membership: entity)
|
||||
}
|
||||
|
||||
fileprivate final func handleComponentUpdatedAtEntity(event: ComponentUpdated) {
|
||||
//let newComponent: Component = event.component
|
||||
//let oldComponent: Component = event.previous
|
||||
let entity: Entity = event.at
|
||||
update(membership: entity)
|
||||
}
|
||||
|
||||
fileprivate final func handleComponentRemovedFromEntity(event: ComponentRemoved) {
|
||||
//let removedComponent: Component = event.component
|
||||
let entity: Entity = event.from
|
||||
update(membership: entity)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// FamilyPredicate.swift
|
||||
// FirebladeECS
|
||||
//
|
||||
// Created by Christian Treffs on 09.10.17.
|
||||
//
|
||||
|
||||
// trait/predicate/characteristic
|
||||
public struct FamilyTraits {
|
||||
let hasAll: Set<UCT>
|
||||
let hasAny: Set<UCT>
|
||||
let hasNone: Set<UCT>
|
||||
|
||||
public init(hasAll: Set<UCT>, hasAny: Set<UCT>, hasNone: Set<UCT>) {
|
||||
self.hasAll = hasAll
|
||||
self.hasAny = hasAny
|
||||
self.hasNone = hasNone
|
||||
assert(isValid)
|
||||
}
|
||||
|
||||
fileprivate var iteratorAll: SetIterator<UCT> { return hasAll.makeIterator() }
|
||||
fileprivate var iteratorAny: SetIterator<UCT> { return hasAny.makeIterator() }
|
||||
fileprivate var iteratorNone: SetIterator<UCT> { return hasNone.makeIterator() }
|
||||
}
|
||||
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: UCT = all.next() {
|
||||
guard entity.has(uct) else { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fileprivate func matches(none entity: Entity) -> Bool {
|
||||
var none = iteratorNone
|
||||
while let uct: UCT = 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: UCT = 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.type)" }.joined(separator: " AND ")
|
||||
let any: String = hasAny.map { "\($0.type)" }.joined(separator: " OR ")
|
||||
let none: String = hasNone.map { "!\($0.type)"}.joined(separator: " NOT ")
|
||||
let out: String = ["\(all)", "\(any)", "\(none)"].joined(separator: " AND ")
|
||||
//TODO: nicer
|
||||
return "FamilyTraits(\(out))"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,15 +14,15 @@ class EntityHubTests: XCTestCase {
|
|||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
entityHub.eventCenter.sniffer = self
|
||||
entityHub.eventHub.sniffer = self
|
||||
}
|
||||
|
||||
func testCreateEntity() {
|
||||
let newEntity: Entity = entityHub.createEntity()
|
||||
|
||||
XCTAssert(newEntity.hasComponents == false)
|
||||
XCTAssert(entityHub.entites[newEntity.uei] == newEntity)
|
||||
XCTAssert(entityHub.entites[newEntity.uei] === newEntity)
|
||||
//TODO: XCTAssert(entityHub.entites[newEntity.uei] == newEntity)
|
||||
//TODO: XCTAssert(entityHub.entites[newEntity.uei] === newEntity)
|
||||
}
|
||||
|
||||
func testCreateEntityAndAddComponent() {
|
||||
|
|
@ -35,8 +35,8 @@ class EntityHubTests: XCTestCase {
|
|||
XCTAssert(newEntity.hasComponents)
|
||||
XCTAssert(newEntity.numComponents == 1)
|
||||
|
||||
XCTAssert(entityHub.entites[newEntity.uei] == newEntity)
|
||||
XCTAssert(entityHub.entites[newEntity.uei] === newEntity)
|
||||
//TODO: XCTAssert(entityHub.entites[newEntity.uei] == newEntity)
|
||||
//TODO: XCTAssert(entityHub.entites[newEntity.uei] === newEntity)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// FamilyTests.swift
|
||||
// FirebladeECS
|
||||
//
|
||||
// Created by Christian Treffs on 09.10.17.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
/*@testable */import FirebladeECS
|
||||
|
||||
class FamilyTests: XCTestCase {
|
||||
|
||||
let entityHub: EntityHub = EntityHub()
|
||||
|
||||
func testFamily() {
|
||||
|
||||
let traits = FamilyTraits(hasAll: [EmptyComponent.uct], hasAny: [], hasNone: [])
|
||||
|
||||
let simpleFamily = entityHub.createFamily(with: traits)
|
||||
|
||||
let e = entityHub.createEntity()
|
||||
e += EmptyComponent()
|
||||
|
||||
e.remove(EmptyComponent.self)
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue