Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Sources/ECS/Commons/FeatureFlags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

struct FeatureFlags: OptionSet {
let rawValue: UInt8
static let enabled: FeatureFlags = []

static let contiguousArrayStorage = FeatureFlags(rawValue: 1 << 0)

static var enabled: FeatureFlags = [
.contiguousArrayStorage
]

static func isEnabled(_ flags: FeatureFlags) -> Bool {
Self.enabled.contains(flags)
Expand Down
56 changes: 56 additions & 0 deletions Sources/ECS/Commons/SparseSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,59 @@ public struct SparseSet<T> {
return self.dense.indices.contains(i)
}
}

public struct ContiguousSparseSet<T> {
typealias DenseIndex = Int

var sparse: [DenseIndex?]
var dense: [Entity]
var data: ContiguousArray<T>

public func value(forEntity entity: Entity) -> T? {
guard let i = self.sparse[entity.slot] else { return nil }
guard self.dense[i] == entity else { return nil }
return self.data[i]
}

public mutating func update(forEntity entity: Entity, _ execute: (inout T) -> ()) {
guard let i = self.sparse[entity.slot] else { return }
guard self.dense[i].generation == entity.generation else { return }
execute(&self.data[i])
}

public mutating func update(_ execute: (inout T) -> ()) {
for i in self.data.indices {
execute(&self.data[i])
}
}

public mutating func allocate() {
self.sparse.append(nil)
}

public mutating func insert(_ value: T, withEntity entity: Entity) {
let denseIndex = self.dense.count
self.sparse[entity.slot] = denseIndex
self.dense.append(entity)
self.data.append(value)
}

public mutating func pop(entity: Entity) {
assert(entity.generation == self.dense[self.sparse[entity.slot]!].generation)
let denseIndexLast = self.dense.count-1
let removeIndex = self.sparse[entity.slot]!

self.sparse[self.dense[denseIndexLast].slot] = removeIndex
self.sparse[self.dense[removeIndex].slot] = nil
self.dense.swapAt(removeIndex, denseIndexLast)
self.data.swapAt(removeIndex, denseIndexLast)
self.data.removeLast()
self.dense.removeLast()
}

public func contains(_ entity: Entity) -> Bool {
guard self.sparse.indices.contains(entity.slot) else { return false }
guard let i = self.sparse[entity.slot] else { return false }
return self.dense.indices.contains(i)
}
}
71 changes: 56 additions & 15 deletions Sources/ECS/Query/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,44 @@

final public class Query<C: QueryTarget>: Chunk, SystemParameter {
var components = SparseSet<Ref<C>>(sparse: [], dense: [], data: [])
var contiguousComponents = ContiguousSparseSet<Ref<C>>(sparse: [], dense: [], data: [])

public override init() {}

public func allocate() {
self.components.allocate()
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousComponents.allocate()
} else {
self.components.allocate()
}
}

public func insert(entityRecord: EntityRecordRef) {
guard let componentRef = entityRecord.ref(C.self) else { return }
self.components.insert(componentRef, withEntity: entityRecord.entity)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousComponents.insert(componentRef, withEntity: entityRecord.entity)
} else {
self.components.insert(componentRef, withEntity: entityRecord.entity)
}
}

public func remove(entity: Entity) {
guard self.components.contains(entity) else { return }
self.components.pop(entity: entity)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
guard self.contiguousComponents.contains(entity) else { return }
self.contiguousComponents.pop(entity: entity)
} else {
guard self.components.contains(entity) else { return }
self.components.pop(entity: entity)
}
}

override func spawn(entityRecord: EntityRecordRef) {
if entityRecord.entity.generation == 0 {
self.components.allocate()
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousComponents.allocate()
} else {
self.components.allocate()
}
}
self.insert(entityRecord: entityRecord)
}
Expand All @@ -40,27 +58,50 @@ final public class Query<C: QueryTarget>: Chunk, SystemParameter {
self.despawn(entity: entityRecord.entity)
return
}
guard !components.contains(entityRecord.entity) else { return }
self.components.insert(
componentRef,
withEntity: entityRecord.entity
)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
guard !contiguousComponents.contains(entityRecord.entity) else { return }
self.contiguousComponents.insert(
componentRef,
withEntity: entityRecord.entity
)
} else {
guard !components.contains(entityRecord.entity) else { return }
self.components.insert(
componentRef,
withEntity: entityRecord.entity
)
}
}

