Skip to content

Commit

Permalink
Fixed time conversion logic when speed=0
Browse files Browse the repository at this point in the history
  • Loading branch information
matt-hammond-001 committed Nov 7, 2016
1 parent 8587bbc commit 32256b6
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 14 deletions.
16 changes: 16 additions & 0 deletions docs/clock.rst
Expand Up @@ -17,6 +17,22 @@ Module: `dvbcss.clock`
:noindex:


.. _nan:

Not a number (nan)
------------------

"Not a number" value of a `float <https://docs.python.org/2/library/functions.html#float>`_. Check if a value is NaN like this::

>>> import math
>>> math.isnan(nanValue)
True

Converting tick values to a parent clock or to the root clock may result in this
value being returned if one or more of the clocks involved has speed zero.



Functions
---------

Expand Down
7 changes: 6 additions & 1 deletion docs/task-internals.rst
Expand Up @@ -15,8 +15,13 @@ Sleep and callback methods cause a task objet to be queued. The scheduler picks
binds to the Clock so that it is notified of adjustments to the clock. When a task is added to the queue, the clock is queried to calculate
the true time at which the tick count is expected to be reached by calling :func:`dvbcss.clock.ClockBase.calcWhen`

If a clock is adjusted the affected tasks are marked as deprecated (but remain in the priority queue) and new tasks are scheduled with a
If a clock is adjusted the affected tasks are marked as deprecated (but remain in the priority queue) and new tasks are rescheduled with a
recalculated time.

When one of the clocks involved has speed 0, then it may not be possible to calculate the time at which the task is to be scheduled. This
happens when :func:`dvbcss.clock.ClockBase.calcWhen` returns :ref:`nan`. The task will not be immediately added to the priority queue,
however it will be added later once the clock speed returns to a non-zero value. This happens automatically as part of the rescheduling
process when a clock is adjusted.


Objects
Expand Down
26 changes: 15 additions & 11 deletions dvbcss/clock.py
Expand Up @@ -516,7 +516,7 @@ def unbind(self,dependent):

def calcWhen(self, ticksWhen):
"""\
:Return: "when" in terms of the underlying clock behind the root clock implementation (e.g. :func:`monotonic_time.time` in the case of :class:`SysClock`)
:Return: "when" in terms of the underlying clock behind the root clock implementation (e.g. :func:`monotonic_time.time` in the case of :class:`SysClock`). Will return :ref:`nan` if the conversion is not possible (e.g. when current `speed` is zero and `ticksWhen` does not match the current `correlation.childTicks`).
|stub-method|
"""
Expand Down Expand Up @@ -555,8 +555,10 @@ def toRootTicks(self, t):
"""\
Return the time for the root clock corresponding to a given time of this clock.
Will return :ref:`nan` if the conversion is not possible (e.g. when current `speed` is zero and `ticksWhen` does not match the current `correlation.childTicks`).
:param t: Tick value for this clock.
:returns: Corresponding tick value of the root clock.
:returns: Corresponding tick value of the root clock, or :ref:`nan`
.. versionadded:: 0.4
"""
Expand All @@ -571,10 +573,12 @@ def toOtherClockTicks(self, otherClock, ticks):
"""\
Converts a tick value for this clock into a tick value corresponding to the timescale of another clock.
Will return :ref:`nan` if the conversion is not possible (e.g. when current `speed` is zero and `ticksWhen` does not match the current `correlation.childTicks`).
:param otherClock: A :class:`~dvbcss.clock` object representing another clock.
:param ticks: A time (tick value) for this clock
:returns: The tick value of the `otherClock` that represents the same moment in time.
:returns: The tick value of the `otherClock` that represents the same moment in time, or :ref:`nan`
:throws NoCommonClock: if there is no common ancestor clock (meaning it is not possible to convert
"""
Expand Down Expand Up @@ -1006,19 +1010,20 @@ class CorrelatedClock(ClockBase):
"""

def __init__(self, parentClock, tickRate, correlation=Correlation(0,0), **kwargs):
def __init__(self, parentClock, tickRate, correlation=Correlation(0,0), speed=1.0, **kwargs):
"""\
:param parentClock: The parent clock for this clock.
:param tickRate: (int) tick rate for this clock (in ticks per second)
:param correlation: (:class:`Correlation`) or tuple `(parentTicks, selfTicks)`. The intial correlation for this clock.
:param speed: Initial speed for this clock.
"""
super(CorrelatedClock,self).__init__(**kwargs)
if tickRate <= 0 or not isinstance(tickRate, numbers.Number):
raise ValueError("Cannot set tickRate to "+repr(tickRate))
self._freq = tickRate
self._parent=parentClock
self._speed = 1.0
self._speed = speed
if isinstance(correlation, tuple):
correlation = Correlation(*correlation)
self._correlation=correlation
Expand Down Expand Up @@ -1104,15 +1109,14 @@ def setCorrelationAndSpeed(self, newCorrelation, newSpeed):
self.notify(self)

def calcWhen(self,ticksWhen):
if self.speed == 0:
refticks=self._correlation.parentTicks # return any arbitrary position if the speed of this clock is zero (pause)
else:
refticks=self._correlation.parentTicks + (ticksWhen - self._correlation.childTicks)*self._parent.tickRate/self._freq/self.speed
return self._parent.calcWhen(refticks)
return self._parent.calcWhen(self.toParentTicks(ticksWhen));

def toParentTicks(self, ticks):
if self.speed == 0:
return self._correlation.parentTicks # return any arbitrary position if the speed of this clock is zero (pause)
if ticks == self._correlation.childTicks:
return self._correlation.parentTicks
else:
return float('nan'); # because not defined if not on the point of correlation. There is no way to map to parent ticks
else:
return self._correlation.parentTicks + (ticks - self._correlation.childTicks)*self._parent.tickRate/self._freq/self.speed

Expand Down
6 changes: 4 additions & 2 deletions dvbcss/task.py
Expand Up @@ -157,7 +157,8 @@ def run(self):
while not self.addQueue.empty():
clock, whenTicks, callBack, args, kwargs = self.addQueue.get_nowait()
task = _Task(clock, whenTicks, callBack, args, kwargs)
heapq.heappush(self.taskheap, (task.when, task))
if not math.isnan(task.when):
heapq.heappush(self.taskheap, (task.when, task))

if clock not in self.clock_Tasks:
self.clock_Tasks[clock] = { task:True }
Expand All @@ -171,7 +172,8 @@ def run(self):
tasksMap=self.clock_Tasks.get(clock, {})
for task in tasksMap.keys():
newTask=task.regenerateAndDeprecate()
heapq.heappush(self.taskheap,(newTask.when, newTask))
if not math.isnan(newtask.when):
heapq.heappush(self.taskheap,(newTask.when, newTask))
tasksMap[newTask] = True
del tasksMap[task]

Expand Down
9 changes: 9 additions & 0 deletions test/test_Clock.py
Expand Up @@ -15,6 +15,7 @@
# limitations under the License.

import unittest
import math

import _useDvbCssUninstalled # Enable to run when dvbcss not yet installed ... @UnusedImport

Expand Down Expand Up @@ -394,6 +395,10 @@ def test_toParentTicks(self):
c = CorrelatedClock(b, 1000, correlation=Correlation(50,300))
self.assertAlmostEqual(c.toParentTicks(400), 50 + (400-300)*2000, places=5 )

c = CorrelatedClock(b, 1000, correlation=Correlation(50,300), speed=0)
self.assertEquals(c.toParentTicks(300), 50)
self.assertTrue(math.isnan(c.toParentTicks(400)))

def test_fromParentTicks(self):
mockTime = self.mockTime

Expand All @@ -403,6 +408,10 @@ def test_fromParentTicks(self):

c = CorrelatedClock(b, 1000, correlation=Correlation(50,300))
self.assertAlmostEqual(c.fromParentTicks(50 + (400-300)*2000), 400, places=5 )

c = CorrelatedClock(b, 1000, correlation=Correlation(50,300), speed=0)
self.assertEquals(c.fromParentTicks(50), 300)
self.assertEquals(c.fromParentTicks(100), 300)

def test_getParent(self):
b = self.newSysClock()
Expand Down

0 comments on commit 32256b6

Please sign in to comment.