TaskTree: Split completely const and runtime trees

Separate completely const tree (data taken from recipe)
from runtime tree. Build runtime tree incrementally, on demand.

The const tree is created on TaskTree::setRecipe() and
the runtime tree is created on TaskTree::start().
The const tree serves as a kind of template for creating
the runtime task tree later, dynamically.

Prepare for the implementation of the loop functionality
(3rd point on the master task below).

Task-number: QTCREATORBUG-28741
Change-Id: I80382e5ef43d53b36ca3c70472b193c5949f5ab9
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2023-11-07 12:44:11 +01:00
parent 0269626a17
commit 3ae0185817
2 changed files with 294 additions and 264 deletions

View File

@@ -3,6 +3,7 @@
#include "tasktree.h" #include "tasktree.h"
#include <QDebug>
#include <QEventLoop> #include <QEventLoop>
#include <QFutureWatcher> #include <QFutureWatcher>
#include <QHash> #include <QHash>
@@ -1053,6 +1054,22 @@ GroupItem GroupItem::withTimeout(const GroupItem &item, milliseconds timeout,
class TaskTreePrivate; class TaskTreePrivate;
class TaskNode; class TaskNode;
class TaskRuntimeNode;
class TaskRuntimeContainer;
class ExecutionContextActivator
{
public:
ExecutionContextActivator(TaskRuntimeContainer *container) { activateContext(container); }
~ExecutionContextActivator() {
for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order
m_activeStorages[i].m_storageData->threadData().activateStorage(0);
}
private:
void activateContext(TaskRuntimeContainer *container);
QList<TreeStorageBase> m_activeStorages;
};
class TaskTreePrivate class TaskTreePrivate
{ {
@@ -1075,10 +1092,10 @@ public:
callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler); callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler);
} }
struct StorageHandler { struct StorageHandler {
TaskTree::StorageVoidHandler m_setupHandler = {}; TreeStorageBase::StorageVoidHandler m_setupHandler = {};
TaskTree::StorageVoidHandler m_doneHandler = {}; TreeStorageBase::StorageVoidHandler m_doneHandler = {};
}; };
typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member typedef TreeStorageBase::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member
void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr) void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr)
{ {
const auto it = m_storageHandlers.constFind(storage); const auto it = m_storageHandlers.constFind(storage);
@@ -1095,98 +1112,176 @@ public:
threadData.activateStorage(0); threadData.activateStorage(0);
} }
// Node related methods
// If returned value != Continue, childDone() needs to be called in parent container (in caller)
// in order to unwind properly.
SetupResult start(TaskRuntimeNode *node);
void stop(TaskRuntimeNode *node);
bool invokeDoneHandler(TaskRuntimeNode *node, DoneWith doneWith);
// Container related methods
SetupResult start(TaskRuntimeContainer *container);
SetupResult continueStart(TaskRuntimeContainer *container, SetupResult startAction, int nextChild);
SetupResult startChildren(TaskRuntimeContainer *container, int nextChild);
SetupResult childDone(TaskRuntimeContainer *container, bool success);
void stop(TaskRuntimeContainer *container);
bool invokeDoneHandler(TaskRuntimeContainer *container, DoneWith doneWith);
template <typename Handler, typename ...Args,
typename ReturnType = typename std::invoke_result_t<Handler, Args...>>
ReturnType invokeHandler(TaskRuntimeContainer *container, Handler &&handler, Args &&...args)
{
ExecutionContextActivator activator(container);
GuardLocker locker(m_guard);
return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
}
TaskTree *q = nullptr; TaskTree *q = nullptr;
Guard m_guard; Guard m_guard;
int m_progressValue = 0; int m_progressValue = 0;
QSet<TreeStorageBase> m_storages; QSet<TreeStorageBase> m_storages;
QHash<TreeStorageBase, StorageHandler> m_storageHandlers; QHash<TreeStorageBase, StorageHandler> m_storageHandlers;
std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
std::unique_ptr<TaskRuntimeNode> m_runtimeRoot = nullptr;
}; };
class TaskContainer class TaskContainer
{ {
Q_DISABLE_COPY_MOVE(TaskContainer) Q_DISABLE_COPY(TaskContainer)
public: public:
TaskContainer(TaskContainer &&other) = default;
TaskContainer(TaskTreePrivate *taskTreePrivate, const GroupItem &task, TaskContainer(TaskTreePrivate *taskTreePrivate, const GroupItem &task,
TaskNode *parentNode, TaskContainer *parentContainer) TaskNode *parentNode, TaskContainer *parentContainer);
: m_constData(taskTreePrivate, task, parentNode, parentContainer, this) {}
SetupResult start();
SetupResult continueStart(SetupResult startAction, int nextChild);
SetupResult startChildren(int nextChild);
SetupResult childDone(bool success);
void stop();
bool invokeDoneHandler(DoneWith doneWith);
bool isRunning() const { return m_runtimeData.has_value(); }
bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); }
struct ConstData { TaskTreePrivate *const m_taskTreePrivate = nullptr;
ConstData(TaskTreePrivate *taskTreePrivate, const GroupItem &task, TaskNode *parentNode, TaskNode *const m_parentNode = nullptr;
TaskContainer *parentContainer, TaskContainer *thisContainer); TaskContainer *const m_parentContainer = nullptr;
~ConstData() { qDeleteAll(m_children); }
TaskTreePrivate * const m_taskTreePrivate = nullptr;
TaskNode * const m_parentNode = nullptr;
TaskContainer * const m_parentContainer = nullptr;
const int m_parallelLimit = 1; const int m_parallelLimit = 1;
const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
const GroupItem::GroupHandler m_groupHandler; const GroupItem::GroupHandler m_groupHandler;
const QList<TreeStorageBase> m_storageList; const QList<TreeStorageBase> m_storageList;
const QList<TaskNode *> m_children; std::vector<TaskNode> m_children;
const int m_taskCount = 0; const int m_taskCount = 0;
};
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;
bool m_successBit = true;
bool m_callStorageDoneHandlersOnDestruction = false;
int m_doneCount = 0;
Guard m_startGuard;
};
const ConstData m_constData;
std::optional<RuntimeData> m_runtimeData;
}; };
class TaskNode class TaskNode
{ {
Q_DISABLE_COPY_MOVE(TaskNode) Q_DISABLE_COPY(TaskNode)
public: public:
TaskNode(TaskNode &&other) = default;
TaskNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task, TaskNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task,
TaskContainer *parentContainer) TaskContainer *parentContainer)
: m_taskHandler(task.m_taskHandler) : m_taskHandler(task.m_taskHandler)
, m_container(taskTreePrivate, task, this, parentContainer) , m_container(taskTreePrivate, task, this, parentContainer)
{} {}
// If returned value != Continue, childDone() needs to be called in parent container (in caller)
// in order to unwind properly.
SetupResult start();
void stop();
bool invokeDoneHandler(DoneWith doneWith);
bool isRunning() const { return m_task || m_container.isRunning(); }
bool isTask() const { return bool(m_taskHandler.m_createHandler); } bool isTask() const { return bool(m_taskHandler.m_createHandler); }
int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; } int taskCount() const { return isTask() ? 1 : m_container.m_taskCount; }
TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; } TaskContainer *parentContainer() const { return m_container.m_parentContainer; }
TaskTree *taskTree() const { return m_container.m_constData.m_taskTreePrivate->q; }
private:
const GroupItem::TaskHandler m_taskHandler; const GroupItem::TaskHandler m_taskHandler;
TaskContainer m_container; TaskContainer m_container;
std::unique_ptr<TaskInterface> m_task;
}; };
static bool initialSuccessBit(WorkflowPolicy workflowPolicy)
{
switch (workflowPolicy) {
case WorkflowPolicy::StopOnError:
case WorkflowPolicy::ContinueOnError:
case WorkflowPolicy::FinishAllAndSuccess:
return true;
case WorkflowPolicy::StopOnSuccess:
case WorkflowPolicy::ContinueOnSuccess:
case WorkflowPolicy::StopOnSuccessOrError:
case WorkflowPolicy::FinishAllAndError:
return false;
}
QT_CHECK(false);
return false;
}
class TaskRuntimeContainer
{
public:
TaskRuntimeContainer(const TaskContainer &taskContainer, TaskRuntimeNode *parentNode,
TaskRuntimeContainer *parentContainer)
: m_taskContainer(taskContainer)
, m_parentNode(parentNode)
, m_parentContainer(parentContainer)
, m_storageIdList(createStorages(taskContainer))
, m_successBit(initialSuccessBit(taskContainer.m_workflowPolicy))
{}
~TaskRuntimeContainer()
{
for (int i = m_taskContainer.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order
const TreeStorageBase storage = m_taskContainer.m_storageList[i];
const int storageId = m_storageIdList.value(i);
if (m_callStorageDoneHandlersOnDestruction)
m_taskContainer.m_taskTreePrivate->callDoneHandler(storage, storageId);
storage.m_storageData->deleteStorage(storageId);
}
qDeleteAll(m_children);
}
static QList<int> createStorages(const TaskContainer &container);
bool isStarting() const { return m_startGuard.isLocked(); }
bool updateSuccessBit(bool success);
int currentLimit() const;
const TaskContainer &m_taskContainer; // Not owning.
TaskRuntimeNode *m_parentNode = nullptr; // Not owning.
TaskRuntimeContainer *m_parentContainer = nullptr; // Not owning.
const QList<int> m_storageIdList;
QList<TaskRuntimeNode *> m_children; // Owning.
bool m_successBit = true;
bool m_callStorageDoneHandlersOnDestruction = false;
int m_doneCount = 0;
Guard m_startGuard;
};
class TaskRuntimeNode
{
public:
TaskRuntimeNode(const TaskNode &taskNode, TaskRuntimeContainer *parentContainer)
: m_taskNode(taskNode)
, m_parentContainer(parentContainer)
{}
const TaskNode &m_taskNode; // Not owning.
TaskRuntimeContainer *m_parentContainer = nullptr; // Not owning.
std::unique_ptr<TaskRuntimeContainer> m_container; // Owning.
std::unique_ptr<TaskInterface> m_task; // Owning.
};
void ExecutionContextActivator::activateContext(TaskRuntimeContainer *container)
{
const TaskContainer &taskContainer = container->m_taskContainer;
for (int i = 0; i < taskContainer.m_storageList.size(); ++i) {
const TreeStorageBase &storage = taskContainer.m_storageList[i];
auto &threadData = storage.m_storageData->threadData();
if (threadData.activeStorageId())
continue; // Storage shadowing: The storage is already active, skipping it...
m_activeStorages.append(storage);
threadData.activateStorage(container->m_storageIdList.value(i));
}
// Go to the parent after activating this storages so that storage shadowing works
// in the direction from child to parent root.
if (container->m_parentContainer)
activateContext(container->m_parentContainer);
}
void TaskTreePrivate::start() void TaskTreePrivate::start()
{ {
QT_ASSERT(m_root, return); QT_ASSERT(m_root, return);
QT_ASSERT(!m_runtimeRoot, return);
m_progressValue = 0; m_progressValue = 0;
emitStartedAndProgress(); emitStartedAndProgress();
// TODO: check storage handlers for not existing storages in tree // TODO: check storage handlers for not existing storages in tree
@@ -1194,15 +1289,16 @@ void TaskTreePrivate::start()
QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
"exist in task tree. Its handlers will never be called.")); "exist in task tree. Its handlers will never be called."));
} }
m_root->start(); m_runtimeRoot.reset(new TaskRuntimeNode(*m_root.get(), nullptr));
start(m_runtimeRoot.get());
} }
void TaskTreePrivate::stop() void TaskTreePrivate::stop()
{ {
QT_ASSERT(m_root, return); QT_ASSERT(m_root, return);
if (!m_root->isRunning()) if (!m_runtimeRoot)
return; return;
m_root->stop(); stop(m_runtimeRoot.get());
emitDone(DoneWith::Cancel); emitDone(DoneWith::Cancel);
} }
@@ -1236,57 +1332,18 @@ void TaskTreePrivate::emitDone(DoneWith result)
emit q->done(result); emit q->done(result);
} }
class ExecutionContextActivator static std::vector<TaskNode> createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container,
const QList<GroupItem> &children)
{ {
public: std::vector<TaskNode> result;
ExecutionContextActivator(TaskContainer *container) { activateContext(container); } result.reserve(children.size());
~ExecutionContextActivator() {
for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order
m_activeStorages[i].m_storageData->threadData().activateStorage(0);
}
private:
void activateContext(TaskContainer *container)
{
QT_ASSERT(container && container->isRunning(), return);
const TaskContainer::ConstData &constData = container->m_constData;
for (int i = 0; i < constData.m_storageList.size(); ++i) {
const TreeStorageBase &storage = constData.m_storageList[i];
auto &threadData = storage.m_storageData->threadData();
if (threadData.activeStorageId())
continue; // Storage shadowing: The storage is already active, skipping it...
m_activeStorages.append(storage);
threadData.activateStorage(container->m_runtimeData->m_storageIdList.value(i));
}
// Go to the parent after activating this storages so that storage shadowing works
// in the direction from child to parent root.
if (constData.m_parentContainer)
activateContext(constData.m_parentContainer);
}
QList<TreeStorageBase> m_activeStorages;
};
template <typename Handler, typename ...Args,
typename ReturnType = typename std::invoke_result_t<Handler, Args...>>
ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args)
{
ExecutionContextActivator activator(container);
GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard);
return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
}
static QList<TaskNode *> createChildren(TaskTreePrivate *taskTreePrivate, TaskContainer *container,
const QList<GroupItem> &children)
{
QList<TaskNode *> result;
for (const GroupItem &child : children) for (const GroupItem &child : children)
result.append(new TaskNode(taskTreePrivate, child, container)); result.emplace_back(taskTreePrivate, child, container);
return result; return result;
} }
TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const GroupItem &task, TaskContainer::TaskContainer(TaskTreePrivate *taskTreePrivate, const GroupItem &task,
TaskNode *parentNode, TaskContainer *parentContainer, TaskNode *parentNode, TaskContainer *parentContainer)
TaskContainer *thisContainer)
: m_taskTreePrivate(taskTreePrivate) : m_taskTreePrivate(taskTreePrivate)
, m_parentNode(parentNode) , m_parentNode(parentNode)
, m_parentContainer(parentContainer) , m_parentContainer(parentContainer)
@@ -1294,184 +1351,157 @@ TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const Grou
, m_workflowPolicy(task.m_groupData.m_workflowPolicy.value_or(WorkflowPolicy::StopOnError)) , m_workflowPolicy(task.m_groupData.m_workflowPolicy.value_or(WorkflowPolicy::StopOnError))
, m_groupHandler(task.m_groupData.m_groupHandler) , m_groupHandler(task.m_groupData.m_groupHandler)
, m_storageList(task.m_storageList) , m_storageList(task.m_storageList)
, m_children(createChildren(taskTreePrivate, thisContainer, task.m_children)) , m_children(createChildren(taskTreePrivate, this, task.m_children))
, m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0, , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0,
[](int r, TaskNode *n) { return r + n->taskCount(); })) [](int r, const TaskNode &n) { return r + n.taskCount(); }))
{ {
for (const TreeStorageBase &storage : m_storageList) for (const TreeStorageBase &storage : m_storageList)
m_taskTreePrivate->m_storages << storage; m_taskTreePrivate->m_storages << storage;
} }
QList<int> TaskContainer::RuntimeData::createStorages(const TaskContainer::ConstData &constData) QList<int> TaskRuntimeContainer::createStorages(const TaskContainer &container)
{ {
QList<int> storageIdList; QList<int> storageIdList;
for (const TreeStorageBase &storage : constData.m_storageList) { for (const TreeStorageBase &storage : container.m_storageList) {
const int storageId = storage.m_storageData->threadData().createStorage(); const int storageId = storage.m_storageData->threadData().createStorage();
storageIdList.append(storageId); storageIdList.append(storageId);
constData.m_taskTreePrivate->callSetupHandler(storage, storageId); container.m_taskTreePrivate->callSetupHandler(storage, storageId);
} }
return storageIdList; return storageIdList;
} }
static bool initialSuccessBit(WorkflowPolicy workflowPolicy) bool TaskRuntimeContainer::updateSuccessBit(bool success)
{ {
switch (workflowPolicy) { if (m_taskContainer.m_workflowPolicy == WorkflowPolicy::FinishAllAndSuccess
case WorkflowPolicy::StopOnError: || m_taskContainer.m_workflowPolicy == WorkflowPolicy::FinishAllAndError
case WorkflowPolicy::ContinueOnError: || m_taskContainer.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError) {
case WorkflowPolicy::FinishAllAndSuccess: if (m_taskContainer.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError)
return true;
case WorkflowPolicy::StopOnSuccess:
case WorkflowPolicy::ContinueOnSuccess:
case WorkflowPolicy::StopOnSuccessOrError:
case WorkflowPolicy::FinishAllAndError:
return false;
}
QT_CHECK(false);
return false;
}
TaskContainer::RuntimeData::RuntimeData(const ConstData &constData)
: m_constData(constData)
, m_storageIdList(createStorages(constData))
, m_successBit(initialSuccessBit(m_constData.m_workflowPolicy))
{}
TaskContainer::RuntimeData::~RuntimeData()
{
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);
if (m_callStorageDoneHandlersOnDestruction)
m_constData.m_taskTreePrivate->callDoneHandler(storage, storageId);
storage.m_storageData->deleteStorage(storageId);
}
}
bool TaskContainer::RuntimeData::updateSuccessBit(bool success)
{
if (m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndSuccess
|| m_constData.m_workflowPolicy == WorkflowPolicy::FinishAllAndError
|| m_constData.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError) {
if (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError)
m_successBit = success; m_successBit = success;
return m_successBit; return m_successBit;
} }
const bool donePolicy = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnSuccess const bool donePolicy = m_taskContainer.m_workflowPolicy == WorkflowPolicy::StopOnSuccess
|| m_constData.m_workflowPolicy == WorkflowPolicy::ContinueOnSuccess; || m_taskContainer.m_workflowPolicy == WorkflowPolicy::ContinueOnSuccess;
m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success); m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success);
return m_successBit; return m_successBit;
} }
int TaskContainer::RuntimeData::currentLimit() const int TaskRuntimeContainer::currentLimit() const
{ {
const int childCount = m_constData.m_children.size(); // TODO: Handle children well
return m_constData.m_parallelLimit const int childCount = m_taskContainer.m_children.size();
? qMin(m_doneCount + m_constData.m_parallelLimit, childCount) : childCount; return m_taskContainer.m_parallelLimit
? qMin(m_doneCount + m_taskContainer.m_parallelLimit, childCount) : childCount;
} }
SetupResult TaskContainer::start() SetupResult TaskTreePrivate::start(TaskRuntimeContainer *container)
{ {
QT_CHECK(!isRunning());
m_runtimeData.emplace(m_constData);
SetupResult startAction = SetupResult::Continue; SetupResult startAction = SetupResult::Continue;
if (m_constData.m_groupHandler.m_setupHandler) { if (container->m_taskContainer.m_groupHandler.m_setupHandler) {
startAction = invokeHandler(this, m_constData.m_groupHandler.m_setupHandler); startAction = invokeHandler(container, container->m_taskContainer.m_groupHandler.m_setupHandler);
if (startAction != SetupResult::Continue) { if (startAction != SetupResult::Continue) {
m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount); // TODO: Handle progress well.
advanceProgress(container->m_taskContainer.m_taskCount);
// Non-Continue SetupResult takes precedence over the workflow policy. // Non-Continue SetupResult takes precedence over the workflow policy.
m_runtimeData->m_successBit = startAction == SetupResult::StopWithSuccess; container->m_successBit = startAction == SetupResult::StopWithSuccess;
} }
} }
if (startAction == SetupResult::Continue) { if (startAction == SetupResult::Continue) {
if (m_constData.m_children.isEmpty()) if (container->m_taskContainer.m_children.empty())
startAction = toSetupResult(m_runtimeData->m_successBit); startAction = toSetupResult(container->m_successBit);
} else { // TODO: Check if repeater exists, call its handler.
} }
return continueStart(startAction, 0); return continueStart(container, startAction, 0);
} }
SetupResult TaskContainer::continueStart(SetupResult startAction, int nextChild) SetupResult TaskTreePrivate::continueStart(TaskRuntimeContainer *container, SetupResult startAction, int nextChild)
{ {
const SetupResult groupAction = startAction == SetupResult::Continue ? startChildren(nextChild) const SetupResult groupAction = startAction == SetupResult::Continue ? startChildren(container, nextChild)
: startAction; : startAction;
QT_CHECK(isRunning()); // TODO: superfluous
if (groupAction != SetupResult::Continue) { if (groupAction != SetupResult::Continue) {
const bool bit = m_runtimeData->updateSuccessBit(groupAction == SetupResult::StopWithSuccess); const bool bit = container->updateSuccessBit(groupAction == SetupResult::StopWithSuccess);
const bool result = invokeDoneHandler(bit ? DoneWith::Success : DoneWith::Error); TaskRuntimeContainer *parentContainer = container->m_parentContainer;
if (TaskContainer *parentContainer = m_constData.m_parentContainer) { const bool result = invokeDoneHandler(container, bit ? DoneWith::Success : DoneWith::Error);
QT_CHECK(parentContainer->isRunning()); if (parentContainer) {
if (!parentContainer->isStarting()) if (!parentContainer->isStarting())
parentContainer->childDone(result); childDone(parentContainer, result);
} else { } else {
m_constData.m_taskTreePrivate->emitDone(result ? DoneWith::Success : DoneWith::Error); m_runtimeRoot.reset();
emitDone(result ? DoneWith::Success : DoneWith::Error);
} }
} }
return groupAction; return groupAction;
} }
SetupResult TaskContainer::startChildren(int nextChild) SetupResult TaskTreePrivate::startChildren(TaskRuntimeContainer *container, int nextChild)
{ {
QT_CHECK(isRunning()); GuardLocker locker(container->m_startGuard);
GuardLocker locker(m_runtimeData->m_startGuard); for (int i = nextChild; i < int(container->m_taskContainer.m_children.size()); ++i) {
for (int i = nextChild; i < m_constData.m_children.size(); ++i) { const int limit = container->currentLimit();
const int limit = m_runtimeData->currentLimit();
if (i >= limit) if (i >= limit)
break; break;
const SetupResult startAction = m_constData.m_children.at(i)->start(); TaskRuntimeNode *newTask = new TaskRuntimeNode(container->m_taskContainer.m_children.at(i), container);
container->m_children.append(newTask);
const SetupResult startAction = start(newTask);
if (startAction == SetupResult::Continue) if (startAction == SetupResult::Continue)
continue; continue;
const SetupResult finalizeAction = childDone(startAction == SetupResult::StopWithSuccess); const SetupResult finalizeAction = childDone(container, startAction == SetupResult::StopWithSuccess);
if (finalizeAction == SetupResult::Continue) if (finalizeAction == SetupResult::Continue)
continue; continue;
int skippedTaskCount = 0; int skippedTaskCount = 0;
// Skip scheduled but not run yet. The current (i) was already notified. // Skip scheduled but not run yet. The current (i) was already notified.
for (int j = i + 1; j < limit; ++j) for (int j = i + 1; j < limit; ++j)
skippedTaskCount += m_constData.m_children.at(j)->taskCount(); skippedTaskCount += container->m_taskContainer.m_children.at(j).taskCount();
m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); // TODO: Handle progress well
advanceProgress(skippedTaskCount);
return finalizeAction; return finalizeAction;
} }
return SetupResult::Continue; return SetupResult::Continue;
} }
SetupResult TaskContainer::childDone(bool success) SetupResult TaskTreePrivate::childDone(TaskRuntimeContainer *container, bool success)
{ {
QT_CHECK(isRunning()); const int limit = container->currentLimit(); // Read before bumping m_doneCount and stop()
const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop() const WorkflowPolicy &workflowPolicy = container->m_taskContainer.m_workflowPolicy;
const bool shouldStop = m_constData.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError const bool shouldStop = workflowPolicy == WorkflowPolicy::StopOnSuccessOrError
|| (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnSuccess && success) || (workflowPolicy == WorkflowPolicy::StopOnSuccess && success)
|| (m_constData.m_workflowPolicy == WorkflowPolicy::StopOnError && !success); || (workflowPolicy == WorkflowPolicy::StopOnError && !success);
if (shouldStop) if (shouldStop)
stop(); stop(container);
++m_runtimeData->m_doneCount; ++container->m_doneCount;
const bool updatedSuccess = m_runtimeData->updateSuccessBit(success); const bool updatedSuccess = container->updateSuccessBit(success);
const SetupResult startAction const SetupResult startAction
= (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size()) = (shouldStop || container->m_doneCount == int(container->m_taskContainer.m_children.size()))
? toSetupResult(updatedSuccess) : SetupResult::Continue; ? toSetupResult(updatedSuccess) : SetupResult::Continue;
if (isStarting()) if (container->isStarting())
return startAction; return startAction;
return continueStart(startAction, limit); return continueStart(container, startAction, limit);
} }
void TaskContainer::stop() void TaskTreePrivate::stop(TaskRuntimeContainer *container)
{ {
if (!isRunning()) const int limit = container->currentLimit();
return; for (int i = 0; i < limit; ++i) {
if (i == container->m_children.size())
const int limit = m_runtimeData->currentLimit(); break;
for (int i = 0; i < limit; ++i) TaskRuntimeNode *child = container->m_children.at(i);
m_constData.m_children.at(i)->stop(); if (child)
stop(child);
}
int skippedTaskCount = 0; int skippedTaskCount = 0;
for (int i = limit; i < m_constData.m_children.size(); ++i) for (int i = limit; i < int(container->m_taskContainer.m_children.size()); ++i)
skippedTaskCount += m_constData.m_children.at(i)->taskCount(); skippedTaskCount += container->m_taskContainer.m_children.at(i).taskCount();
m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); // TODO: Handle progress well
advanceProgress(skippedTaskCount);
} }
static bool shouldCall(CallDoneIf callDoneIf, DoneWith result) static bool shouldCall(CallDoneIf callDoneIf, DoneWith result)
@@ -1481,73 +1511,78 @@ static bool shouldCall(CallDoneIf callDoneIf, DoneWith result)
return callDoneIf != CallDoneIf::Success; return callDoneIf != CallDoneIf::Success;
} }
bool TaskContainer::invokeDoneHandler(DoneWith doneWith) bool TaskTreePrivate::invokeDoneHandler(TaskRuntimeContainer *container, DoneWith doneWith)
{ {
DoneResult result = toDoneResult(doneWith); DoneResult result = toDoneResult(doneWith);
const GroupItem::GroupHandler &groupHandler = m_constData.m_groupHandler; const GroupItem::GroupHandler &groupHandler = container->m_taskContainer.m_groupHandler;
if (groupHandler.m_doneHandler && shouldCall(groupHandler.m_callDoneIf, doneWith)) if (groupHandler.m_doneHandler && shouldCall(groupHandler.m_callDoneIf, doneWith))
result = invokeHandler(this, groupHandler.m_doneHandler, doneWith); result = invokeHandler(container, groupHandler.m_doneHandler, doneWith);
m_runtimeData->m_callStorageDoneHandlersOnDestruction = true; container->m_callStorageDoneHandlersOnDestruction = true;
m_runtimeData.reset(); container->m_parentNode->m_container.reset();
return result == DoneResult::Success; return result == DoneResult::Success;
} }
SetupResult TaskNode::start() SetupResult TaskTreePrivate::start(TaskRuntimeNode *node)
{ {
QT_CHECK(!isRunning()); if (!node->m_taskNode.isTask()) {
if (!isTask()) node->m_container.reset(new TaskRuntimeContainer(node->m_taskNode.m_container, node,
return m_container.start(); node->m_parentContainer));
return start(node->m_container.get());
}
m_task.reset(m_taskHandler.m_createHandler()); const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
const SetupResult startAction = m_taskHandler.m_setupHandler node->m_task.reset(handler.m_createHandler());
? invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, *m_task.get()) const SetupResult startAction = handler.m_setupHandler
? invokeHandler(node->m_parentContainer, handler.m_setupHandler, *node->m_task.get())
: SetupResult::Continue; : SetupResult::Continue;
if (startAction != SetupResult::Continue) { if (startAction != SetupResult::Continue) {
m_container.m_constData.m_taskTreePrivate->advanceProgress(1); // TODO: Handle progress well
m_task.reset(); advanceProgress(1);
node->m_task.reset();
return startAction; return startAction;
} }
const std::shared_ptr<SetupResult> unwindAction const std::shared_ptr<SetupResult> unwindAction
= std::make_shared<SetupResult>(SetupResult::Continue); = std::make_shared<SetupResult>(SetupResult::Continue);
QObject::connect(m_task.get(), &TaskInterface::done, taskTree(), [=](bool success) { QObject::connect(node->m_task.get(), &TaskInterface::done,
const bool result = invokeDoneHandler(success ? DoneWith::Success : DoneWith::Error); q, [this, node, unwindAction](bool success) {
QObject::disconnect(m_task.get(), &TaskInterface::done, taskTree(), nullptr); const bool result = invokeDoneHandler(node, success ? DoneWith::Success : DoneWith::Error);
m_task.release()->deleteLater(); QObject::disconnect(node->m_task.get(), &TaskInterface::done, q, nullptr);
QT_ASSERT(parentContainer() && parentContainer()->isRunning(), return); node->m_task.release()->deleteLater(); // TODO: delete later this???
if (parentContainer()->isStarting()) if (node->m_parentContainer->isStarting())
*unwindAction = toSetupResult(result); *unwindAction = toSetupResult(result);
else else
parentContainer()->childDone(result); childDone(node->m_parentContainer, result);
}); });
m_task->start(); node->m_task->start();
return *unwindAction; return *unwindAction;
} }
void TaskNode::stop() void TaskTreePrivate::stop(TaskRuntimeNode *node)
{ {
if (!isRunning()) if (!node->m_task) {
return; if (!node->m_container)
return;
if (!m_task) { stop(node->m_container.get());
m_container.stop(); node->m_container->updateSuccessBit(false);
m_container.m_runtimeData->updateSuccessBit(false); invokeDoneHandler(node->m_container.get(), DoneWith::Cancel);
m_container.invokeDoneHandler(DoneWith::Cancel);
return; return;
} }
// TODO: cancelHandler? invokeDoneHandler(node, DoneWith::Cancel);
// TODO: call TaskInterface::stop() ? node->m_task.reset();
invokeDoneHandler(DoneWith::Cancel);
m_task.reset();
} }
bool TaskNode::invokeDoneHandler(DoneWith doneWith) bool TaskTreePrivate::invokeDoneHandler(TaskRuntimeNode *node, DoneWith doneWith)
{ {
DoneResult result = toDoneResult(doneWith); DoneResult result = toDoneResult(doneWith);
if (m_taskHandler.m_doneHandler && shouldCall(m_taskHandler.m_callDoneIf, doneWith)) const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
result = invokeHandler(parentContainer(), m_taskHandler.m_doneHandler, *m_task.get(), doneWith); if (handler.m_doneHandler && shouldCall(handler.m_callDoneIf, doneWith)) {
m_container.m_constData.m_taskTreePrivate->advanceProgress(1); result = invokeHandler(node->m_parentContainer,
handler.m_doneHandler, *node->m_task.get(), doneWith);
}
// TODO: Handle progress well
advanceProgress(1);
return result == DoneResult::Success; return result == DoneResult::Success;
} }
@@ -2359,7 +2394,7 @@ void TaskTree::stop()
*/ */
bool TaskTree::isRunning() const bool TaskTree::isRunning() const
{ {
return d->m_root && d->m_root->isRunning(); return bool(d->m_runtimeRoot);
} }
/*! /*!
@@ -2586,8 +2621,8 @@ int TaskTree::progressValue() const
*/ */
void TaskTree::setupStorageHandler(const TreeStorageBase &storage, void TaskTree::setupStorageHandler(const TreeStorageBase &storage,
StorageVoidHandler setupHandler, TreeStorageBase::StorageVoidHandler setupHandler,
StorageVoidHandler doneHandler) TreeStorageBase::StorageVoidHandler doneHandler)
{ {
auto it = d->m_storageHandlers.find(storage); auto it = d->m_storageHandlers.find(storage);
if (it == d->m_storageHandlers.end()) { if (it == d->m_storageHandlers.end()) {

View File

@@ -19,9 +19,7 @@ namespace Tasking {
Q_NAMESPACE_EXPORT(TASKING_EXPORT) Q_NAMESPACE_EXPORT(TASKING_EXPORT)
class ExecutionContextActivator;
class StorageData; class StorageData;
class TaskContainer;
class TaskTreePrivate; class TaskTreePrivate;
class TASKING_EXPORT TaskInterface : public QObject class TASKING_EXPORT TaskInterface : public QObject
@@ -33,7 +31,7 @@ signals:
private: private:
template <typename Task, typename Deleter> friend class TaskAdapter; template <typename Task, typename Deleter> friend class TaskAdapter;
friend class TaskNode; friend class TaskTreePrivate;
TaskInterface() = default; TaskInterface() = default;
#ifdef Q_QDOC #ifdef Q_QDOC
protected: protected:
@@ -46,6 +44,7 @@ class TASKING_EXPORT TreeStorageBase
public: public:
using StorageConstructor = std::function<void *(void)>; using StorageConstructor = std::function<void *(void)>;
using StorageDestructor = std::function<void(void *)>; using StorageDestructor = std::function<void(void *)>;
using StorageVoidHandler = std::function<void(void *)>;
bool isValid() const; bool isValid() const;
@@ -66,9 +65,9 @@ private:
QSharedPointer<StorageData> m_storageData; QSharedPointer<StorageData> m_storageData;
template <typename StorageStruct> friend class TreeStorage; template <typename StorageStruct> friend class TreeStorage;
friend ExecutionContextActivator; friend class ExecutionContextActivator;
friend TaskContainer; friend class TaskRuntimeContainer;
friend TaskTreePrivate; friend class TaskTreePrivate;
}; };
template <typename StorageStruct> template <typename StorageStruct>
@@ -461,8 +460,6 @@ private:
}; };
}; };
class TaskTreePrivate;
class TASKING_EXPORT TaskTree final : public QObject class TASKING_EXPORT TaskTree final : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -515,19 +512,17 @@ signals:
void progressValueChanged(int value); // updated whenever task finished / skipped / stopped void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
private: private:
using StorageVoidHandler = std::function<void(void *)>;
void setupStorageHandler(const TreeStorageBase &storage, void setupStorageHandler(const TreeStorageBase &storage,
StorageVoidHandler setupHandler, TreeStorageBase::StorageVoidHandler setupHandler,
StorageVoidHandler doneHandler); TreeStorageBase::StorageVoidHandler doneHandler);
template <typename StorageStruct, typename StorageHandler> template <typename StorageStruct, typename StorageHandler>
StorageVoidHandler wrapHandler(StorageHandler &&handler) { TreeStorageBase::StorageVoidHandler wrapHandler(StorageHandler &&handler) {
return [=](void *voidStruct) { return [=](void *voidStruct) {
auto *storageStruct = static_cast<StorageStruct *>(voidStruct); auto *storageStruct = static_cast<StorageStruct *>(voidStruct);
std::invoke(handler, *storageStruct); std::invoke(handler, *storageStruct);
}; };
} }
friend class TaskTreePrivate;
TaskTreePrivate *d; TaskTreePrivate *d;
}; };