Introduce unsafe API for performance and refine Single
This commit is contained in:
parent
ef7859a021
commit
c1a68300de
|
|
@ -58,6 +58,10 @@ public class ManagedContiguousArray<Element>: UniformStorage {
|
|||
return store[index]
|
||||
}
|
||||
|
||||
public func get(unsafeAt index: Index) -> Element {
|
||||
return store[index].unsafelyUnwrapped
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func remove(at index: Index) -> Bool {
|
||||
if store[index] != nil {
|
||||
|
|
|
|||
|
|
@ -64,11 +64,22 @@ public extension Nexus {
|
|||
return uniformComponents.get(at: entityId.index)
|
||||
}
|
||||
|
||||
final func get(unsafeComponent componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component {
|
||||
let uniformComponents: UniformComponents = componentsByType[componentId].unsafelyUnwrapped
|
||||
return uniformComponents.get(unsafeAt: entityId.index)
|
||||
}
|
||||
|
||||
final func get<C>(for entityId: EntityIdentifier) -> C? where C: Component {
|
||||
let componentId: ComponentIdentifier = C.identifier
|
||||
return get(componentId: componentId, entityIdx: entityId.index)
|
||||
}
|
||||
|
||||
final func get<C>(unsafeComponentFor entityId: EntityIdentifier) -> C where C: Component {
|
||||
let component: Component = get(unsafeComponent: C.identifier, for: entityId)
|
||||
/// components are guaranteed to be reference tyes so unsafeDowncast is applicable here
|
||||
return unsafeDowncast(component, to: C.self)
|
||||
}
|
||||
|
||||
final func get(components entityId: EntityIdentifier) -> SparseComponentIdentifierSet? {
|
||||
return componentIdsByEntity[entityId.index]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ extension Nexus {
|
|||
|
||||
// swiftlint:disable function_default_parameter_at_end
|
||||
@discardableResult
|
||||
public func create(entity name: String? = nil, with assignedComponents: Component...) -> Entity {
|
||||
public func create(entity name: String? = nil, with assignedComponents: Component...) -> Entity {
|
||||
let newEntityIndex: EntityIndex = nextEntityIdx()
|
||||
let newEntityIdentifier: EntityIdentifier = newEntityIndex.identifier
|
||||
let newEntity = Entity(nexus: self, id: newEntityIdentifier, name: name)
|
||||
|
|
@ -39,6 +39,10 @@ extension Nexus {
|
|||
return entityStorage.get(at: entityId.index)
|
||||
}
|
||||
|
||||
public func get(unsafeEntity entityId: EntityIdentifier) -> Entity {
|
||||
return entityStorage.get(unsafeAt: entityId.index)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func destroy(entity: Entity) -> Bool {
|
||||
let entityId: EntityIdentifier = entity.identifier
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// Nexus+TypedSingle.swift
|
||||
// FirebladeECS
|
||||
//
|
||||
// Created by Christian Treffs on 13.02.19.
|
||||
//
|
||||
|
||||
public extension Nexus {
|
||||
func single<A>(
|
||||
requires componentA: A.Type,
|
||||
excludesAll excludedComponents: Component.Type...
|
||||
) -> TypedSingle1<A> where A: Component {
|
||||
return TypedSingle1(
|
||||
self,
|
||||
requires: componentA,
|
||||
excludesAll: excludedComponents
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// Single.swift
|
||||
// FirebladeECS
|
||||
//
|
||||
// Created by Christian Treffs on 13.02.19.
|
||||
//
|
||||
|
||||
public protocol SingleComponent: Component {
|
||||
init()
|
||||
}
|
||||
|
||||
public extension Nexus {
|
||||
func single<S>(_ component: S.Type) -> Single<S> where S: SingleComponent {
|
||||
let family = self.family(requires: S.self)
|
||||
precondition(family.count <= 1, "Singleton count of \(S.self) must be 0 or 1: \(family.count)")
|
||||
let entityId: EntityIdentifier
|
||||
if family.isEmpty {
|
||||
entityId = create(entity: "\(S.self)", with: S()).identifier
|
||||
} else {
|
||||
entityId = family.memberIds.first.unsafelyUnwrapped
|
||||
}
|
||||
return Single<S>(nexus: self, traits: family.traits, entityId: entityId)
|
||||
}
|
||||
}
|
||||
|
||||
public struct Single<A>: Equatable where A: SingleComponent {
|
||||
public let nexus: Nexus
|
||||
public let traits: FamilyTraitSet
|
||||
public let entityId: EntityIdentifier
|
||||
}
|
||||
|
||||
public extension Single where A: SingleComponent {
|
||||
@inlinable var component: A {
|
||||
/// Since we guarantee that the component will always be present by managing the complete lifecycle of the entity
|
||||
/// and component assignment we may unsafelyUnwrap here.
|
||||
/// Since components will allways be of reference type (class) we may use unsafeDowncast here for performance reasons.
|
||||
return nexus.get(unsafeComponentFor: entityId)
|
||||
}
|
||||
|
||||
var entity: Entity {
|
||||
return nexus.get(entity: entityId).unsafelyUnwrapped
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ public protocol TypedFamilyProtocol: Equatable, Sequence {
|
|||
var nexus: Nexus { get }
|
||||
|
||||
var count: Int { get }
|
||||
var isEmpty: Bool { get }
|
||||
|
||||
var memberIds: UniformEntityIdentifiers { get }
|
||||
var entities: FamilyEntities { get }
|
||||
|
|
@ -45,6 +46,10 @@ public extension TypedFamilyProtocol {
|
|||
return memberIds.count
|
||||
}
|
||||
|
||||
@inlinable var isEmpty: Bool {
|
||||
return memberIds.isEmpty
|
||||
}
|
||||
|
||||
@inlinable var entities: FamilyEntities {
|
||||
return FamilyEntities(nexus, memberIds)
|
||||
}
|
||||
|
|
@ -58,7 +63,6 @@ public protocol ComponentIteratorProtocol: IteratorProtocol {
|
|||
associatedtype TypedFamily: TypedFamilyProtocol
|
||||
|
||||
var memberIdsIterator: UnorderedSparseSetIterator<EntityIdentifier> { get }
|
||||
var nexus: Nexus { get }
|
||||
|
||||
init(_ nexus: Nexus, _ family: TypedFamily)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
//
|
||||
// TypedSingle.swift
|
||||
// FirebladeECS
|
||||
//
|
||||
// Created by Christian Treffs on 13.02.19.
|
||||
//
|
||||
|
||||
public struct TypedSingle1<A>: Equatable where A: Component {
|
||||
public let nexus: Nexus
|
||||
public let traits: FamilyTraitSet
|
||||
|
||||
public init(_ nexus: Nexus, requires compA: A.Type, excludesAll: [Component.Type]) {
|
||||
self.nexus = nexus
|
||||
traits = FamilyTraitSet(requiresAll: [compA], excludesAll: excludesAll)
|
||||
nexus.onFamilyInit(traits: traits)
|
||||
}
|
||||
|
||||
@inlinable public var entityId: EntityIdentifier? {
|
||||
guard let members = nexus.members(withFamilyTraits: traits) else {
|
||||
return nil
|
||||
}
|
||||
guard let singleMemberId: EntityIdentifier = members.first else {
|
||||
return nil
|
||||
}
|
||||
return singleMemberId
|
||||
}
|
||||
|
||||
@inlinable public var entity: Entity? {
|
||||
guard let entityId = entityId else {
|
||||
return nil
|
||||
}
|
||||
return nexus.get(entity: entityId)
|
||||
}
|
||||
|
||||
@inlinable public var component: A? {
|
||||
return entity?.get(component: A.self)
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +66,10 @@ public class UnorderedSparseSet<Element> {
|
|||
return element
|
||||
}
|
||||
|
||||
public func get(unsafeAt key: Key) -> Element {
|
||||
return find(at: key).unsafelyUnwrapped.1
|
||||
}
|
||||
|
||||
/// Removes the element entry for given key in O(1).
|
||||
///
|
||||
/// - Parameter key: the key
|
||||
|
|
|
|||
|
|
@ -62,3 +62,10 @@ class ExampleSystem {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
final class SingleGameState: SingleComponent {
|
||||
var shouldQuit: Bool = false
|
||||
var playerHealth: Int = 67
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,26 +23,23 @@ class SingleTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testSingleCreation() {
|
||||
let single = nexus.single(requires: Position.self,
|
||||
excludesAll: Name.self)
|
||||
let single = nexus.single(SingleGameState.self)
|
||||
XCTAssertEqual(single.nexus, self.nexus)
|
||||
XCTAssertTrue(single.nexus === self.nexus)
|
||||
XCTAssertEqual(single.traits.requiresAll.count, 1)
|
||||
XCTAssertEqual(single.traits.excludesAll.count, 1)
|
||||
XCTAssertEqual(single.traits.excludesAll.count, 0)
|
||||
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.values.count, 1)
|
||||
|
||||
let traits = FamilyTraitSet(requiresAll: [Position.self], excludesAll: [Name.self])
|
||||
let traits = FamilyTraitSet(requiresAll: [SingleGameState.self], excludesAll: [])
|
||||
XCTAssertEqual(single.traits, traits)
|
||||
}
|
||||
|
||||
func testSingleReuse() {
|
||||
let singleA = nexus.single(requires: Position.self,
|
||||
excludesAll: Name.self)
|
||||
let singleA = nexus.single(SingleGameState.self)
|
||||
|
||||
let singleB = nexus.single(requires: Position.self,
|
||||
excludesAll: Name.self)
|
||||
let singleB = nexus.single(SingleGameState.self)
|
||||
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.keys.count, 1)
|
||||
XCTAssertEqual(nexus.familyMembersByTraits.values.count, 1)
|
||||
|
|
@ -52,16 +49,12 @@ class SingleTests: XCTestCase {
|
|||
|
||||
|
||||
func testSingleEntityAndComponentCreation() {
|
||||
let single = nexus.single(requires: Position.self,
|
||||
excludesAll: Name.self)
|
||||
XCTAssertNil(single.entity)
|
||||
XCTAssertNil(single.component)
|
||||
let pos = Position(x: 1, y: 2)
|
||||
nexus.create(with: pos)
|
||||
let single = nexus.single(SingleGameState.self)
|
||||
let gameState = SingleGameState()
|
||||
XCTAssertNotNil(single.entity)
|
||||
XCTAssertNotNil(single.component)
|
||||
XCTAssertEqual(single.component?.x, pos.x)
|
||||
XCTAssertEqual(single.component?.y, pos.y)
|
||||
XCTAssertEqual(single.component.shouldQuit, gameState.shouldQuit)
|
||||
XCTAssertEqual(single.component.playerHealth, gameState.playerHealth)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue