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 [[nodiscard]]
bool isEnabled()
const {
return _enabled; }
87 static size_t getNextVariableId() {
88 static size_t nextId{0};
92 enum class DecoratorType { READ, WRITE };
95 boost::shared_ptr<NDRegisterAccessor<T>> decorate(boost::shared_ptr<NDRegisterAccessor<T>> other,
96 DecoratorType direction,
const std::string& name = {},
size_t varId = 0);
99 using AccessorPair = std::pair<boost::shared_ptr<NDRegisterAccessor<T>>, boost::shared_ptr<NDRegisterAccessor<T>>>;
102 AccessorPair<T> decorate(
103 AccessorPair<T> other,
const VariableNetworkNode& producer,
const VariableNetworkNode& consumer);
108 template<
typename UserType>
109 class AccessorDecorator :
public ChimeraTK::NDRegisterAccessorDecorator<UserType> {
111 AccessorDecorator(detail::TestableMode& testableMode,
112 boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor,
bool handleRead,
bool handleWrite,
113 size_t variableIdRead,
size_t variableIdWrite);
117 bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber = {})
override;
121 bool doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber = {})
override;
123 void doReadTransferSynchronously()
override { _target->readTransfer(); }
128 void doPreRead(TransferType type)
override;
132 void obtainLockAndDecrementCounter(
bool hasNewData);
136 void decrementCounter();
138 void doPostRead(TransferType type,
bool hasNewData)
override;
140 [[nodiscard]] boost::shared_ptr<NDRegisterAccessor<UserType>> decorateDeepInside(
141 [[maybe_unused]] std::function<boost::shared_ptr<NDRegisterAccessor<UserType>>(
142 const boost::shared_ptr<NDRegisterAccessor<UserType>>&)> factory)
override {
149 using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D;
150 using ChimeraTK::NDRegisterAccessorDecorator<UserType>::_target;
152 bool _handleRead, _handleWrite;
153 size_t _variableIdRead, _variableIdWrite;
154 TestableMode& _testableMode;
156 bool accountForWriteOperation(
const std::function<
bool(
void)>& writeOperation);
168 std::atomic<size_t> _counter{0};
174 bool _enabled{
false};
181 std::atomic<size_t> _deviceInitialisationCounter{0};
183 struct VariableDescriptor {
188 boost::shared_ptr<TransferElement> processVariable;
195 std::atomic<size_t> counter{0};
208 static std::shared_timed_mutex _mutex;
220 static std::shared_mutex _mutex2;
226 std::map<size_t, VariableDescriptor> _variables;
236 class LastMutexOwner {
238 LastMutexOwner& operator=(
const boost::thread::id&
id);
240 operator boost::thread::id();
243 boost::thread::id _lastMutexOwner;
244 std::mutex _mxLastMutexOwner;
256 static Lock& getLockObject();
267 [[nodiscard]]
bool tryLockFor(std::chrono::seconds timeout,
bool shared);
269 [[nodiscard]]
bool ownsLock()
const {
return _ownsLock; }
275 bool _ownsLock{
false};
276 bool _isShared{
false};
278 friend Lock& TestableMode::getLockObject();
282 std::map<boost::thread::id, std::string> _threadNames;
285 std::map<boost::thread::id, pid_t> _threadPThreadId;
288 std::mutex _threadNamesMutex;
294 std::string threadName(
const boost::thread::id& threadId = boost::this_thread::get_id());
304 pid_t pthreadId(
const boost::thread::id& threadId = boost::this_thread::get_id());
316 TestableMode::AccessorPair<T> TestableMode::decorate(
317 AccessorPair<T> other,
const VariableNetworkNode& producer,
const VariableNetworkNode& consumer) {
323 <<
" Decorating pair " << producer.getQualifiedName() <<
"[" << other.first->getId() <<
"] -> "
324 << consumer.getQualifiedName() <<
"[" << other.second->getId() <<
"]";
327 size_t varId = detail::TestableMode::getNextVariableId();
329 AccessorPair<T> result;
331 if(producer.getDirection().withReturn) {
332 varIdReturn = detail::TestableMode::getNextVariableId();
336 if(!producer.getDirection().withReturn) {
337 result.first = boost::make_shared<AccessorDecorator<T>>(*
this, other.first,
false,
true, varId, varId);
338 result.second = boost::make_shared<AccessorDecorator<T>>(*
this, other.second,
true,
false, varId, varId);
341 result.first = boost::make_shared<AccessorDecorator<T>>(*
this, other.first,
true,
true, varIdReturn, varId);
342 result.second = boost::make_shared<AccessorDecorator<T>>(*
this, other.second,
true,
true, varId, varIdReturn);
346 auto& variable = _variables.at(varId);
347 variable.name =
"Internal:" + producer.getQualifiedName();
349 variable.name +=
"->" + consumer.getQualifiedName();
351 if(producer.getDirection().withReturn) {
352 auto& returnVariable = _variables.at(varIdReturn);
353 returnVariable.name = variable.name +
" (return)";
362 boost::shared_ptr<NDRegisterAccessor<T>> TestableMode::decorate(
363 boost::shared_ptr<NDRegisterAccessor<T>> other, DecoratorType direction,
const std::string& name,
size_t varId) {
369 <<
" Decorating single " << (direction == DecoratorType::READ ?
"consumer " :
"feeder ") << name <<
"["
370 << other->getId() <<
"]";
373 varId = detail::TestableMode::getNextVariableId();
376 _variables[varId].processVariable = other;
377 if(not name.empty()) {
378 _variables.at(varId).name = name;
381 auto pvarDec = boost::make_shared<AccessorDecorator<T>>(
382 *
this, other, direction == DecoratorType::READ, direction == DecoratorType::WRITE, varId, varId);
393 template<
typename UserType>
394 TestableMode::AccessorDecorator<UserType>::AccessorDecorator(detail::TestableMode& testableMode,
395 boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor,
bool handleRead,
bool handleWrite,
396 size_t variableIdRead,
size_t variableIdWrite)
397 :
ChimeraTK::NDRegisterAccessorDecorator<UserType>(accessor), _handleRead(handleRead), _handleWrite(handleWrite),
398 _variableIdRead(variableIdRead), _variableIdWrite(variableIdWrite), _testableMode(testableMode) {
399 assert(_variableIdRead != 0);
400 assert(_variableIdWrite != 0);
403 if(this->isReadable() && handleRead) {
404 _testableMode._variables[_variableIdRead].processVariable = accessor;
405 assert(accessor->getAccessModeFlags().has(AccessMode::wait_for_new_data));
410 auto bidir = boost::dynamic_pointer_cast<BidirectionalProcessArray<UserType>>(accessor);
412 bidir->setValueRejectCallback([
this] { decrementCounter(); });
415 assert(!(handleRead && handleWrite));
421 template<
typename UserType>
424 bool TestableMode::AccessorDecorator<UserType>::doWriteTransfer(ChimeraTK::VersionNumber versionNumber) {
426 return _target->writeTransfer(versionNumber);
428 return accountForWriteOperation([
this, versionNumber]() {
return _target->writeTransfer(versionNumber); });
433 template<
typename UserType>
436 bool TestableMode::AccessorDecorator<UserType>::doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) {
438 return _target->writeTransferDestructively(versionNumber);
441 return accountForWriteOperation(
442 [
this, versionNumber]() {
return _target->writeTransferDestructively(versionNumber); });
447 template<
typename UserType>
448 void TestableMode::AccessorDecorator<UserType>::releaseLock() {
449 if(_testableMode.testLock()) {
450 _testableMode.unlock(
"doReadTransfer " + this->getName());
456 template<
typename UserType>
457 void TestableMode::AccessorDecorator<UserType>::doPreRead(TransferType type) {
458 _target->preRead(type);
461 if(_handleRead && type == TransferType::read &&
462 TransferElement::_accessModeFlags.has(AccessMode::wait_for_new_data)) {
469 template<
typename UserType>
470 void TestableMode::AccessorDecorator<UserType>::obtainLockAndDecrementCounter(
bool hasNewData) {
471 if(!_testableMode.testLock()) {
472 _testableMode.lock(
"doReadTransfer " + this->getName(),
true);
477 auto& variable = _testableMode._variables.at(_variableIdRead);
478 if(variable.counter > 0) {
479 assert(_testableMode._counter > 0);
480 --_testableMode._counter;
483 <<
"TestableModeAccessorDecorator[name='" << this->getName() <<
"', id=" << _variableIdRead
484 <<
"]: testableMode.counter decreased, now at value " << _testableMode._counter <<
" / " << variable.counter;
488 <<
"TestableModeAccessorDecorator[name='" << this->getName() <<
"', id=" << _variableIdRead
489 <<
"]: testableMode.counter NOT decreased, was already at value " << _testableMode._counter <<
" / "
490 << variable.counter <<
"\n"
497 template<
typename UserType>
498 void TestableMode::AccessorDecorator<UserType>::decrementCounter() {
499 obtainLockAndDecrementCounter(
true);
505 template<
typename UserType>
506 void TestableMode::AccessorDecorator<UserType>::doPostRead(TransferType type,
bool hasNewData) {
508 obtainLockAndDecrementCounter(hasNewData);
510 ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPostRead(type, hasNewData);
513 template<
typename UserType>
514 bool TestableMode::AccessorDecorator<UserType>::accountForWriteOperation(
515 const std::function<
bool(
void)>& writeOperation) {
516 bool dataLost =
false;
517 if(!_testableMode.testLock()) {
519 _testableMode.lock(
"write " + this->getName(),
true);
524 _testableMode._variables.at(_variableIdWrite).counter++;
525 _testableMode._counter++;
527 dataLost = writeOperation();
531 _testableMode._variables.at(_variableIdWrite).counter--;
532 _testableMode._counter--;
537 <<
"TestableModeAccessorDecorator::write[name='" << this->getName() <<
"', id=" << _variableIdWrite
538 <<
"]: testableMode.counter increased, now at value " << _testableMode._counter;
542 <<
"TestableModeAccessorDecorator::write[name='" << this->getName() <<
"', id=" << _variableIdWrite
543 <<
"]: 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.