mirror of https://github.com/kivy/kivy.git
Merge branch 'master' of https://github.com/kivy/kivy
This commit is contained in:
commit
24da014678
|
@ -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)
|
||||
|
|
212
kivy/_clock.pyx
212
kivy/_clock.pyx
|
@ -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
|
||||
|
|
397
kivy/clock.py
397
kivy/clock.py
|
@ -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.
|
||||
'''
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue