Python Thread keep freezing the entire program?

2.6k Views Asked by At

Before everything, I've to say that I'm not very inside python programming as I am in other languages. I'm quite (too) annoyed for searching other ways of solution, so thank you in advance for your help.

I love to make Roguelike games in my free time, so I've tried numerous ways to do my own "engine" implementations and own engines for my own games using C++, C, C#, HTML5 etc. I've never worked before with LibTCOD because I never could make it work in C++ alas is my favourite programming language, that's an issue that I'm not going to talk right now for it's in C++ thread.

Awfully, LibTCOD looks great but has too few mentions and precise documentation, so I've to worked it almost alone. The last days I've done a little python package to easily manage LibTCOD functionality for python and windows, and make the main game code the tiniest possible.

The final implementation I tried to add is to pass the main game loop to a thread, handling every basic game functionality (like keyboard/mouse changes and screen update), and running it by a function call.

Everything works fine... but not after the first loop step, for it freezes everything and stops working.

Basically this is the problematic code:

def ioHandler(l):
    lastx = 0
    lasty = 0
    lastk = None
    c = 0
    noEvent = 0
    casted = False
    while not tcod.console_is_window_closed():
        l.acquire()
        try:
            tcod.sys_check_for_event(tcod.EVENT_KEY_PRESS|tcod.EVENT_MOUSE,key,mouse)
        finally:
            l.release()
        if mouse.lbutton_pressed:
            casted = True
            l.acquire()
            try:
                onClick(mouse, 'left')
            finally:
                l.release()
        if mouse.rbutton_pressed:
            casted = True
            l.acquire()
            try:
                onClick(mouse, 'right')
            finally:
                l.release()           
        if mouse.cx != lastx or mouse.cy != lasty:
            casted = True
            l.acquire()
            try:
                lastx = mouse.cx
                lasty = mouse.cy
                onMouseMove(mouse)
            finally:
                l.release()
        if key != lastk:
            casted = True
            l.acquire()
            try:
                lastk = key
                onKeyPress(key)
            finally:
                l.release()
        if not casted: noEvent += 1
        l.acquire()
        try:
            onTickFrame(c+1)
        finally:
            l.release()

Most of the variables used there are for clearer debug understanding purpose, (even with the 'clean' function it froze) so I've to put those there.

The above 'def' is called from here:

def main_loop():
    l = threading.Lock()
    tr = threading.Thread(target=ioHandler, args=(l,))
    #tr.daemon=True
    tr.start()

For the 'Event' system, I've found this on internet:

class Event:
    handlers = set()
    def __init__(self):
        self.handlers = set()

    def handle(self, handler):
        self.handlers.add(handler)
        return self

    def unhandle(self, handler):
        try:
            self.handlers.remove(handler)
        except:
            raise ValueError("Handler is not handling this event, so cannot unhandle it.")
        return self

    def fire(self, *args, **kargs):
        for handler in self.handlers:
            handler(*args, **kargs)

    def getHandlerCount(self):
        return len(self.handlers)

    __iadd__ = handle
    __isub__ = unhandle
    __call__ = fire
    __len__  = getHandlerCount

As a note: I'm working with Python 2.7, that's the only version that worked for the library, what a shame.

I think the event system may be the main problem. Reading the code again, I think I should apply a lock to the while condition too, therefore to the whole loop, or is that not necessary? Are the locks applied in the proper way? or should I use another methods to make the thread works?

Just to mention, everything works fine if the main game loop is doing on the main script without threads, but everything fails when called as a thread or even if it's not a thread itself but it's called from 'outside' as any other function in the package, so it can't be a library problem (I think).

I have to say, that I've only worked with LibTCOD in Python, as I can't make it work (at least on windows) only on it. If it helps, I've seen that the code for the python library is just a 'bind' for the original C library, so It's not a big deal to understand the python code. For the last statement, I think that's a problem for python threads too, or am I wrong? if there's something I can do to fix the thread implement, please help me!

Thank you all! I hope I have not bored you with my talk.

1

There are 1 best solutions below

1
On

There was enough missing in your example that I couldn't get it running in a timely manner, so I do not have a known solution for you, but I do have some suggestions:

  1. If the same thread will be releasing the lock that acquired it, I suggest using an RLock() instead of Lock()

l = threading.RLock()

  1. To make the code cleaner and less error probe, I suggest using the context manager that the lock provides:

Instead of:

l.acquire():
try:
    tcod.sys_check_for_event(
        tcod.EVENT_KEY_PRESS | tcod.EVENT_MOUSE, key, mouse)
finally:
    l.release()

Try:

with l:
    tcod.sys_check_for_event(
        tcod.EVENT_KEY_PRESS | tcod.EVENT_MOUSE, key, mouse)
  1. As to the question of what else should be locked. That is hard to answer without understanding all of the data structures, but in general anything that will be used in more than one thread should be locked.