template<class Param, class ... Tail>
class jevois::Parameter< Param, Tail ... >
A set of Parameters attached to a component.
This variadic template class is just for the convenience of adding several parameters to a Component in one statement.
The way in which we have implemented Parameter in the JeVois framework may seem unorthodox at first, but is the best way we have found so far in terms of minimizing burden when writing new components with lots of parameters. In our earlier framework, the iLab Neuromorphic Vision Toolkit (iNVT) started in 1995, parameters were included in components as member variables. The burden to programmers was so high that often they just did not include parameters and hardwired values instead, just to avoid that burden. The burden comes from the requirements:
- we want to be able to support parameters of any type
- we want each parameter to have a name, description, default value, specification of valid values
- we want parameters to appear in related groups in the help message
- we want to support callbacks, i.e., functions that are called when one tries to change the parameter value
- we typically want the callback to be a member function of the Component that owns a given parameter, since changing that parameter value will typically trigger some re-organization in that Component (otherwise the callback might not be needed).
Possible implementation using class data members for parameters (similar to what we used in iNVT), here shown for a sample int parameter to specify the size of a queue held in a class MyComp that derives from Component:
ParamDef<int> sizeparamdef(
"size",
"Queue size", 5,
Range<int>(1, 100), categ);
{
public:
Param<int> sizeparam;
void sizeParamCallback(int newval) { myqueue.resize(newval); }
MyComp(std::string const & instance) :
sizeparam(sizeparamdef)
{
sizeparam.setCallback(&MyComp::sizeParamCallback);
}
};
A component of a model hierarchy.
Main namespace for all JeVois classes and functions.
So we basically end up with 3 names that people have no idea what to do with and will just use confusing names for (sizeparamdef, sizeparam, sizeParamCallback), and we have to 1) specify the definition of name, description, etc somewhere using some arbitrary name (here sizeparamdef), then add the member variable for the param to the component using some other name (here sizeparam), then construct the param which would typically require linking it to its definition so we can get the default value and such, and finally hook the callback up (note how MyComp is not fully constructed yet when we construct sizeparam hence referencing sizeParamCallback() at that time is dubious at best). In reality, things are even worse since typically the paramdef, component class declaration, and component implementation, should be in 3 different files.
- Note
- `‘There are only two hard things in Computer Science: cache invalidation and naming things.’' – Phil Karlton
The approach we developed for the Neuromorphic Robotics Toolkit (NRT) and refined for JeVois works as follows:
- each parameter is a unique new class type. We create that type once with one name, and it holds the parameter value and the definition data. This is further facilitated by the JEVOIS_DECLARE_PARAMETER(ParamName, ParamType, ...) variadic macro.
- for parameters with callbacks, their class type includes a pure virtual onParamChange(param, value) function that will need to be implemented by the host component. This is facilitated by the JEVOIS_DECLARE_PARAMETER_WITH_CALLBACK(ParamName, ParamType, ...) variadic macro. The first argument of onParamChange() is the parameter class type, so that a host component with many parameters will have many different onParamChange() functions, one per parameter that has a callback.
- components inherit from their parameters using variadic templates to make inheriting from multiple parameters short and easy.
- each parameter exposes simple functions get(), set(), etc (see ParameterCore and ParameterBase). In a component that has many parameters, accessing parameters is achieved by disambiguating on which base class (i.e., which parameter) one wants to access the get(), set(), etc function, which is achieved by calling paramx::get() vs paramy::get(), etc
- No need to declare parameter member variables (we inherit from them instead).
- No need to do anything at construction of the component.
- No need to manually hook the callback function in the component host class to the parameter.
- When implementing the callback, all members of the host class are available (since the host class inherits from the Parameter). This is critical since usually callbacks are implemented so that the host component will take some action when a parameter value is changed, e.g., reconfigure itself in some way.
- Strong compile-time checking that the programmer did not forget to write the callback function for each parameter that was declared as having a callback.
- Only one name used throughout for that parameter and all its associated machinery (definition, callback).
- It is easy to write scripts that search the source tree for information about all the parameters of a component, since those are always all specified in the Parameter< ... > inheritance statement.
Remaining caveat: it is often desirable to use short names for parameters, such as "size", "length", "dims", "threshold", etc and those names may clash between several components as the .H files for these components are included when building a more complex component or system that uses those. This is not an issue for Module, which is a terminal entity and is typically written as a single .C file with no .H file. For components intended for broad use, we currently recommend putting all the parameters in a namespace that is the lowercase version of the component class name.
Below is the resulting implementation in Manager.H. We start with declaring the parameters, and we inherit from them when declaring the Manager class. We declare the parameters in a new namespace manager to avoid name clashes with other parameters of other components:
{
namespace manager
{
JEVOIS_DECLARE_PARAMETER(help, bool, "Print this help message", false, ParamCateg);
#ifdef JEVOIS_LDEBUG_ENABLE
#else
#endif
JEVOIS_DECLARE_PARAMETER_WITH_CALLBACK(loglevel, LogLevel, "Set the minimum log level to display",
LogLevel::info, LogLevel_Values, ParamCateg);
JEVOIS_DECLARE_PARAMETER_WITH_CALLBACK(tracelevel, unsigned int, "Set the minimum trace level to display",
0, ParamCateg);
JEVOIS_DECLARE_PARAMETER(nickname, std::string, "Nickname associated with this camera, useful when multiple "
"JeVois cameras are connected to a same USB bus", "jevois", ParamCateg);
}
public Parameter<manager::help, manager::loglevel, manager::tracelevel, manager::nickname>
{
public:
Manager of a hierarchy of Component objects.
JEVOIS_DEFINE_ENUM_CLASS(CameraSensor,(any)(imx290)(os08a10)(ar0234))
Enum for different sensor models.
A category to which multiple ParameterDef definitions can belong.
For the parameters that we declared as having a callback, we further include in our definition of the Manager class overrides for the pure virtual onParamChange() functions that they added to our manager class. Note the signatures of these functions: The first argument is a const reference to the parameter for which this callback is, and its main role is to disambiguate between the different onParamChange() functions a component may have. The second argument is the proposed new parameter value. The onParamChange() function should examine the candidate new value and (1) if it does not like it, throw and std::range_error with a descriptive message of why the value is rejected, (2) otherwise, it is assumed that the value is accepted and the callback can then allocate resources or do other work with that value (the actual modification of the Parameter object is handled upstream and the callback does not need to worry about it: if it returns without throwing, the proposed value will become the new value of the Parameter). The Parameter is locked-up for writing as long as the callback is running, to avoid destruction of the parameter and/or concurrent parameter value changes by several different threads. Thus, callbacks should try to execute quickly, and should not call set(), etc on the parameter as this will always deadlock (get() is allowed if your callback needs to know the current value of the parameter).
void onParamChange(manager::loglevel const & param, manager::LogLevel const & newval) override;
void onParamChange(manager::tracelevel const & param, unsigned int const & newval) override;
There is nothing to do in the constructor, destructor, etc of Manager. The only thing that remains to be done is to implement the onParamChange() functions in Manager.C. Note how we do not give a name to the first argument (which is a ref to the parameter itself; that first argument is necessary to disambiguate among the various onParamChange() functions for different parameters, but usually we only care about the new value and do not need the handle to the parameter). This is to avoid compiler warnings about our callback not using that first argument:
{
switch(newval)
{
#ifdef JEVOIS_LDEBUG_ENABLE
#endif
}
}
{
#if !defined(JEVOIS_TRACE_ENABLE) || !defined(JEVOIS_LDEBUG_ENABLE)
if (newval)
LERROR(
"Debug trace has been disabled at compile-time, re-compile with -DJEVOIS_LDEBUG_ENABLE=ON and "
"-DJEVOIS_TRACE_ENABLE=ON to see trace info");
#endif
}
void onParamChange(manager::loglevel const ¶m, manager::LogLevel const &newval) override
Parameter callback.
int logLevel
Current log level.
int traceLevel
Current trace level.
#define LERROR(msg)
Convenience macro for users to print out console or syslog messages, ERROR level.
The host component can use a parameter's member functions by calling them with the parameter name as a prefix (this prefix is basically selecting on which base class we want to run the given function). For example, in Manager, we take some action if the help
parameter has been set at the command line, and then we freeze it. All member functions defined in ParameterBase and in ParameterCore<T> are available on every Parameter (get(), set(), strget(), strset(), name(), descriptor(), summary(), freeze(), etc):
{
if (help::get()) { printHelpMessage();
LINFO(
"JeVois: exit after help message"); exit(0); }
help::freeze(true);
#if !defined(JEVOIS_TRACE_ENABLE) || !defined(JEVOIS_LDEBUG_ENABLE)
tracelevel::freeze(true);
#endif
}
void postInit() override
Checks for the –help flag.
#define LINFO(msg)
Convenience macro for users to print out console or syslog messages, INFO level.
For completeness, if you wonder what JEVOIS_DECLARE_PARAMETER(ParamName, ParamType, ...) and JEVOIS_DECLARE_PARAMETER_WITH_CALLBACK(ParamName, ParamType, ...) exactly do, here they are in ParameterHelpers.H and reproduced here:
#define JEVOIS_DECLARE_PARAMETER(ParamName, ParamType, ...) \
struct ParamName : public virtual jevois::ParameterRegistry, public jevois::ParameterCore<ParamType> \
{ \
typedef ParamType type; \
\
inline ParamName() : jevois::ParameterCore<ParamType>(jevois::ParameterDef<ParamType>(#ParamName, __VA_ARGS__)) \
{ jevois::ParameterRegistry::addParameter(this); } \
\
inline virtual ~ParamName() \
{ jevois::ParameterRegistry::removeParameter(this); } \
\
inline virtual jevois::Component const * owner() const override \
{ return dynamic_cast<jevois::Component const *>(static_cast<jevois::ParameterRegistry const *>(this)); } \
};
#define JEVOIS_DECLARE_PARAMETER_WITH_CALLBACK(ParamName, ParamType, ...) \
struct ParamName : public virtual jevois::ParameterRegistry, public jevois::ParameterCore<ParamType> \
{ \
typedef ParamType type; \
\
virtual void onParamChange(ParamName const & param, ParamType const & newval) = 0; \
\
inline ParamName() : jevois::ParameterCore<ParamType>(jevois::ParameterDef<ParamType>(#ParamName, __VA_ARGS__)) \
{ setCallback([this](ParamType const & newval) { this->onParamChange(*this, newval); }); \
jevois::ParameterRegistry::addParameter(this); } \
\
inline virtual ~ParamName() \
{ jevois::ParameterRegistry::removeParameter(this); } \
\
inline virtual jevois::Component const * owner() const override \
{ return dynamic_cast<jevois::Component const *>(static_cast<jevois::ParameterRegistry const *>(this)); } \
};
Also see Engine.H or the many components in the jevoisbase library.
Definition at line 404 of file Parameter.H.