Thread loading GeoMipTerrain unstable

So like the title implies. I’ve been attempting to load GeoMipTerrains with threads, but my program has been freezing randomly. I’m not to experienced with threaded programming so I spent a long time puzzling over these crashes. I eventually recreated the crash as simply as I could to see if I could rule out something I was doing, but it still crashes.

from direct.stdpy import threading2 as threading
#"from direct.stdpy import threading" crashes even sooner
from pandac.PandaModules import GeoMipTerrain
from pandac.PandaModules import NodePath
from pandac.PandaModules import PNMImage
from direct.showbase.ShowBase import ShowBase
from direct.task import Task

def makeGeoMipTerrain():
    print "thread building GeoMipTerrain"
    terrain = GeoMipTerrain("myDynamicTerrain")
    terrain.setHeightfield(PNMImage(129, 129, 1, 65535))
    terrain.generate()
    print "done"

class TerrainBuilderThread(threading.Thread):
    def run(self):
            makeGeoMipTerrain()

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        # Load the environment model.
        self.environ = self.loader.loadModel("models/environment")
        # Reparent the model to render.
        self.environ.reparentTo(self.render)
        # Apply scale and position transforms on the model.
        self.environ.setScale(0.25, 0.25, 0.25)
        self.environ.setPos(-8, 42, 0)
        taskMgr.doMethodLater(1, self.spawnTerrainThread, 'threadSpawnTask')

    def spawnTerrainThread(self, task):
        print "spawning thread"
        TerrainBuilderThread().start()
        return Task.again

app = MyApp()
app.run()

Ok it seems if I use the normal python threading library I can run this without any crashing…
I wish I had thought of trying that hours ago I’ve been running traces on my program, restructuring code, and trying every debugging trick I could think of. The Panda3d manual seems to discourage using the normal python threading module.

GeoMipTerrain is not thread-safe at the moment, sorry. Make sure that you’re not calling into the same GeoMipTerrain at the same time.

Well it seems my problems have just been compounded. For some reason the application is actually waiting on the thread making it completely useless.

For example see what the thread does to the hello world program.

#from direct.stdpy import threading2 as threading
#"from direct.stdpy import threading" crashes even sooner

from math import cos
from math import pi
from math import sin

from direct.actor.Actor import Actor
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
import logging
from pandac.PandaModules import GeoMipTerrain
from pandac.PandaModules import NodePath
from pandac.PandaModules import PNMImage
import threading
logging.basicConfig(level=logging.INFO,
                    format='(%(threadName)-10s) %(message)s', )


def makeGeoMipTerrain():
    logging.info("thread building GeoMipTerrain")
    terrain = GeoMipTerrain("myDynamicTerrain")
    terrain.setHeightfield(PNMImage(1025, 1025, 1, 65535))
    terrain.generate()
    logging.info("done")

class TerrainBuilderThread(threading.Thread):
    def run(self):
        while True:
            makeGeoMipTerrain()

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        # Load the environment model.
        self.environ = self.loader.loadModel("models/environment")
        # Reparent the model to render.
        self.environ.reparentTo(self.render)
        # Apply scale and position transforms on the model.
        self.environ.setScale(0.25, 0.25, 0.25)
        self.environ.setPos(-8, 42, 0)

        # Add the spinCameraTask procedure to the task manager.
        self.taskMgr.add(self.spinCameraTask, "SpinCameraTask")

        # Load and transform the panda actor.
        self.pandaActor = Actor("models/panda-model",
                                {"walk": "models/panda-walk4"})
        self.pandaActor.setScale(0.005, 0.005, 0.005)
        self.pandaActor.reparentTo(self.render)
        # Loop its animation.
        self.pandaActor.loop("walk")

        logging.info("spawning thread")
        TerrainBuilderThread().start()

    # Define a procedure to move the camera.
    def spinCameraTask(self, task):
        angleDegrees = task.time * 6.0
        angleRadians = angleDegrees * (pi / 180.0)
        self.camera.setPos(20 * sin(angleRadians), -20.0 * cos(angleRadians), 3)
        self.camera.setHpr(angleDegrees, 0, 0)
        return Task.cont
        
app = MyApp()
app.run()

Try putting “Thread.considerYield()” in the infinite loop.

Which version of Panda are you using, and which threading model did you enable in Config.prc?

Oh I see. Thank you. Odd it does seem perfectly stable with the normal python threading module.

Anyways I’m just using threads to make new instances of GeoMipTerrain, which are handed off to a queue where they can be picked up and used immediately without stalling the frame. There shouldn’t be any possibility of different threads calling on it at once.

Yielding there wouldn’t matter unless the thread was looping really fast right? Anyways I get NameError: global name ‘Thread’ is not defined and I don’t seem to have a ‘Thread’ module either. I can use ‘thread’ lowercase which has no considerYield apparently.

Also I’m using Panda 1.8 and I ran this program without a local config.prc so whatever the default in 1.8 is. Adding threading-model Cull/Draw makes it crash right away no matter which version of the threading module I use.

Ok i did a further test and it seems that it is specifically related to the GeoMipMap. Replace the function above with this one and you can see things run smooth while doing continuous calculations but not generating terrain.

def makeGeoMipTerrain():
    logging.info("wasting time adding")
    x = 0
    while x < 9999999:
        x+=1
    logging.info("Done adding. Building GeoMipTerrain")
    terrain = GeoMipTerrain("myDynamicTerrain")
    terrain.setHeightfield(PNMImage(1025, 1025, 1, 65535))
    terrain.generate()
    logging.info("Done building GeoMipTerrain.")

I suppose the python threading library knows how to yield during the math loop, but not during generate() because its spending its time in the c++ engine? Even if it can’t yield it wouldn’t be catastrophic if it could be pushed to another processor core, but I don’t suppose that’s possible to force.