Bizarre behaviour of Pygame mixed with Multiprocessing - python

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

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.

Python threading crashing on asset load in PyGame

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...

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

PyGame seems to be intercepting SIGINT from Ctrl-C

If I attempt to black out the screen using PyGame, PyGame seems to intercept SIGINT and I cannot use control-C to exit my program. If I simplify my code to the most basic pieces, it looks like this:
import signal
import time
import pygame
class Foo:
def __init__(self):
self.bgcolor = [0,0,0]
pygame.display.init()
pygame.font.init()
pygame.mouse.set_visible(False)
size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
self.screen = pygame.display.set_mode(size, pygame.FULLSCREEN)
self.blank_screen()
def blank_screen(self):
self.screen.fill(self.bgcolor)
pygame.display.update()
#pygame.quit()
def sighandle(self, signal, frame):
print('I got a ctrl-c')
self.close()
def close(self):
print('calling close')
pygame.quit()
def run(self):
print('I am running')
time.sleep(10)
if __name__=='__main__':
bar = None
try:
bar = Foo()
signal.signal(signal.SIGINT, bar.sighandle)
signal.signal(signal.SIGTERM, bar.sighandle)
bar.run()
finally:
print('in finally block')
if bar is not None:
bar.close()
If I remove the pygame parts, ctrl-c works as expected, and sighandle()'s message is printed. When I put the pygame parts in, the screen blanks as expected, but ctrl-c is ignored, and sighandle()'s message is never printed. The code just runs to completion. Furthermore, if I uncomment the commented out pygame.quit() in blank_screen(), ctrl-c works again, but of course the blacked out screen goes away.
Why is the code ignoring ctrl-c when I invoke PyGame, and how do I make it work as I expect, so that ctrl-c kills the program through sighandle?
After some research, I found that this is indeed the case by design. I don't see it explicitly mentioned in PyGame's documentation, though.
Ctrl-C can be enabled in the code above by handling it as an event. I can modify Foo.run() as such:
def run(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.sighandle()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_c and pygame.key.get_mods() & pygame.KMOD_CTRL:
print "pressed CTRL-C as an event"
self.sighandle()
print('I am running')
time.sleep(10)
I took the PyGame based screen blanking code from Adafruit's VideoLooper, which also included the signal library based handling of SIGINT. But I don't think their code will handle Ctrl-C as-is either.

Raspberry Pi and pygame.error: video system not initialized

I'm trying to run pygame on my Raspberry Pi Zero W, which has a PS4 controller hooked up to it. I found some code that should work but I get this error when I try to python3 game.py:
Traceback (most recent call last):
File "controller.py", line 74, in
ps4.listen()
File "controller.py", line 52, in listen
for event in pygame.event.get():
pygame.error: video system not initialized
The same code from someone else on Stackoverflow got it to work (at least that's what I assume), but he had a different problem, yet the same code. I did try to run that code instead, but I got the same error. I tried all suggestions I could find from Stackoverflow, but none of them worked. Here's the code I found:
import os
import pprint
import pygame
class PS4Controller(object):
"""Class representing the PS4 controller. Pretty straightforward functionality."""
controller = None
axis_data = None
button_data = None
hat_data = None
def init(self):
"""Initialize the joystick components"""
pygame.init()
pygame.joystick.init()
self.controller = pygame.joystick.Joystick(0)
self.controller.init()
def listen(self):
"""Listen for events to happen"""
if not self.axis_data:
self.axis_data = {}
if not self.button_data:
self.button_data = {}
for i in range(self.controller.get_numbuttons()):
self.button_data[i] = False
if not self.hat_data:
self.hat_data = {}
for i in range(self.controller.get_numhats()):
self.hat_data[i] = (0, 0)
while True:
for event in pygame.event.get():
if event.type == pygame.JOYAXISMOTION:
self.axis_data[event.axis] = round(event.value,2)
elif event.type == pygame.JOYBUTTONDOWN:
self.button_data[event.button] = True
elif event.type == pygame.JOYBUTTONUP:
self.button_data[event.button] = False
elif event.type == pygame.JOYHATMOTION:
self.hat_data[event.hat] = event.value
# Insert your code on what you would like to happen for each event here!
# In the current setup, I have the state simply printing out to the screen.
os.system('clear')
pprint.pprint(self.button_data)
pprint.pprint(self.axis_data)
pprint.pprint(self.hat_data)
if __name__ == "__main__":
ps4 = PS4Controller()
ps4.init()
ps4.listen()
Any clue what to do and why it's not working? I run this on Jessie Lite, so there is no desktop or anything like that.
pygame.init fails silently when a module cannot be initialized:
No exceptions will be raised if a module fails, but the total number if successful and failed inits will be returned as a tuple. You can always initialize individual modules manually, but pygame.init()initialize all imported pygame modules is a convenient way to get everything started. The init() functions for individual modules will raise exceptions when they fail.
In your case, it didn't initialize the display. To have it fail loudly, call pygame.display.init explicitly:
import pygame.display
pygame.display.init()

Categories