diff --git a/MiniBlocks.playground/Sources/Actor/WorldActor.swift b/MiniBlocks.playground/Sources/Actor/WorldActor.swift new file mode 100644 index 0000000..a00f601 --- /dev/null +++ b/MiniBlocks.playground/Sources/Actor/WorldActor.swift @@ -0,0 +1,5 @@ +/// The actor on which the world's model state lives. +@globalActor +actor WorldActor { + static let shared = WorldActor() +} diff --git a/MiniBlocks.playground/Sources/Component/AchievementHUDLoadComponent.swift b/MiniBlocks.playground/Sources/Component/AchievementHUDLoadComponent.swift index b26afca..3a0af19 100644 --- a/MiniBlocks.playground/Sources/Component/AchievementHUDLoadComponent.swift +++ b/MiniBlocks.playground/Sources/Component/AchievementHUDLoadComponent.swift @@ -13,21 +13,21 @@ class AchievementHUDLoadComponent: GKComponent { entity?.component(ofType: SpriteNodeComponent.self)?.node } - private var world: World? { + @WorldActor private var world: World? { get { entity?.component(ofType: WorldAssociationComponent.self)?.world } set { entity?.component(ofType: WorldAssociationComponent.self)?.world = newValue } } - private var playerInfo: PlayerInfo? { + @WorldActor private var playerInfo: PlayerInfo? { get { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo } set { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo = newValue } } - private var achievements: Achievements? { + @WorldActor private var achievements: Achievements? { playerInfo?.achievements } - private var nextAchievement: Achievements? { + @WorldActor private var nextAchievement: Achievements? { achievements?.next } @@ -41,15 +41,21 @@ class AchievementHUDLoadComponent: GKComponent { } override func update(deltaTime seconds: TimeInterval) { + Task.detached { @WorldActor in + await self._update(deltaTime: seconds) + } + } + + @WorldActor private func _update(deltaTime seconds: TimeInterval) async { guard let node = node else { return } if lastAchievement != nextAchievement { // Update when achievements change - node.removeAllChildren() + await node.removeAllChildren() if let nextAchievement = nextAchievement, let achievementNode = makeAchievementHUDNode(for: nextAchievement, fontSize: 13) { - node.addChild(achievementNode) + await node.addChild(achievementNode) } lastAchievement = nextAchievement diff --git a/MiniBlocks.playground/Sources/Component/DebugHUDLoadComponent.swift b/MiniBlocks.playground/Sources/Component/DebugHUDLoadComponent.swift index 4d086ce..45abf8a 100644 --- a/MiniBlocks.playground/Sources/Component/DebugHUDLoadComponent.swift +++ b/MiniBlocks.playground/Sources/Component/DebugHUDLoadComponent.swift @@ -6,12 +6,12 @@ class DebugHUDLoadComponent: GKComponent { entity?.component(ofType: SpriteNodeComponent.self)?.node as? SKLabelNode } - private var world: World? { + @WorldActor private var world: World? { get { entity?.component(ofType: WorldAssociationComponent.self)?.world } set { entity?.component(ofType: WorldAssociationComponent.self)?.world = newValue } } - private var playerInfo: PlayerInfo? { + @WorldActor private var playerInfo: PlayerInfo? { get { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo } set { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo = newValue } } @@ -21,9 +21,17 @@ class DebugHUDLoadComponent: GKComponent { } override func update(deltaTime seconds: TimeInterval) { + Task.detached { @WorldActor in + await self._update(deltaTime: seconds) + } + } + + @WorldActor private func _update(deltaTime seconds: TimeInterval) async { guard let playerInfo = playerInfo, let node = node else { return } + let text: String? + if playerInfo.hasDebugHUDEnabled { var stats = [ ("Position", format(pos: playerInfo.position)), @@ -38,10 +46,14 @@ class DebugHUDLoadComponent: GKComponent { ] } - node.text = stats.map { "\($0.0): \($0.1)" }.joined(separator: "\n") + text = stats.map { "\($0.0): \($0.1)" }.joined(separator: "\n") } else { - node.text = nil + text = nil } + + await Task { @MainActor in + node.text = text + }.value } private func format(pos: BlockPos3) -> String { diff --git a/MiniBlocks.playground/Sources/Component/HandLoadComponent.swift b/MiniBlocks.playground/Sources/Component/HandLoadComponent.swift index ce27145..b6ae926 100644 --- a/MiniBlocks.playground/Sources/Component/HandLoadComponent.swift +++ b/MiniBlocks.playground/Sources/Component/HandLoadComponent.swift @@ -10,21 +10,27 @@ class HandLoadComponent: GKComponent { entity?.component(ofType: HandNodeComponent.self)?.node } - private var world: World? { + @WorldActor private var world: World? { get { entity?.component(ofType: WorldAssociationComponent.self)?.world } set { entity?.component(ofType: WorldAssociationComponent.self)?.world = newValue } } - private var playerInfo: PlayerInfo? { + @WorldActor private var playerInfo: PlayerInfo? { get { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo } set { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo = newValue } } - private var item: Item? { + @WorldActor private var item: Item? { playerInfo?.selectedHotbarStack?.item } override func update(deltaTime seconds: TimeInterval) { + Task.detached { @WorldActor in + self._update(deltaTime: seconds) + } + } + + @WorldActor private func _update(deltaTime seconds: TimeInterval) { guard let node = node, lastItem != item else { return } diff --git a/MiniBlocks.playground/Sources/Component/HotbarHUDLoadComponent.swift b/MiniBlocks.playground/Sources/Component/HotbarHUDLoadComponent.swift index 511b58f..38d14a9 100644 --- a/MiniBlocks.playground/Sources/Component/HotbarHUDLoadComponent.swift +++ b/MiniBlocks.playground/Sources/Component/HotbarHUDLoadComponent.swift @@ -15,57 +15,65 @@ class HotbarHUDLoadComponent: GKComponent { entity?.component(ofType: SpriteNodeComponent.self)?.node } - private var world: World? { + @WorldActor private var world: World? { get { entity?.component(ofType: WorldAssociationComponent.self)?.world } set { entity?.component(ofType: WorldAssociationComponent.self)?.world = newValue } } - private var playerInfo: PlayerInfo? { + @WorldActor private var playerInfo: PlayerInfo? { get { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo } set { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo = newValue } } - private var inventory: Inventory? { + @WorldActor private var inventory: Inventory? { get { playerInfo?.hotbar } set { playerInfo?.hotbar = newValue! } } override func update(deltaTime seconds: TimeInterval) { + Task.detached { @WorldActor in + await self._update(deltaTime: seconds) + } + } + + @WorldActor private func _update(deltaTime seconds: TimeInterval) async { guard let node = node else { return } if inventory != lastInventory { // Redraw slots as inventory has changed // TODO: Delta updates? - node.removeAllChildren() + await node.removeAllChildren() if let inventory = inventory { let slotSize: CGFloat = 40 let itemSize: CGFloat = slotSize * 0.8 let width = CGFloat(inventory.slotCount) * slotSize - for i in 0.. CGFloat { - playerInfo?.selectedHotbarSlot == i ? selectedSlotLineThickness : normalSlotLineThickness + private func slotLineThickness(for i: Int) async -> CGFloat { + await playerInfo?.selectedHotbarSlot == i ? selectedSlotLineThickness : normalSlotLineThickness } } diff --git a/MiniBlocks.playground/Sources/Component/PlayerAssocationComponent.swift b/MiniBlocks.playground/Sources/Component/PlayerAssocationComponent.swift index 49f036e..618683d 100644 --- a/MiniBlocks.playground/Sources/Component/PlayerAssocationComponent.swift +++ b/MiniBlocks.playground/Sources/Component/PlayerAssocationComponent.swift @@ -16,12 +16,12 @@ class PlayerAssociationComponent: GKComponent { entity?.component(ofType: WorldAssociationComponent.self) } - private var world: World? { + @WorldActor private var world: World? { get { worldAssocationComponent?.world } set { worldAssocationComponent?.world = newValue! } } - var playerInfo: PlayerInfo? { + @WorldActor var playerInfo: PlayerInfo? { get { playerName.flatMap { world?[playerInfoFor: $0] } } set { guard let playerName = playerName else { return } diff --git a/MiniBlocks.playground/Sources/Component/PlayerControlComponent.swift b/MiniBlocks.playground/Sources/Component/PlayerControlComponent.swift index 34a44ff..c2be898 100644 --- a/MiniBlocks.playground/Sources/Component/PlayerControlComponent.swift +++ b/MiniBlocks.playground/Sources/Component/PlayerControlComponent.swift @@ -21,7 +21,7 @@ class PlayerControlComponent: GKComponent { private var blockThrottler = Throttler(interval: 0.15) - private var speed: Double { + @WorldActor private var speed: Double { baseSpeed * (motionInput.contains(.sprint) ? sprintFactor : 1) * ((playerInfo?.gameMode.permitsFlight ?? false) ? flightFactor : 1) @@ -39,7 +39,7 @@ class PlayerControlComponent: GKComponent { worldAssocationComponent?.worldNode } - private var world: World? { + @WorldActor private var world: World? { get { worldAssocationComponent?.world } set { worldAssocationComponent?.world = newValue! } } @@ -56,7 +56,7 @@ class PlayerControlComponent: GKComponent { entity?.component(ofType: PlayerAssociationComponent.self) } - private var playerInfo: PlayerInfo? { + @WorldActor private var playerInfo: PlayerInfo? { get { playerAssociationComponent?.playerInfo } set { playerAssociationComponent?.playerInfo = newValue! } } @@ -67,37 +67,41 @@ class PlayerControlComponent: GKComponent { var requestedBaseVelocity: Vec3 = Vec3() { didSet { - playerInfo?.achieve(.moveAround) + Task.detached { @WorldActor in + self.playerInfo?.achieve(.moveAround) + } } } private var requestedVelocity: Vec3? { - guard let node = node, let parent = node.parent else { return nil } - - var vector = SCNVector3(requestedBaseVelocity) - - if motionInput.contains(.forward) { - vector.z -= 1 - } - if motionInput.contains(.back) { - vector.z += 1 - } - if motionInput.contains(.left) { - vector.x -= 1 - } - if motionInput.contains(.right) { - vector.x += 1 - } - - var rotated = node.convertVector(vector, to: parent) - rotated.y = 0 // disable vertical movement - - if rotated.length > 0 { - rotated.normalize() - rotated *= SceneFloat(speed) + get async { + guard let node = node, let parent = node.parent else { return nil } + + var vector = SCNVector3(requestedBaseVelocity) + + if motionInput.contains(.forward) { + vector.z -= 1 + } + if motionInput.contains(.back) { + vector.z += 1 + } + if motionInput.contains(.left) { + vector.x -= 1 + } + if motionInput.contains(.right) { + vector.x += 1 + } + + var rotated = node.convertVector(vector, to: parent) + rotated.y = 0 // disable vertical movement + + if rotated.length > 0 { + rotated.normalize() + rotated *= SceneFloat(await speed) + } + + return Vec3(rotated) } - - return Vec3(rotated) } /// A bit set that represents motion input, usually by the user. @@ -120,8 +124,14 @@ class PlayerControlComponent: GKComponent { } override func update(deltaTime seconds: TimeInterval) { + Task.detached { @WorldActor in + await self._update(deltaTime: seconds) + } + } + + @WorldActor private func _update(deltaTime seconds: TimeInterval) async { // Note that we don't use the if-var-and-assign idiom for playerInfo due to responsiveness issues (and inout bindings aren't in Swift yet) - guard let requestedVelocity = requestedVelocity, + guard let requestedVelocity = await requestedVelocity, playerInfo != nil else { return } // Fetch position and velocity @@ -194,20 +204,22 @@ class PlayerControlComponent: GKComponent { func add(motionInput delta: MotionInput) { motionInput.insert(delta) - if !delta.isDisjoint(with: [.forward, .back, .left, .right]) { - playerInfo?.achieve(.moveAround) - } - if delta.contains(.sprint) { - playerInfo?.achieve(.sprint) - } - if delta.contains(.jump) { - playerInfo?.achieve(.jump) - } - if delta.contains(.breakBlock) { - playerInfo?.achieve(.breakBlock) - } - if delta.contains(.useBlock) { - playerInfo?.achieve(.useBlock) + Task.detached { @WorldActor in + if !delta.isDisjoint(with: [.forward, .back, .left, .right]) { + self.playerInfo?.achieve(.moveAround) + } + if delta.contains(.sprint) { + self.playerInfo?.achieve(.sprint) + } + if delta.contains(.jump) { + self.playerInfo?.achieve(.jump) + } + if delta.contains(.breakBlock) { + self.playerInfo?.achieve(.breakBlock) + } + if delta.contains(.useBlock) { + self.playerInfo?.achieve(.useBlock) + } } if delta.contains(.useBlock) || delta.contains(.breakBlock) { @@ -223,13 +235,17 @@ class PlayerControlComponent: GKComponent { /// Rotates the node vertically by the given angle (in radians). func rotatePitch(by delta: SceneFloat) { guard let node = node, canRotatePitch(by: delta) else { return } - playerInfo?.achieve(.peekAround) + Task.detached { @WorldActor in + self.playerInfo?.achieve(.peekAround) + } node.eulerAngles.x += delta * pitchSpeed } /// Rotates the node horizontally by the given angle (in radians). func rotateYaw(by delta: SceneFloat) { - playerInfo?.achieve(.peekAround) + Task.detached { @WorldActor in + self.playerInfo?.achieve(.peekAround) + } node?.eulerAngles.y += delta * yawSpeed } @@ -238,22 +254,22 @@ class PlayerControlComponent: GKComponent { return pitchRange.contains(node.eulerAngles.x + delta * pitchSpeed) } - func moveHotbarSelection(by delta: Int) { + @WorldActor func moveHotbarSelection(by delta: Int) { playerInfo?.achieve(.hotbar) playerInfo?.selectedHotbarSlot += delta } - func select(hotbarSlot: Int) { + @WorldActor func select(hotbarSlot: Int) { playerInfo?.achieve(.hotbar) playerInfo?.selectedHotbarSlot = hotbarSlot } /// Toggles the debug overlay for the player. - func toggleDebugHUD() { + @WorldActor func toggleDebugHUD() { playerInfo?.hasDebugHUDEnabled = !(playerInfo?.hasDebugHUDEnabled ?? true) } - func jump() { + @WorldActor func jump() { guard playerInfo?.isOnGround ?? false else { return } playerInfo?.achieve(.jump) playerInfo?.velocity.y = jumpSpeed diff --git a/MiniBlocks.playground/Sources/Component/PlayerGravityComponent.swift b/MiniBlocks.playground/Sources/Component/PlayerGravityComponent.swift index 9b752ae..6a05f12 100644 --- a/MiniBlocks.playground/Sources/Component/PlayerGravityComponent.swift +++ b/MiniBlocks.playground/Sources/Component/PlayerGravityComponent.swift @@ -6,7 +6,7 @@ class PlayerGravityComponent: GKComponent { var acceleration: Double = -0.4 private var throttler = Throttler(interval: 0.1) - private var world: World? { + @WorldActor private var world: World? { entity?.component(ofType: WorldAssociationComponent.self)?.world } @@ -14,12 +14,18 @@ class PlayerGravityComponent: GKComponent { entity?.component(ofType: PlayerAssociationComponent.self) } - private var playerInfo: PlayerInfo? { + @WorldActor private var playerInfo: PlayerInfo? { get { playerAssociationComponent?.playerInfo } set { playerAssociationComponent?.playerInfo = newValue! } } override func update(deltaTime seconds: TimeInterval) { + Task.detached { @WorldActor in + await self._update(deltaTime: seconds) + } + } + + @WorldActor private func _update(deltaTime seconds: TimeInterval) async { // Note that we don't use the if-var-and-assign idiom for playerInfo due to responsiveness issues (and inout bindings aren't in Swift yet) guard let world = world, playerInfo != nil, diff --git a/MiniBlocks.playground/Sources/Component/PlayerPositioningComponent.swift b/MiniBlocks.playground/Sources/Component/PlayerPositioningComponent.swift index 5f7582f..e720d81 100644 --- a/MiniBlocks.playground/Sources/Component/PlayerPositioningComponent.swift +++ b/MiniBlocks.playground/Sources/Component/PlayerPositioningComponent.swift @@ -17,12 +17,12 @@ class PlayerPositioningComponent: GKComponent { entity?.component(ofType: PlayerAssociationComponent.self) } - private var playerInfo: PlayerInfo? { + @WorldActor private var playerInfo: PlayerInfo? { get { playerAssociationComponent?.playerInfo } set { playerAssociationComponent?.playerInfo = newValue! } } - override func update(deltaTime seconds: TimeInterval) { + @WorldActor override func update(deltaTime seconds: TimeInterval) { guard let node = node, var playerInfo = playerInfo else { return } diff --git a/MiniBlocks.playground/Sources/Component/WorldAssociationComponent.swift b/MiniBlocks.playground/Sources/Component/WorldAssociationComponent.swift index db9dace..097d54e 100644 --- a/MiniBlocks.playground/Sources/Component/WorldAssociationComponent.swift +++ b/MiniBlocks.playground/Sources/Component/WorldAssociationComponent.swift @@ -4,7 +4,7 @@ import GameplayKit class WorldAssociationComponent: GKComponent { let worldEntity: GKEntity - var world: World? { + @WorldActor var world: World? { get { worldEntity.component(ofType: WorldComponent.self)?.world } set { worldEntity.component(ofType: WorldComponent.self)?.world = newValue! } } diff --git a/MiniBlocks.playground/Sources/Component/WorldComponent.swift b/MiniBlocks.playground/Sources/Component/WorldComponent.swift index fb32ab0..eb0318a 100644 --- a/MiniBlocks.playground/Sources/Component/WorldComponent.swift +++ b/MiniBlocks.playground/Sources/Component/WorldComponent.swift @@ -3,7 +3,7 @@ import GameplayKit /// Lets the associated entity store the corresponding world. class WorldComponent: GKComponent { - var world: World + @WorldActor var world: World init(world: World) { self.world = world diff --git a/MiniBlocks.playground/Sources/Component/WorldLoadComponent.swift b/MiniBlocks.playground/Sources/Component/WorldLoadComponent.swift index 3febde8..418ed24 100644 --- a/MiniBlocks.playground/Sources/Component/WorldLoadComponent.swift +++ b/MiniBlocks.playground/Sources/Component/WorldLoadComponent.swift @@ -27,7 +27,7 @@ class WorldLoadComponent: GKComponent { /// Debounces chunk unloading to a fixed interval. private var debouncer = Debouncer(interval: 2) - private var world: World? { + @WorldActor private var world: World? { get { entity?.component(ofType: WorldComponent.self)?.world } set { entity?.component(ofType: WorldComponent.self)?.world = newValue! } } @@ -55,7 +55,7 @@ class WorldLoadComponent: GKComponent { } } - override func update(deltaTime seconds: TimeInterval) { + @WorldActor override func update(deltaTime seconds: TimeInterval) { guard let node = node, let world = world else { return } @@ -116,7 +116,7 @@ class WorldLoadComponent: GKComponent { dirtyStrips = [] } - private func reload(strips: Set) { + @WorldActor private func reload(strips: Set) { for pos in strips { let chunkPos = ChunkPos(containing: pos) if let chunkNode = loadedChunks[chunkPos] { @@ -129,7 +129,7 @@ class WorldLoadComponent: GKComponent { } } - private func loadChunk(at chunkPos: ChunkPos) -> SCNNode { + @WorldActor private func loadChunk(at chunkPos: ChunkPos) -> SCNNode { let chunkNode = SCNNode() for pos in chunkPos { @@ -139,7 +139,7 @@ class WorldLoadComponent: GKComponent { return chunkNode } - private func loadStrip(at pos: BlockPos2, into chunkNode: SCNNode) { + @WorldActor private func loadStrip(at pos: BlockPos2, into chunkNode: SCNNode) { guard let world = world else { return } for (y, block) in world[pos] { diff --git a/MiniBlocks.playground/Sources/Entity/PlayerEntity.swift b/MiniBlocks.playground/Sources/Entity/PlayerEntity.swift index 673ab11..287327e 100644 --- a/MiniBlocks.playground/Sources/Entity/PlayerEntity.swift +++ b/MiniBlocks.playground/Sources/Entity/PlayerEntity.swift @@ -9,7 +9,7 @@ func makePlayerEntity( retainRadius: Int, ambientOcclusionEnabled: Bool, handShown: Bool -) -> GKEntity { +) async -> GKEntity { // Create node let height: Double = 1.5 let camera = SCNCamera() @@ -43,12 +43,14 @@ func makePlayerEntity( // Set initial player info if let component = worldEntity.component(ofType: WorldComponent.self) { - var playerInfo = component.world[playerInfoFor: name] - - playerInfo.position = position - playerInfo.gameMode = gameMode - - component.world[playerInfoFor: name] = playerInfo + await Task { @WorldActor in + var playerInfo = component.world[playerInfoFor: name] + + playerInfo.position = position + playerInfo.gameMode = gameMode + + component.world[playerInfoFor: name] = playerInfo + }.value } return entity diff --git a/MiniBlocks.playground/Sources/View/MiniBlocksViewController.swift b/MiniBlocks.playground/Sources/View/MiniBlocksViewController.swift index 9f54e41..e9bbc07 100644 --- a/MiniBlocks.playground/Sources/View/MiniBlocksViewController.swift +++ b/MiniBlocks.playground/Sources/View/MiniBlocksViewController.swift @@ -112,42 +112,44 @@ public final class MiniBlocksViewController: ViewController, SCNSceneRendererDel overlayScene.scaleMode = .aspectFill overlayScene.isUserInteractionEnabled = false - // Add light - add(entity: makeSunEntity()) - add(entity: makeAmbientLightEntity()) - - // Add the world - let world = World(generator: worldGenerator) - let worldEntity = makeWorldEntity(world: world) - add(entity: worldEntity) - - // Add player - let playerSpawnPos2 = BlockPos2.zero - let playerSpawnPos3 = playerSpawnPos2.with(y: world.height(at: playerSpawnPos2) ?? 10) - let playerEntity = makePlayerEntity( - name: playerName, - position: Vec3(playerSpawnPos3), - gameMode: gameMode, - worldEntity: worldEntity, - retainRadius: renderDistance, - ambientOcclusionEnabled: ambientOcclusionEnabled, - handShown: handShown - ) - add(entity: playerEntity) - - // Add overlay HUD - add(entity: makeCrosshairHUDEntity(in: overlayScene.frame)) - add(entity: makeHotbarHUDEntity(in: overlayScene.frame, playerEntity: playerEntity)) - add(entity: makeDebugHUDEntity(in: overlayScene.frame, playerEntity: playerEntity)) - - if achievementsShown { - add(entity: makeAchievementHUDEntity(in: overlayScene.frame, playerEntity: playerEntity)) + Task { + // Add light + add(entity: makeSunEntity()) + add(entity: makeAmbientLightEntity()) + + // Add the world + let world = World(generator: worldGenerator) + let worldEntity = makeWorldEntity(world: world) + add(entity: worldEntity) + + // Add player + let playerSpawnPos2 = BlockPos2.zero + let playerSpawnPos3 = playerSpawnPos2.with(y: world.height(at: playerSpawnPos2) ?? 10) + let playerEntity = await makePlayerEntity( + name: playerName, + position: Vec3(playerSpawnPos3), + gameMode: gameMode, + worldEntity: worldEntity, + retainRadius: renderDistance, + ambientOcclusionEnabled: ambientOcclusionEnabled, + handShown: handShown + ) + add(entity: playerEntity) + + // Add overlay HUD + add(entity: makeCrosshairHUDEntity(in: overlayScene.frame)) + add(entity: makeHotbarHUDEntity(in: overlayScene.frame, playerEntity: playerEntity)) + add(entity: makeDebugHUDEntity(in: overlayScene.frame, playerEntity: playerEntity)) + + if achievementsShown { + add(entity: makeAchievementHUDEntity(in: overlayScene.frame, playerEntity: playerEntity)) + } + + #if canImport(AppKit) + add(entity: makePauseHUDEntity(in: overlayScene.frame)) + #endif } - #if canImport(AppKit) - add(entity: makePauseHUDEntity(in: overlayScene.frame)) - #endif - // Set up SCNView let sceneView = sceneFrame.map { MiniBlocksSceneView(frame: $0) } ?? MiniBlocksSceneView() sceneView.scene = scene @@ -253,9 +255,11 @@ public final class MiniBlocksViewController: ViewController, SCNSceneRendererDel previousUpdateTime = time } - private func controlPlayer(with action: (PlayerControlComponent) -> Void) { - for case let component as PlayerControlComponent in playerControlComponentSystem.components { - action(component) + private func controlPlayer(with action: @escaping (PlayerControlComponent) async -> Void) { + Task { + for case let component as PlayerControlComponent in playerControlComponentSystem.components { + await action(component) + } } } @@ -273,13 +277,13 @@ public final class MiniBlocksViewController: ViewController, SCNSceneRendererDel } else if keyCode == .f3 { // Toggle debug information shown as an overlay (e.g. the current position) controlPlayer { component in - component.toggleDebugHUD() + await component.toggleDebugHUD() } } else if let n = keyCode.numericValue { if (1...InventoryConstants.hotbarSlotCount).contains(n) { // Select hotbar slot controlPlayer { component in - component.select(hotbarSlot: n - 1) + await component.select(hotbarSlot: n - 1) } } } else { @@ -367,8 +371,8 @@ public final class MiniBlocksViewController: ViewController, SCNSceneRendererDel // Rotate camera controlPlayer { component in - component.rotateYaw(by: -(event.deltaX * inputSensivity) / 50) - component.rotatePitch(by: -(event.deltaY * inputSensivity) / 50) + component.rotateYaw(by: -(event.deltaX * self.inputSensivity) / 50) + component.rotatePitch(by: -(event.deltaY * self.inputSensivity) / 50) } // Keep mouse at center of window @@ -382,7 +386,7 @@ public final class MiniBlocksViewController: ViewController, SCNSceneRendererDel // Move the selected slot controlPlayer { component in - component.moveHotbarSelection(by: slotDelta) + await component.moveHotbarSelection(by: slotDelta) } } } diff --git a/MiniBlocks/MiniBlocks.xcodeproj/project.pbxproj b/MiniBlocks/MiniBlocks.xcodeproj/project.pbxproj index 2fa0903..9062762 100644 --- a/MiniBlocks/MiniBlocks.xcodeproj/project.pbxproj +++ b/MiniBlocks/MiniBlocks.xcodeproj/project.pbxproj @@ -190,6 +190,8 @@ 35F2680127F76AAC00CC458C /* HandLoadComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F267FF27F76AAC00CC458C /* HandLoadComponent.swift */; }; 35F2680327F76C7D00CC458C /* HandNodeComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F2680227F76C7D00CC458C /* HandNodeComponent.swift */; }; 35F2680427F76C7D00CC458C /* HandNodeComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F2680227F76C7D00CC458C /* HandNodeComponent.swift */; }; + 35F2680C27F7848400CC458C /* WorldActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F2680B27F7848400CC458C /* WorldActor.swift */; }; + 35F2680D27F7848400CC458C /* WorldActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35F2680B27F7848400CC458C /* WorldActor.swift */; }; 35FEC153278E6B1B00E2000E /* Deque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FEC152278E6B1B00E2000E /* Deque.swift */; }; 35FEC154278E6B1B00E2000E /* Deque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FEC152278E6B1B00E2000E /* Deque.swift */; }; 35FEC156278E6BD900E2000E /* CyclicDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FEC155278E6BD900E2000E /* CyclicDeque.swift */; }; @@ -313,6 +315,7 @@ 35EA354D2788B3870066DD82 /* Vec3Protocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Vec3Protocol.swift; path = ../../MiniBlocks.playground/Sources/Utils/Vec3Protocol.swift; sourceTree = ""; }; 35F267FF27F76AAC00CC458C /* HandLoadComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HandLoadComponent.swift; path = ../../MiniBlocks.playground/Sources/Component/HandLoadComponent.swift; sourceTree = ""; }; 35F2680227F76C7D00CC458C /* HandNodeComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HandNodeComponent.swift; path = ../../MiniBlocks.playground/Sources/Component/HandNodeComponent.swift; sourceTree = ""; }; + 35F2680B27F7848400CC458C /* WorldActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WorldActor.swift; path = ../../MiniBlocks.playground/Sources/Actor/WorldActor.swift; sourceTree = ""; }; 35FEC152278E6B1B00E2000E /* Deque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Deque.swift; path = ../../MiniBlocks.playground/Sources/Utils/Deque.swift; sourceTree = ""; }; 35FEC155278E6BD900E2000E /* CyclicDeque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CyclicDeque.swift; path = ../../MiniBlocks.playground/Sources/Utils/CyclicDeque.swift; sourceTree = ""; }; 35FEC158278E700D00E2000E /* CyclicDequeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CyclicDequeTests.swift; sourceTree = ""; }; @@ -513,6 +516,7 @@ isa = PBXGroup; children = ( 35A0C28427873D0400FA8570 /* Utils */, + 35F2680E27F7848900CC458C /* Actor */, 35A0C2992787568300FA8570 /* Model */, 35A55551278C47A7006FA70D /* Node */, 35A0C27E27872CB500FA8570 /* Component */, @@ -569,6 +573,14 @@ path = Utils; sourceTree = ""; }; + 35F2680E27F7848900CC458C /* Actor */ = { + isa = PBXGroup; + children = ( + 35F2680B27F7848400CC458C /* WorldActor.swift */, + ); + name = Actor; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -736,6 +748,7 @@ 35391BF5278BC8ED00B22954 /* CompatibilityLayer.swift in Sources */, 35391BD8278BC5EE00B22954 /* PlayerGravityComponent.swift in Sources */, 35A55557278C4806006FA70D /* CrosshairHUDNode.swift in Sources */, + 35F2680D27F7848400CC458C /* WorldActor.swift in Sources */, 35391BDC278BC5EE00B22954 /* WorldComponent.swift in Sources */, 355006CC27A0BECD0021A0BE /* DebugHUDEntity.swift in Sources */, 35391BCB278BC5D800B22954 /* CoreGraphicsUtils.swift in Sources */, @@ -825,6 +838,7 @@ 352D7A482787BF1C008FB939 /* MiniBlocksSceneView.swift in Sources */, 35A0C28C27873F1400FA8570 /* SceneNodeComponent.swift in Sources */, 35A55556278C4806006FA70D /* CrosshairHUDNode.swift in Sources */, + 35F2680C27F7848400CC458C /* WorldActor.swift in Sources */, 35EA352427889B560066DD82 /* ChunkConstants.swift in Sources */, 355006CB27A0BECD0021A0BE /* DebugHUDEntity.swift in Sources */, 35EA354C2788B1D30066DD82 /* Vec2Protocol.swift in Sources */,