2023-05-10 21:38:41 +02:00
|
|
|
// Copyright (C) 2023 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2022-10-12 14:30:24 +02:00
|
|
|
|
|
|
|
|
#include "tasktree.h"
|
|
|
|
|
|
2023-05-15 10:52:45 +02:00
|
|
|
#include <QEventLoop>
|
|
|
|
|
#include <QFutureWatcher>
|
2023-01-20 10:44:21 +01:00
|
|
|
#include <QSet>
|
2023-05-15 10:52:45 +02:00
|
|
|
#include <QTimer>
|
2023-01-20 10:44:21 +01:00
|
|
|
|
2023-05-10 19:54:52 +02:00
|
|
|
namespace Tasking {
|
|
|
|
|
|
2023-05-10 20:25:05 +02:00
|
|
|
// That's cut down qtcassert.{c,h} to avoid the dependency.
|
|
|
|
|
#define QTC_STRINGIFY_HELPER(x) #x
|
|
|
|
|
#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x)
|
|
|
|
|
#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__))
|
|
|
|
|
#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0)
|
|
|
|
|
#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0)
|
|
|
|
|
|
2023-05-10 18:39:25 +02:00
|
|
|
class Guard
|
|
|
|
|
{
|
|
|
|
|
Q_DISABLE_COPY(Guard)
|
|
|
|
|
public:
|
|
|
|
|
Guard() = default;
|
|
|
|
|
~Guard() { QTC_CHECK(m_lockCount == 0); }
|
|
|
|
|
bool isLocked() const { return m_lockCount; }
|
|
|
|
|
private:
|
|
|
|
|
int m_lockCount = 0;
|
|
|
|
|
friend class GuardLocker;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class GuardLocker
|
|
|
|
|
{
|
|
|
|
|
Q_DISABLE_COPY(GuardLocker)
|
|
|
|
|
public:
|
|
|
|
|
GuardLocker(Guard &guard) : m_guard(guard) { ++m_guard.m_lockCount; }
|
|
|
|
|
~GuardLocker() { --m_guard.m_lockCount; }
|
|
|
|
|
private:
|
|
|
|
|
Guard &m_guard;
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-19 23:28:56 +01:00
|
|
|
static TaskAction toTaskAction(bool success)
|
|
|
|
|
{
|
|
|
|
|
return success ? TaskAction::StopWithDone : TaskAction::StopWithError;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-21 12:20:21 +01:00
|
|
|
bool TreeStorageBase::isValid() const
|
|
|
|
|
{
|
|
|
|
|
return m_storageData && m_storageData->m_constructor && m_storageData->m_destructor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TreeStorageBase::TreeStorageBase(StorageConstructor ctor, StorageDestructor dtor)
|
|
|
|
|
: m_storageData(new StorageData{ctor, dtor}) { }
|
|
|
|
|
|
2022-11-22 15:21:45 +01:00
|
|
|
TreeStorageBase::StorageData::~StorageData()
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(m_storageHash.isEmpty());
|
2022-11-29 19:58:54 +01:00
|
|
|
for (void *ptr : std::as_const(m_storageHash))
|
2022-11-22 15:21:45 +01:00
|
|
|
m_destructor(ptr);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-21 12:20:21 +01:00
|
|
|
void *TreeStorageBase::activeStorageVoid() const
|
|
|
|
|
{
|
2023-04-30 12:18:00 +02:00
|
|
|
QTC_ASSERT(m_storageData->m_activeStorage, qWarning(
|
|
|
|
|
"The referenced storage is not reachable in the running tree. "
|
|
|
|
|
"A nullptr will be returned which might lead to a crash in the calling code. "
|
|
|
|
|
"It is possible that no storage was added to the tree, "
|
|
|
|
|
"or the storage is not reachable from where it is referenced.");
|
|
|
|
|
return nullptr);
|
2022-11-21 12:20:21 +01:00
|
|
|
const auto it = m_storageData->m_storageHash.constFind(m_storageData->m_activeStorage);
|
|
|
|
|
QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return nullptr);
|
|
|
|
|
return it.value();
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-24 21:33:56 +01:00
|
|
|
int TreeStorageBase::createStorage() const
|
2022-11-21 12:20:21 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_storageData->m_constructor, return 0); // TODO: add isValid()?
|
|
|
|
|
QTC_ASSERT(m_storageData->m_destructor, return 0);
|
|
|
|
|
QTC_ASSERT(m_storageData->m_activeStorage == 0, return 0); // TODO: should be allowed?
|
|
|
|
|
const int newId = ++m_storageData->m_storageCounter;
|
|
|
|
|
m_storageData->m_storageHash.insert(newId, m_storageData->m_constructor());
|
|
|
|
|
return newId;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-24 21:33:56 +01:00
|
|
|
void TreeStorageBase::deleteStorage(int id) const
|
2022-11-21 12:20:21 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_storageData->m_constructor, return); // TODO: add isValid()?
|
|
|
|
|
QTC_ASSERT(m_storageData->m_destructor, return);
|
|
|
|
|
QTC_ASSERT(m_storageData->m_activeStorage == 0, return); // TODO: should be allowed?
|
2022-11-29 19:58:54 +01:00
|
|
|
const auto it = m_storageData->m_storageHash.constFind(id);
|
|
|
|
|
QTC_ASSERT(it != m_storageData->m_storageHash.constEnd(), return);
|
2022-11-21 12:20:21 +01:00
|
|
|
m_storageData->m_destructor(it.value());
|
|
|
|
|
m_storageData->m_storageHash.erase(it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// passing 0 deactivates currently active storage
|
2023-01-24 21:33:56 +01:00
|
|
|
void TreeStorageBase::activateStorage(int id) const
|
2022-11-21 12:20:21 +01:00
|
|
|
{
|
|
|
|
|
if (id == 0) {
|
|
|
|
|
QTC_ASSERT(m_storageData->m_activeStorage, return);
|
|
|
|
|
m_storageData->m_activeStorage = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
QTC_ASSERT(m_storageData->m_activeStorage == 0, return);
|
|
|
|
|
const auto it = m_storageData->m_storageHash.find(id);
|
|
|
|
|
QTC_ASSERT(it != m_storageData->m_storageHash.end(), return);
|
|
|
|
|
m_storageData->m_activeStorage = id;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-06 16:07:16 +01:00
|
|
|
ParallelLimit sequential(1);
|
|
|
|
|
ParallelLimit parallel(0);
|
2022-11-11 10:37:02 +01:00
|
|
|
Workflow stopOnError(WorkflowPolicy::StopOnError);
|
|
|
|
|
Workflow continueOnError(WorkflowPolicy::ContinueOnError);
|
|
|
|
|
Workflow stopOnDone(WorkflowPolicy::StopOnDone);
|
|
|
|
|
Workflow continueOnDone(WorkflowPolicy::ContinueOnDone);
|
|
|
|
|
Workflow optional(WorkflowPolicy::Optional);
|
2022-10-12 14:30:24 +02:00
|
|
|
|
|
|
|
|
void TaskItem::addChildren(const QList<TaskItem> &children)
|
|
|
|
|
{
|
2023-02-20 21:32:00 +01:00
|
|
|
QTC_ASSERT(m_type == Type::Group, qWarning("Only Group may have children, skipping...");
|
|
|
|
|
return);
|
2022-10-12 14:30:24 +02:00
|
|
|
for (const TaskItem &child : children) {
|
|
|
|
|
switch (child.m_type) {
|
|
|
|
|
case Type::Group:
|
|
|
|
|
m_children.append(child);
|
|
|
|
|
break;
|
2023-01-06 16:07:16 +01:00
|
|
|
case Type::Limit:
|
2023-02-20 21:32:00 +01:00
|
|
|
QTC_ASSERT(m_type == Type::Group, qWarning("Execution Mode may only be a child of a "
|
|
|
|
|
"Group, skipping..."); return);
|
2023-01-06 16:07:16 +01:00
|
|
|
m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition?
|
2022-10-12 14:30:24 +02:00
|
|
|
break;
|
|
|
|
|
case Type::Policy:
|
2023-02-20 21:32:00 +01:00
|
|
|
QTC_ASSERT(m_type == Type::Group, qWarning("Workflow Policy may only be a child of a "
|
|
|
|
|
"Group, skipping..."); return);
|
2022-10-12 14:30:24 +02:00
|
|
|
m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition?
|
|
|
|
|
break;
|
|
|
|
|
case Type::TaskHandler:
|
|
|
|
|
QTC_ASSERT(child.m_taskHandler.m_createHandler,
|
|
|
|
|
qWarning("Task Create Handler can't be null, skipping..."); return);
|
|
|
|
|
m_children.append(child);
|
|
|
|
|
break;
|
|
|
|
|
case Type::GroupHandler:
|
|
|
|
|
QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a "
|
2023-02-20 21:32:00 +01:00
|
|
|
"child of a Group, skipping..."); break);
|
2022-11-04 14:08:32 +01:00
|
|
|
QTC_ASSERT(!child.m_groupHandler.m_setupHandler
|
|
|
|
|
|| !m_groupHandler.m_setupHandler,
|
2022-10-12 14:30:24 +02:00
|
|
|
qWarning("Group Setup Handler redefinition, overriding..."));
|
2022-11-04 14:08:32 +01:00
|
|
|
QTC_ASSERT(!child.m_groupHandler.m_doneHandler
|
|
|
|
|
|| !m_groupHandler.m_doneHandler,
|
2022-10-12 14:30:24 +02:00
|
|
|
qWarning("Group Done Handler redefinition, overriding..."));
|
2022-11-04 14:08:32 +01:00
|
|
|
QTC_ASSERT(!child.m_groupHandler.m_errorHandler
|
|
|
|
|
|| !m_groupHandler.m_errorHandler,
|
2022-10-12 14:30:24 +02:00
|
|
|
qWarning("Group Error Handler redefinition, overriding..."));
|
2022-11-04 14:08:32 +01:00
|
|
|
if (child.m_groupHandler.m_setupHandler)
|
|
|
|
|
m_groupHandler.m_setupHandler = child.m_groupHandler.m_setupHandler;
|
|
|
|
|
if (child.m_groupHandler.m_doneHandler)
|
|
|
|
|
m_groupHandler.m_doneHandler = child.m_groupHandler.m_doneHandler;
|
|
|
|
|
if (child.m_groupHandler.m_errorHandler)
|
|
|
|
|
m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler;
|
2022-10-12 14:30:24 +02:00
|
|
|
break;
|
2022-11-21 15:05:39 +01:00
|
|
|
case Type::Storage:
|
|
|
|
|
m_storageList.append(child.m_storageList);
|
|
|
|
|
break;
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-02 14:01:49 +02:00
|
|
|
void TaskItem::setTaskSetupHandler(const TaskSetupHandler &handler)
|
|
|
|
|
{
|
|
|
|
|
if (!handler) {
|
|
|
|
|
qWarning("Setting empty Setup Handler is no-op, skipping...");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_taskHandler.m_setupHandler)
|
|
|
|
|
qWarning("Setup Handler redefinition, overriding...");
|
|
|
|
|
m_taskHandler.m_setupHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskItem::setTaskDoneHandler(const TaskEndHandler &handler)
|
|
|
|
|
{
|
|
|
|
|
if (!handler) {
|
|
|
|
|
qWarning("Setting empty Done Handler is no-op, skipping...");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_taskHandler.m_doneHandler)
|
|
|
|
|
qWarning("Done Handler redefinition, overriding...");
|
|
|
|
|
m_taskHandler.m_doneHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskItem::setTaskErrorHandler(const TaskEndHandler &handler)
|
|
|
|
|
{
|
|
|
|
|
if (!handler) {
|
|
|
|
|
qWarning("Setting empty Error Handler is no-op, skipping...");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_taskHandler.m_errorHandler)
|
|
|
|
|
qWarning("Error Handler redefinition, overriding...");
|
|
|
|
|
m_taskHandler.m_errorHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
class TaskTreePrivate;
|
|
|
|
|
class TaskNode;
|
|
|
|
|
|
2023-02-20 21:32:00 +01:00
|
|
|
class TaskTreePrivate
|
|
|
|
|
{
|
2023-05-19 09:35:09 +02:00
|
|
|
Q_DISABLE_COPY_MOVE(TaskTreePrivate)
|
|
|
|
|
|
2023-02-20 21:32:00 +01:00
|
|
|
public:
|
|
|
|
|
TaskTreePrivate(TaskTree *taskTree)
|
|
|
|
|
: q(taskTree) {}
|
|
|
|
|
|
|
|
|
|
void start();
|
|
|
|
|
void stop();
|
|
|
|
|
void advanceProgress(int byValue);
|
|
|
|
|
void emitStartedAndProgress();
|
|
|
|
|
void emitProgress();
|
|
|
|
|
void emitDone();
|
|
|
|
|
void emitError();
|
|
|
|
|
QList<TreeStorageBase> addStorages(const QList<TreeStorageBase> &storages);
|
|
|
|
|
void callSetupHandler(TreeStorageBase storage, int storageId) {
|
|
|
|
|
callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler);
|
|
|
|
|
}
|
|
|
|
|
void callDoneHandler(TreeStorageBase storage, int storageId) {
|
|
|
|
|
callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler);
|
|
|
|
|
}
|
|
|
|
|
struct StorageHandler {
|
|
|
|
|
TaskTree::StorageVoidHandler m_setupHandler = {};
|
|
|
|
|
TaskTree::StorageVoidHandler m_doneHandler = {};
|
|
|
|
|
};
|
|
|
|
|
typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member
|
|
|
|
|
void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr)
|
|
|
|
|
{
|
|
|
|
|
const auto it = m_storageHandlers.constFind(storage);
|
|
|
|
|
if (it == m_storageHandlers.constEnd())
|
|
|
|
|
return;
|
|
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
const StorageHandler storageHandler = *it;
|
|
|
|
|
storage.activateStorage(storageId);
|
|
|
|
|
if (storageHandler.*ptr)
|
|
|
|
|
(storageHandler.*ptr)(storage.activeStorageVoid());
|
|
|
|
|
storage.activateStorage(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TaskTree *q = nullptr;
|
|
|
|
|
Guard m_guard;
|
|
|
|
|
int m_progressValue = 0;
|
|
|
|
|
QSet<TreeStorageBase> m_storages;
|
|
|
|
|
QHash<TreeStorageBase, StorageHandler> m_storageHandlers;
|
|
|
|
|
std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
|
|
|
|
|
};
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
class TaskContainer
|
|
|
|
|
{
|
2023-05-19 09:35:09 +02:00
|
|
|
Q_DISABLE_COPY_MOVE(TaskContainer)
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
public:
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
2023-02-20 21:32:00 +01:00
|
|
|
TaskNode *parentNode, TaskContainer *parentContainer)
|
2023-04-30 11:25:22 +02:00
|
|
|
: m_constData(taskTreePrivate, task, parentNode, parentContainer, this) {}
|
2023-01-19 23:28:56 +01:00
|
|
|
TaskAction start();
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskAction continueStart(TaskAction startAction, int nextChild);
|
2023-01-24 11:56:30 +01:00
|
|
|
TaskAction startChildren(int nextChild);
|
2023-01-19 23:28:56 +01:00
|
|
|
TaskAction childDone(bool success);
|
2023-01-31 12:50:59 +01:00
|
|
|
void stop();
|
2023-01-27 16:28:04 +01:00
|
|
|
void invokeEndHandler();
|
2023-01-31 12:50:59 +01:00
|
|
|
bool isRunning() const { return m_runtimeData.has_value(); }
|
|
|
|
|
bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); }
|
|
|
|
|
|
|
|
|
|
struct ConstData {
|
2023-02-20 21:32:00 +01:00
|
|
|
ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode,
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskContainer *parentContainer, TaskContainer *thisContainer);
|
|
|
|
|
~ConstData() { qDeleteAll(m_children); }
|
|
|
|
|
TaskTreePrivate * const m_taskTreePrivate = nullptr;
|
2023-02-20 21:32:00 +01:00
|
|
|
TaskNode * const m_parentNode = nullptr;
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskContainer * const m_parentContainer = nullptr;
|
|
|
|
|
|
|
|
|
|
const int m_parallelLimit = 1;
|
|
|
|
|
const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
|
|
|
|
|
const TaskItem::GroupHandler m_groupHandler;
|
|
|
|
|
const QList<TreeStorageBase> m_storageList;
|
|
|
|
|
const QList<TaskNode *> m_children;
|
|
|
|
|
const int m_taskCount = 0;
|
|
|
|
|
};
|
2022-10-12 14:30:24 +02:00
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
struct RuntimeData {
|
|
|
|
|
RuntimeData(const ConstData &constData);
|
|
|
|
|
~RuntimeData();
|
|
|
|
|
|
|
|
|
|
static QList<int> createStorages(const TaskContainer::ConstData &constData);
|
2023-02-21 12:05:18 +01:00
|
|
|
void callStorageDoneHandlers();
|
2023-01-31 12:50:59 +01:00
|
|
|
bool updateSuccessBit(bool success);
|
|
|
|
|
int currentLimit() const;
|
|
|
|
|
|
|
|
|
|
const ConstData &m_constData;
|
|
|
|
|
const QList<int> m_storageIdList;
|
|
|
|
|
int m_doneCount = 0;
|
|
|
|
|
bool m_successBit = true;
|
|
|
|
|
Guard m_startGuard;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ConstData m_constData;
|
|
|
|
|
std::optional<RuntimeData> m_runtimeData;
|
2022-11-21 15:05:39 +01:00
|
|
|
};
|
|
|
|
|
|
2023-05-17 17:38:10 +02:00
|
|
|
class TaskNode
|
2022-10-12 14:30:24 +02:00
|
|
|
{
|
2023-05-19 09:35:09 +02:00
|
|
|
Q_DISABLE_COPY_MOVE(TaskNode)
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
public:
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
|
|
|
|
TaskContainer *parentContainer)
|
2022-10-12 14:30:24 +02:00
|
|
|
: m_taskHandler(task.taskHandler())
|
2023-02-20 21:32:00 +01:00
|
|
|
, m_container(taskTreePrivate, task, this, parentContainer)
|
2023-01-31 12:50:59 +01:00
|
|
|
{}
|
2022-10-12 14:30:24 +02:00
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
// If returned value != Continue, childDone() needs to be called in parent container (in caller)
|
|
|
|
|
// in order to unwind properly.
|
2023-01-19 23:28:56 +01:00
|
|
|
TaskAction start();
|
2022-10-12 14:30:24 +02:00
|
|
|
void stop();
|
2023-01-31 12:50:59 +01:00
|
|
|
void invokeEndHandler(bool success);
|
|
|
|
|
bool isRunning() const { return m_task || m_container.isRunning(); }
|
2023-05-02 14:01:49 +02:00
|
|
|
bool isTask() const { return bool(m_taskHandler.m_createHandler); }
|
2023-01-31 12:50:59 +01:00
|
|
|
int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; }
|
|
|
|
|
TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; }
|
2023-05-17 17:38:10 +02:00
|
|
|
TaskTree *taskTree() const { return m_container.m_constData.m_taskTreePrivate->q; }
|
2022-10-12 14:30:24 +02:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const TaskItem::TaskHandler m_taskHandler;
|
|
|
|
|
TaskContainer m_container;
|
|
|
|
|
std::unique_ptr<TaskInterface> m_task;
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-20 21:32:00 +01:00
|
|
|
void TaskTreePrivate::start()
|
2022-10-12 14:30:24 +02:00
|
|
|
{
|
2023-02-20 21:32:00 +01:00
|
|
|
QTC_ASSERT(m_root, return);
|
|
|
|
|
m_progressValue = 0;
|
|
|
|
|
emitStartedAndProgress();
|
|
|
|
|
// TODO: check storage handlers for not existing storages in tree
|
|
|
|
|
for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
|
|
|
|
|
QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
|
|
|
|
|
"exist in task tree. Its handlers will never be called."));
|
2022-11-10 15:59:54 +01:00
|
|
|
}
|
2023-02-20 21:32:00 +01:00
|
|
|
m_root->start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskTreePrivate::stop()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_root, return);
|
|
|
|
|
if (!m_root->isRunning())
|
|
|
|
|
return;
|
|
|
|
|
// TODO: should we have canceled flag (passed to handler)?
|
|
|
|
|
// Just one done handler with result flag:
|
|
|
|
|
// FinishedWithSuccess, FinishedWithError, Canceled, TimedOut.
|
|
|
|
|
// Canceled either directly by user, or by workflow policy - doesn't matter, in both
|
|
|
|
|
// cases canceled from outside.
|
|
|
|
|
m_root->stop();
|
|
|
|
|
emitError();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskTreePrivate::advanceProgress(int byValue)
|
|
|
|
|
{
|
|
|
|
|
if (byValue == 0)
|
|
|
|
|
return;
|
|
|
|
|
QTC_CHECK(byValue > 0);
|
|
|
|
|
QTC_CHECK(m_progressValue + byValue <= m_root->taskCount());
|
|
|
|
|
m_progressValue += byValue;
|
|
|
|
|
emitProgress();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskTreePrivate::emitStartedAndProgress()
|
|
|
|
|
{
|
|
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
emit q->started();
|
|
|
|
|
emit q->progressValueChanged(m_progressValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskTreePrivate::emitProgress()
|
|
|
|
|
{
|
|
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
emit q->progressValueChanged(m_progressValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskTreePrivate::emitDone()
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(m_progressValue == m_root->taskCount());
|
|
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
emit q->done();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskTreePrivate::emitError()
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(m_progressValue == m_root->taskCount());
|
|
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
emit q->errorOccurred();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<TreeStorageBase> TaskTreePrivate::addStorages(const QList<TreeStorageBase> &storages)
|
|
|
|
|
{
|
|
|
|
|
QList<TreeStorageBase> addedStorages;
|
|
|
|
|
for (const TreeStorageBase &storage : storages) {
|
|
|
|
|
QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into "
|
|
|
|
|
"one TaskTree twice, skipping..."); continue);
|
|
|
|
|
addedStorages << storage;
|
|
|
|
|
m_storages << storage;
|
|
|
|
|
}
|
|
|
|
|
return addedStorages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ExecutionContextActivator
|
2023-01-31 12:50:59 +01:00
|
|
|
{
|
|
|
|
|
public:
|
2023-02-20 21:32:00 +01:00
|
|
|
ExecutionContextActivator(TaskContainer *container)
|
|
|
|
|
: m_container(container) { activateContext(m_container); }
|
|
|
|
|
~ExecutionContextActivator() { deactivateContext(m_container); }
|
2023-01-31 12:50:59 +01:00
|
|
|
|
|
|
|
|
private:
|
2023-02-20 21:32:00 +01:00
|
|
|
static void activateContext(TaskContainer *container)
|
2023-01-31 12:50:59 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(container && container->isRunning(), return);
|
|
|
|
|
const TaskContainer::ConstData &constData = container->m_constData;
|
|
|
|
|
if (constData.m_parentContainer)
|
2023-02-20 21:32:00 +01:00
|
|
|
activateContext(constData.m_parentContainer);
|
2023-01-31 12:50:59 +01:00
|
|
|
for (int i = 0; i < constData.m_storageList.size(); ++i)
|
|
|
|
|
constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i));
|
|
|
|
|
}
|
2023-02-20 21:32:00 +01:00
|
|
|
static void deactivateContext(TaskContainer *container)
|
2023-01-31 12:50:59 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(container && container->isRunning(), return);
|
|
|
|
|
const TaskContainer::ConstData &constData = container->m_constData;
|
|
|
|
|
for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order
|
|
|
|
|
constData.m_storageList[i].activateStorage(0);
|
|
|
|
|
if (constData.m_parentContainer)
|
2023-02-20 21:32:00 +01:00
|
|
|
deactivateContext(constData.m_parentContainer);
|
2023-01-31 12:50:59 +01:00
|
|
|
}
|
|
|
|
|
TaskContainer *m_container = nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <typename Handler, typename ...Args,
|
|
|
|
|
typename ReturnType = typename std::invoke_result_t<Handler, Args...>>
|
|
|
|
|
ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args)
|
|
|
|
|
{
|
2023-02-20 21:32:00 +01:00
|
|
|
ExecutionContextActivator activator(container);
|
2023-01-31 12:50:59 +01:00
|
|
|
GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard);
|
|
|
|
|
return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-24 21:33:56 +01:00
|
|
|
static QList<TaskNode *> createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container,
|
|
|
|
|
const TaskItem &task)
|
|
|
|
|
{
|
|
|
|
|
QList<TaskNode *> result;
|
|
|
|
|
const QList<TaskItem> &children = task.children();
|
|
|
|
|
for (const TaskItem &child : children)
|
2023-01-31 12:50:59 +01:00
|
|
|
result.append(new TaskNode(taskTreePrivate, child, container));
|
2023-01-24 21:33:56 +01:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
2023-02-20 21:32:00 +01:00
|
|
|
TaskNode *parentNode, TaskContainer *parentContainer,
|
|
|
|
|
TaskContainer *thisContainer)
|
2022-10-12 14:30:24 +02:00
|
|
|
: m_taskTreePrivate(taskTreePrivate)
|
2023-02-20 21:32:00 +01:00
|
|
|
, m_parentNode(parentNode)
|
2022-10-12 14:30:24 +02:00
|
|
|
, m_parentContainer(parentContainer)
|
2023-01-06 16:07:16 +01:00
|
|
|
, m_parallelLimit(task.parallelLimit())
|
2022-10-12 14:30:24 +02:00
|
|
|
, m_workflowPolicy(task.workflowPolicy())
|
|
|
|
|
, m_groupHandler(task.groupHandler())
|
2022-12-05 10:25:59 +01:00
|
|
|
, m_storageList(taskTreePrivate->addStorages(task.storageList()))
|
2023-01-31 12:50:59 +01:00
|
|
|
, m_children(createChildren(taskTreePrivate, thisContainer, task))
|
2023-01-24 21:33:56 +01:00
|
|
|
, m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0,
|
|
|
|
|
[](int r, TaskNode *n) { return r + n->taskCount(); }))
|
2023-01-31 12:50:59 +01:00
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
QList<int> TaskContainer::RuntimeData::createStorages(const TaskContainer::ConstData &constData)
|
|
|
|
|
{
|
|
|
|
|
QList<int> storageIdList;
|
|
|
|
|
for (const TreeStorageBase &storage : constData.m_storageList) {
|
|
|
|
|
const int storageId = storage.createStorage();
|
|
|
|
|
storageIdList.append(storageId);
|
|
|
|
|
constData.m_taskTreePrivate->callSetupHandler(storage, storageId);
|
|
|
|
|
}
|
|
|
|
|
return storageIdList;
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-21 12:05:18 +01:00
|
|
|
void TaskContainer::RuntimeData::callStorageDoneHandlers()
|
|
|
|
|
{
|
|
|
|
|
for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order
|
|
|
|
|
const TreeStorageBase storage = m_constData.m_storageList[i];
|
|
|
|
|
const int storageId = m_storageIdList.value(i);
|
|
|
|
|
m_constData.m_taskTreePrivate->callDoneHandler(storage, storageId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskContainer::RuntimeData::RuntimeData(const ConstData &constData)
|
|
|
|
|
: m_constData(constData)
|
|
|
|
|
, m_storageIdList(createStorages(constData))
|
|
|
|
|
{
|
|
|
|
|
m_successBit = m_constData.m_workflowPolicy != WorkflowPolicy::StopOnDone
|
|
|
|
|
&& m_constData.m_workflowPolicy != WorkflowPolicy::ContinueOnDone;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TaskContainer::RuntimeData::~RuntimeData()
|
2022-10-12 14:30:24 +02:00
|
|
|
{
|
2023-01-31 12:50:59 +01:00
|
|
|
for (int i = m_constData.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order
|
|
|
|
|
const TreeStorageBase storage = m_constData.m_storageList[i];
|
|
|
|
|
const int storageId = m_storageIdList.value(i);
|
|
|
|
|
storage.deleteStorage(storageId);
|
|
|
|
|
}
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
bool TaskContainer::RuntimeData::updateSuccessBit(bool success)
|
2022-10-12 14:30:24 +02:00
|
|
|
{
|
2023-01-31 12:50:59 +01:00
|
|
|
if (m_constData.m_workflowPolicy == WorkflowPolicy::Optional)
|
|
|
|
|
return m_successBit;
|
|
|
|
|
|
|
|
|
|
const bool donePolicy = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone
|
|
|
|
|
|| m_constData.m_workflowPolicy == WorkflowPolicy::ContinueOnDone;
|
|
|
|
|
m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success);
|
|
|
|
|
return m_successBit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int TaskContainer::RuntimeData::currentLimit() const
|
|
|
|
|
{
|
|
|
|
|
const int childCount = m_constData.m_children.size();
|
|
|
|
|
return m_constData.m_parallelLimit
|
|
|
|
|
? qMin(m_doneCount + m_constData.m_parallelLimit, childCount) : childCount;
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-19 23:28:56 +01:00
|
|
|
TaskAction TaskContainer::start()
|
2022-10-12 14:30:24 +02:00
|
|
|
{
|
2023-01-31 12:50:59 +01:00
|
|
|
QTC_CHECK(!isRunning());
|
|
|
|
|
m_runtimeData.emplace(m_constData);
|
2023-01-27 16:28:04 +01:00
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskAction startAction = TaskAction::Continue;
|
|
|
|
|
if (m_constData.m_groupHandler.m_setupHandler) {
|
|
|
|
|
startAction = invokeHandler(this, m_constData.m_groupHandler.m_setupHandler);
|
|
|
|
|
if (startAction != TaskAction::Continue)
|
|
|
|
|
m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount);
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
2023-02-20 21:32:00 +01:00
|
|
|
if (startAction == TaskAction::Continue) {
|
|
|
|
|
if (m_constData.m_children.isEmpty())
|
|
|
|
|
startAction = TaskAction::StopWithDone;
|
|
|
|
|
}
|
2023-01-31 12:50:59 +01:00
|
|
|
return continueStart(startAction, 0);
|
|
|
|
|
}
|
2022-10-12 14:30:24 +02:00
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskAction TaskContainer::continueStart(TaskAction startAction, int nextChild)
|
|
|
|
|
{
|
|
|
|
|
const TaskAction groupAction = startAction == TaskAction::Continue ? startChildren(nextChild)
|
|
|
|
|
: startAction;
|
|
|
|
|
QTC_CHECK(isRunning()); // TODO: superfluous
|
2023-01-27 16:28:04 +01:00
|
|
|
if (groupAction != TaskAction::Continue) {
|
2023-01-31 12:50:59 +01:00
|
|
|
const bool success = m_runtimeData->updateSuccessBit(groupAction == TaskAction::StopWithDone);
|
|
|
|
|
invokeEndHandler();
|
|
|
|
|
if (TaskContainer *parentContainer = m_constData.m_parentContainer) {
|
|
|
|
|
QTC_CHECK(parentContainer->isRunning());
|
|
|
|
|
if (!parentContainer->isStarting())
|
|
|
|
|
parentContainer->childDone(success);
|
|
|
|
|
} else if (success) {
|
|
|
|
|
m_constData.m_taskTreePrivate->emitDone();
|
|
|
|
|
} else {
|
|
|
|
|
m_constData.m_taskTreePrivate->emitError();
|
|
|
|
|
}
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
2023-01-27 16:28:04 +01:00
|
|
|
return groupAction;
|
2023-01-19 23:28:56 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-24 11:56:30 +01:00
|
|
|
TaskAction TaskContainer::startChildren(int nextChild)
|
2023-01-19 23:28:56 +01:00
|
|
|
{
|
2023-01-31 12:50:59 +01:00
|
|
|
QTC_CHECK(isRunning());
|
|
|
|
|
GuardLocker locker(m_runtimeData->m_startGuard);
|
2023-02-02 00:30:51 +01:00
|
|
|
for (int i = nextChild; i < m_constData.m_children.size(); ++i) {
|
2023-01-31 12:50:59 +01:00
|
|
|
const int limit = m_runtimeData->currentLimit();
|
2023-01-19 23:28:56 +01:00
|
|
|
if (i >= limit)
|
|
|
|
|
break;
|
|
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
const TaskAction startAction = m_constData.m_children.at(i)->start();
|
2023-01-24 15:13:23 +01:00
|
|
|
if (startAction == TaskAction::Continue)
|
2023-01-19 23:28:56 +01:00
|
|
|
continue;
|
|
|
|
|
|
2023-01-24 15:13:23 +01:00
|
|
|
const TaskAction finalizeAction = childDone(startAction == TaskAction::StopWithDone);
|
2023-01-19 23:28:56 +01:00
|
|
|
if (finalizeAction == TaskAction::Continue)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
int skippedTaskCount = 0;
|
|
|
|
|
// Skip scheduled but not run yet. The current (i) was already notified.
|
|
|
|
|
for (int j = i + 1; j < limit; ++j)
|
2023-01-31 12:50:59 +01:00
|
|
|
skippedTaskCount += m_constData.m_children.at(j)->taskCount();
|
|
|
|
|
m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount);
|
2023-01-19 23:28:56 +01:00
|
|
|
return finalizeAction;
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
2023-01-19 23:28:56 +01:00
|
|
|
return TaskAction::Continue;
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-19 23:28:56 +01:00
|
|
|
TaskAction TaskContainer::childDone(bool success)
|
2022-10-12 14:30:24 +02:00
|
|
|
{
|
2023-01-31 12:50:59 +01:00
|
|
|
QTC_CHECK(isRunning());
|
|
|
|
|
const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop()
|
2023-02-02 00:30:51 +01:00
|
|
|
const bool shouldStop = (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnDone && success)
|
|
|
|
|
|| (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success);
|
2023-01-24 15:13:23 +01:00
|
|
|
if (shouldStop)
|
2022-10-12 14:30:24 +02:00
|
|
|
stop();
|
2023-01-19 23:28:56 +01:00
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
++m_runtimeData->m_doneCount;
|
|
|
|
|
const bool updatedSuccess = m_runtimeData->updateSuccessBit(success);
|
|
|
|
|
const TaskAction startAction
|
|
|
|
|
= (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size())
|
2023-02-02 00:30:51 +01:00
|
|
|
? toTaskAction(updatedSuccess) : TaskAction::Continue;
|
2022-10-12 14:30:24 +02:00
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
if (isStarting())
|
|
|
|
|
return startAction;
|
|
|
|
|
return continueStart(startAction, limit);
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
void TaskContainer::stop()
|
2022-10-12 14:30:24 +02:00
|
|
|
{
|
2023-01-31 12:50:59 +01:00
|
|
|
if (!isRunning())
|
2022-10-12 14:30:24 +02:00
|
|
|
return;
|
|
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
const int limit = m_runtimeData->currentLimit();
|
|
|
|
|
for (int i = 0; i < limit; ++i)
|
|
|
|
|
m_constData.m_children.at(i)->stop();
|
2022-11-21 15:05:39 +01:00
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
int skippedTaskCount = 0;
|
|
|
|
|
for (int i = limit; i < m_constData.m_children.size(); ++i)
|
|
|
|
|
skippedTaskCount += m_constData.m_children.at(i)->taskCount();
|
2022-11-21 15:05:39 +01:00
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount);
|
2022-11-21 15:05:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
void TaskContainer::invokeEndHandler()
|
2022-11-21 15:05:39 +01:00
|
|
|
{
|
2023-01-31 12:50:59 +01:00
|
|
|
const TaskItem::GroupHandler &groupHandler = m_constData.m_groupHandler;
|
|
|
|
|
if (m_runtimeData->m_successBit && groupHandler.m_doneHandler)
|
|
|
|
|
invokeHandler(this, groupHandler.m_doneHandler);
|
|
|
|
|
else if (!m_runtimeData->m_successBit && groupHandler.m_errorHandler)
|
|
|
|
|
invokeHandler(this, groupHandler.m_errorHandler);
|
2023-02-21 12:05:18 +01:00
|
|
|
m_runtimeData->callStorageDoneHandlers();
|
2023-01-31 12:50:59 +01:00
|
|
|
m_runtimeData.reset();
|
2022-11-21 15:05:39 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-19 23:28:56 +01:00
|
|
|
TaskAction TaskNode::start()
|
2022-10-12 14:30:24 +02:00
|
|
|
{
|
2023-01-24 11:56:30 +01:00
|
|
|
QTC_CHECK(!isRunning());
|
2023-01-19 23:28:56 +01:00
|
|
|
if (!isTask())
|
|
|
|
|
return m_container.start();
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
m_task.reset(m_taskHandler.m_createHandler());
|
2023-05-02 14:01:49 +02:00
|
|
|
const TaskAction startAction = m_taskHandler.m_setupHandler
|
|
|
|
|
? invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, *m_task.get())
|
|
|
|
|
: TaskAction::Continue;
|
2023-01-31 12:50:59 +01:00
|
|
|
if (startAction != TaskAction::Continue) {
|
|
|
|
|
m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
|
|
|
|
|
m_task.reset();
|
|
|
|
|
return startAction;
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
2023-01-31 12:50:59 +01:00
|
|
|
const std::shared_ptr<TaskAction> unwindAction
|
|
|
|
|
= std::make_shared<TaskAction>(TaskAction::Continue);
|
2023-05-17 17:38:10 +02:00
|
|
|
QObject::connect(m_task.get(), &TaskInterface::done, taskTree(), [=](bool success) {
|
2023-01-31 12:50:59 +01:00
|
|
|
invokeEndHandler(success);
|
2023-05-17 17:38:10 +02:00
|
|
|
QObject::disconnect(m_task.get(), &TaskInterface::done, taskTree(), nullptr);
|
2023-01-31 12:50:59 +01:00
|
|
|
m_task.release()->deleteLater();
|
2023-02-02 00:30:51 +01:00
|
|
|
QTC_ASSERT(parentContainer() && parentContainer()->isRunning(), return);
|
2023-01-31 12:50:59 +01:00
|
|
|
if (parentContainer()->isStarting())
|
2023-01-19 23:28:56 +01:00
|
|
|
*unwindAction = toTaskAction(success);
|
|
|
|
|
else
|
2023-01-31 12:50:59 +01:00
|
|
|
parentContainer()->childDone(success);
|
2022-10-12 14:30:24 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
m_task->start();
|
2023-01-19 23:28:56 +01:00
|
|
|
return *unwindAction;
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskNode::stop()
|
|
|
|
|
{
|
2022-11-10 15:59:54 +01:00
|
|
|
if (!isRunning())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!m_task) {
|
|
|
|
|
m_container.stop();
|
2023-01-27 16:28:04 +01:00
|
|
|
m_container.invokeEndHandler();
|
2022-11-10 15:59:54 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: cancelHandler?
|
|
|
|
|
// TODO: call TaskInterface::stop() ?
|
2023-01-31 12:50:59 +01:00
|
|
|
invokeEndHandler(false);
|
2022-10-12 14:30:24 +02:00
|
|
|
m_task.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
void TaskNode::invokeEndHandler(bool success)
|
2022-11-10 14:31:25 +01:00
|
|
|
{
|
2023-01-31 12:50:59 +01:00
|
|
|
if (success && m_taskHandler.m_doneHandler)
|
|
|
|
|
invokeHandler(parentContainer(), m_taskHandler.m_doneHandler, *m_task.get());
|
|
|
|
|
else if (!success && m_taskHandler.m_errorHandler)
|
|
|
|
|
invokeHandler(parentContainer(), m_taskHandler.m_errorHandler, *m_task.get());
|
|
|
|
|
m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
|
2022-11-10 14:31:25 +01:00
|
|
|
}
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
/*!
|
2023-05-10 19:54:52 +02:00
|
|
|
\class TaskTree
|
|
|
|
|
\inheaderfile solutions/tasking/tasktree.h
|
2023-02-01 14:16:18 +01:00
|
|
|
\inmodule QtCreator
|
|
|
|
|
\ingroup mainclasses
|
|
|
|
|
\brief The TaskTree class runs an async task tree structure defined in a
|
2022-10-12 14:30:24 +02:00
|
|
|
declarative way.
|
|
|
|
|
|
2023-02-01 14:16:18 +01:00
|
|
|
Use the Tasking namespace to build extensible, declarative task tree
|
2023-05-09 23:36:18 +02:00
|
|
|
structures that contain possibly asynchronous tasks, such as Process,
|
|
|
|
|
FileTransfer, or Async<ReturnType>. TaskTree structures enable you
|
2023-02-01 14:16:18 +01:00
|
|
|
to create a sophisticated mixture of a parallel or sequential flow of tasks
|
|
|
|
|
in the form of a tree and to run it any time later.
|
|
|
|
|
|
|
|
|
|
\section1 Root Element and Tasks
|
|
|
|
|
|
|
|
|
|
The TaskTree has a mandatory Group root element, which may contain
|
2023-05-09 23:36:18 +02:00
|
|
|
any number of tasks of various types, such as ProcessTask, FileTransferTask,
|
2023-02-01 14:16:18 +01:00
|
|
|
or AsyncTask<ReturnType>:
|
|
|
|
|
|
|
|
|
|
\code
|
|
|
|
|
using namespace Tasking;
|
|
|
|
|
|
|
|
|
|
const Group root {
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(...),
|
|
|
|
|
AsyncTask<int>(...),
|
|
|
|
|
FileTransferTask(...)
|
2023-02-01 14:16:18 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TaskTree *taskTree = new TaskTree(root);
|
|
|
|
|
connect(taskTree, &TaskTree::done, ...); // a successfully finished handler
|
|
|
|
|
connect(taskTree, &TaskTree::errorOccurred, ...); // an erroneously finished handler
|
|
|
|
|
taskTree->start();
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
The task tree above has a top level element of the Group type that contains
|
2023-05-09 23:36:18 +02:00
|
|
|
tasks of the type ProcessTask, FileTransferTask, and AsyncTask<int>.
|
2023-02-01 14:16:18 +01:00
|
|
|
After taskTree->start() is called, the tasks are run in a chain, starting
|
2023-05-09 23:36:18 +02:00
|
|
|
with ProcessTask. When the ProcessTask finishes successfully, the AsyncTask<int> task is
|
2023-02-01 14:16:18 +01:00
|
|
|
started. Finally, when the asynchronous task finishes successfully, the
|
2023-05-09 23:36:18 +02:00
|
|
|
FileTransferTask task is started.
|
2023-02-01 14:16:18 +01:00
|
|
|
|
|
|
|
|
When the last running task finishes with success, the task tree is considered
|
|
|
|
|
to have run successfully and the TaskTree::done() signal is emitted.
|
|
|
|
|
When a task finishes with an error, the execution of the task tree is stopped
|
|
|
|
|
and the remaining tasks are skipped. The task tree finishes with an error and
|
|
|
|
|
sends the TaskTree::errorOccurred() signal.
|
|
|
|
|
|
|
|
|
|
\section1 Groups
|
|
|
|
|
|
|
|
|
|
The parent of the Group sees it as a single task. Like other tasks,
|
|
|
|
|
the group can be started and it can finish with success or an error.
|
|
|
|
|
The Group elements can be nested to create a tree structure:
|
|
|
|
|
|
|
|
|
|
\code
|
|
|
|
|
const Group root {
|
|
|
|
|
Group {
|
|
|
|
|
parallel,
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(...),
|
|
|
|
|
AsyncTask<int>(...)
|
2023-02-01 14:16:18 +01:00
|
|
|
},
|
2023-05-09 23:36:18 +02:00
|
|
|
FileTransferTask(...)
|
2023-02-01 14:16:18 +01:00
|
|
|
};
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
The example above differs from the first example in that the root element has
|
2023-05-09 23:36:18 +02:00
|
|
|
a subgroup that contains the ProcessTask and AsyncTask<int>. The subgroup is a
|
|
|
|
|
sibling element of the FileTransferTask in the root. The subgroup contains an
|
2023-02-01 14:16:18 +01:00
|
|
|
additional \e parallel element that instructs its Group to execute its tasks
|
|
|
|
|
in parallel.
|
|
|
|
|
|
2023-05-09 23:36:18 +02:00
|
|
|
So, when the tree above is started, the ProcessTask and AsyncTask<int> start
|
2023-02-01 14:16:18 +01:00
|
|
|
immediately and run in parallel. Since the root group doesn't contain a
|
|
|
|
|
\e parallel element, its direct child tasks are run in sequence. Thus, the
|
2023-05-09 23:36:18 +02:00
|
|
|
FileTransferTask starts when the whole subgroup finishes. The group is
|
2023-02-01 14:16:18 +01:00
|
|
|
considered as finished when all its tasks have finished. The order in which
|
|
|
|
|
the tasks finish is not relevant.
|
|
|
|
|
|
2023-05-09 23:36:18 +02:00
|
|
|
So, depending on which task lasts longer (ProcessTask or AsyncTask<int>), the
|
2023-02-01 14:16:18 +01:00
|
|
|
following scenarios can take place:
|
|
|
|
|
|
|
|
|
|
\table
|
|
|
|
|
\header
|
|
|
|
|
\li Scenario 1
|
|
|
|
|
\li Scenario 2
|
|
|
|
|
\row
|
|
|
|
|
\li Root Group starts
|
|
|
|
|
\li Root Group starts
|
|
|
|
|
\row
|
|
|
|
|
\li Sub Group starts
|
|
|
|
|
\li Sub Group starts
|
|
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li ProcessTask starts
|
|
|
|
|
\li ProcessTask starts
|
2023-02-01 14:16:18 +01:00
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li AsyncTask<int> starts
|
|
|
|
|
\li AsyncTask<int> starts
|
2023-02-01 14:16:18 +01:00
|
|
|
\row
|
|
|
|
|
\li ...
|
|
|
|
|
\li ...
|
|
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li \b {ProcessTask finishes}
|
|
|
|
|
\li \b {AsyncTask<int> finishes}
|
2023-02-01 14:16:18 +01:00
|
|
|
\row
|
|
|
|
|
\li ...
|
|
|
|
|
\li ...
|
|
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li \b {AsyncTask<int> finishes}
|
|
|
|
|
\li \b {ProcessTask finishes}
|
2023-02-01 14:16:18 +01:00
|
|
|
\row
|
|
|
|
|
\li Sub Group finishes
|
|
|
|
|
\li Sub Group finishes
|
|
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li FileTransferTask starts
|
|
|
|
|
\li FileTransferTask starts
|
2023-02-01 14:16:18 +01:00
|
|
|
\row
|
|
|
|
|
\li ...
|
|
|
|
|
\li ...
|
|
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li FileTransferTask finishes
|
|
|
|
|
\li FileTransferTask finishes
|
2023-02-01 14:16:18 +01:00
|
|
|
\row
|
|
|
|
|
\li Root Group finishes
|
|
|
|
|
\li Root Group finishes
|
|
|
|
|
\endtable
|
|
|
|
|
|
|
|
|
|
The differences between the scenarios are marked with bold. Three dots mean
|
|
|
|
|
that an unspecified amount of time passes between previous and next events
|
|
|
|
|
(a task or tasks continue to run). No dots between events
|
|
|
|
|
means that they occur synchronously.
|
|
|
|
|
|
|
|
|
|
The presented scenarios assume that all tasks run successfully. If a task
|
|
|
|
|
fails during execution, the task tree finishes with an error. In particular,
|
2023-05-09 23:36:18 +02:00
|
|
|
when ProcessTask finishes with an error while AsyncTask<int> is still being executed,
|
|
|
|
|
the AsyncTask<int> is automatically stopped, the subgroup finishes with an error,
|
|
|
|
|
the FileTransferTask is skipped, and the tree finishes with an error.
|
2023-02-01 14:16:18 +01:00
|
|
|
|
|
|
|
|
\section1 Task Types
|
|
|
|
|
|
|
|
|
|
Each task type is associated with its corresponding task class that executes
|
2023-05-09 23:36:18 +02:00
|
|
|
the task. For example, a ProcessTask inside a task tree is associated with
|
|
|
|
|
the Process class that executes the process. The associated objects are
|
2023-02-01 14:16:18 +01:00
|
|
|
automatically created, started, and destructed exclusively by the task tree
|
|
|
|
|
at the appropriate time.
|
|
|
|
|
|
2023-05-09 23:36:18 +02:00
|
|
|
If a root group consists of five sequential ProcessTask tasks, and the task tree
|
|
|
|
|
executes the group, it creates an instance of Process for the first
|
|
|
|
|
ProcessTask and starts it. If the Process instance finishes successfully,
|
|
|
|
|
the task tree destructs it and creates a new Process instance for the
|
|
|
|
|
second ProcessTask, and so on. If the first task finishes with an error, the task
|
|
|
|
|
tree stops creating Process instances, and the root group finishes with an
|
2023-02-01 14:16:18 +01:00
|
|
|
error.
|
|
|
|
|
|
|
|
|
|
The following table shows examples of task types and their corresponding task
|
|
|
|
|
classes:
|
|
|
|
|
|
|
|
|
|
\table
|
|
|
|
|
\header
|
|
|
|
|
\li Task Type (Tasking Namespace)
|
|
|
|
|
\li Associated Task Class
|
|
|
|
|
\li Brief Description
|
|
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li ProcessTask
|
|
|
|
|
\li Utils::Process
|
2023-02-01 14:16:18 +01:00
|
|
|
\li Starts processes.
|
|
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li AsyncTask<ReturnType>
|
|
|
|
|
\li Utils::Async<ReturnType>
|
2023-02-01 14:16:18 +01:00
|
|
|
\li Starts asynchronous tasks; run in separate thread.
|
|
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li TaskTreeTask
|
2023-02-01 14:16:18 +01:00
|
|
|
\li Utils::TaskTree
|
|
|
|
|
\li Starts a nested task tree.
|
|
|
|
|
\row
|
2023-05-09 23:36:18 +02:00
|
|
|
\li FileTransferTask
|
2023-02-01 14:16:18 +01:00
|
|
|
\li ProjectExplorer::FileTransfer
|
|
|
|
|
\li Starts file transfer between different devices.
|
|
|
|
|
\endtable
|
|
|
|
|
|
|
|
|
|
\section1 Task Handlers
|
|
|
|
|
|
|
|
|
|
Use Task handlers to set up a task for execution and to enable reading
|
|
|
|
|
the output data from the task when it finishes with success or an error.
|
|
|
|
|
|
|
|
|
|
\section2 Task Start Handler
|
|
|
|
|
|
|
|
|
|
When a corresponding task class object is created and before it's started,
|
|
|
|
|
the task tree invokes a mandatory user-provided setup handler. The setup
|
|
|
|
|
handler should always take a \e reference to the associated task class object:
|
|
|
|
|
|
|
|
|
|
\code
|
2023-05-09 23:36:18 +02:00
|
|
|
const auto onSetup = [](Process &process) {
|
2023-02-01 14:16:18 +01:00
|
|
|
process.setCommand({"sleep", {"3"}});
|
|
|
|
|
};
|
|
|
|
|
const Group root {
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(onSetup)
|
2023-02-01 14:16:18 +01:00
|
|
|
};
|
|
|
|
|
\endcode
|
|
|
|
|
|
2023-05-09 23:36:18 +02:00
|
|
|
You can modify the passed Process in the setup handler, so that the task
|
2023-02-01 14:16:18 +01:00
|
|
|
tree can start the process according to your configuration.
|
|
|
|
|
You do not need to call \e {process.start();} in the setup handler,
|
|
|
|
|
as the task tree calls it when needed. The setup handler is mandatory
|
|
|
|
|
and must be the first argument of the task's constructor.
|
|
|
|
|
|
|
|
|
|
Optionally, the setup handler may return a TaskAction. The returned
|
|
|
|
|
TaskAction influences the further start behavior of a given task. The
|
|
|
|
|
possible values are:
|
|
|
|
|
|
|
|
|
|
\table
|
|
|
|
|
\header
|
|
|
|
|
\li TaskAction Value
|
|
|
|
|
\li Brief Description
|
|
|
|
|
\row
|
|
|
|
|
\li Continue
|
|
|
|
|
\li The task is started normally. This is the default behavior when the
|
|
|
|
|
setup handler doesn't return TaskAction (that is, its return type is
|
|
|
|
|
void).
|
|
|
|
|
\row
|
|
|
|
|
\li StopWithDone
|
|
|
|
|
\li The task won't be started and it will report success to its parent.
|
|
|
|
|
\row
|
|
|
|
|
\li StopWithError
|
|
|
|
|
\li The task won't be started and it will report an error to its parent.
|
|
|
|
|
\endtable
|
|
|
|
|
|
|
|
|
|
This is useful for running a task only when a condition is met and the data
|
|
|
|
|
needed to evaluate this condition is not known until previously started tasks
|
|
|
|
|
finish. This way, the setup handler dynamically decides whether to start the
|
|
|
|
|
corresponding task normally or skip it and report success or an error.
|
|
|
|
|
For more information about inter-task data exchange, see \l Storage.
|
|
|
|
|
|
|
|
|
|
\section2 Task's Done and Error Handlers
|
|
|
|
|
|
|
|
|
|
When a running task finishes, the task tree invokes an optionally provided
|
|
|
|
|
done or error handler. Both handlers should always take a \e {const reference}
|
|
|
|
|
to the associated task class object:
|
|
|
|
|
|
|
|
|
|
\code
|
2023-05-09 23:36:18 +02:00
|
|
|
const auto onSetup = [](Process &process) {
|
2023-02-01 14:16:18 +01:00
|
|
|
process.setCommand({"sleep", {"3"}});
|
|
|
|
|
};
|
2023-05-09 23:36:18 +02:00
|
|
|
const auto onDone = [](const Process &process) {
|
2023-02-01 14:16:18 +01:00
|
|
|
qDebug() << "Success" << process.cleanedStdOut();
|
|
|
|
|
};
|
2023-05-09 23:36:18 +02:00
|
|
|
const auto onError = [](const Process &process) {
|
2023-02-01 14:16:18 +01:00
|
|
|
qDebug() << "Failure" << process.cleanedStdErr();
|
|
|
|
|
};
|
|
|
|
|
const Group root {
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(onSetup, onDone, onError)
|
2023-02-01 14:16:18 +01:00
|
|
|
};
|
|
|
|
|
\endcode
|
|
|
|
|
|
2023-05-09 23:36:18 +02:00
|
|
|
The done and error handlers may collect output data from Process, and store it
|
2023-02-01 14:16:18 +01:00
|
|
|
for further processing or perform additional actions. The done handler is optional.
|
|
|
|
|
When used, it must be the second argument of the task constructor.
|
|
|
|
|
The error handler must always be the third argument.
|
|
|
|
|
You can omit the handlers or substitute the ones that you do not need with curly braces ({}).
|
|
|
|
|
|
|
|
|
|
\note If the task setup handler returns StopWithDone or StopWithError,
|
|
|
|
|
neither the done nor error handler is invoked.
|
|
|
|
|
|
|
|
|
|
\section1 Group Handlers
|
|
|
|
|
|
|
|
|
|
Similarly to task handlers, group handlers enable you to set up a group to
|
|
|
|
|
execute and to apply more actions when the whole group finishes with
|
|
|
|
|
success or an error.
|
|
|
|
|
|
|
|
|
|
\section2 Group's Start Handler
|
|
|
|
|
|
|
|
|
|
The task tree invokes the group start handler before it starts the child
|
|
|
|
|
tasks. The group handler doesn't take any arguments:
|
|
|
|
|
|
|
|
|
|
\code
|
|
|
|
|
const auto onGroupSetup = [] {
|
|
|
|
|
qDebug() << "Entering the group";
|
|
|
|
|
};
|
|
|
|
|
const Group root {
|
|
|
|
|
OnGroupSetup(onGroupSetup),
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(...)
|
2023-02-01 14:16:18 +01:00
|
|
|
};
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
The group setup handler is optional. To define a group setup handler, add an
|
|
|
|
|
OnGroupSetup element to a group. The argument of OnGroupSetup is a user
|
|
|
|
|
handler. If you add more than one OnGroupSetup element to a group, an assert
|
|
|
|
|
is triggered at runtime that includes an error message.
|
|
|
|
|
|
|
|
|
|
Like the task start handler, the group start handler may return TaskAction.
|
|
|
|
|
The returned TaskAction value affects the start behavior of the
|
|
|
|
|
whole group. If you do not specify a group start handler or its return type
|
|
|
|
|
is void, the default group's action is TaskAction::Continue, so that all
|
|
|
|
|
tasks are started normally. Otherwise, when the start handler returns
|
|
|
|
|
TaskAction::StopWithDone or TaskAction::StopWithError, the tasks are not
|
|
|
|
|
started (they are skipped) and the group itself reports success or failure,
|
|
|
|
|
depending on the returned value, respectively.
|
|
|
|
|
|
|
|
|
|
\code
|
|
|
|
|
const Group root {
|
|
|
|
|
OnGroupSetup([] { qDebug() << "Root setup"; }),
|
|
|
|
|
Group {
|
|
|
|
|
OnGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }),
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(...) // Process 1
|
2023-02-01 14:16:18 +01:00
|
|
|
},
|
|
|
|
|
Group {
|
|
|
|
|
OnGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }),
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(...) // Process 2
|
2023-02-01 14:16:18 +01:00
|
|
|
},
|
|
|
|
|
Group {
|
|
|
|
|
OnGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }),
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(...) // Process 3
|
2023-02-01 14:16:18 +01:00
|
|
|
},
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(...) // Process 4
|
2023-02-01 14:16:18 +01:00
|
|
|
};
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
In the above example, all subgroups of a root group define their setup handlers.
|
|
|
|
|
The following scenario assumes that all started processes finish with success:
|
|
|
|
|
|
|
|
|
|
\table
|
|
|
|
|
\header
|
|
|
|
|
\li Scenario
|
|
|
|
|
\li Comment
|
|
|
|
|
\row
|
|
|
|
|
\li Root Group starts
|
|
|
|
|
\li Doesn't return TaskAction, so its tasks are executed.
|
|
|
|
|
\row
|
|
|
|
|
\li Group 1 starts
|
|
|
|
|
\li Returns Continue, so its tasks are executed.
|
|
|
|
|
\row
|
|
|
|
|
\li Process 1 starts
|
|
|
|
|
\li
|
|
|
|
|
\row
|
|
|
|
|
\li ...
|
|
|
|
|
\li ...
|
|
|
|
|
\row
|
|
|
|
|
\li Process 1 finishes (success)
|
|
|
|
|
\li
|
|
|
|
|
\row
|
|
|
|
|
\li Group 1 finishes (success)
|
|
|
|
|
\li
|
|
|
|
|
\row
|
|
|
|
|
\li Group 2 starts
|
|
|
|
|
\li Returns StopWithDone, so Process 2 is skipped and Group 2 reports
|
|
|
|
|
success.
|
|
|
|
|
\row
|
|
|
|
|
\li Group 2 finishes (success)
|
|
|
|
|
\li
|
|
|
|
|
\row
|
|
|
|
|
\li Group 3 starts
|
|
|
|
|
\li Returns StopWithError, so Process 3 is skipped and Group 3 reports
|
|
|
|
|
an error.
|
|
|
|
|
\row
|
|
|
|
|
\li Group 3 finishes (error)
|
|
|
|
|
\li
|
|
|
|
|
\row
|
|
|
|
|
\li Root Group finishes (error)
|
|
|
|
|
\li Group 3, which is a direct child of the root group, finished with an
|
|
|
|
|
error, so the root group stops executing, skips Process 4, which has
|
|
|
|
|
not started yet, and reports an error.
|
|
|
|
|
\endtable
|
|
|
|
|
|
|
|
|
|
\section2 Groups's Done and Error Handlers
|
|
|
|
|
|
|
|
|
|
A Group's done or error handler is executed after the successful or failed
|
|
|
|
|
execution of its tasks, respectively. The final value reported by the
|
|
|
|
|
group depends on its \l {Workflow Policy}. The handlers can apply other
|
|
|
|
|
necessary actions. The done and error handlers are defined inside the
|
|
|
|
|
OnGroupDone and OnGroupError elements of a group, respectively. They do not
|
|
|
|
|
take arguments:
|
|
|
|
|
|
|
|
|
|
\code
|
|
|
|
|
const Group root {
|
|
|
|
|
OnGroupSetup([] { qDebug() << "Root setup"; }),
|
2023-05-09 23:36:18 +02:00
|
|
|
ProcessTask(...),
|
2023-02-01 14:16:18 +01:00
|
|
|
OnGroupDone([] { qDebug() << "Root finished with success"; }),
|
|
|
|
|
OnGroupError([] { qDebug() << "Root finished with error"; })
|
|
|
|
|
};
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
The group done and error handlers are optional. If you add more than one
|
|
|
|
|
OnGroupDone or OnGroupError each to a group, an assert is triggered at
|
|
|
|
|
runtime that includes an error message.
|
|
|
|
|
|
|
|
|
|
\note Even if the group setup handler returns StopWithDone or StopWithError,
|
|
|
|
|
one of the task's done or error handlers is invoked. This behavior differs
|
|
|
|
|
from that of task handlers and might change in the future.
|
|
|
|
|
|
|
|
|
|
\section1 Other Group Elements
|
|
|
|
|
|
|
|
|
|
A group can contain other elements that describe the processing flow, such as
|
|
|
|
|
the execution mode or workflow policy. It can also contain storage elements
|
|
|
|
|
that are responsible for collecting and sharing custom common data gathered
|
|
|
|
|
during group execution.
|
|
|
|
|
|
|
|
|
|
\section2 Execution Mode
|
|
|
|
|
|
|
|
|
|
The execution mode element in a Group specifies how the direct child tasks of
|
|
|
|
|
the Group are started.
|
|
|
|
|
|
|
|
|
|
\table
|
|
|
|
|
\header
|
|
|
|
|
\li Execution Mode
|
|
|
|
|
\li Description
|
|
|
|
|
\row
|
|
|
|
|
\li sequential
|
|
|
|
|
\li Default. When a Group has no execution mode, it runs in the
|
|
|
|
|
sequential mode. All the direct child tasks of a group are started
|
|
|
|
|
in a chain, so that when one task finishes, the next one starts.
|
|
|
|
|
This enables you to pass the results from the previous task
|
|
|
|
|
as input to the next task before it starts. This mode guarantees
|
|
|
|
|
that the next task is started only after the previous task finishes.
|
|
|
|
|
\row
|
|
|
|
|
\li parallel
|
|
|
|
|
\li All the direct child tasks of a group are started after the group is
|
|
|
|
|
started, without waiting for the previous tasks to finish. In this
|
|
|
|
|
mode, all tasks run simultaneously.
|
|
|
|
|
\row
|
|
|
|
|
\li ParallelLimit(int limit)
|
|
|
|
|
\li In this mode, a limited number of direct child tasks run simultaneously.
|
|
|
|
|
The \e limit defines the maximum number of tasks running in parallel
|
|
|
|
|
in a group. When the group is started, the first batch tasks is
|
|
|
|
|
started (the number of tasks in batch equals to passed limit, at most),
|
|
|
|
|
while the others are kept waiting. When a running task finishes,
|
|
|
|
|
the group starts the next remaining one, so that the \e limit
|
|
|
|
|
of simultaneously running tasks inside a group isn't exceeded.
|
|
|
|
|
This repeats on every child task's finish until all child tasks are started.
|
|
|
|
|
This enables you to limit the maximum number of tasks that
|
|
|
|
|
run simultaneously, for example if running too many processes might
|
|
|
|
|
block the machine for a long time. The value 1 means \e sequential
|
|
|
|
|
execution. The value 0 means unlimited and equals \e parallel.
|
|
|
|
|
\endtable
|
|
|
|
|
|
|
|
|
|
In all execution modes, a group starts tasks in the oder in which they appear.
|
|
|
|
|
|
|
|
|
|
If a child of a group is also a group (in a nested tree), the child group
|
|
|
|
|
runs its tasks according to its own execution mode.
|
|
|
|
|
|
|
|
|
|
\section2 Workflow Policy
|
|
|
|
|
|
|
|
|
|
The workflow policy element in a Group specifies how the group should behave
|
|
|
|
|
when its direct child tasks finish:
|
|
|
|
|
|
|
|
|
|
\table
|
|
|
|
|
\header
|
|
|
|
|
\li Workflow Policy
|
|
|
|
|
\li Description
|
|
|
|
|
\row
|
|
|
|
|
\li stopOnError
|
|
|
|
|
\li Default. If a task finishes with an error, the group:
|
|
|
|
|
\list 1
|
|
|
|
|
\li Stops the running tasks (if any - for example, in parallel
|
|
|
|
|
mode).
|
|
|
|
|
\li Skips executing tasks it has not started (for example, in the
|
|
|
|
|
sequential mode).
|
|
|
|
|
\li Immediately finishes with an error.
|
|
|
|
|
\endlist
|
|
|
|
|
If all child tasks finish successfully or the group is empty, the group
|
|
|
|
|
finishes with success.
|
|
|
|
|
\row
|
|
|
|
|
\li continueOnError
|
|
|
|
|
\li Similar to stopOnError, but in case any child finishes with
|
|
|
|
|
an error, the execution continues until all tasks finish,
|
|
|
|
|
and the group reports an error afterwards, even when some other
|
|
|
|
|
tasks in group finished with success.
|
|
|
|
|
If a task finishes with an error, the group:
|
|
|
|
|
\list 1
|
|
|
|
|
\li Continues executing the tasks that are running or have not
|
|
|
|
|
started yet.
|
|
|
|
|
\li Finishes with an error when all tasks finish.
|
|
|
|
|
\endlist
|
|
|
|
|
If all tasks finish successfully or the group is empty, the group
|
|
|
|
|
finishes with success.
|
|
|
|
|
\row
|
|
|
|
|
\li stopOnDone
|
|
|
|
|
\li If a task finishes with success, the group:
|
|
|
|
|
\list 1
|
|
|
|
|
\li Stops running tasks and skips those that it has not started.
|
|
|
|
|
\li Immediately finishes with success.
|
|
|
|
|
\endlist
|
|
|
|
|
If all tasks finish with an error or the group is empty, the group
|
|
|
|
|
finishes with an error.
|
|
|
|
|
\row
|
|
|
|
|
\li continueOnDone
|
|
|
|
|
\li Similar to stopOnDone, but in case any child finishes
|
|
|
|
|
successfully, the execution continues until all tasks finish,
|
|
|
|
|
and the group reports success afterwards, even when some other
|
|
|
|
|
tasks in group finished with an error.
|
|
|
|
|
If a task finishes with success, the group:
|
|
|
|
|
\list 1
|
|
|
|
|
\li Continues executing the tasks that are running or have not
|
|
|
|
|
started yet.
|
|
|
|
|
\li Finishes with success when all tasks finish.
|
|
|
|
|
\endlist
|
|
|
|
|
If all tasks finish with an error or the group is empty, the group
|
|
|
|
|
finishes with an error.
|
|
|
|
|
\row
|
|
|
|
|
\li optional
|
|
|
|
|
\li The group executes all tasks and ignores their return state. If all
|
|
|
|
|
tasks finish or the group is empty, the group finishes with success.
|
|
|
|
|
\endtable
|
|
|
|
|
|
|
|
|
|
If a child of a group is also a group (in a nested tree), the child group
|
|
|
|
|
runs its tasks according to its own workflow policy.
|
|
|
|
|
|
|
|
|
|
\section2 Storage
|
|
|
|
|
|
|
|
|
|
Use the Storage element to exchange information between tasks. Especially,
|
|
|
|
|
in the sequential execution mode, when a task needs data from another task
|
|
|
|
|
before it can start. For example, a task tree that copies data by reading
|
|
|
|
|
it from a source and writing it to a destination might look as follows:
|
|
|
|
|
|
|
|
|
|
\code
|
|
|
|
|
static QByteArray load(const FilePath &fileName) { ... }
|
|
|
|
|
static void save(const FilePath &fileName, const QByteArray &array) { ... }
|
|
|
|
|
|
|
|
|
|
static TaskItem diffRecipe(const FilePath &source, const FilePath &destination)
|
|
|
|
|
{
|
|
|
|
|
struct CopyStorage { // [1] custom inter-task struct
|
|
|
|
|
QByteArray content; // [2] custom inter-task data
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// [3] instance of custom inter-task struct manageable by task tree
|
|
|
|
|
const TreeStorage<CopyStorage> storage;
|
|
|
|
|
|
|
|
|
|
const auto onLoaderSetup = [source](Async<QByteArray> &async) {
|
2023-05-09 23:36:18 +02:00
|
|
|
async.setConcurrentCallData(&load, source);
|
2023-02-01 14:16:18 +01:00
|
|
|
};
|
|
|
|
|
// [4] runtime: task tree activates the instance from [5] before invoking handler
|
|
|
|
|
const auto onLoaderDone = [storage](const Async<QByteArray> &async) {
|
|
|
|
|
storage->content = async.result();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// [4] runtime: task tree activates the instance from [5] before invoking handler
|
|
|
|
|
const auto onSaverSetup = [storage, destination](Async<void> &async) {
|
2023-05-09 23:36:18 +02:00
|
|
|
async.setConcurrentCallData(&save, destination, storage->content);
|
2023-02-01 14:16:18 +01:00
|
|
|
};
|
|
|
|
|
const auto onSaverDone = [](const Async<void> &async) {
|
|
|
|
|
qDebug() << "Save done successfully";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Group root {
|
|
|
|
|
// [5] runtime: task tree creates an instance of CopyStorage when root is entered
|
|
|
|
|
Storage(storage),
|
2023-05-09 23:36:18 +02:00
|
|
|
AsyncTask<QByteArray>(onLoaderSetup, onLoaderDone),
|
|
|
|
|
AsyncTask<void>(onSaverSetup, onSaverDone)
|
2023-02-01 14:16:18 +01:00
|
|
|
};
|
|
|
|
|
return root;
|
|
|
|
|
}
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
In the example above, the inter-task data consists of a QByteArray content
|
|
|
|
|
variable [2] enclosed in a CopyStorage custom struct [1]. If the loader
|
|
|
|
|
finishes successfully, it stores the data in a CopyStorage::content
|
|
|
|
|
variable. The saver then uses the variable to configure the saving task.
|
|
|
|
|
|
|
|
|
|
To enable a task tree to manage the CopyStorage struct, an instance of
|
|
|
|
|
TreeStorage<CopyStorage> is created [3]. If a copy of this object is
|
|
|
|
|
inserted as group's child task [5], an instance of CopyStorage struct is
|
|
|
|
|
created dynamically when the task tree enters this group. When the task
|
|
|
|
|
tree leaves this group, the existing instance of CopyStorage struct is
|
|
|
|
|
destructed as it's no longer needed.
|
|
|
|
|
|
|
|
|
|
If several task trees that hold a copy of the common TreeStorage<CopyStorage>
|
|
|
|
|
instance run simultaneously, each task tree contains its own copy of the
|
|
|
|
|
CopyStorage struct.
|
|
|
|
|
|
|
|
|
|
You can access CopyStorage from any handler in the group with a storage object.
|
|
|
|
|
This includes all handlers of all descendant tasks of the group with
|
|
|
|
|
a storage object. To access the custom struct in a handler, pass the
|
|
|
|
|
copy of the TreeStorage<CopyStorage> object to the handler (for example, in
|
|
|
|
|
a lambda capture) [4].
|
|
|
|
|
|
|
|
|
|
When the task tree invokes a handler in a subtree containing the storage [5],
|
|
|
|
|
the task tree activates its own CopyStorage instance inside the
|
|
|
|
|
TreeStorage<CopyStorage> object. Therefore, the CopyStorage struct may be
|
|
|
|
|
accessed only from within the handler body. To access the currently active
|
|
|
|
|
CopyStorage from within TreeStorage<CopyStorage>, use the TreeStorage::operator->()
|
|
|
|
|
or TreeStorage::activeStorage() method.
|
|
|
|
|
|
|
|
|
|
The following list summarizes how to employ a Storage object into the task
|
|
|
|
|
tree:
|
|
|
|
|
\list 1
|
|
|
|
|
\li Define the custom structure MyStorage with custom data [1], [2]
|
|
|
|
|
\li Create an instance of TreeStorage<MyStorage> storage [3]
|
|
|
|
|
\li Pass the TreeStorage<MyStorage> instance to handlers [4]
|
|
|
|
|
\li Insert the TreeStorage<MyStorage> instance into a group [5]
|
|
|
|
|
\endlist
|
|
|
|
|
|
|
|
|
|
\note The current implementation assumes that all running task trees
|
|
|
|
|
containing copies of the same TreeStorage run in the same thread. Otherwise,
|
|
|
|
|
the behavior is undefined.
|
|
|
|
|
|
|
|
|
|
\section1 TaskTree
|
|
|
|
|
|
|
|
|
|
TaskTree executes the tree structure of asynchronous tasks according to the
|
|
|
|
|
recipe described by the Group root element.
|
|
|
|
|
|
|
|
|
|
As TaskTree is also an asynchronous task, it can be a part of another TaskTree.
|
2023-05-10 19:54:52 +02:00
|
|
|
To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask
|
2023-02-01 14:16:18 +01:00
|
|
|
element into other tree's Group element.
|
|
|
|
|
|
|
|
|
|
TaskTree reports progress of completed tasks when running. The progress value
|
|
|
|
|
is increased when a task finishes or is skipped or stopped.
|
|
|
|
|
When TaskTree is finished and the TaskTree::done() or TaskTree::errorOccurred()
|
|
|
|
|
signal is emitted, the current value of the progress equals the maximum
|
|
|
|
|
progress value. Maximum progress equals the total number of tasks in a tree.
|
|
|
|
|
A nested TaskTree is counted as a single task, and its child tasks are not
|
|
|
|
|
counted in the top level tree. Groups themselves are not counted as tasks,
|
|
|
|
|
but their tasks are counted.
|
|
|
|
|
|
|
|
|
|
To set additional initial data for the running tree, modify the storage
|
|
|
|
|
instances in a tree when it creates them by installing a storage setup
|
|
|
|
|
handler:
|
|
|
|
|
|
|
|
|
|
\code
|
|
|
|
|
TreeStorage<CopyStorage> storage;
|
|
|
|
|
Group root = ...; // storage placed inside root's group and inside handlers
|
|
|
|
|
TaskTree taskTree(root);
|
|
|
|
|
auto initStorage = [](CopyStorage *storage){
|
|
|
|
|
storage->content = "initial content";
|
|
|
|
|
};
|
|
|
|
|
taskTree.onStorageSetup(storage, initStorage);
|
|
|
|
|
taskTree.start();
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
When the running task tree creates a CopyStorage instance, and before any
|
|
|
|
|
handler inside a tree is called, the task tree calls the initStorage handler,
|
|
|
|
|
to enable setting up initial data of the storage, unique to this particular
|
|
|
|
|
run of taskTree.
|
|
|
|
|
|
|
|
|
|
Similarly, to collect some additional result data from the running tree,
|
|
|
|
|
read it from storage instances in the tree when they are about to be
|
|
|
|
|
destroyed. To do this, install a storage done handler:
|
|
|
|
|
|
|
|
|
|
\code
|
|
|
|
|
TreeStorage<CopyStorage> storage;
|
|
|
|
|
Group root = ...; // storage placed inside root's group and inside handlers
|
|
|
|
|
TaskTree taskTree(root);
|
|
|
|
|
auto collectStorage = [](CopyStorage *storage){
|
|
|
|
|
qDebug() << "final content" << storage->content;
|
|
|
|
|
};
|
|
|
|
|
taskTree.onStorageDone(storage, collectStorage);
|
|
|
|
|
taskTree.start();
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
When the running task tree is about to destroy a CopyStorage instance, the
|
|
|
|
|
task tree calls the collectStorage handler, to enable reading the final data
|
|
|
|
|
from the storage, unique to this particular run of taskTree.
|
|
|
|
|
|
|
|
|
|
\section1 Task Adapters
|
|
|
|
|
|
|
|
|
|
To extend a TaskTree with new a task type, implement a simple adapter class
|
|
|
|
|
derived from the TaskAdapter class template. The following class is an
|
|
|
|
|
adapter for a single shot timer, which may be considered as a new
|
|
|
|
|
asynchronous task:
|
|
|
|
|
|
|
|
|
|
\code
|
2023-05-10 19:54:52 +02:00
|
|
|
class TimeoutAdapter : public Tasking::TaskAdapter<QTimer>
|
2023-02-01 14:16:18 +01:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
TimeoutAdapter() {
|
|
|
|
|
task()->setSingleShot(true);
|
|
|
|
|
task()->setInterval(1000);
|
|
|
|
|
connect(task(), &QTimer::timeout, this, [this] { emit done(true); });
|
|
|
|
|
}
|
|
|
|
|
void start() final { task()->start(); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter);
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
You must derive the custom adapter from the TaskAdapter class template
|
|
|
|
|
instantiated with a template parameter of the class implementing a running
|
|
|
|
|
task. The code above uses QTimer to run the task. This class appears
|
|
|
|
|
later as an argument to the task's handlers. The instance of this class
|
|
|
|
|
parameter automatically becomes a member of the TaskAdapter template, and is
|
|
|
|
|
accessible through the TaskAdapter::task() method. The constructor
|
|
|
|
|
of TimeoutAdapter initially configures the QTimer object and connects
|
|
|
|
|
to the QTimer::timeout signal. When the signal is triggered, TimeoutAdapter
|
|
|
|
|
emits the done(true) signal to inform the task tree that the task finished
|
|
|
|
|
successfully. If it emits done(false), the task finished with an error.
|
|
|
|
|
The TaskAdapter::start() method starts the timer.
|
|
|
|
|
|
|
|
|
|
To make QTimer accessible inside TaskTree under the \e Timeout name,
|
|
|
|
|
register it with QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter). Timeout
|
2023-05-10 19:54:52 +02:00
|
|
|
becomes a new task type inside Tasking namespace, using TimeoutAdapter.
|
2023-02-01 14:16:18 +01:00
|
|
|
|
|
|
|
|
The new task type is now registered, and you can use it in TaskTree:
|
|
|
|
|
|
|
|
|
|
\code
|
|
|
|
|
const auto onTimeoutSetup = [](QTimer &task) {
|
|
|
|
|
task.setInterval(2000);
|
|
|
|
|
};
|
|
|
|
|
const auto onTimeoutDone = [](const QTimer &task) {
|
|
|
|
|
qDebug() << "timeout triggered";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Group root {
|
|
|
|
|
Timeout(onTimeoutSetup, onTimeoutDone)
|
|
|
|
|
};
|
|
|
|
|
\endcode
|
|
|
|
|
|
|
|
|
|
When a task tree containing the root from the above example is started, it
|
|
|
|
|
prints a debug message within two seconds and then finishes successfully.
|
|
|
|
|
|
|
|
|
|
\note The class implementing the running task should have a default constructor,
|
|
|
|
|
and objects of this class should be freely destructible. It should be allowed
|
|
|
|
|
to destroy a running object, preferably without waiting for the running task
|
|
|
|
|
to finish (that is, safe non-blocking destructor of a running task).
|
2022-10-12 14:30:24 +02:00
|
|
|
*/
|
|
|
|
|
|
2022-11-16 08:07:29 +01:00
|
|
|
TaskTree::TaskTree()
|
|
|
|
|
: d(new TaskTreePrivate(this))
|
2022-10-12 14:30:24 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-16 08:07:29 +01:00
|
|
|
TaskTree::TaskTree(const Group &root) : TaskTree()
|
|
|
|
|
{
|
|
|
|
|
setupRoot(root);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
TaskTree::~TaskTree()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
|
|
|
|
|
"one of its handlers will lead to crash!"));
|
2023-02-20 21:32:00 +01:00
|
|
|
// TODO: delete storages explicitly here?
|
2022-10-12 14:30:24 +02:00
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-10 19:54:52 +02:00
|
|
|
void TaskTree::setupRoot(const Group &root)
|
2022-11-16 08:07:29 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
|
|
|
|
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the"
|
|
|
|
|
"TaskTree handlers, ingoring..."); return);
|
2023-01-20 11:11:23 +01:00
|
|
|
d->m_storages.clear();
|
2023-01-31 12:50:59 +01:00
|
|
|
d->m_root.reset(new TaskNode(d, root, nullptr));
|
2022-11-16 08:07:29 +01:00
|
|
|
}
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
void TaskTree::start()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
|
|
|
|
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the"
|
|
|
|
|
"TaskTree handlers, ingoring..."); return);
|
2022-11-10 15:59:54 +01:00
|
|
|
d->start();
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskTree::stop()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The stop() is called from one of the"
|
|
|
|
|
"TaskTree handlers, ingoring..."); return);
|
2022-11-10 15:59:54 +01:00
|
|
|
d->stop();
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TaskTree::isRunning() const
|
|
|
|
|
{
|
2022-11-16 08:07:29 +01:00
|
|
|
return d->m_root && d->m_root->isRunning();
|
2022-10-12 14:30:24 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-15 10:52:45 +02:00
|
|
|
bool TaskTree::runBlocking(const QFuture<void> &future, int timeoutMs)
|
|
|
|
|
{
|
|
|
|
|
if (isRunning() || future.isCanceled())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
bool ok = false;
|
|
|
|
|
QEventLoop loop;
|
|
|
|
|
|
|
|
|
|
const auto finalize = [&loop, &ok](bool success) {
|
|
|
|
|
ok = success;
|
|
|
|
|
// Otherwise, the tasks from inside the running tree that were deleteLater()
|
|
|
|
|
// will be leaked. Refer to the QObject::deleteLater() docs.
|
|
|
|
|
QMetaObject::invokeMethod(&loop, [&loop] { loop.quit(); }, Qt::QueuedConnection);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
QFutureWatcher<void> watcher;
|
|
|
|
|
connect(&watcher, &QFutureWatcherBase::canceled, this, &TaskTree::stop);
|
|
|
|
|
watcher.setFuture(future);
|
|
|
|
|
|
|
|
|
|
connect(this, &TaskTree::done, &loop, [finalize] { finalize(true); });
|
|
|
|
|
connect(this, &TaskTree::errorOccurred, &loop, [finalize] { finalize(false); });
|
|
|
|
|
start();
|
|
|
|
|
if (!isRunning())
|
|
|
|
|
return ok;
|
|
|
|
|
|
|
|
|
|
QTimer timer;
|
|
|
|
|
if (timeoutMs) {
|
|
|
|
|
timer.setSingleShot(true);
|
|
|
|
|
timer.setInterval(timeoutMs);
|
|
|
|
|
connect(&timer, &QTimer::timeout, this, &TaskTree::stop);
|
|
|
|
|
timer.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loop.exec(QEventLoop::ExcludeUserInputEvents);
|
|
|
|
|
if (!ok) {
|
|
|
|
|
auto nonConstFuture = future;
|
|
|
|
|
nonConstFuture.cancel();
|
|
|
|
|
}
|
|
|
|
|
return ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool TaskTree::runBlocking(int timeoutMs)
|
|
|
|
|
{
|
|
|
|
|
QPromise<void> dummy;
|
|
|
|
|
dummy.start();
|
|
|
|
|
return runBlocking(dummy.future(), timeoutMs);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-10 14:31:25 +01:00
|
|
|
int TaskTree::taskCount() const
|
|
|
|
|
{
|
2022-11-16 08:07:29 +01:00
|
|
|
return d->m_root ? d->m_root->taskCount() : 0;
|
2022-11-10 14:31:25 +01:00
|
|
|
}
|
|
|
|
|
|
2022-11-10 15:59:54 +01:00
|
|
|
int TaskTree::progressValue() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_progressValue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-10 19:54:52 +02:00
|
|
|
void TaskTree::setupStorageHandler(const TreeStorageBase &storage,
|
2022-12-05 11:20:57 +01:00
|
|
|
StorageVoidHandler setupHandler,
|
|
|
|
|
StorageVoidHandler doneHandler)
|
|
|
|
|
{
|
|
|
|
|
auto it = d->m_storageHandlers.find(storage);
|
|
|
|
|
if (it == d->m_storageHandlers.end()) {
|
|
|
|
|
d->m_storageHandlers.insert(storage, {setupHandler, doneHandler});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (setupHandler) {
|
|
|
|
|
QTC_ASSERT(!it->m_setupHandler,
|
|
|
|
|
qWarning("The storage has its setup handler defined, overriding..."));
|
|
|
|
|
it->m_setupHandler = setupHandler;
|
|
|
|
|
}
|
|
|
|
|
if (doneHandler) {
|
|
|
|
|
QTC_ASSERT(!it->m_doneHandler,
|
|
|
|
|
qWarning("The storage has its done handler defined, overriding..."));
|
|
|
|
|
it->m_doneHandler = doneHandler;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-09 23:43:18 +02:00
|
|
|
TaskTreeTaskAdapter::TaskTreeTaskAdapter()
|
2022-11-16 09:06:32 +01:00
|
|
|
{
|
|
|
|
|
connect(task(), &TaskTree::done, this, [this] { emit done(true); });
|
|
|
|
|
connect(task(), &TaskTree::errorOccurred, this, [this] { emit done(false); });
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-09 23:43:18 +02:00
|
|
|
void TaskTreeTaskAdapter::start()
|
2022-11-16 09:06:32 +01:00
|
|
|
{
|
|
|
|
|
task()->start();
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-10 19:54:52 +02:00
|
|
|
} // namespace Tasking
|