Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
task_graph.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/core_pch.hpp>
4
9
10#include <taskflow/algorithm/for_each.hpp>
11#include <taskflow/algorithm/reduce.hpp>
12#include <taskflow/algorithm/sort.hpp>
13#include <taskflow/algorithm/transform.hpp>
14#include <taskflow/taskflow.hpp>
15
16#include <array>
17#include <concepts>
18#include <cstddef>
19#include <functional>
20#include <ranges>
21#include <string>
22#include <string_view>
23#include <type_traits>
24#include <utility>
25#include <vector>
26
27namespace helios::async {
28
29/**
30 * @brief Represents a task dependency graph that can be executed by an Executor.
31 * @details Wraps tf::Taskflow and provides methods to create tasks, manage dependencies, and build
32 * complex computational graphs. Tasks within the graph can have dependencies that determine execution order.
33 * @note Not thread-safe.
34 * Do not modify a graph while it's being executed.
35 */
36class TaskGraph {
37public:
38 /**
39 * @brief Constructs a task graph with the given name.
40 * @param name Name for the task graph (default: "TaskGraph")
41 */
42 explicit TaskGraph(std::string_view name = "TaskGraph") { Name(name); }
43 TaskGraph(const TaskGraph&) = delete;
44 TaskGraph(TaskGraph&&) noexcept = default;
45 ~TaskGraph() = default;
46
47 TaskGraph& operator=(const TaskGraph&) = delete;
48 TaskGraph& operator=(TaskGraph&&) noexcept = default;
49
50 /*
51 * @brief Clears all tasks and dependencies from this graph.
52 */
53 void Clear() { taskflow_.clear(); }
54
55 /**
56 * @brief Applies a visitor function to each task in this graph.
57 * @tparam Visitor Callable type that accepts a Task reference
58 * @param visitor Function to call for each task
59 */
60 template <typename Visitor>
61 requires std::invocable<Visitor, Task&>
62 void ForEachTask(const Visitor& visitor) const;
63
64 /**
65 * @brief Creates a static task with the given callable.
66 * @tparam C Callable type
67 * @param callable Function to execute when the task runs
68 * @return Task handle for the created task
69 */
70 template <StaticTask C>
71 Task EmplaceTask(C&& callable) {
72 return Task(taskflow_.emplace(std::forward<C>(callable)));
73 }
74
75 /**
76 * @brief Creates a dynamic task (subflow) with the given callable.
77 * @details Requires sub_task_graph.hpp to be included.
78 * @tparam C Callable type that accepts a SubTaskGraph reference
79 * @param callable Function to execute when the task runs
80 * @return Task handle for the created task
81 */
82 template <SubTask C>
83 Task EmplaceTask(C&& callable);
84
85 /**
86 * @brief Creates multiple tasks from a list of callables.
87 * @tparam Cs Callable types
88 * @param callables Functions to execute when the tasks run
89 * @return Array of task handles for the created tasks
90 */
91 template <AnyTask... Cs>
92 requires(sizeof...(Cs) > 1)
93 auto EmplaceTasks(Cs&&... callables) -> std::array<Task, sizeof...(Cs)> {
94 return {EmplaceTask(std::forward<Cs>(callables))...};
95 }
96
97 /**
98 * @brief Creates a placeholder task with no assigned work.
99 * @return Task handle that can later be assigned work
100 */
101 Task CreatePlaceholder() { return Task(taskflow_.placeholder()); }
102
103 /**
104 * @brief Creates linear dependencies between tasks in the given range.
105 * @tparam R Range type containing Task objects
106 * @param tasks Range of tasks to linearize (first->second->third->...)
107 */
108 template <std::ranges::range R>
109 requires std::same_as<std::ranges::range_value_t<R>, Task>
110 void Linearize(const R& tasks);
111
112 /**
113 * @brief Creates a parallel for-each task over the given range.
114 * @tparam R Range type
115 * @tparam C Callable type
116 * @param range Input range to iterate over
117 * @param callable Function to apply to each element
118 * @return Task handle for the parallel operation
119 */
120 template <std::ranges::range R, typename C>
121 requires std::invocable<C, std::ranges::range_reference_t<R>>
122 Task ForEach(const R& range, C&& callable) {
123 return Task(taskflow_.for_each(std::ranges::begin(range), std::ranges::end(range), std::forward<C>(callable)));
124 }
125
126 /**
127 * @brief Creates a parallel for-each task over an index range.
128 * @tparam I Integral type
129 * @tparam C Callable type
130 * @param start Starting index (inclusive)
131 * @param end Ending index (exclusive)
132 * @param step Step size
133 * @param callable Function to apply to each index
134 * @return Task handle for the parallel operation
135 */
136 template <std::integral I, typename C>
137 requires std::invocable<C, I>
138 Task ForEachIndex(I start, I end, I step, C&& callable) {
139 return Task(taskflow_.for_each_index(start, end, step, std::forward<C>(callable)));
140 }
141
142 /**
143 * @brief Creates a parallel transform task that applies a function to each element.
144 * @tparam InputRange Input range type
145 * @tparam OutputRange Output range type
146 * @tparam TransformFunc Transform function type
147 * @param input_range Range of input elements
148 * @param output_range Range to store transformed results
149 * @param transform_func Function to apply to each input element
150 * @return Task handle for the parallel operation
151 */
152 template <std::ranges::range InputRange, std::ranges::range OutputRange,
153 std::invocable<std::ranges::range_reference_t<InputRange>> TransformFunc>
154 Task Transform(const InputRange& input_range, OutputRange& output_range, TransformFunc&& transform_func) {
155 return Task(taskflow_.transform(std::ranges::begin(input_range), std::ranges::end(input_range),
156 std::ranges::begin(output_range), std::forward<TransformFunc>(transform_func)));
157 }
158
159 /**
160 * @brief Creates a parallel reduction task that combines elements using a binary operation.
161 * @tparam R Range type
162 * @tparam T Result type
163 * @tparam BinaryOp Binary operation type
164 * @param range Range of elements to reduce
165 * @param init Initial value and storage for the result
166 * @param binary_op Binary function to combine elements
167 * @return Task handle for the parallel operation
168 */
169 template <std::ranges::range R, typename T, typename BinaryOp>
170 requires std::invocable<BinaryOp, T, std::ranges::range_reference_t<R>>
171 Task Reduce(const R& range, T& init, BinaryOp&& binary_op) {
172 return Task(
173 taskflow_.reduce(std::ranges::begin(range), std::ranges::end(range), init, std::forward<BinaryOp>(binary_op)));
174 }
175
176 /**
177 * @brief Creates a parallel sort task for the given range.
178 * @tparam R Random access range type
179 * @tparam Compare Comparator type
180 * @param range Range of elements to sort
181 * @param comparator Comparison function (default: std::less<>)
182 * @return Task handle for the parallel operation
183 */
184 template <std::ranges::random_access_range R, typename Compare = std::less<>>
185 requires std::predicate<Compare, std::ranges::range_reference_t<R>, std::ranges::range_reference_t<R>>
186 Task Sort(R& range, Compare&& comparator = Compare{});
187
188 /**
189 * @brief Removes a task from this graph.
190 * @param task Task to remove
191 */
192 void RemoveTask(const Task& task) { taskflow_.erase(task.UnderlyingTask()); }
193
194 /**
195 * @brief Removes a dependency relationship between two tasks.
196 * @param from Source task (dependent)
197 * @param to Target task (successor)
198 */
199 void RemoveDependency(const Task& from, const Task& to) {
200 taskflow_.remove_dependency(from.UnderlyingTask(), to.UnderlyingTask());
201 }
202
203 /**
204 * @brief Creates a module task that encapsulates another task graph.
205 * @param other_graph Task graph to compose into this graph
206 * @return Task handle representing the composed graph
207 */
208 Task Compose(TaskGraph& other_graph) { return Task(taskflow_.composed_of(other_graph.UnderlyingTaskflow())); }
209
210 /**
211 * @brief Dumps the task graph to a DOT format string.
212 * @return String representation of the graph in DOT format
213 */
214 [[nodiscard]] std::string Dump() const { return taskflow_.dump(); }
215
216 /**
217 * @brief Sets the name of this task graph.
218 * @warning Triggers an assertion if the name is empty.
219 * @param name Name for the graph (must not be empty)
220 */
221 void Name(std::string_view name);
222
223 /**
224 * @brief Checks if this graph has no tasks.
225 * @return True if the graph is empty, false otherwise
226 */
227 [[nodiscard]] bool Empty() const { return taskflow_.empty(); }
228
229 /**
230 * @brief Gets the number of tasks in this graph.
231 * @return Count of tasks
232 */
233 [[nodiscard]] size_t TaskCount() const { return taskflow_.num_tasks(); }
234
235 /**
236 * @brief Gets the name of task graph.
237 * @return Name of the graph
238 */
239 [[nodiscard]] const std::string& Name() const { return taskflow_.name(); }
240
241private:
242 [[nodiscard]] tf::Taskflow& UnderlyingTaskflow() noexcept { return taskflow_; }
243 [[nodiscard]] const tf::Taskflow& UnderlyingTaskflow() const noexcept { return taskflow_; }
244
245 tf::Taskflow taskflow_;
246
247 friend class SubTaskGraph;
248 friend class Executor;
249};
250
251template <typename Visitor>
252 requires std::invocable<Visitor, Task&>
253inline void TaskGraph::ForEachTask(const Visitor& visitor) const {
254 taskflow_.for_each_task([&visitor](const tf::Task& tf_task) {
255 Task helios_task(tf_task);
256 std::invoke(visitor, helios_task);
257 });
258}
259
260template <std::ranges::range R>
261 requires std::same_as<std::ranges::range_value_t<R>, Task>
262inline void TaskGraph::Linearize(const R& tasks) {
263 std::vector<tf::Task> tf_tasks;
264 if constexpr (std::ranges::sized_range<R>) {
265 tf_tasks.reserve(std::ranges::size(tasks));
266 }
267
268 for (const auto& task : tasks) {
269 tf_tasks.push_back(task.UnderlyingTask());
270 }
271
272 taskflow_.linearize(tf_tasks);
273}
274
275template <std::ranges::random_access_range R, typename Compare>
276 requires std::predicate<Compare, std::ranges::range_reference_t<R>, std::ranges::range_reference_t<R>>
277inline Task TaskGraph::Sort(R& range, Compare&& comparator) {
278 if constexpr (std::same_as<std::remove_cvref_t<Compare>, std::less<>>) {
279 return Task(taskflow_.sort(std::ranges::begin(range), std::ranges::end(range)));
280 } else {
281 return Task(taskflow_.sort(std::ranges::begin(range), std::ranges::end(range), std::forward<Compare>(comparator)));
282 }
283}
284
285inline void TaskGraph::Name(std::string_view name) {
286 HELIOS_ASSERT(!name.empty(), "Failed to set task graph name: 'name' cannot be empty!");
287 taskflow_.name(std::string(name));
288}
289
290} // namespace helios::async
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
Manages worker threads and executes task graphs using work-stealing scheduling.
Definition executor.hpp:33
Dynamic task graph that can be created within the execution of a task.
Represents a task dependency graph that can be executed by an Executor.
Task ForEach(const R &range, C &&callable)
Creates a parallel for-each task over the given range.
const std::string & Name() const
Gets the name of task graph.
Task ForEachIndex(I start, I end, I step, C &&callable)
Creates a parallel for-each task over an index range.
TaskGraph(TaskGraph &&) noexcept=default
bool Empty() const
Checks if this graph has no tasks.
Task CreatePlaceholder()
Creates a placeholder task with no assigned work.
TaskGraph(std::string_view name="TaskGraph")
Constructs a task graph with the given name.
Task Transform(const InputRange &input_range, OutputRange &output_range, TransformFunc &&transform_func)
Creates a parallel transform task that applies a function to each element.
Task Reduce(const R &range, T &init, BinaryOp &&binary_op)
Creates a parallel reduction task that combines elements using a binary operation.
Task Compose(TaskGraph &other_graph)
Creates a module task that encapsulates another task graph.
Task Sort(R &range, Compare &&comparator=Compare{})
Creates a parallel sort task for the given range.
void RemoveTask(const Task &task)
Removes a task from this graph.
void RemoveDependency(const Task &from, const Task &to)
Removes a dependency relationship between two tasks.
std::string Dump() const
Dumps the task graph to a DOT format string.
void ForEachTask(const Visitor &visitor) const
Applies a visitor function to each task in this graph.
void Linearize(const R &tasks)
Creates linear dependencies between tasks in the given range.
Task EmplaceTask(C &&callable)
Creates a static task with the given callable.
auto EmplaceTasks(Cs &&... callables) -> std::array< Task, sizeof...(Cs)>
Creates multiple tasks from a list of callables.
TaskGraph(const TaskGraph &)=delete
size_t TaskCount() const
Gets the number of tasks in this graph.
Represents a single task within a task graph.
Definition task.hpp:28
Concept for any valid task callable.
Definition common.hpp:86
STL namespace.