TaskTree: Add DynamicSetup handler for groups

DynamicSetup enables finishing group (with Done or Error)
immediately before the group was even started.
The handler is invoked dynamically, just before starting
the group. The handler may also allow for running only
selected children and omitting others, so it may serve as
dynamic condition or dynamic multiplexer. This can be achieved
by modifying QSet<int> childrenToRun field inside returned
GroupConfig and setting the policy to ContinueSelected.

Change-Id: I15bddbb866a89ae9f783c59cc4bca89dd6a81c75
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2022-11-04 14:08:32 +01:00
parent 9e8d208cad
commit 234b3d1e6f
3 changed files with 151 additions and 37 deletions

View File

@@ -45,21 +45,26 @@ void TaskItem::addChildren(const QList<TaskItem> &children)
case Type::GroupHandler:
QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a "
"child of Group, skipping..."); break);
QTC_ASSERT(!child.m_groupHandler.m_simpleSetupHandler
|| !m_groupHandler.m_simpleSetupHandler,
QTC_ASSERT(!child.m_groupHandler.m_setupHandler
|| !m_groupHandler.m_setupHandler,
qWarning("Group Setup Handler redefinition, overriding..."));
QTC_ASSERT(!child.m_groupHandler.m_simpleDoneHandler
|| !m_groupHandler.m_simpleDoneHandler,
QTC_ASSERT(!child.m_groupHandler.m_doneHandler
|| !m_groupHandler.m_doneHandler,
qWarning("Group Done Handler redefinition, overriding..."));
QTC_ASSERT(!child.m_groupHandler.m_simpleErrorHandler
|| !m_groupHandler.m_simpleErrorHandler,
QTC_ASSERT(!child.m_groupHandler.m_errorHandler
|| !m_groupHandler.m_errorHandler,
qWarning("Group Error Handler redefinition, overriding..."));
if (child.m_groupHandler.m_simpleSetupHandler)
m_groupHandler.m_simpleSetupHandler = child.m_groupHandler.m_simpleSetupHandler;
if (child.m_groupHandler.m_simpleDoneHandler)
m_groupHandler.m_simpleDoneHandler = child.m_groupHandler.m_simpleDoneHandler;
if (child.m_groupHandler.m_simpleErrorHandler)
m_groupHandler.m_simpleErrorHandler = child.m_groupHandler.m_simpleErrorHandler;
QTC_ASSERT(!child.m_groupHandler.m_dynamicSetupHandler
|| !m_groupHandler.m_dynamicSetupHandler,
qWarning("Dynamic Setup Handler redefinition, overriding..."));
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;
if (child.m_groupHandler.m_dynamicSetupHandler)
m_groupHandler.m_dynamicSetupHandler = child.m_groupHandler.m_dynamicSetupHandler;
break;
}
}
@@ -79,10 +84,11 @@ public:
const TaskItem &task);
~TaskContainer();
void start();
void selectChildren();
void stop();
bool isRunning() const;
void childDone(bool success);
void invokeSubTreeHandler(bool success);
void invokeEndHandler(bool success);
void resetSuccessBit();
void updateSuccessBit(bool success);
@@ -91,7 +97,9 @@ public:
const TaskItem::ExecuteMode m_executeMode = TaskItem::ExecuteMode::Parallel;
TaskItem::WorkflowPolicy m_workflowPolicy = TaskItem::WorkflowPolicy::StopOnError;
const TaskItem::GroupHandler m_groupHandler;
GroupConfig m_groupConfig;
QList<TaskNode *> m_children;
QList<TaskNode *> m_selectedChildren;
int m_currentIndex = -1;
bool m_successBit = true;
};
@@ -157,13 +165,29 @@ TaskContainer::~TaskContainer()
void TaskContainer::start()
{
if (m_groupHandler.m_simpleSetupHandler) {
m_groupConfig = {};
m_selectedChildren.clear();
if (m_groupHandler.m_setupHandler) {
GuardLocker locker(m_taskTreePrivate->m_guard);
m_groupHandler.m_simpleSetupHandler();
m_groupHandler.m_setupHandler();
}
if (m_children.isEmpty()) {
invokeSubTreeHandler(true);
if (m_groupHandler.m_dynamicSetupHandler) {
GuardLocker locker(m_taskTreePrivate->m_guard);
m_groupConfig = m_groupHandler.m_dynamicSetupHandler();
}
if (m_groupConfig.action == GroupAction::StopWithDone || m_groupConfig.action == GroupAction::StopWithError) {
const bool success = m_groupConfig.action == GroupAction::StopWithDone;
invokeEndHandler(success);
return;
}
selectChildren();
if (m_selectedChildren.isEmpty()) {
invokeEndHandler(true);
return;
}
@@ -171,21 +195,34 @@ void TaskContainer::start()
resetSuccessBit();
if (m_executeMode == TaskItem::ExecuteMode::Sequential) {
m_children.at(m_currentIndex)->start();
m_selectedChildren.at(m_currentIndex)->start();
return;
}
// Parallel case
for (TaskNode *child : std::as_const(m_children)) {
for (TaskNode *child : std::as_const(m_selectedChildren)) {
if (!child->start())
return;
}
}
void TaskContainer::selectChildren()
{
if (m_groupConfig.action != GroupAction::ContinueSelected) {
m_selectedChildren = m_children;
return;
}
m_selectedChildren.clear();
for (int i = 0; i < m_children.size(); ++i) {
if (m_groupConfig.childrenToRun.contains(i))
m_selectedChildren.append(m_children.at(i));
}
}
void TaskContainer::stop()
{
m_currentIndex = -1;
for (TaskNode *child : std::as_const(m_children))
for (TaskNode *child : std::as_const(m_selectedChildren))
child->stop();
}
@@ -199,32 +236,32 @@ void TaskContainer::childDone(bool success)
if ((m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnDone && success)
|| (m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnError && !success)) {
stop();
invokeSubTreeHandler(success);
invokeEndHandler(success);
return;
}
++m_currentIndex;
updateSuccessBit(success);
if (m_currentIndex == m_children.size()) {
invokeSubTreeHandler(m_successBit);
if (m_currentIndex == m_selectedChildren.size()) {
invokeEndHandler(m_successBit);
return;
}
if (m_executeMode == TaskItem::ExecuteMode::Sequential)
m_children.at(m_currentIndex)->start();
m_selectedChildren.at(m_currentIndex)->start();
}
void TaskContainer::invokeSubTreeHandler(bool success)
void TaskContainer::invokeEndHandler(bool success)
{
m_currentIndex = -1;
m_successBit = success;
if (success && m_groupHandler.m_simpleDoneHandler) {
if (success && m_groupHandler.m_doneHandler) {
GuardLocker locker(m_taskTreePrivate->m_guard);
m_groupHandler.m_simpleDoneHandler();
} else if (!success && m_groupHandler.m_simpleErrorHandler) {
m_groupHandler.m_doneHandler();
} else if (!success && m_groupHandler.m_errorHandler) {
GuardLocker locker(m_taskTreePrivate->m_guard);
m_groupHandler.m_simpleErrorHandler();
m_groupHandler.m_errorHandler();
}
if (m_parentContainer) {
m_parentContainer->childDone(success);
@@ -238,7 +275,7 @@ void TaskContainer::invokeSubTreeHandler(bool success)
void TaskContainer::resetSuccessBit()
{
if (m_children.isEmpty())
if (m_selectedChildren.isEmpty())
m_successBit = true;
if (m_workflowPolicy == TaskItem::WorkflowPolicy::StopOnDone
@@ -261,7 +298,6 @@ void TaskContainer::updateSuccessBit(bool success)
}
}
bool TaskNode::start()
{
if (!m_taskHandler.m_createHandler || !m_taskHandler.m_setupHandler) {

View File

@@ -6,6 +6,7 @@
#include "utils_global.h"
#include <QObject>
#include <QSet>
namespace Utils {
namespace Tasking {
@@ -22,6 +23,21 @@ signals:
void done(bool success);
};
enum class GroupAction
{
ContinueAll,
ContinueSelected,
StopWithDone,
StopWithError
};
class GroupConfig
{
public:
GroupAction action = GroupAction::ContinueAll;
QSet<int> childrenToRun = {};
};
class QTCREATOR_UTILS_EXPORT TaskItem
{
public:
@@ -31,8 +47,12 @@ public:
using TaskSetupHandler = std::function<void(TaskInterface &)>;
// Called on task done / error
using TaskEndHandler = std::function<void(const TaskInterface &)>;
// Called when sub tree entered / after sub three ended with success or failure
// Called when group entered / after group ended with success or failure
using GroupSimpleHandler = std::function<void()>;
// Called when group entered
using GroupSetupHandler = std::function<GroupConfig()>;
// Called after group ended with success or failure, passed set of successful children
// using GroupEndHandler = std::function<void(const QSet<int> &)>;
struct TaskHandler {
TaskCreateHandler m_createHandler;
@@ -42,9 +62,10 @@ public:
};
struct GroupHandler {
GroupSimpleHandler m_simpleSetupHandler;
GroupSimpleHandler m_simpleDoneHandler;
GroupSimpleHandler m_simpleErrorHandler;
GroupSimpleHandler m_setupHandler;
GroupSimpleHandler m_doneHandler = {};
GroupSimpleHandler m_errorHandler = {};
GroupSetupHandler m_dynamicSetupHandler = {};
};
enum class ExecuteMode {
@@ -136,13 +157,13 @@ public:
class QTCREATOR_UTILS_EXPORT OnGroupSetup : public TaskItem
{
public:
OnGroupSetup(const GroupSimpleHandler &handler) : TaskItem({{handler}, {}, {}}) {}
OnGroupSetup(const GroupSimpleHandler &handler) : TaskItem({handler}) {}
};
class QTCREATOR_UTILS_EXPORT OnGroupDone : public TaskItem
{
public:
OnGroupDone(const GroupSimpleHandler &handler) : TaskItem({{}, handler, {}}) {}
OnGroupDone(const GroupSimpleHandler &handler) : TaskItem({{}, handler}) {}
};
class QTCREATOR_UTILS_EXPORT OnGroupError : public TaskItem
@@ -151,6 +172,13 @@ public:
OnGroupError(const GroupSimpleHandler &handler) : TaskItem({{}, {}, handler}) {}
};
class QTCREATOR_UTILS_EXPORT DynamicSetup : public TaskItem
{
public:
DynamicSetup(const GroupSetupHandler &handler) : TaskItem({{}, {}, {}, handler}) {}
};
QTCREATOR_UTILS_EXPORT extern ExecuteInSequence sequential;
QTCREATOR_UTILS_EXPORT extern ExecuteInParallel parallel;
QTCREATOR_UTILS_EXPORT extern WorkflowPolicy stopOnError;

View File

@@ -373,6 +373,52 @@ void tst_TaskTree::processTree_data()
{2, Handler::Error},
{-1, Handler::GroupDone}};
const auto stopWithDoneSetup = [] { return GroupConfig{GroupAction::StopWithDone}; };
const auto stopWithErrorSetup = [] { return GroupConfig{GroupAction::StopWithError}; };
const auto continueAllSetup = [] { return GroupConfig{GroupAction::ContinueAll}; };
const auto continueSelSetup = [] { return GroupConfig{GroupAction::ContinueSelected, {0, 2}}; };
const auto constructDynamicSetup = [=](const DynamicSetup &dynamicSetup) {
return Group {
Group {
Process(std::bind(setupProcess, _1, 1), readResult)
},
Group {
dynamicSetup,
Process(std::bind(setupProcess, _1, 2), readResult),
Process(std::bind(setupProcess, _1, 3), readResult),
Process(std::bind(setupProcess, _1, 4), readResult)
},
OnGroupDone(rootDone),
OnGroupError(rootError)
};
};
const Group dynamicSetupDoneRoot = constructDynamicSetup({stopWithDoneSetup});
const Log dynamicSetupDoneLog{{1, Handler::Setup},
{1, Handler::Done},
{-1, Handler::GroupDone}};
const Group dynamicSetupErrorRoot = constructDynamicSetup({stopWithErrorSetup});
const Log dynamicSetupErrorLog{{1, Handler::Setup},
{1, Handler::Done},
{-1, Handler::GroupError}};
const Group dynamicSetupAllRoot = constructDynamicSetup({continueAllSetup});
const Log dynamicSetupAllLog{{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{3, Handler::Setup},
{3, Handler::Done},
{4, Handler::Setup},
{4, Handler::Done},
{-1, Handler::GroupDone}};
const Group dynamicSetupSelRoot = constructDynamicSetup({continueSelSetup});
const Log dynamicSetupSelLog{{1, Handler::Setup},
{1, Handler::Done},
{2, Handler::Setup},
{2, Handler::Done},
{4, Handler::Setup},
{4, Handler::Done},
{-1, Handler::GroupDone}};
QTest::newRow("Empty") << emptyRoot << emptyLog << false << true;
QTest::newRow("Nested") << nestedRoot << nestedLog << true << true;
QTest::newRow("Parallel") << parallelRoot << parallelLog << true << true;
@@ -385,6 +431,10 @@ void tst_TaskTree::processTree_data()
QTest::newRow("StopOnDone") << stopOnDoneRoot << stopOnDoneLog << true << true;
QTest::newRow("ContinueOnDone") << continueOnDoneRoot << continueOnDoneLog << true << true;
QTest::newRow("Optional") << optionalRoot << optionalLog << true << true;
QTest::newRow("DynamicSetupDone") << dynamicSetupDoneRoot << dynamicSetupDoneLog << true << true;
QTest::newRow("DynamicSetupError") << dynamicSetupErrorRoot << dynamicSetupErrorLog << true << false;
QTest::newRow("DynamicSetupAll") << dynamicSetupAllRoot << dynamicSetupAllLog << true << true;
QTest::newRow("DynamicSetupSelected") << dynamicSetupSelRoot << dynamicSetupSelLog << true << true;
}
void tst_TaskTree::processTree()