Threading in game engine? [SOLVED]

Hi,
I’m experimenting with threads in game engine (blender 2.55). I need an udp-client and some data fetching over the net.

What concerns me is the fact that threads are not stopped when game engine is stopped. Instead they keep running until they are finished or forever if there is a loop. This may cause problems when game engine is stopped and started several times.

So, is it generally a bad idea to use threads in game engine? Any experiences?

The general consensus is that the BGE doesn’t support threading. However, given enough experimentation, something might be feasible. One thing to keep in mind is that due to Python’s GIL (you can Google it if you want more info) Python scripts do not gain a whole lot from threading. Instead, you might want to look into something like the multiprocessing module, which has a similar interface to the threading module but it uses multiple processes instead of multiple threads. Also, a word of caution: Python doesn’t exit when you stop a game when using the embedded player. This sometimes allows for garbage (especially from threads) to build up. Maybe some sort of on exit script can fix this.

Good luck,
Moguri

Thanks for the reply!

The general consensus is that the BGE doesn’t support threading.

This was my fear also.

The main reason I want to use threads is that I want to separate my net traffic from game ticks (that are related to FPS, I believe?). Now the udp client triggered by always sensor is slowing down the whole application. Increasing the frequency of always sensor makes things better but it just doesn’t seem to be a good solution.

I will experiment with multiprocessing module.

Okay, this maybe the ugliest solution ever but it seems to work:

from threading import Thread
import time
    
class MyThread ( Thread ):

    def run ( self ):
        while(1):
            print("You called my start method")
            time.sleep(5)
            try:
                import bge
            except:
                return      

MyThread().start()  

bge module only exists when game engine is running. So we test that in thread and if it is not found then we return. As I said, this may break all the rules but it works :slight_smile:

LOL Yeah, it seems like a viable solution, so press on. It should work alright.

Or you let the game send an echo message to the thread every X milliseconds. If there is not new message end end the thread. It is ugly too ;).

I would make use of a separate process if you truly wanted to separate your networking from the rest of your logic. You can also try making wrapper class around a thread:


import bge

class thread_ptr:
    __init__(self, thread):
        self.thread = thread
    __del__(self):
        self.thread.terminate() # not in Threads

# Assuming MyThread is the thread you want to use and extends Thread by adding some sort of terminating ability

bge.logic.thread = thread_ptr(MyThread)

Now, when thread_ptr gets grabbed by the garbage collector, it should kill MyThread. You can do some Googling around for figuring out how to actually get the thread to terminate. Also, I didn’t test any of this. :wink:

Cheers,
Moguri

Thanks for suggestions!

I’ve been playing around with different approaches. I sort of got multiprocessing working but I didn’t manage to terminate them properly. No big problem since threads are enough in this case.

Using only threads things worked ok except updating textures with videotexture. It seems that I cannot use videotexture from a thread. Here is a back trace from one attempt:

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xb06b0b70 (LWP 2437)]
0x002e4368 in ?? () from /usr/lib/libGLU.so.1
(gdb) bt
#0  0x002e4368 in ?? () from /usr/lib/libGLU.so.1
#1  0x002ea9fc in gluScaleImage () from /usr/lib/libGLU.so.1
#2  0x08e05731 in Texture_refresh(Texture*, _object*) ()
#3  0x092486f0 in PyEval_EvalFrameEx ()
#4  0x0924933d in PyEval_EvalFrameEx ()
#5  0x0924933d in PyEval_EvalFrameEx ()
#6  0x0924933d in PyEval_EvalFrameEx ()
#7  0x09249ea1 in PyEval_EvalCodeEx ()
#8  0x091f86ba in ?? ()
#9  0x091e08bd in PyObject_Call ()
#10 0x091ece4f in ?? ()
#11 0x091e08bd in PyObject_Call ()
#12 0x09242932 in PyEval_CallObjectWithKeywords ()
#13 0x0927e92f in ?? ()
#14 0x0077396e in start_thread () from /lib/tls/i686/cmov/libpthread.so.0
#15 0x006b7a4e in clone () from /lib/tls/i686/cmov/libc.so.6

The idea was to download an image and change it in the thread, so that game engine wouldn’t have to wait. This may not be possible?

If nothing else helps, then I’ll make a preload script that collects all the data to files on engine start.

My suggestion:

just download the image by your thread as you described.
Set a property at an KX_GameObject, to notify there is a new image.
The KX_GameObject senses the property and deals with the VideoTexture as usual.

I think this provides a good modular solution (thread deals with network, KX_GameObject deals with videotexture, interface = property)

@Monster: Thanks, I’ll try that.

Meanwhile, here is a sample script of using threads in bge. Like Moguri suggested, I created class that terminates thread on exit. Not tested very well but this seems to work.

import bge
# example script of using threads in Blender 2.55 game engine
# thread(!) in ba: http://blenderartists.org/forum/showthread.php?t=204802
# Example of terminating thread is from here:
#http://bytes.com/topic/python/answers/45247-terminating-thread-parent


import threading

# class that is responsible for stopping thread (thanks to Moguri@blenderartists)
class thread_ptr():
    def __init__(self,t):
        print("creating thread_ptr")
        self.thread = t
        
    def __del__(self):
        print("deleting thread_ptr")
        self.thread.join()
        #self.thread.terminate() # not in Threads



class TestThread(threading.Thread):
    """
    A sample thread class
    """

    def __init__(self):
        """
        Constructor, setting initial variables
        """
        print("Creating thread")
        self._stopevent = threading.Event()
        self._sleepperiod = 1.0

        threading.Thread.__init__(self, name="TestThread")

    def run(self):
        """
        overload of threading.thread.run()
        main control loop
        """
        print("starts ", self.getName())

        count = 0
        while not self._stopevent.isSet():
            count += 1
            print ("loop " ,count)
            # do some heavy lifting
            for i in range(100000):
                n = i
            #self._stopevent.wait(self._sleepperiod)

        print ("ends ",self.getName())

    def join(self,timeout=None):
        """
        Stop the thread
        """
        print("Terminating thread")
        self._stopevent.set()
        threading.Thread.join(self, timeout)


# run this script only once
ob = bge.logic.getCurrentController().owner

if ob['init'] == 0:
    ob['init'] = 1

    print("INITIALISING")
    thread = TestThread()
    bge.logic.thread = thread_ptr(thread)
    thread.start()
    print("GOING ON")


Thanks for the help everyone!