Merge branch 'release/0.16.0' into master

This commit is contained in:
Christian Treffs 2020-08-22 09:34:59 +02:00
commit 36e8c79e82
No known key found for this signature in database
GPG Key ID: 49A4B4B460BE3ED4
16 changed files with 188 additions and 109 deletions

View File

@ -1,12 +1,11 @@
# Fireblade ECS (Entity-Component System) # Fireblade ECS (Entity-Component System)
[![github CI](https://github.com/fireblade-engine/ecs/workflows/CI/badge.svg)](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
[![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
[![swift version](https://img.shields.io/badge/swift-5.1+-brightgreen.svg)](https://swift.org) [![github CI](https://github.com/fireblade-engine/ecs/workflows/CI/badge.svg)](https://github.com/fireblade-engine/ecs/actions?query=workflow%3ACI)
[![platforms](https://img.shields.io/badge/platforms-%20macOS%20|%20iOS%20|%20tvOS%20|%20watchOS-brightgreen.svg)](#)
[![platforms](https://img.shields.io/badge/platforms-linux-brightgreen.svg)](#)
[![platforms](https://img.shields.io/badge/platforms-WebAssembly-brightgreen.svg)](https://github.com/swiftwasm/swift#swiftwasm)
[![codecov](https://codecov.io/gh/fireblade-engine/ecs/branch/master/graph/badge.svg)](https://codecov.io/gh/fireblade-engine/ecs) [![codecov](https://codecov.io/gh/fireblade-engine/ecs/branch/master/graph/badge.svg)](https://codecov.io/gh/fireblade-engine/ecs)
[![documentation](https://github.com/fireblade-engine/ecs/workflows/Documentation/badge.svg)](https://github.com/fireblade-engine/ecs/wiki) [![documentation](https://github.com/fireblade-engine/ecs/workflows/Documentation/badge.svg)](https://github.com/fireblade-engine/ecs/wiki)
[![spi-swift-versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffireblade-engine%2Fecs%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/fireblade-engine/ecs)
[![spi-swift-platforms](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Ffireblade-engine%2Fecs%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/fireblade-engine/ecs)
[![platform-webassembly](https://img.shields.io/badge/Platform-WebAssembly-blue.svg)](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(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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