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 <florian.fleissner@inpartik.de>
Moved all files but `LEDControl.c` and `LEDControl.h` to
`src/kaleidoscope/plugin/LEDControl`, to not pollute the `kaleidoscope/plugin`
directory needlessly, with headers that should not be explicitly included.
Also updated the documentation and the warnings emitted to make it clear that
the use of `LED-Off.h` and `LEDUtils.h` is deprecated in favour of just using
`<Kaleidoscope-LEDControl.h>`.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Move the documentation to `doc/plugin/LEDControl.md`, sources under
`src/kaleidoscope/plugin/` (appropriately namespaced). This is in preparation of
merging plugins into a single monorepo.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This prevents an insignificant error, but it is more correct to handle the integer
overflow instead of ignoring it. I've also changed syncTimer from a 32-bit to 16-bit
integer, which results in a smaller code size, and changed the computation of the timeout
slightly, so the LED update interval is always the same (we add `syncDelay` to the
previous update's start time, not it's end time), rather than varying based on when
LEDControl's `loopHook()` function is called relative to the last timeout.
Add LEDControl.paused, which we use to pause LED mode updates if true (defaults
to false). This is useful when we want to stop LED modes from updating without
switching to another (like when the host goes to sleep, and we want to turn LEDs
off).
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Intended to force-reactivate the current LED mode, in case we want to refresh
the whole board, and make sure we do so even if the current mode's update is a
no-op.
This can happen when we overrode some keys, and it becomes less costly to update
everything than to iterate over the updated keys.
Signed-off-by: Gergely Nagy <kaleidoscope@gergo.csillger.hu>
There were a number of issues with the model we had before, namely that plugins
that changed LED colors outside of LED modes had no way to signal "go back to
whatever color this key was". To this end, the `LEDMode.refreshAt` method is
introduced, which these plugins can call to tell the mode to update a given key.
As part of this, the API was redesigned, with code that is common between all
LED modes moving to the base class, among other things, much better names, and a
flow of control that is easier to follow.
In the new setup, there are four methods a LED mode can implement:
- `setup()` to do boot-time initialization (registering hooks, etc).
- `onActivate()` called every time the mode is activated.
- `update()` called each cycle.
- `refreshAt()` may be called by other plugins to refresh a particular key.
All of these are protected methods, to be called via `LEDControl` only.
Much of the new API design was done by @cdisselkoen, huge thanks for his work!
Fixes#9.
Signed-off-by: Gergely Nagy <kaleidoscope@gergo.csillger.hu>
The new `init_mode()` method simply (re-)inits the current mode. Useful when a
plugin that changes LEDs outside of LED modes wants to reset the active LED
mode. Doubly useful when the active LED mode does all the work in its `init()`
method.
Signed-off-by: Gergely Nagy <algernon@madhouse-project.org>
Allows setting all of the LEDs to custom, distinct colors (as opposed to
`led.setAll`, which sets them all to the same color). This allows one to
upload a theme in one go, without having to set each LED one by one.
Fixes#5.
Signed-off-by: Gergely Nagy <algernon@madhouse-project.org>
Because `led_sync` is a major cause of slowness, do not sync every cycle. In
most cases, it is pointless to sync 100 times a second, about 60 - or even 30 -
may be more than enough.
For this reason, introduce a timer, and a settable delay: we'll only call
`led_sync` once the delay's up. It can be set to 0 to call it every time, but
defaults to 16 (for about 62 syncs/sec), as a safe bet.
This speeds the loop up dramatically, except for the few exceptions where sync
is called.
Fixes#1.
Signed-off-by: Gergely Nagy <algernon@madhouse-project.org>