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:
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
}
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With