0
\$\begingroup\$

I recently wrote a program where I needed to limit the fps. After some searching I found a way via pygame built-in methods pygame.time.Clock() and clock.tick(fps) but an even better, more accurate way through the time module. But now comes the tricky part and that is delta time and its use in this example. As far as I know it is used to measure the time between frames to be able to calculate the movement of an object in real time. So my question is, how does dt help me limit fps and what is the meaning of the sleep_time variable in this example?

I will be grateful for any explanation and attempt to help with this topic

A quick note: I'm also quite new to python and I'm still trying to understand it, but not programming in general and I already grasp the basics. What I don't understand is the algorithm behind the scenes. So that's basically all I need help with. Thank you.

# Modules ---------------------------------------- #

import pygame as pg, time

# Initialization --------------------------------- #

pg.init()
pg.display.set_mode((720, 400))

# Variables -------------------------------------- #

prev_time = time.time()
FPS = 60
running = True

# Main-loop -------------------------------------- #
while running:
    current_time = time.time()
    dt = current_time - prev_time 
    prev_time = current_time
    sleep_time = 1./FPS - dt
    if sleep_time > 0:
        time.sleep(sleep_time)
    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False
\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

what is the meaning of the sleep_time variable in this example?

The time required to process a single frame will be calculated for each frame. If it takes less than 1/60th of a second (i.e. sleep_time will be bigger than 0), put the process on hold for the remained of the 1/60th of a second, so that this frame actually takes 1/60th of a second.

time.sleep(sleep_time) suspends the thread for at least the duration of sleep_time (it may be longer).

how does dt help me limit fps

So in this example, the dt will be used to make sure you have a fixed/constant time step of FPS.

Sort of.

This way of limiting the framerate is not very good. I've modified the example in the question a bit with "printouts":

import time

prev_time = time.time()
FPS = 60
STEP_TIME = 1./FPS
running = True

count = 0
accum = 0

while running:
    current_time = time.time()
    dt = current_time - prev_time
    accum += dt
    count += 1
    if accum >= 1.0:
        accum -= 1.0
        print(count)
        count = 0
      
    prev_time = current_time
    sleep_time = STEP_TIME - dt
    if sleep_time > 0:
        time.sleep(sleep_time)

With the results:

% py main.py
64
64
68
62
62
62

We don't get 60hz. For two reasons:

First, as mentioned, time.sleep(sleep_time) will guarantee that the thread sleeps for at least this amount of time. That is how the OS works. So you can't control precisely how long your program will be sleeping.

Second, let's take a closer look at dt.

dt = prev_time - current_time

so the dt is the time spent during the previous frame. That's fine and all, and it would likely work if we didn't want to throttle the framerate. The problem comes when we want to sleep. When we call time.sleep(sleep_time), we're actually calling time.sleep(1./FPS - (prev_time - current_time)). We're actually fixing last frames time. This has a nasty effect of having some frames where we'll sleep, and other frames where we'll not sleep, resulting inconsistent frame times, at least from the user perspective; adding print(dt, sleep_time) at the end of the previous code snippet, we get something like:

% py main.py
0.0 0.016666666666666666
0.03285384178161621 -0.016187175114949545
0.0 0.016666666666666666
0.033695220947265625 -0.01702855428059896
0.0 0.016666666666666666
0.03452634811401367 -0.017859681447347005
0.0 0.016666666666666666
0.03141927719116211 -0.014752610524495443
0.0002942085266113281 0.01637245814005534
0.032390594482421875 -0.01572392781575521
0.0 0.016666666666666666
0.031510353088378906 -0.01484368642171224
0.0 0.016666666666666666
0.019999265670776367 -0.0033325990041097008
0.0005021095275878906 0.016164557139078776
0.03202223777770996 -0.015355571111043295
0.0006344318389892578 0.01603223482767741
0.03662467002868652 -0.019958003362019857
0.0 0.016666666666666666

To control the frame rate, I generally prefer the "busy loop" approach, which offers a more fine grained control w.r.t. using any sleep methods.

import time

prev_time = time.perf_counter()
current_time = prev_time
FPS = 60
STEP_TIME = 1./FPS
running = True

count = 0
accum = 0

while running:
    prev_time = current_time
    current_time = time.perf_counter()
    dt = current_time - prev_time

    accum += dt
    count += 1
    if accum >= 1.0:
        accum -= 1.0
        print(count)
        count = 0
    # print(dt)

    # busy loop, where we just wait this frame's time to pass
    while time.perf_counter() < (current_time + STEP_TIME):
        pass

Please note that instead of time.time(), I use time.perf_counter() which is much more precise for what we need to do.

With this method, I get this for the actual frame rate:

% py main.py
61
60
60
60
60
60
60
60

and this for individual frame time:

% py main.py
2.100015990436077e-06
0.016670000040903687
0.016669799922965467
0.016668600030243397
0.016668600030243397
0.016668800031766295
0.016668399912305176
0.01667120005004108
0.016669100034050643
0.016668599913828075
0.016668800031766295
0.016668800031766295
0.016668600030243397
0.016668399912305176
0.016669300035573542
0.0166680000256747
0.016668400028720498
0.016668399912305176
0.0166680000256747

Although dt is still about last frame's time, we're still controlling how this frame's time is spent.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .