Search code examples
pythontimepyopengl

time.sleep sleeping too much on python


I currently need a function that runs 128 times per second in python. Here is my code

import time, threading

run_counter = 0

def runner():
    global run_counter

    while True:
        do_something()
        run_counter += 1
        time.sleep(1/128)

threading.Thread(target=runner).start()
    

Now, I have something to keep track of how many times do_something has been executed every second:

while True:
    print(run_counter)
    run_counter = 0
    time.sleep(1)

Now here is the issue. time.sleep(1/128) should sleep 0.0078125 seconds and it is supposed to run 128 times every second. However, every time print(run_counter), it indicates it only has been run 64 times every second.

Additionally, I have a OpenGL context running on the background. I'm using PyOpenGlTk for this:

class WindowGLTkCanvas(OpenGLFrame):

    def __init__(self, parent, root, size: Dimension = Dimension(100, 100), scale=1):

        OpenGLFrame.__init__(self, root, width=size.x, height=size.y)
        PynamicsObject.__init__(self, parent)
        self.parent = parent
        self.renderable = []
        self.scale = scale

        self.texture_handler = {}

        self.width = size.x
        self.height = size.y

        self.frame = Routine(self, target=self.redraw)


    def initgl(self):
        Logger.info("Call: initgl")
        glViewport(0, 0, self.width, self.height)
        glClearColor(0, 0, 0, 0)

        # setup projection matrix
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(0, self.width, self.height, 0, -1, 1)

        # setup identity model view matrix
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        glEnable(GL_TEXTURE_2D)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glEnable(GL_BLEND)
        #glEnable(GL_LINE_SMOOTH)

    def redraw(self):

        Logger.info("Call: draw")

        glClear(GL_COLOR_BUFFER_BIT)
        glFlush()

        #Logger.info("Call: drawend")

I also have a wrapper to pack this tkinter item on a tkinter root:

class WindowGLTk():

    def __init__(self, parent):
        super().__init__(parent)

        self.root = tk.Tk()
        self.gl_canvas = WindowGLTkCanvas(self, self.root, Dim(500, 500))
        self.gl_canvas.pack(fill=tk.BOTH, expand=tk.YES)
        self.gl_canvas.animate = True

        self.parent._viewport = self

        #self.frame = Routine(self, self._tick, delay=tps_to_seconds(200))

    def _tick(self):
        print("RENDER")
        #self.gl_canvas.redraw()

    def load(self):
        self.root.mainloop()

Finally, another handler calls the load function of this wrapper:

    if self._viewport is None:
        Logger.warn("No window created for this context. Skipping window startup.")
    else:
        #self._viewport.frame.start()
        self._viewport.load()

I have tried implementing a loop-based time sleep that checks the system time continously:

def sleep(seconds: float):
    a = time.time()
    b = a + seconds
    while time.time() < b:
        pass

    return

This code is fine and works perfectly. However, it is lagging the redraw() function of a OpenGLFrame (from PyOpenGlTk), since I debugged the redraw function and found out it is running lesser than the expected value of 144 times per second (144 FPS)

Are there any alternatives for sleeping in python?

EDIT: I solved this by time.sleeping for 1 second every loop, and used threading.Timer inside the loop to evenly space out each run for 1/128 seconds. Might be a dumb approach but it works.

def _loop(self):
    while True:
        for i in range(128):
            threading.Timer(i / 128, self.target).start()
        time.sleep(1)

Solution

  • Are there any alternatives for sleeping in python?

    I suggest taking look at threading.Timer where you specify after what time it is supposed to run function, simple example

    import time
    import threading
    t0 = time.time()
    def func():
        print("Started at %.03f" % (time.time()-t0))
        time.sleep(25)  # simulate time-consumnig do_something
    for i in range(10): # start func 10 times...
        threading.Timer(i/10, func).start()  # ...in 1/10 sec intervals
    

    possible output

    Started at 0.000
    Started at 0.101
    Started at 0.201
    Started at 0.301
    Started at 0.401
    Started at 0.501
    Started at 0.601
    Started at 0.701
    Started at 0.802
    Started at 0.902
    

    (tested in Python 3.10.12)