Pat Trudel
Pat Trudel

Reputation: 352

How to solve SceneKit Renderer "EXC_BAD_ACCESS (code=1, address=0xf000000010a10c10)"?

Here is the error I am getting, check the attached picture for more info.

com.apple.scenekit.scnview-renderer (17): EXC_BAD_ACCESS (code=1, address=0xf000000010a10c10)

Here is a log of the error: More Info about the error

I can reproduce this error when I call the following function but only if this function gets called many times within a second. This would happen If the user rapidly taps the button to cycle to the next available car.

As you can see, I tried wrapping this in a DispatchQueue to solve my problem.

You'll also notice that I created a Bool alreadyCyclingCars to track whether the cycleCarNext() function is finished before allowing it to be called again.

This function essentially iterates through all the unlockedCars in the unlockedCars array.

If the type of car matches the one we are currently looking for, I break the loop.

Then we determine the index of the current car to see whether the next car I need to show is the next one in the array (if there is one) if not, we have arrived at the end of the array so I show the first car in the array.

Does anyone know more than I do about this? Would really be appreciated thank you!

func cycleCarNext() {
        DispatchQueue.main.async { [weak self] in
            guard let self = self else { return }
            if !self.alreadyCyclingCars {
                self.alreadyCyclingCars = true
                var indexOfCurrentCar = 0
                for (index, car) in UserData.shared.unlockedCars.enumerated() {
                    if car.type == self.overlayScene.currentCarOnDisplay {
                        indexOfCurrentCar = index
                        break
                    }
                }

                if indexOfCurrentCar < UserData.shared.unlockedCars.count - 1 {
                    let nextCar = UserData.shared.unlockedCars[indexOfCurrentCar+1]
                    self.playerNode.removeFromParentNode()
                    self.playerNode = nextCar
                    self.playerNode.name = "player"
                    self.playerNode.position = SCNVector3(x: 17, y: 0.3, z: 0)
                    self.playerNode.eulerAngles = SCNVector3(x: 0, y: toRadians(angle: 45),z: 0)
                    self.scene.rootNode.addChildNode(self.playerNode)
                    self.overlayScene.currentCarOnDisplay = nextCar.type
                    self.overlayScene.updateGarageInterface()
                } else {
                    guard let nextCar = UserData.shared.unlockedCars.first else { return }
                    self.playerNode.removeFromParentNode()
                    self.playerNode = nextCar
                    self.playerNode.name = "player"
                    self.playerNode.position = SCNVector3(x: 17, y: 0.3, z: 0)
                    self.playerNode.eulerAngles = SCNVector3(x: 0, y: toRadians(angle: 45),z: 0)
                    self.scene.rootNode.addChildNode(self.playerNode)
                    self.overlayScene.currentCarOnDisplay = nextCar.type
                    self.overlayScene.updateGarageInterface()
                }
                self.alreadyCyclingCars = false
            }
        }
    }

Upvotes: 13

Views: 3811

Answers (5)

Alfredo Luco G
Alfredo Luco G

Reputation: 974

Maybe you didn't init the object and its seems to render before the allocation. You can fix this using the awakeFromNib method. Like this:

override func awakeFromNib() {
    super.awakeFromNib()
    createScene()
}

Upvotes: 0

Ozgur Sahin
Ozgur Sahin

Reputation: 1453

If "GPU Frame Capture" is enabled in your scheme disabling it fixes this issue:

In Xcode go to your current scheme -> select »Edit Scheme…« -> Run/Options: set »GPU Frame Capture« to Disabled.

Taken from here.

Upvotes: 0

Andrew Chinery
Andrew Chinery

Reputation: 231

I've been struggling with this error for a while, and I just wanted to add a note in case it helps anyone using SCNRenderer, which is that I was using

.render(withViewport:commandBuffer:passDescriptor:)

but this does not call the delegate render method. Instead, use

.render(atTime:viewport:commandBuffer:passDescriptor:)

even if you are not using the time interval parameter, and then the delegate method renderer(_:updateAtTime:) will be called, where you can make the scene updates safely.

Upvotes: 0

coldfire
coldfire

Reputation: 1005

Faced with the same problem and figured out that this crash caused by personSegmentationWithDepth on devices with LiDAR

if ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentationWithDepth) {
    configuration.frameSemantics.insert(.personSegmentationWithDepth)
}

Upvotes: 0

lock
lock

Reputation: 2897

It's my experience that those kind of errors occur when you attempt to modify SceneKit's scene graph (add/remove nodes, etc) outside the SCNSceneRendererDelegate delegate methods.

Imagine you have one thread that is performing rendering at 60fps, and another (eg; the main thread) that removes a node from what is to be rendered. At some point the rendering thread is going to be part way through rendering when what it is rendering is removed by the other thread. This is when the EXC_BAD_ACCESS occurs. The more times the scene is modified, the more likely you are to see this conflict, hence why button mashing could more readily reproduce the issue.

The fix is to only modify your scene in one of SCNSceneRendererDelegate delegate methods. I'd try something like...

func cycleCarNext() {
    self.cycleNextCar = true
}

func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
    if (self.cycleNextCar) {
        self.doCycleNextCar()
        self.cycleNextCar = false
    }
}

func doCycleNextCar() {
    var indexOfCurrentCar = 0
    for (index, car) in UserData.shared.unlockedCars.enumerated() {
        if car.type == self.overlayScene.currentCarOnDisplay {
            indexOfCurrentCar = index
            break
        }
    }

    if indexOfCurrentCar < UserData.shared.unlockedCars.count - 1 {
        let nextCar = UserData.shared.unlockedCars[indexOfCurrentCar+1]
        self.playerNode.removeFromParentNode()
        self.playerNode = nextCar
        self.playerNode.name = "player"
        self.playerNode.position = SCNVector3(x: 17, y: 0.3, z: 0)
        self.playerNode.eulerAngles = SCNVector3(x: 0, y: toRadians(angle: 45),z: 0)
        self.scene.rootNode.addChildNode(self.playerNode)
        self.overlayScene.currentCarOnDisplay = nextCar.type
        self.overlayScene.updateGarageInterface()
    } else {
        guard let nextCar = UserData.shared.unlockedCars.first else { return }
        self.playerNode.removeFromParentNode()
        self.playerNode = nextCar
        self.playerNode.name = "player"
        self.playerNode.position = SCNVector3(x: 17, y: 0.3, z: 0)
        self.playerNode.eulerAngles = SCNVector3(x: 0, y: toRadians(angle: 45),z: 0)
        self.scene.rootNode.addChildNode(self.playerNode)
        self.overlayScene.currentCarOnDisplay = nextCar.type
        self.overlayScene.updateGarageInterface()
    }
}

cycleCarNext is to be called by your main thread as it currently is. You'll likely need to set the SCNView's delegate somewhere too (eg; sceneView.delegate = self)

The idea is that while the cycleCarNext boolean is set immediately in the main thread, the scene isn't changed. Instead the change occurs at the correct time/thread in the SceneKit rendering loop.

Upvotes: 11

Related Questions