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

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