在同步非隔离上下文中调用主参与者隔离实例方法“run”

问题描述 投票:0回答:1

与其他几个开发人员一起,我们一直忙于将我们的

SpriteKit
游戏和框架迁移到 Swift 6。

有一个问题我们无法解决,这涉及到

SpriteKit
GameplayKit
之间的相互作用。

创建了一个非常小的演示存储库,可以清楚地演示该问题。可以在这里找到:

https://github.com/AchrafKassioui/GameplayKitExplorer/blob/main/GameplayKitExplorer/Basic.swift

相关代码也贴在这里:

import SwiftUI
import SpriteKit

struct BasicView: View {
    var body: some View {
        SpriteView(scene: BasicScene())
            .ignoresSafeArea()
    }
}

#Preview {
    BasicView()
}

class BasicScene: SKScene {
    override func didMove(to view: SKView) {
        size = view.bounds.size
        anchorPoint = CGPoint(x: 0.5, y: 0.5)
        backgroundColor = .gray
        view.isMultipleTouchEnabled = true
        
        let entity = BasicEntity(color: .systemYellow, size: CGSize(width: 100, height: 100))
        if let renderComponent = entity.component(ofType: BasicRenderComponent.self) {
            addChild(renderComponent.sprite)
        }
    }
}

@MainActor
class BasicEntity: GKEntity {
    init(color: SKColor, size: CGSize) {
        super.init()
        
        let renderComponent = BasicRenderComponent(color: color, size: size)
        addComponent(renderComponent)
        
        let animationComponent = BasicAnimationComponent()
        addComponent(animationComponent)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

@MainActor
class BasicRenderComponent: GKComponent {
    let sprite: SKSpriteNode
    
    init(color: SKColor, size: CGSize) {
        self.sprite = SKSpriteNode(texture: nil, color: color, size: size)
        super.init()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class BasicAnimationComponent: GKComponent {
    let action1 = SKAction.scale(to: 1.3, duration: 0.07)
    let action2 = SKAction.scale(to: 1, duration: 0.15)
    
    override init() {
        super.init()
    }

    override func didAddToEntity() {
        if let renderComponent = entity?.component(ofType: BasicRenderComponent.self) {
            renderComponent.sprite.run(SKAction.repeatForever(SKAction.sequence([action1, action2])))
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

由于

SKNode
被设计为在
MainActor
上运行,因此
BasicRenderComponent
也归属于
MainActor
。这是必需的,因为这个
GKComponent
专用于封装渲染到场景的节点。

还有一个

BasicAnimationComponent
,这个
GKComponent
负责对渲染节点进行动画处理。

显然,这只是一个示例,但是当将

GameplayKit
SpriteKit
结合使用时,
GKComponent
实例操作从另一个
SKNode
实例引用的
GKComponent
是很常见的,通常通过
open func update(deltaTime seconds: TimeInterval)
完成或者如本例所示,在
didAddToEntity
内。

现在,问题在于,在上面的示例中(但对于

update(deltaTime seconds: TimeInterval)
也是如此,方法
didAddToEntity
并未与
MainActor
隔离,因为
GKComponent
也不是孤立的。

这会导致错误

Call to main actor-isolated instance method 'run' in a synchronous nonisolated context
,因为编译器确实无法推断
didAddToEntity
MainActor
是隔离的。

BasicAnimationComponent
标记为
@MainActor
没有帮助,因为这种隔离不会传播回超类继承的方法。

事实上,我们尝试了很多其他选项,但都没有解决这个问题。

我们应该如何进行呢?到目前为止,这确实阻碍了我们迁移到 Swift 6。希望有人能够在这里提供帮助!

sprite-kit gameplay-kit swift6
1个回答
0
投票

您可以将 GameplayKit 组件标记为

@MainActor
,并在操作 SpriteKit 节点的任何回调中使用
MainActor.assumeIsolated {...}
,例如
didAddToEntity()
update(delta:)

例如:

@MainActor // <-- ADDED THIS
class BasicAnimationComponent: GKComponent {
    let action1 = SKAction.scale(to: 1.3, duration: 0.07)
    let action2 = SKAction.scale(to: 1, duration: 0.15)
    
    override init() {
        super.init()
    }

    override func didAddToEntity() {
        MainActor.assumeIsolated { // <-- ADDED THIS
            if let renderComponent = entity?.component(ofType: BasicRenderComponent.self) {
                renderComponent.sprite.run(
                    SKAction.repeatForever(SKAction.sequence([action1, action2]))
                )
            }
        }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

当然,请确保从主要参与者调用组件更新,但在使用 SpriteKit 时通常会出现这种情况。

© www.soinside.com 2019 - 2024. All rights reserved.