Introduce unsafe API for performance and refine Single

This commit is contained in:
Christian Treffs 2019-02-14 10:43:03 +01:00
parent ef7859a021
commit c1a68300de
10 changed files with 88 additions and 75 deletions

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,3 +62,10 @@ class ExampleSystem {
}
}
final class SingleGameState: SingleComponent {
var shouldQuit: Bool = false
var playerHealth: Int = 67
}

View File

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