diff --git a/include/reactor-cpp/environment.hh b/include/reactor-cpp/environment.hh index 693fa51e..14a49715 100644 --- a/include/reactor-cpp/environment.hh +++ b/include/reactor-cpp/environment.hh @@ -59,14 +59,14 @@ private: /// Timeout as given in the constructor const Duration timeout_{}; + Graph graph_{}; + Graph optimized_graph_{}; + /// The start tag as determined during startup() Tag start_tag_{}; /// The timeout tag as determined during startup() Tag timeout_tag_{}; - Graph graph_{}; - Graph optimized_graph_{}; - void build_dependency_graph(Reactor* reactor); void calculate_indexes(); @@ -98,7 +98,6 @@ public: void optimize(); void register_reactor(Reactor* reactor); - void register_port(BasePort* port) noexcept; void register_input_action(BaseAction* action); void assemble(); auto startup() -> std::thread; diff --git a/include/reactor-cpp/graph.hh b/include/reactor-cpp/graph.hh old mode 100644 new mode 100755 index 4009e169..7786dfed --- a/include/reactor-cpp/graph.hh +++ b/include/reactor-cpp/graph.hh @@ -9,16 +9,39 @@ #ifndef REACTOR_CPP_GRAPH_HH #define REACTOR_CPP_GRAPH_HH +#include #include #include #include +#include #include namespace reactor { +class GraphElement { +public: + GraphElement() noexcept = default; + GraphElement(const GraphElement& graph) noexcept = default; + GraphElement(GraphElement&& graph) noexcept = default; + + virtual ~GraphElement() noexcept = default; + [[nodiscard]] virtual auto connected_to_downstream_actions() const noexcept -> bool = 0; + [[nodiscard]] virtual auto connected_to_upstream_actions() const noexcept -> bool = 0; + [[nodiscard]] virtual auto rating() const noexcept -> std::size_t = 0; + + auto operator=([[maybe_unused]] const GraphElement& other) noexcept -> GraphElement& = default; + auto operator=([[maybe_unused]] GraphElement&& other) noexcept -> GraphElement& = default; +}; + // this graph is special, because to every edge properties are annotated -template class Graph { +template class Graph { + // static_assert(std::is_base_of_v); + using E = X*; + using P = ConnectionProperties; + private: - std::map>> graph_; + using Path = std::vector>; + std::map>> graph_{}; + std::set nodes_{}; // custom compare operator this is required if u want special key values in std::map // this is required for the Graph::get_edges() method @@ -50,14 +73,20 @@ public: // adds a single edge to the graph structure void add_edge(E source, E destination, P properties) noexcept { + nodes_.insert(source); + nodes_.insert(destination); + if (graph_.find(source) == std::end(graph_)) { - std::vector> edges{std::make_pair(properties, destination)}; + std::vector> edges; + edges.emplace_back(properties, destination); graph_[source] = edges; } else { graph_[source].emplace_back(properties, destination); } } + auto get_nodes() -> std::set { return nodes_; } + // this groups connections by same source and properties [[nodiscard]] auto get_edges() const noexcept -> std::map, map_key_compare> { std::map, map_key_compare> all_edges{}; @@ -85,43 +114,188 @@ public: return keys; } - // returns the spanning tree of a given source including properties - [[nodiscard]] auto spanning_tree(E source) noexcept -> std::map>> { - std::map>> tree{}; - std::vector work_nodes{source}; - - while (!work_nodes.empty()) { - auto parent = *work_nodes.begin(); - - for (auto child : graph_[parent]) { - // figuring out the properties until this node - std::vector> parent_properties{}; - if (tree.find(parent) != std::end(tree)) { - // this if should always be the case except the first time when tree is empty - parent_properties = tree[parent]; // TODO: make sure this is a copy otherwise we change this properties as - // well - } + // the return type looks a little bit cursed what is happening here ? + // we have a map from the destination as a key to a list of paths through the graph. + // A path here is modelled by a list of edges (with properties and the next vertex). + auto naive_spanning_tree(E source) noexcept -> std::vector>> { + return recursive_spanning_tree(source, std::vector{}); + } - // appending the new property and inserting into the tree - parent_properties.push_back(child); - work_nodes.push_back(child.second); - tree[child.second] = parent_properties; - } + // this function goes recursively though the graph and tries to find every possible path + auto recursive_spanning_tree(E source_node, std::vector visited_nodes) + -> std::vector>> { + std::vector paths{}; - work_nodes.erase(std::begin(work_nodes)); + if (graph_[source_node].empty()) { + return std::vector{Path{}}; } - return tree; + // if this node has an action we need to append the path + if (source_node->connected_to_downstream_actions()) { + paths.push_back(Path{}); + } + + for (auto child : graph_[source_node]) { + E current_node = child.second; + + // we dont need to check for cycles because lf semantics assure that there wont be any cycles + for (auto path : recursive_spanning_tree(current_node, visited_nodes)) { + path.push_back(std::make_tuple(source_node, child.first, current_node)); + paths.push_back(path); + } + } + + return paths; } [[nodiscard]] auto get_destinations(E source) const noexcept -> std::vector> { - return graph_[source]; + return this->graph_.at(source); } - [[nodiscard]] auto get_upstream(E vertex) const noexcept -> std::optional { - for (const auto& [source, sinks] : graph_) { - if (sinks.second.contains(vertex)) { - return source; + [[nodiscard]] auto to_mermaid() const noexcept -> std::string { + std::string mermaid_string = "graph TD;\n"; + std::size_t index{0}; + std::map name_map{}; + + auto name_resolver = [&](E object) -> std::string { + char names[] = "ABCDEFGHIJKLMNOPQRSTUVGXYZabcdefghijklmnopqrstuvgxyz"; // NOLINT + if (name_map.find(object) == std::end(name_map)) { + name_map[object] = names[index]; + index++; + return std::string{names[index - 1], 1}; + } + return name_map[object]; + }; + + for (const auto& [source, destinations] : graph_) { + for (auto dest : destinations) { + mermaid_string += std::string(" ") + name_resolver(source) + std::string("-->") + + name_resolver(dest.second) + std::string(";\n"); + } + } + return mermaid_string; + } + + void optimize(Graph& optimized_graph) { + optimized_graph.clear(); + + static std::map, ConnectionType> construction_table = { + // Normal + x + {std::make_pair(Normal, Normal), Normal}, + {std::make_pair(Normal, Delayed), Delayed}, + {std::make_pair(Normal, Enclaved), Enclaved}, + {std::make_pair(Normal, Physical), Physical}, + {std::make_pair(Normal, DelayedEnclaved), DelayedEnclaved}, + {std::make_pair(Normal, PhysicalEnclaved), PhysicalEnclaved}, + {std::make_pair(Normal, Plugin), Plugin}, + // Delayed + x + {std::make_pair(Delayed, Normal), Delayed}, + {std::make_pair(Delayed, Delayed), Delayed}, + {std::make_pair(Delayed, Enclaved), DelayedEnclaved}, + {std::make_pair(Delayed, Physical), Invalid}, //!!! + {std::make_pair(Delayed, DelayedEnclaved), DelayedEnclaved}, + {std::make_pair(Delayed, PhysicalEnclaved), Invalid}, //!!! + {std::make_pair(Delayed, Plugin), Invalid}, + // Enclaved + x + {std::make_pair(Enclaved, Normal), Enclaved}, + {std::make_pair(Enclaved, Delayed), DelayedEnclaved}, + {std::make_pair(Enclaved, Enclaved), Enclaved}, + {std::make_pair(Enclaved, Physical), PhysicalEnclaved}, + {std::make_pair(Enclaved, DelayedEnclaved), DelayedEnclaved}, + {std::make_pair(Enclaved, PhysicalEnclaved), PhysicalEnclaved}, + {std::make_pair(Enclaved, Plugin), Invalid}, + // Physical + x + {std::make_pair(Physical, Normal), Physical}, + {std::make_pair(Physical, Delayed), Invalid}, // !!! + {std::make_pair(Physical, Enclaved), PhysicalEnclaved}, + {std::make_pair(Physical, Physical), Physical}, + {std::make_pair(Physical, DelayedEnclaved), Invalid}, // !!! + {std::make_pair(Physical, PhysicalEnclaved), PhysicalEnclaved}, + {std::make_pair(Physical, Plugin), Invalid}, + // DelayedEnclaved + x + {std::make_pair(DelayedEnclaved, Normal), DelayedEnclaved}, + {std::make_pair(DelayedEnclaved, Delayed), DelayedEnclaved}, + {std::make_pair(DelayedEnclaved, Enclaved), DelayedEnclaved}, + {std::make_pair(DelayedEnclaved, Physical), Invalid}, // !!! + {std::make_pair(DelayedEnclaved, DelayedEnclaved), DelayedEnclaved}, + {std::make_pair(DelayedEnclaved, PhysicalEnclaved), Invalid}, // !!! + {std::make_pair(DelayedEnclaved, Plugin), Invalid}, + // PhysicalEnclaved + x + {std::make_pair(PhysicalEnclaved, Normal), PhysicalEnclaved}, + {std::make_pair(PhysicalEnclaved, Delayed), Invalid}, // !!! + {std::make_pair(PhysicalEnclaved, Enclaved), PhysicalEnclaved}, + {std::make_pair(PhysicalEnclaved, Physical), PhysicalEnclaved}, + {std::make_pair(PhysicalEnclaved, DelayedEnclaved), Invalid}, // !!! + {std::make_pair(PhysicalEnclaved, PhysicalEnclaved), PhysicalEnclaved}, + {std::make_pair(PhysicalEnclaved, Plugin), Invalid}, + // Plugin + x = Invalid + {std::make_pair(Plugin, Normal), Invalid}, // !!! + {std::make_pair(Plugin, Delayed), Invalid}, // !!! + {std::make_pair(Plugin, Enclaved), Invalid}, // !!! + {std::make_pair(Plugin, Physical), Invalid}, // !!! + {std::make_pair(Plugin, DelayedEnclaved), Invalid}, // !!! + {std::make_pair(Plugin, PhysicalEnclaved), Invalid}, // !!! + {std::make_pair(Plugin, Plugin), Invalid}, // !!! + }; + + // getting all the sources from the graph + auto keys = this->keys(); + + std::vector has_downstreams{}; + std::copy_if(keys.begin(), keys.end(), std::back_inserter(has_downstreams), + [](auto element) { return element->connected_to_downstream_actions(); }); + + std::vector has_upstreams{}; + std::copy_if(keys.begin(), keys.end(), std::back_inserter(has_upstreams), + [](auto element) { return element->connected_to_upstream_actions(); }); + + // generating all the possible destinations for all sources + for (auto* source : has_upstreams) { + auto spanning_tree = naive_spanning_tree(source); + + for (auto& path : spanning_tree) { + if (path.empty()) { + continue; + } + + ConnectionProperties merged_properties{}; + auto* final_destination = std::get<2>(*std::begin(path)); + std::size_t current_rating = 0; + + for (auto edge : path) { + auto property = std::get<1>(edge); + // auto source_port = std::get<0>(edge); + auto* destination_port = std::get<2>(edge); + + current_rating += destination_port->rating(); + + if (current_rating > 0) { + auto return_type = + construction_table[std::pair(merged_properties.type_, property.type_)]; + // invalid will split the connections + if (return_type == Invalid) { + // first add connection until this point + optimized_graph.add_edge(destination_port, final_destination, merged_properties); // NOLINT + + // resetting the properties and destination_port + final_destination = destination_port; + merged_properties = property; + + } else { + + // merging the connections + merged_properties.type_ = return_type; + + // adding up delays + merged_properties.delay_ += property.delay_; + + // updating target enclave if not nullptr + merged_properties.enclave_ = + (property.enclave_ != nullptr) ? property.enclave_ : merged_properties.enclave_; + } + } + } + optimized_graph.add_edge(std::get<0>(*(std::end(path) - 1)), final_destination, merged_properties); } } } @@ -136,4 +310,4 @@ public: } }; } // namespace reactor -#endif // REACTOR_CPP_GRAPH_HH \ No newline at end of file +#endif // REACTOR_CPP_GRAPH_HH diff --git a/include/reactor-cpp/port.hh b/include/reactor-cpp/port.hh index 6fe01494..8999a876 100644 --- a/include/reactor-cpp/port.hh +++ b/include/reactor-cpp/port.hh @@ -15,6 +15,7 @@ #include "assert.hh" #include "connection_properties.hh" #include "fwd.hh" +#include "graph.hh" #include "multiport.hh" #include "reactor_element.hh" #include "value_ptr.hh" @@ -23,7 +24,7 @@ namespace reactor { enum class PortType { Input, Output, Delay }; -class BasePort : public ReactorElement { +class BasePort : public GraphElement, public ReactorElement { // NOLINT private: BasePort* inward_binding_{nullptr}; std::set outward_bindings_{}; @@ -71,6 +72,8 @@ protected: } public: + ~BasePort() noexcept override = default; + void set_inward_binding(BasePort* port) noexcept { inward_binding_ = port; } void add_outward_binding(BasePort* port) noexcept { outward_bindings_.insert(port); // NOLINT @@ -92,6 +95,7 @@ public: [[nodiscard]] inline auto has_outward_bindings() const noexcept -> bool { return !outward_bindings_.empty(); } [[nodiscard]] inline auto has_dependencies() const noexcept -> bool { return !dependencies_.empty(); } [[nodiscard]] inline auto has_anti_dependencies() const noexcept -> bool { return !anti_dependencies_.empty(); } + [[nodiscard]] inline auto has_triggers() const noexcept -> bool { return !triggers_.empty(); } [[nodiscard]] inline auto inward_binding() const noexcept -> BasePort* { return inward_binding_; } [[nodiscard]] inline auto outward_bindings() const noexcept -> const auto& { return outward_bindings_; } @@ -101,6 +105,14 @@ public: [[nodiscard]] inline auto anti_dependencies() const noexcept -> const auto& { return anti_dependencies_; } [[nodiscard]] inline auto port_type() const noexcept -> PortType { return type_; } + [[nodiscard]] auto connected_to_downstream_actions() const noexcept -> bool final { + return has_dependencies() || has_triggers(); + }; + [[nodiscard]] auto connected_to_upstream_actions() const noexcept -> bool final { return has_anti_dependencies(); }; + [[nodiscard]] auto rating() const noexcept -> std::size_t final { + return dependencies_.size() + triggers_.size() + ((set_callback_ != nullptr) ? 1 : 0); + } + void register_set_callback(const PortCallback& callback); void register_clean_callback(const PortCallback& callback); @@ -170,7 +182,7 @@ public: Input(const std::string& name, Reactor* container) : Port(name, PortType::Input, container) {} - Input(Input&&) = default; // NOLINT(performance-noexcept-move-constructor) + Input(Input&&) noexcept = default; // NOLINT(performance-noexcept-move-constructor) }; template class Output : public Port { // NOLINT @@ -178,7 +190,7 @@ public: Output(const std::string& name, Reactor* container) : Port(name, PortType::Output, container) {} - Output(Output&&) = default; // NOLINT(performance-noexcept-move-constructor) + Output(Output&&) noexcept = default; // NOLINT(performance-noexcept-move-constructor) }; } // namespace reactor diff --git a/include/reactor-cpp/reactor_element.hh b/include/reactor-cpp/reactor_element.hh index f6322058..94bbbbaa 100644 --- a/include/reactor-cpp/reactor_element.hh +++ b/include/reactor-cpp/reactor_element.hh @@ -37,7 +37,7 @@ public: virtual ~ReactorElement() = default; // not copyable, but movable - ReactorElement(const ReactorElement&) = delete; + ReactorElement(const ReactorElement&) = default; ReactorElement(ReactorElement&&) = default; [[nodiscard]] auto container() const noexcept -> Reactor* { return container_; } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index fd47bcfb..8b6614dd 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -48,6 +48,13 @@ set_target_properties(${LIB_TARGET} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 1) +option(GRAPH_OPTIMIZATIONS "Graph optimizations" ON) +if(GRAPH_OPTIMIZATIONS) + target_compile_definitions(${LIB_TARGET} PRIVATE GRAPH_OPTIMIZATIONS=1 ) +else (GRAPH_OPTIMIZATIONS) + target_compile_definitions(${LIB_TARGET} PRIVATE GRAPH_OPTIMIZATIONS=0 ) +endif(GRAPH_OPTIMIZATIONS) + if(DEFINED LF_REACTOR_CPP_SUFFIX) install(FILES "${PROJECT_BINARY_DIR}/include/reactor-cpp/config.hh" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${LIB_TARGET}/reactor-cpp") else() diff --git a/lib/environment.cc b/lib/environment.cc index d753a877..6b6f17bb 100644 --- a/lib/environment.cc +++ b/lib/environment.cc @@ -63,8 +63,20 @@ void Environment::register_input_action(BaseAction* action) { } void Environment::optimize() { - // no optimizations - optimized_graph_ = graph_; +#if GRAPH_OPTIMIZATIONS + constexpr bool enable_optimizations = true; +#else + constexpr bool enable_optimizations = false; +#endif + log::Debug() << "Opimizations:" << enable_optimizations; + if constexpr (enable_optimizations) { + log::Debug() << graph_.to_mermaid(); + graph_.optimize(optimized_graph_); + log::Debug() << optimized_graph_.to_mermaid(); + } else { + // no optimizations + optimized_graph_ = graph_; + } } void recursive_assemble(Reactor* container) { // NOLINT @@ -96,6 +108,7 @@ void Environment::assemble() { // NOLINT this->optimize(); log::Debug() << "instantiating port graph declaration"; + log::Debug() << "graph: "; log::Debug() << optimized_graph_; @@ -142,6 +155,13 @@ void Environment::assemble() { // NOLINT } } } + + log::Debug() << "Building the Dependency-Graph"; + for (auto* reactor : top_level_reactors_) { + build_dependency_graph(reactor); + } + + calculate_indexes(); } void Environment::build_dependency_graph(Reactor* reactor) { // NOLINT diff --git a/lib/port.cc b/lib/port.cc index 8525df66..401f9828 100644 --- a/lib/port.cc +++ b/lib/port.cc @@ -30,6 +30,8 @@ void BasePort::register_dependency(Reaction* reaction, bool is_trigger) noexcept "Dependent output ports must belong to a contained reactor"); } + log::Debug() << "registering dependency for : " << this->fqn() << " with reaction: " << reaction->fqn() + << " is trigger: " << is_trigger; [[maybe_unused]] bool result = dependencies_.insert(reaction).second; reactor_assert(result); if (is_trigger) { diff --git a/lib/reactor_element.cc b/lib/reactor_element.cc index db111566..9fd1d5f0 100644 --- a/lib/reactor_element.cc +++ b/lib/reactor_element.cc @@ -39,10 +39,10 @@ ReactorElement::ReactorElement(const std::string& name, ReactorElement::Type typ container->register_action(reinterpret_cast(this)); // NOLINT break; case Type::Input: - container->register_input(reinterpret_cast(this)); // NOLINT + container->register_input(static_cast(this)); // NOLINT break; case Type::Output: - container->register_output(reinterpret_cast(this)); // NOLINT + container->register_output(static_cast(this)); // NOLINT break; case Type::Reaction: container->register_reaction(reinterpret_cast(this)); // NOLINT