(core): variables store
Description
As stated in #1242, in bigger applications, we suffer from having parameters set in children configurations that should be passed to siblings, which are stopped. Thus, usual signal/slot communication is impossible.
We have implemented services that store values to send them later when siblings are started, but this requires caution with synchronisation because it relies on signal/slot communication where we would have preferred to pass "real" initial values to the services configuration. These solutions also increase the number of channels, which is already a big problem and slows down development.
I don't see any simple solution relying only on services to address this issue, so I think it is time to think about upgrading the appconfig.
Functional specifications
Work in Progress
We already support variable substitution in XML:
<parameters>
<param name="var1" />
<param name="var2" />
</parameters>
<config>
<service uid="..." type="...">
<config arg1="${var1}" arg2="${var2}" />
</service>
However, we do not support setting these variables from the C++ code. The idea would be to allow services to change the values of these variables, thanks to a local/global store:
<service uid="selector" type="sight::module::ui::qt::parameters">
<parameters>
<param type="enum" name="" key="${var1}" widget="buttonBar" ...>
<item value="liver" label="Liver" icon="sight::module::ui::icons/Liver.svg" />
<item value="kidney" label="Kidney" icon="sight::module::ui::icons/Kidney.svg" />
</param>
</parameters>
</service>
When substituting ${var1}
in configuration, the app::config_manager
would substitute it with the value in the store.
<service uid="..." type="sight::app::config_controller">
<appConfig id="..." />
<parameter replace="var1" by="${var1}" />
</service>
This would thus work for the cases where we start and stop services, since the configuration will be read again. However this will not help if the service is already started and does not change its state. For this we would still rely on signal/slots. But maybe we could also automate these tedious connections, with a similar way than auto-connections.
Instead of repeating again and again these idioms:
//-----------------------------------------------------------------------------
void service::set_parameter(sight::ui::parameter_t _val, std::string _key)
{
if(_key == "int_value")
{
m_var1 = std::get<bool>(_val);
}
else if(_key == "real_value")
{
m_var2 = std::get<double>(_val);
}
else
{
SIGHT_ERROR("the key '" + _key + "' is not handled");
}
}
We could write something like:
service::service()
{
new_slot(INT_SLOT, [this](sight::ui::parameter_t _val){ m_var1 = std::get<bool>(_val); } );
new_slot(REAL_SLOT, [this](sight::ui::parameter_t _val){ m_var2 = std::get<double>(_val); } );
}
//-----------------------------------------------------------------------------
service::param_connections_t reprojection_error::param_connections() const
{
return {
{ INT_SLOT, "int_value" },
{ REAL_SLOT, "real_value" }
};
}
Or even, we agree on always going the lambda way (which does not prevent from using functions inside...), which could allow to have only one hidden slot:
service::service()
{
}
//-----------------------------------------------------------------------------
service::param_connections_t reprojection_error::param_connections() const
{
return {
{ "int_value", [this](sight::ui::parameter_t _val){ m_var1 = std::get<bool>(_val); } },
{ "real_value", [this](sight::ui::parameter_t _val){ m_var2 = std::get<double>(_val); } }
};
}
Note: not sure that's better...
Doing this would allow the app::config_manager
to automatically connect the appropriate parameter_changed
signals with the services slot.
Could we not go further and have only one place where we set the parameter?
Maybe, yes, the configuring() method could even rely on these connections declarations to automate the parsing?
Opened questions
- should we not use
sight::data::float
etc... instead? Does this approach scale, notably with activities? - should we use global variables? I don't think so, naming conflicts will occur
- do we need to declare "output" variables in the
app::config
?
Technical specifications
Details of the implementation.
Test plan
Describe how you will verify that the implementation meets specifications.