/* * VariableNetwork.cc * * Created on: Jun 14, 2016 * Author: Martin Hierholzer */ #include <sstream> #include "Application.h" #include "VariableNetwork.h" #include "VariableNetworkDumpingVisitor.h" namespace ChimeraTK { /*********************************************************************************************************************/ bool VariableNetwork::hasFeedingNode() const { auto n = std::count_if(nodeList.begin(), nodeList.end(), [](const VariableNetworkNode node) { return node.getDirection().dir == VariableDirection::feeding; }); assert(n < 2); return n == 1; } /*********************************************************************************************************************/ size_t VariableNetwork::countConsumingNodes() const { return nodeList.size() - (hasFeedingNode() ? 1 : 0); } /*********************************************************************************************************************/ void VariableNetwork::addNode(VariableNetworkNode& a) { assert(!a.hasOwner()); // change owner of the node: erase from Application's unconnectedNodeList and // set this as owner a.setOwner(this); // if node is feeding, save as feeder for this network if(a.getDirection().dir == VariableDirection::feeding) { // make sure we only have one feeding node per network if(hasFeedingNode()) { // check if current feeding node is a control system variable: if yes, // switch it to consuming if(getFeedingNode().getType() == NodeType::ControlSystem) { getFeedingNode().setDirection({VariableDirection::consuming, false}); } // Current feeder cannot be switch to consumer: throw exception else { std::stringstream msg; msg << "Trying to add a feeding accessor to a network already having a " "feeding accessor." << std::endl; msg << "The network you were trying to add the new accessor to:" << std::endl; dump("", msg); msg << "The node you were trying to add:" << std::endl; a.dump(msg); throw ChimeraTK::logic_error(msg.str()); } } // force value type, engineering unit and description of the network if set // in this feeding node if(a.getValueType() != typeid(AnyType)) valueType = &(a.getValueType()); if(a.getUnit() != ChimeraTK::TransferElement::unitNotSet) engineeringUnit = a.getUnit(); if(a.getDescription() != "") description = a.getDescription(); } else { // update value type and engineering unit, if not yet set if(valueType == &typeid(AnyType)) valueType = &(a.getValueType()); if(engineeringUnit == ChimeraTK::TransferElement::unitNotSet) engineeringUnit = a.getUnit(); if(description == "") description = a.getDescription(); } // add node to node list nodeList.push_back(a); } /*********************************************************************************************************************/ void VariableNetwork::removeNode(VariableNetworkNode& a) { a.clearOwner(); nodeList.remove(a); // this must be done last, since it will destroy the node if it was the last reference. } /*********************************************************************************************************************/ void VariableNetwork::removeNodeToTrigger(const VariableNetworkNode& nodeToNoLongerTrigger) { for(auto& node : nodeList) { if(node.getType() != NodeType::TriggerReceiver) continue; if(node.getNodeToTrigger() == nodeToNoLongerTrigger) { removeNode(node); // Note: node may have been destroyed at this point. break; // we must leave the loop, since we invalidated the iterators } } } /*********************************************************************************************************************/ void VariableNetwork::dump(const std::string& linePrefix, std::ostream& stream) const { VariableNetworkDumpingVisitor visitor{linePrefix, stream}; accept(visitor); } void VariableNetwork::accept(Visitor<VariableNetwork>& visitor) const { visitor.dispatch(*this); } /*********************************************************************************************************************/ void VariableNetwork::addNodeToTrigger(VariableNetworkNode& nodeToTrigger) { VariableNetworkNode node(nodeToTrigger, 0); node.setOwner(this); nodeList.push_back(node); } /*********************************************************************************************************************/ VariableNetwork::TriggerType VariableNetwork::getTriggerType(bool verboseExceptions) const { if(!hasFeedingNode()) return TriggerType::none; const auto& feeder = getFeedingNode(); // network has an external trigger if(feeder.hasExternalTrigger()) { if(feeder.getMode() == UpdateMode::push) { std::stringstream msg; msg << "Providing an external trigger to a variable network which is fed " "by a pushing variable is not allowed." << std::endl; if(verboseExceptions) { msg << "The illegal network:" << std::endl; dump("", msg); } throw ChimeraTK::logic_error(msg.str()); } return TriggerType::external; } // network is fed by a pushing node if(feeder.getMode() == UpdateMode::push) { return TriggerType::feeder; } // network is fed by a poll-type node: must have exactly one polling consumer size_t nPollingConsumers = count_if(nodeList.begin(), nodeList.end(), [](const VariableNetworkNode& n) { return n.getDirection().dir == VariableDirection::consuming && n.getMode() == UpdateMode::poll; }); if(nPollingConsumers != 1) { std::stringstream msg; msg << "In a network with a poll-type feeder and no external trigger, " "there must be exactly one polling consumer. Maybe you forgot to " "add a trigger?" << std::endl; if(verboseExceptions) { msg << "The illegal network:" << std::endl; dump("", msg); } throw ChimeraTK::logic_error(msg.str()); } return TriggerType::pollingConsumer; } /*********************************************************************************************************************/ void VariableNetwork::check() const { // must have consuming nodes if(countConsumingNodes() == 0) { std::stringstream msg; msg << "No consuming nodes connected to this network!" << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } // must have a feeding node if(!hasFeedingNode()) { std::stringstream msg; msg << "No feeding node connected to this network!" << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } // the network's value type must be correctly set if(*valueType == typeid(AnyType)) { std::stringstream msg; msg << "No data type specified for any of the nodes in this network!" << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } // the feeder node must have a non-zero length size_t length = getFeedingNode().getNumberOfElements(); if(length == 0) { std::stringstream msg; msg << "The feeding node has zero (or undefined) length!" << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } // all consumers must have the same length as the feeder or a zero length for // trigger receivers for(auto& node : nodeList) { if(node.getType() != NodeType::TriggerReceiver) { if(node.getNumberOfElements() != length) { std::stringstream msg; msg << "The network contains a node with a different length than the " "feeding node!" << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } } else { if(node.getNumberOfElements() != 0) { std::stringstream msg; msg << "The network contains a trigger receiver node with a non-zero " "length!" << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } } } // all nodes must have this network as the owner and a value type equal the // network's value type for(auto& node : nodeList) { assert(&(node.getOwner()) == this); if(node.getValueType() == typeid(AnyType)) node.setValueType(*valueType); if(node.getValueType() != *valueType) { std::stringstream msg; msg << "The network contains variables of different value types, which " "is not supported!" << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } } // if the feeder is an application node, it must be in push mode if(getFeedingNode().getType() == NodeType::Application) { assert(getFeedingNode().getMode() == UpdateMode::push); } // check if trigger is correctly defined (the return type doesn't matter, only // the checks done in the function are needed) getTriggerType(); // if (and only if) feeder has return channel, there must be exactly one // consumer with return channel if(getFeedingNode().getDirection().withReturn) { size_t numberOfConsumersWithReturnChannel = 0; for(auto& node : getConsumingNodes()) { if(node.getDirection().withReturn) ++numberOfConsumersWithReturnChannel; } if(numberOfConsumersWithReturnChannel != 1) { std::stringstream msg; msg << "The network has a feeder with return channel but " << numberOfConsumersWithReturnChannel << " consumers" << " with return channel (must have exactly 1)!" << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } } else { for(auto& node : getConsumingNodes()) { if(node.getDirection().withReturn) { std::stringstream msg; msg << "The network has no feeder with return channel but at least one " "consumer with return channel!" << std::endl; msg << "The illegal network:" << std::endl; dump("", msg); throw ChimeraTK::logic_error(msg.str()); } } } } /*********************************************************************************************************************/ VariableNetworkNode VariableNetwork::getFeedingNode() const { auto iter = std::find_if(nodeList.begin(), nodeList.end(), [](const VariableNetworkNode &n) { return n.getDirection().dir == VariableDirection::feeding; }); if(iter == nodeList.end()) { std::stringstream msg; msg << "No feeding node in this network!" << std::endl; msg << "The illegal network:" << std::endl; if(!hasFeedingNode()) dump("", msg); throw ChimeraTK::logic_error(msg.str()); } return *iter; } /*********************************************************************************************************************/ std::list<VariableNetworkNode> VariableNetwork::getConsumingNodes() const { std::list<VariableNetworkNode> consumers; for(auto& n : nodeList) if(n.getDirection().dir == VariableDirection::consuming) consumers.push_back(n); return consumers; } /*********************************************************************************************************************/ bool VariableNetwork::hasApplicationConsumer() const { for(auto& n : nodeList) { if(n.getDirection().dir == VariableDirection::consuming && n.getType() == NodeType::Application) return true; } return false; } /*********************************************************************************************************************/ bool VariableNetwork::merge(VariableNetwork& other) { // check if merging is possible if(hasFeedingNode() || !other.hasFeedingNode()) { if((getFeedingNode().getType() == NodeType::ControlSystem && other.getFeedingNode().getType() == NodeType::ControlSystem) || (getFeedingNode().getType() != NodeType::ControlSystem && other.getFeedingNode().getType() != NodeType::ControlSystem)) { return false; } } // put all consuming nodes of B's owner into A's owner for(auto node : other.getConsumingNodes()) { other.removeNode(node); addNode(node); } // feeding node: if control system type, convert into consumer. Otherwise just // add it, addNode() will convert the other feeding node if necessary if(other.hasFeedingNode()) { VariableNetworkNode otherFeeder = other.getFeedingNode(); other.removeNode(otherFeeder); if(otherFeeder.getType() == NodeType::ControlSystem) otherFeeder.setDirection({VariableDirection::consuming, false}); addNode(otherFeeder); } return true; } } // namespace ChimeraTK