implement stricter and more consistent timeouts.

- stricter timeouts: Instead of adding an entire millisecond, use a list
  of timeouts that are supposed to be handled on the same tick. This
  reduces the delay between target tick and actual tick, especially for
  many timeouts issued to the same tick, i.e. combos.
- consistent timeouts: Timeouts are now guaranteed to be handled in the
  order they were issued. Timer rollover is handled properly.

caveat / change to the interface: the returned `timeout_key` is a tuple
of the target timeout tick and the index of the timeout at that tick.
This commit is contained in:
xs5871 2022-03-09 01:22:09 +00:00 committed by Kyle Brown
parent d4e72b98c9
commit 07a485b04d
2 changed files with 30 additions and 14 deletions

View File

@ -3,6 +3,7 @@ from supervisor import ticks_ms
from kmk.consts import KMK_RELEASE, UnicodeMode from kmk.consts import KMK_RELEASE, UnicodeMode
from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes
from kmk.keys import KC from kmk.keys import KC
from kmk.kmktime import ticks_add, ticks_diff
from kmk.matrix import MatrixScanner, intify_coordinate from kmk.matrix import MatrixScanner, intify_coordinate
@ -210,17 +211,22 @@ class KMKKeyboard:
# We allow passing False as an implicit "run this on the next process timeouts cycle" # We allow passing False as an implicit "run this on the next process timeouts cycle"
timeout_key = ticks_ms() timeout_key = ticks_ms()
else: else:
timeout_key = ticks_ms() + after_ticks timeout_key = ticks_add(ticks_ms(), after_ticks)
while timeout_key in self._timeouts: if timeout_key not in self._timeouts:
timeout_key += 1 self._timeouts[timeout_key] = []
self._timeouts[timeout_key] = callback idx = len(self._timeouts[timeout_key])
return timeout_key self._timeouts[timeout_key].append(callback)
return (timeout_key, idx)
def cancel_timeout(self, timeout_key): def cancel_timeout(self, timeout_key):
if timeout_key in self._timeouts: try:
del self._timeouts[timeout_key] self._timeouts[timeout_key[0]][timeout_key[1]] = None
except (KeyError, IndexError):
if self.debug_enabled:
print(f'no such timeout: {timeout_key}')
def _process_timeouts(self): def _process_timeouts(self):
if not self._timeouts: if not self._timeouts:
@ -228,14 +234,20 @@ class KMKKeyboard:
current_time = ticks_ms() current_time = ticks_ms()
# cast this to a tuple to ensure that if a callback itself sets # Copy to a temporary list to allow sorting and ensure that if a
# timeouts, we do not handle them on the current cycle # callback itself sets timeouts, we do not handle them on the current
timeouts = tuple(self._timeouts.items()) # cycle.
timeouts = []
for k, v in self._timeouts.items():
if ticks_diff(k, current_time) <= 0:
timeouts.append((k, v))
for k, v in timeouts: timeouts.sort(key=lambda k: k[0])
if k <= current_time: for k, callbacks in timeouts:
v() del self._timeouts[k]
self.cancel_timeout(k) for callback in callbacks:
if callback:
callback()
return self return self

View File

@ -12,6 +12,10 @@ def ticks_diff(new, start):
return diff return diff
def ticks_add(ticks, delta):
return (ticks + delta) % _TICKS_PERIOD
def check_deadline(new, start, ms): def check_deadline(new, start, ms):
return ticks_diff(new, start) < ms return ticks_diff(new, start) < ms