diff --git a/tests/layer_cache/config.h b/tests/layer_cache/config.h new file mode 100644 index 0000000000..656cedb338 --- /dev/null +++ b/tests/layer_cache/config.h @@ -0,0 +1,21 @@ +/* Copyright 2018 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#define MATRIX_ROWS 2 +#define MATRIX_COLS 2 +#define PREVENT_STUCK_MODIFIERS diff --git a/tests/layer_cache/keymap.c b/tests/layer_cache/keymap.c new file mode 100644 index 0000000000..d310bff8b9 --- /dev/null +++ b/tests/layer_cache/keymap.c @@ -0,0 +1,24 @@ +/* Copyright 2018 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "quantum.h" + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + [0] = { + {KC_A, KC_B}, + {KC_C, KC_D}, + } +}; diff --git a/tests/layer_cache/rules.mk b/tests/layer_cache/rules.mk new file mode 100644 index 0000000000..9b8615f954 --- /dev/null +++ b/tests/layer_cache/rules.mk @@ -0,0 +1,16 @@ +# Copyright 2018 Fred Sundvik +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +CUSTOM_MATRIX=yes diff --git a/tests/layer_cache/test_layer_cache.cpp b/tests/layer_cache/test_layer_cache.cpp new file mode 100644 index 0000000000..4aebe24b74 --- /dev/null +++ b/tests/layer_cache/test_layer_cache.cpp @@ -0,0 +1,140 @@ +/* Copyright 2018 Fred Sundvik + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "test_common.hpp" + +#if MAX_LAYER_BITS != 5 +#error "Tese tests assume that the MAX_LAYER_BITS is equal to 5" +// If this is changed, change the constants below +#endif + +#if MATRIX_COLS != 2 || MATRIX_ROWS !=2 +#error "These tests assume that the second row starts after the second column" +#endif + +namespace +{ + constexpr uint8_t max_layer_value = 0b11111; + constexpr uint8_t min_layer_value = 0; + constexpr uint8_t alternating_starting_with_1 = 0b10101; + constexpr uint8_t alternating_starting_with_0 = 0b01010; + + + uint8_t read_cache(uint8_t col, uint8_t row) { + keypos_t key; + key.col = col; + key.row = row; + return read_source_layers_cache(key); + } + + void write_cache(uint8_t col, uint8_t row, uint8_t value) { + keypos_t key; + key.col = col; + key.row = row; + return update_source_layers_cache(key, value); + } + + void fill_cache() { + for (int i=0; i < MATRIX_ROWS; i++) { + for (int j=0; j < MATRIX_COLS; j++) { + write_cache(j, i, max_layer_value); + } + } + } + + void clear_cache() { + for (int i=0; i < MATRIX_ROWS; i++) { + for (int j=0; j < MATRIX_COLS; j++) { + write_cache(j, i, min_layer_value); + } + } + } +} + +class LayerCache : public testing::Test +{ +public: + LayerCache() + { + clear_cache(); + } +}; + +TEST_F(LayerCache, LayerCacheIsInitializedToZero) { + for (int i=0; i < MATRIX_ROWS; i++) { + for (int j=0; j < MATRIX_COLS; j++) { + EXPECT_EQ(read_cache(j, i), min_layer_value); + } + } +} + +TEST_F(LayerCache, FillAndClearCache) { + fill_cache(); + clear_cache(); + for (int i=0; i < MATRIX_ROWS; i++) { + for (int j=0; j < MATRIX_COLS; j++) { + EXPECT_EQ(read_cache(j, i), min_layer_value); + } + } +} + +TEST_F(LayerCache, WriteAndReadFirstPosMaximumValue) { + write_cache(0, 0, max_layer_value); + EXPECT_EQ(read_cache(0, 0), max_layer_value); + // The second position should not be updated + EXPECT_EQ(read_cache(1, 0), min_layer_value); + EXPECT_EQ(read_cache(0, 1), min_layer_value); +} + +TEST_F(LayerCache, WriteAndReadSecondPosMaximumValue) { + write_cache(1, 0, max_layer_value); + EXPECT_EQ(read_cache(1, 0), max_layer_value); + // The surrounding positions should not be updated + EXPECT_EQ(read_cache(0, 0), min_layer_value); + EXPECT_EQ(read_cache(0, 1), min_layer_value); +} + +TEST_F(LayerCache, WriteAndReadFirstPosAlternatingBitsStartingWith1) { + write_cache(0, 0, alternating_starting_with_1); + EXPECT_EQ(read_cache(0, 0), alternating_starting_with_1); + // The second position should not be updated + EXPECT_EQ(read_cache(1, 0), min_layer_value); + EXPECT_EQ(read_cache(0, 1), min_layer_value); +} + +TEST_F(LayerCache, WriteAndReadSecondPosAlternatingBitsStartingWith1) { + write_cache(1, 0, alternating_starting_with_1); + EXPECT_EQ(read_cache(1, 0), alternating_starting_with_1); + // The surrounding positions should not be updated + EXPECT_EQ(read_cache(0, 0), min_layer_value); + EXPECT_EQ(read_cache(0, 1), min_layer_value); +} + +TEST_F(LayerCache, WriteAndReadFirstPosAlternatingBitsStartingWith0) { + write_cache(0, 0, alternating_starting_with_0); + EXPECT_EQ(read_cache(0, 0), alternating_starting_with_0); + // The second position should not be updated + EXPECT_EQ(read_cache(1, 0), min_layer_value); + EXPECT_EQ(read_cache(0, 1), min_layer_value); +} + +TEST_F(LayerCache, WriteAndReadSecondPosAlternatingBitsStartingWith0) { + write_cache(1, 0, alternating_starting_with_0); + EXPECT_EQ(read_cache(1, 0), alternating_starting_with_0); + // The surrounding positions should not be updated + EXPECT_EQ(read_cache(0, 0), min_layer_value); + EXPECT_EQ(read_cache(0, 1), min_layer_value); +} diff --git a/tmk_core/common/action_layer.c b/tmk_core/common/action_layer.c index f3cd381ab0..a1ead402c8 100644 --- a/tmk_core/common/action_layer.c +++ b/tmk_core/common/action_layer.c @@ -220,37 +220,72 @@ void layer_debug(void) #endif #if !defined(NO_ACTION_LAYER) && defined(PREVENT_STUCK_MODIFIERS) -uint8_t source_layers_cache[(MATRIX_ROWS * MATRIX_COLS + 7) / 8][MAX_LAYER_BITS] = {{0}}; +uint8_t source_layers_cache[(MATRIX_ROWS * MATRIX_COLS * MAX_LAYER_BITS + 7) / 8] = {0}; +static const uint8_t layer_cache_mask = (1u << MAX_LAYER_BITS) - 1; void update_source_layers_cache(keypos_t key, uint8_t layer) { - const uint8_t key_number = key.col + (key.row * MATRIX_COLS); - const uint8_t storage_row = key_number / 8; - const uint8_t storage_bit = key_number % 8; + const uint16_t key_number = key.col + (key.row * MATRIX_COLS); + const uint32_t bit_number = key_number * MAX_LAYER_BITS; + const uint16_t byte_number = bit_number / 8; + if (byte_number >= sizeof(source_layers_cache)) { + return; + } + const uint8_t bit_position = bit_number % 8; + int8_t shift = 16 - MAX_LAYER_BITS - bit_position; - for (uint8_t bit_number = 0; bit_number < MAX_LAYER_BITS; bit_number++) { - source_layers_cache[storage_row][bit_number] ^= - (-((layer & (1U << bit_number)) != 0) - ^ source_layers_cache[storage_row][bit_number]) - & (1U << storage_bit); + if (shift > 8 ) { + // We need to write only one byte + shift -= 8; + const uint8_t mask = layer_cache_mask << shift; + const uint8_t shifted_layer = layer << shift; + source_layers_cache[byte_number] = (shifted_layer & mask) | (source_layers_cache[byte_number] & (~mask)); + } else { + if (byte_number + 1 >= sizeof(source_layers_cache)) { + return; } + // We need to write two bytes + uint16_t value = layer; + uint16_t mask = layer_cache_mask; + value <<= shift; + mask <<= shift; + + uint16_t masked_value = value & mask; + uint16_t inverse_mask = ~mask; + + // This could potentially be done with a single write, but then we have to assume the endian + source_layers_cache[byte_number + 1] = masked_value | (source_layers_cache[byte_number + 1] & (inverse_mask)); + masked_value >>= 8; + inverse_mask >>= 8; + source_layers_cache[byte_number] = masked_value | (source_layers_cache[byte_number] & (inverse_mask)); + } } uint8_t read_source_layers_cache(keypos_t key) { - const uint8_t key_number = key.col + (key.row * MATRIX_COLS); - const uint8_t storage_row = key_number / 8; - const uint8_t storage_bit = key_number % 8; - uint8_t layer = 0; + const uint16_t key_number = key.col + (key.row * MATRIX_COLS); + const uint32_t bit_number = key_number * MAX_LAYER_BITS; + const uint16_t byte_number = bit_number / 8; + if (byte_number >= sizeof(source_layers_cache)) { + return 0; + } + const uint8_t bit_position = bit_number % 8; - for (uint8_t bit_number = 0; bit_number < MAX_LAYER_BITS; bit_number++) { - layer |= - ((source_layers_cache[storage_row][bit_number] - & (1U << storage_bit)) != 0) - << bit_number; + int8_t shift = 16 - MAX_LAYER_BITS - bit_position; + + if (shift > 8 ) { + // We need to read only one byte + shift -= 8; + return (source_layers_cache[byte_number] >> shift) & layer_cache_mask; + } else { + if (byte_number + 1 >= sizeof(source_layers_cache)) { + return 0; } - - return layer; + // Otherwise read two bytes + // This could potentially be done with a single read, but then we have to assume the endian + uint16_t value = source_layers_cache[byte_number] << 8 | source_layers_cache[byte_number + 1]; + return (value >> shift) & layer_cache_mask; + } } #endif