// 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 #include #include namespace Tasking { class ExecutionContextActivator; class TaskContainer; class TaskNode; 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; using StorageDestructor = std::function; 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 m_storageHash = {}; int m_activeStorage = 0; // 0 means no active storage int m_storageCounter = 0; }; QSharedPointer m_storageData; friend TaskContainer; friend TaskTreePrivate; friend ExecutionContextActivator; }; template 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(activeStorageVoid()); } private: static StorageConstructor ctor() { return [] { return new StorageStruct; }; } static StorageDestructor dtor() { return [](void *storage) { delete static_cast(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. Always run all children, ignore their result and report done 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. When no children it reports done. StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error) ContinueOnDone, // 2b - The same, but children execution continues. When no children it reports done. (?) Optional // 3 - Always reports done after all children finished }; enum class TaskAction { Continue, StopWithDone, StopWithError }; class TASKING_EXPORT TaskItem { public: // Internal, provided by QTC_DECLARE_CUSTOM_TASK using TaskCreateHandler = std::function; // Called prior to task start, just after createHandler using TaskSetupHandler = std::function; // Called on task done / error using TaskEndHandler = std::function; // Called when group entered using GroupSetupHandler = std::function; // Called when group done / error using GroupEndHandler = std::function; 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 = {}; }; int parallelLimit() const { return m_parallelLimit; } WorkflowPolicy workflowPolicy() const { return m_workflowPolicy; } TaskHandler taskHandler() const { return m_taskHandler; } GroupHandler groupHandler() const { return m_groupHandler; } QList children() const { return m_children; } QList storageList() const { return m_storageList; } protected: enum class Type { Group, Storage, Limit, Policy, TaskHandler, GroupHandler }; TaskItem() = default; TaskItem(int parallelLimit) : m_type(Type::Limit) , m_parallelLimit(parallelLimit) {} TaskItem(WorkflowPolicy policy) : m_type(Type::Policy) , m_workflowPolicy(policy) {} TaskItem(const TaskHandler &handler) : m_type(Type::TaskHandler) , m_taskHandler(handler) {} TaskItem(const GroupHandler &handler) : m_type(Type::GroupHandler) , m_groupHandler(handler) {} TaskItem(const TreeStorageBase &storage) : m_type(Type::Storage) , m_storageList{storage} {} void addChildren(const QList &children); void setTaskSetupHandler(const TaskSetupHandler &handler); void setTaskDoneHandler(const TaskEndHandler &handler); void setTaskErrorHandler(const TaskEndHandler &handler); private: Type m_type = Type::Group; int m_parallelLimit = 1; // 0 means unlimited WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; TaskHandler m_taskHandler; GroupHandler m_groupHandler; QList m_storageList; QList m_children; }; class TASKING_EXPORT Group : public TaskItem { public: Group(const QList &children) { addChildren(children); } Group(std::initializer_list children) { addChildren(children); } }; class TASKING_EXPORT Storage : public TaskItem { public: Storage(const TreeStorageBase &storage) : TaskItem(storage) { } }; class TASKING_EXPORT ParallelLimit : public TaskItem { public: ParallelLimit(int parallelLimit) : TaskItem(qMax(parallelLimit, 0)) {} }; class TASKING_EXPORT Workflow : public TaskItem { public: Workflow(WorkflowPolicy policy) : TaskItem(policy) {} }; class TASKING_EXPORT OnGroupSetup : public TaskItem { public: template OnGroupSetup(SetupFunction &&function) : TaskItem({wrapSetup(std::forward(function))}) {} private: template static TaskItem::GroupSetupHandler wrapSetup(SetupFunction &&function) { static constexpr bool isDynamic = std::is_same_v>>; constexpr bool isVoid = std::is_same_v>>; 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(function); std::invoke(function); return TaskAction::Continue; }; }; }; class TASKING_EXPORT OnGroupDone : public TaskItem { public: OnGroupDone(const GroupEndHandler &handler) : TaskItem({{}, handler}) {} }; class TASKING_EXPORT OnGroupError : public TaskItem { public: OnGroupError(const GroupEndHandler &handler) : TaskItem({{}, {}, handler}) {} }; // Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() class TASKING_EXPORT Sync : public Group { public: template Sync(Function &&function) : Group(init(std::forward(function))) {} private: template static QList init(Function &&function) { constexpr bool isInvocable = std::is_invocable_v>; static_assert(isInvocable, "Sync element: The synchronous function can't take any arguments."); constexpr bool isBool = std::is_same_v>>; constexpr bool isVoid = std::is_same_v>>; 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; })}; }; }; TASKING_EXPORT extern ParallelLimit sequential; TASKING_EXPORT extern ParallelLimit parallel; TASKING_EXPORT extern Workflow stopOnError; TASKING_EXPORT extern Workflow continueOnError; TASKING_EXPORT extern Workflow stopOnDone; TASKING_EXPORT extern Workflow continueOnDone; TASKING_EXPORT extern Workflow optional; template 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 class CustomTask : public TaskItem { public: using Task = typename Adapter::Type; using EndHandler = std::function; static Adapter *createAdapter() { return new Adapter; } CustomTask() : TaskItem({&createAdapter}) {} template CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {}) : TaskItem({&createAdapter, wrapSetup(std::forward(function)), wrapEnd(done), wrapEnd(error)}) {} template CustomTask &onSetup(SetupFunction &&function) { setTaskSetupHandler(wrapSetup(std::forward(function))); return *this; } CustomTask &onDone(const EndHandler &handler) { setTaskDoneHandler(wrapEnd(handler)); return *this; } CustomTask &onError(const EndHandler &handler) { setTaskErrorHandler(wrapEnd(handler)); return *this; } private: template static TaskItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { static constexpr bool isDynamic = std::is_same_v, typename Adapter::Type &>>; constexpr bool isVoid = std::is_same_v, 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(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(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; int taskCount() const; int progressMaximum() const { return taskCount(); } int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded template void onStorageSetup(const TreeStorage &storage, StorageHandler &&handler) { setupStorageHandler(storage, wrapHandler(std::forward(handler)), {}); } template void onStorageDone(const TreeStorage &storage, StorageHandler &&handler) { setupStorageHandler(storage, {}, wrapHandler(std::forward(handler))); } signals: void started(); void done(); void errorOccurred(); void progressValueChanged(int value); // updated whenever task finished / skipped / stopped private: using StorageVoidHandler = std::function; void setupStorageHandler(const TreeStorageBase &storage, StorageVoidHandler setupHandler, StorageVoidHandler doneHandler); template StorageVoidHandler wrapHandler(StorageHandler &&handler) { return [=](void *voidStruct) { StorageStruct *storageStruct = static_cast(voidStruct); std::invoke(handler, storageStruct); }; } friend class TaskTreePrivate; TaskTreePrivate *d; }; class TASKING_EXPORT TaskTreeTaskAdapter : public TaskAdapter { public: TaskTreeTaskAdapter(); void start() final; }; } // namespace Tasking #define TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)\ namespace Tasking { using CustomTaskName = CustomTask; } #define TASKING_DECLARE_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)\ namespace Tasking {\ template \ using CustomTaskName = CustomTask>;\ } // namespace Tasking TASKING_DECLARE_TASK(TaskTreeTask, TaskTreeTaskAdapter);