Big garbage collection question

I’ve asked many of questions about garbage collection, since I am not a programmer and I don’t really understand how it works in Panda. I want to change that with one big and hopefully final question.

Below is the entire code block for the vehicle class in my game. When the race is over, I will need to remove this class and all it’s subclasses from memory, completely and totally. For the subclasses like the guns, I can take what I learn from this question and write clean up functions for them on my own.

What I need to know is this: What do I need to do to remove every scrap and trace of an instance of this class from memory?

[code]class Cycle(DirectObject):
def init(self, dataFile, assetLoader, track, boomManager, cycleManager, team, polePos, color, joy = None, ai = False):

	self.setupVarsNodes(assetLoader, dataFile, track, boomManager, cycleManager, team, polePos, color)
	self.setupCollisions()
	# Calls the setup functions common to all cycles
	
	if(ai == False):
		self.setupPS(joy)
		# Sets up the player specific components.
		
		#self.setupCamera()
		self.hud = HUD(self)
		# Sets the camera to follow this cycle and creates a HUD.

	else:
		self.setupAIS(polePos)
		# Sets up the player specific components.
		#if(self.name == "Victor"): 
		#	self.hud = HUD(self)
		#	self.setupCamera()
		# Sets the camera to follow this cycle and creates a HUD.
		
	return

def setupVarsNodes(self, assetLoader, dataFile, track, boomManager, cycleManager, team, polePos, color):
	
	self.assetLoader = assetLoader	# Stores a reference to the assetLoader.
	self.track = track				# Stores a reference to the track.
	self.boomManager = boomManager	# Stores a reference to the boom manager.
	self.team = team				# Stores the team number for the team the cycle is on.
	self.cycleManager = cycleManager
	
	dataFile = self.assetLoader.loadFile("Cycles/" + dataFile, "r")
	cycleData = formatData(dataFile)
	dataFile.close()
	
	pwrFile = self.assetLoader.loadFile("Parts/" + cycleData["Power Plant"], "r")
	pwrData = formatPwrData(pwrFile)
	pwrFile.close()
	
	discFile = self.assetLoader.loadFile("Parts/" + cycleData["Discs"], "r")
	discData = formatDiscData(discFile)
	discFile.close()
	
	shieldFile = self.assetLoader.loadFile("Parts/" + cycleData["Shield Projector"], "r")
	shieldData = formatShieldData(shieldFile)
	shieldFile.close()
	
	pilotFile = self.assetLoader.loadFile("Pilots/" + cycleData["Pilot"], "r")
	pilotData = formatPilotData(pilotFile)
	pilotFile.close()
	
	self.name = pilotData["Name"]
	self.topSpeed = (pwrData["Top Speed"] + discData["Top Speed"]) * pilotData["Top Speed"] # The cycle's top speed in kph
	self.handling = discData["Handling"] * pilotData["Handling"] # The cycle's turning speed in degrees/second. This number should be half of the desired degrees/second.
	self.acceleration = (pwrData["Acceleration"] + discData["Acceleration"]) * pilotData["Acceleration"]	# The cycle's rate of acceleration in kph/second
	self.maxShield = shieldData["Shield Strength"] * pilotData["Shield Strength"]	# The maximum strength of the cycle's shield
	self.shieldRecharge = shieldData["Shield Recharge"] * pilotData["Shield Recharge"]  # The recharge rate of the shield, in units per second.
	self.stability = shieldData["Stability"] * pilotData["Stability"] # The amount of impact the shield can absorb before it affects the cycle's control.
	self.maxEnergy = pwrData["Energy"] # The maximum energy pool the power plant has for weapons fire.
	
	self.dirVec = Vec3(0,0,0)
	self.cycleVec = Vec3(0,0,0)
	self.refVec = Vec3(0,0,1)
	# creates three vectors to be used in aligning the cycle's facing and it's movement.
	
	self.lanes = self.track.lanes	# store a reference to the track markers
	
	self.uc1 = self.lanes[polePos[0]][0] # Preps the first up coming marker variable with the first marker in the lane.
	self.uc2 = self.lanes[polePos[0]][1] # Preps the second up coming marker variable with the second marker in the lane.
	self.uc3 = self.lanes[polePos[0]][2] # Preps the third up coming marker variable with the third marker in the lane.
	
	self.speed = 0			# The cycle's current speed in kph
	self.throttle = 0		# The setting for the throttle in percent. Ranges from -1 - 1.
	self.currentLap = 0		# A lap counter for keeping track of completed laps.
	self.markerCount = 0	# Counts the markers that the cycle has passed to track it's race progress.
	self.freeFall = False	# Indicates if the cycle is in free fall or not.
	self.fallSpeed = 0		# The current speed of falling for the cycle
	self.momentum = 0		# indicates the speed of forward/upward motion during a free fall.
	self.turretSpeed = 30	# The turret's turning speed in degrees/second.
	self.currentShield = self.maxShield  # The current strength of the cycle's shield
	self.currentEnergy = self.maxEnergy # The current energy of the cycle.
	self.weapons = [[],[],[],[]]		# Creates a list to store all the weapons on the cycle.
	
	self.root = render.attachNewNode(self.name + "_CycleRoot")
	self.root.setPos(self.uc1.getPos().getX(), polePos[1], 0)
	# creates a root node in the scene graph and repositions it.
	
	self.refNode = self.root.attachNewNode(self.name + "_CycleRefNode")
	# creates a reference node as a child of the root. This node is used to gather vector info.
	
	self.dirNode = self.root.attachNewNode(self.name + "_DirectionNode")
	# create a node that will face in the direction the cycle is moving.
	

	self.cycle = self.assetLoader.loadActor("Parts/Frame.bam")
	self.cycle.reparentTo(self.root)
	self.coffinMount = self.cycle.exposeJoint(None, "modelRoot", "Coffin")
	self.fuelMount = self.cycle.exposeJoint(None, "modelRoot", "FuelTank")
	self.turretMount = self.cycle.exposeJoint(None, "modelRoot", "TurretMount")
	self.shieldVisMount = self.cycle.exposeJoint(None, "modelRoot", "ShieldVis")
	self.frontDiscMount = self.cycle.exposeJoint(None, "modelRoot", "FrontDisc")
	self.rearDiscMount = self.cycle.exposeJoint(None, "modelRoot", "RearDisc")
	self.shieldProjMount = self.cycle.exposeJoint(None, "modelRoot", "ShieldProjector")
	self.powerPlantMount = self.cycle.exposeJoint(None, "modelRoot", "PowerPlant")
	self.rightWingMount = self.cycle.exposeJoint(None, "modelRoot", "RightWing")
	self.leftWingMount = self.cycle.exposeJoint(None, "modelRoot", "LeftWing")
	self.frontMoldingMount = self.cycle.exposeJoint(None, "modelRoot", "FrontMolding")
	self.rearMoldingMount = self.cycle.exposeJoint(None, "modelRoot", "RearMolding")
	
	self.coffin = self.assetLoader.loadModel("Parts/Coffin.bam")
	self.coffin.reparentTo(self.coffinMount)
	self.fuelTank = self.assetLoader.loadModel("Parts/FuelTank.bam")
	self.fuelTank.reparentTo(self.fuelMount)
	self.shieldVis = self.assetLoader.loadModel("Parts/ShieldVis.bam")
	self.shieldVis.reparentTo(self.shieldVisMount)
	
	self.frontMolding = self.assetLoader.loadModel("Parts/" + pilotData["Team"] + "/FrontMolding.bam")
	self.frontMolding.reparentTo(self.frontMoldingMount)
	self.rearMolding = self.assetLoader.loadActor("Parts/" + pilotData["Team"] + "/RearMolding.bam")
	self.rearMolding.reparentTo(self.rearMoldingMount)
	self.rightWing = self.assetLoader.loadActor("Parts/" + pilotData["Team"] + "/RightWing.bam")
	self.rightWing.reparentTo(self.rightWingMount)
	self.leftWing = self.assetLoader.loadActor("Parts/" + pilotData["Team"] + "/LeftWing.bam")
	self.leftWing.reparentTo(self.leftWingMount)
	self.turret = self.assetLoader.loadActor("Parts/" + pilotData["Team"] + "/Turret.bam")
	self.turret.reparentTo(self.turretMount)
	
	self.powerPlant = self.assetLoader.loadModel("Parts/" + pwrData["Model"])
	self.powerPlant.reparentTo(self.powerPlantMount)
	self.shieldProj = self.assetLoader.loadModel("Parts/" + shieldData["Model"])
	self.shieldProj.reparentTo(self.shieldProjMount)
	self.frontDisc = self.assetLoader.loadModel("Parts/" + discData["Model"])
	self.frontDisc.reparentTo(self.frontDiscMount)
	self.rearDisc = self.assetLoader.loadModel("Parts/" + discData["Model"])
	self.rearDisc.reparentTo(self.rearDiscMount)
			
	if(cycleData["Turret Type"] == "Standard"):
		self.targeterMount = self.turret.exposeJoint(None, "modelRoot", "TargeterMount")
		self.heavyMount = self.turret.exposeJoint(None, "modelRoot", "HeavyMount")
		self.rightLightMount = self.turret.exposeJoint(None, "modelRoot", "RightLightMount")
		self.leftLightMount = self.turret.exposeJoint(None, "modelRoot", "LeftLightMount")
		# loads the turret model, reparents it to the turretMount, and exposes the gun mount joints.
	
		self.hvyGun = HeavyGun(self, cycleData["Heavy Gun"], self.heavyMount)
		self.weapons[0].append(self.hvyGun)
		self.leftLightGun = LightGun(self, cycleData["Light Gun L"], self.leftLightMount)
		self.weapons[0].append(self.leftLightGun)
		self.rightLightGun = LightGun(self, cycleData["Light Gun R"], self.rightLightMount)
		self.weapons[0].append(self.rightLightGun)
		# Loads the gun models, reparents them to the mount joints, and adds them to the list of weapons.
	
	self.rearWpnMount = self.rearMolding.exposeJoint(None, "modelRoot", "WeaponMount")
	if("Rear Layer" in cycleData):
		self.rearLayer = MineLayer(self, cycleData["Rear Layer"], self.rearWpnMount)
		self.weapons[3].append(self.rearLayer)
	elif("Rear Pod" in cycleData):
		self.rearPod = RearHunterPod(self, cycleData["Rear Pod"], self.rearWpnMount)
		self.weapons[3].append(self.rearPod)
	
	self.leftWpnMount = self.leftWing.exposeJoint(None, "modelRoot", "WeaponMount")
	if("Left Side Rack" in cycleData):
		self.leftRack = MissileRack(self, cycleData["Left Side Rack"], self.leftWpnMount, "L")
		self.weapons[1].append(self.leftRack)
	elif("Left Side Pod" in cycleData):
		self.leftPod = SideHunterPod(self, cycleData["Left Side Pod"], self.leftWpnMount, "L")
		self.weapons[2].append(self.leftPod)
	
	self.rightWpnMount = self.rightWing.exposeJoint(None, "modelRoot", "WeaponMount")
	if("Right Side Rack" in cycleData):
		self.rightRack = MissileRack(self, cycleData["Right Side Rack"], self.rightWpnMount, "R")
		self.weapons[1].append(self.rightRack)
	elif("Right Side Pod" in cycleData):
		self.rightPod = SideHunterPod(self, cycleData["Right Side Pod"], self.rightWpnMount, "R")
		self.weapons[2].append(self.rightPod)
		
	for L in self.weapons:
		for W in L:
			W.gunStats["Reload Time"] = W.gunStats["Reload Time"] * pilotData["Reload Time"]
			if("Accuracy" in W.gunStats):
				W.gunStats["Accuracy"] = W.gunStats["Accuracy"] * pilotData["Accuracy"]
	
	#self.shieldVis.setTransparency(TransparencyAttrib.MAlpha)
	#self.shieldVis.setColor(1,1,1,0)
	# Loads the visual for the cycle's shield, parents it to the cycle, turns on transparency, and sets the alpha to 0.
	
	self.trackNode = render.attachNewNode(self.name + "_TrackNode")
	# Create a node that will always maintain perfect "height" on the track and serve as the target position when "falling"
	
	return

