Search code examples
swiftmemory-leakssprite-kitgamekit

How can I avoid these circular references in my GameKit game? I'm using GKAgents2D and GKBehaviours


I am using GKAgents, GKGoals and GKBehaviours, but I'm getting leaked memory between my Behaviour and Component. I think images and code may better explain my problem:

In my entity manager I return all drone entities:

func droneEntities() -> [GKAgent2D] {
        for componentSystem in componentSystems {
            if componentSystem.componentClass is DroneMoveComponent.Type {
                let components = componentSystem.components

                return components.flatMap{ component in
                    return component as? GKAgent2D
                }
            }
        }
        return []
    }

My Dronemovecomponent:

import SpriteKit
import GameplayKit

class DroneMoveComponent: GKAgent2D, GKAgentDelegate {

    weak var entityManager: EntityManager?

    init(entityManager: EntityManager) {
        self.entityManager = entityManager
        super.init()
        delegate = self
        self.maxSpeed = 50.0
        self.mass = 0.01
        self.radius = 16
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    func agentWillUpdate(_ agent: GKAgent) {
        guard let spriteComponent = entity?.component(ofType: SpriteComponent.self) else {
            return
        }

        position = vector_float2(Float(spriteComponent.node.position.x), Float(spriteComponent.node.position.y))
    }

    func agentDidUpdate(_ agent: GKAgent) {
        guard let spriteComponent = entity?.component(ofType: SpriteComponent.self) else {
            return
        }

        spriteComponent.node.position = CGPoint(x: CGFloat(position.x), y: CGFloat(position.y))
    }

    override func update(deltaTime seconds: TimeInterval) {
        super.update(deltaTime: seconds)

        if let controlledComponent = entityManager?.playerEntity?.component(ofType: ControlledComponent.self) {

            let droneEntities = entityManager?.droneEntities().filter{$0 != self}

            behavior = DroneMoveBehaviour(seek: controlledComponent, avoid: (entityManager?.berkEntities())!, drones: droneEntities!)
        }
    }
}

My DroneMoveBehaviour:

import GameplayKit
import SpriteKit

class DroneMoveBehaviour: GKBehavior {
    init(seek: GKAgent, avoid: [GKAgent], drones: [GKAgent]) {

        super.init()
        setWeight(500, for: GKGoal(toReachTargetSpeed: 50))
        setWeight(100, for: GKGoal(toInterceptAgent: seek, maxPredictionTime: 10))
        setWeight(1000, for: GKGoal(toAvoid: avoid, maxPredictionTime: 10))
        setWeight(1000, for: GKGoal(toAvoid: drones, maxPredictionTime: 10))
    }
}

Here is an image of my memory graph:

enter image description here

I have another set of components and behaviours leaking in the same way, I figured if somebody could help me understand how to fix this, I could go on and correct the other ones myself.

Should the array contain weak references? Should I switch to NSPointerArray? Or am I overlooking something else? Any guidance here will be greatly appreciated, thanks.


Solution

  • Setting the DroneMoveComponents behaviour on every update is not necessary! Moving this set only once in the constructor seems to prevent the leaks.