This question already has an answer here:
Multithreading with Pygame
(1 answer)
Closed 1 year ago.
Recently i completed learning about threading in python and now trying to implement it in a program. And here is the code:
from threading import Thread
from time import sleep
import pygame as py
py.init()
X,Y = 900, 800
APP = py.display.set_mode((X, Y))
APP = py.display.set_caption("Test")
def exit_app():
"To terminate program."
while True:
for eve in py.event.get():
if eve.type == py.QUIT:
py.quit()
exit()
def update_display():
"Update pygame window"
while True:
sleep(3)
py.display.update()
if __name__ == "__main__":
Thread(target=exit_app).start()
Thread(target=update_display).start()
Now the problem is that after creating thread i ran into a problem.
When i tried to run code it get executed without any problem but when the code exit(i.e., when i close the pygame window) i get to see the error as follows:
$python3 test.py
pygame 2.0.1 (SDL 2.0.14, Python 3.9.4)
Hello from the pygame community. https://www.pygame.org/contribute.html
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
self.run()
File "/usr/lib/python3.9/threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "/home/cvam/Documents/test.py", line 23, in update_display
py.display.update()
pygame.error: video system not initialized
Ok, so first of all it seems like you are trying to create separate functions with infinite loops for updating the screen and for event handling, to clear the same, all of it can be done in one loop and is perhaps better way of doing the same.[Thus I have removed the exit_app and the update_display function since they use two other infinite while loops.]
Secondly there is no need to use a separate thread for event handling, since the event handling can be done in the same loop, when you create a separate thread for it with an infinite while loop, then that results in confusion as to which thread pygame shall run on, the main thread or the THREAD1.[Thus the same has been commented out.]
Also placing pygame.init function call and all other statements that were before outside inside the if name == 'main' block seems to ensure that they are executed. Also placing them before the thread starts makes sure that they are executed and that pygame stuff is initialized before the thread executes.
Also some suggestions -:
Instead of the sleep function from the time module, a similar function of the pygame time module namely the pygame.time.delay function can be used, its present within pygame and thus is more suitable to use.
I have experienced many errors while quitting the pygame programs using the inbuilt exit and quit functions, and thus seem to prefer using exit function of the sys module of python since it leads to less errors in most cases.
The code for the same shall become -:
# from threading import Thread
# from time import sleep
import pygame
import sys
if __name__ == "__main__":
pygame.init()
X,Y = 900, 800
APP = pygame.display.set_mode((X, Y))
APP = pygame.display.set_caption("Test")
# # By detecting the events within the main game loop there is no need
# # to call a separate thread, and the same may not be working since the two
# # threads the main one and the THREAD1 are both containing infinite loops and pygame
# # can only take over one at a time.
# THREAD1=Thread(target=exit_app)
# THREAD1.start()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit() # sys module's exit method seems to be better and causes less errors at exit
# # Without a delay the window will still not close incase this script is being
# # used for that purpose by you.
# pygame.time.delay(3) # Pygame alternative to sleep method of time module https://www.pygame.org/docs/ref/time.html#pygame.time.delay
pygame.display.update()
continue
pass
Related
I'm trying to create a pygame program with a 2d drawable grid and methods that run for a long time. I want the main game loop to be able to process while the methods are running so I opted for the threading module and it worked fine but I found out that the multiprocessing module is better suited for CPU-heavy programs so I wanted to switch. The code below is an example or representative of my actual code
# importing the necessary libraries
class Board:
# class representing the actual 2d grid or board on screen
class Graph:
# class for drawing methods that take a long time to run
# Graph's methods call q.get() to get the Board object then
# make appropriate changes to it then call q.put() to put it back in the Queue
def draw_board(surface, rects):
# surface: pygame.display
# rects: list of pygame rectangle objects
# draw every rectangle in rects to the display surface.
def main():
# main game loop
board = Board(*args)
q = multiprocessing.Queue()
q.put(board)
graph = Graph(q)
while True:
draw_board(*args)
for event in pygame.event.get():
# checking some conditions and keypresses here
elif event.type == KEYDOWN:
if event.key == pygame.K_r:
t = multiprocessing.Process(target=graph.draw_sth)
t.start()
pygame.display.update()
# fps clock ticks for 60 FPS here
if __name__ == "__main__":
main()
I use multiprocessing.Queue to transfer resources from the main process to the processes spawned inside main() and back. When I run this and click the key "r", nothing happens and the terminal prints the introduction line when main is first called, namely
pygame 2.0.1 (SDL 2.0.14, Python 3.9.5)
Hello from the pygame community. https://www.pygame.org/contribute.html
This doesn't happen when I use threading so I assume that this is due to my usage of Queue or I might have misunderstood and misused multiprocessing instead. Any help is appreciated on the matter. For simplicity, some lines of code have been omitted.
Try calling this function before your event loop. ( I don't know if this will apply to multiprocessing )
def continue_pygame_loop():
pygame.mainloop(0.1)
yield
This post can do a better job of explaining the mechanics behind it : pyGame in a thread
As a Python learner I decided to try out PySimpleGUI, and wrote a script in which the relevant snippet is:
`
import PySimpleGUI as sg
....
window = sg.Window('Output Filename Creator').Layout(layout)
while True:
event, values = window.Read()
if event is None or event == "Cancel":
window.Close()
sys.exit()
else:
outfile = values['file']
window.Close()
return outfile `
I use Windows 10, Python 3.7, Idle 3.7, and PySimpleGUI-3.24.0. After running the script that contains the snippet above (no execution errors) I go to the Idle shell and try to type in len('1'). At entering the open bracket the following error is generated:
Traceback (most recent call last):
File "C:\Users\Paul\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 332, in __del__
if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
RuntimeError: main thread is not in main loop
I know that PySimpleGUI is based on tkinter, but that is as far as my knowledge goes. I don't know how threading works in Python or how PySimpleGUI is interfaced with Tk. Yet I would like to know where the error comes from and what I can do to avoid it.
Update: the code reduced to bare essentials still gives the same error when window is closed by clicking on cross in upper right corner:
def OutputFileName(default):
import PySimpleGUI as sg
layout = [
[sg.In(default, key='file', size=(70,1)),
sg.SaveAs('Browse')],
[sg.Save(), sg.Text(' '*35), sg.Cancel()]
]
window = sg.Window(' ').Layout(layout)
event, values = window.Read()
if event is None or event == "Cancel":
return
elif event == 'Save':
return values['file']
outf = OutputFileName('foo.txt')
This problem is due to IDLE being written using tkinter.
tkinter is very picky about resources and threads. There is a warning in the PySimpleGUI documentation about utilizing PySimpleGUI in a threaded environment because you can get into situations like this one where resources are freed in the incorrect thread or tkinter gets confused about who is running the mainloop.
Here is an older post that talks about problems running multiple mainloops when using IDLE.
https://groups.google.com/forum/#!topic/comp.lang.python/kr7lKj4qMl4
The following bit of code throws an error:
while True:
event = pygame.event.wait()
if (event.type == ENDSONG):
queue_song()
This it what it reads:
File "pygametest.py", line 22, in <module>
event = pygame.event.wait()
pygame.error: video system not initialized
Most of the digging I've done says that the error stems from pygame.init() not being run, but that's what starts my code.
Am I missing something else?
Edit: Added Code.
import pygame
import time
def queue_song():
print "Queueing New Song"
pygame.init()
pygame.mixer.init()
#rest of code
Even if you initialize Pygame, you need to call to display.set_mode to avoid this error:
pygame.init()
pygame.display.set_mode((width, height))
# rest of the code
This happens because the event queue needs the video mode to be set to work properly. From the documentation:
The input queue is heavily dependent on the pygame display module. If the display has not been initialized and a video mode not set, the event queue will not really work.
I am an amateur programmer. I have a small (and urgent) problem. I am working on a text (console) based adventure game for fun. At a certain point, I want a pygame window to open. The player has to click in the window as fast as possible. The reaction time should be returned to the main program, and the pygame window should close. The main program will then continue running.
I've already written the script for the pygame window and it works fine. My main program also works fine. Now how do I call the pygame window from the main program?
I tried importing the pygame script but that didn't work.
Thanks.
Here's my pygame script:
import pygame, sys, time
from pygame.locals import *
pygame.init()
#Set up window
pygame.event.set_grab(0)
pygame.mouse.set_visible(1)
screen = pygame.display.set_mode((300,200))
shape = screen.convert_alpha()
pygame.display.set_caption("Sniper Alert")
#Colors
WHITE = (255, 255, 255)
BLACK = (0,0,0)
RED = (255, 0, 0)
#Draw on surface object
screen.fill(BLACK)
def alert():
#Create a font
font = pygame.font.Font(None,50)
#Render the text
text = font.render("Sniper Alert", True, RED)
#Create a rectangle
textRect = text.get_rect()
#Center the rectangle
textRect.centerx = screen.get_rect().centerx
textRect.centery = screen.get_rect().centery
#Blit the text
screen.blit(text, textRect)
pygame.display.update()
return press()
def press():
t0 = time.clock()
dt = 0
while time.clock() - t0 < 1.5:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
dt = time.clock()- t0
return dt
#Exit
pygame.quit()
sys.exit()
#Run the game loop
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
There are three ways I can think of. Here they are:
Solution Number 1:
This way is probably the worst solution and the hardest to implement, but lets get it out of the way. I wouldn't advise using it but you may want to in some circumstances. You could use the threading module. Threading is designed for multitasking like this, and would do what you want. You can create a new thread like so:
import threading
def DoSomething(a,b,c): #this function will be called in the background, simultaneously to the main program
#do something here
apple = 1
banana = 2
cat = 3
mythread = threading.thread(target=DoSomething,args=(apple,banana,cat)) #defines a thread
mythread.start() #tells the thread to start running
Solution Number 2
A much better way to do this would be just to launch it as a different program. You could do that with the subprocess module, used for running command line commands. This would effectively run the program as if you had executed it in a new tab of your terminal (without the new tab). You can then make the program able to communicate with yours using subprocess.comunicate(). I will connect the input and output of the pygame program to your main program from here. Here is an example of this:
import subprocess
input = <insert input> #when you run the program and it requires something like a raw_input, this will give it input. You could probably avoid needing to send the pygame program input, because all you really want to do is receive an output from it.
process = subprocess.Popen(["python","<popup filename>"],stdin=subprocess.PIPE,stdout=subprocess.PIPE,shell=False) #this function is how subprocess runs shell/command prompt commands. On a side note, if you simply want to run a shell command without accessing input or output, just use "subprocess.call(<list of words in command (so ls -a would be ['ls','-a'])>)"
output = process.communicate(input)[0] #this will return any output the program prints, meaning you can communicate with the program and receive information from it
Using subprocess is probably the best way.
Solution Number 3
The final option, if you want it all in one file and do not want to mess around with threading, would be just to alternate the two programs in a while loop. Basically, you would run a loop that executes code from both programs. This is how it would work:
while True: #an infinite loop. it doesn't necessarily have to be infinite.
#call methods or run code to maintain text based game
#call methods or run code to maintain pygame window
this has worked just fine for me in a pygame game i made which also had a text component, but the subprocess way is probably better...
I've made a pathfinding visualizer using python and pygame. As of now, it can simulate only one algorithm at a time. I want to spawn multiple windows, each simulating different algorithm, side by side so that algorithms can be analyzed against each other. I have a function client.run() that draws the GUI. I'm trying to spawn multiple instances like this:
p=threading.Thread(target = client.run)
q=threading.Thread(target = client.run)
p.start()
q.start()
But by doing so my program hangs! Is there any way to rectify this problem, or any alternative way of running multiple instances/windows?
Pygame is built in a way to have a single window by process, you can't avoid that. The pygame.display module sets you a "display" and that is what you get.
You are in good look, as you have designed you software to work with threads, and have each thread control a display.. Just change the "threading" Python module for the multiprocessing, and use multiprocessing.Process instead of threading.Threads -- as long as you initialize pygame and its display from within each subprocess you should be ok.
I just teste here and teh example bellow works fine:
# -*- coding: utf-8 -*-
import pygame
import multiprocessing
from time import sleep
def init():
pygame.init()
screen = pygame.display.set_mode((320,240))
return screen
def main():
screen = init()
while True:
pygame.event.pump()
event = pygame.event.poll()
if event.type != pygame.NOEVENT:
print event
class Multigame(multiprocessing.Process):
def run(self):
return main()
for i in range(3):
Multigame().start()
while True:
sleep(1)