diff --git a/CMakeLists.txt b/CMakeLists.txt index ea6ebcdb9462f50c8f8027a4f452bdcf298ed25a..794f40cd468bb1c4b6e7726239277c661ba74dd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ ENDIF() # Moderate version of the compiler flags -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -Wextra -Wuninitialized") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -Wextra -Wuninitialized ") # use -DCMAKE_BUILD_TYPE=Debug in your cmake command to turn on the coverage option set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 --coverage -fno-inline -fno-inline-small-functions -fno-default-inline") diff --git a/example/demoApp.cc b/example/demoApp.cc index 720256de01aad88ad7e4306cee4bc6b910638439..ccb28d92a3c3492dd73ccf862f56d38d587af92f 100644 --- a/example/demoApp.cc +++ b/example/demoApp.cc @@ -101,6 +101,8 @@ void ExampleApp::defineConnections() { dev("probeSignal", typeid(int), tableLength) [ macropulseNr ] >> cs("probeSignal"); dumpConnections(); - + dumpConnectionGraph(); + dumpGraph(); + dumpModuleGraph("module-graph.dot"); } diff --git a/include/Application.h b/include/Application.h index f5375e3d2fb51d5f1e76dca3d742501ab2b2a4b4..222dd511021402cbf904ee661cbe3ba132663bd6 100644 --- a/include/Application.h +++ b/include/Application.h @@ -67,6 +67,10 @@ namespace ChimeraTK { * makeConnections() has been called. */ void dumpConnections(); + /** Create Graphviz dot graph and write to file. The graph will contain the connections made in the initilise() + * function. @see dumpConnections */ + void dumpConnectionGraph(const std::string &filename = {"connections-graph.dot"}); + /** Enable warning about unconnected variables. This can be helpful to identify missing connections but is * disabled by default since it may often be very noisy. */ void warnUnconnectedVariables() { enableUnconnectedVariablesWarning = true; } @@ -154,6 +158,8 @@ namespace ChimeraTK { friend class Module; friend class VariableNetwork; friend class VariableNetworkNode; + friend class VariableNetworkGraphDumpingVisitor; + friend class XMLGeneratorVisitor; template<typename UserType> friend class Accessor; diff --git a/include/ApplicationException.h b/include/ApplicationException.h index 8db387e31dfaa223eebc5bed31b4cc26a4dd4a32..07a7d63b93b15aee5e3d673539d69fdcf138ae5e 100644 --- a/include/ApplicationException.h +++ b/include/ApplicationException.h @@ -9,6 +9,7 @@ #define CHIMERATK_APPLICATION_EXCEPTION_H #include <exception> +#include <string> namespace ChimeraTK { diff --git a/include/EntityOwner.h b/include/EntityOwner.h index ad6f976dd8ceca189e3092a75b2f11bd78192e89..5c59353f47d8b7e4d8e23588e2dbd2ea340b6572 100644 --- a/include/EntityOwner.h +++ b/include/EntityOwner.h @@ -126,6 +126,8 @@ namespace ChimeraTK { * through all sub-modules and add all found variables directly to the VirtualModule. */ VirtualModule flatten(); + void accept(Visitor<EntityOwner>& visitor) const { visitor.dispatch(*this); } + /** Print the full hierarchy to stdout. */ void dump(const std::string &prefix="") const; @@ -154,12 +156,6 @@ namespace ChimeraTK { void findTagAndAppendToModule(VirtualModule &module, const std::string &tag, bool eliminateAllHierarchies=false, bool eliminateFirstHierarchy=false, bool negate=false) const; - /** Create Graphviz dot graph write to stream, excluding the surrounding digraph command */ - void dumpGraphInternal(std::ostream &stream, bool showVariables) const; - - /** Clean a fully qualified entity name so it can be used as a dot node name (i.e. strip slashes etc.) */ - std::string cleanDotNode(std::string fullName) const; - /** The name of this instance */ std::string _name; diff --git a/include/Module.h b/include/Module.h index f0681c50aefdd2fb5a762e534f59a0761a006706..8dccad87cad1b029ee8e739fec8c73a6fec29e8b 100644 --- a/include/Module.h +++ b/include/Module.h @@ -108,6 +108,12 @@ namespace ChimeraTK { EntityOwner* getOwner() const { return _owner; } + /** + * Explcitly add accept() method so that we can distinguish between a Module and an EntityOwner in the Visitor. + + */ + void accept(Visitor<Module>& visitor) const { visitor.dispatch(*this); } + protected: /** Owner of this instance */ diff --git a/include/ModuleGraphVisitor.h b/include/ModuleGraphVisitor.h new file mode 100644 index 0000000000000000000000000000000000000000..eec32431f305f00e9af68747cf571ac54ed77758 --- /dev/null +++ b/include/ModuleGraphVisitor.h @@ -0,0 +1,33 @@ +#pragma once + +#include <string> +#include <ostream> + +#include "Visitor.h" + +namespace ChimeraTK { +// Forward declarations +class VariableNetworkNode; +class EntityOwner; +class Module; + +/** + * @brief The ModuleGraphVisitor class + * + * This class is responsible for generating the Graphiviz representation of the module hierarchy. + */ +class ModuleGraphVisitor : public Visitor<EntityOwner, Module, VariableNetworkNode> { +public: + ModuleGraphVisitor(std::ostream& stream, bool showVariables = true); + virtual ~ModuleGraphVisitor() {} + + void dispatch(const EntityOwner &owner); + void dispatch(const Module &module); + void dispatch(const VariableNetworkNode &node); +private: + std::ostream& _stream; + bool _showVariables; + + void dumpEntityOwner(const EntityOwner &owner); +}; +} // namespace ChimeraTK diff --git a/include/VariableNetwork.h b/include/VariableNetwork.h index 834e5b64c65c3b755893f24e05f6a1a387234de7..c25b6389fd7c58bd2cff6043266de9c59ee592f4 100644 --- a/include/VariableNetwork.h +++ b/include/VariableNetwork.h @@ -18,6 +18,7 @@ #include "Flags.h" #include "VariableNetworkNode.h" +#include "Visitor.h" namespace ChimeraTK { @@ -77,6 +78,8 @@ namespace ChimeraTK { /** Dump the network structure to std::cout. The optional linePrefix will be prepended to all lines. */ void dump(const std::string& linePrefix="", std::ostream& stream=std::cout) const; + void accept(Visitor<VariableNetwork> &visitor) const; + /** Compare two networks */ bool operator==(const VariableNetwork &other) const { if(other.valueType != valueType) return false; @@ -109,7 +112,7 @@ namespace ChimeraTK { //void addTrigger(VariableNetworkNode trigger); /** Check if the network is legally configured */ - void check(); + void check() const; /** Check the flag if the network connections has been created already */ bool isCreated() const { return flagIsCreated; } diff --git a/include/VariableNetworkDumpingVisitor.h b/include/VariableNetworkDumpingVisitor.h new file mode 100644 index 0000000000000000000000000000000000000000..60282f2467e644a1560be8c340dca7b2cf2126ec --- /dev/null +++ b/include/VariableNetworkDumpingVisitor.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Visitor.h" +#include "VariableNetworkNodeDumpingVisitor.h" + +#include <string> + +namespace ChimeraTK { + +// Forward declarations +class VariableNetwork; + +/** + * @brief The VariableNetworkDumpingVisitor class + * + * This class provides a textual dump of the VariableNetwork + */ +class VariableNetworkDumpingVisitor : public Visitor<VariableNetwork>, public VariableNetworkNodeDumpingVisitor { +public: + VariableNetworkDumpingVisitor(const std::string &prefix, std::ostream &stream); + virtual ~VariableNetworkDumpingVisitor() {} + void dispatch(const VariableNetwork& t); + using Visitor<VariableNetworkNode>::dispatch; + +private: + std::string _prefix; +}; + +} // namespace ChimeraTK diff --git a/include/VariableNetworkGraphDumpingVisitor.h b/include/VariableNetworkGraphDumpingVisitor.h new file mode 100644 index 0000000000000000000000000000000000000000..784fd68c077db4aed406fcf26caa3cc525bf8662 --- /dev/null +++ b/include/VariableNetworkGraphDumpingVisitor.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Visitor.h" +#include "VariableNetworkNodeDumpingVisitor.h" + +#include <map> + +namespace ChimeraTK { + +// Forward Declarations + +class Application; +class VariableNetwork; + +/** + * @brief The VariableNetworkGraphDumpingVisitor class + * + * This class provides a Graphiviz dump of the VariableNetwork. + * Due to the potential size of the resulting graph, it is recommended to use SVG for + * rendering the resulting graph. + */ +class VariableNetworkGraphDumpingVisitor : public Visitor<Application, VariableNetwork>, VariableNetworkNodeDumpingVisitor { +public: + VariableNetworkGraphDumpingVisitor(std::ostream& stream); + virtual ~VariableNetworkGraphDumpingVisitor() {} + void dispatch(const Application& t); + void dispatch(const VariableNetwork& t); + void dispatch(const VariableNetworkNode& t); +private: + std::map<std::string, std::string> _triggerMap; + std::list<std::string> _triggerConnections; + std::list<std::string> _prefix; + unsigned _networkCount; + unsigned _triggerCount; + + std::string prefix() { return _prefix.back(); } + void pushPrefix(const std::string& prefix) { _prefix.push_back(prefix); } + void popPrefix() { _prefix.pop_back(); } +}; + +} // namespace ChimeraTK diff --git a/include/VariableNetworkNode.h b/include/VariableNetworkNode.h index 7ee500c77a8faa352c2a277d18ac0adabbe6e080..5be94a3a071249994458a4bf4755028e318b8c1d 100644 --- a/include/VariableNetworkNode.h +++ b/include/VariableNetworkNode.h @@ -19,10 +19,7 @@ #include "Flags.h" #include "ConstantAccessor.h" - -namespace xmlpp { - class Element; -} +#include "Visitor.h" namespace ChimeraTK { @@ -112,12 +109,6 @@ namespace ChimeraTK { /** Print node information to std::cout */ void dump(std::ostream& stream=std::cout) const; - /** Create an XML node describing this network node as seen by the control syste. If the type is not - * NodeType::ControlSystem, this function does nothing. Otherwise the correct directory hierarchy will be - * created (if not yet existing) and a variable tag will be created containing the externally visible - * properties of this variable. */ - void createXML(xmlpp::Element *rootElement) const; - /** Check if the node already has an owner */ bool hasOwner() const; @@ -161,6 +152,8 @@ namespace ChimeraTK { void setOwningModule(EntityOwner *newOwner) const; + void accept(Visitor<VariableNetworkNode> &visitor) const; + //protected: @todo make protected again (with proper interface extension) boost::shared_ptr<VariableNetworkNode_data> pdata; diff --git a/include/VariableNetworkNodeDumpingVisitor.h b/include/VariableNetworkNodeDumpingVisitor.h new file mode 100644 index 0000000000000000000000000000000000000000..fbf391ee62f983b65e5035661847a4ab3bc3512f --- /dev/null +++ b/include/VariableNetworkNodeDumpingVisitor.h @@ -0,0 +1,75 @@ +#pragma once + +#include "Visitor.h" + +#include <list> +#include <ostream> +#include <functional> // for std::reference_wrapper + +namespace ChimeraTK { + +// Forward declarations +class VariableNetworkNode; + +/** + * @brief A helper class to replace the output stream temporarily + * + * This is a helper class that is used in the Graphviz dumper to be able to dump the nodes to a stringstream + * instead of directly to the file. + * + * Ideally, the pushStream()/popStream() functions should be called in pairs but popStream() will do nothing + * if the stack is empty. + */ +class PushableStream { +public: + PushableStream(std::ostream &stream) : _streamStack{stream} {} + virtual ~PushableStream() {} + + void pushStream(std::ostream& stream) { + _streamStack.push_back(stream); + } + + std::ostream& stream() { return _streamStack.back().get(); } + + void popStream() { + if (_streamStack.size() == 1) + return; + + _streamStack.pop_back(); + } +private: + std::list<std::reference_wrapper<std::ostream>> _streamStack; +}; + +/** + * @brief The VariableNetworkNodeDumpingVisitor class + * + * This class is serving as one of the base classes for the Graphviz dumper as well as the textual dumper + * providing detailed information about a node. + */ +class VariableNetworkNodeDumpingVisitor : public Visitor<VariableNetworkNode>, public PushableStream { +public: + /** + * @brief VariableNetworkNodeDumpingVisitor::VariableNetworkNodeDumpingVisitor + * @param stream instance of std::ostream to write to + * @param separator the separator to use + * + * Separator is used to be able to use the function in the Graphviz and textual connection dumper. + * We are using newlines for Graphviz, and space for textual + */ + VariableNetworkNodeDumpingVisitor(std::ostream &stream, const std::string& separator); + virtual ~VariableNetworkNodeDumpingVisitor() {} + + /** + * @brief dispatch + * @param t Node to visit + * + * Visitor function for VariableNetworkNode. Will dump a verbose description of the node + */ + void dispatch(const VariableNetworkNode& t); + +private: + std::string _separator; +}; + +} // namespace ChimeraTK diff --git a/include/Visitor.h b/include/Visitor.h new file mode 100644 index 0000000000000000000000000000000000000000..b15e78ca7e8615534199c15efa5ca440da6a038b --- /dev/null +++ b/include/Visitor.h @@ -0,0 +1,23 @@ +#pragma once + +namespace ChimeraTK { + +/* Losely based on https://stackoverflow.com/questions/11796121/implementing-the-visitor-pattern-using-c-templates#11802080 */ + +template <typename... Types> +class Visitor; + +template <typename T> +class Visitor<T> { +public: + virtual void dispatch(const T& t) = 0; +}; + +template <typename T, typename... Types> +class Visitor<T, Types...> : public Visitor<T>, public Visitor<Types...> { +public: + using Visitor<Types...>::dispatch; + using Visitor<T>::dispatch; +}; + +} // namespace ChimeraTK diff --git a/include/VisitorHelper.h b/include/VisitorHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..cce1390e3d63ba94f470c8e07c75fb3c43352a32 --- /dev/null +++ b/include/VisitorHelper.h @@ -0,0 +1,14 @@ +#pragma once + +#include <string> + +namespace ChimeraTK { +class VariableNetworkNode; + +namespace detail { + +std::string encodeDotNodeName(std::string name); +std::string nodeName(const VariableNetworkNode& node); + +} // namespace detail) +}// namespace ChimeraTK diff --git a/include/XMLGeneratorVisitor.h b/include/XMLGeneratorVisitor.h new file mode 100644 index 0000000000000000000000000000000000000000..63335cad61bc95291507263d77c069c194f8e6b5 --- /dev/null +++ b/include/XMLGeneratorVisitor.h @@ -0,0 +1,37 @@ +#pragma once + +#include "Visitor.h" + +#include <string> +#include <memory> + +// Forward declarations +namespace xmlpp { + class Document; + class Element; +} + +namespace ChimeraTK { +// Forward declarations +class Application; +class VariableNetworkNode; + +/** + * @brief The XMLGeneratorVisitor class + * + * This class is responsible for generating the XML representation of the Variables in an Application + */ +class XMLGeneratorVisitor : public Visitor<Application, VariableNetworkNode> { +public: + XMLGeneratorVisitor(); + virtual ~XMLGeneratorVisitor() {} + void dispatch(const Application& app); + void dispatch(const VariableNetworkNode &node); + + void save(const std::string &filename); +private: + std::shared_ptr<xmlpp::Document> _doc; + xmlpp::Element *_rootElement; +}; + +} // namespace ChimeraTK diff --git a/src/Application.cc b/src/Application.cc index 5050b41ea63adaa9377604d9cd8c3183154703cf..7b7c41968d126a60769a614a3a9e80feb1d065a3 100644 --- a/src/Application.cc +++ b/src/Application.cc @@ -11,8 +11,6 @@ #include <boost/fusion/container/map.hpp> -#include <libxml++/libxml++.h> - #include <mtca4u/BackendFactory.h> #include "Application.h" @@ -27,6 +25,9 @@ #include "ConstantAccessor.h" #include "TestDecoratorRegisterAccessor.h" #include "DebugDecoratorRegisterAccessor.h" +#include "Visitor.h" +#include "VariableNetworkGraphDumpingVisitor.h" +#include "XMLGeneratorVisitor.h" using namespace ChimeraTK; @@ -206,27 +207,9 @@ void Application::generateXML() { // also search for unconnected nodes - this is here only executed to print the warnings processUnconnectedNodes(); - // create XML document with root node - xmlpp::Document doc; - xmlpp::Element *rootElement = doc.create_root_node("application", "https://github.com/ChimeraTK/ApplicationCore"); - rootElement->set_attribute("name",applicationName); - - for(auto &network : networkList) { - - // perform checks - network.check(); - - // create xml code for the feeder (if it is a control system node) - auto feeder = network.getFeedingNode(); - feeder.createXML(rootElement); - - // create xml code for the consumers - for(auto &consumer : network.getConsumingNodes()) { - consumer.createXML(rootElement); - } - - } - doc.write_to_file_formatted(applicationName+".xml"); + XMLGeneratorVisitor visitor; + visitor.dispatch(*this); + visitor.save(applicationName + ".xml"); } /*********************************************************************************************************************/ @@ -536,6 +519,13 @@ void Application::dumpConnections() { std::cout << "=====================================================================" << std::endl; // LCOV_EXCL_LINE } // LCOV_EXCL_LINE +void Application::dumpConnectionGraph(const std::string& fileName) { + std::fstream file{fileName, std::ios_base::out}; + + VariableNetworkGraphDumpingVisitor visitor{file}; + visitor.dispatch(*this); +} + /*********************************************************************************************************************/ Application::TypedMakeConnectionCaller::TypedMakeConnectionCaller(Application &owner, VariableNetwork &network) diff --git a/src/EntityOwner.cc b/src/EntityOwner.cc index e29f0ea01432d1913672f1af1cc83098e8122804..19d4ffe98b749ba9608407da7b2880a8b97deb8d 100644 --- a/src/EntityOwner.cc +++ b/src/EntityOwner.cc @@ -11,6 +11,7 @@ #include "EntityOwner.h" #include "Module.h" +#include "ModuleGraphVisitor.h" #include "VirtualModule.h" namespace ChimeraTK { @@ -204,75 +205,16 @@ namespace ChimeraTK { void EntityOwner::dumpGraph(const std::string& fileName) const { std::fstream file(fileName, std::ios_base::out); - file << "digraph G {" << std::endl; - dumpGraphInternal(file, true); - file << "}" << std::endl; - file.close(); + ModuleGraphVisitor v{file, true}; + v.dispatch(*this); } /*********************************************************************************************************************/ void EntityOwner::dumpModuleGraph(const std::string& fileName) const { std::fstream file(fileName, std::ios_base::out); - file << "digraph G {" << std::endl; - dumpGraphInternal(file, false); - file << "}" << std::endl; - file.close(); - } - -/*********************************************************************************************************************/ - - std::string EntityOwner::cleanDotNode(std::string fullName) const { - std::replace(fullName.begin(), fullName.end(), '/', '_'); - std::replace(fullName.begin(), fullName.end(), ':', '_'); - return fullName; - } - -/*********************************************************************************************************************/ - - void EntityOwner::dumpGraphInternal(std::ostream &stream, bool showVariables) const { - - std::string myDotNode = cleanDotNode(getQualifiedName()); - - stream << " " << myDotNode << "[label=\"" << getName() << "\""; - if(_eliminateHierarchy) { - stream << ",style=dotted"; - } - if(getModuleType() == ModuleType::ModuleGroup) { - stream << ",peripheries=2"; - } - if(getModuleType() == ModuleType::ApplicationModule) { - stream << ",penwidth=3"; - } - stream << "]" << std::endl; - - if(showVariables) { - for(auto &node : getAccessorList()) { - std::string dotNode = cleanDotNode(node.getQualifiedName()); - stream << " " << dotNode << "[label=\"{" << node.getName() << "| {"; - bool first = true; - for(auto tag : node.getTags()) { - if(!first) { - stream << "|"; - } - else { - first = false; - } - stream << tag; - } - stream << "}}\", shape=record]" << std::endl; - stream << " " << myDotNode << " -> " << dotNode << std::endl; - } - } - - for(auto submodule : getSubmoduleList()) { - if(submodule->getModuleType() == ModuleType::Device || - submodule->getModuleType() == ModuleType::ControlSystem) continue; - std::string dotNode = cleanDotNode(submodule->getQualifiedName()); - stream << " " << myDotNode << " -> " << dotNode << std::endl; - submodule->dumpGraphInternal(stream, showVariables); - } - + ModuleGraphVisitor v{file, false}; + v.dispatch(*this); } /*********************************************************************************************************************/ diff --git a/src/ModuleGraphVisitor.cc b/src/ModuleGraphVisitor.cc new file mode 100644 index 0000000000000000000000000000000000000000..9d1e8a6d54aa5f3dc06bdb2d0dba7f15a9de8f16 --- /dev/null +++ b/src/ModuleGraphVisitor.cc @@ -0,0 +1,73 @@ +#include "ModuleGraphVisitor.h" + +#include "EntityOwner.h" +#include "Module.h" +#include "VariableNetworkNode.h" +#include "VisitorHelper.h" + +namespace ChimeraTK { + +ModuleGraphVisitor::ModuleGraphVisitor(std::ostream& stream, bool showVariables) + : Visitor<ChimeraTK::EntityOwner, ChimeraTK::Module, ChimeraTK::VariableNetworkNode> () + , _stream(stream) + , _showVariables(showVariables) {} + +void ModuleGraphVisitor::dispatch(const EntityOwner &owner) { + /* If we start with an entity owner, consider ourselves the start of a graph */ + /* When descending from here, we only use Module directly */ + _stream << "digraph G {" << "\n"; + dumpEntityOwner(owner); + _stream << "}" << std::endl; +} + +void ModuleGraphVisitor::dispatch(const VariableNetworkNode &node) { + std::string dotNode = detail::encodeDotNodeName(node.getQualifiedName()); + _stream << " " << dotNode << "[label=\"{" << node.getName() << "| {"; + bool first = true; + for(auto tag : node.getTags()) { + if(!first) { + _stream << "|"; + } + else { + first = false; + } + _stream << tag; + } + _stream << "}}\", shape=record]" << std::endl; +} + +void ModuleGraphVisitor::dispatch(const Module &module) { + dumpEntityOwner(static_cast<const EntityOwner &>(module)); +} + +void ModuleGraphVisitor::dumpEntityOwner(const EntityOwner &module) { + std::string myDotNode = detail::encodeDotNodeName(module.getQualifiedName()); + _stream << " " << myDotNode << "[label=\"" << module.getName() << "\""; + if(module.getEliminateHierarchy()) { + _stream << ",style=dotted"; + } + if(module.getModuleType() == EntityOwner::ModuleType::ModuleGroup) { + _stream << ",peripheries=2"; + } + if(module.getModuleType() == EntityOwner::ModuleType::ApplicationModule) { + _stream << ",penwidth=3"; + } + _stream << "]" << std::endl; + + if(_showVariables) { + for(auto &node : module.getAccessorList()) { + std::string dotNode = detail::encodeDotNodeName(detail::nodeName(node)); + node.accept(*this); + _stream << " " << myDotNode << " -> " << dotNode << std::endl; + } + } + + for(const Module *submodule : module.getSubmoduleList()) { + if(submodule->getModuleType() == EntityOwner::ModuleType::Device || + submodule->getModuleType() == EntityOwner::ModuleType::ControlSystem) continue; + std::string dotNode = detail::encodeDotNodeName(submodule->getQualifiedName()); + _stream << " " << myDotNode << " -> " << dotNode << std::endl; + submodule->accept(*this); + } +} +} diff --git a/src/VariableNetwork.cc b/src/VariableNetwork.cc index fb088796c73ea968ff4e8ee968b6c039d6351365..07125c9bf0408a3b5b3fbc94b1be9e32b57af9f1 100644 --- a/src/VariableNetwork.cc +++ b/src/VariableNetwork.cc @@ -7,10 +7,9 @@ #include <sstream> -#include <libxml++/libxml++.h> - #include "VariableNetwork.h" #include "Application.h" +#include "VariableNetworkDumpingVisitor.h" namespace ChimeraTK { @@ -90,42 +89,12 @@ namespace ChimeraTK { /*********************************************************************************************************************/ void VariableNetwork::dump(const std::string& linePrefix, std::ostream& stream) const { - stream << linePrefix << "VariableNetwork"; - stream << " [ptr: " << this << "]"; - stream << " {" << std::endl; - stream << linePrefix << " value type = " << valueType->name() << ", engineering unit = " << engineeringUnit << std::endl; - stream << linePrefix << " trigger type = "; - try { - TriggerType tt = getTriggerType(false); - if(tt == TriggerType::feeder) stream << "feeder" << std::endl; - if(tt == TriggerType::pollingConsumer) stream << "pollingConsumer" << std::endl; - if(tt == TriggerType::external) stream << "external" << std::endl; - if(tt == TriggerType::none) stream << "none" << std::endl; - } - catch(ApplicationExceptionWithID<ApplicationExceptionID::illegalVariableNetwork> &e) { - stream << "**error**" << std::endl; - } - stream << linePrefix << " feeder"; - if(hasFeedingNode()) { - getFeedingNode().dump(stream); - } - else { - stream << " **error, no feeder found**" << std::endl; - } - stream << linePrefix << " consumers: " << countConsumingNodes() << std::endl; - size_t count = 0; - for(auto &consumer : nodeList) { - if(consumer.getDirection() != VariableDirection::consuming) continue; - stream << linePrefix << " # " << ++count << ":"; - consumer.dump(stream); - } - if(hasFeedingNode()) { - if(getFeedingNode().hasExternalTrigger()) { - stream << linePrefix << " external trigger node: "; - getFeedingNode().getExternalTrigger().dump(stream); - } - } - stream << linePrefix << "}" << std::endl; + VariableNetworkDumpingVisitor visitor{linePrefix, stream}; + accept(visitor); + } + + void VariableNetwork::accept(Visitor<VariableNetwork> &visitor) const { + visitor.dispatch(*this); } /*********************************************************************************************************************/ @@ -177,7 +146,7 @@ namespace ChimeraTK { /*********************************************************************************************************************/ - void VariableNetwork::check() { + void VariableNetwork::check() const { // must have consuming nodes if(countConsumingNodes() == 0) { std::stringstream msg; diff --git a/src/VariableNetworkDumpingVisitor.cc b/src/VariableNetworkDumpingVisitor.cc new file mode 100644 index 0000000000000000000000000000000000000000..ee5e114b9eeda2f123c887e9d379ca5bdd24cd45 --- /dev/null +++ b/src/VariableNetworkDumpingVisitor.cc @@ -0,0 +1,52 @@ +#include "ApplicationException.h" +#include "VariableNetwork.h" +#include "VariableNetworkDumpingVisitor.h" + +namespace ChimeraTK { + +VariableNetworkDumpingVisitor::VariableNetworkDumpingVisitor(const std::string& prefix, std::ostream &stream) + : Visitor<ChimeraTK::VariableNetwork>() + , VariableNetworkNodeDumpingVisitor(stream, " ") + , _prefix(prefix) {} + +void VariableNetworkDumpingVisitor::dispatch(const VariableNetwork& t) { + stream() << _prefix << "VariableNetwork"; + stream() << " {" << std::endl; + stream() << _prefix << " value type = " << boost::core::demangle(t.getValueType().name()) << ", engineering unit = " << t.getUnit() << std::endl; + stream() << _prefix << " trigger type = "; + try { + auto tt = t.getTriggerType(false); + if(tt == VariableNetwork::TriggerType::feeder) stream() << "feeder"; + if(tt == VariableNetwork::TriggerType::pollingConsumer) stream() << "pollingConsumer"; + if(tt == VariableNetwork::TriggerType::external) stream() << "external"; + if(tt == VariableNetwork::TriggerType::none) stream() << "none"; + stream() << std::endl; + } + catch(ApplicationExceptionWithID<ApplicationExceptionID::illegalVariableNetwork> &e) { + stream() << "**error**" << std::endl; + } + stream() << _prefix << " feeder"; + if(t.hasFeedingNode()) { + t.getFeedingNode().accept(*this); + } + else { + stream() << " **error, no feeder found**" << std::endl; + } + stream() << _prefix << " consumers: " << t.countConsumingNodes() << std::endl; + size_t count = 0; + for(auto &consumer : t.getConsumingNodes()) { + if(consumer.getDirection() != VariableDirection::consuming) continue; + stream() << _prefix << " # " << ++count << ":"; + consumer.accept(*this); + } + if(t.hasFeedingNode()) { + if(t.getFeedingNode().hasExternalTrigger()) { + stream() << _prefix << " external trigger node: "; + t.getFeedingNode().getExternalTrigger().accept(*this); + } + } + stream() << _prefix << "}" << std::endl; +} + + +} // namespace ChimeraTK diff --git a/src/VariableNetworkGraphDumpingVisitor.cc b/src/VariableNetworkGraphDumpingVisitor.cc new file mode 100644 index 0000000000000000000000000000000000000000..4431bb55c1e50b6d4e78202fcd56f3eaf2200707 --- /dev/null +++ b/src/VariableNetworkGraphDumpingVisitor.cc @@ -0,0 +1,148 @@ +#include "Application.h" +#include "VariableNetworkGraphDumpingVisitor.h" +#include "VariableNetwork.h" +#include "VisitorHelper.h" + +#include <algorithm> +#include <typeinfo> +#include <sstream> + +namespace ChimeraTK { + +void VariableNetworkGraphDumpingVisitor::dispatch(const VariableNetwork& network) { + std::string networkPrefix = "network_" + std::to_string(_networkCount++); + pushPrefix(networkPrefix); + + stream() << " subgraph cluster_" << prefix() << " {\n" + << " fontsize=\"8\";\n" + << " style=\"filled,rounded\";\n" + << " color=black;\n" + << " fillcolor=white;\n" + << " ordering=out;\n" + << " label=\"" << boost::core::demangle(typeid(network).name()) << "(" << &network << ")\\n" + << network.getDescription() << "\\n" + << "value type = " << boost::core::demangle(network.getValueType().name()) << "\\n" + << "engineering unit = " << network.getUnit() << "\";\n"; + + std::string feeder; + std::string trigger; + if (network.hasFeedingNode()) { + auto feederNode = network.getFeedingNode(); + // We are inside a trigger network... Consumers will be skipped below. Make the feeder a "global" variable + if (!network.getConsumingNodes().empty() && network.getConsumingNodes().begin()->getType() == NodeType::TriggerReceiver) { + feeder = detail::encodeDotNodeName(detail::nodeName(feederNode)); + if (_triggerMap.find(feeder) == _triggerMap.end()) { + std::stringstream ss; + pushStream(ss); pushPrefix(""); + feederNode.accept(*this); + _triggerMap[feeder] = ss.str(); + popPrefix(); popStream(); + } + } else { + feeder = prefix() + detail::encodeDotNodeName(detail::nodeName(feederNode)); + feederNode.accept(*this); + } + + if (feederNode.hasExternalTrigger()) { + auto triggerNode = feederNode.getExternalTrigger(); + trigger = detail::encodeDotNodeName(detail::nodeName(triggerNode)); + if (_triggerMap.find(trigger) == _triggerMap.end()) { + std::stringstream ss; + pushStream(ss); pushPrefix(""); + triggerNode.accept(*this); + _triggerMap[trigger] = ss.str(); + popPrefix(); popStream(); + } + } + } + + for (auto &consumerNode : network.getConsumingNodes()) { + if (consumerNode.getDirection() != VariableDirection::consuming) + continue; + + if (consumerNode.getType() == NodeType::TriggerReceiver) + continue; + + auto consumer = prefix() + detail::encodeDotNodeName(detail::nodeName(consumerNode)); + consumerNode.accept(*this); + std::string helperNode; + + if (!trigger.empty()) { + // Create trigger connection diamond + _triggerCount++; + helperNode = prefix() + "_trigger_helper_" + std::to_string(_triggerCount); + stream() << helperNode << "[label=\"\",shape=diamond,style=\"filled\",color=black,width=.3,height=.3,fixedsize=true,fillcolor=\"#ffcc00\"]\n"; + } + + stream() << feeder << " -> "; + if (!helperNode.empty()) { + stream() << helperNode << " -> " << consumer << "\n"; + // Hack: Make trigger lower in rank than all entry points to subgraphs + _triggerConnections.push_back(trigger + " -> " + feeder + "[style = invis]\n"); + + _triggerConnections.push_back(trigger + " -> " + helperNode + "[style = dashed, color=grey,tailport=s]\n"); + } else { + stream() << consumer << "\n"; + } + } + + stream() << "}\n"; + popPrefix(); +} + +VariableNetworkGraphDumpingVisitor::VariableNetworkGraphDumpingVisitor(std::ostream& stream) + : Visitor<Application, VariableNetwork> () + , VariableNetworkNodeDumpingVisitor(stream, "\\n") + , _triggerMap() + , _triggerConnections() + , _networkCount(0) + , _triggerCount(0) {} + + +void VariableNetworkGraphDumpingVisitor::dispatch(const Application& t) { + stream() << "digraph application {\n" + //<< " rankdir = LR;\n" + << " fontname=\"Sans\";\n" + << " fontsize=\"10\";\n" + << " labelloc=t;\n" + << " nodesep=1;\n" + //<< " splines=ortho;\n" + << " concentrate=true;\n" + << " label=\"<" << boost::core::demangle(typeid(t).name()) << ">" << t.getName() << "\";\n" + << " node [style=\"filled,rounded\", shape=box, fontsize=\"9\", fontname=\"sans\"];\n" + << " edge [labelfontsize=\"6\", fontsize=\"9\", fontname=\"monospace\"];\n" + << " \n"; + + for (auto &network : t.networkList) { + network.accept(*this); + } + + for (auto &t : _triggerMap) { + stream() << t.second; + } + + for (auto &t : _triggerConnections) { + stream() << t; + } + + stream() << "}\n"; +} + +void VariableNetworkGraphDumpingVisitor::dispatch(const VariableNetworkNode& t) { + std::string nodeName = prefix() + detail::encodeDotNodeName(detail::nodeName(t)); + stream() << nodeName << "[\n"; + if (t.getMode() == UpdateMode::push) + stream() << " fillcolor=\"#ff9900\";\n"; + else if (t.getMode() == UpdateMode::poll) + stream() << " fillcolor=\"#b4d848\";\n"; + else { + stream() << " fillcolor=\"#ffffff\";\n"; + } + + stream() << " label=\"" << detail::nodeName(t) << "\\n"; + + VariableNetworkNodeDumpingVisitor::dispatch(t); + + stream() << "\"]\n"; +} +} // namespace ChimeraTK diff --git a/src/VariableNetworkNode.cc b/src/VariableNetworkNode.cc index 650ca43f349203ed9f86e1c572f71ce6ac1668be..95919c3b0afa8282a6669ce65b0919ac687a6686 100644 --- a/src/VariableNetworkNode.cc +++ b/src/VariableNetworkNode.cc @@ -5,12 +5,12 @@ * Author: Martin Hierholzer */ -#include <libxml++/libxml++.h> - #include "VariableNetworkNode.h" #include "VariableNetwork.h" #include "Application.h" #include "EntityOwner.h" +#include "Visitor.h" +#include "VariableNetworkNodeDumpingVisitor.h" namespace ChimeraTK { @@ -125,104 +125,8 @@ namespace ChimeraTK { /*********************************************************************************************************************/ - void VariableNetworkNode::dump(std::ostream& stream) const { - if(pdata->type == NodeType::Application) stream << " type = Application ('" << pdata->name << "')"; - if(pdata->type == NodeType::ControlSystem) stream << " type = ControlSystem ('" << pdata->publicName << "')"; - if(pdata->type == NodeType::Device) stream << " type = Device (" << pdata->deviceAlias << ": " << pdata->registerName << ")"; - if(pdata->type == NodeType::TriggerReceiver) stream << " type = TriggerReceiver"; - if(pdata->type == NodeType::Constant) stream << " type = Constant"; - if(pdata->type == NodeType::invalid) stream << " type = **invalid**"; - - if(pdata->mode == UpdateMode::push) stream << " pushing"; - if(pdata->mode == UpdateMode::poll) stream << " polling"; - - stream << " data type: " << pdata->valueType->name(); - stream << " length: " << pdata->nElements; - - stream << " [ptr: " << &(*pdata) << "]"; - - stream << " { "; - for(auto &tag : pdata->tags) stream << tag << " "; - stream << "}"; - - stream << std::endl; -} - - /*********************************************************************************************************************/ - - void VariableNetworkNode::createXML(xmlpp::Element *rootElement) const { - if(pdata->type != NodeType::ControlSystem) return; - - // Create the directory for the path name in the XML document with all parent directories, if not yet existing: - // First split the publication name into components and loop over each component. For each component, try to find - // the directory node and create it it does not exist. After the loop, the "current" will point to the Element - // representing the directory. - - // strip the variable name from the path - mtca4u::RegisterPath directory(pdata->publicName); - directory--; - - // the namespace map is needed to properly refer to elements with an xpath expression in xmlpp::Element::find() - /// @todo TODO move this somewhere else, or at least take the namespace URI from a common place! - xmlpp::Node::PrefixNsMap nsMap{{"ac", "https://github.com/ChimeraTK/ApplicationCore"}}; - - // go through each directory path component - xmlpp::Element *current = rootElement; - for(auto pathComponent : directory.getComponents()) { - // find directory for this path component in the current directory - std::string xpath = std::string("ac:directory[@name='")+pathComponent+std::string("']"); - auto list = current->find(xpath, nsMap); - if(list.size() == 0) { // not found: create it - xmlpp::Element *newChild = current->add_child("directory"); - newChild->set_attribute("name",pathComponent); - current = newChild; - } - else { - assert(list.size() == 1); - current = dynamic_cast<xmlpp::Element*>(list[0]); - assert(current != nullptr); - } - } - - // now add the variable to the directory - xmlpp::Element *variable = current->add_child("variable"); - mtca4u::RegisterPath pathName(pdata->publicName); - auto pathComponents = pathName.getComponents(); - - // set the name attribute - variable->set_attribute("name",pathComponents[pathComponents.size()-1]); - - // add sub-element containing the data type - std::string dataTypeName{"unknown"}; - if(pdata->network->getValueType() == typeid(int8_t)) { dataTypeName = "int8"; } - else if(pdata->network->getValueType() == typeid(uint8_t)) { dataTypeName = "uint8"; } - else if(pdata->network->getValueType() == typeid(int16_t)) { dataTypeName = "int16"; } - else if(pdata->network->getValueType() == typeid(uint16_t)) { dataTypeName = "uint16"; } - else if(pdata->network->getValueType() == typeid(int32_t)) { dataTypeName = "int32"; } - else if(pdata->network->getValueType() == typeid(uint32_t)) { dataTypeName = "uint32"; } - else if(pdata->network->getValueType() == typeid(float)) { dataTypeName = "float"; } - else if(pdata->network->getValueType() == typeid(double)) { dataTypeName = "double"; } - else if(pdata->network->getValueType() == typeid(std::string)) { dataTypeName = "string"; } - xmlpp::Element *valueTypeElement = variable->add_child("value_type"); - valueTypeElement->set_child_text(dataTypeName); - - // add sub-element containing the data flow direction - std::string dataFlowName{"application_to_control_system"}; - if(pdata->network->getFeedingNode() == *this) { dataFlowName = "control_system_to_application"; } - xmlpp::Element *directionElement = variable->add_child("direction"); - directionElement->set_child_text(dataFlowName); - - // add sub-element containing the engineering unit - xmlpp::Element *unitElement = variable->add_child("unit"); - unitElement->set_child_text(pdata->network->getUnit()); - - // add sub-element containing the description - xmlpp::Element *descriptionElement = variable->add_child("description"); - descriptionElement->set_child_text(pdata->network->getDescription()); - - // add sub-element containing the description - xmlpp::Element *nElementsElement = variable->add_child("numberOfElements"); - nElementsElement->set_child_text(std::to_string(pdata->network->getFeedingNode().getNumberOfElements())); + void VariableNetworkNode::accept( Visitor<VariableNetworkNode>& visitor) const { + visitor.dispatch(*this); } /*********************************************************************************************************************/ @@ -347,6 +251,13 @@ namespace ChimeraTK { /*********************************************************************************************************************/ + void VariableNetworkNode::dump(std::ostream& stream) const { + VariableNetworkNodeDumpingVisitor visitor(stream, " "); + visitor.dispatch(*this); + } + + /*********************************************************************************************************************/ + bool VariableNetworkNode::hasOwner() const { return pdata->network != nullptr; } diff --git a/src/VariableNetworkNodeDumpingVisitor.cc b/src/VariableNetworkNodeDumpingVisitor.cc new file mode 100644 index 0000000000000000000000000000000000000000..fc8d7ae5b305537c222fc27b6d6f9aba47b2d355 --- /dev/null +++ b/src/VariableNetworkNodeDumpingVisitor.cc @@ -0,0 +1,45 @@ + +#include "VariableNetworkNodeDumpingVisitor.h" +#include "VariableNetworkNode.h" + +namespace ChimeraTK { + +VariableNetworkNodeDumpingVisitor::VariableNetworkNodeDumpingVisitor(std::ostream &stream, const std::string& separator) + : Visitor<ChimeraTK::VariableNetworkNode> () + , PushableStream(stream) + , _separator(separator) {} + +void VariableNetworkNodeDumpingVisitor::dispatch(const VariableNetworkNode& t) { + if(t.getType() == NodeType::Application) stream() << " type = Application ('" << t.getQualifiedName() << "')"; + if(t.getType() == NodeType::ControlSystem) stream() << " type = ControlSystem ('" << t.getPublicName() << "')"; + if(t.getType() == NodeType::Device) stream() << " type = Device (" << t.getDeviceAlias() << ": " << t.getRegisterName() << ")"; + if(t.getType() == NodeType::TriggerReceiver) stream() << " type = TriggerReceiver"; + if(t.getType() == NodeType::Constant) stream() << " type = Constant"; + if(t.getType() == NodeType::invalid) stream() << " type = **invalid**"; + + if(t.getMode() == UpdateMode::push) stream() << _separator << "pushing"; + if(t.getMode() == UpdateMode::poll) stream() << _separator << "polling"; + stream() << _separator; + + stream() << "data type: " << boost::core::demangle(t.getValueType().name()); + stream() << _separator; + stream() << "length: " << t.getNumberOfElements(); + stream() << _separator; + + stream() << "[ptr: " << &(*(t.pdata)) << "]"; + stream() << _separator; + + stream() << "tags: ["; + bool first = true; + for(auto &tag : t.getTags()) { + stream() << tag; + if (!first) stream() << ","; + first = false; + } + stream() << "]"; + stream() << _separator; + + stream() << std::endl; +} + +} // namespace ChimeraTK diff --git a/src/VisitorHelper.cc b/src/VisitorHelper.cc new file mode 100644 index 0000000000000000000000000000000000000000..98c4ddcc66625e6849fb2da4b91282cbb8c47667 --- /dev/null +++ b/src/VisitorHelper.cc @@ -0,0 +1,23 @@ +#include "VisitorHelper.h" +#include "VariableNetworkNode.h" + +namespace ChimeraTK { + +namespace detail { + +std::string encodeDotNodeName(std::string name) { + std::replace(name.begin(), name.end(), ':', 'c'); // colon + std::replace(name.begin(), name.end(), '/', 's'); // slash + std::replace(name.begin(), name.end(), '.', 'd'); // dot + std::replace(name.begin(), name.end(), ' ', '_'); // Generic space replacer + std::replace(name.begin(), name.end(), '*', 'a'); // asterisk + + return name; +} + +std::string nodeName(const VariableNetworkNode& node) { + return node.getQualifiedName().empty() ? node.getName() : node.getQualifiedName(); +} + +} // namespace detail +} // namespace ChimeraTK diff --git a/src/XMLGeneratorVisitor.cc b/src/XMLGeneratorVisitor.cc new file mode 100644 index 0000000000000000000000000000000000000000..a47d215c06a049ec1ae72192edcc651dcb5512a0 --- /dev/null +++ b/src/XMLGeneratorVisitor.cc @@ -0,0 +1,114 @@ +#include "Application.h" +#include "VariableNetworkNode.h" + +#include "XMLGeneratorVisitor.h" + +#include <mtca4u/RegisterPath.h> +#include <libxml++/libxml++.h> + +#include <cassert> + +namespace ChimeraTK { +XMLGeneratorVisitor::XMLGeneratorVisitor() + : Visitor<ChimeraTK::Application, ChimeraTK::VariableNetworkNode>() + , _doc(std::make_shared<xmlpp::Document>()) + , _rootElement(_doc->create_root_node("application", "https://github.com/ChimeraTK/ApplicationCore")) +{ +} + +void XMLGeneratorVisitor::save(const std::string &fileName) { + _doc->write_to_file_formatted(fileName); +} + +void XMLGeneratorVisitor::dispatch(const Application& app) { + _rootElement->set_attribute("name", app.getName()); + for (auto &network : app.networkList) { + network.check(); + + auto feeder = network.getFeedingNode(); + feeder.accept(*this); + + for (auto &consumer : network.getConsumingNodes()) { + consumer.accept(*this); + } + } +} + +void XMLGeneratorVisitor::dispatch(const VariableNetworkNode &node) { + if(node.getType() != NodeType::ControlSystem) return; + + // Create the directory for the path name in the XML document with all parent directories, if not yet existing: + // First split the publication name into components and loop over each component. For each component, try to find + // the directory node and create it it does not exist. After the loop, the "current" will point to the Element + // representing the directory. + + // strip the variable name from the path + mtca4u::RegisterPath directory(node.getPublicName()); + directory--; + + // the namespace map is needed to properly refer to elements with an xpath expression in xmlpp::Element::find() + /// @todo TODO move this somewhere else, or at least take the namespace URI from a common place! + xmlpp::Node::PrefixNsMap nsMap{{"ac", "https://github.com/ChimeraTK/ApplicationCore"}}; + + // go through each directory path component + xmlpp::Element *current = _rootElement; + for(auto pathComponent : directory.getComponents()) { + // find directory for this path component in the current directory + std::string xpath = std::string("ac:directory[@name='")+pathComponent+std::string("']"); + auto list = current->find(xpath, nsMap); + if(list.size() == 0) { // not found: create it + xmlpp::Element *newChild = current->add_child("directory"); + newChild->set_attribute("name",pathComponent); + current = newChild; + } + else { + assert(list.size() == 1); + current = dynamic_cast<xmlpp::Element*>(list[0]); + assert(current != nullptr); + } + } + + // now add the variable to the directory + xmlpp::Element *variable = current->add_child("variable"); + mtca4u::RegisterPath pathName(node.getPublicName()); + auto pathComponents = pathName.getComponents(); + + // set the name attribute + variable->set_attribute("name",pathComponents[pathComponents.size()-1]); + + // add sub-element containing the data type + std::string dataTypeName{"unknown"}; + auto &owner = node.getOwner(); + auto &type = owner.getValueType(); + if(type == typeid(int8_t)) { dataTypeName = "int8"; } + else if(type == typeid(uint8_t)) { dataTypeName = "uint8"; } + else if(type == typeid(int16_t)) { dataTypeName = "int16"; } + else if(type == typeid(uint16_t)) { dataTypeName = "uint16"; } + else if(type == typeid(int32_t)) { dataTypeName = "int32"; } + else if(type == typeid(uint32_t)) { dataTypeName = "uint32"; } + else if(type == typeid(float)) { dataTypeName = "float"; } + else if(type == typeid(double)) { dataTypeName = "double"; } + else if(type == typeid(std::string)) { dataTypeName = "string"; } + xmlpp::Element *valueTypeElement = variable->add_child("value_type"); + valueTypeElement->set_child_text(dataTypeName); + + // add sub-element containing the data flow direction + std::string dataFlowName{"application_to_control_system"}; + if(owner.getFeedingNode() == node) { dataFlowName = "control_system_to_application"; } + xmlpp::Element *directionElement = variable->add_child("direction"); + directionElement->set_child_text(dataFlowName); + + // add sub-element containing the engineering unit + xmlpp::Element *unitElement = variable->add_child("unit"); + unitElement->set_child_text(owner.getUnit()); + + // add sub-element containing the description + xmlpp::Element *descriptionElement = variable->add_child("description"); + descriptionElement->set_child_text(owner.getDescription()); + + // add sub-element containing the description + xmlpp::Element *nElementsElement = variable->add_child("numberOfElements"); + nElementsElement->set_child_text(std::to_string(owner.getFeedingNode().getNumberOfElements())); +} + +} // namespace ChimeraTK