diff --git a/.swiftpm/xcode/xcshareddata/xcbaselines/ecs-swiftTests.xcbaseline/2AC02D75-F4A4-4DFA-8C0B-FAD0AD501706.plist b/.swiftpm/xcode/xcshareddata/xcbaselines/ecs-swiftTests.xcbaseline/2AC02D75-F4A4-4DFA-8C0B-FAD0AD501706.plist new file mode 100644 index 0000000..c24d915 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcbaselines/ecs-swiftTests.xcbaseline/2AC02D75-F4A4-4DFA-8C0B-FAD0AD501706.plist @@ -0,0 +1,32 @@ + + + + + classNames + + ecs_swiftTests + + testPerformance() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.004780 + baselineIntegrationDisplayName + Local Baseline + + + testUpdate4Performance() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.015800 + baselineIntegrationDisplayName + Local Baseline + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcbaselines/ecs-swiftTests.xcbaseline/Info.plist b/.swiftpm/xcode/xcshareddata/xcbaselines/ecs-swiftTests.xcbaseline/Info.plist new file mode 100644 index 0000000..a261598 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcbaselines/ecs-swiftTests.xcbaseline/Info.plist @@ -0,0 +1,33 @@ + + + + + runDestinationsByUUID + + 2AC02D75-F4A4-4DFA-8C0B-FAD0AD501706 + + localComputer + + busSpeedInMHz + 0 + cpuCount + 1 + cpuKind + Apple M1 + cpuSpeedInMHz + 0 + logicalCPUCoresPerPackage + 8 + modelCode + MacBookPro17,1 + physicalCPUCoresPerPackage + 8 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + arm64 + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/ECS-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/ECS-Package.xcscheme index b7d7296..e45474d 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/ECS-Package.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/ECS-Package.xcscheme @@ -1,6 +1,6 @@ Target { + .target( + name: module.name, + dependencies: dependencies.map { $0.dependency }, + path: module.path) + } + + static func testTarget(module: Module, dependencies: [Module]) -> Target { + .testTarget( + name: module.name, + dependencies: dependencies.map { $0.dependency }, + path: module.path + ) + } +} + let package = Package( name: "ECS_Swift", + platforms: [.macOS(.v10_15)], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( @@ -45,6 +65,7 @@ let package = Package( .library( name: Module.plugIns.name, targets: [ + Module.graphic2d.name, Module.keyboard.name, Module.mouse.name, Module.objectLink.name, @@ -60,45 +81,46 @@ let package = Package( // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( - name: Module.ecs.name, + module: .ecs, dependencies: []), .target( - name: Module.keyboard.name, - dependencies: [Module.ecs.dependency], - path: Module.keyboard.path), + module: .graphic2d, + dependencies: [.ecs]), .target( - name: Module.mouse.name, - dependencies: [Module.ecs.dependency], - path: Module.mouse.path), + module: .keyboard, + dependencies: [.ecs]), .target( - name: Module.objectLink.name, - dependencies: [Module.ecs.dependency], - path: Module.objectLink.path), + module: .mouse, + dependencies: [.ecs]), .target( - name: Module.scene.name, - dependencies: [Module.ecs.dependency], - path: Module.scene.path), + module: .objectLink, + dependencies: [.ecs]), .target( - name: Module.scroll.name, - dependencies: [Module.ecs.dependency], - path: Module.scroll.path), + module: .scene, + dependencies: [.ecs]), + .target( + module: .scroll, + dependencies: [.ecs]), + .testTarget( + module: .ecs_swiftTests, + dependencies: [.ecs]), .testTarget( - name: Module.ecs_swiftTests.name, - dependencies: [Module.ecs.dependency]), + module: .graphicPlugInTests, + dependencies: [.graphic2d]), .testTarget( - name: Module.keyBoardPlugInTests.name, - dependencies: [Module.keyboard.dependency]), + module: .keyBoardPlugInTests, + dependencies: [.keyboard]), .testTarget( - name: Module.mousePlugInTests.name, - dependencies: [Module.mouse.dependency]), + module: .mousePlugInTests, + dependencies: [.mouse]), .testTarget( - name: Module.objectLinkPlugInTests.name, - dependencies: [Module.objectLink.dependency]), + module: .objectLinkPlugInTests, + dependencies: [.objectLink]), .testTarget( - name: Module.scenePlugInTests.name, - dependencies: [Module.scene.dependency]), + module: .scenePlugInTests, + dependencies: [.scene]), .testTarget( - name: Module.scrollPlugInTests.name, - dependencies: [Module.scroll.dependency]), + module: .scrollPlugInTests, + dependencies: [.scroll]), ] ) diff --git a/Sources/ECS/Chunk/Chunk.swift b/Sources/ECS/Chunk/Chunk.swift index 175c602..3c2f7e4 100644 --- a/Sources/ECS/Chunk/Chunk.swift +++ b/Sources/ECS/Chunk/Chunk.swift @@ -1,8 +1,12 @@ // -// File.swift +// Chunk.swift // // // Created by rrbox on 2023/08/11. // -import Foundation +public class Chunk { + func spawn(entity: Entity, entityRecord: EntityRecord) {} + func despawn(entity: Entity) {} + func applyCurrentState(_ entityRecord: EntityRecord, forEntity entity: Entity) {} +} diff --git a/Sources/ECS/Chunk/ChunkBuffer+.swift b/Sources/ECS/Chunk/ChunkBuffer+.swift index cc38989..88dd8bf 100644 --- a/Sources/ECS/Chunk/ChunkBuffer+.swift +++ b/Sources/ECS/Chunk/ChunkBuffer+.swift @@ -15,8 +15,8 @@ extension ChunkBuffer { self.buffer.component(ofType: ChunkEntityInterface.self)!.add(chunk: chunk) } - func push(entity: Entity, value: Archetype) { - self.buffer.component(ofType: ChunkEntityInterface.self)!.push(entity: entity, value: value) + func push(entity: Entity, entityRecord: EntityRecord) { + self.buffer.component(ofType: ChunkEntityInterface.self)!.push(entity: entity, entityRecord: entityRecord) } func applyEntityQueue() { @@ -27,4 +27,9 @@ extension ChunkBuffer { self.buffer.component(ofType: ChunkEntityInterface.self)!.despawn(entity: entity) } + // entity を最新の状態に更新します. + func applyCurrentState(_ entityRecord: EntityRecord, forEntity entity: Entity) { + self.buffer.component(ofType: ChunkEntityInterface.self)!.applyCurrentState(entityRecord, forEntity: entity) + } + } diff --git a/Sources/ECS/Chunk/ChunkBuffer.swift b/Sources/ECS/Chunk/ChunkBuffer.swift index 175c602..59e67dc 100644 --- a/Sources/ECS/Chunk/ChunkBuffer.swift +++ b/Sources/ECS/Chunk/ChunkBuffer.swift @@ -1,8 +1,38 @@ // -// File.swift +// ChunkBuffer.swift // // // Created by rrbox on 2023/08/11. // import Foundation + +/// Chunk を種類別で格納します +final public class ChunkBuffer { + class ChunkRegistry: BufferElement { + let chunk: ChunkType + init(chunk: ChunkType) { + self.chunk = chunk + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + + let buffer: Buffer + init(buffer: Buffer) { + self.buffer = buffer + } + + public func chunk(ofType type: ChunkType.Type) -> ChunkType? { + self.buffer.component(ofType: ChunkRegistry.self)?.chunk + } +} + +public extension WorldBuffer { + var chunkBuffer: ChunkBuffer { + ChunkBuffer(buffer: self) + } +} diff --git a/Sources/ECS/Chunk/ChunkEntityInterface.swift b/Sources/ECS/Chunk/ChunkEntityInterface.swift new file mode 100644 index 0000000..b4dccbf --- /dev/null +++ b/Sources/ECS/Chunk/ChunkEntityInterface.swift @@ -0,0 +1,57 @@ +// +// ChunkEntityInterface.swift +// +// +// Created by rrbox on 2023/08/11. +// + +/// Chunk を種類関係なく格納するためのコンポーネントです. +/// +/// Entity の変更を全ての Chunk に反映させる目的で使用されます. +class ChunkEntityInterface: BufferElement { + /// entity が spawn されてから component が完全に挿入されるまでの間, entity を queue に保管します. + /// + /// Entity が ``Commands/spawn()`` され, ``EntityCommands/addComponent(_:)`` されるまでの間, Entity は実際には Chunk に反映されず, + var prespawnedEntityQueue = [(Entity, EntityRecord)]() + var chunks = [Chunk]() + + /// chunk を追加します + func add(chunk: Chunk) { + self.chunks.append(chunk) + } + + /// World に entity が追加された時に実行します. + /// + /// entity が queue に追加され、フレームの終わりに全ての chunk に entity を反映します. + func push(entity: Entity, entityRecord: EntityRecord) { + self.prespawnedEntityQueue.append((entity, entityRecord)) + } + + /// Spawn 処理された entity を, 実際に chunk に追加します. + /// + /// Component が完全に追加された後にこの処理を呼び出すことで, Entity の Component の有無が Chunk に反映されるようになります. + func applyEntityQueue() { + for (entity, entityRecord) in prespawnedEntityQueue { + for chunk in self.chunks { + chunk.spawn(entity: entity, entityRecord: entityRecord) + } + } + self.prespawnedEntityQueue = [] + } + + /// World から entity が削除される時に実行します. + /// + /// フレームの終わりに全ての chunk から entity を削除します. + func despawn(entity: Entity) { + for chunk in self.chunks { + chunk.despawn(entity: entity) + } + } + + func applyCurrentState(_ entityRecord: EntityRecord, forEntity entity: Entity) { + for chunk in self.chunks { + chunk.applyCurrentState(entityRecord, forEntity: entity) + } + } + +} diff --git a/Sources/ECS/Chunk/Spawn+Chunk.swift b/Sources/ECS/Chunk/Spawn+Chunk.swift deleted file mode 100644 index 322e460..0000000 --- a/Sources/ECS/Chunk/Spawn+Chunk.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// Spawn+Chunk.swift -// -// -// Created by rrbox on 2023/08/11. -// - -/// Chunk を種類関係なく格納するためのコンポーネントです. -class ChunkEntityInterface { - class Command { - func apply(chunks: [Chunk]) { - - } - } -// var entityQueue = [(Entity, Archetype)]() - var spawnEntityQueue = [(Entity, Archetype)]() - var despawnEntityQueue = [Entity]() - var chunks = [Chunk]() - - /// World に entity が追加された時に実行します. - /// - /// entity が queue に追加され、フレームの終わりに全ての chunk に entity を反映します. - func spawn(entity: Entity, value: Archetype) { - self.spawnEntityQueue.append((entity, value)) - } - - /// World から entity が削除される時に実行します. - /// - /// フレームの終わりに全ての chunk から entity をさ削除します. - func despawn(_ entity: Entity) { - self.despawnEntityQueue.append(entity) - } - - /// フレーム終了時、 enqueue された entity を全ての chunk に追加します. - func applyEntityQueue() { - for (entity, value) in self.spawnEntityQueue { - for chunk in self.chunks { - chunk.spawn(entity: entity, value: value) - } - } - for entity in self.despawnEntityQueue { - for chunk in self.chunks { - chunk.despawn(entity: entity) - } - } - self.spawnEntityQueue = [] - self.despawnEntityQueue = [] - } - -} diff --git a/Sources/ECS/Commands/Command.swift b/Sources/ECS/Commands/Command.swift index 123598b..71702bf 100644 --- a/Sources/ECS/Commands/Command.swift +++ b/Sources/ECS/Commands/Command.swift @@ -1,8 +1,13 @@ // -// File.swift +// Command.swift // // // Created by rrbox on 2023/08/09. // -import Foundation +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 123598b..b746201 100644 --- a/Sources/ECS/Commands/Commands.swift +++ b/Sources/ECS/Commands/Commands.swift @@ -1,8 +1,24 @@ // -// File.swift +// Commands.swift // // // Created by rrbox on 2023/08/09. // -import Foundation +final public class Commands: SystemParameter { + var commandQueue = [Command]() + + /// Commands では, World への登録時には何もしません. + public static func register(to worldBuffer: WorldBuffer) { + + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> Commands? { + worldBuffer.commandsBuffer.commands() + } + + /// CommandQueue にコマンドを追加します. + public func push(command: Command) { + self.commandQueue.append(command) + } +} diff --git a/Sources/ECS/Commands/CommandsBuffer.swift b/Sources/ECS/Commands/CommandsBuffer.swift index 4554040..e999856 100644 --- a/Sources/ECS/Commands/CommandsBuffer.swift +++ b/Sources/ECS/Commands/CommandsBuffer.swift @@ -1,8 +1,42 @@ // -// Commands+WorldBuffer.swift +// CommandsBuffer.swift // // // Created by rrbox on 2023/08/10. // import Foundation + +final public class CommandsBuffer { + class CommandsRegistry: BufferElement { + let commands: Commands + init(commands: Commands) { + self.commands = commands + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + let buffer: Buffer + + init(buffer: Buffer) { + self.buffer = buffer + } + + public func commands() -> Commands? { + self.buffer.component(ofType: CommandsRegistry.self)?.commands + } + + func setCommands(_ commands: Commands) { + self.buffer.addComponent(CommandsRegistry(commands: commands)) + } +} + +// WorldBuffer + Commands +public extension WorldBuffer { + var commandsBuffer: CommandsBuffer { + CommandsBuffer(buffer: self) + } +} diff --git a/Sources/ECS/Commands/World+Commands.swift b/Sources/ECS/Commands/World+Commands.swift index 175c602..4ebbca9 100644 --- a/Sources/ECS/Commands/World+Commands.swift +++ b/Sources/ECS/Commands/World+Commands.swift @@ -1,8 +1,16 @@ // -// File.swift +// World+Commands.swift // // // Created by rrbox on 2023/08/11. // -import Foundation +extension World { + func applyCommands() { + let commands = self.worldBuffer.commandsBuffer.commands()! + for command in commands.commandQueue { + command.runCommand(in: self) + } + commands.commandQueue = [] + } +} diff --git a/Sources/ECS/Commons/Types.swift b/Sources/ECS/Commons/Types.swift index ac44a2f..2f955bd 100644 --- a/Sources/ECS/Commons/Types.swift +++ b/Sources/ECS/Commons/Types.swift @@ -7,7 +7,7 @@ import GameplayKit -public typealias Archetype = GKEntity +public typealias EntityRecord = GKEntity public typealias ArchetypeComponent = GKComponent public typealias Buffer = GKEntity public typealias BufferElement = GKComponent diff --git a/Sources/ECS/EntityCommands/Commands+EntityCommands.swift b/Sources/ECS/EntityCommands/Commands+EntityCommands.swift index e94d427..591ca45 100644 --- a/Sources/ECS/EntityCommands/Commands+EntityCommands.swift +++ b/Sources/ECS/EntityCommands/Commands+EntityCommands.swift @@ -1,8 +1,25 @@ // -// File.swift +// Commands+EntityCommands.swift // // // Created by rrbox on 2023/08/10. // -import Foundation +public extension Commands { + /// Entity を取得して変更を加えます + func entity(_ entity: Entity) -> EntityCommands? { + return EntityCommands(entity: entity, commands: self) + } + + /// Entity を追加して変更を加えます. + func spawn() -> EntityCommands { + let entity = Entity() + self.commandQueue.append(SpawnCommand(id: entity, entityRecord: EntityRecord())) + return EntityCommands(entity: entity, commands: self) + } + + /// Entity を削除します. + func despawn(entity: Entity) { + self.commandQueue.append(DespawnCommand(entity: entity)) + } +} diff --git a/Sources/ECS/EntityCommands/Commands/AddComponentCommand.swift b/Sources/ECS/EntityCommands/Commands/AddComponentCommand.swift index e94d427..a51c701 100644 --- a/Sources/ECS/EntityCommands/Commands/AddComponentCommand.swift +++ b/Sources/ECS/EntityCommands/Commands/AddComponentCommand.swift @@ -1,8 +1,19 @@ // -// File.swift +// AddComponentCommand.swift // // // Created by rrbox on 2023/08/10. // -import Foundation +class AddComponent: EntityCommand { + let componnet: ComponentType + + init(entity: Entity, componnet: ComponentType) { + self.componnet = componnet + super.init(entity: entity) + } + + override func runCommand(in world: World) { + world.addComponent(self.componnet, forEntity: self.entity) + } +} diff --git a/Sources/ECS/EntityCommands/Commands/DespawnCommand.swift b/Sources/ECS/EntityCommands/Commands/DespawnCommand.swift index e94d427..e986818 100644 --- a/Sources/ECS/EntityCommands/Commands/DespawnCommand.swift +++ b/Sources/ECS/EntityCommands/Commands/DespawnCommand.swift @@ -1,8 +1,12 @@ // -// File.swift +// DespawnCommand.swift // // // Created by rrbox on 2023/08/10. // -import Foundation +class DespawnCommand: EntityCommand { + override func runCommand(in world: World) { + world.despawn(entity: self.entity) + } +} diff --git a/Sources/ECS/EntityCommands/Commands/RemoveComponentCommand.swift b/Sources/ECS/EntityCommands/Commands/RemoveComponentCommand.swift index e94d427..7bd9427 100644 --- a/Sources/ECS/EntityCommands/Commands/RemoveComponentCommand.swift +++ b/Sources/ECS/EntityCommands/Commands/RemoveComponentCommand.swift @@ -1,8 +1,16 @@ // -// File.swift +// RemoveComponentCommand.swift // // // Created by rrbox on 2023/08/10. // -import Foundation +class RemoveComponent: EntityCommand { + init(entity: Entity, componentType type: ComponentType.Type) { + super.init(entity: entity) + } + + override func runCommand(in world: World) { + world.removeComponent(ofType: ComponentType.self, fromEntity: self.entity) + } +} diff --git a/Sources/ECS/EntityCommands/Commands/SpawnCommand.swift b/Sources/ECS/EntityCommands/Commands/SpawnCommand.swift index e94d427..87e72a6 100644 --- a/Sources/ECS/EntityCommands/Commands/SpawnCommand.swift +++ b/Sources/ECS/EntityCommands/Commands/SpawnCommand.swift @@ -1,8 +1,20 @@ // -// File.swift +// SpawnCommand.swift // // // Created by rrbox on 2023/08/10. // -import Foundation +class SpawnCommand: EntityCommand { + let entityRecord: EntityRecord + + init(id: Entity, entityRecord: EntityRecord) { + self.entityRecord = entityRecord + super.init(entity: id) + } + + override func runCommand(in world: World) { + world.push(entity: self.entity, entityRecord: self.entityRecord) + } +} + diff --git a/Sources/ECS/EntityCommands/EntityCommands.swift b/Sources/ECS/EntityCommands/EntityCommands.swift deleted file mode 100644 index b591aa4..0000000 --- a/Sources/ECS/EntityCommands/EntityCommands.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// EntityCommands.swift -// -// -// Created by rrbox on 2023/08/09. -// - -final public class EntityCommands { - public let entity: Entity - let commands: Commands - - init(entity: Entity, commands: Commands) { - self.entity = entity - self.commands = commands - } - - public func id() -> Entity { - self.entity - } - -} diff --git a/Sources/ECS/EntityCommands/EntityCommands/EntityCommand.swift b/Sources/ECS/EntityCommands/EntityCommands/EntityCommand.swift new file mode 100644 index 0000000..ba4d307 --- /dev/null +++ b/Sources/ECS/EntityCommands/EntityCommands/EntityCommand.swift @@ -0,0 +1,13 @@ +// +// EntityCommand.swift +// +// +// Created by rrbox on 2023/08/18. +// + +open class EntityCommand: Command { + public let entity: Entity + public init(entity: Entity) { + self.entity = entity + } +} diff --git a/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift new file mode 100644 index 0000000..614fb87 --- /dev/null +++ b/Sources/ECS/EntityCommands/EntityCommands/EntityCommands.swift @@ -0,0 +1,43 @@ +// +// EntityCommands.swift +// +// +// Created by rrbox on 2023/08/09. +// + +final public class EntityCommands { + let entity: Entity + let commands: Commands + + init(entity: Entity, commands: Commands) { + self.entity = entity + self.commands = commands + } + + public func pushCommand(_ command: EntityCommand) { + self.commands.commandQueue.append(command) + } + + /// Commands で操作した Entity を受け取ります. + /// - Returns: ID としての Entity をそのまま返します. + public func id() -> Entity { + self.entity + } + + /// Entity に Component を追加します. + /// - Parameter component: 追加するコンポーネントを指定します. + /// - Returns: Entity component のビルダーです. + @discardableResult public func addComponent(_ component: ComponentType) -> EntityCommands { + self.commands.commandQueue.append(AddComponent(entity: self.entity, componnet: component)) + return self + } + + /// Entity から Component を削除します. + /// - Parameter type: 削除する Component の型を指定します. + /// - Returns: Entity component のビルダーです. + @discardableResult public func removeComponent(ofType type: ComponentType.Type) -> EntityCommands { + self.commands.commandQueue.append(RemoveComponent(entity: self.entity, componentType: ComponentType.self)) + return self + } + +} diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift b/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift new file mode 100644 index 0000000..7133e26 --- /dev/null +++ b/Sources/ECS/Event/CommandsEvent/CommandsEvent+Buffer.swift @@ -0,0 +1,39 @@ +// +// CommandsEvent+Buffer.swift +// +// +// Created by rrbox on 2023/08/29. +// + +import Foundation + +class CommadsEventWriterRegistry: BufferElement { + let writer: CommandsEventWriter + init(writer: CommandsEventWriter) { + self.writer = writer + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension EventBuffer { + func setUpCommandsEventQueue(eventOfType: T.Type) { + self.buffer.addComponent(CommandsEventQueue()) + } + + func commandsEventQueue(eventOfType: T.Type) -> CommandsEventQueue? { + self.buffer.component(ofType: CommandsEventQueue.self) + } + + func commandsEventWriter(eventOfType type: T.Type) -> CommandsEventWriter? { + self.buffer.component(ofType: CommadsEventWriterRegistry.self)?.writer + } + + func registerCommandsEventWriter(eventType: T.Type) { + let eventQueue = self.buffer.component(ofType: CommandsEventQueue.self)! + self.buffer.addComponent(CommadsEventWriterRegistry(writer: CommandsEventWriter(eventQueue: eventQueue))) + } +} diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEvent.swift b/Sources/ECS/Event/CommandsEvent/CommandsEvent.swift new file mode 100644 index 0000000..7be2376 --- /dev/null +++ b/Sources/ECS/Event/CommandsEvent/CommandsEvent.swift @@ -0,0 +1,11 @@ +// +// CommandsEvent.swift +// +// +// Created by rrbox on 2023/08/29. +// + +/// Commands 実行中に発信されるイベントです. +protocol CommandsEventProtocol { + +} diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift b/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift new file mode 100644 index 0000000..978e325 --- /dev/null +++ b/Sources/ECS/Event/CommandsEvent/CommandsEventQueue.swift @@ -0,0 +1,11 @@ +// +// CommandsEventQueue.swift +// +// +// Created by rrbox on 2023/08/29. +// + +class CommandsEventQueue: BufferElement { + var eventQueue = [T]() + var sendingEvents = [T]() +} diff --git a/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift b/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift new file mode 100644 index 0000000..349ad96 --- /dev/null +++ b/Sources/ECS/Event/CommandsEvent/CommandsEventWriter.swift @@ -0,0 +1,27 @@ +// +// CommandsEventWriter.swift +// +// +// Created by rrbox on 2023/08/29. +// + +final class CommandsEventWriter: SystemParameter, SetUpSystemParameter { + 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 worldBuffer: WorldBuffer) { + + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> CommandsEventWriter? { + worldBuffer.eventBuffer.commandsEventWriter(eventOfType: T.self) + } +} + diff --git a/Sources/ECS/Event/EventStreaming/Event.swift b/Sources/ECS/Event/EventStreaming/Event.swift new file mode 100644 index 0000000..edc203f --- /dev/null +++ b/Sources/ECS/Event/EventStreaming/Event.swift @@ -0,0 +1,29 @@ +// +// Event.swift +// +// +// Created by rrbox on 2023/08/14. +// + +public protocol EventProtocol { + +} + +class AnyEvent { + func runEventReceiver(worldBuffer: WorldBuffer) { + + } +} + +final class Event: AnyEvent { + let value: T + init(value: T) { + self.value = value + } + + override func runEventReceiver(worldBuffer: WorldBuffer) { + for system in worldBuffer.systemBuffer.systems(ofType: EventSystemExecute.self) { + system.receive(event: EventReader(value: self.value), worldBuffer: worldBuffer) + } + } +} diff --git a/Sources/ECS/Event/EventStreaming/EventBuffer.swift b/Sources/ECS/Event/EventStreaming/EventBuffer.swift new file mode 100644 index 0000000..aca1bf2 --- /dev/null +++ b/Sources/ECS/Event/EventStreaming/EventBuffer.swift @@ -0,0 +1,53 @@ +// +// EventBuffer.swift +// +// +// Created by rrbox on 2023/08/14. +// + +import Foundation + +// world buffer にプロパティをつけておく +class EventBuffer { + class EventWriterRegistry: BufferElement { + let writer: EventWriter + init(writer: EventWriter) { + self.writer = writer + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + + let buffer: Buffer + init(buffer: Buffer) { + self.buffer = buffer + } + + func setUpEventQueue() { + self.buffer.addComponent(EventQueue()) + } + + func eventQueue() -> EventQueue? { + self.buffer.component(ofType: EventQueue.self) + } + + func eventWriter(eventOfType type: T.Type) -> EventWriter? { + self.buffer.component(ofType: EventWriterRegistry.self)?.writer + } + + func registerEventWriter(eventType: T.Type) { + let eventQueue = self.buffer.component(ofType: EventQueue.self)! + self.buffer.addComponent(EventWriterRegistry(writer: EventWriter(eventQueue: eventQueue))) + } + +} + +extension WorldBuffer { + var eventBuffer: EventBuffer { + EventBuffer(buffer: self) + } +} + diff --git a/Sources/ECS/Event/EventStreaming/EventQueue.swift b/Sources/ECS/Event/EventStreaming/EventQueue.swift new file mode 100644 index 0000000..f807487 --- /dev/null +++ b/Sources/ECS/Event/EventStreaming/EventQueue.swift @@ -0,0 +1,11 @@ +// +// EventQueue.swift +// +// +// Created by rrbox on 2023/08/14. +// + +class EventQueue: BufferElement { + var eventQueue = [AnyEvent]() + var sendingEvents = [AnyEvent]() +} diff --git a/Sources/ECS/Event/EventStreaming/EventReader.swift b/Sources/ECS/Event/EventStreaming/EventReader.swift new file mode 100644 index 0000000..b37b4f1 --- /dev/null +++ b/Sources/ECS/Event/EventStreaming/EventReader.swift @@ -0,0 +1,10 @@ +// +// EventReader.swift +// +// +// Created by rrbox on 2023/08/14. +// + +public struct EventReader { + public let value: T +} diff --git a/Sources/ECS/Event/EventStreaming/EventWriter.swift b/Sources/ECS/Event/EventStreaming/EventWriter.swift new file mode 100644 index 0000000..1924189 --- /dev/null +++ b/Sources/ECS/Event/EventStreaming/EventWriter.swift @@ -0,0 +1,27 @@ +// +// EventWriter.swift +// +// +// Created by rrbox on 2023/08/14. +// + +// Commands と基本的な仕組みは同じ. +final public class EventWriter: SystemParameter, SetUpSystemParameter { + 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 worldBuffer: WorldBuffer) { + + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> EventWriter? { + worldBuffer.eventBuffer.eventWriter(eventOfType: T.self) + } +} diff --git a/Sources/ECS/Event/EventSystem/EventSystem.swift b/Sources/ECS/Event/EventSystem/EventSystem.swift new file mode 100644 index 0000000..8dbc61e --- /dev/null +++ b/Sources/ECS/Event/EventSystem/EventSystem.swift @@ -0,0 +1,30 @@ +// +// EventSystem.swift +// +// +// Created by rrbox on 2023/08/14. +// + +final public class EventSystem: EventSystemExecute { + let execute: (EventReader, Parameter) -> () + + public init(execute: @escaping (EventReader, Parameter) -> ()) { + self.execute = execute + } + + override func receive(event: EventReader, worldBuffer: WorldBuffer) { + self.execute(event, Parameter.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addEventSystem(_ system: EventSystem) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: EventSystemExecute.self) + Parameter.register(to: self.worldBuffer) + return self + } + + @discardableResult func addEventSystem(_ execute: @escaping (EventReader, Parameter) -> ()) -> World { + self.addEventSystem(EventSystem(execute: execute)) + } +} diff --git a/Sources/ECS/Event/EventSystem/EventSystem2.swift b/Sources/ECS/Event/EventSystem/EventSystem2.swift new file mode 100644 index 0000000..aaa09b2 --- /dev/null +++ b/Sources/ECS/Event/EventSystem/EventSystem2.swift @@ -0,0 +1,32 @@ +// +// EventSystem2.swift +// +// +// Created by rrbox on 2023/08/14. +// + +final public class EventSystem2: EventSystemExecute { + let execute: (EventReader, P0, P1) -> () + + public init(execute: @escaping (EventReader, P0, P1) -> ()) { + self.execute = execute + } + + override func receive(event: EventReader, worldBuffer: WorldBuffer) { + self.execute(event, P0.getParameter(from: worldBuffer)!, P1.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addEventSystem(_ system: EventSystem2) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: EventSystemExecute.self) + P0.register(to: self.worldBuffer) + P1.register(to: self.worldBuffer) + return self + } + + @discardableResult func addEventSystem(_ execute: @escaping (EventReader, P0, P1) -> ()) -> World { + self.addEventSystem(EventSystem2(execute: execute)) + } +} + diff --git a/Sources/ECS/Event/EventSystem/EventSystem3.swift b/Sources/ECS/Event/EventSystem/EventSystem3.swift new file mode 100644 index 0000000..06dd8f6 --- /dev/null +++ b/Sources/ECS/Event/EventSystem/EventSystem3.swift @@ -0,0 +1,32 @@ +// +// EventSystem3.swift +// +// +// Created by rrbox on 2023/08/14. +// + +final public class EventSystem3: EventSystemExecute { + let execute: (EventReader, P0, P1, P2) -> () + + public init(execute: @escaping (EventReader, P0, P1, P2) -> ()) { + self.execute = execute + } + + override func receive(event: EventReader, worldBuffer: WorldBuffer) { + self.execute(event, P0.getParameter(from: worldBuffer)!, P1.getParameter(from: worldBuffer)!, P2.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addEventSystem(_ system: EventSystem3) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: EventSystemExecute.self) + P0.register(to: self.worldBuffer) + P1.register(to: self.worldBuffer) + P2.register(to: self.worldBuffer) + return self + } + + @discardableResult func addEventSystem(_ execute: @escaping (EventReader, P0, P1, P2) -> ()) -> World { + self.addEventSystem(EventSystem3(execute: execute)) + } +} diff --git a/Sources/ECS/Event/EventSystem/EventSystem4.swift b/Sources/ECS/Event/EventSystem/EventSystem4.swift new file mode 100644 index 0000000..73d5d6b --- /dev/null +++ b/Sources/ECS/Event/EventSystem/EventSystem4.swift @@ -0,0 +1,33 @@ +// +// EventSystem4.swift +// +// +// Created by rrbox on 2023/08/14. +// + +final public class EventSystem4: EventSystemExecute { + let execute: (EventReader, P0, P1, P2, P3) -> () + + public init(execute: @escaping (EventReader, P0, P1, P2, P3) -> ()) { + self.execute = execute + } + + override func receive(event: EventReader, worldBuffer: WorldBuffer) { + self.execute(event, P0.getParameter(from: worldBuffer)!, P1.getParameter(from: worldBuffer)!, P2.getParameter(from: worldBuffer)!, P3.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addEventSystem(_ system: EventSystem4) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: EventSystemExecute.self) + P0.register(to: self.worldBuffer) + P1.register(to: self.worldBuffer) + P2.register(to: self.worldBuffer) + P3.register(to: self.worldBuffer) + return self + } + + @discardableResult func addEventSystem(_ execute: @escaping (EventReader, P0, P1, P2, P3) -> ()) -> World { + self.addEventSystem(EventSystem4(execute: execute)) + } +} diff --git a/Sources/ECS/Event/EventSystem/EventSystem5.swift b/Sources/ECS/Event/EventSystem/EventSystem5.swift new file mode 100644 index 0000000..5d892f8 --- /dev/null +++ b/Sources/ECS/Event/EventSystem/EventSystem5.swift @@ -0,0 +1,34 @@ +// +// EventSystem5.swift +// +// +// Created by rrbox on 2023/08/14. +// + +final public class EventSystem5: EventSystemExecute { + let execute: (EventReader, P0, P1, P2, P3, P4) -> () + + public init(execute: @escaping (EventReader, P0, P1, P2, P3, P4) -> ()) { + self.execute = execute + } + + override func receive(event: EventReader, worldBuffer: WorldBuffer) { + self.execute(event, P0.getParameter(from: worldBuffer)!, P1.getParameter(from: worldBuffer)!, P2.getParameter(from: worldBuffer)!, P3.getParameter(from: worldBuffer)!, P4.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addEventSystem(_ system: EventSystem5) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: EventSystemExecute.self) + P0.register(to: self.worldBuffer) + P1.register(to: self.worldBuffer) + P2.register(to: self.worldBuffer) + P3.register(to: self.worldBuffer) + P4.register(to: self.worldBuffer) + return self + } + + @discardableResult func addEventSystem(_ execute: @escaping (EventReader, P0, P1, P2, P3, P4) -> ()) -> World { + self.addEventSystem(EventSystem5(execute: execute)) + } +} diff --git a/Sources/ECS/Event/EventSystem/EventSystemExecute.swift b/Sources/ECS/Event/EventSystem/EventSystemExecute.swift new file mode 100644 index 0000000..af387c9 --- /dev/null +++ b/Sources/ECS/Event/EventSystem/EventSystemExecute.swift @@ -0,0 +1,12 @@ +// +// EventSystemExecute.swift +// +// +// Created by rrbox on 2023/08/14. +// + +public class EventSystemExecute: SystemExecute { + func receive(event: EventReader, worldBuffer: WorldBuffer) { + + } +} diff --git a/Sources/ECS/Event/World+EventStreamer.swift b/Sources/ECS/Event/World+EventStreamer.swift new file mode 100644 index 0000000..f49ff0e --- /dev/null +++ b/Sources/ECS/Event/World+EventStreamer.swift @@ -0,0 +1,54 @@ +// +// World+EventStreamer.swift +// +// +// Created by rrbox on 2023/08/14. +// + +public extension World { + /// `T` 型の値をイベントとして送受信するためのセットアップです. + /// + /// `Event` をイベントシステムで扱う前に, World に EventStreamer を追加する必要があります. + @discardableResult func addEventStreamer(eventType: T.Type) -> World { + self.worldBuffer.systemBuffer.registerSystemRegistry(ofType: EventSystemExecute.self) + self.worldBuffer.eventBuffer.registerEventWriter(eventType: T.self) + return self + } +} + +extension World { + func addCommandsEventStreamer(eventType: T.Type) { + self.worldBuffer.systemBuffer.registerSystemRegistry(ofType: EventSystemExecute.self) + self.worldBuffer.eventBuffer.registerCommandsEventWriter(eventType: T.self) + } +} + +extension World { + func applyEventQueue() { + let eventQueue = self.worldBuffer.eventBuffer.eventQueue()! + eventQueue.sendingEvents = eventQueue.eventQueue + eventQueue.eventQueue = [] + for event in eventQueue.sendingEvents { + event.runEventReceiver(worldBuffer: self.worldBuffer) + } + eventQueue.sendingEvents = [] + } + + func applyCommandsEventQueue(eventOfType: T.Type) { + let eventQueue = self.worldBuffer.eventBuffer.commandsEventQueue(eventOfType: T.self)! + eventQueue.sendingEvents = eventQueue.eventQueue + eventQueue.eventQueue = [] + for event in eventQueue.sendingEvents { + for system in self.worldBuffer.systemBuffer.systems(ofType: EventSystemExecute.self) { + system.receive(event: EventReader(value: event), worldBuffer: self.worldBuffer) + } + } + eventQueue.sendingEvents = [] + } +} + +public extension World { + func sendEvent(_ value: T) { + self.worldBuffer.eventBuffer.eventWriter(eventOfType: T.self)?.send(value: value) + } +} diff --git a/Sources/ECS/FilterdQuery/FIlteredQuery.swift b/Sources/ECS/FilterdQuery/FIlteredQuery.swift new file mode 100644 index 0000000..3585fda --- /dev/null +++ b/Sources/ECS/FilterdQuery/FIlteredQuery.swift @@ -0,0 +1,32 @@ +// +// FilteredQuery.swift +// +// +// Created by rrbox on 2023/09/17. +// + +final public class Filtered: Chunk, SystemParameter { + public let query: Q = Q() + + override func spawn(entity: Entity, entityRecord: EntityRecord) { + guard F.condition(forEntityRecord: entityRecord) else { return } + self.query.spawn(entity: entity, entityRecord: entityRecord) + } + + override func despawn(entity: Entity) { + self.query.despawn(entity: entity) + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> Filtered? { + worldBuffer.chunkBuffer.chunk(ofType: Filtered.self) + } + + public static func register(to worldBuffer: WorldBuffer) { + guard worldBuffer.chunkBuffer.chunk(ofType: Self.self) == nil else { + return + } + + worldBuffer.chunkBuffer.addChunk(Filtered()) + } + +} diff --git a/Sources/ECS/FilterdQuery/Filter.swift b/Sources/ECS/FilterdQuery/Filter.swift new file mode 100644 index 0000000..fd097c5 --- /dev/null +++ b/Sources/ECS/FilterdQuery/Filter.swift @@ -0,0 +1,34 @@ +// +// Filter.swift +// +// +// Created by rrbox on 2023/09/17. +// + +public protocol Filter { + static func condition(forEntityRecord entityRecord: EntityRecord) -> Bool +} + +public struct With: Filter { + public static func condition(forEntityRecord entityRecord: EntityRecord) -> Bool { + entityRecord.component(ofType: ComponentRef.self) != nil + } +} + +public struct WithOut: Filter { + public static func condition(forEntityRecord entityRecord: EntityRecord) -> Bool { + entityRecord.component(ofType: ComponentRef.self) == nil + } +} + +public struct And: Filter { + public static func condition(forEntityRecord entityRecord: EntityRecord) -> Bool { + T.condition(forEntityRecord: entityRecord) && U.condition(forEntityRecord: entityRecord) + } +} + +public struct Or: Filter { + public static func condition(forEntityRecord entityRecord: EntityRecord) -> Bool { + T.condition(forEntityRecord: entityRecord) || U.condition(forEntityRecord: entityRecord) + } +} diff --git a/Sources/ECS/FilterdQuery/QueryProtocol.swift b/Sources/ECS/FilterdQuery/QueryProtocol.swift new file mode 100644 index 0000000..56d421d --- /dev/null +++ b/Sources/ECS/FilterdQuery/QueryProtocol.swift @@ -0,0 +1,18 @@ +// +// QueryProtocol.swift +// +// +// Created by rrbox on 2023/09/17. +// + +public protocol QueryProtocol { + func spawn(entity: Entity, entityRecord: EntityRecord) + func despawn(entity: Entity) + init() +} + +extension Query: QueryProtocol {} +extension Query2: QueryProtocol {} +extension Query3: QueryProtocol {} +extension Query4: QueryProtocol {} +extension Query5: QueryProtocol {} diff --git a/Sources/ECS/Query/Query.swift b/Sources/ECS/Query/Query.swift index 307b616..06cdc1e 100644 --- a/Sources/ECS/Query/Query.swift +++ b/Sources/ECS/Query/Query.swift @@ -1,8 +1,61 @@ // -// File.swift +// Query.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +final public class Query: Chunk, SystemParameter { + var components = [Entity: ComponentRef]() + + public override init() {} + + public override func spawn(entity: Entity, entityRecord: EntityRecord) { + guard let componentRef = entityRecord.component(ofType: ComponentRef.self) else { return } + self.components[entity] = componentRef + } + + public override func despawn(entity: Entity) { + self.components.removeValue(forKey: entity) + } + + override func applyCurrentState(_ entityRecord: EntityRecord, forEntity entity: Entity) { + guard let componentRef = entityRecord.component(ofType: ComponentRef.self) else { + self.components.removeValue(forKey: entity) + return + } + self.components[entity] = componentRef + } + + /// Query で指定した Component を持つ entity を world から取得し, イテレーションします. + public func update(_ execute: (Entity, inout ComponentType) -> ()) { + for (entity, _) in self.components { + execute(entity, &self.components[entity]!.value) + } + } + + public func update(_ entity: Entity, _ execute: (inout ComponentType) -> ()) { + guard let componentRef = self.components[entity] else { return } + execute(&componentRef.value) + } + + public func components(forEntity entity: Entity) -> ComponentType? { + self.components[entity]?.value + } + + public static func register(to worldBuffer: WorldBuffer) { + guard worldBuffer.chunkBuffer.chunk(ofType: Self.self) == nil else { + return + } + + let queryRegistory = Self() + + worldBuffer.chunkBuffer.addChunk(queryRegistory) + + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> Self? { + worldBuffer.chunkBuffer.chunk(ofType: Self.self) + } + +} diff --git a/Sources/ECS/Query/Query2.swift b/Sources/ECS/Query/Query2.swift index 307b616..dd06735 100644 --- a/Sources/ECS/Query/Query2.swift +++ b/Sources/ECS/Query/Query2.swift @@ -1,8 +1,67 @@ // -// File.swift +// Query2.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +final public class Query2: Chunk, SystemParameter { + var components = [Entity: (ComponentRef, ComponentRef)]() + var executes: [(Entity, inout C0, inout C1) -> ()] = [] + + public override init() {} + + public override func spawn(entity: Entity, entityRecord: EntityRecord) { + guard let c0 = entityRecord.component(ofType: ComponentRef.self), + let c1 = entityRecord.component(ofType: ComponentRef.self) else { return } + self.components[entity] = (c0, c1) + } + + public override func despawn(entity: Entity) { + self.components.removeValue(forKey: entity) + } + + override func applyCurrentState(_ entityRecord: EntityRecord, forEntity entity: Entity) { + guard let c0 = entityRecord.component(ofType: ComponentRef.self), + let c1 = entityRecord.component(ofType: ComponentRef.self) else { + self.components.removeValue(forKey: entity) + return + } + self.components[entity] = (c0, c1) + + } + + /// Query で指定した Component を持つ entity を world から取得し, イテレーションします. + public func update(_ execute: (Entity, inout C0, inout C1) -> ()) { + for (entity, _) in self.components { + execute(entity, &self.components[entity]!.0.value, &self.components[entity]!.1.value) + } + } + + public func update(_ entity: Entity, _ execute: (inout C0, inout C1) -> ()) { + guard let group = self.components[entity] else { return } + execute(&group.0.value, &group.1.value) + } + + public func components(forEntity entity: Entity) -> (C0, C1)? { + guard let references = components[entity] else { return nil } + return (references.0.value, references.1.value) + } + + + public static func register(to worldBuffer: WorldBuffer) { + guard worldBuffer.chunkBuffer.chunk(ofType: Self.self) == nil else { + return + } + + let queryRegistory = Self() + + worldBuffer.chunkBuffer.addChunk(queryRegistory) + + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> Self? { + worldBuffer.chunkBuffer.chunk(ofType: Self.self) + } + +} diff --git a/Sources/ECS/Query/Query3.swift b/Sources/ECS/Query/Query3.swift index 307b616..9096dbe 100644 --- a/Sources/ECS/Query/Query3.swift +++ b/Sources/ECS/Query/Query3.swift @@ -1,8 +1,66 @@ // -// File.swift +// Query3.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +final public class Query3: Chunk, SystemParameter { + var components = [Entity: (ComponentRef, ComponentRef, ComponentRef)]() + + public override init() {} + + public override func spawn(entity: Entity, entityRecord: EntityRecord) { + guard let c0 = entityRecord.component(ofType: ComponentRef.self), + let c1 = entityRecord.component(ofType: ComponentRef.self), + let c2 = entityRecord.component(ofType: ComponentRef.self) else { return } + self.components[entity] = (c0, c1, c2) + } + + public override func despawn(entity: Entity) { + self.components.removeValue(forKey: entity) + } + + override func applyCurrentState(_ entityRecord: EntityRecord, forEntity entity: Entity) { + guard let c0 = entityRecord.component(ofType: ComponentRef.self), + let c1 = entityRecord.component(ofType: ComponentRef.self), + let c2 = entityRecord.component(ofType: ComponentRef.self) else { + self.components.removeValue(forKey: entity) + return + } + self.components[entity] = (c0, c1, c2) + } + + /// Query で指定した Component を持つ entity を world から取得し, イテレーションします. + public func update(_ execute: (Entity, inout C0, inout C1, inout C2) -> ()) { + for (entity, _) in self.components { + execute(entity, &self.components[entity]!.0.value, &self.components[entity]!.1.value, &self.components[entity]!.2.value) + } + } + + public func update(_ entity: Entity, _ execute: (inout C0, inout C1, inout C2) -> ()) { + guard let group = self.components[entity] else { return } + execute(&group.0.value, &group.1.value, &group.2.value) + } + + public func components(forEntity entity: Entity) -> (C0, C1, C2)? { + guard let references = components[entity] else { return nil } + return (references.0.value, references.1.value, references.2.value) + } + + public static func register(to worldBuffer: WorldBuffer) { + guard worldBuffer.chunkBuffer.chunk(ofType: Self.self) == nil else { + return + } + + let queryRegistory = Self() + + worldBuffer.chunkBuffer.addChunk(queryRegistory) + + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> Self? { + worldBuffer.chunkBuffer.chunk(ofType: Self.self) + } + +} diff --git a/Sources/ECS/Query/Query4.swift b/Sources/ECS/Query/Query4.swift index 307b616..7c7dcaa 100644 --- a/Sources/ECS/Query/Query4.swift +++ b/Sources/ECS/Query/Query4.swift @@ -1,8 +1,68 @@ // -// File.swift +// Query4.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +final public class Query4: Chunk, SystemParameter { + var components = [Entity: (ComponentRef, ComponentRef, ComponentRef, ComponentRef)]() + + public override init() {} + + public override func spawn(entity: Entity, entityRecord: EntityRecord) { + guard let c0 = entityRecord.component(ofType: ComponentRef.self), + let c1 = entityRecord.component(ofType: ComponentRef.self), + let c2 = entityRecord.component(ofType: ComponentRef.self), + let c3 = entityRecord.component(ofType: ComponentRef.self) else { return } + self.components[entity] = (c0, c1, c2, c3) + } + + public override func despawn(entity: Entity) { + self.components.removeValue(forKey: entity) + } + + override func applyCurrentState(_ entityRecord: EntityRecord, forEntity entity: Entity) { + guard let c0 = entityRecord.component(ofType: ComponentRef.self), + let c1 = entityRecord.component(ofType: ComponentRef.self), + let c2 = entityRecord.component(ofType: ComponentRef.self), + let c3 = entityRecord.component(ofType: ComponentRef.self) else { + self.components.removeValue(forKey: entity) + return + } + self.components[entity] = (c0, c1, c2, c3) + } + + /// Query で指定した Component を持つ entity を world から取得し, イテレーションします. + public func update(_ execute: (Entity, inout C0, inout C1, inout C2, inout C3) -> ()) { + for (entity, _) in self.components { + execute(entity, &self.components[entity]!.0.value, &self.components[entity]!.1.value, &self.components[entity]!.2.value, &self.components[entity]!.3.value) + } + } + + public func update(_ entity: Entity, _ execute: (inout C0, inout C1, inout C2, inout C3) -> ()) { + guard let group = self.components[entity] else { return } + execute(&group.0.value, &group.1.value, &group.2.value, &group.3.value) + } + + public func components(forEntity entity: Entity) -> (C0, C1, C2, C3)? { + guard let references = components[entity] else { return nil } + return (references.0.value, references.1.value, references.2.value, references.3.value) + } + + public static func register(to worldBuffer: WorldBuffer) { + guard worldBuffer.chunkBuffer.chunk(ofType: Self.self) == nil else { + return + } + + let queryRegistory = Self() + + worldBuffer.chunkBuffer.addChunk(queryRegistory) + + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> Self? { + worldBuffer.chunkBuffer.chunk(ofType: Self.self) + } + +} diff --git a/Sources/ECS/Query/Query5.swift b/Sources/ECS/Query/Query5.swift index 307b616..7be6c1c 100644 --- a/Sources/ECS/Query/Query5.swift +++ b/Sources/ECS/Query/Query5.swift @@ -1,8 +1,70 @@ // -// File.swift +// Query5.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +final public class Query5: Chunk, SystemParameter { + var components = [Entity: (ComponentRef, ComponentRef, ComponentRef, ComponentRef, ComponentRef)]() + + public override init() {} + + public override func spawn(entity: Entity, entityRecord: EntityRecord) { + guard let c0 = entityRecord.component(ofType: ComponentRef.self), + let c1 = entityRecord.component(ofType: ComponentRef.self), + let c2 = entityRecord.component(ofType: ComponentRef.self), + let c3 = entityRecord.component(ofType: ComponentRef.self), + let c4 = entityRecord.component(ofType: ComponentRef.self) else { return } + self.components[entity] = (c0, c1, c2, c3, c4) + } + + public override func despawn(entity: Entity) { + self.components.removeValue(forKey: entity) + } + + override func applyCurrentState(_ entityRecord: EntityRecord, forEntity entity: Entity) { + guard let c0 = entityRecord.component(ofType: ComponentRef.self), + let c1 = entityRecord.component(ofType: ComponentRef.self), + let c2 = entityRecord.component(ofType: ComponentRef.self), + let c3 = entityRecord.component(ofType: ComponentRef.self), + let c4 = entityRecord.component(ofType: ComponentRef.self) else { + self.components.removeValue(forKey: entity) + return + } + self.components[entity] = (c0, c1, c2, c3, c4) + } + + /// Query で指定した Component を持つ entity を world から取得し, イテレーションします. + public func update(_ execute: (Entity, inout C0, inout C1, inout C2, inout C3, inout C4) -> ()) { + for (entity, _) in self.components { + execute(entity, &self.components[entity]!.0.value, &self.components[entity]!.1.value, &self.components[entity]!.2.value, &self.components[entity]!.3.value, &self.components[entity]!.4.value) + } + } + + public func update(_ entity: Entity, _ execute: (inout C0, inout C1, inout C2, inout C3, inout C4) -> ()) { + guard let group = self.components[entity] else { return } + execute(&group.0.value, &group.1.value, &group.2.value, &group.3.value, &group.4.value) + } + + public func components(forEntity entity: Entity) -> (C0, C1, C2, C3, C4)? { + guard let references = components[entity] else { return nil } + return (references.0.value, references.1.value, references.2.value, references.3.value, references.4.value) + } + + public static func register(to worldBuffer: WorldBuffer) { + guard worldBuffer.chunkBuffer.chunk(ofType: Self.self) == nil else { + return + } + + let queryRegistory = Self() + + worldBuffer.chunkBuffer.addChunk(queryRegistory) + + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> Self? { + worldBuffer.chunkBuffer.chunk(ofType: Self.self) + } + +} diff --git a/Sources/ECS/Resource/Commands+Resource.swift b/Sources/ECS/Resource/Commands+Resource.swift index 307b616..b3d9296 100644 --- a/Sources/ECS/Resource/Commands+Resource.swift +++ b/Sources/ECS/Resource/Commands+Resource.swift @@ -1,8 +1,28 @@ // -// File.swift +// Commands+Resource.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +class AddResource: Command { + let resrouce: T + + init(resrouce: T) { + self.resrouce = resrouce + } + + override func runCommand(in world: World) { + world.addResource(self.resrouce) + } +} + +public extension Commands { + /// world に対して resource を追加します. + /// + /// resource はフレームの終了直前に追加されます. + @discardableResult func addResource(_ resource: T) -> Commands { + self.push(command: AddResource(resrouce: resource)) + return self + } +} diff --git a/Sources/ECS/Resource/Resource.swift b/Sources/ECS/Resource/Resource.swift index 307b616..506c274 100644 --- a/Sources/ECS/Resource/Resource.swift +++ b/Sources/ECS/Resource/Resource.swift @@ -1,8 +1,34 @@ // -// File.swift +// Resource.swift // // // Created by rrbox on 2023/08/12. // import Foundation + +public protocol ResourceProtocol { + +} + +final public class Resource: BufferElement, SystemParameter { + public var resource: T + + init(_ resource: T) { + self.resource = resource + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public static func register(to worldBuffer: WorldBuffer) { + + } + + public static func getParameter(from worldBuffer: WorldBuffer) -> Resource? { + worldBuffer.resourceBuffer.resource(ofType: T.self) + } + +} diff --git a/Sources/ECS/Resource/ResourceBuffer.swift b/Sources/ECS/Resource/ResourceBuffer.swift index 307b616..c31ef2d 100644 --- a/Sources/ECS/Resource/ResourceBuffer.swift +++ b/Sources/ECS/Resource/ResourceBuffer.swift @@ -1,8 +1,27 @@ // -// File.swift +// ResourceBuffer.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +final public class ResourceBuffer { + let buffer: Buffer + init(buffer: Buffer) { + self.buffer = buffer + } + + func addResource(_ resource: T) { + self.buffer.addComponent(Resource(resource)) + } + + public func resource(ofType type: T.Type) -> Resource? { + self.buffer.component(ofType: Resource.self) + } +} + +public extension WorldBuffer { + var resourceBuffer: ResourceBuffer { + ResourceBuffer(buffer: self) + } +} diff --git a/Sources/ECS/Resource/World+Resource.swift b/Sources/ECS/Resource/World+Resource.swift index 307b616..ae39bad 100644 --- a/Sources/ECS/Resource/World+Resource.swift +++ b/Sources/ECS/Resource/World+Resource.swift @@ -1,8 +1,16 @@ // -// File.swift +// World+Resource.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +public extension World { + /// world に resource を追加します. + /// + /// ``Commands/addResource(_:)`` とは異なり, resource はこのメソッドが実行されてすぐに追加されます. + @discardableResult func addResource(_ resource: T) -> World { + self.worldBuffer.resourceBuffer.addResource(resource) + return self + } +} diff --git a/Sources/ECS/SetUpSystem/SetUpSystem.swift b/Sources/ECS/SetUpSystem/SetUpSystem.swift new file mode 100644 index 0000000..af91e40 --- /dev/null +++ b/Sources/ECS/SetUpSystem/SetUpSystem.swift @@ -0,0 +1,34 @@ +// +// SetUpSystem.swift +// +// +// Created by rrbox on 2023/08/12. +// + +public protocol SetUpSystemProtocol: SystemProtocol, SetUpExecute { + +} + +final public class SetUpSystem: SetUpExecute, SetUpSystemProtocol { + let execute: (Parameter) -> () + + public init(_ execute: @escaping (Parameter) -> ()) { + self.execute = execute + } + + override func setUp(worldBuffer: WorldBuffer) { + self.execute(Parameter.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addSetUpSystem(_ system: System) -> World { + self.addSystem(system, as: SetUpExecute.self) + System.Parameter.register(to: self.worldBuffer) + return self + } + + @discardableResult func addSetUpSystem(_ execute: @escaping (Parameter) -> ()) -> World { + self.addSetUpSystem(SetUpSystem(execute)) + } +} diff --git a/Sources/ECS/SetUpSystem/SetUpSystem2.swift b/Sources/ECS/SetUpSystem/SetUpSystem2.swift new file mode 100644 index 0000000..c20287c --- /dev/null +++ b/Sources/ECS/SetUpSystem/SetUpSystem2.swift @@ -0,0 +1,37 @@ +// +// SetUpSystem2.swift +// +// +// Created by rrbox on 2023/08/12. +// + +public protocol SetUpSystemProtocol2: SystemProtocol2, SetUpExecute { + +} + +final public class SetUpSystem2: SetUpExecute, SetUpSystemProtocol2 { + let execute: (P0, P1) -> () + + public init(_ execute: @escaping (P0, P1) -> ()) { + self.execute = execute + } + + override func setUp(worldBuffer: WorldBuffer) { + self.execute( + P0.getParameter(from: worldBuffer)!, + P1.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addSetUpSystem(_ system: System) -> World { + self.addSystem(system, as: SetUpExecute.self) + System.P0.register(to: self.worldBuffer) + System.P1.register(to: self.worldBuffer) + return self + } + + @discardableResult func addSetUpSystem(_ execute: @escaping (P0, P1) -> ()) -> World { + self.addSetUpSystem(SetUpSystem2(execute)) + } +} diff --git a/Sources/ECS/SetUpSystem/SetUpSystem3.swift b/Sources/ECS/SetUpSystem/SetUpSystem3.swift new file mode 100644 index 0000000..ad99ea5 --- /dev/null +++ b/Sources/ECS/SetUpSystem/SetUpSystem3.swift @@ -0,0 +1,39 @@ +// +// SetUpSystem3.swift +// +// +// Created by rrbox on 2023/08/12. +// + +public protocol SetUpSystemProtocol3: SystemProtocol3, SetUpExecute { + +} + +final public class SetUpSystem3: SetUpExecute, SetUpSystemProtocol3 { + let execute: (P0, P1, P2) -> () + + public init(_ execute: @escaping (P0, P1, P2) -> ()) { + self.execute = execute + } + + override func setUp(worldBuffer: WorldBuffer) { + self.execute( + P0.getParameter(from: worldBuffer)!, + P1.getParameter(from: worldBuffer)!, + P2.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addSetUpSystem(_ system: System) -> World { + self.addSystem(system, as: SetUpExecute.self) + System.P0.register(to: self.worldBuffer) + System.P1.register(to: self.worldBuffer) + System.P2.register(to: self.worldBuffer) + return self + } + + @discardableResult func addSetUpSystem(_ execute: @escaping (P0, P1, P2) -> ()) -> World { + self.addSetUpSystem(SetUpSystem3(execute)) + } +} diff --git a/Sources/ECS/SetUpSystem/SetUpSystem4.swift b/Sources/ECS/SetUpSystem/SetUpSystem4.swift new file mode 100644 index 0000000..bdb1cde --- /dev/null +++ b/Sources/ECS/SetUpSystem/SetUpSystem4.swift @@ -0,0 +1,41 @@ +// +// SetUpSystem4.swift +// +// +// Created by rrbox on 2023/08/12. +// + +public protocol SetUpSystemProtocol4: SystemProtocol4, SetUpExecute { + +} + +final public class SetUpSystem4: SetUpExecute, SetUpSystemProtocol4 { + let execute: (P0, P1, P2, P3) -> () + + public init(_ execute: @escaping (P0, P1, P2, P3) -> ()) { + self.execute = execute + } + + override func setUp(worldBuffer: WorldBuffer) { + self.execute( + P0.getParameter(from: worldBuffer)!, + P1.getParameter(from: worldBuffer)!, + P2.getParameter(from: worldBuffer)!, + P3.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addSetUpSystem(_ system: System) -> World { + self.addSystem(system, as: SetUpExecute.self) + System.P0.register(to: self.worldBuffer) + System.P1.register(to: self.worldBuffer) + System.P2.register(to: self.worldBuffer) + System.P3.register(to: self.worldBuffer) + return self + } + + @discardableResult func addSetUpSystem(_ execute: @escaping (P0, P1, P2, P3) -> ()) -> World { + self.addSetUpSystem(SetUpSystem4(execute)) + } +} diff --git a/Sources/ECS/SetUpSystem/SetUpSystem5.swift b/Sources/ECS/SetUpSystem/SetUpSystem5.swift new file mode 100644 index 0000000..8e7868f --- /dev/null +++ b/Sources/ECS/SetUpSystem/SetUpSystem5.swift @@ -0,0 +1,43 @@ +// +// SetUpSystem5.swift +// +// +// Created by rrbox on 2023/08/12. +// + +public protocol SetUpSystemProtocol5: SystemProtocol5, SetUpExecute { + +} + +final public class SetUpSystem5: SetUpExecute, SetUpSystemProtocol5 { + let execute: (P0, P1, P2, P3, P4) -> () + + public init(_ execute: @escaping (P0, P1, P2, P3, P4) -> ()) { + self.execute = execute + } + + override func setUp(worldBuffer: WorldBuffer) { + self.execute( + P0.getParameter(from: worldBuffer)!, + P1.getParameter(from: worldBuffer)!, + P2.getParameter(from: worldBuffer)!, + P3.getParameter(from: worldBuffer)!, + P4.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addSetUpSystem(_ system: System) -> World { + self.addSystem(system, as: SetUpExecute.self) + System.P0.register(to: self.worldBuffer) + System.P1.register(to: self.worldBuffer) + System.P2.register(to: self.worldBuffer) + System.P3.register(to: self.worldBuffer) + System.P4.register(to: self.worldBuffer) + return self + } + + @discardableResult func addSetUpSystem(_ execute: @escaping (P0, P1, P2, P3, P4) -> ()) -> World { + self.addSetUpSystem(SetUpSystem5(execute)) + } +} diff --git a/Sources/ECS/SetUpSystem/SetUpSystemCommons.swift b/Sources/ECS/SetUpSystem/SetUpSystemCommons.swift new file mode 100644 index 0000000..19b3467 --- /dev/null +++ b/Sources/ECS/SetUpSystem/SetUpSystemCommons.swift @@ -0,0 +1,14 @@ +// +// SetUpSystemCommons.swift +// +// +// Created by rrbox on 2023/08/12. +// + +public protocol SetUpSystemParameter: SystemParameter { + +} + +public class SetUpExecute: SystemExecute { + func setUp(worldBuffer: WorldBuffer) {} +} diff --git a/Sources/ECS/SetUpSystem/SetUpsystemParameters.swift b/Sources/ECS/SetUpSystem/SetUpsystemParameters.swift new file mode 100644 index 0000000..4776cec --- /dev/null +++ b/Sources/ECS/SetUpSystem/SetUpsystemParameters.swift @@ -0,0 +1,14 @@ +// +// SetUpsystemParameters.swift +// +// +// Created by rrbox on 2023/08/12. +// + +extension Commands: SetUpSystemParameter { + +} + +extension Resource: SetUpSystemParameter { + +} diff --git a/Sources/ECS/Chunk/AddChunk.swift b/Sources/ECS/States/StateRegistry.swift similarity index 57% rename from Sources/ECS/Chunk/AddChunk.swift rename to Sources/ECS/States/StateRegistry.swift index 175c602..c482cd5 100644 --- a/Sources/ECS/Chunk/AddChunk.swift +++ b/Sources/ECS/States/StateRegistry.swift @@ -2,7 +2,7 @@ // File.swift // // -// Created by rrbox on 2023/08/11. +// Created by rrbox on 2023/10/15. // import Foundation diff --git a/Sources/ECS/States/StateSystemBuffer.swift b/Sources/ECS/States/StateSystemBuffer.swift new file mode 100644 index 0000000..253a29b --- /dev/null +++ b/Sources/ECS/States/StateSystemBuffer.swift @@ -0,0 +1,39 @@ +// +// State.swift +// +// +// Created by rrbox on 2023/10/15. +// + +class StateDependentSystemBuffer { + class StateSystemRegistry: BufferElement { + var stateSystemMap: [State: [System]] = [:] + } + + let buffer: Buffer + init(buffer: Buffer) { + self.buffer = buffer + } + + public func systems( + ofType: System.Type, + forState state: State + ) -> [System] { + self.buffer.component(ofType: StateSystemRegistry.self)!.stateSystemMap[state]! + } + + public func addSystem( + _ system: System, + as: System.Type, + forState state: State + ) { + let registry = self.buffer.component(ofType: StateSystemRegistry.self)! + if registry.stateSystemMap[state] == nil { + registry.stateSystemMap[state] = [system] + } else { + registry.stateSystemMap[state]?.append(system) + } + } + + +} diff --git a/Sources/ECS/UpdateSystem/UpdateSystemProtocols.swift b/Sources/ECS/States/World+States.swift similarity index 57% rename from Sources/ECS/UpdateSystem/UpdateSystemProtocols.swift rename to Sources/ECS/States/World+States.swift index 307b616..c482cd5 100644 --- a/Sources/ECS/UpdateSystem/UpdateSystemProtocols.swift +++ b/Sources/ECS/States/World+States.swift @@ -2,7 +2,7 @@ // File.swift // // -// Created by rrbox on 2023/08/12. +// Created by rrbox on 2023/10/15. // import Foundation diff --git a/Sources/ECS/System/SystemBuffer.swift b/Sources/ECS/System/SystemBuffer.swift index 8cc7f7b..bd8e48e 100644 --- a/Sources/ECS/System/SystemBuffer.swift +++ b/Sources/ECS/System/SystemBuffer.swift @@ -6,31 +6,37 @@ // open class SystemExecute { - -} - -final class SystemRegisotry: BufferElement { - var systems = [Execute]() + public init() {} } -class SystemBuffer { +final public class SystemBuffer { + final class SystemRegisotry: BufferElement { + var systems = [Execute]() + } + let buffer: Buffer init(buffer: Buffer) { self.buffer = buffer } - func plugInSystems(ofType: System.Type) -> [System] { + public func systems(ofType: System.Type) -> [System] { self.buffer.component(ofType: SystemRegisotry.self)!.systems } - func registerPlugIn(ofType type: System.Type) { + func registerSystemRegistry(ofType type: System.Type) { self.buffer.addComponent(SystemRegisotry.init()) } - func addPlugInSystem(_ system: System, as type: System.Type) { + func addSystem(_ system: System, as type: System.Type) { self.buffer .component(ofType: SystemRegisotry.self)! .systems .append(system) } } + +public extension WorldBuffer { + var systemBuffer: SystemBuffer { + SystemBuffer(buffer: self) + } +} diff --git a/Sources/ECS/System/SystemProtocols.swift b/Sources/ECS/System/SystemProtocols.swift new file mode 100644 index 0000000..b6590d2 --- /dev/null +++ b/Sources/ECS/System/SystemProtocols.swift @@ -0,0 +1,36 @@ +// +// 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 +} diff --git a/Sources/ECS/System/World+SystemBuffer.swift b/Sources/ECS/System/World+SystemBuffer.swift index 307b616..e1e7202 100644 --- a/Sources/ECS/System/World+SystemBuffer.swift +++ b/Sources/ECS/System/World+SystemBuffer.swift @@ -1,8 +1,21 @@ // -// File.swift +// World+SystemBuffer.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +public extension World { + @discardableResult func registerSystemRegistry(ofType type: System.Type) -> World { + self.worldBuffer.systemBuffer.registerSystemRegistry(ofType: System.self) + return self + } + + /// World に system を追加します. + /// + /// system を追加する前に, ``World/registerSystemRegistry(ofType:)`` で system を保持するためのメモリ領域を確保する必要があります. + @discardableResult func addSystem(_ system: System, as type: System.Type) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: System.self) + return self + } +} diff --git a/Sources/ECS/SystemParameter/SystemParameter.swift b/Sources/ECS/SystemParameter/SystemParameter.swift index e94d427..d05c5c4 100644 --- a/Sources/ECS/SystemParameter/SystemParameter.swift +++ b/Sources/ECS/SystemParameter/SystemParameter.swift @@ -1,8 +1,12 @@ // -// File.swift +// SystemParameter.swift // // // Created by rrbox on 2023/08/10. // -import Foundation +public protocol SystemParameter: AnyObject { + static func register(to worldBuffer: WorldBuffer) + static func getParameter(from worldBuffer: WorldBuffer) -> Self? +} + diff --git a/Sources/ECS/UpdateSystem/Resources.swift b/Sources/ECS/UpdateSystem/Resources.swift deleted file mode 100644 index 307b616..0000000 --- a/Sources/ECS/UpdateSystem/Resources.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// File.swift -// -// -// Created by rrbox on 2023/08/12. -// - -import Foundation diff --git a/Sources/ECS/UpdateSystem/UpdateExecute.swift b/Sources/ECS/UpdateSystem/UpdateExecute.swift index 307b616..3d8f600 100644 --- a/Sources/ECS/UpdateSystem/UpdateExecute.swift +++ b/Sources/ECS/UpdateSystem/UpdateExecute.swift @@ -1,8 +1,12 @@ // -// File.swift +// UpdateExecute.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +public class UpdateExecute: SystemExecute { + func update(worldBuffer: WorldBuffer) { + + } +} diff --git a/Sources/ECS/UpdateSystem/UpdateSystem.swift b/Sources/ECS/UpdateSystem/UpdateSystem.swift index 307b616..c720e65 100644 --- a/Sources/ECS/UpdateSystem/UpdateSystem.swift +++ b/Sources/ECS/UpdateSystem/UpdateSystem.swift @@ -1,8 +1,34 @@ // -// File.swift +// UpdateSystem.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +public protocol UpdateSystemProtocol: SystemProtocol, UpdateExecute { + +} + +final public class UpdateSystem: UpdateExecute, UpdateSystemProtocol { + let execute: (Parameter) -> () + + public init(_ execute: @escaping (Parameter) -> Void) { + self.execute = execute + } + + override func update(worldBuffer: WorldBuffer) { + self.execute(Parameter.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addUpdateSystem(_ system: System) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: UpdateExecute.self) + System.Parameter.register(to: self.worldBuffer) + return self + } + + @discardableResult func addUpdateSystem(_ execute: @escaping (Parameter) -> ()) -> World { + self.addUpdateSystem(UpdateSystem(execute)) + } +} diff --git a/Sources/ECS/UpdateSystem/UpdateSystem2.swift b/Sources/ECS/UpdateSystem/UpdateSystem2.swift index 307b616..a89a5ce 100644 --- a/Sources/ECS/UpdateSystem/UpdateSystem2.swift +++ b/Sources/ECS/UpdateSystem/UpdateSystem2.swift @@ -1,8 +1,35 @@ // -// File.swift +// UpdateSystem2.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +public protocol UpdateSystemProtocol2: SystemProtocol2, UpdateExecute { + +} + +final public class UpdateSystem2: UpdateExecute, UpdateSystemProtocol2 { + let execute: (P0, P1) -> () + + public init(_ execute: @escaping (P0, P1) -> Void) { + self.execute = execute + } + + override func update(worldBuffer: WorldBuffer) { + self.execute(P0.getParameter(from: worldBuffer)!, P1.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addUpdateSystem(_ system: System) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: UpdateExecute.self) + System.P0.register(to: self.worldBuffer) + System.P1.register(to: self.worldBuffer) + return self + } + + @discardableResult func addUpdateSystem(_ execute: @escaping (P0, P1) -> ()) -> World { + self.addUpdateSystem(UpdateSystem2(execute)) + } +} diff --git a/Sources/ECS/UpdateSystem/UpdateSystem3.swift b/Sources/ECS/UpdateSystem/UpdateSystem3.swift index 307b616..e0fc9ae 100644 --- a/Sources/ECS/UpdateSystem/UpdateSystem3.swift +++ b/Sources/ECS/UpdateSystem/UpdateSystem3.swift @@ -1,8 +1,36 @@ // -// File.swift +// UpdateSystem3.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +public protocol UpdateSystemProtocol3: SystemProtocol3, UpdateExecute { + +} + +final public class UpdateSystem3: UpdateExecute, UpdateSystemProtocol3 { + let execute: (P0, P1, P2) -> () + + public init(_ execute: @escaping (P0, P1, P2) -> Void) { + self.execute = execute + } + + override func update(worldBuffer: WorldBuffer) { + self.execute(P0.getParameter(from: worldBuffer)!, P1.getParameter(from: worldBuffer)!, P2.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addUpdateSystem(_ system: System) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: UpdateExecute.self) + System.P0.register(to: self.worldBuffer) + System.P1.register(to: self.worldBuffer) + System.P2.register(to: self.worldBuffer) + return self + } + + @discardableResult func addUpdateSystem(_ execute: @escaping (P0, P1, P2) -> ()) -> World { + self.addUpdateSystem(UpdateSystem3(execute)) + } +} diff --git a/Sources/ECS/UpdateSystem/UpdateSystem4.swift b/Sources/ECS/UpdateSystem/UpdateSystem4.swift index 307b616..2af6729 100644 --- a/Sources/ECS/UpdateSystem/UpdateSystem4.swift +++ b/Sources/ECS/UpdateSystem/UpdateSystem4.swift @@ -1,8 +1,37 @@ // -// File.swift +// UpdateSystem4.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +public protocol UpdateSystemProtocol4: SystemProtocol4, UpdateExecute { + +} + +final public class UpdateSystem4: UpdateExecute, UpdateSystemProtocol4 { + let execute: (P0, P1, P2, P3) -> () + + public init(_ execute: @escaping (P0, P1, P2, P3) -> Void) { + self.execute = execute + } + + override func update(worldBuffer: WorldBuffer) { + self.execute(P0.getParameter(from: worldBuffer)!, P1.getParameter(from: worldBuffer)!, P2.getParameter(from: worldBuffer)!, P3.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addUpdateSystem(_ system: System) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: UpdateExecute.self) + System.P0.register(to: self.worldBuffer) + System.P1.register(to: self.worldBuffer) + System.P2.register(to: self.worldBuffer) + System.P3.register(to: self.worldBuffer) + return self + } + + @discardableResult func addUpdateSystem(_ execute: @escaping (P0, P1, P2, P3) -> ()) -> World { + self.addUpdateSystem(UpdateSystem4(execute)) + } +} diff --git a/Sources/ECS/UpdateSystem/UpdateSystem5.swift b/Sources/ECS/UpdateSystem/UpdateSystem5.swift index 307b616..d683e71 100644 --- a/Sources/ECS/UpdateSystem/UpdateSystem5.swift +++ b/Sources/ECS/UpdateSystem/UpdateSystem5.swift @@ -1,8 +1,39 @@ // -// File.swift +// UpdateSystem5.swift // // // Created by rrbox on 2023/08/12. // -import Foundation +public protocol UpdateSystemProtocol5: SystemProtocol5, UpdateExecute { + +} + +final public class UpdateSystem5: UpdateExecute, UpdateSystemProtocol5 { + let execute: (P0, P1, P2, P3, P4) -> () + + public init(_ execute: @escaping (P0, P1, P2, P3, P4) -> Void) { + self.execute = execute + } + + override func update(worldBuffer: WorldBuffer) { + self.execute(P0.getParameter(from: worldBuffer)!, P1.getParameter(from: worldBuffer)!, P2.getParameter(from: worldBuffer)!, P3.getParameter(from: worldBuffer)!, P4.getParameter(from: worldBuffer)!) + } +} + +public extension World { + @discardableResult func addUpdateSystem(_ system: System) -> World { + self.worldBuffer.systemBuffer.addSystem(system, as: UpdateExecute.self) + System.P0.register(to: self.worldBuffer) + System.P1.register(to: self.worldBuffer) + System.P2.register(to: self.worldBuffer) + System.P3.register(to: self.worldBuffer) + System.P4.register(to: self.worldBuffer) + return self + } + + @discardableResult func addUpdateSystem(_ execute: @escaping (P0, P1, P2, P3, P4) -> ()) -> World { + self.addUpdateSystem(UpdateSystem5(execute)) + } +} + diff --git a/Sources/ECS/UpdateSystem/UpdateSystemResources.swift b/Sources/ECS/UpdateSystem/UpdateSystemResources.swift new file mode 100644 index 0000000..2a80f1b --- /dev/null +++ b/Sources/ECS/UpdateSystem/UpdateSystemResources.swift @@ -0,0 +1,16 @@ +// +// UpdateSystemResources.swift +// +// +// Created by rrbox on 2023/08/12. +// + +import Foundation + +public struct CurrentTime: ResourceProtocol { + public let value: TimeInterval +} + +public struct DeltaTime: ResourceProtocol { + public let value: TimeInterval +} diff --git a/Sources/ECS/World/World+Entities.swift b/Sources/ECS/World/World+Entities.swift new file mode 100644 index 0000000..3d717a8 --- /dev/null +++ b/Sources/ECS/World/World+Entities.swift @@ -0,0 +1,21 @@ +// +// World+Entities.swift +// +// +// Created by rrbox on 2023/08/18. +// + +public extension World { + func insert(entity: Entity, entityRecord: EntityRecord) { + self.entities.sequence[entity] = entityRecord + } + + func remove(entity: Entity) { + self.entities.sequence.removeValue(forKey: entity) + } + + func entityRecord(forEntity entity: Entity) -> EntityRecord? { + self.entities.sequence[entity] + } + +} diff --git a/Sources/ECS/World/World.swift b/Sources/ECS/World/World.swift index 7bcb18e..7ed0761 100644 --- a/Sources/ECS/World/World.swift +++ b/Sources/ECS/World/World.swift @@ -5,8 +5,19 @@ // Created by rrbox on 2023/08/06. // +public struct Entities { + var sequence: [Entity: EntityRecord] +} + final public class World { - public init() { - + var entities: Entities + public let worldBuffer: WorldBuffer + + init(entities: [Entity: EntityRecord], worldBuffer: WorldBuffer) { + self.entities = Entities(sequence: entities) + self.worldBuffer = worldBuffer } + } + + diff --git a/Sources/ECS/World/WorldBuffer.swift b/Sources/ECS/World/WorldBuffer.swift index de89dee..09b2e81 100644 --- a/Sources/ECS/World/WorldBuffer.swift +++ b/Sources/ECS/World/WorldBuffer.swift @@ -10,4 +10,3 @@ import GameplayKit final public class WorldBuffer: GKEntity { } - diff --git a/Sources/ECS/World/WorldResources.swift b/Sources/ECS/World/WorldResources.swift new file mode 100644 index 0000000..68e6b02 --- /dev/null +++ b/Sources/ECS/World/WorldResources.swift @@ -0,0 +1,10 @@ +// +// WorldResources.swift +// +// +// Created by rrbox on 2023/08/20. +// + +struct EntityCount: ResourceProtocol { + var count: Int +} diff --git a/Sources/ECS/WorldMethods/World+EntityCommands.swift b/Sources/ECS/WorldMethods/World+EntityCommands.swift new file mode 100644 index 0000000..b83e4ad --- /dev/null +++ b/Sources/ECS/WorldMethods/World+EntityCommands.swift @@ -0,0 +1,26 @@ +// +// World+EntityCommands.swift +// +// +// Created by rrbox on 2023/08/14. +// + +extension World { + /// Entity に Component を追加します. + func addComponent(_ component: ComponentType, forEntity entity: Entity) { + let archetype = self.entityRecord(forEntity: entity)! + if let componentRef = archetype.component(ofType: ComponentRef.self) { + componentRef.value = component + } + archetype.addComponent(ComponentRef(component: component)) + self.worldBuffer.chunkBuffer.applyCurrentState(archetype, forEntity: entity) + } + + /// Entity から Component を削除します. + func removeComponent(ofType type: ComponentType.Type, fromEntity entity: Entity) { + let archetype = self.entityRecord(forEntity: entity)! + archetype.removeComponent(ofType: ComponentRef.self) + self.worldBuffer.chunkBuffer.applyCurrentState(archetype, forEntity: entity) + } +} + diff --git a/Sources/ECS/WorldMethods/World+Init.swift b/Sources/ECS/WorldMethods/World+Init.swift index 175c602..3fe6fa3 100644 --- a/Sources/ECS/WorldMethods/World+Init.swift +++ b/Sources/ECS/WorldMethods/World+Init.swift @@ -1,8 +1,40 @@ // -// File.swift +// WorldInit.swift // // // Created by rrbox on 2023/08/11. // -import Foundation +public extension World { + convenience init() { + self.init(entities: [:], worldBuffer: WorldBuffer()) + + // chunk buffer に chunk entity interface を追加します. + self.worldBuffer.chunkBuffer.setUpChunkBuffer() + + // resource buffer に 時間関係の resource を追加します. + self.worldBuffer.resourceBuffer.addResource(CurrentTime(value: 0)) + self.worldBuffer.resourceBuffer.addResource(DeltaTime(value: 0)) + + // resrouce buffer に world の情報関係の resource を追加します. + self.worldBuffer.resourceBuffer.addResource(EntityCount(count: 0)) + + // world buffer に setup system を保持する領域を確保します. + self.worldBuffer.systemBuffer.registerSystemRegistry(ofType: SetUpExecute.self) + + // world buffer に update system を保持する領域を確保します. + self.worldBuffer.systemBuffer.registerSystemRegistry(ofType: UpdateExecute.self) + + // world buffer に event queue を作成します. + self.worldBuffer.eventBuffer.setUpEventQueue() + self.worldBuffer.eventBuffer.setUpCommandsEventQueue(eventOfType: DidSpawnEvent.self) + self.worldBuffer.eventBuffer.setUpCommandsEventQueue(eventOfType: WillDespawnEvent.self) + + // world buffer に spawn/despawn event の streamer を登録します. + self.addCommandsEventStreamer(eventType: DidSpawnEvent.self) + self.addCommandsEventStreamer(eventType: WillDespawnEvent.self) + + // world buffer に commands の初期値を設定します. + self.worldBuffer.commandsBuffer.setCommands(Commands()) + } +} diff --git a/Sources/ECS/WorldMethods/World+PlugIn.swift b/Sources/ECS/WorldMethods/World+PlugIn.swift new file mode 100644 index 0000000..bdd2e1e --- /dev/null +++ b/Sources/ECS/WorldMethods/World+PlugIn.swift @@ -0,0 +1,13 @@ +// +// World+PlugIn.swift +// +// +// Created by rrbox on 2023/08/19. +// + +public extension World { + @discardableResult func addPlugIn(_ execute: (World) -> ()) -> World { + execute(self) + return self + } +} diff --git a/Sources/ECS/WorldMethods/World+SetUp.swift b/Sources/ECS/WorldMethods/World+SetUp.swift new file mode 100644 index 0000000..d9813f5 --- /dev/null +++ b/Sources/ECS/WorldMethods/World+SetUp.swift @@ -0,0 +1,16 @@ +// +// World+SetUp.swift +// +// +// Created by rrbox on 2023/08/12. +// + +public extension World { + func setUpWorld() { + for system in self.worldBuffer.systemBuffer.systems(ofType: SetUpExecute.self) { + system.setUp(worldBuffer: self.worldBuffer) + } + self.applyCommands() + self.worldBuffer.chunkBuffer.applyEntityQueue() + } +} diff --git a/Sources/ECS/WorldMethods/World+Spawn.swift b/Sources/ECS/WorldMethods/World+Spawn.swift index 275d8bc..f30af39 100644 --- a/Sources/ECS/WorldMethods/World+Spawn.swift +++ b/Sources/ECS/WorldMethods/World+Spawn.swift @@ -5,25 +5,43 @@ // Created by rrbox on 2023/08/10. // +public struct DidSpawnEvent: CommandsEventProtocol { + public let spawnedEntity: Entity +} + +public struct WillDespawnEvent: CommandsEventProtocol { + public let despawnedEntity: Entity +} + extension World { /// Entity を登録します. /// /// ``Commands/spawn()`` が実行された後, フレームが終了するタイミングでこの関数が実行されます. - /// entity へのコンポーネントの登録などは, spawn の後に行われます. - func spawn(entity: Entity, value: Archetype) { - self.entities[entity] = value + /// entity へのコンポーネントの登録などは, push の後に行われます. + func push(entity: Entity, entityRecord: EntityRecord) { + self.insert(entity: entity, entityRecord: entityRecord) self.worldBuffer .chunkBuffer - .spawn(entity: entity, value: value) + .push(entity: entity, entityRecord: entityRecord) + + self.worldBuffer + .eventBuffer + .commandsEventWriter(eventOfType: DidSpawnEvent.self)! + .send(value: DidSpawnEvent(spawnedEntity: entity)) } /// Entity を削除します. /// /// ``Commands/despawn()`` が実行された後, フレームが終了するタイミングでこの関数が実行されます. func despawn(entity: Entity) { - self.entities.removeValue(forKey: entity) + self.remove(entity: entity) self.worldBuffer .chunkBuffer .despawn(entity: entity) + + self.worldBuffer + .eventBuffer + .commandsEventWriter(eventOfType: WillDespawnEvent.self)! + .send(value: WillDespawnEvent(despawnedEntity: entity)) } } diff --git a/Sources/ECS/WorldMethods/World+Update.swift b/Sources/ECS/WorldMethods/World+Update.swift index 307b616..7a33de0 100644 --- a/Sources/ECS/WorldMethods/World+Update.swift +++ b/Sources/ECS/WorldMethods/World+Update.swift @@ -1,8 +1,36 @@ // -// File.swift +// World+Update.swift // // // Created by rrbox on 2023/08/12. // import Foundation + +public extension World { + func update(currentTime: TimeInterval) { + let currentTimeResource = self.worldBuffer.resourceBuffer.resource(ofType: CurrentTime.self)! + + self.worldBuffer.resourceBuffer.resource(ofType: DeltaTime.self)?.resource = DeltaTime(value: currentTime - currentTimeResource.resource.value) + + currentTimeResource.resource = CurrentTime(value: currentTime) + + for system in self.worldBuffer.systemBuffer.systems(ofType: UpdateExecute.self) { + system.update(worldBuffer: self.worldBuffer) + } + + // world が受信した event を event system に発信します. + self.applyEventQueue() + + self.applyCommands() + + // will despawn event を配信します. + self.applyCommandsEventQueue(eventOfType: WillDespawnEvent.self) + + // apply commands の際に push された entity を chunk に割り振ります. + self.worldBuffer.chunkBuffer.applyEntityQueue() + + // Did Spawn event を event system に発信します. + self.applyCommandsEventQueue(eventOfType: DidSpawnEvent.self) + } +} diff --git a/Sources/PlugIns/Graphic2D/Camera.swift b/Sources/PlugIns/Graphic2D/Camera.swift new file mode 100644 index 0000000..d985a65 --- /dev/null +++ b/Sources/PlugIns/Graphic2D/Camera.swift @@ -0,0 +1,25 @@ +// +// Camera.swift +// +// +// Created by rrbox on 2023/10/09. +// + +import SpriteKit +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 new file mode 100644 index 0000000..d2ae07e --- /dev/null +++ b/Sources/PlugIns/Graphic2D/Commands/EntityCommands+Graphic.swift @@ -0,0 +1,22 @@ +// +// EntityCommands+Graphic.swift +// +// +// Created by rrbox on 2023/08/20. +// + +import SpriteKit +import ECS + +public extension EntityCommands { + @discardableResult func setGraphic(_ node: Node) -> EntityCommands { + self.pushCommand(SetGraphic(node: node, entity: self.id())) + return self.addComponent(Graphic(node: node)).addComponent(Graphic(node: node)) + } + + @discardableResult func removeGraphic() -> EntityCommands { + self.pushCommand(RemoveGraphic(entity: self.id())) + return self.removeComponent(ofType: Graphic.self).removeComponent(ofType: Graphic.self) + } +} + diff --git a/Sources/PlugIns/Graphic2D/Commands/RemoveGraphic.swift b/Sources/PlugIns/Graphic2D/Commands/RemoveGraphic.swift new file mode 100644 index 0000000..131b6f0 --- /dev/null +++ b/Sources/PlugIns/Graphic2D/Commands/RemoveGraphic.swift @@ -0,0 +1,15 @@ +// +// RemoveGraphic.swift +// +// +// Created by rrbox on 2023/08/20. +// + +import ECS + +class RemoveGraphic: EntityCommand { + override func runCommand(in world: World) { + world.removeGraphic(fromEntity: self.entity) + } +} + diff --git a/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift new file mode 100644 index 0000000..420881d --- /dev/null +++ b/Sources/PlugIns/Graphic2D/Commands/SetGraphic.swift @@ -0,0 +1,23 @@ +// +// SetGraphic.swift +// +// +// Created by rrbox on 2023/08/20. +// + +import SpriteKit +import ECS + +class SetGraphic: EntityCommand { + let node: SKNode + + init(node: SKNode, entity: Entity) { + self.node = node + super.init(entity: entity) + } + + override func runCommand(in world: World) { + world.setGraphic(self.node, forEntity: self.entity) + } +} + diff --git a/Sources/PlugIns/Graphic2D/Graphic2D.swift b/Sources/PlugIns/Graphic2D/Graphic2D.swift new file mode 100644 index 0000000..b11458f --- /dev/null +++ b/Sources/PlugIns/Graphic2D/Graphic2D.swift @@ -0,0 +1,45 @@ +// +// Graphic2D.swift +// +// +// Created by rrbox on 2023/08/18. +// + +import GameplayKit +import ECS + +public struct SceneResource: ResourceProtocol { + public unowned let scene: SKScene + public init(_ scene: SKScene) { + self.scene = scene + } +} + +public struct Graphic: Component { + public unowned let nodeRef: Node + init(node: Node) { + self.nodeRef = node + } +} + +class GraphicDespawmDetector: GKComponent { + unowned let nodeRef: SKNode + init(nodeRef: SKNode) { + self.nodeRef = nodeRef + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.nodeRef.removeFromParent() + } +} + +public extension SKNode { + func ecsEntity() -> Entity { + self.userData!["ECS/Entity"] as! Entity + } +} diff --git a/Sources/PlugIns/Graphic2D/World+Graphic2D.swift b/Sources/PlugIns/Graphic2D/World+Graphic2D.swift new file mode 100644 index 0000000..56f46f7 --- /dev/null +++ b/Sources/PlugIns/Graphic2D/World+Graphic2D.swift @@ -0,0 +1,28 @@ +// +// World+Graphic2D.swift +// +// +// Created by rrbox on 2023/08/20. +// + +import GameplayKit +import ECS + +extension World { + func setGraphic(_ node: Node, forEntity entity: Entity) { + node.userData = [:] + node.userData!["ECS/Entity"] = entity + let scene = self.worldBuffer.resourceBuffer.resource(ofType: SceneResource.self)?.resource.scene + scene?.addChild(node) + let entityRecord = self.entityRecord(forEntity: entity)! + entityRecord.addComponent(GKSKNodeComponent(node: node)) + entityRecord.addComponent(GraphicDespawmDetector(nodeRef: node)) + } + + func removeGraphic(fromEntity entity: Entity) { + let entityRecord = self.entityRecord(forEntity: entity)! + entityRecord.removeComponent(ofType: GKSKNodeComponent.self) + entityRecord.removeComponent(ofType: GraphicDespawmDetector.self) + } +} + diff --git a/Sources/PlugIns/Mouse/Mouse.swift b/Sources/PlugIns/Mouse/Mouse.swift index 96c0980..d09306a 100644 --- a/Sources/PlugIns/Mouse/Mouse.swift +++ b/Sources/PlugIns/Mouse/Mouse.swift @@ -5,4 +5,65 @@ // Created by rrbox on 2023/08/05. // -import Foundation +import ECS +import AppKit + +public struct MouseEnteredEvent: EventProtocol { + public let nsEvent: NSEvent +} + +public struct MouseExitedEvent: EventProtocol { + public let nsEvent: NSEvent +} + +public struct MouseMovedEvent: EventProtocol { + public let nsEvent: NSEvent +} + +public struct MouseDownEvent: EventProtocol { + public let nsEvent: NSEvent +} + +public struct MouseDraggedEvent: EventProtocol { + public let nsEvent: NSEvent +} + +public struct MouseUpEvent: EventProtocol { + public let nsEvent: NSEvent +} + +public func mousePlugIns(_ world: World) { + world + .addEventStreamer(eventType: MouseEnteredEvent.self) + .addEventStreamer(eventType: MouseExitedEvent.self) + .addEventStreamer(eventType: MouseMovedEvent.self) + .addEventStreamer(eventType: MouseDownEvent.self) + .addEventStreamer(eventType: MouseDraggedEvent.self) + .addEventStreamer(eventType: MouseUpEvent.self) +} + +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 new file mode 100644 index 0000000..f991bbe --- /dev/null +++ b/Sources/PlugIns/Mouse/TrackableView.swift @@ -0,0 +1,30 @@ +// +// TrackableView.swift +// +// +// Created by rrbox on 2023/10/09. +// + +import SpriteKit + +class MouseTrackableView: SKView { + var sceneTrackingArea: NSTrackingArea? + + func setTrackingArea() { + let scene = self.scene! + let trackingArea = NSTrackingArea( + rect: frame, + options: [.mouseEnteredAndExited, .mouseMoved, .activeAlways], + owner: scene) + self.addTrackingArea(trackingArea) + self.sceneTrackingArea = trackingArea + } + + override func updateTrackingAreas() { + if let t = self.sceneTrackingArea { + self.removeTrackingArea(t) + } + self.setTrackingArea() + } +} + diff --git a/Tests/GraphicPlugInTests/GraphicPlugInTests.swift b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift new file mode 100644 index 0000000..4492fed --- /dev/null +++ b/Tests/GraphicPlugInTests/GraphicPlugInTests.swift @@ -0,0 +1,12 @@ +// +// GraphicPlugInTests.swift +// +// +// Created by rrbox on 2023/08/18. +// + +import XCTest + +final class GraphicPlugInTests: XCTestCase { + +} diff --git a/Tests/ObjectLinkPlugInTests/ObjectLinkPlugInTests.swift b/Tests/ObjectLinkPlugInTests/ObjectLinkPlugInTests.swift index a060761..4a1fca7 100644 --- a/Tests/ObjectLinkPlugInTests/ObjectLinkPlugInTests.swift +++ b/Tests/ObjectLinkPlugInTests/ObjectLinkPlugInTests.swift @@ -6,7 +6,7 @@ // import XCTest -@testable import ObjectLink +@testable import ECS_ObjectLink final class ObjectLinkTests: XCTestCase { diff --git a/Tests/ScrollPlugInTests/ScrollPlugInTests.swift b/Tests/ScrollPlugInTests/ScrollPlugInTests.swift index 1ca7454..feeaf18 100644 --- a/Tests/ScrollPlugInTests/ScrollPlugInTests.swift +++ b/Tests/ScrollPlugInTests/ScrollPlugInTests.swift @@ -6,7 +6,6 @@ // import XCTest -import ObjectLink final class ScrollPlugInTests: XCTestCase { diff --git a/Tests/ecs-swiftTests/ChunkTests.swift b/Tests/ecs-swiftTests/ChunkTests.swift index eb8e122..a24bef1 100644 --- a/Tests/ecs-swiftTests/ChunkTests.swift +++ b/Tests/ecs-swiftTests/ChunkTests.swift @@ -6,30 +6,58 @@ // import XCTest +@testable import ECS -final class ChunkTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. +class TestChunk: Chunk { + var entities = [Entity: EntityRecord]() + override func spawn(entity: Entity, entityRecord: EntityRecord) { + self.entities[entity] = entityRecord } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. + + override func despawn(entity: Entity) { + self.entities.removeValue(forKey: entity) } +} - 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. +class TestChunk_2: Chunk { + var entities = [Entity: EntityRecord]() + override func spawn(entity: Entity, entityRecord: EntityRecord) { + self.entities[entity] = entityRecord + } + + override func despawn(entity: Entity) { + self.entities.removeValue(forKey: entity) } +} - 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. +final class ChunkTests: XCTestCase { + func testInterface() { + let mockEntities = [Entity(), Entity(), Entity(), Entity(), Entity()] + let world = World() + + // Spawn された entity を単に蓄積するだけの test 用の chunk です. + let testChunk = TestChunk() + let testChunk_2 = TestChunk_2() + world.worldBuffer.chunkBuffer.addChunk(testChunk) + world.worldBuffer.chunkBuffer.addChunk(testChunk_2) + + // chunk interface を介して chunk に entity を push します(回数: 5回). + for entity in mockEntities { + world.push(entity: entity, entityRecord: EntityRecord()) } + + world.worldBuffer.chunkBuffer.applyEntityQueue() + + 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 5f9c196..5946d58 100644 --- a/Tests/ecs-swiftTests/CommandsTests.swift +++ b/Tests/ecs-swiftTests/CommandsTests.swift @@ -6,30 +6,54 @@ // import XCTest +@testable import ECS -final class CommandsTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. +class TestCommand_Spawn: Command { + let entity: Entity + init(entity: Entity) { + self.entity = entity } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. + override func runCommand(in world: World) { + world.push(entity: self.entity, entityRecord: EntityRecord()) } +} - 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. +class TestCommand_Despawn: Command { + let entity: Entity + init(entity: Entity) { + self.entity = entity + } + override func runCommand(in world: World) { + world.despawn(entity: self.entity) } +} - 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. +final class CommandsTests: XCTestCase { + func testCommands() { + let world = World() + world.worldBuffer.commandsBuffer.setCommands(Commands()) + let commands = world.worldBuffer.commandsBuffer.commands()! + + let testEntities = [Entity(), Entity(), Entity()] + + for testEntity in testEntities { + commands.push(command: TestCommand_Spawn(entity: testEntity)) } + + XCTAssertEqual(commands.commandQueue.count, 3) + world.applyCommands() + + XCTAssertEqual(commands.commandQueue.count, 0) + XCTAssertEqual(world.entities.sequence.count, 3) + + for testEntity in testEntities { + commands.push(command: TestCommand_Despawn(entity: testEntity)) + } + + XCTAssertEqual(commands.commandQueue.count, 3) + world.applyCommands() + + XCTAssertEqual(commands.commandQueue.count, 0) + XCTAssertEqual(world.entities.sequence.count, 0) } - } diff --git a/Tests/ecs-swiftTests/EntityCommandsTests.swift b/Tests/ecs-swiftTests/EntityCommandsTests.swift index 5908f68..2c6644a 100644 --- a/Tests/ecs-swiftTests/EntityCommandsTests.swift +++ b/Tests/ecs-swiftTests/EntityCommandsTests.swift @@ -6,30 +6,33 @@ // import XCTest +@testable import ECS final class EntityCommandsTests: 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. - } + func testEntityCommands() { + let commands = Commands() + let world = World() + world.worldBuffer.commandsBuffer.setCommands(commands) + + // world に entity を生成し, component を追加し, id(id としての entity) を受け取ります. + let entity = commands.spawn() + .addComponent(TestComponent(content: "test")) + .id() + world.applyCommands() + + XCTAssertEqual(world.entityRecord(forEntity: entity)!.component(ofType: ComponentRef.self)!.value.content, "test") + + commands.entity(entity)?.removeComponent(ofType: TestComponent.self) + world.applyCommands() + + // world 内に entity が存在し, component が削除されていることをテストします. + XCTAssertNotNil(world.entityRecord(forEntity: entity)) + XCTAssertNil(world.entityRecord(forEntity: entity)!.component(ofType: ComponentRef.self)) + + commands.despawn(entity: entity) + world.applyCommands() + + // world から entity が削除されたことをテストします. + XCTAssertNil(world.entityRecord(forEntity: entity)) } - } diff --git a/Tests/ecs-swiftTests/EventTests.swift b/Tests/ecs-swiftTests/EventTests.swift new file mode 100644 index 0000000..9711647 --- /dev/null +++ b/Tests/ecs-swiftTests/EventTests.swift @@ -0,0 +1,71 @@ +// +// EventTests.swift +// +// +// Created by rrbox on 2023/08/13. +// + +import XCTest +@testable import ECS + +struct TestEvent: EventProtocol { + let name: String +} + +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() +} + +func setUp(eventWriter: EventWriter) { + print("---set up---") + print("-> event send:", "\"test event\"") + eventWriter.send(value: TestEvent(name: "test event")) + print("---") + print() +} + +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() +} + +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() +} + +final class EventTests: XCTestCase { + func testEvent() { + print() + + let world = World() + .addEventStreamer(eventType: TestEvent.self) + .addEventSystem(testEvent(event:eventWriter:commands:currentTime:)) + .addSetUpSystem(setUp(eventWriter:)) + .addEventSystem(spawnedEntitySystem) + .addEventSystem(despanedEntitySystem) + + world.setUpWorld() + + world.update(currentTime: 0) + world.update(currentTime: 1) + world.update(currentTime: 2) + world.update(currentTime: 3) + } +} diff --git a/Tests/ecs-swiftTests/Mocks/ComponentMocks.swift b/Tests/ecs-swiftTests/Mocks/ComponentMocks.swift new file mode 100644 index 0000000..c8fd5d7 --- /dev/null +++ b/Tests/ecs-swiftTests/Mocks/ComponentMocks.swift @@ -0,0 +1,28 @@ +// +// Components.swift +// +// +// Created by rrbox on 2023/08/14. +// + +import ECS + +struct TestComponent: Component { + let content: String +} + +struct TestComponent2: Component { + let content: String +} + +struct TestComponent3: Component { + let content: String +} + +struct TestComponent4: Component { + let content: String +} + +struct TestComponent5: Component { + let content: String +} diff --git a/Tests/ecs-swiftTests/PlugInTests.swift b/Tests/ecs-swiftTests/PlugInTests.swift index aeb2c64..3954b07 100644 --- a/Tests/ecs-swiftTests/PlugInTests.swift +++ b/Tests/ecs-swiftTests/PlugInTests.swift @@ -8,28 +8,5 @@ import XCTest final class PlugInTests: 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. - } - } - + } diff --git a/Tests/ecs-swiftTests/QueryTests.swift b/Tests/ecs-swiftTests/QueryTests.swift index 37953b2..d8945bc 100644 --- a/Tests/ecs-swiftTests/QueryTests.swift +++ b/Tests/ecs-swiftTests/QueryTests.swift @@ -6,30 +6,85 @@ // import XCTest +@testable import ECS final class QueryTests: 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. - } + func testQuery() { + let testQuery = Query() + let testQuery2 = Query2() + let testQuery3 = Query3() + let testQuery4 = Query4() + let testQuery5 = Query5() + + let world = World() + + world.worldBuffer.chunkBuffer.addChunk(testQuery) + world.worldBuffer.chunkBuffer.addChunk(testQuery2) + world.worldBuffer.chunkBuffer.addChunk(testQuery3) + world.worldBuffer.chunkBuffer.addChunk(testQuery4) + world.worldBuffer.chunkBuffer.addChunk(testQuery5) + + let commands = world.worldBuffer.commandsBuffer.commands()! + + let testEntity = commands.spawn().addComponent(TestComponent(content: "test")).id() + + world.applyCommands() + + XCTAssertEqual(testQuery.components.count, 1) + XCTAssertEqual(testQuery2.components.count, 0) + XCTAssertEqual(testQuery3.components.count, 0) + XCTAssertEqual(testQuery4.components.count, 0) + XCTAssertEqual(testQuery5.components.count, 0) + + commands.entity(testEntity)?.addComponent(TestComponent2(content: "test2")) + + world.applyCommands() + + XCTAssertEqual(testQuery.components.count, 1) + XCTAssertEqual(testQuery2.components.count, 1) + XCTAssertEqual(testQuery3.components.count, 0) + XCTAssertEqual(testQuery4.components.count, 0) + XCTAssertEqual(testQuery5.components.count, 0) + + commands.entity(testEntity)?.addComponent(TestComponent3(content: "test2")) + + world.applyCommands() + + XCTAssertEqual(testQuery.components.count, 1) + XCTAssertEqual(testQuery2.components.count, 1) + XCTAssertEqual(testQuery3.components.count, 1) + XCTAssertEqual(testQuery4.components.count, 0) + XCTAssertEqual(testQuery5.components.count, 0) + + commands.entity(testEntity)?.addComponent(TestComponent4(content: "test2")) + + world.applyCommands() + + XCTAssertEqual(testQuery.components.count, 1) + XCTAssertEqual(testQuery2.components.count, 1) + XCTAssertEqual(testQuery3.components.count, 1) + XCTAssertEqual(testQuery4.components.count, 1) + XCTAssertEqual(testQuery5.components.count, 0) + + commands.entity(testEntity)?.addComponent(TestComponent5(content: "test2")) + + world.applyCommands() + + XCTAssertEqual(testQuery.components.count, 1) + XCTAssertEqual(testQuery2.components.count, 1) + XCTAssertEqual(testQuery3.components.count, 1) + XCTAssertEqual(testQuery4.components.count, 1) + XCTAssertEqual(testQuery5.components.count, 1) + + commands.entity(testEntity)?.removeComponent(ofType: TestComponent.self) + + world.applyCommands() + + XCTAssertEqual(testQuery.components.count, 0) + XCTAssertEqual(testQuery2.components.count, 0) + XCTAssertEqual(testQuery3.components.count, 0) + XCTAssertEqual(testQuery4.components.count, 0) + XCTAssertEqual(testQuery5.components.count, 0) } - + } diff --git a/Tests/ecs-swiftTests/ReourceTests.swift b/Tests/ecs-swiftTests/ReourceTests.swift deleted file mode 100644 index 0cb8a6d..0000000 --- a/Tests/ecs-swiftTests/ReourceTests.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// ReourceTests.swift -// -// -// Created by rrbox on 2023/08/11. -// - -import XCTest - -final class ReourceTests: 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. - } - } - -} diff --git a/Tests/ecs-swiftTests/ResourceTests.swift b/Tests/ecs-swiftTests/ResourceTests.swift new file mode 100644 index 0000000..689a974 --- /dev/null +++ b/Tests/ecs-swiftTests/ResourceTests.swift @@ -0,0 +1,12 @@ +// +// ResourceTests.swift +// +// +// Created by rrbox on 2023/08/11. +// + +import XCTest + +final class ResourceTests: XCTestCase { + +} diff --git a/Tests/ecs-swiftTests/SystemParameterTests.swift b/Tests/ecs-swiftTests/SystemParameterTests.swift index 00d131d..528d003 100644 --- a/Tests/ecs-swiftTests/SystemParameterTests.swift +++ b/Tests/ecs-swiftTests/SystemParameterTests.swift @@ -8,28 +8,5 @@ import XCTest final class SystemParameterTests: 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. - } - } - + } diff --git a/Tests/ecs-swiftTests/SystemTests.swift b/Tests/ecs-swiftTests/SystemTests.swift index a846c38..12235b6 100644 --- a/Tests/ecs-swiftTests/SystemTests.swift +++ b/Tests/ecs-swiftTests/SystemTests.swift @@ -6,30 +6,87 @@ // import XCTest +import ECS -final class SystemTests: XCTestCase { +func testSetUp(commands: Commands) { + print("set up") + commands.spawn() + .addComponent(TestComponent(content: "sample_1010")) + + commands.spawn() + .addComponent(TestComponent(content: "sample_120391-2")) +} - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } +func currentTimeTestSystem(time: Resource) { + print(time.resource) +} - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } +func deltaTimeTestSystem(deltaTime: Resource) { + print(deltaTime.resource) +} - 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 apiTestSystem( + q0: Query +) { + q0.update { _, component in + print(component) } +} + +func apiTestSystem( + q0: Query, + q1: Query2 +) { + +} + +func apiTestSystem( + q0: Query, + q1: Query2, + q2: Query3 +) { + +} + +func apiTestSystem( + q0: Query, + q1: Query2, + q2: Query3, + q3: Query4 +) { + +} - 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. - } +func apiTestSystem( + q0: Query, + q1: Query2, + q2: Query3, + q3: Query4, + q4: Query5 +) { + q0.update { _, component in + print(component) } +} +final class SystemTests: XCTestCase { + func testUpdateSystem() { + let world = World() + .addSetUpSystem(testSetUp(commands:)) + .addUpdateSystem(currentTimeTestSystem(time:)) + .addUpdateSystem(deltaTimeTestSystem(deltaTime:)) + .addUpdateSystem(apiTestSystem(q0:)) + .addUpdateSystem(apiTestSystem(q0:q1:)) + .addUpdateSystem(apiTestSystem(q0:q1:q2:)) + .addUpdateSystem(apiTestSystem(q0:q1:q2:q3:)) + .addUpdateSystem(apiTestSystem(q0:q1:q2:q3:q4:)) + + world.setUpWorld() + + world.update(currentTime: 0) + world.update(currentTime: 1) + world.update(currentTime: 2) + world.update(currentTime: 3) + world.update(currentTime: 4) + } } diff --git a/Tests/ecs-swiftTests/UpdateSystemTests.swift b/Tests/ecs-swiftTests/UpdateSystemTests.swift index af820d8..807cfc7 100644 --- a/Tests/ecs-swiftTests/UpdateSystemTests.swift +++ b/Tests/ecs-swiftTests/UpdateSystemTests.swift @@ -6,30 +6,26 @@ // import XCTest +@testable import ECS -final class UpdateSystemTests: 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 mySystem(commands: Commands) { + commands.spawn() + .addComponent(TestComponent(content: "sample")) +} - 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 mySystem2(query: Query) { + query.update { _, component in + XCTAssertEqual(component.content, "sample") } +} - 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. - } +final class UpdateSystemTests: XCTestCase { + func testUpdate() { + let world = World() + .addUpdateSystem(mySystem(commands:)) + .addUpdateSystem(mySystem2(query:)) + + world.update(currentTime: 0) + world.update(currentTime: 0) } - } diff --git a/Tests/ecs-swiftTests/WorldTests.swift b/Tests/ecs-swiftTests/WorldTests.swift index 999346d..fb72245 100644 --- a/Tests/ecs-swiftTests/WorldTests.swift +++ b/Tests/ecs-swiftTests/WorldTests.swift @@ -6,30 +6,10 @@ // import XCTest +import ECS final class WorldTests: 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. - } + func testWorld() { + _ = World() } - } diff --git a/Tests/ecs-swiftTests/ecs_swiftTests.swift b/Tests/ecs-swiftTests/ecs_swiftTests.swift index 0ae8551..f8e555a 100644 --- a/Tests/ecs-swiftTests/ecs_swiftTests.swift +++ b/Tests/ecs-swiftTests/ecs_swiftTests.swift @@ -1,6 +1,84 @@ import XCTest @testable import ECS +struct Text: Component { + let v: String +} + +func entitycreate(commands: Commands) { + for i in 1...20000 { + commands.spawn() + .addComponent(Text(v: "\(i)")) + } +} + +func entitycreate2(commands: Commands) { + for i in 1...20000 { + commands.spawn() + .addComponent(Text(v: "\(i)")) + } +} + + +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 + + } +} + final class ecs_swiftTests: XCTestCase { + // entity: 20000 + // set up: 1 + // update: 1 + // 0.00478 s + func testPerformance() { + let world = World() + .addSetUpSystem(entitycreate(commands:)) + .addUpdateSystem(update(query:)) + world.setUpWorld() + + print(world.entities.sequence.count) + + measure { + world.update(currentTime: 0) + } + } + // entity: 20000 + // set up: 1 + // update: 4 + // 0.0158 s -> およそ 4 倍 + func testUpdate4Performance() { + let world = World() + .addSetUpSystem(entitycreate(commands:)) + .addUpdateSystem(update(query:)) + .addUpdateSystem(update2(query:)) + .addUpdateSystem(update3(query:)) + .addUpdateSystem(update4(query:)) + world.setUpWorld() + + print(world.entities.sequence.count) + + measure { + world.update(currentTime: 0) + } + } }