This commit is contained in:
Zen-CODE 2016-07-27 03:56:07 +02:00
commit 24da014678
4 changed files with 601 additions and 45 deletions

View File

@ -29,6 +29,13 @@ cdef class ClockEvent(object):
cpdef tick(self, double curtime)
cdef class FreeClockEvent(ClockEvent):
cdef public int free
'''Whether this event was scheduled as a free event.
'''
cdef class CyClockBase(object):
cdef public double _last_tick
@ -37,6 +44,16 @@ cdef class CyClockBase(object):
frame. If more iterations occur, a warning is issued.
'''
cdef public double clock_resolution
'''If the remaining time until the event timeout is less than :attr:`clock_resolution`,
the clock will execute the callback even if it hasn't exactly timed out.
If -1, the default, the resolution will be computed from config's ``maxfps``.
Otherwise, the provided value is used. Defaults to -1.
'''
cdef public double _max_fps
cdef public ClockEvent _root_event
'''The first event in the chain. Can be None.
'''
@ -65,6 +82,7 @@ cdef class CyClockBase(object):
cdef public object _lock_acquire
cdef public object _lock_release
cpdef get_resolution(self)
cpdef create_trigger(self, callback, timeout=*, interval=*)
cpdef schedule_once(self, callback, timeout=*)
cpdef schedule_interval(self, callback, timeout)
@ -72,3 +90,14 @@ cdef class CyClockBase(object):
cpdef _release_references(self)
cpdef _process_events(self)
cpdef _process_events_before_frame(self)
cpdef get_min_timeout(self)
cpdef get_events(self)
cdef class CyClockBaseFree(CyClockBase):
cpdef create_trigger_free(self, callback, timeout=*, interval=*)
cpdef schedule_once_free(self, callback, timeout=*)
cpdef schedule_interval_free(self, callback, timeout)
cpdef _process_free_events(self, double last_tick)
cpdef get_min_free_timeout(self)

View File

@ -1,6 +1,10 @@
__all__ = ('CyClockBase', 'ClockEvent')
__all__ = ('ClockEvent', 'CyClockBase', 'FreeClockEvent', 'CyClockBaseFree')
cdef extern from "float.h":
double DBL_MAX
from kivy.weakmethod import WeakMethod
from kivy.logger import Logger
@ -47,6 +51,7 @@ cdef class ClockEvent(object):
clock._last_event.next = self
self.prev = clock._last_event
clock._last_event = self
self.clock.on_schedule(self)
clock._lock_release()
def __call__(self, *largs):
@ -65,6 +70,7 @@ cdef class ClockEvent(object):
self.clock._last_event.next = self
self.prev = self.clock._last_event
self.clock._last_event = self
self.clock.on_schedule(self)
self.clock._lock_release()
return True
self.clock._lock_release()
@ -102,7 +108,7 @@ cdef class ClockEvent(object):
# cap is next, so we have reached the end of the list
# because current one being processed is going to be the last event now
if self.clock._cap_event is self.clock._next_event:
self.clock._cap_event = None
self.clock._cap_event = None
else:
self.clock._cap_event = self.prev # new cap
@ -137,10 +143,8 @@ cdef class ClockEvent(object):
'''(internal method) Processes the event for the kivy thread.
'''
cdef object callback, ret
# timeout happened ? (check also if we would miss from 5ms) this
# 5ms increase the accuracy if the timing of animation for
# example.
if curtime - self._last_dt < self.timeout - 0.005:
# timeout happened ? if less than resolution process it
if curtime - self._last_dt < self.timeout - self.clock.get_resolution():
return True
# calculate current timediff for this event
@ -170,13 +174,29 @@ cdef class ClockEvent(object):
return self.loop
def __repr__(self):
return '<ClockEvent callback=%r>' % self.get_callback()
return '<ClockEvent ({}) callback={}>'.format(self.timeout, self.get_callback())
cdef class FreeClockEvent(ClockEvent):
'''The event returned by the ``Clock.XXX_free`` methods of
:class:`CyClockBaseFree`. It stores whether the event was scheduled as a
free event.
'''
def __init__(self, free, *largs, **kwargs):
self.free = free
ClockEvent.__init__(self, *largs, **kwargs)
cdef class CyClockBase(object):
'''The base clock object with event support.
'''
def __cinit__(self, **kwargs):
self.clock_resolution = -1
self._max_fps = 60
self.max_iteration = 10
def __init__(self, **kwargs):
super(CyClockBase, self).__init__(**kwargs)
self._root_event = None
@ -187,7 +207,26 @@ cdef class CyClockBase(object):
self._lock_acquire = self._lock.acquire
self._lock_release = self._lock.release
self.max_iteration = 10
cpdef get_resolution(self):
'''Returns the minimum resolution the clock has. It's a function of
:attr:`clock_resolution` and ``maxfps`` provided at the config.
'''
cdef double resolution = self.clock_resolution
# timeout happened ? (check also if we would miss from 5ms) this
# 5ms increase the accuracy if the timing of animation for
# example.
if resolution < 0:
if self._max_fps:
resolution = 1 / (3. * self._max_fps)
else:
resolution = 0.0001
return resolution
def on_schedule(self, event):
'''Function that is called internally every time an event is triggered
for this clock. It takes the event as a parameter.
'''
pass
cpdef create_trigger(self, callback, timeout=0, interval=False):
'''Create a Trigger event. Check module documentation for more
@ -337,10 +376,10 @@ cdef class CyClockBase(object):
self._cap_event = self._last_event
event = self._root_event
while not done:
while not done and event is not None:
self._next_event = event.next
done = self._cap_event is event or self._cap_event is None
'''We have to worry about this case:
'''Usage of _cap_event: We have to worry about this case:
If in this iteration the cap event is canceled then at end of this
iteration _cap_event will have shifted to current event (or to the
@ -361,7 +400,7 @@ cdef class CyClockBase(object):
self._lock_acquire()
event = self._next_event
self._cap_event = None
self._next_event = self._cap_event = None
self._lock_release()
cpdef _process_events_before_frame(self):
@ -390,7 +429,7 @@ cdef class CyClockBase(object):
self._cap_event = self._last_event
event = self._root_event
while not done:
while not done and event is not None:
if event.timeout != -1:
done = self._cap_event is event or self._cap_event is None
event = event.next
@ -408,11 +447,29 @@ cdef class CyClockBase(object):
else:
self._lock_acquire()
event = self._next_event
self._cap_event = None
self._next_event = self._cap_event = None
self._lock_release()
def get_events(self):
'''Returns the list of :class:`ClockEvent` currently scheduled.
cpdef get_min_timeout(self):
'''Returns the remaining time since the start of the current frame
for the event with the smallest timeout.
'''
cdef ClockEvent ev
cdef double val = DBL_MAX
self._lock_acquire()
ev = self._root_event
while ev is not None:
if ev.timeout <= 0:
val = 0
break
val = min(val, ev.timeout + ev._last_dt)
ev = ev.next
self._lock_release()
return val
cpdef get_events(self):
'''Returns the list of :class:`ClockEvent` instances currently scheduled.
'''
cdef list events = []
cdef ClockEvent ev
@ -424,3 +481,128 @@ cdef class CyClockBase(object):
ev = ev.next
self._lock_release()
return events
cdef class CyClockBaseFree(CyClockBase):
'''A clock class that supports scheduling free events in addition to normal
events.
Each of the :meth:`~CyClockBase.create_trigger`,
:meth:`~CyClockBase.schedule_once`, and :meth:`~CyClockBase.schedule_interval`
methods, which create a normal event, have a corresponding method
for creating a free event.
'''
cpdef create_trigger(self, callback, timeout=0, interval=False):
cdef FreeClockEvent event
if not callable(callback):
raise ValueError('callback must be a callable, got %s' % callback)
event = FreeClockEvent(False, self, interval, callback, timeout, 0)
event.release()
return event
cpdef schedule_once(self, callback, timeout=0):
cdef FreeClockEvent event
if not callable(callback):
raise ValueError('callback must be a callable, got %s' % callback)
event = FreeClockEvent(
False, self, False, callback, timeout, self._last_tick, None, True)
return event
cpdef schedule_interval(self, callback, timeout):
cdef FreeClockEvent event
if not callable(callback):
raise ValueError('callback must be a callable, got %s' % callback)
event = FreeClockEvent(
False, self, True, callback, timeout, self._last_tick, None, True)
return event
cpdef create_trigger_free(self, callback, timeout=0, interval=False):
'''Similar to :meth:`~CyClockBase.create_trigger`, but instead creates
a free event.
'''
cdef FreeClockEvent event
if not callable(callback):
raise ValueError('callback must be a callable, got %s' % callback)
event = FreeClockEvent(True, self, interval, callback, timeout, 0)
event.release()
return event
cpdef schedule_once_free(self, callback, timeout=0):
'''Similar to :meth:`~CyClockBase.schedule_once`, but instead creates
a free event.
'''
cdef FreeClockEvent event
if not callable(callback):
raise ValueError('callback must be a callable, got %s' % callback)
event = FreeClockEvent(
True, self, False, callback, timeout, self._last_tick, None, True)
return event
cpdef schedule_interval_free(self, callback, timeout):
'''Similar to :meth:`~CyClockBase.schedule_interval`, but instead creates
a free event.
'''
cdef FreeClockEvent event
if not callable(callback):
raise ValueError('callback must be a callable, got %s' % callback)
event = FreeClockEvent(
True, self, True, callback, timeout, self._last_tick, None, True)
return event
cpdef _process_free_events(self, double last_tick):
cdef FreeClockEvent event
cdef int done = False
self._lock_acquire()
if self._root_event is None:
self._lock_release()
return
self._cap_event = self._last_event
event = self._root_event
while not done and event is not None:
if not event.free:
done = self._cap_event is event or self._cap_event is None
event = event.next
continue
self._next_event = event.next
done = self._cap_event is event or self._cap_event is None
self._lock_release()
try:
event.tick(last_tick)
except:
raise
else:
self._lock_acquire()
event = self._next_event
self._next_event = self._cap_event = None
self._lock_release()
cpdef get_min_free_timeout(self):
'''Returns the remaining time since the start of the current frame
for the *free* event with the smallest timeout.
'''
cdef FreeClockEvent ev
cdef double val = DBL_MAX
self._lock_acquire()
ev = self._root_event
while ev is not None:
if ev.free:
if ev.timeout <= 0:
val = 0
break
val = min(val, ev.timeout + ev._last_dt)
ev = ev.next
self._lock_release()
return val

View File

@ -217,9 +217,139 @@ As a a consequence, calling :meth:`CyClockBase.unschedule` with the original
callback is now significantly slower and highly discouraged. Instead, the
returned events should be used to cancel. As a tradeoff, all the other methods
are now significantly faster than before.
Advanced Clock Details
-----------------------
The following section goes into the internal kivy clock details as well
as the various clock options. It is meant only for advanced users.
Fundamentally, the Kivy clock attempts to execute any scheduled callback
rhythmically as determined by the specified fps (frame per second, see
``maxfps`` in :mod:`~kivy.config`). That is, ideally, given e.g. a desired fps
of 30, the clock will execute the callbacks at intervals of 1 / 30 seconds, or
every 33.33 ms. All the callbacks in a frame are given the same timestamp,
i.e. the ``dt`` passed to the callback are all the same and it's the difference
in time between the start of this and the previous frame.
Because of inherent indeterminism, the frames do not actually occur exactly
at intervals of the fps and ``dt`` may be under or over the desired fps.
Also, once the timeout is "close enough" to the desired timeout, as determined
internally, Kivy will execute the callback in the current frame even when the
"actual time" has not elapsed the ``timeout`` amount.
Kivy offers now, since ``1.9.2``, multiple clocks with different behaviors.
Default Clock
^^^^^^^^^^^^^^
The default clock (``default``) behaves as described above. When a callback
with a timeout of zero or non-zero is scheduled, they are executed at the frame
that is near the timeout, which is a function of the fps. So a timeout of zero
would still result in a delay of one frame or about 1 / fps, typically a bit
less but sometimes more depending on the CPU usage of the other events
scheduled for that frame.
In a test using a fps of 30, a callback with a timeout of 0, 0.001, and 0.05,
resulted in a mean callback delay of 0.02487, 0.02488, and 0.05011 seconds,
respectively. When tested with a fps of 600 the delay for 0.05 was similar,
except the standard deviation was reduced resulting in overall better accuracy.
Interruptible Clock
^^^^^^^^^^^^^^^^^^^^
The default clock suffers from the quantization problem, as frames occur only
on intervals and any scheduled timeouts will not be able to occur during an
interval. For example, with the timeout of 0.05, while the mean was 0.05011,
its values ranged between 0.02548 - 0.07348 and a standard deviation of 0.002.
Also, there's the minimum timeout of about 0.02487.
The interruptible clock (``interrupt``) will execute timeouts even during a
frame. So a timeout of zero will execute as quickly as possible and similarly
a non-zero timeout will be executed even during the interval.
This clock, and all the clocks described after this have an option,
:attr:`ClockBaseInterruptBehavior.interupt_next_only`. When True, any of the
behavior new behavior will only apply to the callbacks with a timeout of
zero. Non-zero timeouts will behave like in the default clock. E.g. for this
clock when True, only zero timeouts will execute during the the interval.
In a test using a fps of 30, a callback with a timeout of 0, 0.001, and 0.05,
resulted in a mean callback delay of 0.00013, 0.00013, and 0.04120 seconds,
respectively when :attr:`ClockBaseInterruptBehavior.interupt_next_only` was
False. Also, compared to the default clock the standard deviation was reduced.
When :attr:`ClockBaseInterruptBehavior.interupt_next_only` was True, the values
were 0.00010, 0.02414, and 0.05034, respectively.
Free Clock
^^^^^^^^^^^
The interruptible clock may not be ideal for all cases because all the events
are executed during the intervals and events are not executed anymore
rhythmically as multiples of the fps. For example, there may not be any benefit
for the graphics to update in a sub-interval, so the additional accuracy
wastes CPU.
The Free clock (``free_all``) solves this by having ``Clock.xxx_free`` versions
of all the Clock scheduling methods. By free, we mean the event is free from
the fps because it's not fps limited. E.g.
:meth:`CyClockBaseFree.create_trigger_free` corresponds to
:meth:`CyClockBase.create_trigger`. Only when an event scheduled using the
``Clock.xxx_free`` methods is present will the clock interrupt and execute
the events during the interval. So, if no ``free`` event is present the clock
behaves like the ``default`` clock, otherwise it behaves like the ``interrupt``
clock.
In a test using a fps of 30, a callback with a timeout of 0s, 0.001s, and
0.05s, resulted in a mean callback delay of 0.00012s, 0.00017s, and 0.04121s
seconds, respectively when it was a free event and 0.02403s, 0.02405s, and
0.04829s, respectively when it wasn't.
Free Only Clock
^^^^^^^^^^^^^^^^^
The Free clock executes all events when a free event was scheduled. This
results in normal events also being execute in the middle of the interval
when a free event is scheduled. For example, above, when a free event was
absent, a normal event with a 0.001s timeout was delayed for 0.02405s. However,
if a free event happened to be also scheduled, the normal event was only
delayed 0.00014s, which may be undesirable.
The Free only clock (``free_only``) solves it by only executing free events
during the interval and normal events are always executed like with the
default clock. For example, in the presence of a free event, a normal event
with a timeout of 0.001s still had a delay of 0.02406. So this clock,
treats free and normal events independently, with normal events always being
fps limited, but never the free events.
Summary
^^^^^^^^
The kivy clock type to use can be set with the ``kivy_clock`` option the
:mod:`~kivy.config`. If ``KIVY_CLOCK`` is present in the environment it
overwrites the config selection. Its possible values are as follows:
* When ``kivy_clock`` is ``default``, the normal clock, :class:`ClockBase`,
which limits callbacks to the maxfps quantization - is used.
* When ``kivy_clock`` is ``interrupt``, a interruptible clock,
:class:`ClockBaseInterrupt`, which doesn't limit any callbacks to the
maxfps - is used. Callbacks will be executed at any time.
* When ``kivy_clock`` is ``free_all``, a interruptible clock,
:class:`ClockBaseFreeInterruptAll`, which doesn't limit any callbacks to the
maxfps in the presence of free events, but in their absence it limits events
to the fps quantization interval - is used.
* When ``kivy_clock`` is ``free_only``, a interruptible clock,
:class:`ClockBaseFreeInterruptAll`, which treats free and normal events
independently; normal events are fps limited while free events are not - is
used.
'''
__all__ = ('Clock', 'CyClockBase', 'ClockBase', 'ClockEvent', 'mainthread')
__all__ = (
'Clock', 'ClockEvent', 'FreeClockEvent', 'CyClockBase', 'CyClockBaseFree',
'ClockBaseBehavior', 'ClockBaseInterruptBehavior',
'ClockBaseInterruptFreeBehavior', 'ClockBase', 'ClockBaseInterrupt',
'ClockBaseFreeInterruptAll', 'ClockBaseFreeInterruptOnly', 'mainthread')
from sys import platform
from os import environ
@ -228,10 +358,18 @@ from kivy.context import register_context
from kivy.weakmethod import WeakMethod
from kivy.config import Config
from kivy.logger import Logger
from kivy.compat import clock as _default_time
from kivy.compat import clock as _default_time, PY2
import time
from threading import Lock
from kivy._clock import CyClockBase, ClockEvent
from kivy._clock import CyClockBase, ClockEvent, FreeClockEvent, \
CyClockBaseFree
try:
from multiprocessing import Event as MultiprocessingEvent
except ImportError: # https://bugs.python.org/issue3770
from threading import Event as MultiprocessingEvent
from threading import Event as ThreadingEvent
# some reading: http://gameprogrammingpatterns.com/game-loop.html
def _get_sleep_obj():
@ -317,8 +455,8 @@ except (OSError, ImportError, AttributeError):
time.sleep(microseconds / 1000000.)
class ClockBase(CyClockBase):
'''A clock object with event support.
class ClockBaseBehavior(object):
'''The base of the kivy clock.
'''
_dt = 0.0001
@ -330,16 +468,25 @@ class ClockBase(CyClockBase):
_rfps_counter = 0
_frames = 0
_frames_displayed = 0
_max_fps = 30.
_sleep_obj = None
_events_duration = 0
'''The measured time that it takes to process all the events etc, excepting
any sleep or waiting time. It is the average and is updated every 5
seconds.
'''
_duration_count = 0
_sleep_time = 0
_duration_ts0 = 0
MIN_SLEEP = 0.005
'''The minimum time to sleep. If the remaining time is less than this,
the event loop will continuo
'''
SLEEP_UNDERSHOOT = MIN_SLEEP - 0.001
def __init__(self):
super(ClockBase, self).__init__()
self._sleep_obj = _get_sleep_obj()
self._start_tick = self._last_tick = self.time()
def __init__(self, **kwargs):
super(ClockBaseBehavior, self).__init__(**kwargs)
self._duration_ts0 = self._start_tick = self._last_tick = self.time()
self._max_fps = float(Config.getint('graphics', 'maxfps'))
@property
@ -369,7 +516,31 @@ class ClockBase(CyClockBase):
def usleep(self, microseconds):
'''Sleeps for the number of microseconds.
'''
_usleep(microseconds, self._sleep_obj)
pass
def idle(self):
'''(internal) waits here until the next frame.
'''
fps = self._max_fps
if fps > 0:
min_sleep = self.get_resolution()
undershoot = 4 / 5. * min_sleep
usleep = self.usleep
ready = self._check_ready
done, sleeptime = ready(fps, min_sleep, undershoot)
while not done:
usleep(1000000 * sleeptime)
done, sleeptime = ready(fps, min_sleep, undershoot)
current = self.time()
self._dt = current - self._last_tick
self._last_tick = current
return current
def _check_ready(self, fps, min_sleep, undershoot):
sleeptime = 1 / fps - (self.time() - self._last_tick)
return sleeptime - undershoot <= min_sleep, sleeptime - undershoot
def tick(self):
'''Advance the clock to the next step. Must be called every frame.
@ -378,24 +549,22 @@ class ClockBase(CyClockBase):
self._release_references()
# do we need to sleep ?
if self._max_fps > 0:
min_sleep = self.MIN_SLEEP
sleep_undershoot = self.SLEEP_UNDERSHOOT
fps = self._max_fps
usleep = self.usleep
sleeptime = 1 / fps - (self.time() - self._last_tick)
while sleeptime - sleep_undershoot > min_sleep:
usleep(1000000 * (sleeptime - sleep_undershoot))
sleeptime = 1 / fps - (self.time() - self._last_tick)
ts = self.time()
current = self.idle()
# tick the current time
current = self.time()
self._dt = current - self._last_tick
self._frames += 1
self._fps_counter += 1
self._last_tick = current
# compute how long the event processing takes
self._duration_count += 1
self._sleep_time += current - ts
t_tot = current - self._duration_ts0
if t_tot >= 1.:
self._events_duration = \
(t_tot - self._sleep_time) / float(self._duration_count)
self._duration_ts0 = current
self._sleep_time = self._duration_count = 0
# calculate fps things
if self._last_fps_tick is None:
@ -444,7 +613,165 @@ class ClockBase(CyClockBase):
time = staticmethod(partial(_default_time))
ClockBase.time.__doc__ = '''Proxy method for :func:`~kivy.compat.clock`. '''
ClockBaseBehavior.time.__doc__ = '''Proxy method for :func:`~kivy.compat.clock`. '''
class ClockBaseInterruptBehavior(ClockBaseBehavior):
'''A kivy clock which can be interrupted during a frame to execute events.
'''
interupt_next_only = False
_event = None
_get_min_timeout_func = None
def __init__(self, interupt_next_only=False, **kwargs):
super(ClockBaseInterruptBehavior, self).__init__(**kwargs)
self._event = MultiprocessingEvent() if PY2 else ThreadingEvent()
self.interupt_next_only = interupt_next_only
self._get_min_timeout_func = self.get_min_timeout
def usleep(self, microseconds):
self._event.clear()
self._event.wait(microseconds / 1000000.)
def on_schedule(self, event):
fps = self._max_fps
if not fps:
return
if not event.timeout or (
not self.interupt_next_only and event.timeout
<= 1 / fps # remaining time
- (self.time() - self._last_tick) # elapsed time
+ 4 / 5. * self.get_resolution()): # resolution fudge factor
self._event.set()
def idle(self):
fps = self._max_fps
event = self._event
resolution = self.get_resolution()
if fps > 0:
done, sleeptime = self._check_ready(
fps, resolution, 4 / 5. * resolution)
if not done:
event.wait(sleeptime)
current = self.time()
self._dt = current - self._last_tick
self._last_tick = current
event.clear()
# anything scheduled from now on, if scheduled for the upcoming frame
# will cause a timeout of the event on the next idle due to on_schedule
# `self._last_tick = current` must happen before clear, otherwise the
# on_schedule computation is wrong when exec between the clear and
# the `self._last_tick = current` bytecode.
return current
def _check_ready(self, fps, min_sleep, undershoot):
if self._event.is_set():
return True, 0
t = self._get_min_timeout_func()
if not t:
return True, 0
if not self.interupt_next_only:
curr_t = self.time()
sleeptime = min(1 / fps - (curr_t - self._last_tick), t - curr_t)
else:
sleeptime = 1 / fps - (self.time() - self._last_tick)
return sleeptime - undershoot <= min_sleep, sleeptime - undershoot
class ClockBaseInterruptFreeBehavior(ClockBaseInterruptBehavior):
'''A base class for the clock that interrupts the sleep interval for
free events.
'''
def __init__(self, **kwargs):
super(ClockBaseInterruptFreeBehavior, self).__init__(**kwargs)
self._get_min_timeout_func = self.get_min_free_timeout
def on_schedule(self, event):
if not event.free: # only wake up for free events
return
# free events should use real time not frame time
event._last_dt = self.time()
return super(ClockBaseInterruptFreeBehavior,
self).on_schedule(event)
class ClockBase(ClockBaseBehavior, CyClockBase):
'''The ``default`` kivy clock. See module for details.
'''
_sleep_obj = None
def __init__(self, **kwargs):
super(ClockBase, self).__init__(**kwargs)
self._sleep_obj = _get_sleep_obj()
def usleep(self, microseconds):
_usleep(microseconds, self._sleep_obj)
class ClockBaseInterrupt(ClockBaseInterruptBehavior, CyClockBase):
'''The ``interrupt`` kivy clock. See module for details.
'''
pass
class ClockBaseFreeInterruptAll(
ClockBaseInterruptFreeBehavior, CyClockBaseFree):
'''The ``free_all`` kivy clock. See module for details.
'''
pass
class ClockBaseFreeInterruptOnly(
ClockBaseInterruptFreeBehavior, CyClockBaseFree):
'''The ``free_only`` kivy clock. See module for details.
'''
def idle(self):
fps = self._max_fps
if fps > 0:
event = self._event
min_sleep = self.get_resolution()
usleep = self.usleep
undershoot = 4 / 5. * min_sleep
min_t = self.get_min_free_timeout
interupt_next_only = self.interupt_next_only
current = self.time()
sleeptime = 1 / fps - (current - self._last_tick)
while sleeptime - undershoot > min_sleep:
if event.is_set():
do_free = True
else:
t = min_t()
if not t:
do_free = True
elif interupt_next_only:
do_free = False
else:
sleeptime = min(sleeptime, t - current)
do_free = sleeptime - undershoot <= min_sleep
if do_free:
event.clear()
self._process_free_events(current)
else:
event.wait(sleeptime - undershoot)
current = self.time()
sleeptime = 1 / fps - (current - self._last_tick)
self._dt = current - self._last_tick
self._last_tick = current
event.clear() # this needs to stay after _last_tick
return current
def mainthread(func):
@ -461,6 +788,7 @@ def mainthread(func):
print('The request succedded!',
'This callback is called in the main thread.')
self.req = UrlRequest(url='http://...', on_success=callback)
.. versionadded:: 1.8.0
@ -473,7 +801,18 @@ def mainthread(func):
return delayed_func
if 'KIVY_DOC_INCLUDE' in environ:
#: Instance of :class:`ClockBase`.
#: Instance of :class:`ClockBaseBehavior`.
Clock = None
else:
Clock = register_context('Clock', ClockBase)
_classes = {'default': ClockBase, 'interrupt': ClockBaseInterrupt,
'free_all': ClockBaseFreeInterruptAll,
'free_only': ClockBaseFreeInterruptOnly}
_clk = environ.get('KIVY_CLOCK', Config.get('kivy', 'kivy_clock'))
if _clk not in _classes:
raise Exception(
'{} is not a valid kivy clock. Valid clocks are {}'.format(
_clk, sorted(_classes.keys())))
Clock = register_context('Clock', _classes[_clk])
'''The kivy Clock instance. See module documentation for details.
'''

View File

@ -190,6 +190,8 @@ Available configuration tokens
:class:`~kivy.uix.behaviors.buttonbehavior.ButtonBehavior` to
make sure they display their current visual state for the given
time.
`kivy_clock`: one of `default`, `interrupt`, `free_all`, `free_only`
The clock type to use with kivy. See :mod:`kivy.clock`.
:input:
@ -263,6 +265,7 @@ Available configuration tokens
.. versionchanged:: 1.9.2
`min_state_time` has been added to the `graphics` section.
`kivy_clock` has been added to the kivy section
.. versionchanged:: 1.9.0
`borderless` and `window_state` have been added to the graphics section.
@ -309,7 +312,7 @@ from weakref import ref
_is_rpi = exists('/opt/vc/include/bcm_host.h')
# Version number of current configuration format
KIVY_CONFIG_VERSION = 15
KIVY_CONFIG_VERSION = 16
Config = None
'''The default Kivy configuration object. This is a :class:`ConfigParser`
@ -799,6 +802,9 @@ if not environ.get('KIVY_DOC_INCLUDE'):
elif version == 14:
Config.setdefault('graphics', 'min_state_time', '.035')
elif version == 15:
Config.setdefault('kivy', 'kivy_clock', 'default')
# elif version == 1:
# # add here the command for upgrading from configuration 0 to 1