11from typing import Any , Callable , Iterable , ClassVar
2- from heapq import heappush , heappop
2+ from heapq import heappush , heappop , _siftup
33from hal import (
44 report ,
55 initializeNotifier ,
2424
2525
2626class _Callback :
27+
28+ __slots__ = "func" , "_periodUs" , "expirationUs"
29+
2730 def __init__ (
2831 self ,
2932 func : Callable [[], None ],
@@ -96,6 +99,9 @@ def __repr__(self) -> str:
9699
97100
98101class _OrderedList :
102+
103+ __slots__ = "_data"
104+
99105 def __init__ (self ) -> None :
100106 self ._data : list [Any ] = []
101107
@@ -113,6 +119,9 @@ def peek(
113119 else :
114120 return None
115121
122+ def siftupRoot (self ):
123+ _siftup (self ._data , 0 )
124+
116125 def __len__ (self ) -> int :
117126 return len (self ._data )
118127
@@ -189,13 +198,20 @@ def startCompetition(self) -> None:
189198 # We don't have to check there's an element in the queue first because
190199 # there's always at least one (the constructor adds one). It's re-enqueued
191200 # at the end of the loop.
192- callback = self ._callbacks .pop ()
201+ callback = self ._callbacks .peek ()
193202
194203 status = updateNotifierAlarm (self ._notifier , callback .expirationUs )
195204 if status != 0 :
196205 raise RuntimeError (f"updateNotifierAlarm() returned { status } " )
197206
198207 self ._loopStartTimeUs , status = waitForNotifierAlarm (self ._notifier )
208+
209+ # The C++ code that this was based upon used the following line to establish
210+ # the loopStart time. Uncomment it and
211+ # the "self._loopStartTimeUs = startTimeUs" further below to emulate the
212+ # legacy behavior.
213+ # startTimeUs = _getFPGATime() # uncomment this for legacy behavior
214+
199215 if status != 0 :
200216 raise RuntimeError (
201217 f"waitForNotifierAlarm() returned _loopStartTimeUs={ self ._loopStartTimeUs } status={ status } "
@@ -207,11 +223,24 @@ def startCompetition(self) -> None:
207223 # See the API for waitForNotifierAlarm
208224 break
209225
226+ # On a RoboRio 2, the following print statement results in values like:
227+ # print(f"expUs={callback.expirationUs} current={self._loopStartTimeUs}, legacy={startTimeUs}")
228+ # [2.27] expUs=3418017 current=3418078, legacy=3418152
229+ # [2.29] expUs=3438017 current=3438075, legacy=3438149
230+ # This indicates that there is about 60 microseconds of skid from
231+ # callback.expirationUs to self._loopStartTimeUs
232+ # and there is about 70 microseconds of skid from self._loopStartTimeUs to startTimeUs.
233+ # Consequently, this code uses "self._loopStartTimeUs, status = waitForNotifierAlarm"
234+ # to establish loopStartTime, rather than slowing down the code by adding an extra call to
235+ # "startTimeUs = _getFPGATime()".
236+
237+ # self._loopStartTimeUs = startTimeUs # Uncomment this line for legacy behavior.
238+
210239 self ._runCallbackAndReschedule (callback )
211240
212241 # Process all other callbacks that are ready to run
213242 while self ._callbacks .peek ().expirationUs <= self ._loopStartTimeUs :
214- callback = self ._callbacks .pop ()
243+ callback = self ._callbacks .peek ()
215244 self ._runCallbackAndReschedule (callback )
216245 finally :
217246 # pytests hang on PC when we don't force a call to self._stopNotifier()
@@ -224,7 +253,8 @@ def _runCallbackAndReschedule(self, callback: _Callback) -> None:
224253 # that ran long we immediately push the next invocation to the
225254 # following period.
226255 callback .setNextStartTimeUs (_getFPGATime ())
227- self ._callbacks .add (callback )
256+ # assert callback is self._callbacks.peek()
257+ self ._callbacks .siftupRoot ()
228258
229259 def _stopNotifier (self ):
230260 stopNotifier (self ._notifier )
@@ -269,8 +299,9 @@ def addPeriodic(
269299 to TimedRobotPy.
270300 """
271301
272- self ._callbacks .add (
273- _Callback .makeCallBack (
274- callback , self ._startTimeUs , int (period * 1e6 ), int (offset * 1e6 )
275- )
302+ cb = _Callback .makeCallBack (
303+ callback , self ._startTimeUs , int (period * 1e6 ), int (offset * 1e6 )
276304 )
305+ if len (self ._callbacks ):
306+ assert cb > self ._callbacks .peek ()
307+ self ._callbacks .add (cb )
0 commit comments