Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package qcoro for openSUSE:Factory checked in at 2023-02-09 16:23:24 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/qcoro (Old) and /work/SRC/openSUSE:Factory/.qcoro.new.4462 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "qcoro" Thu Feb 9 16:23:24 2023 rev:7 rq:1063962 version:0.8.0 Changes: -------- --- /work/SRC/openSUSE:Factory/qcoro/qcoro.changes 2022-11-24 12:23:25.877374583 +0100 +++ /work/SRC/openSUSE:Factory/.qcoro.new.4462/qcoro.changes 2023-02-09 16:23:25.174778676 +0100 @@ -1,0 +2,13 @@ +Thu Feb 9 08:26:04 UTC 2023 - Christophe Marin <[email protected]> + +- Update to 0.8.0 + * test: use offscreen QPA for QCoroQuick tests + * Update pymdown-extensions requirement from ~=9.8 to ~=9.9 + * cmake: make sure we explicitly find_package Qt private modules + * Update pygments requirement from ~=2.13 to ~=2.14 + * Implement moveToThread() awaitable + * Implement sleepFor() and sleepUntil() coroutines + * Make QCoro::waitFor() usable with any awaitable + * Fix QCoro::waitFor() for Awaitable with operator co_await + +------------------------------------------------------------------- Old: ---- qcoro-0.7.0.tar.gz New: ---- qcoro-0.8.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ qcoro.spec ++++++ --- /var/tmp/diff_new_pack.MfAXuq/_old 2023-02-09 16:23:28.794797325 +0100 +++ /var/tmp/diff_new_pack.MfAXuq/_new 2023-02-09 16:23:28.798797345 +0100 @@ -36,7 +36,7 @@ %endif # Name: qcoro%{?_pkg_name_suffix} -Version: 0.7.0 +Version: 0.8.0 Release: 0 Summary: Coroutines for Qt License: MIT ++++++ qcoro-0.7.0.tar.gz -> qcoro-0.8.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/.github/workflows/build-linux.yml new/qcoro-0.8.0/.github/workflows/build-linux.yml --- old/qcoro-0.7.0/.github/workflows/build-linux.yml 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/.github/workflows/build-linux.yml 2023-01-31 20:50:01.000000000 +0100 @@ -4,6 +4,8 @@ push: branches: - main + paths-ignore: + - 'docs/**' pull_request: types: [opened, synchronize, reopened, edited] @@ -75,7 +77,7 @@ shell: bash run: | cd build - QT_LOGGING_TO_CONSOLE=1 QT_QPA_PLATFORM=offscreen ctest -C $BUILD_TYPE \ + QT_LOGGING_TO_CONSOLE=1 ctest -C $BUILD_TYPE \ --output-on-failure \ --verbose \ --output-junit ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/.github/workflows/build-macos.yml new/qcoro-0.8.0/.github/workflows/build-macos.yml --- old/qcoro-0.7.0/.github/workflows/build-macos.yml 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/.github/workflows/build-macos.yml 2023-01-31 20:50:01.000000000 +0100 @@ -4,6 +4,8 @@ push: branches: - main + paths-ignore: + - 'docs/**' pull_request: types: [opened, synchronize, reopened, edited] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/.github/workflows/build-windows.yml new/qcoro-0.8.0/.github/workflows/build-windows.yml --- old/qcoro-0.7.0/.github/workflows/build-windows.yml 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/.github/workflows/build-windows.yml 2023-01-31 20:50:01.000000000 +0100 @@ -4,6 +4,8 @@ push: branches: - main + paths-ignore: + - 'docs/**' pull_request: types: [opened, synchronize, reopened, edited] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/.github/workflows/update-docs.yml new/qcoro-0.8.0/.github/workflows/update-docs.yml --- old/qcoro-0.7.0/.github/workflows/update-docs.yml 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/.github/workflows/update-docs.yml 2023-01-31 20:50:01.000000000 +0100 @@ -1,4 +1,4 @@ -name: Generate documentation +name: Build and Deploy Documentation on: push: @@ -7,12 +7,17 @@ paths: - 'mkdocs.yml' - 'docs/**' + - '.github/workflows/update_docs.yml' workflow_dispatch: jobs: - update-docs: + build-and-deploy: + name: Build and Deploy runs-on: ubuntu-latest + environment: + name: cloudflare-pages + url: ${{ steps.deploy.outputs.url }} steps: - uses: actions/checkout@v2 - name: Setup Python @@ -37,11 +42,15 @@ g.type='text/javascript'; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); })();</script><!-- End Matomo Code -->{% endblock %}" > docs/overrides/main.html mkdocs build --verbose - - name: Deploy Github Pages - uses: peaceiris/actions-gh-pages@v3 + - name: Deploy to Cloudflare Pages + id: deploy + uses: cloudflare/pages-action@1 + env: + CLOUDFLARE_ACCOUNT_ID: ${{ SECRETS.CLOUDFLARE_ACCOUNT_ID }} with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./site - cname: qcoro.dvratil.cz - exclude_assets: '__pycache__' + apiToken: ${{ SECRETS.CLOUDFLARE_PAGES_TOKEN }} + accountId: ${{ SECRETS.CLOUDFLARE_ACCOUNT_ID }} + projectName: ${{ vars.CLOUDFLARE_PAGES_NAME }} + directory: ./site + githubToken: ${{ SECRETS.GITHUB_TOKEN }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/CMakeLists.txt new/qcoro-0.8.0/CMakeLists.txt --- old/qcoro-0.7.0/CMakeLists.txt 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/CMakeLists.txt 2023-01-31 20:50:01.000000000 +0100 @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.18.4) -set(qcoro_VERSION 0.7.0) +set(qcoro_VERSION 0.8.0) set(qcoro_SOVERSION 0) project(qcoro LANGUAGES CXX VERSION ${qcoro_VERSION}) @@ -44,6 +44,8 @@ include(cmake/CheckAtomic.cmake) set(REQUIRED_QT_COMPONENTS Core) +set(REQUIRED_QT5_COMPONENTS) +set(REQUIRED_QT6_COMPONENTS) if (QCORO_WITH_QTDBUS) list(APPEND REQUIRED_QT_COMPONENTS DBus) endif() @@ -54,10 +56,12 @@ list(APPEND REQUIRED_QT_COMPONENTS WebSockets) endif() if (QCORO_WITH_QTQUICK) - list(APPEND REQUIRED_QT_COMPONENTS Quick) + list(APPEND REQUIRED_QT_COMPONENTS Quick QuickPrivate) endif() if (QCORO_WITH_QML) list(APPEND REQUIRED_QT_COMPONENTS Qml) + # Qt6 needs access to private API + list(APPEND REQUIRED_QT6_COMPONENTS QmlPrivate) endif() if (QCORO_BUILD_EXAMPLES) list(APPEND REQUIRED_QT_COMPONENTS Widgets Concurrent) @@ -69,21 +73,16 @@ set(MIN_REQUIRED_QT5_VERSION "5.12") set(MIN_REQUIRED_QT6_VERSION "6.2.0") - -if (NOT USE_QT_VERSION) - # FIXME: find_package(QT NAMES Qt6 Qt5 ...) seems to prefer Qt5 on my system - find_package(Qt6 ${MIN_REQUIRED_QT6_VERSION} COMPONENTS ${REQUIRED_QT_COMPONENTS}) - if (NOT Qt6_FOUND) - find_package(Qt5 ${MIN_REQUIRED_QT5_VERSION} COMPONENTS ${REQUIRED_QT_COMPONENTS} REQUIRED) - set(QT_VERSION_MAJOR 5) - else() - set(QT_VERSION_MAJOR 6) - endif() -else() - find_package(Qt${USE_QT_VERSION} ${MIN_REQUIRED_QT${USE_QT_VERSION}_VERSION} - COMPONENTS ${REQUIRED_QT_COMPONENTS} REQUIRED) - set(QT_VERSION_MAJOR ${USE_QT_VERSION}) -endif() +include(cmake/QCoroFindQt.cmake) +# Find Qt. If USE_QT_VERSION is not set, it will try to look for Qt6 first +# and fallback to Qt5 otherwise. +qcoro_find_qt( + QT_VERSION "${USE_QT_VERSION}" + COMPONENTS "${REQUIRED_QT_COMPONENTS}" + QT5_COMPONENTS "${REQUIRED_QT5_COMPONENTS}" + QT6_COMPONENTS "${REQUIRED_QT6_COMPONENTS}" + FOUND_VER_VAR QT_VERSION_MAJOR +) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config.cmake.in diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/README.md new/qcoro-0.8.0/README.md --- old/qcoro-0.7.0/README.md 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/README.md 2023-01-31 20:50:01.000000000 +0100 @@ -1,7 +1,7 @@ [](https://github.com/danvratil/qcoro/actions/workflows/build-linux.yml) [](https://github.com/danvratil/qcoro/actions/workflows/build-windows.yml) [](https://github.com/danvratil/qcoro/actions/workflows/build-macos.yml) -[](https://github.com/danvratil/qcoro/actions/workflows/update-docs.yml) +[](https://github.com/danvratil/qcoro/actions/workflows/update-docs.yml) [](https://github.com/danvratil/qcoro/releases)   diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/cmake/QCoroFindQt.cmake new/qcoro-0.8.0/cmake/QCoroFindQt.cmake --- old/qcoro-0.7.0/cmake/QCoroFindQt.cmake 1970-01-01 01:00:00.000000000 +0100 +++ new/qcoro-0.8.0/cmake/QCoroFindQt.cmake 2023-01-31 20:50:01.000000000 +0100 @@ -0,0 +1,28 @@ +macro(qcoro_find_qt) + set(options) + set(oneValueArgs QT_VERSION FOUND_VER_VAR) + set(multiValueArgs COMPONENTS QT5_COMPONENTS QT6_COMPONENTS) + cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT ARGS_QT_VERSION) + find_package(Qt6Core QUIET) + if (Qt6Core_FOUND) + set(ARGS_QT_VERSION 6) + else() + set(ARGS_QT_VERSION 5) + endif() + endif() + + foreach (component IN LISTS ARGS_COMPONENTS ARGS_QT${ARGS_QT_VERSION}_COMPONENTS) + message(STATUS "Qt component: ${component}") + if ("${component}" MATCHES "Private$$") + string(REPLACE "Private" "" base_component "${component}") + find_package(Qt${ARGS_QT_VERSION}${base_component} REQUIRED COMPONENTS Private) + else() + find_package(Qt${ARGS_QT_VERSION}${component} REQUIRED) + endif() + endforeach() + + set(${ARGS_FOUND_VER_VAR} ${ARGS_QT_VERSION}) +endmacro() + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/docs/changelog.md new/qcoro-0.8.0/docs/changelog.md --- old/qcoro-0.7.0/docs/changelog.md 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/docs/changelog.md 2023-01-31 20:50:01.000000000 +0100 @@ -10,6 +10,12 @@ # Changelog +## 0.7.0 (2022-11-20) + +* [Release announcement](news/2022/2022-11-17-qcoro-0.7.0-announcement.md) +* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.7.0) + + ## 0.6.0 (2022-07-09) * [Release announcement](news/2022/2022-07-09-qcoro-0.6.0-announcement.md) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/docs/news/2023/2023-01-31-qcoro-0.8.0-announcement.md new/qcoro-0.8.0/docs/news/2023/2023-01-31-qcoro-0.8.0-announcement.md --- old/qcoro-0.7.0/docs/news/2023/2023-01-31-qcoro-0.8.0-announcement.md 1970-01-01 01:00:00.000000000 +0100 +++ new/qcoro-0.8.0/docs/news/2023/2023-01-31-qcoro-0.8.0-announcement.md 2023-01-31 20:50:01.000000000 +0100 @@ -0,0 +1,87 @@ +--- +title: QCoro 0.8.0 Release Announcement +date: "2023-01-31" +description: > + Improved QCoro::waitFor(), new sleepFor() and sleepUntil() helper functions and + improved thread support. +--- + +<!-- +SPDX-FileCopyrightText: 2022 Daniel Vrátil <[email protected]> + +SPDX-License-Identifier: GFDL-1.3-or-later +--> + +# QCoro 0.8.0 Release Announcement + +This is a rather small release with only two new features and one small improvement. + +Big thank you to [Xstrahl Inc.](https://xstrahl.com) who sponsored development of +new features included in this release and of QCoro in general. + +And as always, thank you to everyone who reported issues and contributed to QCoro. +Your help is much appreciated! + +## Improved `QCoro::waitFor()` + +Up until this version, `QCoro::waitFor()` was only usable for `QCoro::Task<T>`. +Starting with QCoro 0.8.0, it is possible to use it with any type that satisfies +the `Awaitable` concept. The concept has also been fixed to satisfies not just +types with the `await_resume()`, `await_suspend()` and `await_ready()` member functions, +but also types with member `operator co_await()` and non-member `operator co_await()` +functions. + +## `QCoro::sleepFor()` and `QCoro::sleepUntil()` + +Working both on QCoro codebase as well as some third-party code bases using QCoro +it's clear that there's a usecase for a simple coroutine that will sleep for +specified amount of time (or until a specified timepoint). It is especially useful +in tests, where simulating delays, especially in asynchronous code is common. + +Previously I used to create small coroutines like this: + +```cpp +QCoro::Task<> timer(std::chrono::milliseconds timeout) { + QTimer timer; + timer.setSingleShot(true); + timer.start(timeout); + co_await timer; +} +``` + +Now we can do the same simply by using `QCoro::sleepFor()`. + +Read the [documentation for `QCoro::sleepFor()`](https://qcoro.dvratil.cz/reference/core/qtimer/#qcorosleepfor) +and [`QCoro::sleepUntil()`](https://qcoro.dvratil.cz/reference/core/qtimer/#qcorosleepuntil) for more details. + +## `QCoro::moveToThread()` + +A small helper coroutine that allows a piece of function to be executed in the context +of another thread. + +```cpp +void App::runSlowOperation(QThread *helperThread) { + // Still on the main thread + ui->statusLabel.setText(tr("Running")); + + const QString input = ui->userInput.text(); + + co_await QCoro::moveToThread(helperThread); + // Now we are running in the context of the helper thread, the main thread is not blocked + + // It is safe to use `input` which was created in another thread + doSomeComplexCalculation(input); + + // Move the execution back to the main thread + co_await QCoro::moveToThread(this->thread()); + // Runs on the main thread again + ui->statusLabel.setText(tr("Done")); +} +``` + +Read the [documentation for `QCoro::moveToThread`](https://qcoro.dvratil.cz/reference/core/qthread#qcoromovetothread) for more details. + +## Full changelog + +[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.8.0) + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/docs/reference/core/qthread.md new/qcoro-0.8.0/docs/reference/core/qthread.md --- old/qcoro-0.7.0/docs/reference/core/qthread.md 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/docs/reference/core/qthread.md 2023-01-31 20:50:01.000000000 +0100 @@ -50,6 +50,19 @@ QCoro::Task<bool> QCoroThread::waitForFinished(std::chrono::milliseconds timeout); ``` +## `QCoro::moveToThread()` + +A helper coroutine that allows changing the thread context in which the coroutine +code is currently being executed. + +When `co_await`ed, the current coroutine will be suspended on the current thread and +immediately resumed again, but in the context of the thread that was passed in as +an argument. + +```cpp +QCoro::Task<> QCoro::moveToThread(QThread *thread); +``` + ## Examples ```cpp @@ -58,6 +71,6 @@ [qtdoc-qthread]: https://doc.qt.io/qt-5/qthread.html -[qtdoc-qthread-waitForStarted]: https://doc.qt.io/qt-5/qthread.html#waitForStarted -[qtdoc-qthread-waitForFiished]: https://doc.qt.io/qt-5/qthread.html#waitForFinished +[qtdoc-qthread-started]: https://doc.qt.io/qt-5/qthread.html#started +[qtdoc-qthread-finished]: https://doc.qt.io/qt-5/qthread.html#finished [qcoro-coro]: ../coro/coro.md diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/docs/reference/core/qtimer.md new/qcoro-0.8.0/docs/reference/core/qtimer.md --- old/qcoro-0.7.0/docs/reference/core/qtimer.md 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/docs/reference/core/qtimer.md 2023-01-31 20:50:01.000000000 +0100 @@ -46,5 +46,38 @@ } ``` +## `QCoro::sleepFor()` + +A simple coroutine that will suspend for the specified time duration. Can be quite +useful especially in unit-tests. + +```cpp +template<typename Rep, typename Period> +QCoro::Task<> QCoro::sleepFor(const std::chrono::duration<Rep, Period> &timeout); +``` + +## `QCoro::sleepUntil()` + +A simple coroutine that will suspend until the specified point in time. Can be useful +for scheduling tasks. + +```cpp +template<typename Clock, typename Duration> +QCoro::Task< QCoro::sleepUntil(const std::chrono::time_point<Clock, Duration> &when); +``` + +Example: + +```cpp +const auto now = std::chrono::system_clock::now(); +const auto tomorrow_time = std::chrono::system_clock::to_time_t(now + 86400s); +std::tm *gt = std::gmtime(&tomorrow_time); +gt.tm_hour = 0; +gt.tm_min = 0; +gt.tm_sec = 0; +const auto tomorrow_midnight = std::mktime(>); +co_await QCoro::sleepUntil(std::chrono::system_clock::from_time_t(tomorrow_midnight)); +``` + [qdoc-qtimer]: https://doc.qt.io/qt-5/qtimer.html [qdoc-qtimer-timeout]: https://doc.qt.io/qt-5/qtimer.html#timeout diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/docs/reference/coro/task.md new/qcoro-0.8.0/docs/reference/coro/task.md --- old/qcoro-0.7.0/docs/reference/coro/task.md 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/docs/reference/coro/task.md 2023-01-31 20:50:01.000000000 +0100 @@ -147,14 +147,17 @@ until the coroutine finishes. If the coroutine has a non-void return value, the value is returned from `waitFor().` +Since QCoro 0.8.0 it is possible to use `QCoro::waitFor()` with any awaitable type, not just `QCoro::Task<T>`. + ```cpp QCoro::Task<int> computeAnswer() { - std::this_thread::sleep_for(std::chrono::year{7'500'000}); + co_await QCoro::sleepFor(std::chrono::years{7'500'00}); co_return 42; } void nonCoroutineFunction() { - // The following line will block as if computeAnswer were not a coroutine. + // The following line will block as if computeAnswer were not a coroutine. It will internally run a + // a QEventLoop, so other events are still processed. const int answer = QCoro::waitFor(computeAnswer()); std::cout << "The answer is: " << answer << std::endl; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/examples/network/main.cpp new/qcoro-0.8.0/examples/network/main.cpp --- old/qcoro-0.7.0/examples/network/main.cpp 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/examples/network/main.cpp 2023-01-31 20:50:01.000000000 +0100 @@ -63,14 +63,13 @@ mBtn->setEnabled(false); mBtn->setText(tr("Downloading ...")); - auto *reply = co_await mNam.get(QNetworkRequest{wikiUrl}); + std::unique_ptr<QNetworkReply> reply(co_await mNam.get(QNetworkRequest{wikiUrl})); if (reply->error()) { QMessageBox::warning( this, tr("Network request error"), tr("Error occured during network request. Error code: %1").arg(reply->error())); co_return; } - delete reply; mPb->setVisible(false); mBtn->setEnabled(true); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/mkdocs.yml new/qcoro-0.8.0/mkdocs.yml --- old/qcoro-0.7.0/mkdocs.yml 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/mkdocs.yml 2023-01-31 20:50:01.000000000 +0100 @@ -28,7 +28,8 @@ - admonition plugins: - - search + - search: + separator: '[\s\-]+|::' - include-markdown - blogging: dirs: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/qcoro/core/qcorothread.cpp new/qcoro-0.8.0/qcoro/core/qcorothread.cpp --- old/qcoro-0.7.0/qcoro/core/qcorothread.cpp 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/qcoro/core/qcorothread.cpp 2023-01-31 20:50:01.000000000 +0100 @@ -7,8 +7,80 @@ #include <QThread> +using namespace QCoro; using namespace QCoro::detail; +namespace QCoro::detail { + +class ContextHelper : public QObject { + Q_OBJECT +public: + static QEvent::Type eventType; + + explicit ContextHelper(std::coroutine_handle<> awaiter, QThread *thread) + : mThread(thread) + , mAwaiter(awaiter) + {} + + bool event(QEvent *event) override { + if (event->type() == eventType) { + Q_ASSERT(QThread::currentThread() == mThread); + mAwaiter.resume(); + return true; + } + + return QObject::event(event); + } + +private: + QThread *mThread; + std::coroutine_handle<> mAwaiter; +}; + +QEvent::Type ContextHelper::eventType = static_cast<QEvent::Type>(QEvent::registerEventType()); + +class ThreadContextPrivate { +public: + explicit ThreadContextPrivate(QThread *thread) + : mThread(thread) + {} + + QThread *mThread; + std::unique_ptr<ContextHelper> mContext; +}; + +} // namespace QCoro::detail + +ThreadContext::ThreadContext(QThread *thread) + : d(std::make_unique<ThreadContextPrivate>(thread)) +{} + +#ifdef Q_CC_GNU +ThreadContext::ThreadContext(ThreadContext &&) noexcept = default; +#endif + +ThreadContext::~ThreadContext() = default; + +bool ThreadContext::await_ready() const noexcept { + return false; // never ready! +} + +void ThreadContext::await_suspend(std::coroutine_handle<> awaiter) noexcept { + d->mContext = std::make_unique<ContextHelper>(awaiter, d->mThread); + d->mContext->moveToThread(d->mThread); + qCoro(d->mThread).waitForStarted().then([this]() { + auto *event = new QEvent(static_cast<QEvent::Type>(detail::ContextHelper::eventType)); + QCoreApplication::postEvent(d->mContext.get(), event); + }); +} + +void ThreadContext::await_resume() noexcept {} + + +ThreadContext QCoro::moveToThread(QThread *thread) { + return ThreadContext(thread); +} + QCoroThread::QCoroThread(QThread *thread) : mThread(thread) {} @@ -36,3 +108,5 @@ const auto result = co_await qCoro(mThread.data(), &QThread::finished, timeout); co_return result.has_value(); } + +#include "qcorothread.moc" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/qcoro/core/qcorothread.h new/qcoro-0.8.0/qcoro/core/qcorothread.h --- old/qcoro-0.7.0/qcoro/core/qcorothread.h 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/qcoro/core/qcorothread.h 2023-01-31 20:50:01.000000000 +0100 @@ -5,7 +5,9 @@ #pragma once #include <QPointer> + #include "qcorocore_export.h" +#include "qcoro/coroutine.h" #include <chrono> @@ -14,6 +16,36 @@ namespace QCoro { template<typename T> class Task; + +namespace detail { +class ThreadContextPrivate; +} // namespace detail + +class ThreadContext { +public: + explicit ThreadContext(QThread *thread); + ~ThreadContext(); + ThreadContext(const ThreadContext &) = delete; + ThreadContext &operator=(const ThreadContext &) = delete; +#ifdef Q_CC_GNU + // Workaround for the a GCC bug(?) where GCC tries to move the ThreadContext + // into QCoro::TaskPromise::await_transform() (most likely). + ThreadContext(ThreadContext &&) noexcept; +#else + ThreadContext(ThreadContext &&) = delete; +#endif + ThreadContext &operator=(ThreadContext &&) = delete; + + bool await_ready() const noexcept; + void await_suspend(std::coroutine_handle<> awaiter) noexcept; + void await_resume() noexcept; + +private: + std::unique_ptr<detail::ThreadContextPrivate> d; +}; + +ThreadContext moveToThread(QThread *thread); + } // namespace QCoro namespace QCoro::detail { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/qcoro/core/qcorotimer.h new/qcoro-0.8.0/qcoro/core/qcorotimer.h --- old/qcoro-0.7.0/qcoro/core/qcorotimer.h 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/qcoro/core/qcorotimer.h 2023-01-31 20:50:01.000000000 +0100 @@ -52,6 +52,26 @@ } // namespace QCoro::detail +namespace QCoro { + +//! A coroutine that suspends for given period of time. +template<typename Rep, typename Period> +QCoro::Task<> sleepFor(const std::chrono::duration<Rep, Period> &timeout) { + QTimer timer; + timer.setSingleShot(true); + timer.start(std::chrono::duration_cast<std::chrono::milliseconds>(timeout)); + co_await timer; +} + +//! A coroutine that suspends until the specified time. +template<typename Clock, typename Duration> +QCoro::Task<> sleepUntil(const std::chrono::time_point<Clock, Duration> &when) { + const auto tp = when.time_since_epoch() - std::chrono::steady_clock::now().time_since_epoch(); + return sleepFor(tp); +} + +} // namespace QCoro + /*! \endcond */ //! Returns a coroutine-friendly wrapper for QTimer object. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/qcoro/coroutine.h new/qcoro-0.8.0/qcoro/coroutine.h --- old/qcoro-0.7.0/qcoro/coroutine.h 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/qcoro/coroutine.h 2023-01-31 20:50:01.000000000 +0100 @@ -5,6 +5,7 @@ #pragma once #include <version> +#include <utility> // __cpp_lib_coroutine is not defined if the compiler doesn't support coroutines // (__cpp_impl_coroutine), e.g. clang as of 13.0. @@ -257,6 +258,7 @@ namespace detail { + template<typename T> concept has_await_methods = requires(T t) { { t.await_ready() } -> std::same_as<bool>; @@ -265,11 +267,21 @@ }; template<typename T> -concept has_operator_coawait = requires(T t) { +concept has_member_operator_coawait = requires(T t) { // TODO: Check that result of co_await() satisfies Awaitable again { t.operator co_await() }; }; +template<typename T> +concept has_nonmember_operator_coawait = requires(T t) { + // TODO: Check that result of the operator satisfied Awaitable again +#if defined(_MSC_VER) && !defined(__clang__) + // FIXME: MSVC is unable to perform ADL lookup for operator co_await and just fails to compile + { ::operator co_await(static_cast<T &&>(t)) }; +#else + { operator co_await(static_cast<T &&>(t)) }; +#endif +}; } // namespace detail @@ -278,7 +290,8 @@ * Awaitable type is a type that can be passed as an argument to co_await. */ template<typename T> -concept Awaitable = detail::has_operator_coawait<T> || +concept Awaitable = detail::has_member_operator_coawait<T> || + detail::has_nonmember_operator_coawait<T> || detail::has_await_methods<T>; } // namespace QCoro diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/qcoro/qcorotask.h new/qcoro-0.8.0/qcoro/qcorotask.h --- old/qcoro-0.7.0/qcoro/qcorotask.h 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/qcoro/qcorotask.h 2023-01-31 20:50:01.000000000 +0100 @@ -661,10 +661,42 @@ return Task<void>{std::coroutine_handle<TaskPromise>::from_promise(*this)}; } -} // namespace detail -namespace detail { +template <typename T> +concept TaskConvertible = requires(T v, TaskPromiseBase t) +{ + { t.await_transform(v) }; +}; + +template<typename T> +struct awaitable_return_type { + using type = std::decay_t<decltype(std::declval<T>().await_resume())>; +}; + +template<QCoro::detail::has_member_operator_coawait T> +struct awaitable_return_type<T> { + using type = std::decay_t<typename awaitable_return_type<decltype(std::declval<T>().operator co_await())>::type>; +}; + +template<QCoro::detail::has_nonmember_operator_coawait T> +struct awaitable_return_type<T> { + using type = std::decay_t<typename awaitable_return_type<decltype(operator co_await(std::declval<T>()))>::type>; +}; + +template<Awaitable Awaitable> +using awaitable_return_type_t = typename detail::awaitable_return_type<Awaitable>::type; + +template <typename Awaitable> +requires TaskConvertible<Awaitable> +using convertible_awaitable_return_type_t = typename detail::awaitable_return_type<decltype(std::declval<TaskPromiseBase>().await_transform(Awaitable()))>::type; + +template <typename Awaitable> +requires TaskConvertible<Awaitable> +auto toTask(Awaitable &&future) -> QCoro::Task<detail::convertible_awaitable_return_type_t<Awaitable>> { + co_return co_await future; +} + //! Helper class to run a coroutine in a nested event loop. /*! @@ -675,32 +707,32 @@ * So instead we do basically what Qt does internally, but we make sure to not delete th * QFunctorSlotObjectWithNoArgs until after the event loop quits. */ -template<typename Coroutine> -Task<> runCoroutine(QEventLoop &loop, bool &startLoop, Coroutine &&coroutine) { - co_await coroutine; +template<Awaitable Awaitable> +Task<> runCoroutine(QEventLoop &loop, bool &startLoop, Awaitable &&awaitable) { + co_await awaitable; startLoop = false; loop.quit(); } -template<typename T, typename Coroutine> -Task<> runCoroutine(QEventLoop &loop, bool &startLoop, T &result, Coroutine &&coroutine) { - result = co_await coroutine; +template<typename T, Awaitable Awaitable> +Task<> runCoroutine(QEventLoop &loop, bool &startLoop, T &result, Awaitable &&awaitable) { + result = co_await awaitable; startLoop = false; loop.quit(); } -template<typename T, typename Coroutine> -T waitFor(Coroutine &&coro) { +template<typename T, Awaitable Awaitable> +T waitFor(Awaitable &&awaitable) { QEventLoop loop; bool startLoop = true; // for early returns: calling quit() before exec() still starts the loop if constexpr (std::is_void_v<T>) { - runCoroutine(loop, startLoop, std::forward<Coroutine>(coro)); + runCoroutine(loop, startLoop, std::forward<Awaitable>(awaitable)); if (startLoop) { loop.exec(); } } else { T result; - runCoroutine(loop, startLoop, result, std::forward<Coroutine>(coro)); + runCoroutine(loop, startLoop, result, std::forward<Awaitable>(awaitable)); if (startLoop) { loop.exec(); } @@ -721,43 +753,18 @@ */ template<typename T> inline T waitFor(QCoro::Task<T> &task) { - return detail::waitFor<T>(task); + return detail::waitFor<T>(std::forward<QCoro::Task<T>>(task)); } // \overload template<typename T> inline T waitFor(QCoro::Task<T> &&task) { - return detail::waitFor<T>(task); -} - -namespace detail { - -template <typename T> -concept TaskConvertible = requires(T v, TaskPromiseBase t) -{ - { t.await_transform(v) }; -}; - -template<typename T> -struct awaitable_return_type { - using type = decltype(std::declval<T>().await_resume()); -}; - -template<QCoro::detail::has_operator_coawait T> -struct awaitable_return_type<T> { - using type = typename awaitable_return_type<decltype(std::declval<T>().operator co_await())>::type; -}; - -template <typename Awaitable> -requires TaskConvertible<Awaitable> -using awaitable_return_type_t = typename detail::awaitable_return_type<decltype(std::declval<TaskPromiseBase>().await_transform(Awaitable()))>::type; - -template <typename Awaitable> -requires TaskConvertible<Awaitable> -auto toTask(Awaitable &&future) -> QCoro::Task<detail::awaitable_return_type_t<Awaitable>> { - co_return co_await future; + return detail::waitFor<T>(std::forward<QCoro::Task<T>>(task)); } +template<Awaitable Awaitable> +inline auto waitFor(Awaitable &&awaitable) { + return detail::waitFor<detail::awaitable_return_type_t<Awaitable>>(std::forward<Awaitable>(awaitable)); } //! Connect a callback to be called when the asynchronous task finishes. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/qcoro/quick/CMakeLists.txt new/qcoro-0.8.0/qcoro/quick/CMakeLists.txt --- old/qcoro-0.7.0/qcoro/quick/CMakeLists.txt 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/qcoro/quick/CMakeLists.txt 2023-01-31 20:50:01.000000000 +0100 @@ -12,5 +12,5 @@ QCORO_LINK_LIBRARIES PUBLIC Coro Core QT_LINK_LIBRARIES - PUBLIC Core Quick + PUBLIC Core Quick ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/requirements.txt new/qcoro-0.8.0/requirements.txt --- old/qcoro-0.7.0/requirements.txt 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/requirements.txt 2023-01-31 20:50:01.000000000 +0100 @@ -1,7 +1,7 @@ setuptools wheel -pymdown-extensions~=9.8 -pygments~=2.13 +pymdown-extensions~=9.9 +pygments~=2.14 mkdocs mkdocs-material mkdocs-section-index diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/tests/CMakeLists.txt new/qcoro-0.8.0/tests/CMakeLists.txt --- old/qcoro-0.7.0/tests/CMakeLists.txt 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/tests/CMakeLists.txt 2023-01-31 20:50:01.000000000 +0100 @@ -100,6 +100,9 @@ Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::QuickPrivate ) + get_test_property(test-${_name} ENVIRONMENT _env) + list(APPEND _env QT_QPA_PLATFORM=offscreen) + set_tests_properties(test-${_name} PROPERTIES ENVIRONMENT "${_env}") endfunction() qcoro_add_test(qtimer) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/tests/qcorotask.cpp new/qcoro-0.8.0/tests/qcorotask.cpp --- old/qcoro-0.7.0/tests/qcorotask.cpp 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/tests/qcorotask.cpp 2023-01-31 20:50:01.000000000 +0100 @@ -58,6 +58,66 @@ QString string; }; +struct TestAwaitableBase { + std::chrono::milliseconds delay() const { return mDelay; } +private: + std::chrono::milliseconds mDelay = 100ms; +}; + +template<typename T> +struct TestAwaitable : TestAwaitableBase { +public: + TestAwaitable(T val) + : mResult(val) + {} + + bool await_ready() const { return false; } + void await_suspend(std::coroutine_handle<> handle) { + QTimer::singleShot(100ms, [handle = std::move(handle)]() { + handle.resume(); + }); + } + T await_resume() { + return mResult; + } + +private: + T mResult; +}; + +template<> +struct TestAwaitable<void> : TestAwaitableBase { +public: + bool await_ready() const { return false; } + void await_suspend(std::coroutine_handle<> handle) { + QTimer::singleShot(100ms, [handle = std::move(handle)]() { + handle.resume(); + }); + } + void await_resume() {} +}; + +template<typename T> +struct TestAwaitableWithCoAwait { + TestAwaitableWithCoAwait(T val) + : mResult(val) + {} + + TestAwaitable<T> operator co_await() { + return TestAwaitable(mResult); + } + +private: + T mResult; +}; + +template<> +struct TestAwaitableWithCoAwait<void> { + TestAwaitable<void> operator co_await() { + return TestAwaitable<void>(); + } +}; + } // namespace class QCoroTaskTest : public QCoro::TestObject<QCoroTaskTest> @@ -489,6 +549,53 @@ QCOMPARE(result, 42); } + void testWaitForAwaitable() { + TestAwaitable<int> awaitable(42); + QElapsedTimer timer; + timer.start(); + + static_assert(std::is_same_v<decltype(QCoro::waitFor(awaitable)), int>); + const int result = QCoro::waitFor(awaitable); + QCOMPARE(result, 42); + QVERIFY(timer.elapsed() >= static_cast<float>(awaitable.delay().count()) * 0.9); + + } + + void testWaitForVoidAwaitable() { + TestAwaitable<void> awaitable; + QElapsedTimer timer; + timer.start(); + + static_assert(std::is_void_v<decltype(QCoro::waitFor(awaitable))>); + QCoro::waitFor(awaitable); + + QVERIFY(timer.elapsed() >= static_cast<float>(awaitable.delay().count()) * 0.9); + } + + void testWaitForAwaitableWithOperatorCoAwait() { + TestAwaitableWithCoAwait<int> awaitable(42); + QCoro::waitFor(awaitable); + QElapsedTimer timer; + timer.start(); + + static_assert(std::is_same_v<decltype(QCoro::waitFor(awaitable)), int>); + const int result = QCoro::waitFor(awaitable); + QCOMPARE(result, 42); + QVERIFY(timer.elapsed() >= (90ms).count()); + } + + void testWaitForVoidAwaitableWithOperatorCoAwait() { + TestAwaitableWithCoAwait<void> awaitable; + QElapsedTimer timer; + timer.start(); + + static_assert(std::is_void_v<decltype(QCoro::waitFor(awaitable))>); + QCoro::waitFor(awaitable); + + QVERIFY(timer.elapsed() >= (90ms).count()); + } + + void testIgnoredVoidTaskResult() { QEventLoop el; ignoreCoroutineResult(el, [&el]() -> QCoro::Task<> { @@ -626,6 +733,8 @@ QVERIFY(!called); } + + }; QTEST_GUILESS_MAIN(QCoroTaskTest) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/tests/qcorothread.cpp new/qcoro-0.8.0/tests/qcorothread.cpp --- old/qcoro-0.7.0/tests/qcorothread.cpp 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/tests/qcorothread.cpp 2023-01-31 20:50:01.000000000 +0100 @@ -47,9 +47,28 @@ QCORO_VERIFY(ok); } + QCoro::Task<> testMoveToThread_coro(QCoro::TestContext) { + QThread newThread; + newThread.start(); + + QCORO_COMPARE(QThread::currentThread(), QCoreApplication::instance()->thread()); + + co_await QCoro::moveToThread(&newThread); + + QCORO_COMPARE(QThread::currentThread(), &newThread); + + co_await QCoro::moveToThread(qApp->thread()); + + QCORO_COMPARE(QThread::currentThread(), QCoreApplication::instance()->thread()); + + newThread.exit(); + newThread.wait(); + } + private Q_SLOTS: addTest(WaitForStarted) addTest(WaitForFinished) + addTest(MoveToThread) }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/tests/qtimer.cpp new/qcoro-0.8.0/tests/qtimer.cpp --- old/qcoro-0.7.0/tests/qtimer.cpp 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/tests/qtimer.cpp 2023-01-31 20:50:01.000000000 +0100 @@ -6,6 +6,10 @@ #include "qcorotimer.h" +#include <chrono> + +using namespace std::chrono_literals; + class QCoroTimerTest : public QCoro::TestObject<QCoroTimerTest> { Q_OBJECT @@ -68,12 +72,28 @@ QVERIFY(triggered); } + QCoro::Task<> testSleepFor_coro(QCoro::TestContext) { + QElapsedTimer elapsed; + elapsed.start(); + co_await QCoro::sleepFor(100ms); + QCORO_VERIFY(elapsed.elapsed() >= 75); + } + + QCoro::Task<> testSleepUntil_coro(QCoro::TestContext) { + QElapsedTimer elapsed; + elapsed.start(); + co_await QCoro::sleepUntil(std::chrono::steady_clock::now() + 500ms); + QCORO_VERIFY(elapsed.elapsed() >= 475); + } + private Q_SLOTS: addTest(Triggers) addTest(QCoroWrapperTriggers) addTest(DoesntBlockEventLoop) addTest(DoesntCoAwaitInactiveTimer) addTest(DoesntCoAwaitNullTimer) + addTest(SleepFor) + addTest(SleepUntil) addThenTest(Triggers) }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qcoro-0.7.0/tests/testconstraints.cpp new/qcoro-0.8.0/tests/testconstraints.cpp --- old/qcoro-0.7.0/tests/testconstraints.cpp 2022-11-19 23:54:23.000000000 +0100 +++ new/qcoro-0.8.0/tests/testconstraints.cpp 2023-01-31 20:50:01.000000000 +0100 @@ -14,7 +14,7 @@ template<QCoro::Awaitable T> static constexpr bool is_awaitable_v = is_awaitable<T>::value; -} +} // namespace helper struct TestAwaitable { bool await_ready() const { return true; } @@ -22,6 +22,33 @@ void await_resume() const; }; +struct TestAwaitableWithOperatorCoAwait { + TestAwaitable operator co_await() { + return TestAwaitable{}; + } +}; + +namespace TestNS { + +struct TestAwaitableWithNonmemberOperatorCoAwait {}; + +#if !defined(_MSC_VER) || defined(__clang__) +// This is how it's supposed to work: the operator must be defined in the same namespace +// as the argument type and is discovered via ADL. +auto operator co_await(TestAwaitableWithNonmemberOperatorCoAwait &&) { + return TestAwaitable{}; +} +#endif + +} // namespace TestNS + +#if defined(_MSC_VER) && !defined(__clang__) +// Unfortunately, MSVC is only able to find the operator in global namespace :( +// This is most likely a bug in MSVC. +auto operator co_await(TestNS::TestAwaitableWithNonmemberOperatorCoAwait &&) { + return TestAwaitable{}; +} +#endif class TestConstraints : public QObject { Q_OBJECT @@ -31,7 +58,11 @@ static_assert(helper::is_awaitable_v<QCoro::Task<void>>, "Awaitable concept doesn't accept Task<T>, although it should."); static_assert(helper::is_awaitable_v<TestAwaitable>, - "Awaitable concept doesn't accept TestAwaitable, although it should."); + "Awaitable concept doesn't accept an awaitable with await member functions."); + static_assert(helper::is_awaitable_v<TestAwaitableWithOperatorCoAwait>, + "Awaitable concept doesn't accept an awaitable with member operator co_await."); + static_assert(helper::is_awaitable_v<TestNS::TestAwaitableWithNonmemberOperatorCoAwait>, + "Awaitable concept doesn't accept an awaitable with non-member operator co_await."); } };
