Add Family basics

This commit is contained in:
Christian Treffs 2017-10-09 19:43:05 +02:00
parent 6a045cd643
commit e6cb5770e0
7 changed files with 332 additions and 125 deletions

View File

@ -47,7 +47,7 @@ public extension Entity {
} }
public final func has(_ component: UCT) -> Bool { public final func has(_ component: UCT) -> Bool {
fatalError() return componentMap[component] != nil
} }
public final var hasComponents: Bool { return !componentMap.isEmpty } public final var hasComponents: Bool { return !componentMap.isEmpty }
@ -188,25 +188,25 @@ extension Entity: EventDispatcher {
private func notify<C: Component>(add component: C) { private func notify<C: Component>(add component: C) {
unowned { unowned {
$0.dispatch(ComponentAdded(component: component, to: $0)) $0.dispatch(ComponentAdded(to: $0))
} }
} }
private func notify<C: Component>(update newComponent: C, previous previousComponent: C) { private func notify<C: Component>(update newComponent: C, previous previousComponent: C) {
unowned { unowned {
$0.dispatch(ComponentUpdated(component: newComponent, previous: previousComponent, at: $0)) $0.dispatch(ComponentUpdated(at: $0))
} }
} }
private func notify<C: Component>(removed component: C) { private func notify<C: Component>(removed component: C) {
unowned { unowned {
$0.dispatch(ComponentRemoved(component: component, from: $0)) $0.dispatch(ComponentRemoved(from: $0))
} }
} }
private func notify(removed component: Component) { private func notify(removed component: Component) {
//unowned { /* this keeps a reference since we need it */ //unowned { /* this keeps a reference since we need it */
dispatch(ComponentRemoved(component: component, from: self)) dispatch(ComponentRemoved(from: self))
//} //}
} }

View File

@ -5,41 +5,65 @@
// Created by Christian Treffs on 09.10.17. // Created by Christian Treffs on 09.10.17.
// //
class EntityHub: EventHandler { public class EntityHub: EventHandler {
weak var delegate: EventHub? public weak var delegate: EventHub?
lazy var eventCenter: DefaultEventHub = { return DefaultEventHub() }()
private(set) var entites: [UEI:Entity] = [:] public lazy var eventHub: DefaultEventHub = { return DefaultEventHub() }()
init() { private(set) var entites: Set<Entity>
self.delegate = eventCenter //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: handleEntityCreated)
subscribe(event: handleFamilyCreated)
subscribe(event: handleComponentAdded) subscribe(event: handleComponentAdded)
subscribe(event: handleFamilyMemberAdded)
} }
deinit { deinit {
unsubscribe(event: handleEntityCreated) unsubscribe(event: handleEntityCreated)
unsubscribe(event: handleFamilyCreated)
unsubscribe(event: handleComponentAdded) 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 ^ // ^ dispatches entity creation event here ^
let prevEntity: Entity? = entites.updateValue(newEntity, forKey: newEntity.uei) let (success, _) = entites.insert(newEntity)
assert(prevEntity == nil) assert(success == true, "Entity with the exact identifier already exists")
return newEntity 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 // MARK: - event handler
extension EntityHub { extension EntityHub {
func handleEntityCreated(_ ec: EntityCreated) { func handleEntityCreated(_ e: EntityCreated) { print(e) }
print(ec) func handleFamilyCreated(_ e: FamilyCreated) { print(e) }
}
func handleComponentAdded(_ ca: ComponentAdded) { func handleComponentAdded(_ e: ComponentAdded) { print(e) }
print(ca) func handleFamilyMemberAdded(_ e: FamilyMemberAdded) { print(e) }
}
} }

View File

@ -14,31 +14,41 @@ public struct EntityDestroyed: Event {
} }
public struct ComponentAdded: Event { public struct ComponentAdded: Event {
let component: Component //let component: Component
let to: Entity let to: Entity
} }
public struct ComponentUpdated: Event { public struct ComponentUpdated: Event {
let component: Component //let component: Component
let previous: Component //let previous: Component
let at: Entity let at: Entity
} }
public struct ComponentRemoved: Event { public struct ComponentRemoved: Event {
let component: Component //let component: Component
let from: Entity let from: Entity
} }
/* struct FamilyMemberAdded: Event {
public enum ECSEvent { let member: Entity
let to: Family
case entityCreated(Entity) }
case entityDestroyed(Entity)
struct FamilyMemberUpdated: Event {
case componentAdded(Component, to: Entity) let newMember: Entity
case componentUpdated(Component, previous: Component, at: Entity) let oldMember: Entity
case componentRemoved(Component, from: 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
} }
*/

View File

@ -4,116 +4,159 @@
// //
// Created by Christian Treffs on 08.10.17. // 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 { // MARK: - family
let family: Family public final class Family {
}
struct FamilyDestroyed: Event { public var delegate: EventHub?
//TODO: family 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: public private(set) var members: Set<Entity>
let required: Set<ComponentType>
let excluded: Set<ComponentType>
public private(set) var members: ContiguousArray<Entity> public init(traits: FamilyTraits, eventHub: EventHub & EventDispatcher) {
public convenience init(requiresAll required: ComponentType...) { members = Set<Entity>()
self.init(requiresAll: required, excludesAll: [])
}
public init(requiresAll required: [ComponentType], excludesAll excluded: [ComponentType]) { self.traits = traits
self.required = Set<ComponentType>(required)
self.excluded = Set<ComponentType>(excluded)
self.members = [] delegate = eventHub
dispatcher = eventHub
subscribe(event: handleComponentAddedToEntity) subscribe(event: handleComponentAddedToEntity)
subscribe(event: handleComponentRemovedFromEntity) subscribe(event: handleComponentRemovedFromEntity)
dispatch(event: FamilyCreated(family: self)) defer {
notifyCreated()
}
} }
deinit { deinit {
//TODO: optimize for large sets members.removeAll()
//TODO: dispatch entity removed event
self.members.removeAll()
unsubscribe(event: handleComponentAddedToEntity) unsubscribe(event: handleComponentAddedToEntity)
unsubscribe(event: handleComponentRemovedFromEntity) unsubscribe(event: handleComponentRemovedFromEntity)
dispatch(event: FamilyDestroyed()) defer {
notifyDestroyed()
}
} }
}
final func handleComponentAddedToEntity(event: ComponentAdded) { // MARK: update family membership
//TODO: optimize by more specific comparison extension Family {
self.update(familyMembership: event.toEntity)
}
final func handleComponentRemovedFromEntity(event: ComponentRemoved) {
//TODO: optimize by more specific comparison
self.update(familyMembership: event.fromEntity)
}
final func matches(familyRequirements entity: Entity) -> Bool { fileprivate func update(membership entity: Entity) {
return entity.contains(all: required) && entity.contains(none: excluded) let isMatch: Bool = traits.isMatch(entity)
} switch isMatch {
case true:
final func contains(entity: Entity) -> Bool { push(entity)
return self.members.contains(where: { $0.uid == entity.uid }) case false:
} remove(entity)
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
} }
} }
private final func add(toFamily entity: Entity) { fileprivate func push(_ entity: Entity) {
self.members.append(entity) let (added, member) = members.insert(entity)
dispatch(event: FamilyMemberAdded(entity: entity, family: self)) switch added {
case true:
notify(added: member)
case false:
notify(update: entity, previous: member)
}
} }
private final func remove(entityAtIndex index: Int) { fileprivate func remove(_ entity: Entity) {
let removedEntity: Entity = self.members.remove(at: index) let removed: Entity? = members.remove(entity)
dispatch(event: FamilyMemberRemoved(entity: removedEntity, family: self)) 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)
}
}

