diff --git a/include/reactor-cpp/environment.hh b/include/reactor-cpp/environment.hh index a30bb8cd..ad8d67bd 100644 --- a/include/reactor-cpp/environment.hh +++ b/include/reactor-cpp/environment.hh @@ -59,8 +59,8 @@ private: const Duration timeout_{}; - Graph<BasePort*, ConnectionProperties> graph_{}; - Graph<BasePort*, ConnectionProperties> optimized_graph_{}; + Graph<BasePort> graph_{}; + Graph<BasePort> optimized_graph_{}; void build_dependency_graph(Reactor* reactor); void calculate_indexes(); @@ -91,7 +91,6 @@ public: } void optimize(); - void expand_and_merge(); void register_reactor(Reactor* reactor); void register_input_action(BaseAction* action); diff --git a/include/reactor-cpp/graph.hh b/include/reactor-cpp/graph.hh index 45bd455c..cc7a0c8e 100644 --- a/include/reactor-cpp/graph.hh +++ b/include/reactor-cpp/graph.hh @@ -12,13 +12,34 @@ #include <iostream> #include <map> #include <optional> +#include <type_traits> #include <vector> 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 E, class P> class Graph { +template <class X> class Graph { + // static_assert(std::is_base_of_v<GraphElement, X>); + using E = X*; + using P = ConnectionProperties; + private: - std::map<E, std::vector<std::pair<P, E>>> graph_; + using Path = std::vector<std::tuple<E, P, E>>; + std::map<E, std::vector<std::pair<P, E>>> graph_{}; std::set<E> nodes_{}; // custom compare operator this is required if u want special key values in std::map @@ -39,8 +60,6 @@ public: : graph_(std::move(graph.graph_)) {} ~Graph() noexcept = default; - using Path = std::vector<std::pair<P, E>>; - auto operator=(const Graph other) noexcept -> Graph& { graph_ = other.graph_; return *this; @@ -55,8 +74,10 @@ public: void add_edge(E source, E destination, P properties) noexcept { nodes_.insert(source); nodes_.insert(destination); + if (graph_.find(source) == std::end(graph_)) { - Path edges{std::make_pair(properties, destination)}; + std::vector<std::pair<P, E>> edges; + edges.emplace_back(properties, destination); graph_[source] = edges; } else { graph_[source].emplace_back(properties, destination); @@ -95,79 +116,45 @@ public: // 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<std::vector<std::pair<P, E>>> { + auto naive_spanning_tree(E source) noexcept -> std::vector<std::vector<std::tuple<E, P, E>>> { return recursive_spanning_tree(source, std::vector<E>{}); } // this function goes recursively though the graph and tries to find every possible path auto recursive_spanning_tree(E source_node, std::vector<E> visited_nodes) - -> std::vector<std::vector<std::pair<P, E>>> { + -> std::vector<std::vector<std::tuple<E, P, E>>> { std::vector<Path> paths{}; if (graph_[source_node].empty()) { return std::vector<Path>{Path{}}; } + // 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; - // means this node has not been visited yey - if (std::find(std::begin(visited_nodes), std::end(visited_nodes), current_node) == std::end(visited_nodes)) { - - // creating a temporary vector where the currently selected vertex is appended - auto temp_nodes = visited_nodes; - temp_nodes.push_back(current_node); - - for (auto path : recursive_spanning_tree(current_node, temp_nodes)) { - path.insert(std::begin(path), child); - paths.push_back(path); - } + // 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; } - auto shortest_path(E source, E destination) -> std::optional<Path> { - // TODO: maybe build proper djikstra here - - auto spanning_tre = naive_spanning_tree(source); - std::vector<Path> relevant_paths{}; - - std::copy_if(spanning_tre.begin(), spanning_tre.end(), std::back_inserter(relevant_paths), - [&, destination](Path path) { return path[path.size() - 1].second == destination; }); - - if (relevant_paths.empty()) { - return std::nullopt; - } - - Path best_path = *relevant_paths.begin(); - - for (auto path : relevant_paths) { - if (path.size() < best_path.size()) { - best_path = path; - } - } - - return best_path; - } - [[nodiscard]] auto get_destinations(E source) const noexcept -> std::vector<std::pair<P, E>> { - return graph_[source]; - } - - [[nodiscard]] auto get_upstream(E vertex) const noexcept -> std::optional<E> { - for (const auto& [source, sinks] : graph_) { - if (sinks.second.contains(vertex)) { - return source; - } - } + return this->graph_.at(source); } [[nodiscard]] auto to_mermaid() const noexcept -> std::string { + std::string mermaid_string = "graph TD;\n"; std::size_t index{0}; std::map<E, std::string> name_map{}; - std::string mermaid_string = "graph TD;\n"; auto name_resolver = [&](E object) -> std::string { char names[] = "ABCDEFGHIJKLMNOPQRSTUVGXYZabcdefghijklmnopqrstuvgxyz"; // NOLINT @@ -188,6 +175,126 @@ public: return mermaid_string; } + void optimize(Graph<X>& optimized_graph) { + optimized_graph.clear(); + + static std::map<std::pair<ConnectionType, ConnectionType>, ConnectionType> construction_table = { + // Normal + x + {std::make_pair<ConnectionType, ConnectionType>(Normal, Normal), Normal}, + {std::make_pair<ConnectionType, ConnectionType>(Normal, Delayed), Delayed}, + {std::make_pair<ConnectionType, ConnectionType>(Normal, Enclaved), Enclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Normal, Physical), Physical}, + {std::make_pair<ConnectionType, ConnectionType>(Normal, DelayedEnclaved), DelayedEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Normal, PhysicalEnclaved), PhysicalEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Normal, Plugin), Plugin}, + // Delayed + x + {std::make_pair<ConnectionType, ConnectionType>(Delayed, Normal), Delayed}, + {std::make_pair<ConnectionType, ConnectionType>(Delayed, Delayed), Delayed}, + {std::make_pair<ConnectionType, ConnectionType>(Delayed, Enclaved), DelayedEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Delayed, Physical), Invalid}, //!!! + {std::make_pair<ConnectionType, ConnectionType>(Delayed, DelayedEnclaved), DelayedEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Delayed, PhysicalEnclaved), Invalid}, //!!! + {std::make_pair<ConnectionType, ConnectionType>(Delayed, Plugin), Invalid}, + // Enclaved + x + {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Normal), Enclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Delayed), DelayedEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Enclaved), Enclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Physical), PhysicalEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Enclaved, DelayedEnclaved), DelayedEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Enclaved, PhysicalEnclaved), PhysicalEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Plugin), Invalid}, + // Physical + x + {std::make_pair<ConnectionType, ConnectionType>(Physical, Normal), Physical}, + {std::make_pair<ConnectionType, ConnectionType>(Physical, Delayed), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(Physical, Enclaved), PhysicalEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Physical, Physical), Physical}, + {std::make_pair<ConnectionType, ConnectionType>(Physical, DelayedEnclaved), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(Physical, PhysicalEnclaved), PhysicalEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(Physical, Plugin), Invalid}, + // DelayedEnclaved + x + {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Normal), DelayedEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Delayed), DelayedEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Enclaved), DelayedEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Physical), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, DelayedEnclaved), DelayedEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, PhysicalEnclaved), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Plugin), Invalid}, + // PhysicalEnclaved + x + {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Normal), PhysicalEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Delayed), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Enclaved), PhysicalEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Physical), PhysicalEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, DelayedEnclaved), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, PhysicalEnclaved), PhysicalEnclaved}, + {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Plugin), Invalid}, + // Plugin + x = Invalid + {std::make_pair<ConnectionType, ConnectionType>(Plugin, Normal), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(Plugin, Delayed), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(Plugin, Enclaved), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(Plugin, Physical), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(Plugin, DelayedEnclaved), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(Plugin, PhysicalEnclaved), Invalid}, // !!! + {std::make_pair<ConnectionType, ConnectionType>(Plugin, Plugin), Invalid}, // !!! + }; + + // getting all the sources from the graph + auto keys = this->keys(); + + std::vector<E> has_downstreams{}; + std::copy_if(keys.begin(), keys.end(), std::back_inserter(has_downstreams), + [](auto element) { return element->connected_to_downstream_actions(); }); + + std::vector<E> 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) { + 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<ConnectionType, ConnectionType>(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); + } + } + } + friend auto operator<<(std::ostream& outstream, const Graph& graph) -> std::ostream& { for (auto const& [source, destinations] : graph.graph_) { for (auto destination : destinations) { diff --git a/include/reactor-cpp/port.hh b/include/reactor-cpp/port.hh index ec2b750b..690adcc7 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 ReactorElement, public GraphElement { private: BasePort* inward_binding_{nullptr}; std::set<BasePort*> 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 @@ -93,7 +96,6 @@ public: [[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 rating() const noexcept -> std::size_t { return triggers_.size() + dependencies_.size(); } [[nodiscard]] inline auto inward_binding() const noexcept -> BasePort* { return inward_binding_; } [[nodiscard]] inline auto outward_bindings() const noexcept -> const auto& { return outward_bindings_; } @@ -103,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); diff --git a/lib/environment.cc b/lib/environment.cc index 77103066..ae154a70 100644 --- a/lib/environment.cc +++ b/lib/environment.cc @@ -24,16 +24,6 @@ namespace reactor { -void vector_shuffle(std::vector<std::pair<ConnectionProperties, BasePort*>>& path, BasePort* source) { - for (auto it = std::begin(path); it != std::end(path); ++it) { - if (std::next(it) == std::end(path)) { - it->second = source; - } else { - it->second = std::next(it)->second; - } - } -}; - Environment::Environment(unsigned int num_workers, bool fast_fwd_execution, const Duration& timeout) : log_("Environment") , num_workers_(num_workers) @@ -81,7 +71,7 @@ void Environment::optimize() { log::Debug() << "Opimizations:" << enable_optimizations; if constexpr (enable_optimizations) { log::Debug() << graph_.to_mermaid(); - expand_and_merge(); + graph_.optimize(optimized_graph_); log::Debug() << optimized_graph_.to_mermaid(); } else { // no optimizations @@ -89,121 +79,6 @@ void Environment::optimize() { } } -void Environment::expand_and_merge() { - - static std::map<std::pair<ConnectionType, ConnectionType>, ConnectionType> construction_table = { - // Normal + x - {std::make_pair<ConnectionType, ConnectionType>(Normal, Normal), Normal}, - {std::make_pair<ConnectionType, ConnectionType>(Normal, Delayed), Delayed}, - {std::make_pair<ConnectionType, ConnectionType>(Normal, Enclaved), Enclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Normal, Physical), Physical}, - {std::make_pair<ConnectionType, ConnectionType>(Normal, DelayedEnclaved), DelayedEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Normal, PhysicalEnclaved), PhysicalEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Normal, Plugin), Plugin}, - // Delayed + x - {std::make_pair<ConnectionType, ConnectionType>(Delayed, Normal), Delayed}, - {std::make_pair<ConnectionType, ConnectionType>(Delayed, Delayed), Delayed}, - {std::make_pair<ConnectionType, ConnectionType>(Delayed, Enclaved), DelayedEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Delayed, Physical), Invalid}, //!!! - {std::make_pair<ConnectionType, ConnectionType>(Delayed, DelayedEnclaved), DelayedEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Delayed, PhysicalEnclaved), Invalid}, //!!! - {std::make_pair<ConnectionType, ConnectionType>(Delayed, Plugin), Invalid}, - // Enclaved + x - {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Normal), Enclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Delayed), DelayedEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Enclaved), Enclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Physical), PhysicalEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Enclaved, DelayedEnclaved), DelayedEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Enclaved, PhysicalEnclaved), PhysicalEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Enclaved, Plugin), Invalid}, - // Physical + x - {std::make_pair<ConnectionType, ConnectionType>(Physical, Normal), Physical}, - {std::make_pair<ConnectionType, ConnectionType>(Physical, Delayed), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(Physical, Enclaved), PhysicalEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Physical, Physical), Physical}, - {std::make_pair<ConnectionType, ConnectionType>(Physical, DelayedEnclaved), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(Physical, PhysicalEnclaved), PhysicalEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(Physical, Plugin), Invalid}, - // DelayedEnclaved + x - {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Normal), DelayedEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Delayed), DelayedEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Enclaved), DelayedEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Physical), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, DelayedEnclaved), DelayedEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, PhysicalEnclaved), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(DelayedEnclaved, Plugin), Invalid}, - // PhysicalEnclaved + x - {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Normal), PhysicalEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Delayed), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Enclaved), PhysicalEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Physical), PhysicalEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, DelayedEnclaved), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, PhysicalEnclaved), PhysicalEnclaved}, - {std::make_pair<ConnectionType, ConnectionType>(PhysicalEnclaved, Plugin), Invalid}, - // Plugin + x = Invalid - {std::make_pair<ConnectionType, ConnectionType>(Plugin, Normal), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(Plugin, Delayed), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(Plugin, Enclaved), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(Plugin, Physical), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(Plugin, DelayedEnclaved), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(Plugin, PhysicalEnclaved), Invalid}, // !!! - {std::make_pair<ConnectionType, ConnectionType>(Plugin, Plugin), Invalid}, // !!! - }; - - // discards all current changes - optimized_graph_.clear(); - - // getting all the sources from the graph - auto keys = graph_.keys(); - - // generating all the possible destinations for all sources - for (auto* source : keys) { - auto spanning_tree = graph_.naive_spanning_tree(source); - for (auto& path : spanning_tree) { - ConnectionProperties merged_properties{}; - - std::reverse(path.begin(), path.end()); - - auto* previous_element = std::begin(path)->second; - vector_shuffle(path, source); - - auto current_rating = previous_element->rating(); - - for (auto element : path) { - auto property = element.first; - current_rating += element.second->rating(); - - if (current_rating > 0) { - auto return_type = - construction_table[std::pair<ConnectionType, ConnectionType>(merged_properties.type_, property.type_)]; - // invalid will split the connections - if (return_type == Invalid) { - // first add connection until this point - optimized_graph_.add_edge(element.second, previous_element, merged_properties); - - // updating the source of the connection and resetting the properties - previous_element = element.second; - 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(element.second, previous_element, merged_properties); - } - } - } - } - } -} - void recursive_assemble(Reactor* container) { // NOLINT container->assemble(); for (auto* reactor : container->reactors()) {