diff --git a/fpga/include/villas/dependency_graph.hpp b/fpga/include/villas/dependency_graph.hpp new file mode 100644 index 000000000..f488e8ad7 --- /dev/null +++ b/fpga/include/villas/dependency_graph.hpp @@ -0,0 +1,55 @@ +#ifndef VILLAS_DEPENDENCY_GRAPH_HPP +#define VILLAS_DEPENDENCY_GRAPH_HPP + +#include +#include +#include + +#include "log.hpp" + +namespace villas { +namespace utils { + + +template +class DependencyGraph { +public: + using NodeList = std::list; + + /// Create a node without dependencies if it not yet exists, return if a new + /// node has been created. + bool addNode(const T& node); + + /// Remove a node and all other nodes that depend on it + void removeNode(const T& node); + + /// Add a dependency to a node. Will create the node if it not yet exists + void addDependency(const T& node, const T& dependency); + + void dump(); + + /// Return a sequential evaluation order list. If a circular dependency has been + /// detected, all nodes involved will not be part of that list. + NodeList getEvaluationOrder() const; + +private: + /// Return whether a node already exists or not + bool nodeExists(const T& node) + { return graph.find(node) != graph.end(); } + + static bool + nodeInList(const NodeList& list, const T& node) + { return list.end() != std::find(list.begin(), list.end(), node); } + +private: + using Graph = std::map; + + Graph graph; +}; + +} // namespace utils +} // namespace villas + +#include "dependency_graph_impl.hpp" + +#endif // VILLAS_DEPENDENCY_GRAPH_HPP diff --git a/fpga/include/villas/dependency_graph_impl.hpp b/fpga/include/villas/dependency_graph_impl.hpp new file mode 100644 index 000000000..f5d5b32d7 --- /dev/null +++ b/fpga/include/villas/dependency_graph_impl.hpp @@ -0,0 +1,107 @@ +#ifndef VILLAS_DEPENDENCY_GRAPH_HPP +#error "Do not include this file directly, please include depedency_graph.hpp" +#endif + +#include "dependency_graph.hpp" +#include + +namespace villas { +namespace utils { + +template +bool +DependencyGraph::addNode(const T &node) +{ + bool existedBefore = nodeExists(node); + + // accessing is enough to create if not exists + graph[node]; + + return existedBefore; +} + +template +void +DependencyGraph::removeNode(const T &node) +{ + graph.erase(node); + + // check if other nodes depend on this one + for(auto& [key, dependencies] : graph) { + if(nodeInList(dependencies, node)) { + // remove other node that depends on the one to delete + removeNode(key); + } + } + +} + +template +void +DependencyGraph::addDependency(const T &node, const T &dependency) +{ + NodeList& dependencies = graph[node]; + if(not nodeInList(dependencies, dependency)) + dependencies.push_back(dependency); +} + +template +void +DependencyGraph::dump() { + for(auto& node : graph) { + std::stringstream ss; + for(auto& dep : node.second) { + ss << dep << " "; + } + cpp_debug << node.first << ": " << ss.str(); + } +} + +template +typename DependencyGraph::NodeList +DependencyGraph::getEvaluationOrder() const +{ + // copy graph to preserve information (we have to delete entries later) + Graph graph = this->graph; + + // output list + NodeList out; + + while(graph.size() > 0) { + int added = 0; + + // look for nodes with no dependencies + for(auto& [key, dependencies] : graph) { + + for(auto dep = dependencies.begin(); dep != dependencies.end(); ++dep) { + if(nodeInList(out, *dep)) { + // dependency has been pushed to list in last round + dep = dependencies.erase(dep); + } + } + + // nodes with no dependencies can be pushed to list + if(dependencies.empty()) { + out.push_back(key); + graph.erase(key); + added++; + } + } + + // if a round doesn't add any elements and is not the last, then + // there is a circular dependency + if(added == 0 and graph.size() > 0) { + cpp_error << "Circular dependency detected! IPs not available:"; + Logger::Indenter indent = cpp_debug.indent(); + for(auto& [key, value] : graph) { + cpp_error << key; + } + break; + } + } + + return out; +} + +} // namespace utils +} // namespace villas