Sets up most of the cycle’s arbitrary variables and all the non-collision nodes used by the cycle.

def setupPS(self, joy):
	
	self.ai = False
	self.activeWpnGroup = 0
	
	self.joy = joy	# Get a reference to the joystick.
	
	self.keyMap = { "f" : False,
					"a" : False,
					"s" : False,
					"d" : False,
					"q" : False,
					"w" : False,
					"e" : False,
					"r" : False,
					"g" : False}	# Create a dictionary of keyboard keys and boolean values.
					
	self.accept("f", self.setKey, ["f", True])
	self.accept("a", self.setKey, ["a", True])
	self.accept("s", self.setKey, ["s", True])
	self.accept("d", self.setKey, ["d", True])
	self.accept("q", self.setKey, ["q", True])
	self.accept("w", self.setKey, ["w", True])
	self.accept("e", self.setKey, ["e", True])
	self.accept("r", self.setKey, ["r", True])	
	self.accept("g", self.setKey, ["g", True])	# Register key down events and tie them to the setKey function.
	
	self.accept("f-up", self.setKey, ["f", False])
	self.accept("a-up", self.setKey, ["a", False])
	self.accept("s-up", self.setKey, ["s", False])
	self.accept("d-up", self.setKey, ["d", False])
	self.accept("q-up", self.setKey, ["q", False])
	self.accept("w-up", self.setKey, ["w", False])
	self.accept("e-up", self.setKey, ["e", False])
	self.accept("r-up", self.setKey, ["r", False])
	self.accept("g-up", self.setKey, ["g", False])	# Register key up events and tie them to the setKey function.
	
	self.accept("h", self.nextWpnGroup)
	
	return

Sets up the player specific components.

