Skip to content
Merged
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
6 changes: 4 additions & 2 deletions Sources/ECS/Chunk/Chunk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
//

public class Chunk {
func spawn(entity: Entity, entityRecord: EntityRecordRef) {}
/// World に追加された
func spawn(entityRecord: EntityRecordRef) {}
func despawn(entity: Entity) {}
func applyCurrentState(_ entityRecord: EntityRecordRef, forEntity entity: Entity) {}

func applyCurrentState(_ entityRecord: EntityRecordRef) {}
}
25 changes: 14 additions & 11 deletions Sources/ECS/Chunk/ChunkEntityInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class ChunkEntityInterface: WorldStorageElement {
/// entity が spawn されてから component が完全に挿入されるまでの間, entity を queue に保管します.
///
/// Entity が ``Commands/spawn()`` され, ``EntityCommands/addComponent(_:)`` されるまでの間, Entity は実際には Chunk に反映されず,
var prespawnedEntityQueue = [(Entity, EntityRecordRef)]()
var updatedEntityQueue = [(Entity, EntityRecordRef)]()
var prespawnedEntityQueue = [EntityRecordRef]()
var updatedEntityQueue = [EntityRecordRef]()
var chunks = [Chunk]()

/// chunk を追加します
Expand All @@ -24,39 +24,42 @@ class ChunkEntityInterface: WorldStorageElement {
/// World に entity が追加された時に実行します.
///
/// entity が queue に追加され、フレームの終わりに全ての chunk に entity を反映します.
func pushSpawned(entity: Entity, entityRecord: EntityRecordRef) {
self.prespawnedEntityQueue.append((entity, entityRecord))
func pushSpawned(entityRecord: EntityRecordRef) {
self.prespawnedEntityQueue.append(entityRecord)
}

/// Spawn 処理された entity を, 実際に chunk に追加します.
///
/// Component が完全に追加された後にこの処理を呼び出すことで, Entity の Component の有無が Chunk に反映されるようになります.
func applySpawnedEntityQueue() {
for (entity, entityRecord) in self.prespawnedEntityQueue {
for entityRecord in self.prespawnedEntityQueue {
for chunk in self.chunks {
chunk.spawn(entity: entity, entityRecord: entityRecord)
chunk.spawn(entityRecord: entityRecord)
}
}
self.prespawnedEntityQueue = []
}

/// World から entity が削除される時に実行します.
///
/// フレームの終わりに全ての chunk から entity を削除します.
/// フレームの終わりに全ての chunk から entity を削除します. ← これ嘘では
func despawn(entity: Entity) {
for chunk in self.chunks {
chunk.despawn(entity: entity)
}
}

func pushUpdated(entity: Entity, entityRecord: EntityRecordRef) {
self.updatedEntityQueue.append((entity, entityRecord))
/// World 内にすでに存在している entity の component を追加/削除する制御
///
/// ``Commands/entity(_:)`` で検索された entity のみが対象です.
func pushUpdated(entityRecord: EntityRecordRef) {
self.updatedEntityQueue.append(entityRecord)
}

func applyUpdatedEntityQueue() {
for (entity, entityRecord) in self.updatedEntityQueue {
for entityRecord in self.updatedEntityQueue {
for chunk in self.chunks {
chunk.applyCurrentState(entityRecord, forEntity: entity)
chunk.applyCurrentState(entityRecord)
}
}
self.updatedEntityQueue = []
Expand Down
8 changes: 4 additions & 4 deletions Sources/ECS/Chunk/ChunkStorage+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ extension ChunkStorage {
self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.add(chunk: chunk)
}

func pushSpawned(entity: Entity, entityRecord: EntityRecordRef) {
self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.pushSpawned(entity: entity, entityRecord: entityRecord)
func pushSpawned(entityRecord: EntityRecordRef) {
self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.pushSpawned(entityRecord: entityRecord)
}

func applySpawnedEntityQueue() {
self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.applySpawnedEntityQueue()
}

public func pushUpdated(entity: Entity, entityRecord: EntityRecordRef) {
self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.pushUpdated(entity: entity, entityRecord: entityRecord)
public func pushUpdated(entityRecord: EntityRecordRef) {
self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.pushUpdated(entityRecord: entityRecord)
}

func applyUpdatedEntityQueue() {
Expand Down
12 changes: 12 additions & 0 deletions Sources/ECS/Commands/Commands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,15 @@ final public class Commands: SystemParameter {
self.commandQueue.append(command)
}
}

// MARK: - life cycle

extension Commands {
func refreshEntityTransactions() {
entityTransactions = []
}

func refreshCommandQueue() {
commandQueue = []
}
}
4 changes: 2 additions & 2 deletions Sources/ECS/Commands/World+Commands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ extension World {
for transaction in commands.entityTransactions {
transaction.runCommand(in: self)
}
commands.entityTransactions = []
commands.refreshEntityTransactions()
}

func applyCommands(commands: Commands) {
for command in commands.commandQueue {
command.runCommand(in: self)
}
commands.commandQueue = []
commands.refreshCommandQueue()
}
}
16 changes: 16 additions & 0 deletions Sources/ECS/Commons/EntityRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,23 @@ final public class ImmutableRef<T>: Ref<T> {
}

final public class EntityRecordRef {
let entity: Entity
var map = AnyMap<EntityRecord>()

init(entity: Entity) {
self.entity = entity
}
}

extension EntityRecordRef: Hashable {
public static func == (lhs: EntityRecordRef, rhs: EntityRecordRef) -> Bool {
lhs.entity == rhs.entity
}

public func hash(into hasher: inout Hasher) {
hasher.combine(entity)
}

}

public extension EntityRecordRef {
Expand Down
2 changes: 1 addition & 1 deletion Sources/ECS/Commons/SparseSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public struct SparseSet<T> {
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])
Expand Down
4 changes: 2 additions & 2 deletions Sources/ECS/EntityCommands/Commands+EntityCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ public extension Commands {
/// Entity を追加して変更を加えます.
@discardableResult func spawn() -> SpawnedEntityCommands {
let entity = self.generator.generate()
let record = EntityRecordRef()
let record = EntityRecordRef(entity: entity)

record.map.body[ObjectIdentifier(Entity.self)] = ImmutableRef(value: entity)

self.generator.pop()

self.entityTransactions.append(SpawnCommand(id: entity, entityRecord: record))
self.entityTransactions.append(SpawnCommand(entityRecord: record))
let queue = SpawnedEntityCommandQueue(record: record)
self.entityTransactions.append(queue)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class SearchedEntityCommandQueue: EntityCommandQueue {

override func runCommand(in world: World) {
guard let record = world.entityRecord(forEntity: self.entity) else { return }
world.worldStorage.chunkStorage.pushUpdated(entity: self.entity, entityRecord: record)
world.worldStorage.chunkStorage.pushUpdated(entityRecord: record)
self.queue.forEach { command in
command.runCommand(forRecord: record, inWorld: world)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
//

class SpawnCommand: EntityTransaction {
let entity: Entity
let entityRecord: EntityRecordRef

init(id: Entity, entityRecord: EntityRecordRef) {
self.entity = id
init(entityRecord: EntityRecordRef) {
self.entityRecord = entityRecord
}

override func runCommand(in world: World) {
world.push(entity: self.entity, entityRecord: self.entityRecord)
world.push(entityRecord: self.entityRecord)
}
}
2 changes: 1 addition & 1 deletion Sources/ECS/Event/EventStreaming/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class Event<T: EventProtocol>: AnyEvent {
}
}

for schedule in worldStorage.stateStorage.currentSchedulesWhichAssociatedStates() {
for schedule in worldStorage.stateStorage.currentEventSchedulesWhichAssociatedStates() {
guard let systems = worldStorage.eventStorage.eventResponder(eventOfType: T.self)!.systems[schedule] else { continue }
for system in systems {
system.execute(worldStorage)
Expand Down
4 changes: 2 additions & 2 deletions Sources/ECS/Event/EventStreaming/EventResponder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import Foundation

final public class EventResponderBuilder {
unowned let worldStorage: WorldStorageRef
var systems: [Schedule: [SystemExecute]] = [:]
var systems: [EventSchedule: [SystemExecute]] = [:]

init(worldStorage: WorldStorageRef) {
self.worldStorage = worldStorage
}
}

final public class EventResponder<T>: WorldStorageElement {
var systems: [Schedule: [SystemExecute]] = [:]
var systems: [EventSchedule: [SystemExecute]] = [:]
}

public extension World {
Expand Down
2 changes: 1 addition & 1 deletion Sources/ECS/Event/World+EventStreamer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ extension World {
}
}

for schedule in self.worldStorage.stateStorage.currentSchedulesWhichAssociatedStates() {
for schedule in self.worldStorage.stateStorage.currentEventSchedulesWhichAssociatedStates() {
guard let systems = eventStorage.commandsEventResponder(eventOfType: T.self)!.systems[schedule] else { continue }
for system in systems {
system.execute(self.worldStorage)
Expand Down
12 changes: 6 additions & 6 deletions Sources/ECS/FilterdQuery/FIlteredQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
final public class Filtered<Q: QueryProtocol, F: Filter>: Chunk, SystemParameter {
let query: Q = Q()

override func spawn(entity: Entity, entityRecord: EntityRecordRef) {
if entity.generation == 0 {
override func spawn(entityRecord: EntityRecordRef) {
if entityRecord.entity.generation == 0 {
self.query.allocate()
}
guard F.condition(forEntityRecord: entityRecord) else { return }
self.query.insert(entity: entity, entityRecord: entityRecord)
self.query.insert(entityRecord: entityRecord)
}

override func despawn(entity: Entity) {
Expand All @@ -40,12 +40,12 @@ final public class Filtered<Q: QueryProtocol, F: Filter>: Chunk, SystemParameter
worldStorage.chunkStorage.addChunk(Filtered<Q, F>())
}

override func applyCurrentState(_ entityRecord: EntityRecordRef, forEntity entity: Entity) {
override func applyCurrentState(_ entityRecord: EntityRecordRef) {
guard F.condition(forEntityRecord: entityRecord) else {
self.query.despawn(entity: entity)
self.query.despawn(entity: entityRecord.entity)
return
}
self.query.applyCurrentState(entityRecord, forEntity: entity)
self.query.applyCurrentState(entityRecord)
}

}
4 changes: 2 additions & 2 deletions Sources/ECS/FilterdQuery/QueryProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

public protocol QueryProtocol: Chunk {
associatedtype Update
func insert(entity: Entity, entityRecord: EntityRecordRef)
func despawn(entity: Entity)
func insert(entityRecord: EntityRecordRef)
func remove(entity: Entity)
func allocate()
func update( _ f: Update)
func update(_ entity: Entity, _ f: Update)
Expand Down
31 changes: 19 additions & 12 deletions Sources/ECS/Query/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,37 @@ final public class Query<C: QueryTarget>: Chunk, SystemParameter {
self.components.allocate()
}

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

public override func spawn(entity: Entity, entityRecord: EntityRecordRef) {
if entity.generation == 0 {
public func remove(entity: Entity) {
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()
}
self.insert(entity: entity, entityRecord: entityRecord)
self.insert(entityRecord: entityRecord)
}

public override func despawn(entity: Entity) {
guard self.components.contains(entity) else { return }
self.components.pop(entity: entity)
override func despawn(entity: Entity) {
self.remove(entity: entity)
}

override func applyCurrentState(_ entityRecord: EntityRecordRef, forEntity entity: Entity) {
override func applyCurrentState(_ entityRecord: EntityRecordRef) {
guard let componentRef = entityRecord.ref(C.self) else {
self.despawn(entity: entity)
self.despawn(entity: entityRecord.entity)
return
}
guard !components.contains(entity) else { return }
self.components.insert(componentRef, withEntity: entity)
guard !components.contains(entityRecord.entity) else { return }
self.components.insert(
componentRef,
withEntity: entityRecord.entity
)
}

/// Query で指定した Component を持つ entity を world から取得し, イテレーションします.
Expand Down
50 changes: 50 additions & 0 deletions Sources/ECS/Schedule/EventSchedule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// EventSchedule.swift
// ECS_Swift
//
// Created by rrbox on 2025/06/15.
//

/**
システムが実行されるタイミングを指定します.

Eccentials

- ``Schedule/startUp``
- ``Schedule/update``

State associated schedules

- ``EventSchedule/didEnter(_:)``
- ``EventSchedule/willExit(_:)``
- ``EventSchedule/onUpdate(_:)``
- ``EventSchedule/onInactiveUpdate(_:)``
- ``EventSchedule/onStackUpdate(_:)``
- ``EventSchedule/onPause(_:)``
- ``EventSchedule/onResume(_:)``

Entity spawn / despawn

- ``Schedule/didSpawn``
- ``Schedule/willDespawn``
*/
public struct EventSchedule: Hashable {
let typeId: ObjectIdentifier
let id: AnyHashable

init<T: Hashable>(id: T) {
self.typeId = ObjectIdentifier(T.self)
self.id = id
}
}

public extension EventSchedule {
/**
``World/update(currentTime:)`` 実行時にイベントを受信します.
*/
static let update: EventSchedule = EventSchedule(id: DefaultSchedule.update)

static func customSchedule<T: Hashable>(_ value: T) -> EventSchedule {
EventSchedule(id: value)
}
}
Loading
Loading