1
1
from typing import Any , Callable , Iterable , ClassVar
2
- from heapq import heappush , heappop
2
+ from heapq import heappush , heappop , _siftup
3
3
from hal import (
4
4
report ,
5
5
initializeNotifier ,
24
24
25
25
26
26
class _Callback :
27
+
28
+ __slots__ = "func" , "_periodUs" , "expirationUs"
29
+
27
30
def __init__ (
28
31
self ,
29
32
func : Callable [[], None ],
@@ -96,6 +99,9 @@ def __repr__(self) -> str:
96
99
97
100
98
101
class _OrderedList :
102
+
103
+ __slots__ = "_data"
104
+
99
105
def __init__ (self ) -> None :
100
106
self ._data : list [Any ] = []
101
107
@@ -113,6 +119,9 @@ def peek(
113
119
else :
114
120
return None
115
121
122
+ def siftupRoot (self ):
123
+ _siftup (self ._data , 0 )
124
+
116
125
def __len__ (self ) -> int :
117
126
return len (self ._data )
118
127
@@ -189,13 +198,20 @@ def startCompetition(self) -> None:
189
198
# We don't have to check there's an element in the queue first because
190
199
# there's always at least one (the constructor adds one). It's re-enqueued
191
200
# at the end of the loop.
192
- callback = self ._callbacks .pop ()
201
+ callback = self ._callbacks .peek ()
193
202
194
203
status = updateNotifierAlarm (self ._notifier , callback .expirationUs )
195
204
if status != 0 :
196
205
raise RuntimeError (f"updateNotifierAlarm() returned { status } " )
197
206
198
207
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
+
199
215
if status != 0 :
200
216
raise RuntimeError (
201
217
f"waitForNotifierAlarm() returned _loopStartTimeUs={ self ._loopStartTimeUs } status={ status } "
@@ -207,11 +223,24 @@ def startCompetition(self) -> None:
207
223
# See the API for waitForNotifierAlarm
208
224
break
209
225
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
+
210
239
self ._runCallbackAndReschedule (callback )
211
240
212
241
# Process all other callbacks that are ready to run
213
242
while self ._callbacks .peek ().expirationUs <= self ._loopStartTimeUs :
214
- callback = self ._callbacks .pop ()
243
+ callback = self ._callbacks .peek ()
215
244
self ._runCallbackAndReschedule (callback )
216
245
finally :
217
246
# pytests hang on PC when we don't force a call to self._stopNotifier()
@@ -224,7 +253,8 @@ def _runCallbackAndReschedule(self, callback: _Callback) -> None:
224
253
# that ran long we immediately push the next invocation to the
225
254
# following period.
226
255
callback .setNextStartTimeUs (_getFPGATime ())
227
- self ._callbacks .add (callback )
256
+ # assert callback is self._callbacks.peek()
257
+ self ._callbacks .siftupRoot ()
228
258
229
259
def _stopNotifier (self ):
230
260
stopNotifier (self ._notifier )
@@ -269,8 +299,9 @@ def addPeriodic(
269
299
to TimedRobotPy.
270
300
"""
271
301
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 )
276
304
)
305
+ if len (self ._callbacks ):
306
+ assert cb > self ._callbacks .peek ()
307
+ self ._callbacks .add (cb )
0 commit comments