aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/solutions/tasking/tasktree.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/solutions/tasking/tasktree.h')
-rw-r--r--src/libs/solutions/tasking/tasktree.h468
1 files changed, 468 insertions, 0 deletions
diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h
new file mode 100644
index 0000000000..647c680b5b
--- /dev/null
+++ b/src/libs/solutions/tasking/tasktree.h
@@ -0,0 +1,468 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "tasking_global.h"
+
+#include <QHash>
+#include <QObject>
+#include <QSharedPointer>
+
+QT_BEGIN_NAMESPACE
+template <class T>
+class QFuture;
+QT_END_NAMESPACE
+
+namespace Tasking {
+
+Q_NAMESPACE_EXPORT(TASKING_EXPORT)
+
+class ExecutionContextActivator;
+class TaskContainer;
+class TaskTreePrivate;
+
+class TASKING_EXPORT TaskInterface : public QObject
+{
+ Q_OBJECT
+
+public:
+ TaskInterface() = default;
+ virtual void start() = 0;
+
+signals:
+ void done(bool success);
+};
+
+class TASKING_EXPORT TreeStorageBase
+{
+public:
+ bool isValid() const;
+
+protected:
+ using StorageConstructor = std::function<void *(void)>;
+ using StorageDestructor = std::function<void(void *)>;
+
+ TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor);
+ void *activeStorageVoid() const;
+
+private:
+ int createStorage() const;
+ void deleteStorage(int id) const;
+ void activateStorage(int id) const;
+
+ friend bool operator==(const TreeStorageBase &first, const TreeStorageBase &second)
+ { return first.m_storageData == second.m_storageData; }
+
+ friend bool operator!=(const TreeStorageBase &first, const TreeStorageBase &second)
+ { return first.m_storageData != second.m_storageData; }
+
+ friend size_t qHash(const TreeStorageBase &storage, uint seed = 0)
+ { return size_t(storage.m_storageData.get()) ^ seed; }
+
+ struct StorageData {
+ ~StorageData();
+ StorageConstructor m_constructor = {};
+ StorageDestructor m_destructor = {};
+ QHash<int, void *> m_storageHash = {};
+ int m_activeStorage = 0; // 0 means no active storage
+ int m_storageCounter = 0;
+ };
+ QSharedPointer<StorageData> m_storageData;
+ friend ExecutionContextActivator;
+ friend TaskContainer;
+ friend TaskTreePrivate;
+};
+
+template <typename StorageStruct>
+class TreeStorage : public TreeStorageBase
+{
+public:
+ TreeStorage() : TreeStorageBase(TreeStorage::ctor(), TreeStorage::dtor()) {}
+ StorageStruct &operator*() const noexcept { return *activeStorage(); }
+ StorageStruct *operator->() const noexcept { return activeStorage(); }
+ StorageStruct *activeStorage() const {
+ return static_cast<StorageStruct *>(activeStorageVoid());
+ }
+
+private:
+ static StorageConstructor ctor() { return [] { return new StorageStruct; }; }
+ static StorageDestructor dtor() {
+ return [](void *storage) { delete static_cast<StorageStruct *>(storage); };
+ }
+};
+
+// WorkflowPolicy:
+// 1. When all children finished with done -> report done, otherwise:
+// a) Report error on first error and stop executing other children (including their subtree).
+// b) On first error - continue executing all children and report error afterwards.
+// 2. When all children finished with error -> report error, otherwise:
+// a) Report done on first done and stop executing other children (including their subtree).
+// b) On first done - continue executing all children and report done afterwards.
+// 3. Stops on first finished child. In sequential mode it will never run other children then the first one.
+// Useful only in parallel mode.
+// 4. Always run all children, let them finish, ignore their results and report done afterwards.
+// 5. Always run all children, let them finish, ignore their results and report error afterwards.
+
+enum class WorkflowPolicy {
+ StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done).
+ ContinueOnError, // 1b - The same, but children execution continues. Reports done when no children.
+ StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error).
+ ContinueOnDone, // 2b - The same, but children execution continues. Reports error when no children.
+ StopOnFinished, // 3 - Stops on first finished child and report its result.
+ FinishAllAndDone, // 4 - Reports done after all children finished.
+ FinishAllAndError // 5 - Reports error after all children finished.
+};
+Q_ENUM_NS(WorkflowPolicy);
+
+enum class TaskAction
+{
+ Continue,
+ StopWithDone,
+ StopWithError
+};
+Q_ENUM_NS(TaskAction);
+
+class TASKING_EXPORT TaskItem
+{
+public:
+ // Internal, provided by QTC_DECLARE_CUSTOM_TASK
+ using TaskCreateHandler = std::function<TaskInterface *(void)>;
+ // Called prior to task start, just after createHandler
+ using TaskSetupHandler = std::function<TaskAction(TaskInterface &)>;
+ // Called on task done / error
+ using TaskEndHandler = std::function<void(const TaskInterface &)>;
+ // Called when group entered
+ using GroupSetupHandler = std::function<TaskAction()>;
+ // Called when group done / error
+ using GroupEndHandler = std::function<void()>;
+
+ struct TaskHandler {
+ TaskCreateHandler m_createHandler;
+ TaskSetupHandler m_setupHandler = {};
+ TaskEndHandler m_doneHandler = {};
+ TaskEndHandler m_errorHandler = {};
+ };
+
+ struct GroupHandler {
+ GroupSetupHandler m_setupHandler;
+ GroupEndHandler m_doneHandler = {};
+ GroupEndHandler m_errorHandler = {};
+ };
+
+ struct GroupData {
+ GroupHandler m_groupHandler = {};
+ std::optional<int> m_parallelLimit = {};
+ std::optional<WorkflowPolicy> m_workflowPolicy = {};
+ };
+
+ QList<TaskItem> children() const { return m_children; }
+ GroupData groupData() const { return m_groupData; }
+ QList<TreeStorageBase> storageList() const { return m_storageList; }
+ TaskHandler taskHandler() const { return m_taskHandler; }
+
+protected:
+ enum class Type {
+ Group,
+ GroupData,
+ Storage,
+ TaskHandler
+ };
+
+ TaskItem() = default;
+ TaskItem(const GroupData &data)
+ : m_type(Type::GroupData)
+ , m_groupData(data) {}
+ TaskItem(const TreeStorageBase &storage)
+ : m_type(Type::Storage)
+ , m_storageList{storage} {}
+ TaskItem(const TaskHandler &handler)
+ : m_type(Type::TaskHandler)
+ , m_taskHandler(handler) {}
+ void addChildren(const QList<TaskItem> &children);
+
+ void setTaskSetupHandler(const TaskSetupHandler &handler);
+ void setTaskDoneHandler(const TaskEndHandler &handler);
+ void setTaskErrorHandler(const TaskEndHandler &handler);
+ static TaskItem groupHandler(const GroupHandler &handler) { return TaskItem({handler}); }
+ static TaskItem parallelLimit(int limit) { return TaskItem({{}, limit}); }
+ static TaskItem workflowPolicy(WorkflowPolicy policy) { return TaskItem({{}, {}, policy}); }
+ static TaskItem withTimeout(const TaskItem &item, std::chrono::milliseconds timeout,
+ const GroupEndHandler &handler = {});
+
+private:
+ Type m_type = Type::Group;
+ QList<TaskItem> m_children;
+ GroupData m_groupData;
+ QList<TreeStorageBase> m_storageList;
+ TaskHandler m_taskHandler;
+};
+
+class TASKING_EXPORT Group : public TaskItem
+{
+public:
+ Group(const QList<TaskItem> &children) { addChildren(children); }
+ Group(std::initializer_list<TaskItem> children) { addChildren(children); }
+
+ // GroupData related:
+ template <typename SetupHandler>
+ static TaskItem onGroupSetup(SetupHandler &&handler) {
+ return groupHandler({wrapGroupSetup(std::forward<SetupHandler>(handler))});
+ }
+ static TaskItem onGroupDone(const GroupEndHandler &handler) {
+ return groupHandler({{}, handler});
+ }
+ static TaskItem onGroupError(const GroupEndHandler &handler) {
+ return groupHandler({{}, {}, handler});
+ }
+ using TaskItem::parallelLimit; // Default: 1 (sequential). 0 means unlimited (parallel).
+ using TaskItem::workflowPolicy; // Default: WorkflowPolicy::StopOnError.
+
+ TaskItem withTimeout(std::chrono::milliseconds timeout,
+ const GroupEndHandler &handler = {}) const {
+ return TaskItem::withTimeout(*this, timeout, handler);
+ }
+
+private:
+ template<typename SetupHandler>
+ static GroupSetupHandler wrapGroupSetup(SetupHandler &&handler)
+ {
+ static constexpr bool isDynamic
+ = std::is_same_v<TaskAction, std::invoke_result_t<std::decay_t<SetupHandler>>>;
+ constexpr bool isVoid
+ = std::is_same_v<void, std::invoke_result_t<std::decay_t<SetupHandler>>>;
+ static_assert(isDynamic || isVoid,
+ "Group setup handler needs to take no arguments and has to return "
+ "void or TaskAction. The passed handler doesn't fulfill these requirements.");
+ return [=] {
+ if constexpr (isDynamic)
+ return std::invoke(handler);
+ std::invoke(handler);
+ return TaskAction::Continue;
+ };
+ };
+};
+
+template <typename SetupHandler>
+static TaskItem onGroupSetup(SetupHandler &&handler)
+{
+ return Group::onGroupSetup(std::forward<SetupHandler>(handler));
+}
+
+TASKING_EXPORT TaskItem onGroupDone(const TaskItem::GroupEndHandler &handler);
+TASKING_EXPORT TaskItem onGroupError(const TaskItem::GroupEndHandler &handler);
+TASKING_EXPORT TaskItem parallelLimit(int limit);
+TASKING_EXPORT TaskItem workflowPolicy(WorkflowPolicy policy);
+
+TASKING_EXPORT extern const TaskItem sequential;
+TASKING_EXPORT extern const TaskItem parallel;
+
+TASKING_EXPORT extern const TaskItem stopOnError;
+TASKING_EXPORT extern const TaskItem continueOnError;
+TASKING_EXPORT extern const TaskItem stopOnDone;
+TASKING_EXPORT extern const TaskItem continueOnDone;
+TASKING_EXPORT extern const TaskItem stopOnFinished;
+TASKING_EXPORT extern const TaskItem finishAllAndDone;
+TASKING_EXPORT extern const TaskItem finishAllAndError;
+
+class TASKING_EXPORT Storage : public TaskItem
+{
+public:
+ Storage(const TreeStorageBase &storage) : TaskItem(storage) { }
+};
+
+// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount()
+class TASKING_EXPORT Sync : public Group
+{
+
+public:
+ template<typename Function>
+ Sync(Function &&function) : Group(init(std::forward<Function>(function))) {}
+
+private:
+ template<typename Function>
+ static QList<TaskItem> init(Function &&function) {
+ constexpr bool isInvocable = std::is_invocable_v<std::decay_t<Function>>;
+ static_assert(isInvocable,
+ "Sync element: The synchronous function can't take any arguments.");
+ constexpr bool isBool = std::is_same_v<bool, std::invoke_result_t<std::decay_t<Function>>>;
+ constexpr bool isVoid = std::is_same_v<void, std::invoke_result_t<std::decay_t<Function>>>;
+ static_assert(isBool || isVoid,
+ "Sync element: The synchronous function has to return void or bool.");
+ if constexpr (isBool) {
+ return {onGroupSetup([function] { return function() ? TaskAction::StopWithDone
+ : TaskAction::StopWithError; })};
+ }
+ return {onGroupSetup([function] { function(); return TaskAction::StopWithDone; })};
+ };
+};
+
+template <typename Task>
+class TaskAdapter : public TaskInterface
+{
+public:
+ using Type = Task;
+ TaskAdapter() = default;
+ Task *task() { return &m_task; }
+ const Task *task() const { return &m_task; }
+private:
+ Task m_task;
+};
+
+template <typename Adapter>
+class CustomTask : public TaskItem
+{
+public:
+ using Task = typename Adapter::Type;
+ using EndHandler = std::function<void(const Task &)>;
+ static Adapter *createAdapter() { return new Adapter; }
+ CustomTask() : TaskItem({&createAdapter}) {}
+ template <typename SetupFunction>
+ CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {})
+ : TaskItem({&createAdapter, wrapSetup(std::forward<SetupFunction>(function)),
+ wrapEnd(done), wrapEnd(error)}) {}
+
+ template <typename SetupFunction>
+ CustomTask &onSetup(SetupFunction &&function) {
+ setTaskSetupHandler(wrapSetup(std::forward<SetupFunction>(function)));
+ return *this;
+ }
+ CustomTask &onDone(const EndHandler &handler) {
+ setTaskDoneHandler(wrapEnd(handler));
+ return *this;
+ }
+ CustomTask &onError(const EndHandler &handler) {
+ setTaskErrorHandler(wrapEnd(handler));
+ return *this;
+ }
+
+ TaskItem withTimeout(std::chrono::milliseconds timeout,
+ const GroupEndHandler &handler = {}) const {
+ return TaskItem::withTimeout(*this, timeout, handler);
+ }
+
+private:
+ template<typename SetupFunction>
+ static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) {
+ static constexpr bool isDynamic = std::is_same_v<TaskAction,
+ std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>;
+ constexpr bool isVoid = std::is_same_v<void,
+ std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>;
+ static_assert(isDynamic || isVoid,
+ "Task setup handler needs to take (Task &) as an argument and has to return "
+ "void or TaskAction. The passed handler doesn't fulfill these requirements.");
+ return [=](TaskInterface &taskInterface) {
+ Adapter &adapter = static_cast<Adapter &>(taskInterface);
+ if constexpr (isDynamic)
+ return std::invoke(function, *adapter.task());
+ std::invoke(function, *adapter.task());
+ return TaskAction::Continue;
+ };
+ };
+
+ static TaskEndHandler wrapEnd(const EndHandler &handler) {
+ if (!handler)
+ return {};
+ return [handler](const TaskInterface &taskInterface) {
+ const Adapter &adapter = static_cast<const Adapter &>(taskInterface);
+ handler(*adapter.task());
+ };
+ };
+};
+
+class TaskTreePrivate;
+
+class TASKING_EXPORT TaskTree final : public QObject
+{
+ Q_OBJECT
+
+public:
+ TaskTree();
+ TaskTree(const Group &root);
+ ~TaskTree();
+
+ void setupRoot(const Group &root);
+
+ void start();
+ void stop();
+ bool isRunning() const;
+
+ // Helper methods. They execute a local event loop with ExcludeUserInputEvents.
+ // The passed future is used for listening to the cancel event.
+ // Don't use it in main thread. To be used in non-main threads or in auto tests.
+ bool runBlocking();
+ bool runBlocking(const QFuture<void> &future);
+ static bool runBlocking(const Group &recipe,
+ std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
+ static bool runBlocking(const Group &recipe, const QFuture<void> &future,
+ std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
+
+ int taskCount() const;
+ int progressMaximum() const { return taskCount(); }
+ int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded
+
+ template <typename StorageStruct, typename StorageHandler>
+ void onStorageSetup(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler) {
+ setupStorageHandler(storage,
+ wrapHandler<StorageStruct>(std::forward<StorageHandler>(handler)), {});
+ }
+ template <typename StorageStruct, typename StorageHandler>
+ void onStorageDone(const TreeStorage<StorageStruct> &storage, StorageHandler &&handler) {
+ setupStorageHandler(storage,
+ {}, wrapHandler<StorageStruct>(std::forward<StorageHandler>(handler)));
+ }
+
+signals:
+ void started();
+ void done();
+ void errorOccurred();
+ void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
+
+private:
+ using StorageVoidHandler = std::function<void(void *)>;
+ void setupStorageHandler(const TreeStorageBase &storage,
+ StorageVoidHandler setupHandler,
+ StorageVoidHandler doneHandler);
+ template <typename StorageStruct, typename StorageHandler>
+ StorageVoidHandler wrapHandler(StorageHandler &&handler) {
+ return [=](void *voidStruct) {
+ StorageStruct *storageStruct = static_cast<StorageStruct *>(voidStruct);
+ std::invoke(handler, storageStruct);
+ };
+ }
+
+ friend class TaskTreePrivate;
+ TaskTreePrivate *d;
+};
+
+class TASKING_EXPORT TaskTreeTaskAdapter : public TaskAdapter<TaskTree>
+{
+public:
+ TaskTreeTaskAdapter();
+ void start() final;
+};
+
+class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter<std::chrono::milliseconds>
+{
+public:
+ TimeoutTaskAdapter();
+ ~TimeoutTaskAdapter();
+ void start() final;
+
+private:
+ std::optional<int> m_timerId;
+};
+
+} // namespace Tasking
+
+#define TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)\
+namespace Tasking { using CustomTaskName = CustomTask<TaskAdapterClass>; }
+
+#define TASKING_DECLARE_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\
+namespace Tasking {\
+template <typename ...Args>\
+using CustomTaskName = CustomTask<TaskAdapterClass<Args...>>;\
+} // namespace Tasking
+
+TASKING_DECLARE_TASK(TaskTreeTask, TaskTreeTaskAdapter);
+TASKING_DECLARE_TASK(TimeoutTask, TimeoutTaskAdapter);