def setupAIS(self, polePos):
	
	self.ai = True
	self.targetThrottle = 0	# The thottle setting the AI wants to be at.
	self.target = None # The enemy the AI has chosen to fire on.
	self.blocker = None # Indicates if there is a cycle blocking the way for this cycle.
	self.currentLane = polePos[0]
	
	self.detectorCN = CollisionNode(self.name + "_DetectorCN")
	self.detectorCN.setPythonTag("owner", self)
	self.detectorCS = CollisionSphere(0,0,0, 50)
	self.detectorCN.addSolid(self.detectorCS)
	self.detectorCN.setIntoCollideMask(BitMask32.bit(5))
	self.detectorCN.setFromCollideMask(BitMask32.allOff())
	self.detectorCNP = self.root.attachNewNode(self.detectorCN)
	
	self.dCTrav = CollisionTraverser()
	self.dCHan = CollisionHandlerQueue()
	self.dCTrav.addCollider(self.detectorCNP, self.dCHan)
	
	

	return

Sets up the AI specific components.

def setupCamera(self):
	
	base.disableMouse()			# Disable mouse camera control
	base.camera.reparentTo(self.dirNode)	# Reparent the camera to the cycle so it will follow the cycle and turn with it.
	base.camera.setPos(0, -20, 6)	# Move the camera back and up to look over the cycle
	base.camera.setHpr(0, -10, 0)	# Angle the camera down slightly
	
	#base.camera.setPos(0,0, 40)
	#base.camera.lookAt(self.root)
	
	#base.camera.setPos(0, -100, 200)
	#base.camera.setHpr(0, -60, 0)
	
	return

Sets up the camera to follow this cycle.

def setupCollisions(self):
	
	self.shieldCS = []
	self.shieldCN = CollisionNode(self.name + "_ShieldCN")
	self.shieldCN.setPythonTag("owner", self)
	self.shieldCS.append(CollisionSphere(0, -.025, .75, .785))
	self.shieldCS.append(CollisionSphere(0, -1.075, .85, .835))
	self.shieldCS.append(CollisionSphere(0, 1.125, .6, .61))
	for CS in self.shieldCS:
		self.shieldCN.addSolid(CS)
	self.shieldCN.setIntoCollideMask(BitMask32.range(3,2))
	self.shieldCN.setFromCollideMask(BitMask32.bit(2))
	self.shieldCNP = self.cycle.attachNewNode(self.shieldCN)
	#self.shieldCNP.show()
	# Creates a collision node to contain the shield and adds all the shield spheres to it. The Into masks for this node
	# is set to bits 2, 3 and 4, and the From mask is set to 2.
	
	self.bumpCTrav = CollisionTraverser()
	#self.bumpCTrav.showCollisions(render)
	self.bumpHan = CollisionHandlerPusher()
	self.bumpHan.addCollider(self.shieldCNP, self.root)
	self.bumpHan.addAgainPattern("%fn-again")
	self.bumpCTrav.addCollider(self.shieldCNP, self.bumpHan)
	# Creates a collision traverser and a pusher handler to deal with the cycle bumping into walls and other cycles.
	# The pusher is set to create a uniquely named event when a collision occurs multiple times.
	
	self.accept(self.name + "_ShieldCN-again", self.bump)
	# Registers the uniquely named event of the pusher so that we can reduce speed when the cycle runs into things.

	self.targeterCN = CollisionNode(self.name + "_TargeterCN")
	self.targeterRay = CollisionRay(0,0,0,0,1,0)
	self.targeterCN.addSolid(self.targeterRay)
	self.targeterCN.setFromCollideMask(BitMask32.bit(3))
	self.targeterCN.setIntoCollideMask(BitMask32.allOff())
	self.targeterCNP = self.targeterMount.attachNewNode(self.targeterCN)
	#self.targeterCNP.show()
	# creates 1 collision ray to represent the line of fire of the cycle for the purpose shooting and locking on with missiles.
	# The ray is added to a collision node which is parented to the targeter mount of the turret.
	
	self.targeterCTrav = CollisionTraverser()
	self.targeterCHan = CollisionHandlerQueue()
	self.targeterCTrav.addCollider(self.targeterCNP, self.targeterCHan)
	# Creates a collision traverser and a queue handler to deal with collisions involving the targeter ray.
	
	self.gRayCN = CollisionNode(self.name + "_GRayCN")
	self.fRay = CollisionRay(0, .5, 10, 0, 0, -1)
	self.bRay = CollisionRay(0, -.5, 10, 0, 0, -1)
	self.gRayCN.addSolid(self.fRay)
	self.gRayCN.addSolid(self.bRay)
	self.gRayCN.setFromCollideMask(BitMask32.bit(1))
	self.gRayCN.setIntoCollideMask(BitMask32.allOff())
	self.gRayCNP = self.cycle.attachNewNode(self.gRayCN)
	#self.gRayNP.show()
	# Creates a collision ray called gRay and points it down. This ray will be used to monitor the track beneath the cycle
	# to keep the cycle a certain distance above the track and to ensure it's pitch and roll are correct.
	
	self.gCTrav = CollisionTraverser()
	self.gHan = CollisionHandlerQueue()
	self.gCTrav.addCollider(self.gRayCNP, self.gHan)
	# Creates a traverser and a handler for the ground ray and adds the gRay and it's handler to the traverser.
	
	return

Sets up all the from collision objects, their handlers, and their traversers as well as registering events for collision response.

def start(self):
	if(self.ai == True):
		taskMgr.add(self.runAI, self.name + "_runAI")	# Create a task to run the AI
		taskMgr.doMethodLater(.5, self.checkTarget, self.name + "_checkTarget")
	else:
		taskMgr.add(self.keyCheck, self.name + "_Key_Check")	# Create a task to moniter the key map and respond to key presses.

def throttleUp(self, dt):

	self.throttle = self.throttle + (.25 * dt)
	if(self.throttle > 1): self.throttle = 1
	return

increase the throttle setting by 10 %/second and constrain the throttle to a maximum of 100 percent.

def throttleDown(self, dt):
	
	self.throttle = self.throttle - (.25 * dt)
	if(self.throttle < -1): self.throttle = -1
	return

decrease the throttle setting by 10 %/second and constrain the throttle to a minimum of -100 percent.

def turn(self, dt, hardness = None, dir = None):

	if(self.speed == 0): turnRate = self.handling
	else: turnRate = self.handling * (2 - (self.speed / self.topSpeed))
	# Determine turn rate based on speed.
	
	if(dir == "R"): turnRate = -turnRate
	
	if(hardness != None): turnRate *= -hardness
		
	self.cycle.setH(self.cycle, turnRate * dt)

Turn the cycle.

def rotateTurret(self, dt, hardness = None, dir = None):
	
	turnRate = self.turretSpeed
	if(dir == "R"): turnRate = -turnRate
	
	if(hardness != None): 
		turnRate *= -hardness
	potentialH = self.turret.getH() + turnRate * dt
	if(potentialH > 60): potentialH = 60
	elif(potentialH < -60): potentialH = -60
	self.turret.setH(potentialH)

Turn the turret.

def tiltGuns(self, dt, hardness = None, dir = None):
	
	tiltRate = self.turretSpeed
	if(dir == "D"): tiltRate = -tiltRate
	
	if(hardness != None): tiltRate *= -hardness
	
	potentialTilt = self.turret.getP() + tiltRate * dt
	if(potentialTilt > 30): potentialTilt = 30
	elif(potentialTilt < -10): potentialTilt = -10
	
	self.turret.setP(potentialTilt)

Tilt the guns up or down.

def fire(self, group):
	if(self.currentLap > 0):
		for W in self.weapons[group]:
			W.fire()
	return

Fires all the guns.

def nextWpnGroup(self):
	self.activeWpnGroup += 1
	if(self.activeWpnGroup > 3):
		self.activeWpnGroup = 0
		
def hit(self, damage):
	self.currentShield -= damage
	self.shieldVis.setColor(1,1,1,.8)
	instability = damage - self.stability
	if(instability > 0): 
		if(self.speed - instability > 0):
			self.speed -= instability
		else: self.speed = 0
	return

Registers a hit on the cycle by damaging the shield and turning up the shield’s alpha.

def updateWeapons(self, dt):
	for L in self.weapons:
		for W in L:
			W.update(dt)
	return

Updates all the guns reload progress and ready states

def updateShield(self, dt):
	if(self.currentShield <= 0):
		return(True)
	elif(self.currentShield < self.maxShield):
		newShieldStr = self.currentShield + (self.shieldRecharge * dt)
		if(newShieldStr <= self.maxShield): self.currentShield = newShieldStr
		else: self.currentShield = self.maxShield
	# Checks if the shield is down and sets off the destroyed function if so. Otherwise, recharges the shield if it needs it.
	
	alpha = self.shieldVis.getColor().getW()
	if(alpha > 0):
		if(alpha - dt > 0):	self.shieldVis.setColor(1,1,1, alpha - dt)
		else: self.shieldVis.setColor(1,1,1,0)
	# Checks if the shield is visible, and if so, reduces it's alpha down to zero over time.
	return(False)

Monitors the shield’s status and updates as necessary.

def orientToTrack(self, dt):
	self.gCTrav.traverse(render)
	# Intitiate the ground ray traverser.
	points = [None, None]
	if(self.gHan.getNumEntries() > 1):
		self.gHan.sortEntries()
		for E in range(self.gHan.getNumEntries()):
			entry = self.gHan.getEntry(E)
			if(entry.getFrom() == self.fRay and points[0] == None): points[0] = entry.getSurfacePoint(render)
			elif(entry.getFrom() == self.bRay and points[1] == None): points[1] = entry.getSurfacePoint(render)
		# Get the surface contact points for the nearest 2 collisions, which will correspond to the 2 rays.
		# Put them into the points list in a particular order so they are easy to make sense of.			
		
		
	if(points[0] == None or points[1] == None):
		self.teleport(dt)
		return
	else:
		# If either ray didn't collide with the ground, teleport back onto the center of the track.
		
		if(self.freeFall == False):
			self.refNode.setPos(points[1])
			self.refNode.lookAt(points[0])
			pDiff = self.refNode.getP()- self.cycle.getP()
			if(pDiff < .1 and pDiff > -.1):
				self.cycle.setP(self.refNode.getP())
			else:
				self.cycle.setP(self.cycle, pDiff * dt * 5)
		# When not in free fall, find the pitch between the two collision points and smoothly match the cycle to it.
		elif((self.cycle.getP() - (dt * 10)) > -15): self.cycle.setP(self.cycle, -(dt * 10))
		else: self.cycle.setP(-15)
		# In free fall, let the pitch of the cycle slowly drop.
		
		if(self.speed >= 0): self.trackNode.setPos(points[0].getX(), points[0].getY(), points[0].getZ())
		else: self.trackNode.setPos(points[1].getX(), points[1].getY(), points[1].getZ())
		# Set the track node at the collision point on the leading end of the cycle.
		
		height = self.root.getZ(self.trackNode)
		# Get the height of the root node as seen from the track node.
		if(height > 2 and self.freeFall == False): 
			self.freeFall = True
			self.fallSpeed = 0
			self.momentum = self.speed
		# If the height is greater than 2, enter a free fall state and prep free fall variables.
		if(self.freeFall == True):
			self.fallSpeed += self.track.gravity * dt
			newHeight = height - (self.fallSpeed * dt)
		# In a free fall state, begin accelerating the fall speed and calculate a new height based on that fall speed.
		else:
			hDiff = 1 - height
			if(hDiff > .01 or hDiff < -.01): newHeight = height + (hDiff * dt * 5)
			else: newHeight = 1
		# If not in a freefall state, calculate a new height that gravitates towards the desired height,
		# or if you're close to the desired height, just set it as the new height.

		if(newHeight >= 0): self.root.setZ(self.trackNode, newHeight)
		# If the new height is greater than or equal to zero, set it as root node's height.
		else: 
			self.root.setZ(self.trackNode, 0)
			self.freeFall = False
		# Otherwise, set the root node to a height of 0, and turn off the free fall state.
		self.cycle.setR(0)
		# Constrain the cycle's roll to 0.			

	return

Reads the ground below the cycle and sets the pitch, and Z based on it. Also contrains the cycle’s roll to 0.

def dirNodeCatchUp(self, dt):

	if(self.freeFall == False):
		self.refNode.setPos(self.dirNode, 0, 1, 0)
		self.dirVec.set(self.refNode.getPos().getX(), self.refNode.getPos().getY(), 0)
		# gets the direction vector of the dirNode's facing.
	
		self.refNode.setPos(self.cycle, 0, 1, 0)
		self.cycleVec.set(self.refNode.getPos().getX(), self.refNode.getPos().getY(), 0)
		# gets the direction vector of the cycle's facing.
		
		self.refVec.set(0,0,1)
	
		vecDiff = self.dirVec.signedAngleDeg(self.cycleVec, self.refVec)
		# gets the signed angle difference between the dirNode and cycle vectors.
	
		if(vecDiff < .1 and vecDiff > -.1):
		# check to see how far off the two vectors are.
		
			self.dirNode.setHpr(self.cycle.getHpr().getX(), 0, 0)
			# If they're really close, set the dirNode's Hpr to equal the cycle's Hpr, thus perfectly aligning them.
			
		else: self.dirNode.setHpr(self.dirNode, vecDiff * dt * 2.5, 0, 0)
		# If they aren't really close, start closing the gap by rotating the dirNode.

	self.dirNode.setP(self.cycle.getP())
	self.dirNode.setR(0)
	
	return

Causes the dirNode to slowly alter it’s heading to the cycle’s, and forces the dirNode to maintain the same pitch and roll as the cycle.

def speedCheck(self, dt):
	if(self.freeFall == False):
		if(self.speed < self.topSpeed * self.throttle):
		# Checks if the cycle speed is lower than the throttle setting.
		
			self.speed = self.speed + (self.acceleration * dt)
			# If so, Increases the speed by acceleration * time
		
			if(self.speed > self.topSpeed * self.throttle):
				self.speed = self.topSpeed * self.throttle
			# If the speed has exceeded the throttle setting, set it to the throttle setting.
		
		elif(self.speed > self.topSpeed * self.throttle):
		# Checks if the cycle speed is higher than the throttle setting.
	
			self.speed = self.speed - ((self.acceleration * 1.1) * dt)
			# If so, decreases the speed by acceleration * time. The only method of braking hover cycles have is inverse thrust,
			# so to slow down you have to accelerate backwards.
		
			if(self.speed < self.topSpeed * self.throttle):
				self.speed = self.topSpeed * self.throttle
			# If the speed has dropped below the throttle setting, set it to the throttle setting.
	else:
		self.momentum -= (self.momentum * .125) * dt
		# In a free fall, drop momentum by 25%/second
		
	return

Controls the rate of the cycle’s forward motion.

def move(self, dt):
	if(self.freeFall == False):	mps = self.speed * 1000 / 3600 # Convert kph to meters per second
	# When not in free fall, use speed to determine distance moved along dirNode's facing.
	else: mps = self.momentum * 1000 / 3600 # Convert kph to meters per second
	# In a free fall, use momentum.
	
	self.refNode.setPos(self.dirNode, 0, 1, 0)
	# Set the ref node 1 meter unit forward in the dirNode's facing.
	
	self.dirVec.set(self.refNode.getX(), self.refNode.getY(), self.refNode.getZ())
	# Get the direction vector that corresponds to the dirNode's facing.
	
	self.root.setPos(self.root, self.dirVec.getX() * mps * dt, self.dirVec.getY() * mps * dt, self.dirVec.getZ() * mps * dt) 
	# Advances the cycle in the direction dirNode is facing based on time passed and speed.
			
	self.bumpCTrav.traverse(render)
	# Checks collisions for use in the next frame.	
	
	return

Manages the facing of the dirNode, accelerates and deaccelerates to match throttle, and moves the cycle

according to it’s current speed.

def teleport(self, dt):
	marker = self.track.getNearestMarker(self)
	markerPos = marker.getPos()
	self.root.setPos(markerPos.getX(), markerPos.getY(), self.root.getZ())
	# Put the cycle back on the track.
	
	self.gCTrav.traverse(render)
	# Intitiate the ground ray traverser.
	points = [None, None]
	if(self.gHan.getNumEntries() > 1):
		self.gHan.sortEntries()
		for E in range(self.gHan.getNumEntries()):
			entry = self.gHan.getEntry(E)
			if(entry.getFrom() == self.fRay and points[0] == None): points[0] = entry.getSurfacePoint(render)
			elif(entry.getFrom() == self.bRay and points[1] == None): points[1] = entry.getSurfacePoint(render)
		# Get the surface contact points for the nearest 2 collisions, which will correspond to the 2 rays.
		# Put them into the points list in a particular order so they are easy to make sense of.
	
		if(self.speed >= 0): self.trackNode.setPos(points[0].getX(), points[0].getY(), points[0].getZ())
		else: self.trackNode.setPos(points[1].getX(), points[1].getY(), points[1].getZ())
		# Set the track node at the collision point on the leading end of the cycle.
		
		self.root.setZ(self.trackNode, 1)
		
	self.dirNode.setHpr(marker.getHpr())
	self.cycle.setHpr(marker.getHpr())
	
	self.speed /= 2
	

	
	return

Puts the cycle back on the track and recalls orientToTrack.

def bump(self, entry):
	if(self.speed * .95 > self.topSpeed * .1): self.speed = self.speed * .95
	else: self.speed = self.topSpeed * .1
	self.hit(0)
	if(entry.getIntoNodePath().hasPythonTag("owner")):
		otherCycle = entry.getIntoNodePath().getPythonTag("owner")
		if(otherCycle.speed * .975 > otherCycle.topSpeed * .1): otherCycle.speed = otherCycle.speed * .975
		else: otherCycle.speed = otherCycle.topSpeed * .1
		if(self.checkInFront(otherCycle) == True):
			self.changeLanes()
	
	return

If the cycle hit an obstacle of some kind, this aaevent response will slow the cycle down, to minimum of 10% top speed.

def setKey(self, key, val):	
	self.keyMap[key] = val
	return

Sets the provided key in the keyMap dictionary to the provided value.

def keyCheck(self, task):
	dt = globalClock.getDt()
	if (dt > .20):
		return task.cont
	# Find the amount of time that has passed since the last frame. If this amount is too large,
	# there's been a hiccup and this frame should be skipped.
	
	if(self.keyMap["f"] == True):
		self.throttleUp(dt)
	# checks if the f key is pressed. If so, increase throttle.	
		
	if(self.keyMap["a"] == True):
		self.throttleDown(dt)
	# checks if the a key is pressed. If so, decrease throttle.
		
	if(self.keyMap["s"] == True):
		self.turn(dt, dir = "L")
	# checks if the s key is pressed. If so, turn left.
		
	if(self.keyMap["d"] == True):	
		self.turn(dt, dir = "R")
	# checks if the d key is pressed. If so, turn right.
	
	if(self.keyMap["w"] == True):	
		self.rotateTurret(dt, dir = "L")
	# checks if the w key is pressed. If so, rotate the turret left.
	
	if(self.keyMap["e"] == True):	
		self.rotateTurret(dt, dir = "R")
	# checks if the w key is pressed. If so, rotate the turret left.
	
	if(self.keyMap["q"] == True):	
		self.tiltGuns(dt, dir = "D")
	# checks if the q key is pressed. If so, tilt the guns down.
	
	if(self.keyMap["r"] == True):	
		self.tiltGuns(dt, dir = "U")
	# checks if the r key is pressed. If so, tilt the guns up.
	
	if(self.keyMap["g"] == True):
		self.fire(self.activeWpnGroup)
		
	if(self.joy != None):
		for e in pygame.event.get(): pass
		# allows access to the joystick. must be called every frame.
		
		lAxislr = self.joy.get_axis(0)
		if(lAxislr > .1 or lAxislr < -.1):
			self.turn(dt, hardness = lAxislr)
		# Turn the cycle based on the left and right axis of the left stick.
		
		rAxislr = self.joy.get_axis(2)
		if(rAxislr > .1 or rAxislr < -.1):
			self.rotateTurret(dt, hardness = rAxislr)
		# Rotate the turret based on the left and right axis of the right stick.
		
		rAxisud = self.joy.get_axis(3)
		if(rAxisud > .1 or rAxisud < -.1):
			self.tiltGuns(dt, hardness = rAxisud)
		# Rotate the turret based on the left and right axis of the right stick.
			
		if(self.joy.get_button(10) == 1):
			self.throttleDown(dt)
		elif(self.joy.get_button(8) == 1):
			self.throttleUp(dt)
		# Throttle up and down with the left trigger buttons. Throttle down takes
		# precedence.
		
		if(self.joy.get_button(9) == 1):
			self.fire(2)
	
	destroyed = self.updateShield(dt)
	if(destroyed == True): return task.done
	
	self.updateWeapons(dt)
	
	self.orientToTrack(dt)
	# Read the track to better orient the cycle.
	
	self.dirNodeCatchUp(dt)
	# Let dirNode try to catch up to the cycle's orientation.
	
	self.checkMarkers()
	# Updates the up coming track markers if necessary.
	
	self.speedCheck(dt)
	# updates the cycle's speed, if necessary.
		
	self.move(dt)
	# moves the cycle.
			
	return task.cont

