AssetDownloader: Sync TaskTree sources

Change-Id: Ib3727dbca7b27e796b6fab084cbaaa9890b78118
Reviewed-by: Kai Köhne <kai.koehne@qt.io>
(cherry picked from commit f4db5ce8a8e6994b4dce407977f4e936dcd7f48e)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Jarek Kobus 2024-09-02 12:32:26 +02:00 committed by Qt Cherry-pick Bot
parent d62a2decaf
commit 5fa620173e
9 changed files with 771 additions and 97 deletions

View File

@ -9,11 +9,13 @@ qt_internal_add_module(ExamplesAssetDownloaderPrivate
assetdownloader.cpp assetdownloader.h
tasking/barrier.cpp tasking/barrier.h
tasking/concurrentcall.h
tasking/conditional.cpp tasking/conditional.h
tasking/networkquery.cpp tasking/networkquery.h
tasking/qprocesstask.cpp tasking/qprocesstask.h
tasking/tasking_global.h
tasking/tasktree.cpp tasking/tasktree.h
tasking/tasktreerunner.cpp tasking/tasktreerunner.h
tasking/tcpsocket.cpp tasking/tcpsocket.h
DEFINES
QT_NO_CAST_FROM_ASCII
PUBLIC_LIBRARIES

View File

@ -537,9 +537,9 @@ void AssetDownloader::start()
onGroupSetup(onSkipIfAllAssetsPresent),
NetworkQueryTask(onZipDownloadSetup, onZipDownloadDone),
ConcurrentCallTask<void>(onUnzipSetup, onUnzipDone),
Group {
parallelIdealThreadCountLimit,
For {
downloadIterator,
parallelIdealThreadCountLimit,
onGroupSetup(onAssetsDownloadGroupSetup),
Group {
assetStorage,
@ -547,9 +547,9 @@ void AssetDownloader::start()
ConcurrentCallTask<void>(onAssetWriteSetup, onAssetWriteDone)
}
},
Group {
parallelIdealThreadCountLimit,
For {
copyIterator,
parallelIdealThreadCountLimit,
onGroupSetup(onAssetsCopyGroupSetup),
ConcurrentCallTask<void>(onAssetCopySetup, onAssetCopyDone, CallDoneIf::Success),
onGroupDone(onAssetsCopyGroupDone)

View File

@ -82,7 +82,7 @@ using MultiBarrier = Storage<SharedBarrier<Limit>>;
using SingleBarrier = MultiBarrier<1>;
template <int Limit>
GroupItem waitForBarrierTask(const MultiBarrier<Limit> &sharedBarrier)
ExecutableItem waitForBarrierTask(const MultiBarrier<Limit> &sharedBarrier)
{
return BarrierTask([sharedBarrier](Barrier &barrier) {
SharedBarrier<Limit> *activeBarrier = sharedBarrier.activeStorage();

View File

@ -0,0 +1,108 @@
// Copyright (C) 2024 Jarek Kobus
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "conditional.h"
QT_BEGIN_NAMESPACE
namespace Tasking {
static Group conditionRecipe(const Storage<bool> &bodyExecutedStorage,
const ConditionData &condition)
{
Storage<bool> skipContinuationStorage;
const auto onSetup = [bodyExecutedStorage] {
return *bodyExecutedStorage ? SetupResult::StopWithSuccess : SetupResult::Continue;
};
const auto onConditionDone = [skipContinuationStorage](DoneWith result) {
*skipContinuationStorage = result != DoneWith::Success;
return DoneResult::Success;
};
const auto onContinuationSetup = [skipContinuationStorage, bodyExecutedStorage] {
*bodyExecutedStorage = !*skipContinuationStorage;
return *skipContinuationStorage ? SetupResult::StopWithSuccess : SetupResult::Continue;
};
return {
skipContinuationStorage,
onGroupSetup(onSetup),
condition.m_condition ? Group {
*condition.m_condition,
onGroupDone(onConditionDone)
} : nullItem,
Group {
onGroupSetup(onContinuationSetup),
condition.m_body
}
};
}
static ExecutableItem conditionsRecipe(const QList<ConditionData> &conditions)
{
Storage<bool> bodyExecutedStorage;
QList<GroupItem> recipes;
for (const ConditionData &condition : conditions)
recipes << conditionRecipe(bodyExecutedStorage, condition);
return Group { bodyExecutedStorage, recipes };
}
ThenItem::operator ExecutableItem() const
{
return conditionsRecipe(m_conditions);
}
ThenItem::ThenItem(const If &ifItem, const Then &thenItem)
: m_conditions{{ifItem.m_condition, thenItem.m_body}}
{}
ThenItem::ThenItem(const ElseIfItem &elseIfItem, const Then &thenItem)
: m_conditions(elseIfItem.m_conditions)
{
m_conditions.append({elseIfItem.m_nextCondition, thenItem.m_body});
}
ElseItem::operator ExecutableItem() const
{
return conditionsRecipe(m_conditions);
}
ElseItem::ElseItem(const ThenItem &thenItem, const Else &elseItem)
: m_conditions(thenItem.m_conditions)
{
m_conditions.append({{}, elseItem.m_body});
}
ElseIfItem::ElseIfItem(const ThenItem &thenItem, const ElseIf &elseIfItem)
: m_conditions(thenItem.m_conditions)
, m_nextCondition(elseIfItem.m_condition)
{}
ThenItem operator>>(const If &ifItem, const Then &thenItem)
{
return {ifItem, thenItem};
}
ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem)
{
return {elseIfItem, thenItem};
}
ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem)
{
return {thenItem, elseIfItem};
}
ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem)
{
return {thenItem, elseItem};
}
} // namespace Tasking
QT_END_NAMESPACE

View File

@ -0,0 +1,142 @@
// Copyright (C) 2024 Jarek Kobus
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef TASKING_CONDITIONAL_H
#define TASKING_CONDITIONAL_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "tasking_global.h"
#include "tasktree.h"
QT_BEGIN_NAMESPACE
namespace Tasking {
class Then;
class ThenItem;
class ElseItem;
class ElseIfItem;
class TASKING_EXPORT If
{
public:
explicit If(const ExecutableItem &condition) : m_condition(condition) {}
template <typename Handler,
std::enable_if_t<!std::is_base_of_v<ExecutableItem, std::decay_t<Handler>>, bool> = true>
explicit If(Handler &&handler) : m_condition(Sync(std::forward<Handler>(handler))) {}
private:
TASKING_EXPORT friend ThenItem operator>>(const If &ifItem, const Then &thenItem);
friend class ThenItem;
ExecutableItem m_condition;
};
class TASKING_EXPORT ElseIf
{
public:
explicit ElseIf(const ExecutableItem &condition) : m_condition(condition) {}
template <typename Handler,
std::enable_if_t<!std::is_base_of_v<ExecutableItem, std::decay_t<Handler>>, bool> = true>
explicit ElseIf(Handler &&handler) : m_condition(Sync(std::forward<Handler>(handler))) {}
private:
friend class ElseIfItem;
ExecutableItem m_condition;
};
class TASKING_EXPORT Else
{
public:
explicit Else(const QList<GroupItem> &children) : m_body({children}) {}
explicit Else(std::initializer_list<GroupItem> children) : m_body({children}) {}
private:
friend class ElseItem;
Group m_body;
};
class TASKING_EXPORT Then
{
public:
explicit Then(const QList<GroupItem> &children) : m_body({children}) {}
explicit Then(std::initializer_list<GroupItem> children) : m_body({children}) {}
private:
friend class ThenItem;
Group m_body;
};
class ConditionData
{
public:
std::optional<ExecutableItem> m_condition;
Group m_body;
};
class ElseIfItem;
class TASKING_EXPORT ThenItem
{
public:
operator ExecutableItem() const;
private:
ThenItem(const If &ifItem, const Then &thenItem);
ThenItem(const ElseIfItem &elseIfItem, const Then &thenItem);
TASKING_EXPORT friend ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem);
TASKING_EXPORT friend ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem);
TASKING_EXPORT friend ThenItem operator>>(const If &ifItem, const Then &thenItem);
TASKING_EXPORT friend ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem);
friend class ElseItem;
friend class ElseIfItem;
QList<ConditionData> m_conditions;
};
class TASKING_EXPORT ElseItem
{
public:
operator ExecutableItem() const;
private:
ElseItem(const ThenItem &thenItem, const Else &elseItem);
TASKING_EXPORT friend ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem);
QList<ConditionData> m_conditions;
};
class TASKING_EXPORT ElseIfItem
{
private:
ElseIfItem(const ThenItem &thenItem, const ElseIf &elseIfItem);
TASKING_EXPORT friend ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem);
TASKING_EXPORT friend ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem);
friend class ThenItem;
QList<ConditionData> m_conditions;
ExecutableItem m_nextCondition;
};
} // namespace Tasking
QT_END_NAMESPACE
#endif // TASKING_CONDITIONAL_H

