I was able to recreate the issue with a small sample project that repeatedly calls SCNView.projectPoint(_:)
in a block dispatched to the main actor inside the SCNSceneRendererDelegate.renderer(_:didApplyAnimationsAtTime)
method.
In my personal project, the issue happens when calling that method 6 times (in order to position 6 sprites in the overlay SpriteKit scene that should follow objects in the SceneKit scene), but in the sample I call it 200 times to reliably reproduce the issue.
As mentioned previously, the hangs only happen when running the app in fullscreen, but I noticed that when moving the mouse to the top of the screen to show the menu bar, the game runs smoothly. As soon as the menu bar disappears again, or if I click a main menu item to show the submenu, the game immediately hangs again and shows maybe 2 or even less frames per second. Sometimes the hangs continue happening unless I show the menu bar again, but sometimes the game begins running smoothly again after a couple seconds.
This is again confirmed by Instruments.
Interestingly, if I comment out the line
Task { @MainActor in
and the corresponding closing curly bracket 4 lines down, there is no hang anymore, not in fullscreen, and not when clicking the main menu:
Is this an issue with SCNView.projectPoint(_:)
? Or with the delegate method? Or with doing SceneKit things on the main thread? I thought that we're supposed to add nodes and do modifications on the main thread, so how could I avoid thread races if in this delegate method the only solution is not to use the main thread?
class GameViewController: NSViewController, SCNSceneRendererDelegate {
var scnView: SCNView?
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene(named: "art.scnassets/ship.scn")!
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
ship.runAction(.customAction(duration: 999, action: { node, t in
Task { @MainActor in
node.eulerAngles.y = t
}
}))
let scnView = self.view as! SCNView
self.scnView = scnView
scnView.scene = scene
scnView.delegate = self
}
func renderer(_ renderer: any SCNSceneRenderer, didApplyAnimationsAtTime time: TimeInterval) {
Task { @MainActor in
for _ in 0..<200 {
let _ = scnView?.projectPoint(SCNVector3(x: 0, y: 0, z: 0))
}
}
}
}