Monitors the keyMap for changes caused by key presses and responds to them.

Also calls all of the functions that need to be called every frame.

def findAngle(self, marker, node):

	markPos = marker.getPos(node)
	self.dirVec.set(markPos.getX(), markPos.getY(), 0)
	self.dirVec.normalize()
	# Gets the directional vector to the marker and normalizes it.
	
	self.cycleVec.set(0,1,0)
	# Uses cycleVec for the reference vector.
	
	self.refVec.set(0,0,1)
	
	return(self.cycleVec.signedAngleDeg(self.dirVec, self.refVec))

Returns the angle between the node’s facing and the direction to the marker.

def checkMarkers(self):
	if(self.uc1.checkInFront(self) == True):
		#self.unMark()
		self.uc1 = self.uc2
		self.uc2 = self.uc3
		self.uc3 = self.uc2.nextMarker
		
		self.markerCount += 1
		
		if(self.uc1 == self.lanes[0][1] or self.uc1 == self.lanes[1][1]):
			self.currentLap += 1
		# If the cycle just passed the first marker, which is at the finish line, increment the lap count.
		
		#self.mark()
	return

Checks if uc1 has been passed, and if so, updates all the markers.

def checkThrottle(self, dt):
	
	if(self.throttle < self.targetThrottle): self.throttleUp(dt)
	elif(self.throttle > self.targetThrottle): self.throttleDown(dt)
	# Set the throttle to match the target throttle
	
	return

Monitors the track ahead and sets the target throttle accordingly. Also adjusts the throttle when it doesn’t

match the target throttle.

def	checkTurn(self, dt):
	turnAngle = self.findAngle(self.uc3, self.cycle)
	if(turnAngle < 1 and turnAngle > -1): 
		turnAngle = 0
		if(self.targetThrottle < 1):
			self.targetThrottle = 1
	# Get the perceived turn angle.
	
	if(self.speed == 0): turnRate = self.handling
	else: turnRate = self.handling * (2 - (self.speed / self.topSpeed))
	# Determine turn rate based on speed.
	
	if(turnAngle < 0): 
		turnRate *= -1
	# If it's a right turn, set the turn rate to a negative
		
		if(turnAngle < turnRate * dt): 
			self.cycle.setH(self.cycle, turnRate * dt)
			self.targetThrottle = 1 + (turnRate * dt / self.handling)
			
		else:
			hardness = (turnRate * dt)/ turnAngle
			turnRate *= hardness
			self.cycle.setH(self.cycle, turnRate * dt * hardness)	
	if(turnAngle > 0):
		if(turnAngle > turnRate * dt): 
			self.cycle.setH(self.cycle, turnRate * dt)
			self.targetThrottle = 1 - (turnRate * dt / self.handling)
			
		else:
			hardness = (turnRate * dt)/ turnAngle
			turnRate *= hardness
			self.cycle.setH(self.cycle, turnRate * dt * hardness)
	return

Monitors the track ahead and determines if turning is necessary, how much, and which direction.

def checkTarget(self, task):
	targIR = []
	self.target = None
	self.dCTrav.traverse(render)
	if(self.dCHan.getNumEntries() > 0):
		self.dCHan.sortEntries()
		for E in range(self.dCHan.getNumEntries()):
			target = self.dCHan.getEntry(E).getIntoNodePath().getPythonTag("owner")
			if(target.team != self.team and self.checkInFront(target) == True): 
				targIR.append(target)
				break
	
	if(len(targIR) > 0): self.target = targIR[0]
	else: self.target = self.cycleManager.getLowerPole(self)
			
	if(len(self.weapons[2]) > 0):
		higherPole = self.cycleManager.getHigherPole(self)
		if(higherPole != None and self.markerCount - higherPole.markerCount > 5):
			for W in self.weapons[2]:
				W.setTrackDir("F")
			self.fire(2)
			
	if(len(self.weapons[3]) > 0 or len(self.weapons[2]) > 0):
		lowerPole = self.cycleManager.getLowerPole(self)
		if(lowerPole != None and lowerPole.markerCount - self.markerCount > 5):
			self.fire(3)
			for W in self.weapons[2]:
				W.setTrackDir("B")
			self.fire(2)
			
	return task.again

def changeLanes(self):
	self.blocker = None
	if(self.currentLane == 0):
		self.uc1 = self.lanes[1][self.lanes[0].index(self.uc1)]
		self.uc2 = self.lanes[1][self.lanes[0].index(self.uc2)]
		self.uc3 = self.lanes[1][self.lanes[0].index(self.uc3)]
		self.currentLane = 1
	else:
		self.uc1 = self.lanes[0][self.lanes[1].index(self.uc1)]
		self.uc2 = self.lanes[0][self.lanes[1].index(self.uc2)]
		self.uc3 = self.lanes[0][self.lanes[1].index(self.uc3)]
		self.currentLane = 0
		
def checkAim(self, dt):
	if(self.target != None):
		self.findHAim(dt, self.target)
		self.findPAim(dt, self.target)
		self.targeterCTrav.traverse(render)
		if(self.targeterCHan.getNumEntries() > 0):
			self.targeterCHan.sortEntries()
			if(self.targeterCHan.getEntry(0).getIntoNodePath().hasPythonTag("owner")):
				self.fire(0)
	else:
		self.refNode.setPos(self.cycle,0,1,0)
		self.findHAim(dt, self.refNode)

def findHAim(self, dt, target):

	targetPos = target.getPos(self.targeterCNP)
	self.dirVec.set(targetPos.getX(), targetPos.getY(), targetPos.getZ())
	self.dirVec.normalize()
	# Gets the directional vector to the target and normalizes it.
	
	self.cycleVec.set(0,1,0)
	# Set the cycle vec to straight forward.
	
	self.refVec.set(0,0,1)
	# Sets the reference vector.
	
	aimAngle = self.cycleVec.signedAngleDeg(self.dirVec, self.refVec)
	if(aimAngle < .1 and aimAngle > -.1): 
		aimAngle = 0
	aimRate = self.turretSpeed
	# Determine angle and rate.
	
	if(aimAngle < 0): 
		aimRate *= -1
	# If it's a right turn, set the aim rate to a negative
		
		if(aimAngle < aimRate * dt): 
			self.rotateTurret(dt, hardness = 1)
		else:
			hardness = (aimRate * dt)/ aimAngle
			self.rotateTurret(dt, hardness = -hardness)
			
	if(aimAngle > 0):
		if(aimAngle > aimRate * dt): 
			self.rotateTurret(dt, hardness = -1)
		else:
			hardness = (aimRate * dt)/ aimAngle
			self.rotateTurret(dt, hardness = -hardness)
			
	return

