Add SparseComponentSet
This commit is contained in:
parent
e643c7761a
commit
a491d457ec
|
|
@ -17,6 +17,7 @@ public protocol ManagedContiguousArrayProtocol: class {
|
||||||
associatedtype Element
|
associatedtype Element
|
||||||
static var chunkSize: Int { get }
|
static var chunkSize: Int { get }
|
||||||
init(minCount: Int)
|
init(minCount: Int)
|
||||||
|
var count: Int { get }
|
||||||
func insert(_ element: Element, at index: Int)
|
func insert(_ element: Element, at index: Int)
|
||||||
func has(_ index: Int) -> Bool
|
func has(_ index: Int) -> Bool
|
||||||
func get(at index: Int) -> Element?
|
func get(at index: Int) -> Element?
|
||||||
|
|
@ -27,15 +28,23 @@ public class ManagedContiguousArray: ManagedContiguousArrayProtocol {
|
||||||
public static var chunkSize: Int = 4096
|
public static var chunkSize: Int = 4096
|
||||||
|
|
||||||
public typealias Element = Any
|
public typealias Element = Any
|
||||||
|
var _count: Int = 0
|
||||||
var _store: ContiguousArray<Element?> = []
|
var _store: ContiguousArray<Element?> = []
|
||||||
public required init(minCount: Int = chunkSize) {
|
public required init(minCount: Int = chunkSize) {
|
||||||
_store = ContiguousArray<Element?>(repeating: nil, count: minCount)
|
_store = ContiguousArray<Element?>(repeating: nil, count: minCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var count: Int {
|
||||||
|
return _count
|
||||||
|
}
|
||||||
|
|
||||||
public func insert(_ element: Element, at index: Int) {
|
public func insert(_ element: Element, at index: Int) {
|
||||||
if needsToGrow(index) {
|
if needsToGrow(index) {
|
||||||
grow(including: index)
|
grow(including: index)
|
||||||
}
|
}
|
||||||
|
if _store[index] == nil {
|
||||||
|
_count += 1
|
||||||
|
}
|
||||||
_store[index] = element
|
_store[index] = element
|
||||||
}
|
}
|
||||||
public func has(_ index: Int) -> Bool {
|
public func has(_ index: Int) -> Bool {
|
||||||
|
|
@ -48,6 +57,9 @@ public class ManagedContiguousArray: ManagedContiguousArrayProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func remove(at index: Int) {
|
public func remove(at index: Int) {
|
||||||
|
if _store[index] != nil {
|
||||||
|
_count -= 1
|
||||||
|
}
|
||||||
return _store[index] = nil
|
return _store[index] = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,14 @@
|
||||||
|
|
||||||
extension Nexus {
|
extension Nexus {
|
||||||
|
|
||||||
|
public var numComponents: Int {
|
||||||
|
var count = 0
|
||||||
|
for (_, value) in componentsByType {
|
||||||
|
count += value._count
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
public func has(componentId: ComponentIdentifier, entityIdx: EntityIndex) -> Bool {
|
public func has(componentId: ComponentIdentifier, entityIdx: EntityIndex) -> Bool {
|
||||||
guard let uniforms = componentsByType[componentId] else { return false }
|
guard let uniforms = componentsByType[componentId] else { return false }
|
||||||
return uniforms.has(entityIdx)
|
return uniforms.has(entityIdx)
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,13 @@
|
||||||
|
|
||||||
extension Nexus {
|
extension Nexus {
|
||||||
|
|
||||||
|
public var entities: [Entity] {
|
||||||
|
return entityStorage.filter { $0.isValid }
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate func nextEntityIdx() -> EntityIndex {
|
fileprivate func nextEntityIdx() -> EntityIndex {
|
||||||
guard let nextReused: EntityIdentifier = freeEntities.popLast() else {
|
guard let nextReused: EntityIdentifier = freeEntities.popLast() else {
|
||||||
return entities.count
|
return entityStorage.count
|
||||||
}
|
}
|
||||||
return nextReused.index
|
return nextReused.index
|
||||||
}
|
}
|
||||||
|
|
@ -17,8 +21,8 @@ extension Nexus {
|
||||||
public func create(entity name: String? = nil) -> Entity {
|
public func create(entity name: String? = nil) -> Entity {
|
||||||
let newEntityIndex: EntityIndex = nextEntityIdx()
|
let newEntityIndex: EntityIndex = nextEntityIdx()
|
||||||
let newEntityIdentifier: EntityIdentifier = newEntityIndex.identifier
|
let newEntityIdentifier: EntityIdentifier = newEntityIndex.identifier
|
||||||
if entities.count > newEntityIndex {
|
if entityStorage.count > newEntityIndex {
|
||||||
let reusedEntity: Entity = entities[newEntityIndex]
|
let reusedEntity: Entity = entityStorage[newEntityIndex]
|
||||||
assert(reusedEntity.identifier == EntityIdentifier.invalid, "Stil valid entity \(reusedEntity)")
|
assert(reusedEntity.identifier == EntityIdentifier.invalid, "Stil valid entity \(reusedEntity)")
|
||||||
reusedEntity.identifier = newEntityIdentifier
|
reusedEntity.identifier = newEntityIdentifier
|
||||||
reusedEntity.name = name
|
reusedEntity.name = name
|
||||||
|
|
@ -26,15 +30,15 @@ extension Nexus {
|
||||||
return reusedEntity
|
return reusedEntity
|
||||||
} else {
|
} else {
|
||||||
let newEntity = Entity(nexus: self, id: newEntityIdentifier, name: name)
|
let newEntity = Entity(nexus: self, id: newEntityIdentifier, name: name)
|
||||||
entities.insert(newEntity, at: newEntityIndex)
|
entityStorage.insert(newEntity, at: newEntityIndex)
|
||||||
notify(EntityCreated(entityId: newEntityIdentifier))
|
notify(EntityCreated(entityId: newEntityIdentifier))
|
||||||
return newEntity
|
return newEntity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of entities in nexus.
|
/// Number of entities in nexus.
|
||||||
public var count: Int {
|
public var numEntities: Int {
|
||||||
return entities.count - freeEntities.count
|
return entityStorage.count - freeEntities.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValid(entity: Entity) -> Bool {
|
func isValid(entity: Entity) -> Bool {
|
||||||
|
|
@ -44,7 +48,7 @@ extension Nexus {
|
||||||
func isValid(entity entitiyId: EntityIdentifier) -> Bool {
|
func isValid(entity entitiyId: EntityIdentifier) -> Bool {
|
||||||
return entitiyId != EntityIdentifier.invalid &&
|
return entitiyId != EntityIdentifier.invalid &&
|
||||||
entitiyId.index >= 0 &&
|
entitiyId.index >= 0 &&
|
||||||
entitiyId.index < entities.count
|
entitiyId.index < entityStorage.count
|
||||||
}
|
}
|
||||||
|
|
||||||
public func has(entity entityId: EntityIdentifier) -> Bool {
|
public func has(entity entityId: EntityIdentifier) -> Bool {
|
||||||
|
|
@ -52,7 +56,7 @@ extension Nexus {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func get(entity entityId: EntityIdentifier) -> Entity {
|
public func get(entity entityId: EntityIdentifier) -> Entity {
|
||||||
return entities[entityId.index]
|
return entityStorage[entityId.index]
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
|
@ -70,7 +74,7 @@ extension Nexus {
|
||||||
|
|
||||||
// replace with "new" invalid entity to keep capacity of array
|
// replace with "new" invalid entity to keep capacity of array
|
||||||
let invalidEntity = Entity(nexus: self, id: EntityIdentifier.invalid)
|
let invalidEntity = Entity(nexus: self, id: EntityIdentifier.invalid)
|
||||||
entities[entityId.index] = invalidEntity
|
entityStorage[entityId.index] = invalidEntity
|
||||||
|
|
||||||
freeEntities.append(entityId)
|
freeEntities.append(entityId)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ extension Nexus {
|
||||||
assert(replaced == nil, "Family with exact trait hash already exists: \(traitHash)")
|
assert(replaced == nil, "Family with exact trait hash already exists: \(traitHash)")
|
||||||
|
|
||||||
// FIXME: this is costly for many entities
|
// FIXME: this is costly for many entities
|
||||||
for entity: Entity in entities {
|
for entity: Entity in entityStorage {
|
||||||
update(membership: family, for: entity.identifier)
|
update(membership: family, for: entity.identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public class Nexus {
|
||||||
|
|
||||||
/// - Index: index value matching entity identifier shifted to Int
|
/// - Index: index value matching entity identifier shifted to Int
|
||||||
/// - Value: each element is a entity instance
|
/// - Value: each element is a entity instance
|
||||||
var entities: Entities
|
var entityStorage: Entities
|
||||||
|
|
||||||
/// - Key: component type identifier
|
/// - Key: component type identifier
|
||||||
/// - Value: each element is a component instance of the same type (uniform). New component instances are appended.
|
/// - Value: each element is a component instance of the same type (uniform). New component instances are appended.
|
||||||
|
|
@ -42,11 +42,11 @@ public class Nexus {
|
||||||
var freeEntities: ContiguousArray<EntityIdentifier>
|
var freeEntities: ContiguousArray<EntityIdentifier>
|
||||||
|
|
||||||
var familiyByTraitHash: [FamilyTraitSetHash: Family]
|
var familiyByTraitHash: [FamilyTraitSetHash: Family]
|
||||||
var familyMembersByTraitHash: [FamilyTraitSetHash: [EntityIdentifier]]
|
var familyMembersByTraitHash: [FamilyTraitSetHash: [EntityIdentifier]] // SparseSet for EntityIdentifier
|
||||||
var familyContainsEntityId: [TraitEntityIdHash: Bool]
|
var familyContainsEntityId: [TraitEntityIdHash: Bool]
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
entities = Entities()
|
entityStorage = Entities()
|
||||||
componentsByType = [:]
|
componentsByType = [:]
|
||||||
componentIdsByEntity = [:]
|
componentIdsByEntity = [:]
|
||||||
componentIdsByEntityLookup = [:]
|
componentIdsByEntityLookup = [:]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// SparseSet.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 30.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
public struct SparseComponentSet<Element: Component> {
|
||||||
|
fileprivate typealias ComponentIdx = Int
|
||||||
|
fileprivate typealias EntryTuple = (entityId: EntityIdentifier, component: Element)
|
||||||
|
fileprivate var dense: ContiguousArray<EntryTuple?>
|
||||||
|
fileprivate var sparse: [EntityIdentifier: ComponentIdx]
|
||||||
|
|
||||||
|
public init(_ min: Int = 1024) {
|
||||||
|
dense = ContiguousArray<EntryTuple?>()
|
||||||
|
dense.reserveCapacity(min)
|
||||||
|
|
||||||
|
sparse = [EntityIdentifier: ComponentIdx](minimumCapacity: min)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var count: Int { return dense.count }
|
||||||
|
internal var capacitySparse: Int { return sparse.count }
|
||||||
|
internal var capacityDense: Int { return dense.count }
|
||||||
|
|
||||||
|
public func contains(_ entityId: EntityIdentifier) -> Bool {
|
||||||
|
guard let compIdx: ComponentIdx = sparse[entityId] else { return false }
|
||||||
|
return compIdx < count && dense[compIdx] != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
public mutating func add(_ element: Element, with entityId: EntityIdentifier) -> Bool {
|
||||||
|
if contains(entityId) { return false }
|
||||||
|
sparse[entityId] = count
|
||||||
|
let entry: EntryTuple = EntryTuple(entityId: entityId, component: element)
|
||||||
|
dense.append(entry)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public func get(_ entityId: EntityIdentifier) -> Element? {
|
||||||
|
guard let compIdx: ComponentIdx = sparse[entityId] else { return nil }
|
||||||
|
return dense[compIdx]?.component
|
||||||
|
}
|
||||||
|
|
||||||
|
public mutating func remove(_ entityId: EntityIdentifier) -> Element? {
|
||||||
|
guard let compIdx: ComponentIdx = sparse[entityId] else { return nil }
|
||||||
|
dense.swapAt(compIdx, count-1)
|
||||||
|
sparse[entityId] = nil
|
||||||
|
let swapped: EntryTuple = dense[compIdx]!
|
||||||
|
sparse[swapped.entityId] = compIdx
|
||||||
|
let removed: EntryTuple = dense.popLast()!!
|
||||||
|
return removed.component
|
||||||
|
}
|
||||||
|
|
||||||
|
public mutating func clear(keepingCapacity: Bool = false) {
|
||||||
|
dense.removeAll(keepingCapacity: keepingCapacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SparseComponentSet: Sequence {
|
||||||
|
|
||||||
|
public func makeIterator() -> AnyIterator<Element> {
|
||||||
|
var iterator = dense.makeIterator()
|
||||||
|
|
||||||
|
return AnyIterator<Element> {
|
||||||
|
iterator.next()??.component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,23 @@
|
||||||
import CSDL2
|
import CSDL2
|
||||||
import FirebladeECS
|
import FirebladeECS
|
||||||
|
var tFrame = Timer()
|
||||||
var tSetup = Timer()
|
var tSetup = Timer()
|
||||||
tSetup.start()
|
tSetup.start()
|
||||||
if SDL_Init(SDL_INIT_VIDEO) != 0 {
|
if SDL_Init(SDL_INIT_VIDEO) != 0 {
|
||||||
fatalError("could not init video")
|
fatalError("could not init video")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var frameCount: UInt = 0
|
||||||
|
var fps: Double = 0
|
||||||
|
let nexus = Nexus()
|
||||||
|
|
||||||
|
var windowTitle: String {
|
||||||
|
return "Fireblade ECS demo: [entities: \(nexus.numEntities), components: \(nexus.numComponents)] @ [FPS: \(fps), frames: \(frameCount)]"
|
||||||
|
}
|
||||||
let width: Int32 = 640
|
let width: Int32 = 640
|
||||||
let height: Int32 = 480
|
let height: Int32 = 480
|
||||||
let hWin = SDL_CreateWindow("Fireblade ECS demo", 100, 100, width, height, SDL_WINDOW_SHOWN.rawValue)
|
let hWin = SDL_CreateWindow(windowTitle, 100, 100, width, height, SDL_WINDOW_SHOWN.rawValue)
|
||||||
|
|
||||||
if hWin == nil {
|
if hWin == nil {
|
||||||
SDL_Quit()
|
SDL_Quit()
|
||||||
fatalError("could not crate window")
|
fatalError("could not crate window")
|
||||||
|
|
@ -22,8 +32,6 @@ func randColor() -> UInt8 {
|
||||||
return UInt8(randNorm() * 254) + 1
|
return UInt8(randNorm() * 254) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
let nexus = Nexus()
|
|
||||||
|
|
||||||
class Position: Component {
|
class Position: Component {
|
||||||
var x: Int32 = width/2
|
var x: Int32 = width/2
|
||||||
var y: Int32 = height/2
|
var y: Int32 = height/2
|
||||||
|
|
@ -39,20 +47,43 @@ func createScene() {
|
||||||
let numEntities: Int = 10_000
|
let numEntities: Int = 10_000
|
||||||
|
|
||||||
for i in 0..<numEntities {
|
for i in 0..<numEntities {
|
||||||
let e = nexus.create(entity: "\(i)")
|
createDefaultEntity(name: "\(i)")
|
||||||
e.assign(Position())
|
|
||||||
e.assign(Color())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func batchCreateEntities(count: Int) {
|
||||||
|
for _ in 0..<count {
|
||||||
|
createDefaultEntity(name: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func batchDestroyEntities(count: Int) {
|
||||||
|
|
||||||
|
var i = count
|
||||||
|
for entity in nexus.entities {
|
||||||
|
if nexus.destroy(entity: entity) {
|
||||||
|
i -= 1
|
||||||
|
if i == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDefaultEntity(name: String?) {
|
||||||
|
let e = nexus.create(entity: name)
|
||||||
|
e.assign(Position())
|
||||||
|
e.assign(Color())
|
||||||
|
}
|
||||||
|
|
||||||
class PositionSystem {
|
class PositionSystem {
|
||||||
let family = nexus.family(requiresAll: [Position.self], excludesAll: [])
|
let family = nexus.family(requiresAll: [Position.self], excludesAll: [])
|
||||||
var acceleration: Double = 4.0
|
var velocity: Double = 4.0
|
||||||
func update() {
|
func update() {
|
||||||
family.iterate(components: Position.self) { [unowned self](_, pos) in
|
family.iterate(components: Position.self) { [unowned self](_, pos) in
|
||||||
|
|
||||||
let deltaX: Double = self.acceleration*((randNorm() * 2) - 1)
|
let deltaX: Double = self.velocity*((randNorm() * 2) - 1)
|
||||||
let deltaY: Double = self.acceleration*((randNorm() * 2) - 1)
|
let deltaY: Double = self.velocity*((randNorm() * 2) - 1)
|
||||||
var x = pos!.x + Int32(deltaX)
|
var x = pos!.x + Int32(deltaX)
|
||||||
var y = pos!.y + Int32(deltaY)
|
var y = pos!.y + Int32(deltaY)
|
||||||
|
|
||||||
|
|
@ -142,6 +173,10 @@ func printHelp() {
|
||||||
+ increase movement speed
|
+ increase movement speed
|
||||||
- reduce movement speed
|
- reduce movement speed
|
||||||
space reset to default movement speed
|
space reset to default movement speed
|
||||||
|
e create 1 entity
|
||||||
|
d destroy 1 entity
|
||||||
|
8 batch create 10k entities
|
||||||
|
9 batch destroy 10k entities
|
||||||
"""
|
"""
|
||||||
print(help)
|
print(help)
|
||||||
}
|
}
|
||||||
|
|
@ -155,8 +190,12 @@ printHelp()
|
||||||
tRun.start()
|
tRun.start()
|
||||||
var event: SDL_Event = SDL_Event()
|
var event: SDL_Event = SDL_Event()
|
||||||
var quit: Bool = false
|
var quit: Bool = false
|
||||||
|
var currentTime: UInt32 = 0
|
||||||
|
var lastTime: UInt32 = 0
|
||||||
|
var frameTimes: [UInt64] = []
|
||||||
print("================ RUNNING ================")
|
print("================ RUNNING ================")
|
||||||
while quit == false {
|
while quit == false {
|
||||||
|
tFrame.start()
|
||||||
while SDL_PollEvent(&event) == 1 {
|
while SDL_PollEvent(&event) == 1 {
|
||||||
switch SDL_EventType(rawValue: event.type) {
|
switch SDL_EventType(rawValue: event.type) {
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
|
|
@ -172,13 +211,21 @@ while quit == false {
|
||||||
case SDLK_r:
|
case SDLK_r:
|
||||||
positionResetSystem.update()
|
positionResetSystem.update()
|
||||||
case SDLK_s:
|
case SDLK_s:
|
||||||
positionSystem.acceleration = 0.0
|
positionSystem.velocity = 0.0
|
||||||
case SDLK_PLUS:
|
case SDLK_PLUS:
|
||||||
positionSystem.acceleration += 0.1
|
positionSystem.velocity += 0.1
|
||||||
case SDLK_MINUS:
|
case SDLK_MINUS:
|
||||||
positionSystem.acceleration -= 0.1
|
positionSystem.velocity -= 0.1
|
||||||
case SDLK_SPACE:
|
case SDLK_SPACE:
|
||||||
positionSystem.acceleration = 4.0
|
positionSystem.velocity = 4.0
|
||||||
|
case SDLK_e:
|
||||||
|
batchCreateEntities(count: 1)
|
||||||
|
case SDLK_d:
|
||||||
|
batchDestroyEntities(count: 1)
|
||||||
|
case SDLK_8:
|
||||||
|
batchCreateEntities(count: 10_000)
|
||||||
|
case SDLK_9:
|
||||||
|
batchDestroyEntities(count: 10_000)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -190,6 +237,28 @@ while quit == false {
|
||||||
positionSystem.update()
|
positionSystem.update()
|
||||||
|
|
||||||
renderSystem.render()
|
renderSystem.render()
|
||||||
|
tFrame.stop()
|
||||||
|
|
||||||
|
frameTimes.append(tFrame.nanoSeconds)
|
||||||
|
|
||||||
|
// Print a report once per second
|
||||||
|
currentTime = SDL_GetTicks()
|
||||||
|
if (currentTime > lastTime + 1000) {
|
||||||
|
|
||||||
|
let count = UInt(frameTimes.count)
|
||||||
|
frameCount += count
|
||||||
|
let sum: UInt64 = frameTimes.reduce(0, { $0 + $1 })
|
||||||
|
frameTimes.removeAll(keepingCapacity: true)
|
||||||
|
|
||||||
|
let avergageNanos: Double = Double(sum)/Double(count)
|
||||||
|
|
||||||
|
fps = 1.0 / (avergageNanos * 1.0e-9)
|
||||||
|
fps.round()
|
||||||
|
|
||||||
|
SDL_SetWindowTitle(hWin, windowTitle)
|
||||||
|
lastTime = currentTime
|
||||||
|
}
|
||||||
|
tFrame.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_DestroyWindow(hWin)
|
SDL_DestroyWindow(hWin)
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class FamilyTests: XCTestCase {
|
||||||
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||||
|
|
||||||
XCTAssert(family.count == number)
|
XCTAssert(family.count == number)
|
||||||
XCTAssert(nexus.count == number)
|
XCTAssert(nexus.numEntities == number)
|
||||||
|
|
||||||
measure {
|
measure {
|
||||||
family.iterateMembers({ (entityId) in
|
family.iterateMembers({ (entityId) in
|
||||||
|
|
@ -94,7 +94,7 @@ class FamilyTests: XCTestCase {
|
||||||
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||||
|
|
||||||
XCTAssert(family.count == number)
|
XCTAssert(family.count == number)
|
||||||
XCTAssert(nexus.count == number)
|
XCTAssert(nexus.numEntities == number)
|
||||||
|
|
||||||
measure {
|
measure {
|
||||||
family.iterate(components: Velocity.self) { (_, vel) in
|
family.iterate(components: Velocity.self) { (_, vel) in
|
||||||
|
|
@ -115,7 +115,7 @@ class FamilyTests: XCTestCase {
|
||||||
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
let family = nexus.family(requiresAll: [Position.self, Velocity.self], excludesAll: [Party.self], needsAtLeastOne: [Name.self, EmptyComponent.self])
|
||||||
|
|
||||||
XCTAssert(family.count == number)
|
XCTAssert(family.count == number)
|
||||||
XCTAssert(nexus.count == number)
|
XCTAssert(nexus.numEntities == number)
|
||||||
|
|
||||||
measure {
|
measure {
|
||||||
family.iterate(components: Position.self, Velocity.self, Name.self) { (entityId, pos, vel, nm) in
|
family.iterate(components: Position.self, Velocity.self, Name.self) { (entityId, pos, vel, nm) in
|
||||||
|
|
|
||||||
|
|
@ -21,17 +21,17 @@ class NexusTests: XCTestCase {
|
||||||
|
|
||||||
func testCreateEntity() {
|
func testCreateEntity() {
|
||||||
let nexus: Nexus = Nexus()
|
let nexus: Nexus = Nexus()
|
||||||
XCTAssert(nexus.count == 0)
|
XCTAssert(nexus.numEntities == 0)
|
||||||
|
|
||||||
let e0 = nexus.create()
|
let e0 = nexus.create()
|
||||||
XCTAssert(e0.identifier.index == 0)
|
XCTAssert(e0.identifier.index == 0)
|
||||||
XCTAssert(e0.isValid)
|
XCTAssert(e0.isValid)
|
||||||
XCTAssert(nexus.count == 1)
|
XCTAssert(nexus.numEntities == 1)
|
||||||
|
|
||||||
let e1 = nexus.create(entity: "Named e1")
|
let e1 = nexus.create(entity: "Named e1")
|
||||||
XCTAssert(e1.identifier.index == 1)
|
XCTAssert(e1.identifier.index == 1)
|
||||||
XCTAssert(e1.isValid)
|
XCTAssert(e1.isValid)
|
||||||
XCTAssert(nexus.count == 2)
|
XCTAssert(nexus.numEntities == 2)
|
||||||
|
|
||||||
XCTAssert(e0.name == nil)
|
XCTAssert(e0.name == nil)
|
||||||
XCTAssert(e1.name == "Named e1")
|
XCTAssert(e1.name == "Named e1")
|
||||||
|
|
@ -43,28 +43,28 @@ class NexusTests: XCTestCase {
|
||||||
|
|
||||||
func testDestroyAndReuseEntity() {
|
func testDestroyAndReuseEntity() {
|
||||||
let nexus: Nexus = Nexus()
|
let nexus: Nexus = Nexus()
|
||||||
XCTAssert(nexus.count == 0)
|
XCTAssert(nexus.numEntities == 0)
|
||||||
|
|
||||||
let e0 = nexus.create(entity: "e0")
|
let e0 = nexus.create(entity: "e0")
|
||||||
XCTAssert(e0.isValid)
|
XCTAssert(e0.isValid)
|
||||||
XCTAssert(nexus.count == 1)
|
XCTAssert(nexus.numEntities == 1)
|
||||||
|
|
||||||
let e1 = nexus.create(entity: "e1")
|
let e1 = nexus.create(entity: "e1")
|
||||||
XCTAssert(e1.isValid)
|
XCTAssert(e1.isValid)
|
||||||
XCTAssert(nexus.count == 2)
|
XCTAssert(nexus.numEntities == 2)
|
||||||
|
|
||||||
e0.destroy()
|
e0.destroy()
|
||||||
|
|
||||||
XCTAssert(!e0.isValid)
|
XCTAssert(!e0.isValid)
|
||||||
XCTAssert(e1.isValid)
|
XCTAssert(e1.isValid)
|
||||||
XCTAssert(nexus.count == 1)
|
XCTAssert(nexus.numEntities == 1)
|
||||||
|
|
||||||
let e2 = nexus.create(entity: "e2")
|
let e2 = nexus.create(entity: "e2")
|
||||||
XCTAssert(!e0.isValid)
|
XCTAssert(!e0.isValid)
|
||||||
XCTAssert(e1.isValid)
|
XCTAssert(e1.isValid)
|
||||||
XCTAssert(e2.isValid)
|
XCTAssert(e2.isValid)
|
||||||
|
|
||||||
XCTAssert(nexus.count == 2)
|
XCTAssert(nexus.numEntities == 2)
|
||||||
|
|
||||||
XCTAssert(!(e0 == e2))
|
XCTAssert(!(e0 == e2))
|
||||||
XCTAssert(!(e0 === e2))
|
XCTAssert(!(e0 === e2))
|
||||||
|
|
@ -72,7 +72,7 @@ class NexusTests: XCTestCase {
|
||||||
|
|
||||||
func testComponentCreation() {
|
func testComponentCreation() {
|
||||||
let nexus: Nexus = Nexus()
|
let nexus: Nexus = Nexus()
|
||||||
XCTAssert(nexus.count == 0)
|
XCTAssert(nexus.numEntities == 0)
|
||||||
|
|
||||||
let e0: Entity = nexus.create(entity: "e0")
|
let e0: Entity = nexus.create(entity: "e0")
|
||||||
|
|
||||||
|
|
@ -145,7 +145,7 @@ class NexusTests: XCTestCase {
|
||||||
let b = nexus.create()
|
let b = nexus.create()
|
||||||
let c = nexus.create()
|
let c = nexus.create()
|
||||||
|
|
||||||
XCTAssert(nexus.count == 3)
|
XCTAssert(nexus.numEntities == 3)
|
||||||
|
|
||||||
a.assign(Position(x: 0, y: 0))
|
a.assign(Position(x: 0, y: 0))
|
||||||
b.assign(Position(x: 0, y: 0))
|
b.assign(Position(x: 0, y: 0))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// SparseComponentSetTests.swift
|
||||||
|
// FirebladeECSTests
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 31.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
import FirebladeECS
|
||||||
|
|
||||||
|
class SparseComponentSetTests: XCTestCase {
|
||||||
|
|
||||||
|
func testSet() {
|
||||||
|
var s = SparseComponentSet<Position>()
|
||||||
|
|
||||||
|
s.add(Position(x: 1, y: 2), with: 0)
|
||||||
|
s.add(Position(x: 13, y: 23), with: 13)
|
||||||
|
s.add(Position(x: 123, y: 123), with: 123)
|
||||||
|
|
||||||
|
for p in s {
|
||||||
|
print(p.x, p.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.remove(13)
|
||||||
|
|
||||||
|
for p in s {
|
||||||
|
print(p.x, p.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.remove(234567890)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue