Garbage Collector problem in panda AI?

Well, not really an ask for help post.
I am doing my term paper using panda3d and pathfinding.

Right now I am mainly gathering informarion and doing examples to benchmark Panda3d pathfind efficiency and performance.

Today I run into a very strange problem, here is the code:

#for directx window and functions
import direct.directbase.DirectStart
from pandac.PandaModules import loadPrcFileData
#loadPrcFileData('', 'direct-gui-edit #t')
#loadPrcFileData("", "want-directtools #t")
#loadPrcFileData("", "want-tk #t") 

from math import pi, sin, cos, fabs, radians, degrees, floor
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
from direct.actor.Actor import Actor
from direct.interval.IntervalGlobal import Sequence
from panda3d.core import Point3
import direct.directbase.DirectStart
from direct.gui.OnscreenText import OnscreenText 
from direct.gui.DirectGui import *
from pandac.PandaModules import *
from direct.task import Task
import os, sys
from pandac.PandaModules import Filename
from direct.showbase.DirectObject import DirectObject
from panda3d.ai import *
from random import randrange

# Globals
speed = 0.75
 
# Function to put instructions on the screen.
font = loader.loadFont("cmss12")
def addInstructions(pos, msg):
    return OnscreenText(text=msg, style=1, fg=(1,1,1,1), font = font,
                        pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)
 
class WorldAgent:
	def __init__(self):
		mydir = os.path.abspath(sys.path[0])
		mydir = Filename.fromOsSpecific(mydir).getFullpath()

		max_x, min_x, max_y, min_y = 18, -18, 15, -15
		x = randrange(min_x, max_x)
		y = randrange(min_y, max_y)
		self.agent = Actor(mydir+"/graphics/ralph",
								{"run":mydir+"/graphics/ralph-run"})
		self.agent.reparentTo(render)
		self.agent.setScale(0.5)
		self.agent.setPos(x, y ,0)
		self.agent.loop("run")
		
		self.linesList = []
		self.lastPos = self.agent.getPos()
		
	def getAgentActor(self):
		return self.agent

		
	def drawWay(self):
		segs = LineSegs()
		segs.setThickness( 1.0 )
		segs.setColor( Vec4(1,1,0,1) )
		segs.moveTo( self.lastPos )
		segs.drawTo( self.agent.getPos())
		self.lastPos = self.agent.getPos()
		self.linesList.append(segs)
		render.attachNewNode(segs.create())


	
