Merge branch 'release/0.14.0' into master
This commit is contained in:
commit
c81558b2d9
35
README.md
35
README.md
|
|
@ -66,24 +66,41 @@ let nexus = Nexus()
|
||||||
then create entities by letting the `Nexus` generate them.
|
then create entities by letting the `Nexus` generate them.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let myEntity = nexus.createEntity()
|
// an entity without components
|
||||||
|
let newEntity = nexus.createEntity()
|
||||||
```
|
```
|
||||||
|
|
||||||
You can define `Components` like this
|
To define components conform your class to the `Component` protocol
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
class Movement: Component {
|
final class Position: Component {
|
||||||
var position: (x: Double, y: Double) = (0.0, 1.0)
|
var x: Int = 0
|
||||||
var velocity: Double = 0.1
|
var y: Int = 0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
and assign instances of them to an `Entity` with
|
and assign instances of it to an `Entity` with
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let movement = Movement()
|
let position = Position(x: 1, y: 2)
|
||||||
myEntity.assign(movement)
|
entity.assign(position)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can be more efficient by assigning components while creating an entity.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// an entity with two components assigned.
|
||||||
|
nexus.createEntity {
|
||||||
|
Position(x: 1, y: 2)
|
||||||
|
Color(.red)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bulk create entities with multiple components assigned.
|
||||||
|
nexus.createEntities(count: 100) { _ in
|
||||||
|
Position()
|
||||||
|
Color()
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
### 👪 Families
|
### 👪 Families
|
||||||
|
|
||||||
This ECS uses a grouping approach for entities with the same component types to optimize cache locality and ease up access to them.
|
This ECS uses a grouping approach for entities with the same component types to optimize cache locality and ease up access to them.
|
||||||
|
|
@ -186,7 +203,7 @@ class GameLogicSystem {
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 👫 Relatives
|
### 👫 Relatives (deprecated)
|
||||||
|
|
||||||
This ECS implementation provides an integrated way of creating a [directed acyclic graph (DAG)](https://en.wikipedia.org/wiki/Directed_acyclic_graph) hierarchy of entities by forming parent-child relationships. Entities can become children of a parent entity. In family terms they become **relatives**. Families provide iteration over these relationships.
|
This ECS implementation provides an integrated way of creating a [directed acyclic graph (DAG)](https://en.wikipedia.org/wiki/Directed_acyclic_graph) hierarchy of entities by forming parent-child relationships. Entities can become children of a parent entity. In family terms they become **relatives**. Families provide iteration over these relationships.
|
||||||
The entity hierarchy implementation does not use an additional component therefore keeping the hierarchy intact over different component-families.
|
The entity hierarchy implementation does not use an additional component therefore keeping the hierarchy intact over different component-families.
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ public struct Entity {
|
||||||
/// Add an entity as child.
|
/// Add an entity as child.
|
||||||
/// - Parameter entity: The child entity.
|
/// - Parameter entity: The child entity.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
public func addChild(_ entity: Entity) -> Bool {
|
public func addChild(_ entity: Entity) -> Bool {
|
||||||
nexus.addChild(entity, to: self)
|
nexus.addChild(entity, to: self)
|
||||||
}
|
}
|
||||||
|
|
@ -112,16 +113,19 @@ public struct Entity {
|
||||||
/// Remove entity as child.
|
/// Remove entity as child.
|
||||||
/// - Parameter entity: The child entity.
|
/// - Parameter entity: The child entity.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
public func removeChild(_ entity: Entity) -> Bool {
|
public func removeChild(_ entity: Entity) -> Bool {
|
||||||
nexus.removeChild(entity, from: self)
|
nexus.removeChild(entity, from: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes all children from this entity.
|
/// Removes all children from this entity.
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
public func removeAllChildren() {
|
public func removeAllChildren() {
|
||||||
nexus.removeAllChildren(from: self)
|
nexus.removeAllChildren(from: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the number of children for this entity.
|
/// Returns the number of children for this entity.
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
public var numChildren: Int {
|
public var numChildren: Int {
|
||||||
nexus.numChildren(for: self)
|
nexus.numChildren(for: self)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,14 @@
|
||||||
public struct EntityIdentifier {
|
public struct EntityIdentifier {
|
||||||
static let invalid = EntityIdentifier(.max)
|
static let invalid = EntityIdentifier(.max)
|
||||||
|
|
||||||
public typealias Id = Int
|
public typealias Idx = Int
|
||||||
|
|
||||||
/// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid.
|
/// provides 4294967295 unique identifiers since it's constrained to UInt32 - invalid.
|
||||||
@usableFromInline let id: Id
|
@usableFromInline let id: Idx
|
||||||
|
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
init(_ uint32: UInt32) {
|
init(_ uint32: UInt32) {
|
||||||
self.id = Id(uint32)
|
self.id = Idx(uint32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ private struct FamilyMemberContainer<R> where R: FamilyRequirementsManaging {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CodingUserInfoKey {
|
extension CodingUserInfoKey {
|
||||||
fileprivate static let nexusCodingStrategy = CodingUserInfoKey(rawValue: "nexusCodingStrategy")!
|
static let nexusCodingStrategy = CodingUserInfoKey(rawValue: "nexusCodingStrategy").unsafelyUnwrapped
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - encoding
|
// MARK: - encoding
|
||||||
|
|
@ -43,7 +43,7 @@ extension Family where R: FamilyEncoding {
|
||||||
/// - Returns: The encoded data.
|
/// - Returns: The encoded data.
|
||||||
public func encodeMembers<Encoder>(using encoder: inout Encoder) throws -> Encoder.Output where Encoder: TopLevelEncoder {
|
public func encodeMembers<Encoder>(using encoder: inout Encoder) throws -> Encoder.Output where Encoder: TopLevelEncoder {
|
||||||
encoder.userInfo[.nexusCodingStrategy] = nexus.codingStrategy
|
encoder.userInfo[.nexusCodingStrategy] = nexus.codingStrategy
|
||||||
let components: [R.Components] = self.map { $0 }
|
let components = [R.Components](self)
|
||||||
let container = FamilyMemberContainer<R>(components: components)
|
let container = FamilyMemberContainer<R>(components: components)
|
||||||
return try encoder.encode(container)
|
return try encoder.encode(container)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public struct Family<R> where R: FamilyRequirementsManaging {
|
||||||
nexus.onFamilyInit(traits: traits)
|
nexus.onFamilyInit(traits: traits)
|
||||||
}
|
}
|
||||||
|
|
||||||
@inlinable public var memberIds: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id> {
|
@inlinable public var memberIds: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx> {
|
||||||
nexus.members(withFamilyTraits: traits)
|
nexus.members(withFamilyTraits: traits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ extension Family: LazySequenceProtocol { }
|
||||||
// MARK: - components iterator
|
// MARK: - components iterator
|
||||||
extension Family {
|
extension Family {
|
||||||
public struct ComponentsIterator: IteratorProtocol {
|
public struct ComponentsIterator: IteratorProtocol {
|
||||||
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>.ElementIterator
|
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>.ElementIterator
|
||||||
@usableFromInline unowned let nexus: Nexus
|
@usableFromInline unowned let nexus: Nexus
|
||||||
|
|
||||||
public init(family: Family<R>) {
|
public init(family: Family<R>) {
|
||||||
|
|
@ -85,7 +85,7 @@ extension Family {
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct EntityIterator: IteratorProtocol {
|
public struct EntityIterator: IteratorProtocol {
|
||||||
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>.ElementIterator
|
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>.ElementIterator
|
||||||
@usableFromInline unowned let nexus: Nexus
|
@usableFromInline unowned let nexus: Nexus
|
||||||
|
|
||||||
public init(family: Family<R>) {
|
public init(family: Family<R>) {
|
||||||
|
|
@ -111,7 +111,7 @@ extension Family {
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct EntityComponentIterator: IteratorProtocol {
|
public struct EntityComponentIterator: IteratorProtocol {
|
||||||
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>.ElementIterator
|
@usableFromInline var memberIdsIterator: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>.ElementIterator
|
||||||
@usableFromInline unowned let nexus: Nexus
|
@usableFromInline unowned let nexus: Nexus
|
||||||
|
|
||||||
public init(family: Family<R>) {
|
public init(family: Family<R>) {
|
||||||
|
|
|
||||||
|
|
@ -22,31 +22,9 @@ extension Nexus {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final func assign(component: Component, to entity: Entity) {
|
public final func assign(component: Component, to entity: Entity) {
|
||||||
let componentId: ComponentIdentifier = component.identifier
|
|
||||||
let entityId: EntityIdentifier = entity.identifier
|
let entityId: EntityIdentifier = entity.identifier
|
||||||
|
assign(component: component, entityId: entityId)
|
||||||
// test if component is already assigned
|
delegate?.nexusEvent(ComponentAdded(component: component.identifier, toEntity: entity.identifier))
|
||||||
guard !has(componentId: componentId, entityId: entityId) else {
|
|
||||||
delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)")
|
|
||||||
assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// add component instances to uniform component stores
|
|
||||||
if componentsByType[componentId] == nil {
|
|
||||||
componentsByType[componentId] = ManagedContiguousArray<Component>()
|
|
||||||
}
|
|
||||||
componentsByType[componentId]?.insert(component, at: entityId.id)
|
|
||||||
|
|
||||||
// assigns the component id to the entity id
|
|
||||||
if componentIdsByEntity[entityId] == nil {
|
|
||||||
componentIdsByEntity[entityId] = Set<ComponentIdentifier>()
|
|
||||||
}
|
|
||||||
componentIdsByEntity[entityId]?.insert(componentId) //, at: componentId.hashValue)
|
|
||||||
|
|
||||||
update(familyMembership: entityId)
|
|
||||||
|
|
||||||
delegate?.nexusEvent(ComponentAdded(component: componentId, toEntity: entity.identifier))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final func assign<C>(component: C, to entity: Entity) where C: Component {
|
public final func assign<C>(component: C, to entity: Entity) where C: Component {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
//
|
||||||
|
// Nexus+ComponentsBuilder.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 30.07.20.
|
||||||
|
//
|
||||||
|
|
||||||
|
@_functionBuilder
|
||||||
|
public enum ComponentsBuilderPreview { }
|
||||||
|
public typealias ComponentsBuilder = ComponentsBuilderPreview
|
||||||
|
|
||||||
|
extension ComponentsBuilder {
|
||||||
|
public static func buildBlock(_ components: Component...) -> [Component] {
|
||||||
|
components
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Context {
|
||||||
|
/// The index of the newly created entity.
|
||||||
|
///
|
||||||
|
/// This is **NOT** equal to the entity identifier.
|
||||||
|
public let index: Int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Nexus {
|
||||||
|
/// Create an entity assigning one component.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```
|
||||||
|
/// let newEntity = nexus.createEntity {
|
||||||
|
/// Position(x: 1, y: 2)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// - Parameter builder: The component builder.
|
||||||
|
/// - Returns: The newly created entity with the provided component assigned.
|
||||||
|
@discardableResult
|
||||||
|
public func createEntity(@ComponentsBuilder using builder: () -> Component) -> Entity {
|
||||||
|
self.createEntity(with: builder())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an entity assigning multiple components.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```
|
||||||
|
/// let newEntity = nexus.createEntity {
|
||||||
|
/// Position(x: 1, y: 2)
|
||||||
|
/// Name(name: "Some name")
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// - Parameter builder: The component builder.
|
||||||
|
/// - Returns: The newly created entity with the provided components assigned.
|
||||||
|
@discardableResult
|
||||||
|
public func createEntity(@ComponentsBuilder using builder: () -> [Component]) -> Entity {
|
||||||
|
self.createEntity(with: builder())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create multiple entities assigning one component each.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```
|
||||||
|
/// let newEntities = nexus.createEntities(count: 100) { ctx in
|
||||||
|
/// Velocity(a: Float(ctx.index))
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// - Parameters:
|
||||||
|
/// - count: The count of entities to create.
|
||||||
|
/// - builder: The component builder providing context.
|
||||||
|
/// - Returns: The newly created entities with the provided component assigned.
|
||||||
|
@discardableResult
|
||||||
|
public func createEntities(count: Int, @ComponentsBuilder using builder: (ComponentsBuilder.Context) -> Component) -> [Entity] {
|
||||||
|
(0..<count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create multiple entities assigning multiple components each.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```
|
||||||
|
/// let newEntities = nexus.createEntities(count: 100) { ctx in
|
||||||
|
/// Position(x: ctx.index, y: ctx.index)
|
||||||
|
/// Name(name: "\(ctx.index)")
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// - Parameters:
|
||||||
|
/// - count: The count of entities to create.
|
||||||
|
/// - builder: The component builder providing context.
|
||||||
|
/// - Returns: The newly created entities with the provided components assigned.
|
||||||
|
@discardableResult
|
||||||
|
public func createEntities(count: Int, @ComponentsBuilder using builder: (ComponentsBuilder.Context) -> [Component]) -> [Entity] {
|
||||||
|
(0..<count).map { self.createEntity(with: builder(ComponentsBuilder.Context(index: $0))) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,10 +17,17 @@ extension Nexus {
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func createEntity(with components: Component...) -> Entity {
|
public func createEntity(with components: Component...) -> Entity {
|
||||||
let newEntity = createEntity()
|
let newEntity = createEntity()
|
||||||
components.forEach { newEntity.assign($0) }
|
assign(components: components, to: newEntity.identifier)
|
||||||
return newEntity
|
return newEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public func createEntity<C>(with components: C) -> Entity where C: Collection, C.Element == Component {
|
||||||
|
let entity = self.createEntity()
|
||||||
|
assign(components: components, to: entity.identifier)
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
/// Number of entities in nexus.
|
/// Number of entities in nexus.
|
||||||
public var numEntities: Int {
|
public var numEntities: Int {
|
||||||
entityStorage.count
|
entityStorage.count
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ extension Nexus {
|
||||||
return traits.isMatch(components: componentIds)
|
return traits.isMatch(components: componentIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func members(withFamilyTraits traits: FamilyTraitSet) -> UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id> {
|
public func members(withFamilyTraits traits: FamilyTraitSet) -> UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx> {
|
||||||
familyMembersByTraits[traits] ?? UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>()
|
familyMembersByTraits[traits] ?? UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func isMember(_ entity: Entity, in family: FamilyTraitSet) -> Bool {
|
public func isMember(_ entity: Entity, in family: FamilyTraitSet) -> Bool {
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
//
|
|
||||||
// Nexus+FamilyUpdate.swift
|
|
||||||
// FirebladeECS
|
|
||||||
//
|
|
||||||
// Created by Christian Treffs on 14.02.19.
|
|
||||||
//
|
|
||||||
|
|
||||||
extension Nexus {
|
|
||||||
/// will be called on family init
|
|
||||||
final func onFamilyInit(traits: FamilyTraitSet) {
|
|
||||||
guard familyMembersByTraits[traits] == nil else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
familyMembersByTraits[traits] = UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>()
|
|
||||||
update(familyMembership: traits)
|
|
||||||
}
|
|
||||||
|
|
||||||
final func update(familyMembership traits: FamilyTraitSet) {
|
|
||||||
// FIXME: iterating all entities is costly for many entities
|
|
||||||
var iter = entityStorage.makeIterator()
|
|
||||||
while let entityId = iter.next() {
|
|
||||||
update(membership: traits, for: entityId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final func update(familyMembership entityId: EntityIdentifier) {
|
|
||||||
// FIXME: iterating all families is costly for many families
|
|
||||||
var iter = familyMembersByTraits.keys.makeIterator()
|
|
||||||
while let traits = iter.next() {
|
|
||||||
update(membership: traits, for: entityId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final func update(membership traits: FamilyTraitSet, for entityId: EntityIdentifier) {
|
|
||||||
guard let componentIds = componentIdsByEntity[entityId] else {
|
|
||||||
// no components - so skip
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let isMember: Bool = self.isMember(entity: entityId, inFamilyWithTraits: traits)
|
|
||||||
if !exists(entity: entityId) && isMember {
|
|
||||||
remove(entityWithId: entityId, fromFamilyWithTraits: traits)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let isMatch: Bool = traits.isMatch(components: componentIds)
|
|
||||||
|
|
||||||
switch (isMatch, isMember) {
|
|
||||||
case (true, false):
|
|
||||||
add(entityWithId: entityId, toFamilyWithTraits: traits)
|
|
||||||
delegate?.nexusEvent(FamilyMemberAdded(member: entityId, toFamily: traits))
|
|
||||||
|
|
||||||
case (false, true):
|
|
||||||
remove(entityWithId: entityId, fromFamilyWithTraits: traits)
|
|
||||||
delegate?.nexusEvent(FamilyMemberRemoved(member: entityId, from: traits))
|
|
||||||
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) {
|
|
||||||
familyMembersByTraits[traits]!.insert(entityId, at: entityId.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
final func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) {
|
|
||||||
familyMembersByTraits[traits]!.remove(at: entityId.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
//
|
||||||
|
// Nexus+Internal.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 14.02.19.
|
||||||
|
//
|
||||||
|
|
||||||
|
extension Nexus {
|
||||||
|
func assign<C>(components: C, to entityId: EntityIdentifier) where C: Collection, C.Element == Component {
|
||||||
|
var iter = components.makeIterator()
|
||||||
|
while let component = iter.next() {
|
||||||
|
let componentId = component.identifier
|
||||||
|
// test if component is already assigned
|
||||||
|
guard !has(componentId: componentId, entityId: entityId) else {
|
||||||
|
delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)")
|
||||||
|
assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// add component instances to uniform component stores
|
||||||
|
insertComponentInstance(component, componentId, entityId)
|
||||||
|
|
||||||
|
// assigns the component id to the entity id
|
||||||
|
assign(componentId, entityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update entity membership
|
||||||
|
update(familyMembership: entityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assign(component: Component, entityId: EntityIdentifier) {
|
||||||
|
let componentId = component.identifier
|
||||||
|
|
||||||
|
// test if component is already assigned
|
||||||
|
guard !has(componentId: componentId, entityId: entityId) else {
|
||||||
|
delegate?.nexusNonFatalError("ComponentAdd collision: \(entityId) already has a component \(component)")
|
||||||
|
assertionFailure("ComponentAdd collision: \(entityId) already has a component \(component)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// add component instances to uniform component stores
|
||||||
|
insertComponentInstance(component, componentId, entityId)
|
||||||
|
|
||||||
|
// assigns the component id to the entity id
|
||||||
|
assign(componentId, entityId)
|
||||||
|
|
||||||
|
// Update entity membership
|
||||||
|
update(familyMembership: entityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertComponentInstance(_ component: Component, _ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) {
|
||||||
|
if componentsByType[componentId] == nil {
|
||||||
|
componentsByType[componentId] = ManagedContiguousArray<Component>()
|
||||||
|
}
|
||||||
|
componentsByType[componentId]?.insert(component, at: entityId.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assign(_ componentId: ComponentIdentifier, _ entityId: EntityIdentifier) {
|
||||||
|
if componentIdsByEntity[entityId] == nil {
|
||||||
|
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) {
|
||||||
|
// FIXME: iterating all families is costly for many families
|
||||||
|
// FIXME: this could be parallelized
|
||||||
|
var iter = familyMembersByTraits.keys.makeIterator()
|
||||||
|
while let traits = iter.next() {
|
||||||
|
update(membership: traits, for: entityId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// will be called on family init
|
||||||
|
func onFamilyInit(traits: FamilyTraitSet) {
|
||||||
|
guard familyMembersByTraits[traits] == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
familyMembersByTraits[traits] = UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>()
|
||||||
|
update(familyMembership: traits)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(familyMembership traits: FamilyTraitSet) {
|
||||||
|
// FIXME: iterating all entities is costly for many entities
|
||||||
|
var iter = entityStorage.makeIterator()
|
||||||
|
while let entityId = iter.next() {
|
||||||
|
update(membership: traits, for: entityId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(membership traits: FamilyTraitSet, for entityId: EntityIdentifier) {
|
||||||
|
guard let componentIds = componentIdsByEntity[entityId] else {
|
||||||
|
// no components - so skip
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMember: Bool = self.isMember(entity: entityId, inFamilyWithTraits: traits)
|
||||||
|
if !exists(entity: entityId) && isMember {
|
||||||
|
remove(entityWithId: entityId, fromFamilyWithTraits: traits)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let isMatch: Bool = traits.isMatch(components: componentIds)
|
||||||
|
|
||||||
|
switch (isMatch, isMember) {
|
||||||
|
case (true, false):
|
||||||
|
add(entityWithId: entityId, toFamilyWithTraits: traits)
|
||||||
|
delegate?.nexusEvent(FamilyMemberAdded(member: entityId, toFamily: traits))
|
||||||
|
|
||||||
|
case (false, true):
|
||||||
|
remove(entityWithId: entityId, fromFamilyWithTraits: traits)
|
||||||
|
delegate?.nexusEvent(FamilyMemberRemoved(member: entityId, from: traits))
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(entityWithId entityId: EntityIdentifier, toFamilyWithTraits traits: FamilyTraitSet) {
|
||||||
|
familyMembersByTraits[traits].unsafelyUnwrapped.insert(entityId, at: entityId.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(entityWithId entityId: EntityIdentifier, fromFamilyWithTraits traits: FamilyTraitSet) {
|
||||||
|
familyMembersByTraits[traits].unsafelyUnwrapped.remove(at: entityId.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
extension Nexus {
|
extension Nexus {
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
public final func addChild(_ child: Entity, to parent: Entity) -> Bool {
|
public final func addChild(_ child: Entity, to parent: Entity) -> Bool {
|
||||||
let inserted: Bool
|
let inserted: Bool
|
||||||
if childrenByParentEntity[parent.identifier] == nil {
|
if childrenByParentEntity[parent.identifier] == nil {
|
||||||
|
|
@ -21,10 +22,12 @@ extension Nexus {
|
||||||
return inserted
|
return inserted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
public final func removeChild(_ child: Entity, from parent: Entity) -> Bool {
|
public final func removeChild(_ child: Entity, from parent: Entity) -> Bool {
|
||||||
removeChild(child.identifier, from: parent.identifier)
|
removeChild(child.identifier, from: parent.identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public final func removeChild(_ child: EntityIdentifier, from parent: EntityIdentifier) -> Bool {
|
public final func removeChild(_ child: EntityIdentifier, from parent: EntityIdentifier) -> Bool {
|
||||||
let removed: Bool = childrenByParentEntity[parent]?.remove(child) != nil
|
let removed: Bool = childrenByParentEntity[parent]?.remove(child) != nil
|
||||||
|
|
@ -34,15 +37,18 @@ extension Nexus {
|
||||||
return removed
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
public final func removeAllChildren(from parent: Entity) {
|
public final func removeAllChildren(from parent: Entity) {
|
||||||
self.removeAllChildren(from: parent.identifier)
|
self.removeAllChildren(from: parent.identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
public final func removeAllChildren(from parentId: EntityIdentifier) {
|
public final func removeAllChildren(from parentId: EntityIdentifier) {
|
||||||
childrenByParentEntity[parentId]?.forEach { removeChild($0, from: parentId) }
|
childrenByParentEntity[parentId]?.forEach { removeChild($0, from: parentId) }
|
||||||
return childrenByParentEntity[parentId] = nil
|
return childrenByParentEntity[parentId] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
public final func numChildren(for entity: Entity) -> Int {
|
public final func numChildren(for entity: Entity) -> Int {
|
||||||
childrenByParentEntity[entity.identifier]?.count ?? 0
|
childrenByParentEntity[entity.identifier]?.count ?? 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
public final class Nexus {
|
public final class Nexus {
|
||||||
/// Main entity storage.
|
/// Main entity storage.
|
||||||
/// Entities are tightly packed by EntityIdentifier.
|
/// Entities are tightly packed by EntityIdentifier.
|
||||||
@usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>
|
@usableFromInline final var entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>
|
||||||
|
|
||||||
/// Entity ids that are currently not used.
|
/// Entity ids that are currently not used.
|
||||||
let entityIdGenerator: EntityIdentifierGenerator
|
let entityIdGenerator: EntityIdentifierGenerator
|
||||||
|
|
@ -29,14 +29,14 @@ public final class Nexus {
|
||||||
|
|
||||||
/// - Key: FamilyTraitSet aka component types that make up one distinct family.
|
/// - Key: FamilyTraitSet aka component types that make up one distinct family.
|
||||||
/// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family.
|
/// - Value: Tightly packed EntityIdentifiers that represent the association of an entity to the family.
|
||||||
@usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>]
|
@usableFromInline final var familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>]
|
||||||
|
|
||||||
public final var codingStrategy: CodingStrategy
|
public final var codingStrategy: CodingStrategy
|
||||||
|
|
||||||
public final weak var delegate: NexusEventDelegate?
|
public final weak var delegate: NexusEventDelegate?
|
||||||
|
|
||||||
public convenience init() {
|
public convenience init() {
|
||||||
self.init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>(),
|
self.init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>(),
|
||||||
componentsByType: [:],
|
componentsByType: [:],
|
||||||
componentsByEntity: [:],
|
componentsByEntity: [:],
|
||||||
entityIdGenerator: EntityIdentifierGenerator(),
|
entityIdGenerator: EntityIdentifierGenerator(),
|
||||||
|
|
@ -45,11 +45,11 @@ public final class Nexus {
|
||||||
codingStrategy: DefaultCodingStrategy())
|
codingStrategy: DefaultCodingStrategy())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Id>,
|
internal init(entityStorage: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>,
|
||||||
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.Id>],
|
familyMembersByTraits: [FamilyTraitSet: UnorderedSparseSet<EntityIdentifier, EntityIdentifier.Idx>],
|
||||||
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>],
|
childrenByParentEntity: [EntityIdentifier: Set<EntityIdentifier>],
|
||||||
codingStrategy: CodingStrategy) {
|
codingStrategy: CodingStrategy) {
|
||||||
self.entityStorage = entityStorage
|
self.entityStorage = entityStorage
|
||||||
|
|
@ -87,6 +87,6 @@ public struct DefaultCodingStrategy: CodingStrategy {
|
||||||
public init() { }
|
public init() { }
|
||||||
|
|
||||||
public func codingKey<C>(for componentType: C.Type) -> DynamicCodingKey where C: Component {
|
public func codingKey<C>(for componentType: C.Type) -> DynamicCodingKey where C: Component {
|
||||||
DynamicCodingKey(stringValue: "\(C.self)")!
|
DynamicCodingKey(stringValue: "\(C.self)").unsafelyUnwrapped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,38 +13,143 @@
|
||||||
///
|
///
|
||||||
/// See <https://github.com/bombela/sparseset/blob/master/src/lib.rs> for a reference implementation.
|
/// See <https://github.com/bombela/sparseset/blob/master/src/lib.rs> for a reference implementation.
|
||||||
public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
||||||
/// An index into the dense store.
|
// swiftlint:disable nesting
|
||||||
public typealias DenseIndex = Int
|
@usableFromInline
|
||||||
|
final class Storage {
|
||||||
|
/// An index into the dense store.
|
||||||
|
@usableFromInline
|
||||||
|
typealias DenseIndex = Int
|
||||||
|
|
||||||
/// A sparse store holding indices into the dense mapped to key.
|
/// A sparse store holding indices into the dense mapped to key.
|
||||||
public typealias SparseStore = [Key: DenseIndex]
|
@usableFromInline
|
||||||
|
typealias SparseStore = [Key: DenseIndex]
|
||||||
|
|
||||||
/// A dense store holding all the entries.
|
/// A dense store holding all the entries.
|
||||||
public typealias DenseStore = ContiguousArray<Entry>
|
@usableFromInline
|
||||||
|
typealias DenseStore = ContiguousArray<Entry>
|
||||||
|
|
||||||
public struct Entry {
|
@usableFromInline
|
||||||
public let key: Key
|
struct Entry {
|
||||||
public let element: Element
|
@usableFromInline let key: Key
|
||||||
|
@usableFromInline let element: Element
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
init(key: Key, element: Element) {
|
||||||
|
self.key = key
|
||||||
|
self.element = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline var dense: DenseStore
|
||||||
|
@usableFromInline var sparse: SparseStore
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
init(sparse: SparseStore, dense: DenseStore) {
|
||||||
|
self.sparse = sparse
|
||||||
|
self.dense = dense
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline
|
||||||
|
convenience init() {
|
||||||
|
self.init(sparse: [:], dense: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
@usableFromInline var count: Int { dense.count }
|
||||||
|
@usableFromInline var isEmpty: Bool { dense.isEmpty }
|
||||||
|
|
||||||
|
@inlinable var first: Element? {
|
||||||
|
dense.first?.element
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
func findIndex(at key: Key) -> Int? {
|
||||||
|
guard let denseIndex = sparse[key], denseIndex < count else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return denseIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
func findElement(at key: Key) -> Element? {
|
||||||
|
guard let denseIndex = findIndex(at: key) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let entry = self.dense[denseIndex]
|
||||||
|
guard entry.key == key else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.element
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
func insert(_ element: Element, at key: Key) -> Bool {
|
||||||
|
if let denseIndex = findIndex(at: key) {
|
||||||
|
dense[denseIndex] = Entry(key: key, element: element)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let nIndex = dense.count
|
||||||
|
dense.append(Entry(key: key, element: element))
|
||||||
|
sparse.updateValue(nIndex, forKey: key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
func remove(at key: Key) -> Entry? {
|
||||||
|
guard let denseIndex = findIndex(at: key) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let removed = swapRemove(at: denseIndex)
|
||||||
|
if !dense.isEmpty && denseIndex < dense.count {
|
||||||
|
let swappedElement = dense[denseIndex]
|
||||||
|
sparse[swappedElement.key] = denseIndex
|
||||||
|
}
|
||||||
|
sparse[key] = nil
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes an element from the set and returns it in O(1).
|
||||||
|
/// The removed element is replaced with the last element of the set.
|
||||||
|
///
|
||||||
|
/// - Parameter denseIndex: the dense index
|
||||||
|
/// - Returns: the element entry
|
||||||
|
@inlinable
|
||||||
|
func swapRemove(at denseIndex: Int) -> Entry {
|
||||||
|
dense.swapAt(denseIndex, dense.count - 1)
|
||||||
|
return dense.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
func removeAll(keepingCapacity: Bool = false) {
|
||||||
|
sparse.removeAll(keepingCapacity: keepingCapacity)
|
||||||
|
dense.removeAll(keepingCapacity: keepingCapacity)
|
||||||
|
}
|
||||||
|
|
||||||
|
@inlinable
|
||||||
|
func makeIterator() -> IndexingIterator<ContiguousArray<Storage.Entry>> {
|
||||||
|
dense.makeIterator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@usableFromInline var dense: DenseStore
|
|
||||||
@usableFromInline var sparse: SparseStore
|
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
self.init(sparse: [:], dense: [])
|
self.init(storage: Storage())
|
||||||
}
|
}
|
||||||
|
|
||||||
init(sparse: SparseStore, dense: DenseStore) {
|
@usableFromInline
|
||||||
self.sparse = sparse
|
init(storage: Storage) {
|
||||||
self.dense = dense
|
self.storage = storage
|
||||||
}
|
}
|
||||||
|
|
||||||
public var count: Int { dense.count }
|
@usableFromInline let storage: Storage
|
||||||
public var isEmpty: Bool { dense.isEmpty }
|
|
||||||
|
public var count: Int { storage.count }
|
||||||
|
public var isEmpty: Bool { storage.isEmpty }
|
||||||
|
|
||||||
@inlinable
|
@inlinable
|
||||||
public func contains(_ key: Key) -> Bool {
|
public func contains(_ key: Key) -> Bool {
|
||||||
find(at: key) != nil
|
storage.findIndex(at: key) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inset an element for a given key into the set in O(1).
|
/// Inset an element for a given key into the set in O(1).
|
||||||
|
|
@ -55,16 +160,8 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
||||||
/// - key: the key
|
/// - key: the key
|
||||||
/// - Returns: true if new, false if replaced.
|
/// - Returns: true if new, false if replaced.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public mutating func insert(_ element: Element, at key: Key) -> Bool {
|
public func insert(_ element: Element, at key: Key) -> Bool {
|
||||||
if let (denseIndex, _) = find(at: key) {
|
storage.insert(element, at: key)
|
||||||
dense[denseIndex] = Entry(key: key, element: element)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let nIndex = dense.count
|
|
||||||
dense.append(Entry(key: key, element: element))
|
|
||||||
sparse[key] = nIndex
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the element for the given key in O(1).
|
/// Get the element for the given key in O(1).
|
||||||
|
|
@ -73,16 +170,12 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
||||||
/// - Returns: the element or nil of key not found.
|
/// - Returns: the element or nil of key not found.
|
||||||
@inlinable
|
@inlinable
|
||||||
public func get(at key: Key) -> Element? {
|
public func get(at key: Key) -> Element? {
|
||||||
guard let (_, element) = find(at: key) else {
|
storage.findElement(at: key)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return element
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@inlinable
|
@inlinable
|
||||||
public func get(unsafeAt key: Key) -> Element {
|
public func get(unsafeAt key: Key) -> Element {
|
||||||
find(at: key).unsafelyUnwrapped.1
|
storage.findElement(at: key).unsafelyUnwrapped
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the element entry for given key in O(1).
|
/// Removes the element entry for given key in O(1).
|
||||||
|
|
@ -90,83 +183,45 @@ public struct UnorderedSparseSet<Element, Key: Hashable & Codable> {
|
||||||
/// - Parameter key: the key
|
/// - Parameter key: the key
|
||||||
/// - Returns: removed value or nil if key not found.
|
/// - Returns: removed value or nil if key not found.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public mutating func remove(at key: Key) -> Entry? {
|
public func remove(at key: Key) -> Element? {
|
||||||
guard let (denseIndex, _) = find(at: key) else {
|
storage.remove(at: key)?.element
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let removed = swapRemove(at: denseIndex)
|
|
||||||
if !dense.isEmpty && denseIndex < dense.count {
|
|
||||||
let swappedElement = dense[denseIndex]
|
|
||||||
sparse[swappedElement.key] = denseIndex
|
|
||||||
}
|
|
||||||
sparse[key] = nil
|
|
||||||
return removed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@inlinable
|
@inlinable
|
||||||
public mutating func removeAll(keepingCapacity: Bool = false) {
|
public func removeAll(keepingCapacity: Bool = false) {
|
||||||
sparse.removeAll(keepingCapacity: keepingCapacity)
|
storage.removeAll(keepingCapacity: keepingCapacity)
|
||||||
dense.removeAll(keepingCapacity: keepingCapacity)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes an element from the set and returns it in O(1).
|
|
||||||
/// The removed element is replaced with the last element of the set.
|
|
||||||
///
|
|
||||||
/// - Parameter denseIndex: the dense index
|
|
||||||
/// - Returns: the element entry
|
|
||||||
private mutating func swapRemove(at denseIndex: Int) -> Entry {
|
|
||||||
dense.swapAt(denseIndex, dense.count - 1)
|
|
||||||
return dense.removeLast()
|
|
||||||
}
|
|
||||||
|
|
||||||
@inlinable
|
|
||||||
public func find(at key: Key) -> (Int, Element)? {
|
|
||||||
guard let denseIndex = sparse[key], denseIndex < count else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
let entry = self.dense[denseIndex]
|
|
||||||
guard entry.key == key else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return (denseIndex, entry.element)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@inlinable public var first: Element? {
|
@inlinable public var first: Element? {
|
||||||
dense.first?.element
|
storage.first
|
||||||
}
|
|
||||||
|
|
||||||
@inlinable public var last: Element? {
|
|
||||||
dense.last?.element
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UnorderedSparseSet where Key == Int {
|
extension UnorderedSparseSet where Key == Int {
|
||||||
@inlinable
|
@inlinable
|
||||||
public subscript(position: DenseIndex) -> Element {
|
public subscript(key: Key) -> Element {
|
||||||
get {
|
get {
|
||||||
get(unsafeAt: position)
|
get(unsafeAt: key)
|
||||||
}
|
}
|
||||||
|
|
||||||
set(newValue) {
|
nonmutating set(newValue) {
|
||||||
insert(newValue, at: position)
|
insert(newValue, at: key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Sequence
|
// MARK: - Sequence
|
||||||
extension UnorderedSparseSet: Sequence {
|
extension UnorderedSparseSet: Sequence {
|
||||||
public __consuming func makeIterator() -> ElementIterator {
|
public func makeIterator() -> ElementIterator {
|
||||||
ElementIterator(self)
|
ElementIterator(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UnorderedSparseSetIterator
|
// MARK: - UnorderedSparseSetIterator
|
||||||
public struct ElementIterator: IteratorProtocol {
|
public struct ElementIterator: IteratorProtocol {
|
||||||
public private(set) var iterator: IndexingIterator<ContiguousArray<UnorderedSparseSet<Element, Key>.Entry>>
|
var iterator: IndexingIterator<ContiguousArray<Storage.Entry>>
|
||||||
|
|
||||||
public init(_ sparseSet: UnorderedSparseSet<Element, Key>) {
|
public init(_ sparseSet: UnorderedSparseSet<Element, Key>) {
|
||||||
iterator = sparseSet.dense.makeIterator()
|
iterator = sparseSet.storage.makeIterator()
|
||||||
}
|
}
|
||||||
|
|
||||||
public mutating func next() -> Element? {
|
public mutating func next() -> Element? {
|
||||||
|
|
@ -176,13 +231,20 @@ extension UnorderedSparseSet: Sequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Equatable
|
// MARK: - Equatable
|
||||||
extension UnorderedSparseSet.Entry: Equatable where Element: Equatable { }
|
extension UnorderedSparseSet.Storage.Entry: Equatable where Element: Equatable { }
|
||||||
|
extension UnorderedSparseSet.Storage: Equatable where Element: Equatable {
|
||||||
|
@usableFromInline
|
||||||
|
static func == (lhs: UnorderedSparseSet<Element, Key>.Storage, rhs: UnorderedSparseSet<Element, Key>.Storage) -> Bool {
|
||||||
|
lhs.dense == rhs.dense && lhs.sparse == rhs.sparse
|
||||||
|
}
|
||||||
|
}
|
||||||
extension UnorderedSparseSet: Equatable where Element: Equatable {
|
extension UnorderedSparseSet: Equatable where Element: Equatable {
|
||||||
public static func == (lhs: UnorderedSparseSet<Element, Key>, rhs: UnorderedSparseSet<Element, Key>) -> Bool {
|
public static func == (lhs: UnorderedSparseSet<Element, Key>, rhs: UnorderedSparseSet<Element, Key>) -> Bool {
|
||||||
lhs.dense == rhs.dense && lhs.sparse == rhs.sparse
|
lhs.storage == rhs.storage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Codable
|
// MARK: - Codable
|
||||||
extension UnorderedSparseSet.Entry: Codable where Element: Codable { }
|
extension UnorderedSparseSet.Storage.Entry: Codable where Element: Codable { }
|
||||||
|
extension UnorderedSparseSet.Storage: Codable where Element: Codable { }
|
||||||
extension UnorderedSparseSet: Codable where Element: Codable { }
|
extension UnorderedSparseSet: Codable where Element: Codable { }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
//
|
||||||
|
// EntityCreationTests.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 30.07.20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import FirebladeECS
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class EntityCreationTests: XCTestCase {
|
||||||
|
|
||||||
|
func testCreateEntityOneComponent() throws {
|
||||||
|
let nexus = Nexus()
|
||||||
|
let entity = nexus.createEntity {
|
||||||
|
Position(x: 1, y: 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(entity[\Position.x], 1)
|
||||||
|
XCTAssertEqual(entity[\Position.y], 2)
|
||||||
|
|
||||||
|
XCTAssertEqual(nexus.numEntities, 1)
|
||||||
|
XCTAssertEqual(nexus.numComponents, 1)
|
||||||
|
XCTAssertEqual(nexus.numFamilies, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCreateEntityMultipleComponents() throws {
|
||||||
|
let nexus = Nexus()
|
||||||
|
|
||||||
|
let entity = nexus.createEntity {
|
||||||
|
Position(x: 1, y: 2)
|
||||||
|
Name(name: "Hello")
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(entity[\Position.x], 1)
|
||||||
|
XCTAssertEqual(entity[\Position.y], 2)
|
||||||
|
|
||||||
|
XCTAssertEqual(entity[\Name.name], "Hello")
|
||||||
|
|
||||||
|
XCTAssertEqual(nexus.numEntities, 1)
|
||||||
|
XCTAssertEqual(nexus.numComponents, 2)
|
||||||
|
XCTAssertEqual(nexus.numFamilies, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBulkCreateEntitiesOneComponent() throws {
|
||||||
|
let nexus = Nexus()
|
||||||
|
|
||||||
|
let entities = nexus.createEntities(count: 100) { ctx in
|
||||||
|
Velocity(a: Float(ctx.index))
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(entities[0][\Velocity.a], 0)
|
||||||
|
XCTAssertEqual(entities[99][\Velocity.a], 99)
|
||||||
|
|
||||||
|
XCTAssertEqual(nexus.numEntities, 100)
|
||||||
|
XCTAssertEqual(nexus.numComponents, 100)
|
||||||
|
XCTAssertEqual(nexus.numFamilies, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBulkCreateEntitiesMultipleComponents() throws {
|
||||||
|
let nexus = Nexus()
|
||||||
|
|
||||||
|
let entities = nexus.createEntities(count: 100) { ctx in
|
||||||
|
Position(x: ctx.index, y: ctx.index)
|
||||||
|
Name(name: "\(ctx.index)")
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(entities[0][\Position.x], 0)
|
||||||
|
XCTAssertEqual(entities[0][\Position.y], 0)
|
||||||
|
XCTAssertEqual(entities[0][\Name.name], "0")
|
||||||
|
XCTAssertEqual(entities[99][\Position.x], 99)
|
||||||
|
XCTAssertEqual(entities[99][\Position.y], 99)
|
||||||
|
XCTAssertEqual(entities[99][\Name.name], "99")
|
||||||
|
|
||||||
|
XCTAssertEqual(nexus.numEntities, 100)
|
||||||
|
XCTAssertEqual(nexus.numComponents, 200)
|
||||||
|
XCTAssertEqual(nexus.numFamilies, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import XCTest
|
import XCTest
|
||||||
import FirebladeECS
|
import FirebladeECS
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "This will be removed in the next minor update.")
|
||||||
class SceneGraphTests: XCTestCase {
|
class SceneGraphTests: XCTestCase {
|
||||||
|
|
||||||
var nexus: Nexus!
|
var nexus: Nexus!
|
||||||
|
|
|
||||||
|
|
@ -79,13 +79,13 @@ class SparseSetTests: XCTestCase {
|
||||||
for idx in 0..<num {
|
for idx in 0..<num {
|
||||||
let pos = Position(x: idx, y: idx)
|
let pos = Position(x: idx, y: idx)
|
||||||
set.insert(pos, at: idx)
|
set.insert(pos, at: idx)
|
||||||
XCTAssertEqual(set.sparse[idx], idx)
|
XCTAssertEqual(set.storage.sparse[idx], idx)
|
||||||
XCTAssertEqual(set.dense.count, idx + 1)
|
XCTAssertEqual(set.storage.dense.count, idx + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual(set.count, num)
|
XCTAssertEqual(set.count, num)
|
||||||
XCTAssertEqual(set.sparse.count, num)
|
XCTAssertEqual(set.storage.sparse.count, num)
|
||||||
XCTAssertEqual(set.dense.count, num)
|
XCTAssertEqual(set.storage.dense.count, num)
|
||||||
|
|
||||||
XCTAssertEqual(set.get(at: 0)?.x, 0)
|
XCTAssertEqual(set.get(at: 0)?.x, 0)
|
||||||
XCTAssertEqual(set.get(at: 1)?.x, 1)
|
XCTAssertEqual(set.get(at: 1)?.x, 1)
|
||||||
|
|
@ -105,21 +105,21 @@ class SparseSetTests: XCTestCase {
|
||||||
XCTAssertTrue(set.contains(6))
|
XCTAssertTrue(set.contains(6))
|
||||||
XCTAssertFalse(set.contains(7))
|
XCTAssertFalse(set.contains(7))
|
||||||
|
|
||||||
XCTAssertEqual(set.sparse[0], 0)
|
XCTAssertEqual(set.storage.sparse[0], 0)
|
||||||
XCTAssertEqual(set.sparse[1], 1)
|
XCTAssertEqual(set.storage.sparse[1], 1)
|
||||||
XCTAssertEqual(set.sparse[2], 2)
|
XCTAssertEqual(set.storage.sparse[2], 2)
|
||||||
XCTAssertEqual(set.sparse[3], 3)
|
XCTAssertEqual(set.storage.sparse[3], 3)
|
||||||
XCTAssertEqual(set.sparse[4], 4)
|
XCTAssertEqual(set.storage.sparse[4], 4)
|
||||||
XCTAssertEqual(set.sparse[5], 5)
|
XCTAssertEqual(set.storage.sparse[5], 5)
|
||||||
XCTAssertEqual(set.sparse[6], 6)
|
XCTAssertEqual(set.storage.sparse[6], 6)
|
||||||
XCTAssertEqual(set.sparse[7], nil)
|
XCTAssertEqual(set.storage.sparse[7], nil)
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
set.remove(at: 3)
|
set.remove(at: 3)
|
||||||
|
|
||||||
XCTAssertEqual(set.count, num - 1)
|
XCTAssertEqual(set.count, num - 1)
|
||||||
XCTAssertEqual(set.sparse.count, num - 1)
|
XCTAssertEqual(set.storage.sparse.count, num - 1)
|
||||||
XCTAssertEqual(set.dense.count, num - 1)
|
XCTAssertEqual(set.storage.dense.count, num - 1)
|
||||||
|
|
||||||
XCTAssertEqual(set.get(at: 0)?.x, 0)
|
XCTAssertEqual(set.get(at: 0)?.x, 0)
|
||||||
XCTAssertEqual(set.get(at: 1)?.x, 1)
|
XCTAssertEqual(set.get(at: 1)?.x, 1)
|
||||||
|
|
@ -139,21 +139,21 @@ class SparseSetTests: XCTestCase {
|
||||||
XCTAssertTrue(set.contains(6))
|
XCTAssertTrue(set.contains(6))
|
||||||
XCTAssertFalse(set.contains(7))
|
XCTAssertFalse(set.contains(7))
|
||||||
|
|
||||||
XCTAssertEqual(set.sparse[0], 0)
|
XCTAssertEqual(set.storage.sparse[0], 0)
|
||||||
XCTAssertEqual(set.sparse[1], 1)
|
XCTAssertEqual(set.storage.sparse[1], 1)
|
||||||
XCTAssertEqual(set.sparse[2], 2)
|
XCTAssertEqual(set.storage.sparse[2], 2)
|
||||||
XCTAssertEqual(set.sparse[3], nil)
|
XCTAssertEqual(set.storage.sparse[3], nil)
|
||||||
XCTAssertEqual(set.sparse[4], 4)
|
XCTAssertEqual(set.storage.sparse[4], 4)
|
||||||
XCTAssertEqual(set.sparse[5], 5)
|
XCTAssertEqual(set.storage.sparse[5], 5)
|
||||||
XCTAssertEqual(set.sparse[6], 3)
|
XCTAssertEqual(set.storage.sparse[6], 3)
|
||||||
XCTAssertEqual(set.sparse[7], nil)
|
XCTAssertEqual(set.storage.sparse[7], nil)
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
set.remove(at: 2)
|
set.remove(at: 2)
|
||||||
|
|
||||||
XCTAssertEqual(set.count, num - 2)
|
XCTAssertEqual(set.count, num - 2)
|
||||||
XCTAssertEqual(set.sparse.count, num - 2)
|
XCTAssertEqual(set.storage.sparse.count, num - 2)
|
||||||
XCTAssertEqual(set.dense.count, num - 2)
|
XCTAssertEqual(set.storage.dense.count, num - 2)
|
||||||
|
|
||||||
XCTAssertEqual(set.get(at: 0)?.x, 0)
|
XCTAssertEqual(set.get(at: 0)?.x, 0)
|
||||||
XCTAssertEqual(set.get(at: 1)?.x, 1)
|
XCTAssertEqual(set.get(at: 1)?.x, 1)
|
||||||
|
|
@ -173,21 +173,21 @@ class SparseSetTests: XCTestCase {
|
||||||
XCTAssertTrue(set.contains(6))
|
XCTAssertTrue(set.contains(6))
|
||||||
XCTAssertFalse(set.contains(7))
|
XCTAssertFalse(set.contains(7))
|
||||||
|
|
||||||
XCTAssertEqual(set.sparse[0], 0)
|
XCTAssertEqual(set.storage.sparse[0], 0)
|
||||||
XCTAssertEqual(set.sparse[1], 1)
|
XCTAssertEqual(set.storage.sparse[1], 1)
|
||||||
XCTAssertEqual(set.sparse[2], nil)
|
XCTAssertEqual(set.storage.sparse[2], nil)
|
||||||
XCTAssertEqual(set.sparse[3], nil)
|
XCTAssertEqual(set.storage.sparse[3], nil)
|
||||||
XCTAssertEqual(set.sparse[4], 4)
|
XCTAssertEqual(set.storage.sparse[4], 4)
|
||||||
XCTAssertEqual(set.sparse[5], 2)
|
XCTAssertEqual(set.storage.sparse[5], 2)
|
||||||
XCTAssertEqual(set.sparse[6], 3)
|
XCTAssertEqual(set.storage.sparse[6], 3)
|
||||||
XCTAssertEqual(set.sparse[7], nil)
|
XCTAssertEqual(set.storage.sparse[7], nil)
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
set.remove(at: 0)
|
set.remove(at: 0)
|
||||||
|
|
||||||
XCTAssertEqual(set.count, num - 3)
|
XCTAssertEqual(set.count, num - 3)
|
||||||
XCTAssertEqual(set.sparse.count, num - 3)
|
XCTAssertEqual(set.storage.sparse.count, num - 3)
|
||||||
XCTAssertEqual(set.dense.count, num - 3)
|
XCTAssertEqual(set.storage.dense.count, num - 3)
|
||||||
|
|
||||||
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
||||||
XCTAssertEqual(set.get(at: 1)?.x, 1)
|
XCTAssertEqual(set.get(at: 1)?.x, 1)
|
||||||
|
|
@ -207,21 +207,21 @@ class SparseSetTests: XCTestCase {
|
||||||
XCTAssertTrue(set.contains(6))
|
XCTAssertTrue(set.contains(6))
|
||||||
XCTAssertFalse(set.contains(7))
|
XCTAssertFalse(set.contains(7))
|
||||||
|
|
||||||
XCTAssertEqual(set.sparse[0], nil)
|
XCTAssertEqual(set.storage.sparse[0], nil)
|
||||||
XCTAssertEqual(set.sparse[1], 1)
|
XCTAssertEqual(set.storage.sparse[1], 1)
|
||||||
XCTAssertEqual(set.sparse[2], nil)
|
XCTAssertEqual(set.storage.sparse[2], nil)
|
||||||
XCTAssertEqual(set.sparse[3], nil)
|
XCTAssertEqual(set.storage.sparse[3], nil)
|
||||||
XCTAssertEqual(set.sparse[4], 0)
|
XCTAssertEqual(set.storage.sparse[4], 0)
|
||||||
XCTAssertEqual(set.sparse[5], 2)
|
XCTAssertEqual(set.storage.sparse[5], 2)
|
||||||
XCTAssertEqual(set.sparse[6], 3)
|
XCTAssertEqual(set.storage.sparse[6], 3)
|
||||||
XCTAssertEqual(set.sparse[7], nil)
|
XCTAssertEqual(set.storage.sparse[7], nil)
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
set.remove(at: 1)
|
set.remove(at: 1)
|
||||||
|
|
||||||
XCTAssertEqual(set.count, num - 4)
|
XCTAssertEqual(set.count, num - 4)
|
||||||
XCTAssertEqual(set.sparse.count, num - 4)
|
XCTAssertEqual(set.storage.sparse.count, num - 4)
|
||||||
XCTAssertEqual(set.dense.count, num - 4)
|
XCTAssertEqual(set.storage.dense.count, num - 4)
|
||||||
|
|
||||||
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
||||||
XCTAssertEqual(set.get(at: 1)?.x, nil)
|
XCTAssertEqual(set.get(at: 1)?.x, nil)
|
||||||
|
|
@ -241,21 +241,21 @@ class SparseSetTests: XCTestCase {
|
||||||
XCTAssertTrue(set.contains(6))
|
XCTAssertTrue(set.contains(6))
|
||||||
XCTAssertFalse(set.contains(7))
|
XCTAssertFalse(set.contains(7))
|
||||||
|
|
||||||
XCTAssertEqual(set.sparse[0], nil)
|
XCTAssertEqual(set.storage.sparse[0], nil)
|
||||||
XCTAssertEqual(set.sparse[1], nil)
|
XCTAssertEqual(set.storage.sparse[1], nil)
|
||||||
XCTAssertEqual(set.sparse[2], nil)
|
XCTAssertEqual(set.storage.sparse[2], nil)
|
||||||
XCTAssertEqual(set.sparse[3], nil)
|
XCTAssertEqual(set.storage.sparse[3], nil)
|
||||||
XCTAssertEqual(set.sparse[4], 0)
|
XCTAssertEqual(set.storage.sparse[4], 0)
|
||||||
XCTAssertEqual(set.sparse[5], 2)
|
XCTAssertEqual(set.storage.sparse[5], 2)
|
||||||
XCTAssertEqual(set.sparse[6], 1)
|
XCTAssertEqual(set.storage.sparse[6], 1)
|
||||||
XCTAssertEqual(set.sparse[7], nil)
|
XCTAssertEqual(set.storage.sparse[7], nil)
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
set.remove(at: 6)
|
set.remove(at: 6)
|
||||||
|
|
||||||
XCTAssertEqual(set.count, num - 5)
|
XCTAssertEqual(set.count, num - 5)
|
||||||
XCTAssertEqual(set.sparse.count, num - 5)
|
XCTAssertEqual(set.storage.sparse.count, num - 5)
|
||||||
XCTAssertEqual(set.dense.count, num - 5)
|
XCTAssertEqual(set.storage.dense.count, num - 5)
|
||||||
|
|
||||||
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
||||||
XCTAssertEqual(set.get(at: 1)?.x, nil)
|
XCTAssertEqual(set.get(at: 1)?.x, nil)
|
||||||
|
|
@ -275,21 +275,21 @@ class SparseSetTests: XCTestCase {
|
||||||
XCTAssertFalse(set.contains(6))
|
XCTAssertFalse(set.contains(6))
|
||||||
XCTAssertFalse(set.contains(7))
|
XCTAssertFalse(set.contains(7))
|
||||||
|
|
||||||
XCTAssertEqual(set.sparse[0], nil)
|
XCTAssertEqual(set.storage.sparse[0], nil)
|
||||||
XCTAssertEqual(set.sparse[1], nil)
|
XCTAssertEqual(set.storage.sparse[1], nil)
|
||||||
XCTAssertEqual(set.sparse[2], nil)
|
XCTAssertEqual(set.storage.sparse[2], nil)
|
||||||
XCTAssertEqual(set.sparse[3], nil)
|
XCTAssertEqual(set.storage.sparse[3], nil)
|
||||||
XCTAssertEqual(set.sparse[4], 0)
|
XCTAssertEqual(set.storage.sparse[4], 0)
|
||||||
XCTAssertEqual(set.sparse[5], 1)
|
XCTAssertEqual(set.storage.sparse[5], 1)
|
||||||
XCTAssertEqual(set.sparse[6], nil)
|
XCTAssertEqual(set.storage.sparse[6], nil)
|
||||||
XCTAssertEqual(set.sparse[7], nil)
|
XCTAssertEqual(set.storage.sparse[7], nil)
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
set.remove(at: 5)
|
set.remove(at: 5)
|
||||||
|
|
||||||
XCTAssertEqual(set.count, num - 6)
|
XCTAssertEqual(set.count, num - 6)
|
||||||
XCTAssertEqual(set.sparse.count, num - 6)
|
XCTAssertEqual(set.storage.sparse.count, num - 6)
|
||||||
XCTAssertEqual(set.dense.count, num - 6)
|
XCTAssertEqual(set.storage.dense.count, num - 6)
|
||||||
|
|
||||||
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
||||||
XCTAssertEqual(set.get(at: 1)?.x, nil)
|
XCTAssertEqual(set.get(at: 1)?.x, nil)
|
||||||
|
|
@ -309,21 +309,21 @@ class SparseSetTests: XCTestCase {
|
||||||
XCTAssertFalse(set.contains(6))
|
XCTAssertFalse(set.contains(6))
|
||||||
XCTAssertFalse(set.contains(7))
|
XCTAssertFalse(set.contains(7))
|
||||||
|
|
||||||
XCTAssertEqual(set.sparse[0], nil)
|
XCTAssertEqual(set.storage.sparse[0], nil)
|
||||||
XCTAssertEqual(set.sparse[1], nil)
|
XCTAssertEqual(set.storage.sparse[1], nil)
|
||||||
XCTAssertEqual(set.sparse[2], nil)
|
XCTAssertEqual(set.storage.sparse[2], nil)
|
||||||
XCTAssertEqual(set.sparse[3], nil)
|
XCTAssertEqual(set.storage.sparse[3], nil)
|
||||||
XCTAssertEqual(set.sparse[4], 0)
|
XCTAssertEqual(set.storage.sparse[4], 0)
|
||||||
XCTAssertEqual(set.sparse[5], nil)
|
XCTAssertEqual(set.storage.sparse[5], nil)
|
||||||
XCTAssertEqual(set.sparse[6], nil)
|
XCTAssertEqual(set.storage.sparse[6], nil)
|
||||||
XCTAssertEqual(set.sparse[7], nil)
|
XCTAssertEqual(set.storage.sparse[7], nil)
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
set.remove(at: 4)
|
set.remove(at: 4)
|
||||||
|
|
||||||
XCTAssertEqual(set.count, 0)
|
XCTAssertEqual(set.count, 0)
|
||||||
XCTAssertEqual(set.sparse.count, 0)
|
XCTAssertEqual(set.storage.sparse.count, 0)
|
||||||
XCTAssertEqual(set.dense.count, 0)
|
XCTAssertEqual(set.storage.dense.count, 0)
|
||||||
XCTAssertTrue(set.isEmpty)
|
XCTAssertTrue(set.isEmpty)
|
||||||
|
|
||||||
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
XCTAssertEqual(set.get(at: 0)?.x, nil)
|
||||||
|
|
@ -344,14 +344,14 @@ class SparseSetTests: XCTestCase {
|
||||||
XCTAssertFalse(set.contains(6))
|
XCTAssertFalse(set.contains(6))
|
||||||
XCTAssertFalse(set.contains(7))
|
XCTAssertFalse(set.contains(7))
|
||||||
|
|
||||||
XCTAssertEqual(set.sparse[0], nil)
|
XCTAssertEqual(set.storage.sparse[0], nil)
|
||||||
XCTAssertEqual(set.sparse[1], nil)
|
XCTAssertEqual(set.storage.sparse[1], nil)
|
||||||
XCTAssertEqual(set.sparse[2], nil)
|
XCTAssertEqual(set.storage.sparse[2], nil)
|
||||||
XCTAssertEqual(set.sparse[3], nil)
|
XCTAssertEqual(set.storage.sparse[3], nil)
|
||||||
XCTAssertEqual(set.sparse[4], nil)
|
XCTAssertEqual(set.storage.sparse[4], nil)
|
||||||
XCTAssertEqual(set.sparse[5], nil)
|
XCTAssertEqual(set.storage.sparse[5], nil)
|
||||||
XCTAssertEqual(set.sparse[6], nil)
|
XCTAssertEqual(set.storage.sparse[6], nil)
|
||||||
XCTAssertEqual(set.sparse[7], nil)
|
XCTAssertEqual(set.storage.sparse[7], nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSparseSetRemoveAndAdd() {
|
func testSparseSetRemoveAndAdd() {
|
||||||
|
|
@ -365,8 +365,8 @@ class SparseSetTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual(set.count, indices.count)
|
XCTAssertEqual(set.count, indices.count)
|
||||||
XCTAssertEqual(set.sparse.count, indices.count)
|
XCTAssertEqual(set.storage.sparse.count, indices.count)
|
||||||
XCTAssertEqual(set.dense.count, indices.count)
|
XCTAssertEqual(set.storage.dense.count, indices.count)
|
||||||
XCTAssertFalse(set.isEmpty)
|
XCTAssertFalse(set.isEmpty)
|
||||||
|
|
||||||
XCTAssertEqual(set.get(at: 0)?.x, 0)
|
XCTAssertEqual(set.get(at: 0)?.x, 0)
|
||||||
|
|
@ -387,14 +387,14 @@ class SparseSetTests: XCTestCase {
|
||||||
|
|
||||||
func testSparseSetDoubleRemove() {
|
func testSparseSetDoubleRemove() {
|
||||||
class AClass { }
|
class AClass { }
|
||||||
var set = UnorderedSparseSet<AClass, Int>()
|
let set = UnorderedSparseSet<AClass, Int>()
|
||||||
let a = AClass()
|
let a = AClass()
|
||||||
let b = AClass()
|
let b = AClass()
|
||||||
set.insert(a, at: 0)
|
set.insert(a, at: 0)
|
||||||
set.insert(b, at: 1)
|
set.insert(b, at: 1)
|
||||||
|
|
||||||
XCTAssertEqual(set.sparse.count, 2)
|
XCTAssertEqual(set.storage.sparse.count, 2)
|
||||||
XCTAssertEqual(set.dense.count, 2)
|
XCTAssertEqual(set.storage.dense.count, 2)
|
||||||
|
|
||||||
XCTAssertEqual(set.count, 2)
|
XCTAssertEqual(set.count, 2)
|
||||||
|
|
||||||
|
|
@ -406,14 +406,14 @@ class SparseSetTests: XCTestCase {
|
||||||
XCTAssertNotNil(set.remove(at: 1))
|
XCTAssertNotNil(set.remove(at: 1))
|
||||||
|
|
||||||
XCTAssertEqual(set.count, 1)
|
XCTAssertEqual(set.count, 1)
|
||||||
XCTAssertEqual(set.sparse.count, 1)
|
XCTAssertEqual(set.storage.sparse.count, 1)
|
||||||
XCTAssertEqual(set.dense.count, 1)
|
XCTAssertEqual(set.storage.dense.count, 1)
|
||||||
|
|
||||||
XCTAssertNil(set.remove(at: 1))
|
XCTAssertNil(set.remove(at: 1))
|
||||||
|
|
||||||
XCTAssertEqual(set.count, 1)
|
XCTAssertEqual(set.count, 1)
|
||||||
XCTAssertEqual(set.sparse.count, 1)
|
XCTAssertEqual(set.storage.sparse.count, 1)
|
||||||
XCTAssertEqual(set.dense.count, 1)
|
XCTAssertEqual(set.storage.dense.count, 1)
|
||||||
|
|
||||||
XCTAssertTrue(set.get(at: 0) === a)
|
XCTAssertTrue(set.get(at: 0) === a)
|
||||||
|
|
||||||
|
|
@ -441,7 +441,7 @@ class SparseSetTests: XCTestCase {
|
||||||
|
|
||||||
while let idx = indices.popFirst() {
|
while let idx = indices.popFirst() {
|
||||||
let entry = set.remove(at: idx)!
|
let entry = set.remove(at: idx)!
|
||||||
XCTAssertEqual(entry.key, idx)
|
XCTAssertEqual(entry.x, idx)
|
||||||
recurseValueTest()
|
recurseValueTest()
|
||||||
XCTAssertEqual(set.count, indices.count)
|
XCTAssertEqual(set.count, indices.count)
|
||||||
}
|
}
|
||||||
|
|
@ -471,7 +471,7 @@ class SparseSetTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSparseSetReduce() {
|
func testSparseSetReduce() {
|
||||||
var characters = UnorderedSparseSet<Character, Int>()
|
let characters = UnorderedSparseSet<Character, Int>()
|
||||||
|
|
||||||
characters.insert("H", at: 4)
|
characters.insert("H", at: 4)
|
||||||
characters.insert("e", at: 13)
|
characters.insert("e", at: 13)
|
||||||
|
|
@ -488,7 +488,7 @@ class SparseSetTests: XCTestCase {
|
||||||
|
|
||||||
XCTAssertEqual(characters.count, 11)
|
XCTAssertEqual(characters.count, 11)
|
||||||
|
|
||||||
let string: String = characters.dense.reduce("") { res, char in
|
let string: String = characters.storage.dense.reduce("") { res, char in
|
||||||
res + "\(char.element)"
|
res + "\(char.element)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -497,7 +497,7 @@ class SparseSetTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSubscript() {
|
func testSubscript() {
|
||||||
var characters = UnorderedSparseSet<Character, Int>()
|
let characters = UnorderedSparseSet<Character, Int>()
|
||||||
|
|
||||||
characters[4] = "H"
|
characters[4] = "H"
|
||||||
characters[13] = "e"
|
characters[13] = "e"
|
||||||
|
|
@ -528,32 +528,47 @@ class SparseSetTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStartEndIndex() {
|
func testStartEndIndex() {
|
||||||
var set = UnorderedSparseSet<Character, Int>()
|
let set = UnorderedSparseSet<Character, Int>()
|
||||||
|
|
||||||
set.insert("C", at: 33)
|
set.insert("C", at: 33)
|
||||||
set.insert("A", at: 11)
|
set.insert("A", at: 11)
|
||||||
set.insert("B", at: 22)
|
set.insert("B", at: 22)
|
||||||
|
|
||||||
let mapped = set.dense.map { $0.element }
|
let mapped = set.storage.dense.map { $0.element }
|
||||||
|
|
||||||
XCTAssertEqual(mapped, ["C", "A", "B"])
|
XCTAssertEqual(mapped, ["C", "A", "B"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAlternativeKey() {
|
func testAlternativeKey() {
|
||||||
|
|
||||||
var set = UnorderedSparseSet<Character, String>()
|
let set = UnorderedSparseSet<Character, String>()
|
||||||
|
|
||||||
set.insert("A", at: "a")
|
set.insert("A", at: "a")
|
||||||
set.insert("C", at: "c")
|
set.insert("C", at: "c")
|
||||||
set.insert("B", at: "b")
|
set.insert("B", at: "b")
|
||||||
|
|
||||||
let mapped = set.dense.map { $0.element }
|
let mapped = set.storage.dense.map { $0.element }
|
||||||
XCTAssertEqual(mapped, ["A", "C", "B"])
|
XCTAssertEqual(mapped, ["A", "C", "B"])
|
||||||
let keyValues = set.sparse.sorted(by: { $0.value < $1.value }).map { ($0.key, $0.value) }
|
let keyValues = set.storage.sparse.sorted(by: { $0.value < $1.value }).map { ($0.key, $0.value) }
|
||||||
for (a, b) in zip(keyValues, [("a", 0), ("c", 1), ("b", 2)]) {
|
for (a, b) in zip(keyValues, [("a", 0), ("c", 1), ("b", 2)]) {
|
||||||
XCTAssertEqual(a.0, b.0)
|
XCTAssertEqual(a.0, b.0)
|
||||||
XCTAssertEqual(a.1, b.1)
|
XCTAssertEqual(a.1, b.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testEquality() {
|
||||||
|
|
||||||
|
let setA = UnorderedSparseSet<Int, String>()
|
||||||
|
let setB = UnorderedSparseSet<Int, String>()
|
||||||
|
|
||||||
|
setA.insert(3, at: "Hello")
|
||||||
|
setB.insert(3, at: "Hello")
|
||||||
|
|
||||||
|
XCTAssertEqual(setA, setB)
|
||||||
|
|
||||||
|
setB.insert(4, at: "World")
|
||||||
|
|
||||||
|
XCTAssertNotEqual(setA, setB)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,18 @@ extension ComponentTests {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension EntityCreationTests {
|
||||||
|
// DO NOT MODIFY: This is autogenerated, use:
|
||||||
|
// `swift test --generate-linuxmain`
|
||||||
|
// to regenerate.
|
||||||
|
static let __allTests__EntityCreationTests = [
|
||||||
|
("testBulkCreateEntitiesMultipleComponents", testBulkCreateEntitiesMultipleComponents),
|
||||||
|
("testBulkCreateEntitiesOneComponent", testBulkCreateEntitiesOneComponent),
|
||||||
|
("testCreateEntityMultipleComponents", testCreateEntityMultipleComponents),
|
||||||
|
("testCreateEntityOneComponent", testCreateEntityOneComponent)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
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`
|
||||||
|
|
@ -134,6 +146,7 @@ extension SparseSetTests {
|
||||||
// to regenerate.
|
// to regenerate.
|
||||||
static let __allTests__SparseSetTests = [
|
static let __allTests__SparseSetTests = [
|
||||||
("testAlternativeKey", testAlternativeKey),
|
("testAlternativeKey", testAlternativeKey),
|
||||||
|
("testEquality", testEquality),
|
||||||
("testSparseSetAdd", testSparseSetAdd),
|
("testSparseSetAdd", testSparseSetAdd),
|
||||||
("testSparseSetAddAndReplace", testSparseSetAddAndReplace),
|
("testSparseSetAddAndReplace", testSparseSetAddAndReplace),
|
||||||
("testSparseSetClear", testSparseSetClear),
|
("testSparseSetClear", testSparseSetClear),
|
||||||
|
|
@ -162,6 +175,7 @@ public func __allTests() -> [XCTestCaseEntry] {
|
||||||
return [
|
return [
|
||||||
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
|
testCase(ComponentIdentifierTests.__allTests__ComponentIdentifierTests),
|
||||||
testCase(ComponentTests.__allTests__ComponentTests),
|
testCase(ComponentTests.__allTests__ComponentTests),
|
||||||
|
testCase(EntityCreationTests.__allTests__EntityCreationTests),
|
||||||
testCase(EntityTests.__allTests__EntityTests),
|
testCase(EntityTests.__allTests__EntityTests),
|
||||||
testCase(FamilyCodingTests.__allTests__FamilyCodingTests),
|
testCase(FamilyCodingTests.__allTests__FamilyCodingTests),
|
||||||
testCase(FamilyTests.__allTests__FamilyTests),
|
testCase(FamilyTests.__allTests__FamilyTests),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue