It looks like after calling destroy on an ODE world/space pair that have been set up to use auto-collide, any other auto-collide pair will crash when it tries to step. The more objects that are in the system, the faster/more likely it will crash. With 100 bodies it almost always crashes immediately. The crash occurs in OdeSpace::auto_callback as it is trying to make a contract between two bodies which must be from the space which was deleted.
I’m pretty sure this is a bug, but it could be that I’ve missed something in what is necessary to clean up and remove an ODE world/space. Here is the stripped-down code which exposes the crash (after 8 seconds, the world that is populated is destroyed):
from panda3d.core import *
from panda3d.ode import *
loadPrcFileData('', 'window-type none')
loadPrcFileData('', 'audio-library-name null')
from direct.showbase.ShowBase import ShowBase
class Game(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.levels = []
level1 = Level(self)
level2 = Level(self)
for i in range(100):
Entity(level1, Point3(i, 0, 0))
taskMgr.add(self.update, 'game update', priority=-99)
def update(self, task):
if len(self.levels) > 1 and globalClock.getFrameTime() > 8:
self.levels[1].destroy()
return task.cont
class Level(object):
def __init__(self, game):
self.game = game
self.entities = []
self.world = OdeWorld()
self.world.initSurfaceTable(1)
self.world.setSurfaceEntry(0, 0, 0.5, 0.1, 0.1, 0.5, 0.00001, 0, 0)
self.space = OdeHashSpace()
self.joint_group = OdeJointGroup()
self.space.setAutoCollideWorld(self.world)
self.space.setAutoCollideJointGroup(self.joint_group)
self.steptime = 1.0 / 60.0
self.simtime = 0.0 # accumulated time to simulate
self.game.levels.append(self)
self.step_task = taskMgr.add(self.step, 'physics step')
def destroy(self):
print 'destroying level', self.game.levels.index(self)
for thing in self.entities[:]:
thing.destroy()
self.step_task.remove()
self.step_task = None
self.space.destroy()
self.space = None
self.world.destroy()
self.world = None
self.joint_group.destroy()
self.joint_group = None
self.game.levels.remove(self)
self.game = None
def step(self, task):
self.simtime += globalClock.getDt()
if self.simtime >= self.steptime:
for i in range(int(self.simtime / self.steptime)):
print 'stepping level', self.game.levels.index(self)
self.space.autoCollide()
self.world.quickStep(self.steptime)
self.joint_group.empty()
self.simtime = self.simtime % self.steptime
return task.cont
class Entity(object):
def __init__(self, level, bpos):
self.level = level
self.body = OdeBody(self.level.world)
mass = OdeMass()
mass.setBox(1000, 1, 1, 1)
self.body.setMass(mass)
self.geom = OdeBoxGeom(self.level.space, Vec3(1, 1, 1))
self.geom.setBody(self.body)
self.body.enable()
self.body.setPosition(bpos)
self.level.entities.append(self)
def destroy(self, task=None):
self.geom.destroy()
self.geom = None
self.body.destroy()
self.body = None
self.level.entities.remove(self)
self.level = None
Game().run()