__del__ not called? [SOLVED]

Is it possible for a del function not to be called?

I have mines that vehicles can drop in my game, and I’m trying to verify that they are cleaning up correctly. To this end, I put a del function in the class with a print statement in it so that I would be notified when the class is cleaned up.

In practice, when the mines should be cleaned up, I sometimes see the print and I sometimes don’t.

So I loaded the game with directtools active and I started looking at the memory usage it reports. When a mine blows up but the print statement doesn’t show, the memory usage increases for a second or so and then returns to the starting amount, what it was at before any mines were laid.

I’m not sure if the directtools is only showing me memory consumed by visual objects or not (it does list memory in two categories: render and render2d). If so, then it would make sense that the class isn’t being removed but the visual memory is being cleaned up, since I’m calling removeNode() on the mine’s model.

If directtools isn’t only showing me visual memory, then mine instances are being garbage collected without the del function running.

Can anyone give me some insight on which is the case?

The garbage collector runs at its own discretion, but you can force a run, with ‘gc.collect( )’. That will handle any lingering garbage that can be collected; if something isn’t collected after that call, it’s not (or wasn’t) collectible at that time.

del is supposed to be called when a class instance is garbage collected, as I understand it. That’s why I’m curious about it sometimes being called and sometimes not, even though it would appear from the directtools panel that the memory is being freed.

Is that not the case?

Your observations are inconsistent with my understanding of the data structure and its functions. Obviously observation has to trump conception, but reconciling isn’t always an easy or obvious process. You haven’t shared your exact observations yet, of course.

I don’t believe that del is forced to run when the program ends, IIRC, but I’m not sure on that one; and I think there were some issues with global scope or something.

I made some images of my exact observations, but unfortunately my web host is down at the moment and I can’t post them, so I’ll have to explain it.

I put these three lines of code before my import of DirectStart to bring up the direct session panel.

from pandac.PandaModules import loadPrcFileData
loadPrcFileData("", "want-directtools #t")
loadPrcFileData("", "want-tk #t")

In that panel, there is a memory tab. I checked the tab before laying any mines, and the memory usage listed was:

render: 265824 bytes
render2d: 0 bytes.

After I laid the mines, 1 exploded immediately due to proximity to the enemy. It posted “mine removed” in my command prompt because of the print statement in the del function for the mine class. The other 2 mines did not explode. While they were in existence the memory usage was reported as follows:

render: 271200 bytes
render2d: 0 bytes

I waited for the timer on the remaining 2 mines to expire, and when it did they exploded. Neither one posted a print statement to my command prompt. With both of them gone, I checked my memory usage.

render: 265824 bytes
render2d: 0 bytes.

Exactly the same as before the mines were laid.

Actually, reported memory usage isn’t particularly meaningful. Even when objects are deleted, memory isn’t necessarily (immediately) recovered; it might simply be held for the next allocation. So you can’t conclude that objects aren’t being delete just because the memory usage doesn’t go down.

If the del() function isn’t called, on the other hand, it’s a strong indication that your objects are still being referenced somewhere. (This wouldn’t be the case if Python were strictly a garbage-collected language, but because it’s primarily reference-counted, it should actually destroy objects as soon as the last reference goes away.)

David

I did another test, this time looking at the memory usage reported by the windows task manager. This time when mines exploded but did not print to the command prompt the memory usage did not drop. But, as you say David, that’s not indicative of objects being deleted.

If I’m reading your post correctly, though, you’re telling me that the missing print statements from the del function means the objects are not being deleted.

Now I have to figure out why the clean up script I wrote works some of the time, but not all of the time. :frowning:

Here is the init function from the mine class:

	def __init__(self, cycle, pos, patternPos, gunStats):
		self.cycle = cycle
		self.gunStats = gunStats

		self.root = self.cycle.assetLoader.loadModel(gunStats["Mine Model"]) # Loads and stores the model for the mine.
		self.root.reparentTo(render)
		self.root.setH(self.cycle.getH())
		
		self.mode = "Move"
		self.setPos(pos)	# Sets the mine to the initial position.
		self.patternPos = [float(patternPos[0]), float(patternPos[1])]
		self.currentShield = self.gunStats["Shield Strength"]	# Stores the current strength of the mine's shield.
		self.team = self.cycle.team
		self.track = self.cycle.track
		self.timer = 0
		self.explode = False
		
		self.shieldCN = CollisionNode("MineShieldCN")
		self.shieldCN.setPythonTag("owner", self)
		self.shieldCS = CollisionSphere(0,0,0,1)
		self.shieldCN.addSolid(self.shieldCS)
		self.shieldCN.setIntoCollideMask(BitMask32.range(3,3))
		self.shieldCN.setFromCollideMask(BitMask32.bit(2))
		self.shieldCNP = self.root.attachNewNode(self.shieldCN)
		#self.shieldCNP.show()
		
		self.CN = CollisionNode("MineCN")
		self.CS = CollisionSphere(0,0,0,self.gunStats["Blast Radius"])
		self.CN.addSolid(self.CS)
		self.CN.setIntoCollideMask(BitMask32.allOff())
		self.CN.setFromCollideMask(BitMask32.bit(4))
		self.CNP = self.root.attachNewNode(self.CN)
		#self.CNP.show()
		# Creates a collision sphere for the mine, turns off it's into mask, and sets the from Mask to 4, 
		# The collision node is parented to the graphic mine model.
		
		self.CTrav = CollisionTraverser()
		self.CHan = CollisionHandlerQueue()
		self.CTrav.addCollider(self.CNP, self.CHan)
		# Creates a traverser and queue handler to detect and deal with collisions.
		
		taskMgr.add(self.runAI, "mineAI")
		# Adds the update task to the task manager.

Here is the clean up script. It’s located within the runAI task that’s added at the end of the init function.

		if(self.explode == True):
			self.cycle.boomManager.addBoom(self.gunStats["Boom Model"], self.getPos(), self.gunStats["Blast Radius"], self.gunStats["Damage"])
			self.mode = "Done"
			self.shieldCN.clearPythonTag("owner")
			self.root.removeNode()
			self.shieldCNP.removeNode()
			self.CNP.removeNode()
			return task.done

It’s an almost exact copy of a clean up script I’m using in another weapon type, and that weapon type cleans up without fail every time. For the life of me, I just can’t discern what I’m missing.

Oh, and for your info, the variable that references the class instance directly is deleted when the mine layer detects that the mode has changed to “Done”. I tested and confirmed that part, so I know it’s working properly.

If you see something I’m missing, please let me know.

Okay, I think I resolved it. It seems that the explosions weren’t being clean up correctly, and could have retained a reference to the blast radius and damage values in the mines.

Why I didn’t have the same problem with other explosion causing objects, I don’t know.

I get the print statement for every mine every time now, though, so I call it a win.