From 91d7a59e365b766b5122100e16f226d5d0dbf75e Mon Sep 17 00:00:00 2001 From: Martin Hierholzer <martin.hierholzer@desy.de> Date: Wed, 24 Aug 2022 17:46:28 +0200 Subject: [PATCH] move inline implementations out of line --- include/Application.h | 56 +-- include/ApplicationModule.h | 17 +- include/ArrayAccessor.h | 175 +++++--- include/ConstantAccessor.h | 64 +-- include/ConsumingFanOut.h | 92 ++-- include/ControlSystemModule.h | 7 +- include/DebugPrintAccessorDecorator.h | 76 ++-- include/DeviceModule.h | 52 +-- include/EntityOwner.h | 17 +- include/FanOut.h | 123 +++--- include/FeedingFanOut.h | 394 +++++++++++------- include/Flags.h | 12 + include/InternalModule.h | 36 +- include/InversionOfControlAccessor.h | 137 ++++-- .../MetaDataPropagatingRegisterDecorator.h | 12 +- include/Module.h | 26 +- include/ModuleGroup.h | 8 +- include/ModuleImpl.h | 16 +- include/ScalarAccessor.h | 162 +++++-- include/SupportedUserTypes.h | 8 + include/TestFacility.h | 382 ++++++++--------- include/TestableModeAccessorDecorator.h | 276 +++++++----- include/ThreadedFanOut.h | 301 +++++++------ include/TriggerFanOut.h | 128 ++---- include/VariableGroup.h | 15 +- include/VariableNetwork.h | 6 +- include/VariableNetworkDumpingVisitor.h | 6 + include/VariableNetworkGraphDumpingVisitor.h | 7 +- ...VariableNetworkModuleGraphDumpingVisitor.h | 7 +- include/VariableNetworkNode.h | 27 +- include/VariableNetworkNodeDumpingVisitor.h | 8 + include/VirtualModule.h | 7 +- include/Visitor.h | 13 +- include/VoidAccessor.h | 63 ++- include/XMLGeneratorVisitor.h | 6 + src/Application.cc | 42 ++ src/ApplicationModule.cc | 15 + src/ControlSystemModule.cc | 9 + src/DebugPrintAccessorDecorator.cc | 89 ++++ src/DeviceModule.cc | 84 +++- src/EntityOwner.cc | 55 ++- src/Module.cc | 22 + src/ModuleGroup.cc | 13 + src/ModuleImpl.cc | 21 + src/TestFacility.cc | 131 ++++++ src/TriggerFanOut.cc | 116 ++++++ src/VariableGroup.cc | 13 + src/VariableNetwork.cc | 11 + src/VariableNetworkDumpingVisitor.cc | 6 + src/VariableNetworkGraphDumpingVisitor.cc | 11 + src/VariableNetworkNodeDumpingVisitor.cc | 6 + src/VirtualModule.cc | 37 +- src/VisitorHelper.cc | 6 + src/XMLGeneratorVisitor.cc | 11 + 54 files changed, 2192 insertions(+), 1248 deletions(-) create mode 100644 src/DebugPrintAccessorDecorator.cc create mode 100644 src/TestFacility.cc create mode 100644 src/TriggerFanOut.cc diff --git a/include/Application.h b/include/Application.h index a070f77b..12f85912 100644 --- a/include/Application.h +++ b/include/Application.h @@ -13,10 +13,11 @@ #include <atomic> #include <mutex> -//#include "DeviceModule.h" namespace ChimeraTK { + /*********************************************************************************************************************/ + class Module; class AccessorBase; class VariableNetwork; @@ -32,6 +33,8 @@ namespace ChimeraTK { template<typename UserType> class ConsumingFanOut; + /*********************************************************************************************************************/ + class Application : public ApplicationBase, public EntityOwner { public: /** The constructor takes the application name as an argument. The name must @@ -96,11 +99,7 @@ namespace ChimeraTK { * 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"); - } + void enableTestableMode(); /** * Returns true if application is in testable mode else returns @@ -146,10 +145,7 @@ namespace ChimeraTK { /** 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(); - } + static bool testableModeTestLock(); /** Set string holding the name of the current thread or the specified thread ID. This is used e.g. for * debugging output of the testable mode and for the internal profiler. */ @@ -163,11 +159,7 @@ namespace ChimeraTK { /** 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) { - Application::getInstance().setThreadName(name); - Profiler::registerThread(name); - pthread_setname_np(pthread_self(), name.substr(0, std::min<std::string::size_type>(name.length(), 15)).c_str()); - } + static void registerThread(const std::string& name); void debugMakeConnections() { enableDebugMakeConnections = true; }; @@ -197,27 +189,14 @@ namespace ChimeraTK { void enableDebugDataLoss() { debugDataLoss = true; } /** Incremenet counter for how many write() operations have overwritten unread data */ - static void incrementDataLossCounter(const std::string& name) { - if(getInstance().debugDataLoss) { - std::cout << "Data loss in variable " << name << std::endl; - } - getInstance().dataLossCounter++; - } + static void incrementDataLossCounter(const std::string& name); - 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; - } + static size_t getAndResetDataLossCounter(); /** 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); - } + static VariableNetworkNode makeConstant(UserType value, size_t length = 1, bool makeFeeder = true); void registerDeviceModule(DeviceModule* deviceModule); void unregisterDeviceModule(DeviceModule* deviceModule); @@ -515,10 +494,8 @@ namespace ChimeraTK { std::mutex m_threadNames; template<typename UserType> - friend class TestableModeAccessorDecorator; // needs access to the - // testableMode_mutex and - // testableMode_counter and the - // idMap + friend class + TestableModeAccessorDecorator; // needs access to the testableMode_mutex and testableMode_counter and the idMap friend class TestFacility; // needs access to testableMode_variables friend class DeviceModule; // needs access to testableMode_variables @@ -562,4 +539,13 @@ namespace ChimeraTK { } }; + /*********************************************************************************************************************/ + + template<typename UserType> + VariableNetworkNode Application::makeConstant(UserType value, size_t length, bool makeFeeder) { + return VariableNetworkNode::makeConstant(makeFeeder, value, length); + } + + /*********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/ApplicationModule.h b/include/ApplicationModule.h index 30077027..ed5ea5df 100644 --- a/include/ApplicationModule.h +++ b/include/ApplicationModule.h @@ -11,10 +11,14 @@ namespace ChimeraTK { + /*********************************************************************************************************************/ + class Application; class ModuleGroup; struct ConfigReader; + /*********************************************************************************************************************/ + class ApplicationModule : public ModuleImpl { public: /** Constructor: Create ApplicationModule by the given name with the given description and register it with its @@ -44,12 +48,7 @@ namespace ChimeraTK { 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; - } + ApplicationModule& operator=(ApplicationModule&& other); /** Destructor */ virtual ~ApplicationModule(); @@ -71,9 +70,7 @@ namespace ChimeraTK { void incrementDataFaultCounter() override; void decrementDataFaultCounter() override; - void setCurrentVersionNumber(VersionNumber versionNumber) override { - if(versionNumber > currentVersionNumber) currentVersionNumber = versionNumber; - } + void setCurrentVersionNumber(VersionNumber versionNumber) override; std::list<EntityOwner*> getInputModulesRecursively(std::list<EntityOwner*> startList) override; @@ -115,4 +112,6 @@ namespace ChimeraTK { detail::CircularDependencyDetectionRecursionStopper _recursionStopper; }; + /*********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/ArrayAccessor.h b/include/ArrayAccessor.h index ab3d2e73..0ad95039 100644 --- a/include/ArrayAccessor.h +++ b/include/ArrayAccessor.h @@ -32,52 +32,29 @@ namespace ChimeraTK { using ChimeraTK::OneDRegisterAccessor<UserType>::operator=; /** Move constructor */ - ArrayAccessor(ArrayAccessor<UserType>&& other) { - InversionOfControlAccessor<ArrayAccessor<UserType>>::replace(std::move(other)); - } + ArrayAccessor(ArrayAccessor<UserType>&& 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; - } + ArrayAccessor<UserType>& operator=(ArrayAccessor<UserType>&& other); bool write(ChimeraTK::VersionNumber versionNumber) = delete; bool writeDestructively(ChimeraTK::VersionNumber versionNumber) = delete; void writeIfDifferent(const std::vector<UserType>& newValue, VersionNumber versionNumber) = delete; - bool write() { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - bool dataLoss = ChimeraTK::OneDRegisterAccessor<UserType>::write(versionNumber); - if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); - return dataLoss; - } - - bool writeDestructively() { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - bool dataLoss = ChimeraTK::OneDRegisterAccessor<UserType>::writeDestructively(versionNumber); - if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); - return dataLoss; - } - - void writeIfDifferent(const std::vector<UserType>& newValue) { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - ChimeraTK::OneDRegisterAccessor<UserType>::writeIfDifferent(newValue, versionNumber); - } + bool write(); + + bool writeDestructively(); + + void writeIfDifferent(const std::vector<UserType>& newValue); 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) {} + const std::unordered_set<std::string>& tags = {}); - /** Default constructor creates a dysfunctional accessor (to be assigned with - * a real accessor later) */ + /** Default constructor creates a dysfunctional accessor (to be assigned with a real accessor later) */ ArrayAccessor() {} }; @@ -87,10 +64,8 @@ namespace ChimeraTK { 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>() {} + const std::string& description, const std::unordered_set<std::string>& tags = {}); + ArrayPushInput() = default; using ArrayAccessor<UserType>::operator=; }; @@ -100,10 +75,8 @@ namespace ChimeraTK { 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>() {} + const std::string& description, const std::unordered_set<std::string>& tags = {}); + ArrayPollInput() = default; void read() { this->readLatest(); } using ArrayAccessor<UserType>::operator=; }; @@ -114,10 +87,8 @@ namespace ChimeraTK { 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>() {} + const std::string& description, const std::unordered_set<std::string>& tags = {}); + ArrayOutput() = default; using ArrayAccessor<UserType>::operator=; }; @@ -128,10 +99,8 @@ namespace ChimeraTK { 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>() {} + const std::string& description, const std::unordered_set<std::string>& tags = {}); + ArrayPushInputWB() = default; using ArrayAccessor<UserType>::operator=; }; @@ -142,13 +111,115 @@ namespace ChimeraTK { 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>() {} + const std::string& description, const std::unordered_set<std::string>& tags = {}); + ArrayOutputRB() = default; using ArrayAccessor<UserType>::operator=; }; /********************************************************************************************************************/ + /********************************************************************************************************************/ + /* Implementations below this point */ + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ArrayAccessor<UserType>::ArrayAccessor(ArrayAccessor<UserType>&& other) { + InversionOfControlAccessor<ArrayAccessor<UserType>>::replace(std::move(other)); + } + + /********************************************************************************************************************/ + + template<typename UserType> + ArrayAccessor<UserType>& 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; + } + + /********************************************************************************************************************/ + + template<typename UserType> + bool ArrayAccessor<UserType>::write() { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + bool dataLoss = ChimeraTK::OneDRegisterAccessor<UserType>::write(versionNumber); + if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); + return dataLoss; + } + + /********************************************************************************************************************/ + + template<typename UserType> + bool ArrayAccessor<UserType>::writeDestructively() { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + bool dataLoss = ChimeraTK::OneDRegisterAccessor<UserType>::writeDestructively(versionNumber); + if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); + return dataLoss; + } + + /********************************************************************************************************************/ + + template<typename UserType> + void ArrayAccessor<UserType>::writeIfDifferent(const std::vector<UserType>& newValue) { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + ChimeraTK::OneDRegisterAccessor<UserType>::writeIfDifferent(newValue, versionNumber); + } + + /********************************************************************************************************************/ + + template<typename UserType> + 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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ArrayPushInput<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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ArrayPollInput<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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ArrayOutput<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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ArrayPushInputWB<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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ArrayOutputRB<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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/include/ConstantAccessor.h b/include/ConstantAccessor.h index 24086d41..045f1c7b 100644 --- a/include/ConstantAccessor.h +++ b/include/ConstantAccessor.h @@ -6,6 +6,8 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + /** Implementation of the NDRegisterAccessor which delivers always the same * value and ignors any write operations. @@ -22,34 +24,11 @@ namespace ChimeraTK { template<typename UserType> class ConstantAccessor : public ChimeraTK::NDRegisterAccessor<UserType> { public: - ConstantAccessor(UserType value = 0, size_t length = 1, AccessModeFlags accessModeFlags = AccessModeFlags{}) - : ChimeraTK::NDRegisterAccessor<UserType>("UnnamedConstantAccessor", accessModeFlags), _value(length, value) { - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0] = _value; - - if(TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) { - // This implementation does not have a data transport queue, hence _readQueue - // is not set up as a continuation. We directly make it a cppext::future_queue - TransferElement::_readQueue = cppext::future_queue<void>(3); // minimum required length is 2 - // Push once into the queue for the initial value. - TransferElement::_readQueue.push(); - } - } + ConstantAccessor(UserType value = 0, size_t length = 1, AccessModeFlags accessModeFlags = AccessModeFlags{}); void doReadTransferSynchronously() override {} - void doPostRead(TransferType /*type*/, bool updateUserBuffer) override { - // - updateUserBuffer is false for further calls to readLatest with wait_for_new_data. - // In this case the user buffer must not be touched. - // - updateUserBuffer is true for all calls without wait_for_new_data. The user buffer must - // be overwritten. - if(updateUserBuffer) { - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0] = _value; - // It is OK to generate the version number just here since the read transfer is empty anyway. - this->_versionNumber = {}; - this->_dataValidity = DataValidity::ok; // the constant is always valid by definiton - } - } + void doPostRead(TransferType /*type*/, bool updateUserBuffer) override; bool doWriteTransfer(ChimeraTK::VersionNumber /*versionNumber*/ = {}) override { return false; } @@ -73,4 +52,39 @@ namespace ChimeraTK { std::vector<UserType> _value; }; + /********************************************************************************************************************/ + + template<typename UserType> + ConstantAccessor<UserType>::ConstantAccessor(UserType value, size_t length, AccessModeFlags accessModeFlags) + : ChimeraTK::NDRegisterAccessor<UserType>("UnnamedConstantAccessor", accessModeFlags), _value(length, value) { + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0] = _value; + + if(TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) { + // This implementation does not have a data transport queue, hence _readQueue + // is not set up as a continuation. We directly make it a cppext::future_queue + TransferElement::_readQueue = cppext::future_queue<void>(3); // minimum required length is 2 + // Push once into the queue for the initial value. + TransferElement::_readQueue.push(); + } + } + + /********************************************************************************************************************/ + + template<typename UserType> + void ConstantAccessor<UserType>::doPostRead(TransferType /*type*/, bool updateUserBuffer) { + // - updateUserBuffer is false for further calls to readLatest with wait_for_new_data. + // In this case the user buffer must not be touched. + // - updateUserBuffer is true for all calls without wait_for_new_data. The user buffer must + // be overwritten. + if(updateUserBuffer) { + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0] = _value; + // It is OK to generate the version number just here since the read transfer is empty anyway. + this->_versionNumber = {}; + this->_dataValidity = DataValidity::ok; // the constant is always valid by definiton + } + } + + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/ConsumingFanOut.h b/include/ConsumingFanOut.h index 0e9d4e2f..91c6f47c 100644 --- a/include/ConsumingFanOut.h +++ b/include/ConsumingFanOut.h @@ -8,6 +8,8 @@ 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 @@ -16,52 +18,70 @@ namespace ChimeraTK { class ConsumingFanOut : public FanOut<UserType>, public ChimeraTK::NDRegisterAccessorDecorator<UserType> { public: ConsumingFanOut(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl, - ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) - : FanOut<UserType>(feedingImpl), ChimeraTK::NDRegisterAccessorDecorator<UserType>(feedingImpl) { - assert(feedingImpl->isReadable()); + ConsumerImplementationPairs<UserType> const& consumerImplementationPairs); - _lastReceivedValue.resize(buffer_2D[0].size()); + void doPostRead(TransferType type, bool updateDataBuffer) override; - // Add the consuming accessors - for(auto el : consumerImplementationPairs) { - FanOut<UserType>::addSlave(el.first, el.second); - } + void interrupt() override; + + protected: + using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D; + std::vector<UserType> _lastReceivedValue; + }; + + /********************************************************************************************************************/ + + template<typename UserType> + ConsumingFanOut<UserType>::ConsumingFanOut(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl, + ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) + : FanOut<UserType>(feedingImpl), ChimeraTK::NDRegisterAccessorDecorator<UserType>(feedingImpl) { + assert(feedingImpl->isReadable()); + + _lastReceivedValue.resize(buffer_2D[0].size()); + + // Add the consuming accessors + for(auto el : consumerImplementationPairs) { + FanOut<UserType>::addSlave(el.first, el.second); } + } - void doPostRead(TransferType type, bool updateDataBuffer) override { - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, updateDataBuffer); + /********************************************************************************************************************/ - if(updateDataBuffer) { - // We have to keep a copy to write into the slaves. There might - // be decorators arount this fanout which swap out buffer_2D, so it is - // not available any more for a second read witout updateDataBuffer (exception case). - _lastReceivedValue = buffer_2D[0]; - } + template<typename UserType> + void ConsumingFanOut<UserType>::doPostRead(TransferType type, bool updateDataBuffer) { + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, updateDataBuffer); - // The ConsumingFanOut conceptually never has a wait_fow_new_data flags. Hence each read - // operation returns with "new" data, even in case of an exception. So each read - // always synchronises all slaves and pushes the content of the data buffer. - 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) = _lastReceivedValue; - } - slave->setDataValidity(this->dataValidity()); - slave->writeDestructively(); - } + if(updateDataBuffer) { + // We have to keep a copy to write into the slaves. There might + // be decorators arount this fanout which swap out buffer_2D, so it is + // not available any more for a second read witout updateDataBuffer (exception case). + _lastReceivedValue = buffer_2D[0]; } - void interrupt() override { - // call the interrut sequences of the fan out (interrupts for fan input and all outputs), and the ndRegisterAccessor - FanOut<UserType>::interrupt(); - if(this->_accessModeFlags.has(AccessMode::wait_for_new_data)) { - ChimeraTK::NDRegisterAccessor<UserType>::interrupt(); + // The ConsumingFanOut conceptually never has a wait_fow_new_data flags. Hence each read + // operation returns with "new" data, even in case of an exception. So each read + // always synchronises all slaves and pushes the content of the data buffer. + 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) = _lastReceivedValue; } + slave->setDataValidity(this->dataValidity()); + slave->writeDestructively(); } + } - protected: - using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D; - std::vector<UserType> _lastReceivedValue; - }; + /********************************************************************************************************************/ + + template<typename UserType> + void ConsumingFanOut<UserType>::interrupt() { + // call the interrut sequences of the fan out (interrupts for fan input and all outputs), and the ndRegisterAccessor + FanOut<UserType>::interrupt(); + if(this->_accessModeFlags.has(AccessMode::wait_for_new_data)) { + ChimeraTK::NDRegisterAccessor<UserType>::interrupt(); + } + } + + /********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/include/ControlSystemModule.h b/include/ControlSystemModule.h index cecb9159..f9c85e4c 100644 --- a/include/ControlSystemModule.h +++ b/include/ControlSystemModule.h @@ -20,12 +20,7 @@ namespace ChimeraTK { 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); - return *this; - } + ControlSystemModule& operator=(ControlSystemModule&& other); /** The function call operator returns a VariableNetworkNode which can be used * in the Application::initialise() function to connect the control system diff --git a/include/DebugPrintAccessorDecorator.h b/include/DebugPrintAccessorDecorator.h index 51b54a48..8b810242 100644 --- a/include/DebugPrintAccessorDecorator.h +++ b/include/DebugPrintAccessorDecorator.h @@ -8,64 +8,38 @@ namespace ChimeraTK { - /** Decorator of the NDRegisterAccessor which facilitates tests of the - * application */ + /********************************************************************************************************************/ + + /** + * 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::flush; - auto ret = ChimeraTK::NDRegisterAccessorDecorator<UserType>::doWriteTransfer(versionNumber); - if(ret) { - std::cout << " -> DATA LOSS!"; - } - std::cout << std::endl; - return ret; - } - - bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) override { - std::cout << "doWriteTransferDestructively() called on '" << _fullyQualifiedName << "'." << std::flush; - auto ret = ChimeraTK::NDRegisterAccessorDecorator<UserType>::doWriteTransferDestructively(versionNumber); - if(ret) { - std::cout << " -> DATA LOSS!"; - } - std::cout << std::endl; - return ret; - } - - void doReadTransferSynchronously() override { - std::cout << "doReadTransferSynchronously() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doReadTransferSynchronously(); - } - - void doPreRead(TransferType type) override { - std::cout << "preRead() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreRead(type); - } - - void doPostRead(TransferType type, bool hasNewData) override { - std::cout << "postRead() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, hasNewData); - } - - void doPreWrite(TransferType type, VersionNumber versionNumber) override { - std::cout << "preWrite() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreWrite(type, versionNumber); - } - - void doPostWrite(TransferType type, VersionNumber versionNumber) override { - std::cout << "postWrite() called on '" << _fullyQualifiedName << "'." << std::endl; - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostWrite(type, versionNumber); - } + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, const std::string& fullyQualifiedName); + + bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber) override; + + bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) override; + + void doReadTransferSynchronously() override; + + void doPreRead(TransferType type) override; + + void doPostRead(TransferType type, bool hasNewData) override; + + void doPreWrite(TransferType type, VersionNumber versionNumber) override; + + void doPostWrite(TransferType type, VersionNumber versionNumber) override; protected: std::string _fullyQualifiedName; }; + /********************************************************************************************************************/ + + DECLARE_TEMPLATE_FOR_CHIMERATK_USER_TYPES(DebugPrintAccessorDecorator); + + /********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/include/DeviceModule.h b/include/DeviceModule.h index 1a24bdb6..ab7d526e 100644 --- a/include/DeviceModule.h +++ b/include/DeviceModule.h @@ -31,18 +31,24 @@ namespace ChimeraTK { namespace detail { struct DeviceModuleProxy : Module { DeviceModuleProxy(const DeviceModule& owner, const std::string& registerNamePrefix); + DeviceModuleProxy(DeviceModuleProxy&& other); - DeviceModuleProxy() {} + + DeviceModuleProxy() = default; 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; + VariableNetworkNode operator()(const std::string& variableName) const override; 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; } DeviceModuleProxy& operator=(DeviceModuleProxy&& other); @@ -82,34 +88,18 @@ namespace ChimeraTK { DeviceModule(DeviceModule&& other) { operator=(std::move(other)); } /** Move assignment */ - DeviceModule& operator=(DeviceModule&& other) { - assert(!moduleThread.joinable()); - assert(other.isHoldingInitialValueLatch); - if(owner) owner->unregisterDeviceModule(this); - Module::operator=(std::move(other)); - device = std::move(other.device); - deviceAliasOrURI = std::move(other.deviceAliasOrURI); - registerNamePrefix = std::move(other.registerNamePrefix); - deviceError = std::move(other.deviceError); - owner = other.owner; - proxies = std::move(other.proxies); - deviceHasError = other.deviceHasError; - for(auto& proxy : proxies) proxy.second._myowner = this; - owner->registerDeviceModule(this); - return *this; - } + DeviceModule& operator=(DeviceModule&& other); + /** 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); - } + size_t nElements = 0, UpdateMode mode = UpdateMode::poll) const; + + VariableNetworkNode operator()(const std::string& variableName) const override; Module& operator[](const std::string& moduleName) const override; @@ -133,9 +123,7 @@ namespace ChimeraTK { VersionNumber getCurrentVersionNumber() const override { return currentVersionNumber; } - void setCurrentVersionNumber(VersionNumber versionNumber) override { - if(versionNumber > currentVersionNumber) currentVersionNumber = versionNumber; - } + void setCurrentVersionNumber(VersionNumber versionNumber) override; VersionNumber currentVersionNumber{nullptr}; @@ -145,14 +133,10 @@ namespace ChimeraTK { mutable Device device; DataValidity getDataValidity() const override { return DataValidity::ok; } - void incrementDataFaultCounter() override { - throw ChimeraTK::logic_error("incrementDataFaultCounter() called on a DeviceModule. This is probably " - "caused by incorrect ownership of variables/accessors or VariableGroups."); - } - void decrementDataFaultCounter() override { - throw ChimeraTK::logic_error("decrementDataFaultCounter() called on a DeviceModule. This is probably " - "caused by incorrect ownership of variables/accessors or VariableGroups."); - } + + void incrementDataFaultCounter() override; + + void decrementDataFaultCounter() override; /** Add initialisation handlers to the device. * diff --git a/include/EntityOwner.h b/include/EntityOwner.h index 50a0f2a1..360b1819 100644 --- a/include/EntityOwner.h +++ b/include/EntityOwner.h @@ -10,16 +10,22 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + class AccessorBase; class Module; class VirtualModule; + /********************************************************************************************************************/ + /** * Convenience type definition which can optionally be used as a shortcut for the type which defines a list of * tags. */ using TAGS = const std::unordered_set<std::string>; + /********************************************************************************************************************/ + /** * Base class for owners of other EntityOwners (e.g. Modules) and Accessors. * FIXME: Unify with Module class (not straight forward!). @@ -38,9 +44,7 @@ namespace ChimeraTK { const std::unordered_set<std::string>& tags = {}); /** Default constructor just for late initialisation */ - EntityOwner() - : _name("**INVALID**"), _description("Invalid EntityOwner created by default constructor just " - "as a place holder") {} + EntityOwner(); /** Virtual destructor to make the type polymorphic */ virtual ~EntityOwner(); @@ -106,10 +110,7 @@ namespace ChimeraTK { /** 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); - } + void registerAccessor(VariableNetworkNode accessor); /** Called inside the destructor of Accessor: removes the accessor from the * list */ @@ -272,4 +273,6 @@ namespace ChimeraTK { return "@CONST@" + userTypeToUserType<std::string>(value); } + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/FanOut.h b/include/FanOut.h index a86fdeb1..82ea8d63 100644 --- a/include/FanOut.h +++ b/include/FanOut.h @@ -11,10 +11,14 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + template<typename UserType> using ConsumerImplementationPairs = std::list<std::pair<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>, VariableNetworkNode>>; + /********************************************************************************************************************/ + /** Type independent base */ class FanOutBase { public: @@ -30,6 +34,8 @@ namespace ChimeraTK { bool _disabled{false}; }; + /********************************************************************************************************************/ + /** Base class for several implementations which distribute values from one * feeder to multiple consumers */ template<typename UserType> @@ -40,59 +46,13 @@ namespace ChimeraTK { /** 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(); - what += " Length of master: " + std::to_string(impl->getNumberOfChannels()) + " x " + - std::to_string(impl->getNumberOfSamples()); - what += " Length of slave: " + std::to_string(slave->getNumberOfChannels()) + " x " + - std::to_string(slave->getNumberOfSamples()); - throw ChimeraTK::logic_error(what.c_str()); - } - slaves.push_back(slave); - } + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, VariableNetworkNode& /*consumer*/); // remove a slave identified by its consuming node from the FanOut - void removeSlave(const boost::shared_ptr<ChimeraTK::TransferElement>& slave) override { - // make sure the slave is actually currently in the list, and get it by the right typ - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave_typed; - for(auto& s : slaves) { - if(s == slave) { - slave_typed = s; - break; - } - } - assert(slave_typed != nullptr); - - size_t nOld = slaves.size(); - slaves.remove(slave_typed); - assert(slaves.size() == nOld - 1); - } + void removeSlave(const boost::shared_ptr<ChimeraTK::TransferElement>& slave) override; // interrupt the input and all slaves - virtual void interrupt() { - if(impl) { - if(impl->getAccessModeFlags().has(AccessMode::wait_for_new_data)) { - impl->interrupt(); - } - } - for(auto& slave : slaves) { - if(slave->getAccessModeFlags().has(AccessMode::wait_for_new_data)) { - slave->interrupt(); - } - } - } + virtual void interrupt(); protected: boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> impl; @@ -100,4 +60,69 @@ namespace ChimeraTK { std::list<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>> slaves; }; + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + void FanOut<UserType>::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(); + what += " Length of master: " + std::to_string(impl->getNumberOfChannels()) + " x " + + std::to_string(impl->getNumberOfSamples()); + what += " Length of slave: " + std::to_string(slave->getNumberOfChannels()) + " x " + + std::to_string(slave->getNumberOfSamples()); + throw ChimeraTK::logic_error(what.c_str()); + } + slaves.push_back(slave); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void FanOut<UserType>::removeSlave(const boost::shared_ptr<ChimeraTK::TransferElement>& slave) { + // make sure the slave is actually currently in the list, and get it by the right typ + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave_typed; + for(auto& s : slaves) { + if(s == slave) { + slave_typed = s; + break; + } + } + assert(slave_typed != nullptr); + + size_t nOld = slaves.size(); + slaves.remove(slave_typed); + assert(slaves.size() == nOld - 1); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void FanOut<UserType>::interrupt() { + if(impl) { + if(impl->getAccessModeFlags().has(AccessMode::wait_for_new_data)) { + impl->interrupt(); + } + } + for(auto& slave : slaves) { + if(slave->getAccessModeFlags().has(AccessMode::wait_for_new_data)) { + slave->interrupt(); + } + } + } + + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/FeedingFanOut.h b/include/FeedingFanOut.h index 696de4a6..000b86a5 100644 --- a/include/FeedingFanOut.h +++ b/include/FeedingFanOut.h @@ -11,6 +11,8 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + /** * NDRegisterAccessor implementation which distributes values written to this * accessor out to any number of slaves. @@ -20,208 +22,284 @@ namespace ChimeraTK { public: FeedingFanOut(std::string const& name, std::string const& unit, std::string const& description, size_t numberOfElements, bool withReturn, - ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) - : FanOut<UserType>(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>()), - // We pass default-constructed, empty AccessModeFlags, they may later be determined from _returnSlave - ChimeraTK::NDRegisterAccessor<UserType>("FeedingFanOut:" + name, AccessModeFlags{}, unit, description), - _withReturn(withReturn) { - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); - ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0].resize(numberOfElements); - - this->_readQueue = cppext::future_queue<void>(3); - - // Add the consuming accessors - // TODO FanOut constructors and addSlave should get refactoring - for(auto el : consumerImplementationPairs) { - addSlave(el.first, el.second); - } - } + ConsumerImplementationPairs<UserType> const& consumerImplementationPairs); /** 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()); - } + void addSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, VariableNetworkNode&) override; - // make sure slave is writeable - if(!slave->isWriteable()) { - throw ChimeraTK::logic_error("FeedingFanOut::addSlave() has been called " - "with a receiving implementation!"); - } + bool isReadable() const override { return _withReturn; } - // handle return channels - if(_withReturn) { - if(slave->isReadable()) { - if(_hasReturnSlave) { - throw ChimeraTK::logic_error("FeedingFanOut: Cannot add multiple slaves with return channel!"); - } + bool isReadOnly() const override { return false; } - // Assert the assumption about the return channel made in the constructor - assert(slave->getAccessModeFlags().has(AccessMode::wait_for_new_data)); + bool isWriteable() const override { return true; } - _hasReturnSlave = true; - _returnSlave = slave; + void doReadTransferSynchronously() override; - // Set the readQeue from the return slave - // As this becomes the implemention of the feeding output, the flags are determined by that slave accessor - // If not _withReturn, the queue is not relevant because the feeding node is on output which is never read - this->_readQueue = _returnSlave->getReadQueue(); - this->_accessModeFlags = _returnSlave->getAccessModeFlags(); - } - } + void doPreRead(TransferType type) override; - // add the slave - FanOut<UserType>::slaves.push_back(slave); - } + void doPostRead(TransferType type, bool hasNewData) override; - bool isReadable() const override { return _withReturn; } + void doPreWrite(TransferType, VersionNumber) override; - bool isReadOnly() const override { return false; } + bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber) override; - bool isWriteable() const override { return true; } + bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber = {}) override; + + void doPostWrite(TransferType, VersionNumber) override; + + bool mayReplaceOther(const boost::shared_ptr<const ChimeraTK::TransferElement>&) const override; + + std::list<boost::shared_ptr<ChimeraTK::TransferElement>> getInternalElements() override; + + std::vector<boost::shared_ptr<ChimeraTK::TransferElement>> getHardwareAccessingElements() override; + + void replaceTransferElement(boost::shared_ptr<ChimeraTK::TransferElement>) override; - void doReadTransferSynchronously() override { - if(this->_disabled) return; - assert(_withReturn); - _returnSlave->readTransfer(); + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> getReturnSlave() { return _returnSlave; } + + void interrupt() override; + + 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; + + /// DataValidity to attach to the data + DataValidity validity{DataValidity::ok}; + }; + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + FeedingFanOut<UserType>::FeedingFanOut(std::string const& name, std::string const& unit, + std::string const& description, size_t numberOfElements, bool withReturn, + ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) + : FanOut<UserType>(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>()), + // We pass default-constructed, empty AccessModeFlags, they may later be determined from _returnSlave + ChimeraTK::NDRegisterAccessor<UserType>("FeedingFanOut:" + name, AccessModeFlags{}, unit, description), + _withReturn(withReturn) { + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); + ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0].resize(numberOfElements); + + this->_readQueue = cppext::future_queue<void>(3); + + // Add the consuming accessors + // TODO FanOut constructors and addSlave should get refactoring + for(auto el : consumerImplementationPairs) { + addSlave(el.first, el.second); } + } - void doPreRead(TransferType type) override { - if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); - if(this->_disabled) return; - _returnSlave->accessChannel(0).swap(ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); - _returnSlave->preRead(type); + /********************************************************************************************************************/ + + template<typename UserType> + void FeedingFanOut<UserType>::addSlave( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, VariableNetworkNode&) { + // 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()); } - void doPostRead(TransferType type, bool hasNewData) override { - if(this->_disabled) return; - assert(_withReturn); - assert(_hasReturnSlave); - - auto _ = cppext::finally([&] { - if(!hasNewData) return; - _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->writeDestructively(_returnSlave->getVersionNumber()); + // 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!"); } - }); - _returnSlave->postRead(type, hasNewData); + // Assert the assumption about the return channel made in the constructor + assert(slave->getAccessModeFlags().has(AccessMode::wait_for_new_data)); + + _hasReturnSlave = true; + _returnSlave = slave; - this->_versionNumber = _returnSlave->getVersionNumber(); - this->_dataValidity = _returnSlave->dataValidity(); + // Set the readQeue from the return slave + // As this becomes the implemention of the feeding output, the flags are determined by that slave accessor + // If not _withReturn, the queue is not relevant because the feeding node is on output which is never read + this->_readQueue = _returnSlave->getReadQueue(); + this->_accessModeFlags = _returnSlave->getAccessModeFlags(); + } } - void doPreWrite(TransferType, VersionNumber) override { - if(this->_disabled) return; - 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); - } + // add the slave + FanOut<UserType>::slaves.push_back(slave); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void FeedingFanOut<UserType>::doReadTransferSynchronously() { + if(this->_disabled) return; + assert(_withReturn); + _returnSlave->readTransfer(); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void FeedingFanOut<UserType>::doPreRead(TransferType type) { + if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); + if(this->_disabled) return; + _returnSlave->accessChannel(0).swap(ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); + _returnSlave->preRead(type); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void FeedingFanOut<UserType>::doPostRead(TransferType type, bool hasNewData) { + if(this->_disabled) return; + assert(_withReturn); + assert(_hasReturnSlave); + + auto _ = cppext::finally([&] { + if(!hasNewData) return; + _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->setDataValidity(this->dataValidity()); + slave->writeDestructively(_returnSlave->getVersionNumber()); } + }); - // Don't call pre-write on the slaves. Each slave has to do it's own exception handling, so we call the whole - // operation in doWriteTansfer(). To fulfill the TransferElement specification we would have to check the - // pre-conditions here so no logic error is thrown in the transfer phase (logic_errors are predictable and can - // always pre prevented. They should be thrown here already). - // FIXME: At the moment we can be lazy about it. logic_errors are not treated in ApplicationCore and the only - // effect is that the logic_error would be delayed after postRead() and terminate the application there, and not - // after the transfer. Advantage about being lazy: It safes a few virtual function calls. - } + _returnSlave->postRead(type, hasNewData); + + this->_versionNumber = _returnSlave->getVersionNumber(); + this->_dataValidity = _returnSlave->dataValidity(); + } + + /********************************************************************************************************************/ - bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber) override { - if(this->_disabled) return false; - bool dataLost = false; - bool isFirst = true; - for(auto& slave : FanOut<UserType>::slaves) { - bool ret; - if(isFirst) { - isFirst = false; - ret = slave->write(versionNumber); + template<typename UserType> + void FeedingFanOut<UserType>::doPreWrite(TransferType, VersionNumber) { + if(this->_disabled) return; + 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 { - ret = slave->writeDestructively(versionNumber); + else { // not the first slave: copy the data from the first slave + slave->accessChannel(0) = FanOut<UserType>::slaves.front()->accessChannel(0); } - if(ret) dataLost = true; } - return dataLost; + slave->setDataValidity(this->dataValidity()); } - bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber = {}) override { - if(this->_disabled) return false; - bool dataLost = false; - for(auto& slave : FanOut<UserType>::slaves) { - bool ret = slave->writeDestructively(versionNumber); - if(ret) dataLost = true; + // Don't call pre-write on the slaves. Each slave has to do it's own exception handling, so we call the whole + // operation in doWriteTansfer(). To fulfill the TransferElement specification we would have to check the + // pre-conditions here so no logic error is thrown in the transfer phase (logic_errors are predictable and can + // always pre prevented. They should be thrown here already). + // FIXME: At the moment we can be lazy about it. logic_errors are not treated in ApplicationCore and the only + // effect is that the logic_error would be delayed after postRead() and terminate the application there, and not + // after the transfer. Advantage about being lazy: It safes a few virtual function calls. + } + + /********************************************************************************************************************/ + + template<typename UserType> + bool FeedingFanOut<UserType>::doWriteTransfer(ChimeraTK::VersionNumber versionNumber) { + if(this->_disabled) return false; + bool dataLost = false; + bool isFirst = true; + for(auto& slave : FanOut<UserType>::slaves) { + bool ret; + if(isFirst) { + isFirst = false; + ret = slave->write(versionNumber); + } + else { + ret = slave->writeDestructively(versionNumber); } - return dataLost; + if(ret) dataLost = true; } + return dataLost; + } - void doPostWrite(TransferType, VersionNumber) override { - if(this->_disabled) return; - // the postWrite() on the slaves has already been called - 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? + template<typename UserType> + bool FeedingFanOut<UserType>::doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) { + if(this->_disabled) return false; + bool dataLost = false; + for(auto& slave : FanOut<UserType>::slaves) { + bool ret = slave->writeDestructively(versionNumber); + if(ret) dataLost = true; } + return dataLost; + } - 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? - } + template<typename UserType> + void FeedingFanOut<UserType>::doPostWrite(TransferType, VersionNumber) { + if(this->_disabled) return; + // the postWrite() on the slaves has already been called + FanOut<UserType>::slaves.front()->accessChannel(0).swap(ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); + } - void replaceTransferElement(boost::shared_ptr<ChimeraTK::TransferElement>) override { - // You can't replace anything here. Just do nothing. - /// @todo implement properly? - } + /********************************************************************************************************************/ - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> getReturnSlave() { return _returnSlave; } + template<typename UserType> + bool FeedingFanOut<UserType>::mayReplaceOther(const boost::shared_ptr<const ChimeraTK::TransferElement>&) const { + return false; /// @todo implement properly? + } - void interrupt() override { - // call the interrut sequences of the fan out (interrupts for fan input and all outputs), and the ndRegisterAccessor - FanOut<UserType>::interrupt(); - if(_withReturn) { - _returnSlave->interrupt(); - } - } + /********************************************************************************************************************/ - protected: - /// Flag whether this FeedingFanOut has a return channel. Is specified in the - /// constructor - bool _withReturn; + template<typename UserType> + std::list<boost::shared_ptr<ChimeraTK::TransferElement>> FeedingFanOut<UserType>::getInternalElements() { + return {}; /// @todo implement properly? + } - /// 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; + template<typename UserType> + std::vector<boost::shared_ptr<ChimeraTK::TransferElement>> FeedingFanOut<UserType>::getHardwareAccessingElements() { + return {boost::enable_shared_from_this<ChimeraTK::TransferElement>::shared_from_this()}; /// @todo implement + /// properly? + } - /// DataValidity to attach to the data - DataValidity validity{DataValidity::ok}; - }; + /********************************************************************************************************************/ + + template<typename UserType> + void FeedingFanOut<UserType>::replaceTransferElement(boost::shared_ptr<ChimeraTK::TransferElement>) { + // You can't replace anything here. Just do nothing. + /// @todo implement properly? + } + + /********************************************************************************************************************/ + + template<typename UserType> + void FeedingFanOut<UserType>::interrupt() { + // call the interrut sequences of the fan out (interrupts for fan input and all outputs), and the ndRegisterAccessor + FanOut<UserType>::interrupt(); + if(_withReturn) { + _returnSlave->interrupt(); + } + } + + /********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/include/Flags.h b/include/Flags.h index b0756fd1..65385fc5 100644 --- a/include/Flags.h +++ b/include/Flags.h @@ -4,6 +4,8 @@ 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 { @@ -21,12 +23,18 @@ namespace ChimeraTK { 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 }; + /********************************************************************************************************************/ + /** Hierarchy modifier: specify if and how the module hierarchy should be modified in EntityOwner::findTag() etc. */ enum class HierarchyModifier { none, ///< No modification is performed @@ -43,6 +51,8 @@ namespace ChimeraTK { ///< inside an application. }; + /********************************************************************************************************************/ + /** Enum to define the life-cycle states of an Application. */ enum class LifeCycleState { initialisation, ///< Initialisation phase including ApplicationModule::prepare(). Single threaded operation. All @@ -53,4 +63,6 @@ namespace ChimeraTK { shutdown ///< The application is in the process of shutting down. }; + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/InternalModule.h b/include/InternalModule.h index 3f571791..2a9a5a0f 100644 --- a/include/InternalModule.h +++ b/include/InternalModule.h @@ -10,6 +10,8 @@ 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. @@ -18,7 +20,7 @@ namespace ChimeraTK { * to be properly unified with the normal Module classes. */ class InternalModule : public EntityOwner { public: - ~InternalModule() override {} + ~InternalModule() override = default; /** Activate synchronisation thread if needed * @todo: Unify with Module::run() */ @@ -38,16 +40,28 @@ namespace ChimeraTK { DataValidity getDataValidity() const override { throw; } void incrementDataFaultCounter() override { throw; } void decrementDataFaultCounter() override { throw; } - std::list<EntityOwner*> getInputModulesRecursively([[maybe_unused]] std::list<EntityOwner*> startList) override { - throw ChimeraTK::logic_error("getInputModulesRecursively() called on an InternalModule (ThreadedFanout or " - "TriggerFanout). This is probably " - "caused by incorrect ownership of variables/accessors or VariableGroups."); - } - size_t getCircularNetworkHash() override { - throw ChimeraTK::logic_error("getCircularNetworkHash() called on an InternalModule (ThreadedFanout or " - "TriggerFanout). This is probably " - "caused by incorrect ownership of variables/accessors or VariableGroups."); - } + std::list<EntityOwner*> getInputModulesRecursively([[maybe_unused]] std::list<EntityOwner*> startList) override; + size_t getCircularNetworkHash() override; }; + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + inline std::list<EntityOwner*> InternalModule::getInputModulesRecursively( + [[maybe_unused]] std::list<EntityOwner*> startList) { + throw ChimeraTK::logic_error("getInputModulesRecursively() called on an InternalModule (ThreadedFanout or " + "TriggerFanout). This is probably " + "caused by incorrect ownership of variables/accessors or VariableGroups."); + } + + /********************************************************************************************************************/ + + inline size_t InternalModule::getCircularNetworkHash() { + throw ChimeraTK::logic_error("getCircularNetworkHash() called on an InternalModule (ThreadedFanout or " + "TriggerFanout). This is probably " + "caused by incorrect ownership of variables/accessors or VariableGroups."); + } + + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/InversionOfControlAccessor.h b/include/InversionOfControlAccessor.h index f129c9e7..6913303d 100644 --- a/include/InversionOfControlAccessor.h +++ b/include/InversionOfControlAccessor.h @@ -11,6 +11,8 @@ 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. */ @@ -18,21 +20,15 @@ namespace ChimeraTK { class InversionOfControlAccessor { public: /** Unregister at its owner when deleting */ - ~InversionOfControlAccessor() { - if(getOwner() != nullptr) getOwner()->unregisterAccessor(node); - } + ~InversionOfControlAccessor(); /** 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); 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); - } + const std::unordered_set<std::string>& tags); /** Add a tag. Valid names for tags only contain alpha-numeric characters * (i.e. no spaces and no special characters). */ @@ -40,9 +36,7 @@ namespace ChimeraTK { /** 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); - } + void addTags(const std::unordered_set<std::string>& tags); /** Convert into VariableNetworkNode */ operator VariableNetworkNode() { return node; } @@ -52,54 +46,105 @@ namespace ChimeraTK { 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(); - if(node.getType() == NodeType::Application) { - node.setAppAccessorPointer(static_cast<Derived*>(this)); - } - else { - assert(node.getType() == NodeType::invalid); - } - // Note: the accessor is registered by the VariableNetworkNode, so we don't - // have to re-register. - } + void replace(Derived&& other); /** 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; - } + std::string completeDescription(EntityOwner* owner, const std::string& 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); - } + const std::unordered_set<std::string>& tags = {}); /** Default constructor creates a dysfunctional accessor (to be assigned with * a real accessor later) */ - InversionOfControlAccessor() {} + InversionOfControlAccessor() = default; VariableNetworkNode node; }; + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename Derived> + InversionOfControlAccessor<Derived>::~InversionOfControlAccessor() { + if(getOwner() != nullptr) getOwner()->unregisterAccessor(node); + } + + /********************************************************************************************************************/ + + template<typename Derived> + void InversionOfControlAccessor<Derived>::setMetaData( + const std::string& name, const std::string& unit, const std::string& description) { + node.setMetaData(name, unit, completeDescription(getOwner(), description)); + } + + /********************************************************************************************************************/ + + template<typename Derived> + void InversionOfControlAccessor<Derived>::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); + } + + /********************************************************************************************************************/ + + template<typename Derived> + void InversionOfControlAccessor<Derived>::addTags(const std::unordered_set<std::string>& tags) { + for(auto& tag : tags) node.addTag(tag); + } + + /********************************************************************************************************************/ + + template<typename Derived> + void InversionOfControlAccessor<Derived>::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(); + if(node.getType() == NodeType::Application) { + node.setAppAccessorPointer(static_cast<Derived*>(this)); + } + else { + assert(node.getType() == NodeType::invalid); + } + // Note: the accessor is registered by the VariableNetworkNode, so we don't + // have to re-register. + } + + /********************************************************************************************************************/ + + template<typename Derived> + std::string InversionOfControlAccessor<Derived>::completeDescription( + EntityOwner* owner, const std::string& description) { + auto ownerDescription = owner->getFullDescription(); + if(ownerDescription == "") return description; + if(description == "") return ownerDescription; + return ownerDescription + " - " + description; + } + + /********************************************************************************************************************/ + + template<typename Derived> + InversionOfControlAccessor<Derived>::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); + } + + /********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/include/MetaDataPropagatingRegisterDecorator.h b/include/MetaDataPropagatingRegisterDecorator.h index 200dc3ef..bb9ce2a0 100644 --- a/include/MetaDataPropagatingRegisterDecorator.h +++ b/include/MetaDataPropagatingRegisterDecorator.h @@ -4,14 +4,16 @@ #include <ChimeraTK/NDRegisterAccessorDecorator.h> -/********************************************************************************************************************/ - namespace ChimeraTK { + /********************************************************************************************************************/ + // we can only declare the classes here but not use them/include the header to avoid a circular dependency class EntityOwner; class VariableNetworkNode; + /********************************************************************************************************************/ + /** * A mix-in helper class so you can set the flags without knowing the user data type. */ @@ -36,6 +38,8 @@ namespace ChimeraTK { friend class VariableNetworkNode; }; + /********************************************************************************************************************/ + /** * NDRegisterAccessorDecorator which propagates meta data attached to input process variables through the owning * ApplicationModule. It will set the current version number of the owning ApplicationModule in postRead. At the @@ -62,6 +66,10 @@ namespace ChimeraTK { using MetaDataPropagationFlagProvider::_isCircularInput; }; + /********************************************************************************************************************/ + DECLARE_TEMPLATE_FOR_CHIMERATK_USER_TYPES(MetaDataPropagatingRegisterDecorator); + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/Module.h b/include/Module.h index 0bc75641..5af1e25a 100644 --- a/include/Module.h +++ b/include/Module.h @@ -10,8 +10,12 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + class ApplicationModule; + /********************************************************************************************************************/ + /** Base class for ApplicationModule, DeviceModule and ControlSystemModule, to * have a common interface for these module types. */ class Module : public EntityOwner { @@ -121,19 +125,11 @@ namespace ChimeraTK { */ virtual void connectTo(const Module& target, VariableNetworkNode trigger = {}) const = 0; - std::string getQualifiedName() const override { - return ((_owner != nullptr) ? _owner->getQualifiedName() : "") + "/" + _name; - } + std::string getQualifiedName() const override; virtual std::string getVirtualQualifiedName() const; - 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; - } + std::string getFullDescription() const override; /** Set a new owner. The caller has to take care himself that the Module gets * unregistered with the old owner @@ -148,14 +144,16 @@ namespace ChimeraTK { void accept(Visitor<Module>& visitor) const { visitor.dispatch(*this); } VersionNumber getCurrentVersionNumber() const override { return _owner->getCurrentVersionNumber(); } + void setCurrentVersionNumber(VersionNumber version) override { _owner->setCurrentVersionNumber(version); } DataValidity getDataValidity() const override { return _owner->getDataValidity(); } + void incrementDataFaultCounter() override { _owner->incrementDataFaultCounter(); } + void decrementDataFaultCounter() override { _owner->decrementDataFaultCounter(); } - std::list<EntityOwner*> getInputModulesRecursively(std::list<EntityOwner*> startList) override { - return _owner->getInputModulesRecursively(startList); - } + + std::list<EntityOwner*> getInputModulesRecursively(std::list<EntityOwner*> startList) override; size_t getCircularNetworkHash() override { return _owner->getCircularNetworkHash(); } @@ -174,4 +172,6 @@ namespace ChimeraTK { EntityOwner* _owner{nullptr}; }; + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/ModuleGroup.h b/include/ModuleGroup.h index 3213e67a..27ffdb89 100644 --- a/include/ModuleGroup.h +++ b/include/ModuleGroup.h @@ -39,13 +39,7 @@ namespace ChimeraTK { ModuleGroup(ModuleGroup&& other) { operator=(std::move(other)); } /** Move assignment */ - ModuleGroup& operator=(ModuleGroup&& other) { - ModuleImpl::operator=(std::move(other)); - return *this; - } - - /** Destructor */ - virtual ~ModuleGroup(){}; + ModuleGroup& operator=(ModuleGroup&& other); ModuleType getModuleType() const override { return ModuleType::ModuleGroup; } }; diff --git a/include/ModuleImpl.h b/include/ModuleImpl.h index b6a9d44f..8fbeda18 100644 --- a/include/ModuleImpl.h +++ b/include/ModuleImpl.h @@ -17,25 +17,19 @@ namespace ChimeraTK { public: // constructor inheritances does not work due to a gcc bug!? ModuleImpl(EntityOwner* owner, const std::string& name, const std::string& description, - HierarchyModifier hierarchyModifier = HierarchyModifier::none, const std::unordered_set<std::string>& tags = {}) - : Module(owner, name, description, hierarchyModifier, tags) {} + HierarchyModifier hierarchyModifier = HierarchyModifier::none, + const std::unordered_set<std::string>& tags = {}); ModuleImpl(EntityOwner* owner, const std::string& name, const std::string& description, bool eliminateHierarchy, - const std::unordered_set<std::string>& tags = {}) - : Module(owner, name, description, eliminateHierarchy, tags) {} + const std::unordered_set<std::string>& tags = {}); - ModuleImpl() : Module() {} + ModuleImpl() = default; /** Move constructor */ ModuleImpl(ModuleImpl&& other) { operator=(std::move(other)); } /** 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& operator=(ModuleImpl&& other); VariableNetworkNode operator()(const std::string& variableName) const override; diff --git a/include/ScalarAccessor.h b/include/ScalarAccessor.h index de58d608..fd1c3ac9 100644 --- a/include/ScalarAccessor.h +++ b/include/ScalarAccessor.h @@ -33,52 +33,30 @@ namespace ChimeraTK { using ChimeraTK::ScalarRegisterAccessor<UserType>::operator=; /** Move constructor */ - ScalarAccessor(ScalarAccessor<UserType>&& other) { - InversionOfControlAccessor<ScalarAccessor<UserType>>::replace(std::move(other)); - } + ScalarAccessor(ScalarAccessor<UserType>&& 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; - } + ScalarAccessor<UserType>& operator=(ScalarAccessor<UserType>&& other); bool write(ChimeraTK::VersionNumber versionNumber) = delete; bool writeDestructively(ChimeraTK::VersionNumber versionNumber) = delete; void writeIfDifferent(UserType newValue, VersionNumber versionNumber) = delete; - bool write() { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - bool dataLoss = ChimeraTK::ScalarRegisterAccessor<UserType>::write(versionNumber); - if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); - return dataLoss; - } - - bool writeDestructively() { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - bool dataLoss = ChimeraTK::ScalarRegisterAccessor<UserType>::writeDestructively(versionNumber); - if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); - return dataLoss; - } - - void writeIfDifferent(UserType newValue) { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - ChimeraTK::ScalarRegisterAccessor<UserType>::writeIfDifferent(newValue, versionNumber); - } + bool write(); + + bool writeDestructively(); + + void writeIfDifferent(UserType newValue); 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) {} + UpdateMode mode, const std::string& description, const std::unordered_set<std::string>& tags = {}); /** Default constructor creates a dysfunctional accessor (to be assigned with * a real accessor later) */ - ScalarAccessor() {} + ScalarAccessor() = default; }; /********************************************************************************************************************/ @@ -87,9 +65,7 @@ namespace ChimeraTK { 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) {} + const std::unordered_set<std::string>& tags = {}); ScalarPushInput() : ScalarAccessor<UserType>() {} using ScalarAccessor<UserType>::operator=; }; @@ -100,9 +76,7 @@ namespace ChimeraTK { 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) {} + const std::unordered_set<std::string>& tags = {}); ScalarPollInput() : ScalarAccessor<UserType>() {} void read() { this->readLatest(); } using ScalarAccessor<UserType>::operator=; @@ -114,9 +88,7 @@ namespace ChimeraTK { 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) {} + const std::unordered_set<std::string>& tags = {}); ScalarOutput() : ScalarAccessor<UserType>() {} using ScalarAccessor<UserType>::operator=; }; @@ -127,9 +99,7 @@ namespace ChimeraTK { 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) {} + const std::unordered_set<std::string>& tags = {}); ScalarPushInputWB() : ScalarAccessor<UserType>() {} using ScalarAccessor<UserType>::operator=; }; @@ -140,13 +110,113 @@ namespace ChimeraTK { 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) {} + const std::unordered_set<std::string>& tags = {}); ScalarOutputPushRB() : ScalarAccessor<UserType>() {} using ScalarAccessor<UserType>::operator=; }; /********************************************************************************************************************/ + /********************************************************************************************************************/ + /* Implementations below this point */ + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ScalarAccessor<UserType>::ScalarAccessor(ScalarAccessor<UserType>&& other) { + InversionOfControlAccessor<ScalarAccessor<UserType>>::replace(std::move(other)); + } + + /********************************************************************************************************************/ + + template<typename UserType> + ScalarAccessor<UserType>& 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; + } + + /********************************************************************************************************************/ + + template<typename UserType> + bool ScalarAccessor<UserType>::write() { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + bool dataLoss = ChimeraTK::ScalarRegisterAccessor<UserType>::write(versionNumber); + if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); + return dataLoss; + } + + /********************************************************************************************************************/ + + template<typename UserType> + bool ScalarAccessor<UserType>::writeDestructively() { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + bool dataLoss = ChimeraTK::ScalarRegisterAccessor<UserType>::writeDestructively(versionNumber); + if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); + return dataLoss; + } + + /********************************************************************************************************************/ + + template<typename UserType> + void ScalarAccessor<UserType>::writeIfDifferent(UserType newValue) { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + ChimeraTK::ScalarRegisterAccessor<UserType>::writeIfDifferent(newValue, versionNumber); + } + + /********************************************************************************************************************/ + + template<typename UserType> + 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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ScalarPushInput<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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ScalarPollInput<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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ScalarOutput<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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ScalarPushInputWB<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) {} + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ScalarOutputPushRB<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) {} + + /********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/include/SupportedUserTypes.h b/include/SupportedUserTypes.h index 2c6b84c9..392f12fc 100644 --- a/include/SupportedUserTypes.h +++ b/include/SupportedUserTypes.h @@ -2,8 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #pragma once +#include <ChimeraTK/SupportedUserTypes.h> + 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>, @@ -11,6 +15,8 @@ namespace ChimeraTK { boost::fusion::pair<double, double>> ApplicationCoreUserTypeMap; + /********************************************************************************************************************/ + /** Map of UserType to a class template with the UserType as template argument. */ template<template<typename> class TemplateClass> @@ -24,4 +30,6 @@ namespace ChimeraTK { table; }; + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/TestFacility.h b/include/TestFacility.h index 32bae99c..bc8428dd 100644 --- a/include/TestFacility.h +++ b/include/TestFacility.h @@ -15,6 +15,8 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + namespace detail { template<typename T> @@ -29,6 +31,8 @@ namespace ChimeraTK { } // namespace detail + /********************************************************************************************************************/ + /** Helper class to facilitate tests of applications based on ApplicationCore */ class TestFacility { public: @@ -36,83 +40,15 @@ namespace ChimeraTK { * 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. */ - explicit TestFacility(bool enableTestableMode = true) { - auto pvManagers = createPVManager(); - pvManager = pvManagers.first; - Application::getInstance().setPVManager(pvManagers.second); - if(enableTestableMode) Application::getInstance().enableTestableMode(); - Application::getInstance().initialise(); - } + explicit TestFacility(bool enableTestableMode = true); /** Start the application in testable mode. */ - void runApplication() const { - Application::getInstance().testFacilityRunApplicationCalled = true; - // send default values for all control system variables - for(auto& pv : pvManager->getAllProcessVariables()) { - callForTypeNoVoid(pv->getValueType(), [&pv, this](auto arg) { - // Applies only to writeable variables. @todo FIXME It should also NOT apply for application-to-controlsystem - // variables with a return channel, despite being writeable here! - if(!pv->isWriteable()) return; - // Safety check against incorrect usage - if(pv->getVersionNumber() != VersionNumber(nullptr)) { - throw ChimeraTK::logic_error("The variable '" + pv->getName() + - "' has been written before TestFacility::runApplication() was called. Instead use " - "TestFacility::setScalarDefault() resp. setArrayDefault() to set initial values."); - } - typedef decltype(arg) T; - auto pv_casted = boost::dynamic_pointer_cast<NDRegisterAccessor<T>>(pv); - auto table = boost::fusion::at_key<T>(defaults.table); - // If default value has been stored, copy the default value to the PV. - if(table.find(pv->getName()) != table.end()) { - /// Since pv_casted is the undecorated PV (lacking the TestableModeAccessorDecorator), we need to copy the - /// value also to the decorator. We still have to write through the undecorated PV, otherwise the tests are - /// stalled. @todo It is not understood why this happens! - /// Decorated accessors are stored in different maps for scalars are arrays... - if(pv_casted->getNumberOfSamples() == 1) { // scalar - auto accessor = this->getScalar<T>(pv->getName()); - accessor = table.at(pv->getName())[0]; - } - else { // array - auto accessor = this->getArray<T>(pv->getName()); - accessor = table.at(pv->getName()); - } - // copy value also to undecorated PV - pv_casted->accessChannel(0) = table.at(pv->getName()); - } - // Write the initial value. This must be done even if no default value has been stored, since it is expected - // by the application. - pv_casted->write(); - }); - } - // start the application - Application::getInstance().run(); - // set thread name - Application::registerThread("TestThread"); - // make sure all initial values have been propagated when in testable mode - if(Application::getInstance().isTestableModeEnabled()) { - // call stepApplication() only in testable mode and only if the queues are not empty - if(Application::getInstance().testableMode_counter != 0 || - Application::getInstance().testableMode_deviceInitialisationCounter != 0) { - stepApplication(); - } - } - - // receive all initial values for the control system variables - if(Application::getInstance().isTestableModeEnabled()) { - for(auto& pv : pvManager->getAllProcessVariables()) { - if(!pv->isReadable()) continue; - callForTypeNoVoid(pv->getValueType(), [&](auto t) { - typedef decltype(t) UserType; - this->getArray<UserType>(pv->getName()).readNonBlocking(); - }); - } - } - } + void runApplication() const; /** * Check whether data has been sent to the application so stepApplication() can be called. */ - bool canStepApplication() const { return Application::getInstance().canStepApplication(); } + bool canStepApplication() const; /** Perform a "step" of the application. This runs the application until all * input provided to it has been processed and all application modules wait @@ -121,185 +57,49 @@ namespace ChimeraTK { * 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(bool waitForDeviceInitialisation = true) const { - Application::getInstance().stepApplication(waitForDeviceInitialisation); - } + void stepApplication(bool waitForDeviceInitialisation = true) const; /** Obtain a void process variable from the application, which is published * to the control system. */ - ChimeraTK::VoidRegisterAccessor getVoid(const ChimeraTK::RegisterPath& name) const { - // check for existing accessor in cache - if(boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table).count(name) > 0) { - return boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table)[name]; - } - - // obtain accessor from ControlSystemPVManager - auto pv = pvManager->getProcessArray<ChimeraTK::Void>(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<ChimeraTK::Void>>(pv, false, true, varId, varId); - Application::getInstance().testableMode_names[varId] = "ControlSystem:" + name; - boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table)[name] = deco; - } - else { - boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table)[name] = pv; - } - - // return the accessor as stored in the cache - return boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table)[name]; - } + ChimeraTK::VoidRegisterAccessor getVoid(const ChimeraTK::RegisterPath& name) const; /** 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>(accessorMap.table).count(name) > 0) { - return boost::fusion::at_key<T>(accessorMap.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>(accessorMap.table)[name] = deco; - } - else { - boost::fusion::at_key<T>(accessorMap.table)[name] = pv; - } - - // return the accessor as stored in the cache - return boost::fusion::at_key<T>(accessorMap.table)[name]; - } + ChimeraTK::ScalarRegisterAccessor<T> getScalar(const ChimeraTK::RegisterPath& name) const; /** 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>(accessorMap.table).count(name) > 0) { - return boost::fusion::at_key<T>(accessorMap.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>(accessorMap.table)[name] = deco; - } - else { - boost::fusion::at_key<T>(accessorMap.table)[name] = pv; - } - - // return the accessor as stored in the cache - return boost::fusion::at_key<T>(accessorMap.table)[name]; - } + ChimeraTK::OneDRegisterAccessor<T> getArray(const ChimeraTK::RegisterPath& name) const; /** 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<typename detail::BoolTypeHelper<TYPE>::type>(name); - acc = value; - acc.write(); - } + void writeScalar(const std::string& name, const TYPE value); /** 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<typename detail::BoolTypeHelper<TYPE>::type>(name); - if constexpr(!std::is_same<TYPE, bool>::value) { - acc = value; - } - else { - assert(value.size() == acc.getNElements()); - std::transform(value.begin(), value.end(), acc.begin(), [](const bool& v) -> ChimeraTK::Boolean { return v; }); - } - acc.write(); - } + void writeArray(const std::string& name, const std::vector<TYPE>& value); /** 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<typename detail::BoolTypeHelper<TYPE>::type>(name); - acc.readLatest(); - return acc; - } + TYPE readScalar(const std::string& name); /** 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<typename detail::BoolTypeHelper<TYPE>::type>(name); - acc.readLatest(); - return acc; - } + std::vector<TYPE> readArray(const std::string& name); /** Set default value for scalar process variable. */ template<typename T> - void setScalarDefault(const ChimeraTK::RegisterPath& name, const T& value) { - if(Application::getInstance().testFacilityRunApplicationCalled) { - throw ChimeraTK::logic_error("TestFacility::setScalarDefault() called after runApplication()."); - } - std::vector<T> vv; - vv.push_back(value); - setArrayDefault(name, vv); - } + void setScalarDefault(const ChimeraTK::RegisterPath& name, const T& value); /** Set default value for array process variable. */ template<typename T> - void setArrayDefault(const ChimeraTK::RegisterPath& name, const std::vector<T>& value) { - if(Application::getInstance().testFacilityRunApplicationCalled) { - throw ChimeraTK::logic_error("TestFacility::setArrayDefault() called after runApplication()."); - } - // check if PV exists - auto pv = pvManager->getProcessArray<typename detail::BoolTypeHelper<T>::type>(name); - if(pv == nullptr) { - throw ChimeraTK::logic_error("Process variable '" + name + "' does not exist."); - } - // store default value in map - auto& tv = boost::fusion::at_key<typename detail::BoolTypeHelper<T>::type>(defaults.table)[name]; - if constexpr(!std::is_same<T, bool>::value) { - tv = value; - } - else { - tv.resize(value.size()); - std::transform(value.begin(), value.end(), tv.begin(), [](const bool& v) -> ChimeraTK::Boolean { return v; }); - } - } + void setArrayDefault(const ChimeraTK::RegisterPath& name, const std::vector<T>& value); protected: boost::shared_ptr<ControlSystemPVManager> pvManager; @@ -318,4 +118,152 @@ namespace ChimeraTK { ChimeraTK::TemplateUserTypeMap<Defaults> defaults; }; + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename T> + ChimeraTK::ScalarRegisterAccessor<T> TestFacility::getScalar(const ChimeraTK::RegisterPath& name) const { + // check for existing accessor in cache + if(boost::fusion::at_key<T>(accessorMap.table).count(name) > 0) { + return boost::fusion::at_key<T>(accessorMap.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>(accessorMap.table)[name] = deco; + } + else { + boost::fusion::at_key<T>(accessorMap.table)[name] = pv; + } + + // return the accessor as stored in the cache + return boost::fusion::at_key<T>(accessorMap.table)[name]; + } + + /********************************************************************************************************************/ + + template<typename T> + ChimeraTK::OneDRegisterAccessor<T> TestFacility::getArray(const ChimeraTK::RegisterPath& name) const { + // check for existing accessor in cache + if(boost::fusion::at_key<T>(accessorMap.table).count(name) > 0) { + return boost::fusion::at_key<T>(accessorMap.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>(accessorMap.table)[name] = deco; + } + else { + boost::fusion::at_key<T>(accessorMap.table)[name] = pv; + } + + // return the accessor as stored in the cache + return boost::fusion::at_key<T>(accessorMap.table)[name]; + } + + /********************************************************************************************************************/ + + template<typename TYPE> + void TestFacility::writeScalar(const std::string& name, const TYPE value) { + auto acc = getScalar<typename detail::BoolTypeHelper<TYPE>::type>(name); + acc = value; + acc.write(); + } + + /********************************************************************************************************************/ + + template<typename TYPE> + void TestFacility::writeArray(const std::string& name, const std::vector<TYPE>& value) { + auto acc = getArray<typename detail::BoolTypeHelper<TYPE>::type>(name); + if constexpr(!std::is_same<TYPE, bool>::value) { + acc = value; + } + else { + assert(value.size() == acc.getNElements()); + std::transform(value.begin(), value.end(), acc.begin(), [](const bool& v) -> ChimeraTK::Boolean { return v; }); + } + acc.write(); + } + + /********************************************************************************************************************/ + + template<typename TYPE> + TYPE TestFacility::readScalar(const std::string& name) { + auto acc = getScalar<typename detail::BoolTypeHelper<TYPE>::type>(name); + acc.readLatest(); + return acc; + } + + /********************************************************************************************************************/ + + template<typename TYPE> + std::vector<TYPE> TestFacility::readArray(const std::string& name) { + auto acc = getArray<typename detail::BoolTypeHelper<TYPE>::type>(name); + acc.readLatest(); + return acc; + } + + /********************************************************************************************************************/ + + template<typename T> + void TestFacility::setScalarDefault(const ChimeraTK::RegisterPath& name, const T& value) { + if(Application::getInstance().testFacilityRunApplicationCalled) { + throw ChimeraTK::logic_error("TestFacility::setScalarDefault() called after runApplication()."); + } + std::vector<T> vv; + vv.push_back(value); + setArrayDefault(name, vv); + } + + /********************************************************************************************************************/ + + template<typename T> + void TestFacility::setArrayDefault(const ChimeraTK::RegisterPath& name, const std::vector<T>& value) { + if(Application::getInstance().testFacilityRunApplicationCalled) { + throw ChimeraTK::logic_error("TestFacility::setArrayDefault() called after runApplication()."); + } + // check if PV exists + auto pv = pvManager->getProcessArray<typename detail::BoolTypeHelper<T>::type>(name); + if(pv == nullptr) { + throw ChimeraTK::logic_error("Process variable '" + name + "' does not exist."); + } + // store default value in map + auto& tv = boost::fusion::at_key<typename detail::BoolTypeHelper<T>::type>(defaults.table)[name]; + if constexpr(!std::is_same<T, bool>::value) { + tv = value; + } + else { + tv.resize(value.size()); + std::transform(value.begin(), value.end(), tv.begin(), [](const bool& v) -> ChimeraTK::Boolean { return v; }); + } + } + + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/TestableModeAccessorDecorator.h b/include/TestableModeAccessorDecorator.h index 807fc81a..0d61ce4a 100644 --- a/include/TestableModeAccessorDecorator.h +++ b/include/TestableModeAccessorDecorator.h @@ -9,151 +9,197 @@ 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) - : 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; - assert(accessor->getAccessModeFlags().has(AccessMode::wait_for_new_data)); - } + bool handleWrite, size_t variableIdRead, size_t variableIdWrite); - // 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; + + bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber = {}) override; + + void doReadTransferSynchronously() override { _target->readTransfer(); } + + /** Release the testableModeLock */ + void releaseLock(); + + void doPreRead(TransferType type) override; + + /** Obtain the testableModeLock if not owned yet, and decrement the counter. + */ + void obtainLockAndDecrementCounter(bool hasNewData); + + /** Obtain the testableModeLock if not owned yet, decrement the counter, and + * release the lock again. */ + void decrementCounter(); + + void doPostRead(TransferType type, bool hasNewData) override; + + protected: + using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D; + using ChimeraTK::NDRegisterAccessorDecorator<UserType>::_target; + + bool _handleRead, _handleWrite; + size_t _variableIdRead, _variableIdWrite; + }; + + /********************************************************************************************************************/ + + template<typename UserType> + TestableModeAccessorDecorator<UserType>::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; + assert(accessor->getAccessModeFlags().has(AccessMode::wait_for_new_data)); + } + + // 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->writeTransfer(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->writeTransfer(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; - } + template<typename UserType> + bool TestableModeAccessorDecorator<UserType>::doWriteTransfer(ChimeraTK::VersionNumber versionNumber) { + if(!_handleWrite) return _target->writeTransfer(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->writeTransfer(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; - } + } + 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; } + return dataLost; + } - bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber = {}) override { - if(!_handleWrite) return _target->writeTransferDestructively(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->writeTransferDestructively(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; - } + template<typename UserType> + bool TestableModeAccessorDecorator<UserType>::doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) { + if(!_handleWrite) return _target->writeTransferDestructively(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->writeTransferDestructively(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; - } + } + 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; } + return dataLost; + } - void doReadTransferSynchronously() override { _target->readTransfer(); } + /********************************************************************************************************************/ - /** Release the testableModeLock */ - void releaseLock() { - if(Application::testableModeTestLock()) Application::testableModeUnlock("doReadTransfer " + this->getName()); - } + template<typename UserType> + void TestableModeAccessorDecorator<UserType>::releaseLock() { + if(Application::testableModeTestLock()) Application::testableModeUnlock("doReadTransfer " + this->getName()); + } - void doPreRead(TransferType type) override { - _target->preRead(type); + /********************************************************************************************************************/ - // Blocking reads have to release the lock so the data transport can happen - if(_handleRead && type == TransferType::read && - TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) { - releaseLock(); - } + template<typename UserType> + void TestableModeAccessorDecorator<UserType>::doPreRead(TransferType type) { + _target->preRead(type); + + // Blocking reads have to release the lock so the data transport can happen + if(_handleRead && type == TransferType::read && + TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) { + releaseLock(); } + } - /** Obtain the testableModeLock if not owned yet, and decrement the counter. - */ - void obtainLockAndDecrementCounter(bool hasNewData) { - if(!Application::testableModeTestLock()) Application::testableModeLock("doReadTransfer " + this->getName()); - if(!hasNewData) return; - 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; - } + /********************************************************************************************************************/ + + template<typename UserType> + void TestableModeAccessorDecorator<UserType>::obtainLockAndDecrementCounter(bool hasNewData) { + if(!Application::testableModeTestLock()) Application::testableModeLock("doReadTransfer " + this->getName()); + if(!hasNewData) return; + 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[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; } } + } - /** Obtain the testableModeLock if not owned yet, decrement the counter, and - * release the lock again. */ - void decrementCounter() { - obtainLockAndDecrementCounter(true); - releaseLock(); - } + /********************************************************************************************************************/ - void doPostRead(TransferType type, bool hasNewData) override { - if(_handleRead) obtainLockAndDecrementCounter(hasNewData); - ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, hasNewData); - } + template<typename UserType> + void TestableModeAccessorDecorator<UserType>::decrementCounter() { + obtainLockAndDecrementCounter(true); + releaseLock(); + } - protected: - using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D; - using ChimeraTK::NDRegisterAccessorDecorator<UserType>::_target; + /********************************************************************************************************************/ - bool _handleRead, _handleWrite; - size_t _variableIdRead, _variableIdWrite; - }; + template<typename UserType> + void TestableModeAccessorDecorator<UserType>::doPostRead(TransferType type, bool hasNewData) { + if(_handleRead) obtainLockAndDecrementCounter(hasNewData); + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, hasNewData); + } + + /********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/include/ThreadedFanOut.h b/include/ThreadedFanOut.h index bfe38674..201696b8 100644 --- a/include/ThreadedFanOut.h +++ b/include/ThreadedFanOut.h @@ -11,6 +11,8 @@ 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. */ @@ -18,70 +20,19 @@ namespace ChimeraTK { class ThreadedFanOut : public FanOut<UserType>, public InternalModule { public: ThreadedFanOut(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl, VariableNetwork& network, - ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) - : FanOut<UserType>(feedingImpl), _network(network) { - assert(feedingImpl->getAccessModeFlags().has(AccessMode::wait_for_new_data)); - for(auto el : consumerImplementationPairs) { - FanOut<UserType>::addSlave(el.first, el.second); - } - } + ConsumerImplementationPairs<UserType> const& consumerImplementationPairs); - ~ThreadedFanOut() { deactivate(); } + ~ThreadedFanOut(); - void activate() override { - if(this->_disabled) return; - assert(!_thread.joinable()); - _thread = boost::thread([this] { this->run(); }); - } + void activate() override; - void deactivate() override { - if(_thread.joinable()) { - _thread.interrupt(); - FanOut<UserType>::interrupt(); - _thread.join(); - } - assert(!_thread.joinable()); - } + void deactivate() override; /** 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"); - testableModeReached = true; - - ChimeraTK::VersionNumber version{nullptr}; - version = readInitialValues(); - while(true) { - // send out copies to slaves - Profiler::startMeasurement(); - boost::this_thread::interruption_point(); - auto validity = FanOut<UserType>::impl->dataValidity(); - 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); - } - slave->setDataValidity(validity); - bool dataLoss = slave->writeDestructively(version); - if(dataLoss) Application::incrementDataLossCounter(slave->getName()); - } - // receive data - boost::this_thread::interruption_point(); - Profiler::stopMeasurement(); - FanOut<UserType>::impl->read(); - version = FanOut<UserType>::impl->getVersionNumber(); - } - } + virtual void run(); - VersionNumber readInitialValues() { - Application::testableModeUnlock("readInitialValues"); - FanOut<UserType>::impl->read(); - if(!Application::testableModeTestLock()) { - Application::testableModeLock("readInitialValues"); - } - return FanOut<UserType>::impl->getVersionNumber(); - } + VersionNumber readInitialValues(); protected: /** Thread handling the synchronisation, if needed */ @@ -98,72 +49,14 @@ namespace ChimeraTK { class ThreadedFanOutWithReturn : public ThreadedFanOut<UserType> { public: ThreadedFanOutWithReturn(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl, - VariableNetwork& network, ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) - : ThreadedFanOut<UserType>(feedingImpl, network, consumerImplementationPairs) { - for(auto el : consumerImplementationPairs) { - // TODO Calling a virtual in the constructor seems odd, - // but works because we want this version's implementation - addSlave(el.first, el.second); - } - } + VariableNetwork& network, ConsumerImplementationPairs<UserType> const& consumerImplementationPairs); - void setReturnChannelSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> returnChannelSlave) { - _returnChannelSlave = returnChannelSlave; - } + void setReturnChannelSlave(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> returnChannelSlave); void addSlave( - boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, VariableNetworkNode& consumer) override { - // TODO Adding slaves is currently by done by the ThreadedFanOut base class. - // Refactor constructors and addSlaves for all FanOuts? - // FanOut<UserType>::addSlave(slave, consumer); - if(consumer.getDirection().withReturn) { - assert(_returnChannelSlave == nullptr); - _returnChannelSlave = slave; - } - } + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, VariableNetworkNode& consumer) override; - void run() override { - Application::registerThread("ThFO" + FanOut<UserType>::impl->getName()); - Application::testableModeLock("start"); - testableModeReached = true; - - TransferElementID var; - ChimeraTK::VersionNumber version{nullptr}; - - version = readInitialValues(); - - ReadAnyGroup group({FanOut<UserType>::impl, _returnChannelSlave}); - while(true) { - // send out copies to slaves - 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->writeDestructively(version); - if(dataLoss) Application::incrementDataLossCounter(slave->getName()); - } - // receive data - boost::this_thread::interruption_point(); - Profiler::stopMeasurement(); - 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)); - if(version < _returnChannelSlave->getVersionNumber()) { - version = _returnChannelSlave->getVersionNumber(); - } - FanOut<UserType>::impl->write(version); - } - else { - version = FanOut<UserType>::impl->getVersionNumber(); - } - } - } + void run() override; protected: /** Thread handling the synchronisation, if needed */ @@ -176,4 +69,174 @@ namespace ChimeraTK { using EntityOwner::testableModeReached; }; + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ThreadedFanOut<UserType>::ThreadedFanOut(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl, + VariableNetwork& network, ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) + : FanOut<UserType>(feedingImpl), _network(network) { + assert(feedingImpl->getAccessModeFlags().has(AccessMode::wait_for_new_data)); + for(auto el : consumerImplementationPairs) { + FanOut<UserType>::addSlave(el.first, el.second); + } + } + + /********************************************************************************************************************/ + + template<typename UserType> + ThreadedFanOut<UserType>::~ThreadedFanOut() { + deactivate(); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void ThreadedFanOut<UserType>::activate() { + if(this->_disabled) return; + assert(!_thread.joinable()); + _thread = boost::thread([this] { this->run(); }); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void ThreadedFanOut<UserType>::deactivate() { + if(_thread.joinable()) { + _thread.interrupt(); + FanOut<UserType>::interrupt(); + _thread.join(); + } + assert(!_thread.joinable()); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void ThreadedFanOut<UserType>::run() { + Application::registerThread("ThFO" + FanOut<UserType>::impl->getName()); + Application::testableModeLock("start"); + testableModeReached = true; + + ChimeraTK::VersionNumber version{nullptr}; + version = readInitialValues(); + while(true) { + // send out copies to slaves + Profiler::startMeasurement(); + boost::this_thread::interruption_point(); + auto validity = FanOut<UserType>::impl->dataValidity(); + 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); + } + slave->setDataValidity(validity); + bool dataLoss = slave->writeDestructively(version); + if(dataLoss) Application::incrementDataLossCounter(slave->getName()); + } + // receive data + boost::this_thread::interruption_point(); + Profiler::stopMeasurement(); + FanOut<UserType>::impl->read(); + version = FanOut<UserType>::impl->getVersionNumber(); + } + } + + /********************************************************************************************************************/ + + template<typename UserType> + VersionNumber ThreadedFanOut<UserType>::readInitialValues() { + Application::testableModeUnlock("readInitialValues"); + FanOut<UserType>::impl->read(); + if(!Application::testableModeTestLock()) { + Application::testableModeLock("readInitialValues"); + } + return FanOut<UserType>::impl->getVersionNumber(); + } + + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + ThreadedFanOutWithReturn<UserType>::ThreadedFanOutWithReturn( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl, VariableNetwork& network, + ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) + : ThreadedFanOut<UserType>(feedingImpl, network, consumerImplementationPairs) { + for(auto el : consumerImplementationPairs) { + // TODO Calling a virtual in the constructor seems odd, + // but works because we want this version's implementation + addSlave(el.first, el.second); + } + } + + /********************************************************************************************************************/ + + template<typename UserType> + void ThreadedFanOutWithReturn<UserType>::setReturnChannelSlave( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> returnChannelSlave) { + _returnChannelSlave = returnChannelSlave; + } + + /********************************************************************************************************************/ + + template<typename UserType> + void ThreadedFanOutWithReturn<UserType>::addSlave( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> slave, VariableNetworkNode& consumer) { + // TODO Adding slaves is currently by done by the ThreadedFanOut base class. + // Refactor constructors and addSlaves for all FanOuts? + // FanOut<UserType>::addSlave(slave, consumer); + if(consumer.getDirection().withReturn) { + assert(_returnChannelSlave == nullptr); + _returnChannelSlave = slave; + } + } + + /********************************************************************************************************************/ + + template<typename UserType> + void ThreadedFanOutWithReturn<UserType>::run() { + Application::registerThread("ThFO" + FanOut<UserType>::impl->getName()); + Application::testableModeLock("start"); + testableModeReached = true; + + TransferElementID var; + ChimeraTK::VersionNumber version{nullptr}; + + version = readInitialValues(); + + ReadAnyGroup group({FanOut<UserType>::impl, _returnChannelSlave}); + while(true) { + // send out copies to slaves + 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->writeDestructively(version); + if(dataLoss) Application::incrementDataLossCounter(slave->getName()); + } + // receive data + boost::this_thread::interruption_point(); + Profiler::stopMeasurement(); + 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)); + if(version < _returnChannelSlave->getVersionNumber()) { + version = _returnChannelSlave->getVersionNumber(); + } + FanOut<UserType>::impl->write(version); + } + else { + version = FanOut<UserType>::impl->getVersionNumber(); + } + } + } + + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/TriggerFanOut.h b/include/TriggerFanOut.h index 57201d2e..c5f68c30 100644 --- a/include/TriggerFanOut.h +++ b/include/TriggerFanOut.h @@ -16,31 +16,20 @@ constexpr useconds_t DeviceOpenTimeout = 500; 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, DeviceModule& deviceModule, - VariableNetwork& network) - : externalTrigger(externalTriggerImpl), _deviceModule(deviceModule), _network(network) {} - - ~TriggerFanOut() { deactivate(); } - - void activate() override { - assert(!_thread.joinable()); - _thread = boost::thread([this] { this->run(); }); - } - - void deactivate() override { - if(_thread.joinable()) { - _thread.interrupt(); - if(externalTrigger->getAccessModeFlags().has(AccessMode::wait_for_new_data)) { - externalTrigger->interrupt(); - } - _thread.join(); - } - assert(!_thread.joinable()); - } + VariableNetwork& network); + + ~TriggerFanOut(); + + void activate() override; + + void deactivate() override; /** Add a new network the TriggerFanOut. The network is defined by its feeding * node. This function will return the corresponding FeedingFanOut, to which @@ -48,89 +37,13 @@ namespace ChimeraTK { template<typename UserType> boost::shared_ptr<FeedingFanOut<UserType>> addNetwork( boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingNode, - ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) { - 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 - consumerImplementationPairs); - boost::fusion::at_key<UserType>(fanOutMap.table)[feedingNode] = feedingFanOut; - return feedingFanOut; - } + ConsumerImplementationPairs<UserType> const& consumerImplementationPairs); /** Synchronise feeder and the consumers. This function is executed in the * separate thread. */ - void run() { - Application::registerThread("TrFO" + externalTrigger->getName()); - Application::testableModeLock("start"); - testableModeReached = true; - - ChimeraTK::VersionNumber version = Application::getInstance().getStartVersion(); - - // Wait for the initial value of the trigger. There always will be one, and if we don't read it here we would - // trigger the loop twice. - externalTrigger->read(); - version = externalTrigger->getVersionNumber(); - - // Wait until the device has been initialised for the first time. This means it - // has been opened, and the check in TransferGroup::read() will not throw a logic_error - // We don't have to store the lock. Just need it as a synchronisation point. - // But we have to increase the testable mode counter because we don't want to fall out of testable mode at this - // point already. - if(Application::getInstance().testableMode) ++Application::getInstance().testableMode_deviceInitialisationCounter; - Application::testableModeUnlock("WaitInitialValueLock"); - (void)_deviceModule.waitForInitialValues(); - Application::testableModeLock("Enter while loop"); - if(Application::getInstance().testableMode) --Application::getInstance().testableMode_deviceInitialisationCounter; - - while(true) { - transferGroup.read(); - // send the version number to the consumers - boost::fusion::for_each(fanOutMap.table, SendDataToConsumers(version, externalTrigger->dataValidity())); - - // wait for external trigger - boost::this_thread::interruption_point(); - Profiler::stopMeasurement(); - externalTrigger->read(); - Profiler::startMeasurement(); - boost::this_thread::interruption_point(); - version = externalTrigger->getVersionNumber(); - } - } + void run(); protected: - /** Functor class to send data to the consumers, suitable for - * boost::fusion::for_each(). */ - struct SendDataToConsumers { - SendDataToConsumers(VersionNumber version, DataValidity triggerValidity) - : _version(version), _triggerValidity(triggerValidity) {} - - 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->setDataValidity((_triggerValidity == DataValidity::ok && feeder->dataValidity() == DataValidity::ok) ? - DataValidity::ok : - DataValidity::faulty); - fanOut->accessChannel(0).swap(feeder->accessChannel(0)); - // don't use write destructively. In case of an exception we still need the data for the next read (see - // Exception Handling spec B.2.2.6) - bool dataLoss = fanOut->write(_version); - if(dataLoss) Application::incrementDataLossCounter(fanOut->getName()); - // swap the data back to the feeder so we have a valid copy there. - fanOut->accessChannel(0).swap(feeder->accessChannel(0)); - } - } - - VersionNumber _version; - DataValidity _triggerValidity; - }; /** TransferElement acting as our trigger */ boost::shared_ptr<ChimeraTK::TransferElement> externalTrigger; @@ -155,4 +68,23 @@ namespace ChimeraTK { VariableNetwork& _network; }; + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + template<typename UserType> + boost::shared_ptr<FeedingFanOut<UserType>> TriggerFanOut::addNetwork( + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingNode, + ConsumerImplementationPairs<UserType> const& consumerImplementationPairs) { + 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 + consumerImplementationPairs); + boost::fusion::at_key<UserType>(fanOutMap.table)[feedingNode] = feedingFanOut; + return feedingFanOut; + } + + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/VariableGroup.h b/include/VariableGroup.h index 7a47c251..c842cc4e 100644 --- a/include/VariableGroup.h +++ b/include/VariableGroup.h @@ -10,9 +10,13 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + class ApplicationModule; struct ConfigReader; + /********************************************************************************************************************/ + class VariableGroup : public ModuleImpl { public: /** Constructor: Create ModuleGroup by the given name with the given description and register it with its @@ -36,21 +40,20 @@ namespace ChimeraTK { * 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() {} + VariableGroup() = default; /** Destructor */ - virtual ~VariableGroup(){}; + virtual ~VariableGroup() = default; /** Move constructor */ VariableGroup(VariableGroup&& other) { operator=(std::move(other)); } /** Move assignment */ - VariableGroup& operator=(VariableGroup&& other) { - ModuleImpl::operator=(std::move(other)); - return *this; - } + VariableGroup& operator=(VariableGroup&& other); ModuleType getModuleType() const override { return ModuleType::VariableGroup; } }; + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/VariableNetwork.h b/include/VariableNetwork.h index e1580772..404a4d90 100644 --- a/include/VariableNetwork.h +++ b/include/VariableNetwork.h @@ -83,11 +83,7 @@ namespace ChimeraTK { 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; bool operator!=(const VariableNetwork& other) const { return !operator==(other); } /** Return the trigger type. This function will also do some checking if the diff --git a/include/VariableNetworkDumpingVisitor.h b/include/VariableNetworkDumpingVisitor.h index 4091a79f..1440e3df 100644 --- a/include/VariableNetworkDumpingVisitor.h +++ b/include/VariableNetworkDumpingVisitor.h @@ -9,9 +9,13 @@ namespace ChimeraTK { + /*********************************************************************************************************************/ + // Forward declarations class VariableNetwork; + /*********************************************************************************************************************/ + /** * @brief The VariableNetworkDumpingVisitor class * @@ -28,4 +32,6 @@ namespace ChimeraTK { std::string _prefix; }; + /*********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/include/VariableNetworkGraphDumpingVisitor.h b/include/VariableNetworkGraphDumpingVisitor.h index 5f55a146..0c1e32aa 100644 --- a/include/VariableNetworkGraphDumpingVisitor.h +++ b/include/VariableNetworkGraphDumpingVisitor.h @@ -9,11 +9,14 @@ namespace ChimeraTK { - // Forward Declarations + /*********************************************************************************************************************/ + // Forward Declarations class Application; class VariableNetwork; + /*********************************************************************************************************************/ + /** * @brief The VariableNetworkGraphDumpingVisitor class * @@ -42,4 +45,6 @@ namespace ChimeraTK { void popPrefix() { _prefix.pop_back(); } }; + /*********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/include/VariableNetworkModuleGraphDumpingVisitor.h b/include/VariableNetworkModuleGraphDumpingVisitor.h index d304cf0a..4fdbdbb2 100644 --- a/include/VariableNetworkModuleGraphDumpingVisitor.h +++ b/include/VariableNetworkModuleGraphDumpingVisitor.h @@ -9,12 +9,15 @@ namespace ChimeraTK { - // Forward Declarations + /*********************************************************************************************************************/ + // Forward Declarations class Application; class VariableNetwork; class Module; + /*********************************************************************************************************************/ + /** * @brief The VariableNetworkModuleGraphDumpingVisitor class * @@ -38,4 +41,6 @@ namespace ChimeraTK { std::list<std::string> _deviceList; }; + /*********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/include/VariableNetworkNode.h b/include/VariableNetworkNode.h index 1eb5e17d..1808670d 100644 --- a/include/VariableNetworkNode.h +++ b/include/VariableNetworkNode.h @@ -18,14 +18,20 @@ 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: @@ -186,7 +192,7 @@ namespace ChimeraTK { boost::shared_ptr<VariableNetworkNode_data> pdata; }; - /*********************************************************************************************************************/ + /********************************************************************************************************************/ /** A helper class to create accessors with the right length and value. We use this * to create one constant accessor for each consumer so e don't have to use a fanout: The consumers might be mixed @@ -202,7 +208,7 @@ namespace ChimeraTK { virtual ~ConstantAccessorCreator() {} }; - /*********************************************************************************************************************/ + /********************************************************************************************************************/ /** 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 @@ -275,7 +281,7 @@ namespace ChimeraTK { size_t circularNetworkHash{0}; }; - /*********************************************************************************************************************/ + /********************************************************************************************************************/ // Templated implementation of the ConstantAccessorCreator template<typename UserType> @@ -290,10 +296,9 @@ namespace ChimeraTK { } }; - /*********************************************************************************************************************/ - /*** Implementations - * *************************************************************************************************/ - /*********************************************************************************************************************/ + /********************************************************************************************************************/ + /*** Implementations ************************************************************************************************/ + /********************************************************************************************************************/ template<typename UserType> VariableNetworkNode VariableNetworkNode::makeConstant(bool makeFeeder, UserType value, size_t length) { @@ -315,7 +320,7 @@ namespace ChimeraTK { return node; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ template<typename UserType> ChimeraTK::NDRegisterAccessorAbstractor<UserType>& VariableNetworkNode::getAppAccessor() const { @@ -326,7 +331,7 @@ namespace ChimeraTK { return *accessor; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ template<typename UserType> boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> VariableNetworkNode::createConstAccessor( @@ -335,7 +340,7 @@ namespace ChimeraTK { pdata->constNodeCreator->create(accessModeFlags)); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ template<typename UserType> void VariableNetworkNode::setAppAccessorImplementation(boost::shared_ptr<NDRegisterAccessor<UserType>> impl) const { @@ -345,4 +350,6 @@ namespace ChimeraTK { assert(flagProvider); } + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/include/VariableNetworkNodeDumpingVisitor.h b/include/VariableNetworkNodeDumpingVisitor.h index 1393f288..8a753037 100644 --- a/include/VariableNetworkNodeDumpingVisitor.h +++ b/include/VariableNetworkNodeDumpingVisitor.h @@ -10,9 +10,13 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + // Forward declarations class VariableNetworkNode; + /********************************************************************************************************************/ + /** * @brief A helper class to replace the output stream temporarily * @@ -41,6 +45,8 @@ namespace ChimeraTK { std::list<std::reference_wrapper<std::ostream>> _streamStack; }; + /********************************************************************************************************************/ + /** * @brief The VariableNetworkNodeDumpingVisitor class * @@ -74,4 +80,6 @@ namespace ChimeraTK { std::string _separator; }; + /********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/include/VirtualModule.h b/include/VirtualModule.h index 9f6fde89..922983f7 100644 --- a/include/VirtualModule.h +++ b/include/VirtualModule.h @@ -16,12 +16,7 @@ namespace ChimeraTK { 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 + "'."); - } - } + VirtualModule(const std::string& name, const std::string& description, ModuleType moduleType); /** Copy constructor */ VirtualModule(const VirtualModule& other); diff --git a/include/Visitor.h b/include/Visitor.h index 8ae951eb..ea5e3a6a 100644 --- a/include/Visitor.h +++ b/include/Visitor.h @@ -4,19 +4,28 @@ namespace ChimeraTK { - /* Losely based on + /********************************************************************************************************************/ + + /* + * Losely based on * https://stackoverflow.com/questions/11796121/implementing-the-visitor-pattern-using-c-templates#11802080 */ + /********************************************************************************************************************/ + template<typename... Types> class Visitor; + /********************************************************************************************************************/ + template<typename T> class Visitor<T> { public: virtual void dispatch(const T& t) = 0; }; + /********************************************************************************************************************/ + template<typename T, typename... Types> class Visitor<T, Types...> : public Visitor<T>, public Visitor<Types...> { public: @@ -24,4 +33,6 @@ namespace ChimeraTK { using Visitor<T>::dispatch; }; + /********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/include/VoidAccessor.h b/include/VoidAccessor.h index f69c514a..304a0d84 100644 --- a/include/VoidAccessor.h +++ b/include/VoidAccessor.h @@ -28,38 +28,21 @@ namespace ChimeraTK { VoidAccessor(VoidAccessor&& other) noexcept { InversionOfControlAccessor<VoidAccessor>::replace(std::move(other)); } /** Move assignment. */ - VoidAccessor& operator=(VoidAccessor&& other) noexcept { - // Having a move-assignment operator is required to use the move-assignment - // operator of a module containing an accessor. - InversionOfControlAccessor<VoidAccessor>::replace(std::move(other)); - return *this; - } + VoidAccessor& operator=(VoidAccessor&& other) noexcept; bool write(ChimeraTK::VersionNumber versionNumber) = delete; bool writeDestructively(ChimeraTK::VersionNumber versionNumber) = delete; // void writeIfDifferent(UserType newValue, VersionNumber versionNumber) = delete; - bool write() { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - bool dataLoss = ChimeraTK::VoidRegisterAccessor::write(versionNumber); - if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); - return dataLoss; - } + bool write(); - bool writeDestructively() { - auto versionNumber = this->getOwner()->getCurrentVersionNumber(); - bool dataLoss = ChimeraTK::VoidRegisterAccessor::writeDestructively(versionNumber); - if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); - return dataLoss; - } + bool writeDestructively(); protected: friend class InversionOfControlAccessor<VoidAccessor>; VoidAccessor(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<VoidAccessor>( - owner, name, direction, unit, 1, mode, description, &typeid(ChimeraTK::Void), tags) {} + UpdateMode mode, const std::string& description, const std::unordered_set<std::string>& tags = {}); /** Default constructor creates a dysfunctional accessor (to be assigned with a real accessor later) */ VoidAccessor() = default; @@ -88,5 +71,43 @@ namespace ChimeraTK { }; /********************************************************************************************************************/ + /********************************************************************************************************************/ + /* Implementations below this point */ + /********************************************************************************************************************/ + /********************************************************************************************************************/ + + inline VoidAccessor& VoidAccessor::operator=(VoidAccessor&& other) noexcept { + // Having a move-assignment operator is required to use the move-assignment + // operator of a module containing an accessor. + InversionOfControlAccessor<VoidAccessor>::replace(std::move(other)); + return *this; + } + + /********************************************************************************************************************/ + + inline bool VoidAccessor::write() { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + bool dataLoss = ChimeraTK::VoidRegisterAccessor::write(versionNumber); + if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); + return dataLoss; + } + + /********************************************************************************************************************/ + + inline bool VoidAccessor::writeDestructively() { + auto versionNumber = this->getOwner()->getCurrentVersionNumber(); + bool dataLoss = ChimeraTK::VoidRegisterAccessor::writeDestructively(versionNumber); + if(dataLoss) Application::incrementDataLossCounter(this->node.getQualifiedName()); + return dataLoss; + } + + /********************************************************************************************************************/ + + inline VoidAccessor::VoidAccessor(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<VoidAccessor>( + owner, name, direction, unit, 1, mode, description, &typeid(ChimeraTK::Void), tags) {} + + /********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/include/XMLGeneratorVisitor.h b/include/XMLGeneratorVisitor.h index 93842533..e537fdce 100644 --- a/include/XMLGeneratorVisitor.h +++ b/include/XMLGeneratorVisitor.h @@ -14,10 +14,14 @@ namespace xmlpp { } // namespace xmlpp namespace ChimeraTK { + + /********************************************************************************************************************/ // Forward declarations class Application; class VariableNetworkNode; + /********************************************************************************************************************/ + /** * @brief The XMLGeneratorVisitor class * @@ -38,4 +42,6 @@ namespace ChimeraTK { xmlpp::Element* _rootElement; }; + /********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/src/Application.cc b/src/Application.cc index 6a084871..db59d600 100644 --- a/src/Application.cc +++ b/src/Application.cc @@ -61,6 +61,48 @@ void Application::defineConnections() { /*********************************************************************************************************************/ +void Application::enableTestableMode() { + threadName() = "TEST THREAD"; + testableMode = true; + testableModeLock("enableTestableMode"); +} + +/*********************************************************************************************************************/ + +bool Application::testableModeTestLock() { + if(!getInstance().testableMode) return false; + return getTestableModeLockObject().owns_lock(); +} + +/*********************************************************************************************************************/ + +void Application::registerThread(const std::string& name) { + Application::getInstance().setThreadName(name); + Profiler::registerThread(name); + pthread_setname_np(pthread_self(), name.substr(0, std::min<std::string::size_type>(name.length(), 15)).c_str()); +} + +/*********************************************************************************************************************/ + +void Application::incrementDataLossCounter(const std::string& name) { + if(getInstance().debugDataLoss) { + std::cout << "Data loss in variable " << name << std::endl; + } + getInstance().dataLossCounter++; +} + +/*********************************************************************************************************************/ + +size_t Application::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; +} + +/*********************************************************************************************************************/ + void Application::initialise() { if(initialiseCalled) { throw ChimeraTK::logic_error("Application::initialise() was already called before."); diff --git a/src/ApplicationModule.cc b/src/ApplicationModule.cc index 53a8021e..06a9a8d3 100644 --- a/src/ApplicationModule.cc +++ b/src/ApplicationModule.cc @@ -41,6 +41,15 @@ namespace ChimeraTK { /*********************************************************************************************************************/ + ApplicationModule& 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; + } + + /*********************************************************************************************************************/ + void ApplicationModule::run() { // start the module thread assert(!moduleThread.joinable()); @@ -77,6 +86,12 @@ namespace ChimeraTK { /*********************************************************************************************************************/ + void ApplicationModule::setCurrentVersionNumber(VersionNumber versionNumber) { + if(versionNumber > currentVersionNumber) currentVersionNumber = versionNumber; + } + + /*********************************************************************************************************************/ + void ApplicationModule::mainLoopWrapper() { Application::registerThread("AM_" + getName()); diff --git a/src/ControlSystemModule.cc b/src/ControlSystemModule.cc index c8b8af4f..9c4c9bc6 100644 --- a/src/ControlSystemModule.cc +++ b/src/ControlSystemModule.cc @@ -19,6 +19,15 @@ namespace ChimeraTK { /*********************************************************************************************************************/ + ControlSystemModule& ControlSystemModule::operator=(ControlSystemModule&& other) { + Module::operator=(std::move(other)); + variableNamePrefix = std::move(other.variableNamePrefix); + subModules = std::move(other.subModules); + return *this; + } + + /********************************************************************************************************************/ + VariableNetworkNode ControlSystemModule::operator()( const std::string& variableName, const std::type_info& valueType, size_t nElements) const { assert(variableName.find_first_of("/") == std::string::npos); diff --git a/src/DebugPrintAccessorDecorator.cc b/src/DebugPrintAccessorDecorator.cc new file mode 100644 index 00000000..47e18c69 --- /dev/null +++ b/src/DebugPrintAccessorDecorator.cc @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de> +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "DebugPrintAccessorDecorator.h" + +namespace ChimeraTK { + + /********************************************************************************************************************/ + + template<typename UserType> + DebugPrintAccessorDecorator<UserType>::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; + } + + /********************************************************************************************************************/ + + template<typename UserType> + bool DebugPrintAccessorDecorator<UserType>::doWriteTransfer(ChimeraTK::VersionNumber versionNumber) { + std::cout << "doWriteTransfer() called on '" << _fullyQualifiedName << "'." << std::flush; + auto ret = ChimeraTK::NDRegisterAccessorDecorator<UserType>::doWriteTransfer(versionNumber); + if(ret) { + std::cout << " -> DATA LOSS!"; + } + std::cout << std::endl; + return ret; + } + + /********************************************************************************************************************/ + + template<typename UserType> + bool DebugPrintAccessorDecorator<UserType>::doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) { + std::cout << "doWriteTransferDestructively() called on '" << _fullyQualifiedName << "'." << std::flush; + auto ret = ChimeraTK::NDRegisterAccessorDecorator<UserType>::doWriteTransferDestructively(versionNumber); + if(ret) { + std::cout << " -> DATA LOSS!"; + } + std::cout << std::endl; + return ret; + } + + /********************************************************************************************************************/ + + template<typename UserType> + void DebugPrintAccessorDecorator<UserType>::doReadTransferSynchronously() { + std::cout << "doReadTransferSynchronously() called on '" << _fullyQualifiedName << "'." << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doReadTransferSynchronously(); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void DebugPrintAccessorDecorator<UserType>::doPreRead(TransferType type) { + std::cout << "preRead() called on '" << _fullyQualifiedName << "'." << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreRead(type); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void DebugPrintAccessorDecorator<UserType>::doPostRead(TransferType type, bool hasNewData) { + std::cout << "postRead() called on '" << _fullyQualifiedName << "'." << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, hasNewData); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void DebugPrintAccessorDecorator<UserType>::doPreWrite(TransferType type, VersionNumber versionNumber) { + std::cout << "preWrite() called on '" << _fullyQualifiedName << "'." << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreWrite(type, versionNumber); + } + + /********************************************************************************************************************/ + + template<typename UserType> + void DebugPrintAccessorDecorator<UserType>::doPostWrite(TransferType type, VersionNumber versionNumber) { + std::cout << "postWrite() called on '" << _fullyQualifiedName << "'." << std::endl; + ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostWrite(type, versionNumber); + } + + /********************************************************************************************************************/ + + INSTANTIATE_TEMPLATE_FOR_CHIMERATK_USER_TYPES(DebugPrintAccessorDecorator); + + /********************************************************************************************************************/ + +} // namespace ChimeraTK diff --git a/src/DeviceModule.cc b/src/DeviceModule.cc index 1438bd36..38a9218b 100644 --- a/src/DeviceModule.cc +++ b/src/DeviceModule.cc @@ -76,6 +76,25 @@ namespace ChimeraTK { /*********************************************************************************************************************/ + DeviceModule& DeviceModule::operator=(DeviceModule&& other) { + assert(!moduleThread.joinable()); + assert(other.isHoldingInitialValueLatch); + if(owner) owner->unregisterDeviceModule(this); + Module::operator=(std::move(other)); + device = std::move(other.device); + deviceAliasOrURI = std::move(other.deviceAliasOrURI); + registerNamePrefix = std::move(other.registerNamePrefix); + deviceError = std::move(other.deviceError); + owner = other.owner; + proxies = std::move(other.proxies); + deviceHasError = other.deviceHasError; + for(auto& proxy : proxies) proxy.second._myowner = this; + owner->registerDeviceModule(this); + return *this; + } + + /*********************************************************************************************************************/ + VariableNetworkNode DeviceModule::operator()( const std::string& registerName, UpdateMode mode, const std::type_info& valueType, size_t nElements) const { return {registerName, deviceAliasOrURI, registerNamePrefix / registerName, mode, @@ -84,6 +103,19 @@ namespace ChimeraTK { /*********************************************************************************************************************/ + VariableNetworkNode DeviceModule::operator()( + const std::string& registerName, const std::type_info& valueType, size_t nElements, UpdateMode mode) const { + return operator()(registerName, mode, valueType, nElements); + } + + /*********************************************************************************************************************/ + + VariableNetworkNode DeviceModule::operator()(const std::string& variableName) const { + return operator()(variableName, UpdateMode::poll); + } + + /*********************************************************************************************************************/ + Module& DeviceModule::operator[](const std::string& moduleName) const { assert(moduleName.find_first_of("/") == std::string::npos); return getProxy(moduleName); @@ -492,6 +524,12 @@ namespace ChimeraTK { /*********************************************************************************************************************/ + void DeviceModule::setCurrentVersionNumber(VersionNumber versionNumber) { + if(versionNumber > currentVersionNumber) currentVersionNumber = versionNumber; + } + + /*********************************************************************************************************************/ + void DeviceModule::defineConnections() { // replace all slashes in the deviceAliasOrURI, because URIs might contain slashes and they are not allowed in // module names @@ -537,6 +575,35 @@ namespace ChimeraTK { initialValueLatch.wait(); } + /*********************************************************************************************************************/ + + std::list<EntityOwner*> DeviceModule::getInputModulesRecursively(std::list<EntityOwner*> startList) { + // The DeviceModule is the end of the recursion, and is not considered recursive to itself. + // There will always be circular connections to the CS module which does not pose a problem. + // Just return the startList without adding anything (not even the DeviceModule itself) + return startList; + } + + /*********************************************************************************************************************/ + + size_t DeviceModule::getCircularNetworkHash() { + return 0; // The device module is never part of a circular network + } + + /*********************************************************************************************************************/ + + void DeviceModule::incrementDataFaultCounter() { + throw ChimeraTK::logic_error("incrementDataFaultCounter() called on a DeviceModule. This is probably " + "caused by incorrect ownership of variables/accessors or VariableGroups."); + } + + /*********************************************************************************************************************/ + + void DeviceModule::decrementDataFaultCounter() { + throw ChimeraTK::logic_error("decrementDataFaultCounter() called on a DeviceModule. This is probably " + "caused by incorrect ownership of variables/accessors or VariableGroups."); + } + /*********************************************************************************************************************/ /*********************************************************************************************************************/ @@ -599,21 +666,4 @@ namespace ChimeraTK { /*********************************************************************************************************************/ - std::list<EntityOwner*> DeviceModule::getInputModulesRecursively(std::list<EntityOwner*> startList) { - // The DeviceModule is the end of the recursion, and is not considered recursive to itself. - // There will always be circular connections to the CS module which does not pose a problem. - // Just return the startList without adding anything (not even the DeviceModule itself) - return startList; - } - - /*********************************************************************************************************************/ - - size_t DeviceModule::getCircularNetworkHash() { - return 0; // The device module is never part of a circular network - } - - /*********************************************************************************************************************/ - - /*********************************************************************************************************************/ - } // namespace ChimeraTK diff --git a/src/EntityOwner.cc b/src/EntityOwner.cc index 76a7996d..9290c76e 100644 --- a/src/EntityOwner.cc +++ b/src/EntityOwner.cc @@ -14,23 +14,31 @@ 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), _tags(tags) { if(eliminateHierarchy) _hierarchyModifier = HierarchyModifier::hideThis; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ EntityOwner::EntityOwner(const std::string& name, const std::string& description, HierarchyModifier hierarchyModifier, const std::unordered_set<std::string>& tags) : _name(name), _description(description), _hierarchyModifier(hierarchyModifier), _tags(tags) {} - /*********************************************************************************************************************/ + /********************************************************************************************************************/ + + EntityOwner::EntityOwner() + : _name("**INVALID**"), _description("Invalid EntityOwner created by default constructor just " + "as a place holder") {} + + /********************************************************************************************************************/ EntityOwner::~EntityOwner() {} - /*********************************************************************************************************************/ + /********************************************************************************************************************/ EntityOwner& EntityOwner::operator=(EntityOwner&& other) { _name = std::move(other._name); @@ -48,7 +56,7 @@ namespace ChimeraTK { return *this; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void EntityOwner::registerModule(Module* module, bool addTags) { if(addTags) @@ -56,13 +64,13 @@ namespace ChimeraTK { moduleList.push_back(module); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void EntityOwner::unregisterModule(Module* module) { moduleList.remove(module); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ std::list<VariableNetworkNode> EntityOwner::getAccessorListRecursive() const { // add accessors of this instance itself @@ -76,7 +84,7 @@ namespace ChimeraTK { return list; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ std::list<Module*> EntityOwner::getSubmoduleListRecursive() const { // add modules of this instance itself @@ -90,14 +98,14 @@ namespace ChimeraTK { return list; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ bool EntityOwner::getEliminateHierarchy() const { return (_hierarchyModifier == HierarchyModifier::hideThis) || (_hierarchyModifier == HierarchyModifier::oneUpAndHide); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ VirtualModule EntityOwner::findTag(const std::string& tag) const { // create new module to return @@ -120,7 +128,7 @@ namespace ChimeraTK { return module; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ VirtualModule EntityOwner::excludeTag(const std::string& tag) const { // create new module to return @@ -143,7 +151,14 @@ namespace ChimeraTK { return module; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ + + void EntityOwner::registerAccessor(VariableNetworkNode accessor) { + for(auto& tag : _tags) accessor.addTag(tag); + accessorList.push_back(accessor); + } + + /********************************************************************************************************************/ // The function adds virtual versions of the EntityOwner itself anf all its children to a virtual module (parent). void EntityOwner::findTagAndAppendToModule(VirtualModule& virtualParent, const std::string& tag, @@ -221,7 +236,7 @@ namespace ChimeraTK { } } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ bool EntityOwner::hasSubmodule(const std::string& name) const { for(auto submodule : getSubmoduleList()) { @@ -230,7 +245,7 @@ namespace ChimeraTK { return false; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ Module* EntityOwner::getSubmodule(const std::string& name) const { for(auto submodule : getSubmoduleList()) { @@ -239,7 +254,7 @@ namespace ChimeraTK { throw ChimeraTK::logic_error("Submodule '" + name + "' not found in module '" + getName() + "'!"); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void EntityOwner::dump(const std::string& prefix) const { if(prefix == "") { @@ -257,7 +272,7 @@ namespace ChimeraTK { } } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void EntityOwner::dumpGraph(const std::string& fileName) const { std::fstream file(fileName, std::ios_base::out); @@ -265,7 +280,7 @@ namespace ChimeraTK { v.dispatch(*this); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void EntityOwner::dumpModuleGraph(const std::string& fileName) const { std::fstream file(fileName, std::ios_base::out); @@ -273,7 +288,7 @@ namespace ChimeraTK { v.dispatch(*this); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void EntityOwner::addTag(const std::string& tag) { for(auto& node : getAccessorList()) node.addTag(tag); @@ -281,7 +296,7 @@ namespace ChimeraTK { _tags.insert(tag); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ VirtualModule EntityOwner::flatten() { VirtualModule nextmodule{_name, _description, getModuleType()}; @@ -291,12 +306,12 @@ namespace ChimeraTK { return nextmodule; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ bool EntityOwner::hasReachedTestableMode() { return testableModeReached; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/src/Module.cc b/src/Module.cc index c84b9aa8..0c8bce7e 100644 --- a/src/Module.cc +++ b/src/Module.cc @@ -258,4 +258,26 @@ namespace ChimeraTK { /*********************************************************************************************************************/ + std::string Module::getQualifiedName() const { + return ((_owner != nullptr) ? _owner->getQualifiedName() : "") + "/" + _name; + } + + /*********************************************************************************************************************/ + + std::string Module::getFullDescription() const { + if(_owner == nullptr) return _description; + auto ownerDescription = _owner->getFullDescription(); + if(ownerDescription == "") return _description; + if(_description == "") return ownerDescription; + return ownerDescription + " - " + _description; + } + + /*********************************************************************************************************************/ + + std::list<EntityOwner*> Module::getInputModulesRecursively(std::list<EntityOwner*> startList) { + return _owner->getInputModulesRecursively(startList); + } + + /*********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/src/ModuleGroup.cc b/src/ModuleGroup.cc index c63adbcc..a99a29ed 100644 --- a/src/ModuleGroup.cc +++ b/src/ModuleGroup.cc @@ -6,6 +6,8 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + ModuleGroup::ModuleGroup(EntityOwner* owner, const std::string& name, const std::string& description, HierarchyModifier hierarchyModifier, const std::unordered_set<std::string>& tags) : ModuleImpl(owner, name, description, hierarchyModifier, tags) { @@ -15,6 +17,8 @@ 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) { @@ -24,4 +28,13 @@ namespace ChimeraTK { } } + /********************************************************************************************************************/ + + ModuleGroup& ModuleGroup::operator=(ModuleGroup&& other) { + ModuleImpl::operator=(std::move(other)); + return *this; + } + + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/src/ModuleImpl.cc b/src/ModuleImpl.cc index 52143041..458e4b7e 100644 --- a/src/ModuleImpl.cc +++ b/src/ModuleImpl.cc @@ -9,6 +9,27 @@ namespace ChimeraTK { /*********************************************************************************************************************/ + ModuleImpl::ModuleImpl(EntityOwner* owner, const std::string& name, const std::string& description, + HierarchyModifier hierarchyModifier, const std::unordered_set<std::string>& tags) + : Module(owner, name, description, hierarchyModifier, tags) {} + + /*********************************************************************************************************************/ + + ModuleImpl::ModuleImpl(EntityOwner* owner, const std::string& name, const std::string& description, + bool eliminateHierarchy, const std::unordered_set<std::string>& tags) + : Module(owner, name, description, eliminateHierarchy, tags) {} + + /*********************************************************************************************************************/ + + ModuleImpl& ModuleImpl::operator=(ModuleImpl&& other) { + if(other.virtualisedModule_isValid) virtualisedModule = other.virtualisedModule; + virtualisedModule_isValid = other.virtualisedModule_isValid; + Module::operator=(std::forward<ModuleImpl>(other)); + return *this; + } + + /*********************************************************************************************************************/ + VariableNetworkNode ModuleImpl::operator()(const std::string& variableName) const { return virtualise()(variableName); } diff --git a/src/TestFacility.cc b/src/TestFacility.cc new file mode 100644 index 00000000..dd61df0c --- /dev/null +++ b/src/TestFacility.cc @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de> +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "TestFacility.h" + +namespace ChimeraTK { + + /********************************************************************************************************************/ + + TestFacility::TestFacility(bool enableTestableMode) { + auto pvManagers = createPVManager(); + pvManager = pvManagers.first; + Application::getInstance().setPVManager(pvManagers.second); + if(enableTestableMode) Application::getInstance().enableTestableMode(); + Application::getInstance().initialise(); + } + + /********************************************************************************************************************/ + + void TestFacility::runApplication() const { + Application::getInstance().testFacilityRunApplicationCalled = true; + // send default values for all control system variables + for(auto& pv : pvManager->getAllProcessVariables()) { + callForTypeNoVoid(pv->getValueType(), [&pv, this](auto arg) { + // Applies only to writeable variables. @todo FIXME It should also NOT apply for application-to-controlsystem + // variables with a return channel, despite being writeable here! + if(!pv->isWriteable()) return; + // Safety check against incorrect usage + if(pv->getVersionNumber() != VersionNumber(nullptr)) { + throw ChimeraTK::logic_error("The variable '" + pv->getName() + + "' has been written before TestFacility::runApplication() was called. Instead use " + "TestFacility::setScalarDefault() resp. setArrayDefault() to set initial values."); + } + typedef decltype(arg) T; + auto pv_casted = boost::dynamic_pointer_cast<NDRegisterAccessor<T>>(pv); + auto table = boost::fusion::at_key<T>(defaults.table); + // If default value has been stored, copy the default value to the PV. + if(table.find(pv->getName()) != table.end()) { + /// Since pv_casted is the undecorated PV (lacking the TestableModeAccessorDecorator), we need to copy the + /// value also to the decorator. We still have to write through the undecorated PV, otherwise the tests are + /// stalled. @todo It is not understood why this happens! + /// Decorated accessors are stored in different maps for scalars are arrays... + if(pv_casted->getNumberOfSamples() == 1) { // scalar + auto accessor = this->getScalar<T>(pv->getName()); + accessor = table.at(pv->getName())[0]; + } + else { // array + auto accessor = this->getArray<T>(pv->getName()); + accessor = table.at(pv->getName()); + } + // copy value also to undecorated PV + pv_casted->accessChannel(0) = table.at(pv->getName()); + } + // Write the initial value. This must be done even if no default value has been stored, since it is expected + // by the application. + pv_casted->write(); + }); + } + // start the application + Application::getInstance().run(); + // set thread name + Application::registerThread("TestThread"); + // make sure all initial values have been propagated when in testable mode + if(Application::getInstance().isTestableModeEnabled()) { + // call stepApplication() only in testable mode and only if the queues are not empty + if(Application::getInstance().testableMode_counter != 0 || + Application::getInstance().testableMode_deviceInitialisationCounter != 0) { + stepApplication(); + } + } + + // receive all initial values for the control system variables + if(Application::getInstance().isTestableModeEnabled()) { + for(auto& pv : pvManager->getAllProcessVariables()) { + if(!pv->isReadable()) continue; + callForTypeNoVoid(pv->getValueType(), [&](auto t) { + typedef decltype(t) UserType; + this->getArray<UserType>(pv->getName()).readNonBlocking(); + }); + } + } + } + + /********************************************************************************************************************/ + + bool TestFacility::canStepApplication() const { + return Application::getInstance().canStepApplication(); + } + + /********************************************************************************************************************/ + + void TestFacility::stepApplication(bool waitForDeviceInitialisation) const { + Application::getInstance().stepApplication(waitForDeviceInitialisation); + } + + /********************************************************************************************************************/ + + ChimeraTK::VoidRegisterAccessor TestFacility::getVoid(const ChimeraTK::RegisterPath& name) const { + // check for existing accessor in cache + if(boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table).count(name) > 0) { + return boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table)[name]; + } + + // obtain accessor from ControlSystemPVManager + auto pv = pvManager->getProcessArray<ChimeraTK::Void>(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<ChimeraTK::Void>>(pv, false, true, varId, varId); + Application::getInstance().testableMode_names[varId] = "ControlSystem:" + name; + boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table)[name] = deco; + } + else { + boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table)[name] = pv; + } + + // return the accessor as stored in the cache + return boost::fusion::at_key<ChimeraTK::Void>(accessorMap.table)[name]; + } + + /********************************************************************************************************************/ + +} // namespace ChimeraTK diff --git a/src/TriggerFanOut.cc b/src/TriggerFanOut.cc new file mode 100644 index 00000000..b46ac704 --- /dev/null +++ b/src/TriggerFanOut.cc @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de> +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "TriggerFanOut.h" + +namespace ChimeraTK { + + /********************************************************************************************************************/ + + TriggerFanOut::TriggerFanOut(const boost::shared_ptr<ChimeraTK::TransferElement>& externalTriggerImpl, + DeviceModule& deviceModule, VariableNetwork& network) + : externalTrigger(externalTriggerImpl), _deviceModule(deviceModule), _network(network) {} + + /********************************************************************************************************************/ + + TriggerFanOut::~TriggerFanOut() { + deactivate(); + } + + /********************************************************************************************************************/ + + void TriggerFanOut::activate() { + assert(!_thread.joinable()); + _thread = boost::thread([this] { this->run(); }); + } + + /********************************************************************************************************************/ + + void TriggerFanOut::deactivate() { + if(_thread.joinable()) { + _thread.interrupt(); + if(externalTrigger->getAccessModeFlags().has(AccessMode::wait_for_new_data)) { + externalTrigger->interrupt(); + } + _thread.join(); + } + assert(!_thread.joinable()); + } + + /********************************************************************************************************************/ + + namespace { + struct SendDataToConsumers { + SendDataToConsumers(VersionNumber version, DataValidity triggerValidity) + : _version(version), _triggerValidity(triggerValidity) {} + + 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->setDataValidity((_triggerValidity == DataValidity::ok && feeder->dataValidity() == DataValidity::ok) ? + DataValidity::ok : + DataValidity::faulty); + fanOut->accessChannel(0).swap(feeder->accessChannel(0)); + // don't use write destructively. In case of an exception we still need the data for the next read (see + // Exception Handling spec B.2.2.6) + bool dataLoss = fanOut->write(_version); + if(dataLoss) Application::incrementDataLossCounter(fanOut->getName()); + // swap the data back to the feeder so we have a valid copy there. + fanOut->accessChannel(0).swap(feeder->accessChannel(0)); + } + } + + VersionNumber _version; + DataValidity _triggerValidity; + }; + } // namespace + + /********************************************************************************************************************/ + + void TriggerFanOut::run() { + Application::registerThread("TrFO" + externalTrigger->getName()); + Application::testableModeLock("start"); + testableModeReached = true; + + ChimeraTK::VersionNumber version = Application::getInstance().getStartVersion(); + + // Wait for the initial value of the trigger. There always will be one, and if we don't read it here we would + // trigger the loop twice. + externalTrigger->read(); + version = externalTrigger->getVersionNumber(); + + // Wait until the device has been initialised for the first time. This means it + // has been opened, and the check in TransferGroup::read() will not throw a logic_error + // We don't have to store the lock. Just need it as a synchronisation point. + // But we have to increase the testable mode counter because we don't want to fall out of testable mode at this + // point already. + if(Application::getInstance().testableMode) ++Application::getInstance().testableMode_deviceInitialisationCounter; + Application::testableModeUnlock("WaitInitialValueLock"); + (void)_deviceModule.waitForInitialValues(); + Application::testableModeLock("Enter while loop"); + if(Application::getInstance().testableMode) --Application::getInstance().testableMode_deviceInitialisationCounter; + + while(true) { + transferGroup.read(); + // send the version number to the consumers + boost::fusion::for_each(fanOutMap.table, SendDataToConsumers(version, externalTrigger->dataValidity())); + + // wait for external trigger + boost::this_thread::interruption_point(); + Profiler::stopMeasurement(); + externalTrigger->read(); + Profiler::startMeasurement(); + boost::this_thread::interruption_point(); + version = externalTrigger->getVersionNumber(); + } + } + + /********************************************************************************************************************/ + +} // namespace ChimeraTK diff --git a/src/VariableGroup.cc b/src/VariableGroup.cc index 6ac9b255..87243396 100644 --- a/src/VariableGroup.cc +++ b/src/VariableGroup.cc @@ -7,6 +7,8 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + VariableGroup::VariableGroup(EntityOwner* owner, const std::string& name, const std::string& description, HierarchyModifier hierarchyModifier, const std::unordered_set<std::string>& tags) : ModuleImpl(owner, name, description, hierarchyModifier, tags) { @@ -17,6 +19,8 @@ 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) { @@ -27,4 +31,13 @@ namespace ChimeraTK { } } + /********************************************************************************************************************/ + + VariableGroup& VariableGroup::operator=(VariableGroup&& other) { + ModuleImpl::operator=(std::move(other)); + return *this; + } + + /********************************************************************************************************************/ + } /* namespace ChimeraTK */ diff --git a/src/VariableNetwork.cc b/src/VariableNetwork.cc index 6d36ac44..917e0b4f 100644 --- a/src/VariableNetwork.cc +++ b/src/VariableNetwork.cc @@ -339,4 +339,15 @@ namespace ChimeraTK { return true; } + + /*********************************************************************************************************************/ + + bool VariableNetwork::operator==(const VariableNetwork& other) const { + if(other.valueType != valueType) return false; + if(other.nodeList != nodeList) return false; + return true; + } + + /*********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/src/VariableNetworkDumpingVisitor.cc b/src/VariableNetworkDumpingVisitor.cc index cdd17208..8b0f77a6 100644 --- a/src/VariableNetworkDumpingVisitor.cc +++ b/src/VariableNetworkDumpingVisitor.cc @@ -6,9 +6,13 @@ namespace ChimeraTK { + /*********************************************************************************************************************/ + 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; @@ -49,4 +53,6 @@ namespace ChimeraTK { stream() << _prefix << "}" << std::endl; } + /*********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/src/VariableNetworkGraphDumpingVisitor.cc b/src/VariableNetworkGraphDumpingVisitor.cc index 3b34ce5c..12079281 100644 --- a/src/VariableNetworkGraphDumpingVisitor.cc +++ b/src/VariableNetworkGraphDumpingVisitor.cc @@ -12,6 +12,8 @@ namespace ChimeraTK { + /*********************************************************************************************************************/ + void VariableNetworkGraphDumpingVisitor::dispatch(const VariableNetwork& network) { std::string networkPrefix = "network_" + std::to_string(_networkCount++); pushPrefix(networkPrefix); @@ -101,10 +103,14 @@ namespace ChimeraTK { 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" @@ -136,6 +142,8 @@ namespace ChimeraTK { stream() << "}\n"; } + /*********************************************************************************************************************/ + void VariableNetworkGraphDumpingVisitor::dispatch(const VariableNetworkNode& t) { std::string nodeName = prefix() + detail::encodeDotNodeName(detail::nodeName(t)); stream() << nodeName << "[\n"; @@ -153,4 +161,7 @@ namespace ChimeraTK { stream() << "\"]\n"; } + + /*********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/src/VariableNetworkNodeDumpingVisitor.cc b/src/VariableNetworkNodeDumpingVisitor.cc index 4049dad5..e409cd0b 100644 --- a/src/VariableNetworkNodeDumpingVisitor.cc +++ b/src/VariableNetworkNodeDumpingVisitor.cc @@ -6,10 +6,14 @@ 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() << "')"; @@ -50,4 +54,6 @@ namespace ChimeraTK { stream() << std::endl; } + /********************************************************************************************************************/ + } // namespace ChimeraTK diff --git a/src/VirtualModule.cc b/src/VirtualModule.cc index b456e28d..fb8ef540 100644 --- a/src/VirtualModule.cc +++ b/src/VirtualModule.cc @@ -9,6 +9,17 @@ namespace ChimeraTK { + /********************************************************************************************************************/ + + VirtualModule::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 + "'."); + } + } + + /********************************************************************************************************************/ + VirtualModule::VirtualModule(const VirtualModule& other) : Module(nullptr, other.getName(), other.getDescription()) { // since moduleList stores plain pointers, we need to regenerate this list for(auto& mod : other.submodules) addSubModule(mod); // this creates a copy (call by value) @@ -17,14 +28,14 @@ namespace ChimeraTK { _moduleType = other.getModuleType(); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ VirtualModule::~VirtualModule() { // do not unregister owner in Module destructor _owner = nullptr; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ VirtualModule& VirtualModule::operator=(const VirtualModule& other) { // move-assign a plain new module @@ -36,7 +47,7 @@ namespace ChimeraTK { return *this; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ VariableNetworkNode VirtualModule::operator()(const std::string& variableName) const { for(auto& variable : getAccessorList()) { @@ -45,7 +56,7 @@ namespace ChimeraTK { 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()) { @@ -54,7 +65,7 @@ namespace ChimeraTK { 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 @@ -88,13 +99,13 @@ namespace ChimeraTK { } } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void VirtualModule::addAccessor(VariableNetworkNode accessor) { accessorList.push_back(accessor); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void VirtualModule::addSubModule(VirtualModule module) { if(!hasSubmodule(module.getName())) { @@ -115,7 +126,7 @@ namespace ChimeraTK { } } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void VirtualModule::removeSubModule(const std::string& name) { for(auto module = submodules.begin(); module != submodules.end(); ++module) { @@ -127,13 +138,13 @@ namespace ChimeraTK { } } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ const Module& VirtualModule::virtualise() const { return *this; } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ VirtualModule& VirtualModule::createAndGetSubmodule(const RegisterPath& moduleName) { for(auto& sm : submodules) { @@ -143,7 +154,7 @@ namespace ChimeraTK { return submodules.back(); } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ VirtualModule& VirtualModule::createAndGetSubmoduleRecursive(const RegisterPath& moduleName) { if(moduleName == "") return *this; @@ -158,7 +169,7 @@ namespace ChimeraTK { } } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ void VirtualModule::stripEmptyChildsRecursive() { // first recurse into childs, to make sure to remove all we an @@ -175,6 +186,6 @@ namespace ChimeraTK { } } - /*********************************************************************************************************************/ + /********************************************************************************************************************/ } /* namespace ChimeraTK */ diff --git a/src/VisitorHelper.cc b/src/VisitorHelper.cc index 69f40514..95312655 100644 --- a/src/VisitorHelper.cc +++ b/src/VisitorHelper.cc @@ -6,6 +6,8 @@ namespace ChimeraTK { namespace detail { + /********************************************************************************************************************/ + std::string encodeDotNodeName(std::string name) { std::replace(name.begin(), name.end(), '-', 'm'); // minus std::replace(name.begin(), name.end(), ':', 'c'); // colon @@ -18,8 +20,12 @@ namespace ChimeraTK { namespace detail { return name; } + /********************************************************************************************************************/ + std::string nodeName(const VariableNetworkNode& node) { return node.getQualifiedName().empty() ? node.getName() : node.getQualifiedName(); } + /********************************************************************************************************************/ + }} // namespace ChimeraTK::detail diff --git a/src/XMLGeneratorVisitor.cc b/src/XMLGeneratorVisitor.cc index 9a2c603c..76bf6ff4 100644 --- a/src/XMLGeneratorVisitor.cc +++ b/src/XMLGeneratorVisitor.cc @@ -13,14 +13,21 @@ #include <cxxabi.h> 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")) {} + /********************************************************************************************************************/ + void XMLGeneratorVisitor::save(const std::string& fileName) { _doc->write_to_file_formatted(fileName); } + /********************************************************************************************************************/ + void XMLGeneratorVisitor::dispatch(const Application& app) { _rootElement->set_attribute("name", app.getName()); for(auto& network : app.networkList) { @@ -35,6 +42,8 @@ namespace ChimeraTK { } } + /********************************************************************************************************************/ + void XMLGeneratorVisitor::dispatch(const VariableNetworkNode& node) { if(node.getType() != NodeType::ControlSystem) return; @@ -201,4 +210,6 @@ namespace ChimeraTK { } } + /********************************************************************************************************************/ + } // namespace ChimeraTK -- GitLab