Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package qt6-tasktree for openSUSE:Factory checked in at 2026-10-18 19:22:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/qt6-tasktree (Old) and /work/SRC/openSUSE:Factory/.qt6-tasktree.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "qt6-tasktree" Sun Oct 18 19:22:50 2026 rev:4 rq:1384416 version:6.12.0 Changes: -------- --- /work/SRC/openSUSE:Factory/qt6-tasktree/qt6-tasktree.changes 2026-08-21 16:54:18.848064940 +0200 +++ /work/SRC/openSUSE:Factory/.qt6-tasktree.new.3698736/qt6-tasktree.changes 2026-10-18 19:23:19.227749576 +0200 @@ -1,0 +2,6 @@ +Wed Sep 30 16:16:35 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 6.12.0 + https://www.qt.io/blog/qt-6.12-released + +------------------------------------------------------------------- Old: ---- qttasktree-everywhere-src-6.11.2.tar.xz New: ---- qttasktree-everywhere-src-6.12.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ qt6-tasktree.spec ++++++ --- /var/tmp/diff_new_pack.0Vh6AO/_old 2026-10-18 19:23:20.032782912 +0200 +++ /var/tmp/diff_new_pack.0Vh6AO/_new 2026-10-18 19:23:20.034782995 +0200 @@ -16,8 +16,8 @@ # -%define real_version 6.11.2 -%define short_version 6.11 +%define real_version 6.12.0 +%define short_version 6.12 %define tar_name qttasktree-everywhere-src %define tar_suffix %{nil} # @@ -27,7 +27,7 @@ %endif # Name: qt6-tasktree%{?pkg_suffix} -Version: 6.11.2 +Version: 6.12.0 Release: 0 Summary: Generic framework for automatic management of asynchronous tasks License: GPL-2.0-only OR LGPL-3.0-only OR GPL-3.0-only ++++++ qttasktree-everywhere-src-6.11.2.tar.xz -> qttasktree-everywhere-src-6.12.0.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/.cmake.conf new/qttasktree-everywhere-src-6.12.0/.cmake.conf --- old/qttasktree-everywhere-src-6.11.2/.cmake.conf 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/.cmake.conf 2026-09-26 12:11:26.000000000 +0200 @@ -1,10 +1,11 @@ -set(QT_REPO_MODULE_VERSION "6.11.2") +set(QT_REPO_MODULE_VERSION "6.12.0") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") set(QT_REPO_PUBLIC_NAMESPACE_REGEX "^::QtTaskTree.*") set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_ENABLE_STRICT_MODE_UP_TO=QT_VERSION_CHECK(6,11,0)" # not yet in strict mode: "QT_LEAN_HEADERS=1" + "QT_NO_DISCONNECT_CONST_CONNECTION=1" "QT_NO_SINGLE_ARGUMENT_QHASH_OVERLOAD=1" "QT_NO_QPAIR=1" ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/.tag new/qttasktree-everywhere-src-6.12.0/.tag --- old/qttasktree-everywhere-src-6.11.2/.tag 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/.tag 2026-09-26 12:11:26.000000000 +0200 @@ -1 +1 @@ -0944a965a99ccc5dc494c71441e9333f1a9a17d6 +bb7e5587370bb5073e2d47123beb51ffba5c7fcd diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/CONTRIBUTING.md new/qttasktree-everywhere-src-6.12.0/CONTRIBUTING.md --- old/qttasktree-everywhere-src-6.11.2/CONTRIBUTING.md 1970-01-01 01:00:00.000000000 +0100 +++ new/qttasktree-everywhere-src-6.12.0/CONTRIBUTING.md 2026-09-26 12:11:26.000000000 +0200 @@ -0,0 +1,11 @@ +## Qt contribution guidelines + +We welcome contributions to Qt! + +Note that we cannot accept pull requests on GitHub. All contributions +to the Qt project are exclusively handled through the +[Gerrit code review system](https://codereview.qt-project.org). + +See +[contribute.qt-project.org](https://contribute.qt-project.org/) to learn +more. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/REUSE.toml new/qttasktree-everywhere-src-6.12.0/REUSE.toml --- old/qttasktree-everywhere-src-6.11.2/REUSE.toml 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/REUSE.toml 2026-09-26 12:11:26.000000000 +0200 @@ -28,7 +28,7 @@ SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" [[annotations]] -path = ["**/doc/**"] +path = ["**/doc/**", "CONTRIBUTING.md"] comment = "documentation" precedence = "closest" SPDX-FileCopyrightText = "Copyright (C) The Qt Company Ltd." diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/dependencies.yaml new/qttasktree-everywhere-src-6.12.0/dependencies.yaml --- old/qttasktree-everywhere-src-6.11.2/dependencies.yaml 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/dependencies.yaml 2026-09-26 12:11:26.000000000 +0200 @@ -1,4 +1,4 @@ dependencies: ../qtbase: - ref: ef55f427f2c8b410d34f8a7681020a3000cf6866 + ref: 025bdad181de241e81bf853c8a2d7bf3d19261a9 required: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/licenseRule.json new/qttasktree-everywhere-src-6.12.0/licenseRule.json --- old/qttasktree-everywhere-src-6.11.2/licenseRule.json 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/licenseRule.json 2026-09-26 12:11:26.000000000 +0200 @@ -36,10 +36,10 @@ } }, { - "comment": "Files with the following endings are Documentation licensed.", - "file_pattern_ending": [".qdoc", ".qdocinc" , ".qdocconf", "README", "qt_attribution.json", - "README.md", "README.txt", "config_help.txt", - "READ-THIS-BEFORE-MANUALLY-ADDING-FILES-TO-PACKAGE.txt"], + "comment": "Files with the following endings are documentation licensed.", + "file_pattern_ending": [".qdoc", ".qdocinc" , ".qdocconf", + "README.md", "CONTRIBUTING.md", + "qt_attribution.json"], "location":{ "": { "comment": "", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/src/tasktree/qtasktree.cpp new/qttasktree-everywhere-src-6.12.0/src/tasktree/qtasktree.cpp --- old/qttasktree-everywhere-src-6.11.2/src/tasktree/qtasktree.cpp 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/src/tasktree/qtasktree.cpp 2026-09-26 12:11:26.000000000 +0200 @@ -1466,7 +1466,7 @@ \reentrant The Storage class template is responsible for dynamically creating - and destructing objects of the custom \c StorageStruct type. + and destructing objects of the custom \a StorageStruct type. The creation and destruction are managed by the running task tree. If a Storage object is placed inside a \l {QtTaskTree::} {Group} element, the running task tree creates the \c StorageStruct object @@ -1876,6 +1876,19 @@ */ For::For(const Iterator &iterator) : d(new ForPrivate{iterator}) {} +/*! + \overload + Constructs the For loop element, repeating \a count times. + + This is equivalent to: + \code + For (RepeatIterator(count)) + \endcode + + \sa RepeatIterator +*/ +For::For(qsizetype count) : For(RepeatIterator(count)) {} + Iterator For::iterator() const { return d->m_iterator; @@ -2031,7 +2044,7 @@ The following construction: \code - const ExecutableItem task = Forever { + const Group recipe = Forever { task1, task2 }; @@ -2040,7 +2053,7 @@ is an equivalent to: \code - const ExecutableItem task = For (ForeverIterator()) >> Do { + const Group recipe = For (ForeverIterator()) >> Do { task1, task2 }; @@ -2062,6 +2075,11 @@ addChildren({ For (ForeverIterator()) >> Do { children } } ); } +Forever::operator Group() const +{ + return Group { *this }; +} + /*! Creates QTimeoutTask with \a timeout duration, after which the task finishes with \a result. @@ -2125,27 +2143,36 @@ public: IteratorThreadData() = default; - void pushIteration(qsizetype index) + void push(qsizetype index, void *list) { - m_activeIteratorStack.push_back(index); + m_activeStack.push_back({index, list}); } - void popIteration() + void pop() { - QT_TASKTREE_ASSERT(m_activeIteratorStack.size(), return); - m_activeIteratorStack.pop_back(); + QT_TASKTREE_ASSERT(m_activeStack.size(), return); + m_activeStack.pop_back(); } qsizetype iteration() const { - QT_TASKTREE_ASSERT(m_activeIteratorStack.size(), qWarning( + QT_TASKTREE_ASSERT(m_activeStack.size(), qWarning( "The referenced iterator is not reachable in the running tree. " "A -1 will be returned which might lead to a crash in the calling code. " "It is possible that no iterator was added to the tree, " "or the iterator is not reachable from where it is referenced."); return -1); - return m_activeIteratorStack.last(); + return m_activeStack.last().iterationIndex; + } + void *activeList() const + { + QT_TASKTREE_ASSERT(m_activeStack.size(), return nullptr); + return m_activeStack.last().list; } private: - QList<qsizetype> m_activeIteratorStack; + struct Entry { + qsizetype iterationIndex; + void *list; + }; + QList<Entry> m_activeStack; }; class IteratorPrivate : public QSharedData @@ -2159,12 +2186,16 @@ explicit IteratorPrivate(const Iterator::Condition &condition) : m_condition(condition) {} + explicit IteratorPrivate(Iterator::LazyList &&lazyList) + : m_lazyList(std::move(lazyList)) + {} IteratorThreadData &threadData() { return m_threadData.data(); } const std::optional<qsizetype> m_loopCount = std::nullopt; const Iterator::ValueGetter m_valueGetter = {}; const Iterator::Condition m_condition = {}; + const std::optional<Iterator::LazyList> m_lazyList = std::nullopt; LocalThreadStorage<IteratorThreadData> m_threadData = {}; }; @@ -2190,6 +2221,10 @@ : d(new IteratorPrivate{condition}) {} +Iterator::Iterator(LazyList &&lazyList) + : d(new IteratorPrivate{std::move(lazyList)}) +{} + /*! Returns the iteration index of the currently executing handler inside a \l Do body of the \l For (\l Iterator) >> \l Do {} construct. @@ -2261,6 +2296,8 @@ const void *Iterator::valuePtr() const { + if (d->m_lazyList) + return d->m_lazyList->m_valueGetter(d->threadData().activeList(), iteration()); return d->m_valueGetter(iteration()); } @@ -2459,6 +2496,9 @@ \brief The list iterator to be used inside For element. \reentrant + The template parameter \a T specifies the element type of the list + being iterated. + \sa Iterator, ForeverIterator, RepeatIterator, UntilIterator */ @@ -2466,10 +2506,44 @@ \fn template <typename T> ListIterator<T>::ListIterator<T>(const QList<T> &list) Constructs the list iterator for the - For (ListIterator(list)) >> Do {} construct. + \c {For (ListIterator(list)) >> Do {}} construct. The iterator will iterate over each element from the passed \a list. - \sa Iterator::iteration() + \sa Iterator::iteration(), operator*(), operator->() +*/ + +/*! + \fn template <typename T> template <typename RangeGetter, ListIterator<T>::if_compatible_range<RangeGetter> = true, typename RangeType> ListIterator<T>::ListIterator<T>(RangeGetter &&rangeGetter) + + Constructs the list iterator for the + \c {For (ListIterator(rangeGetter)) >> Do {}} construct. + The \a rangeGetter is a callable with no arguments returning a contiguous + range whose \c value_type is \c T. The returned range does not need to be + a QList<T>. Any contiguous range type supporting \c std::data() is accepted + — for example, a getter returning QString is valid for + \c ListIterator<QChar>. + The getter called once when the \l Group containing this iterator is entered, + with all relevant storages and parent iterations activated. + The call might be skipped if the possible Group's onSetupHandler() + returned a value other than SetupResult::Continue. + The iterator will iterate over each element of the returned range. + + This is a convenient pattern for iterating over nested loops: + + \code + const ListIterator outer(QStringList{"12", "34"}); + const ListIterator inner([outer] { return *outer; }); + + const Group recipe { + For (outer) >> Do { + For (inner) >> Do { + QSyncTask([inner] { qDebug() << *inner; }) + } + } + }; + \endcode + + \sa Iterator::iteration(), operator*(), operator->() */ /*! @@ -2492,7 +2566,7 @@ Make sure that ListIterator is passed to the For element. */ -using StoragePtr = void *; +using StoragePtr = std::shared_ptr<void>; static constexpr QLatin1StringView s_activeStorageWarning = "The referenced storage is not reachable in the running tree. " @@ -2532,25 +2606,22 @@ class StorageBasePrivate : public QSharedData { public: - StorageBasePrivate(const StorageBase::StorageConstructor &ctor, - const StorageBase::StorageDestructor &dtor) - : m_constructor(ctor) - , m_destructor(dtor) + StorageBasePrivate(const StorageBase::StorageCreator &creator) + : m_creator(creator) {} StorageThreadData &threadData() { return m_threadData.data(); } - const StorageBase::StorageConstructor m_constructor = {}; - const StorageBase::StorageDestructor m_destructor = {}; + const StorageBase::StorageCreator m_creator = {}; LocalThreadStorage<StorageThreadData> m_threadData = {}; }; -StorageBase::StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor) - : d(new StorageBasePrivate{ctor, dtor}) +StorageBase::StorageBase(const StorageBase::StorageCreator &creator) + : d(new StorageBasePrivate{creator}) {} -StoragePtr StorageBase::activeStorageVoid() const +void *StorageBase::activeStorageVoid() const { - return d->threadData().activeStorage(); + return d->threadData().activeStorage().get(); } class GroupItemPrivate : public QSharedData @@ -2692,7 +2763,7 @@ } break; case Type::TaskHandler: - QT_TASKTREE_ASSERT(child.d->m_taskHandler.m_taskAdapterConstructor, + QT_TASKTREE_ASSERT(child.d->m_taskHandler.m_taskAdapterCreator, qWarning("Task create handler can't be null, skipping..."); return); d->m_children.append(child); break; @@ -3136,7 +3207,7 @@ for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order m_activeStorages[i].d->threadData().popStorage(); for (int i = m_activeIterators.size() - 1; i >= 0; --i) // iterate in reverse order - m_activeIterators[i].d->threadData().popIteration(); + m_activeIterators[i].d->threadData().pop(); QT_TASKTREE_ASSERT(s_activeTaskTrees.size(), return); s_activeTaskTrees.pop_back(); } @@ -3180,7 +3251,7 @@ , m_container(taskTreePrivate, task) {} - bool isTask() const { return bool(m_taskHandler.m_taskAdapterConstructor); } + bool isTask() const { return bool(m_taskHandler.m_taskAdapterCreator); } qsizetype taskCount() const { return isTask() ? 1 : m_container.m_taskCount; } const GroupItem::TaskHandler m_taskHandler; @@ -3219,7 +3290,7 @@ const StorageHandler storageHandler = *it; if (storageHandler.*ptr) { GuardLocker locker(m_guard); - (storageHandler.*ptr)(storagePtr); + (storageHandler.*ptr)(storagePtr.get()); } } @@ -3323,12 +3394,13 @@ ~RuntimeContainer() { + if (!m_callStorageDoneHandlersOnDestruction) + return; + for (int i = m_containerNode.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order const StorageBase storage = m_containerNode.m_storageList[i]; StoragePtr storagePtr = m_storages.value(i); - if (m_callStorageDoneHandlersOnDestruction) - m_containerNode.m_taskTreePrivate->callDoneHandler(storage, storagePtr); - storage.d->m_destructor(storagePtr); + m_containerNode.m_taskTreePrivate->callDoneHandler(storage, storagePtr); } } @@ -3354,6 +3426,7 @@ int m_nextToStart = 0; int m_runningChildren = 0; bool m_shouldIterate = true; + std::shared_ptr<void> m_lazyList; std::vector<std::unique_ptr<RuntimeIteration>> m_iterations; // Owning. }; @@ -3361,14 +3434,11 @@ { public: TaskInterfaceAdapter(const GroupItem::TaskHandler &taskHandler) - : m_taskAdapter(taskHandler.m_taskAdapterConstructor()) - , m_taskAdapterDestructor(taskHandler.m_taskAdapterDestructor) + : m_taskAdapter(taskHandler.m_taskAdapterCreator()) {} - ~TaskInterfaceAdapter() { m_taskAdapterDestructor(m_taskAdapter); } QTaskInterface m_taskInterface; - GroupItem::TaskAdapterPtr m_taskAdapter = nullptr; // Owning. - GroupItem::TaskAdapterDestructor m_taskAdapterDestructor; + std::shared_ptr<void> m_taskAdapter; }; class RuntimeTask @@ -3416,7 +3486,8 @@ { const std::optional<Iterator> it = iteration->iterator(); if (it) { - it->d->threadData().pushIteration(iteration->m_iterationIndex); + it->d->threadData().push(iteration->m_iterationIndex, + iteration->m_container->m_lazyList.get()); m_activeIterators.append(*it); } activateContext(iteration->m_container); @@ -3547,7 +3618,7 @@ { QList<StoragePtr> storages; for (const StorageBase &storage : container.m_storageList) { - StoragePtr storagePtr = storage.d->m_constructor(); + StoragePtr storagePtr = storage.d->m_creator(); storages.append(storagePtr); container.m_taskTreePrivate->callSetupHandler(storage, storagePtr); } @@ -3732,6 +3803,10 @@ const IteratorPrivate *p = container->m_containerNode.m_iterator->d.get(); if (p->m_loopCount) { container->m_shouldIterate = container->m_iterationCount < p->m_loopCount; + } else if (p->m_lazyList) { + QT_TASKTREE_CHECK(container->m_lazyList); + const qsizetype size = p->m_lazyList->m_listSizer(container->m_lazyList.get()); + container->m_shouldIterate = container->m_iterationCount < size; } else if (p->m_condition) { container->m_shouldIterate = invokeHandler(container, p->m_condition, container->m_iterationCount); @@ -3755,6 +3830,13 @@ container->m_successBit = container->m_parentTask->m_setupResult == SetupResult::StopWithSuccess; } } + if (container->m_parentTask->m_setupResult == SetupResult::Continue) { + if (containerNode.m_iterator) { + const IteratorPrivate *iterData = containerNode.m_iterator->d.get(); + if (iterData->m_lazyList) + container->m_lazyList = invokeHandler(container, iterData->m_lazyList->m_listGetter); + } + } continueContainer(container); return; } @@ -3762,7 +3844,8 @@ const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler; node->m_taskInterfaceAdapter.reset(new TaskInterfaceAdapter(handler)); node->m_setupResult = handler.m_taskAdapterSetupHandler - ? invokeHandler(node->m_parentIteration, handler.m_taskAdapterSetupHandler, node->m_taskInterfaceAdapter->m_taskAdapter) + ? invokeHandler(node->m_parentIteration, handler.m_taskAdapterSetupHandler, + node->m_taskInterfaceAdapter->m_taskAdapter.get()) : SetupResult::Continue; if (node->m_setupResult != SetupResult::Continue) { if (node->m_parentIteration->m_isProgressive) @@ -3783,7 +3866,7 @@ childDone(parentIteration, result); bumpAsyncCount(); }, Qt::SingleShotConnection); - handler.m_taskAdapterStarter(node->m_taskInterfaceAdapter->m_taskAdapter, + handler.m_taskAdapterStarter(node->m_taskInterfaceAdapter->m_taskAdapter.get(), &node->m_taskInterfaceAdapter->m_taskInterface); } @@ -3809,7 +3892,7 @@ const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler; if (handler.m_taskAdapterDoneHandler && shouldCallDone(handler.m_callDoneFlags, doneWith)) { result = invokeHandler(node->m_parentIteration, handler.m_taskAdapterDoneHandler, - node->m_taskInterfaceAdapter->m_taskAdapter, doneWith); + node->m_taskInterfaceAdapter->m_taskAdapter.get(), doneWith); } if (node->m_parentIteration->m_isProgressive) advanceProgress(1); @@ -3865,6 +3948,9 @@ \brief A class template providing default task adapter used in QCustomTask. \reentrant + QDefaultTaskAdapter\<Task\> is a template class where \a Task specifies + the task type to adapt. + The QDefaultTaskAdapter adapting Task may be used when the following conditions are met: @@ -3891,7 +3977,7 @@ Describes custom task items within task tree recipes. Custom task names are aliased with unique names using the - QCustomTask template with a given Task, Adapter and Deleter. + QCustomTask template with a given \a Task, \a Adapter and \a Deleter. For example, QThreadFunctionTask<T> is an alias to the QCustomTask that is defined to work with QThreadFunction<T> as an associated task class. The following table contains custom tasks provided by the TaskTree library diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/src/tasktree/qtasktree.h new/qttasktree-everywhere-src-6.12.0/src/tasktree/qtasktree.h --- old/qttasktree-everywhere-src-6.11.2/src/tasktree/qtasktree.h 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/src/tasktree/qtasktree.h 2026-09-26 12:11:26.000000000 +0200 @@ -140,16 +140,25 @@ Q_TASKTREE_EXPORT qsizetype iteration() const; private: + struct LazyList { + std::function<std::shared_ptr<void>()> m_listGetter; + const void *(*m_valueGetter)(const void *, qsizetype); + qsizetype (*m_listSizer)(const void *); + }; + Q_TASKTREE_EXPORT Iterator(); // ForeverIterator - Q_TASKTREE_EXPORT Iterator(qsizetype count, const ValueGetter &valueGetter = {}); // RepeatIterator, ListIterator + Q_TASKTREE_EXPORT Iterator(qsizetype count, const ValueGetter &valueGetter = {}); // RepeatIterator, ListIterator (direct list) Q_TASKTREE_EXPORT Iterator(const Condition &condition); // UntilIterator + Q_TASKTREE_EXPORT Iterator(LazyList &&lazyList); // ListIterator (LazyList) Q_TASKTREE_EXPORT const void *valuePtr() const; friend class ExecutionContextActivator; - friend class QTaskTreePrivate; friend class ForeverIterator; + friend class IteratorPrivate; + friend class QTaskTreePrivate; friend class RepeatIterator; + friend class RuntimeContainer; friend class UntilIterator; template <typename T> friend class ListIterator; QExplicitlySharedDataPointer<IteratorPrivate> d; @@ -176,24 +185,56 @@ template <typename T> class ListIterator final : public Iterator { + // std::void_t makes the specialisation a SFINAE failure (rather than a hard error) when + // G is not invocable, its return type has no value_type matching T, or it is not a + // contiguous range (std::data() not supported). + template <typename, typename = void> + struct is_compatible_range_getter : std::false_type {}; + template <typename G> + struct is_compatible_range_getter<G, std::void_t< + std::enable_if_t<std::is_same_v<typename std::invoke_result_t<G>::value_type, T>>, + decltype(std::data(std::declval<const std::invoke_result_t<G>>())) + >> : std::true_type {}; + + template <typename RangeGetter> + using if_compatible_range = std::enable_if_t<is_compatible_range_getter<std::decay_t<RangeGetter>>::value, bool>; + public: explicit ListIterator(const QList<T> &list) : Iterator(list.size(), [list](qsizetype i) { return std::addressof(list.at(i)); }) {} + template <typename RangeGetter, + if_compatible_range<RangeGetter> = true, + typename RangeType = std::invoke_result_t<std::decay_t<RangeGetter>>> + explicit ListIterator(RangeGetter &&rangeGetter) + : Iterator({ + [rangeGetter = std::forward<RangeGetter>(rangeGetter)] { + return std::make_shared<RangeType>(rangeGetter()); + }, + [](const void *list, qsizetype i) -> const void * { + return std::data(*static_cast<const RangeType *>(list)) + i; + }, + [](const void *list) -> qsizetype { + return static_cast<const RangeType *>(list)->size(); + }, + }) {} const T *operator->() const { return static_cast<const T *>(valuePtr()); } const T &operator*() const { return *static_cast<const T *>(valuePtr()); } }; +// C++17 deduction guide: deduces T from RangeGetter's return type value_type. +template <typename RangeGetter> +ListIterator(RangeGetter &&) -> ListIterator<typename std::invoke_result_t<RangeGetter>::value_type>; + class StorageBase { public: QT_TASKTREE_DECLARE_SMFS(StorageBase, Q_TASKTREE_EXPORT) private: - using StorageConstructor = std::function<void *(void)>; - using StorageDestructor = std::function<void(void *)>; + using StorageCreator = std::function<std::shared_ptr<void>()>; using StorageHandler = std::function<void(void *)>; - Q_TASKTREE_EXPORT StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor); + Q_TASKTREE_EXPORT StorageBase(const StorageCreator &creator); Q_TASKTREE_EXPORT void *activeStorageVoid() const; @@ -220,23 +261,17 @@ class Storage final : public StorageBase { public: - Storage() : StorageBase(Storage::ctor(), Storage::dtor()) {} + Storage() : StorageBase([] { return std::make_shared<StorageStruct>(); }) {} template <typename FirstArg, typename ...Args, std::enable_if_t<!std::is_same_v<q20::remove_cvref_t<FirstArg>, Storage<StorageStruct>>, bool> = true> explicit Storage(const FirstArg &firstArg, const Args &...args) - : StorageBase([=] { return new StorageStruct{firstArg, args...}; }, Storage::dtor()) {} + : StorageBase([=] { return std::make_shared<StorageStruct>(firstArg, args...); }) {} StorageStruct &operator*() const noexcept { return *activeStorage(); } StorageStruct *operator->() const noexcept { return activeStorage(); } StorageStruct *activeStorage() const { return static_cast<StorageStruct *>(activeStorageVoid()); } - -private: - static StorageConstructor ctor() { return [] { return new StorageStruct(); }; } - static StorageDestructor dtor() { - return [](void *storage) { delete static_cast<StorageStruct *>(storage); }; - } }; class GroupItem @@ -261,9 +296,7 @@ using TaskAdapterPtr = void *; // Internal, provided by QCustomTask - using TaskAdapterConstructor = std::function<TaskAdapterPtr(void)>; - // Internal, provided by QCustomTask - using TaskAdapterDestructor = std::function<void(TaskAdapterPtr)>; + using TaskAdapterCreator = std::function<std::shared_ptr<void>(void)>; // using TaskAdapterStarter = std::function<void(TaskAdapterPtr, QTaskInterface *)>; // Called prior to task start, just after createHandler @@ -272,8 +305,7 @@ using TaskAdapterDoneHandler = std::function<DoneResult(TaskAdapterPtr, DoneWith)>; struct TaskHandler { // TODO: Make it a pimpled value type? - TaskAdapterConstructor m_taskAdapterConstructor; - TaskAdapterDestructor m_taskAdapterDestructor; + TaskAdapterCreator m_taskAdapterCreator; TaskAdapterStarter m_taskAdapterStarter; TaskAdapterSetupHandler m_taskAdapterSetupHandler = {}; TaskAdapterDoneHandler m_taskAdapterDoneHandler = {}; @@ -516,6 +548,7 @@ { public: Q_TASKTREE_EXPORT explicit For(const Iterator &loop); + Q_TASKTREE_EXPORT explicit For(qsizetype count); QT_TASKTREE_DECLARE_SMFS(For, Q_TASKTREE_EXPORT) @@ -549,6 +582,8 @@ public: Q_TASKTREE_EXPORT explicit Forever(const GroupItems &children); Q_TASKTREE_EXPORT explicit Forever(std::initializer_list<GroupItem> children); + + Q_TASKTREE_EXPORT operator Group() const; }; Q_TASKTREE_EXPORT ExecutableItem timeoutTask(const std::chrono::milliseconds &timeout, @@ -656,7 +691,7 @@ explicit QCustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(), CallDone callDone = CallDoneFlag::Always) - : ExecutableItem(TaskHandler{&taskAdapterConstructor, &taskAdapterDestructor, &taskAdapterStarter, + : ExecutableItem(TaskHandler{&taskAdapterCreator, &taskAdapterStarter, wrapSetup(std::forward<SetupHandler>(setup)), wrapDone(std::forward<DoneHandler>(done)), callDone}) {} @@ -681,11 +716,7 @@ Adapter adapter; }; - static TaskAdapter *taskAdapterConstructor() { return new TaskAdapter; } - - static void taskAdapterDestructor(TaskAdapterPtr voidAdapter) { - delete static_cast<TaskAdapter *>(voidAdapter); - } + static std::shared_ptr<void> taskAdapterCreator() { return std::make_shared<TaskAdapter>(); } static void taskAdapterStarter(TaskAdapterPtr voidAdapter, QTaskInterface *iface) { TaskAdapter *taskAdapter = static_cast<TaskAdapter *>(voidAdapter); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/src/tasktree/qtasktreerunner.cpp new/qttasktree-everywhere-src-6.12.0/src/tasktree/qtasktreerunner.cpp --- old/qttasktree-everywhere-src-6.11.2/src/tasktree/qtasktreerunner.cpp 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/src/tasktree/qtasktreerunner.cpp 2026-09-26 12:11:26.000000000 +0200 @@ -363,7 +363,7 @@ \inmodule QtTaskTree \brief A mapped task tree execution controller with a given Key type. - Manages mapped task tree execution using \c Key type. + Manages mapped task tree execution using the \a Key type. Use the start() method to execute a recipe for a given key unconditionally, resetting a possibly running task tree with the same key, and keeping other possibly running task trees with different keys diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/src/tasktree/qthreadfunctiontask.cpp new/qttasktree-everywhere-src-6.12.0/src/tasktree/qthreadfunctiontask.cpp --- old/qttasktree-everywhere-src-6.11.2/src/tasktree/qthreadfunctiontask.cpp 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/src/tasktree/qthreadfunctiontask.cpp 2026-09-26 12:11:26.000000000 +0200 @@ -85,7 +85,8 @@ separate thread via QtConcurrent::run(). \reentrant - A QThreadFunction is a convenient class template that combines a stored + A QThreadFunction is a convenient class template, where \a ResultType + specifies the return type of the stored function, that combines a stored function to be called later via QtConcurrent::run() with an internal QFutureWatcher monitoring the function execution. Use QThreadFunctionTask to execute the QThreadFunction inside the QTaskTree recipe. @@ -391,7 +392,8 @@ \typedef QtTaskTree::QThreadFunctionTask \relates QtTaskTree::QCustomTask - Type alias for the QCustomTask<QThreadFunction<ResultType>>, + Type alias for QCustomTask<QThreadFunction<ResultType>>, + where \a ResultType specifies the return type of the thread function, to be used inside recipes. */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qttasktree-everywhere-src-6.11.2/tests/auto/tasktree/tst_qttasktree.cpp new/qttasktree-everywhere-src-6.12.0/tests/auto/tasktree/tst_qttasktree.cpp --- old/qttasktree-everywhere-src-6.11.2/tests/auto/tasktree/tst_qttasktree.cpp 2026-08-11 19:38:16.000000000 +0200 +++ new/qttasktree-everywhere-src-6.12.0/tests/auto/tasktree/tst_qttasktree.cpp 2026-09-26 12:11:26.000000000 +0200 @@ -54,6 +54,7 @@ Timeout, Storage, Iteration, + IteratorRangeGetter, }; Q_ENUM_NS(Handler); @@ -102,6 +103,54 @@ std::atomic_int CustomStorage::s_count = 0; +// Compile-time CTAD tests for ListIterator. +// Verifies that the deduction guide's forwarding-reference overload does not steal +// QList arguments from the const QList<T> & constructor (the underconstrained-guide hazard). +namespace { +[[maybe_unused]] void testListIteratorCTAD() +{ + // --- QList constructor overload --- + + // const lvalue: baseline case. + const QList<int> constList{1, 2, 3}; + auto a = ListIterator(constList); + static_assert(std::is_same_v<decltype(a), ListIterator<int>>); + + // non-const lvalue: without the enable_if constraint on the deduction guide, + // ListGetter&& would be a perfect match and beat const QList<T> &. + QList<int> mutableList{1, 2, 3}; + auto b = ListIterator(mutableList); + static_assert(std::is_same_v<decltype(b), ListIterator<int>>); + + // rvalue: same hazard as non-const lvalue. + auto c = ListIterator(QList<int>{1, 2, 3}); + static_assert(std::is_same_v<decltype(c), ListIterator<int>>); + + // Different value types. + auto d = ListIterator(QList<QString>{"a", "b"}); + static_assert(std::is_same_v<decltype(d), ListIterator<QString>>); + + auto e = ListIterator(QList<double>{1.0, 2.0}); + static_assert(std::is_same_v<decltype(e), ListIterator<double>>); + + // --- Deduction guide (callable) overload --- + + // Lvalue lambda: T deduced from the return type's value_type. + auto getter = [] { return QList<int>{1, 2, 3}; }; + auto f = ListIterator(getter); + static_assert(std::is_same_v<decltype(f), ListIterator<int>>); + + // Rvalue (inline) lambda. + auto g = ListIterator([] { return QList<QString>{"a"}; }); + static_assert(std::is_same_v<decltype(g), ListIterator<QString>>); + + // std::function. + std::function<QList<double>()> fn = [] { return QList<double>{1.0}; }; + auto h = ListIterator(fn); + static_assert(std::is_same_v<decltype(h), ListIterator<double>>); +} +} // unnamed namespace + struct TestData { Storage<CustomStorage> storage; @@ -217,6 +266,22 @@ }.withTimeout(1s) }; + const Group group6 = Forever { + TestTask() + }; + + const Group group7 = Forever { + TestTask() + }.withTimeout(1s); + + using GroupGetter = std::function<Group()>; + + const GroupGetter getter = [] { + return Forever { + TestTask() + }; + }; + // When turning each of below blocks on, you should see the specific compiler error message. // Sync handler needs to take no arguments and has to return void or bool. @@ -3046,6 +3111,10 @@ sequential, successItems }; + const Group rootSequentialSuccess2 = For (2) >> Do { + sequential, + successItems + }; const Log logSequentialSuccess { {1, Handler::Setup}, {1, Handler::Success}, @@ -3061,6 +3130,10 @@ parallel, successItems }; + const Group rootParallelSuccess2 = For (2) >> Do { + parallel, + successItems + }; const Log logParallelSuccess { {1, Handler::Setup}, {2, Handler::Setup}, @@ -3076,6 +3149,10 @@ ParallelLimit(2), successItems }; + const Group rootParallelLimitSuccess2 = For (2) >> Do { + ParallelLimit(2), + successItems + }; const Log logParallelLimitSuccess { {1, Handler::Setup}, {2, Handler::Setup}, @@ -3097,6 +3174,10 @@ sequential, errorItems }; + const Group rootSequentialError2 = For (2) >> Do { + sequential, + errorItems + }; const Log logSequentialError { {1, Handler::Setup}, {1, Handler::Success}, @@ -3108,6 +3189,10 @@ parallel, errorItems }; + const Group rootParallelError2 = For (2) >> Do { + parallel, + errorItems + }; const Log logParallelError { {1, Handler::Setup}, {2, Handler::Setup}, @@ -3123,6 +3208,10 @@ ParallelLimit(2), errorItems }; + const Group rootParallelLimitError2 = For (2) >> Do { + ParallelLimit(2), + errorItems + }; const Log logParallelLimitError { {1, Handler::Setup}, {2, Handler::Setup}, @@ -3144,6 +3233,19 @@ << TestData{storage, rootParallelError, logParallelError, 4, DoneWith::Error, 2}; QTest::newRow("RepeatParallelLimitError") << TestData{storage, rootParallelLimitError, logParallelLimitError, 4, DoneWith::Error, 2}; + + QTest::newRow("NumberSequentialSuccess") + << TestData{storage, rootSequentialSuccess2, logSequentialSuccess, 4, DoneWith::Success, 4}; + QTest::newRow("NumberParallelSuccess") + << TestData{storage, rootParallelSuccess2, logParallelSuccess, 4, DoneWith::Success, 4}; + QTest::newRow("NumberParallelLimitSuccess") + << TestData{storage, rootParallelLimitSuccess2, logParallelLimitSuccess, 4, DoneWith::Success, 4}; + QTest::newRow("NumberSequentialError") + << TestData{storage, rootSequentialError2, logSequentialError, 4, DoneWith::Error, 2}; + QTest::newRow("NumberParallelError") + << TestData{storage, rootParallelError2, logParallelError, 4, DoneWith::Error, 2}; + QTest::newRow("NumberParallelLimitError") + << TestData{storage, rootParallelLimitError2, logParallelLimitError, 4, DoneWith::Error, 2}; } { @@ -3585,6 +3687,115 @@ } { + // Check if ListIterator work with QList. + const auto recipe = [storage](SetupResult result) { + const ListIterator iterator([storage] { + storage->m_log.append({0, Handler::IteratorRangeGetter}); + return QList{1, 2, 3}; + }); + + return Group { + storage, + For (iterator) >> Do { + onGroupSetup([result] { return result; }), + QSyncTask([storage, iterator] { + storage->m_log.append({*iterator, Handler::Sync}); + }) + } + }; + }; + + const Log log { + {0, Handler::IteratorRangeGetter}, + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync} + }; + + // Check if ListIterator's getter is called once when the group is entered, + // with storage accessible, and the returned list is used for iteration. + QTest::newRow("LazyListGetterExecutedOnceOnContinue") + << TestData{storage, recipe(SetupResult::Continue), log, 0, DoneWith::Success, 0}; + + // Check if ListIterator's getter is skipped when the group's setup handler + // returns a value other than SetupResult::Continue. + QTest::newRow("LazyListGetterSkippedOnStopWithSuccess") + << TestData{storage, recipe(SetupResult::StopWithSuccess), Log(), 0, + DoneWith::Success, 0}; + QTest::newRow("LazyListGetterSkippedOnStopWithError") + << TestData{storage, recipe(SetupResult::StopWithError), Log(), 0, + DoneWith::Error, 0}; + } + + { + // Check if ListIterator work with std::vector. + const auto recipe = [storage](SetupResult result) { + const ListIterator iterator([storage] { + storage->m_log.append({0, Handler::IteratorRangeGetter}); + return std::vector{1, 2, 3}; + }); + + return Group { + storage, + For (iterator) >> Do { + onGroupSetup([result] { return result; }), + QSyncTask([storage, iterator] { + storage->m_log.append({*iterator, Handler::Sync}); + }) + } + }; + }; + + const Log log { + {0, Handler::IteratorRangeGetter}, + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync} + }; + + // Check if ListIterator's getter is called once when the group is entered, + // with storage accessible, and the returned list is used for iteration. + QTest::newRow("LazyVectorGetterExecutedOnceOnContinue") + << TestData{storage, recipe(SetupResult::Continue), log, 0, DoneWith::Success, 0}; + + // Check if ListIterator's getter is skipped when the group's setup handler + // returns a value other than SetupResult::Continue. + QTest::newRow("LazyVectorGetterSkippedOnStopWithSuccess") + << TestData{storage, recipe(SetupResult::StopWithSuccess), Log(), 0, + DoneWith::Success, 0}; + QTest::newRow("LazyVectorGetterSkippedOnStopWithError") + << TestData{storage, recipe(SetupResult::StopWithError), Log(), 0, + DoneWith::Error, 0}; + } + + { + // Check if nested ListIterators work: the outer iterates over strings, the inner + // uses the callable overload to iterate over QChars of the current outer string. + const ListIterator outer(QStringList{"12", "34"}); + const ListIterator inner([outer] { return *outer; }); + + const Group root { + storage, + For (outer) >> Do { + For (inner) >> Do { + QSyncTask([storage, inner] { + storage->m_log.append({(*inner).digitValue(), Handler::Sync}); + }) + } + } + }; + + const Log log { + {1, Handler::Sync}, + {2, Handler::Sync}, + {3, Handler::Sync}, + {4, Handler::Sync}, + }; + + QTest::newRow("NestedLazyList") << TestData{storage, root, log, 0, DoneWith::Success, 0}; + } + + { // This test ensures the task done handler or onGroupDone accepts the DoneResult as an // argument.
