diff --git a/include/FeedingFanOut.h b/include/FeedingFanOut.h index e66a9255d642a6ad70dce11a2d77b831d06f073d..b6ac2d04fd0adc22be7e5a65081084f0889b069c 100644 --- a/include/FeedingFanOut.h +++ b/include/FeedingFanOut.h @@ -23,9 +23,10 @@ namespace ChimeraTK { public: FeedingFanOut(std::string const &name, std::string const &unit, std::string const &description, - size_t numberOfElements) + size_t numberOfElements, bool withReturn) : FanOut<UserType>(boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>()), - ChimeraTK::NDRegisterAccessor<UserType>(name, unit, description) + ChimeraTK::NDRegisterAccessor<UserType>("FeedingFanOut:"+name, unit, description), + _withReturn(withReturn) { ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D.resize(1); ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0].resize(numberOfElements); @@ -33,9 +34,7 @@ namespace ChimeraTK { /** 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) override { - if(!slave->isWriteable()) { - throw ChimeraTK::logic_error("FeedingFanOut::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() != 1 || slave->getNumberOfSamples() != this->getNumberOfSamples() ) ) { @@ -43,11 +42,35 @@ namespace ChimeraTK { what += "' with incompatible array shape! Name of fan out: '" + this->getName() + "'"; throw ChimeraTK::logic_error(what.c_str()); } + + // make sure slave is writeable + if(!slave->isWriteable()) { + throw ChimeraTK::logic_error("FeedingFanOut::addSlave() has been called with a receiving implementation!"); + } + + // handle return channels + if(_withReturn) { + if(slave->isReadable()) { + if(_hasReturnSlave) { + throw ChimeraTK::logic_error("FeedingFanOut: Cannot add multiple slaves with return channel!"); + } + _hasReturnSlave = true; + _returnSlave = slave; + } + } + else { + if(slave->isReadable()) { + throw ChimeraTK::logic_error("FeedingFanOut: Cannot add slaves with return channel to FeedingFanOuts " + "without return channel!"); + } + } + + // add the slave FanOut<UserType>::slaves.push_back(slave); } bool isReadable() const override { - return false; + return _withReturn; } bool isReadOnly() const override { @@ -59,27 +82,45 @@ namespace ChimeraTK { } void doReadTransfer() override { - throw ChimeraTK::logic_error("Read operation called on write-only variable."); + if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); + _returnSlave->doReadTransfer(); } bool doReadTransferNonBlocking() override { - throw ChimeraTK::logic_error("Read operation called on write-only variable."); + if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); + return _returnSlave->doReadTransferNonBlocking(); } bool doReadTransferLatest() override { - throw ChimeraTK::logic_error("Read operation called on write-only variable."); + if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); + return _returnSlave->doReadTransferLatest(); } void doPreRead() override { - throw ChimeraTK::logic_error("Read operation called on write-only variable."); + if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); + _returnSlave->accessChannel(0).swap(ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); + _returnSlave->preRead(); } void doPostRead() override { - throw ChimeraTK::logic_error("Read operation called on write-only variable."); + if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); + assert(_hasReturnSlave); + _returnSlave->postRead(); + _returnSlave->accessChannel(0).swap(ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]); + // distribute return-channel update to the other slaves + for(auto &slave : FanOut<UserType>::slaves) { // send out copies to slaves + if(slave == _returnSlave) continue; + if(slave->getNumberOfSamples() != 0) { // do not send copy if no data is expected (e.g. trigger) + slave->accessChannel(0) = ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D[0]; + } + slave->write(_returnSlave->getVersionNumber()); + } + } ChimeraTK::TransferFuture doReadTransferAsync() override { - throw ChimeraTK::logic_error("Read operation called on write-only variable."); + if(!_withReturn) throw ChimeraTK::logic_error("Read operation called on write-only variable."); + return {_returnSlave->doReadTransferAsync(), this}; } void doPreWrite() override { @@ -133,10 +174,26 @@ namespace ChimeraTK { /// @todo implement properly? } - AccessModeFlags getAccessModeFlags() const override { return {}; } + AccessModeFlags getAccessModeFlags() const override { return {AccessMode::wait_for_new_data}; } VersionNumber getVersionNumber() const override { return FanOut<UserType>::slaves.front()->getVersionNumber(); } + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> getReturnSlave() { + return _returnSlave; + } + + protected: + + /// Flag whether this FeedingFanOut has a return channel. Is specified in the constructor + bool _withReturn; + + /// Used if _withReturn is true: flag whether the corresponding slave with the return channel has already been + /// added. + bool _hasReturnSlave{false}; + + /// The slave with return channel + boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> _returnSlave; + }; } /* namespace ChimeraTK */ diff --git a/include/TestableModeAccessorDecorator.h b/include/TestableModeAccessorDecorator.h index 47596f989d4839cae50404cfcf3cc59c71b83f37..fb07c0af739635e30b33647a804405faae258b16 100644 --- a/include/TestableModeAccessorDecorator.h +++ b/include/TestableModeAccessorDecorator.h @@ -11,6 +11,7 @@ #include <ChimeraTK/NDRegisterAccessorDecorator.h> #include "Application.h" +#include "FeedingFanOut.h" namespace ChimeraTK { @@ -37,7 +38,9 @@ namespace ChimeraTK { // 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();}); + bidir->setValueRejectCallback([this]{ + decrementCounter(); + }); } } diff --git a/include/TriggerFanOut.h b/include/TriggerFanOut.h index 7799bc6798b034adb1d798d50d4c95735c463efc..26431256baff31646f8f57d0f8ff304b7af1c986 100644 --- a/include/TriggerFanOut.h +++ b/include/TriggerFanOut.h @@ -54,7 +54,7 @@ namespace ChimeraTK { assert(feedingNode.get() != nullptr); transferGroup.addAccessor(feedingNode); auto feedingFanOut = boost::make_shared<FeedingFanOut<UserType>>( feedingNode->getName(), feedingNode->getUnit(), - feedingNode->getDescription(), feedingNode->getNumberOfSamples() ); + feedingNode->getDescription(), feedingNode->getNumberOfSamples(), false ); // in TriggerFanOuts we cannot have return channels boost::fusion::at_key<UserType>(fanOutMap.table)[feedingNode] = feedingFanOut; return feedingFanOut; } diff --git a/tests/executables_src/testBidirectionalVariables.cc b/tests/executables_src/testBidirectionalVariables.cc index 73af76609c16f5891b51157271628eed19fc04e3..f11562f8f6c8e0f780b4991f741a4b39fcb459cf 100644 --- a/tests/executables_src/testBidirectionalVariables.cc +++ b/tests/executables_src/testBidirectionalVariables.cc @@ -101,12 +101,9 @@ BOOST_AUTO_TEST_CASE(testNormalOperation) { TestApplication app; - //app.a.connectTo(app.cs); - //app.b.connectTo(app.cs); - app.cs("var1") >> app.a.var1; - app.a.var2 >> app.b.var2; - app.cs("max") >> app.b.max; - app.cs("var3") >> app.b.var3; + // the connections will result in a FeedingFanOut for var2, as it is connected to the control system as well + app.a.connectTo(app.cs); + app.b.connectTo(app.cs); ctk::TestFacility test; app.initialise(); app.run();