I have this pygame code that goes a little like this (obviously not my actual code since its a little long):
def draw():
win.fill(color)
<code for drawing stuff>
pygame.display.update()
def stuff():
while doingStuff:
<some lines of code>
draw()
win = pygame.display.set_mode((width,height))
while True:
<some lines of code>
if conditionMet:
stuff()
draw()
The problem is that when the "stuff" function runs, the pygame window freezes and doesnt update until it leaves the function. The draw function is correct I think, as it runs fine as long as it isn't in the function.
How do I make the pygame window keep updating whilst the function runs?
You may have to use threading or multiprocessing, because it is essential that you understand why your main render function is 'freezing'.
This means while your main method always renders everything so fast in a loop, that you don't see how long the draw() methods needs to calculate the new scene, it becomes obvious how long the stuff() or condition method needs to evaluate a result. Your main loop for drawing freezes because the calculation of the inner while loop from stuff() takes so long to complete. This is my assumption.
This happens because you program sequential. To solve this you need to put the stuff() function into its own program space - or better process space which is usually done with threads, which are subprocesses of your main process.
On the other hand you could try to speed up your stuff() method.
Related
I am having a hard time understanding the window.timeout() function. To be more specific, I am toying with a "snake" game in python:
s = curses.initscr()
curses.curs_set(0)
w = curses.newwin()
w.timeout(100)
while True:
move snake until it hits the wall
I understand that in this case, timeout(100) determines how fast the snake "moves", i.e. printing out new characters on the screen. However, I got stuck when I want to amend the code so that it waits until someone press "start". I wrote something like:
w.timeout(100)
while True:
if w.getch() is not start:
stay at the initial screen
else:
while True:
move the snake until it hits the wall
However, in this case, the timeout(100) seems to govern how long each time the program waits for w.getch(), not how long to wait between each time the snake moves. Also, I notice that in the first example, the timeout is declared at the top, outside the while loop. This looks weird to me because normally if I want to pause a while loop, I would put sleep() at the bottom inside the while loop.
If you want to pause between snake moves, you could use napms to wait a given number of milliseconds (and unlike sleep, does not interfere with screen updates). Setting w.timeout to 100 (milliseconds) is probably too long. If you're not concerned with reading function-keys, you could use nodelay to set the w.getch to non-blocking, relying on the napms to slow down the loop.
Regarding the followup comment: in ncurses, the wtimeout function sets a property of the window named _delay, which acts within the getch function, ultimately passed to a timed-wait function that would return early if there's data to be read.
How come when I run my code, it will sleep for 3 seconds first, then execute the 'label' .lift() and change the text? This is just one function of many in the program. I want the label to read "Starting in 3...2...1..." and the numbers changing when a second has passed.
def predraw(self):
self.lost=False
self.lossmessage.lower()
self.countdown.lift()
self.dx=20
self.dy=0
self.delay=200
self.x=300
self.y=300
self.foodx=self.list[random.randint(0,29)]
self.foody=self.list[random.randint(0,29)]
self.fillcol='blue'
self.canvas['bg']='white'
self.lossmessage['text']='You lost! :('
self.score['text']=0
self.countdown['text']='Starting in...3'
time.sleep(1)
self.countdown['text']='Starting in...2'
time.sleep(1)
self.countdown['text']='Starting in...1'
time.sleep(1)
self.countdown.lower()
self.drawsnake()
It does this because changes in widgets only become visible when the UI enters the event loop. You aren't allowing the screen to update after calling sleep each time, so it appears that it's sleeping three seconds before changing anything.
A simple fix is to call self.update() immediately before calling time.sleep(1), though the better solution is to not call sleep at all. You could do something like this, for example:
self.after(1000, lambda: self.countdown.configure(text="Starting in...3"))
self.after(2000, lambda: self.countdown.configure(text="Starting in...2"))
self.after(3000, lambda: self.countdown.configure(text="Starting in...1"))
self.after(4000, self.drawsnake)
By using after in this manner, your GUI remains responsive during the wait time and you don't have to sprinkle in calls to update.
I've searched for a simple animation code with Tkinter but I've found very different examples and I can't understand the correct way to write an animation.
Here my working code to display a simple moving circle:
import tkinter as tk
import time
root=tk.Tk()
canvas=tk.Canvas(root,width=400,height=400)
canvas.pack()
circle=canvas.create_oval(50,50,80,80,outline="white",fill="blue")
def redraw():
canvas.after(100,redraw)
canvas.move(circle,5,5)
canvas.update()
canvas.after(100,redraw)
root.mainloop()
In this code I can't correctly understand: how the after method works, where correctly put the update and the move method (before after method ?), is there another way to write an animation code? may you post me another example and comment the code please?
Thanks :)
Calling update
You should not call canvas.update(). As a general rule of thumb you should never call update. For a short essay on why, see this essay written by one of the original developers of the underlying tcl interpreter.
If you take out the call to canvas.update(), you have the proper way to do animation in a tkinter program.
Calling after to start the animation
You don't need to call after immediately before calling root.mainloop(). This works just as well:
...
redraw()
root.mainloop()
The choice to use or not use after in this specific case is dependent on if you want the animation to start immediately (possibly even before the widget is visible) or if you want it to happen after a short delay (possibly after the widget is made visible)
How after works
mainloop is nothing more than an infinite loop that checks the event queue for events. When it finds an event, it pops it off of the list and processes it. after is nothing more than making a request that says "in 100 ms, please add a new event to the queue". When the time limit expires, an event is added to the queue that says, in effect, "run this command". The next time the loop checks for events, it sees this event, pulls it off of the queue, and runs the command.
When you call after from within a method that itself was called by after, you're saying in effect "wait 100ms and do it again", creating an infinite loop. If you put the call to after before moving the object, you're saying "every 100ms run this function". If you put it after you're saying "run this function 100 ms after the last time it was run". The difference is very subtle and usually not perceptible unless your function takes a long time to run.
my code is:
from tkinter import *
import time
tk = Tk()
płótno = Canvas(tk, width=500, height=500)
płótno.pack()
płótno.create_polygon(10,10,10,70,70,10,fill="blue",outline="black")
for x in range(0,51):
płótno.move(1,5,0)
płótno.update()
rest(0.05)
płótno means canvas
I wrote a modified program of the 'mines' game, and I hope it shows every step/click graphically. I use time.sleep(0.5) to make a pause. So, in general the main program is like:
check_block():
if mine == 0:
buttons[current].config(image = tile_clicked)
elif mine == 1:
buttons[current].config(image = tile[1])
...
while(1):
time.sleep(0.5)
check_block()
get_next()
if check_fail():
break
However, the buttons don't update every 0.5 second: they are all updated together when the game(loop) finishes.
I guess it's just like 'cout' in C++: if you don't flush they will get stacked. So, is there a method to get them updated step by step, or say, instantly?
Thanks!
In all GUI systems you have to allow the message loop to run so that Windowing events occur promptly. So do not use a while loop like this. Instead, create a method that calls check_block() and get_next() and use after to call that function after a delay. At the end of that function, you use after again to call the same function again so that this function is called every 0.5 second forever. The after function queues a timer event and then lets the message queue be processed. Once your timer event fires, the callback function is run which allows you to do things and keep the UI responsive.
You should never call sleep in a GUI program. This is because the GUI must be "awake" at all times so that it can service events (including internal events that cause the screen to update). Instead, leverage the already-running eventloop by using the after method to put events on the queue at regular intervals.
In your case, you would replace the while loop with something like:
def do_check():
check_block()
if not check_fail():
root.after(500, do_check)
# in your initialization code, start the loop by calling it directly:
do_check()
I don't know what your get_next function does, so I don't know if you need to call it periodically too. Probably not. I'm guessing it waits for the next button press, which you don't need to do with tkinter or most other GUI toolkits. Instead, you configure the button to call a function when clicked.
Regardless, the way to do the type of looping you want is to place events on the event queue at a regular interval.
For my class, I am creating a "Mandelbrot Explorer" program. There is one main issue: I lose control of the GUI (all written in Tkinter/Ttk, in Python 2.7) when actually drawing to the Canvas.
Here is my code:
# There is some code above and below, but only this is relevant
for real, imag in graph.PlaneIteration(self.graph.xMin, self.graph.xMax, resolution, self.graph.yMin, self.graph.yMax, resolution, master = self.graph, buffer_action = self.graph.flush):
# the above line iterates on the complex plane, updating the Canvas for every x value
c = complex(real, imag)
function, draw, z, current_iter = lambda z: z**2 + c, True, 0, 1
while current_iter <= iterations:
z = function(z)
if abs(z) > limit:
draw = False
break
current_iter += 1
self.progressbar.setValue(100 * (real + self.graph.xMax) / total)
color = self.scheme(c, current_iter, iterations, draw)
# returns a hex color value
self.graph.plot(c, color)
# self.graph is an instance of my custom class (ComplexGraph) which is a wrapper
# around the Canvas widget
# self.graph.plot just creates a line on the Canvas:
# self.create_line(xs,ys,xs+1,ys+1, fill=color)
My issue is that when run, the graphing takes a while - about 30 seconds. In this time, I cannot use the GUI. If I try to, the window freezes and only unfreezes once the drawing is done.
I tried using threading (I enclosed the entirety of the upper code in a function, thread_process):
thread.start_new_thread(thread_process, ())
However, the problem remains.
Is there a way to fix this? Thanks!
You can execute your loop "threaded" with Tkinter by implicitly returning to Tkinter's main loop execution after every point your draw. Do this by using widget.after to register the next function call:
plane = graph.PlaneIteration(...)
def plotNextPoint():
try:
real, imag = plane.next()
except StopIteration:
return
c = complex(real, imag)
...
self.graph.plot(c, color)
self.graph.after(0, plotNextPoint)
plotNextPoint()
This way, after each point you draw, the Tkinter mainloop will run again and update the display before calling your plotNextPoint function again. If this is too slow, try wrapping the body of plotNextPoint in a for _ in xrange(n) loop to draw n points between redraws.
You're right about the cause of the problem—the GUI event loop is not running while you're busy running this code.
And you're right about threading being a good solution. (The other major solution is to break the job up into smaller subtasks and have each one schedule the next. For a more detailed overview of the options and all of the wrinkles, see Why your GUI app freezes.)
But it's not quite as simple as putting the whole thing on a thread.
Unfortunately, Tkinter (like many GUI frameworks) is not free-threaded. You cannot call methods on any GUI objects from a background thread. If you do, different things happen on different platforms and versions, ranging from blocking the main thread to crashing the program to raising exceptions.
Also, remember that, even without Tkinter, you can't safely share mutable objects between threads without some kind of synchronization. And you're doing exactly that with the Tkinter objects, right?
The Tkinter wiki explains one way to get around both of these problems at once in Tkinter and Threads: Create a Queue, have the background thread put messages on it, and have the main thread check it every so often (e.g., by using after to schedule a nonblocking get every 100ms until the background thread is done).
If you don't want to come up with a "protocol" for passing data from the background thread to the main thread, remember that in Python, a bound method, or a tuple of a bound method and some arguments, it perfectly good, passable data. So, instead of calling self.graph.plot(c, color), you can just self.q.put((self.graph.plot, c, color)).
The library mtTkinter wraps this all up for you, making it look like Tkinter is free-threaded by using a Queue in the background. It isn't highly tested or frequently maintained, but even if it doesn't work in the future it still makes great sample code.