SceneKit leaking when cloning a node - swift

I have an asset loading and caching singleton defined as such:
class AssetLoader {
fileprivate var rootNodes = Dictionary<String, SCNNode>()
static let sharedInstance = AssetLoader()
fileprivate init() {
}
func rootNode(_ named: String) -> SCNNode {
if self.rootNodes[named] != nil {
return self.rootNodes[named]!.clone()
} else {
let scene = SCNScene(named: "art.scnassets/\(named).scn")
self.rootNodes[named] = scene!.rootNode
return self.rootNodes[named]!.clone()
}
}
}
I am using it to make my scene building faster. I'm creating assets from extensions as such:
extension CAAnimation {
class func animationWithScene(named: String) -> CAAnimation? {
unowned let rootNode = AssetLoader.sharedInstance.rootNode(named)
var animation: CAAnimation?
rootNode.enumerateChildNodes({ (child, stop) in
if child.animationKeys.count > 0 {
animation = child.animation(forKey: child.animationKeys.first!)
stop.initialize(to: true)
}
})
return animation
}
}
extension SCNNode {
class func nodeWithScene(named: String) -> SCNNode? {
unowned let rootNode = AssetLoader.sharedInstance.rootNode(named)
let node = SCNNode()
for child in rootNode.childNodes {
node.addChildNode(child)
}
node.eulerAngles = SCNVector3(x: Float(-M_PI_2), y: 0, z: 0)
node.scale = SCNVector3Make(kMeshScale, kMeshScale, kMeshScale)
return node
}
}
Instruments is saying I'm leaking memory like crazy on each calls to clone(). I tried using weak and unowned wherever I could without causing crashes and it doesn't change anything. Anyone has a clue? Is that a bug in SceneKit?
Thanks

If I understand correctly you keep your original nodes in the rootNodes Dictionary of your AssetLoader and return a clone of those in the rootNode func.
My architecture is similar and my issue was the following : when I would remove the cloned node from the scene tree the memory wouldn't get released. Is that your problem?
I fixed the issue by adding an "unload" func in my singleton to nullify the original nodes when removing the cloned nodes from the scene tree. That fixed my memory issues.
With your code that would look something like :
func unloadRootNode(_ named: String) {
rootNodes.removeValue(forKey: named)
}

Related

Casting an SKSpriteNode in an SKReferenceNode

I've build a Scene1 in Xcode's Scene editor. And I've referenced another scene which has animation to this Scene1.
Now, I'm trying to cast-out an SKSpriteNode which is inside an SKReferenceNode.
The name of the SKSpriteNode that I'm trying to cast, on a scene which was references is: "sc01eyelid".
Any suggestions what I might do wrong here?
thank you in advance.
import SpriteKit
import GameplayKit
class Scene1: SKScene {
var misha: SKReferenceNode = SKReferenceNode()
var eyelidForScene1:SKSpriteNode = SKSpriteNode()
override func didMove(to view: SKView) {
castMishaForScene1()
castOutEyelid()
}
//Casting out misha
func castMishaForScene1() {
if let someSpriteNode:SKReferenceNode = self.childNode(withName: "mishaRefNode") as? SKReferenceNode {
misha = someSpriteNode
print("CASTED\(misha)")
}
else {
print("could not cast\(misha)")
}
}
//Casting out eyelid
func castOutEyelid() {
if let someSpriteNode:SKSpriteNode = misha.childNode(withName: "sc01eyelid") as? SKSpriteNode {
eyelidForScene1 = someSpriteNode
print("CASTED\(eyelidForScene1)")
}
else {
print("could not cast\(eyelidForScene1)")
}
}
}
In order to access any Node of the SKRefference one needs to put additional "//" in the withName statement:
if let someSpriteNode:SKSpriteNode = misha.childNode(withName: "//sc01eyelid") as? SKSpriteNode {}
So withName: "//sc01eyelid" would work to access the sc01eyelid Node.
More info here:
https://developer.apple.com/documentation/spritekit/sknode

How could I prevent duplicate references to class objects in an array?

