1.. _use_nested_flow_graphs: 2 3Use Nested Flow Graphs 4====================== 5 6 7In addition to nesting algorithms within a flow graph node, it is also 8possible to nest flow graphs. For example, below there is a graph ``g`` with 9two nodes, ``a`` and ``b``. When node ``a`` receives a message, it constructs and 10executes an inner dependence graph. When node ``b`` receives a message, it 11constructs and executes an inner data flow graph: 12 13 14:: 15 16 17 graph g; 18 function_node< int, int > a( g, unlimited, []( int i ) -> int { 19 graph h; 20 node_t n1( h, [=]( msg_t ) { cout << "n1: " << i << "\n"; } ); 21 node_t n2( h, [=]( msg_t ) { cout << "n2: " << i << "\n"; } ); 22 node_t n3( h, [=]( msg_t ) { cout << "n3: " << i << "\n"; } ); 23 node_t n4( h, [=]( msg_t ) { cout << "n4: " << i << "\n"; } ); 24 make_edge( n1, n2 ); 25 make_edge( n1, n3 ); 26 make_edge( n2, n4 ); 27 make_edge( n3, n4 ); 28 n1.try_put(continue_msg()); 29 h.wait_for_all(); 30 return i; 31 } ); 32 function_node< int, int > b( g, unlimited, []( int i ) -> int { 33 graph h; 34 function_node< int, int > m1( h, unlimited, []( int j ) -> int { 35 cout << "m1: " << j << "\n"; 36 return j; 37 } ); 38 function_node< int, int > m2( h, unlimited, []( int j ) -> int { 39 cout << "m2: " << j << "\n"; 40 return j; 41 } ); 42 function_node< int, int > m3( h, unlimited, []( int j ) -> int { 43 cout << "m3: " << j << "\n"; 44 return j; 45 } ); 46 function_node< int, int > m4( h, unlimited, []( int j ) -> int { 47 cout << "m4: " << j << "\n"; 48 return j; 49 } ); 50 make_edge( m1, m2 ); 51 make_edge( m1, m3 ); 52 make_edge( m2, m4 ); 53 make_edge( m3, m4 ); 54 m1.try_put(i); 55 h.wait_for_all(); 56 return i; 57 } ); 58 make_edge( a, b ); 59 for ( int i = 0; i < 3; ++i ) { 60 a.try_put(i); 61 } 62 g.wait_for_all(); 63 64 65If the nested graph remains unchanged in structure between invocations 66of the node, it is redundant to construct it each time. Reconstructing 67the graph only adds overhead to the execution. You can modify the 68example above, for example, to have node ``b`` reuse a graph that is 69persistent across its invocations: 70 71 72:: 73 74 75 graph h; 76 function_node< int, int > m1( h, unlimited, []( int j ) -> int { 77 cout << "m1: " << j << "\n"; 78 return j; 79 } ); 80 function_node< int, int > m2( h, unlimited, []( int j ) -> int { 81 cout << "m2: " << j << "\n"; 82 return j; 83 } ); 84 function_node< int, int > m3( h, unlimited, []( int j ) -> int { 85 cout << "m3: " << j << "\n"; 86 return j; 87 } ); 88 function_node< int, int > m4( h, unlimited, []( int j ) -> int { 89 cout << "m4: " << j << "\n"; 90 return j; 91 } ); 92 make_edge( m1, m2 ); 93 make_edge( m1, m3 ); 94 make_edge( m2, m4 ); 95 make_edge( m3, m4 ); 96 97 98 graph g; 99 function_node< int, int > a( g, unlimited, []( int i ) -> int { 100 graph h; 101 node_t n1( h, [=]( msg_t ) { cout << "n1: " << i << "\n"; } ); 102 node_t n2( h, [=]( msg_t ) { cout << "n2: " << i << "\n"; } ); 103 node_t n3( h, [=]( msg_t ) { cout << "n3: " << i << "\n"; } ); 104 node_t n4( h, [=]( msg_t ) { cout << "n4: " << i << "\n"; } ); 105 make_edge( n1, n2 ); 106 make_edge( n1, n3 ); 107 make_edge( n2, n4 ); 108 make_edge( n3, n4 ); 109 n1.try_put(continue_msg()); 110 h.wait_for_all(); 111 return i; 112 } ); 113 function_node< int, int > b( g, unlimited, [&]( int i ) -> int { 114 m1.try_put(i); 115 h.wait_for_all(); // optional since h is not destroyed 116 return i; 117 } ); 118 make_edge( a, b ); 119 for ( int i = 0; i < 3; ++i ) { 120 a.try_put(i); 121 } 122 g.wait_for_all(); 123 124 125It is only necessary to call ``h.wait_for_all()`` at the end of each 126invocation of ``b``'s body in our modified code, if you wish for this ``b``'s 127body to block until the inner graph is done. In the first implementation 128of ``b``, it was necessary to call ``h.wait_for_all`` at the end of each 129invocation since the graph was destroyed at the end of the scope. So it 130would be valid in the body of ``b`` above to call ``m1.try_put(i)`` and then 131return without waiting for ``h`` to become idle. 132 133