Skip to content
Snippets Groups Projects
Commit 86dea4c3 authored by Martin Christoph Hierholzer's avatar Martin Christoph Hierholzer
Browse files

add support for arrays in ConfigReader module

parent 9001c926
No related branches found
No related tags found
No related merge requests found
...@@ -10,7 +10,9 @@ ...@@ -10,7 +10,9 @@
namespace ChimeraTK { namespace ChimeraTK {
struct FunctorFill; struct FunctorFill;
struct ArrayFunctorFill;
struct FunctorSetValues; struct FunctorSetValues;
struct FunctorSetValuesArray;
/** /**
* Generic module to read an XML config file and provide the defined values as constant variables. The config file * Generic module to read an XML config file and provide the defined values as constant variables. The config file
...@@ -30,26 +32,27 @@ namespace ChimeraTK { ...@@ -30,26 +32,27 @@ namespace ChimeraTK {
* ConfigReader::get() function. * ConfigReader::get() function.
*/ */
struct ConfigReader : ApplicationModule { struct ConfigReader : ApplicationModule {
ConfigReader(EntityOwner *owner, const std::string &name, const std::string &fileName, ConfigReader(EntityOwner *owner, const std::string &name, const std::string &fileName,
const std::unordered_set<std::string> &tags={}); const std::unordered_set<std::string> &tags={});
void mainLoop() override {} void mainLoop() override {}
void prepare() override; void prepare() override;
/** Get value for given configuration variable. This is already accessible right after construction of this /** Get value for given configuration variable. This is already accessible right after construction of this
* object. Throws std::out_of_range if variable doesn't exist. */ * object. Throws std::out_of_range if variable doesn't exist.
* To obtain the value of an array, use an std::vector<T> as template argument. */
template<typename T> template<typename T>
const T& get(const std::string &variableName) const; const T& get(const std::string &variableName) const;
protected: protected:
/** File name */ /** File name */
std::string _fileName; std::string _fileName;
/** throw a parsing error with more information */ /** throw a parsing error with more information */
void parsingError(const std::string &message); void parsingError(const std::string &message);
/** Class holding the value and the accessor for one configuration variable */ /** Class holding the value and the accessor for one configuration variable */
template<typename T> template<typename T>
struct Var { struct Var {
...@@ -61,25 +64,57 @@ namespace ChimeraTK { ...@@ -61,25 +64,57 @@ namespace ChimeraTK {
ScalarOutput<T> _accessor; ScalarOutput<T> _accessor;
T _value; T _value;
}; };
/** Class holding the values and the accessor for one configuration array */
template<typename T>
struct Array {
Array(Module *owner, const std::string &name, const std::vector<T> &value)
: _accessor(owner, name, "unknown", value.size(), "Configuration array"),
_value(value)
{}
ArrayOutput<T> _accessor;
std::vector<T> _value;
};
/** Create an instance of Var<T> and place it on the variableMap */ /** Create an instance of Var<T> and place it on the variableMap */
template<typename T> template<typename T>
void createVar(const std::string &name, const std::string &value); void createVar(const std::string &name, const std::string &value);
/** Create an instance of Array<T> and place it on the arrayMap */
template<typename T>
void createArray(const std::string &name, const std::map<size_t, std::string> &values);
/** Define type for map of std::string to Var, so we can put it into the TemplateUserTypeMap */ /** Define type for map of std::string to Var, so we can put it into the TemplateUserTypeMap */
template<typename T> template<typename T>
using MapOfVar = std::map<std::string, Var<T>>; using MapOfVar = std::map<std::string, Var<T>>;
/** Type-depending map of vectors of variables */ /** Type-depending map of vectors of variables */
mtca4u::TemplateUserTypeMap<MapOfVar> variableMap; mtca4u::TemplateUserTypeMap<MapOfVar> variableMap;
/** Define type for map of std::string to Array, so we can put it into the TemplateUserTypeMap */
template<typename T>
using MapOfArray = std::map<std::string, Array<T>>;
/** Type-depending map of vectors of arrays */
mtca4u::TemplateUserTypeMap<MapOfArray> arrayMap;
/** Map assigning string type identifyers to C++ types */ /** Map assigning string type identifyers to C++ types */
mtca4u::SingleTypeUserTypeMap<const char*> typeMap{"int8","uint8","int16","uint16","int32","uint32", mtca4u::SingleTypeUserTypeMap<const char*> typeMap{"int8","uint8","int16","uint16","int32","uint32",
"int64","uint64","float","double","string"}; "int64","uint64","float","double","string"};
/** Implementation of get() which can be overloaded for scalars and vectors. The second argument is a dummy
* only to distinguish the two overloaded functions. */
template<typename T>
const T& get_impl(const std::string &variableName, T*) const;
template<typename T>
const std::vector<T>& get_impl(const std::string &variableName, std::vector<T>*) const;
friend struct FunctorFill; friend struct FunctorFill;
friend struct ArrayFunctorFill;
friend struct FunctorSetValues; friend struct FunctorSetValues;
friend struct FunctorSetValuesArray;
}; };
/*********************************************************************************************************************/ /*********************************************************************************************************************/
...@@ -87,11 +122,33 @@ namespace ChimeraTK { ...@@ -87,11 +122,33 @@ namespace ChimeraTK {
template<typename T> template<typename T>
const T& ConfigReader::get(const std::string &variableName) const { const T& ConfigReader::get(const std::string &variableName) const {
return get_impl(variableName, static_cast<T*>(nullptr));
}
/*********************************************************************************************************************/
/*********************************************************************************************************************/
template<typename T>
const T& ConfigReader::get_impl(const std::string &variableName, T*) const {
try { try {
return boost::fusion::at_key<T>(variableMap.table).at(variableName)._value; return boost::fusion::at_key<T>(variableMap.table).at(variableName)._value;
} }
catch(std::out_of_range &e) { catch(std::out_of_range &e) {
throw(std::out_of_range("ConfigReader: Cannot find a configuration variable of the name '"+ throw(std::out_of_range("ConfigReader: Cannot find a scalar configuration variable of the name '"+
variableName+"' in the config file '"+_fileName+"'."));
}
}
/*********************************************************************************************************************/
/*********************************************************************************************************************/
template<typename T>
const std::vector<T>& ConfigReader::get_impl(const std::string &variableName, std::vector<T>*) const {
try {
return boost::fusion::at_key<T>(arrayMap.table).at(variableName)._value;
}
catch(std::out_of_range &e) {
throw(std::out_of_range("ConfigReader: Cannot find an array configuration variable of the name '"+
variableName+"' in the config file '"+_fileName+"'.")); variableName+"' in the config file '"+_fileName+"'."));
} }
} }
......
...@@ -8,27 +8,58 @@ namespace ChimeraTK { ...@@ -8,27 +8,58 @@ namespace ChimeraTK {
/** Functor to fill variableMap */ /** Functor to fill variableMap */
struct FunctorFill { struct FunctorFill {
FunctorFill(ConfigReader *owner, const std::string &type, const std::string &name, const std::string &value, bool &processed) FunctorFill(ConfigReader *owner, const std::string &type, const std::string &name, const std::string &value,
bool &processed)
: _owner(owner), _type(type), _name(name), _value(value), _processed(processed) : _owner(owner), _type(type), _name(name), _value(value), _processed(processed)
{} {}
template<typename PAIR> template<typename PAIR>
void operator()(PAIR&) const { void operator()(PAIR&) const {
// extract the user type from the pair // extract the user type from the pair
typedef typename PAIR::first_type T; typedef typename PAIR::first_type T;
// skip this type, if not matching the type string in the config file // skip this type, if not matching the type string in the config file
if(_type != boost::fusion::at_key<T>(_owner->typeMap)) return; if(_type != boost::fusion::at_key<T>(_owner->typeMap)) return;
_owner->createVar<T>(_name, _value); _owner->createVar<T>(_name, _value);
_processed = true; _processed = true;
} }
ConfigReader *_owner; ConfigReader *_owner;
const std::string &_type, &_name, &_value; const std::string &_type, &_name, &_value;
bool &_processed; // must be a non-const reference, since we want to return this to the caller bool &_processed; // must be a non-const reference, since we want to return this to the caller
typedef boost::fusion::pair<std::string,ConfigReader::Var<std::string>> StringPair;
};
/*********************************************************************************************************************/
/** Functor to fill variableMap for arrays */
struct ArrayFunctorFill {
ArrayFunctorFill(ConfigReader *owner, const std::string &type, const std::string &name,
const std::map<size_t, std::string> &values, bool &processed)
: _owner(owner), _type(type), _name(name), _values(values), _processed(processed)
{}
template<typename PAIR>
void operator()(PAIR&) const {
// extract the user type from the pair
typedef typename PAIR::first_type T;
// skip this type, if not matching the type string in the config file
if(_type != boost::fusion::at_key<T>(_owner->typeMap)) return;
_owner->createArray<T>(_name, _values);
_processed = true;
}
ConfigReader *_owner;
const std::string &_type, &_name;
const std::map<size_t, std::string> &_values;
bool &_processed; // must be a non-const reference, since we want to return this to the caller
typedef boost::fusion::pair<std::string,ConfigReader::Var<std::string>> StringPair; typedef boost::fusion::pair<std::string,ConfigReader::Var<std::string>> StringPair;
}; };
...@@ -48,7 +79,7 @@ namespace ChimeraTK { ...@@ -48,7 +79,7 @@ namespace ChimeraTK {
else { // note: string is done in template specialisation else { // note: string is done in template specialisation
stream >> convertedValue; stream >> convertedValue;
} }
// place the variable onto the vector // place the variable onto the vector
std::map<std::string, ConfigReader::Var<T>> &theMap = boost::fusion::at_key<T>(variableMap.table); std::map<std::string, ConfigReader::Var<T>> &theMap = boost::fusion::at_key<T>(variableMap.table);
theMap.emplace(std::make_pair(name, ConfigReader::Var<T>(this, name, convertedValue))); theMap.emplace(std::make_pair(name, ConfigReader::Var<T>(this, name, convertedValue)));
...@@ -65,6 +96,72 @@ namespace ChimeraTK { ...@@ -65,6 +96,72 @@ namespace ChimeraTK {
/*********************************************************************************************************************/ /*********************************************************************************************************************/
/*********************************************************************************************************************/
template<typename T>
void ConfigReader::createArray(const std::string &name, const std::map<size_t, std::string> &values) {
std::vector<T> Tvalues;
size_t expectedIndex = 0;
for(auto it = values.begin(); it != values.end(); ++it) {
// check index (std::map should be ordered by the index)
if(it->first != expectedIndex) {
parsingError("Array index "+std::to_string(expectedIndex)+" not found, but "+std::to_string(it->first)+" was. "
"Sparse arrays are not supported!");
}
++expectedIndex;
// convert value into user type
std::stringstream stream(it->second);
T convertedValue;
if(typeid(T) == typeid(int8_t) || typeid(T) == typeid(uint8_t)) { // prevent interpreting int8-types as characters
int16_t intermediate;
stream >> intermediate;
convertedValue = intermediate;
}
else { // note: string is done in template specialisation
stream >> convertedValue;
}
// store value in vector
Tvalues.push_back(convertedValue);
}
// place the variable onto the vector
std::map<std::string, ConfigReader::Array<T>> &theMap = boost::fusion::at_key<T>(arrayMap.table);
theMap.emplace(std::make_pair(name, ConfigReader::Array<T>(this, name, Tvalues)));
}
/*********************************************************************************************************************/
template<>
void ConfigReader::createArray<std::string>(const std::string &name, const std::map<size_t, std::string> &values) {
std::vector<std::string> Tvalues;
size_t expectedIndex = 0;
for(auto it = values.begin(); it != values.end(); ++it) {
// check index (std::map should be ordered by the index)
if(it->first != expectedIndex) {
parsingError("Array index "+std::to_string(expectedIndex)+" not found, but "+std::to_string(it->first)+" was. "
"Sparse arrays are not supported!");
}
++expectedIndex;
// store value in vector
Tvalues.push_back(it->second);
}
// place the variable onto the vector
std::map<std::string, ConfigReader::Array<std::string>> &theMap = boost::fusion::at_key<std::string>(arrayMap.table);
theMap.emplace(std::make_pair(name, ConfigReader::Array<std::string>(this, name, Tvalues)));
}
/*********************************************************************************************************************/
ConfigReader::ConfigReader(EntityOwner *owner, const std::string &name, const std::string &fileName, ConfigReader::ConfigReader(EntityOwner *owner, const std::string &name, const std::string &fileName,
const std::unordered_set<std::string> &tags) const std::unordered_set<std::string> &tags)
: ApplicationModule(owner, name, "Configuration read from file '"+fileName+"'", false, tags), : ApplicationModule(owner, name, "Configuration read from file '"+fileName+"'", false, tags),
...@@ -78,7 +175,7 @@ namespace ChimeraTK { ...@@ -78,7 +175,7 @@ namespace ChimeraTK {
catch(xmlpp::exception &e) { /// @todo change exception! catch(xmlpp::exception &e) { /// @todo change exception!
throw std::runtime_error("ConfigReader: Error opening the config file '"+fileName+"': "+e.what()); throw std::runtime_error("ConfigReader: Error opening the config file '"+fileName+"': "+e.what());
} }
// get root element // get root element
const auto root = parser.get_document()->get_root_node(); const auto root = parser.get_document()->get_root_node();
if(root->get_name() != "configuration") { if(root->get_name() != "configuration") {
...@@ -99,14 +196,57 @@ namespace ChimeraTK { ...@@ -99,14 +196,57 @@ namespace ChimeraTK {
if(!name) parsingError("Missing attribute 'name' for the 'variable' tag."); if(!name) parsingError("Missing attribute 'name' for the 'variable' tag.");
auto type = element->get_attribute("type"); auto type = element->get_attribute("type");
if(!type) parsingError("Missing attribute 'type' for the 'variable' tag."); if(!type) parsingError("Missing attribute 'type' for the 'variable' tag.");
// scalar value: obtain value from attribute
auto value = element->get_attribute("value"); auto value = element->get_attribute("value");
if(!value) parsingError("Missing attribute 'value' for the 'variable' tag."); if(value) {
// create accessor and store value in map using the functor
// create accessor and store value in map using the functor bool processed{false};
bool processed{false}; boost::fusion::for_each( variableMap.table,
boost::fusion::for_each( variableMap.table, FunctorFill(this, type->get_value(), name->get_value(), value->get_value(), processed) );
FunctorFill(this, type->get_value(), name->get_value(), value->get_value(), processed) ); if(!processed) parsingError("Incorrect value '"+type->get_value()+"' for attribute 'type' of the 'variable' tag.");
if(!processed) parsingError("Incorrect value '"+type->get_value()+"' for attribute 'type' of the 'variable' tag."); }
// array value: obtain values from child elements
else {
bool valueFound = false;
std::map<size_t, std::string> values;
for(const auto& valueChild : child->get_children()) {
// obtain value child element and extract index and value from attributes
const xmlpp::Element *valueElement = dynamic_cast<const xmlpp::Element*>(valueChild);
if(!valueElement) continue; // ignore comments etc.
if(valueElement->get_name() != "value") {
parsingError("Expected 'value' tag instead of: "+root->get_name());
}
valueFound = true;
auto index = valueElement->get_attribute("i");
if(!index) parsingError("Missing attribute 'index' for the 'value' tag.");
auto value = valueElement->get_attribute("v");
if(!value) parsingError("Missing attribute 'value' for the 'value' tag.");
// get index as number and store value as a string
size_t intIndex;
try {
intIndex = std::stoi(index->get_value());
}
catch(std::exception &e) {
parsingError("Cannot parse string '"+std::string(index->get_value())+"' as an index number: "+e.what());
}
values[intIndex] = value->get_value();
}
// make sure there is at least one value
if(!valueFound) {
parsingError("Each variable must have a value, either specified as an attribute or as child tags.");
}
// create accessor and store array value in map using functor
bool processed{false};
boost::fusion::for_each( variableMap.table,
ArrayFunctorFill(this, type->get_value(), name->get_value(), values, processed) );
if(!processed) parsingError("Incorrect value '"+type->get_value()+"' for attribute 'type' of the 'variable' tag.");
}
} }
} }
...@@ -118,26 +258,51 @@ namespace ChimeraTK { ...@@ -118,26 +258,51 @@ namespace ChimeraTK {
/*********************************************************************************************************************/ /*********************************************************************************************************************/
/** Functor to set values to the accessors */ /** Functor to set values to the scalar accessors */
struct FunctorSetValues { struct FunctorSetValues {
FunctorSetValues(ConfigReader *owner) : _owner(owner) {} FunctorSetValues(ConfigReader *owner) : _owner(owner) {}
template<typename PAIR> template<typename PAIR>
void operator()(PAIR&) const { void operator()(PAIR&) const {
// get user type and vector // get user type and vector
typedef typename PAIR::first_type T; typedef typename PAIR::first_type T;
std::map<std::string, ConfigReader::Var<T>> &theMap = boost::fusion::at_key<T>(_owner->variableMap.table); std::map<std::string, ConfigReader::Var<T>> &theMap = boost::fusion::at_key<T>(_owner->variableMap.table);
// iterate vector and set values // iterate vector and set values
for(auto &pair : theMap) { for(auto &pair : theMap) {
auto &var = pair.second; auto &var = pair.second;
var._accessor = var._value; var._accessor = var._value;
var._accessor.write(); var._accessor.write();
} }
} }
ConfigReader *_owner;
};
/*********************************************************************************************************************/
/** Functor to set values to the array accessors */
struct FunctorSetValuesArray {
FunctorSetValuesArray(ConfigReader *owner) : _owner(owner) {}
template<typename PAIR>
void operator()(PAIR&) const {
// get user type and vector
typedef typename PAIR::first_type T;
std::map<std::string, ConfigReader::Array<T>> &theMap = boost::fusion::at_key<T>(_owner->arrayMap.table);
// iterate vector and set values
for(auto &pair : theMap) {
auto &var = pair.second;
var._accessor = var._value;
var._accessor.write();
}
}
ConfigReader *_owner; ConfigReader *_owner;
}; };
...@@ -145,6 +310,7 @@ namespace ChimeraTK { ...@@ -145,6 +310,7 @@ namespace ChimeraTK {
void ConfigReader::prepare() { void ConfigReader::prepare() {
boost::fusion::for_each( variableMap.table, FunctorSetValues(this) ); boost::fusion::for_each( variableMap.table, FunctorSetValues(this) );
boost::fusion::for_each( arrayMap.table, FunctorSetValuesArray(this) );
} }
/*********************************************************************************************************************/ /*********************************************************************************************************************/
......
...@@ -32,6 +32,7 @@ struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::Appli ...@@ -32,6 +32,7 @@ struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::Appli
ctk::ScalarPushInput<double> varDouble{this, "varDouble", "MV/m", "Desc"}; ctk::ScalarPushInput<double> varDouble{this, "varDouble", "MV/m", "Desc"};
ctk::ScalarPushInput<std::string> varString{this, "varString", "MV/m", "Desc"}; ctk::ScalarPushInput<std::string> varString{this, "varString", "MV/m", "Desc"};
ctk::ScalarPushInput<int32_t> varAnotherInt{this, "varAnotherInt", "MV/m", "Desc"}; ctk::ScalarPushInput<int32_t> varAnotherInt{this, "varAnotherInt", "MV/m", "Desc"};
ctk::ArrayPushInput<int32_t> intArray{this, "intArray", "MV/m", 10, "Desc"};
std::atomic<bool> done{false}; std::atomic<bool> done{false};
...@@ -50,6 +51,9 @@ struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::Appli ...@@ -50,6 +51,9 @@ struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::Appli
BOOST_CHECK_CLOSE((double)varDouble, -2.8, 0.000001); BOOST_CHECK_CLOSE((double)varDouble, -2.8, 0.000001);
BOOST_CHECK_EQUAL((std::string)varString, "My dear mister singing club!"); BOOST_CHECK_EQUAL((std::string)varString, "My dear mister singing club!");
BOOST_CHECK_EQUAL(intArray.getNElements(), 10);
for(size_t i=0; i<10; ++i) BOOST_CHECK_EQUAL(intArray[i], 10-i);
// no further update shall be received // no further update shall be received
usleep(1000000); // 1 second usleep(1000000); // 1 second
BOOST_CHECK( !var8.readNonBlocking() ); BOOST_CHECK( !var8.readNonBlocking() );
...@@ -63,6 +67,7 @@ struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::Appli ...@@ -63,6 +67,7 @@ struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::Appli
BOOST_CHECK( !varFloat.readNonBlocking() ); BOOST_CHECK( !varFloat.readNonBlocking() );
BOOST_CHECK( !varDouble.readNonBlocking() ); BOOST_CHECK( !varDouble.readNonBlocking() );
BOOST_CHECK( !varString.readNonBlocking() ); BOOST_CHECK( !varString.readNonBlocking() );
BOOST_CHECK( !intArray.readNonBlocking() );
// inform main thread that we are done // inform main thread that we are done
done = true; done = true;
...@@ -105,6 +110,10 @@ BOOST_AUTO_TEST_CASE( testConfigReader ) { ...@@ -105,6 +110,10 @@ BOOST_AUTO_TEST_CASE( testConfigReader ) {
BOOST_CHECK_CLOSE(app.config.get<double>("varDouble"), -2.8, 0.000001); BOOST_CHECK_CLOSE(app.config.get<double>("varDouble"), -2.8, 0.000001);
BOOST_CHECK_EQUAL(app.config.get<std::string>("varString"), "My dear mister singing club!"); BOOST_CHECK_EQUAL(app.config.get<std::string>("varString"), "My dear mister singing club!");
std::vector<int> arrayValue = app.config.get<std::vector<int>>("intArray");
BOOST_CHECK_EQUAL(arrayValue.size(), 10);
for(size_t i=0; i<10; ++i) BOOST_CHECK_EQUAL(arrayValue[i], 10-i);
app.config.connectTo(app.testModule); app.config.connectTo(app.testModule);
app.initialise(); app.initialise();
......
<!-- Just a random comment -->
<configuration> <configuration>
<!-- Just a random comment -->
<variable name="var8" type="int8" value="-123"/> <variable name="var8" type="int8" value="-123"/>
<variable name="var8u" type="uint8" value="34"/> <variable name="var8u" type="uint8" value="34"/>
<variable name="var16" type="int16" value="-567"/> <variable name="var16" type="int16" value="-567"/>
<!-- Just a random comment -->
<variable name="var16u" type="uint16" value="678"/> <variable name="var16u" type="uint16" value="678"/>
<variable name="var32" type="int32" value="-345678"/> <variable name="var32" type="int32" value="-345678"/>
<variable name="var32u" type="uint32" value="234567"/> <variable name="var32u" type="uint32" value="234567"/>
...@@ -11,4 +14,20 @@ ...@@ -11,4 +14,20 @@
<variable name="varDouble" type="double" value="-2.8"/> <variable name="varDouble" type="double" value="-2.8"/>
<variable name="varString" type="string" value="My dear mister singing club!"/> <variable name="varString" type="string" value="My dear mister singing club!"/>
<variable name="varAnotherInt" type="int32" value="666"/> <variable name="varAnotherInt" type="int32" value="666"/>
<variable name="intArray" type="int32">
<!-- Values are intentionally out-of-order -->
<value i="0" v="10"/>
<value i="1" v="9"/>
<value i="2" v="8"/>
<value i="7" v="3"/>
<value i="8" v="2"/>
<value i="9" v="1"/>
<value i="3" v="7"/>
<value i="4" v="6"/>
<!-- Just a random comment -->
<value i="5" v="5"/>
<value i="6" v="4"/>
</variable>
<!-- Just a random comment -->
</configuration> </configuration>
<!-- Just a random comment -->
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment