7

I want to use a pyGame program as a part of another process. Using the following code, pyGame doesn't seem to be processing events; it doesn't respond to the 'q' key nor does it draw the titlebar for the window. If go() is not run as a thread, it works fine. This is under OSX; I'm unsure if that's the problem or not.

import pygame, threading, random

def go():
  pygame.init()
  surf = pygame.display.set_mode((640,480))
  pygame.fastevent.init()

  while True:
    e = pygame.fastevent.poll()
    if e.type == pygame.KEYDOWN and e.unicode == 'q':
      return

    surf.set_at((random.randint(0,640), random.randint(0,480)), (255,255,255))
    pygame.display.flip()

t = threading.Thread(target=go)
t.start()
t.join()
Dan
  • 2,766
  • 3
  • 27
  • 28
  • Your code runs fine on my (linux) machine. – nosklo Jun 04 '10 at 17:12
  • 1
    Also runs on my linux machine, seem to be OSX specific problem. – mr.Shu Aug 28 '11 at 07:33
  • 1
    FYI, Python has a GIL (global interpreter lock) that prevents true parallelism with threads (i.e., if you use threads, it WILL NOT make your code faster). – geometrian Feb 29 '12 at 05:52
  • Having the same problem on a Mac where the drawing doesn't happen if it isn't done in the main thread. Not really a solution and rather more of an observation is that if the display.flip() is called in the main thread while the drawing is done in the other the screen will update as properly required. – fthinker Jul 29 '15 at 03:33

5 Answers5

8

Pygame is not threadsafe and the eventloop is required to run on the main thread! Otherwise, the problem you describe can occur.

One solution is to call pygame.mainloop() from the main thread.

However,maybe you are using other modules that also require running from the main thread. There is in this case one pythonic solution. you have the possibility to run pygame mainloop with an argument. This argument means: run the mainloop for only a few seconds. Hence what you can do is create a generator that runs mainloop for a 0.1 second that you call periodically from main thread. For example:

def continue_pygame_loop():
    pygame.mainloop(0.1)
    yield

then just call continue_pygame_loop() periodically from main thread

Tkinter suffers from the same problem, but has no way to specify runloop() with a timeout. For me, this is why pygame is great!

martineau
  • 119,623
  • 25
  • 170
  • 301
Nath
  • 109
  • 1
  • 2
  • 4
    As far as I know and can find out, `pygame` has no `mainloop()` function. (I'm using v1.9.2), so it unclear what you're talking about. As you confusing it with `tkinter` (which does)? – martineau Jun 14 '17 at 20:29
6

Possibly a long shot, but try making sure you don't import Pygame until you're in the thread. It's just about possible that the problem is that you're importing Pygame on one thread and then using it on another. However, importing on multiple threads can have other issues. In particular, make sure that once you start the Pygame thread you wait for it to finish importing before you do anything that might cause the Python process to shut-down, or you might get a deadlock.

Weeble
  • 17,058
  • 3
  • 60
  • 75
5

It's best to do the event handling and graphics in the main thread. Some environments really don't like you trying to render from other threads, and some don't like you trying to drain the event queue from them either.

It may not even be possible to do what you're hoping to do, since the process you're running within may well have its own ideas about who owns the message queue and the window you're rendering to.

Kylotan
  • 18,290
  • 7
  • 46
  • 74
4

Not really an answer, just an affirmation of the fact that this problem does indeed occur on Mac OS X and not on Linux. I developed my program on Ubuntu and this sort of code worked fine there, but it failed as you described when I tried to run it on a Mac. Running the drawing code in the main thread instead works fine.

If this is really a limitation of either the threading module or pygame (on Mac), then I guess the only way is to restructure your program so that all drawing is handled by the main thread. Alternatively, file a bug report and see what happens.

EDIT: Another, much better, option would be to use the multiprocessing module. You could spawn a separate python process just for rendering to screen. Processes can then communicate information to each other. I'm looking into using this myself.

ztangent
  • 1,811
  • 2
  • 14
  • 11
0

This was solved for me by calling pygame.init() in the thread being started rather than at the top. For example my class which initializes a thread and defines a function which will be the main loop:

class Thing():
    # other class stuff
    def __init__(self):
        # other init stuff
        self.thread = threading.Thread(target=self.gogo)
        self.thread.start()

    def gogo(self):
        print('gogo')
        pg.init()  # <-THIS!
        self.screen = pg.display.set_mode(SIZE)
        while self.get_working():
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    # etc

Before, the screen and init were done at the start, it completely worked on Ubuntu 18.04, just not on a window 10 machine.

bobbins
  • 21
  • 3