Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EXC_BAD_ACCESS at removeFromParentNode() SceneKit

Tags:

scenekit

I've got the following error popping up randomly during my game execution:

enter image description here

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: enter image description here

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.

like image 539
Alessandro Avatar asked Feb 20 '16 22:02

Alessandro


1 Answers

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.

like image 181
Hal Mueller Avatar answered Oct 08 '22 20:10

Hal Mueller