Threading

My terrain system generates terrain tiles. This process however requires the rendering of several textures, and a few other slow things. I would like to thread this process so that when it comes time to render something, instead of calling:
base.graphicsEngine.renderFrame()
I want to wait for a frame to render, and also spread the work out across several frames.

The manual does not show how to use threads, just how to make them:
panda3d.org/manual/index.php/Threading

The reference has a lot of classes related to such things:
panda3d.org/apiref.php?page=TaskThread
panda3d.org/apiref.php?page=TaskThreaded
panda3d.org/apiref.php?page=PythonThread
but all the methods are undocumented.

I think what I want to do is run my tile making task as a TaskThreaded in a TaskThread, and I need some command to pause the thread until the next frame (or at least a way to yield, and I can use a flag to see if a frame has rendered). I can’t figure out how to do this though. Any help would be appreciated.

Thanks
-Craig

Pay no attention to those classes in the API reference; they’re very low-level. There are much better high-level tools for threading your tasks, which are (as you have noted) as yet unfortunately undocumented.

In a nutshell, the idea is to create a new “task chain”. As of Panda3D 1.6, the task manager manages tasks in one or more “task chains”. By default, all tasks go onto the default task chain, but you can have any number of separate task chains. Each task chain is a linear list of tasks, and each task chain can be in its own thread.

So, to create a threaded task, you only need to do:

taskMgr.setupTaskChain('myTaskChainName', numThreads = 1)
taskMgr.add(self.myTask, 'myTask', taskChain = 'myTaskChainName')

Now myTask will run in its own thread, independent of the default task chain which runs in the main thread. There are some implications with running a task in a thread.

(1) You can’t call base.graphicsEngine.renderFrame() from a sub-thread. You shouldn’t try, it might appear to work, but it’s bad.

(2) You have to protect yourself from race conditions with synchronization primitives, provided in direct.stdpy.threading. If you’re not already familiar with standard threading synchronization primitives, there are plenty of resources on the net to learn about them–it’s a wide field of study, and I can’t possibly describe it in a forum post.

(3) You have to call Thread.considerYield() from time to time. Panda threads are (as built by default) semi-cooperative, which has the advantage that it protects you from many of the dangers of getting (2) wrong, but it does mean you can block other threads indefinitely by failing to call Thread.considerYield() frequently enough.

(4) When you send a message via messenger.send(), you can arrange for the receiver to hear the message on your current thread (the default behavior) or on the thread of another taskChain, by passing taskChain = ‘otherTaskChainName’ on the send() call. The default, main task chain is called ‘default’.

(5) Pausing a thread means blocking it, via a synchronization primitive such as a condition variable or a semaphore.

If you’re not already very comfortable with threading, though, I don’t really recommend diving into it unless there really is no other way to solve your problem. Threading is a very tricky business, it’s real easy to get wrong, and real hard to debug when it is wrong.

David