I'm looking for a way to prevent an array from having duplicate references to the same object in it. I don't mean duplicate values - that would be fine.
For example, I know in Apple's SpriteKit framework, an SKNode object stores its children in an array (var children: [SKNode]), and adding a node to a parent twice causes the program to crash.
let parent = SKNode()
let child1 = SKNode()
let child2 = SKNode()
parent.addChild(child1)
parent.addChild(child2) // Allowed, although child2 == child1.
parent.addChild(child1) // Causes a crash.
This is the exact kind of behavior I am wanting to emulate. How would I manage this? Is it possible without having O(n) complexity from having to compare each reference?
I'm not sure why exactly you would want to do this, but here is how you can go about doing it:
...
var childReferences = Set<ObjectIdentifier>
private func validateUniqueness(_ node: SKNode) {
guard childReferences.insert(ObjectIdentifier(node)).inserted else {
fatalError("An attempt was made to add a duplicate child")
}
}
override func addChild(_ node: SKNode) {
validateUniqueness(node)
super.addChild(node)
}
override func insertChild(_ node: SKNode at index: Int) {
validateUniqueness(node)
super.insertChild(node, at: index)
}
override func removeChildren(in nodes: [SKNode]) {
childReferences.subtract(nodes)
super.removeChildren(in nodes: [SKNode])
}
override func removeAllChildren() {
childReferences.removeAll(keepingCapacity: false)
super.removeAllChildren()
}

Gameplaykit: Managing multiple objects using GKStatemachines

