You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Kaleidoscope/src/kaleidoscope_internal/event_dispatch.h

220 lines
14 KiB

/* 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 <http://www.gnu.org/licenses/>.
*/
/*
* This file contains pre-processor macros, which are not namespaced, the
* vast majority of code these macros generate will live under the
* kaleidoscope_internal namespace. That is why this header is here.
*
* The only exception to this is the _KALEIDOSCOPE_INIT_PLUGINS macro, which
* also places a few symbols under the kaleidoscope namespace. But that code is
* tightly coupled with the other parts, which are in kaleidoscope_internal.
*
* The reason we use an entirely separate namespace is that
* Some plugins' classes are in the kaleidoscope namespace, and the instances
* of those classes have the same names as the classes, but in the global
* namespace. In these cases, the macros herein would resolve to the classes,
* when we want them to resolve to instances. To avoid this, we put the
* generated code in an entirely different namespace.
*/
#pragma once
#include "kaleidoscope/macro_helpers.h"
#include "kaleidoscope/plugin.h"
#include "kaleidoscope/hooks.h"
#include "kaleidoscope_internal/eventhandler_signature_check.h"
#include "kaleidoscope/event_handlers.h"
#include "kaleidoscope_internal/sketch_exploration/sketch_exploration.h"
// Some words about the design of hook routing:
//
// The EventDispatcher class implements a compile-time loop over all plugins, which
// calls an event handler on each plugin.
//
// Each hook called by the firmware core compiles down to only a single
// static function call (in class Hooks), rather than one for each plugin.
//
// This approach is better than using virtual event handlers in classes
// derived from kaleidoscope::Plugin because it results in significantly fewer
// virtual function calls and virtual function tables (vtables).
//
// The call to event handlers through kaleidoscope::Hooks and
// kaleidoscope_internal::EventDispatcher is templated to allow for compile time
// static polymorphisms (plugins' EventHandlers aren't virtual).
//
// Technically, plugins don't need to be derived from kaleidoscope::Plugin, but
// only need to implement a set of event handlers whose call signature match the
// API definition. This approach is similar to duck typing in scripting languages:
// The compiler only cares that a method's signature matches, not that the plugin
// is derived from kaleidoscope::Plugin.
//
// Static hook functions inside the Hooks class each use the EventDispatcher
// helper class to cast an associated EventHandler on each plugin instance.
// This defines an auxiliary class 'EventHandler_Foo' for each hook 'Foo'.
// Kaleidoscope::Hooks calls the EventDispatcher class, which in turn invokes
// the event handler method 'Foo' of each registered plugin with a given
// set of arguments.
// The following macros help to generate type or instance names
// by concatenating macro parameter tokens.
//
#define _NAME3(A, B, C) A##B##C
#define _NAME4(A, B, C, D) A##B##C##D
#define _NAME5(A, B, C, D, E) A##B##C##D##E
#define _REGISTER_EVENT_HANDLER( \
HOOK_NAME, HOOK_VERSION, DEPRECATION_TAG, \
SHOULD_ABORT_ON_CONSUMED_EVENT, \
TMPL_PARAM_TYPE_LIST, TMPL_PARAM_LIST, TMPL_DUMMY_ARGS_LIST, \
SIGNATURE, ARGS_LIST) __NL__ \
__NL__ \
namespace kaleidoscope_internal { __NL__ \
__NL__ \
template<bool hook_is_implemented__, __NL__ \
typename Plugin__ __NL__ \
UNWRAP TMPL_PARAM_TYPE_LIST /* may evaluate empty */ __NL__ \
, typename... Args__> __NL__ \
struct _NAME5(EventHandler_, HOOK_NAME, _v, HOOK_VERSION, _caller) { __NL__ \
static DEPRECATION_TAG kaleidoscope::EventHandlerResult __NL__ \
call(Plugin__ &plugin, Args__&&... hook_args) { __NL__ \
return plugin.TEMPLATE_KEYWORD(UNWRAP TMPL_PARAM_LIST) HOOK_NAME __NL__ \
ADD_TEMPLATE_BRACES(UNWRAP TMPL_PARAM_LIST) __NL__ \
(hook_args...); __NL__ \
} __NL__ \
}; __NL__ \
__NL__ \
/* This specialization is used for those hooks that a plugin does not __NL__ \
* implement. __NL__ \
*/ __NL__ \
template<typename Plugin__ __NL__ \
UNWRAP TMPL_PARAM_TYPE_LIST /* may evaluate empty */ __NL__ \
, typename... Args__> __NL__ \
struct _NAME5(EventHandler_, HOOK_NAME, _v, HOOK_VERSION, _caller) __NL__ \
<false, Plugin__ UNWRAP TMPL_PARAM_LIST, Args__...> { __NL__ \
static kaleidoscope::EventHandlerResult __NL__ \
call(Plugin__ &/*plugin*/, Args__&&... /*hook_args*/) { __NL__ \
return kaleidoscope::EventHandlerResult::OK; __NL__ \
} __NL__ \
}; __NL__ \
__NL__ \
MAKE_TEMPLATE_SIGNATURE(UNWRAP TMPL_PARAM_TYPE_LIST) __NL__ \
struct _NAME4(EventHandler_, HOOK_NAME, _v, HOOK_VERSION) { __NL__ \
__NL__ \
static bool shouldAbortOnConsumedEvent() { __NL__ \
return SHOULD_ABORT_ON_CONSUMED_EVENT; __NL__ \
} __NL__ \
__NL__ \
template<typename Plugin__, __NL__ \
typename... Args__> __NL__ \
static kaleidoscope::EventHandlerResult __NL__ \
call(Plugin__ &plugin, Args__&&... hook_args) { __NL__ \
__NL__ \
_VALIDATE_EVENT_HANDLER_SIGNATURE(HOOK_NAME, Plugin__) __NL__ \
__NL__ \
static constexpr bool derived_implements_hook __NL__ \
= HookVersionImplemented_##HOOK_NAME< __NL__ \
Plugin__, HOOK_VERSION>::value; __NL__ \
__NL__ \
/* The caller type adds another level of indirection that */ __NL__ \
/* is required to enable some hooks not to be implemented */ __NL__ \
/* by plugins. */ __NL__ \
__NL__ \
typedef _NAME5(EventHandler_, HOOK_NAME, _v, HOOK_VERSION, _caller) __NL__ \
< __NL__ \
derived_implements_hook, __NL__ \
Plugin__ __NL__ \
UNWRAP TMPL_PARAM_LIST __NL__ \
, Args__... __NL__ \
> Caller; __NL__ \
__NL__ \
return Caller::call(plugin, hook_args...); __NL__ \
} __NL__ \
}; __NL__ \
__NL__ \
} __NL__ \
__NL__ \
namespace kaleidoscope { __NL__ \
__NL__ \
MAKE_TEMPLATE_SIGNATURE(UNWRAP TMPL_PARAM_TYPE_LIST) __NL__ \
EventHandlerResult Hooks::HOOK_NAME SIGNATURE { __NL__ \
return kaleidoscope_internal::EventDispatcher::template __NL__ \
apply<kaleidoscope_internal __NL__ \
::_NAME4(EventHandler_, HOOK_NAME, _v, HOOK_VERSION) __NL__ \
ADD_TEMPLATE_BRACES(UNWRAP TMPL_PARAM_LIST) __NL__ \
>ARGS_LIST; __NL__ \
} __NL__ \
__NL__ \
}
#define _INLINE_EVENT_HANDLER_FOR_PLUGIN(PLUGIN) \
__NL__ \
result = EventHandler__::call(PLUGIN, hook_args...); __NL__ \
__NL__ \
if (EventHandler__::shouldAbortOnConsumedEvent() && __NL__ \
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) { __NL__ \
return result; __NL__ \
} __NL__
// _KALEIDOSCOPE_INIT_PLUGINS builds the loops that execute the plugins'
// implementations of the various event handlers.
//
// Its arguments are a list of references to plugin instances that have been
// instantiated in the global scope.
// EventDispatcher::apply() implements a compile time for-each loop over all
// plugins. The compiler automatically optimizes away calls to any plugin that
// doesn't implement an EventHandler for a given hook.
#define _KALEIDOSCOPE_INIT_PLUGINS(...) __NL__ \
namespace kaleidoscope_internal { __NL__ \
struct EventDispatcher { __NL__ \
__NL__ \
/* Iterate through plugins, calling each one's event handler with */ __NL__ \
/* the arguments passed to the hook */ __NL__ \
template<typename EventHandler__, typename... Args__ > __NL__ \
static kaleidoscope::EventHandlerResult apply(Args__&&... hook_args) { __NL__ \
__NL__ \
kaleidoscope::EventHandlerResult result; __NL__ \
MAP(_INLINE_EVENT_HANDLER_FOR_PLUGIN, __VA_ARGS__) __NL__ \
__NL__ \
return result; __NL__ \
} __NL__ \
}; __NL__ \
__NL__ \
} __NL__ \
/* We register event handlers here - which is not technically related */ __NL__ \
/* to initialization, nor is it in the same namespace - to support the */ __NL__ \
/* transition from the old APIs. When the user sketch does not use */ __NL__ \
/* KALEIDOSCOPE_INIT_PLUGINS(), the event handlers will not exist */ __NL__ \
/* either, thus wrapping them would produce a compile error. For this */ __NL__ \
/* reason, we do the wrapping here, tied to _KALEIDOSCOPE_INIT_PLUGINS. */ __NL__ \
/* */ __NL__ \
/* TODO(anyone): Move this somewhere else, outside of _internal, once */ __NL__ \
/* the V1 API is removed. */ __NL__ \
__NL__ \
_PREPARE_EVENT_HANDLER_SIGNATURE_CHECK __NL__ \
__NL__ \
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 <florian.fleissner@inpartik.de>
6 years ago
_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__) __NL__ \
__NL__ \
_INIT_PLUGIN_EXPLORATION(__VA_ARGS__)