From 739693562810e13c2fd19197bc223c27465f743a Mon Sep 17 00:00:00 2001 From: ryanpeh Date: Fri, 5 Apr 2024 23:49:41 +0800 Subject: [PATCH 01/43] [ark-multiplayer-kit] feat: set up ecs multiplayer scaffolding --- ArkKit.xcodeproj/project.pbxproj | 18 ++- .../ArkMultiplayerECS.swift | 110 ++++++++++++++++++ .../ArkMultiplayerEventManager.swift | 11 +- .../ArkMultiplayerManager.swift | 106 ++++++++++++++--- .../ArkNetworkProtocol.swift | 2 + .../ArkNetworkService.swift | 24 +++- ArkKit/ark-multiplayer-kit/DataWrapper.swift | 2 +- ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift | 2 +- .../ark-ecs-kit/EntityManager.swift | 12 +- .../ark-ecs-kit/{ => entity}/Entity.swift | 2 +- .../entity/EntityIDGenerator.swift | 27 +++++ 11 files changed, 285 insertions(+), 31 deletions(-) create mode 100644 ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift rename ArkKit/ark-state-kit/ark-ecs-kit/{ => entity}/Entity.swift (88%) create mode 100644 ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index fcb6eb9..386e407 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -155,6 +155,7 @@ 9479A3AF2BA952E300F99013 /* AbstractShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479A3AE2BA952E300F99013 /* AbstractShape.swift */; }; 9479A3B12BA953D800F99013 /* ShapeRenderableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479A3B02BA953D800F99013 /* ShapeRenderableComponent.swift */; }; 9479A3B42BA95E7E00F99013 /* AbstractColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479A3B32BA95E7E00F99013 /* AbstractColor.swift */; }; + AD260CA82BBFCC7E008B9654 /* EntityIDGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */; }; AD2B59AC2BB87F2200198E99 /* ArkNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */; }; AD2B59B02BB94DE000198E99 /* ArkMultiplayerEventManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */; }; AD2B59B22BB94FBE00198E99 /* ArkMultiplayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */; }; @@ -193,6 +194,7 @@ ADA847FD2BBC4B5500B19378 /* ArkNetworkProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */; }; ADA847FF2BBC4EA800B19378 /* ArkDataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */; }; ADA848022BBC7DF000B19378 /* ArkSerializableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */; }; + ADD74C092BBFEBF3008CE36B /* ArkMultiplayerECS.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -354,6 +356,7 @@ 9479A3AE2BA952E300F99013 /* AbstractShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractShape.swift; sourceTree = ""; }; 9479A3B02BA953D800F99013 /* ShapeRenderableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeRenderableComponent.swift; sourceTree = ""; }; 9479A3B32BA95E7E00F99013 /* AbstractColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractColor.swift; sourceTree = ""; }; + AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityIDGenerator.swift; sourceTree = ""; }; AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkService.swift; sourceTree = ""; }; AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerEventManager.swift; sourceTree = ""; }; AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerManager.swift; sourceTree = ""; }; @@ -394,6 +397,7 @@ ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkProtocol.swift; sourceTree = ""; }; ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkDataSerializer.swift; sourceTree = ""; }; ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSerializableEvent.swift; sourceTree = ""; }; + ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerECS.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -982,6 +986,15 @@ path = color; sourceTree = ""; }; + AD260CA62BBFCC6C008B9654 /* entity */ = { + isa = PBXGroup; + children = ( + AD787A812B9C6BD9003EBBD0 /* Entity.swift */, + AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */, + ); + path = entity; + sourceTree = ""; + }; AD2B59AA2BB87F0F00198E99 /* ark-multiplayer-kit */ = { isa = PBXGroup; children = ( @@ -991,6 +1004,7 @@ AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */, ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */, AD2B59B52BB958E400198E99 /* DataWrapper.swift */, + ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */, ); path = "ark-multiplayer-kit"; sourceTree = ""; @@ -1049,7 +1063,7 @@ AD6E035F2B9F067100974EBF /* ark-ecs-kit */ = { isa = PBXGroup; children = ( - AD787A812B9C6BD9003EBBD0 /* Entity.swift */, + AD260CA62BBFCC6C008B9654 /* entity */, AD787A832B9C6C78003EBBD0 /* Component.swift */, 0267BA242BBD0FC70010F729 /* system */, 28A032DD2BAD4ED200851BFF /* InternalSystems */, @@ -1357,6 +1371,7 @@ 02C395242BA849B40075F1CA /* JoystickRenderableComponent.swift in Sources */, 942A16792BADF40600F0186B /* ArkAudioPlayer.swift in Sources */, AD787A822B9C6BD9003EBBD0 /* Entity.swift in Sources */, + ADD74C092BBFEBF3008CE36B /* ArkMultiplayerECS.swift in Sources */, 9479A3B42BA95E7E00F99013 /* AbstractColor.swift in Sources */, 0267BA2A2BBD10060010F729 /* CleanUpSystem.swift in Sources */, 280CD3B92BA7391700372C5D /* PhysicsComponent.swift in Sources */, @@ -1381,6 +1396,7 @@ 02E0E8F22BA282C20043E2BA /* UIKitCircle.swift in Sources */, 02D8E94C2BAC99FC00BF3A07 /* GameLoopable.swift in Sources */, 280CD3D02BA74E6000372C5D /* AbstractArkPhysicsScene.swift in Sources */, + AD260CA82BBFCC7E008B9654 /* EntityIDGenerator.swift in Sources */, 02C394E62BA41AC60075F1CA /* ArkGameModel.swift in Sources */, AD36A75B2BAC331F003E938B /* TankComponent.swift in Sources */, 28844EA12BA881E60037A7F6 /* DemoPhysicsComponent.swift in Sources */, diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift new file mode 100644 index 0000000..fa3815e --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift @@ -0,0 +1,110 @@ +import Foundation + +class ArkMultiplayerECS: ArkECSContext { + let arkECS: ArkECS + var delegate: ArkMultiplayerECSDelegate? + + init(arkECS: ArkECS = ArkECS(), + delegate: ArkMultiplayerECSDelegate? = nil) { + self.arkECS = arkECS + self.delegate = delegate + } + func startUp() { + arkECS.startUp() + } + + func update(deltaTime: TimeInterval) { + guard delegate?.isModificationEnabled ?? true else { + return + } + + arkECS.update(deltaTime: deltaTime) + } + + func cleanUp() { + arkECS.cleanUp() + } + + @discardableResult + func createEntity() -> Entity { + guard delegate?.isModificationEnabled ?? true else { + return Entity() + } + + let entity = arkECS.createEntity() + delegate?.didCreateEntity(entity) + return entity + } + + func removeEntity(_ entity: Entity) { + guard delegate?.isModificationEnabled ?? true else { + return + } + + arkECS.removeEntity(entity) + delegate?.didRemoveEntity(entity) + } + + func upsertComponent(_ component: T, to entity: Entity) where T: Component { + guard delegate?.isModificationEnabled ?? true else { + return + } + + arkECS.upsertComponent(component, to: entity) + delegate?.didUpsertComponent(component, to: entity) + } + + func removeComponent(_ componentType: T.Type, from entity: Entity) where T: Component { + guard delegate?.isModificationEnabled ?? true else { + return + } + + arkECS.removeComponent(componentType, from: entity) + delegate?.didRemoveComponent(componentType, from: entity) + } + + func getComponent(ofType type: T.Type, for entity: Entity) -> T? where T: Component { + return arkECS.getComponent(ofType: type, for: entity) + } + + @discardableResult + func createEntity(with components: [any Component]) -> Entity { + guard delegate?.isModificationEnabled ?? true else { + return Entity() + } + + let entity = arkECS.createEntity(with: components) + delegate?.didCreateEntity(entity, with: components) + + return entity + } + + func getEntity(id: EntityID) -> Entity? { + arkECS.getEntity(id: id) + } + + func getEntities(with componentTypes: [any Component.Type]) -> [Entity] { + arkECS.getEntities(with: componentTypes) + } + + func getComponents(from entity: Entity) -> [any Component] { + arkECS.getComponents(from: entity) + } + + func addSystem(_ system: UpdateSystem, schedule: Schedule = .update, isUnique: Bool = true) { + guard delegate?.isModificationEnabled ?? true else { + return + } + + arkECS.addSystem(system, schedule: .update, isUnique: isUnique) + } +} + +protocol ArkMultiplayerECSDelegate { + var isModificationEnabled: Bool { get } + func didCreateEntity(_ entity: Entity) + func didRemoveEntity(_ entity: Entity) + func didRemoveComponent(_ componentType: T.Type, from entity: Entity) + func didUpsertComponent(_ component: T, to entity: Entity) + func didCreateEntity(_ entity: Entity, with components: [Component]) +} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift index 9f95504..d1661e8 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift @@ -8,18 +8,17 @@ import Foundation class ArkMultiplayerEventManager: ArkEventManagerDelegate { - private var arkEventManager: ArkEventContext - var networkManagerDelegate: ArkMultiplayerManagerDelegate? + var delegate: ArkMultiplayerEventManagerDelegate? init(arkEventManager: ArkEventContext = ArkEventManager(), - networkManagerDelegate: ArkMultiplayerManagerDelegate? = nil) { + delegate: ArkMultiplayerEventManagerDelegate? = nil) { self.arkEventManager = arkEventManager - self.networkManagerDelegate = networkManagerDelegate + self.delegate = delegate } func didEmitEvent(_ event: Event) where Event: ArkEvent { - networkManagerDelegate?.shouldSendEvent(event) + delegate?.shouldSendEvent(event) } func emitWithoutBroadcast(_ event: Event) where Event: ArkEvent { @@ -27,6 +26,6 @@ class ArkMultiplayerEventManager: ArkEventManagerDelegate { } } -protocol ArkMultiplayerManagerDelegate: AnyObject { +protocol ArkMultiplayerEventManagerDelegate: AnyObject { func shouldSendEvent(_ event: Event) } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index ac3a085..b532db3 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -7,36 +7,39 @@ import MultipeerConnectivity +enum PeerRole { + case master + case slave +} + class ArkMultiplayerManager: ArkNetworkDelegate { private var networkService: ArkNetworkProtocol var multiplayerEventManager: ArkMultiplayerEventManager? + var arkMultiplayerECS: ArkMultiplayerECS? + private var peers = [String]() + private var masterPeer: String? + private var role: PeerRole = .master init(serviceName: String) { self.networkService = ArkNetworkService(serviceName: serviceName) self.networkService.delegate = self } - func sendEvent(event: any ArkEvent) { - do { - if let encodedEvent = try ArkDataSerializer.encodeEvent(event) { - networkService.sendData(data: encodedEvent) - } - } catch { - print("Error encoding or sending event: \(error)") - } - } - func gameDataReceived(manager: ArkNetworkService, gameData: Data) { print("data received") do { let wrappedData = try JSONDecoder().decode(DataWrapper.self, from: gameData) - if wrappedData.type == .event { - if let event = try ArkEventRegistry.shared.decode(from: wrappedData.payload, - typeName: wrappedData.name) { - processEvent(event: event) - } + if wrappedData.type == .event, + let event = try ArkEventRegistry.shared.decode(from: wrappedData.payload, + typeName: wrappedData.name) { + processEvent(event: event) + } + + if wrappedData.type == .ecsFunction { + } + } catch { print("Error decoding received data: \(error)") } @@ -47,12 +50,83 @@ class ArkMultiplayerManager: ArkNetworkDelegate { } func connectedDevicesChanged(manager: ArkNetworkService, connectedDevices: [String]) { + peers = connectedDevices + updateRoles() + } + + private func updateRoles() { + let sortedPeers = (peers + [networkService.deviceID]).sorted() + masterPeer = sortedPeers.first + + role = masterPeer == networkService.deviceID ? .master : .slave + print("Updated role: \(role)") } } -extension ArkMultiplayerManager: ArkMultiplayerManagerDelegate { +extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { func shouldSendEvent(_ event: Event) { sendEvent(event: event) } + + private func sendEvent(event: any ArkEvent) { + do { + if let encodedEvent = try ArkDataSerializer.encodeEvent(event) { + networkService.sendData(data: encodedEvent) + } + } catch { + print("Error encoding or sending event: \(error)") + } + } +} + +extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { + var isModificationEnabled: Bool { + self.role == .master + } + + private func sendEcsFunction() { + + } + + func didCreateEntity(_ entity: Entity) { + guard self.role == .master else { + return + } + + // Send create entity function to slave + } + + + func didRemoveEntity(_ entity: Entity) { + guard self.role == .master else { + return + } + + // Send remove entity function to slave + } + func didRemoveComponent(_ componentType: T.Type, from entity: Entity) { + guard self.role == .master else { + return + } + + // Send remove component function to slave + } + + func didUpsertComponent(_ component: T, to entity: Entity) { + guard self.role == .master else { + return + } + + // Send upsert component function to slave + } + + func didCreateEntity(_ entity: Entity, with components: [Component]) { + guard self.role == .master else { + return + } + + // Send create entity with components function to slave + } + } diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift index a3efad1..f67a11d 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift @@ -8,7 +8,9 @@ import Foundation protocol ArkNetworkProtocol { var delegate: ArkNetworkDelegate? { get set } + var deviceID: String { get } init(serviceName: String) func sendData(data: Data) + func sendData(_ data: Data, to peerName: String) } diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift index d937034..0334fca 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift @@ -28,16 +28,24 @@ class ArkNetworkService: ArkNetworkProtocol { deinit { session.stopSharing() } + + var deviceID: String { + UIDevice.current.name + } private func setUpHandlers() { self.session.peersChangeHandler = { [weak self] peers in - self?.peers = peers - self?.delegate?.connectedDevicesChanged(manager: self!, connectedDevices: peers.map { $0.peerID }) - print("Peers changed: \(peers.map { $0.peerID })") + guard let strongSelf = self else { + return + } + + strongSelf.peers = peers + strongSelf.delegate?.connectedDevicesChanged(manager: strongSelf, connectedDevices: peers.map { $0.peerID }) + print("Peers changed: \(peers.map { $0.info["name"] ?? $0.peerID })") } self.session.newPeerHandler = { peer in - print("New peer joined: \(peer.peerID)") + print("New peer joined: \(peer.info["name"] ?? peer.peerID)") } self.session.messageReceivedHandler = { [weak self] _, data in @@ -50,6 +58,14 @@ class ArkNetworkService: ArkNetworkProtocol { session.sendToAllPeers(data: data) } } + + func sendData(_ data: Data, to peerName: String) { + guard let peerInfo = peers.first(where: { $0.info["name"] == peerName }) else { + return + } + + session.send(to: peerInfo.peerID, data: data) + } } // MARK: - ArkNetworkDelegate diff --git a/ArkKit/ark-multiplayer-kit/DataWrapper.swift b/ArkKit/ark-multiplayer-kit/DataWrapper.swift index 40be41a..f11901e 100644 --- a/ArkKit/ark-multiplayer-kit/DataWrapper.swift +++ b/ArkKit/ark-multiplayer-kit/DataWrapper.swift @@ -8,7 +8,7 @@ import Foundation enum PayloadType: String, Codable { - case event, state + case event, ecsFunction } struct DataWrapper: Codable { diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift index 8a8fc40..2ecfe6b 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift @@ -27,7 +27,7 @@ extension ArkECS: ArkECSContext { func createEntity() -> Entity { entityManager.createEntity() } - + func removeEntity(_ entity: Entity) { entityManager.removeEntity(entity) } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift index 185860a..ff6c267 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift @@ -10,9 +10,19 @@ import Foundation class EntityManager { private var entities = Set() private var componentsByType = [ObjectIdentifier: [Entity: Component]]() + private var idGenerator = EntityIDGenerator() func createEntity() -> Entity { - let entity = Entity() + let entity = Entity(id: idGenerator.generate()) + entities.insert(entity) + return entity + } + + func createEntity(withId id: EntityID) -> Entity? { + let entity = Entity(id: id) + if entities.contains(entity) { + return nil // Entity with this ID already exists + } entities.insert(entity) return entity } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/Entity.swift b/ArkKit/ark-state-kit/ark-ecs-kit/entity/Entity.swift similarity index 88% rename from ArkKit/ark-state-kit/ark-ecs-kit/Entity.swift rename to ArkKit/ark-state-kit/ark-ecs-kit/entity/Entity.swift index d7bc25b..6ded8cf 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/Entity.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/entity/Entity.swift @@ -7,7 +7,7 @@ import Foundation -typealias EntityID = UUID +typealias EntityID = Int struct Entity { var id = EntityID() diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift b/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift new file mode 100644 index 0000000..a89ee28 --- /dev/null +++ b/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift @@ -0,0 +1,27 @@ +// +// EntityIDGenerator.swift +// ArkKit +// +// Created by Ryan Peh on 5/4/24. +// + +import Foundation + +class EntityIDGenerator { + private var currentID = 0 + private var recycledIDs = Set() + + func generate() -> EntityID { + if let recycledID = recycledIDs.popFirst() { + return recycledID + } + + let id = currentID + currentID += 1 + return id + } + + func recycle(_ id: EntityID) { + recycledIDs.insert(id) + } +} From f834b1ab6f140b20fd53172fffc79c00a62b5e84 Mon Sep 17 00:00:00 2001 From: ryanpeh Date: Sat, 6 Apr 2024 16:52:46 +0800 Subject: [PATCH 02/43] [ark-multiplayer-kit] feat: add ecs multiplayer to multiplayer manager --- ArkKit.xcodeproj/project.pbxproj | 24 +++++- .../ArkMultiplayerECS.swift | 2 - .../ArkMultiplayerManager.swift | 31 ++++---- .../data-serialize/ArkECSSerializer.swift | 78 +++++++++++++++++++ .../{ => data-serialize}/DataWrapper.swift | 0 .../sendable/SendableComponent.swift | 11 +++ .../sendable/SendableEntity.swift | 9 +++ ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift | 10 +++ .../ark-ecs-kit/EntityManager.swift | 24 +++--- 9 files changed, 157 insertions(+), 32 deletions(-) create mode 100644 ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift rename ArkKit/ark-multiplayer-kit/{ => data-serialize}/DataWrapper.swift (100%) create mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift create mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 386e407..92c03f4 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -195,6 +195,9 @@ ADA847FF2BBC4EA800B19378 /* ArkDataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */; }; ADA848022BBC7DF000B19378 /* ArkSerializableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */; }; ADD74C092BBFEBF3008CE36B /* ArkMultiplayerECS.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */; }; + ADD74C0C2BC05677008CE36B /* ArkECSSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0B2BC05677008CE36B /* ArkECSSerializer.swift */; }; + ADD74C0E2BC0573E008CE36B /* SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */; }; + ADD74C102BC05759008CE36B /* SendableEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0F2BC05759008CE36B /* SendableEntity.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -398,6 +401,9 @@ ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkDataSerializer.swift; sourceTree = ""; }; ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSerializableEvent.swift; sourceTree = ""; }; ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerECS.swift; sourceTree = ""; }; + ADD74C0B2BC05677008CE36B /* ArkECSSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSSerializer.swift; sourceTree = ""; }; + ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableComponent.swift; sourceTree = ""; }; + ADD74C0F2BC05759008CE36B /* SendableEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableEntity.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -998,12 +1004,12 @@ AD2B59AA2BB87F0F00198E99 /* ark-multiplayer-kit */ = { isa = PBXGroup; children = ( + ADD74C0A2BC0555D008CE36B /* sendable */, ADA848002BBC7DC600B19378 /* data-serialize */, AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */, AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */, AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */, ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */, - AD2B59B52BB958E400198E99 /* DataWrapper.swift */, ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */, ); path = "ark-multiplayer-kit"; @@ -1177,12 +1183,23 @@ ADA848002BBC7DC600B19378 /* data-serialize */ = { isa = PBXGroup; children = ( - ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */, ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */, + ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */, + ADD74C0B2BC05677008CE36B /* ArkECSSerializer.swift */, + AD2B59B52BB958E400198E99 /* DataWrapper.swift */, ); path = "data-serialize"; sourceTree = ""; }; + ADD74C0A2BC0555D008CE36B /* sendable */ = { + isa = PBXGroup; + children = ( + ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */, + ADD74C0F2BC05759008CE36B /* SendableEntity.swift */, + ); + path = sendable; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1432,6 +1449,7 @@ 28A032E72BAD783D00851BFF /* SKSimulator.swift in Sources */, 8FEB217E2BADF21E00788E20 /* ArkActionContext.swift in Sources */, 02C394F92BA443830075F1CA /* TapRenderable.swift in Sources */, + ADD74C0C2BC05677008CE36B /* ArkECSSerializer.swift in Sources */, 943D41932BAEC52C00F9E88F /* TankShootSound.swift in Sources */, 280CD3CB2BA7455A00372C5D /* CGVector+Extension.swift in Sources */, 02E0E8F82BA284540043E2BA /* UIKitBitmap.swift in Sources */, @@ -1463,6 +1481,7 @@ 9479A3B12BA953D800F99013 /* ShapeRenderableComponent.swift in Sources */, 02C395012BA5A2BE0075F1CA /* Action.swift in Sources */, 0267BA232BBD0F940010F729 /* SystemRunner.swift in Sources */, + ADD74C0E2BC0573E008CE36B /* SendableComponent.swift in Sources */, 02C394E02BA405C10075F1CA /* Canvas.swift in Sources */, ADA848022BBC7DF000B19378 /* ArkSerializableEvent.swift in Sources */, 942A16772BADF3ED00F0186B /* AudioContext.swift in Sources */, @@ -1484,6 +1503,7 @@ 02C3953B2BAB21CB0075F1CA /* CanvasComponentUpdater.swift in Sources */, 02C394EF2BA420210075F1CA /* ArkGameCoordinator.swift in Sources */, 02C3950B2BA5F8030075F1CA /* ArkECSContext.swift in Sources */, + ADD74C102BC05759008CE36B /* SendableEntity.swift in Sources */, 8FEB218C2BAF453900788E20 /* ArkAnimationsComponent.swift in Sources */, 280CD3E02BA81FBA00372C5D /* DefaultSKPhysicsBodyValues.swift in Sources */, 0267BA1E2BBB07D80010F729 /* ArkUpdateSystem.swift in Sources */, diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift index fa3815e..71f9b55 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift @@ -60,7 +60,6 @@ class ArkMultiplayerECS: ArkECSContext { } arkECS.removeComponent(componentType, from: entity) - delegate?.didRemoveComponent(componentType, from: entity) } func getComponent(ofType type: T.Type, for entity: Entity) -> T? where T: Component { @@ -104,7 +103,6 @@ protocol ArkMultiplayerECSDelegate { var isModificationEnabled: Bool { get } func didCreateEntity(_ entity: Entity) func didRemoveEntity(_ entity: Entity) - func didRemoveComponent(_ componentType: T.Type, from entity: Entity) func didUpsertComponent(_ component: T, to entity: Entity) func didCreateEntity(_ entity: Entity, with components: [Component]) } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index b532db3..db7612d 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -85,16 +85,25 @@ extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { self.role == .master } - private func sendEcsFunction() { + private func sendEcsFunction(function: String, entity: Entity, component: Component? = nil, + components: [Component]? = nil) { + do { + if let encodedECSFunction = try ArkECSSerializer.encodeECSFunction(action: function, entity: entity, + component: component, + components: components) { + networkService.sendData(data: encodedECSFunction) + } + } catch { + print("Error encoding or sending ecs function: \(error)") + } } func didCreateEntity(_ entity: Entity) { guard self.role == .master else { return } - - // Send create entity function to slave + sendEcsFunction(function: "createEntity", entity: entity) } @@ -102,31 +111,21 @@ extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { guard self.role == .master else { return } - - // Send remove entity function to slave - } - func didRemoveComponent(_ componentType: T.Type, from entity: Entity) { - guard self.role == .master else { - return - } - - // Send remove component function to slave + sendEcsFunction(function: "removeEntity", entity: entity) } func didUpsertComponent(_ component: T, to entity: Entity) { guard self.role == .master else { return } - - // Send upsert component function to slave + sendEcsFunction(function: "upsertComponent", entity: entity, component: component) } func didCreateEntity(_ entity: Entity, with components: [Component]) { guard self.role == .master else { return } - - // Send create entity with components function to slave + sendEcsFunction(function: "createEntity", entity: entity, components: components) } } diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift new file mode 100644 index 0000000..f6efdf3 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift @@ -0,0 +1,78 @@ +// +// ArkECSSerializer.swift +// ArkKit +// +// Created by Ryan Peh on 5/4/24. +// + +import Foundation + +struct ArkECSSerializer { + + typealias ECSActionClosure = (ECSFunctionComponentData, ArkECS) -> Void + + enum ECSFunctionType: String, Codable { + case createEntity, removeEntity, upsertComponent + } + + + static func encodeECSFunction(action: String, entity: Entity, component: Component? = nil, + components: [Component]? = nil) throws -> Data? { + let sendableComponent = component as? SendableComponent + let sendableComponents: [SendableComponent]? = components?.compactMap { $0 as? SendableComponent } + + if (component != nil && sendableComponent == nil) || + (components != nil && sendableComponents?.count != components?.count) { + return nil + } + + let componentData = ECSFunctionComponentData(entity: entity, component: sendableComponent, + components: sendableComponents) + let data = try JSONEncoder().encode(componentData) + let wrappedData = DataWrapper(type: .ecsFunction, name: action, payload: data) + + return try JSONEncoder().encode(wrappedData) + } + + static func decodeECSFunction(data: Data, ecs: ArkECS) throws { + // TODO: Not working properly, type erasure happens + let actionsDictionary: [ECSFunctionType: ECSActionClosure] = [ + .createEntity: { componentData, ecs in + if let components = componentData.components { + ecs.createEntity(id: componentData.entity.id, with: components) + } else { + ecs.createEntity(id: componentData.entity.id) + } + }, + .removeEntity: { componentData, ecs in + ecs.removeEntity(componentData.entity) + }, + .upsertComponent: { componentData, ecs in + if let component = componentData.component { + ecs.upsertComponent(component, to: componentData.entity) + } + } + ] + + let decoder = JSONDecoder() + let wrappedData = try decoder.decode(DataWrapper.self, from: data) + + let componentData = try decoder.decode(ECSFunctionComponentData.self, from: wrappedData.payload) + if let functionType = ECSFunctionType(rawValue: wrappedData.name), + let actionClosure = actionsDictionary[functionType] { + actionClosure(componentData, ecs) + } else { + print("Unsupported action: \(wrappedData.name)") + } + } + + struct ECSFunctionComponentData: Codable { + var entity: Entity + var component: SendableComponent? + var components: [SendableComponent]? + } + + + +} + diff --git a/ArkKit/ark-multiplayer-kit/DataWrapper.swift b/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift similarity index 100% rename from ArkKit/ark-multiplayer-kit/DataWrapper.swift rename to ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift new file mode 100644 index 0000000..b546905 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift @@ -0,0 +1,11 @@ +// +// SendableComponent.swift +// ArkKit +// +// Created by Ryan Peh on 5/4/24. +// + +import Foundation + +struct SendableComponent: Component, Codable { +} diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift new file mode 100644 index 0000000..5d0c505 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift @@ -0,0 +1,9 @@ +// +// SendableEntity.swift +// ArkKit +// +// Created by Ryan Peh on 5/4/24. +// + +struct SendableEntity: Codable { +} diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift index 2ecfe6b..4d187b8 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift @@ -28,6 +28,11 @@ extension ArkECS: ArkECSContext { entityManager.createEntity() } + @discardableResult + func createEntity(id: EntityID) -> Entity { + entityManager.createEntity(id: id) + } + func removeEntity(_ entity: Entity) { entityManager.removeEntity(entity) } @@ -48,6 +53,11 @@ extension ArkECS: ArkECSContext { func createEntity(with components: [any Component]) -> Entity { entityManager.createEntity(with: components) } + + @discardableResult + func createEntity(id: EntityID, with components: [any Component]) -> Entity { + entityManager.createEntity(id: id, with: components) + } func getEntity(id: EntityID) -> Entity? { entityManager.getEntity(id: id) diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift index ff6c267..01cdb85 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift @@ -12,17 +12,16 @@ class EntityManager { private var componentsByType = [ObjectIdentifier: [Entity: Component]]() private var idGenerator = EntityIDGenerator() - func createEntity() -> Entity { - let entity = Entity(id: idGenerator.generate()) - entities.insert(entity) - return entity - } +// func createEntity() -> Entity { +// let entity = Entity(id: idGenerator.generate()) +// entities.insert(entity) +// return entity +// } - func createEntity(withId id: EntityID) -> Entity? { - let entity = Entity(id: id) - if entities.contains(entity) { - return nil // Entity with this ID already exists - } + func createEntity(id: EntityID? = nil) -> Entity { + let entityId = id ?? idGenerator.generate() + let entity = Entity(id: entityId) + entities.insert(entity) return entity } @@ -49,8 +48,9 @@ class EntityManager { return componentsByType[typeID]?[entity] as? T } - func createEntity(with components: [Component]) -> Entity { - let entity = Entity() + func createEntity(id: EntityID? = nil, with components: [Component]) -> Entity { + let entityId = id ?? idGenerator.generate() + let entity = Entity(id: entityId) entities.insert(entity) for comp in components { upsertComponent(comp, to: entity) From 9c936d18ffea1f7cb63692bac2d183a26f42ca33 Mon Sep 17 00:00:00 2001 From: ryanpeh Date: Sat, 6 Apr 2024 19:39:36 +0800 Subject: [PATCH 03/43] [ark-multiplayer-kit] feat: fix part of ecs serialization --- ArkKit.xcodeproj/project.pbxproj | 4 + ArkKit/ArkBlueprint.swift | 2 +- .../ArkMultiplayerECS.swift | 22 ++-- .../ArkMultiplayerManager.swift | 29 ++--- .../ArkNetworkService.swift | 8 +- .../data-serialize/ArkECSSerializer.swift | 109 ++++++++++-------- .../sendable/SendableComponent.swift | 2 +- ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift | 6 +- .../ark-ecs-kit/ComponentRegistry.swift | 68 +++++++++++ .../ark-ecs-kit/EntityManager.swift | 2 +- .../entity/EntityIDGenerator.swift | 2 +- 11 files changed, 171 insertions(+), 83 deletions(-) create mode 100644 ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 92c03f4..2bdfd02 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -198,6 +198,7 @@ ADD74C0C2BC05677008CE36B /* ArkECSSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0B2BC05677008CE36B /* ArkECSSerializer.swift */; }; ADD74C0E2BC0573E008CE36B /* SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */; }; ADD74C102BC05759008CE36B /* SendableEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0F2BC05759008CE36B /* SendableEntity.swift */; }; + ADD74C122BC15C52008CE36B /* ComponentRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -404,6 +405,7 @@ ADD74C0B2BC05677008CE36B /* ArkECSSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSSerializer.swift; sourceTree = ""; }; ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableComponent.swift; sourceTree = ""; }; ADD74C0F2BC05759008CE36B /* SendableEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableEntity.swift; sourceTree = ""; }; + ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentRegistry.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1077,6 +1079,7 @@ AD787A872B9C6D3A003EBBD0 /* EntityManager.swift */, AD787A8B2B9C70FC003EBBD0 /* SystemManager.swift */, AD6E03602B9F15FA00974EBF /* ArkECS.swift */, + ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */, ); path = "ark-ecs-kit"; sourceTree = ""; @@ -1440,6 +1443,7 @@ 28A032E52BAD781900851BFF /* AbstractPhysicsArkSimulator.swift in Sources */, 02C395152BA83FAE0075F1CA /* RenderableComponent.swift in Sources */, AD787A8A2B9C70D8003EBBD0 /* ArkState.swift in Sources */, + ADD74C122BC15C52008CE36B /* ComponentRegistry.swift in Sources */, 28D006572BAF116A001B4BD4 /* TankGameCollisionStrategyManager.swift in Sources */, AD787A532B9C636F003EBBD0 /* SceneDelegate.swift in Sources */, 8FEB218A2BAF3C8E00788E20 /* ImpactExplosionAnimation.swift in Sources */, diff --git a/ArkKit/ArkBlueprint.swift b/ArkKit/ArkBlueprint.swift index f0ad5ec..79f4987 100644 --- a/ArkKit/ArkBlueprint.swift +++ b/ArkKit/ArkBlueprint.swift @@ -74,7 +74,7 @@ struct ArkBlueprint { let multiplayerManager = ArkMultiplayerManager(serviceName: serviceName) let multiplayerEventManager = ArkMultiplayerEventManager(arkEventManager: events, - networkManagerDelegate: multiplayerManager) + delegate: multiplayerManager) multiplayerManager.multiplayerEventManager = multiplayerEventManager events.delegate = multiplayerEventManager diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift index 71f9b55..64d8d17 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift @@ -3,7 +3,7 @@ import Foundation class ArkMultiplayerECS: ArkECSContext { let arkECS: ArkECS var delegate: ArkMultiplayerECSDelegate? - + init(arkECS: ArkECS = ArkECS(), delegate: ArkMultiplayerECSDelegate? = nil) { self.arkECS = arkECS @@ -17,7 +17,7 @@ class ArkMultiplayerECS: ArkECSContext { guard delegate?.isModificationEnabled ?? true else { return } - + arkECS.update(deltaTime: deltaTime) } @@ -27,20 +27,20 @@ class ArkMultiplayerECS: ArkECSContext { @discardableResult func createEntity() -> Entity { - guard delegate?.isModificationEnabled ?? true else { + guard delegate?.isModificationEnabled ?? true else { return Entity() } - + let entity = arkECS.createEntity() delegate?.didCreateEntity(entity) return entity } - + func removeEntity(_ entity: Entity) { guard delegate?.isModificationEnabled ?? true else { return } - + arkECS.removeEntity(entity) delegate?.didRemoveEntity(entity) } @@ -58,12 +58,12 @@ class ArkMultiplayerECS: ArkECSContext { guard delegate?.isModificationEnabled ?? true else { return } - + arkECS.removeComponent(componentType, from: entity) } func getComponent(ofType type: T.Type, for entity: Entity) -> T? where T: Component { - return arkECS.getComponent(ofType: type, for: entity) + arkECS.getComponent(ofType: type, for: entity) } @discardableResult @@ -71,10 +71,10 @@ class ArkMultiplayerECS: ArkECSContext { guard delegate?.isModificationEnabled ?? true else { return Entity() } - + let entity = arkECS.createEntity(with: components) delegate?.didCreateEntity(entity, with: components) - + return entity } @@ -94,7 +94,7 @@ class ArkMultiplayerECS: ArkECSContext { guard delegate?.isModificationEnabled ?? true else { return } - + arkECS.addSystem(system, schedule: .update, isUnique: isUnique) } } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index db7612d..631b083 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -35,11 +35,13 @@ class ArkMultiplayerManager: ArkNetworkDelegate { typeName: wrappedData.name) { processEvent(event: event) } - - if wrappedData.type == .ecsFunction { - + + if wrappedData.type == .ecsFunction, let arkMultiplayerECS = arkMultiplayerECS { + try ArkECSSerializer.decodeECSFunction(data: wrappedData.payload, + name: wrappedData.name, + ecs: arkMultiplayerECS.arkECS) } - + } catch { print("Error decoding received data: \(error)") } @@ -53,7 +55,7 @@ class ArkMultiplayerManager: ArkNetworkDelegate { peers = connectedDevices updateRoles() } - + private func updateRoles() { let sortedPeers = (peers + [networkService.deviceID]).sorted() masterPeer = sortedPeers.first @@ -68,7 +70,7 @@ extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { func shouldSendEvent(_ event: Event) { sendEvent(event: event) } - + private func sendEvent(event: any ArkEvent) { do { if let encodedEvent = try ArkDataSerializer.encodeEvent(event) { @@ -84,10 +86,10 @@ extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { var isModificationEnabled: Bool { self.role == .master } - + private func sendEcsFunction(function: String, entity: Entity, component: Component? = nil, components: [Component]? = nil) { - + do { if let encodedECSFunction = try ArkECSSerializer.encodeECSFunction(action: function, entity: entity, component: component, @@ -98,34 +100,33 @@ extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { print("Error encoding or sending ecs function: \(error)") } } - + func didCreateEntity(_ entity: Entity) { guard self.role == .master else { return } sendEcsFunction(function: "createEntity", entity: entity) } - - + func didRemoveEntity(_ entity: Entity) { guard self.role == .master else { return } sendEcsFunction(function: "removeEntity", entity: entity) } - + func didUpsertComponent(_ component: T, to entity: Entity) { guard self.role == .master else { return } sendEcsFunction(function: "upsertComponent", entity: entity, component: component) } - + func didCreateEntity(_ entity: Entity, with components: [Component]) { guard self.role == .master else { return } sendEcsFunction(function: "createEntity", entity: entity, components: components) } - + } diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift index 0334fca..fee180b 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift @@ -28,7 +28,7 @@ class ArkNetworkService: ArkNetworkProtocol { deinit { session.stopSharing() } - + var deviceID: String { UIDevice.current.name } @@ -38,7 +38,7 @@ class ArkNetworkService: ArkNetworkProtocol { guard let strongSelf = self else { return } - + strongSelf.peers = peers strongSelf.delegate?.connectedDevicesChanged(manager: strongSelf, connectedDevices: peers.map { $0.peerID }) print("Peers changed: \(peers.map { $0.info["name"] ?? $0.peerID })") @@ -58,12 +58,12 @@ class ArkNetworkService: ArkNetworkProtocol { session.sendToAllPeers(data: data) } } - + func sendData(_ data: Data, to peerName: String) { guard let peerInfo = peers.first(where: { $0.info["name"] == peerName }) else { return } - + session.send(to: peerInfo.peerID, data: data) } } diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift index f6efdf3..4781a09 100644 --- a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift @@ -8,71 +8,86 @@ import Foundation struct ArkECSSerializer { - - typealias ECSActionClosure = (ECSFunctionComponentData, ArkECS) -> Void - + + typealias ECSActionClosure = (Entity, [Component], ArkECS) -> Void + enum ECSFunctionType: String, Codable { case createEntity, removeEntity, upsertComponent } - + static let actionsDictionary: [ECSFunctionType: ECSActionClosure] = [ + .createEntity: { entity, components, ecs in + if components.isEmpty { + ecs.createEntity(id: entity.id) + } else { + ecs.createEntity(id: entity.id, with: components) + } + }, + .removeEntity: { entity, _, ecs in + ecs.removeEntity(entity) + }, + .upsertComponent: { entity, components, ecs in + if let component = components.first { + ecs.upsertComponent(component, to: entity) + } + } + ] + + private static func encodeComponent(_ component: T) throws -> ComponentData? { + guard let component = component as? any SendableComponent else { + return nil + } + let componentData = try JSONEncoder().encode(component) + let name = String(describing: type(of: component)) + + return ComponentData(name: name, data: componentData) + } + static func encodeECSFunction(action: String, entity: Entity, component: Component? = nil, components: [Component]? = nil) throws -> Data? { - let sendableComponent = component as? SendableComponent - let sendableComponents: [SendableComponent]? = components?.compactMap { $0 as? SendableComponent } - - if (component != nil && sendableComponent == nil) || - (components != nil && sendableComponents?.count != components?.count) { - return nil + + var componentData = [ComponentData]() + + if let component = component, + let encodedComponent = try encodeComponent(component) { + componentData.append(encodedComponent) } - - let componentData = ECSFunctionComponentData(entity: entity, component: sendableComponent, - components: sendableComponents) - let data = try JSONEncoder().encode(componentData) + + componentData += components?.compactMap { try? encodeComponent($0) } ?? [] + + let ecsfunctiondata = ECSFunctionData(entity: entity, componentData: componentData) + let data = try JSONEncoder().encode(ecsfunctiondata) let wrappedData = DataWrapper(type: .ecsFunction, name: action, payload: data) - + return try JSONEncoder().encode(wrappedData) } - - static func decodeECSFunction(data: Data, ecs: ArkECS) throws { - // TODO: Not working properly, type erasure happens - let actionsDictionary: [ECSFunctionType: ECSActionClosure] = [ - .createEntity: { componentData, ecs in - if let components = componentData.components { - ecs.createEntity(id: componentData.entity.id, with: components) - } else { - ecs.createEntity(id: componentData.entity.id) - } - }, - .removeEntity: { componentData, ecs in - ecs.removeEntity(componentData.entity) - }, - .upsertComponent: { componentData, ecs in - if let component = componentData.component { - ecs.upsertComponent(component, to: componentData.entity) - } - } - ] - + + static func decodeECSFunction(data: Data, name: String, ecs: ArkECS) throws { let decoder = JSONDecoder() - let wrappedData = try decoder.decode(DataWrapper.self, from: data) + let functionData = try decoder.decode(ECSFunctionData.self, from: data) + + let entity = functionData.entity + let components = functionData.componentData + .compactMap { try? ComponentRegistry.shared.decode(from: $0.data, + typeName: $0.name) + } - let componentData = try decoder.decode(ECSFunctionComponentData.self, from: wrappedData.payload) - if let functionType = ECSFunctionType(rawValue: wrappedData.name), + if let functionType = ECSFunctionType(rawValue: name), let actionClosure = actionsDictionary[functionType] { - actionClosure(componentData, ecs) + actionClosure(entity, components, ecs) } else { - print("Unsupported action: \(wrappedData.name)") + print("Unsupported action: \(functionData)") } } - struct ECSFunctionComponentData: Codable { + struct ComponentData: Codable { + var name: String + var data: Data + } + + struct ECSFunctionData: Codable { var entity: Entity - var component: SendableComponent? - var components: [SendableComponent]? + var componentData: [ComponentData] } - - } - diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift index b546905..62cb06d 100644 --- a/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift @@ -7,5 +7,5 @@ import Foundation -struct SendableComponent: Component, Codable { +protocol SendableComponent: Component, Codable { } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift index 4d187b8..ebc4914 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift @@ -27,12 +27,12 @@ extension ArkECS: ArkECSContext { func createEntity() -> Entity { entityManager.createEntity() } - + @discardableResult func createEntity(id: EntityID) -> Entity { entityManager.createEntity(id: id) } - + func removeEntity(_ entity: Entity) { entityManager.removeEntity(entity) } @@ -53,7 +53,7 @@ extension ArkECS: ArkECSContext { func createEntity(with components: [any Component]) -> Entity { entityManager.createEntity(with: components) } - + @discardableResult func createEntity(id: EntityID, with components: [any Component]) -> Entity { entityManager.createEntity(id: id, with: components) diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift new file mode 100644 index 0000000..a9411b7 --- /dev/null +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift @@ -0,0 +1,68 @@ +// +// ComponentRegistry.swift +// ArkKit +// +// Created by Ryan Peh on 6/4/24. +// + +import Foundation + +class ComponentRegistry { + static let shared = ComponentRegistry() + + private init() { + + } + + private var componentTypes: [String: (Data) throws -> any Component] = [:] + + // Registers an event type with its corresponding decoder + func register(_ componentType: T.Type) { + guard let componentType = componentType as? any SendableComponent.Type else { + return + } + let typeName = String(describing: T.self) + componentTypes[typeName] = { data in + try JSONDecoder().decode(componentType, from: data) + } + } + + func decode(from data: Data, typeName: String) throws -> (any Component)? { + guard let decoder = componentTypes[typeName] else { + return nil + } + return try decoder(data) + } + + private func setUpComponentTypes() { + let componentTypes = subclasses(of: SendableComponent.self) + for componentType in componentTypes { + if let componentType = componentType as? any Component.Type { + register(componentType) + } + } + } + + private func subclasses(of theClass: T) -> [T] { + var count: UInt32 = 0, result: [T] = [] + let classList = objc_copyClassList(&count)! + defer { free(UnsafeMutableRawPointer(classList)) } + let classes = UnsafeBufferPointer(start: classList, count: Int(count)) + let classPtr = address(of: theClass) + + for someClass in classes { + guard let someSuperClass = class_getSuperclass(someClass), address(of: someSuperClass) == classPtr else { + continue + } + // swiftlint:disable force_cast + result.append(someClass as! T) + // swiftlint:enable force_cast + } + + return result + } + + private func address(of object: Any?) -> UnsafeMutableRawPointer { + Unmanaged.passUnretained(object as AnyObject).toOpaque() + } +} diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift index 01cdb85..772e9ee 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift @@ -17,7 +17,7 @@ class EntityManager { // entities.insert(entity) // return entity // } - + func createEntity(id: EntityID? = nil) -> Entity { let entityId = id ?? idGenerator.generate() let entity = Entity(id: entityId) diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift b/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift index a89ee28..3c904d3 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift @@ -15,7 +15,7 @@ class EntityIDGenerator { if let recycledID = recycledIDs.popFirst() { return recycledID } - + let id = currentID currentID += 1 return id From 672d6debb3502d51dd694deffe84896a91be0997 Mon Sep 17 00:00:00 2001 From: ryanpeh Date: Sat, 6 Apr 2024 21:53:52 +0800 Subject: [PATCH 04/43] [ark-multiplayer-kit] refactor: refactor multiplayer --- ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift | 2 +- ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift | 9 ++++----- ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift index ebc4914..b039c2e 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift @@ -56,7 +56,7 @@ extension ArkECS: ArkECSContext { @discardableResult func createEntity(id: EntityID, with components: [any Component]) -> Entity { - entityManager.createEntity(id: id, with: components) + entityManager.createEntity(with: components, id: id) } func getEntity(id: EntityID) -> Entity? { diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift index a9411b7..d4899c0 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift @@ -51,14 +51,13 @@ class ComponentRegistry { let classPtr = address(of: theClass) for someClass in classes { - guard let someSuperClass = class_getSuperclass(someClass), address(of: someSuperClass) == classPtr else { + guard let someSuperClass = class_getSuperclass(someClass), + address(of: someSuperClass) == classPtr, + let castedClass = someClass as? T else { continue } - // swiftlint:disable force_cast - result.append(someClass as! T) - // swiftlint:enable force_cast + result.append(castedClass) } - return result } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift index 772e9ee..038c29b 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift @@ -48,7 +48,7 @@ class EntityManager { return componentsByType[typeID]?[entity] as? T } - func createEntity(id: EntityID? = nil, with components: [Component]) -> Entity { + func createEntity(with components: [Component], id: EntityID? = nil) -> Entity { let entityId = id ?? idGenerator.generate() let entity = Entity(id: entityId) entities.insert(entity) From 84b13c4086751ada03907aa3dec26fa83b9a7cc6 Mon Sep 17 00:00:00 2001 From: ryanpeh Date: Sun, 7 Apr 2024 01:36:46 +0800 Subject: [PATCH 05/43] [ark-multiplayer-kit] refactor: integrate multiplayer into ark setup --- ArkGameExample/SceneDelegate.swift | 2 +- ArkGameExample/TankGame/TankGameManager.swift | 2 +- ArkKit.xcodeproj/project.pbxproj | 14 +++---- .../xcshareddata/swiftpm/Package.resolved | 3 +- ArkKit/Ark.swift | 17 ++++++++- ArkKit/ArkBlueprint.swift | 38 +++++++++---------- ArkKit/ark-event-kit/ArkEventContext.swift | 1 - ArkKit/ark-event-kit/ArkEventManager.swift | 6 --- .../ArkMultiplayerECS.swift | 31 ++++++--------- .../ArkMultiplayerEventManager.swift | 26 +++++++++---- .../ArkMultiplayerManager.swift | 13 +++++-- .../ArkNetworkService.swift | 2 +- ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift | 4 +- .../ark-ecs-kit/ComponentRegistry.swift | 19 +++++++++- .../ark-ecs-kit/EntityManager.swift | 6 --- 15 files changed, 104 insertions(+), 80 deletions(-) diff --git a/ArkGameExample/SceneDelegate.swift b/ArkGameExample/SceneDelegate.swift index 87de1b5..18dfe2a 100644 --- a/ArkGameExample/SceneDelegate.swift +++ b/ArkGameExample/SceneDelegate.swift @@ -57,7 +57,7 @@ extension SceneDelegate { guard let rootView = window.rootViewController as? any AbstractRootView else { return } - ark = Ark(rootView: rootView, blueprint: blueprint) + ark = Ark(rootView: rootView, blueprint: blueprint, multiplayer: true) ark?.start() } } diff --git a/ArkGameExample/TankGame/TankGameManager.swift b/ArkGameExample/TankGame/TankGameManager.swift index 66f4d39..7f9ff4e 100644 --- a/ArkGameExample/TankGame/TankGameManager.swift +++ b/ArkGameExample/TankGame/TankGameManager.swift @@ -126,7 +126,7 @@ class TankGameManager { } func setUpRules() { - blueprint = blueprint.setupMultiplayer(serviceName: "tankGame") +// blueprint = blueprint.setupMultiplayer(serviceName: "tankGame") blueprint = blueprint .on(ScreenResizeEvent.self) { event, context in diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index b8db4fb..2460c87 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -195,7 +195,6 @@ AD787A882B9C6D3A003EBBD0 /* EntityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD787A872B9C6D3A003EBBD0 /* EntityManager.swift */; }; AD787A8A2B9C70D8003EBBD0 /* ArkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD787A892B9C70D8003EBBD0 /* ArkState.swift */; }; AD787A8C2B9C70FC003EBBD0 /* SystemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD787A8B2B9C70FC003EBBD0 /* SystemManager.swift */; }; - ADA847F42BBC11F400B19378 /* P2PShare in Frameworks */ = {isa = PBXBuildFile; productRef = ADA847F32BBC11F400B19378 /* P2PShare */; }; ADA847FD2BBC4B5500B19378 /* ArkNetworkProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */; }; ADA847FF2BBC4EA800B19378 /* ArkDataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */; }; ADA848022BBC7DF000B19378 /* ArkSerializableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */; }; @@ -204,6 +203,7 @@ ADD74C0E2BC0573E008CE36B /* SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */; }; ADD74C102BC05759008CE36B /* SendableEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0F2BC05759008CE36B /* SendableEntity.swift */; }; ADD74C122BC15C52008CE36B /* ComponentRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */; }; + ADD74C162BC19232008CE36B /* P2PShare in Frameworks */ = {isa = PBXBuildFile; productRef = ADD74C152BC19232008CE36B /* P2PShare */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -423,7 +423,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - ADA847F42BBC11F400B19378 /* P2PShare in Frameworks */, + ADD74C162BC19232008CE36B /* P2PShare in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1260,7 +1260,7 @@ ); name = ArkKit; packageProductDependencies = ( - ADA847F32BBC11F400B19378 /* P2PShare */, + ADD74C152BC19232008CE36B /* P2PShare */, ); productName = LevelKit; productReference = AD787A4D2B9C636F003EBBD0 /* ArkKit.app */; @@ -1313,7 +1313,7 @@ ); mainGroup = AD787A442B9C636F003EBBD0; packageReferences = ( - ADA847F22BBC11F400B19378 /* XCRemoteSwiftPackageReference "P2PShareKit" */, + ADD74C142BC19232008CE36B /* XCRemoteSwiftPackageReference "P2PShareKit" */, ); productRefGroup = AD787A4E2B9C636F003EBBD0 /* Products */; projectDirPath = ""; @@ -1873,7 +1873,7 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - ADA847F22BBC11F400B19378 /* XCRemoteSwiftPackageReference "P2PShareKit" */ = { + ADD74C142BC19232008CE36B /* XCRemoteSwiftPackageReference "P2PShareKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/dobster/P2PShareKit/"; requirement = { @@ -1884,9 +1884,9 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - ADA847F32BBC11F400B19378 /* P2PShare */ = { + ADD74C152BC19232008CE36B /* P2PShare */ = { isa = XCSwiftPackageProductDependency; - package = ADA847F22BBC11F400B19378 /* XCRemoteSwiftPackageReference "P2PShareKit" */; + package = ADD74C142BC19232008CE36B /* XCRemoteSwiftPackageReference "P2PShareKit" */; productName = P2PShare; }; /* End XCSwiftPackageProductDependency section */ diff --git a/ArkKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ArkKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 491a139..7a3d0b9 100644 --- a/ArkKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ArkKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "5b4ee4b3c55bec1dd1630d4cab7f72225b9ac100b34f7f97bad2539bbfd1db4e", "pins" : [ { "identity" : "p2psharekit", @@ -11,5 +10,5 @@ } } ], - "version" : 3 + "version" : 2 } diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index 0dfcb1d..154753c 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -37,7 +37,8 @@ class Ark { init(rootView: any AbstractRootView, blueprint: ArkBlueprint, - canvasRenderer: (any RenderableBuilder)? = nil) { + canvasRenderer: (any RenderableBuilder)? = nil, + multiplayer: Bool = false) { self.rootView = rootView self.blueprint = blueprint let eventManager = ArkEventManager() @@ -45,6 +46,10 @@ class Ark { self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) self.audioContext = ArkAudioPlayer() self.canvasRenderer = canvasRenderer + + if multiplayer { + setupMultiplayer() + } } func start() { @@ -66,6 +71,16 @@ class Ark { gameCoordinator.start() } + private func setupMultiplayer() { + let multiplayerManager = ArkMultiplayerManager(serviceName: "tankGame") + let eventManager = ArkMultiplayerEventManager() + let ecsManager = ArkMultiplayerECS() + eventManager.delegate = multiplayerManager + ecsManager.delegate = multiplayerManager + + self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) + } + private func setup(_ rules: [any Rule]) { // filter for event-based rules only let eventRules: [any Rule] = rules.filter { rule in diff --git a/ArkKit/ArkBlueprint.swift b/ArkKit/ArkBlueprint.swift index 3b1f6bd..9e09dc3 100644 --- a/ArkKit/ArkBlueprint.swift +++ b/ArkKit/ArkBlueprint.swift @@ -69,23 +69,23 @@ struct ArkBlueprint { return newSelf } - func setupMultiplayer(serviceName: String = "Ark") -> Self { - let fn: ArkStateSetupDelegate = { context in - var events = context.events - - let multiplayerManager = ArkMultiplayerManager(serviceName: serviceName) - let multiplayerEventManager = ArkMultiplayerEventManager(arkEventManager: events, - delegate: multiplayerManager) - multiplayerManager.multiplayerEventManager = multiplayerEventManager - - events.delegate = multiplayerEventManager - } - - var stateSetupFunctionsCopy = setupFunctions - stateSetupFunctionsCopy.insert(fn, at: 0) - - var newSelf = self - newSelf.setupFunctions = stateSetupFunctionsCopy - return newSelf - } +// func setupMultiplayer(serviceName: String = "Ark") -> Self { +// let fn: ArkStateSetupDelegate = { context in +// var events = context.events +// +// let multiplayerManager = ArkMultiplayerManager(serviceName: serviceName) +// let multiplayerEventManager = ArkMultiplayerEventManager(arkEventManager: events, +// delegate: multiplayerManager) +// multiplayerManager.multiplayerEventManager = multiplayerEventManager +// +// events.delegate = multiplayerEventManager +// } +// +// var stateSetupFunctionsCopy = setupFunctions +// stateSetupFunctionsCopy.insert(fn, at: 0) +// +// var newSelf = self +// newSelf.setupFunctions = stateSetupFunctionsCopy +// return newSelf +// } } diff --git a/ArkKit/ark-event-kit/ArkEventContext.swift b/ArkKit/ark-event-kit/ArkEventContext.swift index baaee9a..cf87a61 100644 --- a/ArkKit/ark-event-kit/ArkEventContext.swift +++ b/ArkKit/ark-event-kit/ArkEventContext.swift @@ -1,6 +1,5 @@ protocol ArkEventContext { typealias EventListener = (any ArkEvent) -> Void - var delegate: ArkEventContextDelegate? { get set } func emit(_ event: Event) func emitWithoutDelegate(_ event: Event) diff --git a/ArkKit/ark-event-kit/ArkEventManager.swift b/ArkKit/ark-event-kit/ArkEventManager.swift index ea1bd2f..872dd6c 100644 --- a/ArkKit/ark-event-kit/ArkEventManager.swift +++ b/ArkKit/ark-event-kit/ArkEventManager.swift @@ -15,7 +15,6 @@ struct DatedEvent { class ArkEventManager: ArkEventContext { private var listeners: [ObjectIdentifier: [(any ArkEvent) -> Void]] = [:] private var eventQueue = PriorityQueue(sort: ArkEventManager.compareEventPriority) - var delegate: ArkEventContextDelegate? func subscribe(to eventType: Event.Type, _ listener: @escaping (any ArkEvent) -> Void) { let typeID = ObjectIdentifier(eventType) @@ -30,7 +29,6 @@ class ArkEventManager: ArkEventContext { func emit(_ event: Event) { let datedEvent = DatedEvent(event: event) eventQueue.enqueue(datedEvent) - delegate?.didEmitEvent(event) } func emitWithoutDelegate(_ event: Event) { @@ -63,7 +61,3 @@ class ArkEventManager: ArkEventContext { return datedEvent1.timestamp < datedEvent2.timestamp } } - -protocol ArkEventManagerDelegate: ArkEventContextDelegate { - func didEmitEvent(_ event: Event) -} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift index 64d8d17..2887706 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift @@ -1,6 +1,6 @@ import Foundation -class ArkMultiplayerECS: ArkECSContext { +class ArkMultiplayerECS: ArkECS { let arkECS: ArkECS var delegate: ArkMultiplayerECSDelegate? @@ -9,11 +9,8 @@ class ArkMultiplayerECS: ArkECSContext { self.arkECS = arkECS self.delegate = delegate } - func startUp() { - arkECS.startUp() - } - func update(deltaTime: TimeInterval) { + override func update(deltaTime: TimeInterval) { guard delegate?.isModificationEnabled ?? true else { return } @@ -21,12 +18,8 @@ class ArkMultiplayerECS: ArkECSContext { arkECS.update(deltaTime: deltaTime) } - func cleanUp() { - arkECS.cleanUp() - } - @discardableResult - func createEntity() -> Entity { + override func createEntity() -> Entity { guard delegate?.isModificationEnabled ?? true else { return Entity() } @@ -36,7 +29,7 @@ class ArkMultiplayerECS: ArkECSContext { return entity } - func removeEntity(_ entity: Entity) { + override func removeEntity(_ entity: Entity) { guard delegate?.isModificationEnabled ?? true else { return } @@ -45,7 +38,7 @@ class ArkMultiplayerECS: ArkECSContext { delegate?.didRemoveEntity(entity) } - func upsertComponent(_ component: T, to entity: Entity) where T: Component { + override func upsertComponent(_ component: T, to entity: Entity) where T: Component { guard delegate?.isModificationEnabled ?? true else { return } @@ -54,7 +47,7 @@ class ArkMultiplayerECS: ArkECSContext { delegate?.didUpsertComponent(component, to: entity) } - func removeComponent(_ componentType: T.Type, from entity: Entity) where T: Component { + override func removeComponent(_ componentType: T.Type, from entity: Entity) where T: Component { guard delegate?.isModificationEnabled ?? true else { return } @@ -62,12 +55,12 @@ class ArkMultiplayerECS: ArkECSContext { arkECS.removeComponent(componentType, from: entity) } - func getComponent(ofType type: T.Type, for entity: Entity) -> T? where T: Component { + override func getComponent(ofType type: T.Type, for entity: Entity) -> T? where T: Component { arkECS.getComponent(ofType: type, for: entity) } @discardableResult - func createEntity(with components: [any Component]) -> Entity { + override func createEntity(with components: [any Component]) -> Entity { guard delegate?.isModificationEnabled ?? true else { return Entity() } @@ -78,19 +71,19 @@ class ArkMultiplayerECS: ArkECSContext { return entity } - func getEntity(id: EntityID) -> Entity? { + override func getEntity(id: EntityID) -> Entity? { arkECS.getEntity(id: id) } - func getEntities(with componentTypes: [any Component.Type]) -> [Entity] { + override func getEntities(with componentTypes: [any Component.Type]) -> [Entity] { arkECS.getEntities(with: componentTypes) } - func getComponents(from entity: Entity) -> [any Component] { + override func getComponents(from entity: Entity) -> [any Component] { arkECS.getComponents(from: entity) } - func addSystem(_ system: UpdateSystem, schedule: Schedule = .update, isUnique: Bool = true) { + override func addSystem(_ system: UpdateSystem, schedule: Schedule = .update, isUnique: Bool = true) { guard delegate?.isModificationEnabled ?? true else { return } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift index d1661e8..54aca1c 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift @@ -7,25 +7,37 @@ import Foundation -class ArkMultiplayerEventManager: ArkEventManagerDelegate { - private var arkEventManager: ArkEventContext +class ArkMultiplayerEventManager: ArkEventManager { + private var arkEventManager: ArkEventManager var delegate: ArkMultiplayerEventManagerDelegate? - init(arkEventManager: ArkEventContext = ArkEventManager(), + init(arkEventManager: ArkEventManager = ArkEventManager(), delegate: ArkMultiplayerEventManagerDelegate? = nil) { self.arkEventManager = arkEventManager self.delegate = delegate } - func didEmitEvent(_ event: Event) where Event: ArkEvent { - delegate?.shouldSendEvent(event) + override func subscribe(to eventType: Event.Type, _ listener: @escaping (any ArkEvent) -> Void) { + arkEventManager.subscribe(to: eventType, listener) } - func emitWithoutBroadcast(_ event: Event) where Event: ArkEvent { - arkEventManager.emitWithoutDelegate(event) + override func emit(_ event: Event) { + if delegate?.isBroadcastEvent == true { + delegate?.shouldSendEvent(event) + } + arkEventManager.emit(event) + } + + func emitWithoutBroadcast(_ event: Event) { + arkEventManager.emit(event) + } + + override func processEvents() { + arkEventManager.processEvents() } } protocol ArkMultiplayerEventManagerDelegate: AnyObject { + var isBroadcastEvent: Bool { get } func shouldSendEvent(_ event: Event) } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index 631b083..7f58822 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -67,14 +67,19 @@ class ArkMultiplayerManager: ArkNetworkDelegate { } extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { + var isBroadcastEvent: Bool { + self.role == .slave + } + func shouldSendEvent(_ event: Event) { sendEvent(event: event) } private func sendEvent(event: any ArkEvent) { do { - if let encodedEvent = try ArkDataSerializer.encodeEvent(event) { - networkService.sendData(data: encodedEvent) + if let encodedEvent = try ArkDataSerializer.encodeEvent(event), + let target = masterPeer { + networkService.sendData(encodedEvent, to: target) } } catch { print("Error encoding or sending event: \(error)") @@ -92,8 +97,8 @@ extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { do { if let encodedECSFunction = try ArkECSSerializer.encodeECSFunction(action: function, entity: entity, - component: component, - components: components) { + component: component, + components: components) { networkService.sendData(data: encodedECSFunction) } } catch { diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift index fee180b..642f6fd 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift @@ -30,7 +30,7 @@ class ArkNetworkService: ArkNetworkProtocol { } var deviceID: String { - UIDevice.current.name + myPeerInfo.peerID } private func setUpHandlers() { diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift index b039c2e..c11df2c 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift @@ -1,6 +1,6 @@ import Foundation -class ArkECS { +class ArkECS: ArkECSContext { private let entityManager: EntityManager private let systemManager: SystemManager @@ -20,9 +20,7 @@ class ArkECS { func cleanUp() { self.systemManager.cleanUp() } -} -extension ArkECS: ArkECSContext { @discardableResult func createEntity() -> Entity { entityManager.createEntity() diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift index d4899c0..19ca064 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift @@ -11,12 +11,12 @@ class ComponentRegistry { static let shared = ComponentRegistry() private init() { - +// setUpComponentTypes() + loadComponentTypes() } private var componentTypes: [String: (Data) throws -> any Component] = [:] - // Registers an event type with its corresponding decoder func register(_ componentType: T.Type) { guard let componentType = componentType as? any SendableComponent.Type else { return @@ -34,8 +34,23 @@ class ComponentRegistry { return try decoder(data) } + private func loadComponentTypes() { + let componentTypes: [Component.Type] = [ + PositionComponent.self, + CameraContainerComponent.self, + PhysicsComponent.self, + ArkAnimationsComponent.self, + ContainerRenderableComponent.self, + BitmapImageRenderableComponent.self, + RotationComponent.self, + WorldComponent.self, + StopWatchComponent.self + ] + } + private func setUpComponentTypes() { let componentTypes = subclasses(of: SendableComponent.self) + print("subclasses: \(componentTypes)") for componentType in componentTypes { if let componentType = componentType as? any Component.Type { register(componentType) diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift index 038c29b..ae10dd8 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift @@ -12,12 +12,6 @@ class EntityManager { private var componentsByType = [ObjectIdentifier: [Entity: Component]]() private var idGenerator = EntityIDGenerator() -// func createEntity() -> Entity { -// let entity = Entity(id: idGenerator.generate()) -// entities.insert(entity) -// return entity -// } - func createEntity(id: EntityID? = nil) -> Entity { let entityId = id ?? idGenerator.generate() let entity = Entity(id: entityId) From 05e731894fb102e74603ba85844aa6e2e67ecadf Mon Sep 17 00:00:00 2001 From: ryanpeh Date: Sun, 7 Apr 2024 15:04:10 +0800 Subject: [PATCH 06/43] [ark-multiplayer-kit] feat: add multiplayer context --- ArkKit.xcodeproj/project.pbxproj | 4 ++++ .../ArkMultiplayerContext.swift | 13 +++++++++++ .../ArkMultiplayerManager.swift | 22 +++++++++++++++++-- .../ArkNetworkProtocol.swift | 1 + .../ArkNetworkService.swift | 2 ++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 2460c87..bb93813 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -173,6 +173,7 @@ AD3ECF432BAF3148002C758D /* ArkEventKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3ECF422BAF3148002C758D /* ArkEventKitTests.swift */; }; AD3ECF472BB01ABC002C758D /* SystemManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3ECF462BB01ABC002C758D /* SystemManagerTests.swift */; }; AD3ECF492BB01CF2002C758D /* ArkECSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3ECF482BB01CF2002C758D /* ArkECSTests.swift */; }; + AD4E14902BC2797000A32C8B /* ArkMultiplayerContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD4E148F2BC2797000A32C8B /* ArkMultiplayerContext.swift */; }; AD5F69752BAC879400A5518D /* TankShootEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD5F69742BAC879400A5518D /* TankShootEvent.swift */; }; AD6E03612B9F15FA00974EBF /* ArkECS.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6E03602B9F15FA00974EBF /* ArkECS.swift */; }; AD6E03652BA1949000974EBF /* ArkEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6E03642BA1949000974EBF /* ArkEvent.swift */; }; @@ -383,6 +384,7 @@ AD3ECF422BAF3148002C758D /* ArkEventKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkEventKitTests.swift; sourceTree = ""; }; AD3ECF462BB01ABC002C758D /* SystemManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemManagerTests.swift; sourceTree = ""; }; AD3ECF482BB01CF2002C758D /* ArkECSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSTests.swift; sourceTree = ""; }; + AD4E148F2BC2797000A32C8B /* ArkMultiplayerContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerContext.swift; sourceTree = ""; }; AD5F69742BAC879400A5518D /* TankShootEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankShootEvent.swift; sourceTree = ""; }; AD6E03602B9F15FA00974EBF /* ArkECS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECS.swift; sourceTree = ""; }; AD6E03642BA1949000974EBF /* ArkEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkEvent.swift; sourceTree = ""; }; @@ -1051,6 +1053,7 @@ AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */, ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */, ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */, + AD4E148F2BC2797000A32C8B /* ArkMultiplayerContext.swift */, ); path = "ark-multiplayer-kit"; sourceTree = ""; @@ -1479,6 +1482,7 @@ 02C9CC2F2BBED04E0098E849 /* ContainerRenderableComponent.swift in Sources */, 02C395182BA83FC40075F1CA /* ButtonRenderableComponent.swift in Sources */, AD6E03652BA1949000974EBF /* ArkEvent.swift in Sources */, + AD4E14902BC2797000A32C8B /* ArkMultiplayerContext.swift in Sources */, 943D418C2BAEBF2E00F9E88F /* ArkSetupContext.swift in Sources */, ADA847FF2BBC4EA800B19378 /* ArkDataSerializer.swift in Sources */, AD36A7582BAC3223003E938B /* TankGameEntityCreator.swift in Sources */, diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift new file mode 100644 index 0000000..362b50a --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift @@ -0,0 +1,13 @@ +// +// ArkMultiplayerContext.swift +// ArkKit +// +// Created by Ryan Peh on 7/4/24. +// + +import Foundation + +protocol ArkMultiplayerContext { + var playerNumber: Int { get } + var serviceName: String { get set } +} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index 7f58822..19aa2b1 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -12,7 +12,7 @@ enum PeerRole { case slave } -class ArkMultiplayerManager: ArkNetworkDelegate { +class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { private var networkService: ArkNetworkProtocol var multiplayerEventManager: ArkMultiplayerEventManager? var arkMultiplayerECS: ArkMultiplayerECS? @@ -24,6 +24,25 @@ class ArkMultiplayerManager: ArkNetworkDelegate { self.networkService = ArkNetworkService(serviceName: serviceName) self.networkService.delegate = self } + + var playerNumber: Int { + let sortedPeers = (peers + [networkService.deviceID]).sorted() + if let deviceIndex = sortedPeers.firstIndex(of: networkService.deviceID) { + return deviceIndex + 1 + } else { + return 0 + } + } + + var serviceName: String { + get { + networkService.serviceName + } + set { + self.networkService = ArkNetworkService(serviceName: newValue) + self.networkService.delegate = self + } + } func gameDataReceived(manager: ArkNetworkService, gameData: Data) { print("data received") @@ -63,7 +82,6 @@ class ArkMultiplayerManager: ArkNetworkDelegate { role = masterPeer == networkService.deviceID ? .master : .slave print("Updated role: \(role)") } - } extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift index f67a11d..27b6388 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift @@ -9,6 +9,7 @@ import Foundation protocol ArkNetworkProtocol { var delegate: ArkNetworkDelegate? { get set } var deviceID: String { get } + var serviceName: String { get } init(serviceName: String) func sendData(data: Data) diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift index 642f6fd..9c4d4e3 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift @@ -13,6 +13,7 @@ class ArkNetworkService: ArkNetworkProtocol { private var peers: [PeerInfo] = [] private var session: MultipeerSession! var delegate: ArkNetworkDelegate? + private(set) var serviceName: String required init(serviceName: String = "Ark") { let config = MultipeerSessionConfig(myPeerInfo: myPeerInfo, @@ -20,6 +21,7 @@ class ArkNetworkService: ArkNetworkProtocol { presharedKey: "12345", identity: serviceName) self.session = MultipeerSession(config: config, queue: .main) + self.serviceName = serviceName setUpHandlers() session.startSharing() From 6c900e783da38b3fa5d005244c223d94dad0acb2 Mon Sep 17 00:00:00 2001 From: ryanpeh Date: Sun, 7 Apr 2024 22:16:13 +0800 Subject: [PATCH 07/43] [ark] refactor: change multiplayer enabling in ark --- ArkGameExample/SceneDelegate.swift | 3 ++- ArkKit/Ark.swift | 12 +++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ArkGameExample/SceneDelegate.swift b/ArkGameExample/SceneDelegate.swift index 18dfe2a..e9084f8 100644 --- a/ArkGameExample/SceneDelegate.swift +++ b/ArkGameExample/SceneDelegate.swift @@ -57,7 +57,8 @@ extension SceneDelegate { guard let rootView = window.rootViewController as? any AbstractRootView else { return } - ark = Ark(rootView: rootView, blueprint: blueprint, multiplayer: true) + ark = Ark(rootView: rootView, blueprint: blueprint) + ark?.multiplayer(serviceName: "tankGame") ark?.start() } } diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index 154753c..e706f3f 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -18,6 +18,7 @@ class Ark { let blueprint: ArkBlueprint let audioContext: AudioContext + var multiplayerContext: ArkMultiplayerContext? var displayContext: ArkDisplayContext { ArkDisplayContext( @@ -37,8 +38,7 @@ class Ark { init(rootView: any AbstractRootView, blueprint: ArkBlueprint, - canvasRenderer: (any RenderableBuilder)? = nil, - multiplayer: Bool = false) { + canvasRenderer: (any RenderableBuilder)? = nil) { self.rootView = rootView self.blueprint = blueprint let eventManager = ArkEventManager() @@ -46,10 +46,6 @@ class Ark { self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) self.audioContext = ArkAudioPlayer() self.canvasRenderer = canvasRenderer - - if multiplayer { - setupMultiplayer() - } } func start() { @@ -70,8 +66,9 @@ class Ark { canvasRenderer: canvasRenderer) gameCoordinator.start() } + - private func setupMultiplayer() { + func multiplayer(serviceName: String) { let multiplayerManager = ArkMultiplayerManager(serviceName: "tankGame") let eventManager = ArkMultiplayerEventManager() let ecsManager = ArkMultiplayerECS() @@ -79,6 +76,7 @@ class Ark { ecsManager.delegate = multiplayerManager self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) + self.multiplayerContext = multiplayerManager } private func setup(_ rules: [any Rule]) { From 1dd2cc616454185bf5550f85a0ecf421fc9795a9 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Tue, 9 Apr 2024 10:34:50 +0800 Subject: [PATCH 08/43] [ark-multiplayer-kit] make components sendable --- ArkGameExample/TankGame/Components/TankComponent.swift | 2 +- ArkKit/ark-camera-kit/Camera.swift | 4 ++-- ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift | 4 ++-- ArkKit/ark-physics-kit/PhysicsComponent.swift | 4 ++-- ArkKit/ark-render-kit/RenderLayer.swift | 2 +- .../ark-ecs-kit/InternalComponents/PositionComponent.swift | 2 +- .../ark-ecs-kit/InternalComponents/RotationComponent.swift | 2 +- .../ark-ecs-kit/InternalComponents/StopWatchComponent.swift | 2 +- .../ark-ecs-kit/InternalComponents/WorldComponent.swift | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ArkGameExample/TankGame/Components/TankComponent.swift b/ArkGameExample/TankGame/Components/TankComponent.swift index e749889..f2aaf8f 100644 --- a/ArkGameExample/TankGame/Components/TankComponent.swift +++ b/ArkGameExample/TankGame/Components/TankComponent.swift @@ -5,6 +5,6 @@ // Created by Ryan Peh on 21/3/24. // -struct TankComponent: Component { +struct TankComponent: SendableComponent { let playerIndex: Int } diff --git a/ArkKit/ark-camera-kit/Camera.swift b/ArkKit/ark-camera-kit/Camera.swift index f266f1c..58c699f 100644 --- a/ArkKit/ark-camera-kit/Camera.swift +++ b/ArkKit/ark-camera-kit/Camera.swift @@ -1,6 +1,6 @@ import Foundation -struct CameraContainerComponent: Component { +struct CameraContainerComponent: SendableComponent { let camera: Camera /// Screen position is the center of the camera placed on to the screen coordinate @@ -15,7 +15,7 @@ struct CameraContainerComponent: Component { * Defines the visible portion of the canvas based off the `canvasPosition`. * The camera takes a slice of renderable components at the canvas layer based off the size. */ -struct Camera { +struct Camera: Codable { /// Defines the anchor position within the `canvas` (game world). let canvasPosition: CGPoint diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index 19aa2b1..fb2d2b0 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -24,7 +24,7 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { self.networkService = ArkNetworkService(serviceName: serviceName) self.networkService.delegate = self } - + var playerNumber: Int { let sortedPeers = (peers + [networkService.deviceID]).sorted() if let deviceIndex = sortedPeers.firstIndex(of: networkService.deviceID) { @@ -33,7 +33,7 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { return 0 } } - + var serviceName: String { get { networkService.serviceName diff --git a/ArkKit/ark-physics-kit/PhysicsComponent.swift b/ArkKit/ark-physics-kit/PhysicsComponent.swift index c3b3a30..c8130bb 100644 --- a/ArkKit/ark-physics-kit/PhysicsComponent.swift +++ b/ArkKit/ark-physics-kit/PhysicsComponent.swift @@ -1,7 +1,7 @@ import Foundation -struct PhysicsComponent: Component { - enum Shape { +struct PhysicsComponent: SendableComponent { + enum Shape: Codable { case circle case rectangle } diff --git a/ArkKit/ark-render-kit/RenderLayer.swift b/ArkKit/ark-render-kit/RenderLayer.swift index e60d044..22a3c4d 100644 --- a/ArkKit/ark-render-kit/RenderLayer.swift +++ b/ArkKit/ark-render-kit/RenderLayer.swift @@ -1,4 +1,4 @@ -enum RenderLayer { +enum RenderLayer: Codable { case canvas case screen } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/PositionComponent.swift b/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/PositionComponent.swift index 8031793..472466e 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/PositionComponent.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/PositionComponent.swift @@ -1,5 +1,5 @@ import Foundation -struct PositionComponent: Component { +struct PositionComponent: SendableComponent { var position: CGPoint } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/RotationComponent.swift b/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/RotationComponent.swift index 9904278..d132c1c 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/RotationComponent.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/RotationComponent.swift @@ -1,5 +1,5 @@ import Foundation -struct RotationComponent: Component { +struct RotationComponent: SendableComponent { var angleInRadians: CGFloat? } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/StopWatchComponent.swift b/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/StopWatchComponent.swift index b2d9415..45c4ae0 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/StopWatchComponent.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/StopWatchComponent.swift @@ -1,6 +1,6 @@ import Foundation -struct StopWatchComponent: Component { +struct StopWatchComponent: SendableComponent { var currentTime: TimeInterval var name: String diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/WorldComponent.swift b/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/WorldComponent.swift index 61fc078..8f34c9e 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/WorldComponent.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/InternalComponents/WorldComponent.swift @@ -1,6 +1,6 @@ import Foundation -struct WorldComponent: Component { +struct WorldComponent: SendableComponent { let center: CGPoint let width: Double let height: Double From 423266ac6942b1efbebfb1e79693061e05fdd243 Mon Sep 17 00:00:00 2001 From: ryanpeh Date: Fri, 12 Apr 2024 23:01:57 +0700 Subject: [PATCH 09/43] [ark-multiplayer-kit] fix: fix bugs --- ArkKit/Ark.swift | 3 ++- ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift | 4 +--- ArkKit/ark-multiplayer-kit/ArkNetworkService.swift | 2 +- .../data-serialize/ArkECSSerializer.swift | 5 +++-- ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift | 4 ++++ 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index e706f3f..bc76ddd 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -66,12 +66,13 @@ class Ark { canvasRenderer: canvasRenderer) gameCoordinator.start() } - func multiplayer(serviceName: String) { let multiplayerManager = ArkMultiplayerManager(serviceName: "tankGame") let eventManager = ArkMultiplayerEventManager() let ecsManager = ArkMultiplayerECS() + multiplayerManager.multiplayerEventManager = eventManager + multiplayerManager.arkMultiplayerECS = ecsManager eventManager.delegate = multiplayerManager ecsManager.delegate = multiplayerManager diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index 19aa2b1..7a560f2 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -24,7 +24,7 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { self.networkService = ArkNetworkService(serviceName: serviceName) self.networkService.delegate = self } - + var playerNumber: Int { let sortedPeers = (peers + [networkService.deviceID]).sorted() if let deviceIndex = sortedPeers.firstIndex(of: networkService.deviceID) { @@ -45,10 +45,8 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { } func gameDataReceived(manager: ArkNetworkService, gameData: Data) { - print("data received") do { let wrappedData = try JSONDecoder().decode(DataWrapper.self, from: gameData) - if wrappedData.type == .event, let event = try ArkEventRegistry.shared.decode(from: wrappedData.payload, typeName: wrappedData.name) { diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift index 9c4d4e3..312adf7 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift @@ -19,7 +19,7 @@ class ArkNetworkService: ArkNetworkProtocol { let config = MultipeerSessionConfig(myPeerInfo: myPeerInfo, bonjourService: "_ArkMultiplayer._tcp", presharedKey: "12345", - identity: serviceName) + identity: "testing") self.session = MultipeerSession(config: config, queue: .main) self.serviceName = serviceName setUpHandlers() diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift index 4781a09..3786a1c 100644 --- a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift @@ -37,6 +37,7 @@ struct ArkECSSerializer { guard let component = component as? any SendableComponent else { return nil } +// print("component is sendable component: \(component)") let componentData = try JSONEncoder().encode(component) let name = String(describing: type(of: component)) @@ -44,7 +45,7 @@ struct ArkECSSerializer { } static func encodeECSFunction(action: String, entity: Entity, component: Component? = nil, - components: [Component]? = nil) throws -> Data? { + components: [Component]? = nil) throws -> Data? { var componentData = [ComponentData]() @@ -69,7 +70,7 @@ struct ArkECSSerializer { let entity = functionData.entity let components = functionData.componentData .compactMap { try? ComponentRegistry.shared.decode(from: $0.data, - typeName: $0.name) + typeName: $0.name) } if let functionType = ECSFunctionType(rawValue: name), diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift index 19ca064..0f1ffb2 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift @@ -46,6 +46,10 @@ class ComponentRegistry { WorldComponent.self, StopWatchComponent.self ] + + for componentType in componentTypes { + register(componentType) + } } private func setUpComponentTypes() { From d9b0deb1d96dfa1f182350f4ca8e5f6ca9353fc0 Mon Sep 17 00:00:00 2001 From: ryanpeh Date: Sat, 13 Apr 2024 14:02:22 +0700 Subject: [PATCH 10/43] [ark-multiplayer-kit] feat: make rederable components sendable --- .../abstract/color/AbstractColor.swift | 2 +- .../BitmapImageRenderableComponent.swift | 39 +++++++++++++++++++ .../ContainerRenderableComponent.swift | 1 + .../shapes/CircleRenderableComponent.swift | 30 ++++++++++++++ .../shapes/PolygonRenderableComponent.swift | 33 ++++++++++++++++ .../shapes/RectRenderableComponent.swift | 32 +++++++++++++++ .../shapes/ShapeRenderableComponent.swift | 4 +- .../ark-ecs-kit/ComponentRegistry.swift | 7 +++- 8 files changed, 144 insertions(+), 4 deletions(-) diff --git a/ArkKit/ark-render-kit/abstract/color/AbstractColor.swift b/ArkKit/ark-render-kit/abstract/color/AbstractColor.swift index de9742b..946e64f 100644 --- a/ArkKit/ark-render-kit/abstract/color/AbstractColor.swift +++ b/ArkKit/ark-render-kit/abstract/color/AbstractColor.swift @@ -7,6 +7,6 @@ import Foundation -enum AbstractColor { +enum AbstractColor: Codable { case `default`, blue, red, green, black } diff --git a/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift index fbe9954..fb1d187 100644 --- a/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift @@ -61,3 +61,42 @@ extension BitmapImageRenderableComponent: AbstractBitmap { return copy } } + +extension BitmapImageRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case center, rotation, zPosition, renderLayer, isUserInteractionEnabled, width, height, + imageResourcePath, isClipToBounds, isScaleAspectFit, isScaleToFill, isScaleAspectFill + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + imageResourcePath = try container.decode(String.self, forKey: .imageResourcePath) + isClipToBounds = try container.decode(Bool.self, forKey: .isClipToBounds) + isScaleAspectFit = try container.decode(Bool.self, forKey: .isScaleAspectFit) + isScaleToFill = try container.decode(Bool.self, forKey: .isScaleToFill) + isScaleAspectFill = try container.decode(Bool.self, forKey: .isScaleAspectFill) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + try container.encode(imageResourcePath, forKey: .imageResourcePath) + try container.encode(isClipToBounds, forKey: .isClipToBounds) + try container.encode(isScaleAspectFit, forKey: .isScaleAspectFit) + try container.encode(isScaleToFill, forKey: .isScaleToFill) + try container.encode(isScaleAspectFill, forKey: .isScaleAspectFill) + } +} diff --git a/ArkKit/ark-render-kit/renderable-components/container/ContainerRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/container/ContainerRenderableComponent.swift index c305931..115e806 100644 --- a/ArkKit/ark-render-kit/renderable-components/container/ContainerRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/container/ContainerRenderableComponent.swift @@ -51,4 +51,5 @@ struct ContainerRenderableComponent: RenderableComponent, AbstractLetterboxable copy.mask = CGRect(origin: origin, size: size) return copy } + } diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/CircleRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/CircleRenderableComponent.swift index a078c98..ddae453 100644 --- a/ArkKit/ark-render-kit/renderable-components/shapes/CircleRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/shapes/CircleRenderableComponent.swift @@ -27,3 +27,33 @@ struct CircleRenderableComponent: ShapeRenderableComponent { builder.build(self) } } + +extension CircleRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case radius, center, rotation, zPosition, renderLayer, isUserInteractionEnabled, fillInfo, strokeInfo + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + radius = try container.decode(Double.self, forKey: .radius) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + fillInfo = try container.decodeIfPresent(ShapeFillInfo.self, forKey: .fillInfo) + strokeInfo = try container.decodeIfPresent(ShapeStrokeInfo.self, forKey: .strokeInfo) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(radius, forKey: .radius) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encodeIfPresent(fillInfo, forKey: .fillInfo) + try container.encodeIfPresent(strokeInfo, forKey: .strokeInfo) + } +} diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift index 45a2437..685ed1d 100644 --- a/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift @@ -33,3 +33,36 @@ struct PolygonRenderableComponent: ShapeRenderableComponent { builder.build(self) } } + +extension PolygonRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case points, frame, center, rotation, zPosition, renderLayer, isUserInteractionEnabled, + fillInfo, strokeInfo + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + points = try container.decode([CGPoint].self, forKey: .points) + frame = try container.decode(CGRect.self, forKey: .frame) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + fillInfo = try container.decodeIfPresent(ShapeFillInfo.self, forKey: .fillInfo) + strokeInfo = try container.decodeIfPresent(ShapeStrokeInfo.self, forKey: .strokeInfo) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(points, forKey: .points) + try container.encode(frame, forKey: .frame) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encodeIfPresent(fillInfo, forKey: .fillInfo) + try container.encodeIfPresent(strokeInfo, forKey: .strokeInfo) + } +} diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift index f1932dd..2f3f33a 100644 --- a/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift @@ -33,3 +33,35 @@ struct RectRenderableComponent: ShapeRenderableComponent { builder.build(self) } } + +extension RectRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case width, height, center, rotation, zPosition, isUserInteractionEnabled, renderLayer, fillInfo, strokeInfo + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + fillInfo = try container.decodeIfPresent(ShapeFillInfo.self, forKey: .fillInfo) + strokeInfo = try container.decodeIfPresent(ShapeStrokeInfo.self, forKey: .strokeInfo) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encodeIfPresent(fillInfo, forKey: .fillInfo) + try container.encodeIfPresent(strokeInfo, forKey: .strokeInfo) + } +} diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift index 43bde55..4f42611 100644 --- a/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift @@ -28,11 +28,11 @@ extension ShapeRenderableComponent { } } -struct ShapeFillInfo { +struct ShapeFillInfo: Codable { let color: AbstractColor } -struct ShapeStrokeInfo { +struct ShapeStrokeInfo: Codable { let lineWidth: Double let color: AbstractColor } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift index 0f1ffb2..3b41079 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift @@ -40,8 +40,13 @@ class ComponentRegistry { CameraContainerComponent.self, PhysicsComponent.self, ArkAnimationsComponent.self, - ContainerRenderableComponent.self, + ButtonRenderableComponent.self, + JoystickRenderableComponent.self, + CircleRenderableComponent.self, + RectRenderableComponent.self, + PolygonRenderableComponent.self, BitmapImageRenderableComponent.self, + ContainerRenderableComponent.self, RotationComponent.self, WorldComponent.self, StopWatchComponent.self From 7deae44fccfc7c26b13686a607add62079eab53d Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Sun, 14 Apr 2024 11:28:30 +0800 Subject: [PATCH 11/43] [ark-render-kit] Standardise variables and init --- .../BitmapImageRenderableComponent.swift | 48 ++++--------------- .../shapes/CircleRenderableComponent.swift | 38 +++------------ .../shapes/PolygonRenderableComponent.swift | 39 ++------------- .../shapes/RectRenderableComponent.swift | 44 +++-------------- .../shapes/ShapeRenderableComponent.swift | 7 --- 5 files changed, 26 insertions(+), 150 deletions(-) diff --git a/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift index fb1d187..dd27b3d 100644 --- a/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift @@ -17,10 +17,17 @@ struct BitmapImageRenderableComponent: RenderableComponent { private(set) var isScaleToFill = false private(set) var isScaleAspectFill = false - init(imageResourcePath: String, width: Double, height: Double) { + init(imageResourcePath: String, width: Double, height: Double, center: CGPoint = .zero, + isClipToBounds: Bool = false, isScaleAspectFit: Bool = false, + isScaleToFill: Bool = false, isScaleAspectFill: Bool = false) { self.imageResourcePath = imageResourcePath self.width = width self.height = height + self.center = center + self.isClipToBounds = isClipToBounds + self.isScaleAspectFit = isScaleAspectFit + self.isScaleToFill = isScaleToFill + self.isScaleAspectFill = isScaleAspectFill } func buildRenderable(using builder: any RenderableBuilder) -> any Renderable { @@ -61,42 +68,3 @@ extension BitmapImageRenderableComponent: AbstractBitmap { return copy } } - -extension BitmapImageRenderableComponent: SendableComponent { - enum CodingKeys: String, CodingKey { - case center, rotation, zPosition, renderLayer, isUserInteractionEnabled, width, height, - imageResourcePath, isClipToBounds, isScaleAspectFit, isScaleToFill, isScaleAspectFill - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - center = try container.decode(CGPoint.self, forKey: .center) - rotation = try container.decode(Double.self, forKey: .rotation) - zPosition = try container.decode(Double.self, forKey: .zPosition) - renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) - isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) - width = try container.decode(Double.self, forKey: .width) - height = try container.decode(Double.self, forKey: .height) - imageResourcePath = try container.decode(String.self, forKey: .imageResourcePath) - isClipToBounds = try container.decode(Bool.self, forKey: .isClipToBounds) - isScaleAspectFit = try container.decode(Bool.self, forKey: .isScaleAspectFit) - isScaleToFill = try container.decode(Bool.self, forKey: .isScaleToFill) - isScaleAspectFill = try container.decode(Bool.self, forKey: .isScaleAspectFill) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(center, forKey: .center) - try container.encode(rotation, forKey: .rotation) - try container.encode(zPosition, forKey: .zPosition) - try container.encode(renderLayer, forKey: .renderLayer) - try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) - try container.encode(width, forKey: .width) - try container.encode(height, forKey: .height) - try container.encode(imageResourcePath, forKey: .imageResourcePath) - try container.encode(isClipToBounds, forKey: .isClipToBounds) - try container.encode(isScaleAspectFit, forKey: .isScaleAspectFit) - try container.encode(isScaleToFill, forKey: .isScaleToFill) - try container.encode(isScaleAspectFill, forKey: .isScaleAspectFill) - } -} diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/CircleRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/CircleRenderableComponent.swift index ddae453..ae5aff1 100644 --- a/ArkKit/ark-render-kit/renderable-components/shapes/CircleRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/shapes/CircleRenderableComponent.swift @@ -1,7 +1,7 @@ import CoreGraphics struct CircleRenderableComponent: ShapeRenderableComponent { - private(set) var radius: Double + let radius: Double var center: CGPoint = .zero var rotation: Double = 0.0 var zPosition: Double = 0.0 @@ -16,6 +16,12 @@ struct CircleRenderableComponent: ShapeRenderableComponent { self.radius = radius } + init(radius: Double, fillInfo: ShapeFillInfo?, strokeInfo: ShapeStrokeInfo?) { + self.radius = radius + self.fillInfo = fillInfo + self.strokeInfo = strokeInfo + } + func modify(fillInfo: ShapeFillInfo?, strokeInfo: ShapeStrokeInfo?) -> CircleRenderableComponent { var copy = self copy.fillInfo = fillInfo @@ -27,33 +33,3 @@ struct CircleRenderableComponent: ShapeRenderableComponent { builder.build(self) } } - -extension CircleRenderableComponent: SendableComponent { - enum CodingKeys: String, CodingKey { - case radius, center, rotation, zPosition, renderLayer, isUserInteractionEnabled, fillInfo, strokeInfo - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - radius = try container.decode(Double.self, forKey: .radius) - center = try container.decode(CGPoint.self, forKey: .center) - rotation = try container.decode(Double.self, forKey: .rotation) - zPosition = try container.decode(Double.self, forKey: .zPosition) - renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) - isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) - fillInfo = try container.decodeIfPresent(ShapeFillInfo.self, forKey: .fillInfo) - strokeInfo = try container.decodeIfPresent(ShapeStrokeInfo.self, forKey: .strokeInfo) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(radius, forKey: .radius) - try container.encode(center, forKey: .center) - try container.encode(rotation, forKey: .rotation) - try container.encode(zPosition, forKey: .zPosition) - try container.encode(renderLayer, forKey: .renderLayer) - try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) - try container.encodeIfPresent(fillInfo, forKey: .fillInfo) - try container.encodeIfPresent(strokeInfo, forKey: .strokeInfo) - } -} diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift index 685ed1d..f1ae096 100644 --- a/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift @@ -13,11 +13,13 @@ struct PolygonRenderableComponent: ShapeRenderableComponent { private(set) var fillInfo: ShapeFillInfo? private(set) var strokeInfo: ShapeStrokeInfo? - init(points: [CGPoint], frame: CGRect, rotation: Double = 0.0) { + init(points: [CGPoint], frame: CGRect, + fillInfo: ShapeFillInfo? = nil, strokeInfo: ShapeStrokeInfo? = nil) { self.points = points self.frame = frame self.center = CGPoint(x: frame.midX, y: frame.midY) - self.rotation = rotation + self.fillInfo = fillInfo + self.strokeInfo = strokeInfo } func modify(fillInfo: ShapeFillInfo?, strokeInfo: ShapeStrokeInfo?) -> PolygonRenderableComponent { @@ -33,36 +35,3 @@ struct PolygonRenderableComponent: ShapeRenderableComponent { builder.build(self) } } - -extension PolygonRenderableComponent: SendableComponent { - enum CodingKeys: String, CodingKey { - case points, frame, center, rotation, zPosition, renderLayer, isUserInteractionEnabled, - fillInfo, strokeInfo - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - points = try container.decode([CGPoint].self, forKey: .points) - frame = try container.decode(CGRect.self, forKey: .frame) - center = try container.decode(CGPoint.self, forKey: .center) - rotation = try container.decode(Double.self, forKey: .rotation) - zPosition = try container.decode(Double.self, forKey: .zPosition) - renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) - isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) - fillInfo = try container.decodeIfPresent(ShapeFillInfo.self, forKey: .fillInfo) - strokeInfo = try container.decodeIfPresent(ShapeStrokeInfo.self, forKey: .strokeInfo) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(points, forKey: .points) - try container.encode(frame, forKey: .frame) - try container.encode(center, forKey: .center) - try container.encode(rotation, forKey: .rotation) - try container.encode(zPosition, forKey: .zPosition) - try container.encode(renderLayer, forKey: .renderLayer) - try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) - try container.encodeIfPresent(fillInfo, forKey: .fillInfo) - try container.encodeIfPresent(strokeInfo, forKey: .strokeInfo) - } -} diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift index 2f3f33a..337ccc8 100644 --- a/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift @@ -3,8 +3,8 @@ import CoreGraphics struct RectRenderableComponent: ShapeRenderableComponent { let width: Double let height: Double - var center: CGPoint - var rotation: Double + var center: CGPoint = .zero + var rotation: Double = 0.0 var zPosition: Double = 0.0 var isUserInteractionEnabled = false var renderLayer: RenderLayer = .canvas @@ -13,11 +13,13 @@ struct RectRenderableComponent: ShapeRenderableComponent { private(set) var fillInfo: ShapeFillInfo? private(set) var strokeInfo: ShapeStrokeInfo? - init(width: Double, height: Double, center: CGPoint = .zero, rotation: Double = 0.0) { + init(width: Double, height: Double, + fillInfo: ShapeFillInfo? = nil, + strokeInfo: ShapeStrokeInfo? = nil) { self.width = width self.height = height - self.center = center - self.rotation = rotation + self.fillInfo = fillInfo + self.strokeInfo = strokeInfo } func modify(fillInfo: ShapeFillInfo?, strokeInfo: ShapeStrokeInfo?) -> RectRenderableComponent { @@ -33,35 +35,3 @@ struct RectRenderableComponent: ShapeRenderableComponent { builder.build(self) } } - -extension RectRenderableComponent: SendableComponent { - enum CodingKeys: String, CodingKey { - case width, height, center, rotation, zPosition, isUserInteractionEnabled, renderLayer, fillInfo, strokeInfo - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - width = try container.decode(Double.self, forKey: .width) - height = try container.decode(Double.self, forKey: .height) - center = try container.decode(CGPoint.self, forKey: .center) - rotation = try container.decode(Double.self, forKey: .rotation) - zPosition = try container.decode(Double.self, forKey: .zPosition) - isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) - renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) - fillInfo = try container.decodeIfPresent(ShapeFillInfo.self, forKey: .fillInfo) - strokeInfo = try container.decodeIfPresent(ShapeStrokeInfo.self, forKey: .strokeInfo) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(width, forKey: .width) - try container.encode(height, forKey: .height) - try container.encode(center, forKey: .center) - try container.encode(rotation, forKey: .rotation) - try container.encode(zPosition, forKey: .zPosition) - try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) - try container.encode(renderLayer, forKey: .renderLayer) - try container.encodeIfPresent(fillInfo, forKey: .fillInfo) - try container.encodeIfPresent(strokeInfo, forKey: .strokeInfo) - } -} diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift index 4f42611..fb6fd6e 100644 --- a/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift @@ -1,10 +1,3 @@ -// -// ShapeCanvasComponent.swift -// ArkKit -// -// Created by En Rong on 19/3/24. -// - import Foundation protocol ShapeRenderableComponent: AbstractShape, RenderableComponent where Color == AbstractColor { From fd8c44016ad9c572e06eb001b9e2cd71137561b9 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Sun, 14 Apr 2024 11:28:48 +0800 Subject: [PATCH 12/43] [ark-multiplayer-kit] Fix decodable --- ArkKit.xcodeproj/project.pbxproj | 24 +++++++++++ .../ArkMultiplayerManager.swift | 41 ++++++++---------- .../data-serialize/ArkECSSerializer.swift | 7 --- .../sendable/SendableComponent.swift | 7 --- .../sendable/SendableEntity.swift | 7 --- ...enderableComponent+SendableComponent.swift | 43 +++++++++++++++++++ ...endarableComponent+SendableComponent.swift | 32 ++++++++++++++ ...enderableComponent+SendableComponent.swift | 35 +++++++++++++++ ...enderableComponent+SendableComponent.swift | 34 +++++++++++++++ 9 files changed, 185 insertions(+), 45 deletions(-) create mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/BitmapImageRenderableComponent+SendableComponent.swift create mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRendarableComponent+SendableComponent.swift create mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/PolygonRenderableComponent+SendableComponent.swift create mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/RectRenderableComponent+SendableComponent.swift diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index bb93813..619fb5b 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -126,6 +126,10 @@ 282248532BA82E5800850D7F /* SKPhysicsBodyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282248522BA82E5800850D7F /* SKPhysicsBodyManager.swift */; }; 286C09C02BADD0BB000343B1 /* TankGameMapBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286C09BF2BADD0BB000343B1 /* TankGameMapBuilder.swift */; }; 286C09C22BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286C09C12BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift */; }; + 2882ABF92BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */; }; + 2882ABFB2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */; }; + 2882ABFD2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFC2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift */; }; + 2882ABFF2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */; }; 28844EA12BA881E60037A7F6 /* DemoPhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28844EA02BA881E60037A7F6 /* DemoPhysicsComponent.swift */; }; 28A032DC2BAD4E8200851BFF /* StopWatchComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A032DB2BAD4E8200851BFF /* StopWatchComponent.swift */; }; 28A032DF2BAD4F2A00851BFF /* ArkTimeSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A032DE2BAD4F2A00851BFF /* ArkTimeSystem.swift */; }; @@ -337,6 +341,10 @@ 282248522BA82E5800850D7F /* SKPhysicsBodyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SKPhysicsBodyManager.swift; sourceTree = ""; }; 286C09BF2BADD0BB000343B1 /* TankGameMapBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameMapBuilder.swift; sourceTree = ""; }; 286C09C12BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameTerrainObjectBuilder.swift; sourceTree = ""; }; + 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BitmapImageRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; + 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RectRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; + 2882ABFC2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CircleRendarableComponent+SendableComponent.swift"; sourceTree = ""; }; + 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PolygonRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; 28844EA02BA881E60037A7F6 /* DemoPhysicsComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoPhysicsComponent.swift; sourceTree = ""; }; 28A032DB2BAD4E8200851BFF /* StopWatchComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopWatchComponent.swift; sourceTree = ""; }; 28A032DE2BAD4F2A00851BFF /* ArkTimeSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkTimeSystem.swift; sourceTree = ""; }; @@ -871,6 +879,17 @@ path = InternalComponents; sourceTree = ""; }; + 2882ABF72BCB77EB0042AC52 /* SendableRenderableComponents */ = { + isa = PBXGroup; + children = ( + 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */, + 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */, + 2882ABFC2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift */, + 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */, + ); + path = SendableRenderableComponents; + sourceTree = ""; + }; 28A032DD2BAD4ED200851BFF /* InternalSystems */ = { isa = PBXGroup; children = ( @@ -1239,6 +1258,7 @@ ADD74C0A2BC0555D008CE36B /* sendable */ = { isa = PBXGroup; children = ( + 2882ABF72BCB77EB0042AC52 /* SendableRenderableComponents */, ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */, ADD74C0F2BC05759008CE36B /* SendableEntity.swift */, ); @@ -1449,6 +1469,7 @@ 9479A3AF2BA952E300F99013 /* AbstractShape.swift in Sources */, 02E0E8EE2BA280BD0043E2BA /* UIKitShape.swift in Sources */, 02C394E92BA41B480075F1CA /* ArkViewModel.swift in Sources */, + 2882ABFD2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift in Sources */, AD36A7562BAC0D36003E938B /* TankGameManager.swift in Sources */, AD6E036B2BA1A71800974EBF /* ArkEventData.swift in Sources */, AD6E03722BA2DB2700974EBF /* Heap.swift in Sources */, @@ -1470,6 +1491,7 @@ 0267BA1C2BBB05B70010F729 /* RuleTrigger.swift in Sources */, 02D8E9532BAC9A5A00BF3A07 /* AbstractView.swift in Sources */, 02C3950D2BA5FE5C0075F1CA /* ArkBlueprint.swift in Sources */, + 2882ABF92BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift in Sources */, 02C394EC2BA41DF00075F1CA /* ArkUIKitView.swift in Sources */, 02E0E8F42BA283180043E2BA /* UIKitRect.swift in Sources */, AD787A552B9C636F003EBBD0 /* RootViewController.swift in Sources */, @@ -1500,6 +1522,7 @@ 02C3952C2BA897890075F1CA /* ArkFlatCanvas.swift in Sources */, 02C394DC2BA402060075F1CA /* GameStateRenderer.swift in Sources */, 28A032E72BAD783D00851BFF /* SKSimulator.swift in Sources */, + 2882ABFB2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift in Sources */, 8FEB217E2BADF21E00788E20 /* ArkActionContext.swift in Sources */, 02C394F92BA443830075F1CA /* TapRenderable.swift in Sources */, ADD74C0C2BC05677008CE36B /* ArkECSSerializer.swift in Sources */, @@ -1514,6 +1537,7 @@ 02C3952A2BA8952F0075F1CA /* CanvasContext.swift in Sources */, 02D8E9512BAC9A3900BF3A07 /* AbstractParentView.swift in Sources */, 945F7F852BA55ECA00933629 /* UIKitButton.swift in Sources */, + 2882ABFF2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift in Sources */, 02E0E8F82BA284540043E2BA /* UIKitBitmap.swift in Sources */, 8F5573C82BA69D61007030C8 /* ArkAnimation.swift in Sources */, 286C09C02BADD0BB000343B1 /* TankGameMapBuilder.swift in Sources */, diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index a3531f1..c04aa2e 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -1,24 +1,17 @@ -// -// ArkMultiplayerManager.swift -// ArkKit -// -// Created by Ryan Peh on 31/3/24. -// - import MultipeerConnectivity enum PeerRole { - case master - case slave + case host + case participant } class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { private var networkService: ArkNetworkProtocol var multiplayerEventManager: ArkMultiplayerEventManager? var arkMultiplayerECS: ArkMultiplayerECS? - private var peers = [String]() - private var masterPeer: String? - private var role: PeerRole = .master + private var participants = [String]() + private var host: String? + private var role: PeerRole = .host init(serviceName: String) { self.networkService = ArkNetworkService(serviceName: serviceName) @@ -26,7 +19,7 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { } var playerNumber: Int { - let sortedPeers = (peers + [networkService.deviceID]).sorted() + let sortedPeers = (participants + [networkService.deviceID]).sorted() if let deviceIndex = sortedPeers.firstIndex(of: networkService.deviceID) { return deviceIndex + 1 } else { @@ -69,22 +62,22 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { } func connectedDevicesChanged(manager: ArkNetworkService, connectedDevices: [String]) { - peers = connectedDevices + participants = connectedDevices updateRoles() } private func updateRoles() { - let sortedPeers = (peers + [networkService.deviceID]).sorted() - masterPeer = sortedPeers.first + let sortedPeers = (participants + [networkService.deviceID]).sorted() + host = sortedPeers.first - role = masterPeer == networkService.deviceID ? .master : .slave + role = host == networkService.deviceID ? .host : .participant print("Updated role: \(role)") } } extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { var isBroadcastEvent: Bool { - self.role == .slave + self.role == .participant } func shouldSendEvent(_ event: Event) { @@ -94,7 +87,7 @@ extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { private func sendEvent(event: any ArkEvent) { do { if let encodedEvent = try ArkDataSerializer.encodeEvent(event), - let target = masterPeer { + let target = host { networkService.sendData(encodedEvent, to: target) } } catch { @@ -105,7 +98,7 @@ extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { var isModificationEnabled: Bool { - self.role == .master + self.role == .host } private func sendEcsFunction(function: String, entity: Entity, component: Component? = nil, @@ -123,28 +116,28 @@ extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { } func didCreateEntity(_ entity: Entity) { - guard self.role == .master else { + guard self.role == .host else { return } sendEcsFunction(function: "createEntity", entity: entity) } func didRemoveEntity(_ entity: Entity) { - guard self.role == .master else { + guard self.role == .host else { return } sendEcsFunction(function: "removeEntity", entity: entity) } func didUpsertComponent(_ component: T, to entity: Entity) { - guard self.role == .master else { + guard self.role == .host else { return } sendEcsFunction(function: "upsertComponent", entity: entity, component: component) } func didCreateEntity(_ entity: Entity, with components: [Component]) { - guard self.role == .master else { + guard self.role == .host else { return } sendEcsFunction(function: "createEntity", entity: entity, components: components) diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift index 3786a1c..762b1c7 100644 --- a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift @@ -1,10 +1,3 @@ -// -// ArkECSSerializer.swift -// ArkKit -// -// Created by Ryan Peh on 5/4/24. -// - import Foundation struct ArkECSSerializer { diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift index 62cb06d..e5e63fc 100644 --- a/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift @@ -1,10 +1,3 @@ -// -// SendableComponent.swift -// ArkKit -// -// Created by Ryan Peh on 5/4/24. -// - import Foundation protocol SendableComponent: Component, Codable { diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift index 5d0c505..44e1c9f 100644 --- a/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift @@ -1,9 +1,2 @@ -// -// SendableEntity.swift -// ArkKit -// -// Created by Ryan Peh on 5/4/24. -// - struct SendableEntity: Codable { } diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/BitmapImageRenderableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/BitmapImageRenderableComponent+SendableComponent.swift new file mode 100644 index 0000000..9792638 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/BitmapImageRenderableComponent+SendableComponent.swift @@ -0,0 +1,43 @@ +import Foundation + +extension BitmapImageRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case center, rotation, zPosition, renderLayer, isUserInteractionEnabled, width, height, + imageResourcePath, isClipToBounds, isScaleAspectFit, isScaleToFill, isScaleAspectFill + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let width = try container.decode(Double.self, forKey: .width) + let height = try container.decode(Double.self, forKey: .height) + let imageResourcePath = try container.decode(String.self, forKey: .imageResourcePath) + let isClipToBounds = try container.decode(Bool.self, forKey: .isClipToBounds) + let isScaleAspectFit = try container.decode(Bool.self, forKey: .isScaleAspectFit) + let isScaleToFill = try container.decode(Bool.self, forKey: .isScaleToFill) + let isScaleAspectFill = try container.decode(Bool.self, forKey: .isScaleAspectFill) + self.init(imageResourcePath: imageResourcePath, width: width, height: height, + isClipToBounds: isClipToBounds, isScaleAspectFit: isScaleAspectFit, + isScaleToFill: isScaleToFill, isScaleAspectFill: isScaleAspectFill) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + try container.encode(imageResourcePath, forKey: .imageResourcePath) + try container.encode(isClipToBounds, forKey: .isClipToBounds) + try container.encode(isScaleAspectFit, forKey: .isScaleAspectFit) + try container.encode(isScaleToFill, forKey: .isScaleToFill) + try container.encode(isScaleAspectFill, forKey: .isScaleAspectFill) + } + } diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRendarableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRendarableComponent+SendableComponent.swift new file mode 100644 index 0000000..54c0491 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRendarableComponent+SendableComponent.swift @@ -0,0 +1,32 @@ +import Foundation + +extension CircleRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case radius, center, rotation, zPosition, renderLayer, isUserInteractionEnabled, fillInfo, strokeInfo + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let radius = try container.decode(Double.self, forKey: .radius) + let fillInfo = try container.decodeIfPresent(ShapeFillInfo.self, forKey: .fillInfo) + let strokeInfo = try container.decodeIfPresent(ShapeStrokeInfo.self, forKey: .strokeInfo) + self.init(radius: radius, fillInfo: fillInfo, strokeInfo: strokeInfo) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(radius, forKey: .radius) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encodeIfPresent(fillInfo, forKey: .fillInfo) + try container.encodeIfPresent(strokeInfo, forKey: .strokeInfo) + } +} diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/PolygonRenderableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/PolygonRenderableComponent+SendableComponent.swift new file mode 100644 index 0000000..e55405f --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/PolygonRenderableComponent+SendableComponent.swift @@ -0,0 +1,35 @@ +import Foundation + +extension PolygonRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case points, frame, center, rotation, zPosition, renderLayer, isUserInteractionEnabled, + fillInfo, strokeInfo + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let points = try container.decode([CGPoint].self, forKey: .points) + let fillInfo = try container.decodeIfPresent(ShapeFillInfo.self, forKey: .fillInfo) + let strokeInfo = try container.decodeIfPresent(ShapeStrokeInfo.self, forKey: .strokeInfo) + let frame = try container.decode(CGRect.self, forKey: .frame) + self.init(points: points, frame: frame, fillInfo: fillInfo, strokeInfo: strokeInfo) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(points, forKey: .points) + try container.encode(frame, forKey: .frame) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encodeIfPresent(fillInfo, forKey: .fillInfo) + try container.encodeIfPresent(strokeInfo, forKey: .strokeInfo) + } +} diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/RectRenderableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/RectRenderableComponent+SendableComponent.swift new file mode 100644 index 0000000..3b9378d --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/RectRenderableComponent+SendableComponent.swift @@ -0,0 +1,34 @@ +import Foundation + +extension RectRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case width, height, center, rotation, zPosition, isUserInteractionEnabled, renderLayer, fillInfo, strokeInfo + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let width = try container.decode(Double.self, forKey: .width) + let height = try container.decode(Double.self, forKey: .height) + let fillInfo = try container.decodeIfPresent(ShapeFillInfo.self, forKey: .fillInfo) + let strokeInfo = try container.decodeIfPresent(ShapeStrokeInfo.self, forKey: .strokeInfo) + self.init(width: width, height: height, fillInfo: fillInfo, strokeInfo: strokeInfo) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encodeIfPresent(fillInfo, forKey: .fillInfo) + try container.encodeIfPresent(strokeInfo, forKey: .strokeInfo) + } +} From d5c1ec551604108fa7292e3303d98002437b54c6 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Sun, 14 Apr 2024 11:30:08 +0800 Subject: [PATCH 13/43] [ark-multiplayer-kit] Chore: rename file --- ArkKit.xcodeproj/project.pbxproj | 8 ++++---- ... => CircleRenderableComponent+SendableComponent.swift} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/{CircleRendarableComponent+SendableComponent.swift => CircleRenderableComponent+SendableComponent.swift} (100%) diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 619fb5b..db51394 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -128,7 +128,7 @@ 286C09C22BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286C09C12BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift */; }; 2882ABF92BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */; }; 2882ABFB2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */; }; - 2882ABFD2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFC2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift */; }; + 2882ABFD2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */; }; 2882ABFF2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */; }; 28844EA12BA881E60037A7F6 /* DemoPhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28844EA02BA881E60037A7F6 /* DemoPhysicsComponent.swift */; }; 28A032DC2BAD4E8200851BFF /* StopWatchComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A032DB2BAD4E8200851BFF /* StopWatchComponent.swift */; }; @@ -343,7 +343,7 @@ 286C09C12BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameTerrainObjectBuilder.swift; sourceTree = ""; }; 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BitmapImageRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RectRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; - 2882ABFC2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CircleRendarableComponent+SendableComponent.swift"; sourceTree = ""; }; + 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CircleRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PolygonRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; 28844EA02BA881E60037A7F6 /* DemoPhysicsComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoPhysicsComponent.swift; sourceTree = ""; }; 28A032DB2BAD4E8200851BFF /* StopWatchComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopWatchComponent.swift; sourceTree = ""; }; @@ -884,7 +884,7 @@ children = ( 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */, 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */, - 2882ABFC2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift */, + 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */, 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */, ); path = SendableRenderableComponents; @@ -1469,7 +1469,7 @@ 9479A3AF2BA952E300F99013 /* AbstractShape.swift in Sources */, 02E0E8EE2BA280BD0043E2BA /* UIKitShape.swift in Sources */, 02C394E92BA41B480075F1CA /* ArkViewModel.swift in Sources */, - 2882ABFD2BCB7BA20042AC52 /* CircleRendarableComponent+SendableComponent.swift in Sources */, + 2882ABFD2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift in Sources */, AD36A7562BAC0D36003E938B /* TankGameManager.swift in Sources */, AD6E036B2BA1A71800974EBF /* ArkEventData.swift in Sources */, AD6E03722BA2DB2700974EBF /* Heap.swift in Sources */, diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRendarableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRenderableComponent+SendableComponent.swift similarity index 100% rename from ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRendarableComponent+SendableComponent.swift rename to ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRenderableComponent+SendableComponent.swift From c40c31c4d93d21b95eeb35d26d341aa91ddb63e4 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Sun, 14 Apr 2024 11:40:50 +0800 Subject: [PATCH 14/43] [ark-multiplayer-kit] chore: remove redundant overrides --- .../ArkMultiplayerECS.swift | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift index 2887706..8af9fc5 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift @@ -55,10 +55,6 @@ class ArkMultiplayerECS: ArkECS { arkECS.removeComponent(componentType, from: entity) } - override func getComponent(ofType type: T.Type, for entity: Entity) -> T? where T: Component { - arkECS.getComponent(ofType: type, for: entity) - } - @discardableResult override func createEntity(with components: [any Component]) -> Entity { guard delegate?.isModificationEnabled ?? true else { @@ -70,29 +66,9 @@ class ArkMultiplayerECS: ArkECS { return entity } - - override func getEntity(id: EntityID) -> Entity? { - arkECS.getEntity(id: id) - } - - override func getEntities(with componentTypes: [any Component.Type]) -> [Entity] { - arkECS.getEntities(with: componentTypes) - } - - override func getComponents(from entity: Entity) -> [any Component] { - arkECS.getComponents(from: entity) - } - - override func addSystem(_ system: UpdateSystem, schedule: Schedule = .update, isUnique: Bool = true) { - guard delegate?.isModificationEnabled ?? true else { - return - } - - arkECS.addSystem(system, schedule: .update, isUnique: isUnique) - } } -protocol ArkMultiplayerECSDelegate { +protocol ArkMultiplayerECSDelegate: AnyObject { var isModificationEnabled: Bool { get } func didCreateEntity(_ entity: Entity) func didRemoveEntity(_ entity: Entity) From 5775aa2175ec876058d1017626291d9349abed4a Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Sun, 14 Apr 2024 16:13:32 +0800 Subject: [PATCH 15/43] [ark-multiplayer-kit] Chore: formatting --- ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift | 7 ------- .../ArkMultiplayerEventManager.swift | 7 ------- ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift | 10 +++++----- ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift | 6 ------ ArkKit/ark-multiplayer-kit/ArkNetworkService.swift | 7 ------- .../data-serialize/ArkECSSerializer.swift | 1 - 6 files changed, 5 insertions(+), 33 deletions(-) diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift index 362b50a..7126328 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift @@ -1,10 +1,3 @@ -// -// ArkMultiplayerContext.swift -// ArkKit -// -// Created by Ryan Peh on 7/4/24. -// - import Foundation protocol ArkMultiplayerContext { diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift index 54aca1c..ad4ba0c 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift @@ -1,10 +1,3 @@ -// -// ArkMultiplayerEventManager.swift -// ArkKit -// -// Created by Ryan Peh on 31/3/24. -// - import Foundation class ArkMultiplayerEventManager: ArkEventManager { diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index c04aa2e..eb725e1 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -1,4 +1,4 @@ -import MultipeerConnectivity +import Foundation enum PeerRole { case host @@ -9,7 +9,7 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { private var networkService: ArkNetworkProtocol var multiplayerEventManager: ArkMultiplayerEventManager? var arkMultiplayerECS: ArkMultiplayerECS? - private var participants = [String]() + private var peers = [String]() private var host: String? private var role: PeerRole = .host @@ -19,7 +19,7 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { } var playerNumber: Int { - let sortedPeers = (participants + [networkService.deviceID]).sorted() + let sortedPeers = (peers + [networkService.deviceID]).sorted() if let deviceIndex = sortedPeers.firstIndex(of: networkService.deviceID) { return deviceIndex + 1 } else { @@ -62,12 +62,12 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { } func connectedDevicesChanged(manager: ArkNetworkService, connectedDevices: [String]) { - participants = connectedDevices + peers = connectedDevices updateRoles() } private func updateRoles() { - let sortedPeers = (participants + [networkService.deviceID]).sorted() + let sortedPeers = (peers + [networkService.deviceID]).sorted() host = sortedPeers.first role = host == networkService.deviceID ? .host : .participant diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift index 27b6388..140a728 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift @@ -1,9 +1,3 @@ -// -// ArkNetworkProtocol.swift -// ArkKit -// -// Created by Ryan Peh on 2/4/24. -// import Foundation protocol ArkNetworkProtocol { diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift index 312adf7..8bf7ecc 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift @@ -1,10 +1,3 @@ -// -// ArkNetworkService.swift -// ArkKit -// -// Created by Ryan Peh on 31/3/24. -// - import UIKit import P2PShare diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift index 762b1c7..9a5d2c7 100644 --- a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift @@ -83,5 +83,4 @@ struct ArkECSSerializer { var entity: Entity var componentData: [ComponentData] } - } From b1dfae1e9b76e8068af91c17ff8736320f78b92c Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Sun, 14 Apr 2024 19:50:09 +0800 Subject: [PATCH 16/43] [ark-ecs-kit] feat: make entity id uint32 --- ArkKit/ark-state-kit/ark-ecs-kit/entity/Entity.swift | 9 +-------- .../ark-ecs-kit/entity/EntityIDGenerator.swift | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/entity/Entity.swift b/ArkKit/ark-state-kit/ark-ecs-kit/entity/Entity.swift index 6ded8cf..12138d8 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/entity/Entity.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/entity/Entity.swift @@ -1,13 +1,6 @@ -// -// Entity.swift -// LevelKit -// -// Created by Ryan Peh on 9/3/24. -// - import Foundation -typealias EntityID = Int +typealias EntityID = UInt32 struct Entity { var id = EntityID() diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift b/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift index 3c904d3..addaa94 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift @@ -1,14 +1,7 @@ -// -// EntityIDGenerator.swift -// ArkKit -// -// Created by Ryan Peh on 5/4/24. -// - import Foundation class EntityIDGenerator { - private var currentID = 0 + private var currentID: UInt32 = 0 private var recycledIDs = Set() func generate() -> EntityID { From 23c592fe5aa488828c45d483fdddebaaebcf9011 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Sun, 14 Apr 2024 23:46:22 +0800 Subject: [PATCH 17/43] Chore: merge conflict resolution --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ArkKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ArkKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 514a0cc..274b213 100644 --- a/ArkKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ArkKit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "9d3178f0fc3659c03375e84cc59cd94238647a3609c56e32dd97d7db9162228e", "pins" : [ { "identity" : "p2psharekit", @@ -19,5 +20,5 @@ } } ], - "version" : 2 + "version" : 3 } From 3ba114f6748d82a5fec928f755dd4c3c16e5d51e Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Mon, 15 Apr 2024 00:03:34 +0800 Subject: [PATCH 18/43] [ark-render-kit] feat: revert imageResourcePath to String --- ArkGameExample/SceneDelegate.swift | 2 +- ArkGameExample/SnakeGame/SnakeGame.swift | 4 ++-- .../TankGame/ImpactExplosionAnimation.swift | 6 +++--- .../TankGame/TankGameEntityCreator.swift | 4 ++-- ArkGameExample/TankGame/TankGameMapBuilder.swift | 2 +- .../TankGame/TankGameTerrainObjectBuilder.swift | 6 +++--- .../TankRaceGame/TankRaceGameEntityCreator.swift | 4 ++-- ArkKit/Ark.swift | 4 +--- ArkKit/ark-camera-kit/Camera.swift | 2 +- .../ark-multiplayer-kit/ArkMultiplayerECS.swift | 4 ++-- .../bitmap/BitmapImageRenderableComponent.swift | 15 ++++++++++++++- .../ark-ecs-kit/ComponentRegistry.swift | 9 ++------- .../ark-uikit-lib/ArkUIKitRenderableBuilder.swift | 2 +- 13 files changed, 35 insertions(+), 29 deletions(-) diff --git a/ArkGameExample/SceneDelegate.swift b/ArkGameExample/SceneDelegate.swift index 06a1f97..e2a630d 100644 --- a/ArkGameExample/SceneDelegate.swift +++ b/ArkGameExample/SceneDelegate.swift @@ -65,7 +65,7 @@ extension SceneDelegate { return } ark = Ark(rootView: rootView, blueprint: blueprint) - ark?.multiplayer(serviceName: "tankGame") +// ark?.multiplayer(serviceName: "tankGame") ark?.start() } } diff --git a/ArkGameExample/SnakeGame/SnakeGame.swift b/ArkGameExample/SnakeGame/SnakeGame.swift index df4782c..1cba438 100644 --- a/ArkGameExample/SnakeGame/SnakeGame.swift +++ b/ArkGameExample/SnakeGame/SnakeGame.swift @@ -45,7 +45,7 @@ extension SnakeGame { let canvasCenter = CGPoint(x: canvasWidth / 2, y: canvasHeight / 2) ecs.createEntity(with: [ - BitmapImageRenderableComponent(imageResourcePath: SnakeGameImages.map, + BitmapImageRenderableComponent(arkImageResourcePath: SnakeGameImages.map, width: canvasWidth, height: canvasHeight) .center(canvasCenter) .zPosition(0) @@ -153,7 +153,7 @@ extension SnakeGame { SnakeGameApple(), SnakeGridPositionComponent(gridPosition: emptyPosition), PositionComponent(position: self.grid.toActualPosition(emptyPosition)), - BitmapImageRenderableComponent(imageResourcePath: SnakeGameImages.apple, + BitmapImageRenderableComponent(arkImageResourcePath: SnakeGameImages.apple, width: Double(self.grid.boxSideLength), height: Double(self.grid.boxSideLength)) .layer(.canvas) diff --git a/ArkGameExample/TankGame/ImpactExplosionAnimation.swift b/ArkGameExample/TankGame/ImpactExplosionAnimation.swift index 212c8c1..66fe564 100644 --- a/ArkGameExample/TankGame/ImpactExplosionAnimation.swift +++ b/ArkGameExample/TankGame/ImpactExplosionAnimation.swift @@ -23,13 +23,13 @@ struct ImpactExplosionAnimation { private func makeBitmapComponent(imageResourcePath: TankGameExplosionAnimationKeyframes) -> BitmapImageRenderableComponent { BitmapImageRenderableComponent( - imageResourcePath: imageResourcePath, + arkImageResourcePath: imageResourcePath, width: width, height: height) .scaleAspectFill() .zPosition(100) .shouldRerender { old, new in - old.imageResourcePath.rawValue != new.imageResourcePath.rawValue + old.imageResourcePath != new.imageResourcePath } } @@ -47,7 +47,7 @@ struct ImpactExplosionAnimation { for: entity) ?? makeBitmapComponent( imageResourcePath: imageResourcePath) - bitmapComponent.imageResourcePath = imageResourcePath + bitmapComponent.imageResourcePath = imageResourcePath.rawValue ecs.upsertComponent(bitmapComponent, to: entity) } diff --git a/ArkGameExample/TankGame/TankGameEntityCreator.swift b/ArkGameExample/TankGame/TankGameEntityCreator.swift index 503953b..505f196 100644 --- a/ArkGameExample/TankGame/TankGameEntityCreator.swift +++ b/ArkGameExample/TankGame/TankGameEntityCreator.swift @@ -52,7 +52,7 @@ enum TankGameEntityCreator { let zPosition = tankContext.zPosition let tankEntity = ecsContext.createEntity(with: [ BitmapImageRenderableComponent( - imageResourcePath: tankIndexToImageAsset[tankContext.tankIndex] ?? .tank_1, + arkImageResourcePath: tankIndexToImageAsset[tankContext.tankIndex] ?? .tank_1, width: 80, height: 100 ) @@ -145,7 +145,7 @@ enum TankGameEntityCreator { let radius = ballContext.radius ecsContext.createEntity(with: [ BitmapImageRenderableComponent( - imageResourcePath: TankGameImages.ball, width: radius * 2.2, height: radius * 2.2 + arkImageResourcePath: TankGameImages.ball, width: radius * 2.2, height: radius * 2.2 ) .center(ballContext.position) .zPosition(ballContext.zPosition) diff --git a/ArkGameExample/TankGame/TankGameMapBuilder.swift b/ArkGameExample/TankGame/TankGameMapBuilder.swift index 0e10621..cbbdd52 100644 --- a/ArkGameExample/TankGame/TankGameMapBuilder.swift +++ b/ArkGameExample/TankGame/TankGameMapBuilder.swift @@ -60,7 +60,7 @@ class TankGameMapBuilder { for (x, value) in row.enumerated() { for strategy in strategies { if let imageResourcePath = strategy.imageResourcePath(forValue: value) { - let component = BitmapImageRenderableComponent(imageResourcePath: imageResourcePath, + let component = BitmapImageRenderableComponent(arkImageResourcePath: imageResourcePath, width: gridSize.width, height: gridSize.height) .shouldRerender { _, _ in false } diff --git a/ArkGameExample/TankGame/TankGameTerrainObjectBuilder.swift b/ArkGameExample/TankGame/TankGameTerrainObjectBuilder.swift index b0132d6..dfa8361 100644 --- a/ArkGameExample/TankGame/TankGameTerrainObjectBuilder.swift +++ b/ArkGameExample/TankGame/TankGameTerrainObjectBuilder.swift @@ -57,7 +57,7 @@ class TankGameLakeStrategy: TankGameTerrainObjectStrategy { func createObject(type: Int, location: CGPoint, size: CGSize, zPos: Double, in ecsContext: ArkECSContext) -> Entity { ecsContext.createEntity(with: [ - BitmapImageRenderableComponent(imageResourcePath: TankGameImages.lake, + BitmapImageRenderableComponent(arkImageResourcePath: TankGameImages.lake, width: size.width, height: size.height) .zPosition(zPos) .center(location) @@ -90,7 +90,7 @@ class TankGameStoneStrategy: TankGameTerrainObjectStrategy { func createObject(type: Int, location: CGPoint, size: CGSize, zPos: Double, in ecsContext: ArkECSContext) -> Entity { ecsContext.createEntity(with: [ - BitmapImageRenderableComponent(imageResourcePath: stoneTypeToImageAsset[type] ?? .stones_1, + BitmapImageRenderableComponent(arkImageResourcePath: stoneTypeToImageAsset[type] ?? .stones_1, width: size.width, height: size.height) .zPosition(zPos) .center(location), @@ -113,7 +113,7 @@ class TankGameHealthPackStrategy: TankGameTerrainObjectStrategy { func createObject(type: Int, location: CGPoint, size: CGSize, zPos: Double, in ecsContext: ArkECSContext) -> Entity { ecsContext.createEntity(with: [ - BitmapImageRenderableComponent(imageResourcePath: TankGameImages.healthPack, + BitmapImageRenderableComponent(arkImageResourcePath: TankGameImages.healthPack, width: size.width, height: size.height) .zPosition(zPos) .center(location), diff --git a/ArkGameExample/TankRaceGame/TankRaceGameEntityCreator.swift b/ArkGameExample/TankRaceGame/TankRaceGameEntityCreator.swift index 497c979..6b1d295 100644 --- a/ArkGameExample/TankRaceGame/TankRaceGameEntityCreator.swift +++ b/ArkGameExample/TankRaceGame/TankRaceGameEntityCreator.swift @@ -82,7 +82,7 @@ enum TankRaceGameEntityCreator { in ecsContext: ArkECSContext, zPosition: Double) -> Entity { let tankEntity = ecsContext.createEntity(with: [ - BitmapImageRenderableComponent(imageResourcePath: tankIndexToImageAsset[tankIndex] ?? .tank_1, + BitmapImageRenderableComponent(arkImageResourcePath: tankIndexToImageAsset[tankIndex] ?? .tank_1, width: 80, height: 100) .center(position) @@ -115,7 +115,7 @@ enum TankRaceGameEntityCreator { CGPoint(x: canvasWidth * 5 / 6, y: canvasWidth / 5)] let entities = positions.map { ecsContext.createEntity(with: [ - BitmapImageRenderableComponent(imageResourcePath: TankRaceGameImages.finish_line, + BitmapImageRenderableComponent(arkImageResourcePath: TankRaceGameImages.finish_line, width: canvasWidth / 3, height: canvasWidth / 5) .center($0) diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index 9d1be69..2b8bd55 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -16,11 +16,9 @@ class Ark: ArkProtocol { var arkState: ArkState var gameLoop: GameLoop? - let blueprint: ArkBlueprint - let audioContext: AudioContext - var multiplayerContext: ArkMultiplayerContext? let blueprint: ArkBlueprint let audioContext: any AudioContext + var multiplayerContext: ArkMultiplayerContext? var displayContext: DisplayContext var actionContext: ArkActionContext { diff --git a/ArkKit/ark-camera-kit/Camera.swift b/ArkKit/ark-camera-kit/Camera.swift index 9f17edc..66d70bc 100644 --- a/ArkKit/ark-camera-kit/Camera.swift +++ b/ArkKit/ark-camera-kit/Camera.swift @@ -36,7 +36,7 @@ struct Camera: Codable { } } -struct CameraZoom { +struct CameraZoom: Codable { let widthZoom: Double let heightZoom: Double } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift index 8af9fc5..ee1bf9f 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift @@ -47,12 +47,12 @@ class ArkMultiplayerECS: ArkECS { delegate?.didUpsertComponent(component, to: entity) } - override func removeComponent(_ componentType: T.Type, from entity: Entity) where T: Component { + override func removeComponent(ofType componentType: T.Type, from entity: Entity) where T: Component { guard delegate?.isModificationEnabled ?? true else { return } - arkECS.removeComponent(componentType, from: entity) + arkECS.removeComponent(ofType: componentType, from: entity) } @discardableResult diff --git a/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift index 8235d46..630085a 100644 --- a/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift @@ -10,13 +10,26 @@ struct BitmapImageRenderableComponent: RenderableComponent { let width: Double let height: Double - var imageResourcePath: any ArkImageEnum + var imageResourcePath: String private(set) var isClipToBounds = false private(set) var isScaleAspectFit = false private(set) var isScaleToFill = false private(set) var isScaleAspectFill = false + init(arkImageResourcePath: any ArkImageEnum, width: Double, height: Double, center: CGPoint = .zero, + isClipToBounds: Bool = false, isScaleAspectFit: Bool = false, + isScaleToFill: Bool = false, isScaleAspectFill: Bool = false) { + self.imageResourcePath = arkImageResourcePath.rawValue + self.width = width + self.height = height + self.center = center + self.isClipToBounds = isClipToBounds + self.isScaleAspectFit = isScaleAspectFit + self.isScaleToFill = isScaleToFill + self.isScaleAspectFill = isScaleAspectFill + } + init(imageResourcePath: String, width: Double, height: Double, center: CGPoint = .zero, isClipToBounds: Bool = false, isScaleAspectFit: Bool = false, isScaleToFill: Bool = false, isScaleAspectFill: Bool = false) { diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift index 3b41079..b230b0b 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift @@ -37,19 +37,14 @@ class ComponentRegistry { private func loadComponentTypes() { let componentTypes: [Component.Type] = [ PositionComponent.self, - CameraContainerComponent.self, - PhysicsComponent.self, - ArkAnimationsComponent.self, ButtonRenderableComponent.self, JoystickRenderableComponent.self, CircleRenderableComponent.self, RectRenderableComponent.self, PolygonRenderableComponent.self, BitmapImageRenderableComponent.self, - ContainerRenderableComponent.self, - RotationComponent.self, - WorldComponent.self, - StopWatchComponent.self + CameraContainerRenderableComponent.self, + RotationComponent.self ] for componentType in componentTypes { diff --git a/ArkKit/ark-uikit-lib/ArkUIKitRenderableBuilder.swift b/ArkKit/ark-uikit-lib/ArkUIKitRenderableBuilder.swift index ecc9bd4..e541239 100644 --- a/ArkKit/ark-uikit-lib/ArkUIKitRenderableBuilder.swift +++ b/ArkKit/ark-uikit-lib/ArkUIKitRenderableBuilder.swift @@ -28,7 +28,7 @@ class ArkUIKitRenderableBuilder: RenderableBuilder { } func build(_ image: BitmapImageRenderableComponent) -> any Renderable { - UIKitImageBitmap(imageResourcePath: image.imageResourcePath.rawValue, + UIKitImageBitmap(imageResourcePath: image.imageResourcePath, center: image.center, width: image.width, height: image.height) From f32448668c91da864c6f42c8069fa8e4a7378692 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Mon, 15 Apr 2024 01:27:25 +0800 Subject: [PATCH 19/43] [ArkGameExample] Create popover to support multiplayer host/participant --- ArkGameExample/RootViewController.swift | 17 +++++- .../pages/ArkDemoGameHostingPage.swift | 11 ++++ ArkGameExample/pages/ArkDemoHomePage.swift | 11 +++- .../pages/ArkDemoMultiplayerPopover.swift | 57 +++++++++++++++++++ .../utils/GameHostingPageFactory.swift | 35 +++++++++++- ArkKit.xcodeproj/project.pbxproj | 47 ++++++++------- 6 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 ArkGameExample/pages/ArkDemoMultiplayerPopover.swift diff --git a/ArkGameExample/RootViewController.swift b/ArkGameExample/RootViewController.swift index f4b2345..3fdb930 100644 --- a/ArkGameExample/RootViewController.swift +++ b/ArkGameExample/RootViewController.swift @@ -1,6 +1,6 @@ import UIKit -class RootViewController: UINavigationController { +class RootViewController: UINavigationController, UIPopoverPresentationControllerDelegate { override func viewDidLoad() { let homePage = ArkDemoHomePage() homePage.rootViewControllerDelegate = self @@ -10,6 +10,19 @@ class RootViewController: UINavigationController { protocol RootViewControllerDelegate: AnyObject { func pushViewController(_ viewController: UIViewController, animated: Bool) + func presentPopover(_ popoverViewController: UIViewController, sourceView: UIView, + sourceRect: CGRect, animated: Bool) } -extension RootViewController: RootViewControllerDelegate { } +extension RootViewController: RootViewControllerDelegate { + func presentPopover(_ popoverViewController: UIViewController, sourceView: UIView, sourceRect: CGRect, animated: Bool) { + popoverViewController.modalPresentationStyle = .popover + if let popoverPresentationController = popoverViewController.popoverPresentationController { + popoverPresentationController.sourceView = sourceView + popoverPresentationController.sourceRect = sourceRect + popoverPresentationController.permittedArrowDirections = .any + popoverPresentationController.delegate = self + } + present(popoverViewController, animated: animated) + } +} diff --git a/ArkGameExample/pages/ArkDemoGameHostingPage.swift b/ArkGameExample/pages/ArkDemoGameHostingPage.swift index 8f5e65b..3aa3cfb 100644 --- a/ArkGameExample/pages/ArkDemoGameHostingPage.swift +++ b/ArkGameExample/pages/ArkDemoGameHostingPage.swift @@ -6,6 +6,7 @@ class ArkDemoGameHostingPage: UIViewController { // inject blueprint here var arkBlueprint: ArkBlueprint? var ark: Ark? + var shouldShowMultiplayerOptions = false override func viewDidLoad() { super.viewDidLoad() @@ -13,6 +14,10 @@ class ArkDemoGameHostingPage: UIViewController { return } + if shouldShowMultiplayerOptions { + presentMultiplayerOptions() + } + // Example on how to inject views into ark blueprint after win/ termination // arkBlueprint = arkBlueprint?.on(TerminateGameLoopEvent.self) { event, context in // // add pop-up view here @@ -23,6 +28,12 @@ class ArkDemoGameHostingPage: UIViewController { ark = Ark(rootView: self, blueprint: blueprint) ark?.start() } + + private func presentMultiplayerOptions() { + let popover = ArkDemoMultiplayerPopover() + popover.modalPresentationStyle = .overFullScreen + self.present(popover, animated: true) + } } extension ArkDemoGameHostingPage: AbstractRootView { diff --git a/ArkGameExample/pages/ArkDemoHomePage.swift b/ArkGameExample/pages/ArkDemoHomePage.swift index eafe733..63e31fb 100644 --- a/ArkGameExample/pages/ArkDemoHomePage.swift +++ b/ArkGameExample/pages/ArkDemoHomePage.swift @@ -45,7 +45,8 @@ class ArkDemoHomePage: UIViewController { button.setTitleColor(DARK_GRAY, for: .normal) var buttonConfig = UIButton.Configuration.filled() - buttonConfig.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16) // Padding + buttonConfig.contentInsets = + NSDirectionalEdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16) // Padding buttonConfig.background.cornerRadius = 8 buttonConfig.background.backgroundColor = LIGHT_BLUE @@ -70,7 +71,11 @@ class ArkDemoHomePage: UIViewController { guard let gameOption = (sender.superview as? UIStackView)?.arrangedSubviews.firstIndex(of: sender) else { return } - let vc = GameHostingPageFactory.generateGameViewController(from: DemoGames.allCases[gameOption]) - self.rootViewControllerDelegate?.pushViewController(vc, animated: false) + + let gameType = DemoGames.allCases[gameOption] + if let parentDelegate = self.rootViewControllerDelegate { + GameHostingPageFactory.loadGame(from: gameType, with: parentDelegate, + sourceView: sender, sourceRect: sender.bounds) + } } } diff --git a/ArkGameExample/pages/ArkDemoMultiplayerPopover.swift b/ArkGameExample/pages/ArkDemoMultiplayerPopover.swift new file mode 100644 index 0000000..f6d2764 --- /dev/null +++ b/ArkGameExample/pages/ArkDemoMultiplayerPopover.swift @@ -0,0 +1,57 @@ +import UIKit + +class ArkDemoMultiplayerPopover: UIViewController { + var onJoin: (() -> Void)? + var onStart: (() -> Void)? + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + view.layer.cornerRadius = 12 + preferredContentSize = CGSize(width: 300, height: 200) + + setupButtons() + } + + private func setupButtons() { + let joinButton = UIButton(type: .system) + joinButton.setTitle("Start Multiplayer Game", for: .normal) + joinButton.translatesAutoresizingMaskIntoConstraints = false + + let startButton = UIButton(type: .system) + startButton.setTitle("Join Multiplayer Game", for: .normal) + startButton.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(joinButton) + view.addSubview(startButton) + + joinButton.addTarget(self, action: #selector(joinTapped), for: .touchUpInside) + startButton.addTarget(self, action: #selector(startTapped), for: .touchUpInside) + + NSLayoutConstraint.activate([ + joinButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + joinButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -25), + joinButton.widthAnchor.constraint(equalToConstant: 200), + joinButton.heightAnchor.constraint(equalToConstant: 40) + ]) + + NSLayoutConstraint.activate([ + startButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + startButton.topAnchor.constraint(equalTo: joinButton.bottomAnchor, constant: 20), + startButton.widthAnchor.constraint(equalToConstant: 200), + startButton.heightAnchor.constraint(equalToConstant: 40) + ]) + } + + @objc func joinTapped() { + dismiss(animated: true) { + self.onJoin?() + } + } + + @objc func startTapped() { + dismiss(animated: true) { + self.onStart?() + } + } +} diff --git a/ArkGameExample/utils/GameHostingPageFactory.swift b/ArkGameExample/utils/GameHostingPageFactory.swift index b033c01..da9ff9b 100644 --- a/ArkGameExample/utils/GameHostingPageFactory.swift +++ b/ArkGameExample/utils/GameHostingPageFactory.swift @@ -1,9 +1,12 @@ +import UIKit + class GameHostingPageFactory { // Factory method - static func generateGameViewController(from game: DemoGames) -> AbstractDemoGameHostingPage { + static func generateGameViewController(from game: DemoGames, as role: ArkPeerRole? = nil) -> AbstractDemoGameHostingPage { switch game { case .TankGame: let blueprint: ArkBlueprint = TankGameManager().blueprint + let vc: ArkDemoGameHostingPage = ArkDemoGameHostingPage() vc.arkBlueprint = blueprint return vc @@ -19,4 +22,34 @@ class GameHostingPageFactory { return vc } } + + static func loadGame(from game: DemoGames, with parentDelegate: RootViewControllerDelegate, + sourceView: UIView, sourceRect: CGRect) { + if shouldPresentMultiplayerOptions(for: game) { + let popover = ArkDemoMultiplayerPopover() + popover.onJoin = { + let gameVC = self.generateGameViewController(from: game, as: .participant) + parentDelegate.pushViewController(gameVC, animated: true) + } + + popover.onStart = { + let gameVC = generateGameViewController(from: game, as: .host) + parentDelegate.pushViewController(gameVC, animated: true) + } + + parentDelegate.presentPopover(popover, sourceView: sourceView, sourceRect: sourceRect, animated: true) + } else { + let gameVC = generateGameViewController(from: game) + parentDelegate.pushViewController(gameVC, animated: true) + } + } + + private static func shouldPresentMultiplayerOptions(for game: DemoGames) -> Bool { + switch game { + case .TankGame, .TankRaceGame: + return true + case .SnakeChomp: + return false + } + } } diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index e675fa4..5e51bc3 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -145,14 +145,14 @@ 282248532BA82E5800850D7F /* SKPhysicsBodyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 282248522BA82E5800850D7F /* SKPhysicsBodyManager.swift */; }; 286C09C02BADD0BB000343B1 /* TankGameMapBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286C09BF2BADD0BB000343B1 /* TankGameMapBuilder.swift */; }; 286C09C22BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286C09C12BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift */; }; - 2882ABF92BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */; }; - 2882ABFB2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */; }; - 2882ABFD2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */; }; - 2882ABFF2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */; }; 287B00552BC16E6C002F0114 /* TankHPComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287B00542BC16E6C002F0114 /* TankHPComponent.swift */; }; 287B00572BC17127002F0114 /* TankDestroyedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287B00562BC17127002F0114 /* TankDestroyedEvent.swift */; }; 287B00592BC17131002F0114 /* TankHPModifyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287B00582BC17131002F0114 /* TankHPModifyEvent.swift */; }; 287B005F2BC19F32002F0114 /* TankReviveEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287B005E2BC19F32002F0114 /* TankReviveEvent.swift */; }; + 2882ABF92BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */; }; + 2882ABFB2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */; }; + 2882ABFD2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */; }; + 2882ABFF2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */; }; 28844EA12BA881E60037A7F6 /* DemoPhysicsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28844EA02BA881E60037A7F6 /* DemoPhysicsComponent.swift */; }; 28A032DC2BAD4E8200851BFF /* StopWatchComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A032DB2BAD4E8200851BFF /* StopWatchComponent.swift */; }; 28A032DF2BAD4F2A00851BFF /* ArkTimeSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28A032DE2BAD4F2A00851BFF /* ArkTimeSystem.swift */; }; @@ -161,6 +161,8 @@ 28D006572BAF116A001B4BD4 /* TankGameCollisionStrategyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D006562BAF116A001B4BD4 /* TankGameCollisionStrategyManager.swift */; }; 28D0065F2BAFD7E1001B4BD4 /* EntityManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D0065E2BAFD7E1001B4BD4 /* EntityManagerTests.swift */; }; 28D006612BAFEE3D001B4BD4 /* ArkPhysicsKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D006602BAFEE3D001B4BD4 /* ArkPhysicsKitTests.swift */; }; + 28EFCB1B2BCC36EB0059A908 /* P2PShare in Frameworks */ = {isa = PBXBuildFile; productRef = ADA847F32BBC11F400B19378 /* P2PShare */; }; + 28EFCB1D2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */; }; 8F5573BF2BA425E4007030C8 /* ShapeRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */; }; 8F5573C12BA42614007030C8 /* Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C02BA42614007030C8 /* Renderable.swift */; }; 8F5573C32BA4284D007030C8 /* BitmapRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */; }; @@ -206,7 +208,6 @@ 9479A3AF2BA952E300F99013 /* AbstractShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479A3AE2BA952E300F99013 /* AbstractShape.swift */; }; 9479A3B12BA953D800F99013 /* ShapeRenderableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479A3B02BA953D800F99013 /* ShapeRenderableComponent.swift */; }; 9479A3B42BA95E7E00F99013 /* AbstractColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479A3B32BA95E7E00F99013 /* AbstractColor.swift */; }; - AD260CA82BBFCC7E008B9654 /* EntityIDGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */; }; 94D053882BC6C6D2000280C6 /* ArkExternalResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D053872BC6C6D2000280C6 /* ArkExternalResources.swift */; }; 94D0538B2BC6CA2A000280C6 /* TankGameExternalResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D0538A2BC6CA2A000280C6 /* TankGameExternalResources.swift */; }; 94D0538E2BC6CB0D000280C6 /* ArkAudioEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D0538D2BC6CB0D000280C6 /* ArkAudioEnum.swift */; }; @@ -215,6 +216,7 @@ 94D0539E2BC7F32E000280C6 /* TankRaceGameExternalResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D0539D2BC7F32E000280C6 /* TankRaceGameExternalResources.swift */; }; 94D053A12BC80E0C000280C6 /* TankGameExplosionAnimationKeyframes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D053A02BC80E0C000280C6 /* TankGameExplosionAnimationKeyframes.swift */; }; 94D053A32BC80E8C000280C6 /* ArkImageEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D053A22BC80E8C000280C6 /* ArkImageEnum.swift */; }; + AD260CA82BBFCC7E008B9654 /* EntityIDGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */; }; AD2B59AC2BB87F2200198E99 /* ArkNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */; }; AD2B59B02BB94DE000198E99 /* ArkMultiplayerEventManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */; }; AD2B59B22BB94FBE00198E99 /* ArkMultiplayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */; }; @@ -258,7 +260,6 @@ ADD74C0E2BC0573E008CE36B /* SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */; }; ADD74C102BC05759008CE36B /* SendableEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0F2BC05759008CE36B /* SendableEntity.swift */; }; ADD74C122BC15C52008CE36B /* ComponentRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */; }; - ADD74C162BC19232008CE36B /* P2PShare in Frameworks */ = {isa = PBXBuildFile; productRef = ADD74C152BC19232008CE36B /* P2PShare */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -410,14 +411,14 @@ 282248522BA82E5800850D7F /* SKPhysicsBodyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SKPhysicsBodyManager.swift; sourceTree = ""; }; 286C09BF2BADD0BB000343B1 /* TankGameMapBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameMapBuilder.swift; sourceTree = ""; }; 286C09C12BADD5FB000343B1 /* TankGameTerrainObjectBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameTerrainObjectBuilder.swift; sourceTree = ""; }; - 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BitmapImageRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; - 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RectRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; - 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CircleRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; - 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PolygonRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; 287B00542BC16E6C002F0114 /* TankHPComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankHPComponent.swift; sourceTree = ""; }; 287B00562BC17127002F0114 /* TankDestroyedEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankDestroyedEvent.swift; sourceTree = ""; }; 287B00582BC17131002F0114 /* TankHPModifyEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankHPModifyEvent.swift; sourceTree = ""; }; 287B005E2BC19F32002F0114 /* TankReviveEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankReviveEvent.swift; sourceTree = ""; }; + 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BitmapImageRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; + 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RectRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; + 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CircleRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; + 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PolygonRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; 28844EA02BA881E60037A7F6 /* DemoPhysicsComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoPhysicsComponent.swift; sourceTree = ""; }; 28A032DB2BAD4E8200851BFF /* StopWatchComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopWatchComponent.swift; sourceTree = ""; }; 28A032DE2BAD4F2A00851BFF /* ArkTimeSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkTimeSystem.swift; sourceTree = ""; }; @@ -426,6 +427,7 @@ 28D006562BAF116A001B4BD4 /* TankGameCollisionStrategyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameCollisionStrategyManager.swift; sourceTree = ""; }; 28D0065E2BAFD7E1001B4BD4 /* EntityManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityManagerTests.swift; sourceTree = ""; }; 28D006602BAFEE3D001B4BD4 /* ArkPhysicsKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkPhysicsKitTests.swift; sourceTree = ""; }; + 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkDemoMultiplayerPopover.swift; sourceTree = ""; }; 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeRenderable.swift; sourceTree = ""; }; 8F5573C02BA42614007030C8 /* Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderable.swift; sourceTree = ""; }; 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitmapRenderable.swift; sourceTree = ""; }; @@ -470,7 +472,6 @@ 9479A3AE2BA952E300F99013 /* AbstractShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractShape.swift; sourceTree = ""; }; 9479A3B02BA953D800F99013 /* ShapeRenderableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeRenderableComponent.swift; sourceTree = ""; }; 9479A3B32BA95E7E00F99013 /* AbstractColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractColor.swift; sourceTree = ""; }; - AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityIDGenerator.swift; sourceTree = ""; }; 94D053872BC6C6D2000280C6 /* ArkExternalResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkExternalResources.swift; sourceTree = ""; }; 94D0538A2BC6CA2A000280C6 /* TankGameExternalResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameExternalResources.swift; sourceTree = ""; }; 94D0538D2BC6CB0D000280C6 /* ArkAudioEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkAudioEnum.swift; sourceTree = ""; }; @@ -479,6 +480,7 @@ 94D0539D2BC7F32E000280C6 /* TankRaceGameExternalResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankRaceGameExternalResources.swift; sourceTree = ""; }; 94D053A02BC80E0C000280C6 /* TankGameExplosionAnimationKeyframes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameExplosionAnimationKeyframes.swift; sourceTree = ""; }; 94D053A22BC80E8C000280C6 /* ArkImageEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkImageEnum.swift; sourceTree = ""; }; + AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityIDGenerator.swift; sourceTree = ""; }; AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkService.swift; sourceTree = ""; }; AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerEventManager.swift; sourceTree = ""; }; AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerManager.swift; sourceTree = ""; }; @@ -532,7 +534,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - ADA847F42BBC11F400B19378 /* P2PShare in Frameworks */, + 28EFCB1B2BCC36EB0059A908 /* P2PShare in Frameworks */, 9454411D2BC950DB00E90ECE /* DequeModule in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -645,6 +647,7 @@ children = ( 02B3C6072BCAEE9A002331A0 /* ArkDemoHomePage.swift */, 026882482BCACAFF00212BD6 /* ArkDemoGameHostingPage.swift */, + 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */, ); path = pages; sourceTree = ""; @@ -1309,15 +1312,6 @@ path = color; sourceTree = ""; }; - AD260CA62BBFCC6C008B9654 /* entity */ = { - isa = PBXGroup; - children = ( - AD787A812B9C6BD9003EBBD0 /* Entity.swift */, - AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */, - ); - path = entity; - sourceTree = ""; - }; 94D053892BC6C9F0000280C6 /* ExternalAssets */ = { isa = PBXGroup; children = ( @@ -1372,6 +1366,15 @@ path = Animation; sourceTree = ""; }; + AD260CA62BBFCC6C008B9654 /* entity */ = { + isa = PBXGroup; + children = ( + AD787A812B9C6BD9003EBBD0 /* Entity.swift */, + AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */, + ); + path = entity; + sourceTree = ""; + }; AD2B59AA2BB87F0F00198E99 /* ark-multiplayer-kit */ = { isa = PBXGroup; children = ( @@ -1927,6 +1930,7 @@ 02C394E02BA405C10075F1CA /* Canvas.swift in Sources */, ADA848022BBC7DF000B19378 /* ArkSerializableEvent.swift in Sources */, 942A16772BADF3ED00F0186B /* AudioContext.swift in Sources */, + 28EFCB1D2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift in Sources */, AD787A842B9C6C78003EBBD0 /* Component.swift in Sources */, 945441252BC99D8800E90ECE /* SnakeGridPositionComponent.swift in Sources */, 8F5573BF2BA425E4007030C8 /* ShapeRenderable.swift in Sources */, @@ -2297,7 +2301,6 @@ }; ADA847F32BBC11F400B19378 /* P2PShare */ = { isa = XCSwiftPackageProductDependency; - package = ADD74C142BC19232008CE36B /* XCRemoteSwiftPackageReference "P2PShareKit" */; productName = P2PShare; }; /* End XCSwiftPackageProductDependency section */ From 3bf221ce726aeef86ee47168312b06a77bc8bf7e Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Mon, 15 Apr 2024 09:22:03 +0800 Subject: [PATCH 20/43] [ark-multiplayer-kit] Encode entire ECS --- ArkKit.xcodeproj/project.pbxproj | 30 ++++--- ArkKit/Ark.swift | 38 +++++--- .../ArkMultiplayerContext.swift | 1 + .../ArkMultiplayerECS.swift | 77 ----------------- .../ArkMultiplayerGameLoop.swift | 28 ++++++ .../ArkMultiplayerManager.swift | 70 ++++----------- .../data-serialize/ArkECSDataSerializer.swift | 17 ++++ .../data-serialize/ArkECSSerializer.swift | 86 ------------------- .../data-serialize/ArkECSWrapper.swift | 51 +++++++++++ ...zer.swift => ArkEventDataSerializer.swift} | 9 +- .../data-serialize/ArkSerializableEvent.swift | 7 -- .../data-serialize}/ComponentRegistry.swift | 7 -- .../data-serialize/DataWrapper.swift | 9 +- .../sendable/SendableEntity.swift | 2 + ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift | 17 ++++ .../ark-ecs-kit/EntityManager.swift | 12 ++- 16 files changed, 181 insertions(+), 280 deletions(-) delete mode 100644 ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift create mode 100644 ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift create mode 100644 ArkKit/ark-multiplayer-kit/data-serialize/ArkECSDataSerializer.swift delete mode 100644 ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift create mode 100644 ArkKit/ark-multiplayer-kit/data-serialize/ArkECSWrapper.swift rename ArkKit/ark-multiplayer-kit/data-serialize/{ArkDataSerializer.swift => ArkEventDataSerializer.swift} (92%) rename ArkKit/{ark-state-kit/ark-ecs-kit => ark-multiplayer-kit/data-serialize}/ComponentRegistry.swift (96%) diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 5e51bc3..fe48645 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -163,6 +163,8 @@ 28D006612BAFEE3D001B4BD4 /* ArkPhysicsKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D006602BAFEE3D001B4BD4 /* ArkPhysicsKitTests.swift */; }; 28EFCB1B2BCC36EB0059A908 /* P2PShare in Frameworks */ = {isa = PBXBuildFile; productRef = ADA847F32BBC11F400B19378 /* P2PShare */; }; 28EFCB1D2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */; }; + 28EFCB252BCCA7CC0059A908 /* ArkECSWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */; }; + 28EFCB272BCCB6130059A908 /* ArkMultiplayerGameLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */; }; 8F5573BF2BA425E4007030C8 /* ShapeRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */; }; 8F5573C12BA42614007030C8 /* Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C02BA42614007030C8 /* Renderable.swift */; }; 8F5573C32BA4284D007030C8 /* BitmapRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */; }; @@ -253,10 +255,9 @@ AD787A8A2B9C70D8003EBBD0 /* ArkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD787A892B9C70D8003EBBD0 /* ArkState.swift */; }; AD787A8C2B9C70FC003EBBD0 /* SystemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD787A8B2B9C70FC003EBBD0 /* SystemManager.swift */; }; ADA847FD2BBC4B5500B19378 /* ArkNetworkProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */; }; - ADA847FF2BBC4EA800B19378 /* ArkDataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */; }; + ADA847FF2BBC4EA800B19378 /* ArkEventDataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FE2BBC4EA800B19378 /* ArkEventDataSerializer.swift */; }; ADA848022BBC7DF000B19378 /* ArkSerializableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */; }; - ADD74C092BBFEBF3008CE36B /* ArkMultiplayerECS.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */; }; - ADD74C0C2BC05677008CE36B /* ArkECSSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0B2BC05677008CE36B /* ArkECSSerializer.swift */; }; + ADD74C0C2BC05677008CE36B /* ArkECSDataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0B2BC05677008CE36B /* ArkECSDataSerializer.swift */; }; ADD74C0E2BC0573E008CE36B /* SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */; }; ADD74C102BC05759008CE36B /* SendableEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0F2BC05759008CE36B /* SendableEntity.swift */; }; ADD74C122BC15C52008CE36B /* ComponentRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */; }; @@ -428,6 +429,8 @@ 28D0065E2BAFD7E1001B4BD4 /* EntityManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityManagerTests.swift; sourceTree = ""; }; 28D006602BAFEE3D001B4BD4 /* ArkPhysicsKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkPhysicsKitTests.swift; sourceTree = ""; }; 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkDemoMultiplayerPopover.swift; sourceTree = ""; }; + 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSWrapper.swift; sourceTree = ""; }; + 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerGameLoop.swift; sourceTree = ""; }; 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeRenderable.swift; sourceTree = ""; }; 8F5573C02BA42614007030C8 /* Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderable.swift; sourceTree = ""; }; 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitmapRenderable.swift; sourceTree = ""; }; @@ -520,10 +523,9 @@ AD787A892B9C70D8003EBBD0 /* ArkState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkState.swift; sourceTree = ""; }; AD787A8B2B9C70FC003EBBD0 /* SystemManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemManager.swift; sourceTree = ""; }; ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkProtocol.swift; sourceTree = ""; }; - ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkDataSerializer.swift; sourceTree = ""; }; + ADA847FE2BBC4EA800B19378 /* ArkEventDataSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkEventDataSerializer.swift; sourceTree = ""; }; ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSerializableEvent.swift; sourceTree = ""; }; - ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerECS.swift; sourceTree = ""; }; - ADD74C0B2BC05677008CE36B /* ArkECSSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSSerializer.swift; sourceTree = ""; }; + ADD74C0B2BC05677008CE36B /* ArkECSDataSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSDataSerializer.swift; sourceTree = ""; }; ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableComponent.swift; sourceTree = ""; }; ADD74C0F2BC05759008CE36B /* SendableEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableEntity.swift; sourceTree = ""; }; ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentRegistry.swift; sourceTree = ""; }; @@ -1381,10 +1383,10 @@ ADD74C0A2BC0555D008CE36B /* sendable */, ADA848002BBC7DC600B19378 /* data-serialize */, AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */, + 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */, AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */, AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */, ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */, - ADD74C082BBFEBF3008CE36B /* ArkMultiplayerECS.swift */, AD4E148F2BC2797000A32C8B /* ArkMultiplayerContext.swift */, ); path = "ark-multiplayer-kit"; @@ -1456,7 +1458,6 @@ AD787A872B9C6D3A003EBBD0 /* EntityManager.swift */, AD787A8B2B9C70FC003EBBD0 /* SystemManager.swift */, AD6E03602B9F15FA00974EBF /* ArkECS.swift */, - ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */, ); path = "ark-ecs-kit"; sourceTree = ""; @@ -1567,9 +1568,11 @@ ADA848002BBC7DC600B19378 /* data-serialize */ = { isa = PBXGroup; children = ( + ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */, + 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */, ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */, - ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */, - ADD74C0B2BC05677008CE36B /* ArkECSSerializer.swift */, + ADA847FE2BBC4EA800B19378 /* ArkEventDataSerializer.swift */, + ADD74C0B2BC05677008CE36B /* ArkECSDataSerializer.swift */, AD2B59B52BB958E400198E99 /* DataWrapper.swift */, ); path = "data-serialize"; @@ -1784,7 +1787,6 @@ 02C395242BA849B40075F1CA /* JoystickRenderableComponent.swift in Sources */, 942A16792BADF40600F0186B /* ArkAudioContext.swift in Sources */, AD787A822B9C6BD9003EBBD0 /* Entity.swift in Sources */, - ADD74C092BBFEBF3008CE36B /* ArkMultiplayerECS.swift in Sources */, 9479A3B42BA95E7E00F99013 /* AbstractColor.swift in Sources */, 0267BA2A2BBD10060010F729 /* CleanUpSystem.swift in Sources */, 02B3C6192BCAF69E002331A0 /* CollisionHandlingStrategy.swift in Sources */, @@ -1817,6 +1819,7 @@ 02D8E94E2BAC9A0E00BF3A07 /* GameLoop.swift in Sources */, 945441102BC9202300E90ECE /* SnakeGameDirection.swift in Sources */, 2812FCC72BC442BD00A0FE24 /* TankRaceGameTerrainObjectBuilder.swift in Sources */, + 28EFCB272BCCB6130059A908 /* ArkMultiplayerGameLoop.swift in Sources */, 0266151D2BC1405C00B56A85 /* TankRaceGame.swift in Sources */, AD6E03612B9F15FA00974EBF /* ArkECS.swift in Sources */, 8F5573CA2BA6A633007030C8 /* ArkAnimationSystem.swift in Sources */, @@ -1856,7 +1859,7 @@ AD6E03652BA1949000974EBF /* ArkEvent.swift in Sources */, AD4E14902BC2797000A32C8B /* ArkMultiplayerContext.swift in Sources */, 943D418C2BAEBF2E00F9E88F /* ArkSetupContext.swift in Sources */, - ADA847FF2BBC4EA800B19378 /* ArkDataSerializer.swift in Sources */, + ADA847FF2BBC4EA800B19378 /* ArkEventDataSerializer.swift in Sources */, AD36A7582BAC3223003E938B /* TankGameEntityCreator.swift in Sources */, 0267BA2F2BBD2BD70010F729 /* ArkCameraSystem.swift in Sources */, 282248532BA82E5800850D7F /* SKPhysicsBodyManager.swift in Sources */, @@ -1881,7 +1884,7 @@ 2882ABFB2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift in Sources */, 8FEB217E2BADF21E00788E20 /* ArkActionContext.swift in Sources */, 02C394F92BA443830075F1CA /* TapRenderable.swift in Sources */, - ADD74C0C2BC05677008CE36B /* ArkECSSerializer.swift in Sources */, + ADD74C0C2BC05677008CE36B /* ArkECSDataSerializer.swift in Sources */, 026882492BCACAFF00212BD6 /* ArkDemoGameHostingPage.swift in Sources */, 2812FCBF2BC3DE2F00A0FE24 /* TankRaceMoveEvent.swift in Sources */, 8FEB217E2BADF21E00788E20 /* ArkActionContext.swift in Sources */, @@ -1935,6 +1938,7 @@ 945441252BC99D8800E90ECE /* SnakeGridPositionComponent.swift in Sources */, 8F5573BF2BA425E4007030C8 /* ShapeRenderable.swift in Sources */, 280CD3B72BA7391100372C5D /* ArkPhysicsSystem.swift in Sources */, + 28EFCB252BCCA7CC0059A908 /* ArkECSWrapper.swift in Sources */, 280CD3D62BA7F8CC00372C5D /* SKPhysicsScene.swift in Sources */, 941BE21E2BC0EC1900707997 /* ArkProtocol.swift in Sources */, AD787A8C2B9C70FC003EBBD0 /* SystemManager.swift in Sources */, diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index 2b8bd55..8a33f09 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -49,6 +49,31 @@ class Ark: ArkProtocol { ) } + init(rootView: any AbstractRootView, + blueprint: ArkBlueprint, + multiplayerContext: ArkMultiplayerContext, + canvasRenderableBuilder: (any RenderableBuilder)? = nil) { + self.rootView = rootView + self.blueprint = blueprint + let ecsManager = ArkECS() + let eventManager = ArkMultiplayerEventManager() + let multiplayerManager = ArkMultiplayerManager(serviceName: multiplayerContext.serviceName, + role: multiplayerContext.role, + ecs: ecsManager) + multiplayerManager.multiplayerEventManager = eventManager + eventManager.delegate = multiplayerManager + self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) + self.audioContext = ArkAudioContext() + self.canvasRenderableBuilder = canvasRenderableBuilder + self.displayContext = ArkDisplayContext( + canvasSize: CGSize( + width: blueprint.frameWidth, + height: blueprint.frameHeight + ), + screenSize: rootView.size + ) + } + func start() { setupDefaultEntities() setupDefaultListeners() @@ -70,19 +95,6 @@ class Ark: ArkProtocol { gameCoordinator.start() } - func multiplayer(serviceName: String) { - let multiplayerManager = ArkMultiplayerManager(serviceName: "tankGame") - let eventManager = ArkMultiplayerEventManager() - let ecsManager = ArkMultiplayerECS() - multiplayerManager.multiplayerEventManager = eventManager - multiplayerManager.arkMultiplayerECS = ecsManager - eventManager.delegate = multiplayerManager - ecsManager.delegate = multiplayerManager - - self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) - self.multiplayerContext = multiplayerManager - } - private func setupDefaultListeners() { arkState.eventManager.subscribe(to: ScreenResizeEvent.self) { [weak self] event in guard let resizeEvent = event as? ScreenResizeEvent, diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift index 7126328..56f3ce3 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift @@ -3,4 +3,5 @@ import Foundation protocol ArkMultiplayerContext { var playerNumber: Int { get } var serviceName: String { get set } + var role: ArkPeerRole { get } } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift deleted file mode 100644 index ee1bf9f..0000000 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerECS.swift +++ /dev/null @@ -1,77 +0,0 @@ -import Foundation - -class ArkMultiplayerECS: ArkECS { - let arkECS: ArkECS - var delegate: ArkMultiplayerECSDelegate? - - init(arkECS: ArkECS = ArkECS(), - delegate: ArkMultiplayerECSDelegate? = nil) { - self.arkECS = arkECS - self.delegate = delegate - } - - override func update(deltaTime: TimeInterval) { - guard delegate?.isModificationEnabled ?? true else { - return - } - - arkECS.update(deltaTime: deltaTime) - } - - @discardableResult - override func createEntity() -> Entity { - guard delegate?.isModificationEnabled ?? true else { - return Entity() - } - - let entity = arkECS.createEntity() - delegate?.didCreateEntity(entity) - return entity - } - - override func removeEntity(_ entity: Entity) { - guard delegate?.isModificationEnabled ?? true else { - return - } - - arkECS.removeEntity(entity) - delegate?.didRemoveEntity(entity) - } - - override func upsertComponent(_ component: T, to entity: Entity) where T: Component { - guard delegate?.isModificationEnabled ?? true else { - return - } - - arkECS.upsertComponent(component, to: entity) - delegate?.didUpsertComponent(component, to: entity) - } - - override func removeComponent(ofType componentType: T.Type, from entity: Entity) where T: Component { - guard delegate?.isModificationEnabled ?? true else { - return - } - - arkECS.removeComponent(ofType: componentType, from: entity) - } - - @discardableResult - override func createEntity(with components: [any Component]) -> Entity { - guard delegate?.isModificationEnabled ?? true else { - return Entity() - } - - let entity = arkECS.createEntity(with: components) - delegate?.didCreateEntity(entity, with: components) - - return entity - } -} - -protocol ArkMultiplayerECSDelegate: AnyObject { - var isModificationEnabled: Bool { get } - func didCreateEntity(_ entity: Entity) - func didRemoveEntity(_ entity: Entity) - func didUpsertComponent(_ component: T, to entity: Entity) - func didCreateEntity(_ entity: Entity, with components: [Component]) -} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift new file mode 100644 index 0000000..14575e6 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift @@ -0,0 +1,28 @@ +class ArkMultiplayerGameLoop: GameLoop { + var updatePhysicsSceneDelegate: (any ArkPhysicsSceneUpdateLoopDelegate)? + + var updateGameWorldDelegate: (any ArkGameWorldUpdateLoopDelegate)? + + func setUp() { + } + + func update() { + } + + func getDeltaTime() -> Double { + 0.0 + } + + func shutDown() { + } + + func pauseLoop() { + } + + func resumeLoop() { + } +} + +protocol ArkMultiplayerECSDelegate { + +} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index eb725e1..6766fb0 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -1,6 +1,6 @@ import Foundation -enum PeerRole { +enum ArkPeerRole { case host case participant } @@ -8,25 +8,21 @@ enum PeerRole { class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { private var networkService: ArkNetworkProtocol var multiplayerEventManager: ArkMultiplayerEventManager? - var arkMultiplayerECS: ArkMultiplayerECS? + var ecs: ArkECS private var peers = [String]() private var host: String? - private var role: PeerRole = .host + private(set) var role: ArkPeerRole + var playerNumber: Int { + peers.firstIndex(of: networkService.deviceID) ?? 0 + } - init(serviceName: String) { + init(serviceName: String, role: ArkPeerRole, ecs: ArkECS) { self.networkService = ArkNetworkService(serviceName: serviceName) + self.ecs = ecs + self.role = role self.networkService.delegate = self } - var playerNumber: Int { - let sortedPeers = (peers + [networkService.deviceID]).sorted() - if let deviceIndex = sortedPeers.firstIndex(of: networkService.deviceID) { - return deviceIndex + 1 - } else { - return 0 - } - } - var serviceName: String { get { networkService.serviceName @@ -46,10 +42,9 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { processEvent(event: event) } - if wrappedData.type == .ecsFunction, let arkMultiplayerECS = arkMultiplayerECS { - try ArkECSSerializer.decodeECSFunction(data: wrappedData.payload, - name: wrappedData.name, - ecs: arkMultiplayerECS.arkECS) + if wrappedData.type == .ecs { + let ecsWrapper = try ArkECSDataSerializer.decodeArkECS(from: wrappedData.payload) + ecs.upsertEntityManager(entities: ecsWrapper.entities, components: ecsWrapper.decodeComponents()) } } catch { @@ -86,7 +81,7 @@ extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { private func sendEvent(event: any ArkEvent) { do { - if let encodedEvent = try ArkDataSerializer.encodeEvent(event), + if let encodedEvent = try ArkEventDataSerializer.encodeEvent(event), let target = host { networkService.sendData(encodedEvent, to: target) } @@ -101,46 +96,13 @@ extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { self.role == .host } - private func sendEcsFunction(function: String, entity: Entity, component: Component? = nil, - components: [Component]? = nil) { + private func sendEcs(ecs: ArkECS) { do { - if let encodedECSFunction = try ArkECSSerializer.encodeECSFunction(action: function, entity: entity, - component: component, - components: components) { - networkService.sendData(data: encodedECSFunction) - } + let encodedECS = try ArkECSDataSerializer.encodeArkECS(ecs: ecs) + networkService.sendData(data: encodedECS) } catch { print("Error encoding or sending ecs function: \(error)") } } - - func didCreateEntity(_ entity: Entity) { - guard self.role == .host else { - return - } - sendEcsFunction(function: "createEntity", entity: entity) - } - - func didRemoveEntity(_ entity: Entity) { - guard self.role == .host else { - return - } - sendEcsFunction(function: "removeEntity", entity: entity) - } - - func didUpsertComponent(_ component: T, to entity: Entity) { - guard self.role == .host else { - return - } - sendEcsFunction(function: "upsertComponent", entity: entity, component: component) - } - - func didCreateEntity(_ entity: Entity, with components: [Component]) { - guard self.role == .host else { - return - } - sendEcsFunction(function: "createEntity", entity: entity, components: components) - } - } diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSDataSerializer.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSDataSerializer.swift new file mode 100644 index 0000000..6d0c476 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSDataSerializer.swift @@ -0,0 +1,17 @@ +import Foundation + +struct ArkECSDataSerializer { + static func encodeArkECS(ecs: ArkECS) throws -> Data { + let encoder = JSONEncoder() + let ecsWrapper = ArkECSWrapper(from: ecs) + let encodedECSData = try encoder.encode(ecsWrapper) + let wrappedData = DataWrapper(type: .ecs, name: "ArkECS", payload: encodedECSData) + return try encoder.encode(wrappedData) + } + + static func decodeArkECS(from data: Data) throws -> ArkECSWrapper { + let decoder = JSONDecoder() + let ecsWrapper = try decoder.decode(ArkECSWrapper.self, from: data) + return ecsWrapper + } +} diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift deleted file mode 100644 index 9a5d2c7..0000000 --- a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSSerializer.swift +++ /dev/null @@ -1,86 +0,0 @@ -import Foundation - -struct ArkECSSerializer { - - typealias ECSActionClosure = (Entity, [Component], ArkECS) -> Void - - enum ECSFunctionType: String, Codable { - case createEntity, removeEntity, upsertComponent - } - - static let actionsDictionary: [ECSFunctionType: ECSActionClosure] = [ - .createEntity: { entity, components, ecs in - if components.isEmpty { - ecs.createEntity(id: entity.id) - } else { - ecs.createEntity(id: entity.id, with: components) - } - }, - .removeEntity: { entity, _, ecs in - ecs.removeEntity(entity) - }, - .upsertComponent: { entity, components, ecs in - if let component = components.first { - ecs.upsertComponent(component, to: entity) - } - } - ] - - private static func encodeComponent(_ component: T) throws -> ComponentData? { - guard let component = component as? any SendableComponent else { - return nil - } -// print("component is sendable component: \(component)") - let componentData = try JSONEncoder().encode(component) - let name = String(describing: type(of: component)) - - return ComponentData(name: name, data: componentData) - } - - static func encodeECSFunction(action: String, entity: Entity, component: Component? = nil, - components: [Component]? = nil) throws -> Data? { - - var componentData = [ComponentData]() - - if let component = component, - let encodedComponent = try encodeComponent(component) { - componentData.append(encodedComponent) - } - - componentData += components?.compactMap { try? encodeComponent($0) } ?? [] - - let ecsfunctiondata = ECSFunctionData(entity: entity, componentData: componentData) - let data = try JSONEncoder().encode(ecsfunctiondata) - let wrappedData = DataWrapper(type: .ecsFunction, name: action, payload: data) - - return try JSONEncoder().encode(wrappedData) - } - - static func decodeECSFunction(data: Data, name: String, ecs: ArkECS) throws { - let decoder = JSONDecoder() - let functionData = try decoder.decode(ECSFunctionData.self, from: data) - - let entity = functionData.entity - let components = functionData.componentData - .compactMap { try? ComponentRegistry.shared.decode(from: $0.data, - typeName: $0.name) - } - - if let functionType = ECSFunctionType(rawValue: name), - let actionClosure = actionsDictionary[functionType] { - actionClosure(entity, components, ecs) - } else { - print("Unsupported action: \(functionData)") - } - } - - struct ComponentData: Codable { - var name: String - var data: Data - } - - struct ECSFunctionData: Codable { - var entity: Entity - var componentData: [ComponentData] - } -} diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSWrapper.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSWrapper.swift new file mode 100644 index 0000000..a9c4acb --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkECSWrapper.swift @@ -0,0 +1,51 @@ +import Foundation + +struct ArkECSWrapper: Codable { + var entities: [Entity] + var components: [EntityID: [String: Data]] + + enum CodingKeys: String, CodingKey { + case entities, components + } + + init(from ecs: ArkECS) { + self.entities = ecs.getEntities(with: []) + self.components = entities.reduce(into: [EntityID: [String: Data]]()) { result, entity in + let allComponents = ecs.getComponents(from: entity).compactMap { $0 as? Codable & Component } + var componentData = [String: Data]() + for component in allComponents { + let typeName = String(describing: type(of: component)) + if let data = try? JSONEncoder().encode(component) { + componentData[typeName] = data + } + } + result[entity.id] = componentData + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(entities, forKey: .entities) + try container.encode(components, forKey: .components) + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + entities = try container.decode([Entity].self, forKey: .entities) + components = try container.decode([EntityID: [String: Data]].self, forKey: .components) + } + + func decodeComponents() -> [EntityID: [any Component]] { + var decodedComponents = [EntityID: [any Component]]() + for (entityId, componentDict) in components { + var entityComponents = [any Component]() + for (typeName, data) in componentDict { + if let component = try? ComponentRegistry.shared.decode(from: data, typeName: typeName) { + entityComponents.append(component) + } + } + decodedComponents[entityId] = entityComponents + } + return decodedComponents + } +} diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkDataSerializer.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkEventDataSerializer.swift similarity index 92% rename from ArkKit/ark-multiplayer-kit/data-serialize/ArkDataSerializer.swift rename to ArkKit/ark-multiplayer-kit/data-serialize/ArkEventDataSerializer.swift index b782d18..1a0110e 100644 --- a/ArkKit/ark-multiplayer-kit/data-serialize/ArkDataSerializer.swift +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkEventDataSerializer.swift @@ -1,13 +1,6 @@ -// -// ArkDataSerializer.swift -// ArkKit -// -// Created by Ryan Peh on 2/4/24. -// - import Foundation -class ArkDataSerializer { +class ArkEventDataSerializer { // Encode any ArkEvent into Data static func encodeEvent(_ event: Event) throws -> Data? { diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkSerializableEvent.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkSerializableEvent.swift index 1aa6ff7..229d647 100644 --- a/ArkKit/ark-multiplayer-kit/data-serialize/ArkSerializableEvent.swift +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkSerializableEvent.swift @@ -1,10 +1,3 @@ -// -// ArkSerializableEvent.swift -// ArkKit -// -// Created by Ryan Peh on 3/4/24. -// - protocol ArkSerializableEventData: ArkEventData, Codable { } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ComponentRegistry.swift similarity index 96% rename from ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift rename to ArkKit/ark-multiplayer-kit/data-serialize/ComponentRegistry.swift index b230b0b..8704c2d 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ComponentRegistry.swift +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ComponentRegistry.swift @@ -1,10 +1,3 @@ -// -// ComponentRegistry.swift -// ArkKit -// -// Created by Ryan Peh on 6/4/24. -// - import Foundation class ComponentRegistry { diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift b/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift index f11901e..3f0e120 100644 --- a/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift +++ b/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift @@ -1,14 +1,7 @@ -// -// DataWrapper.swift -// ArkKit -// -// Created by Ryan Peh on 31/3/24. -// - import Foundation enum PayloadType: String, Codable { - case event, ecsFunction + case event, ecs } struct DataWrapper: Codable { diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift index 44e1c9f..348a2f0 100644 --- a/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift @@ -1,2 +1,4 @@ struct SendableEntity: Codable { } + +typealias Sendable = Codable diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift index 10d5fa9..e880e3c 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift @@ -9,6 +9,23 @@ class ArkECS: ArkECSContext { self.systemManager = SystemManager() } + init(entities: [Entity], components: [EntityID: [any Component]]) { + self.entityManager = EntityManager() + self.systemManager = SystemManager() + for entity in entities { + let components = components[entity.id] ?? [] + _ = entityManager.createEntity(with: components) + } + } + + func upsertEntityManager(entities: [Entity], components: [EntityID: [any Component]]) { + entityManager.removeAllEntities() + for entity in entities { + let components = components[entity.id] ?? [] + _ = entityManager.createEntity(with: components) + } + } + func startUp() { self.systemManager.startUp() } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift index ae10dd8..5aaa249 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift @@ -1,10 +1,3 @@ -// -// EntityManager.swift -// LevelKit -// -// Created by Ryan Peh on 9/3/24. -// - import Foundation class EntityManager { @@ -90,4 +83,9 @@ class EntityManager { }) return result } + + func removeAllEntities() { + entities = [] + componentsByType = [:] + } } From 4a044b59ad636c20baa6e605ccfae52d7711f520 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Mon, 15 Apr 2024 09:30:42 +0800 Subject: [PATCH 21/43] [Ark] feat: Different Ark Start for host and participant --- ArkKit/Ark.swift | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index 8a33f09..ffb5b9a 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -72,15 +72,15 @@ class Ark: ArkProtocol { ), screenSize: rootView.size ) + self.multiplayerContext = multiplayerContext } func start() { - setupDefaultEntities() - setupDefaultListeners() - setupDefaultSystems(blueprint) - setup(blueprint.setupFunctions) - setup(blueprint.rules) - setup(blueprint.soundMapping) + if let multiplayerContext = multiplayerContext, multiplayerContext.role == .participant { + multiplayerParticipantStart() + } else { + defaultStart() + } alignCamera() guard let gameLoop = self.gameLoop else { @@ -94,6 +94,22 @@ class Ark: ArkProtocol { canvasRenderer: canvasRenderableBuilder) gameCoordinator.start() } + + private func multiplayerParticipantStart() { + setupDefaultListeners() + setupMultiplayerGameLoop() + setup(blueprint.soundMapping) + } + + private func defaultStart() { + setupDefaultEntities() + setupDefaultListeners() + setupDefaultSystems(blueprint) + setup(blueprint.setupFunctions) + setup(blueprint.setupFunctions) + setup(blueprint.rules) + setup(blueprint.soundMapping) + } private func setupDefaultListeners() { arkState.eventManager.subscribe(to: ScreenResizeEvent.self) { [weak self] event in @@ -221,6 +237,10 @@ class Ark: ArkProtocol { simulator.physicsScene?.sceneUpdateLoopDelegate = physicsSystem self.gameLoop?.updatePhysicsSceneDelegate = physicsSystem } + + func setupMultiplayerGameLoop() { + gameLoop = ArkMultiplayerGameLoop() + } private func getWorldSize(_ blueprint: ArkBlueprint) -> (width: Double, height: Double) { guard let worldEntity = arkState.arkECS.getEntities(with: [WorldComponent.self]).first, From 6a89721fcd55c66d47e94bd20b8cd6cb25a43ba9 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Mon, 15 Apr 2024 10:53:51 +0800 Subject: [PATCH 22/43] [ark-multiplayer-kit] Setup host and participant receive and send --- ArkKit.xcodeproj/project.pbxproj | 4 +++ ArkKit/Ark.swift | 33 +++++++++++++++---- .../ArkMultiplayerGameLoop.swift | 10 ++++-- .../ArkMultiplayerManager.swift | 33 +++++++++++-------- .../ArkMultiplayerSystem.swift | 15 +++++++++ 5 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 ArkKit/ark-multiplayer-kit/ArkMultiplayerSystem.swift diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index fe48645..cd5ad5b 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -165,6 +165,7 @@ 28EFCB1D2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */; }; 28EFCB252BCCA7CC0059A908 /* ArkECSWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */; }; 28EFCB272BCCB6130059A908 /* ArkMultiplayerGameLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */; }; + 28EFCB292BCCC90B0059A908 /* ArkMultiplayerSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB282BCCC90B0059A908 /* ArkMultiplayerSystem.swift */; }; 8F5573BF2BA425E4007030C8 /* ShapeRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */; }; 8F5573C12BA42614007030C8 /* Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C02BA42614007030C8 /* Renderable.swift */; }; 8F5573C32BA4284D007030C8 /* BitmapRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */; }; @@ -431,6 +432,7 @@ 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkDemoMultiplayerPopover.swift; sourceTree = ""; }; 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSWrapper.swift; sourceTree = ""; }; 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerGameLoop.swift; sourceTree = ""; }; + 28EFCB282BCCC90B0059A908 /* ArkMultiplayerSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerSystem.swift; sourceTree = ""; }; 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeRenderable.swift; sourceTree = ""; }; 8F5573C02BA42614007030C8 /* Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderable.swift; sourceTree = ""; }; 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitmapRenderable.swift; sourceTree = ""; }; @@ -1388,6 +1390,7 @@ AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */, ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */, AD4E148F2BC2797000A32C8B /* ArkMultiplayerContext.swift */, + 28EFCB282BCCC90B0059A908 /* ArkMultiplayerSystem.swift */, ); path = "ark-multiplayer-kit"; sourceTree = ""; @@ -1776,6 +1779,7 @@ AD2B59B22BB94FBE00198E99 /* ArkMultiplayerManager.swift in Sources */, 287B00552BC16E6C002F0114 /* TankHPComponent.swift in Sources */, 02C3950F2BA611B80075F1CA /* ArkEventContext.swift in Sources */, + 28EFCB292BCCC90B0059A908 /* ArkMultiplayerSystem.swift in Sources */, 8FEB217A2BADE60300788E20 /* DisplayContext.swift in Sources */, 022C427D2BC9943B003D6924 /* ResumeGameLoopEvent.swift in Sources */, 28A032DC2BAD4E8200851BFF /* StopWatchComponent.swift in Sources */, diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index ffb5b9a..0c3a85d 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -19,6 +19,7 @@ class Ark: ArkProtocol { let blueprint: ArkBlueprint let audioContext: any AudioContext var multiplayerContext: ArkMultiplayerContext? + var multiplayerManager: ArkMultiplayerManager? var displayContext: DisplayContext var actionContext: ArkActionContext { @@ -61,6 +62,7 @@ class Ark: ArkProtocol { role: multiplayerContext.role, ecs: ecsManager) multiplayerManager.multiplayerEventManager = eventManager + self.multiplayerManager = multiplayerManager eventManager.delegate = multiplayerManager self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) self.audioContext = ArkAudioContext() @@ -76,11 +78,16 @@ class Ark: ArkProtocol { } func start() { - if let multiplayerContext = multiplayerContext, multiplayerContext.role == .participant { - multiplayerParticipantStart() + if let multiplayerContext = multiplayerContext { + if multiplayerContext.role == .host { + multiplayerHostStart() + } else { + multiplayerParticipantStart() + } } else { defaultStart() } + alignCamera() guard let gameLoop = self.gameLoop else { @@ -94,13 +101,22 @@ class Ark: ArkProtocol { canvasRenderer: canvasRenderableBuilder) gameCoordinator.start() } - + + private func multiplayerHostStart() { + defaultStart() + guard let multiplayerManager = multiplayerManager else { + return + } + arkState.arkECS + .addSystem(ArkMultiplayerSystem(multiplayerManager: multiplayerManager)) + } + private func multiplayerParticipantStart() { setupDefaultListeners() setupMultiplayerGameLoop() setup(blueprint.soundMapping) } - + private func defaultStart() { setupDefaultEntities() setupDefaultListeners() @@ -237,9 +253,14 @@ class Ark: ArkProtocol { simulator.physicsScene?.sceneUpdateLoopDelegate = physicsSystem self.gameLoop?.updatePhysicsSceneDelegate = physicsSystem } - + func setupMultiplayerGameLoop() { - gameLoop = ArkMultiplayerGameLoop() + let gameLoop = ArkMultiplayerGameLoop() + self.gameLoop = gameLoop + guard let multiplayerManager = multiplayerManager else { + return + } + self.multiplayerManager?.arkMultiplayerECSDelegate = gameLoop } private func getWorldSize(_ blueprint: ArkBlueprint) -> (width: Double, height: Double) { diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift index 14575e6..14aab8e 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift @@ -1,12 +1,14 @@ class ArkMultiplayerGameLoop: GameLoop { var updatePhysicsSceneDelegate: (any ArkPhysicsSceneUpdateLoopDelegate)? - var updateGameWorldDelegate: (any ArkGameWorldUpdateLoopDelegate)? + weak var updateGameWorldDelegate: ArkGameWorldUpdateLoopDelegate? func setUp() { } func update() { + let deltaTime = self.getDeltaTime() + self.updateGameWorldDelegate?.update(for: deltaTime) } func getDeltaTime() -> Double { @@ -23,6 +25,8 @@ class ArkMultiplayerGameLoop: GameLoop { } } -protocol ArkMultiplayerECSDelegate { - +extension ArkMultiplayerGameLoop: ArkMultiplayerECSDelegate { + func ecsDidUpdate() { + self.update() + } } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index 6766fb0..70e4fc2 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -8,6 +8,7 @@ enum ArkPeerRole { class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { private var networkService: ArkNetworkProtocol var multiplayerEventManager: ArkMultiplayerEventManager? + var arkMultiplayerECSDelegate: ArkMultiplayerECSDelegate? var ecs: ArkECS private var peers = [String]() private var host: String? @@ -45,6 +46,7 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { if wrappedData.type == .ecs { let ecsWrapper = try ArkECSDataSerializer.decodeArkECS(from: wrappedData.payload) ecs.upsertEntityManager(entities: ecsWrapper.entities, components: ecsWrapper.decodeComponents()) + self.arkMultiplayerECSDelegate?.ecsDidUpdate() } } catch { @@ -68,6 +70,21 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { role = host == networkService.deviceID ? .host : .participant print("Updated role: \(role)") } + + var isModificationEnabled: Bool { + self.role == .host + } + + func sendECS() { + if isModificationEnabled { + do { + let encodedECS = try ArkECSDataSerializer.encodeArkECS(ecs: ecs) + networkService.sendData(data: encodedECS) + } catch { + print("Error encoding or sending ecs function: \(error)") + } + } + } } extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { @@ -91,18 +108,6 @@ extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { } } -extension ArkMultiplayerManager: ArkMultiplayerECSDelegate { - var isModificationEnabled: Bool { - self.role == .host - } - - private func sendEcs(ecs: ArkECS) { - - do { - let encodedECS = try ArkECSDataSerializer.encodeArkECS(ecs: ecs) - networkService.sendData(data: encodedECS) - } catch { - print("Error encoding or sending ecs function: \(error)") - } - } +protocol ArkMultiplayerECSDelegate: AnyObject { + func ecsDidUpdate() } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerSystem.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerSystem.swift new file mode 100644 index 0000000..2b2ea9a --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerSystem.swift @@ -0,0 +1,15 @@ +import Foundation + +class ArkMultiplayerSystem: UpdateSystem { + let multiplayerManager: ArkMultiplayerManager + var active: Bool + + init(multiplayerManager: ArkMultiplayerManager) { + self.multiplayerManager = multiplayerManager + self.active = true + } + + func update(deltaTime: TimeInterval, arkECS: ArkECS) { + multiplayerManager.sendECS() + } +} From efe17a7af7af91c99af55a1111bf7177ea2d2cdd Mon Sep 17 00:00:00 2001 From: Jeff Sieu Date: Mon, 15 Apr 2024 11:50:10 +0800 Subject: [PATCH 23/43] [ark-animation]: loop, repeat animations --- .../TankGame/ImpactExplosionAnimation.swift | 11 ++-- ArkKit/ark-animation-kit/ArkAnimation.swift | 51 ++++++++++++++++++- .../ArkAnimationInstance.swift | 13 +++-- .../ArkAnimationSystem.swift | 7 ++- .../ArkAnimationsComponent.swift | 12 ++++- 5 files changed, 82 insertions(+), 12 deletions(-) diff --git a/ArkGameExample/games/TankGame/ImpactExplosionAnimation.swift b/ArkGameExample/games/TankGame/ImpactExplosionAnimation.swift index 212c8c1..fd04831 100644 --- a/ArkGameExample/games/TankGame/ImpactExplosionAnimation.swift +++ b/ArkGameExample/games/TankGame/ImpactExplosionAnimation.swift @@ -52,12 +52,13 @@ struct ImpactExplosionAnimation { ecs.upsertComponent(bitmapComponent, to: entity) } .onComplete { instance in - instance.markForDestroyal() - ecs.removeEntity(entity) + if instance.shouldDestroy { + ecs.removeEntity(entity) + } } - let animationsComponent = ArkAnimationsComponent(animations: [ - animationInstance - ]) + + var animationsComponent = ArkAnimationsComponent() + animationsComponent.addAnimation(animationInstance) ecs.upsertComponent(animationsComponent, to: entity) let bitmapComponent = makeBitmapComponent(imageResourcePath: .Sprite_Effects_Explosion_001) diff --git a/ArkKit/ark-animation-kit/ArkAnimation.swift b/ArkKit/ark-animation-kit/ArkAnimation.swift index 568dd45..96ed7b1 100644 --- a/ArkKit/ark-animation-kit/ArkAnimation.swift +++ b/ArkKit/ark-animation-kit/ArkAnimation.swift @@ -8,9 +8,11 @@ struct AnimationKeyframe: Equatable { protocol Animation { associatedtype T: Equatable - + var keyframes: [AnimationKeyframe] { get } var duration: TimeInterval { get } + var isLooping: Bool { get } + var runCount: Int { get } } /** @@ -18,11 +20,18 @@ protocol Animation { */ struct ArkAnimation: Animation where T: Equatable { private (set) var keyframes: [AnimationKeyframe] + private (set) var isLooping: Bool + private (set) var runCount: Int init() { self.keyframes = [] + self.isLooping = false + self.runCount = 1 } + /** + * The total duration of the animation. + */ var duration: TimeInterval { if let lastFrame = keyframes.last { return lastFrame.offset + lastFrame.duration @@ -31,6 +40,9 @@ struct ArkAnimation: Animation where T: Equatable { return 0 } + /** + * Adds a keyframe to the animation with a given value and duration. + */ func keyframe(_ value: T, duration: Double) -> Self { let newOffset: Double = { if let previousFrame = keyframes.last { @@ -44,4 +56,41 @@ struct ArkAnimation: Animation where T: Equatable { newSelf.keyframes.append(AnimationKeyframe(value: value, offset: newOffset, duration: duration)) return newSelf } + + /** + * Sets the animation to run a given number of times. Should only be called after all keyframes are added. + */ + func `repeat`(times runCount: Int) -> Self { + assert(runCount > 0, "Repeat count must be greater than 0") + var newSelf = self + newSelf.runCount = runCount + return newSelf + } + + /** + * Repeats the animation indefinitely. + */ + func loop() -> Self { + var newSelf = self + newSelf.isLooping = true + return newSelf + } + + /** + * Sets whether the animation should loop indefinitely. + */ + func loop(_ value: Bool) -> Self { + var newSelf = self + newSelf.isLooping = value + return newSelf + } + + /** + * Returns a new animation with the keyframes in reverse order. + */ + func reverse() -> Self { + var newSelf = self + newSelf.keyframes.reverse() + return newSelf + } } diff --git a/ArkKit/ark-animation-kit/ArkAnimationInstance.swift b/ArkKit/ark-animation-kit/ArkAnimationInstance.swift index 858807d..1a9583a 100644 --- a/ArkKit/ark-animation-kit/ArkAnimationInstance.swift +++ b/ArkKit/ark-animation-kit/ArkAnimationInstance.swift @@ -21,7 +21,7 @@ protocol AnimationInstance: AnyObject where T: Equatable { } extension AnimationInstance { - func markForDestroyal() { + func stop() { shouldDestroy = true } @@ -36,6 +36,9 @@ extension AnimationInstance { if !wasComplete { if status == .complete { + if !animation.isLooping { + stop() + } completeDelegate?(self) } } @@ -58,7 +61,7 @@ class ArkAnimationInstance: AnimationInstance where T: Equatable { var shouldDestroy = false var status: AnimationStatus { - if elapsedDelta > animation.duration { + if elapsedDelta > animation.duration * Double(animation.runCount) && !animation.isLooping { return .complete } @@ -66,8 +69,10 @@ class ArkAnimationInstance: AnimationInstance where T: Equatable { } var currentFrame: AnimationKeyframe { - animation.keyframes.first(where: { keyframe in - elapsedDelta >= keyframe.offset && elapsedDelta < keyframe.offset + keyframe.duration + let resolvedDelta = elapsedDelta.truncatingRemainder(dividingBy: animation.duration) + + return animation.keyframes.first(where: { keyframe in + resolvedDelta >= keyframe.offset && resolvedDelta < keyframe.offset + keyframe.duration }) ?? animation.keyframes.last! } diff --git a/ArkKit/ark-animation-kit/ArkAnimationSystem.swift b/ArkKit/ark-animation-kit/ArkAnimationSystem.swift index 75963e7..4e0a946 100644 --- a/ArkKit/ark-animation-kit/ArkAnimationSystem.swift +++ b/ArkKit/ark-animation-kit/ArkAnimationSystem.swift @@ -14,13 +14,18 @@ class ArkAnimationSystem: UpdateSystem { let animationComponents = arkECS.getEntities(with: [ArkAnimationsComponent.self]) for entity in animationComponents { - guard let animationsComponent = arkECS.getComponent(ofType: ArkAnimationsComponent.self, for: entity) else { + guard var animationsComponent = arkECS.getComponent(ofType: ArkAnimationsComponent.self, for: entity) else { return } for animationInstance in animationsComponent.animations { animationInstance.advance(by: deltaTime) + if animationInstance.shouldDestroy { + animationsComponent.removeAnimation(animationInstance) + } } + + arkECS.upsertComponent(animationsComponent, to: entity) } } } diff --git a/ArkKit/ark-animation-kit/ArkAnimationsComponent.swift b/ArkKit/ark-animation-kit/ArkAnimationsComponent.swift index facf07c..26eac94 100644 --- a/ArkKit/ark-animation-kit/ArkAnimationsComponent.swift +++ b/ArkKit/ark-animation-kit/ArkAnimationsComponent.swift @@ -1,3 +1,13 @@ struct ArkAnimationsComponent: Component { - var animations: [any AnimationInstance] + var animations: [any AnimationInstance] = [] + + @discardableResult mutating func addAnimation(_ animation: any AnimationInstance) -> Self { + animations.append(animation) + return self + } + + @discardableResult mutating func removeAnimation(_ animation: any AnimationInstance) -> Self { + animations.removeAll { $0 === animation } + return self + } } From efa606b79738420dbd964a4bcd1cdb15b834f835 Mon Sep 17 00:00:00 2001 From: Jeff Sieu Date: Mon, 15 Apr 2024 12:10:13 +0800 Subject: [PATCH 24/43] [ark-animation]: play/pause an animation instance --- ArkKit/ark-animation-kit/ArkAnimationInstance.swift | 13 ++++++++++++- ArkKit/ark-animation-kit/ArkAnimationSystem.swift | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ArkKit/ark-animation-kit/ArkAnimationInstance.swift b/ArkKit/ark-animation-kit/ArkAnimationInstance.swift index 1a9583a..9027c40 100644 --- a/ArkKit/ark-animation-kit/ArkAnimationInstance.swift +++ b/ArkKit/ark-animation-kit/ArkAnimationInstance.swift @@ -18,9 +18,18 @@ protocol AnimationInstance: AnyObject where T: Equatable { var status: AnimationStatus { get } var shouldDestroy: Bool { get set } var currentFrame: AnimationKeyframe { get } + var isPlaying: Bool { get set } } extension AnimationInstance { + func play() { + isPlaying = true + } + + func pause() { + isPlaying = false + } + func stop() { shouldDestroy = true } @@ -53,6 +62,7 @@ extension AnimationInstance { * Represents a running animation instance as an ArkECS component. */ class ArkAnimationInstance: AnimationInstance where T: Equatable { + var isPlaying: Bool let animation: ArkAnimation var elapsedDelta: TimeInterval var updateDelegate: UpdateDelegate? @@ -76,9 +86,10 @@ class ArkAnimationInstance: AnimationInstance where T: Equatable { }) ?? animation.keyframes.last! } - init(animation: ArkAnimation, elapsedDelta: Double = 0) { + init(animation: ArkAnimation, elapsedDelta: Double = 0, isPlaying: Bool = true) { self.animation = animation self.elapsedDelta = elapsedDelta + self.isPlaying = isPlaying assert(!self.animation.keyframes.isEmpty, "Animation keyframes cannot be empty") } diff --git a/ArkKit/ark-animation-kit/ArkAnimationSystem.swift b/ArkKit/ark-animation-kit/ArkAnimationSystem.swift index 4e0a946..9e120f2 100644 --- a/ArkKit/ark-animation-kit/ArkAnimationSystem.swift +++ b/ArkKit/ark-animation-kit/ArkAnimationSystem.swift @@ -19,6 +19,10 @@ class ArkAnimationSystem: UpdateSystem { } for animationInstance in animationsComponent.animations { + if !animationInstance.isPlaying { + continue + } + animationInstance.advance(by: deltaTime) if animationInstance.shouldDestroy { animationsComponent.removeAnimation(animationInstance) From 9c9cc8d72e0dc2c83b6467310b7b67401f2b4dce Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 14:54:59 +0800 Subject: [PATCH 25/43] [ark-multiplayer] refactor: integrate ark and blueprint with ark network setup --- .../games/TankGame/TankGameManager.swift | 5 +- .../pages/ArkDemoGameHostingPage.swift | 16 +-- .../pages/ArkDemoMultiplayerPopover.swift | 26 ++--- .../utils/GameHostingPageFactory.swift | 23 +++-- ArkKit/Ark.swift | 99 +++++++++---------- ArkKit/ArkBlueprint.swift | 52 ++++++---- .../ArkMultiplayerManager.swift | 1 - 7 files changed, 115 insertions(+), 107 deletions(-) diff --git a/ArkGameExample/games/TankGame/TankGameManager.swift b/ArkGameExample/games/TankGame/TankGameManager.swift index 2e15d12..6589cc2 100644 --- a/ArkGameExample/games/TankGame/TankGameManager.swift +++ b/ArkGameExample/games/TankGame/TankGameManager.swift @@ -22,6 +22,9 @@ class TankGameManager { setUpEntities() setUpSystems() setUpRules() + self.blueprint = self.blueprint.supportNetworkPlay( + roomName: "TankFightGame", numberOfPlayers: 2 + ) } func setUpAudio() { @@ -121,8 +124,6 @@ class TankGameManager { } func setUpRules() { -// blueprint = blueprint.setupMultiplayer(serviceName: "tankGame") - blueprint = blueprint .on(ScreenResizeEvent.self) { event, context in self.handleScreenResize(event, in: context) diff --git a/ArkGameExample/pages/ArkDemoGameHostingPage.swift b/ArkGameExample/pages/ArkDemoGameHostingPage.swift index 3aa3cfb..876f497 100644 --- a/ArkGameExample/pages/ArkDemoGameHostingPage.swift +++ b/ArkGameExample/pages/ArkDemoGameHostingPage.swift @@ -3,29 +3,19 @@ import UIKit protocol AbstractDemoGameHostingPage: UIViewController { } class ArkDemoGameHostingPage: UIViewController { - // inject blueprint here - var arkBlueprint: ArkBlueprint? var ark: Ark? var shouldShowMultiplayerOptions = false override func viewDidLoad() { super.viewDidLoad() - guard let blueprint = arkBlueprint else { - return - } if shouldShowMultiplayerOptions { presentMultiplayerOptions() } + } - // Example on how to inject views into ark blueprint after win/ termination -// arkBlueprint = arkBlueprint?.on(TerminateGameLoopEvent.self) { event, context in -// // add pop-up view here -// self.present(<#T##viewControllerToPresent: UIViewController##UIViewController#>, animated: <#T##Bool#>) -// } - - // load blueprint - ark = Ark(rootView: self, blueprint: blueprint) + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) ark?.start() } diff --git a/ArkGameExample/pages/ArkDemoMultiplayerPopover.swift b/ArkGameExample/pages/ArkDemoMultiplayerPopover.swift index f6d2764..afecf40 100644 --- a/ArkGameExample/pages/ArkDemoMultiplayerPopover.swift +++ b/ArkGameExample/pages/ArkDemoMultiplayerPopover.swift @@ -14,33 +14,33 @@ class ArkDemoMultiplayerPopover: UIViewController { } private func setupButtons() { - let joinButton = UIButton(type: .system) - joinButton.setTitle("Start Multiplayer Game", for: .normal) - joinButton.translatesAutoresizingMaskIntoConstraints = false - let startButton = UIButton(type: .system) - startButton.setTitle("Join Multiplayer Game", for: .normal) + startButton.setTitle("Start Multiplayer Game", for: .normal) startButton.translatesAutoresizingMaskIntoConstraints = false + let joinButton = UIButton(type: .system) + joinButton.setTitle("Join Multiplayer Game", for: .normal) + joinButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(joinButton) view.addSubview(startButton) joinButton.addTarget(self, action: #selector(joinTapped), for: .touchUpInside) startButton.addTarget(self, action: #selector(startTapped), for: .touchUpInside) - NSLayoutConstraint.activate([ - joinButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), - joinButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -25), - joinButton.widthAnchor.constraint(equalToConstant: 200), - joinButton.heightAnchor.constraint(equalToConstant: 40) - ]) - NSLayoutConstraint.activate([ startButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), - startButton.topAnchor.constraint(equalTo: joinButton.bottomAnchor, constant: 20), + startButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -25), startButton.widthAnchor.constraint(equalToConstant: 200), startButton.heightAnchor.constraint(equalToConstant: 40) ]) + + NSLayoutConstraint.activate([ + joinButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + joinButton.topAnchor.constraint(equalTo: startButton.bottomAnchor, constant: 20), + joinButton.widthAnchor.constraint(equalToConstant: 200), + joinButton.heightAnchor.constraint(equalToConstant: 40) + ]) } @objc func joinTapped() { diff --git a/ArkGameExample/utils/GameHostingPageFactory.swift b/ArkGameExample/utils/GameHostingPageFactory.swift index da9ff9b..987728c 100644 --- a/ArkGameExample/utils/GameHostingPageFactory.swift +++ b/ArkGameExample/utils/GameHostingPageFactory.swift @@ -6,19 +6,30 @@ class GameHostingPageFactory { switch game { case .TankGame: let blueprint: ArkBlueprint = TankGameManager().blueprint - let vc: ArkDemoGameHostingPage = ArkDemoGameHostingPage() - vc.arkBlueprint = blueprint + + // inject ark and blueprint dependencies here + if role == nil { + vc.ark = Ark(rootView: vc, blueprint: blueprint) + } else { + guard let role = role else { + return vc + } + var updatedBlueprint = blueprint.setRole(role) + vc.ark = Ark(rootView: vc, blueprint: updatedBlueprint) + } return vc case .SnakeChomp: let blueprint: ArkBlueprint = SnakeGame().blueprint let vc: ArkDemoGameHostingPage = ArkDemoGameHostingPage() - vc.arkBlueprint = blueprint + let ark = Ark(rootView: vc, blueprint: blueprint) + vc.ark = ark return vc case .TankRaceGame: let blueprint: ArkBlueprint = TankRaceGame().blueprint let vc: ArkDemoGameHostingPage = ArkDemoGameHostingPage() - vc.arkBlueprint = blueprint + let ark = Ark(rootView: vc, blueprint: blueprint) + vc.ark = ark return vc } } @@ -46,9 +57,9 @@ class GameHostingPageFactory { private static func shouldPresentMultiplayerOptions(for game: DemoGames) -> Bool { switch game { - case .TankGame, .TankRaceGame: + case .TankGame: return true - case .SnakeChomp: + case .SnakeChomp, .TankRaceGame: return false } } diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index 0c3a85d..18744d4 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -18,8 +18,6 @@ class Ark: ArkProtocol { let blueprint: ArkBlueprint let audioContext: any AudioContext - var multiplayerContext: ArkMultiplayerContext? - var multiplayerManager: ArkMultiplayerManager? var displayContext: DisplayContext var actionContext: ArkActionContext { @@ -29,6 +27,8 @@ class Ark: ArkProtocol { audio: audioContext) } + var multiplayerManager: ArkMultiplayerManager? + var canvasRenderableBuilder: (any RenderableBuilder)? init(rootView: any AbstractRootView, @@ -36,9 +36,7 @@ class Ark: ArkProtocol { canvasRenderableBuilder: (any RenderableBuilder)? = nil) { self.rootView = rootView self.blueprint = blueprint - let eventManager = ArkEventManager() - let ecsManager = ArkECS() - self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) + self.audioContext = ArkAudioContext() self.canvasRenderableBuilder = canvasRenderableBuilder self.displayContext = ArkDisplayContext( @@ -48,45 +46,32 @@ class Ark: ArkProtocol { ), screenSize: rootView.size ) - } - init(rootView: any AbstractRootView, - blueprint: ArkBlueprint, - multiplayerContext: ArkMultiplayerContext, - canvasRenderableBuilder: (any RenderableBuilder)? = nil) { - self.rootView = rootView - self.blueprint = blueprint - let ecsManager = ArkECS() + // inject state management dependencies + guard let networkPlayableInfo = blueprint.networkPlayableInfo else { + let eventManager = ArkEventManager() + let ecsManager = ArkECS() + self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) + return + } let eventManager = ArkMultiplayerEventManager() - let multiplayerManager = ArkMultiplayerManager(serviceName: multiplayerContext.serviceName, - role: multiplayerContext.role, - ecs: ecsManager) - multiplayerManager.multiplayerEventManager = eventManager - self.multiplayerManager = multiplayerManager - eventManager.delegate = multiplayerManager + let ecsManager = ArkECS() self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) - self.audioContext = ArkAudioContext() - self.canvasRenderableBuilder = canvasRenderableBuilder - self.displayContext = ArkDisplayContext( - canvasSize: CGSize( - width: blueprint.frameWidth, - height: blueprint.frameHeight - ), - screenSize: rootView.size + self.multiplayerManager = ArkMultiplayerManager( + serviceName: networkPlayableInfo.roomName, + role: networkPlayableInfo.role ?? .host, // default to host so if unspecified, plays as local + ecs: ecsManager ) - self.multiplayerContext = multiplayerContext + multiplayerManager?.multiplayerEventManager = eventManager + eventManager.delegate = multiplayerManager } func start() { - if let multiplayerContext = multiplayerContext { - if multiplayerContext.role == .host { - multiplayerHostStart() - } else { - multiplayerParticipantStart() - } - } else { - defaultStart() - } + // TODO: refactor to use strategy design pattern here + // use SetUpStrategy.execute() to set up based on status + setUpIfNotParticipant() + setUpIfParticipant() + setUpIfHost() alignCamera() @@ -102,29 +87,37 @@ class Ark: ArkProtocol { gameCoordinator.start() } - private func multiplayerHostStart() { - defaultStart() - guard let multiplayerManager = multiplayerManager else { + private func setUpIfNotParticipant() { + guard multiplayerManager?.role != .participant else { return } - arkState.arkECS - .addSystem(ArkMultiplayerSystem(multiplayerManager: multiplayerManager)) + setupDefaultEntities() + setupDefaultListeners() + setupDefaultSystems(blueprint) + setup(blueprint.setupFunctions) + setup(blueprint.rules) + setup(blueprint.soundMapping) } - private func multiplayerParticipantStart() { + private func setUpIfParticipant() { + guard let multiplayerManager = multiplayerManager, + multiplayerManager.role == .participant else { + return + } setupDefaultListeners() setupMultiplayerGameLoop() setup(blueprint.soundMapping) } - private func defaultStart() { - setupDefaultEntities() - setupDefaultListeners() - setupDefaultSystems(blueprint) - setup(blueprint.setupFunctions) - setup(blueprint.setupFunctions) - setup(blueprint.rules) - setup(blueprint.soundMapping) + private func setUpIfHost() { + guard let multiplayerManager = multiplayerManager, + multiplayerManager.role == .host else { + return + } + multiplayerManager.ecs = arkState.arkECS + self.arkState + .arkECS + .addSystem(ArkMultiplayerSystem(multiplayerManager: multiplayerManager)) } private func setupDefaultListeners() { @@ -255,11 +248,11 @@ class Ark: ArkProtocol { } func setupMultiplayerGameLoop() { - let gameLoop = ArkMultiplayerGameLoop() - self.gameLoop = gameLoop guard let multiplayerManager = multiplayerManager else { return } + let gameLoop = ArkMultiplayerGameLoop() + self.gameLoop = gameLoop self.multiplayerManager?.arkMultiplayerECSDelegate = gameLoop } diff --git a/ArkKit/ArkBlueprint.swift b/ArkKit/ArkBlueprint.swift index bbd801f..6ba1e63 100644 --- a/ArkKit/ArkBlueprint.swift +++ b/ArkKit/ArkBlueprint.swift @@ -7,6 +7,7 @@ struct ArkBlueprint { private(set) var rules: [any Rule] = [] private(set) var setupFunctions: [ArkStateSetupDelegate] = [] private(set) var soundMapping: [ExternalResources.AudioEnum: any Sound]? + private(set) var networkPlayableInfo: ArkNetworkPlayableInfo? // game world size private(set) var frameWidth: Double @@ -80,23 +81,36 @@ struct ArkBlueprint { return newSelf } -// func setupMultiplayer(serviceName: String = "Ark") -> Self { -// let fn: ArkStateSetupDelegate = { context in -// var events = context.events -// -// let multiplayerManager = ArkMultiplayerManager(serviceName: serviceName) -// let multiplayerEventManager = ArkMultiplayerEventManager(arkEventManager: events, -// delegate: multiplayerManager) -// multiplayerManager.multiplayerEventManager = multiplayerEventManager -// -// events.delegate = multiplayerEventManager -// } -// -// var stateSetupFunctionsCopy = setupFunctions -// stateSetupFunctionsCopy.insert(fn, at: 0) -// -// var newSelf = self -// newSelf.setupFunctions = stateSetupFunctionsCopy -// return newSelf -// } + func supportNetworkPlay(roomName: String, numberOfPlayers: Int) -> Self { + var newSelf = self + newSelf.networkPlayableInfo = ArkNetworkPlayableInfo( + roomName: roomName, numberOfPlayers: numberOfPlayers + ) + return newSelf + } + + func setRole(_ role: ArkPeerRole) -> Self { + var newSelf = self + guard let originalNetworkInfo = self.networkPlayableInfo else { + return newSelf + } + newSelf.networkPlayableInfo = ArkNetworkPlayableInfo( + roomName: originalNetworkInfo.roomName, + numberOfPlayers: originalNetworkInfo.numberOfPlayers, + role: role + ) + return newSelf + } +} + +struct ArkNetworkPlayableInfo { + let roomName: String + let numberOfPlayers: Int + let role: ArkPeerRole? + + init(roomName: String, numberOfPlayers: Int, role: ArkPeerRole? = nil) { + self.roomName = roomName + self.numberOfPlayers = numberOfPlayers + self.role = role + } } diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index 70e4fc2..cd21384 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -60,7 +60,6 @@ class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { func connectedDevicesChanged(manager: ArkNetworkService, connectedDevices: [String]) { peers = connectedDevices - updateRoles() } private func updateRoles() { From 1d79e2fb82b8dc0568fb1eedad0e7df430082f47 Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 16:50:41 +0800 Subject: [PATCH 26/43] [ark-multiplayer] refactor: remove multiplayer manager, introduce publisher and subscribers --- ArkKit.xcodeproj/project.pbxproj | 64 ++++++++--- ArkKit/Ark.swift | 66 ++++++----- ArkKit/ark-event-kit/ArkEventManager.swift | 3 + ArkKit/ark-loop-kit/GameLoop.swift | 2 +- ...col.swift => AbstractNetworkService.swift} | 6 +- .../ark-multiplayer-kit/ArkHostSystem.swift | 15 +++ .../ArkMultiplayerContext.swift | 7 -- .../ArkMultiplayerEventManager.swift | 36 ------ .../ArkMultiplayerGameLoop.swift | 6 - .../ArkMultiplayerManager.swift | 106 ------------------ .../ArkMultiplayerSystem.swift | 15 --- .../ArkNetworkService.swift | 21 ++-- .../publisher/ArkHostNetworkPublisher.swift | 29 +++++ .../ArkNetworkPublisherDelegate.swift | 5 + .../ArkParticipantNetworkPublisher.swift | 29 +++++ .../subscriber/ArkHostNetworkSubscriber.swift | 35 ++++++ .../ArkNetworkSubscriberDelegate.swift | 5 + .../ArkParticipantNetworkSubscriber.swift | 35 ++++++ 18 files changed, 251 insertions(+), 234 deletions(-) rename ArkKit/ark-multiplayer-kit/{ArkNetworkProtocol.swift => AbstractNetworkService.swift} (56%) create mode 100644 ArkKit/ark-multiplayer-kit/ArkHostSystem.swift delete mode 100644 ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift delete mode 100644 ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift delete mode 100644 ArkKit/ark-multiplayer-kit/ArkMultiplayerSystem.swift create mode 100644 ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift create mode 100644 ArkKit/ark-multiplayer-kit/publisher/ArkNetworkPublisherDelegate.swift create mode 100644 ArkKit/ark-multiplayer-kit/publisher/ArkParticipantNetworkPublisher.swift create mode 100644 ArkKit/ark-multiplayer-kit/subscriber/ArkHostNetworkSubscriber.swift create mode 100644 ArkKit/ark-multiplayer-kit/subscriber/ArkNetworkSubscriberDelegate.swift create mode 100644 ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index cd5ad5b..2e4c79a 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -41,6 +41,12 @@ 02B3C6132BCAF33C002331A0 /* MockExternalResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6122BCAF33C002331A0 /* MockExternalResource.swift */; }; 02B3C6192BCAF69E002331A0 /* CollisionHandlingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6182BCAF69E002331A0 /* CollisionHandlingStrategy.swift */; }; 02B3C61B2BCAF6B3002331A0 /* CollisionStrategyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C61A2BCAF6B3002331A0 /* CollisionStrategyManager.swift */; }; + 02B3C62A2BCD198E002331A0 /* ArkHostNetworkPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6292BCD198E002331A0 /* ArkHostNetworkPublisher.swift */; }; + 02B3C62C2BCD19AC002331A0 /* ArkNetworkPublisherDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C62B2BCD19AC002331A0 /* ArkNetworkPublisherDelegate.swift */; }; + 02B3C62F2BCD19CC002331A0 /* ArkParticipantNetworkSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C62E2BCD19CC002331A0 /* ArkParticipantNetworkSubscriber.swift */; }; + 02B3C6312BCD19ED002331A0 /* ArkNetworkSubscriberDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6302BCD19ED002331A0 /* ArkNetworkSubscriberDelegate.swift */; }; + 02B3C6332BCD1EA4002331A0 /* ArkHostNetworkSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6322BCD1EA4002331A0 /* ArkHostNetworkSubscriber.swift */; }; + 02B3C6352BCD1EE8002331A0 /* ArkParticipantNetworkPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6342BCD1EE8002331A0 /* ArkParticipantNetworkPublisher.swift */; }; 02C394DC2BA402060075F1CA /* GameStateRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DB2BA402060075F1CA /* GameStateRenderer.swift */; }; 02C394DE2BA4053E0075F1CA /* RenderableBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DD2BA4053E0075F1CA /* RenderableBuilder.swift */; }; 02C394E02BA405C10075F1CA /* Canvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DF2BA405C10075F1CA /* Canvas.swift */; }; @@ -165,7 +171,7 @@ 28EFCB1D2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */; }; 28EFCB252BCCA7CC0059A908 /* ArkECSWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */; }; 28EFCB272BCCB6130059A908 /* ArkMultiplayerGameLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */; }; - 28EFCB292BCCC90B0059A908 /* ArkMultiplayerSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB282BCCC90B0059A908 /* ArkMultiplayerSystem.swift */; }; + 28EFCB292BCCC90B0059A908 /* ArkHostSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB282BCCC90B0059A908 /* ArkHostSystem.swift */; }; 8F5573BF2BA425E4007030C8 /* ShapeRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */; }; 8F5573C12BA42614007030C8 /* Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C02BA42614007030C8 /* Renderable.swift */; }; 8F5573C32BA4284D007030C8 /* BitmapRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */; }; @@ -221,7 +227,6 @@ 94D053A32BC80E8C000280C6 /* ArkImageEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D053A22BC80E8C000280C6 /* ArkImageEnum.swift */; }; AD260CA82BBFCC7E008B9654 /* EntityIDGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */; }; AD2B59AC2BB87F2200198E99 /* ArkNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */; }; - AD2B59B02BB94DE000198E99 /* ArkMultiplayerEventManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */; }; AD2B59B22BB94FBE00198E99 /* ArkMultiplayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */; }; AD2B59B62BB958E400198E99 /* DataWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59B52BB958E400198E99 /* DataWrapper.swift */; }; AD2B59B82BB9672100198E99 /* ArkEventRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59B72BB9672100198E99 /* ArkEventRegistry.swift */; }; @@ -232,7 +237,6 @@ AD3ECF432BAF3148002C758D /* ArkEventKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3ECF422BAF3148002C758D /* ArkEventKitTests.swift */; }; AD3ECF472BB01ABC002C758D /* SystemManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3ECF462BB01ABC002C758D /* SystemManagerTests.swift */; }; AD3ECF492BB01CF2002C758D /* ArkECSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3ECF482BB01CF2002C758D /* ArkECSTests.swift */; }; - AD4E14902BC2797000A32C8B /* ArkMultiplayerContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD4E148F2BC2797000A32C8B /* ArkMultiplayerContext.swift */; }; AD5F69752BAC879400A5518D /* TankShootEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD5F69742BAC879400A5518D /* TankShootEvent.swift */; }; AD6E03612B9F15FA00974EBF /* ArkECS.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6E03602B9F15FA00974EBF /* ArkECS.swift */; }; AD6E03652BA1949000974EBF /* ArkEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6E03642BA1949000974EBF /* ArkEvent.swift */; }; @@ -255,7 +259,7 @@ AD787A882B9C6D3A003EBBD0 /* EntityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD787A872B9C6D3A003EBBD0 /* EntityManager.swift */; }; AD787A8A2B9C70D8003EBBD0 /* ArkState.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD787A892B9C70D8003EBBD0 /* ArkState.swift */; }; AD787A8C2B9C70FC003EBBD0 /* SystemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD787A8B2B9C70FC003EBBD0 /* SystemManager.swift */; }; - ADA847FD2BBC4B5500B19378 /* ArkNetworkProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */; }; + ADA847FD2BBC4B5500B19378 /* AbstractNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FC2BBC4B5500B19378 /* AbstractNetworkService.swift */; }; ADA847FF2BBC4EA800B19378 /* ArkEventDataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA847FE2BBC4EA800B19378 /* ArkEventDataSerializer.swift */; }; ADA848022BBC7DF000B19378 /* ArkSerializableEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */; }; ADD74C0C2BC05677008CE36B /* ArkECSDataSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD74C0B2BC05677008CE36B /* ArkECSDataSerializer.swift */; }; @@ -309,6 +313,12 @@ 02B3C6122BCAF33C002331A0 /* MockExternalResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockExternalResource.swift; sourceTree = ""; }; 02B3C6182BCAF69E002331A0 /* CollisionHandlingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollisionHandlingStrategy.swift; sourceTree = ""; }; 02B3C61A2BCAF6B3002331A0 /* CollisionStrategyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollisionStrategyManager.swift; sourceTree = ""; }; + 02B3C6292BCD198E002331A0 /* ArkHostNetworkPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkHostNetworkPublisher.swift; sourceTree = ""; }; + 02B3C62B2BCD19AC002331A0 /* ArkNetworkPublisherDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkPublisherDelegate.swift; sourceTree = ""; }; + 02B3C62E2BCD19CC002331A0 /* ArkParticipantNetworkSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkParticipantNetworkSubscriber.swift; sourceTree = ""; }; + 02B3C6302BCD19ED002331A0 /* ArkNetworkSubscriberDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkSubscriberDelegate.swift; sourceTree = ""; }; + 02B3C6322BCD1EA4002331A0 /* ArkHostNetworkSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkHostNetworkSubscriber.swift; sourceTree = ""; }; + 02B3C6342BCD1EE8002331A0 /* ArkParticipantNetworkPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkParticipantNetworkPublisher.swift; sourceTree = ""; }; 02C394DB2BA402060075F1CA /* GameStateRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStateRenderer.swift; sourceTree = ""; }; 02C394DD2BA4053E0075F1CA /* RenderableBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderableBuilder.swift; sourceTree = ""; }; 02C394DF2BA405C10075F1CA /* Canvas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Canvas.swift; sourceTree = ""; }; @@ -432,7 +442,7 @@ 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkDemoMultiplayerPopover.swift; sourceTree = ""; }; 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSWrapper.swift; sourceTree = ""; }; 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerGameLoop.swift; sourceTree = ""; }; - 28EFCB282BCCC90B0059A908 /* ArkMultiplayerSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerSystem.swift; sourceTree = ""; }; + 28EFCB282BCCC90B0059A908 /* ArkHostSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkHostSystem.swift; sourceTree = ""; }; 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeRenderable.swift; sourceTree = ""; }; 8F5573C02BA42614007030C8 /* Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderable.swift; sourceTree = ""; }; 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitmapRenderable.swift; sourceTree = ""; }; @@ -487,7 +497,6 @@ 94D053A22BC80E8C000280C6 /* ArkImageEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkImageEnum.swift; sourceTree = ""; }; AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityIDGenerator.swift; sourceTree = ""; }; AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkService.swift; sourceTree = ""; }; - AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerEventManager.swift; sourceTree = ""; }; AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerManager.swift; sourceTree = ""; }; AD2B59B52BB958E400198E99 /* DataWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataWrapper.swift; sourceTree = ""; }; AD2B59B72BB9672100198E99 /* ArkEventRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkEventRegistry.swift; sourceTree = ""; }; @@ -498,7 +507,6 @@ AD3ECF422BAF3148002C758D /* ArkEventKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkEventKitTests.swift; sourceTree = ""; }; AD3ECF462BB01ABC002C758D /* SystemManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemManagerTests.swift; sourceTree = ""; }; AD3ECF482BB01CF2002C758D /* ArkECSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSTests.swift; sourceTree = ""; }; - AD4E148F2BC2797000A32C8B /* ArkMultiplayerContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerContext.swift; sourceTree = ""; }; AD5F69742BAC879400A5518D /* TankShootEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankShootEvent.swift; sourceTree = ""; }; AD6E03602B9F15FA00974EBF /* ArkECS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECS.swift; sourceTree = ""; }; AD6E03642BA1949000974EBF /* ArkEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkEvent.swift; sourceTree = ""; }; @@ -524,7 +532,7 @@ AD787A872B9C6D3A003EBBD0 /* EntityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityManager.swift; sourceTree = ""; }; AD787A892B9C70D8003EBBD0 /* ArkState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkState.swift; sourceTree = ""; }; AD787A8B2B9C70FC003EBBD0 /* SystemManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemManager.swift; sourceTree = ""; }; - ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkProtocol.swift; sourceTree = ""; }; + ADA847FC2BBC4B5500B19378 /* AbstractNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractNetworkService.swift; sourceTree = ""; }; ADA847FE2BBC4EA800B19378 /* ArkEventDataSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkEventDataSerializer.swift; sourceTree = ""; }; ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSerializableEvent.swift; sourceTree = ""; }; ADD74C0B2BC05677008CE36B /* ArkECSDataSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSDataSerializer.swift; sourceTree = ""; }; @@ -693,6 +701,26 @@ path = CollisionHandling; sourceTree = ""; }; + 02B3C6282BCD1977002331A0 /* publisher */ = { + isa = PBXGroup; + children = ( + 02B3C6292BCD198E002331A0 /* ArkHostNetworkPublisher.swift */, + 02B3C62B2BCD19AC002331A0 /* ArkNetworkPublisherDelegate.swift */, + 02B3C6342BCD1EE8002331A0 /* ArkParticipantNetworkPublisher.swift */, + ); + path = publisher; + sourceTree = ""; + }; + 02B3C62D2BCD19BE002331A0 /* subscriber */ = { + isa = PBXGroup; + children = ( + 02B3C62E2BCD19CC002331A0 /* ArkParticipantNetworkSubscriber.swift */, + 02B3C6302BCD19ED002331A0 /* ArkNetworkSubscriberDelegate.swift */, + 02B3C6322BCD1EA4002331A0 /* ArkHostNetworkSubscriber.swift */, + ); + path = subscriber; + sourceTree = ""; + }; 02C394DA2BA401F00075F1CA /* ark-render-kit */ = { isa = PBXGroup; children = ( @@ -1382,15 +1410,15 @@ AD2B59AA2BB87F0F00198E99 /* ark-multiplayer-kit */ = { isa = PBXGroup; children = ( + 02B3C62D2BCD19BE002331A0 /* subscriber */, + 02B3C6282BCD1977002331A0 /* publisher */, ADD74C0A2BC0555D008CE36B /* sendable */, ADA848002BBC7DC600B19378 /* data-serialize */, AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */, 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */, - AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */, AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */, - ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */, - AD4E148F2BC2797000A32C8B /* ArkMultiplayerContext.swift */, - 28EFCB282BCCC90B0059A908 /* ArkMultiplayerSystem.swift */, + ADA847FC2BBC4B5500B19378 /* AbstractNetworkService.swift */, + 28EFCB282BCCC90B0059A908 /* ArkHostSystem.swift */, ); path = "ark-multiplayer-kit"; sourceTree = ""; @@ -1779,7 +1807,7 @@ AD2B59B22BB94FBE00198E99 /* ArkMultiplayerManager.swift in Sources */, 287B00552BC16E6C002F0114 /* TankHPComponent.swift in Sources */, 02C3950F2BA611B80075F1CA /* ArkEventContext.swift in Sources */, - 28EFCB292BCCC90B0059A908 /* ArkMultiplayerSystem.swift in Sources */, + 28EFCB292BCCC90B0059A908 /* ArkHostSystem.swift in Sources */, 8FEB217A2BADE60300788E20 /* DisplayContext.swift in Sources */, 022C427D2BC9943B003D6924 /* ResumeGameLoopEvent.swift in Sources */, 28A032DC2BAD4E8200851BFF /* StopWatchComponent.swift in Sources */, @@ -1796,6 +1824,7 @@ 02B3C6192BCAF69E002331A0 /* CollisionHandlingStrategy.swift in Sources */, 280CD3B92BA7391700372C5D /* PhysicsComponent.swift in Sources */, 02B3C60B2BCAEF9B002331A0 /* DemoGames.swift in Sources */, + 02B3C6352BCD1EE8002331A0 /* ArkParticipantNetworkPublisher.swift in Sources */, 022C427F2BC9944A003D6924 /* TerminateGameLoopEvent.swift in Sources */, 02E0E8F62BA283940043E2BA /* UIKitPolygon.swift in Sources */, 280CD3DE2BA8045200372C5D /* RotationComponent.swift in Sources */, @@ -1810,7 +1839,7 @@ 2812FCC32BC4395D00A0FE24 /* TankRaceGameCollisionStrategyManager.swift in Sources */, 2812FCC52BC4424200A0FE24 /* TankRaceGameEntityCreator.swift in Sources */, 945441042BC9178400E90ECE /* SnakeGame.swift in Sources */, - ADA847FD2BBC4B5500B19378 /* ArkNetworkProtocol.swift in Sources */, + ADA847FD2BBC4B5500B19378 /* AbstractNetworkService.swift in Sources */, 02C3951E2BA849490075F1CA /* CircleRenderableComponent.swift in Sources */, 9479A3AF2BA952E300F99013 /* AbstractShape.swift in Sources */, 02E0E8EE2BA280BD0043E2BA /* UIKitShape.swift in Sources */, @@ -1861,7 +1890,6 @@ 02C9CC2F2BBED04E0098E849 /* CameraContainerRenderableComponent.swift in Sources */, 02C395182BA83FC40075F1CA /* ButtonRenderableComponent.swift in Sources */, AD6E03652BA1949000974EBF /* ArkEvent.swift in Sources */, - AD4E14902BC2797000A32C8B /* ArkMultiplayerContext.swift in Sources */, 943D418C2BAEBF2E00F9E88F /* ArkSetupContext.swift in Sources */, ADA847FF2BBC4EA800B19378 /* ArkEventDataSerializer.swift in Sources */, AD36A7582BAC3223003E938B /* TankGameEntityCreator.swift in Sources */, @@ -1873,6 +1901,7 @@ 02C395152BA83FAE0075F1CA /* RenderableComponent.swift in Sources */, AD787A8A2B9C70D8003EBBD0 /* ArkState.swift in Sources */, ADD74C122BC15C52008CE36B /* ComponentRegistry.swift in Sources */, + 02B3C62F2BCD19CC002331A0 /* ArkParticipantNetworkSubscriber.swift in Sources */, 28D006572BAF116A001B4BD4 /* TankGameCollisionStrategyManager.swift in Sources */, 945441132BC921FC00E90ECE /* SnakeGrid.swift in Sources */, 94D0539E2BC7F32E000280C6 /* TankRaceGameExternalResources.swift in Sources */, @@ -1880,9 +1909,11 @@ 945441232BC98DB100E90ECE /* SnakeGameApple.swift in Sources */, 0267BA312BBD40C20010F729 /* CameraContext.swift in Sources */, 8FEB218A2BAF3C8E00788E20 /* ImpactExplosionAnimation.swift in Sources */, + 02B3C62A2BCD198E002331A0 /* ArkHostNetworkPublisher.swift in Sources */, AD5F69752BAC879400A5518D /* TankShootEvent.swift in Sources */, 945441082BC917D600E90ECE /* SnakeGameImages.swift in Sources */, 02C3952C2BA897890075F1CA /* ArkFlatCanvas.swift in Sources */, + 02B3C6332BCD1EA4002331A0 /* ArkHostNetworkSubscriber.swift in Sources */, 02C394DC2BA402060075F1CA /* GameStateRenderer.swift in Sources */, 28A032E72BAD783D00851BFF /* SKSimulator.swift in Sources */, 2882ABFB2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift in Sources */, @@ -1917,12 +1948,13 @@ 02C394DE2BA4053E0075F1CA /* RenderableBuilder.swift in Sources */, AD6E036F2BA21A5B00974EBF /* Queue.swift in Sources */, AD2B59B62BB958E400198E99 /* DataWrapper.swift in Sources */, + 02B3C62C2BCD19AC002331A0 /* ArkNetworkPublisherDelegate.swift in Sources */, 0230A48E2BB950AF001CFDAF /* OrderedDictionary.swift in Sources */, 9479A3AC2BA951F300F99013 /* AbstractBitmap.swift in Sources */, - AD2B59B02BB94DE000198E99 /* ArkMultiplayerEventManager.swift in Sources */, 945F7F9B2BA8912200933629 /* AbstractPannable.swift in Sources */, AD6E03682BA1A61200974EBF /* ArkEventManager.swift in Sources */, 02C394FB2BA444360075F1CA /* PanRenderable.swift in Sources */, + 02B3C6312BCD19ED002331A0 /* ArkNetworkSubscriberDelegate.swift in Sources */, 9454410A2BC917F700E90ECE /* SnakeGameExternalResources.swift in Sources */, AD787A862B9C6C91003EBBD0 /* System.swift in Sources */, 8F5573C32BA4284D007030C8 /* BitmapRenderable.swift in Sources */, diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index 18744d4..bd7b3ef 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -27,16 +27,22 @@ class Ark: ArkProtocol { audio: audioContext) } - var multiplayerManager: ArkMultiplayerManager? - var canvasRenderableBuilder: (any RenderableBuilder)? + // network dependencies + var participantSubscriber: ArkParticipantNetworkSubscriber? + var hostSubscriber: ArkHostNetworkSubscriber? + init(rootView: any AbstractRootView, blueprint: ArkBlueprint, canvasRenderableBuilder: (any RenderableBuilder)? = nil) { self.rootView = rootView self.blueprint = blueprint + let eventManager = ArkEventManager() + let ecsManager = ArkECS() + self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) + self.audioContext = ArkAudioContext() self.canvasRenderableBuilder = canvasRenderableBuilder self.displayContext = ArkDisplayContext( @@ -46,24 +52,6 @@ class Ark: ArkProtocol { ), screenSize: rootView.size ) - - // inject state management dependencies - guard let networkPlayableInfo = blueprint.networkPlayableInfo else { - let eventManager = ArkEventManager() - let ecsManager = ArkECS() - self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) - return - } - let eventManager = ArkMultiplayerEventManager() - let ecsManager = ArkECS() - self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) - self.multiplayerManager = ArkMultiplayerManager( - serviceName: networkPlayableInfo.roomName, - role: networkPlayableInfo.role ?? .host, // default to host so if unspecified, plays as local - ecs: ecsManager - ) - multiplayerManager?.multiplayerEventManager = eventManager - eventManager.delegate = multiplayerManager } func start() { @@ -88,7 +76,7 @@ class Ark: ArkProtocol { } private func setUpIfNotParticipant() { - guard multiplayerManager?.role != .participant else { + guard blueprint.networkPlayableInfo?.role != .participant else { return } setupDefaultEntities() @@ -100,24 +88,34 @@ class Ark: ArkProtocol { } private func setUpIfParticipant() { - guard let multiplayerManager = multiplayerManager, - multiplayerManager.role == .participant else { + guard let networkPlayableInfo = blueprint.networkPlayableInfo, + networkPlayableInfo.role == .participant else { return } setupDefaultListeners() setupMultiplayerGameLoop() + + let networkService = ArkNetworkService(serviceName: networkPlayableInfo.roomName) + self.participantSubscriber = ArkParticipantNetworkSubscriber(subscribeTo: networkService) + self.participantSubscriber?.localState = self.arkState + self.participantSubscriber?.localGameLoop = self.gameLoop + + let participantPublisher = ArkParticipantNetworkPublisher(publishTo: networkService) + self.arkState.eventManager.networkPublisherDelegate = participantPublisher setup(blueprint.soundMapping) } private func setUpIfHost() { - guard let multiplayerManager = multiplayerManager, - multiplayerManager.role == .host else { + guard let networkPlayableInfo = blueprint.networkPlayableInfo, + networkPlayableInfo.role == .host else { return } - multiplayerManager.ecs = arkState.arkECS - self.arkState - .arkECS - .addSystem(ArkMultiplayerSystem(multiplayerManager: multiplayerManager)) + let networkService = ArkNetworkService(serviceName: networkPlayableInfo.roomName) + let publisher = ArkHostNetworkPublisher(publishTo: networkService) + self.arkState.arkECS.addSystem(ArkHostSystem(publisher: publisher)) + + self.hostSubscriber = ArkHostNetworkSubscriber(subscribeTo: networkService) + self.hostSubscriber?.localState = self.arkState } private func setupDefaultListeners() { @@ -130,7 +128,7 @@ class Ark: ArkProtocol { } arkState.eventManager.subscribe(to: PauseGameLoopEvent.self) { [weak self] event in - guard let pauseGameLoopEvent = event as? PauseGameLoopEvent, + guard let _ = event as? PauseGameLoopEvent, let self = self else { return } @@ -138,7 +136,7 @@ class Ark: ArkProtocol { } arkState.eventManager.subscribe(to: ResumeGameLoopEvent.self) { [weak self] event in - guard let resumeGameLoopEvent = event as? ResumeGameLoopEvent, + guard let _ = event as? ResumeGameLoopEvent, let self = self else { return } @@ -147,7 +145,7 @@ class Ark: ArkProtocol { } arkState.eventManager.subscribe(to: TerminateGameLoopEvent.self) { [weak self] event in - guard let terminateGameEvent = event as? TerminateGameLoopEvent, + guard let _ = event as? TerminateGameLoopEvent, let self = self else { return } @@ -248,12 +246,12 @@ class Ark: ArkProtocol { } func setupMultiplayerGameLoop() { - guard let multiplayerManager = multiplayerManager else { + guard let networkPlayableInfo = blueprint.networkPlayableInfo, + networkPlayableInfo.role == .participant else { return } let gameLoop = ArkMultiplayerGameLoop() self.gameLoop = gameLoop - self.multiplayerManager?.arkMultiplayerECSDelegate = gameLoop } private func getWorldSize(_ blueprint: ArkBlueprint) -> (width: Double, height: Double) { diff --git a/ArkKit/ark-event-kit/ArkEventManager.swift b/ArkKit/ark-event-kit/ArkEventManager.swift index 872dd6c..9129bb7 100644 --- a/ArkKit/ark-event-kit/ArkEventManager.swift +++ b/ArkKit/ark-event-kit/ArkEventManager.swift @@ -16,6 +16,8 @@ class ArkEventManager: ArkEventContext { private var listeners: [ObjectIdentifier: [(any ArkEvent) -> Void]] = [:] private var eventQueue = PriorityQueue(sort: ArkEventManager.compareEventPriority) + var networkPublisherDelegate: ArkNetworkPublisherDelegate? + func subscribe(to eventType: Event.Type, _ listener: @escaping (any ArkEvent) -> Void) { let typeID = ObjectIdentifier(eventType) if listeners[typeID] == nil { @@ -29,6 +31,7 @@ class ArkEventManager: ArkEventContext { func emit(_ event: Event) { let datedEvent = DatedEvent(event: event) eventQueue.enqueue(datedEvent) + networkPublisherDelegate?.publish(event: event) } func emitWithoutDelegate(_ event: Event) { diff --git a/ArkKit/ark-loop-kit/GameLoop.swift b/ArkKit/ark-loop-kit/GameLoop.swift index 4ec04ec..bde0593 100644 --- a/ArkKit/ark-loop-kit/GameLoop.swift +++ b/ArkKit/ark-loop-kit/GameLoop.swift @@ -1,4 +1,4 @@ -protocol GameLoop { +protocol GameLoop: AnyObject { var updatePhysicsSceneDelegate: ArkPhysicsSceneUpdateLoopDelegate? { get set } var updateGameWorldDelegate: ArkGameWorldUpdateLoopDelegate? { get set } func setUp() diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift b/ArkKit/ark-multiplayer-kit/AbstractNetworkService.swift similarity index 56% rename from ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift rename to ArkKit/ark-multiplayer-kit/AbstractNetworkService.swift index 140a728..8ba34da 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift +++ b/ArkKit/ark-multiplayer-kit/AbstractNetworkService.swift @@ -1,7 +1,9 @@ import Foundation -protocol ArkNetworkProtocol { - var delegate: ArkNetworkDelegate? { get set } +protocol AbstractNetworkService { + var subscriber: ArkNetworkSubscriberDelegate? { get set } + var publisher: ArkNetworkPublisherDelegate? { get set } + var deviceID: String { get } var serviceName: String { get } diff --git a/ArkKit/ark-multiplayer-kit/ArkHostSystem.swift b/ArkKit/ark-multiplayer-kit/ArkHostSystem.swift new file mode 100644 index 0000000..8e18852 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/ArkHostSystem.swift @@ -0,0 +1,15 @@ +import Foundation + +class ArkHostSystem: UpdateSystem { + let publisher: ArkHostNetworkPublisher + var active: Bool + + init(publisher: ArkHostNetworkPublisher, active: Bool = true) { + self.publisher = publisher + self.active = active + } + + func update(deltaTime: TimeInterval, arkECS: ArkECS) { + publisher.publish(ecs: arkECS) + } +} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift deleted file mode 100644 index 56f3ce3..0000000 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerContext.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -protocol ArkMultiplayerContext { - var playerNumber: Int { get } - var serviceName: String { get set } - var role: ArkPeerRole { get } -} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift deleted file mode 100644 index ad4ba0c..0000000 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation - -class ArkMultiplayerEventManager: ArkEventManager { - private var arkEventManager: ArkEventManager - var delegate: ArkMultiplayerEventManagerDelegate? - - init(arkEventManager: ArkEventManager = ArkEventManager(), - delegate: ArkMultiplayerEventManagerDelegate? = nil) { - self.arkEventManager = arkEventManager - self.delegate = delegate - } - - override func subscribe(to eventType: Event.Type, _ listener: @escaping (any ArkEvent) -> Void) { - arkEventManager.subscribe(to: eventType, listener) - } - - override func emit(_ event: Event) { - if delegate?.isBroadcastEvent == true { - delegate?.shouldSendEvent(event) - } - arkEventManager.emit(event) - } - - func emitWithoutBroadcast(_ event: Event) { - arkEventManager.emit(event) - } - - override func processEvents() { - arkEventManager.processEvents() - } -} - -protocol ArkMultiplayerEventManagerDelegate: AnyObject { - var isBroadcastEvent: Bool { get } - func shouldSendEvent(_ event: Event) -} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift index 14aab8e..788d993 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift @@ -24,9 +24,3 @@ class ArkMultiplayerGameLoop: GameLoop { func resumeLoop() { } } - -extension ArkMultiplayerGameLoop: ArkMultiplayerECSDelegate { - func ecsDidUpdate() { - self.update() - } -} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift index cd21384..5c7c52a 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift @@ -4,109 +4,3 @@ enum ArkPeerRole { case host case participant } - -class ArkMultiplayerManager: ArkNetworkDelegate, ArkMultiplayerContext { - private var networkService: ArkNetworkProtocol - var multiplayerEventManager: ArkMultiplayerEventManager? - var arkMultiplayerECSDelegate: ArkMultiplayerECSDelegate? - var ecs: ArkECS - private var peers = [String]() - private var host: String? - private(set) var role: ArkPeerRole - var playerNumber: Int { - peers.firstIndex(of: networkService.deviceID) ?? 0 - } - - init(serviceName: String, role: ArkPeerRole, ecs: ArkECS) { - self.networkService = ArkNetworkService(serviceName: serviceName) - self.ecs = ecs - self.role = role - self.networkService.delegate = self - } - - var serviceName: String { - get { - networkService.serviceName - } - set { - self.networkService = ArkNetworkService(serviceName: newValue) - self.networkService.delegate = self - } - } - - func gameDataReceived(manager: ArkNetworkService, gameData: Data) { - do { - let wrappedData = try JSONDecoder().decode(DataWrapper.self, from: gameData) - if wrappedData.type == .event, - let event = try ArkEventRegistry.shared.decode(from: wrappedData.payload, - typeName: wrappedData.name) { - processEvent(event: event) - } - - if wrappedData.type == .ecs { - let ecsWrapper = try ArkECSDataSerializer.decodeArkECS(from: wrappedData.payload) - ecs.upsertEntityManager(entities: ecsWrapper.entities, components: ecsWrapper.decodeComponents()) - self.arkMultiplayerECSDelegate?.ecsDidUpdate() - } - - } catch { - print("Error decoding received data: \(error)") - } - } - - private func processEvent(event: any ArkEvent) { - multiplayerEventManager?.emitWithoutBroadcast(event) - } - - func connectedDevicesChanged(manager: ArkNetworkService, connectedDevices: [String]) { - peers = connectedDevices - } - - private func updateRoles() { - let sortedPeers = (peers + [networkService.deviceID]).sorted() - host = sortedPeers.first - - role = host == networkService.deviceID ? .host : .participant - print("Updated role: \(role)") - } - - var isModificationEnabled: Bool { - self.role == .host - } - - func sendECS() { - if isModificationEnabled { - do { - let encodedECS = try ArkECSDataSerializer.encodeArkECS(ecs: ecs) - networkService.sendData(data: encodedECS) - } catch { - print("Error encoding or sending ecs function: \(error)") - } - } - } -} - -extension ArkMultiplayerManager: ArkMultiplayerEventManagerDelegate { - var isBroadcastEvent: Bool { - self.role == .participant - } - - func shouldSendEvent(_ event: Event) { - sendEvent(event: event) - } - - private func sendEvent(event: any ArkEvent) { - do { - if let encodedEvent = try ArkEventDataSerializer.encodeEvent(event), - let target = host { - networkService.sendData(encodedEvent, to: target) - } - } catch { - print("Error encoding or sending event: \(error)") - } - } -} - -protocol ArkMultiplayerECSDelegate: AnyObject { - func ecsDidUpdate() -} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerSystem.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerSystem.swift deleted file mode 100644 index 2b2ea9a..0000000 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerSystem.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -class ArkMultiplayerSystem: UpdateSystem { - let multiplayerManager: ArkMultiplayerManager - var active: Bool - - init(multiplayerManager: ArkMultiplayerManager) { - self.multiplayerManager = multiplayerManager - self.active = true - } - - func update(deltaTime: TimeInterval, arkECS: ArkECS) { - multiplayerManager.sendECS() - } -} diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift index 8bf7ecc..5029718 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift @@ -1,11 +1,17 @@ import UIKit import P2PShare -class ArkNetworkService: ArkNetworkProtocol { +/** + * Service class that is used by each device to send or listen to updates over the network. + */ +class ArkNetworkService: AbstractNetworkService { private let myPeerInfo = PeerInfo(["name": UIDevice.current.name]) private var peers: [PeerInfo] = [] private var session: MultipeerSession! - var delegate: ArkNetworkDelegate? + + var subscriber: ArkNetworkSubscriberDelegate? + var publisher: ArkNetworkPublisherDelegate? + private(set) var serviceName: String required init(serviceName: String = "Ark") { @@ -35,7 +41,7 @@ class ArkNetworkService: ArkNetworkProtocol { } strongSelf.peers = peers - strongSelf.delegate?.connectedDevicesChanged(manager: strongSelf, connectedDevices: peers.map { $0.peerID }) + strongSelf.publisher?.onChangeInObservers(manager: strongSelf, connectedDevices: peers.map { $0.peerID }) print("Peers changed: \(peers.map { $0.info["name"] ?? $0.peerID })") } @@ -44,7 +50,7 @@ class ArkNetworkService: ArkNetworkProtocol { } self.session.messageReceivedHandler = { [weak self] _, data in - self?.delegate?.gameDataReceived(manager: self!, gameData: data) + self?.subscriber?.onListen(data) } } @@ -62,10 +68,3 @@ class ArkNetworkService: ArkNetworkProtocol { session.send(to: peerInfo.peerID, data: data) } } - -// MARK: - ArkNetworkDelegate - -protocol ArkNetworkDelegate: AnyObject { - func connectedDevicesChanged(manager: ArkNetworkService, connectedDevices: [String]) - func gameDataReceived(manager: ArkNetworkService, gameData: Data) -} diff --git a/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift b/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift new file mode 100644 index 0000000..84ce370 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift @@ -0,0 +1,29 @@ +class ArkHostNetworkPublisher: ArkNetworkPublisherDelegate { + // network related dependencies + var networkService: AbstractNetworkService + private var peers = [String]() + + init(publishTo networkService: AbstractNetworkService) { + self.networkService = networkService + self.networkService.publisher = self + } + + func publish(ecs: ArkECS) { + do { + let encodedECS = try ArkECSDataSerializer.encodeArkECS(ecs: ecs) + networkService.sendData(data: encodedECS) + } catch { + print("Error encoding or sending ecs function: \(error)") + } + } + + func publish(event: any ArkEvent) { + // Host does not publish events + // event updates are propagated through ECS + } + + func onChangeInObservers(manager: ArkNetworkService, connectedDevices: [String]) { + // registers listeners to publish to + peers = connectedDevices + } +} diff --git a/ArkKit/ark-multiplayer-kit/publisher/ArkNetworkPublisherDelegate.swift b/ArkKit/ark-multiplayer-kit/publisher/ArkNetworkPublisherDelegate.swift new file mode 100644 index 0000000..5e83361 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/publisher/ArkNetworkPublisherDelegate.swift @@ -0,0 +1,5 @@ +protocol ArkNetworkPublisherDelegate: AnyObject { + func onChangeInObservers(manager: ArkNetworkService, connectedDevices: [String]) + func publish(ecs: ArkECS) + func publish(event: any ArkEvent) +} diff --git a/ArkKit/ark-multiplayer-kit/publisher/ArkParticipantNetworkPublisher.swift b/ArkKit/ark-multiplayer-kit/publisher/ArkParticipantNetworkPublisher.swift new file mode 100644 index 0000000..d158169 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/publisher/ArkParticipantNetworkPublisher.swift @@ -0,0 +1,29 @@ +class ArkParticipantNetworkPublisher: ArkNetworkPublisherDelegate { + // network related dependencies + var networkService: AbstractNetworkService + private var peers = [String]() + + init(publishTo networkService: AbstractNetworkService) { + self.networkService = networkService + self.networkService.publisher = self + } + + func publish(ecs: ArkECS) { + // Participant does not publish ECS + } + + func publish(event: any ArkEvent) { + do { + if let encodedEvent = try ArkEventDataSerializer.encodeEvent(event) { + networkService.sendData(data: encodedEvent) + } + } catch { + print("Error encoding or sending event: \(error)") + } + } + + func onChangeInObservers(manager: ArkNetworkService, connectedDevices: [String]) { + // registers listeners to publish to + peers = connectedDevices + } +} diff --git a/ArkKit/ark-multiplayer-kit/subscriber/ArkHostNetworkSubscriber.swift b/ArkKit/ark-multiplayer-kit/subscriber/ArkHostNetworkSubscriber.swift new file mode 100644 index 0000000..b9b4559 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/subscriber/ArkHostNetworkSubscriber.swift @@ -0,0 +1,35 @@ +import Foundation + +class ArkHostNetworkSubscriber: ArkNetworkSubscriberDelegate { + // network related dependencies + var networkService: AbstractNetworkService + + // inject dependency + weak var localState: ArkState? + + init(subscribeTo networkService: AbstractNetworkService) { + self.networkService = networkService + self.networkService.subscriber = self + } + + func onListen(_ data: Data) { + do { + let wrappedData = try JSONDecoder().decode(DataWrapper.self, from: data) + + if wrappedData.type == .event, + let event = try ArkEventRegistry.shared.decode(from: wrappedData.payload, + typeName: wrappedData.name) { + processEvent(event: event) + } + + // host does not listen to ecs updates + } catch { + print("Error decoding received data: \(error)") + } + } + + private func processEvent(event: any ArkEvent) { + print("host listening to event from participant") + localState?.eventManager.emit(event) + } +} diff --git a/ArkKit/ark-multiplayer-kit/subscriber/ArkNetworkSubscriberDelegate.swift b/ArkKit/ark-multiplayer-kit/subscriber/ArkNetworkSubscriberDelegate.swift new file mode 100644 index 0000000..28e08f2 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/subscriber/ArkNetworkSubscriberDelegate.swift @@ -0,0 +1,5 @@ +import Foundation + +protocol ArkNetworkSubscriberDelegate: AnyObject { + func onListen(_ data: Data) +} diff --git a/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift b/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift new file mode 100644 index 0000000..8dacd3d --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift @@ -0,0 +1,35 @@ +import Foundation + +class ArkParticipantNetworkSubscriber: ArkNetworkSubscriberDelegate { + // network related dependencies + var networkService: AbstractNetworkService + + // inject dependency + weak var localState: ArkState? + weak var localGameLoop: GameLoop? + + init(subscribeTo networkService: AbstractNetworkService) { + self.networkService = networkService + self.networkService.subscriber = self + } + + /// Participants only listen to ecs updates from host + func onListen(_ data: Data) { + do { + let wrappedData = try JSONDecoder().decode(DataWrapper.self, from: data) + + if wrappedData.type == .ecs { + let ecsWrapper = try ArkECSDataSerializer.decodeArkECS(from: wrappedData.payload) + processECSUpdates(ecsWrapper) + } + + localGameLoop?.update() + } catch { + print("Error decoding received data: \(error)") + } + } + + private func processECSUpdates(_ ecsWrapper: ArkECSWrapper) { + localState?.arkECS.upsertEntityManager(entities: ecsWrapper.entities, components: ecsWrapper.decodeComponents()) + } +} From 6809696238578f2cccd570c05dd7ff3e26f738b3 Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 16:57:46 +0800 Subject: [PATCH 27/43] [ark-multiplayer] chore: rename file to peer role --- ArkKit.xcodeproj/project.pbxproj | 8 ++++---- .../{ArkMultiplayerManager.swift => ArkPeerRole.swift} | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) rename ArkKit/ark-multiplayer-kit/{ArkMultiplayerManager.swift => ArkPeerRole.swift} (74%) diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 2e4c79a..7175a73 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -227,7 +227,7 @@ 94D053A32BC80E8C000280C6 /* ArkImageEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D053A22BC80E8C000280C6 /* ArkImageEnum.swift */; }; AD260CA82BBFCC7E008B9654 /* EntityIDGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */; }; AD2B59AC2BB87F2200198E99 /* ArkNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */; }; - AD2B59B22BB94FBE00198E99 /* ArkMultiplayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */; }; + AD2B59B22BB94FBE00198E99 /* ArkPeerRole.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59B12BB94FBE00198E99 /* ArkPeerRole.swift */; }; AD2B59B62BB958E400198E99 /* DataWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59B52BB958E400198E99 /* DataWrapper.swift */; }; AD2B59B82BB9672100198E99 /* ArkEventRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2B59B72BB9672100198E99 /* ArkEventRegistry.swift */; }; AD36A7562BAC0D36003E938B /* TankGameManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD36A7552BAC0D36003E938B /* TankGameManager.swift */; }; @@ -497,7 +497,7 @@ 94D053A22BC80E8C000280C6 /* ArkImageEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkImageEnum.swift; sourceTree = ""; }; AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityIDGenerator.swift; sourceTree = ""; }; AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkService.swift; sourceTree = ""; }; - AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerManager.swift; sourceTree = ""; }; + AD2B59B12BB94FBE00198E99 /* ArkPeerRole.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkPeerRole.swift; sourceTree = ""; }; AD2B59B52BB958E400198E99 /* DataWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataWrapper.swift; sourceTree = ""; }; AD2B59B72BB9672100198E99 /* ArkEventRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkEventRegistry.swift; sourceTree = ""; }; AD36A7552BAC0D36003E938B /* TankGameManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankGameManager.swift; sourceTree = ""; }; @@ -1414,7 +1414,7 @@ 02B3C6282BCD1977002331A0 /* publisher */, ADD74C0A2BC0555D008CE36B /* sendable */, ADA848002BBC7DC600B19378 /* data-serialize */, - AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */, + AD2B59B12BB94FBE00198E99 /* ArkPeerRole.swift */, 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */, AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */, ADA847FC2BBC4B5500B19378 /* AbstractNetworkService.swift */, @@ -1804,7 +1804,7 @@ 945441172BC948DB00E90ECE /* SnakeGameDeque.swift in Sources */, 280CD3C42BA7419400372C5D /* ArkPhysicsContactUpdateDelegate.swift in Sources */, 022C42792BC9940D003D6924 /* PauseGameLoopEvent.swift in Sources */, - AD2B59B22BB94FBE00198E99 /* ArkMultiplayerManager.swift in Sources */, + AD2B59B22BB94FBE00198E99 /* ArkPeerRole.swift in Sources */, 287B00552BC16E6C002F0114 /* TankHPComponent.swift in Sources */, 02C3950F2BA611B80075F1CA /* ArkEventContext.swift in Sources */, 28EFCB292BCCC90B0059A908 /* ArkHostSystem.swift in Sources */, diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkPeerRole.swift similarity index 74% rename from ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift rename to ArkKit/ark-multiplayer-kit/ArkPeerRole.swift index 5c7c52a..522ff59 100644 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ b/ArkKit/ark-multiplayer-kit/ArkPeerRole.swift @@ -1,5 +1,3 @@ -import Foundation - enum ArkPeerRole { case host case participant From 23d78fb788e7765ddc26917e3dbb197c9f730612 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Mon, 15 Apr 2024 19:19:20 +0800 Subject: [PATCH 28/43] [ark-multiplayer-kit, ark-render-kit] Feat: Make ButtonComponent sendable --- ArkKit.xcodeproj/project.pbxproj | 8 +++ ...enderableComponent+SendableComponent.swift | 58 +++++++++++++++++++ ...enderableComponent+SendableComponent.swift | 1 + .../input/ButtonRenderableComponent.swift | 13 ++++- .../view-components/input/UIKitButton.swift | 4 +- 5 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/ButtonRenderableComponent+SendableComponent.swift create mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 7175a73..89fa5c1 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -172,6 +172,8 @@ 28EFCB252BCCA7CC0059A908 /* ArkECSWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */; }; 28EFCB272BCCB6130059A908 /* ArkMultiplayerGameLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */; }; 28EFCB292BCCC90B0059A908 /* ArkHostSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB282BCCC90B0059A908 /* ArkHostSystem.swift */; }; + 28EFCB2B2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB2A2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift */; }; + 28EFCB2D2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB2C2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift */; }; 8F5573BF2BA425E4007030C8 /* ShapeRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */; }; 8F5573C12BA42614007030C8 /* Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C02BA42614007030C8 /* Renderable.swift */; }; 8F5573C32BA4284D007030C8 /* BitmapRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */; }; @@ -443,6 +445,8 @@ 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSWrapper.swift; sourceTree = ""; }; 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerGameLoop.swift; sourceTree = ""; }; 28EFCB282BCCC90B0059A908 /* ArkHostSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkHostSystem.swift; sourceTree = ""; }; + 28EFCB2A2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ButtonRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; + 28EFCB2C2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JoystickRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeRenderable.swift; sourceTree = ""; }; 8F5573C02BA42614007030C8 /* Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderable.swift; sourceTree = ""; }; 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitmapRenderable.swift; sourceTree = ""; }; @@ -1107,6 +1111,8 @@ 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */, 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */, 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */, + 28EFCB2A2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift */, + 28EFCB2C2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift */, ); path = SendableRenderableComponents; sourceTree = ""; @@ -1851,6 +1857,7 @@ AD6E03722BA2DB2700974EBF /* Heap.swift in Sources */, 02D8E94E2BAC9A0E00BF3A07 /* GameLoop.swift in Sources */, 945441102BC9202300E90ECE /* SnakeGameDirection.swift in Sources */, + 28EFCB2D2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift in Sources */, 2812FCC72BC442BD00A0FE24 /* TankRaceGameTerrainObjectBuilder.swift in Sources */, 28EFCB272BCCB6130059A908 /* ArkMultiplayerGameLoop.swift in Sources */, 0266151D2BC1405C00B56A85 /* TankRaceGame.swift in Sources */, @@ -1862,6 +1869,7 @@ 02D8E94C2BAC99FC00BF3A07 /* GameLoopable.swift in Sources */, 280CD3D02BA74E6000372C5D /* AbstractArkPhysicsScene.swift in Sources */, AD260CA82BBFCC7E008B9654 /* EntityIDGenerator.swift in Sources */, + 28EFCB2B2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift in Sources */, 02C394E62BA41AC60075F1CA /* ArkGameModel.swift in Sources */, AD36A75B2BAC331F003E938B /* TankComponent.swift in Sources */, 28844EA12BA881E60037A7F6 /* DemoPhysicsComponent.swift in Sources */, diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/ButtonRenderableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/ButtonRenderableComponent+SendableComponent.swift new file mode 100644 index 0000000..62108c3 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/ButtonRenderableComponent+SendableComponent.swift @@ -0,0 +1,58 @@ +import Foundation + +extension ButtonRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case center, rotation, zPosition, renderLayer, isUserInteractionEnabled, width, height, buttonStyleConfig + // TODO: Figure out a way to link the tap delegation + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + width = try container.decode(Double.self, forKey: .width) + height = try container.decode(Double.self, forKey: .height) + buttonStyleConfig = try container.decode(ButtonStyleConfiguration.self, forKey: .buttonStyleConfig) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + try container.encode(buttonStyleConfig, forKey: .buttonStyleConfig) + } +} + +extension ButtonStyleConfiguration: Codable { + enum CodingKeys: String, CodingKey { + case labelMapping, backgroundColor, borderRadius, borderWidth, borderColor, padding + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + labelMapping = try container.decodeIfPresent(LabelMapping.self, forKey: .labelMapping) + backgroundColor = try container.decodeIfPresent(AbstractColor.self, forKey: .backgroundColor) + borderRadius = try container.decodeIfPresent(Double.self, forKey: .borderRadius) + borderWidth = try container.decodeIfPresent(Double.self, forKey: .borderWidth) + borderColor = try container.decodeIfPresent(AbstractColor.self, forKey: .borderColor) + padding = try container.decodeIfPresent([PaddingSides: Double].self, forKey: .padding) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(labelMapping, forKey: .labelMapping) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(borderRadius, forKey: .borderRadius) + try container.encodeIfPresent(borderWidth, forKey: .borderWidth) + try container.encodeIfPresent(borderColor, forKey: .borderColor) + try container.encodeIfPresent(padding, forKey: .padding) + } +} diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift @@ -0,0 +1 @@ + diff --git a/ArkKit/ark-render-kit/renderable-components/input/ButtonRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/input/ButtonRenderableComponent.swift index c281741..9137511 100644 --- a/ArkKit/ark-render-kit/renderable-components/input/ButtonRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/input/ButtonRenderableComponent.swift @@ -34,7 +34,7 @@ struct ButtonRenderableComponent: AbstractTappable, RenderableComponent { extension ButtonRenderableComponent: AbstractButtonStyle { func label(_ label: String, color: AbstractColor) -> ButtonRenderableComponent { var newSelf = self - newSelf.buttonStyleConfig.labelMapping = (label, color) + newSelf.buttonStyleConfig.labelMapping = LabelMapping(label: label, color: color) return newSelf } @@ -98,6 +98,11 @@ extension ButtonRenderableComponent: AbstractButtonStyle { } } +struct LabelMapping: Codable { + var label: String + var color: AbstractColor +} + struct ButtonStyleConfiguration { enum BorderSides: CaseIterable { case topLeft @@ -105,13 +110,15 @@ struct ButtonStyleConfiguration { case bottomLeft case bottomRight } - enum PaddingSides: CaseIterable { + + enum PaddingSides: String, Codable, CaseIterable { case top case bottom case left case right } - var labelMapping: (String, AbstractColor)? + + var labelMapping: LabelMapping? var backgroundColor: AbstractColor? var borderRadius: Double? var borderWidth: Double? diff --git a/ArkKit/ark-uikit-lib/view-components/input/UIKitButton.swift b/ArkKit/ark-uikit-lib/view-components/input/UIKitButton.swift index 2f15a4a..b835403 100644 --- a/ArkKit/ark-uikit-lib/view-components/input/UIKitButton.swift +++ b/ArkKit/ark-uikit-lib/view-components/input/UIKitButton.swift @@ -47,8 +47,8 @@ extension UIKitButton { var configuration = self.configuration ?? UIButton.Configuration.filled() // Set title for different states if provided if let label = styleConfig.labelMapping { - self.setTitle(label.0, for: .normal) - self.setTitleColor(colorGetter(label.1), for: .normal) + self.setTitle(label.label, for: .normal) + self.setTitleColor(colorGetter(label.color), for: .normal) } configuration.baseBackgroundColor = colorGetter( styleConfig.backgroundColor ?? .default From 4fb87e715ee9f2417d251b60cb3ff9be309e2b93 Mon Sep 17 00:00:00 2001 From: Markus Yeo Date: Mon, 15 Apr 2024 19:23:36 +0800 Subject: [PATCH 29/43] [ark-multiplayer-kit] feat: make joystickcomponent sendable --- ...enderableComponent+SendableComponent.swift | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift index 8b13789..f30ff27 100644 --- a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift @@ -1 +1,28 @@ +import Foundation +extension JoystickRenderableComponent: SendableComponent { + enum CodingKeys: String, CodingKey { + case center, rotation, zPosition, isUserInteractionEnabled, renderLayer, radius + // TODO: Figure out a way to link the tap delegation + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + center = try container.decode(CGPoint.self, forKey: .center) + rotation = try container.decode(Double.self, forKey: .rotation) + zPosition = try container.decode(Double.self, forKey: .zPosition) + isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) + renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) + radius = try container.decode(Double.self, forKey: .radius) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(center, forKey: .center) + try container.encode(rotation, forKey: .rotation) + try container.encode(zPosition, forKey: .zPosition) + try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) + try container.encode(renderLayer, forKey: .renderLayer) + try container.encode(radius, forKey: .radius) + } +} From a2805b418550147642c52571c48850bbba88d649 Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 21:01:31 +0800 Subject: [PATCH 30/43] [ark-multiplayer] fix: undo input renderables as sendable --- ...enderableComponent+SendableComponent.swift | 58 ------------------- ...enderableComponent+SendableComponent.swift | 28 --------- 2 files changed, 86 deletions(-) delete mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/ButtonRenderableComponent+SendableComponent.swift delete mode 100644 ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/ButtonRenderableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/ButtonRenderableComponent+SendableComponent.swift deleted file mode 100644 index 62108c3..0000000 --- a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/ButtonRenderableComponent+SendableComponent.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Foundation - -extension ButtonRenderableComponent: SendableComponent { - enum CodingKeys: String, CodingKey { - case center, rotation, zPosition, renderLayer, isUserInteractionEnabled, width, height, buttonStyleConfig - // TODO: Figure out a way to link the tap delegation - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - center = try container.decode(CGPoint.self, forKey: .center) - rotation = try container.decode(Double.self, forKey: .rotation) - zPosition = try container.decode(Double.self, forKey: .zPosition) - renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) - isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) - width = try container.decode(Double.self, forKey: .width) - height = try container.decode(Double.self, forKey: .height) - buttonStyleConfig = try container.decode(ButtonStyleConfiguration.self, forKey: .buttonStyleConfig) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(center, forKey: .center) - try container.encode(rotation, forKey: .rotation) - try container.encode(zPosition, forKey: .zPosition) - try container.encode(renderLayer, forKey: .renderLayer) - try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) - try container.encode(width, forKey: .width) - try container.encode(height, forKey: .height) - try container.encode(buttonStyleConfig, forKey: .buttonStyleConfig) - } -} - -extension ButtonStyleConfiguration: Codable { - enum CodingKeys: String, CodingKey { - case labelMapping, backgroundColor, borderRadius, borderWidth, borderColor, padding - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - labelMapping = try container.decodeIfPresent(LabelMapping.self, forKey: .labelMapping) - backgroundColor = try container.decodeIfPresent(AbstractColor.self, forKey: .backgroundColor) - borderRadius = try container.decodeIfPresent(Double.self, forKey: .borderRadius) - borderWidth = try container.decodeIfPresent(Double.self, forKey: .borderWidth) - borderColor = try container.decodeIfPresent(AbstractColor.self, forKey: .borderColor) - padding = try container.decodeIfPresent([PaddingSides: Double].self, forKey: .padding) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encodeIfPresent(labelMapping, forKey: .labelMapping) - try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) - try container.encodeIfPresent(borderRadius, forKey: .borderRadius) - try container.encodeIfPresent(borderWidth, forKey: .borderWidth) - try container.encodeIfPresent(borderColor, forKey: .borderColor) - try container.encodeIfPresent(padding, forKey: .padding) - } -} diff --git a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift deleted file mode 100644 index f30ff27..0000000 --- a/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/JoystickRenderableComponent+SendableComponent.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation - -extension JoystickRenderableComponent: SendableComponent { - enum CodingKeys: String, CodingKey { - case center, rotation, zPosition, isUserInteractionEnabled, renderLayer, radius - // TODO: Figure out a way to link the tap delegation - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - center = try container.decode(CGPoint.self, forKey: .center) - rotation = try container.decode(Double.self, forKey: .rotation) - zPosition = try container.decode(Double.self, forKey: .zPosition) - isUserInteractionEnabled = try container.decode(Bool.self, forKey: .isUserInteractionEnabled) - renderLayer = try container.decode(RenderLayer.self, forKey: .renderLayer) - radius = try container.decode(Double.self, forKey: .radius) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(center, forKey: .center) - try container.encode(rotation, forKey: .rotation) - try container.encode(zPosition, forKey: .zPosition) - try container.encode(isUserInteractionEnabled, forKey: .isUserInteractionEnabled) - try container.encode(renderLayer, forKey: .renderLayer) - try container.encode(radius, forKey: .radius) - } -} From 187aad1515b89e124f87f519b0ab7b8d498a389f Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 21:02:01 +0800 Subject: [PATCH 31/43] [ark-ecs] fix: add clause in upsertComponent to insert entity if not exist --- ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift index 5aaa249..6b57dd6 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift @@ -21,6 +21,10 @@ class EntityManager { } func upsertComponent(_ component: T, to entity: Entity) { + // add entity if entity does not exist + if !entities.contains(entity) { + entities.insert(entity) + } let typeID = ObjectIdentifier(T.self) componentsByType[typeID, default: [:]][entity] = component } From e3c8afc57ed201f237113846e9c2ea4ac8b3bf49 Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 21:03:07 +0800 Subject: [PATCH 32/43] [ark-ecs] refactor: rename method as bulkUpsert and add removeAllEntities --- ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift index e880e3c..0aea278 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift @@ -18,11 +18,12 @@ class ArkECS: ArkECSContext { } } - func upsertEntityManager(entities: [Entity], components: [EntityID: [any Component]]) { - entityManager.removeAllEntities() + func bulkUpsert(entities: [Entity], components: [EntityID: [any Component]]) { for entity in entities { let components = components[entity.id] ?? [] - _ = entityManager.createEntity(with: components) + for component in components { + entityManager.upsertComponent(component, to: entity) + } } } @@ -52,6 +53,17 @@ class ArkECS: ArkECSContext { entityManager.removeEntity(entity) } + func removeAllEntities(except entitiesNotToRemove: [Entity] = []) { + let entities = getEntities() + let entitiesMarkedNotToRemove = Set(entitiesNotToRemove) + for entity in entities { + if entitiesMarkedNotToRemove.contains(entity) { + continue + } + removeEntity(entity) + } + } + func upsertComponent(_ component: T, to entity: Entity) where T: Component { entityManager.upsertComponent(component, to: entity) } @@ -78,7 +90,7 @@ class ArkECS: ArkECSContext { entityManager.getEntity(id: id) } - func getEntities(with componentTypes: [any Component.Type]) -> [Entity] { + func getEntities(with componentTypes: [any Component.Type] = []) -> [Entity] { entityManager.getEntities(with: componentTypes) } From 0de90a41f9c86a175772be75f521a1da65af78ab Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 21:06:16 +0800 Subject: [PATCH 33/43] [ark-multiplayer] fix: update participantsubscriber to remove invalid entities --- .../ArkParticipantNetworkSubscriber.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift b/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift index 8dacd3d..89ec5a5 100644 --- a/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift +++ b/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift @@ -29,7 +29,16 @@ class ArkParticipantNetworkSubscriber: ArkNetworkSubscriberDelegate { } } - private func processECSUpdates(_ ecsWrapper: ArkECSWrapper) { - localState?.arkECS.upsertEntityManager(entities: ecsWrapper.entities, components: ecsWrapper.decodeComponents()) + private func processECSUpdates(_ updatedECSStateWrapper: ArkECSWrapper) { + guard let ecs = localState?.arkECS else { + return + } + // remove all outdated entities + ecs.removeAllEntities(except: updatedECSStateWrapper.entities) + + ecs.bulkUpsert( + entities: updatedECSStateWrapper.entities, + components: updatedECSStateWrapper.decodeComponents() + ) } } From 43ef5be4e85742937e4ccee83370cd3be032c0ff Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 21:06:56 +0800 Subject: [PATCH 34/43] [ark] feat: update setupIfParticipant to preserve screen layer renderables --- ArkKit/Ark.swift | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift index bd7b3ef..716bcd6 100644 --- a/ArkKit/Ark.swift +++ b/ArkKit/Ark.swift @@ -94,6 +94,8 @@ class Ark: ArkProtocol { } setupDefaultListeners() setupMultiplayerGameLoop() + setup(blueprint.setupFunctions) + setup(blueprint.soundMapping) let networkService = ArkNetworkService(serviceName: networkPlayableInfo.roomName) self.participantSubscriber = ArkParticipantNetworkSubscriber(subscribeTo: networkService) @@ -102,7 +104,45 @@ class Ark: ArkProtocol { let participantPublisher = ArkParticipantNetworkPublisher(publishTo: networkService) self.arkState.eventManager.networkPublisherDelegate = participantPublisher - setup(blueprint.soundMapping) + + preserveSelectComponentsIfParticipant() + } + + private func preserveSelectComponentsIfParticipant() { + // Retrieve all entities + let allEntities = arkState.arkECS.getEntities() + + // Filter and preserve entities with renderable components at the screen layer + var preservedEntityToRenderableComponent: [Entity: [any RenderableComponent]] = [:] + for entity in allEntities { + preserveRenderableComponentsAtScreenLayer(for: entity, in: &preservedEntityToRenderableComponent) + } + + // Remove all other entities + let preservedEntities = Array(preservedEntityToRenderableComponent.keys) + arkState.arkECS.removeAllEntities(except: preservedEntities) + + // Bulk upsert the preserved entities and their components + var preservedComponentMapping: [EntityID: [any Component]] = [:] + preservedEntityToRenderableComponent.forEach { entity, componentList in + preservedComponentMapping[entity.id] = componentList + } + arkState.arkECS.bulkUpsert(entities: preservedEntities, components: preservedComponentMapping) + } + + private func preserveRenderableComponentsAtScreenLayer( + for entity: Entity, + in preservedEntityToRenderableComponent: inout [Entity: [any RenderableComponent]] + ) { + for renderableComponentType in ArkCanvasSystem.renderableComponentTypes { + if let renderableComponent = arkState.arkECS.getComponent(ofType: renderableComponentType, for: entity), + renderableComponent.renderLayer == .screen { + if preservedEntityToRenderableComponent[entity] == nil { + preservedEntityToRenderableComponent[entity] = [] + } + preservedEntityToRenderableComponent[entity]?.append(renderableComponent) + } + } } private func setUpIfHost() { From 6baea08d65eb8176a114c761e4c3ff0ed0c2d3b5 Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 21:07:17 +0800 Subject: [PATCH 35/43] [misc] chore: update project file metadata --- ArkKit.xcodeproj/project.pbxproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 89fa5c1..7175a73 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -172,8 +172,6 @@ 28EFCB252BCCA7CC0059A908 /* ArkECSWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */; }; 28EFCB272BCCB6130059A908 /* ArkMultiplayerGameLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */; }; 28EFCB292BCCC90B0059A908 /* ArkHostSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB282BCCC90B0059A908 /* ArkHostSystem.swift */; }; - 28EFCB2B2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB2A2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift */; }; - 28EFCB2D2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28EFCB2C2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift */; }; 8F5573BF2BA425E4007030C8 /* ShapeRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */; }; 8F5573C12BA42614007030C8 /* Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C02BA42614007030C8 /* Renderable.swift */; }; 8F5573C32BA4284D007030C8 /* BitmapRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */; }; @@ -445,8 +443,6 @@ 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkECSWrapper.swift; sourceTree = ""; }; 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkMultiplayerGameLoop.swift; sourceTree = ""; }; 28EFCB282BCCC90B0059A908 /* ArkHostSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkHostSystem.swift; sourceTree = ""; }; - 28EFCB2A2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ButtonRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; - 28EFCB2C2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JoystickRenderableComponent+SendableComponent.swift"; sourceTree = ""; }; 8F5573BE2BA425E4007030C8 /* ShapeRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapeRenderable.swift; sourceTree = ""; }; 8F5573C02BA42614007030C8 /* Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderable.swift; sourceTree = ""; }; 8F5573C22BA4284D007030C8 /* BitmapRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitmapRenderable.swift; sourceTree = ""; }; @@ -1111,8 +1107,6 @@ 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */, 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */, 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */, - 28EFCB2A2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift */, - 28EFCB2C2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift */, ); path = SendableRenderableComponents; sourceTree = ""; @@ -1857,7 +1851,6 @@ AD6E03722BA2DB2700974EBF /* Heap.swift in Sources */, 02D8E94E2BAC9A0E00BF3A07 /* GameLoop.swift in Sources */, 945441102BC9202300E90ECE /* SnakeGameDirection.swift in Sources */, - 28EFCB2D2BCD372A0059A908 /* JoystickRenderableComponent+SendableComponent.swift in Sources */, 2812FCC72BC442BD00A0FE24 /* TankRaceGameTerrainObjectBuilder.swift in Sources */, 28EFCB272BCCB6130059A908 /* ArkMultiplayerGameLoop.swift in Sources */, 0266151D2BC1405C00B56A85 /* TankRaceGame.swift in Sources */, @@ -1869,7 +1862,6 @@ 02D8E94C2BAC99FC00BF3A07 /* GameLoopable.swift in Sources */, 280CD3D02BA74E6000372C5D /* AbstractArkPhysicsScene.swift in Sources */, AD260CA82BBFCC7E008B9654 /* EntityIDGenerator.swift in Sources */, - 28EFCB2B2BCD37200059A908 /* ButtonRenderableComponent+SendableComponent.swift in Sources */, 02C394E62BA41AC60075F1CA /* ArkGameModel.swift in Sources */, AD36A75B2BAC331F003E938B /* TankComponent.swift in Sources */, 28844EA12BA881E60037A7F6 /* DemoPhysicsComponent.swift in Sources */, From 189fbef914777a8d88e90f5a3dd815764fa3003c Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 21:48:44 +0800 Subject: [PATCH 36/43] [ark-app] refactor: move ark level files to app --- ArkKit.xcodeproj/project.pbxproj | 60 +++- ArkKit/Ark.swift | 340 ------------------ ArkKit/ArkBlueprint.swift | 12 - ArkKit/app/Ark.swift | 106 ++++++ ArkKit/{ => app}/ArkProtocol.swift | 0 .../network/ArkNetworkPlayableInfo.swift | 11 + .../utils/set-up/ArkSetUpIfHostStrategy.swift | 20 ++ .../ArkSetUpIfParticipantStrategy.swift | 62 ++++ .../utils/set-up/ArkSetUpOrchestrator.swift | 17 + .../app/utils/set-up/ArkSetUpStrategy.swift | 174 +++++++++ .../ArkSetUpWithoutNetworkStrategy.swift | 16 + 11 files changed, 464 insertions(+), 354 deletions(-) delete mode 100644 ArkKit/Ark.swift create mode 100644 ArkKit/app/Ark.swift rename ArkKit/{ => app}/ArkProtocol.swift (100%) create mode 100644 ArkKit/app/utils/network/ArkNetworkPlayableInfo.swift create mode 100644 ArkKit/app/utils/set-up/ArkSetUpIfHostStrategy.swift create mode 100644 ArkKit/app/utils/set-up/ArkSetUpIfParticipantStrategy.swift create mode 100644 ArkKit/app/utils/set-up/ArkSetUpOrchestrator.swift create mode 100644 ArkKit/app/utils/set-up/ArkSetUpStrategy.swift create mode 100644 ArkKit/app/utils/set-up/ArkSetUpWithoutNetworkStrategy.swift diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 7175a73..768f646 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -47,6 +47,12 @@ 02B3C6312BCD19ED002331A0 /* ArkNetworkSubscriberDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6302BCD19ED002331A0 /* ArkNetworkSubscriberDelegate.swift */; }; 02B3C6332BCD1EA4002331A0 /* ArkHostNetworkSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6322BCD1EA4002331A0 /* ArkHostNetworkSubscriber.swift */; }; 02B3C6352BCD1EE8002331A0 /* ArkParticipantNetworkPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6342BCD1EE8002331A0 /* ArkParticipantNetworkPublisher.swift */; }; + 02B3C6382BCD65D6002331A0 /* ArkSetUpOrchestrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6372BCD65D6002331A0 /* ArkSetUpOrchestrator.swift */; }; + 02B3C63B2BCD65F9002331A0 /* ArkSetUpStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C63A2BCD65F9002331A0 /* ArkSetUpStrategy.swift */; }; + 02B3C63D2BCD6610002331A0 /* ArkSetUpWithoutNetworkStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C63C2BCD660F002331A0 /* ArkSetUpWithoutNetworkStrategy.swift */; }; + 02B3C63F2BCD6639002331A0 /* ArkSetUpIfParticipantStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C63E2BCD6639002331A0 /* ArkSetUpIfParticipantStrategy.swift */; }; + 02B3C6412BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6402BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift */; }; + 02B3C6462BCD6692002331A0 /* ArkNetworkPlayableInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6452BCD6692002331A0 /* ArkNetworkPlayableInfo.swift */; }; 02C394DC2BA402060075F1CA /* GameStateRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DB2BA402060075F1CA /* GameStateRenderer.swift */; }; 02C394DE2BA4053E0075F1CA /* RenderableBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DD2BA4053E0075F1CA /* RenderableBuilder.swift */; }; 02C394E02BA405C10075F1CA /* Canvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DF2BA405C10075F1CA /* Canvas.swift */; }; @@ -319,6 +325,12 @@ 02B3C6302BCD19ED002331A0 /* ArkNetworkSubscriberDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkSubscriberDelegate.swift; sourceTree = ""; }; 02B3C6322BCD1EA4002331A0 /* ArkHostNetworkSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkHostNetworkSubscriber.swift; sourceTree = ""; }; 02B3C6342BCD1EE8002331A0 /* ArkParticipantNetworkPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkParticipantNetworkPublisher.swift; sourceTree = ""; }; + 02B3C6372BCD65D6002331A0 /* ArkSetUpOrchestrator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSetUpOrchestrator.swift; sourceTree = ""; }; + 02B3C63A2BCD65F9002331A0 /* ArkSetUpStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSetUpStrategy.swift; sourceTree = ""; }; + 02B3C63C2BCD660F002331A0 /* ArkSetUpWithoutNetworkStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSetUpWithoutNetworkStrategy.swift; sourceTree = ""; }; + 02B3C63E2BCD6639002331A0 /* ArkSetUpIfParticipantStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSetUpIfParticipantStrategy.swift; sourceTree = ""; }; + 02B3C6402BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSetUpIfHostStrategy.swift; sourceTree = ""; }; + 02B3C6452BCD6692002331A0 /* ArkNetworkPlayableInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkPlayableInfo.swift; sourceTree = ""; }; 02C394DB2BA402060075F1CA /* GameStateRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStateRenderer.swift; sourceTree = ""; }; 02C394DD2BA4053E0075F1CA /* RenderableBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderableBuilder.swift; sourceTree = ""; }; 02C394DF2BA405C10075F1CA /* Canvas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Canvas.swift; sourceTree = ""; }; @@ -721,6 +733,45 @@ path = subscriber; sourceTree = ""; }; + 02B3C6362BCD65B9002331A0 /* app */ = { + isa = PBXGroup; + children = ( + 02C394E22BA407420075F1CA /* Ark.swift */, + 941BE21D2BC0EC1900707997 /* ArkProtocol.swift */, + 02B3C6472BCD6791002331A0 /* utils */, + ); + path = app; + sourceTree = ""; + }; + 02B3C6392BCD65EA002331A0 /* set-up */ = { + isa = PBXGroup; + children = ( + 02B3C6372BCD65D6002331A0 /* ArkSetUpOrchestrator.swift */, + 02B3C63A2BCD65F9002331A0 /* ArkSetUpStrategy.swift */, + 02B3C63C2BCD660F002331A0 /* ArkSetUpWithoutNetworkStrategy.swift */, + 02B3C63E2BCD6639002331A0 /* ArkSetUpIfParticipantStrategy.swift */, + 02B3C6402BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift */, + ); + path = "set-up"; + sourceTree = ""; + }; + 02B3C6442BCD6686002331A0 /* network */ = { + isa = PBXGroup; + children = ( + 02B3C6452BCD6692002331A0 /* ArkNetworkPlayableInfo.swift */, + ); + path = network; + sourceTree = ""; + }; + 02B3C6472BCD6791002331A0 /* utils */ = { + isa = PBXGroup; + children = ( + 02B3C6442BCD6686002331A0 /* network */, + 02B3C6392BCD65EA002331A0 /* set-up */, + ); + path = utils; + sourceTree = ""; + }; 02C394DA2BA401F00075F1CA /* ark-render-kit */ = { isa = PBXGroup; children = ( @@ -1550,9 +1601,8 @@ AD787A4F2B9C636F003EBBD0 /* ArkKit */ = { isa = PBXGroup; children = ( - 02C394E22BA407420075F1CA /* Ark.swift */, - 941BE21D2BC0EC1900707997 /* ArkProtocol.swift */, 02C3950C2BA5FE5C0075F1CA /* ArkBlueprint.swift */, + 02B3C6362BCD65B9002331A0 /* app */, 0230A4822BB7EECD001CFDAF /* ark-uikit-lib */, 0267BA2B2BBD2A9E0010F729 /* ark-camera-kit */, 02E0E8DE2B9F57170043E2BA /* ark-game */, @@ -1882,6 +1932,7 @@ 02E0E8F42BA283180043E2BA /* UIKitRect.swift in Sources */, AD787A552B9C636F003EBBD0 /* RootViewController.swift in Sources */, 02C394E32BA407420075F1CA /* Ark.swift in Sources */, + 02B3C63F2BCD6639002331A0 /* ArkSetUpIfParticipantStrategy.swift in Sources */, 0267BA2D2BBD2AB90010F729 /* Camera.swift in Sources */, 02C9CC382BBF30180098E849 /* AbstractLetterboxable.swift in Sources */, AD6E03762BA2F24800974EBF /* ArkStateEventListener.swift in Sources */, @@ -1896,6 +1947,7 @@ 0267BA2F2BBD2BD70010F729 /* ArkCameraSystem.swift in Sources */, 282248532BA82E5800850D7F /* SKPhysicsBodyManager.swift in Sources */, 28A032E52BAD781900851BFF /* AbstractPhysicsArkSimulator.swift in Sources */, + 02B3C63D2BCD6610002331A0 /* ArkSetUpWithoutNetworkStrategy.swift in Sources */, 9454411F2BC9513900E90ECE /* SnakeBodyBlock.swift in Sources */, 94D0538E2BC6CB0D000280C6 /* ArkAudioEnum.swift in Sources */, 02C395152BA83FAE0075F1CA /* RenderableComponent.swift in Sources */, @@ -1967,8 +2019,10 @@ 0267BA232BBD0F940010F729 /* SystemRunner.swift in Sources */, ADD74C0E2BC0573E008CE36B /* SendableComponent.swift in Sources */, 02C394E02BA405C10075F1CA /* Canvas.swift in Sources */, + 02B3C6462BCD6692002331A0 /* ArkNetworkPlayableInfo.swift in Sources */, ADA848022BBC7DF000B19378 /* ArkSerializableEvent.swift in Sources */, 942A16772BADF3ED00F0186B /* AudioContext.swift in Sources */, + 02B3C6412BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift in Sources */, 28EFCB1D2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift in Sources */, AD787A842B9C6C78003EBBD0 /* Component.swift in Sources */, 945441252BC99D8800E90ECE /* SnakeGridPositionComponent.swift in Sources */, @@ -1981,6 +2035,7 @@ 942A16852BAEA43B00F0186B /* Sound.swift in Sources */, 0230A48C2BB802E0001CFDAF /* ArkViewFactory.swift in Sources */, 02C395262BA890470075F1CA /* ArkCanvasSystem.swift in Sources */, + 02B3C6382BCD65D6002331A0 /* ArkSetUpOrchestrator.swift in Sources */, 02C395202BA8495B0075F1CA /* RectRenderableComponent.swift in Sources */, 02C394EF2BA420210075F1CA /* ArkGameCoordinator.swift in Sources */, 02C3950B2BA5F8030075F1CA /* ArkECSContext.swift in Sources */, @@ -1993,6 +2048,7 @@ 02C394EF2BA420210075F1CA /* ArkGameCoordinator.swift in Sources */, 02C3950B2BA5F8030075F1CA /* ArkECSContext.swift in Sources */, ADD74C102BC05759008CE36B /* SendableEntity.swift in Sources */, + 02B3C63B2BCD65F9002331A0 /* ArkSetUpStrategy.swift in Sources */, 8FEB218C2BAF453900788E20 /* ArkAnimationsComponent.swift in Sources */, 280CD3E02BA81FBA00372C5D /* DefaultSKPhysicsBodyValues.swift in Sources */, 0267BA1E2BBB07D80010F729 /* ArkUpdateSystem.swift in Sources */, diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift deleted file mode 100644 index 716bcd6..0000000 --- a/ArkKit/Ark.swift +++ /dev/null @@ -1,340 +0,0 @@ -import Foundation - -/** - * `Ark` describes the game as is **loaded**. - * - * It loads the various contexts from the `ArkBlueprint` provided and the `GameLoop`. - * `Ark` requires a `rootView: AbstractRootView` to render the game. - * - * `Ark.start()` starts a loaded version of the game by injecting the game context dependencies. - * - * User of the `Ark` instance should ensure that the `arkInstance` is **binded** (strongly referenced), otherwise events - * relying on the `arkInstance` will not emit. - */ -class Ark: ArkProtocol { - let rootView: any AbstractRootView - var arkState: ArkState - var gameLoop: GameLoop? - - let blueprint: ArkBlueprint - let audioContext: any AudioContext - var displayContext: DisplayContext - - var actionContext: ArkActionContext { - ArkActionContext(ecs: arkState.arkECS, - events: arkState.eventManager, - display: displayContext, - audio: audioContext) - } - - var canvasRenderableBuilder: (any RenderableBuilder)? - - // network dependencies - var participantSubscriber: ArkParticipantNetworkSubscriber? - var hostSubscriber: ArkHostNetworkSubscriber? - - init(rootView: any AbstractRootView, - blueprint: ArkBlueprint, - canvasRenderableBuilder: (any RenderableBuilder)? = nil) { - self.rootView = rootView - self.blueprint = blueprint - - let eventManager = ArkEventManager() - let ecsManager = ArkECS() - self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) - - self.audioContext = ArkAudioContext() - self.canvasRenderableBuilder = canvasRenderableBuilder - self.displayContext = ArkDisplayContext( - canvasSize: CGSize( - width: blueprint.frameWidth, - height: blueprint.frameHeight - ), - screenSize: rootView.size - ) - } - - func start() { - // TODO: refactor to use strategy design pattern here - // use SetUpStrategy.execute() to set up based on status - setUpIfNotParticipant() - setUpIfParticipant() - setUpIfHost() - - alignCamera() - - guard let gameLoop = self.gameLoop else { - return - } - - let gameCoordinator = ArkGameCoordinator(rootView: rootView, - arkState: arkState, - displayContext: displayContext, - gameLoop: gameLoop, - canvasRenderer: canvasRenderableBuilder) - gameCoordinator.start() - } - - private func setUpIfNotParticipant() { - guard blueprint.networkPlayableInfo?.role != .participant else { - return - } - setupDefaultEntities() - setupDefaultListeners() - setupDefaultSystems(blueprint) - setup(blueprint.setupFunctions) - setup(blueprint.rules) - setup(blueprint.soundMapping) - } - - private func setUpIfParticipant() { - guard let networkPlayableInfo = blueprint.networkPlayableInfo, - networkPlayableInfo.role == .participant else { - return - } - setupDefaultListeners() - setupMultiplayerGameLoop() - setup(blueprint.setupFunctions) - setup(blueprint.soundMapping) - - let networkService = ArkNetworkService(serviceName: networkPlayableInfo.roomName) - self.participantSubscriber = ArkParticipantNetworkSubscriber(subscribeTo: networkService) - self.participantSubscriber?.localState = self.arkState - self.participantSubscriber?.localGameLoop = self.gameLoop - - let participantPublisher = ArkParticipantNetworkPublisher(publishTo: networkService) - self.arkState.eventManager.networkPublisherDelegate = participantPublisher - - preserveSelectComponentsIfParticipant() - } - - private func preserveSelectComponentsIfParticipant() { - // Retrieve all entities - let allEntities = arkState.arkECS.getEntities() - - // Filter and preserve entities with renderable components at the screen layer - var preservedEntityToRenderableComponent: [Entity: [any RenderableComponent]] = [:] - for entity in allEntities { - preserveRenderableComponentsAtScreenLayer(for: entity, in: &preservedEntityToRenderableComponent) - } - - // Remove all other entities - let preservedEntities = Array(preservedEntityToRenderableComponent.keys) - arkState.arkECS.removeAllEntities(except: preservedEntities) - - // Bulk upsert the preserved entities and their components - var preservedComponentMapping: [EntityID: [any Component]] = [:] - preservedEntityToRenderableComponent.forEach { entity, componentList in - preservedComponentMapping[entity.id] = componentList - } - arkState.arkECS.bulkUpsert(entities: preservedEntities, components: preservedComponentMapping) - } - - private func preserveRenderableComponentsAtScreenLayer( - for entity: Entity, - in preservedEntityToRenderableComponent: inout [Entity: [any RenderableComponent]] - ) { - for renderableComponentType in ArkCanvasSystem.renderableComponentTypes { - if let renderableComponent = arkState.arkECS.getComponent(ofType: renderableComponentType, for: entity), - renderableComponent.renderLayer == .screen { - if preservedEntityToRenderableComponent[entity] == nil { - preservedEntityToRenderableComponent[entity] = [] - } - preservedEntityToRenderableComponent[entity]?.append(renderableComponent) - } - } - } - - private func setUpIfHost() { - guard let networkPlayableInfo = blueprint.networkPlayableInfo, - networkPlayableInfo.role == .host else { - return - } - let networkService = ArkNetworkService(serviceName: networkPlayableInfo.roomName) - let publisher = ArkHostNetworkPublisher(publishTo: networkService) - self.arkState.arkECS.addSystem(ArkHostSystem(publisher: publisher)) - - self.hostSubscriber = ArkHostNetworkSubscriber(subscribeTo: networkService) - self.hostSubscriber?.localState = self.arkState - } - - private func setupDefaultListeners() { - arkState.eventManager.subscribe(to: ScreenResizeEvent.self) { [weak self] event in - guard let resizeEvent = event as? ScreenResizeEvent, - let self = self else { - return - } - self.displayContext.updateScreenSize(resizeEvent.eventData.newSize) - } - - arkState.eventManager.subscribe(to: PauseGameLoopEvent.self) { [weak self] event in - guard let _ = event as? PauseGameLoopEvent, - let self = self else { - return - } - self.gameLoop?.pauseLoop() - } - - arkState.eventManager.subscribe(to: ResumeGameLoopEvent.self) { [weak self] event in - guard let _ = event as? ResumeGameLoopEvent, - let self = self else { - return - } - self.gameLoop?.resumeLoop() - - } - - arkState.eventManager.subscribe(to: TerminateGameLoopEvent.self) { [weak self] event in - guard let _ = event as? TerminateGameLoopEvent, - let self = self else { - return - } - self.gameLoop?.shutDown() - } - } - - private func setup(_ rules: [any Rule]) { - // filter for event-based rules only - let eventRules: [any Rule] = rules.filter { rule in - rule.trigger is RuleEventType - }.map { rule in - guard let eventRule = rule as? any Rule else { - fatalError("[Ark.setup(rules)] map failed: Unexpected type in array") - } - return eventRule - } - // sort the rules by priority before adding to eventContext - let sortedRules = eventRules.sorted(by: { x, y in - if x.trigger == y.trigger { - return x.action.priority < y.action.priority - } - return true - }) - // subscribe all rules to the eventManager - for rule in sortedRules { - arkState.eventManager.subscribe(to: rule.trigger.eventType) { event in - let areConditionsSatisfied = rule.conditions - .allSatisfy { $0(self.actionContext.ecs) } - if areConditionsSatisfied { - event.executeAction(rule.action, context: self.actionContext) - } - } - } - - // filter for time-based rules only - let timeRules: [any Rule] = rules.filter { rule in - guard let trigger = rule.trigger as? RuleTrigger else { - return false - } - return trigger == RuleTrigger.updateSystem - }.map { rule in - guard let timeRule = rule as? any Rule else { - fatalError("[Ark.setup(rules)] map failed: Unexpected type in array") - } - return timeRule - } - - for rule in timeRules { - guard let action = rule.action as? any Action else { - continue - } - let system = ArkUpdateSystem(action: action, context: self.actionContext) - arkState.arkECS.addSystem(system, schedule: .update, isUnique: false) - } - } - - private func setup(_ stateSetupFunctions: [ArkStateSetupDelegate]) { - for stateSetupFunction in stateSetupFunctions { - arkState.setup(stateSetupFunction, displayContext: displayContext) - } - } - - private func setup(_ soundMapping: [ExternalResources.AudioEnum: any Sound]?) { - guard let soundMapping = soundMapping else { - return - } - - audioContext.load(soundMapping) - } - - private func setupDefaultEntities() { - arkState.arkECS.createEntity(with: [StopWatchComponent(name: ArkTimeSystem.ARK_WORLD_TIME)]) - } - - private func setupDefaultSystems(_ blueprint: ArkBlueprint) { - let (worldWidth, worldHeight) = getWorldSize(blueprint) - - let simulator = SKSimulator(size: CGSize(width: worldWidth, height: worldHeight)) - self.gameLoop = simulator - let physicsSystem = ArkPhysicsSystem(simulator: simulator, - eventManager: arkState.eventManager, - arkECS: arkState.arkECS) - let animationSystem = ArkAnimationSystem() - let canvasSystem = ArkCanvasSystem() - let timeSystem = ArkTimeSystem() - let cameraSystem = ArkCameraSystem() - arkState.arkECS.addSystem(timeSystem) - arkState.arkECS.addSystem(physicsSystem) - arkState.arkECS.addSystem(animationSystem) - arkState.arkECS.addSystem(canvasSystem) - arkState.arkECS.addSystem(cameraSystem) - - // inject dependency into game loop - simulator.physicsScene?.sceneContactUpdateDelegate = physicsSystem - simulator.physicsScene?.sceneUpdateLoopDelegate = physicsSystem - self.gameLoop?.updatePhysicsSceneDelegate = physicsSystem - } - - func setupMultiplayerGameLoop() { - guard let networkPlayableInfo = blueprint.networkPlayableInfo, - networkPlayableInfo.role == .participant else { - return - } - let gameLoop = ArkMultiplayerGameLoop() - self.gameLoop = gameLoop - } - - private func getWorldSize(_ blueprint: ArkBlueprint) -> (width: Double, height: Double) { - guard let worldEntity = arkState.arkECS.getEntities(with: [WorldComponent.self]).first, - let worldComponent = arkState.arkECS - .getComponent(ofType: WorldComponent.self, for: worldEntity) - else { - return (blueprint.frameWidth, blueprint.frameHeight) - } - return (worldComponent.width, worldComponent.height) - } - - private func alignCamera() { - let cameraEntities = arkState.arkECS.getEntities(with: [PlacedCameraComponent.self]) - if !cameraEntities.isEmpty { - return - } - arkState.arkECS.createEntity(with: [PlacedCameraComponent( - camera: Camera( - canvasPosition: CGPoint( - x: displayContext.canvasSize.width / 2, - y: displayContext.canvasSize.height / 2 - ), - zoom: 1.0 - ), - screenPosition: CGPoint( - x: displayContext.screenSize.width / 2, - y: displayContext.screenSize.height / 2 - ), - size: displayContext.screenSize) - ]) - } -} - -extension ArkEvent { - /// A workaround to prevent weird behavior when trying to execute - /// `action.execute(event, context: context)` - func executeAction(_ action: some Action, - context: ArkActionContext) { - guard let castedAction = action as? any Action else { - return - } - - castedAction.execute(self, context: context) - } -} diff --git a/ArkKit/ArkBlueprint.swift b/ArkKit/ArkBlueprint.swift index 6ba1e63..84dc721 100644 --- a/ArkKit/ArkBlueprint.swift +++ b/ArkKit/ArkBlueprint.swift @@ -102,15 +102,3 @@ struct ArkBlueprint { return newSelf } } - -struct ArkNetworkPlayableInfo { - let roomName: String - let numberOfPlayers: Int - let role: ArkPeerRole? - - init(roomName: String, numberOfPlayers: Int, role: ArkPeerRole? = nil) { - self.roomName = roomName - self.numberOfPlayers = numberOfPlayers - self.role = role - } -} diff --git a/ArkKit/app/Ark.swift b/ArkKit/app/Ark.swift new file mode 100644 index 0000000..6f77803 --- /dev/null +++ b/ArkKit/app/Ark.swift @@ -0,0 +1,106 @@ +import Foundation + +/** + * `Ark` describes the game as is **loaded**. + * + * It loads the various contexts from the `ArkBlueprint` provided and the `GameLoop`. + * `Ark` requires a `rootView: AbstractRootView` to render the game. + * + * `Ark.start()` starts a loaded version of the game by injecting the game context dependencies. + * + * User of the `Ark` instance should ensure that the `arkInstance` is **binded** (strongly referenced), otherwise events + * relying on the `arkInstance` will not emit. + */ +class Ark: ArkProtocol { + let rootView: any AbstractRootView + var arkState: ArkState + var gameLoop: GameLoop? + + let blueprint: ArkBlueprint + let audioContext: any AudioContext + var displayContext: DisplayContext + + var actionContext: ArkActionContext { + ArkActionContext(ecs: arkState.arkECS, + events: arkState.eventManager, + display: displayContext, + audio: audioContext) + } + + var canvasRenderableBuilder: (any RenderableBuilder)? + + // network dependencies + var participantSubscriber: ArkParticipantNetworkSubscriber? + var hostSubscriber: ArkHostNetworkSubscriber? + + init(rootView: any AbstractRootView, + blueprint: ArkBlueprint, + canvasRenderableBuilder: (any RenderableBuilder)? = nil) { + self.rootView = rootView + self.blueprint = blueprint + + let eventManager = ArkEventManager() + let ecsManager = ArkECS() + self.arkState = ArkState(eventManager: eventManager, arkECS: ecsManager) + + self.audioContext = ArkAudioContext() + self.canvasRenderableBuilder = canvasRenderableBuilder + self.displayContext = ArkDisplayContext( + canvasSize: CGSize( + width: blueprint.frameWidth, + height: blueprint.frameHeight + ), + screenSize: rootView.size + ) + } + + func start() { + ArkSetUpOrchestrator(ark: self).executeSetUp() + alignCamera() + + guard let gameLoop = self.gameLoop else { + return + } + + let gameCoordinator = ArkGameCoordinator(rootView: rootView, + arkState: arkState, + displayContext: displayContext, + gameLoop: gameLoop, + canvasRenderer: canvasRenderableBuilder) + gameCoordinator.start() + } + + private func alignCamera() { + let cameraEntities = arkState.arkECS.getEntities(with: [PlacedCameraComponent.self]) + if !cameraEntities.isEmpty { + return + } + arkState.arkECS.createEntity(with: [PlacedCameraComponent( + camera: Camera( + canvasPosition: CGPoint( + x: displayContext.canvasSize.width / 2, + y: displayContext.canvasSize.height / 2 + ), + zoom: 1.0 + ), + screenPosition: CGPoint( + x: displayContext.screenSize.width / 2, + y: displayContext.screenSize.height / 2 + ), + size: displayContext.screenSize) + ]) + } +} + +extension ArkEvent { + /// A workaround to prevent weird behavior when trying to execute + /// `action.execute(event, context: context)` + func executeAction(_ action: some Action, + context: ArkActionContext) { + guard let castedAction = action as? any Action else { + return + } + + castedAction.execute(self, context: context) + } +} diff --git a/ArkKit/ArkProtocol.swift b/ArkKit/app/ArkProtocol.swift similarity index 100% rename from ArkKit/ArkProtocol.swift rename to ArkKit/app/ArkProtocol.swift diff --git a/ArkKit/app/utils/network/ArkNetworkPlayableInfo.swift b/ArkKit/app/utils/network/ArkNetworkPlayableInfo.swift new file mode 100644 index 0000000..2490e0f --- /dev/null +++ b/ArkKit/app/utils/network/ArkNetworkPlayableInfo.swift @@ -0,0 +1,11 @@ +struct ArkNetworkPlayableInfo { + let roomName: String + let numberOfPlayers: Int + let role: ArkPeerRole? + + init(roomName: String, numberOfPlayers: Int, role: ArkPeerRole? = nil) { + self.roomName = roomName + self.numberOfPlayers = numberOfPlayers + self.role = role + } +} diff --git a/ArkKit/app/utils/set-up/ArkSetUpIfHostStrategy.swift b/ArkKit/app/utils/set-up/ArkSetUpIfHostStrategy.swift new file mode 100644 index 0000000..68be777 --- /dev/null +++ b/ArkKit/app/utils/set-up/ArkSetUpIfHostStrategy.swift @@ -0,0 +1,20 @@ +struct ArkSetUpIfHostStrategy: ArkSetUpStrategy { + weak var ark: Ark? + + func setUp() { + let startingSetUpStrategy = ArkSetUpWithoutNetwork(ark: ark) + startingSetUpStrategy.setUp() + + guard let ark = ark, + let networkPlayableInfo = ark.blueprint.networkPlayableInfo, + networkPlayableInfo.role == .host else { + return + } + let networkService = ArkNetworkService(serviceName: networkPlayableInfo.roomName) + let publisher = ArkHostNetworkPublisher(publishTo: networkService) + ark.arkState.arkECS.addSystem(ArkHostSystem(publisher: publisher)) + + ark.hostSubscriber = ArkHostNetworkSubscriber(subscribeTo: networkService) + ark.hostSubscriber?.localState = ark.arkState + } +} diff --git a/ArkKit/app/utils/set-up/ArkSetUpIfParticipantStrategy.swift b/ArkKit/app/utils/set-up/ArkSetUpIfParticipantStrategy.swift new file mode 100644 index 0000000..b84c389 --- /dev/null +++ b/ArkKit/app/utils/set-up/ArkSetUpIfParticipantStrategy.swift @@ -0,0 +1,62 @@ +struct ArkSetUpIfParticipantStrategy: ArkSetUpStrategy { + weak var ark: Ark? + + func setUp() { + guard let ark = ark, + let networkPlayableInfo = ark.blueprint.networkPlayableInfo, + networkPlayableInfo.role == .participant else { + return + } + setupDefaultListeners() + setupMultiplayerGameLoop() + setup(ark.blueprint.setupFunctions) + setup(ark.blueprint.soundMapping) + + let networkService = ArkNetworkService(serviceName: networkPlayableInfo.roomName) + ark.participantSubscriber = ArkParticipantNetworkSubscriber(subscribeTo: networkService) + ark.participantSubscriber?.localState = ark.arkState + ark.participantSubscriber?.localGameLoop = ark.gameLoop + + let participantPublisher = ArkParticipantNetworkPublisher(publishTo: networkService) + ark.arkState.eventManager.networkPublisherDelegate = participantPublisher + + preserveSelectComponentsIfParticipant() + } + + private func preserveSelectComponentsIfParticipant() { + // Retrieve all entities + let allEntities = ark?.arkState.arkECS.getEntities() ?? [] + + // Filter and preserve entities with renderable components at the screen layer + var preservedEntityToRenderableComponent: [Entity: [any RenderableComponent]] = [:] + for entity in allEntities { + preserveRenderableComponentsAtScreenLayer(for: entity, in: &preservedEntityToRenderableComponent) + } + + // Remove all other entities + let preservedEntities = Array(preservedEntityToRenderableComponent.keys) + ark?.arkState.arkECS.removeAllEntities(except: preservedEntities) + + // Bulk upsert the preserved entities and their components + var preservedComponentMapping: [EntityID: [any Component]] = [:] + preservedEntityToRenderableComponent.forEach { entity, componentList in + preservedComponentMapping[entity.id] = componentList + } + ark?.arkState.arkECS.bulkUpsert(entities: preservedEntities, components: preservedComponentMapping) + } + + private func preserveRenderableComponentsAtScreenLayer( + for entity: Entity, + in preservedEntityToRenderableComponent: inout [Entity: [any RenderableComponent]] + ) { + for renderableComponentType in ArkCanvasSystem.renderableComponentTypes { + if let renderableComponent = ark?.arkState.arkECS.getComponent(ofType: renderableComponentType, for: entity), + renderableComponent.renderLayer == .screen { + if preservedEntityToRenderableComponent[entity] == nil { + preservedEntityToRenderableComponent[entity] = [] + } + preservedEntityToRenderableComponent[entity]?.append(renderableComponent) + } + } + } +} diff --git a/ArkKit/app/utils/set-up/ArkSetUpOrchestrator.swift b/ArkKit/app/utils/set-up/ArkSetUpOrchestrator.swift new file mode 100644 index 0000000..9f0d581 --- /dev/null +++ b/ArkKit/app/utils/set-up/ArkSetUpOrchestrator.swift @@ -0,0 +1,17 @@ +struct ArkSetUpOrchestrator { + weak var ark: Ark? + + func executeSetUp() { + guard let networkPlayableInfo = ark?.blueprint.networkPlayableInfo else { + // no network playable set + let noNetworkSetUpStrategy = ArkSetUpWithoutNetwork(ark: ark) + noNetworkSetUpStrategy.setUp() + return + } + if networkPlayableInfo.role == .host { + ArkSetUpIfHostStrategy(ark: ark).setUp() + } else { + ArkSetUpIfParticipantStrategy(ark: ark).setUp() + } + } +} diff --git a/ArkKit/app/utils/set-up/ArkSetUpStrategy.swift b/ArkKit/app/utils/set-up/ArkSetUpStrategy.swift new file mode 100644 index 0000000..edc4546 --- /dev/null +++ b/ArkKit/app/utils/set-up/ArkSetUpStrategy.swift @@ -0,0 +1,174 @@ +import Foundation + +protocol ArkSetUpStrategy { + associatedtype View + associatedtype ExternalResources: ArkExternalResources + var ark: Ark? { get set } + + func setUp() +} + +extension ArkSetUpStrategy { + func setupDefaultListeners() { + guard let ark = ark else { + return + } + + let arkState = ark.arkState + + arkState.eventManager.subscribe(to: ScreenResizeEvent.self) { event in + guard let resizeEvent = event as? ScreenResizeEvent else { + return + } + ark.displayContext.updateScreenSize(resizeEvent.eventData.newSize) + } + + arkState.eventManager.subscribe(to: PauseGameLoopEvent.self) { event in + guard let _ = event as? PauseGameLoopEvent else { + return + } + ark.gameLoop?.pauseLoop() + } + + arkState.eventManager.subscribe(to: ResumeGameLoopEvent.self) { event in + guard let _ = event as? ResumeGameLoopEvent else { + return + } + ark.gameLoop?.resumeLoop() + + } + + arkState.eventManager.subscribe(to: TerminateGameLoopEvent.self) { event in + guard let _ = event as? TerminateGameLoopEvent else { + return + } + ark.gameLoop?.shutDown() + } + } + + func setup(_ rules: [any Rule]) { + guard let ark = ark else { + return + } + + // filter for event-based rules only + let eventRules: [any Rule] = rules.filter { rule in + rule.trigger is RuleEventType + }.map { rule in + guard let eventRule = rule as? any Rule else { + fatalError("[Ark.setup(rules)] map failed: Unexpected type in array") + } + return eventRule + } + // sort the rules by priority before adding to eventContext + let sortedRules = eventRules.sorted(by: { x, y in + if x.trigger == y.trigger { + return x.action.priority < y.action.priority + } + return true + }) + // subscribe all rules to the eventManager + for rule in sortedRules { + ark.arkState.eventManager.subscribe(to: rule.trigger.eventType) { event in + let areConditionsSatisfied = rule.conditions + .allSatisfy { $0(ark.actionContext.ecs) } + if areConditionsSatisfied { + event.executeAction(rule.action, context: ark.actionContext) + } + } + } + + // filter for time-based rules only + let timeRules: [any Rule] = rules.filter { rule in + guard let trigger = rule.trigger as? RuleTrigger else { + return false + } + return trigger == RuleTrigger.updateSystem + }.map { rule in + guard let timeRule = rule as? any Rule else { + fatalError("[Ark.setup(rules)] map failed: Unexpected type in array") + } + return timeRule + } + + for rule in timeRules { + guard let action = rule.action as? any Action else { + continue + } + let system = ArkUpdateSystem(action: action, context: ark.actionContext) + ark.arkState.arkECS.addSystem(system, schedule: .update, isUnique: false) + } + } + + func setup(_ stateSetupFunctions: [ArkStateSetupDelegate]) { + guard let ark = ark else { + return + } + + for stateSetupFunction in stateSetupFunctions { + ark.arkState.setup(stateSetupFunction, displayContext: ark.displayContext) + } + } + + func setup(_ soundMapping: [ExternalResources.AudioEnum: any Sound]?) { + guard let soundMapping = soundMapping, + let ark = ark else { + return + } + + ark.audioContext.load(soundMapping) + } + + func setupDefaultEntities() { + ark?.arkState.arkECS.createEntity(with: [StopWatchComponent(name: ArkTimeSystem.ARK_WORLD_TIME)]) + } + + func setupDefaultSystems(_ blueprint: ArkBlueprint) { + guard let ark = ark else { + return + } + + let (worldWidth, worldHeight) = getWorldSize(blueprint) + + let simulator = SKSimulator(size: CGSize(width: worldWidth, height: worldHeight)) + ark.gameLoop = simulator + let physicsSystem = ArkPhysicsSystem(simulator: simulator, + eventManager: ark.arkState.eventManager, + arkECS: ark.arkState.arkECS) + let animationSystem = ArkAnimationSystem() + let canvasSystem = ArkCanvasSystem() + let timeSystem = ArkTimeSystem() + let cameraSystem = ArkCameraSystem() + ark.arkState.arkECS.addSystem(timeSystem) + ark.arkState.arkECS.addSystem(physicsSystem) + ark.arkState.arkECS.addSystem(animationSystem) + ark.arkState.arkECS.addSystem(canvasSystem) + ark.arkState.arkECS.addSystem(cameraSystem) + + // inject dependency into game loop + simulator.physicsScene?.sceneContactUpdateDelegate = physicsSystem + simulator.physicsScene?.sceneUpdateLoopDelegate = physicsSystem + ark.gameLoop?.updatePhysicsSceneDelegate = physicsSystem + } + + func setupMultiplayerGameLoop() { + guard let ark = ark, + let networkPlayableInfo = ark.blueprint.networkPlayableInfo, + networkPlayableInfo.role == .participant else { + return + } + let gameLoop = ArkMultiplayerGameLoop() + ark.gameLoop = gameLoop + } + + func getWorldSize(_ blueprint: ArkBlueprint) -> (width: Double, height: Double) { + guard let ark = ark, + let worldEntity = ark.arkState.arkECS.getEntities(with: [WorldComponent.self]).first, + let worldComponent = ark.arkState.arkECS + .getComponent(ofType: WorldComponent.self, for: worldEntity) + else { + return (blueprint.frameWidth, blueprint.frameHeight) + } + return (worldComponent.width, worldComponent.height) + } +} diff --git a/ArkKit/app/utils/set-up/ArkSetUpWithoutNetworkStrategy.swift b/ArkKit/app/utils/set-up/ArkSetUpWithoutNetworkStrategy.swift new file mode 100644 index 0000000..1888a71 --- /dev/null +++ b/ArkKit/app/utils/set-up/ArkSetUpWithoutNetworkStrategy.swift @@ -0,0 +1,16 @@ +struct ArkSetUpWithoutNetwork: ArkSetUpStrategy { + weak var ark: Ark? + + func setUp() { + guard let ark = ark, + ark.blueprint.networkPlayableInfo?.role != .participant else { + return + } + setupDefaultEntities() + setupDefaultListeners() + setupDefaultSystems(ark.blueprint) + setup(ark.blueprint.setupFunctions) + setup(ark.blueprint.rules) + setup(ark.blueprint.soundMapping) + } +} From 8a00b55535022d5747c8ef345838160125bbffa0 Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 21:55:39 +0800 Subject: [PATCH 37/43] [ark-render] chore: move canvas items to canvas folder --- ArkKit.xcodeproj/project.pbxproj | 16 ++++++++++++++-- ArkKit/ark-camera-kit/CameraContext.swift | 2 +- .../ArkCompositeCanvas.swift} | 16 +--------------- ArkKit/ark-render-kit/canvas/ArkFlatCanvas.swift | 13 +++++++++++++ ArkKit/ark-render-kit/{ => canvas}/Canvas.swift | 0 5 files changed, 29 insertions(+), 18 deletions(-) rename ArkKit/ark-render-kit/{ArkFlatCanvas.swift => canvas/ArkCompositeCanvas.swift} (74%) create mode 100644 ArkKit/ark-render-kit/canvas/ArkFlatCanvas.swift rename ArkKit/ark-render-kit/{ => canvas}/Canvas.swift (100%) diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 768f646..47a71c4 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ 02B3C63F2BCD6639002331A0 /* ArkSetUpIfParticipantStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C63E2BCD6639002331A0 /* ArkSetUpIfParticipantStrategy.swift */; }; 02B3C6412BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6402BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift */; }; 02B3C6462BCD6692002331A0 /* ArkNetworkPlayableInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6452BCD6692002331A0 /* ArkNetworkPlayableInfo.swift */; }; + 02B3C64A2BCD692B002331A0 /* ArkCompositeCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6492BCD692B002331A0 /* ArkCompositeCanvas.swift */; }; 02C394DC2BA402060075F1CA /* GameStateRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DB2BA402060075F1CA /* GameStateRenderer.swift */; }; 02C394DE2BA4053E0075F1CA /* RenderableBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DD2BA4053E0075F1CA /* RenderableBuilder.swift */; }; 02C394E02BA405C10075F1CA /* Canvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DF2BA405C10075F1CA /* Canvas.swift */; }; @@ -331,6 +332,7 @@ 02B3C63E2BCD6639002331A0 /* ArkSetUpIfParticipantStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSetUpIfParticipantStrategy.swift; sourceTree = ""; }; 02B3C6402BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSetUpIfHostStrategy.swift; sourceTree = ""; }; 02B3C6452BCD6692002331A0 /* ArkNetworkPlayableInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkPlayableInfo.swift; sourceTree = ""; }; + 02B3C6492BCD692B002331A0 /* ArkCompositeCanvas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkCompositeCanvas.swift; sourceTree = ""; }; 02C394DB2BA402060075F1CA /* GameStateRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStateRenderer.swift; sourceTree = ""; }; 02C394DD2BA4053E0075F1CA /* RenderableBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderableBuilder.swift; sourceTree = ""; }; 02C394DF2BA405C10075F1CA /* Canvas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Canvas.swift; sourceTree = ""; }; @@ -772,18 +774,27 @@ path = utils; sourceTree = ""; }; + 02B3C6482BCD691B002331A0 /* canvas */ = { + isa = PBXGroup; + children = ( + 02C394DF2BA405C10075F1CA /* Canvas.swift */, + 02C3952B2BA897890075F1CA /* ArkFlatCanvas.swift */, + 02B3C6492BCD692B002331A0 /* ArkCompositeCanvas.swift */, + ); + path = canvas; + sourceTree = ""; + }; 02C394DA2BA401F00075F1CA /* ark-render-kit */ = { isa = PBXGroup; children = ( + 02B3C6482BCD691B002331A0 /* canvas */, 945F7F8A2BA8850200933629 /* abstract */, 02C395132BA83F990075F1CA /* renderable-components */, 8F5573BD2BA4258F007030C8 /* renderable */, 02C394DB2BA402060075F1CA /* GameStateRenderer.swift */, - 02C394DF2BA405C10075F1CA /* Canvas.swift */, 02C394DD2BA4053E0075F1CA /* RenderableBuilder.swift */, 02C395252BA890470075F1CA /* ArkCanvasSystem.swift */, 02C395292BA8952F0075F1CA /* CanvasContext.swift */, - 02C3952B2BA897890075F1CA /* ArkFlatCanvas.swift */, 8FEB21772BADE30F00788E20 /* RenderLayer.swift */, 8FEB21792BADE60300788E20 /* DisplayContext.swift */, ); @@ -1911,6 +1922,7 @@ 02E0E8F22BA282C20043E2BA /* UIKitCircle.swift in Sources */, 02D8E94C2BAC99FC00BF3A07 /* GameLoopable.swift in Sources */, 280CD3D02BA74E6000372C5D /* AbstractArkPhysicsScene.swift in Sources */, + 02B3C64A2BCD692B002331A0 /* ArkCompositeCanvas.swift in Sources */, AD260CA82BBFCC7E008B9654 /* EntityIDGenerator.swift in Sources */, 02C394E62BA41AC60075F1CA /* ArkGameModel.swift in Sources */, AD36A75B2BAC331F003E938B /* TankComponent.swift in Sources */, diff --git a/ArkKit/ark-camera-kit/CameraContext.swift b/ArkKit/ark-camera-kit/CameraContext.swift index f0cab63..4098d3c 100644 --- a/ArkKit/ark-camera-kit/CameraContext.swift +++ b/ArkKit/ark-camera-kit/CameraContext.swift @@ -28,7 +28,7 @@ class ArkCameraContext: CameraContext { return canvas } - var transformedCanvas = ArkMegaCanvas(screenElements: filterForScreenComponents(canvas)) + var transformedCanvas = ArkCompositeCanvas(screenElements: filterForScreenComponents(canvas)) for cameraEntity in cameras { guard let cameraContainerComp = ecs.getComponent( diff --git a/ArkKit/ark-render-kit/ArkFlatCanvas.swift b/ArkKit/ark-render-kit/canvas/ArkCompositeCanvas.swift similarity index 74% rename from ArkKit/ark-render-kit/ArkFlatCanvas.swift rename to ArkKit/ark-render-kit/canvas/ArkCompositeCanvas.swift index 51ac38b..b3184f3 100644 --- a/ArkKit/ark-render-kit/ArkFlatCanvas.swift +++ b/ArkKit/ark-render-kit/canvas/ArkCompositeCanvas.swift @@ -1,18 +1,4 @@ -struct ArkFlatCanvas: Canvas { - private(set) var canvasElements: [EntityID: [RenderableComponentType: any RenderableComponent]] = [:] - - mutating func addEntityRenderableToCanvas(entityId: EntityID, - componentType: RenderableComponentType, - renderableComponent: any RenderableComponent) { - if canvasElements[entityId] != nil { - canvasElements[entityId]?[componentType] = renderableComponent - } else { - canvasElements[entityId] = [componentType: renderableComponent] - } - } -} - -struct ArkMegaCanvas: Canvas { +struct ArkCompositeCanvas: Canvas { var canvasElements: [EntityID: [RenderableComponentType: any RenderableComponent]] { // merge cameraElements and screenElements var mergedElements: [EntityID: [RenderableComponentType: any RenderableComponent]] = [:] diff --git a/ArkKit/ark-render-kit/canvas/ArkFlatCanvas.swift b/ArkKit/ark-render-kit/canvas/ArkFlatCanvas.swift new file mode 100644 index 0000000..36503eb --- /dev/null +++ b/ArkKit/ark-render-kit/canvas/ArkFlatCanvas.swift @@ -0,0 +1,13 @@ +struct ArkFlatCanvas: Canvas { + private(set) var canvasElements: [EntityID: [RenderableComponentType: any RenderableComponent]] = [:] + + mutating func addEntityRenderableToCanvas(entityId: EntityID, + componentType: RenderableComponentType, + renderableComponent: any RenderableComponent) { + if canvasElements[entityId] != nil { + canvasElements[entityId]?[componentType] = renderableComponent + } else { + canvasElements[entityId] = [componentType: renderableComponent] + } + } +} diff --git a/ArkKit/ark-render-kit/Canvas.swift b/ArkKit/ark-render-kit/canvas/Canvas.swift similarity index 100% rename from ArkKit/ark-render-kit/Canvas.swift rename to ArkKit/ark-render-kit/canvas/Canvas.swift From 2b44ce93f64df5be5d8d080261d98cbc972b5962 Mon Sep 17 00:00:00 2001 From: Didymus Date: Mon, 15 Apr 2024 23:57:48 +0800 Subject: [PATCH 38/43] [ark-multiplayer] feat: add player set up delegate --- .../ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift diff --git a/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift b/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift new file mode 100644 index 0000000..4091e0c --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift @@ -0,0 +1,8 @@ +// +// ArkPlayerStateSetupDelegate.swift +// ArkKit +// +// Created by Didymus Ne on 15/4/24. +// + +import Foundation From 3d86010cb1888c972122d28c3ce9e7a330a8144e Mon Sep 17 00:00:00 2001 From: Didymus Date: Tue, 16 Apr 2024 00:00:21 +0800 Subject: [PATCH 39/43] [ark-multipalyer] feat: add serializer for peer to player mapping --- .../ArkPlayerStateSetupDelegate.swift | 11 +++-------- .../ArkPeerToPlayerIdSerializer.swift | 18 ++++++++++++++++++ .../data-serialize/DataWrapper.swift | 2 +- 3 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 ArkKit/ark-multiplayer-kit/data-serialize/ArkPeerToPlayerIdSerializer.swift diff --git a/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift b/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift index 4091e0c..8406c3f 100644 --- a/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift +++ b/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift @@ -1,8 +1,3 @@ -// -// ArkPlayerStateSetupDelegate.swift -// ArkKit -// -// Created by Didymus Ne on 15/4/24. -// - -import Foundation +protocol ArkPlayerStateSetupDelegate: AnyObject { + func setup(_ playerId: Int) +} diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/ArkPeerToPlayerIdSerializer.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ArkPeerToPlayerIdSerializer.swift new file mode 100644 index 0000000..0bae82e --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ArkPeerToPlayerIdSerializer.swift @@ -0,0 +1,18 @@ +import Foundation + +class ArkPeerToPlayerIdSerializer { + typealias PeerToPlayerIdMapping = [String: Int] + + static func encodeMapping(_ peerToIdMapping: PeerToPlayerIdMapping) throws -> Data { + let encoder = JSONEncoder() + let payload = try encoder.encode(peerToIdMapping) + let wrappedData = DataWrapper(type: .playerMapping, name: "PeerToPlayerIdMapping", payload: payload) + return try encoder.encode(wrappedData) + } + + static func decodeMapping(from data: Data) throws -> PeerToPlayerIdMapping { + let decoder = JSONDecoder() + let mappingWrapper = try decoder.decode(PeerToPlayerIdMapping.self, from: data) + return mappingWrapper + } +} diff --git a/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift b/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift index 3f0e120..c98cb9b 100644 --- a/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift +++ b/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift @@ -1,7 +1,7 @@ import Foundation enum PayloadType: String, Codable { - case event, ecs + case event, ecs, playerMapping } struct DataWrapper: Codable { From 473c4cdbeb507bdf4263748237820be796b0eea9 Mon Sep 17 00:00:00 2001 From: Didymus Date: Tue, 16 Apr 2024 00:01:04 +0800 Subject: [PATCH 40/43] [ark-multiplayer] feat: add playerStateSetupDelegate to host publisher and participant subscriber --- .../publisher/ArkHostNetworkPublisher.swift | 27 ++++++++++++++++++- .../ArkParticipantNetworkSubscriber.swift | 11 ++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift b/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift index 84ce370..3ec4150 100644 --- a/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift +++ b/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift @@ -2,10 +2,16 @@ class ArkHostNetworkPublisher: ArkNetworkPublisherDelegate { // network related dependencies var networkService: AbstractNetworkService private var peers = [String]() + private(set) var peerInfoToPlayerIdMap: [String: Int] = [:] - init(publishTo networkService: AbstractNetworkService) { + init(publishTo networkService: AbstractNetworkService, + playerStateSetUpDelegate: ArkPlayerStateSetupDelegate? = nil) { self.networkService = networkService self.networkService.publisher = self + + // Host will always be player 0 + self.peerInfoToPlayerIdMap[networkService.deviceID] = 0 + playerStateSetUpDelegate?.setup(0) } func publish(ecs: ArkECS) { @@ -25,5 +31,24 @@ class ArkHostNetworkPublisher: ArkNetworkPublisherDelegate { func onChangeInObservers(manager: ArkNetworkService, connectedDevices: [String]) { // registers listeners to publish to peers = connectedDevices + + // if a peer is not in, assign to new playerId + guard var maxPlayerId = peerInfoToPlayerIdMap.values.compactMap({ $0 }).max() else { + return + } + + for peer in peers where peerInfoToPlayerIdMap[peer] == nil { + peerInfoToPlayerIdMap[peer] = maxPlayerId + 1 + maxPlayerId += 1 + } + + // sendData + do { + let encodedPeerToPlayerMapping = try ArkPeerToPlayerIdSerializer.encodeMapping(peerInfoToPlayerIdMap) + networkService.sendData(data: encodedPeerToPlayerMapping) + } catch { + print("Error encoding or sending peerToPlayerId mapping: \(error)") + } + } } diff --git a/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift b/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift index 89ec5a5..e81dd96 100644 --- a/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift +++ b/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift @@ -3,6 +3,7 @@ import Foundation class ArkParticipantNetworkSubscriber: ArkNetworkSubscriberDelegate { // network related dependencies var networkService: AbstractNetworkService + var playerStateSetUpDelegate: ArkPlayerStateSetupDelegate? // inject dependency weak var localState: ArkState? @@ -18,6 +19,16 @@ class ArkParticipantNetworkSubscriber: ArkNetworkSubscriberDelegate { do { let wrappedData = try JSONDecoder().decode(DataWrapper.self, from: data) + if wrappedData.type == .playerMapping { + let mappingWrapper = try ArkPeerToPlayerIdSerializer.decodeMapping(from: wrappedData.payload) + let myPeerInfo = networkService.deviceID + if let myPlayerId = mappingWrapper[myPeerInfo] { + playerStateSetUpDelegate?.setup(myPlayerId) + // unassign so that set up is only done once + playerStateSetUpDelegate = nil + } + } + if wrappedData.type == .ecs { let ecsWrapper = try ArkECSDataSerializer.decodeArkECS(from: wrappedData.payload) processECSUpdates(ecsWrapper) From 42b95c3a03e90cf5a9a80c31ced6a022bd1be9b8 Mon Sep 17 00:00:00 2001 From: Didymus Date: Tue, 16 Apr 2024 00:02:08 +0800 Subject: [PATCH 41/43] [ark-multiplayer] feat: implement player set up into set up strategy --- .../utils/set-up/ArkSetUpIfHostStrategy.swift | 21 ++++++-- .../ArkSetUpIfParticipantStrategy.swift | 53 ++++++------------- .../ArkSetUpWithoutNetworkStrategy.swift | 6 ++- 3 files changed, 37 insertions(+), 43 deletions(-) diff --git a/ArkKit/app/utils/set-up/ArkSetUpIfHostStrategy.swift b/ArkKit/app/utils/set-up/ArkSetUpIfHostStrategy.swift index 68be777..13c1def 100644 --- a/ArkKit/app/utils/set-up/ArkSetUpIfHostStrategy.swift +++ b/ArkKit/app/utils/set-up/ArkSetUpIfHostStrategy.swift @@ -1,20 +1,33 @@ -struct ArkSetUpIfHostStrategy: ArkSetUpStrategy { +class ArkSetUpIfHostStrategy: ArkSetUpStrategy { weak var ark: Ark? + init(ark: Ark? = nil) { + self.ark = ark + } + func setUp() { let startingSetUpStrategy = ArkSetUpWithoutNetwork(ark: ark) startingSetUpStrategy.setUp() - guard let ark = ark, let networkPlayableInfo = ark.blueprint.networkPlayableInfo, networkPlayableInfo.role == .host else { return } let networkService = ArkNetworkService(serviceName: networkPlayableInfo.roomName) - let publisher = ArkHostNetworkPublisher(publishTo: networkService) + let publisher = ArkHostNetworkPublisher(publishTo: networkService, playerStateSetUpDelegate: self) ark.arkState.arkECS.addSystem(ArkHostSystem(publisher: publisher)) - ark.hostSubscriber = ArkHostNetworkSubscriber(subscribeTo: networkService) ark.hostSubscriber?.localState = ark.arkState } } + +extension ArkSetUpIfHostStrategy: ArkPlayerStateSetupDelegate { + func setup(_ playerId: Int) { + let playerSetUpCallbacks = ark?.blueprint.playerSpecificSetupFunctions + guard let specificPlayerSetUp = playerSetUpCallbacks?[playerId], + let displayContext = ark?.displayContext else { + return + } + ark?.arkState.setup(specificPlayerSetUp, displayContext: displayContext) + } +} diff --git a/ArkKit/app/utils/set-up/ArkSetUpIfParticipantStrategy.swift b/ArkKit/app/utils/set-up/ArkSetUpIfParticipantStrategy.swift index b84c389..69e087d 100644 --- a/ArkKit/app/utils/set-up/ArkSetUpIfParticipantStrategy.swift +++ b/ArkKit/app/utils/set-up/ArkSetUpIfParticipantStrategy.swift @@ -1,6 +1,10 @@ -struct ArkSetUpIfParticipantStrategy: ArkSetUpStrategy { +class ArkSetUpIfParticipantStrategy: ArkSetUpStrategy { weak var ark: Ark? + init(ark: Ark? = nil) { + self.ark = ark + } + func setUp() { guard let ark = ark, let networkPlayableInfo = ark.blueprint.networkPlayableInfo, @@ -9,54 +13,27 @@ struct ArkSetUpIfParticipantStrategy: ArkSetUpStrategy { +class ArkSetUpWithoutNetwork: ArkSetUpStrategy { weak var ark: Ark? + init(ark: Ark? = nil) { + self.ark = ark + } + func setUp() { guard let ark = ark, ark.blueprint.networkPlayableInfo?.role != .participant else { From 3445cba1417e1199d40eced7701492771a2f7948 Mon Sep 17 00:00:00 2001 From: Didymus Date: Tue, 16 Apr 2024 00:02:59 +0800 Subject: [PATCH 42/43] [ark-blueprint] feat: integrate player-specific delegates into blueprint and add to tank-game --- .../games/TankGame/TankGameManager.swift | 100 ++++++++++-------- ArkKit/ArkBlueprint.swift | 16 ++- 2 files changed, 73 insertions(+), 43 deletions(-) diff --git a/ArkGameExample/games/TankGame/TankGameManager.swift b/ArkGameExample/games/TankGame/TankGameManager.swift index 6589cc2..2b5a0ee 100644 --- a/ArkGameExample/games/TankGame/TankGameManager.swift +++ b/ArkGameExample/games/TankGame/TankGameManager.swift @@ -22,9 +22,64 @@ class TankGameManager { setUpEntities() setUpSystems() setUpRules() - self.blueprint = self.blueprint.supportNetworkPlay( - roomName: "TankFightGame", numberOfPlayers: 2 - ) + self.blueprint = self.blueprint + .supportNetworkMultiPlayer( + roomName: "TankFightGame", numberOfPlayers: 2 + ) + .setupPlayer { context in + let ecs = context.ecs + let events = context.events + let screenWidth = context.display.screenSize.width + let screenHeight = context.display.screenSize.height + + let joystick1Entity = TankGameEntityCreator.createJoyStick( + center: CGPoint(x: screenWidth * 1 / 6, y: screenHeight * 7 / 8), + tankId: 1, + in: ecs, + eventContext: events, + zPosition: 999) + + let shootButton1Entity = TankGameEntityCreator.createShootButton( + with: TankShootButtonCreationContext( + position: CGPoint(x: screenWidth * 5 / 6, y: screenHeight * 7 / 8), + tankId: 1, + zPosition: 999, + rotate: false + ), + in: ecs, + eventContext: events + ) + + self.joystick1 = joystick1Entity.id + self.shootButton1 = shootButton1Entity.id + } + .setupPlayer { context in + let ecs = context.ecs + let events = context.events + let screenWidth = context.display.screenSize.width + let screenHeight = context.display.screenSize.height + + let joystick2Entity = TankGameEntityCreator.createJoyStick( + center: CGPoint(x: screenWidth * 5 / 6, y: screenHeight * 1 / 8), + tankId: 2, + in: ecs, + eventContext: events, + zPosition: 999) + + let shootButton2Entity = TankGameEntityCreator.createShootButton( + with: TankShootButtonCreationContext( + position: CGPoint(x: screenWidth * 1 / 6, y: screenHeight * 1 / 8), + tankId: 2, + zPosition: 999, + rotate: true + ), + in: ecs, + eventContext: events + ) + + self.joystick2 = joystick2Entity.id + self.shootButton2 = shootButton2Entity.id + } } func setUpAudio() { @@ -71,45 +126,6 @@ class TankGameManager { hp: 50), in: ecs) self.tankIdEntityMap[2] = tankEntity2 - - let joystick1Entity = TankGameEntityCreator.createJoyStick( - center: CGPoint(x: screenWidth * 1 / 6, y: screenHeight * 7 / 8), - tankId: 1, - in: ecs, - eventContext: events, - zPosition: 999) - let joystick2Entity = TankGameEntityCreator.createJoyStick( - center: CGPoint(x: screenWidth * 5 / 6, y: screenHeight * 1 / 8), - tankId: 2, - in: ecs, - eventContext: events, - zPosition: 999) - - let shootButton1Entity = TankGameEntityCreator.createShootButton( - with: TankShootButtonCreationContext( - position: CGPoint(x: screenWidth * 5 / 6, y: screenHeight * 7 / 8), - tankId: 1, - zPosition: 999, - rotate: false - ), - in: ecs, - eventContext: events - ) - let shootButton2Entity = TankGameEntityCreator.createShootButton( - with: TankShootButtonCreationContext( - position: CGPoint(x: screenWidth * 1 / 6, y: screenHeight * 1 / 8), - tankId: 2, - zPosition: 999, - rotate: true - ), - in: ecs, - eventContext: events - ) - - self.joystick1 = joystick1Entity.id - self.joystick2 = joystick2Entity.id - self.shootButton1 = shootButton1Entity.id - self.shootButton2 = shootButton2Entity.id } } diff --git a/ArkKit/ArkBlueprint.swift b/ArkKit/ArkBlueprint.swift index 84dc721..b9b801c 100644 --- a/ArkKit/ArkBlueprint.swift +++ b/ArkKit/ArkBlueprint.swift @@ -9,6 +9,9 @@ struct ArkBlueprint { private(set) var soundMapping: [ExternalResources.AudioEnum: any Sound]? private(set) var networkPlayableInfo: ArkNetworkPlayableInfo? + // if there is player specific setup + private(set) var playerSpecificSetupFunctions: [ArkStateSetupDelegate] = [] + // game world size private(set) var frameWidth: Double private(set) var frameHeight: Double @@ -81,7 +84,7 @@ struct ArkBlueprint { return newSelf } - func supportNetworkPlay(roomName: String, numberOfPlayers: Int) -> Self { + func supportNetworkMultiPlayer(roomName: String, numberOfPlayers: Int) -> Self { var newSelf = self newSelf.networkPlayableInfo = ArkNetworkPlayableInfo( roomName: roomName, numberOfPlayers: numberOfPlayers @@ -89,6 +92,17 @@ struct ArkBlueprint { return newSelf } + /// Only supports if Multiplayer is defined + /// Note: maybe we can move this into a multiplayerContext so dev defines specific player controls + func setupPlayer(_ fn: @escaping ArkStateSetupDelegate) -> Self { + var playerStateSetupFunctionsCopy = playerSpecificSetupFunctions + playerStateSetupFunctionsCopy.append(fn) + + var newSelf = self + newSelf.playerSpecificSetupFunctions = playerStateSetupFunctionsCopy + return newSelf + } + func setRole(_ role: ArkPeerRole) -> Self { var newSelf = self guard let originalNetworkInfo = self.networkPlayableInfo else { From 8e64eb19842fff52147ea9cff8ef694443b7e7fb Mon Sep 17 00:00:00 2001 From: Didymus Date: Tue, 16 Apr 2024 00:03:11 +0800 Subject: [PATCH 43/43] [misc] chore: update project file metadata --- ArkKit.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 47a71c4..923156d 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -54,6 +54,8 @@ 02B3C6412BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6402BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift */; }; 02B3C6462BCD6692002331A0 /* ArkNetworkPlayableInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6452BCD6692002331A0 /* ArkNetworkPlayableInfo.swift */; }; 02B3C64A2BCD692B002331A0 /* ArkCompositeCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6492BCD692B002331A0 /* ArkCompositeCanvas.swift */; }; + 02B3C64E2BCD8507002331A0 /* ArkPlayerStateSetupDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C64D2BCD8507002331A0 /* ArkPlayerStateSetupDelegate.swift */; }; + 02B3C6502BCD8635002331A0 /* ArkPeerToPlayerIdSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C64F2BCD8635002331A0 /* ArkPeerToPlayerIdSerializer.swift */; }; 02C394DC2BA402060075F1CA /* GameStateRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DB2BA402060075F1CA /* GameStateRenderer.swift */; }; 02C394DE2BA4053E0075F1CA /* RenderableBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DD2BA4053E0075F1CA /* RenderableBuilder.swift */; }; 02C394E02BA405C10075F1CA /* Canvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02C394DF2BA405C10075F1CA /* Canvas.swift */; }; @@ -333,6 +335,8 @@ 02B3C6402BCD6651002331A0 /* ArkSetUpIfHostStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkSetUpIfHostStrategy.swift; sourceTree = ""; }; 02B3C6452BCD6692002331A0 /* ArkNetworkPlayableInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkNetworkPlayableInfo.swift; sourceTree = ""; }; 02B3C6492BCD692B002331A0 /* ArkCompositeCanvas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkCompositeCanvas.swift; sourceTree = ""; }; + 02B3C64D2BCD8507002331A0 /* ArkPlayerStateSetupDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkPlayerStateSetupDelegate.swift; sourceTree = ""; }; + 02B3C64F2BCD8635002331A0 /* ArkPeerToPlayerIdSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkPeerToPlayerIdSerializer.swift; sourceTree = ""; }; 02C394DB2BA402060075F1CA /* GameStateRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameStateRenderer.swift; sourceTree = ""; }; 02C394DD2BA4053E0075F1CA /* RenderableBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderableBuilder.swift; sourceTree = ""; }; 02C394DF2BA405C10075F1CA /* Canvas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Canvas.swift; sourceTree = ""; }; @@ -1481,6 +1485,7 @@ AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */, ADA847FC2BBC4B5500B19378 /* AbstractNetworkService.swift */, 28EFCB282BCCC90B0059A908 /* ArkHostSystem.swift */, + 02B3C64D2BCD8507002331A0 /* ArkPlayerStateSetupDelegate.swift */, ); path = "ark-multiplayer-kit"; sourceTree = ""; @@ -1666,6 +1671,7 @@ ADA847FE2BBC4EA800B19378 /* ArkEventDataSerializer.swift */, ADD74C0B2BC05677008CE36B /* ArkECSDataSerializer.swift */, AD2B59B52BB958E400198E99 /* DataWrapper.swift */, + 02B3C64F2BCD8635002331A0 /* ArkPeerToPlayerIdSerializer.swift */, ); path = "data-serialize"; sourceTree = ""; @@ -1899,6 +1905,7 @@ 02C9CC322BBED5C40098E849 /* UIKitCamera.swift in Sources */, 2812FCC32BC4395D00A0FE24 /* TankRaceGameCollisionStrategyManager.swift in Sources */, 2812FCC52BC4424200A0FE24 /* TankRaceGameEntityCreator.swift in Sources */, + 02B3C64E2BCD8507002331A0 /* ArkPlayerStateSetupDelegate.swift in Sources */, 945441042BC9178400E90ECE /* SnakeGame.swift in Sources */, ADA847FD2BBC4B5500B19378 /* AbstractNetworkService.swift in Sources */, 02C3951E2BA849490075F1CA /* CircleRenderableComponent.swift in Sources */, @@ -1918,6 +1925,7 @@ AD6E03612B9F15FA00974EBF /* ArkECS.swift in Sources */, 8F5573CA2BA6A633007030C8 /* ArkAnimationSystem.swift in Sources */, 280CD3BC2BA73CF900372C5D /* ArkSKPhysicsBody.swift in Sources */, + 02B3C6502BCD8635002331A0 /* ArkPeerToPlayerIdSerializer.swift in Sources */, 287B00572BC17127002F0114 /* TankDestroyedEvent.swift in Sources */, 02E0E8F22BA282C20043E2BA /* UIKitCircle.swift in Sources */, 02D8E94C2BAC99FC00BF3A07 /* GameLoopable.swift in Sources */,