Merge pull request #133 from KMKfw/topic-misc-perf

Slight perf improvements, heavily improved logging/debugging output
This commit is contained in:
Josh Klar 2019-07-12 17:01:26 -07:00 committed by GitHub
commit f6a39acd26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 216 additions and 142 deletions

View File

@ -15,16 +15,18 @@
# chain to import _every single thing_ KMK eventually uses in a normal
# workflow, in order from fewest to least nested dependencies.
# First, stuff that has no dependencies, or only C/MPY deps
# First, system-provided deps
import busio # isort:skip
import collections # isort:skip
import gc # isort:skip
import supervisor # isort:skip
# Now "light" KMK stuff with few/no external deps
import kmk.consts # isort:skip
import kmk.kmktime # isort:skip
import kmk.types # isort:skip
import kmk.util # isort:skip
import busio # isort:skip
import supervisor # isort:skip
from kmk.consts import LeaderMode, UnicodeMode # isort:skip
from kmk.hid import USB_HID # isort:skip
from kmk.internal_state import InternalState # isort:skip
@ -48,6 +50,7 @@ import kmk.internal_state # isort:skip
# Thanks for sticking around. Now let's do real work, starting below
from kmk.kmktime import sleep_ms
from kmk.util import intify_coordinate as ic
@ -100,6 +103,76 @@ class Firmware:
self._state = InternalState(self)
def __repr__(self):
return (
'Firmware('
'debug_enabled={} '
'keymap=truncated '
'coord_mapping=truncated '
'row_pins=truncated '
'col_pins=truncated '
'diode_orientation={} '
'matrix_scanner={} '
'unicode_mode={} '
'tap_time={} '
'leader_mode={} '
'leader_dictionary=truncated '
'leader_timeout={} '
'hid_helper={} '
'extra_data_pin={} '
'split_offsets={} '
'split_flip={} '
'split_side={} '
'split_type={} '
'split_master_left={} '
'is_master={} '
'uart={} '
'uart_flip={} '
'uart_pin={}'
')'
).format(
self.debug_enabled,
# self.keymap,
# self.coord_mapping,
# self.row_pins,
# self.col_pins,
self.diode_orientation,
self.matrix_scanner,
self.unicode_mode,
self.tap_time,
self.leader_mode,
# self.leader_dictionary,
self.leader_timeout,
self.hid_helper.__name__,
self.extra_data_pin,
self.split_offsets,
self.split_flip,
self.split_side,
self.split_type,
self.split_master_left,
self.is_master,
self.uart,
self.uart_flip,
self.uart_pin,
)
def _print_debug_cycle(self, init=False):
pre_alloc = gc.mem_alloc()
pre_free = gc.mem_free()
if self.debug_enabled:
if init:
print('KMKInit()')
print(self)
print(self._state)
print('GCStats(pre_alloc={} pre_free={} alloc={} free={})'.format(
pre_alloc,
pre_free,
gc.mem_alloc(),
gc.mem_free(),
))
def _send_hid(self):
self._hid_helper_inst.create_report(self._state.keys_pressed).send()
self._state.resolve_hid()
@ -155,21 +228,8 @@ class Firmware:
self.uart.write('DEB')
self.uart.write(message, '\n')
def _master_half(self):
if self.is_master is not None:
return self.is_master
# Working around https://github.com/adafruit/circuitpython/issues/1769
try:
self._hid_helper_inst.create_report([]).send()
self.is_master = True
except OSError:
self.is_master = False
return self.is_master
def init_uart(self, pin, timeout=20):
if self._master_half():
if self.is_master:
return busio.UART(tx=None, rx=pin, timeout=timeout)
else:
return busio.UART(tx=pin, rx=None, timeout=timeout)
@ -183,13 +243,27 @@ class Firmware:
self._hid_helper_inst = self.hid_helper()
# Split keyboard Init
if self.split_flip and not self._master_half():
self.col_pins = list(reversed(self.col_pins))
if self.split_type is not None:
try:
# Working around https://github.com/adafruit/circuitpython/issues/1769
self._hid_helper_inst.create_report([]).send()
self.is_master = True
if self.split_side == "Left":
self.split_master_left = self._master_half()
elif self.split_side == "Right":
self.split_master_left = not self._master_half()
# Sleep 2s so master portion doesn't "appear" to boot quicker than
# dependent portions (which will take ~2s to time out on the HID send)
sleep_ms(2000)
except OSError:
self.is_master = False
if self.split_flip and not self.is_master:
self.col_pins = list(reversed(self.col_pins))
if self.split_side == "Left":
self.split_master_left = self.is_master
elif self.split_side == "Right":
self.split_master_left = not self.is_master
else:
self.is_master = True
if self.uart_pin is not None:
self.uart = self.init_uart(self.uart_pin)
@ -211,13 +285,13 @@ class Firmware:
if not isinstance(k, tuple):
del self.leader_dictionary[k]
if self.debug_enabled:
print("Firin' lazers. Keyboard is booted.")
gc.collect()
self._print_debug_cycle(init=True)
while True:
state_changed = False
if self.split_type is not None and self._master_half:
if self.split_type is not None and self.is_master:
update = self._receive_from_slave()
if update is not None:
self._handle_matrix_report(update)
@ -226,7 +300,7 @@ class Firmware:
update = self.matrix.scan_for_changes()
if update is not None:
if self._master_half():
if self.is_master:
self._handle_matrix_report(update)
state_changed = True
else:
@ -246,5 +320,5 @@ class Firmware:
if self._state.hid_pending:
self._send_hid()
if self.debug_enabled and state_changed:
print('New State: {}'.format(self._state._to_dict()))
if state_changed:
self._print_debug_cycle()