View File

@ -629,19 +629,19 @@ private:
*/
/*!
\typealias CustomTask::Task
\typealias Tasking::CustomTask::Task
Type alias for the task type associated with the custom task's \c Adapter.
*/
/*!
\typealias CustomTask::Deleter
\typealias Tasking::CustomTask::Deleter
Type alias for the task's type deleter associated with the custom task's \c Adapter.
*/
/*!
\typealias CustomTask::TaskSetupHandler
\typealias Tasking::CustomTask::TaskSetupHandler
Type alias for \c std::function<SetupResult(Task &)>.
@ -676,9 +676,9 @@ private:
*/
/*!
\typealias CustomTask::TaskDoneHandler
\typealias Tasking::CustomTask::TaskDoneHandler
Type alias for \c std::function<DoneResult(const Task &, DoneWith)>.
Type alias for \c std::function<DoneResult(const Task &, DoneWith)> or DoneResult.
The \c TaskDoneHandler is an optional argument of a custom task element's constructor.
Any function with the above signature, when passed as a task done handler,
@ -703,6 +703,9 @@ private:
the DoneWith argument. When the handler returns the DoneResult value,
the task's final result may be tweaked inside the done handler's body by the returned value.
For a \c TaskDoneHandler of the DoneResult type, no additional handling is executed,
and the task finishes unconditionally with the passed value of DoneResult.
\sa CustomTask(), TaskSetupHandler, GroupDoneHandler
*/
@ -862,7 +865,83 @@ private:
*/
/*!
\variable sequential
\variable Tasking::nullItem
A convenient global group's element indicating a no-op item.
This is useful in conditional expressions to indicate the absence of an optional element:
\code
const ExecutableItem task = ...;
const std::optional<ExecutableItem> optionalTask = ...;
Group group {
task,
optionalTask ? *optionalTask : nullItem
};
\endcode
*/
/*!
\variable Tasking::successItem
A convenient global executable element containing an empty, successful, synchronous task.
This is useful in if-statements to indicate that a branch ends with success:
\code
const ExecutableItem conditionalTask = ...;
Group group {
stopOnDone,
If (conditionalTask) >> Then {
...
} >> Else {
successItem
},
nextTask
};
\endcode
In the above example, if the \c conditionalTask finishes with an error, the \c Else branch
is chosen, which finishes immediately with success. This causes the \c nextTask to be skipped
(because of the stopOnDone workflow policy of the \c group)
and the \c group finishes with success.
\sa errorItem
*/
/*!
\variable Tasking::errorItem
A convenient global executable element containing an empty, erroneous, synchronous task.
This is useful in if-statements to indicate that a branch ends with an error:
\code
const ExecutableItem conditionalTask = ...;
Group group {
stopOnError,
If (conditionalTask) >> Then {
...
} >> Else {
errorItem
},
nextTask
};
\endcode
In the above example, if the \c conditionalTask finishes with an error, the \c Else branch
is chosen, which finishes immediately with an error. This causes the \c nextTask to be skipped
(because of the stopOnError workflow policy of the \c group)
and the \c group finishes with an error.
\sa successItem
*/
/*!
\variable Tasking::sequential
A convenient global group's element describing the sequential execution mode.
This is the default execution mode of the Group element.
@ -877,7 +956,7 @@ private:
*/
/*!
\variable parallel
\variable Tasking::parallel
A convenient global group's element describing the parallel execution mode.
All the direct child tasks of a group are started after the group is started,
@ -888,7 +967,7 @@ private:
*/
/*!
\variable parallelIdealThreadCountLimit
\variable Tasking::parallelIdealThreadCountLimit
A convenient global group's element describing the parallel execution mode with a limited
number of tasks running simultanously. The limit is equal to the ideal number of threads
excluding the calling thread.
@ -902,39 +981,39 @@ private:
*/
/*!
\variable stopOnError
\variable Tasking::stopOnError
A convenient global group's element describing the StopOnError workflow policy.
This is the default workflow policy of the Group element.
*/
/*!
\variable continueOnError
\variable Tasking::continueOnError
A convenient global group's element describing the ContinueOnError workflow policy.
*/
/*!
\variable stopOnSuccess
\variable Tasking::stopOnSuccess
A convenient global group's element describing the StopOnSuccess workflow policy.
*/
/*!
\variable continueOnSuccess
\variable Tasking::continueOnSuccess
A convenient global group's element describing the ContinueOnSuccess workflow policy.
*/
/*!
\variable stopOnSuccessOrError
\variable Tasking::stopOnSuccessOrError
A convenient global group's element describing the StopOnSuccessOrError workflow policy.
*/
/*!
\variable finishAllAndSuccess
\variable Tasking::finishAllAndSuccess
A convenient global group's element describing the FinishAllAndSuccess workflow policy.
*/
/*!
\variable finishAllAndError
\variable Tasking::finishAllAndError
A convenient global group's element describing the FinishAllAndError workflow policy.
*/
@ -1025,7 +1104,7 @@ private:
*/
/*!
\typealias GroupItem::GroupSetupHandler
\typealias Tasking::GroupItem::GroupSetupHandler
Type alias for \c std::function<SetupResult()>.
@ -1055,9 +1134,9 @@ private:
*/
/*!
\typealias GroupItem::GroupDoneHandler
\typealias Tasking::GroupItem::GroupDoneHandler
Type alias for \c std::function<DoneResult(DoneWith)>.
Type alias for \c std::function<DoneResult(DoneWith)> or DoneResult.
The \c GroupDoneHandler is an argument of the onGroupDone() element.
Any function with the above signature, when passed as a group done handler,
@ -1072,6 +1151,10 @@ private:
the DoneWith argument. When the handler returns the DoneResult value,
the group's final result may be tweaked inside the done handler's body by the returned value.
For a \c GroupDoneHandler of the DoneResult type, no additional handling is executed,
and the group finishes unconditionally with the passed value of DoneResult,
ignoring the group's workflow policy.
\sa onGroupDone(), GroupSetupHandler, CustomTask::TaskDoneHandler
*/
@ -1159,9 +1242,9 @@ private:
\sa sequential, parallel
*/
GroupItem parallelLimit(int limit)
GroupItem ParallelLimitFunctor::operator()(int limit) const
{
return Group::parallelLimit(qMax(limit, 0));
return GroupItem({{}, limit});
}
/*!
@ -1172,12 +1255,13 @@ GroupItem parallelLimit(int limit)
\sa stopOnError, continueOnError, stopOnSuccess, continueOnSuccess, stopOnSuccessOrError,
finishAllAndSuccess, finishAllAndError, WorkflowPolicy
*/
GroupItem workflowPolicy(WorkflowPolicy policy)
GroupItem WorkflowPolicyFunctor::operator()(WorkflowPolicy policy) const
{
return Group::workflowPolicy(policy);
return GroupItem({{}, {}, policy});
}
const GroupItem nullItem = GroupItem({});
const ParallelLimitFunctor parallelLimit = ParallelLimitFunctor();
const WorkflowPolicyFunctor workflowPolicy = WorkflowPolicyFunctor();
const GroupItem sequential = parallelLimit(1);
const GroupItem parallel = parallelLimit(0);
@ -1191,6 +1275,11 @@ const GroupItem stopOnSuccessOrError = workflowPolicy(WorkflowPolicy::StopOnSucc
const GroupItem finishAllAndSuccess = workflowPolicy(WorkflowPolicy::FinishAllAndSuccess);
const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError);
// Keep below the above in order to avoid static initialization fiasco.
const GroupItem nullItem = GroupItem({});
const ExecutableItem successItem = Group { finishAllAndSuccess };
const ExecutableItem errorItem = Group { finishAllAndError };
// Please note the thread_local keyword below guarantees a separate instance per thread.
// The s_activeTaskTrees is currently used internally only and is not exposed in the public API.
// It serves for withLog() implementation now. Add a note here when a new usage is introduced.
@ -1292,7 +1381,7 @@ const void *Loop::valuePtr() const
using StoragePtr = void *;
constexpr QLatin1StringView s_activeStorageWarning =
static constexpr QLatin1StringView s_activeStorageWarning =
"The referenced storage is not reachable in the running tree. "
"A nullptr will be returned which might lead to a crash in the calling code. "
"It is possible that no storage was added to the tree, "
@ -1503,6 +1592,128 @@ ExecutableItem ExecutableItem::withLog(const QString &logName) const
};
}
/*!
\fn ExecutableItem ExecutableItem::operator!(const ExecutableItem &item)
Returns an ExecutableItem with the DoneResult of \a item negated.
If \a item reports DoneResult::Success, the returned item reports DoneResult::Error.
If \a item reports DoneResult::Error, the returned item reports DoneResult::Success.
The returned item is equivalent to:
\code
Group {
item,
onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); })
}
\endcode
\sa operator&&(), operator||()
*/
ExecutableItem operator!(const ExecutableItem &item)
{
return Group {
item,
onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); })
};
}
/*!
\fn ExecutableItem ExecutableItem::operator&&(const ExecutableItem &first, const ExecutableItem &second)
Returns an ExecutableItem with \a first and \a second tasks merged with conjunction.
Both \a first and \a second tasks execute in sequence.
If both tasks report DoneResult::Success, the returned item reports DoneResult::Success.
Otherwise, the returned item reports DoneResult::Error.
The returned item is
\l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}:
if the \a first task reports DoneResult::Error, the \a second task is skipped,
and the returned item reports DoneResult::Error immediately.
The returned item is equivalent to:
\code
Group { stopOnError, first, second }
\endcode
\note Parallel execution of conjunction in a short-circuit manner can be achieved with the
following code: \c {Group { parallel, stopOnError, first, second }}. In this case:
if the \e {first finished} task reports DoneResult::Error,
the \e other task is canceled, and the group reports DoneResult::Error immediately.
\sa operator||(), operator!()
*/
ExecutableItem operator&&(const ExecutableItem &first, const ExecutableItem &second)
{
return Group { stopOnError, first, second };
}
/*!
\fn ExecutableItem ExecutableItem::operator||(const ExecutableItem &first, const ExecutableItem &second)
Returns an ExecutableItem with \a first and \a second tasks merged with disjunction.
Both \a first and \a second tasks execute in sequence.
If both tasks report DoneResult::Error, the returned item reports DoneResult::Error.
Otherwise, the returned item reports DoneResult::Success.
The returned item is
\l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}:
if the \a first task reports DoneResult::Success, the \a second task is skipped,
and the returned item reports DoneResult::Success immediately.
The returned item is equivalent to:
\code
Group { stopOnSuccess, first, second }
\endcode
\note Parallel execution of disjunction in a short-circuit manner can be achieved with the
following code: \c {Group { parallel, stopOnSuccess, first, second }}. In this case:
if the \e {first finished} task reports DoneResult::Success,
the \e other task is canceled, and the group reports DoneResult::Success immediately.
\sa operator&&(), operator!()
*/
ExecutableItem operator||(const ExecutableItem &first, const ExecutableItem &second)
{
return Group { stopOnSuccess, first, second };
}
/*!
\fn ExecutableItem ExecutableItem::operator&&(const ExecutableItem &item, DoneResult result)
\overload ExecutableItem::operator&&()
Returns the \a item task if the \a result is DoneResult::Success; otherwise returns
the \a item task with its done result tweaked to DoneResult::Error.
The \c {task && DoneResult::Error} is an eqivalent to tweaking the task's done result
into DoneResult::Error unconditionally.
*/
ExecutableItem operator&&(const ExecutableItem &item, DoneResult result)
{
if (result == DoneResult::Success)
return item;
return Group { finishAllAndError, item };
}
/*!
\fn ExecutableItem ExecutableItem::operator||(const ExecutableItem &item, DoneResult result)
\overload ExecutableItem::operator||()
Returns the \a item task if the \a result is DoneResult::Error; otherwise returns
the \a item task with its done result tweaked to DoneResult::Success.
The \c {task || DoneResult::Success} is an eqivalent to tweaking the task's done result
into DoneResult::Success unconditionally.
*/
ExecutableItem operator||(const ExecutableItem &item, DoneResult result)
{
if (result == DoneResult::Error)
return item;
return Group { finishAllAndSuccess, item };
}
ExecutableItem ExecutableItem::withCancelImpl(
const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const
{
@ -1974,13 +2185,6 @@ SetupResult TaskTreePrivate::start(RuntimeContainer *container)
container->m_successBit = startAction == SetupResult::StopWithSuccess;
}
}
if (startAction == SetupResult::Continue
&& (containerNode.m_children.empty()
|| (containerNode.m_loop && !invokeLoopHandler(container)))) {
if (isProgressive(container))
advanceProgress(containerNode.m_taskCount);
startAction = toSetupResult(container->m_successBit);
}
return continueStart(container, startAction);
}
@ -1988,23 +2192,24 @@ SetupResult TaskTreePrivate::continueStart(RuntimeContainer *container, SetupRes
{
const SetupResult groupAction = startAction == SetupResult::Continue ? startChildren(container)
: startAction;
if (groupAction != SetupResult::Continue) {
const bool bit = container->updateSuccessBit(groupAction == SetupResult::StopWithSuccess);
RuntimeIteration *parentIteration = container->parentIteration();
RuntimeTask *parentTask = container->m_parentTask;
QT_CHECK(parentTask);
const bool result = invokeDoneHandler(container, bit ? DoneWith::Success : DoneWith::Error);
if (parentIteration) {
parentIteration->deleteChild(parentTask);
if (!parentIteration->m_container->isStarting())
childDone(parentIteration, result);
} else {
QT_CHECK(m_runtimeRoot.get() == parentTask);
m_runtimeRoot.reset();
emitDone(result ? DoneWith::Success : DoneWith::Error);
}
if (groupAction == SetupResult::Continue)
return groupAction;
const bool bit = container->updateSuccessBit(groupAction == SetupResult::StopWithSuccess);
RuntimeIteration *parentIteration = container->parentIteration();
RuntimeTask *parentTask = container->m_parentTask;
QT_CHECK(parentTask);
const bool result = invokeDoneHandler(container, bit ? DoneWith::Success : DoneWith::Error);
if (parentIteration) {
parentIteration->deleteChild(parentTask);
if (!parentIteration->m_container->isStarting())
childDone(parentIteration, result);
} else {
QT_CHECK(m_runtimeRoot.get() == parentTask);
m_runtimeRoot.reset();
emitDone(result ? DoneWith::Success : DoneWith::Error);
}
return groupAction;
return toSetupResult(result);
}
SetupResult TaskTreePrivate::startChildren(RuntimeContainer *container)
@ -2013,14 +2218,14 @@ SetupResult TaskTreePrivate::startChildren(RuntimeContainer *container)
const int childCount = int(containerNode.m_children.size());
if (container->m_iterationCount == 0) {
if (container->m_shouldIterate && !invokeLoopHandler(container)) {
if (isProgressive(container))
advanceProgress(containerNode.m_taskCount);
return toSetupResult(container->m_successBit);
}
container->m_iterations.emplace_back(
std::make_unique<RuntimeIteration>(container->m_iterationCount, container));
++container->m_iterationCount;
} else if (containerNode.m_parallelLimit == 0) {
container->deleteFinishedIterations();
if (container->m_iterations.empty())
return toSetupResult(container->m_successBit);
return SetupResult::Continue;
}
GuardLocker locker(container->m_startGuard);
@ -2029,17 +2234,20 @@ SetupResult TaskTreePrivate::startChildren(RuntimeContainer *container)
|| container->m_runningChildren < containerNode.m_parallelLimit) {
container->deleteFinishedIterations();
if (container->m_nextToStart == childCount) {
if (container->m_shouldIterate && invokeLoopHandler(container)) {
if (invokeLoopHandler(container)) {
container->m_nextToStart = 0;
container->m_iterations.emplace_back(
std::make_unique<RuntimeIteration>(container->m_iterationCount, container));
++container->m_iterationCount;
} else if (container->m_iterations.empty()) {
return toSetupResult(container->m_successBit);
} else {
if (container->m_iterations.empty())
return toSetupResult(container->m_successBit);
return SetupResult::Continue;
}
}
if (containerNode.m_children.size() == 0) // Empty loop body.
continue;
RuntimeIteration *iteration = container->m_iterations.back().get();
RuntimeTask *newTask = new RuntimeTask{containerNode.m_children.at(container->m_nextToStart),
iteration};
@ -2436,7 +2644,7 @@ bool TaskTreePrivate::invokeDoneHandler(RuntimeTask *node, DoneWith doneWith)
\section2 Task's Done Handler
When a running task finishes, the task tree invokes an optionally provided done handler.
The handler should always take a \c const \e reference to the associated task class object:
The handler should take a \c const \e reference to the associated task class object:
\code
const auto onSetup = [](QProcess &process) {
@ -3399,13 +3607,13 @@ void TimeoutTaskAdapter::start()
}
/*!
\typealias TaskTreeTask
\typealias Tasking::TaskTreeTask
Type alias for the CustomTask, to be used inside recipes, associated with the TaskTree task.
*/
/*!
\typealias TimeoutTask
\typealias Tasking::TimeoutTask
Type alias for the CustomTask, to be used inside recipes, associated with the
\c std::chrono::milliseconds type. \c std::chrono::milliseconds is used to set up the

View File

@ -218,13 +218,12 @@ public:
: m_type(Type::Storage)
, m_storageList{storage} {}
GroupItem(const Loop &loop) : GroupItem(GroupData{{}, {}, {}, loop}) {}
// TODO: Add tests.
GroupItem(const QList<GroupItem> &children) : m_type(Type::List) { addChildren(children); }
GroupItem(std::initializer_list<GroupItem> children) : m_type(Type::List) { addChildren(children); }
protected:
GroupItem(const Loop &loop) : GroupItem(GroupData{{}, {}, {}, loop}) {}
// Internal, provided by CustomTask
using InterfaceCreateHandler = std::function<TaskInterface *(void)>;
// Called prior to task start, just after createHandler
@ -271,8 +270,6 @@ protected:
void addChildren(const QList<GroupItem> &children);
static GroupItem groupHandler(const GroupHandler &handler) { return GroupItem({handler}); }
static GroupItem parallelLimit(int limit) { return GroupItem({{}, limit}); }
static GroupItem workflowPolicy(WorkflowPolicy policy) { return GroupItem({{}, {}, policy}); }
// Checks if Function may be invoked with Args and if Function's return type is Result.
template <typename Result, typename Function, typename ...Args,
@ -287,8 +284,11 @@ protected:
private:
friend class ContainerNode;
friend class For;
friend class TaskNode;
friend class TaskTreePrivate;
friend class ParallelLimitFunctor;
friend class WorkflowPolicyFunctor;
Type m_type = Type::Group;
QList<GroupItem> m_children;
GroupData m_groupData;
@ -319,6 +319,14 @@ protected:
ExecutableItem(const TaskHandler &handler) : GroupItem(handler) {}
private:
TASKING_EXPORT friend ExecutableItem operator!(const ExecutableItem &item);
TASKING_EXPORT friend ExecutableItem operator&&(const ExecutableItem &first,
const ExecutableItem &second);
TASKING_EXPORT friend ExecutableItem operator||(const ExecutableItem &first,
const ExecutableItem &second);
TASKING_EXPORT friend ExecutableItem operator&&(const ExecutableItem &item, DoneResult result);
TASKING_EXPORT friend ExecutableItem operator||(const ExecutableItem &item, DoneResult result);
ExecutableItem withCancelImpl(
const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const;
};
@ -338,8 +346,6 @@ public:
static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) {
return groupHandler({{}, wrapGroupDone(std::forward<Handler>(handler)), callDoneIf});
}
using GroupItem::parallelLimit; // Default: 1 (sequential). 0 means unlimited (parallel).
using GroupItem::workflowPolicy; // Default: WorkflowPolicy::StopOnError.
private:
template <typename Handler>
@ -361,24 +367,34 @@ private:
template <typename Handler>
static GroupDoneHandler wrapGroupDone(Handler &&handler)
{
// R, V, D stands for: Done[R]esult, [V]oid, [D]oneWith
static constexpr bool isDoneResultType = std::is_same_v<Handler, DoneResult>;
// R, B, V, D stands for: Done[R]esult, [B]ool, [V]oid, [D]oneWith
static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>();
static constexpr bool isR = isInvocable<DoneResult, Handler>();
static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>();
static constexpr bool isB = isInvocable<bool, Handler>();
static constexpr bool isVD = isInvocable<void, Handler, DoneWith>();
static constexpr bool isV = isInvocable<void, Handler>();
static_assert(isRD || isR || isVD || isV,
static_assert(isDoneResultType || isRD || isR || isBD || isB || isVD || isV,
"Group done handler needs to take (DoneWith) or (void) as an argument and has to "
"return void or DoneResult. The passed handler doesn't fulfill these requirements.");
"return void, bool or DoneResult. Alternatively, it may be of DoneResult type. "
"The passed handler doesn't fulfill these requirements.");
return [handler](DoneWith result) {
if constexpr (isDoneResultType)
return handler;
if constexpr (isRD)
return std::invoke(handler, result);
if constexpr (isR)
return std::invoke(handler);
if constexpr (isBD)
return toDoneResult(std::invoke(handler, result));
if constexpr (isB)
return toDoneResult(std::invoke(handler));
if constexpr (isVD)
std::invoke(handler, result);
else if constexpr (isV)
std::invoke(handler);
return result == DoneWith::Success ? DoneResult::Success : DoneResult::Error;
return toDoneResult(result == DoneWith::Success);
};
}
};
@ -395,10 +411,22 @@ static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDone
return Group::onGroupDone(std::forward<Handler>(handler), callDoneIf);
}
TASKING_EXPORT GroupItem parallelLimit(int limit);
TASKING_EXPORT GroupItem workflowPolicy(WorkflowPolicy policy);
class TASKING_EXPORT ParallelLimitFunctor
{
public:
// Default: 1 (sequential). 0 means unlimited (parallel).
GroupItem operator()(int limit) const;
};
TASKING_EXPORT extern const GroupItem nullItem;
class TASKING_EXPORT WorkflowPolicyFunctor
{
public:
// Default: WorkflowPolicy::StopOnError.
GroupItem operator()(WorkflowPolicy policy) const;
};
TASKING_EXPORT extern const ParallelLimitFunctor parallelLimit;
TASKING_EXPORT extern const WorkflowPolicyFunctor workflowPolicy;
TASKING_EXPORT extern const GroupItem sequential;
TASKING_EXPORT extern const GroupItem parallel;
@ -412,11 +440,46 @@ TASKING_EXPORT extern const GroupItem stopOnSuccessOrError;
TASKING_EXPORT extern const GroupItem finishAllAndSuccess;
TASKING_EXPORT extern const GroupItem finishAllAndError;
class TASKING_EXPORT Forever final : public Group
TASKING_EXPORT extern const GroupItem nullItem;
TASKING_EXPORT extern const ExecutableItem successItem;
TASKING_EXPORT extern const ExecutableItem errorItem;
class TASKING_EXPORT For : public Group
{
public:
Forever(const QList<GroupItem> &children) : Group({LoopForever(), children}) {}
Forever(std::initializer_list<GroupItem> children) : Group({LoopForever(), children}) {}
template <typename ...Args>
For(const Loop &loop, const Args &...args)
: Group(withLoop(loop, args...)) { }
protected:
For(const Loop &loop, const QList<GroupItem> &children) : Group({loop, children}) {}
For(const Loop &loop, std::initializer_list<GroupItem> children) : Group({loop, children}) {}
private:
template <typename ...Args>
QList<GroupItem> withLoop(const Loop &loop, const Args &...args) {
QList<GroupItem> children{GroupItem(loop)};
appendChildren(std::make_tuple(args...), &children);
return children;
}
template <typename Tuple, std::size_t N = 0>
void appendChildren(const Tuple &tuple, QList<GroupItem> *children) {
constexpr auto TupleSize = std::tuple_size_v<Tuple>;
if constexpr (TupleSize > 0) {
// static_assert(workflowPolicyCount<Tuple>() <= 1, "Too many workflow policies in one group.");
children->append(std::get<N>(tuple));
if constexpr (N + 1 < TupleSize)
appendChildren<Tuple, N + 1>(tuple, children);
}
}
};
class TASKING_EXPORT Forever final : public For
{
public:
Forever(const QList<GroupItem> &children) : For(LoopForever(), children) {}
Forever(std::initializer_list<GroupItem> children) : For(LoopForever(), children) {}
};
// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount()
@ -425,26 +488,20 @@ class TASKING_EXPORT Sync final : public ExecutableItem
public:
template <typename Handler>
Sync(Handler &&handler) {
addChildren({ onGroupSetup(wrapHandler(std::forward<Handler>(handler))) });
addChildren({ onGroupDone(wrapHandler(std::forward<Handler>(handler))) });
}
private:
template <typename Handler>
static GroupSetupHandler wrapHandler(Handler &&handler) {
// R, V stands for: Done[R]esult, [V]oid
static auto wrapHandler(Handler &&handler) {
// R, B, V stands for: Done[R]esult, [B]ool, [V]oid
static constexpr bool isR = isInvocable<DoneResult, Handler>();
static constexpr bool isB = isInvocable<bool, Handler>();
static constexpr bool isV = isInvocable<void, Handler>();
static_assert(isR || isV,
"Sync handler needs to take no arguments and has to return void or DoneResult. "
static_assert(isR || isB || isV,
"Sync handler needs to take no arguments and has to return void, bool or DoneResult. "
"The passed handler doesn't fulfill these requirements.");
return [handler] {
if constexpr (isR) {
return std::invoke(handler) == DoneResult::Success ? SetupResult::StopWithSuccess
: SetupResult::StopWithError;
}
std::invoke(handler);
return SetupResult::StopWithSuccess;
};
return handler;
}
};
@ -507,21 +564,31 @@ private:
template <typename Handler>
static InterfaceDoneHandler wrapDone(Handler &&handler) {
if constexpr (std::is_same_v<Handler, TaskDoneHandler>)
return {}; // When user passed {} for the done handler.
// R, V, T, D stands for: Done[R]esult, [V]oid, [T]ask, [D]oneWith
return {}; // User passed {} for the done handler.
static constexpr bool isDoneResultType = std::is_same_v<Handler, DoneResult>;
// R, B, V, T, D stands for: Done[R]esult, [B]ool, [V]oid, [T]ask, [D]oneWith
static constexpr bool isRTD = isInvocable<DoneResult, Handler, const Task &, DoneWith>();
static constexpr bool isRT = isInvocable<DoneResult, Handler, const Task &>();
static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>();
static constexpr bool isR = isInvocable<DoneResult, Handler>();
static constexpr bool isBTD = isInvocable<bool, Handler, const Task &, DoneWith>();
static constexpr bool isBT = isInvocable<bool, Handler, const Task &>();
static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>();
static constexpr bool isB = isInvocable<bool, Handler>();
static constexpr bool isVTD = isInvocable<void, Handler, const Task &, DoneWith>();
static constexpr bool isVT = isInvocable<void, Handler, const Task &>();
static constexpr bool isVD = isInvocable<void, Handler, DoneWith>();
static constexpr bool isV = isInvocable<void, Handler>();
static_assert(isRTD || isRT || isRD || isR || isVTD || isVT || isVD || isV,
static_assert(isDoneResultType || isRTD || isRT || isRD || isR
|| isBTD || isBT || isBD || isB
|| isVTD || isVT || isVD || isV,
"Task done handler needs to take (const Task &, DoneWith), (const Task &), "
"(DoneWith) or (void) as arguments and has to return void or DoneResult. "
"(DoneWith) or (void) as arguments and has to return void, bool or DoneResult. "
"Alternatively, it may be of DoneResult type. "
"The passed handler doesn't fulfill these requirements.");
return [handler](const TaskInterface &taskInterface, DoneWith result) {
if constexpr (isDoneResultType)
return handler;
const Adapter &adapter = static_cast<const Adapter &>(taskInterface);
if constexpr (isRTD)
return std::invoke(handler, *adapter.task(), result);
@ -531,6 +598,14 @@ private:
return std::invoke(handler, result);
if constexpr (isR)
return std::invoke(handler);
if constexpr (isBTD)
return toDoneResult(std::invoke(handler, *adapter.task(), result));
if constexpr (isBT)
return toDoneResult(std::invoke(handler, *adapter.task()));
if constexpr (isBD)
return toDoneResult(std::invoke(handler, result));
if constexpr (isB)
return toDoneResult(std::invoke(handler));
if constexpr (isVTD)
std::invoke(handler, *adapter.task(), result);
else if constexpr (isVT)
@ -539,7 +614,7 @@ private:
std::invoke(handler, result);
else if constexpr (isV)
std::invoke(handler);
return result == DoneWith::Success ? DoneResult::Success : DoneResult::Error;
return toDoneResult(result == DoneWith::Success);
};
}
};

View File

@ -0,0 +1,67 @@
// Copyright (C) 2024 Jarek Kobus
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "tcpsocket.h"
QT_BEGIN_NAMESPACE
namespace Tasking {
void TcpSocket::start()
{
if (m_socket) {
qWarning("The TcpSocket is already running. Ignoring the call to start().");
return;
}
if (m_address.isNull()) {
qWarning("Can't start the TcpSocket with invalid address. "
"Stopping with an error.");
m_error = QAbstractSocket::HostNotFoundError;
emit done(DoneResult::Error);
return;
}
m_socket.reset(new QTcpSocket);
connect(m_socket.get(), &QAbstractSocket::errorOccurred, this,
[this](QAbstractSocket::SocketError error) {
m_error = error;
m_socket->disconnect();
emit done(DoneResult::Error);
m_socket.release()->deleteLater();
});
connect(m_socket.get(), &QAbstractSocket::connected, this, [this] {
if (!m_writeData.isEmpty())
m_socket->write(m_writeData);
emit started();
});
connect(m_socket.get(), &QAbstractSocket::disconnected, this, [this] {
m_socket->disconnect();
emit done(DoneResult::Success);
m_socket.release()->deleteLater();
});
m_socket->connectToHost(m_address, m_port);
}
TcpSocket::~TcpSocket()
{
if (m_socket) {
m_socket->disconnect();
m_socket->abort();
}
}
TcpSocketTaskAdapter::TcpSocketTaskAdapter()
{
connect(task(), &TcpSocket::done, this, &TaskInterface::done);
}
void TcpSocketTaskAdapter::start()
{
task()->start();
}
} // namespace Tasking
QT_END_NAMESPACE

View File

@ -0,0 +1,72 @@
// Copyright (C) 2024 Jarek Kobus
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef TASKING_TCPSOCKET_H
#define TASKING_TCPSOCKET_H
#include "tasking_global.h"
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "tasktree.h"
#include <QtNetwork/QTcpSocket>
#include <memory>
QT_BEGIN_NAMESPACE
namespace Tasking {
// This class introduces the dependency to Qt::Network, otherwise Tasking namespace
// is independent on Qt::Network.
// Possibly, it could be placed inside Qt::Network library, as a wrapper around QTcpSocket.
class TASKING_EXPORT TcpSocket final : public QObject
{
Q_OBJECT
public:
~TcpSocket();
void setAddress(const QHostAddress &address) { m_address = address; }
void setPort(quint16 port) { m_port = port; }
void setWriteData(const QByteArray &data) { m_writeData = data; }
QTcpSocket *socket() const { return m_socket.get(); }
void start();
Q_SIGNALS:
void started();
void done(DoneResult result);
private:
QHostAddress m_address;
quint16 m_port = 0;
QByteArray m_writeData;
std::unique_ptr<QTcpSocket> m_socket;
QAbstractSocket::SocketError m_error = QAbstractSocket::UnknownSocketError;
};
class TASKING_EXPORT TcpSocketTaskAdapter final : public TaskAdapter<TcpSocket>
{
public:
TcpSocketTaskAdapter();
void start() final;
};
using TcpSocketTask = CustomTask<TcpSocketTaskAdapter>;
} // namespace Tasking
QT_END_NAMESPACE
#endif // TASKING_TCPSOCKET_H