From aa002707c4490a8b4318928d4257ab80748031b1 Mon Sep 17 00:00:00 2001 From: Martin Hierholzer <martin.hierholzer@desy.de> Date: Thu, 21 Feb 2019 18:21:25 +0100 Subject: [PATCH] changed formatting --- Modules/include/BitMask.h | 165 ++- Modules/include/ConfigReader.h | 277 ++-- Modules/include/DataLossCounter.h | 57 +- Modules/include/LimitValue.h | 139 +- Modules/include/Logging.h | 159 ++- Modules/include/MicroDAQ.h | 177 ++- Modules/include/Multiplier.h | 221 +-- Modules/include/PeriodicTrigger.h | 94 +- Modules/include/Pipe.h | 173 +-- Modules/include/ServerHistory.h | 87 +- Modules/include/SplitArray.h | 294 ++-- Modules/src/ConfigReader.cc | 541 +++---- Modules/src/Logging.cc | 179 +-- Modules/src/MicroDAQ.cc | 698 ++++----- Modules/src/ServerHistory.cc | 151 +- example/DemoDummyDevice.cc | 68 +- example/TimerDummyDevice.cc | 175 ++- example/demoApp.cc | 135 +- example2/demoApp2.cc | 51 +- example2a/demoApp2a.cc | 100 +- example3/demoApp3.cc | 18 +- include/Application.h | 737 +++++----- include/ApplicationCore.h | 14 +- include/ApplicationModule.h | 142 +- include/ArrayAccessor.h | 289 ++-- include/ConstantAccessor.h | 189 +-- include/ConsumingFanOut.h | 58 +- include/ControlSystemModule.h | 118 +- include/DebugPrintAccessorDecorator.h | 134 +- include/DeviceModule.h | 255 ++-- include/EnableXMLGenerator.h | 23 +- include/EntityOwner.h | 349 +++-- include/FanOut.h | 78 +- include/FeedingFanOut.h | 382 ++--- include/Flags.h | 64 +- include/InternalModule.h | 28 +- include/InversionOfControlAccessor.h | 209 +-- include/Module.h | 265 ++-- include/ModuleGraphVisitor.h | 25 +- include/ModuleGroup.h | 76 +- include/ModuleImpl.h | 67 +- include/Profiler.h | 163 +-- include/ScalarAccessor.h | 281 ++-- include/SupportedUserTypes.h | 50 +- include/TestFacility.h | 319 +++-- include/TestableModeAccessorDecorator.h | 294 ++-- include/ThreadedFanOut.h | 238 ++-- include/TriggerFanOut.h | 195 +-- include/VariableGroup.h | 81 +- include/VariableNetwork.h | 286 ++-- include/VariableNetworkDumpingVisitor.h | 16 +- include/VariableNetworkGraphDumpingVisitor.h | 39 +- include/VariableNetworkNode.h | 522 +++---- include/VariableNetworkNodeDumpingVisitor.h | 79 +- .../VersionNumberUpdatingRegisterDecorator.h | 38 +- include/VirtualModule.h | 81 +- include/Visitor.h | 16 +- include/VisitorHelper.h | 6 +- include/XMLGeneratorVisitor.h | 26 +- src/Application.cc | 1024 +++++++------ src/ApplicationModule.cc | 84 +- src/ControlSystemModule.cc | 83 +- src/DeviceModule.cc | 410 +++--- src/EntityOwner.cc | 316 ++-- src/Module.cc | 159 ++- src/ModuleGraphVisitor.cc | 99 +- src/ModuleGroup.cc | 19 +- src/ModuleImpl.cc | 32 +- src/Profiler.cc | 4 +- src/VariableGroup.cc | 22 +- src/VariableNetwork.cc | 538 +++---- src/VariableNetworkDumpingVisitor.cc | 89 +- src/VariableNetworkGraphDumpingVisitor.cc | 268 ++-- src/VariableNetworkNode.cc | 635 +++++---- src/VariableNetworkNodeDumpingVisitor.cc | 85 +- src/VersionNumberUpdatingRegisterDecorator.cc | 15 +- src/VirtualModule.cc | 183 +-- src/VisitorHelper.cc | 17 +- src/XMLGeneratorVisitor.cc | 202 +-- .../testAppModuleConnections.cc | 271 ++-- tests/executables_src/testApplication.cc | 383 ++--- .../testBidirectionalVariables.cc | 492 +++---- tests/executables_src/testConfigReader.cc | 81 +- tests/executables_src/testConnectTo.cc | 130 +- .../testControlSystemAccessors.cc | 188 +-- tests/executables_src/testDeviceAccessors.cc | 176 +-- tests/executables_src/testDirectDeviceToCS.cc | 230 +-- tests/executables_src/testExceptionTest.cc | 44 +- tests/executables_src/testIllegalNetworks.cc | 135 +- tests/executables_src/testLogging.cc | 51 +- tests/executables_src/testModules.cc | 1265 ++++++++++------- tests/executables_src/testTestFacilities.cc | 588 ++++---- tests/executables_src/testTrigger.cc | 241 ++-- tests/executables_src/testVariableGroup.cc | 105 +- tests/include/ExceptionDevice.h | 28 +- 95 files changed, 10147 insertions(+), 8736 deletions(-) diff --git a/Modules/include/BitMask.h b/Modules/include/BitMask.h index 2a7c25db..af71f498 100644 --- a/Modules/include/BitMask.h +++ b/Modules/include/BitMask.h @@ -9,105 +9,112 @@ namespace ChimeraTK { - template<size_t NBITS> - struct WriteBitMask : public ApplicationModule { - using ApplicationModule::ApplicationModule; - - /// individual inputs for each bit - struct Input : public VariableGroup { - Input() {} - Input(EntityOwner *owner, const std::string &name, const std::string &description) - : VariableGroup(owner, name, description) - { - setEliminateHierarchy(); - for(size_t i=0; i<NBITS; ++i) { - bit[i].replace(ScalarPushInput<int>(this, "bit"+std::to_string(i), "", "The bit "+std::to_string(i)+" of the bit mask")); - } - } - - ScalarPushInput<int> bit[NBITS]; - }; - Input input{this, "input", "The input bits"}; +template <size_t NBITS> struct WriteBitMask : public ApplicationModule { + using ApplicationModule::ApplicationModule; + + /// individual inputs for each bit + struct Input : public VariableGroup { + Input() {} + Input(EntityOwner *owner, const std::string &name, + const std::string &description) + : VariableGroup(owner, name, description) { + setEliminateHierarchy(); + for (size_t i = 0; i < NBITS; ++i) { + bit[i].replace(ScalarPushInput<int>(this, "bit" + std::to_string(i), "", + "The bit " + std::to_string(i) + + " of the bit mask")); + } + } - ScalarOutput<int32_t> bitmask{this, "bitmask", "", "Output bit mask."}; + ScalarPushInput<int> bit[NBITS]; + }; + Input input{this, "input", "The input bits"}; - void mainLoop() { + ScalarOutput<int32_t> bitmask{this, "bitmask", "", "Output bit mask."}; - auto readGroup = input.readAnyGroup(); + void mainLoop() { - while(true) { + auto readGroup = input.readAnyGroup(); - // create bit mask - bitmask = 0; - for(size_t i=0; i<NBITS; ++i) { - if(input.bit[i] != 0) { - bitmask |= 1 << i; - } - } - bitmask.write(); + while (true) { - // wait for new input values (at the end, since we want to process the initial values first) - readGroup.readAny(); + // create bit mask + bitmask = 0; + for (size_t i = 0; i < NBITS; ++i) { + if (input.bit[i] != 0) { + bitmask |= 1 << i; } } + bitmask.write(); - }; + // wait for new input values (at the end, since we want to process the + // initial values first) + readGroup.readAny(); + } + } +}; - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - template<size_t NBITS> - struct ReadBitMask : public ApplicationModule { - ReadBitMask(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}) +template <size_t NBITS> struct ReadBitMask : public ApplicationModule { + ReadBitMask(EntityOwner *owner, const std::string &name, + const std::string &description, bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}) : ApplicationModule(owner, name, description, eliminateHierarchy, tags) {} - ReadBitMask() {} - - ReadBitMask( EntityOwner *owner, const std::string &inputName, const std::string &inputDescription, - const std::unordered_set<std::string> &inputTags, const std::array<std::string, NBITS> &outputNames, - const std::array<std::string, NBITS> &outputDescriptions, const std::unordered_set<std::string> &outputTags ) - : ApplicationModule(owner, inputName, inputDescription, true) - { - bitmask.setMetaData(inputName, "", inputDescription, inputTags); - output.setEliminateHierarchy(); - for(size_t i=0; i<NBITS; ++i) { - output.bit[i].setMetaData(outputNames[i], "", outputDescriptions[i], outputTags); - } + ReadBitMask() {} + + ReadBitMask(EntityOwner *owner, const std::string &inputName, + const std::string &inputDescription, + const std::unordered_set<std::string> &inputTags, + const std::array<std::string, NBITS> &outputNames, + const std::array<std::string, NBITS> &outputDescriptions, + const std::unordered_set<std::string> &outputTags) + : ApplicationModule(owner, inputName, inputDescription, true) { + bitmask.setMetaData(inputName, "", inputDescription, inputTags); + output.setEliminateHierarchy(); + for (size_t i = 0; i < NBITS; ++i) { + output.bit[i].setMetaData(outputNames[i], "", outputDescriptions[i], + outputTags); + } + } + + /// individual outputs for each bit + struct Output : public VariableGroup { + Output() {} + Output(EntityOwner *owner, const std::string &name, + const std::string &description) + : VariableGroup(owner, name, description) { + setEliminateHierarchy(); + for (size_t i = 0; i < NBITS; ++i) { + bit[i].replace(ScalarOutput<int>(this, "bit" + std::to_string(i), "", + "The bit " + std::to_string(i) + + " of the bit mask")); } + } - /// individual outputs for each bit - struct Output : public VariableGroup { - Output() {} - Output(EntityOwner *owner, const std::string &name, const std::string &description) - : VariableGroup(owner, name, description) - { - setEliminateHierarchy(); - for(size_t i=0; i<NBITS; ++i) { - bit[i].replace(ScalarOutput<int>(this, "bit"+std::to_string(i), "", "The bit "+std::to_string(i)+" of the bit mask")); - } - } - - ScalarOutput<int> bit[NBITS]; - }; - Output output{this, "output", "The extracted output bits"}; - - ScalarPushInput<int32_t> bitmask{this, "bitmask", "", "Input bit mask."}; + ScalarOutput<int> bit[NBITS]; + }; + Output output{this, "output", "The extracted output bits"}; - void mainLoop() { - while(true) { + ScalarPushInput<int32_t> bitmask{this, "bitmask", "", "Input bit mask."}; - // decode bit mask - for(size_t i=0; i<NBITS; ++i) { - output.bit[i] = (bitmask & (1 << i)) != 0; - output.bit[i].write(); /// @todo TODO better make a writeAll() for VariableGroups - } + void mainLoop() { + while (true) { - // wait for new input values (at the end, since we want to process the initial values first) - bitmask.read(); - } + // decode bit mask + for (size_t i = 0; i < NBITS; ++i) { + output.bit[i] = (bitmask & (1 << i)) != 0; + output.bit[i] + .write(); /// @todo TODO better make a writeAll() for VariableGroups } - }; + // wait for new input values (at the end, since we want to process the + // initial values first) + bitmask.read(); + } + } +}; } // namespace ChimeraTK diff --git a/Modules/include/ConfigReader.h b/Modules/include/ConfigReader.h index 956e095a..fa3fb2cc 100644 --- a/Modules/include/ConfigReader.h +++ b/Modules/include/ConfigReader.h @@ -9,149 +9,156 @@ namespace ChimeraTK { - struct FunctorFill; - struct ArrayFunctorFill; - struct FunctorSetValues; - struct FunctorSetValuesArray; - - /** - * Generic module to read an XML config file and provide the defined values as constant variables. The config file - * should look like this: - * \code{.xml} +struct FunctorFill; +struct ArrayFunctorFill; +struct FunctorSetValues; +struct FunctorSetValuesArray; + +/** + * Generic module to read an XML config file and provide the defined values as +constant variables. The config file + * should look like this: + * \code{.xml} <configuration> - <variable name="variableName" type="int32" value="42"/> - <variable name="anotherVariable" type="string" value="Hello world!"/> +<variable name="variableName" type="int32" value="42"/> +<variable name="anotherVariable" type="string" value="Hello world!"/> </configuration> - \endcode - * - * Outputs are created for each variable, so they can be connected to other modules. All values will be provided to - * the receivers already in the preparation phase, so no read() must be called. Updates will never be sent, so any - * blocking read operation on the receivers will block forever. - * - * Configuration values can already be accessed during the Application::defineConnection() function by using the - * ConfigReader::get() function. - */ - struct ConfigReader : ApplicationModule { - - ConfigReader(EntityOwner *owner, const std::string &name, const std::string &fileName, - const std::unordered_set<std::string> &tags={}); - - void mainLoop() override {} - void prepare() override; - - /** 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. - * To obtain the value of an array, use an std::vector<T> as template argument. */ - template<typename T> - const T& get(const std::string &variableName) const; - - protected: - - /** File name */ - std::string _fileName; - - /** throw a parsing error with more information */ - void parsingError(const std::string &message); - - /** Class holding the value and the accessor for one configuration variable */ - template<typename T> - struct Var { - Var(Module *owner, const std::string &name, const T &value) - : _accessor(owner, name, "unknown", "Configuration variable"), - _value(value) - {} - - ScalarOutput<T> _accessor; - 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 */ - template<typename T> - 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 */ - template<typename T> - using MapOfVar = std::map<std::string, Var<T>>; - - /** Type-depending map of vectors of variables */ - ChimeraTK::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 */ - ChimeraTK::TemplateUserTypeMap<MapOfArray> arrayMap; - - /** Map assigning string type identifyers to C++ types */ - ChimeraTK::SingleTypeUserTypeMap<const char*> typeMap{"int8","uint8","int16","uint16","int32","uint32", - "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 ArrayFunctorFill; - friend struct FunctorSetValues; - friend struct FunctorSetValuesArray; - + \endcode + * + * Outputs are created for each variable, so they can be connected to other +modules. All values will be provided to + * the receivers already in the preparation phase, so no read() must be called. +Updates will never be sent, so any + * blocking read operation on the receivers will block forever. + * + * Configuration values can already be accessed during the +Application::defineConnection() function by using the + * ConfigReader::get() function. + */ +struct ConfigReader : ApplicationModule { + + ConfigReader(EntityOwner *owner, const std::string &name, + const std::string &fileName, + const std::unordered_set<std::string> &tags = {}); + + void mainLoop() override {} + void prepare() override; + + /** 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. To obtain the value of an array, use an + * std::vector<T> as template argument. */ + template <typename T> const T &get(const std::string &variableName) const; + +protected: + /** File name */ + std::string _fileName; + + /** throw a parsing error with more information */ + void parsingError(const std::string &message); + + /** Class holding the value and the accessor for one configuration variable */ + template <typename T> struct Var { + Var(Module *owner, const std::string &name, const T &value) + : _accessor(owner, name, "unknown", "Configuration variable"), + _value(value) {} + + ScalarOutput<T> _accessor; + 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) {} - template<typename T> - const T& ConfigReader::get(const std::string &variableName) const { - return get_impl(variableName, static_cast<T*>(nullptr)); - } + ArrayOutput<T> _accessor; + std::vector<T> _value; + }; - /*********************************************************************************************************************/ - /*********************************************************************************************************************/ - - template<typename T> - const T& ConfigReader::get_impl(const std::string &variableName, T*) const { - try { - return boost::fusion::at_key<T>(variableMap.table).at(variableName)._value; - } - catch(std::out_of_range &e) { - throw(ChimeraTK::logic_error("ConfigReader: Cannot find a scalar configuration variable of the name '"+ - variableName+"' in the config file '"+_fileName+"'.")); - } + /** Create an instance of Var<T> and place it on the variableMap */ + template <typename T> + 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 */ + template <typename T> using MapOfVar = std::map<std::string, Var<T>>; + + /** Type-depending map of vectors of variables */ + ChimeraTK::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 */ + ChimeraTK::TemplateUserTypeMap<MapOfArray> arrayMap; + + /** Map assigning string type identifyers to C++ types */ + ChimeraTK::SingleTypeUserTypeMap<const char *> typeMap{ + "int8", "uint8", "int16", "uint16", "int32", "uint32", + "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 ArrayFunctorFill; + friend struct FunctorSetValues; + friend struct FunctorSetValuesArray; +}; + +/*********************************************************************************************************************/ +/*********************************************************************************************************************/ + +template <typename T> +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 { + return boost::fusion::at_key<T>(variableMap.table).at(variableName)._value; + } catch (std::out_of_range &e) { + throw(ChimeraTK::logic_error("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(ChimeraTK::logic_error("ConfigReader: Cannot find an array 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(ChimeraTK::logic_error("ConfigReader: Cannot find an array " + "configuration variable of the name '" + + variableName + "' in the config file '" + + _fileName + "'.")); } +} } // namespace ChimeraTK diff --git a/Modules/include/DataLossCounter.h b/Modules/include/DataLossCounter.h index c06f5c20..8b8f05b3 100644 --- a/Modules/include/DataLossCounter.h +++ b/Modules/include/DataLossCounter.h @@ -9,32 +9,37 @@ namespace ChimeraTK { - /** - * Module which gathers statistics on data loss inside the application. It will read the data loss counter once per - * trigger and update the output statistics variables. - */ - struct DataLossCounter : ApplicationModule { - - using ApplicationModule::ApplicationModule; - - ScalarPushInput<int32_t> trigger{this, "trigger", "", "Trigger input"}; - - ScalarOutput<uint64_t> lostDataInLastTrigger{this, "lostDataInLastTrigger", "", "Number of data transfers during " - "the last trigger which resulted in data loss."}; - ScalarOutput<uint64_t> triggersWithDataLoss{this, "triggersWithDataLoss", "", "Number of trigger periods during " - "which at least on data transfer resulted in data loss."}; - - void mainLoop() override { - while(true) { - trigger.read(); - uint64_t counter = Application::getAndResetDataLossCounter(); - lostDataInLastTrigger = counter; - if(counter > 0) ++triggersWithDataLoss; - writeAll(); - } - } - - }; +/** + * Module which gathers statistics on data loss inside the application. It will + * read the data loss counter once per trigger and update the output statistics + * variables. + */ +struct DataLossCounter : ApplicationModule { + + using ApplicationModule::ApplicationModule; + + ScalarPushInput<int32_t> trigger{this, "trigger", "", "Trigger input"}; + + ScalarOutput<uint64_t> lostDataInLastTrigger{ + this, "lostDataInLastTrigger", "", + "Number of data transfers during " + "the last trigger which resulted in data loss."}; + ScalarOutput<uint64_t> triggersWithDataLoss{ + this, "triggersWithDataLoss", "", + "Number of trigger periods during " + "which at least on data transfer resulted in data loss."}; + + void mainLoop() override { + while (true) { + trigger.read(); + uint64_t counter = Application::getAndResetDataLossCounter(); + lostDataInLastTrigger = counter; + if (counter > 0) + ++triggersWithDataLoss; + writeAll(); + } + } +}; } // namespace ChimeraTK diff --git a/Modules/include/LimitValue.h b/Modules/include/LimitValue.h index fb7891b8..752dd2c4 100644 --- a/Modules/include/LimitValue.h +++ b/Modules/include/LimitValue.h @@ -9,75 +9,76 @@ namespace ChimeraTK { - template<typename UserType> - struct LimitValueModuleBase : public ApplicationModule { - using ApplicationModule::ApplicationModule; - - ScalarPushInput<UserType> input{this, "input", "", "The input value to be limited into the range."}; - ScalarOutput<UserType> output{this, "output", "", "The output value after limiting."}; - ScalarOutput<int> isLimited{this, "isLimited", "", "Boolean set to true if the value was limited and to false otherwise."}; - - void applyLimit(UserType min, UserType max) { - bool wasLimited = isLimited; - - // clamp input value into given range - UserType value = input; - if(value > max) { - output = max; - isLimited = true; - } - else if(value < min) { - output = min; - isLimited = true; - } - else { - output = value; - isLimited = false; - } - - // write output. isLimited is only written when changed - output.write(); - if(isLimited != wasLimited) isLimited.write(); - wasLimited = isLimited; - - } - - }; - - template<typename UserType> - struct LimitValue : public LimitValueModuleBase<UserType> { - using LimitValueModuleBase<UserType>::LimitValueModuleBase; - ScalarPushInput<UserType> min{this, "min", "", "The minimum allowed value."}; - ScalarPushInput<UserType> max{this, "max", "", "The maximum allowed value."}; - using LimitValueModuleBase<UserType>::input; - using LimitValueModuleBase<UserType>::applyLimit; - - void mainLoop() { - auto readGroup = this->readAnyGroup(); - while(true) { - applyLimit(min,max); - // wait for new input values (at the end, since we want to process the initial values first) - readGroup.readAny(); - } - } - }; - - - template<typename UserType, UserType min, UserType max> - struct FixedLimitValue : public LimitValueModuleBase<UserType> { - using LimitValueModuleBase<UserType>::LimitValueModuleBase; - using LimitValueModuleBase<UserType>::input; - using LimitValueModuleBase<UserType>::applyLimit; - - void mainLoop() { - while(true) { - applyLimit(min,max); - // wait for new input values (at the end, since we want to process the initial values first) - input.read(); - } - } - - }; +template <typename UserType> +struct LimitValueModuleBase : public ApplicationModule { + using ApplicationModule::ApplicationModule; + + ScalarPushInput<UserType> input{ + this, "input", "", "The input value to be limited into the range."}; + ScalarOutput<UserType> output{this, "output", "", + "The output value after limiting."}; + ScalarOutput<int> isLimited{ + this, "isLimited", "", + "Boolean set to true if the value was limited and to false otherwise."}; + + void applyLimit(UserType min, UserType max) { + bool wasLimited = isLimited; + + // clamp input value into given range + UserType value = input; + if (value > max) { + output = max; + isLimited = true; + } else if (value < min) { + output = min; + isLimited = true; + } else { + output = value; + isLimited = false; + } + + // write output. isLimited is only written when changed + output.write(); + if (isLimited != wasLimited) + isLimited.write(); + wasLimited = isLimited; + } +}; + +template <typename UserType> +struct LimitValue : public LimitValueModuleBase<UserType> { + using LimitValueModuleBase<UserType>::LimitValueModuleBase; + ScalarPushInput<UserType> min{this, "min", "", "The minimum allowed value."}; + ScalarPushInput<UserType> max{this, "max", "", "The maximum allowed value."}; + using LimitValueModuleBase<UserType>::input; + using LimitValueModuleBase<UserType>::applyLimit; + + void mainLoop() { + auto readGroup = this->readAnyGroup(); + while (true) { + applyLimit(min, max); + // wait for new input values (at the end, since we want to process the + // initial values first) + readGroup.readAny(); + } + } +}; + +template <typename UserType, UserType min, UserType max> +struct FixedLimitValue : public LimitValueModuleBase<UserType> { + using LimitValueModuleBase<UserType>::LimitValueModuleBase; + using LimitValueModuleBase<UserType>::input; + using LimitValueModuleBase<UserType>::applyLimit; + + void mainLoop() { + while (true) { + applyLimit(min, max); + // wait for new input values (at the end, since we want to process the + // initial values first) + input.read(); + } + } +}; } // namespace ChimeraTK diff --git a/Modules/include/Logging.h b/Modules/include/Logging.h index 64e107bf..c06b2ba6 100644 --- a/Modules/include/Logging.h +++ b/Modules/include/Logging.h @@ -3,22 +3,32 @@ * \date 03.04.2018 * \page loggingdoc Logging module and Logger * \section logginintro Introduction to the logging mechanism - * The logging provided here requires to add the LoggingModule to your Application. - * The module introduces the following input variables, that need to be connected to the control system: - * - targetStream: Allows to choose where messages send to the logging module end up: + * The logging provided here requires to add the LoggingModule to your + Application. + * The module introduces the following input variables, that need to be + connected to the control system: + * - targetStream: Allows to choose where messages send to the logging module + end up: * - 0: cout/cerr+logfile * - 1: logfile * - 2: cout/cerr * - 3: nowhere - * - logFile: Give the logfile name. If the file is not empty logging messages will be appended. If - * you choose targetStream 0 or 1 and don't set a logFile the Logging module simply skips the file + * - logFile: Give the logfile name. If the file is not empty logging messages + will be appended. If + * you choose targetStream 0 or 1 and don't set a logFile the Logging module + simply skips the file * writing. - * - logLevel: Choose a certain logging level of the Module. Messages send to the Logging module also include a logging - * level. The Logging module compares both levels and decides if a message is dropped (e.g. message level is + * - logLevel: Choose a certain logging level of the Module. Messages send to + the Logging module also include a logging + * level. The Logging module compares both levels and decides if a message is + dropped (e.g. message level is * DEBUG and Module level is ERROR) or broadcasted. - * - tailLength: The number of messages published by the Logging module (see logTail), i.e. to the control system. - * This length has no influence on the targetStreams, that receive all messages (depending on the logLevel). The - * logLevel also applies to messages that are published by the Logging module via the logTail + * - tailLength: The number of messages published by the Logging module (see + logTail), i.e. to the control system. + * This length has no influence on the targetStreams, that receive all + messages (depending on the logLevel). The + * logLevel also applies to messages that are published by the Logging module + via the logTail * * Available logging levels are: * - DEBUG @@ -27,16 +37,23 @@ * - ERROR * - SILENT * - * The only variable that is published by the Logging module is the logTail. It contains the list of latest messages. - * Messages are separated by a newline character. The number of messages published in the logTail is set via the - * input variable tailLength. Other than that, messages are written to cout/cerr and/or a log file as explained above. + * The only variable that is published by the Logging module is the logTail. It + contains the list of latest messages. + * Messages are separated by a newline character. The number of messages + published in the logTail is set via the + * input variable tailLength. Other than that, messages are written to + cout/cerr and/or a log file as explained above. * - * In order to add a source to the Logging module the method \c addSource(Logger*) is + * In order to add a source to the Logging module the method \c + addSource(Logger*) is * available. - * The foreseen way of using the Logger is to add a Logger to a module that should send log messages. - * In the definition of connections of the application (\c defineConnections() ) one can add this source to the Logging module. + * The foreseen way of using the Logger is to add a Logger to a module that + should send log messages. + * In the definition of connections of the application (\c defineConnections() + ) one can add this source to the Logging module. * - * The following example shows how to integrate the Logging module and the Logging into an application (myApp) and one module sending + * The following example shows how to integrate the Logging module and the + Logging into an application (myApp) and one module sending * messages (TestModule). * \code * sruct TestModule: public ChimeraTK::ApplicationModule{ @@ -75,33 +92,35 @@ * \code * DEBUG::LogggingModule/test 2018-Apr-12 14:03:07.947949 -> Test * \endcode - * \remark Instead of adding a Logger to every module that should feed the Logging module, one could also consider using only one Logger object. - * This is not thread safe and would not work for multiple modules trying to send messages via the Logger object to the Logging module at the same time. + * \remark Instead of adding a Logger to every module that should feed the + Logging module, one could also consider using only one Logger object. + * This is not thread safe and would not work for multiple modules trying to + send messages via the Logger object to the Logging module at the same time. */ - #ifndef MODULES_LOGGING_H_ #define MODULES_LOGGING_H_ #undef GENERATE_XML #include "ApplicationCore.h" -#include <queue> -#include <map> #include <fstream> +#include <map> +#include <queue> namespace ctk = ChimeraTK; -namespace logging{ +namespace logging { /** Pair of message and messageLevel */ -using Message = std::pair<ctk::ScalarPushInput<std::string>,ctk::ScalarPushInput<uint> >; +using Message = + std::pair<ctk::ScalarPushInput<std::string>, ctk::ScalarPushInput<uint>>; /** Define available logging levels. */ enum LogLevel { DEBUG, INFO, WARNING, ERROR, SILENT }; /** Define stream operator to use the LogLevel in streams, e.g. std::cout */ -std::ostream& operator<<(std::ostream& os, const LogLevel &level); +std::ostream &operator<<(std::ostream &os, const LogLevel &level); /** Construct a sting containing the current time. */ std::string getTime(); @@ -109,30 +128,36 @@ std::string getTime(); /** * \brief Class used to send messages in a convenient way to the LoggingModule. * - * In principle this class only adds two output variables and provides a simple method - * to fill these variables. They are supposed to be connected to the LoggingModule via LoggingModule::addSource. - * If sendMessage is used before chimeraTK process variables are initialized an internal buffer is used to store those - * messages. Once the process variables are initialized the messages from the buffer are send. - * \attention This only happens once a message is send after chimeraTK process variables are initialized! - * In other words if no message is send in the mainLoop messages from defineConnections will never be shown. + * In principle this class only adds two output variables and provides a simple + * method to fill these variables. They are supposed to be connected to the + * LoggingModule via LoggingModule::addSource. If sendMessage is used before + * chimeraTK process variables are initialized an internal buffer is used to + * store those messages. Once the process variables are initialized the messages + * from the buffer are send. \attention This only happens once a message is send + * after chimeraTK process variables are initialized! In other words if no + * message is send in the mainLoop messages from defineConnections will never be + * shown. */ -class Logger{ +class Logger { private: - std::queue<std::pair<std::string, logging::LogLevel> > msg_buffer; + std::queue<std::pair<std::string, logging::LogLevel>> msg_buffer; + public: /** - * \brief Default constructor: Allows late initialization of modules (e.g. when creating arrays of modules). + * \brief Default constructor: Allows late initialization of modules (e.g. + * when creating arrays of modules). * - * This constructor also has to be here to mitigate a bug in gcc. It is needed to allow constructor - * inheritance of modules owning other modules. This constructor will not actually be called then. - * See this bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ + * This constructor also has to be here to mitigate a bug in gcc. It is + * needed to allow constructor inheritance of modules owning other modules. + * This constructor will not actually be called then. See this bug report: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ Logger(){}; /** * \brief Constructor to be used. * - * \param module The owning module that is using the Logger. It will appear as sender in the LoggingModule, - * which is receiving messages from the Logger. + * \param module The owning module that is using the Logger. It will appear as + * sender in the LoggingModule, which is receiving messages from the Logger. */ Logger(ctk::Module *module); /** Message to be send to the logging module */ @@ -142,7 +167,8 @@ public: ctk::ScalarOutput<uint> messageLevel; /** - * \brief Send a message, which means to update the message and messageLevel member variables. + * \brief Send a message, which means to update the message and messageLevel + * member variables. * */ void sendMessage(const std::string &msg, const logging::LogLevel &level); @@ -152,9 +178,9 @@ public: * \brief Module used to handle logging messages. * * A ChimeraTK module is producing messages, that are send to the LoggingModule - * via the \c message variable. The message is then put into the logfile ring buffer - * and published in the \c LogFileTail. In addidtion the message can be put to an ostream. - * Available streams are: + * via the \c message variable. The message is then put into the logfile ring + * buffer and published in the \c LogFileTail. In addidtion the message can be + * put to an ostream. Available streams are: * - file stream * - cout/cerr * @@ -166,19 +192,21 @@ public: * * The logfile is given by the client using the logFile variable. */ -class LoggingModule: public ctk::ApplicationModule { +class LoggingModule : public ctk::ApplicationModule { private: - std::pair<ctk::VariableNetworkNode,ctk::VariableNetworkNode> getAccessorPair(const std::string &sender); + std::pair<ctk::VariableNetworkNode, ctk::VariableNetworkNode> + getAccessorPair(const std::string &sender); /** Map key is the feeding module */ - std::map<std::string,Message> msg_list; + std::map<std::string, Message> msg_list; /** Update message or messageLevel from the sending module - * Basically the first element read by readAny() could be either the message or - * the messageLevel. - * Here the element not updated by readSAny is also read, since both are PushInput variables. + * Basically the first element read by readAny() could be either the message + * or the messageLevel. Here the element not updated by readSAny is also read, + * since both are PushInput variables. */ - std::map<std::string, Message>::iterator UpdatePair(const ChimeraTK::TransferElementID &id); + std::map<std::string, Message>::iterator + UpdatePair(const ChimeraTK::TransferElementID &id); /** Number of messages stored in the tail */ size_t messageCounter; @@ -192,20 +220,28 @@ private: public: using ctk::ApplicationModule::ApplicationModule; - ctk::ScalarPollInput<uint> targetStream { this, "targetStream", "", - "Set the tagret stream: 0 (cout/cerr+logfile), 1 (logfile), 2 (cout/cerr), 3 (none)" }; + ctk::ScalarPollInput<uint> targetStream{ + this, "targetStream", "", + "Set the tagret stream: 0 (cout/cerr+logfile), 1 (logfile), 2 " + "(cout/cerr), 3 (none)"}; - ctk::ScalarPollInput<std::string> logFile { this, "Logfile", "", - "Name of the external logfile. If empty messages are pushed to cout/cerr" }; + ctk::ScalarPollInput<std::string> logFile{ + this, "Logfile", "", + "Name of the external logfile. If empty messages are pushed to " + "cout/cerr"}; - ctk::ScalarPollInput<uint> tailLength { this, "maxLength", "", - "Maximum number of messages to be shown in the logging stream tail." }; + ctk::ScalarPollInput<uint> tailLength{ + this, "maxLength", "", + "Maximum number of messages to be shown in the logging stream tail."}; - ctk::ScalarPollInput<uint> logLevel { this, "logLevel", "", - "Current log level used for messages." }; + ctk::ScalarPollInput<uint> logLevel{this, "logLevel", "", + "Current log level used for messages."}; - ctk::ScalarOutput<std::string> logTail { this, "LogTail", "", "Tail of the logging stream.", - { "CS", "PROCESS", getName() } }; + ctk::ScalarOutput<std::string> logTail{this, + "LogTail", + "", + "Tail of the logging stream.", + {"CS", "PROCESS", getName()}}; std::unique_ptr<std::ofstream> file; ///< Log file where to write log messages @@ -218,9 +254,8 @@ public: void mainLoop() override; void terminate() override; - }; -} +} // namespace logging #endif /* MODULES_LOGGING_H_ */ diff --git a/Modules/include/MicroDAQ.h b/Modules/include/MicroDAQ.h index 4444c77f..b79547d2 100644 --- a/Modules/include/MicroDAQ.h +++ b/Modules/include/MicroDAQ.h @@ -10,85 +10,112 @@ namespace ChimeraTK { - namespace detail { - struct AccessorAttacher; - struct H5storage; - struct DataSpaceCreator; - struct DataWriter; - } +namespace detail { +struct AccessorAttacher; +struct H5storage; +struct DataSpaceCreator; +struct DataWriter; +} // namespace detail + +/** + * MicroDAQ module for logging data to HDF5 files. This can be usefull in + * enviromenents where no sufficient logging of data is possible through the + * control system. Any ChimeraTK::Module can act as a data source. Which + * variables should be logged can be selected through EntityOwner::findTag(). + */ +struct MicroDAQ : public ApplicationModule { /** - * MicroDAQ module for logging data to HDF5 files. This can be usefull in enviromenents where no sufficient logging - * of data is possible through the control system. Any ChimeraTK::Module can act as a data source. Which variables - * should be logged can be selected through EntityOwner::findTag(). + * Constructor. decimationFactor and decimationThreshold are configuration + * constants which determine how the data reduction is working. Arrays with a + * size bigger than decimationThreshold will be decimated by decimationFactor + * before writing to the HDF5 file. */ - struct MicroDAQ : public ApplicationModule { - - /** - * Constructor. decimationFactor and decimationThreshold are configuration constants which determine how the - * data reduction is working. Arrays with a size bigger than decimationThreshold will be decimated by - * decimationFactor before writing to the HDF5 file. - */ - MicroDAQ(EntityOwner *owner, const std::string &name, const std::string &description, uint32_t decimationFactor=10, - uint32_t decimationThreshold=1000, bool eliminateHierarchy=false, - const std::unordered_set<std::string> &tags={}) + MicroDAQ(EntityOwner *owner, const std::string &name, + const std::string &description, uint32_t decimationFactor = 10, + uint32_t decimationThreshold = 1000, bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}) : ApplicationModule(owner, name, description, eliminateHierarchy, tags), - decimationFactor_(decimationFactor), decimationThreshold_(decimationThreshold) {} - - /** Default constructor, creates a non-working module. Can be used for late initialisation. */ - MicroDAQ() : decimationFactor_(0), decimationThreshold_(0) {} - - ScalarPushInput<int> trigger{this, "trigger", "", "When written, the MicroDAQ write snapshot of all variables " - "to the file", {"MicroDAQ.CONFIG"}}; - ScalarPollInput<int> enable{this, "enable", "", "DAQ is active when set to 0 and disabled when set to 0.", - {"MicroDAQ.CONFIG"}}; - - ScalarPollInput<uint32_t> nMaxFiles{this, "nMaxFiles", "", "Maximum number of files in the ring buffer " - "(oldest file will be overwritten).", {"MicroDAQ.CONFIG"}}; - ScalarPollInput<uint32_t> nTriggersPerFile{this, "nTriggersPerFile", "", - "Number of triggers stored in each file.", {"MicroDAQ.CONFIG"}}; - - ScalarOutput<uint32_t> currentFile{this, "currentFile", "", "File number currently written to.", - {"MicroDAQ.CONFIG"}}; - - /** Add a Module as a source to this DAQ. */ - void addSource(const Module &source, const RegisterPath &namePrefix=""); - - protected: - - void mainLoop() override; - - template<typename UserType> - VariableNetworkNode getAccessor(const std::string &variableName); - - /** Map of VariableGroups required to build the hierarchies. The key it the full path name. */ - std::map<std::string, VariableGroup> groupMap; - - /** boost::fusion::map of UserTypes to std::lists containing the ArrayPollInput accessors. These accessors are - * dynamically created by the AccessorAttacher. */ - template<typename UserType> - using AccessorList = std::list<ArrayPollInput<UserType>>; - TemplateUserTypeMap<AccessorList> accessorListMap; - - /** boost::fusion::map of UserTypes to std::lists containing the names of the accessors. Technically there would - * be no need to use TemplateUserTypeMap for this (as type does not depend on the UserType), but since these - * lists must be filled consistently with the accessorListMap, the same construction is used here. */ - template<typename UserType> - using NameList = std::list<std::string>; - TemplateUserTypeMap<NameList> nameListMap; - - /** Overall variable name list, used to detect name collisions */ - std::list<std::string> overallVariableList; - - /** Parameters for the data decimation */ - uint32_t decimationFactor_, decimationThreshold_; - - friend struct detail::AccessorAttacher; - friend struct detail::H5storage; - friend struct detail::DataSpaceCreator; - friend struct detail::DataWriter; - - }; + decimationFactor_(decimationFactor), + decimationThreshold_(decimationThreshold) {} + + /** Default constructor, creates a non-working module. Can be used for late + * initialisation. */ + MicroDAQ() : decimationFactor_(0), decimationThreshold_(0) {} + + ScalarPushInput<int> trigger{ + this, + "trigger", + "", + "When written, the MicroDAQ write snapshot of all variables " + "to the file", + {"MicroDAQ.CONFIG"}}; + ScalarPollInput<int> enable{ + this, + "enable", + "", + "DAQ is active when set to 0 and disabled when set to 0.", + {"MicroDAQ.CONFIG"}}; + + ScalarPollInput<uint32_t> nMaxFiles{ + this, + "nMaxFiles", + "", + "Maximum number of files in the ring buffer " + "(oldest file will be overwritten).", + {"MicroDAQ.CONFIG"}}; + ScalarPollInput<uint32_t> nTriggersPerFile{ + this, + "nTriggersPerFile", + "", + "Number of triggers stored in each file.", + {"MicroDAQ.CONFIG"}}; + + ScalarOutput<uint32_t> currentFile{this, + "currentFile", + "", + "File number currently written to.", + {"MicroDAQ.CONFIG"}}; + + /** Add a Module as a source to this DAQ. */ + void addSource(const Module &source, const RegisterPath &namePrefix = ""); + +protected: + void mainLoop() override; + + template <typename UserType> + VariableNetworkNode getAccessor(const std::string &variableName); + + /** Map of VariableGroups required to build the hierarchies. The key it the + * full path name. */ + std::map<std::string, VariableGroup> groupMap; + + /** boost::fusion::map of UserTypes to std::lists containing the + * ArrayPollInput accessors. These accessors are dynamically created by the + * AccessorAttacher. */ + template <typename UserType> + using AccessorList = std::list<ArrayPollInput<UserType>>; + TemplateUserTypeMap<AccessorList> accessorListMap; + + /** boost::fusion::map of UserTypes to std::lists containing the names of the + * accessors. Technically there would be no need to use TemplateUserTypeMap + * for this (as type does not depend on the UserType), but since these lists + * must be filled consistently with the accessorListMap, the same construction + * is used here. */ + template <typename UserType> using NameList = std::list<std::string>; + TemplateUserTypeMap<NameList> nameListMap; + + /** Overall variable name list, used to detect name collisions */ + std::list<std::string> overallVariableList; + + /** Parameters for the data decimation */ + uint32_t decimationFactor_, decimationThreshold_; + + friend struct detail::AccessorAttacher; + friend struct detail::H5storage; + friend struct detail::DataSpaceCreator; + friend struct detail::DataWriter; +}; } // namespace ChimeraTK diff --git a/Modules/include/Multiplier.h b/Modules/include/Multiplier.h index 7c7acb18..2efeb582 100644 --- a/Modules/include/Multiplier.h +++ b/Modules/include/Multiplier.h @@ -5,140 +5,149 @@ #ifndef CHIMERATK_APPLICATION_CORE_MULTIPLIER_H #define CHIMERATK_APPLICATION_CORE_MULTIPLIER_H -#include <limits> #include <cmath> +#include <limits> #include "ApplicationCore.h" namespace ChimeraTK { - template<typename InputType, typename OutputType=InputType, size_t NELEMS=1> - struct ConstMultiplier : public ApplicationModule { +template <typename InputType, typename OutputType = InputType, + size_t NELEMS = 1> +struct ConstMultiplier : public ApplicationModule { - ConstMultiplier(EntityOwner *owner, const std::string &name, const std::string &description, double factor) + ConstMultiplier(EntityOwner *owner, const std::string &name, + const std::string &description, double factor) : ApplicationModule(owner, name, ""), input(this, "input", "", NELEMS, description), - output(this, "output", "", NELEMS, description), - _factor(factor) - { - setEliminateHierarchy(); - } - - ArrayPushInput<InputType> input; - ArrayOutput<OutputType> output; - - double _factor; - - void mainLoop() { - while(true) { - - // scale value (with rounding, if integral type) - if(!std::numeric_limits<OutputType>::is_integer) { - for(size_t i=0; i<NELEMS; ++i) output[i] = input[i] * _factor; - } - else { - for(size_t i=0; i<NELEMS; ++i) output[i] = std::round(input[i] * _factor); - } - - // write scaled value - output.write(); + output(this, "output", "", NELEMS, description), _factor(factor) { + setEliminateHierarchy(); + } - // wait for new input value (at the end, since we want to process the initial values first) - input.read(); - } - } - - }; + ArrayPushInput<InputType> input; + ArrayOutput<OutputType> output; - template<typename InputType, typename OutputType=InputType, size_t NELEMS=1> - struct Multiplier : public ApplicationModule { + double _factor; - using ApplicationModule::ApplicationModule; + void mainLoop() { + while (true) { - Multiplier(EntityOwner *owner, const std::string &name, const std::string &factorName, - const std::string &unitInput, const std::string &unitOutput, - const std::string &description, const std::unordered_set<std::string> &tagsInput={}, - const std::unordered_set<std::string> &tagsOutput={}, - const std::unordered_set<std::string> &tagsFactor={}) - : ApplicationModule(owner, name, "", true) - { - input.replace(ArrayPushInput<InputType>(this, name, unitInput, NELEMS, description, tagsInput)); - output.replace(ArrayOutput<OutputType>(this, name, unitOutput, NELEMS, description, tagsOutput)); - factor.replace(ScalarPushInput<double>(this, factorName, "("+unitOutput+")/("+unitInput+")", description, tagsFactor)); + // scale value (with rounding, if integral type) + if (!std::numeric_limits<OutputType>::is_integer) { + for (size_t i = 0; i < NELEMS; ++i) + output[i] = input[i] * _factor; + } else { + for (size_t i = 0; i < NELEMS; ++i) + output[i] = std::round(input[i] * _factor); } - /** Note: This constructor is deprectated! */ - Multiplier(EntityOwner *owner, const std::string &name, const std::string &description) + // write scaled value + output.write(); + + // wait for new input value (at the end, since we want to process the + // initial values first) + input.read(); + } + } +}; + +template <typename InputType, typename OutputType = InputType, + size_t NELEMS = 1> +struct Multiplier : public ApplicationModule { + + using ApplicationModule::ApplicationModule; + + Multiplier(EntityOwner *owner, const std::string &name, + const std::string &factorName, const std::string &unitInput, + const std::string &unitOutput, const std::string &description, + const std::unordered_set<std::string> &tagsInput = {}, + const std::unordered_set<std::string> &tagsOutput = {}, + const std::unordered_set<std::string> &tagsFactor = {}) + : ApplicationModule(owner, name, "", true) { + input.replace(ArrayPushInput<InputType>(this, name, unitInput, NELEMS, + description, tagsInput)); + output.replace(ArrayOutput<OutputType>(this, name, unitOutput, NELEMS, + description, tagsOutput)); + factor.replace(ScalarPushInput<double>( + this, factorName, "(" + unitOutput + ")/(" + unitInput + ")", + description, tagsFactor)); + } + + /** Note: This constructor is deprectated! */ + Multiplier(EntityOwner *owner, const std::string &name, + const std::string &description) : ApplicationModule(owner, name, "", true), input(this, "input", "", NELEMS, description), factor(this, "factor", "", "Factor to scale the input value with"), - output(this, "output", "", NELEMS, description) - {} - - ArrayPushInput<InputType> input; - ScalarPushInput<double> factor; - ArrayOutput<OutputType> output; - - void mainLoop() { - ReadAnyGroup group{input, factor}; - while(true) { - - // scale value (with rounding, if integral type) - if(!std::numeric_limits<OutputType>::is_integer) { - for(size_t i=0; i<NELEMS; ++i) output[i] = input[i] * factor; - } - else { - for(size_t i=0; i<NELEMS; ++i) output[i] = std::round(input[i] * factor); - } - - // write scaled value - output.write(); - - // wait for new input value (at the end, since we want to process the initial values first) - group.readAny(); - } + output(this, "output", "", NELEMS, description) {} + + ArrayPushInput<InputType> input; + ScalarPushInput<double> factor; + ArrayOutput<OutputType> output; + + void mainLoop() { + ReadAnyGroup group{input, factor}; + while (true) { + + // scale value (with rounding, if integral type) + if (!std::numeric_limits<OutputType>::is_integer) { + for (size_t i = 0; i < NELEMS; ++i) + output[i] = input[i] * factor; + } else { + for (size_t i = 0; i < NELEMS; ++i) + output[i] = std::round(input[i] * factor); } - }; + // write scaled value + output.write(); - template<typename InputType, typename OutputType=InputType, size_t NELEMS=1> - struct Divider : public ApplicationModule { + // wait for new input value (at the end, since we want to process the + // initial values first) + group.readAny(); + } + } +}; - using ApplicationModule::ApplicationModule; - Divider(EntityOwner *owner, const std::string &name, const std::string &description) +template <typename InputType, typename OutputType = InputType, + size_t NELEMS = 1> +struct Divider : public ApplicationModule { + + using ApplicationModule::ApplicationModule; + Divider(EntityOwner *owner, const std::string &name, + const std::string &description) : ApplicationModule(owner, name, ""), input(this, "input", "", NELEMS, description), divider(this, "divider", "", "Divider to scale the input value with"), - output(this, "output", "", NELEMS, description) - { - setEliminateHierarchy(); + output(this, "output", "", NELEMS, description) { + setEliminateHierarchy(); + } + + ArrayPushInput<InputType> input; + ScalarPushInput<double> divider; + ArrayOutput<OutputType> output; + + void mainLoop() { + ReadAnyGroup group{input, divider}; + while (true) { + + // scale value (with rounding, if integral type) + if (!std::numeric_limits<OutputType>::is_integer) { + for (size_t i = 0; i < NELEMS; ++i) + output[i] = input[i] / divider; + } else { + for (size_t i = 0; i < NELEMS; ++i) + output[i] = std::round(input[i] / divider); } - ArrayPushInput<InputType> input; - ScalarPushInput<double> divider; - ArrayOutput<OutputType> output; - - void mainLoop() { - ReadAnyGroup group{input, divider}; - while(true) { - - // scale value (with rounding, if integral type) - if(!std::numeric_limits<OutputType>::is_integer) { - for(size_t i=0; i<NELEMS; ++i) output[i] = input[i] / divider; - } - else { - for(size_t i=0; i<NELEMS; ++i) output[i] = std::round(input[i] / divider); - } - - // write scaled value - output.write(); - - // wait for new input value (at the end, since we want to process the initial values first) - group.readAny(); - } - } + // write scaled value + output.write(); - }; + // wait for new input value (at the end, since we want to process the + // initial values first) + group.readAny(); + } + } +}; } // namespace ChimeraTK diff --git a/Modules/include/PeriodicTrigger.h b/Modules/include/PeriodicTrigger.h index 809d04a6..6a734a34 100644 --- a/Modules/include/PeriodicTrigger.h +++ b/Modules/include/PeriodicTrigger.h @@ -7,55 +7,63 @@ namespace ChimeraTK { - /** - * Simple periodic trigger that fires a variable once per second. - * After configurable number of seconds it will wrap around - */ - struct PeriodicTrigger : public ApplicationModule { - - /** Constructor. In addition to the usual arguments of an ApplicationModule, the default timeout value is specified. - * This value is used as a timeout if the timeout value is set to 0. The timeout value is in milliseconds. */ - PeriodicTrigger(EntityOwner *owner, const std::string &name, const std::string &description, - const uint32_t defaultPeriod=1000, bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}) +/** + * Simple periodic trigger that fires a variable once per second. + * After configurable number of seconds it will wrap around + */ +struct PeriodicTrigger : public ApplicationModule { + + /** Constructor. In addition to the usual arguments of an ApplicationModule, + * the default timeout value is specified. + * This value is used as a timeout if the timeout value is set to 0. The + * timeout value is in milliseconds. */ + PeriodicTrigger(EntityOwner *owner, const std::string &name, + const std::string &description, + const uint32_t defaultPeriod = 1000, + bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}) : ApplicationModule(owner, name, description, eliminateHierarchy, tags), - defaultPeriod_(defaultPeriod) - {} + defaultPeriod_(defaultPeriod) {} - ScalarPollInput<uint32_t> period{this, "period", "ms", "period in milliseconds. The trigger is sent once per the specified duration."}; - ScalarOutput<uint64_t> tick{this, "tick", "", "Timer tick. Counts the trigger number starting from 0."}; + ScalarPollInput<uint32_t> period{this, "period", "ms", + "period in milliseconds. The trigger is " + "sent once per the specified duration."}; + ScalarOutput<uint64_t> tick{ + this, "tick", "", + "Timer tick. Counts the trigger number starting from 0."}; - void sendTrigger() { - ++tick; - tick.write(); - } + void sendTrigger() { + ++tick; + tick.write(); + } - void mainLoop() { - if (Application::getInstance().isTestableModeEnabled()) { - return; - } - tick = 0; - std::chrono::time_point<std::chrono::steady_clock> t = std::chrono::steady_clock::now(); - - while(true) { - period.read(); - if(period == 0){ - // set receiving end of timeout. Will only be overwritten if there is new data. - period = defaultPeriod_; - } - t += std::chrono::milliseconds(static_cast<uint32_t>(period)); - boost::this_thread::interruption_point(); - std::this_thread::sleep_until(t); - - tick++; - tick.write(); - } - } + void mainLoop() { + if (Application::getInstance().isTestableModeEnabled()) { + return; + } + tick = 0; + std::chrono::time_point<std::chrono::steady_clock> t = + std::chrono::steady_clock::now(); - private: + while (true) { + period.read(); + if (period == 0) { + // set receiving end of timeout. Will only be overwritten if there is + // new data. + period = defaultPeriod_; + } + t += std::chrono::milliseconds(static_cast<uint32_t>(period)); + boost::this_thread::interruption_point(); + std::this_thread::sleep_until(t); - uint32_t defaultPeriod_; + tick++; + tick.write(); + } + } - }; -} +private: + uint32_t defaultPeriod_; +}; +} // namespace ChimeraTK #endif // CHIMERATK_APPLICATION_CORE_PERIODIC_TRIGGER_H diff --git a/Modules/include/Pipe.h b/Modules/include/Pipe.h index d58a2e85..d711b0db 100644 --- a/Modules/include/Pipe.h +++ b/Modules/include/Pipe.h @@ -1,93 +1,104 @@ #ifndef CHIMERATK_APPLICATION_CORE_PIPE_H #define CHIMERATK_APPLICATION_CORE_PIPE_H -#include <limits> #include <cmath> +#include <limits> #include "ApplicationCore.h" namespace ChimeraTK { - /** - * Generic module to pipe through a scalar value without altering it. - * @todo Make it more efficient by removing this module entirely in the ApplicationCore connection logic! - */ - template<typename Type> - struct ScalarPipe : public ApplicationModule { - - ScalarPipe(EntityOwner *owner, const std::string &name, const std::string &unit, const std::string &description, - const std::unordered_set<std::string> &tagsInput={}, const std::unordered_set<std::string> &tagsOutput={}) - : ApplicationModule(owner, name, "", true) - { - input.replace(ScalarPushInput<Type>(this, name, unit, description, tagsInput)); - output.replace(ScalarOutput<Type>(this, name, unit, description, tagsOutput)); - } - - ScalarPipe(EntityOwner *owner, const std::string &inputName, const std::string &outputName, const std::string &unit, - const std::string &description, - const std::unordered_set<std::string> &tagsInput={}, const std::unordered_set<std::string> &tagsOutput={}) - : ApplicationModule(owner, inputName, "", true) - { - input.replace(ScalarPushInput<Type>(this, inputName, unit, description, tagsInput)); - output.replace(ScalarOutput<Type>(this, outputName, unit, description, tagsOutput)); - } - - ScalarPipe() {} - - ScalarPushInput<Type> input; - ScalarOutput<Type> output; - - void mainLoop() { - while(true) { - output = static_cast<Type>(input); - output.write(); - input.read(); - } - } - - }; - - /** - * Generic module to pipe through a scalar value without altering it. - * @todo Make it more efficient by removing this module entirely in the ApplicationCore connection logic! - */ - template<typename Type> - struct ArrayPipe : public ApplicationModule { - - ArrayPipe(EntityOwner *owner, const std::string &name, const std::string &unit, size_t nElements, const std::string &description, - const std::unordered_set<std::string> &tagsInput={}, const std::unordered_set<std::string> &tagsOutput={}) - : ApplicationModule(owner, name, description, true) - { - input.replace(ArrayPushInput<Type>(this, name, unit, nElements, description, tagsInput)); - output.replace(ArrayOutput<Type>(this, name, unit, nElements, description, tagsOutput)); - } - - ArrayPipe(EntityOwner *owner, const std::string &inputName, const std::string &outputName, const std::string &unit, - size_t nElements, const std::string &description, - const std::unordered_set<std::string> &tagsInput={}, const std::unordered_set<std::string> &tagsOutput={}) - : ApplicationModule(owner, inputName, description, true) - { - input.replace(ArrayPushInput<Type>(this, inputName, unit, nElements, description, tagsInput)); - output.replace(ArrayOutput<Type>(this, outputName, unit, nElements, description, tagsOutput)); - } - - ArrayPipe() {} - - ArrayPushInput<Type> input; - ArrayOutput<Type> output; - - void mainLoop() { - std::vector<Type> temp(input.getNElements()); - while(true) { - input.swap(temp); - output.swap(temp); - input.swap(temp); - output.write(); - input.read(); - } - } - - }; +/** + * Generic module to pipe through a scalar value without altering it. + * @todo Make it more efficient by removing this module entirely in the + * ApplicationCore connection logic! + */ +template <typename Type> struct ScalarPipe : public ApplicationModule { + + ScalarPipe(EntityOwner *owner, const std::string &name, + const std::string &unit, const std::string &description, + const std::unordered_set<std::string> &tagsInput = {}, + const std::unordered_set<std::string> &tagsOutput = {}) + : ApplicationModule(owner, name, "", true) { + input.replace( + ScalarPushInput<Type>(this, name, unit, description, tagsInput)); + output.replace( + ScalarOutput<Type>(this, name, unit, description, tagsOutput)); + } + + ScalarPipe(EntityOwner *owner, const std::string &inputName, + const std::string &outputName, const std::string &unit, + const std::string &description, + const std::unordered_set<std::string> &tagsInput = {}, + const std::unordered_set<std::string> &tagsOutput = {}) + : ApplicationModule(owner, inputName, "", true) { + input.replace( + ScalarPushInput<Type>(this, inputName, unit, description, tagsInput)); + output.replace( + ScalarOutput<Type>(this, outputName, unit, description, tagsOutput)); + } + + ScalarPipe() {} + + ScalarPushInput<Type> input; + ScalarOutput<Type> output; + + void mainLoop() { + while (true) { + output = static_cast<Type>(input); + output.write(); + input.read(); + } + } +}; + +/** + * Generic module to pipe through a scalar value without altering it. + * @todo Make it more efficient by removing this module entirely in the + * ApplicationCore connection logic! + */ +template <typename Type> struct ArrayPipe : public ApplicationModule { + + ArrayPipe(EntityOwner *owner, const std::string &name, + const std::string &unit, size_t nElements, + const std::string &description, + const std::unordered_set<std::string> &tagsInput = {}, + const std::unordered_set<std::string> &tagsOutput = {}) + : ApplicationModule(owner, name, description, true) { + input.replace(ArrayPushInput<Type>(this, name, unit, nElements, description, + tagsInput)); + output.replace(ArrayOutput<Type>(this, name, unit, nElements, description, + tagsOutput)); + } + + ArrayPipe(EntityOwner *owner, const std::string &inputName, + const std::string &outputName, const std::string &unit, + size_t nElements, const std::string &description, + const std::unordered_set<std::string> &tagsInput = {}, + const std::unordered_set<std::string> &tagsOutput = {}) + : ApplicationModule(owner, inputName, description, true) { + input.replace(ArrayPushInput<Type>(this, inputName, unit, nElements, + description, tagsInput)); + output.replace(ArrayOutput<Type>(this, outputName, unit, nElements, + description, tagsOutput)); + } + + ArrayPipe() {} + + ArrayPushInput<Type> input; + ArrayOutput<Type> output; + + void mainLoop() { + std::vector<Type> temp(input.getNElements()); + while (true) { + input.swap(temp); + output.swap(temp); + input.swap(temp); + output.write(); + input.read(); + } + } +}; } // namespace ChimeraTK diff --git a/Modules/include/ServerHistory.h b/Modules/include/ServerHistory.h index 6681b26a..39df411b 100644 --- a/Modules/include/ServerHistory.h +++ b/Modules/include/ServerHistory.h @@ -3,20 +3,24 @@ * \date 10.08.2018 * \page historydoc Server based history module * \section historyintro Server based history - * Some control systems offer a variable history but some do not. In this case the \c ServerHistory can be used to - * create a history ring buffer on the server. If only a local history is needed consider to use the \c MicroDAQ module. - * In order to do so you connect the variable that should have a history on the server - * to the \c ServerHistory module. - * The history length is set during module construction and fixed per module. Every time one of the variable handled by the history module - * is updated it will be filled into the history buffer. The buffer length (history length) can not be changed during runtime. + * Some control systems offer a variable history but some do not. In this case + * the \c ServerHistory can be used to create a history ring buffer on the + * server. If only a local history is needed consider to use the \c MicroDAQ + * module. In order to do so you connect the variable that should have a history + * on the server to the \c ServerHistory module. The history length is set + * during module construction and fixed per module. Every time one of the + * variable handled by the history module is updated it will be filled into the + * history buffer. The buffer length (history length) can not be changed during + * runtime. * * - * Output variables created by the \c ServerHistory module are named like their feeding process variables with a prefixed name - * that is set when the process variables is added to the history module. - * In case of Array type feeding process variables n history buffers are created (where n is the Array size) and the element index i is appended to the - * feeding process variable name. - * In consequence an input array of length i will result in i output history arrays. - * The following tags are added to the history output variable: + * Output variables created by the \c ServerHistory module are named like their + * feeding process variables with a prefixed name that is set when the process + * variables is added to the history module. In case of Array type feeding + * process variables n history buffers are created (where n is the Array size) + * and the element index i is appended to the feeding process variable name. In + * consequence an input array of length i will result in i output history + * arrays. The following tags are added to the history output variable: * - CS * - name of the history module * @@ -24,12 +28,14 @@ * The following example shows how to integrate the \c ServerHistory module. * \code * sruct TestModule: public ChimeraTK::ApplicationModule{ - * chimeraTK::ScalarOutput<float> measurement{this, "measurement", "" , "measurement variable", {"CS", History"}}; + * chimeraTK::ScalarOutput<float> measurement{this, "measurement", "" , + * "measurement variable", {"CS", History"}}; * ... * }; * struct myApp : public ChimeraTK::Application{ * - * history::ServerHistory<float> history{this, "ServerHistory", "History for certain scalara float variables", 20}; // History buffer length is 20 + * history::ServerHistory<float> history{this, "ServerHistory", "History for + * certain scalara float variables", 20}; // History buffer length is 20 * * ChimeraTK::ControlSystemModule cs; * @@ -49,28 +55,30 @@ * \endcode */ - #ifndef MODULES_SERVERHISTORY_H_ #define MODULES_SERVERHISTORY_H_ #include "ApplicationCore.h" #include <ChimeraTK/SupportedUserTypes.h> -#include <vector> #include <tuple> +#include <vector> -namespace ChimeraTK{ -namespace history{ +namespace ChimeraTK { +namespace history { struct AccessorAttacher; -struct ServerHistory: public ApplicationModule{ - ServerHistory(EntityOwner *owner, const std::string &name, const std::string &description, size_t historyLength=1200, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}) - : ApplicationModule(owner, name, description, eliminateHierarchy, tags), - _historyLength(historyLength){} +struct ServerHistory : public ApplicationModule { + ServerHistory(EntityOwner *owner, const std::string &name, + const std::string &description, size_t historyLength = 1200, + bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}) + : ApplicationModule(owner, name, description, eliminateHierarchy, tags), + _historyLength(historyLength) {} - /** Default constructor, creates a non-working module. Can be used for late initialisation. */ + /** Default constructor, creates a non-working module. Can be used for late + * initialisation. */ ServerHistory() : _historyLength(1200) {} /** Add a Module as a source to this History module. */ @@ -79,23 +87,28 @@ struct ServerHistory: public ApplicationModule{ protected: void mainLoop() override; - template<typename UserType> - VariableNetworkNode getAccessor(const std::string &variableName, const size_t &nElements); + template <typename UserType> + VariableNetworkNode getAccessor(const std::string &variableName, + const size_t &nElements); - /** Map of VariableGroups required to build the hierarchies. The key it the full path name. */ + /** Map of VariableGroups required to build the hierarchies. The key it the + * full path name. */ std::map<std::string, VariableGroup> groupMap; - /** boost::fusion::map of UserTypes to std::lists containing the ArrayPushInput and ArrayOutput accessors. These accessors are - * dynamically created by the AccessorAttacher. */ - template<typename UserType> - using AccessorList = std::list<std::pair<ArrayPushInput<UserType>, std::vector<ArrayOutput<UserType> > > >; + /** boost::fusion::map of UserTypes to std::lists containing the + * ArrayPushInput and ArrayOutput accessors. These accessors are dynamically + * created by the AccessorAttacher. */ + template <typename UserType> + using AccessorList = std::list< + std::pair<ArrayPushInput<UserType>, std::vector<ArrayOutput<UserType>>>>; TemplateUserTypeMap<AccessorList> _accessorListMap; - /** boost::fusion::map of UserTypes to std::lists containing the names of the accessors. Technically there would - * be no need to use TemplateUserTypeMap for this (as type does not depend on the UserType), but since these - * lists must be filled consistently with the accessorListMap, the same construction is used here. */ - template<typename UserType> - using NameList = std::list<std::string>; + /** boost::fusion::map of UserTypes to std::lists containing the names of the + * accessors. Technically there would be no need to use TemplateUserTypeMap + * for this (as type does not depend on the UserType), but since these lists + * must be filled consistently with the accessorListMap, the same construction + * is used here. */ + template <typename UserType> using NameList = std::list<std::string>; TemplateUserTypeMap<NameList> _nameListMap; /** Overall variable name list, used to detect name collisions */ @@ -106,6 +119,6 @@ protected: friend struct AccessorAttacher; }; -} // namespace histroy +} // namespace history } // namespace ChimeraTK #endif /* MODULES_SERVERHISTORY_H_ */ diff --git a/Modules/include/SplitArray.h b/Modules/include/SplitArray.h index 5733cef5..3461c0ee 100644 --- a/Modules/include/SplitArray.h +++ b/Modules/include/SplitArray.h @@ -9,171 +9,181 @@ namespace ChimeraTK { - /** - * Split an array of the data type TYPE into nGroups with each nElemsPerGroup elements. The splitted array is an - * output of this module and will be written everytime any of the inputs is updated. Each input is an array of the - * length nElemsPerGroup and there are nGroups inputs. nElemsPerGroup defaults to 1, so the array is split into - * its individual elements (and the inputs can be used as scalars). - * - * The output array is called "output", while each input is called "input#", where # is the index of the input - * counting from 0. When using the C++ interface, the inputs are stored in a std::vector and thus can be accessed - * e.g. through "input[index]". - * - * The output array has a size of nGroups*nElementsPerGroup. - */ - template<typename TYPE> - struct WriteSplitArrayModule : public ApplicationModule { - - WriteSplitArrayModule(EntityOwner *owner, const std::string &name, const std::string &description, - size_t nGroups, size_t nElemsPerGroup=1); - - WriteSplitArrayModule() {} - - /** Vector of input arrays, each with a length of nElemsPerGroup. If nElemsPerGroup is 1 (default), the inputs - * can be used as scalars. - * - * The input with the index "index" corresponds to the elements "index*nElemsPerGroup" to - * "(index+1)*nElemsPerGroup-1" of the output array. */ - std::vector<ArrayPushInput<TYPE>> input; - - /** Output array. Will be updated each time any input was changed with the corresponding data from the input. */ - ArrayOutput<TYPE> output; +/** + * Split an array of the data type TYPE into nGroups with each nElemsPerGroup + * elements. The splitted array is an output of this module and will be written + * everytime any of the inputs is updated. Each input is an array of the length + * nElemsPerGroup and there are nGroups inputs. nElemsPerGroup defaults to 1, so + * the array is split into its individual elements (and the inputs can be used + * as scalars). + * + * The output array is called "output", while each input is called "input#", + * where # is the index of the input counting from 0. When using the C++ + * interface, the inputs are stored in a std::vector and thus can be accessed + * e.g. through "input[index]". + * + * The output array has a size of nGroups*nElementsPerGroup. + */ +template <typename TYPE> +struct WriteSplitArrayModule : public ApplicationModule { - void mainLoop(); + WriteSplitArrayModule(EntityOwner *owner, const std::string &name, + const std::string &description, size_t nGroups, + size_t nElemsPerGroup = 1); - private: + WriteSplitArrayModule() {} - size_t _nGroups; - size_t _nElemsPerGroup; + /** Vector of input arrays, each with a length of nElemsPerGroup. If + * nElemsPerGroup is 1 (default), the inputs can be used as scalars. + * + * The input with the index "index" corresponds to the elements + * "index*nElemsPerGroup" to + * "(index+1)*nElemsPerGroup-1" of the output array. */ + std::vector<ArrayPushInput<TYPE>> input; + + /** Output array. Will be updated each time any input was changed with the + * corresponding data from the input. */ + ArrayOutput<TYPE> output; + + void mainLoop(); + +private: + size_t _nGroups; + size_t _nElemsPerGroup; +}; + +/*********************************************************************************************************************/ + +/** + * Split an array of the data type TYPE into nGroups with each nElemsPerGroup + * elements. The splitted array is an input to this module and ann outputs will + * be written to each time the input is updated. Each output is an array of then + * length nElemsPerGroup and there are nGroups inputs. nElemsPerGroup defaults + * to 1, so the array is split into its individual elements (and the outpus can + * be used as scalars). + * + * The input array is called "input", while each output is called "output#", + * where # is the index of the output counting from 0. When using the C++ + * interface, the outputs are stored in a std::vector and thus can be accessed + * e.g. through "output[index]". + * + * The input array has a size of nGroups*nElementsPerGroup. + */ +template <typename TYPE> +struct ReadSplitArrayModule : public ApplicationModule { - }; + ReadSplitArrayModule(EntityOwner *owner, const std::string &name, + const std::string &description, size_t nGroups, + size_t nElemsPerGroup = 1); - /*********************************************************************************************************************/ + ReadSplitArrayModule() {} - /** - * Split an array of the data type TYPE into nGroups with each nElemsPerGroup elements. The splitted array is an - * input to this module and ann outputs will be written to each time the input is updated. Each output is an array of - * then length nElemsPerGroup and there are nGroups inputs. nElemsPerGroup defaults to 1, so the array is split into - * its individual elements (and the outpus can be used as scalars). - * - * The input array is called "input", while each output is called "output#", where # is the index of the output - * counting from 0. When using the C++ interface, the outputs are stored in a std::vector and thus can be accessed - * e.g. through "output[index]". + /** Vector of output arrays, each with a length of nElemsPerGroup. If + * nElemsPerGroup is 1 (default), the outputs can be used as scalars. * - * The input array has a size of nGroups*nElementsPerGroup. - */ - template<typename TYPE> - struct ReadSplitArrayModule : public ApplicationModule { - - ReadSplitArrayModule(EntityOwner *owner, const std::string &name, const std::string &description, - size_t nGroups, size_t nElemsPerGroup=1); - - ReadSplitArrayModule() {} - - /** Vector of output arrays, each with a length of nElemsPerGroup. If nElemsPerGroup is 1 (default), the outputs - * can be used as scalars. - * - * The output with the index "index" corresponds to the elements "index*nElemsPerGroup" to - * "(index+1)*nElemsPerGroup-1" of the input array. */ - std::vector<ArrayOutput<TYPE>> output; - - /** Input array. Each time this input is changed, all outputs will be updated with the corresponding data. */ - ArrayPushInput<TYPE> input; - - void mainLoop(); - - private: - - size_t _nGroups; - size_t _nElemsPerGroup; - - }; - - /*********************************************************************************************************************/ - /*********************************************************************************************************************/ - - template<typename TYPE> - WriteSplitArrayModule<TYPE>::WriteSplitArrayModule(EntityOwner *owner, const std::string &name, - const std::string &description, size_t nGroups, size_t nElemsPerGroup) - : ApplicationModule(owner, name, description), - output(this, "output", "", nGroups*nElemsPerGroup, "Output array"), - _nGroups(nGroups), - _nElemsPerGroup(nElemsPerGroup) - { - for(size_t i=0; i<_nGroups; ++i) { - std::string comment; - if(_nElemsPerGroup == 1) { - comment = "The element "+std::to_string(i)+" of the output array"; - } - else { - comment = "The elements "+std::to_string(i*_nElemsPerGroup)+" to "+ - std::to_string((i+1)*_nElemsPerGroup-1)+" of the output array"; - } - input.push_back(ArrayPushInput<TYPE>(this, "input"+std::to_string(i), "", _nElemsPerGroup, comment)); + * The output with the index "index" corresponds to the elements + * "index*nElemsPerGroup" to + * "(index+1)*nElemsPerGroup-1" of the input array. */ + std::vector<ArrayOutput<TYPE>> output; + + /** Input array. Each time this input is changed, all outputs will be updated + * with the corresponding data. */ + ArrayPushInput<TYPE> input; + + void mainLoop(); + +private: + size_t _nGroups; + size_t _nElemsPerGroup; +}; + +/*********************************************************************************************************************/ +/*********************************************************************************************************************/ + +template <typename TYPE> +WriteSplitArrayModule<TYPE>::WriteSplitArrayModule( + EntityOwner *owner, const std::string &name, const std::string &description, + size_t nGroups, size_t nElemsPerGroup) + : ApplicationModule(owner, name, description), + output(this, "output", "", nGroups * nElemsPerGroup, "Output array"), + _nGroups(nGroups), _nElemsPerGroup(nElemsPerGroup) { + for (size_t i = 0; i < _nGroups; ++i) { + std::string comment; + if (_nElemsPerGroup == 1) { + comment = "The element " + std::to_string(i) + " of the output array"; + } else { + comment = "The elements " + std::to_string(i * _nElemsPerGroup) + " to " + + std::to_string((i + 1) * _nElemsPerGroup - 1) + + " of the output array"; } + input.push_back(ArrayPushInput<TYPE>(this, "input" + std::to_string(i), "", + _nElemsPerGroup, comment)); } +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - template<typename TYPE> - void WriteSplitArrayModule<TYPE>::mainLoop() { - auto readGroup = readAnyGroup(); - while(true) { +template <typename TYPE> void WriteSplitArrayModule<TYPE>::mainLoop() { + auto readGroup = readAnyGroup(); + while (true) { - // write the array from the individual elements - for(size_t i=0; i<_nGroups; ++i) { - for(size_t k=0; k<_nElemsPerGroup; ++k) { - output[i*_nElemsPerGroup + k] = input[i][k]; - } + // write the array from the individual elements + for (size_t i = 0; i < _nGroups; ++i) { + for (size_t k = 0; k < _nElemsPerGroup; ++k) { + output[i * _nElemsPerGroup + k] = input[i][k]; } - output.write(); - - // wait for new input values (at the end, since we want to process the initial values first) - readGroup.readAny(); } - } + output.write(); - /*********************************************************************************************************************/ - - template<typename TYPE> - ReadSplitArrayModule<TYPE>::ReadSplitArrayModule(EntityOwner *owner, const std::string &name, - const std::string &description, size_t nGroups, size_t nElemsPerGroup) - : ApplicationModule(owner, name, description), - input(this, "input", "", nGroups*nElemsPerGroup, "Input array"), - _nGroups(nGroups), - _nElemsPerGroup(nElemsPerGroup) - { - for(size_t i=0; i<_nGroups; ++i) { - std::string comment; - if(_nElemsPerGroup == 1) { - comment = "The element "+std::to_string(i)+" of the input array"; - } - else { - comment = "The elements "+std::to_string(i*_nElemsPerGroup)+" to "+ - std::to_string((i+1)*_nElemsPerGroup-1)+" of the input array"; - } - output.push_back(ArrayOutput<TYPE>(this, "output"+std::to_string(i), "", _nElemsPerGroup, comment)); + // wait for new input values (at the end, since we want to process the + // initial values first) + readGroup.readAny(); + } +} + +/*********************************************************************************************************************/ + +template <typename TYPE> +ReadSplitArrayModule<TYPE>::ReadSplitArrayModule(EntityOwner *owner, + const std::string &name, + const std::string &description, + size_t nGroups, + size_t nElemsPerGroup) + : ApplicationModule(owner, name, description), + input(this, "input", "", nGroups * nElemsPerGroup, "Input array"), + _nGroups(nGroups), _nElemsPerGroup(nElemsPerGroup) { + for (size_t i = 0; i < _nGroups; ++i) { + std::string comment; + if (_nElemsPerGroup == 1) { + comment = "The element " + std::to_string(i) + " of the input array"; + } else { + comment = "The elements " + std::to_string(i * _nElemsPerGroup) + " to " + + std::to_string((i + 1) * _nElemsPerGroup - 1) + + " of the input array"; } + output.push_back(ArrayOutput<TYPE>(this, "output" + std::to_string(i), "", + _nElemsPerGroup, comment)); } +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - template<typename TYPE> - void ReadSplitArrayModule<TYPE>::mainLoop() { - while(true) { +template <typename TYPE> void ReadSplitArrayModule<TYPE>::mainLoop() { + while (true) { - // write the array from the individual elements - for(size_t i=0; i<_nGroups; ++i) { - for(size_t k=0; k<_nElemsPerGroup; ++k) { - input[i][k] = output[i*_nElemsPerGroup + k]; - } + // write the array from the individual elements + for (size_t i = 0; i < _nGroups; ++i) { + for (size_t k = 0; k < _nElemsPerGroup; ++k) { + input[i][k] = output[i * _nElemsPerGroup + k]; } - writeAll(); - - // wait for new input values (at the end, since we want to process the initial values first) - input.read(); } + writeAll(); + + // wait for new input values (at the end, since we want to process the + // initial values first) + input.read(); } +} } // namespace ChimeraTK diff --git a/Modules/src/ConfigReader.cc b/Modules/src/ConfigReader.cc index 289f8435..ec0e5098 100644 --- a/Modules/src/ConfigReader.cc +++ b/Modules/src/ConfigReader.cc @@ -4,316 +4,357 @@ namespace ChimeraTK { - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - /** Functor to fill variableMap */ - struct FunctorFill { - 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) - {} +/** Functor to fill variableMap */ +struct FunctorFill { + 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) {} - template<typename PAIR> - void operator()(PAIR&) const { + template <typename PAIR> void operator()(PAIR &) const { - // extract the user type from the pair - typedef typename PAIR::first_type T; + // 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; + // skip this type, if not matching the type string in the config file + if (_type != boost::fusion::at_key<T>(_owner->typeMap)) + return; - _owner->createVar<T>(_name, _value); - _processed = true; - } - - ConfigReader *_owner; - const std::string &_type, &_name, &_value; - bool &_processed; // must be a non-const reference, since we want to return this to the caller + _owner->createVar<T>(_name, _value); + _processed = true; + } - typedef boost::fusion::pair<std::string,ConfigReader::Var<std::string>> StringPair; - }; + ConfigReader *_owner; + const std::string &_type, &_name, &_value; + 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 { +/** 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) {} - // extract the user type from the pair - typedef typename PAIR::first_type T; + template <typename PAIR> void operator()(PAIR &) const { - // skip this type, if not matching the type string in the config file - if(_type != boost::fusion::at_key<T>(_owner->typeMap)) return; + // extract the user type from the pair + typedef typename PAIR::first_type T; - _owner->createArray<T>(_name, _values); - _processed = true; - } + // skip this type, if not matching the type string in the config file + if (_type != boost::fusion::at_key<T>(_owner->typeMap)) + return; - 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 + _owner->createArray<T>(_name, _values); + _processed = true; + } - typedef boost::fusion::pair<std::string,ConfigReader::Var<std::string>> StringPair; - }; + 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; +}; + +/*********************************************************************************************************************/ + +template <typename T> +void ConfigReader::createVar(const std::string &name, + const std::string &value) { + // convert value into user type + /// @todo error handling! + std::stringstream stream(value); + 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; + } - /*********************************************************************************************************************/ + // place the variable onto the vector + 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))); +} + +/*********************************************************************************************************************/ + +template <> +void ConfigReader::createVar<std::string>(const std::string &name, + const std::string &value) { + // place the variable onto the vector + std::map<std::string, ConfigReader::Var<std::string>> &theMap = + boost::fusion::at_key<std::string>(variableMap.table); + theMap.emplace( + std::make_pair(name, ConfigReader::Var<std::string>(this, name, value))); +} + +/*********************************************************************************************************************/ + +/*********************************************************************************************************************/ + +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; - template<typename T> - void ConfigReader::createVar(const std::string &name, const std::string &value) { // convert value into user type - /// @todo error handling! - std::stringstream stream(value); + std::stringstream stream(it->second); T convertedValue; - if(typeid(T) == typeid(int8_t) || typeid(T) == typeid(uint8_t)) { // prevent interpreting int8-types as characters + 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 + } else { // note: string is done in template specialisation stream >> convertedValue; } - // place the variable onto the vector - 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))); - } - - /*********************************************************************************************************************/ - - template<> - void ConfigReader::createVar<std::string>(const std::string &name, const std::string &value) { - // place the variable onto the vector - std::map<std::string, ConfigReader::Var<std::string>> &theMap = boost::fusion::at_key<std::string>(variableMap.table); - theMap.emplace(std::make_pair(name, ConfigReader::Var<std::string>(this, name, value))); + // store value in vector + Tvalues.push_back(convertedValue); } - /*********************************************************************************************************************/ - - /*********************************************************************************************************************/ - - 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; - // 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))); + // store value in vector + Tvalues.push_back(it->second); } - /*********************************************************************************************************************/ - - 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, + const std::unordered_set<std::string> &tags) + : ApplicationModule(owner, name, + "Configuration read from file '" + fileName + "'", + false, tags), + _fileName(fileName) { + // parse the file into a DOM structure + xmlpp::DomParser parser; + try { + parser.parse_file(fileName); + } catch (xmlpp::exception &e) { /// @todo change exception! + throw ChimeraTK::logic_error( + "ConfigReader: Error opening the config file '" + fileName + + "': " + e.what()); + } - // 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))); + // get root element + const auto root = parser.get_document()->get_root_node(); + if (root->get_name() != "configuration") { + parsingError("Expected 'configuration' tag instead of: " + + root->get_name()); } - /*********************************************************************************************************************/ - - ConfigReader::ConfigReader(EntityOwner *owner, const std::string &name, const std::string &fileName, - const std::unordered_set<std::string> &tags) - : ApplicationModule(owner, name, "Configuration read from file '"+fileName+"'", false, tags), - _fileName(fileName) - { - // parse the file into a DOM structure - xmlpp::DomParser parser; - try { - parser.parse_file(fileName); - } - catch(xmlpp::exception &e) { /// @todo change exception! - throw ChimeraTK::logic_error("ConfigReader: Error opening the config file '"+fileName+"': "+e.what()); + // parsing loop + for (const auto &child : root->get_children()) { + // cast into element, ignore if not an element (e.g. comment) + const xmlpp::Element *element = dynamic_cast<const xmlpp::Element *>(child); + if (!element) + continue; + if (element->get_name() != "variable") { + parsingError("Expected 'variable' tag instead of: " + root->get_name()); } - // get root element - const auto root = parser.get_document()->get_root_node(); - if(root->get_name() != "configuration") { - parsingError("Expected 'configuration' tag instead of: "+root->get_name()); + // obtain attributes from the element + auto name = element->get_attribute("name"); + if (!name) + parsingError("Missing attribute 'name' for the 'variable' tag."); + auto type = element->get_attribute("type"); + if (!type) + parsingError("Missing attribute 'type' for the 'variable' tag."); + + // scalar value: obtain value from attribute + auto value = element->get_attribute("value"); + if (value) { + // create accessor and store value in map using the functor + bool processed{false}; + boost::fusion::for_each(variableMap.table, + 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."); } - - // parsing loop - for(const auto& child : root->get_children()) { - // cast into element, ignore if not an element (e.g. comment) - const xmlpp::Element *element = dynamic_cast<const xmlpp::Element*>(child); - if(!element) continue; - if(element->get_name() != "variable") { - parsingError("Expected 'variable' tag instead of: "+root->get_name()); - } - - // obtain attributes from the element - auto name = element->get_attribute("name"); - if(!name) parsingError("Missing attribute 'name' for the 'variable' tag."); - auto type = element->get_attribute("type"); - if(!type) parsingError("Missing attribute 'type' for the 'variable' tag."); - - // scalar value: obtain value from attribute - auto value = element->get_attribute("value"); - if(value) { - // create accessor and store value in map using the functor - bool processed{false}; - boost::fusion::for_each( variableMap.table, - 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."); - } - // 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(); - + // 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()); } - - // 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."); + 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(); + } - // 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."); + // 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."); } } +} - /********************************************************************************************************************/ - - void ConfigReader::parsingError(const std::string &message) { - throw ChimeraTK::logic_error("ConfigReader: Error parsing the config file '"+_fileName+"': "+message); - } +/********************************************************************************************************************/ - /*********************************************************************************************************************/ +void ConfigReader::parsingError(const std::string &message) { + throw ChimeraTK::logic_error("ConfigReader: Error parsing the config file '" + + _fileName + "': " + message); +} - /** Functor to set values to the scalar accessors */ - struct FunctorSetValues { - FunctorSetValues(ConfigReader *owner) : _owner(owner) {} +/*********************************************************************************************************************/ - template<typename PAIR> - void operator()(PAIR&) const { +/** Functor to set values to the scalar accessors */ +struct FunctorSetValues { + FunctorSetValues(ConfigReader *owner) : _owner(owner) {} - // get user type and vector - typedef typename PAIR::first_type T; - std::map<std::string, ConfigReader::Var<T>> &theMap = boost::fusion::at_key<T>(_owner->variableMap.table); + template <typename PAIR> void operator()(PAIR &) const { - // iterate vector and set values - for(auto &pair : theMap) { - auto &var = pair.second; - var._accessor = var._value; - var._accessor.write(); - } + // get user type and vector + typedef typename PAIR::first_type T; + std::map<std::string, ConfigReader::Var<T>> &theMap = + boost::fusion::at_key<T>(_owner->variableMap.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; +}; - /** Functor to set values to the array accessors */ - struct FunctorSetValuesArray { - FunctorSetValuesArray(ConfigReader *owner) : _owner(owner) {} +/*********************************************************************************************************************/ - template<typename PAIR> - void operator()(PAIR&) const { +/** Functor to set values to the array accessors */ +struct FunctorSetValuesArray { + FunctorSetValuesArray(ConfigReader *owner) : _owner(owner) {} - // 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); + template <typename PAIR> void operator()(PAIR &) const { - // iterate vector and set values - for(auto &pair : theMap) { - auto &var = pair.second; - var._accessor = var._value; - var._accessor.write(); - } + // 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; +}; - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void ConfigReader::prepare() { - boost::fusion::for_each( variableMap.table, FunctorSetValues(this) ); - boost::fusion::for_each( arrayMap.table, FunctorSetValuesArray(this) ); - } +void ConfigReader::prepare() { + boost::fusion::for_each(variableMap.table, FunctorSetValues(this)); + boost::fusion::for_each(arrayMap.table, FunctorSetValuesArray(this)); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ } // namespace ChimeraTK - diff --git a/Modules/src/Logging.cc b/Modules/src/Logging.cc index 4b2446cb..1331224c 100644 --- a/Modules/src/Logging.cc +++ b/Modules/src/Logging.cc @@ -5,58 +5,62 @@ * Author: zenker */ -#include <string> -#include <sstream> -#include <ostream> #include <fstream> +#include <ostream> +#include <sstream> +#include <string> #include <vector> -#include "boost/date_time/posix_time/posix_time.hpp" #include "Logging.h" +#include "boost/date_time/posix_time/posix_time.hpp" using namespace logging; -std::ostream& logging::operator<<(std::ostream &os,const LogLevel &level){ - switch(level){ - case LogLevel::DEBUG: - os << "DEBUG::"; - break; - case LogLevel::INFO: - os << "INFO::"; - break; - case LogLevel::WARNING: - os << "WARNING::"; - break; - case LogLevel::ERROR: - os << "ERROR::"; - break; - default: - break; +std::ostream &logging::operator<<(std::ostream &os, const LogLevel &level) { + switch (level) { + case LogLevel::DEBUG: + os << "DEBUG::"; + break; + case LogLevel::INFO: + os << "INFO::"; + break; + case LogLevel::WARNING: + os << "WARNING::"; + break; + case LogLevel::ERROR: + os << "ERROR::"; + break; + default: + break; } return os; } -std::string logging::getTime(){ +std::string logging::getTime() { std::string str; - str.append(boost::posix_time::to_simple_string(boost::posix_time::microsec_clock::local_time()) + " "); + str.append(boost::posix_time::to_simple_string( + boost::posix_time::microsec_clock::local_time()) + + " "); str.append(" -> "); return str; } -Logger::Logger(ctk::Module* module): - message(module, "message", "", "Message of the module to the logging System", - { "Logging", "OneWire", module->getName() }), - messageLevel(module, "messageLevel", "", "Logging level of the message", - { "Logging", "OneWire", module->getName() }){} - -void Logger::sendMessage(const std::string &msg, const logging::LogLevel &level){ - if(message.isInitialised()){ - while(!msg_buffer.empty()){ - message = msg_buffer.front().first; - messageLevel = msg_buffer.front().second; - message.write(); - messageLevel.write(); - msg_buffer.pop(); +Logger::Logger(ctk::Module *module) + : message(module, "message", "", + "Message of the module to the logging System", + {"Logging", "OneWire", module->getName()}), + messageLevel(module, "messageLevel", "", "Logging level of the message", + {"Logging", "OneWire", module->getName()}) {} + +void Logger::sendMessage(const std::string &msg, + const logging::LogLevel &level) { + if (message.isInitialised()) { + while (!msg_buffer.empty()) { + message = msg_buffer.front().first; + messageLevel = msg_buffer.front().second; + message.write(); + messageLevel.write(); + msg_buffer.pop(); } message = msg + "\n"; @@ -69,28 +73,28 @@ void Logger::sendMessage(const std::string &msg, const logging::LogLevel &level) } } -void LoggingModule::broadcastMessage(std::string msg, bool isError){ - if(msg.back() != '\n'){ +void LoggingModule::broadcastMessage(std::string msg, bool isError) { + if (msg.back() != '\n') { msg.append("\n"); } - std::string tmpLog = (std::string)logTail; - if( tailLength == 0 && messageCounter > 20){ + std::string tmpLog = (std::string)logTail; + if (tailLength == 0 && messageCounter > 20) { messageCounter--; - tmpLog = tmpLog.substr(tmpLog.find_first_of("\n")+1, tmpLog.length()); - } else if (tailLength > 0){ - while(messageCounter >= tailLength){ + tmpLog = tmpLog.substr(tmpLog.find_first_of("\n") + 1, tmpLog.length()); + } else if (tailLength > 0) { + while (messageCounter >= tailLength) { messageCounter--; - tmpLog = tmpLog.substr(tmpLog.find_first_of("\n")+1, tmpLog.length()); + tmpLog = tmpLog.substr(tmpLog.find_first_of("\n") + 1, tmpLog.length()); } } - if(targetStream == 0 || targetStream == 2){ - if(isError) + if (targetStream == 0 || targetStream == 2) { + if (isError) std::cerr << msg; else std::cout << msg; } - if(targetStream == 0 || targetStream == 1){ - if(file->is_open()){ + if (targetStream == 0 || targetStream == 1) { + if (file->is_open()) { (*file) << msg.c_str(); file->flush(); } @@ -101,41 +105,47 @@ void LoggingModule::broadcastMessage(std::string msg, bool isError){ logTail.write(); } -void LoggingModule::mainLoop(){ +void LoggingModule::mainLoop() { file.reset(new std::ofstream()); messageCounter = 0; std::stringstream greeter; - greeter << getName() << " " << getTime() << "There are " << msg_list.size() << " modules registered for logging:" << std::endl; + greeter << getName() << " " << getTime() << "There are " << msg_list.size() + << " modules registered for logging:" << std::endl; broadcastMessage(greeter.str()); - for(auto &module : msg_list){ + for (auto &module : msg_list) { broadcastMessage(std::string("\t - ") + module.first); } auto group = readAnyGroup(); - while(1){ + while (1) { auto id = group.readAny(); auto sender = UpdatePair(id); - if(targetStream == 3) + if (targetStream == 3) continue; LogLevel level = static_cast<LogLevel>((uint)sender->second.second); LogLevel setLevel = static_cast<LogLevel>((uint)logLevel); std::stringstream ss; - ss << level << getName() << "/" << sender->first << " " << getTime() << (std::string)sender->second.first; - if(targetStream == 0 || targetStream == 1){ - if(!((std::string)logFile).empty() && !file->is_open()){ - file->open((std::string)logFile, std::ofstream::out | std::ofstream::app); + ss << level << getName() << "/" << sender->first << " " << getTime() + << (std::string)sender->second.first; + if (targetStream == 0 || targetStream == 1) { + if (!((std::string)logFile).empty() && !file->is_open()) { + file->open((std::string)logFile, + std::ofstream::out | std::ofstream::app); std::stringstream ss_file; - if(!file->is_open() && setLevel <= LogLevel::ERROR){ - ss_file << LogLevel::ERROR << getName() << " " << getTime() << "Failed to open log file for writing: " << (std::string)logFile << std::endl; + if (!file->is_open() && setLevel <= LogLevel::ERROR) { + ss_file << LogLevel::ERROR << getName() << " " << getTime() + << "Failed to open log file for writing: " + << (std::string)logFile << std::endl; broadcastMessage(ss_file.str(), true); - } else if (file->is_open() && setLevel <= LogLevel::INFO){ - ss_file << LogLevel::INFO << getName() << " " << getTime() << "Opened log file for writing: " << (std::string)logFile << std::endl; + } else if (file->is_open() && setLevel <= LogLevel::INFO) { + ss_file << LogLevel::INFO << getName() << " " << getTime() + << "Opened log file for writing: " << (std::string)logFile + << std::endl; broadcastMessage(ss_file.str()); } - } } - if(level >= setLevel){ - if(level < LogLevel::ERROR) + if (level >= setLevel) { + if (level < LogLevel::ERROR) broadcastMessage(ss.str()); else broadcastMessage(ss.str(), true); @@ -143,44 +153,49 @@ void LoggingModule::mainLoop(){ } } -void LoggingModule::addSource(Logger *logger){ +void LoggingModule::addSource(Logger *logger) { auto acc = getAccessorPair(logger->message.getOwner()->getName()); logger->message >> acc.first; logger->messageLevel >> acc.second; } -std::pair<ctk::VariableNetworkNode,ctk::VariableNetworkNode> LoggingModule::getAccessorPair(const std::string &sender) { - if(msg_list.count(sender) == 0){ - msg_list.emplace(std::piecewise_construct, std::make_tuple(sender),std::forward_as_tuple( - std::piecewise_construct, - std::forward_as_tuple(ctk::ScalarPushInput<std::string>{this, sender + "Msg", "", ""}), - std::forward_as_tuple(ctk::ScalarPushInput<uint>{this, sender + "MsgLevel", "", ""}))); +std::pair<ctk::VariableNetworkNode, ctk::VariableNetworkNode> +LoggingModule::getAccessorPair(const std::string &sender) { + if (msg_list.count(sender) == 0) { + msg_list.emplace( + std::piecewise_construct, std::make_tuple(sender), + std::forward_as_tuple( + std::piecewise_construct, + std::forward_as_tuple(ctk::ScalarPushInput<std::string>{ + this, sender + "Msg", "", ""}), + std::forward_as_tuple(ctk::ScalarPushInput<uint>{ + this, sender + "MsgLevel", "", ""}))); } else { - throw ChimeraTK::logic_error("Cannot add logging for module "+sender+ - " since logging was already added for this module."); + throw ChimeraTK::logic_error( + "Cannot add logging for module " + sender + + " since logging was already added for this module."); } return msg_list[sender]; } -std::map<std::string, Message>::iterator LoggingModule::UpdatePair(const ChimeraTK::TransferElementID &id){ - for(auto it = msg_list.begin(), iend = msg_list.end(); it != iend; it++){ - if(it->second.first.getId() == id){ +std::map<std::string, Message>::iterator +LoggingModule::UpdatePair(const ChimeraTK::TransferElementID &id) { + for (auto it = msg_list.begin(), iend = msg_list.end(); it != iend; it++) { + if (it->second.first.getId() == id) { it->second.second.read(); return it; } - if(it->second.second.getId() == id){ + if (it->second.second.getId() == id) { it->second.first.read(); return it; } } throw ChimeraTK::logic_error("Cannot find element id" - "when updating logging variables."); + "when updating logging variables."); } -void LoggingModule::terminate(){ - if((file.get() != nullptr) && (file->is_open())) +void LoggingModule::terminate() { + if ((file.get() != nullptr) && (file->is_open())) file->close(); ApplicationModule::terminate(); } - - diff --git a/Modules/src/MicroDAQ.cc b/Modules/src/MicroDAQ.cc index a6e26c9e..dd80d9a9 100644 --- a/Modules/src/MicroDAQ.cc +++ b/Modules/src/MicroDAQ.cc @@ -9,391 +9,421 @@ namespace ChimeraTK { - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - namespace detail { +namespace detail { - /** Callable class for use with boost::fusion::for_each: Attach the given accessor to the MicroDAQ with proper - * handling of the UserType. */ - struct AccessorAttacher { - AccessorAttacher(VariableNetworkNode& feeder, MicroDAQ *owner, const std::string &name) +/** Callable class for use with boost::fusion::for_each: Attach the given + * accessor to the MicroDAQ with proper handling of the UserType. */ +struct AccessorAttacher { + AccessorAttacher(VariableNetworkNode &feeder, MicroDAQ *owner, + const std::string &name) : _feeder(feeder), _owner(owner), _name(name) {} - template<typename PAIR> - void operator()(PAIR&) const { + template <typename PAIR> void operator()(PAIR &) const { - // only continue if the call is for the right type - if(typeid(typename PAIR::first_type) != _feeder.getValueType()) return; + // only continue if the call is for the right type + if (typeid(typename PAIR::first_type) != _feeder.getValueType()) + return; - // register connection - _feeder >> _owner->template getAccessor<typename PAIR::first_type>(_name); + // register connection + _feeder >> _owner->template getAccessor<typename PAIR::first_type>(_name); + } - } + VariableNetworkNode &_feeder; + MicroDAQ *_owner; + const std::string &_name; +}; - VariableNetworkNode &_feeder; - MicroDAQ *_owner; - const std::string &_name; - }; +} // namespace detail - } +/*********************************************************************************************************************/ - /*********************************************************************************************************************/ +void MicroDAQ::addSource(const Module &source, const RegisterPath &namePrefix) { - void MicroDAQ::addSource(const Module &source, const RegisterPath &namePrefix) { + // for simplification, first create a VirtualModule containing the correct + // hierarchy structure (obeying eliminate hierarchy etc.) + auto dynamicModel = source.findTag(".*"); /// @todo use virtualise() instead - // for simplification, first create a VirtualModule containing the correct hierarchy structure (obeying eliminate - // hierarchy etc.) - auto dynamicModel = source.findTag(".*"); /// @todo use virtualise() instead - - // create variable group map for namePrefix if needed - if(groupMap.find(namePrefix) == groupMap.end()) { - // search for existing parent (if any) - auto parentPrefix = namePrefix; - while(groupMap.find(parentPrefix) == groupMap.end()) { - if(parentPrefix == "/") break; // no existing parent found - parentPrefix = std::string(parentPrefix).substr(0,std::string(parentPrefix).find_last_of("/")); - } - // create all not-yet-existing parents - while(parentPrefix != namePrefix) { - EntityOwner *owner = this; - if(parentPrefix != "/") owner = &groupMap[parentPrefix]; - auto stop = std::string(namePrefix).find_first_of("/", parentPrefix.length()+1); - if(stop == std::string::npos) stop = namePrefix.length(); - RegisterPath name = std::string(namePrefix).substr(parentPrefix.length(),stop-parentPrefix.length()); - parentPrefix /= name; - groupMap[parentPrefix] = VariableGroup(owner, std::string(name).substr(1), ""); - } + // create variable group map for namePrefix if needed + if (groupMap.find(namePrefix) == groupMap.end()) { + // search for existing parent (if any) + auto parentPrefix = namePrefix; + while (groupMap.find(parentPrefix) == groupMap.end()) { + if (parentPrefix == "/") + break; // no existing parent found + parentPrefix = + std::string(parentPrefix) + .substr(0, std::string(parentPrefix).find_last_of("/")); } - - // add all accessors on this hierarchy level - for(auto &acc : dynamicModel.getAccessorList()) { - boost::fusion::for_each(accessorListMap.table, detail::AccessorAttacher(acc, this, namePrefix/acc.getName())); + // create all not-yet-existing parents + while (parentPrefix != namePrefix) { + EntityOwner *owner = this; + if (parentPrefix != "/") + owner = &groupMap[parentPrefix]; + auto stop = + std::string(namePrefix).find_first_of("/", parentPrefix.length() + 1); + if (stop == std::string::npos) + stop = namePrefix.length(); + RegisterPath name = + std::string(namePrefix) + .substr(parentPrefix.length(), stop - parentPrefix.length()); + parentPrefix /= name; + groupMap[parentPrefix] = + VariableGroup(owner, std::string(name).substr(1), ""); } + } - // recurse into submodules - for(auto mod : dynamicModel.getSubmoduleList()) { - addSource(*mod, namePrefix/mod->getName()); - } + // add all accessors on this hierarchy level + for (auto &acc : dynamicModel.getAccessorList()) { + boost::fusion::for_each( + accessorListMap.table, + detail::AccessorAttacher(acc, this, namePrefix / acc.getName())); + } + // recurse into submodules + for (auto mod : dynamicModel.getSubmoduleList()) { + addSource(*mod, namePrefix / mod->getName()); } +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - template<typename UserType> - VariableNetworkNode MicroDAQ::getAccessor(const std::string &variableName) { +template <typename UserType> +VariableNetworkNode MicroDAQ::getAccessor(const std::string &variableName) { - // check if variable name already registered - for(auto &name : overallVariableList) { - if(name == variableName) { - throw ChimeraTK::logic_error("Cannot add '"+variableName+ - "' to MicroDAQ since a variable with that name is already registered."); + // check if variable name already registered + for (auto &name : overallVariableList) { + if (name == variableName) { + throw ChimeraTK::logic_error("Cannot add '" + variableName + + "' to MicroDAQ since a variable with that " + "name is already registered."); + } + } + overallVariableList.push_back(variableName); + + // add accessor and name to lists + auto &accessorList = boost::fusion::at_key<UserType>(accessorListMap.table); + auto &nameList = boost::fusion::at_key<UserType>(nameListMap.table); + auto dirName = variableName.substr(0, variableName.find_last_of("/")); + auto baseName = variableName.substr(variableName.find_last_of("/") + 1); + accessorList.emplace_back(&groupMap[dirName], baseName, "", 0, ""); + nameList.push_back(variableName); + + // return the accessor + return accessorList.back(); +} + +/*********************************************************************************************************************/ + +namespace detail { + +struct H5storage { + H5storage(MicroDAQ *owner) : _owner(owner) {} + + H5::H5File outFile; + std::string currentGroupName; + + /** Unique list of groups, used to create the groups in the file */ + std::list<std::string> groupList; + + /** boost::fusion::map of UserTypes to std::lists containing the H5::DataSpace + * objects. */ + template <typename UserType> using dataSpaceList = std::list<H5::DataSpace>; + TemplateUserTypeMap<dataSpaceList> dataSpaceListMap; + + /** boost::fusion::map of UserTypes to std::lists containing decimation + * factors. */ + template <typename UserType> using decimationFactorList = std::list<size_t>; + TemplateUserTypeMap<decimationFactorList> decimationFactorListMap; + + uint32_t currentBuffer{0}; + uint32_t nFillsInBuffer{0}; + bool isOpened{false}; + bool firstTrigger{true}; + + void processTrigger(); + void writeData(); + + MicroDAQ *_owner; +}; + +/*********************************************************************************************************************/ + +struct DataSpaceCreator { + DataSpaceCreator(H5storage &storage) : _storage(storage) {} + + template <typename PAIR> void operator()(PAIR &pair) const { + typedef typename PAIR::first_type UserType; + + // get the lists for the UserType + auto &accessorList = pair.second; + auto &decimationFactorList = + boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table); + auto &dataSpaceList = + boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table); + auto &nameList = + boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table); + + // iterate through all accessors for this UserType + auto name = nameList.begin(); + for (auto accessor = accessorList.begin(); accessor != accessorList.end(); + ++accessor, ++name) { + // determine decimation factor + int factor = 1; + if (accessor->getNElements() > _storage._owner->decimationThreshold_) { + factor = _storage._owner->decimationFactor_; + } + decimationFactorList.push_back(factor); + + // define data space + hsize_t dimsf[1]; // dataset dimensions + dimsf[0] = accessor->getNElements() / factor; + dataSpaceList.push_back(H5::DataSpace(1, dimsf)); + + // put all group names in list (each hierarchy level separately) + size_t idx = 0; + while ((idx = name->find('/', idx + 1)) != std::string::npos) { + std::string groupName = name->substr(0, idx); + _storage.groupList.push_back(groupName); } } - overallVariableList.push_back(variableName); - - // add accessor and name to lists - auto &accessorList = boost::fusion::at_key<UserType>(accessorListMap.table); - auto &nameList = boost::fusion::at_key<UserType>(nameListMap.table); - auto dirName = variableName.substr(0,variableName.find_last_of("/")); - auto baseName = variableName.substr(variableName.find_last_of("/")+1); - accessorList.emplace_back(&groupMap[dirName], baseName, "", 0, ""); - nameList.push_back(variableName); - - // return the accessor - return accessorList.back(); } - /*********************************************************************************************************************/ - - namespace detail { - - struct H5storage { - H5storage(MicroDAQ *owner) : _owner(owner) {} - - H5::H5File outFile; - std::string currentGroupName; - - /** Unique list of groups, used to create the groups in the file */ - std::list <std::string> groupList; - - /** boost::fusion::map of UserTypes to std::lists containing the H5::DataSpace objects. */ - template<typename UserType> - using dataSpaceList = std::list<H5::DataSpace>; - TemplateUserTypeMap<dataSpaceList> dataSpaceListMap; - - /** boost::fusion::map of UserTypes to std::lists containing decimation factors. */ - template<typename UserType> - using decimationFactorList = std::list<size_t>; - TemplateUserTypeMap<decimationFactorList> decimationFactorListMap; + H5storage &_storage; +}; - uint32_t currentBuffer{0}; - uint32_t nFillsInBuffer{0}; - bool isOpened{false}; - bool firstTrigger{true}; +} // namespace detail - void processTrigger(); - void writeData(); +/*********************************************************************************************************************/ - MicroDAQ *_owner; - }; +void MicroDAQ::mainLoop() { + std::cout << "Initialising MicroDAQ system..."; - /*********************************************************************************************************************/ + // storage object + detail::H5storage storage(this); - struct DataSpaceCreator { - DataSpaceCreator(H5storage &storage) : _storage(storage) {} + // create the data spaces + boost::fusion::for_each(accessorListMap.table, + detail::DataSpaceCreator(storage)); - template<typename PAIR> - void operator()(PAIR &pair) const { - typedef typename PAIR::first_type UserType; + // sort group list and make unique to make sure lower levels get created first + storage.groupList.sort(); + storage.groupList.unique(); - // get the lists for the UserType - auto &accessorList = pair.second; - auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table); - auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table); - auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table); - - // iterate through all accessors for this UserType - auto name = nameList.begin(); - for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++name) { - // determine decimation factor - int factor = 1; - if(accessor->getNElements() > _storage._owner->decimationThreshold_) { - factor = _storage._owner->decimationFactor_; - } - decimationFactorList.push_back(factor); - - // define data space - hsize_t dimsf[1]; // dataset dimensions - dimsf[0] = accessor->getNElements()/factor; - dataSpaceList.push_back( H5::DataSpace(1, dimsf) ); + // loop: process incoming triggers + while (true) { + trigger.read(); + storage.processTrigger(); + } - // put all group names in list (each hierarchy level separately) - size_t idx = 0; - while( (idx = name->find('/', idx+1)) != std::string::npos ) { - std::string groupName = name->substr(0,idx); - _storage.groupList.push_back(groupName); - } - } - } + std::cout << " done." << std::endl; +} + +/*********************************************************************************************************************/ + +namespace detail { + +void H5storage::processTrigger() { + + // update configuration variables + _owner->enable.readLatest(); + _owner->nMaxFiles.readLatest(); + _owner->nTriggersPerFile.readLatest(); + + // need to open or close file? + if (!isOpened && _owner->enable != 0) { + std::fstream bufferNumber; + + // some things to be done only on first trigger + if (firstTrigger) { + // create sub-directory + boost::filesystem::create_directory("uDAQ"); + + // determine current buffer number + bufferNumber.open("uDAQ/currentBuffer", std::ofstream::in); + bufferNumber.seekg(0); + if (!bufferNumber.eof()) { + bufferNumber >> currentBuffer; + char filename[64]; + std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer); + if (boost::filesystem::exists(filename) && + boost::filesystem::file_size(filename) > 1000) + currentBuffer++; + if (currentBuffer >= _owner->nMaxFiles) + currentBuffer = 0; + } else { + currentBuffer = 0; + } + bufferNumber.close(); + } - H5storage &_storage; - }; + // store current buffer number to disk + char filename[64]; + std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer); + std::cout << "uDAQ: Starting with file: " << filename << std::endl; + bufferNumber.open("uDAQ/currentBuffer", std::ofstream::out); + bufferNumber << currentBuffer << std::endl; + bufferNumber.close(); + + // update file number process variables + _owner->currentFile = currentBuffer; + _owner->currentFile.write(); + + // open file + try { + outFile = H5::H5File(filename, H5F_ACC_TRUNC); + } catch (H5::FileIException &) { + return; + } + isOpened = true; + } else if (isOpened && _owner->enable == 0) { + outFile.close(); + isOpened = false; } - /*********************************************************************************************************************/ - - void MicroDAQ::mainLoop() { - std::cout << "Initialising MicroDAQ system..."; + // if file is opened, this trigger should be included in the DAQ + if (isOpened) { - // storage object - detail::H5storage storage(this); + // write data + writeData(); - // create the data spaces - boost::fusion::for_each(accessorListMap.table, detail::DataSpaceCreator(storage)); + // increment counter, after nTriggersPerFile triggers written to the same + // file, switch the file + nFillsInBuffer++; + if (nFillsInBuffer > _owner->nTriggersPerFile) { - // sort group list and make unique to make sure lower levels get created first - storage.groupList.sort(); - storage.groupList.unique(); + // increment file number. use at most nMaxFiles files, overwrite old files + currentBuffer++; + if (currentBuffer >= _owner->nMaxFiles) + currentBuffer = 0; + nFillsInBuffer = 0; - // loop: process incoming triggers - while(true) { - trigger.read(); - storage.processTrigger(); + // just close the file here, will re-open on next trigger + outFile.close(); + isOpened = false; + } + } +} + +/*********************************************************************************************************************/ + +struct DataWriter { + DataWriter(detail::H5storage &storage) : _storage(storage) {} + + template <typename PAIR> void operator()(PAIR &pair) const { + typedef typename PAIR::first_type UserType; + + // get the lists for the UserType + auto &accessorList = pair.second; + auto &decimationFactorList = + boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table); + auto &dataSpaceList = + boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table); + auto &nameList = + boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table); + + // iterate through all accessors for this UserType + auto decimationFactor = decimationFactorList.begin(); + auto dataSpace = dataSpaceList.begin(); + auto name = nameList.begin(); + for (auto accessor = accessorList.begin(); accessor != accessorList.end(); + ++accessor, ++decimationFactor, ++dataSpace, ++name) { + + // form full path name of data set + std::string dataSetName = _storage.currentGroupName + "/" + *name; + + // write to file (this is mainly a function call to allow template + // specialisations at this point) + try { + write2hdf<UserType>(*accessor, dataSetName, *decimationFactor, + *dataSpace); + } catch (H5::FileIException &) { + std::cout << "MicroDAQ: ERROR writing data set " << dataSetName + << std::endl; + throw; } - - std::cout << " done." << std::endl; + } } - /*********************************************************************************************************************/ - - namespace detail { - - void H5storage::processTrigger() { - - // update configuration variables - _owner->enable.readLatest(); - _owner->nMaxFiles.readLatest(); - _owner->nTriggersPerFile.readLatest(); - - // need to open or close file? - if(!isOpened && _owner->enable != 0) { - std::fstream bufferNumber; - - // some things to be done only on first trigger - if(firstTrigger) { - // create sub-directory - boost::filesystem::create_directory("uDAQ"); - - // determine current buffer number - bufferNumber.open("uDAQ/currentBuffer", std::ofstream::in); - bufferNumber.seekg(0); - if(!bufferNumber.eof()) { - bufferNumber >> currentBuffer; - char filename[64]; - std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer); - if(boost::filesystem::exists(filename) && boost::filesystem::file_size(filename) > 1000) currentBuffer++; - if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0; - } - else { - currentBuffer = 0; - } - bufferNumber.close(); - } - - // store current buffer number to disk - char filename[64]; - std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer); - std::cout << "uDAQ: Starting with file: " << filename << std::endl; - bufferNumber.open("uDAQ/currentBuffer", std::ofstream::out); - bufferNumber << currentBuffer << std::endl; - bufferNumber.close(); - - // update file number process variables - _owner->currentFile = currentBuffer; - _owner->currentFile.write(); - - // open file - try { - outFile = H5::H5File(filename, H5F_ACC_TRUNC); - } - catch(H5::FileIException &) { - return; - } - isOpened = true; - - } - else if(isOpened && _owner->enable == 0) { - outFile.close(); - isOpened = false; - } - - // if file is opened, this trigger should be included in the DAQ - if(isOpened) { - - // write data - writeData(); - - // increment counter, after nTriggersPerFile triggers written to the same file, switch the file - nFillsInBuffer++; - if(nFillsInBuffer > _owner->nTriggersPerFile) { - - // increment file number. use at most nMaxFiles files, overwrite old files - currentBuffer++; - if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0; - nFillsInBuffer = 0; - - // just close the file here, will re-open on next trigger - outFile.close(); - isOpened = false; - } - } - } + template <typename UserType> + void write2hdf(ArrayPollInput<UserType> &accessor, std::string &name, + size_t decimationFactor, H5::DataSpace &dataSpace) const; - /*********************************************************************************************************************/ - - struct DataWriter { - DataWriter(detail::H5storage &storage) : _storage(storage) {} - - template<typename PAIR> - void operator()(PAIR &pair) const { - typedef typename PAIR::first_type UserType; - - // get the lists for the UserType - auto &accessorList = pair.second; - auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table); - auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table); - auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table); - - // iterate through all accessors for this UserType - auto decimationFactor = decimationFactorList.begin(); - auto dataSpace = dataSpaceList.begin(); - auto name = nameList.begin(); - for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++decimationFactor, ++dataSpace, ++name) { - - // form full path name of data set - std::string dataSetName = _storage.currentGroupName+"/"+*name; - - // write to file (this is mainly a function call to allow template specialisations at this point) - try { - write2hdf<UserType>(*accessor, dataSetName, *decimationFactor, *dataSpace); - } - catch(H5::FileIException&) { - std::cout << "MicroDAQ: ERROR writing data set " << dataSetName << std::endl; - throw; - } - } - } - - template<typename UserType> - void write2hdf(ArrayPollInput<UserType> &accessor, std::string &name, size_t decimationFactor, H5::DataSpace &dataSpace) const; - - H5storage &_storage; - }; - - /*********************************************************************************************************************/ - - template<typename UserType> - void DataWriter::write2hdf(ArrayPollInput<UserType> &accessor, std::string &dataSetName, size_t decimationFactor, H5::DataSpace &dataSpace) const { - - // prepare decimated buffer - size_t n = accessor.getNElements()/decimationFactor; - std::vector<float> buffer(n); - for(size_t i=0; i<n; ++i) { - buffer[i] = accessor[i*decimationFactor]; - } + H5storage &_storage; +}; - // write data from internal buffer to data set in HDF5 file - H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::NATIVE_FLOAT, dataSpace); - dataset.write(buffer.data(), H5::PredType::NATIVE_FLOAT); - } +/*********************************************************************************************************************/ - /*********************************************************************************************************************/ +template <typename UserType> +void DataWriter::write2hdf(ArrayPollInput<UserType> &accessor, + std::string &dataSetName, size_t decimationFactor, + H5::DataSpace &dataSpace) const { - template<> - void DataWriter::write2hdf<std::string>(ArrayPollInput<std::string> &accessor, std::string &dataSetName, size_t, H5::DataSpace &dataSpace) const { + // prepare decimated buffer + size_t n = accessor.getNElements() / decimationFactor; + std::vector<float> buffer(n); + for (size_t i = 0; i < n; ++i) { + buffer[i] = accessor[i * decimationFactor]; + } - // write data from internal buffer to data set in HDF5 file - H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::C_S1, dataSpace); - dataset.write(accessor[0].c_str(), H5::PredType::NATIVE_FLOAT); - } + // write data from internal buffer to data set in HDF5 file + H5::DataSet dataset = _storage.outFile.createDataSet( + dataSetName, H5::PredType::NATIVE_FLOAT, dataSpace); + dataset.write(buffer.data(), H5::PredType::NATIVE_FLOAT); +} + +/*********************************************************************************************************************/ + +template <> +void DataWriter::write2hdf<std::string>(ArrayPollInput<std::string> &accessor, + std::string &dataSetName, size_t, + H5::DataSpace &dataSpace) const { + + // write data from internal buffer to data set in HDF5 file + H5::DataSet dataset = _storage.outFile.createDataSet( + dataSetName, H5::PredType::C_S1, dataSpace); + dataset.write(accessor[0].c_str(), H5::PredType::NATIVE_FLOAT); +} + +/*********************************************************************************************************************/ + +void H5storage::writeData() { + + // format current time + struct timeval tv; + gettimeofday(&tv, nullptr); + time_t t = tv.tv_sec; + if (t == 0) + t = time(nullptr); + struct tm *tmp = localtime(&t); + char timeString[64]; + std::sprintf(timeString, "%04d-%02d-%02d %02d:%02d:%02d.%03d", + 1900 + tmp->tm_year, tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, + tmp->tm_min, tmp->tm_sec, static_cast<int>(tv.tv_usec / 1000)); + + // create groups + currentGroupName = std::string("/") + std::string(timeString); + try { + outFile.createGroup(currentGroupName); + for (auto &group : groupList) + outFile.createGroup(currentGroupName + "/" + group); + } catch (H5::FileIException &) { + outFile.close(); + isOpened = false; // will re-open file on next trigger + return; + } - /*********************************************************************************************************************/ - - void H5storage::writeData() { - - // format current time - struct timeval tv; - gettimeofday(&tv, nullptr); - time_t t = tv.tv_sec; - if(t == 0) t = time(nullptr); - struct tm *tmp = localtime(&t); - char timeString[64]; - std::sprintf(timeString, "%04d-%02d-%02d %02d:%02d:%02d.%03d", 1900+tmp->tm_year, tmp->tm_mon+1, tmp->tm_mday, - tmp->tm_hour, tmp->tm_min, tmp->tm_sec, static_cast<int>(tv.tv_usec / 1000)); - - // create groups - currentGroupName = std::string("/")+std::string(timeString); - try { - outFile.createGroup(currentGroupName); - for(auto &group : groupList) outFile.createGroup(currentGroupName+"/"+group); - } - catch(H5::FileIException &) { - outFile.close(); - isOpened = false; // will re-open file on next trigger - return; - } - - // read all input data - _owner->readAllLatest(); - - // write all data to file - boost::fusion::for_each(_owner->accessorListMap.table, DataWriter(*this)); + // read all input data + _owner->readAllLatest(); - } + // write all data to file + boost::fusion::for_each(_owner->accessorListMap.table, DataWriter(*this)); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - } // namespace detail +} // namespace detail } // namespace ChimeraTK diff --git a/Modules/src/ServerHistory.cc b/Modules/src/ServerHistory.cc index 5ecc1d1b..2b250932 100644 --- a/Modules/src/ServerHistory.cc +++ b/Modules/src/ServerHistory.cc @@ -2,24 +2,25 @@ //#include "ChimeraTK/TransferElementID.h" -namespace ChimeraTK{ -namespace history{ +namespace ChimeraTK { +namespace history { -/** Callable class for use with boost::fusion::for_each: Attach the given accessor to the History with proper - * handling of the UserType. */ +/** Callable class for use with boost::fusion::for_each: Attach the given + * accessor to the History with proper handling of the UserType. */ struct AccessorAttacher { - AccessorAttacher(VariableNetworkNode& feeder, ServerHistory *owner, const std::string &name) - : _feeder(feeder), _owner(owner), _name(name) {} + AccessorAttacher(VariableNetworkNode &feeder, ServerHistory *owner, + const std::string &name) + : _feeder(feeder), _owner(owner), _name(name) {} - template<typename PAIR> - void operator()(PAIR&) const { + template <typename PAIR> void operator()(PAIR &) const { // only continue if the call is for the right type - if(typeid(typename PAIR::first_type) != _feeder.getValueType()) return; + if (typeid(typename PAIR::first_type) != _feeder.getValueType()) + return; // register connection - _feeder >> _owner->template getAccessor<typename PAIR::first_type>(_name, _feeder.pdata->nElements); - + _feeder >> _owner->template getAccessor<typename PAIR::first_type>( + _name, _feeder.pdata->nElements); } VariableNetworkNode &_feeder; @@ -27,52 +28,65 @@ struct AccessorAttacher { const std::string &_name; }; -void ServerHistory::addSource(const Module &source, const RegisterPath &namePrefix) { +void ServerHistory::addSource(const Module &source, + const RegisterPath &namePrefix) { - // for simplification, first create a VirtualModule containing the correct hierarchy structure (obeying eliminate - // hierarchy etc.) - auto dynamicModel = source.findTag(".*"); /// @todo use virtualise() instead + // for simplification, first create a VirtualModule containing the correct + // hierarchy structure (obeying eliminate hierarchy etc.) + auto dynamicModel = source.findTag(".*"); /// @todo use virtualise() instead // create variable group map for namePrefix if needed - if(groupMap.find(namePrefix) == groupMap.end()) { + if (groupMap.find(namePrefix) == groupMap.end()) { // search for existing parent (if any) auto parentPrefix = namePrefix; - while(groupMap.find(parentPrefix) == groupMap.end()) { - if(parentPrefix == "/") break; // no existing parent found - parentPrefix = std::string(parentPrefix).substr(0,std::string(parentPrefix).find_last_of("/")); + while (groupMap.find(parentPrefix) == groupMap.end()) { + if (parentPrefix == "/") + break; // no existing parent found + parentPrefix = + std::string(parentPrefix) + .substr(0, std::string(parentPrefix).find_last_of("/")); } // create all not-yet-existing parents - while(parentPrefix != namePrefix) { + while (parentPrefix != namePrefix) { EntityOwner *owner = this; - if(parentPrefix != "/") owner = &groupMap[parentPrefix]; - auto stop = std::string(namePrefix).find_first_of("/", parentPrefix.length()+1); - if(stop == std::string::npos) stop = namePrefix.length(); - RegisterPath name = std::string(namePrefix).substr(parentPrefix.length(),stop-parentPrefix.length()); + if (parentPrefix != "/") + owner = &groupMap[parentPrefix]; + auto stop = + std::string(namePrefix).find_first_of("/", parentPrefix.length() + 1); + if (stop == std::string::npos) + stop = namePrefix.length(); + RegisterPath name = + std::string(namePrefix) + .substr(parentPrefix.length(), stop - parentPrefix.length()); parentPrefix /= name; - groupMap[parentPrefix] = VariableGroup(owner, std::string(name).substr(1), ""); + groupMap[parentPrefix] = + VariableGroup(owner, std::string(name).substr(1), ""); } } // add all accessors on this hierarchy level - for(auto &acc : dynamicModel.getAccessorList()) { - boost::fusion::for_each(_accessorListMap.table, AccessorAttacher(acc, this, namePrefix/acc.getName())); + for (auto &acc : dynamicModel.getAccessorList()) { + boost::fusion::for_each( + _accessorListMap.table, + AccessorAttacher(acc, this, namePrefix / acc.getName())); } // recurse into submodules - for(auto mod : dynamicModel.getSubmoduleList()) { - addSource(*mod, namePrefix/mod->getName()); + for (auto mod : dynamicModel.getSubmoduleList()) { + addSource(*mod, namePrefix / mod->getName()); } - } -template<typename UserType> -VariableNetworkNode ServerHistory::getAccessor(const std::string &variableName, const size_t &nElements) { +template <typename UserType> +VariableNetworkNode ServerHistory::getAccessor(const std::string &variableName, + const size_t &nElements) { // check if variable name already registered - for(auto &name : _overallVariableList) { - if(name == variableName) { - throw ChimeraTK::logic_error("Cannot add '"+variableName+ - "' to History since a variable with that name is already registered."); + for (auto &name : _overallVariableList) { + if (name == variableName) { + throw ChimeraTK::logic_error("Cannot add '" + variableName + + "' to History since a variable with that " + "name is already registered."); } } _overallVariableList.push_back(variableName); @@ -80,20 +94,37 @@ VariableNetworkNode ServerHistory::getAccessor(const std::string &variableName, // add accessor and name to lists auto &accessorList = boost::fusion::at_key<UserType>(_accessorListMap.table); auto &nameList = boost::fusion::at_key<UserType>(_nameListMap.table); - auto dirName = variableName.substr(0,variableName.find_last_of("/")); - auto baseName = variableName.substr(variableName.find_last_of("/")+1); - accessorList.emplace_back(std::piecewise_construct, - std::forward_as_tuple(ArrayPushInput<UserType>{&groupMap[dirName], baseName + "_in", "", 0, "",}), - std::forward_as_tuple(std::vector<ArrayOutput<UserType> >{})); - for(size_t i =0; i < nElements; i++){ - if(nElements == 1) { + auto dirName = variableName.substr(0, variableName.find_last_of("/")); + auto baseName = variableName.substr(variableName.find_last_of("/") + 1); + accessorList.emplace_back( + std::piecewise_construct, + std::forward_as_tuple(ArrayPushInput<UserType>{ + &groupMap[dirName], + baseName + "_in", + "", + 0, + "", + }), + std::forward_as_tuple(std::vector<ArrayOutput<UserType>>{})); + for (size_t i = 0; i < nElements; i++) { + if (nElements == 1) { // in case of a scalar history only use the variableName - accessorList.back().second.emplace_back(ArrayOutput<UserType>{&groupMap[dirName], baseName, "", _historyLength, "", - { "CS", getName() }}); + accessorList.back().second.emplace_back( + ArrayOutput<UserType>{&groupMap[dirName], + baseName, + "", + _historyLength, + "", + {"CS", getName()}}); } else { // in case of an array history append the index to the variableName - accessorList.back().second.emplace_back(ArrayOutput<UserType>{&groupMap[dirName], baseName + "_" + std::to_string(i), "", _historyLength, "", - { "CS", getName() }}); + accessorList.back().second.emplace_back( + ArrayOutput<UserType>{&groupMap[dirName], + baseName + "_" + std::to_string(i), + "", + _historyLength, + "", + {"CS", getName()}}); } } nameList.push_back(variableName); @@ -103,16 +134,18 @@ VariableNetworkNode ServerHistory::getAccessor(const std::string &variableName, } struct Update { - Update(ChimeraTK::TransferElementID id): _id(id){} + Update(ChimeraTK::TransferElementID id) : _id(id) {} - template<typename PAIR> - void operator()(PAIR &pair) const{ + template <typename PAIR> void operator()(PAIR &pair) const { auto &accessorList = pair.second; - for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor){ - if(accessor->first.getId() == _id){ - for(size_t i = 0; i < accessor->first.getNElements(); i++) { - std::rotate(accessor->second.at(i).begin(), accessor->second.at(i).begin()+1, accessor->second.at(i).end()); - *(accessor->second.at(i).end()-1) = accessor->first[i]; + for (auto accessor = accessorList.begin(); accessor != accessorList.end(); + ++accessor) { + if (accessor->first.getId() == _id) { + for (size_t i = 0; i < accessor->first.getNElements(); i++) { + std::rotate(accessor->second.at(i).begin(), + accessor->second.at(i).begin() + 1, + accessor->second.at(i).end()); + *(accessor->second.at(i).end() - 1) = accessor->first[i]; accessor->second.at(i).write(); } } @@ -122,13 +155,13 @@ struct Update { TransferElementID _id; }; -void ServerHistory::mainLoop(){ +void ServerHistory::mainLoop() { auto group = readAnyGroup(); - while(true){ + while (true) { auto id = group.readAny(); boost::fusion::for_each(_accessorListMap.table, Update(id)); } } -}// namespace history -}// namespace ChimeraTK +} // namespace history +} // namespace ChimeraTK diff --git a/example/DemoDummyDevice.cc b/example/DemoDummyDevice.cc index 7d4b90e7..20d07dbe 100644 --- a/example/DemoDummyDevice.cc +++ b/example/DemoDummyDevice.cc @@ -1,44 +1,50 @@ -#include <ChimeraTK/DummyBackend.h> #include <ChimeraTK/BackendFactory.h> #include <ChimeraTK/DeviceAccessVersion.h> +#include <ChimeraTK/DummyBackend.h> class DemoDummy : public ChimeraTK::DummyBackend { - public: - DemoDummy(std::string mapFileName) : DummyBackend(mapFileName) {} - - static boost::shared_ptr<DeviceBackend> createInstance(std::string, std::string, std::list<std::string> parameters, std::string) { - return boost::shared_ptr<DeviceBackend>(new DemoDummy(parameters.front())); - } - - void read(uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) override { - - // if probeSignal register is read, fill it first - if(bar == 2) { - assert(address == 0); - assert(sizeInBytes == 65536); - - // build average of feed forward and setpoint tables - for(int i=0; i<65536; ++i) { - _barContents[2][i] = (_barContents[0][i] + _barContents[1][i])/2; - } +public: + DemoDummy(std::string mapFileName) : DummyBackend(mapFileName) {} + + static boost::shared_ptr<DeviceBackend> + createInstance(std::string, std::string, std::list<std::string> parameters, + std::string) { + return boost::shared_ptr<DeviceBackend>(new DemoDummy(parameters.front())); + } + + void read(uint8_t bar, uint32_t address, int32_t *data, + size_t sizeInBytes) override { + + // if probeSignal register is read, fill it first + if (bar == 2) { + assert(address == 0); + assert(sizeInBytes == 65536); + + // build average of feed forward and setpoint tables + for (int i = 0; i < 65536; ++i) { + _barContents[2][i] = (_barContents[0][i] + _barContents[1][i]) / 2; } - - // perform the original read - DummyBackend::read(bar,address,data,sizeInBytes); } - /** Class to register the backend type with the factory. */ - class BackendRegisterer { - public: - BackendRegisterer(); - }; - static BackendRegisterer backendRegisterer; -}; + // perform the original read + DummyBackend::read(bar, address, data, sizeInBytes); + } + /** Class to register the backend type with the factory. */ + class BackendRegisterer { + public: + BackendRegisterer(); + }; + static BackendRegisterer backendRegisterer; +}; DemoDummy::BackendRegisterer DemoDummy::backendRegisterer; DemoDummy::BackendRegisterer::BackendRegisterer() { - std::cout << "DemoDummy::BackendRegisterer: registering backend type DemoDummy" << std::endl; - ChimeraTK::BackendFactory::getInstance().registerBackendType("DemoDummy","",&DemoDummy::createInstance, CHIMERATK_DEVICEACCESS_VERSION); + std::cout + << "DemoDummy::BackendRegisterer: registering backend type DemoDummy" + << std::endl; + ChimeraTK::BackendFactory::getInstance().registerBackendType( + "DemoDummy", "", &DemoDummy::createInstance, + CHIMERATK_DEVICEACCESS_VERSION); } diff --git a/example/TimerDummyDevice.cc b/example/TimerDummyDevice.cc index 28deed04..7fbbb638 100644 --- a/example/TimerDummyDevice.cc +++ b/example/TimerDummyDevice.cc @@ -1,109 +1,126 @@ -#include <ChimeraTK/DeviceBackendImpl.h> #include <ChimeraTK/BackendFactory.h> #include <ChimeraTK/DeviceAccessVersion.h> +#include <ChimeraTK/DeviceBackendImpl.h> #include <ChimeraTK/SyncNDRegisterAccessor.h> -template<typename UserType> -class TimerDummyRegisterAccessor; +template <typename UserType> class TimerDummyRegisterAccessor; class TimerDummy : public ChimeraTK::DeviceBackendImpl { +public: + TimerDummy() : DeviceBackendImpl() { + FILL_VIRTUAL_FUNCTION_TEMPLATE_VTABLE(getRegisterAccessor_impl); + } + + static boost::shared_ptr<DeviceBackend> createInstance(std::string, + std::string, + std::list<std::string>, + std::string) { + return boost::shared_ptr<DeviceBackend>(new TimerDummy()); + } + + template <typename UserType> + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> + getRegisterAccessor_impl(const ChimeraTK::RegisterPath ®isterPathName, + size_t, size_t, ChimeraTK::AccessModeFlags flags); + DEFINE_VIRTUAL_FUNCTION_TEMPLATE_VTABLE_FILLER(TimerDummy, + getRegisterAccessor_impl, 4); + + void open() override {} + + void close() override {} + + std::string readDeviceInfo() override { + return std::string("Dummy timing device "); + } + + /** Class to register the backend type with the factory. */ + class BackendRegisterer { public: - TimerDummy() : DeviceBackendImpl() { - FILL_VIRTUAL_FUNCTION_TEMPLATE_VTABLE(getRegisterAccessor_impl); - } - - static boost::shared_ptr<DeviceBackend> createInstance(std::string, std::string, std::list<std::string>, std::string) { - return boost::shared_ptr<DeviceBackend>(new TimerDummy()); - } - - template<typename UserType> - boost::shared_ptr< ChimeraTK::NDRegisterAccessor<UserType> > getRegisterAccessor_impl( - const ChimeraTK::RegisterPath ®isterPathName, size_t , size_t , ChimeraTK::AccessModeFlags flags); - DEFINE_VIRTUAL_FUNCTION_TEMPLATE_VTABLE_FILLER( TimerDummy, getRegisterAccessor_impl, 4); - - void open() override {} - - void close() override {} - - std::string readDeviceInfo() override { - return std::string("Dummy timing device "); - } - - /** Class to register the backend type with the factory. */ - class BackendRegisterer { - public: - BackendRegisterer(); - }; - static BackendRegisterer backendRegisterer; + BackendRegisterer(); + }; + static BackendRegisterer backendRegisterer; }; - TimerDummy::BackendRegisterer TimerDummy::backendRegisterer; TimerDummy::BackendRegisterer::BackendRegisterer() { - std::cout << "TimerDummy::BackendRegisterer: registering backend type TimerDummy" << std::endl; - ChimeraTK::BackendFactory::getInstance().registerBackendType("TimerDummy","",&TimerDummy::createInstance, CHIMERATK_DEVICEACCESS_VERSION); + std::cout + << "TimerDummy::BackendRegisterer: registering backend type TimerDummy" + << std::endl; + ChimeraTK::BackendFactory::getInstance().registerBackendType( + "TimerDummy", "", &TimerDummy::createInstance, + CHIMERATK_DEVICEACCESS_VERSION); } -template<typename UserType> -class TimerDummyRegisterAccessor : public ChimeraTK::SyncNDRegisterAccessor<UserType> { - public: - TimerDummyRegisterAccessor(const ChimeraTK::RegisterPath ®isterPathName) - : ChimeraTK::SyncNDRegisterAccessor<UserType>(registerPathName) - { - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0].resize(1); - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0][0] = UserType(); - } - - ~TimerDummyRegisterAccessor() { this->shutdown(); } - - void doReadTransfer() override { - usleep(1000000); - } +template <typename UserType> +class TimerDummyRegisterAccessor + : public ChimeraTK::SyncNDRegisterAccessor<UserType> { +public: + TimerDummyRegisterAccessor(const ChimeraTK::RegisterPath ®isterPathName) + : ChimeraTK::SyncNDRegisterAccessor<UserType>(registerPathName) { + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0].resize(1); + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0][0] = UserType(); + } - void doPostRead() override { - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0][0]++; - currentVersion = {}; - } + ~TimerDummyRegisterAccessor() { this->shutdown(); } - bool doWriteTransfer(ChimeraTK::VersionNumber) override { return false; } + void doReadTransfer() override { usleep(1000000); } - bool doReadTransferNonBlocking() override { return false; } + void doPostRead() override { + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0][0]++; + currentVersion = {}; + } - bool doReadTransferLatest() override { return false; } - bool isReadOnly() const override { return true; } - bool isReadable() const override { return true; } - bool isWriteable() const override { return false; } + bool doWriteTransfer(ChimeraTK::VersionNumber) override { return false; } - ChimeraTK::AccessModeFlags getAccessModeFlags() const override { return {ChimeraTK::AccessMode::wait_for_new_data}; } + bool doReadTransferNonBlocking() override { return false; } - bool mayReplaceOther(const boost::shared_ptr<ChimeraTK::TransferElement const> &) const override { return false; } + bool doReadTransferLatest() override { return false; } + bool isReadOnly() const override { return true; } + bool isReadable() const override { return true; } + bool isWriteable() const override { return false; } - std::vector<boost::shared_ptr<ChimeraTK::TransferElement> > getHardwareAccessingElements() override { return { this->shared_from_this() }; } + ChimeraTK::AccessModeFlags getAccessModeFlags() const override { + return {ChimeraTK::AccessMode::wait_for_new_data}; + } - void replaceTransferElement(boost::shared_ptr<ChimeraTK::TransferElement>) override {} + bool mayReplaceOther(const boost::shared_ptr<ChimeraTK::TransferElement const> + &) const override { + return false; + } - std::list<boost::shared_ptr<ChimeraTK::TransferElement> > getInternalElements() override { return {}; } + std::vector<boost::shared_ptr<ChimeraTK::TransferElement>> + getHardwareAccessingElements() override { + return {this->shared_from_this()}; + } - ChimeraTK::VersionNumber getVersionNumber() const override { return currentVersion; } + void replaceTransferElement( + boost::shared_ptr<ChimeraTK::TransferElement>) override {} - protected: + std::list<boost::shared_ptr<ChimeraTK::TransferElement>> + getInternalElements() override { + return {}; + } - ChimeraTK::VersionNumber currentVersion; + ChimeraTK::VersionNumber getVersionNumber() const override { + return currentVersion; + } +protected: + ChimeraTK::VersionNumber currentVersion; }; -template<> -void TimerDummyRegisterAccessor<std::string>::doPostRead() { -} - - -template<typename UserType> -boost::shared_ptr< ChimeraTK::NDRegisterAccessor<UserType> > TimerDummy::getRegisterAccessor_impl( - const ChimeraTK::RegisterPath ®isterPathName, size_t , size_t , ChimeraTK::AccessModeFlags flags) { - assert(registerPathName == "/macropulseNr"); - assert(flags.has(ChimeraTK::AccessMode::wait_for_new_data)); - flags.checkForUnknownFlags({ChimeraTK::AccessMode::wait_for_new_data}); - return boost::shared_ptr< ChimeraTK::NDRegisterAccessor<UserType> >(new TimerDummyRegisterAccessor<UserType>(registerPathName)); +template <> void TimerDummyRegisterAccessor<std::string>::doPostRead() {} + +template <typename UserType> +boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> +TimerDummy::getRegisterAccessor_impl( + const ChimeraTK::RegisterPath ®isterPathName, size_t, size_t, + ChimeraTK::AccessModeFlags flags) { + assert(registerPathName == "/macropulseNr"); + assert(flags.has(ChimeraTK::AccessMode::wait_for_new_data)); + flags.checkForUnknownFlags({ChimeraTK::AccessMode::wait_for_new_data}); + return boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>( + new TimerDummyRegisterAccessor<UserType>(registerPathName)); } diff --git a/example/demoApp.cc b/example/demoApp.cc index 79a5d118..7a8f15af 100644 --- a/example/demoApp.cc +++ b/example/demoApp.cc @@ -13,96 +13,99 @@ namespace ctk = ChimeraTK; struct Automation : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - ctk::ScalarPollInput<double> opSP{this, "opSP", "MV", "..."}; - ctk::ScalarOutput<double> curSP{this, "curSP", "MV", "..."}; - ctk::ScalarPushInput<int> trigger{this, "trigger", "", "..."}; - - void mainLoop() { - while(true) { - trigger.read(); - opSP.readLatest(); // opSP.read() would be equivalent - if(std::abs(opSP - curSP) > 0.01) { - curSP += std::max( std::min(opSP - curSP, 0.1), -0.1); - curSP.write(); - } + using ctk::ApplicationModule::ApplicationModule; + ctk::ScalarPollInput<double> opSP{this, "opSP", "MV", "..."}; + ctk::ScalarOutput<double> curSP{this, "curSP", "MV", "..."}; + ctk::ScalarPushInput<int> trigger{this, "trigger", "", "..."}; + + void mainLoop() { + while (true) { + trigger.read(); + opSP.readLatest(); // opSP.read() would be equivalent + if (std::abs(opSP - curSP) > 0.01) { + curSP += std::max(std::min(opSP - curSP, 0.1), -0.1); + curSP.write(); } } + } }; constexpr size_t tableLength{16384}; -constexpr double samplingFrequency = 9.0; // MHz +constexpr double samplingFrequency = 9.0; // MHz constexpr double bitScalingFactor = 2000.0; // bits/MV struct TableGeneration : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - struct TableParameters : public ctk::VariableGroup { - using ctk::VariableGroup::VariableGroup; - ctk::ScalarPushInput<double> pulseLength{this, "pulseLength", "us", "..."}; - ctk::ScalarPushInput<double> setpoint{this, "setpoint", "MV", "..."}; - }; - TableParameters tableParameters{this, "tableParameters", "..."}; - - ctk::ArrayOutput<int32_t> setpointTable{this, "setpointTable", "bits", tableLength, "..."}; - ctk::ArrayOutput<int32_t> feedforwardTable{this, "feedforwardTable", "bits", tableLength, "..."}; - - void mainLoop() { - ctk::ReadAnyGroup tableParametersReadGroup = tableParameters.readAnyGroup(); - while(true) { - tableParametersReadGroup.readAny(); // block until the any table parameter is changed - - for(size_t i = 0; i < tableLength; ++i) { - if(i < tableParameters.pulseLength * samplingFrequency) { - setpointTable[i] = tableParameters.setpoint * bitScalingFactor; - feedforwardTable[i] = 0.5 * tableParameters.setpoint * bitScalingFactor; - } - else { - setpointTable[i] = 0; - feedforwardTable[i] = 0; - } + using ctk::ApplicationModule::ApplicationModule; + struct TableParameters : public ctk::VariableGroup { + using ctk::VariableGroup::VariableGroup; + ctk::ScalarPushInput<double> pulseLength{this, "pulseLength", "us", "..."}; + ctk::ScalarPushInput<double> setpoint{this, "setpoint", "MV", "..."}; + }; + TableParameters tableParameters{this, "tableParameters", "..."}; + + ctk::ArrayOutput<int32_t> setpointTable{this, "setpointTable", "bits", + tableLength, "..."}; + ctk::ArrayOutput<int32_t> feedforwardTable{this, "feedforwardTable", "bits", + tableLength, "..."}; + + void mainLoop() { + ctk::ReadAnyGroup tableParametersReadGroup = tableParameters.readAnyGroup(); + while (true) { + tableParametersReadGroup + .readAny(); // block until the any table parameter is changed + + for (size_t i = 0; i < tableLength; ++i) { + if (i < tableParameters.pulseLength * samplingFrequency) { + setpointTable[i] = tableParameters.setpoint * bitScalingFactor; + feedforwardTable[i] = + 0.5 * tableParameters.setpoint * bitScalingFactor; + } else { + setpointTable[i] = 0; + feedforwardTable[i] = 0; } - - setpointTable.write(); - feedforwardTable.write(); } + + setpointTable.write(); + feedforwardTable.write(); } + } }; struct ExampleApp : public ctk::Application { - ExampleApp() : Application("exampleApp") {} - ~ExampleApp() { shutdown(); } + ExampleApp() : Application("exampleApp") {} + ~ExampleApp() { shutdown(); } - Automation automation{this, "automation", "..."}; - TableGeneration tableGeneration{this, "tableGeneration", "..."}; - ctk::DeviceModule dev{"Device"}; - ctk::DeviceModule timer{"Timer"}; - ctk::ControlSystemModule cs{"MyLocation"}; - - void defineConnections(); + Automation automation{this, "automation", "..."}; + TableGeneration tableGeneration{this, "tableGeneration", "..."}; + ctk::DeviceModule dev{"Device"}; + ctk::DeviceModule timer{"Timer"}; + ctk::ControlSystemModule cs{"MyLocation"}; + void defineConnections(); }; ExampleApp theExampleApp; - void ExampleApp::defineConnections() { - ChimeraTK::setDMapFilePath("dummy.dmap"); + ChimeraTK::setDMapFilePath("dummy.dmap"); - cs("setpoint") >> automation.opSP; - automation.curSP >> tableGeneration.tableParameters.setpoint >> cs("currentSetpoint"); + cs("setpoint") >> automation.opSP; + automation.curSP >> tableGeneration.tableParameters.setpoint >> + cs("currentSetpoint"); - auto macropulseNr = timer("macropulseNr", typeid(int), 1, ctk::UpdateMode::push); - macropulseNr >> automation.trigger; + auto macropulseNr = + timer("macropulseNr", typeid(int), 1, ctk::UpdateMode::push); + macropulseNr >> automation.trigger; - cs("pulseLength") >> tableGeneration.tableParameters.pulseLength; + cs("pulseLength") >> tableGeneration.tableParameters.pulseLength; - tableGeneration.setpointTable >> dev("setpointTable"); - tableGeneration.feedforwardTable >> dev("feedforwardTable"); + tableGeneration.setpointTable >> dev("setpointTable"); + tableGeneration.feedforwardTable >> dev("feedforwardTable"); - dev("probeSignal", typeid(int), tableLength) [ macropulseNr ] >> cs("probeSignal"); + dev("probeSignal", typeid(int), tableLength)[macropulseNr] >> + cs("probeSignal"); - dumpConnections(); - dumpConnectionGraph(); - dumpGraph(); - dumpModuleGraph("module-graph.dot"); + dumpConnections(); + dumpConnectionGraph(); + dumpGraph(); + dumpModuleGraph("module-graph.dot"); } - diff --git a/example2/demoApp2.cc b/example2/demoApp2.cc index a8b14b06..32b3f7c9 100644 --- a/example2/demoApp2.cc +++ b/example2/demoApp2.cc @@ -4,42 +4,43 @@ namespace ctk = ChimeraTK; struct Controller : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - ctk::ScalarPollInput<double> sp{this, "temperatureSetpoint", "degC", "Description", {"CS"}}; - ctk::ScalarPushInput<double> rb{this, "temperatureReadback", "degC", "...", {"DEV", "CS"}}; - ctk::ScalarOutput<double> cur{this, "heatingCurrent", "mA", "...", {"DEV"}}; - - void mainLoop() { - const double gain = 100.0; - while(true) { - readAll(); // waits until rb updated, then reads sp - - cur = gain * (sp - rb); - writeAll(); // writes any outputs - } + using ctk::ApplicationModule::ApplicationModule; + ctk::ScalarPollInput<double> sp{ + this, "temperatureSetpoint", "degC", "Description", {"CS"}}; + ctk::ScalarPushInput<double> rb{ + this, "temperatureReadback", "degC", "...", {"DEV", "CS"}}; + ctk::ScalarOutput<double> cur{this, "heatingCurrent", "mA", "...", {"DEV"}}; + + void mainLoop() { + const double gain = 100.0; + while (true) { + readAll(); // waits until rb updated, then reads sp + + cur = gain * (sp - rb); + writeAll(); // writes any outputs } + } }; - struct ExampleApp : public ctk::Application { - ExampleApp() : Application("exampleApp") {} - ~ExampleApp() { shutdown(); } + ExampleApp() : Application("exampleApp") {} + ~ExampleApp() { shutdown(); } - Controller controller{this, "Controller", "The Controller"}; + Controller controller{this, "Controller", "The Controller"}; - ctk::PeriodicTrigger timer{this, "Timer", "Periodic timer for the controller", 1000}; + ctk::PeriodicTrigger timer{this, "Timer", "Periodic timer for the controller", + 1000}; - ctk::DeviceModule heater{"oven","heater"}; - ctk::ControlSystemModule cs{"Bakery"}; + ctk::DeviceModule heater{"oven", "heater"}; + ctk::ControlSystemModule cs{"Bakery"}; - void defineConnections(); + void defineConnections(); }; static ExampleApp theExampleApp; - void ExampleApp::defineConnections() { - ChimeraTK::setDMapFilePath("example2.dmap"); + ChimeraTK::setDMapFilePath("example2.dmap"); - controller.findTag("DEV").connectTo(heater, timer.tick); - controller.findTag("CS").connectTo(cs); + controller.findTag("DEV").connectTo(heater, timer.tick); + controller.findTag("CS").connectTo(cs); } diff --git a/example2a/demoApp2a.cc b/example2a/demoApp2a.cc index 20f2ff08..ee4b264f 100644 --- a/example2a/demoApp2a.cc +++ b/example2a/demoApp2a.cc @@ -1,72 +1,76 @@ #include <ApplicationCore.h> -#include <PeriodicTrigger.h> #include <ConfigReader.h> +#include <PeriodicTrigger.h> namespace ctk = ChimeraTK; struct Controller : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - ctk::ScalarPollInput<double> sp{this, "temperatureSetpoint", "degC", "Description", {"CS"}}; - ctk::ScalarPushInput<double> rb{this, "temperatureReadback", "degC", "...", {"DEV", "CS"}}; - ctk::ScalarOutput<double> cur{this, "heatingCurrent", "mA", "...", {"DEV"}}; - - void mainLoop() { - const double gain = 100.0; - while(true) { - readAll(); // waits until rb updated, then reads sp - - cur = gain * (sp - rb); - writeAll(); // writes any outputs - } + using ctk::ApplicationModule::ApplicationModule; + ctk::ScalarPollInput<double> sp{ + this, "temperatureSetpoint", "degC", "Description", {"CS"}}; + ctk::ScalarPushInput<double> rb{ + this, "temperatureReadback", "degC", "...", {"DEV", "CS"}}; + ctk::ScalarOutput<double> cur{this, "heatingCurrent", "mA", "...", {"DEV"}}; + + void mainLoop() { + const double gain = 100.0; + while (true) { + readAll(); // waits until rb updated, then reads sp + + cur = gain * (sp - rb); + writeAll(); // writes any outputs } + } }; struct Automation : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - ctk::ScalarPollInput<double> opSp{this, "operatorSetpoint", "degC", "...", {"CS"}}; - ctk::ScalarOutput<double> actSp{this, "temperatureSetpoint", "degC", "...", {"Controller"}}; - ctk::ScalarPushInput<uint64_t> trigger{this, "trigger", "", "..."}; - - void mainLoop() { - const double maxStep = 0.1; - while(true) { - readAll(); // waits until trigger received, then read opSp - actSp += std::max(std::min(opSp - actSp, maxStep), -maxStep); - writeAll(); - } + using ctk::ApplicationModule::ApplicationModule; + ctk::ScalarPollInput<double> opSp{ + this, "operatorSetpoint", "degC", "...", {"CS"}}; + ctk::ScalarOutput<double> actSp{ + this, "temperatureSetpoint", "degC", "...", {"Controller"}}; + ctk::ScalarPushInput<uint64_t> trigger{this, "trigger", "", "..."}; + + void mainLoop() { + const double maxStep = 0.1; + while (true) { + readAll(); // waits until trigger received, then read opSp + actSp += std::max(std::min(opSp - actSp, maxStep), -maxStep); + writeAll(); } + } }; - struct ExampleApp : public ctk::Application { - ExampleApp() : Application("exampleApp") {} - ~ExampleApp() { shutdown(); } + ExampleApp() : Application("exampleApp") {} + ~ExampleApp() { shutdown(); } - ctk::ConfigReader config{this, "config", "demoApp2a.xml"}; + ctk::ConfigReader config{this, "config", "demoApp2a.xml"}; - Controller controller{this, "Controller", "The Controller"}; - Automation automation; + Controller controller{this, "Controller", "The Controller"}; + Automation automation; - ctk::PeriodicTrigger timer{this, "Timer", "Periodic timer for the controller", 1000}; + ctk::PeriodicTrigger timer{this, "Timer", "Periodic timer for the controller", + 1000}; - ctk::DeviceModule heater{"oven","heater"}; - ctk::ControlSystemModule cs{"Bakery"}; + ctk::DeviceModule heater{"oven", "heater"}; + ctk::ControlSystemModule cs{"Bakery"}; - void defineConnections(); + void defineConnections(); }; static ExampleApp theExampleApp; - void ExampleApp::defineConnections() { - ChimeraTK::setDMapFilePath("example2.dmap"); - - config.connectTo(cs["Configuration"]); - if(config.get<int>("enableAutomation")) { - automation = Automation(this, "Automation", "Slow setpoint ramping algorithm"); - automation.findTag("Controller").connectTo(controller); - timer.tick >> automation.trigger; - } - - controller.findTag("DEV").connectTo(heater, timer.tick); - findTag("CS").connectTo(cs); + ChimeraTK::setDMapFilePath("example2.dmap"); + + config.connectTo(cs["Configuration"]); + if (config.get<int>("enableAutomation")) { + automation = + Automation(this, "Automation", "Slow setpoint ramping algorithm"); + automation.findTag("Controller").connectTo(controller); + timer.tick >> automation.trigger; + } + + controller.findTag("DEV").connectTo(heater, timer.tick); + findTag("CS").connectTo(cs); } diff --git a/example3/demoApp3.cc b/example3/demoApp3.cc index cd5b7cd3..df38a720 100644 --- a/example3/demoApp3.cc +++ b/example3/demoApp3.cc @@ -4,20 +4,20 @@ namespace ctk = ChimeraTK; struct ExampleApp : public ctk::Application { - ExampleApp() : Application("exampleApp") {} - ~ExampleApp() { shutdown(); } + ExampleApp() : Application("exampleApp") {} + ~ExampleApp() { shutdown(); } - ctk::PeriodicTrigger timer{this, "Timer", "Periodic timer for the controller", 1000}; + ctk::PeriodicTrigger timer{this, "Timer", "Periodic timer for the controller", + 1000}; - ctk::DeviceModule dev{"oven"}; - ctk::ControlSystemModule cs{"Bakery"}; + ctk::DeviceModule dev{"oven"}; + ctk::ControlSystemModule cs{"Bakery"}; - void defineConnections(); + void defineConnections(); }; static ExampleApp theExampleApp; - void ExampleApp::defineConnections() { - ChimeraTK::setDMapFilePath("example2.dmap"); - dev.connectTo(cs, timer.tick); + ChimeraTK::setDMapFilePath("example2.dmap"); + dev.connectTo(cs, timer.tick); } diff --git a/include/Application.h b/include/Application.h index 4183cfec..7f360346 100644 --- a/include/Application.h +++ b/include/Application.h @@ -8,364 +8,411 @@ #ifndef CHIMERATK_APPLICATION_H #define CHIMERATK_APPLICATION_H -#include <mutex> #include <atomic> +#include <mutex> -#include <ChimeraTK/DeviceBackend.h> #include <ChimeraTK/ControlSystemAdapter/ApplicationBase.h> +#include <ChimeraTK/DeviceBackend.h> -#include "VariableNetwork.h" +#include "EntityOwner.h" #include "Flags.h" #include "InternalModule.h" -#include "EntityOwner.h" #include "Profiler.h" +#include "VariableNetwork.h" //#include "DeviceModule.h" namespace ChimeraTK { - class Module; - class AccessorBase; - class VariableNetwork; - class TriggerFanOut; - class TestFacility; - class DeviceModule; - - template<typename UserType> - class Accessor; - - class Application : public ApplicationBase, public EntityOwner { - - public: - - /** The constructor takes the application name as an argument. The name must have a non-zero length and must not - * contain any spaces or special characters. Use only alphanumeric characters and underscores. */ - Application(const std::string& name); - - ~Application() {} - - using ApplicationBase::getName; - - /** This will remove the global pointer to the instance and allows creating another instance - * afterwards. This is mostly useful for writing tests, as it allows to run several applications sequentially - * in the same executable. Note that any ApplicationModules etc. owned by this Application are no longer - * valid after destroying the Application and must be destroyed as well (or at least no longer used). */ - void shutdown() override; - - /** Define the connections between process variables. Must be implemented by the application developer. */ - virtual void defineConnections() = 0; - - void initialise() override; - - void run() override; - - /** Instead of running the application, just initialise it and output the published variables to an XML file. */ - void generateXML(); - - /** Output the connections requested in the initialise() function to std::cout. This may be done also before - * makeConnections() has been called. */ - void dumpConnections(); - - /** Create Graphviz dot graph and write to file. The graph will contain the connections made in the initilise() - * function. @see dumpConnections */ - void dumpConnectionGraph(const std::string &filename = {"connections-graph.dot"}); - - /** Enable warning about unconnected variables. This can be helpful to identify missing connections but is - * disabled by default since it may often be very noisy. */ - void warnUnconnectedVariables() { enableUnconnectedVariablesWarning = true; } - - /** Obtain instance of the application. Will throw an exception if called before the instance has been - * created by the control system adapter, or if the instance is not based on the Application class. */ - static Application& getInstance(); - - /** Enable the testable mode. This allows to pause and resume the application for testing purposes using the - * functions pauseApplication() and resumeApplication(). The application will start in paused state. - * - * This function must be called before the application is initialised (i.e. before the call to initialise()). - * - * Note: Enabling the testable mode will have a singificant impact on the performance, since it will prevent - * any module threads to run at the same time! */ - void enableTestableMode() { - threadName() = "TEST THREAD"; - testableMode = true; - testableModeLock("enableTestableMode"); - } - - /** - * Returns true if application is in testable mode else returns - * false. - **/ - bool isTestableModeEnabled() { return testableMode; } - - /** Resume the application until all application threads are stuck in a blocking read operation. Works only when - * the testable mode was enabled. */ - void stepApplication(); - - /** Enable some additional (potentially noisy) debug output for the testable mode. Can be useful if tests - * of applications seem to hang for no reason in stepApplication. */ - void debugTestableMode() { enableDebugTestableMode = true; } - - /** Lock the testable mode mutex for the current thread. Internally, a thread-local std::unique_lock<std::mutex> - * will be created and re-used in subsequent calls within the same thread to this function and to - * testableModeUnlock(). - * - * This function should generally not be used in user code. */ - static void testableModeLock(const std::string& name); - - /** Unlock the testable mode mutex for the current thread. See also testableModeLock(). - * - * Initially the lock will not be owned by the current thread, so the first call to this function will throw an - * exception (see std::unique_lock::unlock()), unless testableModeLock() has been called first. - * - * This function should generally not be used in user code. */ - static void testableModeUnlock(const std::string& name); - - /** Test if the testable mode mutex is locked by the current thread. - * - * This function should generally not be used in user code. */ - static bool testableModeTestLock() { - if(!getInstance().testableMode) return false; - return getTestableModeLockObject().owns_lock(); - } - - /** Get string holding the name of the current thread. This is used e.g. for debugging output of the testable - * mode and for the internal profiler. */ - static std::string& threadName(); - - /** Register the thread in the application system and give it a name. This should be done for all threads used by - * the application to help with debugging and to allow profiling. */ - static void registerThread(const std::string &name) { - threadName() = name; - Profiler::registerThread(name); - pthread_setname_np(pthread_self(), name.substr(0,std::min(name.length(),15UL)).c_str()); - } - - ModuleType getModuleType() const override { return ModuleType::ModuleGroup; } - - std::string getQualifiedName() const override { - return "/" + _name; - } - - std::string getFullDescription() const override { - return ""; - } - - /** Special exception class which will be thrown if tests with the testable mode are stalled. Normally this - * exception should never be caught. The only reason for catching it might be a situation where the expected - * behaviour of an app is to do nothing and one wants to test this. Note that the stall condition only appears - * after a certain timeout, thus tests relying on this will take time. - * - * This exception must not be based on a generic exception class to prevent catching it unintentionally. */ - class TestsStalled {}; - - /** Enable debug output for a given variable. */ - void enableVariableDebugging(const VariableNetworkNode &node) { - debugMode_variableList.insert(node.getUniqueId()); - } - - /** Incremenet counter for how many write() operations have overwritten unread data */ - static void incrementDataLossCounter() { - getInstance().dataLossCounter++; - } - - static size_t getAndResetDataLossCounter() { - size_t counter = getInstance().dataLossCounter.load(std::memory_order_relaxed); - while(!getInstance().dataLossCounter.compare_exchange_weak( counter, 0, std::memory_order_release, - std::memory_order_relaxed )); - return counter; - } - - /** Convenience function for creating constants. See VariableNetworkNode::makeConstant() for details. */ - template<typename UserType> - static VariableNetworkNode makeConstant(UserType value, size_t length=1, bool makeFeeder=true) { - return VariableNetworkNode::makeConstant(makeFeeder, value, length); - } - - void registerDeviceModule(DeviceModule* deviceModule); - - protected: - - friend class Module; - friend class VariableNetwork; - friend class VariableNetworkNode; - friend class VariableNetworkGraphDumpingVisitor; - friend class XMLGeneratorVisitor; - - - template<typename UserType> - friend class Accessor; - - /** Finalise connections, i.e. decide still-undecided details mostly for Device and ControlSystem variables */ - void finaliseNetworks(); - - /** Check if all connections are valid. Internally called in initialise(). */ - void checkConnections(); - - /** Obtain the lock object for the testable mode lock for the current thread. The returned object has - * thread_local storage duration and must only be used inside the current thread. Initially (i.e. after - * the first call in one particular thread) the lock will not be owned by the returned object, so it is - * important to catch the corresponding exception when calling std::unique_lock::unlock(). */ - static std::unique_lock<std::mutex>& getTestableModeLockObject(); - - /** Register the connections to constants for previously unconnected nodes. */ - void processUnconnectedNodes(); - - /** Make the connections between accessors as requested in the initialise() function. */ - void makeConnections(); - - /** Apply optimisations to the VariableNetworks, e.g. by merging networks sharing the same feeder. */ - void optimiseConnections(); - - /** Make the connections for a single network */ - void makeConnectionsForNetwork(VariableNetwork &network); - - /** UserType-dependent part of makeConnectionsForNetwork() */ - template<typename UserType> - void typedMakeConnection(VariableNetwork &network); - - /** Functor class to call typedMakeConnection() with the right template argument. */ - struct TypedMakeConnectionCaller { - TypedMakeConnectionCaller(Application &owner, VariableNetwork &network); - - template<typename PAIR> - void operator()(PAIR&) const; - - Application &_owner; - VariableNetwork &_network; - mutable bool done{false}; - }; - - /** Register a connection between two VariableNetworkNode */ - VariableNetwork& connect(VariableNetworkNode a, VariableNetworkNode b); - - /** Perform the actual connection of an accessor to a device register */ - template<typename UserType> - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> createDeviceVariable(const std::string &deviceAlias, - const std::string ®isterName, VariableDirection direction, UpdateMode mode, size_t nElements); - - /** Create a process variable with the PVManager, which is exported to the control system adapter. nElements will - be the array size of the created variable. */ - template<typename UserType> - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> createProcessVariable(VariableNetworkNode const &node); - - /** Create a local process variable which is not exported. The first element in the returned pair will be the - * sender, the second the receiver. If two nodes are passed, the first node should be the sender and the second - * the receiver. */ - template<typename UserType> - std::pair< boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> > - createApplicationVariable(VariableNetworkNode const &node, VariableNetworkNode const &consumer={}); - - /** List of InternalModules */ - std::list<boost::shared_ptr<InternalModule>> internalModuleList; - - - /** List of variable networks */ - std::list<VariableNetwork> networkList; - - /** List of constant variable nodes */ - std::list<VariableNetworkNode> constantList; - - /** Map of trigger sources to their corresponding TriggerFanOuts. Note: the key is the unique ID of the - * triggering node. */ - std::map<const void*, boost::shared_ptr<TriggerFanOut>> triggerMap; - - /** Create a new, empty network */ - VariableNetwork& createNetwork(); - - /** Instance of VariableNetwork to indicate an invalid network */ - VariableNetwork invalidNetwork; - - /** Map of DeviceBackends used by this application. The map key is the alias name from the DMAP file */ - std::map<std::string, boost::shared_ptr<ChimeraTK::DeviceBackend>> deviceMap; - - /** Flag if connections should be made in testable mode (i.e. the TestableModeAccessorDecorator is put around all - * push-type input accessors etc.). */ - bool testableMode{false}; - - /** Mutex used in testable mode to take control over the application threads. Use only through the lock object - * obtained through getLockObjectForCurrentThread(). - * - * This member is static, since it should survive destroying an application instance and creating a new one. - * Otherwise getTestableModeLockObject() would not work, since it relies on thread_local instances which have to - * be static. The static storage duration presents no problem in either case, since there can only be one single - * instance of Application at a time (see ApplicationBase constructor). */ - static std::mutex testableMode_mutex; - - /** Semaphore counter used in testable mode to check if application code is finished executing. This value may - * only be accessed while holding the testableMode_mutex. */ - size_t testableMode_counter{0}; - - /** Flag if noisy debug output is enabled for the testable mode */ - bool enableDebugTestableMode{false}; - - /** Flag whether to warn about unconnected variables or not */ - bool enableUnconnectedVariablesWarning{false}; - - /** Map from ProcessArray uniqueId to the variable ID for control system variables. This is required for the - * TestFacility. */ - std::map<size_t, size_t> pvIdMap; - - /** Return a fresh variable ID which can be assigned to a sender/receiver pair. The ID will always be non-zero. */ - static size_t getNextVariableId() { - static size_t nextId{0}; - return ++nextId; - } - - /** Last thread which successfully obtained the lock for the testable mode. This is used to prevent spamming - * repeating messages if the same thread acquires and releases the lock in a loop without another thread - * activating in between. */ - std::thread::id testableMode_lastMutexOwner; - - /** Counter how often the same thread has acquired the testable mode mutex in a row without another thread - * owning it in between. This is an indicator for the test being stalled due to data send through a process - * variable but not read by the receiver. */ - std::atomic<size_t> testableMode_repeatingMutexOwner{false}; - - /** Testable mode: like testableMode_counter but broken out for each variable. This is not actually used as a - * semaphore counter but only in case of a detected stall (see testableMode_repeatingMutexOwner) to print - * a list of variables which still contain unread values. The index of the map is the unique ID of the - * variable. */ - std::map<size_t, size_t> testableMode_perVarCounter; - - /** Map of unique IDs to namess, used along with testableMode_perVarCounter to print sensible information. */ - std::map<size_t, std::string> testableMode_names; - - /** Map of unique IDs to process variables which have been decorated with the TestableModeAccessorDecorator. */ - std::map<size_t, boost::shared_ptr<TransferElement>> testableMode_processVars; - - /** Map of unique IDs to flags whether the update mode is UpdateMode::poll (so we do not use the decorator) */ - std::map<size_t, bool> testableMode_isPollMode; - - /** List of variables for which debug output was requested via enableVariableDebugging(). Stored is the unique - * id of the VariableNetworkNode.*/ - std::unordered_set<const void*> debugMode_variableList; - - template<typename UserType> - friend class TestableModeAccessorDecorator; // needs access to the testableMode_mutex and testableMode_counter and the idMap - - friend class TestFacility; // needs access to testableMode_variables - - template<typename UserType> - friend class DebugPrintAccessorDecorator; // needs access to the idMap - - /** Counter for how many write() operations have overwritten unread data */ - std::atomic<size_t> dataLossCounter{0}; - - VersionNumber getCurrentVersionNumber() const override { - throw ChimeraTK::logic_error("getCurrentVersionNumber() called on the application. This is probably caused by " - "incorrect ownership of variables/accessors or VariableGroups."); - } - void setCurrentVersionNumber(VersionNumber) { - throw ChimeraTK::logic_error("setCurrentVersionNumber() called on the application. This is probably caused by " - "incorrect ownership of variables/accessors or VariableGroups."); - } - - std::list<DeviceModule*> deviceModuleList; - - - - +class Module; +class AccessorBase; +class VariableNetwork; +class TriggerFanOut; +class TestFacility; +class DeviceModule; + +template <typename UserType> class Accessor; + +class Application : public ApplicationBase, public EntityOwner { + +public: + /** The constructor takes the application name as an argument. The name must + * have a non-zero length and must not contain any spaces or special + * characters. Use only alphanumeric characters and underscores. */ + Application(const std::string &name); + + ~Application() {} + + using ApplicationBase::getName; + + /** This will remove the global pointer to the instance and allows creating + * another instance afterwards. This is mostly useful for writing tests, as it + * allows to run several applications sequentially in the same executable. + * Note that any ApplicationModules etc. owned by this Application are no + * longer + * valid after destroying the Application and must be destroyed as well (or + * at least no longer used). */ + void shutdown() override; + + /** Define the connections between process variables. Must be implemented by + * the application developer. */ + virtual void defineConnections() = 0; + + void initialise() override; + + void run() override; + + /** Instead of running the application, just initialise it and output the + * published variables to an XML file. */ + void generateXML(); + + /** Output the connections requested in the initialise() function to + * std::cout. This may be done also before + * makeConnections() has been called. */ + void dumpConnections(); + + /** Create Graphviz dot graph and write to file. The graph will contain the + * connections made in the initilise() function. @see dumpConnections */ + void dumpConnectionGraph(const std::string &filename = { + "connections-graph.dot"}); + + /** Enable warning about unconnected variables. This can be helpful to + * identify missing connections but is + * disabled by default since it may often be very noisy. */ + void warnUnconnectedVariables() { enableUnconnectedVariablesWarning = true; } + + /** Obtain instance of the application. Will throw an exception if called + * before the instance has been created by the control system adapter, or if + * the instance is not based on the Application class. */ + static Application &getInstance(); + + /** Enable the testable mode. This allows to pause and resume the application + * for testing purposes using the functions pauseApplication() and + * resumeApplication(). The application will start in paused state. + * + * This function must be called before the application is initialised (i.e. + * before the call to initialise()). + * + * Note: Enabling the testable mode will have a singificant impact on the + * performance, since it will prevent any module threads to run at the same + * time! */ + void enableTestableMode() { + threadName() = "TEST THREAD"; + testableMode = true; + testableModeLock("enableTestableMode"); + } + + /** + * Returns true if application is in testable mode else returns + * false. + **/ + bool isTestableModeEnabled() { return testableMode; } + + /** Resume the application until all application threads are stuck in a + * blocking read operation. Works only when the testable mode was enabled. */ + void stepApplication(); + + /** Enable some additional (potentially noisy) debug output for the testable + * mode. Can be useful if tests + * of applications seem to hang for no reason in stepApplication. */ + void debugTestableMode() { enableDebugTestableMode = true; } + + /** Lock the testable mode mutex for the current thread. Internally, a + * thread-local std::unique_lock<std::mutex> will be created and re-used in + * subsequent calls within the same thread to this function and to + * testableModeUnlock(). + * + * This function should generally not be used in user code. */ + static void testableModeLock(const std::string &name); + + /** Unlock the testable mode mutex for the current thread. See also + * testableModeLock(). + * + * Initially the lock will not be owned by the current thread, so the first + * call to this function will throw an exception (see + * std::unique_lock::unlock()), unless testableModeLock() has been called + * first. + * + * This function should generally not be used in user code. */ + static void testableModeUnlock(const std::string &name); + + /** Test if the testable mode mutex is locked by the current thread. + * + * This function should generally not be used in user code. */ + static bool testableModeTestLock() { + if (!getInstance().testableMode) + return false; + return getTestableModeLockObject().owns_lock(); + } + + /** Get string holding the name of the current thread. This is used e.g. for + * debugging output of the testable mode and for the internal profiler. */ + static std::string &threadName(); + + /** Register the thread in the application system and give it a name. This + * should be done for all threads used by the application to help with + * debugging and to allow profiling. */ + static void registerThread(const std::string &name) { + threadName() = name; + Profiler::registerThread(name); + pthread_setname_np(pthread_self(), + name.substr(0, std::min(name.length(), 15UL)).c_str()); + } + + ModuleType getModuleType() const override { return ModuleType::ModuleGroup; } + + std::string getQualifiedName() const override { return "/" + _name; } + + std::string getFullDescription() const override { return ""; } + + /** Special exception class which will be thrown if tests with the testable + * mode are stalled. Normally this exception should never be caught. The only + * reason for catching it might be a situation where the expected behaviour of + * an app is to do nothing and one wants to test this. Note that the stall + * condition only appears after a certain timeout, thus tests relying on this + * will take time. + * + * This exception must not be based on a generic exception class to prevent + * catching it unintentionally. */ + class TestsStalled {}; + + /** Enable debug output for a given variable. */ + void enableVariableDebugging(const VariableNetworkNode &node) { + debugMode_variableList.insert(node.getUniqueId()); + } + + /** Incremenet counter for how many write() operations have overwritten unread + * data */ + static void incrementDataLossCounter() { getInstance().dataLossCounter++; } + + static size_t getAndResetDataLossCounter() { + size_t counter = + getInstance().dataLossCounter.load(std::memory_order_relaxed); + while (!getInstance().dataLossCounter.compare_exchange_weak( + counter, 0, std::memory_order_release, std::memory_order_relaxed)) + ; + return counter; + } + + /** Convenience function for creating constants. See + * VariableNetworkNode::makeConstant() for details. */ + template <typename UserType> + static VariableNetworkNode makeConstant(UserType value, size_t length = 1, + bool makeFeeder = true) { + return VariableNetworkNode::makeConstant(makeFeeder, value, length); + } + + void registerDeviceModule(DeviceModule *deviceModule); + +protected: + friend class Module; + friend class VariableNetwork; + friend class VariableNetworkNode; + friend class VariableNetworkGraphDumpingVisitor; + friend class XMLGeneratorVisitor; + + template <typename UserType> friend class Accessor; + + /** Finalise connections, i.e. decide still-undecided details mostly for + * Device and ControlSystem variables */ + void finaliseNetworks(); + + /** Check if all connections are valid. Internally called in initialise(). */ + void checkConnections(); + + /** Obtain the lock object for the testable mode lock for the current thread. + * The returned object has thread_local storage duration and must only be used + * inside the current thread. Initially (i.e. after the first call in one + * particular thread) the lock will not be owned by the returned object, so it + * is important to catch the corresponding exception when calling + * std::unique_lock::unlock(). */ + static std::unique_lock<std::mutex> &getTestableModeLockObject(); + + /** Register the connections to constants for previously unconnected nodes. */ + void processUnconnectedNodes(); + + /** Make the connections between accessors as requested in the initialise() + * function. */ + void makeConnections(); + + /** Apply optimisations to the VariableNetworks, e.g. by merging networks + * sharing the same feeder. */ + void optimiseConnections(); + + /** Make the connections for a single network */ + void makeConnectionsForNetwork(VariableNetwork &network); + + /** UserType-dependent part of makeConnectionsForNetwork() */ + template <typename UserType> + void typedMakeConnection(VariableNetwork &network); + + /** Functor class to call typedMakeConnection() with the right template + * argument. */ + struct TypedMakeConnectionCaller { + TypedMakeConnectionCaller(Application &owner, VariableNetwork &network); + + template <typename PAIR> void operator()(PAIR &) const; + + Application &_owner; + VariableNetwork &_network; + mutable bool done{false}; }; + /** Register a connection between two VariableNetworkNode */ + VariableNetwork &connect(VariableNetworkNode a, VariableNetworkNode b); + + /** Perform the actual connection of an accessor to a device register */ + template <typename UserType> + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> + createDeviceVariable(const std::string &deviceAlias, + const std::string ®isterName, + VariableDirection direction, UpdateMode mode, + size_t nElements); + + /** Create a process variable with the PVManager, which is exported to the + control system adapter. nElements will be the array size of the created + variable. */ + template <typename UserType> + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> + createProcessVariable(VariableNetworkNode const &node); + + /** Create a local process variable which is not exported. The first element + * in the returned pair will be the sender, the second the receiver. If two + * nodes are passed, the first node should be the sender and the second the + * receiver. */ + template <typename UserType> + std::pair<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>> + createApplicationVariable(VariableNetworkNode const &node, + VariableNetworkNode const &consumer = {}); + + /** List of InternalModules */ + std::list<boost::shared_ptr<InternalModule>> internalModuleList; + + /** List of variable networks */ + std::list<VariableNetwork> networkList; + + /** List of constant variable nodes */ + std::list<VariableNetworkNode> constantList; + + /** Map of trigger sources to their corresponding TriggerFanOuts. Note: the + * key is the unique ID of the triggering node. */ + std::map<const void *, boost::shared_ptr<TriggerFanOut>> triggerMap; + + /** Create a new, empty network */ + VariableNetwork &createNetwork(); + + /** Instance of VariableNetwork to indicate an invalid network */ + VariableNetwork invalidNetwork; + + /** Map of DeviceBackends used by this application. The map key is the alias + * name from the DMAP file */ + std::map<std::string, boost::shared_ptr<ChimeraTK::DeviceBackend>> deviceMap; + + /** Flag if connections should be made in testable mode (i.e. the + * TestableModeAccessorDecorator is put around all push-type input accessors + * etc.). */ + bool testableMode{false}; + + /** Mutex used in testable mode to take control over the application threads. + * Use only through the lock object obtained through + * getLockObjectForCurrentThread(). + * + * This member is static, since it should survive destroying an application + * instance and creating a new one. Otherwise getTestableModeLockObject() + * would not work, since it relies on thread_local instances which have to be + * static. The static storage duration presents no problem in either case, + * since there can only be one single instance of Application at a time (see + * ApplicationBase constructor). */ + static std::mutex testableMode_mutex; + + /** Semaphore counter used in testable mode to check if application code is + * finished executing. This value may only be accessed while holding the + * testableMode_mutex. */ + size_t testableMode_counter{0}; + + /** Flag if noisy debug output is enabled for the testable mode */ + bool enableDebugTestableMode{false}; + + /** Flag whether to warn about unconnected variables or not */ + bool enableUnconnectedVariablesWarning{false}; + + /** Map from ProcessArray uniqueId to the variable ID for control system + * variables. This is required for the TestFacility. */ + std::map<size_t, size_t> pvIdMap; + + /** Return a fresh variable ID which can be assigned to a sender/receiver + * pair. The ID will always be non-zero. */ + static size_t getNextVariableId() { + static size_t nextId{0}; + return ++nextId; + } + + /** Last thread which successfully obtained the lock for the testable mode. + * This is used to prevent spamming repeating messages if the same thread + * acquires and releases the lock in a loop without another thread + * activating in between. */ + std::thread::id testableMode_lastMutexOwner; + + /** Counter how often the same thread has acquired the testable mode mutex in + * a row without another thread owning it in between. This is an indicator for + * the test being stalled due to data send through a process + * variable but not read by the receiver. */ + std::atomic<size_t> testableMode_repeatingMutexOwner{false}; + + /** Testable mode: like testableMode_counter but broken out for each variable. + * This is not actually used as a semaphore counter but only in case of a + * detected stall (see testableMode_repeatingMutexOwner) to print a list of + * variables which still contain unread values. The index of the map is the + * unique ID of the variable. */ + std::map<size_t, size_t> testableMode_perVarCounter; + + /** Map of unique IDs to namess, used along with testableMode_perVarCounter to + * print sensible information. */ + std::map<size_t, std::string> testableMode_names; + + /** Map of unique IDs to process variables which have been decorated with the + * TestableModeAccessorDecorator. */ + std::map<size_t, boost::shared_ptr<TransferElement>> testableMode_processVars; + + /** Map of unique IDs to flags whether the update mode is UpdateMode::poll (so + * we do not use the decorator) */ + std::map<size_t, bool> testableMode_isPollMode; + + /** List of variables for which debug output was requested via + * enableVariableDebugging(). Stored is the unique id of the + * VariableNetworkNode.*/ + std::unordered_set<const void *> debugMode_variableList; + + template <typename UserType> + friend class TestableModeAccessorDecorator; // needs access to the + // testableMode_mutex and + // testableMode_counter and the + // idMap + + friend class TestFacility; // needs access to testableMode_variables + + template <typename UserType> + friend class DebugPrintAccessorDecorator; // needs access to the idMap + + /** Counter for how many write() operations have overwritten unread data */ + std::atomic<size_t> dataLossCounter{0}; + + VersionNumber getCurrentVersionNumber() const override { + throw ChimeraTK::logic_error( + "getCurrentVersionNumber() called on the application. This is probably " + "caused by " + "incorrect ownership of variables/accessors or VariableGroups."); + } + void setCurrentVersionNumber(VersionNumber) { + throw ChimeraTK::logic_error( + "setCurrentVersionNumber() called on the application. This is probably " + "caused by " + "incorrect ownership of variables/accessors or VariableGroups."); + } + + std::list<DeviceModule *> deviceModuleList; +}; + } /* namespace ChimeraTK */ #endif /* CHIMERATK_APPLICATION_H */ diff --git a/include/ApplicationCore.h b/include/ApplicationCore.h index 64cd51b1..c88f35da 100644 --- a/include/ApplicationCore.h +++ b/include/ApplicationCore.h @@ -4,18 +4,18 @@ * Created on: Jun 14, 2016 * Author: Martin Hierholzer * - * This is the main header file for the ApplicationCore library. It brings all includes and functionality needed - * for writing an application. + * This is the main header file for the ApplicationCore library. It brings all + * includes and functionality needed for writing an application. */ -#include <ChimeraTK/Utilities.h> // for ChimeraTK::setDMapFilePath(), which is used by all applications +#include <ChimeraTK/Utilities.h> // for ChimeraTK::setDMapFilePath(), which is used by all applications #include "Application.h" -#include "ScalarAccessor.h" -#include "ArrayAccessor.h" #include "ApplicationModule.h" -#include "DeviceModule.h" +#include "ArrayAccessor.h" #include "ControlSystemModule.h" -#include "VariableGroup.h" +#include "DeviceModule.h" #include "ModuleGroup.h" +#include "ScalarAccessor.h" +#include "VariableGroup.h" #include "VirtualModule.h" diff --git a/include/ApplicationModule.h b/include/ApplicationModule.h index ce14bb8c..59562ee5 100644 --- a/include/ApplicationModule.h +++ b/include/ApplicationModule.h @@ -14,75 +14,83 @@ #include "ModuleImpl.h" - namespace ChimeraTK { - class Application; - class ModuleGroup; - - class ApplicationModule : public ModuleImpl { - - public: - - /** Constructor: register the module with its owner. If eliminateHierarchy is true, the hierarchy level - * introduced by this module will be eliminated from the "dynamic" data model (see - * EntityOwner::setEliminateHierarchy()). The tags given as the last argument are added to all variables - * in this module recursively (see EntityOwner::addTag()). - * - * Note: ApplicationModules may only be owned by ModuleGroups or Applications. */ - ApplicationModule(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}); - - /** Default constructor: Allows late initialisation of modules (e.g. when creating arrays of modules). - * - * This construtor also has to be here to mitigate a bug in gcc. It is needed to allow constructor - * inheritance of modules owning other modules. This constructor will not actually be called then. - * See this bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ - ApplicationModule() {} - - /** Move operation with the move constructor */ - ApplicationModule(ApplicationModule &&other) - { - operator=(std::move(other)); - } - - /** Move assignment */ - ApplicationModule& operator=(ApplicationModule &&other) { - assert(!moduleThread.joinable()); // if the thread is already running, moving is no longer allowed! - ModuleImpl::operator=(std::move(other)); - return *this; - } - - /** Destructor */ - virtual ~ApplicationModule(); - - /** To be implemented by the user: function called in a separate thread executing the main loop of the module */ - virtual void mainLoop() = 0; - - void run() override; - - void terminate() override; - - ModuleType getModuleType() const override { return ModuleType::ApplicationModule; } - - VersionNumber getCurrentVersionNumber() const override { return currentVersionNumber; } - - protected: - - /** Wrapper around mainLoop(), to execute additional tasks in the thread before entering the main loop */ - void mainLoopWrapper(); - - /** The thread executing mainLoop() */ - boost::thread moduleThread; - - void setCurrentVersionNumber(VersionNumber versionNumber) override { - if(versionNumber > currentVersionNumber) currentVersionNumber = versionNumber; - } - - /** Version number of last push-type read operation - will be passed on to any write operations */ - VersionNumber currentVersionNumber; - - }; +class Application; +class ModuleGroup; + +class ApplicationModule : public ModuleImpl { + +public: + /** Constructor: register the module with its owner. If eliminateHierarchy is + * true, the hierarchy level introduced by this module will be eliminated from + * the "dynamic" data model (see EntityOwner::setEliminateHierarchy()). The + * tags given as the last argument are added to all variables in this module + * recursively (see EntityOwner::addTag()). + * + * Note: ApplicationModules may only be owned by ModuleGroups or + * Applications. */ + ApplicationModule(EntityOwner *owner, const std::string &name, + const std::string &description, + bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}); + + /** Default constructor: Allows late initialisation of modules (e.g. when + * creating arrays of modules). + * + * This construtor also has to be here to mitigate a bug in gcc. It is needed + * to allow constructor inheritance of modules owning other modules. This + * constructor will not actually be called then. See this bug report: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ + ApplicationModule() {} + + /** Move operation with the move constructor */ + ApplicationModule(ApplicationModule &&other) { operator=(std::move(other)); } + + /** Move assignment */ + ApplicationModule &operator=(ApplicationModule &&other) { + assert(!moduleThread.joinable()); // if the thread is already running, + // moving is no longer allowed! + ModuleImpl::operator=(std::move(other)); + return *this; + } + + /** Destructor */ + virtual ~ApplicationModule(); + + /** To be implemented by the user: function called in a separate thread + * executing the main loop of the module */ + virtual void mainLoop() = 0; + + void run() override; + + void terminate() override; + + ModuleType getModuleType() const override { + return ModuleType::ApplicationModule; + } + + VersionNumber getCurrentVersionNumber() const override { + return currentVersionNumber; + } + +protected: + /** Wrapper around mainLoop(), to execute additional tasks in the thread + * before entering the main loop */ + void mainLoopWrapper(); + + /** The thread executing mainLoop() */ + boost::thread moduleThread; + + void setCurrentVersionNumber(VersionNumber versionNumber) override { + if (versionNumber > currentVersionNumber) + currentVersionNumber = versionNumber; + } + + /** Version number of last push-type read operation - will be passed on to any + * write operations */ + VersionNumber currentVersionNumber; +}; } /* namespace ChimeraTK */ diff --git a/include/ArrayAccessor.h b/include/ArrayAccessor.h index 166ddc2f..c07c12a2 100644 --- a/include/ArrayAccessor.h +++ b/include/ArrayAccessor.h @@ -1,9 +1,9 @@ /* -* ArrayAccessor.h -* -* Created on: Jun 07, 2016 -* Author: Martin Hierholzer -*/ + * ArrayAccessor.h + * + * Created on: Jun 07, 2016 + * Author: Martin Hierholzer + */ #ifndef CHIMERATK_ARRAY_ACCESSOR_H #define CHIMERATK_ARRAY_ACCESSOR_H @@ -15,143 +15,158 @@ #include <ChimeraTK/OneDRegisterAccessor.h> +#include "Application.h" #include "InversionOfControlAccessor.h" #include "Profiler.h" -#include "Application.h" namespace ChimeraTK { - /********************************************************************************************************************/ - - /** Accessor for array variables (i.e. vectors). Note for users: Use the convenience classes - * ArrayPollInput, ArrayPushInput, ArrayOutput instead of this class directly. */ - template< typename UserType > - class ArrayAccessor : public ChimeraTK::OneDRegisterAccessor<UserType>, - public InversionOfControlAccessor<ArrayAccessor<UserType>> { - public: - - using InversionOfControlAccessor<ArrayAccessor<UserType>>::operator VariableNetworkNode; - using InversionOfControlAccessor<ArrayAccessor<UserType>>::operator>>; - void replace(const ChimeraTK::NDRegisterAccessorAbstractor<UserType> &newAccessor) = delete; - using InversionOfControlAccessor<ArrayAccessor<UserType>>::replace; - ArrayAccessor<UserType>& operator=(ArrayAccessor<UserType> &other) = delete; - using ChimeraTK::OneDRegisterAccessor<UserType>::operator=; - - /** Move constructor */ - ArrayAccessor(ArrayAccessor<UserType> &&other) { - InversionOfControlAccessor<ArrayAccessor<UserType>>::replace(std::move(other)); - } - - /** Move assignment */ - ArrayAccessor<UserType>& operator=(ArrayAccessor<UserType> &&other) { - // Having a move-assignment operator is required to use the move-assignment operator of a module containing - // an accessor. - InversionOfControlAccessor<ArrayAccessor<UserType>>::replace(std::move(other)); - return *this; - } - - bool write(ChimeraTK::VersionNumber versionNumber) = delete; - - bool write() { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - bool dataLoss = ChimeraTK::OneDRegisterAccessor<UserType>::write(versionNumber); - if(dataLoss) Application::incrementDataLossCounter(); - return dataLoss; - } - - TransferFuture readAsync() { - throw ChimeraTK::logic_error("ArrayAccessor::readAsync() is currently not supported by ApplicationCore!"); - } - - protected: - - friend class InversionOfControlAccessor<ArrayAccessor<UserType>>; - - ArrayAccessor(Module *owner, const std::string &name, VariableDirection direction, std::string unit, - size_t nElements, UpdateMode mode, const std::string &description, - const std::unordered_set<std::string> &tags={}) - : InversionOfControlAccessor<ArrayAccessor<UserType>>(owner, name, direction, unit, nElements, mode, - description, &typeid(UserType), tags) - {} - - /** Default constructor creates a dysfunctional accessor (to be assigned with a real accessor later) */ - ArrayAccessor() {} - - }; - - /********************************************************************************************************************/ - - /** Convenience class for input array accessors with UpdateMode::push */ - template< typename UserType > - struct ArrayPushInput : public ArrayAccessor<UserType> { - ArrayPushInput(Module *owner, const std::string &name, std::string unit, size_t nElements, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ArrayAccessor<UserType>(owner, name, {VariableDirection::consuming, false}, unit, nElements, UpdateMode::push, - description, tags) - {} - ArrayPushInput() : ArrayAccessor<UserType>() {} - using ArrayAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ - - /** Convenience class for input array accessors with UpdateMode::poll */ - template< typename UserType > - struct ArrayPollInput : public ArrayAccessor<UserType> { - ArrayPollInput(Module *owner, const std::string &name, std::string unit, size_t nElements, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ArrayAccessor<UserType>(owner, name, {VariableDirection::consuming, false}, unit, nElements, UpdateMode::poll, - description, tags) - {} - ArrayPollInput() : ArrayAccessor<UserType>() {} - void doReadTransfer() { this->doReadTransferLatest(); } - void read() { this->readLatest(); } - using ArrayAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ - - /** Convenience class for output array accessors (always UpdateMode::push) */ - template< typename UserType > - struct ArrayOutput : public ArrayAccessor<UserType> { - ArrayOutput(Module *owner, const std::string &name, std::string unit, size_t nElements, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ArrayAccessor<UserType>(owner, name, {VariableDirection::feeding, false}, unit, nElements, UpdateMode::push, - description, tags) - {} - ArrayOutput() : ArrayAccessor<UserType>() {} - using ArrayAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ - - /** Convenience class for input array accessors with return channel ("write back") and UpdateMode::push */ - template< typename UserType > - struct ArrayPushInputWB : public ArrayAccessor<UserType> { - ArrayPushInputWB(Module *owner, const std::string &name, std::string unit, size_t nElements, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ArrayAccessor<UserType>(owner, name, {VariableDirection::consuming, true}, unit, nElements, UpdateMode::push, - description, tags) - {} - ArrayPushInputWB() : ArrayAccessor<UserType>() {} - using ArrayAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ - - /** Convenience class for output array accessors with return channel ("read back") (always UpdateMode::push) */ - template< typename UserType > - struct ArrayOutputRB : public ArrayAccessor<UserType> { - ArrayOutputRB(Module *owner, const std::string &name, std::string unit, size_t nElements, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ArrayAccessor<UserType>(owner, name, {VariableDirection::feeding, true}, unit, nElements, UpdateMode::push, - description, tags) - {} - ArrayOutputRB() : ArrayAccessor<UserType>() {} - using ArrayAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ +/********************************************************************************************************************/ + +/** Accessor for array variables (i.e. vectors). Note for users: Use the + * convenience classes ArrayPollInput, ArrayPushInput, ArrayOutput instead of + * this class directly. */ +template <typename UserType> +class ArrayAccessor + : public ChimeraTK::OneDRegisterAccessor<UserType>, + public InversionOfControlAccessor<ArrayAccessor<UserType>> { +public: + using InversionOfControlAccessor<ArrayAccessor<UserType>>:: + operator VariableNetworkNode; + using InversionOfControlAccessor<ArrayAccessor<UserType>>::operator>>; + void replace(const ChimeraTK::NDRegisterAccessorAbstractor<UserType> + &newAccessor) = delete; + using InversionOfControlAccessor<ArrayAccessor<UserType>>::replace; + ArrayAccessor<UserType> &operator=(ArrayAccessor<UserType> &other) = delete; + using ChimeraTK::OneDRegisterAccessor<UserType>::operator=; + + /** Move constructor */ + ArrayAccessor(ArrayAccessor<UserType> &&other) { + InversionOfControlAccessor<ArrayAccessor<UserType>>::replace( + std::move(other)); + } + + /** Move assignment */ + ArrayAccessor<UserType> &operator=(ArrayAccessor<UserType> &&other) { + // Having a move-assignment operator is required to use the move-assignment + // operator of a module containing an accessor. + InversionOfControlAccessor<ArrayAccessor<UserType>>::replace( + std::move(other)); + return *this; + } + + bool write(ChimeraTK::VersionNumber versionNumber) = delete; + + bool write() { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + bool dataLoss = + ChimeraTK::OneDRegisterAccessor<UserType>::write(versionNumber); + if (dataLoss) + Application::incrementDataLossCounter(); + return dataLoss; + } + + TransferFuture readAsync() { + throw ChimeraTK::logic_error("ArrayAccessor::readAsync() is currently not " + "supported by ApplicationCore!"); + } + +protected: + friend class InversionOfControlAccessor<ArrayAccessor<UserType>>; + + ArrayAccessor(Module *owner, const std::string &name, + VariableDirection direction, std::string unit, size_t nElements, + UpdateMode mode, const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : InversionOfControlAccessor<ArrayAccessor<UserType>>( + owner, name, direction, unit, nElements, mode, description, + &typeid(UserType), tags) {} + + /** Default constructor creates a dysfunctional accessor (to be assigned with + * a real accessor later) */ + ArrayAccessor() {} +}; + +/********************************************************************************************************************/ + +/** Convenience class for input array accessors with UpdateMode::push */ +template <typename UserType> +struct ArrayPushInput : public ArrayAccessor<UserType> { + ArrayPushInput(Module *owner, const std::string &name, std::string unit, + size_t nElements, const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ArrayAccessor<UserType>( + owner, name, {VariableDirection::consuming, false}, unit, nElements, + UpdateMode::push, description, tags) {} + ArrayPushInput() : ArrayAccessor<UserType>() {} + using ArrayAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ + +/** Convenience class for input array accessors with UpdateMode::poll */ +template <typename UserType> +struct ArrayPollInput : public ArrayAccessor<UserType> { + ArrayPollInput(Module *owner, const std::string &name, std::string unit, + size_t nElements, const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ArrayAccessor<UserType>( + owner, name, {VariableDirection::consuming, false}, unit, nElements, + UpdateMode::poll, description, tags) {} + ArrayPollInput() : ArrayAccessor<UserType>() {} + void doReadTransfer() { this->doReadTransferLatest(); } + void read() { this->readLatest(); } + using ArrayAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ + +/** Convenience class for output array accessors (always UpdateMode::push) */ +template <typename UserType> +struct ArrayOutput : public ArrayAccessor<UserType> { + ArrayOutput(Module *owner, const std::string &name, std::string unit, + size_t nElements, const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ArrayAccessor<UserType>( + owner, name, {VariableDirection::feeding, false}, unit, nElements, + UpdateMode::push, description, tags) {} + ArrayOutput() : ArrayAccessor<UserType>() {} + using ArrayAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ + +/** Convenience class for input array accessors with return channel ("write + * back") and UpdateMode::push */ +template <typename UserType> +struct ArrayPushInputWB : public ArrayAccessor<UserType> { + ArrayPushInputWB(Module *owner, const std::string &name, std::string unit, + size_t nElements, const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ArrayAccessor<UserType>( + owner, name, {VariableDirection::consuming, true}, unit, nElements, + UpdateMode::push, description, tags) {} + ArrayPushInputWB() : ArrayAccessor<UserType>() {} + using ArrayAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ + +/** Convenience class for output array accessors with return channel ("read + * back") (always UpdateMode::push) */ +template <typename UserType> +struct ArrayOutputRB : public ArrayAccessor<UserType> { + ArrayOutputRB(Module *owner, const std::string &name, std::string unit, + size_t nElements, const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ArrayAccessor<UserType>(owner, name, {VariableDirection::feeding, true}, + unit, nElements, UpdateMode::push, description, + tags) {} + ArrayOutputRB() : ArrayAccessor<UserType>() {} + using ArrayAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/include/ConstantAccessor.h b/include/ConstantAccessor.h index cec3a3c0..0b05a255 100644 --- a/include/ConstantAccessor.h +++ b/include/ConstantAccessor.h @@ -12,99 +12,102 @@ namespace ChimeraTK { - /** Implementation of the NDRegisterAccessor which delivers always the same value and ignors any write operations */ - template<typename UserType> - class ConstantAccessor : public ChimeraTK::SyncNDRegisterAccessor<UserType> { - - public: - - ConstantAccessor(UserType value=0, size_t length=1) - : ChimeraTK::SyncNDRegisterAccessor<UserType>("UnnamedConstantAccessor"), _value(length, value) - { - try { - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0] = _value; - } - catch(...) { - this->shutdown(); - throw; - } - } - - ~ConstantAccessor() { - this->shutdown(); - } - - void doReadTransfer() override { - if(firstRead) { - firstRead = false; - return; - } - // block forever - isInterrupted = false; - promise.get_future().wait(); - // if we get here, interrupt() has been called - throw boost::thread_interrupted(); - } - - bool doReadTransferNonBlocking() override { - if(firstRead) { - firstRead = false; - return true; - } - return false; - } - - bool doReadTransferLatest() override { - return doReadTransferNonBlocking(); - } - - void doPostRead() override { - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0] = _value; - } - - bool doWriteTransfer(ChimeraTK::VersionNumber /*versionNumber*/={}) override { - return true; - } - - bool mayReplaceOther(const boost::shared_ptr<ChimeraTK::TransferElement const>&) const override { - return false; /// @todo implement properly? - } - - bool isReadOnly() const override {return false;} - - bool isReadable() const override {return true;} - - bool isWriteable() const override {return true;} - - std::vector< boost::shared_ptr<ChimeraTK::TransferElement> > getHardwareAccessingElements() override {return{};} - - void replaceTransferElement(boost::shared_ptr<ChimeraTK::TransferElement>) override {} - - std::list<boost::shared_ptr<ChimeraTK::TransferElement> > getInternalElements() override {return {};} - - AccessModeFlags getAccessModeFlags() const override { return {}; } - - void interrupt() override { - if(isInterrupted) return; - promise.set_value(); - isInterrupted = true; - } - - VersionNumber getVersionNumber() const override { return versionNumber; } - - protected: - - std::vector<UserType> _value; - - bool firstRead{true}; - - bool isInterrupted{false}; - boost::promise<void> promise; - - VersionNumber versionNumber; - - }; +/** Implementation of the NDRegisterAccessor which delivers always the same + * value and ignors any write operations */ +template <typename UserType> +class ConstantAccessor : public ChimeraTK::SyncNDRegisterAccessor<UserType> { + +public: + ConstantAccessor(UserType value = 0, size_t length = 1) + : ChimeraTK::SyncNDRegisterAccessor<UserType>("UnnamedConstantAccessor"), + _value(length, value) { + try { + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0] = _value; + } catch (...) { + this->shutdown(); + throw; + } + } + + ~ConstantAccessor() { this->shutdown(); } + + void doReadTransfer() override { + if (firstRead) { + firstRead = false; + return; + } + // block forever + isInterrupted = false; + promise.get_future().wait(); + // if we get here, interrupt() has been called + throw boost::thread_interrupted(); + } + + bool doReadTransferNonBlocking() override { + if (firstRead) { + firstRead = false; + return true; + } + return false; + } + + bool doReadTransferLatest() override { return doReadTransferNonBlocking(); } + + void doPostRead() override { + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0] = _value; + } + + bool + doWriteTransfer(ChimeraTK::VersionNumber /*versionNumber*/ = {}) override { + return true; + } + + bool mayReplaceOther(const boost::shared_ptr<ChimeraTK::TransferElement const> + &) const override { + return false; /// @todo implement properly? + } + + bool isReadOnly() const override { return false; } + + bool isReadable() const override { return true; } + + bool isWriteable() const override { return true; } + + std::vector<boost::shared_ptr<ChimeraTK::TransferElement>> + getHardwareAccessingElements() override { + return {}; + } + + void replaceTransferElement( + boost::shared_ptr<ChimeraTK::TransferElement>) override {} + + std::list<boost::shared_ptr<ChimeraTK::TransferElement>> + getInternalElements() override { + return {}; + } + + AccessModeFlags getAccessModeFlags() const override { return {}; } + + void interrupt() override { + if (isInterrupted) + return; + promise.set_value(); + isInterrupted = true; + } + + VersionNumber getVersionNumber() const override { return versionNumber; } + +protected: + std::vector<UserType> _value; + + bool firstRead{true}; + + bool isInterrupted{false}; + boost::promise<void> promise; + + VersionNumber versionNumber; +}; } /* namespace ChimeraTK */ diff --git a/include/ConsumingFanOut.h b/include/ConsumingFanOut.h index f942cda3..5a76febd 100644 --- a/include/ConsumingFanOut.h +++ b/include/ConsumingFanOut.h @@ -14,38 +14,38 @@ namespace ChimeraTK { - /** FanOut implementation which acts as a read-only (i.e. consuming) NDRegisterAccessor. The values read through - * this accessor will be obtained from the given feeding implementation and distributed to any number of slaves. */ - template<typename UserType> - class ConsumingFanOut : public FanOut<UserType>, public ChimeraTK::NDRegisterAccessorDecorator<UserType> { - - public: - - ConsumingFanOut(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl) - : FanOut<UserType>(feedingImpl), - ChimeraTK::NDRegisterAccessorDecorator<UserType>(feedingImpl) - { - assert(feedingImpl->isReadable()); +/** FanOut implementation which acts as a read-only (i.e. consuming) + * NDRegisterAccessor. The values read through this accessor will be obtained + * from the given feeding implementation and distributed to any number of + * slaves. */ +template <typename UserType> +class ConsumingFanOut + : public FanOut<UserType>, + public ChimeraTK::NDRegisterAccessorDecorator<UserType> { + +public: + ConsumingFanOut( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl) + : FanOut<UserType>(feedingImpl), + ChimeraTK::NDRegisterAccessorDecorator<UserType>(feedingImpl) { + assert(feedingImpl->isReadable()); + } + + void doPostRead() override { + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(); + for (auto &slave : FanOut<UserType>::slaves) { // send out copies to slaves + // do not send copy if no data is expected (e.g. trigger) + if (slave->getNumberOfSamples() != 0) { + slave->accessChannel(0) = buffer_2D[0]; } + slave->write(); + } + } - void doPostRead() override { - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(); - for(auto &slave : FanOut<UserType>::slaves) { // send out copies to slaves - // do not send copy if no data is expected (e.g. trigger) - if(slave->getNumberOfSamples() != 0) { - slave->accessChannel(0) = buffer_2D[0]; - } - slave->write(); - } - } - - protected: - - using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D; - - }; +protected: + using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D; +}; } /* namespace ChimeraTK */ #endif /* CHIMERATK_CONSUMING_FAN_OUT_H */ - diff --git a/include/ControlSystemModule.h b/include/ControlSystemModule.h index dfbdf0b0..a5396ac3 100644 --- a/include/ControlSystemModule.h +++ b/include/ControlSystemModule.h @@ -12,66 +12,72 @@ #include <ChimeraTK/RegisterPath.h> -#include "VariableNetworkNode.h" #include "Module.h" +#include "VariableNetworkNode.h" namespace ChimeraTK { - class ControlSystemModule : public Module { - - public: - - /** Constructor: the optional variableNamePrefix will be prepended to all control system variable names - * (separated by a slash). */ - ControlSystemModule(const std::string& variableNamePrefix=""); - - /** Move operation with the move constructor */ - ControlSystemModule(ControlSystemModule &&other) { operator=(std::move(other)); } - - /** Move assignment */ - ControlSystemModule& operator=(ControlSystemModule &&other) { - Module::operator=(std::move(other)); - variableNamePrefix = std::move(other.variableNamePrefix); - subModules = std::move(other.subModules); - variables = std::move(other.variables); - return *this; - } - - /** The function call operator returns a VariableNetworkNode which can be used in the Application::initialise() - * function to connect the control system variable with another variable. */ - VariableNetworkNode operator()(const std::string& variableName, const std::type_info &valueType, - size_t nElements=0) const; - VariableNetworkNode operator()(const std::string& variableName) const override { - return operator()(variableName, typeid(AnyType)); - } - - void connectTo(const Module &, VariableNetworkNode ={}) const override { - throw; /// @todo make proper exception - } - - Module& operator[](const std::string& moduleName) const override; - - ModuleType getModuleType() const override { return ModuleType::ControlSystem; } - - const Module& virtualise() const override; - - std::list<VariableNetworkNode> getAccessorList() const override; - - std::list<Module*> getSubmoduleList() const override; - - protected: - - ChimeraTK::RegisterPath variableNamePrefix; - - // List of sub modules accessed through the operator[]. This is mutable since it is little more than a cache and - // thus does not change the logical state of this module - mutable std::map<std::string, ControlSystemModule> subModules; - - // List of variables accessed through the operator(). This is mutable since it is little more than a cache and - // thus does not change the logical state of this module - mutable std::map<std::string, VariableNetworkNode> variables; - - }; +class ControlSystemModule : public Module { + +public: + /** Constructor: the optional variableNamePrefix will be prepended to all + * control system variable names (separated by a slash). */ + ControlSystemModule(const std::string &variableNamePrefix = ""); + + /** Move operation with the move constructor */ + ControlSystemModule(ControlSystemModule &&other) { + operator=(std::move(other)); + } + + /** Move assignment */ + ControlSystemModule &operator=(ControlSystemModule &&other) { + Module::operator=(std::move(other)); + variableNamePrefix = std::move(other.variableNamePrefix); + subModules = std::move(other.subModules); + variables = std::move(other.variables); + return *this; + } + + /** The function call operator returns a VariableNetworkNode which can be used + * in the Application::initialise() function to connect the control system + * variable with another variable. */ + VariableNetworkNode operator()(const std::string &variableName, + const std::type_info &valueType, + size_t nElements = 0) const; + VariableNetworkNode + operator()(const std::string &variableName) const override { + return operator()(variableName, typeid(AnyType)); + } + + void connectTo(const Module &, VariableNetworkNode = {}) const override { + throw; /// @todo make proper exception + } + + Module &operator[](const std::string &moduleName) const override; + + ModuleType getModuleType() const override { + return ModuleType::ControlSystem; + } + + const Module &virtualise() const override; + + std::list<VariableNetworkNode> getAccessorList() const override; + + std::list<Module *> getSubmoduleList() const override; + +protected: + ChimeraTK::RegisterPath variableNamePrefix; + + // List of sub modules accessed through the operator[]. This is mutable since + // it is little more than a cache and thus does not change the logical state + // of this module + mutable std::map<std::string, ControlSystemModule> subModules; + + // List of variables accessed through the operator(). This is mutable since it + // is little more than a cache and thus does not change the logical state of + // this module + mutable std::map<std::string, VariableNetworkNode> variables; +}; } /* namespace ChimeraTK */ diff --git a/include/DebugPrintAccessorDecorator.h b/include/DebugPrintAccessorDecorator.h index 10ddcb36..56a7b9ad 100644 --- a/include/DebugPrintAccessorDecorator.h +++ b/include/DebugPrintAccessorDecorator.h @@ -14,67 +14,81 @@ namespace ChimeraTK { - /** Decorator of the NDRegisterAccessor which facilitates tests of the application */ - template<typename UserType> - class DebugPrintAccessorDecorator : public ChimeraTK::NDRegisterAccessorDecorator<UserType> { - public: - DebugPrintAccessorDecorator(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, - const std::string &fullyQualifiedName) +/** Decorator of the NDRegisterAccessor which facilitates tests of the + * application */ +template <typename UserType> +class DebugPrintAccessorDecorator + : public ChimeraTK::NDRegisterAccessorDecorator<UserType> { +public: + DebugPrintAccessorDecorator( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, + const std::string &fullyQualifiedName) : ChimeraTK::NDRegisterAccessorDecorator<UserType>(accessor), - _fullyQualifiedName(fullyQualifiedName) - { - std::cout << "Enable debug output for variable '" << _fullyQualifiedName << "'." << std::endl; - } - - bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber={}) override { - std::cout << "doWriteTransfer() called on '" << _fullyQualifiedName << "'." << std::endl; - return ChimeraTK::NDRegisterAccessorDecorator<UserType>::doWriteTransfer(versionNumber); - } - - void doReadTransfer() override { - std::cout << "doReadTransfer() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doReadTransfer(); - } - - bool doReadTransferNonBlocking() override { - std::cout << "doReadTransferNonBlocking() called on '" << _fullyQualifiedName << "'." << std::endl; - return ChimeraTK::NDRegisterAccessorDecorator<UserType>::doReadTransferNonBlocking(); - } - - bool doReadTransferLatest() override { - std::cout << "doReadTransferLatest() called on '" << _fullyQualifiedName << "'." << std::endl; - return ChimeraTK::NDRegisterAccessorDecorator<UserType>::doReadTransferLatest(); - } - - TransferFuture doReadTransferAsync() override { - std::cout << "doReadTransferAsync() called on '" << _fullyQualifiedName << std::endl; - return ChimeraTK::NDRegisterAccessorDecorator<UserType>::readAsync(); - } - - void doPreRead() override { - std::cout << "preRead() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreRead(); - } - - void doPostRead() override { - std::cout << "postRead() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(); - } - - void doPreWrite() override { - std::cout << "preWrite() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreWrite(); - } - - void doPostWrite() override { - std::cout << "postWrite() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostWrite(); - } - - protected: - - std::string _fullyQualifiedName; - }; + _fullyQualifiedName(fullyQualifiedName) { + std::cout << "Enable debug output for variable '" << _fullyQualifiedName + << "'." << std::endl; + } + + bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber = {}) override { + std::cout << "doWriteTransfer() called on '" << _fullyQualifiedName << "'." + << std::endl; + return ChimeraTK::NDRegisterAccessorDecorator<UserType>::doWriteTransfer( + versionNumber); + } + + void doReadTransfer() override { + std::cout << "doReadTransfer() called on '" << _fullyQualifiedName << "'." + << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doReadTransfer(); + } + + bool doReadTransferNonBlocking() override { + std::cout << "doReadTransferNonBlocking() called on '" + << _fullyQualifiedName << "'." << std::endl; + return ChimeraTK::NDRegisterAccessorDecorator< + UserType>::doReadTransferNonBlocking(); + } + + bool doReadTransferLatest() override { + std::cout << "doReadTransferLatest() called on '" << _fullyQualifiedName + << "'." << std::endl; + return ChimeraTK::NDRegisterAccessorDecorator< + UserType>::doReadTransferLatest(); + } + + TransferFuture doReadTransferAsync() override { + std::cout << "doReadTransferAsync() called on '" << _fullyQualifiedName + << std::endl; + return ChimeraTK::NDRegisterAccessorDecorator<UserType>::readAsync(); + } + + void doPreRead() override { + std::cout << "preRead() called on '" << _fullyQualifiedName << "'." + << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreRead(); + } + + void doPostRead() override { + std::cout << "postRead() called on '" << _fullyQualifiedName << "'." + << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(); + } + + void doPreWrite() override { + std::cout << "preWrite() called on '" << _fullyQualifiedName << "'." + << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreWrite(); + } + + void doPostWrite() override { + std::cout << "postWrite() called on '" << _fullyQualifiedName << "'." + << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostWrite(); + } + +protected: + std::string _fullyQualifiedName; +}; } /* namespace ChimeraTK */ diff --git a/include/DeviceModule.h b/include/DeviceModule.h index 9822ddea..a96e6c42 100644 --- a/include/DeviceModule.h +++ b/include/DeviceModule.h @@ -4,11 +4,13 @@ * \page excpetiondoc Exception Handling * \section Introduction * - * To handle expection, the current simple implementation includes two error state variables: + * To handle expection, the current simple implementation includes two error + * state variables: * - "state" (boolean flag if error occurred) * - "message" (string with error message) * - * These variables are automatically connected to the control systen in this format: + * These variables are automatically connected to the control systen in this + * format: * - /Devices/{AliasName}/message * - /Devices/{AliasName}/status * @@ -23,7 +25,8 @@ * lead to exception e.g., read or write, should be repeated after the exception * is handled. * - * Checkout testExceptionTest.cc under tests/executables_src to see how it works. + * Checkout testExceptionTest.cc under tests/executables_src to see how it + * works. */ /* @@ -36,125 +39,145 @@ #ifndef CHIMERATK_DEVICE_MODULE_H #define CHIMERATK_DEVICE_MODULE_H -#include <ChimeraTK/ForwardDeclarations.h> -#include <ChimeraTK/RegisterPath.h> -#include "VariableNetworkNode.h" +#include "ControlSystemModule.h" #include "Module.h" -#include "VirtualModule.h" -#include "VariableGroup.h" #include "ScalarAccessor.h" -#include "ControlSystemModule.h" +#include "VariableGroup.h" +#include "VariableNetworkNode.h" +#include "VirtualModule.h" +#include <ChimeraTK/ForwardDeclarations.h> +#include <ChimeraTK/RegisterPath.h> namespace ChimeraTK { - class Application; - class DeviceModule : public Module { - public: - /** Constructor: The device represented by this DeviceModule is identified by either the device alias found - * in the DMAP file or directly an URI. The given optional prefix will be prepended to all register names - * (separated by a slash). */ +class Application; +class DeviceModule : public Module { +public: + /** Constructor: The device represented by this DeviceModule is identified by + * either the device alias found in the DMAP file or directly an URI. The + * given optional prefix will be prepended to all register names + * (separated by a slash). */ + + DeviceModule(Application *application, const std::string &deviceAliasOrURI, + const std::string ®isterNamePrefix = ""); + + DeviceModule(const std::string &deviceAliasOrURI, + const std::string ®isterNamePrefix = ""); + + /** Default constructor: create dysfunctional device module */ + DeviceModule() {} + + /** Destructor */ + virtual ~DeviceModule(); + + /** Move operation with the move constructor */ + DeviceModule(DeviceModule &&other) { operator=(std::move(other)); } + + /** Move assignment */ + DeviceModule &operator=(DeviceModule &&other) { + Module::operator=(std::move(other)); + deviceAliasOrURI = std::move(other.deviceAliasOrURI); + registerNamePrefix = std::move(other.registerNamePrefix); + subModules = std::move(other.subModules); + deviceError = std::move(other.deviceError); + return *this; + } + + /** The subscript operator returns a VariableNetworkNode which can be used in + * the Application::initialise() + * function to connect the register with another variable. */ + VariableNetworkNode + operator()(const std::string ®isterName, UpdateMode mode, + const std::type_info &valueType = typeid(AnyType), + size_t nElements = 0) const; + VariableNetworkNode operator()(const std::string ®isterName, + const std::type_info &valueType, + size_t nElements = 0, + UpdateMode mode = UpdateMode::poll) const { + return operator()(registerName, mode, valueType, nElements); + } + VariableNetworkNode + operator()(const std::string &variableName) const override { + return operator()(variableName, UpdateMode::poll); + } + + Module &operator[](const std::string &moduleName) const override; + + const Module &virtualise() const override; + + void connectTo(const Module &target, + VariableNetworkNode trigger = {}) const override; + + ModuleType getModuleType() const override { return ModuleType::Device; } + + /** Use this function to report an exception. It should be called whenever a + * ChimeraTK::runtime_error has been caught when trying to interact with this + * device. This function shall not be called by the user, all exception + * handling is done internally by ApplicationCore. */ + void reportException(std::string errMsg); + + void run() override; + + void terminate() override; + + VersionNumber getCurrentVersionNumber() const override { + return currentVersionNumber; + } + + void setCurrentVersionNumber(VersionNumber versionNumber) override { + if (versionNumber > currentVersionNumber) + currentVersionNumber = versionNumber; + } + + VersionNumber currentVersionNumber; + /** This function connects DeviceError VariableGroup to ContolSystem*/ + void defineConnections() override; + +protected: + // populate virtualisedModuleFromCatalog based on the information in the + // device's catalogue + VirtualModule &virtualiseFromCatalog() const; + mutable VirtualModule virtualisedModuleFromCatalog{"INVALID", "", + ModuleType::Invalid}; + mutable bool virtualisedModuleFromCatalog_isValid{false}; + + std::string deviceAliasOrURI; + ChimeraTK::RegisterPath registerNamePrefix; + + // List of sub modules accessed through the operator[]. This is mutable since + // it is little more than a cache and thus does not change the logical state + // of this module + mutable std::map<std::string, DeviceModule> subModules; + + /** A VariableGroup for exception status and message. It can be protected, as + * it is automatically connected to the control system in + * DeviceModule::defineConnections() */ + struct DeviceError : public VariableGroup { + using VariableGroup::VariableGroup; + ScalarOutput<int> status{this, "status", "", ""}; + ScalarOutput<std::string> message{this, "message", "", ""}; + }; + DeviceError deviceError{this, "DeviceError", "Error status of the device"}; - DeviceModule( - Application* application, const std::string& deviceAliasOrURI, const std::string& registerNamePrefix = ""); - - DeviceModule(const std::string& deviceAliasOrURI, const std::string& registerNamePrefix = ""); - - /** Default constructor: create dysfunctional device module */ - DeviceModule() {} - - /** Destructor */ - virtual ~DeviceModule(); - - /** Move operation with the move constructor */ - DeviceModule(DeviceModule&& other) { operator=(std::move(other)); } - - /** Move assignment */ - DeviceModule& operator=(DeviceModule&& other) { - Module::operator=(std::move(other)); - deviceAliasOrURI = std::move(other.deviceAliasOrURI); - registerNamePrefix = std::move(other.registerNamePrefix); - subModules = std::move(other.subModules); - deviceError = std::move(other.deviceError); - return *this; - } - - /** The subscript operator returns a VariableNetworkNode which can be used in the Application::initialise() - * function to connect the register with another variable. */ - VariableNetworkNode operator()(const std::string& registerName, UpdateMode mode, - const std::type_info& valueType = typeid(AnyType), size_t nElements = 0) const; - VariableNetworkNode operator()(const std::string& registerName, const std::type_info& valueType, - size_t nElements = 0, UpdateMode mode = UpdateMode::poll) const { - return operator()(registerName, mode, valueType, nElements); - } - VariableNetworkNode operator()(const std::string& variableName) const override { - return operator()(variableName, UpdateMode::poll); - } - - Module& operator[](const std::string& moduleName) const override; - - const Module& virtualise() const override; - - void connectTo(const Module& target, VariableNetworkNode trigger = {}) const override; - - ModuleType getModuleType() const override { return ModuleType::Device; } - - /** Use this function to report an exception. It should be called whenever a ChimeraTK::runtime_error has been - * caught when trying to interact with this device. This function shall not be called by the user, all exception - * handling is done internally by ApplicationCore. */ - void reportException(std::string errMsg); - - void run() override; - - void terminate() override; - - VersionNumber getCurrentVersionNumber() const override { return currentVersionNumber; } - - void setCurrentVersionNumber(VersionNumber versionNumber) override { - if(versionNumber > currentVersionNumber) currentVersionNumber = versionNumber; - } - - VersionNumber currentVersionNumber; - /** This function connects DeviceError VariableGroup to ContolSystem*/ - void defineConnections() override; - - protected: - // populate virtualisedModuleFromCatalog based on the information in the device's catalogue - VirtualModule& virtualiseFromCatalog() const; - mutable VirtualModule virtualisedModuleFromCatalog{"INVALID", "", ModuleType::Invalid}; - mutable bool virtualisedModuleFromCatalog_isValid{false}; - - std::string deviceAliasOrURI; - ChimeraTK::RegisterPath registerNamePrefix; - - // List of sub modules accessed through the operator[]. This is mutable since it is little more than a cache and - // thus does not change the logical state of this module - mutable std::map<std::string, DeviceModule> subModules; - - /** A VariableGroup for exception status and message. It can be protected, as it is automatically connected to the - * control system in DeviceModule::defineConnections() */ - struct DeviceError : public VariableGroup { - using VariableGroup::VariableGroup; - ScalarOutput<int> status{this, "status", "", ""}; - ScalarOutput<std::string> message{this, "message", "", ""}; - }; - DeviceError deviceError{this, "DeviceError", "Error status of the device"}; - - /** The thread waiting for reportException(). It runs handleException() */ - boost::thread moduleThread; - - /** Queue used for communication between reportException() and the moduleThread. */ - cppext::future_queue<std::string> errorQueue{5}; - - /** Mutex for errorCondVar */ - std::mutex errorMutex; - - /** This condition variable is used to block reportException() until the error state has been resolved by the - * moduleThread. */ - std::condition_variable errorCondVar; + /** The thread waiting for reportException(). It runs handleException() */ + boost::thread moduleThread; - /** This functions tries to open the device and set the deviceError. Once done it notifies the waiting thread(s). - * The function is running an endless loop inside its own thread (moduleThread). */ - void handleException(); - }; + /** Queue used for communication between reportException() and the + * moduleThread. */ + cppext::future_queue<std::string> errorQueue{5}; + + /** Mutex for errorCondVar */ + std::mutex errorMutex; + + /** This condition variable is used to block reportException() until the error + * state has been resolved by the moduleThread. */ + std::condition_variable errorCondVar; + + /** This functions tries to open the device and set the deviceError. Once done + * it notifies the waiting thread(s). + * The function is running an endless loop inside its own thread + * (moduleThread). */ + void handleException(); +}; } /* namespace ChimeraTK */ diff --git a/include/EnableXMLGenerator.h b/include/EnableXMLGenerator.h index 183566e3..033f1603 100644 --- a/include/EnableXMLGenerator.h +++ b/include/EnableXMLGenerator.h @@ -4,24 +4,25 @@ * Created on: May 5, 2017 * Author: Martin Hierholzer * - * Include this header file in your main application .cc file, so the xml generator can be compiled using the - * compiler switch -DGENERATE_XML + * Include this header file in your main application .cc file, so the xml + * generator can be compiled using the compiler switch -DGENERATE_XML */ #ifndef CHIMERATK_APPLICATION_CORE_H #define CHIMERATK_APPLICATION_CORE_H -/** Compile-time switch: two executables will be created. One will generate an XML file containing the - * application's variable list. The other will be the actual control system server running the - * application. The main function for the actual control system server is coming from the respective - * ControlSystemAdapter implementation library, so inly the XML generator's main function is definde - * here. */ +/** Compile-time switch: two executables will be created. One will generate an + * XML file containing the application's variable list. The other will be the + * actual control system server running the application. The main function for + * the actual control system server is coming from the respective + * ControlSystemAdapter implementation library, so inly the XML generator's + * main function is definde here. */ #ifdef GENERATE_XML - int main(int, char **) { - ChimeraTK::Application::getInstance().generateXML(); - return 0; - } +int main(int, char **) { + ChimeraTK::Application::getInstance().generateXML(); + return 0; +} #endif /* GENERATE_XML */ diff --git a/include/EntityOwner.h b/include/EntityOwner.h index cba1b6b5..c992474d 100644 --- a/include/EntityOwner.h +++ b/include/EntityOwner.h @@ -8,184 +8,219 @@ #ifndef CHIMERATK_ENTITY_OWNER_H #define CHIMERATK_ENTITY_OWNER_H -#include <string> #include <list> +#include <string> #include "VariableNetworkNode.h" namespace ChimeraTK { - class AccessorBase; - class Module; - class VirtualModule; - - /** Base class for owners of other EntityOwners (e.g. Modules) and Accessors. - * @todo Rename this class to "Owner" and make it more generic. It should basically just implement the - * "Composite Pattern". The classes AccessorBase, Module and Owner should have a common base class called - * "Component". - */ - class EntityOwner { +class AccessorBase; +class Module; +class VirtualModule; - public: +/** Base class for owners of other EntityOwners (e.g. Modules) and Accessors. + * @todo Rename this class to "Owner" and make it more generic. It should + * basically just implement the "Composite Pattern". The classes AccessorBase, + * Module and Owner should have a common base class called "Component". + */ +class EntityOwner { - /** Constructor: register the EntityOwner with its owner */ - EntityOwner(const std::string &name, const std::string &description, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}); +public: + /** Constructor: register the EntityOwner with its owner */ + EntityOwner(const std::string &name, const std::string &description, + bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}); - /** Default constructor just for late initialisation */ - EntityOwner() + /** Default constructor just for late initialisation */ + EntityOwner() : _name("**INVALID**"), - _description("Invalid EntityOwner created by default constructor just as a place holder") - {} - - /** Virtual destructor to make the type polymorphic */ - virtual ~EntityOwner(); - - /** Move constructor */ - EntityOwner(EntityOwner &&other) { operator=(std::move(other)); } - EntityOwner(const EntityOwner &other) = delete; - - /** Move assignment operator */ - EntityOwner& operator=(EntityOwner &&other); - EntityOwner& operator=(const EntityOwner &other) = delete; - - /** Get the name of the module instance */ - const std::string& getName() const { return _name; } - - /** Get the fully qualified name of the module instance, i.e. the name containing all module names further up in - * the hierarchy. */ - virtual std::string getQualifiedName() const = 0; - - /** Get the decription of the module instance */ - const std::string& getDescription() const { return _description; } - - /** Obtain the full description including the full description of the owner. */ - virtual std::string getFullDescription() const = 0; - - /** Obtain the list of accessors/variables directly associated with this instance */ - virtual std::list<VariableNetworkNode> getAccessorList() const { return accessorList; } - - /** Obtain the list of submodules associated with this instance */ - virtual std::list<Module*> getSubmoduleList() const { return moduleList; } - - /** Obtain the list of accessors/variables associated with this instance and any submodules */ - std::list<VariableNetworkNode> getAccessorListRecursive(); - - /** Obtain the list of submodules associated with this instance and any submodules */ - std::list<Module*> getSubmoduleListRecursive(); - - /** Check whether a submodule exists by the given name (not taking into account eliminated hierarchies etc.) */ - bool hasSubmodule(const std::string &name) const; - - /** Get a submodule by the given name (not taking into account eliminated hierarchies etc.) */ - Module* getSubmodule(const std::string &name) const; - - /** Return a VirtualModule containing the part of the tree structure matching the given tag. The resulting - * VirtualModule might have virtual sub-modules, if this EntityOwner contains sub-EntityOwners with - * entities matchting the tag. "tag" is interpreted as a regular expression (see std::regex_match). */ - VirtualModule findTag(const std::string &tag) const; - - /** Return a VirtualModule containing the part of the tree structure not matching the given tag. This is - * the negation of findTag(), this function will keep those variables which findTag() would remove from the - * tree. Again, "tag" is interpreted as a regular expression (see std::regex_match). */ - VirtualModule excludeTag(const std::string &tag) const; - - /** Called inside the constructor of Accessor: adds the accessor to the list */ - void registerAccessor(VariableNetworkNode accessor) { - for(auto &tag : _tags) accessor.addTag(tag); - accessorList.push_back(accessor); - } - - /** Called inside the destructor of Accessor: removes the accessor from the list */ - void unregisterAccessor(VariableNetworkNode accessor) { - accessorList.remove(accessor); - } - - /** Register another module as a sub-mdoule. Will be called automatically by all modules in their constructors. - * If addTags is set to false, the tags of this EntityOwner will not be set to the module being registered. - * This is e.g. used in the move-constructor of Module to prevent from altering the tags in the move operation. */ - void registerModule(Module* module, bool addTags=true); - - /** Unregister another module as a sub-mdoule. Will be called automatically by all modules in their destructors. */ - void unregisterModule(Module* module); - - /** Add a tag to all Application-type nodes inside this group. It will recurse into any subgroups. See - * VariableNetworkNode::addTag() for additional information about tags. */ - void addTag(const std::string &tag); - - /** Eliminate the level of hierarchy represented by this EntityOwner. This is e.g. used when building the - * hierarchy of VirtualModules in findTag(). Eliminating one level of hierarchy will make all childs of that - * hierarchy level to appear as if there were direct childs of the next higher hierarchy level. If e.g. there is - * a variable on the third level "A.B.C" and one selects to eliminate the second level of hierarchy (e.g. calls - * B.eliminateHierarchy()), the structure would look like "A.C". This of course only affects the "dynamic" data - * model, while the static C++ model is fixed at compile time. */ - void setEliminateHierarchy() { _eliminateHierarchy = true; } - - /** Returns the flag whether this level of hierarchy should be eliminated */ - bool getEliminateHierarchy() const { return _eliminateHierarchy; } - - /** Create a VirtualModule which contains all variables of this EntityOwner in a flat hierarchy. It will recurse - * through all sub-modules and add all found variables directly to the VirtualModule. */ - VirtualModule flatten(); - - void accept(Visitor<EntityOwner>& visitor) const { visitor.dispatch(*this); } - - /** Print the full hierarchy to stdout. */ - void dump(const std::string &prefix="") const; - - /** Create Graphviz dot graph and write to file. The graph will contain the full hierarchy of modules and - * variables below (and including) this module. Each variable will also show which tags are attached to it. - * ModuleGroups will be drawn with a double line, ApplicationModules with a bold line. Hierarchies which will - * be eliminated in the dynamic information model are shown with a dotted line. */ - void dumpGraph(const std::string &fileName="graph.dot") const; - - /** Create a Graphiz dot graph similar to the one created with dumpGraph, but just show the modules and not the - * variables. This allows to get an overview over more complex applications. */ - void dumpModuleGraph(const std::string &fileName="graph.dot") const; - - enum class ModuleType { - ApplicationModule, ModuleGroup, VariableGroup, ControlSystem, Device, Invalid - }; - - /** Return the module type of this module, or in case of a VirtualModule the module type this VirtualModule was - * derived from. */ - virtual ModuleType getModuleType() const = 0; - - /** Return the current version number which has been received with the last pusy-type read operation. */ - virtual VersionNumber getCurrentVersionNumber() const = 0; + _description("Invalid EntityOwner created by default constructor just " + "as a place holder") {} + + /** Virtual destructor to make the type polymorphic */ + virtual ~EntityOwner(); + + /** Move constructor */ + EntityOwner(EntityOwner &&other) { operator=(std::move(other)); } + EntityOwner(const EntityOwner &other) = delete; + + /** Move assignment operator */ + EntityOwner &operator=(EntityOwner &&other); + EntityOwner &operator=(const EntityOwner &other) = delete; + + /** Get the name of the module instance */ + const std::string &getName() const { return _name; } + + /** Get the fully qualified name of the module instance, i.e. the name + * containing all module names further up in the hierarchy. */ + virtual std::string getQualifiedName() const = 0; + + /** Get the decription of the module instance */ + const std::string &getDescription() const { return _description; } + + /** Obtain the full description including the full description of the owner. + */ + virtual std::string getFullDescription() const = 0; + + /** Obtain the list of accessors/variables directly associated with this + * instance */ + virtual std::list<VariableNetworkNode> getAccessorList() const { + return accessorList; + } + + /** Obtain the list of submodules associated with this instance */ + virtual std::list<Module *> getSubmoduleList() const { return moduleList; } + + /** Obtain the list of accessors/variables associated with this instance and + * any submodules */ + std::list<VariableNetworkNode> getAccessorListRecursive(); + + /** Obtain the list of submodules associated with this instance and any + * submodules */ + std::list<Module *> getSubmoduleListRecursive(); + + /** Check whether a submodule exists by the given name (not taking into + * account eliminated hierarchies etc.) */ + bool hasSubmodule(const std::string &name) const; + + /** Get a submodule by the given name (not taking into account eliminated + * hierarchies etc.) */ + Module *getSubmodule(const std::string &name) const; + + /** Return a VirtualModule containing the part of the tree structure matching + * the given tag. The resulting VirtualModule might have virtual sub-modules, + * if this EntityOwner contains sub-EntityOwners with entities matchting the + * tag. "tag" is interpreted as a regular expression (see std::regex_match). + */ + VirtualModule findTag(const std::string &tag) const; + + /** Return a VirtualModule containing the part of the tree structure not + * matching the given tag. This is the negation of findTag(), this function + * will keep those variables which findTag() would remove from the tree. + * Again, "tag" is interpreted as a regular expression (see std::regex_match). + */ + VirtualModule excludeTag(const std::string &tag) const; + + /** Called inside the constructor of Accessor: adds the accessor to the list + */ + void registerAccessor(VariableNetworkNode accessor) { + for (auto &tag : _tags) + accessor.addTag(tag); + accessorList.push_back(accessor); + } + + /** Called inside the destructor of Accessor: removes the accessor from the + * list */ + void unregisterAccessor(VariableNetworkNode accessor) { + accessorList.remove(accessor); + } + + /** Register another module as a sub-mdoule. Will be called automatically by + * all modules in their constructors. If addTags is set to false, the tags of + * this EntityOwner will not be set to the module being registered. This is + * e.g. used in the move-constructor of Module to prevent from altering the + * tags in the move operation. */ + void registerModule(Module *module, bool addTags = true); + + /** Unregister another module as a sub-mdoule. Will be called automatically by + * all modules in their destructors. */ + void unregisterModule(Module *module); + + /** Add a tag to all Application-type nodes inside this group. It will recurse + * into any subgroups. See VariableNetworkNode::addTag() for additional + * information about tags. */ + void addTag(const std::string &tag); + + /** Eliminate the level of hierarchy represented by this EntityOwner. This is + * e.g. used when building the hierarchy of VirtualModules in findTag(). + * Eliminating one level of hierarchy will make all childs of that hierarchy + * level to appear as if there were direct childs of the next higher hierarchy + * level. If e.g. there is a variable on the third level "A.B.C" and one + * selects to eliminate the second level of hierarchy (e.g. calls + * B.eliminateHierarchy()), the structure would look like "A.C". This of + * course only affects the "dynamic" data + * model, while the static C++ model is fixed at compile time. */ + void setEliminateHierarchy() { _eliminateHierarchy = true; } + + /** Returns the flag whether this level of hierarchy should be eliminated */ + bool getEliminateHierarchy() const { return _eliminateHierarchy; } + + /** Create a VirtualModule which contains all variables of this EntityOwner in + * a flat hierarchy. It will recurse + * through all sub-modules and add all found variables directly to the + * VirtualModule. */ + VirtualModule flatten(); + + void accept(Visitor<EntityOwner> &visitor) const { visitor.dispatch(*this); } + + /** Print the full hierarchy to stdout. */ + void dump(const std::string &prefix = "") const; + + /** Create Graphviz dot graph and write to file. The graph will contain the + * full hierarchy of modules and variables below (and including) this module. + * Each variable will also show which tags are attached to it. ModuleGroups + * will be drawn with a double line, ApplicationModules with a bold line. + * Hierarchies which will be eliminated in the dynamic information model are + * shown with a dotted line. */ + void dumpGraph(const std::string &fileName = "graph.dot") const; + + /** Create a Graphiz dot graph similar to the one created with dumpGraph, but + * just show the modules and not the + * variables. This allows to get an overview over more complex applications. + */ + void dumpModuleGraph(const std::string &fileName = "graph.dot") const; + + enum class ModuleType { + ApplicationModule, + ModuleGroup, + VariableGroup, + ControlSystem, + Device, + Invalid + }; - /** Set the current version number. This function is called by the push-type input accessors in their read - * functions. */ - virtual void setCurrentVersionNumber(VersionNumber versionNumber) = 0; + /** Return the module type of this module, or in case of a VirtualModule the + * module type this VirtualModule was derived from. */ + virtual ModuleType getModuleType() const = 0; - protected: + /** Return the current version number which has been received with the last + * pusy-type read operation. */ + virtual VersionNumber getCurrentVersionNumber() const = 0; - /** Add the part of the tree structure matching the given tag to a VirtualModule. Users normally will use - * findTag() instead. "tag" is interpreted as a regular expression (see std::regex_match). */ - void findTagAndAppendToModule(VirtualModule &module, const std::string &tag, bool eliminateAllHierarchies=false, - bool eliminateFirstHierarchy=false, bool negate=false) const; + /** Set the current version number. This function is called by the push-type + * input accessors in their read functions. */ + virtual void setCurrentVersionNumber(VersionNumber versionNumber) = 0; - /** The name of this instance */ - std::string _name; +protected: + /** Add the part of the tree structure matching the given tag to a + * VirtualModule. Users normally will use findTag() instead. "tag" is + * interpreted as a regular expression (see std::regex_match). */ + void findTagAndAppendToModule(VirtualModule &module, const std::string &tag, + bool eliminateAllHierarchies = false, + bool eliminateFirstHierarchy = false, + bool negate = false) const; - /** The description of this instance */ - std::string _description; + /** The name of this instance */ + std::string _name; - /** List of accessors owned by this instance */ - std::list<VariableNetworkNode> accessorList; + /** The description of this instance */ + std::string _description; - /** List of modules owned by this instance */ - std::list<Module*> moduleList; + /** List of accessors owned by this instance */ + std::list<VariableNetworkNode> accessorList; - /** Flag whether this level of hierarchy should be eliminated or not */ - bool _eliminateHierarchy{false}; + /** List of modules owned by this instance */ + std::list<Module *> moduleList; - /** List of tags to be added to all accessors and modules inside this module */ - std::unordered_set<std::string> _tags; + /** Flag whether this level of hierarchy should be eliminated or not */ + bool _eliminateHierarchy{false}; - }; + /** List of tags to be added to all accessors and modules inside this module + */ + std::unordered_set<std::string> _tags; +}; } /* namespace ChimeraTK */ #endif /* CHIMERATK_ENTITY_OWNER_H */ - diff --git a/include/FanOut.h b/include/FanOut.h index 58c7d0e4..461a84eb 100644 --- a/include/FanOut.h +++ b/include/FanOut.h @@ -8,49 +8,49 @@ #ifndef CHIMERATK_FAN_OUT_H #define CHIMERATK_FAN_OUT_H -#include <ChimeraTK/NDRegisterAccessor.h> #include "VariableNetworkNode.h" +#include <ChimeraTK/NDRegisterAccessor.h> namespace ChimeraTK { - /** Base class for several implementations which distribute values from one feeder to multiple consumers */ - template<typename UserType> - class FanOut { - - public: - - FanOut(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl) - : impl(feedingImpl) - {} - - virtual ~FanOut() {} - - /** Add a slave to the FanOut. Only sending end-points of a consuming node may be added. */ - virtual void addSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, - VariableNetworkNode &/*consumer*/) { - if(!slave->isWriteable()) { - throw ChimeraTK::logic_error("FanOut::addSlave() has been called with a receiving implementation!"); - } - // check if array shape is compatible, unless the receiver is a trigger node, so no data is expected - if( slave->getNumberOfSamples() != 0 && - ( slave->getNumberOfChannels() != impl->getNumberOfChannels() || - slave->getNumberOfSamples() != impl->getNumberOfSamples() ) ) { - std::string what = "FanOut::addSlave(): Trying to add a slave '"; - what += slave->getName(); - what += "' with incompatible array shape! Name of master: "; - what += impl->getName(); - throw ChimeraTK::logic_error(what.c_str()); - } - slaves.push_back(slave); - } - - protected: - - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> impl; - - std::list<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>> slaves; - - }; +/** Base class for several implementations which distribute values from one + * feeder to multiple consumers */ +template <typename UserType> class FanOut { + +public: + FanOut(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl) + : impl(feedingImpl) {} + + virtual ~FanOut() {} + + /** Add a slave to the FanOut. Only sending end-points of a consuming node may + * be added. */ + virtual void + addSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, + VariableNetworkNode & /*consumer*/) { + if (!slave->isWriteable()) { + throw ChimeraTK::logic_error("FanOut::addSlave() has been called with a " + "receiving implementation!"); + } + // check if array shape is compatible, unless the receiver is a trigger + // node, so no data is expected + if (slave->getNumberOfSamples() != 0 && + (slave->getNumberOfChannels() != impl->getNumberOfChannels() || + slave->getNumberOfSamples() != impl->getNumberOfSamples())) { + std::string what = "FanOut::addSlave(): Trying to add a slave '"; + what += slave->getName(); + what += "' with incompatible array shape! Name of master: "; + what += impl->getName(); + throw ChimeraTK::logic_error(what.c_str()); + } + slaves.push_back(slave); + } + +protected: + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> impl; + + std::list<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>> slaves; +}; } /* namespace ChimeraTK */ diff --git a/include/FeedingFanOut.h b/include/FeedingFanOut.h index 46b8075b..7071ce3b 100644 --- a/include/FeedingFanOut.h +++ b/include/FeedingFanOut.h @@ -14,183 +14,219 @@ namespace ChimeraTK { - /** - * NDRegisterAccessor implementation which distributes values written to this accessor out to any number of slaves. - */ - template<typename UserType> - class FeedingFanOut : public FanOut<UserType>, public ChimeraTK::NDRegisterAccessor<UserType> { - - public: - - FeedingFanOut(std::string const &name, std::string const &unit, std::string const &description, - size_t numberOfElements, bool withReturn) - : FanOut<UserType>(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>()), - ChimeraTK::NDRegisterAccessor<UserType>("FeedingFanOut:"+name, unit, description), - _withReturn(withReturn) - { - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0].resize(numberOfElements); - } - - /** Add a slave to the FanOut. Only sending end-points of a consuming node may be added. */ - void addSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, VariableNetworkNode&) override { - - // check if array shape is compatible, unless the receiver is a trigger node, so no data is expected - if( slave->getNumberOfSamples() != 0 && - ( slave->getNumberOfChannels() != 1 || slave->getNumberOfSamples() != this->getNumberOfSamples() ) ) { - std::string what = "FeedingFanOut::addSlave(): Trying to add a slave '" + slave->getName(); - what += "' with incompatible array shape! Name of fan out: '" + this->getName() + "'"; - throw ChimeraTK::logic_error(what.c_str()); - } - - // make sure slave is writeable - if(!slave->isWriteable()) { - throw ChimeraTK::logic_error("FeedingFanOut::addSlave() has been called with a receiving implementation!"); - } - - // handle return channels - if(_withReturn) { - if(slave->isReadable()) { - if(_hasReturnSlave) { - throw ChimeraTK::logic_error("FeedingFanOut: Cannot add multiple slaves with return channel!"); - } - _hasReturnSlave = true; - _returnSlave = slave; - } - } - - // add the slave - FanOut<UserType>::slaves.push_back(slave); - } - - bool isReadable() const override { - return _withReturn; - } - - bool isReadOnly() const override { - return false; - } - - bool isWriteable() const override { - return true; - } - - void doReadTransfer() override { - if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); - _returnSlave->doReadTransfer(); - } - - bool doReadTransferNonBlocking() override { - if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); - return _returnSlave->doReadTransferNonBlocking(); - } - - bool doReadTransferLatest() override { - if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); - return _returnSlave->doReadTransferLatest(); - } - - void doPreRead() override { - if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); - _returnSlave->accessChannel(0).swap(ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); - _returnSlave->preRead(); - } - - void doPostRead() override { - if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); - assert(_hasReturnSlave); - _returnSlave->postRead(); - _returnSlave->accessChannel(0).swap(ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); - // distribute return-channel update to the other slaves - for(auto &slave : FanOut<UserType>::slaves) { // send out copies to slaves - if(slave == _returnSlave) continue; - if(slave->getNumberOfSamples() != 0) { // do not send copy if no data is expected (e.g. trigger) - slave->accessChannel(0) = ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]; - } - slave->write(_returnSlave->getVersionNumber()); - } - - } - - ChimeraTK::TransferFuture doReadTransferAsync() override { - if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); - return {_returnSlave->doReadTransferAsync(), this}; - } - - void doPreWrite() override { - for(auto &slave : FanOut<UserType>::slaves) { // send out copies to slaves - if(slave->getNumberOfSamples() != 0) { // do not send copy if no data is expected (e.g. trigger) - if(slave == FanOut<UserType>::slaves.front()) { // in case of first slave, swap instead of copy - slave->accessChannel(0).swap(ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); - } - else { // not the first slave: copy the data from the first slave - slave->accessChannel(0) = FanOut<UserType>::slaves.front()->accessChannel(0); - } - } - } - // pre write may only be called on the target accessors after we have filled them all, otherwise the first - // accessor might take us the data away... - for(auto &slave : FanOut<UserType>::slaves) { - slave->preWrite(); - } - } - - bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber={}) override { - bool dataLost = false; - for(auto &slave : FanOut<UserType>::slaves) { - bool ret = slave->doWriteTransfer(versionNumber); - if(ret) dataLost = true; +/** + * NDRegisterAccessor implementation which distributes values written to this + * accessor out to any number of slaves. + */ +template <typename UserType> +class FeedingFanOut : public FanOut<UserType>, + public ChimeraTK::NDRegisterAccessor<UserType> { + +public: + FeedingFanOut(std::string const &name, std::string const &unit, + std::string const &description, size_t numberOfElements, + bool withReturn) + : FanOut<UserType>( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>()), + ChimeraTK::NDRegisterAccessor<UserType>("FeedingFanOut:" + name, unit, + description), + _withReturn(withReturn) { + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0].resize( + numberOfElements); + } + + /** Add a slave to the FanOut. Only sending end-points of a consuming node may + * be added. */ + void + addSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, + VariableNetworkNode &) override { + + // check if array shape is compatible, unless the receiver is a trigger + // node, so no data is expected + if (slave->getNumberOfSamples() != 0 && + (slave->getNumberOfChannels() != 1 || + slave->getNumberOfSamples() != this->getNumberOfSamples())) { + std::string what = "FeedingFanOut::addSlave(): Trying to add a slave '" + + slave->getName(); + what += "' with incompatible array shape! Name of fan out: '" + + this->getName() + "'"; + throw ChimeraTK::logic_error(what.c_str()); + } + + // make sure slave is writeable + if (!slave->isWriteable()) { + throw ChimeraTK::logic_error("FeedingFanOut::addSlave() has been called " + "with a receiving implementation!"); + } + + // handle return channels + if (_withReturn) { + if (slave->isReadable()) { + if (_hasReturnSlave) { + throw ChimeraTK::logic_error( + "FeedingFanOut: Cannot add multiple slaves with return channel!"); } - return dataLost; - } - - void doPostWrite() override { - for(auto &slave : FanOut<UserType>::slaves) { - slave->postWrite(); + _hasReturnSlave = true; + _returnSlave = slave; + } + } + + // add the slave + FanOut<UserType>::slaves.push_back(slave); + } + + bool isReadable() const override { return _withReturn; } + + bool isReadOnly() const override { return false; } + + bool isWriteable() const override { return true; } + + void doReadTransfer() override { + if (!_withReturn) + throw ChimeraTK::logic_error( + "Read operation called on write-only variable."); + _returnSlave->doReadTransfer(); + } + + bool doReadTransferNonBlocking() override { + if (!_withReturn) + throw ChimeraTK::logic_error( + "Read operation called on write-only variable."); + return _returnSlave->doReadTransferNonBlocking(); + } + + bool doReadTransferLatest() override { + if (!_withReturn) + throw ChimeraTK::logic_error( + "Read operation called on write-only variable."); + return _returnSlave->doReadTransferLatest(); + } + + void doPreRead() override { + if (!_withReturn) + throw ChimeraTK::logic_error( + "Read operation called on write-only variable."); + _returnSlave->accessChannel(0).swap( + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); + _returnSlave->preRead(); + } + + void doPostRead() override { + if (!_withReturn) + throw ChimeraTK::logic_error( + "Read operation called on write-only variable."); + assert(_hasReturnSlave); + _returnSlave->postRead(); + _returnSlave->accessChannel(0).swap( + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); + // distribute return-channel update to the other slaves + for (auto &slave : FanOut<UserType>::slaves) { // send out copies to slaves + if (slave == _returnSlave) + continue; + if (slave->getNumberOfSamples() != + 0) { // do not send copy if no data is expected (e.g. trigger) + slave->accessChannel(0) = + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]; + } + slave->write(_returnSlave->getVersionNumber()); + } + } + + ChimeraTK::TransferFuture doReadTransferAsync() override { + if (!_withReturn) + throw ChimeraTK::logic_error( + "Read operation called on write-only variable."); + return {_returnSlave->doReadTransferAsync(), this}; + } + + void doPreWrite() override { + for (auto &slave : FanOut<UserType>::slaves) { // send out copies to slaves + if (slave->getNumberOfSamples() != + 0) { // do not send copy if no data is expected (e.g. trigger) + if (slave == + FanOut<UserType>::slaves + .front()) { // in case of first slave, swap instead of copy + slave->accessChannel(0).swap( + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); + } else { // not the first slave: copy the data from the first slave + slave->accessChannel(0) = + FanOut<UserType>::slaves.front()->accessChannel(0); } - FanOut<UserType>::slaves.front()->accessChannel(0).swap(ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); - } - - bool mayReplaceOther(const boost::shared_ptr<const ChimeraTK::TransferElement>&) const override { - return false; /// @todo implement properly? - } - - std::list<boost::shared_ptr<ChimeraTK::TransferElement> > getInternalElements() override { - return {}; /// @todo implement properly? } - - std::vector<boost::shared_ptr<ChimeraTK::TransferElement> > getHardwareAccessingElements() override { - return { boost::enable_shared_from_this<ChimeraTK::TransferElement>::shared_from_this() }; /// @todo implement properly? - } - - void replaceTransferElement(boost::shared_ptr<ChimeraTK::TransferElement>) override { - // You can't replace anything here. Just do nothing. - /// @todo implement properly? - } - - AccessModeFlags getAccessModeFlags() const override { return {AccessMode::wait_for_new_data}; } - - VersionNumber getVersionNumber() const override { return FanOut<UserType>::slaves.front()->getVersionNumber(); } - - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> getReturnSlave() { - return _returnSlave; - } - - protected: - - /// Flag whether this FeedingFanOut has a return channel. Is specified in the constructor - bool _withReturn; - - /// Used if _withReturn is true: flag whether the corresponding slave with the return channel has already been - /// added. - bool _hasReturnSlave{false}; - - /// The slave with return channel - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> _returnSlave; - - }; + } + // pre write may only be called on the target accessors after we have filled + // them all, otherwise the first accessor might take us the data away... + for (auto &slave : FanOut<UserType>::slaves) { + slave->preWrite(); + } + } + + bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber = {}) override { + bool dataLost = false; + for (auto &slave : FanOut<UserType>::slaves) { + bool ret = slave->doWriteTransfer(versionNumber); + if (ret) + dataLost = true; + } + return dataLost; + } + + void doPostWrite() override { + for (auto &slave : FanOut<UserType>::slaves) { + slave->postWrite(); + } + FanOut<UserType>::slaves.front()->accessChannel(0).swap( + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); + } + + bool mayReplaceOther(const boost::shared_ptr<const ChimeraTK::TransferElement> + &) const override { + return false; /// @todo implement properly? + } + + std::list<boost::shared_ptr<ChimeraTK::TransferElement>> + getInternalElements() override { + return {}; /// @todo implement properly? + } + + std::vector<boost::shared_ptr<ChimeraTK::TransferElement>> + getHardwareAccessingElements() override { + return {boost::enable_shared_from_this<ChimeraTK::TransferElement>:: + shared_from_this()}; /// @todo implement properly? + } + + void replaceTransferElement( + boost::shared_ptr<ChimeraTK::TransferElement>) override { + // You can't replace anything here. Just do nothing. + /// @todo implement properly? + } + + AccessModeFlags getAccessModeFlags() const override { + return {AccessMode::wait_for_new_data}; + } + + VersionNumber getVersionNumber() const override { + return FanOut<UserType>::slaves.front()->getVersionNumber(); + } + + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> getReturnSlave() { + return _returnSlave; + } + +protected: + /// Flag whether this FeedingFanOut has a return channel. Is specified in the + /// constructor + bool _withReturn; + + /// Used if _withReturn is true: flag whether the corresponding slave with the + /// return channel has already been added. + bool _hasReturnSlave{false}; + + /// The slave with return channel + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> _returnSlave; +}; } /* namespace ChimeraTK */ #endif /* CHIMERATK_FEEDING_FAN_OUT_H */ - diff --git a/include/Flags.h b/include/Flags.h index c0585ebf..f6e7715c 100644 --- a/include/Flags.h +++ b/include/Flags.h @@ -10,36 +10,40 @@ namespace ChimeraTK { - /** Struct to define the direction of variables. The main direction is defined with an enum. In addition the presence - * of a return channel is specified. */ - struct VariableDirection { - /** Enum to define directions of variables. The direction is always defined from the point-of-view of the - * owner, i.e. the application module owning the instance of the accessor in this context. */ - enum { - consuming, feeding, invalid - } dir; - - /** Presence of return channel */ - bool withReturn; - - /** Comparison */ - bool operator==(const VariableDirection &other) const { - return dir == other.dir && withReturn == other.withReturn; - } - bool operator!=(const VariableDirection &other) const { - return !operator==(other); - } - }; - - /** Enum to define the update mode of variables. */ - enum class UpdateMode { - poll, push, invalid - }; - - /** Enum to define types of VariableNetworkNode */ - enum class NodeType { - Device, ControlSystem, Application, TriggerReceiver, TriggerProvider, Constant, invalid - }; +/** Struct to define the direction of variables. The main direction is defined + * with an enum. In addition the presence of a return channel is specified. */ +struct VariableDirection { + /** Enum to define directions of variables. The direction is always defined + * from the point-of-view of the + * owner, i.e. the application module owning the instance of the accessor in + * this context. */ + enum { consuming, feeding, invalid } dir; + + /** Presence of return channel */ + bool withReturn; + + /** Comparison */ + bool operator==(const VariableDirection &other) const { + return dir == other.dir && withReturn == other.withReturn; + } + bool operator!=(const VariableDirection &other) const { + return !operator==(other); + } +}; + +/** Enum to define the update mode of variables. */ +enum class UpdateMode { poll, push, invalid }; + +/** Enum to define types of VariableNetworkNode */ +enum class NodeType { + Device, + ControlSystem, + Application, + TriggerReceiver, + TriggerProvider, + Constant, + invalid +}; } /* namespace ChimeraTK */ diff --git a/include/InternalModule.h b/include/InternalModule.h index 5a4140a9..cdf5fa44 100644 --- a/include/InternalModule.h +++ b/include/InternalModule.h @@ -14,20 +14,20 @@ namespace ChimeraTK { - /** Base class for internal modules which are created by the variable connection code - * (e.g. Application::makeConnections()). These modules have to be handled differently since the instance is - * created dynamically and thus we cannot store the plain pointer in Application::overallModuleList. */ - class InternalModule { - public: - virtual ~InternalModule(){} - - /** Activate synchronisation thread if needed */ - virtual void activate() {} - - /** Deactivate synchronisation thread if running*/ - virtual void deactivate() {} - }; - +/** Base class for internal modules which are created by the variable connection + * code (e.g. Application::makeConnections()). These modules have to be handled + * differently since the instance is created dynamically and thus we cannot + * store the plain pointer in Application::overallModuleList. */ +class InternalModule { +public: + virtual ~InternalModule() {} + + /** Activate synchronisation thread if needed */ + virtual void activate() {} + + /** Deactivate synchronisation thread if running*/ + virtual void deactivate() {} +}; } /* namespace ChimeraTK */ diff --git a/include/InversionOfControlAccessor.h b/include/InversionOfControlAccessor.h index 21b6f4fe..cfcd9b2b 100644 --- a/include/InversionOfControlAccessor.h +++ b/include/InversionOfControlAccessor.h @@ -1,9 +1,9 @@ /* -* InversionOfControlAccessor.h -* -* Created on: Sep 28, 2017 -* Author: Martin Hierholzer -*/ + * InversionOfControlAccessor.h + * + * Created on: Sep 28, 2017 + * Author: Martin Hierholzer + */ #ifndef CHIMERATK_INVERSION_OF_CONTROL_ACCESSOR_H #define CHIMERATK_INVERSION_OF_CONTROL_ACCESSOR_H @@ -17,99 +17,110 @@ namespace ChimeraTK { - /** Adds features required for inversion of control to an accessor. This is needed for both the ArrayAccessor and - * the ScalarAccessor classes, thus it uses a CRTP. */ - template< typename Derived > - class InversionOfControlAccessor { - - public: - - /** Unregister at its owner when deleting */ - ~InversionOfControlAccessor() { - if(getOwner() != nullptr) getOwner()->unregisterAccessor(node); - } - - /** Change meta data (name, unit, description and optionally tags). This function may only be used on - * Application-type nodes. If the optional argument tags is omitted, the tags will not be changed. To clear the - * tags, an empty set can be passed. */ - void setMetaData(const std::string &name, const std::string &unit, const std::string &description) { - node.setMetaData(name, unit, completeDescription(getOwner(), description)); - } - void setMetaData(const std::string &name, const std::string &unit, const std::string &description, - const std::unordered_set<std::string> &tags) { - node.setMetaData(name, unit, completeDescription(getOwner(), description), tags); - } - - /** Add a tag. Valid names for tags only contain alpha-numeric characters (i.e. no spaces and no special - * characters). */ - void addTag(const std::string &tag) { - node.addTag(tag); - } - - /** Add multiple tags. Valid names for tags only contain alpha-numeric characters (i.e. no spaces and no special - * characters). */ - void addTags(const std::unordered_set<std::string> &tags) { - for(auto &tag : tags) node.addTag(tag); - } - - /** Convert into VariableNetworkNode */ - operator VariableNetworkNode() { - return node; - } - operator const VariableNetworkNode() const { - return node; - } - - /** Connect with other node */ - VariableNetworkNode operator>>(const VariableNetworkNode &otherNode) { - return node >> otherNode; - } - - /** Replace with other accessor */ - void replace(Derived &&other) { - assert(static_cast<Derived*>(this)->_impl == nullptr && other._impl == nullptr); - if(getOwner() != nullptr) getOwner()->unregisterAccessor(node); - node = other.node; // just copies the pointer, but other will be destroyed right after this move constructor - other.node = VariableNetworkNode(); - node.setAppAccessorPointer(static_cast<Derived*>(this)); - // Note: the accessor is registered by the VariableNetworkNode, so we don't have to re-register. - } - - /** Return the owning module */ - EntityOwner* getOwner() const { return node.getOwningModule(); } - - protected: - - /// complete the description with the full description from the owner - std::string completeDescription(EntityOwner *owner, const std::string &description) { - auto ownerDescription = owner->getFullDescription(); - if(ownerDescription == "") return description; - if(description == "") return ownerDescription; - return ownerDescription + " - " + description; - } - - InversionOfControlAccessor(Module *owner, const std::string &name, VariableDirection direction, std::string unit, - size_t nElements, UpdateMode mode, const std::string &description, const std::type_info* valueType, - const std::unordered_set<std::string> &tags={}) - : node(owner, static_cast<Derived*>(this), name, direction, unit, nElements, mode, - completeDescription(owner, description), valueType, tags) - { - static_assert(std::is_base_of<InversionOfControlAccessor<Derived>, Derived>::value, - "InversionOfControlAccessor<> must be used in a curiously recurring template pattern!"); - if(name.find_first_of("/") != std::string::npos) { - throw ChimeraTK::logic_error("Accessor names must not contain slashes: '"+name+"' in module '" - +owner->getQualifiedName()+"'."); - } - owner->registerAccessor(node); - } - - /** Default constructor creates a dysfunctional accessor (to be assigned with a real accessor later) */ - InversionOfControlAccessor() {} - - VariableNetworkNode node; - - }; - -} +/** Adds features required for inversion of control to an accessor. This is + * needed for both the ArrayAccessor and the ScalarAccessor classes, thus it + * uses a CRTP. */ +template <typename Derived> class InversionOfControlAccessor { + +public: + /** Unregister at its owner when deleting */ + ~InversionOfControlAccessor() { + if (getOwner() != nullptr) + getOwner()->unregisterAccessor(node); + } + + /** Change meta data (name, unit, description and optionally tags). This + * function may only be used on Application-type nodes. If the optional + * argument tags is omitted, the tags will not be changed. To clear the + * tags, an empty set can be passed. */ + void setMetaData(const std::string &name, const std::string &unit, + const std::string &description) { + node.setMetaData(name, unit, completeDescription(getOwner(), description)); + } + void setMetaData(const std::string &name, const std::string &unit, + const std::string &description, + const std::unordered_set<std::string> &tags) { + node.setMetaData(name, unit, completeDescription(getOwner(), description), + tags); + } + + /** Add a tag. Valid names for tags only contain alpha-numeric characters + * (i.e. no spaces and no special characters). */ + void addTag(const std::string &tag) { node.addTag(tag); } + + /** Add multiple tags. Valid names for tags only contain alpha-numeric + * characters (i.e. no spaces and no special characters). */ + void addTags(const std::unordered_set<std::string> &tags) { + for (auto &tag : tags) + node.addTag(tag); + } + + /** Convert into VariableNetworkNode */ + operator VariableNetworkNode() { return node; } + operator const VariableNetworkNode() const { return node; } + + /** Connect with other node */ + VariableNetworkNode operator>>(const VariableNetworkNode &otherNode) { + return node >> otherNode; + } + + /** Replace with other accessor */ + void replace(Derived &&other) { + assert(static_cast<Derived *>(this)->_impl == nullptr && + other._impl == nullptr); + if (getOwner() != nullptr) + getOwner()->unregisterAccessor(node); + node = other.node; // just copies the pointer, but other will be destroyed + // right after this move constructor + other.node = VariableNetworkNode(); + node.setAppAccessorPointer(static_cast<Derived *>(this)); + // Note: the accessor is registered by the VariableNetworkNode, so we don't + // have to re-register. + } + + /** Return the owning module */ + EntityOwner *getOwner() const { return node.getOwningModule(); } + +protected: + /// complete the description with the full description from the owner + std::string completeDescription(EntityOwner *owner, + const std::string &description) { + auto ownerDescription = owner->getFullDescription(); + if (ownerDescription == "") + return description; + if (description == "") + return ownerDescription; + return ownerDescription + " - " + description; + } + + InversionOfControlAccessor(Module *owner, const std::string &name, + VariableDirection direction, std::string unit, + size_t nElements, UpdateMode mode, + const std::string &description, + const std::type_info *valueType, + const std::unordered_set<std::string> &tags = {}) + : node(owner, static_cast<Derived *>(this), name, direction, unit, + nElements, mode, completeDescription(owner, description), + valueType, tags) { + static_assert( + std::is_base_of<InversionOfControlAccessor<Derived>, Derived>::value, + "InversionOfControlAccessor<> must be used in a curiously recurring " + "template pattern!"); + if (name.find_first_of("/") != std::string::npos) { + throw ChimeraTK::logic_error( + "Accessor names must not contain slashes: '" + name + + "' in module '" + owner->getQualifiedName() + "'."); + } + owner->registerAccessor(node); + } + + /** Default constructor creates a dysfunctional accessor (to be assigned with + * a real accessor later) */ + InversionOfControlAccessor() {} + + VariableNetworkNode node; +}; + +} // namespace ChimeraTK #endif /* CHIMERATK_INVERSION_OF_CONTROL_ACCESSOR_H */ diff --git a/include/Module.h b/include/Module.h index b7c8ae85..bce5e9df 100644 --- a/include/Module.h +++ b/include/Module.h @@ -8,136 +8,151 @@ #ifndef CHIMERATK_MODULE_H #define CHIMERATK_MODULE_H -#include "VariableNetworkNode.h" #include "EntityOwner.h" -#include <ChimeraTK/TransferElement.h> +#include "VariableNetworkNode.h" #include <ChimeraTK/ReadAnyGroup.h> +#include <ChimeraTK/TransferElement.h> namespace ChimeraTK { - /** Base class for ApplicationModule, DeviceModule and ControlSystemModule, to have a common interface for these - * module types. */ - class Module : public EntityOwner { - - public: - - /** Constructor: register the module with its owner. If eliminateHierarchy is true, the hierarchy level - * introduced by this module will be eliminated from the "dynamic" data model (see - * EntityOwner::setEliminateHierarchy()). The tags given as the last argument are added to all variables - * in this module recursively (see EntityOwner::addTag()). */ - Module(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}); - - /** Default constructor: Allows late initialisation of modules (e.g. when creating arrays of modules). - * - * This construtor also has to be here to mitigate a bug in gcc. It is needed to allow constructor - * inheritance of modules owning other modules. This constructor will not actually be called then. - * See this bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ - Module() : EntityOwner(), _owner{nullptr} {} - - /** Destructor */ - virtual ~Module(); - - /** Move constructor */ - Module(Module &&other) { operator=(std::move(other)); } - - /** Move assignment operator */ - Module& operator=(Module &&other); - - /** Prepare the execution of the module. This function is called before any module is started (including internal - * modules like FanOuts) and before the initial values of the variables are pushed into the queues. */ - virtual void prepare() {}; - - /** Execute the module. */ - virtual void run() {}; - - /** Terminate the module. Must/will be called before destruction, if run() was called previously. */ - virtual void terminate() {}; - - /** Create a ChimeraTK::ReadAnyGroup for all readable variables in this Module. */ - ChimeraTK::ReadAnyGroup readAnyGroup(); - - /** Read all readable variables in the group. If there are push-type variables in the group, this call will block - * until all of the variables have received an update. All push-type variables are read first, the poll-type - * variables are therefore updated with the latest values upon return. */ - void readAll(); - - /** Just call readNonBlocking() on all readable variables in the group. */ - void readAllNonBlocking(); - - /** Just call readLatest() on all readable variables in the group. */ - void readAllLatest(); - - /** Just call write() on all writable variables in the group. */ - void writeAll(); - - /** Function call operator: Return VariableNetworkNode of the given variable name */ - virtual VariableNetworkNode operator()(const std::string& variableName) const = 0; - - /** Subscript operator: Return sub-module of the given name. Hierarchies will already be eliminated, if - * requested. Thus the returned reference will not point to any user-defined object but to a VirtualModule - * containing the variable structure. */ - virtual Module& operator[](const std::string& moduleName) const = 0; - - /** Return the virtual version of this module and its sub-modules, i.e. eliminate hierarchies where requested and - * apply other dynamic model changes. */ - virtual const Module& virtualise() const = 0; - virtual void defineConnections(){}; - - /** - * Connect the entire module into another module. All variables inside this module and all - * submodules are connected to the target module. All variables and submodules must have an equally - * named and typed counterpart in the target module (or the target module allows creation of - * such entities, as in case of a ControlSystemModule). The target module may contain additional - * variables or submodules, which are ignored. - * - * If an optional trigger node is specified, this trigger node is applied to all poll-type output variables - * of the target module, which are being connected during this operation, if the corresponding variable - * in this module is push-type. - */ - virtual void connectTo(const Module &target, VariableNetworkNode trigger={}) const = 0; - - std::string getQualifiedName() const override { - return ( (_owner != nullptr) ? _owner->getQualifiedName() : "" ) + "/" + _name; - } - - std::string getFullDescription() const override { - if(_owner == nullptr) return _description; - auto ownerDescription = _owner->getFullDescription(); - if(ownerDescription == "") return _description; - if(_description == "") return ownerDescription; - return ownerDescription + " - " + _description; - } - - /** Set a new owner. The caller has to take care himself that the Module gets unregistered with the old owner - * and registered with the new one. Do not use in user code! */ - void setOwner(EntityOwner *newOwner) { - _owner = newOwner; - } - - EntityOwner* getOwner() const { return _owner; } - - /** - * Explcitly add accept() method so that we can distinguish between a Module and an EntityOwner in the Visitor. - - */ - void accept(Visitor<Module>& visitor) const { visitor.dispatch(*this); } - - VersionNumber getCurrentVersionNumber() const override { - return _owner->getCurrentVersionNumber(); - } - void setCurrentVersionNumber(VersionNumber version) override { - _owner->setCurrentVersionNumber(version); - } - - - - protected: - - /** Owner of this instance */ - EntityOwner *_owner{nullptr}; - - }; +/** Base class for ApplicationModule, DeviceModule and ControlSystemModule, to + * have a common interface for these module types. */ +class Module : public EntityOwner { + +public: + /** Constructor: register the module with its owner. If eliminateHierarchy is + * true, the hierarchy level introduced by this module will be eliminated from + * the "dynamic" data model (see EntityOwner::setEliminateHierarchy()). The + * tags given as the last argument are added to all variables in this module + * recursively (see EntityOwner::addTag()). */ + Module(EntityOwner *owner, const std::string &name, + const std::string &description, bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}); + + /** Default constructor: Allows late initialisation of modules (e.g. when + * creating arrays of modules). + * + * This construtor also has to be here to mitigate a bug in gcc. It is needed + * to allow constructor inheritance of modules owning other modules. This + * constructor will not actually be called then. See this bug report: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ + Module() : EntityOwner(), _owner{nullptr} {} + + /** Destructor */ + virtual ~Module(); + + /** Move constructor */ + Module(Module &&other) { operator=(std::move(other)); } + + /** Move assignment operator */ + Module &operator=(Module &&other); + + /** Prepare the execution of the module. This function is called before any + * module is started (including internal + * modules like FanOuts) and before the initial values of the variables are + * pushed into the queues. */ + virtual void prepare(){}; + + /** Execute the module. */ + virtual void run(){}; + + /** Terminate the module. Must/will be called before destruction, if run() was + * called previously. */ + virtual void terminate(){}; + + /** Create a ChimeraTK::ReadAnyGroup for all readable variables in this + * Module. */ + ChimeraTK::ReadAnyGroup readAnyGroup(); + + /** Read all readable variables in the group. If there are push-type variables + * in the group, this call will block until all of the variables have received + * an update. All push-type variables are read first, the poll-type + * variables are therefore updated with the latest values upon return. */ + void readAll(); + + /** Just call readNonBlocking() on all readable variables in the group. */ + void readAllNonBlocking(); + + /** Just call readLatest() on all readable variables in the group. */ + void readAllLatest(); + + /** Just call write() on all writable variables in the group. */ + void writeAll(); + + /** Function call operator: Return VariableNetworkNode of the given variable + * name */ + virtual VariableNetworkNode + operator()(const std::string &variableName) const = 0; + + /** Subscript operator: Return sub-module of the given name. Hierarchies will + * already be eliminated, if requested. Thus the returned reference will not + * point to any user-defined object but to a VirtualModule containing the + * variable structure. */ + virtual Module &operator[](const std::string &moduleName) const = 0; + + /** Return the virtual version of this module and its sub-modules, i.e. + * eliminate hierarchies where requested and apply other dynamic model + * changes. */ + virtual const Module &virtualise() const = 0; + virtual void defineConnections(){}; + + /** + * Connect the entire module into another module. All variables inside this + * module and all submodules are connected to the target module. All variables + * and submodules must have an equally named and typed counterpart in the + * target module (or the target module allows creation of such entities, as in + * case of a ControlSystemModule). The target module may contain additional + * variables or submodules, which are ignored. + * + * If an optional trigger node is specified, this trigger node is applied to + * all poll-type output variables of the target module, which are being + * connected during this operation, if the corresponding variable in this + * module is push-type. + */ + virtual void connectTo(const Module &target, + VariableNetworkNode trigger = {}) const = 0; + + std::string getQualifiedName() const override { + return ((_owner != nullptr) ? _owner->getQualifiedName() : "") + "/" + + _name; + } + + std::string getFullDescription() const override { + if (_owner == nullptr) + return _description; + auto ownerDescription = _owner->getFullDescription(); + if (ownerDescription == "") + return _description; + if (_description == "") + return ownerDescription; + return ownerDescription + " - " + _description; + } + + /** Set a new owner. The caller has to take care himself that the Module gets + * unregistered with the old owner + * and registered with the new one. Do not use in user code! */ + void setOwner(EntityOwner *newOwner) { _owner = newOwner; } + + EntityOwner *getOwner() const { return _owner; } + + /** + * Explcitly add accept() method so that we can distinguish between a Module + and an EntityOwner in the Visitor. + + */ + void accept(Visitor<Module> &visitor) const { visitor.dispatch(*this); } + + VersionNumber getCurrentVersionNumber() const override { + return _owner->getCurrentVersionNumber(); + } + void setCurrentVersionNumber(VersionNumber version) override { + _owner->setCurrentVersionNumber(version); + } + +protected: + /** Owner of this instance */ + EntityOwner *_owner{nullptr}; +}; } /* namespace ChimeraTK */ diff --git a/include/ModuleGraphVisitor.h b/include/ModuleGraphVisitor.h index eec32431..c1db42e9 100644 --- a/include/ModuleGraphVisitor.h +++ b/include/ModuleGraphVisitor.h @@ -1,7 +1,7 @@ #pragma once -#include <string> #include <ostream> +#include <string> #include "Visitor.h" @@ -14,20 +14,23 @@ class Module; /** * @brief The ModuleGraphVisitor class * - * This class is responsible for generating the Graphiviz representation of the module hierarchy. + * This class is responsible for generating the Graphiviz representation of the + * module hierarchy. */ -class ModuleGraphVisitor : public Visitor<EntityOwner, Module, VariableNetworkNode> { +class ModuleGraphVisitor + : public Visitor<EntityOwner, Module, VariableNetworkNode> { public: - ModuleGraphVisitor(std::ostream& stream, bool showVariables = true); - virtual ~ModuleGraphVisitor() {} + ModuleGraphVisitor(std::ostream &stream, bool showVariables = true); + virtual ~ModuleGraphVisitor() {} + + void dispatch(const EntityOwner &owner); + void dispatch(const Module &module); + void dispatch(const VariableNetworkNode &node); - void dispatch(const EntityOwner &owner); - void dispatch(const Module &module); - void dispatch(const VariableNetworkNode &node); private: - std::ostream& _stream; - bool _showVariables; + std::ostream &_stream; + bool _showVariables; - void dumpEntityOwner(const EntityOwner &owner); + void dumpEntityOwner(const EntityOwner &owner); }; } // namespace ChimeraTK diff --git a/include/ModuleGroup.h b/include/ModuleGroup.h index 8dbd0b24..2de2aa40 100644 --- a/include/ModuleGroup.h +++ b/include/ModuleGroup.h @@ -16,44 +16,46 @@ namespace ChimeraTK { - class ModuleGroup : public ModuleImpl { - - public: - - /** Constructor: register the ModuleGroup with its owner. If eliminateHierarchy is true, the hierarchy level - * introduced by this group will be eliminated from the "dynamic" data model (see - * EntityOwner::setEliminateHierarchy()). The tags given as the last argument are added to all variables - * in this module recursively (see EntityOwner::addTag()). - * - * Note: ModuleGroups may only be owned by the Application or other ModuleGroups. */ - ModuleGroup(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}); - - /** Default constructor: Allows late initialisation of ModuleGroups (e.g. when creating arrays of - * ModuleGroups). - * - * This construtor also has to be here to mitigate a bug in gcc. It is needed to allow constructor - * inheritance of modules owning other modules. This constructor will not actually be called then. - * See this bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ - ModuleGroup() : ModuleImpl() {} - - /** Move constructor */ - ModuleGroup(ModuleGroup &&other) { operator=(std::move(other)); } - - /** Move assignment */ - ModuleGroup& operator=(ModuleGroup &&other) { - ModuleImpl::operator=(std::move(other)); - return *this; - } - - /** Destructor */ - virtual ~ModuleGroup() {}; - - ModuleType getModuleType() const override { return ModuleType::ModuleGroup; } - - }; +class ModuleGroup : public ModuleImpl { + +public: + /** Constructor: register the ModuleGroup with its owner. If + * eliminateHierarchy is true, the hierarchy level introduced by this group + * will be eliminated from the "dynamic" data model (see + * EntityOwner::setEliminateHierarchy()). The tags given as the last argument + * are added to all variables in this module recursively (see + * EntityOwner::addTag()). + * + * Note: ModuleGroups may only be owned by the Application or other + * ModuleGroups. */ + ModuleGroup(EntityOwner *owner, const std::string &name, + const std::string &description, bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}); + + /** Default constructor: Allows late initialisation of ModuleGroups (e.g. when + * creating arrays of ModuleGroups). + * + * This construtor also has to be here to mitigate a bug in gcc. It is needed + * to allow constructor inheritance of modules owning other modules. This + * constructor will not actually be called then. See this bug report: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ + ModuleGroup() : ModuleImpl() {} + + /** Move constructor */ + ModuleGroup(ModuleGroup &&other) { operator=(std::move(other)); } + + /** Move assignment */ + ModuleGroup &operator=(ModuleGroup &&other) { + ModuleImpl::operator=(std::move(other)); + return *this; + } + + /** Destructor */ + virtual ~ModuleGroup(){}; + + ModuleType getModuleType() const override { return ModuleType::ModuleGroup; } +}; } /* namespace ChimeraTK */ #endif /* CHIMERATK_MODULE_GROUP_H */ - diff --git a/include/ModuleImpl.h b/include/ModuleImpl.h index cbd2568d..4c9053e2 100644 --- a/include/ModuleImpl.h +++ b/include/ModuleImpl.h @@ -13,49 +13,50 @@ namespace ChimeraTK { - /** - * Some common implementations of a few functions in Module used by most modules (but the VirtualModule). - */ - class ModuleImpl: public Module { - - public: - - // constructor inheritances does not work due to a gcc bug!? - ModuleImpl(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}) - : Module(owner, name, description, eliminateHierarchy, tags) - {} - - ModuleImpl() : Module() {} +/** + * Some common implementations of a few functions in Module used by most + * modules (but the VirtualModule). + */ +class ModuleImpl : public Module { - /** Move constructor */ - ModuleImpl(ModuleImpl &&other) { operator=(std::move(other)); } +public: + // constructor inheritances does not work due to a gcc bug!? + ModuleImpl(EntityOwner *owner, const std::string &name, + const std::string &description, bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}) + : Module(owner, name, description, eliminateHierarchy, tags) {} - /** Move assignment operator */ - ModuleImpl& operator=(ModuleImpl &&other) { - if(other.virtualisedModule_isValid) virtualisedModule = other.virtualisedModule; - virtualisedModule_isValid = other.virtualisedModule_isValid; - Module::operator=(std::forward<ModuleImpl>(other)); - return *this; - } + ModuleImpl() : Module() {} - VariableNetworkNode operator()(const std::string& variableName) const override; + /** Move constructor */ + ModuleImpl(ModuleImpl &&other) { operator=(std::move(other)); } - Module& operator[](const std::string& moduleName) const override; + /** Move assignment operator */ + ModuleImpl &operator=(ModuleImpl &&other) { + if (other.virtualisedModule_isValid) + virtualisedModule = other.virtualisedModule; + virtualisedModule_isValid = other.virtualisedModule_isValid; + Module::operator=(std::forward<ModuleImpl>(other)); + return *this; + } - void connectTo(const Module &target, VariableNetworkNode trigger={}) const override; + VariableNetworkNode + operator()(const std::string &variableName) const override; - const Module& virtualise() const override; + Module &operator[](const std::string &moduleName) const override; - protected: + void connectTo(const Module &target, + VariableNetworkNode trigger = {}) const override; - /// Cached return value of virtualise(). Caching is required since virtualise() returns a reference. - mutable VirtualModule virtualisedModule{"INVALID", "", ModuleType::Invalid}; - mutable bool virtualisedModule_isValid{false}; + const Module &virtualise() const override; - }; +protected: + /// Cached return value of virtualise(). Caching is required since + /// virtualise() returns a reference. + mutable VirtualModule virtualisedModule{"INVALID", "", ModuleType::Invalid}; + mutable bool virtualisedModule_isValid{false}; +}; } /* namespace ChimeraTK */ #endif /* CHIMERATK_MODULE_IMPL_H */ - diff --git a/include/Profiler.h b/include/Profiler.h index e45c1bc5..e0d13071 100644 --- a/include/Profiler.h +++ b/include/Profiler.h @@ -8,104 +8,105 @@ #ifndef CHIMERATK_PROFILER_H #define CHIMERATK_PROFILER_H -#include <string> +#include <assert.h> #include <atomic> #include <chrono> #include <list> #include <mutex> -#include <assert.h> +#include <string> namespace ChimeraTK { - class Profiler { - - public: - - class ThreadData { - - public: - - /** Return the name of the thread */ - const std::string& getName() const { return name; } - - /** Return the integrated active time of the thread in microseconds. */ - uint64_t getIntegratedTime() const { return integratedTime; } - - /** Return the integrated active time of the thread in microseconds and atomically reset the counter to 0. */ - uint64_t getAndResetIntegratedTime() { - uint64_t time = integratedTime; - integratedTime.fetch_sub(time); - return time; - } - - private: - - friend class Profiler; - - /** Copy of Application::threadName(), stored here to make it accessible outside the thread */ - std::string name; - - /** Reference point for the time measurement */ - std::chrono::high_resolution_clock::time_point lastActiated; - - /** Flag whether this thread is currently active */ - bool isActive{false}; - - /** Integrated time this thread was active in microseconds */ - std::atomic<uint64_t> integratedTime; - - }; +class Profiler { - /** Register a thread in the profiler. This function must be called in each thread before calling - * startMeasurement() and stopMeasurement() in the same thread. The function must not be called twice in the - * same thread. The call to this function implicitly triggers starting the time measurement - * (see startMeasurement()) */ - static void registerThread(const std::string &name) { - getThreadData().name = name; - std::lock_guard<std::mutex> lock(threadDataList_mutex); - threadDataList.emplace_back(&getThreadData()); - startMeasurement(); - } +public: + class ThreadData { - /** Obtain a list of ThreadData references for all threads registered with the profiler. */ - static const std::list<ThreadData*>& getDataList() { - return threadDataList; - } + public: + /** Return the name of the thread */ + const std::string &getName() const { return name; } - /** Start the time measurement for the current thread. Call this immediately after the thread woke up e.g. from - * blocking read. */ - static void startMeasurement() { - if(getThreadData().isActive) return; - getThreadData().isActive = true; - getThreadData().lastActiated = std::chrono::high_resolution_clock::now(); - } + /** Return the integrated active time of the thread in microseconds. */ + uint64_t getIntegratedTime() const { return integratedTime; } - /** Stop the time measurement for the current thread. Call this right before putting the thread to sleep e.g. - * before a blocking read. */ - static void stopMeasurement() { - if(!getThreadData().isActive) return; - getThreadData().isActive = false; - auto duration = std::chrono::high_resolution_clock::now() - getThreadData().lastActiated; - getThreadData().integratedTime += std::chrono::duration_cast<std::chrono::microseconds>(duration).count(); - } + /** Return the integrated active time of the thread in microseconds and + * atomically reset the counter to 0. */ + uint64_t getAndResetIntegratedTime() { + uint64_t time = integratedTime; + integratedTime.fetch_sub(time); + return time; + } - private: + private: + friend class Profiler; - /** Return the ThreadData object associated with the current thread. */ - static ThreadData& getThreadData() { - thread_local static ThreadData data; - return data; - } + /** Copy of Application::threadName(), stored here to make it accessible + * outside the thread */ + std::string name; - /** List of ThreadData references registered with the profiler. */ - static std::list<ThreadData*> threadDataList; + /** Reference point for the time measurement */ + std::chrono::high_resolution_clock::time_point lastActiated; - /** Mutex for write access to the threadDataList member. Access to existing list entries through the public - * member functions of ThreadData is allowed without holding this mutex. */ - static std::mutex threadDataList_mutex; + /** Flag whether this thread is currently active */ + bool isActive{false}; + /** Integrated time this thread was active in microseconds */ + std::atomic<uint64_t> integratedTime; }; -} + /** Register a thread in the profiler. This function must be called in each + * thread before calling startMeasurement() and stopMeasurement() in the same + * thread. The function must not be called twice in the same thread. The call + * to this function implicitly triggers starting the time measurement (see + * startMeasurement()) */ + static void registerThread(const std::string &name) { + getThreadData().name = name; + std::lock_guard<std::mutex> lock(threadDataList_mutex); + threadDataList.emplace_back(&getThreadData()); + startMeasurement(); + } + + /** Obtain a list of ThreadData references for all threads registered with the + * profiler. */ + static const std::list<ThreadData *> &getDataList() { return threadDataList; } + + /** Start the time measurement for the current thread. Call this immediately + * after the thread woke up e.g. from blocking read. */ + static void startMeasurement() { + if (getThreadData().isActive) + return; + getThreadData().isActive = true; + getThreadData().lastActiated = std::chrono::high_resolution_clock::now(); + } + + /** Stop the time measurement for the current thread. Call this right before + * putting the thread to sleep e.g. before a blocking read. */ + static void stopMeasurement() { + if (!getThreadData().isActive) + return; + getThreadData().isActive = false; + auto duration = std::chrono::high_resolution_clock::now() - + getThreadData().lastActiated; + getThreadData().integratedTime += + std::chrono::duration_cast<std::chrono::microseconds>(duration).count(); + } + +private: + /** Return the ThreadData object associated with the current thread. */ + static ThreadData &getThreadData() { + thread_local static ThreadData data; + return data; + } + + /** List of ThreadData references registered with the profiler. */ + static std::list<ThreadData *> threadDataList; + + /** Mutex for write access to the threadDataList member. Access to existing + * list entries through the public member functions of ThreadData is allowed + * without holding this mutex. */ + static std::mutex threadDataList_mutex; +}; + +} // namespace ChimeraTK #endif /* CHIMERATK_PROFILER_H */ diff --git a/include/ScalarAccessor.h b/include/ScalarAccessor.h index e2d1d6d3..ee4106fa 100644 --- a/include/ScalarAccessor.h +++ b/include/ScalarAccessor.h @@ -1,9 +1,9 @@ /* -* ScalarAccessor.h -* -* Created on: Jun 07, 2016 -* Author: Martin Hierholzer -*/ + * ScalarAccessor.h + * + * Created on: Jun 07, 2016 + * Author: Martin Hierholzer + */ #ifndef CHIMERATK_SCALAR_ACCESSOR_H #define CHIMERATK_SCALAR_ACCESSOR_H @@ -15,139 +15,154 @@ #include <ChimeraTK/ScalarRegisterAccessor.h> +#include "Application.h" #include "InversionOfControlAccessor.h" #include "Profiler.h" -#include "Application.h" namespace ChimeraTK { - /********************************************************************************************************************/ - - /** Accessor for scalar variables (i.e. single values). Note for users: Use the convenience classes - * ScalarPollInput, ScalarPushInput, ScalarOutput instead of this class directly. */ - template< typename UserType > - class ScalarAccessor : public ChimeraTK::ScalarRegisterAccessor<UserType>, - public InversionOfControlAccessor<ScalarAccessor<UserType>> { - public: - - using InversionOfControlAccessor<ScalarAccessor<UserType>>::operator VariableNetworkNode; - using InversionOfControlAccessor<ScalarAccessor<UserType>>::operator>>; - void replace(const ChimeraTK::NDRegisterAccessorAbstractor<UserType> &newAccessor) = delete; - using InversionOfControlAccessor<ScalarAccessor<UserType>>::replace; - ScalarAccessor<UserType>& operator=(ScalarAccessor<UserType> &other) = delete; - using ChimeraTK::ScalarRegisterAccessor<UserType>::operator=; - - /** Move constructor */ - ScalarAccessor(ScalarAccessor<UserType> &&other) { - InversionOfControlAccessor<ScalarAccessor<UserType>>::replace(std::move(other)); - } - - /** Move assignment. */ - ScalarAccessor<UserType>& operator=(ScalarAccessor<UserType> &&other) { - // Having a move-assignment operator is required to use the move-assignment operator of a module containing - // an accessor. - InversionOfControlAccessor<ScalarAccessor<UserType>>::replace(std::move(other)); - return *this; - } - - bool write(ChimeraTK::VersionNumber versionNumber) = delete; - - bool write() { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - bool dataLoss = ChimeraTK::ScalarRegisterAccessor<UserType>::write(versionNumber); - if(dataLoss) Application::incrementDataLossCounter(); - return dataLoss; - } - - protected: - - friend class InversionOfControlAccessor<ScalarAccessor<UserType>>; - - ScalarAccessor(Module *owner, const std::string &name, VariableDirection direction, std::string unit, - UpdateMode mode, const std::string &description, - const std::unordered_set<std::string> &tags={}) - : InversionOfControlAccessor<ScalarAccessor<UserType>>(owner, name, direction, unit, 1, mode, - description, &typeid(UserType), tags) - {} - - /** Default constructor creates a dysfunctional accessor (to be assigned with a real accessor later) */ - ScalarAccessor() {} - - }; - - /********************************************************************************************************************/ - - /** Convenience class for input scalar accessors with UpdateMode::push */ - template< typename UserType > - struct ScalarPushInput : public ScalarAccessor<UserType> { - ScalarPushInput(Module *owner, const std::string &name, std::string unit, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ScalarAccessor<UserType>(owner, name, {VariableDirection::consuming, false}, unit, UpdateMode::push, - description, tags) - {} - ScalarPushInput() : ScalarAccessor<UserType>() {} - using ScalarAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ - - /** Convenience class for input scalar accessors with UpdateMode::poll */ - template< typename UserType > - struct ScalarPollInput : public ScalarAccessor<UserType> { - ScalarPollInput(Module *owner, const std::string &name, std::string unit, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ScalarAccessor<UserType>(owner, name, {VariableDirection::consuming, false}, unit, UpdateMode::poll, - description, tags) - {} - ScalarPollInput() : ScalarAccessor<UserType>() {} - void doReadTransfer() { this->doReadTransferLatest(); } - void read() { this->readLatest(); } - using ScalarAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ - - /** Convenience class for output scalar accessors (always UpdateMode::push) */ - template< typename UserType > - struct ScalarOutput : public ScalarAccessor<UserType> { - ScalarOutput(Module *owner, const std::string &name, std::string unit, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ScalarAccessor<UserType>(owner, name, {VariableDirection::feeding, false}, unit, UpdateMode::push, - description, tags) - {} - ScalarOutput() : ScalarAccessor<UserType>() {} - using ScalarAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ - - /** Convenience class for input scalar accessors with return channel ("write back") and UpdateMode::push */ - template< typename UserType > - struct ScalarPushInputWB : public ScalarAccessor<UserType> { - ScalarPushInputWB(Module *owner, const std::string &name, std::string unit, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ScalarAccessor<UserType>(owner, name, {VariableDirection::consuming, true}, unit, UpdateMode::push, - description, tags) - {} - ScalarPushInputWB() : ScalarAccessor<UserType>() {} - using ScalarAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ - - /** Convenience class for output scalar accessors with return channel ("read back") (always UpdateMode::push) */ - template< typename UserType > - struct ScalarOutputPushRB : public ScalarAccessor<UserType> { - ScalarOutputPushRB(Module *owner, const std::string &name, std::string unit, - const std::string &description, const std::unordered_set<std::string> &tags={}) - : ScalarAccessor<UserType>(owner, name, {VariableDirection::feeding, true}, unit, UpdateMode::push, - description, tags) - {} - ScalarOutputPushRB() : ScalarAccessor<UserType>() {} - using ScalarAccessor<UserType>::operator=; - }; - - /********************************************************************************************************************/ +/********************************************************************************************************************/ + +/** Accessor for scalar variables (i.e. single values). Note for users: Use the + * convenience classes + * ScalarPollInput, ScalarPushInput, ScalarOutput instead of this class + * directly. */ +template <typename UserType> +class ScalarAccessor + : public ChimeraTK::ScalarRegisterAccessor<UserType>, + public InversionOfControlAccessor<ScalarAccessor<UserType>> { +public: + using InversionOfControlAccessor<ScalarAccessor<UserType>>:: + operator VariableNetworkNode; + using InversionOfControlAccessor<ScalarAccessor<UserType>>::operator>>; + void replace(const ChimeraTK::NDRegisterAccessorAbstractor<UserType> + &newAccessor) = delete; + using InversionOfControlAccessor<ScalarAccessor<UserType>>::replace; + ScalarAccessor<UserType> &operator=(ScalarAccessor<UserType> &other) = delete; + using ChimeraTK::ScalarRegisterAccessor<UserType>::operator=; + + /** Move constructor */ + ScalarAccessor(ScalarAccessor<UserType> &&other) { + InversionOfControlAccessor<ScalarAccessor<UserType>>::replace( + std::move(other)); + } + + /** Move assignment. */ + ScalarAccessor<UserType> &operator=(ScalarAccessor<UserType> &&other) { + // Having a move-assignment operator is required to use the move-assignment + // operator of a module containing an accessor. + InversionOfControlAccessor<ScalarAccessor<UserType>>::replace( + std::move(other)); + return *this; + } + + bool write(ChimeraTK::VersionNumber versionNumber) = delete; + + bool write() { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + bool dataLoss = + ChimeraTK::ScalarRegisterAccessor<UserType>::write(versionNumber); + if (dataLoss) + Application::incrementDataLossCounter(); + return dataLoss; + } + +protected: + friend class InversionOfControlAccessor<ScalarAccessor<UserType>>; + + ScalarAccessor(Module *owner, const std::string &name, + VariableDirection direction, std::string unit, UpdateMode mode, + const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : InversionOfControlAccessor<ScalarAccessor<UserType>>( + owner, name, direction, unit, 1, mode, description, + &typeid(UserType), tags) {} + + /** Default constructor creates a dysfunctional accessor (to be assigned with + * a real accessor later) */ + ScalarAccessor() {} +}; + +/********************************************************************************************************************/ + +/** Convenience class for input scalar accessors with UpdateMode::push */ +template <typename UserType> +struct ScalarPushInput : public ScalarAccessor<UserType> { + ScalarPushInput(Module *owner, const std::string &name, std::string unit, + const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ScalarAccessor<UserType>(owner, name, + {VariableDirection::consuming, false}, unit, + UpdateMode::push, description, tags) {} + ScalarPushInput() : ScalarAccessor<UserType>() {} + using ScalarAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ + +/** Convenience class for input scalar accessors with UpdateMode::poll */ +template <typename UserType> +struct ScalarPollInput : public ScalarAccessor<UserType> { + ScalarPollInput(Module *owner, const std::string &name, std::string unit, + const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ScalarAccessor<UserType>(owner, name, + {VariableDirection::consuming, false}, unit, + UpdateMode::poll, description, tags) {} + ScalarPollInput() : ScalarAccessor<UserType>() {} + void doReadTransfer() { this->doReadTransferLatest(); } + void read() { this->readLatest(); } + using ScalarAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ + +/** Convenience class for output scalar accessors (always UpdateMode::push) */ +template <typename UserType> +struct ScalarOutput : public ScalarAccessor<UserType> { + ScalarOutput(Module *owner, const std::string &name, std::string unit, + const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ScalarAccessor<UserType>(owner, name, + {VariableDirection::feeding, false}, unit, + UpdateMode::push, description, tags) {} + ScalarOutput() : ScalarAccessor<UserType>() {} + using ScalarAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ + +/** Convenience class for input scalar accessors with return channel ("write + * back") and UpdateMode::push */ +template <typename UserType> +struct ScalarPushInputWB : public ScalarAccessor<UserType> { + ScalarPushInputWB(Module *owner, const std::string &name, std::string unit, + const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ScalarAccessor<UserType>(owner, name, + {VariableDirection::consuming, true}, unit, + UpdateMode::push, description, tags) {} + ScalarPushInputWB() : ScalarAccessor<UserType>() {} + using ScalarAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ + +/** Convenience class for output scalar accessors with return channel ("read + * back") (always UpdateMode::push) */ +template <typename UserType> +struct ScalarOutputPushRB : public ScalarAccessor<UserType> { + ScalarOutputPushRB(Module *owner, const std::string &name, std::string unit, + const std::string &description, + const std::unordered_set<std::string> &tags = {}) + : ScalarAccessor<UserType>(owner, name, + {VariableDirection::feeding, true}, unit, + UpdateMode::push, description, tags) {} + ScalarOutputPushRB() : ScalarAccessor<UserType>() {} + using ScalarAccessor<UserType>::operator=; +}; + +/********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/include/SupportedUserTypes.h b/include/SupportedUserTypes.h index be45dcbc..041e4cab 100644 --- a/include/SupportedUserTypes.h +++ b/include/SupportedUserTypes.h @@ -10,33 +10,31 @@ namespace ChimeraTK { - /** Map of UserType to value of the UserType. */ - typedef boost::fusion::map< - boost::fusion::pair<int8_t,int8_t>, - boost::fusion::pair<uint8_t,uint8_t>, - boost::fusion::pair<int16_t,int16_t>, - boost::fusion::pair<uint16_t,uint16_t>, - boost::fusion::pair<int32_t,int32_t>, - boost::fusion::pair<uint32_t,uint32_t>, - boost::fusion::pair<float,float>, - boost::fusion::pair<double,double> - > ApplicationCoreUserTypeMap; +/** Map of UserType to value of the UserType. */ +typedef boost::fusion::map< + boost::fusion::pair<int8_t, int8_t>, boost::fusion::pair<uint8_t, uint8_t>, + boost::fusion::pair<int16_t, int16_t>, + boost::fusion::pair<uint16_t, uint16_t>, + boost::fusion::pair<int32_t, int32_t>, + boost::fusion::pair<uint32_t, uint32_t>, boost::fusion::pair<float, float>, + boost::fusion::pair<double, double>> + ApplicationCoreUserTypeMap; - /** Map of UserType to a class template with the UserType as template argument. */ - template< template<typename> class TemplateClass > - class ApplicationCoreTemplateUserTypeMap { - public: - boost::fusion::map< - boost::fusion::pair<int8_t, TemplateClass<int8_t> >, - boost::fusion::pair<uint8_t, TemplateClass<uint8_t> >, - boost::fusion::pair<int16_t, TemplateClass<int16_t> >, - boost::fusion::pair<uint16_t, TemplateClass<uint16_t> >, - boost::fusion::pair<int32_t, TemplateClass<int32_t> >, - boost::fusion::pair<uint32_t, TemplateClass<uint32_t> >, - boost::fusion::pair<float, TemplateClass<float> >, - boost::fusion::pair<double, TemplateClass<double> > - > table; - }; +/** Map of UserType to a class template with the UserType as template argument. + */ +template <template <typename> class TemplateClass> +class ApplicationCoreTemplateUserTypeMap { +public: + boost::fusion::map<boost::fusion::pair<int8_t, TemplateClass<int8_t>>, + boost::fusion::pair<uint8_t, TemplateClass<uint8_t>>, + boost::fusion::pair<int16_t, TemplateClass<int16_t>>, + boost::fusion::pair<uint16_t, TemplateClass<uint16_t>>, + boost::fusion::pair<int32_t, TemplateClass<int32_t>>, + boost::fusion::pair<uint32_t, TemplateClass<uint32_t>>, + boost::fusion::pair<float, TemplateClass<float>>, + boost::fusion::pair<double, TemplateClass<double>>> + table; +}; } /* namespace ChimeraTK */ diff --git a/include/TestFacility.h b/include/TestFacility.h index d2824cea..9d321027 100644 --- a/include/TestFacility.h +++ b/include/TestFacility.h @@ -11,162 +11,183 @@ #include <boost/fusion/include/at_key.hpp> #include <ChimeraTK/ControlSystemAdapter/ControlSystemPVManager.h> -#include <ChimeraTK/ScalarRegisterAccessor.h> #include <ChimeraTK/OneDRegisterAccessor.h> +#include <ChimeraTK/ScalarRegisterAccessor.h> #include "Application.h" #include "TestableModeAccessorDecorator.h" namespace ChimeraTK { - /** Helper class to facilitate tests of applications based on ApplicationCore */ - class TestFacility { - - public: - - /** The constructor will internally obtain the instance of the application, so the instance of the TestFacility - * must not be created before the application (i.e. usually not before the main() routine). The application will - * automatically be put into the testable mode and initialised. */ - TestFacility() { - auto pvManagers = createPVManager(); - pvManager = pvManagers.first; - Application::getInstance().setPVManager(pvManagers.second); - Application::getInstance().enableTestableMode(); - Application::getInstance().initialise(); - } - - /** Start the application. This simply calls Application::run(). Since the application is in testable mode, it - * will be ??? TODO define precisely what happens on start up */ - void runApplication() const { - Application::getInstance().run(); - Application::registerThread("TestThread"); - } - - /** Perform a "step" of the application. This runs the application until all input provided to it has been - * processed and all application modules wait for new data in blocking read calls. This function returns only - * after the application has reached that stated and was paused again. After returning from this function, - * the result can be checked and new data can be provided to the application. The new data will not be - * processed until the next call to step(). */ - void stepApplication() const { - Application::getInstance().stepApplication(); - } - - /** Obtain a scalar process variable from the application, which is published to the control system. */ - template<typename T> - ChimeraTK::ScalarRegisterAccessor<T> getScalar(const ChimeraTK::RegisterPath &name) const { - - // check for existing accessor in cache - if(boost::fusion::at_key<T>(scalarMap.table).count(name) > 0) { - return boost::fusion::at_key<T>(scalarMap.table)[name]; - } - - // obtain accessor from ControlSystemPVManager - auto pv = pvManager->getProcessArray<T>(name); - if(pv == nullptr) { - throw ChimeraTK::logic_error("Process variable '"+name+"' does not exist."); - } - - // obtain variable id from pvIdMap and transfer it to idMap (required by the TestableModeAccessorDecorator) - size_t varId = Application::getInstance().pvIdMap[pv->getUniqueId()]; - - // decorate with TestableModeAccessorDecorator if variable is sender and receiver is not poll-type, - // and store it in cache - if(pv->isWriteable() && !Application::getInstance().testableMode_isPollMode[varId]) { - auto deco = boost::make_shared<TestableModeAccessorDecorator<T>>(pv, false, true, varId, varId); - Application::getInstance().testableMode_names[varId] = "ControlSystem:"+name; - boost::fusion::at_key<T>(scalarMap.table)[name].replace(ChimeraTK::ScalarRegisterAccessor<T>(deco)); - } - else { - boost::fusion::at_key<T>(scalarMap.table)[name].replace(ChimeraTK::ScalarRegisterAccessor<T>(pv)); - } - - // return the accessor as stored in the cache - return boost::fusion::at_key<T>(scalarMap.table)[name]; - } - - /** Obtain an array-type process variable from the application, which is published to the control system. */ - template<typename T> - ChimeraTK::OneDRegisterAccessor<T> getArray(const ChimeraTK::RegisterPath &name) const { - - // check for existing accessor in cache - if(boost::fusion::at_key<T>(arrayMap.table).count(name) > 0) { - return boost::fusion::at_key<T>(arrayMap.table)[name]; - } - - // obtain accessor from ControlSystemPVManager - auto pv = pvManager->getProcessArray<T>(name); - if(pv == nullptr) { - throw ChimeraTK::logic_error("Process variable '"+name+"' does not exist."); - } - - // obtain variable id from pvIdMap and transfer it to idMap (required by the TestableModeAccessorDecorator) - size_t varId = Application::getInstance().pvIdMap[pv->getUniqueId()]; - - // decorate with TestableModeAccessorDecorator if variable is sender and receiver is not poll-type, - // and store it in cache - if(pv->isWriteable() && !Application::getInstance().testableMode_isPollMode[varId]) { - auto deco = boost::make_shared<TestableModeAccessorDecorator<T>>(pv, false, true, varId, varId); - Application::getInstance().testableMode_names[varId] = "ControlSystem:"+name; - boost::fusion::at_key<T>(arrayMap.table)[name].replace(ChimeraTK::OneDRegisterAccessor<T>(deco)); - } - else { - boost::fusion::at_key<T>(arrayMap.table)[name].replace(ChimeraTK::OneDRegisterAccessor<T>(pv)); - } - - // return the accessor as stored in the cache - return boost::fusion::at_key<T>(arrayMap.table)[name]; - } - - /** Convenience function to write a scalar process variable in a single call */ - template<typename TYPE> - void writeScalar( const std::string &name, const TYPE value ) { - auto acc = getScalar<TYPE>(name); - acc = value; - acc.write(); - } - - /** Convenience function to write an array process variable in a single call */ - template<typename TYPE> - void writeArray( const std::string &name, const std::vector<TYPE> &value ) { - auto acc = getArray<TYPE>(name); - acc = value; - acc.write(); - } - - /** Convenience function to read the latest value of a scalar process variable in a single call */ - template<typename TYPE> - TYPE readScalar( const std::string &name ) { - auto acc = getScalar<TYPE>(name); - acc.readLatest(); - return acc; - } - - /** Convenience function to read the latest value of an array process variable in a single call */ - template<typename TYPE> - std::vector<TYPE> readArray( const std::string &name ) { - auto acc = getArray<TYPE>(name); - acc.readLatest(); - return acc; - } - - - protected: - - boost::shared_ptr<ControlSystemPVManager> pvManager; - - // Cache (possible decorated) accessors to avoid the need to create accessors multiple times. This would not work - // if the accessor is decorated, since the buffer would be lost and thus the current value could no longer be - // obtained. This has to be done separately for scalar and array accessors and in dependence of the user type. - // Since this is a cache and does not change the logical behaviour of the class, the maps are defined mutable. - template<typename UserType> - using ScalarMap = std::map<std::string, ChimeraTK::ScalarRegisterAccessor<UserType>>; - mutable ChimeraTK::TemplateUserTypeMap<ScalarMap> scalarMap; - - template<typename UserType> - using ArrayMap = std::map<std::string, ChimeraTK::OneDRegisterAccessor<UserType>>; - mutable ChimeraTK::TemplateUserTypeMap<ArrayMap> arrayMap; - - }; +/** Helper class to facilitate tests of applications based on ApplicationCore */ +class TestFacility { + +public: + /** The constructor will internally obtain the instance of the application, so + * the instance of the TestFacility must not be created before the application + * (i.e. usually not before the main() routine). The application will + * automatically be put into the testable mode and initialised. */ + TestFacility() { + auto pvManagers = createPVManager(); + pvManager = pvManagers.first; + Application::getInstance().setPVManager(pvManagers.second); + Application::getInstance().enableTestableMode(); + Application::getInstance().initialise(); + } + + /** Start the application. This simply calls Application::run(). Since the + * application is in testable mode, it + * will be ??? TODO define precisely what happens on start up */ + void runApplication() const { + Application::getInstance().run(); + Application::registerThread("TestThread"); + } + + /** Perform a "step" of the application. This runs the application until all + * input provided to it has been processed and all application modules wait + * for new data in blocking read calls. This function returns only after the + * application has reached that stated and was paused again. After returning + * from this function, the result can be checked and new data can be provided + * to the application. The new data will not be + * processed until the next call to step(). */ + void stepApplication() const { Application::getInstance().stepApplication(); } + + /** Obtain a scalar process variable from the application, which is published + * to the control system. */ + template <typename T> + ChimeraTK::ScalarRegisterAccessor<T> + getScalar(const ChimeraTK::RegisterPath &name) const { + + // check for existing accessor in cache + if (boost::fusion::at_key<T>(scalarMap.table).count(name) > 0) { + return boost::fusion::at_key<T>(scalarMap.table)[name]; + } + + // obtain accessor from ControlSystemPVManager + auto pv = pvManager->getProcessArray<T>(name); + if (pv == nullptr) { + throw ChimeraTK::logic_error("Process variable '" + name + + "' does not exist."); + } + + // obtain variable id from pvIdMap and transfer it to idMap (required by the + // TestableModeAccessorDecorator) + size_t varId = Application::getInstance().pvIdMap[pv->getUniqueId()]; + + // decorate with TestableModeAccessorDecorator if variable is sender and + // receiver is not poll-type, and store it in cache + if (pv->isWriteable() && + !Application::getInstance().testableMode_isPollMode[varId]) { + auto deco = boost::make_shared<TestableModeAccessorDecorator<T>>( + pv, false, true, varId, varId); + Application::getInstance().testableMode_names[varId] = + "ControlSystem:" + name; + boost::fusion::at_key<T>(scalarMap.table)[name].replace( + ChimeraTK::ScalarRegisterAccessor<T>(deco)); + } else { + boost::fusion::at_key<T>(scalarMap.table)[name].replace( + ChimeraTK::ScalarRegisterAccessor<T>(pv)); + } + + // return the accessor as stored in the cache + return boost::fusion::at_key<T>(scalarMap.table)[name]; + } + + /** Obtain an array-type process variable from the application, which is + * published to the control system. */ + template <typename T> + ChimeraTK::OneDRegisterAccessor<T> + getArray(const ChimeraTK::RegisterPath &name) const { + + // check for existing accessor in cache + if (boost::fusion::at_key<T>(arrayMap.table).count(name) > 0) { + return boost::fusion::at_key<T>(arrayMap.table)[name]; + } + + // obtain accessor from ControlSystemPVManager + auto pv = pvManager->getProcessArray<T>(name); + if (pv == nullptr) { + throw ChimeraTK::logic_error("Process variable '" + name + + "' does not exist."); + } + + // obtain variable id from pvIdMap and transfer it to idMap (required by the + // TestableModeAccessorDecorator) + size_t varId = Application::getInstance().pvIdMap[pv->getUniqueId()]; + + // decorate with TestableModeAccessorDecorator if variable is sender and + // receiver is not poll-type, and store it in cache + if (pv->isWriteable() && + !Application::getInstance().testableMode_isPollMode[varId]) { + auto deco = boost::make_shared<TestableModeAccessorDecorator<T>>( + pv, false, true, varId, varId); + Application::getInstance().testableMode_names[varId] = + "ControlSystem:" + name; + boost::fusion::at_key<T>(arrayMap.table)[name].replace( + ChimeraTK::OneDRegisterAccessor<T>(deco)); + } else { + boost::fusion::at_key<T>(arrayMap.table)[name].replace( + ChimeraTK::OneDRegisterAccessor<T>(pv)); + } + + // return the accessor as stored in the cache + return boost::fusion::at_key<T>(arrayMap.table)[name]; + } + + /** Convenience function to write a scalar process variable in a single call + */ + template <typename TYPE> + void writeScalar(const std::string &name, const TYPE value) { + auto acc = getScalar<TYPE>(name); + acc = value; + acc.write(); + } + + /** Convenience function to write an array process variable in a single call + */ + template <typename TYPE> + void writeArray(const std::string &name, const std::vector<TYPE> &value) { + auto acc = getArray<TYPE>(name); + acc = value; + acc.write(); + } + + /** Convenience function to read the latest value of a scalar process variable + * in a single call */ + template <typename TYPE> TYPE readScalar(const std::string &name) { + auto acc = getScalar<TYPE>(name); + acc.readLatest(); + return acc; + } + + /** Convenience function to read the latest value of an array process variable + * in a single call */ + template <typename TYPE> + std::vector<TYPE> readArray(const std::string &name) { + auto acc = getArray<TYPE>(name); + acc.readLatest(); + return acc; + } + +protected: + boost::shared_ptr<ControlSystemPVManager> pvManager; + + // Cache (possible decorated) accessors to avoid the need to create accessors + // multiple times. This would not work if the accessor is decorated, since the + // buffer would be lost and thus the current value could no longer be + // obtained. This has to be done separately for scalar and array accessors and + // in dependence of the user type. Since this is a cache and does not change + // the logical behaviour of the class, the maps are defined mutable. + template <typename UserType> + using ScalarMap = + std::map<std::string, ChimeraTK::ScalarRegisterAccessor<UserType>>; + mutable ChimeraTK::TemplateUserTypeMap<ScalarMap> scalarMap; + + template <typename UserType> + using ArrayMap = + std::map<std::string, ChimeraTK::OneDRegisterAccessor<UserType>>; + mutable ChimeraTK::TemplateUserTypeMap<ArrayMap> arrayMap; +}; } /* namespace ChimeraTK */ diff --git a/include/TestableModeAccessorDecorator.h b/include/TestableModeAccessorDecorator.h index aaea14c8..262bdae1 100644 --- a/include/TestableModeAccessorDecorator.h +++ b/include/TestableModeAccessorDecorator.h @@ -15,146 +15,172 @@ namespace ChimeraTK { - /** Decorator of the NDRegisterAccessor which facilitates tests of the application */ - template<typename UserType> - class TestableModeAccessorDecorator : public ChimeraTK::NDRegisterAccessorDecorator<UserType> { - public: - TestableModeAccessorDecorator(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, - bool handleRead, bool handleWrite, - size_t variableIdRead, size_t variableIdWrite) +/** Decorator of the NDRegisterAccessor which facilitates tests of the + * application */ +template <typename UserType> +class TestableModeAccessorDecorator + : public ChimeraTK::NDRegisterAccessorDecorator<UserType> { +public: + TestableModeAccessorDecorator( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, + bool handleRead, bool handleWrite, size_t variableIdRead, + size_t variableIdWrite) : ChimeraTK::NDRegisterAccessorDecorator<UserType>(accessor), - _handleRead(handleRead), - _handleWrite(handleWrite), - _variableIdRead(variableIdRead), - _variableIdWrite(variableIdWrite) - { - assert(_variableIdRead != 0); - assert(_variableIdWrite != 0); - - // if receiving end, register for testable mode (stall detection) - if(this->isReadable() && handleRead) { - Application::getInstance().testableMode_processVars[_variableIdRead] = accessor; - } - - // if this decorating a bidirectional process variable, set the valueRejectCallback - auto bidir = boost::dynamic_pointer_cast<BidirectionalProcessArray<UserType>>(accessor); - if(bidir) { - bidir->setValueRejectCallback([this]{ - decrementCounter(); - }); - } - else { - assert(! (handleRead && handleWrite) ); - } + _handleRead(handleRead), _handleWrite(handleWrite), + _variableIdRead(variableIdRead), _variableIdWrite(variableIdWrite) { + assert(_variableIdRead != 0); + assert(_variableIdWrite != 0); + + // if receiving end, register for testable mode (stall detection) + if (this->isReadable() && handleRead) { + Application::getInstance().testableMode_processVars[_variableIdRead] = + accessor; + } + + // if this decorating a bidirectional process variable, set the + // valueRejectCallback + auto bidir = + boost::dynamic_pointer_cast<BidirectionalProcessArray<UserType>>( + accessor); + if (bidir) { + bidir->setValueRejectCallback([this] { decrementCounter(); }); + } else { + assert(!(handleRead && handleWrite)); + } + } + + bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber = {}) override { + if (!_handleWrite) + return _target->doWriteTransfer(versionNumber); + + bool dataLost = false; + if (!Application::testableModeTestLock()) { + // may happen if first write in thread is done before first blocking read + Application::testableModeLock("write " + this->getName()); + } + dataLost = _target->doWriteTransfer(versionNumber); + if (!dataLost) { + ++Application::getInstance().testableMode_counter; + ++Application::getInstance().testableMode_perVarCounter[_variableIdWrite]; + if (Application::getInstance().enableDebugTestableMode) { + std::cout << "TestableModeAccessorDecorator::write[name='" + << this->getName() << "', id=" << _variableIdWrite + << "]: testableMode_counter " + "increased, now at value " + << Application::getInstance().testableMode_counter + << std::endl; } - - bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber={}) override { - if(!_handleWrite) return _target->doWriteTransfer(versionNumber); - - bool dataLost = false; - if(!Application::testableModeTestLock()) { - // may happen if first write in thread is done before first blocking read - Application::testableModeLock("write "+this->getName()); - } - dataLost = _target->doWriteTransfer(versionNumber); - if(!dataLost) { - ++Application::getInstance().testableMode_counter; - ++Application::getInstance().testableMode_perVarCounter[_variableIdWrite]; - if(Application::getInstance().enableDebugTestableMode) { - std::cout << "TestableModeAccessorDecorator::write[name='"<<this->getName()<<"', id="<<_variableIdWrite<<"]: testableMode_counter " - "increased, now at value " << Application::getInstance().testableMode_counter << std::endl; - } - } - else { - if(Application::getInstance().enableDebugTestableMode) { - std::cout << "TestableModeAccessorDecorator::write[name='"<<this->getName()<<"', id="<<_variableIdWrite<<"]: testableMode_counter not " - "increased due to lost data" << std::endl; - } - } - return dataLost; - } - - void doReadTransfer() override { - if(_handleRead) releaseLock(); - _target->doReadTransfer(); - } - - /** Release the testableModeLock */ - void releaseLock() { - if(Application::testableModeTestLock()) Application::testableModeUnlock("doReadTransfer "+this->getName()); - } - - /** Implement callback called by TransferFuture before it blocks in wait() */ - void transferFutureWaitCallback() override { - releaseLock(); - } - - /** Obtain the testableModeLock if not owned yet, and decrement the counter. */ - void obtainLockAndDecrementCounter() { - if(!Application::testableModeTestLock()) Application::testableModeLock("doReadTransfer "+this->getName()); - if(Application::getInstance().testableMode_perVarCounter[_variableIdRead] > 0) { - assert(Application::getInstance().testableMode_counter > 0); - --Application::getInstance().testableMode_counter; - --Application::getInstance().testableMode_perVarCounter[_variableIdRead]; - if(Application::getInstance().enableDebugTestableMode) { - std::cout << "TestableModeAccessorDecorator[name='"<<this->getName()<<"', id="<<_variableIdRead<<"]: testableMode_counter " - "decreased, now at value " << Application::getInstance().testableMode_counter << " / " << - Application::getInstance().testableMode_perVarCounter[_variableIdRead] << std::endl; - } - } - else { - if(Application::getInstance().enableDebugTestableMode) { - std::cout << "TestableModeAccessorDecorator[name='"<<this->getName()<<"', id="<<_variableIdRead<<"]: testableMode_counter " - "NOT decreased, was already at value " << Application::getInstance().testableMode_counter << " / " << - Application::getInstance().testableMode_perVarCounter[_variableIdRead] << std::endl; - std::cout << Application::getInstance().testableMode_names[_variableIdRead] << std::endl; - } - } + } else { + if (Application::getInstance().enableDebugTestableMode) { + std::cout << "TestableModeAccessorDecorator::write[name='" + << this->getName() << "', id=" << _variableIdWrite + << "]: testableMode_counter not " + "increased due to lost data" + << std::endl; } - - /** Obtain the testableModeLock if not owned yet, decrement the counter, and release the lock again. */ - void decrementCounter() { - obtainLockAndDecrementCounter(); - releaseLock(); + } + return dataLost; + } + + void doReadTransfer() override { + if (_handleRead) + releaseLock(); + _target->doReadTransfer(); + } + + /** Release the testableModeLock */ + void releaseLock() { + if (Application::testableModeTestLock()) + Application::testableModeUnlock("doReadTransfer " + this->getName()); + } + + /** Implement callback called by TransferFuture before it blocks in wait() */ + void transferFutureWaitCallback() override { releaseLock(); } + + /** Obtain the testableModeLock if not owned yet, and decrement the counter. + */ + void obtainLockAndDecrementCounter() { + if (!Application::testableModeTestLock()) + Application::testableModeLock("doReadTransfer " + this->getName()); + if (Application::getInstance().testableMode_perVarCounter[_variableIdRead] > + 0) { + assert(Application::getInstance().testableMode_counter > 0); + --Application::getInstance().testableMode_counter; + --Application::getInstance().testableMode_perVarCounter[_variableIdRead]; + if (Application::getInstance().enableDebugTestableMode) { + std::cout << "TestableModeAccessorDecorator[name='" << this->getName() + << "', id=" << _variableIdRead + << "]: testableMode_counter " + "decreased, now at value " + << Application::getInstance().testableMode_counter << " / " + << Application::getInstance() + .testableMode_perVarCounter[_variableIdRead] + << std::endl; } - - bool doReadTransferNonBlocking() override { - bool newData = _target->doReadTransferNonBlocking(); - if(!newData) return false; - return true; + } else { + if (Application::getInstance().enableDebugTestableMode) { + std::cout << "TestableModeAccessorDecorator[name='" << this->getName() + << "', id=" << _variableIdRead + << "]: testableMode_counter " + "NOT decreased, was already at value " + << Application::getInstance().testableMode_counter << " / " + << Application::getInstance() + .testableMode_perVarCounter[_variableIdRead] + << std::endl; + std::cout + << Application::getInstance().testableMode_names[_variableIdRead] + << std::endl; } - - bool doReadTransferLatest() override { - bool newData = _target->doReadTransferLatest(); - if(!newData) return false; - - // the queue has been emptied, so make sure that the testableMode_counter reflects this - // we only reduce the counter to 1, since it will be decremented in postRead(). - if(_handleRead) { - auto &app = Application::getInstance(); - assert(Application::testableModeTestLock()); - if(app.testableMode_perVarCounter[_variableIdRead] > 1) { - app.testableMode_counter -= app.testableMode_perVarCounter[_variableIdRead] - 1; - app.testableMode_perVarCounter[_variableIdRead] = 1; - } - } - return true; - } - - void doPostRead() override { - if(_handleRead) obtainLockAndDecrementCounter(); - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(); + } + } + + /** Obtain the testableModeLock if not owned yet, decrement the counter, and + * release the lock again. */ + void decrementCounter() { + obtainLockAndDecrementCounter(); + releaseLock(); + } + + bool doReadTransferNonBlocking() override { + bool newData = _target->doReadTransferNonBlocking(); + if (!newData) + return false; + return true; + } + + bool doReadTransferLatest() override { + bool newData = _target->doReadTransferLatest(); + if (!newData) + return false; + + // the queue has been emptied, so make sure that the testableMode_counter + // reflects this we only reduce the counter to 1, since it will be + // decremented in postRead(). + if (_handleRead) { + auto &app = Application::getInstance(); + assert(Application::testableModeTestLock()); + if (app.testableMode_perVarCounter[_variableIdRead] > 1) { + app.testableMode_counter -= + app.testableMode_perVarCounter[_variableIdRead] - 1; + app.testableMode_perVarCounter[_variableIdRead] = 1; } - - protected: - - using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D; - using ChimeraTK::NDRegisterAccessorDecorator<UserType>::_target; - - bool _handleRead, _handleWrite; - size_t _variableIdRead, _variableIdWrite; - - }; + } + return true; + } + + void doPostRead() override { + if (_handleRead) + obtainLockAndDecrementCounter(); + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(); + } + +protected: + using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D; + using ChimeraTK::NDRegisterAccessorDecorator<UserType>::_target; + + bool _handleRead, _handleWrite; + size_t _variableIdRead, _variableIdWrite; +}; } /* namespace ChimeraTK */ diff --git a/include/ThreadedFanOut.h b/include/ThreadedFanOut.h index fdffef78..db170c09 100644 --- a/include/ThreadedFanOut.h +++ b/include/ThreadedFanOut.h @@ -17,133 +17,133 @@ namespace ChimeraTK { - /** FanOut implementation with an internal thread which waits for new data which is read from the given feeding - * implementation and distributed to any number of slaves. */ - template<typename UserType> - class ThreadedFanOut : public FanOut<UserType>, public InternalModule { - - public: - - ThreadedFanOut(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl) - : FanOut<UserType>(feedingImpl) - {} - - - ~ThreadedFanOut() { - deactivate(); - } - - void activate() override { - assert(!_thread.joinable()); - _thread = boost::thread([this] { this->run(); }); - } - - void deactivate() override { - if(_thread.joinable()) { - _thread.interrupt(); - FanOut<UserType>::impl->interrupt(); - _thread.join(); - } - assert(!_thread.joinable()); - } - - /** Synchronise feeder and the consumers. This function is executed in the separate thread. */ - virtual void run() { - Application::registerThread("ThFO"+FanOut<UserType>::impl->getName()); - Application::testableModeLock("start"); - - while(true) { - // receive data - boost::this_thread::interruption_point(); - Profiler::stopMeasurement(); - FanOut<UserType>::impl->read(); - Profiler::startMeasurement(); - boost::this_thread::interruption_point(); - // send out copies to slaves - auto version = FanOut<UserType>::impl->getVersionNumber(); - for(auto &slave : FanOut<UserType>::slaves) { - // do not send copy if no data is expected (e.g. trigger) - if(slave->getNumberOfSamples() != 0) { - slave->accessChannel(0) = FanOut<UserType>::impl->accessChannel(0); - } - bool dataLoss = slave->write(version); - if(dataLoss) Application::incrementDataLossCounter(); - } +/** FanOut implementation with an internal thread which waits for new data which + * is read from the given feeding implementation and distributed to any number + * of slaves. */ +template <typename UserType> +class ThreadedFanOut : public FanOut<UserType>, public InternalModule { + +public: + ThreadedFanOut( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl) + : FanOut<UserType>(feedingImpl) {} + + ~ThreadedFanOut() { deactivate(); } + + void activate() override { + assert(!_thread.joinable()); + _thread = boost::thread([this] { this->run(); }); + } + + void deactivate() override { + if (_thread.joinable()) { + _thread.interrupt(); + FanOut<UserType>::impl->interrupt(); + _thread.join(); + } + assert(!_thread.joinable()); + } + + /** Synchronise feeder and the consumers. This function is executed in the + * separate thread. */ + virtual void run() { + Application::registerThread("ThFO" + FanOut<UserType>::impl->getName()); + Application::testableModeLock("start"); + + while (true) { + // receive data + boost::this_thread::interruption_point(); + Profiler::stopMeasurement(); + FanOut<UserType>::impl->read(); + Profiler::startMeasurement(); + boost::this_thread::interruption_point(); + // send out copies to slaves + auto version = FanOut<UserType>::impl->getVersionNumber(); + for (auto &slave : FanOut<UserType>::slaves) { + // do not send copy if no data is expected (e.g. trigger) + if (slave->getNumberOfSamples() != 0) { + slave->accessChannel(0) = FanOut<UserType>::impl->accessChannel(0); } + bool dataLoss = slave->write(version); + if (dataLoss) + Application::incrementDataLossCounter(); } - - protected: - - /** Thread handling the synchronisation, if needed */ - boost::thread _thread; - - }; - - /********************************************************************************************************************/ - - /** Same as ThreadedFanOut but with return channel */ - template<typename UserType> - class ThreadedFanOutWithReturn : public ThreadedFanOut<UserType> { - - public: - - using ThreadedFanOut<UserType>::ThreadedFanOut; - - void setReturnChannelSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> returnChannelSlave) { - _returnChannelSlave = returnChannelSlave; - } - - void addSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, - VariableNetworkNode &consumer) override { - FanOut<UserType>::addSlave(slave, consumer); - if(consumer.getDirection().withReturn) { - assert(_returnChannelSlave == nullptr); - _returnChannelSlave = slave; - } + } + } + +protected: + /** Thread handling the synchronisation, if needed */ + boost::thread _thread; +}; + +/********************************************************************************************************************/ + +/** Same as ThreadedFanOut but with return channel */ +template <typename UserType> +class ThreadedFanOutWithReturn : public ThreadedFanOut<UserType> { + +public: + using ThreadedFanOut<UserType>::ThreadedFanOut; + + void setReturnChannelSlave( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> + returnChannelSlave) { + _returnChannelSlave = returnChannelSlave; + } + + void + addSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, + VariableNetworkNode &consumer) override { + FanOut<UserType>::addSlave(slave, consumer); + if (consumer.getDirection().withReturn) { + assert(_returnChannelSlave == nullptr); + _returnChannelSlave = slave; + } + } + + void run() override { + Application::registerThread("ThFO" + FanOut<UserType>::impl->getName()); + Application::testableModeLock("start"); + + ReadAnyGroup group({FanOut<UserType>::impl, _returnChannelSlave}); + while (true) { + // receive data + boost::this_thread::interruption_point(); + Profiler::stopMeasurement(); + auto var = group.readAny(); + Profiler::startMeasurement(); + boost::this_thread::interruption_point(); + // if the update came through the return channel, return it to the feeder + if (var == _returnChannelSlave->getId()) { + FanOut<UserType>::impl->accessChannel(0).swap( + _returnChannelSlave->accessChannel(0)); + FanOut<UserType>::impl->write(_returnChannelSlave->getVersionNumber()); } - - void run() override { - Application::registerThread("ThFO"+FanOut<UserType>::impl->getName()); - Application::testableModeLock("start"); - - ReadAnyGroup group({FanOut<UserType>::impl, _returnChannelSlave}); - while(true) { - // receive data - boost::this_thread::interruption_point(); - Profiler::stopMeasurement(); - auto var = group.readAny(); - Profiler::startMeasurement(); - boost::this_thread::interruption_point(); - // if the update came through the return channel, return it to the feeder - if(var == _returnChannelSlave->getId()) { - FanOut<UserType>::impl->accessChannel(0).swap(_returnChannelSlave->accessChannel(0)); - FanOut<UserType>::impl->write(_returnChannelSlave->getVersionNumber()); - } - // send out copies to slaves - auto version = FanOut<UserType>::impl->getVersionNumber(); - for(auto &slave : FanOut<UserType>::slaves) { - // do not feed back value returnChannelSlave if it was received from it - if(slave->getId() == var) continue; - // do not send copy if no data is expected (e.g. trigger) - if(slave->getNumberOfSamples() != 0) { - slave->accessChannel(0) = FanOut<UserType>::impl->accessChannel(0); - } - bool dataLoss = slave->write(version); - if(dataLoss) Application::incrementDataLossCounter(); - } + // send out copies to slaves + auto version = FanOut<UserType>::impl->getVersionNumber(); + for (auto &slave : FanOut<UserType>::slaves) { + // do not feed back value returnChannelSlave if it was received from it + if (slave->getId() == var) + continue; + // do not send copy if no data is expected (e.g. trigger) + if (slave->getNumberOfSamples() != 0) { + slave->accessChannel(0) = FanOut<UserType>::impl->accessChannel(0); } + bool dataLoss = slave->write(version); + if (dataLoss) + Application::incrementDataLossCounter(); } + } + } - protected: +protected: + /** Thread handling the synchronisation, if needed */ + boost::thread _thread; - /** Thread handling the synchronisation, if needed */ - boost::thread _thread; - - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> _returnChannelSlave; - - }; + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> + _returnChannelSlave; +}; } /* namespace ChimeraTK */ #endif /* CHIMERATK_THREADED_FAN_OUT_H */ - diff --git a/include/TriggerFanOut.h b/include/TriggerFanOut.h index 9d8b93dc..c7edf041 100644 --- a/include/TriggerFanOut.h +++ b/include/TriggerFanOut.h @@ -19,109 +19,112 @@ namespace ChimeraTK { - /** InternalModule which waits for a trigger, then reads a number of variables and distributes each of them to any - * number of slaves. */ - class TriggerFanOut : public InternalModule { - - public: - - TriggerFanOut(const boost::shared_ptr<ChimeraTK::TransferElement>& externalTriggerImpl) - : externalTrigger(externalTriggerImpl) - {} - - ~TriggerFanOut() { - deactivate(); - } - - void activate() override { - assert(!_thread.joinable()); - _thread = boost::thread([this] { this->run(); }); - } - - void deactivate() override { - if(_thread.joinable()) { - _thread.interrupt(); - externalTrigger->interrupt(); - _thread.join(); - } - assert(!_thread.joinable()); - } - - /** Add a new network the TriggerFanOut. The network is defined by its feeding node. This function will return - * the corresponding FeedingFanOut, to which all slaves have to be added. */ - template<typename UserType> - boost::shared_ptr<FeedingFanOut<UserType>> addNetwork(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingNode) { - assert(feedingNode.get() != nullptr); - transferGroup.addAccessor(feedingNode); - auto feedingFanOut = boost::make_shared<FeedingFanOut<UserType>>( feedingNode->getName(), feedingNode->getUnit(), - feedingNode->getDescription(), feedingNode->getNumberOfSamples(), false ); // in TriggerFanOuts we cannot have return channels - boost::fusion::at_key<UserType>(fanOutMap.table)[feedingNode] = feedingFanOut; - return feedingFanOut; - } - - /** Synchronise feeder and the consumers. This function is executed in the separate thread. */ - void run() { - Application::registerThread("TrFO"+externalTrigger->getName()); - Application::testableModeLock("start"); - while(true) { - // wait for external trigger - boost::this_thread::interruption_point(); - Profiler::stopMeasurement(); - externalTrigger->read(); - Profiler::startMeasurement(); - boost::this_thread::interruption_point(); - // receive data - transferGroup.read(); - // send the data to the consumers - auto version = externalTrigger->getVersionNumber(); - boost::fusion::for_each(fanOutMap.table, SendDataToConsumers(version)); - } +/** InternalModule which waits for a trigger, then reads a number of variables + * and distributes each of them to any number of slaves. */ +class TriggerFanOut : public InternalModule { + +public: + TriggerFanOut( + const boost::shared_ptr<ChimeraTK::TransferElement> &externalTriggerImpl) + : externalTrigger(externalTriggerImpl) {} + + ~TriggerFanOut() { deactivate(); } + + void activate() override { + assert(!_thread.joinable()); + _thread = boost::thread([this] { this->run(); }); + } + + void deactivate() override { + if (_thread.joinable()) { + _thread.interrupt(); + externalTrigger->interrupt(); + _thread.join(); + } + assert(!_thread.joinable()); + } + + /** Add a new network the TriggerFanOut. The network is defined by its feeding + * node. This function will return the corresponding FeedingFanOut, to which + * all slaves have to be added. */ + template <typename UserType> + boost::shared_ptr<FeedingFanOut<UserType>> addNetwork( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingNode) { + assert(feedingNode.get() != nullptr); + transferGroup.addAccessor(feedingNode); + auto feedingFanOut = boost::make_shared<FeedingFanOut<UserType>>( + feedingNode->getName(), feedingNode->getUnit(), + feedingNode->getDescription(), feedingNode->getNumberOfSamples(), + false); // in TriggerFanOuts we cannot have return channels + boost::fusion::at_key<UserType>(fanOutMap.table)[feedingNode] = + feedingFanOut; + return feedingFanOut; + } + + /** Synchronise feeder and the consumers. This function is executed in the + * separate thread. */ + void run() { + Application::registerThread("TrFO" + externalTrigger->getName()); + Application::testableModeLock("start"); + while (true) { + // wait for external trigger + boost::this_thread::interruption_point(); + Profiler::stopMeasurement(); + externalTrigger->read(); + Profiler::startMeasurement(); + boost::this_thread::interruption_point(); + // receive data + transferGroup.read(); + // send the data to the consumers + auto version = externalTrigger->getVersionNumber(); + boost::fusion::for_each(fanOutMap.table, SendDataToConsumers(version)); + } + } + +protected: + /** Functor class to send data to the consumers, suitable for + * boost::fusion::for_each(). */ + struct SendDataToConsumers { + SendDataToConsumers(VersionNumber version) : _version(version) {} + + template <typename PAIR> void operator()(PAIR &pair) const { + + auto theMap = pair.second; // map of feeder to FeedingFanOut (i.e. part of + // the fanOutMap) + + // iterate over all feeder/FeedingFanOut pairs + for (auto &network : theMap) { + auto feeder = network.first; + auto fanOut = network.second; + fanOut->accessChannel(0).swap(feeder->accessChannel(0)); + bool dataLoss = fanOut->write(_version); + if (dataLoss) + Application::incrementDataLossCounter(); + // no need to swap back since we don't need the data } + } - protected: - - /** Functor class to send data to the consumers, suitable for boost::fusion::for_each(). */ - struct SendDataToConsumers { - SendDataToConsumers(VersionNumber version): _version(version) {} - - template<typename PAIR> - void operator()(PAIR &pair) const { - - auto theMap = pair.second; // map of feeder to FeedingFanOut (i.e. part of the fanOutMap) - - // iterate over all feeder/FeedingFanOut pairs - for(auto &network : theMap) { - auto feeder = network.first; - auto fanOut = network.second; - fanOut->accessChannel(0).swap(feeder->accessChannel(0)); - bool dataLoss = fanOut->write(_version); - if(dataLoss) Application::incrementDataLossCounter(); - // no need to swap back since we don't need the data - } - - } - - VersionNumber _version; - }; - - /** TransferElement acting as our trigger */ - boost::shared_ptr<ChimeraTK::TransferElement> externalTrigger; + VersionNumber _version; + }; - /** Map of the feeding NDRegisterAccessor to the corresponding FeedingFanOut for each UserType */ - template<typename UserType> - using FanOutMap = std::map<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, boost::shared_ptr<FeedingFanOut<UserType>>>; - TemplateUserTypeMap<FanOutMap> fanOutMap; + /** TransferElement acting as our trigger */ + boost::shared_ptr<ChimeraTK::TransferElement> externalTrigger; - /** TransferGroup containing all feeders NDRegisterAccessors */ - ChimeraTK::TransferGroup transferGroup; + /** Map of the feeding NDRegisterAccessor to the corresponding FeedingFanOut + * for each UserType */ + template <typename UserType> + using FanOutMap = + std::map<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, + boost::shared_ptr<FeedingFanOut<UserType>>>; + TemplateUserTypeMap<FanOutMap> fanOutMap; - /** Thread handling the synchronisation, if needed */ - boost::thread _thread; + /** TransferGroup containing all feeders NDRegisterAccessors */ + ChimeraTK::TransferGroup transferGroup; - }; + /** Thread handling the synchronisation, if needed */ + boost::thread _thread; +}; } /* namespace ChimeraTK */ #endif /* CHIMERATK_TRIGGER_FAN_OUT_H */ - - diff --git a/include/VariableGroup.h b/include/VariableGroup.h index bb19a2bc..263ee7d6 100644 --- a/include/VariableGroup.h +++ b/include/VariableGroup.h @@ -16,44 +16,49 @@ namespace ChimeraTK { - class ApplicationModule; - - class VariableGroup : public ModuleImpl { - - public: - - /** Constructor: register the VariableGroup with its owner. If eliminateHierarchy is true, the hierarchy level - * introduced by this group will be eliminated from the "dynamic" data model (see - * EntityOwner::setEliminateHierarchy()). The tags given as the last argument are added to all variables - * in this group recursively (see EntityOwner::addTag()). - * - * Note: VariableGroups may only be owned by ApplicationModules or other VariableGroups. */ - VariableGroup(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}); - - /** Default constructor: Allows late initialisation of VariableGroups (e.g. when creating arrays of - * VariableGroups). - * - * This construtor also has to be here to mitigate a bug in gcc. It is needed to allow constructor - * inheritance of modules owning other modules. This constructor will not actually be called then. - * See this bug report: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ - VariableGroup() {} - - /** Destructor */ - virtual ~VariableGroup() {}; - - /** Move constructor */ - VariableGroup(VariableGroup &&other) { operator=(std::move(other)); } - - /** Move assignment */ - VariableGroup& operator=(VariableGroup &&other) { - ModuleImpl::operator=(std::move(other)); - return *this; - } - - ModuleType getModuleType() const override { return ModuleType::VariableGroup; } - - }; +class ApplicationModule; + +class VariableGroup : public ModuleImpl { + +public: + /** Constructor: register the VariableGroup with its owner. If + * eliminateHierarchy is true, the hierarchy level introduced by this group + * will be eliminated from the "dynamic" data model (see + * EntityOwner::setEliminateHierarchy()). The tags given as the last argument + * are added to all variables in this group recursively (see + * EntityOwner::addTag()). + * + * Note: VariableGroups may only be owned by ApplicationModules or other + * VariableGroups. */ + VariableGroup(EntityOwner *owner, const std::string &name, + const std::string &description, bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}); + + /** Default constructor: Allows late initialisation of VariableGroups (e.g. + * when creating arrays of VariableGroups). + * + * This construtor also has to be here to mitigate a bug in gcc. It is needed + * to allow constructor inheritance of modules owning other modules. This + * constructor will not actually be called then. See this bug report: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054 */ + VariableGroup() {} + + /** Destructor */ + virtual ~VariableGroup(){}; + + /** Move constructor */ + VariableGroup(VariableGroup &&other) { operator=(std::move(other)); } + + /** Move assignment */ + VariableGroup &operator=(VariableGroup &&other) { + ModuleImpl::operator=(std::move(other)); + return *this; + } + + ModuleType getModuleType() const override { + return ModuleType::VariableGroup; + } +}; } /* namespace ChimeraTK */ diff --git a/include/VariableNetwork.h b/include/VariableNetwork.h index a41724bb..6075a8e5 100644 --- a/include/VariableNetwork.h +++ b/include/VariableNetwork.h @@ -8,11 +8,11 @@ #ifndef CHIMERATK_VARIABLE_NETWORK_H #define CHIMERATK_VARIABLE_NETWORK_H +#include <boost/mpl/for_each.hpp> +#include <iostream> #include <list> #include <string> -#include <iostream> #include <typeinfo> -#include <boost/mpl/for_each.hpp> #include <ChimeraTK/ControlSystemAdapter/ProcessVariable.h> @@ -22,146 +22,162 @@ namespace ChimeraTK { - class AccessorBase; - - /** This class describes a network of variables all connected to each other. */ - class VariableNetwork { - - VariableNetwork( const VariableNetwork& other ) = delete; // non construction-copyable - VariableNetwork& operator=( const VariableNetwork& ) = delete; // non copyable - - public: - - VariableNetwork() {} - - /** Define trigger types. The trigger decides when values are fed into the network and distributed to the consumers. */ - enum class TriggerType { - feeder, ///< The feeder has an UpdateMode::push and thus decides when new values are fed - pollingConsumer, ///< If there is exacly one consumer with UpdateMode::poll, it will trigger the feeding - external, ///< another variable network can trigger the feeding of this network - none ///< no trigger has yet been selected - }; - - /** Add an node to the network. The node must not yet be part of any network. */ - void addNode(VariableNetworkNode &a); - - /** Add a trigger receiver node. The node must not yet be part of any network. */ - void addNodeToTrigger(VariableNetworkNode& nodeToTrigger); - - /** Remove a node from the network. The node must be part of the given network. */ - void removeNode(VariableNetworkNode &a); - - /** Remove a trigger receiver node from the network. The node must be part of the given network. */ - void removeNodeToTrigger(const VariableNetworkNode &nodeToNoLongerTrigger); - - /** Check if the network already has a feeding node connected to it. */ - bool hasFeedingNode() const; - - /** Count the number of consuming nodes in the network */ - size_t countConsumingNodes() const; - - /** Obtain the type info of the UserType. If the network type has not yet been determined (i.e. if no output - * accessor has been assigned yet), the typeid of void will be returned. */ - const std::type_info& getValueType() const { - return *valueType; - } - - /** Return the feeding node */ - VariableNetworkNode getFeedingNode() const; - - /** Return list of consuming nodes */ - std::list<VariableNetworkNode> getConsumingNodes() const; - - /** Check whether the network has a consuming application node */ - bool hasApplicationConsumer() const; +class AccessorBase; - /** Dump the network structure to std::cout. The optional linePrefix will be prepended to all lines. */ - void dump(const std::string& linePrefix="", std::ostream& stream=std::cout) const; +/** This class describes a network of variables all connected to each other. */ +class VariableNetwork { - void accept(Visitor<VariableNetwork> &visitor) const; + VariableNetwork(const VariableNetwork &other) = + delete; // non construction-copyable + VariableNetwork &operator=(const VariableNetwork &) = delete; // non copyable - /** Compare two networks */ - bool operator==(const VariableNetwork &other) const { - if(other.valueType != valueType) return false; - if(other.nodeList != nodeList) return false; - return true; - } - bool operator!=(const VariableNetwork &other) const { - return !operator==(other); - } +public: + VariableNetwork() {} - /** Return the trigger type. This function will also do some checking if the network confguration is valid under - * the aspect of the trigger type. - * The optional argument is only internally used to prevent endless recursive calls if getTriggerType() is - * called inside dump(). */ - TriggerType getTriggerType(bool verboseExceptions=true) const; - - /** Return the enginerring unit */ - const std::string& getUnit() const { return engineeringUnit; } - - /** Return the description */ - const std::string& getDescription() const { return description; } - - /** Return the network providing the external trigger to this network, if TriggerType::external. If the network - * has another trigger type, an exception will be thrown. */ - //VariableNetwork& getExternalTrigger(); - - /** Add an accessor belonging to another node as an external trigger to this network. Whenever the - * VariableNetwork of the given node will be fed with a new value, feeding of this network will be - * triggered as well. */ - //void addTrigger(VariableNetworkNode trigger); - - /** Check if the network is legally configured */ - void check() const; - - /** Check the flag if the network connections has been created already */ - bool isCreated() const { return flagIsCreated; } - - /** Set the flag that the network connections are created */ - void markCreated() { flagIsCreated = true; } - - /** Assign a ProcessVariable as implementation for the external trigger */ - void setExternalTriggerImpl(boost::shared_ptr< ChimeraTK::ProcessVariable > impl) { - externalTriggerImpl = impl; - } - - /** */ - boost::shared_ptr< ChimeraTK::ProcessVariable > getExternalTriggerImpl() const { - return externalTriggerImpl; - } - - /** Merge with another VaraibleNetwork. The other network will become invalid and gets removed from the - * application. If merging is not possible, false is returned and no change is made. */ - bool merge(VariableNetwork &other); - - protected: - - /** List of nodes in the network */ - std::list<VariableNetworkNode> nodeList; - - /** The network value type id. Since in C++, std::type_info is non-copyable and typeid() returns a reference to - * an object with static storage duration, we have to (and can safely) store a pointer here. */ - const std::type_info* valueType{&typeid(AnyType)}; - - /** Engineering unit */ - std::string engineeringUnit{ChimeraTK::TransferElement::unitNotSet}; - - /** User-provided description */ - std::string description; - - /** Flag if an external trigger has been added to this network */ - //bool hasExternalTrigger{false}; - - /** Pointer to the network providing the external trigger */ - //VariableNetwork *externalTrigger{nullptr}; + /** Define trigger types. The trigger decides when values are fed into the + * network and distributed to the consumers. */ + enum class TriggerType { + feeder, ///< The feeder has an UpdateMode::push and thus decides when new + ///< values are fed + pollingConsumer, ///< If there is exacly one consumer with UpdateMode::poll, + ///< it will trigger the feeding + external, ///< another variable network can trigger the feeding of this + ///< network + none ///< no trigger has yet been selected + }; - /** Pointer to ProcessVariable providing the trigger (if external trigger is enabled) */ - boost::shared_ptr< ChimeraTK::ProcessVariable > externalTriggerImpl; + /** Add an node to the network. The node must not yet be part of any network. + */ + void addNode(VariableNetworkNode &a); + + /** Add a trigger receiver node. The node must not yet be part of any network. + */ + void addNodeToTrigger(VariableNetworkNode &nodeToTrigger); + + /** Remove a node from the network. The node must be part of the given + * network. */ + void removeNode(VariableNetworkNode &a); + + /** Remove a trigger receiver node from the network. The node must be part of + * the given network. */ + void removeNodeToTrigger(const VariableNetworkNode &nodeToNoLongerTrigger); + + /** Check if the network already has a feeding node connected to it. */ + bool hasFeedingNode() const; + + /** Count the number of consuming nodes in the network */ + size_t countConsumingNodes() const; - /** Flag if the network connections have been created already */ - bool flagIsCreated{false}; + /** Obtain the type info of the UserType. If the network type has not yet been + * determined (i.e. if no output accessor has been assigned yet), the typeid + * of void will be returned. */ + const std::type_info &getValueType() const { return *valueType; } + + /** Return the feeding node */ + VariableNetworkNode getFeedingNode() const; - }; + /** Return list of consuming nodes */ + std::list<VariableNetworkNode> getConsumingNodes() const; + + /** Check whether the network has a consuming application node */ + bool hasApplicationConsumer() const; + + /** Dump the network structure to std::cout. The optional linePrefix will be + * prepended to all lines. */ + void dump(const std::string &linePrefix = "", + std::ostream &stream = std::cout) const; + + void accept(Visitor<VariableNetwork> &visitor) const; + + /** Compare two networks */ + bool operator==(const VariableNetwork &other) const { + if (other.valueType != valueType) + return false; + if (other.nodeList != nodeList) + return false; + return true; + } + bool operator!=(const VariableNetwork &other) const { + return !operator==(other); + } + + /** Return the trigger type. This function will also do some checking if the + * network confguration is valid under the aspect of the trigger type. The + * optional argument is only internally used to prevent endless recursive + * calls if getTriggerType() is called inside dump(). */ + TriggerType getTriggerType(bool verboseExceptions = true) const; + + /** Return the enginerring unit */ + const std::string &getUnit() const { return engineeringUnit; } + + /** Return the description */ + const std::string &getDescription() const { return description; } + + /** Return the network providing the external trigger to this network, if + * TriggerType::external. If the network has another trigger type, an + * exception will be thrown. */ + // VariableNetwork& getExternalTrigger(); + + /** Add an accessor belonging to another node as an external trigger to this + * network. Whenever the VariableNetwork of the given node will be fed with a + * new value, feeding of this network will be triggered as well. */ + // void addTrigger(VariableNetworkNode trigger); + + /** Check if the network is legally configured */ + void check() const; + + /** Check the flag if the network connections has been created already */ + bool isCreated() const { return flagIsCreated; } + + /** Set the flag that the network connections are created */ + void markCreated() { flagIsCreated = true; } + + /** Assign a ProcessVariable as implementation for the external trigger */ + void + setExternalTriggerImpl(boost::shared_ptr<ChimeraTK::ProcessVariable> impl) { + externalTriggerImpl = impl; + } + + /** */ + boost::shared_ptr<ChimeraTK::ProcessVariable> getExternalTriggerImpl() const { + return externalTriggerImpl; + } + + /** Merge with another VaraibleNetwork. The other network will become invalid + * and gets removed from the + * application. If merging is not possible, false is returned and no change + * is made. */ + bool merge(VariableNetwork &other); + +protected: + /** List of nodes in the network */ + std::list<VariableNetworkNode> nodeList; + + /** The network value type id. Since in C++, std::type_info is non-copyable + * and typeid() returns a reference to + * an object with static storage duration, we have to (and can safely) store + * a pointer here. */ + const std::type_info *valueType{&typeid(AnyType)}; + + /** Engineering unit */ + std::string engineeringUnit{ChimeraTK::TransferElement::unitNotSet}; + + /** User-provided description */ + std::string description; + + /** Flag if an external trigger has been added to this network */ + // bool hasExternalTrigger{false}; + + /** Pointer to the network providing the external trigger */ + // VariableNetwork *externalTrigger{nullptr}; + + /** Pointer to ProcessVariable providing the trigger (if external trigger is + * enabled) */ + boost::shared_ptr<ChimeraTK::ProcessVariable> externalTriggerImpl; + + /** Flag if the network connections have been created already */ + bool flagIsCreated{false}; +}; } /* namespace ChimeraTK */ diff --git a/include/VariableNetworkDumpingVisitor.h b/include/VariableNetworkDumpingVisitor.h index 60282f24..84c19638 100644 --- a/include/VariableNetworkDumpingVisitor.h +++ b/include/VariableNetworkDumpingVisitor.h @@ -1,7 +1,7 @@ #pragma once -#include "Visitor.h" #include "VariableNetworkNodeDumpingVisitor.h" +#include "Visitor.h" #include <string> @@ -15,15 +15,17 @@ class VariableNetwork; * * This class provides a textual dump of the VariableNetwork */ -class VariableNetworkDumpingVisitor : public Visitor<VariableNetwork>, public VariableNetworkNodeDumpingVisitor { +class VariableNetworkDumpingVisitor : public Visitor<VariableNetwork>, + public VariableNetworkNodeDumpingVisitor { public: - VariableNetworkDumpingVisitor(const std::string &prefix, std::ostream &stream); - virtual ~VariableNetworkDumpingVisitor() {} - void dispatch(const VariableNetwork& t); - using Visitor<VariableNetworkNode>::dispatch; + VariableNetworkDumpingVisitor(const std::string &prefix, + std::ostream &stream); + virtual ~VariableNetworkDumpingVisitor() {} + void dispatch(const VariableNetwork &t); + using Visitor<VariableNetworkNode>::dispatch; private: - std::string _prefix; + std::string _prefix; }; } // namespace ChimeraTK diff --git a/include/VariableNetworkGraphDumpingVisitor.h b/include/VariableNetworkGraphDumpingVisitor.h index 784fd68c..88c67650 100644 --- a/include/VariableNetworkGraphDumpingVisitor.h +++ b/include/VariableNetworkGraphDumpingVisitor.h @@ -1,7 +1,7 @@ #pragma once -#include "Visitor.h" #include "VariableNetworkNodeDumpingVisitor.h" +#include "Visitor.h" #include <map> @@ -16,26 +16,29 @@ class VariableNetwork; * @brief The VariableNetworkGraphDumpingVisitor class * * This class provides a Graphiviz dump of the VariableNetwork. - * Due to the potential size of the resulting graph, it is recommended to use SVG for - * rendering the resulting graph. + * Due to the potential size of the resulting graph, it is recommended to use + * SVG for rendering the resulting graph. */ -class VariableNetworkGraphDumpingVisitor : public Visitor<Application, VariableNetwork>, VariableNetworkNodeDumpingVisitor { +class VariableNetworkGraphDumpingVisitor + : public Visitor<Application, VariableNetwork>, + VariableNetworkNodeDumpingVisitor { public: - VariableNetworkGraphDumpingVisitor(std::ostream& stream); - virtual ~VariableNetworkGraphDumpingVisitor() {} - void dispatch(const Application& t); - void dispatch(const VariableNetwork& t); - void dispatch(const VariableNetworkNode& t); + VariableNetworkGraphDumpingVisitor(std::ostream &stream); + virtual ~VariableNetworkGraphDumpingVisitor() {} + void dispatch(const Application &t); + void dispatch(const VariableNetwork &t); + void dispatch(const VariableNetworkNode &t); + private: - std::map<std::string, std::string> _triggerMap; - std::list<std::string> _triggerConnections; - std::list<std::string> _prefix; - unsigned _networkCount; - unsigned _triggerCount; - - std::string prefix() { return _prefix.back(); } - void pushPrefix(const std::string& prefix) { _prefix.push_back(prefix); } - void popPrefix() { _prefix.pop_back(); } + std::map<std::string, std::string> _triggerMap; + std::list<std::string> _triggerConnections; + std::list<std::string> _prefix; + unsigned _networkCount; + unsigned _triggerCount; + + std::string prefix() { return _prefix.back(); } + void pushPrefix(const std::string &prefix) { _prefix.push_back(prefix); } + void popPrefix() { _prefix.pop_back(); } }; } // namespace ChimeraTK diff --git a/include/VariableNetworkNode.h b/include/VariableNetworkNode.h index 9e4c0e64..a560114b 100644 --- a/include/VariableNetworkNode.h +++ b/include/VariableNetworkNode.h @@ -8,9 +8,9 @@ #ifndef CHIMERATK_VARIABLE_NETWORK_NODE_H #define CHIMERATK_VARIABLE_NETWORK_NODE_H -#include <unordered_set> -#include <unordered_map> #include <iostream> +#include <unordered_map> +#include <unordered_set> #include <assert.h> @@ -18,276 +18,308 @@ #include <ChimeraTK/NDRegisterAccessorAbstractor.h> -#include "Flags.h" #include "ConstantAccessor.h" -#include "Visitor.h" +#include "Flags.h" #include "VersionNumberUpdatingRegisterDecorator.h" +#include "Visitor.h" namespace ChimeraTK { - class VariableNetwork; - class AccessorBase; - class EntityOwner; - struct VariableNetworkNode_data; - - /** Pseudo type to identify nodes which can have arbitrary types */ - class AnyType {}; - - /** Class describing a node of a variable network */ - class VariableNetworkNode { - - public: - - /** Copy-constructor: Just copy the pointer to the data storage object */ - VariableNetworkNode(const VariableNetworkNode &other); - - /** Copy by assignment operator: Just copy the pointer to the data storage object */ - VariableNetworkNode& operator=(const VariableNetworkNode &rightHandSide); - - /** Constructor for an Application node */ - VariableNetworkNode(EntityOwner *owner, ChimeraTK::TransferElementAbstractor *accessorBridge, const std::string &name, - VariableDirection direction, std::string unit, size_t nElements, UpdateMode mode, - const std::string &description, const std::type_info* valueType, - const std::unordered_set<std::string> &tags={}); - - /** Constructor for a Device node */ - VariableNetworkNode(const std::string &name, const std::string &deviceAlias, const std::string ®isterName, - UpdateMode mode, VariableDirection direction, const std::type_info &valTyp=typeid(AnyType), size_t nElements=0); - - /** Constructor for a ControlSystem node */ - VariableNetworkNode(std::string publicName, VariableDirection direction, - const std::type_info &valTyp=typeid(AnyType), size_t nElements=0); - - /** Constructor for a TriggerReceiver node triggering the data transfer of another network. The additional dummy - * argument is only there to discriminate the signature from the copy constructor and will be ignored. */ - VariableNetworkNode(VariableNetworkNode& nodeToTrigger, int); - - /** Constructor to wrap a VariableNetworkNode_data pointer */ - VariableNetworkNode(boost::shared_ptr<VariableNetworkNode_data> pdata); - - /** Default constructor for an invalid node */ - VariableNetworkNode(); - - /** Factory function for a constant (a constructor cannot be templated) */ - template<typename UserType> - static VariableNetworkNode makeConstant(bool makeFeeder, UserType value=0, size_t length=1); - - /** Change meta data (name, unit, description and optionally tags). This function may only be used on - * Application-type nodes. If the optional argument tags is omitted, the tags will not be changed. To clear the - * tags, an empty set can be passed. */ - void setMetaData(const std::string &name, const std::string &unit, const std::string &description); - void setMetaData(const std::string &name, const std::string &unit, const std::string &description, - const std::unordered_set<std::string> &tags); - - /** Set the owner network of this node. If an owner network is already set, an assertion will be raised */ - void setOwner(VariableNetwork *network); - - /** Clear the owner network of this node. */ - void clearOwner(); - - /** Set the value type for this node. Only possible of the current value type is undecided (i.e. AnyType). */ - void setValueType(const std::type_info& newType) const; - - /** Set the direction for this node. Only possible if current direction is VariableDirection::feeding and the - * node type is NodeType::ControlSystem. */ - void setDirection(VariableDirection newDirection) const; - - /** Function checking if the node requires a fixed implementation */ - bool hasImplementation() const; - - /** Compare two nodes */ - bool operator==(const VariableNetworkNode& other) const; - bool operator!=(const VariableNetworkNode& other) const; - bool operator<(const VariableNetworkNode& other) const; - - /** Connect two nodes */ - VariableNetworkNode operator>>(VariableNetworkNode other); - - /** Add a trigger */ - VariableNetworkNode operator[](VariableNetworkNode trigger); - - /** Check for presence of an external trigger */ - bool hasExternalTrigger() const; +class VariableNetwork; +class AccessorBase; +class EntityOwner; +struct VariableNetworkNode_data; + +/** Pseudo type to identify nodes which can have arbitrary types */ +class AnyType {}; + +/** Class describing a node of a variable network */ +class VariableNetworkNode { + +public: + /** Copy-constructor: Just copy the pointer to the data storage object */ + VariableNetworkNode(const VariableNetworkNode &other); + + /** Copy by assignment operator: Just copy the pointer to the data storage + * object */ + VariableNetworkNode &operator=(const VariableNetworkNode &rightHandSide); + + /** Constructor for an Application node */ + VariableNetworkNode(EntityOwner *owner, + ChimeraTK::TransferElementAbstractor *accessorBridge, + const std::string &name, VariableDirection direction, + std::string unit, size_t nElements, UpdateMode mode, + const std::string &description, + const std::type_info *valueType, + const std::unordered_set<std::string> &tags = {}); + + /** Constructor for a Device node */ + VariableNetworkNode(const std::string &name, const std::string &deviceAlias, + const std::string ®isterName, UpdateMode mode, + VariableDirection direction, + const std::type_info &valTyp = typeid(AnyType), + size_t nElements = 0); + + /** Constructor for a ControlSystem node */ + VariableNetworkNode(std::string publicName, VariableDirection direction, + const std::type_info &valTyp = typeid(AnyType), + size_t nElements = 0); + + /** Constructor for a TriggerReceiver node triggering the data transfer of + * another network. The additional dummy argument is only there to + * discriminate the signature from the copy constructor and will be ignored. + */ + VariableNetworkNode(VariableNetworkNode &nodeToTrigger, int); + + /** Constructor to wrap a VariableNetworkNode_data pointer */ + VariableNetworkNode(boost::shared_ptr<VariableNetworkNode_data> pdata); + + /** Default constructor for an invalid node */ + VariableNetworkNode(); + + /** Factory function for a constant (a constructor cannot be templated) */ + template <typename UserType> + static VariableNetworkNode makeConstant(bool makeFeeder, UserType value = 0, + size_t length = 1); + + /** Change meta data (name, unit, description and optionally tags). This + * function may only be used on Application-type nodes. If the optional + * argument tags is omitted, the tags will not be changed. To clear the + * tags, an empty set can be passed. */ + void setMetaData(const std::string &name, const std::string &unit, + const std::string &description); + void setMetaData(const std::string &name, const std::string &unit, + const std::string &description, + const std::unordered_set<std::string> &tags); + + /** Set the owner network of this node. If an owner network is already set, an + * assertion will be raised */ + void setOwner(VariableNetwork *network); + + /** Clear the owner network of this node. */ + void clearOwner(); + + /** Set the value type for this node. Only possible of the current value type + * is undecided (i.e. AnyType). */ + void setValueType(const std::type_info &newType) const; + + /** Set the direction for this node. Only possible if current direction is + * VariableDirection::feeding and the node type is NodeType::ControlSystem. */ + void setDirection(VariableDirection newDirection) const; + + /** Function checking if the node requires a fixed implementation */ + bool hasImplementation() const; + + /** Compare two nodes */ + bool operator==(const VariableNetworkNode &other) const; + bool operator!=(const VariableNetworkNode &other) const; + bool operator<(const VariableNetworkNode &other) const; + + /** Connect two nodes */ + VariableNetworkNode operator>>(VariableNetworkNode other); + + /** Add a trigger */ + VariableNetworkNode operator[](VariableNetworkNode trigger); + + /** Check for presence of an external trigger */ + bool hasExternalTrigger() const; + + /** Return the external trigger node. if no external trigger is present, an + * assertion will be raised. */ + VariableNetworkNode getExternalTrigger(); - /** Return the external trigger node. if no external trigger is present, an assertion will be raised. */ - VariableNetworkNode getExternalTrigger(); + /** Print node information to std::cout */ + void dump(std::ostream &stream = std::cout) const; - /** Print node information to std::cout */ - void dump(std::ostream& stream=std::cout) const; + /** Check if the node already has an owner */ + bool hasOwner() const; + + /** Add a tag. This function may only be used on Application-type nodes. Valid + * names for tags only contain + * alpha-numeric characters (i.e. no spaces and no special characters). @todo + * enforce this!*/ + void addTag(const std::string &tag); - /** Check if the node already has an owner */ - bool hasOwner() const; + /** Getter for the properties */ + NodeType getType() const; + UpdateMode getMode() const; + VariableDirection getDirection() const; + const std::type_info &getValueType() const; + std::string getName() const; + std::string getQualifiedName() const; + const std::string &getUnit() const; + const std::string &getDescription() const; + VariableNetwork &getOwner() const; + VariableNetworkNode getNodeToTrigger(); + const std::string &getPublicName() const; + const std::string &getDeviceAlias() const; + const std::string &getRegisterName() const; + const std::unordered_set<std::string> &getTags() const; + void setNumberOfElements(size_t nElements); + size_t getNumberOfElements() const; + ChimeraTK::TransferElementAbstractor &getAppAccessorNoType(); - /** Add a tag. This function may only be used on Application-type nodes. Valid names for tags only contain - * alpha-numeric characters (i.e. no spaces and no special characters). @todo enforce this!*/ - void addTag(const std::string &tag); + void setPublicName(const std::string &name) const; - /** Getter for the properties */ - NodeType getType() const; - UpdateMode getMode() const; - VariableDirection getDirection() const; - const std::type_info& getValueType() const; - std::string getName() const; - std::string getQualifiedName() const; - const std::string& getUnit() const; - const std::string& getDescription() const; - VariableNetwork& getOwner() const; - VariableNetworkNode getNodeToTrigger(); - const std::string& getPublicName() const; - const std::string& getDeviceAlias() const; - const std::string& getRegisterName() const; - const std::unordered_set<std::string>& getTags() const; - void setNumberOfElements(size_t nElements); - size_t getNumberOfElements() const; - ChimeraTK::TransferElementAbstractor& getAppAccessorNoType(); + template <typename UserType> + ChimeraTK::NDRegisterAccessorAbstractor<UserType> &getAppAccessor() const; - void setPublicName(const std::string& name) const; + template <typename UserType> + void setAppAccessorImplementation( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> impl) const; - template<typename UserType> - ChimeraTK::NDRegisterAccessorAbstractor<UserType>& getAppAccessor() const; + template <typename UserType> + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> + getConstAccessor() const; - template<typename UserType> - void setAppAccessorImplementation(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> impl) const; + /** Return the unique ID of this node (will change every time the application + * is started). */ + const void *getUniqueId() const { return pdata.get(); } + /** Change pointer to the accessor. May only be used for application nodes. */ + void setAppAccessorPointer(ChimeraTK::TransferElementAbstractor *accessor); + + EntityOwner *getOwningModule() const; + + void setOwningModule(EntityOwner *newOwner) const; + + void accept(Visitor<VariableNetworkNode> &visitor) const; + + // protected: @todo make protected again (with proper interface extension) + + boost::shared_ptr<VariableNetworkNode_data> pdata; +}; - template<typename UserType> - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> getConstAccessor() const; +/*********************************************************************************************************************/ - /** Return the unique ID of this node (will change every time the application is started). */ - const void* getUniqueId() const { return pdata.get(); } +/** We use a pimpl pattern so copied instances of VariableNetworkNode refer to + * the same instance of the data structure and thus stay consistent all the + * time. */ +struct VariableNetworkNode_data { - /** Change pointer to the accessor. May only be used for application nodes. */ - void setAppAccessorPointer(ChimeraTK::TransferElementAbstractor *accessor); + VariableNetworkNode_data() {} - EntityOwner* getOwningModule() const; + /** Type of the node (Application, Device, ControlSystem, Trigger) */ + NodeType type{NodeType::invalid}; - void setOwningModule(EntityOwner *newOwner) const; + /** Update mode: poll or push */ + UpdateMode mode{UpdateMode::invalid}; - void accept(Visitor<VariableNetworkNode> &visitor) const; + /** Node direction: feeding or consuming */ + VariableDirection direction{VariableDirection::invalid, false}; + + /** Value type of this node. If the type_info is the typeid of AnyType, the + * actual type can be decided when making the connections. */ + const std::type_info *valueType{&typeid(AnyType)}; + + /** Engineering unit. If equal to ChimeraTK::TransferElement::unitNotSet, no + * unit has been defined (and any unit is allowed). */ + std::string unit{ChimeraTK::TransferElement::unitNotSet}; + + /** Description */ + std::string description{""}; + + /** The network this node belongs to */ + VariableNetwork *network{nullptr}; + + /** Pointer to implementation if type == Constant */ + boost::shared_ptr<ChimeraTK::TransferElement> constNode; + + /** Pointer to implementation if type == Application */ + ChimeraTK::TransferElementAbstractor *appNode{nullptr}; + + /** Pointer to network which should be triggered by this node */ + VariableNetworkNode nodeToTrigger{nullptr}; + + /** Pointer to the network providing the external trigger. May only be used + * for feeding nodes with an update mode poll. When enabled, the update mode + * will be converted into push. */ + VariableNetworkNode externalTrigger{nullptr}; + + /** Public name if type == ControlSystem */ + std::string publicName; + + /** Accessor name if type == Application */ + std::string name; + std::string qualifiedName; + + /** Device information if type == Device */ + std::string deviceAlias; + std::string registerName; - //protected: @todo make protected again (with proper interface extension) + /** Number of elements in the variable. 0 means not yet decided. */ + size_t nElements{0}; - boost::shared_ptr<VariableNetworkNode_data> pdata; - }; + /** Set of tags if type == Application */ + std::unordered_set<std::string> tags; - /*********************************************************************************************************************/ + /** Map to store triggered versions of this node. The map key is the trigger + * node and the value is the node with the respective trigger added. */ + std::map<VariableNetworkNode, VariableNetworkNode> nodeWithTrigger; - /** We use a pimpl pattern so copied instances of VariableNetworkNode refer to the same instance of the data - * structure and thus stay consistent all the time. */ - struct VariableNetworkNode_data { - - VariableNetworkNode_data() {} - - /** Type of the node (Application, Device, ControlSystem, Trigger) */ - NodeType type{NodeType::invalid}; - - /** Update mode: poll or push */ - UpdateMode mode{UpdateMode::invalid}; - - /** Node direction: feeding or consuming */ - VariableDirection direction{VariableDirection::invalid, false}; - - /** Value type of this node. If the type_info is the typeid of AnyType, the actual type can be decided when making - * the connections. */ - const std::type_info* valueType{&typeid(AnyType)}; - - /** Engineering unit. If equal to ChimeraTK::TransferElement::unitNotSet, no unit has been defined (and any unit is allowed). */ - std::string unit{ChimeraTK::TransferElement::unitNotSet}; - - /** Description */ - std::string description{""}; - - /** The network this node belongs to */ - VariableNetwork *network{nullptr}; - - /** Pointer to implementation if type == Constant */ - boost::shared_ptr<ChimeraTK::TransferElement> constNode; - - /** Pointer to implementation if type == Application */ - ChimeraTK::TransferElementAbstractor *appNode{nullptr}; - - /** Pointer to network which should be triggered by this node */ - VariableNetworkNode nodeToTrigger{nullptr}; - - /** Pointer to the network providing the external trigger. May only be used for feeding nodes with an - * update mode poll. When enabled, the update mode will be converted into push. */ - VariableNetworkNode externalTrigger{nullptr}; - - /** Public name if type == ControlSystem */ - std::string publicName; - - /** Accessor name if type == Application */ - std::string name; - std::string qualifiedName; - - /** Device information if type == Device */ - std::string deviceAlias; - std::string registerName; - - /** Number of elements in the variable. 0 means not yet decided. */ - size_t nElements{0}; - - /** Set of tags if type == Application */ - std::unordered_set<std::string> tags; - - /** Map to store triggered versions of this node. The map key is the trigger node and the value is the node - * with the respective trigger added. */ - std::map<VariableNetworkNode, VariableNetworkNode> nodeWithTrigger; - - /** Pointer to the module owning this node */ - EntityOwner *owningModule{nullptr}; - - }; - - /*********************************************************************************************************************/ - /*** Implementations *************************************************************************************************/ - /*********************************************************************************************************************/ - - template<typename UserType> - VariableNetworkNode VariableNetworkNode::makeConstant(bool makeFeeder, UserType value, size_t length) { - VariableNetworkNode node; - node.pdata = boost::make_shared<VariableNetworkNode_data>(); - node.pdata->constNode.reset(new ConstantAccessor<UserType>(value, length)); - node.pdata->type = NodeType::Constant; - node.pdata->valueType = &typeid(UserType); - node.pdata->nElements = length; - node.pdata->name = "*UNNAMED CONSTANT*"; - if(makeFeeder) { - node.pdata->direction = {VariableDirection::feeding, false}; - node.pdata->mode = UpdateMode::push; - } - else { - node.pdata->direction = {VariableDirection::consuming, false}; - node.pdata->mode = UpdateMode::poll; - } - return node; - } - - /*********************************************************************************************************************/ - - template<typename UserType> - ChimeraTK::NDRegisterAccessorAbstractor<UserType>& VariableNetworkNode::getAppAccessor() const { - assert(typeid(UserType) == getValueType()); - assert(pdata->type == NodeType::Application); - auto accessor = static_cast<ChimeraTK::NDRegisterAccessorAbstractor<UserType>*>(pdata->appNode); - assert(accessor != nullptr); - return *accessor; - } - - /*********************************************************************************************************************/ - - template<typename UserType> - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> VariableNetworkNode::getConstAccessor() const { - return boost::dynamic_pointer_cast<ChimeraTK::NDRegisterAccessor<UserType>>(pdata->constNode); - } + /** Pointer to the module owning this node */ + EntityOwner *owningModule{nullptr}; +}; - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ +/*** Implementations + * *************************************************************************************************/ +/*********************************************************************************************************************/ - template<typename UserType> - void VariableNetworkNode::setAppAccessorImplementation(boost::shared_ptr<NDRegisterAccessor<UserType>> impl) const { - auto decorated = boost::make_shared<VersionNumberUpdatingRegisterDecorator<UserType>>(impl, getOwningModule()); - getAppAccessor<UserType>().replace(decorated); +template <typename UserType> +VariableNetworkNode VariableNetworkNode::makeConstant(bool makeFeeder, + UserType value, + size_t length) { + VariableNetworkNode node; + node.pdata = boost::make_shared<VariableNetworkNode_data>(); + node.pdata->constNode.reset(new ConstantAccessor<UserType>(value, length)); + node.pdata->type = NodeType::Constant; + node.pdata->valueType = &typeid(UserType); + node.pdata->nElements = length; + node.pdata->name = "*UNNAMED CONSTANT*"; + if (makeFeeder) { + node.pdata->direction = {VariableDirection::feeding, false}; + node.pdata->mode = UpdateMode::push; + } else { + node.pdata->direction = {VariableDirection::consuming, false}; + node.pdata->mode = UpdateMode::poll; } + return node; +} + +/*********************************************************************************************************************/ + +template <typename UserType> +ChimeraTK::NDRegisterAccessorAbstractor<UserType> & +VariableNetworkNode::getAppAccessor() const { + assert(typeid(UserType) == getValueType()); + assert(pdata->type == NodeType::Application); + auto accessor = + static_cast<ChimeraTK::NDRegisterAccessorAbstractor<UserType> *>( + pdata->appNode); + assert(accessor != nullptr); + return *accessor; +} + +/*********************************************************************************************************************/ + +template <typename UserType> +boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> +VariableNetworkNode::getConstAccessor() const { + return boost::dynamic_pointer_cast<ChimeraTK::NDRegisterAccessor<UserType>>( + pdata->constNode); +} + +/*********************************************************************************************************************/ + +template <typename UserType> +void VariableNetworkNode::setAppAccessorImplementation( + boost::shared_ptr<NDRegisterAccessor<UserType>> impl) const { + auto decorated = + boost::make_shared<VersionNumberUpdatingRegisterDecorator<UserType>>( + impl, getOwningModule()); + getAppAccessor<UserType>().replace(decorated); +} } /* namespace ChimeraTK */ diff --git a/include/VariableNetworkNodeDumpingVisitor.h b/include/VariableNetworkNodeDumpingVisitor.h index fbf391ee..c6dc0f78 100644 --- a/include/VariableNetworkNodeDumpingVisitor.h +++ b/include/VariableNetworkNodeDumpingVisitor.h @@ -2,9 +2,9 @@ #include "Visitor.h" +#include <functional> // for std::reference_wrapper #include <list> #include <ostream> -#include <functional> // for std::reference_wrapper namespace ChimeraTK { @@ -14,62 +14,65 @@ class VariableNetworkNode; /** * @brief A helper class to replace the output stream temporarily * - * This is a helper class that is used in the Graphviz dumper to be able to dump the nodes to a stringstream - * instead of directly to the file. + * This is a helper class that is used in the Graphviz dumper to be able to dump + * the nodes to a stringstream instead of directly to the file. * - * Ideally, the pushStream()/popStream() functions should be called in pairs but popStream() will do nothing - * if the stack is empty. + * Ideally, the pushStream()/popStream() functions should be called in pairs but + * popStream() will do nothing if the stack is empty. */ class PushableStream { public: - PushableStream(std::ostream &stream) : _streamStack{stream} {} - virtual ~PushableStream() {} + PushableStream(std::ostream &stream) : _streamStack{stream} {} + virtual ~PushableStream() {} + + void pushStream(std::ostream &stream) { _streamStack.push_back(stream); } - void pushStream(std::ostream& stream) { - _streamStack.push_back(stream); - } + std::ostream &stream() { return _streamStack.back().get(); } - std::ostream& stream() { return _streamStack.back().get(); } + void popStream() { + if (_streamStack.size() == 1) + return; - void popStream() { - if (_streamStack.size() == 1) - return; + _streamStack.pop_back(); + } - _streamStack.pop_back(); - } private: - std::list<std::reference_wrapper<std::ostream>> _streamStack; + std::list<std::reference_wrapper<std::ostream>> _streamStack; }; /** * @brief The VariableNetworkNodeDumpingVisitor class * - * This class is serving as one of the base classes for the Graphviz dumper as well as the textual dumper - * providing detailed information about a node. + * This class is serving as one of the base classes for the Graphviz dumper as + * well as the textual dumper providing detailed information about a node. */ -class VariableNetworkNodeDumpingVisitor : public Visitor<VariableNetworkNode>, public PushableStream { +class VariableNetworkNodeDumpingVisitor : public Visitor<VariableNetworkNode>, + public PushableStream { public: - /** - * @brief VariableNetworkNodeDumpingVisitor::VariableNetworkNodeDumpingVisitor - * @param stream instance of std::ostream to write to - * @param separator the separator to use - * - * Separator is used to be able to use the function in the Graphviz and textual connection dumper. - * We are using newlines for Graphviz, and space for textual - */ - VariableNetworkNodeDumpingVisitor(std::ostream &stream, const std::string& separator); - virtual ~VariableNetworkNodeDumpingVisitor() {} + /** + * @brief VariableNetworkNodeDumpingVisitor::VariableNetworkNodeDumpingVisitor + * @param stream instance of std::ostream to write to + * @param separator the separator to use + * + * Separator is used to be able to use the function in the Graphviz and + * textual connection dumper. We are using newlines for Graphviz, and space + * for textual + */ + VariableNetworkNodeDumpingVisitor(std::ostream &stream, + const std::string &separator); + virtual ~VariableNetworkNodeDumpingVisitor() {} - /** - * @brief dispatch - * @param t Node to visit - * - * Visitor function for VariableNetworkNode. Will dump a verbose description of the node - */ - void dispatch(const VariableNetworkNode& t); + /** + * @brief dispatch + * @param t Node to visit + * + * Visitor function for VariableNetworkNode. Will dump a verbose description + * of the node + */ + void dispatch(const VariableNetworkNode &t); private: - std::string _separator; + std::string _separator; }; } // namespace ChimeraTK diff --git a/include/VersionNumberUpdatingRegisterDecorator.h b/include/VersionNumberUpdatingRegisterDecorator.h index d234093d..cb03c9c4 100644 --- a/include/VersionNumberUpdatingRegisterDecorator.h +++ b/include/VersionNumberUpdatingRegisterDecorator.h @@ -7,24 +7,26 @@ namespace ChimeraTK { - class EntityOwner; - - /** - * NDRegisterAccessorDecorator which sets the current version number of the owning ApplicationModule in postRead - */ - template<typename T> - struct VersionNumberUpdatingRegisterDecorator : NDRegisterAccessorDecorator<T,T> { - - VersionNumberUpdatingRegisterDecorator(const boost::shared_ptr<NDRegisterAccessor<T>> &target, - EntityOwner *owner) - : NDRegisterAccessorDecorator<T,T>(target), _owner(owner) {} - - void doPostRead() override; - - protected: - - EntityOwner *_owner; - }; +class EntityOwner; + +/** + * NDRegisterAccessorDecorator which sets the current version number of the + * owning ApplicationModule in postRead + */ +template <typename T> +struct VersionNumberUpdatingRegisterDecorator + : NDRegisterAccessorDecorator<T, T> { + + VersionNumberUpdatingRegisterDecorator( + const boost::shared_ptr<NDRegisterAccessor<T>> &target, + EntityOwner *owner) + : NDRegisterAccessorDecorator<T, T>(target), _owner(owner) {} + + void doPostRead() override; + +protected: + EntityOwner *_owner; +}; } /* namespace ChimeraTK */ diff --git a/include/VirtualModule.h b/include/VirtualModule.h index 33c0a09b..0f632044 100644 --- a/include/VirtualModule.h +++ b/include/VirtualModule.h @@ -12,63 +12,66 @@ #include <boost/thread.hpp> -#include <ChimeraTK/RegisterPath.h> #include "Module.h" +#include <ChimeraTK/RegisterPath.h> namespace ChimeraTK { - /** A virtual module generated by EntityOwner::findTag(). */ - class VirtualModule : public Module { - - public: - - /** Constructor */ - VirtualModule(const std::string &name, const std::string &description, ModuleType moduleType) - : Module(nullptr, name, description), _moduleType(moduleType) - { - if(name.find_first_of("/") != std::string::npos) { - throw ChimeraTK::logic_error("Module names must not contain slashes: '"+name+"'."); - } - } - - /** Copy constructor */ - VirtualModule(const VirtualModule &other); +/** A virtual module generated by EntityOwner::findTag(). */ +class VirtualModule : public Module { - /** Assignment operator */ - VirtualModule& operator=(const VirtualModule &other); +public: + /** Constructor */ + VirtualModule(const std::string &name, const std::string &description, + ModuleType moduleType) + : Module(nullptr, name, description), _moduleType(moduleType) { + if (name.find_first_of("/") != std::string::npos) { + throw ChimeraTK::logic_error("Module names must not contain slashes: '" + + name + "'."); + } + } - /** Destructor */ - virtual ~VirtualModule(); + /** Copy constructor */ + VirtualModule(const VirtualModule &other); - VariableNetworkNode operator()(const std::string& variableName) const override; + /** Assignment operator */ + VirtualModule &operator=(const VirtualModule &other); - Module& operator[](const std::string& moduleName) const override; + /** Destructor */ + virtual ~VirtualModule(); - void connectTo(const Module &target, VariableNetworkNode trigger={}) const override; + VariableNetworkNode + operator()(const std::string &variableName) const override; - /** Add an accessor. The accessor instance will be added to an internal list. */ - void addAccessor(VariableNetworkNode accessor); + Module &operator[](const std::string &moduleName) const override; - /** Add a virtual sub-module. The module instance will be added to an internal list. */ - void addSubModule(VirtualModule module); + void connectTo(const Module &target, + VariableNetworkNode trigger = {}) const override; - /** Return the submodule with the given name. If it doesn't exist, create it first. */ - VirtualModule& createAndGetSubmodule(const RegisterPath& moduleName); + /** Add an accessor. The accessor instance will be added to an internal list. + */ + void addAccessor(VariableNetworkNode accessor); - /** Like createAndGetSubmodule(), but recursively create a hierarchy of submodules separated by "/" in the - * moduleName. */ - VirtualModule& createAndGetSubmoduleRecursive(const RegisterPath& moduleName); + /** Add a virtual sub-module. The module instance will be added to an internal + * list. */ + void addSubModule(VirtualModule module); - ModuleType getModuleType() const override { return _moduleType; } + /** Return the submodule with the given name. If it doesn't exist, create it + * first. */ + VirtualModule &createAndGetSubmodule(const RegisterPath &moduleName); - const Module& virtualise() const override; + /** Like createAndGetSubmodule(), but recursively create a hierarchy of + * submodules separated by "/" in the moduleName. */ + VirtualModule &createAndGetSubmoduleRecursive(const RegisterPath &moduleName); - protected: + ModuleType getModuleType() const override { return _moduleType; } - std::list<VirtualModule> submodules; - ModuleType _moduleType; + const Module &virtualise() const override; - }; +protected: + std::list<VirtualModule> submodules; + ModuleType _moduleType; +}; } /* namespace ChimeraTK */ diff --git a/include/Visitor.h b/include/Visitor.h index b15e78ca..fb98636d 100644 --- a/include/Visitor.h +++ b/include/Visitor.h @@ -2,22 +2,22 @@ namespace ChimeraTK { -/* Losely based on https://stackoverflow.com/questions/11796121/implementing-the-visitor-pattern-using-c-templates#11802080 */ +/* Losely based on + * https://stackoverflow.com/questions/11796121/implementing-the-visitor-pattern-using-c-templates#11802080 + */ -template <typename... Types> -class Visitor; +template <typename... Types> class Visitor; -template <typename T> -class Visitor<T> { +template <typename T> class Visitor<T> { public: - virtual void dispatch(const T& t) = 0; + virtual void dispatch(const T &t) = 0; }; template <typename T, typename... Types> class Visitor<T, Types...> : public Visitor<T>, public Visitor<Types...> { public: - using Visitor<Types...>::dispatch; - using Visitor<T>::dispatch; + using Visitor<Types...>::dispatch; + using Visitor<T>::dispatch; }; } // namespace ChimeraTK diff --git a/include/VisitorHelper.h b/include/VisitorHelper.h index cce1390e..885e86d6 100644 --- a/include/VisitorHelper.h +++ b/include/VisitorHelper.h @@ -8,7 +8,7 @@ class VariableNetworkNode; namespace detail { std::string encodeDotNodeName(std::string name); -std::string nodeName(const VariableNetworkNode& node); +std::string nodeName(const VariableNetworkNode &node); -} // namespace detail) -}// namespace ChimeraTK +} // namespace detail +} // namespace ChimeraTK diff --git a/include/XMLGeneratorVisitor.h b/include/XMLGeneratorVisitor.h index 63335cad..7e8c869d 100644 --- a/include/XMLGeneratorVisitor.h +++ b/include/XMLGeneratorVisitor.h @@ -2,14 +2,14 @@ #include "Visitor.h" -#include <string> #include <memory> +#include <string> // Forward declarations namespace xmlpp { - class Document; - class Element; -} +class Document; +class Element; +} // namespace xmlpp namespace ChimeraTK { // Forward declarations @@ -19,19 +19,21 @@ class VariableNetworkNode; /** * @brief The XMLGeneratorVisitor class * - * This class is responsible for generating the XML representation of the Variables in an Application + * This class is responsible for generating the XML representation of the + * Variables in an Application */ class XMLGeneratorVisitor : public Visitor<Application, VariableNetworkNode> { public: - XMLGeneratorVisitor(); - virtual ~XMLGeneratorVisitor() {} - void dispatch(const Application& app); - void dispatch(const VariableNetworkNode &node); + XMLGeneratorVisitor(); + virtual ~XMLGeneratorVisitor() {} + void dispatch(const Application &app); + void dispatch(const VariableNetworkNode &node); + + void save(const std::string &filename); - void save(const std::string &filename); private: - std::shared_ptr<xmlpp::Document> _doc; - xmlpp::Element *_rootElement; + std::shared_ptr<xmlpp::Document> _doc; + xmlpp::Element *_rootElement; }; } // namespace ChimeraTK diff --git a/src/Application.cc b/src/Application.cc index 2840b781..acebf5c9 100644 --- a/src/Application.cc +++ b/src/Application.cc @@ -5,10 +5,10 @@ * Author: Martin Hierholzer */ -#include <string> -#include <thread> #include <exception> #include <fstream> +#include <string> +#include <thread> #include <boost/fusion/container/map.hpp> @@ -16,20 +16,20 @@ #include "Application.h" #include "ApplicationModule.h" -#include "ThreadedFanOut.h" +#include "ArrayAccessor.h" +#include "ConstantAccessor.h" #include "ConsumingFanOut.h" +#include "DebugPrintAccessorDecorator.h" +#include "DeviceModule.h" #include "FeedingFanOut.h" -#include "TriggerFanOut.h" -#include "VariableNetworkNode.h" #include "ScalarAccessor.h" -#include "ArrayAccessor.h" -#include "ConstantAccessor.h" #include "TestableModeAccessorDecorator.h" -#include "DebugPrintAccessorDecorator.h" -#include "Visitor.h" +#include "ThreadedFanOut.h" +#include "TriggerFanOut.h" #include "VariableNetworkGraphDumpingVisitor.h" +#include "VariableNetworkNode.h" +#include "Visitor.h" #include "XMLGeneratorVisitor.h" -#include "DeviceModule.h" using namespace ChimeraTK; @@ -37,78 +37,88 @@ std::mutex Application::testableMode_mutex; /*********************************************************************************************************************/ -Application::Application(const std::string& name) : ApplicationBase(name), EntityOwner(name, "") { +Application::Application(const std::string &name) + : ApplicationBase(name), EntityOwner(name, "") { // check if the application name has been set - if(applicationName == "") { + if (applicationName == "") { shutdown(); - throw ChimeraTK::logic_error("Error: An instance of Application must have its applicationName set."); + throw ChimeraTK::logic_error( + "Error: An instance of Application must have its applicationName set."); } // check if application name contains illegal characters - std::string legalChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"; - bool nameContainsIllegalChars = name.find_first_not_of(legalChars) != std::string::npos; - if(nameContainsIllegalChars) { + std::string legalChars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"; + bool nameContainsIllegalChars = + name.find_first_not_of(legalChars) != std::string::npos; + if (nameContainsIllegalChars) { shutdown(); - throw ChimeraTK::logic_error( - "Error: The application name may only contain alphanumeric characters and underscores."); + throw ChimeraTK::logic_error("Error: The application name may only contain " + "alphanumeric characters and underscores."); } } /*********************************************************************************************************************/ void Application::initialise() { - // call the user-defined defineConnections() function which describes the structure of the application + // call the user-defined defineConnections() function which describes the + // structure of the application defineConnections(); // connect any unconnected accessors with constant values processUnconnectedNodes(); - // realise the connections between variable accessors as described in the initialise() function + // realise the connections between variable accessors as described in the + // initialise() function makeConnections(); } /*********************************************************************************************************************/ -/** Functor class to create a constant for otherwise unconnected variables, suitable for boost::fusion::for_each(). */ +/** Functor class to create a constant for otherwise unconnected variables, + * suitable for boost::fusion::for_each(). */ namespace { - struct CreateConstantForUnconnectedVar { - /// @todo test unconnected variables for all types! - CreateConstantForUnconnectedVar(const std::type_info& typeInfo, bool makeFeeder, size_t length) - : _typeInfo(typeInfo), _makeFeeder(makeFeeder), _length(length) {} - - template<typename PAIR> - void operator()(PAIR&) const { - if(typeid(typename PAIR::first_type) != _typeInfo) return; - theNode = VariableNetworkNode::makeConstant<typename PAIR::first_type>( - _makeFeeder, typename PAIR::first_type(), _length); - done = true; - } +struct CreateConstantForUnconnectedVar { + /// @todo test unconnected variables for all types! + CreateConstantForUnconnectedVar(const std::type_info &typeInfo, + bool makeFeeder, size_t length) + : _typeInfo(typeInfo), _makeFeeder(makeFeeder), _length(length) {} + + template <typename PAIR> void operator()(PAIR &) const { + if (typeid(typename PAIR::first_type) != _typeInfo) + return; + theNode = VariableNetworkNode::makeConstant<typename PAIR::first_type>( + _makeFeeder, typename PAIR::first_type(), _length); + done = true; + } - const std::type_info& _typeInfo; - bool _makeFeeder; - size_t _length; - mutable bool done{false}; - mutable VariableNetworkNode theNode; - }; + const std::type_info &_typeInfo; + bool _makeFeeder; + size_t _length; + mutable bool done{false}; + mutable VariableNetworkNode theNode; +}; } // namespace /*********************************************************************************************************************/ void Application::processUnconnectedNodes() { - for(auto& module : getSubmoduleListRecursive()) { - for(auto& accessor : module->getAccessorList()) { - if(!accessor.hasOwner()) { - if(enableUnconnectedVariablesWarning) { - std::cerr << "*** Warning: Variable '" << accessor.getQualifiedName() - << "' is not connected. " // LCOV_EXCL_LINE - "Reading will always result in 0, writing will be ignored." - << std::endl; // LCOV_EXCL_LINE + for (auto &module : getSubmoduleListRecursive()) { + for (auto &accessor : module->getAccessorList()) { + if (!accessor.hasOwner()) { + if (enableUnconnectedVariablesWarning) { + std::cerr + << "*** Warning: Variable '" << accessor.getQualifiedName() + << "' is not connected. " // LCOV_EXCL_LINE + "Reading will always result in 0, writing will be ignored." + << std::endl; // LCOV_EXCL_LINE } networkList.emplace_back(); networkList.back().addNode(accessor); bool makeFeeder = !(networkList.back().hasFeedingNode()); size_t length = accessor.getNumberOfElements(); - auto callable = CreateConstantForUnconnectedVar(accessor.getValueType(), makeFeeder, length); + auto callable = CreateConstantForUnconnectedVar(accessor.getValueType(), + makeFeeder, length); boost::fusion::for_each(ChimeraTK::userTypeMap(), std::ref(callable)); assert(callable.done); constantList.emplace_back(callable.theNode); @@ -122,18 +132,20 @@ void Application::processUnconnectedNodes() { void Application::checkConnections() { // check all networks for validity - for(auto& network : networkList) { + for (auto &network : networkList) { network.check(); } // check if all accessors are connected - // note: this in principle cannot happen, since processUnconnectedNodes() is called before - for(auto& module : getSubmoduleListRecursive()) { - for(auto& accessor : module->getAccessorList()) { - if(!accessor.hasOwner()) { - throw ChimeraTK::logic_error("The accessor '" + accessor.getName() + "' of the module '" + - module->getName() + // LCOV_EXCL_LINE - "' was not connected!"); // LCOV_EXCL_LINE + // note: this in principle cannot happen, since processUnconnectedNodes() is + // called before + for (auto &module : getSubmoduleListRecursive()) { + for (auto &accessor : module->getAccessorList()) { + if (!accessor.hasOwner()) { + throw ChimeraTK::logic_error("The accessor '" + accessor.getName() + + "' of the module '" + + module->getName() + // LCOV_EXCL_LINE + "' was not connected!"); // LCOV_EXCL_LINE } } } @@ -145,31 +157,31 @@ void Application::run() { assert(applicationName != ""); // prepare the modules - for(auto& module : getSubmoduleListRecursive()) { + for (auto &module : getSubmoduleListRecursive()) { module->prepare(); } // start the necessary threads for the FanOuts etc. - for(auto& internalModule : internalModuleList) { + for (auto &internalModule : internalModuleList) { internalModule->activate(); } - // read all input variables once, to set the startup value e.g. coming from the config file - // (without triggering an action inside the application) - for(auto& module : getSubmoduleListRecursive()) { - for(auto& variable : module->getAccessorList()) { - if(variable.getDirection().dir == VariableDirection::consuming) { + // read all input variables once, to set the startup value e.g. coming from + // the config file (without triggering an action inside the application) + for (auto &module : getSubmoduleListRecursive()) { + for (auto &variable : module->getAccessorList()) { + if (variable.getDirection().dir == VariableDirection::consuming) { variable.getAppAccessorNoType().readLatest(); } } } // start the threads for the modules - for(auto& module : getSubmoduleListRecursive()) { + for (auto &module : getSubmoduleListRecursive()) { std::cout << module->getFullDescription() << std::endl; module->run(); } - for(auto& deviceModule : deviceModuleList) { + for (auto &deviceModule : deviceModuleList) { deviceModule->run(); } } @@ -177,23 +189,25 @@ void Application::run() { /*********************************************************************************************************************/ void Application::shutdown() { - // first allow to run the application threads again, if we are in testable mode - if(testableMode && testableModeTestLock()) { + // first allow to run the application threads again, if we are in testable + // mode + if (testableMode && testableModeTestLock()) { testableModeUnlock("shutdown"); } - // deactivate the FanOuts first, since they have running threads inside accessing the modules etc. - // (note: the modules are members of the Application implementation and thus get destroyed after this destructor) - for(auto& internalModule : internalModuleList) { + // deactivate the FanOuts first, since they have running threads inside + // accessing the modules etc. (note: the modules are members of the + // Application implementation and thus get destroyed after this destructor) + for (auto &internalModule : internalModuleList) { internalModule->deactivate(); } // next deactivate the modules, as they have running threads inside as well - for(auto& module : getSubmoduleListRecursive()) { + for (auto &module : getSubmoduleListRecursive()) { module->terminate(); } - for(auto& deviceModule : deviceModuleList) { + for (auto &deviceModule : deviceModuleList) { deviceModule->terminate(); } ApplicationBase::shutdown(); @@ -206,11 +220,12 @@ void Application::generateXML() { // define the connections defineConnections(); - // also search for unconnected nodes - this is here only executed to print the warnings + // also search for unconnected nodes - this is here only executed to print the + // warnings processUnconnectedNodes(); - // finalise connections: decide still-undecided details, in particular for control-system and device varibales, which - // get created "on the fly". + // finalise connections: decide still-undecided details, in particular for + // control-system and device varibales, which get created "on the fly". finaliseNetworks(); XMLGeneratorVisitor visitor; @@ -220,27 +235,28 @@ void Application::generateXML() { /*********************************************************************************************************************/ -VariableNetwork& Application::connect(VariableNetworkNode a, VariableNetworkNode b) { - // if one of the nodes has the value type AnyType, set it to the type of the other - // if both are AnyType, nothing changes. - if(a.getValueType() == typeid(AnyType)) { +VariableNetwork &Application::connect(VariableNetworkNode a, + VariableNetworkNode b) { + // if one of the nodes has the value type AnyType, set it to the type of the + // other if both are AnyType, nothing changes. + if (a.getValueType() == typeid(AnyType)) { a.setValueType(b.getValueType()); - } - else if(b.getValueType() == typeid(AnyType)) { + } else if (b.getValueType() == typeid(AnyType)) { b.setValueType(a.getValueType()); } - // if one of the nodes has not yet a defined number of elements, set it to the number of elements of the other. - // if both are undefined, nothing changes. - if(a.getNumberOfElements() == 0) { + // if one of the nodes has not yet a defined number of elements, set it to the + // number of elements of the other. if both are undefined, nothing changes. + if (a.getNumberOfElements() == 0) { a.setNumberOfElements(b.getNumberOfElements()); - } - else if(b.getNumberOfElements() == 0) { + } else if (b.getNumberOfElements() == 0) { b.setNumberOfElements(a.getNumberOfElements()); } - if(a.getNumberOfElements() != b.getNumberOfElements()) { + if (a.getNumberOfElements() != b.getNumberOfElements()) { std::stringstream what; - what << "*** ERROR: Cannot connect array variables with difference number of elements!" << std::endl; + what << "*** ERROR: Cannot connect array variables with difference number " + "of elements!" + << std::endl; what << "Node A:" << std::endl; a.dump(what); what << "Node B:" << std::endl; @@ -248,16 +264,18 @@ VariableNetwork& Application::connect(VariableNetworkNode a, VariableNetworkNode throw ChimeraTK::logic_error(what.str()); } - // if both nodes already have an owner, we are either already done (same owners) or we need to try to merge the - // networks - if(a.hasOwner() && b.hasOwner()) { - if(&(a.getOwner()) != &(b.getOwner())) { - auto& networkToMerge = b.getOwner(); + // if both nodes already have an owner, we are either already done (same + // owners) or we need to try to merge the networks + if (a.hasOwner() && b.hasOwner()) { + if (&(a.getOwner()) != &(b.getOwner())) { + auto &networkToMerge = b.getOwner(); bool success = a.getOwner().merge(networkToMerge); - if(!success) { + if (!success) { std::stringstream what; - what << "*** ERROR: Trying to connect two nodes which are already part of different networks, and merging these" - " networks is not possible (cannot have two non-control-system or two control-system feeders)!" + what << "*** ERROR: Trying to connect two nodes which are already part " + "of different networks, and merging these" + " networks is not possible (cannot have two non-control-system " + "or two control-system feeders)!" << std::endl; what << "Node A:" << std::endl; a.dump(what); @@ -269,8 +287,8 @@ VariableNetwork& Application::connect(VariableNetworkNode a, VariableNetworkNode b.getOwner().dump("", what); throw ChimeraTK::logic_error(what.str()); } - for(auto n = networkList.begin(); n != networkList.end(); ++n) { - if(&*n == &networkToMerge) { + for (auto n = networkList.begin(); n != networkList.end(); ++n) { + if (&*n == &networkToMerge) { networkList.erase(n); break; } @@ -278,12 +296,12 @@ VariableNetwork& Application::connect(VariableNetworkNode a, VariableNetworkNode } } // add b to the existing network of a - else if(a.hasOwner()) { + else if (a.hasOwner()) { a.getOwner().addNode(b); } // add a to the existing network of b - else if(b.hasOwner()) { + else if (b.hasOwner()) { b.getOwner().addNode(a); } // create new network @@ -297,22 +315,28 @@ VariableNetwork& Application::connect(VariableNetworkNode a, VariableNetworkNode /*********************************************************************************************************************/ -template<typename UserType> -boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> Application::createDeviceVariable( - const std::string& deviceAlias, const std::string& registerName, VariableDirection direction, UpdateMode mode, - size_t nElements) { +template <typename UserType> +boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> +Application::createDeviceVariable(const std::string &deviceAlias, + const std::string ®isterName, + VariableDirection direction, UpdateMode mode, + size_t nElements) { // open device if needed - if(deviceMap.count(deviceAlias) == 0) { - deviceMap[deviceAlias] = ChimeraTK::BackendFactory::getInstance().createBackend(deviceAlias); - if(!deviceMap[deviceAlias]->isOpen()) deviceMap[deviceAlias]->open(); + if (deviceMap.count(deviceAlias) == 0) { + deviceMap[deviceAlias] = + ChimeraTK::BackendFactory::getInstance().createBackend(deviceAlias); + if (!deviceMap[deviceAlias]->isOpen()) + deviceMap[deviceAlias]->open(); } // use wait_for_new_data mode if push update mode was requested ChimeraTK::AccessModeFlags flags{}; - if(mode == UpdateMode::push && direction.dir == VariableDirection::consuming) flags = {AccessMode::wait_for_new_data}; + if (mode == UpdateMode::push && direction.dir == VariableDirection::consuming) + flags = {AccessMode::wait_for_new_data}; // obatin the register accessor from the device - auto accessor = deviceMap[deviceAlias]->getRegisterAccessor<UserType>(registerName, nElements, 0, flags); + auto accessor = deviceMap[deviceAlias]->getRegisterAccessor<UserType>( + registerName, nElements, 0, flags); // return accessor return accessor; @@ -320,58 +344,66 @@ boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> Application::createDe /*********************************************************************************************************************/ -template<typename UserType> -boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> Application::createProcessVariable( - VariableNetworkNode const& node) { +template <typename UserType> +boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> +Application::createProcessVariable(VariableNetworkNode const &node) { // determine the SynchronizationDirection SynchronizationDirection dir; - if(node.getDirection().withReturn) { + if (node.getDirection().withReturn) { dir = SynchronizationDirection::bidirectional; - } - else if(node.getDirection().dir == VariableDirection::feeding) { + } else if (node.getDirection().dir == VariableDirection::feeding) { dir = SynchronizationDirection::controlSystemToDevice; - } - else { + } else { dir = SynchronizationDirection::deviceToControlSystem; } AccessModeFlags flags = {}; - if(node.getDirection().dir == VariableDirection::consuming) { // Application-to-controlsystem must be push-type + if (node.getDirection().dir == + VariableDirection::consuming) { // Application-to-controlsystem must be + // push-type flags = {AccessMode::wait_for_new_data}; - } - else { - for(auto& consumer : node.getOwner().getConsumingNodes()) { - if(consumer.getMode() == UpdateMode::push) flags = {AccessMode::wait_for_new_data}; + } else { + for (auto &consumer : node.getOwner().getConsumingNodes()) { + if (consumer.getMode() == UpdateMode::push) + flags = {AccessMode::wait_for_new_data}; } } // create the ProcessArray for the proper UserType - auto pvar = _processVariableManager->createProcessArray<UserType>(dir, node.getPublicName(), - node.getNumberOfElements(), node.getOwner().getUnit(), node.getOwner().getDescription(), {}, false, 3, flags); + auto pvar = _processVariableManager->createProcessArray<UserType>( + dir, node.getPublicName(), node.getNumberOfElements(), + node.getOwner().getUnit(), node.getOwner().getDescription(), {}, false, 3, + flags); assert(pvar->getName() != ""); // create variable ID auto varId = getNextVariableId(); pvIdMap[pvar->getUniqueId()] = varId; - // Decorate the process variable if testable mode is enabled and this is the receiving end of the variable. - // Also don't decorate, if the mode is polling. Instead flag the variable to be polling, so the TestFacility is aware - // of this. - if(testableMode && node.getDirection().dir == VariableDirection::feeding) { - // The transfer mode of this process variable is considered to be polling, if only one consumer exists and this - // consumer is polling. Reason: mulitple consumers will result in the use of a FanOut, so the communication up to - // the FanOut will be push-type, even if all consumers are poll-type. + // Decorate the process variable if testable mode is enabled and this is the + // receiving end of the variable. Also don't decorate, if the mode is polling. + // Instead flag the variable to be polling, so the TestFacility is aware of + // this. + if (testableMode && node.getDirection().dir == VariableDirection::feeding) { + // The transfer mode of this process variable is considered to be polling, + // if only one consumer exists and this consumer is polling. Reason: + // mulitple consumers will result in the use of a FanOut, so the + // communication up to the FanOut will be push-type, even if all consumers + // are poll-type. /// @todo Check if this is true! auto mode = UpdateMode::push; - if(node.getOwner().countConsumingNodes() == 1) { - if(node.getOwner().getConsumingNodes().front().getMode() == UpdateMode::poll) mode = UpdateMode::poll; + if (node.getOwner().countConsumingNodes() == 1) { + if (node.getOwner().getConsumingNodes().front().getMode() == + UpdateMode::poll) + mode = UpdateMode::poll; } - if(mode != UpdateMode::poll) { - auto pvarDec = boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvar, true, false, varId, varId); + if (mode != UpdateMode::poll) { + auto pvarDec = + boost::make_shared<TestableModeAccessorDecorator<UserType>>( + pvar, true, false, varId, varId); testableMode_names[varId] = "ControlSystem:" + node.getPublicName(); return pvarDec; - } - else { + } else { testableMode_isPollMode[varId] = true; } } @@ -382,35 +414,39 @@ boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> Application::createPr /*********************************************************************************************************************/ -template<typename UserType> +template <typename UserType> std::pair<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>> - Application::createApplicationVariable(VariableNetworkNode const& node, VariableNetworkNode const& consumer) { + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>> +Application::createApplicationVariable(VariableNetworkNode const &node, + VariableNetworkNode const &consumer) { // obtain the meta data size_t nElements = node.getNumberOfElements(); std::string name = node.getName(); assert(name != ""); AccessModeFlags flags = {}; - if(consumer.getType() != NodeType::invalid) { - if(consumer.getMode() == UpdateMode::push) flags = {AccessMode::wait_for_new_data}; - } - else { - if(node.getMode() == UpdateMode::push) flags = {AccessMode::wait_for_new_data}; + if (consumer.getType() != NodeType::invalid) { + if (consumer.getMode() == UpdateMode::push) + flags = {AccessMode::wait_for_new_data}; + } else { + if (node.getMode() == UpdateMode::push) + flags = {AccessMode::wait_for_new_data}; } // create the ProcessArray for the proper UserType std::pair<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>> + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>> pvarPair; - if(consumer.getType() != NodeType::invalid) - assert(node.getDirection().withReturn == consumer.getDirection().withReturn); - if(!node.getDirection().withReturn) { + if (consumer.getType() != NodeType::invalid) + assert(node.getDirection().withReturn == + consumer.getDirection().withReturn); + if (!node.getDirection().withReturn) { pvarPair = createSynchronizedProcessArray<UserType>( - nElements, name, node.getUnit(), node.getDescription(), {}, 3, false, {}, flags); - } - else { + nElements, name, node.getUnit(), node.getDescription(), {}, 3, false, + {}, flags); + } else { pvarPair = createBidirectionalSynchronizedProcessArray<UserType>( - nElements, name, node.getUnit(), node.getDescription(), {}, 3, {}, {}, flags); + nElements, name, node.getUnit(), node.getDescription(), {}, 3, {}, {}, + flags); } assert(pvarPair.first->getName() != ""); assert(pvarPair.second->getName() != ""); @@ -418,47 +454,57 @@ std::pair<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, // create variable IDs size_t varId = getNextVariableId(); size_t varIdReturn; - if(node.getDirection().withReturn) varIdReturn = getNextVariableId(); + if (node.getDirection().withReturn) + varIdReturn = getNextVariableId(); - // decorate the process variable if testable mode is enabled and mode is push-type - if(testableMode && node.getMode() == UpdateMode::push) { - if(!node.getDirection().withReturn) { + // decorate the process variable if testable mode is enabled and mode is + // push-type + if (testableMode && node.getMode() == UpdateMode::push) { + if (!node.getDirection().withReturn) { pvarPair.first = - boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.first, false, true, varId, varId); + boost::make_shared<TestableModeAccessorDecorator<UserType>>( + pvarPair.first, false, true, varId, varId); pvarPair.second = - boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.second, true, false, varId, varId); - } - else { + boost::make_shared<TestableModeAccessorDecorator<UserType>>( + pvarPair.second, true, false, varId, varId); + } else { pvarPair.first = - boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.first, true, true, varIdReturn, varId); + boost::make_shared<TestableModeAccessorDecorator<UserType>>( + pvarPair.first, true, true, varIdReturn, varId); pvarPair.second = - boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.second, true, true, varId, varIdReturn); + boost::make_shared<TestableModeAccessorDecorator<UserType>>( + pvarPair.second, true, true, varId, varIdReturn); } // put the decorators into the list testableMode_names[varId] = "Internal:" + node.getQualifiedName(); - if(consumer.getType() != NodeType::invalid) { + if (consumer.getType() != NodeType::invalid) { testableMode_names[varId] += "->" + consumer.getQualifiedName(); } - if(node.getDirection().withReturn) testableMode_names[varIdReturn] = testableMode_names[varId] + " (return)"; + if (node.getDirection().withReturn) + testableMode_names[varIdReturn] = testableMode_names[varId] + " (return)"; } // if debug mode was requested for either node, decorate both accessors - if(debugMode_variableList.count(node.getUniqueId()) || - (consumer.getType() != NodeType::invalid && debugMode_variableList.count(consumer.getUniqueId()))) { - if(consumer.getType() != NodeType::invalid) { + if (debugMode_variableList.count(node.getUniqueId()) || + (consumer.getType() != NodeType::invalid && + debugMode_variableList.count(consumer.getUniqueId()))) { + if (consumer.getType() != NodeType::invalid) { assert(node.getDirection().dir == VariableDirection::feeding); assert(consumer.getDirection().dir == VariableDirection::consuming); pvarPair.first = - boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.first, node.getQualifiedName()); + boost::make_shared<DebugPrintAccessorDecorator<UserType>>( + pvarPair.first, node.getQualifiedName()); pvarPair.second = - boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.second, consumer.getQualifiedName()); - } - else { + boost::make_shared<DebugPrintAccessorDecorator<UserType>>( + pvarPair.second, consumer.getQualifiedName()); + } else { pvarPair.first = - boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.first, node.getQualifiedName()); + boost::make_shared<DebugPrintAccessorDecorator<UserType>>( + pvarPair.first, node.getQualifiedName()); pvarPair.second = - boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.second, node.getQualifiedName()); + boost::make_shared<DebugPrintAccessorDecorator<UserType>>( + pvarPair.second, node.getQualifiedName()); } } @@ -469,29 +515,31 @@ std::pair<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, /*********************************************************************************************************************/ void Application::makeConnections() { - for(auto& devModule : deviceModuleList) { + for (auto &devModule : deviceModuleList) { devModule->defineConnections(); } - // finalise connections: decide still-undecided details, in particular for control-system and device varibales, which - // get created "on the fly". + // finalise connections: decide still-undecided details, in particular for + // control-system and device varibales, which get created "on the fly". finaliseNetworks(); // apply optimisations - // note: checks may not be run before since sometimes networks may only be valid after optimisations + // note: checks may not be run before since sometimes networks may only be + // valid after optimisations optimiseConnections(); // run checks checkConnections(); // make the connections for all networks - for(auto& network : networkList) { + for (auto &network : networkList) { makeConnectionsForNetwork(network); } // set all initial version numbers in the modules to the same value VersionNumber startVersion; - for(auto& module : getSubmoduleListRecursive()) { - if(module->getModuleType() != ModuleType::ApplicationModule) continue; + for (auto &module : getSubmoduleListRecursive()) { + if (module->getModuleType() != ModuleType::ApplicationModule) + continue; module->setCurrentVersionNumber(startVersion); } } @@ -500,13 +548,16 @@ void Application::makeConnections() { void Application::finaliseNetworks() { // check for control system variables which should be made bidirectional - for(auto& network : networkList) { + for (auto &network : networkList) { size_t nBidir = network.getFeedingNode().getDirection().withReturn ? 1 : 0; - for(auto& consumer : network.getConsumingNodes()) { - if(consumer.getDirection().withReturn) ++nBidir; + for (auto &consumer : network.getConsumingNodes()) { + if (consumer.getDirection().withReturn) + ++nBidir; } - if(nBidir != 1) continue; // only if there is exactly one node with return channel we need to guess its peer - if(network.getFeedingNode().getType() != NodeType::ControlSystem) { + if (nBidir != 1) + continue; // only if there is exactly one node with return channel we need + // to guess its peer + if (network.getFeedingNode().getType() != NodeType::ControlSystem) { // only a feeding control system variable can be made bidirectional continue; } @@ -517,54 +568,71 @@ void Application::finaliseNetworks() { /*********************************************************************************************************************/ void Application::optimiseConnections() { - // list of iterators of networks to be removed from the networkList after the merge operation - std::list<VariableNetwork*> deleteNetworks; + // list of iterators of networks to be removed from the networkList after the + // merge operation + std::list<VariableNetwork *> deleteNetworks; // search for networks with the same feeder - for(auto it1 = networkList.begin(); it1 != networkList.end(); ++it1) { - for(auto it2 = it1; it2 != networkList.end(); ++it2) { - if(it1 == it2) continue; + for (auto it1 = networkList.begin(); it1 != networkList.end(); ++it1) { + for (auto it2 = it1; it2 != networkList.end(); ++it2) { + if (it1 == it2) + continue; auto feeder1 = it1->getFeedingNode(); auto feeder2 = it2->getFeedingNode(); - // this optimisation is only necessary for device-type nodes, since application and control-system nodes will - // automatically create merged networks when having the same feeder - /// @todo check if this assumtion is true! control-system nodes can be created with different types, too! - if(feeder1.getType() != NodeType::Device || feeder2.getType() != NodeType::Device) continue; + // this optimisation is only necessary for device-type nodes, since + // application and control-system nodes will automatically create merged + // networks when having the same feeder + /// @todo check if this assumtion is true! control-system nodes can be + /// created with different types, too! + if (feeder1.getType() != NodeType::Device || + feeder2.getType() != NodeType::Device) + continue; // check if referrring to same register - if(feeder1.getDeviceAlias() != feeder2.getDeviceAlias()) continue; - if(feeder1.getRegisterName() != feeder2.getRegisterName()) continue; + if (feeder1.getDeviceAlias() != feeder2.getDeviceAlias()) + continue; + if (feeder1.getRegisterName() != feeder2.getRegisterName()) + continue; // check if directions are the same - if(feeder1.getDirection() != feeder2.getDirection()) continue; + if (feeder1.getDirection() != feeder2.getDirection()) + continue; // check if value types and number of elements are compatible - if(feeder1.getValueType() != feeder2.getValueType()) continue; - if(feeder1.getNumberOfElements() != feeder2.getNumberOfElements()) continue; + if (feeder1.getValueType() != feeder2.getValueType()) + continue; + if (feeder1.getNumberOfElements() != feeder2.getNumberOfElements()) + continue; // check if transfer mode is the same - if(feeder1.getMode() != feeder2.getMode()) continue; + if (feeder1.getMode() != feeder2.getMode()) + continue; // check if triggers are compatible, if present - if(feeder1.hasExternalTrigger() != feeder2.hasExternalTrigger()) continue; - if(feeder1.hasExternalTrigger()) { - if(feeder1.getExternalTrigger() != feeder2.getExternalTrigger()) continue; + if (feeder1.hasExternalTrigger() != feeder2.hasExternalTrigger()) + continue; + if (feeder1.hasExternalTrigger()) { + if (feeder1.getExternalTrigger() != feeder2.getExternalTrigger()) + continue; } - // everything should be compatible at this point: merge the networks. We will merge the network of the outer - // loop into the network of the inner loop, since the network of the outer loop will not be found a second time - // in the inner loop. - for(auto consumer : it1->getConsumingNodes()) { + // everything should be compatible at this point: merge the networks. We + // will merge the network of the outer loop into the network of the inner + // loop, since the network of the outer loop will not be found a second + // time in the inner loop. + for (auto consumer : it1->getConsumingNodes()) { consumer.clearOwner(); it2->addNode(consumer); } - // if trigger present, remove corresponding trigger receiver node from the trigger network - if(feeder1.hasExternalTrigger()) { - for(auto& itTrig : networkList) { - if(itTrig.getFeedingNode() != feeder1.getExternalTrigger()) continue; + // if trigger present, remove corresponding trigger receiver node from the + // trigger network + if (feeder1.hasExternalTrigger()) { + for (auto &itTrig : networkList) { + if (itTrig.getFeedingNode() != feeder1.getExternalTrigger()) + continue; itTrig.removeNodeToTrigger(it1->getFeedingNode()); } } @@ -576,22 +644,26 @@ void Application::optimiseConnections() { } // remove networks from the network list - for(auto net : deleteNetworks) { + for (auto net : deleteNetworks) { networkList.remove(*net); } } /*********************************************************************************************************************/ -void Application::dumpConnections() { // LCOV_EXCL_LINE - std::cout << "==== List of all variable connections of the current Application ====" << std::endl; // LCOV_EXCL_LINE - for(auto& network : networkList) { // LCOV_EXCL_LINE - network.dump(); // LCOV_EXCL_LINE - } // LCOV_EXCL_LINE - std::cout << "=====================================================================" << std::endl; // LCOV_EXCL_LINE +void Application::dumpConnections() { // LCOV_EXCL_LINE + std::cout + << "==== List of all variable connections of the current Application ====" + << std::endl; // LCOV_EXCL_LINE + for (auto &network : networkList) { // LCOV_EXCL_LINE + network.dump(); // LCOV_EXCL_LINE + } // LCOV_EXCL_LINE + std::cout + << "=====================================================================" + << std::endl; // LCOV_EXCL_LINE } // LCOV_EXCL_LINE -void Application::dumpConnectionGraph(const std::string& fileName) { +void Application::dumpConnectionGraph(const std::string &fileName) { std::fstream file{fileName, std::ios_base::out}; VariableNetworkGraphDumpingVisitor visitor{file}; @@ -600,28 +672,33 @@ void Application::dumpConnectionGraph(const std::string& fileName) { /*********************************************************************************************************************/ -Application::TypedMakeConnectionCaller::TypedMakeConnectionCaller(Application& owner, VariableNetwork& network) -: _owner(owner), _network(network) {} +Application::TypedMakeConnectionCaller::TypedMakeConnectionCaller( + Application &owner, VariableNetwork &network) + : _owner(owner), _network(network) {} /*********************************************************************************************************************/ -template<typename PAIR> -void Application::TypedMakeConnectionCaller::operator()(PAIR&) const { - if(typeid(typename PAIR::first_type) != _network.getValueType()) return; +template <typename PAIR> +void Application::TypedMakeConnectionCaller::operator()(PAIR &) const { + if (typeid(typename PAIR::first_type) != _network.getValueType()) + return; _owner.typedMakeConnection<typename PAIR::first_type>(_network); done = true; } /*********************************************************************************************************************/ -void Application::makeConnectionsForNetwork(VariableNetwork& network) { +void Application::makeConnectionsForNetwork(VariableNetwork &network) { // if the network has been created already, do nothing - if(network.isCreated()) return; + if (network.isCreated()) + return; // if the trigger type is external, create the trigger first - if(network.getFeedingNode().hasExternalTrigger()) { - VariableNetwork& dependency = network.getFeedingNode().getExternalTrigger().getOwner(); - if(!dependency.isCreated()) makeConnectionsForNetwork(dependency); + if (network.getFeedingNode().hasExternalTrigger()) { + VariableNetwork &dependency = + network.getFeedingNode().getExternalTrigger().getOwner(); + if (!dependency.isCreated()) + makeConnectionsForNetwork(dependency); } // defer actual network creation to templated function @@ -635,294 +712,317 @@ void Application::makeConnectionsForNetwork(VariableNetwork& network) { /*********************************************************************************************************************/ -template<typename UserType> -void Application::typedMakeConnection(VariableNetwork& network) { +template <typename UserType> +void Application::typedMakeConnection(VariableNetwork &network) { bool connectionMade = false; // to check the logic... size_t nNodes = network.countConsumingNodes() + 1; auto feeder = network.getFeedingNode(); auto consumers = network.getConsumingNodes(); - bool useExternalTrigger = network.getTriggerType() == VariableNetwork::TriggerType::external; - bool useFeederTrigger = network.getTriggerType() == VariableNetwork::TriggerType::feeder; + bool useExternalTrigger = + network.getTriggerType() == VariableNetwork::TriggerType::external; + bool useFeederTrigger = + network.getTriggerType() == VariableNetwork::TriggerType::feeder; bool constantFeeder = feeder.getType() == NodeType::Constant; // 1st case: the feeder requires a fixed implementation - if(feeder.hasImplementation() && !constantFeeder) { - // Create feeding implementation. Note: though the implementation is derived from the feeder, it will be used as - // the implementation of the (or one of the) consumer. Logically, implementations are always pairs of - // implementations (sender and receiver), but in this case the feeder already has a fixed implementation pair. - // So our feedingImpl will contain the consumer-end of the implementation pair. This is the reason why the - // functions createProcessScalar() and createDeviceAccessor() get the VariableDirection::consuming. + if (feeder.hasImplementation() && !constantFeeder) { + // Create feeding implementation. Note: though the implementation is derived + // from the feeder, it will be used as the implementation of the (or one of + // the) consumer. Logically, implementations are always pairs of + // implementations (sender and receiver), but in this case the feeder + // already has a fixed implementation pair. So our feedingImpl will contain + // the consumer-end of the implementation pair. This is the reason why the + // functions createProcessScalar() and createDeviceAccessor() get the + // VariableDirection::consuming. boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl; - if(feeder.getType() == NodeType::Device) { - feedingImpl = createDeviceVariable<UserType>(feeder.getDeviceAlias(), feeder.getRegisterName(), - {VariableDirection::consuming, false}, feeder.getMode(), feeder.getNumberOfElements()); - } - else if(feeder.getType() == NodeType::ControlSystem) { + if (feeder.getType() == NodeType::Device) { + feedingImpl = createDeviceVariable<UserType>( + feeder.getDeviceAlias(), feeder.getRegisterName(), + {VariableDirection::consuming, false}, feeder.getMode(), + feeder.getNumberOfElements()); + } else if (feeder.getType() == NodeType::ControlSystem) { feedingImpl = createProcessVariable<UserType>(feeder); - } - else { - throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) + } else { + throw ChimeraTK::logic_error( + "Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) } // if we just have two nodes, directly connect them - if(nNodes == 2 && !useExternalTrigger) { + if (nNodes == 2 && !useExternalTrigger) { auto consumer = consumers.front(); - if(consumer.getType() == NodeType::Application) { + if (consumer.getType() == NodeType::Application) { consumer.setAppAccessorImplementation(feedingImpl); connectionMade = true; - } - else if(consumer.getType() == NodeType::Device) { - auto consumingImpl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(), - {VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements()); - // connect the Device with e.g. a ControlSystem node via a ThreadedFanOut + } else if (consumer.getType() == NodeType::Device) { + auto consumingImpl = createDeviceVariable<UserType>( + consumer.getDeviceAlias(), consumer.getRegisterName(), + {VariableDirection::feeding, false}, consumer.getMode(), + consumer.getNumberOfElements()); + // connect the Device with e.g. a ControlSystem node via a + // ThreadedFanOut auto fanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl); fanOut->addSlave(consumingImpl, consumer); internalModuleList.push_back(fanOut); connectionMade = true; - } - else if(consumer.getType() == NodeType::ControlSystem) { + } else if (consumer.getType() == NodeType::ControlSystem) { auto consumingImpl = createProcessVariable<UserType>(consumer); - // connect the ControlSystem with e.g. a Device node via an ThreadedFanOut + // connect the ControlSystem with e.g. a Device node via an + // ThreadedFanOut auto fanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl); fanOut->addSlave(consumingImpl, consumer); internalModuleList.push_back(fanOut); connectionMade = true; - } - else if(consumer.getType() == NodeType::TriggerReceiver) { - consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(feedingImpl); + } else if (consumer.getType() == NodeType::TriggerReceiver) { + consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl( + feedingImpl); connectionMade = true; + } else { + throw ChimeraTK::logic_error( + "Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) } - else { - throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) - } - } - else { /* !(nNodes == 2 && !useExternalTrigger) */ + } else { /* !(nNodes == 2 && !useExternalTrigger) */ // create the right FanOut type boost::shared_ptr<FanOut<UserType>> fanOut; boost::shared_ptr<ConsumingFanOut<UserType>> consumingFanOut; - if(useExternalTrigger) { - // if external trigger is enabled, use externally triggered threaded FanOut + if (useExternalTrigger) { + // if external trigger is enabled, use externally triggered threaded + // FanOut auto triggerNode = feeder.getExternalTrigger(); auto triggerFanOut = triggerMap[triggerNode.getUniqueId()]; - if(!triggerFanOut) { - triggerFanOut = boost::make_shared<TriggerFanOut>(network.getExternalTriggerImpl()); + if (!triggerFanOut) { + triggerFanOut = boost::make_shared<TriggerFanOut>( + network.getExternalTriggerImpl()); triggerMap[triggerNode.getUniqueId()] = triggerFanOut; internalModuleList.push_back(triggerFanOut); } fanOut = triggerFanOut->addNetwork(feedingImpl); - } - else if(useFeederTrigger) { - // if the trigger is provided by the pushing feeder, use the treaded version of the FanOut to distribute - // new values immediately to all consumers. - // Depending on whether we have a return channel or not, pick the right implementation of the FanOut + } else if (useFeederTrigger) { + // if the trigger is provided by the pushing feeder, use the treaded + // version of the FanOut to distribute new values immediately to all + // consumers. Depending on whether we have a return channel or not, pick + // the right implementation of the FanOut boost::shared_ptr<ThreadedFanOut<UserType>> threadedFanOut; - if(!feeder.getDirection().withReturn) { - threadedFanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl); - } - else { - threadedFanOut = boost::make_shared<ThreadedFanOutWithReturn<UserType>>(feedingImpl); + if (!feeder.getDirection().withReturn) { + threadedFanOut = + boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl); + } else { + threadedFanOut = + boost::make_shared<ThreadedFanOutWithReturn<UserType>>( + feedingImpl); } internalModuleList.push_back(threadedFanOut); fanOut = threadedFanOut; - } - else { - assert(network.hasApplicationConsumer()); // checkConnections should catch this - consumingFanOut = boost::make_shared<ConsumingFanOut<UserType>>(feedingImpl); + } else { + assert(network.hasApplicationConsumer()); // checkConnections should + // catch this + consumingFanOut = + boost::make_shared<ConsumingFanOut<UserType>>(feedingImpl); fanOut = consumingFanOut; } - // In case we have one or more trigger receivers among our consumers, we produce exactly one application variable - // for it. We never need more, since the distribution is done with a TriggerFanOut. - bool usedTriggerReceiver{false}; // flag if we already have a trigger receiver - auto triggerConnection = createApplicationVariable<UserType>(feeder); // will get destroyed if not used + // In case we have one or more trigger receivers among our consumers, we + // produce exactly one application variable for it. We never need more, + // since the distribution is done with a TriggerFanOut. + bool usedTriggerReceiver{ + false}; // flag if we already have a trigger receiver + auto triggerConnection = createApplicationVariable<UserType>( + feeder); // will get destroyed if not used // add all consumers to the FanOut - for(auto& consumer : consumers) { - if(consumer.getType() == NodeType::Application) { - if(consumingFanOut && consumer.getMode() == UpdateMode::poll) { + for (auto &consumer : consumers) { + if (consumer.getType() == NodeType::Application) { + if (consumingFanOut && consumer.getMode() == UpdateMode::poll) { consumer.setAppAccessorImplementation<UserType>(consumingFanOut); consumingFanOut.reset(); - } - else { + } else { auto impls = createApplicationVariable<UserType>(consumer); fanOut->addSlave(impls.first, consumer); consumer.setAppAccessorImplementation<UserType>(impls.second); } - } - else if(consumer.getType() == NodeType::ControlSystem) { + } else if (consumer.getType() == NodeType::ControlSystem) { auto impl = createProcessVariable<UserType>(consumer); fanOut->addSlave(impl, consumer); - } - else if(consumer.getType() == NodeType::Device) { - auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(), - {VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements()); + } else if (consumer.getType() == NodeType::Device) { + auto impl = createDeviceVariable<UserType>( + consumer.getDeviceAlias(), consumer.getRegisterName(), + {VariableDirection::feeding, false}, consumer.getMode(), + consumer.getNumberOfElements()); fanOut->addSlave(impl, consumer); - } - else if(consumer.getType() == NodeType::TriggerReceiver) { - if(!usedTriggerReceiver) fanOut->addSlave(triggerConnection.first, consumer); + } else if (consumer.getType() == NodeType::TriggerReceiver) { + if (!usedTriggerReceiver) + fanOut->addSlave(triggerConnection.first, consumer); usedTriggerReceiver = true; - consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(triggerConnection.second); - } - else { - throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) + consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl( + triggerConnection.second); + } else { + throw ChimeraTK::logic_error( + "Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) } } connectionMade = true; } } // 2nd case: the feeder does not require a fixed implementation - else if(!constantFeeder) { /* !feeder.hasImplementation() */ + else if (!constantFeeder) { /* !feeder.hasImplementation() */ // we should be left with an application feeder node - if(feeder.getType() != NodeType::Application) { - throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) + if (feeder.getType() != NodeType::Application) { + throw ChimeraTK::logic_error( + "Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) } assert(!useExternalTrigger); // if we just have two nodes, directly connect them - if(nNodes == 2) { + if (nNodes == 2) { auto consumer = consumers.front(); - if(consumer.getType() == NodeType::Application) { + if (consumer.getType() == NodeType::Application) { auto impls = createApplicationVariable<UserType>(feeder, consumer); feeder.setAppAccessorImplementation<UserType>(impls.first); consumer.setAppAccessorImplementation<UserType>(impls.second); connectionMade = true; - } - else if(consumer.getType() == NodeType::ControlSystem) { + } else if (consumer.getType() == NodeType::ControlSystem) { auto impl = createProcessVariable<UserType>(consumer); feeder.setAppAccessorImplementation<UserType>(impl); connectionMade = true; - } - else if(consumer.getType() == NodeType::Device) { - auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(), - {VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements()); + } else if (consumer.getType() == NodeType::Device) { + auto impl = createDeviceVariable<UserType>( + consumer.getDeviceAlias(), consumer.getRegisterName(), + {VariableDirection::feeding, false}, consumer.getMode(), + consumer.getNumberOfElements()); feeder.setAppAccessorImplementation<UserType>(impl); connectionMade = true; - } - else if(consumer.getType() == NodeType::TriggerReceiver) { + } else if (consumer.getType() == NodeType::TriggerReceiver) { auto impls = createApplicationVariable<UserType>(feeder, consumer); feeder.setAppAccessorImplementation<UserType>(impls.first); - consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(impls.second); + consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl( + impls.second); connectionMade = true; - } - else if(consumer.getType() == NodeType::Constant) { + } else if (consumer.getType() == NodeType::Constant) { auto impl = consumer.getConstAccessor<UserType>(); feeder.setAppAccessorImplementation<UserType>(impl); connectionMade = true; + } else { + throw ChimeraTK::logic_error( + "Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) } - else { - throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) - } - } - else { + } else { // create FanOut and use it as the feeder implementation - auto fanOut = boost::make_shared<FeedingFanOut<UserType>>(feeder.getName(), feeder.getUnit(), - feeder.getDescription(), feeder.getNumberOfElements(), feeder.getDirection().withReturn); + auto fanOut = boost::make_shared<FeedingFanOut<UserType>>( + feeder.getName(), feeder.getUnit(), feeder.getDescription(), + feeder.getNumberOfElements(), feeder.getDirection().withReturn); feeder.setAppAccessorImplementation<UserType>(fanOut); - // In case we have one or more trigger receivers among our consumers, we produce exactly one application variable - // for it. We never need more, since the distribution is done with a TriggerFanOut. - bool usedTriggerReceiver{false}; // flag if we already have a trigger receiver - auto triggerConnection = createApplicationVariable<UserType>(feeder); // will get destroyed if not used + // In case we have one or more trigger receivers among our consumers, we + // produce exactly one application variable for it. We never need more, + // since the distribution is done with a TriggerFanOut. + bool usedTriggerReceiver{ + false}; // flag if we already have a trigger receiver + auto triggerConnection = createApplicationVariable<UserType>( + feeder); // will get destroyed if not used - for(auto& consumer : consumers) { - if(consumer.getType() == NodeType::Application) { + for (auto &consumer : consumers) { + if (consumer.getType() == NodeType::Application) { auto impls = createApplicationVariable<UserType>(consumer); fanOut->addSlave(impls.first, consumer); consumer.setAppAccessorImplementation<UserType>(impls.second); - } - else if(consumer.getType() == NodeType::ControlSystem) { + } else if (consumer.getType() == NodeType::ControlSystem) { auto impl = createProcessVariable<UserType>(consumer); fanOut->addSlave(impl, consumer); - } - else if(consumer.getType() == NodeType::Device) { - auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(), - {VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements()); + } else if (consumer.getType() == NodeType::Device) { + auto impl = createDeviceVariable<UserType>( + consumer.getDeviceAlias(), consumer.getRegisterName(), + {VariableDirection::feeding, false}, consumer.getMode(), + consumer.getNumberOfElements()); fanOut->addSlave(impl, consumer); - } - else if(consumer.getType() == NodeType::TriggerReceiver) { - if(!usedTriggerReceiver) fanOut->addSlave(triggerConnection.first, consumer); + } else if (consumer.getType() == NodeType::TriggerReceiver) { + if (!usedTriggerReceiver) + fanOut->addSlave(triggerConnection.first, consumer); usedTriggerReceiver = true; - consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(triggerConnection.second); - } - else { - throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) + consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl( + triggerConnection.second); + } else { + throw ChimeraTK::logic_error( + "Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) } } connectionMade = true; } - } - else { /* constantFeeder */ + } else { /* constantFeeder */ assert(feeder.getType() == NodeType::Constant); auto feedingImpl = feeder.getConstAccessor<UserType>(); assert(feedingImpl != nullptr); - for(auto& consumer : consumers) { - if(consumer.getType() == NodeType::Application) { - if(testableMode) { + for (auto &consumer : consumers) { + if (consumer.getType() == NodeType::Application) { + if (testableMode) { auto varId = getNextVariableId(); auto pvarDec = - boost::make_shared<TestableModeAccessorDecorator<UserType>>(feedingImpl, true, false, varId, varId); + boost::make_shared<TestableModeAccessorDecorator<UserType>>( + feedingImpl, true, false, varId, varId); testableMode_names[varId] = "Constant"; consumer.setAppAccessorImplementation<UserType>(pvarDec); - } - else { + } else { consumer.setAppAccessorImplementation<UserType>(feedingImpl); } - } - else if(consumer.getType() == NodeType::ControlSystem) { + } else if (consumer.getType() == NodeType::ControlSystem) { auto impl = createProcessVariable<UserType>(consumer); impl->accessChannel(0) = feedingImpl->accessChannel(0); impl->write(); - } - else if(consumer.getType() == NodeType::Device) { - auto impl = createDeviceVariable<UserType>(consumer.getDeviceAlias(), consumer.getRegisterName(), - {VariableDirection::feeding, false}, consumer.getMode(), consumer.getNumberOfElements()); + } else if (consumer.getType() == NodeType::Device) { + auto impl = createDeviceVariable<UserType>( + consumer.getDeviceAlias(), consumer.getRegisterName(), + {VariableDirection::feeding, false}, consumer.getMode(), + consumer.getNumberOfElements()); impl->accessChannel(0) = feedingImpl->accessChannel(0); impl->write(); - } - else if(consumer.getType() == NodeType::TriggerReceiver) { - throw ChimeraTK::logic_error("Using constants as triggers is not supported!"); - } - else { - throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) + } else if (consumer.getType() == NodeType::TriggerReceiver) { + throw ChimeraTK::logic_error( + "Using constants as triggers is not supported!"); + } else { + throw ChimeraTK::logic_error( + "Unexpected node type!"); // LCOV_EXCL_LINE (assert-like) } } connectionMade = true; } - if(!connectionMade) { // LCOV_EXCL_LINE (assert-like) - throw ChimeraTK::logic_error( // LCOV_EXCL_LINE (assert-like) + if (!connectionMade) { // LCOV_EXCL_LINE (assert-like) + throw ChimeraTK::logic_error( // LCOV_EXCL_LINE (assert-like) "The variable network cannot be handled. Implementation missing!"); // LCOV_EXCL_LINE (assert-like) - } // LCOV_EXCL_LINE (assert-like) + } // LCOV_EXCL_LINE (assert-like) } /*********************************************************************************************************************/ -VariableNetwork& Application::createNetwork() { +VariableNetwork &Application::createNetwork() { networkList.emplace_back(); return networkList.back(); } /*********************************************************************************************************************/ -Application& Application::getInstance() { - return dynamic_cast<Application&>(ApplicationBase::getInstance()); +Application &Application::getInstance() { + return dynamic_cast<Application &>(ApplicationBase::getInstance()); } /*********************************************************************************************************************/ void Application::stepApplication() { - // testableMode_counter must be non-zero, otherwise there is no input for the application to process - if(testableMode_counter == 0) { + // testableMode_counter must be non-zero, otherwise there is no input for the + // application to process + if (testableMode_counter == 0) { throw ChimeraTK::logic_error( - "Application::stepApplication() called despite no input was provided to the application to process!"); + "Application::stepApplication() called despite no input was provided " + "to the application to process!"); } - // let the application run until it has processed all data (i.e. the semaphore counter is 0) + // let the application run until it has processed all data (i.e. the semaphore + // counter is 0) size_t oldCounter = 0; - while(testableMode_counter > 0) { - if(enableDebugTestableMode && (oldCounter != testableMode_counter)) { // LCOV_EXCL_LINE (only cout) - std::cout << "Application::stepApplication(): testableMode_counter = " << testableMode_counter + while (testableMode_counter > 0) { + if (enableDebugTestableMode && + (oldCounter != testableMode_counter)) { // LCOV_EXCL_LINE (only cout) + std::cout << "Application::stepApplication(): testableMode_counter = " + << testableMode_counter << std::endl; // LCOV_EXCL_LINE (only cout) oldCounter = testableMode_counter; // LCOV_EXCL_LINE (only cout) } @@ -934,119 +1034,153 @@ void Application::stepApplication() { /*********************************************************************************************************************/ -void Application::testableModeLock(const std::string& name) { +void Application::testableModeLock(const std::string &name) { // don't do anything if testable mode is not enabled - if(!getInstance().testableMode) return; + if (!getInstance().testableMode) + return; // debug output if enabled (also prevent spamming the same message) - if(getInstance().enableDebugTestableMode && getInstance().testableMode_repeatingMutexOwner == 0) { // LCOV_EXCL_LINE - // (only cout) - std::cout << "Application::testableModeLock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout) - << " tries to obtain lock for " << name << std::endl; // LCOV_EXCL_LINE (only cout) - } // LCOV_EXCL_LINE (only cout) - - // if last lock was obtained repeatedly by the same thread, sleep a short time before obtaining the lock to give the - // other threads a chance to get the lock first - if(getInstance().testableMode_repeatingMutexOwner > 0) usleep(10000); + if (getInstance().enableDebugTestableMode && + getInstance().testableMode_repeatingMutexOwner == 0) { // LCOV_EXCL_LINE + // (only cout) + std::cout << "Application::testableModeLock(): Thread " + << threadName() // LCOV_EXCL_LINE (only cout) + << " tries to obtain lock for " << name + << std::endl; // LCOV_EXCL_LINE (only cout) + } // LCOV_EXCL_LINE (only cout) + + // if last lock was obtained repeatedly by the same thread, sleep a short time + // before obtaining the lock to give the other threads a chance to get the + // lock first + if (getInstance().testableMode_repeatingMutexOwner > 0) + usleep(10000); // obtain the lock getTestableModeLockObject().lock(); - // check if the last owner of the mutex was this thread, which may be a hint that no other thread is waiting for the - // lock - if(getInstance().testableMode_lastMutexOwner == std::this_thread::get_id()) { + // check if the last owner of the mutex was this thread, which may be a hint + // that no other thread is waiting for the lock + if (getInstance().testableMode_lastMutexOwner == std::this_thread::get_id()) { // debug output if enabled - if(getInstance().enableDebugTestableMode && getInstance().testableMode_repeatingMutexOwner == 0) { // LCOV_EXCL_LINE - // (only cout) - std::cout << "Application::testableModeLock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout) - << " repeatedly obtained lock successfully for " << name // LCOV_EXCL_LINE (only cout) - << ". Further messages will be suppressed." << std::endl; // LCOV_EXCL_LINE (only cout) - } // LCOV_EXCL_LINE (only cout) + if (getInstance().enableDebugTestableMode && + getInstance().testableMode_repeatingMutexOwner == 0) { // LCOV_EXCL_LINE + // (only cout) + std::cout << "Application::testableModeLock(): Thread " + << threadName() // LCOV_EXCL_LINE (only cout) + << " repeatedly obtained lock successfully for " + << name // LCOV_EXCL_LINE (only cout) + << ". Further messages will be suppressed." + << std::endl; // LCOV_EXCL_LINE (only cout) + } // LCOV_EXCL_LINE (only cout) // increase counter for stall detection getInstance().testableMode_repeatingMutexOwner++; - // detect stall: if the same thread got the mutex with no other thread obtaining it in between for one second, we - // assume no other thread is able to process data at this time. The test should fail in this case - if(getInstance().testableMode_repeatingMutexOwner > 100) { - // print an informative message first, which lists also all variables currently containing unread data. - std::cout << "*** Tests are stalled due to data which has been sent but not received." << std::endl; - std::cout << " The following variables still contain unread values or had data loss due to a queue overflow:" + // detect stall: if the same thread got the mutex with no other thread + // obtaining it in between for one second, we assume no other thread is able + // to process data at this time. The test should fail in this case + if (getInstance().testableMode_repeatingMutexOwner > 100) { + // print an informative message first, which lists also all variables + // currently containing unread data. + std::cout << "*** Tests are stalled due to data which has been sent but " + "not received." + << std::endl; + std::cout << " The following variables still contain unread values or " + "had data loss due to a queue overflow:" << std::endl; - for(auto& pair : Application::getInstance().testableMode_perVarCounter) { - if(pair.second > 0) { - std::cout << " - " << Application::getInstance().testableMode_names[pair.first] << " [" - << getInstance().testableMode_processVars[pair.first]->getId() << "]"; + for (auto &pair : Application::getInstance().testableMode_perVarCounter) { + if (pair.second > 0) { + std::cout + << " - " + << Application::getInstance().testableMode_names[pair.first] + << " [" + << getInstance().testableMode_processVars[pair.first]->getId() + << "]"; // check if process variable still has data in the queue try { - if(getInstance().testableMode_processVars[pair.first]->readNonBlocking()) { + if (getInstance() + .testableMode_processVars[pair.first] + ->readNonBlocking()) { std::cout << " (unread data in queue)"; - } - else { + } else { std::cout << " (data loss)"; } - } - catch(std::logic_error&) { - // if we receive a logic_error in readNonBlocking() it just means another thread is waiting on a - // TransferFuture of this variable, and we actually were not allowed to read... + } catch (std::logic_error &) { + // if we receive a logic_error in readNonBlocking() it just means + // another thread is waiting on a TransferFuture of this variable, + // and we actually were not allowed to read... std::cout << " (data loss)"; } std::cout << std::endl; } } std::cout << "(end of list)" << std::endl; - // throw a specialised exception to make sure whoever catches it really knows what he does... + // throw a specialised exception to make sure whoever catches it really + // knows what he does... throw TestsStalled(); // getInstance().testableMode_counter = 0; - // for(auto &pair : Application::getInstance().testableMode_perVarCounter) pair.second = 0; + // for(auto &pair : Application::getInstance().testableMode_perVarCounter) + // pair.second = 0; } - } - else { - // last owner of the mutex was different: reset the counter and store the thread id + } else { + // last owner of the mutex was different: reset the counter and store the + // thread id getInstance().testableMode_repeatingMutexOwner = 0; getInstance().testableMode_lastMutexOwner = std::this_thread::get_id(); // debug output if enabled - if(getInstance().enableDebugTestableMode) { // LCOV_EXCL_LINE (only cout) - std::cout << "Application::testableModeLock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout) - << " obtained lock successfully for " << name << std::endl; // LCOV_EXCL_LINE (only cout) - } // LCOV_EXCL_LINE (only cout) + if (getInstance().enableDebugTestableMode) { // LCOV_EXCL_LINE (only cout) + std::cout << "Application::testableModeLock(): Thread " + << threadName() // LCOV_EXCL_LINE (only cout) + << " obtained lock successfully for " << name + << std::endl; // LCOV_EXCL_LINE (only cout) + } // LCOV_EXCL_LINE (only cout) } } /*********************************************************************************************************************/ -void Application::testableModeUnlock(const std::string& name) { - if(!getInstance().testableMode) return; - if(getInstance().enableDebugTestableMode && - (!getInstance().testableMode_repeatingMutexOwner // LCOV_EXCL_LINE (only cout) - || getInstance().testableMode_lastMutexOwner != std::this_thread::get_id())) { // LCOV_EXCL_LINE (only cout) - std::cout << "Application::testableModeUnlock(): Thread " << threadName() // LCOV_EXCL_LINE (only cout) - << " releases lock for " << name << std::endl; // LCOV_EXCL_LINE (only cout) - } // LCOV_EXCL_LINE (only cout) +void Application::testableModeUnlock(const std::string &name) { + if (!getInstance().testableMode) + return; + if (getInstance().enableDebugTestableMode && + (!getInstance() + .testableMode_repeatingMutexOwner // LCOV_EXCL_LINE (only cout) + || getInstance().testableMode_lastMutexOwner != + std::this_thread::get_id())) { // LCOV_EXCL_LINE (only cout) + std::cout << "Application::testableModeUnlock(): Thread " + << threadName() // LCOV_EXCL_LINE (only cout) + << " releases lock for " << name + << std::endl; // LCOV_EXCL_LINE (only cout) + } // LCOV_EXCL_LINE (only cout) getTestableModeLockObject().unlock(); } /*********************************************************************************************************************/ -std::string& Application::threadName() { - // Note: due to a presumed bug in gcc (still present in gcc 7), the thread_local definition must be in the cc file - // to prevent seeing different objects in the same thread under some conditions. - // Another workaround for this problem can be found in commit dc051bfe35ce6c1ed954010559186f63646cf5d4 +std::string &Application::threadName() { + // Note: due to a presumed bug in gcc (still present in gcc 7), the + // thread_local definition must be in the cc file to prevent seeing different + // objects in the same thread under some conditions. Another workaround for + // this problem can be found in commit + // dc051bfe35ce6c1ed954010559186f63646cf5d4 thread_local std::string name{"**UNNAMED**"}; return name; } /*********************************************************************************************************************/ -std::unique_lock<std::mutex>& Application::getTestableModeLockObject() { - // Note: due to a presumed bug in gcc (still present in gcc 7), the thread_local definition must be in the cc file - // to prevent seeing different objects in the same thread under some conditions. - // Another workaround for this problem can be found in commit dc051bfe35ce6c1ed954010559186f63646cf5d4 - thread_local std::unique_lock<std::mutex> myLock(Application::testableMode_mutex, std::defer_lock); +std::unique_lock<std::mutex> &Application::getTestableModeLockObject() { + // Note: due to a presumed bug in gcc (still present in gcc 7), the + // thread_local definition must be in the cc file to prevent seeing different + // objects in the same thread under some conditions. Another workaround for + // this problem can be found in commit + // dc051bfe35ce6c1ed954010559186f63646cf5d4 + thread_local std::unique_lock<std::mutex> myLock( + Application::testableMode_mutex, std::defer_lock); return myLock; } /*********************************************************************************************************************/ -void Application::registerDeviceModule(DeviceModule* deviceModule) { +void Application::registerDeviceModule(DeviceModule *deviceModule) { deviceModuleList.push_back(deviceModule); } diff --git a/src/ApplicationModule.cc b/src/ApplicationModule.cc index 9bbbea3d..1db517da 100644 --- a/src/ApplicationModule.cc +++ b/src/ApplicationModule.cc @@ -11,62 +11,66 @@ namespace ChimeraTK { /*********************************************************************************************************************/ - ApplicationModule::ApplicationModule(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy, const std::unordered_set<std::string> &tags) - : ModuleImpl(owner,name,description,eliminateHierarchy,tags) - { - if(!dynamic_cast<ModuleGroup*>(owner) && !dynamic_cast<Application*>(owner)) { - throw ChimeraTK::logic_error("ApplicationModules must be owned either by ModuleGroups or the Application!"); - } - if(name.find_first_of("/") != std::string::npos) { - throw ChimeraTK::logic_error("Module names must not contain slashes: '"+name+" owned by '" - +owner->getQualifiedName()+"'."); - } +ApplicationModule::ApplicationModule( + EntityOwner *owner, const std::string &name, const std::string &description, + bool eliminateHierarchy, const std::unordered_set<std::string> &tags) + : ModuleImpl(owner, name, description, eliminateHierarchy, tags) { + if (!dynamic_cast<ModuleGroup *>(owner) && + !dynamic_cast<Application *>(owner)) { + throw ChimeraTK::logic_error("ApplicationModules must be owned either by " + "ModuleGroups or the Application!"); + } + if (name.find_first_of("/") != std::string::npos) { + throw ChimeraTK::logic_error("Module names must not contain slashes: '" + + name + " owned by '" + + owner->getQualifiedName() + "'."); } +} /*********************************************************************************************************************/ - void ApplicationModule::run() { +void ApplicationModule::run() { - // start the module thread - assert(!moduleThread.joinable()); - moduleThread = boost::thread(&ApplicationModule::mainLoopWrapper, this); - } + // start the module thread + assert(!moduleThread.joinable()); + moduleThread = boost::thread(&ApplicationModule::mainLoopWrapper, this); +} /*********************************************************************************************************************/ - void ApplicationModule::terminate() { - if(moduleThread.joinable()) { - moduleThread.interrupt(); - // try joining the thread - while(!moduleThread.try_join_for(boost::chrono::milliseconds(10))) { - // if thread is not yet joined, send interrupt() to all variables. - for(auto &var : getAccessorListRecursive()) { - if(var.getDirection() == VariableDirection{VariableDirection::feeding,false}) continue; - var.getAppAccessorNoType().getHighLevelImplElement()->interrupt(); - } - // it may not suffice to send interrupt() once, as the exception might get overwritten in the queue, thus we - // repeat this until the thread was joined. +void ApplicationModule::terminate() { + if (moduleThread.joinable()) { + moduleThread.interrupt(); + // try joining the thread + while (!moduleThread.try_join_for(boost::chrono::milliseconds(10))) { + // if thread is not yet joined, send interrupt() to all variables. + for (auto &var : getAccessorListRecursive()) { + if (var.getDirection() == + VariableDirection{VariableDirection::feeding, false}) + continue; + var.getAppAccessorNoType().getHighLevelImplElement()->interrupt(); } + // it may not suffice to send interrupt() once, as the exception might get + // overwritten in the queue, thus we repeat this until the thread was + // joined. } - assert(!moduleThread.joinable()); } + assert(!moduleThread.joinable()); +} /*********************************************************************************************************************/ - ApplicationModule::~ApplicationModule() { - assert(!moduleThread.joinable()); - } +ApplicationModule::~ApplicationModule() { assert(!moduleThread.joinable()); } /*********************************************************************************************************************/ - void ApplicationModule::mainLoopWrapper() { - Application::registerThread("AM_"+getName()); - Application::testableModeLock("start"); - // enter the main loop - std::cout<<"mainLoopWrapper"<<std::endl; - mainLoop(); - Application::testableModeUnlock("terminate"); - } +void ApplicationModule::mainLoopWrapper() { + Application::registerThread("AM_" + getName()); + Application::testableModeLock("start"); + // enter the main loop + std::cout << "mainLoopWrapper" << std::endl; + mainLoop(); + Application::testableModeUnlock("terminate"); +} } /* namespace ChimeraTK */ diff --git a/src/ControlSystemModule.cc b/src/ControlSystemModule.cc index 26cb6ec2..0d263fd5 100644 --- a/src/ControlSystemModule.cc +++ b/src/ControlSystemModule.cc @@ -5,62 +5,67 @@ * Author: Martin Hierholzer */ -#include "Application.h" #include "ControlSystemModule.h" +#include "Application.h" namespace ChimeraTK { - ControlSystemModule::ControlSystemModule(const std::string& _variableNamePrefix) +ControlSystemModule::ControlSystemModule(const std::string &_variableNamePrefix) : Module(nullptr, - _variableNamePrefix.empty() ? "<ControlSystem>" - : _variableNamePrefix.substr(_variableNamePrefix.find_last_of("/")+1), + _variableNamePrefix.empty() + ? "<ControlSystem>" + : _variableNamePrefix.substr( + _variableNamePrefix.find_last_of("/") + 1), ""), - variableNamePrefix(_variableNamePrefix) - {} + variableNamePrefix(_variableNamePrefix) {} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableNetworkNode ControlSystemModule::operator()(const std::string& variableName, const std::type_info &valueType, - size_t nElements) const { - assert(variableName.find_first_of("/") == std::string::npos); - if(variables.count(variableName) == 0) { - variables[variableName] = {variableNamePrefix/variableName, {VariableDirection::invalid, false}, valueType, nElements}; - } - return variables[variableName]; +VariableNetworkNode ControlSystemModule:: +operator()(const std::string &variableName, const std::type_info &valueType, + size_t nElements) const { + assert(variableName.find_first_of("/") == std::string::npos); + if (variables.count(variableName) == 0) { + variables[variableName] = {variableNamePrefix / variableName, + {VariableDirection::invalid, false}, + valueType, + nElements}; } + return variables[variableName]; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - Module& ControlSystemModule::operator[](const std::string& moduleName) const { - assert(moduleName.find_first_of("/") == std::string::npos); - if(subModules.count(moduleName) == 0) { - subModules[moduleName] = {variableNamePrefix/moduleName}; - } - return subModules[moduleName]; +Module &ControlSystemModule::operator[](const std::string &moduleName) const { + assert(moduleName.find_first_of("/") == std::string::npos); + if (subModules.count(moduleName) == 0) { + subModules[moduleName] = {variableNamePrefix / moduleName}; } + return subModules[moduleName]; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - const Module& ControlSystemModule::virtualise() const { - return *this; - } +const Module &ControlSystemModule::virtualise() const { return *this; } - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - std::list<VariableNetworkNode> ControlSystemModule::getAccessorList() const { - std::list<VariableNetworkNode> list; - for(auto &v : variables) list.push_back(v.second); - return list; - } +std::list<VariableNetworkNode> ControlSystemModule::getAccessorList() const { + std::list<VariableNetworkNode> list; + for (auto &v : variables) + list.push_back(v.second); + return list; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - std::list<Module*> ControlSystemModule::getSubmoduleList() const { - std::list<Module*> list; - for(auto &m : subModules) list.push_back(&m.second); - return list; - } +std::list<Module *> ControlSystemModule::getSubmoduleList() const { + std::list<Module *> list; + for (auto &m : subModules) + list.push_back(&m.second); + return list; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ -} +} // namespace ChimeraTK diff --git a/src/DeviceModule.cc b/src/DeviceModule.cc index 677fa6eb..6bab5800 100644 --- a/src/DeviceModule.cc +++ b/src/DeviceModule.cc @@ -5,8 +5,8 @@ * Author: Martin Hierholzer */ -#include <ChimeraTK/DeviceBackend.h> #include <ChimeraTK/Device.h> +#include <ChimeraTK/DeviceBackend.h> #include "Application.h" #include "DeviceModule.h" @@ -14,235 +14,245 @@ namespace ChimeraTK { - DeviceModule::DeviceModule(const std::string& _deviceAliasOrURI, const std::string& _registerNamePrefix) - : Module(nullptr, - _registerNamePrefix.empty() ? "<Device:" + _deviceAliasOrURI + ">" : - _registerNamePrefix.substr(_registerNamePrefix.find_last_of("/") + 1), - ""), - deviceAliasOrURI(_deviceAliasOrURI), registerNamePrefix(_registerNamePrefix) {} - - /*********************************************************************************************************************/ - - DeviceModule::DeviceModule(Application* application, - const std::string& _deviceAliasOrURI, - const std::string& _registerNamePrefix) - : Module(nullptr, - _registerNamePrefix.empty() ? "<Device:" + _deviceAliasOrURI + ">" : - _registerNamePrefix.substr(_registerNamePrefix.find_last_of("/") + 1), - ""), - deviceAliasOrURI(_deviceAliasOrURI), registerNamePrefix(_registerNamePrefix) { - application->registerDeviceModule(this); - } - - /*********************************************************************************************************************/ - - DeviceModule::~DeviceModule() { assert(!moduleThread.joinable()); } - - /*********************************************************************************************************************/ - - VariableNetworkNode DeviceModule::operator()(const std::string& registerName, - UpdateMode mode, - const std::type_info& valueType, - size_t nElements) const { - return {registerName, deviceAliasOrURI, registerNamePrefix / registerName, mode, - {VariableDirection::invalid, false}, valueType, nElements}; +DeviceModule::DeviceModule(const std::string &_deviceAliasOrURI, + const std::string &_registerNamePrefix) + : Module(nullptr, + _registerNamePrefix.empty() + ? "<Device:" + _deviceAliasOrURI + ">" + : _registerNamePrefix.substr( + _registerNamePrefix.find_last_of("/") + 1), + ""), + deviceAliasOrURI(_deviceAliasOrURI), + registerNamePrefix(_registerNamePrefix) {} + +/*********************************************************************************************************************/ + +DeviceModule::DeviceModule(Application *application, + const std::string &_deviceAliasOrURI, + const std::string &_registerNamePrefix) + : Module(nullptr, + _registerNamePrefix.empty() + ? "<Device:" + _deviceAliasOrURI + ">" + : _registerNamePrefix.substr( + _registerNamePrefix.find_last_of("/") + 1), + ""), + deviceAliasOrURI(_deviceAliasOrURI), + registerNamePrefix(_registerNamePrefix) { + application->registerDeviceModule(this); +} + +/*********************************************************************************************************************/ + +DeviceModule::~DeviceModule() { assert(!moduleThread.joinable()); } + +/*********************************************************************************************************************/ + +VariableNetworkNode DeviceModule::operator()(const std::string ®isterName, + UpdateMode mode, + const std::type_info &valueType, + size_t nElements) const { + return {registerName, + deviceAliasOrURI, + registerNamePrefix / registerName, + mode, + {VariableDirection::invalid, false}, + valueType, + nElements}; +} + +/*********************************************************************************************************************/ + +Module &DeviceModule::operator[](const std::string &moduleName) const { + if (subModules.count(moduleName) == 0) { + subModules[moduleName] = {deviceAliasOrURI, + registerNamePrefix / moduleName}; } + return subModules[moduleName]; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - Module& DeviceModule::operator[](const std::string& moduleName) const { - if(subModules.count(moduleName) == 0) { - subModules[moduleName] = {deviceAliasOrURI, registerNamePrefix / moduleName}; - } - return subModules[moduleName]; - } +const Module &DeviceModule::virtualise() const { return *this; } - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - const Module& DeviceModule::virtualise() const { return *this; } +void DeviceModule::connectTo(const Module &target, + VariableNetworkNode trigger) const { + auto &cat = virtualiseFromCatalog(); + cat.connectTo(target, trigger); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void DeviceModule::connectTo(const Module& target, VariableNetworkNode trigger) const { - auto& cat = virtualiseFromCatalog(); - cat.connectTo(target, trigger); - } - - /*********************************************************************************************************************/ - - VirtualModule& DeviceModule::virtualiseFromCatalog() const { - if(virtualisedModuleFromCatalog_isValid) return virtualisedModuleFromCatalog; - - virtualisedModuleFromCatalog = VirtualModule(deviceAliasOrURI, "Device module", ModuleType::Device); - - // obtain register catalogue - Device d; - d.open(deviceAliasOrURI); /// @todo: do not actually open the device (needs extension of DeviceAccess)! - auto catalog = d.getRegisterCatalogue(); - - // iterate catalogue, create VariableNetworkNode for all registers starting with the registerNamePrefix - size_t prefixLength = registerNamePrefix.length(); - for(auto& reg : catalog) { - if(std::string(reg.getRegisterName()).substr(0, prefixLength) != std::string(registerNamePrefix)) continue; - - // ignore 2D registers - if(reg.getNumberOfDimensions() > 1) continue; +VirtualModule &DeviceModule::virtualiseFromCatalog() const { + if (virtualisedModuleFromCatalog_isValid) + return virtualisedModuleFromCatalog; - // guess direction and determine update mode - VariableDirection direction; - UpdateMode updateMode; - if(reg.isWriteable()) { - direction = {VariableDirection::consuming, false}; + virtualisedModuleFromCatalog = + VirtualModule(deviceAliasOrURI, "Device module", ModuleType::Device); + + // obtain register catalogue + Device d; + d.open(deviceAliasOrURI); /// @todo: do not actually open the device (needs + /// extension of DeviceAccess)! + auto catalog = d.getRegisterCatalogue(); + + // iterate catalogue, create VariableNetworkNode for all registers starting + // with the registerNamePrefix + size_t prefixLength = registerNamePrefix.length(); + for (auto ® : catalog) { + if (std::string(reg.getRegisterName()).substr(0, prefixLength) != + std::string(registerNamePrefix)) + continue; + + // ignore 2D registers + if (reg.getNumberOfDimensions() > 1) + continue; + + // guess direction and determine update mode + VariableDirection direction; + UpdateMode updateMode; + if (reg.isWriteable()) { + direction = {VariableDirection::consuming, false}; + updateMode = UpdateMode::push; + } else { + direction = {VariableDirection::feeding, false}; + if (reg.getSupportedAccessModes().has(AccessMode::wait_for_new_data)) { updateMode = UpdateMode::push; + } else { + updateMode = UpdateMode::poll; } - else { - direction = {VariableDirection::feeding, false}; - if(reg.getSupportedAccessModes().has(AccessMode::wait_for_new_data)) { - updateMode = UpdateMode::push; - } - else { - updateMode = UpdateMode::poll; - } - } + } - // guess type - const std::type_info* valTyp{&typeid(AnyType)}; - auto& dd = reg.getDataDescriptor(); // numeric, string, boolean, nodata, undefined - if(dd.fundamentalType() == RegisterInfo::FundamentalType::numeric) { - if(dd.isIntegral()) { - if(dd.isSigned()) { - if(dd.nDigits() > 11) { - valTyp = &typeid(int64_t); - } - else if(dd.nDigits() > 6) { - valTyp = &typeid(int32_t); - } - else if(dd.nDigits() > 4) { - valTyp = &typeid(int16_t); - } - else { - valTyp = &typeid(int8_t); - } + // guess type + const std::type_info *valTyp{&typeid(AnyType)}; + auto &dd = + reg.getDataDescriptor(); // numeric, string, boolean, nodata, undefined + if (dd.fundamentalType() == RegisterInfo::FundamentalType::numeric) { + if (dd.isIntegral()) { + if (dd.isSigned()) { + if (dd.nDigits() > 11) { + valTyp = &typeid(int64_t); + } else if (dd.nDigits() > 6) { + valTyp = &typeid(int32_t); + } else if (dd.nDigits() > 4) { + valTyp = &typeid(int16_t); + } else { + valTyp = &typeid(int8_t); } - else { - if(dd.nDigits() > 10) { - valTyp = &typeid(uint64_t); - } - else if(dd.nDigits() > 5) { - valTyp = &typeid(uint32_t); - } - else if(dd.nDigits() > 3) { - valTyp = &typeid(uint16_t); - } - else { - valTyp = &typeid(uint8_t); - } + } else { + if (dd.nDigits() > 10) { + valTyp = &typeid(uint64_t); + } else if (dd.nDigits() > 5) { + valTyp = &typeid(uint32_t); + } else if (dd.nDigits() > 3) { + valTyp = &typeid(uint16_t); + } else { + valTyp = &typeid(uint8_t); } } - else { // fractional - valTyp = &typeid(double); - } - } - else if(dd.fundamentalType() == RegisterInfo::FundamentalType::boolean) { - valTyp = &typeid(int32_t); - } - else if(dd.fundamentalType() == RegisterInfo::FundamentalType::string) { - valTyp = &typeid(std::string); + } else { // fractional + valTyp = &typeid(double); } - - auto name = std::string(reg.getRegisterName()).substr(prefixLength); - auto lastSlash = name.find_last_of("/"); - auto dirname = name.substr(0, lastSlash); - auto basename = name.substr(lastSlash + 1); - VariableNetworkNode node( - basename, deviceAliasOrURI, reg.getRegisterName(), updateMode, direction, *valTyp, reg.getNumberOfElements()); - virtualisedModuleFromCatalog.createAndGetSubmoduleRecursive(dirname).addAccessor(node); + } else if (dd.fundamentalType() == RegisterInfo::FundamentalType::boolean) { + valTyp = &typeid(int32_t); + } else if (dd.fundamentalType() == RegisterInfo::FundamentalType::string) { + valTyp = &typeid(std::string); } - virtualisedModuleFromCatalog_isValid = true; - return virtualisedModuleFromCatalog; - } - - /*********************************************************************************************************************/ - - void DeviceModule::reportException(std::string errMsg) { - std::unique_lock<std::mutex> lk(errorMutex); - errorQueue.push(errMsg); - errorCondVar.wait(lk); - lk.unlock(); + auto name = std::string(reg.getRegisterName()).substr(prefixLength); + auto lastSlash = name.find_last_of("/"); + auto dirname = name.substr(0, lastSlash); + auto basename = name.substr(lastSlash + 1); + VariableNetworkNode node(basename, deviceAliasOrURI, reg.getRegisterName(), + updateMode, direction, *valTyp, + reg.getNumberOfElements()); + virtualisedModuleFromCatalog.createAndGetSubmoduleRecursive(dirname) + .addAccessor(node); } - /*********************************************************************************************************************/ - - void DeviceModule::handleException() { - Application::registerThread("DM_" + getName()); - Device d; - std::string error; - - try { - while(true) { - errorQueue.pop_wait(error); + virtualisedModuleFromCatalog_isValid = true; + return virtualisedModuleFromCatalog; +} + +/*********************************************************************************************************************/ + +void DeviceModule::reportException(std::string errMsg) { + std::unique_lock<std::mutex> lk(errorMutex); + errorQueue.push(errMsg); + errorCondVar.wait(lk); + lk.unlock(); +} + +/*********************************************************************************************************************/ + +void DeviceModule::handleException() { + Application::registerThread("DM_" + getName()); + Device d; + std::string error; + + try { + while (true) { + errorQueue.pop_wait(error); + boost::this_thread::interruption_point(); + std::lock_guard<std::mutex> lk(errorMutex); + deviceError.status = 1; + deviceError.message = error; + deviceError.setCurrentVersionNumber({}); + deviceError.writeAll(); + while (true) { boost::this_thread::interruption_point(); - std::lock_guard<std::mutex> lk(errorMutex); - deviceError.status = 1; - deviceError.message = error; - deviceError.setCurrentVersionNumber({}); - deviceError.writeAll(); - while(true) { - boost::this_thread::interruption_point(); - try { - d.open(deviceAliasOrURI); - if(d.isOpened()) { - break; - } + try { + d.open(deviceAliasOrURI); + if (d.isOpened()) { + break; } - catch(std::exception& ex) { - deviceError.status = 1; - deviceError.message = ex.what(); - deviceError.setCurrentVersionNumber({}); - deviceError.writeAll(); - } - usleep(500000); + } catch (std::exception &ex) { + deviceError.status = 1; + deviceError.message = ex.what(); + deviceError.setCurrentVersionNumber({}); + deviceError.writeAll(); } - deviceError.status = 0; - deviceError.message = ""; - deviceError.setCurrentVersionNumber({}); - deviceError.writeAll(); - errorCondVar.notify_all(); + usleep(500000); } - } - catch(...) { - // before we leave this thread, we might need to notify other waiting threads. - // boost::this_thread::interruption_point() throws an exception when the thread should be interrupted, so we will - // end up here + deviceError.status = 0; + deviceError.message = ""; + deviceError.setCurrentVersionNumber({}); + deviceError.writeAll(); errorCondVar.notify_all(); - throw; } + } catch (...) { + // before we leave this thread, we might need to notify other waiting + // threads. boost::this_thread::interruption_point() throws an exception + // when the thread should be interrupted, so we will end up here + errorCondVar.notify_all(); + throw; } +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void DeviceModule::run() { - // start the module thread - assert(!moduleThread.joinable()); - moduleThread = boost::thread(&DeviceModule::handleException, this); - } - - /*********************************************************************************************************************/ +void DeviceModule::run() { + // start the module thread + assert(!moduleThread.joinable()); + moduleThread = boost::thread(&DeviceModule::handleException, this); +} - void DeviceModule::terminate() { - if(moduleThread.joinable()) { - moduleThread.interrupt(); - reportException("ExitOnNone"); - moduleThread.join(); - } - assert(!moduleThread.joinable()); - } +/*********************************************************************************************************************/ - void DeviceModule::defineConnections() { - std::string prefix = "Devices/" + deviceAliasOrURI + "/"; - ControlSystemModule cs(prefix); - deviceError.connectTo(cs); +void DeviceModule::terminate() { + if (moduleThread.joinable()) { + moduleThread.interrupt(); + reportException("ExitOnNone"); + moduleThread.join(); } + assert(!moduleThread.joinable()); +} + +void DeviceModule::defineConnections() { + std::string prefix = "Devices/" + deviceAliasOrURI + "/"; + ControlSystemModule cs(prefix); + deviceError.connectTo(cs); +} } // namespace ChimeraTK diff --git a/src/EntityOwner.cc b/src/EntityOwner.cc index 1029e702..2ae9b6b0 100644 --- a/src/EntityOwner.cc +++ b/src/EntityOwner.cc @@ -6,9 +6,9 @@ */ #include <cassert> -#include <regex> -#include <iostream> #include <fstream> +#include <iostream> +#include <regex> #include "EntityOwner.h" #include "Module.h" @@ -17,223 +17,243 @@ namespace ChimeraTK { - EntityOwner::EntityOwner(const std::string &name, const std::string &description, - bool eliminateHierarchy, const std::unordered_set<std::string> &tags) - : _name(name), _description(description), _eliminateHierarchy(eliminateHierarchy), _tags(tags) - {} +EntityOwner::EntityOwner(const std::string &name, + const std::string &description, + bool eliminateHierarchy, + const std::unordered_set<std::string> &tags) + : _name(name), _description(description), + _eliminateHierarchy(eliminateHierarchy), _tags(tags) {} /*********************************************************************************************************************/ - EntityOwner::~EntityOwner() {} +EntityOwner::~EntityOwner() {} /*********************************************************************************************************************/ - EntityOwner& EntityOwner::operator=(EntityOwner &&other) { - _name = std::move(other._name); - _description = std::move(other._description); - accessorList = std::move(other.accessorList); - moduleList = std::move(other.moduleList); - _eliminateHierarchy = other._eliminateHierarchy; - _tags = std::move(other._tags); - for(auto mod : moduleList) { - mod->setOwner(this); - } - for(auto node : accessorList) { - node.setOwningModule(this); - } - return *this; +EntityOwner &EntityOwner::operator=(EntityOwner &&other) { + _name = std::move(other._name); + _description = std::move(other._description); + accessorList = std::move(other.accessorList); + moduleList = std::move(other.moduleList); + _eliminateHierarchy = other._eliminateHierarchy; + _tags = std::move(other._tags); + for (auto mod : moduleList) { + mod->setOwner(this); + } + for (auto node : accessorList) { + node.setOwningModule(this); } + return *this; +} /*********************************************************************************************************************/ - void EntityOwner::registerModule(Module *module, bool addTags) { - if(addTags) for(auto &tag : _tags) module->addTag(tag); - moduleList.push_back(module); - } +void EntityOwner::registerModule(Module *module, bool addTags) { + if (addTags) + for (auto &tag : _tags) + module->addTag(tag); + moduleList.push_back(module); +} /*********************************************************************************************************************/ - void EntityOwner::unregisterModule(Module *module) { - moduleList.remove(module); - } +void EntityOwner::unregisterModule(Module *module) { + moduleList.remove(module); +} /*********************************************************************************************************************/ - std::list<VariableNetworkNode> EntityOwner::getAccessorListRecursive() { - // add accessors of this instance itself - std::list<VariableNetworkNode> list = getAccessorList(); +std::list<VariableNetworkNode> EntityOwner::getAccessorListRecursive() { + // add accessors of this instance itself + std::list<VariableNetworkNode> list = getAccessorList(); - // iterate through submodules - for(auto submodule : getSubmoduleList()) { - auto sublist = submodule->getAccessorListRecursive(); - list.insert(list.end(), sublist.begin(), sublist.end()); - } - return list; + // iterate through submodules + for (auto submodule : getSubmoduleList()) { + auto sublist = submodule->getAccessorListRecursive(); + list.insert(list.end(), sublist.begin(), sublist.end()); } + return list; +} /*********************************************************************************************************************/ - std::list<Module*> EntityOwner::getSubmoduleListRecursive() { - // add modules of this instance itself - std::list<Module*> list = getSubmoduleList(); +std::list<Module *> EntityOwner::getSubmoduleListRecursive() { + // add modules of this instance itself + std::list<Module *> list = getSubmoduleList(); - // iterate through submodules - for(auto submodule : getSubmoduleList()) { - auto sublist = submodule->getSubmoduleListRecursive(); - list.insert(list.end(), sublist.begin(), sublist.end()); - } - return list; + // iterate through submodules + for (auto submodule : getSubmoduleList()) { + auto sublist = submodule->getSubmoduleListRecursive(); + list.insert(list.end(), sublist.begin(), sublist.end()); } + return list; +} /*********************************************************************************************************************/ - VirtualModule EntityOwner::findTag(const std::string &tag) const { +VirtualModule EntityOwner::findTag(const std::string &tag) const { - // create new module to return - VirtualModule module{_name, _description, getModuleType()}; + // create new module to return + VirtualModule module{_name, _description, getModuleType()}; - // add everything matching the tag to the virtual module and return it - findTagAndAppendToModule(module, tag, false, true); - return module; - } + // add everything matching the tag to the virtual module and return it + findTagAndAppendToModule(module, tag, false, true); + return module; +} /*********************************************************************************************************************/ - VirtualModule EntityOwner::excludeTag(const std::string &tag) const { +VirtualModule EntityOwner::excludeTag(const std::string &tag) const { - // create new module to return - VirtualModule module{_name, _description, getModuleType()}; + // create new module to return + VirtualModule module{_name, _description, getModuleType()}; - // add everything matching the tag to the virtual module and return it - findTagAndAppendToModule(module, tag, false, true, true); - return module; - } + // add everything matching the tag to the virtual module and return it + findTagAndAppendToModule(module, tag, false, true, true); + return module; +} /*********************************************************************************************************************/ - void EntityOwner::findTagAndAppendToModule(VirtualModule &module, const std::string &tag, bool eliminateAllHierarchies, - bool eliminateFirstHierarchy, bool negate) const { - - VirtualModule nextmodule{_name, _description, getModuleType()}; - VirtualModule *moduleToAddTo; - - bool needToAddSubModule = false; - if(!getEliminateHierarchy() && !eliminateAllHierarchies && !eliminateFirstHierarchy) { - moduleToAddTo = &nextmodule; - needToAddSubModule = true; - } - else { - moduleToAddTo = &module; - } +void EntityOwner::findTagAndAppendToModule(VirtualModule &module, + const std::string &tag, + bool eliminateAllHierarchies, + bool eliminateFirstHierarchy, + bool negate) const { + + VirtualModule nextmodule{_name, _description, getModuleType()}; + VirtualModule *moduleToAddTo; + + bool needToAddSubModule = false; + if (!getEliminateHierarchy() && !eliminateAllHierarchies && + !eliminateFirstHierarchy) { + moduleToAddTo = &nextmodule; + needToAddSubModule = true; + } else { + moduleToAddTo = &module; + } - // add nodes to the module if matching the tag - std::regex expr(tag); - for(auto node : getAccessorList()) { - bool addNode = false; - for(auto &nodeTag : node.getTags()) { - if(std::regex_match(nodeTag, expr)) { - addNode = true; - break; - } + // add nodes to the module if matching the tag + std::regex expr(tag); + for (auto node : getAccessorList()) { + bool addNode = false; + for (auto &nodeTag : node.getTags()) { + if (std::regex_match(nodeTag, expr)) { + addNode = true; + break; } - if(node.getTags().size() == 0) if(std::regex_match("", expr)) addNode = true; // check if empty tag matches, if no tag applied to node - if(negate) addNode = !addNode; - if(addNode) moduleToAddTo->registerAccessor(node); } + if (node.getTags().size() == 0) + if (std::regex_match("", expr)) + addNode = true; // check if empty tag matches, if no tag applied to node + if (negate) + addNode = !addNode; + if (addNode) + moduleToAddTo->registerAccessor(node); + } - // iterate through submodules - for(auto submodule : getSubmoduleList()) { - // check if submodule already exists by this name and its hierarchy should not be eliminated - if(!moduleToAddTo->getEliminateHierarchy() && moduleToAddTo->hasSubmodule(submodule->getName())) { - // exists: add to the existing module - auto *existingSubModule = dynamic_cast<VirtualModule*>(moduleToAddTo->getSubmodule(submodule->getName())); - assert(existingSubModule != nullptr); - submodule->findTagAndAppendToModule(*existingSubModule, tag, eliminateAllHierarchies, true, negate); - } - else { - // does not yet exist: add as new submodule to the current module - submodule->findTagAndAppendToModule(*moduleToAddTo, tag, eliminateAllHierarchies, false, negate); - } + // iterate through submodules + for (auto submodule : getSubmoduleList()) { + // check if submodule already exists by this name and its hierarchy should + // not be eliminated + if (!moduleToAddTo->getEliminateHierarchy() && + moduleToAddTo->hasSubmodule(submodule->getName())) { + // exists: add to the existing module + auto *existingSubModule = dynamic_cast<VirtualModule *>( + moduleToAddTo->getSubmodule(submodule->getName())); + assert(existingSubModule != nullptr); + submodule->findTagAndAppendToModule( + *existingSubModule, tag, eliminateAllHierarchies, true, negate); + } else { + // does not yet exist: add as new submodule to the current module + submodule->findTagAndAppendToModule( + *moduleToAddTo, tag, eliminateAllHierarchies, false, negate); } + } - if(needToAddSubModule) { - if( nextmodule.getAccessorList().size() > 0 || nextmodule.getSubmoduleList().size() > 0 ) { - module.addSubModule(nextmodule); - } + if (needToAddSubModule) { + if (nextmodule.getAccessorList().size() > 0 || + nextmodule.getSubmoduleList().size() > 0) { + module.addSubModule(nextmodule); } - } +} /*********************************************************************************************************************/ - bool EntityOwner::hasSubmodule(const std::string &name) const { - for(auto submodule : getSubmoduleList()) { - if(submodule->getName() == name) return true; - } - return false; +bool EntityOwner::hasSubmodule(const std::string &name) const { + for (auto submodule : getSubmoduleList()) { + if (submodule->getName() == name) + return true; } + return false; +} /*********************************************************************************************************************/ - Module* EntityOwner::getSubmodule(const std::string &name) const { - for(auto submodule : getSubmoduleList()) { - if(submodule->getName() == name) return submodule; - } - throw ChimeraTK::logic_error("Submodule '"+name+"' not found in module '"+getName()+"'!"); +Module *EntityOwner::getSubmodule(const std::string &name) const { + for (auto submodule : getSubmoduleList()) { + if (submodule->getName() == name) + return submodule; } + throw ChimeraTK::logic_error("Submodule '" + name + + "' not found in module '" + getName() + "'!"); +} /*********************************************************************************************************************/ - void EntityOwner::dump(const std::string &prefix) const { - - if(prefix == "") { - std::cout << "==== Hierarchy dump of module '" << _name << "':" << std::endl; - } +void EntityOwner::dump(const std::string &prefix) const { - for(auto &node : getAccessorList()) { - std::cout << prefix << "+ "; - node.dump(); - } + if (prefix == "") { + std::cout << "==== Hierarchy dump of module '" << _name + << "':" << std::endl; + } - for(auto &submodule : getSubmoduleList()) { - std::cout << prefix << "| " << submodule->getName() << std::endl; - submodule->dump(prefix+"| "); - } + for (auto &node : getAccessorList()) { + std::cout << prefix << "+ "; + node.dump(); + } + for (auto &submodule : getSubmoduleList()) { + std::cout << prefix << "| " << submodule->getName() << std::endl; + submodule->dump(prefix + "| "); } +} /*********************************************************************************************************************/ - void EntityOwner::dumpGraph(const std::string& fileName) const { - std::fstream file(fileName, std::ios_base::out); - ModuleGraphVisitor v{file, true}; - v.dispatch(*this); - } +void EntityOwner::dumpGraph(const std::string &fileName) const { + std::fstream file(fileName, std::ios_base::out); + ModuleGraphVisitor v{file, true}; + v.dispatch(*this); +} /*********************************************************************************************************************/ - void EntityOwner::dumpModuleGraph(const std::string& fileName) const { - std::fstream file(fileName, std::ios_base::out); - ModuleGraphVisitor v{file, false}; - v.dispatch(*this); - } +void EntityOwner::dumpModuleGraph(const std::string &fileName) const { + std::fstream file(fileName, std::ios_base::out); + ModuleGraphVisitor v{file, false}; + v.dispatch(*this); +} /*********************************************************************************************************************/ - void EntityOwner::addTag(const std::string &tag) { - for(auto &node : getAccessorList()) node.addTag(tag); - for(auto &submodule : getSubmoduleList()) submodule->addTag(tag); - _tags.insert(tag); - } +void EntityOwner::addTag(const std::string &tag) { + for (auto &node : getAccessorList()) + node.addTag(tag); + for (auto &submodule : getSubmoduleList()) + submodule->addTag(tag); + _tags.insert(tag); +} /*********************************************************************************************************************/ - VirtualModule EntityOwner::flatten() { - VirtualModule nextmodule{_name, _description, getModuleType()}; - for(auto &node : getAccessorListRecursive()) { - nextmodule.registerAccessor(node); - } - return nextmodule; +VirtualModule EntityOwner::flatten() { + VirtualModule nextmodule{_name, _description, getModuleType()}; + for (auto &node : getAccessorListRecursive()) { + nextmodule.registerAccessor(node); } + return nextmodule; +} } /* namespace ChimeraTK */ diff --git a/src/Module.cc b/src/Module.cc index d69579ab..75ca0cf0 100644 --- a/src/Module.cc +++ b/src/Module.cc @@ -5,108 +5,127 @@ * Author: Martin Hierholzer */ -#include "Application.h" #include "Module.h" +#include "Application.h" #include "VirtualModule.h" namespace ChimeraTK { - Module::Module(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy, const std::unordered_set<std::string> &tags) - : EntityOwner(name, description, eliminateHierarchy, tags), - _owner(owner) - { - if(_owner != nullptr) _owner->registerModule(this); - } +Module::Module(EntityOwner *owner, const std::string &name, + const std::string &description, bool eliminateHierarchy, + const std::unordered_set<std::string> &tags) + : EntityOwner(name, description, eliminateHierarchy, tags), _owner(owner) { + if (_owner != nullptr) + _owner->registerModule(this); +} /*********************************************************************************************************************/ - Module::~Module() - { - if(_owner != nullptr) _owner->unregisterModule(this); - } +Module::~Module() { + if (_owner != nullptr) + _owner->unregisterModule(this); +} /*********************************************************************************************************************/ - Module& Module::operator=(Module &&other) { - EntityOwner::operator=(std::move(other)); - _owner = other._owner; - if(_owner != nullptr) _owner->registerModule(this, false); - // note: the other module unregisters itself in its destructor - will will be called next after any move operation - return *this; - } +Module &Module::operator=(Module &&other) { + EntityOwner::operator=(std::move(other)); + _owner = other._owner; + if (_owner != nullptr) + _owner->registerModule(this, false); + // note: the other module unregisters itself in its destructor - will will be + // called next after any move operation + return *this; +} /*********************************************************************************************************************/ - ChimeraTK::ReadAnyGroup Module::readAnyGroup() { +ChimeraTK::ReadAnyGroup Module::readAnyGroup() { - auto accessorList = getAccessorListRecursive(); - - // put push-type transfer elements into a ReadAnyGroup - ChimeraTK::ReadAnyGroup group; - for(auto &accessor : accessorList) { - if(accessor.getDirection() == VariableDirection{VariableDirection::feeding, false}) continue; - group.add(accessor.getAppAccessorNoType()); - } - - group.finalise(); - return group; + auto accessorList = getAccessorListRecursive(); + // put push-type transfer elements into a ReadAnyGroup + ChimeraTK::ReadAnyGroup group; + for (auto &accessor : accessorList) { + if (accessor.getDirection() == + VariableDirection{VariableDirection::feeding, false}) + continue; + group.add(accessor.getAppAccessorNoType()); } + group.finalise(); + return group; +} + /*********************************************************************************************************************/ - void Module::readAll() { - auto accessorList = getAccessorListRecursive(); - // first blockingly read all push-type variables - for(auto accessor : accessorList) { - if(accessor.getDirection() == VariableDirection{VariableDirection::feeding, false}) continue; - if(accessor.getMode() != UpdateMode::push) continue; - accessor.getAppAccessorNoType().read(); - } - // next non-blockingly read the latest values of all poll-type variables - for(auto accessor : accessorList) { - if(accessor.getDirection() == VariableDirection{VariableDirection::feeding, false}) continue; - if(accessor.getMode() == UpdateMode::push) continue; - accessor.getAppAccessorNoType().readLatest(); - } +void Module::readAll() { + auto accessorList = getAccessorListRecursive(); + // first blockingly read all push-type variables + for (auto accessor : accessorList) { + if (accessor.getDirection() == + VariableDirection{VariableDirection::feeding, false}) + continue; + if (accessor.getMode() != UpdateMode::push) + continue; + accessor.getAppAccessorNoType().read(); } + // next non-blockingly read the latest values of all poll-type variables + for (auto accessor : accessorList) { + if (accessor.getDirection() == + VariableDirection{VariableDirection::feeding, false}) + continue; + if (accessor.getMode() == UpdateMode::push) + continue; + accessor.getAppAccessorNoType().readLatest(); + } +} /*********************************************************************************************************************/ - void Module::readAllNonBlocking() { - auto accessorList = getAccessorListRecursive(); - for(auto accessor : accessorList) { - if(accessor.getDirection() == VariableDirection{VariableDirection::feeding, false}) continue; - if(accessor.getMode() != UpdateMode::push) continue; - accessor.getAppAccessorNoType().readNonBlocking(); - } - for(auto accessor : accessorList) { - if(accessor.getDirection() == VariableDirection{VariableDirection::feeding, false}) continue; - if(accessor.getMode() == UpdateMode::push) continue; - accessor.getAppAccessorNoType().readLatest(); - } +void Module::readAllNonBlocking() { + auto accessorList = getAccessorListRecursive(); + for (auto accessor : accessorList) { + if (accessor.getDirection() == + VariableDirection{VariableDirection::feeding, false}) + continue; + if (accessor.getMode() != UpdateMode::push) + continue; + accessor.getAppAccessorNoType().readNonBlocking(); + } + for (auto accessor : accessorList) { + if (accessor.getDirection() == + VariableDirection{VariableDirection::feeding, false}) + continue; + if (accessor.getMode() == UpdateMode::push) + continue; + accessor.getAppAccessorNoType().readLatest(); } +} /*********************************************************************************************************************/ - void Module::readAllLatest() { - auto accessorList = getAccessorListRecursive(); - for(auto accessor : accessorList) { - if(accessor.getDirection() == VariableDirection{VariableDirection::feeding, false}) continue; - accessor.getAppAccessorNoType().readLatest(); - } +void Module::readAllLatest() { + auto accessorList = getAccessorListRecursive(); + for (auto accessor : accessorList) { + if (accessor.getDirection() == + VariableDirection{VariableDirection::feeding, false}) + continue; + accessor.getAppAccessorNoType().readLatest(); } +} /*********************************************************************************************************************/ - void Module::writeAll() { - auto versionNumber = getCurrentVersionNumber(); - auto accessorList = getAccessorListRecursive(); - for(auto accessor : accessorList) { - if(accessor.getDirection() == VariableDirection{VariableDirection::consuming, false}) continue; - accessor.getAppAccessorNoType().write(versionNumber); - } +void Module::writeAll() { + auto versionNumber = getCurrentVersionNumber(); + auto accessorList = getAccessorListRecursive(); + for (auto accessor : accessorList) { + if (accessor.getDirection() == + VariableDirection{VariableDirection::consuming, false}) + continue; + accessor.getAppAccessorNoType().write(versionNumber); } +} } /* namespace ChimeraTK */ diff --git a/src/ModuleGraphVisitor.cc b/src/ModuleGraphVisitor.cc index 9d1e8a6d..d65c5dad 100644 --- a/src/ModuleGraphVisitor.cc +++ b/src/ModuleGraphVisitor.cc @@ -7,67 +7,70 @@ namespace ChimeraTK { -ModuleGraphVisitor::ModuleGraphVisitor(std::ostream& stream, bool showVariables) - : Visitor<ChimeraTK::EntityOwner, ChimeraTK::Module, ChimeraTK::VariableNetworkNode> () - , _stream(stream) - , _showVariables(showVariables) {} +ModuleGraphVisitor::ModuleGraphVisitor(std::ostream &stream, bool showVariables) + : Visitor<ChimeraTK::EntityOwner, ChimeraTK::Module, + ChimeraTK::VariableNetworkNode>(), + _stream(stream), _showVariables(showVariables) {} void ModuleGraphVisitor::dispatch(const EntityOwner &owner) { - /* If we start with an entity owner, consider ourselves the start of a graph */ - /* When descending from here, we only use Module directly */ - _stream << "digraph G {" << "\n"; - dumpEntityOwner(owner); - _stream << "}" << std::endl; + /* If we start with an entity owner, consider ourselves the start of a graph + */ + /* When descending from here, we only use Module directly */ + _stream << "digraph G {" + << "\n"; + dumpEntityOwner(owner); + _stream << "}" << std::endl; } void ModuleGraphVisitor::dispatch(const VariableNetworkNode &node) { - std::string dotNode = detail::encodeDotNodeName(node.getQualifiedName()); - _stream << " " << dotNode << "[label=\"{" << node.getName() << "| {"; - bool first = true; - for(auto tag : node.getTags()) { - if(!first) { - _stream << "|"; - } - else { - first = false; - } - _stream << tag; + std::string dotNode = detail::encodeDotNodeName(node.getQualifiedName()); + _stream << " " << dotNode << "[label=\"{" << node.getName() << "| {"; + bool first = true; + for (auto tag : node.getTags()) { + if (!first) { + _stream << "|"; + } else { + first = false; } - _stream << "}}\", shape=record]" << std::endl; + _stream << tag; + } + _stream << "}}\", shape=record]" << std::endl; } void ModuleGraphVisitor::dispatch(const Module &module) { - dumpEntityOwner(static_cast<const EntityOwner &>(module)); + dumpEntityOwner(static_cast<const EntityOwner &>(module)); } void ModuleGraphVisitor::dumpEntityOwner(const EntityOwner &module) { - std::string myDotNode = detail::encodeDotNodeName(module.getQualifiedName()); - _stream << " " << myDotNode << "[label=\"" << module.getName() << "\""; - if(module.getEliminateHierarchy()) { - _stream << ",style=dotted"; - } - if(module.getModuleType() == EntityOwner::ModuleType::ModuleGroup) { - _stream << ",peripheries=2"; - } - if(module.getModuleType() == EntityOwner::ModuleType::ApplicationModule) { - _stream << ",penwidth=3"; - } - _stream << "]" << std::endl; + std::string myDotNode = detail::encodeDotNodeName(module.getQualifiedName()); + _stream << " " << myDotNode << "[label=\"" << module.getName() << "\""; + if (module.getEliminateHierarchy()) { + _stream << ",style=dotted"; + } + if (module.getModuleType() == EntityOwner::ModuleType::ModuleGroup) { + _stream << ",peripheries=2"; + } + if (module.getModuleType() == EntityOwner::ModuleType::ApplicationModule) { + _stream << ",penwidth=3"; + } + _stream << "]" << std::endl; - if(_showVariables) { - for(auto &node : module.getAccessorList()) { - std::string dotNode = detail::encodeDotNodeName(detail::nodeName(node)); - node.accept(*this); - _stream << " " << myDotNode << " -> " << dotNode << std::endl; - } + if (_showVariables) { + for (auto &node : module.getAccessorList()) { + std::string dotNode = detail::encodeDotNodeName(detail::nodeName(node)); + node.accept(*this); + _stream << " " << myDotNode << " -> " << dotNode << std::endl; } + } - for(const Module *submodule : module.getSubmoduleList()) { - if(submodule->getModuleType() == EntityOwner::ModuleType::Device || - submodule->getModuleType() == EntityOwner::ModuleType::ControlSystem) continue; - std::string dotNode = detail::encodeDotNodeName(submodule->getQualifiedName()); - _stream << " " << myDotNode << " -> " << dotNode << std::endl; - submodule->accept(*this); - } -} + for (const Module *submodule : module.getSubmoduleList()) { + if (submodule->getModuleType() == EntityOwner::ModuleType::Device || + submodule->getModuleType() == EntityOwner::ModuleType::ControlSystem) + continue; + std::string dotNode = + detail::encodeDotNodeName(submodule->getQualifiedName()); + _stream << " " << myDotNode << " -> " << dotNode << std::endl; + submodule->accept(*this); + } } +} // namespace ChimeraTK diff --git a/src/ModuleGroup.cc b/src/ModuleGroup.cc index f92fda98..e96eb8ca 100644 --- a/src/ModuleGroup.cc +++ b/src/ModuleGroup.cc @@ -9,15 +9,16 @@ namespace ChimeraTK { - ModuleGroup::ModuleGroup(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy, const std::unordered_set<std::string> &tags) - : ModuleImpl(owner,name,description,eliminateHierarchy,tags) - { - if(!dynamic_cast<Application*>(owner) && !dynamic_cast<ModuleGroup*>(owner)) { - throw ChimeraTK::logic_error("ModuleGroups must be owned either by the Application or other ModuleGroups!"); - } +ModuleGroup::ModuleGroup(EntityOwner *owner, const std::string &name, + const std::string &description, + bool eliminateHierarchy, + const std::unordered_set<std::string> &tags) + : ModuleImpl(owner, name, description, eliminateHierarchy, tags) { + if (!dynamic_cast<Application *>(owner) && + !dynamic_cast<ModuleGroup *>(owner)) { + throw ChimeraTK::logic_error("ModuleGroups must be owned either by the " + "Application or other ModuleGroups!"); } +} } /* namespace ChimeraTK */ - - diff --git a/src/ModuleImpl.cc b/src/ModuleImpl.cc index 3f946ff5..075a2c20 100644 --- a/src/ModuleImpl.cc +++ b/src/ModuleImpl.cc @@ -4,30 +4,32 @@ namespace ChimeraTK { /*********************************************************************************************************************/ - VariableNetworkNode ModuleImpl::operator()(const std::string& variableName) const { - return virtualise()(variableName); - } +VariableNetworkNode ModuleImpl:: +operator()(const std::string &variableName) const { + return virtualise()(variableName); +} /*********************************************************************************************************************/ - Module& ModuleImpl::operator[](const std::string& moduleName) const { - return virtualise()[moduleName]; - } +Module &ModuleImpl::operator[](const std::string &moduleName) const { + return virtualise()[moduleName]; +} /*********************************************************************************************************************/ - void ModuleImpl::connectTo(const Module &target, VariableNetworkNode trigger) const { - virtualise().connectTo(target.virtualise(), trigger); - } +void ModuleImpl::connectTo(const Module &target, + VariableNetworkNode trigger) const { + virtualise().connectTo(target.virtualise(), trigger); +} /*********************************************************************************************************************/ - const Module& ModuleImpl::virtualise() const { - if(!virtualisedModule_isValid) { - virtualisedModule = findTag(".*"); - virtualisedModule_isValid = true; - } - return virtualisedModule; +const Module &ModuleImpl::virtualise() const { + if (!virtualisedModule_isValid) { + virtualisedModule = findTag(".*"); + virtualisedModule_isValid = true; } + return virtualisedModule; +} } // namespace ChimeraTK diff --git a/src/Profiler.cc b/src/Profiler.cc index 58088d7d..d48259d3 100644 --- a/src/Profiler.cc +++ b/src/Profiler.cc @@ -2,8 +2,8 @@ namespace ChimeraTK { - std::list<Profiler::ThreadData*> Profiler::threadDataList; +std::list<Profiler::ThreadData *> Profiler::threadDataList; - std::mutex Profiler::threadDataList_mutex; +std::mutex Profiler::threadDataList_mutex; } /* namespace ChimeraTK */ diff --git a/src/VariableGroup.cc b/src/VariableGroup.cc index 9c0f2755..606038d7 100644 --- a/src/VariableGroup.cc +++ b/src/VariableGroup.cc @@ -9,15 +9,19 @@ namespace ChimeraTK { - VariableGroup::VariableGroup(EntityOwner *owner, const std::string &name, const std::string &description, - bool eliminateHierarchy, const std::unordered_set<std::string> &tags) - : ModuleImpl(owner,name,description,eliminateHierarchy,tags) - { - if(!dynamic_cast<ApplicationModule*>(owner) && !dynamic_cast<DeviceModule*>(owner) && !dynamic_cast<VariableGroup*>(owner)) { - std::cout<<"VariableGroup is throwing"<<std::endl; - throw ChimeraTK::logic_error("VariableGroups must be owned by ApplicationModule, DeviceModule or other VariableGroups!"); - } +VariableGroup::VariableGroup(EntityOwner *owner, const std::string &name, + const std::string &description, + bool eliminateHierarchy, + const std::unordered_set<std::string> &tags) + : ModuleImpl(owner, name, description, eliminateHierarchy, tags) { + if (!dynamic_cast<ApplicationModule *>(owner) && + !dynamic_cast<DeviceModule *>(owner) && + !dynamic_cast<VariableGroup *>(owner)) { + std::cout << "VariableGroup is throwing" << std::endl; + throw ChimeraTK::logic_error( + "VariableGroups must be owned by ApplicationModule, DeviceModule or " + "other VariableGroups!"); } +} } /* namespace ChimeraTK */ - diff --git a/src/VariableNetwork.cc b/src/VariableNetwork.cc index f5d9d59d..6a725e8d 100644 --- a/src/VariableNetwork.cc +++ b/src/VariableNetwork.cc @@ -7,327 +7,367 @@ #include <sstream> -#include "VariableNetwork.h" #include "Application.h" +#include "VariableNetwork.h" #include "VariableNetworkDumpingVisitor.h" namespace ChimeraTK { - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - bool VariableNetwork::hasFeedingNode() const { - auto n = std::count_if( nodeList.begin(), nodeList.end(), - [](const VariableNetworkNode node) { - return node.getDirection().dir == VariableDirection::feeding; - } ); - assert(n < 2); - return n == 1; - } +bool VariableNetwork::hasFeedingNode() const { + auto n = std::count_if( + nodeList.begin(), nodeList.end(), [](const VariableNetworkNode node) { + return node.getDirection().dir == VariableDirection::feeding; + }); + assert(n < 2); + return n == 1; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - size_t VariableNetwork::countConsumingNodes() const { - return nodeList.size() - (hasFeedingNode() ? 1 : 0); - } +size_t VariableNetwork::countConsumingNodes() const { + return nodeList.size() - (hasFeedingNode() ? 1 : 0); +} + +/*********************************************************************************************************************/ - /*********************************************************************************************************************/ - - void VariableNetwork::addNode(VariableNetworkNode &a) { - assert(!a.hasOwner()); - - // change owner of the node: erase from Application's unconnectedNodeList and set this as owner - a.setOwner(this); - - // if node is feeding, save as feeder for this network - if(a.getDirection().dir == VariableDirection::feeding) { - // make sure we only have one feeding node per network - if(hasFeedingNode()) { - // check if current feeding node is a control system variable: if yes, switch it to consuming - if(getFeedingNode().getType() == NodeType::ControlSystem) { - getFeedingNode().setDirection({VariableDirection::consuming, false}); - } - // Current feeder cannot be switch to consumer: throw exception - else{ - std::stringstream msg; - msg << "Trying to add a feeding accessor to a network already having a feeding accessor." << std::endl; - msg << "The network you were trying to add the new accessor to:" << std::endl; - dump("", msg); - msg << "The node you were trying to add:" << std::endl; - a.dump(msg); - throw ChimeraTK::logic_error(msg.str()); - } +void VariableNetwork::addNode(VariableNetworkNode &a) { + assert(!a.hasOwner()); + + // change owner of the node: erase from Application's unconnectedNodeList and + // set this as owner + a.setOwner(this); + + // if node is feeding, save as feeder for this network + if (a.getDirection().dir == VariableDirection::feeding) { + // make sure we only have one feeding node per network + if (hasFeedingNode()) { + // check if current feeding node is a control system variable: if yes, + // switch it to consuming + if (getFeedingNode().getType() == NodeType::ControlSystem) { + getFeedingNode().setDirection({VariableDirection::consuming, false}); + } + // Current feeder cannot be switch to consumer: throw exception + else { + std::stringstream msg; + msg << "Trying to add a feeding accessor to a network already having a " + "feeding accessor." + << std::endl; + msg << "The network you were trying to add the new accessor to:" + << std::endl; + dump("", msg); + msg << "The node you were trying to add:" << std::endl; + a.dump(msg); + throw ChimeraTK::logic_error(msg.str()); } - // force value type, engineering unit and description of the network if set in this feeding node - if(a.getValueType() != typeid(AnyType)) valueType = &(a.getValueType()); - if(a.getUnit() != ChimeraTK::TransferElement::unitNotSet) engineeringUnit = a.getUnit(); - if(a.getDescription() != "") description = a.getDescription(); - } - else { - // update value type and engineering unit, if not yet set - if(valueType == &typeid(AnyType)) valueType = &(a.getValueType()); - if(engineeringUnit == ChimeraTK::TransferElement::unitNotSet) engineeringUnit = a.getUnit(); - if(description == "") description = a.getDescription(); } - // add node to node list - nodeList.push_back(a); + // force value type, engineering unit and description of the network if set + // in this feeding node + if (a.getValueType() != typeid(AnyType)) + valueType = &(a.getValueType()); + if (a.getUnit() != ChimeraTK::TransferElement::unitNotSet) + engineeringUnit = a.getUnit(); + if (a.getDescription() != "") + description = a.getDescription(); + } else { + // update value type and engineering unit, if not yet set + if (valueType == &typeid(AnyType)) + valueType = &(a.getValueType()); + if (engineeringUnit == ChimeraTK::TransferElement::unitNotSet) + engineeringUnit = a.getUnit(); + if (description == "") + description = a.getDescription(); } + // add node to node list + nodeList.push_back(a); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetwork::removeNode(VariableNetworkNode &a) { - auto nNodes = nodeList.size(); - nodeList.remove(a); - a.clearOwner(); - (void)nNodes; - assert(nodeList.size() != nNodes); - } +void VariableNetwork::removeNode(VariableNetworkNode &a) { + auto nNodes = nodeList.size(); + nodeList.remove(a); + a.clearOwner(); + (void)nNodes; + assert(nodeList.size() != nNodes); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetwork::removeNodeToTrigger(const VariableNetworkNode &nodeToNoLongerTrigger) { - for(auto &node : nodeList) { - if(node.getType() != NodeType::TriggerReceiver) continue; - if(node.getNodeToTrigger() == nodeToNoLongerTrigger) { - removeNode(node); - break; - } +void VariableNetwork::removeNodeToTrigger( + const VariableNetworkNode &nodeToNoLongerTrigger) { + for (auto &node : nodeList) { + if (node.getType() != NodeType::TriggerReceiver) + continue; + if (node.getNodeToTrigger() == nodeToNoLongerTrigger) { + removeNode(node); + break; } } +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetwork::dump(const std::string& linePrefix, std::ostream& stream) const { - VariableNetworkDumpingVisitor visitor{linePrefix, stream}; - accept(visitor); - } +void VariableNetwork::dump(const std::string &linePrefix, + std::ostream &stream) const { + VariableNetworkDumpingVisitor visitor{linePrefix, stream}; + accept(visitor); +} - void VariableNetwork::accept(Visitor<VariableNetwork> &visitor) const { - visitor.dispatch(*this); - } +void VariableNetwork::accept(Visitor<VariableNetwork> &visitor) const { + visitor.dispatch(*this); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetwork::addNodeToTrigger(VariableNetworkNode& nodeToTrigger) { - VariableNetworkNode node(nodeToTrigger, 0); - node.setOwner(this); - nodeList.push_back(node); - } +void VariableNetwork::addNodeToTrigger(VariableNetworkNode &nodeToTrigger) { + VariableNetworkNode node(nodeToTrigger, 0); + node.setOwner(this); + nodeList.push_back(node); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableNetwork::TriggerType VariableNetwork::getTriggerType(bool verboseExceptions) const { - if(!hasFeedingNode()) return TriggerType::none; - const auto &feeder = getFeedingNode(); - // network has an external trigger - if(feeder.hasExternalTrigger()) { - if(feeder.getMode() == UpdateMode::push) { - std::stringstream msg; - msg << "Providing an external trigger to a variable network which is fed by a pushing variable is not allowed." << std::endl; - if(verboseExceptions) { - msg << "The illegal network:" << std::endl; - dump("", msg); - } - throw ChimeraTK::logic_error(msg.str()); - } - return TriggerType::external; - } - // network is fed by a pushing node - if(feeder.getMode() == UpdateMode::push) { - return TriggerType::feeder; - } - // network is fed by a poll-type node: must have exactly one polling consumer - size_t nPollingConsumers = count_if( nodeList.begin(), nodeList.end(), - [](const VariableNetworkNode &n) { - return n.getDirection().dir == VariableDirection::consuming && n.getMode() == UpdateMode::poll; - } ); - if(nPollingConsumers != 1) { +VariableNetwork::TriggerType +VariableNetwork::getTriggerType(bool verboseExceptions) const { + if (!hasFeedingNode()) + return TriggerType::none; + const auto &feeder = getFeedingNode(); + // network has an external trigger + if (feeder.hasExternalTrigger()) { + if (feeder.getMode() == UpdateMode::push) { std::stringstream msg; - msg << "In a network with a poll-type feeder and no external trigger, there must be exactly one polling consumer. Maybe you forgot to add a trigger?" << std::endl; - if(verboseExceptions) { + msg << "Providing an external trigger to a variable network which is fed " + "by a pushing variable is not allowed." + << std::endl; + if (verboseExceptions) { msg << "The illegal network:" << std::endl; dump("", msg); } throw ChimeraTK::logic_error(msg.str()); } - return TriggerType::pollingConsumer; + return TriggerType::external; } - - /*********************************************************************************************************************/ - - void VariableNetwork::check() const { - // must have consuming nodes - if(countConsumingNodes() == 0) { - std::stringstream msg; - msg << "No consuming nodes connected to this network!" << std::endl; + // network is fed by a pushing node + if (feeder.getMode() == UpdateMode::push) { + return TriggerType::feeder; + } + // network is fed by a poll-type node: must have exactly one polling consumer + size_t nPollingConsumers = count_if( + nodeList.begin(), nodeList.end(), [](const VariableNetworkNode &n) { + return n.getDirection().dir == VariableDirection::consuming && + n.getMode() == UpdateMode::poll; + }); + if (nPollingConsumers != 1) { + std::stringstream msg; + msg << "In a network with a poll-type feeder and no external trigger, " + "there must be exactly one polling consumer. Maybe you forgot to " + "add a trigger?" + << std::endl; + if (verboseExceptions) { msg << "The illegal network:" << std::endl; dump("", msg); - throw ChimeraTK::logic_error(msg.str()); } + throw ChimeraTK::logic_error(msg.str()); + } + return TriggerType::pollingConsumer; +} - // must have a feeding node - if(!hasFeedingNode()) { - std::stringstream msg; - msg << "No feeding node connected to this network!" << std::endl; - msg << "The illegal network:" << std::endl; - dump("", msg); - throw ChimeraTK::logic_error(msg.str()); - } +/*********************************************************************************************************************/ - // the network's value type must be correctly set - if(*valueType == typeid(AnyType)) { - std::stringstream msg; - msg << "No data type specified for any of the nodes in this network!" << std::endl; - msg << "The illegal network:" << std::endl; - dump("", msg); - throw ChimeraTK::logic_error(msg.str()); - } +void VariableNetwork::check() const { + // must have consuming nodes + if (countConsumingNodes() == 0) { + std::stringstream msg; + msg << "No consuming nodes connected to this network!" << std::endl; + msg << "The illegal network:" << std::endl; + dump("", msg); + throw ChimeraTK::logic_error(msg.str()); + } - // the feeder node must have a non-zero length - size_t length = getFeedingNode().getNumberOfElements(); - if(length == 0) { - std::stringstream msg; - msg << "The feeding node has zero (or undefined) length!" << std::endl; - msg << "The illegal network:" << std::endl; - dump("", msg); - throw ChimeraTK::logic_error(msg.str()); - } + // must have a feeding node + if (!hasFeedingNode()) { + std::stringstream msg; + msg << "No feeding node connected to this network!" << std::endl; + msg << "The illegal network:" << std::endl; + dump("", msg); + throw ChimeraTK::logic_error(msg.str()); + } - // all consumers must have the same length as the feeder or a zero length for trigger receivers - for(auto &node : nodeList) { - if(node.getType() != NodeType::TriggerReceiver) { - if(node.getNumberOfElements() != length) { - std::stringstream msg; - msg << "The network contains a node with a different length than the feeding node!" << std::endl; - msg << "The illegal network:" << std::endl; - dump("", msg); - throw ChimeraTK::logic_error(msg.str()); - } - } - else { - if(node.getNumberOfElements() != 0) { - std::stringstream msg; - msg << "The network contains a trigger receiver node with a non-zero length!" << std::endl; - msg << "The illegal network:" << std::endl; - dump("", msg); - throw ChimeraTK::logic_error(msg.str()); - } - } - } + // the network's value type must be correctly set + if (*valueType == typeid(AnyType)) { + std::stringstream msg; + msg << "No data type specified for any of the nodes in this network!" + << std::endl; + msg << "The illegal network:" << std::endl; + dump("", msg); + throw ChimeraTK::logic_error(msg.str()); + } + + // the feeder node must have a non-zero length + size_t length = getFeedingNode().getNumberOfElements(); + if (length == 0) { + std::stringstream msg; + msg << "The feeding node has zero (or undefined) length!" << std::endl; + msg << "The illegal network:" << std::endl; + dump("", msg); + throw ChimeraTK::logic_error(msg.str()); + } - // all nodes must have this network as the owner and a value type equal the network's value type - for(auto &node : nodeList) { - assert(&(node.getOwner()) == this); - if(node.getValueType() == typeid(AnyType)) node.setValueType(*valueType); - if(node.getValueType() != *valueType) { + // all consumers must have the same length as the feeder or a zero length for + // trigger receivers + for (auto &node : nodeList) { + if (node.getType() != NodeType::TriggerReceiver) { + if (node.getNumberOfElements() != length) { std::stringstream msg; - msg << "The network contains variables of different value types, which is not supported!" << std::endl; + msg << "The network contains a node with a different length than the " + "feeding node!" + << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } - } - - // if the feeder is an application node, it must be in push mode - if(getFeedingNode().getType() == NodeType::Application) { - assert(getFeedingNode().getMode() == UpdateMode::push); - } - - // check if trigger is correctly defined (the return type doesn't matter, only the checks done in the function are needed) - getTriggerType(); - - // if (and only if) feeder has return channel, there must be exactly one consumer with return channel - if(getFeedingNode().getDirection().withReturn) { - size_t numberOfConsumersWithReturnChannel = 0; - for(auto &node : getConsumingNodes()) { - if(node.getDirection().withReturn) ++numberOfConsumersWithReturnChannel; - } - if(numberOfConsumersWithReturnChannel != 1) { + } else { + if (node.getNumberOfElements() != 0) { std::stringstream msg; - msg << "The network has a feeder with return channel but " << numberOfConsumersWithReturnChannel << " consumers" - << " with return channel (must have exactly 1)!" << std::endl; + msg << "The network contains a trigger receiver node with a non-zero " + "length!" + << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } } - else { - for(auto &node : getConsumingNodes()) { - if(node.getDirection().withReturn) { - std::stringstream msg; - msg << "The network has no feeder with return channel but at least one consumer with return channel!" << std::endl; - msg << "The illegal network:" << std::endl; - dump("", msg); - throw ChimeraTK::logic_error(msg.str()); - } - } + } + // all nodes must have this network as the owner and a value type equal the + // network's value type + for (auto &node : nodeList) { + assert(&(node.getOwner()) == this); + if (node.getValueType() == typeid(AnyType)) + node.setValueType(*valueType); + if (node.getValueType() != *valueType) { + std::stringstream msg; + msg << "The network contains variables of different value types, which " + "is not supported!" + << std::endl; + msg << "The illegal network:" << std::endl; + dump("", msg); + throw ChimeraTK::logic_error(msg.str()); } } - /*********************************************************************************************************************/ + // if the feeder is an application node, it must be in push mode + if (getFeedingNode().getType() == NodeType::Application) { + assert(getFeedingNode().getMode() == UpdateMode::push); + } - VariableNetworkNode VariableNetwork::getFeedingNode() const { - auto iter = std::find_if( nodeList.begin(), nodeList.end(), - [](const VariableNetworkNode n) { - return n.getDirection().dir == VariableDirection::feeding; - } ); - if(iter == nodeList.end()) { + // check if trigger is correctly defined (the return type doesn't matter, only + // the checks done in the function are needed) + getTriggerType(); + + // if (and only if) feeder has return channel, there must be exactly one + // consumer with return channel + if (getFeedingNode().getDirection().withReturn) { + size_t numberOfConsumersWithReturnChannel = 0; + for (auto &node : getConsumingNodes()) { + if (node.getDirection().withReturn) + ++numberOfConsumersWithReturnChannel; + } + if (numberOfConsumersWithReturnChannel != 1) { std::stringstream msg; - msg << "No feeding node in this network!" << std::endl; + msg << "The network has a feeder with return channel but " + << numberOfConsumersWithReturnChannel << " consumers" + << " with return channel (must have exactly 1)!" << std::endl; msg << "The illegal network:" << std::endl; - if(!hasFeedingNode()) dump("", msg); + dump("", msg); throw ChimeraTK::logic_error(msg.str()); } - return *iter; + } else { + for (auto &node : getConsumingNodes()) { + if (node.getDirection().withReturn) { + std::stringstream msg; + msg << "The network has no feeder with return channel but at least one " + "consumer with return channel!" + << std::endl; + msg << "The illegal network:" << std::endl; + dump("", msg); + throw ChimeraTK::logic_error(msg.str()); + } + } } +} - /*********************************************************************************************************************/ - - std::list<VariableNetworkNode> VariableNetwork::getConsumingNodes() const{ - std::list<VariableNetworkNode> consumers; - for(auto &n : nodeList) if(n.getDirection().dir == VariableDirection::consuming) consumers.push_back(n); - return consumers; +/*********************************************************************************************************************/ + +VariableNetworkNode VariableNetwork::getFeedingNode() const { + auto iter = std::find_if( + nodeList.begin(), nodeList.end(), [](const VariableNetworkNode n) { + return n.getDirection().dir == VariableDirection::feeding; + }); + if (iter == nodeList.end()) { + std::stringstream msg; + msg << "No feeding node in this network!" << std::endl; + msg << "The illegal network:" << std::endl; + if (!hasFeedingNode()) + dump("", msg); + throw ChimeraTK::logic_error(msg.str()); } + return *iter; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - bool VariableNetwork::hasApplicationConsumer() const { - for(auto &n : nodeList) { - if(n.getDirection().dir == VariableDirection::consuming && n.getType() == NodeType::Application) return true; - } - return false; - } +std::list<VariableNetworkNode> VariableNetwork::getConsumingNodes() const { + std::list<VariableNetworkNode> consumers; + for (auto &n : nodeList) + if (n.getDirection().dir == VariableDirection::consuming) + consumers.push_back(n); + return consumers; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - bool VariableNetwork::merge(VariableNetwork &other) { +bool VariableNetwork::hasApplicationConsumer() const { + for (auto &n : nodeList) { + if (n.getDirection().dir == VariableDirection::consuming && + n.getType() == NodeType::Application) + return true; + } + return false; +} - // check if merging is possible - if(hasFeedingNode() || !other.hasFeedingNode()) { - if( (getFeedingNode().getType() == NodeType::ControlSystem && - other.getFeedingNode().getType() == NodeType::ControlSystem) - || (getFeedingNode().getType() != NodeType::ControlSystem && - other.getFeedingNode().getType() != NodeType::ControlSystem) ) { - return false; - } - } +/*********************************************************************************************************************/ - // put all consuming nodes of B's owner into A's owner - for(auto node : other.getConsumingNodes()) { - other.removeNode(node); - addNode(node); - } +bool VariableNetwork::merge(VariableNetwork &other) { - // feeding node: if control system type, convert into consumer. Otherwise just add it, addNode() will convert the - // other feeding node if necessary - if(other.hasFeedingNode()) { - VariableNetworkNode otherFeeder = other.getFeedingNode(); - other.removeNode(otherFeeder); - if(otherFeeder.getType() == NodeType::ControlSystem) otherFeeder.setDirection({VariableDirection::consuming, false}); - addNode(otherFeeder); + // check if merging is possible + if (hasFeedingNode() || !other.hasFeedingNode()) { + if ((getFeedingNode().getType() == NodeType::ControlSystem && + other.getFeedingNode().getType() == NodeType::ControlSystem) || + (getFeedingNode().getType() != NodeType::ControlSystem && + other.getFeedingNode().getType() != NodeType::ControlSystem)) { + return false; } + } - return true; + // put all consuming nodes of B's owner into A's owner + for (auto node : other.getConsumingNodes()) { + other.removeNode(node); + addNode(node); + } + // feeding node: if control system type, convert into consumer. Otherwise just + // add it, addNode() will convert the other feeding node if necessary + if (other.hasFeedingNode()) { + VariableNetworkNode otherFeeder = other.getFeedingNode(); + other.removeNode(otherFeeder); + if (otherFeeder.getType() == NodeType::ControlSystem) + otherFeeder.setDirection({VariableDirection::consuming, false}); + addNode(otherFeeder); } + + return true; } +} // namespace ChimeraTK diff --git a/src/VariableNetworkDumpingVisitor.cc b/src/VariableNetworkDumpingVisitor.cc index 95b0d9e1..ff873237 100644 --- a/src/VariableNetworkDumpingVisitor.cc +++ b/src/VariableNetworkDumpingVisitor.cc @@ -1,51 +1,56 @@ -#include "VariableNetwork.h" #include "VariableNetworkDumpingVisitor.h" +#include "VariableNetwork.h" namespace ChimeraTK { -VariableNetworkDumpingVisitor::VariableNetworkDumpingVisitor(const std::string& prefix, std::ostream &stream) - : Visitor<ChimeraTK::VariableNetwork>() - , VariableNetworkNodeDumpingVisitor(stream, " ") - , _prefix(prefix) {} +VariableNetworkDumpingVisitor::VariableNetworkDumpingVisitor( + const std::string &prefix, std::ostream &stream) + : Visitor<ChimeraTK::VariableNetwork>(), + VariableNetworkNodeDumpingVisitor(stream, " "), _prefix(prefix) {} -void VariableNetworkDumpingVisitor::dispatch(const VariableNetwork& t) { - stream() << _prefix << "VariableNetwork"; - stream() << " {" << std::endl; - stream() << _prefix << " value type = " << boost::core::demangle(t.getValueType().name()) << ", engineering unit = " << t.getUnit() << std::endl; - stream() << _prefix << " trigger type = "; - try { - auto tt = t.getTriggerType(false); - if(tt == VariableNetwork::TriggerType::feeder) stream() << "feeder"; - if(tt == VariableNetwork::TriggerType::pollingConsumer) stream() << "pollingConsumer"; - if(tt == VariableNetwork::TriggerType::external) stream() << "external"; - if(tt == VariableNetwork::TriggerType::none) stream() << "none"; - stream() << std::endl; - } - catch(ChimeraTK::logic_error &e) { - stream() << "**error**" << std::endl; - } - stream() << _prefix << " feeder"; - if(t.hasFeedingNode()) { - t.getFeedingNode().accept(*this); +void VariableNetworkDumpingVisitor::dispatch(const VariableNetwork &t) { + stream() << _prefix << "VariableNetwork"; + stream() << " {" << std::endl; + stream() << _prefix << " value type = " + << boost::core::demangle(t.getValueType().name()) + << ", engineering unit = " << t.getUnit() << std::endl; + stream() << _prefix << " trigger type = "; + try { + auto tt = t.getTriggerType(false); + if (tt == VariableNetwork::TriggerType::feeder) + stream() << "feeder"; + if (tt == VariableNetwork::TriggerType::pollingConsumer) + stream() << "pollingConsumer"; + if (tt == VariableNetwork::TriggerType::external) + stream() << "external"; + if (tt == VariableNetwork::TriggerType::none) + stream() << "none"; + stream() << std::endl; + } catch (ChimeraTK::logic_error &e) { + stream() << "**error**" << std::endl; + } + stream() << _prefix << " feeder"; + if (t.hasFeedingNode()) { + t.getFeedingNode().accept(*this); + } else { + stream() << " **error, no feeder found**" << std::endl; + } + stream() << _prefix << " consumers: " << t.countConsumingNodes() + << std::endl; + size_t count = 0; + for (auto &consumer : t.getConsumingNodes()) { + if (consumer.getDirection().dir != VariableDirection::consuming) + continue; + stream() << _prefix << " # " << ++count << ":"; + consumer.accept(*this); + } + if (t.hasFeedingNode()) { + if (t.getFeedingNode().hasExternalTrigger()) { + stream() << _prefix << " external trigger node: "; + t.getFeedingNode().getExternalTrigger().accept(*this); } - else { - stream() << " **error, no feeder found**" << std::endl; - } - stream() << _prefix << " consumers: " << t.countConsumingNodes() << std::endl; - size_t count = 0; - for(auto &consumer : t.getConsumingNodes()) { - if(consumer.getDirection().dir != VariableDirection::consuming) continue; - stream() << _prefix << " # " << ++count << ":"; - consumer.accept(*this); - } - if(t.hasFeedingNode()) { - if(t.getFeedingNode().hasExternalTrigger()) { - stream() << _prefix << " external trigger node: "; - t.getFeedingNode().getExternalTrigger().accept(*this); - } - } - stream() << _prefix << "}" << std::endl; + } + stream() << _prefix << "}" << std::endl; } - } // namespace ChimeraTK diff --git a/src/VariableNetworkGraphDumpingVisitor.cc b/src/VariableNetworkGraphDumpingVisitor.cc index f1d1ec51..d5ec4c02 100644 --- a/src/VariableNetworkGraphDumpingVisitor.cc +++ b/src/VariableNetworkGraphDumpingVisitor.cc @@ -1,148 +1,168 @@ -#include "Application.h" #include "VariableNetworkGraphDumpingVisitor.h" +#include "Application.h" #include "VariableNetwork.h" #include "VisitorHelper.h" #include <algorithm> -#include <typeinfo> #include <sstream> +#include <typeinfo> namespace ChimeraTK { -void VariableNetworkGraphDumpingVisitor::dispatch(const VariableNetwork& network) { - std::string networkPrefix = "network_" + std::to_string(_networkCount++); - pushPrefix(networkPrefix); - - stream() << " subgraph cluster_" << prefix() << " {\n" - << " fontsize=\"8\";\n" - << " style=\"filled,rounded\";\n" - << " color=black;\n" - << " fillcolor=white;\n" - << " ordering=out;\n" - << " label=\"" << boost::core::demangle(typeid(network).name()) << "(" << &network << ")\\n" - << network.getDescription() << "\\n" - << "value type = " << boost::core::demangle(network.getValueType().name()) << "\\n" - << "engineering unit = " << network.getUnit() << "\";\n"; - - std::string feeder; - std::string trigger; - if (network.hasFeedingNode()) { - auto feederNode = network.getFeedingNode(); - // We are inside a trigger network... Consumers will be skipped below. Make the feeder a "global" variable - if (!network.getConsumingNodes().empty() && network.getConsumingNodes().begin()->getType() == NodeType::TriggerReceiver) { - feeder = detail::encodeDotNodeName(detail::nodeName(feederNode)); - if (_triggerMap.find(feeder) == _triggerMap.end()) { - std::stringstream ss; - pushStream(ss); pushPrefix(""); - feederNode.accept(*this); - _triggerMap[feeder] = ss.str(); - popPrefix(); popStream(); - } - } else { - feeder = prefix() + detail::encodeDotNodeName(detail::nodeName(feederNode)); - feederNode.accept(*this); - } - - if (feederNode.hasExternalTrigger()) { - auto triggerNode = feederNode.getExternalTrigger(); - trigger = detail::encodeDotNodeName(detail::nodeName(triggerNode)); - if (_triggerMap.find(trigger) == _triggerMap.end()) { - std::stringstream ss; - pushStream(ss); pushPrefix(""); - triggerNode.accept(*this); - _triggerMap[trigger] = ss.str(); - popPrefix(); popStream(); - } - } +void VariableNetworkGraphDumpingVisitor::dispatch( + const VariableNetwork &network) { + std::string networkPrefix = "network_" + std::to_string(_networkCount++); + pushPrefix(networkPrefix); + + stream() << " subgraph cluster_" << prefix() << " {\n" + << " fontsize=\"8\";\n" + << " style=\"filled,rounded\";\n" + << " color=black;\n" + << " fillcolor=white;\n" + << " ordering=out;\n" + << " label=\"" << boost::core::demangle(typeid(network).name()) + << "(" << &network << ")\\n" + << network.getDescription() << "\\n" + << "value type = " + << boost::core::demangle(network.getValueType().name()) << "\\n" + << "engineering unit = " << network.getUnit() << "\";\n"; + + std::string feeder; + std::string trigger; + if (network.hasFeedingNode()) { + auto feederNode = network.getFeedingNode(); + // We are inside a trigger network... Consumers will be skipped below. Make + // the feeder a "global" variable + if (!network.getConsumingNodes().empty() && + network.getConsumingNodes().begin()->getType() == + NodeType::TriggerReceiver) { + feeder = detail::encodeDotNodeName(detail::nodeName(feederNode)); + if (_triggerMap.find(feeder) == _triggerMap.end()) { + std::stringstream ss; + pushStream(ss); + pushPrefix(""); + feederNode.accept(*this); + _triggerMap[feeder] = ss.str(); + popPrefix(); + popStream(); + } + } else { + feeder = + prefix() + detail::encodeDotNodeName(detail::nodeName(feederNode)); + feederNode.accept(*this); } - for (auto &consumerNode : network.getConsumingNodes()) { - if (consumerNode.getDirection().dir != VariableDirection::consuming) - continue; - - if (consumerNode.getType() == NodeType::TriggerReceiver) - continue; - - auto consumer = prefix() + detail::encodeDotNodeName(detail::nodeName(consumerNode)); - consumerNode.accept(*this); - std::string helperNode; - - if (!trigger.empty()) { - // Create trigger connection diamond - _triggerCount++; - helperNode = prefix() + "_trigger_helper_" + std::to_string(_triggerCount); - stream() << helperNode << "[label=\"\",shape=diamond,style=\"filled\",color=black,width=.3,height=.3,fixedsize=true,fillcolor=\"#ffcc00\"]\n"; - } - - stream() << feeder << " -> "; - if (!helperNode.empty()) { - stream() << helperNode << " -> " << consumer << "\n"; - // Hack: Make trigger lower in rank than all entry points to subgraphs - _triggerConnections.push_back(trigger + " -> " + feeder + "[style = invis]\n"); - - _triggerConnections.push_back(trigger + " -> " + helperNode + "[style = dashed, color=grey,tailport=s]\n"); - } else { - stream() << consumer << "\n"; - } + if (feederNode.hasExternalTrigger()) { + auto triggerNode = feederNode.getExternalTrigger(); + trigger = detail::encodeDotNodeName(detail::nodeName(triggerNode)); + if (_triggerMap.find(trigger) == _triggerMap.end()) { + std::stringstream ss; + pushStream(ss); + pushPrefix(""); + triggerNode.accept(*this); + _triggerMap[trigger] = ss.str(); + popPrefix(); + popStream(); + } } - - stream() << "}\n"; - popPrefix(); -} - -VariableNetworkGraphDumpingVisitor::VariableNetworkGraphDumpingVisitor(std::ostream& stream) - : Visitor<Application, VariableNetwork> () - , VariableNetworkNodeDumpingVisitor(stream, "\\n") - , _triggerMap() - , _triggerConnections() - , _networkCount(0) - , _triggerCount(0) {} - - -void VariableNetworkGraphDumpingVisitor::dispatch(const Application& t) { - stream() << "digraph application {\n" - //<< " rankdir = LR;\n" - << " fontname=\"Sans\";\n" - << " fontsize=\"10\";\n" - << " labelloc=t;\n" - << " nodesep=1;\n" - //<< " splines=ortho;\n" - << " concentrate=true;\n" - << " label=\"<" << boost::core::demangle(typeid(t).name()) << ">" << t.getName() << "\";\n" - << " node [style=\"filled,rounded\", shape=box, fontsize=\"9\", fontname=\"sans\"];\n" - << " edge [labelfontsize=\"6\", fontsize=\"9\", fontname=\"monospace\"];\n" - << " \n"; - - for (auto &network : t.networkList) { - network.accept(*this); + } + + for (auto &consumerNode : network.getConsumingNodes()) { + if (consumerNode.getDirection().dir != VariableDirection::consuming) + continue; + + if (consumerNode.getType() == NodeType::TriggerReceiver) + continue; + + auto consumer = + prefix() + detail::encodeDotNodeName(detail::nodeName(consumerNode)); + consumerNode.accept(*this); + std::string helperNode; + + if (!trigger.empty()) { + // Create trigger connection diamond + _triggerCount++; + helperNode = + prefix() + "_trigger_helper_" + std::to_string(_triggerCount); + stream() << helperNode + << "[label=\"\",shape=diamond,style=\"filled\",color=black," + "width=.3,height=.3,fixedsize=true,fillcolor=\"#ffcc00\"]\n"; } - for (auto &t : _triggerMap) { - stream() << t.second; + stream() << feeder << " -> "; + if (!helperNode.empty()) { + stream() << helperNode << " -> " << consumer << "\n"; + // Hack: Make trigger lower in rank than all entry points to subgraphs + _triggerConnections.push_back(trigger + " -> " + feeder + + "[style = invis]\n"); + + _triggerConnections.push_back( + trigger + " -> " + helperNode + + "[style = dashed, color=grey,tailport=s]\n"); + } else { + stream() << consumer << "\n"; } + } - for (auto &t : _triggerConnections) { - stream() << t; - } + stream() << "}\n"; + popPrefix(); +} - stream() << "}\n"; +VariableNetworkGraphDumpingVisitor::VariableNetworkGraphDumpingVisitor( + std::ostream &stream) + : Visitor<Application, VariableNetwork>(), + VariableNetworkNodeDumpingVisitor(stream, "\\n"), _triggerMap(), + _triggerConnections(), _networkCount(0), _triggerCount(0) {} + +void VariableNetworkGraphDumpingVisitor::dispatch(const Application &t) { + stream() << "digraph application {\n" + //<< " rankdir = LR;\n" + << " fontname=\"Sans\";\n" + << " fontsize=\"10\";\n" + << " labelloc=t;\n" + << " nodesep=1;\n" + //<< " splines=ortho;\n" + << " concentrate=true;\n" + << " label=\"<" << boost::core::demangle(typeid(t).name()) << ">" + << t.getName() << "\";\n" + << " node [style=\"filled,rounded\", shape=box, fontsize=\"9\", " + "fontname=\"sans\"];\n" + << " edge [labelfontsize=\"6\", fontsize=\"9\", " + "fontname=\"monospace\"];\n" + << " \n"; + + for (auto &network : t.networkList) { + network.accept(*this); + } + + for (auto &t : _triggerMap) { + stream() << t.second; + } + + for (auto &t : _triggerConnections) { + stream() << t; + } + + stream() << "}\n"; } -void VariableNetworkGraphDumpingVisitor::dispatch(const VariableNetworkNode& t) { - std::string nodeName = prefix() + detail::encodeDotNodeName(detail::nodeName(t)); - stream() << nodeName << "[\n"; - if (t.getMode() == UpdateMode::push) - stream() << " fillcolor=\"#ff9900\";\n"; - else if (t.getMode() == UpdateMode::poll) - stream() << " fillcolor=\"#b4d848\";\n"; - else { - stream() << " fillcolor=\"#ffffff\";\n"; - } +void VariableNetworkGraphDumpingVisitor::dispatch( + const VariableNetworkNode &t) { + std::string nodeName = + prefix() + detail::encodeDotNodeName(detail::nodeName(t)); + stream() << nodeName << "[\n"; + if (t.getMode() == UpdateMode::push) + stream() << " fillcolor=\"#ff9900\";\n"; + else if (t.getMode() == UpdateMode::poll) + stream() << " fillcolor=\"#b4d848\";\n"; + else { + stream() << " fillcolor=\"#ffffff\";\n"; + } - stream() << " label=\"" << detail::nodeName(t) << "\\n"; + stream() << " label=\"" << detail::nodeName(t) << "\\n"; - VariableNetworkNodeDumpingVisitor::dispatch(t); + VariableNetworkNodeDumpingVisitor::dispatch(t); - stream() << "\"]\n"; + stream() << "\"]\n"; } } // namespace ChimeraTK diff --git a/src/VariableNetworkNode.cc b/src/VariableNetworkNode.cc index 510c003b..d399a4f8 100644 --- a/src/VariableNetworkNode.cc +++ b/src/VariableNetworkNode.cc @@ -6,430 +6,441 @@ */ #include "VariableNetworkNode.h" -#include "VariableNetwork.h" #include "Application.h" #include "EntityOwner.h" -#include "Visitor.h" +#include "VariableNetwork.h" #include "VariableNetworkNodeDumpingVisitor.h" +#include "Visitor.h" namespace ChimeraTK { - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableNetworkNode::VariableNetworkNode(const VariableNetworkNode &other) - : pdata(other.pdata) - {} +VariableNetworkNode::VariableNetworkNode(const VariableNetworkNode &other) + : pdata(other.pdata) {} - /*********************************************************************************************************************/ - - VariableNetworkNode& VariableNetworkNode::operator=(const VariableNetworkNode &rightHandSide) { - pdata = rightHandSide.pdata; - return *this; - } +/*********************************************************************************************************************/ +VariableNetworkNode &VariableNetworkNode:: +operator=(const VariableNetworkNode &rightHandSide) { + pdata = rightHandSide.pdata; + return *this; +} - /*********************************************************************************************************************/ - - VariableNetworkNode::VariableNetworkNode(EntityOwner *owner, ChimeraTK::TransferElementAbstractor *accessorBridge, - const std::string &name, - VariableDirection direction, std::string unit, size_t nElements, UpdateMode mode, - const std::string &description, const std::type_info* valueType, - const std::unordered_set<std::string> &tags) - : pdata(boost::make_shared<VariableNetworkNode_data>()) - { - pdata->owningModule = owner; - pdata->type = NodeType::Application; - pdata->appNode = accessorBridge; - pdata->name = name; - pdata->qualifiedName = owner->getQualifiedName() + "/" + name; - pdata->mode = mode; - pdata->direction = direction; - pdata->valueType = valueType; - pdata->unit = unit; - pdata->nElements = nElements; - pdata->description = description; - pdata->tags = tags; - } +/*********************************************************************************************************************/ + +VariableNetworkNode::VariableNetworkNode( + EntityOwner *owner, ChimeraTK::TransferElementAbstractor *accessorBridge, + const std::string &name, VariableDirection direction, std::string unit, + size_t nElements, UpdateMode mode, const std::string &description, + const std::type_info *valueType, + const std::unordered_set<std::string> &tags) + : pdata(boost::make_shared<VariableNetworkNode_data>()) { + pdata->owningModule = owner; + pdata->type = NodeType::Application; + pdata->appNode = accessorBridge; + pdata->name = name; + pdata->qualifiedName = owner->getQualifiedName() + "/" + name; + pdata->mode = mode; + pdata->direction = direction; + pdata->valueType = valueType; + pdata->unit = unit; + pdata->nElements = nElements; + pdata->description = description; + pdata->tags = tags; +} - /*********************************************************************************************************************/ - - VariableNetworkNode::VariableNetworkNode(const std::string &name, const std::string &devAlias, - const std::string ®Name, UpdateMode mode, VariableDirection dir, const std::type_info &valTyp, size_t nElements) - : pdata(boost::make_shared<VariableNetworkNode_data>()) - { - pdata->name = name; - pdata->type = NodeType::Device; - pdata->mode = mode; - pdata->direction = dir; - pdata->valueType = &valTyp; - pdata->deviceAlias = devAlias; - pdata->registerName = regName; - pdata->nElements = nElements; - } +/*********************************************************************************************************************/ + +VariableNetworkNode::VariableNetworkNode(const std::string &name, + const std::string &devAlias, + const std::string ®Name, + UpdateMode mode, VariableDirection dir, + const std::type_info &valTyp, + size_t nElements) + : pdata(boost::make_shared<VariableNetworkNode_data>()) { + pdata->name = name; + pdata->type = NodeType::Device; + pdata->mode = mode; + pdata->direction = dir; + pdata->valueType = &valTyp; + pdata->deviceAlias = devAlias; + pdata->registerName = regName; + pdata->nElements = nElements; +} - /*********************************************************************************************************************/ - - VariableNetworkNode::VariableNetworkNode(std::string pubName, VariableDirection dir, const std::type_info &valTyp, - size_t nElements) - : pdata(boost::make_shared<VariableNetworkNode_data>()) - { - pdata->name = pubName; - pdata->type = NodeType::ControlSystem; - pdata->mode = UpdateMode::push; - pdata->direction = dir; - pdata->valueType = &valTyp; - pdata->publicName = pubName; - pdata->nElements = nElements; - } +/*********************************************************************************************************************/ + +VariableNetworkNode::VariableNetworkNode(std::string pubName, + VariableDirection dir, + const std::type_info &valTyp, + size_t nElements) + : pdata(boost::make_shared<VariableNetworkNode_data>()) { + pdata->name = pubName; + pdata->type = NodeType::ControlSystem; + pdata->mode = UpdateMode::push; + pdata->direction = dir; + pdata->valueType = &valTyp; + pdata->publicName = pubName; + pdata->nElements = nElements; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableNetworkNode::VariableNetworkNode(VariableNetworkNode& nodeToTrigger, int) - : pdata(boost::make_shared<VariableNetworkNode_data>()) - { - pdata->type = NodeType::TriggerReceiver; - pdata->direction = {VariableDirection::consuming, false}; - pdata->nodeToTrigger = nodeToTrigger; - pdata->name = "trigger:"+nodeToTrigger.getName(); - } +VariableNetworkNode::VariableNetworkNode(VariableNetworkNode &nodeToTrigger, + int) + : pdata(boost::make_shared<VariableNetworkNode_data>()) { + pdata->type = NodeType::TriggerReceiver; + pdata->direction = {VariableDirection::consuming, false}; + pdata->nodeToTrigger = nodeToTrigger; + pdata->name = "trigger:" + nodeToTrigger.getName(); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableNetworkNode::VariableNetworkNode(boost::shared_ptr<VariableNetworkNode_data> _pdata) - : pdata(_pdata) - {} +VariableNetworkNode::VariableNetworkNode( + boost::shared_ptr<VariableNetworkNode_data> _pdata) + : pdata(_pdata) {} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableNetworkNode::VariableNetworkNode() - : pdata(boost::make_shared<VariableNetworkNode_data>()) - {} +VariableNetworkNode::VariableNetworkNode() + : pdata(boost::make_shared<VariableNetworkNode_data>()) {} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::setOwner(VariableNetwork *net) { - assert(pdata->network == nullptr); - assert(pdata->type != NodeType::invalid); - pdata->network = net; - } +void VariableNetworkNode::setOwner(VariableNetwork *net) { + assert(pdata->network == nullptr); + assert(pdata->type != NodeType::invalid); + pdata->network = net; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::clearOwner() { - pdata->network = nullptr; - } +void VariableNetworkNode::clearOwner() { pdata->network = nullptr; } - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - bool VariableNetworkNode::hasImplementation() const { - return pdata->type == NodeType::Device || pdata->type == NodeType::ControlSystem || pdata->type == NodeType::Constant; - } +bool VariableNetworkNode::hasImplementation() const { + return pdata->type == NodeType::Device || + pdata->type == NodeType::ControlSystem || + pdata->type == NodeType::Constant; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::accept( Visitor<VariableNetworkNode>& visitor) const { - visitor.dispatch(*this); - } +void VariableNetworkNode::accept(Visitor<VariableNetworkNode> &visitor) const { + visitor.dispatch(*this); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - bool VariableNetworkNode::operator==(const VariableNetworkNode& other) const { - return (other.pdata == pdata) || (pdata->type == NodeType::invalid && other.pdata->type == NodeType::invalid); - } +bool VariableNetworkNode::operator==(const VariableNetworkNode &other) const { + return (other.pdata == pdata) || (pdata->type == NodeType::invalid && + other.pdata->type == NodeType::invalid); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - bool VariableNetworkNode::operator!=(const VariableNetworkNode& other) const { - return !operator==(other); - } +bool VariableNetworkNode::operator!=(const VariableNetworkNode &other) const { + return !operator==(other); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - bool VariableNetworkNode::operator<(const VariableNetworkNode& other) const { - if(pdata->type == NodeType::invalid && other.pdata->type == NodeType::invalid) return false; - return (other.pdata < pdata); - } - - /*********************************************************************************************************************/ +bool VariableNetworkNode::operator<(const VariableNetworkNode &other) const { + if (pdata->type == NodeType::invalid && + other.pdata->type == NodeType::invalid) + return false; + return (other.pdata < pdata); +} - VariableNetworkNode VariableNetworkNode::operator>>(VariableNetworkNode other) { - if(pdata->direction.dir == VariableDirection::invalid) { - if(!other.hasOwner()) { - pdata->direction = {VariableDirection::feeding, false}; - } - else { - if(other.getOwner().hasFeedingNode()) { - pdata->direction = {VariableDirection::consuming, false}; - if(getType() == NodeType::Device) { // special treatment for Device-type variables: consumers are push-type - pdata->mode = UpdateMode::push; - } - } - else { - pdata->direction = {VariableDirection::feeding, false}; +/*********************************************************************************************************************/ + +VariableNetworkNode VariableNetworkNode::operator>>(VariableNetworkNode other) { + if (pdata->direction.dir == VariableDirection::invalid) { + if (!other.hasOwner()) { + pdata->direction = {VariableDirection::feeding, false}; + } else { + if (other.getOwner().hasFeedingNode()) { + pdata->direction = {VariableDirection::consuming, false}; + if (getType() == + NodeType::Device) { // special treatment for Device-type variables: + // consumers are push-type + pdata->mode = UpdateMode::push; } + } else { + pdata->direction = {VariableDirection::feeding, false}; } } - if(other.pdata->direction.dir == VariableDirection::invalid) { - if(!hasOwner()) { + } + if (other.pdata->direction.dir == VariableDirection::invalid) { + if (!hasOwner()) { + other.pdata->direction = {VariableDirection::consuming, false}; + if (other.getType() == + NodeType::Device) { // special treatment for Device-type variables: + // consumers are push-type + other.pdata->mode = UpdateMode::push; + } + } else { + if (getOwner().hasFeedingNode()) { other.pdata->direction = {VariableDirection::consuming, false}; - if(other.getType() == NodeType::Device) { // special treatment for Device-type variables: consumers are push-type + if (other.getType() == + NodeType::Device) { // special treatment for Device-type variables: + // consumers are push-type other.pdata->mode = UpdateMode::push; } - } - else { - if(getOwner().hasFeedingNode()) { - other.pdata->direction = {VariableDirection::consuming, false}; - if(other.getType() == NodeType::Device) { // special treatment for Device-type variables: consumers are push-type - other.pdata->mode = UpdateMode::push; - } - } - else { - other.pdata->direction = {VariableDirection::feeding, false}; - } + } else { + other.pdata->direction = {VariableDirection::feeding, false}; } } - Application::getInstance().connect(*this, other); - return *this; } + Application::getInstance().connect(*this, other); + return *this; +} - /*********************************************************************************************************************/ - - VariableNetworkNode VariableNetworkNode::operator[](VariableNetworkNode trigger) { - - // check if node already has a trigger - if(pdata->externalTrigger.getType() != NodeType::invalid) { - throw ChimeraTK::logic_error("Only one external trigger per variable network is allowed."); - } - - // force direction of the node we are operating on to be feeding - if(pdata->direction.dir == VariableDirection::invalid) pdata->direction = {VariableDirection::feeding, false}; - assert(pdata->direction.dir == VariableDirection::feeding); +/*********************************************************************************************************************/ - // force direction of the triggering node to be feeding - if(trigger.pdata->direction.dir == VariableDirection::invalid) trigger.pdata->direction = {VariableDirection::feeding, false}; - assert(trigger.pdata->direction.dir == VariableDirection::feeding); +VariableNetworkNode VariableNetworkNode:: +operator[](VariableNetworkNode trigger) { - // check if already existing in map - if(pdata->nodeWithTrigger.count(trigger) > 0) { - return pdata->nodeWithTrigger[trigger]; - } - - // create copy of the node - pdata->nodeWithTrigger[trigger].pdata = boost::make_shared<VariableNetworkNode_data>(*pdata); + // check if node already has a trigger + if (pdata->externalTrigger.getType() != NodeType::invalid) { + throw ChimeraTK::logic_error( + "Only one external trigger per variable network is allowed."); + } - // add ourselves as a trigger receiver to the other network - if(!trigger.hasOwner()) { - Application::getInstance().createNetwork().addNode(trigger); - } - trigger.getOwner().addNodeToTrigger(pdata->nodeWithTrigger[trigger]); + // force direction of the node we are operating on to be feeding + if (pdata->direction.dir == VariableDirection::invalid) + pdata->direction = {VariableDirection::feeding, false}; + assert(pdata->direction.dir == VariableDirection::feeding); - // set flag and store pointer to other network - pdata->nodeWithTrigger[trigger].pdata->externalTrigger = trigger; + // force direction of the triggering node to be feeding + if (trigger.pdata->direction.dir == VariableDirection::invalid) + trigger.pdata->direction = {VariableDirection::feeding, false}; + assert(trigger.pdata->direction.dir == VariableDirection::feeding); - // return the new node + // check if already existing in map + if (pdata->nodeWithTrigger.count(trigger) > 0) { return pdata->nodeWithTrigger[trigger]; } - /*********************************************************************************************************************/ + // create copy of the node + pdata->nodeWithTrigger[trigger].pdata = + boost::make_shared<VariableNetworkNode_data>(*pdata); - void VariableNetworkNode::setValueType(const std::type_info& newType) const { - assert(*pdata->valueType == typeid(AnyType)); - pdata->valueType = &newType; + // add ourselves as a trigger receiver to the other network + if (!trigger.hasOwner()) { + Application::getInstance().createNetwork().addNode(trigger); } + trigger.getOwner().addNodeToTrigger(pdata->nodeWithTrigger[trigger]); - /*********************************************************************************************************************/ + // set flag and store pointer to other network + pdata->nodeWithTrigger[trigger].pdata->externalTrigger = trigger; - void VariableNetworkNode::setDirection(VariableDirection newDirection) const { - assert(pdata->type == NodeType::ControlSystem); - assert(pdata->direction.dir == VariableDirection::feeding); - pdata->direction = newDirection; - } + // return the new node + return pdata->nodeWithTrigger[trigger]; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - bool VariableNetworkNode::hasExternalTrigger() const { - return pdata->externalTrigger.getType() != NodeType::invalid; - } +void VariableNetworkNode::setValueType(const std::type_info &newType) const { + assert(*pdata->valueType == typeid(AnyType)); + pdata->valueType = &newType; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableNetworkNode VariableNetworkNode::getExternalTrigger() { - assert(pdata->externalTrigger.getType() != NodeType::invalid); - return pdata->externalTrigger; - } +void VariableNetworkNode::setDirection(VariableDirection newDirection) const { + assert(pdata->type == NodeType::ControlSystem); + assert(pdata->direction.dir == VariableDirection::feeding); + pdata->direction = newDirection; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::dump(std::ostream& stream) const { - VariableNetworkNodeDumpingVisitor visitor(stream, " "); - visitor.dispatch(*this); - } +bool VariableNetworkNode::hasExternalTrigger() const { + return pdata->externalTrigger.getType() != NodeType::invalid; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - bool VariableNetworkNode::hasOwner() const { - return pdata->network != nullptr; - } +VariableNetworkNode VariableNetworkNode::getExternalTrigger() { + assert(pdata->externalTrigger.getType() != NodeType::invalid); + return pdata->externalTrigger; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - NodeType VariableNetworkNode::getType() const { - if(!pdata) return NodeType::invalid; - return pdata->type; - } +void VariableNetworkNode::dump(std::ostream &stream) const { + VariableNetworkNodeDumpingVisitor visitor(stream, " "); + visitor.dispatch(*this); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - UpdateMode VariableNetworkNode::getMode() const { - return pdata->mode; - } +bool VariableNetworkNode::hasOwner() const { return pdata->network != nullptr; } - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableDirection VariableNetworkNode::getDirection() const { - return pdata->direction; - } +NodeType VariableNetworkNode::getType() const { + if (!pdata) + return NodeType::invalid; + return pdata->type; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - const std::type_info& VariableNetworkNode::getValueType() const { - return *(pdata->valueType); - } +UpdateMode VariableNetworkNode::getMode() const { return pdata->mode; } - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - std::string VariableNetworkNode::getName() const { - return pdata->name; - } +VariableDirection VariableNetworkNode::getDirection() const { + return pdata->direction; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - std::string VariableNetworkNode::getQualifiedName() const { - return pdata->qualifiedName; - } +const std::type_info &VariableNetworkNode::getValueType() const { + return *(pdata->valueType); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - const std::string& VariableNetworkNode::getUnit() const { - return pdata->unit; - } +std::string VariableNetworkNode::getName() const { return pdata->name; } - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - const std::string& VariableNetworkNode::getDescription() const { - return pdata->description; - } +std::string VariableNetworkNode::getQualifiedName() const { + return pdata->qualifiedName; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableNetwork& VariableNetworkNode::getOwner() const { - assert(pdata->network != nullptr); - return *(pdata->network); - } +const std::string &VariableNetworkNode::getUnit() const { return pdata->unit; } - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - VariableNetworkNode VariableNetworkNode::getNodeToTrigger() { - assert(pdata->nodeToTrigger.getType() != NodeType::invalid); - return pdata->nodeToTrigger; - } +const std::string &VariableNetworkNode::getDescription() const { + return pdata->description; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - const std::string& VariableNetworkNode::getPublicName() const { - assert(pdata->type == NodeType::ControlSystem); - return pdata->publicName; - } +VariableNetwork &VariableNetworkNode::getOwner() const { + assert(pdata->network != nullptr); + return *(pdata->network); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - const std::string& VariableNetworkNode::getDeviceAlias() const { - assert(pdata->type == NodeType::Device); - return pdata->deviceAlias; - } +VariableNetworkNode VariableNetworkNode::getNodeToTrigger() { + assert(pdata->nodeToTrigger.getType() != NodeType::invalid); + return pdata->nodeToTrigger; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - const std::string& VariableNetworkNode::getRegisterName() const { - assert(pdata->type == NodeType::Device); - return pdata->registerName; - } +const std::string &VariableNetworkNode::getPublicName() const { + assert(pdata->type == NodeType::ControlSystem); + return pdata->publicName; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::setNumberOfElements(size_t nElements) { - pdata->nElements = nElements; - } +const std::string &VariableNetworkNode::getDeviceAlias() const { + assert(pdata->type == NodeType::Device); + return pdata->deviceAlias; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - size_t VariableNetworkNode::getNumberOfElements() const { - return pdata->nElements; - } +const std::string &VariableNetworkNode::getRegisterName() const { + assert(pdata->type == NodeType::Device); + return pdata->registerName; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - ChimeraTK::TransferElementAbstractor& VariableNetworkNode::getAppAccessorNoType() { - return *(pdata->appNode); - } +void VariableNetworkNode::setNumberOfElements(size_t nElements) { + pdata->nElements = nElements; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::setMetaData(const std::string &name, const std::string &unit, - const std::string &description) { - if(getType() != NodeType::Application) { - throw ChimeraTK::logic_error("Calling VariableNetworkNode::updateMetaData() is not allowed for non-application type nodes."); - } - pdata->name = name; - pdata->qualifiedName = pdata->owningModule->getQualifiedName()+"/"+name; - pdata->unit = unit; - pdata->description = description; - } +size_t VariableNetworkNode::getNumberOfElements() const { + return pdata->nElements; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::setMetaData(const std::string &name, const std::string &unit, - const std::string &description, const std::unordered_set<std::string> &tags) { - setMetaData(name, unit, description); - pdata->tags = tags; - } +ChimeraTK::TransferElementAbstractor & +VariableNetworkNode::getAppAccessorNoType() { + return *(pdata->appNode); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::addTag(const std::string &tag) { - pdata->tags.insert(tag); +void VariableNetworkNode::setMetaData(const std::string &name, + const std::string &unit, + const std::string &description) { + if (getType() != NodeType::Application) { + throw ChimeraTK::logic_error( + "Calling VariableNetworkNode::updateMetaData() is not allowed for " + "non-application type nodes."); } + pdata->name = name; + pdata->qualifiedName = pdata->owningModule->getQualifiedName() + "/" + name; + pdata->unit = unit; + pdata->description = description; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - const std::unordered_set<std::string>& VariableNetworkNode::getTags() const { - return pdata->tags; - } +void VariableNetworkNode::setMetaData( + const std::string &name, const std::string &unit, + const std::string &description, + const std::unordered_set<std::string> &tags) { + setMetaData(name, unit, description); + pdata->tags = tags; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::setAppAccessorPointer(ChimeraTK::TransferElementAbstractor *accessor) { - assert(getType() == NodeType::Application); - pdata->appNode = accessor; - } +void VariableNetworkNode::addTag(const std::string &tag) { + pdata->tags.insert(tag); +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - EntityOwner* VariableNetworkNode::getOwningModule() const { - return pdata->owningModule; - } +const std::unordered_set<std::string> &VariableNetworkNode::getTags() const { + return pdata->tags; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::setOwningModule(EntityOwner *newOwner) const { - pdata->owningModule = newOwner; - } +void VariableNetworkNode::setAppAccessorPointer( + ChimeraTK::TransferElementAbstractor *accessor) { + assert(getType() == NodeType::Application); + pdata->appNode = accessor; +} - /*********************************************************************************************************************/ +/*********************************************************************************************************************/ - void VariableNetworkNode::setPublicName(const std::string& name) const { - pdata->publicName = name; - } +EntityOwner *VariableNetworkNode::getOwningModule() const { + return pdata->owningModule; +} + +/*********************************************************************************************************************/ +void VariableNetworkNode::setOwningModule(EntityOwner *newOwner) const { + pdata->owningModule = newOwner; +} + +/*********************************************************************************************************************/ +void VariableNetworkNode::setPublicName(const std::string &name) const { + pdata->publicName = name; } + +} // namespace ChimeraTK diff --git a/src/VariableNetworkNodeDumpingVisitor.cc b/src/VariableNetworkNodeDumpingVisitor.cc index 8cbaca9b..e595f2b8 100644 --- a/src/VariableNetworkNodeDumpingVisitor.cc +++ b/src/VariableNetworkNodeDumpingVisitor.cc @@ -4,43 +4,54 @@ namespace ChimeraTK { -VariableNetworkNodeDumpingVisitor::VariableNetworkNodeDumpingVisitor(std::ostream &stream, const std::string& separator) - : Visitor<ChimeraTK::VariableNetworkNode> () - , PushableStream(stream) - , _separator(separator) {} - -void VariableNetworkNodeDumpingVisitor::dispatch(const VariableNetworkNode& t) { - if(t.getType() == NodeType::Application) stream() << " type = Application ('" << t.getQualifiedName() << "')"; - if(t.getType() == NodeType::ControlSystem) stream() << " type = ControlSystem ('" << t.getPublicName() << "')"; - if(t.getType() == NodeType::Device) stream() << " type = Device (" << t.getDeviceAlias() << ": " << t.getRegisterName() << ")"; - if(t.getType() == NodeType::TriggerReceiver) stream() << " type = TriggerReceiver"; - if(t.getType() == NodeType::Constant) stream() << " type = Constant"; - if(t.getType() == NodeType::invalid) stream() << " type = **invalid**"; - - if(t.getMode() == UpdateMode::push) stream() << _separator << "pushing"; - if(t.getMode() == UpdateMode::poll) stream() << _separator << "polling"; - if(t.getDirection().withReturn) stream() << _separator << "with return"; - stream() << _separator; - - stream() << "data type: " << boost::core::demangle(t.getValueType().name()); - stream() << _separator; - stream() << "length: " << t.getNumberOfElements(); - stream() << _separator; - - stream() << "[ptr: " << &(*(t.pdata)) << "]"; - stream() << _separator; - - stream() << "tags: ["; - bool first = true; - for(auto &tag : t.getTags()) { - if (!first) stream() << ","; - stream() << tag; - first = false; - } - stream() << "]"; - stream() << _separator; - - stream() << std::endl; +VariableNetworkNodeDumpingVisitor::VariableNetworkNodeDumpingVisitor( + std::ostream &stream, const std::string &separator) + : Visitor<ChimeraTK::VariableNetworkNode>(), PushableStream(stream), + _separator(separator) {} + +void VariableNetworkNodeDumpingVisitor::dispatch(const VariableNetworkNode &t) { + if (t.getType() == NodeType::Application) + stream() << " type = Application ('" << t.getQualifiedName() << "')"; + if (t.getType() == NodeType::ControlSystem) + stream() << " type = ControlSystem ('" << t.getPublicName() << "')"; + if (t.getType() == NodeType::Device) + stream() << " type = Device (" << t.getDeviceAlias() << ": " + << t.getRegisterName() << ")"; + if (t.getType() == NodeType::TriggerReceiver) + stream() << " type = TriggerReceiver"; + if (t.getType() == NodeType::Constant) + stream() << " type = Constant"; + if (t.getType() == NodeType::invalid) + stream() << " type = **invalid**"; + + if (t.getMode() == UpdateMode::push) + stream() << _separator << "pushing"; + if (t.getMode() == UpdateMode::poll) + stream() << _separator << "polling"; + if (t.getDirection().withReturn) + stream() << _separator << "with return"; + stream() << _separator; + + stream() << "data type: " << boost::core::demangle(t.getValueType().name()); + stream() << _separator; + stream() << "length: " << t.getNumberOfElements(); + stream() << _separator; + + stream() << "[ptr: " << &(*(t.pdata)) << "]"; + stream() << _separator; + + stream() << "tags: ["; + bool first = true; + for (auto &tag : t.getTags()) { + if (!first) + stream() << ","; + stream() << tag; + first = false; + } + stream() << "]"; + stream() << _separator; + + stream() << std::endl; } } // namespace ChimeraTK diff --git a/src/VersionNumberUpdatingRegisterDecorator.cc b/src/VersionNumberUpdatingRegisterDecorator.cc index 2e98d20b..a3af2c3e 100644 --- a/src/VersionNumberUpdatingRegisterDecorator.cc +++ b/src/VersionNumberUpdatingRegisterDecorator.cc @@ -3,12 +3,13 @@ namespace ChimeraTK { - template<typename T> - void VersionNumberUpdatingRegisterDecorator<T>::doPostRead() { - NDRegisterAccessorDecorator<T,T>::doPostRead(); - _owner->setCurrentVersionNumber(this->getVersionNumber()); - } - +template <typename T> +void VersionNumberUpdatingRegisterDecorator<T>::doPostRead() { + NDRegisterAccessorDecorator<T, T>::doPostRead(); + _owner->setCurrentVersionNumber(this->getVersionNumber()); } -INSTANTIATE_TEMPLATE_FOR_CHIMERATK_USER_TYPES( ChimeraTK::VersionNumberUpdatingRegisterDecorator ); +} // namespace ChimeraTK + +INSTANTIATE_TEMPLATE_FOR_CHIMERATK_USER_TYPES( + ChimeraTK::VersionNumberUpdatingRegisterDecorator); diff --git a/src/VirtualModule.cc b/src/VirtualModule.cc index a608bdc2..c54c28b7 100644 --- a/src/VirtualModule.cc +++ b/src/VirtualModule.cc @@ -8,137 +8,146 @@ #include <ChimeraTK/TransferElement.h> #include "Application.h" -#include "VirtualModule.h" #include "Module.h" +#include "VirtualModule.h" namespace ChimeraTK { - VirtualModule::VirtualModule(const VirtualModule &other) - : Module(nullptr, other.getName(), other.getDescription()) { - // since moduleList stores plain pointers, we need to regenerate this list - /// @todo find a better way than storing plain pointers! - for(auto &mod : other.submodules) addSubModule(mod); - accessorList = other.accessorList; - _eliminateHierarchy = other._eliminateHierarchy; - _moduleType = other.getModuleType(); - } +VirtualModule::VirtualModule(const VirtualModule &other) + : Module(nullptr, other.getName(), other.getDescription()) { + // since moduleList stores plain pointers, we need to regenerate this list + /// @todo find a better way than storing plain pointers! + for (auto &mod : other.submodules) + addSubModule(mod); + accessorList = other.accessorList; + _eliminateHierarchy = other._eliminateHierarchy; + _moduleType = other.getModuleType(); +} /*********************************************************************************************************************/ - VirtualModule::~VirtualModule() { - } +VirtualModule::~VirtualModule() {} /*********************************************************************************************************************/ - VirtualModule& VirtualModule::operator=(const VirtualModule &other) { - // move-assign a plain new module - Module::operator=(VirtualModule(other.getName(), other.getDescription(), other.getModuleType())); - // since moduleList stores plain pointers, we need to regenerate this list - /// @todo find a better way than storing plain pointers! - for(auto &mod : other.submodules) addSubModule(mod); - accessorList = other.accessorList; - _eliminateHierarchy = other._eliminateHierarchy; - return *this; - } +VirtualModule &VirtualModule::operator=(const VirtualModule &other) { + // move-assign a plain new module + Module::operator=(VirtualModule(other.getName(), other.getDescription(), + other.getModuleType())); + // since moduleList stores plain pointers, we need to regenerate this list + /// @todo find a better way than storing plain pointers! + for (auto &mod : other.submodules) + addSubModule(mod); + accessorList = other.accessorList; + _eliminateHierarchy = other._eliminateHierarchy; + return *this; +} /*********************************************************************************************************************/ - VariableNetworkNode VirtualModule::operator()(const std::string& variableName) const { - for(auto variable : getAccessorList()) { - if(variable.getName() == variableName) return VariableNetworkNode(variable); - } - throw ChimeraTK::logic_error("Variable '"+variableName+"' is not part of the variable group '"+_name+"'."); +VariableNetworkNode VirtualModule:: +operator()(const std::string &variableName) const { + for (auto variable : getAccessorList()) { + if (variable.getName() == variableName) + return VariableNetworkNode(variable); } + throw ChimeraTK::logic_error("Variable '" + variableName + + "' is not part of the variable group '" + _name + + "'."); +} /*********************************************************************************************************************/ - Module& VirtualModule::operator[](const std::string& moduleName) const { - for(auto submodule : getSubmoduleList()) { - if(submodule->getName() == moduleName) return *submodule; - } - throw ChimeraTK::logic_error("Sub-module '"+moduleName+"' is not part of the variable group '"+_name+"'."); +Module &VirtualModule::operator[](const std::string &moduleName) const { + for (auto submodule : getSubmoduleList()) { + if (submodule->getName() == moduleName) + return *submodule; } + throw ChimeraTK::logic_error("Sub-module '" + moduleName + + "' is not part of the variable group '" + _name + + "'."); +} /*********************************************************************************************************************/ - void VirtualModule::connectTo(const Module &target, VariableNetworkNode trigger) const { - - // connect all direct variables of this module to their counter-parts in the right-hand-side module - for(auto variable : getAccessorList()) { - if(variable.getDirection().dir == VariableDirection::feeding) { - // use trigger? - if(trigger != VariableNetworkNode() && target(variable.getName()).getMode() == UpdateMode::push - && variable.getMode() == UpdateMode::poll ) { - variable [ trigger ] >> target(variable.getName()); - } - else { - variable >> target(variable.getName()); - } +void VirtualModule::connectTo(const Module &target, + VariableNetworkNode trigger) const { + + // connect all direct variables of this module to their counter-parts in the + // right-hand-side module + for (auto variable : getAccessorList()) { + if (variable.getDirection().dir == VariableDirection::feeding) { + // use trigger? + if (trigger != VariableNetworkNode() && + target(variable.getName()).getMode() == UpdateMode::push && + variable.getMode() == UpdateMode::poll) { + variable[trigger] >> target(variable.getName()); + } else { + variable >> target(variable.getName()); } - else { - // use trigger? - if(trigger != VariableNetworkNode() && target(variable.getName()).getMode() == UpdateMode::poll - && variable.getMode() == UpdateMode::push ) { - target(variable.getName()) [ trigger ] >> variable; - } - else { - target(variable.getName()) >> variable; - } + } else { + // use trigger? + if (trigger != VariableNetworkNode() && + target(variable.getName()).getMode() == UpdateMode::poll && + variable.getMode() == UpdateMode::push) { + target(variable.getName())[trigger] >> variable; + } else { + target(variable.getName()) >> variable; } } + } - // connect all sub-modules to their couter-parts in the right-hand-side module - for(auto submodule : getSubmoduleList()) { - submodule->connectTo(target[submodule->getName()], trigger); - } - + // connect all sub-modules to their couter-parts in the right-hand-side module + for (auto submodule : getSubmoduleList()) { + submodule->connectTo(target[submodule->getName()], trigger); } +} /*********************************************************************************************************************/ - void VirtualModule::addAccessor(VariableNetworkNode accessor) { - accessorList.push_back(accessor); - } +void VirtualModule::addAccessor(VariableNetworkNode accessor) { + accessorList.push_back(accessor); +} /*********************************************************************************************************************/ - void VirtualModule::addSubModule(VirtualModule module) { - submodules.push_back(module); - registerModule(&(submodules.back())); - } +void VirtualModule::addSubModule(VirtualModule module) { + submodules.push_back(module); + registerModule(&(submodules.back())); +} /*********************************************************************************************************************/ - const Module& VirtualModule::virtualise() const { - return *this; - } +const Module &VirtualModule::virtualise() const { return *this; } /*********************************************************************************************************************/ - VirtualModule& VirtualModule::createAndGetSubmodule(const RegisterPath& moduleName) { - for(auto &sm : submodules) { - if(moduleName == sm.getName()) return sm; - } - addSubModule(VirtualModule(std::string(moduleName).substr(1), getDescription(), getModuleType())); - return submodules.back(); +VirtualModule & +VirtualModule::createAndGetSubmodule(const RegisterPath &moduleName) { + for (auto &sm : submodules) { + if (moduleName == sm.getName()) + return sm; } + addSubModule(VirtualModule(std::string(moduleName).substr(1), + getDescription(), getModuleType())); + return submodules.back(); +} /*********************************************************************************************************************/ - VirtualModule& VirtualModule::createAndGetSubmoduleRecursive(const RegisterPath& moduleName) { - auto slash = std::string(moduleName).find_first_of("/",1); - if(slash == std::string::npos) { - return createAndGetSubmodule(moduleName); - } - else { - auto firstSubmodule = std::string(moduleName).substr(0,slash); - auto remainingSubmodules = std::string(moduleName).substr(slash+1); - return createAndGetSubmodule(firstSubmodule).createAndGetSubmoduleRecursive(remainingSubmodules); - } +VirtualModule & +VirtualModule::createAndGetSubmoduleRecursive(const RegisterPath &moduleName) { + auto slash = std::string(moduleName).find_first_of("/", 1); + if (slash == std::string::npos) { + return createAndGetSubmodule(moduleName); + } else { + auto firstSubmodule = std::string(moduleName).substr(0, slash); + auto remainingSubmodules = std::string(moduleName).substr(slash + 1); + return createAndGetSubmodule(firstSubmodule) + .createAndGetSubmoduleRecursive(remainingSubmodules); } +} /*********************************************************************************************************************/ - } /* namespace ChimeraTK */ - diff --git a/src/VisitorHelper.cc b/src/VisitorHelper.cc index 98c4ddcc..f8e621d7 100644 --- a/src/VisitorHelper.cc +++ b/src/VisitorHelper.cc @@ -6,17 +6,18 @@ namespace ChimeraTK { namespace detail { std::string encodeDotNodeName(std::string name) { - std::replace(name.begin(), name.end(), ':', 'c'); // colon - std::replace(name.begin(), name.end(), '/', 's'); // slash - std::replace(name.begin(), name.end(), '.', 'd'); // dot - std::replace(name.begin(), name.end(), ' ', '_'); // Generic space replacer - std::replace(name.begin(), name.end(), '*', 'a'); // asterisk + std::replace(name.begin(), name.end(), ':', 'c'); // colon + std::replace(name.begin(), name.end(), '/', 's'); // slash + std::replace(name.begin(), name.end(), '.', 'd'); // dot + std::replace(name.begin(), name.end(), ' ', '_'); // Generic space replacer + std::replace(name.begin(), name.end(), '*', 'a'); // asterisk - return name; + return name; } -std::string nodeName(const VariableNetworkNode& node) { - return node.getQualifiedName().empty() ? node.getName() : node.getQualifiedName(); +std::string nodeName(const VariableNetworkNode &node) { + return node.getQualifiedName().empty() ? node.getName() + : node.getQualifiedName(); } } // namespace detail diff --git a/src/XMLGeneratorVisitor.cc b/src/XMLGeneratorVisitor.cc index 359e6547..fb4081a2 100644 --- a/src/XMLGeneratorVisitor.cc +++ b/src/XMLGeneratorVisitor.cc @@ -10,113 +10,127 @@ namespace ChimeraTK { XMLGeneratorVisitor::XMLGeneratorVisitor() - : Visitor<ChimeraTK::Application, ChimeraTK::VariableNetworkNode>() - , _doc(std::make_shared<xmlpp::Document>()) - , _rootElement(_doc->create_root_node("application", "https://github.com/ChimeraTK/ApplicationCore")) -{ -} + : Visitor<ChimeraTK::Application, ChimeraTK::VariableNetworkNode>(), + _doc(std::make_shared<xmlpp::Document>()), + _rootElement(_doc->create_root_node( + "application", "https://github.com/ChimeraTK/ApplicationCore")) {} void XMLGeneratorVisitor::save(const std::string &fileName) { - _doc->write_to_file_formatted(fileName); + _doc->write_to_file_formatted(fileName); } -void XMLGeneratorVisitor::dispatch(const Application& app) { - _rootElement->set_attribute("name", app.getName()); - for (auto &network : app.networkList) { - network.check(); +void XMLGeneratorVisitor::dispatch(const Application &app) { + _rootElement->set_attribute("name", app.getName()); + for (auto &network : app.networkList) { + network.check(); - auto feeder = network.getFeedingNode(); - feeder.accept(*this); + auto feeder = network.getFeedingNode(); + feeder.accept(*this); - for (auto &consumer : network.getConsumingNodes()) { - consumer.accept(*this); - } + for (auto &consumer : network.getConsumingNodes()) { + consumer.accept(*this); } + } } void XMLGeneratorVisitor::dispatch(const VariableNetworkNode &node) { - if(node.getType() != NodeType::ControlSystem) return; - - // Create the directory for the path name in the XML document with all parent directories, if not yet existing: - // First split the publication name into components and loop over each component. For each component, try to find - // the directory node and create it it does not exist. After the loop, the "current" will point to the Element - // representing the directory. - - // strip the variable name from the path - ChimeraTK::RegisterPath directory(node.getPublicName()); - directory--; - - // the namespace map is needed to properly refer to elements with an xpath expression in xmlpp::Element::find() - /// @todo TODO move this somewhere else, or at least take the namespace URI from a common place! - xmlpp::Node::PrefixNsMap nsMap{{"ac", "https://github.com/ChimeraTK/ApplicationCore"}}; - - // go through each directory path component - xmlpp::Element *current = _rootElement; - for(auto pathComponent : directory.getComponents()) { - // find directory for this path component in the current directory - std::string xpath = std::string("ac:directory[@name='")+pathComponent+std::string("']"); - auto list = current->find(xpath, nsMap); - if(list.size() == 0) { // not found: create it - xmlpp::Element *newChild = current->add_child("directory"); - newChild->set_attribute("name",pathComponent); - current = newChild; - } - else { - assert(list.size() == 1); - current = dynamic_cast<xmlpp::Element*>(list[0]); - assert(current != nullptr); - } + if (node.getType() != NodeType::ControlSystem) + return; + + // Create the directory for the path name in the XML document with all parent + // directories, if not yet existing: First split the publication name into + // components and loop over each component. For each component, try to find + // the directory node and create it it does not exist. After the loop, the + // "current" will point to the Element representing the directory. + + // strip the variable name from the path + ChimeraTK::RegisterPath directory(node.getPublicName()); + directory--; + + // the namespace map is needed to properly refer to elements with an xpath + // expression in xmlpp::Element::find() + /// @todo TODO move this somewhere else, or at least take the namespace URI + /// from a common place! + xmlpp::Node::PrefixNsMap nsMap{ + {"ac", "https://github.com/ChimeraTK/ApplicationCore"}}; + + // go through each directory path component + xmlpp::Element *current = _rootElement; + for (auto pathComponent : directory.getComponents()) { + // find directory for this path component in the current directory + std::string xpath = + std::string("ac:directory[@name='") + pathComponent + std::string("']"); + auto list = current->find(xpath, nsMap); + if (list.size() == 0) { // not found: create it + xmlpp::Element *newChild = current->add_child("directory"); + newChild->set_attribute("name", pathComponent); + current = newChild; + } else { + assert(list.size() == 1); + current = dynamic_cast<xmlpp::Element *>(list[0]); + assert(current != nullptr); } - - // now add the variable to the directory - xmlpp::Element *variable = current->add_child("variable"); - ChimeraTK::RegisterPath pathName(node.getPublicName()); - auto pathComponents = pathName.getComponents(); - - // set the name attribute - variable->set_attribute("name",pathComponents[pathComponents.size()-1]); - - // add sub-element containing the data type - std::string dataTypeName{"unknown"}; - auto &owner = node.getOwner(); - auto &type = owner.getValueType(); - if(type == typeid(int8_t)) { dataTypeName = "int8"; } - else if(type == typeid(uint8_t)) { dataTypeName = "uint8"; } - else if(type == typeid(int16_t)) { dataTypeName = "int16"; } - else if(type == typeid(uint16_t)) { dataTypeName = "uint16"; } - else if(type == typeid(int32_t)) { dataTypeName = "int32"; } - else if(type == typeid(uint32_t)) { dataTypeName = "uint32"; } - else if(type == typeid(float)) { dataTypeName = "float"; } - else if(type == typeid(double)) { dataTypeName = "double"; } - else if(type == typeid(std::string)) { dataTypeName = "string"; } - xmlpp::Element *valueTypeElement = variable->add_child("value_type"); - valueTypeElement->set_child_text(dataTypeName); - - // add sub-element containing the data flow direction - std::string dataFlowName{"application_to_control_system"}; - if(owner.getFeedingNode() == node) { + } + + // now add the variable to the directory + xmlpp::Element *variable = current->add_child("variable"); + ChimeraTK::RegisterPath pathName(node.getPublicName()); + auto pathComponents = pathName.getComponents(); + + // set the name attribute + variable->set_attribute("name", pathComponents[pathComponents.size() - 1]); + + // add sub-element containing the data type + std::string dataTypeName{"unknown"}; + auto &owner = node.getOwner(); + auto &type = owner.getValueType(); + if (type == typeid(int8_t)) { + dataTypeName = "int8"; + } else if (type == typeid(uint8_t)) { + dataTypeName = "uint8"; + } else if (type == typeid(int16_t)) { + dataTypeName = "int16"; + } else if (type == typeid(uint16_t)) { + dataTypeName = "uint16"; + } else if (type == typeid(int32_t)) { + dataTypeName = "int32"; + } else if (type == typeid(uint32_t)) { + dataTypeName = "uint32"; + } else if (type == typeid(float)) { + dataTypeName = "float"; + } else if (type == typeid(double)) { + dataTypeName = "double"; + } else if (type == typeid(std::string)) { + dataTypeName = "string"; + } + xmlpp::Element *valueTypeElement = variable->add_child("value_type"); + valueTypeElement->set_child_text(dataTypeName); + + // add sub-element containing the data flow direction + std::string dataFlowName{"application_to_control_system"}; + if (owner.getFeedingNode() == node) { + dataFlowName = "control_system_to_application"; + if (!owner.getFeedingNode().getDirection().withReturn) { dataFlowName = "control_system_to_application"; - if(!owner.getFeedingNode().getDirection().withReturn) { - dataFlowName = "control_system_to_application"; - } - else { - dataFlowName = "control_system_to_application_with_return"; - } + } else { + dataFlowName = "control_system_to_application_with_return"; } - xmlpp::Element *directionElement = variable->add_child("direction"); - directionElement->set_child_text(dataFlowName); - - // add sub-element containing the engineering unit - xmlpp::Element *unitElement = variable->add_child("unit"); - unitElement->set_child_text(owner.getUnit()); - - // add sub-element containing the description - xmlpp::Element *descriptionElement = variable->add_child("description"); - descriptionElement->set_child_text(owner.getDescription()); - - // add sub-element containing the description - xmlpp::Element *nElementsElement = variable->add_child("numberOfElements"); - nElementsElement->set_child_text(std::to_string(owner.getFeedingNode().getNumberOfElements())); + } + xmlpp::Element *directionElement = variable->add_child("direction"); + directionElement->set_child_text(dataFlowName); + + // add sub-element containing the engineering unit + xmlpp::Element *unitElement = variable->add_child("unit"); + unitElement->set_child_text(owner.getUnit()); + + // add sub-element containing the description + xmlpp::Element *descriptionElement = variable->add_child("description"); + descriptionElement->set_child_text(owner.getDescription()); + + // add sub-element containing the description + xmlpp::Element *nElementsElement = variable->add_child("numberOfElements"); + nElementsElement->set_child_text( + std::to_string(owner.getFeedingNode().getNumberOfElements())); } } // namespace ChimeraTK diff --git a/tests/executables_src/testAppModuleConnections.cc b/tests/executables_src/testAppModuleConnections.cc index 9ecb9228..8962ff6c 100644 --- a/tests/executables_src/testAppModuleConnections.cc +++ b/tests/executables_src/testAppModuleConnections.cc @@ -9,79 +9,88 @@ #define BOOST_TEST_MODULE testAppModuleConnections +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> #include <ChimeraTK/BackendFactory.h> #include "Application.h" -#include "ScalarAccessor.h" -#include "ArrayAccessor.h" #include "ApplicationModule.h" +#include "ArrayAccessor.h" +#include "ScalarAccessor.h" using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; // list of user types the accessors are tested with -typedef boost::mpl::list<int8_t,uint8_t, - int16_t,uint16_t, - int32_t,uint32_t, - float,double> test_types; +typedef boost::mpl::list<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + float, double> + test_types; /*********************************************************************************************************************/ /* the ApplicationModule for the test is a template of the user type */ -template<typename T> -struct TestModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - - ctk::ScalarOutput<T> feedingPush{this, "feedingPush", "MV/m", "Some output scalar"}; - ctk::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", "Descrption"}; - ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", "Descrption"}; - ctk::ScalarPushInput<T> consumingPush3{this, "consumingPush3", "MV/m", "Descrption"}; - - ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", "Descrption"}; - ctk::ScalarPollInput<T> consumingPoll2{this, "consumingPoll2", "MV/m", "Descrption"}; - ctk::ScalarPollInput<T> consumingPoll3{this, "consumingPoll3", "MV/m", "Descrption"}; - - ctk::ArrayPollInput<T> consumingPollArray{this, "consumingPollArray", "m", 10, "Descrption"}; - ctk::ArrayPushInput<T> consumingPushArray{this, "consumingPushArray", "m", 10, "Descrption"}; - - ctk::ArrayOutput<T> feedingArray{this, "feedingArray", "m", 10, "Descrption"}; - ctk::ArrayOutput<T> feedingPseudoArray{this, "feedingPseudoArray", "m", 1, "Descrption"}; - - ctk::ScalarPollInput<T> lateConstrScalarPollInput; - ctk::ScalarPushInput<T> lateConstrScalarPushInput; - ctk::ScalarOutput<T> lateConstrScalarOutput; - - ctk::ArrayPollInput<T> lateConstrArrayPollInput; - ctk::ArrayPushInput<T> lateConstrArrayPushInput; - ctk::ArrayOutput<T> lateConstrArrayOutput; - - void mainLoop() {} +template <typename T> struct TestModule : public ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; + + ctk::ScalarOutput<T> feedingPush{this, "feedingPush", "MV/m", + "Some output scalar"}; + ctk::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", + "Descrption"}; + ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", + "Descrption"}; + ctk::ScalarPushInput<T> consumingPush3{this, "consumingPush3", "MV/m", + "Descrption"}; + + ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", + "Descrption"}; + ctk::ScalarPollInput<T> consumingPoll2{this, "consumingPoll2", "MV/m", + "Descrption"}; + ctk::ScalarPollInput<T> consumingPoll3{this, "consumingPoll3", "MV/m", + "Descrption"}; + + ctk::ArrayPollInput<T> consumingPollArray{this, "consumingPollArray", "m", 10, + "Descrption"}; + ctk::ArrayPushInput<T> consumingPushArray{this, "consumingPushArray", "m", 10, + "Descrption"}; + + ctk::ArrayOutput<T> feedingArray{this, "feedingArray", "m", 10, "Descrption"}; + ctk::ArrayOutput<T> feedingPseudoArray{this, "feedingPseudoArray", "m", 1, + "Descrption"}; + + ctk::ScalarPollInput<T> lateConstrScalarPollInput; + ctk::ScalarPushInput<T> lateConstrScalarPushInput; + ctk::ScalarOutput<T> lateConstrScalarOutput; + + ctk::ArrayPollInput<T> lateConstrArrayPollInput; + ctk::ArrayPushInput<T> lateConstrArrayPushInput; + ctk::ArrayOutput<T> lateConstrArrayOutput; + + void mainLoop() {} }; /*********************************************************************************************************************/ /* dummy application */ -template<typename T> -struct TestApplication : public ctk::Application { +template <typename T> struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") {} - ~TestApplication() { shutdown(); } + TestApplication() : Application("testSuite") {} + ~TestApplication() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // the setup is done in the tests + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // the setup is done in the tests - TestModule<T> testModule{this,"testModule", "The test module"}; + TestModule<T> testModule{this, "testModule", "The test module"}; }; /*********************************************************************************************************************/ /* test case for two scalar accessors in push mode */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoScalarPushAccessors, T, test_types ) { - std::cout << "*** testTwoScalarPushAccessors<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testTwoScalarPushAccessors, T, test_types) { + std::cout << "*** testTwoScalarPushAccessors<" << typeid(T).name() << ">" + << std::endl; TestApplication<T> app; @@ -97,9 +106,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoScalarPushAccessors, T, test_types ) { app.testModule.consumingPush.read(); BOOST_CHECK(app.testModule.consumingPush == 42); - // launch read() on the consumer asynchronously and make sure it does not yet receive anything - auto futRead = std::async(std::launch::async, [&app]{ app.testModule.consumingPush.read(); }); - BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); + // launch read() on the consumer asynchronously and make sure it does not yet + // receive anything + auto futRead = std::async(std::launch::async, + [&app] { app.testModule.consumingPush.read(); }); + BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == + std::future_status::timeout); BOOST_CHECK(app.testModule.consumingPush == 42); @@ -108,16 +120,18 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoScalarPushAccessors, T, test_types ) { app.testModule.feedingPush.write(); // check that the consumer now receives the just written value - BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); - BOOST_CHECK( app.testModule.consumingPush == 120 ); - + BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); + BOOST_CHECK(app.testModule.consumingPush == 120); } /*********************************************************************************************************************/ -/* test case for four scalar accessors in push mode: one feeder and three consumers */ +/* test case for four scalar accessors in push mode: one feeder and three + * consumers */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testFourScalarPushAccessors, T, test_types ) { - std::cout << "*** testFourScalarPushAccessors<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testFourScalarPushAccessors, T, test_types) { + std::cout << "*** testFourScalarPushAccessors<" << typeid(T).name() << ">" + << std::endl; TestApplication<T> app; @@ -149,19 +163,26 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testFourScalarPushAccessors, T, test_types ) { BOOST_CHECK(app.testModule.consumingPush3 == 3); app.testModule.consumingPush3.read(); BOOST_CHECK(app.testModule.consumingPush == 42); - BOOST_CHECK(app.testModule.consumingPush2 ==42); + BOOST_CHECK(app.testModule.consumingPush2 == 42); BOOST_CHECK(app.testModule.consumingPush3 == 42); - // launch read() on the consumers asynchronously and make sure it does not yet receive anything - auto futRead = std::async(std::launch::async, [&app]{ app.testModule.consumingPush.read(); }); - auto futRead2 = std::async(std::launch::async, [&app]{ app.testModule.consumingPush2.read(); }); - auto futRead3 = std::async(std::launch::async, [&app]{ app.testModule.consumingPush3.read(); }); - BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); - BOOST_CHECK(futRead2.wait_for(std::chrono::milliseconds(1)) == std::future_status::timeout); - BOOST_CHECK(futRead3.wait_for(std::chrono::milliseconds(1)) == std::future_status::timeout); + // launch read() on the consumers asynchronously and make sure it does not yet + // receive anything + auto futRead = std::async(std::launch::async, + [&app] { app.testModule.consumingPush.read(); }); + auto futRead2 = std::async(std::launch::async, + [&app] { app.testModule.consumingPush2.read(); }); + auto futRead3 = std::async(std::launch::async, + [&app] { app.testModule.consumingPush3.read(); }); + BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == + std::future_status::timeout); + BOOST_CHECK(futRead2.wait_for(std::chrono::milliseconds(1)) == + std::future_status::timeout); + BOOST_CHECK(futRead3.wait_for(std::chrono::milliseconds(1)) == + std::future_status::timeout); BOOST_CHECK(app.testModule.consumingPush == 42); - BOOST_CHECK(app.testModule.consumingPush2 ==42); + BOOST_CHECK(app.testModule.consumingPush2 == 42); BOOST_CHECK(app.testModule.consumingPush3 == 42); // write to the feeder @@ -169,20 +190,24 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testFourScalarPushAccessors, T, test_types ) { app.testModule.feedingPush.write(); // check that the consumers now receive the just written value - BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); - BOOST_CHECK(futRead2.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); - BOOST_CHECK(futRead3.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); - BOOST_CHECK( app.testModule.consumingPush == 120 ); - BOOST_CHECK( app.testModule.consumingPush2 == 120 ); - BOOST_CHECK( app.testModule.consumingPush3 == 120 ); - + BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); + BOOST_CHECK(futRead2.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); + BOOST_CHECK(futRead3.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); + BOOST_CHECK(app.testModule.consumingPush == 120); + BOOST_CHECK(app.testModule.consumingPush2 == 120); + BOOST_CHECK(app.testModule.consumingPush3 == 120); } /*********************************************************************************************************************/ -/* test case for two scalar accessors, feeder in push mode and consumer in poll mode */ +/* test case for two scalar accessors, feeder in push mode and consumer in poll + * mode */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoScalarPushPollAccessors, T, test_types ) { - std::cout << "*** testTwoScalarPushPollAccessors<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testTwoScalarPushPollAccessors, T, test_types) { + std::cout << "*** testTwoScalarPushPollAccessors<" << typeid(T).name() << ">" + << std::endl; TestApplication<T> app; @@ -206,19 +231,19 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoScalarPushPollAccessors, T, test_types ) { app.testModule.feedingPush.write(); BOOST_CHECK(app.testModule.consumingPoll == 42); app.testModule.consumingPoll.read(); - BOOST_CHECK( app.testModule.consumingPoll == 120 ); + BOOST_CHECK(app.testModule.consumingPoll == 120); app.testModule.consumingPoll.read(); - BOOST_CHECK( app.testModule.consumingPoll == 120 ); + BOOST_CHECK(app.testModule.consumingPoll == 120); app.testModule.consumingPoll.read(); - BOOST_CHECK( app.testModule.consumingPoll == 120 ); - + BOOST_CHECK(app.testModule.consumingPoll == 120); } /*********************************************************************************************************************/ /* test case for two array accessors in push mode */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoArrayAccessors, T, test_types ) { - std::cout << "*** testTwoArrayAccessors<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testTwoArrayAccessors, T, test_types) { + std::cout << "*** testTwoArrayAccessors<" << typeid(T).name() << ">" + << std::endl; TestApplication<T> app; @@ -229,54 +254,74 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoArrayAccessors, T, test_types ) { BOOST_CHECK(app.testModule.consumingPushArray.getNElements() == 10); // single theaded test - for(auto &val : app.testModule.consumingPushArray) val = 0; - for(unsigned int i=0; i<10; ++i) app.testModule.feedingArray[i] = 99+(T)i; - for(auto &val : app.testModule.consumingPushArray) BOOST_CHECK(val == 0); + for (auto &val : app.testModule.consumingPushArray) + val = 0; + for (unsigned int i = 0; i < 10; ++i) + app.testModule.feedingArray[i] = 99 + (T)i; + for (auto &val : app.testModule.consumingPushArray) + BOOST_CHECK(val == 0); app.testModule.feedingArray.write(); - for(auto &val : app.testModule.consumingPushArray) BOOST_CHECK(val == 0); + for (auto &val : app.testModule.consumingPushArray) + BOOST_CHECK(val == 0); app.testModule.consumingPushArray.read(); - for(unsigned int i=0; i<10; ++i) BOOST_CHECK(app.testModule.consumingPushArray[i] == 99+(T)i); + for (unsigned int i = 0; i < 10; ++i) + BOOST_CHECK(app.testModule.consumingPushArray[i] == 99 + (T)i); - // launch read() on the consumer asynchronously and make sure it does not yet receive anything - auto futRead = std::async(std::launch::async, [&app]{ app.testModule.consumingPushArray.read(); }); - BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); + // launch read() on the consumer asynchronously and make sure it does not yet + // receive anything + auto futRead = std::async( + std::launch::async, [&app] { app.testModule.consumingPushArray.read(); }); + BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == + std::future_status::timeout); - for(unsigned int i=0; i<10; ++i) BOOST_CHECK(app.testModule.consumingPushArray[i] == 99+(T)i); + for (unsigned int i = 0; i < 10; ++i) + BOOST_CHECK(app.testModule.consumingPushArray[i] == 99 + (T)i); // write to the feeder - for(unsigned int i=0; i<10; ++i) app.testModule.feedingArray[i] = 42-(T)i; + for (unsigned int i = 0; i < 10; ++i) + app.testModule.feedingArray[i] = 42 - (T)i; app.testModule.feedingArray.write(); // check that the consumer now receives the just written value - BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); - for(unsigned int i=0; i<10; ++i) BOOST_CHECK(app.testModule.consumingPushArray[i] == 42-(T)i); - + BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); + for (unsigned int i = 0; i < 10; ++i) + BOOST_CHECK(app.testModule.consumingPushArray[i] == 42 - (T)i); } /*********************************************************************************************************************/ /* test case for late constructing accessors */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testLateConstruction, T, test_types ) { - std::cout << "*** testLateConstruction<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testLateConstruction, T, test_types) { + std::cout << "*** testLateConstruction<" << typeid(T).name() << ">" + << std::endl; TestApplication<T> app; // create the scalars - app.testModule.lateConstrScalarPollInput.replace( ctk::ScalarPollInput<T>(&app.testModule, "LateName1", "", "") ); - app.testModule.lateConstrScalarPushInput.replace( ctk::ScalarPushInput<T>(&app.testModule, "LateName2", "", "") ); - app.testModule.lateConstrScalarOutput.replace( ctk::ScalarOutput<T>(&app.testModule, "LateName3", "", "") ); + app.testModule.lateConstrScalarPollInput.replace( + ctk::ScalarPollInput<T>(&app.testModule, "LateName1", "", "")); + app.testModule.lateConstrScalarPushInput.replace( + ctk::ScalarPushInput<T>(&app.testModule, "LateName2", "", "")); + app.testModule.lateConstrScalarOutput.replace( + ctk::ScalarOutput<T>(&app.testModule, "LateName3", "", "")); // connect the scalars - app.testModule.lateConstrScalarOutput >> app.testModule.lateConstrScalarPollInput; + app.testModule.lateConstrScalarOutput >> + app.testModule.lateConstrScalarPollInput; app.testModule.feedingPush >> app.testModule.lateConstrScalarPushInput; // create the arrays - app.testModule.lateConstrArrayPollInput.replace( ctk::ArrayPollInput<T>(&app.testModule, "LateName4", "", 10, "") ); - app.testModule.lateConstrArrayPushInput.replace( ctk::ArrayPushInput<T>(&app.testModule, "LateName5", "", 10, "") ); - app.testModule.lateConstrArrayOutput.replace( ctk::ArrayOutput<T>(&app.testModule, "LateName6", "", 10, "") ); + app.testModule.lateConstrArrayPollInput.replace( + ctk::ArrayPollInput<T>(&app.testModule, "LateName4", "", 10, "")); + app.testModule.lateConstrArrayPushInput.replace( + ctk::ArrayPushInput<T>(&app.testModule, "LateName5", "", 10, "")); + app.testModule.lateConstrArrayOutput.replace( + ctk::ArrayOutput<T>(&app.testModule, "LateName6", "", 10, "")); // connect the arrays - app.testModule.lateConstrArrayOutput >> app.testModule.lateConstrArrayPollInput; + app.testModule.lateConstrArrayOutput >> + app.testModule.lateConstrArrayPollInput; app.testModule.feedingArray >> app.testModule.lateConstrArrayPushInput; // run the app @@ -302,29 +347,32 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testLateConstruction, T, test_types ) { BOOST_CHECK(app.testModule.lateConstrScalarPollInput == 120); // test the arrays - app.testModule.feedingArray = {1,2,3,4,5,6,7,8,9,10}; + app.testModule.feedingArray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; app.testModule.feedingArray.write(); app.testModule.lateConstrArrayPushInput.read(); - for(T i=0; i<10; ++i) BOOST_CHECK(app.testModule.lateConstrArrayPushInput[i] == i+1); + for (T i = 0; i < 10; ++i) + BOOST_CHECK(app.testModule.lateConstrArrayPushInput[i] == i + 1); - app.testModule.feedingArray = {10,20,30,40,50,60,70,80,90,100}; + app.testModule.feedingArray = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; app.testModule.feedingArray.write(); app.testModule.lateConstrArrayPushInput.read(); - for(T i=0; i<10; ++i) BOOST_CHECK(app.testModule.lateConstrArrayPushInput[i] == (i+1)*10); + for (T i = 0; i < 10; ++i) + BOOST_CHECK(app.testModule.lateConstrArrayPushInput[i] == (i + 1) * 10); - app.testModule.lateConstrArrayOutput = {0,1,2,3,4,5,6,7,8,9}; + app.testModule.lateConstrArrayOutput = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; app.testModule.lateConstrArrayOutput.write(); app.testModule.lateConstrArrayPollInput.read(); - for(T i=0; i<10; ++i) BOOST_CHECK(app.testModule.lateConstrArrayPollInput[i] == i); + for (T i = 0; i < 10; ++i) + BOOST_CHECK(app.testModule.lateConstrArrayPollInput[i] == i); app.testModule.lateConstrArrayPollInput.read(); - for(T i=0; i<10; ++i) BOOST_CHECK(app.testModule.lateConstrArrayPollInput[i] == i); - + for (T i = 0; i < 10; ++i) + BOOST_CHECK(app.testModule.lateConstrArrayPollInput[i] == i); } /*********************************************************************************************************************/ /* test case for connecting array of length 1 with scalar */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testPseudoArray, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testPseudoArray, T, test_types) { std::cout << "*** testPseudoArray<" << typeid(T).name() << ">" << std::endl; TestApplication<T> app; @@ -339,6 +387,5 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testPseudoArray, T, test_types ) { app.testModule.feedingPseudoArray[0] = 33; app.testModule.feedingPseudoArray.write(); app.testModule.consumingPush.read(); - BOOST_CHECK( app.testModule.consumingPush == 33 ); - + BOOST_CHECK(app.testModule.consumingPush == 33); } diff --git a/tests/executables_src/testApplication.cc b/tests/executables_src/testApplication.cc index 641aed9f..79416386 100644 --- a/tests/executables_src/testApplication.cc +++ b/tests/executables_src/testApplication.cc @@ -5,16 +5,16 @@ * Author: Martin Hierholzer */ -#include <future> #include <chrono> +#include <future> #define BOOST_TEST_MODULE testApplication +#include <boost/filesystem.hpp> +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> #include <boost/thread.hpp> -#include <boost/filesystem.hpp> #include <libxml++/libxml++.h> @@ -30,135 +30,188 @@ namespace ctk = ChimeraTK; /* Application without name */ struct TestApp : public ctk::Application { - TestApp(const std::string &name) : ctk::Application(name) {} - ~TestApp() { shutdown(); } - - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() { - multiplierD.output >> csmod("myVarD"); - csmod["mySubModule"]("myVarSIn") >> pipe.input; - pipe.output >> csmod["mySubModule"]("myVarSOut"); - csmod("myVarU16") >> multiplierU16.input; - } - - ctk::ConstMultiplier<double> multiplierD{this, "multiplierD", "Some module", 42}; - ctk::ScalarPipe<std::string> pipe{this, "pipe", "unit", "Some pipe module"}; - ctk::ConstMultiplier<uint16_t,uint16_t, 120> multiplierU16{this, "multiplierU16", "Some other module", 42}; - ctk::ControlSystemModule csmod; + TestApp(const std::string &name) : ctk::Application(name) {} + ~TestApp() { shutdown(); } + + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() { + multiplierD.output >> csmod("myVarD"); + csmod["mySubModule"]("myVarSIn") >> pipe.input; + pipe.output >> csmod["mySubModule"]("myVarSOut"); + csmod("myVarU16") >> multiplierU16.input; + } + + ctk::ConstMultiplier<double> multiplierD{this, "multiplierD", "Some module", + 42}; + ctk::ScalarPipe<std::string> pipe{this, "pipe", "unit", "Some pipe module"}; + ctk::ConstMultiplier<uint16_t, uint16_t, 120> multiplierU16{ + this, "multiplierU16", "Some other module", 42}; + ctk::ControlSystemModule csmod; }; /*********************************************************************************************************************/ -/* test trigger by app variable when connecting a polled device register to an app variable */ - -BOOST_AUTO_TEST_CASE( testApplicationExceptions ) { - std::cout << "*********************************************************************************************************************" << std::endl; - std::cout << "==> testApplicationExceptions" << std::endl; - - // zero length name forbidden - try { - TestApp app(""); - BOOST_FAIL("Exception expected."); - } - catch(ChimeraTK::logic_error&) { - } - - // names with spaces and special characters are forbidden - try { - TestApp app("With space"); - BOOST_FAIL("Exception expected."); - } - catch(ChimeraTK::logic_error&) { - } - try { - TestApp app("WithExclamationMark!"); - BOOST_FAIL("Exception expected."); - } - catch(ChimeraTK::logic_error&) { - } - - // all allowed characters in the name - { - TestApp app("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz_1234567890"); - } - - // repeated characters are allowed - { - TestApp app("AAAAAAA"); - } - - // Two apps at the same time are not allowed - TestApp app1("FirstInstance"); - try { - TestApp app2("SecondInstance"); - BOOST_FAIL("Exception expected."); - } - catch(ChimeraTK::logic_error&) { - } - +/* test trigger by app variable when connecting a polled device register to an + * app variable */ + +BOOST_AUTO_TEST_CASE(testApplicationExceptions) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; + std::cout << "==> testApplicationExceptions" << std::endl; + + // zero length name forbidden + try { + TestApp app(""); + BOOST_FAIL("Exception expected."); + } catch (ChimeraTK::logic_error &) { + } + + // names with spaces and special characters are forbidden + try { + TestApp app("With space"); + BOOST_FAIL("Exception expected."); + } catch (ChimeraTK::logic_error &) { + } + try { + TestApp app("WithExclamationMark!"); + BOOST_FAIL("Exception expected."); + } catch (ChimeraTK::logic_error &) { + } + + // all allowed characters in the name + { + TestApp app( + "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz_1234567890"); + } + + // repeated characters are allowed + { TestApp app("AAAAAAA"); } + + // Two apps at the same time are not allowed + TestApp app1("FirstInstance"); + try { + TestApp app2("SecondInstance"); + BOOST_FAIL("Exception expected."); + } catch (ChimeraTK::logic_error &) { + } } /*********************************************************************************************************************/ // Helper function for testXmlGeneration: // Obtain a value from an XML node -std::string getValueFromNode(const xmlpp::Node *node, const std::string &subnodeName) { - xmlpp::Node *theChild = nullptr; - for(const auto& child : node->get_children()) { - if(child->get_name() == subnodeName) theChild = child; - } - BOOST_REQUIRE(theChild != nullptr); // requested child tag is there - auto subChildList = theChild->get_children(); - if(subChildList.size() == 0) { // special case: no text in the tag -> return empty string - return ""; - } - BOOST_REQUIRE_EQUAL(subChildList.size(), 1); // child tag contains only text (no further sub-tags) - const xmlpp::TextNode *textNode = dynamic_cast<xmlpp::TextNode*>(subChildList.front()); - BOOST_REQUIRE(textNode != nullptr); - return textNode->get_content(); +std::string getValueFromNode(const xmlpp::Node *node, + const std::string &subnodeName) { + xmlpp::Node *theChild = nullptr; + for (const auto &child : node->get_children()) { + if (child->get_name() == subnodeName) + theChild = child; + } + BOOST_REQUIRE(theChild != nullptr); // requested child tag is there + auto subChildList = theChild->get_children(); + if (subChildList.size() == + 0) { // special case: no text in the tag -> return empty string + return ""; + } + BOOST_REQUIRE_EQUAL(subChildList.size(), + 1); // child tag contains only text (no further sub-tags) + const xmlpp::TextNode *textNode = + dynamic_cast<xmlpp::TextNode *>(subChildList.front()); + BOOST_REQUIRE(textNode != nullptr); + return textNode->get_content(); } /*********************************************************************************************************************/ /* test creation of XML file describing the variable tree */ -BOOST_AUTO_TEST_CASE( testXmlGeneration ) { - std::cout << "*********************************************************************************************************************" << std::endl; - std::cout << "==> testXmlGeneration" << std::endl; - - // delete XML file if already existing - boost::filesystem::remove("TestAppInstance.xml"); - - // create app which exports some properties and generate its XML file - TestApp app("TestAppInstance"); - app.generateXML(); - - // validate the XML file - xmlpp::XsdValidator validator("application.xsd"); - validator.validate("TestAppInstance.xml"); - - // parse XML file - xmlpp::DomParser parser; - try { - parser.parse_file("TestAppInstance.xml"); - } - catch(xmlpp::exception &e) { - throw std::runtime_error(std::string("ConfigReader: Error opening the config file 'TestAppInstance.xml': ")+e.what()); - } - - // get root element - const auto root = parser.get_document()->get_root_node(); - BOOST_CHECK_EQUAL(root->get_name(), "application"); - - // parsing loop - bool found_myVarD{false}; - bool found_myVarSIn{false}; - bool found_myVarSOut{false}; - bool found_myVarU8{false}; - - for(const auto& child : root->get_children()) { - // cast into element, ignore if not an element (e.g. comment) - auto *element = dynamic_cast<const xmlpp::Element*>(child); - if(!element) continue; - - if(element->get_name() == "variable") { +BOOST_AUTO_TEST_CASE(testXmlGeneration) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; + std::cout << "==> testXmlGeneration" << std::endl; + + // delete XML file if already existing + boost::filesystem::remove("TestAppInstance.xml"); + + // create app which exports some properties and generate its XML file + TestApp app("TestAppInstance"); + app.generateXML(); + + // validate the XML file + xmlpp::XsdValidator validator("application.xsd"); + validator.validate("TestAppInstance.xml"); + + // parse XML file + xmlpp::DomParser parser; + try { + parser.parse_file("TestAppInstance.xml"); + } catch (xmlpp::exception &e) { + throw std::runtime_error( + std::string("ConfigReader: Error opening the config file " + "'TestAppInstance.xml': ") + + e.what()); + } + + // get root element + const auto root = parser.get_document()->get_root_node(); + BOOST_CHECK_EQUAL(root->get_name(), "application"); + + // parsing loop + bool found_myVarD{false}; + bool found_myVarSIn{false}; + bool found_myVarSOut{false}; + bool found_myVarU8{false}; + + for (const auto &child : root->get_children()) { + // cast into element, ignore if not an element (e.g. comment) + auto *element = dynamic_cast<const xmlpp::Element *>(child); + if (!element) + continue; + + if (element->get_name() == "variable") { + + // obtain attributes from the element + auto xname = element->get_attribute("name"); + BOOST_REQUIRE(xname != nullptr); + std::string name(xname->get_value()); + + // obtain values from sub-elements + std::string value_type = getValueFromNode(element, "value_type"); + std::string direction = getValueFromNode(element, "direction"); + std::string unit = getValueFromNode(element, "unit"); + std::string description = getValueFromNode(element, "description"); + std::string numberOfElements = + getValueFromNode(element, "numberOfElements"); + + // check if variables are described correctly + if (name == "myVarD") { + found_myVarD = true; + BOOST_CHECK_EQUAL(value_type, "double"); + BOOST_CHECK_EQUAL(direction, "application_to_control_system"); + BOOST_CHECK_EQUAL(unit, ""); + BOOST_CHECK_EQUAL(description, "Some module"); + BOOST_CHECK_EQUAL(numberOfElements, "1"); + } else if (name == "myVarU16") { + found_myVarU8 = true; + BOOST_CHECK_EQUAL(value_type, "uint16"); + BOOST_CHECK_EQUAL(direction, "control_system_to_application"); + BOOST_CHECK_EQUAL(unit, ""); + BOOST_CHECK_EQUAL(description, "Some other module"); + BOOST_CHECK_EQUAL(numberOfElements, "120"); + } else { + BOOST_ERROR("Wrong variable name found."); + } + } else if (element->get_name() == "directory") { + auto xname = element->get_attribute("name"); + BOOST_REQUIRE(xname != nullptr); + std::string name(xname->get_value()); + BOOST_CHECK_EQUAL(name, "mySubModule"); + + for (const auto &subchild : child->get_children()) { + auto *element = dynamic_cast<const xmlpp::Element *>(subchild); + if (!element) + continue; + BOOST_CHECK_EQUAL(element->get_name(), "variable"); // obtain attributes from the element auto xname = element->get_attribute("name"); @@ -170,83 +223,35 @@ BOOST_AUTO_TEST_CASE( testXmlGeneration ) { std::string direction = getValueFromNode(element, "direction"); std::string unit = getValueFromNode(element, "unit"); std::string description = getValueFromNode(element, "description"); - std::string numberOfElements = getValueFromNode(element, "numberOfElements"); + std::string numberOfElements = + getValueFromNode(element, "numberOfElements"); - // check if variables are described correctly - if(name == "myVarD") { - found_myVarD = true; - BOOST_CHECK_EQUAL(value_type, "double"); + if (name == "myVarSIn") { + found_myVarSIn = true; + BOOST_CHECK_EQUAL(value_type, "string"); + BOOST_CHECK_EQUAL(direction, "control_system_to_application"); + BOOST_CHECK_EQUAL(unit, "unit"); + BOOST_CHECK_EQUAL(description, "Some pipe module"); + BOOST_CHECK_EQUAL(numberOfElements, "1"); + } else if (name == "myVarSOut") { + found_myVarSOut = true; + BOOST_CHECK_EQUAL(value_type, "string"); BOOST_CHECK_EQUAL(direction, "application_to_control_system"); - BOOST_CHECK_EQUAL(unit, ""); - BOOST_CHECK_EQUAL(description, "Some module"); + BOOST_CHECK_EQUAL(unit, "unit"); + BOOST_CHECK_EQUAL(description, "Some pipe module"); BOOST_CHECK_EQUAL(numberOfElements, "1"); - } - else if(name == "myVarU16") { - found_myVarU8 = true; - BOOST_CHECK_EQUAL(value_type, "uint16"); - BOOST_CHECK_EQUAL(direction, "control_system_to_application"); - BOOST_CHECK_EQUAL(unit, ""); - BOOST_CHECK_EQUAL(description, "Some other module"); - BOOST_CHECK_EQUAL(numberOfElements, "120"); - } - else { + } else { BOOST_ERROR("Wrong variable name found."); } } - else if(element->get_name() == "directory") { - auto xname = element->get_attribute("name"); - BOOST_REQUIRE(xname != nullptr); - std::string name(xname->get_value()); - BOOST_CHECK_EQUAL(name, "mySubModule"); - - for(const auto& subchild : child->get_children()) { - auto *element = dynamic_cast<const xmlpp::Element*>(subchild); - if(!element) continue; - BOOST_CHECK_EQUAL(element->get_name(), "variable"); - - // obtain attributes from the element - auto xname = element->get_attribute("name"); - BOOST_REQUIRE(xname != nullptr); - std::string name(xname->get_value()); - - // obtain values from sub-elements - std::string value_type = getValueFromNode(element, "value_type"); - std::string direction = getValueFromNode(element, "direction"); - std::string unit = getValueFromNode(element, "unit"); - std::string description = getValueFromNode(element, "description"); - std::string numberOfElements = getValueFromNode(element, "numberOfElements"); - - if(name == "myVarSIn") { - found_myVarSIn = true; - BOOST_CHECK_EQUAL(value_type, "string"); - BOOST_CHECK_EQUAL(direction, "control_system_to_application"); - BOOST_CHECK_EQUAL(unit, "unit"); - BOOST_CHECK_EQUAL(description, "Some pipe module"); - BOOST_CHECK_EQUAL(numberOfElements, "1"); - } - else if(name == "myVarSOut") { - found_myVarSOut = true; - BOOST_CHECK_EQUAL(value_type, "string"); - BOOST_CHECK_EQUAL(direction, "application_to_control_system"); - BOOST_CHECK_EQUAL(unit, "unit"); - BOOST_CHECK_EQUAL(description, "Some pipe module"); - BOOST_CHECK_EQUAL(numberOfElements, "1"); - } - else { - BOOST_ERROR("Wrong variable name found."); - } - } - - } - else { - BOOST_ERROR("Wrong tag found."); - } + } else { + BOOST_ERROR("Wrong tag found."); } + } - BOOST_CHECK(found_myVarD); - BOOST_CHECK(found_myVarSIn); - BOOST_CHECK(found_myVarSOut); - BOOST_CHECK(found_myVarU8); - + BOOST_CHECK(found_myVarD); + BOOST_CHECK(found_myVarSIn); + BOOST_CHECK(found_myVarSOut); + BOOST_CHECK(found_myVarU8); } diff --git a/tests/executables_src/testBidirectionalVariables.cc b/tests/executables_src/testBidirectionalVariables.cc index 8d35b54f..7eca6666 100644 --- a/tests/executables_src/testBidirectionalVariables.cc +++ b/tests/executables_src/testBidirectionalVariables.cc @@ -9,17 +9,17 @@ #define BOOST_TEST_MODULE testBidirectionalVariables +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> #include <ChimeraTK/BackendFactory.h> #include "Application.h" -#include "ScalarAccessor.h" -#include "ArrayAccessor.h" #include "ApplicationModule.h" +#include "ArrayAccessor.h" #include "ControlSystemModule.h" +#include "ScalarAccessor.h" #include "TestFacility.h" using namespace boost::unit_test_framework; @@ -27,283 +27,295 @@ namespace ctk = ChimeraTK; /*********************************************************************************************************************/ -/* Module which converts the input data from inches to centimeters - and the other way round for the return channel. - * In case of the return channel, the data is rounded downwards to integer inches and sent again forward. */ +/* Module which converts the input data from inches to centimeters - and the + * other way round for the return channel. In case of the return channel, the + * data is rounded downwards to integer inches and sent again forward. */ struct ModuleA : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - - ctk::ScalarPushInputWB<int> var1{this, "var1", "inches", "A length, for some reason rounded to integer"}; - ctk::ScalarOutputPushRB<double> var2{this, "var2", "centimeters", "Same length converted to centimeters"}; - - void mainLoop() { - auto group = readAnyGroup(); - while(true) { - auto var = group.readAny(); - if(var == var2.getId()) { - var1 = std::floor(var2/2.54); - var1.write(); - } - var2 = var1*2.54; - var2.write(); + using ctk::ApplicationModule::ApplicationModule; + + ctk::ScalarPushInputWB<int> var1{ + this, "var1", "inches", "A length, for some reason rounded to integer"}; + ctk::ScalarOutputPushRB<double> var2{this, "var2", "centimeters", + "Same length converted to centimeters"}; + + void mainLoop() { + auto group = readAnyGroup(); + while (true) { + auto var = group.readAny(); + if (var == var2.getId()) { + var1 = std::floor(var2 / 2.54); + var1.write(); } + var2 = var1 * 2.54; + var2.write(); } + } }; /*********************************************************************************************************************/ /* Module which limits a value to stay below a maximum value. */ struct ModuleB : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - - ctk::ScalarPushInputWB<double> var2{this, "var2", "centimeters", "Some length, confined to a configuratble range"}; - ctk::ScalarPushInput<double> max{this, "max", "centimeters", "Maximum length"}; - ctk::ScalarOutput<double> var3{this, "var3", "centimeters", "The limited length"}; - - void mainLoop() { - auto group = readAnyGroup(); - while(true) { - auto var = group.readAny(); - bool write = var == var2.getId(); - if(var2 > max) { - var2 = static_cast<double>(max); - var2.write(); - write = true; - } - if(write) { // write only if var2 was received or the value was changed due to a reduced limit - var3 = static_cast<double>(var2); - var3.write(); - } + using ctk::ApplicationModule::ApplicationModule; + + ctk::ScalarPushInputWB<double> var2{ + this, "var2", "centimeters", + "Some length, confined to a configuratble range"}; + ctk::ScalarPushInput<double> max{this, "max", "centimeters", + "Maximum length"}; + ctk::ScalarOutput<double> var3{this, "var3", "centimeters", + "The limited length"}; + + void mainLoop() { + auto group = readAnyGroup(); + while (true) { + auto var = group.readAny(); + bool write = var == var2.getId(); + if (var2 > max) { + var2 = static_cast<double>(max); + var2.write(); + write = true; + } + if (write) { // write only if var2 was received or the value was changed + // due to a reduced limit + var3 = static_cast<double>(var2); + var3.write(); } } + } }; /*********************************************************************************************************************/ struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") {} - ~TestApplication() { shutdown(); } + TestApplication() : Application("testSuite") {} + ~TestApplication() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // the setup is done in the tests + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // the setup is done in the tests - ctk::ControlSystemModule cs; - ModuleA a; - ModuleB b; + ctk::ControlSystemModule cs; + ModuleA a; + ModuleB b; }; /*********************************************************************************************************************/ BOOST_AUTO_TEST_CASE(testDirectAppToCSConnections) { - ctk::ExperimentalFeatures::enable(); - std::cout << "*** testDirectAppToCSConnections" << std::endl; - - TestApplication app; - app.b = {&app,"b", ""}; - app.b.connectTo(app.cs); - - ctk::TestFacility test; - app.initialise(); - app.run(); - auto var2 = test.getScalar<double>("var2"); - auto var3 = test.getScalar<double>("var3"); - auto max = test.getScalar<double>("max"); - - // set maximum in B - max = 49.5; - max.write(); - test.stepApplication(); - - // inject value which does not get limited - var2 = 49; - var2.write(); - test.stepApplication(); - var3.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 49, 0.001); - BOOST_CHECK( var2.readNonBlocking() == false ); - BOOST_CHECK( var3.readNonBlocking() == false ); - - // inject value which gets limited - var2 = 50; - var2.write(); - test.stepApplication(); - var2.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 49.5, 0.001); - var3.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 49.5, 0.001); - BOOST_CHECK( var2.readNonBlocking() == false ); - BOOST_CHECK( var3.readNonBlocking() == false ); - - // change the limit so the current value gets changed - max = 48.5; - max.write(); - test.stepApplication(); - var2.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 48.5, 0.001); - var3.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 48.5, 0.001); - BOOST_CHECK( var2.readNonBlocking() == false ); - BOOST_CHECK( var3.readNonBlocking() == false ); - + ctk::ExperimentalFeatures::enable(); + std::cout << "*** testDirectAppToCSConnections" << std::endl; + + TestApplication app; + app.b = {&app, "b", ""}; + app.b.connectTo(app.cs); + + ctk::TestFacility test; + app.initialise(); + app.run(); + auto var2 = test.getScalar<double>("var2"); + auto var3 = test.getScalar<double>("var3"); + auto max = test.getScalar<double>("max"); + + // set maximum in B + max = 49.5; + max.write(); + test.stepApplication(); + + // inject value which does not get limited + var2 = 49; + var2.write(); + test.stepApplication(); + var3.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 49, 0.001); + BOOST_CHECK(var2.readNonBlocking() == false); + BOOST_CHECK(var3.readNonBlocking() == false); + + // inject value which gets limited + var2 = 50; + var2.write(); + test.stepApplication(); + var2.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 49.5, 0.001); + var3.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 49.5, 0.001); + BOOST_CHECK(var2.readNonBlocking() == false); + BOOST_CHECK(var3.readNonBlocking() == false); + + // change the limit so the current value gets changed + max = 48.5; + max.write(); + test.stepApplication(); + var2.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 48.5, 0.001); + var3.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 48.5, 0.001); + BOOST_CHECK(var2.readNonBlocking() == false); + BOOST_CHECK(var3.readNonBlocking() == false); } /*********************************************************************************************************************/ BOOST_AUTO_TEST_CASE(testRealisticExample) { - ctk::ExperimentalFeatures::enable(); - std::cout << "*** testRealisticExample" << std::endl; - - TestApplication app; - app.a = {&app,"a", ""}; - app.b = {&app,"b", ""}; - - // the connections will result in a FeedingFanOut for var2, as it is connected to the control system as well - app.a.connectTo(app.cs); - app.b.connectTo(app.cs); - app.a.var1 >> app.cs("var1_copied"); // add a ThreadedFanOut with return channel as well... - ctk::TestFacility test; - app.initialise(); - app.run(); - auto var1 = test.getScalar<int>("var1"); - auto var1_copied = test.getScalar<int>("var1_copied"); - auto var2 = test.getScalar<double>("var2"); - auto var3 = test.getScalar<double>("var3"); - auto max = test.getScalar<double>("max"); - - // set maximum in B, so that var1=49 is still below maximum but var2=50 is already above and rounding in - // ModuleB will change the value again - max = 49.5*2.54; + ctk::ExperimentalFeatures::enable(); + std::cout << "*** testRealisticExample" << std::endl; + + TestApplication app; + app.a = {&app, "a", ""}; + app.b = {&app, "b", ""}; + + // the connections will result in a FeedingFanOut for var2, as it is connected + // to the control system as well + app.a.connectTo(app.cs); + app.b.connectTo(app.cs); + app.a.var1 >> + app.cs( + "var1_copied"); // add a ThreadedFanOut with return channel as well... + ctk::TestFacility test; + app.initialise(); + app.run(); + auto var1 = test.getScalar<int>("var1"); + auto var1_copied = test.getScalar<int>("var1_copied"); + auto var2 = test.getScalar<double>("var2"); + auto var3 = test.getScalar<double>("var3"); + auto max = test.getScalar<double>("max"); + + // set maximum in B, so that var1=49 is still below maximum but var2=50 is + // already above and rounding in ModuleB will change the value again + max = 49.5 * 2.54; + max.write(); + test.stepApplication(); + + // inject value which does not get limited + var1 = 49; + var1.write(); + test.stepApplication(); + var1_copied.read(); + var2.read(); + var3.read(); + BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 49); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 49 * 2.54, 0.001); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 49 * 2.54, 0.001); + BOOST_CHECK(var1.readNonBlocking() == + false); // nothing was sent through the return channel + BOOST_CHECK(var1_copied.readLatest() == false); + BOOST_CHECK(var2.readNonBlocking() == false); + BOOST_CHECK(var3.readNonBlocking() == false); + + // inject value which gets limited + var1 = 50; + var1.write(); + test.stepApplication(); + var1.read(); + BOOST_CHECK_EQUAL(static_cast<int>(var1), 49); + var1_copied.read(); + BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 50); + var1_copied.read(); + BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 49); + var2.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 50 * 2.54, 0.001); + var2.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 49.5 * 2.54, 0.001); + var2.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 49 * 2.54, 0.001); + var3.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 49.5 * 2.54, 0.001); + var3.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 49 * 2.54, 0.001); + BOOST_CHECK(var1.readNonBlocking() == false); + BOOST_CHECK(var1_copied.readLatest() == false); + BOOST_CHECK(var2.readNonBlocking() == false); + BOOST_CHECK(var3.readNonBlocking() == false); + + // change the limit so the current value gets changed + max = 48.5 * 2.54; + max.write(); + test.stepApplication(); + var1.read(); + BOOST_CHECK_EQUAL(static_cast<int>(var1), 48); + var1_copied.read(); + BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 48); + var2.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 48.5 * 2.54, 0.001); + var2.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 48 * 2.54, 0.001); + var3.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 48.5 * 2.54, 0.001); + var3.read(); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 48 * 2.54, 0.001); + BOOST_CHECK(var1.readNonBlocking() == false); + BOOST_CHECK(var1_copied.readLatest() == false); + BOOST_CHECK(var2.readNonBlocking() == false); + BOOST_CHECK(var3.readNonBlocking() == false); + + // Run the following tests a couple of times, as they are testing for the + // absence of race conditions. This makes it more likely to find failures in a + // single run of the test + for (size_t i = 0; i < 10; ++i) { + + // feed in some default values (so the tests can be executed multiple times + // in a row) + max = 48.5 * 2.54; max.write(); test.stepApplication(); - - // inject value which does not get limited - var1 = 49; + var1 = 50; var1.write(); test.stepApplication(); - var1_copied.read(); - var2.read(); - var3.read(); - BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 49); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 49*2.54, 0.001); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 49*2.54, 0.001); - BOOST_CHECK( var1.readNonBlocking() == false ); // nothing was sent through the return channel - BOOST_CHECK( var1_copied.readLatest() == false ); - BOOST_CHECK( var2.readNonBlocking() == false ); - BOOST_CHECK( var3.readNonBlocking() == false ); - - // inject value which gets limited - var1 = 50; + var1.readLatest(); // emtpy the queues + var1_copied.readLatest(); + var2.readLatest(); + var3.readLatest(); + BOOST_CHECK_EQUAL(static_cast<int>(var1), 48); + BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 48); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 48 * 2.54, 0.001); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 48 * 2.54, 0.001); + BOOST_CHECK(var1.readNonBlocking() == false); + BOOST_CHECK(var1_copied.readLatest() == false); + BOOST_CHECK(var2.readNonBlocking() == false); + BOOST_CHECK(var3.readNonBlocking() == false); + + // concurrent change of value and limit. Note: The final result must be + // deterministic, but which values are seen in between is subject to race + // conditions between the two concurrent updates. Thus we are using + // readLatest() in some cases here. + var1 = 30; + max = 25.5 * 2.54; var1.write(); + max.write(); test.stepApplication(); var1.read(); - BOOST_CHECK_EQUAL(static_cast<int>(var1), 49); - var1_copied.read(); - BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 50); + BOOST_CHECK_EQUAL(static_cast<int>(var1), 25); var1_copied.read(); - BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 49); - var2.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 50*2.54, 0.001); - var2.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 49.5*2.54, 0.001); - var2.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 49*2.54, 0.001); - var3.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 49.5*2.54, 0.001); - var3.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 49*2.54, 0.001); - BOOST_CHECK( var1.readNonBlocking() == false ); - BOOST_CHECK( var1_copied.readLatest() == false ); - BOOST_CHECK( var2.readNonBlocking() == false ); - BOOST_CHECK( var3.readNonBlocking() == false ); - - // change the limit so the current value gets changed - max = 48.5*2.54; + BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 30); + BOOST_CHECK(var1_copied.readLatest() == true); + BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 25); + BOOST_CHECK(var2.readLatest() == true); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 25 * 2.54, 0.001); + BOOST_CHECK(var3.readLatest() == true); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 25 * 2.54, 0.001); + BOOST_CHECK(var1.readNonBlocking() == false); + BOOST_CHECK(var1_copied.readLatest() == false); + BOOST_CHECK(var2.readNonBlocking() == false); + BOOST_CHECK(var3.readNonBlocking() == false); + + // concurrent change of value and limit - other order than before + var1 = 15; + max = 20.5 * 2.54; max.write(); + var1.write(); test.stepApplication(); - var1.read(); - BOOST_CHECK_EQUAL(static_cast<int>(var1), 48); var1_copied.read(); - BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 48); - var2.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 48.5*2.54, 0.001); - var2.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 48*2.54, 0.001); - var3.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 48.5*2.54, 0.001); - var3.read(); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 48*2.54, 0.001); - BOOST_CHECK( var1.readNonBlocking() == false ); - BOOST_CHECK( var1_copied.readLatest() == false ); - BOOST_CHECK( var2.readNonBlocking() == false ); - BOOST_CHECK( var3.readNonBlocking() == false ); - - // Run the following tests a couple of times, as they are testing for the absence of race conditions. This makes - // it more likely to find failures in a single run of the test - for(size_t i=0; i<10; ++i) { - - // feed in some default values (so the tests can be executed multiple times in a row) - max = 48.5*2.54; - max.write(); - test.stepApplication(); - var1 = 50; - var1.write(); - test.stepApplication(); - var1.readLatest(); // emtpy the queues - var1_copied.readLatest(); - var2.readLatest(); - var3.readLatest(); - BOOST_CHECK_EQUAL(static_cast<int>(var1), 48); - BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 48); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 48*2.54, 0.001); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 48*2.54, 0.001); - BOOST_CHECK( var1.readNonBlocking() == false ); - BOOST_CHECK( var1_copied.readLatest() == false ); - BOOST_CHECK( var2.readNonBlocking() == false ); - BOOST_CHECK( var3.readNonBlocking() == false ); - - // concurrent change of value and limit. Note: The final result must be deterministic, but which values are seen - // in between is subject to race conditions between the two concurrent updates. Thus we are using readLatest() in - // some cases here. - var1 = 30; - max = 25.5*2.54; - var1.write(); - max.write(); - test.stepApplication(); - var1.read(); - BOOST_CHECK_EQUAL(static_cast<int>(var1), 25); - var1_copied.read(); - BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 30); - BOOST_CHECK( var1_copied.readLatest() == true ); - BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 25); - BOOST_CHECK( var2.readLatest() == true ); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 25*2.54, 0.001); - BOOST_CHECK( var3.readLatest() == true ); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 25*2.54, 0.001); - BOOST_CHECK( var1.readNonBlocking() == false ); - BOOST_CHECK( var1_copied.readLatest() == false ); - BOOST_CHECK( var2.readNonBlocking() == false ); - BOOST_CHECK( var3.readNonBlocking() == false ); - - // concurrent change of value and limit - other order than before - var1 = 15; - max = 20.5*2.54; - max.write(); - var1.write(); - test.stepApplication(); - var1_copied.read(); - BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 15); - BOOST_CHECK( var2.readLatest() == true ); - BOOST_CHECK_CLOSE(static_cast<double>(var2), 15*2.54, 0.001); - BOOST_CHECK( var3.readLatest() == true ); - BOOST_CHECK_CLOSE(static_cast<double>(var3), 15*2.54, 0.001); - BOOST_CHECK( var1.readNonBlocking() == false ); - BOOST_CHECK( var1_copied.readLatest() == false ); - BOOST_CHECK( var2.readNonBlocking() == false ); - BOOST_CHECK( var3.readNonBlocking() == false ); - - } - + BOOST_CHECK_EQUAL(static_cast<int>(var1_copied), 15); + BOOST_CHECK(var2.readLatest() == true); + BOOST_CHECK_CLOSE(static_cast<double>(var2), 15 * 2.54, 0.001); + BOOST_CHECK(var3.readLatest() == true); + BOOST_CHECK_CLOSE(static_cast<double>(var3), 15 * 2.54, 0.001); + BOOST_CHECK(var1.readNonBlocking() == false); + BOOST_CHECK(var1_copied.readLatest() == false); + BOOST_CHECK(var2.readNonBlocking() == false); + BOOST_CHECK(var3.readNonBlocking() == false); + } } /*********************************************************************************************************************/ - diff --git a/tests/executables_src/testConfigReader.cc b/tests/executables_src/testConfigReader.cc index fa1c8306..2c84fe2e 100644 --- a/tests/executables_src/testConfigReader.cc +++ b/tests/executables_src/testConfigReader.cc @@ -18,7 +18,8 @@ namespace ctk = ChimeraTK; /*********************************************************************************************************************/ /* Module to receive the config values */ -struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::ApplicationModule; +struct TestModule : ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; ctk::ScalarPushInput<int8_t> var8{this, "var8", "MV/m", "Desc"}; ctk::ScalarPushInput<uint8_t> var8u{this, "var8u", "MV/m", "Desc"}; @@ -30,10 +31,13 @@ struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::Appli ctk::ScalarPushInput<uint64_t> var64u{this, "var64u", "MV/m", "Desc"}; ctk::ScalarPushInput<float> varFloat{this, "varFloat", "MV/m", "Desc"}; ctk::ScalarPushInput<double> varDouble{this, "varDouble", "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<std::string> varString{this, "varString", "MV/m", + "Desc"}; + ctk::ScalarPushInput<int32_t> varAnotherInt{this, "varAnotherInt", "MV/m", + "Desc"}; ctk::ArrayPushInput<int32_t> intArray{this, "intArray", "MV/m", 10, "Desc"}; - ctk::ArrayPushInput<std::string> stringArray{this, "stringArray", "", 8, "Desc"}; + ctk::ArrayPushInput<std::string> stringArray{this, "stringArray", "", 8, + "Desc"}; std::atomic<bool> done{false}; @@ -53,50 +57,54 @@ struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::Appli 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); + for (size_t i = 0; i < 10; ++i) + BOOST_CHECK_EQUAL(intArray[i], 10 - i); BOOST_CHECK_EQUAL(stringArray.getNElements(), 8); - for(size_t i=0; i<8; ++i) BOOST_CHECK_EQUAL(stringArray[i], "Hallo"+std::to_string(i+1)); + for (size_t i = 0; i < 8; ++i) + BOOST_CHECK_EQUAL(stringArray[i], "Hallo" + std::to_string(i + 1)); // no further update shall be received - usleep(1000000); // 1 second - BOOST_CHECK( !var8.readNonBlocking() ); - BOOST_CHECK( !var8u.readNonBlocking() ); - BOOST_CHECK( !var16.readNonBlocking() ); - BOOST_CHECK( !var16u.readNonBlocking() ); - BOOST_CHECK( !var32.readNonBlocking() ); - BOOST_CHECK( !var32u.readNonBlocking() ); - BOOST_CHECK( !var64.readNonBlocking() ); - BOOST_CHECK( !var64u.readNonBlocking() ); - BOOST_CHECK( !varFloat.readNonBlocking() ); - BOOST_CHECK( !varDouble.readNonBlocking() ); - BOOST_CHECK( !varString.readNonBlocking() ); - BOOST_CHECK( !intArray.readNonBlocking() ); + usleep(1000000); // 1 second + BOOST_CHECK(!var8.readNonBlocking()); + BOOST_CHECK(!var8u.readNonBlocking()); + BOOST_CHECK(!var16.readNonBlocking()); + BOOST_CHECK(!var16u.readNonBlocking()); + BOOST_CHECK(!var32.readNonBlocking()); + BOOST_CHECK(!var32u.readNonBlocking()); + BOOST_CHECK(!var64.readNonBlocking()); + BOOST_CHECK(!var64u.readNonBlocking()); + BOOST_CHECK(!varFloat.readNonBlocking()); + BOOST_CHECK(!varDouble.readNonBlocking()); + BOOST_CHECK(!varString.readNonBlocking()); + BOOST_CHECK(!intArray.readNonBlocking()); // inform main thread that we are done done = true; } - }; /*********************************************************************************************************************/ /* dummy application */ struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") {} - ~TestApplication() { shutdown(); } + TestApplication() : Application("testSuite") {} + ~TestApplication() { shutdown(); } - void defineConnections() {} // the setup is done in the tests + void defineConnections() {} // the setup is done in the tests - ctk::ConfigReader config{this, "config", "validConfig.xml", {"MyTAG"}}; - TestModule testModule{this, "TestModule", "The test module"}; + ctk::ConfigReader config{this, "config", "validConfig.xml", {"MyTAG"}}; + TestModule testModule{this, "TestModule", "The test module"}; }; /*********************************************************************************************************************/ -/* test trigger by app variable when connecting a polled device register to an app variable */ +/* test trigger by app variable when connecting a polled device register to an + * app variable */ -BOOST_AUTO_TEST_CASE( testConfigReader ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testConfigReader) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testConfigReader" << std::endl; TestApplication app; @@ -112,15 +120,19 @@ BOOST_AUTO_TEST_CASE( testConfigReader ) { BOOST_CHECK_EQUAL(app.config.get<uint64_t>("var64u"), 12345678901234567890U); BOOST_CHECK_CLOSE(app.config.get<float>("varFloat"), 3.1415, 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); + for (size_t i = 0; i < 10; ++i) + BOOST_CHECK_EQUAL(arrayValue[i], 10 - i); - std::vector<std::string> arrayValueString = app.config.get<std::vector<std::string>>("stringArray"); + std::vector<std::string> arrayValueString = + app.config.get<std::vector<std::string>>("stringArray"); BOOST_CHECK_EQUAL(arrayValueString.size(), 8); - for(size_t i=0; i<8; ++i) BOOST_CHECK_EQUAL(arrayValueString[i], "Hallo"+std::to_string(i+1)); + for (size_t i = 0; i < 8; ++i) + BOOST_CHECK_EQUAL(arrayValueString[i], "Hallo" + std::to_string(i + 1)); app.config.connectTo(app.testModule); @@ -128,7 +140,6 @@ BOOST_AUTO_TEST_CASE( testConfigReader ) { app.run(); // wait until tests in TestModule::mainLoop() are complete - while(app.testModule.done == false) usleep(10000); - + while (app.testModule.done == false) + usleep(10000); } - diff --git a/tests/executables_src/testConnectTo.cc b/tests/executables_src/testConnectTo.cc index 3f1013ea..5276fdd0 100644 --- a/tests/executables_src/testConnectTo.cc +++ b/tests/executables_src/testConnectTo.cc @@ -5,21 +5,21 @@ * Author: Martin Hierholzer */ -#include <future> #include <chrono> +#include <future> #define BOOST_TEST_MODULE testConnectTo +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> #include <boost/thread.hpp> #include "Application.h" -#include "ScalarAccessor.h" #include "ApplicationModule.h" -#include "VariableGroup.h" #include "ModuleGroup.h" +#include "ScalarAccessor.h" +#include "VariableGroup.h" #include "VirtualModule.h" using namespace boost::unit_test_framework; @@ -28,11 +28,14 @@ namespace ctk = ChimeraTK; /*********************************************************************************************************************/ /* Build first hierarchy */ -struct FirstHierarchy : ctk::ModuleGroup { using ctk::ModuleGroup::ModuleGroup; +struct FirstHierarchy : ctk::ModuleGroup { + using ctk::ModuleGroup::ModuleGroup; - struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::ApplicationModule; + struct TestModule : ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; - struct VarGroup : ctk::VariableGroup { using ctk::VariableGroup::VariableGroup; + struct VarGroup : ctk::VariableGroup { + using ctk::VariableGroup::VariableGroup; ctk::ScalarPushInput<int> varA{this, "varA", "MV/m", "Desc"}; ctk::ScalarPushInput<int> varB{this, "varB", "MV/m", "Desc"}; ctk::ScalarOutput<int> varC{this, "varC", "MV/m", "Desc"}; @@ -45,12 +48,15 @@ struct FirstHierarchy : ctk::ModuleGroup { using ctk::ModuleGroup::ModuleGroup; } testModule{this, "TestModule", ""}; struct SecondModule : ctk::ApplicationModule { - SecondModule(EntityOwner *owner, const std::string &name, const std::string &description) - : ctk::ApplicationModule(owner, name, description) - { - for(size_t i=0; i<22; ++i) myVec.emplace_back(this, "Var"+std::to_string(i), "Unit", "Foo"); + SecondModule(EntityOwner *owner, const std::string &name, + const std::string &description) + : ctk::ApplicationModule(owner, name, description) { + for (size_t i = 0; i < 22; ++i) + myVec.emplace_back(this, "Var" + std::to_string(i), "Unit", "Foo"); } - SecondModule() { throw; } // work around for gcc bug: constructor must be present but is unused + SecondModule() { + throw; + } // work around for gcc bug: constructor must be present but is unused std::vector<ctk::ScalarPushInput<int>> myVec; @@ -62,11 +68,14 @@ struct FirstHierarchy : ctk::ModuleGroup { using ctk::ModuleGroup::ModuleGroup; /*********************************************************************************************************************/ /* Build second hierarchy */ -struct SecondHierarchy : ctk::ModuleGroup { using ctk::ModuleGroup::ModuleGroup; +struct SecondHierarchy : ctk::ModuleGroup { + using ctk::ModuleGroup::ModuleGroup; - struct TestModule : ctk::ApplicationModule { using ctk::ApplicationModule::ApplicationModule; + struct TestModule : ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; - struct VarGroup : ctk::VariableGroup { using ctk::VariableGroup::VariableGroup; + struct VarGroup : ctk::VariableGroup { + using ctk::VariableGroup::VariableGroup; ctk::ScalarOutput<int> varA{this, "varA", "MV/m", "Desc"}; ctk::ScalarPushInput<int> varC{this, "varC", "MV/m", "Desc"}; ctk::ScalarPushInput<int> varD{this, "varD", "MV/m", "Desc"}; @@ -75,25 +84,34 @@ struct SecondHierarchy : ctk::ModuleGroup { using ctk::ModuleGroup::ModuleGroup; ctk::ScalarPushInput<int> extraVar{this, "extraVar", "MV/m", "Desc"}; ctk::ScalarOutput<int> varA{this, "varA", "MV/m", "Desc"}; - struct EliminatedGroup : ctk::VariableGroup { using ctk::VariableGroup::VariableGroup; + struct EliminatedGroup : ctk::VariableGroup { + using ctk::VariableGroup::VariableGroup; ctk::ScalarPushInput<int> varX{this, "varX", "MV/m", "Desc"}; - struct VarGroup : ctk::VariableGroup { using ctk::VariableGroup::VariableGroup; + struct VarGroup : ctk::VariableGroup { + using ctk::VariableGroup::VariableGroup; ctk::ScalarOutput<int> varB{this, "varB", "MV/m", "Desc"}; - } varGroup{this, "VarGroup", "This group shall be merged with testModule.varGroup in connectTo()"}; - } eliminatedGroup{this, "eliminatedGroup", "A group whose hierarchy gets eliminated", true}; + } varGroup{ + this, "VarGroup", + "This group shall be merged with testModule.varGroup in connectTo()"}; + } eliminatedGroup{this, "eliminatedGroup", + "A group whose hierarchy gets eliminated", true}; void mainLoop() {} } testModule{this, "TestModule", ""}; struct SecondModule : ctk::ApplicationModule { - SecondModule(EntityOwner *owner, const std::string &name, const std::string &description) - : ctk::ApplicationModule(owner, name, description) - { - for(size_t i=0; i<22; ++i) myVec.emplace_back(this, "Var"+std::to_string(i), "Unit", "Foo"); + SecondModule(EntityOwner *owner, const std::string &name, + const std::string &description) + : ctk::ApplicationModule(owner, name, description) { + for (size_t i = 0; i < 22; ++i) + myVec.emplace_back(this, "Var" + std::to_string(i), "Unit", "Foo"); } - SecondModule() { throw; } // work around for gcc bug: constructor must be present but is unused + SecondModule() { + throw; + } // work around for gcc bug: constructor must be present but is unused - struct ExtraGroup : ctk::VariableGroup { using ctk::VariableGroup::VariableGroup; + struct ExtraGroup : ctk::VariableGroup { + using ctk::VariableGroup::VariableGroup; ctk::ScalarOutput<int> varA{this, "varA", "MV/m", "Desc"}; } extraGroup{this, "ExtraGroup", "A group"}; @@ -108,21 +126,24 @@ struct SecondHierarchy : ctk::ModuleGroup { using ctk::ModuleGroup::ModuleGroup; /* dummy application */ struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") {} - ~TestApplication() { shutdown(); } + TestApplication() : Application("testSuite") {} + ~TestApplication() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // the setup is done in the tests + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // the setup is done in the tests - FirstHierarchy first{this, "first", "The test module"}; - SecondHierarchy second{this, "second", "The test module"}; + FirstHierarchy first{this, "first", "The test module"}; + SecondHierarchy second{this, "second", "The test module"}; }; /*********************************************************************************************************************/ /* test connectTo() */ -BOOST_AUTO_TEST_CASE( testConnectTo ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testConnectTo) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testConnectTo" << std::endl; TestApplication app; @@ -137,8 +158,8 @@ BOOST_AUTO_TEST_CASE( testConnectTo ) { app.first.testModule.varGroup.varC = 3; app.second.testModule.varA = 4; app.first.testModule.varX = 5; - for(int i=0; i<22; ++i) { - app.second.secondModule.myVec[i] = 6+i; + for (int i = 0; i < 22; ++i) { + app.second.secondModule.myVec[i] = 6 + i; } app.first.testModule.writeAll(); @@ -150,22 +171,23 @@ BOOST_AUTO_TEST_CASE( testConnectTo ) { app.second.testModule.readAllLatest(); app.second.secondModule.readAllLatest(); - BOOST_CHECK_EQUAL( (int) app.first.testModule.varGroup.varA, 1 ); - BOOST_CHECK_EQUAL( (int) app.first.testModule.varGroup.varB, 2 ); - BOOST_CHECK_EQUAL( (int) app.second.testModule.varGroup.varC, 3 ); - BOOST_CHECK_EQUAL( (int) app.first.testModule.varA, 4 ); - BOOST_CHECK_EQUAL( (int) app.second.testModule.eliminatedGroup.varX, 5 ); - for(int i=0; i<22; ++i) { - BOOST_CHECK_EQUAL( (int) app.first.secondModule.myVec[i], 6+i ); + BOOST_CHECK_EQUAL((int)app.first.testModule.varGroup.varA, 1); + BOOST_CHECK_EQUAL((int)app.first.testModule.varGroup.varB, 2); + BOOST_CHECK_EQUAL((int)app.second.testModule.varGroup.varC, 3); + BOOST_CHECK_EQUAL((int)app.first.testModule.varA, 4); + BOOST_CHECK_EQUAL((int)app.second.testModule.eliminatedGroup.varX, 5); + for (int i = 0; i < 22; ++i) { + BOOST_CHECK_EQUAL((int)app.first.secondModule.myVec[i], 6 + i); } - } /*********************************************************************************************************************/ /* check if makeing the same connection twice does not fail */ -BOOST_AUTO_TEST_CASE( testConnectTwice ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testConnectTwice) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testConnectTwice" << std::endl; TestApplication app; @@ -181,8 +203,8 @@ BOOST_AUTO_TEST_CASE( testConnectTwice ) { app.first.testModule.varGroup.varC = 3; app.second.testModule.varA = 4; app.first.testModule.varX = 5; - for(int i=0; i<22; ++i) { - app.second.secondModule.myVec[i] = 6+i; + for (int i = 0; i < 22; ++i) { + app.second.secondModule.myVec[i] = 6 + i; } app.first.testModule.writeAll(); @@ -194,12 +216,12 @@ BOOST_AUTO_TEST_CASE( testConnectTwice ) { app.second.testModule.readAllLatest(); app.second.secondModule.readAllLatest(); - BOOST_CHECK_EQUAL( (int) app.first.testModule.varGroup.varA, 1 ); - BOOST_CHECK_EQUAL( (int) app.first.testModule.varGroup.varB, 2 ); - BOOST_CHECK_EQUAL( (int) app.second.testModule.varGroup.varC, 3 ); - BOOST_CHECK_EQUAL( (int) app.first.testModule.varA, 4 ); - BOOST_CHECK_EQUAL( (int) app.second.testModule.eliminatedGroup.varX, 5 ); - for(int i=0; i<22; ++i) { - BOOST_CHECK_EQUAL( (int) app.first.secondModule.myVec[i], 6+i ); + BOOST_CHECK_EQUAL((int)app.first.testModule.varGroup.varA, 1); + BOOST_CHECK_EQUAL((int)app.first.testModule.varGroup.varB, 2); + BOOST_CHECK_EQUAL((int)app.second.testModule.varGroup.varC, 3); + BOOST_CHECK_EQUAL((int)app.first.testModule.varA, 4); + BOOST_CHECK_EQUAL((int)app.second.testModule.eliminatedGroup.varX, 5); + for (int i = 0; i < 22; ++i) { + BOOST_CHECK_EQUAL((int)app.first.secondModule.myVec[i], 6 + i); } } diff --git a/tests/executables_src/testControlSystemAccessors.cc b/tests/executables_src/testControlSystemAccessors.cc index 8f78b036..be5ff7dc 100644 --- a/tests/executables_src/testControlSystemAccessors.cc +++ b/tests/executables_src/testControlSystemAccessors.cc @@ -9,78 +9,80 @@ #define BOOST_TEST_MODULE testControlSystemAccessors +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> #include <ChimeraTK/BackendFactory.h> -#include <ChimeraTK/Device.h> -#include <ChimeraTK/ControlSystemAdapter/PVManager.h> #include <ChimeraTK/ControlSystemAdapter/ControlSystemPVManager.h> #include <ChimeraTK/ControlSystemAdapter/DevicePVManager.h> +#include <ChimeraTK/ControlSystemAdapter/PVManager.h> +#include <ChimeraTK/Device.h> #include "Application.h" -#include "ScalarAccessor.h" #include "ApplicationModule.h" #include "ControlSystemModule.h" #include "DeviceModule.h" +#include "ScalarAccessor.h" using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; // list of user types the accessors are tested with -typedef boost::mpl::list<int8_t,uint8_t, - int16_t,uint16_t, - int32_t,uint32_t, - float,double> test_types; - -#define CHECK_TIMEOUT(condition, maxMilliseconds) \ - { \ - std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \ - while(!(condition)) { \ - bool timeout_reached = (std::chrono::steady_clock::now()-t0) > std::chrono::milliseconds(maxMilliseconds); \ - BOOST_CHECK( !timeout_reached ); \ - if(timeout_reached) break; \ - usleep(1000); \ - } \ - } +typedef boost::mpl::list<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + float, double> + test_types; + +#define CHECK_TIMEOUT(condition, maxMilliseconds) \ + { \ + std::chrono::steady_clock::time_point t0 = \ + std::chrono::steady_clock::now(); \ + while (!(condition)) { \ + bool timeout_reached = (std::chrono::steady_clock::now() - t0) > \ + std::chrono::milliseconds(maxMilliseconds); \ + BOOST_CHECK(!timeout_reached); \ + if (timeout_reached) \ + break; \ + usleep(1000); \ + } \ + } /*********************************************************************************************************************/ /* the ApplicationModule for the test is a template of the user type */ -template<typename T> -struct TestModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; +template <typename T> struct TestModule : public ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; - ctk::ScalarPushInput<T> consumer{this, "consumer", "", "No comment."}; - ctk::ScalarOutput<T> feeder{this, "feeder", "MV/m", "Some fancy explanation about this variable"}; + ctk::ScalarPushInput<T> consumer{this, "consumer", "", "No comment."}; + ctk::ScalarOutput<T> feeder{this, "feeder", "MV/m", + "Some fancy explanation about this variable"}; - void mainLoop() {} + void mainLoop() {} }; /*********************************************************************************************************************/ /* dummy application */ -template<typename T> -struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") { - ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); - } - ~TestApplication() { shutdown(); } +template <typename T> struct TestApplication : public ctk::Application { + TestApplication() : Application("testSuite") { + ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); + } + ~TestApplication() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // the setup is done in the tests + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // the setup is done in the tests - TestModule<T> testModule{this, "TestModule", "The test module"}; - ctk::ControlSystemModule cs; + TestModule<T> testModule{this, "TestModule", "The test module"}; + ctk::ControlSystemModule cs; - ctk::DeviceModule dev{"Dummy0"}; + ctk::DeviceModule dev{"Dummy0"}; }; /*********************************************************************************************************************/ /* test feeding a scalar to the control system adapter */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testFeedToCS, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testFeedToCS, T, test_types) { TestApplication<T> app; @@ -92,9 +94,10 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testFeedToCS, T, test_types ) { BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 1); auto myFeeder = pvManagers.first->getProcessArray<T>("/myFeeder"); - BOOST_CHECK( myFeeder->getName() == "/myFeeder" ); - BOOST_CHECK( myFeeder->getUnit() == "MV/m" ); - BOOST_CHECK( myFeeder->getDescription() == "The test module - Some fancy explanation about this variable" ); + BOOST_CHECK(myFeeder->getName() == "/myFeeder"); + BOOST_CHECK(myFeeder->getUnit() == "MV/m"); + BOOST_CHECK(myFeeder->getDescription() == + "The test module - Some fancy explanation about this variable"); app.testModule.feeder = 42; BOOST_CHECK_EQUAL(myFeeder->readNonBlocking(), false); @@ -109,13 +112,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testFeedToCS, T, test_types ) { BOOST_CHECK_EQUAL(myFeeder->readNonBlocking(), true); BOOST_CHECK_EQUAL(myFeeder->readNonBlocking(), false); BOOST_CHECK(myFeeder->accessData(0) == 120); - } /*********************************************************************************************************************/ /* test consuming a scalar from the control system adapter */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumeFromCS, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testConsumeFromCS, T, test_types) { TestApplication<T> app; @@ -127,9 +129,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumeFromCS, T, test_types ) { BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 1); auto myConsumer = pvManagers.first->getProcessArray<T>("/myConsumer"); - BOOST_CHECK( myConsumer->getName() == "/myConsumer" ); - BOOST_CHECK( myConsumer->getUnit() == "" ); - BOOST_CHECK( myConsumer->getDescription() == "The test module - No comment." ); + BOOST_CHECK(myConsumer->getName() == "/myConsumer"); + BOOST_CHECK(myConsumer->getUnit() == ""); + BOOST_CHECK(myConsumer->getDescription() == "The test module - No comment."); myConsumer->accessData(0) = 42; myConsumer->write(); @@ -140,13 +142,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumeFromCS, T, test_types ) { myConsumer->write(); app.testModule.consumer.read(); BOOST_CHECK(app.testModule.consumer == 120); - } /*********************************************************************************************************************/ /* test multiple publications of the same variable */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testMultiplePublications, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testMultiplePublications, T, test_types) { TestApplication<T> app; @@ -158,7 +159,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testMultiplePublications, T, test_types ) { app.testModule.feeder >> app.cs("myFeeder2"); app.testModule.feeder >> app.cs("myFeeder3"); app.initialise(); - app.run(); // make the connections and start the FanOut threads + app.run(); // make the connections and start the FanOut threads BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 4); auto myFeeder0 = pvManagers.first->getProcessArray<T>("/myFeeder0"); @@ -166,21 +167,25 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testMultiplePublications, T, test_types ) { auto myFeeder2 = pvManagers.first->getProcessArray<T>("/myFeeder2"); auto myFeeder3 = pvManagers.first->getProcessArray<T>("/myFeeder3"); - BOOST_CHECK( myFeeder0->getName() == "/myFeeder0" ); - BOOST_CHECK( myFeeder0->getUnit() == "MV/m" ); - BOOST_CHECK( myFeeder0->getDescription() == "The test module - Some fancy explanation about this variable" ); + BOOST_CHECK(myFeeder0->getName() == "/myFeeder0"); + BOOST_CHECK(myFeeder0->getUnit() == "MV/m"); + BOOST_CHECK(myFeeder0->getDescription() == + "The test module - Some fancy explanation about this variable"); - BOOST_CHECK( myFeeder1->getName() == "/myFeeder1" ); - BOOST_CHECK( myFeeder1->getUnit() == "MV/m" ); - BOOST_CHECK( myFeeder1->getDescription() == "The test module - Some fancy explanation about this variable" ); + BOOST_CHECK(myFeeder1->getName() == "/myFeeder1"); + BOOST_CHECK(myFeeder1->getUnit() == "MV/m"); + BOOST_CHECK(myFeeder1->getDescription() == + "The test module - Some fancy explanation about this variable"); - BOOST_CHECK( myFeeder2->getName() == "/myFeeder2" ); - BOOST_CHECK( myFeeder2->getUnit() == "MV/m" ); - BOOST_CHECK( myFeeder2->getDescription() == "The test module - Some fancy explanation about this variable" ); + BOOST_CHECK(myFeeder2->getName() == "/myFeeder2"); + BOOST_CHECK(myFeeder2->getUnit() == "MV/m"); + BOOST_CHECK(myFeeder2->getDescription() == + "The test module - Some fancy explanation about this variable"); - BOOST_CHECK( myFeeder3->getName() == "/myFeeder3" ); - BOOST_CHECK( myFeeder3->getUnit() == "MV/m" ); - BOOST_CHECK( myFeeder3->getDescription() == "The test module - Some fancy explanation about this variable" ); + BOOST_CHECK(myFeeder3->getName() == "/myFeeder3"); + BOOST_CHECK(myFeeder3->getUnit() == "MV/m"); + BOOST_CHECK(myFeeder3->getDescription() == + "The test module - Some fancy explanation about this variable"); app.testModule.feeder = 42; BOOST_CHECK(myFeeder0->readNonBlocking() == false); @@ -241,13 +246,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testMultiplePublications, T, test_types ) { BOOST_CHECK(myFeeder1->readNonBlocking() == false); BOOST_CHECK(myFeeder2->readNonBlocking() == false); BOOST_CHECK(myFeeder3->readNonBlocking() == false); - } /*********************************************************************************************************************/ /* test multiple re-publications of a variable fed from the control system */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testMultipleRePublications, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testMultipleRePublications, T, test_types) { TestApplication<T> app; @@ -259,29 +263,35 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testMultipleRePublications, T, test_types ) { app.testModule.consumer >> app.cs("myConsumer_copy2"); app.testModule.consumer >> app.cs("myConsumer_copy3"); app.initialise(); - app.run(); // make the connections and start the FanOut threads + app.run(); // make the connections and start the FanOut threads BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 4); auto myConsumer = pvManagers.first->getProcessArray<T>("/myConsumer"); - auto myConsumer_copy1 = pvManagers.first->getProcessArray<T>("/myConsumer_copy1"); - auto myConsumer_copy2 = pvManagers.first->getProcessArray<T>("/myConsumer_copy2"); - auto myConsumer_copy3 = pvManagers.first->getProcessArray<T>("/myConsumer_copy3"); - - BOOST_CHECK( myConsumer->getName() == "/myConsumer" ); - BOOST_CHECK( myConsumer->getUnit() == "" ); - BOOST_CHECK( myConsumer->getDescription() == "The test module - No comment." ); - - BOOST_CHECK( myConsumer_copy1->getName() == "/myConsumer_copy1" ); - BOOST_CHECK( myConsumer_copy1->getUnit() == "" ); - BOOST_CHECK( myConsumer_copy1->getDescription() == "The test module - No comment." ); - - BOOST_CHECK( myConsumer_copy2->getName() == "/myConsumer_copy2" ); - BOOST_CHECK( myConsumer_copy2->getUnit() == "" ); - BOOST_CHECK( myConsumer_copy2->getDescription() == "The test module - No comment." ); - - BOOST_CHECK( myConsumer_copy3->getName() == "/myConsumer_copy3" ); - BOOST_CHECK( myConsumer_copy3->getUnit() == "" ); - BOOST_CHECK( myConsumer_copy3->getDescription() == "The test module - No comment." ); + auto myConsumer_copy1 = + pvManagers.first->getProcessArray<T>("/myConsumer_copy1"); + auto myConsumer_copy2 = + pvManagers.first->getProcessArray<T>("/myConsumer_copy2"); + auto myConsumer_copy3 = + pvManagers.first->getProcessArray<T>("/myConsumer_copy3"); + + BOOST_CHECK(myConsumer->getName() == "/myConsumer"); + BOOST_CHECK(myConsumer->getUnit() == ""); + BOOST_CHECK(myConsumer->getDescription() == "The test module - No comment."); + + BOOST_CHECK(myConsumer_copy1->getName() == "/myConsumer_copy1"); + BOOST_CHECK(myConsumer_copy1->getUnit() == ""); + BOOST_CHECK(myConsumer_copy1->getDescription() == + "The test module - No comment."); + + BOOST_CHECK(myConsumer_copy2->getName() == "/myConsumer_copy2"); + BOOST_CHECK(myConsumer_copy2->getUnit() == ""); + BOOST_CHECK(myConsumer_copy2->getDescription() == + "The test module - No comment."); + + BOOST_CHECK(myConsumer_copy3->getName() == "/myConsumer_copy3"); + BOOST_CHECK(myConsumer_copy3->getUnit() == ""); + BOOST_CHECK(myConsumer_copy3->getDescription() == + "The test module - No comment."); myConsumer->accessData(0) = 42; BOOST_CHECK(myConsumer_copy1->readNonBlocking() == false); @@ -336,13 +346,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testMultipleRePublications, T, test_types ) { BOOST_CHECK(myConsumer_copy3->readNonBlocking() == false); app.testModule.consumer.read(); BOOST_CHECK(app.testModule.consumer == 120); - } /*********************************************************************************************************************/ /* test direct control system to control system connections */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testDirectCStoCS, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testDirectCStoCS, T, test_types) { TestApplication<T> app; @@ -355,23 +364,22 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testDirectCStoCS, T, test_types ) { BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 2); auto mySender = pvManagers.first->getProcessArray<T>("/mySender"); - BOOST_CHECK( mySender->getName() == "/mySender" ); + BOOST_CHECK(mySender->getName() == "/mySender"); auto myReceiver = pvManagers.first->getProcessArray<T>("/myReceiver"); - BOOST_CHECK( myReceiver->getName() == "/myReceiver" ); + BOOST_CHECK(myReceiver->getName() == "/myReceiver"); mySender->accessData(0) = 22; mySender->write(); myReceiver->read(); - BOOST_CHECK_EQUAL( myReceiver->accessData(0), 22 ); + BOOST_CHECK_EQUAL(myReceiver->accessData(0), 22); mySender->accessData(0) = 23; mySender->write(); myReceiver->read(); - BOOST_CHECK_EQUAL( myReceiver->accessData(0), 23 ); + BOOST_CHECK_EQUAL(myReceiver->accessData(0), 23); mySender->accessData(0) = 24; mySender->write(); myReceiver->read(); - BOOST_CHECK_EQUAL( myReceiver->accessData(0), 24 ); - + BOOST_CHECK_EQUAL(myReceiver->accessData(0), 24); } diff --git a/tests/executables_src/testDeviceAccessors.cc b/tests/executables_src/testDeviceAccessors.cc index a49d427c..29ebf9b2 100644 --- a/tests/executables_src/testDeviceAccessors.cc +++ b/tests/executables_src/testDeviceAccessors.cc @@ -9,80 +9,87 @@ #define BOOST_TEST_MODULE testDeviceAccessors +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> -#include <ChimeraTK/Device.h> #include <ChimeraTK/BackendFactory.h> +#include <ChimeraTK/Device.h> #include <ChimeraTK/NDRegisterAccessor.h> #include "Application.h" -#include "ScalarAccessor.h" #include "ApplicationModule.h" #include "DeviceModule.h" +#include "ScalarAccessor.h" using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; // list of user types the accessors are tested with -typedef boost::mpl::list<int8_t,uint8_t, - int16_t,uint16_t, - int32_t,uint32_t, - float,double> test_types; - -#define CHECK_TIMEOUT(condition, maxMilliseconds) \ - { \ - std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \ - while(!(condition)) { \ - bool timeout_reached = (std::chrono::steady_clock::now()-t0) > std::chrono::milliseconds(maxMilliseconds); \ - BOOST_CHECK( !timeout_reached ); \ - if(timeout_reached) break; \ - usleep(1000); \ - } \ - } +typedef boost::mpl::list<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + float, double> + test_types; + +#define CHECK_TIMEOUT(condition, maxMilliseconds) \ + { \ + std::chrono::steady_clock::time_point t0 = \ + std::chrono::steady_clock::now(); \ + while (!(condition)) { \ + bool timeout_reached = (std::chrono::steady_clock::now() - t0) > \ + std::chrono::milliseconds(maxMilliseconds); \ + BOOST_CHECK(!timeout_reached); \ + if (timeout_reached) \ + break; \ + usleep(1000); \ + } \ + } /*********************************************************************************************************************/ /* the ApplicationModule for the test is a template of the user type */ -template<typename T> -struct TestModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; +template <typename T> struct TestModule : public ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; - ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", "Description"}; + ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", + "Description"}; - ctk::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", "Description"}; - ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", "Description"}; + ctk::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", + "Description"}; + ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", + "Description"}; - ctk::ScalarOutput<T> feedingToDevice{this, "feedingToDevice", "MV/m", "Description"}; + ctk::ScalarOutput<T> feedingToDevice{this, "feedingToDevice", "MV/m", + "Description"}; - void mainLoop() {} + void mainLoop() {} }; /*********************************************************************************************************************/ /* dummy application */ -template<typename T> -struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") {} - ~TestApplication() { shutdown(); } +template <typename T> struct TestApplication : public ctk::Application { + TestApplication() : Application("testSuite") {} + ~TestApplication() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - using Application::deviceMap; // expose the device map for the tests - using Application::networkList; // expose network list to check merging networks - void defineConnections() {} // the setup is done in the tests + using Application::deviceMap; // expose the device map for the tests + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + using Application::networkList; // expose network list to check merging + // networks + void defineConnections() {} // the setup is done in the tests - TestModule<T> testModule{this,"testModule", "The test module"}; - ctk::DeviceModule devMymodule{"Dummy0","MyModule"}; - ctk::DeviceModule dev{"Dummy0"}; + TestModule<T> testModule{this, "testModule", "The test module"}; + ctk::DeviceModule devMymodule{"Dummy0", "MyModule"}; + ctk::DeviceModule dev{"Dummy0"}; - // note: direct device-to-controlsystem connections are tested in testControlSystemAccessors! + // note: direct device-to-controlsystem connections are tested in + // testControlSystemAccessors! }; /*********************************************************************************************************************/ /* test feeding a scalar to a device */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testFeedToDevice, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testFeedToDevice, T, test_types) { std::cout << "testFeedToDevice" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -107,21 +114,20 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testFeedToDevice, T, test_types ) { app.testModule.feedingToDevice.write(); regacc.read(); BOOST_CHECK(regacc == 120); - } /*********************************************************************************************************************/ /* test feeding a scalar to two different device registers */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testFeedToDeviceFanOut, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testFeedToDeviceFanOut, T, test_types) { std::cout << "testFeedToDeviceFanOut" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); TestApplication<T> app; - app.testModule.feedingToDevice >> app.devMymodule("actuator") - >> app.devMymodule("readBack"); + app.testModule.feedingToDevice >> app.devMymodule("actuator") >> + app.devMymodule("readBack"); app.initialise(); ChimeraTK::Device dev; @@ -147,13 +153,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testFeedToDeviceFanOut, T, test_types ) { BOOST_CHECK(regac == 120); regrb.read(); BOOST_CHECK(regrb == 120); - } /*********************************************************************************************************************/ /* test consuming a scalar from a device */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumeFromDevice, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testConsumeFromDevice, T, test_types) { std::cout << "testConsumeFromDevice" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -182,28 +187,26 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumeFromDevice, T, test_types ) { regacc.write(); BOOST_CHECK(app.testModule.consumingPoll == 42); app.testModule.consumingPoll.read(); - BOOST_CHECK( app.testModule.consumingPoll == 120 ); + BOOST_CHECK(app.testModule.consumingPoll == 120); app.testModule.consumingPoll.read(); - BOOST_CHECK( app.testModule.consumingPoll == 120 ); + BOOST_CHECK(app.testModule.consumingPoll == 120); app.testModule.consumingPoll.read(); - BOOST_CHECK( app.testModule.consumingPoll == 120 ); - + BOOST_CHECK(app.testModule.consumingPoll == 120); } /*********************************************************************************************************************/ -/* test consuming a scalar from a device with a ConsumingFanOut (i.e. one poll-type consumer and several push-type - * consumers). */ +/* test consuming a scalar from a device with a ConsumingFanOut (i.e. one + * poll-type consumer and several push-type consumers). */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumingFanOut, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testConsumingFanOut, T, test_types) { std::cout << "testConsumingFanOut" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); TestApplication<T> app; - app.dev("/MyModule/actuator") >> app.testModule.consumingPoll - >> app.testModule.consumingPush - >> app.testModule.consumingPush2; + app.dev("/MyModule/actuator") >> app.testModule.consumingPoll >> + app.testModule.consumingPush >> app.testModule.consumingPush2; app.initialise(); ChimeraTK::Device dev; @@ -254,7 +257,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumingFanOut, T, test_types ) { app.testModule.consumingPoll.read(); BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == true); BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == true); - BOOST_CHECK( app.testModule.consumingPoll == 120 ); + BOOST_CHECK(app.testModule.consumingPoll == 120); BOOST_CHECK(app.testModule.consumingPush == 120); BOOST_CHECK(app.testModule.consumingPush2 == 120); BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false); @@ -275,13 +278,13 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testConsumingFanOut, T, test_types ) { BOOST_CHECK(app.testModule.consumingPush2 == 120); BOOST_CHECK(app.testModule.consumingPush.readNonBlocking() == false); BOOST_CHECK(app.testModule.consumingPush2.readNonBlocking() == false); - } /*********************************************************************************************************************/ -/* test merged networks (optimisation done in Application::optimiseConnections()) */ +/* test merged networks (optimisation done in + * Application::optimiseConnections()) */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testMergedNetworks, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testMergedNetworks, T, test_types) { std::cout << "testMergedNetworks" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -289,25 +292,29 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testMergedNetworks, T, test_types ) { TestApplication<T> app; // we abuse "feedingToDevice" as trigger here... - app.dev("/MyModule/actuator") [ app.testModule.feedingToDevice ] >> app.testModule.consumingPush; - app.dev("/MyModule/actuator") [ app.testModule.feedingToDevice ] >> app.testModule.consumingPush2; + app.dev("/MyModule/actuator")[app.testModule.feedingToDevice] >> + app.testModule.consumingPush; + app.dev("/MyModule/actuator")[app.testModule.feedingToDevice] >> + app.testModule.consumingPush2; // check that we have two separate networks for both connections size_t nDeviceFeeders = 0; - for(auto &net : app.networkList) { - if( net.getFeedingNode().getType() == ctk::NodeType::Device ) nDeviceFeeders++; + for (auto &net : app.networkList) { + if (net.getFeedingNode().getType() == ctk::NodeType::Device) + nDeviceFeeders++; } - BOOST_CHECK_EQUAL( nDeviceFeeders, 2 ); + BOOST_CHECK_EQUAL(nDeviceFeeders, 2); // the optimisation to test takes place here app.initialise(); // check we are left with just one network fed by the device nDeviceFeeders = 0; - for(auto &net : app.networkList) { - if( net.getFeedingNode().getType() == ctk::NodeType::Device ) nDeviceFeeders++; + for (auto &net : app.networkList) { + if (net.getFeedingNode().getType() == ctk::NodeType::Device) + nDeviceFeeders++; } - BOOST_CHECK_EQUAL( nDeviceFeeders, 1 ); + BOOST_CHECK_EQUAL(nDeviceFeeders, 1); // run the application to see if everything still behaves as expected app.run(); @@ -335,59 +342,57 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testMergedNetworks, T, test_types ) { app.testModule.feedingToDevice.write(); app.testModule.consumingPush.read(); app.testModule.consumingPush2.read(); - BOOST_CHECK( app.testModule.consumingPush == 120 ); - BOOST_CHECK( app.testModule.consumingPush2 == 120 ); - + BOOST_CHECK(app.testModule.consumingPush == 120); + BOOST_CHECK(app.testModule.consumingPush2 == 120); } /*********************************************************************************************************************/ /* test feeding a constant to a device register. */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testConstantToDevice, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testConstantToDevice, T, test_types) { std::cout << "testConstantToDevice" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); TestApplication<T> app; - ctk::VariableNetworkNode::makeConstant<T>(true, 18) >> app.dev("/MyModule/actuator"); + ctk::VariableNetworkNode::makeConstant<T>(true, 18) >> + app.dev("/MyModule/actuator"); app.initialise(); app.run(); ChimeraTK::Device dev; dev.open("Dummy0"); - CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 18, 3000 ); - + CHECK_TIMEOUT(dev.read<T>("/MyModule/actuator") == 18, 3000); } /*********************************************************************************************************************/ /* test feeding a constant to a device register with a fan out. */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testConstantToDeviceFanOut, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testConstantToDeviceFanOut, T, test_types) { std::cout << "testConstantToDeviceFanOut" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); TestApplication<T> app; - ctk::VariableNetworkNode::makeConstant<T>(true, 20) >> app.dev("/MyModule/actuator") - >> app.dev("/MyModule/readBack"); + ctk::VariableNetworkNode::makeConstant<T>(true, 20) >> + app.dev("/MyModule/actuator") >> app.dev("/MyModule/readBack"); app.initialise(); app.run(); ChimeraTK::Device dev; dev.open("Dummy0"); - CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 20, 3000 ); - CHECK_TIMEOUT( dev.read<T>("/MyModule/readBack") == 20, 3000 ); - + CHECK_TIMEOUT(dev.read<T>("/MyModule/actuator") == 20, 3000); + CHECK_TIMEOUT(dev.read<T>("/MyModule/readBack") == 20, 3000); } /*********************************************************************************************************************/ /* test subscript operator of DeviceModule */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testDeviceModuleSubscriptOp, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testDeviceModuleSubscriptOp, T, test_types) { std::cout << "testDeviceModuleSubscriptOp" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -412,23 +417,22 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testDeviceModuleSubscriptOp, T, test_types ) { app.testModule.feedingToDevice.write(); regacc.read(); BOOST_CHECK(regacc == 120); - } /*********************************************************************************************************************/ /* test DeviceModule::virtualise() (trivial implementation) */ -BOOST_AUTO_TEST_CASE( testDeviceModuleVirtuallise ) { +BOOST_AUTO_TEST_CASE(testDeviceModuleVirtuallise) { std::cout << "testDeviceModuleVirtuallise" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); TestApplication<int> app; - app.testModule.feedingToDevice >> app.dev.virtualise()["MyModule"]("actuator"); + app.testModule.feedingToDevice >> + app.dev.virtualise()["MyModule"]("actuator"); app.initialise(); - BOOST_CHECK( &(app.dev.virtualise()) == &(app.dev) ); - + BOOST_CHECK(&(app.dev.virtualise()) == &(app.dev)); } diff --git a/tests/executables_src/testDirectDeviceToCS.cc b/tests/executables_src/testDirectDeviceToCS.cc index 384ef554..3e9e40ac 100644 --- a/tests/executables_src/testDirectDeviceToCS.cc +++ b/tests/executables_src/testDirectDeviceToCS.cc @@ -7,127 +7,126 @@ #define BOOST_TEST_MODULE testDirectDeviceToCS -#include <boost/test/included/unit_test.hpp> #include <boost/mpl/list.hpp> +#include <boost/test/included/unit_test.hpp> -#include <ChimeraTK/Device.h> #include "Application.h" -#include "PeriodicTrigger.h" -#include "DeviceModule.h" #include "ControlSystemModule.h" +#include "DeviceModule.h" +#include "PeriodicTrigger.h" #include "TestFacility.h" +#include <ChimeraTK/Device.h> using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; // list of user types the accessors are tested with -typedef boost::mpl::list<int8_t,uint8_t, - int16_t,uint16_t, - int32_t,uint32_t, - float,double> test_types; - -#define CHECK_TIMEOUT(condition, maxMilliseconds) \ - { \ - std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \ - while(!(condition)) { \ - bool timeout_reached = (std::chrono::steady_clock::now()-t0) > std::chrono::milliseconds(maxMilliseconds); \ - BOOST_CHECK( !timeout_reached ); \ - if(timeout_reached) break; \ - usleep(1000); \ - } \ - } +typedef boost::mpl::list<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + float, double> + test_types; + +#define CHECK_TIMEOUT(condition, maxMilliseconds) \ + { \ + std::chrono::steady_clock::time_point t0 = \ + std::chrono::steady_clock::now(); \ + while (!(condition)) { \ + bool timeout_reached = (std::chrono::steady_clock::now() - t0) > \ + std::chrono::milliseconds(maxMilliseconds); \ + BOOST_CHECK(!timeout_reached); \ + if (timeout_reached) \ + break; \ + usleep(1000); \ + } \ + } /*********************************************************************************************************************/ /* dummy application */ -template<typename T> -struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") { - ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); - } - ~TestApplication() { shutdown(); } +template <typename T> struct TestApplication : public ctk::Application { + TestApplication() : Application("testSuite") { + ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); + } + ~TestApplication() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // the setup is done in the tests + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // the setup is done in the tests - ctk::ControlSystemModule cs; + ctk::ControlSystemModule cs; - ctk::DeviceModule dev{"Dummy0"}; + ctk::DeviceModule dev{"Dummy0"}; }; /*********************************************************************************************************************/ /* dummy application for connectTo() test */ struct TestApplicationConnectTo : ctk::Application { - TestApplicationConnectTo() : Application("testSuite") {} - ~TestApplicationConnectTo(); + TestApplicationConnectTo() : Application("testSuite") {} + ~TestApplicationConnectTo(); - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() { - dev.connectTo(cs, trigger.tick); - } + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() { dev.connectTo(cs, trigger.tick); } - ctk::PeriodicTrigger trigger{this, "trigger", ""}; + ctk::PeriodicTrigger trigger{this, "trigger", ""}; - ctk::DeviceModule dev{"(dummy?map=test3.map)"}; - ctk::ControlSystemModule cs; + ctk::DeviceModule dev{"(dummy?map=test3.map)"}; + ctk::ControlSystemModule cs; }; -TestApplicationConnectTo::~TestApplicationConnectTo() { - shutdown(); -} +TestApplicationConnectTo::~TestApplicationConnectTo() { shutdown(); } /*********************************************************************************************************************/ -template<typename T, typename LAMBDA> -void testDirectRegister(ctk::TestFacility &test, ChimeraTK::ScalarRegisterAccessor<T> sender, - ChimeraTK::ScalarRegisterAccessor<T> receiver, - LAMBDA trigger, bool testMinMax=true) { +template <typename T, typename LAMBDA> +void testDirectRegister(ctk::TestFacility &test, + ChimeraTK::ScalarRegisterAccessor<T> sender, + ChimeraTK::ScalarRegisterAccessor<T> receiver, + LAMBDA trigger, bool testMinMax = true) { + + sender = 42; + sender.write(); + trigger(); + test.stepApplication(); + receiver.read(); + BOOST_CHECK_EQUAL(receiver, 42); + + if (std::numeric_limits<T>::is_signed) { + sender = -120; + sender.write(); + trigger(); + test.stepApplication(); + receiver.read(); + BOOST_CHECK_EQUAL(receiver, -120); + } - sender = 42; + if (testMinMax) { + sender = std::numeric_limits<T>::max(); sender.write(); trigger(); test.stepApplication(); receiver.read(); - BOOST_CHECK_EQUAL( receiver, 42 ); - - if(std::numeric_limits<T>::is_signed) { - sender = -120; - sender.write(); - trigger(); - test.stepApplication(); - receiver.read(); - BOOST_CHECK_EQUAL( receiver, -120 ); - } - - if(testMinMax) { - sender = std::numeric_limits<T>::max(); - sender.write(); - trigger(); - test.stepApplication(); - receiver.read(); - BOOST_CHECK_EQUAL( receiver, std::numeric_limits<T>::max() ); - - sender = std::numeric_limits<T>::min(); - sender.write(); - trigger(); - test.stepApplication(); - receiver.read(); - BOOST_CHECK_EQUAL( receiver, std::numeric_limits<T>::min() ); - - sender = std::numeric_limits<T>::epsilon(); - sender.write(); - trigger(); - test.stepApplication(); - receiver.read(); - BOOST_CHECK_EQUAL( receiver, std::numeric_limits<T>::epsilon() ); - } + BOOST_CHECK_EQUAL(receiver, std::numeric_limits<T>::max()); + sender = std::numeric_limits<T>::min(); + sender.write(); + trigger(); + test.stepApplication(); + receiver.read(); + BOOST_CHECK_EQUAL(receiver, std::numeric_limits<T>::min()); + + sender = std::numeric_limits<T>::epsilon(); + sender.write(); + trigger(); + test.stepApplication(); + receiver.read(); + BOOST_CHECK_EQUAL(receiver, std::numeric_limits<T>::epsilon()); + } } /*********************************************************************************************************************/ /* test direct control system to device connections */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testDirectCStoDev, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testDirectCStoDev, T, test_types) { std::cout << "testDirectCStoDev" << std::endl; TestApplication<T> app; @@ -144,22 +143,21 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testDirectCStoDev, T, test_types ) { BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 1); auto myFeeder = pvManagers.first->getProcessArray<T>("/myFeeder"); - BOOST_CHECK( myFeeder->getName() == "/myFeeder" ); + BOOST_CHECK(myFeeder->getName() == "/myFeeder"); myFeeder->accessData(0) = 18; myFeeder->write(); - CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 18, 3000); + CHECK_TIMEOUT(dev.read<T>("/MyModule/actuator") == 18, 3000); myFeeder->accessData(0) = 20; myFeeder->write(); - CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 20, 3000); - + CHECK_TIMEOUT(dev.read<T>("/MyModule/actuator") == 20, 3000); } /*********************************************************************************************************************/ /* test direct control system to device connections with fan out */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testDirectCStoDevFanOut, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testDirectCStoDevFanOut, T, test_types) { std::cout << "testDirectCStoDevFanOut" << std::endl; TestApplication<T> app; @@ -167,8 +165,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testDirectCStoDevFanOut, T, test_types ) { auto pvManagers = ctk::createPVManager(); app.setPVManager(pvManagers.second); - app.cs("myFeeder", typeid(T), 1) >> app.dev("/MyModule/actuator") - >> app.dev("/MyModule/readBack"); + app.cs("myFeeder", typeid(T), 1) >> app.dev("/MyModule/actuator") >> + app.dev("/MyModule/readBack"); app.initialise(); app.run(); @@ -177,24 +175,23 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testDirectCStoDevFanOut, T, test_types ) { BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 1); auto myFeeder = pvManagers.first->getProcessArray<T>("/myFeeder"); - BOOST_CHECK( myFeeder->getName() == "/myFeeder" ); + BOOST_CHECK(myFeeder->getName() == "/myFeeder"); myFeeder->accessData(0) = 18; myFeeder->write(); - CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 18, 3000); - CHECK_TIMEOUT( dev.read<T>("/MyModule/readBack") == 18, 3000); + CHECK_TIMEOUT(dev.read<T>("/MyModule/actuator") == 18, 3000); + CHECK_TIMEOUT(dev.read<T>("/MyModule/readBack") == 18, 3000); myFeeder->accessData(0) = 20; myFeeder->write(); - CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 20, 3000); - CHECK_TIMEOUT( dev.read<T>("/MyModule/readBack") == 20, 3000); - + CHECK_TIMEOUT(dev.read<T>("/MyModule/actuator") == 20, 3000); + CHECK_TIMEOUT(dev.read<T>("/MyModule/readBack") == 20, 3000); } /*********************************************************************************************************************/ /* test connectTo */ -BOOST_AUTO_TEST_CASE( testConnectTo ) { +BOOST_AUTO_TEST_CASE(testConnectTo) { std::cout << "testConnectTo" << std::endl; ctk::Device dev; @@ -203,17 +200,23 @@ BOOST_AUTO_TEST_CASE( testConnectTo ) { TestApplicationConnectTo app; ctk::TestFacility test; - auto devActuator = dev.getScalarRegisterAccessor<int32_t>("/MyModule/actuator"); - auto devReadback = dev.getScalarRegisterAccessor<int32_t>("/MyModule/readBack"); + auto devActuator = + dev.getScalarRegisterAccessor<int32_t>("/MyModule/actuator"); + auto devReadback = + dev.getScalarRegisterAccessor<int32_t>("/MyModule/readBack"); auto devint32 = dev.getScalarRegisterAccessor<int32_t>("/Integers/signed32"); - auto devuint32 = dev.getScalarRegisterAccessor<uint32_t>("/Integers/unsigned32"); + auto devuint32 = + dev.getScalarRegisterAccessor<uint32_t>("/Integers/unsigned32"); auto devint16 = dev.getScalarRegisterAccessor<int16_t>("/Integers/signed16"); - auto devuint16 = dev.getScalarRegisterAccessor<uint16_t>("/Integers/unsigned16"); + auto devuint16 = + dev.getScalarRegisterAccessor<uint16_t>("/Integers/unsigned16"); auto devint8 = dev.getScalarRegisterAccessor<int8_t>("/Integers/signed8"); auto devuint8 = dev.getScalarRegisterAccessor<uint8_t>("/Integers/unsigned8"); auto devfloat = dev.getScalarRegisterAccessor<double>("/FixedPoint/value"); - auto devDeep1 = dev.getScalarRegisterAccessor<int32_t>("/Deep/Hierarchies/Need/Tests/As/well"); - auto devDeep2 = dev.getScalarRegisterAccessor<int32_t>("/Deep/Hierarchies/Need/Another/test"); + auto devDeep1 = dev.getScalarRegisterAccessor<int32_t>( + "/Deep/Hierarchies/Need/Tests/As/well"); + auto devDeep2 = dev.getScalarRegisterAccessor<int32_t>( + "/Deep/Hierarchies/Need/Another/test"); auto csActuator = test.getScalar<int32_t>("/MyModule/actuator"); auto csReadback = test.getScalar<int32_t>("/MyModule/readBack"); auto csint32 = test.getScalar<int32_t>("/Integers/signed32"); @@ -223,20 +226,21 @@ BOOST_AUTO_TEST_CASE( testConnectTo ) { auto csint8 = test.getScalar<int8_t>("/Integers/signed8"); auto csuint8 = test.getScalar<uint8_t>("/Integers/unsigned8"); auto csfloat = test.getScalar<double>("/FixedPoint/value"); - auto csDeep1 = test.getScalar<int32_t>("/Deep/Hierarchies/Need/Tests/As/well"); + auto csDeep1 = + test.getScalar<int32_t>("/Deep/Hierarchies/Need/Tests/As/well"); auto csDeep2 = test.getScalar<int32_t>("/Deep/Hierarchies/Need/Another/test"); test.runApplication(); - testDirectRegister(test, csActuator, devActuator, []{}); - testDirectRegister(test, devReadback, csReadback, [&]{app.trigger.sendTrigger();}); - testDirectRegister(test, csint32, devint32, []{}); - testDirectRegister(test, csuint32, devuint32, []{}); - testDirectRegister(test, csint16, devint16, []{}); - testDirectRegister(test, csuint16, devuint16, []{}); - testDirectRegister(test, csint8, devint8, []{}); - testDirectRegister(test, csuint8, devuint8, []{}); - testDirectRegister(test, csfloat, devfloat, []{}, false); - testDirectRegister(test, csDeep1, devDeep1, []{}); - testDirectRegister(test, csDeep2, devDeep2, []{}); - + testDirectRegister(test, csActuator, devActuator, [] {}); + testDirectRegister(test, devReadback, csReadback, + [&] { app.trigger.sendTrigger(); }); + testDirectRegister(test, csint32, devint32, [] {}); + testDirectRegister(test, csuint32, devuint32, [] {}); + testDirectRegister(test, csint16, devint16, [] {}); + testDirectRegister(test, csuint16, devuint16, [] {}); + testDirectRegister(test, csint8, devint8, [] {}); + testDirectRegister(test, csuint8, devuint8, [] {}); + testDirectRegister(test, csfloat, devfloat, [] {}, false); + testDirectRegister(test, csDeep1, devDeep1, [] {}); + testDirectRegister(test, csDeep2, devDeep2, [] {}); } diff --git a/tests/executables_src/testExceptionTest.cc b/tests/executables_src/testExceptionTest.cc index 9c4b7413..5e542bb3 100644 --- a/tests/executables_src/testExceptionTest.cc +++ b/tests/executables_src/testExceptionTest.cc @@ -2,21 +2,21 @@ #define BOOST_TEST_MODULE testExceptionTest +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> -#include <ChimeraTK/Device.h> #include <ChimeraTK/BackendFactory.h> +#include <ChimeraTK/Device.h> #include <ChimeraTK/NDRegisterAccessor.h> #include "Application.h" -#include "ScalarAccessor.h" #include "ApplicationModule.h" -#include "DeviceModule.h" -#include "TestFacility.h" #include "ControlSystemModule.h" +#include "DeviceModule.h" #include "ExceptionDevice.h" +#include "ScalarAccessor.h" +#include "TestFacility.h" using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; @@ -27,7 +27,8 @@ struct TestApplication : public ctk::Application { TestApplication() : Application("testSuite") {} ~TestApplication() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. void defineConnections() {} // the setup is done in the tests @@ -39,15 +40,19 @@ struct TestApplication : public ctk::Application { BOOST_AUTO_TEST_CASE(testThinkOfAName) { TestApplication app; - boost::shared_ptr<ExceptionDummy> backend = boost::dynamic_pointer_cast<ExceptionDummy>( - ChimeraTK::BackendFactory::getInstance().createBackend("(ExceptionDummy?map=DemoDummy.map)")); + boost::shared_ptr<ExceptionDummy> backend = + boost::dynamic_pointer_cast<ExceptionDummy>( + ChimeraTK::BackendFactory::getInstance().createBackend( + "(ExceptionDummy?map=DemoDummy.map)")); app.dev.connectTo(app.cs); ctk::TestFacility test; app.initialise(); app.run(); - auto message = test.getScalar<std::string>("/Devices/(ExceptionDummy?map=DemoDummy.map)/message"); - auto status = test.getScalar<int>("/Devices/(ExceptionDummy?map=DemoDummy.map)/status"); + auto message = test.getScalar<std::string>( + "/Devices/(ExceptionDummy?map=DemoDummy.map)/message"); + auto status = + test.getScalar<int>("/Devices/(ExceptionDummy?map=DemoDummy.map)/status"); // initially there should be no error set message.readLatest(); @@ -63,23 +68,26 @@ BOOST_AUTO_TEST_CASE(testThinkOfAName) { try { backend->open(); BOOST_FAIL("Exception expected."); - } - catch(ChimeraTK::runtime_error&) { + } catch (ChimeraTK::runtime_error &) { } - // report exception to the DeviceModule: it should try reopening the device but fail + // report exception to the DeviceModule: it should try reopening the device + // but fail std::atomic<bool> reportExceptionFinished; reportExceptionFinished = false; - std::thread reportThread([&] { // need to launch in background, reportException() blocks - app.dev.reportException("Some fancy exception text"); - reportExceptionFinished = true; - }); + std::thread reportThread( + [&] { // need to launch in background, reportException() blocks + app.dev.reportException("Some fancy exception text"); + reportExceptionFinished = true; + }); // check the error status and that reportException() is still blocking sleep(2); message.readLatest(); status.readLatest(); - BOOST_CHECK_EQUAL(static_cast<std::string>(message), "DummyException: This is a test"); // from the ExceptionDummy + BOOST_CHECK_EQUAL( + static_cast<std::string>(message), + "DummyException: This is a test"); // from the ExceptionDummy BOOST_CHECK(status == 1); BOOST_CHECK(reportExceptionFinished == false); BOOST_CHECK(!backend->isOpen()); diff --git a/tests/executables_src/testIllegalNetworks.cc b/tests/executables_src/testIllegalNetworks.cc index 18c9b042..0ebed05c 100644 --- a/tests/executables_src/testIllegalNetworks.cc +++ b/tests/executables_src/testIllegalNetworks.cc @@ -9,68 +9,74 @@ #define BOOST_TEST_MODULE testIllegalNetworks +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> #include <ChimeraTK/BackendFactory.h> #include "Application.h" -#include "ScalarAccessor.h" -#include "ArrayAccessor.h" #include "ApplicationModule.h" +#include "ArrayAccessor.h" #include "DeviceModule.h" +#include "ScalarAccessor.h" using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; // list of user types the accessors are tested with -typedef boost::mpl::list<int8_t,uint8_t, - int16_t,uint16_t, - int32_t,uint32_t, - float,double> test_types; +typedef boost::mpl::list<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + float, double> + test_types; /*********************************************************************************************************************/ /* the ApplicationModule for the test is a template of the user type */ -template<typename T> -struct TestModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - - ctk::ScalarOutput<T> feedingPush{this, "feedingPush", "MV/m", "Descrption"}; - ctk::ScalarOutput<T> feedingPush2{this, "feedingPush2", "MV/m", "Descrption"}; - ctk::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", "Descrption"}; - ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", "Descrption"}; - ctk::ScalarPushInput<T> consumingPush3{this, "consumingPush3", "MV/m", "Descrption"}; - - ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", "Descrption"}; - ctk::ScalarPollInput<T> consumingPoll2{this, "consumingPoll2", "MV/m", "Descrption"}; - ctk::ScalarPollInput<T> consumingPoll3{this, "consumingPoll3", "MV/m", "Descrption"}; - - ctk::ArrayOutput<T> feedingArray{this, "feedingArray", "MV/m", 10, "Description"}; - - void mainLoop() {} +template <typename T> struct TestModule : public ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; + + ctk::ScalarOutput<T> feedingPush{this, "feedingPush", "MV/m", "Descrption"}; + ctk::ScalarOutput<T> feedingPush2{this, "feedingPush2", "MV/m", "Descrption"}; + ctk::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", + "Descrption"}; + ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", + "Descrption"}; + ctk::ScalarPushInput<T> consumingPush3{this, "consumingPush3", "MV/m", + "Descrption"}; + + ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", + "Descrption"}; + ctk::ScalarPollInput<T> consumingPoll2{this, "consumingPoll2", "MV/m", + "Descrption"}; + ctk::ScalarPollInput<T> consumingPoll3{this, "consumingPoll3", "MV/m", + "Descrption"}; + + ctk::ArrayOutput<T> feedingArray{this, "feedingArray", "MV/m", 10, + "Description"}; + + void mainLoop() {} }; /*********************************************************************************************************************/ /* dummy application */ -template<typename T> -struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") {} - ~TestApplication() { shutdown(); } +template <typename T> struct TestApplication : public ctk::Application { + TestApplication() : Application("testSuite") {} + ~TestApplication() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // the setup is done in the tests + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // the setup is done in the tests - TestModule<T> testModule{this, "testModule", "The test module"}; - ctk::DeviceModule dev{"Dummy0"}; + TestModule<T> testModule{this, "testModule", "The test module"}; + ctk::DeviceModule dev{"Dummy0"}; }; /*********************************************************************************************************************/ -/* test case for two scalar accessors, feeder in poll mode and consumer in push mode (without trigger) */ +/* test case for two scalar accessors, feeder in poll mode and consumer in push + * mode (without trigger) */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoScalarPollPushAccessors, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testTwoScalarPollPushAccessors, T, test_types) { ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); TestApplication<T> app; @@ -79,17 +85,15 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoScalarPollPushAccessors, T, test_types ) { try { app.initialise(); BOOST_ERROR("Exception expected."); + } catch (ChimeraTK::logic_error &e) { + BOOST_CHECK_NO_THROW(e.what();); } - catch(ChimeraTK::logic_error &e) { - BOOST_CHECK_NO_THROW( e.what(); ); - } - } /*********************************************************************************************************************/ /* test case for no feeder */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testNoFeeder, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testNoFeeder, T, test_types) { TestApplication<T> app; @@ -97,54 +101,49 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testNoFeeder, T, test_types ) { try { app.initialise(); BOOST_ERROR("Exception expected."); + } catch (ChimeraTK::logic_error &e) { + BOOST_CHECK_NO_THROW(e.what();); } - catch(ChimeraTK::logic_error &e) { - BOOST_CHECK_NO_THROW( e.what(); ); - } - } /*********************************************************************************************************************/ /* test case for two feeders */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTwoFeeders, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testTwoFeeders, T, test_types) { TestApplication<T> app; try { app.testModule.feedingPush >> app.testModule.feedingPush2; BOOST_ERROR("Exception expected."); + } catch (ChimeraTK::logic_error &e) { + BOOST_CHECK_NO_THROW(e.what();); } - catch(ChimeraTK::logic_error &e) { - BOOST_CHECK_NO_THROW( e.what(); ); - } - } /*********************************************************************************************************************/ /* test case for too many polling consumers */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTooManyPollingConsumers, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testTooManyPollingConsumers, T, test_types) { ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); TestApplication<T> app; - app.dev("/MyModule/Variable") >> app.testModule.consumingPoll >> app.testModule.consumingPoll2; + app.dev("/MyModule/Variable") >> app.testModule.consumingPoll >> + app.testModule.consumingPoll2; try { app.initialise(); BOOST_ERROR("Exception expected."); + } catch (ChimeraTK::logic_error &e) { + BOOST_CHECK_NO_THROW(e.what();); } - catch(ChimeraTK::logic_error &e) { - BOOST_CHECK_NO_THROW( e.what(); ); - } - } /*********************************************************************************************************************/ /* test case for different number of elements */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testDifferentNrElements, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testDifferentNrElements, T, test_types) { ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -153,17 +152,15 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testDifferentNrElements, T, test_types ) { try { app.testModule.feedingArray >> app.testModule.consumingPoll; BOOST_ERROR("Exception expected."); + } catch (ChimeraTK::logic_error &e) { + BOOST_CHECK_NO_THROW(e.what();); } - catch(ChimeraTK::logic_error &e) { - BOOST_CHECK_NO_THROW( e.what(); ); - } - } /*********************************************************************************************************************/ /* test case for "merging" two networks */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testMergeNetworks, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testMergeNetworks, T, test_types) { ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -173,28 +170,26 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testMergeNetworks, T, test_types ) { try { app.testModule.consumingPush >> app.testModule.consumingPush2; BOOST_ERROR("Exception expected."); + } catch (ChimeraTK::logic_error &e) { + BOOST_CHECK_NO_THROW(e.what();); } - catch(ChimeraTK::logic_error &e) { - BOOST_CHECK_NO_THROW( e.what(); ); - } - } /*********************************************************************************************************************/ /* test case for constant as trigger */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testConstantTrigger, T, test_types ) { +BOOST_AUTO_TEST_CASE_TEMPLATE(testConstantTrigger, T, test_types) { ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); TestApplication<T> app; - app.dev("/MyModule/Variable") [ ctk::VariableNetworkNode::makeConstant<int>(true) ] >> app.testModule.consumingPush; + app.dev("/MyModule/Variable")[ctk::VariableNetworkNode::makeConstant<int>( + true)] >> + app.testModule.consumingPush; try { app.initialise(); BOOST_ERROR("Exception expected."); + } catch (ChimeraTK::logic_error &e) { + BOOST_CHECK_NO_THROW(e.what();); } - catch(ChimeraTK::logic_error &e) { - BOOST_CHECK_NO_THROW( e.what(); ); - } - } diff --git a/tests/executables_src/testLogging.cc b/tests/executables_src/testLogging.cc index 006a91d3..981dc9c3 100644 --- a/tests/executables_src/testLogging.cc +++ b/tests/executables_src/testLogging.cc @@ -10,8 +10,8 @@ #include <fstream> -#include <boost/filesystem.hpp> #include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> #include <boost/thread.hpp> @@ -28,23 +28,23 @@ using namespace boost::unit_test_framework; * Define a test app to test the SoftwareMasterModule. */ struct testApp : public ChimeraTK::Application { - testApp() : Application("test"), fileCreated(false){ } + testApp() : Application("test"), fileCreated(false) {} ~testApp() { shutdown(); - if(fileCreated){ - BOOST_CHECK_EQUAL(boost::filesystem::remove("/tmp/testLogging/test.log"), true); + if (fileCreated) { + BOOST_CHECK_EQUAL(boost::filesystem::remove("/tmp/testLogging/test.log"), + true); BOOST_CHECK_EQUAL(boost::filesystem::remove("/tmp/testLogging/"), true); } } - LoggingModule log { this, "LoggingModule", - "LoggingModule test" }; + LoggingModule log{this, "LoggingModule", "LoggingModule test"}; Logger logger{&log}; ChimeraTK::ControlSystemModule cs; - void defineConnections() override{ + void defineConnections() override { cs("targetStream") >> log.targetStream; cs("logLevel") >> log.logLevel; cs("logFile") >> log.logFile; @@ -52,13 +52,12 @@ struct testApp : public ChimeraTK::Application { log.addSource(&logger); log.findTag("CS").connectTo(cs); - - } + } bool fileCreated; }; -BOOST_AUTO_TEST_CASE( testLogMsg) { +BOOST_AUTO_TEST_CASE(testLogMsg) { testApp app; ChimeraTK::TestFacility tf; tf.runApplication(); @@ -67,11 +66,11 @@ BOOST_AUTO_TEST_CASE( testLogMsg) { tailLength = 1; tailLength.write(); tf.stepApplication(); - std::string ss = (std::string)tf.readScalar<std::string>("LogTail"); - BOOST_CHECK_EQUAL(ss.substr(ss.find("->")+3), std::string("test\n")); + std::string ss = (std::string)tf.readScalar<std::string>("LogTail"); + BOOST_CHECK_EQUAL(ss.substr(ss.find("->") + 3), std::string("test\n")); } -BOOST_AUTO_TEST_CASE( testLogfileFails) { +BOOST_AUTO_TEST_CASE(testLogfileFails) { testApp app; ChimeraTK::TestFacility tf; @@ -82,19 +81,22 @@ BOOST_AUTO_TEST_CASE( testLogfileFails) { // message not considered here but used to step through the application app.logger.sendMessage("test", LogLevel::DEBUG); tf.stepApplication(); - std::string ss = (std::string)tf.readScalar<std::string>("LogTail"); + std::string ss = (std::string)tf.readScalar<std::string>("LogTail"); std::vector<std::string> strs; boost::split(strs, ss, boost::is_any_of("\n"), boost::token_compress_on); - BOOST_CHECK_EQUAL(strs.at(2).substr(strs.at(2).find("->")+3), std::string("Failed to open log file for writing: /tmp/testLogging/test.log")); + BOOST_CHECK_EQUAL( + strs.at(2).substr(strs.at(2).find("->") + 3), + std::string( + "Failed to open log file for writing: /tmp/testLogging/test.log")); } -BOOST_AUTO_TEST_CASE( testLogfile) { +BOOST_AUTO_TEST_CASE(testLogfile) { testApp app; ChimeraTK::TestFacility tf; auto logFile = tf.getScalar<std::string>("logFile"); - if(!boost::filesystem::is_directory("/tmp/testLogging/")) + if (!boost::filesystem::is_directory("/tmp/testLogging/")) boost::filesystem::create_directory("/tmp/testLogging/"); tf.runApplication(); logFile = std::string("/tmp/testLogging/test.log"); @@ -105,17 +107,19 @@ BOOST_AUTO_TEST_CASE( testLogfile) { std::fstream file; file.open("/tmp/testLogging/test.log"); BOOST_CHECK_EQUAL(file.good(), true); - if(file.good()) + if (file.good()) app.fileCreated = true; std::string line; std::getline(file, line); - BOOST_CHECK_EQUAL(line.substr(line.find("->")+3), std::string("Opened log file for writing: /tmp/testLogging/test.log")); + BOOST_CHECK_EQUAL( + line.substr(line.find("->") + 3), + std::string("Opened log file for writing: /tmp/testLogging/test.log")); std::getline(file, line); - BOOST_CHECK_EQUAL(line.substr(line.find("->")+3), std::string("test")); + BOOST_CHECK_EQUAL(line.substr(line.find("->") + 3), std::string("test")); } -BOOST_AUTO_TEST_CASE( testLogging) { +BOOST_AUTO_TEST_CASE(testLogging) { testApp app; ChimeraTK::TestFacility tf; @@ -133,9 +137,10 @@ BOOST_AUTO_TEST_CASE( testLogging) { app.logger.sendMessage("2nd test message", LogLevel::DEBUG); tf.stepApplication(); auto tail = tf.readScalar<std::string>("LogTail"); - std::vector< std::string > result; + std::vector<std::string> result; boost::algorithm::split(result, tail, boost::is_any_of("\n")); - // result length should be 3 not 2, because new line is used to split, which results in 3 items although there are only two messages. + // result length should be 3 not 2, because new line is used to split, which + // results in 3 items although there are only two messages. BOOST_CHECK_EQUAL(result.size(), 3); /**** Test log level ****/ diff --git a/tests/executables_src/testModules.cc b/tests/executables_src/testModules.cc index 2c90cd82..0c480d82 100644 --- a/tests/executables_src/testModules.cc +++ b/tests/executables_src/testModules.cc @@ -5,8 +5,8 @@ * Author: Martin Hierholzer */ -#include <future> #include <chrono> +#include <future> #define BOOST_TEST_MODULE testModules @@ -25,200 +25,228 @@ namespace ctk = ChimeraTK; struct SomeGroup : ctk::VariableGroup { using ctk::VariableGroup::VariableGroup; - ctk::ScalarPushInput<std::string> inGroup{this, "inGroup", "", "This is a string", {"C", "A"}}; - ctk::ArrayPushInput<int64_t> alsoInGroup{this, "alsoInGroup", "justANumber", 16, "A 64 bit number array", {"A", "D"}}; + ctk::ScalarPushInput<std::string> inGroup{ + this, "inGroup", "", "This is a string", {"C", "A"}}; + ctk::ArrayPushInput<int64_t> alsoInGroup{ + this, "alsoInGroup", "justANumber", + 16, "A 64 bit number array", {"A", "D"}}; }; /*********************************************************************************************************************/ /* A plain application module for testing */ struct TestModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - - ctk::ScalarPushInput<int> someInput{this, "nameOfSomeInput", "cm", "This is just some input for testing", {"A", "B"}}; - ctk::ScalarOutput<double> someOutput{this, "someOutput", "V", "Description", {"A", "C"}}; - - SomeGroup someGroup{this, "someGroup", "Description of my test group"}; - - struct AnotherGroup : ctk::VariableGroup { - using ctk::VariableGroup::VariableGroup; - ctk::ScalarPushInput<uint8_t> foo{this, "foo", "counts", "Some counter", {"D"}}; - } anotherGroup{this, "anotherName", "Description of my other group"}; - - void mainLoop() { - while(true) { - someInput.read(); - int val = someInput; - someOutput = val; - someOutput.write(); - } + using ctk::ApplicationModule::ApplicationModule; + + ctk::ScalarPushInput<int> someInput{this, + "nameOfSomeInput", + "cm", + "This is just some input for testing", + {"A", "B"}}; + ctk::ScalarOutput<double> someOutput{ + this, "someOutput", "V", "Description", {"A", "C"}}; + + SomeGroup someGroup{this, "someGroup", "Description of my test group"}; + + struct AnotherGroup : ctk::VariableGroup { + using ctk::VariableGroup::VariableGroup; + ctk::ScalarPushInput<uint8_t> foo{ + this, "foo", "counts", "Some counter", {"D"}}; + } anotherGroup{this, "anotherName", "Description of my other group"}; + + void mainLoop() { + while (true) { + someInput.read(); + int val = someInput; + someOutput = val; + someOutput.write(); } + } }; /*********************************************************************************************************************/ /* Simple application with just one module */ struct OneModuleApp : public ctk::Application { - OneModuleApp() : Application("myApp") {} - ~OneModuleApp() { shutdown(); } + OneModuleApp() : Application("myApp") {} + ~OneModuleApp() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // the setup is done in the tests + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // the setup is done in the tests - TestModule testModule{this, "testModule", "Module to test"}; + TestModule testModule{this, "testModule", "Module to test"}; }; /*********************************************************************************************************************/ /* Application with a vector of modules */ struct VectorOfModulesApp : public ctk::Application { - VectorOfModulesApp(size_t nInstances) - : Application("myApp"), - _nInstances(nInstances) - {} - ~VectorOfModulesApp() { shutdown(); } - - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - - void defineConnections() { - for(size_t i = 0; i < _nInstances; ++i) { - std::string name = "testModule_" + std::to_string(i) + "_instance"; - vectorOfTestModule.emplace_back(this, name, "Description"); - } + VectorOfModulesApp(size_t nInstances) + : Application("myApp"), _nInstances(nInstances) {} + ~VectorOfModulesApp() { shutdown(); } + + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + + void defineConnections() { + for (size_t i = 0; i < _nInstances; ++i) { + std::string name = "testModule_" + std::to_string(i) + "_instance"; + vectorOfTestModule.emplace_back(this, name, "Description"); } + } - size_t _nInstances; - std::vector<TestModule> vectorOfTestModule; + size_t _nInstances; + std::vector<TestModule> vectorOfTestModule; }; /*********************************************************************************************************************/ /* An application module with a vector of a variable group*/ struct VectorModule : public ctk::ApplicationModule { - VectorModule(ctk::EntityOwner *owner, const std::string &name, const std::string &description, size_t nInstances, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}) - : ctk::ApplicationModule(owner, name, description, eliminateHierarchy, tags) - { - for(size_t i=0; i < nInstances; ++i) { - std::string name = "testGroup_" + std::to_string(i); - vectorOfSomeGroup.emplace_back(this, name, "Description 2"); - } + VectorModule(ctk::EntityOwner *owner, const std::string &name, + const std::string &description, size_t nInstances, + bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}) + : ctk::ApplicationModule(owner, name, description, eliminateHierarchy, + tags) { + for (size_t i = 0; i < nInstances; ++i) { + std::string name = "testGroup_" + std::to_string(i); + vectorOfSomeGroup.emplace_back(this, name, "Description 2"); } - VectorModule() {} - - ctk::ScalarPushInput<int> someInput{this, "nameOfSomeInput", "cm", "This is just some input for testing", {"A", "B"}}; - ctk::ArrayOutput<double> someOutput{this, "someOutput", "V", 1, "Description", {"A", "C"}}; - - std::vector<SomeGroup> vectorOfSomeGroup; - - struct AnotherGroup : ctk::VariableGroup { - using ctk::VariableGroup::VariableGroup; - ctk::ScalarPushInput<uint8_t> foo{this, "foo", "counts", "Some counter", {"D"}}; - } anotherGroup{this, "anotherName", "Description of my other group"}; - - void mainLoop() { - while(true) { - someInput.read(); - int val = someInput; - someOutput[0] = val; - someOutput.write(); - } + } + VectorModule() {} + + ctk::ScalarPushInput<int> someInput{this, + "nameOfSomeInput", + "cm", + "This is just some input for testing", + {"A", "B"}}; + ctk::ArrayOutput<double> someOutput{this, "someOutput", "V", + 1, "Description", {"A", "C"}}; + + std::vector<SomeGroup> vectorOfSomeGroup; + + struct AnotherGroup : ctk::VariableGroup { + using ctk::VariableGroup::VariableGroup; + ctk::ScalarPushInput<uint8_t> foo{ + this, "foo", "counts", "Some counter", {"D"}}; + } anotherGroup{this, "anotherName", "Description of my other group"}; + + void mainLoop() { + while (true) { + someInput.read(); + int val = someInput; + someOutput[0] = val; + someOutput.write(); } + } }; /*********************************************************************************************************************/ /* An module group with a vector of a application moduoles */ struct VectorModuleGroup : public ctk::ModuleGroup { - VectorModuleGroup(EntityOwner *owner, const std::string &name, const std::string &description, size_t nInstances, - bool eliminateHierarchy=false, const std::unordered_set<std::string> &tags={}) - : ctk::ModuleGroup(owner, name, description, eliminateHierarchy, tags) - { - for(size_t i=0; i < nInstances; ++i) { - std::string name = "test_" + std::to_string(i); - vectorOfVectorModule.emplace_back(this, name, "Description 3", nInstances); - } + VectorModuleGroup(EntityOwner *owner, const std::string &name, + const std::string &description, size_t nInstances, + bool eliminateHierarchy = false, + const std::unordered_set<std::string> &tags = {}) + : ctk::ModuleGroup(owner, name, description, eliminateHierarchy, tags) { + for (size_t i = 0; i < nInstances; ++i) { + std::string name = "test_" + std::to_string(i); + vectorOfVectorModule.emplace_back(this, name, "Description 3", + nInstances); } + } - VectorModuleGroup() {} - - std::vector<VectorModule> vectorOfVectorModule; + VectorModuleGroup() {} + std::vector<VectorModule> vectorOfVectorModule; }; /*********************************************************************************************************************/ -/* Application with a vector of module groups containing a vector of modules containing a vector of variable groups */ +/* Application with a vector of module groups containing a vector of modules + * containing a vector of variable groups */ struct VectorOfEverythingApp : public ctk::Application { - VectorOfEverythingApp(size_t nInstances) - : Application("myApp"), - _nInstances(nInstances) - {} - ~VectorOfEverythingApp() { shutdown(); } - - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - - void defineConnections() { - for(size_t i = 0; i < _nInstances; ++i) { - std::string name = "testModule_" + std::to_string(i) + "_instance"; - vectorOfVectorModuleGroup.emplace_back(this, name, "Description", _nInstances); - } + VectorOfEverythingApp(size_t nInstances) + : Application("myApp"), _nInstances(nInstances) {} + ~VectorOfEverythingApp() { shutdown(); } + + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + + void defineConnections() { + for (size_t i = 0; i < _nInstances; ++i) { + std::string name = "testModule_" + std::to_string(i) + "_instance"; + vectorOfVectorModuleGroup.emplace_back(this, name, "Description", + _nInstances); } + } - size_t _nInstances; - std::vector<VectorModuleGroup> vectorOfVectorModuleGroup; + size_t _nInstances; + std::vector<VectorModuleGroup> vectorOfVectorModuleGroup; }; /*********************************************************************************************************************/ -/* Application with various modules that get initialised only during defineConnections(). */ +/* Application with various modules that get initialised only during + * defineConnections(). */ struct AssignModuleLaterApp : public ctk::Application { - AssignModuleLaterApp() - : Application("myApp") - {} - ~AssignModuleLaterApp() { shutdown(); } - - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - - void defineConnections() { - modGroupInstanceToAssignLater = VectorModuleGroup(this, "modGroupInstanceToAssignLater", - "This instance of VectorModuleGroup was assigned using the operator=()", 42); - modInstanceToAssignLater = VectorModule(this, "modInstanceToAssignLater", - "This instance of VectorModule was assigned using the operator=()", 13); - } + AssignModuleLaterApp() : Application("myApp") {} + ~AssignModuleLaterApp() { shutdown(); } + + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + + void defineConnections() { + modGroupInstanceToAssignLater = VectorModuleGroup( + this, "modGroupInstanceToAssignLater", + "This instance of VectorModuleGroup was assigned using the operator=()", + 42); + modInstanceToAssignLater = VectorModule( + this, "modInstanceToAssignLater", + "This instance of VectorModule was assigned using the operator=()", 13); + } - VectorModuleGroup modGroupInstanceToAssignLater; - VectorModule modInstanceToAssignLater; + VectorModuleGroup modGroupInstanceToAssignLater; + VectorModule modInstanceToAssignLater; }; /*********************************************************************************************************************/ /* test module and variable ownerships */ -BOOST_AUTO_TEST_CASE( test_ownership ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(test_ownership) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> test_ownership" << std::endl; OneModuleApp app; - BOOST_CHECK( app.testModule.getOwner() == &app ); - BOOST_CHECK( app.testModule.someGroup.getOwner() == &(app.testModule) ); - BOOST_CHECK( app.testModule.anotherGroup.getOwner() == &(app.testModule) ); + BOOST_CHECK(app.testModule.getOwner() == &app); + BOOST_CHECK(app.testModule.someGroup.getOwner() == &(app.testModule)); + BOOST_CHECK(app.testModule.anotherGroup.getOwner() == &(app.testModule)); - BOOST_CHECK( app.testModule.someInput.getOwner() == &(app.testModule) ); - BOOST_CHECK( app.testModule.someOutput.getOwner() == &(app.testModule) ); + BOOST_CHECK(app.testModule.someInput.getOwner() == &(app.testModule)); + BOOST_CHECK(app.testModule.someOutput.getOwner() == &(app.testModule)); - BOOST_CHECK( app.testModule.someGroup.inGroup.getOwner() == &(app.testModule.someGroup) ); - BOOST_CHECK( app.testModule.someGroup.alsoInGroup.getOwner() == &(app.testModule.someGroup) ); - - BOOST_CHECK( app.testModule.anotherGroup.foo.getOwner() == &(app.testModule.anotherGroup) ); + BOOST_CHECK(app.testModule.someGroup.inGroup.getOwner() == + &(app.testModule.someGroup)); + BOOST_CHECK(app.testModule.someGroup.alsoInGroup.getOwner() == + &(app.testModule.someGroup)); + BOOST_CHECK(app.testModule.anotherGroup.foo.getOwner() == + &(app.testModule.anotherGroup)); } /*********************************************************************************************************************/ /* test that modules cannot be owned by the wrong types */ -BOOST_AUTO_TEST_CASE( test_badHierarchies ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(test_badHierarchies) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> test_badHierarchies" << std::endl; // ****************************************** @@ -230,8 +258,7 @@ BOOST_AUTO_TEST_CASE( test_badHierarchies ) { try { TestModule willFail(&(app.testModule), "willFail", ""); BOOST_FAIL("Exception expected"); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } } @@ -241,8 +268,7 @@ BOOST_AUTO_TEST_CASE( test_badHierarchies ) { try { TestModule willFail(&(app.testModule.someGroup), "willFail", ""); BOOST_FAIL("Exception expected"); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } } @@ -252,8 +278,7 @@ BOOST_AUTO_TEST_CASE( test_badHierarchies ) { try { TestModule willFail(nullptr, "willFail", ""); BOOST_FAIL("Exception expected"); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } } @@ -266,8 +291,7 @@ BOOST_AUTO_TEST_CASE( test_badHierarchies ) { try { SomeGroup willFail(&(app), "willFail", ""); BOOST_FAIL("Exception expected"); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } } @@ -278,8 +302,7 @@ BOOST_AUTO_TEST_CASE( test_badHierarchies ) { try { SomeGroup willFail(&(app.vectorOfVectorModuleGroup[0]), "willFail", ""); BOOST_FAIL("Exception expected"); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } } @@ -289,8 +312,7 @@ BOOST_AUTO_TEST_CASE( test_badHierarchies ) { try { SomeGroup willFail(nullptr, "willFail", ""); BOOST_FAIL("Exception expected"); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } } @@ -303,8 +325,7 @@ BOOST_AUTO_TEST_CASE( test_badHierarchies ) { try { VectorModuleGroup willFail(&(app.testModule), "willFail", "", 1); BOOST_FAIL("Exception expected"); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } } @@ -312,10 +333,10 @@ BOOST_AUTO_TEST_CASE( test_badHierarchies ) { { OneModuleApp app; try { - VectorModuleGroup willFail(&(app.testModule.someGroup), "willFail", "", 1); + VectorModuleGroup willFail(&(app.testModule.someGroup), "willFail", "", + 1); BOOST_FAIL("Exception expected"); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } } @@ -325,18 +346,18 @@ BOOST_AUTO_TEST_CASE( test_badHierarchies ) { try { VectorModuleGroup willFail(nullptr, "willFail", "", 1); BOOST_FAIL("Exception expected"); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } } - } /*********************************************************************************************************************/ /* test that modules can be owned by the right types */ -BOOST_AUTO_TEST_CASE( test_allowedHierarchies ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(test_allowedHierarchies) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> test_allowedHierarchies" << std::endl; // ****************************************** @@ -351,7 +372,8 @@ BOOST_AUTO_TEST_CASE( test_allowedHierarchies ) { { VectorOfEverythingApp app(1); app.defineConnections(); - TestModule shouldNotFail(&(app.vectorOfVectorModuleGroup[0]), "shouldNotFail", ""); + TestModule shouldNotFail(&(app.vectorOfVectorModuleGroup[0]), + "shouldNotFail", ""); } // ****************************************** @@ -382,228 +404,273 @@ BOOST_AUTO_TEST_CASE( test_allowedHierarchies ) { { VectorOfEverythingApp app(1); app.defineConnections(); - VectorModuleGroup shouldNotFail(&(app.vectorOfVectorModuleGroup[0]), "shouldNotFail", "", 1); + VectorModuleGroup shouldNotFail(&(app.vectorOfVectorModuleGroup[0]), + "shouldNotFail", "", 1); } - } /*********************************************************************************************************************/ /* test getSubmoduleList() and getSubmoduleListRecursive() */ -BOOST_AUTO_TEST_CASE( test_getSubmoduleList ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(test_getSubmoduleList) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> test_getSubmoduleList" << std::endl; OneModuleApp app; { - std::list<ctk::Module*> list = app.getSubmoduleList(); - BOOST_CHECK( list.size() == 1 ); - BOOST_CHECK( list.front() == &(app.testModule) ); + std::list<ctk::Module *> list = app.getSubmoduleList(); + BOOST_CHECK(list.size() == 1); + BOOST_CHECK(list.front() == &(app.testModule)); } { - std::list<ctk::Module*> list = app.testModule.getSubmoduleList(); - BOOST_CHECK( list.size() == 2 ); + std::list<ctk::Module *> list = app.testModule.getSubmoduleList(); + BOOST_CHECK(list.size() == 2); size_t foundSomeGroup = 0; size_t foundAnotherGroup = 0; - for(auto mod : list) { - if(mod == &(app.testModule.someGroup)) foundSomeGroup++; - if(mod == &(app.testModule.anotherGroup)) foundAnotherGroup++; - } - BOOST_CHECK( foundSomeGroup == 1 ); - BOOST_CHECK( foundAnotherGroup == 1 ); + for (auto mod : list) { + if (mod == &(app.testModule.someGroup)) + foundSomeGroup++; + if (mod == &(app.testModule.anotherGroup)) + foundAnotherGroup++; + } + BOOST_CHECK(foundSomeGroup == 1); + BOOST_CHECK(foundAnotherGroup == 1); } { - std::list<ctk::Module*> list = app.getSubmoduleListRecursive(); - BOOST_CHECK( list.size() == 3 ); + std::list<ctk::Module *> list = app.getSubmoduleListRecursive(); + BOOST_CHECK(list.size() == 3); size_t foundTestModule = 0; size_t foundSomeGroup = 0; size_t foundAnotherGroup = 0; - for(auto mod : list) { - if(mod == &(app.testModule)) foundTestModule++; - if(mod == &(app.testModule.someGroup)) foundSomeGroup++; - if(mod == &(app.testModule.anotherGroup)) foundAnotherGroup++; - } - BOOST_CHECK( foundTestModule == 1 ); - BOOST_CHECK( foundSomeGroup == 1 ); - BOOST_CHECK( foundAnotherGroup == 1 ); + for (auto mod : list) { + if (mod == &(app.testModule)) + foundTestModule++; + if (mod == &(app.testModule.someGroup)) + foundSomeGroup++; + if (mod == &(app.testModule.anotherGroup)) + foundAnotherGroup++; + } + BOOST_CHECK(foundTestModule == 1); + BOOST_CHECK(foundSomeGroup == 1); + BOOST_CHECK(foundAnotherGroup == 1); } { - std::list<ctk::Module*> list = app.testModule.getSubmoduleListRecursive(); // identical to getSubmoduleList(), since no deeper hierarchies - BOOST_CHECK( list.size() == 2 ); + std::list<ctk::Module *> list = + app.testModule + .getSubmoduleListRecursive(); // identical to getSubmoduleList(), + // since no deeper hierarchies + BOOST_CHECK(list.size() == 2); size_t foundSomeGroup = 0; size_t foundAnotherGroup = 0; - for(auto mod : list) { - if(mod == &(app.testModule.someGroup)) foundSomeGroup++; - if(mod == &(app.testModule.anotherGroup)) foundAnotherGroup++; - } - BOOST_CHECK( foundSomeGroup == 1 ); - BOOST_CHECK( foundAnotherGroup == 1 ); + for (auto mod : list) { + if (mod == &(app.testModule.someGroup)) + foundSomeGroup++; + if (mod == &(app.testModule.anotherGroup)) + foundAnotherGroup++; + } + BOOST_CHECK(foundSomeGroup == 1); + BOOST_CHECK(foundAnotherGroup == 1); } - } /*********************************************************************************************************************/ /* test getAccessorList() and getAccessorListRecursive() */ -BOOST_AUTO_TEST_CASE( test_getAccessorList ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(test_getAccessorList) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> test_getAccessorList" << std::endl; OneModuleApp app; { std::list<ctk::VariableNetworkNode> list = app.testModule.getAccessorList(); - BOOST_CHECK( list.size() == 2 ); + BOOST_CHECK(list.size() == 2); size_t foundSomeInput = 0; size_t foundSomeOutput = 0; - for(auto var : list) { - if(var == app.testModule.someInput) foundSomeInput++; - if(var == app.testModule.someOutput) foundSomeOutput++; - } - BOOST_CHECK( foundSomeInput == 1 ); - BOOST_CHECK( foundSomeOutput == 1 ); + for (auto var : list) { + if (var == app.testModule.someInput) + foundSomeInput++; + if (var == app.testModule.someOutput) + foundSomeOutput++; + } + BOOST_CHECK(foundSomeInput == 1); + BOOST_CHECK(foundSomeOutput == 1); } { const SomeGroup &someGroup(app.testModule.someGroup); - const std::list<ctk::VariableNetworkNode> list = someGroup.getAccessorList(); - BOOST_CHECK( list.size() == 2 ); + const std::list<ctk::VariableNetworkNode> list = + someGroup.getAccessorList(); + BOOST_CHECK(list.size() == 2); size_t foundInGroup = 0; size_t foundAlsoInGroup = 0; - for(auto var : list) { - if(var == app.testModule.someGroup.inGroup) foundInGroup++; - if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++; - } - BOOST_CHECK( foundInGroup == 1 ); - BOOST_CHECK( foundAlsoInGroup == 1 ); + for (auto var : list) { + if (var == app.testModule.someGroup.inGroup) + foundInGroup++; + if (var == app.testModule.someGroup.alsoInGroup) + foundAlsoInGroup++; + } + BOOST_CHECK(foundInGroup == 1); + BOOST_CHECK(foundAlsoInGroup == 1); } { std::list<ctk::VariableNetworkNode> list = app.getAccessorListRecursive(); - BOOST_CHECK( list.size() == 5 ); + BOOST_CHECK(list.size() == 5); size_t foundSomeInput = 0; size_t foundSomeOutput = 0; size_t foundInGroup = 0; size_t foundAlsoInGroup = 0; size_t foundFoo = 0; - for(auto var : list) { - if(var == app.testModule.someInput) foundSomeInput++; - if(var == app.testModule.someOutput) foundSomeOutput++; - if(var == app.testModule.someGroup.inGroup) foundInGroup++; - if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++; - if(var == app.testModule.anotherGroup.foo) foundFoo++; - } - BOOST_CHECK( foundSomeInput == 1 ); - BOOST_CHECK( foundSomeOutput == 1 ); - BOOST_CHECK( foundInGroup == 1 ); - BOOST_CHECK( foundAlsoInGroup == 1 ); - BOOST_CHECK( foundFoo == 1 ); + for (auto var : list) { + if (var == app.testModule.someInput) + foundSomeInput++; + if (var == app.testModule.someOutput) + foundSomeOutput++; + if (var == app.testModule.someGroup.inGroup) + foundInGroup++; + if (var == app.testModule.someGroup.alsoInGroup) + foundAlsoInGroup++; + if (var == app.testModule.anotherGroup.foo) + foundFoo++; + } + BOOST_CHECK(foundSomeInput == 1); + BOOST_CHECK(foundSomeOutput == 1); + BOOST_CHECK(foundInGroup == 1); + BOOST_CHECK(foundAlsoInGroup == 1); + BOOST_CHECK(foundFoo == 1); } { - std::list<ctk::VariableNetworkNode> list = app.testModule.getAccessorListRecursive(); - BOOST_CHECK( list.size() == 5 ); + std::list<ctk::VariableNetworkNode> list = + app.testModule.getAccessorListRecursive(); + BOOST_CHECK(list.size() == 5); size_t foundSomeInput = 0; size_t foundSomeOutput = 0; size_t foundInGroup = 0; size_t foundAlsoInGroup = 0; size_t foundFoo = 0; - for(auto var : list) { - if(var == app.testModule.someInput) foundSomeInput++; - if(var == app.testModule.someOutput) foundSomeOutput++; - if(var == app.testModule.someGroup.inGroup) foundInGroup++; - if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++; - if(var == app.testModule.anotherGroup.foo) foundFoo++; - } - BOOST_CHECK( foundSomeInput == 1 ); - BOOST_CHECK( foundSomeOutput == 1 ); - BOOST_CHECK( foundInGroup == 1 ); - BOOST_CHECK( foundAlsoInGroup == 1 ); - BOOST_CHECK( foundFoo == 1 ); + for (auto var : list) { + if (var == app.testModule.someInput) + foundSomeInput++; + if (var == app.testModule.someOutput) + foundSomeOutput++; + if (var == app.testModule.someGroup.inGroup) + foundInGroup++; + if (var == app.testModule.someGroup.alsoInGroup) + foundAlsoInGroup++; + if (var == app.testModule.anotherGroup.foo) + foundFoo++; + } + BOOST_CHECK(foundSomeInput == 1); + BOOST_CHECK(foundSomeOutput == 1); + BOOST_CHECK(foundInGroup == 1); + BOOST_CHECK(foundAlsoInGroup == 1); + BOOST_CHECK(foundFoo == 1); } { - std::list<ctk::VariableNetworkNode> list = app.testModule.anotherGroup.getAccessorListRecursive(); - BOOST_CHECK( list.size() == 1 ); + std::list<ctk::VariableNetworkNode> list = + app.testModule.anotherGroup.getAccessorListRecursive(); + BOOST_CHECK(list.size() == 1); size_t foundFoo = 0; - for(auto var : list) { - if(var == app.testModule.anotherGroup.foo) foundFoo++; + for (auto var : list) { + if (var == app.testModule.anotherGroup.foo) + foundFoo++; } - BOOST_CHECK( foundFoo == 1 ); + BOOST_CHECK(foundFoo == 1); } - } /*********************************************************************************************************************/ /* test function call operator of the ApplicationModule */ -BOOST_AUTO_TEST_CASE( testApplicationModuleFnCallOp ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testApplicationModuleFnCallOp) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testApplicationModuleFnCallOp" << std::endl; OneModuleApp app; - BOOST_CHECK( app.testModule("nameOfSomeInput") == static_cast<ctk::VariableNetworkNode>(app.testModule.someInput) ); - BOOST_CHECK( app.testModule("nameOfSomeInput") != static_cast<ctk::VariableNetworkNode>(app.testModule.someOutput) ); - BOOST_CHECK( app.testModule("someOutput") == static_cast<ctk::VariableNetworkNode>(app.testModule.someOutput) ); - - BOOST_CHECK( app.testModule("nameOfSomeInput").getType() == ctk::NodeType::Application ); - BOOST_CHECK( app.testModule("nameOfSomeInput").getMode() == ctk::UpdateMode::push ); - BOOST_CHECK( app.testModule("nameOfSomeInput").getDirection().dir == ctk::VariableDirection::consuming ); - BOOST_CHECK( app.testModule("nameOfSomeInput").getDirection().withReturn == false ); - BOOST_CHECK( app.testModule("nameOfSomeInput").getValueType() == typeid(int) ); - BOOST_CHECK( app.testModule("nameOfSomeInput").getName() == "nameOfSomeInput" ); - BOOST_CHECK( app.testModule("nameOfSomeInput").getQualifiedName() == "/myApp/testModule/nameOfSomeInput" ); - BOOST_CHECK( app.testModule("nameOfSomeInput").getUnit() == "cm" ); - BOOST_CHECK( app.testModule("nameOfSomeInput").getDescription() == "Module to test - This is just some input for testing" ); - BOOST_CHECK( app.testModule("nameOfSomeInput").getTags() == std::unordered_set<std::string>({"A", "B"}) ); + BOOST_CHECK(app.testModule("nameOfSomeInput") == + static_cast<ctk::VariableNetworkNode>(app.testModule.someInput)); + BOOST_CHECK(app.testModule("nameOfSomeInput") != + static_cast<ctk::VariableNetworkNode>(app.testModule.someOutput)); + BOOST_CHECK(app.testModule("someOutput") == + static_cast<ctk::VariableNetworkNode>(app.testModule.someOutput)); + + BOOST_CHECK(app.testModule("nameOfSomeInput").getType() == + ctk::NodeType::Application); + BOOST_CHECK(app.testModule("nameOfSomeInput").getMode() == + ctk::UpdateMode::push); + BOOST_CHECK(app.testModule("nameOfSomeInput").getDirection().dir == + ctk::VariableDirection::consuming); + BOOST_CHECK(app.testModule("nameOfSomeInput").getDirection().withReturn == + false); + BOOST_CHECK(app.testModule("nameOfSomeInput").getValueType() == typeid(int)); + BOOST_CHECK(app.testModule("nameOfSomeInput").getName() == "nameOfSomeInput"); + BOOST_CHECK(app.testModule("nameOfSomeInput").getQualifiedName() == + "/myApp/testModule/nameOfSomeInput"); + BOOST_CHECK(app.testModule("nameOfSomeInput").getUnit() == "cm"); + BOOST_CHECK(app.testModule("nameOfSomeInput").getDescription() == + "Module to test - This is just some input for testing"); + BOOST_CHECK(app.testModule("nameOfSomeInput").getTags() == + std::unordered_set<std::string>({"A", "B"})); // check exception if variable not found try { app.testModule("notExisting"); BOOST_FAIL("Exception expected"); + } catch (ChimeraTK::logic_error &) { } - catch(ChimeraTK::logic_error&){ - } - } /*********************************************************************************************************************/ /* test function call operator of the ApplicationModule */ -BOOST_AUTO_TEST_CASE( testApplicationModuleSubscriptOp ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testApplicationModuleSubscriptOp) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testApplicationModuleSubscriptOp" << std::endl; OneModuleApp app; - BOOST_CHECK( app.testModule["someGroup"].getName() == "someGroup" ); - BOOST_CHECK( app.testModule["anotherName"].getName() == "anotherName" ); + BOOST_CHECK(app.testModule["someGroup"].getName() == "someGroup"); + BOOST_CHECK(app.testModule["anotherName"].getName() == "anotherName"); - BOOST_CHECK( app.testModule["someGroup"]("inGroup") == app.testModule.someGroup.inGroup ); - BOOST_CHECK( app.testModule["someGroup"]("alsoInGroup") == app.testModule.someGroup.alsoInGroup ); + BOOST_CHECK(app.testModule["someGroup"]("inGroup") == + app.testModule.someGroup.inGroup); + BOOST_CHECK(app.testModule["someGroup"]("alsoInGroup") == + app.testModule.someGroup.alsoInGroup); - BOOST_CHECK( app.testModule["anotherName"]("foo") == app.testModule.anotherGroup.foo ); + BOOST_CHECK(app.testModule["anotherName"]("foo") == + app.testModule.anotherGroup.foo); // check exception if group not found try { app.testModule["notExisting"]; BOOST_FAIL("Exception expected"); + } catch (ChimeraTK::logic_error &) { } - catch(ChimeraTK::logic_error&){ - } - } /*********************************************************************************************************************/ /* test finding variables by tag */ -BOOST_AUTO_TEST_CASE( testFindTags ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testFindTags) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testFindTags" << std::endl; OneModuleApp app; @@ -615,37 +682,41 @@ BOOST_AUTO_TEST_CASE( testFindTags ) { // check direct variables { std::list<ctk::VariableNetworkNode> list = tagA.getAccessorList(); - BOOST_CHECK( list.size() == 2 ); + BOOST_CHECK(list.size() == 2); size_t foundSomeInput = 0; size_t foundSomeOutput = 0; - for(auto var : list) { - if(var == app.testModule.someInput) foundSomeInput++; - if(var == app.testModule.someOutput) foundSomeOutput++; + for (auto var : list) { + if (var == app.testModule.someInput) + foundSomeInput++; + if (var == app.testModule.someOutput) + foundSomeOutput++; } - BOOST_CHECK( foundSomeInput == 1 ); - BOOST_CHECK( foundSomeOutput == 1 ); + BOOST_CHECK(foundSomeInput == 1); + BOOST_CHECK(foundSomeOutput == 1); } // check number of submodules { - std::list<ctk::Module*> list = tagA.getSubmoduleList(); - BOOST_CHECK( list.size() == 1 ); + std::list<ctk::Module *> list = tagA.getSubmoduleList(); + BOOST_CHECK(list.size() == 1); } // check content of submodule { - std::list<ctk::VariableNetworkNode> list = tagA["someGroup"].getAccessorList(); - BOOST_CHECK( list.size() == 2 ); + std::list<ctk::VariableNetworkNode> list = + tagA["someGroup"].getAccessorList(); + BOOST_CHECK(list.size() == 2); size_t foundInGroup = 0; size_t foundAlsoInGroup = 0; - for(auto var : list) { - if(var == app.testModule.someGroup.inGroup) foundInGroup++; - if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++; + for (auto var : list) { + if (var == app.testModule.someGroup.inGroup) + foundInGroup++; + if (var == app.testModule.someGroup.alsoInGroup) + foundAlsoInGroup++; } - BOOST_CHECK( foundInGroup == 1 ); - BOOST_CHECK( foundAlsoInGroup == 1 ); + BOOST_CHECK(foundInGroup == 1); + BOOST_CHECK(foundAlsoInGroup == 1); } - } // search for tag "D" @@ -655,37 +726,40 @@ BOOST_AUTO_TEST_CASE( testFindTags ) { // check direct variables { std::list<ctk::VariableNetworkNode> list = tagD.getAccessorList(); - BOOST_CHECK( list.size() == 0 ); + BOOST_CHECK(list.size() == 0); } // check number of submodules { - std::list<ctk::Module*> list = tagD.getSubmoduleList(); - BOOST_CHECK( list.size() == 2 ); + std::list<ctk::Module *> list = tagD.getSubmoduleList(); + BOOST_CHECK(list.size() == 2); } // check content of submodule "someGroup" { - std::list<ctk::VariableNetworkNode> list = tagD["someGroup"].getAccessorList(); - BOOST_CHECK( list.size() == 1 ); + std::list<ctk::VariableNetworkNode> list = + tagD["someGroup"].getAccessorList(); + BOOST_CHECK(list.size() == 1); size_t foundAlsoInGroup = 0; - for(auto var : list) { - if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++; + for (auto var : list) { + if (var == app.testModule.someGroup.alsoInGroup) + foundAlsoInGroup++; } - BOOST_CHECK( foundAlsoInGroup == 1 ); + BOOST_CHECK(foundAlsoInGroup == 1); } // check content of submodule "anotherName" { - std::list<ctk::VariableNetworkNode> list = tagD["anotherName"].getAccessorList(); - BOOST_CHECK( list.size() == 1 ); + std::list<ctk::VariableNetworkNode> list = + tagD["anotherName"].getAccessorList(); + BOOST_CHECK(list.size() == 1); size_t foundFoo = 0; - for(auto var : list) { - if(var == app.testModule.anotherGroup.foo) foundFoo++; + for (auto var : list) { + if (var == app.testModule.anotherGroup.foo) + foundFoo++; } - BOOST_CHECK( foundFoo == 1 ); + BOOST_CHECK(foundFoo == 1); } - } // search for tag "D", exclude tag "A" @@ -695,34 +769,37 @@ BOOST_AUTO_TEST_CASE( testFindTags ) { // check direct variables { std::list<ctk::VariableNetworkNode> list = tagDnotA.getAccessorList(); - BOOST_CHECK( list.size() == 0 ); + BOOST_CHECK(list.size() == 0); } // check number of submodules { - std::list<ctk::Module*> list = tagDnotA.getSubmoduleList(); - BOOST_CHECK( list.size() == 1 ); + std::list<ctk::Module *> list = tagDnotA.getSubmoduleList(); + BOOST_CHECK(list.size() == 1); } // check content of submodule "anotherName" { - std::list<ctk::VariableNetworkNode> list = tagDnotA["anotherName"].getAccessorList(); - BOOST_CHECK( list.size() == 1 ); + std::list<ctk::VariableNetworkNode> list = + tagDnotA["anotherName"].getAccessorList(); + BOOST_CHECK(list.size() == 1); size_t foundFoo = 0; - for(auto var : list) { - if(var == app.testModule.anotherGroup.foo) foundFoo++; + for (auto var : list) { + if (var == app.testModule.anotherGroup.foo) + foundFoo++; } - BOOST_CHECK( foundFoo == 1 ); + BOOST_CHECK(foundFoo == 1); } - } } /*********************************************************************************************************************/ /* test flatten() */ -BOOST_AUTO_TEST_CASE( testFlatten ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testFlatten) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testFlatten" << std::endl; OneModuleApp app; @@ -731,39 +808,46 @@ BOOST_AUTO_TEST_CASE( testFlatten ) { // check number of submodules { - std::list<ctk::Module*> list = flattened.getSubmoduleList(); - BOOST_CHECK( list.size() == 0 ); + std::list<ctk::Module *> list = flattened.getSubmoduleList(); + BOOST_CHECK(list.size() == 0); } // check direct variables { std::list<ctk::VariableNetworkNode> list = flattened.getAccessorList(); - BOOST_CHECK( list.size() == 5 ); + BOOST_CHECK(list.size() == 5); size_t foundSomeInput = 0; size_t foundSomeOutput = 0; size_t foundInGroup = 0; size_t foundAlsoInGroup = 0; size_t foundFoo = 0; - for(auto var : list) { - if(var == app.testModule.someInput) foundSomeInput++; - if(var == app.testModule.someOutput) foundSomeOutput++; - if(var == app.testModule.someGroup.inGroup) foundInGroup++; - if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++; - if(var == app.testModule.anotherGroup.foo) foundFoo++; - } - BOOST_CHECK( foundSomeInput == 1 ); - BOOST_CHECK( foundSomeOutput == 1 ); - BOOST_CHECK( foundInGroup == 1 ); - BOOST_CHECK( foundAlsoInGroup == 1 ); - BOOST_CHECK( foundFoo == 1 ); + for (auto var : list) { + if (var == app.testModule.someInput) + foundSomeInput++; + if (var == app.testModule.someOutput) + foundSomeOutput++; + if (var == app.testModule.someGroup.inGroup) + foundInGroup++; + if (var == app.testModule.someGroup.alsoInGroup) + foundAlsoInGroup++; + if (var == app.testModule.anotherGroup.foo) + foundFoo++; + } + BOOST_CHECK(foundSomeInput == 1); + BOOST_CHECK(foundSomeOutput == 1); + BOOST_CHECK(foundInGroup == 1); + BOOST_CHECK(foundAlsoInGroup == 1); + BOOST_CHECK(foundFoo == 1); } } /*********************************************************************************************************************/ /* test addTag() */ -BOOST_AUTO_TEST_CASE( testAddTag ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testAddTag) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testAddTag" << std::endl; OneModuleApp app; @@ -773,147 +857,183 @@ BOOST_AUTO_TEST_CASE( testAddTag ) { // check submodule hierarchy { - BOOST_CHECK( withNewTag.getSubmoduleList().size() == 1 ); - BOOST_CHECK( withNewTag.getAccessorList().size() == 0 ); - BOOST_CHECK( withNewTag["testModule"].getSubmoduleList().size() == 2 ); - BOOST_CHECK( withNewTag["testModule"].getAccessorList().size() == 2 ); - BOOST_CHECK( withNewTag["testModule"]["someGroup"].getSubmoduleList().size() == 0 ); - BOOST_CHECK( withNewTag["testModule"]["someGroup"].getAccessorList().size() == 2 ); - BOOST_CHECK( withNewTag["testModule"]["anotherName"].getSubmoduleList().size() == 0 ); - BOOST_CHECK( withNewTag["testModule"]["anotherName"].getAccessorList().size() == 1 ); + BOOST_CHECK(withNewTag.getSubmoduleList().size() == 1); + BOOST_CHECK(withNewTag.getAccessorList().size() == 0); + BOOST_CHECK(withNewTag["testModule"].getSubmoduleList().size() == 2); + BOOST_CHECK(withNewTag["testModule"].getAccessorList().size() == 2); + BOOST_CHECK( + withNewTag["testModule"]["someGroup"].getSubmoduleList().size() == 0); + BOOST_CHECK( + withNewTag["testModule"]["someGroup"].getAccessorList().size() == 2); + BOOST_CHECK( + withNewTag["testModule"]["anotherName"].getSubmoduleList().size() == 0); + BOOST_CHECK( + withNewTag["testModule"]["anotherName"].getAccessorList().size() == 1); } // check all variables { - std::list<ctk::VariableNetworkNode> list = withNewTag.getAccessorListRecursive(); - BOOST_CHECK( list.size() == 5 ); + std::list<ctk::VariableNetworkNode> list = + withNewTag.getAccessorListRecursive(); + BOOST_CHECK(list.size() == 5); size_t foundSomeInput = 0; size_t foundSomeOutput = 0; size_t foundInGroup = 0; size_t foundAlsoInGroup = 0; size_t foundFoo = 0; - for(auto var : list) { - if(var == app.testModule.someInput) foundSomeInput++; - if(var == app.testModule.someOutput) foundSomeOutput++; - if(var == app.testModule.someGroup.inGroup) foundInGroup++; - if(var == app.testModule.someGroup.alsoInGroup) foundAlsoInGroup++; - if(var == app.testModule.anotherGroup.foo) foundFoo++; - } - BOOST_CHECK( foundSomeInput == 1 ); - BOOST_CHECK( foundSomeOutput == 1 ); - BOOST_CHECK( foundInGroup == 1 ); - BOOST_CHECK( foundAlsoInGroup == 1 ); - BOOST_CHECK( foundFoo == 1 ); + for (auto var : list) { + if (var == app.testModule.someInput) + foundSomeInput++; + if (var == app.testModule.someOutput) + foundSomeOutput++; + if (var == app.testModule.someGroup.inGroup) + foundInGroup++; + if (var == app.testModule.someGroup.alsoInGroup) + foundAlsoInGroup++; + if (var == app.testModule.anotherGroup.foo) + foundFoo++; + } + BOOST_CHECK(foundSomeInput == 1); + BOOST_CHECK(foundSomeOutput == 1); + BOOST_CHECK(foundInGroup == 1); + BOOST_CHECK(foundAlsoInGroup == 1); + BOOST_CHECK(foundFoo == 1); } } /*********************************************************************************************************************/ /* test correct behaviour when using a std::vector of ApplicationModules */ -BOOST_AUTO_TEST_CASE( testVectorOfApplicationModule ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testVectorOfApplicationModule) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testVectorOfApplicationModule" << std::endl; // create app with a vector containing 10 modules size_t nInstances = 10; VectorOfModulesApp app(nInstances); - // the app creates the 10 module instances in defineConnections, check if this is done proplery (a quite redundant test...) + // the app creates the 10 module instances in defineConnections, check if this + // is done proplery (a quite redundant test...) BOOST_CHECK(app.vectorOfTestModule.size() == 0); app.defineConnections(); BOOST_CHECK(app.vectorOfTestModule.size() == nInstances); // some direct checks on the created instances - for(size_t i=0; i<nInstances; ++i) { + for (size_t i = 0; i < nInstances; ++i) { std::string name = "testModule_" + std::to_string(i) + "_instance"; - BOOST_CHECK( app.vectorOfTestModule[i].getName() == name ); - auto node = static_cast<ctk::VariableNetworkNode>(app.vectorOfTestModule[i].someInput); - BOOST_CHECK( node.getQualifiedName() == "/myApp/"+name+"/nameOfSomeInput" ); + BOOST_CHECK(app.vectorOfTestModule[i].getName() == name); + auto node = static_cast<ctk::VariableNetworkNode>( + app.vectorOfTestModule[i].someInput); + BOOST_CHECK(node.getQualifiedName() == + "/myApp/" + name + "/nameOfSomeInput"); // check accessor list - std::list<ctk::VariableNetworkNode> accList = app.vectorOfTestModule[i].getAccessorList(); - BOOST_CHECK( accList.size() == 2 ); + std::list<ctk::VariableNetworkNode> accList = + app.vectorOfTestModule[i].getAccessorList(); + BOOST_CHECK(accList.size() == 2); size_t foundSomeInput = 0; size_t foundSomeOutput = 0; - for(auto &acc : accList) { - if(acc == app.vectorOfTestModule[i].someInput) foundSomeInput++; - if(acc == app.vectorOfTestModule[i].someOutput) foundSomeOutput++; + for (auto &acc : accList) { + if (acc == app.vectorOfTestModule[i].someInput) + foundSomeInput++; + if (acc == app.vectorOfTestModule[i].someOutput) + foundSomeOutput++; } - BOOST_CHECK( foundSomeInput == 1 ); - BOOST_CHECK( foundSomeOutput == 1 ); + BOOST_CHECK(foundSomeInput == 1); + BOOST_CHECK(foundSomeOutput == 1); // check submodule list - std::list<ctk::Module*> modList = app.vectorOfTestModule[i].getSubmoduleList(); - BOOST_CHECK( modList.size() == 2 ); + std::list<ctk::Module *> modList = + app.vectorOfTestModule[i].getSubmoduleList(); + BOOST_CHECK(modList.size() == 2); size_t foundSomeGroup = 0; size_t foundAnotherGroup = 0; - for(auto mod : modList) { - if(mod == &(app.vectorOfTestModule[i].someGroup)) foundSomeGroup++; - if(mod == &(app.vectorOfTestModule[i].anotherGroup)) foundAnotherGroup++; - } - BOOST_CHECK( foundSomeGroup == 1 ); - BOOST_CHECK( foundAnotherGroup == 1 ); + for (auto mod : modList) { + if (mod == &(app.vectorOfTestModule[i].someGroup)) + foundSomeGroup++; + if (mod == &(app.vectorOfTestModule[i].anotherGroup)) + foundAnotherGroup++; + } + BOOST_CHECK(foundSomeGroup == 1); + BOOST_CHECK(foundAnotherGroup == 1); } // check if instances appear properly in getSubmoduleList() { - std::list<ctk::Module*> list = app.getSubmoduleList(); - BOOST_CHECK( list.size() == nInstances ); + std::list<ctk::Module *> list = app.getSubmoduleList(); + BOOST_CHECK(list.size() == nInstances); std::map<size_t, size_t> instancesFound; - for(size_t i = 0; i < nInstances; ++i) instancesFound[i] = 0; - for(auto mod : list) { - for(size_t i = 0; i < nInstances; ++i) { - if(mod == &(app.vectorOfTestModule[i])) instancesFound[i]++; + for (size_t i = 0; i < nInstances; ++i) + instancesFound[i] = 0; + for (auto mod : list) { + for (size_t i = 0; i < nInstances; ++i) { + if (mod == &(app.vectorOfTestModule[i])) + instancesFound[i]++; } } - for(size_t i = 0; i < nInstances; ++i) { - BOOST_CHECK( instancesFound[i] == 1 ); + for (size_t i = 0; i < nInstances; ++i) { + BOOST_CHECK(instancesFound[i] == 1); } } // check if instances appear properly in getSubmoduleListRecursive() as well { - std::list<ctk::Module*> list = app.getSubmoduleListRecursive(); - BOOST_CHECK( list.size() == 3*nInstances ); - std::map<size_t, size_t> instancesFound, instancesSomeGroupFound, instancesAnotherGroupFound; - for(size_t i = 0; i < nInstances; ++i) { + std::list<ctk::Module *> list = app.getSubmoduleListRecursive(); + BOOST_CHECK(list.size() == 3 * nInstances); + std::map<size_t, size_t> instancesFound, instancesSomeGroupFound, + instancesAnotherGroupFound; + for (size_t i = 0; i < nInstances; ++i) { instancesFound[i] = 0; instancesSomeGroupFound[i] = 0; instancesAnotherGroupFound[i] = 0; } - for(auto mod : list) { - for(size_t i = 0; i < nInstances; ++i) { - if(mod == &(app.vectorOfTestModule[i])) instancesFound[i]++; - if(mod == &(app.vectorOfTestModule[i].someGroup)) instancesSomeGroupFound[i]++; - if(mod == &(app.vectorOfTestModule[i].anotherGroup)) instancesAnotherGroupFound[i]++; + for (auto mod : list) { + for (size_t i = 0; i < nInstances; ++i) { + if (mod == &(app.vectorOfTestModule[i])) + instancesFound[i]++; + if (mod == &(app.vectorOfTestModule[i].someGroup)) + instancesSomeGroupFound[i]++; + if (mod == &(app.vectorOfTestModule[i].anotherGroup)) + instancesAnotherGroupFound[i]++; } } - for(size_t i = 0; i < nInstances; ++i) { - BOOST_CHECK( instancesFound[i] == 1 ); - BOOST_CHECK( instancesSomeGroupFound[i] == 1 ); - BOOST_CHECK( instancesAnotherGroupFound[i] == 1 ); + for (size_t i = 0; i < nInstances; ++i) { + BOOST_CHECK(instancesFound[i] == 1); + BOOST_CHECK(instancesSomeGroupFound[i] == 1); + BOOST_CHECK(instancesAnotherGroupFound[i] == 1); } } // check ownerships - for(size_t i = 0; i < nInstances; ++i) { - BOOST_CHECK( app.vectorOfTestModule[i].getOwner() == &app ); - BOOST_CHECK( app.vectorOfTestModule[i].someInput.getOwner() == &(app.vectorOfTestModule[i]) ); - BOOST_CHECK( app.vectorOfTestModule[i].someOutput.getOwner() == &(app.vectorOfTestModule[i]) ); - BOOST_CHECK( app.vectorOfTestModule[i].someGroup.getOwner() == &(app.vectorOfTestModule[i]) ); - BOOST_CHECK( app.vectorOfTestModule[i].someGroup.inGroup.getOwner() == &(app.vectorOfTestModule[i].someGroup) ); - BOOST_CHECK( app.vectorOfTestModule[i].someGroup.alsoInGroup.getOwner() == &(app.vectorOfTestModule[i].someGroup) ); - BOOST_CHECK( app.vectorOfTestModule[i].anotherGroup.getOwner() == &(app.vectorOfTestModule[i]) ); - BOOST_CHECK( app.vectorOfTestModule[i].anotherGroup.foo.getOwner() == &(app.vectorOfTestModule[i].anotherGroup) ); + for (size_t i = 0; i < nInstances; ++i) { + BOOST_CHECK(app.vectorOfTestModule[i].getOwner() == &app); + BOOST_CHECK(app.vectorOfTestModule[i].someInput.getOwner() == + &(app.vectorOfTestModule[i])); + BOOST_CHECK(app.vectorOfTestModule[i].someOutput.getOwner() == + &(app.vectorOfTestModule[i])); + BOOST_CHECK(app.vectorOfTestModule[i].someGroup.getOwner() == + &(app.vectorOfTestModule[i])); + BOOST_CHECK(app.vectorOfTestModule[i].someGroup.inGroup.getOwner() == + &(app.vectorOfTestModule[i].someGroup)); + BOOST_CHECK(app.vectorOfTestModule[i].someGroup.alsoInGroup.getOwner() == + &(app.vectorOfTestModule[i].someGroup)); + BOOST_CHECK(app.vectorOfTestModule[i].anotherGroup.getOwner() == + &(app.vectorOfTestModule[i])); + BOOST_CHECK(app.vectorOfTestModule[i].anotherGroup.foo.getOwner() == + &(app.vectorOfTestModule[i].anotherGroup)); } - } /*********************************************************************************************************************/ -/* test correct behaviour when using a std::vector of ModuleGroup, ApplicationModule and VariableGroup at the same time */ +/* test correct behaviour when using a std::vector of ModuleGroup, + * ApplicationModule and VariableGroup at the same time */ -BOOST_AUTO_TEST_CASE( testVectorsOfAllModules ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testVectorsOfAllModules) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testVectorsOfAllModules" << std::endl; // create app with a vector containing 10 modules @@ -921,106 +1041,144 @@ BOOST_AUTO_TEST_CASE( testVectorsOfAllModules ) { VectorOfEverythingApp app(nInstances); //------------------------------------------------------------------------------------------------------------------- - // the app creates the 10 module instances in defineConnections, check if this is done proplery (a quite redundant - // test...) + // the app creates the 10 module instances in defineConnections, check if this + // is done proplery (a quite redundant test...) BOOST_CHECK(app.vectorOfVectorModuleGroup.size() == 0); app.defineConnections(); BOOST_CHECK(app.vectorOfVectorModuleGroup.size() == nInstances); - for(size_t i=0; i < nInstances; ++i) { - BOOST_CHECK(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule.size() == nInstances); - for(size_t k=0; k < nInstances; ++k) { - BOOST_CHECK(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup.size() == nInstances); + for (size_t i = 0; i < nInstances; ++i) { + BOOST_CHECK(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule.size() == + nInstances); + for (size_t k = 0; k < nInstances; ++k) { + BOOST_CHECK(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup.size() == nInstances); } } //------------------------------------------------------------------------------------------------------------------- // check presence in lists (getSubmoduleList() and getAccessorList()) - { // checks on first hierarchy level (application has the list of module groups) - std::list<ctk::Module*> list = app.getSubmoduleList(); - BOOST_CHECK( list.size() == nInstances ); + { // checks on first hierarchy level (application has the list of module + // groups) + std::list<ctk::Module *> list = app.getSubmoduleList(); + BOOST_CHECK(list.size() == nInstances); std::map<size_t, size_t> found; - for(size_t i=0; i < nInstances; ++i) found[i] = 0; - for(auto mod : list) { - for(size_t i=0; i < nInstances; ++i) { - if(mod == &(app.vectorOfVectorModuleGroup[i])) found[i]++; + for (size_t i = 0; i < nInstances; ++i) + found[i] = 0; + for (auto mod : list) { + for (size_t i = 0; i < nInstances; ++i) { + if (mod == &(app.vectorOfVectorModuleGroup[i])) + found[i]++; } } - for(size_t i=0; i < nInstances; ++i) BOOST_CHECK( found[i] == 1 ); - + for (size_t i = 0; i < nInstances; ++i) + BOOST_CHECK(found[i] == 1); } - { // checks on second hierarchy level (each module group has the list of modules) - for(size_t i=0; i < nInstances; ++i) { - std::list<ctk::Module*> list = app.vectorOfVectorModuleGroup[i].getSubmoduleList(); - BOOST_CHECK( list.size() == nInstances ); + { // checks on second hierarchy level (each module group has the list of + // modules) + for (size_t i = 0; i < nInstances; ++i) { + std::list<ctk::Module *> list = + app.vectorOfVectorModuleGroup[i].getSubmoduleList(); + BOOST_CHECK(list.size() == nInstances); std::map<size_t, size_t> found; - for(size_t k=0; k < nInstances; ++k) found[k] = 0; - for(auto mod : list) { - for(size_t k=0; k < nInstances; ++k) { - if(mod == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k])) found[k]++; + for (size_t k = 0; k < nInstances; ++k) + found[k] = 0; + for (auto mod : list) { + for (size_t k = 0; k < nInstances; ++k) { + if (mod == + &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k])) + found[k]++; } } - for(size_t k=0; k < nInstances; ++k) BOOST_CHECK( found[k] == 1 ); - + for (size_t k = 0; k < nInstances; ++k) + BOOST_CHECK(found[k] == 1); } } - { // checks on third hierarchy level (each module has accessors and variable groups) - for(size_t i=0; i < nInstances; ++i) { - for(size_t k=0; k < nInstances; ++k) { + { // checks on third hierarchy level (each module has accessors and variable + // groups) + for (size_t i = 0; i < nInstances; ++i) { + for (size_t k = 0; k < nInstances; ++k) { // search for accessors - std::list<ctk::VariableNetworkNode> accList = app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].getAccessorList(); - BOOST_CHECK_EQUAL( accList.size(), 2 ); + std::list<ctk::VariableNetworkNode> accList = + app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .getAccessorList(); + BOOST_CHECK_EQUAL(accList.size(), 2); size_t someInputFound = 0; size_t someOutputFound = 0; - for(auto acc : accList) { - if(acc == app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someInput) someInputFound++; - if(acc == app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someOutput) someOutputFound++; + for (auto acc : accList) { + if (acc == app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .someInput) + someInputFound++; + if (acc == app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .someOutput) + someOutputFound++; } - BOOST_CHECK_EQUAL( someInputFound, 1 ); - BOOST_CHECK_EQUAL( someOutputFound, 1 ); + BOOST_CHECK_EQUAL(someInputFound, 1); + BOOST_CHECK_EQUAL(someOutputFound, 1); // search for variable groups - std::list<ctk::Module*> modList = app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].getSubmoduleList(); - BOOST_CHECK_EQUAL( modList.size(), nInstances + 1 ); + std::list<ctk::Module *> modList = app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .getSubmoduleList(); + BOOST_CHECK_EQUAL(modList.size(), nInstances + 1); std::map<size_t, size_t> someGroupFound; - for(size_t m=0; m < nInstances; ++m) someGroupFound[m] = 0; + for (size_t m = 0; m < nInstances; ++m) + someGroupFound[m] = 0; size_t anotherGroupFound = 0; - for(auto mod : modList) { - for(size_t m=0; m < nInstances; ++m) { - if(mod == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m])) someGroupFound[m]++; + for (auto mod : modList) { + for (size_t m = 0; m < nInstances; ++m) { + if (mod == &(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m])) + someGroupFound[m]++; } - if(mod == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].anotherGroup)) anotherGroupFound++; + if (mod == &(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .anotherGroup)) + anotherGroupFound++; } - for(size_t m=0; m < nInstances; ++m) { - BOOST_CHECK_EQUAL( someGroupFound[m], 1 ); + for (size_t m = 0; m < nInstances; ++m) { + BOOST_CHECK_EQUAL(someGroupFound[m], 1); } - BOOST_CHECK_EQUAL( anotherGroupFound, 1 ); - + BOOST_CHECK_EQUAL(anotherGroupFound, 1); } } } { // checks on fourth hierarchy level (each variable group has accessors) - for(size_t i=0; i < nInstances; ++i) { - for(size_t k=0; k < nInstances; ++k) { - for(size_t m=0; m < nInstances; ++m) { + for (size_t i = 0; i < nInstances; ++i) { + for (size_t k = 0; k < nInstances; ++k) { + for (size_t m = 0; m < nInstances; ++m) { // search for accessors std::list<ctk::VariableNetworkNode> accList = - app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].getAccessorList(); + app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .getAccessorList(); BOOST_CHECK_EQUAL(accList.size(), 2); size_t inGroupFound = 0; size_t alsoInGroupFound = 0; - for(auto acc : accList) { - if(acc == app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].inGroup) { + for (auto acc : accList) { + if (acc == app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .inGroup) { inGroupFound++; } - if(acc == app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].alsoInGroup) { + if (acc == app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .alsoInGroup) { alsoInGroupFound++; } } @@ -1028,9 +1186,12 @@ BOOST_AUTO_TEST_CASE( testVectorsOfAllModules ) { BOOST_CHECK_EQUAL(alsoInGroupFound, 1); // make sure no further subgroups exist - BOOST_CHECK_EQUAL(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k]. - vectorOfSomeGroup[m].getSubmoduleList().size(), 0); - + BOOST_CHECK_EQUAL(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .getSubmoduleList() + .size(), + 0); } } } @@ -1038,53 +1199,96 @@ BOOST_AUTO_TEST_CASE( testVectorsOfAllModules ) { //------------------------------------------------------------------------------------------------------------------- // check ownerships - for(size_t i = 0; i < nInstances; ++i) { - BOOST_CHECK( app.vectorOfVectorModuleGroup[i].getOwner() == &app ); - for(size_t k = 0; k < nInstances; ++k) { - BOOST_CHECK( app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].getOwner() == &(app.vectorOfVectorModuleGroup[i]) ); - BOOST_CHECK( app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someInput.getOwner() - == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k]) ); - BOOST_CHECK( app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someOutput.getOwner() - == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k]) ); - for(size_t m = 0; m < nInstances; ++m) { - BOOST_CHECK( app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].getOwner() - == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k]) ); - BOOST_CHECK( app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].inGroup.getOwner() - == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m]) ); - BOOST_CHECK( app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].alsoInGroup.getOwner() - == &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m]) ); + for (size_t i = 0; i < nInstances; ++i) { + BOOST_CHECK(app.vectorOfVectorModuleGroup[i].getOwner() == &app); + for (size_t k = 0; k < nInstances; ++k) { + BOOST_CHECK( + app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].getOwner() == + &(app.vectorOfVectorModuleGroup[i])); + BOOST_CHECK(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .someInput.getOwner() == + &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k])); + BOOST_CHECK(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .someOutput.getOwner() == + &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k])); + for (size_t m = 0; m < nInstances; ++m) { + BOOST_CHECK( + app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .getOwner() == + &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k])); + BOOST_CHECK(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .inGroup.getOwner() == + &(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m])); + BOOST_CHECK(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .alsoInGroup.getOwner() == + &(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m])); } } } //------------------------------------------------------------------------------------------------------------------- // check pointers to accessors in VariableNetworkNode - for(size_t i = 0; i < nInstances; ++i) { - for(size_t k = 0; k < nInstances; ++k) { + for (size_t i = 0; i < nInstances; ++i) { + for (size_t k = 0; k < nInstances; ++k) { { auto a = &(static_cast<ctk::VariableNetworkNode>( - app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someInput).getAppAccessorNoType()); - auto b = &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someInput); - BOOST_CHECK( a == b ); + app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .someInput) + .getAppAccessorNoType()); + auto b = &( + app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someInput); + BOOST_CHECK(a == b); } { auto a = &(static_cast<ctk::VariableNetworkNode>( - app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someOutput).getAppAccessorNoType()); - auto b = &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].someOutput); - BOOST_CHECK( a == b ); + app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .someOutput) + .getAppAccessorNoType()); + auto b = &(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .someOutput); + BOOST_CHECK(a == b); } - for(size_t m = 0; m < nInstances; ++m) { + for (size_t m = 0; m < nInstances; ++m) { { auto a = &(static_cast<ctk::VariableNetworkNode>( - app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].inGroup).getAppAccessorNoType()); - auto b = &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].inGroup); - BOOST_CHECK( a == b ); + app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .inGroup) + .getAppAccessorNoType()); + auto b = &(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .inGroup); + BOOST_CHECK(a == b); } { auto a = &(static_cast<ctk::VariableNetworkNode>( - app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].alsoInGroup).getAppAccessorNoType()); - auto b = &(app.vectorOfVectorModuleGroup[i].vectorOfVectorModule[k].vectorOfSomeGroup[m].alsoInGroup); - BOOST_CHECK( a == b ); + app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .alsoInGroup) + .getAppAccessorNoType()); + auto b = &(app.vectorOfVectorModuleGroup[i] + .vectorOfVectorModule[k] + .vectorOfSomeGroup[m] + .alsoInGroup); + BOOST_CHECK(a == b); } } } @@ -1093,30 +1297,30 @@ BOOST_AUTO_TEST_CASE( testVectorsOfAllModules ) { //------------------------------------------------------------------------------------------------------------------- // search for tags and check result auto searchResult = app.findTag("A"); - std::list<ctk::Module*> list = searchResult.getSubmoduleList(); + std::list<ctk::Module *> list = searchResult.getSubmoduleList(); { // checks on first hierarchy level BOOST_CHECK(list.size() == nInstances); std::map<std::string, int> nameMap; - for(auto mod : list) { + for (auto mod : list) { nameMap[mod->getName()]++; } - for(size_t i=0; i < nInstances; ++i) { + for (size_t i = 0; i < nInstances; ++i) { std::string name = "testModule_" + std::to_string(i) + "_instance"; BOOST_CHECK(nameMap[name] == 1); } } { // checks on second hierarchy level - for(auto mod : list) { - std::list<ctk::Module*> list2 = mod->getSubmoduleList(); + for (auto mod : list) { + std::list<ctk::Module *> list2 = mod->getSubmoduleList(); BOOST_CHECK(list2.size() == nInstances); std::map<std::string, int> nameMap; - for(auto mod2 : list2) { + for (auto mod2 : list2) { nameMap[mod2->getName()]++; } - for(size_t i=0; i < nInstances; ++i) { + for (size_t i = 0; i < nInstances; ++i) { std::string name = "test_" + std::to_string(i); BOOST_CHECK(nameMap[name] == 1); } @@ -1124,17 +1328,17 @@ BOOST_AUTO_TEST_CASE( testVectorsOfAllModules ) { } { // checks on third hierarchy level - for(auto mod : list) { - std::list<ctk::Module*> list2 = mod->getSubmoduleList(); - for(auto mod2 : list2) { - std::list<ctk::Module*> list3 = mod2->getSubmoduleList(); + for (auto mod : list) { + std::list<ctk::Module *> list2 = mod->getSubmoduleList(); + for (auto mod2 : list2) { + std::list<ctk::Module *> list3 = mod2->getSubmoduleList(); BOOST_CHECK(list3.size() == nInstances); std::map<std::string, int> nameMap; - for(auto mod3 : list3) { + for (auto mod3 : list3) { nameMap[mod3->getName()]++; } - for(size_t i=0; i < nInstances; ++i) { + for (size_t i = 0; i < nInstances; ++i) { std::string name = "testGroup_" + std::to_string(i); BOOST_CHECK(nameMap[name] == 1); } @@ -1143,64 +1347,75 @@ BOOST_AUTO_TEST_CASE( testVectorsOfAllModules ) { } { // checks on fourth hierarchy level (actual variables) - for(auto mod : list) { - std::list<ctk::Module*> list2 = mod->getSubmoduleList(); - for(auto mod2 : list2) { - std::list<ctk::Module*> list3 = mod2->getSubmoduleList(); - for(auto mod3 : list3) { + for (auto mod : list) { + std::list<ctk::Module *> list2 = mod->getSubmoduleList(); + for (auto mod2 : list2) { + std::list<ctk::Module *> list3 = mod2->getSubmoduleList(); + for (auto mod3 : list3) { std::list<ctk::VariableNetworkNode> vars = mod3->getAccessorList(); BOOST_CHECK(vars.size() == 2); size_t foundInGroup = 0; size_t foundAlsoInGroup = 0; - for(auto var : vars) { - if(var.getName() == "inGroup") ++foundInGroup; - if(var.getName() == "alsoInGroup") ++foundAlsoInGroup; + for (auto var : vars) { + if (var.getName() == "inGroup") + ++foundInGroup; + if (var.getName() == "alsoInGroup") + ++foundAlsoInGroup; } - BOOST_CHECK( foundInGroup == 1 ); - BOOST_CHECK( foundAlsoInGroup == 1 ); - + BOOST_CHECK(foundInGroup == 1); + BOOST_CHECK(foundAlsoInGroup == 1); } } } } - } /*********************************************************************************************************************/ /* test late initialisation of modules using the assignment operator */ -BOOST_AUTO_TEST_CASE( test_assignmentOperator ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(test_assignmentOperator) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> test_assignmentOperator" << std::endl; std::cout << std::endl; AssignModuleLaterApp app; - BOOST_CHECK( app.getSubmoduleList().size() == 0 ); - BOOST_CHECK_EQUAL( app.modGroupInstanceToAssignLater.getSubmoduleList().size(), 0 ); + BOOST_CHECK(app.getSubmoduleList().size() == 0); + BOOST_CHECK_EQUAL(app.modGroupInstanceToAssignLater.getSubmoduleList().size(), + 0); app.defineConnections(); - BOOST_CHECK( app.modGroupInstanceToAssignLater.getName() == "modGroupInstanceToAssignLater" ); - BOOST_CHECK( app.modGroupInstanceToAssignLater.getDescription() == "This instance of VectorModuleGroup was assigned using the operator=()" ); + BOOST_CHECK(app.modGroupInstanceToAssignLater.getName() == + "modGroupInstanceToAssignLater"); + BOOST_CHECK( + app.modGroupInstanceToAssignLater.getDescription() == + "This instance of VectorModuleGroup was assigned using the operator=()"); - BOOST_CHECK( app.modInstanceToAssignLater.getName() == "modInstanceToAssignLater" ); - BOOST_CHECK( app.modInstanceToAssignLater.getDescription() == "This instance of VectorModule was assigned using the operator=()" ); + BOOST_CHECK(app.modInstanceToAssignLater.getName() == + "modInstanceToAssignLater"); + BOOST_CHECK( + app.modInstanceToAssignLater.getDescription() == + "This instance of VectorModule was assigned using the operator=()"); auto list = app.getSubmoduleList(); - BOOST_CHECK( list.size() == 2 ); + BOOST_CHECK(list.size() == 2); bool modGroupInstanceToAssignLater_found = false; bool modInstanceToAssignLater_found = false; - for(auto mod : list) { - if(mod == &(app.modGroupInstanceToAssignLater)) modGroupInstanceToAssignLater_found = true; - if(mod == &(app.modInstanceToAssignLater)) modInstanceToAssignLater_found = true; + for (auto mod : list) { + if (mod == &(app.modGroupInstanceToAssignLater)) + modGroupInstanceToAssignLater_found = true; + if (mod == &(app.modInstanceToAssignLater)) + modInstanceToAssignLater_found = true; } BOOST_CHECK(modGroupInstanceToAssignLater_found); BOOST_CHECK(modInstanceToAssignLater_found); - BOOST_CHECK_EQUAL( app.modGroupInstanceToAssignLater.getSubmoduleList().size(), 42 ); - BOOST_CHECK_EQUAL( app.modInstanceToAssignLater.getSubmoduleList().size(), 14 ); - + BOOST_CHECK_EQUAL(app.modGroupInstanceToAssignLater.getSubmoduleList().size(), + 42); + BOOST_CHECK_EQUAL(app.modInstanceToAssignLater.getSubmoduleList().size(), 14); } diff --git a/tests/executables_src/testTestFacilities.cc b/tests/executables_src/testTestFacilities.cc index 44222aa7..a571531a 100644 --- a/tests/executables_src/testTestFacilities.cc +++ b/tests/executables_src/testTestFacilities.cc @@ -5,228 +5,244 @@ * Author: Martin Hierholzer */ -#include <future> #include <chrono> +#include <future> #define BOOST_TEST_MODULE testTestFacilities +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> #include <ChimeraTK/Device.h> #include "Application.h" -#include "ScalarAccessor.h" #include "ApplicationModule.h" -#include "DeviceModule.h" #include "ControlSystemModule.h" +#include "DeviceModule.h" +#include "ScalarAccessor.h" +#include "TestFacility.h" #include "TestableModeAccessorDecorator.h" #include "VariableGroup.h" -#include "TestFacility.h" using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; -#define CHECK_TIMEOUT(condition, maxMilliseconds) \ - { \ - std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \ - while(!(condition)) { \ - bool timeout_reached = (std::chrono::steady_clock::now()-t0) > std::chrono::milliseconds(maxMilliseconds); \ - BOOST_CHECK( !timeout_reached ); \ - if(timeout_reached) break; \ - usleep(1000); \ - } \ - } +#define CHECK_TIMEOUT(condition, maxMilliseconds) \ + { \ + std::chrono::steady_clock::time_point t0 = \ + std::chrono::steady_clock::now(); \ + while (!(condition)) { \ + bool timeout_reached = (std::chrono::steady_clock::now() - t0) > \ + std::chrono::milliseconds(maxMilliseconds); \ + BOOST_CHECK(!timeout_reached); \ + if (timeout_reached) \ + break; \ + usleep(1000); \ + } \ + } // list of user types the accessors are tested with -typedef boost::mpl::list<int8_t,uint8_t, - int16_t,uint16_t, - int32_t,uint32_t, - float,double> test_types; +typedef boost::mpl::list<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + float, double> + test_types; constexpr char dummySdm[] = "sdm://./dummy=test.map"; /*********************************************************************************************************************/ -/* the BlockingReadTestModule blockingly reads its input in the main loop and writes the result to its output */ +/* the BlockingReadTestModule blockingly reads its input in the main loop and + * writes the result to its output */ -template<typename T> +template <typename T> struct BlockingReadTestModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - - ctk::ScalarPushInput<T> someInput{this, "someInput", "cm", "This is just some input for testing"}; - ctk::ScalarOutput<T> someOutput{this, "someOutput", "cm", "Description"}; - - void mainLoop() { - while(true) { - someInput.read(); - T val = someInput; - someOutput = val; - usleep(10000); // wait some extra time to make sure we are really blocking the test procedure thread - someOutput.write(); - } + using ctk::ApplicationModule::ApplicationModule; + + ctk::ScalarPushInput<T> someInput{this, "someInput", "cm", + "This is just some input for testing"}; + ctk::ScalarOutput<T> someOutput{this, "someOutput", "cm", "Description"}; + + void mainLoop() { + while (true) { + someInput.read(); + T val = someInput; + someOutput = val; + usleep(10000); // wait some extra time to make sure we are really blocking + // the test procedure thread + someOutput.write(); } + } }; /*********************************************************************************************************************/ -/* the AsyncReadTestModule asynchronously reads its input in the main loop and writes the result to its output */ +/* the AsyncReadTestModule asynchronously reads its input in the main loop and + * writes the result to its output */ -template<typename T> +template <typename T> struct AsyncReadTestModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - - ctk::ScalarPushInput<T> someInput{this, "someInput", "cm", "This is just some input for testing"}; - ctk::ScalarOutput<T> someOutput{this, "someOutput", "cm", "Description"}; - - void mainLoop() { - while(true) { - auto future = someInput.readAsync(); - future.wait(); - T val = someInput; - someOutput = val; - usleep(10000); // wait some extra time to make sure we are really blocking the test procedure thread - someOutput.write(); - } + using ctk::ApplicationModule::ApplicationModule; + + ctk::ScalarPushInput<T> someInput{this, "someInput", "cm", + "This is just some input for testing"}; + ctk::ScalarOutput<T> someOutput{this, "someOutput", "cm", "Description"}; + + void mainLoop() { + while (true) { + auto future = someInput.readAsync(); + future.wait(); + T val = someInput; + someOutput = val; + usleep(10000); // wait some extra time to make sure we are really blocking + // the test procedure thread + someOutput.write(); } + } }; /*********************************************************************************************************************/ -/* the ReadAnyTestModule calls readAny on a bunch of inputs and outputs some information on the received data */ - -template<typename T> -struct ReadAnyTestModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - - struct Inputs : public ctk::VariableGroup { - using ctk::VariableGroup::VariableGroup; - ctk::ScalarPushInput<T> v1{this, "v1", "cm", "Input 1 for testing"}; - ctk::ScalarPushInput<T> v2{this, "v2", "cm", "Input 2 for testing"}; - ctk::ScalarPushInput<T> v3{this, "v3", "cm", "Input 3 for testing"}; - ctk::ScalarPushInput<T> v4{this, "v4", "cm", "Input 4 for testing"}; - }; - Inputs inputs{this, "inputs", "A group of inputs"}; - ctk::ScalarOutput<T> value{this, "value", "cm", "The last value received from any of the inputs"}; - ctk::ScalarOutput<uint32_t> index{this, "index", "", "The index (1..4) of the input where the last value was received"}; - - void mainLoop() { - auto group = inputs.readAnyGroup(); - while(true) { - auto justRead = group.readAny(); - if(inputs.v1.getId() == justRead) { - index = 1; - value = (T)inputs.v1; - } - else if(inputs.v2.getId() == justRead) { - index = 2; - value = (T)inputs.v2; - } - else if(inputs.v3.getId() == justRead) { - index = 3; - value = (T)inputs.v3; - } - else if(inputs.v4.getId() == justRead) { - index = 4; - value = (T)inputs.v4; - } - else { - index = 0; - value = 0; - } - usleep(10000); // wait some extra time to make sure we are really blocking the test procedure thread - index.write(); - value.write(); +/* the ReadAnyTestModule calls readAny on a bunch of inputs and outputs some + * information on the received data */ + +template <typename T> struct ReadAnyTestModule : public ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; + + struct Inputs : public ctk::VariableGroup { + using ctk::VariableGroup::VariableGroup; + ctk::ScalarPushInput<T> v1{this, "v1", "cm", "Input 1 for testing"}; + ctk::ScalarPushInput<T> v2{this, "v2", "cm", "Input 2 for testing"}; + ctk::ScalarPushInput<T> v3{this, "v3", "cm", "Input 3 for testing"}; + ctk::ScalarPushInput<T> v4{this, "v4", "cm", "Input 4 for testing"}; + }; + Inputs inputs{this, "inputs", "A group of inputs"}; + ctk::ScalarOutput<T> value{this, "value", "cm", + "The last value received from any of the inputs"}; + ctk::ScalarOutput<uint32_t> index{ + this, "index", "", + "The index (1..4) of the input where the last value was received"}; + + void mainLoop() { + auto group = inputs.readAnyGroup(); + while (true) { + auto justRead = group.readAny(); + if (inputs.v1.getId() == justRead) { + index = 1; + value = (T)inputs.v1; + } else if (inputs.v2.getId() == justRead) { + index = 2; + value = (T)inputs.v2; + } else if (inputs.v3.getId() == justRead) { + index = 3; + value = (T)inputs.v3; + } else if (inputs.v4.getId() == justRead) { + index = 4; + value = (T)inputs.v4; + } else { + index = 0; + value = 0; } + usleep(10000); // wait some extra time to make sure we are really blocking + // the test procedure thread + index.write(); + value.write(); } + } }; /*********************************************************************************************************************/ -/* the PollingReadModule is designed to test poll-type transfers (even mixed with push-type) */ - -template<typename T> -struct PollingReadModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; - - ctk::ScalarPushInput<T> push{this, "push", "cm", "A push-type input"}; - ctk::ScalarPushInput<T> push2{this, "push2", "cm", "A second push-type input"}; - ctk::ScalarPollInput<T> poll{this, "poll", "cm", "A poll-type input"}; - - ctk::ScalarOutput<T> valuePush{this, "valuePush", "cm", "The last value received for 'push'"}; - ctk::ScalarOutput<T> valuePoll{this, "valuePoll", "cm", "The last value received for 'poll'"}; - ctk::ScalarOutput<int> state{this, "state", "", "State of the test mainLoop"}; - - void mainLoop() { - - while(true) { - push.read(); - poll.read(); - valuePush = (T)push; - valuePoll = (T)poll; - valuePoll.write(); - valuePush.write(); - state = 1; - state.write(); - - push2.read(); - push.readNonBlocking(); - poll.read(); - valuePush = (T)push; - valuePoll = (T)poll; - valuePoll.write(); - valuePush.write(); - state = 2; - state.write(); - - push2.read(); - push.readLatest(); - poll.read(); - valuePush = (T)push; - valuePoll = (T)poll; - valuePoll.write(); - valuePush.write(); - state = 3; - state.write(); - - } +/* the PollingReadModule is designed to test poll-type transfers (even mixed + * with push-type) */ + +template <typename T> struct PollingReadModule : public ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; + + ctk::ScalarPushInput<T> push{this, "push", "cm", "A push-type input"}; + ctk::ScalarPushInput<T> push2{this, "push2", "cm", + "A second push-type input"}; + ctk::ScalarPollInput<T> poll{this, "poll", "cm", "A poll-type input"}; + + ctk::ScalarOutput<T> valuePush{this, "valuePush", "cm", + "The last value received for 'push'"}; + ctk::ScalarOutput<T> valuePoll{this, "valuePoll", "cm", + "The last value received for 'poll'"}; + ctk::ScalarOutput<int> state{this, "state", "", "State of the test mainLoop"}; + + void mainLoop() { + + while (true) { + push.read(); + poll.read(); + valuePush = (T)push; + valuePoll = (T)poll; + valuePoll.write(); + valuePush.write(); + state = 1; + state.write(); + + push2.read(); + push.readNonBlocking(); + poll.read(); + valuePush = (T)push; + valuePoll = (T)poll; + valuePoll.write(); + valuePush.write(); + state = 2; + state.write(); + + push2.read(); + push.readLatest(); + poll.read(); + valuePush = (T)push; + valuePoll = (T)poll; + valuePoll.write(); + valuePush.write(); + state = 3; + state.write(); } + } }; /*********************************************************************************************************************/ /* dummy application */ -template<typename T> -struct TestApplication : public ctk::Application { - TestApplication() : Application("testApplication") {} - ~TestApplication() { shutdown(); } - - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // setup is done in the tests - - ctk::ControlSystemModule cs{""}; - ctk::DeviceModule dev{dummySdm,""}; - BlockingReadTestModule<T> blockingReadTestModule{this,"blockingReadTestModule", "Module for testing blocking read"}; - AsyncReadTestModule<T> asyncReadTestModule{this,"asyncReadTestModule", "Module for testing async read"}; - ReadAnyTestModule<T> readAnyTestModule{this,"readAnyTestModule", "Module for testing readAny()"}; +template <typename T> struct TestApplication : public ctk::Application { + TestApplication() : Application("testApplication") {} + ~TestApplication() { shutdown(); } + + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // setup is done in the tests + + ctk::ControlSystemModule cs{""}; + ctk::DeviceModule dev{dummySdm, ""}; + BlockingReadTestModule<T> blockingReadTestModule{ + this, "blockingReadTestModule", "Module for testing blocking read"}; + AsyncReadTestModule<T> asyncReadTestModule{this, "asyncReadTestModule", + "Module for testing async read"}; + ReadAnyTestModule<T> readAnyTestModule{this, "readAnyTestModule", + "Module for testing readAny()"}; }; /*********************************************************************************************************************/ /* second application */ -template<typename T> -struct PollingTestApplication : public ctk::Application { - PollingTestApplication() : Application("testApplication") {} - ~PollingTestApplication() { shutdown(); } +template <typename T> struct PollingTestApplication : public ctk::Application { + PollingTestApplication() : Application("testApplication") {} + ~PollingTestApplication() { shutdown(); } - void defineConnections() {} // setup is done in the tests + void defineConnections() {} // setup is done in the tests - ctk::ControlSystemModule cs{""}; - PollingReadModule<T> pollingReadModule{this,"pollingReadModule", "Module for testing poll-type transfers"}; + ctk::ControlSystemModule cs{""}; + PollingReadModule<T> pollingReadModule{ + this, "pollingReadModule", "Module for testing poll-type transfers"}; }; /*********************************************************************************************************************/ -/* test that no TestableModeAccessorDecorator is used if the testable mode is not enabled */ +/* test that no TestableModeAccessorDecorator is used if the testable mode is + * not enabled */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testNoDecorator, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testNoDecorator, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testNoDecorator<" << typeid(T).name() << ">" << std::endl; TestApplication<T> app; @@ -243,19 +259,25 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testNoDecorator, T, test_types ) { // check if we got the decorator for the input auto hlinput = app.blockingReadTestModule.someInput.getHighLevelImplElement(); - BOOST_CHECK( boost::dynamic_pointer_cast<ctk::TestableModeAccessorDecorator<T>>(hlinput) == nullptr ); + BOOST_CHECK( + boost::dynamic_pointer_cast<ctk::TestableModeAccessorDecorator<T>>( + hlinput) == nullptr); // check that we did not get the decorator for the output - auto hloutput = app.blockingReadTestModule.someOutput.getHighLevelImplElement(); - BOOST_CHECK( boost::dynamic_pointer_cast<ctk::TestableModeAccessorDecorator<T>>(hloutput) == nullptr ); - + auto hloutput = + app.blockingReadTestModule.someOutput.getHighLevelImplElement(); + BOOST_CHECK( + boost::dynamic_pointer_cast<ctk::TestableModeAccessorDecorator<T>>( + hloutput) == nullptr); } /*********************************************************************************************************************/ /* test blocking read in test mode */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testBlockingRead, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testBlockingRead, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testBlockingRead<" << typeid(T).name() << ">" << std::endl; TestApplication<T> app; @@ -270,32 +292,35 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testBlockingRead, T, test_types ) { auto pvOutput = test.getScalar<T>("output"); test.runApplication(); - // test blocking read when taking control in the test thread (note: the blocking read is executed in the app module!) - for(int i=0; i<5; ++i) { - pvInput = 120+i; + // test blocking read when taking control in the test thread (note: the + // blocking read is executed in the app module!) + for (int i = 0; i < 5; ++i) { + pvInput = 120 + i; pvInput.write(); usleep(10000); BOOST_CHECK(pvOutput.readNonBlocking() == false); test.stepApplication(); CHECK_TIMEOUT(pvOutput.readNonBlocking() == true, 200); int val = pvOutput; - BOOST_CHECK(val == 120+i); + BOOST_CHECK(val == 120 + i); } - } /*********************************************************************************************************************/ /* test async read in test mode */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testAsyncRead, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testAsyncRead, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testAsyncRead<" << typeid(T).name() << ">" << std::endl; TestApplication<T> app; app.cs("input") >> app.asyncReadTestModule.someInput; app.asyncReadTestModule.someOutput >> app.cs("output"); - app.blockingReadTestModule.connectTo(app.cs["blocking"]); // avoid runtime warning + app.blockingReadTestModule.connectTo( + app.cs["blocking"]); // avoid runtime warning app.readAnyTestModule.connectTo(app.cs["readAny"]); // avoid runtime warning ctk::TestFacility test; @@ -306,28 +331,29 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testAsyncRead, T, test_types ) { test.runApplication(); // test blocking read when taking control in the test thread - for(int i=0; i<5; ++i) { - pvInput = 120+i; + for (int i = 0; i < 5; ++i) { + pvInput = 120 + i; pvInput.write(); usleep(10000); BOOST_CHECK(pvOutput.readNonBlocking() == false); test.stepApplication(); bool ret = pvOutput.readNonBlocking(); BOOST_CHECK(ret == true); - if(!ret) { + if (!ret) { CHECK_TIMEOUT(pvOutput.readNonBlocking() == true, 10000); } int val = pvOutput; - BOOST_CHECK(val == 120+i); + BOOST_CHECK(val == 120 + i); } - } /*********************************************************************************************************************/ /* test testReadAny in test mode */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testReadAny, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testReadAny, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testReadAny<" << typeid(T).name() << ">" << std::endl; TestApplication<T> app; @@ -335,8 +361,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testReadAny, T, test_types ) { app.readAnyTestModule.inputs.connectTo(app.cs["input"]); app.readAnyTestModule.value >> app.cs("value"); app.readAnyTestModule.index >> app.cs("index"); - app.blockingReadTestModule.connectTo(app.cs["blocking"]); // avoid runtime warning - app.asyncReadTestModule.connectTo(app.cs["async"]); // avoid runtime warning + app.blockingReadTestModule.connectTo( + app.cs["blocking"]); // avoid runtime warning + app.asyncReadTestModule.connectTo(app.cs["async"]); // avoid runtime warning ctk::TestFacility test; auto value = test.getScalar<T>("value"); @@ -432,12 +459,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testReadAny, T, test_types ) { BOOST_CHECK(value == 50); BOOST_CHECK(index == 2); - // check that stepApplication() throws an exception if no input data is available + // check that stepApplication() throws an exception if no input data is + // available try { test.stepApplication(); BOOST_ERROR("IllegalParameter exception expected."); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } // check that we still don't receive anything anymore @@ -460,15 +487,18 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testReadAny, T, test_types ) { BOOST_CHECK(index.readNonBlocking() == true); BOOST_CHECK(value == 35); BOOST_CHECK(index == 1); - } /*********************************************************************************************************************/ -/* test the interplay of multiple chained modules and their threads in test mode */ +/* test the interplay of multiple chained modules and their threads in test mode + */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testChainedModules, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; - std::cout << "==> testChainedModules<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testChainedModules, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; + std::cout << "==> testChainedModules<" << typeid(T).name() << ">" + << std::endl; TestApplication<T> app; @@ -541,33 +571,35 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testChainedModules, T, test_types ) { BOOST_CHECK(value == 13); BOOST_CHECK(index == 3); - // check that stepApplication() throws an exception if no input data is available + // check that stepApplication() throws an exception if no input data is + // available try { test.stepApplication(); BOOST_ERROR("IllegalParameter exception expected."); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } // check that we still don't receive anything anymore usleep(10000); BOOST_CHECK(value.readNonBlocking() == false); BOOST_CHECK(index.readNonBlocking() == false); - } /*********************************************************************************************************************/ /* test combination with fan out */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testWithFanOut, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testWithFanOut, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testWithFanOut<" << typeid(T).name() << ">" << std::endl; TestApplication<T> app; // distribute a value to multiple inputs app.readAnyTestModule.inputs.connectTo(app.cs["input"]); - app.readAnyTestModule.value >> app.blockingReadTestModule.someInput >> app.asyncReadTestModule.someInput; + app.readAnyTestModule.value >> app.blockingReadTestModule.someInput >> + app.asyncReadTestModule.someInput; app.blockingReadTestModule.someOutput >> app.cs("valueFromBlocking"); app.asyncReadTestModule.someOutput >> app.cs("valueFromAsync"); app.readAnyTestModule.index >> app.cs("index"); @@ -645,12 +677,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testWithFanOut, T, test_types ) { BOOST_CHECK_EQUAL((T)valueFromAsync, 13); BOOST_CHECK_EQUAL((unsigned int)index, 3); - // check that stepApplication() throws an exception if no input data is available + // check that stepApplication() throws an exception if no input data is + // available try { test.stepApplication(); BOOST_ERROR("IllegalParameter exception expected."); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } // check that we still don't receive anything anymore @@ -658,24 +690,26 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testWithFanOut, T, test_types ) { BOOST_CHECK(valueFromBlocking.readNonBlocking() == false); BOOST_CHECK(valueFromAsync.readNonBlocking() == false); BOOST_CHECK(index.readNonBlocking() == false); - } /*********************************************************************************************************************/ /* test combination with trigger */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testWithTrigger, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testWithTrigger, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testWithTrigger<" << typeid(T).name() << ">" << std::endl; TestApplication<T> app; // distribute a value to multiple inputs auto triggernode = app.cs("trigger", typeid(int), 1); app.cs("v1") >> app.readAnyTestModule.inputs.v1; - app.dev("REG2") [ triggernode ] >> app.readAnyTestModule.inputs.v2; + app.dev("REG2")[triggernode] >> app.readAnyTestModule.inputs.v2; app.cs("v3") >> app.readAnyTestModule.inputs.v3; app.cs("v4") >> app.readAnyTestModule.inputs.v4; - app.readAnyTestModule.value >> app.blockingReadTestModule.someInput >> app.asyncReadTestModule.someInput; + app.readAnyTestModule.value >> app.blockingReadTestModule.someInput >> + app.asyncReadTestModule.someInput; app.blockingReadTestModule.someOutput >> app.cs("valueFromBlocking"); app.asyncReadTestModule.someOutput >> app.cs("valueFromAsync"); app.readAnyTestModule.index >> app.cs("index"); @@ -736,12 +770,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testWithTrigger, T, test_types ) { BOOST_CHECK(valueFromAsync == 22); BOOST_CHECK(index == 2); - // check that stepApplication() throws an exception if no input data is available + // check that stepApplication() throws an exception if no input data is + // available try { test.stepApplication(); BOOST_ERROR("IllegalParameter exception expected."); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } // check that we still don't receive anything anymore @@ -749,25 +783,27 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testWithTrigger, T, test_types ) { BOOST_CHECK(valueFromBlocking.readNonBlocking() == false); BOOST_CHECK(valueFromAsync.readNonBlocking() == false); BOOST_CHECK(index.readNonBlocking() == false); - } /*********************************************************************************************************************/ /* test combination with trigger distributed to mutliple receivers */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testWithTriggerFanOut, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; - std::cout << "==> testWithTriggerFanOut<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testWithTriggerFanOut, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; + std::cout << "==> testWithTriggerFanOut<" << typeid(T).name() << ">" + << std::endl; TestApplication<T> app; // distribute a value to multiple inputs auto triggernode = app.cs("trigger", typeid(int), 1); - app.dev("REG1") [ triggernode ] >> app.readAnyTestModule.inputs.v1; + app.dev("REG1")[triggernode] >> app.readAnyTestModule.inputs.v1; app.cs("v2") >> app.readAnyTestModule.inputs.v2; app.cs("v3") >> app.readAnyTestModule.inputs.v3; app.cs("v4") >> app.readAnyTestModule.inputs.v4; - app.dev("REG2") [ triggernode ] >> app.asyncReadTestModule.someInput; - app.dev("REG3") [ triggernode ] >> app.blockingReadTestModule.someInput; + app.dev("REG2")[triggernode] >> app.asyncReadTestModule.someInput; + app.dev("REG3")[triggernode] >> app.blockingReadTestModule.someInput; app.readAnyTestModule.value >> app.cs("valueFromAny"); app.readAnyTestModule.index >> app.cs("index"); app.blockingReadTestModule.someOutput >> app.cs("valueFromBlocking"); @@ -854,12 +890,12 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testWithTriggerFanOut, T, test_types ) { BOOST_CHECK(valueFromAny == 6); BOOST_CHECK(index == 1); - // check that stepApplication() throws an exception if no input data is available + // check that stepApplication() throws an exception if no input data is + // available try { test.stepApplication(); BOOST_ERROR("IllegalParameter exception expected."); - } - catch(ChimeraTK::logic_error&) { + } catch (ChimeraTK::logic_error &) { } // check that we still don't receive anything anymore @@ -868,15 +904,17 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testWithTriggerFanOut, T, test_types ) { BOOST_CHECK(valueFromAsync.readNonBlocking() == false); BOOST_CHECK(valueFromAny.readNonBlocking() == false); BOOST_CHECK(index.readNonBlocking() == false); - } /*********************************************************************************************************************/ /* test convenience read functions */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testConvenienceRead, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; - std::cout << "==> testConvenienceRead<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testConvenienceRead, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; + std::cout << "==> testConvenienceRead<" << typeid(T).name() << ">" + << std::endl; TestApplication<T> app; @@ -888,38 +926,47 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testConvenienceRead, T, test_types ) { ctk::TestFacility test; test.runApplication(); - // test blocking read when taking control in the test thread (note: the blocking read is executed in the app module!) - for(int i=0; i<5; ++i) { - test.writeScalar<T>("input", 120+i); + // test blocking read when taking control in the test thread (note: the + // blocking read is executed in the app module!) + for (int i = 0; i < 5; ++i) { + test.writeScalar<T>("input", 120 + i); test.stepApplication(); - CHECK_TIMEOUT(test.readScalar<T>("output") == T(120+i), 200); + CHECK_TIMEOUT(test.readScalar<T>("output") == T(120 + i), 200); } - // same with array function (still a scalar variable behind, but this does not matter) - for(int i=0; i<5; ++i) { - std::vector<T> myValue{T(120+i)}; + // same with array function (still a scalar variable behind, but this does not + // matter) + for (int i = 0; i < 5; ++i) { + std::vector<T> myValue{T(120 + i)}; test.writeArray<T>("input", myValue); test.stepApplication(); - CHECK_TIMEOUT(test.readArray<T>("output") == std::vector<T>{T(120+i)}, 200); + CHECK_TIMEOUT(test.readArray<T>("output") == std::vector<T>{T(120 + i)}, + 200); } - } /*********************************************************************************************************************/ /* test testable mode when reading from constants */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testConstants, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testConstants, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testConstants<" << typeid(T).name() << ">" << std::endl; { TestApplication<T> app; - ctk::VariableNetworkNode::makeConstant<T>(true, 18) >> app.blockingReadTestModule.someInput; - ctk::VariableNetworkNode::makeConstant<T>(true, 20) >> app.asyncReadTestModule.someInput; - ctk::VariableNetworkNode::makeConstant<T>(true, 22) >> app.readAnyTestModule.inputs.v1; - ctk::VariableNetworkNode::makeConstant<T>(true, 23) >> app.readAnyTestModule.inputs.v2; - ctk::VariableNetworkNode::makeConstant<T>(true, 24) >> app.readAnyTestModule.inputs.v3; + ctk::VariableNetworkNode::makeConstant<T>(true, 18) >> + app.blockingReadTestModule.someInput; + ctk::VariableNetworkNode::makeConstant<T>(true, 20) >> + app.asyncReadTestModule.someInput; + ctk::VariableNetworkNode::makeConstant<T>(true, 22) >> + app.readAnyTestModule.inputs.v1; + ctk::VariableNetworkNode::makeConstant<T>(true, 23) >> + app.readAnyTestModule.inputs.v2; + ctk::VariableNetworkNode::makeConstant<T>(true, 24) >> + app.readAnyTestModule.inputs.v3; app.blockingReadTestModule.someOutput >> app.cs("blockingOutput"); app.asyncReadTestModule.someOutput >> app.cs("asyncOutput"); app.cs("v4") >> app.readAnyTestModule.inputs.v4; @@ -929,61 +976,63 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testConstants, T, test_types ) { ctk::TestFacility test; test.runApplication(); - BOOST_CHECK_EQUAL( (T)app.blockingReadTestModule.someInput, 18 ); - BOOST_CHECK_EQUAL( (T)app.asyncReadTestModule.someInput, 20 ); - BOOST_CHECK_EQUAL( (T)app.readAnyTestModule.inputs.v1, 22 ); - BOOST_CHECK_EQUAL( (T)app.readAnyTestModule.inputs.v2, 23 ); - BOOST_CHECK_EQUAL( (T)app.readAnyTestModule.inputs.v3, 24 ); + BOOST_CHECK_EQUAL((T)app.blockingReadTestModule.someInput, 18); + BOOST_CHECK_EQUAL((T)app.asyncReadTestModule.someInput, 20); + BOOST_CHECK_EQUAL((T)app.readAnyTestModule.inputs.v1, 22); + BOOST_CHECK_EQUAL((T)app.readAnyTestModule.inputs.v2, 23); + BOOST_CHECK_EQUAL((T)app.readAnyTestModule.inputs.v3, 24); test.writeScalar<T>("v4", 27); test.stepApplication(); - BOOST_CHECK_EQUAL( test.readScalar<uint32_t>("index"), 4 ); - BOOST_CHECK_EQUAL( test.readScalar<T>("value"), 27 ); + BOOST_CHECK_EQUAL(test.readScalar<uint32_t>("index"), 4); + BOOST_CHECK_EQUAL(test.readScalar<T>("value"), 27); test.writeScalar<T>("v4", 30); test.stepApplication(); - BOOST_CHECK_EQUAL( test.readScalar<uint32_t>("index"), 4 ); - BOOST_CHECK_EQUAL( test.readScalar<T>("value"), 30 ); + BOOST_CHECK_EQUAL(test.readScalar<uint32_t>("index"), 4); + BOOST_CHECK_EQUAL(test.readScalar<T>("value"), 30); } { PollingTestApplication<T> app; - ctk::VariableNetworkNode::makeConstant<T>(true, 18) >> app.pollingReadModule.push2; - ctk::VariableNetworkNode::makeConstant<T>(true, 20) >> app.pollingReadModule.poll; + ctk::VariableNetworkNode::makeConstant<T>(true, 18) >> + app.pollingReadModule.push2; + ctk::VariableNetworkNode::makeConstant<T>(true, 20) >> + app.pollingReadModule.poll; app.pollingReadModule.connectTo(app.cs); ctk::TestFacility test; test.runApplication(); - BOOST_CHECK_EQUAL( (T)app.pollingReadModule.push2, 18 ); - BOOST_CHECK_EQUAL( (T)app.pollingReadModule.poll, 20 ); - BOOST_CHECK_EQUAL( test.readScalar<T>("push2"), 18 ); - BOOST_CHECK_EQUAL( test.readScalar<T>("poll"), 20 ); + BOOST_CHECK_EQUAL((T)app.pollingReadModule.push2, 18); + BOOST_CHECK_EQUAL((T)app.pollingReadModule.poll, 20); + BOOST_CHECK_EQUAL(test.readScalar<T>("push2"), 18); + BOOST_CHECK_EQUAL(test.readScalar<T>("poll"), 20); test.writeScalar<T>("push", 22); test.stepApplication(); - BOOST_CHECK_EQUAL( test.readScalar<int>("state"), 1 ); - BOOST_CHECK_EQUAL( test.readScalar<T>("valuePush"), 22 ); - BOOST_CHECK_EQUAL( test.readScalar<T>("valuePoll"), 20 ); + BOOST_CHECK_EQUAL(test.readScalar<int>("state"), 1); + BOOST_CHECK_EQUAL(test.readScalar<T>("valuePush"), 22); + BOOST_CHECK_EQUAL(test.readScalar<T>("valuePoll"), 20); // continuing will now stall the tests test.writeScalar<T>("push", 23); try { test.stepApplication(); BOOST_ERROR("Exception expected."); - } - catch(ctk::Application::TestsStalled&) { + } catch (ctk::Application::TestsStalled &) { } } - } /*********************************************************************************************************************/ /* test poll-type transfers mixed with push-type */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testPolling, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testPolling, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testPolling<" << typeid(T).name() << ">" << std::endl; PollingTestApplication<T> app; @@ -1012,7 +1061,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testPolling, T, test_types ) { BOOST_CHECK_EQUAL((T)pv_valuePush, 120); BOOST_CHECK_EQUAL((T)pv_state, 1); - // this time the application gets triggered by push2, push is read non-blockingly (single value only) + // this time the application gets triggered by push2, push is read + // non-blockingly (single value only) pv_push = 22; pv_push.write(); pv_poll = 44; @@ -1028,7 +1078,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testPolling, T, test_types ) { BOOST_CHECK_EQUAL((T)pv_valuePush, 22); BOOST_CHECK_EQUAL((T)pv_state, 2); - // this time the application gets triggered by push2, push is read with readLatest() + // this time the application gets triggered by push2, push is read with + // readLatest() pv_push = 24; pv_push.write(); pv_poll = 46; @@ -1041,5 +1092,4 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testPolling, T, test_types ) { BOOST_CHECK_EQUAL((T)pv_valuePoll, 46); BOOST_CHECK_EQUAL((T)pv_valuePush, 24); BOOST_CHECK_EQUAL((T)pv_state, 3); - } diff --git a/tests/executables_src/testTrigger.cc b/tests/executables_src/testTrigger.cc index cf3bac55..837ce1be 100644 --- a/tests/executables_src/testTrigger.cc +++ b/tests/executables_src/testTrigger.cc @@ -5,29 +5,29 @@ * Author: Martin Hierholzer */ -#include <future> #include <chrono> +#include <future> #define BOOST_TEST_MODULE testTrigger +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> #include <ChimeraTK/BackendFactory.h> -#include <ChimeraTK/DummyBackend.h> #include <ChimeraTK/Device.h> #include <ChimeraTK/DeviceAccessVersion.h> +#include <ChimeraTK/DummyBackend.h> -#include <ChimeraTK/ControlSystemAdapter/PVManager.h> #include <ChimeraTK/ControlSystemAdapter/ControlSystemPVManager.h> #include <ChimeraTK/ControlSystemAdapter/DevicePVManager.h> +#include <ChimeraTK/ControlSystemAdapter/PVManager.h> #include "Application.h" -#include "ScalarAccessor.h" #include "ApplicationModule.h" -#include "DeviceModule.h" #include "ControlSystemModule.h" +#include "DeviceModule.h" +#include "ScalarAccessor.h" using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; @@ -35,93 +35,110 @@ namespace ctk = ChimeraTK; constexpr char dummySdm[] = "sdm://./TestTransferGroupDummy=test.map"; // list of user types the accessors are tested with -typedef boost::mpl::list<int8_t,uint8_t, - int16_t,uint16_t, - int32_t,uint32_t, - float,double> test_types; - -#define CHECK_TIMEOUT(condition, maxMilliseconds) \ - { \ - std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); \ - while(!(condition)) { \ - bool timeout_reached = (std::chrono::steady_clock::now()-t0) > std::chrono::milliseconds(maxMilliseconds); \ - BOOST_CHECK( !timeout_reached ); \ - if(timeout_reached) break; \ - usleep(1000); \ - } \ - } +typedef boost::mpl::list<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, + float, double> + test_types; + +#define CHECK_TIMEOUT(condition, maxMilliseconds) \ + { \ + std::chrono::steady_clock::time_point t0 = \ + std::chrono::steady_clock::now(); \ + while (!(condition)) { \ + bool timeout_reached = (std::chrono::steady_clock::now() - t0) > \ + std::chrono::milliseconds(maxMilliseconds); \ + BOOST_CHECK(!timeout_reached); \ + if (timeout_reached) \ + break; \ + usleep(1000); \ + } \ + } /**********************************************************************************************************************/ class TestTransferGroupDummy : public ChimeraTK::DummyBackend { - public: - TestTransferGroupDummy(std::string mapFileName) : DummyBackend(mapFileName) {} - - static boost::shared_ptr<DeviceBackend> createInstance(std::string, std::string, std::list<std::string> parameters, std::string) { - return boost::shared_ptr<DeviceBackend>(new TestTransferGroupDummy(parameters.front())); - } - - void read(uint8_t bar, uint32_t address, int32_t* data, size_t sizeInBytes) override { - last_bar = bar; - last_address = address; - last_sizeInBytes = sizeInBytes; - numberOfTransfers++; - DummyBackend::read(bar,address,data,sizeInBytes); - } - - std::atomic<size_t> numberOfTransfers{0}; - std::atomic<uint8_t> last_bar; - std::atomic<uint32_t> last_address; - std::atomic<size_t> last_sizeInBytes; +public: + TestTransferGroupDummy(std::string mapFileName) : DummyBackend(mapFileName) {} + + static boost::shared_ptr<DeviceBackend> + createInstance(std::string, std::string, std::list<std::string> parameters, + std::string) { + return boost::shared_ptr<DeviceBackend>( + new TestTransferGroupDummy(parameters.front())); + } + + void read(uint8_t bar, uint32_t address, int32_t *data, + size_t sizeInBytes) override { + last_bar = bar; + last_address = address; + last_sizeInBytes = sizeInBytes; + numberOfTransfers++; + DummyBackend::read(bar, address, data, sizeInBytes); + } + + std::atomic<size_t> numberOfTransfers{0}; + std::atomic<uint8_t> last_bar; + std::atomic<uint32_t> last_address; + std::atomic<size_t> last_sizeInBytes; }; /*********************************************************************************************************************/ /* the ApplicationModule for the test is a template of the user type */ -template<typename T> -struct TestModule : public ctk::ApplicationModule { - using ctk::ApplicationModule::ApplicationModule; +template <typename T> struct TestModule : public ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; - ctk::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", "Description"}; - ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", "Description"}; - ctk::ScalarPushInput<T> consumingPush3{this, "consumingPush3", "MV/m", "Description"}; + ctk::ScalarPushInput<T> consumingPush{this, "consumingPush", "MV/m", + "Description"}; + ctk::ScalarPushInput<T> consumingPush2{this, "consumingPush2", "MV/m", + "Description"}; + ctk::ScalarPushInput<T> consumingPush3{this, "consumingPush3", "MV/m", + "Description"}; - ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", "Description"}; - ctk::ScalarPollInput<T> consumingPoll2{this, "consumingPoll2", "MV/m", "Description"}; - ctk::ScalarPollInput<T> consumingPoll3{this, "consumingPoll3", "MV/m", "Description"}; + ctk::ScalarPollInput<T> consumingPoll{this, "consumingPoll", "MV/m", + "Description"}; + ctk::ScalarPollInput<T> consumingPoll2{this, "consumingPoll2", "MV/m", + "Description"}; + ctk::ScalarPollInput<T> consumingPoll3{this, "consumingPoll3", "MV/m", + "Description"}; - ctk::ScalarOutput<T> theTrigger{this, "theTrigger", "MV/m", "Description"}; - ctk::ScalarOutput<T> feedingToDevice{this, "feedingToDevice", "MV/m", "Description"}; + ctk::ScalarOutput<T> theTrigger{this, "theTrigger", "MV/m", "Description"}; + ctk::ScalarOutput<T> feedingToDevice{this, "feedingToDevice", "MV/m", + "Description"}; - void mainLoop() {} + void mainLoop() {} }; /*********************************************************************************************************************/ /* dummy application */ -template<typename T> -struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") { - ChimeraTK::BackendFactory::getInstance().registerBackendType("TestTransferGroupDummy", "", - &TestTransferGroupDummy::createInstance, CHIMERATK_DEVICEACCESS_VERSION); - } - ~TestApplication() { shutdown(); } - - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // the setup is done in the tests - - TestModule<T> testModule{this,"testModule", "The test module"}; - ctk::DeviceModule dev{"Dummy0"}; - ctk::DeviceModule dev2{dummySdm}; - ctk::ControlSystemModule cs; +template <typename T> struct TestApplication : public ctk::Application { + TestApplication() : Application("testSuite") { + ChimeraTK::BackendFactory::getInstance().registerBackendType( + "TestTransferGroupDummy", "", &TestTransferGroupDummy::createInstance, + CHIMERATK_DEVICEACCESS_VERSION); + } + ~TestApplication() { shutdown(); } + + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // the setup is done in the tests + + TestModule<T> testModule{this, "testModule", "The test module"}; + ctk::DeviceModule dev{"Dummy0"}; + ctk::DeviceModule dev2{dummySdm}; + ctk::ControlSystemModule cs; }; /*********************************************************************************************************************/ -/* test trigger by app variable when connecting a polled device register to an app variable */ +/* test trigger by app variable when connecting a polled device register to an + * app variable */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerDevToApp, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; - std::cout << "==> testTriggerDevToApp<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerDevToApp, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; + std::cout << "==> testTriggerDevToApp<" << typeid(T).name() << ">" + << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -129,7 +146,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerDevToApp, T, test_types ) { app.testModule.feedingToDevice >> app.dev("/MyModule/actuator"); - app.dev("/MyModule/readBack") [ app.testModule.theTrigger ] >> app.testModule.consumingPush; + app.dev("/MyModule/readBack")[app.testModule.theTrigger] >> + app.testModule.consumingPush; app.initialise(); app.run(); @@ -144,32 +162,41 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerDevToApp, T, test_types ) { app.testModule.consumingPush.read(); BOOST_CHECK(app.testModule.consumingPush == 42); - // launch read() on the consumer asynchronously and make sure it does not yet receive anything - auto futRead = std::async(std::launch::async, [&app]{ app.testModule.consumingPush.read(); }); - BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); + // launch read() on the consumer asynchronously and make sure it does not yet + // receive anything + auto futRead = std::async(std::launch::async, + [&app] { app.testModule.consumingPush.read(); }); + BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == + std::future_status::timeout); BOOST_CHECK(app.testModule.consumingPush == 42); // write to the feeder app.testModule.feedingToDevice = 120; app.testModule.feedingToDevice.write(); - BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); + BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(200)) == + std::future_status::timeout); BOOST_CHECK(app.testModule.consumingPush == 42); // send trigger app.testModule.theTrigger.write(); // check that the consumer now receives the just written value - BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); - BOOST_CHECK( app.testModule.consumingPush == 120 ); + BOOST_CHECK(futRead.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); + BOOST_CHECK(app.testModule.consumingPush == 120); } /*********************************************************************************************************************/ -/* test trigger by app variable when connecting a polled device register to control system variable */ +/* test trigger by app variable when connecting a polled device register to + * control system variable */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerDevToCS, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; - std::cout << "==> testTriggerDevToCS<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerDevToCS, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; + std::cout << "==> testTriggerDevToCS<" << typeid(T).name() << ">" + << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -180,7 +207,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerDevToCS, T, test_types ) { app.testModule.feedingToDevice >> app.dev("/MyModule/actuator"); - app.dev("/MyModule/readBack", typeid(T), 1) [ app.testModule.theTrigger ] >> app.cs("myCSVar"); + app.dev("/MyModule/readBack", typeid(T), 1)[app.testModule.theTrigger] >> + app.cs("myCSVar"); app.initialise(); app.run(); @@ -188,7 +216,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerDevToCS, T, test_types ) { BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 1); auto myCSVar = pvManagers.first->getProcessArray<T>("/myCSVar"); - // single theaded test only, since the receiving process scalar does not support blocking + // single theaded test only, since the receiving process scalar does not + // support blocking BOOST_CHECK(myCSVar->readNonBlocking() == false); app.testModule.feedingToDevice = 42; BOOST_CHECK(myCSVar->readNonBlocking() == false); @@ -211,10 +240,13 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerDevToCS, T, test_types ) { } /*********************************************************************************************************************/ -/* test trigger by app variable when connecting a polled device register to control system variable */ +/* test trigger by app variable when connecting a polled device register to + * control system variable */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerByCS, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerByCS, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testTriggerByCS<" << typeid(T).name() << ">" << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -226,7 +258,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerByCS, T, test_types ) { app.testModule.feedingToDevice >> app.dev("/MyModule/actuator"); - app.dev("/MyModule/readBack", typeid(T), 1) [ app.cs("theTrigger", typeid(T), 1) ] >> app.cs("myCSVar"); + app.dev("/MyModule/readBack", typeid(T), + 1)[app.cs("theTrigger", typeid(T), 1)] >> + app.cs("myCSVar"); app.initialise(); app.run(); @@ -235,7 +269,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerByCS, T, test_types ) { auto myCSVar = pvManagers.first->getProcessArray<T>("/myCSVar"); auto theTrigger = pvManagers.first->getProcessArray<T>("/theTrigger"); - // single theaded test only, since the receiving process scalar does not support blocking + // single theaded test only, since the receiving process scalar does not + // support blocking BOOST_CHECK(myCSVar->readNonBlocking() == false); app.testModule.feedingToDevice = 42; BOOST_CHECK(myCSVar->readNonBlocking() == false); @@ -260,11 +295,15 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerByCS, T, test_types ) { } /*********************************************************************************************************************/ -/* test that multiple variables triggered by the same source are put into the same TransferGroup */ +/* test that multiple variables triggered by the same source are put into the + * same TransferGroup */ -BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerTransferGroup, T, test_types ) { - std::cout << "*********************************************************************************************************************" << std::endl; - std::cout << "==> testTriggerTransferGroup<" << typeid(T).name() << ">" << std::endl; +BOOST_AUTO_TEST_CASE_TEMPLATE(testTriggerTransferGroup, T, test_types) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; + std::cout << "==> testTriggerTransferGroup<" << typeid(T).name() << ">" + << std::endl; ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap"); @@ -272,12 +311,13 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerTransferGroup, T, test_types ) { ChimeraTK::Device dev; dev.open(dummySdm); - auto backend = boost::dynamic_pointer_cast<TestTransferGroupDummy>(ChimeraTK::BackendFactory::getInstance().createBackend(dummySdm)); - BOOST_CHECK( backend != NULL ); + auto backend = boost::dynamic_pointer_cast<TestTransferGroupDummy>( + ChimeraTK::BackendFactory::getInstance().createBackend(dummySdm)); + BOOST_CHECK(backend != NULL); - app.dev2("/REG1") [ app.testModule.theTrigger ] >> app.testModule.consumingPush; - app.dev2("/REG2") [ app.testModule.theTrigger ] >> app.testModule.consumingPush2; - app.dev2("/REG3") [ app.testModule.theTrigger ] >> app.testModule.consumingPush3; + app.dev2("/REG1")[app.testModule.theTrigger] >> app.testModule.consumingPush; + app.dev2("/REG2")[app.testModule.theTrigger] >> app.testModule.consumingPush2; + app.dev2("/REG3")[app.testModule.theTrigger] >> app.testModule.consumingPush3; app.initialise(); app.run(); @@ -300,7 +340,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerTransferGroup, T, test_types ) { app.testModule.consumingPush.read(); app.testModule.consumingPush2.read(); app.testModule.consumingPush3.read(); - BOOST_CHECK(app.testModule.consumingPush == 11); + BOOST_CHECK(app.testModule.consumingPush == 11); BOOST_CHECK(app.testModule.consumingPush2 == 22); BOOST_CHECK(app.testModule.consumingPush3 == 33); @@ -320,10 +360,9 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( testTriggerTransferGroup, T, test_types ) { app.testModule.consumingPush.read(); app.testModule.consumingPush2.read(); app.testModule.consumingPush3.read(); - BOOST_CHECK(app.testModule.consumingPush == 12); + BOOST_CHECK(app.testModule.consumingPush == 12); BOOST_CHECK(app.testModule.consumingPush2 == 23); BOOST_CHECK(app.testModule.consumingPush3 == 34); dev.close(); - } diff --git a/tests/executables_src/testVariableGroup.cc b/tests/executables_src/testVariableGroup.cc index ce64ed52..63fd5145 100644 --- a/tests/executables_src/testVariableGroup.cc +++ b/tests/executables_src/testVariableGroup.cc @@ -5,19 +5,19 @@ * Author: Martin Hierholzer */ -#include <future> #include <chrono> +#include <future> #define BOOST_TEST_MODULE testVariableGroup +#include <boost/mpl/list.hpp> #include <boost/test/included/unit_test.hpp> #include <boost/test/test_case_template.hpp> -#include <boost/mpl/list.hpp> #include <boost/thread.hpp> #include "Application.h" -#include "ScalarAccessor.h" #include "ApplicationModule.h" +#include "ScalarAccessor.h" #include "VariableGroup.h" using namespace boost::unit_test_framework; @@ -31,21 +31,32 @@ struct TestModule : public ctk::ApplicationModule { struct MixedGroup : public ctk::VariableGroup { using ctk::VariableGroup::VariableGroup; - ctk::ScalarPushInput<int> consumingPush{this, "consumingPush", "MV/m", "Descrption"}; - ctk::ScalarPushInput<int> consumingPush2{this, "consumingPush2", "MV/m", "Descrption"}; - ctk::ScalarPushInput<int> consumingPush3{this, "consumingPush3", "MV/m", "Descrption"}; - ctk::ScalarPollInput<int> consumingPoll{this, "consumingPoll", "MV/m", "Descrption"}; - ctk::ScalarPollInput<int> consumingPoll2{this, "consumingPoll2", "MV/m", "Descrption"}; - ctk::ScalarPollInput<int> consumingPoll3{this, "consumingPoll3", "MV/m", "Descrption"}; + ctk::ScalarPushInput<int> consumingPush{this, "consumingPush", "MV/m", + "Descrption"}; + ctk::ScalarPushInput<int> consumingPush2{this, "consumingPush2", "MV/m", + "Descrption"}; + ctk::ScalarPushInput<int> consumingPush3{this, "consumingPush3", "MV/m", + "Descrption"}; + ctk::ScalarPollInput<int> consumingPoll{this, "consumingPoll", "MV/m", + "Descrption"}; + ctk::ScalarPollInput<int> consumingPoll2{this, "consumingPoll2", "MV/m", + "Descrption"}; + ctk::ScalarPollInput<int> consumingPoll3{this, "consumingPoll3", "MV/m", + "Descrption"}; }; - MixedGroup mixedGroup{this, "mixedGroup", "A group with both push and poll inputs"}; + MixedGroup mixedGroup{this, "mixedGroup", + "A group with both push and poll inputs"}; ctk::ScalarOutput<int> feedingPush{this, "feedingPush", "MV/m", "Descrption"}; - ctk::ScalarOutput<int> feedingPush2{this, "feedingPush2", "MV/m", "Descrption"}; - ctk::ScalarOutput<int> feedingPush3{this, "feedingPush3", "MV/m", "Descrption"}; + ctk::ScalarOutput<int> feedingPush2{this, "feedingPush2", "MV/m", + "Descrption"}; + ctk::ScalarOutput<int> feedingPush3{this, "feedingPush3", "MV/m", + "Descrption"}; ctk::ScalarOutput<int> feedingPoll{this, "feedingPoll", "MV/m", "Descrption"}; - ctk::ScalarOutput<int> feedingPoll2{this, "feedingPoll2", "MV/m", "Descrption"}; - ctk::ScalarOutput<int> feedingPoll3{this, "feedingPoll3", "MV/m", "Descrption"}; + ctk::ScalarOutput<int> feedingPoll2{this, "feedingPoll2", "MV/m", + "Descrption"}; + ctk::ScalarOutput<int> feedingPoll3{this, "feedingPoll3", "MV/m", + "Descrption"}; void mainLoop() {} }; @@ -54,19 +65,20 @@ struct TestModule : public ctk::ApplicationModule { /* dummy application */ struct TestApplication : public ctk::Application { - TestApplication() : Application("testSuite") {} - ~TestApplication() { shutdown(); } + TestApplication() : Application("testSuite") {} + ~TestApplication() { shutdown(); } - using Application::makeConnections; // we call makeConnections() manually in the tests to catch exceptions etc. - void defineConnections() {} // the setup is done in the tests + using Application::makeConnections; // we call makeConnections() manually in + // the tests to catch exceptions etc. + void defineConnections() {} // the setup is done in the tests - TestModule testModule{this, "testModule", "The test module"}; + TestModule testModule{this, "testModule", "The test module"}; }; /*********************************************************************************************************************/ /* test module-wide read/write operations */ -BOOST_AUTO_TEST_CASE( testModuleReadWrite ) { +BOOST_AUTO_TEST_CASE(testModuleReadWrite) { std::cout << "*** testModuleReadWrite" << std::endl; TestApplication app; @@ -203,14 +215,16 @@ BOOST_AUTO_TEST_CASE( testModuleReadWrite ) { BOOST_CHECK(app.testModule.mixedGroup.consumingPoll == 23); BOOST_CHECK(app.testModule.mixedGroup.consumingPoll2 == 33); BOOST_CHECK(app.testModule.mixedGroup.consumingPoll3 == 44); - } /*********************************************************************************************************************/ -/* test trigger by app variable when connecting a polled device register to an app variable */ +/* test trigger by app variable when connecting a polled device register to an + * app variable */ -BOOST_AUTO_TEST_CASE( testReadAny ) { - std::cout << "*********************************************************************************************************************" << std::endl; +BOOST_AUTO_TEST_CASE(testReadAny) { + std::cout << "***************************************************************" + "******************************************************" + << std::endl; std::cout << "==> testReadAny" << std::endl; TestApplication app; @@ -286,9 +300,12 @@ BOOST_AUTO_TEST_CASE( testReadAny ) { BOOST_CHECK(app.testModule.mixedGroup.consumingPoll2 == 11); BOOST_CHECK(app.testModule.mixedGroup.consumingPoll3 == 12); - // launch readAny() asynchronously and make sure it does not yet receive anything - auto futureRead = std::async(std::launch::async, [&group]{ group.readAny(); }); - BOOST_CHECK(futureRead.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); + // launch readAny() asynchronously and make sure it does not yet receive + // anything + auto futureRead = + std::async(std::launch::async, [&group] { group.readAny(); }); + BOOST_CHECK(futureRead.wait_for(std::chrono::milliseconds(200)) == + std::future_status::timeout); BOOST_CHECK(app.testModule.mixedGroup.consumingPush == 0); BOOST_CHECK(app.testModule.mixedGroup.consumingPush2 == 666); BOOST_CHECK(app.testModule.mixedGroup.consumingPush3 == 120); @@ -301,7 +318,8 @@ BOOST_AUTO_TEST_CASE( testReadAny ) { app.testModule.feedingPush.write(); // check that the group now receives the just written value - BOOST_CHECK(futureRead.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); + BOOST_CHECK(futureRead.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); BOOST_CHECK(app.testModule.mixedGroup.consumingPush == 3); BOOST_CHECK(app.testModule.mixedGroup.consumingPush2 == 666); BOOST_CHECK(app.testModule.mixedGroup.consumingPush3 == 120); @@ -309,9 +327,12 @@ BOOST_AUTO_TEST_CASE( testReadAny ) { BOOST_CHECK(app.testModule.mixedGroup.consumingPoll2 == 11); BOOST_CHECK(app.testModule.mixedGroup.consumingPoll3 == 12); - // launch another readAny() asynchronously and make sure it does not yet receive anything - auto futureRead2 = std::async(std::launch::async, [&group]{ group.readAny(); }); - BOOST_CHECK(futureRead2.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); + // launch another readAny() asynchronously and make sure it does not yet + // receive anything + auto futureRead2 = + std::async(std::launch::async, [&group] { group.readAny(); }); + BOOST_CHECK(futureRead2.wait_for(std::chrono::milliseconds(200)) == + std::future_status::timeout); BOOST_CHECK(app.testModule.mixedGroup.consumingPush == 3); BOOST_CHECK(app.testModule.mixedGroup.consumingPush2 == 666); BOOST_CHECK(app.testModule.mixedGroup.consumingPush3 == 120); @@ -328,7 +349,8 @@ BOOST_AUTO_TEST_CASE( testReadAny ) { app.testModule.feedingPoll3.write(); // make sure readAny still does not receive anything - BOOST_CHECK(futureRead2.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); + BOOST_CHECK(futureRead2.wait_for(std::chrono::milliseconds(200)) == + std::future_status::timeout); BOOST_CHECK(app.testModule.mixedGroup.consumingPush == 3); BOOST_CHECK(app.testModule.mixedGroup.consumingPush2 == 666); BOOST_CHECK(app.testModule.mixedGroup.consumingPush3 == 120); @@ -341,7 +363,8 @@ BOOST_AUTO_TEST_CASE( testReadAny ) { app.testModule.feedingPush2.write(); // check that the group now receives the just written values - BOOST_CHECK(futureRead2.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); + BOOST_CHECK(futureRead2.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); BOOST_CHECK(app.testModule.mixedGroup.consumingPush == 3); BOOST_CHECK(app.testModule.mixedGroup.consumingPush2 == 123); BOOST_CHECK(app.testModule.mixedGroup.consumingPush3 == 120); @@ -350,8 +373,10 @@ BOOST_AUTO_TEST_CASE( testReadAny ) { BOOST_CHECK(app.testModule.mixedGroup.consumingPoll3 == 88); // two changes at a time - auto futureRead3 = std::async(std::launch::async, [&group]{ group.readAny(); }); - BOOST_CHECK(futureRead3.wait_for(std::chrono::milliseconds(200)) == std::future_status::timeout); + auto futureRead3 = + std::async(std::launch::async, [&group] { group.readAny(); }); + BOOST_CHECK(futureRead3.wait_for(std::chrono::milliseconds(200)) == + std::future_status::timeout); BOOST_CHECK(app.testModule.mixedGroup.consumingPush == 3); BOOST_CHECK(app.testModule.mixedGroup.consumingPush2 == 123); BOOST_CHECK(app.testModule.mixedGroup.consumingPush3 == 120); @@ -364,14 +389,16 @@ BOOST_AUTO_TEST_CASE( testReadAny ) { app.testModule.feedingPush2.write(); app.testModule.feedingPush3.write(); - BOOST_CHECK(futureRead3.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); - auto futureRead4 = std::async(std::launch::async, [&group]{ group.readAny(); }); - BOOST_CHECK(futureRead4.wait_for(std::chrono::milliseconds(2000)) == std::future_status::ready); + BOOST_CHECK(futureRead3.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); + auto futureRead4 = + std::async(std::launch::async, [&group] { group.readAny(); }); + BOOST_CHECK(futureRead4.wait_for(std::chrono::milliseconds(2000)) == + std::future_status::ready); BOOST_CHECK(app.testModule.mixedGroup.consumingPush == 3); BOOST_CHECK(app.testModule.mixedGroup.consumingPush2 == 234); BOOST_CHECK(app.testModule.mixedGroup.consumingPush3 == 345); BOOST_CHECK(app.testModule.mixedGroup.consumingPoll == 66); BOOST_CHECK(app.testModule.mixedGroup.consumingPoll2 == 77); BOOST_CHECK(app.testModule.mixedGroup.consumingPoll3 == 88); - } diff --git a/tests/include/ExceptionDevice.h b/tests/include/ExceptionDevice.h index 07927de3..5fb56957 100644 --- a/tests/include/ExceptionDevice.h +++ b/tests/include/ExceptionDevice.h @@ -1,24 +1,27 @@ -#include <ChimeraTK/DummyBackend.h> #include <ChimeraTK/BackendFactory.h> #include <ChimeraTK/DeviceAccessVersion.h> +#include <ChimeraTK/DummyBackend.h> class ExceptionDummy : public ChimeraTK::DummyBackend { - public: - ExceptionDummy(std::string mapFileName) : DummyBackend(mapFileName) { throwException = false; } +public: + ExceptionDummy(std::string mapFileName) : DummyBackend(mapFileName) { + throwException = false; + } bool throwException; - static boost::shared_ptr<DeviceBackend> createInstance(std::string, std::map<std::string, std::string> parameters) { - return boost::shared_ptr<DeviceBackend>(new ExceptionDummy(parameters["map"])); + static boost::shared_ptr<DeviceBackend> + createInstance(std::string, std::map<std::string, std::string> parameters) { + return boost::shared_ptr<DeviceBackend>( + new ExceptionDummy(parameters["map"])); } void open() override { - if(throwException) { + if (throwException) { throw(ChimeraTK::runtime_error("DummyException: This is a test")); - } - else + } else ChimeraTK::DummyBackend::open(); } class BackendRegisterer { - public: + public: BackendRegisterer(); }; static BackendRegisterer backendRegisterer; @@ -27,6 +30,9 @@ class ExceptionDummy : public ChimeraTK::DummyBackend { ExceptionDummy::BackendRegisterer ExceptionDummy::backendRegisterer; ExceptionDummy::BackendRegisterer::BackendRegisterer() { - std::cout << "ExceptionDummy::BackendRegisterer: registering backend type ExceptionDummy" << std::endl; - ChimeraTK::BackendFactory::getInstance().registerBackendType("ExceptionDummy", &ExceptionDummy::createInstance); + std::cout << "ExceptionDummy::BackendRegisterer: registering backend type " + "ExceptionDummy" + << std::endl; + ChimeraTK::BackendFactory::getInstance().registerBackendType( + "ExceptionDummy", &ExceptionDummy::createInstance); } -- GitLab