From f54e11a9b2604ec0fe2931549773bbec42815397 Mon Sep 17 00:00:00 2001 From: Florian Fleissner Date: Mon, 18 Mar 2019 17:02:11 +0100 Subject: [PATCH] Introduced dynamic LED modes This PR introduces the concept of dynamic LED modes. Those are LED modes whose class instances have a restricted lifetime that lasts only as long as a LED mode is active. By this means it is possible to support a greater amount of LED modes - especially RAM-hungry ones - in the same firmware build. The amount of RAM used to store dynamic LED modes is now bounded by the maximum size (`sizeof(...)`) of the largest dynamic LED mode. Old-style LED modes are furtheron called _static_ in the terminology of this PR. They are still supported and blend in nicely with the newly introduced dynamic LED modes. All changes are entirely backward compatible. No user sketches or existing user plugins require changes. The greatest benefit of this change is that it drastically reduces the consumption of RAM when multiple complex LED modes are used. Currently the most complex stock LED mode is the wavepool effect. Its plugin requires around 140 bytes of RAM that are statically allocated and cannot be shared with any other features. With this change it becomes possible to have a large number of such resource-hungry LED modes in parallel without a significant gain in RAM consumption. For the stock firmware this change means a small (~30 byte) growth in terms of PROGMEM. On the other hand it reduces the amount of statically consumed RAM by ~90 bytes. As the current atmel architectures come with around ten times as much PROGMEM as RAM, this means a great improvement as RAM is the more critical resource. If the wavepool effect, a especially RAM-hungry LED mode is added to the stock firmware, the saving of RAM increases to 160 bytes which is almost 8% of RAM of the Keyboardio Model01. A new interface class `LEDModeInterface` was introduced that those plugins that export dynamic LED modes inherit from. To remain backward compatible, the `LEDMode` class that all pre-existing LED mode plugins inherited from is also derived from `LEDModeInterface`. The new interface class currently lives in header `LEDMode.h` (see information about this new header below). This is because `LEDMode` and `LEDModeInterface` will always be used together by dynamic LED modes. Thus, an extra header for `LEDModeInterface` would only mean extra include work for users writing plugins. Those plugins that export dynamic LED modes must furtheron provide a exported type `DynamicLEDMode`. This can either be done by defining a nested class of that name or by typedef-ing a class that is defined at global scope to `DynamicLEDMode`. See the modified stock LED modes for examples. Some of those plugins that export dynamic led modes require access to their particular dynamic LED mode. By adding the macro `ACCESS_THIS_LED_MODE` to the plugin class definition, additional data and methods (an integer `led_mode_id_) are synthesized, that enable the plugin class to gain access to their particular dynamic LED mode instance (as long as it is active). The synthesized integer member `led_mode_id_` can be used to query if the currently active LED mode is the oned handled by the plugin class instance (note that there might be more than one plugin instance of the same class and thus also several dynamic LED modes, see e.g. the solid color LED mode). A query in the plugin's event handler e.g. looks as follows. ```cpp if (::LEDControl.get_mode_index() != led_mode_id_) return EventHandlerResult::OK; ``` All stock LED modes have been adapted to export dynamic LED modes (if possible). This does not apply to all of them as for some the transition would have provided no gain. It would even have meant a deterioration of resource consumption for those few pre-existing stock LED mode plugins that hardly have no (static) data-members at all (like e.g. `LEDOff`). To reduce the amount of compile unit and header interdependencies, the class `LEDMode` has been moved to a header/implementation file of its own. The `LEDControl` class now does not have a static array anymore to store LED mode pointers. Instead, it delegates the core LED mode handling to a newly introduced `LEDModeManager` class that lives in internal namespace. The `LEDModeManager` class is there to restrict access to LED modes but also to wrap up core LED mode handling. If this functionality would have been added to class `LEDControl`, far too much of the internals of LED mode handling would have been exposed to users through header `LEDControl.h`. The new internal header `array_like_storage.h` contains a template class that is used to generate array-like storages. Here array-like means that the contained pieces of information are stored contiguously in memory in the same way as they would be when defining language intrinsic (C-style) arrays. This type of storage is especially useful to generate array-like data struktures in PROGMEM at compile time based on a list of global objects or POD data. By casting the array-like storage's address to the content's pointer type, an array-like indexed access is possible. In this PR an array-like data structure is used to generate a PROGMEM array of LED mode factories. Array-like data structures could also become useful in other places and for future applications. The most complex part of the implementation of the new LED mode handling is wrapped up in `LEDModeManager.h` and `LEDModeManager.cpp` to hide it from users' site. There, recursive template classes are used to setup an array-like data structure of `LEDModeFactory` instances in PROGMEM. Each of the stored `LEDModeFactory`s are associated with one LED mode-plugin as specified in the sketch. The template mechanism filters out any other plugins unrelated to LED modes. `LEDModeFactory`s thereby handle both static and dynamic LED modes. Class `LEDModeManager` provides access to the LED mode factories and LED modes in general. It exports methods to query the number of LED modes and to activate a LED mode by its mode-ID. Most of this is only available to `LEDControl` that represents the actual user interface. When a dynamic LED mode is activated, a dedicated `LEDModeFactory` generates an instance of the dynamic LED mode class in the LED mode buffer. This buffer is shared by all dynamic LED modes. Its size has been determined at compile time by examining all exported dynamic LED mode types and determining the maximum necessary amount of RAM to store any of those. All LED mode handling related data structures are generated at compile time, based on the list of plugins that are passed to `KALEIDOSCOPE_INIT_PLUGINS(...)`. This function macro invokes a new function macro `_INIT_LED_MODE_MANAGER` from `LEDModeManager.h` that handles the LED mode related stuff. Signed-off-by: Florian Fleissner --- src/Kaleidoscope-LEDControl.h | 1 + src/kaleidoscope/Kaleidoscope.h | 1 + src/kaleidoscope/plugin/Colormap.cpp | 18 +- src/kaleidoscope/plugin/Colormap.h | 29 +- src/kaleidoscope/plugin/FingerPainter.cpp | 1 - src/kaleidoscope/plugin/FingerPainter.h | 5 + src/kaleidoscope/plugin/HardwareTestMode.cpp | 10 +- src/kaleidoscope/plugin/Heatmap.cpp | 48 +- src/kaleidoscope/plugin/Heatmap.h | 42 +- .../plugin/LED-ActiveLayerColor.cpp | 24 +- .../plugin/LED-ActiveLayerColor.h | 37 +- .../plugin/LED-AlphaSquare/Effect.cpp | 29 +- .../plugin/LED-AlphaSquare/Effect.h | 25 +- src/kaleidoscope/plugin/LED-Stalker.cpp | 26 +- src/kaleidoscope/plugin/LED-Stalker.h | 33 +- src/kaleidoscope/plugin/LED-Wavepool.cpp | 56 ++- src/kaleidoscope/plugin/LED-Wavepool.h | 43 +- src/kaleidoscope/plugin/LEDControl.cpp | 93 ++-- src/kaleidoscope/plugin/LEDControl.h | 121 ++--- src/kaleidoscope/plugin/LEDControl/LED-Off.h | 4 + src/kaleidoscope/plugin/LEDEffect-Breathe.cpp | 4 +- src/kaleidoscope/plugin/LEDEffect-Breathe.h | 26 +- src/kaleidoscope/plugin/LEDEffect-Chase.cpp | 6 +- src/kaleidoscope/plugin/LEDEffect-Chase.h | 32 +- src/kaleidoscope/plugin/LEDEffect-Rainbow.cpp | 12 +- src/kaleidoscope/plugin/LEDEffect-Rainbow.h | 70 ++- .../plugin/LEDEffect-SolidColor.cpp | 18 +- .../plugin/LEDEffect-SolidColor.h | 34 +- src/kaleidoscope/plugin/LEDMode.cpp | 30 ++ src/kaleidoscope/plugin/LEDMode.h | 144 ++++++ src/kaleidoscope/plugin/Model01-TestMode.cpp | 11 +- src/kaleidoscope/plugin/TriColor.cpp | 10 +- src/kaleidoscope/plugin/TriColor.h | 25 +- src/kaleidoscope_internal/LEDModeManager.cpp | 112 +++++ src/kaleidoscope_internal/LEDModeManager.h | 474 ++++++++++++++++++ .../array_like_storage.h | 114 +++++ src/kaleidoscope_internal/event_dispatch.h | 6 +- 37 files changed, 1453 insertions(+), 321 deletions(-) create mode 100644 src/kaleidoscope/plugin/LEDMode.cpp create mode 100644 src/kaleidoscope/plugin/LEDMode.h create mode 100644 src/kaleidoscope_internal/LEDModeManager.cpp create mode 100644 src/kaleidoscope_internal/LEDModeManager.h create mode 100644 src/kaleidoscope_internal/array_like_storage.h diff --git a/src/Kaleidoscope-LEDControl.h b/src/Kaleidoscope-LEDControl.h index 3564aa5a..6a1ea252 100644 --- a/src/Kaleidoscope-LEDControl.h +++ b/src/Kaleidoscope-LEDControl.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include #include diff --git a/src/kaleidoscope/Kaleidoscope.h b/src/kaleidoscope/Kaleidoscope.h index d77a6020..20948d42 100644 --- a/src/kaleidoscope/Kaleidoscope.h +++ b/src/kaleidoscope/Kaleidoscope.h @@ -49,6 +49,7 @@ extern HARDWARE_IMPLEMENTATION KeyboardHardware; #include "kaleidoscope/layers.h" #include "kaleidoscope/macro_map.h" #include "kaleidoscope_internal/event_dispatch.h" +#include "kaleidoscope_internal/LEDModeManager.h" #include "kaleidoscope/macro_helpers.h" #include "kaleidoscope/plugin.h" diff --git a/src/kaleidoscope/plugin/Colormap.cpp b/src/kaleidoscope/plugin/Colormap.cpp index ac7b3144..8c000908 100644 --- a/src/kaleidoscope/plugin/Colormap.cpp +++ b/src/kaleidoscope/plugin/Colormap.cpp @@ -38,23 +38,23 @@ void ColormapEffect::max_layers(uint8_t max_) { map_base_ = ::LEDPaletteTheme.reserveThemes(max_layers_); } -void ColormapEffect::onActivate(void) { +void ColormapEffect::TransientLEDMode::onActivate(void) { if (!Kaleidoscope.has_leds) return; - top_layer_ = Layer.top(); - if (top_layer_ <= max_layers_) - ::LEDPaletteTheme.updateHandler(map_base_, top_layer_); + parent_->top_layer_ = Layer.top(); + if (parent_->top_layer_ <= parent_->max_layers_) + ::LEDPaletteTheme.updateHandler(parent_->map_base_, parent_->top_layer_); } -void ColormapEffect::refreshAt(byte row, byte col) { - if (top_layer_ <= max_layers_) - ::LEDPaletteTheme.refreshAt(map_base_, top_layer_, row, col); +void ColormapEffect::TransientLEDMode::refreshAt(byte row, byte col) { + if (parent_->top_layer_ <= parent_->max_layers_) + ::LEDPaletteTheme.refreshAt(parent_->map_base_, parent_->top_layer_, row, col); } EventHandlerResult ColormapEffect::onLayerChange() { - if (::LEDControl.get_mode() == this) - onActivate(); + if (::LEDControl.get_mode_index() == led_mode_id_) + ::LEDControl.get_mode()->onActivate(); return EventHandlerResult::OK; } diff --git a/src/kaleidoscope/plugin/Colormap.h b/src/kaleidoscope/plugin/Colormap.h index 7837a8c3..eedc2db0 100644 --- a/src/kaleidoscope/plugin/Colormap.h +++ b/src/kaleidoscope/plugin/Colormap.h @@ -22,7 +22,9 @@ namespace kaleidoscope { namespace plugin { -class ColormapEffect : public LEDMode { +class ColormapEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: ColormapEffect(void) {} @@ -31,9 +33,28 @@ class ColormapEffect : public LEDMode { EventHandlerResult onLayerChange(); EventHandlerResult onFocusEvent(const char *command); - protected: - void onActivate(void) final; - void refreshAt(byte row, byte col) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const ColormapEffect *parent) : parent_(parent) {} + + protected: + + friend class ColormapEffect; + + virtual void onActivate(void) final; + virtual void refreshAt(byte row, byte col) final; + + private: + + const ColormapEffect *parent_; + }; private: static uint8_t top_layer_; diff --git a/src/kaleidoscope/plugin/FingerPainter.cpp b/src/kaleidoscope/plugin/FingerPainter.cpp index f52c9ed0..622d131d 100644 --- a/src/kaleidoscope/plugin/FingerPainter.cpp +++ b/src/kaleidoscope/plugin/FingerPainter.cpp @@ -30,7 +30,6 @@ bool FingerPainter::edit_mode_; EventHandlerResult FingerPainter::onSetup() { color_base_ = ::LEDPaletteTheme.reserveThemes(1); - ::LEDControl.mode_add(this); return EventHandlerResult::OK; } diff --git a/src/kaleidoscope/plugin/FingerPainter.h b/src/kaleidoscope/plugin/FingerPainter.h index e13e58b3..add9f175 100644 --- a/src/kaleidoscope/plugin/FingerPainter.h +++ b/src/kaleidoscope/plugin/FingerPainter.h @@ -21,6 +21,11 @@ namespace kaleidoscope { namespace plugin { + +// This class is implemented as a persistent LED mode +// as there is no benefit of transforming it into a dynamic +// LED mode in terms of PROGMEM or RAM. +// class FingerPainter : public LEDMode { public: FingerPainter(void) {} diff --git a/src/kaleidoscope/plugin/HardwareTestMode.cpp b/src/kaleidoscope/plugin/HardwareTestMode.cpp index 04aece10..d36f0f9b 100644 --- a/src/kaleidoscope/plugin/HardwareTestMode.cpp +++ b/src/kaleidoscope/plugin/HardwareTestMode.cpp @@ -56,10 +56,18 @@ void HardwareTestMode::testLeds(void) { setLeds(blue); setLeds(green); setLeds(red); + + // This works under the assumption that LEDRainbowEffect + // has been registered with KALEIDOSCOPE_INIT_PLUGINS in + // the sketch. Otherwise LEDRainbowEffect would not be + // known to LEDControl. + // + ::LEDControl.activate(&::LEDRainbowEffect); + // rainbow for 10 seconds ::LEDRainbowEffect.update_delay(5); for (auto i = 0; i < 300; i++) { - ::LEDRainbowEffect.update(); + ::LEDControl.update(); ::LEDControl.syncLeds(); } } diff --git a/src/kaleidoscope/plugin/Heatmap.cpp b/src/kaleidoscope/plugin/Heatmap.cpp index 49096efe..b2a4dcff 100644 --- a/src/kaleidoscope/plugin/Heatmap.cpp +++ b/src/kaleidoscope/plugin/Heatmap.cpp @@ -17,16 +17,11 @@ #include #include +#include namespace kaleidoscope { namespace plugin { -// store the number of times each key has been strock -uint16_t Heatmap::heatmap_[ROWS][COLS]; -// max of heatmap_ (we divide by it so we start at 1) -uint16_t Heatmap::highest_ = 1; -// next heatmap computation time -uint32_t Heatmap::next_heatmap_comp_time_ = 0; // in the cRGB struct the order is blue, green, red (It should be called cBGR…) // default heat_colors black green yellow red static const cRGB heat_colors_default_[] PROGMEM = {{0, 0, 0}, {25, 255, 25}, {25, 255, 255}, {25, 25, 255}}; @@ -37,7 +32,17 @@ uint8_t Heatmap::heat_colors_length = 4; // number of millisecond to wait between each heatmap computation uint16_t Heatmap::update_delay = 1000; -cRGB Heatmap::computeColor(float v) { +Heatmap::TransientLEDMode::TransientLEDMode(const Heatmap *parent) + : parent_(parent), + // store the number of times each key has been strock + heatmap_{}, + // max of heatmap_ (we divide by it so we start at 1) + highest_(1), + // next heatmap computation time + next_heatmap_comp_time_(0) +{} + +cRGB Heatmap::TransientLEDMode::computeColor(float v) { // compute the color corresponding to a value between 0 and 1 /* @@ -95,7 +100,7 @@ cRGB Heatmap::computeColor(float v) { return {b, g, r}; } -void Heatmap::shiftStats(void) { +void Heatmap::TransientLEDMode::shiftStats(void) { // this method is called when: // 1. a value in heatmap_ reach INT8_MAX // 2. highest_ reach heat_colors_length*512 (see Heatmap::loopHook) @@ -112,6 +117,15 @@ void Heatmap::shiftStats(void) { } void Heatmap::resetMap(void) { + + if (::LEDControl.get_mode_index() != led_mode_id_) + return; + + ::LEDControl.get_mode()->resetMap(); +} + +void Heatmap::TransientLEDMode::resetMap() { + // this method can be used as a way to work around an existing bug with a single key // getting special attention or if the user just wants a button to reset the map for (uint8_t r = 0; r < ROWS; r++) { @@ -138,6 +152,15 @@ EventHandlerResult Heatmap::onKeyswitchEvent(Key &mapped_key, byte row, byte col if (!keyToggledOn(key_state)) return EventHandlerResult::OK; + // if the LED mode is not current, skip it + if (::LEDControl.get_mode_index() != led_mode_id_) + return EventHandlerResult::OK; + + return ::LEDControl.get_mode() + ->onKeyswitchEvent(mapped_key, row, col, key_state); +} + +EventHandlerResult Heatmap::TransientLEDMode::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) { // increment the heatmap_ value related to the key heatmap_[row][col]++; @@ -160,6 +183,13 @@ EventHandlerResult Heatmap::beforeEachCycle() { if (!Kaleidoscope.has_leds) return EventHandlerResult::OK; + if (::LEDControl.get_mode_index() != led_mode_id_) + return EventHandlerResult::OK; + + return ::LEDControl.get_mode()->beforeEachCycle(); +} + +EventHandlerResult Heatmap::TransientLEDMode::beforeEachCycle() { // this methode is called frequently by Kaleidoscope // even if the module isn't activated @@ -175,7 +205,7 @@ EventHandlerResult Heatmap::beforeEachCycle() { return EventHandlerResult::OK; } -void Heatmap::update(void) { +void Heatmap::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; diff --git a/src/kaleidoscope/plugin/Heatmap.h b/src/kaleidoscope/plugin/Heatmap.h index cb15da61..82cd61f0 100644 --- a/src/kaleidoscope/plugin/Heatmap.h +++ b/src/kaleidoscope/plugin/Heatmap.h @@ -22,7 +22,9 @@ namespace kaleidoscope { namespace plugin { -class Heatmap : public LEDMode { +class Heatmap : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: Heatmap(void) {} @@ -34,16 +36,38 @@ class Heatmap : public LEDMode { EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state); EventHandlerResult beforeEachCycle(); - protected: - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - private: - static uint16_t heatmap_[ROWS][COLS]; - static uint16_t highest_; - static uint32_t next_heatmap_comp_time_; + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const Heatmap *parent); - static void shiftStats(void); - static cRGB computeColor(float v); + void resetMap(); + EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state); + EventHandlerResult beforeEachCycle(); + + protected: + + virtual void update() final; + + private: + + const Heatmap *parent_; + + uint16_t heatmap_[ROWS][COLS]; + uint16_t highest_; + uint32_t next_heatmap_comp_time_; + + void shiftStats(void); + cRGB computeColor(float v); + + friend class Heatmap; + }; }; } } diff --git a/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp b/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp index ae7fd618..cc424987 100644 --- a/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp +++ b/src/kaleidoscope/plugin/LED-ActiveLayerColor.cpp @@ -20,26 +20,31 @@ namespace kaleidoscope { namespace plugin { -cRGB LEDActiveLayerColorEffect::active_color_; const cRGB *LEDActiveLayerColorEffect::colormap_; +LEDActiveLayerColorEffect::TransientLEDMode::TransientLEDMode( + const LEDActiveLayerColorEffect *parent) + : parent_(parent), + active_color_{0, 0, 0} +{} + void LEDActiveLayerColorEffect::setColormap(const cRGB colormap[]) { colormap_ = colormap; } -cRGB LEDActiveLayerColorEffect::getActiveColor() { +cRGB LEDActiveLayerColorEffect::TransientLEDMode::getActiveColor() { cRGB color; uint8_t top_layer = ::Layer.top(); - color.r = pgm_read_byte(&(colormap_[top_layer].r)); - color.g = pgm_read_byte(&(colormap_[top_layer].g)); - color.b = pgm_read_byte(&(colormap_[top_layer].b)); + color.r = pgm_read_byte(&(parent_->colormap_[top_layer].r)); + color.g = pgm_read_byte(&(parent_->colormap_[top_layer].g)); + color.b = pgm_read_byte(&(parent_->colormap_[top_layer].b)); return color; } -void LEDActiveLayerColorEffect::onActivate(void) { +void LEDActiveLayerColorEffect::TransientLEDMode::onActivate(void) { if (!Kaleidoscope.has_leds) return; @@ -47,13 +52,14 @@ void LEDActiveLayerColorEffect::onActivate(void) { ::LEDControl.set_all_leds_to(active_color_); } -void LEDActiveLayerColorEffect::refreshAt(byte row, byte col) { +void LEDActiveLayerColorEffect::TransientLEDMode::refreshAt(byte row, byte col) { ::LEDControl.setCrgbAt(row, col, active_color_); } EventHandlerResult LEDActiveLayerColorEffect::onLayerChange() { - if (::LEDControl.get_mode() == this) - onActivate(); + if (::LEDControl.get_mode_index() == led_mode_id_) + ::LEDControl.get_mode()->onActivate(); + return EventHandlerResult::OK; } diff --git a/src/kaleidoscope/plugin/LED-ActiveLayerColor.h b/src/kaleidoscope/plugin/LED-ActiveLayerColor.h index 611b6b19..2030e0ed 100644 --- a/src/kaleidoscope/plugin/LED-ActiveLayerColor.h +++ b/src/kaleidoscope/plugin/LED-ActiveLayerColor.h @@ -21,22 +21,45 @@ namespace kaleidoscope { namespace plugin { -class LEDActiveLayerColorEffect : public LEDMode { +class LEDActiveLayerColorEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: LEDActiveLayerColorEffect(void) {} EventHandlerResult onLayerChange(); void setColormap(const cRGB colormap[]); - protected: - void onActivate(void) final; - void refreshAt(byte row, byte col) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDActiveLayerColorEffect *parent); + + protected: + + virtual void onActivate(void) final; + virtual void refreshAt(byte row, byte col) final; + + private: + + const LEDActiveLayerColorEffect *parent_; + + cRGB active_color_; + + cRGB getActiveColor(); + + friend class LEDActiveLayerColorEffect; + }; private: - static const cRGB *colormap_; - static cRGB active_color_; - static cRGB getActiveColor(); + static const cRGB *colormap_; }; } } diff --git a/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp b/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp index 692a7151..6b6d428f 100644 --- a/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp +++ b/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp @@ -21,10 +21,15 @@ namespace kaleidoscope { namespace plugin { uint16_t AlphaSquareEffect::length = 1000; -uint32_t AlphaSquareEffect::end_time_left_, AlphaSquareEffect::end_time_right_; -Key AlphaSquareEffect::last_key_left_, AlphaSquareEffect::last_key_right_; -void AlphaSquareEffect::update(void) { +AlphaSquareEffect::TransientLEDMode::TransientLEDMode(AlphaSquareEffect */*parent*/) + : end_time_left_(0), + end_time_right_(0), + last_key_left_(Key{}), + last_key_right_(Key{}) +{} + +void AlphaSquareEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; @@ -38,7 +43,7 @@ void AlphaSquareEffect::update(void) { } } -void AlphaSquareEffect::refreshAt(byte row, byte col) { +void AlphaSquareEffect::TransientLEDMode::refreshAt(byte row, byte col) { bool timed_out; uint8_t display_col = 2; Key key = last_key_left_; @@ -59,7 +64,7 @@ EventHandlerResult AlphaSquareEffect::onKeyswitchEvent(Key &mappedKey, byte row, if (!Kaleidoscope.has_leds) return EventHandlerResult::OK; - if (::LEDControl.get_mode() != &::AlphaSquareEffect) + if (::LEDControl.get_mode_index() != led_mode_id_) return EventHandlerResult::OK; if (keyState & INJECTED) @@ -72,15 +77,17 @@ EventHandlerResult AlphaSquareEffect::onKeyswitchEvent(Key &mappedKey, byte row, return EventHandlerResult::OK; uint8_t display_col = 2; - Key prev_key = last_key_left_; + auto this_led_mode = ::LEDControl.get_mode(); + + Key prev_key = this_led_mode->last_key_left_; if (col < COLS / 2) { - last_key_left_ = mappedKey; - end_time_left_ = millis() + length; + this_led_mode->last_key_left_ = mappedKey; + this_led_mode->end_time_left_ = millis() + length; } else { - prev_key = last_key_right_; - last_key_right_ = mappedKey; - end_time_right_ = millis() + length; + prev_key = this_led_mode->last_key_right_; + this_led_mode->last_key_right_ = mappedKey; + this_led_mode->end_time_right_ = millis() + length; display_col = 10; } diff --git a/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h b/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h index 43579e6b..57b68645 100644 --- a/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h +++ b/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h @@ -22,7 +22,9 @@ namespace kaleidoscope { namespace plugin { -class AlphaSquareEffect : public LEDMode { +class AlphaSquareEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: AlphaSquareEffect(void) {} @@ -30,13 +32,22 @@ class AlphaSquareEffect : public LEDMode { EventHandlerResult onKeyswitchEvent(Key &mappedKey, byte row, byte col, uint8_t keyState); - protected: - void update(void) final; - void refreshAt(byte row, byte col) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + TransientLEDMode(AlphaSquareEffect *parent); - private: - static uint32_t end_time_left_, end_time_right_; - static Key last_key_left_, last_key_right_; + protected: + void update(void) final; + void refreshAt(byte row, byte col) final; + + private: + uint32_t end_time_left_, end_time_right_; + Key last_key_left_, last_key_right_; + + friend class AlphaSquareEffect; + }; }; } } diff --git a/src/kaleidoscope/plugin/LED-Stalker.cpp b/src/kaleidoscope/plugin/LED-Stalker.cpp index fb1c83c9..d7ea6185 100644 --- a/src/kaleidoscope/plugin/LED-Stalker.cpp +++ b/src/kaleidoscope/plugin/LED-Stalker.cpp @@ -16,21 +16,22 @@ */ #include +#include namespace kaleidoscope { namespace plugin { -uint8_t StalkerEffect::map_[ROWS][COLS]; StalkerEffect::ColorComputer *StalkerEffect::variant; uint16_t StalkerEffect::step_length = 50; -uint16_t StalkerEffect::step_start_time_; cRGB StalkerEffect::inactive_color = (cRGB) { 0, 0, 0 }; -void StalkerEffect::onActivate(void) { - memset(map_, 0, sizeof(map_)); -} +StalkerEffect::TransientLEDMode::TransientLEDMode(const StalkerEffect *parent) + : parent_(parent), + step_start_time_(0), + map_{} +{} EventHandlerResult StalkerEffect::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState) { if (!Kaleidoscope.has_leds) @@ -39,35 +40,38 @@ EventHandlerResult StalkerEffect::onKeyswitchEvent(Key &mapped_key, byte row, by if (row >= ROWS || col >= COLS) return EventHandlerResult::OK; + if (::LEDControl.get_mode_index() != led_mode_id_) + return EventHandlerResult::OK; + if (keyIsPressed(keyState)) { - map_[row][col] = 0xff; + ::LEDControl.get_mode()->map_[row][col] = 0xff; } return EventHandlerResult::OK; } -void StalkerEffect::update(void) { +void StalkerEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; - if (!variant) + if (!parent_->variant) return; uint16_t elapsed = Kaleidoscope.millisAtCycleStart() - step_start_time_; - if (elapsed < step_length) + if (elapsed < parent_->step_length) return; for (byte r = 0; r < ROWS; r++) { for (byte c = 0; c < COLS; c++) { uint8_t step = map_[r][c]; if (step) { - ::LEDControl.setCrgbAt(r, c, variant->compute(&step)); + ::LEDControl.setCrgbAt(r, c, parent_->variant->compute(&step)); } map_[r][c] = step; if (!map_[r][c]) - ::LEDControl.setCrgbAt(r, c, inactive_color); + ::LEDControl.setCrgbAt(r, c, parent_->inactive_color); } } diff --git a/src/kaleidoscope/plugin/LED-Stalker.h b/src/kaleidoscope/plugin/LED-Stalker.h index 80e893e0..dc7ca1a3 100644 --- a/src/kaleidoscope/plugin/LED-Stalker.h +++ b/src/kaleidoscope/plugin/LED-Stalker.h @@ -24,7 +24,9 @@ namespace kaleidoscope { namespace plugin { -class StalkerEffect : public LEDMode { +class StalkerEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: class ColorComputer { public: @@ -39,13 +41,30 @@ class StalkerEffect : public LEDMode { EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState); - protected: - void onActivate(void) final; - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - private: - static uint16_t step_start_time_; - static uint8_t map_[ROWS][COLS]; + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const StalkerEffect *parent); + + protected: + + virtual void update() final; + + private: + + const StalkerEffect *parent_; + + uint16_t step_start_time_; + uint8_t map_[ROWS][COLS]; + + friend class StalkerEffect; + }; }; namespace stalker { diff --git a/src/kaleidoscope/plugin/LED-Wavepool.cpp b/src/kaleidoscope/plugin/LED-Wavepool.cpp index d0a5ee37..767b4ef6 100644 --- a/src/kaleidoscope/plugin/LED-Wavepool.cpp +++ b/src/kaleidoscope/plugin/LED-Wavepool.cpp @@ -27,52 +27,64 @@ namespace plugin { #define MS_PER_FRAME 40 // 40 = 25 fps #define FRAMES_PER_DROP 120 // max time between raindrops during idle animation -int8_t WavepoolEffect::surface[2][WP_WID * WP_HGT]; -uint8_t WavepoolEffect::page = 0; -uint8_t WavepoolEffect::frames_since_event = 0; uint16_t WavepoolEffect::idle_timeout = 5000; // 5 seconds int16_t WavepoolEffect::ripple_hue = WavepoolEffect::rainbow_hue; // automatic hue // map native keyboard coordinates (16x4) into geometric space (14x5) -PROGMEM const uint8_t WavepoolEffect::rc2pos[ROWS * COLS] = { +PROGMEM const uint8_t WavepoolEffect::TransientLEDMode::rc2pos[ROWS * COLS] = { 0, 1, 2, 3, 4, 5, 6, 59, 66, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 34, 60, 65, 35, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 48, 61, 64, 49, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 58, 62, 63, 67, 50, 51, 52, 53, 54, 55, }; +WavepoolEffect::TransientLEDMode::TransientLEDMode(const WavepoolEffect *parent) + : parent_(parent), + frames_since_event_(0), + surface_{}, + page_(0) +{} + EventHandlerResult WavepoolEffect::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) { if (row >= ROWS || col >= COLS) return EventHandlerResult::OK; + if (::LEDControl.get_mode_index() != led_mode_id_) + return EventHandlerResult::OK; + + return ::LEDControl.get_mode() + ->onKeyswitchEvent(mapped_key, row, col, key_state); +} + +EventHandlerResult WavepoolEffect::TransientLEDMode::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) { if (keyIsPressed(key_state)) { uint8_t offset = (row * COLS) + col; - surface[page][pgm_read_byte(rc2pos + offset)] = 0x7f; - frames_since_event = 0; + surface_[page_][pgm_read_byte(rc2pos + offset)] = 0x7f; + frames_since_event_ = 0; } return EventHandlerResult::OK; } -void WavepoolEffect::raindrop(uint8_t x, uint8_t y, int8_t *page) { +void WavepoolEffect::TransientLEDMode::raindrop(uint8_t x, uint8_t y, int8_t *page_) { uint8_t rainspot = (y * WP_WID) + x; - page[rainspot] = 0x7f; - if (y > 0) page[rainspot - WP_WID] = 0x60; - if (y < (WP_HGT - 1)) page[rainspot + WP_WID] = 0x60; - if (x > 0) page[rainspot - 1] = 0x60; - if (x < (WP_WID - 1)) page[rainspot + 1] = 0x60; + page_[rainspot] = 0x7f; + if (y > 0) page_[rainspot - WP_WID] = 0x60; + if (y < (WP_HGT - 1)) page_[rainspot + WP_WID] = 0x60; + if (x > 0) page_[rainspot - 1] = 0x60; + if (x < (WP_WID - 1)) page_[rainspot + 1] = 0x60; } // this is a lot smaller than the standard library's rand(), // and still looks random-ish -uint8_t WavepoolEffect::wp_rand() { +uint8_t WavepoolEffect::TransientLEDMode::wp_rand() { static uint16_t offset = 0x400; offset = ((offset + 1) & 0x4fff) | 0x400; return (millis() / MS_PER_FRAME) + pgm_read_byte(offset); } -void WavepoolEffect::update(void) { +void WavepoolEffect::TransientLEDMode::update(void) { // limit the frame rate; one frame every 64 ms static uint8_t prev_time = 0; @@ -89,11 +101,11 @@ void WavepoolEffect::update(void) { static uint8_t current_hue = 0; current_hue ++; - frames_since_event ++; + frames_since_event_ ++; // needs two pages of height map to do the calculations - int8_t *newpg = &surface[page ^ 1][0]; - int8_t *oldpg = &surface[page][0]; + int8_t *newpg = &surface_[page_ ^ 1][0]; + int8_t *oldpg = &surface_[page_][0]; // rain a bit while idle static uint8_t frames_till_next_drop = 0; @@ -112,11 +124,11 @@ void WavepoolEffect::update(void) { raindrop(prev_x, prev_y, oldpg); prev_x = prev_y = -1; } - if (frames_since_event + if (frames_since_event_ >= (frames_till_next_drop + (idle_timeout / MS_PER_FRAME))) { frames_till_next_drop = 4 + (wp_rand() % FRAMES_PER_DROP); - frames_since_event = idle_timeout / MS_PER_FRAME; + frames_since_event_ = idle_timeout / MS_PER_FRAME; uint8_t x = wp_rand() % WP_WID; uint8_t y = wp_rand() % WP_HGT; @@ -195,7 +207,7 @@ void WavepoolEffect::update(void) { uint8_t value = (intensity >= 128) ? 255 : intensity << 1; int16_t hue = ripple_hue; - if (ripple_hue == rainbow_hue) { + if (ripple_hue == WavepoolEffect::rainbow_hue) { // color starts white but gets dimmer and more saturated as it fades, // with hue wobbling according to height map hue = (current_hue + height + (height >> 1)) & 0xff; @@ -209,10 +221,10 @@ void WavepoolEffect::update(void) { #ifdef INTERPOLATE // swap pages every other frame - if (!(now & 1)) page ^= 1; + if (!(now & 1)) page_ ^= 1; #else // swap pages every frame - page ^= 1; + page_ ^= 1; #endif } diff --git a/src/kaleidoscope/plugin/LED-Wavepool.h b/src/kaleidoscope/plugin/LED-Wavepool.h index c6fed02b..0799f3d3 100644 --- a/src/kaleidoscope/plugin/LED-Wavepool.h +++ b/src/kaleidoscope/plugin/LED-Wavepool.h @@ -28,7 +28,9 @@ namespace kaleidoscope { namespace plugin { -class WavepoolEffect : public LEDMode { +class WavepoolEffect : public Plugin, + public LEDModeInterface, + public AccessTransientLEDMode { public: WavepoolEffect(void) {} @@ -39,17 +41,38 @@ class WavepoolEffect : public LEDMode { static int16_t ripple_hue; static constexpr int16_t rainbow_hue = INT16_MAX; - protected: - void update(void) final; - private: - static uint8_t frames_since_event; - static int8_t surface[2][WP_WID * WP_HGT]; - static uint8_t page; - static PROGMEM const uint8_t rc2pos[ROWS * COLS]; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - static void raindrop(uint8_t x, uint8_t y, int8_t *page); - static uint8_t wp_rand(); + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const WavepoolEffect *parent); + + EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state); + + protected: + + virtual void update() final; + + private: + + const WavepoolEffect *parent_; + + uint8_t frames_since_event_; + int8_t surface_[2][WP_WID * WP_HGT]; + uint8_t page_; + static PROGMEM const uint8_t rc2pos[ROWS * COLS]; + + void raindrop(uint8_t x, uint8_t y, int8_t *page); + uint8_t wp_rand(); + + friend class WavepoolEffect; + }; }; } diff --git a/src/kaleidoscope/plugin/LEDControl.cpp b/src/kaleidoscope/plugin/LEDControl.cpp index 1afec63e..2dd9d590 100644 --- a/src/kaleidoscope/plugin/LEDControl.cpp +++ b/src/kaleidoscope/plugin/LEDControl.cpp @@ -16,88 +16,72 @@ #include "Kaleidoscope-LEDControl.h" #include "Kaleidoscope-FocusSerial.h" +#include "kaleidoscope_internal/LEDModeManager.h" + +using namespace kaleidoscope::internal; namespace kaleidoscope { namespace plugin { -LEDMode *LEDControl::modes[LED_MAX_MODES]; -uint8_t LEDControl::mode; -uint16_t LEDControl::syncDelay = 32; // 32ms interval => 30Hz refresh rate +static constexpr uint8_t uninitialized_mode_id = 255; + +uint8_t LEDControl::mode_id = uninitialized_mode_id; +uint8_t LEDControl::num_led_modes_ = LEDModeManager::numLEDModes(); +LEDMode *LEDControl::cur_led_mode_; +uint16_t LEDControl::syncDelay = 32; uint16_t LEDControl::syncTimer; bool LEDControl::paused = false; -void LEDMode::activate(void) { - ::LEDControl.activate(this); -} - -kaleidoscope::EventHandlerResult LEDMode::onSetup() { - ::LEDControl.mode_add(this); - setup(); - - return EventHandlerResult::OK; -} - LEDControl::LEDControl(void) { - mode = 0; - memset(modes, 0, LED_MAX_MODES * sizeof(modes[0])); } void LEDControl::next_mode(void) { - mode++; + mode_id++; - if (mode >= LED_MAX_MODES || !modes[mode]) { + if (mode_id >= num_led_modes_) { return set_mode(0); } - return set_mode(mode); + return set_mode(mode_id); } void LEDControl::prev_mode(void) { - if (mode == 0) { + if (mode_id == 0) { // wrap around - mode = LED_MAX_MODES - 1; - // then count down until reaching a valid mode - while (mode > 0 && !modes[mode]) mode--; + mode_id = num_led_modes_ - 1; } else { - mode--; + mode_id--; } - return set_mode(mode); + return set_mode(mode_id); } void LEDControl::set_mode(uint8_t mode_) { - if (mode_ >= LED_MAX_MODES) + if (mode_ >= num_led_modes_) return; - mode = mode_; - refreshAll(); -} + mode_id = mode_; -uint8_t LEDControl::get_mode_index(void) { - return mode; -} + // Cache the LED mode + // + cur_led_mode_ = LEDModeManager::getLEDMode(mode_id); -LEDMode *LEDControl::get_mode(void) { - return modes[mode]; + refreshAll(); } -void LEDControl::activate(LEDMode *mode) { - for (uint8_t i = 0; i < LED_MAX_MODES; i++) { - if (modes[i] == mode) - return set_mode(i); - } -} +void LEDControl::activate(LEDModeInterface *plugin) { + for (uint8_t i = 0; i < num_led_modes_; i++) { + + led_mode_management::LEDModeFactory fac; -int8_t LEDControl::mode_add(LEDMode *mode) { - for (int i = 0; i < LED_MAX_MODES; i++) { - if (modes[i]) - continue; + LEDModeManager::retreiveLEDModeFactoryFromPROGMEM(i, fac); - modes[i] = mode; - return i; + if (fac.isAssociatedWithPlugin(plugin)) { + set_mode(i); + return; + } } - return -1; } void LEDControl::set_all_leds_to(uint8_t r, uint8_t g, uint8_t b) { @@ -139,13 +123,14 @@ void LEDControl::syncLeds(void) { kaleidoscope::EventHandlerResult LEDControl::onSetup() { set_all_leds_to({0, 0, 0}); - for (uint8_t i = 0; i < LED_MAX_MODES; i++) { - if (modes[i]) - (modes[i]->setup)(); - } + LEDModeManager::setupPersistentLEDModes(); syncTimer = millis() + syncDelay; + if (mode_id == uninitialized_mode_id) { + set_mode(0); + } + return EventHandlerResult::OK; } @@ -249,10 +234,10 @@ EventHandlerResult FocusLEDCommand::onFocusEvent(const char *command) { } else if (peek == 'p') { ::LEDControl.prev_mode(); } else { - uint8_t mode; + uint8_t mode_id; - ::Focus.read(mode); - ::LEDControl.set_mode(mode); + ::Focus.read(mode_id); + ::LEDControl.set_mode(mode_id); } break; } diff --git a/src/kaleidoscope/plugin/LEDControl.h b/src/kaleidoscope/plugin/LEDControl.h index a9faa6ee..7a500057 100644 --- a/src/kaleidoscope/plugin/LEDControl.h +++ b/src/kaleidoscope/plugin/LEDControl.h @@ -18,88 +18,20 @@ #include -#define LED_MAX_MODES 24 - #define LED_TOGGLE B00000001 // Synthetic, internal #define Key_LEDEffectNext Key(0, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | LED_TOGGLE) #define Key_LEDEffectPrevious Key(1, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | LED_TOGGLE) +#define _DEPRECATED_MESSAGE_LED_CONTROL_MODE_ADD \ + "LEDControl::mode_add(LEDMode *mode) is deprecated. LEDModes are now \n" \ + "automatically registered. You can safely remove any calls to \n" \ + "LEDControl::mode_add from your code." + namespace kaleidoscope { namespace plugin { -/** Base class for LED modes. - * - * LED modes are a special kind of plugin, they are in charge of updating LED - * colors, setting a theme. While it is possible to have other plugins - * override the mode's colors, the LED mode is the baseline. - * - * Most of its functionality is called via @ref LEDControl, with only a few - * public methods. - * - * A LED mode **must** implement at least one of @ref onActivate or @ref - * update, and possibly @ref refreshAt too. - */ -class LEDMode : public kaleidoscope::Plugin { - friend class LEDControl; - protected: - // These methods should only be called by LEDControl. - - /** One-time setup, called at keyboard boot. - * - * Any hooks that need registering, any one-time setup that needs to be - * performed, shall be done here. This is purely for preparation purposes, the - * LEDs should not be touched yet at this time. - */ - virtual void setup(void) {} - - /** Function to call whenever the mode is activated. - * - * Like @ref setup, this method need not touch LEDs, @ref update will be - * called right after it. The purpose of this callback is to allow a plugin to - * do some preparation whenever it is activated, instead of only on boot, or - * always at each cycle. - * - * However, unlike @ref setup, this method can change LED colors, if so - * desired. Either to provide an initial state, or a static color set. In the - * latter case, consider implementing @ref refreshAt too, because other - * plugins may override some of the colors set at activation time, and @ref - * refreshAt can be used to restore them when needed. - * - * Before the callback runs, LEDs will be blanked. - */ - virtual void onActivate(void) {} - - /** Update the LEDs once per cycle. - * - * Usually the brains of the plugin, which updates the LEDs each cycle. It is - * called after the matrix has been scanned, once per cycle. - */ - virtual void update(void) {} - - /** Refresh the color of a given key. - * - * If we have another plugin that overrides colors set by the active LED mode - * (either at @onActivate time, or via @ref update), if that plugin wants to - * restore whatever color the mode would set the key color to, this is the - * method it will call. - * - * @param row is the row coordinate of the key to refresh the color of. - * @param col is the column coordinate of the key to refresh the color of. - */ - virtual void refreshAt(byte row, byte col) {} - public: - /** Activate the current object as the LED mode. - */ - void activate(void); - - /** Plugin initialization. - * - * Called via `Kaleidoscope.use()`, registers the LED mode, and does the - * necessary initialization steps. Calls @ref setup at the end. - */ - kaleidoscope::EventHandlerResult onSetup(); -}; +class LEDMode; class LEDControl : public kaleidoscope::Plugin { public: @@ -112,19 +44,26 @@ class LEDControl : public kaleidoscope::Plugin { if (!Kaleidoscope.has_leds) return; - if (modes[mode]) - modes[mode]->update(); + cur_led_mode_->update(); } static void refreshAt(byte row, byte col) { if (!Kaleidoscope.has_leds) return; - if (modes[mode]) - modes[mode]->refreshAt(row, col); + cur_led_mode_->refreshAt(row, col); + } + static void set_mode(uint8_t mode_id); + static uint8_t get_mode_index() { + return mode_id; + } + static LEDMode *get_mode() { + return cur_led_mode_; + } + template + static LEDMode__ *get_mode() { + return static_cast(cur_led_mode_); } - static void set_mode(uint8_t mode); - static uint8_t get_mode_index(); - static LEDMode *get_mode(); + static void refreshAll() { if (!Kaleidoscope.has_leds) return; @@ -133,11 +72,14 @@ class LEDControl : public kaleidoscope::Plugin { return; set_all_leds_to({0, 0, 0}); - if (modes[mode]) - modes[mode]->onActivate(); + + cur_led_mode_->onActivate(); } - static int8_t mode_add(LEDMode *mode); + DEPRECATED(LED_CONTROL_MODE_ADD) + static int8_t mode_add(LEDMode *mode) { + return 0; + } static void setCrgbAt(int8_t i, cRGB crgb); static void setCrgbAt(byte row, byte col, cRGB color); @@ -147,7 +89,11 @@ class LEDControl : public kaleidoscope::Plugin { static void set_all_leds_to(uint8_t r, uint8_t g, uint8_t b); static void set_all_leds_to(cRGB color); - static void activate(LEDMode *mode); + // We restict activate to LEDModeInterface to make sure that + // a compiler error is thrown when activate() is accidentally + // applied to a non-LED mode plugin. + // + static void activate(LEDModeInterface *plugin); static uint16_t syncDelay; static bool paused; @@ -158,8 +104,9 @@ class LEDControl : public kaleidoscope::Plugin { private: static uint16_t syncTimer; - static LEDMode *modes[LED_MAX_MODES]; - static uint8_t mode; + static uint8_t mode_id; + static uint8_t num_led_modes_; + static LEDMode *cur_led_mode_; }; class FocusLEDCommand : public Plugin { diff --git a/src/kaleidoscope/plugin/LEDControl/LED-Off.h b/src/kaleidoscope/plugin/LEDControl/LED-Off.h index 3a931e9d..a13bec5a 100644 --- a/src/kaleidoscope/plugin/LEDControl/LED-Off.h +++ b/src/kaleidoscope/plugin/LEDControl/LED-Off.h @@ -20,6 +20,10 @@ namespace kaleidoscope { namespace plugin { + +// This is still an old style persistent LEDMode as it does not have +// any members and thus there would not be any gain from making it dynamic. +// class LEDOff : public LEDMode { public: LEDOff(void) { } diff --git a/src/kaleidoscope/plugin/LEDEffect-Breathe.cpp b/src/kaleidoscope/plugin/LEDEffect-Breathe.cpp index d3d35020..9a85fba6 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Breathe.cpp +++ b/src/kaleidoscope/plugin/LEDEffect-Breathe.cpp @@ -20,7 +20,7 @@ namespace kaleidoscope { namespace plugin { -void LEDBreatheEffect::update(void) { +void LEDBreatheEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; @@ -29,7 +29,7 @@ void LEDBreatheEffect::update(void) { return; last_update_ = now; - cRGB color = breath_compute(hue, saturation); + cRGB color = breath_compute(parent_->hue, parent_->saturation); ::LEDControl.set_all_leds_to(color); } } diff --git a/src/kaleidoscope/plugin/LEDEffect-Breathe.h b/src/kaleidoscope/plugin/LEDEffect-Breathe.h index ade1e7dc..814073b3 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Breathe.h +++ b/src/kaleidoscope/plugin/LEDEffect-Breathe.h @@ -20,18 +20,34 @@ namespace kaleidoscope { namespace plugin { -class LEDBreatheEffect : public LEDMode { +class LEDBreatheEffect : public Plugin, + public LEDModeInterface { public: LEDBreatheEffect(void) {} uint8_t hue = 170; uint8_t saturation = 255; - protected: - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - private: - uint16_t last_update_ = 0; + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDBreatheEffect *parent) + : parent_(parent) {} + + protected: + virtual void update(void) final; + + private: + + const LEDBreatheEffect *parent_; + uint16_t last_update_ = 0; + }; }; } } diff --git a/src/kaleidoscope/plugin/LEDEffect-Chase.cpp b/src/kaleidoscope/plugin/LEDEffect-Chase.cpp index 0e18f28e..629b66ba 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Chase.cpp +++ b/src/kaleidoscope/plugin/LEDEffect-Chase.cpp @@ -19,19 +19,19 @@ namespace kaleidoscope { namespace plugin { -void LEDChaseEffect::update(void) { +void LEDChaseEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; uint16_t now = Kaleidoscope.millisAtCycleStart(); - if ((now - last_update_) < update_delay_) { + if ((now - last_update_) < parent_->update_delay_) { return; } last_update_ = now; // The red LED is at `pos_`; the blue one follows behind. `direction_` is // either +1 or -1; `distance_` is the gap between them. - int8_t pos2 = pos_ - (direction_ * distance_); + int8_t pos2 = pos_ - (direction_ * parent_->distance_); // First, we turn off the LEDs that were turned on in the previous update. // `pos_` is always in the valid range (0 <= pos_ < LED_COUNT), but after it diff --git a/src/kaleidoscope/plugin/LEDEffect-Chase.h b/src/kaleidoscope/plugin/LEDEffect-Chase.h index 52ee2798..6dd0da46 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Chase.h +++ b/src/kaleidoscope/plugin/LEDEffect-Chase.h @@ -20,7 +20,8 @@ namespace kaleidoscope { namespace plugin { -class LEDChaseEffect : public LEDMode { +class LEDChaseEffect : public Plugin, + public LEDModeInterface { public: LEDChaseEffect(void) {} @@ -37,15 +38,34 @@ class LEDChaseEffect : public LEDMode { distance_ = value; } - protected: - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDChaseEffect *parent) + : parent_(parent) {} + + protected: + + virtual void update() final; + + private: + + const LEDChaseEffect *parent_; + + int8_t pos_ = 0; + int8_t direction_ = 1; + uint16_t last_update_ = 0; + }; private: - int8_t pos_ = 0; - int8_t direction_ = 1; uint8_t distance_ = 5; uint16_t update_delay_ = 150; - uint16_t last_update_ = 0; }; } } diff --git a/src/kaleidoscope/plugin/LEDEffect-Rainbow.cpp b/src/kaleidoscope/plugin/LEDEffect-Rainbow.cpp index 2b8b5b3e..f063897c 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Rainbow.cpp +++ b/src/kaleidoscope/plugin/LEDEffect-Rainbow.cpp @@ -19,18 +19,18 @@ namespace kaleidoscope { namespace plugin { -void LEDRainbowEffect::update(void) { +void LEDRainbowEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; uint16_t now = millis(); - if ((now - rainbow_last_update) < rainbow_update_delay) { + if ((now - rainbow_last_update) < parent_->rainbow_update_delay) { return; } else { rainbow_last_update = now; } - cRGB rainbow = hsvToRgb(rainbow_hue, rainbow_saturation, rainbow_value); + cRGB rainbow = hsvToRgb(rainbow_hue, rainbow_saturation, parent_->rainbow_value); rainbow_hue += rainbow_steps; if (rainbow_hue >= 255) { @@ -50,12 +50,12 @@ void LEDRainbowEffect::update_delay(byte delay) { // --------- -void LEDRainbowWaveEffect::update(void) { +void LEDRainbowWaveEffect::TransientLEDMode::update(void) { if (!Kaleidoscope.has_leds) return; uint16_t now = millis(); - if ((now - rainbow_last_update) < rainbow_update_delay) { + if ((now - rainbow_last_update) < parent_->rainbow_update_delay) { return; } else { rainbow_last_update = now; @@ -66,7 +66,7 @@ void LEDRainbowWaveEffect::update(void) { if (key_hue >= 255) { key_hue -= 255; } - cRGB rainbow = hsvToRgb(key_hue, rainbow_saturation, rainbow_value); + cRGB rainbow = hsvToRgb(key_hue, rainbow_saturation, parent_->rainbow_value); ::LEDControl.setCrgbAt(i, rainbow); } rainbow_hue += rainbow_wave_steps; diff --git a/src/kaleidoscope/plugin/LEDEffect-Rainbow.h b/src/kaleidoscope/plugin/LEDEffect-Rainbow.h index 3bc8f654..cfff7d0f 100644 --- a/src/kaleidoscope/plugin/LEDEffect-Rainbow.h +++ b/src/kaleidoscope/plugin/LEDEffect-Rainbow.h @@ -20,7 +20,8 @@ namespace kaleidoscope { namespace plugin { -class LEDRainbowEffect : public LEDMode { +class LEDRainbowEffect : public Plugin, + public LEDModeInterface { public: LEDRainbowEffect(void) {} @@ -32,21 +33,40 @@ class LEDRainbowEffect : public LEDMode { byte update_delay(void) { return rainbow_update_delay; } - void update(void) final; - private: - uint16_t rainbow_hue = 0; // stores 0 to 614 + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - uint8_t rainbow_steps = 1; // number of hues we skip in a 360 range per update - uint16_t rainbow_last_update = 0; - uint16_t rainbow_update_delay = 40; // delay between updates (ms) + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDRainbowEffect *parent) + : parent_(parent) {} + + virtual void update() final; + + private: + + const LEDRainbowEffect *parent_; - byte rainbow_saturation = 255; + uint16_t rainbow_hue = 0; // stores 0 to 614 + + uint8_t rainbow_steps = 1; // number of hues we skip in a 360 range per update + uint16_t rainbow_last_update = 0; + + byte rainbow_saturation = 255; + }; + + private: + uint16_t rainbow_update_delay = 40; // delay between updates (ms) byte rainbow_value = 50; }; -class LEDRainbowWaveEffect : public LEDMode { +class LEDRainbowWaveEffect : public Plugin, public LEDModeInterface { public: LEDRainbowWaveEffect(void) {} @@ -58,16 +78,34 @@ class LEDRainbowWaveEffect : public LEDMode { byte update_delay(void) { return rainbow_update_delay; } - void update(void) final; - private: - uint16_t rainbow_hue = 0; // stores 0 to 614 + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: - uint8_t rainbow_wave_steps = 1; // number of hues we skip in a 360 range per update - uint16_t rainbow_last_update = 0; - uint16_t rainbow_update_delay = 40; // delay between updates (ms) + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDRainbowWaveEffect *parent) + : parent_(parent) {} + + virtual void update() final; + + private: + + const LEDRainbowWaveEffect *parent_; - byte rainbow_saturation = 255; + uint16_t rainbow_hue = 0; // stores 0 to 614 + + uint8_t rainbow_wave_steps = 1; // number of hues we skip in a 360 range per update + uint16_t rainbow_last_update = 0; + + byte rainbow_saturation = 255; + }; + + uint16_t rainbow_update_delay = 40; // delay between updates (ms) byte rainbow_value = 50; }; } diff --git a/src/kaleidoscope/plugin/LEDEffect-SolidColor.cpp b/src/kaleidoscope/plugin/LEDEffect-SolidColor.cpp index d030e86d..ad929256 100644 --- a/src/kaleidoscope/plugin/LEDEffect-SolidColor.cpp +++ b/src/kaleidoscope/plugin/LEDEffect-SolidColor.cpp @@ -18,18 +18,18 @@ namespace kaleidoscope { namespace plugin { -LEDSolidColor::LEDSolidColor(uint8_t r, uint8_t g, uint8_t b) { - this->r = r; - this->g = g; - this->b = b; -} -void LEDSolidColor::onActivate(void) { - ::LEDControl.set_all_leds_to(r, g, b); +void LEDSolidColor::TransientLEDMode::onActivate(void) { + ::LEDControl.set_all_leds_to(parent_->r_, + parent_->g_, + parent_->b_); } -void LEDSolidColor::refreshAt(byte row, byte col) { - ::LEDControl.setCrgbAt(row, col, CRGB(r, g, b)); +void LEDSolidColor::TransientLEDMode::refreshAt(byte row, byte col) { + ::LEDControl.setCrgbAt(row, col, + CRGB(parent_->r_, + parent_->g_, + parent_->b_)); } } diff --git a/src/kaleidoscope/plugin/LEDEffect-SolidColor.h b/src/kaleidoscope/plugin/LEDEffect-SolidColor.h index 30ebc14d..d66971f9 100644 --- a/src/kaleidoscope/plugin/LEDEffect-SolidColor.h +++ b/src/kaleidoscope/plugin/LEDEffect-SolidColor.h @@ -20,16 +20,38 @@ namespace kaleidoscope { namespace plugin { -class LEDSolidColor : public LEDMode { +class LEDSolidColor : public Plugin, + public LEDModeInterface { public: - LEDSolidColor(uint8_t r, uint8_t g, uint8_t b); - protected: - void onActivate(void) final; - void refreshAt(byte row, byte col) final; + LEDSolidColor(uint8_t r, uint8_t g, uint8_t b) + : r_(r), g_(g), b_(b) + {} + + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const LEDSolidColor *parent) + : parent_(parent) {} + + protected: + virtual void onActivate(void) final; + virtual void refreshAt(byte row, byte col) final; + + private: + + const LEDSolidColor *parent_; + }; private: - uint8_t r, g, b; + + uint8_t r_, g_, b_; }; } } diff --git a/src/kaleidoscope/plugin/LEDMode.cpp b/src/kaleidoscope/plugin/LEDMode.cpp new file mode 100644 index 00000000..ed422ab0 --- /dev/null +++ b/src/kaleidoscope/plugin/LEDMode.cpp @@ -0,0 +1,30 @@ +/* Kaleidoscope-LEDControl - LED control plugin for Kaleidoscope + * Copyright (C) 2017-2018 Keyboard.io, Inc. + * + * 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, version 3. + * + * 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 +#include +#include + +namespace kaleidoscope { +namespace plugin { + +void LEDModeInterface::activate() { + LEDControl::activate(this); +} + +} // end namespace plugin +} // end namespace kaleidoscope diff --git a/src/kaleidoscope/plugin/LEDMode.h b/src/kaleidoscope/plugin/LEDMode.h new file mode 100644 index 00000000..2459ba95 --- /dev/null +++ b/src/kaleidoscope/plugin/LEDMode.h @@ -0,0 +1,144 @@ +/* Kaleidoscope-LEDControl - LED control plugin for Kaleidoscope + * Copyright (C) 2017-2018 Keyboard.io, Inc. + * + * 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, version 3. + * + * 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 + +#include "kaleidoscope/plugin.h" + +namespace kaleidoscope { + +namespace internal { +// Forward declaration +class LEDModeManager; +} // end namespace internal + +namespace plugin { + +class LEDModeInterface { + public: + + void activate(); + + // This auxiliary class helps to generate a verbose error message + // in case that there is no TransientLEDMode typedef or nested + // class present in a derived class of LEDModeInterface. + // + struct _____LEDModeInterface_derived_class_is_missing_a_TransientLEDMode_typedef_or_nested_class_____ {}; + typedef _____LEDModeInterface_derived_class_is_missing_a_TransientLEDMode_typedef_or_nested_class_____ NoLEDMode; + + // By redefining TransientLEDMode, derived plugins export + // a LED mode that becomes part of the set of LED modes whose + // lifetime is handled dynamically. + // + typedef NoLEDMode DynamicLEDMode; +}; + +class AccessTransientLEDMode { + public: + + // This method is called when a plugin's LED mode is activated. + // Derived plugins may reimplement it to store the id of their + // exported LED mode. A plugin can thus check + // whether their LED mode is currently active. + // + void registerLEDModeActivated(uint8_t led_mode_id) { + led_mode_id_ = led_mode_id; + } + + protected: + + uint8_t led_mode_id_ = 255; /* 255 means uninitialized */ +}; + +/** Base class for LED modes. +* +* LED modes are a special kind of plugin, they are in charge of updating LED +* colors, setting a theme. While it is possible to have other plugins +* override the mode's colors, the LED mode is the baseline. +* +* Most of its functionality is called via @ref LEDControl, with only a few +* public methods. +* +* A LED mode **must** implement at least one of @ref onActivate or @ref +* update, and possibly @ref refreshAt too. +*/ +class LEDMode : public kaleidoscope::Plugin, + public LEDModeInterface { + friend class LEDControl; + friend class kaleidoscope::internal::LEDModeManager; + protected: + // These methods should only be called by LEDControl. + + /** One-time setup, called at keyboard boot. + * + * Any hooks that need registering, any one-time setup that needs to be + * performed, shall be done here. This is purely for preparation purposes, the + * LEDs should not be touched yet at this time. + */ + virtual void setup(void) {} + + /** Function to call whenever the mode is activated. + * + * Like @ref setup, this method need not touch LEDs, @ref update will be + * called right after it. The purpose of this callback is to allow a plugin to + * do some preparation whenever it is activated, instead of only on boot, or + * always at each cycle. + * + * However, unlike @ref setup, this method can change LED colors, if so + * desired. Either to provide an initial state, or a static color set. In the + * latter case, consider implementing @ref refreshAt too, because other + * plugins may override some of the colors set at activation time, and @ref + * refreshAt can be used to restore them when needed. + * + * Before the callback runs, LEDs will be blanked. + */ + virtual void onActivate(void) {} + + /** Update the LEDs once per cycle. + * + * Usually the brains of the plugin, which updates the LEDs each cycle. It is + * called after the matrix has been scanned, once per cycle. + */ + virtual void update(void) {} + + /** Refresh the color of a given key. + * + * If we have another plugin that overrides colors set by the active LED mode + * (either at @onActivate time, or via @ref update), if that plugin wants to + * restore whatever color the mode would set the key color to, this is the + * method it will call. + * + * @param row is the row coordinate of the key to refresh the color of. + * @param col is the column coordinate of the key to refresh the color of. + */ + virtual void refreshAt(byte row, byte col) {} + + public: + + /** Plugin initialization. + * + * Called via `Kaleidoscope.use()`, registers the LED mode, and does the + * necessary initialization steps. Calls @ref setup at the end. + */ + kaleidoscope::EventHandlerResult onSetup() { + setup(); + + return EventHandlerResult::OK; + } +}; + +} // end namespace plugin +} // end namespace kaleidoscope diff --git a/src/kaleidoscope/plugin/Model01-TestMode.cpp b/src/kaleidoscope/plugin/Model01-TestMode.cpp index 27cf61ed..5789a314 100644 --- a/src/kaleidoscope/plugin/Model01-TestMode.cpp +++ b/src/kaleidoscope/plugin/Model01-TestMode.cpp @@ -19,6 +19,7 @@ #include "Kaleidoscope.h" #include "Kaleidoscope-Model01-TestMode.h" #include "Kaleidoscope-LEDEffect-Rainbow.h" +#include "kaleidoscope_internal/LEDModeManager.h" namespace kaleidoscope { namespace plugin { @@ -73,9 +74,17 @@ void TestMode::test_leds(void) { set_leds(blue); // make all the LEDs bright white (1.6A) set_leds(brightWhite); + + // This works under the assumption that LEDRainbowEffect + // has been registered with KALEIDOSCOPE_INIT_PLUGINS in + // the sketch. Otherwise LEDRainbowEffect would not be + // known to LEDControl. + // + ::LEDControl.activate(&::LEDRainbowEffect); + // rainbow for 10 seconds for (auto i = 0; i < 1000; i++) { - ::LEDRainbowEffect.update(); + ::LEDControl.update(); ::LEDControl.syncLeds(); } waitForKeypress(); diff --git a/src/kaleidoscope/plugin/TriColor.cpp b/src/kaleidoscope/plugin/TriColor.cpp index c13eb626..cb457ec5 100644 --- a/src/kaleidoscope/plugin/TriColor.cpp +++ b/src/kaleidoscope/plugin/TriColor.cpp @@ -26,18 +26,18 @@ TriColor::TriColor(cRGB base_color, cRGB mod_color, cRGB esc_color) { esc_color_ = esc_color; } -void TriColor::update(void) { +void TriColor::TransientLEDMode::update(void) { for (uint8_t r = 0; r < ROWS; r++) { for (uint8_t c = 0; c < COLS; c++) { Key k = Layer.lookup(r, c); // Special keys are always mod_color if (k.flags != 0) { - ::LEDControl.setCrgbAt(r, c, mod_color_); + ::LEDControl.setCrgbAt(r, c, parent_->mod_color_); continue; } - cRGB color = mod_color_; + cRGB color = parent_->mod_color_; switch (k.keyCode) { case Key_A.keyCode ... Key_0.keyCode: @@ -46,10 +46,10 @@ void TriColor::update(void) { case Key_Keypad1.keyCode ... Key_KeypadDot.keyCode: case Key_F1.keyCode ... Key_F4.keyCode: case Key_F9.keyCode ... Key_F12.keyCode: - color = base_color_; + color = parent_->base_color_; break; case Key_Escape.keyCode: - color = esc_color_; + color = parent_->esc_color_; break; } diff --git a/src/kaleidoscope/plugin/TriColor.h b/src/kaleidoscope/plugin/TriColor.h index b4d62ef9..ead3cd40 100644 --- a/src/kaleidoscope/plugin/TriColor.h +++ b/src/kaleidoscope/plugin/TriColor.h @@ -22,13 +22,32 @@ namespace kaleidoscope { namespace plugin { -class TriColor : public LEDMode { +class TriColor : public Plugin, + public LEDModeInterface { public: TriColor(cRGB base_color, cRGB mod_color, cRGB esc_color); TriColor(cRGB base_color, cRGB mod_color) : TriColor(base_color, mod_color, mod_color) {} - protected: - void update(void) final; + // This class' instance has dynamic lifetime + // + class TransientLEDMode : public LEDMode { + public: + + // Please note that storing the parent ptr is only required + // for those LED modes that require access to + // members of their parent class. Most LED modes can do without. + // + TransientLEDMode(const TriColor *parent) + : parent_(parent) {} + + protected: + + virtual void update(void) final; + + private: + + const TriColor *parent_; + }; private: cRGB base_color_; diff --git a/src/kaleidoscope_internal/LEDModeManager.cpp b/src/kaleidoscope_internal/LEDModeManager.cpp new file mode 100644 index 00000000..151c0db6 --- /dev/null +++ b/src/kaleidoscope_internal/LEDModeManager.cpp @@ -0,0 +1,112 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-2018 Keyboard.io, Inc. + * + * 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, version 3. + * + * 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 "kaleidoscope_internal/LEDModeManager.h" +#include "kaleidoscope/plugin/LEDMode.h" + +namespace kaleidoscope { +namespace internal { + +namespace { + +// We want to export as little symbols as possible. That's why the +// internal state of the LED mode management lives in +// an anonymous namespace. + +uint8_t cur_mode_id = 255; // We use 255 as a flag value that signals +// uninitialized. That's why we can only have +// LED mode ids in the range [0..254]. + +// A pointer to cache the current transient LED mode. +// +kaleidoscope::plugin::LEDMode *cur_led_mode = nullptr; + +bool current_led_mode_dynamic = false; + +} + +kaleidoscope::plugin::LEDMode *LEDModeManager::getLEDMode(uint8_t mode_id) { + + // If we end up here, the requested LED mode is a dynamic one. + + // Check if the requested LED mode is already active + // + if (cur_mode_id == mode_id) { + return cur_led_mode; + } + + // If there is already an active LED mode, its obviously the wrong one + // (see test above). To generate a new transient LED mode, we need + // to destroy the current one. To achieve this, we call its + // (possibly - see explanation below) virtual destuctor and let + // it take care of the cleanup. + // + // Please note that due to the fact that transient LED modes are + // allocated using placement new within a pre-existing static buffer, + // there is no need to free any memory after the destructor of the + // current LED mode was called. We can just reuse the buffer + // for the next LED mode instance. + // + // Please note: Currently, LEDMode has no virtual destructor. + // That's why the explicit destructor call below is a noop + // that is optimized out by the compiler. It is there + // to enable the possible future introduction of a virtual + // destructor for class LEDMode. + // + // A virtual destructor would enable LEDModes to do any types of + // cleanup on destruction. But as it's a virtual method, + // introducing such a destructor would create additional + // entries in vtables and mean higher PROGMEM consumption. + // If it will be introduced in future versions of Kaleidoscope + // uncomment the commented lines below. + // + if (current_led_mode_dynamic) { + cur_led_mode->~LEDMode(); + } + + // Store the current mode id to enable cache access (see above). + // + cur_mode_id = mode_id; + + // Get a factory struct for the creation of the new LED mode. + // + led_mode_management::LEDModeFactory fac; + + retreiveLEDModeFactoryFromPROGMEM(mode_id, fac); + + // The factories for persistent LED modes serve to pass through the LED + // mode plugin's pointer in parent_plugin_. + // + if (fac.isPersistentLEDMode()) { + current_led_mode_dynamic = false; + cur_led_mode = fac.getPersistentLEDMode(); + } else { + current_led_mode_dynamic = true; + + // Generate a new led mode by calling the factory function + // (fac.generate_led_mode_). + // + // We store a pointer to the newly created LED mode to enable + // cached access (see check above). + // + cur_led_mode = fac.generateTransientLEDMode(led_mode_buffer_, mode_id); + } + + return cur_led_mode; +} + +} // end namespace internal +} // end namespace kaleidoscope diff --git a/src/kaleidoscope_internal/LEDModeManager.h b/src/kaleidoscope_internal/LEDModeManager.h new file mode 100644 index 00000000..b8b7bfef --- /dev/null +++ b/src/kaleidoscope_internal/LEDModeManager.h @@ -0,0 +1,474 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-2018 Keyboard.io, Inc. + * + * 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, version 3. + * + * 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 + +#include "kaleidoscope_internal/array_like_storage.h" +#include "kaleidoscope/plugin.h" +#include "kaleidoscope/plugin/LEDMode.h" +#include "kaleidoscope_internal/type_traits/has_method.h" + +#include + +// To enable placement new, we need to supply a global operator +// function. +// +inline void* operator new (size_t, void* __p) throw() { + return __p; +} + +namespace kaleidoscope { + +namespace plugin { +// Forward declarations to avoid header inclusions +class LEDControl; +class LEDModeInterface; +} // end namespace plugin + +namespace internal { +namespace led_mode_management { + +template struct Bool2Type {}; + +// Functions of this type allocate a new LED mode within a global +// buffer and give it access to it's parent plugin. +// This allows a transient LED mode to access member data of its parent +// plugin, which can help to reduce its RAM footprint. +// Some transient LED modes might ignore the parent pointer entirely, e.g. if +// they do not have a configurable internal state. +// +// We use a factory function instead of a polymorphic +// class to generate LED modes. By this means we can avoid the additional +// cost of vtables which safes some program memory. +// Conceptually, the factory function approach is similar to having +// a polymorphic class with only one method, that is accessed directly +// instead of the detour via the vtable. +// +typedef kaleidoscope::plugin::LEDMode* +(*LEDModeFactoryFunc)(void *raw_buffer, + kaleidoscope::plugin::LEDModeInterface *parent_plugin, + uint8_t mode_id + ); + +template +inline void registerLEDModeActivated(Bool2Type, + ParentPlugin__ *parent_plugin, + uint8_t mode_id) { + parent_plugin->registerLEDModeActivated(mode_id); +} + +template +inline void registerLEDModeActivated(Bool2Type, + ParentPlugin__ *parent_plugin, + uint8_t mode_id) { + // Noop +} + +DEFINE_HAS_METHOD_TRAITS(Plugin, registerLEDModeActivated, void, (uint8_t led_mode_id)) + +// A templated implementation of LEDModeFactoryFunc. +// +// Please note that the actual type of ParentPlugin__ is only +// known to the context where the function is invoked or in our case +// the function pointer to generateLEDMode is generated (at compile time). +// +// The caller can be completely agnostic of ParentPlugin__ an just pass +// a pointer to kaleidoscope::Plugin for argument parent_plugin. +// +// The function generateLEDMode knows the actual type of parent_plugin. +// Or, more precise, we can rely on the fact that we store a pointer to a plugin +// of appropriate type together with the generateLEDMode-pointer in +// an LEDModeFactory instance in PROGMEM. Thus, generateLEDMode will only +// be called with a parent_plugin of appropriate type. +// +template +static kaleidoscope::plugin::LEDMode * +generateLEDMode(void *raw_buffer, + kaleidoscope::plugin::LEDModeInterface *parent_plugin, + uint8_t mode_id + ) { + // We know the type of the parent plugin via the template parameter + // ParentPlugin__, thus it is safe to cast to the actual type. + // + auto parent_plugin_actual = static_cast(parent_plugin); + + // Types defined by template parameters like ParentPlugin__ must + // be declared explicitly using the "typename" keyword. + // Because of this lengthy syntax, we use a typedef to + // shorten the constructor call below. + // + typedef typename ParentPlugin__::TransientLEDMode TLM; + + // Generate a transient LED mode within the LED mode buffer. + // + auto led_mode_ptr + = new (raw_buffer) TLM{parent_plugin_actual}; + + constexpr bool accesses_transient_led_mode + = Plugin_HasMethod_registerLEDModeActivated::value; + + // Register the newly created LED mode with its parent plugin. + // Please note that this call is optimized away by the compiler + // for all those plugins that do not reimplement registerLEDModeActivated. + // + registerLEDModeActivated(Bool2Type(), + parent_plugin_actual, mode_id); + + return led_mode_ptr; +} + +// A data structure that contains all the information that is required +// to generate a transient LED mode. Instances of this class are stored +// in an array-like data structure in PROGMEM. +// +struct LEDModeFactory { + + // Don't try to make this a class with a constructor. This + // will turn it into a non POD which cannot be used as + // return type of a constexpr. + + bool isPersistentLEDMode() const { + return !generate_led_mode_; + } + + kaleidoscope::plugin::LEDMode *getPersistentLEDMode() const { + return static_cast(parent_plugin_); + } + + kaleidoscope::plugin::LEDMode *generateTransientLEDMode( + uint8_t *buffer, uint8_t mode_id) const { + return (*generate_led_mode_)(buffer, parent_plugin_, mode_id); + } + + bool isAssociatedWithPlugin(kaleidoscope::plugin::LEDModeInterface *plugin) const { + return parent_plugin_ == plugin; + } + + kaleidoscope::plugin::LEDModeInterface *parent_plugin_; + LEDModeFactoryFunc generate_led_mode_; +}; + +// The traits class remove_pointer is part of the C++ standard library +// but not present on Arduino. +// +template< class T > struct remove_pointer { + typedef T type; +}; +template< class T > struct remove_pointer { + typedef T type; +}; + +enum PluginType { + PluginType_NoLEDMode, + PluginType_PersistentLEDMode, + PluginType_TransientLEDMode +}; + +// The following three functions enable determining the PluginType +// at compile time by examining a constexpr pointer to one of the +// global plugin instances (pointers to global variables/objects are +// constexpr). +// +static constexpr int ledModePluginType(kaleidoscope::plugin::LEDMode *) { + return PluginType_PersistentLEDMode; +} + +static constexpr int ledModePluginType(kaleidoscope::plugin::LEDModeInterface *) { + return PluginType_TransientLEDMode; +} + +// Invoked for all plugins that inherit neither from LEDMode +// nor from LEDModeInterface. +// +static constexpr bool ledModePluginType(void *) { + return PluginType_NoLEDMode; +} + +// The following traits classes are used to distinguish between three cases +// +// 1) A plugin is unrelated to LEDModes (i.e. non of the other two applies). +// +// 2) A plugin is an old syle persistent LEDMode (plugin is derived from +// LED mode = persistent LED mode) +// +// 3) A plugin exports a LED mode with dynamic lifetime (= transient LED mode) + +// Checks if a plugin is related to LED modes in any kind of ways. +// This traits check if for a plugin +// an entry in the plugin factories array needs to be reserved. +// +constexpr bool pluginControlsLEDMode(int led_mode_plugin_type) { + return led_mode_plugin_type != PluginType_NoLEDMode; +} + +// The template GenerateLEDModeFactory generates LED mode factories +// in three different ways, depending on the type of a plugin (PluginType) + +// Generates a dummy factory (discarded at compile time) for +// those plugins that are unrelated to LED modes. This template +// is selected for LEDModePluginType__ == NoLEDMode. +// +template +struct GenerateLEDModeFactory { + + static constexpr LEDModeFactory apply(kaleidoscope::Plugin */* non LED mode plugin*/) { + return LEDModeFactory{nullptr, nullptr}; + } +}; + +template<> +struct GenerateLEDModeFactory { + + // This version of apply must be templated as we must know the actual + // type of plugin as the instanciatioin of template function + // generateLEDMode depends upon it. + // + template + static constexpr LEDModeFactory apply(Plugin__ *plugin) { + return LEDModeFactory{ + plugin, + generateLEDMode // pointer to template instantiation of + // generateLEDMode<...> + }; + } +}; + +template<> +struct GenerateLEDModeFactory { + + static constexpr LEDModeFactory apply(kaleidoscope::plugin::LEDModeInterface *plugin) { + + // For persistent LED modes, we use the LED factory to simply store + // the plugin pointer (a persistent LED mode is itself the LEDMode). + // Thus, no factory function is required. + // + return LEDModeFactory{plugin, nullptr}; + } +}; + +// The following template TransientLEDModeSize is used to determine +// the size of a transient LED mode. It is only active for dynamic +// LED modes. For all other plugins, it simply returns zero. +// +template +struct TransientLEDModeSize { + static constexpr size_t value = 0; +}; + +template +struct TransientLEDModeSize { + typedef typename remove_pointer::type Plugin__; + static constexpr size_t value = sizeof(typename Plugin__::TransientLEDMode); +}; + +// This helper class serves to determine the maximum memory footprint +// of any transient LED mode in the sketch. +// It implements a template type recursion +// that examins all specified LED modes. For this calculation +// it is no problem that also the type NoLEDMode is considered +// in this check as it is an empty class (size == 1 byte) and +// thus does not affect the maximum size computation. +// +template +struct TransientLEDModeMaxSize { + static constexpr size_t this_size + = TransientLEDModeSize::value; + + static constexpr size_t nested_size = TransientLEDModeMaxSize::value; + + static constexpr size_t value = (this_size > nested_size) ? this_size : nested_size; +}; + +// Specialization to end recursion +// +template +struct TransientLEDModeMaxSize { + static constexpr size_t value = TransientLEDModeSize::value; +}; + +} // end namespace led_mode_management + +class LEDModeManager { + public: + + // Everything in this class private on purpose. + // + // Only the class LEDControl is supposed to gain + // access to any inventory of this class to ensure that + // the handling of transient LED modes is safely handled + // by a well defined central instance. + // + friend class kaleidoscope::plugin::LEDControl; + + static uint8_t numLEDModes(); + +#if 0 + // This method could be used in rare cases + // where a stack allocation of a LED mode may not be avoided. + // + template + static auto + generateLEDModeTemporary(void *raw_buffer, + ParentPlugin__ *parent_plugin + ) -> typename ParentPlugin__::TransientLEDMode * { + auto led_mode = static_cast( + led_mode_management::generateLEDMode( + raw_buffer, + parent_plugin, + 0 /*dummy*/ + ) + ); + parent_plugin->registerLEDModeActivated(255 /* dymmy to disable access + to transient LED modes through LEDControl */ + ); + + return led_mode; + } +#endif + + private: + + + // For the sake of convenience make type LEDModeFactory + // available in class namespace + // + typedef led_mode_management::LEDModeFactory LEDModeFactory; + + static kaleidoscope::plugin::LEDMode *getLEDMode(uint8_t mode_id); + + static void retreiveLEDModeFactoryFromPROGMEM(uint8_t mode_id, + LEDModeFactory &fac); + + static void setupPersistentLEDModes(); + + // Persistent LED mode plugins are derived from kaleidoscope::plugin::LEDMode. + // The standard dictates that for them this more specialized overload + // of setupLEDMode is supposed to be called instead of the more + // general one below. + // + static void setupLEDMode(kaleidoscope::plugin::LEDMode *persistent_led_mode) { + persistent_led_mode->setup(); + } + + static void setupLEDMode(kaleidoscope::Plugin */*not_a_persistent_led_mode*/) {} + + static uint8_t led_mode_buffer_[]; +}; + +} // end namespace internal +} // end namespace kaleidoscope + +// Some auxiliary macros that are mapped to the list of +// plugins defined via KALEIDOSCOPE_INIT_PLUGINS follow. + +// Evaluates to a boolean value that signals whether +// a plugin is related to LED modes and thus needs to be considered +// during setup of the LED mode factory array. +// +#define _LED_MODE_MANAGEMENT__PLUGIN_CONTROLS_LED_MODE(PLUGIN) \ + pluginControlsLEDMode(ledModePluginType(&::PLUGIN)) + +// Generates a LEDModeFactory for each plugin. For all those +// plugins that do not export transient LED modes, a nullptr-initialized +// factory is created. This does not mean a performance penalty, as +// those empty factories are then directly discarded, still at compile time. +// +#define _LED_MODE_MANAGEMENT__GENERATE_LED_MODE_FACTORY(PLUGIN) \ + kaleidoscope::internal::led_mode_management __NL__ \ + ::GenerateLEDModeFactory::apply(&::PLUGIN) + +// Retreive the pointer type of the exported transient LED mode of a plugin. +// +#define _LED_MODE_MANAGEMENT__PLUGIN_PTR_TYPE(PLUGIN) \ + decltype(&::PLUGIN) + +#define _LED_MODE_MANAGEMENT__SETUP_STATIC_LED_MODE(PLUGIN) \ + LEDModeManager::setupLEDMode(&::PLUGIN); + +#define _INIT_LED_MODE_MANAGER(...) \ + namespace kaleidoscope { __NL__ \ + namespace internal { __NL__ \ + namespace led_mode_management { __NL__ \ + __NL__ \ + /* Setup the array-like data structure that stores __NL__ \ + * LEDModeFactories */ __NL__ \ + typedef kaleidoscope::internal::ArrayLikeStorage< __NL__ \ + LEDModeFactory, __NL__ \ + /* Map the list of global plugin __NL__ \ + * objects to a list of led mode __NL__ \ + * factories */ __NL__ \ + MAP_LIST( __NL__ \ + /* Generate a series of boolean values. True for __NL__ \ + * each plugin that is related to LED modes, __NL__ \ + * false otherwise. */ __NL__ \ + _LED_MODE_MANAGEMENT__PLUGIN_CONTROLS_LED_MODE, __NL__ \ + __VA_ARGS__ __NL__ \ + ) __NL__ \ + > ArrayType; __NL__ \ + __NL__ \ + /* Generate the actual instance of template class __NL__ \ + * TypedPluginArray and initialize it with a list of __NL__ \ + * LEDModeFactories. __NL__ \ + */ __NL__ \ + const PROGMEM ArrayType led_mode_factories( __NL__ \ + MAP_LIST( __NL__ \ + _LED_MODE_MANAGEMENT__GENERATE_LED_MODE_FACTORY, __NL__ \ + __VA_ARGS__ __NL__ \ + ) __NL__ \ + ); __NL__ \ + } /* end namespace led_mode_management */ __NL__ \ + __NL__ \ + /* Store the number of LEDModeFactories. __NL__ \ + */ __NL__ \ + const PROGMEM uint8_t num_led_modes __NL__ \ + = led_mode_management::ArrayType::n_entries; __NL__ \ + __NL__ \ + uint8_t LEDModeManager::numLEDModes() { __NL__ \ + return pgm_read_byte(&num_led_modes); __NL__ \ + } __NL__ \ + __NL__ \ + void LEDModeManager::retreiveLEDModeFactoryFromPROGMEM( __NL__ \ + uint8_t mode_id, __NL__ \ + LEDModeFactory &fac) { __NL__ \ + memcpy_P(&fac, __NL__ \ + &reinterpret_cast( __NL__ \ + &led_mode_management::led_mode_factories __NL__ \ + )[mode_id], __NL__ \ + sizeof(LEDModeFactory) __NL__ \ + ); __NL__ \ + } __NL__ \ + __NL__ \ + static constexpr size_t max_led_mode_size __NL__ \ + = led_mode_management::TransientLEDModeMaxSize< __NL__ \ + MAP_LIST( __NL__ \ + _LED_MODE_MANAGEMENT__PLUGIN_PTR_TYPE, __NL__ \ + __VA_ARGS__ __NL__ \ + ) __NL__ \ + >::value; __NL__ \ + __NL__ \ + /* This buffer is dimensioned in a way that it can hold __NL__ \ + * the largest of all transient LED modes defined in the sketch. __NL__ \ + */ __NL__ \ + uint8_t LEDModeManager::led_mode_buffer_[max_led_mode_size]; __NL__ \ + __NL__ \ + void LEDModeManager::setupPersistentLEDModes() { __NL__ \ + MAP( __NL__ \ + _LED_MODE_MANAGEMENT__SETUP_STATIC_LED_MODE, __NL__ \ + __VA_ARGS__ __NL__ \ + ) __NL__ \ + } __NL__ \ + } /* end namespace internal */ __NL__ \ + } /* end namespace kaleidoscope */ diff --git a/src/kaleidoscope_internal/array_like_storage.h b/src/kaleidoscope_internal/array_like_storage.h new file mode 100644 index 00000000..10ad9222 --- /dev/null +++ b/src/kaleidoscope_internal/array_like_storage.h @@ -0,0 +1,114 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-2018 Keyboard.io, Inc. + * + * 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, version 3. + * + * 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 + +#include + +namespace kaleidoscope { +namespace internal { + +// The ArrayLikeStorage class stores data of a specific +// type in a recursive fashion. It is used to generate array like +// data structures at compile time that are initialized from a +// list of entities only a subset of which is supposed to be +// stored in the container. +// +template +class ArrayLikeStorage { + + public: + + typedef ArrayLikeStorage NestedArray; + + template + constexpr ArrayLikeStorage(StoredType__ entry, MoreEntities__...more_entities) + : entry_(entry), + nested_array_(more_entities...) {} + + static constexpr uint8_t n_entries + = NestedArray::n_entries + 1; + + typedef StoredType__ ContentType; + + private: + StoredType__ entry_; + NestedArray nested_array_; +} __attribute__((packed)); // Make sure that there are no padding +// bytes added by the compiler. +// This is important to let the class +// have the same layout as a POD array. + + +template +class ArrayLikeStorage { + + public: + + typedef ArrayLikeStorage NestedArray; + + template + constexpr ArrayLikeStorage(AnyType__/* non-matching entity */, + MoreEntities__...more_entities) + : nested_array_(more_entities...) {} + + static constexpr uint8_t n_entries + = NestedArray::n_entries; + + typedef StoredType__ ContentType; + + private: + + NestedArray nested_array_; +} __attribute__((packed)); + +template +struct ArrayLikeStorage { + + public: + + constexpr ArrayLikeStorage(StoredType__ entry) + : entry_(entry) + {} + + static constexpr uint8_t n_entries = 1; + + typedef StoredType__ ContentType; + + private: + StoredType__ entry_; +} __attribute__((packed)); + +template +struct ArrayLikeStorage { + + public: + + template + constexpr ArrayLikeStorage(AnyType__/* non-matching entity */) {} + + static constexpr uint8_t n_entries = 0; + + typedef StoredType__ ContentType; +} __attribute__((packed)); + +} // end namespace internal +} // end namespace kaleidoscope diff --git a/src/kaleidoscope_internal/event_dispatch.h b/src/kaleidoscope_internal/event_dispatch.h index 84b271ae..a93a024f 100644 --- a/src/kaleidoscope_internal/event_dispatch.h +++ b/src/kaleidoscope_internal/event_dispatch.h @@ -198,4 +198,8 @@ __NL__ \ _PREPARE_EVENT_HANDLER_SIGNATURE_CHECK __NL__ \ __NL__ \ - _FOR_EACH_EVENT_HANDLER(_REGISTER_EVENT_HANDLER) + _FOR_EACH_EVENT_HANDLER(_REGISTER_EVENT_HANDLER) __NL__ \ + __NL__ \ + /* This generates a PROGMEM array-kind-of data structure that contains */ __NL__ \ + /* LEDModeFactory entries */ __NL__ \ + _INIT_LED_MODE_MANAGER(__VA_ARGS__)