Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
sub_task_graph.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
6#include <helios/async/details/profile.hpp>
10
11#include <taskflow/algorithm/for_each.hpp>
12#include <taskflow/algorithm/reduce.hpp>
13#include <taskflow/algorithm/sort.hpp>
14#include <taskflow/algorithm/transform.hpp>
15#include <taskflow/core/async_task.hpp>
16#include <taskflow/taskflow.hpp>
17
18#include <array>
19#include <concepts>
20#include <cstddef>
21#include <functional>
22#include <future>
23#include <ranges>
24#include <string>
25#include <type_traits>
26#include <utility>
27#include <vector>
28
29namespace helios::async {
30
31/**
32 * @brief Dynamic task graph that can be created within the execution of a task.
33 * @details Wraps `tf::Subflow` and provides methods to create tasks dynamically
34 * at runtime. `SubTaskGraph`s are spawned from the execution of a `SubTask` and
35 * allow for runtime-dependent task creation and dependency management.
36 * @note Partially thread-safe.
37 * @warning Only the worker thread that spawned the subflow should modify it.
38 */
40public:
41 SubTaskGraph(const SubTaskGraph&) = delete;
43 ~SubTaskGraph() = default;
44
47
48 /**
49 * @brief Joins the subflow with its parent task.
50 * @details Called automatically when the `SubTaskGraph` goes out of scope,
51 * unless `Join()` has already been called.
52 * @warning Must be called by the same worker thread that created this
53 * subflow.
54 */
55 void Join();
56 /**
57 * @brief Specifies whether to keep the sub task graph after it is joined.
58 * @details By default, a sub task graph is destroyed after being joined.
59 * Retaining it allows it to remain valid after being joined.
60 * @warning Must be called by the same worker thread that created this
61 * subflow.
62 * @param flag True to retain, false otherwise
63 */
64 void Retain(bool flag) noexcept { subflow_.retain(flag); }
65
66 /**
67 * @brief Creates a static task with the given callable.
68 * @note Not thread-safe
69 * @tparam C Callable type
70 * @param callable Function to execute when the task runs
71 * @return Task handle for the created task
72 */
73 template <StaticTask C>
74 Task EmplaceTask(C&& callable) {
75 return Task(subflow_.emplace(std::forward<C>(callable)));
76 }
77
78 /**
79 * @brief Creates a dynamic task (nested subflow) with the given callable.
80 * @note Not thread-safe
81 * @tparam C Callable type that accepts a `SubTaskGraph` reference
82 * @param callable Function to execute when the task runs
83 * @return Task handle for the created task
84 */
85 template <SubTask C>
86 Task EmplaceTask(C&& callable);
87
88 /**
89 * @brief Creates multiple tasks from a list of callables.
90 * @note Not thread-safe
91 * @tparam Cs Callable types
92 * @param callables Functions to execute when the tasks run
93 * @return Array of task handles for the created tasks
94 */
95 template <AnyTask... Cs>
96 requires(sizeof...(Cs) > 1)
97 auto EmplaceTasks(Cs&&... callables) -> std::array<Task, sizeof...(Cs)> {
98 return {EmplaceTask(std::forward<Cs>(callables))...};
99 }
100
101 /**
102 * @brief Creates a placeholder task with no assigned work.
103 * @note Not thread-safe
104 * @return Task handle that can later be assigned work
105 */
106 Task CreatePlaceholder() { return Task(subflow_.placeholder()); }
107
108 /**
109 * @brief Creates linear dependencies between tasks in the given range.
110 * @note Not thread-safe
111 * @tparam R Range type containing Task objects
112 * @param tasks Range of tasks to linearize (first->second->third->...)
113 */
114 template <std::ranges::range R>
115 requires std::same_as<std::ranges::range_value_t<R>, Task>
116 void Linearize(const R& tasks);
117
118 /**
119 * @brief Creates a parallel for-each task over the given range.
120 * @note Not thread-safe
121 * @tparam R Range type
122 * @tparam C Callable type
123 * @param range Input range to iterate over
124 * @param callable Function to apply to each element
125 * @return Task handle for the parallel operation
126 */
127 template <std::ranges::range R,
128 std::invocable<std::ranges::range_reference_t<R>> C>
129 Task ForEach(const R& range, C&& callable) {
130 return Task(subflow_.for_each(std::ranges::begin(range),
131 std::ranges::end(range),
132 std::forward<C>(callable)));
133 }
134
135 /**
136 * @brief Creates a parallel for-each task over an index range.
137 * @note Not thread-safe
138 * @tparam I Integral type
139 * @tparam C Callable type
140 * @param start Starting index (inclusive)
141 * @param end Ending index (exclusive)
142 * @param step Step size
143 * @param callable Function to apply to each index
144 * @return Task handle for the parallel operation
145 */
146 template <std::integral I, std::invocable<I> C>
147 Task ForEachIndex(I start, I end, I step, C&& callable) {
148 return Task(
149 subflow_.for_each_index(start, end, step, std::forward<C>(callable)));
150 }
151
152 /**
153 * @brief Creates a parallel transform task that applies a function to each
154 * element.
155 * @note Not thread-safe
156 * @tparam InputRange Input range type
157 * @tparam OutputRange Output range type
158 * @tparam TransformFunc Transform function type
159 * @param input_range Range of input elements
160 * @param output_range Range to store transformed results
161 * @param transform_func Function to apply to each input element
162 * @return Task handle for the parallel operation
163 */
164 template <
165 std::ranges::range InputRange, std::ranges::range OutputRange,
166 std::invocable<std::ranges::range_reference_t<InputRange>> TransformFunc>
167 Task Transform(const InputRange& input_range, OutputRange& output_range,
168 TransformFunc&& transform_func) {
169 return Task(subflow_.transform(
170 std::ranges::begin(input_range), std::ranges::end(input_range),
171 std::ranges::begin(output_range),
172 std::forward<TransformFunc>(transform_func)));
173 }
174
175 /**
176 * @brief Creates a parallel reduction task that combines elements using a
177 * binary operation.
178 * @note Not thread-safe
179 * @tparam R Range type
180 * @tparam T Result type
181 * @tparam BinaryOp Binary operation type
182 * @param range Range of elements to reduce
183 * @param init Initial value and storage for the result
184 * @param binary_op Binary function to combine elements
185 * @return Task handle for the parallel operation
186 */
187 template <std::ranges::range R, typename T, typename BinaryOp>
188 requires std::invocable<BinaryOp, T, std::ranges::range_reference_t<R>>
189 Task Reduce(const R& range, T& init, BinaryOp&& binary_op) {
190 return Task(subflow_.reduce(std::ranges::begin(range),
191 std::ranges::end(range), init,
192 std::forward<BinaryOp>(binary_op)));
193 }
194
195 /**
196 * @brief Creates a parallel sort task for the given range.
197 * @note Not thread-safe
198 * @tparam R Random access range type
199 * @tparam Compare Comparator type
200 * @param range Range of elements to sort
201 * @param comparator Comparison function (default: std::less<>)
202 * @return Task handle for the parallel operation
203 */
204 template <std::ranges::random_access_range R, typename Compare = std::less<>>
205 requires std::predicate<Compare, std::ranges::range_reference_t<R>,
206 std::ranges::range_reference_t<R>>
207 Task Sort(R& range, Compare&& comparator = Compare{});
208
209 /**
210 * @brief Removes a task from this subflow.
211 * @note Not thread-safe
212 * @param task Task to remove
213 */
214 void RemoveTask(const Task& task) { subflow_.erase(task.UnderlyingTask()); }
215
216 /**
217 * @brief Creates a module task that encapsulates another task graph.
218 * @note Not thread-safe
219 * @param other_graph Task graph to compose into this subflow
220 * @return Task handle representing the composed graph
221 */
222 template <typename T>
223 Task ComposedOf(T& other_graph) {
224 return Task(subflow_.composed_of(other_graph.UnderlyingTaskflow()));
225 }
226
227 /**
228 * @brief Checks if this subflow can be joined.
229 * @details A subflow is joinable if it has not yet been joined with its
230 * parent task.
231 * @return True if the subflow is joinable, false otherwise
232 */
233 [[nodiscard]] bool Joinable() const noexcept { return subflow_.joinable(); }
234
235 /**
236 * @brief Checks if this subflow will be retained.
237 * @details A retained subflow remains valid after being joined.
238 * @return True if the subflow will be retained, false otherwise
239 */
240 [[nodiscard]] bool WillBeRetained() const noexcept {
241 return subflow_.retain();
242 }
243
244 // Executor related methods
245
246 /**
247 * @brief Runs a task graph once.
248 * @details Task graph is not owned - ensure it remains alive during
249 * execution.
250 * @note Thread-safe.
251 * @param graph Task graph to execute
252 * @return Future that completes when execution finishes
253 */
254 auto Run(TaskGraph& graph) -> Future<void> {
255 return Future<void>(subflow_.executor().run(graph.UnderlyingTaskflow()));
256 }
257
258 /**
259 * @brief Runs a task graph once.
260 * @note Thread-safe.
261 * @param graph Task graph to execute (moved)
262 * @return Future that completes when execution finishes
263 */
264 auto Run(TaskGraph&& graph) -> Future<void> {
265 return Future<void>(subflow_.executor().run(
266 std::move(std::move(graph).UnderlyingTaskflow())));
267 }
268
269 /**
270 * @brief Runs a task graph once and invokes a callback upon completion.
271 * @details Task graph is not owned - ensure it remains alive during
272 * execution.
273 * @note Thread-safe.
274 * @tparam C Callable type
275 * @param graph Task graph to execute
276 * @param callable Callback to invoke after execution completes
277 * @return Future that completes when execution finishes
278 */
279 template <std::invocable C>
280 auto Run(TaskGraph& graph, C&& callable) -> Future<void> {
281 return Future<void>(subflow_.executor().run(graph.UnderlyingTaskflow(),
282 std::forward<C>(callable)));
283 }
284
285 /**
286 * @brief Runs a moved task graph once and invokes a callback upon completion.
287 * @note Thread-safe.
288 * @tparam C Callable type
289 * @param graph Task graph to execute (moved)
290 * @param callable Callback to invoke after execution completes
291 * @return Future that completes when execution finishes
292 */
293 template <std::invocable C>
294 auto Run(TaskGraph&& graph, C&& callable) -> Future<void> {
295 return Future<void>(subflow_.executor().run(
296 std::move(std::move(graph).UnderlyingTaskflow()),
297 std::forward<C>(callable)));
298 }
299
300 /**
301 * @brief Runs a task graph for the specified number of times.
302 * @details Task graph is not owned - ensure it remains alive during
303 * execution.
304 * @note Thread-safe.
305 * @param graph Task graph to execute
306 * @param count Number of times to run the graph
307 * @return Future that completes when all executions finish
308 */
309 auto RunN(TaskGraph& graph, size_t count) -> Future<void> {
310 return Future<void>(
311 subflow_.executor().run_n(graph.UnderlyingTaskflow(), count));
312 }
313
314 /**
315 * @brief Runs a moved task graph for the specified number of times.
316 * @note Thread-safe.
317 * @param graph Task graph to execute (moved)
318 * @param count Number of times to run the graph
319 * @return Future that completes when all executions finish
320 */
321 auto RunN(TaskGraph&& graph, size_t count) -> Future<void> {
322 return Future<void>(subflow_.executor().run_n(
323 std::move(std::move(graph).UnderlyingTaskflow()), count));
324 }
325
326 /**
327 * @brief Runs a task graph for the specified number of times and invokes a
328 * callback.
329 * @details Task graph is not owned - ensure it remains alive during
330 * execution.
331 * @note Thread-safe.
332 * @tparam C Callable type
333 * @param graph Task graph to execute
334 * @param count Number of times to run the graph
335 * @param callable Callback to invoke after all executions complete
336 * @return Future that completes when all executions finish
337 */
338 template <std::invocable C>
339 auto RunN(TaskGraph& graph, size_t count, C&& callable) -> Future<void> {
340 return Future<void>(subflow_.executor().run_n(
341 graph.UnderlyingTaskflow(), count, std::forward<C>(callable)));
342 }
343
344 /**
345 * @brief Runs a moved task graph for the specified number of times and
346 * invokes a callback.
347 * @note Thread-safe.
348 * @tparam C Callable type
349 * @param graph Task graph to execute (moved)
350 * @param count Number of times to run the graph
351 * @param callable Callback to invoke after all executions complete
352 * @return Future that completes when all executions finish
353 */
354 template <std::invocable C>
355 auto RunN(TaskGraph&& graph, size_t count, C&& callable) -> Future<void> {
356 return Future<void>(subflow_.executor().run_n(
357 std::move(std::move(graph).UnderlyingTaskflow()), count,
358 std::forward<C>(callable)));
359 }
360
361 /**
362 * @brief Runs a task graph repeatedly until the predicate returns true.
363 * @details Task graph is not owned - ensure it remains alive during
364 * execution.
365 * @note Thread-safe.
366 * @tparam Predicate Predicate type
367 * @param graph Task graph to execute
368 * @param predicate Boolean predicate to determine when to stop
369 * @return Future that completes when predicate returns true
370 */
371 template <std::predicate Predicate>
372 auto RunUntil(TaskGraph& graph, Predicate&& predicate) -> Future<void> {
373 return Future<void>(subflow_.executor().run_until(
374 graph.UnderlyingTaskflow(), std::forward<Predicate>(predicate)));
375 }
376
377 /**
378 * @brief Runs a moved task graph repeatedly until the predicate returns true.
379 * @note Thread-safe.
380 * @tparam Predicate Predicate type
381 * @param graph Task graph to execute (moved)
382 * @param predicate Boolean predicate to determine when to stop
383 * @return Future that completes when predicate returns true
384 */
385 template <std::predicate Predicate>
386 auto RunUntil(TaskGraph&& graph, Predicate&& predicate) -> Future<void> {
387 return Future<void>(subflow_.executor().run_until(
388 std::move(std::move(graph).UnderlyingTaskflow()),
389 std::forward<Predicate>(predicate)));
390 }
391
392 /**
393 * @brief Runs a task graph repeatedly until the predicate returns true, then
394 * invokes a callback.
395 * @details Task graph is not owned - ensure it remains alive during
396 * execution.
397 * @note Thread-safe.
398 * @tparam Predicate Predicate type
399 * @tparam C Callable type
400 * @param graph Task graph to execute
401 * @param predicate Boolean predicate to determine when to stop
402 * @param callable Callback to invoke after execution completes
403 * @return Future that completes when predicate returns true and callback
404 * finishes
405 */
406 template <std::predicate Predicate, std::invocable C>
407 auto RunUntil(TaskGraph& graph, Predicate&& predicate, C&& callable)
408 -> Future<void> {
409 return Future<void>(subflow_.executor().run_until(
410 graph.UnderlyingTaskflow(), std::forward<Predicate>(predicate),
411 std::forward<C>(callable)));
412 }
413
414 /**
415 * @brief Runs a moved task graph repeatedly until the predicate returns true,
416 * then invokes a callback.
417 * @note Thread-safe.
418 * @tparam Predicate Predicate type
419 * @tparam C Callable type
420 * @param graph Task graph to execute (moved)
421 * @param predicate Boolean predicate to determine when to stop
422 * @param callable Callback to invoke after execution completes
423 * @return Future that completes when predicate returns true and callback
424 * finishes
425 */
426 template <std::predicate Predicate, std::invocable C>
427 auto RunUntil(TaskGraph&& graph, Predicate&& predicate, C&& callable)
428 -> Future<void> {
429 return Future<void>(subflow_.executor().run_until(
430 std::move(std::move(graph).UnderlyingTaskflow()),
431 std::forward<Predicate>(predicate), std::forward<C>(callable)));
432 }
433
434 /**
435 * @brief Creates an asynchronous task that runs the given callable.
436 * @details The task is scheduled immediately and runs independently.
437 * @note Thread-safe.
438 * @tparam C Callable type
439 * @param callable Function to execute asynchronously
440 * @return Future that will hold the result of the execution
441 */
442 template <std::invocable C>
443 auto Async(C&& callable) -> std::future<std::invoke_result_t<C>> {
444 return subflow_.executor().async(std::forward<C>(callable));
445 }
446
447 /**
448 * @brief Creates a named asynchronous task that runs the given callable.
449 * @details The task is scheduled immediately and runs independently.
450 * @note Thread-safe.
451 * @tparam C Callable type
452 * @param name Name for the task (useful for debugging/profiling)
453 * @param callable Function to execute asynchronously
454 * @return Future that will hold the result of the execution
455 */
456 template <std::invocable C>
457 auto Async(std::string name, C&& callable)
458 -> std::future<std::invoke_result_t<C>> {
459 return subflow_.executor().async(std::move(name),
460 std::forward<C>(callable));
461 }
462
463 /**
464 * @brief Creates an asynchronous task without returning a future.
465 * @details More efficient than Async when you don't need the result.
466 * @note Thread-safe.
467 * @tparam C Callable type
468 * @param callable Function to execute asynchronously
469 */
470 template <std::invocable C>
471 void SilentAsync(C&& callable) {
472 subflow_.executor().silent_async(std::forward<C>(callable));
473 }
474
475 /**
476 * @brief Creates a named asynchronous task without returning a future.
477 * @details More efficient than Async when you don't need the result.
478 * @note Thread-safe.
479 * @tparam C Callable type
480 * @param name Name for the task (useful for debugging/profiling)
481 * @param callable Function to execute asynchronously
482 */
483 template <std::invocable C>
484 void SilentAsync(std::string name, C&& callable) {
485 subflow_.executor().silent_async(std::move(name),
486 std::forward<C>(callable));
487 }
488
489 /**
490 * @brief Creates an asynchronous task that runs after specified dependencies
491 * complete.
492 * @details The task will only execute after all dependencies finish.
493 * @note Thread-safe.
494 * @tparam C Callable type
495 * @tparam Dependencies Range type containing AsyncTask dependencies
496 * @param callable Function to execute asynchronously
497 * @param dependencies Tasks that must complete before this task runs
498 * @return Pair containing AsyncTask handle and Future for the result
499 */
500 template <std::invocable C, std::ranges::range Dependencies>
501 requires std::same_as<std::ranges::range_value_t<Dependencies>, AsyncTask>
502 auto DependentAsync(C&& callable, const Dependencies& dependencies)
503 -> std::pair<AsyncTask, std::future<std::invoke_result_t<C>>>;
504
505 /**
506 * @brief Creates an asynchronous task that runs after dependencies complete,
507 * without returning a future.
508 * @details More efficient than DependentAsync when you don't need the result.
509 * @note Thread-safe.
510 * @tparam C Callable type
511 * @tparam Dependencies Range type containing AsyncTask dependencies
512 * @param callable Function to execute asynchronously
513 * @param dependencies Tasks that must complete before this task runs
514 * @return AsyncTask handle
515 */
516 template <std::invocable C, std::ranges::range Dependencies>
517 requires std::same_as<std::ranges::range_value_t<Dependencies>, AsyncTask>
518 AsyncTask SilentDependentAsync(C&& callable,
519 const Dependencies& dependencies);
520
521 /**
522 * @brief Blocks until all submitted tasks complete.
523 * @details Waits for all taskflows and async tasks to finish.
524 * @note Thread-safe.
525 */
526 void WaitForAll() { subflow_.executor().wait_for_all(); }
527
528 /**
529 * @brief Runs a task graph cooperatively and waits until it completes using
530 * the current worker thread.
531 * @warning Must be called from within a worker thread of this executor.
532 * Triggers assertion if called from a non-worker thread.
533 * @param graph Task graph to execute
534 */
535 void CoRun(TaskGraph& graph);
536
537 /**
538 * @brief Keeps the current worker thread running until the predicate returns
539 * true.
540 * @warning Must be called from within a worker thread of this executor.
541 * Triggers assertion if called from a non-worker thread.
542 * @tparam Predicate Predicate type
543 * @param predicate Boolean predicate to determine when to stop
544 */
545 template <std::predicate Predicate>
546 void CoRunUntil(Predicate&& predicate);
547
548 /**
549 * @brief Checks if the current thread is a worker thread of this executor.
550 * @note Thread safe.
551 * @return True if current thread is a worker, false otherwise
552 */
553 [[nodiscard]] bool IsWorkerThread() const { return CurrentWorkerId() != -1; }
554
555 /**
556 * @brief Gets the ID of the current worker thread.
557 * @note Thread-safe.
558 * @return Worker ID (0 to N-1) or -1 if not a worker thread
559 */
560 [[nodiscard]] int CurrentWorkerId() const {
561 return subflow_.executor().this_worker_id();
562 }
563
564 /**
565 * @brief Gets the total number of worker threads.
566 * @note Thread safe.
567 * @return Count of workers.
568 */
569 [[nodiscard]] size_t WorkerCount() const noexcept {
570 return subflow_.executor().num_workers();
571 }
572
573 /**
574 * @brief Gets the number of worker threads currently waiting for work.
575 * @note Thread safe.
576 * @return Count of idle workers.
577 */
578 [[nodiscard]] size_t IdleWorkerCount() const noexcept {
579 return subflow_.executor().num_waiters();
580 }
581
582 /**
583 * @brief Gets the number of task queues in the work-stealing scheduler.
584 * @note Thread safe.
585 * @return Count of queues.
586 */
587 [[nodiscard]] size_t QueueCount() const noexcept {
588 return subflow_.executor().num_queues();
589 }
590
591 /**
592 * @brief Gets the number of task graphs currently being executed.
593 * @note Thread safe.
594 * @return Count of running topologies.
595 */
596 [[nodiscard]] size_t RunningTopologyCount() const {
597 return subflow_.executor().num_topologies();
598 }
599
600private:
601 explicit SubTaskGraph(tf::Subflow& subflow) : subflow_(subflow) {}
602
603 [[nodiscard]] tf::Subflow& UnderlyingSubflow() noexcept { return subflow_; }
604 [[nodiscard]] const tf::Subflow& UnderlyingSubflow() const noexcept {
605 return subflow_;
606 }
607
608 tf::Subflow& subflow_;
609
610 friend class TaskGraph;
611 friend class Executor;
612};
613
614inline void SubTaskGraph::Join() {
615 HELIOS_ASYNC_PROFILE_SCOPE_N("helios::async::SubTaskGraph::Join");
616 subflow_.join();
617}
618
619template <SubTask C>
620inline Task SubTaskGraph::EmplaceTask(C&& callable) {
621 return Task(subflow_.emplace(
622 [callable = std::forward<C>(callable)](tf::Subflow& sf) mutable {
623 SubTaskGraph sub_graph(sf);
624 callable(sub_graph);
625 }));
626}
627
628template <std::ranges::range R>
629 requires std::same_as<std::ranges::range_value_t<R>, Task>
630inline void SubTaskGraph::Linearize(const R& tasks) {
631 HELIOS_ASYNC_PROFILE_SCOPE_N("helios::async::SubTaskGraph::Linearize");
632
633 std::vector<tf::Task> tf_tasks;
634 tf_tasks.reserve(std::ranges::size(tasks));
635
636 for (const auto& task : tasks) {
637 tf_tasks.push_back(task.UnderlyingTask());
638 }
639
640 subflow_.linearize(tf_tasks);
641}
642
643template <std::ranges::random_access_range R, typename Compare>
644 requires std::predicate<Compare, std::ranges::range_reference_t<R>,
645 std::ranges::range_reference_t<R>>
646inline Task SubTaskGraph::Sort(R& range, Compare&& comparator) {
647 if constexpr (std::same_as<std::remove_cvref_t<Compare>, std::less<>>) {
648 return Task(
649 subflow_.sort(std::ranges::begin(range), std::ranges::end(range)));
650 } else {
651 return Task(subflow_.sort(std::ranges::begin(range),
652 std::ranges::end(range),
653 std::forward<Compare>(comparator)));
654 }
655}
656
657template <std::invocable C, std::ranges::range Dependencies>
658 requires std::same_as<std::ranges::range_value_t<Dependencies>, AsyncTask>
659inline auto SubTaskGraph::DependentAsync(C&& callable,
660 const Dependencies& dependencies)
661 -> std::pair<AsyncTask, std::future<std::invoke_result_t<C>>> {
662 std::vector<tf::AsyncTask> tf_deps;
663 if constexpr (std::ranges::sized_range<Dependencies>) {
664 tf_deps.reserve(std::ranges::size(dependencies));
665 }
666
667 for (const auto& dep : dependencies) {
668 tf_deps.push_back(dep.UnderlyingTask());
669 }
670
671 auto [task, future] = subflow_.executor().dependent_async(
672 std::forward<C>(callable), tf_deps.begin(), tf_deps.end());
673 return std::make_pair(AsyncTask(std::move(task)), std::move(future));
674}
675
676template <std::invocable C, std::ranges::range Dependencies>
677 requires std::same_as<std::ranges::range_value_t<Dependencies>, AsyncTask>
679 C&& callable, const Dependencies& dependencies) {
680 std::vector<tf::AsyncTask> tf_deps;
681 if constexpr (std::ranges::sized_range<Dependencies>) {
682 tf_deps.reserve(std::ranges::size(dependencies));
683 }
684
685 for (const auto& dep : dependencies) {
686 tf_deps.push_back(dep.UnderlyingTask());
687 }
688
689 return AsyncTask(subflow_.executor().silent_dependent_async(
690 std::forward<C>(callable), tf_deps.begin(), tf_deps.end()));
691}
692
693template <SubTask C>
694inline Task TaskGraph::EmplaceTask(C&& callable) {
695 return Task(taskflow_.emplace(
696 [callable = std::forward<C>(callable)](tf::Subflow& subflow) mutable {
697 SubTaskGraph sub_graph(subflow);
698 std::invoke(callable, sub_graph);
699 }));
700}
701
702inline void SubTaskGraph::CoRun(TaskGraph& graph) {
703 HELIOS_ASSERT(IsWorkerThread(), "Must be called from a worker thread!");
704 HELIOS_ASYNC_PROFILE_SCOPE_N("helios::async::SubTaskGraph::CoRun");
705 subflow_.executor().corun(graph.UnderlyingTaskflow());
706}
707
708template <std::predicate Predicate>
709inline void SubTaskGraph::CoRunUntil(Predicate&& predicate) {
710 HELIOS_ASSERT(IsWorkerThread(), "Must be called from a worker thread!");
711 HELIOS_ASYNC_PROFILE_SCOPE();
712 subflow_.executor().corun_until(std::forward<Predicate>(predicate));
713}
714
715} // namespace helios::async
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
Handle to an asynchronous task managed by the Executor.
Wrapper around tf::Future for handling asynchronous task results.
Definition future.hpp:22
auto Run(TaskGraph &&graph, C &&callable) -> Future< void >
Runs a moved task graph once and invokes a callback upon completion.
bool IsWorkerThread() const
Checks if the current thread is a worker thread of this executor.
Task EmplaceTask(C &&callable)
Creates a static task with the given callable.
Task ComposedOf(T &other_graph)
Creates a module task that encapsulates another task graph.
Task ForEach(const R &range, C &&callable)
Creates a parallel for-each task over the given range.
size_t WorkerCount() const noexcept
Gets the total number of worker threads.
size_t IdleWorkerCount() const noexcept
Gets the number of worker threads currently waiting for work.
size_t QueueCount() const noexcept
Gets the number of task queues in the work-stealing scheduler.
SubTaskGraph(SubTaskGraph &&)=default
void RemoveTask(const Task &task)
Removes a task from this subflow.
auto Async(C &&callable) -> std::future< std::invoke_result_t< C > >
Creates an asynchronous task that runs the given callable.
void Retain(bool flag) noexcept
Specifies whether to keep the sub task graph after it is joined.
auto RunUntil(TaskGraph &graph, Predicate &&predicate, C &&callable) -> Future< void >
Runs a task graph repeatedly until the predicate returns true, then invokes a callback.
bool Joinable() const noexcept
Checks if this subflow can be joined.
SubTaskGraph & operator=(SubTaskGraph &&)=delete
auto Run(TaskGraph &graph) -> Future< void >
Runs a task graph once.
int CurrentWorkerId() const
Gets the ID of the current worker thread.
auto RunN(TaskGraph &graph, size_t count, C &&callable) -> Future< void >
Runs a task graph for the specified number of times and invokes a callback.
void Join()
Joins the subflow with its parent task.
void CoRun(TaskGraph &graph)
Runs a task graph cooperatively and waits until it completes using the current worker thread.
auto RunN(TaskGraph &graph, size_t count) -> Future< void >
Runs a task graph for the specified number of times.
auto RunUntil(TaskGraph &&graph, Predicate &&predicate) -> Future< void >
Runs a moved task graph repeatedly until the predicate returns true.
auto EmplaceTasks(Cs &&... callables) -> std::array< Task, sizeof...(Cs)>
Creates multiple tasks from a list of callables.
SubTaskGraph & operator=(const SubTaskGraph &)=delete
SubTaskGraph(const SubTaskGraph &)=delete
auto Run(TaskGraph &&graph) -> Future< void >
Runs a task graph once.
Task Sort(R &range, Compare &&comparator=Compare{})
Creates a parallel sort task for the given range.
auto Async(std::string name, C &&callable) -> std::future< std::invoke_result_t< C > >
Creates a named asynchronous task that runs the given callable.
Task Transform(const InputRange &input_range, OutputRange &output_range, TransformFunc &&transform_func)
Creates a parallel transform task that applies a function to each element.
void SilentAsync(C &&callable)
Creates an asynchronous task without returning a future.
void Linearize(const R &tasks)
Creates linear dependencies between tasks in the given range.
auto RunN(TaskGraph &&graph, size_t count) -> Future< void >
Runs a moved task graph for the specified number of times.
auto RunN(TaskGraph &&graph, size_t count, C &&callable) -> Future< void >
Runs a moved task graph for the specified number of times and invokes a callback.
void WaitForAll()
Blocks until all submitted tasks complete.
Task Reduce(const R &range, T &init, BinaryOp &&binary_op)
Creates a parallel reduction task that combines elements using a binary operation.
Task CreatePlaceholder()
Creates a placeholder task with no assigned work.
size_t RunningTopologyCount() const
Gets the number of task graphs currently being executed.
void SilentAsync(std::string name, C &&callable)
Creates a named asynchronous task without returning a future.
auto Run(TaskGraph &graph, C &&callable) -> Future< void >
Runs a task graph once and invokes a callback upon completion.
auto DependentAsync(C &&callable, const Dependencies &dependencies) -> std::pair< AsyncTask, std::future< std::invoke_result_t< C > > >
Creates an asynchronous task that runs after specified dependencies complete.
Task ForEachIndex(I start, I end, I step, C &&callable)
Creates a parallel for-each task over an index range.
AsyncTask SilentDependentAsync(C &&callable, const Dependencies &dependencies)
Creates an asynchronous task that runs after dependencies complete, without returning a future.
auto RunUntil(TaskGraph &&graph, Predicate &&predicate, C &&callable) -> Future< void >
Runs a moved task graph repeatedly until the predicate returns true, then invokes a callback.
auto RunUntil(TaskGraph &graph, Predicate &&predicate) -> Future< void >
Runs a task graph repeatedly until the predicate returns true.
void CoRunUntil(Predicate &&predicate)
Keeps the current worker thread running until the predicate returns true.
bool WillBeRetained() const noexcept
Checks if this subflow will be retained.
Represents a task dependency graph that can be executed by an Executor.
Task EmplaceTask(C &&callable)
Creates a static task with the given callable.
Represents a single task within a task graph.
Definition task.hpp:25
Concept for any valid task callable.
Definition common.hpp:77