From e735e0508ccfe0af5d726e4d8525fb031fa4dd8c Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 Aug 2024 02:59:55 +0900 Subject: [PATCH 01/53] Remove unnecessary whitespaces --- Package.swift | 12 +- Sources/ECS/Chunk/ChunkEntityInterface.swift | 14 +-- Sources/ECS/Chunk/ChunkStorage+.swift | 14 +-- Sources/ECS/Chunk/ChunkStorage.swift | 4 +- Sources/ECS/Commands/Command.swift | 2 +- Sources/ECS/Commands/Commands.swift | 8 +- Sources/ECS/Commands/CommandsStorage.swift | 8 +- .../Transactions/EntityTransaction.swift | 2 +- Sources/ECS/Commands/World+Commands.swift | 2 +- Sources/ECS/Commons/AnyMap.swift | 2 +- Sources/ECS/Commons/Component.swift | 4 +- Sources/ECS/Commons/EntityGenerator.swift | 6 +- Sources/ECS/Commons/EntityRecord.swift | 16 +-- Sources/ECS/Commons/SparseSet.swift | 16 +-- Sources/ECS/Commons/WorldStorage.swift | 6 +- .../Commands+EntityCommands.swift | 12 +- .../ComponentTransactions/AddBundle.swift | 4 +- .../AddComponentCommand.swift | 6 +- .../RemoveComponentCommand.swift | 4 +- .../EntityCommands/EntityCommand.swift | 4 +- .../EntityCommands/EntityCommands.swift | 18 +-- .../EntityTransactions/DespawnCommand.swift | 4 +- .../EntityTransactions/SpawnCommand.swift | 5 +- .../CommandsEvent/CommandsEvent+Buffer.swift | 10 +- .../Event/CommandsEvent/CommandsEvent.swift | 4 +- .../CommandsEvent/CommandsEventWriter.swift | 11 +- Sources/ECS/Event/EventStreaming/Event.swift | 13 ++- .../Event/EventStreaming/EventReader.swift | 8 +- .../Event/EventStreaming/EventResponder.swift | 14 +-- .../Event/EventStreaming/EventStorage.swift | 16 +-- .../Event/EventStreaming/EventWriter.swift | 10 +- Sources/ECS/Event/World+EventStreamer.swift | 14 +-- Sources/ECS/FilterdQuery/FIlteredQuery.swift | 18 +-- Sources/ECS/Query/Query.swift | 28 ++--- Sources/ECS/Resource/Commands+Resource.swift | 4 +- Sources/ECS/Resource/Resource.swift | 12 +- Sources/ECS/Resource/ResourceBuffer.swift | 4 +- Sources/ECS/Schedule/Schedule.swift | 22 ++-- Sources/ECS/States/State.swift | 64 +++++------ .../ECS/States/StateAssociatedSchedules.swift | 12 +- Sources/ECS/States/StateControll.swift | 18 +-- Sources/ECS/System/SystemBuffer.swift | 11 +- .../ECS/SystemParameter/SystemParameter.swift | 1 - Sources/ECS/Systems/System.swift | 8 +- Sources/ECS/World/World+Entities.swift | 4 +- Sources/ECS/World/World.swift | 32 +++--- Sources/ECS/WorldMethods/World+Init.swift | 22 ++-- Sources/ECS/WorldMethods/World+SetUp.swift | 6 +- Sources/ECS/WorldMethods/World+Spawn.swift | 8 +- Sources/ECS/WorldMethods/World+Update.swift | 26 ++--- Sources/ECS_Macros/BundleMacro.swift | 6 +- Sources/ECS_Macros/QueryMacro.swift | 24 ++-- Sources/ECS_Macros/SystemMacro.swift | 28 ++--- Sources/PlugIns/Graphic2D/Camera.swift | 5 +- .../Commands/EntityCommands+Graphic.swift | 16 +-- .../Graphic2D/Commands/SetGraphic.swift | 8 +- Sources/PlugIns/Graphic2D/Graphic2D.swift | 4 +- Sources/PlugIns/Graphic2D/PlugInExport.swift | 9 +- .../PlugIns/Graphic2D/World+Graphic2D.swift | 1 - Sources/PlugIns/Keyboard/KeyBoard.swift | 2 +- Sources/PlugIns/Mouse/Mouse.swift | 10 +- Sources/PlugIns/Mouse/TrackableView.swift | 5 +- Sources/PlugIns/ObjectLink/ObjectLink.swift | 2 +- Sources/PlugIns/Scene/Scene.swift | 12 +- .../GraphicPlugInTests.swift | 36 +++--- .../ObjectLinkPlugInTests.swift | 2 +- Tests/ScenePlugInTests/ScenePlugInTests.swift | 2 +- Tests/ecs-swiftTests/BundleTests.swift | 4 +- Tests/ecs-swiftTests/ChunkTests.swift | 18 +-- Tests/ecs-swiftTests/CommandsTests.swift | 14 +-- .../ecs-swiftTests/EntityCommandsTests.swift | 18 +-- Tests/ecs-swiftTests/EventTests.swift | 6 +- Tests/ecs-swiftTests/FilteredQueryTests.swift | 34 +++--- Tests/ecs-swiftTests/PlugInTests.swift | 2 +- Tests/ecs-swiftTests/QueryTests.swift | 108 +++++++++--------- Tests/ecs-swiftTests/ResourceTests.swift | 2 +- .../ecs-swiftTests/SystemParameterTests.swift | 2 +- Tests/ecs-swiftTests/SystemTests.swift | 12 +- Tests/ecs-swiftTests/UpdateSystemTests.swift | 2 +- Tests/ecs-swiftTests/ecs_swiftTests.swift | 18 +-- 80 files changed, 492 insertions(+), 493 deletions(-) diff --git a/Package.swift b/Package.swift index 99639d1..0dba14c 100644 --- a/Package.swift +++ b/Package.swift @@ -7,16 +7,16 @@ import CompilerPluginSupport struct Module { let name: String let path: String? - + init(name: String, path: String? = nil) { self.name = name self.path = path } - + var dependency: Target.Dependency { Target.Dependency(stringLiteral: self.name) } - + } extension Module { @@ -29,7 +29,7 @@ extension Module { static let objectLink = Module(name: "ECS_ObjectLink", path: "Sources/PlugIns/ObjectLink") static let scene = Module(name: "ECS_Scene", path: "Sources/PlugIns/Scene") static let scroll = Module(name: "ECS_Scroll", path: "Sources/PlugIns/Scroll") - + static let ecs_swiftTests = Module(name: "ecs-swiftTests") static let graphicPlugInTests = Module(name: "GraphicPlugInTests") static let keyBoardPlugInTests = Module(name: "KeyBoardPlugInTests") @@ -46,7 +46,7 @@ extension Target { dependencies: dependencies.map { $0.dependency }, path: module.path) } - + static func testTarget(module: Module, dependencies: [Module]) -> Target { .testTarget( name: module.name, @@ -54,7 +54,7 @@ extension Target { path: module.path ) } - + static func macro(module: Module, dependencies: [Module]) -> Target { .macro( name: module.name, diff --git a/Sources/ECS/Chunk/ChunkEntityInterface.swift b/Sources/ECS/Chunk/ChunkEntityInterface.swift index 1eb537f..58a9699 100644 --- a/Sources/ECS/Chunk/ChunkEntityInterface.swift +++ b/Sources/ECS/Chunk/ChunkEntityInterface.swift @@ -15,19 +15,19 @@ class ChunkEntityInterface: WorldStorageElement { var prespawnedEntityQueue = [(Entity, EntityRecordRef)]() var updatedEntityQueue = [(Entity, EntityRecordRef)]() var chunks = [Chunk]() - + /// chunk を追加します func add(chunk: Chunk) { self.chunks.append(chunk) } - + /// World に entity が追加された時に実行します. /// /// entity が queue に追加され、フレームの終わりに全ての chunk に entity を反映します. func pushSpawned(entity: Entity, entityRecord: EntityRecordRef) { self.prespawnedEntityQueue.append((entity, entityRecord)) } - + /// Spawn 処理された entity を, 実際に chunk に追加します. /// /// Component が完全に追加された後にこの処理を呼び出すことで, Entity の Component の有無が Chunk に反映されるようになります. @@ -39,7 +39,7 @@ class ChunkEntityInterface: WorldStorageElement { } self.prespawnedEntityQueue = [] } - + /// World から entity が削除される時に実行します. /// /// フレームの終わりに全ての chunk から entity を削除します. @@ -48,11 +48,11 @@ class ChunkEntityInterface: WorldStorageElement { chunk.despawn(entity: entity) } } - + func pushUpdated(entity: Entity, entityRecord: EntityRecordRef) { self.updatedEntityQueue.append((entity, entityRecord)) } - + func applyUpdatedEntityQueue() { for (entity, entityRecord) in self.updatedEntityQueue { for chunk in self.chunks { @@ -61,5 +61,5 @@ class ChunkEntityInterface: WorldStorageElement { } self.updatedEntityQueue = [] } - + } diff --git a/Sources/ECS/Chunk/ChunkStorage+.swift b/Sources/ECS/Chunk/ChunkStorage+.swift index 4999c8a..8cfcd97 100644 --- a/Sources/ECS/Chunk/ChunkStorage+.swift +++ b/Sources/ECS/Chunk/ChunkStorage+.swift @@ -9,30 +9,30 @@ extension ChunkStorage { func setUpChunkBuffer() { self.buffer.map.push(ChunkEntityInterface()) } - + func addChunk(_ chunk: ChunkType) { self.buffer.map.push(chunk) 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 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) } - + func applyUpdatedEntityQueue() { self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.applyUpdatedEntityQueue() } - + func despawn(entity: Entity) { self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.despawn(entity: entity) } - + } diff --git a/Sources/ECS/Chunk/ChunkStorage.swift b/Sources/ECS/Chunk/ChunkStorage.swift index d80771f..29c3edd 100644 --- a/Sources/ECS/Chunk/ChunkStorage.swift +++ b/Sources/ECS/Chunk/ChunkStorage.swift @@ -6,7 +6,7 @@ // extension Chunk: WorldStorageElement { - + } /// Chunk を種類別で格納します @@ -15,7 +15,7 @@ final public class ChunkStorage { init(buffer: WorldStorageRef) { self.buffer = buffer } - + public func chunk(ofType type: ChunkType.Type) -> ChunkType? { self.buffer.map.valueRef(ofType: ChunkType.self)?.body } diff --git a/Sources/ECS/Commands/Command.swift b/Sources/ECS/Commands/Command.swift index 71702bf..be54b88 100644 --- a/Sources/ECS/Commands/Command.swift +++ b/Sources/ECS/Commands/Command.swift @@ -8,6 +8,6 @@ open class Command { public init() {} open func runCommand(in world: World) { - + } } diff --git a/Sources/ECS/Commands/Commands.swift b/Sources/ECS/Commands/Commands.swift index 0de2cb1..2ebbcf6 100644 --- a/Sources/ECS/Commands/Commands.swift +++ b/Sources/ECS/Commands/Commands.swift @@ -24,16 +24,16 @@ final public class Commands: SystemParameter { var commandQueue = [Command]() var generator = EntityGenerator() var entityTransactions = [EntityTransaction]() - + /// Commands では, World への登録時には何もしません. public static func register(to worldStorage: WorldStorageRef) { - + } - + public static func getParameter(from worldStorage: WorldStorageRef) -> Commands? { worldStorage.commandsStorage.commands() } - + /// CommandQueue にコマンドを追加します. public func push(command: Command) { self.commandQueue.append(command) diff --git a/Sources/ECS/Commands/CommandsStorage.swift b/Sources/ECS/Commands/CommandsStorage.swift index 25c8548..5183f2b 100644 --- a/Sources/ECS/Commands/CommandsStorage.swift +++ b/Sources/ECS/Commands/CommandsStorage.swift @@ -8,21 +8,21 @@ final public class CommandsStorage { class CommandsRegistry: WorldStorageElement { let commands: Commands - + init(commands: Commands) { self.commands = commands } } let buffer: WorldStorageRef - + init(buffer: WorldStorageRef) { self.buffer = buffer } - + public func commands() -> Commands? { self.buffer.map.valueRef(ofType: CommandsRegistry.self)?.body.commands } - + func setCommands(_ commands: Commands) { self.buffer.map.push(CommandsRegistry(commands: commands)) } diff --git a/Sources/ECS/Commands/Transactions/EntityTransaction.swift b/Sources/ECS/Commands/Transactions/EntityTransaction.swift index 1e2a025..d308adf 100644 --- a/Sources/ECS/Commands/Transactions/EntityTransaction.swift +++ b/Sources/ECS/Commands/Transactions/EntityTransaction.swift @@ -7,6 +7,6 @@ class EntityTransaction { func runCommand(in world: World) { - + } } diff --git a/Sources/ECS/Commands/World+Commands.swift b/Sources/ECS/Commands/World+Commands.swift index 6823042..bbcaf2b 100644 --- a/Sources/ECS/Commands/World+Commands.swift +++ b/Sources/ECS/Commands/World+Commands.swift @@ -12,7 +12,7 @@ extension World { } commands.entityTransactions = [] } - + func applyCommands(commands: Commands) { for command in commands.commandQueue { command.runCommand(in: self) diff --git a/Sources/ECS/Commons/AnyMap.swift b/Sources/ECS/Commons/AnyMap.swift index be3441b..5a35bae 100644 --- a/Sources/ECS/Commons/AnyMap.swift +++ b/Sources/ECS/Commons/AnyMap.swift @@ -6,7 +6,7 @@ // public class Item { - + } public struct AnyMap { diff --git a/Sources/ECS/Commons/Component.swift b/Sources/ECS/Commons/Component.swift index cd380eb..bcc5b0d 100644 --- a/Sources/ECS/Commons/Component.swift +++ b/Sources/ECS/Commons/Component.swift @@ -6,9 +6,9 @@ // public protocol QueryTarget { - + } public protocol Component: QueryTarget { - + } diff --git a/Sources/ECS/Commons/EntityGenerator.swift b/Sources/ECS/Commons/EntityGenerator.swift index 75d9a4f..cff4888 100644 --- a/Sources/ECS/Commons/EntityGenerator.swift +++ b/Sources/ECS/Commons/EntityGenerator.swift @@ -8,16 +8,16 @@ struct EntityGenerator { private var reusables: [Entity] = [Entity(slot: 0, generation: 0)] private var lastIndex: Int = 0 - + func generate() -> Entity { self.reusables[lastIndex] } - + mutating func stack(entity: Entity) { self.reusables.append(entity) self.lastIndex += 1 } - + mutating func pop() { if self.lastIndex == 0 { self.reusables[0] = Entity(slot: self.reusables[0].slot+1, generation: 0) diff --git a/Sources/ECS/Commons/EntityRecord.swift b/Sources/ECS/Commons/EntityRecord.swift index 52d423d..1ba92a1 100644 --- a/Sources/ECS/Commons/EntityRecord.swift +++ b/Sources/ECS/Commons/EntityRecord.swift @@ -16,12 +16,12 @@ public class Ref: Item { final public class ComponentRef: Ref { var _value: T - + public override var value: T { get { self._value } set { self._value = newValue } } - + init(value: T) { self._value = value } @@ -29,12 +29,12 @@ final public class ComponentRef: Ref { final public class ImmutableRef: Ref { let _value: T - + public override var value: T { get { self._value } set {} } - + init(value: T) { self._value = value } @@ -48,20 +48,20 @@ public extension EntityRecordRef { func addComponent(_ component: T) { self.map.body[ObjectIdentifier(T.self)] = ComponentRef(value: component) } - + func removeComponent(ofType type: T.Type) { self.map.body.removeValue(forKey: ObjectIdentifier(T.self)) } - + func ref(_ type: T.Type) -> Ref? { guard let result: Item = self.map.body[ObjectIdentifier(T.self)] else { return nil } return (result as! Ref) } - + func componentRef(ofType type: T.Type) -> ComponentRef? { return self.map.body[ObjectIdentifier(T.self)] as? ComponentRef } - + func component(ofType type: T.Type) -> T? { return self.componentRef(ofType: T.self)?.value } diff --git a/Sources/ECS/Commons/SparseSet.swift b/Sources/ECS/Commons/SparseSet.swift index b2ba0c3..eadd393 100644 --- a/Sources/ECS/Commons/SparseSet.swift +++ b/Sources/ECS/Commons/SparseSet.swift @@ -7,17 +7,17 @@ public struct SparseSet { typealias DenseIndex = Int - + var sparse: [DenseIndex?] var dense: [Entity] var data: [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 } @@ -29,23 +29,23 @@ public struct SparseSet { 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) @@ -53,7 +53,7 @@ public struct SparseSet { 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 } diff --git a/Sources/ECS/Commons/WorldStorage.swift b/Sources/ECS/Commons/WorldStorage.swift index 1b1f2e9..d25aa8e 100644 --- a/Sources/ECS/Commons/WorldStorage.swift +++ b/Sources/ECS/Commons/WorldStorage.swift @@ -11,7 +11,7 @@ protocol WorldStorageElement {} class Box: Item { var body: T - + init(body: T) { self.body = body } @@ -21,11 +21,11 @@ extension AnyMap where Mode == WorldStorage { mutating func push(_ data: T) { self.body[ObjectIdentifier(T.self)] = Box(body: data) } - + mutating func pop(_ type: T.Type) { self.body.removeValue(forKey: ObjectIdentifier(T.self)) } - + func valueRef(ofType type: T.Type) -> Box? { guard let result = self.body[ObjectIdentifier(T.self)] else { return nil } return (result as! Box) diff --git a/Sources/ECS/EntityCommands/Commands+EntityCommands.swift b/Sources/ECS/EntityCommands/Commands+EntityCommands.swift index 336d36a..fd9d78e 100644 --- a/Sources/ECS/EntityCommands/Commands+EntityCommands.swift +++ b/Sources/ECS/EntityCommands/Commands+EntityCommands.swift @@ -12,23 +12,23 @@ public extension Commands { self.entityTransactions.append(queue) return SearchedEntityCommands(entity: entity, commandsQueue: queue) } - + /// Entity を追加して変更を加えます. @discardableResult func spawn() -> SpawnedEntityCommands { let entity = self.generator.generate() let record = EntityRecordRef() - + record.map.body[ObjectIdentifier(Entity.self)] = ImmutableRef(value: entity) - + self.generator.pop() - + self.entityTransactions.append(SpawnCommand(id: entity, entityRecord: record)) let queue = SpawnedEntityCommandQueue(record: record) self.entityTransactions.append(queue) - + return SpawnedEntityCommands(entity: entity, commandsQueue: queue) } - + /// Entity を削除します. func despawn(entity: Entity) { self.generator.stack(entity: Entity(slot: entity.slot, generation: entity.generation+1)) diff --git a/Sources/ECS/EntityCommands/ComponentTransactions/AddBundle.swift b/Sources/ECS/EntityCommands/ComponentTransactions/AddBundle.swift index c775cea..402722c 100644 --- a/Sources/ECS/EntityCommands/ComponentTransactions/AddBundle.swift +++ b/Sources/ECS/EntityCommands/ComponentTransactions/AddBundle.swift @@ -7,12 +7,12 @@ class AddBundle: EntityCommand { let bundle: T - + init(entity: Entity, bundle: T) { self.bundle = bundle super.init(entity: entity) } - + override func runCommand(forRecord record: EntityRecordRef, inWorld world: World) { bundle.addComponent(forEntity: record) } diff --git a/Sources/ECS/EntityCommands/ComponentTransactions/AddComponentCommand.swift b/Sources/ECS/EntityCommands/ComponentTransactions/AddComponentCommand.swift index af493f5..ca50c19 100644 --- a/Sources/ECS/EntityCommands/ComponentTransactions/AddComponentCommand.swift +++ b/Sources/ECS/EntityCommands/ComponentTransactions/AddComponentCommand.swift @@ -7,14 +7,14 @@ class AddComponent: EntityCommand { let component: C - + init(entity: Entity, component: C) { self.component = component super.init(entity: entity) } - + override func runCommand(forRecord record: EntityRecordRef, inWorld world: World) { record.addComponent(component) } - + } diff --git a/Sources/ECS/EntityCommands/ComponentTransactions/RemoveComponentCommand.swift b/Sources/ECS/EntityCommands/ComponentTransactions/RemoveComponentCommand.swift index c1f83a0..969fa47 100644 --- a/Sources/ECS/EntityCommands/ComponentTransactions/RemoveComponentCommand.swift +++ b/Sources/ECS/EntityCommands/ComponentTransactions/RemoveComponentCommand.swift @@ -9,9 +9,9 @@ class RemoveComponent: EntityCommand { init(entity: Entity, componentType type: C.Type) { super.init(entity: entity) } - + override func runCommand(forRecord record: EntityRecordRef, inWorld world: World) { record.removeComponent(ofType: C.self) } - + } diff --git a/Sources/ECS/EntityCommands/EntityCommands/EntityCommand.swift b/Sources/ECS/EntityCommands/EntityCommands/EntityCommand.swift index 799b3fb..4bb03bb 100644 --- a/Sources/ECS/EntityCommands/EntityCommands/EntityCommand.swift +++ b/Sources/ECS/EntityCommands/EntityCommands/EntityCommand.swift @@ -10,8 +10,8 @@ open class EntityCommand { public init(entity: Entity) { self.entity = entity } - + open func runCommand(forRecord record: EntityRecordRef, inWorld world: World) { - + } } diff --git a/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift index a270af4..8c947be 100644 --- a/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift +++ b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift @@ -11,9 +11,11 @@ class EntityCommandQueue: EntityTransaction { class SpawnedEntityCommandQueue: EntityCommandQueue { let record: EntityRecordRef + init(record: EntityRecordRef) { self.record = record } + override func runCommand(in world: World) { self.queue.forEach { command in command.runCommand(forRecord: self.record, inWorld: world) @@ -23,9 +25,11 @@ class SpawnedEntityCommandQueue: EntityCommandQueue { class SearchedEntityCommandQueue: EntityCommandQueue { let entity: Entity + init(entity: Entity) { self.entity = entity } + 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) @@ -38,22 +42,22 @@ class SearchedEntityCommandQueue: EntityCommandQueue { public class EntityCommands { let entity: Entity let commandQueue: EntityCommandQueue - + init(entity: Entity, commandsQueue: EntityCommandQueue) { self.entity = entity self.commandQueue = commandsQueue } - + public func pushCommand(_ command: EntityCommand) { self.commandQueue.queue.append(command) } - + /// Commands で操作した Entity を受け取ります. /// - Returns: ID としての Entity をそのまま返します. public func id() -> Entity { self.entity } - + /// Entity に Component を追加します. /// - Parameter component: 追加するコンポーネントを指定します. /// - Returns: Entity component のビルダーです. @@ -61,7 +65,7 @@ public class EntityCommands { self.pushCommand(AddComponent(entity: self.entity, component: component)) return self } - + /// Entity から Component を削除します. /// - Parameter type: 削除する Component の型を指定します. /// - Returns: Entity component のビルダーです. @@ -69,10 +73,10 @@ public class EntityCommands { self.pushCommand(RemoveComponent(entity: entity, componentType: ComponentType.self)) return self } - + @discardableResult public func addBundle(_ bundle: T) -> Self { self.pushCommand(AddBundle(entity: self.entity, bundle: bundle)) return self } - + } diff --git a/Sources/ECS/EntityCommands/EntityTransactions/DespawnCommand.swift b/Sources/ECS/EntityCommands/EntityTransactions/DespawnCommand.swift index 5c568ba..18a2652 100644 --- a/Sources/ECS/EntityCommands/EntityTransactions/DespawnCommand.swift +++ b/Sources/ECS/EntityCommands/EntityTransactions/DespawnCommand.swift @@ -7,11 +7,11 @@ class DespawnCommand: EntityTransaction { let entity: Entity - + init(entity: Entity) { self.entity = entity } - + override func runCommand(in world: World) { world.despawn(entity: self.entity) } diff --git a/Sources/ECS/EntityCommands/EntityTransactions/SpawnCommand.swift b/Sources/ECS/EntityCommands/EntityTransactions/SpawnCommand.swift index 958cc46..1150183 100644 --- a/Sources/ECS/EntityCommands/EntityTransactions/SpawnCommand.swift +++ b/Sources/ECS/EntityCommands/EntityTransactions/SpawnCommand.swift @@ -8,14 +8,13 @@ class SpawnCommand: EntityTransaction { let entity: Entity let entityRecord: EntityRecordRef - + init(id: Entity, entityRecord: EntityRecordRef) { self.entity = id self.entityRecord = entityRecord } - + override func runCommand(in world: World) { world.push(entity: self.entity, entityRecord: self.entityRecord) } } - diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift b/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift index 427cc70..c83991c 100644 --- a/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift +++ b/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift @@ -9,24 +9,24 @@ extension EventStorage { func setUpCommandsEventQueue(eventOfType: T.Type) { self.buffer.map.push(CommandsEventQueue()) } - + func commandsEventQueue(eventOfType: T.Type) -> CommandsEventQueue? { self.buffer.map.valueRef(ofType: CommandsEventQueue.self)?.body } - + func commandsEventWriter(eventOfType type: T.Type) -> CommandsEventWriter? { self.buffer.map.valueRef(ofType: CommandsEventWriter.self)?.body } - + func registerCommandsEventWriter(eventType: T.Type) { let eventQueue = self.buffer.map.valueRef(ofType: CommandsEventQueue.self)!.body self.buffer.map.push(CommandsEventWriter(eventQueue: eventQueue)) } - + func commandsEventResponder(eventOfType type: T.Type) -> EventResponder? { self.buffer.map.valueRef(ofType: EventResponder.self)?.body } - + func resisterCommandsEventResponder(eventType: T.Type) { self.buffer.map.push(EventResponder()) } diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEvent.swift b/Sources/ECS/Event/CommandsEvent/CommandsEvent.swift index 5734e0b..e074c3b 100644 --- a/Sources/ECS/Event/CommandsEvent/CommandsEvent.swift +++ b/Sources/ECS/Event/CommandsEvent/CommandsEvent.swift @@ -7,11 +7,11 @@ /// Commands 実行中に発信されるイベントです. protocol CommandsEventProtocol { - + } struct OnCommandsEvent: Hashable { - + } extension Schedule { diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift b/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift index d880090..4ee9134 100644 --- a/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift +++ b/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift @@ -7,21 +7,20 @@ final class CommandsEventWriter: SystemParameter, WorldStorageElement { unowned let eventQueue: CommandsEventQueue - + init(eventQueue: CommandsEventQueue) { self.eventQueue = eventQueue } - + public func send(value: T) { self.eventQueue.eventQueue.append(value) } - + public static func register(to worldStorage: WorldStorageRef) { - + } - + public static func getParameter(from worldStorage: WorldStorageRef) -> CommandsEventWriter? { worldStorage.eventStorage.commandsEventWriter(eventOfType: T.self) } } - diff --git a/Sources/ECS/Event/EventStreaming/Event.swift b/Sources/ECS/Event/EventStreaming/Event.swift index 9b8cf0f..0d59d31 100644 --- a/Sources/ECS/Event/EventStreaming/Event.swift +++ b/Sources/ECS/Event/EventStreaming/Event.swift @@ -6,37 +6,38 @@ // public protocol EventProtocol { - + } class AnyEvent { func runEventReceiver(worldStorage: WorldStorageRef) { - + } } final class Event: AnyEvent { let value: T + init(value: T) { self.value = value } - + override func runEventReceiver(worldStorage: WorldStorageRef) { worldStorage.map.push(EventReader(value: self.value)) - + if let systems = worldStorage.eventStorage.eventResponder(eventOfType: T.self)!.systems[.update] { for system in systems { system.execute(worldStorage) } } - + for schedule in worldStorage.stateStorage.currentSchedulesWhichAssociatedStates() { guard let systems = worldStorage.eventStorage.eventResponder(eventOfType: T.self)!.systems[schedule] else { continue } for system in systems { system.execute(worldStorage) } } - + worldStorage.map.pop(EventReader.self) } } diff --git a/Sources/ECS/Event/EventStreaming/EventReader.swift b/Sources/ECS/Event/EventStreaming/EventReader.swift index c50ea8c..7214b5a 100644 --- a/Sources/ECS/Event/EventStreaming/EventReader.swift +++ b/Sources/ECS/Event/EventStreaming/EventReader.swift @@ -7,15 +7,15 @@ final public class EventReader: SystemParameter, WorldStorageElement { public let value: T - + init(value: T) { self.value = value } - + public static func register(to worldStorage: WorldStorageRef) { - + } - + public static func getParameter(from worldStorage: WorldStorageRef) -> EventReader? { return worldStorage.map.valueRef(ofType: EventReader.self)?.body } diff --git a/Sources/ECS/Event/EventStreaming/EventResponder.swift b/Sources/ECS/Event/EventStreaming/EventResponder.swift index 6356ea6..fb0f0a1 100644 --- a/Sources/ECS/Event/EventStreaming/EventResponder.swift +++ b/Sources/ECS/Event/EventStreaming/EventResponder.swift @@ -10,7 +10,7 @@ import Foundation final public class EventResponderBuilder { unowned let worldStorage: WorldStorageRef var systems: [Schedule: [SystemExecute]] = [:] - + init(worldStorage: WorldStorageRef) { self.worldStorage = worldStorage } @@ -24,32 +24,32 @@ public extension World { @discardableResult func buildEventResponder(_ eventType: T.Type, _ build: (EventResponderBuilder) -> ()) -> World { let builder = EventResponderBuilder(worldStorage: self.worldStorage) build(builder) - + self.worldStorage.eventStorage.eventResponder(eventOfType: T.self)! .systems .merge(builder.systems) { fromWorldStorage, new in fromWorldStorage + new } - + return self } - + private func buildCommandsEventResponder(_ eventType: T.Type, _ build: (EventResponderBuilder) -> ()) { let builder = EventResponderBuilder(worldStorage: self.worldStorage) build(builder) - + self.worldStorage.eventStorage.commandsEventResponder(eventOfType: T.self)! .systems .merge(builder.systems) { fromWorldStorage, new in fromWorldStorage + new } } - + @discardableResult func buildDidSpawnResponder(_ build: (EventResponderBuilder) -> ()) -> World { self.buildCommandsEventResponder(DidSpawnEvent.self, build) return self } - + @discardableResult func buildWillDespawnResponder(_ build: (EventResponderBuilder) -> ()) -> World { self.buildCommandsEventResponder(WillDespawnEvent.self, build) return self diff --git a/Sources/ECS/Event/EventStreaming/EventStorage.swift b/Sources/ECS/Event/EventStreaming/EventStorage.swift index ca52ed0..fee77a2 100644 --- a/Sources/ECS/Event/EventStreaming/EventStorage.swift +++ b/Sources/ECS/Event/EventStreaming/EventStorage.swift @@ -10,35 +10,36 @@ import Foundation // world buffer にプロパティをつけておく class EventStorage { let buffer: WorldStorageRef + init(buffer: WorldStorageRef) { self.buffer = buffer } - + func setUpEventQueue() { self.buffer.map.push(EventQueue()) } - + func eventQueue() -> EventQueue? { self.buffer.map.valueRef(ofType: EventQueue.self)?.body } - + func eventWriter(eventOfType type: T.Type) -> EventWriter? { self.buffer.map.valueRef(ofType: EventWriter.self)?.body } - + func registerEventWriter(eventType: T.Type) { let eventQueue = self.buffer.map.valueRef(ofType: EventQueue.self)!.body self.buffer.map.push(EventWriter(eventQueue: eventQueue)) } - + func eventResponder(eventOfType type: T.Type) -> EventResponder? { self.buffer.map.valueRef(ofType: EventResponder.self)?.body } - + func registerEventResponder(eventType: T.Type) { self.buffer.map.push(EventResponder()) } - + } extension WorldStorageRef { @@ -46,4 +47,3 @@ extension WorldStorageRef { EventStorage(buffer: self) } } - diff --git a/Sources/ECS/Event/EventStreaming/EventWriter.swift b/Sources/ECS/Event/EventStreaming/EventWriter.swift index 87b3edb..2701ef4 100644 --- a/Sources/ECS/Event/EventStreaming/EventWriter.swift +++ b/Sources/ECS/Event/EventStreaming/EventWriter.swift @@ -8,19 +8,19 @@ // Commands と基本的な仕組みは同じ. final public class EventWriter: SystemParameter, WorldStorageElement { unowned let eventQueue: EventQueue - + init(eventQueue: EventQueue) { self.eventQueue = eventQueue } - + public func send(value: T) { self.eventQueue.eventQueue.append(Event(value: value)) } - + public static func register(to worldStorage: WorldStorageRef) { - + } - + public static func getParameter(from worldStorage: WorldStorageRef) -> EventWriter? { worldStorage.eventStorage.eventWriter(eventOfType: T.self) } diff --git a/Sources/ECS/Event/World+EventStreamer.swift b/Sources/ECS/Event/World+EventStreamer.swift index c2c4154..3979eee 100644 --- a/Sources/ECS/Event/World+EventStreamer.swift +++ b/Sources/ECS/Event/World+EventStreamer.swift @@ -13,7 +13,7 @@ public extension World { let eventStorage = self.worldStorage.eventStorage eventStorage.registerEventWriter(eventType: T.self) eventStorage.registerEventResponder(eventType: T.self) - + return self } } @@ -21,7 +21,7 @@ public extension World { extension World { func addCommandsEventStreamer(eventType: T.Type) { self.worldStorage.systemStorage.insertSchedule(.onCommandsEvent(ofType: T.self)) - + let eventStorage = self.worldStorage.eventStorage eventStorage.registerCommandsEventWriter(eventType: T.self) eventStorage.resisterCommandsEventResponder(eventType: T.self) @@ -38,7 +38,7 @@ extension World { } eventQueue.sendingEvents = [] } - + func applyCommandsEventQueue(eventOfType: T.Type) { let eventStorage = self.worldStorage.eventStorage let eventQueue = eventStorage.commandsEventQueue(eventOfType: T.self)! @@ -46,20 +46,20 @@ extension World { eventQueue.eventQueue = [] for event in eventQueue.sendingEvents { self.worldStorage.map.push(EventReader(value: event)) - + if let systems = eventStorage.commandsEventResponder(eventOfType: T.self)!.systems[.update] { for system in systems { system.execute(self.worldStorage) } } - + for schedule in self.worldStorage.stateStorage.currentSchedulesWhichAssociatedStates() { guard let systems = eventStorage.commandsEventResponder(eventOfType: T.self)!.systems[schedule] else { continue } for system in systems { system.execute(self.worldStorage) } } - + self.worldStorage.map.pop(EventReader.self) } eventQueue.sendingEvents = [] @@ -69,7 +69,7 @@ extension World { public extension World { /** ``World`` インスタンスを介して Event を配信します. - + System 内で Event を発信する場合は ``EventWriter`` を参照してください. */ func sendEvent(_ value: T) { diff --git a/Sources/ECS/FilterdQuery/FIlteredQuery.swift b/Sources/ECS/FilterdQuery/FIlteredQuery.swift index b2efd32..21f955d 100644 --- a/Sources/ECS/FilterdQuery/FIlteredQuery.swift +++ b/Sources/ECS/FilterdQuery/FIlteredQuery.swift @@ -7,7 +7,7 @@ final public class Filtered: Chunk, SystemParameter { let query: Q = Q() - + override func spawn(entity: Entity, entityRecord: EntityRecordRef) { if entity.generation == 0 { self.query.allocate() @@ -15,31 +15,31 @@ final public class Filtered: Chunk, SystemParameter guard F.condition(forEntityRecord: entityRecord) else { return } self.query.insert(entity: entity, entityRecord: entityRecord) } - + override func despawn(entity: Entity) { self.query.despawn(entity: entity) } - + public func update( _ f: Q.Update) { self.query.update(f) } - + public func update(_ entity: Entity, _ f: Q.Update) { self.query.update(entity, f) } - + public static func getParameter(from worldStorage: WorldStorageRef) -> Filtered? { worldStorage.chunkStorage.chunk(ofType: Filtered.self) } - + public static func register(to worldStorage: WorldStorageRef) { guard worldStorage.chunkStorage.chunk(ofType: Self.self) == nil else { return } - + worldStorage.chunkStorage.addChunk(Filtered()) } - + override func applyCurrentState(_ entityRecord: EntityRecordRef, forEntity entity: Entity) { guard F.condition(forEntityRecord: entityRecord) else { self.query.despawn(entity: entity) @@ -47,5 +47,5 @@ final public class Filtered: Chunk, SystemParameter } self.query.applyCurrentState(entityRecord, forEntity: entity) } - + } diff --git a/Sources/ECS/Query/Query.swift b/Sources/ECS/Query/Query.swift index e10f861..8274681 100644 --- a/Sources/ECS/Query/Query.swift +++ b/Sources/ECS/Query/Query.swift @@ -7,30 +7,30 @@ final public class Query: Chunk, SystemParameter { var components = SparseSet>(sparse: [], dense: [], data: []) - + public override init() {} - + public func allocate() { self.components.allocate() } - + public func insert(entity: Entity, entityRecord: EntityRecordRef) { guard let componentRef = entityRecord.ref(C.self) else { return } self.components.insert(componentRef, withEntity: entity) } - + public override func spawn(entity: Entity, entityRecord: EntityRecordRef) { if entity.generation == 0 { self.components.allocate() } self.insert(entity: entity, entityRecord: entityRecord) } - + public override func despawn(entity: Entity) { guard self.components.contains(entity) else { return } self.components.pop(entity: entity) } - + override func applyCurrentState(_ entityRecord: EntityRecordRef, forEntity entity: Entity) { guard let componentRef = entityRecord.ref(C.self) else { self.despawn(entity: entity) @@ -39,36 +39,36 @@ final public class Query: Chunk, SystemParameter { guard !components.contains(entity) else { return } self.components.insert(componentRef, withEntity: entity) } - + /// Query で指定した Component を持つ entity を world から取得し, イテレーションします. public func update(_ f: (inout C) -> ()) { 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) } - + public func components(forEntity entity: Entity) -> C? { self.components.value(forEntity: entity)?.value } - + public static func register(to worldStorage: WorldStorageRef) { guard worldStorage.chunkStorage.chunk(ofType: Self.self) == nil else { return } - + let queryRegistory = Self() - + worldStorage.chunkStorage.addChunk(queryRegistory) } - + public static func getParameter(from worldStorage: WorldStorageRef) -> Self? { worldStorage.chunkStorage.chunk(ofType: Self.self) } - + } diff --git a/Sources/ECS/Resource/Commands+Resource.swift b/Sources/ECS/Resource/Commands+Resource.swift index b3d9296..afa7845 100644 --- a/Sources/ECS/Resource/Commands+Resource.swift +++ b/Sources/ECS/Resource/Commands+Resource.swift @@ -7,11 +7,11 @@ class AddResource: Command { let resrouce: T - + init(resrouce: T) { self.resrouce = resrouce } - + override func runCommand(in world: World) { world.addResource(self.resrouce) } diff --git a/Sources/ECS/Resource/Resource.swift b/Sources/ECS/Resource/Resource.swift index 6ca4053..88b63d6 100644 --- a/Sources/ECS/Resource/Resource.swift +++ b/Sources/ECS/Resource/Resource.swift @@ -11,7 +11,7 @@ - note: 詳細は を参照してください. */ public protocol ResourceProtocol { - + } /** @@ -21,17 +21,17 @@ public protocol ResourceProtocol { */ final public class Resource: WorldStorageElement, SystemParameter { public var resource: T - + init(_ resource: T) { self.resource = resource } - + public static func register(to worldStorage: WorldStorageRef) { - + } - + public static func getParameter(from worldStorage: WorldStorageRef) -> Resource? { worldStorage.resourceBuffer.resource(ofType: T.self) } - + } diff --git a/Sources/ECS/Resource/ResourceBuffer.swift b/Sources/ECS/Resource/ResourceBuffer.swift index 6f77b48..3e788b5 100644 --- a/Sources/ECS/Resource/ResourceBuffer.swift +++ b/Sources/ECS/Resource/ResourceBuffer.swift @@ -10,11 +10,11 @@ final public class ResourceBuffer { init(buffer: WorldStorageRef) { self.buffer = buffer } - + func addResource(_ resource: T) { self.buffer.map.push(Resource(resource)) } - + public func resource(ofType type: T.Type) -> Resource? { self.buffer.map.valueRef(ofType: Resource.self)?.body } diff --git a/Sources/ECS/Schedule/Schedule.swift b/Sources/ECS/Schedule/Schedule.swift index 226bb03..fb6d6a0 100644 --- a/Sources/ECS/Schedule/Schedule.swift +++ b/Sources/ECS/Schedule/Schedule.swift @@ -31,7 +31,7 @@ public struct Schedule: Hashable { let typeId: ObjectIdentifier let id: AnyHashable - + init(id: T) { self.typeId = ObjectIdentifier(T.self) self.id = id @@ -47,15 +47,15 @@ enum DefaultSchedule { public extension Schedule { /** ``World/setUpWorld()`` 実行時にシステムを実行します. - + ```swift func createEntity(commands: Commands) { commands.spawn() // entity } - + let world = World() .addSystem(.startUp, createEntity(commands:)) - + world.setUpWorld() // entity spawn here ``` */ @@ -63,36 +63,36 @@ public extension Schedule { /** ``World/update(currentTime:)`` 実行時にシステムを実行します. - + ```swift struct Name: Component { let value: String } - + func createEntity(commands: Commands) { commands.spawn() // entity .addComponent(Name(value: "Entity_0")) } - + func echoEntityName(query: Query) { query.update { _, name in print(name) // echo entity name } } - + let world = World() .addSystem(.startUp, createEntity(commands:)) .addSystem(.update, echoEntityName(query: Query)) // system as `update` - + world.setUpWorld() // entity spawn here - + world.update(currentTime: 0) // prepareing frame world.update(currentTime: 1) // "Entity_0" world.update(currentTime: 2) // "Entity_0" ``` */ static let update: Schedule = Schedule(id: DefaultSchedule.update) - + static func customSchedule(_ value: T) -> Schedule { Schedule(id: value) } diff --git a/Sources/ECS/States/State.swift b/Sources/ECS/States/State.swift index e6c2aff..d89ad78 100644 --- a/Sources/ECS/States/State.swift +++ b/Sources/ECS/States/State.swift @@ -6,126 +6,126 @@ // public protocol StateProtocol: Hashable { - + } public class StateStorage { class StateRegistry: WorldStorageElement { var currentState: T var inactiveStates = [T]() - + init(currentState: T) { self.currentState = currentState } - + } - + class StateAssociatedSchedules: WorldStorageElement { var schedules = Set() } - + class StatesDidEnterInStartUp: WorldStorageElement { // world 構築時に初期値として設定された state を一時的に保持します. // world の start up 時に didEnter system が実行されます. // start up 実行後も保持され続けられるため, world 初期化のために start up を実行した場合も同じ効果が得られます. var schedules = Set() } - + let storageRef: WorldStorageRef - + init(storageRef: WorldStorageRef) { self.storageRef = storageRef } - + func setUp() { self.storageRef.map.push(StateAssociatedSchedules()) self.storageRef.map.push(StatesDidEnterInStartUp()) } - + func registerState(initialState: T, states: [T]) { self.storageRef.map.push(StateRegistry(currentState: initialState)) self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body.schedules.insert(.onUpdate(initialState)) self.storageRef.map.valueRef(ofType: StatesDidEnterInStartUp.self)!.body.schedules.insert(.didEnter(initialState)) } - + func currentState(ofType type: T.Type) -> T? { self.storageRef.map.valueRef(ofType: StateRegistry.self)?.body.currentState } - + func currentSchedulesWhichAssociatedStates() -> Set { self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body.schedules } - + func enter(_ state: T) { let stateRegistry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body let schedulesManager = self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body - + schedulesManager.schedules.remove(.onUpdate(stateRegistry.currentState)) schedulesManager.schedules.remove(.onStackUpdate(stateRegistry.currentState)) - + // will exit for system in self.storageRef.systemStorage.systems(.willExit(stateRegistry.currentState)) { system.execute(self.storageRef) } - + self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body.currentState = state - + // did enter for system in self.storageRef.systemStorage.systems(.didEnter(state)) { system.execute(self.storageRef) } - + schedulesManager.schedules.insert(.onUpdate(state)) schedulesManager.schedules.insert(.onStackUpdate(state)) } - + func push(_ state: T) { let registry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body let schedulesManager = self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body - + // on pause for system in self.storageRef.systemStorage.systems(.onPause(registry.currentState)) { system.execute(self.storageRef) } - + schedulesManager.schedules.remove(.onUpdate(registry.currentState)) schedulesManager.schedules.insert(.onInactiveUpdate(registry.currentState)) - + registry.inactiveStates.append(registry.currentState) registry.currentState = state - + schedulesManager.schedules.insert(.onUpdate(state)) schedulesManager.schedules.insert(.onStackUpdate(state)) - + // did enter for system in self.storageRef.systemStorage.systems(.didEnter(state)) { system.execute(self.storageRef) } } - + func pop(_ stateType: T.Type) { let registry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body let schedulesManager = self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body - + // will exit for system in self.storageRef.systemStorage.systems(.willExit(registry.currentState)) { system.execute(self.storageRef) } - + schedulesManager.schedules.remove(.onUpdate(registry.currentState)) schedulesManager.schedules.remove(.onStackUpdate(registry.currentState)) - + registry.currentState = registry.inactiveStates.removeLast() - + // on resume for system in self.storageRef.systemStorage.systems(.onResume(registry.currentState)) { system.execute(self.storageRef) } - + schedulesManager.schedules.remove(.onInactiveUpdate(registry.currentState)) schedulesManager.schedules.insert(.onUpdate(registry.currentState)) } - + } public extension WorldStorageRef { @@ -137,7 +137,7 @@ public extension WorldStorageRef { public extension World { @discardableResult func addState(initialState: T, states: [T]) -> World { self.worldStorage.stateStorage.registerState(initialState: initialState, states: states) - + for state in states { self.worldStorage.systemStorage.insertSchedule(.onUpdate(state)) self.worldStorage.systemStorage.insertSchedule(.onInactiveUpdate(state)) @@ -147,7 +147,7 @@ public extension World { self.worldStorage.systemStorage.insertSchedule(.onPause(state)) self.worldStorage.systemStorage.insertSchedule(.onResume(state)) } - + return self } } diff --git a/Sources/ECS/States/StateAssociatedSchedules.swift b/Sources/ECS/States/StateAssociatedSchedules.swift index be02375..ef7b1ca 100644 --- a/Sources/ECS/States/StateAssociatedSchedules.swift +++ b/Sources/ECS/States/StateAssociatedSchedules.swift @@ -38,31 +38,31 @@ public extension Schedule { static func onUpdate(_ state: T) -> Schedule { Schedule(id: OnUpdate(value: state)) } - + /// `state` が inactive の間の ``World/update(currentTime:)`` 実行時にシステムを実行します. static func onInactiveUpdate(_ state: T) -> Schedule { Schedule(id: OnInactiveUpdate(value: state)) } - + /// `state` が active/inactive 関係なくスタックされている間の ``World/update(currentTime:)`` 実行時にシステムを実行します. static func onStackUpdate(_ state: T) -> Schedule { Schedule(id: OnStackUpdate(value: state)) } - + /// `state` を active にした時にシステムを実行します. static func didEnter(_ state: T) -> Schedule { Schedule(id: DidEnter(value: state)) } - + /// `state` を inactive にした時にシステムを実行します. static func willExit(_ state: T) -> Schedule { Schedule(id: WillExit(value: state)) } - + static func onPause(_ state: T) -> Schedule { Schedule(id: OnPause(value: state)) } - + static func onResume(_ state: T) -> Schedule { Schedule(id: OnResume(value: state)) } diff --git a/Sources/ECS/States/StateControll.swift b/Sources/ECS/States/StateControll.swift index 9d821e0..2b0189c 100644 --- a/Sources/ECS/States/StateControll.swift +++ b/Sources/ECS/States/StateControll.swift @@ -7,14 +7,14 @@ /** ``World`` の状態管理をシステムから行います. - + - note: 詳細は を参照してください. */ final public class State: SystemParameter { let stateStrageRef: StateStorage let currentState: T - + init?(stateStrageRef: StateStorage, currentStaete: T?) { guard let currentStaete = currentStaete else { return nil } self.stateStrageRef = stateStrageRef @@ -22,14 +22,14 @@ final public class State: SystemParameter { } public static func register(to worldStorage: WorldStorageRef) { - + } public static func getParameter(from worldStorage: WorldStorageRef) -> State? { State(stateStrageRef: worldStorage.stateStorage, currentStaete: worldStorage.stateStorage.currentState(ofType: T.self)) } - + /** 別の状態へ遷移します. - Parameter state: 遷移先の `State` を指定します. @@ -37,11 +37,11 @@ final public class State: SystemParameter { public func enter(_ state: T) { self.stateStrageRef.enter(state) } - + /** 現在の状態を inactive にして, 別の状態へ遷移します. - Parameter state: 遷移先の `State` を指定します. - + - `state` の ``Schedule/didEnter(_:)`` と関連づけられたシステムが実行されます. - 以前の状態の ``Schedule/onPause(_:)`` と関連づけられたシステムが実行されます. - `state` の ``Schedule/onUpdate(_:)`` と関連づけられたシステムが常時実行されます. @@ -51,10 +51,10 @@ final public class State: SystemParameter { public func push(_ state: T) { self.stateStrageRef.push(state) } - + /** 1つ前の状態に戻ります. - + - 以前(戻り先)の状態の ``Schedule/onResume(_:)`` と関連づけられたシステムが実行されます. - 戻り先の状態の ``Schedule/onUpdate(_:)`` と関連づけられたシステムが常時実行されます. - 直前の状態の ``Schedule/willExit(_:)`` と関連づけられたシステムが実行されます. @@ -64,5 +64,5 @@ final public class State: SystemParameter { public func pop() { self.stateStrageRef.pop(T.self) } - + } diff --git a/Sources/ECS/System/SystemBuffer.swift b/Sources/ECS/System/SystemBuffer.swift index 6374ded..eee1d8e 100644 --- a/Sources/ECS/System/SystemBuffer.swift +++ b/Sources/ECS/System/SystemBuffer.swift @@ -14,24 +14,25 @@ final public class SystemStorage { final class SystemRegistry: WorldStorageElement { var systems = [Schedule: [SystemExecute]]() } - + let buffer: WorldStorageRef + init(buffer: WorldStorageRef) { self.buffer = buffer } - + public func systems(_ schedule: Schedule) -> [SystemExecute] { self.buffer.map.valueRef(ofType: SystemRegistry.self)!.body.systems[schedule]! } - + func registerSystemRegistry() { self.buffer.map.push(SystemRegistry.init()) } - + func insertSchedule(_ schedule: Schedule) { self.buffer.map.valueRef(ofType: SystemRegistry.self)!.body.systems[schedule] = [] } - + func addSystem(_ schedule: Schedule, _ system: SystemExecute) { self.buffer .map diff --git a/Sources/ECS/SystemParameter/SystemParameter.swift b/Sources/ECS/SystemParameter/SystemParameter.swift index a80fecf..13760cd 100644 --- a/Sources/ECS/SystemParameter/SystemParameter.swift +++ b/Sources/ECS/SystemParameter/SystemParameter.swift @@ -12,4 +12,3 @@ public protocol SystemParameter: AnyObject { static func register(to worldStorage: WorldStorageRef) static func getParameter(from worldStorage: WorldStorageRef) -> Self? } - diff --git a/Sources/ECS/Systems/System.swift b/Sources/ECS/Systems/System.swift index a41d45a..8793c6d 100644 --- a/Sources/ECS/Systems/System.swift +++ b/Sources/ECS/Systems/System.swift @@ -7,11 +7,11 @@ class System: SystemExecute { let execute: (P) -> () - - init(_ execute: @escaping (P) -> ()) { + + init(_ execute: @escaping (P) -> ()) { self.execute = execute } - + override func execute(_ worldStorageRef: WorldStorageRef) { self.execute(P.getParameter(from: worldStorageRef)!) } @@ -30,7 +30,7 @@ public extension EventResponderBuilder { if !self.systems.keys.contains(schedule) { self.systems[schedule] = [] } - + self.systems[schedule]!.append(System

(system)) P.register(to: self.worldStorage) return self diff --git a/Sources/ECS/World/World+Entities.swift b/Sources/ECS/World/World+Entities.swift index 9959b72..1d63dbd 100644 --- a/Sources/ECS/World/World+Entities.swift +++ b/Sources/ECS/World/World+Entities.swift @@ -9,11 +9,11 @@ public extension World { func insert(entity: Entity, entityRecord: EntityRecordRef) { self.entities.insert(entityRecord, withEntity: entity) } - + func remove(entity: Entity) { self.entities.pop(entity: entity) } - + func entityRecord(forEntity entity: Entity) -> EntityRecordRef? { self.entities.value(forEntity: entity) } diff --git a/Sources/ECS/World/World.swift b/Sources/ECS/World/World.swift index 078c456..2231967 100644 --- a/Sources/ECS/World/World.swift +++ b/Sources/ECS/World/World.swift @@ -7,50 +7,50 @@ /** ECS 全体をコントロールします. - + ## Topics - + ### 初期化 - + - ``World/init()`` - + ### System の追加 - + - ``World/addSystem(_:_:)-9frsg`` - ``World/addSystem(_:_:)-86ff2`` - ``World/addSystem(_:_:)-7kznt`` - ``World/addSystem(_:_:)-1s1oy`` - ``World/addSystem(_:_:)-4jv38`` - + ### Event 関連の設定の追加 - + - ``World/addEventStreamer(eventType:)`` - ``World/buildEventResponder(_:_:)`` - ``World/buildDidSpawnResponder(_:)`` - ``World/buildWillDespawnResponder(_:)`` - + ### 起動 - + - ``World/setUpWorld()`` - + ### 更新処理 - + - ``World/update(currentTime:)`` - + ### Event の発信 - + - ``World/sendEvent(_:)`` - + */ final public class World { var entities: SparseSet var updateSchedule: Schedule public let worldStorage: WorldStorageRef - + init(worldStorage: WorldStorageRef) { self.entities = SparseSet(sparse: [], dense: [], data: []) self.updateSchedule = .firstFrame self.worldStorage = worldStorage } - + } diff --git a/Sources/ECS/WorldMethods/World+Init.swift b/Sources/ECS/WorldMethods/World+Init.swift index d3cb05c..d3a733a 100644 --- a/Sources/ECS/WorldMethods/World+Init.swift +++ b/Sources/ECS/WorldMethods/World+Init.swift @@ -8,42 +8,42 @@ public extension World { convenience init() { self.init(worldStorage: WorldStorageRef()) - + // chunk buffer に chunk entity interface を追加します. self.worldStorage.chunkStorage.setUpChunkBuffer() - + // resource buffer に 時間関係の resource を追加します. self.worldStorage.resourceBuffer.addResource(CurrentTime(value: 0)) self.worldStorage.resourceBuffer.addResource(DeltaTime(value: 0)) - + // resrouce buffer に world の情報関係の resource を追加します. self.worldStorage.resourceBuffer.addResource(EntityCount(count: 0)) - + // world storage に system を保持する領域を確保します. self.worldStorage.systemStorage.registerSystemRegistry() - + // world buffer に setup system を保持する領域を確保します. self.worldStorage.systemStorage.insertSchedule(.startUp) - + // world buffer に update system を保持する領域を確保します. self.worldStorage.systemStorage.insertSchedule(.firstFrame) self.worldStorage.systemStorage.insertSchedule(.update) - + // state storage に schedule 管理をするための準備をします. self.worldStorage.stateStorage.setUp() - + // world buffer に event queue を作成します. self.worldStorage.eventStorage.setUpEventQueue() self.worldStorage.eventStorage.setUpCommandsEventQueue(eventOfType: DidSpawnEvent.self) self.worldStorage.eventStorage.setUpCommandsEventQueue(eventOfType: WillDespawnEvent.self) - + // world buffer に spawn/despawn event の streamer を登録します. self.addCommandsEventStreamer(eventType: DidSpawnEvent.self) self.addCommandsEventStreamer(eventType: WillDespawnEvent.self) - + // world buffer に commands の初期値を設定します. self.worldStorage.commandsStorage.setCommands(Commands()) - + // world に一番最初のフレームで実行されるシステムを追加します. self.worldStorage.systemStorage.addSystem(.firstFrame, System(firstFrameSystem(commands:))) } diff --git a/Sources/ECS/WorldMethods/World+SetUp.swift b/Sources/ECS/WorldMethods/World+SetUp.swift index bd13284..6d06a45 100644 --- a/Sources/ECS/WorldMethods/World+SetUp.swift +++ b/Sources/ECS/WorldMethods/World+SetUp.swift @@ -17,12 +17,12 @@ public extension World { } } let commands = self.worldStorage.commandsStorage.commands()! - + self.applyEnityTransactions(commands: commands) self.worldStorage.chunkStorage.applySpawnedEntityQueue() - + self.applyCommands(commands: commands) - + // entity の変更を world 全体に適用. self.worldStorage.chunkStorage.applyUpdatedEntityQueue() } diff --git a/Sources/ECS/WorldMethods/World+Spawn.swift b/Sources/ECS/WorldMethods/World+Spawn.swift index b04fcca..18562a7 100644 --- a/Sources/ECS/WorldMethods/World+Spawn.swift +++ b/Sources/ECS/WorldMethods/World+Spawn.swift @@ -27,18 +27,18 @@ extension World { if entity.generation == 0 { self.entities.allocate() } - + self.insert(entity: entity, entityRecord: entityRecord) self.worldStorage .chunkStorage .pushSpawned(entity: entity, entityRecord: entityRecord) - + self.worldStorage .eventStorage .commandsEventWriter(eventOfType: DidSpawnEvent.self)! .send(value: DidSpawnEvent(spawnedEntity: entity)) } - + /// Entity を削除します. /// /// ``Commands/despawn()`` が実行された後, フレームが終了するタイミングでこの関数が実行されます. @@ -47,7 +47,7 @@ extension World { self.worldStorage .chunkStorage .despawn(entity: entity) - + self.worldStorage .eventStorage .commandsEventWriter(eventOfType: WillDespawnEvent.self)! diff --git a/Sources/ECS/WorldMethods/World+Update.swift b/Sources/ECS/WorldMethods/World+Update.swift index d23b6d0..560e4da 100644 --- a/Sources/ECS/WorldMethods/World+Update.swift +++ b/Sources/ECS/WorldMethods/World+Update.swift @@ -18,49 +18,49 @@ public struct DeltaTime: ResourceProtocol { public extension World { /** ゲームの更新処理を実行します. - + - note: 最初のフレーム (current time = 0) は準備用フレームとして実行されるため, システムが実行されません. */ func update(currentTime: TimeInterval) { let currentTimeResource = self.worldStorage.resourceBuffer.resource(ofType: CurrentTime.self)! - + self.worldStorage.resourceBuffer.resource(ofType: DeltaTime.self)?.resource = DeltaTime(value: currentTime - currentTimeResource.resource.value) - + currentTimeResource.resource = CurrentTime(value: currentTime) - + for system in self.worldStorage.systemStorage.systems(self.updateSchedule) { system.execute(self.worldStorage) } - + // activate な state を shcedule によって紐づけられた system を実行します. for schedule in self.worldStorage.stateStorage.currentSchedulesWhichAssociatedStates() { for system in self.worldStorage.systemStorage.systems(schedule) { system.execute(self.worldStorage) } } - + // world が受信した event を event system に発信します. self.applyEventQueue() - + let commands = self.worldStorage.commandsStorage.commands()! - + // will despawn event を配信します. self.applyCommandsEventQueue(eventOfType: WillDespawnEvent.self) - + // これから spawn する entity を chunk storage 内で enqueue // despawn 登録された entity を削除 // これから spawn する record に addComponent などのコマンドを実行 // セットアップされた record を chunk storage 内で enqueue self.applyEnityTransactions(commands: commands) - + // apply commands の際に push された entity を chunk に割り振ります(spawn). self.worldStorage.chunkStorage.applySpawnedEntityQueue() - + // Did Spawn event を event system に発信します. self.applyCommandsEventQueue(eventOfType: DidSpawnEvent.self) - + self.applyCommands(commands: commands) - + // world 内の entity のコンポーネントの追加/削除. // 同じフレーム内で entity の変更を world 全体に適用するために一番最後に再度実行. self.worldStorage.chunkStorage.applyUpdatedEntityQueue() diff --git a/Sources/ECS_Macros/BundleMacro.swift b/Sources/ECS_Macros/BundleMacro.swift index 342b3be..f6c4f8b 100644 --- a/Sources/ECS_Macros/BundleMacro.swift +++ b/Sources/ECS_Macros/BundleMacro.swift @@ -25,7 +25,7 @@ struct BundleMacro: ExtensionMacro { partialResult.append("record.addComponent(self.\(identifier))\n") } .dropLast() - + let declSyntax: DeclSyntax = """ extension \(type.trimmed): BundleProtocol { @@ -34,11 +34,11 @@ struct BundleMacro: ExtensionMacro { } } """ - + guard let extensionSyntax = declSyntax.as(ExtensionDeclSyntax.self) else { return [] } - + return [ extensionSyntax ] diff --git a/Sources/ECS_Macros/QueryMacro.swift b/Sources/ECS_Macros/QueryMacro.swift index 27a4b28..f6556c0 100644 --- a/Sources/ECS_Macros/QueryMacro.swift +++ b/Sources/ECS_Macros/QueryMacro.swift @@ -20,7 +20,7 @@ struct QueryMacro: DeclarationMacro { fatalError("compiler bug: argument is not integer literal") } let n = Int(intArg.text)! - + let genericArguments = (0..: Chunk, SystemParameter, QueryProtocol { public var components = SparseSet<(\(raw: refTypes))>(sparse: [], dense: [], data: []) public override init() {} - + public func allocate() { self.components.allocate() } - + public func insert(entity: Entity, entityRecord: EntityRecordRef) { guard \(raw: refDeclarationsFromRecord) else { return } self.components.insert((\(raw: refs)), withEntity: entity) } - + public override func spawn(entity: Entity, entityRecord: EntityRecordRef) { if entity.generation == 0 { self.components.allocate() } self.insert(entity: entity, entityRecord: entityRecord) } - + public override func despawn(entity: Entity) { guard self.components.contains(entity) else { return } self.components.pop(entity: entity) } - + override func applyCurrentState(_ entityRecord: EntityRecordRef, forEntity entity: Entity) { guard \(raw: refDeclarationsFromRecord) else { self.despawn(entity: entity) @@ -82,29 +82,29 @@ struct QueryMacro: DeclarationMacro { guard !components.contains(entity) else { return } self.components.insert((\(raw: refs)), withEntity: entity) } - + public func update(_ f: (\(raw: parameters)) -> ()) { 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)) } - + public func components(forEntity entity: Entity) -> (\(raw: valueTypes))? { guard let components = components.value(forEntity: entity) else { return nil } return (\(raw: componentValuess)) } - + public static func register(to worldStorage: WorldStorageRef) { guard worldStorage.chunkStorage.chunk(ofType: Self.self) == nil else { return } let queryRegistory = Self() worldStorage.chunkStorage.addChunk(queryRegistory) } - + public static func getParameter(from worldStorage: WorldStorageRef) -> Self? { worldStorage.chunkStorage.chunk(ofType: Self.self) } diff --git a/Sources/ECS_Macros/SystemMacro.swift b/Sources/ECS_Macros/SystemMacro.swift index 5254335..faa246a 100644 --- a/Sources/ECS_Macros/SystemMacro.swift +++ b/Sources/ECS_Macros/SystemMacro.swift @@ -20,7 +20,7 @@ struct SystemMacro: DeclarationMacro { fatalError("compiler bug: argument is not integer literal") } let n = Int(intArg.text)! - + let genericArguments = (0..: SystemExecute { let execute: (\(raw: valueTypes)) -> () - + init(_ execute: @escaping (\(raw: valueTypes)) -> ()) { self.execute = execute } - + override func execute(_ worldStorageRef: WorldStorageRef) { self.execute(\(raw: parameterGetExpressions)) } @@ -60,7 +60,7 @@ struct AddSystemMacroForWorld: DeclarationMacro { let registerExpressions = (0..(_ schedule: Schedule, _ system: @escaping (\(raw: valueTypes)) -> ()) -> World { self.worldStorage.systemStorage.addSystem(schedule, Systems.System\(raw: n)<\(raw: valueTypes)>(system)) @@ -68,10 +68,10 @@ struct AddSystemMacroForWorld: DeclarationMacro { return self } """ - + return result } - + static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext @@ -83,11 +83,11 @@ struct AddSystemMacroForWorld: DeclarationMacro { fatalError("compiler bug: argument is not integer literal") } let n = Int(intArg.text)! - + let declarations = (2...n).reduce(into: []) { partialResult, i in partialResult.append(createAddSystemDeclaration(i)) } - + return declarations } } @@ -103,7 +103,7 @@ struct AddSystemMacroForEventResponderBuilder: DeclarationMacro { let registerExpressions = (0..(_ schedule: Schedule, _ system: @escaping (\(raw: valueTypes)) -> ()) -> EventResponderBuilder { if !self.systems.keys.contains(schedule) { @@ -114,10 +114,10 @@ struct AddSystemMacroForEventResponderBuilder: DeclarationMacro { return self } """ - + return result } - + static func expansion( of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext @@ -129,11 +129,11 @@ struct AddSystemMacroForEventResponderBuilder: DeclarationMacro { fatalError("compiler bug: argument is not integer literal") } let n = Int(intArg.text)! - + let declarations = (2...n).reduce(into: []) { partialResult, i in partialResult.append(createAddSystemDeclaration(i)) } - + return declarations } } diff --git a/Sources/PlugIns/Graphic2D/Camera.swift b/Sources/PlugIns/Graphic2D/Camera.swift index d985a65..ce60744 100644 --- a/Sources/PlugIns/Graphic2D/Camera.swift +++ b/Sources/PlugIns/Graphic2D/Camera.swift @@ -10,16 +10,15 @@ import ECS public struct Camera: ResourceProtocol { public let camera: SKCameraNode - + public static func setDefaultCamera(scene: SKScene) -> Camera { let cameraNode = SKCameraNode() scene.camera = cameraNode return Camera(camera: cameraNode) } - + public static func createFrom(cameraNamed name: String, inScene scene: SKScene) -> Camera { let cameraNode = scene.childNode(withName: name) as! SKCameraNode return Camera(camera: cameraNode) } } - diff --git a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift index 9edfbb5..43d1583 100644 --- a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift @@ -23,7 +23,7 @@ public struct Parent: Component { } struct _RemoveFromParentTransaction: Component { - + } class AddChild: EntityCommand { @@ -32,12 +32,12 @@ class AddChild: EntityCommand { self.child = child super.init(entity: parent) } - + override func runCommand(forRecord record: EntityRecordRef, inWorld world: World) { let childRecord = world.entityRecord(forEntity: self.child)! childRecord.addComponent(_AddChildNodeTransaction(parentEntity: self.entity)) } - + } class RemoveAllChildren: EntityCommand { @@ -45,13 +45,13 @@ class RemoveAllChildren: EntityCommand { let node = record.component(ofType: Graphic.self)!.nodeRef node.removeAllChildren() record.componentRef(ofType: Parent.self)?.value._children = [] - + for child in record.componentRef(ofType: Parent.self)!.value.children { let childRecord = world.entityRecord(forEntity: child)! childRecord.removeComponent(ofType: Child.self) world.worldStorage.chunkStorage.pushUpdated(entity: child, entityRecord: childRecord) } - + } } @@ -70,17 +70,17 @@ public extension EntityCommands { self.pushCommand(SetGraphic(node: node, entity: self.id())) return self.addComponent(Graphic(node: node)).addComponent(Graphic(node: node)) } - + @discardableResult func addChild(_ entity: Entity) -> Self { self.pushCommand(AddChild(parent: self.id(), child: entity)) return self } - + @discardableResult func removeAllChildren() -> Self { self.pushCommand(RemoveAllChildren(entity: self.id())) return self } - + @discardableResult func removeFromParent() -> Self { self.pushCommand(RemoveFromParent(entity: self.id())) return self diff --git a/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift index 18329cf..bd76db1 100644 --- a/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift @@ -14,17 +14,17 @@ struct _AddChildNodeTransaction: Component { class SetGraphic: EntityCommand { let node: SKNode - + init(node: SKNode, entity: Entity) { self.node = node super.init(entity: entity) } - + func setEntityInfoForNode(_ entity: Entity) { self.node.userData = [:] self.node.userData!["ECS/Entity"] = entity } - + override func runCommand(forRecord record: EntityRecordRef, inWorld world: World) { self.setEntityInfoForNode(entity) @@ -32,5 +32,5 @@ class SetGraphic: EntityCommand { record.addComponent(Parent(_children: [])) record.addComponent(_AddChildNodeTransaction(parentEntity: nil)) } - + } diff --git a/Sources/PlugIns/Graphic2D/Graphic2D.swift b/Sources/PlugIns/Graphic2D/Graphic2D.swift index b8b0827..5c67cb2 100644 --- a/Sources/PlugIns/Graphic2D/Graphic2D.swift +++ b/Sources/PlugIns/Graphic2D/Graphic2D.swift @@ -17,11 +17,11 @@ public struct SceneResource: ResourceProtocol { class GraphicStrongRef: Component { let node: SKNode - + init(node: SKNode) { self.node = node } - + deinit { self.node.removeFromParent() } diff --git a/Sources/PlugIns/Graphic2D/PlugInExport.swift b/Sources/PlugIns/Graphic2D/PlugInExport.swift index e8b18a4..bfb858c 100644 --- a/Sources/PlugIns/Graphic2D/PlugInExport.swift +++ b/Sources/PlugIns/Graphic2D/PlugInExport.swift @@ -31,7 +31,7 @@ func _addChildNodeSystem( } else { scene.resource.scene.addChild(graphic.nodeRef) } - + commands .entity(childEntity) .removeComponent(ofType: _AddChildNodeTransaction.self) @@ -60,14 +60,13 @@ func _addChildNodeSystem( } else { fatalError("parent entity not found") } - + commands .entity(childEntity) .removeComponent(ofType: _AddChildNodeTransaction.self) } } - func _removeFromParentSystem( query: Filtered, Child>, With<_RemoveFromParentTransaction>>, parents: Query, @@ -78,7 +77,7 @@ func _removeFromParentSystem( commands.entity(childEntity) .removeComponent(ofType: Child.self) .removeComponent(ofType: _RemoveFromParentTransaction.self) - + parents.update(child.parent) { parent in parent._children.remove(childEntity) } @@ -91,7 +90,7 @@ public func graphicPlugIn(world: World) { .addSystem(.update, _addChildNodeSystem(query:graphics:scene:commands:)) .addSystem(.update, _addChildNodeSystem(query:graphics:commands:)) .addSystem(.update, _removeFromParentSystem(query:parents:commands:)) - + .buildWillDespawnResponder { responder in responder .addSystem(.update, removeChildIfDespawned(despawnEvent:query:parentQuery:)) diff --git a/Sources/PlugIns/Graphic2D/World+Graphic2D.swift b/Sources/PlugIns/Graphic2D/World+Graphic2D.swift index 1c09b37..abee187 100644 --- a/Sources/PlugIns/Graphic2D/World+Graphic2D.swift +++ b/Sources/PlugIns/Graphic2D/World+Graphic2D.swift @@ -14,4 +14,3 @@ extension World { entityRecord.removeComponent(ofType: GraphicStrongRef.self) } } - diff --git a/Sources/PlugIns/Keyboard/KeyBoard.swift b/Sources/PlugIns/Keyboard/KeyBoard.swift index 4b24efd..291918b 100644 --- a/Sources/PlugIns/Keyboard/KeyBoard.swift +++ b/Sources/PlugIns/Keyboard/KeyBoard.swift @@ -26,7 +26,7 @@ public extension World { func keyDown(with event: NSEvent) { self.sendEvent(KeyDownEvent(nsEvent: event)) } - + func keyUp(with event: NSEvent) { self.sendEvent(KeyUpEvent(nsEvent: event)) } diff --git a/Sources/PlugIns/Mouse/Mouse.swift b/Sources/PlugIns/Mouse/Mouse.swift index d09306a..59b3ab8 100644 --- a/Sources/PlugIns/Mouse/Mouse.swift +++ b/Sources/PlugIns/Mouse/Mouse.swift @@ -46,23 +46,23 @@ public extension World { func mouseEntered(with event: NSEvent) { self.sendEvent(MouseEnteredEvent(nsEvent: event)) } - + func mouseExited(with event: NSEvent) { self.sendEvent(MouseExitedEvent(nsEvent: event)) } - + func mouseMoved(with event: NSEvent) { self.sendEvent(MouseMovedEvent(nsEvent: event)) } - + func mouseDown(with event: NSEvent) { self.sendEvent(MouseDownEvent(nsEvent: event)) } - + func mouseDragged(with event: NSEvent) { self.sendEvent(MouseDraggedEvent(nsEvent: event)) } - + func mouseUp(with event: NSEvent) { self.sendEvent(MouseUpEvent(nsEvent: event)) } diff --git a/Sources/PlugIns/Mouse/TrackableView.swift b/Sources/PlugIns/Mouse/TrackableView.swift index f991bbe..dbcfb0a 100644 --- a/Sources/PlugIns/Mouse/TrackableView.swift +++ b/Sources/PlugIns/Mouse/TrackableView.swift @@ -9,7 +9,7 @@ import SpriteKit class MouseTrackableView: SKView { var sceneTrackingArea: NSTrackingArea? - + func setTrackingArea() { let scene = self.scene! let trackingArea = NSTrackingArea( @@ -19,7 +19,7 @@ class MouseTrackableView: SKView { self.addTrackingArea(trackingArea) self.sceneTrackingArea = trackingArea } - + override func updateTrackingAreas() { if let t = self.sceneTrackingArea { self.removeTrackingArea(t) @@ -27,4 +27,3 @@ class MouseTrackableView: SKView { self.setTrackingArea() } } - diff --git a/Sources/PlugIns/ObjectLink/ObjectLink.swift b/Sources/PlugIns/ObjectLink/ObjectLink.swift index 1715b08..646d9aa 100644 --- a/Sources/PlugIns/ObjectLink/ObjectLink.swift +++ b/Sources/PlugIns/ObjectLink/ObjectLink.swift @@ -15,5 +15,5 @@ public struct Object: Component { } extension Commands { - + } diff --git a/Sources/PlugIns/Scene/Scene.swift b/Sources/PlugIns/Scene/Scene.swift index aa5fa25..56e0895 100644 --- a/Sources/PlugIns/Scene/Scene.swift +++ b/Sources/PlugIns/Scene/Scene.swift @@ -10,27 +10,27 @@ import ECS open class Scene: SKScene { var _world: World! - + open func buildWorld() -> World { World() } - + public var world: World { self._world } - + final public override func didMove(to view: SKView) { self._world = self.buildWorld() self._world?.setUpWorld() } - + final public override func update(_ currentTime: TimeInterval) { self._world?.update(currentTime: currentTime) } - + public func restartWorld() { self._world = self.buildWorld() self._world.setUpWorld() } - + } diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index af092fa..c0d8f1f 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -20,13 +20,13 @@ final class GraphicPlugInTests: XCTestCase { commands.spawn() .setGraphic(SKNode()) } - + world.setUpWorld() world.update(currentTime: -1) // first frame state world.update(currentTime: 0) // update state XCTAssertEqual(scene.children.count, 1) } - + func testAddChild() { let scene = SKScene() let parentNode = SKNode() @@ -58,14 +58,14 @@ final class GraphicPlugInTests: XCTestCase { default: return } } - + world.setUpWorld() world.update(currentTime: -1) // first frame state world.update(currentTime: 0) // update つまりここで graphic system が実行される world.update(currentTime: 1) XCTAssertEqual(parentNode.children.count, 1) } - + func testAddChildIfNotHasNode() { let scene = SKScene() var frags = [0] @@ -78,7 +78,7 @@ final class GraphicPlugInTests: XCTestCase { .id() commands.spawn() .addChild(child_0) - + let child_1 = commands.spawn() .id() commands.spawn() @@ -90,16 +90,16 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(children.components.data.count, 0) } - + world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) world.update(currentTime: 1) - + XCTAssertEqual(frags, [2]) - + } - + // entity hierarchy から取り外す処理のテスト func testRemoveFromParent() { let scene = SKScene() @@ -152,7 +152,7 @@ final class GraphicPlugInTests: XCTestCase { default: break } } - + world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) @@ -160,10 +160,10 @@ final class GraphicPlugInTests: XCTestCase { world.update(currentTime: 2) // この一番最後で _remove from parent tarnsaction 追加 world.update(currentTime: 3) // remove from parent system 実行, 一番最後に component に変更反映 world.update(currentTime: 4) // ここで結果が出る - + XCTAssertEqual(frags, [2, 3]) } - + // SKNode が紐づけられた Entity がデスポーンするときの挙動をてすと func testParentDespawn() { // デスポーンする entity の子 entity もデスポーンする. @@ -205,7 +205,7 @@ final class GraphicPlugInTests: XCTestCase { commands.despawn(entity: entity) } } - + XCTAssertEqual(parents.components.data.count, 3) XCTAssertEqual(children.components.data.count, 2) XCTAssertEqual(totalEntities.components.data.count, 3) @@ -224,7 +224,7 @@ final class GraphicPlugInTests: XCTestCase { fatalError() // ここは通過しません. } } - + world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) @@ -232,10 +232,10 @@ final class GraphicPlugInTests: XCTestCase { world.update(currentTime: 2) world.update(currentTime: 3) world.update(currentTime: 4) - + XCTAssertEqual(frags, [5]) } - + // 子 entity をデスポーンすると、親から観測されなくなります. func testChildDespaen() { let scene = SKScene() @@ -282,14 +282,14 @@ final class GraphicPlugInTests: XCTestCase { fatalError() // ここは通過しません. } } - + world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) world.update(currentTime: 1) world.update(currentTime: 2) world.update(currentTime: 3) - + XCTAssertEqual(frags, [4]) } } diff --git a/Tests/ObjectLinkPlugInTests/ObjectLinkPlugInTests.swift b/Tests/ObjectLinkPlugInTests/ObjectLinkPlugInTests.swift index 4a1fca7..673dfdf 100644 --- a/Tests/ObjectLinkPlugInTests/ObjectLinkPlugInTests.swift +++ b/Tests/ObjectLinkPlugInTests/ObjectLinkPlugInTests.swift @@ -9,5 +9,5 @@ import XCTest @testable import ECS_ObjectLink final class ObjectLinkTests: XCTestCase { - + } diff --git a/Tests/ScenePlugInTests/ScenePlugInTests.swift b/Tests/ScenePlugInTests/ScenePlugInTests.swift index 85bc9a4..23c4e10 100644 --- a/Tests/ScenePlugInTests/ScenePlugInTests.swift +++ b/Tests/ScenePlugInTests/ScenePlugInTests.swift @@ -8,5 +8,5 @@ import XCTest final class ScenePlugInTests: XCTestCase { - + } diff --git a/Tests/ecs-swiftTests/BundleTests.swift b/Tests/ecs-swiftTests/BundleTests.swift index 51bb397..ffeaf0c 100644 --- a/Tests/ecs-swiftTests/BundleTests.swift +++ b/Tests/ecs-swiftTests/BundleTests.swift @@ -21,11 +21,11 @@ final class BundleTests: XCTestCase { frags[0] += 1 XCTAssertEqual(query.components.data.count, 1) } - + world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) - + XCTAssertEqual(frags, [1]) } } diff --git a/Tests/ecs-swiftTests/ChunkTests.swift b/Tests/ecs-swiftTests/ChunkTests.swift index f08f8ad..766d423 100644 --- a/Tests/ecs-swiftTests/ChunkTests.swift +++ b/Tests/ecs-swiftTests/ChunkTests.swift @@ -13,7 +13,7 @@ class TestChunk: Chunk { override func spawn(entity: Entity, entityRecord: EntityRecordRef) { self.entities[entity] = entityRecord } - + override func despawn(entity: Entity) { self.entities.removeValue(forKey: entity) } @@ -24,7 +24,7 @@ class TestChunk_2: Chunk { override func spawn(entity: Entity, entityRecord: EntityRecordRef) { self.entities[entity] = entityRecord } - + override func despawn(entity: Entity) { self.entities.removeValue(forKey: entity) } @@ -34,30 +34,30 @@ final class ChunkTests: XCTestCase { func testInterface() { let mockEntities = [Entity(slot: 0, generation: 0), Entity(slot: 1, generation: 0), Entity(slot: 2, generation: 0), Entity(slot: 3, generation: 0), Entity(slot: 4, generation: 0)] let world = World() - + // Spawn された entity を単に蓄積するだけの test 用の chunk です. let testChunk = TestChunk() let testChunk_2 = TestChunk_2() world.worldStorage.chunkStorage.addChunk(testChunk) world.worldStorage.chunkStorage.addChunk(testChunk_2) - + // chunk interface を介して chunk に entity を push します(回数: 5回). for entity in mockEntities { world.push(entity: entity, entityRecord: EntityRecordRef()) } - + world.worldStorage.chunkStorage.applySpawnedEntityQueue() - + XCTAssertEqual(testChunk.entities.count, 5) XCTAssertEqual(testChunk_2.entities.count, 5) - + // chunk interface を介して chunk から entity を削除します. for entity in mockEntities { world.despawn(entity: entity) } - + XCTAssertEqual(testChunk.entities.count, 0) XCTAssertEqual(testChunk_2.entities.count, 0) - + } } diff --git a/Tests/ecs-swiftTests/CommandsTests.swift b/Tests/ecs-swiftTests/CommandsTests.swift index 7e0867b..d146d92 100644 --- a/Tests/ecs-swiftTests/CommandsTests.swift +++ b/Tests/ecs-swiftTests/CommandsTests.swift @@ -33,26 +33,26 @@ final class CommandsTests: XCTestCase { let world = World() world.worldStorage.commandsStorage.setCommands(Commands()) let commands = world.worldStorage.commandsStorage.commands()! - + let testEntities = [Entity(slot: 0, generation: 0), Entity(slot: 1, generation: 0), Entity(slot: 2, generation: 0)] - + for testEntity in testEntities { commands.push(command: TestCommand_Spawn(entity: testEntity)) } - + XCTAssertEqual(commands.commandQueue.count, 3) world.applyCommands(commands: commands) - + XCTAssertEqual(commands.commandQueue.count, 0) XCTAssertEqual(world.entities.data.count, 3) - + for testEntity in testEntities { commands.push(command: TestCommand_Despawn(entity: testEntity)) } - + XCTAssertEqual(commands.commandQueue.count, 3) world.applyCommands(commands: commands) - + XCTAssertEqual(commands.commandQueue.count, 0) XCTAssertEqual(world.entities.data.count, 0) } diff --git a/Tests/ecs-swiftTests/EntityCommandsTests.swift b/Tests/ecs-swiftTests/EntityCommandsTests.swift index 7d3afdb..c04ba0c 100644 --- a/Tests/ecs-swiftTests/EntityCommandsTests.swift +++ b/Tests/ecs-swiftTests/EntityCommandsTests.swift @@ -13,30 +13,30 @@ final class EntityCommandsTests: XCTestCase { let commands = Commands() let world = World() world.worldStorage.commandsStorage.setCommands(commands) - + // world に entity を生成し, component を追加し, id(id としての entity) を受け取ります. let entity = commands.spawn() .addComponent(TestComponent(content: "test")) .id() commands.spawn() .addComponent(TestComponent(content: "test_2")) - + world.update(currentTime: 0) - + XCTAssertEqual(world.entityRecord(forEntity: entity)!.ref(TestComponent.self)!.value.content, "test") - + commands.entity(entity).removeComponent(ofType: TestComponent.self) - + world.update(currentTime: 0) - + // world 内に entity が存在し, component が削除されていることをテストします. XCTAssertNotNil(world.entityRecord(forEntity: entity)) XCTAssertNil(world.entityRecord(forEntity: entity)!.ref(TestComponent.self)) - + commands.despawn(entity: entity) - + world.update(currentTime: 0) - + // world から entity が削除されたことをテストします. XCTAssertNil(world.entityRecord(forEntity: entity)) } diff --git a/Tests/ecs-swiftTests/EventTests.swift b/Tests/ecs-swiftTests/EventTests.swift index b68ddd9..83c4dc5 100644 --- a/Tests/ecs-swiftTests/EventTests.swift +++ b/Tests/ecs-swiftTests/EventTests.swift @@ -53,7 +53,7 @@ func despanedEntitySystem(eventReader: EventReader, commands: final class EventTests: XCTestCase { func testEvent() { print() - + let world = World() .addEventStreamer(eventType: TestEvent.self) .buildEventResponder(TestEvent.self, { responder in @@ -62,9 +62,9 @@ final class EventTests: XCTestCase { .addSystem(.startUp, setUp(eventWriter:)) .addSystem(.didSpawn, spawnedEntitySystem(eventReader:commands:currentTime:)) .addSystem(.willDespawn, despanedEntitySystem(eventReader:commands:currentTime:)) - + world.setUpWorld() - + world.update(currentTime: 0) world.update(currentTime: 1) world.update(currentTime: 2) diff --git a/Tests/ecs-swiftTests/FilteredQueryTests.swift b/Tests/ecs-swiftTests/FilteredQueryTests.swift index e851da6..62ace4c 100644 --- a/Tests/ecs-swiftTests/FilteredQueryTests.swift +++ b/Tests/ecs-swiftTests/FilteredQueryTests.swift @@ -13,59 +13,59 @@ final class FilteredQueryTests: XCTestCase { let testQueryAnd = Filtered, And, With>>() let testQueryOr = Filtered, Or, With>>() let testQueryWithout = Filtered, And, WithOut>>() - + let world = World() - + world.worldStorage.chunkStorage.addChunk(testQueryAnd) world.worldStorage.chunkStorage.addChunk(testQueryOr) world.worldStorage.chunkStorage.addChunk(testQueryWithout) - + let commands = world.worldStorage.commandsStorage.commands()! - + let entity = commands.spawn() .addComponent(TestComponent(content: "c0")) .id() - + // world.applyCommands(commands: commands) // world.worldStorage.chunkStorage.applySpawnedEntityQueue() world.update(currentTime: 0) - + XCTAssertEqual(testQueryAnd.query.components.data.count, 0) XCTAssertEqual(testQueryOr.query.components.data.count, 0) XCTAssertEqual(testQueryWithout.query.components.data.count, 1) - + commands.entity(entity).addComponent(TestComponent2(content: "c1")) - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQueryAnd.query.components.data.count, 0) XCTAssertEqual(testQueryOr.query.components.data.count, 1) XCTAssertEqual(testQueryWithout.query.components.data.count, 0) - + commands.entity(entity).addComponent(TestComponent3(content: "c2")) world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQueryAnd.query.components.data.count, 1) XCTAssertEqual(testQueryOr.query.components.data.count, 1) XCTAssertEqual(testQueryWithout.query.components.data.count, 0) - + commands.entity(entity).removeComponent(ofType: TestComponent2.self) - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQueryAnd.query.components.data.count, 0) XCTAssertEqual(testQueryOr.query.components.data.count, 1) XCTAssertEqual(testQueryWithout.query.components.data.count, 0) - + commands.entity(entity).removeComponent(ofType: TestComponent.self) - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQueryAnd.query.components.data.count, 0) XCTAssertEqual(testQueryOr.query.components.data.count, 0) XCTAssertEqual(testQueryWithout.query.components.data.count, 0) diff --git a/Tests/ecs-swiftTests/PlugInTests.swift b/Tests/ecs-swiftTests/PlugInTests.swift index 3954b07..3cf66f4 100644 --- a/Tests/ecs-swiftTests/PlugInTests.swift +++ b/Tests/ecs-swiftTests/PlugInTests.swift @@ -8,5 +8,5 @@ import XCTest final class PlugInTests: XCTestCase { - + } diff --git a/Tests/ecs-swiftTests/QueryTests.swift b/Tests/ecs-swiftTests/QueryTests.swift index 5be625a..5d99157 100644 --- a/Tests/ecs-swiftTests/QueryTests.swift +++ b/Tests/ecs-swiftTests/QueryTests.swift @@ -15,126 +15,126 @@ final class QueryTests: XCTestCase { let testQuery3 = Query3() let testQuery4 = Query4() let testQuery5 = Query5() - + let testEntityQuery = Query() let testEntityQuery2 = Query2() let testEntityQuery3 = Query3() let testEntityQuery4 = Query4() let testEntityQuery5 = Query5() - + let world = World() - + world.worldStorage.chunkStorage.addChunk(testQuery) world.worldStorage.chunkStorage.addChunk(testQuery2) world.worldStorage.chunkStorage.addChunk(testQuery3) world.worldStorage.chunkStorage.addChunk(testQuery4) world.worldStorage.chunkStorage.addChunk(testQuery5) - + world.worldStorage.chunkStorage.addChunk(testEntityQuery) world.worldStorage.chunkStorage.addChunk(testEntityQuery2) world.worldStorage.chunkStorage.addChunk(testEntityQuery3) world.worldStorage.chunkStorage.addChunk(testEntityQuery4) world.worldStorage.chunkStorage.addChunk(testEntityQuery5) - + let commands = world.worldStorage.commandsStorage.commands()! - + let testEntity = commands.spawn().addComponent(TestComponent(content: "test")).id() - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 0) XCTAssertEqual(testQuery3.components.data.count, 0) XCTAssertEqual(testQuery4.components.data.count, 0) XCTAssertEqual(testQuery5.components.data.count, 0) - + XCTAssertEqual(testEntityQuery.components.data.count, 1) XCTAssertEqual(testEntityQuery2.components.data.count, 1) XCTAssertEqual(testEntityQuery3.components.data.count, 0) XCTAssertEqual(testEntityQuery4.components.data.count, 0) XCTAssertEqual(testEntityQuery5.components.data.count, 0) - + commands.entity(testEntity).addComponent(TestComponent2(content: "test2")) - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) XCTAssertEqual(testQuery3.components.data.count, 0) XCTAssertEqual(testQuery4.components.data.count, 0) XCTAssertEqual(testQuery5.components.data.count, 0) - + XCTAssertEqual(testEntityQuery.components.data.count, 1) XCTAssertEqual(testEntityQuery2.components.data.count, 1) XCTAssertEqual(testEntityQuery3.components.data.count, 1) XCTAssertEqual(testEntityQuery4.components.data.count, 0) XCTAssertEqual(testEntityQuery5.components.data.count, 0) - + commands.entity(testEntity).addComponent(TestComponent3(content: "test2")) - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) XCTAssertEqual(testQuery3.components.data.count, 1) XCTAssertEqual(testQuery4.components.data.count, 0) XCTAssertEqual(testQuery5.components.data.count, 0) - + XCTAssertEqual(testEntityQuery.components.data.count, 1) XCTAssertEqual(testEntityQuery2.components.data.count, 1) XCTAssertEqual(testEntityQuery3.components.data.count, 1) XCTAssertEqual(testEntityQuery4.components.data.count, 1) XCTAssertEqual(testEntityQuery5.components.data.count, 0) - + commands.entity(testEntity).addComponent(TestComponent4(content: "test2")) - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) XCTAssertEqual(testQuery3.components.data.count, 1) XCTAssertEqual(testQuery4.components.data.count, 1) XCTAssertEqual(testQuery5.components.data.count, 0) - + XCTAssertEqual(testEntityQuery.components.data.count, 1) XCTAssertEqual(testEntityQuery2.components.data.count, 1) XCTAssertEqual(testEntityQuery3.components.data.count, 1) XCTAssertEqual(testEntityQuery4.components.data.count, 1) XCTAssertEqual(testEntityQuery5.components.data.count, 1) - + commands.entity(testEntity).addComponent(TestComponent5(content: "test2")) - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) XCTAssertEqual(testQuery3.components.data.count, 1) XCTAssertEqual(testQuery4.components.data.count, 1) XCTAssertEqual(testQuery5.components.data.count, 1) - + commands.entity(testEntity).removeComponent(ofType: TestComponent.self) - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 0) XCTAssertEqual(testQuery2.components.data.count, 0) XCTAssertEqual(testQuery3.components.data.count, 0) XCTAssertEqual(testQuery4.components.data.count, 0) XCTAssertEqual(testQuery5.components.data.count, 0) - + XCTAssertEqual(testEntityQuery.components.data.count, 1) XCTAssertEqual(testEntityQuery2.components.data.count, 0) XCTAssertEqual(testEntityQuery3.components.data.count, 0) XCTAssertEqual(testEntityQuery4.components.data.count, 0) XCTAssertEqual(testEntityQuery5.components.data.count, 0) } - + // v0.2 以降は entity 追加直後の component 追加処理は扱いが変わるため, それ専用のテストも作成します. func testSpawnedEntityCommands() { let testQuery = Query() @@ -142,109 +142,109 @@ final class QueryTests: XCTestCase { let testQuery3 = Query3() let testQuery4 = Query4() let testQuery5 = Query5() - + let testEntityQuery = Query() let testEntityQuery2 = Query2() let testEntityQuery3 = Query3() let testEntityQuery4 = Query4() let testEntityQuery5 = Query5() - + let world = World() - + world.worldStorage.chunkStorage.addChunk(testQuery) world.worldStorage.chunkStorage.addChunk(testQuery2) world.worldStorage.chunkStorage.addChunk(testQuery3) world.worldStorage.chunkStorage.addChunk(testQuery4) world.worldStorage.chunkStorage.addChunk(testQuery5) - + world.worldStorage.chunkStorage.addChunk(testEntityQuery) world.worldStorage.chunkStorage.addChunk(testEntityQuery2) world.worldStorage.chunkStorage.addChunk(testEntityQuery3) world.worldStorage.chunkStorage.addChunk(testEntityQuery4) world.worldStorage.chunkStorage.addChunk(testEntityQuery5) - + let commands = world.worldStorage.commandsStorage.commands()! - + let testEntity = commands.spawn().addComponent(TestComponent(content: "test")).id() - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 0) XCTAssertEqual(testQuery3.components.data.count, 0) XCTAssertEqual(testQuery4.components.data.count, 0) XCTAssertEqual(testQuery5.components.data.count, 0) - + XCTAssertEqual(testEntityQuery.components.data.count, 1) XCTAssertEqual(testEntityQuery2.components.data.count, 1) XCTAssertEqual(testEntityQuery3.components.data.count, 0) XCTAssertEqual(testEntityQuery4.components.data.count, 0) XCTAssertEqual(testEntityQuery5.components.data.count, 0) - + commands.despawn(entity: testEntity) let testEntity_2 = commands.spawn() .addComponent(TestComponent(content: "test")) .addComponent(TestComponent2(content: "test2")).id() - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) XCTAssertEqual(testQuery3.components.data.count, 0) XCTAssertEqual(testQuery4.components.data.count, 0) XCTAssertEqual(testQuery5.components.data.count, 0) - + XCTAssertEqual(testEntityQuery.components.data.count, 1) XCTAssertEqual(testEntityQuery2.components.data.count, 1) XCTAssertEqual(testEntityQuery3.components.data.count, 1) XCTAssertEqual(testEntityQuery4.components.data.count, 0) XCTAssertEqual(testEntityQuery5.components.data.count, 0) - + commands.despawn(entity: testEntity_2) let testEntity_3 = commands.spawn() .addComponent(TestComponent(content: "test")) .addComponent(TestComponent2(content: "test2")) .addComponent(TestComponent3(content: "test2")).id() - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) XCTAssertEqual(testQuery3.components.data.count, 1) XCTAssertEqual(testQuery4.components.data.count, 0) XCTAssertEqual(testQuery5.components.data.count, 0) - + XCTAssertEqual(testEntityQuery.components.data.count, 1) XCTAssertEqual(testEntityQuery2.components.data.count, 1) XCTAssertEqual(testEntityQuery3.components.data.count, 1) XCTAssertEqual(testEntityQuery4.components.data.count, 1) XCTAssertEqual(testEntityQuery5.components.data.count, 0) - + commands.despawn(entity: testEntity_3) let testEntity_4 = commands.spawn() .addComponent(TestComponent(content: "test")) .addComponent(TestComponent2(content: "test2")) .addComponent(TestComponent3(content: "test2")) .addComponent(TestComponent4(content: "test2")).id() - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) XCTAssertEqual(testQuery3.components.data.count, 1) XCTAssertEqual(testQuery4.components.data.count, 1) XCTAssertEqual(testQuery5.components.data.count, 0) - + XCTAssertEqual(testEntityQuery.components.data.count, 1) XCTAssertEqual(testEntityQuery2.components.data.count, 1) XCTAssertEqual(testEntityQuery3.components.data.count, 1) XCTAssertEqual(testEntityQuery4.components.data.count, 1) XCTAssertEqual(testEntityQuery5.components.data.count, 1) - + commands.despawn(entity: testEntity_4) commands.spawn() .addComponent(TestComponent(content: "test")) @@ -252,10 +252,10 @@ final class QueryTests: XCTestCase { .addComponent(TestComponent3(content: "test2")) .addComponent(TestComponent4(content: "test2")) .addComponent(TestComponent5(content: "test2")) - + world.update(currentTime: 0) world.update(currentTime: 0) - + XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) XCTAssertEqual(testQuery3.components.data.count, 1) @@ -263,5 +263,5 @@ final class QueryTests: XCTestCase { XCTAssertEqual(testQuery5.components.data.count, 1) } - + } diff --git a/Tests/ecs-swiftTests/ResourceTests.swift b/Tests/ecs-swiftTests/ResourceTests.swift index 689a974..be55137 100644 --- a/Tests/ecs-swiftTests/ResourceTests.swift +++ b/Tests/ecs-swiftTests/ResourceTests.swift @@ -8,5 +8,5 @@ import XCTest final class ResourceTests: XCTestCase { - + } diff --git a/Tests/ecs-swiftTests/SystemParameterTests.swift b/Tests/ecs-swiftTests/SystemParameterTests.swift index 528d003..0b7f621 100644 --- a/Tests/ecs-swiftTests/SystemParameterTests.swift +++ b/Tests/ecs-swiftTests/SystemParameterTests.swift @@ -8,5 +8,5 @@ import XCTest final class SystemParameterTests: XCTestCase { - + } diff --git a/Tests/ecs-swiftTests/SystemTests.swift b/Tests/ecs-swiftTests/SystemTests.swift index dd656ea..52bf7c3 100644 --- a/Tests/ecs-swiftTests/SystemTests.swift +++ b/Tests/ecs-swiftTests/SystemTests.swift @@ -12,7 +12,7 @@ func testSetUp(commands: Commands) { print("set up") commands.spawn() .addComponent(TestComponent(content: "sample_1010")) - + commands.spawn() .addComponent(TestComponent(content: "sample_120391-2")) } @@ -37,7 +37,7 @@ func apiTestSystem( q0: Query, q1: Query2 ) { - + } func apiTestSystem( @@ -45,7 +45,7 @@ func apiTestSystem( q1: Query2, q2: Query3 ) { - + } func apiTestSystem( @@ -54,7 +54,7 @@ func apiTestSystem( q2: Query3, q3: Query4 ) { - + } func apiTestSystem( @@ -80,9 +80,9 @@ final class SystemTests: XCTestCase { .addSystem(.update, apiTestSystem(q0:q1:q2:)) .addSystem(.update, apiTestSystem(q0:q1:q2:q3:)) .addSystem(.update, apiTestSystem(q0:q1:q2:q3:q4:)) - + world.setUpWorld() - + world.update(currentTime: 0) world.update(currentTime: 1) world.update(currentTime: 2) diff --git a/Tests/ecs-swiftTests/UpdateSystemTests.swift b/Tests/ecs-swiftTests/UpdateSystemTests.swift index a7e2e98..947d91b 100644 --- a/Tests/ecs-swiftTests/UpdateSystemTests.swift +++ b/Tests/ecs-swiftTests/UpdateSystemTests.swift @@ -24,7 +24,7 @@ final class UpdateSystemTests: XCTestCase { let world = World() .addSystem(.update, mySystem(commands:)) .addSystem(.update, mySystem2(query:)) - + world.update(currentTime: 0) world.update(currentTime: 0) } diff --git a/Tests/ecs-swiftTests/ecs_swiftTests.swift b/Tests/ecs-swiftTests/ecs_swiftTests.swift index b7ca227..a1f92d1 100644 --- a/Tests/ecs-swiftTests/ecs_swiftTests.swift +++ b/Tests/ecs-swiftTests/ecs_swiftTests.swift @@ -22,25 +22,25 @@ func entitycreate2(commands: Commands) { func update(query: Query) { query.update { _ in - + } } func update2(query: Query) { query.update { _ in - + } } func update3(query: Query) { query.update { _ in - + } } func update4(query: Query) { query.update { _ in - + } } @@ -54,14 +54,14 @@ final class ecs_swiftTests: XCTestCase { .addSystem(.startUp, entitycreate(commands:)) .addSystem(.update, update(query:)) world.setUpWorld() - + print(world.entities.data.count) - + measure { world.update(currentTime: 0) } } - + // entity: 20000 // set up: 1 // update: 4 @@ -74,9 +74,9 @@ final class ecs_swiftTests: XCTestCase { .addSystem(.update, update3(query:)) .addSystem(.update, update4(query:)) world.setUpWorld() - + print(world.entities.data.count) - + measure { world.update(currentTime: 0) } From 6ae7ff75f11c93cee758dd36adac78fe936fe0b4 Mon Sep 17 00:00:00 2001 From: RedRimmedBox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 Aug 2024 14:46:41 +0900 Subject: [PATCH 02/53] Add .circleci/config.yml --- .circleci/config.yml | 62 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..be2e03e --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,62 @@ +version: 2.1 + +executors: + swift: + docker: + - image: swift:5.9 + working_directory: ~/project + +jobs: + build: + executor: swift + steps: + - checkout + - run: apt-get update && apt-get install -y curl + - run: + name: Install dependencies + command: swift package resolve + - run: + name: Build project + command: swift build + - run: + name: Run tests + command: swift test + - run: + name: Discord Notify Success + command: | + curl -H "Content-Type: application/json" \ + -d "{ + \"avatar_url\": \"https://github.com/circleci.png\", + \"embeds\": [ + { + \"title\": \"$CIRCLE_PROJECT_REPONAME:$CIRCLE_BRANCH\", + \"description\": \"✅ Build and tests completed successfully!\", + \"url\": \"$CIRCLE_PULL_REQUEST\", + \"color\": 2423811 + } + ] + }" \ + $DISCORD_NOTIFY_URL + when: on_success + - run: + name: Discord Notify Failure + command: | + curl -H "Content-Type: application/json" \ + -d "{ + \"avatar_url\": \"https://github.com/circleci.png\", + \"embeds\": [ + { + \"title\": \"$CIRCLE_PROJECT_REPONAME:$CIRCLE_BRANCH\", + \"description\": \"⚠️ Build or tests failed. Please check the logs.\", + \"url\": \"$CIRCLE_PULL_REQUEST\", + \"color\": 16518915 + } + ] + }" \ + $DISCORD_NOTIFY_URL + when: on_fail + +workflows: + swiftpm_build: + jobs: + - build \ No newline at end of file From 745a50ad51a644c7ce5b9158be6a50aca5a49d51 Mon Sep 17 00:00:00 2001 From: RedRimmedBox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 Aug 2024 16:26:35 +0900 Subject: [PATCH 03/53] Updated config.yml --- .circleci/config.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index be2e03e..0c876e2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,17 +1,12 @@ version: 2.1 -executors: - swift: - docker: - - image: swift:5.9 - working_directory: ~/project - jobs: build: - executor: swift + macos: + xcode: 14.3.1 steps: - checkout - - run: apt-get update && apt-get install -y curl + # - run: apt-get update && apt-get install -y curl - run: name: Install dependencies command: swift package resolve From 16a7cb9468f967a91840771865c8a581824a84b6 Mon Sep 17 00:00:00 2001 From: RedRimmedBox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 Aug 2024 18:50:13 +0900 Subject: [PATCH 04/53] Updated config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c876e2..6039391 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2.1 jobs: build: macos: - xcode: 14.3.1 + xcode: 15.4.0 steps: - checkout # - run: apt-get update && apt-get install -y curl From 45083a27be5f1890cef7a1eb40c3346d479de16d Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 Aug 2024 21:24:34 +0900 Subject: [PATCH 05/53] Remove unnecessary file --- Sources/ECS/System/SystemProtocols.swift | 36 ------------------------ 1 file changed, 36 deletions(-) delete mode 100644 Sources/ECS/System/SystemProtocols.swift diff --git a/Sources/ECS/System/SystemProtocols.swift b/Sources/ECS/System/SystemProtocols.swift deleted file mode 100644 index b6590d2..0000000 --- a/Sources/ECS/System/SystemProtocols.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// UpdateSystemProtocols.swift -// -// -// Created by rrbox on 2023/08/12. -// - -public protocol SystemProtocol { - associatedtype Parameter: SystemParameter -} - -public protocol SystemProtocol2 { - associatedtype P0: SystemParameter - associatedtype P1: SystemParameter -} - -public protocol SystemProtocol3 { - associatedtype P0: SystemParameter - associatedtype P1: SystemParameter - associatedtype P2: SystemParameter -} - -public protocol SystemProtocol4 { - associatedtype P0: SystemParameter - associatedtype P1: SystemParameter - associatedtype P2: SystemParameter - associatedtype P3: SystemParameter -} - -public protocol SystemProtocol5 { - associatedtype P0: SystemParameter - associatedtype P1: SystemParameter - associatedtype P2: SystemParameter - associatedtype P3: SystemParameter - associatedtype P4: SystemParameter -} From 886a86d61ee6f2d2b02528e231eed4d0fb276ab8 Mon Sep 17 00:00:00 2001 From: RedRimmedBox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 Aug 2024 21:34:45 +0900 Subject: [PATCH 06/53] Updated config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6039391..d22688c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,4 +54,4 @@ jobs: workflows: swiftpm_build: jobs: - - build \ No newline at end of file + - build From 5cfec9f545523c9d4a7e3b578f4e3a453c0f288b Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 Aug 2024 21:48:50 +0900 Subject: [PATCH 07/53] Add CircleCI Status Badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b8f48b7..05acefc 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![GitHub issues](https://img.shields.io/github/issues/rrbox/ecs-swift)](https://github.com/rrbox/ecs-swift/issues) [![GitHub License](https://img.shields.io/github/license/rrbox/ecs-swift)](https://github.com/rrbox/ecs-swift/blob/main/LICENSE) [![Swift](https://github.com/rrbox/ecs-swift/actions/workflows/swift.yml/badge.svg?branch=release%2Flatest)](https://github.com/rrbox/ecs-swift/actions/workflows/swift.yml) +[![CircleCI](https://dl.circleci.com/status-badge/img/gh/rrbox/ecs-swift/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/rrbox/ecs-swift/tree/main) `ecs-swift` is a library that implements the Entity Component System (ECS) in Swift. It is used for developing games with ECS design in frameworks such as SpriteKit and SceneKit. From d92b394a28c28e0f3709a759f39257876f045d7a Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 1 Sep 2024 14:40:08 +0900 Subject: [PATCH 08/53] Remove import statement for macro target --- Sources/ECS/Commons/Bundle.swift | 2 -- Sources/ECS/Query/MultiParamatersQuery.swift | 2 -- Sources/ECS/Systems/MultiParametersSystem.swift | 2 -- 3 files changed, 6 deletions(-) diff --git a/Sources/ECS/Commons/Bundle.swift b/Sources/ECS/Commons/Bundle.swift index fc68825..f064d2a 100644 --- a/Sources/ECS/Commons/Bundle.swift +++ b/Sources/ECS/Commons/Bundle.swift @@ -5,8 +5,6 @@ // Created by rrbox on 2024/06/02. // -import ECS_Macros - @attached(extension, names: named(addComponent(forEntity:)), conformances: BundleProtocol) public macro Bundle() = #externalMacro(module: "ECS_Macros", type: "BundleMacro") diff --git a/Sources/ECS/Query/MultiParamatersQuery.swift b/Sources/ECS/Query/MultiParamatersQuery.swift index abdd4a0..ef44fc1 100644 --- a/Sources/ECS/Query/MultiParamatersQuery.swift +++ b/Sources/ECS/Query/MultiParamatersQuery.swift @@ -5,8 +5,6 @@ // Created by rrbox on 2024/06/23. // -import ECS_Macros - @freestanding(declaration, names: arbitrary) macro Query(_ c: Int) = #externalMacro(module: "ECS_Macros", type: "QueryMacro") diff --git a/Sources/ECS/Systems/MultiParametersSystem.swift b/Sources/ECS/Systems/MultiParametersSystem.swift index f20851b..2330709 100644 --- a/Sources/ECS/Systems/MultiParametersSystem.swift +++ b/Sources/ECS/Systems/MultiParametersSystem.swift @@ -5,8 +5,6 @@ // Created by rrbox on 2024/06/30. // -import ECS_Macros - @freestanding(declaration, names: arbitrary) macro System(_ n: Int) = #externalMacro(module: "ECS_Macros", type: "SystemMacro") From 73505b3d867cf19fb58d7ee37201672920dc254d Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 1 Sep 2024 14:40:29 +0900 Subject: [PATCH 09/53] Add ios platform setting --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 0dba14c..887ed6a 100644 --- a/Package.swift +++ b/Package.swift @@ -68,7 +68,7 @@ extension Target { let package = Package( name: "ECS_Swift", - platforms: [.macOS(.v10_15)], + platforms: [.macOS(.v10_15), .iOS(.v13)], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( From 6f80879fba1b0923a8dadae1ac4eab32740b328a Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 1 Sep 2024 21:36:34 +0900 Subject: [PATCH 10/53] Disable specific code for iOS build --- Sources/PlugIns/Keyboard/KeyBoard.swift | 4 ++++ Sources/PlugIns/Mouse/Mouse.swift | 4 ++++ Sources/PlugIns/Mouse/TrackableView.swift | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/Sources/PlugIns/Keyboard/KeyBoard.swift b/Sources/PlugIns/Keyboard/KeyBoard.swift index 291918b..6f000b6 100644 --- a/Sources/PlugIns/Keyboard/KeyBoard.swift +++ b/Sources/PlugIns/Keyboard/KeyBoard.swift @@ -5,6 +5,8 @@ // Created by rrbox on 2023/08/05. // +#if os(macOS) + import ECS import AppKit @@ -31,3 +33,5 @@ public extension World { self.sendEvent(KeyUpEvent(nsEvent: event)) } } + +#endif diff --git a/Sources/PlugIns/Mouse/Mouse.swift b/Sources/PlugIns/Mouse/Mouse.swift index 59b3ab8..820e0d2 100644 --- a/Sources/PlugIns/Mouse/Mouse.swift +++ b/Sources/PlugIns/Mouse/Mouse.swift @@ -5,6 +5,8 @@ // Created by rrbox on 2023/08/05. // +#if os(macOS) + import ECS import AppKit @@ -67,3 +69,5 @@ public extension World { self.sendEvent(MouseUpEvent(nsEvent: event)) } } + +#endif diff --git a/Sources/PlugIns/Mouse/TrackableView.swift b/Sources/PlugIns/Mouse/TrackableView.swift index dbcfb0a..e0a32aa 100644 --- a/Sources/PlugIns/Mouse/TrackableView.swift +++ b/Sources/PlugIns/Mouse/TrackableView.swift @@ -5,6 +5,8 @@ // Created by rrbox on 2023/10/09. // +#if os(macOS) + import SpriteKit class MouseTrackableView: SKView { @@ -27,3 +29,5 @@ class MouseTrackableView: SKView { self.setTrackingArea() } } + +#endif From 6388fb74949ec15ab0067095fe7563c64e9a18e0 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 1 Sep 2024 22:21:36 +0900 Subject: [PATCH 11/53] Add Touch --- Package.swift | 9 +++ Sources/PlugIns/Touch/Touch.swift | 60 +++++++++++++++++++ Tests/TouchPlugInTests/TouchPlugInTests.swift | 35 +++++++++++ 3 files changed, 104 insertions(+) create mode 100644 Sources/PlugIns/Touch/Touch.swift create mode 100644 Tests/TouchPlugInTests/TouchPlugInTests.swift diff --git a/Package.swift b/Package.swift index 887ed6a..cef1b8d 100644 --- a/Package.swift +++ b/Package.swift @@ -29,6 +29,7 @@ extension Module { static let objectLink = Module(name: "ECS_ObjectLink", path: "Sources/PlugIns/ObjectLink") static let scene = Module(name: "ECS_Scene", path: "Sources/PlugIns/Scene") static let scroll = Module(name: "ECS_Scroll", path: "Sources/PlugIns/Scroll") + static let touch = Module(name: "ECS_Touch", path: "Sources/PlugIns/Touch") static let ecs_swiftTests = Module(name: "ecs-swiftTests") static let graphicPlugInTests = Module(name: "GraphicPlugInTests") @@ -37,6 +38,7 @@ extension Module { static let objectLinkPlugInTests = Module(name: "ObjectLinkPlugInTests") static let scenePlugInTests = Module(name: "ScenePlugInTests") static let scrollPlugInTests = Module(name: "ScrollPlugInTests") + static let touchPlugInTests = Module(name: "TouchPlugInTests") } extension Target { @@ -83,6 +85,7 @@ let package = Package( Module.objectLink.name, Module.scene.name, Module.scroll.name, + Module.touch.name, ]), ], dependencies: [ @@ -118,6 +121,9 @@ let package = Package( .target( module: .scroll, dependencies: [.ecs]), + .target( + module: .touch, + dependencies: [.ecs]), .testTarget( module: .ecs_swiftTests, dependencies: [.ecs]), @@ -139,5 +145,8 @@ let package = Package( .testTarget( module: .scrollPlugInTests, dependencies: [.scroll]), + .testTarget( + module: .touchPlugInTests, + dependencies: [.touch]), ] ) diff --git a/Sources/PlugIns/Touch/Touch.swift b/Sources/PlugIns/Touch/Touch.swift new file mode 100644 index 0000000..6f1b4b4 --- /dev/null +++ b/Sources/PlugIns/Touch/Touch.swift @@ -0,0 +1,60 @@ +// +// Touch.swift +// +// +// Created by rrbox on 2024/09/01. +// + +#if os(iOS) + +import ECS +import UIKit + +public struct TouchesBeganEvent: EventProtocol { + public let touches: Set + public let withEvent: UIEvent? +} + +public struct TouchesMovedEvent: EventProtocol { + public let touches: Set + public let withEvent: UIEvent? +} + +public struct TouchesEndedEvent: EventProtocol { + public let touches: Set + public let withEvent: UIEvent? +} + +public struct TouchesCancelled: EventProtocol { + public let touches: Set + public let withEvent: UIEvent? +} + +public func touchPlugIns(_ world: World) { + world + .addEventStreamer(eventType: TouchesBeganEvent.self) + .addEventStreamer(eventType: TouchesMovedEvent.self) + .addEventStreamer(eventType: TouchesEndedEvent.self) + .addEventStreamer(eventType: TouchesCancelled.self) +} + +public extension World { + func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.sendEvent(TouchesBeganEvent(touches: touches, withEvent: event)) + } + + func touchesMoved(_ touches: Set, with event: UIEvent?) { + self.sendEvent(TouchesMovedEvent(touches: touches, withEvent: event)) + } + + func touchesEnded(_ touches: Set, with event: UIEvent?) { + self.sendEvent(TouchesEndedEvent(touches: touches, withEvent: event)) + } + + func touchesCancelled(_ touches: Set, with event: UIEvent?) { + self.sendEvent(TouchesCancelled(touches: touches, withEvent: event)) + } +} + +#endif + diff --git a/Tests/TouchPlugInTests/TouchPlugInTests.swift b/Tests/TouchPlugInTests/TouchPlugInTests.swift new file mode 100644 index 0000000..42d66ac --- /dev/null +++ b/Tests/TouchPlugInTests/TouchPlugInTests.swift @@ -0,0 +1,35 @@ +// +// TouchPlugInTests.swift +// +// +// Created by rrbox on 2024/09/01. +// + +import XCTest + +final class TouchPlugInTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} From 879aa625fcc09b41edcf0a54c859b76f444da4b8 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 1 Sep 2024 22:22:10 +0900 Subject: [PATCH 12/53] Update schemes --- .../xcode/xcshareddata/xcschemes/ECS.xcscheme | 208 ------------------ .../xcschemes/ECS_Swift-Package.xcscheme | 10 + .../xcshareddata/xcschemes/PlugIns.xcscheme | 98 +++++++++ 3 files changed, 108 insertions(+), 208 deletions(-) diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ECS.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ECS.xcscheme index 59fa7e4..d731966 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/ECS.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/ECS.xcscheme @@ -20,118 +20,6 @@ ReferencedContainer = "container:"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Sun, 13 Apr 2025 22:06:44 +0900 Subject: [PATCH 13/53] Prepare tests for the implementation of the new lifecycle --- Sources/ECS/ECS.docc/World/SystemLifeCycle.md | 4 + Tests/ecs-swiftTests/StartUpTest.swift | 84 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 Tests/ecs-swiftTests/StartUpTest.swift diff --git a/Sources/ECS/ECS.docc/World/SystemLifeCycle.md b/Sources/ECS/ECS.docc/World/SystemLifeCycle.md index 5ee0695..208ad3d 100644 --- a/Sources/ECS/ECS.docc/World/SystemLifeCycle.md +++ b/Sources/ECS/ECS.docc/World/SystemLifeCycle.md @@ -4,5 +4,9 @@ ### Topics +- ``Schedule/preStartUp`` - ``Schedule/startUp`` +- ``Schedule/postStartUp`` +- ``Schedule/preUpdate`` - ``Schedule/update`` +- ``Schedule/postUpdate`` diff --git a/Tests/ecs-swiftTests/StartUpTest.swift b/Tests/ecs-swiftTests/StartUpTest.swift new file mode 100644 index 0000000..db54241 --- /dev/null +++ b/Tests/ecs-swiftTests/StartUpTest.swift @@ -0,0 +1,84 @@ +// +// Test.swift +// ECS_Swift +// +// Created by rrbox on 2025/03/16. +// + +import ECS +import Testing + +struct Test { + enum StateCase: StateProtocol { + case title + case inGame + } + + @MainActor + @Test func startUpSystems() async throws { + var flags = [0, 0, 0, 0, 0, 0] + let world = World() + .addSystem(.preStartUp) { (_: Commands) in // 1 + flags[0] += 1 + #expect(flags == [1, 0, 0, 0, 0, 0]) + } + .addSystem(.startUp) { (_: Commands) in // 2 + flags[1] += 1 + #expect(flags == [1, 1, 0, 0, 0, 0]) + } + .addSystem(.postStartUp) { (_: Commands) in // 3 + flags[2] += 1 + #expect(flags == [1, 1, 1, 0, 0, 0]) + } + .addSystem(.preUpdate) { (_: Commands) in // 4 + flags[3] += 1 + #expect(flags == [1, 1, 1, 1, 0, 0]) + } + .addSystem(.update) { (_: Commands) in // 5 + flags[4] += 1 + #expect(flags == [1, 1, 1, 1, 1, 0]) + } + .addSystem(.postUpdate) { (_: Commands) in // 6 + flags[5] += 1 + #expect(flags == [1, 1, 1, 1, 1, 1]) + } + + world.setUpWorld() + + world.update(currentTime: -1) + world.update(currentTime: 0) + + #expect(flags == [1, 1, 1, 1, 1, 1]) + } + + @MainActor + @Test func stateDidEnter() async throws { + var flags = [0, 0, 0, 0] + let world = World() + .addState(initialState: StateCase.title, states: [.title, .inGame]) + .addSystem(.didEnter(StateCase.title), { (_ :Commands) in // 3 + flags[2] += 1 + #expect(flags == [1, 1, 1, 0]) + }) + .addSystem(.preStartUp) { (_: Commands) in // 1 + flags[0] += 1 + #expect(flags == [1, 0, 0, 0]) + } + .addSystem(.startUp) { (_: Commands) in // 2 + flags[1] += 1 + #expect(flags == [1, 1, 0, 0]) + } + .addSystem(.postStartUp) { (_: Commands) in // 4 + flags[3] += 1 + #expect(flags == [1, 1, 1, 1]) + } + + world.setUpWorld() + + world.update(currentTime: -1) + world.update(currentTime: 0) + + #expect(flags == [1, 1, 1, 1]) + } + +} From ff70509df8c50303af258d21d05cebca9a34cbdd Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 13 Apr 2025 22:36:19 +0900 Subject: [PATCH 14/53] Refactor scheduling and lifecycle management in World and Schedule --- Sources/ECS/Schedule/Schedule.swift | 14 +++--- Sources/ECS/World/World.swift | 6 ++- Sources/ECS/WorldMethods/FirstFrame.swift | 27 +++++++++-- Sources/ECS/WorldMethods/World+Init.swift | 26 +++++++---- Sources/ECS/WorldMethods/World+SetUp.swift | 11 ----- Sources/ECS/WorldMethods/World+Update.swift | 34 +++++++++++++- .../GraphicPlugInTests.swift | 46 +++++++++---------- Tests/ecs-swiftTests/BundleTests.swift | 6 +-- Tests/ecs-swiftTests/StartUpTest.swift | 13 +++--- 9 files changed, 118 insertions(+), 65 deletions(-) diff --git a/Sources/ECS/Schedule/Schedule.swift b/Sources/ECS/Schedule/Schedule.swift index fb6d6a0..9c7554b 100644 --- a/Sources/ECS/Schedule/Schedule.swift +++ b/Sources/ECS/Schedule/Schedule.swift @@ -39,12 +39,16 @@ public struct Schedule: Hashable { } enum DefaultSchedule { + case preStartUp case startUp - case firstFrame + case postStartUp + case preUpdate case update + case postUpdate } public extension Schedule { + static let preStartUp: Schedule = .init(id: DefaultSchedule.preStartUp) /** ``World/setUpWorld()`` 実行時にシステムを実行します. @@ -60,7 +64,8 @@ public extension Schedule { ``` */ static let startUp: Schedule = Schedule(id: DefaultSchedule.startUp) - + static let postStartUp: Schedule = .init(id: DefaultSchedule.postStartUp) + static let preUpdate: Schedule = .init(id: DefaultSchedule.preUpdate) /** ``World/update(currentTime:)`` 実行時にシステムを実行します. @@ -92,12 +97,9 @@ public extension Schedule { ``` */ static let update: Schedule = Schedule(id: DefaultSchedule.update) + static let postUpdate: Schedule = .init(id: DefaultSchedule.postUpdate) static func customSchedule(_ value: T) -> Schedule { Schedule(id: value) } } - -extension Schedule { - static let firstFrame: Schedule = Schedule(id: DefaultSchedule.firstFrame) -} diff --git a/Sources/ECS/World/World.swift b/Sources/ECS/World/World.swift index 2231967..28940b6 100644 --- a/Sources/ECS/World/World.swift +++ b/Sources/ECS/World/World.swift @@ -44,12 +44,16 @@ */ final public class World { var entities: SparseSet + var preUpdateSchedule: Schedule var updateSchedule: Schedule + var postUpdateSchedule: Schedule public let worldStorage: WorldStorageRef init(worldStorage: WorldStorageRef) { self.entities = SparseSet(sparse: [], dense: [], data: []) - self.updateSchedule = .firstFrame + self.preUpdateSchedule = .preStartUp + self.updateSchedule = .startUp + self.postUpdateSchedule = .postStartUp self.worldStorage = worldStorage } diff --git a/Sources/ECS/WorldMethods/FirstFrame.swift b/Sources/ECS/WorldMethods/FirstFrame.swift index 2a1138b..191426f 100644 --- a/Sources/ECS/WorldMethods/FirstFrame.swift +++ b/Sources/ECS/WorldMethods/FirstFrame.swift @@ -5,13 +5,32 @@ // Created by rrbox on 2023/11/20. // -class FirstFrameCommand: Command { +private class PreUpdateFirstFrameCommand: Command { + override func runCommand(in world: World) { + world.preUpdateSchedule = .preUpdate + } +} + +private class UpdateSystemFirstFrameCommand: Command { override func runCommand(in world: World) { world.updateSchedule = .update } } -// Delta time resource の設定のため, 一番最初のフレームはスキップします. -func firstFrameSystem(commands: Commands) { - commands.push(command: FirstFrameCommand()) +private class PostUpdateFirstFrameCommand: Command { + override func runCommand(in world: World) { + world.postUpdateSchedule = .postUpdate + } +} + +func preUpdateSystemFirstFrameSystem(commands: Commands) { + commands.push(command: PreUpdateFirstFrameCommand()) +} + +func updateSystemFirstFrameSystem(commands: Commands) { + commands.push(command: UpdateSystemFirstFrameCommand()) +} + +func postUpdateSystemFirstFrameSystem(commands: Commands) { + commands.push(command: PostUpdateFirstFrameCommand()) } diff --git a/Sources/ECS/WorldMethods/World+Init.swift b/Sources/ECS/WorldMethods/World+Init.swift index d3a733a..42d5ece 100644 --- a/Sources/ECS/WorldMethods/World+Init.swift +++ b/Sources/ECS/WorldMethods/World+Init.swift @@ -19,23 +19,29 @@ public extension World { // resrouce buffer に world の情報関係の resource を追加します. self.worldStorage.resourceBuffer.addResource(EntityCount(count: 0)) + let systemStorage = self.worldStorage.systemStorage + let eventStorage = self.worldStorage.eventStorage + // world storage に system を保持する領域を確保します. - self.worldStorage.systemStorage.registerSystemRegistry() + systemStorage.registerSystemRegistry() // world buffer に setup system を保持する領域を確保します. - self.worldStorage.systemStorage.insertSchedule(.startUp) + systemStorage.insertSchedule(.preStartUp) + systemStorage.insertSchedule(.startUp) + systemStorage.insertSchedule(.postStartUp) - // world buffer に update system を保持する領域を確保します. - self.worldStorage.systemStorage.insertSchedule(.firstFrame) - self.worldStorage.systemStorage.insertSchedule(.update) + // world buffer に update system を保持する領域を確保します. + systemStorage.insertSchedule(.preUpdate) + systemStorage.insertSchedule(.update) + systemStorage.insertSchedule(.postUpdate) // state storage に schedule 管理をするための準備をします. self.worldStorage.stateStorage.setUp() // world buffer に event queue を作成します. - self.worldStorage.eventStorage.setUpEventQueue() - self.worldStorage.eventStorage.setUpCommandsEventQueue(eventOfType: DidSpawnEvent.self) - self.worldStorage.eventStorage.setUpCommandsEventQueue(eventOfType: WillDespawnEvent.self) + eventStorage.setUpEventQueue() + eventStorage.setUpCommandsEventQueue(eventOfType: DidSpawnEvent.self) + eventStorage.setUpCommandsEventQueue(eventOfType: WillDespawnEvent.self) // world buffer に spawn/despawn event の streamer を登録します. self.addCommandsEventStreamer(eventType: DidSpawnEvent.self) @@ -45,6 +51,8 @@ public extension World { self.worldStorage.commandsStorage.setCommands(Commands()) // world に一番最初のフレームで実行されるシステムを追加します. - self.worldStorage.systemStorage.addSystem(.firstFrame, System(firstFrameSystem(commands:))) + systemStorage.addSystem(.preStartUp, System(preUpdateSystemFirstFrameSystem(commands:))) + systemStorage.addSystem(.startUp, System(updateSystemFirstFrameSystem(commands:))) + systemStorage.addSystem(.postStartUp, System(postUpdateSystemFirstFrameSystem(commands:))) } } diff --git a/Sources/ECS/WorldMethods/World+SetUp.swift b/Sources/ECS/WorldMethods/World+SetUp.swift index 6d06a45..d322fae 100644 --- a/Sources/ECS/WorldMethods/World+SetUp.swift +++ b/Sources/ECS/WorldMethods/World+SetUp.swift @@ -7,22 +7,11 @@ public extension World { func setUpWorld() { - for system in self.worldStorage.systemStorage.systems(.startUp) { - system.execute(self.worldStorage) - } - // 初期値として設定された state に対して did move schedule で関連づけられた system を実行します. - for schedule in self.worldStorage.map.valueRef(ofType: StateStorage.StatesDidEnterInStartUp.self)!.body.schedules { - for system in self.worldStorage.systemStorage.systems(schedule) { - system.execute(self.worldStorage) - } - } let commands = self.worldStorage.commandsStorage.commands()! self.applyEnityTransactions(commands: commands) self.worldStorage.chunkStorage.applySpawnedEntityQueue() - self.applyCommands(commands: commands) - // entity の変更を world 全体に適用. self.worldStorage.chunkStorage.applyUpdatedEntityQueue() } diff --git a/Sources/ECS/WorldMethods/World+Update.swift b/Sources/ECS/WorldMethods/World+Update.swift index 560e4da..402d894 100644 --- a/Sources/ECS/WorldMethods/World+Update.swift +++ b/Sources/ECS/WorldMethods/World+Update.swift @@ -28,6 +28,31 @@ public extension World { currentTimeResource.resource = CurrentTime(value: currentTime) + let commands = self.worldStorage.commandsStorage.commands()! + + // lifecycle 1: pre update + self.preUpdatePhase() + self.applyCommandsPhase(commands) + + // lifecycle 2: udpate + self.updatePhase() + self.applyCommandsPhase(commands) + + // lifecycle 3: post update + self.postUpdatePhase() + self.applyCommandsPhase(commands) + } +} + +extension World { + func preUpdatePhase() { + let systemStorage = worldStorage.systemStorage + for system in systemStorage.systems(self.preUpdateSchedule) { + system.execute(worldStorage) + } + } + + func updatePhase() { for system in self.worldStorage.systemStorage.systems(self.updateSchedule) { system.execute(self.worldStorage) } @@ -41,9 +66,16 @@ public extension World { // world が受信した event を event system に発信します. self.applyEventQueue() + } - let commands = self.worldStorage.commandsStorage.commands()! + func postUpdatePhase() { + for system in self.worldStorage.systemStorage.systems(self.postUpdateSchedule) { + system.execute(self.worldStorage) + } + } + // 各システムが動いた後に実行される + func applyCommandsPhase(_ commands: Commands) { // will despawn event を配信します. self.applyCommandsEventQueue(eventOfType: WillDespawnEvent.self) diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index c0d8f1f..6befe4a 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -68,7 +68,7 @@ final class GraphicPlugInTests: XCTestCase { func testAddChildIfNotHasNode() { let scene = SKScene() - var frags = [0] + var flags = [0] let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) @@ -86,7 +86,7 @@ final class GraphicPlugInTests: XCTestCase { .addChild(child_1) } .addSystem(.update) { (children: Query, parents: Query2) in - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(children.components.data.count, 0) } @@ -96,7 +96,7 @@ final class GraphicPlugInTests: XCTestCase { world.update(currentTime: 0) world.update(currentTime: 1) - XCTAssertEqual(frags, [2]) + XCTAssertEqual(flags, [2]) } @@ -104,7 +104,7 @@ final class GraphicPlugInTests: XCTestCase { func testRemoveFromParent() { let scene = SKScene() let parentNode = SKNode() - var frags = [0, 0] + var flags = [0, 0] let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) @@ -122,11 +122,11 @@ final class GraphicPlugInTests: XCTestCase { switch currentTime.resource.value { case -1: fatalError() // ここは通過しない. case 0: - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(parentNode.children.count, 1) XCTAssertEqual(children.components.data.count, 0) case 1: - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(children.components.data.count, 1) default: return } @@ -136,17 +136,17 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(parents.components.data.count, 2) switch currentTime.resource.value { case 2: - frags[1] += 1 + flags[1] += 1 children.update { entity in commands.entity(entity) .removeFromParent() } case 3: - frags[1] += 1 + flags[1] += 1 XCTAssertEqual(children.query.components.data.count, 1) XCTAssertEqual(parentNode.children.count, 0) case 4: - frags[1] += 1 + flags[1] += 1 XCTAssertEqual(children.query.components.data.count, 0) XCTAssertEqual(parentNode.children.count, 0) default: break @@ -161,7 +161,7 @@ final class GraphicPlugInTests: XCTestCase { world.update(currentTime: 3) // remove from parent system 実行, 一番最後に component に変更反映 world.update(currentTime: 4) // ここで結果が出る - XCTAssertEqual(frags, [2, 3]) + XCTAssertEqual(flags, [2, 3]) } // SKNode が紐づけられた Entity がデスポーンするときの挙動をてすと @@ -169,7 +169,7 @@ final class GraphicPlugInTests: XCTestCase { // デスポーンする entity の子 entity もデスポーンする. let scene = SKScene() let parent = SKNode() - var frags = [0] + var flags = [0] let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) @@ -189,17 +189,17 @@ final class GraphicPlugInTests: XCTestCase { switch currentTime.resource.value { case -1: fatalError() // ここは通過しません. case 0: - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(parents.components.data.count, 3) XCTAssertEqual(children.components.data.count, 0) XCTAssertEqual(totalEntities.components.data.count, 3) case 1: - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(parents.components.data.count, 3) XCTAssertEqual(children.components.data.count, 2) XCTAssertEqual(totalEntities.components.data.count, 3) case 2: - frags[0] += 1 + flags[0] += 1 parents.update { entity, parent in if parent.children.count == 1 { commands.despawn(entity: entity) @@ -210,13 +210,13 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(children.components.data.count, 2) XCTAssertEqual(totalEntities.components.data.count, 3) case 3: - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(parents.components.data.count, 1) XCTAssertEqual(children.components.data.count, 1) XCTAssertEqual(totalEntities.components.data.count, 1) // このフレームの時だけ、world に存在しない 親を持つ子がいることになる. case 4: - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(parents.components.data.count, 0) XCTAssertEqual(children.components.data.count, 0) XCTAssertEqual(totalEntities.components.data.count, 0) @@ -233,13 +233,13 @@ final class GraphicPlugInTests: XCTestCase { world.update(currentTime: 3) world.update(currentTime: 4) - XCTAssertEqual(frags, [5]) + XCTAssertEqual(flags, [5]) } // 子 entity をデスポーンすると、親から観測されなくなります. func testChildDespaen() { let scene = SKScene() - var frags = [0] + var flags = [0] let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) @@ -255,17 +255,17 @@ final class GraphicPlugInTests: XCTestCase { switch currentTime.resource.value { case -1: fatalError() // ここは通過しません. case 0: - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(children.query.components.data.count, 0) XCTAssertEqual(totalEntities.components.data.count, 2) case 1: - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(children.query.components.data.count, 1) XCTAssertEqual(totalEntities.components.data.count, 2) case 2: - frags[0] += 1 + flags[0] += 1 children.update { entity in commands.despawn(entity: entity) } @@ -274,7 +274,7 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(children.query.components.data.count, 1) XCTAssertEqual(totalEntities.components.data.count, 2) case 3: - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(parents.components.data.count, 1) XCTAssertEqual(children.query.components.data.count, 0) XCTAssertEqual(totalEntities.components.data.count, 1) @@ -290,6 +290,6 @@ final class GraphicPlugInTests: XCTestCase { world.update(currentTime: 2) world.update(currentTime: 3) - XCTAssertEqual(frags, [4]) + XCTAssertEqual(flags, [4]) } } diff --git a/Tests/ecs-swiftTests/BundleTests.swift b/Tests/ecs-swiftTests/BundleTests.swift index ffeaf0c..4b0d4ea 100644 --- a/Tests/ecs-swiftTests/BundleTests.swift +++ b/Tests/ecs-swiftTests/BundleTests.swift @@ -10,7 +10,7 @@ import XCTest final class BundleTests: XCTestCase { func testAddBundle() { - var frags = [0] + var flags = [0] let world = World() .addSystem(.startUp) { (commands: Commands) in commands @@ -18,7 +18,7 @@ final class BundleTests: XCTestCase { .addBundle(TestBundle()) } .addSystem(.update) { (query: Query5) in - frags[0] += 1 + flags[0] += 1 XCTAssertEqual(query.components.data.count, 1) } @@ -26,6 +26,6 @@ final class BundleTests: XCTestCase { world.update(currentTime: -1) world.update(currentTime: 0) - XCTAssertEqual(frags, [1]) + XCTAssertEqual(flags, [1]) } } diff --git a/Tests/ecs-swiftTests/StartUpTest.swift b/Tests/ecs-swiftTests/StartUpTest.swift index db54241..319a6c3 100644 --- a/Tests/ecs-swiftTests/StartUpTest.swift +++ b/Tests/ecs-swiftTests/StartUpTest.swift @@ -56,17 +56,17 @@ struct Test { var flags = [0, 0, 0, 0] let world = World() .addState(initialState: StateCase.title, states: [.title, .inGame]) - .addSystem(.didEnter(StateCase.title), { (_ :Commands) in // 3 - flags[2] += 1 - #expect(flags == [1, 1, 1, 0]) + .addSystem(.didEnter(StateCase.title), { (_ :Commands) in // 2 + flags[1] += 1 + #expect(flags == [1, 1, 0, 0]) }) .addSystem(.preStartUp) { (_: Commands) in // 1 flags[0] += 1 #expect(flags == [1, 0, 0, 0]) } - .addSystem(.startUp) { (_: Commands) in // 2 - flags[1] += 1 - #expect(flags == [1, 1, 0, 0]) + .addSystem(.startUp) { (_: Commands) in // 3 + flags[2] += 1 + #expect(flags == [1, 1, 1, 0]) } .addSystem(.postStartUp) { (_: Commands) in // 4 flags[3] += 1 @@ -76,7 +76,6 @@ struct Test { world.setUpWorld() world.update(currentTime: -1) - world.update(currentTime: 0) #expect(flags == [1, 1, 1, 1]) } From 024c8fbea92edbe065303e9e9d0c65f55a198233 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 27 Apr 2025 22:11:23 +0900 Subject: [PATCH 15/53] Update StateTests --- Tests/ecs-swiftTests/StateTests.swift | 205 ++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 Tests/ecs-swiftTests/StateTests.swift diff --git a/Tests/ecs-swiftTests/StateTests.swift b/Tests/ecs-swiftTests/StateTests.swift new file mode 100644 index 0000000..d337a64 --- /dev/null +++ b/Tests/ecs-swiftTests/StateTests.swift @@ -0,0 +1,205 @@ +// +// StateTests.swift +// ECS_Swift +// +// Created by rrbox on 2025/03/30. +// + +import ECS +import Testing + +struct StateTests { + + enum StateCase: StateProtocol { + case title + case inGame + case pause + } + + @Test func transitionState() async throws { + var flags: [Int] = .init(repeating: 0, count: 6) + + let world = World() + .addState(initialState: StateCase.title, states: [.title, .inGame, .pause]) + .addSystem(.didEnter(StateCase.title)) { (currentTime: Resource) in + flags[0] += 1 + #expect(flags == [1, 0, 0, 0, 0, 0]) + #expect(currentTime.resource.value == -1) + } + .addSystem(.onUpdate(StateCase.title)) { (currentTime: Resource, + state: State) in + flags[1] += 1 + #expect(flags == [1, 1, 0, 0, 0, 0]) + #expect(currentTime.resource.value == -1) + state.enter(.inGame) + } + .addSystem(.willExit(StateCase.title), { (currentTime: Resource) in + flags[2] += 1 + #expect(flags == [1, 1, 1, 0, 0, 0]) + #expect(currentTime.resource.value == 0) + }) + .addSystem(.didEnter(StateCase.inGame)) { (currentTime: Resource) in + flags[3] += 1 + #expect(flags == [1, 1, 1, 1, 0, 0]) + #expect(currentTime.resource.value == 0) + } + .addSystem(.onUpdate(StateCase.inGame)) { (currentTime: Resource) in + flags[4] += 1 + #expect(flags == [1, 1, 1, 1, 1, 1]) + #expect(currentTime.resource.value == 0) + } + .addSystem(.update) { (currentTime: Resource) in + flags[5] += 1 + #expect(currentTime.resource.value == 0) + } + + // didEnter title, on update title + world.update(currentTime: -1) + + // will exit title, did enter inGame, on update inGame + world.update(currentTime: 0) + + #expect(flags == [1, 1, 1, 1, 1, 1]) + } + + // case 1: `push` in start up + @Test func pushStateInStartUp() async throws { + var flags = [Int](repeating: 0, count: 6) + + let world = World() + .addState(initialState: StateCase.inGame, states: [.inGame, .pause]) + .addSystem(.onStackUpdate(StateCase.inGame)) { (currentTime: Resource) in + flags[0] += 1 + + if flags == [0, 0, 0, 0, 0, 0] { + #expect(currentTime.resource.value == 0) + } + } + .addSystem(.startUp, { (state: State, + currentTime: Resource) in + state.push(.pause) + #expect(currentTime.resource.value == -1) + }) + .addSystem(.onPause(StateCase.inGame)) { (currentTime: Resource) in + flags[1] += 1 + #expect(flags == [1, 1, 0, 0, 0, 0]) + #expect(currentTime.resource.value == 0) + } + .addSystem(.didEnter(StateCase.pause)) { (currentTime: Resource) in + flags[2] += 1 + #expect(flags == [1, 1, 1, 0, 0, 0]) + #expect(currentTime.resource.value == 0) + } + .addSystem(.onStackUpdate(StateCase.pause)) { (currentTime: Resource) in + flags[3] += 1 + #expect(currentTime.resource.value == 0) + } + .addSystem(.onUpdate(StateCase.pause)) { (currentTime: Resource) in + flags[4] += 1 + #expect(currentTime.resource.value == 0) + } + .addSystem(.onInactiveUpdate(StateCase.inGame)) { (currentTime: Resource) in + flags[5] += 1 + #expect(currentTime.resource.value == 0) + } + + // didEnter inGame, update inGame, onStackUpdate inGame + world.update(currentTime: -1) + + // onPause inGame, didEnter pause, [onStackUpdate inGame, onStackUpdate pause, onUpdate pause, onInactiveUpdate inGame] + world.update(currentTime: 0) + + #expect(flags == [2, 1, 1, 1, 1, 1]) + } + + // case 2: push in `update` + @Test func pushStateInUpdate() async throws { + var flags = [Int](repeating: 0, count: 6) + + let world = World() + .addState(initialState: StateCase.inGame, states: [.inGame, .pause]) + .addSystem(.onStackUpdate(StateCase.inGame)) { (currentTime: Resource) in + flags[0] += 1 + } + .addSystem(.update, { (state: State, + currentTime: Resource) in + if currentTime.resource.value == 0 { + state.push(.pause) + } + }) + .addSystem(.onPause(StateCase.inGame)) { (currentTime: Resource) in + flags[1] += 1 + #expect(flags == [2, 1, 0, 0, 0, 0]) + #expect(currentTime.resource.value == 1) + } + .addSystem(.didEnter(StateCase.pause)) { (currentTime: Resource) in + flags[2] += 1 + #expect(flags == [2, 1, 1, 0, 0, 0]) + #expect(currentTime.resource.value == 1) + } + .addSystem(.onStackUpdate(StateCase.pause)) { (currentTime: Resource) in + flags[3] += 1 + #expect(currentTime.resource.value == 1) + } + .addSystem(.onUpdate(StateCase.pause)) { (currentTime: Resource) in + flags[4] += 1 + #expect(currentTime.resource.value == 1) + } + .addSystem(.onInactiveUpdate(StateCase.inGame)) { (currentTime: Resource) in + flags[5] += 1 + #expect(currentTime.resource.value == 1) + } + + // didEnter inGame, update inGame, onStackUpdate inGame + world.update(currentTime: -1) + + // push pause, update inGame, onStackUpdate inGame + world.update(currentTime: 0) + + // onPause inGame, didEnter pause, [onStackUpdate inGame, onStackUpdate pause, onUpdate pause, onInactiveUpdate inGame] + world.update(currentTime: 1) + + #expect(flags == [3, 1, 1, 1, 1, 1]) + } + + // case 3: pop in `update` + // + // - start up で pop するケースは存在しない. + @Test func popStateInUpdate() async throws { + var flags = [Int](repeating: 0, count: 2) + + let world = World() + .addState(initialState: StateCase.inGame, states: [.inGame, .pause]) + .addSystem(.startUp, { (state: State, + currentTime: Resource) in + state.push(.pause) + #expect(currentTime.resource.value == -1) + }) + .addSystem(.update) { (state: State, currentTime: Resource) in + if currentTime.resource.value == 0 { + state.pop() + } + } + .addSystem(.willExit(StateCase.pause)) { (currentTime: Resource) in + flags[0] += 1 + #expect(flags == [1, 0]) + #expect(currentTime.resource.value == 1) + } + .addSystem(.onResume(StateCase.inGame)) { (currentTime: Resource) in + flags[1] += 1 + #expect(flags == [1, 1]) + #expect(currentTime.resource.value == 1) + } + + // didEnter inGame, update inGame, onStackUpdate inGame, push pause + world.update(currentTime: -1) + + // onPause inGame, didEnter pause, [onStackUpdate inGame, onStackUpdate pause, onUpdate pause, onInactiveUpdate inGame], pop pause + world.update(currentTime: 0) + + // onResume inGame, willExit pause + world.update(currentTime: 1) + + #expect(flags == [1, 1]) + } +} From e45c1020bb9f2c79fc550bcb1187c76140ed55af Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 27 Apr 2025 22:11:35 +0900 Subject: [PATCH 16/53] Refactor state management in StateStorage and World to enhance state transition handling --- Sources/ECS/States/State.swift | 240 ++++++++++++++----- Sources/ECS/WorldMethods/World+Update.swift | 71 ++++++ Sources/PlugIns/Graphic2D/PlugInExport.swift | 1 + 3 files changed, 247 insertions(+), 65 deletions(-) diff --git a/Sources/ECS/States/State.swift b/Sources/ECS/States/State.swift index d89ad78..49c6a18 100644 --- a/Sources/ECS/States/State.swift +++ b/Sources/ECS/States/State.swift @@ -9,26 +9,88 @@ public protocol StateProtocol: Hashable { } -public class StateStorage { - class StateRegistry: WorldStorageElement { - var currentState: T - var inactiveStates = [T]() - - init(currentState: T) { - self.currentState = currentState - } +class StateRegistry: WorldStorageElement { + var currentState: T + var inactiveStates = [T]() + init(currentState: T) { + self.currentState = currentState } +} +public class StateStorage { class StateAssociatedSchedules: WorldStorageElement { var schedules = Set() } - class StatesDidEnterInStartUp: WorldStorageElement { - // world 構築時に初期値として設定された state を一時的に保持します. - // world の start up 時に didEnter system が実行されます. - // start up 実行後も保持され続けられるため, world 初期化のために start up を実行した場合も同じ効果が得られます. - var schedules = Set() + class StateTransitionQueue: WorldStorageElement { + private(set) var didEnterQueue = [Schedule]() + private(set) var willExitQueue = [Schedule]() + private(set) var onResumeQueue = [Schedule]() + private(set) var onPauseQueue = [Schedule]() + private(set) var onUpdateNewStateQueue = [Schedule]() + private(set) var onUpdatePreviousStateQueue = [Schedule]() + private(set) var onStackUpdateNewStateQueue = [Schedule]() + private(set) var onStackUpdatePreviousStateQueue = [Schedule]() + private(set) var onInactiveUpdateNewStateQueue = [Schedule]() + private(set) var onInactiveUpdatePreviousStateQueue = [Schedule]() + + // enter + + func enqueueEntered(state: T) { + didEnterQueue.append(.didEnter(state)) + onUpdateNewStateQueue.append(.onUpdate(state)) + onStackUpdateNewStateQueue.append(.onStackUpdate(state)) + } + + func enqueueExited(state: T) { + willExitQueue.append(.willExit(state)) + onUpdatePreviousStateQueue.append(.onUpdate(state)) + onStackUpdatePreviousStateQueue.append(.onStackUpdate(state)) + } + + // push + + func enqueuePushed(state: T) { + didEnterQueue.append(.didEnter(state)) + onUpdateNewStateQueue.append(.onUpdate(state)) + onStackUpdateNewStateQueue.append(.onStackUpdate(state)) + } + + func enqueuePaused(state: T) { + onPauseQueue.append(.onPause(state)) + onUpdatePreviousStateQueue.append(.onUpdate(state)) + onInactiveUpdateNewStateQueue.append(.onInactiveUpdate(state)) + } + + // pop + + func enqueuePopped(state: T) { + onUpdatePreviousStateQueue.append(.onUpdate(state)) + onStackUpdatePreviousStateQueue.append(.onStackUpdate(state)) + willExitQueue.append(.willExit(state)) + } + + func enqueueResumed(state: T) { + onResumeQueue.append(.onResume(state)) + onUpdateNewStateQueue.append(.onUpdate(state)) + onInactiveUpdatePreviousStateQueue.append(.onInactiveUpdate(state)) + } + + // clear + + func clear() { + didEnterQueue = [] + willExitQueue = [] + onResumeQueue = [] + onPauseQueue = [] + onUpdatePreviousStateQueue = [] + onUpdateNewStateQueue = [] + onStackUpdatePreviousStateQueue = [] + onStackUpdateNewStateQueue = [] + onInactiveUpdatePreviousStateQueue = [] + onInactiveUpdateNewStateQueue = [] + } } let storageRef: WorldStorageRef @@ -39,13 +101,13 @@ public class StateStorage { func setUp() { self.storageRef.map.push(StateAssociatedSchedules()) - self.storageRef.map.push(StatesDidEnterInStartUp()) + self.storageRef.map.push(StateTransitionQueue()) } func registerState(initialState: T, states: [T]) { self.storageRef.map.push(StateRegistry(currentState: initialState)) - self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body.schedules.insert(.onUpdate(initialState)) - self.storageRef.map.valueRef(ofType: StatesDidEnterInStartUp.self)!.body.schedules.insert(.didEnter(initialState)) + let queue = self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)!.body + queue.enqueueEntered(state: initialState) } func currentState(ofType type: T.Type) -> T? { @@ -56,74 +118,105 @@ public class StateStorage { self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body.schedules } - func enter(_ state: T) { - let stateRegistry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body - let schedulesManager = self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body + // MARK: - queue - schedulesManager.schedules.remove(.onUpdate(stateRegistry.currentState)) - schedulesManager.schedules.remove(.onStackUpdate(stateRegistry.currentState)) + func didEnterQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .didEnterQueue + } - // will exit - for system in self.storageRef.systemStorage.systems(.willExit(stateRegistry.currentState)) { - system.execute(self.storageRef) - } + func willExitQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .willExitQueue + } - self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body.currentState = state + func onPauseQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .onPauseQueue + } - // did enter - for system in self.storageRef.systemStorage.systems(.didEnter(state)) { - system.execute(self.storageRef) - } + func onResumeQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .onResumeQueue + } - schedulesManager.schedules.insert(.onUpdate(state)) - schedulesManager.schedules.insert(.onStackUpdate(state)) + func onUpdatePreviousStateQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .onUpdatePreviousStateQueue } - func push(_ state: T) { - let registry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body - let schedulesManager = self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body + func onStackUpdatePreviousStateQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .onStackUpdatePreviousStateQueue + } - // on pause - for system in self.storageRef.systemStorage.systems(.onPause(registry.currentState)) { - system.execute(self.storageRef) - } + func onUpdateNewStateQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .onUpdateNewStateQueue + } - schedulesManager.schedules.remove(.onUpdate(registry.currentState)) - schedulesManager.schedules.insert(.onInactiveUpdate(registry.currentState)) + func onStackUpdateNewStateQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .onStackUpdateNewStateQueue + } - registry.inactiveStates.append(registry.currentState) - registry.currentState = state + func onInactiveNewStateQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .onInactiveUpdateNewStateQueue + } - schedulesManager.schedules.insert(.onUpdate(state)) - schedulesManager.schedules.insert(.onStackUpdate(state)) + func onInactivePreviousStateQueue() -> [Schedule] { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body + .onInactiveUpdatePreviousStateQueue + } - // did enter - for system in self.storageRef.systemStorage.systems(.didEnter(state)) { - system.execute(self.storageRef) - } + func clearQueue() { + self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + .body.clear() } - func pop(_ stateType: T.Type) { - let registry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body - let schedulesManager = self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body + // MARK: - commands - // will exit - for system in self.storageRef.systemStorage.systems(.willExit(registry.currentState)) { - system.execute(self.storageRef) - } + func enter(_ state: T) { + let stateRegistry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body + let queue = self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)!.body + let previous = stateRegistry.currentState + + queue.enqueueExited(state: previous) + queue.enqueueEntered(state: state) + stateRegistry.currentState = state + } - schedulesManager.schedules.remove(.onUpdate(registry.currentState)) - schedulesManager.schedules.remove(.onStackUpdate(registry.currentState)) + func push(_ state: T) { + let stateRegistry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body + let queue = self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)!.body + let previous = stateRegistry.currentState - registry.currentState = registry.inactiveStates.removeLast() + queue.enqueuePaused(state: previous) + queue.enqueuePushed(state: state) + stateRegistry.inactiveStates.append(previous) + stateRegistry.currentState = state + } - // on resume - for system in self.storageRef.systemStorage.systems(.onResume(registry.currentState)) { - system.execute(self.storageRef) - } + func pop(_ stateType: T.Type) { + let registry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body + let queue = self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)!.body + let previeous = registry.currentState + let new = registry.inactiveStates.removeLast() - schedulesManager.schedules.remove(.onInactiveUpdate(registry.currentState)) - schedulesManager.schedules.insert(.onUpdate(registry.currentState)) + queue.enqueuePopped(state: previeous) + queue.enqueueResumed(state: new) + registry.currentState = new } } @@ -135,6 +228,23 @@ public extension WorldStorageRef { } public extension World { + /// Adds a set of states to the world and registers their associated schedules. + /// + /// - Parameters: + /// - initialState: The initial state to be registered. This state must conform to `StateProtocol`. + /// - states: An array of states to be added to the world. Each state must conform to `StateProtocol`. + /// - Returns: The current `World` instance, allowing for method chaining. + /// + /// This method performs the following actions: + /// 1. Registers the initial state and the provided states with the world's state storage. + /// 2. For each state in the `states` array, it inserts the following schedules into the world's system storage: + /// - `.onUpdate`: Called when the state is updated. + /// - `.onInactiveUpdate`: Called when the state is updated while inactive. + /// - `.onStackUpdate`: Called when the state is updated while on the stack. + /// - `.didEnter`: Called when the state is entered. This is also invoked for the initialState during the world's startup phase. + /// - `.willExit`: Called when the state is about to exit. + /// - `.onPause`: Called when the state is paused. + /// - `.onResume`: Called when the state is resumed. @discardableResult func addState(initialState: T, states: [T]) -> World { self.worldStorage.stateStorage.registerState(initialState: initialState, states: states) diff --git a/Sources/ECS/WorldMethods/World+Update.swift b/Sources/ECS/WorldMethods/World+Update.swift index 402d894..ccadada 100644 --- a/Sources/ECS/WorldMethods/World+Update.swift +++ b/Sources/ECS/WorldMethods/World+Update.swift @@ -47,9 +47,80 @@ public extension World { extension World { func preUpdatePhase() { let systemStorage = worldStorage.systemStorage + let stateStorage = worldStorage.stateStorage + let stateSchedulesManager = worldStorage + .map + .valueRef(ofType: StateStorage.StateAssociatedSchedules.self)! + .body + + let willExitQueue = stateStorage.willExitQueue() + let didEnterQueue = stateStorage.didEnterQueue() + let onPauseQueue = stateStorage.onPauseQueue() + let onResumeQueue = stateStorage.onResumeQueue() + + let onUpdatePreviousStateQueue = stateStorage.onUpdatePreviousStateQueue() + let onUpdateNewStateQueue = stateStorage.onUpdateNewStateQueue() + let onStackUpdatePreviousStateQueue = stateStorage.onStackUpdatePreviousStateQueue() + let onStackUpdateNewStateQueue = stateStorage.onStackUpdateNewStateQueue() + let onInactiveUpdatePreviousStateQueue = stateStorage.onInactivePreviousStateQueue() + let onInactiveUpdateNewStateQueue = stateStorage.onInactiveNewStateQueue() + + stateStorage.clearQueue() + for system in systemStorage.systems(self.preUpdateSchedule) { system.execute(worldStorage) } + + // state が queue に入っていたら will exit と did enter を呼び出す + // queue を for-loop して state を取り出す + + for previousState in onUpdatePreviousStateQueue { + stateSchedulesManager.schedules.remove(previousState) + } + + for previousState in onStackUpdatePreviousStateQueue { + stateSchedulesManager.schedules.remove(previousState) + } + + for previousState in onInactiveUpdatePreviousStateQueue { + stateSchedulesManager.schedules.remove(previousState) + } + + for willExit in willExitQueue { + for system in systemStorage.systems(willExit) { + system.execute(worldStorage) + } + } + + for onPause in onPauseQueue { + for system in systemStorage.systems(onPause) { + system.execute(worldStorage) + } + } + + for onResume in onResumeQueue { + for system in systemStorage.systems(onResume) { + system.execute(worldStorage) + } + } + + for didEnter in didEnterQueue { + for system in systemStorage.systems(didEnter) { + system.execute(worldStorage) + } + } + + for newState in onUpdateNewStateQueue { + stateSchedulesManager.schedules.insert(newState) + } + + for newState in onStackUpdateNewStateQueue { + stateSchedulesManager.schedules.insert(newState) + } + + for newState in onInactiveUpdateNewStateQueue { + stateSchedulesManager.schedules.insert(newState) + } } func updatePhase() { diff --git a/Sources/PlugIns/Graphic2D/PlugInExport.swift b/Sources/PlugIns/Graphic2D/PlugInExport.swift index bfb858c..6c9545b 100644 --- a/Sources/PlugIns/Graphic2D/PlugInExport.swift +++ b/Sources/PlugIns/Graphic2D/PlugInExport.swift @@ -85,6 +85,7 @@ func _removeFromParentSystem( } +@MainActor public func graphicPlugIn(world: World) { world .addSystem(.update, _addChildNodeSystem(query:graphics:scene:commands:)) From 7cd36f97705370c021fcf8399e4e7d170cb81b84 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 27 Apr 2025 22:29:03 +0900 Subject: [PATCH 17/53] Update GraphicPlugInTests to revise assertions for entity counts --- Tests/GraphicPlugInTests/GraphicPlugInTests.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index 6befe4a..07606b3 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -210,10 +210,11 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(children.components.data.count, 2) XCTAssertEqual(totalEntities.components.data.count, 3) case 3: + // TODO: - テスト項目を見直す flags[0] += 1 - XCTAssertEqual(parents.components.data.count, 1) - XCTAssertEqual(children.components.data.count, 1) - XCTAssertEqual(totalEntities.components.data.count, 1) + XCTAssertEqual(parents.components.data.count, 0) + XCTAssertEqual(children.components.data.count, 0) + XCTAssertEqual(totalEntities.components.data.count, 0) // このフレームの時だけ、world に存在しない 親を持つ子がいることになる. case 4: flags[0] += 1 From 7f56f9a396b66026fbb89689c277366a3bb53f8f Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 27 Apr 2025 22:49:45 +0900 Subject: [PATCH 18/53] Update Xcode version in CircleCI configuration to 16.1.0 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d22688c..489011f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2.1 jobs: build: macos: - xcode: 15.4.0 + xcode: 16.1.0 steps: - checkout # - run: apt-get update && apt-get install -y curl From 7288a1a9974b35a4581d2f253364f9fc92f91437 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 27 Apr 2025 22:51:07 +0900 Subject: [PATCH 19/53] Update macOS version in GitHub Actions workflow to 14 --- .github/workflows/swift.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index c14106d..1fa4fb6 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: macos-13 + runs-on: macos-14 steps: - uses: actions/checkout@v3 From 143e548d36a13505d0d763874fa10c1b50aefbc9 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Wed, 30 Apr 2025 01:24:12 +0900 Subject: [PATCH 20/53] Update README --- README.md | 4 ++-- README_ja.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 05acefc..a4f9601 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,12 @@ From the developer of this tool, [@rrbox](https://github.com/rrbox). ### Requirements - Xcode 14.3 -- Swift 5.8 +- Swift 5.9 ### Swift package ```swift -// swift-tools-version:5.8 +// swift-tools-version:5.9 import PackageDescription diff --git a/README_ja.md b/README_ja.md index 1328c0d..aa47279 100644 --- a/README_ja.md +++ b/README_ja.md @@ -23,12 +23,12 @@ ### Requirements - Xcode 14.3 -- Swift 5.8 +- Swift 5.9 ### Swift Package Manager ```swift -// swift-tools-version:5.8 +// swift-tools-version:5.9 import PackageDescription From 0ab4b997b50621ccf491df335f88b3f87ef1f9b8 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 May 2025 21:32:06 +0900 Subject: [PATCH 21/53] Add Nodes --- .../Commands/EntityCommands+Graphic.swift | 10 +++---- .../Graphic2D/Commands/SetGraphic.swift | 5 ++-- Sources/PlugIns/Graphic2D/Graphic2D.swift | 12 -------- Sources/PlugIns/Graphic2D/Nodes.swift | 30 +++++++++++++++++++ .../PlugIns/Graphic2D/World+Graphic2D.swift | 1 - 5 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 Sources/PlugIns/Graphic2D/Nodes.swift diff --git a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift index 43d1583..27f8544 100644 --- a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift @@ -61,13 +61,11 @@ class RemoveFromParent: EntityCommand { } } -public protocol GraphicCommands { - @discardableResult func setGraphic(_ node: Node) -> Self -} - public extension EntityCommands { - @discardableResult func setGraphic(_ node: Node) -> Self { - self.pushCommand(SetGraphic(node: node, entity: self.id())) + @discardableResult func setGraphic(_ nodeCreate: Nodes.NodeCreate) -> Self { + let node = nodeCreate.node + nodeCreate.register(id(), node) + self.pushCommand(SetGraphic(node: node, entity: id())) return self.addComponent(Graphic(node: node)).addComponent(Graphic(node: node)) } diff --git a/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift index bd76db1..35f8261 100644 --- a/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift @@ -27,10 +27,9 @@ class SetGraphic: EntityCommand { override func runCommand(forRecord record: EntityRecordRef, inWorld world: World) { self.setEntityInfoForNode(entity) - - record.addComponent(GraphicStrongRef(node: self.node)) + record.addComponent(Parent(_children: [])) record.addComponent(_AddChildNodeTransaction(parentEntity: nil)) } - + } diff --git a/Sources/PlugIns/Graphic2D/Graphic2D.swift b/Sources/PlugIns/Graphic2D/Graphic2D.swift index 5c67cb2..2fcbe2d 100644 --- a/Sources/PlugIns/Graphic2D/Graphic2D.swift +++ b/Sources/PlugIns/Graphic2D/Graphic2D.swift @@ -15,18 +15,6 @@ public struct SceneResource: ResourceProtocol { } } -class GraphicStrongRef: Component { - let node: SKNode - - init(node: SKNode) { - self.node = node - } - - deinit { - self.node.removeFromParent() - } -} - public struct Graphic: Component { public unowned let nodeRef: Node init(node: Node) { diff --git a/Sources/PlugIns/Graphic2D/Nodes.swift b/Sources/PlugIns/Graphic2D/Nodes.swift new file mode 100644 index 0000000..7a834cd --- /dev/null +++ b/Sources/PlugIns/Graphic2D/Nodes.swift @@ -0,0 +1,30 @@ +// +// Nodes.swift +// ECS_Swift +// +// Created by rrbox on 2025/05/01. +// + +import ECS +import SpriteKit + +@MainActor +public final class Nodes: ResourceProtocol { + public struct NodeCreate { + let node: Node + let register: (Entity, Node) -> Void + } + + var store = [Entity: SKNode]() + + public func create(node: Node) -> NodeCreate { + return .init( + node: node, + register: regiester(entity:node:) + ) + } + + func regiester(entity: Entity, node: Node) { + store[entity] = node + } +} diff --git a/Sources/PlugIns/Graphic2D/World+Graphic2D.swift b/Sources/PlugIns/Graphic2D/World+Graphic2D.swift index abee187..a5acf59 100644 --- a/Sources/PlugIns/Graphic2D/World+Graphic2D.swift +++ b/Sources/PlugIns/Graphic2D/World+Graphic2D.swift @@ -11,6 +11,5 @@ import ECS extension World { func removeGraphic(fromEntity entity: Entity) { let entityRecord = self.entityRecord(forEntity: entity)! - entityRecord.removeComponent(ofType: GraphicStrongRef.self) } } From 044931f10b6c2668c26a2ab2ea3d7fc81c9fbd2a Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 May 2025 21:32:24 +0900 Subject: [PATCH 22/53] Update graphic system tests --- .../GraphicPlugInTests.swift | 97 +++++++++++-------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index 07606b3..d9fe62a 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -10,20 +10,21 @@ import ECS_Graphic import SpriteKit import XCTest +@MainActor final class GraphicPlugInTests: XCTestCase { func testSetGraphic() { - let scene = SKScene() + let scene = SKScene() // 画面 let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) - .addSystem(.startUp) { (commands: Commands) in + .addSystem(.startUp) { (commands: Commands, nodes: Resource) in + let node = SKNode() commands.spawn() - .setGraphic(SKNode()) + .setGraphic(nodes.resource.create(node: node)) } world.setUpWorld() world.update(currentTime: -1) // first frame state - world.update(currentTime: 0) // update state XCTAssertEqual(scene.children.count, 1) } @@ -33,28 +34,31 @@ final class GraphicPlugInTests: XCTestCase { let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) - .addSystem(.update) { (commands: Commands, currentTime: Resource) in + .addSystem(.update) { (commands: Commands, currentTime: Resource, nodes: Resource) in if currentTime.resource.value == 0 { + let childNode = SKNode() let child = commands.spawn() - .setGraphic(SKNode()) + .setGraphic(nodes.resource.create(node: childNode)) .id() commands.spawn() - .setGraphic(parentNode) + .setGraphic(nodes.resource.create(node: parentNode)) .addChild(child) } } - .addSystem(.update) { (children: Query, parents: Query2, currentTime: Resource, commands: Commands) in + .addSystem(.update) { ( + children: Query, + parents: Query3>, + currentTime: Resource, + commands: Commands + ) in switch currentTime.resource.value { case -1: fatalError() // ここは通過しない. case 0: - break - case 1: XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(parentNode.children.count, 1) - XCTAssertEqual(children.components.data.count, 0) - case 2: - XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(children.components.data.count, 1) + break + case 1: break default: return } } @@ -72,9 +76,12 @@ final class GraphicPlugInTests: XCTestCase { let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) - .addSystem(.startUp) { (commands: Commands) in + .addSystem(.startUp) { (commands: Commands, nodes: Resource) in + let childNode_0 = SKNode() + let childNode_1 = SKNode() + let child_0 = commands.spawn() - .setGraphic(SKNode()) + .setGraphic(nodes.resource.create(node: childNode_0)) .id() commands.spawn() .addChild(child_0) @@ -82,7 +89,7 @@ final class GraphicPlugInTests: XCTestCase { let child_1 = commands.spawn() .id() commands.spawn() - .setGraphic(SKNode()) + .setGraphic(nodes.resource.create(node: childNode_1)) .addChild(child_1) } .addSystem(.update) { (children: Query, parents: Query2) in @@ -108,12 +115,14 @@ final class GraphicPlugInTests: XCTestCase { let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) - .addSystem(.startUp) { (commands: Commands) in + .addSystem(.startUp) { (commands: Commands, nodes: Resource) in + let childNode = SKNode() + let child = commands.spawn() - .setGraphic(SKNode()) + .setGraphic(nodes.resource.create(node: childNode)) .id() commands.spawn() - .setGraphic(parentNode) + .setGraphic(nodes.resource.create(node: parentNode)) .addChild(child) } .addSystem(.update) { (children: Query, parents: Query2, currentTime: Resource) in @@ -124,9 +133,10 @@ final class GraphicPlugInTests: XCTestCase { case 0: flags[0] += 1 XCTAssertEqual(parentNode.children.count, 1) - XCTAssertEqual(children.components.data.count, 0) - case 1: + XCTAssertEqual(children.components.data.count, 1) + case 1: // FIXME: - 削除 flags[0] += 1 + XCTAssertEqual(parentNode.children.count, 1) XCTAssertEqual(children.components.data.count, 1) default: return } @@ -143,9 +153,9 @@ final class GraphicPlugInTests: XCTestCase { } case 3: flags[1] += 1 - XCTAssertEqual(children.query.components.data.count, 1) + XCTAssertEqual(children.query.components.data.count, 0) XCTAssertEqual(parentNode.children.count, 0) - case 4: + case 4: // FIXME: - 削除 flags[1] += 1 XCTAssertEqual(children.query.components.data.count, 0) XCTAssertEqual(parentNode.children.count, 0) @@ -156,10 +166,10 @@ final class GraphicPlugInTests: XCTestCase { world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) - world.update(currentTime: 1) + world.update(currentTime: 1) // FIXME: - 削除 world.update(currentTime: 2) // この一番最後で _remove from parent tarnsaction 追加 - world.update(currentTime: 3) // remove from parent system 実行, 一番最後に component に変更反映 - world.update(currentTime: 4) // ここで結果が出る + world.update(currentTime: 3) // remove from parent system 実行, 一番最後に component に変更反映 | ここで結果が出る + world.update(currentTime: 4) // FIXME: - 削除 XCTAssertEqual(flags, [2, 3]) } @@ -173,16 +183,19 @@ final class GraphicPlugInTests: XCTestCase { let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) - .addSystem(.startUp) { (commands: Commands) in + .addSystem(.startUp) { (commands: Commands, nodes: Resource) in + let grandChildNode = SKNode() + let childNode = SKNode() + let grandchild = commands.spawn() - .setGraphic(SKNode()) + .setGraphic(nodes.resource.create(node: grandChildNode)) .id() let child = commands.spawn() - .setGraphic(SKNode()) + .setGraphic(nodes.resource.create(node: childNode)) .addChild(grandchild) .id() commands.spawn() - .setGraphic(parent) + .setGraphic(nodes.resource.create(node: parent)) .addChild(child) } .addSystem(.update) { (currentTime: Resource, commands: Commands, children: Query, parents: Query2, totalEntities: Query) in @@ -191,9 +204,9 @@ final class GraphicPlugInTests: XCTestCase { case 0: flags[0] += 1 XCTAssertEqual(parents.components.data.count, 3) - XCTAssertEqual(children.components.data.count, 0) + XCTAssertEqual(children.components.data.count, 2) XCTAssertEqual(totalEntities.components.data.count, 3) - case 1: + case 1: // FIXME: - 削除 flags[0] += 1 XCTAssertEqual(parents.components.data.count, 3) XCTAssertEqual(children.components.data.count, 2) @@ -216,7 +229,7 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(children.components.data.count, 0) XCTAssertEqual(totalEntities.components.data.count, 0) // このフレームの時だけ、world に存在しない 親を持つ子がいることになる. - case 4: + case 4: // FIXME: - 削除 flags[0] += 1 XCTAssertEqual(parents.components.data.count, 0) XCTAssertEqual(children.components.data.count, 0) @@ -229,10 +242,10 @@ final class GraphicPlugInTests: XCTestCase { world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) - world.update(currentTime: 1) + world.update(currentTime: 1) // FIXME: - 削除 world.update(currentTime: 2) world.update(currentTime: 3) - world.update(currentTime: 4) + world.update(currentTime: 4) // FIXME: - 削除 XCTAssertEqual(flags, [5]) } @@ -244,12 +257,14 @@ final class GraphicPlugInTests: XCTestCase { let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) - .addSystem(.startUp) { (commands: Commands) in + .addSystem(.startUp) { (commands: Commands, nodes: Resource) in + let childNode = SKNode() + let parentNode = SKNode() let child = commands.spawn() - .setGraphic(SKNode()) + .setGraphic(nodes.resource.create(node: childNode)) .id() commands.spawn() - .setGraphic(SKNode()) + .setGraphic(nodes.resource.create(node: parentNode)) .addChild(child) } .addSystem(.update) { (currentTime: Resource, commands: Commands, children: Filtered, With>, parents: Query2, totalEntities: Query) in @@ -258,9 +273,9 @@ final class GraphicPlugInTests: XCTestCase { case 0: flags[0] += 1 XCTAssertEqual(parents.components.data.count, 2) - XCTAssertEqual(children.query.components.data.count, 0) + XCTAssertEqual(children.query.components.data.count, 1) XCTAssertEqual(totalEntities.components.data.count, 2) - case 1: + case 1: // FIXME: - 削除 flags[0] += 1 XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(children.query.components.data.count, 1) @@ -287,7 +302,7 @@ final class GraphicPlugInTests: XCTestCase { world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) - world.update(currentTime: 1) + world.update(currentTime: 1) // FIXME: - 削除 world.update(currentTime: 2) world.update(currentTime: 3) From 8361868ce63c44305e113ae2ba0e1fb0537613a6 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 18 May 2025 22:00:46 +0900 Subject: [PATCH 23/53] Update graphic system to use postStartUp and postUpdate phases --- Sources/PlugIns/Graphic2D/PlugInExport.swift | 10 +++++++--- Tests/GraphicPlugInTests/GraphicPlugInTests.swift | 10 ++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Sources/PlugIns/Graphic2D/PlugInExport.swift b/Sources/PlugIns/Graphic2D/PlugInExport.swift index 6c9545b..a17b85e 100644 --- a/Sources/PlugIns/Graphic2D/PlugInExport.swift +++ b/Sources/PlugIns/Graphic2D/PlugInExport.swift @@ -88,9 +88,13 @@ func _removeFromParentSystem( @MainActor public func graphicPlugIn(world: World) { world - .addSystem(.update, _addChildNodeSystem(query:graphics:scene:commands:)) - .addSystem(.update, _addChildNodeSystem(query:graphics:commands:)) - .addSystem(.update, _removeFromParentSystem(query:parents:commands:)) + .addResource(Nodes()) + .addSystem(.postStartUp, _addChildNodeSystem(query:graphics:scene:commands:)) + .addSystem(.postStartUp, _addChildNodeSystem(query:graphics:commands:)) + .addSystem(.postStartUp, _removeFromParentSystem(query:parents:commands:)) + .addSystem(.postUpdate, _addChildNodeSystem(query:graphics:scene:commands:)) + .addSystem(.postUpdate, _addChildNodeSystem(query:graphics:commands:)) + .addSystem(.postUpdate, _removeFromParentSystem(query:parents:commands:)) .buildWillDespawnResponder { responder in responder diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index d9fe62a..93713c6 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -28,7 +28,7 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(scene.children.count, 1) } - func testAddChild() { + func testAddChildOnUpdate() { let scene = SKScene() let parentNode = SKNode() let world = World() @@ -54,11 +54,13 @@ final class GraphicPlugInTests: XCTestCase { switch currentTime.resource.value { case -1: fatalError() // ここは通過しない. case 0: + XCTAssertEqual(parents.components.data.count, 0) + XCTAssertEqual(parentNode.children.count, 0) + XCTAssertEqual(children.components.data.count, 0) + case 1: XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(parentNode.children.count, 1) XCTAssertEqual(children.components.data.count, 1) - break - case 1: break default: return } } @@ -66,7 +68,7 @@ final class GraphicPlugInTests: XCTestCase { world.setUpWorld() world.update(currentTime: -1) // first frame state world.update(currentTime: 0) // update つまりここで graphic system が実行される - world.update(currentTime: 1) + world.update(currentTime: 1) // FIXME: - 削除 XCTAssertEqual(parentNode.children.count, 1) } From 8bf6b2da3d07ba842681fa5d5ccf441910e1b620 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 1 Jun 2025 21:19:31 +0900 Subject: [PATCH 24/53] Update graphic system to remove graphic --- Sources/PlugIns/Graphic2D/Nodes.swift | 4 +++ Sources/PlugIns/Graphic2D/PlugInExport.swift | 8 ++++-- .../GraphicPlugInTests.swift | 28 ++++++++----------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Sources/PlugIns/Graphic2D/Nodes.swift b/Sources/PlugIns/Graphic2D/Nodes.swift index 7a834cd..c6cef35 100644 --- a/Sources/PlugIns/Graphic2D/Nodes.swift +++ b/Sources/PlugIns/Graphic2D/Nodes.swift @@ -27,4 +27,8 @@ public final class Nodes: ResourceProtocol { func regiester(entity: Entity, node: Node) { store[entity] = node } + + func removeNode(forEntity entity: Entity) { + store.removeValue(forKey: entity) + } } diff --git a/Sources/PlugIns/Graphic2D/PlugInExport.swift b/Sources/PlugIns/Graphic2D/PlugInExport.swift index a17b85e..9fc0a6d 100644 --- a/Sources/PlugIns/Graphic2D/PlugInExport.swift +++ b/Sources/PlugIns/Graphic2D/PlugInExport.swift @@ -67,13 +67,16 @@ func _addChildNodeSystem( } } +@MainActor func _removeFromParentSystem( query: Filtered, Child>, With<_RemoveFromParentTransaction>>, parents: Query, + nodes: Resource, commands: Commands ) { query.update { childEntity, childNode, child in childNode.nodeRef.removeFromParent() + nodes.resource.removeNode(forEntity: childEntity) commands.entity(childEntity) .removeComponent(ofType: Child.self) .removeComponent(ofType: _RemoveFromParentTransaction.self) @@ -82,7 +85,6 @@ func _removeFromParentSystem( parent._children.remove(childEntity) } } - } @MainActor @@ -91,10 +93,10 @@ public func graphicPlugIn(world: World) { .addResource(Nodes()) .addSystem(.postStartUp, _addChildNodeSystem(query:graphics:scene:commands:)) .addSystem(.postStartUp, _addChildNodeSystem(query:graphics:commands:)) - .addSystem(.postStartUp, _removeFromParentSystem(query:parents:commands:)) + .addSystem(.postStartUp, _removeFromParentSystem(query:parents:nodes:commands:)) .addSystem(.postUpdate, _addChildNodeSystem(query:graphics:scene:commands:)) .addSystem(.postUpdate, _addChildNodeSystem(query:graphics:commands:)) - .addSystem(.postUpdate, _removeFromParentSystem(query:parents:commands:)) + .addSystem(.postUpdate, _removeFromParentSystem(query:parents:nodes:commands:)) .buildWillDespawnResponder { responder in responder diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index 93713c6..f5a986a 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -31,6 +31,7 @@ final class GraphicPlugInTests: XCTestCase { func testAddChildOnUpdate() { let scene = SKScene() let parentNode = SKNode() + var flags = [0, 0] let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) @@ -57,19 +58,22 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(parents.components.data.count, 0) XCTAssertEqual(parentNode.children.count, 0) XCTAssertEqual(children.components.data.count, 0) + flags[0] += 1 case 1: XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(parentNode.children.count, 1) XCTAssertEqual(children.components.data.count, 1) + flags[1] += 1 default: return } } world.setUpWorld() world.update(currentTime: -1) // first frame state - world.update(currentTime: 0) // update つまりここで graphic system が実行される - world.update(currentTime: 1) // FIXME: - 削除 + world.update(currentTime: 0) // update: ここで graphic system が実行される + world.update(currentTime: 1) XCTAssertEqual(parentNode.children.count, 1) + XCTAssertEqual(flags, [1, 1]) } func testAddChildIfNotHasNode() { @@ -136,10 +140,6 @@ final class GraphicPlugInTests: XCTestCase { flags[0] += 1 XCTAssertEqual(parentNode.children.count, 1) XCTAssertEqual(children.components.data.count, 1) - case 1: // FIXME: - 削除 - flags[0] += 1 - XCTAssertEqual(parentNode.children.count, 1) - XCTAssertEqual(children.components.data.count, 1) default: return } } @@ -147,17 +147,13 @@ final class GraphicPlugInTests: XCTestCase { // remove from parent 関数の効果をチェック XCTAssertEqual(parents.components.data.count, 2) switch currentTime.resource.value { - case 2: + case 1: flags[1] += 1 children.update { entity in commands.entity(entity) .removeFromParent() } - case 3: - flags[1] += 1 - XCTAssertEqual(children.query.components.data.count, 0) - XCTAssertEqual(parentNode.children.count, 0) - case 4: // FIXME: - 削除 + case 2: flags[1] += 1 XCTAssertEqual(children.query.components.data.count, 0) XCTAssertEqual(parentNode.children.count, 0) @@ -168,12 +164,10 @@ final class GraphicPlugInTests: XCTestCase { world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) - world.update(currentTime: 1) // FIXME: - 削除 - world.update(currentTime: 2) // この一番最後で _remove from parent tarnsaction 追加 - world.update(currentTime: 3) // remove from parent system 実行, 一番最後に component に変更反映 | ここで結果が出る - world.update(currentTime: 4) // FIXME: - 削除 + world.update(currentTime: 1) // この一番最後で _remove from parent tarnsaction 追加 + world.update(currentTime: 2) // remove from parent system 実行, 一番最後に component に変更反映 | ここで結果が出る - XCTAssertEqual(flags, [2, 3]) + XCTAssertEqual(flags, [1, 2]) } // SKNode が紐づけられた Entity がデスポーンするときの挙動をてすと From dddf29790ea3a54bf89e520b575884eb803b412a Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 1 Jun 2025 21:31:39 +0900 Subject: [PATCH 25/53] Update graphic plugin tests to enhance functionality and fix assertions --- .../GraphicPlugInTests.swift | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index f5a986a..6c84278 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -6,7 +6,7 @@ // @testable import ECS -import ECS_Graphic +@testable import ECS_Graphic import SpriteKit import XCTest @@ -175,7 +175,7 @@ final class GraphicPlugInTests: XCTestCase { // デスポーンする entity の子 entity もデスポーンする. let scene = SKScene() let parent = SKNode() - var flags = [0] + var flags = [0, 0, 0, 0] let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) @@ -194,7 +194,14 @@ final class GraphicPlugInTests: XCTestCase { .setGraphic(nodes.resource.create(node: parent)) .addChild(child) } - .addSystem(.update) { (currentTime: Resource, commands: Commands, children: Query, parents: Query2, totalEntities: Query) in + .addSystem(.update) { ( + currentTime: Resource, + commands: Commands, + children: Query, + parents: Query2, + totalEntities: Query, + nodes: Resource + ) in switch currentTime.resource.value { case -1: fatalError() // ここは通過しません. case 0: @@ -202,13 +209,9 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(parents.components.data.count, 3) XCTAssertEqual(children.components.data.count, 2) XCTAssertEqual(totalEntities.components.data.count, 3) - case 1: // FIXME: - 削除 - flags[0] += 1 - XCTAssertEqual(parents.components.data.count, 3) - XCTAssertEqual(children.components.data.count, 2) - XCTAssertEqual(totalEntities.components.data.count, 3) - case 2: - flags[0] += 1 + XCTAssertEqual(nodes.resource.store.count, 3) + case 1: + flags[1] += 1 parents.update { entity, parent in if parent.children.count == 1 { commands.despawn(entity: entity) @@ -218,32 +221,25 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(parents.components.data.count, 3) XCTAssertEqual(children.components.data.count, 2) XCTAssertEqual(totalEntities.components.data.count, 3) - case 3: - // TODO: - テスト項目を見直す - flags[0] += 1 - XCTAssertEqual(parents.components.data.count, 0) - XCTAssertEqual(children.components.data.count, 0) - XCTAssertEqual(totalEntities.components.data.count, 0) - // このフレームの時だけ、world に存在しない 親を持つ子がいることになる. - case 4: // FIXME: - 削除 - flags[0] += 1 + XCTAssertEqual(nodes.resource.store.count, 3) + case 2: + flags[2] += 1 XCTAssertEqual(parents.components.data.count, 0) XCTAssertEqual(children.components.data.count, 0) XCTAssertEqual(totalEntities.components.data.count, 0) + XCTAssertEqual(nodes.resource.store.count, 0) default: - fatalError() // ここは通過しません. + fatalError() } } world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) - world.update(currentTime: 1) // FIXME: - 削除 + world.update(currentTime: 1) world.update(currentTime: 2) - world.update(currentTime: 3) - world.update(currentTime: 4) // FIXME: - 削除 - XCTAssertEqual(flags, [5]) + XCTAssertEqual(flags, [1, 1, 1, 1]) } // 子 entity をデスポーンすると、親から観測されなくなります. From 90c28dbc97c4a4d083aa60daec34d50ad3a00999 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 1 Jun 2025 21:59:20 +0900 Subject: [PATCH 26/53] Add _removeNodeIfDespawned system --- Sources/PlugIns/Graphic2D/PlugInExport.swift | 10 +++++++ .../GraphicPlugInTests.swift | 27 +++++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/Sources/PlugIns/Graphic2D/PlugInExport.swift b/Sources/PlugIns/Graphic2D/PlugInExport.swift index 9fc0a6d..cb9835d 100644 --- a/Sources/PlugIns/Graphic2D/PlugInExport.swift +++ b/Sources/PlugIns/Graphic2D/PlugInExport.swift @@ -87,6 +87,13 @@ func _removeFromParentSystem( } } +@MainActor +func _removeNodeIfDespawned(despawn: EventReader, nodes: Resource) { + let entity = despawn.value.despawnedEntity + nodes.resource.removeNode(forEntity: entity) +} + +// TODO: - Node 操作イベントのハンドリングは他のフェーズでもお同様に行なわなくてもOK? @MainActor public func graphicPlugIn(world: World) { world @@ -102,5 +109,8 @@ public func graphicPlugIn(world: World) { responder .addSystem(.update, removeChildIfDespawned(despawnEvent:query:parentQuery:)) .addSystem(.update, despawnChildIfParentDespawned(despawnedEntityEvent:children:commands:)) + .addSystem(.preUpdate, _removeNodeIfDespawned(despawn:nodes:)) + .addSystem(.update, _removeNodeIfDespawned(despawn:nodes:)) + .addSystem(.postUpdate, _removeNodeIfDespawned(despawn:nodes:)) } } diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index 6c84278..363d4e5 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -50,7 +50,8 @@ final class GraphicPlugInTests: XCTestCase { children: Query, parents: Query3>, currentTime: Resource, - commands: Commands + commands: Commands, + nodes: Resource ) in switch currentTime.resource.value { case -1: fatalError() // ここは通過しない. @@ -58,11 +59,13 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(parents.components.data.count, 0) XCTAssertEqual(parentNode.children.count, 0) XCTAssertEqual(children.components.data.count, 0) + XCTAssertEqual(nodes.resource.store.count, 2) flags[0] += 1 case 1: XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(parentNode.children.count, 1) XCTAssertEqual(children.components.data.count, 1) + XCTAssertEqual(nodes.resource.store.count, 2) flags[1] += 1 default: return } @@ -83,6 +86,7 @@ final class GraphicPlugInTests: XCTestCase { .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) .addSystem(.startUp) { (commands: Commands, nodes: Resource) in + // set graphic されていない entity に対して addChild を動かす let childNode_0 = SKNode() let childNode_1 = SKNode() @@ -98,10 +102,15 @@ final class GraphicPlugInTests: XCTestCase { .setGraphic(nodes.resource.create(node: childNode_1)) .addChild(child_1) } - .addSystem(.update) { (children: Query, parents: Query2) in + .addSystem(.update) { ( + children: Query, + parents: Query2, + nodes: Resource + ) in flags[0] += 1 XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(children.components.data.count, 0) + XCTAssertEqual(nodes.resource.store.count, 2) } world.setUpWorld() @@ -143,7 +152,14 @@ final class GraphicPlugInTests: XCTestCase { default: return } } - .addSystem(.update) { (children: Filtered, With>, parents: Query, commands: Commands, currentTime: Resource) in + .addSystem(.update) { ( + children: Filtered, + With>, + parents: Query, + commands: Commands, + currentTime: Resource, + nodes: Resource + ) in // remove from parent 関数の効果をチェック XCTAssertEqual(parents.components.data.count, 2) switch currentTime.resource.value { @@ -157,6 +173,7 @@ final class GraphicPlugInTests: XCTestCase { flags[1] += 1 XCTAssertEqual(children.query.components.data.count, 0) XCTAssertEqual(parentNode.children.count, 0) + XCTAssertEqual(nodes.resource.store.count, 1) default: break } } @@ -175,7 +192,7 @@ final class GraphicPlugInTests: XCTestCase { // デスポーンする entity の子 entity もデスポーンする. let scene = SKScene() let parent = SKNode() - var flags = [0, 0, 0, 0] + var flags = [0, 0, 0] let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) @@ -239,7 +256,7 @@ final class GraphicPlugInTests: XCTestCase { world.update(currentTime: 1) world.update(currentTime: 2) - XCTAssertEqual(flags, [1, 1, 1, 1]) + XCTAssertEqual(flags, [1, 1, 1]) } // 子 entity をデスポーンすると、親から観測されなくなります. From e44728ae6ed1a349e78a49c0c830ea32ad68424a Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 1 Jun 2025 22:19:15 +0900 Subject: [PATCH 27/53] Refactor child despawn tests to use step order assertions and simplify logic --- .../GraphicPlugInTests.swift | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index 363d4e5..1871f86 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -10,6 +10,21 @@ import SpriteKit import XCTest +func XCTAssertStepOrder( + currentStep: Int, + steps: inout [Int], + file: StaticString = #filePath, + line: UInt = #line +) { + let stepCount = steps.count + var targetSteps = [Int](repeating: 0, count: stepCount) + for i in 0 ... currentStep { + targetSteps[i] += 1 + } + steps[currentStep] += 1 + XCTAssertEqual(targetSteps, steps, file: file, line: line) +} + @MainActor final class GraphicPlugInTests: XCTestCase { func testSetGraphic() { @@ -262,7 +277,7 @@ final class GraphicPlugInTests: XCTestCase { // 子 entity をデスポーンすると、親から観測されなくなります. func testChildDespaen() { let scene = SKScene() - var flags = [0] + var flags = [0, 0, 0] let world = World() .addResource(SceneResource(scene)) .addPlugIn(graphicPlugIn(world:)) @@ -280,17 +295,12 @@ final class GraphicPlugInTests: XCTestCase { switch currentTime.resource.value { case -1: fatalError() // ここは通過しません. case 0: - flags[0] += 1 + XCTAssertStepOrder(currentStep: 0, steps: &flags) XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(children.query.components.data.count, 1) XCTAssertEqual(totalEntities.components.data.count, 2) - case 1: // FIXME: - 削除 - flags[0] += 1 - XCTAssertEqual(parents.components.data.count, 2) - XCTAssertEqual(children.query.components.data.count, 1) - XCTAssertEqual(totalEntities.components.data.count, 2) - case 2: - flags[0] += 1 + case 1: + XCTAssertStepOrder(currentStep: 1, steps: &flags) children.update { entity in commands.despawn(entity: entity) } @@ -298,8 +308,8 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(parents.components.data.count, 2) XCTAssertEqual(children.query.components.data.count, 1) XCTAssertEqual(totalEntities.components.data.count, 2) - case 3: - flags[0] += 1 + case 2: + XCTAssertStepOrder(currentStep: 2, steps: &flags) XCTAssertEqual(parents.components.data.count, 1) XCTAssertEqual(children.query.components.data.count, 0) XCTAssertEqual(totalEntities.components.data.count, 1) @@ -311,10 +321,9 @@ final class GraphicPlugInTests: XCTestCase { world.setUpWorld() world.update(currentTime: -1) world.update(currentTime: 0) - world.update(currentTime: 1) // FIXME: - 削除 + world.update(currentTime: 1) world.update(currentTime: 2) - world.update(currentTime: 3) - XCTAssertEqual(flags, [4]) + XCTAssertEqual(flags, [1, 1, 1]) } } From 588af72b1f32cb7671eea83da3cf1b66e2b3e309 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 8 Jun 2025 21:25:26 +0900 Subject: [PATCH 28/53] Fix test setup by adding initial world update before performance measurement --- Tests/ecs-swiftTests/ecs_swiftTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/ecs-swiftTests/ecs_swiftTests.swift b/Tests/ecs-swiftTests/ecs_swiftTests.swift index a1f92d1..51327e2 100644 --- a/Tests/ecs-swiftTests/ecs_swiftTests.swift +++ b/Tests/ecs-swiftTests/ecs_swiftTests.swift @@ -54,6 +54,7 @@ final class ecs_swiftTests: XCTestCase { .addSystem(.startUp, entitycreate(commands:)) .addSystem(.update, update(query:)) world.setUpWorld() + world.update(currentTime: -1) print(world.entities.data.count) @@ -74,6 +75,7 @@ final class ecs_swiftTests: XCTestCase { .addSystem(.update, update3(query:)) .addSystem(.update, update4(query:)) world.setUpWorld() + world.update(currentTime: -1) print(world.entities.data.count) From f540cbe3f343d3b2bdac48ecdf83f4ab143a1a72 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 15 Jun 2025 09:05:13 +0900 Subject: [PATCH 29/53] Add ECSTAssertStepOrder function for step order assertions in tests --- Tests/ecs-swiftTests/Common/StepOrder.swift | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Tests/ecs-swiftTests/Common/StepOrder.swift diff --git a/Tests/ecs-swiftTests/Common/StepOrder.swift b/Tests/ecs-swiftTests/Common/StepOrder.swift new file mode 100644 index 0000000..352b9d5 --- /dev/null +++ b/Tests/ecs-swiftTests/Common/StepOrder.swift @@ -0,0 +1,23 @@ +// +// StepOrder.swift +// ECS_Swift +// +// Created by rrbox on 2025/06/08. +// + +import XCTest + +func ECSTAssertStepOrder( + currentStep: Int, + steps: inout [Int], + file: StaticString = #filePath, + line: UInt = #line +) { + let stepCount = steps.count + var targetSteps = [Int](repeating: 0, count: stepCount) + for i in 0 ... currentStep { + targetSteps[i] += 1 + } + steps[currentStep] += 1 + XCTAssertEqual(targetSteps, steps, file: file, line: line) +} From dbc83d63a0de2d3d856f66dfd3128cf178d6a0fe Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 15 Jun 2025 16:38:16 +0900 Subject: [PATCH 30/53] Add EventSchedule and StateAssociatedEventSchedules for event handling --- Sources/ECS/Schedule/EventSchedule.swift | 50 +++++++++++++++++++ .../StateAssociatedEventSchedules.swift | 44 ++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 Sources/ECS/Schedule/EventSchedule.swift create mode 100644 Sources/ECS/States/StateAssociatedEventSchedules.swift diff --git a/Sources/ECS/Schedule/EventSchedule.swift b/Sources/ECS/Schedule/EventSchedule.swift new file mode 100644 index 0000000..b8dc59f --- /dev/null +++ b/Sources/ECS/Schedule/EventSchedule.swift @@ -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(id: T) { + self.typeId = ObjectIdentifier(T.self) + self.id = id + } +} + +public extension EventSchedule { + /** + ``World/update(currentTime:)`` 実行時にイベントを受信します. + */ + static let update: Schedule = Schedule(id: DefaultSchedule.update) + + static func customSchedule(_ value: T) -> Schedule { + Schedule(id: value) + } +} diff --git a/Sources/ECS/States/StateAssociatedEventSchedules.swift b/Sources/ECS/States/StateAssociatedEventSchedules.swift new file mode 100644 index 0000000..8781554 --- /dev/null +++ b/Sources/ECS/States/StateAssociatedEventSchedules.swift @@ -0,0 +1,44 @@ +// +// StateAssociatedEventSchedules.swift +// ECS_Swift +// +// Created by rrbox on 2025/06/15. +// + +public extension EventSchedule { + /// `state` が active の間の ``World/update(currentTime:)`` 実行時にイベントを受信します. + static func onUpdate(_ state: T) -> EventSchedule { + EventSchedule(id: OnUpdate(value: state)) + } + + /// `state` が inactive の間の ``World/update(currentTime:)`` 実行時にイベントを受信します. + static func onInactiveUpdate(_ state: T) -> EventSchedule { + EventSchedule(id: OnInactiveUpdate(value: state)) + } + + /// `state` が active/inactive 関係なくスタックされている間の ``World/update(currentTime:)`` 実行時にイベントを受信します. + static func onStackUpdate(_ state: T) -> EventSchedule { + EventSchedule(id: OnStackUpdate(value: state)) + } + + /// `state` を active にした時にイベントを受信します. + static func didEnter(_ state: T) -> EventSchedule { + EventSchedule(id: DidEnter(value: state)) + } + + /// `state` を inactive にした時にイベントを受信します. + static func willExit(_ state: T) -> EventSchedule { + EventSchedule(id: WillExit(value: state)) + } + + /// `state` を pause した時にイベントを受信します. + static func onPause(_ state: T) -> EventSchedule { + EventSchedule(id: OnPause(value: state)) + } + + /// `state` が resume された時にイベントを受信します. + static func onResume(_ state: T) -> EventSchedule { + EventSchedule(id: OnResume(value: state)) + } +} + From 5a9c0b15ebe7cf006e6bc794e6e1bb5d2acba4dd Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 15 Jun 2025 21:16:46 +0900 Subject: [PATCH 31/53] Refactor event handling to use EventSchedule --- Sources/ECS/Event/EventStreaming/Event.swift | 2 +- .../Event/EventStreaming/EventResponder.swift | 4 +- Sources/ECS/Event/World+EventStreamer.swift | 2 +- Sources/ECS/Schedule/EventSchedule.swift | 6 +-- Sources/ECS/States/State.swift | 45 +++++++++++++++++++ Sources/ECS/Systems/System.swift | 2 +- Sources/ECS/WorldMethods/World+Update.swift | 4 ++ Sources/ECS_Macros/SystemMacro.swift | 2 +- Sources/PlugIns/Graphic2D/PlugInExport.swift | 2 - Tests/ecs-swiftTests/EventTests.swift | 37 +++++++++++++++ 10 files changed, 95 insertions(+), 11 deletions(-) diff --git a/Sources/ECS/Event/EventStreaming/Event.swift b/Sources/ECS/Event/EventStreaming/Event.swift index 0d59d31..6908539 100644 --- a/Sources/ECS/Event/EventStreaming/Event.swift +++ b/Sources/ECS/Event/EventStreaming/Event.swift @@ -31,7 +31,7 @@ final class Event: 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) diff --git a/Sources/ECS/Event/EventStreaming/EventResponder.swift b/Sources/ECS/Event/EventStreaming/EventResponder.swift index fb0f0a1..8a91352 100644 --- a/Sources/ECS/Event/EventStreaming/EventResponder.swift +++ b/Sources/ECS/Event/EventStreaming/EventResponder.swift @@ -9,7 +9,7 @@ import Foundation final public class EventResponderBuilder { unowned let worldStorage: WorldStorageRef - var systems: [Schedule: [SystemExecute]] = [:] + var systems: [EventSchedule: [SystemExecute]] = [:] init(worldStorage: WorldStorageRef) { self.worldStorage = worldStorage @@ -17,7 +17,7 @@ final public class EventResponderBuilder { } final public class EventResponder: WorldStorageElement { - var systems: [Schedule: [SystemExecute]] = [:] + var systems: [EventSchedule: [SystemExecute]] = [:] } public extension World { diff --git a/Sources/ECS/Event/World+EventStreamer.swift b/Sources/ECS/Event/World+EventStreamer.swift index 3979eee..23c8654 100644 --- a/Sources/ECS/Event/World+EventStreamer.swift +++ b/Sources/ECS/Event/World+EventStreamer.swift @@ -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) diff --git a/Sources/ECS/Schedule/EventSchedule.swift b/Sources/ECS/Schedule/EventSchedule.swift index b8dc59f..1164af7 100644 --- a/Sources/ECS/Schedule/EventSchedule.swift +++ b/Sources/ECS/Schedule/EventSchedule.swift @@ -42,9 +42,9 @@ public extension EventSchedule { /** ``World/update(currentTime:)`` 実行時にイベントを受信します. */ - static let update: Schedule = Schedule(id: DefaultSchedule.update) + static let update: EventSchedule = EventSchedule(id: DefaultSchedule.update) - static func customSchedule(_ value: T) -> Schedule { - Schedule(id: value) + static func customSchedule(_ value: T) -> EventSchedule { + EventSchedule(id: value) } } diff --git a/Sources/ECS/States/State.swift b/Sources/ECS/States/State.swift index 49c6a18..4b3a3d5 100644 --- a/Sources/ECS/States/State.swift +++ b/Sources/ECS/States/State.swift @@ -21,6 +21,7 @@ class StateRegistry: WorldStorageElement { public class StateStorage { class StateAssociatedSchedules: WorldStorageElement { var schedules = Set() + var eventSchedules = Set() } class StateTransitionQueue: WorldStorageElement { @@ -35,46 +36,75 @@ public class StateStorage { private(set) var onInactiveUpdateNewStateQueue = [Schedule]() private(set) var onInactiveUpdatePreviousStateQueue = [Schedule]() + private(set) var didEnterEventQueue = [EventSchedule]() + private(set) var willExitEventQueue = [EventSchedule]() + private(set) var onResumeEventQueue = [EventSchedule]() + private(set) var onPauseEventQueue = [EventSchedule]() + private(set) var onUpdateNewStateEventQueue = [EventSchedule]() + private(set) var onUpdatePreviousStateEventQueue = [EventSchedule]() + private(set) var onStackUpdateNewStateEventQueue = [EventSchedule]() + private(set) var onStackUpdatePreviousStateEventQueue = [EventSchedule]() + private(set) var onInactiveUpdateNewStateEventQueue = [EventSchedule]() + private(set) var onInactiveUpdatePreviousStateEventQueue = [EventSchedule]() + // enter func enqueueEntered(state: T) { didEnterQueue.append(.didEnter(state)) + didEnterEventQueue.append(.didEnter(state)) onUpdateNewStateQueue.append(.onUpdate(state)) + onUpdateNewStateEventQueue.append(.onUpdate(state)) onStackUpdateNewStateQueue.append(.onStackUpdate(state)) + onStackUpdateNewStateEventQueue.append(.onStackUpdate(state)) } func enqueueExited(state: T) { willExitQueue.append(.willExit(state)) + willExitEventQueue.append(.willExit(state)) onUpdatePreviousStateQueue.append(.onUpdate(state)) + onUpdatePreviousStateEventQueue.append(.onUpdate(state)) onStackUpdatePreviousStateQueue.append(.onStackUpdate(state)) + onStackUpdatePreviousStateEventQueue.append(.onStackUpdate(state)) } // push func enqueuePushed(state: T) { didEnterQueue.append(.didEnter(state)) + didEnterEventQueue.append(.didEnter(state)) onUpdateNewStateQueue.append(.onUpdate(state)) + onUpdateNewStateEventQueue.append(.onUpdate(state)) onStackUpdateNewStateQueue.append(.onStackUpdate(state)) + onStackUpdateNewStateEventQueue.append(.onStackUpdate(state)) } func enqueuePaused(state: T) { onPauseQueue.append(.onPause(state)) + onPauseEventQueue.append(.onPause(state)) onUpdatePreviousStateQueue.append(.onUpdate(state)) + onUpdatePreviousStateEventQueue.append(.onUpdate(state)) onInactiveUpdateNewStateQueue.append(.onInactiveUpdate(state)) + onInactiveUpdateNewStateEventQueue.append(.onInactiveUpdate(state)) } // pop func enqueuePopped(state: T) { onUpdatePreviousStateQueue.append(.onUpdate(state)) + onUpdatePreviousStateEventQueue.append(.onUpdate(state)) onStackUpdatePreviousStateQueue.append(.onStackUpdate(state)) + onStackUpdatePreviousStateEventQueue.append(.onStackUpdate(state)) willExitQueue.append(.willExit(state)) + willExitEventQueue.append(.willExit(state)) } func enqueueResumed(state: T) { onResumeQueue.append(.onResume(state)) + onResumeEventQueue.append(.onResume(state)) onUpdateNewStateQueue.append(.onUpdate(state)) + onUpdateNewStateEventQueue.append(.onUpdate(state)) onInactiveUpdatePreviousStateQueue.append(.onInactiveUpdate(state)) + onInactiveUpdatePreviousStateEventQueue.append(.onInactiveUpdate(state)) } // clear @@ -90,6 +120,17 @@ public class StateStorage { onStackUpdateNewStateQueue = [] onInactiveUpdatePreviousStateQueue = [] onInactiveUpdateNewStateQueue = [] + + didEnterEventQueue = [] + willExitEventQueue = [] + onResumeEventQueue = [] + onPauseEventQueue = [] + onUpdateNewStateEventQueue = [] + onUpdatePreviousStateEventQueue = [] + onStackUpdateNewStateEventQueue = [] + onStackUpdatePreviousStateEventQueue = [] + onInactiveUpdateNewStateEventQueue = [] + onInactiveUpdatePreviousStateEventQueue = [] } } @@ -118,6 +159,10 @@ public class StateStorage { self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body.schedules } + func currentEventSchedulesWhichAssociatedStates() -> Set { + self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body.eventSchedules + } + // MARK: - queue func didEnterQueue() -> [Schedule] { diff --git a/Sources/ECS/Systems/System.swift b/Sources/ECS/Systems/System.swift index 8793c6d..e83613f 100644 --- a/Sources/ECS/Systems/System.swift +++ b/Sources/ECS/Systems/System.swift @@ -26,7 +26,7 @@ public extension World { } public extension EventResponderBuilder { - @discardableResult func addSystem(_ schedule: Schedule, _ system: @escaping (P) -> ()) -> EventResponderBuilder { + @discardableResult func addSystem(_ schedule: EventSchedule, _ system: @escaping (P) -> ()) -> EventResponderBuilder { if !self.systems.keys.contains(schedule) { self.systems[schedule] = [] } diff --git a/Sources/ECS/WorldMethods/World+Update.swift b/Sources/ECS/WorldMethods/World+Update.swift index ccadada..80212c9 100644 --- a/Sources/ECS/WorldMethods/World+Update.swift +++ b/Sources/ECS/WorldMethods/World+Update.swift @@ -121,6 +121,8 @@ extension World { for newState in onInactiveUpdateNewStateQueue { stateSchedulesManager.schedules.insert(newState) } + + self.applyEventQueue() } func updatePhase() { @@ -143,6 +145,8 @@ extension World { for system in self.worldStorage.systemStorage.systems(self.postUpdateSchedule) { system.execute(self.worldStorage) } + + self.applyEventQueue() } // 各システムが動いた後に実行される diff --git a/Sources/ECS_Macros/SystemMacro.swift b/Sources/ECS_Macros/SystemMacro.swift index faa246a..234afc7 100644 --- a/Sources/ECS_Macros/SystemMacro.swift +++ b/Sources/ECS_Macros/SystemMacro.swift @@ -105,7 +105,7 @@ struct AddSystemMacroForEventResponderBuilder: DeclarationMacro { }.dropLast() let result: DeclSyntax = """ - @discardableResult func addSystem<\(raw: genericArguments)>(_ schedule: Schedule, _ system: @escaping (\(raw: valueTypes)) -> ()) -> EventResponderBuilder { + @discardableResult func addSystem<\(raw: genericArguments)>(_ schedule: EventSchedule, _ system: @escaping (\(raw: valueTypes)) -> ()) -> EventResponderBuilder { if !self.systems.keys.contains(schedule) { self.systems[schedule] = [] } diff --git a/Sources/PlugIns/Graphic2D/PlugInExport.swift b/Sources/PlugIns/Graphic2D/PlugInExport.swift index cb9835d..746c5f2 100644 --- a/Sources/PlugIns/Graphic2D/PlugInExport.swift +++ b/Sources/PlugIns/Graphic2D/PlugInExport.swift @@ -109,8 +109,6 @@ public func graphicPlugIn(world: World) { responder .addSystem(.update, removeChildIfDespawned(despawnEvent:query:parentQuery:)) .addSystem(.update, despawnChildIfParentDespawned(despawnedEntityEvent:children:commands:)) - .addSystem(.preUpdate, _removeNodeIfDespawned(despawn:nodes:)) .addSystem(.update, _removeNodeIfDespawned(despawn:nodes:)) - .addSystem(.postUpdate, _removeNodeIfDespawned(despawn:nodes:)) } } diff --git a/Tests/ecs-swiftTests/EventTests.swift b/Tests/ecs-swiftTests/EventTests.swift index 83c4dc5..72b912e 100644 --- a/Tests/ecs-swiftTests/EventTests.swift +++ b/Tests/ecs-swiftTests/EventTests.swift @@ -64,10 +64,47 @@ final class EventTests: XCTestCase { .addSystem(.willDespawn, despanedEntitySystem(eventReader:commands:currentTime:)) world.setUpWorld() + world.update(currentTime: -1) world.update(currentTime: 0) world.update(currentTime: 1) world.update(currentTime: 2) world.update(currentTime: 3) } + + func testEventStream() { + var flags = [0, 0, 0, 0] + + let world = World() + .addEventStreamer(eventType: TestEvent.self) + .addSystem(.startUp, { (eventWriter: EventWriter) in + eventWriter.send(value: .init(name: "test event")) + ECSTAssertStepOrder(currentStep: 0, steps: &flags) + }) + .buildEventResponder(TestEvent.self) { responder in + responder.addSystem(.update) { (event: EventReader, commands: Commands) in + ECSTAssertStepOrder(currentStep: 1, steps: &flags) + commands.spawn().addComponent(TestComponent(content: event.value.name)) + } + } + .buildDidSpawnResponder { responder in + responder + .addSystem(.update) { (event: EventReader, commands: Commands) in + ECSTAssertStepOrder(currentStep: 2, steps: &flags) + commands.despawn(entity: event.value.spawnedEntity) + } + } + .buildWillDespawnResponder { responder in + responder + .addSystem(.update) { (event: EventReader) in + ECSTAssertStepOrder(currentStep: 3, steps: &flags) + } + } + + world.setUpWorld() + world.update(currentTime: -1) + world.update(currentTime: 0) + + XCTAssertEqual(flags, [1, 1, 1, 1]) + } } From 71b86f1b91163edcacf3e48630097c53f28c12be Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 15 Jun 2025 21:18:03 +0900 Subject: [PATCH 32/53] Refactor setUpWorld to remove event handling --- Sources/ECS/WorldMethods/World+SetUp.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Sources/ECS/WorldMethods/World+SetUp.swift b/Sources/ECS/WorldMethods/World+SetUp.swift index d322fae..01b03c4 100644 --- a/Sources/ECS/WorldMethods/World+SetUp.swift +++ b/Sources/ECS/WorldMethods/World+SetUp.swift @@ -7,12 +7,8 @@ public extension World { func setUpWorld() { - let commands = self.worldStorage.commandsStorage.commands()! - - self.applyEnityTransactions(commands: commands) - self.worldStorage.chunkStorage.applySpawnedEntityQueue() - - // entity の変更を world 全体に適用. - self.worldStorage.chunkStorage.applyUpdatedEntityQueue() + self.preUpdateSchedule = .preStartUp + self.updateSchedule = .startUp + self.postUpdateSchedule = .postStartUp } } From 516f008cae19ecf696782bcefc5a1ad8380a9164 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:22:37 +0900 Subject: [PATCH 33/53] Refactor entity handling to use EntityRecordRef across multiple components and commands --- Sources/ECS/Chunk/Chunk.swift | 6 ++-- Sources/ECS/Chunk/ChunkEntityInterface.swift | 25 ++++++++------ Sources/ECS/Chunk/ChunkStorage+.swift | 8 ++--- Sources/ECS/Commands/Commands.swift | 12 +++++++ Sources/ECS/Commands/World+Commands.swift | 4 +-- Sources/ECS/Commons/EntityRecord.swift | 16 +++++++++ Sources/ECS/Commons/SparseSet.swift | 2 +- .../Commands+EntityCommands.swift | 4 +-- .../EntityCommands/EntityCommands.swift | 2 +- .../EntityTransactions/SpawnCommand.swift | 6 ++-- Sources/ECS/FilterdQuery/FIlteredQuery.swift | 12 +++---- Sources/ECS/FilterdQuery/QueryProtocol.swift | 4 +-- Sources/ECS/Query/Query.swift | 31 ++++++++++------- Sources/ECS/World/World+Entities.swift | 5 +-- Sources/ECS/WorldMethods/World+Spawn.swift | 12 +++---- Sources/ECS_Macros/QueryMacro.swift | 28 ++++++++------- .../Commands/EntityCommands+Graphic.swift | 2 +- .../GraphicPlugInTests.swift | 34 +++++++++++++++++++ Tests/ecs-swiftTests/ChunkTests.swift | 10 +++--- Tests/ecs-swiftTests/CommandsTests.swift | 2 +- Tests/ecs-swiftTests/FilteredQueryTests.swift | 2 ++ 21 files changed, 152 insertions(+), 75 deletions(-) diff --git a/Sources/ECS/Chunk/Chunk.swift b/Sources/ECS/Chunk/Chunk.swift index f5e4ec6..045f90d 100644 --- a/Sources/ECS/Chunk/Chunk.swift +++ b/Sources/ECS/Chunk/Chunk.swift @@ -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) {} } diff --git a/Sources/ECS/Chunk/ChunkEntityInterface.swift b/Sources/ECS/Chunk/ChunkEntityInterface.swift index 58a9699..a669fbb 100644 --- a/Sources/ECS/Chunk/ChunkEntityInterface.swift +++ b/Sources/ECS/Chunk/ChunkEntityInterface.swift @@ -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 を追加します @@ -24,17 +24,17 @@ 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 = [] @@ -42,21 +42,24 @@ class ChunkEntityInterface: WorldStorageElement { /// 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 = [] diff --git a/Sources/ECS/Chunk/ChunkStorage+.swift b/Sources/ECS/Chunk/ChunkStorage+.swift index 8cfcd97..b554bbe 100644 --- a/Sources/ECS/Chunk/ChunkStorage+.swift +++ b/Sources/ECS/Chunk/ChunkStorage+.swift @@ -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() { diff --git a/Sources/ECS/Commands/Commands.swift b/Sources/ECS/Commands/Commands.swift index 2ebbcf6..716216c 100644 --- a/Sources/ECS/Commands/Commands.swift +++ b/Sources/ECS/Commands/Commands.swift @@ -39,3 +39,15 @@ final public class Commands: SystemParameter { self.commandQueue.append(command) } } + +// MARK: - life cycle + +extension Commands { + func refreshEntityTransactions() { + entityTransactions = [] + } + + func refreshCommandQueue() { + commandQueue = [] + } +} diff --git a/Sources/ECS/Commands/World+Commands.swift b/Sources/ECS/Commands/World+Commands.swift index bbcaf2b..467f7b6 100644 --- a/Sources/ECS/Commands/World+Commands.swift +++ b/Sources/ECS/Commands/World+Commands.swift @@ -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() } } diff --git a/Sources/ECS/Commons/EntityRecord.swift b/Sources/ECS/Commons/EntityRecord.swift index 1ba92a1..96276d6 100644 --- a/Sources/ECS/Commons/EntityRecord.swift +++ b/Sources/ECS/Commons/EntityRecord.swift @@ -41,7 +41,23 @@ final public class ImmutableRef: Ref { } final public class EntityRecordRef { + let entity: Entity var map = AnyMap() + + 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 { diff --git a/Sources/ECS/Commons/SparseSet.swift b/Sources/ECS/Commons/SparseSet.swift index eadd393..d576b23 100644 --- a/Sources/ECS/Commons/SparseSet.swift +++ b/Sources/ECS/Commons/SparseSet.swift @@ -23,7 +23,7 @@ public struct SparseSet { 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]) diff --git a/Sources/ECS/EntityCommands/Commands+EntityCommands.swift b/Sources/ECS/EntityCommands/Commands+EntityCommands.swift index fd9d78e..0e30900 100644 --- a/Sources/ECS/EntityCommands/Commands+EntityCommands.swift +++ b/Sources/ECS/EntityCommands/Commands+EntityCommands.swift @@ -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) diff --git a/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift index 8c947be..8d70331 100644 --- a/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift +++ b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift @@ -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) } diff --git a/Sources/ECS/EntityCommands/EntityTransactions/SpawnCommand.swift b/Sources/ECS/EntityCommands/EntityTransactions/SpawnCommand.swift index 1150183..9ad2797 100644 --- a/Sources/ECS/EntityCommands/EntityTransactions/SpawnCommand.swift +++ b/Sources/ECS/EntityCommands/EntityTransactions/SpawnCommand.swift @@ -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) } } diff --git a/Sources/ECS/FilterdQuery/FIlteredQuery.swift b/Sources/ECS/FilterdQuery/FIlteredQuery.swift index 21f955d..ee374a3 100644 --- a/Sources/ECS/FilterdQuery/FIlteredQuery.swift +++ b/Sources/ECS/FilterdQuery/FIlteredQuery.swift @@ -8,12 +8,12 @@ final public class Filtered: 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) { @@ -40,12 +40,12 @@ final public class Filtered: Chunk, SystemParameter worldStorage.chunkStorage.addChunk(Filtered()) } - 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) } } diff --git a/Sources/ECS/FilterdQuery/QueryProtocol.swift b/Sources/ECS/FilterdQuery/QueryProtocol.swift index 2e4cca3..013ebd1 100644 --- a/Sources/ECS/FilterdQuery/QueryProtocol.swift +++ b/Sources/ECS/FilterdQuery/QueryProtocol.swift @@ -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) diff --git a/Sources/ECS/Query/Query.swift b/Sources/ECS/Query/Query.swift index 8274681..0a0e243 100644 --- a/Sources/ECS/Query/Query.swift +++ b/Sources/ECS/Query/Query.swift @@ -14,30 +14,37 @@ final public class Query: 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 から取得し, イテレーションします. diff --git a/Sources/ECS/World/World+Entities.swift b/Sources/ECS/World/World+Entities.swift index 1d63dbd..209e10e 100644 --- a/Sources/ECS/World/World+Entities.swift +++ b/Sources/ECS/World/World+Entities.swift @@ -6,11 +6,12 @@ // public extension World { - func insert(entity: Entity, entityRecord: EntityRecordRef) { - self.entities.insert(entityRecord, withEntity: entity) + func insert(entityRecord: EntityRecordRef) { + self.entities.insert(entityRecord, withEntity: entityRecord.entity) } func remove(entity: Entity) { + guard self.entities.contains(entity) else { return } self.entities.pop(entity: entity) } diff --git a/Sources/ECS/WorldMethods/World+Spawn.swift b/Sources/ECS/WorldMethods/World+Spawn.swift index 18562a7..6bd8500 100644 --- a/Sources/ECS/WorldMethods/World+Spawn.swift +++ b/Sources/ECS/WorldMethods/World+Spawn.swift @@ -23,20 +23,19 @@ extension World { /// /// ``Commands/spawn()`` が実行された後, フレームが終了するタイミングでこの関数が実行されます. /// entity へのコンポーネントの登録などは, push の後に行われます. - func push(entity: Entity, entityRecord: EntityRecordRef) { - if entity.generation == 0 { + func push(entityRecord: EntityRecordRef) { + if entityRecord.entity.generation == 0 { self.entities.allocate() } - self.insert(entity: entity, entityRecord: entityRecord) + self.insert(entityRecord: entityRecord) self.worldStorage .chunkStorage - .pushSpawned(entity: entity, entityRecord: entityRecord) - + .pushSpawned(entityRecord: entityRecord) self.worldStorage .eventStorage .commandsEventWriter(eventOfType: DidSpawnEvent.self)! - .send(value: DidSpawnEvent(spawnedEntity: entity)) + .send(value: DidSpawnEvent(spawnedEntity: entityRecord.entity)) } /// Entity を削除します. @@ -47,7 +46,6 @@ extension World { self.worldStorage .chunkStorage .despawn(entity: entity) - self.worldStorage .eventStorage .commandsEventWriter(eventOfType: WillDespawnEvent.self)! diff --git a/Sources/ECS_Macros/QueryMacro.swift b/Sources/ECS_Macros/QueryMacro.swift index f6556c0..20e8db5 100644 --- a/Sources/ECS_Macros/QueryMacro.swift +++ b/Sources/ECS_Macros/QueryMacro.swift @@ -57,30 +57,34 @@ struct QueryMacro: DeclarationMacro { self.components.allocate() } - public func insert(entity: Entity, entityRecord: EntityRecordRef) { + public func insert(entityRecord: EntityRecordRef) { guard \(raw: refDeclarationsFromRecord) else { return } - self.components.insert((\(raw: refs)), withEntity: entity) + self.components.insert((\(raw: refs)), 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) + } + + public 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 \(raw: refDeclarationsFromRecord) else { - self.despawn(entity: entity) + self.despawn(entity: entityRecord.entity) return } - guard !components.contains(entity) else { return } - self.components.insert((\(raw: refs)), withEntity: entity) + guard !components.contains(entityRecord.entity) else { return } + self.components.insert((\(raw: refs)), withEntity: entityRecord.entity) } public func update(_ f: (\(raw: parameters)) -> ()) { diff --git a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift index 27f8544..b87fb5b 100644 --- a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift @@ -49,7 +49,7 @@ class RemoveAllChildren: EntityCommand { for child in record.componentRef(ofType: Parent.self)!.value.children { let childRecord = world.entityRecord(forEntity: child)! childRecord.removeComponent(ofType: Child.self) - world.worldStorage.chunkStorage.pushUpdated(entity: child, entityRecord: childRecord) + world.worldStorage.chunkStorage.pushUpdated(entityRecord: childRecord) } } diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index 1871f86..6cb76d6 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -326,4 +326,38 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(flags, [1, 1, 1]) } + + func testExclusiveControl() { + let scene = SKScene() + let world = World() + .addResource(SceneResource(scene)) + .addPlugIn(graphicPlugIn(world:)) + .addSystem(.startUp) { (commands: Commands, nodes: Resource) in + let child = commands.spawn() + .setGraphic(nodes.resource.create(node: SKNode())) + .id() + commands.spawn() + .setGraphic(nodes.resource.create(node: SKNode())) + .addChild(child) + } + .addSystem(.update, { (graphic: Query2>, commands: Commands) in + graphic.update { entity, _ in + commands.entity(entity).removeAllChildren() + } + }) + .addSystem(.update, { (graphic: Query2>, commands: Commands) in + graphic.update { entity, _ in + commands.despawn(entity: entity) + } + }) + .addSystem(.update) { (graphic: Query2>) in + graphic.update { entity, graphic in + graphic.nodeRef.position.x += 1 + } + } + + world.setUpWorld() + world.update(currentTime: -1) + world.update(currentTime: 0) + } } diff --git a/Tests/ecs-swiftTests/ChunkTests.swift b/Tests/ecs-swiftTests/ChunkTests.swift index 766d423..588a151 100644 --- a/Tests/ecs-swiftTests/ChunkTests.swift +++ b/Tests/ecs-swiftTests/ChunkTests.swift @@ -10,8 +10,8 @@ import XCTest class TestChunk: Chunk { var entities = [Entity: EntityRecordRef]() - override func spawn(entity: Entity, entityRecord: EntityRecordRef) { - self.entities[entity] = entityRecord + override func spawn(entityRecord: EntityRecordRef) { + self.entities[entityRecord.entity] = entityRecord } override func despawn(entity: Entity) { @@ -21,8 +21,8 @@ class TestChunk: Chunk { class TestChunk_2: Chunk { var entities = [Entity: EntityRecordRef]() - override func spawn(entity: Entity, entityRecord: EntityRecordRef) { - self.entities[entity] = entityRecord + override func spawn(entityRecord: EntityRecordRef) { + self.entities[entityRecord.entity] = entityRecord } override func despawn(entity: Entity) { @@ -43,7 +43,7 @@ final class ChunkTests: XCTestCase { // chunk interface を介して chunk に entity を push します(回数: 5回). for entity in mockEntities { - world.push(entity: entity, entityRecord: EntityRecordRef()) + world.push(entityRecord: EntityRecordRef(entity: entity)) } world.worldStorage.chunkStorage.applySpawnedEntityQueue() diff --git a/Tests/ecs-swiftTests/CommandsTests.swift b/Tests/ecs-swiftTests/CommandsTests.swift index d146d92..171b2a6 100644 --- a/Tests/ecs-swiftTests/CommandsTests.swift +++ b/Tests/ecs-swiftTests/CommandsTests.swift @@ -14,7 +14,7 @@ class TestCommand_Spawn: Command { self.entity = entity } override func runCommand(in world: World) { - world.push(entity: self.entity, entityRecord: EntityRecordRef()) + world.push(entityRecord: EntityRecordRef(entity: entity)) } } diff --git a/Tests/ecs-swiftTests/FilteredQueryTests.swift b/Tests/ecs-swiftTests/FilteredQueryTests.swift index 62ace4c..8d78ef7 100644 --- a/Tests/ecs-swiftTests/FilteredQueryTests.swift +++ b/Tests/ecs-swiftTests/FilteredQueryTests.swift @@ -70,4 +70,6 @@ final class FilteredQueryTests: XCTestCase { XCTAssertEqual(testQueryOr.query.components.data.count, 0) XCTAssertEqual(testQueryWithout.query.components.data.count, 0) } + + // TODO: - Filtered Query2 のテスト } From edfdc081ddf40e4a913db8b62a59ec8f3dac5a49 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:23:28 +0900 Subject: [PATCH 34/53] Remove World+Graphic2D.swift file and associated graphic handling extension --- Sources/PlugIns/Graphic2D/World+Graphic2D.swift | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 Sources/PlugIns/Graphic2D/World+Graphic2D.swift diff --git a/Sources/PlugIns/Graphic2D/World+Graphic2D.swift b/Sources/PlugIns/Graphic2D/World+Graphic2D.swift deleted file mode 100644 index a5acf59..0000000 --- a/Sources/PlugIns/Graphic2D/World+Graphic2D.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// World+Graphic2D.swift -// -// -// Created by rrbox on 2023/08/20. -// - -import GameplayKit -import ECS - -extension World { - func removeGraphic(fromEntity entity: Entity) { - let entityRecord = self.entityRecord(forEntity: entity)! - } -} From 53c6fd77d463dbdeca7262b4e3e54444af34fc7d Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 29 Jun 2025 22:27:18 +0900 Subject: [PATCH 35/53] Remove redundant world.update(currentTime: 0) calls in tests --- Sources/PlugIns/Graphic2D/PlugInExport.swift | 2 +- Tests/ecs-swiftTests/FilteredQueryTests.swift | 6 ------ Tests/ecs-swiftTests/QueryTests.swift | 5 ----- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/Sources/PlugIns/Graphic2D/PlugInExport.swift b/Sources/PlugIns/Graphic2D/PlugInExport.swift index 746c5f2..063f0ab 100644 --- a/Sources/PlugIns/Graphic2D/PlugInExport.swift +++ b/Sources/PlugIns/Graphic2D/PlugInExport.swift @@ -93,7 +93,7 @@ func _removeNodeIfDespawned(despawn: EventReader, nodes: Resou nodes.resource.removeNode(forEntity: entity) } -// TODO: - Node 操作イベントのハンドリングは他のフェーズでもお同様に行なわなくてもOK? +// TODO: - Node 操作イベントのハンドリングは他のフェーズでも同様に行なわなくてもOK? @MainActor public func graphicPlugIn(world: World) { world diff --git a/Tests/ecs-swiftTests/FilteredQueryTests.swift b/Tests/ecs-swiftTests/FilteredQueryTests.swift index 8d78ef7..1f493bf 100644 --- a/Tests/ecs-swiftTests/FilteredQueryTests.swift +++ b/Tests/ecs-swiftTests/FilteredQueryTests.swift @@ -26,8 +26,6 @@ final class FilteredQueryTests: XCTestCase { .addComponent(TestComponent(content: "c0")) .id() -// world.applyCommands(commands: commands) -// world.worldStorage.chunkStorage.applySpawnedEntityQueue() world.update(currentTime: 0) XCTAssertEqual(testQueryAnd.query.components.data.count, 0) @@ -36,7 +34,6 @@ final class FilteredQueryTests: XCTestCase { commands.entity(entity).addComponent(TestComponent2(content: "c1")) - world.update(currentTime: 0) world.update(currentTime: 0) XCTAssertEqual(testQueryAnd.query.components.data.count, 0) @@ -45,7 +42,6 @@ final class FilteredQueryTests: XCTestCase { commands.entity(entity).addComponent(TestComponent3(content: "c2")) - world.update(currentTime: 0) world.update(currentTime: 0) XCTAssertEqual(testQueryAnd.query.components.data.count, 1) @@ -54,7 +50,6 @@ final class FilteredQueryTests: XCTestCase { commands.entity(entity).removeComponent(ofType: TestComponent2.self) - world.update(currentTime: 0) world.update(currentTime: 0) XCTAssertEqual(testQueryAnd.query.components.data.count, 0) @@ -63,7 +58,6 @@ final class FilteredQueryTests: XCTestCase { commands.entity(entity).removeComponent(ofType: TestComponent.self) - world.update(currentTime: 0) world.update(currentTime: 0) XCTAssertEqual(testQueryAnd.query.components.data.count, 0) diff --git a/Tests/ecs-swiftTests/QueryTests.swift b/Tests/ecs-swiftTests/QueryTests.swift index 5d99157..c0df716 100644 --- a/Tests/ecs-swiftTests/QueryTests.swift +++ b/Tests/ecs-swiftTests/QueryTests.swift @@ -167,7 +167,6 @@ final class QueryTests: XCTestCase { let testEntity = commands.spawn().addComponent(TestComponent(content: "test")).id() - world.update(currentTime: 0) world.update(currentTime: 0) XCTAssertEqual(testQuery.components.data.count, 1) @@ -188,7 +187,6 @@ final class QueryTests: XCTestCase { .addComponent(TestComponent2(content: "test2")).id() world.update(currentTime: 0) - world.update(currentTime: 0) XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) @@ -209,7 +207,6 @@ final class QueryTests: XCTestCase { .addComponent(TestComponent3(content: "test2")).id() world.update(currentTime: 0) - world.update(currentTime: 0) XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) @@ -231,7 +228,6 @@ final class QueryTests: XCTestCase { .addComponent(TestComponent4(content: "test2")).id() world.update(currentTime: 0) - world.update(currentTime: 0) XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) @@ -254,7 +250,6 @@ final class QueryTests: XCTestCase { .addComponent(TestComponent5(content: "test2")) world.update(currentTime: 0) - world.update(currentTime: 0) XCTAssertEqual(testQuery.components.data.count, 1) XCTAssertEqual(testQuery2.components.data.count, 1) From 18230210fae416fef31d14b224b34b368b5aa9df Mon Sep 17 00:00:00 2001 From: RedRimmedBox <87851278+rrbox@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:02:43 +0900 Subject: [PATCH 36/53] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a4f9601..8cc0fd2 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![GitHub License](https://img.shields.io/github/license/rrbox/ecs-swift)](https://github.com/rrbox/ecs-swift/blob/main/LICENSE) [![Swift](https://github.com/rrbox/ecs-swift/actions/workflows/swift.yml/badge.svg?branch=release%2Flatest)](https://github.com/rrbox/ecs-swift/actions/workflows/swift.yml) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/rrbox/ecs-swift/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/rrbox/ecs-swift/tree/main) +[![Discord](https://img.shields.io/discord/1292321583506915460?style=flat-square&logo=discord&logoColor=7885F2&label=discord)](https://discord.gg/pA6qkhJxYE) `ecs-swift` is a library that implements the Entity Component System (ECS) in Swift. It is used for developing games with ECS design in frameworks such as SpriteKit and SceneKit. From 52ad9c1766a1099207dc6f80ea06532f10c8edd2 Mon Sep 17 00:00:00 2001 From: RedRimmedBox <87851278+rrbox@users.noreply.github.com> Date: Sun, 29 Jun 2025 23:09:45 +0900 Subject: [PATCH 37/53] Update README_ja.md --- README_ja.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_ja.md b/README_ja.md index aa47279..747a581 100644 --- a/README_ja.md +++ b/README_ja.md @@ -3,6 +3,8 @@ [![GitHub issues](https://img.shields.io/github/issues/rrbox/ecs-swift)](https://github.com/rrbox/ecs-swift/issues) [![GitHub License](https://img.shields.io/github/license/rrbox/ecs-swift)](https://github.com/rrbox/ecs-swift/blob/main/LICENSE) [![Swift](https://github.com/rrbox/ecs-swift/actions/workflows/swift.yml/badge.svg?branch=release%2Flatest)](https://github.com/rrbox/ecs-swift/actions/workflows/swift.yml) +[![CircleCI](https://dl.circleci.com/status-badge/img/gh/rrbox/ecs-swift/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/rrbox/ecs-swift/tree/main) +[![Discord](https://img.shields.io/discord/1292321583506915460?style=flat-square&logo=discord&logoColor=7885F2&label=discord)](https://discord.gg/pA6qkhJxYE) `ecs-swift` は、Swift で Entity Component System (ECS) を実装したライブラリです。SpriteKit や SceneKit などのフレームワークで ECS 設計のゲームを開発するために使用します。 From 4542ed34e25f27ab75713b7f3664631f089db705 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Mon, 30 Jun 2025 00:35:23 +0900 Subject: [PATCH 38/53] Update workflow to run on macOS 15 --- .github/workflows/swift.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 1fa4fb6..10c80b9 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: macos-14 + runs-on: macos-15 steps: - uses: actions/checkout@v3 From 7411dd11818eb820d389169a82f8489112da4058 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Wed, 2 Jul 2025 21:05:18 +0900 Subject: [PATCH 39/53] Remove CommandsStorage and access Commands directly from WorldStorageRef --- Sources/ECS/Commands/Commands.swift | 2 +- Sources/ECS/Commands/CommandsStorage.swift | 36 ------------------- Sources/ECS/World/WorldStorageRef.swift | 1 + Sources/ECS/WorldMethods/World+Init.swift | 3 -- Sources/ECS/WorldMethods/World+Update.swift | 2 +- Tests/ecs-swiftTests/CommandsTests.swift | 3 +- .../ecs-swiftTests/EntityCommandsTests.swift | 3 +- Tests/ecs-swiftTests/FilteredQueryTests.swift | 2 +- Tests/ecs-swiftTests/QueryTests.swift | 4 +-- 9 files changed, 8 insertions(+), 48 deletions(-) delete mode 100644 Sources/ECS/Commands/CommandsStorage.swift diff --git a/Sources/ECS/Commands/Commands.swift b/Sources/ECS/Commands/Commands.swift index 716216c..67f27ee 100644 --- a/Sources/ECS/Commands/Commands.swift +++ b/Sources/ECS/Commands/Commands.swift @@ -31,7 +31,7 @@ final public class Commands: SystemParameter { } public static func getParameter(from worldStorage: WorldStorageRef) -> Commands? { - worldStorage.commandsStorage.commands() + worldStorage.commands } /// CommandQueue にコマンドを追加します. diff --git a/Sources/ECS/Commands/CommandsStorage.swift b/Sources/ECS/Commands/CommandsStorage.swift deleted file mode 100644 index 5183f2b..0000000 --- a/Sources/ECS/Commands/CommandsStorage.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// CommandsStorage.swift -// -// -// Created by rrbox on 2023/08/10. -// - -final public class CommandsStorage { - class CommandsRegistry: WorldStorageElement { - let commands: Commands - - init(commands: Commands) { - self.commands = commands - } - } - let buffer: WorldStorageRef - - init(buffer: WorldStorageRef) { - self.buffer = buffer - } - - public func commands() -> Commands? { - self.buffer.map.valueRef(ofType: CommandsRegistry.self)?.body.commands - } - - func setCommands(_ commands: Commands) { - self.buffer.map.push(CommandsRegistry(commands: commands)) - } -} - -// WorldStorage + Commands -public extension WorldStorageRef { - var commandsStorage: CommandsStorage { - CommandsStorage(buffer: self) - } -} diff --git a/Sources/ECS/World/WorldStorageRef.swift b/Sources/ECS/World/WorldStorageRef.swift index 1b004d3..c9f1f59 100644 --- a/Sources/ECS/World/WorldStorageRef.swift +++ b/Sources/ECS/World/WorldStorageRef.swift @@ -6,5 +6,6 @@ // final public class WorldStorageRef { + let commands = Commands() var map = AnyMap() } diff --git a/Sources/ECS/WorldMethods/World+Init.swift b/Sources/ECS/WorldMethods/World+Init.swift index 42d5ece..188d7f0 100644 --- a/Sources/ECS/WorldMethods/World+Init.swift +++ b/Sources/ECS/WorldMethods/World+Init.swift @@ -47,9 +47,6 @@ public extension World { self.addCommandsEventStreamer(eventType: DidSpawnEvent.self) self.addCommandsEventStreamer(eventType: WillDespawnEvent.self) - // world buffer に commands の初期値を設定します. - self.worldStorage.commandsStorage.setCommands(Commands()) - // world に一番最初のフレームで実行されるシステムを追加します. systemStorage.addSystem(.preStartUp, System(preUpdateSystemFirstFrameSystem(commands:))) systemStorage.addSystem(.startUp, System(updateSystemFirstFrameSystem(commands:))) diff --git a/Sources/ECS/WorldMethods/World+Update.swift b/Sources/ECS/WorldMethods/World+Update.swift index 80212c9..f3519e3 100644 --- a/Sources/ECS/WorldMethods/World+Update.swift +++ b/Sources/ECS/WorldMethods/World+Update.swift @@ -28,7 +28,7 @@ public extension World { currentTimeResource.resource = CurrentTime(value: currentTime) - let commands = self.worldStorage.commandsStorage.commands()! + let commands = worldStorage.commands // lifecycle 1: pre update self.preUpdatePhase() diff --git a/Tests/ecs-swiftTests/CommandsTests.swift b/Tests/ecs-swiftTests/CommandsTests.swift index 171b2a6..cd99ee2 100644 --- a/Tests/ecs-swiftTests/CommandsTests.swift +++ b/Tests/ecs-swiftTests/CommandsTests.swift @@ -31,8 +31,7 @@ class TestCommand_Despawn: Command { final class CommandsTests: XCTestCase { func testCommands() { let world = World() - world.worldStorage.commandsStorage.setCommands(Commands()) - let commands = world.worldStorage.commandsStorage.commands()! + let commands = world.worldStorage.commands let testEntities = [Entity(slot: 0, generation: 0), Entity(slot: 1, generation: 0), Entity(slot: 2, generation: 0)] diff --git a/Tests/ecs-swiftTests/EntityCommandsTests.swift b/Tests/ecs-swiftTests/EntityCommandsTests.swift index c04ba0c..8baaaca 100644 --- a/Tests/ecs-swiftTests/EntityCommandsTests.swift +++ b/Tests/ecs-swiftTests/EntityCommandsTests.swift @@ -10,9 +10,8 @@ import XCTest final class EntityCommandsTests: XCTestCase { func testEntityCommands() { - let commands = Commands() let world = World() - world.worldStorage.commandsStorage.setCommands(commands) + let commands = world.worldStorage.commands // world に entity を生成し, component を追加し, id(id としての entity) を受け取ります. let entity = commands.spawn() diff --git a/Tests/ecs-swiftTests/FilteredQueryTests.swift b/Tests/ecs-swiftTests/FilteredQueryTests.swift index 1f493bf..b79a833 100644 --- a/Tests/ecs-swiftTests/FilteredQueryTests.swift +++ b/Tests/ecs-swiftTests/FilteredQueryTests.swift @@ -20,7 +20,7 @@ final class FilteredQueryTests: XCTestCase { world.worldStorage.chunkStorage.addChunk(testQueryOr) world.worldStorage.chunkStorage.addChunk(testQueryWithout) - let commands = world.worldStorage.commandsStorage.commands()! + let commands = world.worldStorage.commands let entity = commands.spawn() .addComponent(TestComponent(content: "c0")) diff --git a/Tests/ecs-swiftTests/QueryTests.swift b/Tests/ecs-swiftTests/QueryTests.swift index c0df716..89544a8 100644 --- a/Tests/ecs-swiftTests/QueryTests.swift +++ b/Tests/ecs-swiftTests/QueryTests.swift @@ -36,7 +36,7 @@ final class QueryTests: XCTestCase { world.worldStorage.chunkStorage.addChunk(testEntityQuery4) world.worldStorage.chunkStorage.addChunk(testEntityQuery5) - let commands = world.worldStorage.commandsStorage.commands()! + let commands = world.worldStorage.commands let testEntity = commands.spawn().addComponent(TestComponent(content: "test")).id() @@ -163,7 +163,7 @@ final class QueryTests: XCTestCase { world.worldStorage.chunkStorage.addChunk(testEntityQuery4) world.worldStorage.chunkStorage.addChunk(testEntityQuery5) - let commands = world.worldStorage.commandsStorage.commands()! + let commands = world.worldStorage.commands let testEntity = commands.spawn().addComponent(TestComponent(content: "test")).id() From 50b69987a423a2cd8c025abb268fa321ff8ae7eb Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Fri, 4 Jul 2025 03:39:04 +0900 Subject: [PATCH 40/53] Add AdditionalStorage protocol and extension for managing storage elements --- Sources/ECS/Commons/AdditionalStorage.swift | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Sources/ECS/Commons/AdditionalStorage.swift diff --git a/Sources/ECS/Commons/AdditionalStorage.swift b/Sources/ECS/Commons/AdditionalStorage.swift new file mode 100644 index 0000000..c089d34 --- /dev/null +++ b/Sources/ECS/Commons/AdditionalStorage.swift @@ -0,0 +1,26 @@ +// +// AdditionalStorage.swift +// ECS_Swift +// +// Created by rrbox on 2025/07/03. +// + +public protocol AdditionalStorageElement: WorldStorageElement {} + +public enum AdditionalStorage: WorldStorageType {} + +public extension AnyMap where Mode == AdditionalStorage { + mutating func push(_ data: T) { + self.body[ObjectIdentifier(T.self)] = Box(body: data) + } + + mutating func pop(_ type: T.Type) { + self.body.removeValue(forKey: ObjectIdentifier(T.self)) + } + + func valueRef(ofType type: T.Type) -> Box? { + guard let result = self.body[ObjectIdentifier(T.self)] else { return nil } + return (result as! Box) + } +} + From 1ef2d20b529efa0c0b3082f795f1b0aa324bb2ac Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Fri, 4 Jul 2025 03:39:18 +0900 Subject: [PATCH 41/53] Refactor Storage Management --- Sources/ECS/Chunk/Chunk.swift | 2 +- Sources/ECS/Chunk/ChunkEntityInterface.swift | 2 +- Sources/ECS/Chunk/ChunkStorage+.swift | 22 +- Sources/ECS/Chunk/ChunkStorage.swift | 29 +- Sources/ECS/Commons/WorldStorage.swift | 23 +- .../ComponentTransactions/AddBundle.swift | 2 +- .../AddComponentCommand.swift | 2 +- .../RemoveComponentCommand.swift | 2 +- .../EntityCommands/EntityCommands.swift | 4 +- .../CommandsEvent/CommandsEvent+Buffer.swift | 22 +- .../CommandsEvent/CommandsEventQueue.swift | 2 +- .../CommandsEvent/CommandsEventWriter.swift | 2 +- Sources/ECS/Event/EventStreaming/Event.swift | 4 +- .../ECS/Event/EventStreaming/EventQueue.swift | 2 +- .../Event/EventStreaming/EventReader.swift | 4 +- .../Event/EventStreaming/EventResponder.swift | 2 +- .../Event/EventStreaming/EventStorage.swift | 51 ++-- .../Event/EventStreaming/EventWriter.swift | 2 +- Sources/ECS/Event/World+EventStreamer.swift | 18 +- Sources/ECS/Resource/Commands+Resource.swift | 2 +- Sources/ECS/Resource/Resource.swift | 4 +- Sources/ECS/Resource/ResourceBuffer.swift | 30 +- Sources/ECS/Resource/World+Resource.swift | 2 +- Sources/ECS/States/State.swift | 288 +++++++++--------- Sources/ECS/States/StateControll.swift | 22 +- Sources/ECS/System/SystemBuffer.swift | 45 +-- Sources/ECS/World/WorldStorageRef.swift | 7 +- Sources/ECS/WorldMethods/FirstFrame.swift | 6 +- Sources/ECS/WorldMethods/World+Init.swift | 43 ++- Sources/ECS/WorldMethods/World+Update.swift | 8 +- .../Commands/EntityCommands+Graphic.swift | 6 +- .../Graphic2D/Commands/SetGraphic.swift | 2 +- 32 files changed, 341 insertions(+), 321 deletions(-) diff --git a/Sources/ECS/Chunk/Chunk.swift b/Sources/ECS/Chunk/Chunk.swift index 045f90d..fab3f73 100644 --- a/Sources/ECS/Chunk/Chunk.swift +++ b/Sources/ECS/Chunk/Chunk.swift @@ -5,7 +5,7 @@ // Created by rrbox on 2023/08/11. // -public class Chunk { +public class Chunk: ChunkStorageElement { /// World に追加された func spawn(entityRecord: EntityRecordRef) {} func despawn(entity: Entity) {} diff --git a/Sources/ECS/Chunk/ChunkEntityInterface.swift b/Sources/ECS/Chunk/ChunkEntityInterface.swift index a669fbb..2ef96f4 100644 --- a/Sources/ECS/Chunk/ChunkEntityInterface.swift +++ b/Sources/ECS/Chunk/ChunkEntityInterface.swift @@ -8,7 +8,7 @@ /// Chunk を種類関係なく格納するためのコンポーネントです. /// /// Entity の変更を全ての Chunk に反映させる目的で使用されます. -class ChunkEntityInterface: WorldStorageElement { +class ChunkEntityInterface: ChunkStorageElement { /// entity が spawn されてから component が完全に挿入されるまでの間, entity を queue に保管します. /// /// Entity が ``Commands/spawn()`` され, ``EntityCommands/addComponent(_:)`` されるまでの間, Entity は実際には Chunk に反映されず, diff --git a/Sources/ECS/Chunk/ChunkStorage+.swift b/Sources/ECS/Chunk/ChunkStorage+.swift index b554bbe..97bc867 100644 --- a/Sources/ECS/Chunk/ChunkStorage+.swift +++ b/Sources/ECS/Chunk/ChunkStorage+.swift @@ -5,34 +5,34 @@ // Created by rrbox on 2023/08/11. // -extension ChunkStorage { - func setUpChunkBuffer() { - self.buffer.map.push(ChunkEntityInterface()) +extension AnyMap { + mutating func setUpChunkBuffer() { + push(ChunkEntityInterface()) } - func addChunk(_ chunk: ChunkType) { - self.buffer.map.push(chunk) - self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.add(chunk: chunk) + mutating func addChunk(_ chunk: ChunkType) { + push(chunk) + valueRef(ofType: ChunkEntityInterface.self)!.body.add(chunk: chunk) } func pushSpawned(entityRecord: EntityRecordRef) { - self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.pushSpawned(entityRecord: entityRecord) + valueRef(ofType: ChunkEntityInterface.self)!.body.pushSpawned(entityRecord: entityRecord) } func applySpawnedEntityQueue() { - self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.applySpawnedEntityQueue() + valueRef(ofType: ChunkEntityInterface.self)!.body.applySpawnedEntityQueue() } public func pushUpdated(entityRecord: EntityRecordRef) { - self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.pushUpdated(entityRecord: entityRecord) + valueRef(ofType: ChunkEntityInterface.self)!.body.pushUpdated(entityRecord: entityRecord) } func applyUpdatedEntityQueue() { - self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.applyUpdatedEntityQueue() + valueRef(ofType: ChunkEntityInterface.self)!.body.applyUpdatedEntityQueue() } func despawn(entity: Entity) { - self.buffer.map.valueRef(ofType: ChunkEntityInterface.self)!.body.despawn(entity: entity) + valueRef(ofType: ChunkEntityInterface.self)!.body.despawn(entity: entity) } } diff --git a/Sources/ECS/Chunk/ChunkStorage.swift b/Sources/ECS/Chunk/ChunkStorage.swift index 29c3edd..5b90cf3 100644 --- a/Sources/ECS/Chunk/ChunkStorage.swift +++ b/Sources/ECS/Chunk/ChunkStorage.swift @@ -5,24 +5,31 @@ // Created by rrbox on 2023/08/11. // -extension Chunk: WorldStorageElement { +protocol ChunkStorageElement: WorldStorageElement { } -/// Chunk を種類別で格納します -final public class ChunkStorage { - let buffer: WorldStorageRef - init(buffer: WorldStorageRef) { - self.buffer = buffer +public enum ChunkStorage: WorldStorageType { + +} + +extension AnyMap where Mode == ChunkStorage { + mutating func push(_ data: T) { + self.body[ObjectIdentifier(T.self)] = Box(body: data) + } + + mutating func pop(_ type: T.Type) { + self.body.removeValue(forKey: ObjectIdentifier(T.self)) } - public func chunk(ofType type: ChunkType.Type) -> ChunkType? { - self.buffer.map.valueRef(ofType: ChunkType.self)?.body + func valueRef(ofType type: T.Type) -> Box? { + guard let result = self.body[ObjectIdentifier(T.self)] else { return nil } + return (result as! Box) } } -public extension WorldStorageRef { - var chunkStorage: ChunkStorage { - ChunkStorage(buffer: self) +extension AnyMap { + func chunk(ofType type: ChunkType.Type) -> ChunkType? { + valueRef(ofType: ChunkType.self)?.body } } diff --git a/Sources/ECS/Commons/WorldStorage.swift b/Sources/ECS/Commons/WorldStorage.swift index d25aa8e..373c49f 100644 --- a/Sources/ECS/Commons/WorldStorage.swift +++ b/Sources/ECS/Commons/WorldStorage.swift @@ -5,29 +5,14 @@ // Created by rrbox on 2023/10/22. // -enum WorldStorage {} +protocol WorldStorageType {} -protocol WorldStorageElement {} +public protocol WorldStorageElement {} -class Box: Item { - var body: T +final public class Box: Item { + private(set) var body: T init(body: T) { self.body = body } } - -extension AnyMap where Mode == WorldStorage { - mutating func push(_ data: T) { - self.body[ObjectIdentifier(T.self)] = Box(body: data) - } - - mutating func pop(_ type: T.Type) { - self.body.removeValue(forKey: ObjectIdentifier(T.self)) - } - - func valueRef(ofType type: T.Type) -> Box? { - guard let result = self.body[ObjectIdentifier(T.self)] else { return nil } - return (result as! Box) - } -} diff --git a/Sources/ECS/EntityCommands/ComponentTransactions/AddBundle.swift b/Sources/ECS/EntityCommands/ComponentTransactions/AddBundle.swift index 402722c..1b4ab51 100644 --- a/Sources/ECS/EntityCommands/ComponentTransactions/AddBundle.swift +++ b/Sources/ECS/EntityCommands/ComponentTransactions/AddBundle.swift @@ -5,7 +5,7 @@ // Created by rrbox on 2024/06/09. // -class AddBundle: EntityCommand { +final class AddBundle: EntityCommand { let bundle: T init(entity: Entity, bundle: T) { diff --git a/Sources/ECS/EntityCommands/ComponentTransactions/AddComponentCommand.swift b/Sources/ECS/EntityCommands/ComponentTransactions/AddComponentCommand.swift index ca50c19..d009fac 100644 --- a/Sources/ECS/EntityCommands/ComponentTransactions/AddComponentCommand.swift +++ b/Sources/ECS/EntityCommands/ComponentTransactions/AddComponentCommand.swift @@ -5,7 +5,7 @@ // Created by rrbox on 2023/08/10. // -class AddComponent: EntityCommand { +final class AddComponent: EntityCommand { let component: C init(entity: Entity, component: C) { diff --git a/Sources/ECS/EntityCommands/ComponentTransactions/RemoveComponentCommand.swift b/Sources/ECS/EntityCommands/ComponentTransactions/RemoveComponentCommand.swift index 969fa47..29fc205 100644 --- a/Sources/ECS/EntityCommands/ComponentTransactions/RemoveComponentCommand.swift +++ b/Sources/ECS/EntityCommands/ComponentTransactions/RemoveComponentCommand.swift @@ -5,7 +5,7 @@ // Created by rrbox on 2023/08/10. // -class RemoveComponent: EntityCommand { +final class RemoveComponent: EntityCommand { init(entity: Entity, componentType type: C.Type) { super.init(entity: entity) } diff --git a/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift index 8d70331..aecf214 100644 --- a/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift +++ b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift @@ -9,7 +9,7 @@ class EntityCommandQueue: EntityTransaction { var queue = [EntityCommand]() } -class SpawnedEntityCommandQueue: EntityCommandQueue { +final class SpawnedEntityCommandQueue: EntityCommandQueue { let record: EntityRecordRef init(record: EntityRecordRef) { @@ -23,7 +23,7 @@ class SpawnedEntityCommandQueue: EntityCommandQueue { } } -class SearchedEntityCommandQueue: EntityCommandQueue { +final class SearchedEntityCommandQueue: EntityCommandQueue { let entity: Entity init(entity: Entity) { diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift b/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift index c83991c..999954b 100644 --- a/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift +++ b/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift @@ -5,29 +5,29 @@ // Created by rrbox on 2023/08/29. // -extension EventStorage { - func setUpCommandsEventQueue(eventOfType: T.Type) { - self.buffer.map.push(CommandsEventQueue()) +extension AnyMap { + mutating func setUpCommandsEventQueue(eventOfType: T.Type) { + push(CommandsEventQueue()) } func commandsEventQueue(eventOfType: T.Type) -> CommandsEventQueue? { - self.buffer.map.valueRef(ofType: CommandsEventQueue.self)?.body + valueRef(ofType: CommandsEventQueue.self)?.body } func commandsEventWriter(eventOfType type: T.Type) -> CommandsEventWriter? { - self.buffer.map.valueRef(ofType: CommandsEventWriter.self)?.body + valueRef(ofType: CommandsEventWriter.self)?.body } - func registerCommandsEventWriter(eventType: T.Type) { - let eventQueue = self.buffer.map.valueRef(ofType: CommandsEventQueue.self)!.body - self.buffer.map.push(CommandsEventWriter(eventQueue: eventQueue)) + mutating func registerCommandsEventWriter(eventType: T.Type) { + let eventQueue = valueRef(ofType: CommandsEventQueue.self)!.body + push(CommandsEventWriter(eventQueue: eventQueue)) } func commandsEventResponder(eventOfType type: T.Type) -> EventResponder? { - self.buffer.map.valueRef(ofType: EventResponder.self)?.body + valueRef(ofType: EventResponder.self)?.body } - func resisterCommandsEventResponder(eventType: T.Type) { - self.buffer.map.push(EventResponder()) + mutating func resisterCommandsEventResponder(eventType: T.Type) { + push(EventResponder()) } } diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift b/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift index c464e48..b582aee 100644 --- a/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift +++ b/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift @@ -5,7 +5,7 @@ // Created by rrbox on 2023/08/29. // -class CommandsEventQueue: WorldStorageElement { +final class CommandsEventQueue: EventStorageElement { var eventQueue = [T]() var sendingEvents = [T]() } diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift b/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift index 4ee9134..afceacf 100644 --- a/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift +++ b/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift @@ -5,7 +5,7 @@ // Created by rrbox on 2023/08/29. // -final class CommandsEventWriter: SystemParameter, WorldStorageElement { +final class CommandsEventWriter: SystemParameter, EventStorageElement { unowned let eventQueue: CommandsEventQueue init(eventQueue: CommandsEventQueue) { diff --git a/Sources/ECS/Event/EventStreaming/Event.swift b/Sources/ECS/Event/EventStreaming/Event.swift index 6908539..b890dec 100644 --- a/Sources/ECS/Event/EventStreaming/Event.swift +++ b/Sources/ECS/Event/EventStreaming/Event.swift @@ -23,7 +23,7 @@ final class Event: AnyEvent { } override func runEventReceiver(worldStorage: WorldStorageRef) { - worldStorage.map.push(EventReader(value: self.value)) + worldStorage.eventStorage.push(EventReader(value: self.value)) if let systems = worldStorage.eventStorage.eventResponder(eventOfType: T.self)!.systems[.update] { for system in systems { @@ -38,6 +38,6 @@ final class Event: AnyEvent { } } - worldStorage.map.pop(EventReader.self) + worldStorage.eventStorage.pop(EventReader.self) } } diff --git a/Sources/ECS/Event/EventStreaming/EventQueue.swift b/Sources/ECS/Event/EventStreaming/EventQueue.swift index 60b3c4c..979c4b7 100644 --- a/Sources/ECS/Event/EventStreaming/EventQueue.swift +++ b/Sources/ECS/Event/EventStreaming/EventQueue.swift @@ -5,7 +5,7 @@ // Created by rrbox on 2023/08/14. // -class EventQueue: WorldStorageElement { +final class EventQueue: EventStorageElement { var eventQueue = [AnyEvent]() var sendingEvents = [AnyEvent]() } diff --git a/Sources/ECS/Event/EventStreaming/EventReader.swift b/Sources/ECS/Event/EventStreaming/EventReader.swift index 7214b5a..f5fb161 100644 --- a/Sources/ECS/Event/EventStreaming/EventReader.swift +++ b/Sources/ECS/Event/EventStreaming/EventReader.swift @@ -5,7 +5,7 @@ // Created by rrbox on 2023/08/14. // -final public class EventReader: SystemParameter, WorldStorageElement { +final public class EventReader: SystemParameter, EventStorageElement { public let value: T init(value: T) { @@ -17,6 +17,6 @@ final public class EventReader: SystemParameter, WorldStorageElement { } public static func getParameter(from worldStorage: WorldStorageRef) -> EventReader? { - return worldStorage.map.valueRef(ofType: EventReader.self)?.body + return worldStorage.eventStorage.valueRef(ofType: EventReader.self)?.body } } diff --git a/Sources/ECS/Event/EventStreaming/EventResponder.swift b/Sources/ECS/Event/EventStreaming/EventResponder.swift index 8a91352..a1c3e01 100644 --- a/Sources/ECS/Event/EventStreaming/EventResponder.swift +++ b/Sources/ECS/Event/EventStreaming/EventResponder.swift @@ -16,7 +16,7 @@ final public class EventResponderBuilder { } } -final public class EventResponder: WorldStorageElement { +final public class EventResponder: EventStorageElement { var systems: [EventSchedule: [SystemExecute]] = [:] } diff --git a/Sources/ECS/Event/EventStreaming/EventStorage.swift b/Sources/ECS/Event/EventStreaming/EventStorage.swift index fee77a2..64c5528 100644 --- a/Sources/ECS/Event/EventStreaming/EventStorage.swift +++ b/Sources/ECS/Event/EventStreaming/EventStorage.swift @@ -7,43 +7,50 @@ import Foundation -// world buffer にプロパティをつけておく -class EventStorage { - let buffer: WorldStorageRef +enum EventStorage: WorldStorageType {} + +protocol EventStorageElement: WorldStorageElement {} - init(buffer: WorldStorageRef) { - self.buffer = buffer +extension AnyMap where Mode == EventStorage { + mutating func push(_ data: T) { + self.body[ObjectIdentifier(T.self)] = Box(body: data) } - func setUpEventQueue() { - self.buffer.map.push(EventQueue()) + mutating func pop(_ type: T.Type) { + self.body.removeValue(forKey: ObjectIdentifier(T.self)) } - func eventQueue() -> EventQueue? { - self.buffer.map.valueRef(ofType: EventQueue.self)?.body + func valueRef(ofType type: T.Type) -> Box? { + guard let result = self.body[ObjectIdentifier(T.self)] else { return nil } + return (result as! Box) } +} - func eventWriter(eventOfType type: T.Type) -> EventWriter? { - self.buffer.map.valueRef(ofType: EventWriter.self)?.body +// world buffer にプロパティをつけておく + +extension AnyMap { + mutating func setUpEventQueue() { + push(EventQueue()) } - func registerEventWriter(eventType: T.Type) { - let eventQueue = self.buffer.map.valueRef(ofType: EventQueue.self)!.body - self.buffer.map.push(EventWriter(eventQueue: eventQueue)) + func eventQueue() -> EventQueue? { + valueRef(ofType: EventQueue.self)?.body } - func eventResponder(eventOfType type: T.Type) -> EventResponder? { - self.buffer.map.valueRef(ofType: EventResponder.self)?.body + func eventWriter(eventOfType type: T.Type) -> EventWriter? { + valueRef(ofType: EventWriter.self)?.body } - func registerEventResponder(eventType: T.Type) { - self.buffer.map.push(EventResponder()) + mutating func registerEventWriter(eventType: T.Type) { + let eventQueue = valueRef(ofType: EventQueue.self)!.body + push(EventWriter(eventQueue: eventQueue)) } -} + func eventResponder(eventOfType type: T.Type) -> EventResponder? { + valueRef(ofType: EventResponder.self)?.body + } -extension WorldStorageRef { - var eventStorage: EventStorage { - EventStorage(buffer: self) + mutating func registerEventResponder(eventType: T.Type) { + push(EventResponder()) } } diff --git a/Sources/ECS/Event/EventStreaming/EventWriter.swift b/Sources/ECS/Event/EventStreaming/EventWriter.swift index 2701ef4..ec47c4d 100644 --- a/Sources/ECS/Event/EventStreaming/EventWriter.swift +++ b/Sources/ECS/Event/EventStreaming/EventWriter.swift @@ -6,7 +6,7 @@ // // Commands と基本的な仕組みは同じ. -final public class EventWriter: SystemParameter, WorldStorageElement { +final public class EventWriter: SystemParameter, EventStorageElement { unowned let eventQueue: EventQueue init(eventQueue: EventQueue) { diff --git a/Sources/ECS/Event/World+EventStreamer.swift b/Sources/ECS/Event/World+EventStreamer.swift index 23c8654..f80cf95 100644 --- a/Sources/ECS/Event/World+EventStreamer.swift +++ b/Sources/ECS/Event/World+EventStreamer.swift @@ -10,21 +10,17 @@ public extension World { /// /// `Event` をイベントシステムで扱う前に, World に EventStreamer を追加する必要があります. @discardableResult func addEventStreamer(eventType: T.Type) -> World { - let eventStorage = self.worldStorage.eventStorage - eventStorage.registerEventWriter(eventType: T.self) - eventStorage.registerEventResponder(eventType: T.self) - + worldStorage.eventStorage.registerEventWriter(eventType: T.self) + worldStorage.eventStorage.registerEventResponder(eventType: T.self) return self } } extension World { func addCommandsEventStreamer(eventType: T.Type) { - self.worldStorage.systemStorage.insertSchedule(.onCommandsEvent(ofType: T.self)) - - let eventStorage = self.worldStorage.eventStorage - eventStorage.registerCommandsEventWriter(eventType: T.self) - eventStorage.resisterCommandsEventResponder(eventType: T.self) + worldStorage.systemStorage.insertSchedule(.onCommandsEvent(ofType: T.self)) + worldStorage.eventStorage.registerCommandsEventWriter(eventType: T.self) + worldStorage.eventStorage.resisterCommandsEventResponder(eventType: T.self) } } @@ -45,7 +41,7 @@ extension World { eventQueue.sendingEvents = eventQueue.eventQueue eventQueue.eventQueue = [] for event in eventQueue.sendingEvents { - self.worldStorage.map.push(EventReader(value: event)) + worldStorage.eventStorage.push(EventReader(value: event)) if let systems = eventStorage.commandsEventResponder(eventOfType: T.self)!.systems[.update] { for system in systems { @@ -60,7 +56,7 @@ extension World { } } - self.worldStorage.map.pop(EventReader.self) + worldStorage.eventStorage.pop(EventReader.self) } eventQueue.sendingEvents = [] } diff --git a/Sources/ECS/Resource/Commands+Resource.swift b/Sources/ECS/Resource/Commands+Resource.swift index afa7845..f37c2de 100644 --- a/Sources/ECS/Resource/Commands+Resource.swift +++ b/Sources/ECS/Resource/Commands+Resource.swift @@ -5,7 +5,7 @@ // Created by rrbox on 2023/08/12. // -class AddResource: Command { +final class AddResource: Command { let resrouce: T init(resrouce: T) { diff --git a/Sources/ECS/Resource/Resource.swift b/Sources/ECS/Resource/Resource.swift index 88b63d6..ba4ee64 100644 --- a/Sources/ECS/Resource/Resource.swift +++ b/Sources/ECS/Resource/Resource.swift @@ -19,7 +19,7 @@ public protocol ResourceProtocol { - note: 詳細は を参照してください. */ -final public class Resource: WorldStorageElement, SystemParameter { +final public class Resource: ResourceStorageElement, SystemParameter { public var resource: T init(_ resource: T) { @@ -31,7 +31,7 @@ final public class Resource: WorldStorageElement, SystemPar } public static func getParameter(from worldStorage: WorldStorageRef) -> Resource? { - worldStorage.resourceBuffer.resource(ofType: T.self) + worldStorage.resourceStorage.resource(ofType: T.self) } } diff --git a/Sources/ECS/Resource/ResourceBuffer.swift b/Sources/ECS/Resource/ResourceBuffer.swift index 3e788b5..c6016de 100644 --- a/Sources/ECS/Resource/ResourceBuffer.swift +++ b/Sources/ECS/Resource/ResourceBuffer.swift @@ -5,23 +5,31 @@ // Created by rrbox on 2023/08/12. // -final public class ResourceBuffer { - let buffer: WorldStorageRef - init(buffer: WorldStorageRef) { - self.buffer = buffer +enum ResourceStorage: WorldStorageType {} + +protocol ResourceStorageElement: WorldStorageElement {} + +extension AnyMap where Mode == ResourceStorage { + mutating func push(_ data: T) { + self.body[ObjectIdentifier(T.self)] = Box(body: data) } - func addResource(_ resource: T) { - self.buffer.map.push(Resource(resource)) + mutating func pop(_ type: T.Type) { + self.body.removeValue(forKey: ObjectIdentifier(T.self)) } - public func resource(ofType type: T.Type) -> Resource? { - self.buffer.map.valueRef(ofType: Resource.self)?.body + func valueRef(ofType type: T.Type) -> Box? { + guard let result = self.body[ObjectIdentifier(T.self)] else { return nil } + return (result as! Box) } } -public extension WorldStorageRef { - var resourceBuffer: ResourceBuffer { - ResourceBuffer(buffer: self) +extension AnyMap { + mutating func addResource(_ resource: T) { + push(Resource(resource)) + } + + public func resource(ofType type: T.Type) -> Resource? { + valueRef(ofType: Resource.self)?.body } } diff --git a/Sources/ECS/Resource/World+Resource.swift b/Sources/ECS/Resource/World+Resource.swift index 309e2dc..478efd2 100644 --- a/Sources/ECS/Resource/World+Resource.swift +++ b/Sources/ECS/Resource/World+Resource.swift @@ -10,7 +10,7 @@ public extension World { /// /// ``Commands/addResource(_:)`` とは異なり, resource はこのメソッドが実行されてすぐに追加されます. @discardableResult func addResource(_ resource: T) -> World { - self.worldStorage.resourceBuffer.addResource(resource) + worldStorage.resourceStorage.addResource(resource) return self } } diff --git a/Sources/ECS/States/State.swift b/Sources/ECS/States/State.swift index 4b3a3d5..11c49d3 100644 --- a/Sources/ECS/States/State.swift +++ b/Sources/ECS/States/State.swift @@ -9,7 +9,7 @@ public protocol StateProtocol: Hashable { } -class StateRegistry: WorldStorageElement { +class StateRegistry: StateStorageElement { var currentState: T var inactiveStates = [T]() @@ -18,223 +18,237 @@ class StateRegistry: WorldStorageElement { } } -public class StateStorage { - class StateAssociatedSchedules: WorldStorageElement { - var schedules = Set() - var eventSchedules = Set() +enum StateStorage: WorldStorageType {} + +protocol StateStorageElement: WorldStorageElement {} + +extension AnyMap where Mode == StateStorage { + mutating func pushStorageElement(_ data: T) { + self.body[ObjectIdentifier(T.self)] = Box(body: data) } - class StateTransitionQueue: WorldStorageElement { - private(set) var didEnterQueue = [Schedule]() - private(set) var willExitQueue = [Schedule]() - private(set) var onResumeQueue = [Schedule]() - private(set) var onPauseQueue = [Schedule]() - private(set) var onUpdateNewStateQueue = [Schedule]() - private(set) var onUpdatePreviousStateQueue = [Schedule]() - private(set) var onStackUpdateNewStateQueue = [Schedule]() - private(set) var onStackUpdatePreviousStateQueue = [Schedule]() - private(set) var onInactiveUpdateNewStateQueue = [Schedule]() - private(set) var onInactiveUpdatePreviousStateQueue = [Schedule]() - - private(set) var didEnterEventQueue = [EventSchedule]() - private(set) var willExitEventQueue = [EventSchedule]() - private(set) var onResumeEventQueue = [EventSchedule]() - private(set) var onPauseEventQueue = [EventSchedule]() - private(set) var onUpdateNewStateEventQueue = [EventSchedule]() - private(set) var onUpdatePreviousStateEventQueue = [EventSchedule]() - private(set) var onStackUpdateNewStateEventQueue = [EventSchedule]() - private(set) var onStackUpdatePreviousStateEventQueue = [EventSchedule]() - private(set) var onInactiveUpdateNewStateEventQueue = [EventSchedule]() - private(set) var onInactiveUpdatePreviousStateEventQueue = [EventSchedule]() - - // enter - - func enqueueEntered(state: T) { - didEnterQueue.append(.didEnter(state)) - didEnterEventQueue.append(.didEnter(state)) - onUpdateNewStateQueue.append(.onUpdate(state)) - onUpdateNewStateEventQueue.append(.onUpdate(state)) - onStackUpdateNewStateQueue.append(.onStackUpdate(state)) - onStackUpdateNewStateEventQueue.append(.onStackUpdate(state)) - } + mutating func pop(_ type: T.Type) { + self.body.removeValue(forKey: ObjectIdentifier(T.self)) + } - func enqueueExited(state: T) { - willExitQueue.append(.willExit(state)) - willExitEventQueue.append(.willExit(state)) - onUpdatePreviousStateQueue.append(.onUpdate(state)) - onUpdatePreviousStateEventQueue.append(.onUpdate(state)) - onStackUpdatePreviousStateQueue.append(.onStackUpdate(state)) - onStackUpdatePreviousStateEventQueue.append(.onStackUpdate(state)) - } + func valueRef(ofType type: T.Type) -> Box? { + guard let result = self.body[ObjectIdentifier(T.self)] else { return nil } + return (result as! Box) + } +} - // push +final class StateAssociatedSchedules: StateStorageElement { + var schedules = Set() + var eventSchedules = Set() +} - func enqueuePushed(state: T) { - didEnterQueue.append(.didEnter(state)) - didEnterEventQueue.append(.didEnter(state)) - onUpdateNewStateQueue.append(.onUpdate(state)) - onUpdateNewStateEventQueue.append(.onUpdate(state)) - onStackUpdateNewStateQueue.append(.onStackUpdate(state)) - onStackUpdateNewStateEventQueue.append(.onStackUpdate(state)) - } +final class StateTransitionQueue: StateStorageElement { + private(set) var didEnterQueue = [Schedule]() + private(set) var willExitQueue = [Schedule]() + private(set) var onResumeQueue = [Schedule]() + private(set) var onPauseQueue = [Schedule]() + private(set) var onUpdateNewStateQueue = [Schedule]() + private(set) var onUpdatePreviousStateQueue = [Schedule]() + private(set) var onStackUpdateNewStateQueue = [Schedule]() + private(set) var onStackUpdatePreviousStateQueue = [Schedule]() + private(set) var onInactiveUpdateNewStateQueue = [Schedule]() + private(set) var onInactiveUpdatePreviousStateQueue = [Schedule]() + + private(set) var didEnterEventQueue = [EventSchedule]() + private(set) var willExitEventQueue = [EventSchedule]() + private(set) var onResumeEventQueue = [EventSchedule]() + private(set) var onPauseEventQueue = [EventSchedule]() + private(set) var onUpdateNewStateEventQueue = [EventSchedule]() + private(set) var onUpdatePreviousStateEventQueue = [EventSchedule]() + private(set) var onStackUpdateNewStateEventQueue = [EventSchedule]() + private(set) var onStackUpdatePreviousStateEventQueue = [EventSchedule]() + private(set) var onInactiveUpdateNewStateEventQueue = [EventSchedule]() + private(set) var onInactiveUpdatePreviousStateEventQueue = [EventSchedule]() + + // enter + + func enqueueEntered(state: T) { + didEnterQueue.append(.didEnter(state)) + didEnterEventQueue.append(.didEnter(state)) + onUpdateNewStateQueue.append(.onUpdate(state)) + onUpdateNewStateEventQueue.append(.onUpdate(state)) + onStackUpdateNewStateQueue.append(.onStackUpdate(state)) + onStackUpdateNewStateEventQueue.append(.onStackUpdate(state)) + } - func enqueuePaused(state: T) { - onPauseQueue.append(.onPause(state)) - onPauseEventQueue.append(.onPause(state)) - onUpdatePreviousStateQueue.append(.onUpdate(state)) - onUpdatePreviousStateEventQueue.append(.onUpdate(state)) - onInactiveUpdateNewStateQueue.append(.onInactiveUpdate(state)) - onInactiveUpdateNewStateEventQueue.append(.onInactiveUpdate(state)) - } + func enqueueExited(state: T) { + willExitQueue.append(.willExit(state)) + willExitEventQueue.append(.willExit(state)) + onUpdatePreviousStateQueue.append(.onUpdate(state)) + onUpdatePreviousStateEventQueue.append(.onUpdate(state)) + onStackUpdatePreviousStateQueue.append(.onStackUpdate(state)) + onStackUpdatePreviousStateEventQueue.append(.onStackUpdate(state)) + } - // pop + // push - func enqueuePopped(state: T) { - onUpdatePreviousStateQueue.append(.onUpdate(state)) - onUpdatePreviousStateEventQueue.append(.onUpdate(state)) - onStackUpdatePreviousStateQueue.append(.onStackUpdate(state)) - onStackUpdatePreviousStateEventQueue.append(.onStackUpdate(state)) - willExitQueue.append(.willExit(state)) - willExitEventQueue.append(.willExit(state)) - } + func enqueuePushed(state: T) { + didEnterQueue.append(.didEnter(state)) + didEnterEventQueue.append(.didEnter(state)) + onUpdateNewStateQueue.append(.onUpdate(state)) + onUpdateNewStateEventQueue.append(.onUpdate(state)) + onStackUpdateNewStateQueue.append(.onStackUpdate(state)) + onStackUpdateNewStateEventQueue.append(.onStackUpdate(state)) + } - func enqueueResumed(state: T) { - onResumeQueue.append(.onResume(state)) - onResumeEventQueue.append(.onResume(state)) - onUpdateNewStateQueue.append(.onUpdate(state)) - onUpdateNewStateEventQueue.append(.onUpdate(state)) - onInactiveUpdatePreviousStateQueue.append(.onInactiveUpdate(state)) - onInactiveUpdatePreviousStateEventQueue.append(.onInactiveUpdate(state)) - } + func enqueuePaused(state: T) { + onPauseQueue.append(.onPause(state)) + onPauseEventQueue.append(.onPause(state)) + onUpdatePreviousStateQueue.append(.onUpdate(state)) + onUpdatePreviousStateEventQueue.append(.onUpdate(state)) + onInactiveUpdateNewStateQueue.append(.onInactiveUpdate(state)) + onInactiveUpdateNewStateEventQueue.append(.onInactiveUpdate(state)) + } - // clear - - func clear() { - didEnterQueue = [] - willExitQueue = [] - onResumeQueue = [] - onPauseQueue = [] - onUpdatePreviousStateQueue = [] - onUpdateNewStateQueue = [] - onStackUpdatePreviousStateQueue = [] - onStackUpdateNewStateQueue = [] - onInactiveUpdatePreviousStateQueue = [] - onInactiveUpdateNewStateQueue = [] - - didEnterEventQueue = [] - willExitEventQueue = [] - onResumeEventQueue = [] - onPauseEventQueue = [] - onUpdateNewStateEventQueue = [] - onUpdatePreviousStateEventQueue = [] - onStackUpdateNewStateEventQueue = [] - onStackUpdatePreviousStateEventQueue = [] - onInactiveUpdateNewStateEventQueue = [] - onInactiveUpdatePreviousStateEventQueue = [] - } + // pop + + func enqueuePopped(state: T) { + onUpdatePreviousStateQueue.append(.onUpdate(state)) + onUpdatePreviousStateEventQueue.append(.onUpdate(state)) + onStackUpdatePreviousStateQueue.append(.onStackUpdate(state)) + onStackUpdatePreviousStateEventQueue.append(.onStackUpdate(state)) + willExitQueue.append(.willExit(state)) + willExitEventQueue.append(.willExit(state)) } - let storageRef: WorldStorageRef + func enqueueResumed(state: T) { + onResumeQueue.append(.onResume(state)) + onResumeEventQueue.append(.onResume(state)) + onUpdateNewStateQueue.append(.onUpdate(state)) + onUpdateNewStateEventQueue.append(.onUpdate(state)) + onInactiveUpdatePreviousStateQueue.append(.onInactiveUpdate(state)) + onInactiveUpdatePreviousStateEventQueue.append(.onInactiveUpdate(state)) + } - init(storageRef: WorldStorageRef) { - self.storageRef = storageRef + // clear + + func clear() { + didEnterQueue = [] + willExitQueue = [] + onResumeQueue = [] + onPauseQueue = [] + onUpdatePreviousStateQueue = [] + onUpdateNewStateQueue = [] + onStackUpdatePreviousStateQueue = [] + onStackUpdateNewStateQueue = [] + onInactiveUpdatePreviousStateQueue = [] + onInactiveUpdateNewStateQueue = [] + + didEnterEventQueue = [] + willExitEventQueue = [] + onResumeEventQueue = [] + onPauseEventQueue = [] + onUpdateNewStateEventQueue = [] + onUpdatePreviousStateEventQueue = [] + onStackUpdateNewStateEventQueue = [] + onStackUpdatePreviousStateEventQueue = [] + onInactiveUpdateNewStateEventQueue = [] + onInactiveUpdatePreviousStateEventQueue = [] } +} + +extension AnyMap { - func setUp() { - self.storageRef.map.push(StateAssociatedSchedules()) - self.storageRef.map.push(StateTransitionQueue()) + mutating func setUp() { + pushStorageElement(StateAssociatedSchedules()) + pushStorageElement(StateTransitionQueue()) } - func registerState(initialState: T, states: [T]) { - self.storageRef.map.push(StateRegistry(currentState: initialState)) - let queue = self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)!.body + mutating func registerState(initialState: T, states: [T]) { + pushStorageElement(StateRegistry(currentState: initialState)) + let queue = valueRef(ofType: StateTransitionQueue.self)!.body queue.enqueueEntered(state: initialState) } func currentState(ofType type: T.Type) -> T? { - self.storageRef.map.valueRef(ofType: StateRegistry.self)?.body.currentState + valueRef(ofType: StateRegistry.self)?.body.currentState } func currentSchedulesWhichAssociatedStates() -> Set { - self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body.schedules + valueRef(ofType: StateAssociatedSchedules.self)!.body.schedules } func currentEventSchedulesWhichAssociatedStates() -> Set { - self.storageRef.map.valueRef(ofType: StateAssociatedSchedules.self)!.body.eventSchedules + valueRef(ofType: StateAssociatedSchedules.self)!.body.eventSchedules } // MARK: - queue func didEnterQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .didEnterQueue } func willExitQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .willExitQueue } func onPauseQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .onPauseQueue } func onResumeQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .onResumeQueue } func onUpdatePreviousStateQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .onUpdatePreviousStateQueue } func onStackUpdatePreviousStateQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .onStackUpdatePreviousStateQueue } func onUpdateNewStateQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .onUpdateNewStateQueue } func onStackUpdateNewStateQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .onStackUpdateNewStateQueue } func onInactiveNewStateQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .onInactiveUpdateNewStateQueue } func onInactivePreviousStateQueue() -> [Schedule] { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body .onInactiveUpdatePreviousStateQueue } func clearQueue() { - self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)! + valueRef(ofType: StateTransitionQueue.self)! .body.clear() } // MARK: - commands func enter(_ state: T) { - let stateRegistry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body - let queue = self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)!.body + let stateRegistry = valueRef(ofType: StateRegistry.self)!.body + let queue = valueRef(ofType: StateTransitionQueue.self)!.body let previous = stateRegistry.currentState queue.enqueueExited(state: previous) @@ -243,8 +257,8 @@ public class StateStorage { } func push(_ state: T) { - let stateRegistry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body - let queue = self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)!.body + let stateRegistry = valueRef(ofType: StateRegistry.self)!.body + let queue = valueRef(ofType: StateTransitionQueue.self)!.body let previous = stateRegistry.currentState queue.enqueuePaused(state: previous) @@ -254,8 +268,8 @@ public class StateStorage { } func pop(_ stateType: T.Type) { - let registry = self.storageRef.map.valueRef(ofType: StateRegistry.self)!.body - let queue = self.storageRef.map.valueRef(ofType: StateTransitionQueue.self)!.body + let registry = valueRef(ofType: StateRegistry.self)!.body + let queue = valueRef(ofType: StateTransitionQueue.self)!.body let previeous = registry.currentState let new = registry.inactiveStates.removeLast() @@ -266,12 +280,6 @@ public class StateStorage { } -public extension WorldStorageRef { - var stateStorage: StateStorage { - StateStorage(storageRef: self) - } -} - public extension World { /// Adds a set of states to the world and registers their associated schedules. /// diff --git a/Sources/ECS/States/StateControll.swift b/Sources/ECS/States/StateControll.swift index 2b0189c..e767e7e 100644 --- a/Sources/ECS/States/StateControll.swift +++ b/Sources/ECS/States/StateControll.swift @@ -12,22 +12,22 @@ */ final public class State: SystemParameter { - let stateStrageRef: StateStorage + let worldStorage: WorldStorageRef let currentState: T - init?(stateStrageRef: StateStorage, currentStaete: T?) { - guard let currentStaete = currentStaete else { return nil } - self.stateStrageRef = stateStrageRef - self.currentState = currentStaete + init?(worldStorage: WorldStorageRef, currentState: T?) { + guard let currentState = currentState else { return nil } + self.worldStorage = worldStorage + self.currentState = currentState } public static func register(to worldStorage: WorldStorageRef) { } - + public static func getParameter(from worldStorage: WorldStorageRef) -> State? { - State(stateStrageRef: worldStorage.stateStorage, - currentStaete: worldStorage.stateStorage.currentState(ofType: T.self)) + State(worldStorage: worldStorage, + currentState: worldStorage.stateStorage.currentState(ofType: T.self)) } /** @@ -35,7 +35,7 @@ final public class State: SystemParameter { - Parameter state: 遷移先の `State` を指定します. */ public func enter(_ state: T) { - self.stateStrageRef.enter(state) + worldStorage.stateStorage.enter(state) } /** @@ -49,7 +49,7 @@ final public class State: SystemParameter { - 以前の状態および `state` の ``Schedule/onStackUpdate(_:)`` と関連づけられたシステムが常時実行されます. */ public func push(_ state: T) { - self.stateStrageRef.push(state) + worldStorage.stateStorage.push(state) } /** @@ -62,7 +62,7 @@ final public class State: SystemParameter { - 直前の状態の ``Schedule/onStackUpdate(_:)`` のシステムが実行しなくなります. 戻り先の状態の ``Schedule/onStackUpdate(_:)`` のシステムは引き続き常時実行されます. */ public func pop() { - self.stateStrageRef.pop(T.self) + worldStorage.stateStorage.pop(T.self) } } diff --git a/Sources/ECS/System/SystemBuffer.swift b/Sources/ECS/System/SystemBuffer.swift index eee1d8e..ff8a0c6 100644 --- a/Sources/ECS/System/SystemBuffer.swift +++ b/Sources/ECS/System/SystemBuffer.swift @@ -5,50 +5,57 @@ // Created by rrbox on 2023/08/12. // +enum SystemStorage: WorldStorageType {} + +protocol SystemStorageElement: WorldStorageElement {} + public class SystemExecute { init() {} public func execute(_ worldStorage: WorldStorageRef) {} } -final public class SystemStorage { - final class SystemRegistry: WorldStorageElement { - var systems = [Schedule: [SystemExecute]]() +final class SystemRegistry: SystemStorageElement { + var systems = [Schedule: [SystemExecute]]() +} + +extension AnyMap where Mode == SystemStorage { + mutating func push(_ data: T) { + self.body[ObjectIdentifier(T.self)] = Box(body: data) } - let buffer: WorldStorageRef + mutating func pop(_ type: T.Type) { + self.body.removeValue(forKey: ObjectIdentifier(T.self)) + } - init(buffer: WorldStorageRef) { - self.buffer = buffer + func valueRef(ofType type: T.Type) -> Box? { + guard let result = self.body[ObjectIdentifier(T.self)] else { return nil } + return (result as! Box) } +} + + +extension AnyMap { public func systems(_ schedule: Schedule) -> [SystemExecute] { - self.buffer.map.valueRef(ofType: SystemRegistry.self)!.body.systems[schedule]! + valueRef(ofType: SystemRegistry.self)!.body.systems[schedule]! } - func registerSystemRegistry() { - self.buffer.map.push(SystemRegistry.init()) + mutating func registerSystemRegistry() { + push(SystemRegistry.init()) } func insertSchedule(_ schedule: Schedule) { - self.buffer.map.valueRef(ofType: SystemRegistry.self)!.body.systems[schedule] = [] + valueRef(ofType: SystemRegistry.self)!.body.systems[schedule] = [] } func addSystem(_ schedule: Schedule, _ system: SystemExecute) { - self.buffer - .map - .valueRef(ofType: SystemRegistry.self)! + valueRef(ofType: SystemRegistry.self)! .body .systems[schedule]! .append(system) } } -public extension WorldStorageRef { - var systemStorage: SystemStorage { - SystemStorage(buffer: self) - } -} - public extension World { @discardableResult func insertSchedule(_ schedule: Schedule) -> World { self.worldStorage.systemStorage.insertSchedule(schedule) diff --git a/Sources/ECS/World/WorldStorageRef.swift b/Sources/ECS/World/WorldStorageRef.swift index c9f1f59..91bb6d7 100644 --- a/Sources/ECS/World/WorldStorageRef.swift +++ b/Sources/ECS/World/WorldStorageRef.swift @@ -7,5 +7,10 @@ final public class WorldStorageRef { let commands = Commands() - var map = AnyMap() + var eventStorage = AnyMap() + public var chunkStorage = AnyMap() + var resourceStorage = AnyMap() + var stateStorage = AnyMap() + var systemStorage = AnyMap() + public var additionalStorage = AnyMap() } diff --git a/Sources/ECS/WorldMethods/FirstFrame.swift b/Sources/ECS/WorldMethods/FirstFrame.swift index 191426f..55af0f5 100644 --- a/Sources/ECS/WorldMethods/FirstFrame.swift +++ b/Sources/ECS/WorldMethods/FirstFrame.swift @@ -5,19 +5,19 @@ // Created by rrbox on 2023/11/20. // -private class PreUpdateFirstFrameCommand: Command { +final private class PreUpdateFirstFrameCommand: Command { override func runCommand(in world: World) { world.preUpdateSchedule = .preUpdate } } -private class UpdateSystemFirstFrameCommand: Command { +final private class UpdateSystemFirstFrameCommand: Command { override func runCommand(in world: World) { world.updateSchedule = .update } } -private class PostUpdateFirstFrameCommand: Command { +final private class PostUpdateFirstFrameCommand: Command { override func runCommand(in world: World) { world.postUpdateSchedule = .postUpdate } diff --git a/Sources/ECS/WorldMethods/World+Init.swift b/Sources/ECS/WorldMethods/World+Init.swift index 188d7f0..229a141 100644 --- a/Sources/ECS/WorldMethods/World+Init.swift +++ b/Sources/ECS/WorldMethods/World+Init.swift @@ -10,46 +10,43 @@ public extension World { self.init(worldStorage: WorldStorageRef()) // chunk buffer に chunk entity interface を追加します. - self.worldStorage.chunkStorage.setUpChunkBuffer() + worldStorage.chunkStorage.setUpChunkBuffer() // resource buffer に 時間関係の resource を追加します. - self.worldStorage.resourceBuffer.addResource(CurrentTime(value: 0)) - self.worldStorage.resourceBuffer.addResource(DeltaTime(value: 0)) + worldStorage.resourceStorage.addResource(CurrentTime(value: 0)) + worldStorage.resourceStorage.addResource(DeltaTime(value: 0)) // resrouce buffer に world の情報関係の resource を追加します. - self.worldStorage.resourceBuffer.addResource(EntityCount(count: 0)) - - let systemStorage = self.worldStorage.systemStorage - let eventStorage = self.worldStorage.eventStorage + worldStorage.resourceStorage.addResource(EntityCount(count: 0)) // world storage に system を保持する領域を確保します. - systemStorage.registerSystemRegistry() + worldStorage.systemStorage.registerSystemRegistry() // world buffer に setup system を保持する領域を確保します. - systemStorage.insertSchedule(.preStartUp) - systemStorage.insertSchedule(.startUp) - systemStorage.insertSchedule(.postStartUp) + worldStorage.systemStorage.insertSchedule(.preStartUp) + worldStorage.systemStorage.insertSchedule(.startUp) + worldStorage.systemStorage.insertSchedule(.postStartUp) // world buffer に update system を保持する領域を確保します. - systemStorage.insertSchedule(.preUpdate) - systemStorage.insertSchedule(.update) - systemStorage.insertSchedule(.postUpdate) + worldStorage.systemStorage.insertSchedule(.preUpdate) + worldStorage.systemStorage.insertSchedule(.update) + worldStorage.systemStorage.insertSchedule(.postUpdate) // state storage に schedule 管理をするための準備をします. - self.worldStorage.stateStorage.setUp() + worldStorage.stateStorage.setUp() // world buffer に event queue を作成します. - eventStorage.setUpEventQueue() - eventStorage.setUpCommandsEventQueue(eventOfType: DidSpawnEvent.self) - eventStorage.setUpCommandsEventQueue(eventOfType: WillDespawnEvent.self) + worldStorage.eventStorage.setUpEventQueue() + worldStorage.eventStorage.setUpCommandsEventQueue(eventOfType: DidSpawnEvent.self) + worldStorage.eventStorage.setUpCommandsEventQueue(eventOfType: WillDespawnEvent.self) // world buffer に spawn/despawn event の streamer を登録します. - self.addCommandsEventStreamer(eventType: DidSpawnEvent.self) - self.addCommandsEventStreamer(eventType: WillDespawnEvent.self) + addCommandsEventStreamer(eventType: DidSpawnEvent.self) + addCommandsEventStreamer(eventType: WillDespawnEvent.self) // world に一番最初のフレームで実行されるシステムを追加します. - systemStorage.addSystem(.preStartUp, System(preUpdateSystemFirstFrameSystem(commands:))) - systemStorage.addSystem(.startUp, System(updateSystemFirstFrameSystem(commands:))) - systemStorage.addSystem(.postStartUp, System(postUpdateSystemFirstFrameSystem(commands:))) + worldStorage.systemStorage.addSystem(.preStartUp, System(preUpdateSystemFirstFrameSystem(commands:))) + worldStorage.systemStorage.addSystem(.startUp, System(updateSystemFirstFrameSystem(commands:))) + worldStorage.systemStorage.addSystem(.postStartUp, System(postUpdateSystemFirstFrameSystem(commands:))) } } diff --git a/Sources/ECS/WorldMethods/World+Update.swift b/Sources/ECS/WorldMethods/World+Update.swift index f3519e3..17e82cd 100644 --- a/Sources/ECS/WorldMethods/World+Update.swift +++ b/Sources/ECS/WorldMethods/World+Update.swift @@ -22,9 +22,9 @@ public extension World { - note: 最初のフレーム (current time = 0) は準備用フレームとして実行されるため, システムが実行されません. */ func update(currentTime: TimeInterval) { - let currentTimeResource = self.worldStorage.resourceBuffer.resource(ofType: CurrentTime.self)! + let currentTimeResource = worldStorage.resourceStorage.resource(ofType: CurrentTime.self)! - self.worldStorage.resourceBuffer.resource(ofType: DeltaTime.self)?.resource = DeltaTime(value: currentTime - currentTimeResource.resource.value) + worldStorage.resourceStorage.resource(ofType: DeltaTime.self)?.resource = DeltaTime(value: currentTime - currentTimeResource.resource.value) currentTimeResource.resource = CurrentTime(value: currentTime) @@ -49,8 +49,8 @@ extension World { let systemStorage = worldStorage.systemStorage let stateStorage = worldStorage.stateStorage let stateSchedulesManager = worldStorage - .map - .valueRef(ofType: StateStorage.StateAssociatedSchedules.self)! + .stateStorage + .valueRef(ofType: StateAssociatedSchedules.self)! .body let willExitQueue = stateStorage.willExitQueue() diff --git a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift index b87fb5b..7000338 100644 --- a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift @@ -26,7 +26,7 @@ struct _RemoveFromParentTransaction: Component { } -class AddChild: EntityCommand { +final class AddChild: EntityCommand { let child: Entity init(parent: Entity, child: Entity) { self.child = child @@ -40,7 +40,7 @@ class AddChild: EntityCommand { } -class RemoveAllChildren: EntityCommand { +final class RemoveAllChildren: EntityCommand { override func runCommand(forRecord record: EntityRecordRef, inWorld world: World) { let node = record.component(ofType: Graphic.self)!.nodeRef node.removeAllChildren() @@ -55,7 +55,7 @@ class RemoveAllChildren: EntityCommand { } } -class RemoveFromParent: EntityCommand { +final class RemoveFromParent: EntityCommand { override func runCommand(forRecord record: EntityRecordRef, inWorld world: World) { record.addComponent(_RemoveFromParentTransaction()) } diff --git a/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift index 35f8261..d8cee2e 100644 --- a/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift @@ -12,7 +12,7 @@ struct _AddChildNodeTransaction: Component { var parentEntity: Entity? } -class SetGraphic: EntityCommand { +final class SetGraphic: EntityCommand { let node: SKNode init(node: SKNode, entity: Entity) { From fbc3e4b980f26caf7aee3b59cb80a42315853a46 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Fri, 4 Jul 2025 04:31:12 +0900 Subject: [PATCH 42/53] Clean up ChunkStorage methods --- Sources/ECS/Chunk/ChunkStorage+.swift | 21 +++-- Sources/ECS/Commons/WorldStorage.swift | 2 +- Tests/ecs-swiftTests/UsageTest.swift | 122 +++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 Tests/ecs-swiftTests/UsageTest.swift diff --git a/Sources/ECS/Chunk/ChunkStorage+.swift b/Sources/ECS/Chunk/ChunkStorage+.swift index 97bc867..31c4e06 100644 --- a/Sources/ECS/Chunk/ChunkStorage+.swift +++ b/Sources/ECS/Chunk/ChunkStorage+.swift @@ -6,10 +6,17 @@ // extension AnyMap { - mutating func setUpChunkBuffer() { - push(ChunkEntityInterface()) + + // MARK: - public + + /// World 内の特定の entity を更新します + /// - Parameter entityRecord: entity の components 構成クラス + public func pushUpdated(entityRecord: EntityRecordRef) { + valueRef(ofType: ChunkEntityInterface.self)!.body.pushUpdated(entityRecord: entityRecord) } + // MARK: - internal + mutating func addChunk(_ chunk: ChunkType) { push(chunk) valueRef(ofType: ChunkEntityInterface.self)!.body.add(chunk: chunk) @@ -19,12 +26,14 @@ extension AnyMap { valueRef(ofType: ChunkEntityInterface.self)!.body.pushSpawned(entityRecord: entityRecord) } - func applySpawnedEntityQueue() { - valueRef(ofType: ChunkEntityInterface.self)!.body.applySpawnedEntityQueue() + // MARK: - life cycle + + mutating func setUpChunkBuffer() { + push(ChunkEntityInterface()) } - public func pushUpdated(entityRecord: EntityRecordRef) { - valueRef(ofType: ChunkEntityInterface.self)!.body.pushUpdated(entityRecord: entityRecord) + func applySpawnedEntityQueue() { + valueRef(ofType: ChunkEntityInterface.self)!.body.applySpawnedEntityQueue() } func applyUpdatedEntityQueue() { diff --git a/Sources/ECS/Commons/WorldStorage.swift b/Sources/ECS/Commons/WorldStorage.swift index 373c49f..36950d0 100644 --- a/Sources/ECS/Commons/WorldStorage.swift +++ b/Sources/ECS/Commons/WorldStorage.swift @@ -10,7 +10,7 @@ protocol WorldStorageType {} public protocol WorldStorageElement {} final public class Box: Item { - private(set) var body: T + private(set) public var body: T init(body: T) { self.body = body diff --git a/Tests/ecs-swiftTests/UsageTest.swift b/Tests/ecs-swiftTests/UsageTest.swift new file mode 100644 index 0000000..717a9d6 --- /dev/null +++ b/Tests/ecs-swiftTests/UsageTest.swift @@ -0,0 +1,122 @@ +// +// UsageTest.swift +// ECS_Swift +// +// Created by rrbox on 2025/07/04. +// + +import Testing +import ECS + +struct UsageTest { + + // MARK: - Custom Resource + + struct TestResource: ResourceProtocol { + let state: String + } + + // MARK: - Custom State + + enum TestState: StateProtocol { + case title + case inGame + case end + } + + // MARK: - Custom Parameter + + final class SampleParameter: SystemParameter, AdditionalStorageElement { + + static func register(to worldStorage: ECS.WorldStorageRef) { + guard worldStorage.additionalStorage.valueRef(ofType: Self.self) == nil else { + return + } + worldStorage.additionalStorage.push(SampleParameter()) + } + + static func getParameter(from worldStorage: ECS.WorldStorageRef) -> Self? { + worldStorage.additionalStorage.valueRef(ofType: Self.self)!.body + } + + } + + // MARK: - Custom Systems + + func startUp(commands: Commands) { + commands.spawn() + .addComponent(TestComponent(content: "test0")) + commands.spawn() + .addComponent(TestComponent(content: "test1")) + commands.spawn() + .addComponent(TestComponent(content: "test2")) + .addComponent(TestComponent2(content: "test2")) + } + + func update(query: Query) { + query.update { component in + print(component.content) + } + } + + func update( + commands: Commands, + query: Filtered, With>, + currentTime: Resource + ) { + if currentTime.resource.value == 5 { + query.update { entity, _ in + commands.entity(entity) + .addComponent(TestComponent3(content: "search test")) + .removeComponent(ofType: TestComponent2.self) + } + } + } + + func inGameUpdate(query: Query, currentTime: Resource, stateMachine: State) { + query.update { component in + print(component.content) + } + + if currentTime.resource.value == 6 { + stateMachine.enter(.end) + } + } + + func update(resource: Resource) { + resource.resource = .init(state: "update") + } + + func access(sampleParameter: SampleParameter, currentTime: Resource) { + if currentTime.resource.value == 7 { + print(sampleParameter) + } + } + + // MARK: - Use + + @Test func usage() async throws { + let world = World() + .addResource(TestResource(state: "start")) + .addState(initialState: TestState.title, states: [.title, .inGame, .end]) + .addSystem(.update, update(query:)) + .addSystem(.update, update(commands:query:currentTime:)) + .addSystem(.onUpdate(TestState.inGame), inGameUpdate(query:currentTime:stateMachine:)) + .addSystem(.update, access(sampleParameter:currentTime:)) + + world.setUpWorld() + + world.update(currentTime: -1) + world.update(currentTime: 0) + world.update(currentTime: 1) + world.update(currentTime: 2) + world.update(currentTime: 3) + world.update(currentTime: 4) + world.update(currentTime: 5) + world.update(currentTime: 6) + world.update(currentTime: 7) + world.update(currentTime: 8) + world.update(currentTime: 9) + } + +} From 40ec6256759bd9f313777a9baefa668641bfe06b Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:42:35 +0900 Subject: [PATCH 43/53] Update methods and refactor for migration to ChunkStorageRef --- Sources/ECS/Chunk/ChunkStorage+.swift | 40 ++++++++++++++----- Sources/ECS/Chunk/ChunkStorage.swift | 10 ++--- .../EntityCommands/EntityCommands.swift | 2 +- Sources/ECS/FilterdQuery/FIlteredQuery.swift | 6 +-- Sources/ECS/Query/Query.swift | 8 ++-- Sources/ECS/World/WorldStorageRef.swift | 5 ++- Sources/ECS/WorldMethods/World+Init.swift | 2 +- Sources/ECS/WorldMethods/World+Spawn.swift | 4 +- Sources/ECS/WorldMethods/World+Update.swift | 4 +- Sources/ECS_Macros/QueryMacro.swift | 6 +-- .../Commands/EntityCommands+Graphic.swift | 2 +- 11 files changed, 55 insertions(+), 34 deletions(-) diff --git a/Sources/ECS/Chunk/ChunkStorage+.swift b/Sources/ECS/Chunk/ChunkStorage+.swift index 31c4e06..c90fd2e 100644 --- a/Sources/ECS/Chunk/ChunkStorage+.swift +++ b/Sources/ECS/Chunk/ChunkStorage+.swift @@ -7,41 +7,61 @@ extension AnyMap { +} + +extension ChunkStorageRef { + // MARK: - public /// World 内の特定の entity を更新します /// - Parameter entityRecord: entity の components 構成クラス public func pushUpdated(entityRecord: EntityRecordRef) { - valueRef(ofType: ChunkEntityInterface.self)!.body.pushUpdated(entityRecord: entityRecord) + storage.valueRef(ofType: ChunkEntityInterface.self)! + .body + .pushUpdated(entityRecord: entityRecord) } // MARK: - internal - mutating func addChunk(_ chunk: ChunkType) { - push(chunk) - valueRef(ofType: ChunkEntityInterface.self)!.body.add(chunk: chunk) + func chunk(ofType type: ChunkType.Type) -> ChunkType? { + storage.valueRef(ofType: ChunkType.self)?.body + } + + func addChunk(_ chunk: ChunkType) { + storage.push(chunk) + storage.valueRef(ofType: ChunkEntityInterface.self)! + .body + .add(chunk: chunk) } func pushSpawned(entityRecord: EntityRecordRef) { - valueRef(ofType: ChunkEntityInterface.self)!.body.pushSpawned(entityRecord: entityRecord) + storage.valueRef(ofType: ChunkEntityInterface.self)! + .body + .pushSpawned(entityRecord: entityRecord) } // MARK: - life cycle - mutating func setUpChunkBuffer() { - push(ChunkEntityInterface()) + func setUpChunkBuffer() { + storage.push(ChunkEntityInterface()) } func applySpawnedEntityQueue() { - valueRef(ofType: ChunkEntityInterface.self)!.body.applySpawnedEntityQueue() + storage.valueRef(ofType: ChunkEntityInterface.self)! + .body + .applySpawnedEntityQueue() } func applyUpdatedEntityQueue() { - valueRef(ofType: ChunkEntityInterface.self)!.body.applyUpdatedEntityQueue() + storage.valueRef(ofType: ChunkEntityInterface.self)! + .body + .applyUpdatedEntityQueue() } func despawn(entity: Entity) { - valueRef(ofType: ChunkEntityInterface.self)!.body.despawn(entity: entity) + storage.valueRef(ofType: ChunkEntityInterface.self)! + .body + .despawn(entity: entity) } } diff --git a/Sources/ECS/Chunk/ChunkStorage.swift b/Sources/ECS/Chunk/ChunkStorage.swift index 5b90cf3..2a863ff 100644 --- a/Sources/ECS/Chunk/ChunkStorage.swift +++ b/Sources/ECS/Chunk/ChunkStorage.swift @@ -13,6 +13,10 @@ public enum ChunkStorage: WorldStorageType { } +final public class ChunkStorageRef { + var storage = AnyMap() +} + extension AnyMap where Mode == ChunkStorage { mutating func push(_ data: T) { self.body[ObjectIdentifier(T.self)] = Box(body: data) @@ -27,9 +31,3 @@ extension AnyMap where Mode == ChunkStorage { return (result as! Box) } } - -extension AnyMap { - func chunk(ofType type: ChunkType.Type) -> ChunkType? { - valueRef(ofType: ChunkType.self)?.body - } -} diff --git a/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift index aecf214..6724f46 100644 --- a/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift +++ b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift @@ -32,7 +32,7 @@ final class SearchedEntityCommandQueue: EntityCommandQueue { override func runCommand(in world: World) { guard let record = world.entityRecord(forEntity: self.entity) else { return } - world.worldStorage.chunkStorage.pushUpdated(entityRecord: record) + world.worldStorage.chunkStorageRef.pushUpdated(entityRecord: record) self.queue.forEach { command in command.runCommand(forRecord: record, inWorld: world) } diff --git a/Sources/ECS/FilterdQuery/FIlteredQuery.swift b/Sources/ECS/FilterdQuery/FIlteredQuery.swift index ee374a3..4645332 100644 --- a/Sources/ECS/FilterdQuery/FIlteredQuery.swift +++ b/Sources/ECS/FilterdQuery/FIlteredQuery.swift @@ -29,15 +29,15 @@ final public class Filtered: Chunk, SystemParameter } public static func getParameter(from worldStorage: WorldStorageRef) -> Filtered? { - worldStorage.chunkStorage.chunk(ofType: Filtered.self) + worldStorage.chunkStorageRef.chunk(ofType: Filtered.self) } public static func register(to worldStorage: WorldStorageRef) { - guard worldStorage.chunkStorage.chunk(ofType: Self.self) == nil else { + guard worldStorage.chunkStorageRef.chunk(ofType: Self.self) == nil else { return } - worldStorage.chunkStorage.addChunk(Filtered()) + worldStorage.chunkStorageRef.addChunk(Filtered()) } override func applyCurrentState(_ entityRecord: EntityRecordRef) { diff --git a/Sources/ECS/Query/Query.swift b/Sources/ECS/Query/Query.swift index 0a0e243..0c0328a 100644 --- a/Sources/ECS/Query/Query.swift +++ b/Sources/ECS/Query/Query.swift @@ -64,18 +64,18 @@ final public class Query: Chunk, SystemParameter { } public static func register(to worldStorage: WorldStorageRef) { - guard worldStorage.chunkStorage.chunk(ofType: Self.self) == nil else { + guard worldStorage.chunkStorageRef.chunk(ofType: Self.self) == nil else { return } let queryRegistory = Self() - worldStorage.chunkStorage.addChunk(queryRegistory) - + worldStorage.chunkStorageRef.addChunk(queryRegistory) + } public static func getParameter(from worldStorage: WorldStorageRef) -> Self? { - worldStorage.chunkStorage.chunk(ofType: Self.self) + worldStorage.chunkStorageRef.chunk(ofType: Self.self) } } diff --git a/Sources/ECS/World/WorldStorageRef.swift b/Sources/ECS/World/WorldStorageRef.swift index 91bb6d7..542b9b7 100644 --- a/Sources/ECS/World/WorldStorageRef.swift +++ b/Sources/ECS/World/WorldStorageRef.swift @@ -8,9 +8,12 @@ final public class WorldStorageRef { let commands = Commands() var eventStorage = AnyMap() - public var chunkStorage = AnyMap() var resourceStorage = AnyMap() var stateStorage = AnyMap() var systemStorage = AnyMap() + + // MARK: - public + + public let chunkStorageRef = ChunkStorageRef() public var additionalStorage = AnyMap() } diff --git a/Sources/ECS/WorldMethods/World+Init.swift b/Sources/ECS/WorldMethods/World+Init.swift index 229a141..448b305 100644 --- a/Sources/ECS/WorldMethods/World+Init.swift +++ b/Sources/ECS/WorldMethods/World+Init.swift @@ -10,7 +10,7 @@ public extension World { self.init(worldStorage: WorldStorageRef()) // chunk buffer に chunk entity interface を追加します. - worldStorage.chunkStorage.setUpChunkBuffer() + worldStorage.chunkStorageRef.setUpChunkBuffer() // resource buffer に 時間関係の resource を追加します. worldStorage.resourceStorage.addResource(CurrentTime(value: 0)) diff --git a/Sources/ECS/WorldMethods/World+Spawn.swift b/Sources/ECS/WorldMethods/World+Spawn.swift index 6bd8500..740833c 100644 --- a/Sources/ECS/WorldMethods/World+Spawn.swift +++ b/Sources/ECS/WorldMethods/World+Spawn.swift @@ -30,7 +30,7 @@ extension World { self.insert(entityRecord: entityRecord) self.worldStorage - .chunkStorage + .chunkStorageRef .pushSpawned(entityRecord: entityRecord) self.worldStorage .eventStorage @@ -44,7 +44,7 @@ extension World { func despawn(entity: Entity) { self.remove(entity: entity) self.worldStorage - .chunkStorage + .chunkStorageRef .despawn(entity: entity) self.worldStorage .eventStorage diff --git a/Sources/ECS/WorldMethods/World+Update.swift b/Sources/ECS/WorldMethods/World+Update.swift index 17e82cd..36c82f6 100644 --- a/Sources/ECS/WorldMethods/World+Update.swift +++ b/Sources/ECS/WorldMethods/World+Update.swift @@ -161,7 +161,7 @@ extension World { self.applyEnityTransactions(commands: commands) // apply commands の際に push された entity を chunk に割り振ります(spawn). - self.worldStorage.chunkStorage.applySpawnedEntityQueue() + self.worldStorage.chunkStorageRef.applySpawnedEntityQueue() // Did Spawn event を event system に発信します. self.applyCommandsEventQueue(eventOfType: DidSpawnEvent.self) @@ -170,6 +170,6 @@ extension World { // world 内の entity のコンポーネントの追加/削除. // 同じフレーム内で entity の変更を world 全体に適用するために一番最後に再度実行. - self.worldStorage.chunkStorage.applyUpdatedEntityQueue() + self.worldStorage.chunkStorageRef.applyUpdatedEntityQueue() } } diff --git a/Sources/ECS_Macros/QueryMacro.swift b/Sources/ECS_Macros/QueryMacro.swift index 20e8db5..289fe99 100644 --- a/Sources/ECS_Macros/QueryMacro.swift +++ b/Sources/ECS_Macros/QueryMacro.swift @@ -104,13 +104,13 @@ struct QueryMacro: DeclarationMacro { } public static func register(to worldStorage: WorldStorageRef) { - guard worldStorage.chunkStorage.chunk(ofType: Self.self) == nil else { return } + guard worldStorage.chunkStorageRef.chunk(ofType: Self.self) == nil else { return } let queryRegistory = Self() - worldStorage.chunkStorage.addChunk(queryRegistory) + worldStorage.chunkStorageRef.addChunk(queryRegistory) } public static func getParameter(from worldStorage: WorldStorageRef) -> Self? { - worldStorage.chunkStorage.chunk(ofType: Self.self) + worldStorage.chunkStorageRef.chunk(ofType: Self.self) } } """ diff --git a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift index 7000338..9f59bdc 100644 --- a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift @@ -49,7 +49,7 @@ final class RemoveAllChildren: EntityCommand { for child in record.componentRef(ofType: Parent.self)!.value.children { let childRecord = world.entityRecord(forEntity: child)! childRecord.removeComponent(ofType: Child.self) - world.worldStorage.chunkStorage.pushUpdated(entityRecord: childRecord) + world.worldStorage.chunkStorageRef.pushUpdated(entityRecord: childRecord) } } From 3e50592594a4d7f615038f8cb0af6535f9f03924 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:44:07 +0900 Subject: [PATCH 44/53] Remove public access modifier from ChunkStorage enum --- Sources/ECS/Chunk/ChunkStorage.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ECS/Chunk/ChunkStorage.swift b/Sources/ECS/Chunk/ChunkStorage.swift index 2a863ff..ece477c 100644 --- a/Sources/ECS/Chunk/ChunkStorage.swift +++ b/Sources/ECS/Chunk/ChunkStorage.swift @@ -9,7 +9,7 @@ protocol ChunkStorageElement: WorldStorageElement { } -public enum ChunkStorage: WorldStorageType { +enum ChunkStorage: WorldStorageType { } From b7966a4a6dcac5cf36d1746408059743edc0f7e0 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Fri, 4 Jul 2025 23:49:29 +0900 Subject: [PATCH 45/53] Refactor chunk storage references in tests to use chunkStorageRef --- Tests/ecs-swiftTests/ChunkTests.swift | 6 +-- Tests/ecs-swiftTests/FilteredQueryTests.swift | 6 +-- Tests/ecs-swiftTests/QueryTests.swift | 44 +++++++++---------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Tests/ecs-swiftTests/ChunkTests.swift b/Tests/ecs-swiftTests/ChunkTests.swift index 588a151..76c7eb7 100644 --- a/Tests/ecs-swiftTests/ChunkTests.swift +++ b/Tests/ecs-swiftTests/ChunkTests.swift @@ -38,15 +38,15 @@ final class ChunkTests: XCTestCase { // Spawn された entity を単に蓄積するだけの test 用の chunk です. let testChunk = TestChunk() let testChunk_2 = TestChunk_2() - world.worldStorage.chunkStorage.addChunk(testChunk) - world.worldStorage.chunkStorage.addChunk(testChunk_2) + world.worldStorage.chunkStorageRef.addChunk(testChunk) + world.worldStorage.chunkStorageRef.addChunk(testChunk_2) // chunk interface を介して chunk に entity を push します(回数: 5回). for entity in mockEntities { world.push(entityRecord: EntityRecordRef(entity: entity)) } - world.worldStorage.chunkStorage.applySpawnedEntityQueue() + world.worldStorage.chunkStorageRef.applySpawnedEntityQueue() XCTAssertEqual(testChunk.entities.count, 5) XCTAssertEqual(testChunk_2.entities.count, 5) diff --git a/Tests/ecs-swiftTests/FilteredQueryTests.swift b/Tests/ecs-swiftTests/FilteredQueryTests.swift index b79a833..fd436e9 100644 --- a/Tests/ecs-swiftTests/FilteredQueryTests.swift +++ b/Tests/ecs-swiftTests/FilteredQueryTests.swift @@ -16,9 +16,9 @@ final class FilteredQueryTests: XCTestCase { let world = World() - world.worldStorage.chunkStorage.addChunk(testQueryAnd) - world.worldStorage.chunkStorage.addChunk(testQueryOr) - world.worldStorage.chunkStorage.addChunk(testQueryWithout) + world.worldStorage.chunkStorageRef.addChunk(testQueryAnd) + world.worldStorage.chunkStorageRef.addChunk(testQueryOr) + world.worldStorage.chunkStorageRef.addChunk(testQueryWithout) let commands = world.worldStorage.commands diff --git a/Tests/ecs-swiftTests/QueryTests.swift b/Tests/ecs-swiftTests/QueryTests.swift index 89544a8..3710af5 100644 --- a/Tests/ecs-swiftTests/QueryTests.swift +++ b/Tests/ecs-swiftTests/QueryTests.swift @@ -24,17 +24,17 @@ final class QueryTests: XCTestCase { let world = World() - world.worldStorage.chunkStorage.addChunk(testQuery) - world.worldStorage.chunkStorage.addChunk(testQuery2) - world.worldStorage.chunkStorage.addChunk(testQuery3) - world.worldStorage.chunkStorage.addChunk(testQuery4) - world.worldStorage.chunkStorage.addChunk(testQuery5) - - world.worldStorage.chunkStorage.addChunk(testEntityQuery) - world.worldStorage.chunkStorage.addChunk(testEntityQuery2) - world.worldStorage.chunkStorage.addChunk(testEntityQuery3) - world.worldStorage.chunkStorage.addChunk(testEntityQuery4) - world.worldStorage.chunkStorage.addChunk(testEntityQuery5) + world.worldStorage.chunkStorageRef.addChunk(testQuery) + world.worldStorage.chunkStorageRef.addChunk(testQuery2) + world.worldStorage.chunkStorageRef.addChunk(testQuery3) + world.worldStorage.chunkStorageRef.addChunk(testQuery4) + world.worldStorage.chunkStorageRef.addChunk(testQuery5) + + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery) + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery2) + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery3) + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery4) + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery5) let commands = world.worldStorage.commands @@ -151,17 +151,17 @@ final class QueryTests: XCTestCase { let world = World() - world.worldStorage.chunkStorage.addChunk(testQuery) - world.worldStorage.chunkStorage.addChunk(testQuery2) - world.worldStorage.chunkStorage.addChunk(testQuery3) - world.worldStorage.chunkStorage.addChunk(testQuery4) - world.worldStorage.chunkStorage.addChunk(testQuery5) - - world.worldStorage.chunkStorage.addChunk(testEntityQuery) - world.worldStorage.chunkStorage.addChunk(testEntityQuery2) - world.worldStorage.chunkStorage.addChunk(testEntityQuery3) - world.worldStorage.chunkStorage.addChunk(testEntityQuery4) - world.worldStorage.chunkStorage.addChunk(testEntityQuery5) + world.worldStorage.chunkStorageRef.addChunk(testQuery) + world.worldStorage.chunkStorageRef.addChunk(testQuery2) + world.worldStorage.chunkStorageRef.addChunk(testQuery3) + world.worldStorage.chunkStorageRef.addChunk(testQuery4) + world.worldStorage.chunkStorageRef.addChunk(testQuery5) + + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery) + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery2) + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery3) + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery4) + world.worldStorage.chunkStorageRef.addChunk(testEntityQuery5) let commands = world.worldStorage.commands From 7edd2162eab036ee74d8147563876e0a77c052a6 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sat, 5 Jul 2025 02:35:50 +0900 Subject: [PATCH 46/53] Add setGraphic method for connecting existing SKNode to entity --- .../Commands/EntityCommands+Graphic.swift | 23 ++++++++++++-- .../Graphic2D/Commands/SetGraphic.swift | 1 - Sources/PlugIns/Graphic2D/Nodes.swift | 31 +++++++++++++++++++ .../GraphicPlugInTests.swift | 27 ++++++++++++++++ 4 files changed, 79 insertions(+), 3 deletions(-) diff --git a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift index 9f59bdc..f06400e 100644 --- a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift @@ -62,11 +62,30 @@ final class RemoveFromParent: EntityCommand { } public extension EntityCommands { - @discardableResult func setGraphic(_ nodeCreate: Nodes.NodeCreate) -> Self { + /// Node hierarchy に存在しない SKNode を entity に紐づけて SceneResource の SKScene に配置します. + @discardableResult func setGraphic( + _ nodeCreate: Nodes.NodeCreate + ) -> Self { let node = nodeCreate.node nodeCreate.register(id(), node) self.pushCommand(SetGraphic(node: node, entity: id())) - return self.addComponent(Graphic(node: node)).addComponent(Graphic(node: node)) + return self + .addComponent(Graphic(node: node)) + .addComponent(Graphic(node: node)) + .addComponent(_AddChildNodeTransaction(parentEntity: nil)) + } + + /// SKScene にすでに追加されている SKNode を entity に紐付けます. + @discardableResult func setGraphic( + _ nodeCreate: Nodes.NodeConnect + ) -> Self { + let node = nodeCreate.node + nodeCreate.register(id(), node) + self.pushCommand(SetGraphic(node: node, entity: id())) + // _AddChildNodeTransaction を追加しない + return self + .addComponent(Graphic(node: node)) + .addComponent(Graphic(node: node)) } @discardableResult func addChild(_ entity: Entity) -> Self { diff --git a/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift index d8cee2e..9c7d628 100644 --- a/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift @@ -29,7 +29,6 @@ final class SetGraphic: EntityCommand { self.setEntityInfoForNode(entity) record.addComponent(Parent(_children: [])) - record.addComponent(_AddChildNodeTransaction(parentEntity: nil)) } } diff --git a/Sources/PlugIns/Graphic2D/Nodes.swift b/Sources/PlugIns/Graphic2D/Nodes.swift index c6cef35..cdb12f2 100644 --- a/Sources/PlugIns/Graphic2D/Nodes.swift +++ b/Sources/PlugIns/Graphic2D/Nodes.swift @@ -15,8 +15,14 @@ public final class Nodes: ResourceProtocol { let register: (Entity, Node) -> Void } + public struct NodeConnect { + let node: Node + let register: (Entity, Node) -> Void + } + var store = [Entity: SKNode]() + /// node hierarchy に存在しない SKNode を entity に紐付けます. public func create(node: Node) -> NodeCreate { return .init( node: node, @@ -24,6 +30,31 @@ public final class Nodes: ResourceProtocol { ) } + /// SKScene にすでに配置された SKNode を entity に紐付けます. + public func connect( + nodeWithName name: String, + typeOf type: Node.Type, + fromScene scene: SKScene + ) -> NodeConnect? { + guard let node = scene.childNode(withName: name) as? Node else { + return nil + } + return .init( + node: node, + register: regiester(entity:node:) + ) + } + + /// SKScene にすでに配置された SKNode を entity に紐付けます. + public func connect( + node: Node + ) -> NodeConnect { + return .init( + node: node, + register: regiester(entity:node:) + ) + } + func regiester(entity: Entity, node: Node) { store[entity] = node } diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift index 6cb76d6..0eb8f5a 100644 --- a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -43,6 +43,33 @@ final class GraphicPlugInTests: XCTestCase { XCTAssertEqual(scene.children.count, 1) } + func testSetGraphicConnectNode() { + let scene = SKScene() + let node = SKNode() + node.name = "test_node" + scene.addChild(node) + var flags = [0] + + let world = World() + .addResource(SceneResource(scene)) + .addPlugIn(graphicPlugIn(world:)) + .addSystem(.startUp) { (commands: Commands, nodes: Resource) in + commands.spawn() + .setGraphic(nodes.resource.connect(node: node)) + } + .addSystem(.update) { (node: Query) in + node.update { _ in + XCTAssertStepOrder(currentStep: 0, steps: &flags) + } + } + + world.setUpWorld() + world.update(currentTime: -1) // first frame state + world.update(currentTime: 0) + XCTAssertEqual(scene.children.count, 1) + XCTAssertEqual(flags, [1]) + } + func testAddChildOnUpdate() { let scene = SKScene() let parentNode = SKNode() From acf5482ca6edbb7cac2969a62113177a3c15dd81 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sat, 5 Jul 2025 09:38:21 +0900 Subject: [PATCH 47/53] Add overloaded connect method for SceneResource to link existing SKNode to entity --- Sources/PlugIns/Graphic2D/Nodes.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Sources/PlugIns/Graphic2D/Nodes.swift b/Sources/PlugIns/Graphic2D/Nodes.swift index cdb12f2..8895d61 100644 --- a/Sources/PlugIns/Graphic2D/Nodes.swift +++ b/Sources/PlugIns/Graphic2D/Nodes.swift @@ -45,6 +45,21 @@ public final class Nodes: ResourceProtocol { ) } + /// SKScene にすでに配置された SKNode を entity に紐付けます. + public func connect( + nodeWithName name: String, + typeOf type: Node.Type, + fromScene scene: SceneResource + ) -> NodeConnect? { + guard let node = scene.scene.childNode(withName: name) as? Node else { + return nil + } + return .init( + node: node, + register: regiester(entity:node:) + ) + } + /// SKScene にすでに配置された SKNode を entity に紐付けます. public func connect( node: Node From 7d0e7eeeada596e838ce71452cd5dab746f5e115 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sat, 5 Jul 2025 09:54:02 +0900 Subject: [PATCH 48/53] Rename connect method argument from typeOf to as --- Sources/PlugIns/Graphic2D/Nodes.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/PlugIns/Graphic2D/Nodes.swift b/Sources/PlugIns/Graphic2D/Nodes.swift index 8895d61..701cddd 100644 --- a/Sources/PlugIns/Graphic2D/Nodes.swift +++ b/Sources/PlugIns/Graphic2D/Nodes.swift @@ -33,7 +33,7 @@ public final class Nodes: ResourceProtocol { /// SKScene にすでに配置された SKNode を entity に紐付けます. public func connect( nodeWithName name: String, - typeOf type: Node.Type, + as type: Node.Type, fromScene scene: SKScene ) -> NodeConnect? { guard let node = scene.childNode(withName: name) as? Node else { @@ -48,7 +48,7 @@ public final class Nodes: ResourceProtocol { /// SKScene にすでに配置された SKNode を entity に紐付けます. public func connect( nodeWithName name: String, - typeOf type: Node.Type, + as type: Node.Type, fromScene scene: SceneResource ) -> NodeConnect? { guard let node = scene.scene.childNode(withName: name) as? Node else { From 23bd4543af3ef7a18f5271524cb5040e3ca1bc2b Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 6 Jul 2025 22:45:20 +0900 Subject: [PATCH 49/53] Refactor event handling in EventTests to iterate over events consistently --- Tests/ecs-swiftTests/EventTests.swift | 102 +++++++++++++++++++------- 1 file changed, 77 insertions(+), 25 deletions(-) diff --git a/Tests/ecs-swiftTests/EventTests.swift b/Tests/ecs-swiftTests/EventTests.swift index 72b912e..77ccec6 100644 --- a/Tests/ecs-swiftTests/EventTests.swift +++ b/Tests/ecs-swiftTests/EventTests.swift @@ -13,15 +13,17 @@ struct TestEvent: EventProtocol { } func testEvent(event: EventReader, eventWriter: EventWriter, commands: Commands, currentTime: Resource) { - print("---test event read---") - print("frame:", currentTime.resource.value) - print("<- read event:", event.value.name) - let spawned = commands.spawn().addComponent(TestComponent(content: event.value.name)).id() - print("-> spawn:", spawned) - print("-> event send:", "\"link\"") - eventWriter.send(value: TestEvent(name: "[\(currentTime.resource.value)]: link")) - print("---") - print() + for event in event.events { + print("---test event read---") + print("frame:", currentTime.resource.value) + print("<- read event:", event.name) + let spawned = commands.spawn().addComponent(TestComponent(content: event.name)).id() + print("-> spawn:", spawned) + print("-> event send:", "\"link\"") + eventWriter.send(value: TestEvent(name: "[\(currentTime.resource.value)]: link")) + print("---") + print() + } } func setUp(eventWriter: EventWriter) { @@ -33,21 +35,25 @@ func setUp(eventWriter: EventWriter) { } func spawnedEntitySystem(eventReader: EventReader, commands: Commands, currentTime: Resource) { - print("---spawned entity event read---") - print("frame:", currentTime.resource.value) - print("<- spawned(receive):", eventReader.value.spawnedEntity) - print("-> despawn:", eventReader.value.spawnedEntity) - commands.despawn(entity: eventReader.value.spawnedEntity) - print("---") - print() + for event in eventReader.events { + print("---spawned entity event read---") + print("frame:", currentTime.resource.value) + print("<- spawned(receive):", event.spawnedEntity) + print("-> despawn:", event.spawnedEntity) + commands.despawn(entity: event.spawnedEntity) + print("---") + print() + } } func despanedEntitySystem(eventReader: EventReader, commands: Commands, currentTime: Resource) { - print("---despawned entity event read---") - print("frame:", currentTime.resource.value) - print("<- despawned(receive):", eventReader.value.despawnedEntity) - print("---") - print() + for event in eventReader.events { + print("---despawned entity event read---") + print("frame:", currentTime.resource.value) + print("<- despawned(receive):", event.despawnedEntity) + print("---") + print() + } } final class EventTests: XCTestCase { @@ -83,15 +89,19 @@ final class EventTests: XCTestCase { }) .buildEventResponder(TestEvent.self) { responder in responder.addSystem(.update) { (event: EventReader, commands: Commands) in - ECSTAssertStepOrder(currentStep: 1, steps: &flags) - commands.spawn().addComponent(TestComponent(content: event.value.name)) + for event in event.events { + ECSTAssertStepOrder(currentStep: 1, steps: &flags) + commands.spawn().addComponent(TestComponent(content: event.name)) + } } } .buildDidSpawnResponder { responder in responder .addSystem(.update) { (event: EventReader, commands: Commands) in - ECSTAssertStepOrder(currentStep: 2, steps: &flags) - commands.despawn(entity: event.value.spawnedEntity) + for event in event.events { + ECSTAssertStepOrder(currentStep: 2, steps: &flags) + commands.despawn(entity: event.spawnedEntity) + } } } .buildWillDespawnResponder { responder in @@ -107,4 +117,46 @@ final class EventTests: XCTestCase { XCTAssertEqual(flags, [1, 1, 1, 1]) } + + func testSendTwoEventsInOneUpdate() { + var count = 0 + + let world = World() + .addEventStreamer(eventType: TestEvent.self) + .addSystem(.startUp, { (eventWriter: EventWriter) in + eventWriter.send(value: .init(name: "event 1")) + eventWriter.send(value: .init(name: "event 2")) + }) + .buildEventResponder(TestEvent.self) { responder in + responder.addSystem(.update) { (event: EventReader) in + count += event.events.count + } + } + + world.setUpWorld() + world.update(currentTime: 0) + + XCTAssertEqual(count, 2) + } + + func testSystemExecutesOnceWithTwoEvents() { + var executionCount = 0 + + let world = World() + .addEventStreamer(eventType: TestEvent.self) + .addSystem(.startUp, { (eventWriter: EventWriter) in + eventWriter.send(value: .init(name: "event 1")) + eventWriter.send(value: .init(name: "event 2")) + }) + .buildEventResponder(TestEvent.self) { responder in + responder.addSystem(.update) { (_: EventReader) in + executionCount += 1 + } + } + + world.setUpWorld() + world.update(currentTime: 0) + + XCTAssertEqual(executionCount, 1) + } } From 61dad99e2f8123ad084c5bb0ba2fefc14d8f3327 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 6 Jul 2025 22:45:33 +0900 Subject: [PATCH 50/53] Remove unused event-related classes and protocols --- .../CommandsEvent/CommandsEventQueue.swift | 11 ----- Sources/ECS/Event/EventStreaming/Event.swift | 43 ------------------- .../ECS/Event/EventStreaming/EventQueue.swift | 11 ----- 3 files changed, 65 deletions(-) delete mode 100644 Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift delete mode 100644 Sources/ECS/Event/EventStreaming/Event.swift delete mode 100644 Sources/ECS/Event/EventStreaming/EventQueue.swift diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift b/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift deleted file mode 100644 index b582aee..0000000 --- a/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// CommandsEventQueue.swift -// -// -// Created by rrbox on 2023/08/29. -// - -final class CommandsEventQueue: EventStorageElement { - var eventQueue = [T]() - var sendingEvents = [T]() -} diff --git a/Sources/ECS/Event/EventStreaming/Event.swift b/Sources/ECS/Event/EventStreaming/Event.swift deleted file mode 100644 index b890dec..0000000 --- a/Sources/ECS/Event/EventStreaming/Event.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// Event.swift -// -// -// Created by rrbox on 2023/08/14. -// - -public protocol EventProtocol { - -} - -class AnyEvent { - func runEventReceiver(worldStorage: WorldStorageRef) { - - } -} - -final class Event: AnyEvent { - let value: T - - init(value: T) { - self.value = value - } - - override func runEventReceiver(worldStorage: WorldStorageRef) { - worldStorage.eventStorage.push(EventReader(value: self.value)) - - if let systems = worldStorage.eventStorage.eventResponder(eventOfType: T.self)!.systems[.update] { - for system in systems { - system.execute(worldStorage) - } - } - - 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) - } - } - - worldStorage.eventStorage.pop(EventReader.self) - } -} diff --git a/Sources/ECS/Event/EventStreaming/EventQueue.swift b/Sources/ECS/Event/EventStreaming/EventQueue.swift deleted file mode 100644 index 979c4b7..0000000 --- a/Sources/ECS/Event/EventStreaming/EventQueue.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// EventQueue.swift -// -// -// Created by rrbox on 2023/08/14. -// - -final class EventQueue: EventStorageElement { - var eventQueue = [AnyEvent]() - var sendingEvents = [AnyEvent]() -} From cb419894f390fe1f842e71255a40e88fdb729677 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 6 Jul 2025 22:45:52 +0900 Subject: [PATCH 51/53] Add event handling classes and protocols for ECS event system --- .../CommandsEvent/CommandsEventReceiver.swift | 29 +++++++++++++++++++ .../EventStreaming/AnyEventReceiver.swift | 10 +++++++ .../Event/EventStreaming/EventProtocol.swift | 10 +++++++ .../Event/EventStreaming/EventReceiver.swift | 29 +++++++++++++++++++ .../Event/EventStreaming/EventReceivers.swift | 10 +++++++ 5 files changed, 88 insertions(+) create mode 100644 Sources/ECS/Event/CommandsEvent/CommandsEventReceiver.swift create mode 100644 Sources/ECS/Event/EventStreaming/AnyEventReceiver.swift create mode 100644 Sources/ECS/Event/EventStreaming/EventProtocol.swift create mode 100644 Sources/ECS/Event/EventStreaming/EventReceiver.swift create mode 100644 Sources/ECS/Event/EventStreaming/EventReceivers.swift diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEventReceiver.swift b/Sources/ECS/Event/CommandsEvent/CommandsEventReceiver.swift new file mode 100644 index 0000000..fe8080b --- /dev/null +++ b/Sources/ECS/Event/CommandsEvent/CommandsEventReceiver.swift @@ -0,0 +1,29 @@ +// +// CommandsEventReceiver.swift +// ECS_Swift +// +// Created by rrbox on 2025/07/06. +// + +final class CommandsEventReceiver: AnyEventReceiver, EventStorageElement { + var eventBuffer = [T]() + + override func receive(worldStorage: WorldStorageRef) { + let events = eventBuffer + eventBuffer = [] + guard !events.isEmpty else { return } + worldStorage.eventStorage.push(EventReader(events: events)) + if let systems = worldStorage.eventStorage.commandsEventResponder(eventOfType: T.self)!.systems[.update] { + for system in systems { + system.execute(worldStorage) + } + } + for schedule in worldStorage.stateStorage.currentEventSchedulesWhichAssociatedStates() { + guard let systems = worldStorage.eventStorage.commandsEventResponder(eventOfType: T.self)!.systems[schedule] else { continue } + for system in systems { + system.execute(worldStorage) + } + } + worldStorage.eventStorage.pop(EventReader.self) + } +} diff --git a/Sources/ECS/Event/EventStreaming/AnyEventReceiver.swift b/Sources/ECS/Event/EventStreaming/AnyEventReceiver.swift new file mode 100644 index 0000000..d894cbe --- /dev/null +++ b/Sources/ECS/Event/EventStreaming/AnyEventReceiver.swift @@ -0,0 +1,10 @@ +// +// AnyEventReceiver.swift +// ECS_Swift +// +// Created by rrbox on 2025/07/06. +// + +class AnyEventReceiver { + func receive(worldStorage: WorldStorageRef) {} +} diff --git a/Sources/ECS/Event/EventStreaming/EventProtocol.swift b/Sources/ECS/Event/EventStreaming/EventProtocol.swift new file mode 100644 index 0000000..3c722d2 --- /dev/null +++ b/Sources/ECS/Event/EventStreaming/EventProtocol.swift @@ -0,0 +1,10 @@ +// +// EventProtocol.swift +// ECS_Swift +// +// Created by rrbox on 2025/07/06. +// + +public protocol EventProtocol { + +} diff --git a/Sources/ECS/Event/EventStreaming/EventReceiver.swift b/Sources/ECS/Event/EventStreaming/EventReceiver.swift new file mode 100644 index 0000000..335dbe2 --- /dev/null +++ b/Sources/ECS/Event/EventStreaming/EventReceiver.swift @@ -0,0 +1,29 @@ +// +// EventReceiver.swift +// ECS_Swift +// +// Created by rrbox on 2025/07/06. +// + +final class EventReceiver: AnyEventReceiver, EventStorageElement { + var eventBuffer = [T]() + + override func receive(worldStorage: WorldStorageRef) { + let events = eventBuffer + eventBuffer = [] + guard !events.isEmpty else { return } + worldStorage.eventStorage.push(EventReader(events: events)) + if let systems = worldStorage.eventStorage.eventResponder(eventOfType: T.self)!.systems[.update] { + for system in systems { + system.execute(worldStorage) + } + } + 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) + } + } + worldStorage.eventStorage.pop(EventReader.self) + } +} diff --git a/Sources/ECS/Event/EventStreaming/EventReceivers.swift b/Sources/ECS/Event/EventStreaming/EventReceivers.swift new file mode 100644 index 0000000..8127f94 --- /dev/null +++ b/Sources/ECS/Event/EventStreaming/EventReceivers.swift @@ -0,0 +1,10 @@ +// +// EventReceivers.swift +// ECS_Swift +// +// Created by rrbox on 2025/07/06. +// + +final class EventReceivers: EventStorageElement { + var eventReceivers = [AnyEventReceiver]() +} From a04006435acb55f4bc3f3da296ecb0caea80ad5e Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 6 Jul 2025 22:46:50 +0900 Subject: [PATCH 52/53] Refactor event handling to use receivers instead of queues, enhancing event processing in the ECS system --- .../CommandsEvent/CommandsEvent+Buffer.swift | 12 +++---- .../CommandsEvent/CommandsEventWriter.swift | 8 ++--- .../Event/EventStreaming/EventReader.swift | 6 ++-- .../Event/EventStreaming/EventStorage.swift | 23 ++++++++---- .../Event/EventStreaming/EventWriter.swift | 8 ++--- Sources/ECS/Event/World+EventStreamer.swift | 35 ++++--------------- Sources/ECS/WorldMethods/World+Init.swift | 4 +-- .../Commands/EntityCommands+Graphic.swift | 19 ++++++---- Sources/PlugIns/Graphic2D/PlugInExport.swift | 6 ++-- 9 files changed, 58 insertions(+), 63 deletions(-) diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift b/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift index 999954b..062051b 100644 --- a/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift +++ b/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift @@ -6,12 +6,12 @@ // extension AnyMap { - mutating func setUpCommandsEventQueue(eventOfType: T.Type) { - push(CommandsEventQueue()) + func commandsEventReceiver(eventOfType type: T.Type) -> CommandsEventReceiver? { + valueRef(ofType: CommandsEventReceiver.self)?.body } - func commandsEventQueue(eventOfType: T.Type) -> CommandsEventQueue? { - valueRef(ofType: CommandsEventQueue.self)?.body + mutating func registerCommandsEventReceiver(eventType: T.Type) { + push(CommandsEventReceiver()) } func commandsEventWriter(eventOfType type: T.Type) -> CommandsEventWriter? { @@ -19,8 +19,8 @@ extension AnyMap { } mutating func registerCommandsEventWriter(eventType: T.Type) { - let eventQueue = valueRef(ofType: CommandsEventQueue.self)!.body - push(CommandsEventWriter(eventQueue: eventQueue)) + let receiver = valueRef(ofType: CommandsEventReceiver.self)!.body + push(CommandsEventWriter(receiver: receiver)) } func commandsEventResponder(eventOfType type: T.Type) -> EventResponder? { diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift b/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift index afceacf..3b27de9 100644 --- a/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift +++ b/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift @@ -6,14 +6,14 @@ // final class CommandsEventWriter: SystemParameter, EventStorageElement { - unowned let eventQueue: CommandsEventQueue + unowned let receiver: CommandsEventReceiver - init(eventQueue: CommandsEventQueue) { - self.eventQueue = eventQueue + init(receiver: CommandsEventReceiver) { + self.receiver = receiver } public func send(value: T) { - self.eventQueue.eventQueue.append(value) + receiver.eventBuffer.append(value) } public static func register(to worldStorage: WorldStorageRef) { diff --git a/Sources/ECS/Event/EventStreaming/EventReader.swift b/Sources/ECS/Event/EventStreaming/EventReader.swift index f5fb161..b5f42d2 100644 --- a/Sources/ECS/Event/EventStreaming/EventReader.swift +++ b/Sources/ECS/Event/EventStreaming/EventReader.swift @@ -6,10 +6,10 @@ // final public class EventReader: SystemParameter, EventStorageElement { - public let value: T + public let events: [T] - init(value: T) { - self.value = value + init(events: [T]) { + self.events = events } public static func register(to worldStorage: WorldStorageRef) { diff --git a/Sources/ECS/Event/EventStreaming/EventStorage.swift b/Sources/ECS/Event/EventStreaming/EventStorage.swift index 64c5528..2cd0d7a 100644 --- a/Sources/ECS/Event/EventStreaming/EventStorage.swift +++ b/Sources/ECS/Event/EventStreaming/EventStorage.swift @@ -29,12 +29,23 @@ extension AnyMap where Mode == EventStorage { // world buffer にプロパティをつけておく extension AnyMap { - mutating func setUpEventQueue() { - push(EventQueue()) + func eventReceivers() -> EventReceivers? { + valueRef(ofType: EventReceivers.self)?.body } - func eventQueue() -> EventQueue? { - valueRef(ofType: EventQueue.self)?.body + mutating func registerEventReceivers() { + push(EventReceivers()) + } + + func eventReceiver(eventOfType type: T.Type) -> EventReceiver? { + valueRef(ofType: EventReceiver.self)?.body + } + + mutating func registerEventReceiver(eventType: T.Type) { + let eventReceiver = EventReceiver() + let eventReceivers = eventReceivers() + eventReceivers?.eventReceivers.append(eventReceiver) + push(eventReceiver) } func eventWriter(eventOfType type: T.Type) -> EventWriter? { @@ -42,8 +53,8 @@ extension AnyMap { } mutating func registerEventWriter(eventType: T.Type) { - let eventQueue = valueRef(ofType: EventQueue.self)!.body - push(EventWriter(eventQueue: eventQueue)) + let receiver = valueRef(ofType: EventReceiver.self)!.body + push(EventWriter(receiver: receiver)) } func eventResponder(eventOfType type: T.Type) -> EventResponder? { diff --git a/Sources/ECS/Event/EventStreaming/EventWriter.swift b/Sources/ECS/Event/EventStreaming/EventWriter.swift index ec47c4d..c782637 100644 --- a/Sources/ECS/Event/EventStreaming/EventWriter.swift +++ b/Sources/ECS/Event/EventStreaming/EventWriter.swift @@ -7,14 +7,14 @@ // Commands と基本的な仕組みは同じ. final public class EventWriter: SystemParameter, EventStorageElement { - unowned let eventQueue: EventQueue + unowned let receiver: EventReceiver - init(eventQueue: EventQueue) { - self.eventQueue = eventQueue + init(receiver: EventReceiver) { + self.receiver = receiver } public func send(value: T) { - self.eventQueue.eventQueue.append(Event(value: value)) + receiver.eventBuffer.append(value) } public static func register(to worldStorage: WorldStorageRef) { diff --git a/Sources/ECS/Event/World+EventStreamer.swift b/Sources/ECS/Event/World+EventStreamer.swift index f80cf95..a08f7f5 100644 --- a/Sources/ECS/Event/World+EventStreamer.swift +++ b/Sources/ECS/Event/World+EventStreamer.swift @@ -10,6 +10,7 @@ public extension World { /// /// `Event` をイベントシステムで扱う前に, World に EventStreamer を追加する必要があります. @discardableResult func addEventStreamer(eventType: T.Type) -> World { + worldStorage.eventStorage.registerEventReceiver(eventType: T.self) worldStorage.eventStorage.registerEventWriter(eventType: T.self) worldStorage.eventStorage.registerEventResponder(eventType: T.self) return self @@ -19,6 +20,7 @@ public extension World { extension World { func addCommandsEventStreamer(eventType: T.Type) { worldStorage.systemStorage.insertSchedule(.onCommandsEvent(ofType: T.self)) + worldStorage.eventStorage.registerCommandsEventReceiver(eventType: T.self) worldStorage.eventStorage.registerCommandsEventWriter(eventType: T.self) worldStorage.eventStorage.resisterCommandsEventResponder(eventType: T.self) } @@ -26,39 +28,16 @@ extension World { extension World { func applyEventQueue() { - let eventQueue = self.worldStorage.eventStorage.eventQueue()! - eventQueue.sendingEvents = eventQueue.eventQueue - eventQueue.eventQueue = [] - for event in eventQueue.sendingEvents { - event.runEventReceiver(worldStorage: self.worldStorage) + let receivers = self.worldStorage.eventStorage.eventReceivers()! + for receiver in receivers.eventReceivers { + receiver.receive(worldStorage: worldStorage) } - eventQueue.sendingEvents = [] } func applyCommandsEventQueue(eventOfType: T.Type) { let eventStorage = self.worldStorage.eventStorage - let eventQueue = eventStorage.commandsEventQueue(eventOfType: T.self)! - eventQueue.sendingEvents = eventQueue.eventQueue - eventQueue.eventQueue = [] - for event in eventQueue.sendingEvents { - worldStorage.eventStorage.push(EventReader(value: event)) - - if let systems = eventStorage.commandsEventResponder(eventOfType: T.self)!.systems[.update] { - for system in systems { - system.execute(self.worldStorage) - } - } - - 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) - } - } - - worldStorage.eventStorage.pop(EventReader.self) - } - eventQueue.sendingEvents = [] + let receiver = eventStorage.commandsEventReceiver(eventOfType: T.self)! + receiver.receive(worldStorage: worldStorage) } } diff --git a/Sources/ECS/WorldMethods/World+Init.swift b/Sources/ECS/WorldMethods/World+Init.swift index 448b305..c242ec0 100644 --- a/Sources/ECS/WorldMethods/World+Init.swift +++ b/Sources/ECS/WorldMethods/World+Init.swift @@ -36,9 +36,7 @@ public extension World { worldStorage.stateStorage.setUp() // world buffer に event queue を作成します. - worldStorage.eventStorage.setUpEventQueue() - worldStorage.eventStorage.setUpCommandsEventQueue(eventOfType: DidSpawnEvent.self) - worldStorage.eventStorage.setUpCommandsEventQueue(eventOfType: WillDespawnEvent.self) + worldStorage.eventStorage.registerEventReceivers() // world buffer に spawn/despawn event の streamer を登録します. addCommandsEventStreamer(eventType: DidSpawnEvent.self) diff --git a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift index f06400e..88063d1 100644 --- a/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift +++ b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift @@ -109,10 +109,12 @@ func removeChildIfDespawned( query: Query, parentQuery: Query ) { - let entity = despawnEvent.value.despawnedEntity - guard let parent = query.components(forEntity: entity)?.parent else { return } - parentQuery.update(parent) { p in - p._children.remove(entity) + for event in despawnEvent.events { + let entity = event.despawnedEntity + guard let parent = query.components(forEntity: entity)?.parent else { continue } + parentQuery.update(parent) { p in + p._children.remove(entity) + } } } @@ -137,7 +139,10 @@ func despawnChildIfParentDespawned( commands: Commands ) { // despawn した entity と自分の親が一致する子を despawn する. - despawnChildRecursive(despawnedEntity: despawnedEntityEvent.value.despawnedEntity, - children: children, - commands: commands) + for event in despawnedEntityEvent.events { + let despawnedEntity = event.despawnedEntity + despawnChildRecursive(despawnedEntity: despawnedEntity, + children: children, + commands: commands) + } } diff --git a/Sources/PlugIns/Graphic2D/PlugInExport.swift b/Sources/PlugIns/Graphic2D/PlugInExport.swift index 063f0ab..16ca6c1 100644 --- a/Sources/PlugIns/Graphic2D/PlugInExport.swift +++ b/Sources/PlugIns/Graphic2D/PlugInExport.swift @@ -89,8 +89,10 @@ func _removeFromParentSystem( @MainActor func _removeNodeIfDespawned(despawn: EventReader, nodes: Resource) { - let entity = despawn.value.despawnedEntity - nodes.resource.removeNode(forEntity: entity) + for event in despawn.events { + let despawnedEntity = event.despawnedEntity + nodes.resource.removeNode(forEntity: despawnedEntity) + } } // TODO: - Node 操作イベントのハンドリングは他のフェーズでも同様に行なわなくてもOK? From f51bc881882e0f7c769fdf33573a8b1d1445f223 Mon Sep 17 00:00:00 2001 From: rrbox <87851278+rrbox@users.noreply.github.com> Date: Sun, 6 Jul 2025 22:47:14 +0900 Subject: [PATCH 53/53] Remove unnecessary entity spawn/despawn info from event schedule documentation --- Sources/ECS/Schedule/EventSchedule.swift | 19 +------------------ Sources/ECS/Schedule/Schedule.swift | 6 +----- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/Sources/ECS/Schedule/EventSchedule.swift b/Sources/ECS/Schedule/EventSchedule.swift index 1164af7..6656e1d 100644 --- a/Sources/ECS/Schedule/EventSchedule.swift +++ b/Sources/ECS/Schedule/EventSchedule.swift @@ -8,25 +8,8 @@ /** システムが実行されるタイミングを指定します. - Eccentials + - ``EventSchedule/update`` - - ``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 diff --git a/Sources/ECS/Schedule/Schedule.swift b/Sources/ECS/Schedule/Schedule.swift index 9c7554b..aceedab 100644 --- a/Sources/ECS/Schedule/Schedule.swift +++ b/Sources/ECS/Schedule/Schedule.swift @@ -22,11 +22,7 @@ - ``Schedule/onStackUpdate(_:)`` - ``Schedule/onPause(_:)`` - ``Schedule/onResume(_:)`` - - Entity spawn / despawn - - - ``Schedule/didSpawn`` - - ``Schedule/willDespawn`` + */ public struct Schedule: Hashable { let typeId: ObjectIdentifier