diff options
| author | Stephen Ostermiller <stephenostermiller@users.noreply.github.com> | 2025-11-23 06:32:36 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-23 22:32:36 +1100 |
| commit | 1a954e8da5dcbd81eeccb9d6ac41b6eda64d7b85 (patch) | |
| tree | 4f09599d467a9c9bbb1dde722511139fa965ff03 /quantum/process_keycode | |
| parent | c7e17538eea98540dbee71de5d024db99f7786fe (diff) | |
Reduce tap dance memory usage, move state out of data (#25415)
* Use less tap dance memory.
Use dynamically allocated sparse array for tap dance state, dynamically allocate tap dance state when needed and free it when the tap dance is done.
* new approach
* Use null, check for null
* Reformat with docker
* Use uint8 with idx rather than uint16 with keycode in state
* fix accidental change
* reformat
* Add null check
* add documentation tip suggested by tzarc
* Only allow tap dance state allocation on key down, not on key up
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
* Only allow tap dance allocation on key down, not on key up
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
* add user action required section
---------
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
Diffstat (limited to 'quantum/process_keycode')
| -rw-r--r-- | quantum/process_keycode/process_tap_dance.c | 122 | ||||
| -rw-r--r-- | quantum/process_keycode/process_tap_dance.h | 11 |
2 files changed, 93 insertions, 40 deletions
diff --git a/quantum/process_keycode/process_tap_dance.c b/quantum/process_keycode/process_tap_dance.c index 11df62763d..36a94d8d28 100644 --- a/quantum/process_keycode/process_tap_dance.c +++ b/quantum/process_keycode/process_tap_dance.c | |||
| @@ -24,8 +24,46 @@ | |||
| 24 | #include "keymap_introspection.h" | 24 | #include "keymap_introspection.h" |
| 25 | 25 | ||
| 26 | static uint16_t active_td; | 26 | static uint16_t active_td; |
| 27 | |||
| 28 | #ifndef TAP_DANCE_MAX_SIMULTANEOUS | ||
| 29 | # define TAP_DANCE_MAX_SIMULTANEOUS 3 | ||
| 30 | #endif | ||
| 31 | |||
| 32 | static tap_dance_state_t tap_dance_states[TAP_DANCE_MAX_SIMULTANEOUS]; | ||
| 33 | |||
| 27 | static uint16_t last_tap_time; | 34 | static uint16_t last_tap_time; |
| 28 | 35 | ||
| 36 | static tap_dance_state_t *tap_dance_get_or_allocate_state(uint8_t tap_dance_idx, bool allocate) { | ||
| 37 | uint8_t i; | ||
| 38 | if (tap_dance_idx >= tap_dance_count()) { | ||
| 39 | return NULL; | ||
| 40 | } | ||
| 41 | // Search for a state already used for this keycode | ||
| 42 | for (i = 0; i < TAP_DANCE_MAX_SIMULTANEOUS; i++) { | ||
| 43 | if (tap_dance_states[i].in_use && tap_dance_states[i].index == tap_dance_idx) { | ||
| 44 | return &tap_dance_states[i]; | ||
| 45 | } | ||
| 46 | } | ||
| 47 | // No existing state found; bail out if new state allocation is not allowed | ||
| 48 | if (!allocate) { | ||
| 49 | return NULL; | ||
| 50 | } | ||
| 51 | // Search for the first available state | ||
| 52 | for (i = 0; i < TAP_DANCE_MAX_SIMULTANEOUS; i++) { | ||
| 53 | if (!tap_dance_states[i].in_use) { | ||
| 54 | tap_dance_states[i].index = tap_dance_idx; | ||
| 55 | tap_dance_states[i].in_use = true; | ||
| 56 | return &tap_dance_states[i]; | ||
| 57 | } | ||
| 58 | } | ||
| 59 | // No states are available, tap dance won't happen | ||
| 60 | return NULL; | ||
| 61 | } | ||
| 62 | |||
| 63 | tap_dance_state_t *tap_dance_get_state(uint8_t tap_dance_idx) { | ||
| 64 | return tap_dance_get_or_allocate_state(tap_dance_idx, false); | ||
| 65 | } | ||
| 66 | |||
| 29 | void tap_dance_pair_on_each_tap(tap_dance_state_t *state, void *user_data) { | 67 | void tap_dance_pair_on_each_tap(tap_dance_state_t *state, void *user_data) { |
| 30 | tap_dance_pair_t *pair = (tap_dance_pair_t *)user_data; | 68 | tap_dance_pair_t *pair = (tap_dance_pair_t *)user_data; |
| 31 | 69 | ||
| @@ -86,58 +124,64 @@ static inline void _process_tap_dance_action_fn(tap_dance_state_t *state, void * | |||
| 86 | } | 124 | } |
| 87 | } | 125 | } |
| 88 | 126 | ||
| 89 | static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *action) { | 127 | static inline void process_tap_dance_action_on_each_tap(tap_dance_action_t *action, tap_dance_state_t *state) { |
| 90 | action->state.count++; | 128 | state->count++; |
| 91 | action->state.weak_mods = get_mods(); | 129 | state->weak_mods = get_mods(); |
| 92 | action->state.weak_mods |= get_weak_mods(); | 130 | state->weak_mods |= get_weak_mods(); |
| 93 | #ifndef NO_ACTION_ONESHOT | 131 | #ifndef NO_ACTION_ONESHOT |
| 94 | action->state.oneshot_mods = get_oneshot_mods(); | 132 | state->oneshot_mods = get_oneshot_mods(); |
| 95 | #endif | 133 | #endif |
| 96 | _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_tap); | 134 | _process_tap_dance_action_fn(state, action->user_data, action->fn.on_each_tap); |
| 97 | } | 135 | } |
| 98 | 136 | ||
| 99 | static inline void process_tap_dance_action_on_each_release(tap_dance_action_t *action) { | 137 | static inline void process_tap_dance_action_on_each_release(tap_dance_action_t *action, tap_dance_state_t *state) { |
| 100 | _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_each_release); | 138 | _process_tap_dance_action_fn(state, action->user_data, action->fn.on_each_release); |
| 101 | } | 139 | } |
| 102 | 140 | ||
| 103 | static inline void process_tap_dance_action_on_reset(tap_dance_action_t *action) { | 141 | static inline void process_tap_dance_action_on_reset(tap_dance_action_t *action, tap_dance_state_t *state) { |
| 104 | _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_reset); | 142 | _process_tap_dance_action_fn(state, action->user_data, action->fn.on_reset); |
| 105 | del_weak_mods(action->state.weak_mods); | 143 | del_weak_mods(state->weak_mods); |
| 106 | #ifndef NO_ACTION_ONESHOT | 144 | #ifndef NO_ACTION_ONESHOT |
| 107 | del_mods(action->state.oneshot_mods); | 145 | del_mods(state->oneshot_mods); |
| 108 | #endif | 146 | #endif |
| 109 | send_keyboard_report(); | 147 | send_keyboard_report(); |
| 110 | action->state = (const tap_dance_state_t){0}; | 148 | // Clear the tap dance state and mark it as unused |
| 149 | memset(state, 0, sizeof(tap_dance_state_t)); | ||
| 111 | } | 150 | } |
| 112 | 151 | ||
| 113 | static inline void process_tap_dance_action_on_dance_finished(tap_dance_action_t *action) { | 152 | static inline void process_tap_dance_action_on_dance_finished(tap_dance_action_t *action, tap_dance_state_t *state) { |
| 114 | if (!action->state.finished) { | 153 | if (!state->finished) { |
| 115 | action->state.finished = true; | 154 | state->finished = true; |
| 116 | add_weak_mods(action->state.weak_mods); | 155 | add_weak_mods(state->weak_mods); |
| 117 | #ifndef NO_ACTION_ONESHOT | 156 | #ifndef NO_ACTION_ONESHOT |
| 118 | add_mods(action->state.oneshot_mods); | 157 | add_mods(state->oneshot_mods); |
| 119 | #endif | 158 | #endif |
| 120 | send_keyboard_report(); | 159 | send_keyboard_report(); |
| 121 | _process_tap_dance_action_fn(&action->state, action->user_data, action->fn.on_dance_finished); | 160 | _process_tap_dance_action_fn(state, action->user_data, action->fn.on_dance_finished); |
| 122 | } | 161 | } |
| 123 | active_td = 0; | 162 | active_td = 0; |
| 124 | if (!action->state.pressed) { | 163 | if (!state->pressed) { |
| 125 | // There will not be a key release event, so reset now. | 164 | // There will not be a key release event, so reset now. |
| 126 | process_tap_dance_action_on_reset(action); | 165 | process_tap_dance_action_on_reset(action, state); |
| 127 | } | 166 | } |
| 128 | } | 167 | } |
| 129 | 168 | ||
| 130 | bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { | 169 | bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { |
| 131 | tap_dance_action_t *action; | 170 | tap_dance_action_t *action; |
| 171 | tap_dance_state_t * state; | ||
| 132 | 172 | ||
| 133 | if (!record->event.pressed) return false; | 173 | if (!record->event.pressed) return false; |
| 134 | 174 | ||
| 135 | if (!active_td || keycode == active_td) return false; | 175 | if (!active_td || keycode == active_td) return false; |
| 136 | 176 | ||
| 137 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); | 177 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); |
| 138 | action->state.interrupted = true; | 178 | state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(active_td)); |
| 139 | action->state.interrupting_keycode = keycode; | 179 | if (state == NULL) { |
| 140 | process_tap_dance_action_on_dance_finished(action); | 180 | return false; |
| 181 | } | ||
| 182 | state->interrupted = true; | ||
| 183 | state->interrupting_keycode = keycode; | ||
| 184 | process_tap_dance_action_on_dance_finished(action, state); | ||
| 141 | 185 | ||
| 142 | // Tap dance actions can leave some weak mods active (e.g., if the tap dance is mapped to a keycode with | 186 | // Tap dance actions can leave some weak mods active (e.g., if the tap dance is mapped to a keycode with |
| 143 | // modifiers), but these weak mods should not affect the keypress which interrupted the tap dance. | 187 | // modifiers), but these weak mods should not affect the keypress which interrupted the tap dance. |
| @@ -151,8 +195,9 @@ bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { | |||
| 151 | } | 195 | } |
| 152 | 196 | ||
| 153 | bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { | 197 | bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { |
| 154 | int td_index; | 198 | uint8_t td_index; |
| 155 | tap_dance_action_t *action; | 199 | tap_dance_action_t *action; |
| 200 | tap_dance_state_t * state; | ||
| 156 | 201 | ||
| 157 | switch (keycode) { | 202 | switch (keycode) { |
| 158 | case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: | 203 | case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: |
| @@ -161,16 +206,19 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { | |||
| 161 | return false; | 206 | return false; |
| 162 | } | 207 | } |
| 163 | action = tap_dance_get(td_index); | 208 | action = tap_dance_get(td_index); |
| 164 | 209 | state = tap_dance_get_or_allocate_state(td_index, record->event.pressed); | |
| 165 | action->state.pressed = record->event.pressed; | 210 | if (state == NULL) { |
| 211 | return false; | ||
| 212 | } | ||
| 213 | state->pressed = record->event.pressed; | ||
| 166 | if (record->event.pressed) { | 214 | if (record->event.pressed) { |
| 167 | last_tap_time = timer_read(); | 215 | last_tap_time = timer_read(); |
| 168 | process_tap_dance_action_on_each_tap(action); | 216 | process_tap_dance_action_on_each_tap(action, state); |
| 169 | active_td = action->state.finished ? 0 : keycode; | 217 | active_td = state->finished ? 0 : keycode; |
| 170 | } else { | 218 | } else { |
| 171 | process_tap_dance_action_on_each_release(action); | 219 | process_tap_dance_action_on_each_release(action, state); |
| 172 | if (action->state.finished) { | 220 | if (state->finished) { |
| 173 | process_tap_dance_action_on_reset(action); | 221 | process_tap_dance_action_on_reset(action, state); |
| 174 | if (active_td == keycode) { | 222 | if (active_td == keycode) { |
| 175 | active_td = 0; | 223 | active_td = 0; |
| 176 | } | 224 | } |
| @@ -185,16 +233,18 @@ bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { | |||
| 185 | 233 | ||
| 186 | void tap_dance_task(void) { | 234 | void tap_dance_task(void) { |
| 187 | tap_dance_action_t *action; | 235 | tap_dance_action_t *action; |
| 236 | tap_dance_state_t * state; | ||
| 188 | 237 | ||
| 189 | if (!active_td || timer_elapsed(last_tap_time) <= GET_TAPPING_TERM(active_td, &(keyrecord_t){})) return; | 238 | if (!active_td || timer_elapsed(last_tap_time) <= GET_TAPPING_TERM(active_td, &(keyrecord_t){})) return; |
| 190 | 239 | ||
| 191 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); | 240 | action = tap_dance_get(QK_TAP_DANCE_GET_INDEX(active_td)); |
| 192 | if (!action->state.interrupted) { | 241 | state = tap_dance_get_state(QK_TAP_DANCE_GET_INDEX(active_td)); |
| 193 | process_tap_dance_action_on_dance_finished(action); | 242 | if (state != NULL && !state->interrupted) { |
| 243 | process_tap_dance_action_on_dance_finished(action, state); | ||
| 194 | } | 244 | } |
| 195 | } | 245 | } |
| 196 | 246 | ||
| 197 | void reset_tap_dance(tap_dance_state_t *state) { | 247 | void reset_tap_dance(tap_dance_state_t *state) { |
| 198 | active_td = 0; | 248 | active_td = 0; |
| 199 | process_tap_dance_action_on_reset((tap_dance_action_t *)state); | 249 | process_tap_dance_action_on_reset(tap_dance_get(state->index), state); |
| 200 | } | 250 | } |
diff --git a/quantum/process_keycode/process_tap_dance.h b/quantum/process_keycode/process_tap_dance.h index 5cccbdf439..5a972cee5a 100644 --- a/quantum/process_keycode/process_tap_dance.h +++ b/quantum/process_keycode/process_tap_dance.h | |||
| @@ -28,15 +28,16 @@ typedef struct { | |||
| 28 | #ifndef NO_ACTION_ONESHOT | 28 | #ifndef NO_ACTION_ONESHOT |
| 29 | uint8_t oneshot_mods; | 29 | uint8_t oneshot_mods; |
| 30 | #endif | 30 | #endif |
| 31 | bool pressed : 1; | 31 | bool pressed : 1; |
| 32 | bool finished : 1; | 32 | bool finished : 1; |
| 33 | bool interrupted : 1; | 33 | bool interrupted : 1; |
| 34 | bool in_use : 1; | ||
| 35 | uint8_t index; | ||
| 34 | } tap_dance_state_t; | 36 | } tap_dance_state_t; |
| 35 | 37 | ||
| 36 | typedef void (*tap_dance_user_fn_t)(tap_dance_state_t *state, void *user_data); | 38 | typedef void (*tap_dance_user_fn_t)(tap_dance_state_t *state, void *user_data); |
| 37 | 39 | ||
| 38 | typedef struct tap_dance_action_t { | 40 | typedef struct tap_dance_action_t { |
| 39 | tap_dance_state_t state; | ||
| 40 | struct { | 41 | struct { |
| 41 | tap_dance_user_fn_t on_each_tap; | 42 | tap_dance_user_fn_t on_each_tap; |
| 42 | tap_dance_user_fn_t on_dance_finished; | 43 | tap_dance_user_fn_t on_dance_finished; |
| @@ -80,6 +81,8 @@ typedef struct { | |||
| 80 | 81 | ||
| 81 | void reset_tap_dance(tap_dance_state_t *state); | 82 | void reset_tap_dance(tap_dance_state_t *state); |
| 82 | 83 | ||
| 84 | tap_dance_state_t *tap_dance_get_state(uint8_t tap_dance_idx); | ||
| 85 | |||
| 83 | /* To be used internally */ | 86 | /* To be used internally */ |
| 84 | 87 | ||
| 85 | bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record); | 88 | bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record); |
