I've got the following error popping up randomly during my game execution:
I am creating multiple instances of a carNode, at random moments in time, and all of them has this action of removing the node from the scene. Most of the time it is not a problem, but in some cases the app crashes with the error displayed in the photo. It seems like I could get the error in these cases:
An object is not initialized An object is already released Something else that is not very likely to happen
..excluding the first one, I'm more than sure that I am not releasing the object ahead of time.. so what could it be?
This is the complete code:
func spawnCarAtPosition(position: SCNVector3) {
// Create a material using the model_texture.tga image
let carMaterial = SCNMaterial()
carMaterial.diffuse.contents = UIImage(named: "assets.scnassets/Textures/model_texture.tga")
carMaterial.locksAmbientWithDiffuse = false
// Create a clone of the Car node of the carScene - you need a clone because you need to add many cars
var carNode: SCNNode!
let randomNumb = AppDelegate().randRange(0, upper: beachCarArray.count - 1)
let selectedObject = beachCarArray[randomNumb]
carNode = selectedObject.objectscene.rootNode.childNodeWithName(selectedObject.objectname, recursively: false)!.clone() as SCNNode
carNode.name = selectedObject.objectname
carNode.position = position
// Set the material
carNode.geometry!.firstMaterial = carMaterial
// x = length, y = height, z = width
let xscale = 0.6
let yscale = 0.8
let zscale = 0.5
carNode.scale = SCNVector3(xscale, yscale, zscale)
// Create a physicsbody for collision detection
let boundingBox = AppDelegate().sizeOfBoundingBoxFromNode(carNode)
let carPhysicsBodyShape = SCNPhysicsShape(geometry: SCNBox(width: CGFloat(boundingBox.width * Float(xscale)), height: CGFloat(boundingBox.height * Float(yscale)), length: CGFloat(boundingBox.depth * Float(zscale)), chamferRadius: 0.0), options: nil)
carNode.physicsBody = SCNPhysicsBody(type: SCNPhysicsBodyType.Kinematic, shape: carPhysicsBodyShape)
carNode.physicsBody!.categoryBitMask = PhysicsCategory.Car
carNode.physicsBody!.collisionBitMask = PhysicsCategory.Player
carNode.physicsBody?.contactTestBitMask = PhysicsCategory.Player
// Move the car
let moveDirection: Float = position.x > 0.0 ? -1.0 : 1.0
let moveDistance = levelData.gameLevelWidth()
let moveAction = SCNAction.moveBy(SCNVector3(x: moveDistance * moveDirection, y: 0.0, z: 0.0), duration: Double(AppDelegate().randomBetweenNumbers(6.0, secondNum: 7.0)))
let removeAction = SCNAction.runBlock { node -> Void in
node.removeFromParentNode()
}
carNode.runAction(SCNAction.sequence([moveAction, removeAction]))
// Rotate the car to move it in the right direction
if moveDirection > 0.0 {
carNode.rotation = SCNVector4(x: 0.0, y: 1.0, z: 0.0, w: 3.1415)
}
// add node to screen
rootNode.addChildNode(carNode)
}
EDIT:
Print of the object before crash:
<SCNNode: 0x1374e5ba0 'BeachCar_2' pos(1.500000 0.035000 -4.800000) rot(0.000000 1.000000 0.000000 3.141500) scale(0.600000 0.800000 0.500000) | geometry=<SCNGeometry: 0x135fe80c0 'BeachCar_2'> | no child>
With SCNAnimation remove action:
EDIT 2:
func createSpawnNode(gridPosition: SCNVector3, row: Int, parentNode: SCNNode) {
// Determine if the car should start from the left of the right
let startCol = row % 2 == 0 ? 0 : data.columnCount() - 1
let moveDirection : Float = row % 2 == 0 ? 1.0 : -1.0
// Determine the position of the node
var position = coordinatesForGridPosition(column: startCol, row: row)
position = SCNVector3(x: position.x, y: GameVariables.gamePlaneHeight / 2, z: position.z)
// Create node
let spawnNode = SCNNode()
spawnNode.position = position
var spawnAction: SCNAction!
var delayAction: SCNAction!
// Create an action to make the node spawn cars
spawnAction = SCNAction.runBlock({ node in
self.spawnDelegate!.spawnCarAtPosition(node.position)
})
delayAction = SCNAction.waitForDuration(3.0, withRange: 2.0)
spawnNode.runAction(SCNAction.repeatActionForever(SCNAction.sequence([spawnAction, delayAction])))
parentNode.addChildNode(spawnNode)
// record newly created node
recordNodeAtRow(spawnNode, row: row)
}
The parentNode is the rootNode of the Scene.
Speculating...maybe something internal is holding a reference to carNode
that lasts past your removeFromParentNode()
call? Looks like there's a retain cycle: carNode
holds a strong reference to removeAction
, whose closure holds a (strong?) reference back to carNode
.
I take the existence of removeFromParentNode
on SCNAction
to be a very big hint, though. It wouldn't be there if it weren't needed. Instead of a closure-based call, try
let removeAction = SCNAction.removeFromParentNode()
carNode.runAction(SCNAction.sequence([moveAction, removeAction]))
And just to make sure: you're invoking spawnCarAtPosition()
from the main thread, not a background thread, right?
Edit after your EDIT 2:
createSpawnNode()
was a bit confusing to read, because of your reuse of position
(how about two let
definitions instead of one var
?) (the other two var
defs would be more straightforward as let
, btw). I don't see anything that I think would cause a crash.
Instead of copying the source Car's objectName
, I'd give each node a unique name (maybe NSDate().description
) to see if you can detect a pattern.
I'd also try moving the spawning out of an SCNAction, and into a render callback (probably renderer(_:updateAtTime:)
, in case you're tickling an internal SceneKit bug. You are invoking one SCNAction
within another. Documentation is silent on whether that's allowed, but since you're seeing erratic behavior it seems like a good thing to simplify out.
I assume you've tried the standard tricks of turning on zombies, and setting an exception breakpoint.
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