2022-10-12 14:30:24 +02:00
|
|
|
// Copyright (C) 2022 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"
|
|
|
|
|
|
|
|
|
|
#include "guard.h"
|
|
|
|
|
#include "qtcassert.h"
|
|
|
|
|
|
2023-01-20 10:44:21 +01:00
|
|
|
#include <QSet>
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
namespace Utils {
|
|
|
|
|
namespace Tasking {
|
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_storageData->m_activeStorage, return nullptr);
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_type == Type::Group, qWarning("Only Task may have children, skipping..."); return);
|
|
|
|
|
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:
|
2022-10-12 14:30:24 +02:00
|
|
|
QTC_ASSERT(m_type == Type::Group,
|
|
|
|
|
qWarning("Mode may only be a child of 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:
|
|
|
|
|
QTC_ASSERT(m_type == Type::Group,
|
|
|
|
|
qWarning("Workflow Policy may only be a child of Group, skipping..."); return);
|
|
|
|
|
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);
|
|
|
|
|
QTC_ASSERT(child.m_taskHandler.m_setupHandler,
|
|
|
|
|
qWarning("Task Setup 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 "
|
|
|
|
|
"child of 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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace Tasking
|
|
|
|
|
|
|
|
|
|
using namespace Tasking;
|
|
|
|
|
|
|
|
|
|
class TaskTreePrivate;
|
|
|
|
|
class TaskNode;
|
|
|
|
|
|
|
|
|
|
class TaskContainer
|
|
|
|
|
{
|
|
|
|
|
public:
|
2023-01-31 12:50:59 +01:00
|
|
|
TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
|
|
|
|
TaskContainer *parentContainer)
|
|
|
|
|
: m_constData(taskTreePrivate, task, 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 {
|
|
|
|
|
ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
|
|
|
|
|
TaskContainer *parentContainer, TaskContainer *thisContainer);
|
|
|
|
|
~ConstData() { qDeleteAll(m_children); }
|
|
|
|
|
TaskTreePrivate * const m_taskTreePrivate = nullptr;
|
|
|
|
|
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);
|
|
|
|
|
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
|
|
|
};
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
class TaskNode : public QObject
|
|
|
|
|
{
|
|
|
|
|
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-01-31 12:50:59 +01:00
|
|
|
, m_container(taskTreePrivate, task, parentContainer)
|
|
|
|
|
{}
|
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(); }
|
|
|
|
|
bool isTask() const { return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler; }
|
|
|
|
|
int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; }
|
|
|
|
|
TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; }
|
2022-10-12 14:30:24 +02:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const TaskItem::TaskHandler m_taskHandler;
|
|
|
|
|
TaskContainer m_container;
|
|
|
|
|
std::unique_ptr<TaskInterface> m_task;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class TaskTreePrivate
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-11-16 08:07:29 +01:00
|
|
|
TaskTreePrivate(TaskTree *taskTree)
|
|
|
|
|
: q(taskTree) {}
|
2022-10-12 14:30:24 +02:00
|
|
|
|
2022-11-10 15:59:54 +01:00
|
|
|
void start() {
|
2022-11-16 08:07:29 +01:00
|
|
|
QTC_ASSERT(m_root, return);
|
2022-11-10 15:59:54 +01:00
|
|
|
m_progressValue = 0;
|
|
|
|
|
emitStartedAndProgress();
|
2022-12-05 11:20:57 +01:00
|
|
|
// 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."));
|
|
|
|
|
}
|
2023-01-24 21:04:59 +01:00
|
|
|
m_root->start();
|
2022-11-10 15:59:54 +01:00
|
|
|
}
|
|
|
|
|
void stop() {
|
2022-11-16 08:07:29 +01:00
|
|
|
QTC_ASSERT(m_root, return);
|
|
|
|
|
if (!m_root->isRunning())
|
2022-11-10 15:59:54 +01:00
|
|
|
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.
|
2022-11-16 08:07:29 +01:00
|
|
|
m_root->stop();
|
2022-11-10 15:59:54 +01:00
|
|
|
emitError();
|
|
|
|
|
}
|
|
|
|
|
void advanceProgress(int byValue) {
|
|
|
|
|
if (byValue == 0)
|
|
|
|
|
return;
|
|
|
|
|
QTC_CHECK(byValue > 0);
|
2022-11-16 08:07:29 +01:00
|
|
|
QTC_CHECK(m_progressValue + byValue <= m_root->taskCount());
|
2022-11-10 15:59:54 +01:00
|
|
|
m_progressValue += byValue;
|
|
|
|
|
emitProgress();
|
|
|
|
|
}
|
|
|
|
|
void emitStartedAndProgress() {
|
|
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
emit q->started();
|
|
|
|
|
emit q->progressValueChanged(m_progressValue);
|
|
|
|
|
}
|
|
|
|
|
void emitProgress() {
|
|
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
emit q->progressValueChanged(m_progressValue);
|
|
|
|
|
}
|
2022-10-12 14:30:24 +02:00
|
|
|
void emitDone() {
|
2022-11-16 08:07:29 +01:00
|
|
|
QTC_CHECK(m_progressValue == m_root->taskCount());
|
2022-10-12 14:30:24 +02:00
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
emit q->done();
|
|
|
|
|
}
|
|
|
|
|
void emitError() {
|
2022-11-16 08:07:29 +01:00
|
|
|
QTC_CHECK(m_progressValue == m_root->taskCount());
|
2022-10-12 14:30:24 +02:00
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
emit q->errorOccurred();
|
|
|
|
|
}
|
2022-12-05 10:25:59 +01:00
|
|
|
QList<TreeStorageBase> 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;
|
|
|
|
|
}
|
2022-12-05 11:20:57 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2022-10-12 14:30:24 +02:00
|
|
|
|
|
|
|
|
TaskTree *q = nullptr;
|
|
|
|
|
Guard m_guard;
|
2022-11-10 15:59:54 +01:00
|
|
|
int m_progressValue = 0;
|
2022-12-05 10:25:59 +01:00
|
|
|
QSet<TreeStorageBase> m_storages;
|
2022-12-05 11:20:57 +01:00
|
|
|
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
|
|
|
};
|
|
|
|
|
|
2023-01-31 12:50:59 +01:00
|
|
|
class StorageActivator
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
StorageActivator(TaskContainer *container)
|
|
|
|
|
: m_container(container) { activateStorages(m_container); }
|
|
|
|
|
~StorageActivator() { deactivateStorages(m_container); }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
static void activateStorages(TaskContainer *container)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(container && container->isRunning(), return);
|
|
|
|
|
const TaskContainer::ConstData &constData = container->m_constData;
|
|
|
|
|
if (constData.m_parentContainer)
|
|
|
|
|
activateStorages(constData.m_parentContainer);
|
|
|
|
|
for (int i = 0; i < constData.m_storageList.size(); ++i)
|
|
|
|
|
constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i));
|
|
|
|
|
}
|
|
|
|
|
static void deactivateStorages(TaskContainer *container)
|
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
deactivateStorages(constData.m_parentContainer);
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
StorageActivator activator(container);
|
|
|
|
|
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,
|
|
|
|
|
TaskContainer *parentContainer, TaskContainer *thisContainer)
|
2022-10-12 14:30:24 +02:00
|
|
|
: m_taskTreePrivate(taskTreePrivate)
|
|
|
|
|
, 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
m_constData.m_taskTreePrivate->callDoneHandler(storage, storageId);
|
|
|
|
|
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-01-31 12:50:59 +01:00
|
|
|
if (m_constData.m_children.isEmpty() && startAction == TaskAction::Continue)
|
|
|
|
|
startAction = TaskAction::StopWithDone;
|
|
|
|
|
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);
|
|
|
|
|
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-01-31 12:50:59 +01:00
|
|
|
const TaskAction startAction = invokeHandler(parentContainer(), m_taskHandler.m_setupHandler,
|
|
|
|
|
*m_task.get());
|
|
|
|
|
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-01-09 20:52:12 +01:00
|
|
|
connect(m_task.get(), &TaskInterface::done, this, [=](bool success) {
|
2023-01-31 12:50:59 +01:00
|
|
|
invokeEndHandler(success);
|
|
|
|
|
disconnect(m_task.get(), &TaskInterface::done, this, nullptr);
|
|
|
|
|
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
|
|
|
/*!
|
|
|
|
|
\class Utils::TaskTree
|
|
|
|
|
|
|
|
|
|
\brief The TaskTree class is responsible for running async task tree structure defined in a
|
|
|
|
|
declarative way.
|
|
|
|
|
|
|
|
|
|
The Tasking namespace (similar to Layouting) is designer for building declarative task
|
|
|
|
|
tree structure. The examples of tasks that can be used inside TaskTree are e.g. QtcProcess,
|
|
|
|
|
FileTransfer, AsyncTask<>. It's extensible, so any possible asynchronous task may be
|
|
|
|
|
integrated and used inside TaskTree. TaskTree enables to form sophisticated mixtures of
|
|
|
|
|
parallel or sequential flow of tasks in tree form.
|
|
|
|
|
|
|
|
|
|
The TaskTree consist of Group root element. The Group can have nested Group elements.
|
|
|
|
|
The Group may also contain any number of tasks, e.g. Process, FileTransfer,
|
|
|
|
|
AsyncTask<ReturnType>.
|
|
|
|
|
|
|
|
|
|
Each Group can contain various other elements describing the processing flow.
|
|
|
|
|
|
|
|
|
|
The execute mode elements of a Group specify how direct children of a Group will be executed.
|
|
|
|
|
The "sequential" element of a Group means all tasks in a group will be executed in chain,
|
|
|
|
|
so after the previous task finished, the next will be started. This is the default Group
|
|
|
|
|
behavior. The "parallel" element of a Group means that all tasks in a Group will be started
|
|
|
|
|
simultaneously. When having nested Groups hierarchy, we may mix execute modes freely
|
|
|
|
|
and each Group will be executed according to its own execute mode.
|
|
|
|
|
The "sequential" mode may be very useful in cases when result data from one task is need to
|
|
|
|
|
be passed as an input data to the other task - sequential mode guarantees that the next
|
|
|
|
|
task will be started only after the previous task has already finished.
|
|
|
|
|
|
|
|
|
|
There are many possible "workflow" behaviors for the Group. E.g. "stopOnError",
|
|
|
|
|
the default Group workflow behavior, means that whenever any direct child of a Group
|
|
|
|
|
finished with error, we immediately stop processing other tasks in this group
|
|
|
|
|
(in parallel case) by canceling them and immediately finish the Group with error.
|
|
|
|
|
|
|
|
|
|
The user of TaskTree specifies how to setup his tasks (by providing TaskSetupHandlers)
|
|
|
|
|
and how to collect output data from the finished tasks (by providing TaskEndHandlers).
|
|
|
|
|
The user don't need to create tasks manually - TaskTree will create them when it's needed
|
|
|
|
|
and destroy when they are not used anymore.
|
|
|
|
|
|
|
|
|
|
Whenever a Group elemenent is being started, the Group's OnGroupSetup handler is being called.
|
|
|
|
|
Just after the handler finishes, all Group's children are executed (either in parallel or
|
|
|
|
|
in sequence). When all Group's children finished, one of Group's OnGroupDone or OnGroupError
|
|
|
|
|
is being executed, depending on results of children execution and Group's workflow policy.
|
|
|
|
|
*/
|
|
|
|
|
|
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!"));
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-16 08:07:29 +01:00
|
|
|
void TaskTree::setupRoot(const Tasking::Group &root)
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-05 11:20:57 +01:00
|
|
|
void TaskTree::setupStorageHandler(const Tasking::TreeStorageBase &storage,
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-16 09:06:32 +01:00
|
|
|
TaskTreeAdapter::TaskTreeAdapter()
|
|
|
|
|
{
|
|
|
|
|
connect(task(), &TaskTree::done, this, [this] { emit done(true); });
|
|
|
|
|
connect(task(), &TaskTree::errorOccurred, this, [this] { emit done(false); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TaskTreeAdapter::start()
|
|
|
|
|
{
|
|
|
|
|
task()->start();
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-12 14:30:24 +02:00
|
|
|
} // namespace Utils
|