View File

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

View File

@ -14,15 +14,15 @@ class EntityHubTests: XCTestCase {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
entityHub.eventCenter.sniffer = self entityHub.eventHub.sniffer = self
} }
func testCreateEntity() { func testCreateEntity() {
let newEntity: Entity = entityHub.createEntity() let newEntity: Entity = entityHub.createEntity()
XCTAssert(newEntity.hasComponents == false) XCTAssert(newEntity.hasComponents == false)
XCTAssert(entityHub.entites[newEntity.uei] == newEntity) //TODO: XCTAssert(entityHub.entites[newEntity.uei] == newEntity)
XCTAssert(entityHub.entites[newEntity.uei] === newEntity) //TODO: XCTAssert(entityHub.entites[newEntity.uei] === newEntity)
} }
func testCreateEntityAndAddComponent() { func testCreateEntityAndAddComponent() {
@ -35,8 +35,8 @@ class EntityHubTests: XCTestCase {
XCTAssert(newEntity.hasComponents) XCTAssert(newEntity.hasComponents)
XCTAssert(newEntity.numComponents == 1) XCTAssert(newEntity.numComponents == 1)
XCTAssert(entityHub.entites[newEntity.uei] == newEntity) //TODO: XCTAssert(entityHub.entites[newEntity.uei] == newEntity)
XCTAssert(entityHub.entites[newEntity.uei] === newEntity) //TODO: XCTAssert(entityHub.entites[newEntity.uei] === newEntity)
} }

View File

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