1// Copyright (C) 2024 Jarek Kobus
2// Copyright (C) 2024 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#ifndef TASKING_TASKTREE_H
6#define TASKING_TASKTREE_H
7
8//
9// W A R N I N G
10// -------------
11//
12// This file is not part of the Qt API. It exists purely as an
13// implementation detail. This header file may change from version to
14// version without notice, or even be removed.
15//
16// We mean it.
17//
18
19#include "tasking_global.h"
20
21#include <QtCore/QList>
22#include <QtCore/QObject>
23
24#include <memory>
25
26QT_BEGIN_NAMESPACE
27template <class T>
28class QFuture;
29
30namespace Tasking {
31
32Q_NAMESPACE
33
34// WorkflowPolicy:
35// 1. When all children finished with success -> report success, otherwise:
36// a) Report error on first error and stop executing other children (including their subtree).
37// b) On first error - continue executing all children and report error afterwards.
38// 2. When all children finished with error -> report error, otherwise:
39// a) Report success on first success and stop executing other children (including their subtree).
40// b) On first success - continue executing all children and report success afterwards.
41// 3. Stops on first finished child. In sequential mode it will never run other children then the first one.
42// Useful only in parallel mode.
43// 4. Always run all children, let them finish, ignore their results and report success afterwards.
44// 5. Always run all children, let them finish, ignore their results and report error afterwards.
45
46enum class WorkflowPolicy
47{
48 StopOnError, // 1a - Reports error on first child error, otherwise success (if all children were success).
49 ContinueOnError, // 1b - The same, but children execution continues. Reports success when no children.
50 StopOnSuccess, // 2a - Reports success on first child success, otherwise error (if all children were error).
51 ContinueOnSuccess, // 2b - The same, but children execution continues. Reports error when no children.
52 StopOnSuccessOrError, // 3 - Stops on first finished child and report its result.
53 FinishAllAndSuccess, // 4 - Reports success after all children finished.
54 FinishAllAndError // 5 - Reports error after all children finished.
55};
56Q_ENUM_NS(WorkflowPolicy)
57
58enum class SetupResult
59{
60 Continue,
61 StopWithSuccess,
62 StopWithError
63};
64Q_ENUM_NS(SetupResult)
65
66enum class DoneResult
67{
68 Success,
69 Error
70};
71Q_ENUM_NS(DoneResult)
72
73enum class DoneWith
74{
75 Success,
76 Error,
77 Cancel
78};
79Q_ENUM_NS(DoneWith)
80
81enum class CallDoneIf
82{
83 SuccessOrError,
84 Success,
85 Error
86};
87Q_ENUM_NS(CallDoneIf)
88
89TASKING_EXPORT DoneResult toDoneResult(bool success);
90
91class LoopData;
92class StorageData;
93class TaskTreePrivate;
94
95class TASKING_EXPORT TaskInterface : public QObject
96{
97 Q_OBJECT
98
99Q_SIGNALS:
100 void done(DoneResult result);
101
102private:
103 template <typename Task, typename Deleter> friend class TaskAdapter;
104 friend class TaskTreePrivate;
105 TaskInterface() = default;
106#ifdef Q_QDOC
107protected:
108#endif
109 virtual void start() = 0;
110};
111
112class TASKING_EXPORT Loop
113{
114public:
115 using Condition = std::function<bool(int)>; // Takes iteration, called prior to each iteration.
116 using ValueGetter = std::function<const void *(int)>; // Takes iteration, returns ptr to ref.
117
118 int iteration() const;
119
120protected:
121 Loop(); // LoopForever
122 Loop(int count, const ValueGetter &valueGetter = {}); // LoopRepeat, LoopList
123 Loop(const Condition &condition); // LoopUntil
124
125 const void *valuePtr() const;
126
127private:
128 friend class ExecutionContextActivator;
129 friend class TaskTreePrivate;
130 std::shared_ptr<LoopData> m_loopData;
131};
132
133class TASKING_EXPORT LoopForever final : public Loop
134{
135public:
136 LoopForever() : Loop() {}
137};
138
139class TASKING_EXPORT LoopRepeat final : public Loop
140{
141public:
142 LoopRepeat(int count) : Loop(count) {}
143};
144
145class TASKING_EXPORT LoopUntil final : public Loop
146{
147public:
148 LoopUntil(const Condition &condition) : Loop(condition) {}
149};
150
151template <typename T>
152class LoopList final : public Loop
153{
154public:
155 LoopList(const QList<T> &list) : Loop(list.size(), [list](int i) { return &list.at(i); }) {}
156 const T *operator->() const { return static_cast<const T *>(valuePtr()); }
157 const T &operator*() const { return *static_cast<const T *>(valuePtr()); }
158};
159
160class TASKING_EXPORT StorageBase
161{
162private:
163 using StorageConstructor = std::function<void *(void)>;
164 using StorageDestructor = std::function<void(void *)>;
165 using StorageHandler = std::function<void(void *)>;
166
167 StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor);
168
169 void *activeStorageVoid() const;
170
171 friend bool operator==(const StorageBase &first, const StorageBase &second)
172 { return first.m_storageData == second.m_storageData; }
173
174 friend bool operator!=(const StorageBase &first, const StorageBase &second)
175 { return first.m_storageData != second.m_storageData; }
176
177 friend size_t qHash(const StorageBase &storage, uint seed = 0)
178 { return size_t(storage.m_storageData.get()) ^ seed; }
179
180 std::shared_ptr<StorageData> m_storageData;
181
182 template <typename StorageStruct> friend class Storage;
183 friend class ExecutionContextActivator;
184 friend class StorageData;
185 friend class RuntimeContainer;
186 friend class TaskTree;
187 friend class TaskTreePrivate;
188};
189
190template <typename StorageStruct>
191class Storage final : public StorageBase
192{
193public:
194 Storage() : StorageBase(Storage::ctor(), Storage::dtor()) {}
195 StorageStruct &operator*() const noexcept { return *activeStorage(); }
196 StorageStruct *operator->() const noexcept { return activeStorage(); }
197 StorageStruct *activeStorage() const {
198 return static_cast<StorageStruct *>(activeStorageVoid());
199 }
200
201private:
202 static StorageConstructor ctor() { return [] { return new StorageStruct(); }; }
203 static StorageDestructor dtor() {
204 return [](void *storage) { delete static_cast<StorageStruct *>(storage); };
205 }
206};
207
208class TASKING_EXPORT GroupItem
209{
210public:
211 // Called when group entered, after group's storages are created
212 using GroupSetupHandler = std::function<SetupResult()>;
213 // Called when group done, before group's storages are deleted
214 using GroupDoneHandler = std::function<DoneResult(DoneWith)>;
215
216 template <typename StorageStruct>
217 GroupItem(const Storage<StorageStruct> &storage)
218 : m_type(Type::Storage)
219 , m_storageList{storage} {}
220
221 // TODO: Add tests.
222 GroupItem(const QList<GroupItem> &children) : m_type(Type::List) { addChildren(children); }
223 GroupItem(std::initializer_list<GroupItem> children) : m_type(Type::List) { addChildren(children); }
224
225protected:
226 GroupItem(const Loop &loop) : GroupItem(GroupData{.m_groupHandler: {}, .m_parallelLimit: {}, .m_workflowPolicy: {}, .m_loop: loop}) {}
227 // Internal, provided by CustomTask
228 using InterfaceCreateHandler = std::function<TaskInterface *(void)>;
229 // Called prior to task start, just after createHandler
230 using InterfaceSetupHandler = std::function<SetupResult(TaskInterface &)>;
231 // Called on task done, just before deleteLater
232 using InterfaceDoneHandler = std::function<DoneResult(const TaskInterface &, DoneWith)>;
233
234 struct TaskHandler {
235 InterfaceCreateHandler m_createHandler;
236 InterfaceSetupHandler m_setupHandler = {};
237 InterfaceDoneHandler m_doneHandler = {};
238 CallDoneIf m_callDoneIf = CallDoneIf::SuccessOrError;
239 };
240
241 struct GroupHandler {
242 GroupSetupHandler m_setupHandler;
243 GroupDoneHandler m_doneHandler = {};
244 CallDoneIf m_callDoneIf = CallDoneIf::SuccessOrError;
245 };
246
247 struct GroupData {
248 GroupHandler m_groupHandler = {};
249 std::optional<int> m_parallelLimit = {};
250 std::optional<WorkflowPolicy> m_workflowPolicy = {};
251 std::optional<Loop> m_loop = {};
252 };
253
254 enum class Type {
255 List,
256 Group,
257 GroupData,
258 Storage,
259 TaskHandler
260 };
261
262 GroupItem() = default;
263 GroupItem(Type type) : m_type(type) { }
264 GroupItem(const GroupData &data)
265 : m_type(Type::GroupData)
266 , m_groupData(data) {}
267 GroupItem(const TaskHandler &handler)
268 : m_type(Type::TaskHandler)
269 , m_taskHandler(handler) {}
270 void addChildren(const QList<GroupItem> &children);
271
272 static GroupItem groupHandler(const GroupHandler &handler) { return GroupItem({.m_groupHandler: handler}); }
273
274 // Checks if Function may be invoked with Args and if Function's return type is Result.
275 template <typename Result, typename Function, typename ...Args,
276 typename DecayedFunction = std::decay_t<Function>>
277 static constexpr bool isInvocable()
278 {
279 // Note, that std::is_invocable_r_v doesn't check Result type properly.
280 if constexpr (std::is_invocable_r_v<Result, DecayedFunction, Args...>)
281 return std::is_same_v<Result, std::invoke_result_t<DecayedFunction, Args...>>;
282 return false;
283 }
284
285private:
286 friend class ContainerNode;
287 friend class For;
288 friend class TaskNode;
289 friend class TaskTreePrivate;
290 friend class ParallelLimitFunctor;
291 friend class WorkflowPolicyFunctor;
292 Type m_type = Type::Group;
293 QList<GroupItem> m_children;
294 GroupData m_groupData;
295 QList<StorageBase> m_storageList;
296 TaskHandler m_taskHandler;
297};
298
299class TASKING_EXPORT ExecutableItem : public GroupItem
300{
301public:
302 ExecutableItem withTimeout(std::chrono::milliseconds timeout,
303 const std::function<void()> &handler = {}) const;
304 ExecutableItem withLog(const QString &logName) const;
305 template <typename SenderSignalPairGetter>
306 ExecutableItem withCancel(SenderSignalPairGetter &&getter) const
307 {
308 const auto connectWrapper = [getter](QObject *guard, const std::function<void()> &trigger) {
309 const auto senderSignalPair = getter();
310 QObject::connect(senderSignalPair.first, senderSignalPair.second, guard, [trigger] {
311 trigger();
312 }, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
313 };
314 return withCancelImpl(connectWrapper);
315 }
316
317protected:
318 ExecutableItem() = default;
319 ExecutableItem(const TaskHandler &handler) : GroupItem(handler) {}
320
321private:
322 TASKING_EXPORT friend ExecutableItem operator!(const ExecutableItem &item);
323 TASKING_EXPORT friend ExecutableItem operator&&(const ExecutableItem &first,
324 const ExecutableItem &second);
325 TASKING_EXPORT friend ExecutableItem operator||(const ExecutableItem &first,
326 const ExecutableItem &second);
327 TASKING_EXPORT friend ExecutableItem operator&&(const ExecutableItem &item, DoneResult result);
328 TASKING_EXPORT friend ExecutableItem operator||(const ExecutableItem &item, DoneResult result);
329
330 ExecutableItem withCancelImpl(
331 const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const;
332};
333
334class TASKING_EXPORT Group : public ExecutableItem
335{
336public:
337 Group(const QList<GroupItem> &children) { addChildren(children); }
338 Group(std::initializer_list<GroupItem> children) { addChildren(children); }
339
340 // GroupData related:
341 template <typename Handler>
342 static GroupItem onGroupSetup(Handler &&handler) {
343 return groupHandler(handler: {wrapGroupSetup(std::forward<Handler>(handler))});
344 }
345 template <typename Handler>
346 static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) {
347 return groupHandler(handler: {{}, wrapGroupDone(std::forward<Handler>(handler)), callDoneIf});
348 }
349
350private:
351 template <typename Handler>
352 static GroupSetupHandler wrapGroupSetup(Handler &&handler)
353 {
354 // R, V stands for: Setup[R]esult, [V]oid
355 static constexpr bool isR = isInvocable<SetupResult, Handler>();
356 static constexpr bool isV = isInvocable<void, Handler>();
357 static_assert(isR || isV,
358 "Group setup handler needs to take no arguments and has to return void or SetupResult. "
359 "The passed handler doesn't fulfill these requirements.");
360 return [handler] {
361 if constexpr (isR)
362 return std::invoke(handler);
363 std::invoke(handler);
364 return SetupResult::Continue;
365 };
366 }
367 template <typename Handler>
368 static GroupDoneHandler wrapGroupDone(Handler &&handler)
369 {
370 static constexpr bool isDoneResultType = std::is_same_v<Handler, DoneResult>;
371 // R, B, V, D stands for: Done[R]esult, [B]ool, [V]oid, [D]oneWith
372 static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>();
373 static constexpr bool isR = isInvocable<DoneResult, Handler>();
374 static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>();
375 static constexpr bool isB = isInvocable<bool, Handler>();
376 static constexpr bool isVD = isInvocable<void, Handler, DoneWith>();
377 static constexpr bool isV = isInvocable<void, Handler>();
378 static_assert(isDoneResultType || isRD || isR || isBD || isB || isVD || isV,
379 "Group done handler needs to take (DoneWith) or (void) as an argument and has to "
380 "return void, bool or DoneResult. Alternatively, it may be of DoneResult type. "
381 "The passed handler doesn't fulfill these requirements.");
382 return [handler](DoneWith result) {
383 if constexpr (isDoneResultType)
384 return handler;
385 if constexpr (isRD)
386 return std::invoke(handler, result);
387 if constexpr (isR)
388 return std::invoke(handler);
389 if constexpr (isBD)
390 return toDoneResult(std::invoke(handler, result));
391 if constexpr (isB)
392 return toDoneResult(std::invoke(handler));
393 if constexpr (isVD)
394 std::invoke(handler, result);
395 else if constexpr (isV)
396 std::invoke(handler);
397 return toDoneResult(success: result == DoneWith::Success);
398 };
399 }
400};
401
402template <typename Handler>
403static GroupItem onGroupSetup(Handler &&handler)
404{
405 return Group::onGroupSetup(std::forward<Handler>(handler));
406}
407
408template <typename Handler>
409static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
410{
411 return Group::onGroupDone(std::forward<Handler>(handler), callDoneIf);
412}
413
414class TASKING_EXPORT ParallelLimitFunctor
415{
416public:
417 // Default: 1 (sequential). 0 means unlimited (parallel).
418 GroupItem operator()(int limit) const;
419};
420
421class TASKING_EXPORT WorkflowPolicyFunctor
422{
423public:
424 // Default: WorkflowPolicy::StopOnError.
425 GroupItem operator()(WorkflowPolicy policy) const;
426};
427
428TASKING_EXPORT extern const ParallelLimitFunctor parallelLimit;
429TASKING_EXPORT extern const WorkflowPolicyFunctor workflowPolicy;
430
431TASKING_EXPORT extern const GroupItem sequential;
432TASKING_EXPORT extern const GroupItem parallel;
433TASKING_EXPORT extern const GroupItem parallelIdealThreadCountLimit;
434
435TASKING_EXPORT extern const GroupItem stopOnError;
436TASKING_EXPORT extern const GroupItem continueOnError;
437TASKING_EXPORT extern const GroupItem stopOnSuccess;
438TASKING_EXPORT extern const GroupItem continueOnSuccess;
439TASKING_EXPORT extern const GroupItem stopOnSuccessOrError;
440TASKING_EXPORT extern const GroupItem finishAllAndSuccess;
441TASKING_EXPORT extern const GroupItem finishAllAndError;
442
443TASKING_EXPORT extern const GroupItem nullItem;
444TASKING_EXPORT extern const ExecutableItem successItem;
445TASKING_EXPORT extern const ExecutableItem errorItem;
446
447class TASKING_EXPORT For : public Group
448{
449public:
450 template <typename ...Args>
451 For(const Loop &loop, const Args &...args)
452 : Group(withLoop(loop, args...)) { }
453
454protected:
455 For(const Loop &loop, const QList<GroupItem> &children) : Group({loop, children}) {}
456 For(const Loop &loop, std::initializer_list<GroupItem> children) : Group({loop, children}) {}
457
458private:
459 template <typename ...Args>
460 QList<GroupItem> withLoop(const Loop &loop, const Args &...args) {
461 QList<GroupItem> children{GroupItem(loop)};
462 appendChildren(std::make_tuple(args...), &children);
463 return children;
464 }
465
466 template <typename Tuple, std::size_t N = 0>
467 void appendChildren(const Tuple &tuple, QList<GroupItem> *children) {
468 constexpr auto TupleSize = std::tuple_size_v<Tuple>;
469 if constexpr (TupleSize > 0) {
470 // static_assert(workflowPolicyCount<Tuple>() <= 1, "Too many workflow policies in one group.");
471 children->append(std::get<N>(tuple));
472 if constexpr (N + 1 < TupleSize)
473 appendChildren<Tuple, N + 1>(tuple, children);
474 }
475 }
476};
477
478class TASKING_EXPORT Forever final : public For
479{
480public:
481 Forever(const QList<GroupItem> &children) : For(LoopForever(), children) {}
482 Forever(std::initializer_list<GroupItem> children) : For(LoopForever(), children) {}
483};
484
485// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount()
486class TASKING_EXPORT Sync final : public ExecutableItem
487{
488public:
489 template <typename Handler>
490 Sync(Handler &&handler) {
491 addChildren(children: { onGroupDone(wrapHandler(std::forward<Handler>(handler))) });
492 }
493
494private:
495 template <typename Handler>
496 static auto wrapHandler(Handler &&handler) {
497 // R, B, V stands for: Done[R]esult, [B]ool, [V]oid
498 static constexpr bool isR = isInvocable<DoneResult, Handler>();
499 static constexpr bool isB = isInvocable<bool, Handler>();
500 static constexpr bool isV = isInvocable<void, Handler>();
501 static_assert(isR || isB || isV,
502 "Sync handler needs to take no arguments and has to return void, bool or DoneResult. "
503 "The passed handler doesn't fulfill these requirements.");
504 return handler;
505 }
506};
507
508template <typename Task, typename Deleter = std::default_delete<Task>>
509class TaskAdapter : public TaskInterface
510{
511protected:
512 TaskAdapter() : m_task(new Task) {}
513 Task *task() { return m_task.get(); }
514 const Task *task() const { return m_task.get(); }
515
516private:
517 using TaskType = Task;
518 using DeleterType = Deleter;
519 template <typename Adapter> friend class CustomTask;
520 std::unique_ptr<Task, Deleter> m_task;
521};
522
523template <typename Adapter>
524class CustomTask final : public ExecutableItem
525{
526public:
527 using Task = typename Adapter::TaskType;
528 using Deleter = typename Adapter::DeleterType;
529 static_assert(std::is_base_of_v<TaskAdapter<Task, Deleter>, Adapter>,
530 "The Adapter type for the CustomTask<Adapter> needs to be derived from "
531 "TaskAdapter<Task>.");
532 using TaskSetupHandler = std::function<SetupResult(Task &)>;
533 using TaskDoneHandler = std::function<DoneResult(const Task &, DoneWith)>;
534
535 template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler>
536 CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(),
537 CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
538 : ExecutableItem({&createAdapter, wrapSetup(std::forward<SetupHandler>(setup)),
539 wrapDone(std::forward<DoneHandler>(done)), callDoneIf})
540 {}
541
542private:
543 static Adapter *createAdapter() { return new Adapter; }
544
545 template <typename Handler>
546 static InterfaceSetupHandler wrapSetup(Handler &&handler) {
547 if constexpr (std::is_same_v<Handler, TaskSetupHandler>)
548 return {}; // When user passed {} for the setup handler.
549 // R, V stands for: Setup[R]esult, [V]oid
550 static constexpr bool isR = isInvocable<SetupResult, Handler, Task &>();
551 static constexpr bool isV = isInvocable<void, Handler, Task &>();
552 static_assert(isR || isV,
553 "Task setup handler needs to take (Task &) as an argument and has to return void or "
554 "SetupResult. The passed handler doesn't fulfill these requirements.");
555 return [handler](TaskInterface &taskInterface) {
556 Adapter &adapter = static_cast<Adapter &>(taskInterface);
557 if constexpr (isR)
558 return std::invoke(handler, *adapter.task());
559 std::invoke(handler, *adapter.task());
560 return SetupResult::Continue;
561 };
562 }
563
564 template <typename Handler>
565 static InterfaceDoneHandler wrapDone(Handler &&handler) {
566 if constexpr (std::is_same_v<Handler, TaskDoneHandler>)
567 return {}; // User passed {} for the done handler.
568 static constexpr bool isDoneResultType = std::is_same_v<Handler, DoneResult>;
569 // R, B, V, T, D stands for: Done[R]esult, [B]ool, [V]oid, [T]ask, [D]oneWith
570 static constexpr bool isRTD = isInvocable<DoneResult, Handler, const Task &, DoneWith>();
571 static constexpr bool isRT = isInvocable<DoneResult, Handler, const Task &>();
572 static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>();
573 static constexpr bool isR = isInvocable<DoneResult, Handler>();
574 static constexpr bool isBTD = isInvocable<bool, Handler, const Task &, DoneWith>();
575 static constexpr bool isBT = isInvocable<bool, Handler, const Task &>();
576 static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>();
577 static constexpr bool isB = isInvocable<bool, Handler>();
578 static constexpr bool isVTD = isInvocable<void, Handler, const Task &, DoneWith>();
579 static constexpr bool isVT = isInvocable<void, Handler, const Task &>();
580 static constexpr bool isVD = isInvocable<void, Handler, DoneWith>();
581 static constexpr bool isV = isInvocable<void, Handler>();
582 static_assert(isDoneResultType || isRTD || isRT || isRD || isR
583 || isBTD || isBT || isBD || isB
584 || isVTD || isVT || isVD || isV,
585 "Task done handler needs to take (const Task &, DoneWith), (const Task &), "
586 "(DoneWith) or (void) as arguments and has to return void, bool or DoneResult. "
587 "Alternatively, it may be of DoneResult type. "
588 "The passed handler doesn't fulfill these requirements.");
589 return [handler](const TaskInterface &taskInterface, DoneWith result) {
590 if constexpr (isDoneResultType)
591 return handler;
592 const Adapter &adapter = static_cast<const Adapter &>(taskInterface);
593 if constexpr (isRTD)
594 return std::invoke(handler, *adapter.task(), result);
595 if constexpr (isRT)
596 return std::invoke(handler, *adapter.task());
597 if constexpr (isRD)
598 return std::invoke(handler, result);
599 if constexpr (isR)
600 return std::invoke(handler);
601 if constexpr (isBTD)
602 return toDoneResult(std::invoke(handler, *adapter.task(), result));
603 if constexpr (isBT)
604 return toDoneResult(std::invoke(handler, *adapter.task()));
605 if constexpr (isBD)
606 return toDoneResult(std::invoke(handler, result));
607 if constexpr (isB)
608 return toDoneResult(std::invoke(handler));
609 if constexpr (isVTD)
610 std::invoke(handler, *adapter.task(), result);
611 else if constexpr (isVT)
612 std::invoke(handler, *adapter.task());
613 else if constexpr (isVD)
614 std::invoke(handler, result);
615 else if constexpr (isV)
616 std::invoke(handler);
617 return toDoneResult(success: result == DoneWith::Success);
618 };
619 }
620};
621
622class TASKING_EXPORT TaskTree final : public QObject
623{
624 Q_OBJECT
625
626public:
627 TaskTree();
628 TaskTree(const Group &recipe);
629 ~TaskTree();
630
631 void setRecipe(const Group &recipe);
632
633 void start();
634 void cancel();
635 bool isRunning() const;
636
637 // Helper methods. They execute a local event loop with ExcludeUserInputEvents.
638 // The passed future is used for listening to the cancel event.
639 // Don't use it in main thread. To be used in non-main threads or in auto tests.
640 DoneWith runBlocking();
641 DoneWith runBlocking(const QFuture<void> &future);
642 static DoneWith runBlocking(const Group &recipe,
643 std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
644 static DoneWith runBlocking(const Group &recipe, const QFuture<void> &future,
645 std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
646
647 int asyncCount() const;
648 int taskCount() const;
649 int progressMaximum() const { return taskCount(); }
650 int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded
651
652 template <typename StorageStruct, typename Handler>
653 void onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler) {
654 static_assert(std::is_invocable_v<std::decay_t<Handler>, StorageStruct &>,
655 "Storage setup handler needs to take (Storage &) as an argument. "
656 "The passed handler doesn't fulfill this requirement.");
657 setupStorageHandler(storage,
658 setupHandler: wrapHandler<StorageStruct>(std::forward<Handler>(handler)), doneHandler: {});
659 }
660 template <typename StorageStruct, typename Handler>
661 void onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler) {
662 static_assert(std::is_invocable_v<std::decay_t<Handler>, const StorageStruct &>,
663 "Storage done handler needs to take (const Storage &) as an argument. "
664 "The passed handler doesn't fulfill this requirement.");
665 setupStorageHandler(storage, setupHandler: {},
666 doneHandler: wrapHandler<const StorageStruct>(std::forward<Handler>(handler)));
667 }
668
669Q_SIGNALS:
670 void started();
671 void done(DoneWith result);
672 void asyncCountChanged(int count);
673 void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
674
675private:
676 void setupStorageHandler(const StorageBase &storage,
677 StorageBase::StorageHandler setupHandler,
678 StorageBase::StorageHandler doneHandler);
679 template <typename StorageStruct, typename Handler>
680 StorageBase::StorageHandler wrapHandler(Handler &&handler) {
681 return [handler](void *voidStruct) {
682 auto *storageStruct = static_cast<StorageStruct *>(voidStruct);
683 std::invoke(handler, *storageStruct);
684 };
685 }
686
687 TaskTreePrivate *d;
688};
689
690class TASKING_EXPORT TaskTreeTaskAdapter : public TaskAdapter<TaskTree>
691{
692public:
693 TaskTreeTaskAdapter();
694
695private:
696 void start() final;
697};
698
699class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter<std::chrono::milliseconds>
700{
701public:
702 TimeoutTaskAdapter();
703 ~TimeoutTaskAdapter();
704
705private:
706 void start() final;
707 std::optional<int> m_timerId;
708};
709
710using TaskTreeTask = CustomTask<TaskTreeTaskAdapter>;
711using TimeoutTask = CustomTask<TimeoutTaskAdapter>;
712
713} // namespace Tasking
714
715QT_END_NAMESPACE
716
717#endif // TASKING_TASKTREE_H
718

source code of qtbase/src/assets/downloader/tasking/tasktree.h