Skip to content
Snippets Groups Projects
VariableNetwork.cc 12.83 KiB
/*
 * 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) {
  auto nNodes = nodeList.size();
  nodeList.remove(a);
  a.clearOwner();
  (void)nNodes;
  assert(nodeList.size() != nNodes);
}

/*********************************************************************************************************************/

void VariableNetwork::removeNodeToTrigger(
    const VariableNetworkNode &nodeToNoLongerTrigger) {
  for (auto &node : nodeList) {
    if (node.getType() != NodeType::TriggerReceiver)
      continue;
    if (node.getNodeToTrigger() == nodeToNoLongerTrigger) {
      removeNode(node);
      break;
    }
  }
}

/*********************************************************************************************************************/

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