Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Persistence of SCNAudioPlayer on SCNNodes in Xcode Instrument

I have created a subclass of SCNNode. It is made up of few child nodes. I have declared a method, viz. soundCasual() which adds a SCNAudioPlayer to the instance of this class. Everything is working as expected and audio is being played, when this method is called. This method is called on that node whenever that node is tapped (gesture).

Code:

class MyNode: SCNNode {
    let wrapperNode = SCNNode()
    let audioSource5 = SCNAudioSource(fileNamed: "audiofile.mp3")

    override init() {
        super.init()

        if let virtualScene = SCNScene(named: "MyNode.scn", inDirectory: "Assets.scnassets/Shapes/MyNode") {
            for child in virtualScene.rootNode.childNodes {
                wrapperNode.addChildNode(child)
            }
        }
    }

   func soundCasual() {
        DispatchQueue.global(qos: .userInteractive).async { [weak self] in
            if let audioSource = self?.audioSource5 {
                let audioPlayer = SCNAudioPlayer(source: audioSource)
                self?.wrapperNode.removeAllAudioPlayers()
                self?.wrapperNode.addAudioPlayer(audioPlayer)
            }
        }
    }
}

Issue within Instruments (Allocations)

When I analyse my whole codebase (which is several other things), I see that whenever I tap on that node, allocation count of SCNAudioPlayer is increased by one while profiling within Instruments. But all of the increase is persistent. By definition of SCNAudioPlayer, I assumed that this the player is removed after playback, which is why the increment should be in Transient allocations, but it is not working like this. That is why I tried removeAllAudioPlayers() before adding an SCNAudioPlayer to the node, as you can see in the code for soundCasual(). But the issue remains.

Till this snapshot was taken, I had tapped on that node about 17 times, and it also shows 17 against Persistent allocations for SCNAudioPlayer.

Note: SCNAudioSource is 10, as it should be, since there are 10 audio source I am using in the app

enter image description here

And this is happening for all other SCNNodes in my application without fail. Kindly help as I am not able to understand what exactly am I missing.

EDIT

As per recommended, I changed my init() as

let path = Bundle.main.path(forResource: "Keemo", ofType: "scn", inDirectory: "Assets.scnassets/Shapes/Keemo")
if let path = path , let keemo = SCNReferenceNode(url: URL(fileURLWithPath: path)) {
    keemo.load()
}
func soundPlay() {
    DispatchQueue.global(qos: .userInteractive).async { [weak self] in
        if let audioSource = self?.audioSourcePlay {
            audioSource.volume = 0.1
            let audioPlayer = SCNAudioPlayer(source: audioSource)
            self?.removeAllAudioPlayers()
            self?.addAudioPlayer(audioPlayer)
        }
    }
}

Despite, allocations in Instruments show audioPlayers as persistent. Though on checking node.audioPlayers it shows that at one point, there is only one audioPlayer node attached.

EDIT

This issue appears even in a simple case when I use the boilerplate codebase attached in a Scenekit app created by default in XCode. Hence, this issue has been raised as a bug to Apple. https://bugreport.apple.com/web/?problemID=43482539

WORKAROUND

I am using AVAudioPlayer instead of SCNAudioPlayer, not exactly the same thing, but at least memory this way will not cause a crash.

like image 622
Manganese Avatar asked Jul 19 '18 20:07

Manganese


2 Answers

If you haven't found the answer already, you need to remove the SCNAudioPlayer from the node once it has completed playing:

if let audioSource = self?.audioSourcePlay {
    audioSource.volume = 0.1
    let audioPlayer = SCNAudioPlayer(source: audioSource)
    self?.removeAllAudioPlayers()
    self?.addAudioPlayer(audioPlayer)
    self?.audioplayer.didFinishPlayback = {
        self?.removeAudioPlayer(audioPlayer)
    }
}
like image 123
Dominic Avatar answered Nov 02 '22 18:11

Dominic


I am not familiar with SceneKit, but from my experience with UIKit and SpriteKit I suspect that your use of wrapperNode and virtualScene is messing with the garbage collector.

I would try removing wrapperNode and adding everything to self (since self is a SCNNode).

Which node is being used in your scene? self or wrapperNode? And with your sample code wrapperNode is not added to self so it may or may not actually be part of the scene.

Also, you should probably be using SCNReferenceNode instead of the virtual scene thing you're using.

!!! this code has not been tested !!!

class MyNode: SCNReferenceNode {
    let audioSource5 = SCNAudioSource(fileNamed: "audiofile.mp3")

    func soundCasual() {
        DispatchQueue.global(qos: .userInteractive).async { [weak self] in
            if let audioSource = self?.audioSource5 {
                let audioPlayer = SCNAudioPlayer(source: audioSource)
                self?.removeAllAudioPlayers()
                self?.addAudioPlayer(audioPlayer)
            }
        }
    }
}


// If you programmatically create this node, you'll have to call .load() on it
let referenceNode = SCNReferenceNode(URL: referenceURL)
referenceNode.load()

HtH!

like image 28
Stephen Furlani Avatar answered Nov 02 '22 18:11

Stephen Furlani