8#include <ChimeraTK/ControlSystemAdapter/BidirectionalProcessArray.h>
9#include <ChimeraTK/TransferElement.h>
11#include <boost/shared_ptr.hpp>
12#include <boost/thread.hpp>
17#include <shared_mutex>
20 class ConnectionMaker;
26namespace ChimeraTK::detail {
38 void lock(
const std::string& name,
bool shared);
48 void unlock(
const std::string& name);
55 [[nodiscard]]
bool testLock()
const;
60 [[nodiscard]]
bool canStep()
const {
return _counter != 0; }
66 void step(
bool waitForDeviceInitialisation);
82 void setEnableDebug(
bool enable =
true) { _enableDebug = enable; }
87 void setEnableDebugDecorating(
bool enable =
true) { _debugDecorating = enable; }
92 [[nodiscard]]
bool isEnabled()
const {
return _enabled; }
97 static size_t getNextVariableId() {
98 static size_t nextId{0};
102 enum class DecoratorType { READ, WRITE };
105 boost::shared_ptr<NDRegisterAccessor<T>> decorate(boost::shared_ptr<NDRegisterAccessor<T>> other,
106 DecoratorType direction,
const std::string& name = {},
size_t varId = 0);
109 using AccessorPair = std::pair<boost::shared_ptr<NDRegisterAccessor<T>>, boost::shared_ptr<NDRegisterAccessor<T>>>;
112 AccessorPair<T> decorate(
113 AccessorPair<T> other,
const VariableNetworkNode& producer,
const VariableNetworkNode& consumer);
118 template<
typename UserType>
119 class AccessorDecorator :
public ChimeraTK::NDRegisterAccessorDecorator<UserType> {
121 AccessorDecorator(detail::TestableMode& testableMode,
122 boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor,
bool handleRead,
bool handleWrite,
123 size_t variableIdRead,
size_t variableIdWrite);
127 bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber = {})
override;
131 bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber = {})
override;
133 void doReadTransferSynchronously()
override { _target->readTransfer(); }
138 void doPreRead(TransferType type)
override;
142 void obtainLockAndDecrementCounter(
bool hasNewData);
146 void decrementCounter();
148 void doPostRead(TransferType type,
bool hasNewData)
override;
150 [[nodiscard]] boost::shared_ptr<NDRegisterAccessor<UserType>> decorateDeepInside(
151 [[maybe_unused]] std::function<boost::shared_ptr<NDRegisterAccessor<UserType>>(
152 const boost::shared_ptr<NDRegisterAccessor<UserType>>&)> factory)
override {
159 using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D;
160 using ChimeraTK::NDRegisterAccessorDecorator<UserType>::_target;
162 bool _handleRead, _handleWrite;
163 size_t _variableIdRead, _variableIdWrite;
164 TestableMode& _testableMode;
166 bool accountForWriteOperation(
const std::function<
bool(
void)>& writeOperation);
177 bool _enableDebug{
false};
183 std::atomic<size_t> _counter{0};
189 bool _enabled{
false};
196 std::atomic<size_t> _deviceInitialisationCounter{0};
198 struct VariableDescriptor {
203 boost::shared_ptr<TransferElement> processVariable;
210 std::atomic<size_t> counter{0};
223 static std::shared_timed_mutex _mutex;
235 static std::shared_mutex _mutex2;
241 std::map<size_t, VariableDescriptor> _variables;
251 class LastMutexOwner {
253 LastMutexOwner& operator=(
const boost::thread::id&
id);
255 operator boost::thread::id();
258 boost::thread::id _lastMutexOwner;
259 std::mutex _mxLastMutexOwner;
271 static Lock& getLockObject();
282 [[nodiscard]]
bool tryLockFor(std::chrono::seconds timeout,
bool shared);
284 [[nodiscard]]
bool ownsLock()
const {
return _ownsLock; }
290 bool _ownsLock{
false};
291 bool _isShared{
false};
293 friend Lock& TestableMode::getLockObject();
297 std::map<boost::thread::id, std::string> _threadNames;
300 std::map<boost::thread::id, pid_t> _threadPThreadId;
303 std::mutex _threadNamesMutex;
309 std::string threadName(
const boost::thread::id& threadId = boost::this_thread::get_id());
319 pid_t pthreadId(
const boost::thread::id& threadId = boost::this_thread::get_id());
321 bool _debugDecorating{
false};
332 TestableMode::AccessorPair<T> TestableMode::decorate(
333 AccessorPair<T> other,
const VariableNetworkNode& producer,
const VariableNetworkNode& consumer) {
338 if(_debugDecorating) {
340 <<
" Decorating pair " << producer.getQualifiedName() <<
"[" << other.first->getId() <<
"] -> "
341 << consumer.getQualifiedName() <<
"[" << other.second->getId() <<
"]";
345 size_t varId = detail::TestableMode::getNextVariableId();
347 AccessorPair<T> result;
349 if(producer.getDirection().withReturn) {
350 varIdReturn = detail::TestableMode::getNextVariableId();
354 if(!producer.getDirection().withReturn) {
355 result.first = boost::make_shared<AccessorDecorator<T>>(*
this, other.first,
false,
true, varId, varId);
356 result.second = boost::make_shared<AccessorDecorator<T>>(*
this, other.second,
true,
false, varId, varId);
359 result.first = boost::make_shared<AccessorDecorator<T>>(*
this, other.first,
true,
true, varIdReturn, varId);
360 result.second = boost::make_shared<AccessorDecorator<T>>(*
this, other.second,
true,
true, varId, varIdReturn);
364 auto& variable = _variables.at(varId);
365 variable.name =
"Internal:" + producer.getQualifiedName();
367 variable.name +=
"->" + consumer.getQualifiedName();
369 if(producer.getDirection().withReturn) {
370 auto& returnVariable = _variables.at(varIdReturn);
371 returnVariable.name = variable.name +
" (return)";
380 boost::shared_ptr<NDRegisterAccessor<T>> TestableMode::decorate(
381 boost::shared_ptr<NDRegisterAccessor<T>> other, DecoratorType direction,
const std::string& name,
size_t varId) {
386 if(_debugDecorating) {
388 <<
" Decorating single " << (direction == DecoratorType::READ ?
"consumer " :
"feeder ") << name <<
"["
389 << other->getId() <<
"]";
393 varId = detail::TestableMode::getNextVariableId();
396 _variables[varId].processVariable = other;
397 if(not name.empty()) {
398 _variables.at(varId).name = name;
401 auto pvarDec = boost::make_shared<AccessorDecorator<T>>(
402 *
this, other, direction == DecoratorType::READ, direction == DecoratorType::WRITE, varId, varId);
413 template<
typename UserType>
414 TestableMode::AccessorDecorator<UserType>::AccessorDecorator(detail::TestableMode& testableMode,
415 boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor,
bool handleRead,
bool handleWrite,
416 size_t variableIdRead,
size_t variableIdWrite)
417 :
ChimeraTK::NDRegisterAccessorDecorator<UserType>(accessor), _handleRead(handleRead), _handleWrite(handleWrite),
418 _variableIdRead(variableIdRead), _variableIdWrite(variableIdWrite), _testableMode(testableMode) {
419 assert(_variableIdRead != 0);
420 assert(_variableIdWrite != 0);
423 if(this->isReadable() && handleRead) {
424 _testableMode._variables[_variableIdRead].processVariable = accessor;
425 assert(accessor->getAccessModeFlags().has(AccessMode::wait_for_new_data));
430 auto bidir = boost::dynamic_pointer_cast<BidirectionalProcessArray<UserType>>(accessor);
432 bidir->setValueRejectCallback([
this] { decrementCounter(); });
435 assert(!(handleRead && handleWrite));
441 template<
typename UserType>
444 bool TestableMode::AccessorDecorator<UserType>::doWriteTransfer(ChimeraTK::VersionNumber versionNumber) {
446 return _target->writeTransfer(versionNumber);
448 return accountForWriteOperation([
this, versionNumber]() {
return _target->writeTransfer(versionNumber); });
453 template<
typename UserType>
456 bool TestableMode::AccessorDecorator<UserType>::doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) {
458 return _target->writeTransferDestructively(versionNumber);
461 return accountForWriteOperation(
462 [
this, versionNumber]() {
return _target->writeTransferDestructively(versionNumber); });
467 template<
typename UserType>
468 void TestableMode::AccessorDecorator<UserType>::releaseLock() {
469 if(_testableMode.testLock()) {
470 _testableMode.unlock(
"doReadTransfer " + this->getName());
476 template<
typename UserType>
477 void TestableMode::AccessorDecorator<UserType>::doPreRead(TransferType type) {
478 _target->preRead(type);
481 if(_handleRead && type == TransferType::read &&
482 TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) {
489 template<
typename UserType>
490 void TestableMode::AccessorDecorator<UserType>::obtainLockAndDecrementCounter(
bool hasNewData) {
491 if(!_testableMode.testLock()) {
492 _testableMode.lock(
"doReadTransfer " + this->getName(),
true);
497 auto& variable = _testableMode._variables.at(_variableIdRead);
498 if(variable.counter > 0) {
499 assert(_testableMode._counter > 0);
500 --_testableMode._counter;
502 if(_testableMode._enableDebug) {
504 <<
"TestableModeAccessorDecorator[name='" << this->getName() <<
"', id=" << _variableIdRead
505 <<
"]: testableMode.counter decreased, now at value " << _testableMode._counter <<
" / "
510 if(_testableMode._enableDebug) {
512 <<
"TestableModeAccessorDecorator[name='" << this->getName() <<
"', id=" << _variableIdRead
513 <<
"]: testableMode.counter NOT decreased, was already at value " << _testableMode._counter <<
" / "
514 << variable.counter <<
"\n"
522 template<
typename UserType>
523 void TestableMode::AccessorDecorator<UserType>::decrementCounter() {
524 obtainLockAndDecrementCounter(
true);
530 template<
typename UserType>
531 void TestableMode::AccessorDecorator<UserType>::doPostRead(TransferType type,
bool hasNewData) {
533 obtainLockAndDecrementCounter(hasNewData);
535 ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, hasNewData);
538 template<
typename UserType>
539 bool TestableMode::AccessorDecorator<UserType>::accountForWriteOperation(
540 const std::function<
bool(
void)>& writeOperation) {
541 bool dataLost =
false;
542 if(!_testableMode.testLock()) {
544 _testableMode.lock(
"write " + this->getName(),
true);
549 _testableMode._variables.at(_variableIdWrite).counter++;
550 _testableMode._counter++;
552 dataLost = writeOperation();
556 _testableMode._variables.at(_variableIdWrite).counter--;
557 _testableMode._counter--;
560 if(_testableMode._enableDebug) {
563 <<
"TestableModeAccessorDecorator::write[name='" << this->getName() <<
"', id=" << _variableIdWrite
564 <<
"]: testableMode.counter increased, now at value " << _testableMode._counter;
568 <<
"TestableModeAccessorDecorator::write[name='" << this->getName() <<
"', id=" << _variableIdWrite
569 <<
"]: testableMode.counter not increased due to lost data";
Implements access to a ChimeraTK::Device.
Helper class to facilitate tests of applications based on ApplicationCore.
InternalModule which waits for a trigger, then reads a number of variables and distributes each of th...
void setThreadName(const std::string &name)
Set name of the current thread.
InvalidityTracer application module.
Logger::StreamProxy logger(Logger::Severity severity, std::string context)
Convenience function to obtain the logger stream.