Stabelized API
This commit is contained in:
parent
ec80d1ad1b
commit
6a045cd643
|
|
@ -8,19 +8,17 @@
|
||||||
public final class Entity: UniqueEntityIdentifiable {
|
public final class Entity: UniqueEntityIdentifiable {
|
||||||
public let uei: UEI
|
public let uei: UEI
|
||||||
|
|
||||||
|
fileprivate var eventDispatcher: EventDispatcher
|
||||||
|
|
||||||
fileprivate var componentMap: [UCT:Component]
|
fileprivate var componentMap: [UCT:Component]
|
||||||
|
|
||||||
private init(uei: UEI) {
|
init(uei: UEI, dispatcher: EventDispatcher) {
|
||||||
self.uei = uei
|
self.uei = uei
|
||||||
|
self.eventDispatcher = dispatcher
|
||||||
componentMap = [UCT: Component]()
|
componentMap = [UCT: Component]()
|
||||||
componentMap.reserveCapacity(2)
|
componentMap.reserveCapacity(2)
|
||||||
}
|
|
||||||
|
|
||||||
convenience public init() {
|
|
||||||
let uei: UEI = UEI.next
|
|
||||||
self.init(uei: uei)
|
|
||||||
defer {
|
defer {
|
||||||
notify(init: unownedRef)
|
notifyInit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +54,7 @@ public extension Entity {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - push component(s)
|
// MARK: - add/push component(s)
|
||||||
public extension Entity {
|
public extension Entity {
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
|
@ -70,6 +68,11 @@ public extension Entity {
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public final func add<C: Component>(component: C) -> Entity {
|
||||||
|
return push(component: component)
|
||||||
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public static func += <C: Component>(lhs: Entity, rhs: C) -> Entity {
|
public static func += <C: Component>(lhs: Entity, rhs: C) -> Entity {
|
||||||
return lhs.push(component: rhs)
|
return lhs.push(component: rhs)
|
||||||
|
|
@ -154,56 +157,85 @@ extension Entity {
|
||||||
final func destroy() {
|
final func destroy() {
|
||||||
removeAll()
|
removeAll()
|
||||||
UEI.free(uei)
|
UEI.free(uei)
|
||||||
notify(destroyed: unownedRef)
|
notifyDestoryed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Entity: EventSender {
|
extension Entity: EventDispatcher {
|
||||||
|
public func dispatch<E>(_ event: E) where E : Event {
|
||||||
private unowned var unownedRef: Entity {
|
eventDispatcher.dispatch(event)
|
||||||
return self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func notify(init: Entity) {
|
fileprivate func unowned(_ closure: @escaping (Entity) -> Void) {
|
||||||
dispatch(event: EntityCreated(entity: unownedRef))
|
#if DEBUG
|
||||||
|
let preActionCount: Int = retainCount(self)
|
||||||
|
#endif
|
||||||
|
let unownedClosure = { [unowned self] in
|
||||||
|
closure(self)
|
||||||
|
}
|
||||||
|
unownedClosure()
|
||||||
|
#if DEBUG
|
||||||
|
let postActionCount: Int = retainCount(self)
|
||||||
|
assert(postActionCount == preActionCount, "retain count missmatch [\(preActionCount)] -> [\(postActionCount)]")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notifyInit() {
|
||||||
|
unowned {
|
||||||
|
$0.dispatch(EntityCreated(entity: $0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func notify<C: Component>(add component: C) {
|
private func notify<C: Component>(add component: C) {
|
||||||
dispatch(event: ComponentAdded(component: component, to: unownedRef))
|
unowned {
|
||||||
|
$0.dispatch(ComponentAdded(component: component, to: $0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func notify<C: Component>(update newComponent: C, previous previousComponent: C) {
|
private func notify<C: Component>(update newComponent: C, previous previousComponent: C) {
|
||||||
dispatch(event: ComponentUpdated(component: newComponent, previous: previousComponent, at: unownedRef))
|
unowned {
|
||||||
|
$0.dispatch(ComponentUpdated(component: newComponent, previous: previousComponent, at: $0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func notify<C: Component>(removed component: C) {
|
private func notify<C: Component>(removed component: C) {
|
||||||
dispatch(event: ComponentRemoved(component: component, from: unownedRef))
|
unowned {
|
||||||
|
$0.dispatch(ComponentRemoved(component: component, from: $0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func notify(removed component: Component) {
|
private func notify(removed component: Component) {
|
||||||
dispatch(event: ComponentRemoved(component: component, from: unownedRef))
|
//unowned { /* this keeps a reference since we need it */
|
||||||
|
dispatch(ComponentRemoved(component: component, from: self))
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func notify(destroyed: Entity) {
|
private func notifyDestoryed() {
|
||||||
dispatch(event: EntityDestroyed(entity: unownedRef))
|
//unowned {
|
||||||
|
//$0.dispatch(event: EntityDestroyed(entity: $0))
|
||||||
|
//TODO: here entity is already dead
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - debugging and string representation
|
// MARK: - debugging and string representation
|
||||||
/*
|
|
||||||
extension Entity: CustomStringConvertible, CustomDebugStringConvertible {
|
extension Entity: CustomStringConvertible {
|
||||||
public var description: String { return "Entity\(stringifyLabel())[\(uid)]" }
|
|
||||||
public var debugDescription: String {
|
fileprivate var componentsString: String {
|
||||||
let comps: String = self.componentMap.map { (_: ComponentType, comp: Component) in
|
let compTypes: [String] = componentMap.map { String(describing: $0.value.uct.type) }
|
||||||
return comp.debugDescription
|
return compTypes.joined(separator: ",")
|
||||||
}.joined(separator: ",")
|
|
||||||
return "\(self.description){ \(comps) }"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
return "Entity[\(uei)][\(numComponents):\(componentsString)]"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Entity: CustomPlaygroundQuickLookable {
|
extension Entity: CustomPlaygroundQuickLookable {
|
||||||
public var customPlaygroundQuickLook: PlaygroundQuickLook {
|
public var customPlaygroundQuickLook: PlaygroundQuickLook {
|
||||||
return .text(self.debugDescription)
|
return .text(self.description)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// EntityHub.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 09.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
class EntityHub: EventHandler {
|
||||||
|
weak var delegate: EventHub?
|
||||||
|
lazy var eventCenter: DefaultEventHub = { return DefaultEventHub() }()
|
||||||
|
|
||||||
|
private(set) var entites: [UEI:Entity] = [:]
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.delegate = eventCenter
|
||||||
|
|
||||||
|
subscribe(event: handleEntityCreated)
|
||||||
|
subscribe(event: handleComponentAdded)
|
||||||
|
}
|
||||||
|
deinit {
|
||||||
|
unsubscribe(event: handleEntityCreated)
|
||||||
|
unsubscribe(event: handleComponentAdded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEntity() -> Entity {
|
||||||
|
let newEntity = Entity(uei: UEI.next, dispatcher: eventCenter)
|
||||||
|
// ^ dispatches entity creation event here ^
|
||||||
|
let prevEntity: Entity? = entites.updateValue(newEntity, forKey: newEntity.uei)
|
||||||
|
assert(prevEntity == nil)
|
||||||
|
|
||||||
|
return newEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - event handler
|
||||||
|
extension EntityHub {
|
||||||
|
func handleEntityCreated(_ ec: EntityCreated) {
|
||||||
|
print(ec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleComponentAdded(_ ca: ComponentAdded) {
|
||||||
|
print(ca)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
//
|
|
||||||
// Event.swift
|
|
||||||
// FirebladeECS
|
|
||||||
//
|
|
||||||
// Created by Christian Treffs on 08.10.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
protocol ECSEvent: UniqueEventIdentifiable { }
|
|
||||||
extension ECSEvent {
|
|
||||||
static var uet: UET { return UET(Self.self) }
|
|
||||||
var uet: UET { return Self.uet }
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - event dispachter protocol
|
|
||||||
protocol EventHandler: class {
|
|
||||||
func subscribe<E: ECSEvent>(event eventHandler: @escaping (E) -> Void)
|
|
||||||
func unsubscribe<E: ECSEvent>(event eventHandler: @escaping (E) -> Void)
|
|
||||||
|
|
||||||
unowned var listenerRef: EventHandler { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EventHandler {
|
|
||||||
|
|
||||||
unowned var listenerRef: EventHandler {
|
|
||||||
return self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subscribe with an event handler closure to receive events of type T
|
|
||||||
///
|
|
||||||
/// - Parameter eventHandler: event handler closure
|
|
||||||
func subscribe<E: ECSEvent>(event eventHandler: @escaping (E) -> Void) {
|
|
||||||
EventHub.shared.add(listener: listenerRef, handler: eventHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unsubscribe from an event handler closure to stop receiving events of type T
|
|
||||||
///
|
|
||||||
/// - Parameter eventHandler: event handler closure
|
|
||||||
func unsubscribe<E: ECSEvent>(event eventHandler: @escaping (E) -> Void) {
|
|
||||||
EventHub.shared.remove(listener: listenerRef, handler: eventHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol EventSender: class {
|
|
||||||
func dispatch<E: ECSEvent>(event: E)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EventSender {
|
|
||||||
/// Dispatch an event of type E
|
|
||||||
///
|
|
||||||
/// - Parameter event: event of type E
|
|
||||||
func dispatch<E: ECSEvent>(event: E) {
|
|
||||||
EventHub.shared.dispatch(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - event hub central
|
|
||||||
fileprivate typealias EventListener = (weakRef: EventHandler, eventHandler: (ECSEvent) -> Void )
|
|
||||||
final class EventHub {
|
|
||||||
|
|
||||||
static let shared: EventHub = EventHub()
|
|
||||||
|
|
||||||
private var listeners: [UET: ContiguousArray<EventListener>] = [:]
|
|
||||||
|
|
||||||
private init() {}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ContiguousArray where Element == EventListener {
|
|
||||||
func index(is listenerRef: EventHandler) -> Int? {
|
|
||||||
return index { (eventListener: EventListener) -> Bool in
|
|
||||||
return eventListener.weakRef === listenerRef
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EventHub {
|
|
||||||
|
|
||||||
private static func relayEvent<E: ECSEvent>(opaqueEvent: ECSEvent, eventHandler: @escaping (E) -> Void ) {
|
|
||||||
guard let typedEvent: E = opaqueEvent as? E else {
|
|
||||||
fatalError() // TODO: meaningful message
|
|
||||||
}
|
|
||||||
eventHandler(typedEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
final func add<E: ECSEvent>(listener listenerRef: EventHandler, handler: @escaping (E) -> Void) {
|
|
||||||
let eventListener: EventListener = (weakRef: listenerRef,
|
|
||||||
eventHandler: { EventHub.relayEvent(opaqueEvent: $0, eventHandler: handler) })
|
|
||||||
|
|
||||||
push(listener: eventListener, for: E.uet)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func push(listener newListener: EventListener, `for` uet: UET) {
|
|
||||||
if listeners[uet] == nil {
|
|
||||||
listeners[uet] = []
|
|
||||||
listeners.reserveCapacity(1)
|
|
||||||
}
|
|
||||||
listeners[uet]?.append(newListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EventHub {
|
|
||||||
|
|
||||||
final func remove<E: ECSEvent>(listener listenerRef: EventHandler, handler: @escaping (E) -> Void) {
|
|
||||||
let uet: UET = E.uet
|
|
||||||
|
|
||||||
if let removeIdx: Int = listeners[uet]?.index(is: listenerRef) {
|
|
||||||
let removed = listeners[uet]?.remove(at: removeIdx)
|
|
||||||
assert(removed != nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EventHub {
|
|
||||||
|
|
||||||
final func dispatch<E: ECSEvent>(_ event: E) {
|
|
||||||
let uet: UET = E.uet
|
|
||||||
listeners[uet]?.forEach {
|
|
||||||
$0.eventHandler(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
extension EventHandler {
|
|
||||||
func subscribe<T: Event>(event eventHandler: @escaping (T) -> Void, syncOnQueue queue: WorkQueue)
|
|
||||||
func subscribe<T: Event>(event eventHandler: @escaping (T) -> Void, asyncOnQueue queue: WorkQueue)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EventSender {
|
|
||||||
|
|
||||||
/// Subscribe with a synchronous dispatched event handler closure to receive events of type T
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - eventHandler: event handler closure
|
|
||||||
/// - queue: queue to handle events
|
|
||||||
func subscribe<T: Event>(event eventHandler: @escaping (T) -> Void, syncOnQueue queue: WorkQueue) {
|
|
||||||
EventHub.shared.addSyncListener(owner: type(of: self), syncOnQueue: queue, handler: eventHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subscribe with an asynchronous dispatched event handler closure to receive events of type T
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - eventHandler: event handler closure
|
|
||||||
/// - queue: queue to handle events on
|
|
||||||
func subscribe<T: Event>(event eventHandler: @escaping (T) -> Void, asyncOnQueue queue: WorkQueue) {
|
|
||||||
EventHub.shared.addAsyncListener(owner: type(of: self), asyncOnQueue: queue, handler: eventHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EventHub {
|
|
||||||
|
|
||||||
final func addSyncListener<T: Event>(owner: AnyClass, syncOnQueue queue: WorkQueue, handler: @escaping (T) -> Void) {
|
|
||||||
let syncListener: Listener = (owner: owner, handler: { event in
|
|
||||||
guard let tEvent: T = event as? T else { fatalError("can not cast event to required type") }
|
|
||||||
queue.syncExec { handler(tEvent) }
|
|
||||||
})
|
|
||||||
addToList(eventType: T.eventType, listener: syncListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
final func addAsyncListener<T: Event>(owner: AnyClass, asyncOnQueue queue: WorkQueue, handler: @escaping (T) -> Void) {
|
|
||||||
let asyncListener: Listener = (owner: owner, handler: { event in
|
|
||||||
guard let tEvent: T = event as? T else { fatalError("can not cast event to required type") }
|
|
||||||
queue.asyncExec { handler(tEvent) }
|
|
||||||
})
|
|
||||||
addToList(eventType: T.eventType, listener: asyncListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
//
|
||||||
|
// EventHandling.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 08.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
// MARK: - event
|
||||||
|
public protocol Event: UniqueEventIdentifiable { }
|
||||||
|
public extension Event {
|
||||||
|
static var uet: UET { return UET(Self.self) }
|
||||||
|
var uet: UET { return Self.uet }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - event handler
|
||||||
|
public struct EventHandlerRef {
|
||||||
|
unowned var eventHandlerClassRefUnowned: EventHandler
|
||||||
|
let eventHandler: (Event) -> Void
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol EventHandler: class {
|
||||||
|
func subscribe<E: Event>(event eventHandler: @escaping (E) -> Void)
|
||||||
|
func unsubscribe<E: Event>(event eventHandler: @escaping (E) -> Void)
|
||||||
|
|
||||||
|
weak var delegate: EventHub? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension EventHandler {
|
||||||
|
|
||||||
|
fileprivate func unowned(_ closure: @escaping (EventHandler) -> Void) {
|
||||||
|
#if DEBUG
|
||||||
|
let preActionCount: Int = retainCount(self)
|
||||||
|
#endif
|
||||||
|
let unownedClosure = { [unowned self] in
|
||||||
|
closure(self)
|
||||||
|
}
|
||||||
|
unownedClosure()
|
||||||
|
#if DEBUG
|
||||||
|
let postActionCount: Int = retainCount(self)
|
||||||
|
assert(postActionCount == preActionCount, "retain count missmatch [\(preActionCount)] -> [\(postActionCount)]")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribe with an event handler closure to receive events of type T
|
||||||
|
///
|
||||||
|
/// - Parameter eventHandler: event handler closure
|
||||||
|
public func subscribe<E: Event>(event eventHandler: @escaping (E) -> Void) {
|
||||||
|
unowned {
|
||||||
|
self.delegate?.subscribe(classRef: $0, eventHandler: eventHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unsubscribe from an event handler closure to stop receiving events of type T
|
||||||
|
///
|
||||||
|
/// - Parameter eventHandler: event handler closure
|
||||||
|
public func unsubscribe<E: Event>(event eventHandler: @escaping (E) -> Void) {
|
||||||
|
unowned {
|
||||||
|
self.delegate?.unsubscribe(classRef: $0, eventHandler: eventHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ContiguousArray where Element == EventHandlerRef {
|
||||||
|
public func index(is eventHandler: EventHandler) -> Int? { // FIXME: this my be costly
|
||||||
|
return index { (eventHandlerRef: EventHandlerRef) -> Bool in
|
||||||
|
return eventHandlerRef.eventHandlerClassRefUnowned === eventHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - event dispatcher
|
||||||
|
public protocol EventDispatcher: class {
|
||||||
|
func dispatch<E: Event>(_ event: E)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
//
|
||||||
|
// EventHub.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 09.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
public protocol EventHub: class {
|
||||||
|
func subscribe<E: Event>(classRef eventHandlerRefUnowned: EventHandler, eventHandler: @escaping (E) -> Void)
|
||||||
|
func unsubscribe<E: Event>(classRef eventHandlerRefUnowned: EventHandler, eventHandler: @escaping (E) -> Void)
|
||||||
|
|
||||||
|
weak var sniffer: EventSniffer? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DefaultEventHub: EventHub, EventDispatcher {
|
||||||
|
|
||||||
|
public var sniffer: EventSniffer?
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
|
||||||
|
private var listeners: [UET: ContiguousArray<EventHandlerRef>] = [:]
|
||||||
|
|
||||||
|
private func typeEvent<E: Event>(opaqueEvent: Event, eventHandler: @escaping (E) -> Void ) {
|
||||||
|
guard let typedEvent: E = opaqueEvent as? E else {
|
||||||
|
fatalError() // TODO: meaningful message
|
||||||
|
}
|
||||||
|
eventHandler(typedEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func push(listener newListener: EventHandlerRef, `for` uet: UET) {
|
||||||
|
if listeners[uet] == nil {
|
||||||
|
listeners[uet] = []
|
||||||
|
listeners.reserveCapacity(1)
|
||||||
|
}
|
||||||
|
listeners[uet]?.append(newListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - event center
|
||||||
|
extension DefaultEventHub {
|
||||||
|
|
||||||
|
final public func subscribe<E: Event>(classRef unownedListenerRef: EventHandler, eventHandler handler: @escaping (E) -> Void) {
|
||||||
|
let eventListener: EventHandlerRef = EventHandlerRef(eventHandlerClassRefUnowned: unownedListenerRef,
|
||||||
|
eventHandler: { self.typeEvent(opaqueEvent: $0, eventHandler: handler) })
|
||||||
|
|
||||||
|
push(listener: eventListener, for: E.uet)
|
||||||
|
sniffer?.subscriber(subsribed: E.uet)
|
||||||
|
}
|
||||||
|
|
||||||
|
final public func unsubscribe<E: Event>(classRef listenerRef: EventHandler, eventHandler handler: @escaping (E) -> Void) {
|
||||||
|
let uet: UET = E.uet
|
||||||
|
|
||||||
|
if let removeIdx: Int = listeners[uet]?.index(is: listenerRef) {
|
||||||
|
let removed = listeners[uet]?.remove(at: removeIdx)
|
||||||
|
assert(removed != nil)
|
||||||
|
sniffer?.subscriber(unsubsribed: uet)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - event dispatcher delegate
|
||||||
|
extension DefaultEventHub {
|
||||||
|
final public func dispatch<E: Event>(_ event: E) {
|
||||||
|
let uet: UET = E.uet
|
||||||
|
listeners[uet]?.forEach {
|
||||||
|
$0.eventHandler(event)
|
||||||
|
}
|
||||||
|
sniffer?.dispatched(event: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - event dumper
|
||||||
|
public protocol EventSniffer: class {
|
||||||
|
func subscriber(subsribed to: UET)
|
||||||
|
func subscriber(unsubsribed from: UET)
|
||||||
|
func dispatched<E: Event>(event: E)
|
||||||
|
}
|
||||||
|
|
@ -5,26 +5,26 @@
|
||||||
// Created by Christian Treffs on 08.10.17.
|
// Created by Christian Treffs on 08.10.17.
|
||||||
//
|
//
|
||||||
|
|
||||||
struct EntityCreated: ECSEvent {
|
public struct EntityCreated: Event {
|
||||||
let entity: Entity
|
let entity: Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EntityDestroyed: ECSEvent {
|
public struct EntityDestroyed: Event {
|
||||||
let entity: Entity
|
let entity: Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ComponentAdded: ECSEvent {
|
public struct ComponentAdded: Event {
|
||||||
let component: Component
|
let component: Component
|
||||||
let to: Entity
|
let to: Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ComponentUpdated: ECSEvent {
|
public struct ComponentUpdated: Event {
|
||||||
let component: Component
|
let component: Component
|
||||||
let previous: Component
|
let previous: Component
|
||||||
let at: Entity
|
let at: Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ComponentRemoved: ECSEvent {
|
public struct ComponentRemoved: Event {
|
||||||
let component: Component
|
let component: Component
|
||||||
let from: Entity
|
let from: Entity
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,15 @@
|
||||||
/// Unique Event Type
|
/// Unique Event Type
|
||||||
public struct UET {
|
public struct UET {
|
||||||
let objectIdentifier: ObjectIdentifier
|
let objectIdentifier: ObjectIdentifier
|
||||||
let type: ECSEvent.Type
|
let type: Event.Type
|
||||||
|
|
||||||
init(_ eventType: ECSEvent.Type) {
|
init(_ eventType: Event.Type) {
|
||||||
objectIdentifier = ObjectIdentifier(eventType)
|
objectIdentifier = ObjectIdentifier(eventType)
|
||||||
type = eventType
|
type = eventType
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ event: ECSEvent) {
|
init(_ event: Event) {
|
||||||
let eventType: ECSEvent.Type = event.uet.type
|
let eventType: Event.Type = event.uet.type
|
||||||
self.init(eventType)
|
self.init(eventType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// Utils.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 09.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if os(macOS) || os(iOS) || os(tvOS)
|
||||||
|
import CoreFoundation
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if os(macOS) || os(iOS) || os(tvOS)
|
||||||
|
|
||||||
|
func retainCount<T: AnyObject>(_ ref: T?) -> Int {
|
||||||
|
let retainCount: CFIndex = CFGetRetainCount(ref)
|
||||||
|
return retainCount
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
func retainCount<T: AnyObject>(_ ref: T?) -> Int { return -1 }
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// Base.swift
|
||||||
|
// FirebladeECSTests
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 09.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FirebladeECS
|
||||||
|
|
||||||
|
struct EmptyComponent: Component { }
|
||||||
|
|
||||||
|
class DebugEventHandler: EventHandler {
|
||||||
|
|
||||||
|
let eventHub = DefaultEventHub()
|
||||||
|
|
||||||
|
var delegate: EventHub?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
delegate = eventHub
|
||||||
|
subscribe(event: handleEntityCreated)
|
||||||
|
subscribe(event: handleEntityDestroyed)
|
||||||
|
subscribe(event: handleComponentAdded)
|
||||||
|
}
|
||||||
|
deinit {
|
||||||
|
unsubscribe(event: handleEntityCreated)
|
||||||
|
unsubscribe(event: handleComponentAdded)
|
||||||
|
unsubscribe(event: handleEntityDestroyed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleEntityCreated(ec: EntityCreated) {
|
||||||
|
print(ec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleEntityDestroyed(ed: EntityDestroyed) {
|
||||||
|
print(ed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleComponentAdded(ca: ComponentAdded) {
|
||||||
|
print(ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
//
|
|
||||||
// Components.swift
|
|
||||||
// FirebladeECSTests
|
|
||||||
//
|
|
||||||
// Created by Christian Treffs on 09.10.17.
|
|
||||||
//
|
|
||||||
|
|
||||||
import FirebladeECS
|
|
||||||
|
|
||||||
struct EmptyComponent: Component { }
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
//
|
||||||
|
// EntityHubTests.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 09.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import FirebladeECS
|
||||||
|
|
||||||
|
class EntityHubTests: XCTestCase {
|
||||||
|
|
||||||
|
let entityHub = EntityHub()
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
entityHub.eventCenter.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCreateEntityAndAddComponent() {
|
||||||
|
let newEntity: Entity = entityHub.createEntity()
|
||||||
|
|
||||||
|
let emptyComp = EmptyComponent()
|
||||||
|
|
||||||
|
newEntity.add(component: emptyComp)
|
||||||
|
|
||||||
|
XCTAssert(newEntity.hasComponents)
|
||||||
|
XCTAssert(newEntity.numComponents == 1)
|
||||||
|
|
||||||
|
XCTAssert(entityHub.entites[newEntity.uei] == newEntity)
|
||||||
|
XCTAssert(entityHub.entites[newEntity.uei] === newEntity)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EntityHubTests: EventSniffer {
|
||||||
|
public func subscriber(subsribed to: UET) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public func subscriber(unsubsribed from: UET) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dispatched<E>(event: E) where E : Event {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import XCTest
|
|
||||||
@testable import FirebladeECS
|
|
||||||
|
|
||||||
class FirebladeECSTests: XCTestCase {
|
|
||||||
|
|
||||||
class MyEventHandler: EventHandler {
|
|
||||||
init() {
|
|
||||||
subscribe(event: handleEntityCreated)
|
|
||||||
subscribe(event: handleEntityDestroyed)
|
|
||||||
subscribe(event: handleComponentAdded)
|
|
||||||
}
|
|
||||||
deinit {
|
|
||||||
unsubscribe(event: handleEntityCreated)
|
|
||||||
unsubscribe(event: handleComponentAdded)
|
|
||||||
unsubscribe(event: handleEntityDestroyed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleEntityCreated(ec: EntityCreated) {
|
|
||||||
Log.warn(ec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleEntityDestroyed(ed: EntityDestroyed) {
|
|
||||||
Log.warn(ed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleComponentAdded(ca: ComponentAdded) {
|
|
||||||
Log.debug(ca)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
let eventHandler: MyEventHandler = MyEventHandler()
|
|
||||||
|
|
||||||
func testCreateEntity() {
|
|
||||||
let newEntity = Entity()
|
|
||||||
|
|
||||||
XCTAssert(newEntity.hasComponents == false)
|
|
||||||
XCTAssert(newEntity.uei == 1)
|
|
||||||
XCTAssert(newEntity.numComponents == 0)
|
|
||||||
|
|
||||||
let pC: EmptyComponent? = newEntity.peekComponent()
|
|
||||||
XCTAssert(pC == nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// MemoryTests.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 09.10.17.
|
||||||
|
//
|
||||||
|
import XCTest
|
||||||
|
@testable import FirebladeECS
|
||||||
|
|
||||||
|
class MemoryTests: XCTestCase {
|
||||||
|
|
||||||
|
func testCheckRetainCount() {
|
||||||
|
|
||||||
|
class MyClass {}
|
||||||
|
let myClass = MyClass()
|
||||||
|
|
||||||
|
let initalCount: Int = FirebladeECS.retainCount(myClass)
|
||||||
|
XCTAssert(initalCount == 3)
|
||||||
|
|
||||||
|
let ownerdSelf: MyClass = myClass
|
||||||
|
_ = ownerdSelf // creates strong reference
|
||||||
|
let ownedOnceCount: Int = FirebladeECS.retainCount(myClass)
|
||||||
|
XCTAssert(ownedOnceCount == initalCount + 1) // 4
|
||||||
|
|
||||||
|
unowned let unownedSelf: MyClass = myClass
|
||||||
|
_ = unownedSelf // creates unowned refrerence
|
||||||
|
let unownedOnceCount: Int = FirebladeECS.retainCount(myClass)
|
||||||
|
let unownedCount: Int = FirebladeECS.retainCount(unownedSelf)
|
||||||
|
XCTAssert(ownedOnceCount == unownedCount)
|
||||||
|
XCTAssert(ownedOnceCount == unownedOnceCount)
|
||||||
|
|
||||||
|
weak var weakSelf: MyClass? = myClass
|
||||||
|
_ = weakSelf // creates weak refrerence
|
||||||
|
let weakOnceCount: Int = FirebladeECS.retainCount(myClass)
|
||||||
|
let weakCount: Int = FirebladeECS.retainCount(weakSelf)
|
||||||
|
XCTAssert(ownedOnceCount == weakCount)
|
||||||
|
XCTAssert(ownedOnceCount == weakOnceCount)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
//
|
||||||
|
// PerformanceTests.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 09.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import FirebladeECS
|
||||||
|
|
||||||
|
class PerformanceTests: XCTestCase {
|
||||||
|
|
||||||
|
var eventHub: DefaultEventHub = DefaultEventHub()
|
||||||
|
|
||||||
|
var entityArray: [Entity] = [Entity]()
|
||||||
|
var entityContArray: ContiguousArray<Entity> = ContiguousArray<Entity>()
|
||||||
|
|
||||||
|
var entitySet: Set<Entity> = Set<Entity>()
|
||||||
|
var entityMap: [UEI: Entity] = [UEI: Entity]()
|
||||||
|
|
||||||
|
static let maxNum: Int = 65536
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
entitySet.removeAll()
|
||||||
|
entitySet.reserveCapacity(0)
|
||||||
|
|
||||||
|
entityMap.removeAll()
|
||||||
|
entityMap.reserveCapacity(0)
|
||||||
|
|
||||||
|
entityArray.removeAll()
|
||||||
|
entityArray.reserveCapacity(0)
|
||||||
|
|
||||||
|
entityContArray.removeAll()
|
||||||
|
entityContArray.reserveCapacity(0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEntitySetLinearInsert() {
|
||||||
|
measure {
|
||||||
|
for i in 0..<65536 {
|
||||||
|
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
|
||||||
|
entitySet.insert(newEntity)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEntityMapLinearInsert() {
|
||||||
|
entityMap.removeAll()
|
||||||
|
entityMap.reserveCapacity(0)
|
||||||
|
measure {
|
||||||
|
for i in 0..<65536 {
|
||||||
|
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
|
||||||
|
entityMap[newEntity.uei] = newEntity
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEntityMapUpdateLinarInsert() {
|
||||||
|
entityMap.removeAll()
|
||||||
|
entityMap.reserveCapacity(0)
|
||||||
|
|
||||||
|
measure {
|
||||||
|
for i in 0..<65536 {
|
||||||
|
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
|
||||||
|
entityMap.updateValue(newEntity, forKey: newEntity.uei)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEntityContArrayLinearInsert() {
|
||||||
|
measure {
|
||||||
|
for i in 0..<65536 {
|
||||||
|
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
|
||||||
|
entityContArray.append(newEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEntityContArrayFixPosInsert() {
|
||||||
|
|
||||||
|
let count: Int = Int(65536)
|
||||||
|
var entityContArray2 = ContiguousArray<Entity!>(repeating: nil, count: count)
|
||||||
|
entityContArray2.reserveCapacity(count)
|
||||||
|
measure {
|
||||||
|
for i: Int in 0..<count-1 {
|
||||||
|
let uei = UEI(i)
|
||||||
|
let newEntity = Entity(uei: uei, dispatcher: eventHub)
|
||||||
|
|
||||||
|
entityContArray2[i] = newEntity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEntityArrayLinearInsert() {
|
||||||
|
measure {
|
||||||
|
for i in 0..<65536 {
|
||||||
|
let newEntity = Entity(uei: UEI(i), dispatcher: eventHub)
|
||||||
|
entityArray.append(newEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue