Skip to content

Commit 119cdc9

Browse files
Merge pull request taskflow#73 from paolobolzoni/master
Add a ready-to-use generic class to associate data to the flows
2 parents b9c204d + a720d3d commit 119cdc9

File tree

3 files changed

+381
-13
lines changed

3 files changed

+381
-13
lines changed

example/dataflow.hpp

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#ifndef DATAFLOW_HPP
2+
#define DATAFLOW_HPP 1
3+
4+
#include <taskflow/taskflow.hpp>
5+
6+
#include <utility>
7+
#include <vector>
8+
#include <functional>
9+
10+
namespace df {
11+
12+
13+
// "pads" are the object where functions on nodes can read from or write to;
14+
// pads are all of the same compile time type T.
15+
//
16+
// Class hierarchies and virtual functions can be used to have different types
17+
// of pads. In those cases, T would be probably a std::unique_ptr
18+
template <typename T>
19+
using Pad_id = typename std::vector<T>::size_type;
20+
21+
22+
// "nodes" corresponds to taskflow tasks, but are agumented with input and
23+
// output pads.
24+
template <typename T>
25+
class Node;
26+
template <typename T>
27+
using Node_id = typename std::vector< Node<T> >::size_type;
28+
29+
30+
// each node executes a "functor" of type Tranform_f passing itself as a
31+
// argument; the functor is expected to read from input pads and write to
32+
// output pads.
33+
template <typename T>
34+
using Transform_f = std::function<void(Node<T>& element)>;
35+
36+
37+
// "Dataflow_generator" is the type that keeps track of the relations between
38+
// nodes and their pads. See below for the interface.
39+
template <typename T>
40+
class Dataflow_generator;
41+
42+
43+
// Node interface, gives access to input and output pads.
44+
// For convenience output pads can be accessed with the [] operator
45+
template <typename T>
46+
class Node {
47+
public:
48+
// get input pad
49+
T const& ipad(Pad_id<T> id) const;
50+
51+
// get output pad
52+
T& opad(Pad_id<T> id);
53+
T& operator[](Pad_id<T>);
54+
55+
// get all the input pad ids
56+
std::vector< Pad_id<T> > const& ipad_list() const;
57+
58+
// get all the output pad ids
59+
std::vector< Pad_id<T> > const& opad_list() const;
60+
61+
62+
private:
63+
std::vector<T>& pads_;
64+
std::vector< Pad_id<T> > ipads_;
65+
std::vector< Pad_id<T> > opads_;
66+
Transform_f<T> compute_;
67+
68+
explicit Node(std::vector<T>& pads)
69+
: pads_{pads},
70+
ipads_{},
71+
opads_{},
72+
compute_{ nullptr } {}
73+
74+
friend class Dataflow_generator<T>;
75+
};
76+
77+
78+
// Dataflow generator interface, allows to:
79+
// . create nodes
80+
// . set up what each node executes
81+
// . create arcs
82+
// . execute the dag
83+
//
84+
// nodes are basically taskflow tasks agumented with input and output pads
85+
// edges are taskflow precedences
86+
// execution is done via taskflow taskflow.run_until
87+
template <typename T>
88+
class Dataflow_generator {
89+
public:
90+
// creates a new node
91+
Node_id<T> create_node();
92+
93+
// creates a new node and set up its functor
94+
Node_id<T> create_node(Transform_f<T> f);
95+
96+
// creates a new arc ensuring that source output pads become the target
97+
// input pads
98+
Pad_id<T> create_arc(Node_id<T> source, Node_id<T> target);
99+
100+
// Set up the functor for an existing node
101+
void set_function(Node_id<T> id, Transform_f<T> f);
102+
103+
// Peek to a node, it might be useful to see how many input or output pads
104+
// it has
105+
Node<T> const& node(Node_id<T> id) const;
106+
107+
// Executes the flow repeatly until the functor Cond returns true
108+
template <typename Cond>
109+
void start_flow(Cond && cond);
110+
111+
// Executes the flow once
112+
void start_flow_once();
113+
114+
Dataflow_generator() = default;
115+
private:
116+
std::vector<T> pads_;
117+
std::vector< Node<T> > nodes_;
118+
std::vector<tf::Task> tasks_;
119+
tf::Framework executor_;
120+
};
121+
122+
123+
124+
// Implementations
125+
126+
template <typename T>
127+
T const& Node<T>::
128+
ipad(Pad_id<T> id) const {
129+
return pads_[id];
130+
}
131+
132+
133+
template <typename T>
134+
T& Node<T>::
135+
opad(Pad_id<T> id) {
136+
return pads_[id];
137+
}
138+
139+
140+
template <typename T>
141+
std::vector<Pad_id<T>> const& Node<T>::
142+
ipad_list() const {
143+
return ipads_;
144+
}
145+
146+
147+
template <typename T>
148+
std::vector<Pad_id<T>> const& Node<T>::
149+
opad_list() const {
150+
return opads_;
151+
}
152+
153+
154+
template <typename T>
155+
T& Node<T>::
156+
operator[](Pad_id<T> id) {
157+
return pads_[id];
158+
}
159+
160+
161+
template <typename T>
162+
Node_id<T> Dataflow_generator<T>::
163+
create_node() {
164+
Node_id<T> node_id { nodes_.size() };
165+
166+
nodes_.emplace_back( Node(pads_) );
167+
tasks_.emplace_back( executor_.emplace([this, node_id]() { this->nodes_[node_id].compute_( this->nodes_[node_id] ); }) );
168+
169+
return node_id;
170+
}
171+
172+
173+
template <typename T>
174+
Node_id<T> Dataflow_generator<T>::
175+
create_node(Transform_f<T> f) {
176+
Node_id<T> node_id { create_node() };
177+
set_function(node_id, f);
178+
return node_id;
179+
}
180+
181+
182+
template <typename T>
183+
Pad_id<T> Dataflow_generator<T>::
184+
create_arc(Node_id<T> source, Node_id<T> target) {
185+
Pad_id<T> pad_id { pads_.size() };
186+
pads_.emplace_back( T{} );
187+
188+
nodes_[source].opads_.push_back(pad_id);
189+
nodes_[target].ipads_.push_back(pad_id);
190+
191+
tasks_[source].precede(tasks_[target]);
192+
193+
return pad_id;
194+
}
195+
196+
197+
template <typename T>
198+
template <typename Cond>
199+
void Dataflow_generator<T>::
200+
start_flow(Cond && cond) {
201+
tf::Taskflow taskflow{};
202+
taskflow.run_until(executor_, cond);
203+
}
204+
205+
206+
template <typename T>
207+
void Dataflow_generator<T>::
208+
start_flow_once() {
209+
bool leave{true};
210+
start_flow( [&leave]() { leave = !leave; return leave;} );
211+
}
212+
213+
214+
template <typename T>
215+
Node<T> const& Dataflow_generator<T> ::
216+
node(Node_id<T> id) const {
217+
return nodes_[id];
218+
}
219+
220+
221+
template <typename T>
222+
void Dataflow_generator<T> ::
223+
set_function(Node_id<T> id, Transform_f<T> f) {
224+
nodes_[id].compute_ = f;
225+
}
226+
227+
}
228+
#endif
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,22 @@
55
cpp-taskflow works on directed acyclic graphs.
66
And here we want to pass information between the flow elements.
77
8-
To do so, we see the cpp-taskflow arcs as objects memory where the functions on
9-
the nodes read from or write to.
8+
To do so, we see the cpp-taskflow arcs as objects where the functions on the
9+
nodes read from or write to.
1010
11-
The function on every node will *read from* the objects of memory of its
12-
incoming edges and *write to* the objects of its outcoming edges.
11+
The function on every node will *read from* the objects on the incoming arcs
12+
and *write to* the objects on the outcoming arcs.
1313
1414
The cpp-taskflow semantics ensures the synchronization.
1515
1616
17-
Nodes without incoming edges will require the input from somewhere else;
18-
instead nodes without outcoming edges have to execute some side effects to be
19-
useful.
17+
Nodes without incoming arcs will require the input from somewhere else; instead
18+
nodes without outcoming arcs have to execute some side effects to be useful.
2019
2120
22-
In this example we fill up (in parallel) two vectors of the results of a fair
23-
percentile die and we pick up the maximum values from each cell, and output the
24-
result.
21+
In this example we fill up (in parallel) two vectors of the same size with the
22+
results of a fair percentile die, once done we pick up the maximum values from
23+
each cell. Finally we output the result.
2524
2625
.----------------.
2726
| fill in vector |----|
@@ -38,7 +37,8 @@ The code assumes the taskflow is executed once, when using the Framework
3837
feature the programmer needs care to keep the invariants.
3938
4039
It is then suggested to use const references (eg., vector<int> const&) for the
41-
objects related to the incoming arcs and references for outcoming ones.
40+
objects related to the incoming arcs and non-cost references for outcoming
41+
ones.
4242
4343
*/
4444

@@ -94,7 +94,7 @@ class Pick_up_max {
9494
for (std::vector<int>::size_type i{}, e = in1_.size(); i < e; ++i) {
9595
in1_[i] = std::max(in1_[i], in2_[i]);
9696
}
97-
// the taskflow is executed once, so we avoid one copy
97+
// the taskflow is executed once, so we avoid one allocation
9898
out_.swap(in1_);
9999
}
100100
private:
@@ -143,7 +143,7 @@ int main() {
143143
Print(out)
144144
);
145145

146-
// Set up dependencies
146+
// Set up the dependencies
147147
fill_in_vector1.precede(pick_up_max);
148148
fill_in_vector2.precede(pick_up_max);
149149
pick_up_max.precede(print);

0 commit comments

Comments
 (0)