View File

@ -3,24 +3,31 @@ from kmk.kmktime import ticks_diff, ticks_ms
def df_pressed(key, state, *args, **kwargs):
"""Switches the default layer"""
state.active_layers[0] = key.meta.layer
state.reversed_active_layers = list(reversed(state.active_layers))
state.active_layers[-1] = key.meta.layer
return state
def mo_pressed(key, state, *args, **kwargs):
"""Momentarily activates layer, switches off when you let go"""
state.active_layers.append(key.meta.layer)
state.reversed_active_layers = list(reversed(state.active_layers))
state.active_layers.insert(0, key.meta.layer)
return state
def mo_released(key, state, KC, *args, **kwargs):
state.active_layers = [
layer for layer in state.active_layers
if layer != key.meta.layer
]
state.reversed_active_layers = list(reversed(state.active_layers))
# remove the first instance of the target layer
# from the active list
# under almost all normal use cases, this will
# disable the layer (but preserve it if it was triggered
# as a default layer, etc.)
# this also resolves an issue where using DF() on a layer
# triggered by MO() and then defaulting to the MO()'s layer
# would result in no layers active
try:
del_idx = state.active_layers.index(key.meta.layer)
del state.active_layers[del_idx]
except ValueError:
pass
return state
@ -62,23 +69,21 @@ def lt_released(key, state, *args, **kwargs):
def tg_pressed(key, state, *args, **kwargs):
"""Toggles the layer (enables it if not active, and vise versa)"""
if key.meta.layer in state.active_layers:
state.active_layers = [
layer for layer in state.active_layers
if layer != key.meta.layer
]
else:
state.active_layers.append(key.meta.layer)
state.reversed_active_layers = list(reversed(state.active_layers))
# See mo_released for implementation details around this
try:
del_idx = state.active_layers.index(key.meta.layer)
del state.active_layers[del_idx]
except ValueError:
state.active_layers.insert(0, key.meta.layer)
return state
def to_pressed(key, state, *args, **kwargs):
"""Activates layer and deactivates all other layers"""
state.active_layers = [key.meta.layer]
state.reversed_active_layers = list(reversed(state.active_layers))
state.active_layers.clear()
state.active_layers.insert(0, key.meta.layer)
return state

View File

@ -38,9 +38,9 @@ def bootloader(*args, **kwargs):
def debug_pressed(key, state, KC, *args, **kwargs):
if state.config.debug_enabled:
print('Disabling debug mode, bye!')
print('DebugDisable()')
else:
print('Enabling debug mode. Welcome to the jungle.')
print('DebugEnable()')
state.config.debug_enabled = not state.config.debug_enabled

View File

@ -21,6 +21,12 @@ class USB_HID:
self.post_init()
def __repr__(self):
return '{}(REPORT_BYTES={})'.format(
self.__class__.__name__,
self.REPORT_BYTES,
)
def post_init(self):
pass

View File

@ -12,8 +12,12 @@ class InternalState:
leader_last_len = 0
hid_pending = False
leader_mode_history = []
# this should almost always be PREpended to, replaces
# former use of reversed_active_layers which had pointless
# overhead (the underlying list was never used anyway)
active_layers = [0]
reversed_active_layers = list(reversed(active_layers))
start_time = {
'lt': None,
'tg': None,
@ -30,21 +34,35 @@ class InternalState:
self.config = config
def __repr__(self):
return 'InternalState({})'.format(self._to_dict())
def _to_dict(self):
ret = {
'keys_pressed': self.keys_pressed,
'active_layers': self.active_layers,
'leader_mode_history': self.leader_mode_history,
'leader_mode': self.config.leader_mode,
'start_time': self.start_time,
'tapping': self.tapping,
'tap_dance_counts': self.tap_dance_counts,
'timeouts': self.timeouts,
}
return ret
return (
'InternalState('
'keys_pressed={} '
'coord_keys_pressed={} '
'leader_pending={} '
'leader_last_len={} '
'hid_pending={} '
'leader_mode_history={} '
'active_layers={} '
'start_time={} '
'timeouts={} '
'tapping={} '
'tap_dance_counts={} '
'tap_side_effects={}'
')'
).format(
self.keys_pressed,
self.coord_keys_pressed,
self.leader_pending,
self.leader_last_len,
self.hid_pending,
self.leader_mode_history,
self.active_layers,
self.start_time,
self.timeouts,
self.tapping,
self.tap_dance_counts,
self.tap_side_effects,
)
def _find_key_in_map(self, row, col):
ic = intify_coordinate(row, col)
@ -54,7 +72,7 @@ class InternalState:
except ValueError:
if self.config.debug_enabled:
print(
'No coord_mapping index for value {}, row={} col={}'.format(
'CoordMappingNotFound(ic={}, row={}, col={})'.format(
ic,
row,
col,
@ -63,16 +81,14 @@ class InternalState:
return None
# Later-added layers have priority. Sift through the layers
# in reverse order until we find a valid keycode object
for layer in self.reversed_active_layers:
for layer in self.active_layers:
layer_key = self.config.keymap[layer][idx]
if not layer_key or layer_key == KC.TRNS:
continue
if self.config.debug_enabled:
print('Resolved key: {}'.format(layer_key))
print('KeyResolution(key={})'.format(layer_key))
return layer_key
@ -112,15 +128,17 @@ class InternalState:
def matrix_changed(self, row, col, is_pressed):
if self.config.debug_enabled:
print('Matrix changed (col, row, pressed?): {}, {}, {}'.format(
col, row, is_pressed,
print('MatrixChange(col={} row={} pressed={})'.format(
col,
row,
is_pressed,
))
int_coord = intify_coordinate(row, col)
kc_changed = self._find_key_in_map(row, col)
if kc_changed is None:
print('No key accessible for col, row: {}, {}'.format(row, col))
print('MatrixUndefinedCoordinate(col={} row={})'.format(col, row))
return self
return self.process_key(kc_changed, is_pressed, int_coord, (row, col))

32
kmk/key_validators.py Normal file
View File

@ -0,0 +1,32 @@
from kmk.types import (KeySeqSleepMeta, LayerKeyMeta, ModTapKeyMeta,
TapDanceKeyMeta, UnicodeModeKeyMeta)
def key_seq_sleep_validator(ms):
return KeySeqSleepMeta(ms)
def layer_key_validator(layer, kc=None):
'''
Validates the syntax (but not semantics) of a layer key call. We won't
have access to the keymap here, so we can't verify much of anything useful
here (like whether the target layer actually exists). The spirit of this
existing is mostly that Python will catch extraneous args/kwargs and error
out.
'''
return LayerKeyMeta(layer=layer, kc=kc)
def mod_tap_validator(kc, mods=None):
'''
Validates that mod tap keys are correctly used
'''
return ModTapKeyMeta(kc=kc, mods=mods)
def tap_dance_key_validator(*codes):
return TapDanceKeyMeta(codes)
def unicode_mode_key_validator(mode):
return UnicodeModeKeyMeta(mode)

View File

@ -1,11 +1,11 @@
import gc
import kmk.handlers.layers as layers
import kmk.handlers.modtap as modtap
import kmk.handlers.stock as handlers
from kmk.consts import UnicodeMode
from kmk.types import (AttrDict, KeySeqSleepMeta, LayerKeyMeta, ModTapKeyMeta,
TapDanceKeyMeta, UnicodeModeKeyMeta)
from kmk.key_validators import (key_seq_sleep_validator, layer_key_validator,
mod_tap_validator, tap_dance_key_validator,
unicode_mode_key_validator)
from kmk.types import AttrDict, UnicodeModeKeyMeta
FIRST_KMK_INTERNAL_KEY = 1000
NEXT_AVAILABLE_KEY = 1000
@ -367,8 +367,6 @@ def make_argumented_key(
return _argumented_key
gc.collect()
# Modifiers
make_mod_key(code=0x01, names=('LEFT_CONTROL', 'LCTRL', 'LCTL'))
make_mod_key(code=0x02, names=('LEFT_SHIFT', 'LSHIFT', 'LSFT'))
@ -383,8 +381,6 @@ make_mod_key(code=0x07, names=('MEH',))
# HYPR = LCTL | LALT | LSFT | LGUI
make_mod_key(code=0x0F, names=('HYPER', 'HYPR'))
gc.collect()
# Basic ASCII letters
make_key(code=4, names=('A',))
make_key(code=5, names=('B',))
@ -413,8 +409,6 @@ make_key(code=27, names=('X',))
make_key(code=28, names=('Y',))
make_key(code=29, names=('Z',))
gc.collect()
# Numbers
# Aliases to play nicely with AttrDict, since KC.1 isn't a valid
# attribute key in Python, but KC.N1 is
@ -429,8 +423,6 @@ make_key(code=37, names=('8', 'N8'))
make_key(code=38, names=('9', 'N9'))
make_key(code=39, names=('0', 'N0'))
gc.collect()
# More ASCII standard keys
make_key(code=40, names=('ENTER', 'ENT', "\n"))
make_key(code=41, names=('ESCAPE', 'ESC'))
@ -449,8 +441,6 @@ make_key(code=54, names=('COMMA', 'COMM', ','))
make_key(code=55, names=('DOT', '.'))
make_key(code=56, names=('SLASH', 'SLSH'))
gc.collect()
# Function Keys
make_key(code=58, names=('F1',))
make_key(code=59, names=('F2',))
@ -477,8 +467,6 @@ make_key(code=113, names=('F22',))
make_key(code=114, names=('F23',))
make_key(code=115, names=('F24',))
gc.collect()
# Lock Keys, Navigation, etc.
make_key(code=57, names=('CAPS_LOCK', 'CAPSLOCK', 'CLCK', 'CAPS'))
# FIXME: Investigate whether this key actually works, and
@ -501,8 +489,6 @@ make_key(code=80, names=('LEFT',))
make_key(code=81, names=('DOWN',))
make_key(code=82, names=('UP',))
gc.collect()
# Numpad
make_key(code=83, names=('NUM_LOCK', 'NUMLOCK', 'NLCK'))
# FIXME: Investigate whether this key actually works, and
@ -528,8 +514,6 @@ make_key(code=103, names=('KP_EQUAL', 'PEQL', 'NUMPAD_EQUAL'))
make_key(code=133, names=('KP_COMMA', 'PCMM', 'NUMPAD_COMMA'))
make_key(code=134, names=('KP_EQUAL_AS400', 'NUMPAD_EQUAL_AS400'))
gc.collect()
# Making life better for folks on tiny keyboards especially: exposes
# the "shifted" keys as raw keys. Under the hood we're still
# sending Shift+(whatever key is normally pressed) to get these, so
@ -556,8 +540,6 @@ make_shifted_key('COMMA', names=('LEFT_ANGLE_BRACKET', 'LABK', '<'))
make_shifted_key('DOT', names=('RIGHT_ANGLE_BRACKET', 'RABK', '>'))
make_shifted_key('SLSH', names=('QUESTION', 'QUES', '?'))
gc.collect()
# International
make_key(code=50, names=('NONUS_HASH', 'NUHS'))
make_key(code=100, names=('NONUS_BSLASH', 'NUBS'))
@ -582,8 +564,6 @@ make_key(code=150, names=('LANG7',))
make_key(code=151, names=('LANG8',))
make_key(code=152, names=('LANG9',))
gc.collect()
# Consumer ("media") keys. Most known keys aren't supported here. A much
# longer list used to exist in this file, but the codes were almost certainly
# incorrect, conflicting with each other, or otherwise "weird". We'll add them
@ -605,8 +585,6 @@ make_consumer_key(code=184, names=('MEDIA_EJECT', 'EJCT')) # 0xB8
make_consumer_key(code=179, names=('MEDIA_FAST_FORWARD', 'MFFD')) # 0xB3
make_consumer_key(code=180, names=('MEDIA_REWIND', 'MRWD')) # 0xB4
gc.collect()
# Internal, diagnostic, or auxiliary/enhanced keys
# NO and TRNS are functionally identical in how they (don't) mutate
@ -631,18 +609,6 @@ make_key(
on_release=handlers.passthrough,
)
def layer_key_validator(layer, kc=None):
'''
Validates the syntax (but not semantics) of a layer key call. We won't
have access to the keymap here, so we can't verify much of anything useful
here (like whether the target layer actually exists). The spirit of this
existing is mostly that Python will catch extraneous args/kwargs and error
out.
'''
return LayerKeyMeta(layer=layer, kc=kc)
# Layers
make_argumented_key(
validator=layer_key_validator,
@ -684,15 +650,6 @@ make_argumented_key(
on_release=layers.tt_released,
)
def mod_tap_validator(kc, mods=None):
'''
Validates that mod tap keys are correctly used
'''
return ModTapKeyMeta(kc=kc, mods=mods)
# ModTap
make_argumented_key(
validator=mod_tap_validator,
names=('MT',),
@ -700,14 +657,6 @@ make_argumented_key(
on_release=modtap.mt_released,
)
gc.collect()
def key_seq_sleep_validator(ms):
return KeySeqSleepMeta(ms)
# A dummy key to trigger a sleep_ms call in a sequence of other keys in a
# simple sequence macro.
make_argumented_key(
@ -716,8 +665,6 @@ make_argumented_key(
on_press=handlers.sleep_pressed,
)
# Switch unicode modes at runtime
make_key(
names=('UC_MODE_NOOP', 'UC_DISABLE'),
meta=UnicodeModeKeyMeta(UnicodeMode.NOOP),
@ -738,22 +685,14 @@ make_key(
meta=UnicodeModeKeyMeta(UnicodeMode.WINC),
on_press=handlers.uc_mode_pressed,
)
def unicode_mode_key_validator(mode):
return UnicodeModeKeyMeta(mode)
make_argumented_key(
validator=unicode_mode_key_validator,
names=('UC_MODE',),
on_press=handlers.uc_mode_pressed,
)
# Tap Dance
make_argumented_key(
validator=lambda *codes: TapDanceKeyMeta(codes),
validator=tap_dance_key_validator,
names=('TAP_DANCE', 'TD'),
on_press=handlers.td_pressed,
on_release=handlers.td_released,

View File

@ -6,7 +6,7 @@ from kmk.keys import KC
keyboard = Firmware()
keyboard.debug_enabled = True
keyboard.debug_enabled = False
keyboard.unicode_mode = UnicodeMode.LINUX
keyboard.tap_time = 750