Monitors the target and determines if turning is necessary, how much, and which direction.

def findPAim(self, dt, target):

	targetPos = target.getPos(self.targeterCNP)
	self.dirVec.set(targetPos.getX(), targetPos.getY(), targetPos.getZ())
	self.dirVec.normalize()
	# Gets the directional vector to the target and normalizes it.
	
	self.cycleVec.set(0,1,0)
	# Set the cycle vec to straight forward.
	
	self.refVec.set(1,0,0)
	# Sets the reference vector.
	
	aimAngle = self.cycleVec.signedAngleDeg(self.dirVec, self.refVec)
	if(aimAngle < 1 and aimAngle > -1): 
		aimAngle = 0
	aimRate = self.turretSpeed
	# Determine angle and rate.
	
	if(aimAngle < 0): 
		aimRate *= -1
	# If it's a right turn, set the aim rate to a negative
		
		if(aimAngle < aimRate * dt): 
			self.tiltGuns(dt, hardness = 1)
		else:
			hardness = (aimRate * dt)/ aimAngle
			self.tiltGuns(dt, hardness = -hardness)
			
	if(aimAngle > 0):
		if(aimAngle > aimRate * dt): 
			self.tiltGuns(dt, hardness = -1)
		else:
			hardness = (aimRate * dt)/ aimAngle
			self.tiltGuns(dt, hardness = -hardness)
	return

def checkInFront(self, cycle):

	cyclePos = cycle.root.getPos(self.cycle)
	self.dirVec.set(cyclePos.getX(), cyclePos.getY(), self.cycle.getZ())
	self.dirVec.normalize()
	# Gets the directional vector to the cycle and normalizes it.
	
	self.cycleVec.set(0,1,0)
	
	cycleAngle = self.cycleVec.angleDeg(self.dirVec)
	# Gets the angle between the cycle's facing and the direction to the other cycle.
	
	if(cycleAngle > 90): return(False)
	else: return(True)
	# Returns True if the cycle is in front of this cycle or False if it is behind it.
	
def runAI(self, task):
	dt = globalClock.getDt()
	if (dt > .20):
		return task.cont
	# Find the amount of time that has passed since the last frame. If this amount is too large,
	# there's been a hiccup and this frame should be skipped.
	
	destroyed = self.updateShield(dt)
	if(destroyed == True): return task.done
	
	self.updateWeapons(dt)
	
	self.orientToTrack(dt)
	# Read the track to better orient the cycle.
	
	self.dirNodeCatchUp(dt)
	# Let dirNode try to catch up to the cycle's orientation.
	
	self.checkMarkers()
	
	self.checkThrottle(dt)
	
	self.checkTurn(dt)
	
	self.speedCheck(dt)
	# updates the cycle's speed, if necessary.
	
	self.move(dt)
	# moves the cycle.
	
	#self.checkAim(dt)
			
	return task.cont

Performs all the AI functions each frame.

def unMark(self):
	self.uc1.sphere.setColor(1,1,1)
	self.uc2.sphere.setColor(1,1,1)
	self.uc3.sphere.setColor(1,1,1)

def mark(self):
	self.uc1.sphere.setColor(1,0,0)
	self.uc2.sphere.setColor(1,1,0)
	self.uc3.sphere.setColor(0,1,0)

def getPos(self, ref = None):
	if(ref == None): return(self.root.getPos())
	else: return(self.root.getPos(ref))
	
def getH(self):
	return(self.cycle.getH())[/code]

Hm, there’s a lot going on in there.

In general, you should understand that what you are relying on mostly in Python’s reference counting system, not its garbage collection system, though the distinction may not be important to you at this point. Both kinds of systems have the same property: when you are no longer referencing an object anywhere, it is automatically cleaned up.

Panda in the mix kind of bolloxes this, because Panda can hold onto references that you’re not aware of. For instance, the scene graph might still contain nodes that you’ve forgotten to remove or detach; or the Messenger might hold events that you’ve forgotten to ignore, or the task manager might hold tasks that you’ve forgotten to remove, or the interval manager might hold intervals that you’ve forgotten to finish. Also, the TexturePool and ModelPool will cache copies of every texture and model that have been loaded in the current session, so if it’s really, really important to free those caches as well, you’ll have to do that explicitly (or via something like the misleadingly-named TexturePool.garbageCollect()).

Finally, because it’s a reference-counting system more than a garbage collection system, you have to be careful of circular references: objects that, directly or indirectly, reference themselves and so can’t be cleaned up by Python’s reference-counting system. Python does have a garbage-collection system that comes in behind and can detect these circular references, but it’s limited in its ability to clean them up. But you can use it to detect them so you can figure out where you should have broken some links yourself. Use something like:

import gc
gc.collect()
print gc.garbage

and if gc.garbage is not empty, it contains objects that the garbage collector detected but could not clean up.

David

I think part of my problem is that I don’t know exactly what constitutes a reference. To illustrate this, let me explain a problem I encountered.

When my game is running, I create 8 vehicles and the track. At this point, these items are never removed. The vehicles are equipped with weapons that create four kinds of objects:
Booms (graphic effects combined with collision spheres that can damage vehicles)
Pops (graphic effects with no collision)
Missiles (model that moves toward a vehicle and create a Boom on contact)
Hunters (models that follow the track until they find a vehicle, then move toward it and create a Boom on contact)

These are the only other objects being created during runtime, and the only objects that could be created in excessive quantities.

When these objects are finished doing what they need to do, I take the following steps to clean them up:

  1. Call .removeNode() on every node they have added to the scene graph. Empty nodes, models, and collision nodes
  2. return task.done in the tasks they use to operate
  3. Delete the reference to the class instance from the list those references are stored in.

The problem is, I don’t know if that’s enough, or too much, or what. I don’t even know how I can check to see what has been garbage collected in a frame, or what hasn’t. I don’t know how to tell if components of the class instances still exist, or even if the entire instance still exists.

Thinking back, I probably should have asked this question to begin with: Are there tools that can tell me those things, and how do I use them? Knowing that would probably allow me to answer all my other clean up questions myself.

import gc
gc.collect()
print gc.garbage

This is a great start. Is there more stuff like this? If so, and I learn to use it, could I write up an entry for the manual to explain this? The only thing the manual mentions about garbage collection is in the performance tuning section, and all it states is that the section is incomplete and will be updated soon. I’d be happy to expand the manual to include more about garbage collection, since it is a major part of game programming and not everyone who wants to use Panda3D is familiar enough with programming or python to be able to understand it without assistance.

In general, this is easy: all Python variables, and many C++ pointers, count as a reference.

Specifically, it’s a more difficult question. You have to know about all of the systems that might keep variables or pointers to your things. Mostly, you just have to know about these; there (mostly) aren’t tools that can tell you which things are holding references to a given object.

