SpriteKit scene used as SCNView.overlaySKScene crashes due to SKShapeNode

I recently published my first game on the App Store. It uses SceneKit with a SpriteKit overlay. All crashes Xcode downloaded for it so far are related to some SpriteKit/SceneKit internals.

The most common crash is caused by SKCShapeNode::_NEW_copyRenderPathData. What could cause such a crash?

While developing this game (and the BoardGameKit framework that appears in the crash log) over the years I experienced many crashes presumably caused by the SpriteKit overlay (I opened a post SceneKit app randomly crashes with EXC_BAD_ACCESS in jet_context::set_fragment_texture about such a crash in September 2024), and other people on the internet also mention that they experience crashes when using SpriteKit as a SceneKit overlay. Should I use a separate SKView and lay it on top of SCNView rather than setting SCNView.overlaySKScene? That seemed to solve the crashes for a guy on stackoverflow, but is it also encouraged by Apple?

I know SceneKit is deprecated, but according to Apple critical bugs would still be fixed. Could this be considered a critical bug?

Hello

The crash you posted is in SpriteKit code, not SceneKit code. However, I'm not certain this is a SpriteKit bug.

A common cause of SpriteKit crashes is accessing/modifying SpriteKit objects outside of SpriteKit's update loop. Even calling methods or accessing properties that look like they don't modify anything (e.g, -frame) can trigger mutation to SpriteKit's internal data structures, which is unsafe if done from multiple threads.

Indeed, while looking through past SpriteKit bugs I found one for the exact function shown in your crash report. The resolution of that bug was in the app, which was found to be updating SpriteKit objects from multiple threads.

I suggest looking through each of the crash reports in the Xcode organizer. Look for reports where multiple thread backtraces contain SpriteKit methods/functions. That should point to locations your code is accessing/modifying SpriteKit objects where it should not be.

-- Justin

Thanks for your help. Updating SpriteKit from multiple threads is what I suspected too, but the crash shows only one thread accessing SpriteKit in that moment. Although the code in thread 1 does in fact update the path and fillColor properties of a SKShapeNode 6 lines before the one mentioned in the crash report. Could this still be the issue?

Yes, the crash report you posted shows only one thread accessing SpriteKit. However crash reports don't perfectly capture the state of every other thread at the instant of the crash because control needs to first return to the kernel - via sys call, pre-emption, etc - before thread state can be recorded for the crash report, by which point the other thread may have finished interacting with SpriteKit.

My suggestion is to look through other crash reports for the same crash point. In my experience given a sufficiently large enough number of crash reports, one of them will end up capturing the other thread in the act of concurrently mutating whatever data - SpriteKit internals in this case - caused the "crashing" thread to crash.

-- Justin

Ok, so just to make things clear, is it expected that whenever I want to change the SpriteKit scene and also avoid concurrently accessing objects that may be changed in the current thread, I have to queue the SpriteKit changes by capturing all the needed state?

It's just surprising to me that this is needed when SpriteKit is used as an overlay in SceneKit, but when running SpriteKit alone, it doesn't seem to be needed. I filed at least one feedback recently about crashes happening only when SpriteKit is used as SCNView.overlaySKScene. Is it expected that one has to use this... boilerplate code when embedded in SceneKit? Couldn't one do without it by simply overlaying a SkView on top of SCNView?

class SceneController {
    
    private var shapeNode: SKShapeNode!
    
    func main() {
        let shapePath = CGPath(rect: CGRect(x: 0, y: 0, width: 10, height: 10), transform: nil)
        GameViewController.shared.updateSpriteKit { [shapeNode, shapePath] in
            shapeNode!.path = shapePath
            shapeNode!.fillColor = .systemRed
        }
    }
    
}

class GameViewController: NSObject, SKSceneDelegate {
    
    static let shared = GameViewController()

    private var skQueue = (queue: DispatchQueue(label: "skQueue", qos: .userInteractive), updates: [() -> Void]())
    
    override func loadView() {
        let overlay = SKScene()
        overlay.delegate = self
    }
    
    func updateSpriteKit(_ block: @escaping () -> Void) {
        skQueue.queue.async { [self] in
            skQueue.updates.append(block)
        }
    }
    
    func update(_ currentTime: TimeInterval, for scene: SKScene) {
        skQueue.queue.sync {
            for block in skQueue.updates {
                block()
            }
            skQueue.updates.removeAll()
        }
    }
    
}
SpriteKit scene used as SCNView.overlaySKScene crashes due to SKShapeNode
 
 
Q