Merge branch 'release/0.16.0' into master
This commit is contained in:
commit
36e8c79e82
11
README.md
11
README.md
|
|
@ -1,12 +1,11 @@
|
||||||
# Fireblade ECS (Entity-Component System)
|
# Fireblade ECS (Entity-Component System)
|
||||||
[](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://swift.org)
|
[](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
|
||||||
[](#)
|
|
||||||
[](#)
|
|
||||||
[](https://github.com/swiftwasm/swift#swiftwasm)
|
|
||||||
[](https://codecov.io/gh/fireblade-engine/ecs)
|
[](https://codecov.io/gh/fireblade-engine/ecs)
|
||||||
[](https://github.com/fireblade-engine/ecs/wiki)
|
[](https://github.com/fireblade-engine/ecs/wiki)
|
||||||
|
[](https://swiftpackageindex.com/fireblade-engine/ecs)
|
||||||
|
[](https://swiftpackageindex.com/fireblade-engine/ecs)
|
||||||
|
[](https://github.com/swiftwasm/swift#swiftwasm)
|
||||||
|
|
||||||
This is a **dependency free**, **lightweight**, **fast** and **easy to use** [Entity-Component System](https://en.wikipedia.org/wiki/Entity_component_system) implementation in Swift. It is developed and maintained as part of the [Fireblade Game Engine project](https://github.com/fireblade-engine).
|
This is a **dependency free**, **lightweight**, **fast** and **easy to use** [Entity-Component System](https://en.wikipedia.org/wiki/Entity_component_system) implementation in Swift. It is developed and maintained as part of the [Fireblade Game Engine project](https://github.com/fireblade-engine).
|
||||||
|
|
||||||
|
|
@ -36,7 +35,7 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "YourPackageName",
|
name: "YourPackageName",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.15.4")
|
.package(url: "https://github.com/fireblade-engine/ecs.git", from: "0.16.0")
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,18 @@
|
||||||
|
|
||||||
/// Identifies a component by it's meta type
|
/// Identifies a component by it's meta type
|
||||||
public struct ComponentIdentifier {
|
public struct ComponentIdentifier {
|
||||||
@usableFromInline
|
public typealias Identifier = Int
|
||||||
typealias Hash = Int
|
public let id: Identifier
|
||||||
@usableFromInline
|
|
||||||
typealias StableId = UInt64
|
|
||||||
|
|
||||||
@usableFromInline let hash: Hash
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComponentIdentifier {
|
extension ComponentIdentifier {
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
init<C>(_ componentType: C.Type) where C: Component {
|
init<C>(_ componentType: C.Type) where C: Component {
|
||||||
self.hash = Self.makeRuntimeHash(componentType)
|
self.id = Self.makeRuntimeHash(componentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// object identifier hash (only stable during runtime) - arbitrary hash is ok.
|
/// object identifier hash (only stable during runtime) - arbitrary hash is ok.
|
||||||
internal static func makeRuntimeHash<C>(_ componentType: C.Type) -> Hash where C: Component {
|
internal static func makeRuntimeHash<C>(_ componentType: C.Type) -> Identifier where C: Component {
|
||||||
ObjectIdentifier(componentType).hashValue
|
ObjectIdentifier(componentType).hashValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,23 @@
|
||||||
// Created by Christian Treffs on 26.06.20.
|
// Created by Christian Treffs on 26.06.20.
|
||||||
//
|
//
|
||||||
|
|
||||||
/// An entity identifier generator provides new entity
|
/// **Entity Identifier Generator**
|
||||||
/// identifiers on entity creation.
|
///
|
||||||
/// It also allows entity ids to be marked for re-use.
|
/// An entity identifier generator provides new entity identifiers on entity creation.
|
||||||
/// Entity identifiers must be unique.
|
/// It also allows entity ids to be marked as unused (to be re-usable).
|
||||||
|
///
|
||||||
|
/// You should strive to keep entity ids tightly packed around `EntityIdentifier.Identifier.min` since it has an influence on the underlying memory layout.
|
||||||
public protocol EntityIdentifierGenerator {
|
public protocol EntityIdentifierGenerator {
|
||||||
/// Initialize the generator with entity ids already in use.
|
/// Initialize the generator providing entity ids to begin with when creating new entities.
|
||||||
/// - Parameter entityIds: The entity ids already in use. Default should be an empty array.
|
///
|
||||||
init(inUse entityIds: [EntityIdentifier])
|
/// Entity ids provided should be passed to `nextId()` in last out order up until the collection is empty.
|
||||||
|
/// The default is an empty collection.
|
||||||
|
/// - Parameter initialEntityIds: The entity ids to start providing up until the collection is empty (in last out order).
|
||||||
|
init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier
|
||||||
|
|
||||||
/// Provides the next unused entity identifier.
|
/// Provides the next unused entity identifier.
|
||||||
///
|
///
|
||||||
/// The provided entity identifier is at least unique during runtime.
|
/// The provided entity identifier must be unique during runtime.
|
||||||
func nextId() -> EntityIdentifier
|
func nextId() -> EntityIdentifier
|
||||||
|
|
||||||
/// Marks the given entity identifier as free and ready for re-use.
|
/// Marks the given entity identifier as free and ready for re-use.
|
||||||
|
|
@ -27,55 +32,71 @@ public protocol EntityIdentifierGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A default entity identifier generator implementation.
|
/// A default entity identifier generator implementation.
|
||||||
|
public typealias DefaultEntityIdGenerator = LinearIncrementingEntityIdGenerator
|
||||||
|
|
||||||
|
/// **Linear incrementing entity id generator**
|
||||||
///
|
///
|
||||||
/// Provides entity ids starting at `0` incrementing until `UInt32.max`.
|
/// This entity id generator creates linearly incrementing entity ids
|
||||||
public struct DefaultEntityIdGenerator: EntityIdentifierGenerator {
|
/// unless an entity is marked as unused then the marked id is returned next in a FIFO order.
|
||||||
|
///
|
||||||
|
/// Furthermore it respects order of entity ids on initialization, meaning the provided ids on initialization will be provided in order
|
||||||
|
/// until all are in use. After that the free entities start at the lowest available id increasing linearly skipping already in-use entity ids.
|
||||||
|
public struct LinearIncrementingEntityIdGenerator: EntityIdentifierGenerator {
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
final class Storage {
|
final class Storage {
|
||||||
@usableFromInline var stack: [UInt32]
|
@usableFromInline var stack: [EntityIdentifier.Identifier]
|
||||||
@usableFromInline var count: Int { stack.count }
|
@usableFromInline var count: Int { stack.count }
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
init(inUse entityIds: [EntityIdentifier]) {
|
init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier {
|
||||||
stack = entityIds.reversed().map { UInt32($0.id) }
|
let initialInUse: [EntityIdentifier.Identifier] = initialEntityIds.map { $0.id }
|
||||||
|
let maxInUseValue = initialInUse.max() ?? 0
|
||||||
|
let inUseSet = Set(initialInUse) // a set of all eIds in use
|
||||||
|
let allSet = Set(0...maxInUseValue) // all eIds from 0 to including maxInUseValue
|
||||||
|
let freeSet = allSet.subtracting(inUseSet) // all "holes" / unused / free eIds
|
||||||
|
let initialFree = Array(freeSet).sorted().reversed() // order them to provide them linear increasing after all initially used are provided.
|
||||||
|
stack = initialFree + initialInUse
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
init() {
|
||||||
|
stack = [0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
func nextId() -> EntityIdentifier {
|
func nextId() -> EntityIdentifier {
|
||||||
if stack.count == 1 {
|
guard stack.count == 1 else {
|
||||||
defer { stack[0] += 1 }
|
|
||||||
return EntityIdentifier(stack[0])
|
|
||||||
} else {
|
|
||||||
return EntityIdentifier(stack.removeLast())
|
return EntityIdentifier(stack.removeLast())
|
||||||
}
|
}
|
||||||
|
defer { stack[0] += 1 }
|
||||||
|
return EntityIdentifier(stack[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
func markUnused(entityId: EntityIdentifier) {
|
func markUnused(entityId: EntityIdentifier) {
|
||||||
stack.append(UInt32(entityId.id))
|
stack.append(entityId.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@usableFromInline let storage: Storage
|
@usableFromInline let storage: Storage
|
||||||
|
|
||||||
@usableFromInline var count: Int { storage.count }
|
@usableFromInline var count: Int { storage.count }
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
public init<EntityIds>(startProviding initialEntityIds: EntityIds) where EntityIds: BidirectionalCollection, EntityIds.Element == EntityIdentifier {
|
||||||
|
self.storage = Storage(startProviding: initialEntityIds)
|
||||||
|
}
|
||||||
|
|
||||||
@inlinable
|
@inlinable
|
||||||
public init() {
|
public init() {
|
||||||
self.init(inUse: [EntityIdentifier(0)])
|
self.storage = Storage()
|
||||||
}
|
}
|
||||||
|
|
||||||
@inlinable
|
@inline(__always)
|
||||||
public init(inUse entityIds: [EntityIdentifier]) {
|
|
||||||
self.storage = Storage(inUse: entityIds)
|
|
||||||
}
|
|
||||||
|
|
||||||
@inlinable
|
|
||||||
public func nextId() -> EntityIdentifier {
|
public func nextId() -> EntityIdentifier {
|
||||||
storage.nextId()
|
storage.nextId()
|
||||||
}
|
}
|
||||||
|
|
||||||
@inlinable
|
@inline(__always)
|
||||||
public func markUnused(entityId: EntityIdentifier) {
|
public func markUnused(entityId: EntityIdentifier) {
|
||||||
storage.markUnused(entityId: entityId)
|
storage.markUnused(entityId: entityId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ extension Family {
|
||||||
guard let entityId = memberIdsIterator.next() else {
|
guard let entityId = memberIdsIterator.next() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return nexus.get(unsafeEntity: entityId)
|
return Entity(nexus: nexus, id: entityId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ public struct Requires1<Comp1>: FamilyRequirementsManaging where Comp1: Componen
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1) {
|
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1) {
|
||||||
let entity: Entity = nexus.get(unsafeEntity: entityId)
|
let entity = Entity(nexus: nexus, id: entityId)
|
||||||
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
||||||
return (entity, comp1)
|
return (entity, comp1)
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +122,7 @@ public struct Requires2<Comp1, Comp2>: FamilyRequirementsManaging where Comp1: C
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2) {
|
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2) {
|
||||||
let entity: Entity = nexus.get(unsafeEntity: entityId)
|
let entity = Entity(nexus: nexus, id: entityId)
|
||||||
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
||||||
return (entity, comp1, comp2)
|
return (entity, comp1, comp2)
|
||||||
|
|
@ -222,7 +222,7 @@ public struct Requires3<Comp1, Comp2, Comp3>: FamilyRequirementsManaging where C
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3) {
|
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3) {
|
||||||
let entity: Entity = nexus.get(unsafeEntity: entityId)
|
let entity = Entity(nexus: nexus, id: entityId)
|
||||||
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
||||||
|
|
@ -328,7 +328,7 @@ public struct Requires4<Comp1, Comp2, Comp3, Comp4>: FamilyRequirementsManaging
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4) {
|
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4) {
|
||||||
let entity: Entity = nexus.get(unsafeEntity: entityId)
|
let entity = Entity(nexus: nexus, id: entityId)
|
||||||
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
||||||
|
|
@ -440,7 +440,7 @@ public struct Requires5<Comp1, Comp2, Comp3, Comp4, Comp5>: FamilyRequirementsMa
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5) {
|
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5) {
|
||||||
let entity: Entity = nexus.get(unsafeEntity: entityId)
|
let entity = Entity(nexus: nexus, id: entityId)
|
||||||
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
||||||
|
|
@ -558,7 +558,7 @@ public struct Requires6<Comp1, Comp2, Comp3, Comp4, Comp5, Comp6>: FamilyRequire
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6) {
|
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6) {
|
||||||
let entity: Entity = nexus.get(unsafeEntity: entityId)
|
let entity = Entity(nexus: nexus, id: entityId)
|
||||||
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
||||||
|
|
@ -682,7 +682,7 @@ public struct Requires7<Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7>: Family
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7) {
|
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7) {
|
||||||
let entity: Entity = nexus.get(unsafeEntity: entityId)
|
let entity = Entity(nexus: nexus, id: entityId)
|
||||||
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
||||||
|
|
@ -812,7 +812,7 @@ public struct Requires8<Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8>:
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8) {
|
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, Comp1, Comp2, Comp3, Comp4, Comp5, Comp6, Comp7, Comp8) {
|
||||||
let entity: Entity = nexus.get(unsafeEntity: entityId)
|
let entity = Entity(nexus: nexus, id: entityId)
|
||||||
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
let comp1: Comp1 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
let comp2: Comp2 = nexus.get(unsafeComponentFor: entityId)
|
||||||
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
let comp3: Comp3 = nexus.get(unsafeComponentFor: entityId)
|
||||||
|
|
|
||||||
|
|
@ -45,15 +45,6 @@ public func hash(combine seed: Int, _ value: Int) -> Int {
|
||||||
return Int(bitPattern: uSeed)
|
return Int(bitPattern: uSeed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the combined hash value of the elements. This implementation is based on boost::hash_range.
|
|
||||||
/// Is sensitive to the order of the elements.
|
|
||||||
/// - Parameter hashValues: sequence of hash values to combine.
|
|
||||||
/// - Returns: combined hash value.
|
|
||||||
public func hash<S: Sequence>(combine hashValues: S) -> Int where S.Element == Int {
|
|
||||||
/// http://www.boost.org/doc/libs/1_65_1/doc/html/hash/reference.html#boost.hash_range_idp517643120
|
|
||||||
hashValues.reduce(0) { hash(combine: $0, $1) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the combined hash value of the elements. This implementation is based on boost::hash_range.
|
/// Calculates the combined hash value of the elements. This implementation is based on boost::hash_range.
|
||||||
/// Is sensitive to the order of the elements.
|
/// Is sensitive to the order of the elements.
|
||||||
/// - Parameter hashValues: sequence of hash values to combine.
|
/// - Parameter hashValues: sequence of hash values to combine.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ extension Nexus {
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func createEntity() -> Entity {
|
public func createEntity() -> Entity {
|
||||||
let entityId: EntityIdentifier = entityIdGenerator.nextId()
|
let entityId: EntityIdentifier = entityIdGenerator.nextId()
|
||||||
entityStorage.insert(entityId, at: entityId.id)
|
componentIdsByEntity[entityId] = []
|
||||||
delegate?.nexusEvent(EntityCreated(entityId: entityId))
|
delegate?.nexusEvent(EntityCreated(entityId: entityId))
|
||||||
return Entity(nexus: self, id: entityId)
|
return Entity(nexus: self, id: entityId)
|
||||||
}
|
}
|
||||||
|
|
@ -30,22 +30,15 @@ extension Nexus {
|
||||||
|
|
||||||
/// Number of entities in nexus.
|
/// Number of entities in nexus.
|
||||||
public var numEntities: Int {
|
public var numEntities: Int {
|
||||||
entityStorage.count
|
componentIdsByEntity.keys.count
|
||||||
}
|
}
|
||||||
|
|
||||||
public func exists(entity entityId: EntityIdentifier) -> Bool {
|
public func exists(entity entityId: EntityIdentifier) -> Bool {
|
||||||
entityStorage.contains(entityId.id)
|
componentIdsByEntity.keys.contains(entityId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func get(entity entityId: EntityIdentifier) -> Entity? {
|
public func entity(from entityId: EntityIdentifier) -> Entity {
|
||||||
guard let id = entityStorage.get(at: entityId.id) else {
|
Entity(nexus: self, id: entityId)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return Entity(nexus: self, id: id)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func get(unsafeEntity entityId: EntityIdentifier) -> Entity {
|
|
||||||
Entity(nexus: self, id: entityStorage.get(unsafeAt: entityId.id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
|
@ -55,7 +48,7 @@ extension Nexus {
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func destroy(entityId: EntityIdentifier) -> Bool {
|
public func destroy(entityId: EntityIdentifier) -> Bool {
|
||||||
guard entityStorage.remove(at: entityId.id) != nil else {
|
guard componentIdsByEntity.keys.contains(entityId) else {
|
||||||
delegate?.nexusNonFatalError("EntityRemove failure: no entity \(entityId) to remove")
|
delegate?.nexusNonFatalError("EntityRemove failure: no entity \(entityId) to remove")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -64,6 +57,10 @@ extension Nexus {
|
||||||
update(familyMembership: entityId)
|
update(familyMembership: entityId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let index = componentIdsByEntity.index(forKey: entityId) {
|
||||||
|
componentIdsByEntity.remove(at: index)
|
||||||
|
}
|
||||||
|
|
||||||
entityIdGenerator.markUnused(entityId: entityId)
|
entityIdGenerator.markUnused(entityId: entityId)
|
||||||
|
|
||||||
delegate?.nexusEvent(EntityDestroyed(entityId: entityId))
|
delegate?.nexusEvent(EntityDestroyed(entityId: entityId))
|
||||||
|
|
|
||||||
|
|
@ -56,19 +56,7 @@ extension Nexus {
|
||||||
}
|
}
|
||||||
|
|
||||||
func assign(_ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) {
|
func assign(_ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) {
|
||||||
if componentIdsByEntity[entityId] == nil {
|
componentIdsByEntity[entityId]!.insert(componentId)
|
||||||
componentIdsByEntity[entityId] = Set<ComponentIdentifier>(arrayLiteral: componentId)
|
|
||||||
} else {
|
|
||||||
componentIdsByEntity[entityId]?.insert(componentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assign(_ componentIds: Set<ComponentIdentifier>, _ entityId: EntityIdentifier) {
|
|
||||||
if componentIdsByEntity[entityId] == nil {
|
|
||||||
componentIdsByEntity[entityId] = componentIds
|
|
||||||
} else {
|
|
||||||
componentIdsByEntity[entityId]?.formUnion(componentIds)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(familyMembership entityId: EntityIdentifier) {
|
func update(familyMembership entityId: EntityIdentifier) {
|
||||||
|
|
@ -92,7 +80,7 @@ extension Nexus {
|
||||||
|
|
||||||
func update(familyMembership traits: FamilyTraitSet) {
|
func update(familyMembership traits: FamilyTraitSet) {
|
||||||
// FIXME: iterating all entities is costly for many entities
|
// FIXME: iterating all entities is costly for many entities
|
||||||
var iter = entityStorage.makeIterator()
|
var iter = componentIdsByEntity.keys.makeIterator()
|
||||||
while let entityId = iter.next() {
|
while let entityId = iter.next() {
|
||||||
update(membership: traits, for: entityId)
|
update(membership: traits, for: entityId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
public final class Nexus {
|
public final class Nexus {
|
||||||
/// Main entity storage.
|
|
||||||
/// Entities are tightly packed by EntityIdentifier.
|
|
||||||
@usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Identifier>
|
|
||||||
|
|
||||||
/// - Key: ComponentIdentifier aka component type.
|
/// - Key: ComponentIdentifier aka component type.
|
||||||
/// - Value: Array of component instances of same type (uniform).
|
/// - Value: Array of component instances of same type (uniform).
|
||||||
/// New component instances are appended.
|
/// New component instances are appended.
|
||||||
|
|
@ -39,21 +35,18 @@ public final class Nexus {
|
||||||
public final weak var delegate: NexusEventDelegate?
|
public final weak var delegate: NexusEventDelegate?
|
||||||
|
|
||||||
public convenience init() {
|
public convenience init() {
|
||||||
self.init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Identifier>(),
|
self.init(componentsByType: [:],
|
||||||
componentsByType: [:],
|
|
||||||
componentsByEntity: [:],
|
componentsByEntity: [:],
|
||||||
entityIdGenerator: DefaultEntityIdGenerator(),
|
entityIdGenerator: DefaultEntityIdGenerator(),
|
||||||
familyMembersByTraits: [:],
|
familyMembersByTraits: [:],
|
||||||
codingStrategy: DefaultCodingStrategy())
|
codingStrategy: DefaultCodingStrategy())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Identifier>,
|
internal init(componentsByType: [ComponentIdentifier: ManagedContiguousArray<Component>],
|
||||||
componentsByType: [ComponentIdentifier: ManagedContiguousArray<Component>],
|
|
||||||
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
componentsByEntity: [EntityIdentifier: Set<ComponentIdentifier>],
|
||||||
entityIdGenerator: EntityIdentifierGenerator,
|
entityIdGenerator: EntityIdentifierGenerator,
|
||||||
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Identifier>],
|
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Identifier>],
|
||||||
codingStrategy: CodingStrategy) {
|
codingStrategy: CodingStrategy) {
|
||||||
self.entityStorage = entityStorage
|
|
||||||
self.componentsByType = componentsByType
|
self.componentsByType = componentsByType
|
||||||
self.componentIdsByEntity = componentsByEntity
|
self.componentIdsByEntity = componentsByEntity
|
||||||
self.familyMembersByTraits = familyMembersByTraits
|
self.familyMembersByTraits = familyMembersByTraits
|
||||||
|
|
@ -66,8 +59,6 @@ public final class Nexus {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final func clear() {
|
public final func clear() {
|
||||||
entityStorage.forEach { destroy(entityId: $0) }
|
|
||||||
entityStorage.removeAll()
|
|
||||||
componentsByType.removeAll()
|
componentsByType.removeAll()
|
||||||
componentIdsByEntity.removeAll()
|
componentIdsByEntity.removeAll()
|
||||||
familyMembersByTraits.removeAll()
|
familyMembersByTraits.removeAll()
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ extension Single where A: SingleComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var entity: Entity {
|
public var entity: Entity {
|
||||||
nexus.get(entity: entityId).unsafelyUnwrapped
|
Entity(nexus: self.nexus, id: entityId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ public struct Requires{{ idx }}<{{ CompParams }}>: FamilyRequirementsManaging wh
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, {{ CompParams }}) {
|
public static func entityAndComponents(nexus: Nexus, entityId: EntityIdentifier) -> (Entity, {{ CompParams }}) {
|
||||||
let entity: Entity = nexus.get(unsafeEntity: entityId)
|
let entity: Entity = Entity(nexus: nexus, id: entityId)
|
||||||
{% for comp in components %}
|
{% for comp in components %}
|
||||||
let {{ comp|lowercase }}: {{ comp }} = nexus.get(unsafeComponentFor: entityId)
|
let {{ comp|lowercase }}: {{ comp }} = nexus.get(unsafeComponentFor: entityId)
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,7 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
let entry = self.dense[denseIndex]
|
let entry = self.dense[denseIndex]
|
||||||
guard entry.key == key else {
|
assert(entry.key == key, "entry.key and findIndex(at: key) must be equal!")
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry.element
|
return entry.element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
//
|
||||||
|
// EntityIdGenTests.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 21.08.20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FirebladeECS
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class EntityIdGenTests: XCTestCase {
|
||||||
|
var gen: EntityIdentifierGenerator!
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
gen = DefaultEntityIdGenerator()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratorDefaultInit() {
|
||||||
|
XCTAssertEqual(gen.nextId(), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratorWithDefaultEmptyCollection() {
|
||||||
|
gen = DefaultEntityIdGenerator(startProviding: [])
|
||||||
|
XCTAssertEqual(gen.nextId(), 0)
|
||||||
|
XCTAssertEqual(gen.nextId(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLinearIncrement() {
|
||||||
|
for i in 0..<1_000_000 {
|
||||||
|
XCTAssertEqual(gen.nextId(), EntityIdentifier(EntityIdentifier.Identifier(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGenerateWithInitialIds() {
|
||||||
|
let initialIds: [EntityIdentifier] = [2, 4, 11, 3, 0, 304]
|
||||||
|
gen = DefaultEntityIdGenerator(startProviding: initialIds)
|
||||||
|
|
||||||
|
let generatedIds: [EntityIdentifier] = (0..<initialIds.count).map { _ in gen.nextId() }.reversed()
|
||||||
|
XCTAssertEqual(initialIds, generatedIds)
|
||||||
|
XCTAssertEqual(gen.nextId(), 1)
|
||||||
|
XCTAssertEqual(gen.nextId(), 5)
|
||||||
|
XCTAssertEqual(gen.nextId(), 6)
|
||||||
|
XCTAssertEqual(gen.nextId(), 7)
|
||||||
|
XCTAssertEqual(gen.nextId(), 8)
|
||||||
|
XCTAssertEqual(gen.nextId(), 9)
|
||||||
|
XCTAssertEqual(gen.nextId(), 10)
|
||||||
|
XCTAssertEqual(gen.nextId(), 12)
|
||||||
|
|
||||||
|
for i in 13...304 {
|
||||||
|
XCTAssertEqual(gen.nextId(), EntityIdentifier(EntityIdentifier.Identifier(i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(gen.nextId(), 305)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGeneratorMarkUnused() {
|
||||||
|
XCTAssertEqual(gen.nextId(), 0)
|
||||||
|
XCTAssertEqual(gen.nextId(), 1)
|
||||||
|
XCTAssertEqual(gen.nextId(), 2)
|
||||||
|
|
||||||
|
gen.markUnused(entityId: EntityIdentifier(1))
|
||||||
|
|
||||||
|
XCTAssertEqual(gen.nextId(), 1)
|
||||||
|
XCTAssertEqual(gen.nextId(), 3)
|
||||||
|
XCTAssertEqual(gen.nextId(), 4)
|
||||||
|
|
||||||
|
gen.markUnused(entityId: 3)
|
||||||
|
gen.markUnused(entityId: 0)
|
||||||
|
|
||||||
|
XCTAssertEqual(gen.nextId(), 0)
|
||||||
|
XCTAssertEqual(gen.nextId(), 3)
|
||||||
|
|
||||||
|
gen.markUnused(entityId: 3)
|
||||||
|
|
||||||
|
XCTAssertEqual(gen.nextId(), 3)
|
||||||
|
XCTAssertEqual(gen.nextId(), 5)
|
||||||
|
XCTAssertEqual(gen.nextId(), 6)
|
||||||
|
XCTAssertEqual(gen.nextId(), 7)
|
||||||
|
XCTAssertEqual(gen.nextId(), 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,8 @@ class FamilyTests: XCTestCase {
|
||||||
XCTAssertEqual(nexus.numFamilies, 1)
|
XCTAssertEqual(nexus.numFamilies, 1)
|
||||||
XCTAssertEqual(nexus.numComponents, 0)
|
XCTAssertEqual(nexus.numComponents, 0)
|
||||||
XCTAssertEqual(nexus.numEntities, 0)
|
XCTAssertEqual(nexus.numEntities, 0)
|
||||||
|
XCTAssertFalse(family.traits.description.isEmpty)
|
||||||
|
XCTAssertFalse(family.traits.debugDescription.isEmpty)
|
||||||
|
|
||||||
let traits = FamilyTraitSet(requiresAll: [Position.self], excludesAll: [Name.self])
|
let traits = FamilyTraitSet(requiresAll: [Position.self], excludesAll: [Name.self])
|
||||||
XCTAssertEqual(family.traits, traits)
|
XCTAssertEqual(family.traits, traits)
|
||||||
|
|
|
||||||
|
|
@ -33,22 +33,23 @@ class NexusTests: XCTestCase {
|
||||||
|
|
||||||
XCTAssert(e1.identifier.id == 1)
|
XCTAssert(e1.identifier.id == 1)
|
||||||
XCTAssert(nexus.numEntities == 2)
|
XCTAssert(nexus.numEntities == 2)
|
||||||
|
XCTAssertFalse(nexus.debugDescription.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEntityDestroy() {
|
func testEntityDestroy() {
|
||||||
testEntityCreate()
|
testEntityCreate()
|
||||||
XCTAssertEqual(nexus.numEntities, 2)
|
XCTAssertEqual(nexus.numEntities, 2)
|
||||||
|
|
||||||
let e1: Entity = nexus.get(entity: EntityIdentifier(1))!
|
let e1 = nexus.entity(from: EntityIdentifier(1))
|
||||||
|
XCTAssertTrue(nexus.exists(entity: EntityIdentifier(1)))
|
||||||
XCTAssertEqual(e1.identifier.id, 1)
|
XCTAssertEqual(e1.identifier.id, 1)
|
||||||
|
|
||||||
XCTAssertTrue(nexus.destroy(entity: e1))
|
XCTAssertTrue(nexus.destroy(entity: e1))
|
||||||
XCTAssertFalse(nexus.destroy(entity: e1))
|
XCTAssertFalse(nexus.destroy(entity: e1))
|
||||||
|
|
||||||
XCTAssertEqual(nexus.numEntities, 1)
|
XCTAssertFalse(nexus.exists(entity: EntityIdentifier(1)))
|
||||||
|
|
||||||
let e1Again: Entity? = nexus.get(entity: EntityIdentifier(1))
|
XCTAssertEqual(nexus.numEntities, 1)
|
||||||
XCTAssertNil(e1Again)
|
|
||||||
|
|
||||||
XCTAssertEqual(nexus.numEntities, 1)
|
XCTAssertEqual(nexus.numEntities, 1)
|
||||||
|
|
||||||
|
|
@ -78,7 +79,7 @@ class NexusTests: XCTestCase {
|
||||||
func testComponentDeletion() {
|
func testComponentDeletion() {
|
||||||
let identifier: EntityIdentifier = nexus.createEntity().identifier
|
let identifier: EntityIdentifier = nexus.createEntity().identifier
|
||||||
|
|
||||||
let e0 = nexus.get(entity: identifier)!
|
let e0 = nexus.entity(from: identifier)
|
||||||
|
|
||||||
XCTAssert(e0.numComponents == 0)
|
XCTAssert(e0.numComponents == 0)
|
||||||
e0.remove(Position.self)
|
e0.remove(Position.self)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,19 @@ extension EntityCreationTests {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension EntityIdGenTests {
|
||||||
|
// DO NOT MODIFY: This is autogenerated, use:
|
||||||
|
// `swift test --generate-linuxmain`
|
||||||
|
// to regenerate.
|
||||||
|
static let __allTests__EntityIdGenTests = [
|
||||||
|
("testGenerateWithInitialIds", testGenerateWithInitialIds),
|
||||||
|
("testGeneratorDefaultInit", testGeneratorDefaultInit),
|
||||||
|
("testGeneratorMarkUnused", testGeneratorMarkUnused),
|
||||||
|
("testGeneratorWithDefaultEmptyCollection", testGeneratorWithDefaultEmptyCollection),
|
||||||
|
("testLinearIncrement", testLinearIncrement)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
extension EntityTests {
|
extension EntityTests {
|
||||||
// DO NOT MODIFY: This is autogenerated, use:
|
// DO NOT MODIFY: This is autogenerated, use:
|
||||||
// `swift test --generate-linuxmain`
|
// `swift test --generate-linuxmain`
|
||||||
|
|
@ -292,6 +305,7 @@ public func __allTests() -> [XCTestCaseEntry] {
|
||||||
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
|
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
|
||||||
testCase(ComponentTests.__allTests__ComponentTests),
|
testCase(ComponentTests.__allTests__ComponentTests),
|
||||||
testCase(EntityCreationTests.__allTests__EntityCreationTests),
|
testCase(EntityCreationTests.__allTests__EntityCreationTests),
|
||||||
|
testCase(EntityIdGenTests.__allTests__EntityIdGenTests),
|
||||||
testCase(EntityTests.__allTests__EntityTests),
|
testCase(EntityTests.__allTests__EntityTests),
|
||||||
testCase(Family1Tests.__allTests__Family1Tests),
|
testCase(Family1Tests.__allTests__Family1Tests),
|
||||||
testCase(Family2Tests.__allTests__Family2Tests),
|
testCase(Family2Tests.__allTests__Family2Tests),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue