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/SceneDelegate.swift b/ArkGameExample/SceneDelegate.swift index 71025f3..e2a630d 100644 --- a/ArkGameExample/SceneDelegate.swift +++ b/ArkGameExample/SceneDelegate.swift @@ -65,6 +65,7 @@ extension SceneDelegate { return } ark = Ark(rootView: rootView, blueprint: blueprint) +// ark?.multiplayer(serviceName: "tankGame") ark?.start() } } diff --git a/ArkGameExample/games/SnakeGame/SnakeGame.swift b/ArkGameExample/games/SnakeGame/SnakeGame.swift index df4782c..1cba438 100644 --- a/ArkGameExample/games/SnakeGame/SnakeGame.swift +++ b/ArkGameExample/games/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/games/TankGame/ImpactExplosionAnimation.swift b/ArkGameExample/games/TankGame/ImpactExplosionAnimation.swift index 212c8c1..d2b07fc 100644 --- a/ArkGameExample/games/TankGame/ImpactExplosionAnimation.swift +++ b/ArkGameExample/games/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,17 +47,18 @@ struct ImpactExplosionAnimation { for: entity) ?? makeBitmapComponent( imageResourcePath: imageResourcePath) - bitmapComponent.imageResourcePath = imageResourcePath + bitmapComponent.imageResourcePath = imageResourcePath.rawValue 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/ArkGameExample/games/TankGame/TankGameEntityCreator.swift b/ArkGameExample/games/TankGame/TankGameEntityCreator.swift index 503953b..505f196 100644 --- a/ArkGameExample/games/TankGame/TankGameEntityCreator.swift +++ b/ArkGameExample/games/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/games/TankGame/TankGameManager.swift b/ArkGameExample/games/TankGame/TankGameManager.swift index 42fda48..2b5a0ee 100644 --- a/ArkGameExample/games/TankGame/TankGameManager.swift +++ b/ArkGameExample/games/TankGame/TankGameManager.swift @@ -22,6 +22,64 @@ class TankGameManager { setUpEntities() setUpSystems() setUpRules() + 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() { @@ -68,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 } } @@ -121,8 +140,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/games/TankGame/TankGameMapBuilder.swift b/ArkGameExample/games/TankGame/TankGameMapBuilder.swift index 0e10621..cbbdd52 100644 --- a/ArkGameExample/games/TankGame/TankGameMapBuilder.swift +++ b/ArkGameExample/games/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/games/TankGame/TankGameTerrainObjectBuilder.swift b/ArkGameExample/games/TankGame/TankGameTerrainObjectBuilder.swift index b0132d6..dfa8361 100644 --- a/ArkGameExample/games/TankGame/TankGameTerrainObjectBuilder.swift +++ b/ArkGameExample/games/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/games/TankRaceGame/TankRaceGameEntityCreator.swift b/ArkGameExample/games/TankRaceGame/TankRaceGameEntityCreator.swift index 2de0ec2..4c95662 100644 --- a/ArkGameExample/games/TankRaceGame/TankRaceGameEntityCreator.swift +++ b/ArkGameExample/games/TankRaceGame/TankRaceGameEntityCreator.swift @@ -112,7 +112,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) @@ -146,7 +146,7 @@ enum TankRaceGameEntityCreator { let entities = positions.map { ecsContext.createEntity(with: [ BitmapImageRenderableComponent( - imageResourcePath: TankRaceGameImages.finish_line, + arkImageResourcePath: TankRaceGameImages.finish_line, width: canvasWidth / 3, height: canvasWidth / 5 ) diff --git a/ArkGameExample/pages/ArkDemoGameHostingPage.swift b/ArkGameExample/pages/ArkDemoGameHostingPage.swift index 8f5e65b..876f497 100644 --- a/ArkGameExample/pages/ArkDemoGameHostingPage.swift +++ b/ArkGameExample/pages/ArkDemoGameHostingPage.swift @@ -3,26 +3,27 @@ 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 - } - // 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#>) -// } + if shouldShowMultiplayerOptions { + presentMultiplayerOptions() + } + } - // load blueprint - ark = Ark(rootView: self, blueprint: blueprint) + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) 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..afecf40 --- /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 startButton = UIButton(type: .system) + 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([ + startButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + 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() { + 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 87ae968..f5642aa 100644 --- a/ArkGameExample/utils/GameHostingPageFactory.swift +++ b/ArkGameExample/utils/GameHostingPageFactory.swift @@ -1,22 +1,67 @@ +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 + + // inject ark and blueprint dependencies here + if role == nil { + vc.ark = Ark(rootView: vc, blueprint: blueprint) + } else { + guard let role = role else { + return vc + } + let 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 vc: ArkDemoGameHostingPage = ArkDemoGameHostingPage() let blueprint: ArkBlueprint = TankRaceGame(rootView: vc).blueprint - vc.arkBlueprint = blueprint +// vc.arkBlueprint = blueprint + let ark = Ark(rootView: vc, blueprint: blueprint) + vc.ark = ark 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: + return true + case .SnakeChomp, .TankRaceGame: + return false + } + } } diff --git a/ArkKit.xcodeproj/project.pbxproj b/ArkKit.xcodeproj/project.pbxproj index 154cf38..7d4050c 100644 --- a/ArkKit.xcodeproj/project.pbxproj +++ b/ArkKit.xcodeproj/project.pbxproj @@ -46,6 +46,21 @@ 02B3C6222BCBE226002331A0 /* TankRaceEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6212BCBE226002331A0 /* TankRaceEventHandler.swift */; }; 02B3C6242BCBF6C1002331A0 /* TankWinEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6232BCBF6C1002331A0 /* TankWinEvent.swift */; }; 02B3C6272BCBF8F9002331A0 /* TankWinCustomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B3C6262BCBF8F9002331A0 /* TankWinCustomView.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 */; }; + 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 */; }; + 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 */; }; @@ -154,6 +169,10 @@ 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 */; }; @@ -162,6 +181,11 @@ 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 */; }; + 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 */; }; 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 */; }; @@ -215,9 +239,9 @@ 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 */; }; + 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 */; }; @@ -249,10 +273,13 @@ 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 */; }; + 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 */; }; + 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 */ @@ -305,6 +332,21 @@ 02B3C6212BCBE226002331A0 /* TankRaceEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankRaceEventHandler.swift; sourceTree = ""; }; 02B3C6232BCBF6C1002331A0 /* TankWinEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankWinEvent.swift; sourceTree = ""; }; 02B3C6262BCBF8F9002331A0 /* TankWinCustomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TankWinCustomView.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 = ""; }; + 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 = ""; }; + 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 = ""; }; @@ -413,6 +455,10 @@ 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 = ""; }; @@ -421,6 +467,10 @@ 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 = ""; }; + 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 = ""; }; 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 = ""; }; @@ -473,9 +523,9 @@ 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 = ""; }; + 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 = ""; }; @@ -510,9 +560,13 @@ 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 = ""; }; - ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArkDataSerializer.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 = ""; }; + 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 */ @@ -520,7 +574,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - ADA847F42BBC11F400B19378 /* P2PShare in Frameworks */, + 28EFCB1B2BCC36EB0059A908 /* P2PShare in Frameworks */, 9454411D2BC950DB00E90ECE /* DequeModule in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -635,6 +689,7 @@ children = ( 02B3C6072BCAEE9A002331A0 /* ArkDemoHomePage.swift */, 026882482BCACAFF00212BD6 /* ArkDemoGameHostingPage.swift */, + 28EFCB1C2BCC3C4E0059A908 /* ArkDemoMultiplayerPopover.swift */, ); path = pages; sourceTree = ""; @@ -695,18 +750,86 @@ path = CustomViews; 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 = ""; + }; + 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 = ""; + }; + 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 */, ); @@ -1074,6 +1197,17 @@ path = InternalComponents; sourceTree = ""; }; + 2882ABF72BCB77EB0042AC52 /* SendableRenderableComponents */ = { + isa = PBXGroup; + children = ( + 2882ABF82BCB78060042AC52 /* BitmapImageRenderableComponent+SendableComponent.swift */, + 2882ABFA2BCB784C0042AC52 /* RectRenderableComponent+SendableComponent.swift */, + 2882ABFC2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift */, + 2882ABFE2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift */, + ); + path = SendableRenderableComponents; + sourceTree = ""; + }; 28A032DD2BAD4ED200851BFF /* InternalSystems */ = { isa = PBXGroup; children = ( @@ -1361,15 +1495,28 @@ path = Animation; sourceTree = ""; }; + AD260CA62BBFCC6C008B9654 /* entity */ = { + isa = PBXGroup; + children = ( + AD787A812B9C6BD9003EBBD0 /* Entity.swift */, + AD260CA72BBFCC7E008B9654 /* EntityIDGenerator.swift */, + ); + path = entity; + sourceTree = ""; + }; AD2B59AA2BB87F0F00198E99 /* ark-multiplayer-kit */ = { isa = PBXGroup; children = ( + 02B3C62D2BCD19BE002331A0 /* subscriber */, + 02B3C6282BCD1977002331A0 /* publisher */, + ADD74C0A2BC0555D008CE36B /* sendable */, ADA848002BBC7DC600B19378 /* data-serialize */, - AD2B59B12BB94FBE00198E99 /* ArkMultiplayerManager.swift */, - AD2B59AF2BB94DE000198E99 /* ArkMultiplayerEventManager.swift */, + AD2B59B12BB94FBE00198E99 /* ArkPeerRole.swift */, + 28EFCB262BCCB6130059A908 /* ArkMultiplayerGameLoop.swift */, AD2B59AB2BB87F2200198E99 /* ArkNetworkService.swift */, - ADA847FC2BBC4B5500B19378 /* ArkNetworkProtocol.swift */, - AD2B59B52BB958E400198E99 /* DataWrapper.swift */, + ADA847FC2BBC4B5500B19378 /* AbstractNetworkService.swift */, + 28EFCB282BCCC90B0059A908 /* ArkHostSystem.swift */, + 02B3C64D2BCD8507002331A0 /* ArkPlayerStateSetupDelegate.swift */, ); path = "ark-multiplayer-kit"; sourceTree = ""; @@ -1432,7 +1579,7 @@ AD6E035F2B9F067100974EBF /* ark-ecs-kit */ = { isa = PBXGroup; children = ( - AD787A812B9C6BD9003EBBD0 /* Entity.swift */, + AD260CA62BBFCC6C008B9654 /* entity */, AD787A832B9C6C78003EBBD0 /* Component.swift */, 0267BA242BBD0FC70010F729 /* system */, 28A032DD2BAD4ED200851BFF /* InternalSystems */, @@ -1501,9 +1648,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 */, @@ -1550,12 +1696,27 @@ ADA848002BBC7DC600B19378 /* data-serialize */ = { isa = PBXGroup; children = ( - ADA847FE2BBC4EA800B19378 /* ArkDataSerializer.swift */, + ADD74C112BC15C52008CE36B /* ComponentRegistry.swift */, + 28EFCB242BCCA7CC0059A908 /* ArkECSWrapper.swift */, ADA848012BBC7DF000B19378 /* ArkSerializableEvent.swift */, + ADA847FE2BBC4EA800B19378 /* ArkEventDataSerializer.swift */, + ADD74C0B2BC05677008CE36B /* ArkECSDataSerializer.swift */, + AD2B59B52BB958E400198E99 /* DataWrapper.swift */, + 02B3C64F2BCD8635002331A0 /* ArkPeerToPlayerIdSerializer.swift */, ); path = "data-serialize"; sourceTree = ""; }; + ADD74C0A2BC0555D008CE36B /* sendable */ = { + isa = PBXGroup; + children = ( + 2882ABF72BCB77EB0042AC52 /* SendableRenderableComponents */, + ADD74C0D2BC0573E008CE36B /* SendableComponent.swift */, + ADD74C0F2BC05759008CE36B /* SendableEntity.swift */, + ); + path = sendable; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1742,9 +1903,10 @@ 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 */, 8FEB217A2BADE60300788E20 /* DisplayContext.swift in Sources */, 022C427D2BC9943B003D6924 /* ResumeGameLoopEvent.swift in Sources */, 28A032DC2BAD4E8200851BFF /* StopWatchComponent.swift in Sources */, @@ -1761,6 +1923,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 */, @@ -1774,12 +1937,14 @@ 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 /* ArkNetworkProtocol.swift in Sources */, + ADA847FD2BBC4B5500B19378 /* AbstractNetworkService.swift in Sources */, 02C3951E2BA849490075F1CA /* CircleRenderableComponent.swift in Sources */, 9479A3AF2BA952E300F99013 /* AbstractShape.swift in Sources */, 02E0E8EE2BA280BD0043E2BA /* UIKitShape.swift in Sources */, 02C394E92BA41B480075F1CA /* ArkViewModel.swift in Sources */, + 2882ABFD2BCB7BA20042AC52 /* CircleRenderableComponent+SendableComponent.swift in Sources */, AD36A7562BAC0D36003E938B /* TankGameManager.swift in Sources */, 02B3C60D2BCAEFB3002331A0 /* GameHostingPageFactory.swift in Sources */, 02B3C6242BCBF6C1002331A0 /* TankWinEvent.swift in Sources */, @@ -1788,14 +1953,18 @@ 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 */, 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 */, 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 */, 28844EA12BA881E60037A7F6 /* DemoPhysicsComponent.swift in Sources */, @@ -1811,11 +1980,13 @@ 94D0538B2BC6CA2A000280C6 /* TankGameExternalResources.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 */, 02B3C6272BCBF8F9002331A0 /* TankWinCustomView.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 */, @@ -1827,14 +1998,18 @@ 943D418C2BAEBF2E00F9E88F /* ArkSetupContext.swift in Sources */, ADA847FF2BBC4EA800B19378 /* ArkDataSerializer.swift in Sources */, 02B3C6202BCBDFEC002331A0 /* TankRaceEndPedalEvent.swift in Sources */, + ADA847FF2BBC4EA800B19378 /* ArkEventDataSerializer.swift in Sources */, AD36A7582BAC3223003E938B /* TankGameEntityCreator.swift in Sources */, 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 */, 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 */, @@ -1842,11 +2017,17 @@ 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 */, + 8FEB217E2BADF21E00788E20 /* ArkActionContext.swift in Sources */, + 02C394F92BA443830075F1CA /* TapRenderable.swift in Sources */, + ADD74C0C2BC05677008CE36B /* ArkECSDataSerializer.swift in Sources */, 026882492BCACAFF00212BD6 /* ArkDemoGameHostingPage.swift in Sources */, 2812FCBF2BC3DE2F00A0FE24 /* TankRacePedalEvent.swift in Sources */, 8FEB217E2BADF21E00788E20 /* ArkActionContext.swift in Sources */, @@ -1865,6 +2046,7 @@ 02C3952A2BA8952F0075F1CA /* CanvasContext.swift in Sources */, 02D8E9512BAC9A3900BF3A07 /* AbstractParentView.swift in Sources */, 945F7F852BA55ECA00933629 /* UIKitButton.swift in Sources */, + 2882ABFF2BCB7BB40042AC52 /* PolygonRenderableComponent+SendableComponent.swift in Sources */, 94D053992BC7E7E0000280C6 /* TankRaceGameImages.swift in Sources */, 02E0E8F82BA284540043E2BA /* UIKitBitmap.swift in Sources */, 8F5573C82BA69D61007030C8 /* ArkAnimation.swift in Sources */, @@ -1874,12 +2056,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 */, @@ -1890,19 +2073,25 @@ 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 */, + 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 */, 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 */, 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 */, @@ -1914,6 +2103,8 @@ AD2B59AC2BB87F2200198E99 /* ArkNetworkService.swift in Sources */, 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 */, @@ -2263,7 +2454,6 @@ }; ADA847F32BBC11F400B19378 /* P2PShare */ = { isa = XCSwiftPackageProductDependency; - package = ADA847F22BBC11F400B19378 /* 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 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 } diff --git a/ArkKit/Ark.swift b/ArkKit/Ark.swift deleted file mode 100644 index 499c05d..0000000 --- a/ArkKit/Ark.swift +++ /dev/null @@ -1,243 +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)? - - 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() { - setupDefaultEntities() - setupDefaultListeners() - setupDefaultSystems(blueprint) - setup(blueprint.setupFunctions) - setup(blueprint.rules) - setup(blueprint.soundMapping) - 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 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 pauseGameLoopEvent = event as? PauseGameLoopEvent, - let self = self else { - return - } - self.gameLoop?.pauseLoop() - } - - arkState.eventManager.subscribe(to: ResumeGameLoopEvent.self) { [weak self] event in - guard let resumeGameLoopEvent = event as? ResumeGameLoopEvent, - let self = self else { - return - } - self.gameLoop?.resumeLoop() - - } - - arkState.eventManager.subscribe(to: TerminateGameLoopEvent.self) { [weak self] event in - guard let terminateGameEvent = 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 - } - - 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 28277b4..b9b801c 100644 --- a/ArkKit/ArkBlueprint.swift +++ b/ArkKit/ArkBlueprint.swift @@ -7,6 +7,10 @@ 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? + + // if there is player specific setup + private(set) var playerSpecificSetupFunctions: [ArkStateSetupDelegate] = [] // game world size private(set) var frameWidth: Double @@ -80,23 +84,35 @@ 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, - networkManagerDelegate: multiplayerManager) - multiplayerManager.multiplayerEventManager = multiplayerEventManager + func supportNetworkMultiPlayer(roomName: String, numberOfPlayers: Int) -> Self { + var newSelf = self + newSelf.networkPlayableInfo = ArkNetworkPlayableInfo( + roomName: roomName, numberOfPlayers: numberOfPlayers + ) + return newSelf + } - events.delegate = multiplayerEventManager - } + /// 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 stateSetupFunctionsCopy = setupFunctions - stateSetupFunctionsCopy.insert(fn, at: 0) + var newSelf = self + newSelf.playerSpecificSetupFunctions = playerStateSetupFunctionsCopy + return newSelf + } + func setRole(_ role: ArkPeerRole) -> Self { var newSelf = self - newSelf.setupFunctions = stateSetupFunctionsCopy + guard let originalNetworkInfo = self.networkPlayableInfo else { + return newSelf + } + newSelf.networkPlayableInfo = ArkNetworkPlayableInfo( + roomName: originalNetworkInfo.roomName, + numberOfPlayers: originalNetworkInfo.numberOfPlayers, + role: role + ) return newSelf } } 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..13c1def --- /dev/null +++ b/ArkKit/app/utils/set-up/ArkSetUpIfHostStrategy.swift @@ -0,0 +1,33 @@ +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, 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 new file mode 100644 index 0000000..69e087d --- /dev/null +++ b/ArkKit/app/utils/set-up/ArkSetUpIfParticipantStrategy.swift @@ -0,0 +1,39 @@ +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, + networkPlayableInfo.role == .participant else { + return + } + setupDefaultListeners() + setupMultiplayerGameLoop() + 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 + ark.participantSubscriber?.playerStateSetUpDelegate = self + + let participantPublisher = ArkParticipantNetworkPublisher(publishTo: networkService) + ark.arkState.eventManager.networkPublisherDelegate = participantPublisher + } +} + +extension ArkSetUpIfParticipantStrategy: ArkPlayerStateSetupDelegate { + func setup(_ playerId: Int) { + let playerSetUpCallbacks = ark?.blueprint.playerSpecificSetupFunctions + guard playerId < playerSetUpCallbacks?.count ?? 0, + let specificPlayerSetUp = playerSetUpCallbacks?[playerId], + let displayContext = ark?.displayContext else { + return + } + ark?.arkState.setup(specificPlayerSetUp, displayContext: displayContext) + } +} 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..abc44db --- /dev/null +++ b/ArkKit/app/utils/set-up/ArkSetUpWithoutNetworkStrategy.swift @@ -0,0 +1,20 @@ +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 { + return + } + setupDefaultEntities() + setupDefaultListeners() + setupDefaultSystems(ark.blueprint) + setup(ark.blueprint.setupFunctions) + setup(ark.blueprint.rules) + setup(ark.blueprint.soundMapping) + } +} diff --git a/ArkKit/ark-animation-kit/ArkAnimation.swift b/ArkKit/ark-animation-kit/ArkAnimation.swift index 568dd45..4131136 100644 --- a/ArkKit/ark-animation-kit/ArkAnimation.swift +++ b/ArkKit/ark-animation-kit/ArkAnimation.swift @@ -11,6 +11,8 @@ protocol Animation { 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..7807229 100644 --- a/ArkKit/ark-animation-kit/ArkAnimationInstance.swift +++ b/ArkKit/ark-animation-kit/ArkAnimationInstance.swift @@ -18,10 +18,19 @@ 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 markForDestroyal() { + func play() { + isPlaying = true + } + + func pause() { + isPlaying = false + } + + func stop() { shouldDestroy = true } @@ -36,6 +45,9 @@ extension AnimationInstance { if !wasComplete { if status == .complete { + if !animation.isLooping { + stop() + } completeDelegate?(self) } } @@ -50,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? @@ -58,7 +71,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,14 +79,17 @@ 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! } - 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 75963e7..70124f1 100644 --- a/ArkKit/ark-animation-kit/ArkAnimationSystem.swift +++ b/ArkKit/ark-animation-kit/ArkAnimationSystem.swift @@ -14,13 +14,22 @@ 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 { + if !animationInstance.isPlaying { + continue + } + 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..42def6d 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 + } } diff --git a/ArkKit/ark-camera-kit/Camera.swift b/ArkKit/ark-camera-kit/Camera.swift index b1aa31b..66d70bc 100644 --- a/ArkKit/ark-camera-kit/Camera.swift +++ b/ArkKit/ark-camera-kit/Camera.swift @@ -15,7 +15,7 @@ struct PlacedCameraComponent: 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 @@ -36,7 +36,7 @@ struct Camera { } } -struct CameraZoom { +struct CameraZoom: Codable { let widthZoom: Double let heightZoom: Double } 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-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 635331e..a62d77d 100644 --- a/ArkKit/ark-event-kit/ArkEventManager.swift +++ b/ArkKit/ark-event-kit/ArkEventManager.swift @@ -15,7 +15,8 @@ struct DatedEvent { class ArkEventManager: ArkEventContext { private var listeners: [ObjectIdentifier: [(any ArkEvent) -> Void]] = [:] private var eventQueue = PriorityQueue(sort: ArkEventManager.compareEventPriority) - var delegate: ArkEventContextDelegate? + + var networkPublisherDelegate: ArkNetworkPublisherDelegate? func subscribe(to eventType: Event.Type, _ listener: @escaping (any ArkEvent) -> Void) { let typeID = ObjectIdentifier(eventType) @@ -30,7 +31,7 @@ class ArkEventManager: ArkEventContext { func emit(_ event: Event) { let datedEvent = DatedEvent(event: event) eventQueue.enqueue(datedEvent) - delegate?.didEmitEvent(event) + networkPublisherDelegate?.publish(event: event) } func emitWithoutDelegate(_ event: Event) { @@ -67,7 +68,3 @@ class ArkEventManager: ArkEventContext { return datedEvent1.timestamp < datedEvent2.timestamp } } - -protocol ArkEventManagerDelegate: ArkEventContextDelegate { - func didEmitEvent(_ 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/AbstractNetworkService.swift b/ArkKit/ark-multiplayer-kit/AbstractNetworkService.swift new file mode 100644 index 0000000..8ba34da --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/AbstractNetworkService.swift @@ -0,0 +1,13 @@ +import Foundation + +protocol AbstractNetworkService { + var subscriber: ArkNetworkSubscriberDelegate? { get set } + var publisher: ArkNetworkPublisherDelegate? { get set } + + var deviceID: String { get } + var serviceName: String { get } + + init(serviceName: String) + func sendData(data: Data) + func sendData(_ data: Data, to peerName: String) +} 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/ArkMultiplayerEventManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift deleted file mode 100644 index 9f95504..0000000 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerEventManager.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// ArkMultiplayerEventManager.swift -// ArkKit -// -// Created by Ryan Peh on 31/3/24. -// - -import Foundation - -class ArkMultiplayerEventManager: ArkEventManagerDelegate { - - private var arkEventManager: ArkEventContext - var networkManagerDelegate: ArkMultiplayerManagerDelegate? - - init(arkEventManager: ArkEventContext = ArkEventManager(), - networkManagerDelegate: ArkMultiplayerManagerDelegate? = nil) { - self.arkEventManager = arkEventManager - self.networkManagerDelegate = networkManagerDelegate - } - - func didEmitEvent(_ event: Event) where Event: ArkEvent { - networkManagerDelegate?.shouldSendEvent(event) - } - - func emitWithoutBroadcast(_ event: Event) where Event: ArkEvent { - arkEventManager.emitWithoutDelegate(event) - } -} - -protocol ArkMultiplayerManagerDelegate: AnyObject { - func shouldSendEvent(_ event: Event) -} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift new file mode 100644 index 0000000..788d993 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/ArkMultiplayerGameLoop.swift @@ -0,0 +1,26 @@ +class ArkMultiplayerGameLoop: GameLoop { + var updatePhysicsSceneDelegate: (any ArkPhysicsSceneUpdateLoopDelegate)? + + weak var updateGameWorldDelegate: ArkGameWorldUpdateLoopDelegate? + + func setUp() { + } + + func update() { + let deltaTime = self.getDeltaTime() + self.updateGameWorldDelegate?.update(for: deltaTime) + } + + func getDeltaTime() -> Double { + 0.0 + } + + func shutDown() { + } + + func pauseLoop() { + } + + func resumeLoop() { + } +} diff --git a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift b/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift deleted file mode 100644 index ac3a085..0000000 --- a/ArkKit/ark-multiplayer-kit/ArkMultiplayerManager.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// ArkMultiplayerManager.swift -// ArkKit -// -// Created by Ryan Peh on 31/3/24. -// - -import MultipeerConnectivity - -class ArkMultiplayerManager: ArkNetworkDelegate { - private var networkService: ArkNetworkProtocol - var multiplayerEventManager: ArkMultiplayerEventManager? - - 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) - } - } - } catch { - print("Error decoding received data: \(error)") - } - } - - private func processEvent(event: any ArkEvent) { - multiplayerEventManager?.emitWithoutBroadcast(event) - } - - func connectedDevicesChanged(manager: ArkNetworkService, connectedDevices: [String]) { - } - -} - -extension ArkMultiplayerManager: ArkMultiplayerManagerDelegate { - func shouldSendEvent(_ event: Event) { - sendEvent(event: event) - } -} diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift deleted file mode 100644 index a3efad1..0000000 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkProtocol.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// ArkNetworkProtocol.swift -// ArkKit -// -// Created by Ryan Peh on 2/4/24. -// -import Foundation - -protocol ArkNetworkProtocol { - var delegate: ArkNetworkDelegate? { get set } - - init(serviceName: String) - func sendData(data: Data) -} diff --git a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift index d937034..5029718 100644 --- a/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift +++ b/ArkKit/ark-multiplayer-kit/ArkNetworkService.swift @@ -1,25 +1,26 @@ -// -// ArkNetworkService.swift -// ArkKit -// -// Created by Ryan Peh on 31/3/24. -// - 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") { 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() session.startSharing() @@ -29,19 +30,27 @@ class ArkNetworkService: ArkNetworkProtocol { session.stopSharing() } + var deviceID: String { + myPeerInfo.peerID + } + 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.publisher?.onChangeInObservers(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 - self?.delegate?.gameDataReceived(manager: self!, gameData: data) + self?.subscriber?.onListen(data) } } @@ -50,11 +59,12 @@ class ArkNetworkService: ArkNetworkProtocol { session.sendToAllPeers(data: data) } } -} -// MARK: - ArkNetworkDelegate + func sendData(_ data: Data, to peerName: String) { + guard let peerInfo = peers.first(where: { $0.info["name"] == peerName }) else { + return + } -protocol ArkNetworkDelegate: AnyObject { - func connectedDevicesChanged(manager: ArkNetworkService, connectedDevices: [String]) - func gameDataReceived(manager: ArkNetworkService, gameData: Data) + session.send(to: peerInfo.peerID, data: data) + } } diff --git a/ArkKit/ark-multiplayer-kit/ArkPeerRole.swift b/ArkKit/ark-multiplayer-kit/ArkPeerRole.swift new file mode 100644 index 0000000..522ff59 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/ArkPeerRole.swift @@ -0,0 +1,4 @@ +enum ArkPeerRole { + case host + case participant +} diff --git a/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift b/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift new file mode 100644 index 0000000..8406c3f --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/ArkPlayerStateSetupDelegate.swift @@ -0,0 +1,3 @@ +protocol ArkPlayerStateSetupDelegate: AnyObject { + func setup(_ playerId: Int) +} 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/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/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/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-multiplayer-kit/data-serialize/ComponentRegistry.swift b/ArkKit/ark-multiplayer-kit/data-serialize/ComponentRegistry.swift new file mode 100644 index 0000000..8704c2d --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/data-serialize/ComponentRegistry.swift @@ -0,0 +1,79 @@ +import Foundation + +class ComponentRegistry { + static let shared = ComponentRegistry() + + private init() { +// setUpComponentTypes() + loadComponentTypes() + } + + private var componentTypes: [String: (Data) throws -> any Component] = [:] + + 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 loadComponentTypes() { + let componentTypes: [Component.Type] = [ + PositionComponent.self, + ButtonRenderableComponent.self, + JoystickRenderableComponent.self, + CircleRenderableComponent.self, + RectRenderableComponent.self, + PolygonRenderableComponent.self, + BitmapImageRenderableComponent.self, + CameraContainerRenderableComponent.self, + RotationComponent.self + ] + + for componentType in componentTypes { + register(componentType) + } + } + + 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) + } + } + } + + 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, + let castedClass = someClass as? T else { + continue + } + result.append(castedClass) + } + return result + } + + private func address(of object: Any?) -> UnsafeMutableRawPointer { + Unmanaged.passUnretained(object as AnyObject).toOpaque() + } +} diff --git a/ArkKit/ark-multiplayer-kit/DataWrapper.swift b/ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift similarity index 61% rename from ArkKit/ark-multiplayer-kit/DataWrapper.swift rename to ArkKit/ark-multiplayer-kit/data-serialize/DataWrapper.swift index 40be41a..c98cb9b 100644 --- a/ArkKit/ark-multiplayer-kit/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, state + case event, ecs, playerMapping } struct DataWrapper: Codable { diff --git a/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift b/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift new file mode 100644 index 0000000..3ec4150 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/publisher/ArkHostNetworkPublisher.swift @@ -0,0 +1,54 @@ +class ArkHostNetworkPublisher: ArkNetworkPublisherDelegate { + // network related dependencies + var networkService: AbstractNetworkService + private var peers = [String]() + private(set) var peerInfoToPlayerIdMap: [String: Int] = [:] + + 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) { + 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 + + // 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/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/sendable/SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift new file mode 100644 index 0000000..e5e63fc --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableComponent.swift @@ -0,0 +1,4 @@ +import Foundation + +protocol 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..348a2f0 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableEntity.swift @@ -0,0 +1,4 @@ +struct SendableEntity: Codable { +} + +typealias Sendable = 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/CircleRenderableComponent+SendableComponent.swift b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRenderableComponent+SendableComponent.swift new file mode 100644 index 0000000..54c0491 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/sendable/SendableRenderableComponents/CircleRenderableComponent+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) + } +} 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..e81dd96 --- /dev/null +++ b/ArkKit/ark-multiplayer-kit/subscriber/ArkParticipantNetworkSubscriber.swift @@ -0,0 +1,55 @@ +import Foundation + +class ArkParticipantNetworkSubscriber: ArkNetworkSubscriberDelegate { + // network related dependencies + var networkService: AbstractNetworkService + var playerStateSetUpDelegate: ArkPlayerStateSetupDelegate? + + // 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 == .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) + } + + localGameLoop?.update() + } catch { + print("Error decoding received data: \(error)") + } + } + + 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() + ) + } +} 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-render-kit/abstract/color/AbstractColor.swift b/ArkKit/ark-render-kit/abstract/color/AbstractColor.swift index a3b4493..535c044 100644 --- a/ArkKit/ark-render-kit/abstract/color/AbstractColor.swift +++ b/ArkKit/ark-render-kit/abstract/color/AbstractColor.swift @@ -1,12 +1,5 @@ -// -// AbstractColor.swift -// ArkKit -// -// Created by En Rong on 19/3/24. -// - import Foundation -enum AbstractColor { +enum AbstractColor: Codable { case `default`, blue, red, green, black, white, gray } 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 diff --git a/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift index 33e9d99..630085a 100644 --- a/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift +++ b/ArkKit/ark-render-kit/renderable-components/bitmap/BitmapImageRenderableComponent.swift @@ -10,17 +10,37 @@ 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(imageResourcePath: some ArkImageEnum, width: Double, height: Double) { + 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) { 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 { 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-render-kit/renderable-components/shapes/CircleRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/CircleRenderableComponent.swift index a078c98..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 diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/PolygonRenderableComponent.swift index 45a2437..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 { diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/RectRenderableComponent.swift index f1932dd..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 { diff --git a/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift b/ArkKit/ark-render-kit/renderable-components/shapes/ShapeRenderableComponent.swift index 43bde55..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 { @@ -28,11 +21,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/ArkECS.swift b/ArkKit/ark-state-kit/ark-ecs-kit/ArkECS.swift index d7a120d..0aea278 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 @@ -9,6 +9,24 @@ class ArkECS { 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 bulkUpsert(entities: [Entity], components: [EntityID: [any Component]]) { + for entity in entities { + let components = components[entity.id] ?? [] + for component in components { + entityManager.upsertComponent(component, to: entity) + } + } + } + func startUp() { self.systemManager.startUp() } @@ -20,19 +38,32 @@ class ArkECS { func cleanUp() { self.systemManager.cleanUp() } -} - -extension ArkECS: ArkECSContext { @discardableResult func createEntity() -> Entity { entityManager.createEntity() } + @discardableResult + func createEntity(id: EntityID) -> Entity { + entityManager.createEntity(id: id) + } + func removeEntity(_ entity: Entity) { 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) } @@ -50,11 +81,16 @@ extension ArkECS: ArkECSContext { entityManager.createEntity(with: components) } + @discardableResult + func createEntity(id: EntityID, with components: [any Component]) -> Entity { + entityManager.createEntity(with: components, id: id) + } + func getEntity(id: EntityID) -> Entity? { entityManager.getEntity(id: id) } - func getEntities(with componentTypes: [any Component.Type]) -> [Entity] { + func getEntities(with componentTypes: [any Component.Type] = []) -> [Entity] { entityManager.getEntities(with: componentTypes) } diff --git a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift index 185860a..6b57dd6 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift +++ b/ArkKit/ark-state-kit/ark-ecs-kit/EntityManager.swift @@ -1,18 +1,14 @@ -// -// EntityManager.swift -// LevelKit -// -// Created by Ryan Peh on 9/3/24. -// - import Foundation class EntityManager { private var entities = Set() private var componentsByType = [ObjectIdentifier: [Entity: Component]]() + private var idGenerator = EntityIDGenerator() + + func createEntity(id: EntityID? = nil) -> Entity { + let entityId = id ?? idGenerator.generate() + let entity = Entity(id: entityId) - func createEntity() -> Entity { - let entity = Entity() entities.insert(entity) return entity } @@ -25,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 } @@ -39,8 +39,9 @@ class EntityManager { return componentsByType[typeID]?[entity] as? T } - func createEntity(with components: [Component]) -> Entity { - let entity = Entity() + func createEntity(with components: [Component], id: EntityID? = nil) -> Entity { + let entityId = id ?? idGenerator.generate() + let entity = Entity(id: entityId) entities.insert(entity) for comp in components { upsertComponent(comp, to: entity) @@ -86,4 +87,9 @@ class EntityManager { }) return result } + + func removeAllEntities() { + entities = [] + componentsByType = [:] + } } 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 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 55% 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..12138d8 100644 --- a/ArkKit/ark-state-kit/ark-ecs-kit/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 = UUID +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 new file mode 100644 index 0000000..addaa94 --- /dev/null +++ b/ArkKit/ark-state-kit/ark-ecs-kit/entity/EntityIDGenerator.swift @@ -0,0 +1,20 @@ +import Foundation + +class EntityIDGenerator { + private var currentID: UInt32 = 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) + } +} 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) 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