Newer
Older
Martin Christoph Hierholzer
committed
/*
* Application.cc
*
* Created on: Jun 10, 2016
* Author: Martin Hierholzer
*/
Martin Christoph Hierholzer
committed
#include <exception>
#include <fstream>
Martin Christoph Hierholzer
committed
#include <boost/fusion/container/map.hpp>
#include <ChimeraTK/BackendFactory.h>
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
#include "Application.h"
#include "ApplicationModule.h"
#include "ArrayAccessor.h"
#include "ConstantAccessor.h"
Martin Christoph Hierholzer
committed
#include "ConsumingFanOut.h"
#include "DebugPrintAccessorDecorator.h"
#include "DeviceModule.h"
#include "FeedingFanOut.h"
Martin Christoph Hierholzer
committed
#include "ScalarAccessor.h"
Martin Christoph Hierholzer
committed
#include "TestableModeAccessorDecorator.h"
#include "ThreadedFanOut.h"
#include "TriggerFanOut.h"
#include "VariableNetworkGraphDumpingVisitor.h"
#include "VariableNetworkNode.h"
#include "Visitor.h"
#include "ExceptionHandlingDecorator.h"
Martin Christoph Hierholzer
committed
using namespace ChimeraTK;
Martin Christoph Hierholzer
committed
std::mutex Application::testableMode_mutex;
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Application::Application(const std::string& name) : ApplicationBase(name), EntityOwner(name, "") {
Martin Christoph Hierholzer
committed
// check if the application name has been set
Martin Christoph Hierholzer
committed
shutdown();
throw ChimeraTK::logic_error("Error: An instance of Application must have its applicationName set.");
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// check if application name contains illegal characters
std::string legalChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_";
bool nameContainsIllegalChars = name.find_first_not_of(legalChars) != std::string::npos;
if(nameContainsIllegalChars) {
Martin Christoph Hierholzer
committed
shutdown();
throw ChimeraTK::logic_error("Error: The application name may only contain "
"alphanumeric characters and underscores.");
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::initialise() {
if(initialiseCalled) {
throw ChimeraTK::logic_error("Application::initialise() was already called before.");
}
// call the user-defined defineConnections() function which describes the structure of the application
Martin Christoph Hierholzer
committed
defineConnections();
for(auto& module : getSubmoduleListRecursive()) {
module->defineConnections();
}
// call defineConnections() for alldevice modules
for(auto& devModule : deviceModuleMap) {
devModule.second->defineConnections();
}
Martin Christoph Hierholzer
committed
// connect any unconnected accessors with constant values
processUnconnectedNodes();
// realise the connections between variable accessors as described in the
// initialise() function
Martin Christoph Hierholzer
committed
makeConnections();
Martin Christoph Hierholzer
committed
// set flag to prevent further calls to this function and to prevent definition of additional connections.
initialiseCalled = true;
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
/** Functor class to create a constant for otherwise unconnected variables,
* suitable for boost::fusion::for_each(). */
namespace {
struct CreateConstantForUnconnectedVar {
/// @todo test unconnected variables for all types!
CreateConstantForUnconnectedVar(const std::type_info& typeInfo, bool makeFeeder, size_t length)
: _typeInfo(typeInfo), _makeFeeder(makeFeeder), _length(length) {}
template<typename PAIR>
void operator()(PAIR&) const {
if(typeid(typename PAIR::first_type) != _typeInfo) return;
theNode = VariableNetworkNode::makeConstant<typename PAIR::first_type>(
_makeFeeder, typename PAIR::first_type(), _length);
done = true;
}
const std::type_info& _typeInfo;
bool _makeFeeder;
size_t _length;
mutable bool done{false};
mutable VariableNetworkNode theNode;
};
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::processUnconnectedNodes() {
for(auto& module : getSubmoduleListRecursive()) {
for(auto& accessor : module->getAccessorList()) {
if(!accessor.hasOwner()) {
if(enableUnconnectedVariablesWarning) {
std::cerr << "*** Warning: Variable '" << accessor.getQualifiedName()
<< "' is not connected. " // LCOV_EXCL_LINE
"Reading will always result in 0, writing will be ignored."
<< std::endl; // LCOV_EXCL_LINE
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
networkList.emplace_back();
Martin Christoph Hierholzer
committed
networkList.back().addNode(accessor);
Martin Christoph Hierholzer
committed
bool makeFeeder = !(networkList.back().hasFeedingNode());
Martin Christoph Hierholzer
committed
size_t length = accessor.getNumberOfElements();
auto callable = CreateConstantForUnconnectedVar(accessor.getValueType(), makeFeeder, length);
Martin Christoph Hierholzer
committed
boost::fusion::for_each(ChimeraTK::userTypeMap(), std::ref(callable));
assert(callable.done);
constantList.emplace_back(callable.theNode);
Martin Christoph Hierholzer
committed
networkList.back().addNode(constantList.back());
}
}
}
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::checkConnections() {
// check all networks for validity
Martin Christoph Hierholzer
committed
network.check();
}
Martin Christoph Hierholzer
committed
// check if all accessors are connected
// note: this in principle cannot happen, since processUnconnectedNodes() is
// called before
for(auto& module : getSubmoduleListRecursive()) {
for(auto& accessor : module->getAccessorList()) {
if(!accessor.hasOwner()) {
throw ChimeraTK::logic_error("The accessor '" + accessor.getName() + "' of the module '" +
module->getName() + // LCOV_EXCL_LINE
"' was not connected!"); // LCOV_EXCL_LINE
Martin Christoph Hierholzer
committed
}
}
}
}
/*********************************************************************************************************************/
void Application::run() {
Martin Christoph Hierholzer
committed
assert(applicationName != "");
Martin Christoph Hierholzer
committed
if(testableMode) {
if(!testFacilityRunApplicationCalled) {
throw ChimeraTK::logic_error(
"Testable mode enabled but Application::run() called directly. Call TestFacility::runApplication() instead.");
}
}
if(runCalled) {
throw ChimeraTK::logic_error("Application::run() has already been called before.");
}
runCalled = true;
Martin Killenberg
committed
// set all initial version numbers in the modules to the same value
for(auto& module : getSubmoduleListRecursive()) {
if(module->getModuleType() != ModuleType::ApplicationModule) continue;
Martin Christoph Hierholzer
committed
module->setCurrentVersionNumber(getStartVersion());
Martin Killenberg
committed
}
// prepare the modules
for(auto& module : getSubmoduleListRecursive()) {
module->prepare();
}
Martin Killenberg
committed
for(auto& deviceModule : deviceModuleMap) {
deviceModule.second->prepare();
// Switch life-cycle state to run
lifeCycleState = LifeCycleState::run;
Martin Christoph Hierholzer
committed
// start the necessary threads for the FanOuts etc.
for(auto& internalModule : internalModuleList) {
internalModule->activate();
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Killenberg
committed
for(auto& deviceModule : deviceModuleMap) {
deviceModule.second->run();
Martin Christoph Hierholzer
committed
// start the threads for the modules
for(auto& module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
module->run();
}
// When in testable mode, wait for all modules to report that they have reched the testable mode.
// We have to start all module threads first because some modules might only send the initial
// values in their main loop, and following modules need them to enter testable mode.
// just a small helper lambda to avoid code repetition
auto waitForTestableMode = [](EntityOwner* module) {
while(!module->hasReachedTestableMode()) {
Application::getInstance().testableModeUnlock("releaseForReachTestableMode");
usleep(100);
Application::getInstance().testableModeLock("acquireForReachTestableMode");
}
};
if(Application::getInstance().isTestableModeEnabled()) {
for(auto& internalModule : internalModuleList) {
waitForTestableMode(internalModule.get());
}
for(auto& deviceModule : deviceModuleMap) {
waitForTestableMode(deviceModule.second);
}
for(auto& module : getSubmoduleListRecursive()) {
waitForTestableMode(module);
}
}
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::shutdown() {
// switch life-cycle state
lifeCycleState = LifeCycleState::shutdown;
// first allow to run the application threads again, if we are in testable
// mode
if(testableMode && testableModeTestLock()) {
Martin Christoph Hierholzer
committed
testableModeUnlock("shutdown");
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// deactivate the FanOuts first, since they have running threads inside
// accessing the modules etc. (note: the modules are members of the
// Application implementation and thus get destroyed after this destructor)
for(auto& internalModule : internalModuleList) {
internalModule->deactivate();
Martin Christoph Hierholzer
committed
}
// next deactivate the modules, as they have running threads inside as well
for(auto& module : getSubmoduleListRecursive()) {
Martin Christoph Hierholzer
committed
module->terminate();
}
Martin Killenberg
committed
for(auto& deviceModule : deviceModuleMap) {
deviceModule.second->terminate();
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::generateXML() {
assert(applicationName != "");
Martin Christoph Hierholzer
committed
// define the connections
defineConnections();
for(auto& module : getSubmoduleListRecursive()) {
module->defineConnections();
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// create connections for exception handling
Martin Killenberg
committed
for(auto& devModule : deviceModuleMap) {
devModule.second->defineConnections();
Martin Christoph Hierholzer
committed
}
// also search for unconnected nodes - this is here only executed to print the
// warnings
processUnconnectedNodes();
// finalise connections: decide still-undecided details, in particular for
// control-system and device varibales, which get created "on the fly".
finaliseNetworks();
XMLGeneratorVisitor visitor;
visitor.dispatch(*this);
visitor.save(applicationName + ".xml");
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
VariableNetwork& Application::connect(VariableNetworkNode a, VariableNetworkNode b) {
// if one of the nodes has the value type AnyType, set it to the type of the
// other if both are AnyType, nothing changes.
if(a.getValueType() == typeid(AnyType)) {
Martin Christoph Hierholzer
committed
a.setValueType(b.getValueType());
}
else if(b.getValueType() == typeid(AnyType)) {
Martin Christoph Hierholzer
committed
b.setValueType(a.getValueType());
}
// if one of the nodes has not yet a defined number of elements, set it to the
// number of elements of the other. if both are undefined, nothing changes.
a.setNumberOfElements(b.getNumberOfElements());
}
else if(b.getNumberOfElements() == 0) {
b.setNumberOfElements(a.getNumberOfElements());
}
if(a.getNumberOfElements() != b.getNumberOfElements()) {
Martin Christoph Hierholzer
committed
std::stringstream what;
what << "*** ERROR: Cannot connect array variables with difference number "
"of elements!"
<< std::endl;
Martin Christoph Hierholzer
committed
what << "Node A:" << std::endl;
a.dump(what);
what << "Node B:" << std::endl;
b.dump(what);
throw ChimeraTK::logic_error(what.str());
// if both nodes already have an owner, we are either already done (same
// owners) or we need to try to merge the networks
if(a.hasOwner() && b.hasOwner()) {
if(&(a.getOwner()) != &(b.getOwner())) {
auto& networkToMerge = b.getOwner();
bool success = a.getOwner().merge(networkToMerge);
std::stringstream what;
what << "*** ERROR: Trying to connect two nodes which are already part "
"of different networks, and merging these"
" networks is not possible (cannot have two non-control-system "
"or two control-system feeders)!"
what << "Node A:" << std::endl;
a.dump(what);
what << "Node B:" << std::endl;
b.dump(what);
what << "Owner of node A:" << std::endl;
what << "Owner of node B:" << std::endl;
throw ChimeraTK::logic_error(what.str());
}
for(auto n = networkList.begin(); n != networkList.end(); ++n) {
if(&*n == &networkToMerge) {
networkList.erase(n);
break;
}
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// add b to the existing network of a
Martin Christoph Hierholzer
committed
a.getOwner().addNode(b);
}
Martin Christoph Hierholzer
committed
// add a to the existing network of b
Martin Christoph Hierholzer
committed
b.getOwner().addNode(a);
}
Martin Christoph Hierholzer
committed
// create new network
Martin Christoph Hierholzer
committed
else {
networkList.emplace_back();
networkList.back().addNode(a);
networkList.back().addNode(b);
}
Martin Christoph Hierholzer
committed
return a.getOwner();
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
template<typename UserType>
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> Application::createDeviceVariable(
Martin Killenberg
committed
VariableNetworkNode const& node) {
auto deviceAlias = node.getDeviceAlias();
auto registerName = node.getRegisterName();
auto direction = node.getDirection();
auto mode = node.getMode();
auto nElements = node.getNumberOfElements();
// Device opens in DeviceModule
if(deviceMap.count(deviceAlias) == 0) {
deviceMap[deviceAlias] = ChimeraTK::BackendFactory::getInstance().createBackend(deviceAlias);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// use wait_for_new_data mode if push update mode was requested
// Feeding to the network means reading from a device to feed it into the network.
ChimeraTK::AccessModeFlags flags{};
if(mode == UpdateMode::push && direction.dir == VariableDirection::feeding) flags = {AccessMode::wait_for_new_data};
Martin Christoph Hierholzer
committed
// obtain the register accessor from the device
auto accessor = deviceMap[deviceAlias]->getRegisterAccessor<UserType>(registerName, nElements, 0, flags);
// Receiving accessors should be faulty after construction,
// see data validity propagation spec 2.6.1
if(node.getDirection().dir == VariableDirection::feeding) {
accessor->setDataValidity(DataValidity::faulty);
}
return boost::make_shared<ExceptionHandlingDecorator<UserType>>(accessor, node);
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
template<typename UserType>
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> Application::createProcessVariable(
VariableNetworkNode const& node) {
Martin Christoph Hierholzer
committed
// determine the SynchronizationDirection
SynchronizationDirection dir;
Martin Christoph Hierholzer
committed
dir = SynchronizationDirection::bidirectional;
}
else if(node.getDirection().dir == VariableDirection::feeding) {
Martin Christoph Hierholzer
committed
dir = SynchronizationDirection::controlSystemToDevice;
Martin Christoph Hierholzer
committed
dir = SynchronizationDirection::deviceToControlSystem;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
AccessModeFlags flags = {};
if(node.getDirection().dir == VariableDirection::consuming) { // Application-to-controlsystem must be
// push-type
Martin Christoph Hierholzer
committed
flags = {AccessMode::wait_for_new_data};
Martin Christoph Hierholzer
committed
if(node.getOwner().getConsumingNodes().size() == 1 &&
node.getOwner().getConsumingNodes().front().getType() == NodeType::Application) {
// exactly one consumer which is an ApplicationModule input: decide flag depending on input type
auto consumer = node.getOwner().getConsumingNodes().front();
if(consumer.getMode() == UpdateMode::push) flags = {AccessMode::wait_for_new_data};
Martin Christoph Hierholzer
committed
}
else {
// multiple consumers (or a single Device/CS consumer) result in a ThreadedFanOut which requires push-type input
flags = {AccessMode::wait_for_new_data};
Martin Christoph Hierholzer
committed
}
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// create the ProcessArray for the proper UserType
auto pvar = _processVariableManager->createProcessArray<UserType>(dir, node.getPublicName(),
Martin Christoph Hierholzer
committed
node.getNumberOfElements(), node.getOwner().getUnit(), node.getOwner().getDescription(), {}, 3, flags);
Martin Christoph Hierholzer
committed
assert(pvar->getName() != "");
Martin Christoph Hierholzer
committed
// create variable ID
Martin Christoph Hierholzer
committed
auto varId = getNextVariableId();
pvIdMap[pvar->getUniqueId()] = varId;
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// Decorate the process variable if testable mode is enabled and this is the receiving end of the variable (feeding
// to the network), or a bidirectional consumer. Also don't decorate, if the mode is polling. Instead flag the
// variable to be polling, so the TestFacility is aware of this.
if(testableMode) {
if(node.getDirection().dir == VariableDirection::feeding) {
// The transfer mode of this process variable is considered to be polling,
// if only one consumer exists and this consumer is polling. Reason:
// mulitple consumers will result in the use of a FanOut, so the
// communication up to the FanOut will be push-type, even if all consumers
// are poll-type.
/// @todo Check if this is true!
auto mode = UpdateMode::push;
if(node.getOwner().countConsumingNodes() == 1) {
if(node.getOwner().getConsumingNodes().front().getMode() == UpdateMode::poll) mode = UpdateMode::poll;
}
Martin Christoph Hierholzer
committed
if(mode != UpdateMode::poll) {
auto pvarDec = boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvar, true, false, varId, varId);
testableMode_names[varId] = "ControlSystem:" + node.getPublicName();
return pvarDec;
}
else {
testableMode_isPollMode[varId] = true;
}
}
else if(node.getDirection().withReturn) {
// Return channels are always push. The decorator must handle only reads on the return channel, since writes into
// the control system do not block the testable mode.
auto pvarDec = boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvar, true, false, varId, varId);
testableMode_names[varId] = "ControlSystem:" + node.getPublicName();
return pvarDec;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// return the process variable
return pvar;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
std::pair<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>,
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>>
Application::createApplicationVariable(VariableNetworkNode const& node, VariableNetworkNode const& consumer) {
Martin Christoph Hierholzer
committed
// obtain the meta data
size_t nElements = node.getNumberOfElements();
std::string name = node.getName();
Martin Christoph Hierholzer
committed
assert(name != "");
Martin Christoph Hierholzer
committed
AccessModeFlags flags = {};
if(consumer.getType() != NodeType::invalid) {
if(consumer.getMode() == UpdateMode::push) flags = {AccessMode::wait_for_new_data};
}
else {
if(node.getMode() == UpdateMode::push) flags = {AccessMode::wait_for_new_data};
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// create the ProcessArray for the proper UserType
std::pair<boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>,
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>>>
if(consumer.getType() != NodeType::invalid)
assert(node.getDirection().withReturn == consumer.getDirection().withReturn);
if(!node.getDirection().withReturn) {
pvarPair =
createSynchronizedProcessArray<UserType>(nElements, name, node.getUnit(), node.getDescription(), {}, 3, flags);
pvarPair = createBidirectionalSynchronizedProcessArray<UserType>(
nElements, name, node.getUnit(), node.getDescription(), {}, 3, flags);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
assert(pvarPair.first->getName() != "");
assert(pvarPair.second->getName() != "");
Martin Christoph Hierholzer
committed
// create variable IDs
Martin Christoph Hierholzer
committed
size_t varId = getNextVariableId();
size_t varIdReturn;
if(node.getDirection().withReturn) varIdReturn = getNextVariableId();
// decorate the process variable if testable mode is enabled and mode is push-type
if(testableMode && flags.has(AccessMode::wait_for_new_data)) {
boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.first, false, true, varId, varId);
boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.second, true, false, varId, varId);
}
else {
boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.first, true, true, varIdReturn, varId);
boost::make_shared<TestableModeAccessorDecorator<UserType>>(pvarPair.second, true, true, varId, varIdReturn);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// put the decorators into the list
testableMode_names[varId] = "Internal:" + node.getQualifiedName();
if(consumer.getType() != NodeType::invalid) {
testableMode_names[varId] += "->" + consumer.getQualifiedName();
Martin Christoph Hierholzer
committed
}
if(node.getDirection().withReturn) testableMode_names[varIdReturn] = testableMode_names[varId] + " (return)";
Martin Christoph Hierholzer
committed
}
// if debug mode was requested for either node, decorate both accessors
if(debugMode_variableList.count(node.getUniqueId()) ||
(consumer.getType() != NodeType::invalid && debugMode_variableList.count(consumer.getUniqueId()))) {
if(consumer.getType() != NodeType::invalid) {
Martin Christoph Hierholzer
committed
assert(node.getDirection().dir == VariableDirection::feeding);
assert(consumer.getDirection().dir == VariableDirection::consuming);
boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.first, node.getQualifiedName());
boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.second, consumer.getQualifiedName());
}
else {
boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.first, node.getQualifiedName());
boost::make_shared<DebugPrintAccessorDecorator<UserType>>(pvarPair.second, node.getQualifiedName());
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// return the pair
return pvarPair;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::makeConnections() {
// finalise connections: decide still-undecided details, in particular for
// control-system and device varibales, which get created "on the fly".
Martin Christoph Hierholzer
committed
// apply optimisations
// note: checks may not be run before since sometimes networks may only be
// valid after optimisations
optimiseConnections();
Martin Christoph Hierholzer
committed
// run checks
checkConnections();
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// make the connections for all networks
Martin Christoph Hierholzer
committed
makeConnectionsForNetwork(network);
}
}
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
void Application::finaliseNetworks() {
// check for control system variables which should be made bidirectional
size_t nBidir = network.getFeedingNode().getDirection().withReturn ? 1 : 0;
for(auto& consumer : network.getConsumingNodes()) {
if(consumer.getDirection().withReturn) ++nBidir;
continue; // only if there is exactly one node with return channel we need
// to guess its peer
if(network.getFeedingNode().getType() != NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
// only a feeding control system variable can be made bidirectional
continue;
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
network.getFeedingNode().setDirection({VariableDirection::feeding, true});
Martin Christoph Hierholzer
committed
// check for networks which have an external trigger but should be triggered by pollling consumer
for(auto& network : networkList) {
if(network.getTriggerType() == VariableNetwork::TriggerType::external) {
size_t pollingComsumers{0};
for(auto& consumer : network.getConsumingNodes()) {
if(consumer.getMode() == UpdateMode::poll) ++pollingComsumers;
}
if(pollingComsumers == 1) {
network.getFeedingNode().removeExternalTrigger();
}
}
}
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
void Application::optimiseConnections() {
// list of iterators of networks to be removed from the networkList after the
// merge operation
std::list<VariableNetwork*> deleteNetworks;
// search for networks with the same feeder
for(auto it1 = networkList.begin(); it1 != networkList.end(); ++it1) {
for(auto it2 = it1; it2 != networkList.end(); ++it2) {
if(it1 == it2) continue;
auto feeder1 = it1->getFeedingNode();
auto feeder2 = it2->getFeedingNode();
// this optimisation is only necessary for device-type nodes, since
// application and control-system nodes will automatically create merged
// networks when having the same feeder
/// @todo check if this assumtion is true! control-system nodes can be
/// created with different types, too!
if(feeder1.getType() != NodeType::Device || feeder2.getType() != NodeType::Device) continue;
// check if referrring to same register
if(feeder1.getDeviceAlias() != feeder2.getDeviceAlias()) continue;
if(feeder1.getRegisterName() != feeder2.getRegisterName()) continue;
// check if directions are the same
if(feeder1.getDirection() != feeder2.getDirection()) continue;
// check if value types and number of elements are compatible
if(feeder1.getValueType() != feeder2.getValueType()) continue;
if(feeder1.getNumberOfElements() != feeder2.getNumberOfElements()) continue;
// check if transfer mode is the same
if(feeder1.getMode() != feeder2.getMode()) continue;
// check if triggers are compatible, if present
if(feeder1.hasExternalTrigger() != feeder2.hasExternalTrigger()) continue;
if(feeder1.hasExternalTrigger()) {
if(feeder1.getExternalTrigger() != feeder2.getExternalTrigger()) continue;
}
// everything should be compatible at this point: merge the networks. We
// will merge the network of the outer loop into the network of the inner
// loop, since the network of the outer loop will not be found a second
// time in the inner loop.
for(auto consumer : it1->getConsumingNodes()) {
consumer.clearOwner();
it2->addNode(consumer);
}
// if trigger present, remove corresponding trigger receiver node from the
// trigger network
Martin Christoph Hierholzer
committed
feeder1.getExternalTrigger().getOwner().removeNodeToTrigger(it1->getFeedingNode());
Martin Christoph Hierholzer
committed
}
// schedule the outer loop network for deletion and stop processing it
deleteNetworks.push_back(&(*it1));
break;
}
}
// remove networks from the network list
networkList.remove(*net);
}
}
/*********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void Application::dumpConnections(std::ostream& stream) { // LCOV_EXCL_LINE
stream << "==== List of all variable connections of the current Application ====" << std::endl; // LCOV_EXCL_LINE
for(auto& network : networkList) { // LCOV_EXCL_LINE
network.dump("", stream); // LCOV_EXCL_LINE
} // LCOV_EXCL_LINE
stream << "=====================================================================" << std::endl; // LCOV_EXCL_LINE
Martin Christoph Hierholzer
committed
void Application::dumpConnectionGraph(const std::string& fileName) {
std::fstream file{fileName, std::ios_base::out};
VariableNetworkGraphDumpingVisitor visitor{file};
visitor.dispatch(*this);
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
Application::TypedMakeConnectionCaller::TypedMakeConnectionCaller(Application& owner, VariableNetwork& network)
: _owner(owner), _network(network) {}
Martin Christoph Hierholzer
committed
/*********************************************************************************************************************/
template<typename PAIR>
void Application::TypedMakeConnectionCaller::operator()(PAIR&) const {
if(typeid(typename PAIR::first_type) != _network.getValueType()) return;
Martin Christoph Hierholzer
committed
_owner.typedMakeConnection<typename PAIR::first_type>(_network);
done = true;
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
void Application::makeConnectionsForNetwork(VariableNetwork& network) {
Martin Christoph Hierholzer
committed
// if the network has been created already, do nothing
Martin Christoph Hierholzer
committed
Martin Christoph Hierholzer
committed
// if the trigger type is external, create the trigger first
if(network.getFeedingNode().hasExternalTrigger()) {
VariableNetwork& dependency = network.getFeedingNode().getExternalTrigger().getOwner();
if(!dependency.isCreated()) makeConnectionsForNetwork(dependency);
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
// defer actual network creation to templated function
Martin Christoph Hierholzer
committed
auto callable = TypedMakeConnectionCaller(*this, network);
Martin Christoph Hierholzer
committed
boost::fusion::for_each(ChimeraTK::userTypeMap(), std::ref(callable));
Martin Christoph Hierholzer
committed
assert(callable.done);
Martin Christoph Hierholzer
committed
// mark the network as created
network.markCreated();
Martin Christoph Hierholzer
committed
}
/*********************************************************************************************************************/
template<typename UserType>
void Application::typedMakeConnection(VariableNetwork& network) {
Christoph Kampmeyer
committed
if(enableDebugMakeConnections) {
std::cout << std::endl << "Executing typedMakeConnections for network:" << std::endl;
network.dump("", std::cout);
std::cout << std::endl;
}
try { // catch exceptions to add information about the failed network
bool connectionMade = false; // to check the logic...
size_t nNodes = network.countConsumingNodes() + 1;
auto feeder = network.getFeedingNode();
auto consumers = network.getConsumingNodes();
bool useExternalTrigger = network.getTriggerType() == VariableNetwork::TriggerType::external;
bool useFeederTrigger = network.getTriggerType() == VariableNetwork::TriggerType::feeder;
bool constantFeeder = feeder.getType() == NodeType::Constant;
// 1st case: the feeder requires a fixed implementation
if(feeder.hasImplementation() && !constantFeeder) {
Christoph Kampmeyer
committed
if(enableDebugMakeConnections) {
std::cout << " Creating fixed implementation for feeder '" << feeder.getName() << "'..." << std::endl;
}
Martin Killenberg
committed
// Create feeding implementation.
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> feedingImpl;
if(feeder.getType() == NodeType::Device) {
Martin Killenberg
committed
feedingImpl = createDeviceVariable<UserType>(feeder);
else if(feeder.getType() == NodeType::ControlSystem) {
feedingImpl = createProcessVariable<UserType>(feeder);
Martin Christoph Hierholzer
committed
}
else {
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
}
// if we just have two nodes, directly connect them
if(nNodes == 2 && !useExternalTrigger) {
Christoph Kampmeyer
committed
if(enableDebugMakeConnections) {
std::cout << " Setting up direct connection without external trigger." << std::endl;
}
bool needsFanOut{false};
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> consumingImpl;
auto consumer = consumers.front();
if(consumer.getType() == NodeType::Application) {
consumer.setAppAccessorImplementation(feedingImpl);
Christoph Kampmeyer
committed
connectionMade = true;
else if(consumer.getType() == NodeType::Device) {
Martin Killenberg
committed
consumingImpl = createDeviceVariable<UserType>(consumer);
// connect the Device with e.g. a ControlSystem node via a
// ThreadedFanOut
}
else if(consumer.getType() == NodeType::ControlSystem) {
consumingImpl = createProcessVariable<UserType>(consumer);
// connect the ControlSystem with e.g. a Device node via an
// ThreadedFanOut
}
else if(consumer.getType() == NodeType::TriggerReceiver) {
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(feedingImpl);
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
if(needsFanOut) {
assert(consumingImpl != nullptr);
auto consumerImplPair = ConsumerImplementationPairs<UserType>{{consumingImpl, consumer}};
auto fanOut = boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl, network, consumerImplPair);
internalModuleList.push_back(fanOut);
}
connectionMade = true;
else { /* !(nNodes == 2 && !useExternalTrigger) */
Christoph Kampmeyer
committed
if(enableDebugMakeConnections) {
std::cout << " Setting up triggered connection." << std::endl;
}
// create the right FanOut type
boost::shared_ptr<FanOut<UserType>> fanOut;
boost::shared_ptr<ConsumingFanOut<UserType>> consumingFanOut;
// Fanouts need to know the consumers on contruction, so we collect them first
auto consumerImplementationPairs = setConsumerImplementations<UserType>(feeder, consumers);
if(useExternalTrigger) {
Christoph Kampmeyer
committed
if(enableDebugMakeConnections) {
std::cout << " Using external trigger." << std::endl;
}
// if external trigger is enabled, use externally triggered threaded
// FanOut. Create one per external trigger impl.
void* triggerImplId = network.getExternalTriggerImpl().get();
Martin Killenberg
committed
auto triggerFanOut = triggerMap[triggerImplId];
if(!triggerFanOut) {
Martin Killenberg
committed
assert(deviceModuleMap.find(feeder.getDeviceAlias()) != deviceModuleMap.end());
Martin Christoph Hierholzer
committed
// create the trigger fan out and store it in the map and the internalModuleList
Martin Killenberg
committed
triggerFanOut = boost::make_shared<TriggerFanOut>(
network.getExternalTriggerImpl(), *deviceModuleMap[feeder.getDeviceAlias()], network);
Martin Killenberg
committed
triggerMap[triggerImplId] = triggerFanOut;
internalModuleList.push_back(triggerFanOut);
}
fanOut = triggerFanOut->addNetwork(feedingImpl, consumerImplementationPairs);
}
else if(useFeederTrigger) {
Christoph Kampmeyer
committed
if(enableDebugMakeConnections) {
std::cout << " Using trigger provided by the feeder." << std::endl;
}
// if the trigger is provided by the pushing feeder, use the treaded
// version of the FanOut to distribute new values immediately to all
// consumers. Depending on whether we have a return channel or not, pick
// the right implementation of the FanOut
boost::shared_ptr<ThreadedFanOut<UserType>> threadedFanOut;
if(!feeder.getDirection().withReturn) {
threadedFanOut =
boost::make_shared<ThreadedFanOut<UserType>>(feedingImpl, network, consumerImplementationPairs);
}
else {
threadedFanOut = boost::make_shared<ThreadedFanOutWithReturn<UserType>>(
feedingImpl, network, consumerImplementationPairs);
}
internalModuleList.push_back(threadedFanOut);
fanOut = threadedFanOut;
}
else {
Christoph Kampmeyer
committed
if(enableDebugMakeConnections) {
std::cout << " No trigger, using consuming fanout." << std::endl;
}
assert(network.hasApplicationConsumer()); // checkConnections should
// catch this
consumingFanOut = boost::make_shared<ConsumingFanOut<UserType>>(feedingImpl, consumerImplementationPairs);
fanOut = consumingFanOut;
Christoph Kampmeyer
committed
// TODO Is this correct? we already added all consumer as slaves in the fanout constructor.
// Maybe assert that we only have a single poll-type node (is there a check in checkConnections?)
for(auto consumer : consumers) {
if(consumer.getMode() == UpdateMode::poll) {
consumer.setAppAccessorImplementation<UserType>(consumingFanOut);
Martin Killenberg
committed
}
Martin Christoph Hierholzer
committed
}
connectionMade = true;
}
}
// 2nd case: the feeder does not require a fixed implementation
else if(!constantFeeder) { /* !feeder.hasImplementation() */
Christoph Kampmeyer
committed
if(enableDebugMakeConnections) {
std::cout << " Feeder '" << feeder.getName() << "' does not require a fixed implementation." << std::endl;
}
// we should be left with an application feeder node
if(feeder.getType() != NodeType::Application) {
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
}
assert(!useExternalTrigger);
// if we just have two nodes, directly connect them
if(nNodes == 2) {
auto consumer = consumers.front();
if(consumer.getType() == NodeType::Application) {
auto impls = createApplicationVariable<UserType>(feeder, consumer);
feeder.setAppAccessorImplementation<UserType>(impls.first);
consumer.setAppAccessorImplementation<UserType>(impls.second);
}
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto impl = createProcessVariable<UserType>(consumer);
feeder.setAppAccessorImplementation<UserType>(impl);
}
else if(consumer.getType() == NodeType::Device) {
Martin Killenberg
committed
auto impl = createDeviceVariable<UserType>(consumer);
feeder.setAppAccessorImplementation<UserType>(impl);
}
else if(consumer.getType() == NodeType::TriggerReceiver) {
auto impls = createApplicationVariable<UserType>(feeder, consumer);
feeder.setAppAccessorImplementation<UserType>(impls.first);
consumer.getNodeToTrigger().getOwner().setExternalTriggerImpl(impls.second);
}
else if(consumer.getType() == NodeType::Constant) {
auto impl = consumer.createConstAccessor<UserType>({});
feeder.setAppAccessorImplementation<UserType>(impl);
}
else {
throw ChimeraTK::logic_error("Unexpected node type!"); // LCOV_EXCL_LINE (assert-like)
Martin Christoph Hierholzer
committed
}
Martin Christoph Hierholzer
committed
}
auto consumerImplementationPairs = setConsumerImplementations<UserType>(feeder, consumers);
// create FanOut and use it as the feeder implementation
auto fanOut =
boost::make_shared<FeedingFanOut<UserType>>(feeder.getName(), feeder.getUnit(), feeder.getDescription(),
feeder.getNumberOfElements(), feeder.getDirection().withReturn, consumerImplementationPairs);
feeder.setAppAccessorImplementation<UserType>(fanOut);
connectionMade = true;
else { /* constantFeeder */
Christoph Kampmeyer
committed
if(enableDebugMakeConnections) {
std::cout << " Using constant feeder '" << feeder.getName() << "'..." << std::endl;
}
assert(feeder.getType() == NodeType::Constant);
Martin Christoph Hierholzer
committed
AccessModeFlags flags{};
if(consumer.getMode() == UpdateMode::push) {
flags = {AccessMode::wait_for_new_data};
}
// each consumer gets its own implementation
auto feedingImpl = feeder.createConstAccessor<UserType>(flags);
if(consumer.getType() == NodeType::Application) {
if(testableMode && consumer.getMode() == UpdateMode::push) {
auto varId = getNextVariableId();
auto pvarDec =
boost::make_shared<TestableModeAccessorDecorator<UserType>>(feedingImpl, true, false, varId, varId);
testableMode_names[varId] = "Constant";
consumer.setAppAccessorImplementation<UserType>(pvarDec);
}
else {
consumer.setAppAccessorImplementation<UserType>(feedingImpl);
}
}
else if(consumer.getType() == NodeType::ControlSystem) {
Martin Christoph Hierholzer
committed
auto impl = createProcessVariable<UserType>(consumer);
impl->accessChannel(0) = feedingImpl->accessChannel(0);
impl->write();
}
else if(consumer.getType() == NodeType::Device) {
Martin Killenberg
committed
// we register the required accessor as a recovery accessor. This is just a bare RegisterAccessor without any decorations directly from the backend.
if(deviceMap.count(consumer.getDeviceAlias()) == 0) {
deviceMap[consumer.getDeviceAlias()] =
ChimeraTK::BackendFactory::getInstance().createBackend(consumer.getDeviceAlias());
}
auto impl = deviceMap[consumer.getDeviceAlias()]->getRegisterAccessor<UserType>(
consumer.getRegisterName(), consumer.getNumberOfElements(), 0, {});
impl->accessChannel(0) = feedingImpl->accessChannel(0);
Martin Killenberg
committed
assert(deviceModuleMap.find(consumer.getDeviceAlias()) != deviceModuleMap.end());
DeviceModule* devmod = deviceModuleMap[consumer.getDeviceAlias()];