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

View File

@@ -6,6 +6,7 @@
#include "utils_global.h" #include "utils_global.h"
#include <QObject> #include <QObject>
#include <QSet>
namespace Utils { namespace Utils {
namespace Tasking { namespace Tasking {
@@ -22,6 +23,21 @@ signals:
void done(bool success); 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 class QTCREATOR_UTILS_EXPORT TaskItem
{ {
public: public:
@@ -31,8 +47,12 @@ public:
using TaskSetupHandler = std::function<void(TaskInterface &)>; using TaskSetupHandler = std::function<void(TaskInterface &)>;
// Called on task done / error // Called on task done / error
using TaskEndHandler = std::function<void(const TaskInterface &)>; 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()>; 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 { struct TaskHandler {
TaskCreateHandler m_createHandler; TaskCreateHandler m_createHandler;
@@ -42,9 +62,10 @@ public:
}; };
struct GroupHandler { struct GroupHandler {
GroupSimpleHandler m_simpleSetupHandler; GroupSimpleHandler m_setupHandler;
GroupSimpleHandler m_simpleDoneHandler; GroupSimpleHandler m_doneHandler = {};
GroupSimpleHandler m_simpleErrorHandler; GroupSimpleHandler m_errorHandler = {};
GroupSetupHandler m_dynamicSetupHandler = {};
}; };
enum class ExecuteMode { enum class ExecuteMode {
@@ -136,13 +157,13 @@ public:
class QTCREATOR_UTILS_EXPORT OnGroupSetup : public TaskItem class QTCREATOR_UTILS_EXPORT OnGroupSetup : public TaskItem
{ {
public: public:
OnGroupSetup(const GroupSimpleHandler &handler) : TaskItem({{handler}, {}, {}}) {} OnGroupSetup(const GroupSimpleHandler &handler) : TaskItem({handler}) {}
}; };
class QTCREATOR_UTILS_EXPORT OnGroupDone : public TaskItem class QTCREATOR_UTILS_EXPORT OnGroupDone : public TaskItem
{ {
public: public:
OnGroupDone(const GroupSimpleHandler &handler) : TaskItem({{}, handler, {}}) {} OnGroupDone(const GroupSimpleHandler &handler) : TaskItem({{}, handler}) {}
}; };
class QTCREATOR_UTILS_EXPORT OnGroupError : public TaskItem class QTCREATOR_UTILS_EXPORT OnGroupError : public TaskItem
@@ -151,6 +172,13 @@ public:
OnGroupError(const GroupSimpleHandler &handler) : TaskItem({{}, {}, handler}) {} 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 ExecuteInSequence sequential;
QTCREATOR_UTILS_EXPORT extern ExecuteInParallel parallel; QTCREATOR_UTILS_EXPORT extern ExecuteInParallel parallel;
QTCREATOR_UTILS_EXPORT extern WorkflowPolicy stopOnError; QTCREATOR_UTILS_EXPORT extern WorkflowPolicy stopOnError;

View File

@@ -373,6 +373,52 @@ void tst_TaskTree::processTree_data()
{2, Handler::Error}, {2, Handler::Error},
{-1, Handler::GroupDone}}; {-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("Empty") << emptyRoot << emptyLog << false << true;
QTest::newRow("Nested") << nestedRoot << nestedLog << true << true; QTest::newRow("Nested") << nestedRoot << nestedLog << true << true;
QTest::newRow("Parallel") << parallelRoot << parallelLog << 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("StopOnDone") << stopOnDoneRoot << stopOnDoneLog << true << true;
QTest::newRow("ContinueOnDone") << continueOnDoneRoot << continueOnDoneLog << true << true; QTest::newRow("ContinueOnDone") << continueOnDoneRoot << continueOnDoneLog << true << true;
QTest::newRow("Optional") << optionalRoot << optionalLog << 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() void tst_TaskTree::processTree()