简体   繁体   中英

Kivy events are scheduled too early

Python 3.7 Kivy 1.11.1 OSX 10.14.6

Im am writing a Kivy app with 25 fps that should have a fast event loop with 10ms. The problem is that the loop is called much faster - almost instantly. Is there a way to fix this? Here is my test code:

from kivy.config import Config

Config.read("config.ini")
Config.set('graphics', 'maxfps', '25')
Config.set('kivy', 'kivy_clock', 'free_all')
Config.write()

from kivy.uix.relativelayout import RelativeLayout
from kivy.app import App
from time import time
from kivy.clock import Clock
from collections import deque
from numpy import median

class Screen(RelativeLayout):

    durations_ms = deque(maxlen=100)
    last_event_loop_time = time()
    counter = 1

    @staticmethod
    def event_loop(dt):
        duration_ms = (time() - Screen.last_event_loop_time) * 1000
        Screen.durations_ms.append(duration_ms)
        Screen.last_event_loop_time = time()
        if Screen.counter == 0:
            print("Interval time (ms) median: {0:.3f}, min: {1:.3f}, max: {2:.3f}"
                  .format(median(Screen.durations_ms), min(Screen.durations_ms), max(Screen.durations_ms)))
        Screen.counter = (Screen.counter + 1) % 100

    def __init__(self, **kwargs):
        super(Screen, self).__init__(**kwargs)
        Clock.schedule_interval_free(self.event_loop, 0.010)


class TestApp(App):

    def build(self):
        return Screen()

if __name__ == "__main__":
    TestApp().run()

The output is:

Interval time (ms) median: 0.035, min: 0.030, max: 1.431
Interval time (ms) median: 0.061, min: 0.030, max: 0.408
Interval time (ms) median: 0.037, min: 0.031, max: 0.551

Your problem piqued my interest. I suggest using a different approach to scheduling your event_loop using the Python sched module. Here is a modified version of your code that does that:

from kivy.config import Config

Config.read("config.ini")
Config.set('graphics', 'maxfps', '25')
Config.set('kivy', 'kivy_clock', 'free_all')
Config.write()

from kivy.uix.relativelayout import RelativeLayout
from kivy.app import App
import sched
from time import time, sleep
from collections import deque
from numpy import median

class Screen(RelativeLayout):

    durations_ms = deque(maxlen=100)
    last_event_loop_time = time()
    counter = 1
    s = sched.scheduler(time, sleep)  # create the scheduler

    def event_loop(self):
        duration_ms = (time() - Screen.last_event_loop_time) * 1000
        Screen.last_event_loop_time = time()
        Screen.durations_ms.append(duration_ms)
        if Screen.counter == 0:
            print("Interval time (ms) median: {0:.3f}, min: {1:.3f}, max: {2:.3f}"
                  .format(median(Screen.durations_ms), min(Screen.durations_ms), max(Screen.durations_ms)))
        Screen.counter = (Screen.counter + 1) % 100

        # enter the next call to event_loop into the scheduler
        Screen.s.enter(0.01, 1, self.event_loop)

    def __init__(self, **kwargs):
        super(Screen, self).__init__(**kwargs)

        # enter the first call to event_loop and start the scheduler
        Screen.s.enter(0.01, 1, self.event_loop)
        Screen.s.run()


class TestApp(App):

    def build(self):
        return Screen()

if __name__ == "__main__":
    TestApp().run()

Note that this runs your event_loop on the main thread. If your event_loop is doing GUI updates, this is good, otherwise it is very bad.

If your GUI is suffering as a result of this running on the main thread, you can run it on another thread using this version:

from kivy.config import Config

Config.read("config.ini")
Config.set('graphics', 'maxfps', '25')
Config.set('kivy', 'kivy_clock', 'free_all')
Config.write()

from kivy.uix.relativelayout import RelativeLayout
from kivy.app import App
import sched
from time import time, sleep
from collections import deque
from numpy import median
import threading


class Screen(RelativeLayout):

    durations_ms = deque(maxlen=100)
    last_event_loop_time = time()
    counter = 1

    def run_event_loop(self):
        self.s = sched.scheduler(time, sleep)
        self.s.enter(0.01, 1, self.event_loop)
        self.s.run()

    def event_loop(self):
        duration_ms = (time() - Screen.last_event_loop_time) * 1000
        Screen.last_event_loop_time = time()
        Screen.durations_ms.append(duration_ms)
        if Screen.counter == 0:
            print("Interval time (ms) median: {0:.3f}, min: {1:.3f}, max: {2:.3f}"
                  .format(median(Screen.durations_ms), min(Screen.durations_ms), max(Screen.durations_ms)))
        Screen.counter = (Screen.counter + 1) % 100
        self.s.enter(0.01, 1, self.event_loop)

    def __init__(self, **kwargs):
        super(Screen, self).__init__(**kwargs)
        threading.Thread(target=self.run_event_loop, daemon=True).start()


class TestApp(App):

    def build(self):
        return Screen()

if __name__ == "__main__":
    TestApp().run()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM