Skip to content

Commit 7c417db

Browse files
committed
Further optimize the code, assuming that the invariant holds
this invariant is: the report keys are unique (any nonzero entry appears at most once) and compact (all nonzero entries are first). A benchmark loop measures how long add/remove report take depending on the number of other keys pressed: ``` kbd = Keyboard(usb_hid.devices) t0 = ticks_ms() for _ in range(1000): kbd._add_keycode_to_report(K.A) kbd._remove_keycode_from_report(K.A) t1 = ticks_ms() print(f"Press release key time {ticks_diff(t1,t0)}us/call") kbd = Keyboard(usb_hid.devices) t0 = ticks_ms() for _ in range(1000): kbd._add_keycode_to_report(K.A) kbd._remove_keycode_from_report(K.A) t1 = ticks_ms() print(f"Press release key time {ticks_diff(t1,t0)}us/call") for k in range(K.ONE, K.SIX): kbd._add_keycode_to_report(k) t0 = ticks_ms() for _ in range(1000): kbd._add_keycode_to_report(K.A) kbd._remove_keycode_from_report(K.A) t1 = ticks_ms() print(f"Press release key time {ticks_diff(t1,t0)}us/call") kbd.release_all() ``` Timings were done on a KB2040 with 8.0.0-beta. | Test | Before | After | | Empty | 238 | 158 | | 1 | 246 | 188 | | 2 | 255 | 219 | | 3 | 266 | 249 | | 4 | 274 | 280 | | 5 | 283 | 301 | So in the usual case (fewer than 4 non-modifier keys pressed) this slightly improves performance, but when the report is full (even if it doesn't overflow) performance decreases, albeit by just 18us. Compared to the 8ms time to send a report or the default 20ms between polling a keypad.KeyMatrix this is minor.
1 parent b7ad1d5 commit 7c417db

File tree

1 file changed

+13
-13
lines changed

1 file changed

+13
-13
lines changed

adafruit_hid/keyboard.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,18 +134,15 @@ def _add_keycode_to_report(self, keycode: int) -> None:
134134
self.report_modifier[0] |= modifier
135135
else:
136136
# Don't press twice.
137-
# (I'd like to use 'not in self.report_keys' here, but that's not implemented.)
138-
free_slot = None
139137
for i in range(_MAX_KEYPRESSES):
140-
if free_slot is None and self.report_keys[i] == 0:
141-
free_slot = i
138+
if self.report_keys[i] == 0:
139+
# Put keycode in first empty slot. Since the report_keys
140+
# are compact and unique, this is not a repeated key
141+
self.report_keys[i] = keycode
142+
return
142143
if self.report_keys[i] == keycode:
143144
# Already pressed.
144145
return
145-
# Put keycode in first empty slot.
146-
if free_slot is not None:
147-
self.report_keys[free_slot] = keycode
148-
return
149146
# All slots are filled. Shuffle down and reuse last slot
150147
for i in range(_MAX_KEYPRESSES - 1):
151148
self.report_keys[i] = self.report_keys[i + 1]
@@ -158,16 +155,19 @@ def _remove_keycode_from_report(self, keycode: int) -> None:
158155
# Turn off the bit for this modifier.
159156
self.report_modifier[0] &= ~modifier
160157
else:
161-
# Clear any matching slots and move remaining keys down
158+
# Clear the at most one matching slot and move remaining keys down
162159
j = 0
163160
for i in range(_MAX_KEYPRESSES):
164161
pressed = self.report_keys[i]
165-
if (not pressed) or (pressed == keycode):
162+
if not pressed:
163+
break # Handled all used report slots
164+
if pressed == keycode:
166165
continue # Remove this entry
167-
self.report_keys[j] = self.report_keys[i]
166+
if i != j:
167+
self.report_keys[j] = self.report_keys[i]
168168
j += 1
169-
# Check all the slots, just in case there's a duplicate. (There should not be.)
170-
while j < _MAX_KEYPRESSES:
169+
# Clear any remaining slots
170+
while j < _MAX_KEYPRESSES and self.report_keys[j]:
171171
self.report_keys[j] = 0
172172
j += 1
173173

0 commit comments

Comments
 (0)