Python threading crashing on asset load in PyGame - python

I have constructed some code to load assets into a character class. This works fine in a single thread, but I want to have a loading screen when the assets are loading.
I put the asset loading into a function, and tried to make it run in a separate thread. The issue is that it now hangs, and seemingly does not even execute the main thread.
I've kept an eye on the memory usage, and the memory is well-behaved (never goes above 500M out of 1976M, bits or bytes I'm not sure, it's whatever PyCharm reports).
import threading
import os
import pygame
from pygame.locals import *
def load_assets(screen: pygame.Surface, results: List):
print("thread: started to load assets in thread")
appearances1 = {} # some dictionary, Dict[str, Dict[str, str]]
default_outfit = "office_wear"
print("thread: instance of game class to be created")
game = Game("lmao", screen) # crashes somewhere here when running multithreaded
print("thread: game initialized") # this is never achieved
# rest of function not relevant
results.append(game)
def main():
# ====== INITIALIZE PYGAME ======
pygame.init()
pygame.font.init()
screen = pygame.display.set_mode((1024, 768))
clock = pygame.time.Clock()
loading = create_loading_screen(screen)
loading.display(screen)
pygame.display.flip()
# ====== START OTHER THREAD FOR LOADING ASSETS ======
results = []
x = threading.Thread(
target=load_assets, args=(screen, results), daemon=True
)
x.start()
while x.is_alive():
# never see this print statement!
print("x is fine") # want to blit loading screen here
# if I comment out the threading, and run the following instead, my game boots up just fine
# in under 3-4 seconds of waiting for the assets to load
# load_assets(screen, results)
game = results[0]
# other code here to render the game, no more threading after this point
while 1:
for event in pygame.event.get():
if event.type == QUIT:
return -1
game.main()
pygame.display.flip()
clock.tick(game.fps)
if __name__ == '__main__':
main()
You can see the print statements here (everything hangs and we don't even make it to the main thread!)

I figured out the issue. I feel really dumb now... The issue was my Game initializer was touching the pygame.display() which I already knew it shouldn't... I need a nap...

Related

How to dynamically change self variables, parameters, args... in multiprocessing?

I don't know much of Python yet, but I'm trying to create an app that controls multiple streams of sound simultaneously (It has to do with binaural beats, noise and the brain). Given that I would like to control the volume and state of each of the tracks separately, I think that I need to use multiprocessing module. The first of those streams is a simple background music. I would like the user to pause it whenever he wants and I'm using pygame module.
import pygame
import time
import os
import multiprocessing
class play_music:
def __init__(self, path="", name=""):
self.state="" #pause, play, stop
self.name=name #name of the song
self.path=path #path of the song
def play_music(self):
path=self.path
pygame.init()
pygame.mixer.music.load(path)
pygame.mixer.music.play()
print (f"Playing {path}...")
while True:
if self.state=="pause":
pygame.mixer.music.pause()
self.state=""
while self.state=="":
if self.state == "continue":
pygame.mixer.music.unpause()
elif self.state=="stop"():
pygame.mixer.music.stop()
break
elif self.state=="stop":
pygame.mixer.music.stop()
break
def main():
task = ""
while not ( task == "meditacion" or task == "estudio"):
task = input ("Introduce que vas a hacer (meditacion, estudio): ").rstrip()
name ="non-existing track"
while not (os.path.exists(f"musica/{task}/{name}")):
name = input ("Introduce pista musical: ").rstrip()
path = f"musica/{task}/{name}"
print (f"Correct track. Path: {path}")
music = play_music()
music.path=path
music.name=name
p1 = multiprocessing.Process(target=music.play_music)
p1.start()
time.sleep(3) #for letting the process start correctly
while True:
music.state=input("pause, stop?: ")
if __name__=="__main__":
main()
This doesn't work. The state doesn't get modified whenever I input pause or stop. Any help is welcomed. Thank you so much in advance.
Since multiprocessing module creates separate Python processes, it is not possible to share variables in an easy way.
In case you would like to share variables yet still parallelize some of your computing, you should look into the built-in threading module.

Stop Thread without closing GUI window

I am learning python on my own and my level is probably a poor excuse for a "script kiddie" as I kinda understand and mostly end up borrowing and mashing together different scripts till it does what I want. However this is the first time I'm trying to create a GUI for one of the scripts I have. I'm using PySimpleGUI and I've been able to understand it surprisingly well. All but one thing is working the way I want it to.
The issue is I want to stop a running daemon thread without exiting the GUI. If I get the stop button to work the GUI closes and if the GUI does not close it doesn't stop the thread. The issue is between lines '64-68'. I have tried a few things and just put a place holder on line '65' to remember that I was trying to keep the GUI ("Main Thread" in my head-speak) running. The script will run in this state but the 'Stop' button does not work.
Note: I put a lot of comments in my scripts so I remember what each part is, what it does and what I need to clean up. I don't know if this is a good practice if I plan on sharing a script. Also, if it matters, I use Visual Studio Code.
#!/usr/local/bin/python3
import PySimpleGUI as sg
import pyautogui
import queue
import threading
import time
import sys
from datetime import datetime
from idlelib import window
pyautogui.FAILSAFE = False
numMin = None
# ------------------ Thread ---------------------
def move_cursor(gui_queue):
if ((len(sys.argv)<2) or sys.argv[1].isalpha() or int(sys.argv[1])<1):
numMin = 3
else:
numMin = int(sys.argv[1])
while(True):
x=0
while(x<numMin):
time.sleep(5) # Set short for debugging (will set to '60' later)
x+=1
for i in range(0,50):
pyautogui.moveTo(0,i*4)
pyautogui.moveTo(1,1)
for i in range(0,3):
pyautogui.press("shift")
print("Movement made at {}".format(datetime.now().time()))
# --------------------- GUI ---------------------
def the_gui():
sg.theme('LightGrey1') # Add a touch of color
gui_queue = queue.Queue() # Used to communicate between GUI and thread
layout = [ [sg.Text('Execution Log')],
[sg.Output(size=(30, 6))],
[sg.Button('Start'), sg.Button('Stop'), sg.Button('Click Me'), sg.Button('Close')] ]
window = sg.Window('Stay Available', layout)
# -------------- EVENT LOOP ---------------------
# Event Loop to process "events"
while True:
event, values = window.read(timeout=100)
if event in (None,'Close'):
break
elif event.startswith('Start'): # Start button event
try:
print('Starting "Stay Available" app')
threading.Thread(target=move_cursor,
args=(gui_queue,), daemon=True).start()
except queue.Empty:
print('App did not run')
elif event.startswith('Stop'): # Stop button event
try:
print('Stopping "Stay Available" app')
threading.main_thread # To remind me I want to go back to the original state
except queue.Empty:
print('App did not stop')
elif event == 'Click Me': # To see if GUI is responding (will be removed later)
print('Your GUI is alive and well')
window.close(); del window
if __name__ == '__main__':
gui_queue = queue.Queue() # Not sure if it goes here or where it is above
the_gui()
print('Exiting Program')
From this answer: create the class stoppable_thread.
Then: store the threads on a global variable:
# [...]
# store the threads on a global variable or somewhere
all_threads = []
# Create the function that will send events to the ui loop
def start_reading(window, sudo_password = ""):
While True:
window.write_event_value('-THREAD-', 'event')
time.sleep(.5)
# Create start and stop threads function
def start_thread(window):
t1 = Stoppable_Thread(target=start_reading, args=(window,), daemon=True)
t1.start()
all_threads.append(t1)
def stop_all_threads():
for thread in all_threads:
thread.terminate()
Finally, on the main window loop, handle the events that start, stop or get information from the thread.

Run simultaneous process inside python class

I'm developping a game using pygame and I want to create a loading screen while the assets are loaded. The loading screen have animations, so loading screen and assets loading should be occurring at the same time.
Consider the code below:
class Game:
def __init__(self):
self.loading_screen()
self.load_assets()
def loading_screen(self):
# do something while load_assets() is running
def load_assets(self):
# load all assets needed
I've tried Process from multiprocessing, but I dont know how to keep loading_screen() running without freezes while load_assets() are running.
Also, I've tried threads, but python doesn't run threads simultaneously, so, in some moment, the loading_screen() will freeze. (This could be wrong, but this was observed in the game)
Some help about this?
Thanks for all
Threads are suitable for this. Nothing runs exactly simultaneously on a multi user system anyway.
import threading
from time import sleep
class Game:
def __init__(self):
self.loading_screen()
def loading_screen(self):
t = threading.Thread(target=self.load_assets)
t.daemon = True
t.start()
while t.isAlive():
sleep(0.1)
print("loading screen")
print ("assets done, ready to proceed now")
def load_assets(self):
i = 0
while i < 10:
print("loading assets")
i += 1
sleep(1)
g = Game()
print("done")
This puts your asset loading to a thread and then enters loading_screen() in the main thread. This just prints messages to demonstrate they both run in parallel.
I can understand what problems the OP is having, a preferred solution is to use multiprocessing rather than multithreading. The given demo from the first answer uses time.sleep for simulating 'heavy loading' in the loading function, but when loading is actually going to take place, it will take a major amount of performance out of the CPU.
So it might happen that your loading animation is playing at 5-10 FPS when you expected 100+ FPS from it.
In this case, it is best to use multiprocessing to achieve the same. From what I have heard, it is a bit complicated to implement it, but it is quite possible. The loading function will then run as a separate process, not a separate thread from the same process.
Here is a small demo program to illustrate a loading screen using multiprocessing:
import multiprocessing
from multiprocessing.connection import Connection
def loading_screen(conn: Connection, stage):
clock = pygame.time.Clock()
fps = 60
c = 0
font = pygame.font.SysFont('consolas', 25)
while True:
screen.fill((0, 0, 50))
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_ESCAPE:
return
if conn.poll(): # check if any object is available to be received or not
# this line is used to prevent waiting until the next object is received
c = conn.recv() # if available, receive the object
if c == 'end':
# process end condition checking
return
pygame.draw.rect(screen, 'white', (50, 50, c, 50))
screen.blit(font.render(f'{c} / {stage * 100} % [stage {stage}]', True, 'white'), (50, 150))
screen.blit(font.render('Check console for status', True, 'white'), (50, 200))
pygame.display.update()
clock.tick(fps)
pygame.display.set_caption(f'Multiprocessing Loading Screen [FPS = {int(clock.get_fps())}]')
def resource_load(conn: Connection, items):
# load resources
# for now it increments a counter and sends it via the connection object
# you can replace it for CPU intensive processes
# and pass the loaded objects to the connection object
c = 0
while True:
c += 1
if c > items:
conn.send('end') # signal end of loading current stage
return # returning will end the process automatically
conn.send(c) # send the counter value via the connection object
if __name__ == '__main__':
import pygame
pygame.init()
screen = pygame.display.set_mode((500, 300)) # initialize display
receiver, sender = multiprocessing.Pipe(duplex=False) # initialize sender and receiver connection objects [unidirectional]
print('Program to demonstrate a simple loading screen using multiprocessing')
print()
print('starting stages...')
multiprocessing.Process(target=resource_load, args=(sender, 100)).start()
print('loading stage 1')
loading_screen(receiver, 1)
multiprocessing.Process(target=resource_load, args=(sender, 200)).start()
print('loading stage 2')
loading_screen(receiver, 2)
multiprocessing.Process(target=resource_load, args=(sender, 300)).start()
print('loading stage 3')
loading_screen(receiver, 3)
input('finished loading all stages... press Enter to exit console') # press Enter inside the console to exit program
EDIT:
I have made a video on this if interested: https://www.youtube.com/watch?v=KWGDgPldPVo

Bizarre behaviour of Pygame mixed with Multiprocessing

I am working on a small project including Raspberry Pi camera. I am using multiprocessing on the PC to perform various tasks and one of them is displaying images in a Pygame window. I have manager to minimize the code suffering the problem. This is the main function, which just creates a Game process, runs it and waits for it to end:
# main.py
from multiprocessing import Manager
import game
with Manager() as manager:
namespace = manager.Namespace()
namespace.done = False
jobs = [
game.Game(namespace),
]
for job in jobs:
job.start()
for job in jobs:
job.join()
Below is the code of the Game class. The state is shared because there will be other processes checking if the program has finished.
# game.py
import multiprocessing
import pygame
import time
import sys
class Game(multiprocessing.Process):
def __init__(self, the_state):
super(Game, self).__init__()
pygame.init()
self.screen = pygame.display.set_mode((100, 100))
self.state = the_state
def handle_key_events(self):
""" Wrapper for handling key events """
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.state.done = True
return
def run(self):
while True:
self.handle_key_events()
if self.state.done:
print('exiting PyGame thread')
pygame.display.quit()
pygame.quit()
return
The behaviour right now is that it doesn't end on clicking the Pygame's exit button. However, if I comment out the pygame.quit() line from Game's run method, it gives me the following message:
exiting PyGame thread
XIO: fatal IO error 11 (Resource temporarily unavailable) on X server ":0"
after 120 requests (120 known processed) with 1 events remaining.
I tried googling that error but little success. I can't shake off the feeling that I'm attempting something I shouldn't be, considering that probably the Pygame's window is a process on its own.
I would appreciate some insight and recommendations on where to read up
As Blckknght suggested, the issue was that the parent process (main.py) initiated PyGame's window, while the child process (game.py) attempted to quit it. It can be resolved by moving this part:
pygame.init()
self.screen = pygame.display.set_mode((100, 100))
into Game's run method

PyGame code doesn't execute properly when event is executed

I am trying to make the most simple pythong code that will respond when a button is pressed on a joystick. I used code from several different examples and I still cannot get it to work. The following code will not dispatch the event when I press the trigger (or any button for that matter)
import pygame
joy = []
def handleJoyEvent(e):
if e.type == pygame.JOYBUTTONDOWN:
str = "Button: %d" % (e.dict['button'])
if (e.dict['button'] == 0):
print ("Pressed!\n")
else:
pass
def joystickControl():
while True:
e = pygame.event.wait()
if (e.type == pygame.JOYBUTTONDOWN):
handleJoyEvent(e)
# main method
def main():
pygame.joystick.init()
pygame.display.init()
for i in range(pygame.joystick.get_count()):
myjoy = pygame.joystick.Joystick(i)
myjoy.init()
joy.append(myjoy)
# run joystick listener loop
joystickControl()
# allow use as a module or standalone script
if __name__ == "__main__":
main()
I assume you've tried leaving off the if and just printing str?
Your joystick might also not be working properly. Does it work in other programs?
If you are using linux you might need to install a joystick driver. For Windows, check the device manager.

Categories