Jørgen Jørgen - 5 months ago 17
Python Question

Control the speed of a loop

I'm currently reading physics in the university, and im learning python as a little hobby.

To practise both at the same time, i figured I'll write a little "physics engine" that calculates the movement of an object based on x,y and z coordinates. Im only gonna return the movement in text (at least for now!) but i want the position updates to be real-time.

To do that i need to update the position of an object, lets say a hundred times a second, and print it back to the screen. So every 10 ms the program prints the current position.

So if the execution of the calculations take 2 ms, then the loop must wait 8ms before it prints and recalculate for the next position.

Whats the best way of constructing a loop like that, and is 100 times a second a fair frequency or would you go slower, like 25 times/sec?

Answer

The basic way to wait in python is to import time and use time.sleep. Then the question is, how long to sleep? This depends on how you want to handle cases where your loop misses the desired timing. The following implementation tries to catch up to the target interval if it misses.

import time
import random

def doTimeConsumingStep(N):
    """
    This represents the computational part of your simulation.

    For the sake of illustration, I've set it up so that it takes a random
    amount of time which is occasionally longer than the interval you want.
    """
    r = random.random()
    computationTime = N * (r + 0.2)
    print("...computing for %f seconds..."%(computationTime,))
    time.sleep(computationTime)


def timerTest(N=1):
    repsCompleted = 0
    beginningOfTime = time.clock()

    start = time.clock()
    goAgainAt = start + N
    while 1:
        print("Loop #%d at time %f"%(repsCompleted, time.clock() - beginningOfTime))
        repsCompleted += 1
        doTimeConsumingStep(N)
        #If we missed our interval, iterate immediately and increment the target time
        if time.clock() > goAgainAt:
            print("Oops, missed an iteration")
            goAgainAt += N
            continue
        #Otherwise, wait for next interval
        timeToSleep = goAgainAt - time.clock()
        goAgainAt += N
        time.sleep(timeToSleep)

if __name__ == "__main__":
    timerTest()

Note that you will miss your desired timing on a normal OS, so things like this are necessary. Note that even with asynchronous frameworks like tulip and twisted you can't guarantee timing on a normal operating system.