/// Query で指定した Component を持つ entity を world から取得し, イテレーションします.
public func update(_ f: (inout C) -> ()) {
for ref in self.components.data {
f(&ref.value)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
for ref in self.contiguousComponents.data {
f(&ref.value)
}
} else {
for ref in self.components.data {
f(&ref.value)
}
}
}

public func update(_ entity: Entity, _ f: (inout C) -> ()) {
guard let ref = self.components.value(forEntity: entity) else { return }
f(&ref.value)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
guard let ref = self.contiguousComponents.value(forEntity: entity) else { return }
f(&ref.value)
} else {
guard let ref = self.components.value(forEntity: entity) else { return }
f(&ref.value)
}
}

public func components(forEntity entity: Entity) -> C? {
self.components.value(forEntity: entity)?.value
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousComponents.value(forEntity: entity)?.value
} else {
self.components.value(forEntity: entity)?.value
}
}

public static func register(to worldStorage: WorldStorageRef) {
Expand Down
21 changes: 17 additions & 4 deletions Sources/ECS/World/World+Entities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,29 @@

public extension World {
func insert(entityRecord: EntityRecordRef) {
self.entities.insert(entityRecord, withEntity: entityRecord.entity)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousEntities.insert(entityRecord, withEntity: entityRecord.entity)
} else {
self.defaultEntities.insert(entityRecord, withEntity: entityRecord.entity)
}
}

func remove(entity: Entity) {
guard self.entities.contains(entity) else { return }
self.entities.pop(entity: entity)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
guard self.contiguousEntities.contains(entity) else { return }
self.contiguousEntities.pop(entity: entity)
} else {
guard self.defaultEntities.contains(entity) else { return }
self.defaultEntities.pop(entity: entity)
}
}

func entityRecord(forEntity entity: Entity) -> EntityRecordRef? {
self.entities.value(forEntity: entity)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousEntities.value(forEntity: entity)
} else {
self.defaultEntities.value(forEntity: entity)
}
}

}
6 changes: 4 additions & 2 deletions Sources/ECS/World/World.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,16 @@

*/
final public class World {
var entities: SparseSet<EntityRecordRef>
var defaultEntities: SparseSet<EntityRecordRef>
var contiguousEntities: ContiguousSparseSet<EntityRecordRef>
var preUpdateSchedule: Schedule
var updateSchedule: Schedule
var postUpdateSchedule: Schedule
public let worldStorage: WorldStorageRef

init(worldStorage: WorldStorageRef) {
self.entities = SparseSet(sparse: [], dense: [], data: [])
self.defaultEntities = SparseSet(sparse: [], dense: [], data: [])
self.contiguousEntities = ContiguousSparseSet(sparse: [], dense: [], data: [])
self.preUpdateSchedule = .preStartUp
self.updateSchedule = .startUp
self.postUpdateSchedule = .postStartUp
Expand Down
6 changes: 5 additions & 1 deletion Sources/ECS/WorldMethods/World+Spawn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ extension World {
/// entity へのコンポーネントの登録などは, push の後に行われます.
func push(entityRecord: EntityRecordRef) {
if entityRecord.entity.generation == 0 {
self.entities.allocate()
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousEntities.allocate()
} else {
self.defaultEntities.allocate()
}
}

self.insert(entityRecord: entityRecord)
Expand Down
65 changes: 52 additions & 13 deletions Sources/ECS_Macros/QueryMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,26 +50,44 @@ struct QueryMacro: DeclarationMacro {
"""
final public class Query\(raw: n)<\(raw: genericArguments)>: Chunk, SystemParameter, QueryProtocol {
public var components = SparseSet<(\(raw: refTypes))>(sparse: [], dense: [], data: [])
public var contiguousComponents = ContiguousSparseSet<(\(raw: refTypes))>(sparse: [], dense: [], data: [])

public override init() {}

public func allocate() {
self.components.allocate()
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousComponents.allocate()
} else {
self.components.allocate()
}
}

public func insert(entityRecord: EntityRecordRef) {
guard \(raw: refDeclarationsFromRecord) else { return }
self.components.insert((\(raw: refs)), withEntity: entityRecord.entity)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousComponents.insert((\(raw: refs)), withEntity: entityRecord.entity)
} else {
self.components.insert((\(raw: refs)), withEntity: entityRecord.entity)
}
}

public func remove(entity: Entity) {
guard self.components.contains(entity) else { return }
self.components.pop(entity: entity)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
guard self.contiguousComponents.contains(entity) else { return }
self.contiguousComponents.pop(entity: entity)
} else {
guard self.components.contains(entity) else { return }
self.components.pop(entity: entity)
}
}

public override func spawn(entityRecord: EntityRecordRef) {
if entityRecord.entity.generation == 0 {
self.components.allocate()
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousComponents.allocate()
} else {
self.components.allocate()
}
}
self.insert(entityRecord: entityRecord)
}
Expand All @@ -83,24 +101,45 @@ struct QueryMacro: DeclarationMacro {
self.despawn(entity: entityRecord.entity)
return
}
guard !components.contains(entityRecord.entity) else { return }
self.components.insert((\(raw: refs)), withEntity: entityRecord.entity)
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
guard !contiguousComponents.contains(entityRecord.entity) else { return }
self.contiguousComponents.insert((\(raw: refs)), withEntity: entityRecord.entity)
} else {
guard !components.contains(entityRecord.entity) else { return }
self.components.insert((\(raw: refs)), withEntity: entityRecord.entity)
}
}

public func update(_ f: (\(raw: parameters)) -> ()) {
self.components.data.forEach { components in
f(\(raw: componentRefs))
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
self.contiguousComponents.data.forEach { components in
f(\(raw: componentRefs))
}
} else {
self.components.data.forEach { components in
f(\(raw: componentRefs))
}
}
}

public func update(_ entity: Entity, _ f: (\(raw: parameters)) -> ()) {
guard let components = self.components.value(forEntity: entity) else { return }
f(\(raw: componentRefs))
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
guard let components = self.contiguousComponents.value(forEntity: entity) else { return }
f(\(raw: componentRefs))
} else {
guard let components = self.components.value(forEntity: entity) else { return }
f(\(raw: componentRefs))
}
}

public func components(forEntity entity: Entity) -> (\(raw: valueTypes))? {
guard let components = components.value(forEntity: entity) else { return nil }
return (\(raw: componentValuess))
if FeatureFlags.isEnabled(.contiguousArrayStorage) {
guard let components = contiguousComponents.value(forEntity: entity) else { return nil }
return (\(raw: componentValuess))
} else {
guard let components = components.value(forEntity: entity) else { return nil }
return (\(raw: componentValuess))
}
}

public static func register(to worldStorage: WorldStorageRef) {
Expand Down
4 changes: 2 additions & 2 deletions Tests/ecs-swiftTests/CommandsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ final class CommandsTests: XCTestCase {
world.applyCommands(commands: commands)

XCTAssertEqual(commands.commandQueue.count, 0)
XCTAssertEqual(world.entities.data.count, 3)
XCTAssertEqual(world.defaultEntities.data.count, 3)

for testEntity in testEntities {
commands.push(command: TestCommand_Despawn(entity: testEntity))
Expand All @@ -53,6 +53,6 @@ final class CommandsTests: XCTestCase {
world.applyCommands(commands: commands)

XCTAssertEqual(commands.commandQueue.count, 0)
XCTAssertEqual(world.entities.data.count, 0)
XCTAssertEqual(world.defaultEntities.data.count, 0)
}
}
Loading
Loading