edge.h
Go to the documentation of this file.
1 #ifndef TTG_EDGE_H
2 #define TTG_EDGE_H
3 
4 #include <iostream>
5 #include <memory>
6 #include <vector>
7 
8 #include "ttg/base/terminal.h"
9 #include "ttg/terminal.h"
10 #include "ttg/util/diagnose.h"
11 #include "ttg/util/print.h"
12 #include "ttg/util/trace.h"
13 
14 namespace ttg {
15 
24  template <typename keyT, typename valueT>
25  class Edge {
26  private:
27  // An EdgeImpl represents a single edge that most usually will
28  // connect a single output terminal with a single
29  // input terminal. However, we had to relax this constraint in
30  // order to easily accommodate connecting an input/output edge to
31  // an operation that to the outside looked like a single op but
32  // internally was implemented as multiple operations. Thus, the
33  // input/output edge has to connect to multiple terminals.
34  // Permitting multiple end points makes this much easier to
35  // compose, easier to implement, and likely more efficient at
36  // runtime. This is why outs/ins are vectors rather than pointers
37  // to a single terminal.
38  struct EdgeImpl {
39  std::string name;
40  bool is_pull_edge = false;
41  std::vector<TerminalBase *> outs; // In<keyT, valueT> or In<keyT, const valueT>
42  std::vector<Out<keyT, valueT> *> ins;
43 
45 
46  EdgeImpl() : name(""), outs(), ins() {}
47 
48  EdgeImpl(const std::string &name) : name(name), outs(), ins() {}
49 
50  EdgeImpl(const std::string &name, bool is_pull, ttg::detail::ContainerWrapper<keyT, valueT> &c)
51  : name(name), is_pull_edge(is_pull), container(c), outs(), ins() {
52  static_assert(!meta::is_void_v<keyT>, "Void keys are not supported with pull terminals.");
53  }
54 
55  void set_in(Out<keyT, valueT> *in) {
56  if (ins.size()) {
57  trace("Edge: ", name, " : has multiple inputs");
58  }
60  ins.push_back(in);
61  try_to_connect_new_in(in);
62  }
63 
64  void set_out(TerminalBase *out) {
65  if (outs.size()) {
66  trace("Edge: ", name, " : has multiple outputs");
67  }
69  static_cast<In<keyT, valueT> *>(out)->container = container;
70  outs.push_back(out);
71  try_to_connect_new_out(out);
72  }
73 
74  void try_to_connect_new_in(Out<keyT, valueT> *in) const {
75  for (auto out : outs)
76  if (in && out) in->connect(out);
77  }
78 
79  void try_to_connect_new_out(TerminalBase *out) const {
80  assert(out->get_type() != TerminalBase::Type::Write); // out must be an In<>
81  if (out->is_pull_terminal) {
82  out->connect_pull_nopred(out);
83  } else {
84  for (auto in : ins)
85  if (in && out) in->connect(out);
86  }
87  }
88 
89  ~EdgeImpl() {
90  if (diagnose() && ((ins.size() == 0 && outs.size() != 0) || (ins.size() != 0 && outs.size() == 0)) &&
91  !is_pull_edge) {
92  print_error("Edge: destroying edge pimpl ('", name,
93  "') with either in or out not assigned --- graph may be incomplete");
94  }
95  }
96  };
97 
98  // We have a vector here to accommodate fusing multiple edges together
99  // when connecting them all to a single terminal.
100  mutable std::vector<std::shared_ptr<EdgeImpl>> p; // Need shallow copy semantics
101 
102  public:
104  typedef keyT key_type;
105  typedef valueT value_type;
106  static_assert(std::is_same_v<keyT, std::decay_t<keyT>>, "Edge<keyT,valueT> assumes keyT is a non-decayable type");
107  static_assert(std::is_same_v<valueT, std::decay_t<valueT>>,
108  "Edge<keyT,valueT> assumes valueT is a non-decayable type");
109 
110  Edge(const std::string name = "anonymous edge") : p(1) { p[0] = std::make_shared<EdgeImpl>(name); }
111 
112  Edge(const std::string name, bool is_pull, ttg::detail::ContainerWrapper<keyT, valueT> c) : p(1) {
113  p[0] = std::make_shared<EdgeImpl>(name, is_pull, c);
114  }
115 
117  template <typename... valuesT, typename = std::enable_if_t<(std::is_same_v<valuesT, valueT> && ...)>>
118  Edge(const Edge<keyT, valuesT> &...edges) : p(0) {
119  std::vector<Edge<keyT, valueT>> v = {edges...};
120  // Do not allow fusing of push and pull terminals
121  if (!std::all_of(v.begin(), v.end(), [](Edge<keyT, valueT> e) { return !e.is_pull_edge(); }))
122  throw std::runtime_error("Edge: fusing push and pull terminals is not supported.");
123 
124  for (auto &edge : v) {
125  p.insert(p.end(), edge.p.begin(), edge.p.end());
126  }
127  }
128 
131  Edge<keyT, valueT> edge() const { return *this; }
132 
134  bool live() const {
135  bool result = false;
136  for (const auto &edge : p) {
137  if (!edge->ins.empty()) return true;
138  }
139  return result;
140  }
141 
142  bool is_pull_edge() const { return p.at(0)->is_pull_edge; }
143 
145  void set_in(Out<keyT, valueT> *in) const {
146  for (auto &edge : p) edge->set_in(in);
147  }
148 
150  void set_out(TerminalBase *out) const {
151  for (auto &edge : p) edge->set_out(out);
152  }
153 
156  template <typename Key = keyT, typename Value = valueT>
157  std::enable_if_t<ttg::meta::is_all_void_v<Key, Value>> fire() const {
158  for (auto &&e : p)
159  for (auto &&out : e->outs) {
160  out->get_tt()->invoke();
161  }
162  }
163  };
164 
165  // Make type of tuple of edges from type of tuple of terminals
166  template <typename termsT>
168  template <typename... termsT>
169  struct terminals_to_edges<std::tuple<termsT...>> {
170  typedef std::tuple<typename termsT::edge_type...> type;
171  };
172 
173  // Make type of tuple of output terminals from type of tuple of edges
174  template <typename edgesT>
176  template <typename... edgesT>
177  struct edges_to_output_terminals<std::tuple<edgesT...>> {
178  typedef std::tuple<typename edgesT::output_terminal_type...> type;
179  };
180 
181  namespace detail {
182  template <typename keyT, typename valuesT>
183  struct edges_tuple;
184 
185  template <typename keyT, typename... valuesT>
186  struct edges_tuple<keyT, std::tuple<valuesT...>> {
187  using type = std::tuple<ttg::Edge<keyT, valuesT>...>;
188  };
189 
190  template <typename keyT, typename valuesT>
192  } // namespace detail
193 
194 } // namespace ttg
195 
196 #endif // TTG_EDGE_H
Edge is used to connect In and Out terminals.
Definition: edge.h:25
void set_in(Out< keyT, valueT > *in) const
Sets the output terminal that goes into this Edge.
Definition: edge.h:145
std::enable_if_t< ttg::meta::is_all_void_v< Key, Value > > fire() const
Definition: edge.h:157
valueT value_type
Definition: edge.h:105
Edge(const std::string name="anonymous edge")
Definition: edge.h:110
Edge< keyT, valueT > edge() const
Definition: edge.h:131
bool live() const
probes if this is already has at least one input received on the input terminal
Definition: edge.h:134
Edge(const std::string name, bool is_pull, ttg::detail::ContainerWrapper< keyT, valueT > c)
Definition: edge.h:112
keyT key_type
Definition: edge.h:104
Edge(const Edge< keyT, valuesT > &...edges)
Edge carrying a tuple of values.
Definition: edge.h:118
bool is_pull_edge() const
Definition: edge.h:142
Out< keyT, valueT > output_terminal_type
Definition: edge.h:103
void set_out(TerminalBase *out) const
Sets the input terminal that this Edge goes into.
Definition: edge.h:150
void connect(TerminalBase *in) override
Definition: terminal.h:449
bool is_pull_terminal
Definition: terminal.h:15
Type get_type() const
Returns the terminal type.
Definition: terminal.h:104
void connect_pull_nopred(TerminalBase *p)
Definition: terminal.h:113
@ Write
can only be written to
typename edges_tuple< keyT, valuesT >::type edges_tuple_t
Definition: edge.h:191
top-level TTG namespace contains runtime-neutral functionality
Definition: keymap.h:8
bool diagnose()
Definition: diagnose.h:12
void print_error(const T &t, const Ts &... ts)
atomically prints to std::cerr a sequence of items (separated by ttg::print_separator) followed by st...
Definition: print.h:138
void trace(const T &t, const Ts &... ts)
Definition: trace.h:43
auto edges(inedgesT &&...args)
Make a tuple of Edges to pass to.
Definition: func.h:147
std::tuple< ttg::Edge< keyT, valuesT >... > type
Definition: edge.h:187
std::tuple< typename edgesT::output_terminal_type... > type
Definition: edge.h:178
std::tuple< typename termsT::edge_type... > type
Definition: edge.h:170