diff --git a/Sources/FirebladeECS/ManagedContiguousArray.swift b/Sources/FirebladeECS/ManagedContiguousArray.swift index 3344c62..1165d89 100644 --- a/Sources/FirebladeECS/ManagedContiguousArray.swift +++ b/Sources/FirebladeECS/ManagedContiguousArray.swift @@ -58,6 +58,10 @@ public class ManagedContiguousArray: 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 { diff --git a/Sources/FirebladeECS/Nexus+Component.swift b/Sources/FirebladeECS/Nexus+Component.swift index 069c9fd..5ed4bd0 100644 --- a/Sources/FirebladeECS/Nexus+Component.swift +++ b/Sources/FirebladeECS/Nexus+Component.swift @@ -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(for entityId: EntityIdentifier) -> C? where C: Component { let componentId: ComponentIdentifier = C.identifier return get(componentId: componentId, entityIdx: entityId.index) } + final func get(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] } diff --git a/Sources/FirebladeECS/Nexus+Entity.swift b/Sources/FirebladeECS/Nexus+Entity.swift index c56fb18..f1e9ee0 100644 --- a/Sources/FirebladeECS/Nexus+Entity.swift +++ b/Sources/FirebladeECS/Nexus+Entity.swift @@ -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 diff --git a/Sources/FirebladeECS/Nexus+TypedSingle.swift b/Sources/FirebladeECS/Nexus+TypedSingle.swift deleted file mode 100644 index 7ddd63d..0000000 --- a/Sources/FirebladeECS/Nexus+TypedSingle.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Nexus+TypedSingle.swift -// FirebladeECS -// -// Created by Christian Treffs on 13.02.19. -// - -public extension Nexus { - func single( - requires componentA: A.Type, - excludesAll excludedComponents: Component.Type... - ) -> TypedSingle1 where A: Component { - return TypedSingle1( - self, - requires: componentA, - excludesAll: excludedComponents - ) - } -} diff --git a/Sources/FirebladeECS/Single.swift b/Sources/FirebladeECS/Single.swift new file mode 100644 index 0000000..908a81b --- /dev/null +++ b/Sources/FirebladeECS/Single.swift @@ -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(_ component: S.Type) -> Single 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(nexus: self, traits: family.traits, entityId: entityId) + } +} + +public struct Single: 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 + } +} diff --git a/Sources/FirebladeECS/TypedFamily.swift b/Sources/FirebladeECS/TypedFamily.swift index f299db8..847b94f 100644 --- a/Sources/FirebladeECS/TypedFamily.swift +++ b/Sources/FirebladeECS/TypedFamily.swift @@ -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 { get } - var nexus: Nexus { get } init(_ nexus: Nexus, _ family: TypedFamily) } diff --git a/Sources/FirebladeECS/TypedSingle.swift b/Sources/FirebladeECS/TypedSingle.swift deleted file mode 100644 index af8b795..0000000 --- a/Sources/FirebladeECS/TypedSingle.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// TypedSingle.swift -// FirebladeECS -// -// Created by Christian Treffs on 13.02.19. -// - -public struct TypedSingle1: 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) - } -} diff --git a/Sources/FirebladeECS/UnorderedSparseSet.swift b/Sources/FirebladeECS/UnorderedSparseSet.swift index b5337a9..489d797 100644 --- a/Sources/FirebladeECS/UnorderedSparseSet.swift +++ b/Sources/FirebladeECS/UnorderedSparseSet.swift @@ -66,6 +66,10 @@ public class UnorderedSparseSet { 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 diff --git a/Tests/FirebladeECSTests/Base.swift b/Tests/FirebladeECSTests/Base.swift index 5a413d1..63db0ff 100644 --- a/Tests/FirebladeECSTests/Base.swift +++ b/Tests/FirebladeECSTests/Base.swift @@ -62,3 +62,10 @@ class ExampleSystem { } } + + +final class SingleGameState: SingleComponent { + var shouldQuit: Bool = false + var playerHealth: Int = 67 + +} diff --git a/Tests/FirebladeECSTests/SingleTests.swift b/Tests/FirebladeECSTests/SingleTests.swift index 4c52f23..d36d654 100644 --- a/Tests/FirebladeECSTests/SingleTests.swift +++ b/Tests/FirebladeECSTests/SingleTests.swift @@ -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) } }