Note that the function gc.get_referrers() is supposed to tell you the (Python) things that are keeping a pointer to a given (Python) object. I haven’t successfully gotten this give me any meaningful information.

But, you can check to see how many references are being held for a particular object. The sys.getrefcount() function in Python will tell you this. Try this:

>>> np = NodePath('np')
>>> sys.getrefcount(np)
2

It reports “2” because np keeps one reference count, and the temporary value created to pass to the sys.getrefcount() function itself keeps another one.

Try:

>>> np2 = np
>>> sys.getrefcount(np)
3
>>> np2 = None
>>> sys.getrefcount(np)
2

And now there is a third refcount for np2.

But this is just the Python reference-counting side. In Panda, nodes have a different reference count, which is tracked separately:

>>> np.node().getRefCount()
2
>>> np.reparentTo(render)
>>> np.node().getRefCount()
3
>>> sys.getrefcount(np)
2

Panda does not reference-count NodePaths, but only the PandaNodes within them. The method to return the reference count on any Panda object is getRefCount(). Initially, the PandaNode within np reports a reference count of 2: one for the NodePath, and one for the temporary return value from np.node(). When we reparent it to render, it gains a third one, for the parent-child relationship. But this new reference count doesn’t affect the Python count of references to np.

You should be able to play with sys.getrefcount() and obj.getRefCount() to see how the two reference counting systems work. In general, an object cannot be deleted until both reference counts go to 0.

You could also try putting a print statement in a del() method. This method will be called when the reference count goes to 0 and the object is actually deleted. When you see the print happen, you can be confident that the object is gone.

That sounds like a good start. You should also consider calling ignoreAll() on any cleaned-up object, to ensure that it has no pending messages, since the messenger can keep a handle to an object unexpectedly.

Please do! But note that it is indeed a big subject.

David

So, if I want to clean up a custom class, I don’t need to remove any static variables it has, only nodes?

If I delete the reference to a custom class instance, and the class has nodes in it, are the nodes referencing back to the class and thus keeping it alive? Or would the class get deleted, which would remove the class’s reference to the nodes, and cause them to be deleted?

If the del function is called when a class is being garbage collected, could I put all of my clean up code in there? Like calling removeNode(), etc?

I’m sorry to be asking so many questions David, but I really need to understand this and these forums are the only reliably source of information I know of.

No, the del method would have to go in a meta-class (a subclass of ‘type’) in order to get called when the class leaves reachability. But after you add the del, the interpreter loses the ability to break cycles, so you’ll have to examine gc.garbage yourself. This is somewhat pervasive.

Generally true. Note that you don’t also have to remove nodes, per se; you just have to make sure they’re no longer in the scene graph. A standalone node will be deleted when its Python wrapper is deleted. For the purposes of ensuring the node is no longer in the scene graph, np.detachNode() is equivalent to np.removeNode().

The latter is true: nodes (and most objects) do not generally keep back-pointers to the class that contains them, so deleting the containing object is usually sufficient to delete all the nodes and other objects contained within it.

Generally not. Everyone wants to do this at first; it is a tempting place to put required cleanup calls. But it is a mistake, because object.del() is not called until after the object’s reference count has already dropped to 0, which means that you have to do any required cleanup before del can be called!

Of course, you could in fact call self.nestedNode.removeNode() in del, because as I said above, nodes don’t keep back pointers to their containing objects. But I don’t think this is a good idea, because you don’t have any control over when del is called. It might not be called immediately when the last reference goes away, which means there will be nodes remaining in the scene graph (and visible!) for an indeterminate length of time. It’s best to perform all of your cleanup explicitly, at a time you specify.

Edit: also, as castironpi points out above, having del at all breaks the garbage collector. Better to avoid defining such a method if you can, and use one only temporarily, for debugging.

David

Okay. I think I’m starting to get the hang of this.

Let’s say I create the following code:

class Alpha:
   def __init__(self):
      self.model = loader.loadModel("somemodel")
      self.model.reparentTo(render)
      self.refNode = render.attachNewNode("AlphaRefNode")
      self.b = Beta(self)
      

class Beta:
  def __init__(self, alpha):
      self.alpha = alpha
      self.model = loader.loadModel("somemodel")
      self.model.reparentTo(render)
      self.refNode = render.attachNewNode("BetaRefNode")

Then to kill the whole thing, I would need the following two functions, in their respective classes.

class Alpha:
   def cleanUp(self):
      self.model.removeNode()
      self.refNode.removeNode()
      self.b.cleanUp()
      self.b = None
      

class Beta:
  def cleanUp(self):
      self.alpha = None
      self.model.removeNode()
      self.refNode.removeNode()

Is that right?

Strictly, you would only need one of:

   self.b = None

and

   self.alpha = None

since after either one, you’d no longer have a cycle (and therefore the reference count of the one that still has the reference to the other would go to zero, and get destroyed, which would take the reference count of the other to zero).

Okay, great.

Regarding the message system David was talking about. Is it possible for things to be waiting for messages if they aren’t set to accept any?

I get the impression that in order for something to receive a message, you need to having something like self.accept(“event”, myFunc). Or will the messages get sent regardless?

Lastly, I have only been able to get self.accept to work on objects that inherit from DirectObject. Does this mean that only objects that inherit from DirectObject receive messages, and need to have ignoreAll() called?

Right on both counts. :slight_smile: Objects must inherit from DirectObject, and will not be listening for events if you never called accept().

David

Alright, awesome. I think I have a working understanding of cleaning up now. I’ll start working on a manual page for it this weekend.

When I called .removeNode() on an actor, it threw an error telling me that I called .removeNode() without calling .cleanUp(). Obviously, this means I need to call .cleanUp() on actors.

I’m curious though. If I have an actor that is being removed because there is no reference to it and it has been removed from the scene graph, do I still need to call .cleanUp() on it? I’m guessing I do, because I have a feeling that the animation system retains a handler to it that .cleanUp() gets rid of. I could be wrong about that though.

Also, I get an error when I call actor.cleanUp(), so I looked up cleanUp() in the API and I couldn’t find anything that seemed to be the method I’m supposed to call.

Hmm, that’s actually a new one on me. The method name is cleanup(), not cleanUp(), but I don’t see anything in it that would really need to be done. Maybe someone put this in to enforce a general policy, rather than an actual specific need.

David

Interesting. I take it that means I don’t need to call cleanup() on actors before I de-reference them and let them get garbage collected, which was my primary concern. Thanks again.

Wait, what happened to actor.delete()?

Did you try preceding it with detachNode? What if a third-party object had a reference to the node you removed?

No third parties have references to the actor. Also, the error being throw isn’t a stop error, just a warning, so if I don’t have to care about what it’s warning me about, I don’t have to care at all.

I thought delete() was only for distributed actors. It’s undocumented, so I’m not sure what all it does.

The information in this thread is absolutely priceless. It helped me solve my many level clean-up issues and actually understand what was going on. I’d have been completely lost otherwise.

I fully support adding this to the Wiki.

Thanks guys, you rock!