1
\$\begingroup\$

I have decided to try out pygame. I created a class that is supposed to handle all the GUI and game related stuff.

My idea is that I would have two seperate threads. One to handle the GUI and one to handle all the calculations the game does in the background.

import threading
import pygame
import pygame_gui


class Game ():


    engine_running = True
    gui_running = True
    clock = pygame.time.Clock()

    gui_framerate = 60.0
    engine_framerate = 120.0

    def __init__(self):

        pygame.init()
        pygame.display.set_caption('Explodotech')
        self.window_surface = pygame.display.set_mode((800, 600))
        self.background = pygame.Surface((800, 600))
        self.background.fill(pygame.Color('#000000'))

        self.manager = pygame_gui.UIManager((800, 600))

        ### Define GUI Elements here

        self.quit_button = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((650, 500),
                        (100, 50)), text='Quit', manager=self.manager,                         
                        tool_tip_text = "Quit the game")

        ### Create and start the threads
        self.gui_thread = threading.Thread(target = self.gui_loop)
        self.engine_thread = threading.Thread(target = self.engine_loop, args = [])

        self.gui_thread.start()
        self.engine_thread.start()


    def start_polling(self):
        """Starts the main loop"""
        self.engine_running = True

    def stop_polling(self):
        """Stops the main loop"""
        self.engine_running = False

    def gui_loop(self):
        """Managing all the GUI stuff"""

        print ("GUI-loop started!")

        while self.gui_running:
            print("Polling GUI")
            dT = self.clock.tick_busy_loop(60)/1000.0

            for event in pygame.event.get():

                if event.type == pygame.QUIT:
                    self.gui_running = False

                if event.type == pygame.USEREVENT:
                    if event.user_type == pygame_gui.UI_BUTTON_PRESSED:
                        if event.ui_element == self.quit_button:
                            self.quit_button_event()

                self.manager.process_events(event)

            self.manager.update(dT)

            self.window_surface.blit(self.background, (0, 0))
            self.manager.draw_ui(self.window_surface)

            pygame.display.update()

    def engine_loop(self):
        """Doing all the game calculations in the background"""
        while self.engine_running:
            print(self.clock.tick_busy_loop(1))
        

    ### Define GUI events here

    def quit_button_event(self):
        """Shut down the GUI"""
        print("Quit-button pressed!")
        self.gui_running = False
        self.engine_running = False

Basically what's happening here is that I have two threads one should run the "gui_loop" function the other should run the "engine_loop". However when the gui thread starts and gets into its while-loop it get stuck. The windows goes unresponsive and I have to force windows to shut it down. However if I a create a Game-object and call the function directly it all works fine (obviously commenting out the party where I create the Thread-object):

g = Game()
g.gui_loop()

Any pointers what I am doing wrong would be greatly appreciated!

\$\endgroup\$
3
  • \$\begingroup\$ This past Q&A suggests that your event loop should be on the main thread (where __init__ is called), rather than on a separate thread. Does changing where you do the event polling have any impact in your case? \$\endgroup\$
    – DMGregory
    Commented Nov 9, 2020 at 18:31
  • \$\begingroup\$ not really... I think... I am still at the very beginning and would like to understand whats happening (or why it's NOT happening). But I will still be able to feed data into the gui from the other thread, right? \$\endgroup\$ Commented Nov 9, 2020 at 18:45
  • 1
    \$\begingroup\$ You should start by doing everything on a single thread. That's the typical game loop; it spins until the game exits, and in each cycle you fetch inputs, update the game world, draw the game, and draw the GUI on top, over and over again, all on one thread. If there's more then one thread, then you have to worry about synchronization problems. And if it's not done carefully, it may actually be slower because of context switching, locking, etc. Then there are these platform-specific concerns about GUI rendering. All that makes things needlessly harder. \$\endgroup\$ Commented Nov 10, 2020 at 19:48

3 Answers 3

2
\$\begingroup\$

Your program gets stuck after closing because you only stop the engine, not the Python program.

You need to exit the system as follows:

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        pygame.quit()
        sys.exit()
\$\endgroup\$
1
\$\begingroup\$

Python is an interpreted language, and there's a thing called Global Interpreter Lock, which prevents multiple threads to run Python code simultaneously. (See details in Documentations of threading module.) If you run two full while loops in a single Python process, one of them is going to block another. You GUI freezes because your core loop blocked it. My computer, on the other hand, won't freeze the GUI, but the core thread won't run because the GUI thread blocked it.

You can have two ways around this:

  1. Try combine two while loops into one, run the higher frequency one as main loop, and count how many time it has looped to occasionally run the lower frequency update.

     [Pseudo Code]
     update(self):
       dt = self.get_dt(self.core_freq)
       self.update_core()
       self.accumulated_dt += dt
       if self.accumulated_dt > 1/self.gui_freq:
         self.update_gui()
         self.accumulated_dt = 0
    
  2. Use multiprocessing to create multiple processes, and communicate between to share data. Python multiprocessing gets around the GI Lock by create more processes, but communicating between them can be costly and dangerous (causing data races). So, I won't suggest you to make a game with it. Complex mechanisms like this should really be handled at engine level.

\$\endgroup\$
0
\$\begingroup\$

I am not sure why your code doesn't run correctly on your pc; it runs perfectly fine on mine.

Maybe your CPU doesn't support multithreading, or just doesn't have enough cores. My pc has 8 cores with 16 threads, not sure about your PC.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .