]> https://gitweb.dealii.org/ - dealii.git/commitdiff
Import the current development version of TaskFlow 3.7.0. 16892/head
authorWolfgang Bangerth <bangerth@colostate.edu>
Tue, 16 Apr 2024 19:53:43 +0000 (13:53 -0600)
committerWolfgang Bangerth <bangerth@colostate.edu>
Tue, 7 May 2024 08:34:53 +0000 (14:04 +0530)
84 files changed:
bundled/setup_bundled.cmake
bundled/taskflow-3.6.0/include/taskflow/algorithm/reduce.hpp [deleted file]
bundled/taskflow-3.6.0/include/taskflow/core/async.hpp [deleted file]
bundled/taskflow-3.6.0/include/taskflow/core/async_task.hpp [deleted file]
bundled/taskflow-3.6.0/include/taskflow/utility/macros.hpp [deleted file]
bundled/taskflow-3.7.0/LICENSE [moved from bundled/taskflow-3.6.0/LICENSE with 92% similarity]
bundled/taskflow-3.7.0/README.md [moved from bundled/taskflow-3.6.0/README.md with 93% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/critical.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/critical.hpp with 97% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/data_pipeline.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/data_pipeline.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/find.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/find.hpp with 75% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/for_each.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/for_each.hpp with 59% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/launch.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/launch.hpp with 57% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/partitioner.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/partitioner.hpp with 55% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/pipeline.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/pipeline.hpp with 93% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/reduce.hpp [new file with mode: 0644]
bundled/taskflow-3.7.0/taskflow/algorithm/scan.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/scan.hpp with 76% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/sort.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/sort.hpp with 96% similarity]
bundled/taskflow-3.7.0/taskflow/algorithm/transform.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/algorithm/transform.hpp with 58% similarity]
bundled/taskflow-3.7.0/taskflow/core/async.hpp [new file with mode: 0644]
bundled/taskflow-3.7.0/taskflow/core/async_task.hpp [new file with mode: 0644]
bundled/taskflow-3.7.0/taskflow/core/declarations.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/declarations.hpp with 86% similarity]
bundled/taskflow-3.7.0/taskflow/core/environment.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/environment.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/core/error.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/error.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/core/executor-module-opt.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/executor-module-opt.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/core/executor.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/executor.hpp with 81% similarity]
bundled/taskflow-3.7.0/taskflow/core/flow_builder.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/flow_builder.hpp with 87% similarity]
bundled/taskflow-3.7.0/taskflow/core/graph.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/graph.hpp with 77% similarity]
bundled/taskflow-3.7.0/taskflow/core/notifier.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/notifier.hpp with 87% similarity]
bundled/taskflow-3.7.0/taskflow/core/observer.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/observer.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/core/semaphore.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/semaphore.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/core/task.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/task.hpp with 97% similarity]
bundled/taskflow-3.7.0/taskflow/core/taskflow.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/taskflow.hpp with 88% similarity]
bundled/taskflow-3.7.0/taskflow/core/topology.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/topology.hpp with 57% similarity]
bundled/taskflow-3.7.0/taskflow/core/tsq.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/tsq.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/core/worker.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/core/worker.hpp with 62% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/algorithm/find.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/find.hpp with 99% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/algorithm/for_each.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/for_each.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/algorithm/matmul.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/matmul.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/algorithm/merge.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/merge.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/algorithm/reduce.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/reduce.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/algorithm/scan.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/scan.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/algorithm/sort.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/sort.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/algorithm/transform.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/transform.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/algorithm/transpose.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/transpose.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_capturer.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_capturer.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_device.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_device.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_error.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_error.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_execution_policy.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_execution_policy.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_graph.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_graph.hpp with 98% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_memory.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_memory.hpp with 99% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_meta.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_meta.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_object.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_object.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_optimizer.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_optimizer.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_stream.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_stream.hpp with 98% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cuda_task.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_task.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/cuda/cudaflow.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/cuda/cudaflow.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/dsl/connection.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/dsl/connection.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/dsl/dsl.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/dsl/dsl.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/dsl/meta_macro.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/dsl/meta_macro.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/dsl/task_analyzer.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/dsl/task_analyzer.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/dsl/task_dsl.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/dsl/task_dsl.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/dsl/task_trait.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/dsl/task_trait.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/dsl/tuple_utils.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/dsl/tuple_utils.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/dsl/type_list.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/dsl/type_list.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/sycl/algorithm/reduce.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/sycl/algorithm/reduce.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/sycl/algorithm/sycl_for_each.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/sycl/algorithm/sycl_for_each.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/sycl/algorithm/sycl_transform.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/sycl/algorithm/sycl_transform.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/sycl/sycl_execution_policy.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_execution_policy.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/sycl/sycl_graph.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_graph.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/sycl/sycl_meta.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_meta.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/sycl/sycl_task.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/sycl/sycl_task.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/sycl/syclflow.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/sycl/syclflow.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/taskflow.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/taskflow.hpp with 93% similarity]
bundled/taskflow-3.7.0/taskflow/utility/iterator.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/iterator.hpp with 81% similarity]
bundled/taskflow-3.7.0/taskflow/utility/macros.hpp [new file with mode: 0644]
bundled/taskflow-3.7.0/taskflow/utility/math.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/math.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/utility/object_pool.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/object_pool.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/utility/os.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/os.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/utility/serializer.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/serializer.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/utility/singleton.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/singleton.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/utility/small_vector.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/small_vector.hpp with 100% similarity]
bundled/taskflow-3.7.0/taskflow/utility/stream.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/stream.hpp with 97% similarity]
bundled/taskflow-3.7.0/taskflow/utility/traits.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/traits.hpp with 99% similarity]
bundled/taskflow-3.7.0/taskflow/utility/uuid.hpp [moved from bundled/taskflow-3.6.0/include/taskflow/utility/uuid.hpp with 100% similarity]

index 09df8f1880cfe1f94c514e2702bac742d2e49a19..776cd76d148fad2fbad0a4fa872195dca7e6c357 100644 (file)
@@ -100,12 +100,12 @@ option(DEAL_II_FORCE_BUNDLED_TASKFLOW
   "Always use the bundled taskflow header library instead of an external one."
   OFF)
 
-set(TASKFLOW_FOLDER "${CMAKE_SOURCE_DIR}/bundled/taskflow-3.6.0")
+set(TASKFLOW_FOLDER "${CMAKE_SOURCE_DIR}/bundled/taskflow-3.7.0")
 
 macro(feature_taskflow_configure_bundled)
-  set(TASKFLOW_VERSION "3.6.0")
+  set(TASKFLOW_VERSION "3.7.0")
 
-  list(APPEND DEAL_II_BUNDLED_INCLUDE_DIRS ${TASKFLOW_FOLDER}/include)
+  list(APPEND DEAL_II_BUNDLED_INCLUDE_DIRS ${TASKFLOW_FOLDER})
 endmacro()
 
 
diff --git a/bundled/taskflow-3.6.0/include/taskflow/algorithm/reduce.hpp b/bundled/taskflow-3.6.0/include/taskflow/algorithm/reduce.hpp
deleted file mode 100644 (file)
index 64869dc..0000000
+++ /dev/null
@@ -1,295 +0,0 @@
-#pragma once
-
-#include "launch.hpp"
-
-namespace tf {
-
-namespace detail {
-
-// Function: make_reduce_task
-template <typename B, typename E, typename T, typename O, typename P>
-TF_FORCE_INLINE auto make_reduce_task(B beg, E end, T& init, O bop, P&& part) {
-
-  using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
-  using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
-  using namespace std::string_literals;
-
-  return 
-  [b=beg, e=end, &r=init, bop, part=std::forward<P>(part)] 
-  (Runtime& rt) mutable {
-
-    // fetch the iterator values
-    B_t beg = b;
-    E_t end = e;
-
-    size_t W = rt.executor().num_workers();
-    size_t N = std::distance(beg, end);
-
-    // only myself - no need to spawn another graph
-    if(W <= 1 || N <= part.chunk_size()) {
-      for(; beg!=end; r = bop(r, *beg++));
-      return;
-    }
-
-    if(N < W) {
-      W = N;
-    }
-
-    std::mutex mtx;
-
-    // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
-      
-      size_t chunk_size;
-
-      for(size_t w=0, curr_b=0; w<W && curr_b < N; ++w, curr_b += chunk_size) {
-        
-        // we force chunk size to be at least two because the temporary
-        // variable sum need to avoid copy at the first step
-        chunk_size = std::max(size_t{2}, part.adjusted_chunk_size(N, W, w));
-        
-        launch_loop(W, w, rt, [=, &bop, &mtx, &r, &part] () mutable {
-
-          std::advance(beg, curr_b);
-
-          if(N - curr_b == 1) {
-            std::lock_guard<std::mutex> lock(mtx);
-            r = bop(r, *beg);
-            return;
-          }
-
-          auto beg1 = beg++;
-          auto beg2 = beg++;
-          T sum = bop(*beg1, *beg2);
-        
-          // loop reduce
-          part.loop(N, W, curr_b, chunk_size,
-            [&, prev_e=curr_b+2](size_t curr_b, size_t curr_e) mutable {
-
-              if(curr_b > prev_e) {
-                std::advance(beg, curr_b - prev_e);
-              }
-              else {
-                curr_b = prev_e;
-              }
-
-              for(size_t x=curr_b; x<curr_e; x++, beg++) {
-                sum = bop(sum, *beg);
-              }
-              prev_e = curr_e;
-            }
-          ); 
-          
-          // final reduce
-          std::lock_guard<std::mutex> lock(mtx);
-          r = bop(r, sum);
-
-        });
-      }
-      rt.join();
-    }
-    // dynamic partitioner
-    else {
-      std::atomic<size_t> next(0);
-      launch_loop(N, W, rt, next, part, [=, &bop, &mtx, &next, &r, &part] () mutable {
-        // pre-reduce
-        size_t s0 = next.fetch_add(2, std::memory_order_relaxed);
-
-        if(s0 >= N) {
-          return;
-        }
-
-        std::advance(beg, s0);
-
-        if(N - s0 == 1) {
-          std::lock_guard<std::mutex> lock(mtx);
-          r = bop(r, *beg);
-          return;
-        }
-
-        auto beg1 = beg++;
-        auto beg2 = beg++;
-
-        T sum = bop(*beg1, *beg2);
-        
-        // loop reduce
-        part.loop(N, W, next, 
-          [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable {
-            std::advance(beg, curr_b - prev_e);
-            for(size_t x=curr_b; x<curr_e; x++, beg++) {
-              sum = bop(sum, *beg);
-            }
-            prev_e = curr_e;
-          }
-        ); 
-        
-        // final reduce
-        std::lock_guard<std::mutex> lock(mtx);
-        r = bop(r, sum);
-      });
-    }
-  };
-}
-
-// Function: make_transform_reduce_task
-template <typename B, typename E, typename T, typename BOP, typename UOP, typename P>
-TF_FORCE_INLINE auto make_transform_reduce_task(
-  B beg, E end, T& init, BOP bop, UOP uop, P&& part
-) {
-
-  using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
-  using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
-  using namespace std::string_literals;
-
-  return 
-  [b=beg, e=end, &r=init, bop, uop, part=std::forward<P>(part)] 
-  (Runtime& rt) mutable {
-
-    // fetch the iterator values
-    B_t beg = b;
-    E_t end = e;
-
-    size_t W = rt.executor().num_workers();
-    size_t N = std::distance(beg, end);
-
-    // only myself - no need to spawn another graph
-    if(W <= 1 || N <= part.chunk_size()) {
-      for(; beg!=end; r = bop(std::move(r), uop(*beg++)));
-      return;
-    }
-
-    if(N < W) {
-      W = N;
-    }
-
-    std::mutex mtx;
-    
-    // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
-      
-      size_t chunk_size;
-
-      for(size_t w=0, curr_b=0; w<W && curr_b < N; ++w, curr_b += chunk_size) {
-      
-        chunk_size = part.adjusted_chunk_size(N, W, w);
-
-        launch_loop(W, w, rt, [=, &bop, &uop, &mtx, &r, &part] () mutable {
-
-          std::advance(beg, curr_b);
-
-          if(N - curr_b == 1) {
-            std::lock_guard<std::mutex> lock(mtx);
-            r = bop(std::move(r), uop(*beg));
-            return;
-          }
-
-          //auto beg1 = beg++;
-          //auto beg2 = beg++;
-          //T sum = bop(uop(*beg1), uop(*beg2));
-
-          T sum = (chunk_size == 1) ? uop(*beg++) : bop(uop(*beg++), uop(*beg++));
-        
-          // loop reduce
-          part.loop(N, W, curr_b, chunk_size,
-            [&, prev_e=curr_b+(chunk_size == 1 ? 1 : 2)]
-            (size_t curr_b, size_t curr_e) mutable {
-              if(curr_b > prev_e) {
-                std::advance(beg, curr_b - prev_e);
-              }
-              else {
-                curr_b = prev_e;
-              }
-              for(size_t x=curr_b; x<curr_e; x++, beg++) {
-                sum = bop(std::move(sum), uop(*beg));
-              }
-              prev_e = curr_e;
-            }
-          ); 
-          
-          // final reduce
-          std::lock_guard<std::mutex> lock(mtx);
-          r = bop(std::move(r), std::move(sum));
-
-        });
-      }
-      
-      rt.join();
-    }
-    // dynamic partitioner
-    else {
-      std::atomic<size_t> next(0);
-        
-      launch_loop(N, W, rt, next, part, [=, &bop, &uop, &mtx, &next, &r, &part] () mutable {
-
-        // pre-reduce
-        size_t s0 = next.fetch_add(2, std::memory_order_relaxed);
-
-        if(s0 >= N) {
-          return;
-        }
-
-        std::advance(beg, s0);
-
-        if(N - s0 == 1) {
-          std::lock_guard<std::mutex> lock(mtx);
-          r = bop(std::move(r), uop(*beg));
-          return;
-        }
-
-        auto beg1 = beg++;
-        auto beg2 = beg++;
-
-        T sum = bop(uop(*beg1), uop(*beg2));
-        
-        // loop reduce
-        part.loop(N, W, next, 
-          [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable {
-            std::advance(beg, curr_b - prev_e);
-            for(size_t x=curr_b; x<curr_e; x++, beg++) {
-              sum = bop(std::move(sum), uop(*beg));
-            }
-            prev_e = curr_e;
-          }
-        ); 
-        
-        // final reduce
-        std::lock_guard<std::mutex> lock(mtx);
-        r = bop(std::move(r), std::move(sum));
-      });
-    }
-  };
-}
-
-}  // end of namespace detail -------------------------------------------------
-
-// ----------------------------------------------------------------------------
-// default reduction
-// ----------------------------------------------------------------------------
-
-// Function: reduce
-template <typename B, typename E, typename T, typename O, typename P>
-Task FlowBuilder::reduce(B beg, E end, T& init, O bop, P&& part) {
-  return emplace(detail::make_reduce_task(
-    beg, end, init, bop, std::forward<P>(part)
-  ));
-}
-
-// ----------------------------------------------------------------------------
-// default transform and reduction
-// ----------------------------------------------------------------------------
-
-// Function: transform_reduce
-template <typename B, typename E, typename T, typename BOP, typename UOP, typename P>
-Task FlowBuilder::transform_reduce(
-  B beg, E end, T& init, BOP bop, UOP uop, P&& part
-) {
-  return emplace(detail::make_transform_reduce_task(
-    beg, end, init, bop, uop, std::forward<P>(part)
-  ));
-}
-
-}  // end of namespace tf -----------------------------------------------------
-
-
-
-
diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/async.hpp b/bundled/taskflow-3.6.0/include/taskflow/core/async.hpp
deleted file mode 100644 (file)
index 69788c6..0000000
+++ /dev/null
@@ -1,396 +0,0 @@
-#pragma once
-
-#include "executor.hpp"
-
-// https://hackmd.io/@sysprog/concurrency-atomics
-
-namespace tf {
-
-// ----------------------------------------------------------------------------
-// Async
-// ----------------------------------------------------------------------------
-
-// Function: async
-template <typename F>
-auto Executor::async(const std::string& name, F&& f) {
-
-  _increment_topology();
-
-  using R = std::invoke_result_t<std::decay_t<F>>;
-
-  std::promise<R> p;
-  auto fu{p.get_future()};
-
-  auto node = node_pool.animate(
-    name, 0, nullptr, nullptr, 0,
-    std::in_place_type_t<Node::Async>{}, 
-    _make_promised_async(std::move(p), std::forward<F>(f))
-  );
-
-  _schedule_async_task(node);
-
-  return fu;
-}
-
-// Function: async
-template <typename F>
-auto Executor::async(F&& f) {
-  return async("", std::forward<F>(f));
-}
-
-// ----------------------------------------------------------------------------
-// Silent Async
-// ----------------------------------------------------------------------------
-
-// Function: silent_async
-template <typename F>
-void Executor::silent_async(const std::string& name, F&& f) {
-
-  _increment_topology();
-
-  auto node = node_pool.animate(
-    name, 0, nullptr, nullptr, 0,
-    std::in_place_type_t<Node::Async>{}, std::forward<F>(f)
-  );
-
-  _schedule_async_task(node);
-}
-
-// Function: silent_async
-template <typename F>
-void Executor::silent_async(F&& f) {
-  silent_async("", std::forward<F>(f));
-}
-
-// ----------------------------------------------------------------------------
-// Async Helper Methods
-// ----------------------------------------------------------------------------
-
-// Function: _make_promised_async
-template <typename R, typename F>
-auto Executor::_make_promised_async(std::promise<R>&& p, F&& func) {
-  return [p=make_moc(std::move(p)), func=std::forward<F>(func)]() mutable {
-    if constexpr(std::is_same_v<R, void>) {
-      func();
-      p.object.set_value();
-    }
-    else {
-      p.object.set_value(func());
-    }
-  };
-}
-  
-// Procedure: _schedule_async_task
-inline void Executor::_schedule_async_task(Node* node) {  
-  if(auto w = _this_worker(); w) {
-    _schedule(*w, node);
-  }
-  else{
-    _schedule(node);
-  }
-}
-
-// Procedure: _tear_down_async
-inline void Executor::_tear_down_async(Node* node) {
-  // from runtime
-  if(node->_parent) {
-    node->_parent->_join_counter.fetch_sub(1, std::memory_order_release);
-  }
-  // from executor
-  else {
-    _decrement_topology_and_notify();
-  }
-  node_pool.recycle(node);
-}
-
-// ----------------------------------------------------------------------------
-// Silent Dependent Async
-// ----------------------------------------------------------------------------
-
-// Function: silent_dependent_async
-template <typename F, typename... Tasks,
-  std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>*
->
-tf::AsyncTask Executor::silent_dependent_async(F&& func, Tasks&&... tasks) {
-  return silent_dependent_async("", std::forward<F>(func), std::forward<Tasks>(tasks)...);
-}
-
-// Function: silent_dependent_async
-template <typename F, typename... Tasks,
-  std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>*
->
-tf::AsyncTask Executor::silent_dependent_async(
-  const std::string& name, F&& func, Tasks&&... tasks 
-){
-
-  _increment_topology();
-
-  size_t num_dependents = sizeof...(Tasks);
-  
-  std::shared_ptr<Node> node(
-    node_pool.animate(
-      name, 0, nullptr, nullptr, num_dependents,
-      std::in_place_type_t<Node::DependentAsync>{}, std::forward<F>(func)
-    ),
-    [&](Node* ptr){ node_pool.recycle(ptr); }
-  );
-  
-  {
-    std::scoped_lock lock(_asyncs_mutex);
-    _asyncs.insert(node);
-  }
-  
-  if constexpr(sizeof...(Tasks) > 0) {
-    (_process_async_dependent(node.get(), tasks, num_dependents), ...);
-  }
-
-  if(num_dependents == 0) {
-    _schedule_async_task(node.get());
-  }
-
-  return AsyncTask(std::move(node));
-}
-
-// Function: silent_dependent_async
-template <typename F, typename I,
-  std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>*
->
-tf::AsyncTask Executor::silent_dependent_async(F&& func, I first, I last) {
-  return silent_dependent_async("", std::forward<F>(func), first, last);
-}
-
-// Function: silent_dependent_async
-template <typename F, typename I,
-  std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>*
->
-tf::AsyncTask Executor::silent_dependent_async(
-  const std::string& name, F&& func, I first, I last
-) {
-
-  _increment_topology();
-
-  size_t num_dependents = std::distance(first, last);
-  
-  std::shared_ptr<Node> node(
-    node_pool.animate(
-      name, 0, nullptr, nullptr, num_dependents,
-      std::in_place_type_t<Node::DependentAsync>{}, std::forward<F>(func)
-    ),
-    [&](Node* ptr){ node_pool.recycle(ptr); }
-  );
-  
-  {
-    std::scoped_lock lock(_asyncs_mutex);
-    _asyncs.insert(node);
-  }
-  
-  for(; first != last; first++){
-    _process_async_dependent(node.get(), *first, num_dependents);
-  }
-
-  if(num_dependents == 0) {
-    _schedule_async_task(node.get());
-  }
-
-  return AsyncTask(std::move(node));
-}
-
-// ----------------------------------------------------------------------------
-// Dependent Async
-// ----------------------------------------------------------------------------
-
-// Function: dependent_async
-template <typename F, typename... Tasks,
-  std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>*
->
-auto Executor::dependent_async(F&& func, Tasks&&... tasks) {
-  return dependent_async("", std::forward<F>(func), std::forward<Tasks>(tasks)...);
-}
-
-// Function: dependent_async
-template <typename F, typename... Tasks,
-  std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>*
->
-auto Executor::dependent_async(
-  const std::string& name, F&& func, Tasks&&... tasks 
-) {
-  
-  _increment_topology();
-  
-  using R = std::invoke_result_t<std::decay_t<F>>;
-
-  std::promise<R> p;
-  auto fu{p.get_future()};
-
-  size_t num_dependents = sizeof...(tasks);
-
-  std::shared_ptr<Node> node(
-    node_pool.animate(
-      name, 0, nullptr, nullptr, num_dependents,
-      std::in_place_type_t<Node::DependentAsync>{},
-      _make_promised_async(std::move(p), std::forward<F>(func))
-    ),
-    [&](Node* ptr){ node_pool.recycle(ptr); }
-  );
-  
-  {
-    std::scoped_lock lock(_asyncs_mutex);
-    _asyncs.insert(node);
-  }
-  
-  if constexpr(sizeof...(Tasks) > 0) {
-    (_process_async_dependent(node.get(), tasks, num_dependents), ...);
-  }
-
-  if(num_dependents == 0) {
-    _schedule_async_task(node.get());
-  }
-
-  return std::make_pair(AsyncTask(std::move(node)), std::move(fu));
-}
-
-// Function: dependent_async
-template <typename F, typename I,
-  std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>*
->
-auto Executor::dependent_async(F&& func, I first, I last) {
-  return dependent_async("", std::forward<F>(func), first, last);
-}
-
-// Function: dependent_async
-template <typename F, typename I,
-  std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>*
->
-auto Executor::dependent_async(
-  const std::string& name, F&& func, I first, I last
-) {
-  
-  _increment_topology();
-  
-  using R = std::invoke_result_t<std::decay_t<F>>;
-
-  std::promise<R> p;
-  auto fu{p.get_future()};
-
-  size_t num_dependents = std::distance(first, last);
-
-  std::shared_ptr<Node> node(
-    node_pool.animate(
-      name, 0, nullptr, nullptr, num_dependents,
-      std::in_place_type_t<Node::DependentAsync>{},
-      _make_promised_async(std::move(p), std::forward<F>(func))
-    ),
-    [&](Node* ptr){ node_pool.recycle(ptr); }
-  );
-  
-  {
-    std::scoped_lock lock(_asyncs_mutex);
-    _asyncs.insert(node);
-  }
-  
-  for(; first != last; first++) {
-    _process_async_dependent(node.get(), *first, num_dependents);
-  }
-
-  if(num_dependents == 0) {
-    _schedule_async_task(node.get());
-  }
-
-  return std::make_pair(AsyncTask(std::move(node)), std::move(fu));
-}
-
-// ----------------------------------------------------------------------------
-// Dependent Async Helper Functions
-// ----------------------------------------------------------------------------
-
-// Procedure: _process_async_dependent
-inline void Executor::_process_async_dependent(
-  Node* node, tf::AsyncTask& task, size_t& num_dependents
-) {
-
-  std::shared_ptr<Node> dep;
-  {
-    std::scoped_lock lock(_asyncs_mutex);
-    if(auto itr = _asyncs.find(task._node); itr != _asyncs.end()){
-      dep = *itr;
-    }
-  }
-  
-  // if the dependent task exists
-  if(dep) {
-    auto& state = std::get_if<Node::DependentAsync>(&(dep->_handle))->state;
-
-    add_dependent:
-
-    auto target = Node::AsyncState::UNFINISHED;
-    
-    // acquires the lock
-    if(state.compare_exchange_weak(target, Node::AsyncState::LOCKED,
-                                   std::memory_order_acq_rel,
-                                   std::memory_order_acquire)) {
-      dep->_successors.push_back(node);
-      state.store(Node::AsyncState::UNFINISHED, std::memory_order_release);
-    }
-    // dep's state is FINISHED, which means dep finished its callable already
-    // thus decrement the node's join counter by 1
-    else if (target == Node::AsyncState::FINISHED) {
-      // decrement the counter needs to be the order of acquire and release
-      // to synchronize with the worker
-      num_dependents = node->_join_counter.fetch_sub(1, std::memory_order_acq_rel) - 1;
-    }
-    // another worker adding an async task that shares the same dependent
-    else {
-      goto add_dependent;
-    }
-  }
-  else {
-    num_dependents = node->_join_counter.fetch_sub(1, std::memory_order_acq_rel) - 1;
-  }
-}
-
-// Procedure: _tear_down_dependent_async
-inline void Executor::_tear_down_dependent_async(Worker& worker, Node* node) {
-  
-  // this async task comes from Executor
-  auto& state = std::get_if<Node::DependentAsync>(&(node->_handle))->state;
-  auto target = Node::AsyncState::UNFINISHED;
-
-  while(!state.compare_exchange_weak(target, Node::AsyncState::FINISHED,
-                                     std::memory_order_acq_rel,
-                                     std::memory_order_relaxed)) {
-    target = Node::AsyncState::UNFINISHED;
-  }
-  
-  // spaw successors whenever their dependencies are resolved
-  worker._cache = nullptr;
-  for(size_t i=0; i<node->_successors.size(); ++i) {
-    //if(auto s = node->_successors[i]; --(s->_join_counter) == 0) {
-    if(auto s = node->_successors[i]; 
-      s->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1
-    ) {
-      if(worker._cache) {
-        _schedule(worker, worker._cache);
-      }
-      worker._cache = s;
-    }
-  }
-    
-  // remove myself from the asyncs using extraction to avoid calling
-  // ~Node inside the lock
-  typename std::unordered_set<std::shared_ptr<Node>>::node_type extracted;
-  {
-    std::shared_ptr<Node> ptr(node, [](Node*){});
-    std::scoped_lock lock(_asyncs_mutex); 
-    extracted = _asyncs.extract(ptr);
-    // assert(extracted.empty() == false);
-  }
-  
-  _decrement_topology_and_notify();
-}
-
-
-
-
-
-}  // end of namespace tf -----------------------------------------------------
-
diff --git a/bundled/taskflow-3.6.0/include/taskflow/core/async_task.hpp b/bundled/taskflow-3.6.0/include/taskflow/core/async_task.hpp
deleted file mode 100644 (file)
index 7c92d8e..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-#pragma once
-
-#include "graph.hpp"
-
-/**
-@file async_task.hpp
-@brief asynchronous task include file
-*/
-
-namespace tf {
-
-// ----------------------------------------------------------------------------
-// AsyncTask
-// ----------------------------------------------------------------------------
-
-/**
-@brief class to create a dependent asynchronous task
-
-A tf::AsyncTask is a lightweight handle that retains @em shared ownership
-of a dependent async task created by an executor.
-This shared ownership ensures that the async task remains alive when
-adding it to the dependency list of another async task, 
-thus avoiding the classical [ABA problem](https://en.wikipedia.org/wiki/ABA_problem).
-
-@code{.cpp}
-// main thread retains shared ownership of async task A
-tf::AsyncTask A = executor.silent_dependent_async([](){});
-
-// task A remains alive (i.e., at least one ref count by the main thread) 
-// when being added to the dependency list of async task B
-tf::AsyncTask B = executor.silent_dependent_async([](){}, A);
-@endcode
-
-Currently, tf::AsyncTask is implemented based on C++ smart pointer std::shared_ptr and 
-is considered cheap to copy or move as long as only a handful of objects
-own it.
-When a worker completes an async task, it will remove the task from the executor,
-decrementing the number of shared owners by one.
-If that counter reaches zero, the task is destroyed.
-*/
-class AsyncTask {
-  
-  friend class FlowBuilder;
-  friend class Runtime;
-  friend class Taskflow;
-  friend class TaskView;
-  friend class Executor;
-  
-  public:
-    
-    /**
-    @brief constructs an empty task handle
-    */
-    AsyncTask() = default;
-    
-    /**
-    @brief destroys the managed asynchronous task if this is the last owner
-    */
-    ~AsyncTask() = default;
-    
-    /**
-    @brief constructs an task that shares ownership of @c rhs
-    */
-    AsyncTask(const AsyncTask& rhs) = default;
-
-    /**
-    @brief move-constructs an task from @c rhs
-    */
-    AsyncTask(AsyncTask&& rhs) = default;
-    
-    /**
-    @brief shares ownership of the task managed by @c rhs
-    */
-    AsyncTask& operator = (const AsyncTask& rhs) = default;
-
-    /**
-    @brief move-assigns the task from @c rhs
-    */
-    AsyncTask& operator = (AsyncTask&& rhs) = default;
-    
-    /**
-    @brief checks if the task stores a non-null shared pointer
-    */
-    bool empty() const;
-    
-    /**
-    @brief release the ownership 
-    */
-    void reset();
-    
-    /**
-    @brief obtains a hash value of the underlying node
-    */
-    size_t hash_value() const;
-
-  private:
-
-    AsyncTask(std::shared_ptr<Node>);
-
-    std::shared_ptr<Node> _node;
-};
-
-// Constructor
-inline AsyncTask::AsyncTask(std::shared_ptr<Node> ptr) : _node {std::move(ptr)} {
-}
-
-// Function: empty
-inline bool AsyncTask::empty() const {
-  return _node == nullptr;
-}
-
-// Function: reset
-inline void AsyncTask::reset() {
-  _node.reset();
-}
-
-// Function: hash_value
-inline size_t AsyncTask::hash_value() const {
-  return std::hash<std::shared_ptr<Node>>{}(_node);
-}
-
-}  // end of namespace tf ----------------------------------------------------
-
-
-
diff --git a/bundled/taskflow-3.6.0/include/taskflow/utility/macros.hpp b/bundled/taskflow-3.6.0/include/taskflow/utility/macros.hpp
deleted file mode 100644 (file)
index e7598cf..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#if defined(_MSC_VER)
-  #define TF_FORCE_INLINE __forceinline
-#elif defined(__GNUC__) && __GNUC__ > 3
-  #define TF_FORCE_INLINE __attribute__((__always_inline__)) inline
-#else
-  #define TF_FORCE_INLINE inline
-#endif
-
-#if defined(_MSC_VER)
-  #define TF_NO_INLINE __declspec(noinline)
-#elif defined(__GNUC__) && __GNUC__ > 3
-  #define TF_NO_INLINE __attribute__((__noinline__))
-#else
-  #define TF_NO_INLINE
-#endif
similarity index 92%
rename from bundled/taskflow-3.6.0/LICENSE
rename to bundled/taskflow-3.7.0/LICENSE
index 37524bc43140661a3d06df66054f37ccd45cf429..aa56ff9223b0811f54ad343079a79ae02ad12de1 100644 (file)
@@ -1,6 +1,8 @@
 TASKFLOW MIT LICENSE 
 
-Copyright (c) 2018-2022 Dr. Tsung-Wei Huang
+Copyright (c) 2018-2024 Dr. Tsung-Wei Huang
+
+The University of Wisconsin at Madison
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
similarity index 93%
rename from bundled/taskflow-3.6.0/README.md
rename to bundled/taskflow-3.7.0/README.md
index cbb21a0757fbc638d906e1f09fcde889ff5cd822..5ea8d9ad2fea1fabb4662bb848a9dce1b5edc292 100644 (file)
@@ -6,7 +6,7 @@
 [![Windows](https://github.com/taskflow/taskflow/workflows/Windows/badge.svg)](https://github.com/taskflow/taskflow/actions?query=workflow%3AWindows)
 [![Wiki](image/api-doc.svg)][documentation]
 [![TFProf](image/tfprof.svg)](https://taskflow.github.io/tfprof/)
-[![Cite](image/cite-tpds.svg)][TPDS21]
+[![Cite](image/cite-tpds.svg)][TPDS22]
 
 Taskflow helps you quickly write parallel and heterogeneous task programs in modern C++
 
@@ -22,7 +22,7 @@ Taskflow lets you quickly implement task decomposition strategies
 that incorporate both regular and irregular compute patterns,
 together with an efficient *work-stealing* scheduler to optimize your multithreaded performance.
 
-| [Static Tasking](#get-started-with-taskflow) | [Dynamic Tasking](#dynamic-tasking) |
+| [Static Tasking](#start-your-first-taskflow-program) | [Subflow Tasking](#create-a-subflow-graph) |
 | :------------: | :-------------: |
 | ![](image/static_graph.svg) | <img align="right" src="image/dynamic_graph.svg" width="100%"> |
 
@@ -30,7 +30,7 @@ Taskflow supports conditional tasking for you to make rapid control-flow decisio
 across dependent tasks to implement cycles and conditions that were otherwise difficult to do
 with existing tools.
 
-| [Conditional Tasking](#conditional-tasking) |
+| [Conditional Tasking](#integrate-control-flow-to-a-task-graph) |
 | :-----------------: |
 | ![](image/condition.svg) |
 
@@ -38,7 +38,7 @@ Taskflow is composable. You can create large parallel graphs through
 composition of modular and reusable blocks that are easier to optimize
 at an individual scope.
 
-| [Taskflow Composition](#composable-tasking) |
+| [Taskflow Composition](#compose-task-graphs) |
 | :---------------: |
 |![](image/framework.svg)|
 
@@ -46,7 +46,7 @@ Taskflow supports heterogeneous tasking for you to
 accelerate a wide range of scientific computing applications
 by harnessing the power of CPU-GPU collaborative computing.
 
-| [Concurrent CPU-GPU Tasking](#concurrent-cpu-gpu-tasking) |
+| [Concurrent CPU-GPU Tasking](#offload-a-task-to-a-gpu) |
 | :-----------------: |
 | ![](image/cudaflow.svg) |
 
@@ -77,6 +77,7 @@ The following program (`simple.cpp`) creates four tasks
 `A`, `B`, `C`, and `D`, where `A` runs before `B` and `C`, and `D`
 runs after `B` and `C`.
 When `A` finishes, `B` and `C` can run in parallel.
+Try it live on [Compiler Explorer (godbolt)](https://godbolt.org/z/j8hx3xnnx)!
 
 
 
@@ -275,26 +276,25 @@ f1_module_task.succeed(f2A, f2B)
 ## Launch Asynchronous Tasks
 
 Taskflow supports *asynchronous* tasking.
-You can launch tasks asynchronously to incorporate independent, dynamic 
-parallelism in your taskflows.
+You can launch tasks asynchronously to dynamically explore task graph parallelism.
 
 ```cpp
 tf::Executor executor;
-tf::Taskflow taskflow;
 
 // create asynchronous tasks directly from an executor
-tf::Future<std::optional<int>> future = executor.async([](){ 
+std::future<int> future = executor.async([](){ 
   std::cout << "async task returns 1\n";
   return 1;
 }); 
-executor.silent_async([](){ std::cout << "async task of no return\n"; });
+executor.silent_async([](){ std::cout << "async task does not return\n"; });
 
-// launch an asynchronous task from a running task
-taskflow.emplace([&](){
-  executor.async([](){ std::cout << "async task within a task\n"; });
-});
+// create asynchronous tasks with dynamic dependencies
+tf::AsyncTask A = executor.silent_dependent_async([](){ printf("A\n"); });
+tf::AsyncTask B = executor.silent_dependent_async([](){ printf("B\n"); }, A);
+tf::AsyncTask C = executor.silent_dependent_async([](){ printf("C\n"); }, A);
+tf::AsyncTask D = executor.silent_dependent_async([](){ printf("D\n"); }, B, C);
 
-executor.run(taskflow).wait();
+executor.wait_for_all();
 ```
 
 ## Execute a Taskflow
@@ -446,5 +446,5 @@ You are completely free to re-distribute your work derived from Taskflow.
 [PayMe]:                 https://www.paypal.me/twhuang/10
 [email me]:              mailto:twh760812@gmail.com
 [Cpp Conference 2018]:   https://github.com/CppCon/CppCon2018
-[TPDS21]:                https://tsung-wei-huang.github.io/papers/tpds21-taskflow.pdf
+[TPDS22]:                https://tsung-wei-huang.github.io/papers/tpds21-taskflow.pdf
 
similarity index 97%
rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/critical.hpp
rename to bundled/taskflow-3.7.0/taskflow/algorithm/critical.hpp
index c781d282713891a7fd47c670bdeddc15670afa6f..46ac3959518113d6e45b63dc95670b79ce95cab6 100644 (file)
@@ -18,7 +18,7 @@ namespace tf {
 
 @brief class to create a critical region of limited workers to run tasks
 
-tf::CriticalSection is a warpper over tf::Semaphore and is specialized for
+tf::CriticalSection is a wrapper over tf::Semaphore and is specialized for
 limiting the maximum concurrency over a set of tasks.
 A critical section starts with an initial count representing that limit.
 When a task is added to the critical section,
similarity index 75%
rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/find.hpp
rename to bundled/taskflow-3.7.0/taskflow/algorithm/find.hpp
index ab0d8011deddbec5e2112563dbcd2bcd17fd8a8b..cb3d080c266b0def79f86c7ab2feaa9c80082a5b 100644 (file)
@@ -8,13 +8,13 @@ namespace detail {
 
 // Function: find_if_loop
 template <typename Iterator, typename Predicate>
-TF_FORCE_INLINE bool find_if_loop(
+bool find_if_loop(
   std::atomic<size_t>& offset, 
   Iterator& beg,
   size_t& prev_e,
   size_t  curr_b, 
   size_t  curr_e,
-  Predicate&& predicate
+  Predicate predicate
 ) {
   // early prune
   if(offset.load(std::memory_order_relaxed) < curr_b) {
@@ -33,13 +33,13 @@ TF_FORCE_INLINE bool find_if_loop(
 
 // Function: find_if_not_loop
 template <typename Iterator, typename Predicate>
-TF_FORCE_INLINE bool find_if_not_loop(
+bool find_if_not_loop(
   std::atomic<size_t>& offset, 
   Iterator& beg,
   size_t& prev_e,
   size_t  curr_b, 
   size_t  curr_e,
-  Predicate&& predicate
+  Predicate predicate
 ) {
 
   // early prune
@@ -57,30 +57,29 @@ TF_FORCE_INLINE bool find_if_not_loop(
   return false;
 }
 
+}  // namespace detail --------------------------------------------------------
+
 // Function: make_find_if_task
-template <typename B, typename E, typename T, typename UOP, typename P>
-TF_FORCE_INLINE auto make_find_if_task(
-  B first, E last, T& result, UOP predicate, P&& part
-) {
+template <typename B, typename E, typename T, typename UOP, typename P = DefaultPartitioner>
+auto make_find_if_task(B first, E last, T& result, UOP predicate, P part = P()) {
   
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
-  using namespace std::string_literals;
 
-  return 
-  [b=first, e=last, predicate, &result, part=std::forward<P>(part)] 
-  (Runtime& rt) mutable {
+  return [=, &result] (Runtime& rt) mutable {
 
     // fetch the stateful values
-    B_t beg = b;
-    E_t end = e;
+    B_t beg = first;
+    E_t end = last;
 
     size_t W = rt.executor().num_workers();
     size_t N = std::distance(beg, end);
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= part.chunk_size()) {
-      result = std::find_if(beg, end, predicate);
+      launch_loop(part, [&](){
+        result = std::find_if(beg, end, predicate);
+      });
       return;
     }
 
@@ -91,7 +90,7 @@ TF_FORCE_INLINE auto make_find_if_task(
     std::atomic<size_t> offset(N);
     
     // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
+    if constexpr(part.type() == PartitionerType::STATIC) {
 
       size_t chunk_size;
 
@@ -99,21 +98,20 @@ TF_FORCE_INLINE auto make_find_if_task(
       
         chunk_size = part.adjusted_chunk_size(N, W, w);
 
-        launch_loop(W, w, rt,
-          [N, W, curr_b, chunk_size, beg, &predicate, &offset, &part] 
-          () mutable {
+        launch_loop(W, w, rt, part,
+          [N, W, curr_b, chunk_size, beg, &predicate, &offset, &part] () mutable {
             part.loop_until(N, W, curr_b, chunk_size,
-              [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable {
+              [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable {
                 return detail::find_if_loop(
-                  offset, beg, prev_e, curr_b, curr_e, predicate
+                  offset, beg, prev_e, part_b, part_e, predicate
                 );
               }
-            ); 
+            );
           }
         );
       }
 
-      rt.join();
+      rt.corun_all();
     }
     // dynamic partitioner
     else {
@@ -126,7 +124,7 @@ TF_FORCE_INLINE auto make_find_if_task(
                 offset, beg, prev_e, curr_b, curr_e, predicate
               );
             }
-          ); 
+          );
         }
       );
     }
@@ -137,29 +135,26 @@ TF_FORCE_INLINE auto make_find_if_task(
 }
 
 // Function: make_find_if_not_task
-template <typename B, typename E, typename T, typename UOP, typename P>
-TF_FORCE_INLINE auto make_find_if_not_task(
-  B first, E last, T& result, UOP predicate, P&& part
-) {
+template <typename B, typename E, typename T, typename UOP, typename P = DefaultPartitioner>
+auto make_find_if_not_task(B first, E last, T& result, UOP predicate, P part = P()) {
   
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
-  using namespace std::string_literals;
 
-  return
-  [b=first, e=last, predicate, &result, part=std::forward<P>(part)] 
-  (Runtime& rt) mutable {
+  return [=, &result] (Runtime& rt) mutable {
 
     // fetch the stateful values
-    B_t beg = b;
-    E_t end = e;
+    B_t beg = first;
+    E_t end = last;
 
     size_t W = rt.executor().num_workers();
     size_t N = std::distance(beg, end);
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= part.chunk_size()) {
-      result = std::find_if_not(beg, end, predicate);
+      launch_loop(part, [&](){
+        result = std::find_if_not(beg, end, predicate);
+      });
       return;
     }
 
@@ -170,7 +165,7 @@ TF_FORCE_INLINE auto make_find_if_not_task(
     std::atomic<size_t> offset(N);
     
     // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
+    if constexpr(part.type() == PartitionerType::STATIC) {
 
       size_t chunk_size;
 
@@ -178,20 +173,20 @@ TF_FORCE_INLINE auto make_find_if_not_task(
       
         chunk_size = part.adjusted_chunk_size(N, W, w);
 
-        launch_loop(W, w, rt,
+        launch_loop(W, w, rt, part,
           [N, W, curr_b, chunk_size, beg, &predicate, &offset, &part] () mutable {
             part.loop_until(N, W, curr_b, chunk_size,
-              [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable {
+              [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable {
                 return detail::find_if_not_loop(
-                  offset, beg, prev_e, curr_b, curr_e, predicate
+                  offset, beg, prev_e, part_b, part_e, predicate
                 );
               }
-            ); 
+            );
           }
         );
       }
 
-      rt.join();
+      rt.corun_all();
     }
     // dynamic partitioner
     else {
@@ -204,7 +199,7 @@ TF_FORCE_INLINE auto make_find_if_not_task(
                 offset, beg, prev_e, curr_b, curr_e, predicate
               );
             }
-          ); 
+          );
         }
       );
     }
@@ -215,29 +210,26 @@ TF_FORCE_INLINE auto make_find_if_not_task(
 }
 
 // Function: make_min_element_task
-template <typename B, typename E, typename T, typename C, typename P>
-TF_FORCE_INLINE auto make_min_element_task(
-  B first, E last, T& result, C comp, P&& part
-) {
+template <typename B, typename E, typename T, typename C, typename P = DefaultPartitioner>
+auto make_min_element_task(B first, E last, T& result, C comp, P part = P()) {
 
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
-  using namespace std::string_literals;
 
-  return 
-  [b=first, e=last, &result, comp, part=std::forward<P>(part)] 
-  (Runtime& rt) mutable {
+  return [=, &result] (Runtime& rt) mutable {
 
     // fetch the iterator values
-    B_t beg = b;
-    E_t end = e;
+    B_t beg = first;
+    E_t end = last;
 
     size_t W = rt.executor().num_workers();
     size_t N = std::distance(beg, end);
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= part.chunk_size()) {
-      result = std::min_element(beg, end, comp);
+      launch_loop(part, [&](){
+        result = std::min_element(beg, end, comp);
+      });
       return;
     }
 
@@ -252,7 +244,7 @@ TF_FORCE_INLINE auto make_min_element_task(
     N--;
 
     // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
+    if constexpr(part.type() == PartitionerType::STATIC) {
       
       size_t chunk_size;
 
@@ -262,9 +254,8 @@ TF_FORCE_INLINE auto make_min_element_task(
         // variable sum needs to avoid copy at the first step
         chunk_size = std::max(size_t{2}, part.adjusted_chunk_size(N, W, w));
         
-        launch_loop(W, w, rt,
+        launch_loop(W, w, rt, part,
         [beg, curr_b, N, W, chunk_size, &comp, &mutex, &result, &part] () mutable {
-
           std::advance(beg, curr_b);
 
           if(N - curr_b == 1) {
@@ -281,21 +272,21 @@ TF_FORCE_INLINE auto make_min_element_task(
         
           // loop reduce
           part.loop(N, W, curr_b, chunk_size,
-            [&, prev_e=curr_b+2](size_t curr_b, size_t curr_e) mutable {
+            [&, prev_e=curr_b+2](size_t part_b, size_t part_e) mutable {
 
-              if(curr_b > prev_e) {
-                std::advance(beg, curr_b - prev_e);
+              if(part_b > prev_e) {
+                std::advance(beg, part_b - prev_e);
               }
               else {
-                curr_b = prev_e;
+                part_b = prev_e;
               }
 
-              for(size_t x=curr_b; x<curr_e; x++, beg++) {
+              for(size_t x=part_b; x<part_e; x++, beg++) {
                 if(comp(*beg, *smallest)) {
                   smallest = beg;
                 }
               }
-              prev_e = curr_e;
+              prev_e = part_e;
             }
           ); 
           
@@ -306,7 +297,7 @@ TF_FORCE_INLINE auto make_min_element_task(
           }
         });
       }
-      rt.join();
+      rt.corun_all();
     }
     // dynamic partitioner
     else {
@@ -337,14 +328,14 @@ TF_FORCE_INLINE auto make_min_element_task(
           
           // loop reduce
           part.loop(N, W, next, 
-            [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable {
-              std::advance(beg, curr_b - prev_e);
-              for(size_t x=curr_b; x<curr_e; x++, beg++) {
+            [&, prev_e=s0+2](size_t part_b, size_t part_e) mutable {
+              std::advance(beg, part_b - prev_e);
+              for(size_t x=part_b; x<part_e; x++, beg++) {
                 if(comp(*beg, *smallest)) {
                   smallest = beg;
                 }
               }
-              prev_e = curr_e;
+              prev_e = part_e;
             }
           ); 
           
@@ -360,27 +351,26 @@ TF_FORCE_INLINE auto make_min_element_task(
 }
 
 // Function: make_max_element_task
-template <typename B, typename E, typename T, typename C, typename P>
-TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P&& part) {
+template <typename B, typename E, typename T, typename C, typename P = DefaultPartitioner>
+auto make_max_element_task(B first, E last, T& result, C comp, P part = P()) {
 
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
-  using namespace std::string_literals;
 
-  return 
-  [b=first, e=last, &result, comp, part=std::forward<P>(part)] 
-  (Runtime& rt) mutable {
+  return [=, &result] (Runtime& rt) mutable {
 
     // fetch the iterator values
-    B_t beg = b;
-    E_t end = e;
+    B_t beg = first;
+    E_t end = last;
 
     size_t W = rt.executor().num_workers();
     size_t N = std::distance(beg, end);
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= part.chunk_size()) {
-      result = std::max_element(beg, end, comp);
+      launch_loop(part, [&](){
+        result = std::max_element(beg, end, comp);
+      });
       return;
     }
 
@@ -395,7 +385,7 @@ TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P
     N--;
 
     // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
+    if constexpr(part.type() == PartitionerType::STATIC) {
       
       size_t chunk_size;
 
@@ -405,9 +395,8 @@ TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P
         // variable sum needs to avoid copy at the first step
         chunk_size = std::max(size_t{2}, part.adjusted_chunk_size(N, W, w));
         
-        launch_loop(W, w, rt,
+        launch_loop(W, w, rt, part,
         [beg, curr_b, N, W, chunk_size, &comp, &mutex, &result, &part] () mutable {
-
           std::advance(beg, curr_b);
 
           if(N - curr_b == 1) {
@@ -424,21 +413,21 @@ TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P
         
           // loop reduce
           part.loop(N, W, curr_b, chunk_size,
-            [&, prev_e=curr_b+2](size_t curr_b, size_t curr_e) mutable {
+            [&, prev_e=curr_b+2](size_t part_b, size_t part_e) mutable {
 
-              if(curr_b > prev_e) {
-                std::advance(beg, curr_b - prev_e);
+              if(part_b > prev_e) {
+                std::advance(beg, part_b - prev_e);
               }
               else {
-                curr_b = prev_e;
+                part_b = prev_e;
               }
 
-              for(size_t x=curr_b; x<curr_e; x++, beg++) {
+              for(size_t x=part_b; x<part_e; x++, beg++) {
                 if(comp(*largest, *beg)) {
                   largest = beg;
                 }
               }
-              prev_e = curr_e;
+              prev_e = part_e;
             }
           ); 
           
@@ -449,7 +438,7 @@ TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P
           }
         });
       }
-      rt.join();
+      rt.corun_all();
     }
     // dynamic partitioner
     else {
@@ -480,14 +469,14 @@ TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P
           
           // loop reduce
           part.loop(N, W, next, 
-            [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable {
-              std::advance(beg, curr_b - prev_e);
-              for(size_t x=curr_b; x<curr_e; x++, beg++) {
+            [&, prev_e=s0+2](size_t part_b, size_t part_e) mutable {
+              std::advance(beg, part_b - prev_e);
+              for(size_t x=part_b; x<part_e; x++, beg++) {
                 if(comp(*largest, *beg)) {
                   largest = beg;
                 }
               }
-              prev_e = curr_e;
+              prev_e = part_e;
             }
           ); 
           
@@ -502,22 +491,18 @@ TF_FORCE_INLINE auto make_max_element_task(B first, E last, T& result, C comp, P
   };
 }
 
-}  // namespace detail --------------------------------------------------------
+
 
 // Function: find_if
 template <typename B, typename E, typename T, typename UOP, typename P>
-Task tf::FlowBuilder::find_if(B first, E last, T& result, UOP predicate, P&& part) {
-  return emplace(detail::make_find_if_task(
-    first, last, result, predicate, std::forward<P>(part)
-  ));
+Task tf::FlowBuilder::find_if(B first, E last, T& result, UOP predicate, P part) {
+  return emplace(make_find_if_task(first, last, result, predicate, part));
 }
 
 // Function: find_if_not
 template <typename B, typename E, typename T, typename UOP, typename P>
-Task tf::FlowBuilder::find_if_not(B first, E last, T& result, UOP predicate, P&& part) {
-  return emplace(detail::make_find_if_not_task(
-    first, last, result, predicate, std::forward<P>(part)
-  ));
+Task tf::FlowBuilder::find_if_not(B first, E last, T& result, UOP predicate, P part) {
+  return emplace(make_find_if_not_task(first, last, result, predicate, part));
 }
 
 // ----------------------------------------------------------------------------
@@ -526,10 +511,8 @@ Task tf::FlowBuilder::find_if_not(B first, E last, T& result, UOP predicate, P&&
 
 // Function: min_element
 template <typename B, typename E, typename T, typename C, typename P>
-Task FlowBuilder::min_element(B first, E last, T& result, C comp, P&& part) {
-  return emplace(detail::make_min_element_task(
-    first, last, result, comp, std::forward<P>(part)
-  ));
+Task FlowBuilder::min_element(B first, E last, T& result, C comp, P part) {
+  return emplace(make_min_element_task(first, last, result, comp, part));
 }
 
 // ----------------------------------------------------------------------------
@@ -538,10 +521,8 @@ Task FlowBuilder::min_element(B first, E last, T& result, C comp, P&& part) {
 
 // Function: max_element
 template <typename B, typename E, typename T, typename C, typename P>
-Task FlowBuilder::max_element(B first, E last, T& result, C comp, P&& part) {
-  return emplace(detail::make_max_element_task(
-    first, last, result, comp, std::forward<P>(part)
-  ));
+Task FlowBuilder::max_element(B first, E last, T& result, C comp, P part) {
+  return emplace(make_max_element_task(first, last, result, comp, part));
 }
 
 }  // end of namespace tf -----------------------------------------------------
similarity index 59%
rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/for_each.hpp
rename to bundled/taskflow-3.7.0/taskflow/algorithm/for_each.hpp
index d15958abd780c7787c7d6af6f3574ab591a5aa3b..8c98e84ea252c69991394a83d889809d3120ce46 100644 (file)
@@ -4,17 +4,14 @@
 
 namespace tf {
 
-namespace detail {
-
 // Function: make_for_each_task
-template <typename B, typename E, typename C, typename P>
-TF_FORCE_INLINE auto make_for_each_task(B beg, E end, C c, P&& part) {
-  
+template <typename B, typename E, typename C, typename P = DefaultPartitioner>
+auto make_for_each_task(B b, E e, C c, P part = P()) {
+
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
-  using namespace std::string_literals;
 
-  return [b=beg, e=end, c, part=std::forward<P>(part)] (Runtime& rt) mutable {
+  return [=] (Runtime& rt) mutable {
 
     // fetch the stateful values
     B_t beg = b;
@@ -25,7 +22,9 @@ TF_FORCE_INLINE auto make_for_each_task(B beg, E end, C c, P&& part) {
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= part.chunk_size()) {
-      std::for_each(beg, end, c);
+      launch_loop(part, [&](){
+        std::for_each(beg, end, c);
+      });
       return;
     }
 
@@ -34,69 +33,73 @@ TF_FORCE_INLINE auto make_for_each_task(B beg, E end, C c, P&& part) {
     }
     
     // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
+    if constexpr(part.type() == PartitionerType::STATIC) {
       size_t chunk_size;
       for(size_t w=0, curr_b=0; w<W && curr_b < N; ++w, curr_b += chunk_size) {
         chunk_size = part.adjusted_chunk_size(N, W, w);
-        launch_loop(W, w, rt, [=, &c, &part] () mutable {
+        launch_loop(W, w, rt, part, [=, &c, &part] () mutable {
           part.loop(N, W, curr_b, chunk_size,
-            [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable {
-              std::advance(beg, curr_b - prev_e);
-              for(size_t x = curr_b; x<curr_e; x++) {
+            [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable {
+              std::advance(beg, part_b - prev_e);
+              for(size_t x = part_b; x<part_e; x++) {
                 c(*beg++);
               }
-              prev_e = curr_e;
+              prev_e = part_e;
             }
           ); 
         });
       }
 
-      rt.join();
+      rt.corun_all();
     }
     // dynamic partitioner
     else {
       std::atomic<size_t> next(0);
       launch_loop(N, W, rt, next, part, [=, &c, &next, &part] () mutable {
         part.loop(N, W, next, 
-          [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable {
-            std::advance(beg, curr_b - prev_e);
-            for(size_t x = curr_b; x<curr_e; x++) {
+          [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable {
+            std::advance(beg, part_b - prev_e);
+            for(size_t x = part_b; x<part_e; x++) {
               c(*beg++);
             }
-            prev_e = curr_e;
+            prev_e = part_e;
           }
-        ); 
+        );
       });
     }
   };
 }
 
 // Function: make_for_each_index_task
-template <typename B, typename E, typename S, typename C, typename P>
-TF_FORCE_INLINE auto make_for_each_index_task(B beg, E end, S inc, C c, P&& part){
-
-  using namespace std::string_literals;
+template <typename B, typename E, typename S, typename C, typename P = DefaultPartitioner>
+auto make_for_each_index_task(B b, E e, S s, C c, P part = P()){
 
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
   using S_t = std::decay_t<unwrap_ref_decay_t<S>>;
 
-  return [b=beg, e=end, a=inc, c, part=std::forward<P>(part)] 
-  (Runtime& rt) mutable {
+  return [=] (Runtime& rt) mutable {
 
     // fetch the iterator values
     B_t beg = b;
     E_t end = e;
-    S_t inc = a;
+    S_t inc = s;
+    
+    // nothing to be done if the range is invalid
+    if(is_range_invalid(beg, end, inc)) {
+      return;
+    }
 
     size_t W = rt.executor().num_workers();
     size_t N = distance(beg, end, inc);
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= part.chunk_size()) {
-      for(size_t x=0; x<N; x++, beg+=inc) {
-        c(beg);
-      }
+      launch_loop(part, [&](){
+        for(size_t x=0; x<N; x++, beg+=inc) {
+          c(beg);
+        }
+      });
       return;
     }
 
@@ -105,54 +108,50 @@ TF_FORCE_INLINE auto make_for_each_index_task(B beg, E end, S inc, C c, P&& part
     }
     
     // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
-
+    if constexpr(part.type() == PartitionerType::STATIC) {
       size_t chunk_size;
-
       for(size_t w=0, curr_b=0; w<W && curr_b < N; ++w, curr_b += chunk_size) {
         chunk_size = part.adjusted_chunk_size(N, W, w);
-        launch_loop(W, w, rt, [=, &c, &part] () mutable {
+        launch_loop(W, w, rt, part, [=, &c, &part] () mutable {
           part.loop(N, W, curr_b, chunk_size,
-            [&](size_t curr_b, size_t curr_e) {
-              auto idx = static_cast<B_t>(curr_b) * inc + beg;
-              for(size_t x=curr_b; x<curr_e; x++, idx += inc) {
+            [&](size_t part_b, size_t part_e) {
+              auto idx = static_cast<B_t>(part_b) * inc + beg;
+              for(size_t x=part_b; x<part_e; x++, idx += inc) {
                 c(idx);
               }
             }
-          ); 
+          );
         });
       }
 
-      rt.join();
+      rt.corun_all();
     }
     // dynamic partitioner
     else {
       std::atomic<size_t> next(0);
       launch_loop(N, W, rt, next, part, [=, &c, &next, &part] () mutable {
         part.loop(N, W, next, 
-          [&](size_t curr_b, size_t curr_e) {
-            auto idx = static_cast<B_t>(curr_b) * inc + beg;
-            for(size_t x=curr_b; x<curr_e; x++, idx += inc) {
+          [&](size_t part_b, size_t part_e) {
+            auto idx = static_cast<B_t>(part_b) * inc + beg;
+            for(size_t x=part_b; x<part_e; x++, idx += inc) {
               c(idx);
             }
           }
-        ); 
+        );
       });
     }
   };
 }
 
-}  // end of namespace detail -------------------------------------------------
-
 // ----------------------------------------------------------------------------
 // for_each
 // ----------------------------------------------------------------------------
 
 // Function: for_each
 template <typename B, typename E, typename C, typename P>
-Task FlowBuilder::for_each(B beg, E end, C c, P&& part) {
+Task FlowBuilder::for_each(B beg, E end, C c, P part) {
   return emplace(
-    detail::make_for_each_task(beg, end, c, std::forward<P>(part))
+    make_for_each_task(beg, end, c, part)
   );
 }
 
@@ -162,9 +161,9 @@ Task FlowBuilder::for_each(B beg, E end, C c, P&& part) {
 
 // Function: for_each_index
 template <typename B, typename E, typename S, typename C, typename P>
-Task FlowBuilder::for_each_index(B beg, E end, S inc, C c, P&& part){
+Task FlowBuilder::for_each_index(B beg, E end, S inc, C c, P part){
   return emplace(
-    detail::make_for_each_index_task(beg, end, inc, c, std::forward<P>(part))
+    make_for_each_index_task(beg, end, inc, c, part)
   );
 }
 
similarity index 57%
rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/launch.hpp
rename to bundled/taskflow-3.7.0/taskflow/algorithm/launch.hpp
index 363223e60e95da162d58a04345444388379e7131..527fb2fe07e3309e2cb154636f10d8a5fa3d49b4 100644 (file)
@@ -1,9 +1,26 @@
 #pragma once
 
+#include <functional>
 #include "../core/async.hpp"
 
 namespace tf {
 
+// Function: launch_loop
+template<typename P, typename Loop>
+TF_FORCE_INLINE void launch_loop(P part, Loop loop) {
+
+  constexpr bool is_default_wrapper_v = std::is_same_v<
+    typename std::decay_t<P>::closure_wrapper_type, DefaultClosureWrapper
+  >;
+  
+  if constexpr(is_default_wrapper_v) {
+    loop();
+  }
+  else {
+    std::invoke(part.closure_wrapper(), loop);
+  }
+}
+
 // Function: launch_loop
 template <typename P, typename Loop>
 TF_FORCE_INLINE void launch_loop(
@@ -11,8 +28,8 @@ TF_FORCE_INLINE void launch_loop(
   size_t W, 
   Runtime& rt, 
   std::atomic<size_t>& next, 
-  P&& part, 
-  Loop&& loop
+  P part, 
+  Loop loop
 ) {
 
   //static_assert(std::is_lvalue_reference_v<Loop>, "");
@@ -27,31 +44,32 @@ TF_FORCE_INLINE void launch_loop(
     }
     // tail optimization
     if(r <= part.chunk_size() || w == W-1) {
-      loop();
+      launch_loop(part, loop);
       break;
     }
     else {
-      rt.silent_async_unchecked("loop-"s + std::to_string(w), loop);
+      rt.silent_async_unchecked([=](){ launch_loop(part, loop); });
     }
   }
       
-  rt.join();
+  rt.corun_all();
 }
 
 // Function: launch_loop
-template <typename Loop>
+template <typename P, typename Loop>
 TF_FORCE_INLINE void launch_loop(
   size_t W,
   size_t w,
   Runtime& rt, 
-  Loop&& loop 
+  P part,
+  Loop loop 
 ) {
   using namespace std::string_literals;
   if(w == W-1) {
-    loop();
+    launch_loop(part, loop);
   }
   else {
-    rt.silent_async_unchecked("loop-"s + std::to_string(w), loop);
+    rt.silent_async_unchecked([=](){ launch_loop(part, loop); });
   }
 }
 
similarity index 55%
rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/partitioner.hpp
rename to bundled/taskflow-3.7.0/taskflow/algorithm/partitioner.hpp
index 4a253fafe6240d2c79d10d06e920515826be58b1..04406f8343d0e465a31cf89a6376e378abdcd89e 100644 (file)
 
 namespace tf {
 
+/**
+@enum PartitionerType
+
+@brief enumeration of all partitioner types
+*/  
+enum class PartitionerType : int {
+  /** @brief static partitioner type */
+  STATIC,
+  /** @brief dynamic partitioner type */
+  DYNAMIC
+};
+
+
+//template <typename C>
+//class PartitionInvoker : public PartitionerBase {
+//
+//  protected
+//
+//  C _closure;
+//
+//  template <typename... ArgsT>
+//  auto operator()(ArgsT&&... args) {
+//    return std::invoke(closure, std::forward<ArgsT>(args)...);
+//  }
+//
+//  template <typename... ArgsT>
+//  auto operator()(ArgsT&&... args) const {
+//    return std::invoke(closure, std::forward<ArgsT>(args)...);
+//  }
+//
+//};
+
+/**
+@struct DefaultClosureWrapper
+
+@brief default closure wrapper that simplies runs the given closure as is
+*/
+struct DefaultClosureWrapper {
+};
+
+/**
+@private
+*/
+struct IsPartitioner {
+};
+
 // ----------------------------------------------------------------------------
 // Partitioner Base
 // ----------------------------------------------------------------------------
@@ -20,17 +66,19 @@ namespace tf {
 
 @brief class to derive a partitioner for scheduling parallel algorithms
 
+@tparam C closure wrapper type
+
 The class provides base methods to derive a partitioner that can be used
 to schedule parallel iterations (e.g., tf::Taskflow::for_each).
 
 An partitioner defines the scheduling method for running parallel algorithms,
 such tf::Taskflow::for_each, tf::Taskflow::reduce, and so on.
-By default, we provide the following partitioners:
+By default, we provide the following partitioners: 
 
-+ tf::GuidedPartitioner to enable guided scheduling algorithm of adaptive chunk size
++ tf::GuidedPartitioner  to enable guided scheduling algorithm of adaptive chunk size
 + tf::DynamicPartitioner to enable dynamic scheduling algorithm of equal chunk size
-+ tf::StaticPartitioner to enable static scheduling algorithm of static chunk size
-+ tf::RandomPartitioner to enable random scheduling algorithm of random chunk size
++ tf::StaticPartitioner  to enable static scheduling algorithm of static chunk size
++ tf::RandomPartitioner  to enable random scheduling algorithm of random chunk size
 
 Depending on applications, partitioning algorithms can impact the performance
 a lot. 
@@ -40,10 +88,53 @@ On the other hand, if the work unit per iteration is irregular and unbalanced,
 tf::GuidedPartitioner or tf::DynamicPartitioner can outperform tf::StaticPartitioner.
 In most situations, tf::GuidedPartitioner can deliver decent performance and
 is thus used as our default partitioner.
+
+@note
+Giving the partition size of 0 lets the %Taskflow runtime automatically determines
+the partition size for the given partitioner.
+
+
+In addition to partition size, the application can specify a closure wrapper
+for a partitioner.
+A closure wrapper allows the application to wrapper a partitioned task 
+(i.e., closure) with a custom function object that performs additional tasks.
+For example:
+
+@code{.cpp}
+std::atomic<int> count = 0;
+tf::Taskflow taskflow;
+taskflow.for_each_index(0, 100, 1, 
+  [](){                 
+    printf("%d\n", i); 
+  },
+  tf::StaticPartitioner(0, [](auto&& closure){
+    // do something before invoking the partitioned task
+    // ...
+    
+    // invoke the partitioned task
+    closure();
+
+    // do something else after invoking the partitioned task
+    // ...
+  }
+);
+executor.run(taskflow).wait();
+@endcode
+
+@note
+The default closure wrapper (tf::DefaultClosureWrapper) does nothing but invoke
+the partitioned task (closure).
+
 */
-class PartitionerBase {
+template <typename C = DefaultClosureWrapper>
+class PartitionerBase : public IsPartitioner {
 
   public:
+  
+  /** 
+  @brief the closure type
+  */
+  using closure_wrapper_type = C;
 
   /**
   @brief default constructor
@@ -54,6 +145,14 @@ class PartitionerBase {
   @brief construct a partitioner with the given chunk size
   */
   explicit PartitionerBase(size_t chunk_size) : _chunk_size {chunk_size} {}
+  
+  /**
+  @brief construct a partitioner with the given chunk size and closure wrapper
+  */
+  PartitionerBase(size_t chunk_size, C&& closure_wrapper) :
+    _chunk_size {chunk_size},
+    _closure_wrapper {std::forward<C>(closure_wrapper)} {
+  }
 
   /**
   @brief query the chunk size of this partitioner
@@ -65,41 +164,100 @@ class PartitionerBase {
   */
   void chunk_size(size_t cz) { _chunk_size = cz; }
 
+  /**
+  @brief acquire an immutable access to the closure wrapper object
+  */
+  const C& closure_wrapper() const { return _closure_wrapper; }
+
+  /**
+  @brief modify the closure wrapper object
+  */
+  template <typename F>
+  void closure_wrapper(F&& fn) { _closure_wrapper = std::forward<F>(fn); }
+
   protected:
   
   /**
   @brief chunk size 
   */
   size_t _chunk_size{0};
+
+  /**
+  @brief closure wrapper
+  */
+  C _closure_wrapper;
 };
 
 // ----------------------------------------------------------------------------
 // Guided Partitioner
 // ----------------------------------------------------------------------------
-  
+
 /**
 @class GuidedPartitioner
 
+@tparam C closure wrapper type (default tf::DefaultClosureWrapper)
+
 @brief class to construct a guided partitioner for scheduling parallel algorithms
 
 The size of a partition is proportional to the number of unassigned iterations 
 divided by the number of workers, 
 and the size will gradually decrease to the given chunk size.
 The last partition may be smaller than the chunk size.
+
+In addition to partition size, the application can specify a closure wrapper
+for a guided partitioner.
+A closure wrapper allows the application to wrapper a partitioned task 
+(i.e., closure) with a custom function object that performs additional tasks.
+For example:
+
+@code{.cpp}
+std::atomic<int> count = 0;
+tf::Taskflow taskflow;
+taskflow.for_each_index(0, 100, 1, 
+  [](){                 
+    printf("%d\n", i); 
+  },
+  tf::GuidedPartitioner(0, [](auto&& closure){
+    // do something before invoking the partitioned task
+    // ...
+    
+    // invoke the partitioned task
+    closure();
+
+    // do something else after invoking the partitioned task
+    // ...
+  }
+);
+executor.run(taskflow).wait();
+@endcode
 */
-class GuidedPartitioner : public PartitionerBase {
+template <typename C = DefaultClosureWrapper>
+class GuidedPartitioner : public PartitionerBase<C> {
 
   public:
   
+  /**
+  @brief queries the partition type (dynamic)
+  */
+  static constexpr PartitionerType type() { return PartitionerType::DYNAMIC; }
+  
   /**
   @brief default constructor
   */
-  GuidedPartitioner() : PartitionerBase{1} {}
+  GuidedPartitioner() = default;
 
   /**
   @brief construct a guided partitioner with the given chunk size
+
   */
-  explicit GuidedPartitioner(size_t sz) : PartitionerBase (sz) {}
+  explicit GuidedPartitioner(size_t sz) : PartitionerBase<C> (sz) {}
+  /**
+  @brief construct a guided partitioner with the given chunk size and the closure
+  */ 
+  explicit GuidedPartitioner(size_t sz, C&& closure) :
+    PartitionerBase<C>(sz, std::forward<C>(closure)) {
+  }
   
   // --------------------------------------------------------------------------
   // scheduling methods
@@ -112,13 +270,10 @@ class GuidedPartitioner : public PartitionerBase {
     std::enable_if_t<std::is_invocable_r_v<void, F, size_t, size_t>, void>* = nullptr
   >
   void loop(
-    size_t N, 
-    size_t W, 
-    std::atomic<size_t>& next, 
-    F&& func
+    size_t N, size_t W, std::atomic<size_t>& next, F&& func
   ) const {
 
-    size_t chunk_size = (_chunk_size == 0) ? size_t{1} : _chunk_size;
+    size_t chunk_size = (this->_chunk_size == 0) ? size_t{1} : this->_chunk_size;
 
     size_t p1 = 2 * W * (chunk_size + 1);
     float  p2 = 0.5f / static_cast<float>(W);
@@ -163,13 +318,10 @@ class GuidedPartitioner : public PartitionerBase {
     std::enable_if_t<std::is_invocable_r_v<bool, F, size_t, size_t>, void>* = nullptr
   >
   void loop_until(
-    size_t N, 
-    size_t W, 
-    std::atomic<size_t>& next, 
-    F&& func
+    size_t N, size_t W, std::atomic<size_t>& next, F&& func
   ) const {
 
-    size_t chunk_size = (_chunk_size == 0) ? size_t{1} : _chunk_size;
+    size_t chunk_size = (this->_chunk_size == 0) ? size_t{1} : this->_chunk_size;
 
     size_t p1 = 2 * W * (chunk_size + 1);
     float  p2 = 0.5f / static_cast<float>(W);
@@ -210,6 +362,7 @@ class GuidedPartitioner : public PartitionerBase {
       }
     }
   }
+
 };
 
 // ----------------------------------------------------------------------------
@@ -221,29 +374,71 @@ class GuidedPartitioner : public PartitionerBase {
 
 @brief class to construct a dynamic partitioner for scheduling parallel algorithms
 
+@tparam C closure wrapper type (default tf::DefaultClosureWrapper)
+
 The partitioner splits iterations into many partitions each of size equal to 
 the given chunk size.
 Different partitions are distributed dynamically to workers 
 without any specific order.
+
+In addition to partition size, the application can specify a closure wrapper
+for a dynamic partitioner.
+A closure wrapper allows the application to wrapper a partitioned task 
+(i.e., closure) with a custom function object that performs additional tasks.
+For example:
+
+@code{.cpp}
+std::atomic<int> count = 0;
+tf::Taskflow taskflow;
+taskflow.for_each_index(0, 100, 1, 
+  [](){                 
+    printf("%d\n", i); 
+  },
+  tf::DynamicPartitioner(0, [](auto&& closure){
+    // do something before invoking the partitioned task
+    // ...
+    
+    // invoke the partitioned task
+    closure();
+
+    // do something else after invoking the partitioned task
+    // ...
+  }
+);
+executor.run(taskflow).wait();
+@endcode
 */
-class DynamicPartitioner : public PartitionerBase {
+template <typename C = DefaultClosureWrapper>
+class DynamicPartitioner : public PartitionerBase<C> {
 
   public:
+  
+  /**
+  @brief queries the partition type (dynamic)
+  */
+  static constexpr PartitionerType type() { return PartitionerType::DYNAMIC; }
 
   /**
   @brief default constructor
   */
-  DynamicPartitioner() : PartitionerBase{1} {};
+  DynamicPartitioner() = default;
   
   /**
   @brief construct a dynamic partitioner with the given chunk size
   */
-  explicit DynamicPartitioner(size_t sz) : PartitionerBase (sz) {}
+  explicit DynamicPartitioner(size_t sz) : PartitionerBase<C>(sz) {}
+  
+  /**
+  @brief construct a dynamic partitioner with the given chunk size and the closure
+  */ 
+  explicit DynamicPartitioner(size_t sz, C&& closure) :
+    PartitionerBase<C>(sz, std::forward<C>(closure)) {
+  }
   
   // --------------------------------------------------------------------------
   // scheduling methods
   // --------------------------------------------------------------------------
-  
+
   /**
   @private
   */
@@ -251,13 +446,10 @@ class DynamicPartitioner : public PartitionerBase {
     std::enable_if_t<std::is_invocable_r_v<void, F, size_t, size_t>, void>* = nullptr
   >
   void loop(
-    size_t N, 
-    size_t, 
-    std::atomic<size_t>& next, 
-    F&& func
+    size_t N, size_t, std::atomic<size_t>& next, F&& func
   ) const {
 
-    size_t chunk_size = (_chunk_size == 0) ? size_t{1} : _chunk_size;
+    size_t chunk_size = (this->_chunk_size == 0) ? size_t{1} : this->_chunk_size;
     size_t curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed);
 
     while(curr_b < N) {
@@ -273,13 +465,10 @@ class DynamicPartitioner : public PartitionerBase {
     std::enable_if_t<std::is_invocable_r_v<bool, F, size_t, size_t>, void>* = nullptr
   >
   void loop_until(
-    size_t N, 
-    size_t, 
-    std::atomic<size_t>& next, 
-    F&& func
+    size_t N, size_t, std::atomic<size_t>& next, F&& func
   ) const {
 
-    size_t chunk_size = (_chunk_size == 0) ? size_t{1} : _chunk_size;
+    size_t chunk_size = (this->_chunk_size == 0) ? size_t{1} : this->_chunk_size;
     size_t curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed);
 
     while(curr_b < N) {
@@ -289,6 +478,7 @@ class DynamicPartitioner : public PartitionerBase {
       curr_b = next.fetch_add(chunk_size, std::memory_order_relaxed);
     }
   }
+
 };
 
 // ----------------------------------------------------------------------------
@@ -298,7 +488,9 @@ class DynamicPartitioner : public PartitionerBase {
 /**
 @class StaticPartitioner
 
-@brief class to construct a dynamic partitioner for scheduling parallel algorithms
+@brief class to construct a static partitioner for scheduling parallel algorithms
+
+@tparam C closure wrapper type (default tf::DefaultClosureWrapper)
 
 The partitioner divides iterations into chunks and distributes chunks 
 to workers in order.
@@ -312,20 +504,60 @@ taskflow.for_each(
 );
 executor.run(taskflow).run();
 @endcode
+
+In addition to partition size, the application can specify a closure wrapper
+for a static partitioner.
+A closure wrapper allows the application to wrapper a partitioned task 
+(i.e., closure) with a custom function object that performs additional tasks.
+For example:
+
+@code{.cpp}
+std::atomic<int> count = 0;
+tf::Taskflow taskflow;
+taskflow.for_each_index(0, 100, 1, 
+  [](){                 
+    printf("%d\n", i); 
+  },
+  tf::StaticPartitioner(0, [](auto&& closure){
+    // do something before invoking the partitioned task
+    // ...
+    
+    // invoke the partitioned task
+    closure();
+
+    // do something else after invoking the partitioned task
+    // ...
+  }
+);
+executor.run(taskflow).wait();
+@endcode
 */
-class StaticPartitioner : public PartitionerBase {
+template <typename C = DefaultClosureWrapper>
+class StaticPartitioner : public PartitionerBase<C> {
 
   public:
+  
+  /**
+  @brief queries the partition type (static)
+  */
+  static constexpr PartitionerType type() { return PartitionerType::STATIC; }
 
   /**
   @brief default constructor
   */
-  StaticPartitioner() : PartitionerBase{0} {};
+  StaticPartitioner() = default;
   
   /**
-  @brief construct a dynamic partitioner with the given chunk size
+  @brief construct a static partitioner with the given chunk size
   */
-  explicit StaticPartitioner(size_t sz) : PartitionerBase(sz) {}
+  explicit StaticPartitioner(size_t sz) : PartitionerBase<C>(sz) {}
+  
+  /**
+  @brief construct a static partitioner with the given chunk size and the closure
+  */ 
+  explicit StaticPartitioner(size_t sz, C&& closure) :
+    PartitionerBase<C>(sz, std::forward<C>(closure)) {
+  }
   
   /**
   @brief queries the adjusted chunk size
@@ -335,13 +567,13 @@ class StaticPartitioner : public PartitionerBase {
   @c W is the number of workers, and @c w is the worker ID.
   */
   size_t adjusted_chunk_size(size_t N, size_t W, size_t w) const {
-    return _chunk_size ? _chunk_size : N/W + (w < N%W);
+    return this->_chunk_size ? this->_chunk_size : N/W + (w < N%W);
   }
   
   // --------------------------------------------------------------------------
   // scheduling methods
   // --------------------------------------------------------------------------
-
+  
   /**
   @private
   */
@@ -349,11 +581,7 @@ class StaticPartitioner : public PartitionerBase {
     std::enable_if_t<std::is_invocable_r_v<void, F, size_t, size_t>, void>* = nullptr
   >
   void loop(
-    size_t N, 
-    size_t W, 
-    size_t curr_b, 
-    size_t chunk_size,
-    F&& func
+    size_t N, size_t W, size_t curr_b, size_t chunk_size, F&& func
   ) {
     size_t stride = W * chunk_size;
     while(curr_b < N) {
@@ -370,11 +598,7 @@ class StaticPartitioner : public PartitionerBase {
     std::enable_if_t<std::is_invocable_r_v<bool, F, size_t, size_t>, void>* = nullptr
   >
   void loop_until(
-    size_t N, 
-    size_t W, 
-    size_t curr_b, 
-    size_t chunk_size,
-    F&& func
+    size_t N, size_t W, size_t curr_b, size_t chunk_size, F&& func
   ) {
     size_t stride = W * chunk_size;
     while(curr_b < N) {
@@ -396,15 +620,49 @@ class StaticPartitioner : public PartitionerBase {
 
 @brief class to construct a random partitioner for scheduling parallel algorithms
 
+@tparam C closure wrapper type (default tf::DefaultClosureWrapper)
+
 Similar to tf::DynamicPartitioner, 
 the partitioner splits iterations into many partitions but each with a random
 chunk size in the range, <tt>c = [alpha * N * W, beta * N * W]</tt>.
 By default, @c alpha is <tt>0.01</tt> and @c beta is <tt>0.5</tt>, respectively.
 
+In addition to partition size, the application can specify a closure wrapper
+for a random partitioner.
+A closure wrapper allows the application to wrapper a partitioned task 
+(i.e., closure) with a custom function object that performs additional tasks.
+For example:
+
+@code{.cpp}
+std::atomic<int> count = 0;
+tf::Taskflow taskflow;
+taskflow.for_each_index(0, 100, 1, 
+  [](){                 
+    printf("%d\n", i); 
+  },
+  tf::RandomPartitioner(0, [](auto&& closure){
+    // do something before invoking the partitioned task
+    // ...
+    
+    // invoke the partitioned task
+    closure();
+
+    // do something else after invoking the partitioned task
+    // ...
+  }
+);
+executor.run(taskflow).wait();
+@endcode
 */
-class RandomPartitioner : public PartitionerBase {
+template <typename C = DefaultClosureWrapper>
+class RandomPartitioner : public PartitionerBase<C> {
 
   public:
+  
+  /**
+  @brief queries the partition type (dynamic)
+  */
+  static constexpr PartitionerType type() { return PartitionerType::DYNAMIC; }
 
   /**
   @brief default constructor
@@ -412,14 +670,29 @@ class RandomPartitioner : public PartitionerBase {
   RandomPartitioner() = default;
   
   /**
-  @brief constructs a random partitioner 
+  @brief construct a dynamic partitioner with the given chunk size
   */
-  RandomPartitioner(size_t cz) : PartitionerBase(cz) {}
+  explicit RandomPartitioner(size_t sz) : PartitionerBase<C>(sz) {}
+  
+  /**
+  @brief construct a random partitioner with the given chunk size and the closure
+  */ 
+  explicit RandomPartitioner(size_t sz, C&& closure) :
+    PartitionerBase<C>(sz, std::forward<C>(closure)) {
+  }
   
   /**
   @brief constructs a random partitioner with the given parameters
   */
-  RandomPartitioner(float alpha, float beta) : _alpha {alpha}, _beta {beta} {}
+  RandomPartitioner(float alpha, float beta) : _alpha{alpha}, _beta{beta} {}
+
+  /**
+  @brief constructs a random partitioner with the given parameters and the closure
+  */
+  RandomPartitioner(float alpha, float beta, C&& closure) : 
+    _alpha {alpha}, _beta {beta}, 
+    PartitionerBase<C>(0, std::forward<C>(closure)) {
+  }
 
   /**
   @brief queries the @c alpha value
@@ -463,10 +736,7 @@ class RandomPartitioner : public PartitionerBase {
     std::enable_if_t<std::is_invocable_r_v<void, F, size_t, size_t>, void>* = nullptr
   >
   void loop(
-    size_t N, 
-    size_t W, 
-    std::atomic<size_t>& next, 
-    F&& func
+    size_t N, size_t W, std::atomic<size_t>& next, F&& func
   ) const {
 
     auto [b1, b2] = chunk_size_range(N, W); 
@@ -491,10 +761,7 @@ class RandomPartitioner : public PartitionerBase {
     std::enable_if_t<std::is_invocable_r_v<bool, F, size_t, size_t>, void>* = nullptr
   >
   void loop_until(
-    size_t N, 
-    size_t W, 
-    std::atomic<size_t>& next, 
-    F&& func
+    size_t N, size_t W, std::atomic<size_t>& next, F&& func
   ) const {
 
     auto [b1, b2] = chunk_size_range(N, W); 
@@ -518,7 +785,6 @@ class RandomPartitioner : public PartitionerBase {
 
   float _alpha {0.01f};
   float _beta  {0.5f};
-
 };
 
 /**
@@ -527,15 +793,15 @@ class RandomPartitioner : public PartitionerBase {
 Guided partitioner can achieve decent performance for most parallel algorithms,
 especially for those with irregular and unbalanced workload per iteration.
 */
-using DefaultPartitioner = GuidedPartitioner;
+using DefaultPartitioner = GuidedPartitioner<>;
 
 /**
 @brief determines if a type is a partitioner 
 
 A partitioner is a derived type from tf::PartitionerBase.
 */
-template <typename C>
-inline constexpr bool is_partitioner_v = std::is_base_of<PartitionerBase, C>::value;
+template <typename P>
+inline constexpr bool is_partitioner_v = std::is_base_of<IsPartitioner, P>::value;
 
 }  // end of namespace tf -----------------------------------------------------
 
similarity index 93%
rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/pipeline.hpp
rename to bundled/taskflow-3.7.0/taskflow/algorithm/pipeline.hpp
index 5442d5600668d055089a7f2f0115ee5be25171f7..79689d08794eb1196c84c11da8a5c23330b3cebd 100644 (file)
@@ -1078,6 +1078,7 @@ class ScalablePipeline {
     std::atomic<size_t> join_counter;
   };
 
+
   public:
 
   /**
@@ -1282,6 +1283,7 @@ ScalablePipeline<P>::ScalablePipeline(size_t num_lines, P first, P last) :
   _build();
 }
 
+/*
 // move constructor
 template <typename P>
 ScalablePipeline<P>::ScalablePipeline(ScalablePipeline&& rhs) :
@@ -1298,23 +1300,99 @@ ScalablePipeline<P>::ScalablePipeline(ScalablePipeline&& rhs) :
 
   rhs._longest_deferral = 0;
   rhs._num_tokens       = 0;
+  std::cout << "scalable move constructor\n";
+}
+*/
+
+// move constructor
+template <typename P>
+ScalablePipeline<P>::ScalablePipeline(ScalablePipeline&& rhs):
+  _num_tokens           {rhs._num_tokens},
+  _pipes                {std::move(rhs._pipes)},
+  _pipeflows            {std::move(rhs._pipeflows)},
+  _lines                {std::move(rhs._lines)},
+  _ready_tokens         {std::move(rhs._ready_tokens)},
+  _token_dependencies   {std::move(rhs._token_dependencies)},
+  _deferred_tokens      {std::move(rhs._deferred_tokens)},
+  _longest_deferral     {rhs._longest_deferral}{
+
+
+  //_num_tokens = rhs._num_tokens;
+
+  //_pipes.resize(rhs.num_pipes());
+  //size_t i=0;
+  //for(auto itr = rhs._pipes.begin(); itr != rhs._pipes.end(); itr++) {
+  //  _pipes[i++] = *itr;
+  //}
+
+
+  //_pipeflows.resize(rhs.num_lines());
+  //for(size_t l = 0; l<rhs.num_lines(); l++) {
+  //  _pipeflows[l]._pipe = rhs._pipeflows[l]._pipe;
+  //  _pipeflows[l]._line = rhs._pipeflows[l]._line;
+  //  _pipeflows[l]._num_deferrals = 0;
+  //  _pipeflows[l]._dependents.clear();
+  //}
+
+  //_lines = std::make_unique<Line[]>(rhs.num_lines() * rhs._pipes.size());
+  //for(size_t l=0; l<num_lines(); l++) {
+  //  for(size_t f=0; f<num_pipes(); f++) {
+  //    _line(l, f).join_counter.store(
+  //      rhs._line(l, f).join_counter, std::memory_order_relaxed
+  //    );
+  //  }
+  //}
+  //_ready_tokens = std::move(rhs._ready_tokens);
+  //_token_dependencies = std::move(rhs._token_dependencies);
+  //_deferred_tokens = std::move(rhs._deferred_tokens);
+
+  _graph.clear();
+  _tasks.resize(_pipeflows.size()+1);
+  rhs._longest_deferral = 0;
+  rhs._num_tokens       = 0;
+  rhs._tasks.clear();
+  _build();
 }
 
+//// move assignment operator
+//template <typename P>
+//ScalablePipeline<P>& ScalablePipeline<P>::operator = (ScalablePipeline&& rhs) {
+//  _graph                = std::move(rhs._graph);
+//  _num_tokens           = rhs._num_tokens;
+//  _pipes                = std::move(rhs._pipes);
+//  _tasks                = std::move(rhs._tasks);
+//  _pipeflows            = std::move(rhs._pipeflows);
+//  _lines                = std::move(rhs._lines);
+//  rhs._num_tokens       = 0;
+//  _ready_tokens         = std::move(rhs._ready_tokens);
+//  _token_dependencies   = std::move(rhs._token_dependencies);
+//  _deferred_tokens      = std::move(rhs._deferred_tokens);
+//  _longest_deferral     = rhs._longest_deferral;
+//  rhs._longest_deferral = 0;
+//  std::cout << "scalable move assignment\n";
+//  return *this;
+//}
+
 // move assignment operator
 template <typename P>
 ScalablePipeline<P>& ScalablePipeline<P>::operator = (ScalablePipeline&& rhs) {
-  _graph                = std::move(rhs._graph);
-  _num_tokens           = rhs._num_tokens;
-  _pipes                = std::move(rhs._pipes);
-  _tasks                = std::move(rhs._tasks);
-  _pipeflows            = std::move(rhs._pipeflows);
-  _lines                = std::move(rhs._lines);
-  rhs._num_tokens       = 0;
-  _ready_tokens         = std::move(rhs._ready_tokens);
-  _token_dependencies   = std::move(rhs._token_dependencies);
-  _deferred_tokens      = std::move(rhs._deferred_tokens);
-  _longest_deferral     = rhs._longest_deferral;
+  _num_tokens         = rhs._num_tokens;
+  _pipes              = std::move(rhs._pipes);
+  _pipeflows          = std::move(rhs._pipeflows);
+  _lines              = std::move(rhs._lines);
+  _ready_tokens       = std::move(rhs._ready_tokens);
+  _token_dependencies = std::move(rhs._token_dependencies);
+  _deferred_tokens    = std::move(rhs._deferred_tokens);
+  _longest_deferral   = rhs._longest_deferral;
+
+  _graph.clear();
+  _tasks.resize(_pipeflows.size()+1);
+
   rhs._longest_deferral = 0;
+  rhs._num_tokens       = 0;
+  rhs._tasks.clear();
+  _build();
   return *this;
 }
 
@@ -1525,7 +1603,7 @@ void ScalablePipeline<P>::_build() {
   using namespace std::literals::string_literals;
 
   FlowBuilder fb(_graph);
-
+  
   // init task
   _tasks[0] = fb.emplace([this]() {
     return static_cast<int>(_num_tokens % num_lines());
@@ -1533,7 +1611,7 @@ void ScalablePipeline<P>::_build() {
 
   // line task
   for(size_t l = 0; l < num_lines(); l++) {
-
+    
     _tasks[l + 1] = fb.emplace([this, l] (tf::Runtime& rt) mutable {
 
       auto pf = &_pipeflows[l];
@@ -1598,11 +1676,11 @@ void ScalablePipeline<P>::_build() {
       else {
         _on_pipe(*pf, rt);
       }
-
+      
       size_t c_f = pf->_pipe;
       size_t n_f = (pf->_pipe + 1) % num_pipes();
       size_t n_l = (pf->_line + 1) % num_lines();
-
+      
       pf->_pipe = n_f;
 
       // ---- scheduling starts here ----
diff --git a/bundled/taskflow-3.7.0/taskflow/algorithm/reduce.hpp b/bundled/taskflow-3.7.0/taskflow/algorithm/reduce.hpp
new file mode 100644 (file)
index 0000000..b280934
--- /dev/null
@@ -0,0 +1,434 @@
+#pragma once
+
+#include "launch.hpp"
+
+namespace tf {
+
+// Function: make_reduce_task
+template <typename B, typename E, typename T, typename O, typename P = DefaultPartitioner>
+auto make_reduce_task(B b, E e, T& init, O bop, P part = P()) {
+
+  using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
+  using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
+
+  return [=, &r=init] (Runtime& rt) mutable {
+
+    // fetch the iterator values
+    B_t beg = b;
+    E_t end = e;
+
+    size_t W = rt.executor().num_workers();
+    size_t N = std::distance(beg, end);
+
+    // only myself - no need to spawn another graph
+    if(W <= 1 || N <= part.chunk_size()) {
+      launch_loop(part, [&](){
+        for(; beg!=end; r = bop(r, *beg++));
+      });
+      return;
+    }
+
+    if(N < W) {
+      W = N;
+    }
+
+    std::mutex mtx;
+
+    // static partitioner
+    if constexpr(part.type() == PartitionerType::STATIC) {
+      
+      size_t chunk_size;
+
+      for(size_t w=0, curr_b=0; w<W && curr_b < N; ++w, curr_b += chunk_size) {
+        
+        // we force chunk size to be at least two because the temporary
+        // variable sum need to avoid copy at the first step
+        chunk_size = std::max(size_t{2}, part.adjusted_chunk_size(N, W, w));
+        
+        launch_loop(W, w, rt, part, [=, &bop, &mtx, &r, &part] () mutable {
+
+          std::advance(beg, curr_b);
+
+          if(N - curr_b == 1) {
+            std::lock_guard<std::mutex> lock(mtx);
+            r = bop(r, *beg);
+            return;
+          }
+
+          auto beg1 = beg++;
+          auto beg2 = beg++;
+          T sum = bop(*beg1, *beg2);
+        
+          // loop reduce
+          part.loop(N, W, curr_b, chunk_size,
+            [&, prev_e=curr_b+2](size_t part_b, size_t part_e) mutable {
+
+              if(part_b > prev_e) {
+                std::advance(beg, part_b - prev_e);
+              }
+              else {
+                part_b = prev_e;
+              }
+
+              for(size_t x=part_b; x<part_e; x++, beg++) {
+                sum = bop(sum, *beg);
+              }
+              prev_e = part_e;
+            }
+          ); 
+          
+          // final reduce
+          std::lock_guard<std::mutex> lock(mtx);
+          r = bop(r, sum);
+        });
+      }
+      rt.corun_all();
+    }
+    // dynamic partitioner
+    else {
+      std::atomic<size_t> next(0);
+      launch_loop(N, W, rt, next, part, [=, &bop, &mtx, &next, &r, &part] () mutable {
+        // pre-reduce
+        size_t s0 = next.fetch_add(2, std::memory_order_relaxed);
+
+        if(s0 >= N) {
+          return;
+        }
+
+        std::advance(beg, s0);
+
+        if(N - s0 == 1) {
+          std::lock_guard<std::mutex> lock(mtx);
+          r = bop(r, *beg);
+          return;
+        }
+
+        auto beg1 = beg++;
+        auto beg2 = beg++;
+
+        T sum = bop(*beg1, *beg2);
+        
+        // loop reduce
+        part.loop(N, W, next, 
+          [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable {
+            std::advance(beg, curr_b - prev_e);
+            for(size_t x=curr_b; x<curr_e; x++, beg++) {
+              sum = bop(sum, *beg);
+            }
+            prev_e = curr_e;
+          }
+        ); 
+        
+        // final reduce
+        std::lock_guard<std::mutex> lock(mtx);
+        r = bop(r, sum);
+      });
+    }
+  };
+}
+
+// Function: make_transform_reduce_task
+template <
+  typename B, typename E, typename T, typename BOP, typename UOP, 
+  typename P = DefaultPartitioner
+>
+auto make_transform_reduce_task(B b, E e, T& init, BOP bop, UOP uop, P part = P()) {
+
+  using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
+  using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
+
+  return [=, &r=init] (Runtime& rt) mutable {
+
+    // fetch the iterator values
+    B_t beg = b;
+    E_t end = e;
+
+    size_t W = rt.executor().num_workers();
+    size_t N = std::distance(beg, end);
+
+    // only myself - no need to spawn another graph
+    if(W <= 1 || N <= part.chunk_size()) {
+      launch_loop(part, [&](){
+        for(; beg!=end; r = bop(std::move(r), uop(*beg++))); 
+      });
+      return;
+    }
+
+    if(N < W) {
+      W = N;
+    }
+
+    std::mutex mtx;
+    
+    // static partitioner
+    if constexpr(part.type() == PartitionerType::STATIC) {
+      
+      size_t chunk_size;
+
+      for(size_t w=0, curr_b=0; w<W && curr_b < N; ++w, curr_b += chunk_size) {
+      
+        chunk_size = part.adjusted_chunk_size(N, W, w);
+
+        launch_loop(W, w, rt, part, [=, &bop, &uop, &mtx, &r, &part] () mutable {
+          std::advance(beg, curr_b);
+
+          if(N - curr_b == 1) {
+            std::lock_guard<std::mutex> lock(mtx);
+            r = bop(std::move(r), uop(*beg));
+            return;
+          }
+
+          //auto beg1 = beg++;
+          //auto beg2 = beg++;
+          //T sum = bop(uop(*beg1), uop(*beg2));
+
+          T sum = (chunk_size == 1) ? uop(*beg++) : bop(uop(*beg++), uop(*beg++));
+        
+          // loop reduce
+          part.loop(N, W, curr_b, chunk_size,
+            [&, prev_e=curr_b+(chunk_size == 1 ? 1 : 2)]
+            (size_t part_b, size_t part_e) mutable {
+              if(part_b > prev_e) {
+                std::advance(beg, part_b - prev_e);
+              }
+              else {
+                part_b = prev_e;
+              }
+              for(size_t x=part_b; x<part_e; x++, beg++) {
+                sum = bop(std::move(sum), uop(*beg));
+              }
+              prev_e = part_e;
+            }
+          ); 
+          
+          // final reduce
+          std::lock_guard<std::mutex> lock(mtx);
+          r = bop(std::move(r), std::move(sum));
+        });
+      }
+      
+      rt.corun_all();
+    }
+    // dynamic partitioner
+    else {
+      std::atomic<size_t> next(0);
+        
+      launch_loop(N, W, rt, next, part, [=, &bop, &uop, &mtx, &next, &r, &part] () mutable {
+        // pre-reduce
+        size_t s0 = next.fetch_add(2, std::memory_order_relaxed);
+
+        if(s0 >= N) {
+          return;
+        }
+
+        std::advance(beg, s0);
+
+        if(N - s0 == 1) {
+          std::lock_guard<std::mutex> lock(mtx);
+          r = bop(std::move(r), uop(*beg));
+          return;
+        }
+
+        auto beg1 = beg++;
+        auto beg2 = beg++;
+
+        T sum = bop(uop(*beg1), uop(*beg2));
+        
+        // loop reduce
+        part.loop(N, W, next, 
+          [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable {
+            std::advance(beg, curr_b - prev_e);
+            for(size_t x=curr_b; x<curr_e; x++, beg++) {
+              sum = bop(std::move(sum), uop(*beg));
+            }
+            prev_e = curr_e;
+          }
+        ); 
+        
+        // final reduce
+        std::lock_guard<std::mutex> lock(mtx);
+        r = bop(std::move(r), std::move(sum));
+      });
+    }
+  };
+}
+
+// Function: make_transform_reduce_task with two binary operation
+template <
+  typename B1, typename E1, typename B2, typename T, typename BOP_R, typename BOP_T, 
+  typename P = DefaultPartitioner,
+  std::enable_if_t<!is_partitioner_v<std::decay_t<BOP_T>>, void>* = nullptr
+>
+auto make_transform_reduce_task(
+  B1 b1, E1 e1, B2 b2, T& init, BOP_R bop_r, BOP_T bop_t, P part = P()
+) {
+
+  using B1_t = std::decay_t<unwrap_ref_decay_t<B1>>;
+  using E1_t = std::decay_t<unwrap_ref_decay_t<E1>>;
+  using B2_t = std::decay_t<unwrap_ref_decay_t<B2>>;
+
+  return [=, &r=init] (Runtime& rt) mutable {
+
+    // fetch the iterator values
+    B1_t beg1 = b1;
+    E1_t end1 = e1;
+    B2_t beg2 = b2; 
+
+    size_t W = rt.executor().num_workers();
+    size_t N = std::distance(beg1, end1);
+
+    // only myself - no need to spawn another graph
+    if(W <= 1 || N <= part.chunk_size()) {
+      launch_loop(part, [&](){
+        for(; beg1!=end1; r = bop_r(std::move(r), bop_t(*beg1++, *beg2++)));
+      });
+      return;
+    }   
+
+    if(N < W) {
+      W = N;
+    }   
+
+    std::mutex mtx;
+    
+    // static partitioner
+    if constexpr(part.type() == PartitionerType::STATIC) {
+    
+      size_t chunk_size;
+
+      for(size_t w=0, curr_b=0; w<W && curr_b < N; ++w, curr_b += chunk_size) {
+    
+        chunk_size = part.adjusted_chunk_size(N, W, w); 
+
+        launch_loop(W, w, rt, part, [=, &bop_r, &bop_t, &mtx, &r, &part] () mutable {
+          std::advance(beg1, curr_b);
+          std::advance(beg2, curr_b);
+
+          if(N - curr_b == 1) {
+            std::lock_guard<std::mutex> lock(mtx);
+            r = bop_r(std::move(r), bop_t(*beg1, *beg2));
+            return;
+          }   
+
+          T sum = (chunk_size == 1) ? bop_t(*beg1++, *beg2++) : 
+            bop_r(bop_t(*beg1++, *beg2++), bop_t(*beg1++, *beg2++));
+    
+          // loop reduce
+          part.loop(N, W, curr_b, chunk_size,
+            [&, prev_e=curr_b+(chunk_size == 1 ? 1 : 2)] 
+            (size_t part_b, size_t part_e) mutable {
+              if(part_b > prev_e) {
+                std::advance(beg1, part_b - prev_e);
+                std::advance(beg2, part_b - prev_e);
+              }   
+              else {
+                part_b = prev_e;
+              }   
+              for(size_t x=part_b; x<part_e; x++, beg1++, beg2++) { 
+                sum = bop_r(std::move(sum), bop_t(*beg1, *beg2));
+              }   
+              prev_e = part_e;
+            }   
+          );  
+    
+          // final reduce
+          std::lock_guard<std::mutex> lock(mtx);
+          r = bop_r(std::move(r), std::move(sum));
+        }); 
+      }   
+    
+      rt.corun_all();
+    }   
+    // dynamic partitioner
+    else {
+      std::atomic<size_t> next(0);
+    
+      launch_loop(N, W, rt, next, part, [=, &bop_r, &bop_t, &mtx, &next, &r, &part] () mutable {
+        // pre-reduce
+        size_t s0 = next.fetch_add(2, std::memory_order_relaxed);
+
+        if(s0 >= N) {
+          return;
+        }   
+
+        std::advance(beg1, s0);
+        std::advance(beg2, s0);
+
+        if(N - s0 == 1) {
+          std::lock_guard<std::mutex> lock(mtx);
+          r = bop_r(std::move(r), bop_t(*beg1, *beg2));
+          return;
+        }   
+
+        auto beg11 = beg1++;
+        auto beg12 = beg1++;
+        auto beg21 = beg2++;
+        auto beg22 = beg2++;
+
+        T sum = bop_r(bop_t(*beg11, *beg21), bop_t(*beg12, *beg22));
+
+        // loop reduce
+        part.loop(N, W, next, 
+          [&, prev_e=s0+2](size_t curr_b, size_t curr_e) mutable {
+            std::advance(beg1, curr_b - prev_e);
+            std::advance(beg2, curr_b - prev_e);
+            for(size_t x=curr_b; x<curr_e; x++, beg1++, beg2++) {
+              sum = bop_r(std::move(sum), bop_t(*beg1, *beg2));
+            }   
+            prev_e = curr_e;
+          }   
+        );  
+    
+        // final reduce
+        std::lock_guard<std::mutex> lock(mtx);
+        r = bop_r(std::move(r), std::move(sum));
+      }); 
+    }   
+  };  
+}
+
+// ----------------------------------------------------------------------------
+// default reduction
+// ----------------------------------------------------------------------------
+
+// Function: reduce
+template <typename B, typename E, typename T, typename O, typename P>
+Task FlowBuilder::reduce(B beg, E end, T& init, O bop, P part) {
+  return emplace(make_reduce_task(beg, end, init, bop, part));
+}
+
+// ----------------------------------------------------------------------------
+// default transform and reduction
+// ----------------------------------------------------------------------------
+
+// Function: transform_reduce
+template <typename B, typename E, typename T, typename BOP, typename UOP, typename P,
+  std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>*
+>
+Task FlowBuilder::transform_reduce(
+  B beg, E end, T& init, BOP bop, UOP uop, P part
+) {
+  return emplace(make_transform_reduce_task(
+    beg, end, init, bop, uop, part
+  ));
+}
+
+// Function: transform_reduce
+template <
+  typename B1, typename E1, typename B2, typename T, typename BOP_R, typename BOP_T, 
+  typename P,
+  std::enable_if_t<!is_partitioner_v<std::decay_t<BOP_T>>, void>*
+>
+Task FlowBuilder::transform_reduce(
+  B1 beg1, E1 end1, B2 beg2, T& init, BOP_R bop_r, BOP_T bop_t, P part
+) {
+  return emplace(make_transform_reduce_task(
+    beg1, end1, beg2, init, bop_r, bop_t, part
+  ));
+}
+
+}  // end of namespace tf -----------------------------------------------------
+
+
+
+
similarity index 76%
rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/scan.hpp
rename to bundled/taskflow-3.7.0/taskflow/algorithm/scan.hpp
index cccb2057dd37b606e7a0aefa17c425fe5ec01f3d..c1682126b77157a492ffcae4a6179178bbe33712 100644 (file)
@@ -8,11 +8,11 @@ namespace detail {
 
 // Function: scan_loop
 template <typename Iterator, typename BufferT, typename B>
-TF_FORCE_INLINE void scan_loop(
+void scan_loop(
   tf::Runtime& rt,
   std::atomic<size_t>& counter, 
   BufferT& buf, 
-  B&& bop, 
+  B bop, 
   Iterator d_beg, 
   size_t W,
   size_t w, 
@@ -42,15 +42,21 @@ TF_FORCE_INLINE void scan_loop(
   }
 }
 
+}  // end of namespace tf::detail ---------------------------------------------
+
+
 // Function: make_inclusive_scan_task
-template <typename B, typename E, typename D, typename BOP>
-TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bop) {
+template <typename B, typename E, typename D, typename BOP, typename P = DefaultPartitioner,
+  std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>* = nullptr
+>
+auto make_inclusive_scan_task(
+  B first, E last, D d_first, BOP bop, P part = P()
+) {
   
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
   using D_t = std::decay_t<unwrap_ref_decay_t<D>>;
   using value_type = typename std::iterator_traits<B_t>::value_type;
-  using namespace std::string_literals;
   
   return [=] (Runtime& rt) mutable {
 
@@ -68,7 +74,9 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= 2) {
-      std::inclusive_scan(s_beg, s_end, d_beg, bop);
+      launch_loop(part, [&](){
+        std::inclusive_scan(s_beg, s_end, d_beg, bop);
+      });
       return;
     }
 
@@ -90,8 +98,7 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo
       chunk_size = std::min(Q + (w < R), N - curr_b);
 
       // block scan
-      launch_loop(W, w, rt, [=, &rt, &bop, &buf, &counter] () mutable {
-
+      launch_loop(W, w, rt, part, [=, &rt, &bop, &buf, &counter] () mutable {
         auto result = d_beg;
 
         // local scan per worker
@@ -134,19 +141,22 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo
       curr_b += chunk_size;
     }
 
-    rt.join();
+    rt.corun_all();
   };
 }
 
 // Function: make_inclusive_scan_task
-template <typename B, typename E, typename D, typename BOP, typename T>
-TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bop, T init) {
+template <typename B, typename E, typename D, typename BOP, typename T, typename P = DefaultPartitioner,
+  std::enable_if_t<!is_partitioner_v<std::decay_t<T>>, void>* = nullptr
+>
+auto make_inclusive_scan_task(
+  B first, E last, D d_first, BOP bop, T init, P part = P()
+) {
   
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
   using D_t = std::decay_t<unwrap_ref_decay_t<D>>;
   using value_type = typename std::iterator_traits<B_t>::value_type;
-  using namespace std::string_literals;
   
   return [=] (Runtime& rt) mutable {
 
@@ -164,7 +174,9 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= 2) {
-      std::inclusive_scan(s_beg, s_end, d_beg, bop, init);
+      launch_loop(part, [&](){
+        std::inclusive_scan(s_beg, s_end, d_beg, bop, init);
+      });
       return;
     }
 
@@ -186,16 +198,15 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo
       chunk_size = std::min(Q + (w < R), N - curr_b);
 
       // block scan
-      launch_loop(W, w, rt, [=, &rt, &bop, &buf, &counter] () mutable {
-
+      launch_loop(W, w, rt, part, [=, &rt, &bop, &buf, &counter] () mutable {
         auto result = d_beg;
 
         // local scan per worker
-        auto& init = buf[w].data;
-        *d_beg++ = init = (w == 0) ? bop(init, *s_beg++) : *s_beg++;
+        auto& local = buf[w].data;
+        *d_beg++ = local = (w == 0) ? bop(local, *s_beg++) : *s_beg++;
 
         for(size_t i=1; i<chunk_size; i++){
-          *d_beg++ = init = bop(init, *s_beg++); 
+          *d_beg++ = local = bop(local, *s_beg++); 
         }
         
         // block scan
@@ -207,7 +218,7 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo
       curr_b += chunk_size;
     }
 
-    rt.join();
+    rt.corun_all();
   };
 }
 
@@ -216,16 +227,17 @@ TF_FORCE_INLINE auto make_inclusive_scan_task(B first, E last, D d_first, BOP bo
 // ----------------------------------------------------------------------------
 
 // Function: transform_inclusive_scan
-template <typename B, typename E, typename D, typename BOP, typename UOP>
-TF_FORCE_INLINE auto make_transform_inclusive_scan_task(
-  B first, E last, D d_first, BOP bop, UOP uop
+template <typename B, typename E, typename D, typename BOP, typename UOP, typename P = DefaultPartitioner,
+  std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>* = nullptr
+>
+auto make_transform_inclusive_scan_task(
+  B first, E last, D d_first, BOP bop, UOP uop, P part = P()
 ) {
   
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
   using D_t = std::decay_t<unwrap_ref_decay_t<D>>;
   using value_type = typename std::iterator_traits<B_t>::value_type;
-  using namespace std::string_literals;
   
   return [=] (Runtime& rt) mutable {
 
@@ -243,7 +255,9 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task(
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= 2) {
-      std::transform_inclusive_scan(s_beg, s_end, d_beg, bop, uop);
+      launch_loop(part, [&](){
+        std::transform_inclusive_scan(s_beg, s_end, d_beg, bop, uop); 
+      });
       return;
     }
 
@@ -262,8 +276,7 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task(
       chunk_size = std::min(Q + (w < R), N - curr_b);
 
       // block scan
-      launch_loop(W, w, rt, [=, &rt, &bop, &uop, &buf, &counter] () mutable {
-
+      launch_loop(W, w, rt, part, [=, &rt, &bop, &uop, &buf, &counter] () mutable {
         auto result = d_beg;
 
         // local scan per worker
@@ -283,21 +296,22 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task(
       curr_b += chunk_size;
     }
 
-    rt.join();
+    rt.corun_all();
   };
 }
 
 // Function: transform_inclusive_scan
-template <typename B, typename E, typename D, typename BOP, typename UOP, typename T>
-TF_FORCE_INLINE auto make_transform_inclusive_scan_task(
-  B first, E last, D d_first, BOP bop, UOP uop, T init
+template <typename B, typename E, typename D, typename BOP, typename UOP, typename T, typename P = DefaultPartitioner,
+  std::enable_if_t<!is_partitioner_v<std::decay_t<T>>, void>* = nullptr
+>
+auto make_transform_inclusive_scan_task(
+  B first, E last, D d_first, BOP bop, UOP uop, T init, P part = P()
 ) {
   
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
   using D_t = std::decay_t<unwrap_ref_decay_t<D>>;
   using value_type = typename std::iterator_traits<B_t>::value_type;
-  using namespace std::string_literals;
   
   return [=] (Runtime& rt) mutable {
 
@@ -315,7 +329,9 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task(
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= 2) {
-      std::transform_inclusive_scan(s_beg, s_end, d_beg, bop, uop, init);
+      launch_loop(part, [&](){
+        std::transform_inclusive_scan(s_beg, s_end, d_beg, bop, uop, init);
+      });
       return;
     }
 
@@ -337,16 +353,15 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task(
       chunk_size = std::min(Q + (w < R), N - curr_b);
 
       // block scan
-      launch_loop(W, w, rt, [=, &rt, &bop, &uop, &buf, &counter] () mutable {
-
+      launch_loop(W, w, rt, part, [=, &rt, &bop, &uop, &buf, &counter] () mutable {
         auto result = d_beg;
 
         // local scan per worker
-        auto& init = buf[w].data;
-        *d_beg++ = init = (w == 0) ? bop(init, uop(*s_beg++)) : uop(*s_beg++);
+        auto& local = buf[w].data;
+        *d_beg++ = local = (w == 0) ? bop(local, uop(*s_beg++)) : uop(*s_beg++);
 
         for(size_t i=1; i<chunk_size; i++){
-          *d_beg++ = init = bop(init, uop(*s_beg++)); 
+          *d_beg++ = local = bop(local, uop(*s_beg++)); 
         }
         
         // block scan
@@ -358,7 +373,7 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task(
       curr_b += chunk_size;
     }
 
-    rt.join();
+    rt.corun_all();
     
   };
 }
@@ -368,16 +383,15 @@ TF_FORCE_INLINE auto make_transform_inclusive_scan_task(
 // ----------------------------------------------------------------------------
 
 // Function: make_exclusive_scan_task
-template <typename B, typename E, typename D, typename T, typename BOP>
-TF_FORCE_INLINE auto make_exclusive_scan_task(
-  B first, E last, D d_first, T init, BOP bop
+template <typename B, typename E, typename D, typename T, typename BOP, typename P = DefaultPartitioner>
+auto make_exclusive_scan_task(
+  B first, E last, D d_first, T init, BOP bop, P part = P()
 ) {
   
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
   using D_t = std::decay_t<unwrap_ref_decay_t<D>>;
   using value_type = typename std::iterator_traits<B_t>::value_type;
-  using namespace std::string_literals;
   
   return [=] (Runtime& rt) mutable {
 
@@ -395,7 +409,9 @@ TF_FORCE_INLINE auto make_exclusive_scan_task(
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= 2) {
-      std::exclusive_scan(s_beg, s_end, d_beg, init, bop);
+      launch_loop(part, [&](){
+        std::exclusive_scan(s_beg, s_end, d_beg, init, bop);
+      });
       return;
     }
 
@@ -423,19 +439,18 @@ TF_FORCE_INLINE auto make_exclusive_scan_task(
       chunk_size = std::min(Q + (w < R), N - curr_b);
 
       // block scan
-      launch_loop(W, w, rt, [=, &rt, &bop, &buf, &counter] () mutable {
-
+      launch_loop(W, w, rt, part, [=, &rt, &bop, &buf, &counter] () mutable {
         auto result = d_beg;
 
         // local scan per worker
-        auto& init = buf[w].data;
+        auto& local = buf[w].data;
 
         for(size_t i=1; i<chunk_size; i++) {
-          auto v = init;
-          init = bop(init, *s_beg++);
+          auto v = local;
+          local = bop(local, *s_beg++);
           *d_beg++ = std::move(v);
         }
-        *d_beg++ = init;
+        *d_beg++ = local;
         
         // block scan
         detail::scan_loop(rt, counter, buf, bop, result, W, w, chunk_size);
@@ -446,7 +461,7 @@ TF_FORCE_INLINE auto make_exclusive_scan_task(
       curr_b += chunk_size;
     }
 
-    rt.join();
+    rt.corun_all();
     
   };
 }
@@ -456,16 +471,15 @@ TF_FORCE_INLINE auto make_exclusive_scan_task(
 // ----------------------------------------------------------------------------
 
 // Function: 
-template <typename B, typename E, typename D, typename T, typename BOP, typename UOP>
-TF_FORCE_INLINE auto make_transform_exclusive_scan_task(
-  B first, E last, D d_first, T init, BOP bop, UOP uop
+template <typename B, typename E, typename D, typename T, typename BOP, typename UOP, typename P = DefaultPartitioner>
+auto make_transform_exclusive_scan_task(
+  B first, E last, D d_first, T init, BOP bop, UOP uop, P part = P()
 ) {
   
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
   using D_t = std::decay_t<unwrap_ref_decay_t<D>>;
   using value_type = typename std::iterator_traits<B_t>::value_type;
-  using namespace std::string_literals;
   
   return [=] (Runtime& rt) mutable {
 
@@ -483,7 +497,9 @@ TF_FORCE_INLINE auto make_transform_exclusive_scan_task(
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= 2) {
-      std::transform_exclusive_scan(s_beg, s_end, d_beg, init, bop, uop);
+      launch_loop(part, [&](){
+        std::transform_exclusive_scan(s_beg, s_end, d_beg, init, bop, uop);
+      });
       return;
     }
 
@@ -511,19 +527,18 @@ TF_FORCE_INLINE auto make_transform_exclusive_scan_task(
       chunk_size = std::min(Q + (w < R), N - curr_b);
 
       // block scan
-      launch_loop(W, w, rt, [=, &rt, &bop, &uop, &buf, &counter] () mutable {
-
+      launch_loop(W, w, rt, part, [=, &rt, &bop, &uop, &buf, &counter] () mutable {
         auto result = d_beg;
 
         // local scan per worker
-        auto& init = buf[w].data;
+        auto& local = buf[w].data;
 
         for(size_t i=1; i<chunk_size; i++) {
-          auto v = init;
-          init = bop(init, uop(*s_beg++));
+          auto v = local;
+          local = bop(local, uop(*s_beg++));
           *d_beg++ = std::move(v);
         }
-        *d_beg++ = init;
+        *d_beg++ = local;
         
         // block scan
         detail::scan_loop(rt, counter, buf, bop, result, W, w, chunk_size);
@@ -534,31 +549,30 @@ TF_FORCE_INLINE auto make_transform_exclusive_scan_task(
       curr_b += chunk_size;
     }
 
-    rt.join();
+    rt.corun_all();
     
   };
 }
 
-}  // end of namespace tf::detail ---------------------------------------------
 
 // ----------------------------------------------------------------------------
 // Inclusive Scan
 // ----------------------------------------------------------------------------
 
 // Function: inclusive_scan
-template <typename B, typename E, typename D, typename BOP>
-Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop) {
-  return emplace(detail::make_inclusive_scan_task(
-    first, last, d_first, bop
-  ));
+template <typename B, typename E, typename D, typename BOP, typename P,
+  std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>*
+>
+Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop, P part) {
+  return emplace(make_inclusive_scan_task(first, last, d_first, bop, part));
 }
 
 // Function: inclusive_scan
-template <typename B, typename E, typename D, typename BOP, typename T>
-Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop, T init) {
-  return emplace(detail::make_inclusive_scan_task(
-    first, last, d_first, bop, init
-  ));
+template <typename B, typename E, typename D, typename BOP, typename T, typename P,
+  std::enable_if_t<!is_partitioner_v<std::decay_t<T>>, void>*
+>
+Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop, T init, P part) {
+  return emplace(make_inclusive_scan_task(first, last, d_first, bop, init, part));
 }
 
 // ----------------------------------------------------------------------------
@@ -566,22 +580,26 @@ Task FlowBuilder::inclusive_scan(B first, E last, D d_first, BOP bop, T init) {
 // ----------------------------------------------------------------------------
 
 // Function: transform_inclusive_scan
-template <typename B, typename E, typename D, typename BOP, typename UOP>
+template <typename B, typename E, typename D, typename BOP, typename UOP, typename P,
+  std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>*
+>
 Task FlowBuilder::transform_inclusive_scan(
-  B first, E last, D d_first, BOP bop, UOP uop
+  B first, E last, D d_first, BOP bop, UOP uop, P part
 ) {
-  return emplace(detail::make_transform_inclusive_scan_task(
-    first, last, d_first, bop, uop
+  return emplace(make_transform_inclusive_scan_task(
+    first, last, d_first, bop, uop, part
   ));
 }
 
 // Function: transform_inclusive_scan
-template <typename B, typename E, typename D, typename BOP, typename UOP, typename T>
+template <typename B, typename E, typename D, typename BOP, typename UOP, typename T, typename P,
+  std::enable_if_t<!is_partitioner_v<std::decay_t<T>>, void>*
+>
 Task FlowBuilder::transform_inclusive_scan(
-  B first, E last, D d_first, BOP bop, UOP uop, T init
+  B first, E last, D d_first, BOP bop, UOP uop, T init, P part
 ) {
-  return emplace(detail::make_transform_inclusive_scan_task(
-    first, last, d_first, bop, uop, init
+  return emplace(make_transform_inclusive_scan_task(
+    first, last, d_first, bop, uop, init, part
   ));  
 }
 
@@ -590,10 +608,10 @@ Task FlowBuilder::transform_inclusive_scan(
 // ----------------------------------------------------------------------------
 
 // Function: exclusive_scan
-template <typename B, typename E, typename D, typename T, typename BOP>
-Task FlowBuilder::exclusive_scan(B first, E last, D d_first, T init, BOP bop) {
-  return emplace(detail::make_exclusive_scan_task(
-    first, last, d_first, init, bop
+template <typename B, typename E, typename D, typename T, typename BOP, typename P>
+Task FlowBuilder::exclusive_scan(B first, E last, D d_first, T init, BOP bop, P part) {
+  return emplace(make_exclusive_scan_task(
+    first, last, d_first, init, bop, part
   ));
 }
 
@@ -602,13 +620,14 @@ Task FlowBuilder::exclusive_scan(B first, E last, D d_first, T init, BOP bop) {
 // ----------------------------------------------------------------------------
 
 // Function: transform_exclusive_scan
-template <typename B, typename E, typename D, typename T, typename BOP, typename UOP>
+template <typename B, typename E, typename D, typename T, typename BOP, typename UOP, typename P>
 Task FlowBuilder::transform_exclusive_scan(
-  B first, E last, D d_first, T init, BOP bop, UOP uop
+  B first, E last, D d_first, T init, BOP bop, UOP uop, P part
 ) {
-  return emplace(detail::make_transform_exclusive_scan_task(
-    first, last, d_first, init, bop, uop
+  return emplace(make_transform_exclusive_scan_task(
+    first, last, d_first, init, bop, uop, part
   )); 
 }
 
 }  // end of namespace tf -----------------------------------------------------
+
similarity index 96%
rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/sort.hpp
rename to bundled/taskflow-3.7.0/taskflow/algorithm/sort.hpp
index a4fdf3ce76dabab1f48590a31c64f69f3facc725..4460f8f4a3613a169cbace86635d228de40c0543 100644 (file)
@@ -2,7 +2,7 @@
 
 #include "../core/async.hpp"
 
-namespace tf {
+namespace tf::detail {
 
 // threshold whether or not to perform parallel sort
 template <typename I>
@@ -595,15 +595,15 @@ void parallel_3wqsort(tf::Runtime& rt, RandItr first, RandItr last, C compare) {
   //rt.join();
 }
 
-// ----------------------------------------------------------------------------
-// tf::Taskflow::sort
-// ----------------------------------------------------------------------------
+}  // end of namespace tf::detail ---------------------------------------------
 
-// Function: sort
-template <typename B, typename E, typename C>
-Task FlowBuilder::sort(B beg, E end, C cmp) {
+namespace tf { 
 
-  Task task = emplace([b=beg, e=end, cmp] (Runtime& rt) mutable {
+// Function: make_sort_task
+template <typename B, typename E, typename C>
+TF_FORCE_INLINE auto make_sort_task(B b, E e, C cmp) {
+  
+  return [b, e, cmp] (Runtime& rt) mutable {
 
     using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
     using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
@@ -616,32 +616,45 @@ Task FlowBuilder::sort(B beg, E end, C cmp) {
       return;
     }
 
-    size_t W = rt._executor.num_workers();
+    size_t W = rt.executor().num_workers();
     size_t N = std::distance(beg, end);
 
     // only myself - no need to spawn another graph
-    if(W <= 1 || N <= parallel_sort_cutoff<B_t>()) {
+    if(W <= 1 || N <= detail::parallel_sort_cutoff<B_t>()) {
       std::sort(beg, end, cmp);
       return;
     }
 
     //parallel_3wqsort(rt, beg, end-1, cmp);
-    parallel_pdqsort<B_t, C,
+    detail::parallel_pdqsort<B_t, C,
       is_std_compare_v<std::decay_t<C>> &&
       std::is_arithmetic_v<typename std::iterator_traits<B_t>::value_type>
     >(rt, beg, end, cmp, log2(end - beg));
 
-    rt.join();
-  });
+    rt.corun_all();
+  };
+}
+  
+template <typename B, typename E>
+TF_FORCE_INLINE auto make_sort_task(B beg, E end) {
+  using value_type = std::decay_t<decltype(*std::declval<B>())>;
+  return make_sort_task(beg, end, std::less<value_type>{});
+}
+
+// ----------------------------------------------------------------------------
+// tf::Taskflow::sort
+// ----------------------------------------------------------------------------
 
-  return task;
+// Function: sort
+template <typename B, typename E, typename C>
+Task FlowBuilder::sort(B beg, E end, C cmp) {
+  return emplace(make_sort_task(beg, end, cmp));
 }
 
 // Function: sort
 template <typename B, typename E>
 Task FlowBuilder::sort(B beg, E end) {
-  using value_type = std::decay_t<decltype(*std::declval<B>())>;
-  return sort(beg, end, std::less<value_type>{});
+  return emplace(make_sort_task(beg, end));
 }
 
 }  // namespace tf ------------------------------------------------------------
similarity index 58%
rename from bundled/taskflow-3.6.0/include/taskflow/algorithm/transform.hpp
rename to bundled/taskflow-3.7.0/taskflow/algorithm/transform.hpp
index 4c8788770706fc3dabab37779984903c509cc2a8..b155f658bcdbf0e9f496e1b07c7d184c8e7b6841 100644 (file)
@@ -4,23 +4,18 @@
 
 namespace tf {
 
-namespace detail {
-
 // Function: make_transform_task
-template <typename B, typename E, typename O, typename C, typename P>
-TF_FORCE_INLINE auto make_transform_task(
-  B first1, E last1, O d_first, C c, P&& part
-) {
-
-  using namespace std::string_literals;
+template <
+  typename B, typename E, typename O, typename C, typename P = DefaultPartitioner,
+  std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>* = nullptr
+>
+auto make_transform_task(B first1, E last1, O d_first, C c, P part = P()) {
 
   using B_t = std::decay_t<unwrap_ref_decay_t<B>>;
   using E_t = std::decay_t<unwrap_ref_decay_t<E>>;
   using O_t = std::decay_t<unwrap_ref_decay_t<O>>;
   
-  return
-  [first1, last1, d_first, c, part=std::forward<P>(part)] 
-  (Runtime& rt) mutable {
+  return [=] (Runtime& rt) mutable {
 
     // fetch the stateful values
     B_t beg   = first1;
@@ -32,7 +27,9 @@ TF_FORCE_INLINE auto make_transform_task(
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= part.chunk_size()) {
-      std::transform(beg, end, d_beg, c);
+      launch_loop(part, [&](){
+        std::transform(beg, end, d_beg, c);
+      });
       return;
     }
 
@@ -41,38 +38,37 @@ TF_FORCE_INLINE auto make_transform_task(
     }
 
     // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
+    if constexpr(part.type() == PartitionerType::STATIC) {
       size_t chunk_size;
       for(size_t w=0, curr_b=0; w<W && curr_b < N; ++w, curr_b += chunk_size) {
         chunk_size = part.adjusted_chunk_size(N, W, w);
-        launch_loop(W, w, rt, [=, &part] () mutable {
+        launch_loop(W, w, rt, part, [=, &part] () mutable {
           part.loop(N, W, curr_b, chunk_size,
-            [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable {
-              std::advance(beg, curr_b - prev_e);
-              std::advance(d_beg, curr_b - prev_e);
-              for(size_t x = curr_b; x<curr_e; x++) {
+            [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable {
+              std::advance(beg, part_b - prev_e);
+              std::advance(d_beg, part_b - prev_e);
+              for(size_t x = part_b; x<part_e; x++) {
                 *d_beg++ = c(*beg++);
               }
-              prev_e = curr_e;
+              prev_e = part_e;
             }
-          ); 
+          );
         });
       }
-      rt.join();
+      rt.corun_all();
     }
     // dynamic partitioner
     else {
       std::atomic<size_t> next(0);
-      
       launch_loop(N, W, rt, next, part, [=, &next, &part] () mutable {
         part.loop(N, W, next, 
-          [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable {
-            std::advance(beg, curr_b - prev_e);
-            std::advance(d_beg, curr_b - prev_e);
-            for(size_t x = curr_b; x<curr_e; x++) {
+          [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable {
+            std::advance(beg, part_b - prev_e);
+            std::advance(d_beg, part_b - prev_e);
+            for(size_t x = part_b; x<part_e; x++) {
               *d_beg++ = c(*beg++);
             }
-            prev_e = curr_e;
+            prev_e = part_e;
           }
         ); 
       });
@@ -82,23 +78,17 @@ TF_FORCE_INLINE auto make_transform_task(
 
 // Function: make_transform_task
 template <
-  typename B1, typename E1, typename B2, typename O, typename C, typename P,
+  typename B1, typename E1, typename B2, typename O, typename C, typename P = DefaultPartitioner,
   std::enable_if_t<!is_partitioner_v<std::decay_t<C>>, void>* = nullptr
 >
-TF_FORCE_INLINE auto make_transform_task(
-  B1 first1, E1 last1, B2 first2, O d_first, C c, P&& part
-) {
-
-  using namespace std::string_literals;
+auto make_transform_task(B1 first1, E1 last1, B2 first2, O d_first, C c, P part = P()) {
 
   using B1_t = std::decay_t<unwrap_ref_decay_t<B1>>;
   using E1_t = std::decay_t<unwrap_ref_decay_t<E1>>;
   using B2_t = std::decay_t<unwrap_ref_decay_t<B2>>;
   using O_t = std::decay_t<unwrap_ref_decay_t<O>>;
 
-  return
-  [first1, last1, first2, d_first, c, part=std::forward<P>(part)] 
-  (Runtime& rt) mutable {
+  return [=] (Runtime& rt) mutable {
 
     // fetch the stateful values
     B1_t beg1 = first1;
@@ -111,7 +101,9 @@ TF_FORCE_INLINE auto make_transform_task(
 
     // only myself - no need to spawn another graph
     if(W <= 1 || N <= part.chunk_size()) {
-      std::transform(beg1, end1, beg2, d_beg, c);
+      launch_loop(part, [&](){
+        std::transform(beg1, end1, beg2, d_beg, c);
+      });
       return;
     }
 
@@ -120,57 +112,57 @@ TF_FORCE_INLINE auto make_transform_task(
     }
 
     // static partitioner
-    if constexpr(std::is_same_v<std::decay_t<P>, StaticPartitioner>) {
+    if constexpr(part.type() == PartitionerType::STATIC) {
       size_t chunk_size;
       for(size_t w=0, curr_b=0; w<W && curr_b < N; ++w, curr_b += chunk_size) {
         chunk_size = part.adjusted_chunk_size(N, W, w);
-        launch_loop(W, w, rt, [=, &c, &part] () mutable {
+        launch_loop(W, w, rt, part, [=, &c, &part] () mutable {
           part.loop(N, W, curr_b, chunk_size,
-            [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable {
-              std::advance(beg1, curr_b - prev_e);
-              std::advance(beg2, curr_b - prev_e);
-              std::advance(d_beg, curr_b - prev_e);
-              for(size_t x = curr_b; x<curr_e; x++) {
+            [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable {
+              std::advance(beg1, part_b - prev_e);
+              std::advance(beg2, part_b - prev_e);
+              std::advance(d_beg, part_b - prev_e);
+              for(size_t x = part_b; x<part_e; x++) {
                 *d_beg++ = c(*beg1++, *beg2++);
               }
-              prev_e = curr_e;
+              prev_e = part_e;
             }
-          ); 
+          );
         });
       }
-      rt.join();
+      rt.corun_all();
     }
     // dynamic partitioner
     else {
       std::atomic<size_t> next(0);
       launch_loop(N, W, rt, next, part, [=, &c, &next, &part] () mutable {
         part.loop(N, W, next, 
-          [&, prev_e=size_t{0}](size_t curr_b, size_t curr_e) mutable {
-            std::advance(beg1, curr_b - prev_e);
-            std::advance(beg2, curr_b - prev_e);
-            std::advance(d_beg, curr_b - prev_e);
-            for(size_t x = curr_b; x<curr_e; x++) {
+          [&, prev_e=size_t{0}](size_t part_b, size_t part_e) mutable {
+            std::advance(beg1, part_b - prev_e);
+            std::advance(beg2, part_b - prev_e);
+            std::advance(d_beg, part_b - prev_e);
+            for(size_t x = part_b; x<part_e; x++) {
               *d_beg++ = c(*beg1++, *beg2++);
             }
-            prev_e = curr_e;
+            prev_e = part_e;
           }
-        ); 
+        );
       });
     }
   };
 }
 
-}  // end of namespace detail -------------------------------------------------
-
 // ----------------------------------------------------------------------------
 // transform
 // ----------------------------------------------------------------------------
 
 // Function: transform
-template <typename B, typename E, typename O, typename C, typename P>
-Task FlowBuilder::transform(B first1, E last1, O d_first, C c, P&& part) {
+template <typename B, typename E, typename O, typename C, typename P,
+  std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>*
+>
+Task FlowBuilder::transform(B first1, E last1, O d_first, C c, P part) {
   return emplace(
-    detail::make_transform_task(first1, last1, d_first, c, std::forward<P>(part))
+    make_transform_task(first1, last1, d_first, c, part)
   );
 }
 
@@ -184,11 +176,10 @@ template <
   std::enable_if_t<!is_partitioner_v<std::decay_t<C>>, void>*
 >
 Task FlowBuilder::transform(
-  B1 first1, E1 last1, B2 first2, O d_first, C c, P&& part
+  B1 first1, E1 last1, B2 first2, O d_first, C c, P part
 ) {
-
-  return emplace(detail::make_transform_task(
-    first1, last1, first2, d_first, c, std::forward<P>(part)
+  return emplace(make_transform_task(
+    first1, last1, first2, d_first, c, part
   ));
 }
 
diff --git a/bundled/taskflow-3.7.0/taskflow/core/async.hpp b/bundled/taskflow-3.7.0/taskflow/core/async.hpp
new file mode 100644 (file)
index 0000000..ed671f9
--- /dev/null
@@ -0,0 +1,331 @@
+#pragma once
+
+#include "executor.hpp"
+
+// https://hackmd.io/@sysprog/concurrency-atomics
+
+namespace tf {
+
+// ----------------------------------------------------------------------------
+// Async
+// ----------------------------------------------------------------------------
+
+// Function: async
+template <typename P, typename F>
+auto Executor::async(P&& params, F&& f) {
+
+  _increment_topology();
+
+  using R = std::invoke_result_t<std::decay_t<F>>;
+
+  std::packaged_task<R()> p(std::forward<F>(f));
+  auto fu{p.get_future()};
+
+  auto node = node_pool.animate(
+    std::forward<P>(params), nullptr, nullptr, 0, 
+    // handle
+    std::in_place_type_t<Node::Async>{}, 
+    [p=make_moc(std::move(p))]() mutable { p.object(); }
+  );
+
+  _schedule_async_task(node);
+
+  return fu;
+}
+
+// Function: async
+template <typename F>
+auto Executor::async(F&& f) {
+  return async(DefaultTaskParams{}, std::forward<F>(f));
+}
+
+// ----------------------------------------------------------------------------
+// Silent Async
+// ----------------------------------------------------------------------------
+
+// Function: silent_async
+template <typename P, typename F>
+void Executor::silent_async(P&& params, F&& f) {
+
+  _increment_topology();
+  
+  auto node = node_pool.animate(
+    std::forward<P>(params), nullptr, nullptr, 0, 
+    // handle
+    std::in_place_type_t<Node::Async>{}, std::forward<F>(f)
+  );
+
+  _schedule_async_task(node);
+}
+
+// Function: silent_async
+template <typename F>
+void Executor::silent_async(F&& f) {
+  silent_async(DefaultTaskParams{}, std::forward<F>(f));
+}
+
+// ----------------------------------------------------------------------------
+// Async Helper Methods
+// ----------------------------------------------------------------------------
+
+// Procedure: _schedule_async_task
+inline void Executor::_schedule_async_task(Node* node) {  
+  if(auto w = _this_worker(); w) {
+    _schedule(*w, node);
+  }
+  else{
+    _schedule(node);
+  }
+}
+
+// Procedure: _tear_down_async
+inline void Executor::_tear_down_async(Node* node) {
+  // from runtime
+  if(node->_parent) {
+    node->_parent->_join_counter.fetch_sub(1, std::memory_order_release);
+  }
+  // from executor
+  else {
+    _decrement_topology();
+  }
+  node_pool.recycle(node);
+}
+
+// ----------------------------------------------------------------------------
+// Silent Dependent Async
+// ----------------------------------------------------------------------------
+
+// Function: silent_dependent_async
+template <typename F, typename... Tasks,
+  std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>*
+>
+tf::AsyncTask Executor::silent_dependent_async(F&& func, Tasks&&... tasks) {
+  return silent_dependent_async(
+    DefaultTaskParams{}, std::forward<F>(func), std::forward<Tasks>(tasks)...
+  );
+}
+
+// Function: silent_dependent_async
+template <typename P, typename F, typename... Tasks,
+  std::enable_if_t<is_task_params_v<P> && all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>*
+>
+tf::AsyncTask Executor::silent_dependent_async(
+  P&& params, F&& func, Tasks&&... tasks 
+){
+
+  _increment_topology();
+
+  size_t num_dependents = sizeof...(Tasks);
+  
+  // create a task before scheduling the node to retain a shared ownership first
+  AsyncTask task(node_pool.animate(
+    std::forward<P>(params), nullptr, nullptr, num_dependents,
+    std::in_place_type_t<Node::DependentAsync>{}, std::forward<F>(func)
+  ));
+  
+  if constexpr(sizeof...(Tasks) > 0) {
+    (_process_async_dependent(task._node, tasks, num_dependents), ...);
+  }
+
+  if(num_dependents == 0) {
+    _schedule_async_task(task._node);
+  }
+
+  return task;
+}
+
+// Function: silent_dependent_async
+template <typename F, typename I,
+  std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>*
+>
+tf::AsyncTask Executor::silent_dependent_async(F&& func, I first, I last) {
+  return silent_dependent_async(DefaultTaskParams{}, std::forward<F>(func), first, last);
+}
+
+// Function: silent_dependent_async
+template <typename P, typename F, typename I,
+  std::enable_if_t<is_task_params_v<P> && !std::is_same_v<std::decay_t<I>, AsyncTask>, void>*
+>
+tf::AsyncTask Executor::silent_dependent_async(
+  P&& params, F&& func, I first, I last
+) {
+
+  _increment_topology();
+
+  size_t num_dependents = std::distance(first, last);
+  
+  AsyncTask task(node_pool.animate(
+    std::forward<P>(params), nullptr, nullptr, num_dependents,
+    std::in_place_type_t<Node::DependentAsync>{}, std::forward<F>(func)
+  ));
+  
+  for(; first != last; first++){
+    _process_async_dependent(task._node, *first, num_dependents);
+  }
+
+  if(num_dependents == 0) {
+    _schedule_async_task(task._node);
+  }
+
+  return task;
+}
+
+// ----------------------------------------------------------------------------
+// Dependent Async
+// ----------------------------------------------------------------------------
+
+// Function: dependent_async
+template <typename F, typename... Tasks,
+  std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>*
+>
+auto Executor::dependent_async(F&& func, Tasks&&... tasks) {
+  return dependent_async(DefaultTaskParams{}, std::forward<F>(func), std::forward<Tasks>(tasks)...);
+}
+
+// Function: dependent_async
+template <typename P, typename F, typename... Tasks,
+  std::enable_if_t<is_task_params_v<P> && all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>*
+>
+auto Executor::dependent_async(P&& params, F&& func, Tasks&&... tasks) {
+  
+  _increment_topology();
+  
+  using R = std::invoke_result_t<std::decay_t<F>>;
+
+  std::packaged_task<R()> p(std::forward<F>(func));
+  auto fu{p.get_future()};
+
+  size_t num_dependents = sizeof...(tasks);
+
+  AsyncTask task(node_pool.animate(
+    std::forward<P>(params), nullptr, nullptr, num_dependents,
+    std::in_place_type_t<Node::DependentAsync>{},
+    [p=make_moc(std::move(p))] () mutable { p.object(); }
+  ));
+  
+  if constexpr(sizeof...(Tasks) > 0) {
+    (_process_async_dependent(task._node, tasks, num_dependents), ...);
+  }
+
+  if(num_dependents == 0) {
+    _schedule_async_task(task._node);
+  }
+
+  return std::make_pair(std::move(task), std::move(fu));
+}
+
+// Function: dependent_async
+template <typename F, typename I,
+  std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>*
+>
+auto Executor::dependent_async(F&& func, I first, I last) {
+  return dependent_async(DefaultTaskParams{}, std::forward<F>(func), first, last);
+}
+
+// Function: dependent_async
+template <typename P, typename F, typename I,
+  std::enable_if_t<is_task_params_v<P> && !std::is_same_v<std::decay_t<I>, AsyncTask>, void>*
+>
+auto Executor::dependent_async(P&& params, F&& func, I first, I last) {
+  
+  _increment_topology();
+  
+  using R = std::invoke_result_t<std::decay_t<F>>;
+
+  std::packaged_task<R()> p(std::forward<F>(func));
+  auto fu{p.get_future()};
+
+  size_t num_dependents = std::distance(first, last);
+
+  AsyncTask task(node_pool.animate(
+    std::forward<P>(params), nullptr, nullptr, num_dependents,
+    std::in_place_type_t<Node::DependentAsync>{},
+    [p=make_moc(std::move(p))] () mutable { p.object(); }
+  ));
+
+  for(; first != last; first++) {
+    _process_async_dependent(task._node, *first, num_dependents);
+  }
+
+  if(num_dependents == 0) {
+    _schedule_async_task(task._node);
+  }
+
+  return std::make_pair(std::move(task), std::move(fu));
+}
+
+// ----------------------------------------------------------------------------
+// Dependent Async Helper Functions
+// ----------------------------------------------------------------------------
+
+// Procedure: _process_async_dependent
+inline void Executor::_process_async_dependent(
+  Node* node, tf::AsyncTask& task, size_t& num_dependents
+) {
+
+  auto& state = std::get_if<Node::DependentAsync>(&(task._node->_handle))->state;
+
+  add_successor:
+
+  auto target = Node::AsyncState::UNFINISHED;
+  
+  // acquires the lock
+  if(state.compare_exchange_weak(target, Node::AsyncState::LOCKED,
+                                 std::memory_order_acq_rel,
+                                 std::memory_order_acquire)) {
+    task._node->_successors.push_back(node);
+    state.store(Node::AsyncState::UNFINISHED, std::memory_order_release);
+  }
+  // dep's state is FINISHED, which means dep finished its callable already
+  // thus decrement the node's join counter by 1
+  else if (target == Node::AsyncState::FINISHED) {
+    num_dependents = node->_join_counter.fetch_sub(1, std::memory_order_acq_rel) - 1;
+  }
+  // another worker adding its async task to the same successors of this node
+  else {
+    goto add_successor;
+  }
+}
+
+
+// Procedure: _tear_down_dependent_async
+inline void Executor::_tear_down_dependent_async(Worker& worker, Node* node) {
+
+  auto handle = std::get_if<Node::DependentAsync>(&(node->_handle));
+
+  // this async task comes from Executor
+  auto target = Node::AsyncState::UNFINISHED;
+
+  while(!handle->state.compare_exchange_weak(target, Node::AsyncState::FINISHED,
+                                             std::memory_order_acq_rel,
+                                             std::memory_order_relaxed)) {
+    target = Node::AsyncState::UNFINISHED;
+  }
+  
+  // spaw successors whenever their dependencies are resolved
+  worker._cache = nullptr;
+  for(size_t i=0; i<node->_successors.size(); ++i) {
+    if(auto s = node->_successors[i]; 
+      s->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1
+    ) {
+      if(worker._cache) {
+        _schedule(worker, worker._cache);
+      }
+      worker._cache = s;
+    }
+  }
+  
+  // now the executor no longer needs to retain ownership
+  if(handle->use_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+    node_pool.recycle(node);
+  }
+
+  _decrement_topology();
+}
+
+
+
+
+
+}  // end of namespace tf -----------------------------------------------------
+
diff --git a/bundled/taskflow-3.7.0/taskflow/core/async_task.hpp b/bundled/taskflow-3.7.0/taskflow/core/async_task.hpp
new file mode 100644 (file)
index 0000000..026e8cb
--- /dev/null
@@ -0,0 +1,209 @@
+#pragma once
+
+#include "graph.hpp"
+
+/**
+@file async_task.hpp
+@brief asynchronous task include file
+*/
+
+namespace tf {
+
+// ----------------------------------------------------------------------------
+// AsyncTask
+// ----------------------------------------------------------------------------
+
+/**
+@brief class to create a dependent asynchronous task
+
+A tf::AsyncTask is a lightweight handle that retains @em shared ownership
+of a dependent async task created by an executor.
+This shared ownership ensures that the async task remains alive when
+adding it to the dependency list of another async task, 
+thus avoiding the classical [ABA problem](https://en.wikipedia.org/wiki/ABA_problem).
+
+@code{.cpp}
+// main thread retains shared ownership of async task A
+tf::AsyncTask A = executor.silent_dependent_async([](){});
+
+// task A remains alive (i.e., at least one ref count by the main thread) 
+// when being added to the dependency list of async task B
+tf::AsyncTask B = executor.silent_dependent_async([](){}, A);
+@endcode
+
+Currently, tf::AsyncTask is implemented based on the logic of 
+C++ smart pointer std::shared_ptr and 
+is considered cheap to copy or move as long as only a handful of objects
+own it.
+When a worker completes an async task, it will remove the task from the executor,
+decrementing the number of shared owners by one.
+If that counter reaches zero, the task is destroyed.
+*/
+class AsyncTask {
+  
+  friend class Executor;
+  
+  public:
+    
+    /**
+    @brief constructs an empty task handle
+    */
+    AsyncTask() = default;
+    
+    /**
+    @brief destroys the managed asynchronous task if this is the last owner
+    */
+    ~AsyncTask();
+    
+    /**
+    @brief constructs an asynchronous task that shares ownership of @c rhs
+    */
+    AsyncTask(const AsyncTask& rhs);
+
+    /**
+    @brief move-constructs an asynchronous task from @c rhs
+    */
+    AsyncTask(AsyncTask&& rhs);
+    
+    /**
+    @brief copy-assigns the asynchronous task from @c rhs
+
+    Releases the managed object of @c this and retains a new shared ownership
+    of @c rhs.
+    */
+    AsyncTask& operator = (const AsyncTask& rhs);
+
+    /**
+    @brief move-assigns the asynchronous task from @c rhs
+    
+    Releases the managed object of @c this and takes over the ownership of @c rhs.
+    */
+    AsyncTask& operator = (AsyncTask&& rhs);
+    
+    /**
+    @brief checks if the asynchronous task stores nothing
+    */
+    bool empty() const;
+
+    /**
+    @brief release the managed object of @c this
+    */
+    void reset();
+    
+    /**
+    @brief obtains a hash value of this asynchronous task
+    */
+    size_t hash_value() const;
+
+    /**
+    @brief returns the number of shared owners that are currently managing 
+           this asynchronous task
+    */
+    size_t use_count() const;
+
+    /**                                                                                                       
+    @brief returns the boolean indicating whether the async task is done
+    */
+    bool is_done() const; 
+
+  private:
+
+    explicit AsyncTask(Node*);
+
+    Node* _node {nullptr};
+
+    void _incref();
+    void _decref();
+};
+
+// Constructor
+inline AsyncTask::AsyncTask(Node* ptr) : _node{ptr} {
+  _incref();
+}
+
+// Function: _incref
+inline void AsyncTask::_incref() {
+  if(_node) {
+    std::get_if<Node::DependentAsync>(&(_node->_handle))->use_count.fetch_add(
+      1, std::memory_order_relaxed
+    );
+  }
+}
+
+// Function: _decref
+inline void AsyncTask::_decref() {
+  if(_node && std::get_if<Node::DependentAsync>(&(_node->_handle))->use_count.fetch_sub(
+      1, std::memory_order_acq_rel
+    ) == 1) {
+    node_pool.recycle(_node);
+  }
+}
+
+// Copy Constructor
+inline AsyncTask::AsyncTask(const AsyncTask& rhs) : 
+  _node{rhs._node} {
+  _incref();
+}
+
+// Move Constructor
+inline AsyncTask::AsyncTask(AsyncTask&& rhs) :
+  _node {rhs._node} {
+  rhs._node = nullptr;
+}
+
+// Destructor
+inline AsyncTask::~AsyncTask() {
+  _decref();
+}
+
+// Copy assignment
+inline AsyncTask& AsyncTask::operator = (const AsyncTask& rhs) {
+  _decref();
+  _node = rhs._node;
+  _incref();
+  return *this;
+}
+
+// Move assignment
+inline AsyncTask& AsyncTask::operator = (AsyncTask&& rhs) {
+  _decref();
+  _node = rhs._node;
+  rhs._node = nullptr;
+  return *this;
+}
+
+// Function: empty
+inline bool AsyncTask::empty() const {
+  return _node == nullptr;
+}
+
+// Function: reset
+inline void AsyncTask::reset() {
+  _decref();
+  _node = nullptr;
+}
+
+// Function: hash_value
+inline size_t AsyncTask::hash_value() const {
+  return std::hash<Node*>{}(_node);
+}
+
+// Function: use_count
+inline size_t AsyncTask::use_count() const {
+  return _node == nullptr ? size_t{0} : 
+  std::get_if<Node::DependentAsync>(&(_node->_handle))->use_count.load(
+    std::memory_order_relaxed
+  );
+}
+
+// Function: is_done
+inline bool AsyncTask::is_done() const {
+  return std::get_if<Node::DependentAsync>(&(_node->_handle))->state.load(
+    std::memory_order_acquire
+  ) == Node::AsyncState::FINISHED;
+}
+
+}  // end of namespace tf ----------------------------------------------------
+
+
+
similarity index 86%
rename from bundled/taskflow-3.6.0/include/taskflow/core/declarations.hpp
rename to bundled/taskflow-3.7.0/taskflow/core/declarations.hpp
index dd89ab3b210403e564f88f7ec918f2d1f2a272b8..7763fab0ba10f2c6a3ae47a968ff8f4e290dc614 100644 (file)
@@ -52,6 +52,12 @@ class syclGraph;
 class syclTask;
 class syclFlow;
 
+// ----------------------------------------------------------------------------
+// struct 
+// ----------------------------------------------------------------------------
+struct TaskParams;
+struct DefaultTaskParams;
+
 
 }  // end of namespace tf -----------------------------------------------------
 
similarity index 81%
rename from bundled/taskflow-3.6.0/include/taskflow/core/executor.hpp
rename to bundled/taskflow-3.7.0/taskflow/core/executor.hpp
index a5607e044b67498f1dc1fd66d124acebd299d16d..a9d9dc457dfa8e1f7cf6534c5e139ce749f897fb 100644 (file)
@@ -59,23 +59,15 @@ class Executor {
   /**
   @brief constructs the executor with @c N worker threads
 
-
-  @param N number of workers (default std::thread::hardware_concurrency)
-  @param wix worker interface class to alter worker (thread) behaviors
+  @param N the number of workers (default std::thread::hardware_concurrency)
   
   The constructor spawns @c N worker threads to run tasks in a
   work-stealing loop. The number of workers must be greater than zero
   or an exception will be thrown.
   By default, the number of worker threads is equal to the maximum
   hardware concurrency returned by std::thread::hardware_concurrency.
-
-  Users can alter the worker behavior, such as changing thread affinity,
-  via deriving an instance from tf::WorkerInterface.
   */
-  explicit Executor(
-    size_t N = std::thread::hardware_concurrency(),
-    std::shared_ptr<WorkerInterface> wix = nullptr 
-  );
+  explicit Executor(size_t N = std::thread::hardware_concurrency());
 
   /**
   @brief destructs the executor
@@ -599,23 +591,25 @@ class Executor {
   // --------------------------------------------------------------------------
   // Async Task Methods
   // --------------------------------------------------------------------------
-
+  
   /**
-  @brief runs a given function asynchronously
+  @brief creates a parameterized asynchronous task to run the given function
 
+  @tparam P task parameter type
   @tparam F callable type
 
+  @param params task parameters
   @param func callable object
 
   @return a @std_future that will hold the result of the execution
-
-  The method creates an asynchronous task to run the given function
-  and return a @std_future object that eventually will hold the result
-  of the return value.
+  
+  The method creates a parameterized asynchronous task 
+  to run the given function and return a @std_future object 
+  that eventually will hold the result of the execution.
 
   @code{.cpp}
-  std::future<int> future = executor.async([](){
-    std::cout << "create an asynchronous task and returns 1\n";
+  std::future<int> future = executor.async("name", [](){
+    std::cout << "create an asynchronous task with a name and returns 1\n";
     return 1;
   });
   future.get();
@@ -623,27 +617,25 @@ class Executor {
 
   This member function is thread-safe.
   */
-  template <typename F>
-  auto async(F&& func);
+  template <typename P, typename F>
+  auto async(P&& params, F&& func);
 
   /**
-  @brief runs a given function asynchronously and gives a name to this task
+  @brief runs a given function asynchronously
 
   @tparam F callable type
 
-  @param name name of the asynchronous task
   @param func callable object
 
   @return a @std_future that will hold the result of the execution
-  
-  The method creates and assigns a name to an asynchronous task 
-  to run the given function, 
-  returning @std_future object that eventually will hold the result
-  Assigned task names will appear in the observers of the executor.
+
+  The method creates an asynchronous task to run the given function
+  and return a @std_future object that eventually will hold the result
+  of the return value.
 
   @code{.cpp}
-  std::future<int> future = executor.async("name", [](){
-    std::cout << "create an asynchronous task with a name and returns 1\n";
+  std::future<int> future = executor.async([](){
+    std::cout << "create an asynchronous task and returns 1\n";
     return 1;
   });
   future.get();
@@ -652,47 +644,50 @@ class Executor {
   This member function is thread-safe.
   */
   template <typename F>
-  auto async(const std::string& name, F&& func);
+  auto async(F&& func);
 
   /**
   @brief similar to tf::Executor::async but does not return a future object
-  
+
   @tparam F callable type
-  
+
+  @param params task parameters
   @param func callable object
 
-  This member function is more efficient than tf::Executor::async
-  and is encouraged to use when you do not want a @std_future to
-  acquire the result or synchronize the execution.
+  The method creates a parameterized asynchronous task 
+  to run the given function without returning any @std_future object.
+  This member function is more efficient than tf::Executor::async 
+  and is encouraged to use when applications do not need a @std_future to acquire
+  the result or synchronize the execution.
 
   @code{.cpp}
-  executor.silent_async([](){
-    std::cout << "create an asynchronous task with no return\n";
+  executor.silent_async("name", [](){
+    std::cout << "create an asynchronous task with a name and no return\n";
   });
   executor.wait_for_all();
   @endcode
 
   This member function is thread-safe.
   */
-  template <typename F>
-  void silent_async(F&& func);
-
+  template <typename P, typename F>
+  void silent_async(P&& params, F&& func);
+  
   /**
   @brief similar to tf::Executor::async but does not return a future object
-
+  
   @tparam F callable type
-
-  @param name assigned name to the task
+  
   @param func callable object
 
-  This member function is more efficient than tf::Executor::async
-  and is encouraged to use when you do not want a @std_future to
-  acquire the result or synchronize the execution.
-  Assigned task names will appear in the observers of the executor.
+  The method creates an asynchronous task 
+  to run the given function without returning any @std_future object.
+  This member function is more efficient than tf::Executor::async 
+  and is encouraged to use when applications do not need a @std_future to acquire
+  the result or synchronize the execution.
 
   @code{.cpp}
-  executor.silent_async("name", [](){
-    std::cout << "create an asynchronous task with a name and no return\n";
+  executor.silent_async([](){
+    std::cout << "create an asynchronous task with no return\n";
   });
   executor.wait_for_all();
   @endcode
@@ -700,7 +695,7 @@ class Executor {
   This member function is thread-safe.
   */
   template <typename F>
-  void silent_async(const std::string& name, F&& func);
+  void silent_async(F&& func);
 
   // --------------------------------------------------------------------------
   // Silent Dependent Async Methods
@@ -739,13 +734,13 @@ class Executor {
   tf::AsyncTask silent_dependent_async(F&& func, Tasks&&... tasks);
   
   /**
-  @brief names and runs the given function asynchronously 
+  @brief runs the given function asynchronously 
          when the given dependents finish
   
   @tparam F callable type
   @tparam Tasks task types convertible to tf::AsyncTask
 
-  @param name assigned name to the task
+  @param params task parameters
   @param func callable object
   @param tasks asynchronous tasks on which this execution depends
   
@@ -769,10 +764,10 @@ class Executor {
 
   This member function is thread-safe.
   */
-  template <typename F, typename... Tasks,
-    std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr
+  template <typename P, typename F, typename... Tasks,
+    std::enable_if_t<is_task_params_v<P> && all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr
   >
-  tf::AsyncTask silent_dependent_async(const std::string& name, F&& func, Tasks&&... tasks);
+  tf::AsyncTask silent_dependent_async(P&& params, F&& func, Tasks&&... tasks);
   
   /**
   @brief runs the given function asynchronously 
@@ -812,13 +807,13 @@ class Executor {
   tf::AsyncTask silent_dependent_async(F&& func, I first, I last);
   
   /**
-  @brief names and runs the given function asynchronously 
+  @brief runs the given function asynchronously 
          when the given range of dependents finish
   
   @tparam F callable type
   @tparam I iterator type 
 
-  @param name assigned name to the task
+  @param params tasks parameters
   @param func callable object
   @param first iterator to the beginning (inclusive)
   @param last iterator to the end (exclusive)
@@ -845,10 +840,10 @@ class Executor {
 
   This member function is thread-safe.
   */
-  template <typename F, typename I, 
-    std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr
+  template <typename P, typename F, typename I, 
+    std::enable_if_t<is_task_params_v<P> && !std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr
   >
-  tf::AsyncTask silent_dependent_async(const std::string& name, F&& func, I first, I last);
+  tf::AsyncTask silent_dependent_async(P&& params, F&& func, I first, I last);
   
   // --------------------------------------------------------------------------
   // Dependent Async Methods
@@ -897,13 +892,14 @@ class Executor {
   auto dependent_async(F&& func, Tasks&&... tasks);
   
   /**
-  @brief names and runs the given function asynchronously
+  @brief runs the given function asynchronously
          when the given dependents finish
   
+  @tparam P task parameters type
   @tparam F callable type
   @tparam Tasks task types convertible to tf::AsyncTask
   
-  @param name assigned name to the task
+  @param params task parameters
   @param func callable object
   @param tasks asynchronous tasks on which this execution depends
   
@@ -936,10 +932,10 @@ class Executor {
 
   This member function is thread-safe.
   */
-  template <typename F, typename... Tasks,
-    std::enable_if_t<all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr
+  template <typename P, typename F, typename... Tasks,
+    std::enable_if_t<is_task_params_v<P> && all_same_v<AsyncTask, std::decay_t<Tasks>...>, void>* = nullptr
   >
-  auto dependent_async(const std::string& name, F&& func, Tasks&&... tasks);
+  auto dependent_async(P&& params, F&& func, Tasks&&... tasks);
   
   /**
   @brief runs the given function asynchronously 
@@ -987,13 +983,14 @@ class Executor {
   auto dependent_async(F&& func, I first, I last);
   
   /**
-  @brief names and runs the given function asynchronously 
+  @brief runs the given function asynchronously 
          when the given range of dependents finish
   
+  @tparam P task parameters type
   @tparam F callable type
   @tparam I iterator type 
   
-  @param name assigned name to the task
+  @param params task parameters
   @param func callable object
   @param first iterator to the beginning (inclusive)
   @param last iterator to the end (exclusive)
@@ -1029,42 +1026,44 @@ class Executor {
 
   This member function is thread-safe.
   */
-  template <typename F, typename I,
-    std::enable_if_t<!std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr
+  template <typename P, typename F, typename I,
+    std::enable_if_t<is_task_params_v<P> && !std::is_same_v<std::decay_t<I>, AsyncTask>, void>* = nullptr
   >
-  auto dependent_async(const std::string& name, F&& func, I first, I last);
+  auto dependent_async(P&& params, F&& func, I first, I last);
 
   private:
     
   const size_t _MAX_STEALS;
+  
+  std::mutex _wsq_mutex;
+  std::mutex _taskflows_mutex;
 
+#ifdef __cpp_lib_atomic_wait
+  std::atomic<size_t> _num_topologies {0};
+  std::atomic_flag _all_spawned = ATOMIC_FLAG_INIT;
+#else
   std::condition_variable _topology_cv;
-  std::mutex _taskflows_mutex;
   std::mutex _topology_mutex;
-  std::mutex _wsq_mutex;
-  std::mutex _asyncs_mutex;
-
   size_t _num_topologies {0};
+#endif
   
   std::unordered_map<std::thread::id, size_t> _wids;
   std::vector<std::thread> _threads;
   std::vector<Worker> _workers;
   std::list<Taskflow> _taskflows;
 
-  std::unordered_set<std::shared_ptr<Node>> _asyncs;
-
   Notifier _notifier;
 
   TaskQueue<Node*> _wsq;
 
   std::atomic<bool> _done {0};
 
-  std::shared_ptr<WorkerInterface> _worker_interface;
   std::unordered_set<std::shared_ptr<ObserverInterface>> _observers;
 
   Worker* _this_worker();
-
+  
   bool _wait_for_task(Worker&, Node*&);
+  bool _invoke_module_task_internal(Worker&, Node*);
 
   void _observer_prologue(Worker&, Node*);
   void _observer_epilogue(Worker&, Node*);
@@ -1076,48 +1075,45 @@ class Executor {
   void _schedule(Worker&, const SmallVector<Node*>&);
   void _schedule(const SmallVector<Node*>&);
   void _set_up_topology(Worker*, Topology*);
+  void _set_up_graph(Graph&, Node*, Topology*, int, SmallVector<Node*>&);
   void _tear_down_topology(Worker&, Topology*);
   void _tear_down_async(Node*);
   void _tear_down_dependent_async(Worker&, Node*);
   void _tear_down_invoke(Worker&, Node*);
   void _increment_topology();
   void _decrement_topology();
-  void _decrement_topology_and_notify();
   void _invoke(Worker&, Node*);
   void _invoke_static_task(Worker&, Node*);
-  void _invoke_dynamic_task(Worker&, Node*);
-  void _consume_graph(Worker&, Node*, Graph&);
-  void _detach_dynamic_task(Worker&, Node*, Graph&);
+  void _invoke_subflow_task(Worker&, Node*);
+  void _detach_subflow_task(Worker&, Node*, Graph&);
   void _invoke_condition_task(Worker&, Node*, SmallVector<int>&);
   void _invoke_multi_condition_task(Worker&, Node*, SmallVector<int>&);
   void _invoke_module_task(Worker&, Node*);
   void _invoke_async_task(Worker&, Node*);
   void _invoke_dependent_async_task(Worker&, Node*);
   void _process_async_dependent(Node*, tf::AsyncTask&, size_t&);
+  void _process_exception(Worker&, Node*);
   void _schedule_async_task(Node*);
+  void _corun_graph(Worker&, Node*, Graph&);
   
   template <typename P>
   void _corun_until(Worker&, P&&);
-  
-  template <typename R, typename F>
-  auto _make_promised_async(std::promise<R>&&, F&&);
 };
 
 // Constructor
-inline Executor::Executor(size_t N, std::shared_ptr<WorkerInterface> wix) :
+inline Executor::Executor(size_t N) :
   _MAX_STEALS {((N+1) << 1)},
   _threads    {N},
   _workers    {N},
-  _notifier   {N},
-  _worker_interface {std::move(wix)} {
+  _notifier   {N} {
 
   if(N == 0) {
-    TF_THROW("no cpu workers to execute taskflows");
+    TF_THROW("executor must define at least one worker");
   }
 
   _spawn(N);
 
-  // instantite the default observer if requested
+  // initialize the default observer if requested
   if(has_env(TF_ENABLE_PROFILER)) {
     TFProfManager::get()._manage(make_observer<TFProfObserver>());
   }
@@ -1146,7 +1142,11 @@ inline size_t Executor::num_workers() const noexcept {
 
 // Function: num_topologies
 inline size_t Executor::num_topologies() const {
+#ifdef __cpp_lib_atomic_wait
+  return _num_topologies.load(std::memory_order_relaxed);
+#else
   return _num_topologies;
+#endif
 }
 
 // Function: num_taskflows
@@ -1169,9 +1169,12 @@ inline int Executor::this_worker_id() const {
 // Procedure: _spawn
 inline void Executor::_spawn(size_t N) {
 
+#ifdef __cpp_lib_atomic_wait
+#else
   std::mutex mutex;
   std::condition_variable cond;
   size_t n=0;
+#endif
 
   for(size_t id=0; id<N; ++id) {
 
@@ -1180,14 +1183,15 @@ inline void Executor::_spawn(size_t N) {
     _workers[id]._executor = this;
     _workers[id]._waiter = &_notifier._waiters[id];
 
-    _threads[id] = std::thread([this] (
-      Worker& w, std::mutex& mutex, std::condition_variable& cond, size_t& n
-    ) -> void {
-      
-      // assign the thread
-      w._thread = &_threads[w._id];
+    _threads[id] = std::thread([&, &w=_workers[id]] () {
 
-      // enables the mapping
+#ifdef __cpp_lib_atomic_wait
+      // wait for the caller thread to initialize the ID mapping
+      _all_spawned.wait(false, std::memory_order_acquire);
+      w._thread = &_threads[w._id];
+#else
+      // update the ID mapping of this thread
+      w._thread = &_threads[w._id];
       {
         std::scoped_lock lock(mutex);
         _wids[std::this_thread::get_id()] = w._id;
@@ -1195,41 +1199,22 @@ inline void Executor::_spawn(size_t N) {
           cond.notify_one();
         }
       }
+#endif
 
       Node* t = nullptr;
       
-      // before entering the scheduler (work-stealing loop), 
-      // call the user-specified prologue function
-      if(_worker_interface) {
-        _worker_interface->scheduler_prologue(w);
-      }
-      
-      // must use 1 as condition instead of !done because
-      // the previous worker may stop while the following workers
-      // are still preparing for entering the scheduling loop
-      std::exception_ptr ptr{nullptr};
-      try {
-        while(1) {
-
-          // execute the tasks.
-          _exploit_task(w, t);
-
-          // wait for tasks
-          if(_wait_for_task(w, t) == false) {
-            break;
-          }
+      while(1) {
+
+        // execute the tasks.
+        _exploit_task(w, t);
+
+        // wait for tasks
+        if(_wait_for_task(w, t) == false) {
+          break;
         }
-      } 
-      catch(...) {
-        ptr = std::current_exception();
-      }
-      
-      // call the user-specified epilogue function
-      if(_worker_interface) {
-        _worker_interface->scheduler_epilogue(w, ptr);
       }
 
-    }, std::ref(_workers[id]), std::ref(mutex), std::ref(cond), std::ref(n));
+    });
     
     // POSIX-like system can use the following to affine threads to cores 
     //cpu_set_t cpuset;
@@ -1238,10 +1223,22 @@ inline void Executor::_spawn(size_t N) {
     //pthread_setaffinity_np(
     //  _threads[id].native_handle(), sizeof(cpu_set_t), &cpuset
     //);
-  }
 
+#ifdef __cpp_lib_atomic_wait
+    //_wids[_threads[id].get_id()] = id;
+    _wids.emplace(std::piecewise_construct,
+      std::forward_as_tuple(_threads[id].get_id()), std::forward_as_tuple(id)
+    );
+#endif
+  }
+  
+#ifdef __cpp_lib_atomic_wait
+  _all_spawned.test_and_set(std::memory_order_release);
+  _all_spawned.notify_all();
+#else
   std::unique_lock<std::mutex> lock(mutex);
   cond.wait(lock, [&](){ return n==N; });
+#endif
 }
 
 // Function: _corun_until
@@ -1521,6 +1518,8 @@ inline void Executor::_invoke(Worker& worker, Node* node) {
   while(!(node->_state.load(std::memory_order_acquire) & Node::READY));
 
   begin_invoke:
+  
+  SmallVector<int> conds;
 
   // no need to do other things if the topology is cancelled
   if(node->_is_cancelled()) {
@@ -1540,7 +1539,6 @@ inline void Executor::_invoke(Worker& worker, Node* node) {
 
   // condition task
   //int cond = -1;
-  SmallVector<int> conds;
 
   // switch is faster than nested if-else due to jump table
   switch(node->_handle.index()) {
@@ -1550,9 +1548,9 @@ inline void Executor::_invoke(Worker& worker, Node* node) {
     }
     break;
 
-    // dynamic task
-    case Node::DYNAMIC: {
-      _invoke_dynamic_task(worker, node);
+    // subflow task
+    case Node::SUBFLOW: {
+      _invoke_subflow_task(worker, node);
     }
     break;
 
@@ -1599,6 +1597,8 @@ inline void Executor::_invoke(Worker& worker, Node* node) {
     break;
   }
 
+  //invoke_successors:
+
   // if releasing semaphores exist, release them
   if(node->_semaphores && !node->_semaphores->to_release.empty()) {
     _schedule(worker, node->_release_all());
@@ -1687,19 +1687,31 @@ inline void Executor::_invoke(Worker& worker, Node* node) {
   }
 }
 
-// Proecdure: _tear_down_invoke
+// Procedure: _tear_down_invoke
 inline void Executor::_tear_down_invoke(Worker& worker, Node* node) {
-  // we must check parent first before substracting the join counter,
+  // we must check parent first before subtracting the join counter,
   // or it can introduce data race
-  if(node->_parent == nullptr) {
+  if(auto parent = node->_parent; parent == nullptr) {
     if(node->_topology->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) {
       _tear_down_topology(worker, node->_topology);
     }
   }
-  // joined subflow
-  else {  
-    node->_parent->_join_counter.fetch_sub(1, std::memory_order_release);
+  // Here we asssume the parent is in a busy loop (e.g., corun) waiting for
+  // its join counter to become 0.
+  else {
+    //parent->_join_counter.fetch_sub(1, std::memory_order_acq_rel);
+    parent->_join_counter.fetch_sub(1, std::memory_order_release);
   }
+  //// module task
+  //else {  
+  //  auto id = parent->_handle.index();
+  //  if(parent->_join_counter.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+  //    if(id == Node::MODULE) {
+  //      return parent;
+  //    }
+  //  }
+  //}
+  //return nullptr;
 }
 
 // Procedure: _observer_prologue
@@ -1716,47 +1728,68 @@ inline void Executor::_observer_epilogue(Worker& worker, Node* node) {
   }
 }
 
+// Procedure: _process_exception
+inline void Executor::_process_exception(Worker&, Node* node) {
+
+  constexpr static auto flag = Topology::EXCEPTION | Topology::CANCELLED;
+  
+  // if the node has a parent, we store the exception in its parent
+  if(auto parent = node->_parent; parent) { 
+    if ((parent->_state.fetch_or(Node::EXCEPTION, std::memory_order_relaxed) & Node::EXCEPTION) == 0) {
+      parent->_exception_ptr = std::current_exception();
+    }
+    // TODO if the node has a topology, cancel it to enable early stop
+    //if(auto tpg = node->_topology; tpg) {
+    //  tpg->_state.fetch_or(Topology::CANCELLED, std::memory_order_relaxed);
+    //}
+  }
+  // multiple tasks may throw, so we only take the first thrown exception
+  else if(auto tpg = node->_topology; tpg && 
+    ((tpg->_state.fetch_or(flag, std::memory_order_relaxed) & Topology::EXCEPTION) == 0)
+  ) {
+    tpg->_exception_ptr = std::current_exception();
+  }
+  // TODO: skip the exception that is not associated with any taskflows
+}
+
 // Procedure: _invoke_static_task
 inline void Executor::_invoke_static_task(Worker& worker, Node* node) {
   _observer_prologue(worker, node);
-  auto& work = std::get_if<Node::Static>(&node->_handle)->work;
-  switch(work.index()) {
-    case 0:
-      std::get_if<0>(&work)->operator()();
-    break;
+  TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
+    auto& work = std::get_if<Node::Static>(&node->_handle)->work;
+    switch(work.index()) {
+      case 0:
+        std::get_if<0>(&work)->operator()();
+      break;
 
-    case 1:
-      Runtime rt(*this, worker, node);
-      std::get_if<1>(&work)->operator()(rt);
-    break;
-  }
+      case 1:
+        Runtime rt(*this, worker, node);
+        std::get_if<1>(&work)->operator()(rt);
+        node->_process_exception();
+      break;
+    }
+  });
   _observer_epilogue(worker, node);
 }
 
-// Procedure: _invoke_dynamic_task
-inline void Executor::_invoke_dynamic_task(Worker& w, Node* node) {
-
+// Procedure: _invoke_subflow_task
+inline void Executor::_invoke_subflow_task(Worker& w, Node* node) {
   _observer_prologue(w, node);
-
-  auto handle = std::get_if<Node::Dynamic>(&node->_handle);
-
-  handle->subgraph._clear();
-
-  Subflow sf(*this, w, node, handle->subgraph);
-
-  handle->work(sf);
-
-  if(sf._joinable) {
-    _consume_graph(w, node, handle->subgraph);
-  }
-
+  TF_EXECUTOR_EXCEPTION_HANDLER(w, node, {
+    auto handle = std::get_if<Node::Subflow>(&node->_handle);
+    handle->subgraph._clear();
+    Subflow sf(*this, w, node, handle->subgraph);
+    handle->work(sf);
+    if(sf._joinable) {
+      _corun_graph(w, node, handle->subgraph);
+    }
+    node->_process_exception();
+  });
   _observer_epilogue(w, node);
 }
 
-// Procedure: _detach_dynamic_task
-inline void Executor::_detach_dynamic_task(
-  Worker& w, Node* p, Graph& g
-) {
+// Procedure: _detach_subflow_task
+inline void Executor::_detach_subflow_task(Worker& w, Node* p, Graph& g) {
 
   // graph is empty and has no async tasks
   if(g.empty() && p->_join_counter.load(std::memory_order_acquire) == 0) {
@@ -1764,18 +1797,7 @@ inline void Executor::_detach_dynamic_task(
   }
 
   SmallVector<Node*> src;
-
-  for(auto n : g._nodes) {
-
-    n->_state.store(Node::DETACHED, std::memory_order_relaxed);
-    n->_set_up_join_counter();
-    n->_topology = p->_topology;
-    n->_parent = nullptr;
-
-    if(n->num_dependents() == 0) {
-      src.push_back(n);
-    }
-  }
+  _set_up_graph(g, nullptr, p->_topology, Node::DETACHED, src);
 
   {
     std::lock_guard<std::mutex> lock(p->_topology->_taskflow._mutex);
@@ -1786,29 +1808,26 @@ inline void Executor::_detach_dynamic_task(
   _schedule(w, src);
 }
 
-// Procedure: _consume_graph
-inline void Executor::_consume_graph(Worker& w, Node* p, Graph& g) {
+// Procedure: _corun_graph
+inline void Executor::_corun_graph(Worker& w, Node* p, Graph& g) {
 
-  // graph is empty and has no async tasks
+  // assert(p);
+
+  // graph is empty and has no async tasks (subflow)
   if(g.empty() && p->_join_counter.load(std::memory_order_acquire) == 0) {
     return;
   }
 
   SmallVector<Node*> src;
 
-  for(auto n : g._nodes) {
-    n->_state.store(0, std::memory_order_relaxed);
-    n->_set_up_join_counter();
-    n->_topology = p->_topology;
-    n->_parent = p;
-    if(n->num_dependents() == 0) {
-      src.push_back(n);
-    }
-  }
+  _set_up_graph(g, p, p->_topology, 0, src);
   p->_join_counter.fetch_add(src.size(), std::memory_order_relaxed);
   
   _schedule(w, src);
-  _corun_until(w, [p] () -> bool { return p->_join_counter.load(std::memory_order_acquire) == 0; });
+
+  _corun_until(w, [p] () -> bool { 
+    return p->_join_counter.load(std::memory_order_acquire) == 0; }
+  );
 }
 
 // Procedure: _invoke_condition_task
@@ -1816,17 +1835,20 @@ inline void Executor::_invoke_condition_task(
   Worker& worker, Node* node, SmallVector<int>& conds
 ) {
   _observer_prologue(worker, node);
-  auto& work = std::get_if<Node::Condition>(&node->_handle)->work;
-  switch(work.index()) {
-    case 0:
-      conds = { std::get_if<0>(&work)->operator()() };
-    break;
+  TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
+    auto& work = std::get_if<Node::Condition>(&node->_handle)->work;
+    switch(work.index()) {
+      case 0:
+        conds = { std::get_if<0>(&work)->operator()() };
+      break;
 
-    case 1:
-      Runtime rt(*this, worker, node);
-      conds = { std::get_if<1>(&work)->operator()(rt) };
-    break;
-  }
+      case 1:
+        Runtime rt(*this, worker, node);
+        conds = { std::get_if<1>(&work)->operator()(rt) };
+        node->_process_exception();
+      break;
+    }
+  });
   _observer_epilogue(worker, node);
 }
 
@@ -1835,41 +1857,88 @@ inline void Executor::_invoke_multi_condition_task(
   Worker& worker, Node* node, SmallVector<int>& conds
 ) {
   _observer_prologue(worker, node);
-  auto& work = std::get_if<Node::MultiCondition>(&node->_handle)->work;
-  switch(work.index()) {
-    case 0:
-      conds = std::get_if<0>(&work)->operator()();
-    break;
+  TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
+    auto& work = std::get_if<Node::MultiCondition>(&node->_handle)->work;
+    switch(work.index()) {
+      case 0:
+        conds = std::get_if<0>(&work)->operator()();
+      break;
 
-    case 1:
-      Runtime rt(*this, worker, node);
-      conds = std::get_if<1>(&work)->operator()(rt);
-    break;
-  }
+      case 1:
+        Runtime rt(*this, worker, node);
+        conds = std::get_if<1>(&work)->operator()(rt);
+        node->_process_exception();
+      break;
+    }
+  });
   _observer_epilogue(worker, node);
 }
 
 // Procedure: _invoke_module_task
 inline void Executor::_invoke_module_task(Worker& w, Node* node) {
   _observer_prologue(w, node);
-  _consume_graph(
-    w, node, std::get_if<Node::Module>(&node->_handle)->graph
-  );
+  TF_EXECUTOR_EXCEPTION_HANDLER(w, node, {
+    _corun_graph(w, node, std::get_if<Node::Module>(&node->_handle)->graph);
+    node->_process_exception();
+  });
   _observer_epilogue(w, node);
 }
 
+//// Function: _invoke_module_task_internal
+//inline bool Executor::_invoke_module_task_internal(Worker& w, Node* p) {
+//  
+//  // acquire the underlying graph
+//  auto& g = std::get_if<Node::Module>(&p->_handle)->graph;
+//
+//  // no need to do anything if the graph is empty
+//  if(g.empty()) {
+//    return false;
+//  }
+//
+//  SmallVector<Node*> src;
+//  _set_up_graph(g, p, p->_topology, 0, src);
+//  p->_join_counter.fetch_add(src.size(), std::memory_order_relaxed);
+//
+//  _schedule(w, src);
+//  return true;
+//}
+
 // Procedure: _invoke_async_task
-inline void Executor::_invoke_async_task(Worker& w, Node* node) {
-  _observer_prologue(w, node);
-  std::get_if<Node::Async>(&node->_handle)->work();
-  _observer_epilogue(w, node);
+inline void Executor::_invoke_async_task(Worker& worker, Node* node) {
+  _observer_prologue(worker, node);
+  TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
+    auto& work = std::get_if<Node::Async>(&node->_handle)->work;
+    switch(work.index()) {
+      case 0:
+        std::get_if<0>(&work)->operator()();
+      break;
+
+      case 1:
+        Runtime rt(*this, worker, node);
+        std::get_if<1>(&work)->operator()(rt);
+      break;
+    }
+  });
+  _observer_epilogue(worker, node);
 }
 
 // Procedure: _invoke_dependent_async_task
-inline void Executor::_invoke_dependent_async_task(Worker& w, Node* node) {
-  _observer_prologue(w, node);
-  std::get_if<Node::DependentAsync>(&node->_handle)->work();
-  _observer_epilogue(w, node);
+inline void Executor::_invoke_dependent_async_task(Worker& worker, Node* node) {
+  _observer_prologue(worker, node);
+  TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, {
+    auto& work = std::get_if<Node::DependentAsync>(&node->_handle)->work;
+    switch(work.index()) {
+      case 0:
+        std::get_if<0>(&work)->operator()();
+      break;
+
+      case 1:
+        Runtime rt(*this, worker, node);
+        std::get_if<1>(&work)->operator()(rt);
+      break;
+    }
+  });
+  _observer_epilogue(worker, node);
 }
 
 // Function: run
@@ -1938,7 +2007,7 @@ tf::Future<void> Executor::run_until(Taskflow& f, P&& p, C&& c) {
 
   _increment_topology();
 
-  // Need to check the empty under the lock since dynamic task may
+  // Need to check the empty under the lock since subflow task may
   // define detached blocks that modify the taskflow at the same time
   bool empty;
   {
@@ -1951,8 +2020,8 @@ tf::Future<void> Executor::run_until(Taskflow& f, P&& p, C&& c) {
     c();
     std::promise<void> promise;
     promise.set_value();
-    _decrement_topology_and_notify();
-    return tf::Future<void>(promise.get_future(), std::monostate{});
+    _decrement_topology();
+    return tf::Future<void>(promise.get_future());
   }
 
   // create a topology for this run
@@ -1998,8 +2067,9 @@ void Executor::corun(T& target) {
     TF_THROW("corun must be called by a worker of the executor");
   }
 
-  Node parent;  // dummy parent
-  _consume_graph(*w, &parent, target.graph());
+  Node parent;  // auxiliary parent
+  _corun_graph(*w, &parent, target.graph());
+  parent._process_exception();
 }
 
 // Function: corun_until
@@ -2013,32 +2083,46 @@ void Executor::corun_until(P&& predicate) {
   }
 
   _corun_until(*w, std::forward<P>(predicate));
+
+  // TODO: exception?
 }
 
 // Procedure: _increment_topology
 inline void Executor::_increment_topology() {
+#ifdef __cpp_lib_atomic_wait
+  _num_topologies.fetch_add(1, std::memory_order_relaxed);
+#else
   std::lock_guard<std::mutex> lock(_topology_mutex);
   ++_num_topologies;
+#endif
 }
 
-// Procedure: _decrement_topology_and_notify
-inline void Executor::_decrement_topology_and_notify() {
+// Procedure: _decrement_topology
+inline void Executor::_decrement_topology() {
+#ifdef __cpp_lib_atomic_wait
+  if(_num_topologies.fetch_sub(1, std::memory_order_acq_rel) == 1) {
+    _num_topologies.notify_all();
+  }
+#else
   std::lock_guard<std::mutex> lock(_topology_mutex);
   if(--_num_topologies == 0) {
     _topology_cv.notify_all();
   }
-}
-
-// Procedure: _decrement_topology
-inline void Executor::_decrement_topology() {
-  std::lock_guard<std::mutex> lock(_topology_mutex);
-  --_num_topologies;
+#endif
 }
 
 // Procedure: wait_for_all
 inline void Executor::wait_for_all() {
+#ifdef __cpp_lib_atomic_wait
+  size_t n = _num_topologies.load(std::memory_order_acquire);
+  while(n != 0) {
+    _num_topologies.wait(n, std::memory_order_acquire);
+    n = _num_topologies.load(std::memory_order_acquire);
+  }
+#else
   std::unique_lock<std::mutex> lock(_topology_mutex);
   _topology_cv.wait(lock, [&](){ return _num_topologies == 0; });
+#endif
 }
 
 // Function: _set_up_topology
@@ -2048,21 +2132,7 @@ inline void Executor::_set_up_topology(Worker* worker, Topology* tpg) {
 
   tpg->_sources.clear();
   tpg->_taskflow._graph._clear_detached();
-
-  // scan each node in the graph and build up the links
-  for(auto node : tpg->_taskflow._graph._nodes) {
-
-    node->_topology = tpg;
-    node->_parent = nullptr;
-    node->_state.store(0, std::memory_order_relaxed);
-
-    if(node->num_dependents() == 0) {
-      tpg->_sources.push_back(node);
-    }
-
-    node->_set_up_join_counter();
-  }
-
+  _set_up_graph(tpg->_taskflow._graph, nullptr, tpg, 0, tpg->_sources);
   tpg->_join_counter.store(tpg->_sources.size(), std::memory_order_relaxed);
 
   if(worker) {
@@ -2073,6 +2143,22 @@ inline void Executor::_set_up_topology(Worker* worker, Topology* tpg) {
   }
 }
 
+// Function: _set_up_graph
+inline void Executor::_set_up_graph(
+  Graph& g, Node* parent, Topology* tpg, int state, SmallVector<Node*>& src
+) {
+  for(auto node : g._nodes) {
+    node->_topology = tpg;
+    node->_parent = parent;
+    node->_state.store(state, std::memory_order_relaxed);
+    if(node->num_dependents() == 0) {
+      src.push_back(node);
+    }
+    node->_set_up_join_counter();
+    node->_exception_ptr = nullptr;
+  }
+}
+
 // Function: _tear_down_topology
 inline void Executor::_tear_down_topology(Worker& worker, Topology* tpg) {
 
@@ -2081,7 +2167,7 @@ inline void Executor::_tear_down_topology(Worker& worker, Topology* tpg) {
   //assert(&tpg == &(f._topologies.front()));
 
   // case 1: we still need to run the topology again
-  if(!tpg->_is_cancelled && !tpg->_pred()) {
+  if(!tpg->_exception_ptr && !tpg->cancelled() && !tpg->_pred()) {
     //assert(tpg->_join_counter == 0);
     std::lock_guard<std::mutex> lock(f._mutex);
     tpg->_join_counter.store(tpg->_sources.size(), std::memory_order_relaxed);
@@ -2114,36 +2200,24 @@ inline void Executor::_tear_down_topology(Worker& worker, Topology* tpg) {
     else {
       //assert(f._topologies.size() == 1);
 
-      // Need to back up the promise first here becuz taskflow might be
-      // destroy soon after calling get
-      auto p {std::move(tpg->_promise)};
-
-      // Back up lambda capture in case it has the topology pointer,
-      // to avoid it releasing on pop_front ahead of _mutex.unlock &
-      // _promise.set_value. Released safely when leaving scope.
-      auto c {std::move(tpg->_call)};
-
-      // Get the satellite if any
-      auto s {f._satellite};
-
-      // Now we remove the topology from this taskflow
+      auto fetched_tpg {std::move(f._topologies.front())};
       f._topologies.pop();
+      auto satellite {f._satellite};
 
-      //f._mutex.unlock();
       lock.unlock();
+      
+      // Soon after we carry out the promise, there is no longer any guarantee
+      // for the lifetime of the associated taskflow.
+      fetched_tpg->_carry_out_promise();
 
-      // We set the promise in the end in case taskflow leaves the scope.
-      // After set_value, the caller will return from wait
-      p.set_value();
-
-      _decrement_topology_and_notify();
+      _decrement_topology();
 
       // remove the taskflow if it is managed by the executor
       // TODO: in the future, we may need to synchronize on wait
       // (which means the following code should the moved before set_value)
-      if(s) {
-        std::scoped_lock<std::mutex> lock(_taskflows_mutex);
-        _taskflows.erase(*s);
+      if(satellite) {
+        std::scoped_lock<std::mutex> satellite_lock(_taskflows_mutex);
+        _taskflows.erase(*satellite);
       }
     }
   }
@@ -2162,7 +2236,11 @@ inline void Subflow::join() {
   }
 
   // only the parent worker can join the subflow
-  _executor._consume_graph(_worker, _parent, _graph);
+  _executor._corun_graph(_worker, _parent, _graph);
+
+  // if any exception is caught from subflow tasks, rethrow it
+  _parent->_process_exception();
+
   _joinable = false;
 }
 
@@ -2175,7 +2253,7 @@ inline void Subflow::detach() {
   }
 
   // only the parent worker can detach the subflow
-  _executor._detach_dynamic_task(_worker, _parent, _graph);
+  _executor._detach_subflow_task(_worker, _parent, _graph);
   _joinable = false;
 }
 
@@ -2201,36 +2279,44 @@ inline void Runtime::schedule(Task task) {
 // Procedure: corun
 template <typename T>
 void Runtime::corun(T&& target) {
-
-  // dynamic task (subflow)
-  if constexpr(is_dynamic_task_v<T>) {
-    Graph graph;
-    Subflow sf(_executor, _worker, _parent, graph);
-    target(sf);
-    if(sf._joinable) {
-      _executor._consume_graph(_worker, _parent, graph);
-    }
-  }
-  // a composable graph object with `tf::Graph& T::graph()` defined
-  else {
-    _executor._consume_graph(_worker, _parent, target.graph());
-  }
+  _executor._corun_graph(_worker, _parent, target.graph());
+  _parent->_process_exception();
 }
 
 // Procedure: corun_until
 template <typename P>
 void Runtime::corun_until(P&& predicate) {
   _executor._corun_until(_worker, std::forward<P>(predicate));
+  // TODO: exception?
+}
+
+// Function: corun_all
+inline void Runtime::corun_all() {
+  _executor._corun_until(_worker, [this] () -> bool { 
+    return _parent->_join_counter.load(std::memory_order_acquire) == 0; 
+  });
+  _parent->_process_exception();
 }
 
+// Destructor
+inline Runtime::~Runtime() {
+  _executor._corun_until(_worker, [this] () -> bool { 
+    return _parent->_join_counter.load(std::memory_order_acquire) == 0; 
+  });
+}
+
+// ------------------------------------
+// Runtime::silent_async series
+// ------------------------------------
+
 // Function: _silent_async
-template <typename F>
-void Runtime::_silent_async(Worker& w, const std::string& name, F&& f) {
+template <typename P, typename F>
+void Runtime::_silent_async(Worker& w, P&& params, F&& f) {
 
   _parent->_join_counter.fetch_add(1, std::memory_order_relaxed);
 
   auto node = node_pool.animate(
-    name, 0, _parent->_topology, _parent, 0,
+    std::forward<P>(params), _parent->_topology, _parent, 0,
     std::in_place_type_t<Node::Async>{}, std::forward<F>(f)
   );
 
@@ -2240,44 +2326,46 @@ void Runtime::_silent_async(Worker& w, const std::string& name, F&& f) {
 // Function: silent_async
 template <typename F>
 void Runtime::silent_async(F&& f) {
-  _silent_async(*_executor._this_worker(), "", std::forward<F>(f));
+  _silent_async(*_executor._this_worker(), DefaultTaskParams{}, std::forward<F>(f));
 }
 
 // Function: silent_async
-template <typename F>
-void Runtime::silent_async(const std::string& name, F&& f) {
-  _silent_async(*_executor._this_worker(), name, std::forward<F>(f));
+template <typename P, typename F>
+void Runtime::silent_async(P&& params, F&& f) {
+  _silent_async(*_executor._this_worker(), std::forward<P>(params), std::forward<F>(f));
 }
 
 // Function: silent_async_unchecked
 template <typename F>
-void Runtime::silent_async_unchecked(const std::string& name, F&& f) {
-  _silent_async(_worker, name, std::forward<F>(f));
+void Runtime::silent_async_unchecked(F&& f) {
+  _silent_async(_worker, DefaultTaskParams{}, std::forward<F>(f));
 }
 
+// Function: silent_async_unchecked
+template <typename P, typename F>
+void Runtime::silent_async_unchecked(P&& params, F&& f) {
+  _silent_async(_worker, std::forward<P>(params), std::forward<F>(f));
+}
+
+// ------------------------------------
+// Runtime::async series
+// ------------------------------------
+
 // Function: _async
-template <typename F>
-auto Runtime::_async(Worker& w, const std::string& name, F&& f) {
+template <typename P, typename F>
+auto Runtime::_async(Worker& w, P&& params, F&& f) {
 
   _parent->_join_counter.fetch_add(1, std::memory_order_relaxed);
 
   using R = std::invoke_result_t<std::decay_t<F>>;
 
-  std::promise<R> p;
+  std::packaged_task<R()> p(std::forward<F>(f));
   auto fu{p.get_future()};
 
   auto node = node_pool.animate(
-    name, 0, _parent->_topology, _parent, 0,
+    std::forward<P>(params), _parent->_topology, _parent, 0, 
     std::in_place_type_t<Node::Async>{},
-    [p=make_moc(std::move(p)), f=std::forward<F>(f)] () mutable {
-      if constexpr(std::is_same_v<R, void>) {
-        f();
-        p.object.set_value();
-      }
-      else {
-        p.object.set_value(f());
-      }
-    }
+    [p=make_moc(std::move(p))] () mutable { p.object(); }
   );
 
   _executor._schedule(w, node);
@@ -2288,21 +2376,16 @@ auto Runtime::_async(Worker& w, const std::string& name, F&& f) {
 // Function: async
 template <typename F>
 auto Runtime::async(F&& f) {
-  return _async(*_executor._this_worker(), "", std::forward<F>(f));
+  return _async(*_executor._this_worker(), DefaultTaskParams{}, std::forward<F>(f));
 }
 
 // Function: async
-template <typename F>
-auto Runtime::async(const std::string& name, F&& f) {
-  return _async(*_executor._this_worker(), name, std::forward<F>(f));
+template <typename P, typename F>
+auto Runtime::async(P&& params, F&& f) {
+  return _async(*_executor._this_worker(), std::forward<P>(params), std::forward<F>(f));
 }
 
-// Function: join
-inline void Runtime::join() {
-  corun_until([this] () -> bool { 
-    return _parent->_join_counter.load(std::memory_order_acquire) == 0; 
-  });
-}
+
 
 }  // end of namespace tf -----------------------------------------------------
 
similarity index 87%
rename from bundled/taskflow-3.6.0/include/taskflow/core/flow_builder.hpp
rename to bundled/taskflow-3.7.0/taskflow/core/flow_builder.hpp
index 3e90d8e74d332e7b90d0dfc3ecbaede699487634..df1d02fc526f52834744d417cd8b1ae0d5eb0a42 100644 (file)
@@ -71,10 +71,10 @@ class FlowBuilder {
   });
   @endcode
 
-  Please refer to @ref DynamicTasking for details.
+  Please refer to @ref SubflowTasking for details.
   */
   template <typename C,
-    std::enable_if_t<is_dynamic_task_v<C>, void>* = nullptr
+    std::enable_if_t<is_subflow_task_v<C>, void>* = nullptr
   >
   Task emplace(C&& callable);
 
@@ -221,7 +221,7 @@ class FlowBuilder {
   The taskflow object @c t2 is composed of another taskflow object @c t1,
   preceded by another static task @c init.
   When taskflow @c t2 is submitted to an executor,
-  @c init will run first and then @c comp which spwans its definition
+  @c init will run first and then @c comp which spawns its definition
   in taskflow @c t1.
 
   The target @c object being composed must define the method
@@ -313,6 +313,7 @@ class FlowBuilder {
   */
   void linearize(std::initializer_list<Task> tasks);
 
+
   // ------------------------------------------------------------------------
   // parallel iterations
   // ------------------------------------------------------------------------
@@ -323,7 +324,7 @@ class FlowBuilder {
   @tparam B beginning iterator type
   @tparam E ending iterator type
   @tparam C callable type
-  @tparam P partitioner type (default tf::GuidedPartitioner)
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first iterator to the beginning (inclusive)
   @param last iterator to the end (exclusive)
@@ -348,8 +349,8 @@ class FlowBuilder {
 
   Please refer to @ref ParallelIterations for details.
   */
-  template <typename B, typename E, typename C, typename P = GuidedPartitioner>
-  Task for_each(B first, E last, C callable, P&& part = P());
+  template <typename B, typename E, typename C, typename P = DefaultPartitioner>
+  Task for_each(B first, E last, C callable, P part = P());
   
   /**
   @brief constructs an STL-styled index-based parallel-for task 
@@ -358,7 +359,7 @@ class FlowBuilder {
   @tparam E ending index type (must be integral)
   @tparam S step type (must be integral)
   @tparam C callable type
-  @tparam P partitioner type (default tf::GuidedPartitioner)
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first index of the beginning (inclusive)
   @param last index of the end (exclusive)
@@ -389,9 +390,9 @@ class FlowBuilder {
 
   Please refer to @ref ParallelIterations for details.
   */
-  template <typename B, typename E, typename S, typename C, typename P = GuidedPartitioner>
+  template <typename B, typename E, typename S, typename C, typename P = DefaultPartitioner>
   Task for_each_index(
-    B first, E last, S step, C callable, P&& part = P()
+    B first, E last, S step, C callable, P part = P()
   );
 
   // ------------------------------------------------------------------------
@@ -405,7 +406,7 @@ class FlowBuilder {
   @tparam E ending input iterator type
   @tparam O output iterator type
   @tparam C callable type
-  @tparam P partitioner type (default tf::GuidedPartitioner)
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first1 iterator to the beginning of the first range
   @param last1 iterator to the end of the first range
@@ -432,9 +433,10 @@ class FlowBuilder {
   Please refer to @ref ParallelTransforms for details.
   */
   template <
-    typename B, typename E, typename O, typename C, typename P = GuidedPartitioner
+    typename B, typename E, typename O, typename C, typename P = DefaultPartitioner,
+    std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>* = nullptr
   >
-  Task transform(B first1, E last1, O d_first, C c, P&& part = P());
+  Task transform(B first1, E last1, O d_first, C c, P part = P());
   
   /**
   @brief constructs a parallel-transform task
@@ -444,7 +446,7 @@ class FlowBuilder {
   @tparam B2 beginning input iterator type for the first second range
   @tparam O output iterator type
   @tparam C callable type
-  @tparam P partitioner type (default tf::GuidedPartitioner)
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first1 iterator to the beginning of the first input range
   @param last1 iterator to the end of the first input range
@@ -472,10 +474,10 @@ class FlowBuilder {
   Please refer to @ref ParallelTransforms for details.
   */
   template <
-    typename B1, typename E1, typename B2, typename O, typename C, typename P=GuidedPartitioner,
+    typename B1, typename E1, typename B2, typename O, typename C, typename P=DefaultPartitioner,
     std::enable_if_t<!is_partitioner_v<std::decay_t<C>>, void>* = nullptr
   >
-  Task transform(B1 first1, E1 last1, B2 first2, O d_first, C c, P&& part = P());
+  Task transform(B1 first1, E1 last1, B2 first2, O d_first, C c, P part = P());
   
   // ------------------------------------------------------------------------
   // reduction
@@ -488,7 +490,7 @@ class FlowBuilder {
   @tparam E ending iterator type
   @tparam T result type
   @tparam O binary reducer type
-  @tparam P partitioner type (default tf::GuidedPartitioner)
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first iterator to the beginning (inclusive)
   @param last iterator to the end (exclusive)
@@ -513,11 +515,11 @@ class FlowBuilder {
 
   Please refer to @ref ParallelReduction for details.
   */
-  template <typename B, typename E, typename T, typename O, typename P = GuidedPartitioner>
-  Task reduce(B first, E last, T& init, O bop, P&& part = P());
+  template <typename B, typename E, typename T, typename O, typename P = DefaultPartitioner>
+  Task reduce(B first, E last, T& init, O bop, P part = P());
   
   // ------------------------------------------------------------------------
-  // transfrom and reduction
+  // transform and reduction
   // ------------------------------------------------------------------------
 
   /**
@@ -527,8 +529,8 @@ class FlowBuilder {
   @tparam E ending iterator type
   @tparam T result type
   @tparam BOP binary reducer type
-  @tparam UOP unary transformion type
-  @tparam P partitioner type (default tf::GuidedPartitioner)
+  @tparam UOP unary transformation type
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first iterator to the beginning (inclusive)
   @param last iterator to the end (exclusive)
@@ -555,10 +557,56 @@ class FlowBuilder {
   Please refer to @ref ParallelReduction for details.
   */
   template <
-   typename B, typename E, typename T, typename BOP, typename UOP, typename P = GuidedPartitioner
+    typename B, typename E, typename T, typename BOP, typename UOP, typename P = DefaultPartitioner,
+    std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>* = nullptr
   >
-  Task transform_reduce(B first, E last, T& init, BOP bop, UOP uop, P&& part = P());
+  Task transform_reduce(B first, E last, T& init, BOP bop, UOP uop, P part = P());
+
+  /**
+  @brief constructs an STL-styled parallel transform-reduce task
+  @tparam B1 first beginning iterator type
+  @tparam E1 first ending iterator type
+  @tparam B2 second beginning iterator type
+  @tparam T result type
+  @tparam BOP_R binary reducer type
+  @tparam BOP_T binary transformation type
+  @tparam P partitioner type (default tf::DefaultPartitioner)
+  @param first1 iterator to the beginning of the first range (inclusive)
+  @param last1 iterator to the end of the first range (exclusive)
+  @param first2 iterator to the beginning of the second range
+  @param init initial value of the reduction and the storage for the reduced result
+  @param bop_r binary operator that will be applied in unspecified order to the results of @c bop_t
+  @param bop_t binary operator that will be applied to transform each element in the range to the result type
+  @param part partitioning algorithm to schedule parallel iterations
+  @return a tf::Task handle
+  The task spawns asynchronous tasks to perform parallel reduction over @c init and
+  transformed elements in the range <tt>[first, last)</tt>.
+  The reduced result is store in @c init.
+  This method is equivalent to the parallel execution of the following loop:
+  @code{.cpp}
+  for(auto itr1=first1, itr2=first2; itr1!=last1; itr1++, itr2++) {
+    init = bop_r(init, bop_t(*itr1, *itr2));
+  }
+  @endcode
+  Iterators are templated to enable stateful range using std::reference_wrapper.
+
+  Please refer to @ref ParallelReduction for details.
+  */
   
+  template <
+    typename B1, typename E1, typename B2, typename T, typename BOP_R, typename BOP_T, 
+    typename P = DefaultPartitioner,
+    std::enable_if_t<!is_partitioner_v<std::decay_t<BOP_T>>, void>* = nullptr
+  >
+  Task transform_reduce(
+    B1 first1, E1 last1, B2 first2, T& init, BOP_R bop_r, BOP_T bop_t, P part = P()
+  );
+
   // ------------------------------------------------------------------------
   // scan
   // ------------------------------------------------------------------------
@@ -570,11 +618,13 @@ class FlowBuilder {
   @tparam E ending iterator type
   @tparam D destination iterator type
   @tparam BOP summation operator type
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first start of input range
   @param last end of input range
   @param d_first start of output range (may be the same as input range)
   @param bop function to perform summation
+  @param part partitioning algorithm to schedule parallel iterations
 
   Performs the cumulative sum (aka prefix sum, aka scan) of the input range
   and writes the result to the output range. 
@@ -600,8 +650,10 @@ class FlowBuilder {
   
   Please refer to @ref ParallelScan for details.
   */
-  template <typename B, typename E, typename D, typename BOP>
-  Task inclusive_scan(B first, E last, D d_first, BOP bop);
+  template <typename B, typename E, typename D, typename BOP, typename P = DefaultPartitioner,
+    std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>* = nullptr
+  >
+  Task inclusive_scan(B first, E last, D d_first, BOP bop, P part = P());
   
   /**
   @brief creates an STL-styled parallel inclusive-scan task with an initial value
@@ -611,12 +663,14 @@ class FlowBuilder {
   @tparam D destination iterator type
   @tparam BOP summation operator type
   @tparam T initial value type
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first start of input range
   @param last end of input range
   @param d_first start of output range (may be the same as input range)
   @param bop function to perform summation
   @param init initial value
+  @param part partitioning algorithm to schedule parallel iterations
 
   Performs the cumulative sum (aka prefix sum, aka scan) of the input range
   and writes the result to the output range. 
@@ -643,8 +697,10 @@ class FlowBuilder {
   Please refer to @ref ParallelScan for details.
 
   */
-  template <typename B, typename E, typename D, typename BOP, typename T>
-  Task inclusive_scan(B first, E last, D d_first, BOP bop, T init);
+  template <typename B, typename E, typename D, typename BOP, typename T, typename P = DefaultPartitioner,
+    std::enable_if_t<!is_partitioner_v<std::decay_t<T>>, void>* = nullptr
+  >
+  Task inclusive_scan(B first, E last, D d_first, BOP bop, T init, P part = P());
   
   /**
   @brief creates an STL-styled parallel exclusive-scan task
@@ -654,12 +710,14 @@ class FlowBuilder {
   @tparam D destination iterator type
   @tparam T initial value type
   @tparam BOP summation operator type
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first start of input range
   @param last end of input range
   @param d_first start of output range (may be the same as input range)
   @param init initial value
   @param bop function to perform summation
+  @param part partitioning algorithm to schedule parallel iterations
 
   Performs the cumulative sum (aka prefix sum, aka scan) of the input range
   and writes the result to the output range. 
@@ -685,8 +743,8 @@ class FlowBuilder {
   
   Please refer to @ref ParallelScan for details.
   */
-  template <typename B, typename E, typename D, typename T, typename BOP>
-  Task exclusive_scan(B first, E last, D d_first, T init, BOP bop);
+  template <typename B, typename E, typename D, typename T, typename BOP, typename P = DefaultPartitioner>
+  Task exclusive_scan(B first, E last, D d_first, T init, BOP bop, P part = P());
   
   // ------------------------------------------------------------------------
   // transform scan
@@ -700,12 +758,14 @@ class FlowBuilder {
   @tparam D destination iterator type
   @tparam BOP summation operator type
   @tparam UOP transform operator type
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first start of input range
   @param last end of input range
   @param d_first start of output range (may be the same as input range)
   @param bop function to perform summation
   @param uop function to transform elements of the input range
+  @param part partitioning algorithm to schedule parallel iterations
 
   Write the cumulative sum (aka prefix sum, aka scan) of the input range
   to the output range. Each element of the output range contains the
@@ -732,8 +792,10 @@ class FlowBuilder {
   
   Please refer to @ref ParallelScan for details.
   */
-  template <typename B, typename E, typename D, typename BOP, typename UOP>
-  Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop);
+  template <typename B, typename E, typename D, typename BOP, typename UOP, typename P = DefaultPartitioner,
+    std::enable_if_t<is_partitioner_v<std::decay_t<P>>, void>* = nullptr
+  >
+  Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop, P part = P());
   
   /**
   @brief creates an STL-styled parallel transform-inclusive scan task
@@ -744,6 +806,7 @@ class FlowBuilder {
   @tparam BOP summation operator type
   @tparam UOP transform operator type
   @tparam T initial value type
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first start of input range
   @param last end of input range
@@ -751,6 +814,7 @@ class FlowBuilder {
   @param bop function to perform summation
   @param uop function to transform elements of the input range
   @param init initial value
+  @param part partitioning algorithm to schedule parallel iterations
 
   Write the cumulative sum (aka prefix sum, aka scan) of the input range
   to the output range. Each element of the output range contains the
@@ -778,8 +842,10 @@ class FlowBuilder {
   
   Please refer to @ref ParallelScan for details.
   */
-  template <typename B, typename E, typename D, typename BOP, typename UOP, typename T>
-  Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop, T init);
+  template <typename B, typename E, typename D, typename BOP, typename UOP, typename T, typename P = DefaultPartitioner,
+    std::enable_if_t<!is_partitioner_v<std::decay_t<T>>, void>* = nullptr
+  >
+  Task transform_inclusive_scan(B first, E last, D d_first, BOP bop, UOP uop, T init, P part = P());
   
   /**
   @brief creates an STL-styled parallel transform-exclusive scan task
@@ -790,6 +856,7 @@ class FlowBuilder {
   @tparam BOP summation operator type
   @tparam UOP transform operator type
   @tparam T initial value type
+  @tparam P partitioner type (default tf::DefaultPartitioner)
 
   @param first start of input range
   @param last end of input range
@@ -797,6 +864,7 @@ class FlowBuilder {
   @param bop function to perform summation
   @param uop function to transform elements of the input range
   @param init initial value
+  @param part partitioning algorithm to schedule parallel iterations
 
   Write the cumulative sum (aka prefix sum, aka scan) of the input range
   to the output range. Each element of the output range contains the
@@ -823,8 +891,8 @@ class FlowBuilder {
   
   Please refer to @ref ParallelScan for details.
   */
-  template <typename B, typename E, typename D, typename T, typename BOP, typename UOP>
-  Task transform_exclusive_scan(B first, E last, D d_first, T init, BOP bop, UOP uop);
+  template <typename B, typename E, typename D, typename T, typename BOP, typename UOP, typename P = DefaultPartitioner>
+  Task transform_exclusive_scan(B first, E last, D d_first, T init, BOP bop, UOP uop, P part = P());
 
   // ------------------------------------------------------------------------
   // find
@@ -843,7 +911,7 @@ class FlowBuilder {
   @param last end of the input range
   @param result resulting iterator to the found element in the input range
   @param predicate unary predicate which returns @c true for the required element
-  @param part partitioning algorithm (default tf::GuidedPartitioner)
+  @param part partitioning algorithm (default tf::DefaultPartitioner)
 
   Returns an iterator to the first element in the range <tt>[first, last)</tt> 
   that satisfies the given criteria (or last if there is no such iterator).
@@ -875,9 +943,9 @@ class FlowBuilder {
   
   Iterators are templated to enable stateful range using std::reference_wrapper.
   */
-  template <typename B, typename E, typename T, typename UOP, typename P = GuidedPartitioner>
-  Task find_if(B first, E last, T& result, UOP predicate, P&& part = P());
-  
+  template <typename B, typename E, typename T, typename UOP, typename P = DefaultPartitioner>
+  Task find_if(B first, E last, T &result, UOP predicate, P part = P());
+
   /**
   @brief constructs a task to perform STL-styled find-if-not algorithm
 
@@ -891,7 +959,7 @@ class FlowBuilder {
   @param last end of the input range
   @param result resulting iterator to the found element in the input range
   @param predicate unary predicate which returns @c false for the required element
-  @param part partitioning algorithm (default tf::GuidedPartitioner)
+  @param part partitioning algorithm (default tf::DefaultPartitioner)
 
   Returns an iterator to the first element in the range <tt>[first, last)</tt> 
   that satisfies the given criteria (or last if there is no such iterator).
@@ -923,8 +991,8 @@ class FlowBuilder {
   
   Iterators are templated to enable stateful range using std::reference_wrapper.
   */
-  template <typename B, typename E, typename T, typename UOP,typename P = GuidedPartitioner>
-  Task find_if_not(B first, E last, T& result, UOP predicate, P&& part = P());
+  template <typename B, typename E, typename T, typename UOP, typename P = DefaultPartitioner>
+  Task find_if_not(B first, E last, T &result, UOP predicate, P part = P());
 
   /**
   @brief constructs a task to perform STL-styled min-element algorithm
@@ -939,7 +1007,7 @@ class FlowBuilder {
   @param last end of the input range
   @param result resulting iterator to the found element in the input range
   @param comp comparison function object
-  @param part partitioning algorithm (default tf::GuidedPartitioner)
+  @param part partitioning algorithm (default tf::DefaultPartitioner)
 
   Finds the smallest element in the <tt>[first, last)</tt> 
   using the given comparison function object.
@@ -976,7 +1044,7 @@ class FlowBuilder {
   Iterators are templated to enable stateful range using std::reference_wrapper.
   */
   template <typename B, typename E, typename T, typename C, typename P>
-  Task min_element(B first, E last, T& result, C comp, P&& part);
+  Task min_element(B first, E last, T& result, C comp, P part);
   
   /**
   @brief constructs a task to perform STL-styled max-element algorithm
@@ -991,7 +1059,7 @@ class FlowBuilder {
   @param last end of the input range
   @param result resulting iterator to the found element in the input range
   @param comp comparison function object
-  @param part partitioning algorithm (default tf::GuidedPartitioner)
+  @param part partitioning algorithm (default tf::DefaultPartitioner)
 
   Finds the largest element in the <tt>[first, last)</tt> 
   using the given comparison function object.
@@ -1028,7 +1096,7 @@ class FlowBuilder {
   Iterators are templated to enable stateful range using std::reference_wrapper.
   */
   template <typename B, typename E, typename T, typename C, typename P>
-  Task max_element(B first, E last, T& result, C comp, P&& part);
+  Task max_element(B first, E last, T& result, C comp, P part);
 
   // ------------------------------------------------------------------------
   // sort
@@ -1065,7 +1133,7 @@ class FlowBuilder {
   @param first iterator to the beginning (inclusive)
   @param last iterator to the end (exclusive)
 
-  The task spawns asynchronous tasks to parallelly sort elements in the range
+  The task spawns asynchronous tasks to parallel sort elements in the range
   <tt>[first, last)</tt> using the @c std::less<T> comparator,
   where @c T is the dereferenced iterator type.
 
@@ -1103,10 +1171,10 @@ Task FlowBuilder::emplace(C&& c) {
 }
 
 // Function: emplace
-template <typename C, std::enable_if_t<is_dynamic_task_v<C>, void>*>
+template <typename C, std::enable_if_t<is_subflow_task_v<C>, void>*>
 Task FlowBuilder::emplace(C&& c) {
   return Task(_graph._emplace_back("", 0, nullptr, nullptr, 0,
-    std::in_place_type_t<Node::Dynamic>{}, std::forward<C>(c)
+    std::in_place_type_t<Node::Subflow>{}, std::forward<C>(c)
   ));
 }
 
similarity index 77%
rename from bundled/taskflow-3.6.0/include/taskflow/core/graph.hpp
rename to bundled/taskflow-3.7.0/taskflow/core/graph.hpp
index 475422d0822d7afd9cd59088ca060c349c180f8d..efaa4ffc4c70bf1841268b42cae0ca144ef63233 100644 (file)
@@ -25,6 +25,7 @@ namespace tf {
 // Class: Graph
 // ----------------------------------------------------------------------------
 
+
 /**
 @class Graph
 
@@ -147,6 +148,14 @@ class Runtime {
   friend class FlowBuilder;
 
   public:
+  
+  /**
+  @brief destroys the runtime object
+
+  Issues a tf::Runtime::corun_all to finish all spawned asynchronous tasks
+  and then destroys the runtime object.
+  */
+  ~Runtime();
 
   /**
   @brief obtains the running executor
@@ -211,9 +220,9 @@ class Runtime {
   The method creates an asynchronous task to launch the given
   function on the given arguments.
   The difference to tf::Executor::async is that the created asynchronous task
-  pertains to the runtime.
-  When the runtime joins, all asynchronous tasks created from the runtime
-  are guaranteed to finish after the join returns.
+  pertains to the runtime object.
+  Applications can explicitly issue tf::Runtime::corun_all
+  to wait for all spawned asynchronous tasks to finish.
   For example:
 
   @code{.cpp}
@@ -230,8 +239,8 @@ class Runtime {
       rt.async([&](){ counter++; });
     }
     
-    // explicit join 100 asynchronous tasks
-    rt.join();
+    // wait for the 100 asynchronous tasks to finish
+    rt.corun_all();
     assert(counter == 102);
   });
   @endcode
@@ -254,8 +263,8 @@ class Runtime {
       });
     }
     
-    // explicit join 100 asynchronous tasks
-    rt.join();
+    // wait for the 200 asynchronous tasks to finish
+    rt.corun_all();
     assert(counter == 200);
   });
   @endcode
@@ -264,11 +273,12 @@ class Runtime {
   auto async(F&& f);
   
   /**
-  @brief similar to tf::Runtime::async but assigns the task a name
+  @brief runs the given callable asynchronously
 
   @tparam F callable type
+  @tparam P task parameters type
 
-  @param name assigned name to the task
+  @param params task parameters
   @param f callable
 
   @code{.cpp}
@@ -279,8 +289,8 @@ class Runtime {
   @endcode
 
   */
-  template <typename F>
-  auto async(const std::string& name, F&& f);
+  template <typename P, typename F>
+  auto async(P&& params, F&& f);
 
   /**
   @brief runs the given function asynchronously without returning any future object
@@ -297,7 +307,7 @@ class Runtime {
     for(int i=0; i<100; i++) {
       rt.silent_async([&](){ counter++; });
     }
-    rt.join();
+    rt.corun_all();
     assert(counter == 100);
   });
   @endcode
@@ -308,28 +318,50 @@ class Runtime {
   void silent_async(F&& f);
   
   /**
-  @brief similar to tf::Runtime::silent_async but assigns the task a name
+  @brief runs the given function asynchronously without returning any future object
 
   @tparam F callable type
-  @param name assigned name to the task
+  @param params task parameters
   @param f callable
   
   @code{.cpp}
   taskflow.emplace([&](tf::Runtime& rt){
     rt.silent_async("my task", [](){});
-    rt.join();
+    rt.corun_all();
+  });
+  @endcode
+  */
+  template <typename P, typename F>
+  void silent_async(P&& params, F&& f);
+  
+  /**
+  @brief similar to tf::Runtime::silent_async but the caller must be the worker of the runtime
+
+  @tparam F callable type
+
+  @param f callable
+
+  The method bypass the check of the caller worker from the executor 
+  and thus can only called by the worker of this runtime.
+
+  @code{.cpp}
+  taskflow.emplace([&](tf::Runtime& rt){
+    // running by the worker of this runtime
+    rt.silent_async_unchecked([](){});
+    rt.corun_all();
   });
   @endcode
   */
   template <typename F>
-  void silent_async(const std::string& name, F&& f);
+  void silent_async_unchecked(F&& f);
   
   /**
   @brief similar to tf::Runtime::silent_async but the caller must be the worker of the runtime
 
   @tparam F callable type
+  @tparam P task parameters type
 
-  @param name assigned name to the task
+  @param params task parameters
   @param f callable
 
   The method bypass the check of the caller worker from the executor 
@@ -339,18 +371,18 @@ class Runtime {
   taskflow.emplace([&](tf::Runtime& rt){
     // running by the worker of this runtime
     rt.silent_async_unchecked("my task", [](){});
-    rt.join();
+    rt.corun_all();
   });
   @endcode
   */
-  template <typename F>
-  void silent_async_unchecked(const std::string& name, F&& f);
+  template <typename P, typename F>
+  void silent_async_unchecked(P&& params, F&& f);
 
   /**
   @brief co-runs the given target and waits until it completes
   
   A target can be one of the following forms:
-    + a dynamic task to spawn a subflow or
+    + a subflow task to spawn a subflow or
     + a composable graph object with `tf::Graph& T::graph()` defined
 
   @code{.cpp}
@@ -376,6 +408,9 @@ class Runtime {
   the caller thread (worker) is not blocked (e.g., sleeping or holding any lock). 
   Instead, the caller thread joins the work-stealing loop of the executor 
   and returns when all tasks in the target completes.
+  
+  @attention
+  Only the worker of this tf::Runtime can issue corun.
   */
   template <typename T>
   void corun(T&& target);
@@ -388,41 +423,43 @@ class Runtime {
 
   The method keeps the caller worker running in the work-stealing loop
   until the stop predicate becomes true.
+  
+  @attention
+  Only the worker of this tf::Runtime can issue corun.
   */
   template <typename P>
   void corun_until(P&& predicate);
   
   /**
-  @brief joins all asynchronous tasks spawned by this runtime
+  @brief corun all asynchronous tasks spawned by this runtime with other workers
 
-  Immediately joins all asynchronous tasks (tf::Runtime::async,
-  tf::Runtime::silent_async).
-  Unlike tf::Subflow::join, you can join multiples times from
-  a tf::Runtime object.
+  Coruns all asynchronous tasks (tf::Runtime::async,
+  tf::Runtime::silent_async) with other workers until all those 
+  asynchronous tasks finish.
     
   @code{.cpp}
   std::atomic<size_t> counter{0};
   taskflow.emplace([&](tf::Runtime& rt){
-    // spawn 100 async tasks and join
+    // spawn 100 async tasks and wait
     for(int i=0; i<100; i++) {
       rt.silent_async([&](){ counter++; });
     }
-    rt.join();
+    rt.corun_all();
     assert(counter == 100);
     
-    // spawn another 100 async tasks and join
+    // spawn another 100 async tasks and wait
     for(int i=0; i<100; i++) {
       rt.silent_async([&](){ counter++; });
     }
-    rt.join();
+    rt.corun_all();
     assert(counter == 200);
   });
   @endcode
 
   @attention
-  Only the worker of this tf::Runtime can issue join.
+  Only the worker of this tf::Runtime can issue tf::Runtime::corun_all.
   */
-  inline void join();
+  inline void corun_all();
 
   /**
   @brief acquire a reference to the underlying worker
@@ -454,14 +491,14 @@ class Runtime {
   /**
   @private
   */
-  template <typename F>
-  auto _async(Worker& w, const std::string& name, F&& f);
+  template <typename P, typename F>
+  auto _async(Worker& w, P&& params, F&& f);
   
   /**
   @private
   */
-  template <typename F>
-  void _silent_async(Worker& w, const std::string& name, F&& f);
+  template <typename P, typename F>
+  void _silent_async(Worker& w, P&& params, F&& f);
 };
 
 // constructor
@@ -481,6 +518,54 @@ inline Worker& Runtime::worker() {
   return _worker;
 }
 
+// ----------------------------------------------------------------------------
+// TaskParams
+// ----------------------------------------------------------------------------
+
+/**
+@struct TaskParams
+
+@brief task parameters to use when creating an asynchronous task
+*/
+struct TaskParams {
+  /**
+  @brief name of the task
+  */
+  std::string name;
+
+  /**
+  @brief priority of the tassk
+  */
+  unsigned priority {0};
+
+  /**
+  @brief C-styled pointer to user data
+  */
+  void* data {nullptr};
+};
+
+/**
+@struct DefaultTaskParams
+
+@brief empty task parameter type for compile-time optimization
+*/
+struct DefaultTaskParams {
+};
+
+/**
+@brief determines if the given type is a task parameter type
+
+Task parameters can be specified in one of the following types:
+  + tf::TaskParams: assign the struct of defined parameters
+  + tf::DefaultTaskParams: assign nothing
+  + std::string: assign a name to the task
+*/
+template <typename P>
+constexpr bool is_task_params_v =
+  std::is_same_v<std::decay_t<P>, TaskParams> ||
+  std::is_same_v<std::decay_t<P>, DefaultTaskParams> ||
+  std::is_constructible_v<std::string, P>;
+
 // ----------------------------------------------------------------------------
 // Node
 // ----------------------------------------------------------------------------
@@ -492,6 +577,7 @@ class Node {
 
   friend class Graph;
   friend class Task;
+  friend class AsyncTask;
   friend class TaskView;
   friend class Taskflow;
   friend class Executor;
@@ -512,6 +598,7 @@ class Node {
   constexpr static int DETACHED    = 2;
   constexpr static int ACQUIRED    = 4;
   constexpr static int READY       = 8;
+  constexpr static int EXCEPTION   = 16;
 
   using Placeholder = std::monostate;
 
@@ -526,13 +613,13 @@ class Node {
     > work;
   };
 
-  // dynamic work handle
-  struct Dynamic {
+  // subflow work handle
+  struct Subflow {
 
     template <typename C>
-    Dynamic(C&&);
+    Subflow(C&&);
 
-    std::function<void(Subflow&)> work;
+    std::function<void(tf::Subflow&)> work;
     Graph subgraph;
   };
 
@@ -573,7 +660,9 @@ class Node {
     template <typename T>
     Async(T&&);
 
-    std::function<void()> work;
+    std::variant<
+      std::function<void()>, std::function<void(Runtime&)>
+    > work;
   };
   
   // silent dependent async
@@ -582,20 +671,23 @@ class Node {
     template <typename C>
     DependentAsync(C&&);
     
-    std::function<void()> work;
-    
+    std::variant<
+      std::function<void()>, std::function<void(Runtime&)>
+    > work;
+   
+    std::atomic<size_t> use_count {1};
     std::atomic<AsyncState> state {AsyncState::UNFINISHED};
   };
 
   using handle_t = std::variant<
     Placeholder,      // placeholder
     Static,           // static tasking
-    Dynamic,          // dynamic tasking
+    Subflow,          // subflow tasking
     Condition,        // conditional tasking
     MultiCondition,   // multi-conditional tasking
     Module,           // composable tasking
     Async,            // async tasking
-    DependentAsync    // dependent async tasking (no future)
+    DependentAsync    // dependent async tasking
   >;
 
   struct Semaphores {
@@ -608,7 +700,7 @@ class Node {
   // variant index
   constexpr static auto PLACEHOLDER     = get_index_v<Placeholder, handle_t>;
   constexpr static auto STATIC          = get_index_v<Static, handle_t>;
-  constexpr static auto DYNAMIC         = get_index_v<Dynamic, handle_t>;
+  constexpr static auto SUBFLOW         = get_index_v<Subflow, handle_t>;
   constexpr static auto CONDITION       = get_index_v<Condition, handle_t>;
   constexpr static auto MULTI_CONDITION = get_index_v<MultiCondition, handle_t>;
   constexpr static auto MODULE          = get_index_v<Module, handle_t>;
@@ -618,7 +710,16 @@ class Node {
   Node() = default;
 
   template <typename... Args>
-  Node(const std::string&, unsigned, Topology*, Node*, size_t, Args&&... args);
+  Node(const std::string&, unsigned, Topology*, Node*, size_t, Args&&...);
+  
+  template <typename... Args>
+  Node(const std::string&, Topology*, Node*, size_t, Args&&...);
+  
+  template <typename... Args>
+  Node(const TaskParams&, Topology*, Node*, size_t, Args&&...);
+  
+  template <typename... Args>
+  Node(const DefaultTaskParams&, Topology*, Node*, size_t, Args&&...);
 
   ~Node();
 
@@ -635,11 +736,11 @@ class Node {
   
   unsigned _priority {0};
   
+  void* _data {nullptr};
+  
   Topology* _topology {nullptr};
   Node* _parent {nullptr};
 
-  void* _data {nullptr};
-
   SmallVector<Node*> _successors;
   SmallVector<Node*> _dependents;
 
@@ -647,11 +748,13 @@ class Node {
   std::atomic<size_t> _join_counter {0};
 
   std::unique_ptr<Semaphores> _semaphores;
+  std::exception_ptr _exception_ptr {nullptr};
   
   handle_t _handle;
 
   void _precede(Node*);
   void _set_up_join_counter();
+  void _process_exception();
 
   bool _is_cancelled() const;
   bool _is_conditioner() const;
@@ -679,12 +782,12 @@ Node::Static::Static(C&& c) : work {std::forward<C>(c)} {
 }
 
 // ----------------------------------------------------------------------------
-// Definition for Node::Dynamic
+// Definition for Node::Subflow
 // ----------------------------------------------------------------------------
 
 // Constructor
 template <typename C>
-Node::Dynamic::Dynamic(C&& c) : work {std::forward<C>(c)} {
+Node::Subflow::Subflow(C&& c) : work {std::forward<C>(c)} {
 }
 
 // ----------------------------------------------------------------------------
@@ -754,19 +857,65 @@ Node::Node(
   _handle       {std::forward<Args>(args)...} {
 }
 
-//Node::Node(Args&&... args): _handle{std::forward<Args>(args)...} {
-//}
+// Constructor
+template <typename... Args>
+Node::Node(
+  const std::string& name,
+  Topology* topology, 
+  Node* parent, 
+  size_t join_counter,
+  Args&&... args
+) :
+  _name         {name},
+  _topology     {topology},
+  _parent       {parent},
+  _join_counter {join_counter},
+  _handle       {std::forward<Args>(args)...} {
+}
+
+// Constructor
+template <typename... Args>
+Node::Node(
+  const TaskParams& params,
+  Topology* topology, 
+  Node* parent, 
+  size_t join_counter,
+  Args&&... args
+) :
+  _name         {params.name},
+  _priority     {params.priority},
+  _data         {params.data},
+  _topology     {topology},
+  _parent       {parent},
+  _join_counter {join_counter},
+  _handle       {std::forward<Args>(args)...} {
+}
+
+// Constructor
+template <typename... Args>
+Node::Node(
+  const DefaultTaskParams&,
+  Topology* topology, 
+  Node* parent, 
+  size_t join_counter,
+  Args&&... args
+) :
+  _topology     {topology},
+  _parent       {parent},
+  _join_counter {join_counter},
+  _handle       {std::forward<Args>(args)...} {
+}
 
 // Destructor
 inline Node::~Node() {
   // this is to avoid stack overflow
 
-  if(_handle.index() == DYNAMIC) {
+  if(_handle.index() == SUBFLOW) {
     // using std::get_if instead of std::get makes this compatible
     // with older macOS versions
     // the result of std::get_if is guaranteed to be non-null
     // due to the index check above
-    auto& subgraph = std::get_if<Dynamic>(&_handle)->subgraph;
+    auto& subgraph = std::get_if<Subflow>(&_handle)->subgraph;
     std::vector<Node*> nodes;
     nodes.reserve(subgraph.size());
 
@@ -779,8 +928,8 @@ inline Node::~Node() {
 
     while(i < nodes.size()) {
 
-      if(nodes[i]->_handle.index() == DYNAMIC) {
-        auto& sbg = std::get_if<Dynamic>(&(nodes[i]->_handle))->subgraph;
+      if(nodes[i]->_handle.index() == SUBFLOW) {
+        auto& sbg = std::get_if<Subflow>(&(nodes[i]->_handle))->subgraph;
         std::move(
           sbg._nodes.begin(), sbg._nodes.end(), std::back_inserter(nodes)
         );
@@ -849,8 +998,11 @@ inline bool Node::_is_conditioner() const {
 }
 
 // Function: _is_cancelled
+// we currently only support cancellation of taskflow (no async task)
 inline bool Node::_is_cancelled() const {
-  return _topology && _topology->_is_cancelled.load(std::memory_order_relaxed);
+  //return _topology && _topology->_is_cancelled.load(std::memory_order_relaxed);
+  return _topology &&
+         (_topology->_state.load(std::memory_order_relaxed) & Topology::CANCELLED);
 }
 
 // Procedure: _set_up_join_counter
@@ -865,9 +1017,17 @@ inline void Node::_set_up_join_counter() {
       c++;
     }
   }
-  _join_counter.store(c, std::memory_order_release);
+  _join_counter.store(c, std::memory_order_relaxed);
 }
 
+// Procedure: _process_exception
+inline void Node::_process_exception() {
+  if(_exception_ptr) {
+    auto e = _exception_ptr;
+    _exception_ptr = nullptr;
+    std::rethrow_exception(e);
+  }
+}
 
 // Function: _acquire_all
 inline bool Node::_acquire_all(SmallVector<Node*>& nodes) {
@@ -995,4 +1155,15 @@ Node* Graph::_emplace_back(ArgsT&&... args) {
   return _nodes.back();
 }
 
+
 }  // end of namespace tf. ---------------------------------------------------
+
+
+
+
+
+
+
+
+
+
similarity index 87%
rename from bundled/taskflow-3.6.0/include/taskflow/core/notifier.hpp
rename to bundled/taskflow-3.7.0/taskflow/core/notifier.hpp
index 39bcf6495f47728bba2e6d832fdad3b5bd4745e1..61663798a0c4f999bbe0d769453096a975b10421 100644 (file)
@@ -70,15 +70,20 @@ class Notifier {
 
   struct Waiter {
     std::atomic<Waiter*> next;
-    std::mutex mu;
-    std::condition_variable cv;
     uint64_t epoch;
-    unsigned state;
-    enum {
-      kNotSignaled,
+    enum : unsigned {
+      kNotSignaled = 0,
       kWaiting,
       kSignaled,
     };
+
+#ifdef __cpp_lib_atomic_wait
+    std::atomic<unsigned> state {0};
+#else
+    std::mutex mu;
+    std::condition_variable cv;
+    unsigned state;
+#endif
   };
 
   explicit Notifier(size_t N) : _waiters{N} {
@@ -101,8 +106,13 @@ class Notifier {
   }
 
   // commit_wait commits waiting.
+  // only the waiter itself can call
   void commit_wait(Waiter* w) {
+#ifdef __cpp_lib_atomic_wait
+    w->state.store(Waiter::kNotSignaled, std::memory_order_relaxed);
+#else
     w->state = Waiter::kNotSignaled;
+#endif
     // Modification epoch of this waiter.
     uint64_t epoch =
         (w->epoch & kEpochMask) +
@@ -137,8 +147,8 @@ class Notifier {
   // cancel_wait cancels effects of the previous prepare_wait call.
   void cancel_wait(Waiter* w) {
     uint64_t epoch =
-        (w->epoch & kEpochMask) +
-        (((w->epoch & kWaiterMask) >> kWaiterShift) << kEpochShift);
+      (w->epoch & kEpochMask) +
+      (((w->epoch & kWaiterMask) >> kWaiterShift) << kEpochShift);
     uint64_t state = _state.load(std::memory_order_relaxed);
     for (;;) {
       if (int64_t((state & kEpochMask) - epoch) < 0) {
@@ -165,8 +175,9 @@ class Notifier {
     uint64_t state = _state.load(std::memory_order_acquire);
     for (;;) {
       // Easy case: no waiters.
-      if ((state & kStackMask) == kStackMask && (state & kWaiterMask) == 0)
+      if ((state & kStackMask) == kStackMask && (state & kWaiterMask) == 0) {
         return;
+      }
       uint64_t waiters = (state & kWaiterMask) >> kWaiterShift;
       uint64_t newstate;
       if (all) {
@@ -181,7 +192,9 @@ class Notifier {
         Waiter* wnext = w->next.load(std::memory_order_relaxed);
         uint64_t next = kStackMask;
         //if (wnext != nullptr) next = wnext - &_waiters[0];
-        if (wnext != nullptr) next = static_cast<uint64_t>(wnext - &_waiters[0]);
+        if (wnext != nullptr) {
+          next = static_cast<uint64_t>(wnext - &_waiters[0]);
+        }
         // Note: we don't add kEpochInc here. ABA problem on the lock-free stack
         // can't happen because a waiter is re-pushed onto the stack only after
         // it was in the pre-wait state which inevitably leads to epoch
@@ -193,7 +206,9 @@ class Notifier {
         if (!all && waiters) return;  // unblocked pre-wait thread
         if ((state & kStackMask) == kStackMask) return;
         Waiter* w = &_waiters[state & kStackMask];
-        if (!all) w->next.store(nullptr, std::memory_order_relaxed);
+        if (!all) {
+          w->next.store(nullptr, std::memory_order_relaxed);
+        }
         _unpark(w);
         return;
       }
@@ -237,17 +252,35 @@ class Notifier {
   std::vector<Waiter> _waiters;
 
   void _park(Waiter* w) {
+#ifdef __cpp_lib_atomic_wait
+    unsigned target = Waiter::kNotSignaled;
+    if(w->state.compare_exchange_strong(target, Waiter::kWaiting,
+                                        std::memory_order_relaxed,
+                                        std::memory_order_relaxed)) {
+      w->state.wait(Waiter::kWaiting, std::memory_order_relaxed);
+    }
+#else
     std::unique_lock<std::mutex> lock(w->mu);
     while (w->state != Waiter::kSignaled) {
       w->state = Waiter::kWaiting;
       w->cv.wait(lock);
     }
+#endif
   }
 
   void _unpark(Waiter* waiters) {
     Waiter* next = nullptr;
     for (Waiter* w = waiters; w; w = next) {
       next = w->next.load(std::memory_order_relaxed);
+#ifdef __cpp_lib_atomic_wait
+      // We only notify if the other is waiting - this is why we use tri-state
+      // variable instead of binary-state variable (i.e., atomic_flag)
+      // Performance is about 0.1% faster
+      if(w->state.exchange(Waiter::kSignaled, std::memory_order_relaxed) == 
+         Waiter::kWaiting) {
+        w->state.notify_one();
+      }
+#else
       unsigned state;
       {
         std::unique_lock<std::mutex> lock(w->mu);
@@ -256,6 +289,7 @@ class Notifier {
       }
       // Avoid notifying if it wasn't waiting.
       if (state == Waiter::kWaiting) w->cv.notify_one();
+#endif
     }
   }
 
similarity index 97%
rename from bundled/taskflow-3.6.0/include/taskflow/core/task.hpp
rename to bundled/taskflow-3.7.0/taskflow/core/task.hpp
index cd10b731178dfdc3e47ba1af2124014e4b3e3bd4..1070671c4097553d46c195531a6d88d1a41cdd8f 100644 (file)
@@ -24,7 +24,7 @@ enum class TaskType : int {
   /** @brief static task type */
   STATIC,
   /** @brief dynamic (subflow) task type */
-  DYNAMIC,
+  SUBFLOW,
   /** @brief condition task type */
   CONDITION,
   /** @brief module task type */
@@ -42,7 +42,7 @@ enum class TaskType : int {
 inline constexpr std::array<TaskType, 6> TASK_TYPES = {
   TaskType::PLACEHOLDER,
   TaskType::STATIC,
-  TaskType::DYNAMIC,
+  TaskType::SUBFLOW,
   TaskType::CONDITION,
   TaskType::MODULE,
   TaskType::ASYNC,
@@ -56,7 +56,7 @@ The name of each task type is the litte-case string of its characters.
 @code{.cpp}
 TaskType::PLACEHOLDER     ->  "placeholder"
 TaskType::STATIC          ->  "static"
-TaskType::DYNAMIC         ->  "subflow"
+TaskType::SUBFLOW         ->  "subflow"
 TaskType::CONDITION       ->  "condition"
 TaskType::MODULE          ->  "module"
 TaskType::ASYNC           ->  "async"
@@ -69,7 +69,7 @@ inline const char* to_string(TaskType type) {
   switch(type) {
     case TaskType::PLACEHOLDER:      val = "placeholder";     break;
     case TaskType::STATIC:           val = "static";          break;
-    case TaskType::DYNAMIC:          val = "subflow";         break;
+    case TaskType::SUBFLOW:          val = "subflow";         break;
     case TaskType::CONDITION:        val = "condition";       break;
     case TaskType::MODULE:           val = "module";          break;
     case TaskType::ASYNC:            val = "async";           break;
@@ -89,7 +89,7 @@ inline const char* to_string(TaskType type) {
 A dynamic task is a callable object constructible from std::function<void(Subflow&)>.
 */
 template <typename C>
-constexpr bool is_dynamic_task_v = 
+constexpr bool is_subflow_task_v = 
   std::is_invocable_r_v<void, C, Subflow&> &&
   !std::is_invocable_r_v<void, C, Runtime&>;
 
@@ -102,7 +102,7 @@ or std::function<int(tf::Runtime&)>.
 template <typename C>
 constexpr bool is_condition_task_v = 
   (std::is_invocable_r_v<int, C> || std::is_invocable_r_v<int, C, Runtime&>) &&
-  !is_dynamic_task_v<C>;
+  !is_subflow_task_v<C>;
 
 /**
 @brief determines if a callable is a multi-condition task
@@ -115,7 +115,7 @@ template <typename C>
 constexpr bool is_multi_condition_task_v =
   (std::is_invocable_r_v<SmallVector<int>, C> ||
   std::is_invocable_r_v<SmallVector<int>, C, Runtime&>) &&
-  !is_dynamic_task_v<C>;
+  !is_subflow_task_v<C>;
 
 /**
 @brief determines if a callable is a static task
@@ -128,7 +128,7 @@ constexpr bool is_static_task_v =
   (std::is_invocable_r_v<void, C> || std::is_invocable_r_v<void, C, Runtime&>) &&
   !is_condition_task_v<C> &&
   !is_multi_condition_task_v<C> &&
-  !is_dynamic_task_v<C>;
+  !is_subflow_task_v<C>;
 
 // ----------------------------------------------------------------------------
 // Task
@@ -307,7 +307,7 @@ class Task {
     @return @c *this
     */
     Task& data(void* data);
-      
+    
     /**
     @brief assigns a priority value to the task
 
@@ -512,7 +512,7 @@ inline TaskType Task::type() const {
   switch(_node->_handle.index()) {
     case Node::PLACEHOLDER:     return TaskType::PLACEHOLDER;
     case Node::STATIC:          return TaskType::STATIC;
-    case Node::DYNAMIC:         return TaskType::DYNAMIC;
+    case Node::SUBFLOW:         return TaskType::SUBFLOW;
     case Node::CONDITION:       return TaskType::CONDITION;
     case Node::MULTI_CONDITION: return TaskType::CONDITION;
     case Node::MODULE:          return TaskType::MODULE;
@@ -558,8 +558,8 @@ Task& Task::work(C&& c) {
   if constexpr(is_static_task_v<C>) {
     _node->_handle.emplace<Node::Static>(std::forward<C>(c));
   }
-  else if constexpr(is_dynamic_task_v<C>) {
-    _node->_handle.emplace<Node::Dynamic>(std::forward<C>(c));
+  else if constexpr(is_subflow_task_v<C>) {
+    _node->_handle.emplace<Node::Subflow>(std::forward<C>(c));
   }
   else if constexpr(is_condition_task_v<C>) {
     _node->_handle.emplace<Node::Condition>(std::forward<C>(c));
@@ -711,7 +711,7 @@ inline TaskType TaskView::type() const {
   switch(_node._handle.index()) {
     case Node::PLACEHOLDER:     return TaskType::PLACEHOLDER;
     case Node::STATIC:          return TaskType::STATIC;
-    case Node::DYNAMIC:         return TaskType::DYNAMIC;
+    case Node::SUBFLOW:         return TaskType::SUBFLOW;
     case Node::CONDITION:       return TaskType::CONDITION;
     case Node::MULTI_CONDITION: return TaskType::CONDITION;
     case Node::MODULE:          return TaskType::MODULE;
@@ -742,7 +742,7 @@ void TaskView::for_each_dependent(V&& visitor) const {
   }
 }
 
-}  // end of namespace tf. ---------------------------------------------------
+}  // end of namespace tf. ----------------------------------------------------
 
 namespace std {
 
similarity index 88%
rename from bundled/taskflow-3.6.0/include/taskflow/core/taskflow.hpp
rename to bundled/taskflow-3.7.0/taskflow/core/taskflow.hpp
index ff836f5fbde72cf0f1044f70da8b49aa4a70586c..f6a0f424a2334c8534437ae74ffa1fb284877c5b 100644 (file)
@@ -22,7 +22,7 @@ dependency between two tasks. A task is one of the following types:
 
   1. static task         : the callable constructible from
                            @c std::function<void()>
-  2. dynamic task        : the callable constructible from
+  2. subflow task        : the callable constructible from
                            @c std::function<void(tf::Subflow&)>
   3. condition task      : the callable constructible from
                            @c std::function<int()>
@@ -246,6 +246,32 @@ class Taskflow : public FlowBuilder {
     template <typename V>
     void for_each_task(V&& visitor) const;
 
+    /**
+    @brief removes dependencies that go from task @c from to task @c to
+
+    @param from from task (dependent)
+    @param to to task (successor)
+  
+    @code{.cpp}
+    tf::Taskflow taskflow;
+    auto a = taskflow.placeholder().name("a");
+    auto b = taskflow.placeholder().name("b");
+    auto c = taskflow.placeholder().name("c");
+    auto d = taskflow.placeholder().name("d");
+
+    a.precede(b, c, d);
+    assert(a.num_successors() == 3);
+    assert(b.num_dependents() == 1);
+    assert(c.num_dependents() == 1);
+    assert(d.num_dependents() == 1);
+  
+    taskflow.remove_dependency(a, b);
+    assert(a.num_successors() == 2);
+    assert(b.num_dependents() == 0);
+    @endcode
+    */
+    inline void remove_dependency(Task from, Task to);
+
     /**
     @brief returns a reference to the underlying graph object
 
@@ -264,7 +290,6 @@ class Taskflow : public FlowBuilder {
     Graph _graph;
 
     std::queue<std::shared_ptr<Topology>> _topologies;
-
     std::optional<std::list<Taskflow>::iterator> _satellite;
 
     void _dump(std::ostream&, const Graph*) const;
@@ -346,6 +371,21 @@ void Taskflow::for_each_task(V&& visitor) const {
   }
 }
 
+// Procedure: remove_dependency
+inline void Taskflow::remove_dependency(Task from, Task to) {
+  from._node->_successors.erase(std::remove_if(
+    from._node->_successors.begin(), from._node->_successors.end(), [&](Node* i){
+      return i == to._node;
+    }
+  ), from._node->_successors.end());
+  
+  to._node->_dependents.erase(std::remove_if(
+    to._node->_dependents.begin(), to._node->_dependents.end(), [&](Node* i){
+      return i == from._node;
+    }
+  ), to._node->_dependents.end());
+}
+
 // Procedure: dump
 inline std::string Taskflow::dump() const {
   std::ostringstream oss;
@@ -429,7 +469,7 @@ inline void Taskflow::_dump(
   }
 
   // subflow join node
-  if(node->_parent && node->_parent->_handle.index() == Node::DYNAMIC &&
+  if(node->_parent && node->_parent->_handle.index() == Node::SUBFLOW &&
      node->_successors.size() == 0
     ) {
     os << 'p' << node << " -> p" << node->_parent << ";\n";
@@ -438,8 +478,8 @@ inline void Taskflow::_dump(
   // node info
   switch(node->_handle.index()) {
 
-    case Node::DYNAMIC: {
-      auto& sbg = std::get_if<Node::Dynamic>(&node->_handle)->subgraph;
+    case Node::SUBFLOW: {
+      auto& sbg = std::get_if<Node::Subflow>(&node->_handle)->subgraph;
       if(!sbg.empty()) {
         os << "subgraph cluster_p" << node << " {\nlabel=\"Subflow: ";
         if(node->_name.empty()) os << 'p' << node;
@@ -502,7 +542,6 @@ inline void Taskflow::_dump(
 
 tf::Future is a derived class from std::future that will eventually hold the
 execution result of a submitted taskflow (tf::Executor::run)
-or an asynchronous task (tf::Executor::async, tf::Executor::silent_async).
 In addition to the base methods inherited from std::future,
 you can call tf::Future::cancel to cancel the execution of the running taskflow
 associated with this future object.
@@ -536,10 +575,6 @@ class Future : public std::future<T>  {
   friend class Subflow;
   friend class Runtime;
 
-  using handle_t = std::variant<
-    std::monostate, std::weak_ptr<Topology>
-  >;
-
   public:
 
     /**
@@ -582,37 +617,26 @@ class Future : public std::future<T>  {
     bool cancel();
 
   private:
+    
+    std::weak_ptr<Topology> _topology;
 
-    handle_t _handle;
-
-    template <typename P>
-    Future(std::future<T>&&, P&&);
+    Future(std::future<T>&&, std::weak_ptr<Topology> = std::weak_ptr<Topology>());
 };
 
 template <typename T>
-template <typename P>
-Future<T>::Future(std::future<T>&& fu, P&& p) :
-  std::future<T> {std::move(fu)},
-  _handle        {std::forward<P>(p)} {
+Future<T>::Future(std::future<T>&& f, std::weak_ptr<Topology> p) :
+  std::future<T> {std::move(f)},
+  _topology      {std::move(p)} {
 }
 
 // Function: cancel
 template <typename T>
 bool Future<T>::cancel() {
-  return std::visit([](auto&& arg){
-    using P = std::decay_t<decltype(arg)>;
-    if constexpr(std::is_same_v<P, std::monostate>) {
-      return false;
-    }
-    else {
-      auto ptr = arg.lock();
-      if(ptr) {
-        ptr->_is_cancelled.store(true, std::memory_order_relaxed);
-        return true;
-      }
-      return false;
-    }
-  }, _handle);
+  if(auto ptr = _topology.lock(); ptr) {
+    ptr->_state.fetch_or(Topology::CANCELLED, std::memory_order_relaxed);
+    return true;
+  }
+  return false;
 }
 
 
similarity index 57%
rename from bundled/taskflow-3.6.0/include/taskflow/core/topology.hpp
rename to bundled/taskflow-3.7.0/taskflow/core/topology.hpp
index b4d9eab2e050cf1b4b5e76dcc666b4eb47052f2b..335ccfb80a8c9b712ef35d84e09904a255e30863 100644 (file)
@@ -4,33 +4,31 @@ namespace tf {
 
 // ----------------------------------------------------------------------------
 
-// class: TopologyBase
 class TopologyBase {
 
-  friend class Executor;
-  friend class Node;
-
-  template <typename T>
-  friend class Future;
-
-  protected:
-
-  std::atomic<bool> _is_cancelled { false };
 };
 
-// ----------------------------------------------------------------------------
-
 // class: Topology
-class Topology : public TopologyBase {
+class Topology {
 
   friend class Executor;
   friend class Runtime;
+  friend class Node;
+
+  template <typename T>
+  friend class Future;
+  
+  constexpr static int CLEAN = 0;
+  constexpr static int CANCELLED = 1;
+  constexpr static int EXCEPTION = 2;
 
   public:
 
     template <typename P, typename C>
     Topology(Taskflow&, P&&, C&&);
 
+    bool cancelled() const;
+
   private:
 
     Taskflow& _taskflow;
@@ -43,6 +41,11 @@ class Topology : public TopologyBase {
     std::function<void()> _call;
 
     std::atomic<size_t> _join_counter {0};
+    std::atomic<int> _state {CLEAN};
+
+    std::exception_ptr _exception_ptr {nullptr};
+
+    void _carry_out_promise();
 };
 
 // Constructor
@@ -53,4 +56,21 @@ Topology::Topology(Taskflow& tf, P&& p, C&& c):
   _call {std::forward<C>(c)} {
 }
 
+// Procedure
+inline void Topology::_carry_out_promise() {
+  if(_exception_ptr) {
+    auto e = _exception_ptr;
+    _exception_ptr = nullptr;
+    _promise.set_exception(e);
+  }
+  else {
+    _promise.set_value();
+  }
+}
+
+// Function: cancelled
+inline bool Topology::cancelled() const {
+  return _state.load(std::memory_order_relaxed) & CANCELLED;
+}
+
 }  // end of namespace tf. ----------------------------------------------------
similarity index 62%
rename from bundled/taskflow-3.6.0/include/taskflow/core/worker.hpp
rename to bundled/taskflow-3.7.0/taskflow/core/worker.hpp
index 47fcf819d65bfb34deb6122a0643e7e195e50dd3..8f86381a8fe939e546fac6e42de2e9827a7f32b2 100644 (file)
@@ -167,94 +167,6 @@ inline size_t WorkerView::queue_capacity() const {
 }
 
 
-// ----------------------------------------------------------------------------
-// Class Definition: WorkerInterface
-// ----------------------------------------------------------------------------
-
-/**
-@class WorkerInterface
-
-@brief class to configure worker behavior in an executor
-
-The tf::WorkerInterface class lets users interact with the executor
-to customize the worker behavior,
-such as calling custom methods before and after a worker enters and leaves
-the loop.
-When you create an executor, it spawns a set of workers to run tasks.
-The interaction between the executor and its spawned workers looks like
-the following:
-
-for(size_t n=0; n<num_workers; n++) {
-  create_thread([](Worker& worker)
-  
-    // pre-processing executor-specific worker information
-    // ...
-  
-    // enter the scheduling loop
-    // Here, WorkerInterface::scheduler_prologue is invoked, if any
-    
-    while(1) {
-      perform_work_stealing_algorithm();
-      if(stop) {
-        break; 
-      }
-    }
-  
-    // leaves the scheduling loop and joins this worker thread
-    // Here, WorkerInterface::scheduler_epilogue is invoked, if any
-  );
-}
-
-@note
-Methods defined in tf::WorkerInterface are not thread-safe and may be
-be invoked by multiple workers concurrently.
-
-*/
-class WorkerInterface {
-
-  public:
-  
-  /**
-  @brief default destructor
-  */
-  virtual ~WorkerInterface() = default;
-  
-  /**
-  @brief method to call before a worker enters the scheduling loop
-  @param worker a reference to the worker
-
-  The method is called by the constructor of an executor.
-  */
-  virtual void scheduler_prologue(Worker& worker) = 0;
-  
-  /**
-  @brief method to call after a worker leaves the scheduling loop
-  @param worker a reference to the worker
-  @param ptr an pointer to the exception thrown by the scheduling loop
-
-  The method is called by the constructor of an executor.
-  */
-  virtual void scheduler_epilogue(Worker& worker, std::exception_ptr ptr) = 0;
-
-};
-
-/**
-@brief helper function to create an instance derived from tf::WorkerInterface
-
-@tparam T type derived from tf::WorkerInterface
-@tparam ArgsT argument types to construct @c T
-
-@param args arguments to forward to the constructor of @c T
-*/
-template <typename T, typename... ArgsT>
-std::shared_ptr<T> make_worker_interface(ArgsT&&... args) {
-  static_assert(
-    std::is_base_of_v<WorkerInterface, T>, 
-    "T must be derived from WorkerInterface"
-  );
-  return std::make_shared<T>(std::forward<ArgsT>(args)...);
-}
-
 }  // end of namespact tf -----------------------------------------------------
 
 
similarity index 99%
rename from bundled/taskflow-3.6.0/include/taskflow/cuda/algorithm/find.hpp
rename to bundled/taskflow-3.7.0/taskflow/cuda/algorithm/find.hpp
index 9d5b10fd555256fb61b285d6099509e9de7cca40..f3446669509d2fa891f31ffa576216cc3db98e84 100644 (file)
@@ -35,7 +35,6 @@ void cuda_find_if_loop(P&& p, I input, unsigned count, unsigned* idx, U pred) {
 
   // set the index to the maximum
   cuda_single_task(p, [=] __device__ () { *idx = count; });
-ls
 
   // launch the kernel to atomic-find the minimum
   cuda_kernel<<<B, E::nt, 0, p.stream()>>>([=] __device__ (auto tid, auto bid) {
similarity index 98%
rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_graph.hpp
rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_graph.hpp
index f239c8d4311990088f59422a55d069be3a312d98..a326aedeaec7e205e4e7219b506162b52e6cb2bb 100644 (file)
@@ -248,12 +248,12 @@ inline const char* cuda_graph_node_type_to_string(cudaGraphNodeType type) {
 @param graph native CUDA graph
 */
 template <typename T>
-void cuda_dump_graph(T& os, cudaGraph_t graph) {
+void cuda_dump_graph(T& os, cudaGraph_t g) {
 
   os << "digraph cudaGraph {\n";
 
   std::stack<std::tuple<cudaGraph_t, cudaGraphNode_t, int>> stack;
-  stack.push(std::make_tuple(graph, nullptr, 1));
+  stack.push(std::make_tuple(g, nullptr, 1));
 
   int pl = 0;
 
@@ -281,9 +281,9 @@ void cuda_dump_graph(T& os, cudaGraph_t graph) {
       auto type = cuda_get_graph_node_type(node);
       if(type == cudaGraphNodeTypeGraph) {
 
-        cudaGraph_t graph;
-        TF_CHECK_CUDA(cudaGraphChildGraphNodeGetGraph(node, &graph), "");
-        stack.push(std::make_tuple(graph, node, l+1));
+        cudaGraph_t child_graph;
+        TF_CHECK_CUDA(cudaGraphChildGraphNodeGetGraph(node, &child_graph), "");
+        stack.push(std::make_tuple(child_graph, node, l+1));
 
         os << 'p' << node << "["
            << "shape=folder, style=filled, fontcolor=white, fillcolor=purple, "
@@ -635,8 +635,8 @@ cudaFlowNode::Kernel::Kernel(F&& f) :
 
 // Capture handle constructor
 template <typename C>
-cudaFlowNode::Capture::Capture(C&& work) :
-  work {std::forward<C>(work)} {
+cudaFlowNode::Capture::Capture(C&& c) :
+  work {std::forward<C>(c)} {
 }
 
 // Constructor
similarity index 99%
rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_memory.hpp
rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_memory.hpp
index 0740d491e35d864c0eab5a1dcfdb7accb3d922dd..44648683e79edc373a631b3bc0bac9ffc2a4467a 100644 (file)
@@ -498,7 +498,7 @@ class cudaDeviceAllocator {
   @param n number of elements (each of size sizeof(value_type)) to be allocated
   @return a pointer to the initial element in the block of storage.
   */
-  pointer allocate( size_type n, std::allocator<void>::const_pointer = 0 )
+  pointer allocate( size_type n, const void* = 0 )
   {
     void* ptr = NULL;
     TF_CHECK_CUDA(
@@ -694,7 +694,7 @@ class cudaUSMAllocator {
   @param n number of elements (each of size sizeof(value_type)) to be allocated
   @return a pointer to the initial element in the block of storage.
   */
-  pointer allocate( size_type n, std::allocator<void>::const_pointer = 0 )
+  pointer allocate( size_type n, const void* = 0 )
   {
     void* ptr {nullptr};
     TF_CHECK_CUDA(
similarity index 98%
rename from bundled/taskflow-3.6.0/include/taskflow/cuda/cuda_stream.hpp
rename to bundled/taskflow-3.7.0/taskflow/cuda/cuda_stream.hpp
index f3e48f145f03ad130ea42ebcf29a0d8e20308c41..1e312605be86b10953c71a7c2de006efe1979662 100644 (file)
@@ -185,7 +185,9 @@ struct cudaEventCreator {
 */
 struct cudaEventDeleter {
   void operator () (cudaEvent_t event) const {
-    cudaEventDestroy(event);
+    if (event != nullptr) {
+      cudaEventDestroy(event);
+    }
   }
 };
 
similarity index 93%
rename from bundled/taskflow-3.6.0/include/taskflow/taskflow.hpp
rename to bundled/taskflow-3.7.0/taskflow/taskflow.hpp
index 38ac741698f1193835aff1e64afdf243a4f2554f..c2403f81f55404d9857a25a6a12a56a3c8d4585c 100644 (file)
@@ -33,8 +33,8 @@
 // TF_VERSION / 100 % 1000 is the minor version
 // TF_VERSION / 100000 is the major version
 
-// current version: 3.6.0
-#define TF_VERSION 300600
+// current version: 3.7.0
+#define TF_VERSION 300700
 
 #define TF_MAJOR_VERSION TF_VERSION/100000
 #define TF_MINOR_VERSION TF_VERSION/100%1000
@@ -57,7 +57,7 @@ namespace detail { }
 Release notes are available here: https://taskflow.github.io/taskflow/Releases.html
 */
 constexpr const char* version() {
-  return "3.6.0";
+  return "3.7.0";
 }
 
 
similarity index 81%
rename from bundled/taskflow-3.6.0/include/taskflow/utility/iterator.hpp
rename to bundled/taskflow-3.7.0/taskflow/utility/iterator.hpp
index 6441391f941fa059018d92e1773ce2a2ae0d07ef..8636a3bccedcc3c650116000eaabc667880e082b 100644 (file)
@@ -9,8 +9,8 @@ template <typename T>
 constexpr std::enable_if_t<std::is_integral<std::decay_t<T>>::value, bool>
 is_range_invalid(T beg, T end, T step) {
   return ((step == 0 && beg != end) ||
-          (beg < end && step <=  0) ||
-          (beg > end && step >=  0));
+          (beg < end && step <=  0) ||  // positive range
+          (beg > end && step >=  0));   // negative range
 }
 
 template <typename T>
diff --git a/bundled/taskflow-3.7.0/taskflow/utility/macros.hpp b/bundled/taskflow-3.7.0/taskflow/utility/macros.hpp
new file mode 100644 (file)
index 0000000..f184468
--- /dev/null
@@ -0,0 +1,33 @@
+#pragma once
+
+#if defined(_MSC_VER)
+  #define TF_FORCE_INLINE __forceinline
+#elif defined(__GNUC__) && __GNUC__ > 3
+  #define TF_FORCE_INLINE __attribute__((__always_inline__)) inline
+#else
+  #define TF_FORCE_INLINE inline
+#endif
+
+#if defined(_MSC_VER)
+  #define TF_NO_INLINE __declspec(noinline)
+#elif defined(__GNUC__) && __GNUC__ > 3
+  #define TF_NO_INLINE __attribute__((__noinline__))
+#else
+  #define TF_NO_INLINE
+#endif
+
+// ----------------------------------------------------------------------------
+
+#ifdef TF_DISABLE_EXCEPTION_HANDLING
+  #define TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, code_block) \
+    code_block;
+#else
+  #define TF_EXECUTOR_EXCEPTION_HANDLER(worker, node, code_block)  \
+    try {                                          \
+      code_block;                                  \
+    } catch(...) {                                 \
+      _process_exception(worker, node);            \
+    }
+#endif
+
+// ----------------------------------------------------------------------------    
similarity index 97%
rename from bundled/taskflow-3.6.0/include/taskflow/utility/stream.hpp
rename to bundled/taskflow-3.7.0/taskflow/utility/stream.hpp
index 320aa6c63d4cc6a2d04781fc8f0a468cb5413ef5..34a86ff569969ff41c2de06a99238b77eef0461e 100644 (file)
@@ -1,6 +1,7 @@
 #pragma once
 
 #include <iostream>
+#include <sstream>
 #include <string>
 
 namespace tf {
similarity index 99%
rename from bundled/taskflow-3.6.0/include/taskflow/utility/traits.hpp
rename to bundled/taskflow-3.7.0/taskflow/utility/traits.hpp
index 196b147c85bd62d46336df9777da7162dc21be13..dd3953bd44251f3d52fce9bb2f61e1cb74d885c3 100644 (file)
@@ -1,5 +1,9 @@
 #pragma once
 
+#if __has_include(<version>)
+#  include <version>
+#endif
+
 #include <type_traits>
 #include <iterator>
 #include <iostream>

In the beginning the Universe was created. This has made a lot of people very angry and has been widely regarded as a bad move.

Douglas Adams


Typeset in Trocchi and Trocchi Bold Sans Serif.