tkinter after() lagging after period of time - python

I was writing code to show time every second and then noticed that eventually after some time(approx 30 seconds) time starts lagging behind the original time. I was able to reproduce the issue, so was someone I know. Hopefully you guys too can reproduce this.
Code:
from tkinter import *
import time
win = Tk()
win.geometry('400x400')
frame=Frame(win)
frame.grid()
labelTD=Label(frame)
labelTD.grid(row=2,column=0)
def countdown(n):
mn, secs =divmod(n, 60)
hr, mn = divmod(mn, 60)
labelCD.config(text=f"{hr:02}:{mn:02}:{secs:02}")
labelCD.config(font='infra 50 bold',foreground='black',background='white')
labelCD.grid(row=0)
if n >= 0:
labelCD.after(1000, countdown, n-1)
else:
labelCD.destroy()
def clock():
t=time.strftime('%A''\n''%D''\n''%I:%M:%S',time.localtime())
if t!='':
labelTD.config(text=t,font='infra 50 bold',foreground='black',background='white')
Hr = time.strftime('%H')
Mn = time.strftime('%M')
Sc = time.strftime('%S')
if int(Hr)==8 and int(Mn)==41 and int(Sc)==0: ### you can trigger it at any time you want
countdown(3600)
labelTD.after(1000,clock)
labelCD = Label(frame)
labelCD.grid()
clock()
win.mainloop()
Could this be due to usage of after() or calling the function every second for a long time? If so, any alternatives to show timers? Thanks in advance :D

The after method doesn't guarantee any sort of precision. The only guarantee it makes is that the function will be called some time after the requested timeout. It could be at 1000 ms, it could be at 1001, 1010, or something else. It depends on what else is going on in the system.
If you need something more accurate, you can save the current time in ms in your function, and do some math from the previous time your function ran, and then use that delta to adjust your calculations.

Related

Can't make the timer decrease more then once

I am trying to create a timer, which right now looks like this:
#Timer 60 sec.
import time
sec = 60
def updateTimer():
trecker.config(text = sec-1)
trecker.after(1000, updateTimer)
trecker = Label(gui, text = sec)
trecker.place(x=500, y=200)
trecker.after(1000, updateTimer)
And the reason I write here is that it displays 60, changes to 59 and then stops. I tried to loop it with for in range, but it doesn't help. My idea right now, is that it calls for updateTimer
function in the inside of its execution restarting itself. But it, for some reason does nothing.
sec-1 is just always 59 because 60-1=59. Instead write:
def updateTimer():
sec -= 1
trecker.config(text = sec)
trecker.after(1000, updateTimer)

Have python.sleep variate with computation time

For an animation project, we want to model a variable number of objects. This means that the computation and rendering part will take variable computation time. But we want the frame rate of the animation to remain constant. So we would like to compute how much time a section of code has taken, and if it is less then the expected frame rate, we wait the remainded before calculation the next frame.
Is there an easy way to do this?
One way to do it is by using the time library that has a method time that allows you to count how much time a section of code has taken to execute, an example down below.
import time
start_time = time.time()
#Your code here
end_time = time.time() - start_time
print(end_time)
You stopwatch the time, and sleep it for 1-x seconds.
sleep_time = 1/1 #Second number is frames per unit of time, first number is unit of time. In seconds.
from time import time #Important function: time(): Secs since unix epoch
while True: #Init animation
init_time = time() #Init stopwatch
#Code here Do your code
timer = time()-init_time #End stopwatch and remember time in a variable.
while time() < init_time+sleep_time-timer: pass #Wait ___ seconds
#Restart and go to next frame.
If the code that you coded in lasts longer than 'sleep time', than it will not wait.
Hope this helps, and good luck with animating!

Using tkinter after to produce an animation

Background Information - I'm attempting to create somewhat of animation for a frame object with TKinter with the following code:
from tkinter import Frame, Tk, Label, Button
import time
def runAnim():
for width in range(0, 200):
app.after(5000, lambda width = width: test_label.config(width=width))
app = Tk()
app.geometry("500x500")
test_label = Frame(bg="#222", width=0)
test_label.pack(side="left", fill="y")
test_button = Button(text="toggle", command=lambda: runAnim() )
test_button.pack(side="right")
The problem is that it this is not producing the desired behaviour. My understanding is that this should gradually increase the width every 5 seconds, however the 0-200 range seems to complete within these 5 seconds, rather than it being an increased width of 1 every 5 seconds.
Any solutions would be appreciated!
That after(5000, …) means 5 seconds after right now, as after is being called, not 5 seconds after some future point in time that tkinter can only guess by reading your mind.
So, you're just creating 200 callbacks, and scheduling them all to run 5 seconds from now. That's obviously not what you want, but it's what you're asking for, so that's what you get.
In general, you can't do loops like this in event-based programming. What you need to do is turn the loop inside-out: each step does one iteration, then schedules the next call for the next one.
The fully-general transformation looks like this:
def runAnim():
iterwidth = iter(range(0, 200))
stepAnim(iterwidth)
def stepAnim(iterwidth):
try:
width = next(iterwidth)
except StopIteration:
return
test_label.config(width=width))
app.after(5000, stepAnim, iterwidth)
While that works for any iterable, when you're just iterating over numbers, it's usually a bit nicer to turn the for loop into an explicit counter, which is easier to invert. (Yes, that's the opposite of the "usual for instead of while and += 1 when you're not inverting things. The difference is that here, we can't access the magic of for or while, and while is a lot less magical, and therefore easier to invert.)
def runAnim():
stepAnim(0, 200):
def stepAnim(width, maxWidth):
test_label.config(width=width))
width += 1
if width < maxWidth:
app.after(5000, stepAnim, width, maxWidth)
However, in this particularly simple case, you might be able to get away with scheduling 200 callbacks, ranging from 5 to 1000 seconds into the future:
def runAnim():
for width in range(0, 200):
app.after(5000 * width, lambda width = width: test_label.config(width=width))
This might cause the timer to drift a lot more badly, or it might even choke up the scheduler and add lag to your program, but it's at least worth trying.
Speaking of drift:
Back at the start, I mentioned that after(5000, …) means 5 seconds after right now.
An after can fire a bit late. As the docs say: "Tkinter only guarantees that the callback will not be called earlier than that; if the system is busy, the actual delay may be much longer."
So, what happens if it fires after, say, 5.2 seconds? Then the second tick happens 5 seconds after that, at 10.2 seconds, not at 10 seconds. And if they're all firing a bit late, that adds up, so by the end, we could be 20 seconds behind.
Worse, what if after fires exactly at 5.0 seconds, but the Label.config takes 0.2 seconds to run? Then we're absolutely guaranteed to be 20 seconds behind. (Plus any additional error from after itself.)
If this matters, you need to keep track of the desired "next time", and wait until then, not until 5 seconds from whenever it is now. For example:
import datetime as dt
def runAnim():
stepAnim(0, 200, dt.datetime.now() + dt.timedelta(seconds=5):
def stepAnim(width, maxWidth, nextTick):
test_label.config(width=width))
width += 1
if width < maxWidth:
now = dt.datetime.now()
delay = (nextTick - now).total_seconds() * 1000
nextTick += dt.timedelta(seconds=5)
app.after(delay, stepAnim, width, maxWidth, nextTick)

How many Windows wall clock seconds are there in an actual second?

This code:
import random,time
raw_input()
while True:
try:
TIME += 0
except:
TIME = time.clock()
x = random.random()
if x > 0.999999:
print x
print time.clock()-TIME
break
Provided this output:
0.999999910337
63.5525445557
However, I tested on a stopwatch and I seemed to get a second and 3 quarters on the stopwatch. I wasn't going for an accurate reading, I was simply trying to figure out how many Windows wall clock seconds there are to every second.
2nd Try
0.999999028728
0.93168230208
Took a second and a quarter
I kept getting weird answers. Anyway, does anybody know? Or is it a rate?

What's the proper way to write a game loop in Python?

I'm trying to write a python game loop that hopefully takes into account FPS. What is the correct way to call the loop? Some of the possibilities I've considered are below. I'm trying not to use a library like pygame.
1.
while True:
mainLoop()
2.
def mainLoop():
# run some game code
time.sleep(Interval)
mainLoop()
3.
def mainLoop():
# run some game code
threading.timer(Interval, mainLoop).start()
4.
Use sched.scheduler?
If I understood correctly you want to base your game logic on a time delta.
Try getting a time delta between every frame and then have your objects move with respect to that time delta.
import time
while True:
# dt is the time delta in seconds (float).
currentTime = time.time()
dt = currentTime - lastFrameTime
lastFrameTime = currentTime
game_logic(dt)
def game_logic(dt):
# Where speed might be a vector. E.g speed.x = 1 means
# you will move by 1 unit per second on x's direction.
plane.position += speed * dt;
If you also want to limit your frames per second, an easy way would be sleeping the appropriate amount of time after every update.
FPS = 60
while True:
sleepTime = 1./FPS - (currentTime - lastFrameTime)
if sleepTime > 0:
time.sleep(sleepTime)
Be aware thought that this will only work if your hardware is more than fast enough for your game. For more information about game loops check this.
PS) Sorry for the Javaish variable names... Just took a break from some Java coding.

Categories

Resources