66#include " phlex/concurrency.hpp"
77#include " phlex/core/concepts.hpp"
88#include " phlex/core/fold/send.hpp"
9+ #include " phlex/core/fold_join_node.hpp"
910#include " phlex/core/fwd.hpp"
1011#include " phlex/core/input_arguments.hpp"
1112#include " phlex/core/message.hpp"
12- #include " phlex/core/multilayer_join_node.hpp"
1313#include " phlex/core/product_query.hpp"
1414#include " phlex/core/products_consumer.hpp"
15- #include " phlex/core/store_counters.hpp"
1615#include " phlex/model/algorithm_name.hpp"
1716#include " phlex/model/data_cell_index.hpp"
1817#include " phlex/model/handle.hpp"
@@ -40,26 +39,44 @@ namespace phlex::experimental {
4039 public:
4140 declared_fold (algorithm_name name,
4241 std::vector<std::string> predicates,
43- product_queries input_products);
42+ product_queries input_products,
43+ std::string partition_layer);
4444 ~declared_fold () override ;
4545
4646 virtual tbb::flow::sender<message>& output_port () = 0;
47- virtual tbb::flow::receiver<flush_message>& flush_port () = 0;
4847 virtual product_specifications const & output () const = 0;
48+ virtual tbb::flow::receiver<index_message>& partition_port () = 0;
4949 virtual std::size_t product_count () const = 0;
50+ identifier const & partition_layer () const { return partition_layer_; }
51+
52+ private:
53+ identifier partition_layer_;
5054 };
5155
5256 using declared_fold_ptr = std::unique_ptr<declared_fold>;
5357 using declared_folds = simple_ptr_map<declared_fold_ptr>;
5458
59+ template <typename FoldResult, typename InitTuple, std::size_t ... Is>
60+ auto make_initializer (InitTuple args, std::index_sequence<Is...>)
61+ {
62+ // The compiler emits a warning if an empty args tuple is captured by reference and then not expanded inside the lambda expression. We therefore only capture args in the lambda expression if sizeof...(Is) > 0.
63+ if constexpr (sizeof ...(Is) == 0 ) {
64+ return [](data_cell_index const &) { return std::make_unique<FoldResult>(); };
65+ } else {
66+ return [args](data_cell_index const &) {
67+ return std::make_unique<FoldResult>(std::get<Is>(args)...);
68+ };
69+ }
70+ }
71+
5572 // =====================================================================================
5673
5774 template <typename AlgorithmBits, typename InitTuple>
58- class fold_node : public declared_fold , private count_stores {
75+ class fold_node : public declared_fold {
5976 using all_parameter_types = typename AlgorithmBits::input_parameter_types;
77+ using result_type = std::decay_t <std::tuple_element_t <0 , all_parameter_types>>;
6078 using input_parameter_types = skip_first_type<all_parameter_types>; // Skip fold object
6179 static constexpr auto num_inputs = std::tuple_size_v<input_parameter_types>;
62- using result_type = std::decay_t <std::tuple_element_t <0 , all_parameter_types>>;
6380
6481 static constexpr std::size_t num_outputs = 1 ; // hard-coded for now
6582 using function_t = typename AlgorithmBits::bound_type;
@@ -73,136 +90,69 @@ namespace phlex::experimental {
7390 InitTuple initializer,
7491 product_queries input_products,
7592 std::vector<std::string> output,
76- std::string partition) :
77- declared_fold{std::move (algo_name), std::move (predicates), std::move (input_products)},
78- initializer_{std::move (initializer)},
93+ std::string partition_layer) :
94+ declared_fold{std::move (algo_name),
95+ std::move (predicates),
96+ std::move (input_products),
97+ std::move (partition_layer)},
7998 output_{to_product_specifications (name (), std::move (output), make_type_ids<result_type>())},
80- partition_{std::move (partition)},
81- flush_receiver_{g,
82- tbb::flow::unlimited,
83- [this ](flush_message const & msg) -> tbb::flow::continue_msg {
84- auto const & [index, counts, original_message_id] = msg;
85- if (index->layer_name () != partition_) {
86- return {};
87- }
88-
89- counter_for (index->hash ()).set_flush_value (counts, original_message_id);
90- emit_and_evict_if_done (index);
91- return {};
92- }},
93- join_{make_join_or_none<num_inputs>(
94- g, name ().full (), layers ())}, // FIXME: This should change to include result product!
99+ join_{g,
100+ name ().full (),
101+ this ->partition_layer (),
102+ layers (),
103+ this ->output (),
104+ make_initializer<result_type>(
105+ std::move (initializer), std::make_index_sequence<std::tuple_size_v<InitTuple>>{})},
95106 fold_{g,
96107 concurrency,
97- [this , ft = alg.release_algorithm ()](messages_t <num_inputs> const & messages, auto &) {
98- // N.B. The assumption is that a fold will *never* need to cache
99- // the product store it creates. Any flush messages *do not* need
100- // to be propagated to downstream nodes.
101- auto const & msg = most_derived (messages);
102- auto const & index = msg.store ->index ();
103-
104- auto fold_index = index->parent (partition_);
105- if (not fold_index) {
106- return ;
107- }
108-
109- auto index_hash_for_counter = fold_index->hash ();
108+ [this , ft = alg.release_algorithm ()](
109+ accumulator_with_messages<result_type, num_inputs> const & accum_with_msgs, auto &) {
110+ std::size_t const partition_hash = apply_fold (ft, accum_with_msgs);
110111
111- call (ft, messages, std::make_index_sequence<num_inputs>{});
112112 ++calls_;
113113
114- counter_for (index_hash_for_counter).increment (index->layer_hash ());
115-
116- emit_and_evict_if_done (fold_index);
114+ join_.notify_result_repeater_port ().try_put (partition_hash);
117115 }}
118116 {
119- if constexpr (num_inputs > 1ull ) {
120- make_edge (join_, fold_);
121- }
117+ make_edge (join_, fold_);
122118 }
123119
124120 private:
125- void emit_and_evict_if_done (data_cell_index_ptr const & fold_index)
126- {
127- if (auto counter = done_with (fold_index->hash ())) {
128- auto parent = std::make_shared<product_store>(fold_index, name ());
129- commit (parent);
130- ++product_count_;
131- tbb::flow::output_port<0 >(fold_).try_put (
132- {.store = parent, .id = counter->original_message_id ()});
133- }
134- }
135-
136121 tbb::flow::receiver<message>& port_for (product_query const & input_product) override
137122 {
138- return receiver_for<num_inputs> (join_, input (), input_product, fold_ );
123+ return receiver_for (join_, input (), input_product);
139124 }
140125
141- std::vector<tbb::flow::receiver<message>*> ports () override
142- {
143- return input_ports<num_inputs>(join_, fold_);
144- }
126+ std::vector<tbb::flow::receiver<message>*> ports () override { return input_ports (join_); }
145127
146- tbb::flow::receiver<flush_message >& flush_port () override { return flush_receiver_ ; }
147- tbb::flow::sender<message>& output_port () override { return tbb::flow:: output_port< 0 >(fold_ ); }
128+ tbb::flow::receiver<index_message >& partition_port () override { return join_. partition_port () ; }
129+ tbb::flow::sender<message>& output_port () override { return join_. output_port ( ); }
148130 product_specifications const & output () const override { return output_; }
149131
150- template <std::size_t ... Is>
151- void call (function_t const & ft,
152- messages_t <num_inputs> const & messages,
153- std::index_sequence<Is...>)
154- {
155- auto const parent_index = most_derived (messages).store ->index ()->parent (partition_);
156-
157- // FIXME: Not the safest approach!
158- auto it = results_.find (parent_index->hash ());
159- if (it == results_.end ()) {
160- it = results_
161- .insert ({parent_index->hash (),
162- initialized_object (
163- initializer_, std::make_index_sequence<std::tuple_size_v<InitTuple>>{})})
164- .first ;
165- }
166-
167- if constexpr (num_inputs == 1ull ) {
168- std::invoke (ft, *it->second , std::get<Is>(input_).retrieve (messages)...);
169- } else {
170- std::invoke (ft, *it->second , std::get<Is>(input_).retrieve (std::get<Is>(messages))...);
171- }
172- }
173-
174132 named_index_ports index_ports () final { return join_.index_ports (); }
175133 std::size_t num_calls () const final { return calls_.load (); }
176134 std::size_t product_count () const final { return product_count_.load (); }
177135
178- template <size_t ... Is>
179- auto initialized_object (InitTuple const & init, std::index_sequence<Is...>) const
180- {
181- return std::unique_ptr<result_type>(new result_type{std::get<Is>(init)...});
182- }
183-
184- auto commit (product_store_ptr& store)
136+ std::size_t apply_fold (
137+ function_t const & ft,
138+ accumulator_with_messages<result_type, num_inputs> const & accum_with_msgs)
185139 {
186- auto & result = results_.at (store->index ()->hash ());
187- if constexpr (requires { send (*result); }) {
188- store->add_product (output ()[0 ], send (*result));
189- } else {
190- store->add_product (output ()[0 ], std::move (result));
191- }
192- // Reclaim some memory; it would be better to erase the entire entry from the map,
193- // but that is not thread-safe.
194- result.reset ();
140+ // We have to do awkward index management until we can use structured bindings with packs.
141+ auto & accumulator = std::get<0 >(accum_with_msgs);
142+ [&]<std::size_t ... Is>(std::index_sequence<Is...>) {
143+ accumulator.partial_result ->call (
144+ ft, std::get<Is>(input_).retrieve (std::get<Is + 1 >(accum_with_msgs))...);
145+ }(std::make_index_sequence<num_inputs>{});
146+ return accumulator.index ->hash ();
195147 }
196148
197149 InitTuple initializer_;
198150 input_retriever_types<input_parameter_types> input_{input_arguments<input_parameter_types>()};
199151 product_specifications output_;
200- identifier partition_;
201- tbb::flow::function_node<flush_message> flush_receiver_;
202- join_or_none_t <num_inputs> join_;
203- tbb::flow::multifunction_node<messages_t <num_inputs>, message_tuple<1 >> fold_;
204- tbb::concurrent_unordered_map<data_cell_index::hash_type, std::unique_ptr<result_type>>
205- results_;
152+ fold_join_node<result_type, num_inputs> join_;
153+ tbb::flow::multifunction_node<accumulator_with_messages<result_type, num_inputs>,
154+ message_tuple<1 >>
155+ fold_;
206156 std::atomic<std::size_t > calls_;
207157 std::atomic<std::size_t > product_count_;
208158 };
0 commit comments