Add ECS basics
This commit is contained in:
parent
32685c37a2
commit
4afb0ff42d
|
|
@ -9,7 +9,7 @@ let package = Package(
|
||||||
// Products define the executables and libraries produced by a package, and make them visible to other packages.
|
// Products define the executables and libraries produced by a package, and make them visible to other packages.
|
||||||
.library(
|
.library(
|
||||||
name: "FirebladeECS",
|
name: "FirebladeECS",
|
||||||
targets: ["FirebladeECS"]),
|
targets: ["FirebladeECS"])
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
// Dependencies declare other packages that this package depends on.
|
// Dependencies declare other packages that this package depends on.
|
||||||
|
|
@ -23,6 +23,6 @@ let package = Package(
|
||||||
dependencies: []),
|
dependencies: []),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "FirebladeECSTests",
|
name: "FirebladeECSTests",
|
||||||
dependencies: ["FirebladeECS"]),
|
dependencies: ["FirebladeECS"])
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// Component.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 08.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
public protocol Component: UniqueComponentIdentifiable {}
|
||||||
|
|
||||||
|
// MARK: UCI
|
||||||
|
extension Component {
|
||||||
|
/// Uniquely identifies the component by its meta type
|
||||||
|
public static var uct: UCT { return UCT(Self.self) }
|
||||||
|
/// Uniquely identifies the component by its meta type
|
||||||
|
public var uct: UCT { return Self.uct }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Equatable
|
||||||
|
public func ==<A: Component, B: Component>(lhs: A, rhs: B) -> Bool {
|
||||||
|
return A.uct == B.uct
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
//
|
||||||
|
// Entity.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 08.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
public final class Entity: UniqueEntityIdentifiable {
|
||||||
|
public let uei: UEI
|
||||||
|
|
||||||
|
fileprivate var componentMap: [UCT:Component]
|
||||||
|
|
||||||
|
private init(uei: UEI) {
|
||||||
|
self.uei = uei
|
||||||
|
componentMap = [UCT: Component]()
|
||||||
|
componentMap.reserveCapacity(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience public init() {
|
||||||
|
let uei: UEI = UEI.next
|
||||||
|
self.init(uei: uei)
|
||||||
|
defer {
|
||||||
|
notify(init: unownedRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
defer {
|
||||||
|
destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Equatable
|
||||||
|
public func ==(lhs: Entity, rhs: Entity) -> Bool {
|
||||||
|
return lhs.uei == rhs.uei
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - number of components
|
||||||
|
public extension Entity {
|
||||||
|
public final var numComponents: Int { return componentMap.count }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - has component(s)
|
||||||
|
public extension Entity {
|
||||||
|
|
||||||
|
public final func has(_ component: Component.Type) -> Bool {
|
||||||
|
return has(UCT(component))
|
||||||
|
}
|
||||||
|
|
||||||
|
public final func has(_ component: UCT) -> Bool {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
public final var hasComponents: Bool { return !componentMap.isEmpty }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - push component(s)
|
||||||
|
public extension Entity {
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public final func push<C: Component>(component: C) -> Entity {
|
||||||
|
let previousComponent: C? = componentMap.updateValue(component, forKey: component.uct) as? C
|
||||||
|
if let pComp: C = previousComponent {
|
||||||
|
notify(update: component, previous: pComp)
|
||||||
|
} else {
|
||||||
|
notify(add: component)
|
||||||
|
}
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public static func += <C: Component>(lhs: Entity, rhs: C) -> Entity {
|
||||||
|
return lhs.push(component: rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public static func << <C: Component>(lhs: Entity, rhs: C) -> Entity {
|
||||||
|
return lhs.push(component: rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - peek component
|
||||||
|
public extension Entity {
|
||||||
|
|
||||||
|
public final func peekComponent<C: Component>() -> C? {
|
||||||
|
return componentMap[C.uct] as? C
|
||||||
|
}
|
||||||
|
|
||||||
|
public final func peek<C: Component>(_ componentType: C.Type) -> C? {
|
||||||
|
return componentMap[componentType.uct] as? C
|
||||||
|
}
|
||||||
|
|
||||||
|
public final func peek<C: Component>(_ uct: UCT) -> C? {
|
||||||
|
return componentMap[uct] as? C
|
||||||
|
}
|
||||||
|
|
||||||
|
public final func peek<C: Component>(_ unwrapping: (C?) -> C) -> C {
|
||||||
|
return unwrapping(peekComponent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public final func peek<C: Component, Result>(_ applying: (C?) -> Result) -> Result {
|
||||||
|
return applying(peekComponent())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - remove component(s)
|
||||||
|
public extension Entity {
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public final func remove(_ component: Component) -> Entity {
|
||||||
|
return remove(component.uct)
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public final func remove<C: Component>(_ componentType: C.Type) -> Entity {
|
||||||
|
let removedComponent: C? = componentMap.removeValue(forKey: C.uct) as? C
|
||||||
|
if let rComp: C = removedComponent {
|
||||||
|
notify(removed: rComp)
|
||||||
|
}
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public final func remove(_ uct: UCT) -> Entity {
|
||||||
|
let removedComponent = componentMap.removeValue(forKey: uct)
|
||||||
|
if let rComp = removedComponent {
|
||||||
|
assert(rComp.uct.type == uct.type)
|
||||||
|
notify(removed: rComp)
|
||||||
|
}
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
public final func removeAll() {
|
||||||
|
componentMap.forEach { remove($0.key) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public static func -= <C: Component>(lhs: Entity, rhs: C) -> Entity {
|
||||||
|
return lhs.remove(rhs)
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public static func -= <C: Component>(lhs: Entity, rhs: C.Type) -> Entity {
|
||||||
|
return lhs.remove(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - destroy/deinit entity
|
||||||
|
extension Entity {
|
||||||
|
final func destroy() {
|
||||||
|
removeAll()
|
||||||
|
UEI.free(uei)
|
||||||
|
notify(destroyed: unownedRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Entity: EventSender {
|
||||||
|
|
||||||
|
private unowned var unownedRef: Entity {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notify(init: Entity) {
|
||||||
|
dispatch(event: EntityCreated(entity: unownedRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notify<C: Component>(add component: C) {
|
||||||
|
dispatch(event: ComponentAdded(component: component, to: unownedRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notify<C: Component>(update newComponent: C, previous previousComponent: C) {
|
||||||
|
dispatch(event: ComponentUpdated(component: newComponent, previous: previousComponent, at: unownedRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notify<C: Component>(removed component: C) {
|
||||||
|
dispatch(event: ComponentRemoved(component: component, from: unownedRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notify(removed component: Component) {
|
||||||
|
dispatch(event: ComponentRemoved(component: component, from: unownedRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notify(destroyed: Entity) {
|
||||||
|
dispatch(event: EntityDestroyed(entity: unownedRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - debugging and string representation
|
||||||
|
/*
|
||||||
|
extension Entity: CustomStringConvertible, CustomDebugStringConvertible {
|
||||||
|
public var description: String { return "Entity\(stringifyLabel())[\(uid)]" }
|
||||||
|
public var debugDescription: String {
|
||||||
|
let comps: String = self.componentMap.map { (_: ComponentType, comp: Component) in
|
||||||
|
return comp.debugDescription
|
||||||
|
}.joined(separator: ",")
|
||||||
|
return "\(self.description){ \(comps) }"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extension Entity: CustomPlaygroundQuickLookable {
|
||||||
|
public var customPlaygroundQuickLook: PlaygroundQuickLook {
|
||||||
|
return .text(self.debugDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
//
|
||||||
|
// 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 {
|
||||||
|
|
||||||
|
/// 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,44 @@
|
||||||
|
//
|
||||||
|
// Events.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 08.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
struct EntityCreated: ECSEvent {
|
||||||
|
let entity: Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EntityDestroyed: ECSEvent {
|
||||||
|
let entity: Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ComponentAdded: ECSEvent {
|
||||||
|
let component: Component
|
||||||
|
let to: Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ComponentUpdated: ECSEvent {
|
||||||
|
let component: Component
|
||||||
|
let previous: Component
|
||||||
|
let at: Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ComponentRemoved: ECSEvent {
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
//
|
||||||
|
// Family.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FamilyDestroyed: Event {
|
||||||
|
//TODO: family
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class Family: EventSender, EventHandler {
|
||||||
|
|
||||||
|
// members of this Family must conform to:
|
||||||
|
let required: Set<ComponentType>
|
||||||
|
let excluded: Set<ComponentType>
|
||||||
|
|
||||||
|
public private(set) var members: ContiguousArray<Entity>
|
||||||
|
|
||||||
|
public convenience init(requiresAll required: ComponentType...) {
|
||||||
|
self.init(requiresAll: required, excludesAll: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(requiresAll required: [ComponentType], excludesAll excluded: [ComponentType]) {
|
||||||
|
self.required = Set<ComponentType>(required)
|
||||||
|
self.excluded = Set<ComponentType>(excluded)
|
||||||
|
|
||||||
|
self.members = []
|
||||||
|
|
||||||
|
subscribe(event: handleComponentAddedToEntity)
|
||||||
|
subscribe(event: handleComponentRemovedFromEntity)
|
||||||
|
|
||||||
|
dispatch(event: FamilyCreated(family: self))
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
|
||||||
|
//TODO: optimize for large sets
|
||||||
|
//TODO: dispatch entity removed event
|
||||||
|
self.members.removeAll()
|
||||||
|
|
||||||
|
unsubscribe(event: handleComponentAddedToEntity)
|
||||||
|
unsubscribe(event: handleComponentRemovedFromEntity)
|
||||||
|
|
||||||
|
dispatch(event: FamilyDestroyed())
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final func add(toFamily entity: Entity) {
|
||||||
|
self.members.append(entity)
|
||||||
|
dispatch(event: FamilyMemberAdded(entity: entity, family: self))
|
||||||
|
}
|
||||||
|
|
||||||
|
private final func remove(entityAtIndex index: Int) {
|
||||||
|
let removedEntity: Entity = self.members.remove(at: index)
|
||||||
|
dispatch(event: FamilyMemberRemoved(entity: removedEntity, family: self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
struct FirebladeECS {
|
|
||||||
var text = "Hello, World!"
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
//
|
||||||
|
// Logging.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 08.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
struct Log {
|
||||||
|
|
||||||
|
private init() { }
|
||||||
|
|
||||||
|
enum Level {
|
||||||
|
case info, debug, warn, error
|
||||||
|
|
||||||
|
var toString: String {
|
||||||
|
switch self {
|
||||||
|
case .info: return "INFO"
|
||||||
|
case .debug: return "DEBUG"
|
||||||
|
case .warn: return "WARN"
|
||||||
|
case .error: return "ERROR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func info(_ args: Any?...) { Log.log(level: .info, args: args) }
|
||||||
|
static func debug(_ args: Any?...) { Log.log(level: .debug, args: args) }
|
||||||
|
static func warn(_ args: Any?...) { Log.log(level: .warn, args: args) }
|
||||||
|
static func error(_ args: Any?...) { Log.log(level: .error, args: args) }
|
||||||
|
|
||||||
|
private static func log(level: Log.Level, args: [Any?]) {
|
||||||
|
let entry: String = Log.serialize(level: level, args: args)
|
||||||
|
Swift.print(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func serialize(level: Log.Level, args: [Any?]) -> String {
|
||||||
|
let argStrings: [String] = args.flatMap { arg in
|
||||||
|
if let arg = arg {
|
||||||
|
return String(describing: arg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let argString: String = argStrings.joined(separator: " ")
|
||||||
|
let levelString: String = level.toString
|
||||||
|
|
||||||
|
return ["[", levelString, "]\t", argString].joined()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
//
|
||||||
|
// System.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 08.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
public enum SystemState {
|
||||||
|
case running, paused, inactive
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol System: class {
|
||||||
|
//var state: SystemState { set get }
|
||||||
|
func startup()
|
||||||
|
func shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol EntitySystem: System {
|
||||||
|
//TODO: var systemFamily: Family { get }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// UCT.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 08.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
// MARK: Unique Component Type
|
||||||
|
/// Unique Component Type
|
||||||
|
public struct UCT {
|
||||||
|
let objectIdentifier: ObjectIdentifier
|
||||||
|
let type: Component.Type
|
||||||
|
|
||||||
|
init(_ componentType: Component.Type) {
|
||||||
|
objectIdentifier = ObjectIdentifier(componentType)
|
||||||
|
type = componentType
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ component: Component) {
|
||||||
|
let componentType: Component.Type = component.uct.type
|
||||||
|
self.init(componentType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UCT: Equatable {
|
||||||
|
public static func ==(lhs: UCT, rhs: UCT) -> Bool {
|
||||||
|
return lhs.objectIdentifier == rhs.objectIdentifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UCT: Hashable {
|
||||||
|
public var hashValue: Int {
|
||||||
|
return objectIdentifier.hashValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Unique Component Identifiable
|
||||||
|
public protocol UniqueComponentIdentifiable {
|
||||||
|
static var uct: UCT { get }
|
||||||
|
var uct: UCT { get }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// UEI.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 08.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
// MARK: Unique Entity Index
|
||||||
|
|
||||||
|
public typealias UEI = UInt32 // provides 4294967295 unique identifiers
|
||||||
|
|
||||||
|
public extension UEI {
|
||||||
|
|
||||||
|
private static var currentUEI: UEI = UInt32.min // starts at 0
|
||||||
|
|
||||||
|
/// Provides the next (higher/free) unique entity identifer.
|
||||||
|
/// Minimum: 1, maximum: 4294967295.
|
||||||
|
/// - Returns: next higher unique entity identifer.
|
||||||
|
public static var next: UEI {
|
||||||
|
currentUEI += 1
|
||||||
|
return currentUEI
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static func free(_ uei: UEI) {
|
||||||
|
// TODO: free used index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Unique Entity Identifiable
|
||||||
|
public protocol UniqueEntityIdentifiable: Hashable {
|
||||||
|
var uei: UEI { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension UniqueEntityIdentifiable {
|
||||||
|
public var hashValue: Int { return uei.hashValue }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// UET.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 09.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
// MARK: Unique Event Type
|
||||||
|
/// Unique Event Type
|
||||||
|
public struct UET {
|
||||||
|
let objectIdentifier: ObjectIdentifier
|
||||||
|
let type: ECSEvent.Type
|
||||||
|
|
||||||
|
init(_ eventType: ECSEvent.Type) {
|
||||||
|
objectIdentifier = ObjectIdentifier(eventType)
|
||||||
|
type = eventType
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ event: ECSEvent) {
|
||||||
|
let eventType: ECSEvent.Type = event.uet.type
|
||||||
|
self.init(eventType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UET: Equatable {
|
||||||
|
public static func ==(lhs: UET, rhs: UET) -> Bool {
|
||||||
|
return lhs.objectIdentifier == rhs.objectIdentifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UET: Hashable {
|
||||||
|
public var hashValue: Int {
|
||||||
|
return objectIdentifier.hashValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Unique Event Identifiable
|
||||||
|
public protocol UniqueEventIdentifiable {
|
||||||
|
static var uet: UET { get }
|
||||||
|
var uet: UET { get }
|
||||||
|
}
|
||||||
|
|
@ -2,15 +2,11 @@ import XCTest
|
||||||
@testable import FirebladeECS
|
@testable import FirebladeECS
|
||||||
|
|
||||||
class FirebladeECSTests: XCTestCase {
|
class FirebladeECSTests: XCTestCase {
|
||||||
func testExample() {
|
|
||||||
// This is an example of a functional test case.
|
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
|
||||||
// results.
|
|
||||||
XCTAssertEqual(FirebladeECS().text, "Hello, World!")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static var allTests = [
|
func testCreateEntity() {
|
||||||
("testExample", testExample),
|
let newEntity = Entity()
|
||||||
]
|
XCTAssert(newEntity.hasComponents == false)
|
||||||
|
XCTAssert(newEntity.uei == 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@ import XCTest
|
||||||
@testable import FirebladeECSTests
|
@testable import FirebladeECSTests
|
||||||
|
|
||||||
XCTMain([
|
XCTMain([
|
||||||
testCase(FirebladeECSTests.allTests),
|
testCase(FirebladeECSTests.allTests)
|
||||||
])
|
])
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue