-
Notifications
You must be signed in to change notification settings - Fork 257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Redesign of the plugin and hooking interface II #276
Redesign of the plugin and hooking interface II #276
Conversation
Let KaleidoscopePlugin::init() call KaleidoscopePlugin::begin() to support legacy plugins Added missing startLoopHook() and endLoopHook() calls. Removed HookAdapterAccessor. Fixed astyle formatting errors Replaced loop hook method names Fixed a small hook method naming error caused by regex replacement Added instuctions about how to deal with pre-processed code Fixed code style Fixed a typo Added hook signature checks and fixed macro continuation line alignment Added a check that ensures at compile time that derived plugins' hook methods have the correct signature. Fixed the alignment of macro continuation line markers. Added reporting of the culprit plugin to hook method signature checks Refactored Kaleidoscope.h that has grown too large Some modification to increases hook signature check error output Added a missing newline Fixed case of hook task helper classes Implemented major simplifications of the new plugin hooking system Applied make astyle Mod Redesign of the plugin and hooking interface (II) astyle
Travis tests for this PR fail because of an attempt to build the firmware with an unsuitable sketch file. If you want to try the latest changes, keep track of my branch |
@@ -9,6 +11,9 @@ Kaleidoscope_::Kaleidoscope_(void) { | |||
|
|||
void | |||
Kaleidoscope_::setup(void) { | |||
|
|||
kaleidoscope::Hooks::init(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hooks are now access via static functions of class Hook
. The call to init()
initializes all plugins. This is what formerly has been done through Kaleidoscope.run()
.
|
||
#include KALEIDOSCOPE_HARDWARE_H | ||
#include "key_events.h" | ||
#include "kaleidoscope/hid.h" | ||
#include "layers.h" | ||
#include "details/preprocessor_macro_map.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementation details now live in files below directory details
.
@@ -36,29 +40,7 @@ extern HARDWARE_IMPLEMENTATION KeyboardHardware; | |||
const uint8_t KEYMAP_SIZE | |||
__attribute__((deprecated("Kaleidoscope.setup() does not require KEYMAP_SIZE anymore."))) = 0; | |||
|
|||
class Kaleidoscope_; | |||
|
|||
class KaleidoscopePlugin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Class KaleidoscopePlugin
now lives in header plugin.h
it was renamed to kaleidoscope::Plugin
, thereby keeping the original name as deprecated type.
static void replaceEventHandlerHook(eventHandlerHook oldHook, eventHandlerHook newHook); | ||
static void appendEventHandlerHook(eventHandlerHook hook); | ||
static void useEventHandlerHook(eventHandlerHook hook); | ||
static void replaceEventHandlerHook(eventHandlerHook oldHook, eventHandlerHook newHook) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Old event handlers were declared deprecated.
|
||
static bool focusHook(const char *command); | ||
}; | ||
|
||
extern Kaleidoscope_ Kaleidoscope; | ||
extern kaleidoscope::Kaleidoscope_ Kaleidoscope; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The singleton instance Kaleidoscope
has for compatibility reasons moved to namespace kaleidoscope
. The old name is preserved as a deprecated symbol.
__attribute__((deprecated("Use Kaleidoscope.useEventHandlerHook instead"))); | ||
void loop_hook_use(Kaleidoscope_::loopHook hook) | ||
void loop_hook_use(kaleidoscope::Kaleidoscope_::loopHook hook) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added appropriate namespace.
// hooking system. The macro accepts a list of plugin instances that | ||
// must have been instanciated at global scope. | ||
// | ||
#define KALEIDOSCOPE_INIT_PLUGINS(...) _KALEIDOSCOPE_INIT_PLUGINS(__VA_ARGS__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This macro is used in the sketch at global scope in global namespace to register plugins.
Could be renamed to KALEIDOSCOPE_REGISTER_PLUGINS(...)
. Maybe that's a better name.
// return type of a hook member function. | ||
// | ||
template<typename T> | ||
struct ReturnTypeTraits {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Traits classes are needed to determine specific properties of C++ types at compile time. They are an essential part of TMP. Here we determine the return type of a specific hook method.
// It must be invoked in global namespace as it adds namespace kaleidoscope | ||
// to the Hooks' function implementations. | ||
// | ||
#define _HOOKS_STATIC_METHODS_IMPLEMENTATION \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the implementation of the static functions of class Hook
. The implementation depends on the registered plugins that are considered by the HookLoop
class. That is the reason why it must be implemented within a macro. The macro is called at the end of _KALEIDOSCOPE_INIT_PLUGINS
.
|
||
typedef union Key_ { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In C we have to add a typedef to be able to use a struct or union name without prepending it with keywords struct
or union
. This typedef is useless in C++.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original name Key_
is preserved by an appropriate typedef in global namespace (see below).
@@ -28,14 +29,14 @@ typedef union Key_ { | |||
inline bool operator==(uint16_t rhs) { | |||
return this->raw == rhs; | |||
} | |||
inline bool operator==(const Key_ rhs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After removing the typdedef of Key_
to Key
, we have to operate on Key
.
// For compatibility reasons make the Key class also available | ||
// in global namespace. | ||
// | ||
typedef kaleidoscope::Key Key __attribute__((deprecated("class Key in global namespace is deprecated. Please use kaleidoscope::Key instead."))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compatibility typedefs.
|
||
class Kaleidoscope_; | ||
|
||
struct EventKey { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameters of the old eventHandlerHook have been grouped in struct EventKey
. This enables adding further parameters in the future without breaking the interface.
// be called from other places than the friend classes and functions listed | ||
// below, just add a suitable friend declaration. | ||
|
||
class Hooks { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any (core) compilation units that are supposed to call hook functions only include details/hooks.h
without a need to know anything about hook routing. Header hooks.h
has been moved to details
to make sure that it is not part of the plugin interface.
Thanks for the new PR, and the explanations. I started to work my way through the previous, but wasn't able to finish due to Christmas. I'll work through this one between the holidays. |
… backwards compatibility
src/details/hooks.cpp
Outdated
|
||
namespace kaleidoscope { | ||
|
||
__attribute__((weak)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added weak implementation of Hooks
's static methods to enable backwards compatibility. Without this, any sketch that does not register new plugins with KALEIDOSCOPE_INIT_PLUGINS(...)
would cause undefined symbol errors because of the missing methods of class Hooks
.
Added a comment to explain the use of weak symbols in hooks.cpp
Updated the comment about how to add new hooks
…e inside of hook tasks.
In the past few weeks, while trying to figure out how we can get this in, we arrived at a possible way to do so, that we hope will satisfy everyone involved. But before I get there, a short explanation why this has not been merged yet. The new API looks good, and is a direction we want to go in. The PROGMEM & RAM savings are also very lucrative. But therein lies the problem: to get there, one needs to do some non-trivial amounts of "magic". While most - if not all - of that would be invisible for plugin developers and end-users, it is something that is hard to understand for those with less C++ knowledge (myself included). As such, we did not feel comfortable merging code we have trouble understanding - I went through it, and with the help of your comments and explanation, I understand it. But it wasn't easy to get there, to follow the code. We want to end up with something maintainable in the long run, something we understand too, something we can explain to future contributors too. Yet, we do want to have everything in, eventually. So I'd propose a two-stage merge: first we get the new API in, but with virtual functions (and thus less magic), and the rooting part in a separate step. If my understanding is correct, doing this second step separately would not break the API, so plugins would not need to change. This also has the advantage of having a clearer picture about what's the new API, and what is the optimization that allows us to skip using virtuals. What do you think about this approach? (I'm happy to do the grunt work this would involve, fwiw). |
Yeah, I know, the implementation is not a beauty. But that's just because C++ is a beast ;-) I am curious to see, how you are planning to implement the part that gets rid of statically sized arrays. I am not completely convinced of the two-stage merge. Once you introduce virtual hook methods in the |
I think I made a breakthrough, that will allow us to do a one-stage merge, with less magic. The key is the Something along these lines: #define KALEIDOSCOPE_MAKE_PLUGIN_HOOK(hookName, method) \
inline void hookName () {} \
inline void hookName(auto *p) { \
p->method(); \
} \
template <typename... Plugins> \
void hookName(auto *first, Plugins&&... plugins) { \
hookName(first); \
hookName(plugins...); \
}
KALEIDOSCOPE_MAKE_PLUGIN_HOOK(runPreScan, preScan);
KALEIDOSCOPE_MAKE_PLUGIN_HOOK(runPostScan, postScan); However, this does come with downsides too: we can pass anything to these functions, as long as they have the appropriately named method. They do not have to be children of the base I think this is something we can live with, because the code is - in my opinion at least - easier to understand, and the compiled thing should be pretty much the same, too. |
You could use a template to achieve the same, like template<typename Plugin__>
inline void hookName(Plugin__ *p) {
p->method();
} That would be proper C++, but would also allow the duck typing you mentioned. |
Hm, indeed, it doesn't work with c++11, needs c++14. Will try your suggestion, and see if it works with the gcc shipped with Arduino. (So far, I've been trying with a much more recent gcc) Update: #define KALEIDOSCOPE_MAKE_PLUGIN_HOOK(hookName, method) \
inline void hookName () {} \
template<typename Plugin__> \
inline void hookName (Plugin__ *p) { p->method(); } \
template <typename Plugin__, typename... Plugins> \
void hookName(Plugin__ *first, Plugins&&... plugins) { \
hookName(first); \
hookName(plugins...); \
} This compiles with |
But what about those hooks whose return values decide about further processing? And what about the call parameters of some of the hooks? Those are the reason why the original magic was there in the first place. |
FWIW, |
Quick update: there will be movement forwards on this soon. I already summed up my work for @obra, will do so in public too very soon. Just wanted to note that there is progress being made, and the redesign is not forgotten. :) |
}; | ||
|
||
_HOOK_TASK(init, ContinueIfHookReturnsTrue) // Defines HookTask_init | ||
_HOOK_TASK(eventHandlerHook, AlwaysContinue) // Defines HookTask_eventHandlerHook |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't init
be AlwaysContinue
(since it is void anyway), and eventHandlerHook
ContinueIfHookReturns
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you are right that's a mistake. Unless ... you want to let plugin initializations block each other, which does not make much sense.
I would strongly recommend to add #if !KALEIDOSCOPE_DISABLE_V1_API
virtual void begin(void) {}
#else
void begin(void) {}
#endif in src/plugin.h. Otherwise, all plugins will have a useless virtual function pointer&table even when the old API is disabled. FWIW, the second |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have made a few changes to this PR, on the f/plugin-redesign-2.1 branch of Kaleidoscope. The major differences are:
- A few things this PR deprecates are undeprecated (either because they resulted in warnings even in cases where the old APIs weren't even used, or because they introduced too much change)
- I got rid of
EventKey
for now, because it is an orthogonal change, which we'd like to handle separately (within the same major version, but separate PR, with a separate discussion, possibly together with merging row+col into an index) - The old API can be disabled with a compiler flag.
- I added a WIP document explaining how the template & preprocessor magic works, along with an example or two to follow how the code is transformed.
OrderedPlugins
was moved tokaleidoscope_internal
, due to a naming pattern that caused issues (the compiler tried to use a class as an object, and failed in spectacular ways with cryptic messages).
Other than this, I have one question, which I asked in-line.
I still need to finish up a few loose ends, go over naming with @obra, but finally there is progress I can talk about!
I'd love to have feedback on the changes, and on the WIP document I prepared in the aforementioned branch.
Thanks, added. Also fixed the |
Just read your WIP document. Sweet! 😍 Never has any code that I've written been explained so nicely. Joking apart. Really great job. I know how hard it can be to understand and deal with other peoples' code. All your changes make sense to me. I am still fond of the idea of plugin grouping. If you are interested I can provide a PR as soon as the redesign and your changes have been merged to master. |
I'm sorry if it came/comes across as demonizing. Most of my issues I may have voiced are more against C++ than your code in particular. (Did I mention I'm not a big fan of C++? But Clojure isn't well suited for keyboard firmware, so C++ it is for now :p) Once I managed to figure it all out, connect the pieces, when it dawned on me how it makes things work, I started to see it in a very different light. It is a beautiful piece of code. Twisted too, and tangled, with a hint of madness sprinkled on top - but beautiful nevertheless.
I think I like the idea, but I'd suggest opening an issue first, so we can discuss naming and perhaps a few other things. This would allow you to only start working once things like that are agreed upon. Noone would need to update it again, would we find the need to change a name or two here and there. |
There are times when I feel the same. But I am afraid, I am too old to switch to another nicer low level language. Hopefully, C++ will stay around for a while.
You are a true poet.
Good idea. |
@algernon: Could you open a temporary PR with your changes in f/plugin-redesign-2.1? I found some small issues in the WIP document and it would be easiest if I could comment the respective lines directly. |
Done, #316. |
Closing this in favour of #316. |
With this redesign, we introduce a new way to create plugins, which is easier to extend with new hook points, provides a better interface, uses less memory, less program space, and is a tiny bit faster too. It all begins with `kaleidoscope::Plugin` being the base class, which provides the hook methods plugins can implement. Plugins should be declared with `KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro is a bit of magic (see the new documentation) that allows us to unroll the hook method calls, avoid vtables, and so on. It creates an override for `kaleidoscope::Hooks::*` methods, each of which will call the respective methods of each initialized plugin. With the new API come new names: all of the methods plugins can implement received new, more descriptive names that all follow a similar pattern. The old (dubbed V1) API still remains in place, although deprecated. One can turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero, while compiling the firmware. This work is based on #276, written by @noseglasses. @obra and @algernon did some cleaning up and applied a little naming treatment. Signed-off-by: noseglasses <shinynoseglasses@gmail.com> Signed-off-by: Gergely Nagy <algernon@keyboard.io>
With this redesign, we introduce a new way to create plugins, which is easier to extend with new hook points, provides a better interface, uses less memory, less program space, and is a tiny bit faster too. It all begins with `kaleidoscope::Plugin` being the base class, which provides the hook methods plugins can implement. Plugins should be declared with `KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro is a bit of magic (see the new documentation) that allows us to unroll the hook method calls, avoid vtables, and so on. It creates an override for `kaleidoscope::Hooks::*` methods, each of which will call the respective methods of each initialized plugin. With the new API come new names: all of the methods plugins can implement received new, more descriptive names that all follow a similar pattern. The old (dubbed V1) API still remains in place, although deprecated. One can turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero, while compiling the firmware. This work is based on #276, written by @noseglasses. @obra and @algernon did some cleaning up and applied a little naming treatment. Signed-off-by: noseglasses <shinynoseglasses@gmail.com> Signed-off-by: Jesse Vincent <jesse@keyboard.io> Signed-off-by: Gergely Nagy <algernon@keyboard.io>
With this redesign, we introduce a new way to create plugins, which is easier to extend with new hook points, provides a better interface, uses less memory, less program space, and is a tiny bit faster too. It all begins with `kaleidoscope::Plugin` being the base class, which provides the hook methods plugins can implement. Plugins should be declared with `KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro is a bit of magic (see the new documentation) that allows us to unroll the hook method calls, avoid vtables, and so on. It creates an override for `kaleidoscope::Hooks::*` methods, each of which will call the respective methods of each initialized plugin. With the new API come new names: all of the methods plugins can implement received new, more descriptive names that all follow a similar pattern. The old (dubbed V1) API still remains in place, although deprecated. One can turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero, while compiling the firmware. This work is based on #276, written by @noseglasses. @obra and @algernon did some cleaning up and applied a little naming treatment. Signed-off-by: noseglasses <shinynoseglasses@gmail.com> Signed-off-by: Jesse Vincent <jesse@keyboard.io> Signed-off-by: Gergely Nagy <algernon@keyboard.io>
With this redesign, we introduce a new way to create plugins, which is easier to extend with new hook points, provides a better interface, uses less memory, less program space, and is a tiny bit faster too. It all begins with `kaleidoscope::Plugin` being the base class, which provides the hook methods plugins can implement. Plugins should be declared with `KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro is a bit of magic (see the in-code documentation) that allows us to unroll the hook method calls, avoid vtables, and so on. It creates an override for `kaleidoscope::Hooks::*` methods, each of which will call the respective methods of each initialized plugin. With the new API come new names: all of the methods plugins can implement received new, more descriptive names that all follow a similar pattern. The old (dubbed V1) API still remains in place, although deprecated. One can turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero, while compiling the firmware. This work is based on #276, written by @noseglasses. @obra and @algernon did some cleaning up and applied a little naming treatment. Signed-off-by: noseglasses <shinynoseglasses@gmail.com> Signed-off-by: Jesse Vincent <jesse@keyboard.io> Signed-off-by: Gergely Nagy <algernon@keyboard.io>
With this redesign, we introduce a new way to create plugins, which is easier to extend with new hook points, provides a better interface, uses less memory, less program space, and is a tiny bit faster too. It all begins with `kaleidoscope::Plugin` being the base class, which provides the hook methods plugins can implement. Plugins should be declared with `KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro is a bit of magic (see the in-code documentation) that allows us to unroll the hook method calls, avoid vtables, and so on. It creates an override for `kaleidoscope::Hooks::*` methods, each of which will call the respective methods of each initialized plugin. With the new API come new names: all of the methods plugins can implement received new, more descriptive names that all follow a similar pattern. The old (dubbed V1) API still remains in place, although deprecated. One can turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero, while compiling the firmware. This work is based on #276, written by @noseglasses. @obra and @algernon did some cleaning up and applied a little naming treatment. Signed-off-by: noseglasses <shinynoseglasses@gmail.com> Signed-off-by: Jesse Vincent <jesse@keyboard.io> Signed-off-by: Gergely Nagy <algernon@keyboard.io>
With this redesign, we introduce a new way to create plugins, which is easier to extend with new hook points, provides a better interface, uses less memory, less program space, and is a tiny bit faster too. It all begins with `kaleidoscope::Plugin` being the base class, which provides the hook methods plugins can implement. Plugins should be declared with `KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro is a bit of magic (see the in-code documentation) that allows us to unroll the hook method calls, avoid vtables, and so on. It creates an override for `kaleidoscope::Hooks::*` methods, each of which will call the respective methods of each initialized plugin. With the new API come new names: all of the methods plugins can implement received new, more descriptive names that all follow a similar pattern. The old (dubbed V1) API still remains in place, although deprecated. One can turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero, while compiling the firmware. This work is based on #276, written by @noseglasses. @obra and @algernon did some cleaning up and applied a little naming treatment. Signed-off-by: noseglasses <shinynoseglasses@gmail.com> Signed-off-by: Jesse Vincent <jesse@keyboard.io> Signed-off-by: Gergely Nagy <algernon@keyboard.io>
With this redesign, we introduce a new way to create plugins, which is easier to extend with new hook points, provides a better interface, uses less memory, less program space, and is a tiny bit faster too. It all begins with `kaleidoscope::Plugin` being the base class, which provides the hook methods plugins can implement. Plugins should be declared with `KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro is a bit of magic (see the in-code documentation) that allows us to unroll the hook method calls, avoid vtables, and so on. It creates an override for `kaleidoscope::Hooks::*` methods, each of which will call the respective methods of each initialized plugin. With the new API come new names: all of the methods plugins can implement received new, more descriptive names that all follow a similar pattern. The old (dubbed V1) API still remains in place, although deprecated. One can turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero, while compiling the firmware. This work is based on #276, written by @noseglasses. @obra and @algernon did some cleaning up and applied a little naming treatment. Signed-off-by: noseglasses <shinynoseglasses@gmail.com> Signed-off-by: Jesse Vincent <jesse@keyboard.io> Signed-off-by: Gergely Nagy <algernon@keyboard.io>
With this redesign, we introduce a new way to create plugins, which is easier to extend with new hook points, provides a better interface, uses less memory, less program space, and is a tiny bit faster too. It all begins with `kaleidoscope::Plugin` being the base class, which provides the hook methods plugins can implement. Plugins should be declared with `KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro is a bit of magic (see the in-code documentation) that allows us to unroll the hook method calls, avoid vtables, and so on. It creates an override for `kaleidoscope::Hooks::*` methods, each of which will call the respective methods of each initialized plugin. With the new API come new names: all of the methods plugins can implement received new, more descriptive names that all follow a similar pattern. The old (dubbed V1) API still remains in place, although deprecated. One can turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero, while compiling the firmware. This work is based on #276, written by @noseglasses. @obra and @algernon did some cleaning up and applied a little naming treatment. Signed-off-by: noseglasses <shinynoseglasses@gmail.com> Signed-off-by: Jesse Vincent <jesse@keyboard.io> Signed-off-by: Gergely Nagy <algernon@keyboard.io>
With this redesign, we introduce a new way to create plugins, which is easier to extend with new hook points, provides a better interface, uses less memory, less program space, and is a tiny bit faster too. It all begins with `kaleidoscope::Plugin` being the base class, which provides the hook methods plugins can implement. Plugins should be declared with `KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro is a bit of magic (see the in-code documentation) that allows us to unroll the hook method calls, avoid vtables, and so on. It creates an override for `kaleidoscope::Hooks::*` methods, each of which will call the respective methods of each initialized plugin. With the new API come new names: all of the methods plugins can implement received new, more descriptive names that all follow a similar pattern. The old (dubbed V1) API still remains in place, although deprecated. One can turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero, while compiling the firmware. This work is based on #276, written by @noseglasses. @obra and @algernon did some cleaning up and applied a little naming treatment. Signed-off-by: noseglasses <shinynoseglasses@gmail.com> Signed-off-by: Jesse Vincent <jesse@keyboard.io> Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Finally, I found some time to think a bit more about the redesign of the plugin hooking interface started as #240 .
Parts of the changes proposed in #240 were not yet optimal, namely
Kaleidoscope_
is preferred against other classes and free functions of the core that need to call hook methods.Kaleidoscope
. This type of access is overkill as the adaptor class could actually be a singleton.eventHandlerHook
.The solution to all this is very simple. Let's have a
Hooks
class that has non-virtual but static functions that define the entry points to hook method routing. This allows for proper encapsulation to allow calls to hook methods only from specific classes and functions of the firmware core.This new approach solves issue 1). By using global functions as hook entry points, we automatically also get rid of issues 2) and 3).
This change improves performance (removal of virtual functions) and PROGMEM and RAM storage (removal of vtables and vptrs as we do not have any polymorphic classes involved).
The reason why this did not come to my mind in the first place is, that I was distracted by the term plugin. In other contexts, plugin refers to something runtime pluggable and unpluggable.
In the firmware, however, plugins ase hardwired. This provides great potential for optimizations as we do not have to care for polymorphic interfaces.
This PR is a squashed version of #240 that eventually consisted of too many individual commits. Also #240 started to become unreadable. That's why I started a new PR.
Changes against #240
Hook routing system
The auxiliary polymorphic classes
PluginHookAdapter
andPluginHookAdapter
were removed and replaced by a new classHooks
that encapsules the entry points of the hook routing system.Plugin registration
The macro
KALEIDOSCOPE_CONNECT_PLUGINS
is no more necessary, as a registration of the hooking system with classKaleidoscope_
in no more necessary. Now plugins can be registered via a single invocation of function macroKALEIDOSCOPE_INIT_PLUGINS(...)
.Addition of new hooks
To add a new hook, changes are now required in three places instead of four.EDIT 1: This is no more true after the introduction of weak symbols in
details/hooks.cpp
. There remain four places where code for new hooks has to be added. This is documented at the top ofdetails/hook_routing.h
.Encapsulation
The restriction of who is allowed to invoke a hook is now centralized by defining
friends of class
Hooks
. This is more transparent than adding friend declarations toKaleidoscope_
and much more safe than allowing hooks to be called by everybody.Naming
Symbol names
Appart from some symbol name changes in the internals of the hook routing system.
The plugin base class is now
kaleidoscope::Plugin
(with a compatibility typedefKaleidoscopePlugin
in global namespace. I allowed myself the freedom to rename existing symbols that appear in newly written code, to reduce the maintainance overhead that is necessary when thesesymbols are eventually changed. Any symbols whose names start with Kaleidoscope should be renamed when moved to
namespace kaleidoscope
to keep names short.Macro names
Internal macros (those defined and used in code parts that are considered as details)
have been prefixed with a single underscore.
Namespaces
As I implemented quite a lot of new code, I added everything to namespace
kaleidoscope
. We could have a nested namespacekaleidoscope::hooks
but I am not sure that would help much.To make it fully compatible, I moved some of the existing classes
Kaleidoscope_
,Key
andKaleidoscopePlugin
to namespacekaleidoscope
. This changes is not supposed to break anything as all existing classes were typedefed in global namespace and declared deprecated.Implementation details
The hooking system comes with a great deal of implementation details that are quite complex for C++ newbies. All those parts that are definitely not meant to be read and used by non core-maintainters have been moved to a subdirectory details. I recommend to do the same with all other stuff that is not meant for the eyes of plugin developers. It is a good custom to flag certain parts of the code as "poisonous" to tell new users that it is not necessary to deal with this part of the code in order to implement new functionality. Also adding code to detail folders informs that user that those parts are subject to changes and do by no means define a programming interface.