ChimeraTK-DeviceAccess 03.29.00
Loading...
Searching...
No Matches
TransferElement.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3#pragma once
4
5#include "AccessMode.h"
6#include "DeviceBackend.h"
7#include "Exception.h"
8#include "TransferElementID.h"
9#include "VersionNumber.h"
10
11#include <ChimeraTK/cppext/future_queue.hpp>
12
13#include <boost/bind/bind.hpp>
14#include <boost/enable_shared_from_this.hpp>
15#include <boost/numeric/conversion/cast.hpp>
16#include <boost/shared_ptr.hpp>
17#include <boost/thread.hpp>
18#include <boost/thread/future.hpp>
19
20#include <functional>
21#include <iostream>
22#include <list>
23#include <string>
24#include <typeinfo>
25#include <utility>
26#include <vector>
27
28namespace ChimeraTK {
29 class PersistentDataStorage;
30 class TransferGroup;
31 class ReadAnyGroup;
32
42 enum class DataValidity {
43 ok,
44 faulty
45 };
46
47 std::ostream& operator<<(std::ostream& os, const DataValidity& validity);
48
53
54 namespace detail {
61 class DiscardValueException {};
62
63 } /* namespace detail */
64
65 /********************************************************************************************************************/
66
68 class TransferElement : public boost::enable_shared_from_this<TransferElement> {
69 public:
71 TransferElement(std::string name, AccessModeFlags accessModeFlags, std::string unit = std::string(unitNotSet),
72 std::string description = std::string())
73 : _name(std::move(name)), _unit(std::move(unit)), _description(std::move(description)),
74 _accessModeFlags(std::move(accessModeFlags)) {}
75
77 TransferElement(const TransferElement& other) = delete;
79 TransferElement& operator=(const TransferElement& other) = delete;
81
83 virtual ~TransferElement() = default;
84
86 using SharedPtr = boost::shared_ptr<TransferElement>;
87
89 const std::string& getName() const { return _name; }
90
93 const std::string& getUnit() const { return _unit; }
94
96 const std::string& getDescription() const { return _description; }
97
98 void setUnit(const std::string& unit) { _unit = unit; }
99 void setDescription(const std::string& description) { _description = description; }
100
104 virtual const std::type_info& getValueType() const = 0;
105
108
112
116
120 void read() {
122 throw ChimeraTK::logic_error("Calling read() or write() on the TransferElement '" + _name +
123 "' which is part of a TransferGroup is not allowed.");
124 }
126 throw ChimeraTK::logic_error("Directly calling read() on the TransferElement '" + _name +
127 "' which is part of a ReadAnyGroup is not allowed.");
128 }
129 this->readTransactionInProgress = false;
130
131 preReadAndHandleExceptions(TransferType::read);
132 if(!_activeException) {
133 handleTransferException([&] { readTransfer(); });
134 }
135
136 postReadAndHandleExceptions(TransferType::read, !_activeException);
137 }
138
152 throw ChimeraTK::logic_error("Calling read() or write() on the TransferElement '" + _name +
153 "' which is part of a TransferGroup is not allowed.");
154 }
156 throw ChimeraTK::logic_error("Directly calling readNonBlocking() on the TransferElement '" + _name +
157 "' which is part of a ReadAnyGroup is not allowed.");
158 }
159 this->readTransactionInProgress = false;
160 preReadAndHandleExceptions(TransferType::readNonBlocking);
161 bool updateDataBuffer = false;
162 if(!_activeException) {
163 handleTransferException([&] { updateDataBuffer = readTransferNonBlocking(); });
164 }
165
166 bool retVal = updateDataBuffer;
167 if(_activeException) {
168 auto previousVersionNumber = _versionNumber;
169 auto previousDataValidity = _dataValidity;
170 // always call postRead with updateDataBuffer = false in case of an exception
171 postReadAndHandleExceptions(TransferType::readNonBlocking, false);
172 // Usually we do not reach this point because postRead() is re-throwing the _activeException.
173 // If we reach this point the exception has been suppressed. We have to calculate a
174 // new return value because the dataBuffer has not changed, but the meta data
175 // could have, in which case we have to return true.
176 retVal = (previousVersionNumber != _versionNumber) || (previousDataValidity != _dataValidity);
177 }
178 else {
179 // call postRead with updateDataBuffer as returned by readTransferNonBlocking
180 postReadAndHandleExceptions(TransferType::readNonBlocking, updateDataBuffer);
181 }
182 return retVal;
183 }
184
189 bool readLatest() {
191 bool updateDataBuffer = false;
192 // Call readNonBlocking until there is no new data to be read any more
193 while(readNonBlocking()) {
194 // remember whether we have new data
195 updateDataBuffer = true;
196 }
197 return updateDataBuffer;
198 }
199 // Without wait_for_new_data readNonBlocking always returns true, and the while loop above would never end.
200 // Hence we just call the (synchronous) read and return true;
201 read();
202 return true;
203 }
204
208 bool write(ChimeraTK::VersionNumber versionNumber = {}) {
210 throw ChimeraTK::logic_error("Calling read() or write() on the TransferElement '" + _name +
211 "' which is part of a TransferGroup is not allowed.");
212 }
213 this->writeTransactionInProgress = false;
214 bool previousDataLost = true; // the value here does not matter. If there was an exception, it will be re-thrown
215 // in postWrite, so it is never returned
216
217 preWriteAndHandleExceptions(TransferType::write, versionNumber);
218 if(!_activeException) {
219 handleTransferException([&] { previousDataLost = writeTransfer(versionNumber); });
220 }
221
222 postWriteAndHandleExceptions(TransferType::write, versionNumber);
223 return previousDataLost;
224 }
225
232 throw ChimeraTK::logic_error("Calling read() or write() on the TransferElement '" + _name +
233 "' which is part of a TransferGroup is not allowed.");
234 }
235 this->writeTransactionInProgress = false;
236
237 preWriteAndHandleExceptions(TransferType::writeDestructively, versionNumber);
238 bool previousDataLost = true; // the value here does not matter. If there was an exception, it will be re-thrown
239 // in postWrite, so it is never returned
240 if(!_activeException) {
241 handleTransferException([&] { previousDataLost = writeTransferDestructively(versionNumber); });
242 }
243
244 postWriteAndHandleExceptions(TransferType::writeDestructively, versionNumber);
245 return previousDataLost;
246 }
247
264
267 virtual bool isReadOnly() const = 0;
268
271 virtual bool isReadable() const = 0;
272
275 virtual bool isWriteable() const = 0;
276
282 void setActiveException(std::exception_ptr& setThisException) {
283 if(setThisException) {
284 _activeException = setThisException;
285 setThisException = nullptr;
286 }
287 }
288
299 virtual void setExceptionBackend(boost::shared_ptr<DeviceBackend> exceptionBackend) {
300 _exceptionBackend = std::move(exceptionBackend);
301 }
302
305 boost::shared_ptr<DeviceBackend> getExceptionBackend() { return _exceptionBackend; }
306
310 cppext::future_queue<void> getReadQueue() { return _readQueue; }
311
312 protected:
317 boost::shared_ptr<DeviceBackend> _exceptionBackend;
318
319 private:
323 template<typename Callable>
324 void handleTransferException(Callable function) {
325 try {
326 function();
327 }
329 _activeException = std::current_exception();
330 }
331 catch(boost::thread_interrupted&) {
332 _activeException = std::current_exception();
333 }
334 }
335
336 // helper function that just gets rid of the DiscardValueException and otherwise does a pop_wait on the _readQueue.
337 // It does not deal with other exceptions. This is done in handleTransferException.
338 void readTransferAsyncWaitingImpl() {
339 retry:
340 try {
341 _readQueue.pop_wait();
342 }
343 catch(detail::DiscardValueException&) {
344 goto retry;
345 }
346 }
347
348 public:
360 readTransferAsyncWaitingImpl();
361 }
362 else {
364 }
365 }
366
367 protected:
380 virtual void doReadTransferSynchronously() = 0;
381
382 private:
383 // helper function that just gets rid of the DiscardValueException and otherwise does a pop on the _readQueue.
384 // It does not deal with other exceptions. This is done in handleTransferException.
385 bool readTransferAsyncNonWaitingImpl() {
386 retry:
387 try {
388 return _readQueue.pop();
389 }
390 catch(detail::DiscardValueException&) {
391 goto retry;
392 }
393 }
394
395 public:
408 return readTransferAsyncNonWaitingImpl();
409 }
411 return true;
412 }
413
414 private:
416 void preReadAndHandleExceptions(TransferType type) noexcept {
417 try {
418 preRead(type);
419 }
420 catch(ChimeraTK::logic_error&) {
421 _activeException = std::current_exception();
422 }
424 _activeException = std::current_exception();
425 }
426 catch(boost::thread_interrupted&) {
427 _activeException = std::current_exception();
428 }
429 }
430
431 public:
437 if(readTransactionInProgress) return;
438 _activeException = {nullptr};
439
440 readTransactionInProgress = true; // remember that doPreRead has been called. It might throw, so we remember
441 // before we call it
442 doPreRead(type);
443 }
444
451 protected:
452 virtual void doPreRead(TransferType) {}
453
454 private:
457 void postReadAndHandleExceptions(TransferType type, bool updateDataBuffer) {
458 try {
459 postRead(type, updateDataBuffer);
460 }
461 catch(ChimeraTK::runtime_error& ex) {
463 _exceptionBackend->setException(ex.what());
464 }
465 throw;
466 }
467 }
468
469 public:
477 void postRead(TransferType type, bool updateDataBuffer) {
478 // only delegate to doPostRead() the first time postRead() is called in a row.
479 if(readTransactionInProgress) {
480 readTransactionInProgress = false;
481 doPostRead(type, updateDataBuffer);
482 }
483
484 // Throw on each call of postRead(). All high-level elements for a shared low-level transfer element must see the
485 // exception. Note: doPostRead can throw an exception, but in that case _activeException must be false (we can
486 // only have one exception at a time). In case other code is added here later which needs to be executed after
487 // doPostRead() always, a try-catch block may be necessary.
488 if(_activeException) {
489 // don't clear the active connection. This is done in preRead().
490 std::rethrow_exception(_activeException);
491 }
492 }
493
506 protected:
507 virtual void doPostRead(TransferType, bool /*updateDataBuffer*/) {}
508
509 private:
511 void preWriteAndHandleExceptions(TransferType type, ChimeraTK::VersionNumber versionNumber) noexcept {
512 try {
513 preWrite(type, versionNumber);
514 }
515 catch(ChimeraTK::logic_error&) {
516 _activeException = std::current_exception();
517 }
519 _activeException = std::current_exception();
520 }
521 catch(boost::thread_interrupted&) {
522 _activeException = std::current_exception();
523 }
524 }
525
526 public:
535 if(writeTransactionInProgress) return;
536
537 _activeException = {};
538 if(versionNumber < getVersionNumber()) {
539 throw ChimeraTK::logic_error("The version number " + std::string(versionNumber) +
540 " passed to write() of TransferElement '" + _name + "' is less than the last version number used " +
541 std::string(getVersionNumber()) + ".");
542 }
543 writeTransactionInProgress = true; // must not be set, if the logic_error is thrown above due to the old version
544 doPreWrite(type, versionNumber);
545 }
546
553 protected:
555
556 private:
559 void postWriteAndHandleExceptions(TransferType type, VersionNumber versionNumber) {
560 try {
561 postWrite(type, versionNumber);
562 }
563 catch(ChimeraTK::runtime_error& ex) {
565 _exceptionBackend->setException(ex.what());
566 }
567 throw;
568 }
569 }
570
571 public:
578 void postWrite(TransferType type, VersionNumber versionNumber) {
579 if(writeTransactionInProgress) {
580 writeTransactionInProgress = false;
581 doPostWrite(type, versionNumber);
582 }
583
584 // Note: doPostWrite can throw an exception, but in that case hasSeenException must be false (we can only have one
585 // exception at a time). In case other code is added here later which needs to be executed after doPostWrite()
586 // always, a try-catch block may be necessary.
587 // Another note: If writeTransactionInProgress == false, there can still be an exception, if the version number
588 // used in a write was too old (see preWrite).
589 if(_activeException) {
590 std::rethrow_exception(_activeException);
591 }
592
593 // only after a successful write the version number is updated
594 _versionNumber = versionNumber;
595 }
596
603 protected:
605
606 public:
615 bool writeTransfer(ChimeraTK::VersionNumber versionNumber) { return doWriteTransfer(versionNumber); }
616
617 protected:
625 virtual bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber) = 0;
626
627 public:
640 return doWriteTransferDestructively(versionNumber);
641 }
642
643 protected:
655 return doWriteTransfer(versionNumber);
656 }
657
658 public:
686 virtual bool mayReplaceOther(const boost::shared_ptr<TransferElement const>& other) const {
687 (void)other; // prevent warning
688 return false;
689 }
690
699 virtual std::vector<boost::shared_ptr<TransferElement>> getHardwareAccessingElements() = 0;
700
720 virtual std::list<boost::shared_ptr<TransferElement>> getInternalElements() = 0;
721
731 virtual boost::shared_ptr<TransferElement> getHighLevelImplElement() { return shared_from_this(); }
732
739 // FIXME #11279 Implement API breaking changes from linter warnings
740 // NOLINTNEXTLINE(performance-unnecessary-value-param)
741 virtual void replaceTransferElement([[maybe_unused]] boost::shared_ptr<TransferElement> newElement) {}
742
747 virtual boost::shared_ptr<TransferElement> makeCopyRegisterDecorator() = 0;
748
751 static constexpr char unitNotSet[] = "n./a.";
752
763 // FIXME #11279 Implement API breaking changes from linter warnings
764 // NOLINTNEXTLINE(performance-unnecessary-value-param)
765 virtual void setPersistentDataStorage(boost::shared_ptr<ChimeraTK::PersistentDataStorage>) {}
766
771 TransferElementID getId() const { return _id; }
772
792 virtual void interrupt() {}
793
800 template<typename QUEUE_TYPE>
801 void interrupt_impl(QUEUE_TYPE& dataTransportQueue) {
802 dataTransportQueue.push_overwrite_exception(std::make_exception_ptr(boost::thread_interrupted()));
803 }
804
806 bool isReadTransactionInProgress() const { return readTransactionInProgress; }
807
809 bool isWriteTransactionInProgress() const { return writeTransactionInProgress; }
810
812 [[nodiscard]] ReadAnyGroup* getReadAnyGroup() const { return _inReadAnyGroup; }
813
815 virtual void setInReadAnyGroup(ReadAnyGroup* rag) { _inReadAnyGroup = rag; }
816
817 protected:
819 std::string _name;
820
822 std::string _unit;
823
825 std::string _description;
826
829
832
836
843
846
847 friend class TransferGroup;
848 friend class ReadAnyGroup;
849
850 private:
856 bool readTransactionInProgress{false};
857
860 bool writeTransactionInProgress{false};
861
862 protected:
866 cppext::future_queue<void> _readQueue;
867
871
875
878 std::exception_ptr _activeException{nullptr};
879 }; // namespace ChimeraTK
880
881} /* namespace ChimeraTK */
Set of AccessMode flags with additional functionality for an easier handling.
Definition AccessMode.h:48
bool has(AccessMode flag) const
Check if a certain flag is in the set.
Definition AccessMode.cc:20
Group several registers (= TransferElement) to allow waiting for an update of any of the registers.
Base class for register accessors which can be part of a TransferGroup.
bool writeTransferDestructively(ChimeraTK::VersionNumber versionNumber)
Write the data to the device.
void setDataValidity(DataValidity validity=DataValidity::ok)
Set the current DataValidity for this TransferElement.
cppext::future_queue< void > getReadQueue()
Function to get a copy of the read queue.
bool readNonBlocking()
Read the next value, if available in the input buffer.
std::string _name
Identifier uniquely identifying the TransferElement.
TransferElementID _id
The ID of this TransferElement.
ReadAnyGroup * _inReadAnyGroup
ReadAnyGroup this TransferElement has been added to, nullptr if not in a ReadAnyGroup.
virtual void setExceptionBackend(boost::shared_ptr< DeviceBackend > exceptionBackend)
Set the backend to which the exception has to be reported.
boost::shared_ptr< TransferElement > SharedPtr
A typedef for more compact syntax.
AccessModeFlags _accessModeFlags
The access mode flags for this transfer element.
DataValidity _dataValidity
The validity of the data in the application buffer.
void preRead(TransferType type)
Perform any pre-read tasks if necessary.
void setDescription(const std::string &description)
virtual std::list< boost::shared_ptr< TransferElement > > getInternalElements()=0
Obtain the full list of TransferElements internally used by this TransferElement.
virtual ~TransferElement()=default
Abstract base classes need a virtual destructor.
TransferElementID getId() const
Obtain unique ID for this TransferElement, see TransferElementID for details.
virtual void doPreRead(TransferType)
Backend specific implementation of preRead().
virtual void doPostRead(TransferType, bool)
Backend specific implementation of postRead().
std::string _description
Description of this variable/register.
ChimeraTK::VersionNumber getVersionNumber() const
Returns the version number that is associated with the last transfer (i.e.
virtual const std::type_info & getValueType() const =0
Returns the std::type_info for the value type of this transfer element.
virtual void setPersistentDataStorage(boost::shared_ptr< ChimeraTK::PersistentDataStorage >)
Associate a persistent data storage object to be updated on each write operation of this ProcessArray...
DataValidity dataValidity() const
Return current validity of the data.
std::exception_ptr _activeException
Exception to be rethrown in postXXX() in case hasSeenException == true Can be set via setActiveExcept...
virtual void replaceTransferElement(boost::shared_ptr< TransferElement > newElement)
Search for all underlying TransferElements which are considered identical (see sameRegister()) with t...
void postWrite(TransferType type, VersionNumber versionNumber)
Perform any post-write clean-ups if necessary.
virtual void doPostWrite(TransferType, VersionNumber)
Backend specific implementation of postWrite().
void interrupt_impl(QUEUE_TYPE &dataTransportQueue)
Implementation of interrupt()
ReadAnyGroup * getReadAnyGroup() const
Obtain the ReadAnyGroup this TransferElement is part of, or nullptr if not in a ReadAnyGroup.
void makeUniqueId()
Allow generating a unique ID from derived classes.
const std::string & getUnit() const
Returns the engineering unit.
bool isReadTransactionInProgress() const
Check whether a read transaction is in progress, i.e.
AccessModeFlags getAccessModeFlags() const
Return the AccessModeFlags for this TransferElement.
std::string _unit
Engineering unit.
TransferElement(const TransferElement &other)=delete
Copying and moving is not allowed.
virtual bool isReadable() const =0
Check if transfer element is readable.
bool writeDestructively(ChimeraTK::VersionNumber versionNumber={})
Just like write(), but allows the implementation to destroy the content of the user buffer in the pro...
bool readLatest()
Read the latest value, discarding any other update since the last read if present.
virtual void doReadTransferSynchronously()=0
Implementation version of readTransfer() for synchronous reads.
virtual bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber)
Implementation version of writeTransferDestructively().
VersionNumber _versionNumber
The version number of the last successful transfer.
void read()
Read the data from the device.
virtual void doPreWrite(TransferType, VersionNumber)
Backend specific implementation of preWrite().
void preWrite(TransferType type, ChimeraTK::VersionNumber versionNumber)
Transfer the data from the user buffer into the device send buffer, while converting the data from th...
bool _isInTransferGroup
Flag whether this TransferElement has been added to a TransferGroup or not.
virtual bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber)=0
Implementation version of writeTransfer().
void setUnit(const std::string &unit)
TransferElement & operator=(TransferElement &&other)=delete
bool writeTransfer(ChimeraTK::VersionNumber versionNumber)
Write the data to the device.
static constexpr char unitNotSet[]
Constant string to be used as a unit when the unit is not provided or known.
cppext::future_queue< void > _readQueue
The queue for asynchronous read transfers.
virtual void setInReadAnyGroup(ReadAnyGroup *rag)
Set the ReadAnyGroup of which this TransferElement is part of.
bool readTransferNonBlocking()
Read the data from the device but do not fill it into the user buffer of this TransferElement.
virtual std::vector< boost::shared_ptr< TransferElement > > getHardwareAccessingElements()=0
Obtain the underlying TransferElements with actual hardware access.
const std::string & getDescription() const
Returns the description of this variable/register.
virtual bool isWriteable() const =0
Check if transfer element is writeable.
TransferElement(TransferElement &&other)=delete
virtual bool mayReplaceOther(const boost::shared_ptr< TransferElement const > &other) const
Check whether the TransferElement can be used in places where the TransferElement "other" is currentl...
void readTransfer()
Read the data from the device but do not fill it into the user buffer of this TransferElement.
void setActiveException(std::exception_ptr &setThisException)
Set an active exception.
bool isWriteTransactionInProgress() const
Check whether a write transaction is in progress, i.e.
boost::shared_ptr< DeviceBackend > getExceptionBackend()
Return the exception backend.
const std::string & getName() const
Returns the name that identifies the process variable.
TransferElement & operator=(const TransferElement &other)=delete
bool write(ChimeraTK::VersionNumber versionNumber={})
Write the data to device.
void postRead(TransferType type, bool updateDataBuffer)
Transfer the data from the device receive buffer into the user buffer, while converting the data into...
TransferElement(std::string name, AccessModeFlags accessModeFlags, std::string unit=std::string(unitNotSet), std::string description=std::string())
Creates a transfer element with the specified name.
boost::shared_ptr< DeviceBackend > _exceptionBackend
The backend to which the runtime_errors are reported via DeviceBackend::setException().
virtual boost::shared_ptr< TransferElement > makeCopyRegisterDecorator()=0
Create a CopyRegisterDecorator of the right type decorating this TransferElement.
virtual void interrupt()
Return from a blocking read immediately and throw boost::thread_interrupted.
virtual bool isReadOnly() const =0
Check if transfer element is read only, i.e.
virtual boost::shared_ptr< TransferElement > getHighLevelImplElement()
Obtain the highest level implementation TransferElement.
Simple class holding a unique ID for a TransferElement.
void makeUnique()
Assign an ID to this instance.
Group multiple data accessors to efficiently trigger data transfers on the whole group.
Class for generating and holding version numbers without exposing a numeric representation.
Exception thrown when a logic error has occured.
Definition Exception.h:51
Exception thrown when a runtime error has occured.
Definition Exception.h:18
const char * what() const noexcept override
Return the message describing what exactly went wrong.
Definition Exception.cpp:14
std::ostream & operator<<(std::ostream &stream, const DataDescriptor::FundamentalType &fundamentalType)
DataValidity
The current state of the data.
@ faulty
The data is considered valid.
@ wait_for_new_data
Make any read blocking until new data has arrived since the last read.
TransferType
Used to indicate the applicable operation on a Transferelement.
STL namespace.