diff --git a/src/assets/downloader/CMakeLists.txt b/src/assets/downloader/CMakeLists.txt index 872932ad2a6..b7dc5977b19 100644 --- a/src/assets/downloader/CMakeLists.txt +++ b/src/assets/downloader/CMakeLists.txt @@ -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 diff --git a/src/assets/downloader/assetdownloader.cpp b/src/assets/downloader/assetdownloader.cpp index fb82dc29adb..2964b10e088 100644 --- a/src/assets/downloader/assetdownloader.cpp +++ b/src/assets/downloader/assetdownloader.cpp @@ -537,9 +537,9 @@ void AssetDownloader::start() onGroupSetup(onSkipIfAllAssetsPresent), NetworkQueryTask(onZipDownloadSetup, onZipDownloadDone), ConcurrentCallTask(onUnzipSetup, onUnzipDone), - Group { - parallelIdealThreadCountLimit, + For { downloadIterator, + parallelIdealThreadCountLimit, onGroupSetup(onAssetsDownloadGroupSetup), Group { assetStorage, @@ -547,9 +547,9 @@ void AssetDownloader::start() ConcurrentCallTask(onAssetWriteSetup, onAssetWriteDone) } }, - Group { - parallelIdealThreadCountLimit, + For { copyIterator, + parallelIdealThreadCountLimit, onGroupSetup(onAssetsCopyGroupSetup), ConcurrentCallTask(onAssetCopySetup, onAssetCopyDone, CallDoneIf::Success), onGroupDone(onAssetsCopyGroupDone) diff --git a/src/assets/downloader/tasking/barrier.h b/src/assets/downloader/tasking/barrier.h index f99fdae60db..c8a6627a8c4 100644 --- a/src/assets/downloader/tasking/barrier.h +++ b/src/assets/downloader/tasking/barrier.h @@ -82,7 +82,7 @@ using MultiBarrier = Storage>; using SingleBarrier = MultiBarrier<1>; template -GroupItem waitForBarrierTask(const MultiBarrier &sharedBarrier) +ExecutableItem waitForBarrierTask(const MultiBarrier &sharedBarrier) { return BarrierTask([sharedBarrier](Barrier &barrier) { SharedBarrier *activeBarrier = sharedBarrier.activeStorage(); diff --git a/src/assets/downloader/tasking/conditional.cpp b/src/assets/downloader/tasking/conditional.cpp new file mode 100644 index 00000000000..73636d659dd --- /dev/null +++ b/src/assets/downloader/tasking/conditional.cpp @@ -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 &bodyExecutedStorage, + const ConditionData &condition) +{ + Storage 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 &conditions) +{ + Storage bodyExecutedStorage; + + QList 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 diff --git a/src/assets/downloader/tasking/conditional.h b/src/assets/downloader/tasking/conditional.h new file mode 100644 index 00000000000..ab30b492f3a --- /dev/null +++ b/src/assets/downloader/tasking/conditional.h @@ -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 >, bool> = true> + explicit If(Handler &&handler) : m_condition(Sync(std::forward(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 >, bool> = true> + explicit ElseIf(Handler &&handler) : m_condition(Sync(std::forward(handler))) {} + +private: + friend class ElseIfItem; + ExecutableItem m_condition; +}; + +class TASKING_EXPORT Else +{ +public: + explicit Else(const QList &children) : m_body({children}) {} + explicit Else(std::initializer_list children) : m_body({children}) {} + +private: + friend class ElseItem; + Group m_body; +}; + +class TASKING_EXPORT Then +{ +public: + explicit Then(const QList &children) : m_body({children}) {} + explicit Then(std::initializer_list children) : m_body({children}) {} + +private: + friend class ThenItem; + Group m_body; +}; + +class ConditionData +{ +public: + std::optional 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 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 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 m_conditions; + ExecutableItem m_nextCondition; +}; + +} // namespace Tasking + +QT_END_NAMESPACE + +#endif // TASKING_CONDITIONAL_H diff --git a/src/assets/downloader/tasking/tasktree.cpp b/src/assets/downloader/tasking/tasktree.cpp index 0e62ceef7f8..8ff32ea0fe2 100644 --- a/src/assets/downloader/tasking/tasktree.cpp +++ b/src/assets/downloader/tasking/tasktree.cpp @@ -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. @@ -676,9 +676,9 @@ private: */ /*! - \typealias CustomTask::TaskDoneHandler + \typealias Tasking::CustomTask::TaskDoneHandler - Type alias for \c std::function. + Type alias for \c std::function 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 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. @@ -1055,9 +1134,9 @@ private: */ /*! - \typealias GroupItem::GroupDoneHandler + \typealias Tasking::GroupItem::GroupDoneHandler - Type alias for \c std::function. + Type alias for \c std::function 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 &)> &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(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(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 diff --git a/src/assets/downloader/tasking/tasktree.h b/src/assets/downloader/tasking/tasktree.h index fa5f5188dfc..53595628c2f 100644 --- a/src/assets/downloader/tasking/tasktree.h +++ b/src/assets/downloader/tasking/tasktree.h @@ -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 &children) : m_type(Type::List) { addChildren(children); } GroupItem(std::initializer_list children) : m_type(Type::List) { addChildren(children); } protected: + GroupItem(const Loop &loop) : GroupItem(GroupData{{}, {}, {}, loop}) {} // Internal, provided by CustomTask using InterfaceCreateHandler = std::function; // Called prior to task start, just after createHandler @@ -271,8 +270,6 @@ protected: void addChildren(const QList &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 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 &)> &connectWrapper) const; }; @@ -338,8 +346,6 @@ public: static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) { return groupHandler({{}, wrapGroupDone(std::forward(handler)), callDoneIf}); } - using GroupItem::parallelLimit; // Default: 1 (sequential). 0 means unlimited (parallel). - using GroupItem::workflowPolicy; // Default: WorkflowPolicy::StopOnError. private: template @@ -361,24 +367,34 @@ private: template 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; + // R, B, V, D stands for: Done[R]esult, [B]ool, [V]oid, [D]oneWith static constexpr bool isRD = isInvocable(); static constexpr bool isR = isInvocable(); + static constexpr bool isBD = isInvocable(); + static constexpr bool isB = isInvocable(); static constexpr bool isVD = isInvocable(); static constexpr bool isV = isInvocable(); - 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), 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 &children) : Group({LoopForever(), children}) {} - Forever(std::initializer_list children) : Group({LoopForever(), children}) {} + template + For(const Loop &loop, const Args &...args) + : Group(withLoop(loop, args...)) { } + +protected: + For(const Loop &loop, const QList &children) : Group({loop, children}) {} + For(const Loop &loop, std::initializer_list children) : Group({loop, children}) {} + +private: + template + QList withLoop(const Loop &loop, const Args &...args) { + QList children{GroupItem(loop)}; + appendChildren(std::make_tuple(args...), &children); + return children; + } + + template + void appendChildren(const Tuple &tuple, QList *children) { + constexpr auto TupleSize = std::tuple_size_v; + if constexpr (TupleSize > 0) { + // static_assert(workflowPolicyCount() <= 1, "Too many workflow policies in one group."); + children->append(std::get(tuple)); + if constexpr (N + 1 < TupleSize) + appendChildren(tuple, children); + } + } +}; + +class TASKING_EXPORT Forever final : public For +{ +public: + Forever(const QList &children) : For(LoopForever(), children) {} + Forever(std::initializer_list 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 Sync(Handler &&handler) { - addChildren({ onGroupSetup(wrapHandler(std::forward(handler))) }); + addChildren({ onGroupDone(wrapHandler(std::forward(handler))) }); } private: template - 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(); + static constexpr bool isB = isInvocable(); static constexpr bool isV = isInvocable(); - 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 static InterfaceDoneHandler wrapDone(Handler &&handler) { if constexpr (std::is_same_v) - 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; + // R, B, V, T, D stands for: Done[R]esult, [B]ool, [V]oid, [T]ask, [D]oneWith static constexpr bool isRTD = isInvocable(); static constexpr bool isRT = isInvocable(); static constexpr bool isRD = isInvocable(); static constexpr bool isR = isInvocable(); + static constexpr bool isBTD = isInvocable(); + static constexpr bool isBT = isInvocable(); + static constexpr bool isBD = isInvocable(); + static constexpr bool isB = isInvocable(); static constexpr bool isVTD = isInvocable(); static constexpr bool isVT = isInvocable(); static constexpr bool isVD = isInvocable(); static constexpr bool isV = isInvocable(); - 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(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); }; } }; diff --git a/src/assets/downloader/tasking/tcpsocket.cpp b/src/assets/downloader/tasking/tcpsocket.cpp new file mode 100644 index 00000000000..20cd8b8e2b8 --- /dev/null +++ b/src/assets/downloader/tasking/tcpsocket.cpp @@ -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 diff --git a/src/assets/downloader/tasking/tcpsocket.h b/src/assets/downloader/tasking/tcpsocket.h new file mode 100644 index 00000000000..9e17d703c9e --- /dev/null +++ b/src/assets/downloader/tasking/tcpsocket.h @@ -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 + +#include + +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 m_socket; + QAbstractSocket::SocketError m_error = QAbstractSocket::UnknownSocketError; +}; + +class TASKING_EXPORT TcpSocketTaskAdapter final : public TaskAdapter +{ +public: + TcpSocketTaskAdapter(); + void start() final; +}; + +using TcpSocketTask = CustomTask; + +} // namespace Tasking + +QT_END_NAMESPACE + +#endif // TASKING_TCPSOCKET_H