I'm writing a game that has a number of switch sprites that can be moved by the game player.I was intending to use a 'Game-play-kit' state machine to organize my code. I can't figure out how to manage multiple state machines - specifically I store my switches in an array, and each switch object includes a statemachine - how do I reference the 'parent' switch from within GKState classses in order to change it's properties(in this case running a new animation?)
This is my switch class:
class RailSwitch: SKSpriteNode {
var switchID: Int
var currentSwitchPosition: switchPosition
var initialSwitchPosition: switchPosition
var switchLocation: CGPoint
var isSwitchLocked: Bool
var isLeftMiddleSwitch: Bool
var currentAnimation: switchAnimation /* this is a dictionary of animation textures */
var stateMachine : GKStateMachine!
init(switchID: Int,
switchLocation: CGPoint,
initialSwitchPosition: switchPosition,
isSwitchLocked: Bool,
isLeftMiddleSwitch: Bool,
currentAnimation: switchAnimation,
texture:SKTexture!) {
self.switchID = switchID
self.switchLocation = switchLocation
self.initialSwitchPosition = initialSwitchPosition
self.currentSwitchPosition = initialSwitchPosition
self.isSwitchLocked = isSwitchLocked
self.isLeftMiddleSwitch = isLeftMiddleSwitch
self.currentAnimation = currentAnimation
super.init (texture: texture!, color: UIColor.clearColor(), size: texture!.size())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
This is my switch Network class:
class SwitchNetwork {
var level : Int
var switchNetwork = [RailSwitch]()
var railSwitchAnimation : [switchAnimationState: switchAnimation]
init (level:Int) {
self.level = level
self.switchNetwork = []
self.railSwitchAnimation = [:]
}
func initialiseSwitches() {
/* one test example switch - in practice there will be many*/
railSwitchAnimation = loadSwitchAnimations()
switchNetwork.append(RailSwitch(switchID: 1,
switchLocation: CGPointMake(400,300),
initialSwitchPosition: (.left) ,
isSwitchLocked: false,
isLeftMiddleSwitch: true,
currentAnimation: railSwitchAnimation[.left]!,
texture: railSwitchAnimation[.left]!.textures[0]
))
}
I initiate the switches from within GameScene:
func initiateSwitchNetwork() {
for thisSwitch in 0 ... switches.switchNetwork.count - 1 {
switches.switchNetwork[thisSwitch].stateMachine = GKStateMachine(states: [
GameStart(scene: self),
SwitchLeft(scene: self),
SwitchRight(scene: self),
SwitchMiddle(scene: self),
SwitchLeftLocked(scene: self),
SwitchRightLocked(scene: self),
SwitchMiddleLocked(scene: self)])
switches.switchNetwork[thisSwitch].stateMachine.enterState(GameStart)
}
Here's my question.From within the switch statemachine gkstate classes, who do I change the animation?I need to access the parent switch object that holds the statemachine somehow?
class GameStart: GKState {
unowned let scene: GameScene
init(scene: SKScene) {
self.scene = scene as! GameScene
super.init()
}
override func didEnterWithPreviousState(previousState: GKState?) {
// scene.addChild(scene.switches.switchNetwork[0].currentAnimation.textures[0])
}
}
One approach to consider is instead of passing the scene into each state's init function, you could pass a reference to the parent switch instead? So your state's init function looks like this;
init(switch: RailSwitch) {
self.railSwitch = switch
super.init()
}
Then your RailSwitch might have a function to change the animation which you would call in the state's updateWithDeltaTime function.
override func updateWithDeltaTime(seconds: NSTimeInterval) {
self.railSwitch.changeAnimation(to switchTexture: .left)
}
Note that you have access to the stateMachine in each state;
override func updateWithDeltaTime(seconds: NSTimeInterval) {
self.stateMachine?.enterState(SwitchRight.self)
}
As an aside, I would prefer to use Strategy Pattern to implement this kind of functionality, unless a switches next state is strongly determined by current state. Strategy is better suited where an external factor will determine the next change.

How do edit a sprite based on its name?

So in my game i have a function that spawns coins,they are given the name "coin", Now I have no way to reference the coins,example to kill them or move them.So what I'm trying to do is make a reference to be able to use in my code to just change its zPosition.
Everytime I run my app and have a function run that uses the coinRef [ex. to change the zPosition], the app crashes with the error:
'Thread 1 EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)'
Heres my code:
let coinRef: SKSpriteNode = self.childNodeWithName("coin")! as! SKSpriteNode
func hideCoins() {
coinRef.zPosition = -1
}
func showCoins() {
coinRef.zPosition = 101
}
func killCoins() {
coinRef.removeFromParent()
}
Looking at what you write
So in my game i have a function that spawns coins,they are given the name "coin"
it looks like there are multiple coins in your scene. As you can imagine a single name coin is not enough to univocally identify more then 1 coin :)
We'll need a way do identity multiple coins.
1. The Coin class
class Coin: SKSpriteNode {
private static var lastID: UInt = 0
let id:UInt
init() {
self.id = Coin.lastID++
let texture = SKTexture(imageNamed: "coin")
super.init(texture: texture, color: UIColor.clearColor(), size: texture.size())
self.name = "coin"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
As you can see Coin has an internal mechanism to assign a new id to each new instance. You can use this id to reference the coins in your scene.
let coin0 = Coin()
coin0.id // 0
let coin1 = Coin()
coin1.id // 1
let coin2 = Coin()
coin2.id // 2
2. Managing your coins
class GameScene: SKScene {
func retrieveCoin(id:UInt) -> Coin? {
return children.filter { ($0 as? Coin)?.id == id }.first as? Coin
}
func hideCoin(id:UInt) {
retrieveCoin(id)?.hidden = true
}
func showCoin(id:UInt) {
retrieveCoin(id)?.hidden = true
}
func deleteCoin(id:UInt) {
retrieveCoin(id)?.removeFromParent()
}
}
The retrieveCoin method returns (if does exist) a coin with the specified id. Otherwise nil is returned.
The hideCoin and showCoin do change the hidden property to change its visibility.
Finally deleteCoin remove from the scene the Coin with the specified id.
Try this. Initialise coinRef before the didMoveToView function, and then give coinRef its value in the didMoveToView function.
class scene : SKScene {
let coinRef: SKSpriteNode = SKSpriteNode()
override func didMoveToView(view: SKView) {
coinRef: SKSpriteNode = self.childNodeWithName("coin")! as! SKSpriteNode
}
func hideCoins() {
coinRef.zPosition = -1
}
func showCoins() {
coinRef.zPosition = 101
}
func killCoins() {
coinRef.removeFromParent()
}
}

Subclass of SKEmitterNode not calling init or deist

I am able to initialize this class, and see my particle effect, but the subclass doesn't call init() or deinit. Why?
class Particles: SKEmitterNode {
var test: Int?
override init() {
test = 1
super.init()
println("created particle emitter")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
println("destroyed particle emitter")
}
}
This is called from GameScene()
let particle = Particles(fileNamed:"test")
and I tried this to:
var particle: Particles?
viewDidLoad:
particle = Particles(fileNamed:"test")
Subclasses can only call superclass designated initialiser so cannot call super.init(fileNamed:). A workaround is not to make an initialiser at all but make a class method and perform setup code in there.
I copied the code apple provides to unarchive an sks scene and changed it to work with your emitter. The method finds the particle file if it exists and unarchives it as a Particles object:
class Particles: SKEmitterNode {
var test: Int?
class func fromFile(file : String) -> Particles? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var data = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: data)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKEmitterNode")
let p = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as Particles
archiver.finishDecoding()
//Perform any setup here
p.test=11
return p
}
return nil
}
}
and use it like so:
let p=Particles.fromFile("MyParticle")!
p.test=0
p.particleSpeed=10
addChild(p)