class World(DirectObject):

	def __init__(self):
		base.disableMouse()
		base.cam.setPosHpr(0,0,55,0,-90,0)
		self.clock = ClockObject()
		self.loadModels()
		self.setAI()
		self.setMovement()

	def loadModels(self):

		# Seeker
		self.seekerList = []
		self.seekerList.append(WorldAgent())
		
		mydir = os.path.abspath(sys.path[0])
		mydir = Filename.fromOsSpecific(mydir).getFullpath()
		
		# Target
		self.target = loader.loadModel(mydir+"/graphics/crate.egg")
		self.target.setColor(1,0,0)
		self.target.setPos(5,0,0)
		self.target.setScale(1)
		self.target.reparentTo(render)

		# Obstacles
		self.obstacleList = []
		i = -18
		while i <= 18:
			j = -12
			while j <= 13:
				obstacle = loader.loadModel(mydir+"/graphics/crate.egg")
				obstacle.reparentTo(render)
				obstacle.setColor(0,0,1)
				obstacle.setPos(i,j,0)
				obstacle.setScale(1)
				self.obstacleList.append(obstacle)
				j += 5
			i+=5
			
	def addAgentToTheWorld(self, agent):
		newWorld = AIWorld(render)
		for obstacle in self.obstacleList:
			newWorld.addObstacle(obstacle)

		#Ok, this works
		#self.AIchar = AICharacter("pursuer"+str(randrange(1, 500)),agent, 100, 1, 10)
		#newWorld.addAiChar(self.AIchar)
		#self.AIbehaviors = self.AIchar.getAiBehaviors()
		#self.AIbehaviors.pursue(self.target, 0.5)
		#self.AIbehaviors.obstacleAvoidance(5.0)
		
		newChar = AICharacter("pursuer"+str(randrange(1, 500)),agent.getAgentActor(), 100, 1, 10)
		newWorld.addAiChar(newChar)
		newBehaviors = newChar.getAiBehaviors()
		newBehaviors.pursue(self.target, 0.5)
		newBehaviors.obstacleAvoidance(5.0)

		self.AIcharsList.append(newChar)
		self.AIBehaviorsList.append(newBehaviors)
		
		self.AIworldList.append(newWorld)

	def setAI(self):
		self.AIworldList = []
		self.AIcharsList = []
		self.AIBehaviorsList = []
		#self.AIWorld = AIWorld(render)
		
		for seeker in self.seekerList:
			self.addAgentToTheWorld(seeker)
		
		self.AItimeMeter = OnscreenText(
			text = "Avarage time for AI: 0",
			pos = (1, 0.9), align=TextNode.ARight,
			scale = .05,
			mayChange = True
		)

		self.agentsCounter = OnscreenText(
			text = "Number of agents: "+str(len(self.seekerList)),
			pos = (1, 0.8), align=TextNode.ARight,
			scale = .05,
			mayChange = True
		)
		
		self.lastAIUpdate = self.clock.getLongTime()
		self.aiUpdateCounter = 0
		self.timeAcumulator = 0

		#AI World update        
		taskMgr.add(self.AIUpdate,"AIUpdate")

		
	def createAgentAndAddItToTheWorld(self):
		newAgent = WorldAgent()
		self.seekerList.append(newAgent)
		self.addAgentToTheWorld(newAgent)
		self.agentsCounter.setText("Number of agents: "+str(len(self.seekerList)))
		
	def AIUpdate(self,task):
		start = self.clock.getLongTime()
		for world in self.AIworldList:
			world.update()
			
		#for agent in self.seekerList:
		#	agent.drawWay()
		
		self.timeAcumulator += (self.clock.getLongTime() - start)
		if (self.clock.getLongTime() - self.lastAIUpdate >= 1):
			self.AItimeMeter.setText("Avarage time for AI: "+str(self.timeAcumulator/self.aiUpdateCounter))
			self.aiUpdateCounter = 0
			self.timeAcumulator = 0
			self.lastAIUpdate = self.clock.getLongTime()
		self.aiUpdateCounter += 1
		return Task.cont

	#All the movement functions for the Target
	def setMovement(self):
		self.keyMap = {"left":0, "right":0, "up":0, "down":0}
		self.accept("arrow_left", self.setKey, ["left",1])
		self.accept("arrow_right", self.setKey, ["right",1])
		self.accept("arrow_up", self.setKey, ["up",1])
		self.accept("arrow_down", self.setKey, ["down",1])
		self.accept("arrow_left-up", self.setKey, ["left",0])
		self.accept("arrow_right-up", self.setKey, ["right",0])
		self.accept("arrow_up-up", self.setKey, ["up",0])
		self.accept("arrow_down-up", self.setKey, ["down",0])
		self.accept("a", self.createAgentAndAddItToTheWorld)
		#movement task
		taskMgr.add(self.Mover,"Mover")

		addInstructions(0.9, "Use the Arrow keys to move the Red Target")

	def setKey(self, key, value):
		self.keyMap[key] = value
		
	def Mover(self,task):
		startPos = self.target.getPos()
		if (self.keyMap["left"]!=0):
			self.target.setPos(startPos + Point3(-speed,0,0))
		if (self.keyMap["right"]!=0):
			self.target.setPos(startPos + Point3(speed,0,0))
		if (self.keyMap["up"]!=0):
			self.target.setPos(startPos + Point3(0,speed,0))
		if (self.keyMap["down"]!=0):
			self.target.setPos(startPos + Point3(0,-speed,0))

		return Task.cont

w = World()
base.setFrameRateMeter(True)
run()

The strange thing is that if we don’t have this two lines:

		self.AIcharsList.append(newChar)
		self.AIBehaviorsList.append(newBehaviors)

I run into a load of those errors:

Assertion failed: !is_empty() at line 1253 of panda/src/pgraph/nodePath.cxx
Assertion failed: !is_empty() at line 1754 of panda/src/pgraph/nodePath.cxx
Assertion failed: !is_empty() at line 6227 of panda/src/pgraph/nodePath.cxx
Assertion failed: !is_empty() at line 1253 of panda/src/pgraph/nodePath.cxx
Assertion failed: !is_empty() at line 1253 of panda/src/pgraph/nodePath.cxx
Assertion failed: !is_empty() at line 59 of c:\panda3d-1.7.0\built\include\boundingSphere.I

And why is that strange?

At the point:

  newWorld.addAiChar(newChar) 

I created a new world and added a character to it, I would assume it keeps a copy or a reference to that char and it shouldn’t be garbage collected.

I may be wrong, but it would be pretty strange to have to force a copy of an object that you just added to you class so it wouldn’t be garbage collected.

Specially because if the error happens, it means that the AIWorld object tried to access a reference to the AIChar object.

Ah, indeed. I’m not very familiar with pandaai myself (it’s contributed by a team separate from the core Panda development team), but looking at the code, it appears that you are correct: it does not save reference counts to the objects you store within it, but requires you to independently ensure they do not destruct.

David