pax_global_header00006660000000000000000000000064141656414760014530gustar00rootroot0000000000000052 comment=3f74afd37d463ba84d337f86f5c324c829ec0744 qcoro-0.4.0/000077500000000000000000000000001416564147600126545ustar00rootroot00000000000000qcoro-0.4.0/.clang-format000066400000000000000000000102251416564147600152270ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveMacros: false AlignConsecutiveAssignments: false AlignConsecutiveBitFields: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: Align AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortEnumsOnASingleLine: false AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: Never AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(qcoro)/' Priority: 2 SortPriority: 0 - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 SortPriority: 0 - Regex: '.*' Priority: 1 SortPriority: 0 IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: AfterExternBlock IndentWidth: 4 IndentWrappedFunctionNames: false InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: false SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false Standard: Latest StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION - addTest TabWidth: 8 UseCRLF: false UseTab: Never WhitespaceSensitiveMacros: - STRINGIZE - PP_STRINGIZE - BOOST_PP_STRINGIZE ... qcoro-0.4.0/.git-blame-ignore-revs000066400000000000000000000001071416564147600167520ustar00rootroot00000000000000# Initial run of clang-format 0c01ca40fd8bd337ecae3ea21be1e5a97ba40e4b qcoro-0.4.0/.github/000077500000000000000000000000001416564147600142145ustar00rootroot00000000000000qcoro-0.4.0/.github/actions/000077500000000000000000000000001416564147600156545ustar00rootroot00000000000000qcoro-0.4.0/.github/actions/install-qt/000077500000000000000000000000001416564147600177445ustar00rootroot00000000000000qcoro-0.4.0/.github/actions/install-qt/action.yml000066400000000000000000000031051416564147600217430ustar00rootroot00000000000000name: 'Install Qt' description: 'Install Qt' inputs: qt_version: description: 'Version of Qt to install' required: true qt_modules: description: 'List of Qt modules to intall' default: qtbase icu compiler: description: 'Name of the compiler to use' required: true runs: using: composite steps: - shell: bash run: | pip3 install aqtinstall~=1.2.5 if [[ "${{ inputs.compiler }}" == "msvc" ]]; then aqt install -O C:\Qt -m ${{ inputs.qt_modules }} --noarchives ${{ inputs.qt_version }} windows desktop win64_msvc2019_64 QT_BASE_DIR="C:\Qt\${{ inputs.qt_version }}\msvc2019_64" powershell "./.github/actions/install-qt/install-dbus.ps1" "$QT_BASE_DIR" echo "$QT_BASE_DIR\\bin" >> $GITHUB_PATH echo "CMAKE_PREFIX_PATH=$QT_BASE_DIR\\lib\\cmake" >> $GITHUB_ENV else sudo apt-get update sudo apt-get install -y --no-install-recommends build-essential dbus dbus-x11 libgl-dev libegl-dev if [[ "${{ inputs.compiler }}" == "clang"* ]]; then sudo apt-get install -y --no-install-recommends libc++1-11 libc++-11-dev libc++abi-11-dev fi aqt install -O /opt/qt -m ${{ inputs.qt_modules }} --noarchives ${{ inputs.qt_version }} linux desktop gcc_64 QT_BASE_DIR="/opt/qt/${{ inputs.qt_version }}/gcc_64/" echo "$QT_BASE_DIR/bin" >> $GITHUB_PATH echo "LD_LIBRARY_PATH=$QT_BASE_DIR/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV echo "XDG_DATA_DIRS=$QT_BASE_DIR/share:$XDG_DATA_DIRS" >> $GITHUB_ENV fi qcoro-0.4.0/.github/actions/install-qt/install-dbus.ps1000066400000000000000000000010571416564147600227750ustar00rootroot00000000000000[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Set-PSRepository -Name 'PSGallery' -SourceLocation "https://www.powershellgallery.com/api/v2" -InstallationPolicy Trusted Install-Module -Name 7Zip4PowerShell -Force curl https://files.kde.org/craft/master/Qt_5.15.2-1/windows/msvc2019_64/cl/RelWithDebInfo/libs/dbus/dbus-1.13.18-3-131-20210415T121131-windows-msvc2019_64-cl.7z -o C:\Qt\dbus.7z Expand-7Zip -ArchiveFileName C:\Qt\dbus.7z -TargetPath $args[0] qcoro-0.4.0/.github/workflows/000077500000000000000000000000001416564147600162515ustar00rootroot00000000000000qcoro-0.4.0/.github/workflows/build.yml000066400000000000000000000075011416564147600200760ustar00rootroot00000000000000name: Build and Test on: push: branches: - main pull_request: types: [opened, synchronize, reopened, edited] env: BUILD_TYPE: RelWithDebInfo QTEST_FUNCTION_TIMEOUT: 60000 jobs: build: strategy: fail-fast: false matrix: os: [ ubuntu-latest, windows-latest ] qt_version: [ 5.12.1, 6.1.2 ] compiler: [ gcc-10, clang-11, msvc ] exclude: - os: ubuntu-latest compiler: msvc - os: windows-latest compiler: gcc-10 - os: windows-latest compiler: clang-11 - os: windows-latest qt_version: 5.12.1 include: - os: windows-latest compiler: msvc qt_version: 5.15.2 runs-on: ${{ matrix.os }} name: ${{ matrix.os }}-qt${{ matrix.qt_version}}-${{ matrix.compiler }} steps: - name: Checkout sources uses: actions/checkout@v2 - name: Install Qt uses: ./.github/actions/install-qt with: qt_version: ${{ matrix.qt_version }} compiler: ${{ matrix.compiler }} - name: Create Build Environment run: | cmake -E make_directory ${{github.workspace}}/build - name: Configure CMake shell: bash working-directory: ${{github.workspace}}/build run: | QT_VERSION_MAJOR=$(echo ${{ matrix.qt_version }} | cut -d'.' -f1) ARGS="-DCMAKE_BUILD_TYPE=$BULD_TYPE -DUSE_QT_VERSION=$QT_VERSION_MAJOR -DQCORO_WITH_DBUS=ON" # Don't enable ASAN on Qt5 MSVC build: it crashes due to some false-positive issue outside of QCoro if [[ "${{ matrix.compiler }}" != "msvc" || "${QT_VERSION_MAJOR}" != "5" ]]; then ARGS="${ARGS} -DQCORO_ENABLE_ASAN=ON" fi if [[ "${{ matrix.compiler }}" == "gcc-10"* ]]; then ARGS="$ARGS -DCMAKE_CXX_COMPILER=/usr/bin/g++-10" elif [[ "${{ matrix.compiler }}" == "clang-11" ]]; then ARGS="$ARGS -DCMAKE_CXX_COMPILER=/usr/bin/clang++-11" fi cmake $GITHUB_WORKSPACE $ARGS - name: Add ASAN DLL directory to PATH if: ${{ matrix.os == 'windows-latest' }} shell: cmd run: | setlocal enabledelayedexpansion for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do (set InstallDir=%%i) if exist "%InstallDir%\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" ( set /p Version=<"%InstallDir%\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" set Version=!Version: =! ) if not "%Version%" == "" ( set /p="%InstallDir%\VC\Tools\MSVC\%Version%\bin\HostX64\x64" < nul > %GITHUB_PATH% exit 0 ) - name: Build working-directory: ${{github.workspace}}/build shell: bash run: cmake --build . --config $BUILD_TYPE --parallel 4 - name: Test working-directory: ${{github.workspace}}/build shell: bash run: ctest -C $BUILD_TYPE --output-on-failure --verbose --output-junit qcoro-test-results.xml - name: Upload Test Results if: always() uses: actions/upload-artifact@v2 with: name: Unit Tests Results (${{ matrix.os }}-qt${{ matrix.qt_version }}-${{ matrix.compiler }}) path: | ${{ github.workspace }}/build/qcoro-test-results.xml - name: Upload build logs on failure if: failure() uses: actions/upload-artifact@v2 with: name: build-${{ matrix.os }}-qt${{ matrix.qt_version }}-${{ matrix.compiler }} path: build/** event_file: name: "Event File" runs-on: ubuntu-latest steps: - name: Upload uses: actions/upload-artifact@v2 with: name: Event File path: ${{ github.event_path }} qcoro-0.4.0/.github/workflows/unit_tests_results.yml000066400000000000000000000021251416564147600227560ustar00rootroot00000000000000name: Unit Test Results on: workflow_run: workflows: ["Build and Test"] types: - completed jobs: unit-test-results: name: Unit Test Results runs-on: ubuntu-latest if: github.event.workflow_run.conclusion != 'skipped' steps: - name: Download and Extract Artifacts env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} run: | mkdir -p artifacts && cd artifacts artifacts_url=${{ github.event.workflow_run.artifacts_url }} gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact do IFS=$'\t' read name url <<< "$artifact" gh api $url > "$name.zip" unzip -d "$name" "$name.zip" done - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1 with: commit: ${{ github.event.workflow_run.head_sha }} event_file: artifacts/Event File/event.json event_name: ${{ github.event.workflow_run.event }} files: "artifacts/**/*.xml" qcoro-0.4.0/.github/workflows/update-docs.yml000066400000000000000000000035751416564147600212160ustar00rootroot00000000000000name: Generate documentation on: push: branches: - main paths: - 'mkdocs.yml' - 'docs/**' workflow_dispatch: jobs: update-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Python uses: actions/setup-python@v2 with: python-version: 3.7 - name: Install dependencies run: | python -m pip install -r requirements.txt - name: Build run: | echo "{% extends \"base.html\" %}{% block analytics %} {% endblock %}" > docs/overrides/main.html mkdocs build -d /tmp/site - name: Commit files run: | git fetch origin gh-pages git checkout gh-pages git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" git rm -r assets cp -r /tmp/site/* ./ git add -A git commit -m "Update docs from main branch ${{ github.sha }}" -a - name: Publish if: success() uses: ad-m/github-push-action@master with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: gh-pages qcoro-0.4.0/.gitignore000066400000000000000000000001711416564147600146430ustar00rootroot00000000000000build build-* .*.swp .ccls-cache compile_commands.json /.vs /CMakeSettings.json # Python (from mkdocs) __pycache__ venv qcoro-0.4.0/.reuse/000077500000000000000000000000001416564147600140555ustar00rootroot00000000000000qcoro-0.4.0/.reuse/dep5000066400000000000000000000004331416564147600146350ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: QCoro Upstream-Contact: Daniel Vrátil Source: https://qcoro.github.io # Sample paragraph, commented out: # # Files: src/* # Copyright: $YEAR $NAME <$CONTACT> # License: ... qcoro-0.4.0/CMakeLists.txt000066400000000000000000000122261416564147600154170ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.19) set(qcoro_VERSION 0.4.0) set(qcoro_SOVERSION 0) project(qcoro LANGUAGES CXX VERSION ${qcoro_VERSION}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(GNUInstallDirs) include(CTest) include(FeatureSummary) # Custom includes include(GenerateHeaders) include(AddQCoroLibrary) #-----------------------------------------------------------# # Options #-----------------------------------------------------------# option(QCORO_BUILD_EXAMPLES "Build examples" ON) add_feature_info(Examples QCORO_BUILD_EXAMPLES "Build examples") option(QCORO_ENABLE_ASAN "Build with AddressSanitizer" OFF) add_feature_info(Asan QCORO_ENABLE_ASAN "Build with AddressSanitizer") if(WIN32 OR APPLE OR ANDROID) option(QCORO_WITH_QTDBUS "Build QtDBus support" OFF) else() option(QCORO_WITH_QTDBUS "Build QtDBus support" ON) endif() add_feature_info(QtDBus QCORO_WITH_QTDBUS "Build QtDBus support") option(QCORO_WITH_QTNETWORK "Build QtNetwork support" ON) add_feature_info(QtNetwork QCORO_WITH_QTNETWORK "Build QtNetwork support") #-----------------------------------------------------------# # Compiler Settings #-----------------------------------------------------------# set(CMAKE_CXX_STANDARD 20) set(CMAKE_AUTOMOC ON) if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /WX") # Disable warning C5054: "operator '&': deprecated between enumerations of different types" caused by QtWidgets/qsizepolicy.h # Disable warning C4127: "conditional expression is constant" caused by QtCore/qiterable.h set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd5054 /wd4127") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror -pedantic") endif() if (QCORO_ENABLE_ASAN) if (MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") endif() endif() include(qcoro/QCoroMacros.cmake) qcoro_enable_coroutines() #-----------------------------------------------------------# # Dependencies #-----------------------------------------------------------# find_package(Threads REQUIRED) set(REQUIRED_QT_COMPONENTS Core) if (QCORO_WITH_QTDBUS) list(APPEND REQUIRED_QT_COMPONENTS DBus) endif() if (QCORO_WITH_QTNETWORK) list(APPEND REQUIRED_QT_COMPONENTS Network) endif() if (QCORO_BUILD_EXAMPLES) list(APPEND REQUIRED_QT_COMPONENTS Widgets Concurrent) endif() if (BUILD_TESTING) list(APPEND REQUIRED_QT_COMPONENTS Test Concurrent) endif() set(MIN_REQUIRED_QT5_VERSION "5.12") # 6.1.2 fixes QTBUG-93270 which prevents compiling against Qt6 with C++20 enabled set(MIN_REQUIRED_QT6_VERSION "6.1.2") 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() configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/config.h ) #-----------------------------------------------------------# # Definitions #-----------------------------------------------------------# set(QCORO_TARGET_PREFIX "QCoro${QT_VERSION_MAJOR}") set(QCORO_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/qcoro${QT_VERSION_MAJOR}") #-----------------------------------------------------------# # Sources #-----------------------------------------------------------# set(QCORO_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(qcoro) if (QCORO_BUILD_EXAMPLES) add_subdirectory(examples) endif() if (BUILD_TESTING) add_subdirectory(tests) endif() #-----------------------------------------------------------# # Installation #-----------------------------------------------------------# include(CMakePackageConfigHelpers) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/config.h DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro COMPONENT Devel ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/QCoroConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}Config.cmake" INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/QCoro${QT_VERSION_MAJOR}" PATH_VARS CMAKE_INSTALL_INCLUDEDIR ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}ConfigVersion.cmake" VERSION ${qcoro_VERSION} COMPATIBILITY SameMajorVersion ) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}ConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/QCoro${QT_VERSION_MAJOR}" COMPONENT Devel ) #-----------------------------------------------------------# # Summary #-----------------------------------------------------------# feature_summary(FATAL_ON_MISSING_REQUIRED_PACKAGES WHAT ALL) qcoro-0.4.0/LICENSES/000077500000000000000000000000001416564147600140615ustar00rootroot00000000000000qcoro-0.4.0/LICENSES/MIT.txt000066400000000000000000000021011416564147600152450ustar00rootroot00000000000000MIT License Copyright (c) 2021 Daniel Vrátil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. qcoro-0.4.0/QCoroConfig.cmake.in000066400000000000000000000031611416564147600164350ustar00rootroot00000000000000if (CMAKE_VERSION VERSION_LESS 3.1.0) message(FATAL_ERROR \"QCoro@QT_VERSION_MAJOR@ requires at least CMake version 3.1.0\") endif() if (NOT QCoro@QT_VERSION_MAJOR@_FIND_COMPONENTS) set(QCoro@QT_VERSION_MAJOR@_NOT_FOUND_MESSAGE "The QCoro@QT_VERSION_MAJOR@ package requires at least one component") set(QCoro@QT_VERSION_MAJOR@_FOUND FALSE) return() endif() set(_QCoro_FIND_PARTS_REQUIRED) if (QCoro@QT_VERSION_MAJOR@_FIND_REQUIRED) set(_QCoro_FIND_PARTS_REQUIRED REQUIRED) endif() set(_QCoro_FIND_PARTS_QUIET) if (QCoro@QT_VERSION_MAJOR@_FIND_QUIET) set(_QCoro_FIND_PARTS_QUIET QUIET) endif() get_filename_component(_qcoro_install_prefix "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) set(_QCoro_NOTFOUND_MESSAGE) foreach(module ${QCoro@QT_VERSION_MAJOR@_FIND_COMPONENTS}) find_package(QCoro@QT_VERSION_MAJOR@${module} ${_QCoro_FIND_PARTS_QUIET} ${_QCoro_FIND_PARTS_REQUIRED} PATHS ${_qcoro_install_prefix} NO_DEFAULT_PATH ) if (NOT QCoro@QT_VERSION_MAJOR@${module}_FOUND) if (QCoro@QT_VERSION_MAJOR@_FIND_REQUIRED_${module}) set(_QCoro_NOTFOUND_MESSAGE "${_QCoro_NOTFOUND_MESSAGE}Failed to find QCoro component \"${module}\" config file at \"${_qcoro_install_prefix}\"\n") elseif (NOT QCoro@QT_VERSION_MAJOR@_FIND_QUIETLY) message(WARNING "Failed to find QCoro@QT_VERSION_MAJOR@ component \"${module}\" config file at \"${_qcoro_install_prefix}\"") endif() endif() endforeach() if (_QCoro_NOTFOUND_MESSAGE) set(QCoro@QT_VERSION_MAJOR@_NOT_FOUND_MESSAGE "${_QCoro_NOTFOUND_MESSAGE}") set(QCoro@QT_VERSION_MAJOR@_FOUND FALSE) endif() qcoro-0.4.0/README.md000066400000000000000000000076251416564147600141450ustar00rootroot00000000000000[![Build](https://github.com/danvratil/qcoro/actions/workflows/build.yml/badge.svg)](https://github.com/danvratil/qcoro/actions/workflows/build.yml) # QCoro - Coroutines for Qt5 and Qt6 The QCoro library provides set of tools to make use of the C++20 coroutines in connection with certain asynchronous Qt actions. Take a look at the example below to see what an amazing thing coroutines are: ```cpp QNetworkAccessManager networkAccessManager; // co_await the reply - the coroutine is suspended until the QNetworkReply is finished. // While the coroutine is suspended, *the Qt event loop runs as usual*. const QNetworkReply *reply = co_await networkAccessManager.get(url); // Once the reply is finished, your code resumes here as if nothing amazing has just happened ;-) const auto data = reply->readAll(); ``` This is a rather experimental library that helps me to understand coroutines in C++. It requires compiler with support for the couroutines TS. ## Documentation 👉 📘 [Documentation](https://qcoro.dvratil.cz/) ## Supported Qt Types QCoro supports using the `co_await` keyword with several Qt types: ### `QDBusPendingCall` QCoro can wait for an asynchronous D-Bus call to finish. There's no need to use `QDBusPendingCallWatcher` with QCoro - just `co_await` the result instead. While co_awaiting, the Qt event loop runs as usual. ```cpp QDBusInterface remoteServiceInterface{serviceName, objectPath, interface}; const QDBusReply isReady = co_await remoteServiceInterface.asyncCall(QStringLiteral("isReady")); ``` [Full documentation here](https://qcoro.dvratil.cz/reference/qdbuspendingcall). ### `QFuture` QFuture represents a result of an asynchronous task. Normally you have to use `QFutureWatcher` to get notified when the future is ready. With QCoro, you can just `co_await` it! ```cpp const QFuture task1 = QtConcurrent::run(....); const QFuture task2 = QtConcurrent::run(....); const int a = co_await task1; const int b = co_await task2; co_return a + b; ``` [Full documentation here](https://qcoro.dvratil.cz/reference/qfuture). ### `QNetworkReply` Doing network requests with Qt can be tedious - the signal/slot approach breaks the flow of your code. Chaining requests and error handling quickly become mess and your code is broken into numerous functions. But not with QCoro, where you can simply `co_await` the `QNetworkReply` to finish: ```cpp QNetworkReply qnam; QNetworkReply *reply = qnam.get(QStringLiteral("https://github.com/danvratil/qcoro")); const auto contents = co_await reply; reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { co_return handleError(reply); } const auto link = findLinkInReturnedHtmlCode(contents); reply = qnam.get(link); const auto data = co_await reply; reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { co_return handleError(reply); } ... ``` [Full documentation here](https://qcoro.dvratil.cz/reference/qnetworkreply). ### `QTimer` Maybe you want to delay executing your code for a second, maybe you want to execute some code in repeated interval. This becomes super-trivial with `co_await`: ```cpp QTimer timer; timer.setInterval(1s); timer.start(); for (int i = 1; i <= 100; ++i) { co_await timer; qDebug() << "Waiting for " << i << " seconds..."; } qDebug() << "Done!"; ``` [Full documentation here](https://qcoro.dvratil.cz/reference/qtimer). ### `QIODevice` `QIODevice` is a base-class for many classes in Qt that allow data to be asynchronously written and read. How do you find out that there are data ready to be read? You could connect to `QIODevice::readyRead()` singal, or you could use QCoro and `co_await` the object: ```cpp socket->write("PING"); // Waiting for "pong" const auto data = co_await socket; co_return calculateLatency(data); ``` [Full documentation here](https://qcoro.dvratil.cz/reference/qiodevice). ### ...and more! Go check the [full documentation](https://qcoro.dvratil.cz) to learn more. qcoro-0.4.0/cmake/000077500000000000000000000000001416564147600137345ustar00rootroot00000000000000qcoro-0.4.0/cmake/AddQCoroLibrary.cmake000066400000000000000000000105301416564147600177160ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2022 Daniel Vrátil # # SPDX-License-Identifier: MIT include(GenerateHeaders) include(CMakePackageConfigHelpers) function(add_qcoro_library) function(prefix_libraries) set(oneValueArgs PREFIX OUTPUT) set(multiValueArgs LIBRARIES) cmake_parse_arguments(prf "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(_libs) foreach(libname ${prf_LIBRARIES}) if ("${libname}" MATCHES "PUBLIC|PRIVATE|INTERFACE") list(APPEND _libs "${libname}") else() list(APPEND _libs "${prf_PREFIX}::${libname}") endif() endforeach() set(${prf_OUTPUT} ${_libs} PARENT_SCOPE) endfunction() set(params INTERFACE) set(oneValueArgs NAME) set(multiValueArgs SOURCES CAMELCASE_HEADERS HEADERS QCORO_LINK_LIBRARIES QT_LINK_LIBRARIES) cmake_parse_arguments(LIB "${params}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) set(target_name "${QCORO_TARGET_PREFIX}${LIB_NAME}") string(TOLOWER "${target_name}" target_name_lowercase) set(target_interface) set(target_include_interface "PUBLIC") if (LIB_INTERFACE) set(target_interface "INTERFACE") set(target_include_interface "INTERFACE") endif() prefix_libraries( PREFIX ${QCORO_TARGET_PREFIX} LIBRARIES ${LIB_QCORO_LINK_LIBRARIES} OUTPUT qcoro_LIBS ) prefix_libraries( PREFIX Qt${QT_VERSION_MAJOR} LIBRARIES ${LIB_QT_LINK_LIBRARIES} OUTPUT qt_LIBS ) # TODO: How is it done in Qt? # We want to export target QCoro5::Network but so far we are exporting # QCoro5::QCoro5Network :shrug: add_library(${target_name} ${target_interface}) add_library(${QCORO_TARGET_PREFIX}::${LIB_NAME} ALIAS ${target_name}) if (LIB_SOURCES) target_sources(${target_name} PRIVATE ${LIB_SOURCES}) endif() target_include_directories( ${target_name} ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ${target_include_interface} $ ) target_link_libraries(${target_name} ${qcoro_LIBS}) target_link_libraries(${target_name} ${qt_LIBS}) set_target_properties( ${target_name} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS 1 VERSION ${qcoro_VERSION} SOVERSION ${qcoro_SOVERSION} EXPORT_NAME ${LIB_NAME} ) generate_headers( camelcase_HEADERS HEADER_NAMES ${LIB_CAMELCASE_HEADERS} OUTPUT_DIR QCoro ORIGINAL_HEADERS_VAR source_HEADERS ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/QCoro${LIB_NAME}Config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/${target_name}Config.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${target_name} PATH_VARS CMAKE_INSTALL_INCLUDEDIR ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/${target_name}ConfigVersion.cmake" VERSION ${qcoro_VERSION} COMPATIBILITY SameMajorVersion ) install( TARGETS ${target_name} EXPORT ${target_name}Targets ) install( FILES ${source_HEADERS} ${LIB_HEADERS} DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro/ COMPONENT Devel ) install( FILES ${camelcase_HEADERS} DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/QCoro/ COMPONENT Devel ) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/${target_name}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${target_name}ConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${target_name}" COMPONENT Devel ) install( EXPORT ${target_name}Targets DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${target_name}" FILE "${target_name}Targets.cmake" NAMESPACE ${QCORO_TARGET_PREFIX}:: COMPONENT Devel ) endfunction() qcoro-0.4.0/cmake/GenerateHeaders.cmake000066400000000000000000000022641416564147600177700ustar00rootroot00000000000000function(generate_headers output_var) set(options) set(oneValueArgs OUTPUT_DIR ORIGINAL_PREFIX ORIGINAL_HEADERS_VAR) set(multiValueArgs HEADER_NAMES) cmake_parse_arguments(GH "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if (GH_OUTPUT_DIR) set(GH_OUTPUT_DIR "${GH_OUTPUT_DIR}/") endif() foreach(_headername ${GH_HEADER_NAMES}) string(TOLOWER "${_headername}" originalbase) set(CC_ORIGINAL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${GH_ORIGINAL_PREFIX}/${originalbase}.h") if (NOT EXISTS ${CC_ORIGINAL_FILE}) message(FATAL_ERROR "Could not find header \"${CC_ORIGINAL_FILE}\"") endif() set(CC_HEADER_FILE "${CMAKE_CURRENT_BINARY_DIR}/${GH_OUTPUT_DIR}/${_headername}") if (NOT EXISTS ${CC_HEADER_FILE}) file(WRITE ${CC_HEADER_FILE} "#include \"${GH_ORIGINAL_PREFIX}${originalbase}.h\"") endif() list(APPEND ${output_var} ${CC_HEADER_FILE}) list(APPEND ${GH_ORIGINAL_HEADERS_VAR} ${CC_ORIGINAL_FILE}) endforeach() set(${output_var} ${${output_var}} PARENT_SCOPE) set(${GH_ORIGINAL_HEADERS_VAR} ${${GH_ORIGINAL_HEADERS_VAR}} PARENT_SCOPE) endfunction() qcoro-0.4.0/config.cmake.in000066400000000000000000000000451416564147600155270ustar00rootroot00000000000000#cmakedefine QCORO_QT_HAS_COMPAT_ABI qcoro-0.4.0/docs/000077500000000000000000000000001416564147600136045ustar00rootroot00000000000000qcoro-0.4.0/docs/about/000077500000000000000000000000001416564147600147165ustar00rootroot00000000000000qcoro-0.4.0/docs/about/license.md000066400000000000000000000021761416564147600166700ustar00rootroot00000000000000# License --- QCoro is published under the MIT License ## MIT License Copyright (c) 2021 Daniel Vrátil Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. qcoro-0.4.0/docs/building-and-using.md000066400000000000000000000032771416564147600176170ustar00rootroot00000000000000# Building and Using QCoro ## Building QCoro QCoro uses CMake build system. You can pass following options to the `cmake` command when building QCoro to customize the build: * `-DQCORO_BUILD_EXAMPLES` - whether to build examples or not (`ON` by default). * `-DQCORO_ENABLE_ASAN` - whether to build QCoro with AddressSanitizer (`OFF` by default). * `-DBUILD_SHARED_LIBS` - whether to build QCoro as a shared library (`OFF` by default). * `-DBUILD_TESTING` - whether to build tests (`ON` by default). * `-DUSE_QT_VERSION` - set to `5` or `6` to force a particular version of Qt. When not set the highest available version is used. * `-DQCORO_WITH_QTDBUS` - whether to compile support for QtDBus (`ON` by default). * `-DQCORO_WITH_QTNETWORK` - whether to compile support for QtNetwork (`ON` by default). ``` mkdir build cd build cmake .. make # This will install QCoro into /usr/local/ prefix, change it by passing -DCMAKE_INSTALL_PREFIX=/usr # to the cmake command above. sudo make install ``` ## Add it to your CMake Depending on whether you want to use Qt5 or Qt6 build of QCoro, you should use `QCoro5` or QCoro6` in your CMake code, respectively. The example below is assuming Qt6: ```cmake # Use QCoro5 if you are building for Qt5! find_package(QCoro6 REQUIRED COMPONENTS Core Network DBus) # Set necessary compiler flags to enable coroutine support qcoro_enable_coroutines() ... target_link_libraries(your-target QCoro::Core QCoro::Network QCoro::DBus) ``` Note the missing Qt version number in the `QCoro` target namespace: QCoro provides both versioned (`QCoro5` and `QCoro6`) namespaces as well as version-less namespace, which is especially useful for transitioning codebase from Qt5 to Qt6. qcoro-0.4.0/docs/changelog.md000066400000000000000000000135751416564147600160700ustar00rootroot00000000000000# Changelog ## 0.4.0 (2022-01-06) Major highlights in this release: * Co-installability of Qt5 and Qt6 builds of QCoro * Complete re-work of CMake configuration * Support for compiling QCoro with Clang against libstdc++ ### Co-installability of Qt5 and Qt6 builds of QCoro This change mostly affects packagers of QCoro. It is now possible to install both Qt5 and Qt6 versions of QCoro alongside each other without conflicting files. The shared libraries now contain the Qt version number in their name (e.g. `libQCoro6Core.so`) and header files are also located in dedicated subdirectories (e.g. `/usr/include/qcoro6/{qcoro,QCoro}`). User of QCoro should not need to do any changes to their codebase. ### Complete re-work of CMake configuration This change affects users of QCoro, as they will need to adjust CMakeLists.txt of their projects. First, depending on whether they want to use Qt5 or Qt6 version of QCoro, a different package must be used. Additionally, list of QCoro components to use must be specified: ``` find_package(QCoro5 REQUIRED COMPONENTS Core Network DBus) ``` Finally, the target names to use in `target_link_libraries` have changed as well: * `QCoro::Core` * `QCoro::Network` * `QCoro::DBus` The version-less `QCoro` namespace can be used regardless of whether using Qt5 or Qt6 build of QCoro. `QCoro5` and `QCoro6` namespaces are available as well, in case users need to combine both Qt5 and Qt6 versions in their codebase. This change brings QCoro CMake configuration system to the same style and behavior as Qt itself, so it should now be easier to use QCoro, especially when supporting both Qt5 and Qt6. ### Support for compiling QCoro with Clang against libstdc++ Until now, when the Clang compiler was detected, QCoro forced usage of LLVM's libc++ standard library. Coroutine support requires tight co-operation between the compiler and standard library. Because Clang still considers their coroutine support experimental it expects all coroutine-related types in standard library to be located in `std::experimental` namespace. In GNU's libstdc++, coroutines are fully supported and thus implemented in the `std` namespace. This requires a little bit of extra glue, which is now in place. ### Full changelog * QCoro can now be built with Clang against libstdc++ ([#38](https://github.com/danvratil/qcoro/pull/38), [#22](https://github.com/danvratil/qcoro/issues/22)) * Qt5 and Qt6 builds of QCoro are now co-installable ([#36](https://github.com/danvratil/qcoro/issues/36), [#37](https://github.com/danvratil/qcoro/pull/37)) * Fixed early co_return not resuming the caller ([#24](https://github.com/danvratil/qcoro/issue/24), [#35](https://github.com/danvratil/qcoro/pull/35)) * Fixed QProcess example ([#34](https://github.com/danvratil/qcoro/pull/34)) * Test suite has been improved and extended ([#29](https://github.com/danvratil/qcoro/pull/29), [#31](https://github.com/danvratil/qcoro/pull/31)) * Task move assignment operator checks for self-assignment ([#27](https://github.com/danvratil/qcoro/pull/27)) * QCoro can now be built as a subdirectory inside another CMake project ([#25](https://github.com/danvratil/qcoro/pull/25)) * Fixed QCoroCore/qcorocore.h header ([#23](https://github.com/danvratil/qcoro/pull/23)) * DBus is disabled by default on Windows, Mac and Android ([#21](https://github.com/danvratil/qcoro/pull/21)) Thanks to everyone who contributed to QCoro! ## 0.3.0 (2021-10-11) * Added SOVERSION to shared libraries ([#17](https://github.com/danvratil/qcoro/pull/17)) * Fixed building tests when not building examples ([#19](https://github.com/danvratil/qcoro/pull/19)) * Fixed CI Thanks to everyone who contributed to QCoro 0.3.0! ## 0.2.0 (2021-09-08) ### Library modularity The code has been reorganized into three modules (and thus three standalone libraries): QCoroCore, QCoroDBus and QCoroNetwork. QCoroCore contains the elementary QCoro tools (`QCoro::Task`, `qCoro()` wrapper etc.) and coroutine support for some QtCore types. The QCoroDBus module contains coroutine support for types from the QtDBus module and equally the QCoroNetwork module contains coroutine support for types from the QtNetwork module. The latter two modules are also optional, the library can be built without them. It also means that an application that only uses let's say QtNetwork and has no DBus dependency will no longer get QtDBus pulled in through QCoro, as long as it only links against `libQCoroCore` and `libQCoroNetwork`. The reorganization will also allow for future support of additional Qt modules. ### Headers clean up The include headers in QCoro we a bit of a mess and in 0.2.0 they all got a unified form. All public header files now start with `qcoro` (e.g. `qcorotimer.h`, `qcoronetworkreply.h` etc.), and QCoro also provides CamelCase headers now. Thus users should simply do `#include ` if they want coroutine support for `QTimer`. The reorganization of headers makes QCoro 0.2.0 incompatible with previous versions and any users of QCoro will have to update their `#include` statements. I'm sorry about this extra hassle, but with this brings much needed sanity into the header organization and naming scheme. ### Docs update The documentation has been updated to reflect the reorganization as well as some internal changes. It should be easier to understand now and hopefully will make it easier for users to start with QCoro now. ### Internal API cleanup and code de-duplication Historically, certain types types which can be directly `co_await`ed with QCoro, for instance `QTimer` has their coroutine support implemented differently than types that have multiple asynchronous operations and thus have a coroutine-friendly wrapper classes (like `QIODevice` and it's `QCoroIODevice` wrapper). In 0.2.0 I have unified the code so that even the coroutine support for simple types like `QTimer` are implemented through wrapper classes (so there's `QCoroTimer` now) ## 0.1.0 (2021-08-15) * Initial release QCoro qcoro-0.4.0/docs/coroutines/000077500000000000000000000000001416564147600157765ustar00rootroot00000000000000qcoro-0.4.0/docs/coroutines/coawait.md000066400000000000000000000044321416564147600177520ustar00rootroot00000000000000# `co_await` Explained The following paragraphs try to explain what is a coroutine and what `co_await` does in some simple way. I don't guarantee that any of this is factically correct. For more gritty (and correct) details, refer to the articles linked at the bottom of this document. Coroutines, simply put, are like normal functions except that they can be suspended (and resumed) in the middle. When a coroutine is suspended, execution returns to the function that has called the coroutine. If that function is also a coroutine and is waiting (`co_await`ing) for the current coroutine to finish, then it is suspended as well and the execution returns to the function that has called that coroutine and so on, until a function that is an actual function (not a coroutine) is reached. In case of a regular Qt program, this "top-level" non-coroutine function will be the Qt's event loop - which means that while your coroutine, when called from the Qt event loop is suspended, the Qt event loop will continue to run until the coroutine is resumed again. Amongst many other things, this allows you to write asynchronous code as if it were synchronous without blocking the Qt event loop and making your application unresponsive. See the different examples in this document. Now let's look at the `co_await` keyword. This keyword tells the compiler that this is the point where the coroutine wants to be suspended, until the *awaited* object (the *awaitable*) is ready. Anything type can be *awaitable* - either because it directly implements the interface needed by the C++ coroutine machinery, or because some external tools (like this library) are provided to wrap that type into something that implements the *awaitable* interface. The C++ coroutines introduce two additional keywords -`co_return` and `co_yield`: From an application programmer point of view, `co_return` behaves exactly the same as `return`, except that you cannot use the regular `return` in coroutines. There are some major differences under the hood, though, which is likely why there's a special keyword for returning from coroutines. `co_yield` allows a coroutine to produce a result without actually returning. Can be used for writing generators. Currently, this library has no support/usage of `co_yield`, so I won't go into more details here. qcoro-0.4.0/docs/coroutines/qt-vs-coawait.md000066400000000000000000000025701416564147600210230ustar00rootroot00000000000000# Qt vs. co_await One of the best examples where coroutines simplify your code is when dealing with asynchronous operations, like network operations. Let's see how a simple HTTP request would be handled in Qt using the signals/slots mechanism: ```cpp void MyClass::fetchData() { auto *nam = new QNetworkAccessManager(this); auto *reply = nam->get(QUrl{QStringLiteral("https://.../api/fetch")}); QObject::connect(reply, &QNetworkReply::finished, [reply, nam]() { const auto data = reply->readAll(); doSomethingWithData(data); reply->deleteLater(); nam->deleteLater(); }); } ``` Now let's see how the code looks like if we use coroutines: ```cpp QCoro::Task<> MyClass::fetchData() { QNetworkReply nam; auto *reply = co_await nam.get(QUrl{QStringLiteral("https://.../api/fetch")}); const auto data = reply->readAll(); reply->deleteLater(); doSomethingWithData(data); } ``` The magic here is the `co_await` keyword which has turned our method `fetchData()` into a coroutine and suspended its execution while the network request was running. When the request finishes, the coroutine is resumed from where it was suspended and continues. And the best part? While the coroutine is suspended, the Qt event loop runs as usual! qcoro-0.4.0/docs/coroutines/reading.md000066400000000000000000000014421416564147600177320ustar00rootroot00000000000000# More reading This library is inspired by Lewis Bakers' cppcoro library, which also served as a guide to implementing the coroutine machinery, alongside his great series on C++ coroutines: * [Coroutine Theory](https://lewissbaker.github.io/2017/09/25/coroutine-theory) * [Understanding Operator co_await](https://lewissbaker.github.io/2017/11/17/understanding-operator-co-await) * [Understanding the promise type](https://lewissbaker.github.io/2018/09/05/understanding-the-promise-type) * [Understanding Symmetric Transfer](https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer) I can also recommend numerous articles about C++ coroutines by [Raymond Chen on his blog OldNewThink][oldnewthing]. [oldnewthing]: https://devblogs.microsoft.com/oldnewthing/author/oldnewthing qcoro-0.4.0/docs/examples/000077500000000000000000000000001416564147600154225ustar00rootroot00000000000000qcoro-0.4.0/docs/examples/qdbus.cpp000066400000000000000000000026751416564147600172560ustar00rootroot00000000000000#include QCoro::Task PlayerControl::nextSong() { // Create a regular QDBusInterface representing the Spotify MPRIS interface QDBusInterface spotifyPlayer{QStringLiteral("org.mpris.MediaPlayer2.spotify"), QStringLiteral("/org/mpris/MediaPlayer2"), QStringLiteral("org.mpris.MediaPlayer2.Player")}; // Call CanGoNext DBus method and co_await reply. During that the current coroutine is suspended. const QDBusReply canGoNext = co_await spotifyPlayer.asyncCall(QStringLiteral("CanGoNext")); // Response has arrived and coroutine is resumed. If the player can go to the next song, // do another async call to do so. if (static_cast(canGoNext)) { // co_await the call to finish, but throw away the result co_await spotifyPlayer.asyncCall(QStringLiteral("Next")); } // Finally, another async call to retrieve new track metadata. Once again, the coroutine // is suspended while we wait for the result. const QDBusReply metadata = co_await spotifyPlayer.asyncCall(QStringLiteral("Metadata")); // Since this function uses co_await, it is in fact a coroutine, so it must use co_return in order // to return our result. By definition, the result of this function can be co_awaited by the caller. co_return static_cast(metadata)[QStringLiteral("xesam:title")].toString(); } qcoro-0.4.0/docs/examples/qfuture.cpp000066400000000000000000000010421416564147600176160ustar00rootroot00000000000000#include QCoro::Task<> runTask() { // Starts a concurrent task and co_awaits on the returned QFuture. While the task is // running, the coroutine is suspended. const QString value = co_await QtConcurrent::run([]() { QString result; ... // do some long-running computation ... return result; }); // When the future has finished, the coroutine is resumed and the result of the // QFuture is returned and stored in `value`. // ... now do something with the value } qcoro-0.4.0/docs/examples/qnetworkreply.cpp000066400000000000000000000013441416564147600210560ustar00rootroot00000000000000#include QCoro::Task<> MyClass::fetchData() { // Creates QNetworkAccessManager on stack QNetworkAccessManager nam; // Calls QNetworkAccessManager::get() and co_awaits on the returned QNetworkReply* // until it finishes. The current coroutine is suspended until that. auto *reply = co_await nam.get(QUrl{QStringLiteral("https://.../api/fetch")}); // When the reply finishes, the coroutine is resumed and we can access the reply content. const auto data = reply->readAll(); // Raise your hand if you never forgot to delete a QNetworkReply... delete reply; doSomethingWithData(data); // Extra bonus: the QNetworkAccessManager is destroyed automatically, since it's on stack. } qcoro-0.4.0/docs/examples/qprocess.cpp000066400000000000000000000006261416564147600177710ustar00rootroot00000000000000#include QCoro::Task listDir(const QString &dirPath) { QProcess basicProcess; auto process = qCoro(basicProcess); qDebug() << "Starting ls..."; co_await process.start(QStringLiteral("/bin/ls"), {dirPath}); qDebug() << "Ls started, reading directory..."; co_await process.waitForFinished(); qDebug() << "Done"; return basicProcess.readAll(); } qcoro-0.4.0/docs/examples/qtcpserver.cpp000066400000000000000000000005271416564147600203300ustar00rootroot00000000000000#include QCoro::Task<> runServer(uint16_t port) { QTcpServer server; server.listen(QHostAddress::LocalHost, port); while (server.isListening()) { auto *socket = co_await qCoro(server).waitForNewConnection(10s); if (socket != nullptr) { newClientConnection(socket); } } } qcoro-0.4.0/docs/examples/qtcpsocket.cpp000066400000000000000000000007121416564147600203060ustar00rootroot00000000000000#include QCoro::Task requestDataFromServer(const QString &hostName) { QTcpSocket socket; if (!co_await qCoro(socket).connectToHost(hostName)) { qWarning() << "Failed to connect to the server"; co_return QByteArray{}; } socket.write("SEND ME DATA!"); QByteArray data; while (!data.endsWith("\r\n.\r\n")) { data += co_await qCoro(socket).readAll(); } co_return data; } ` qcoro-0.4.0/docs/index.md000066400000000000000000000106771416564147600152500ustar00rootroot00000000000000# QCoro C++ Coroutine library for Qt5 and Qt6 --- ## Overview QCoro is a C++ library that provide set of tools to make use of C++20 coroutines in connection with certain asynchronous Qt actions. Take a look at the example below to see what an amazing thing coroutines are: ```cpp QNetworkAccessManager networkAccessManager; // co_await the reply - the coroutine is suspended until the QNetworkReply is finished. // While the coroutine is suspended, *the Qt event loop runs as usual*. const QNetworkReply *reply = co_await networkAccessManager.get(url); // Once the reply is finished, your code resumes here as if nothing amazing has just happened ;-) const auto data = reply->readAll(); ``` This library has only one class and one function that the user must be aware of: the class is [`QCoro::Task`](reference/coro/task.md) and must be used as a return type for any coroutine that `co_await`s a Qt type. The function is [`qCoro()`](reference/coro/coro.md) and it provides coroutine-friendly wrappers for Qt types that have multiple asynchronous operations that the user may want to `co_await` (for example [`QProcess`](reference/core/qprocess.md)). All the other code (basically everything in the `QCoro::detail` namespace) is here to provide the cogs and gears for the C++ coroutine machinery, making it possible to use Qt types with coroutines. The major benefit of using coroutines with Qt types is that it allows writing asynchronous code as if it were synchronous and, most importantly, while the coroutine is `co_await`ing, the __Qt event loop runs as usual__, meaning that your application remains responsive. This is a rather experimental library that I started working on to better understand coroutines in C++. After reading numerous articles and blog posts about coroutines, it still wasn't exactly clear to me how the whole thing works, so I started working on this library to get a better idea about coroutines. ## Coroutines Coroutines are regular functions, except that they can be suspended and resumed again. When a coroutine is suspended, it returns sort of a promise to the caller and the caller continues executing their code. At some point, the caller can use the newly introduced `co_await` keyword to wait for the returned promise to be fulfilled. When that happens, the caller is suspended and instead the coroutine is resumed. This allows writing asynchronous code as if it were synchronous, making it much easier to read and understand. That's not all that coroutines can do, you can read more about it in the 'Coroutines' section of this documentation. ## Supported Compilers This library requires a compiler that supports the Coroutine TS (obviously). Currently GCC, Clang and MSVC are supported. All examples were tested with GCC 10 and Clang 11, although even slightly older versions should work. In both GCC and Clang, coroutine support must be explicitly enabled. ### GCC To enable coroutines support in GCC, add `-fcoroutines` to `CXX_FLAGS`. CMake: ``` set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines") ``` Alternatively, just use `qcoro_enable_coroutines()` CMake macro provided by QCoro to set the flags automatically. ### Clang In Clang coroutines are still considered experimental (unlike in GCC), so you cannot mix Clang and libstdc++. You must use Clang with libc++. Coroutines are enabled by adding `-fcoroutines-ts` to `CMAKE_CXX_FLAGS`. CMake: ``` set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines-ts -stdlib=libc++") ``` Alternatively, just use `qcoro_enable_coroutines()` CMake macro provided by QCoro to set the flags automatically. !!! info "LLVM libc++ vs. GNU libstdc++" While both libraries implement the same standard they are not mutually compatible - for one thing LLVM's libc++ is a bit behind libstdc++ on implementing all the features, but more importantly they are not ABI compatible. That means that if you compile Qt with libstdc++ (which most Linux distributions do) and your application with QCoro using libc++, you will likely run into `undefined reference` errors when linking your app against Qt. One option is to build Qt youself with libc++, other option is to use GCC or MSVC and avoid Clang until it has full support for coroutines, which will hopefully allow using libstdc++ with Clang. ### MSVC Coroutine support in MSVC is enabled automatically by CMake when C++20 standard is specified in `CMAKE_CXX_STANDARD`: ``` set(CMAKE_CXX_STANDARD 20) ``` qcoro-0.4.0/docs/macros.py000066400000000000000000000014471416564147600154500ustar00rootroot00000000000000def define_env(env): @env.macro def doctable(module, include, inherits=None, inheritedBy=[]): def row(th, td): return f"{ th }{ td }" def inheritsLink(inherits): return f"""{inherits[1]}""" out = """
""" out += row("Module", module) out += row("Include", f""" ```cpp #include <{include}> ``` """) out += row("CMake", f""" ```cpp target_link_libraries(myapp QCoro::{module}) ``` """) if inherits: out += row("Inherits", inheritsLink(inherits)) if inheritedBy: out += row("Inherited By", ', '.join(sorted(map(inheritsLink, inheritedBy)))) out += "
" return out qcoro-0.4.0/docs/overrides/000077500000000000000000000000001416564147600156065ustar00rootroot00000000000000qcoro-0.4.0/docs/overrides/.gitkeep000066400000000000000000000000001416564147600172250ustar00rootroot00000000000000qcoro-0.4.0/docs/reference/000077500000000000000000000000001416564147600155425ustar00rootroot00000000000000qcoro-0.4.0/docs/reference/core/000077500000000000000000000000001416564147600164725ustar00rootroot00000000000000qcoro-0.4.0/docs/reference/core/index.md000066400000000000000000000004251416564147600201240ustar00rootroot00000000000000# Core Module The `Core` module contains coroutine-friendly wrapper for [QtCore][qtdoc-qtcore] classes. ## CMake usage ``` find_package(QCoro6 COMPONENTS Core) ... target_link_libraries(my-target QCoro::Core) ``` [qtdoc-qtcore]: https://doc.qt.io/qt-5/qtcore-index.html qcoro-0.4.0/docs/reference/core/qfuture.md000066400000000000000000000020121416564147600205020ustar00rootroot00000000000000# QFuture {{ doctable("Core", "QCoroFuture") }} [`QFuture`][qdoc-qfuture], which represents an asynchronously executed call, doesn't have any operation on its own that could be awaited asynchronously, this is usually done through a helper class called [`QFutureWatcher`][qdoc-qfuturewatcher]. To simplify the API, QCoro allows to directly `co_await` completion of the running `QFuture` or use a wrapper class `QCoroFuture`. To wrap a `QFuture` into a `QCoroFuture`, use [`qCoro()`][qcoro-coro]: ```cpp template QCoroFuture qCoro(const QFuture &future); ``` ## `waitForFinished()` {% include-markdown "../../../qcoro/core/qcorofuture.h" dedent=true rewrite-relative-urls=false start="" end="" %} ## Example ```cpp {% include "../../examples/qfuture.cpp" %} ``` [qdoc-qfuture]: https://doc.qt.io/qt-5/qfuture.html [qdoc-qfuturewatcher]: https://doc.qt.io/qt-5/qfuturewatcher.html [qcoro-coro]: ../coro/coro.md qcoro-0.4.0/docs/reference/core/qiodevice.md000066400000000000000000000063741416564147600207760ustar00rootroot00000000000000# QIODevice {{ doctable("Core", "QCoroIODevice", None, [('network/qabstractsocket', 'QCoroAbstractSocket'), ('network/qlocalsocket', 'QCoroLocalSocket'), ('network/qnetworkreply', 'QCoroNetworkReply'), ('core/qprocess', 'QCoroProcess')]) }} ```cpp class QCoroIODevice ``` [`QIODevice`][qtdoc-qiodevice] has several different IO operations that can be waited on asynchronously. Since `QIODevice` itself doesn't provide the abaility to `co_await` those operations, QCoro provides a wrapper class called `QCoroIODevice`. To wrap a `QIODevice` into a `QCoroIODevice`, use [`qCoro()`][qcoro-coro]: ```cpp QCoroIODevice qCoro(QIODevice &); QCoroIODevice qCoro(QIODevice *); ``` Note that Qt provides several subclasses of `QIODevice`. QCoro provides coroutine-friendly wrappers for some of those types as well (e.g. for [`QLocalSocket`][qlocalsocket]). This subclass can be passed to `qCoro()` function as well. Oftentimes the wrapper class will provide some additional features (like co_awaiting establishing connection etc.). You can check whether QCoro supports the QIODevice subclass by checking the list of supported Qt types. ## `readAll()` Waits until there are any data to be read from the device (similar to waiting until the device emits [`QIODevice::readyRead()`][qtdoc-qiodevice-readyread] signal) and then returns all data available in the buffer as a `QByteArray`. Doesn't suspend the coroutine if there are already data available in the `QIODevice` or if the `QIODevice` is not opened for reading. This is the default operation when `co_await`ing an instance of a `QIODevice` directly. Thus, it is possible to just do ```cpp const QByteArray content = co_await device; ``` instead of ```cpp const QByteArray content = qCoro(device).readAll(); ``` See documentation for [`QIODevice::readAll()`][qtdoc-qiodevice-readall] for details. ```cpp Awaitable auto QCoroIODevice::readAll(); ``` ## `read()` Waits until there are any data to be read from the device (similar to waiting until the device emits [`QIODevice::readyRead()`][qtdoc-qiodevice-readyread] signal) and then returns up to `maxSize` bytes as a `QByteArray`. Doesn't suspend the coroutine if there are already data available in the `QIODevice` or if the device is not opened for reading. See documentation for [`QIODevice::read()`][qtdoc-qiodevice-read] for details. ```cpp Awaitable auto QCoroIODevice::read(qint64 maxSize = 0); ``` ## `readLine()` Repeatedly waits for data to arrive until it encounters a newline character, end-of-data or until it reads `maxSize` bytes. Returns the resulting data as `QByteArray`. See documentation for [`QIODevice::readLine()`][qtdoc-qiodevice-readline] for details. ```cpp Awaitable auto QCoroIODevice::readLine(qint64 maxSize = 0) ``` ## Examples ```cpp const QByteArray data = co_await qCoro(device).readAll(); ``` [qlocalsocket]: ../network/qlocalsocket.md [qcoro-coro]: ../coro/coro.md [qtdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html [qtdoc-qiodevice-read]: https://doc.qt.io/qt-5/qiodevice.html#read [qtdoc-qiodevice-readyread]: https://doc.qt.io/qt-5/qiodevice.html#readyRead [qtdoc-qiodevice-readall]: https://doc.qt.io/qt-5/qiodevice.html#readAll [qtdoc-qiodevice-readline]: https://doc.qt.io/qt-5/qiodevice.html#readLine qcoro-0.4.0/docs/reference/core/qprocess.md000066400000000000000000000052021416564147600206520ustar00rootroot00000000000000# QProcess {{ doctable("Core", "QCoroProcess", ("core/qiodevice", "QCoroIODevice")) }} [`QProcess`][qtdoc-qprocess] normally has two features to wait for asynchronously: the process to start and to finish. Since `QProcess` itself doesn't provide the ability to `co_await` those operations, QCoro provides a wrapper class `QCoroProcess`. To wrap a `QProcess` object into the `QCoroProcess` wrapper, use [`qCoro()`][qcoro-coro]: ```cpp QCoroProcess qCoro(QProcess &); QCoroProcess qCoro(QProcess *); ``` Same as `QProcess` is a subclass of `QIODevice`, `QCoroProcess` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected QIODevice functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details. ## `waitForStarted()` Waits for the process to be started or until it times out. Returns `bool` indicating whether the process has started successfuly or timed out. See documentation for [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted] for details. ```cpp Awaitable auto QCoroProcess::waitForStarted(int timeout = 30'000); Awaitable auto QCoroProcess::waitForStarted(std::chrono::milliseconds timeout); ``` ## `waitForFinished()` Waits for the process to finish or until it times out. Returns `bool` indicating whether the process has finished successfuly or timed out. See documentation for [`QProcess::waitForFinished()`][qtdoc-qprocess-waitForFinished] for details. ```cpp Awaitable auto QCoroProcess::waitForFinishedint timeout = 30'000); Awaitable auto QCoroProcess::waitForFinished(std::chrono::milliseconds timeout); ``` ## `start()` QCoroProcess provides an additional method called `start()` which is equivalent to calling `QProcess::start()` followed by `QCoroProcess::waitForStarted()`. This operation is `co_awaitable` as well. See the documentation for [`QProcess::start()`][qtdoc-qprocess-start] and [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted] for details. ```cpp Awaitable auto QCoroProcess::start(QIODevice::OpenMode openMode) = QIODevice::ReadOnly; Awaitable auto QCoroProcess::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode openMode = QIODevice::ReadOnly); ``` ## Examples ```cpp {% include "../../examples/qprocess.cpp" %} ``` [qtdoc-qprocess]: https://doc.qt.io/qt-5/qprocess.html [qtdoc-qprocess-start]: https://doc.qt.io/qt-5/qprocess.html#start [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted [qtdoc-qprocess-waitForFiished]: https://doc.qt.io/qt-5/qprocess.html#waitForFinished [qcoro-coro]: ../coro/coro.md [qcoro-qcoroiodevice]: qiodevice.md qcoro-0.4.0/docs/reference/core/qtimer.md000066400000000000000000000022021416564147600203110ustar00rootroot00000000000000# QTimer {{ doctable("Core", "QCoroTimer") }} ```cpp QTimer timer; timer.start(1s); co_await timer; ``` The QCoro frameworks allows `co_await`ing on [QTimer][qdoc-qtimer] object. The co-awaiting coroutine is suspended, until the timer finishes, that is until [`QTimer::timeout()`][qdoc-qtimer-timeout] signal is emitted. The timer must be active. If the timer is not active (not started yet or already finished) the `co_await` expression will return immediately. To make it work, include `QCoroTimer` in your implementation. ```cpp #include #include using namespace std::chrono_literals; QCoro::Task<> MyClass::pretendWork() { // Creates and starts a QTimer that will tick every second QTimer timer; timer.setInterval(1s); timer.start(); for (int i = 1; i <= 100; ++i) { // Wait for the timer to tick co_await timer; // Update the progress bar value mProgressBar->setValue(i); // And repeat... } // ... until the for loop finishes. } ``` [qdoc-qtimer]: https://doc.qt.io/qt-5/qtimer.html [qdoc-qtimer-timeout]: https://doc.qt.io/qt-5/qtimer.html#timeout qcoro-0.4.0/docs/reference/coro/000077500000000000000000000000001416564147600165045ustar00rootroot00000000000000qcoro-0.4.0/docs/reference/coro/coro.md000066400000000000000000000052771416564147600200030ustar00rootroot00000000000000# qCoro() ## Wrapping Qt Signals ```cpp Awaitable qCoro(QObject *, QtSignalPtr); ``` It is possible to `co_await` an emission of a Qt signal. Signal arguments are returned as a result of the `co_await` expression: ```cpp MyDialog dialog; ... const int result = co_await qCoro(&dialog, &QDialog::finished); if (result == QDialog::Accepted) { ... } ``` If the signal has more than one argument, they are returned as a tuple: ```cpp QProcess process; ... const auto [exitCode, exitStatus] = co_await qCoro(&process, &QProcess::finished); ``` If the signal has no arguments, then the result of the `co_await` expression is `void`. ## Wrapping Qt Types ```cpp QCoroType qCoro(QtClass *); QCoroType qCoro(QtClass &); ``` This function is overloaded for all Qt types supported by this library. It accepts either a pointer or a reference to a Qt type, and returns a QCoro type that wraps the Qt type and provides coroutine-friendly API for the type. Some objects have only a single asynchronous event, so it makes sense to make them directly `co_await`able. An example is `QTimer`, where only one thing can be `co_await`ed - the timer timeout. Thus with QCoro, it's possible to simply do this: ```cpp QTimer timer; ... co_await timer; ``` However, some Qt classes have multiple asynchronous operations that the user may want to `co_await`. For such types, simply `co_await`ing the class instance doesn't make sense since it's not clear what operation is being `co_await`ed. For those types, QCoro provides `qCoro()` function which returns a wrapper that provides coroutine-friendly versions of the asynchronous methods for the given type. Let's take QProcess as an example: one may want to `co_await` for the program to start or finish. Therefore the type must be wrapped into `qCoro()` like this: ```cpp QProcess process; // Wait for the process to be started co_await qCoro(process).start(...); // The process is running now ... ... // Wait for it to finish co_await qCoro(process).finished(); // The process is no longer running ... ``` `qCoro()` is simply overloaded for all the Qt types currently supported by the QCoro library. The function returns a wrapper object (e.g. `QCoro::detail::QCoroProcess`) which copies the QProcess API. It doesn't copy the entire API, only the bits that we want to make `co_await`able. When you call one of those metods (e.g. `QCoroProcess::start()`), it returns an awaitable type that calls `QProcess::start()`, suspends the coroutine and resumes it again when the wrapped `QProcess` object emits the `started()` signal. Normally you don't need to concern yourself with anything inside the `QCoro::detail` namespace, it's mentioned in the previous paragraph simply to explain how the wrapper works. qcoro-0.4.0/docs/reference/coro/index.md000066400000000000000000000014011416564147600201310ustar00rootroot00000000000000# Coro module The Coro module contains the fundamental coroutine functionality - the coroutine return type [QCoro::Task][qcoro-task]. Another useful bit of the Coro module is the [qCoro()][qcoro-coro] wrapper function that wraps native Qt types into a coroutine-friendly versions supported by QCoro (check the Core, Network and DBus modules of QCoro to see which Qt types are currently supported by QCoro). If you don't want to use any of the Qt types supported by QCoro in your code, but you still want to use C++ coroutines with QCoro, you can simply just link against `QCoro::Coro` target in your CMakeLists.txt. This will give you all you need to start implementing custom coroutine-native types with Qt and QCoro. [qcoro-task]: task.md [qcoro-coro]: coro.md qcoro-0.4.0/docs/reference/coro/task.md000066400000000000000000000052171416564147600177750ustar00rootroot00000000000000# QCoro::Task {{ doctable("Coro", "Task") }} ```cpp template class QCoro::Task ``` Any coroutine that wants to `co_await` one of the types supported by the QCoro library must have return type `QCoro::Task`, where `T` is the type of the "regular" coroutine return value. There's no need by the user to interact with or construct `QCoro::Task` manually, the object is constructed automatically by the compiler before the user code is executed. To return a value from a coroutine, use `co_return`, which will store the result in the `Task` object and leave the coroutine. ```cpp QCoro::Task getUserName(UserID userId) { ... // Obtain a QString by co_awaiting another coroutine const QString result = co_await fetchUserNameFromDb(userId); ... // Return the QString from the coroutine as you would from a regular function, // just use `co_return` instead of `return` keyword. co_return result; } ``` To obtain the result of a coroutine that returns `QCoro::Task`, the result must be `co_await`ed. When the coroutine `co_return`s a result, the result is stored in the `Task` object and the `co_await`ing coroutine is resumed. The result is obtained from the returned `Task` object and returned as a result of the `co_await` call. ```cpp QCoro::Task getUserDetails(UserID userId) { ... const QString name = co_await getUserName(userId); ... } ``` !!! info "Exception Propagation" When coroutines throws an unhandled exception, the exception is stored in the `Task` object and is re-thrown from the `co_await` call in the awaiting coroutine. ## Blocking wait Sometimes it's necessary to wait for a coroutine in a blocking manner - this is especially useful in tests where possibly no event loop is running. QCoro has `QCoro::waitFor()` function which takes `QCoro::Task` (that is, result of calling any QCoro-based coroutine) and blocks until the coroutine finishes. If the coroutine has a non-void return value, the value is returned from `waitFor().` ```cpp QCoro::Task computeAnswer() { std::this_thread::sleep_for(std::chrono::year{7'500'000}); co_return 42; } void nonCoroutineFunction() { // The following line will block as if computeAnswer were not a coroutine. const int answer = QCoro::waitFor(computeAnswer()); std::cout << "The answer is: " << answer << std::endl; } ``` !!! info "Event loops" The implementation internally uses a `QEventLoop` to wait for the coroutine to be completed. This means that a `QCoreApplication` instance must exist, although it does not need to be executed. Usual warnings about using a nested event loop apply here as well. qcoro-0.4.0/docs/reference/dbus/000077500000000000000000000000001416564147600164775ustar00rootroot00000000000000qcoro-0.4.0/docs/reference/dbus/index.md000066400000000000000000000004261416564147600201320ustar00rootroot00000000000000# DBus Module The `DBus` module contains coroutine-friendly wrapper for [QtDBus][qtdoc-qtdbus] classes. ## CMake usage ``` find_package(QCoro6 COMPONENTS DBus) ... target_link_libraries(my-target QCoro::DBus) ``` [qtdoc-qtdbus]: https://doc.qt.io/qt-5/qtdbus-index.html qcoro-0.4.0/docs/reference/dbus/qdbuspendingcall.md000066400000000000000000000050551416564147600223450ustar00rootroot00000000000000# QDBusPendingCall {{ doctable("DBus", "QCoroDBusPendingCall") }} [`QDBusPendingCall`][qdoc-qdbuspendingcall] on its own doesn't have any operation that could be awaited asynchronously, this is usually done through a helper class called [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]. To simplify the API, QCoro allows to directly `co_await` completion of the pending call or use a wrapper class `QCoroDBusPendingCall`. To wrap a `QDBusPendingCall` into a `QCoroDBusPendingCall`, use [`qCoro()`][qcoro-coro]: ```cpp QCoroDBusPendingCall qCoro(const QDBusPendingCall &); ``` To await completion of the pending call without the `qCoro` wrapper, just use the pending call in a `co_await` expression. The behavior is identical to awaiting on result of `QCoroDBusPendingCall::waitForFinished()`. ```cpp QDBusPendingCall call = interface.asyncCall(...); const QDBusReply<...> reply = co_await pendigCall; ``` !!! info "`QDBusPendingCall` vs. `QDBusPendingReply`" As the Qt documentation for [`QDBusPendingCall`][qdoc-qdbuspendingcall] says, you are more likely to use [`QDBusPendingReply`][qdoc-qdbuspendingreply] in application code than `QDBusPendingCall`. QCoro has explicit support for `QDBusPendingCall` to allow using functions that return `QDBusPendingCall` directly in `co_await` expressions without the programmer having to first convert it to `QDBusPendingReply`. `QDBusPendingReply` can be constructed from a `QDBusMessage`, which is a result of awaiting `QDBusPendingCall`, therefore it's possible to perform both the conversion and awaiting in a single line of code: ```cpp QDBusPendingReply<...> reply = co_await iface.asyncCall(...); ``` Note that [`QDBusAbstractInterface::asyncCall`][qdoc-qdbusabstractinterface-asyncCall] returns a `QDBusPendingCall`. ## `waitForFinished()` {% include-markdown "../../../qcoro/dbus/qcorodbuspendingcall.h" dedent=true rewrite-relative-urls=false start="" end="" %} ## Example ```cpp {% include "../../examples/qdbus.cpp" %} ``` [qdoc-qdbuspendingcall]: https://doc.qt.io/qt-5/qdbuspendingcall.html [qdoc-qdbuspendingreply]: https://doc.qt.io/qt-5/qdbuspendingreply.html [qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html [qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished [qdoc-qdbusabstractinterface-asyncCall]: https://doc.qt.io/qt-5/qdbusabstractinterface.html#asyncCall-1 [qcoro-coro]: ../coro/coro.md qcoro-0.4.0/docs/reference/dbus/qdbuspendingreply.md000066400000000000000000000044221416564147600225620ustar00rootroot00000000000000# QDBusPendingReply {{ doctable("DBus", "QCoroDBusPendingReply") }} [`QDBusPendingReply`][qdoc-qdbuspendingreply] on its own doesn't have any operation that could be awaited asynchronously, this is usually done through a helper class called [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]. To simplify the API, QCoro allows to directly `co_await` completion of the pending reply or use a wrapper class `QCoroDBusPendingReply`. To wrap a `QDBusPendingReply` into a `QCoroDBusPendingReply`, use [`qCoro()`][qcoro-coro]: ```cpp template QCoroDBusPendingCall qCoro(const QDBusPendingReply &); ``` !!! info "`QDBusPendingReply` in Qt5 vs Qt6" `QDBusPendingReply` in Qt6 is a variadic template, meaning that it can take any amount of template arguments. In Qt5, however, `QDBusPendingReply` is a template class that accepts only up to 8 paremeters. In QCoro the `QCoroDBusPendingReply` wrapper is implemented as a variadic template for compatibility with Qt6, but when building against Qt5, the number of template parameters is artificially limited to 8 to mirror the limitation of Qt5 `QDBusPendingReply` limitation. To await completion of the pending call without the `qCoro` wrapper, just use the pending call in a `co_await` expression. The behavior is identical to awaiting on result of `QCoroDBusPendingReply::waitForFinished()`. ```cpp QDBusPendingReply<...> reply = interface.asyncCall(...); co_await reply; // Now the reply is finished and the result can be retrieved. ``` ## `waitForFinished()` {% include-markdown "../../../qcoro/dbus/qcorodbuspendingreply.h" dedent=true rewrite-relative-urls=false start="" end="" %} ## Example ```cpp {% include "../../examples/qdbus.cpp" %} ``` [qdoc-qdbuspendingcall]: https://doc.qt.io/qt-5/qdbuspendingcall.html [qdoc-qdbuspendingreply]: https://doc.qt.io/qt-5/qdbuspendingreply.html [qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html [qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished [qdoc-qdbusabstractinterface-asyncCall]: https://doc.qt.io/qt-5/qdbusabstractinterface.html#asyncCall-1 [qcoro-coro]: ../coro/coro.md qcoro-0.4.0/docs/reference/network/000077500000000000000000000000001416564147600172335ustar00rootroot00000000000000qcoro-0.4.0/docs/reference/network/index.md000066400000000000000000000004541416564147600206670ustar00rootroot00000000000000# Network Module The `Network` module contains coroutine-friendly wrapper for [QtNetwork][qtdoc-qtnetwork] classes. ## CMake usage ``` find_package(QCoro6 COMPONENTS Network) ... target_link_libraries(my-target QCoro::Network) ``` [qtdoc-qtnetwork]: https://doc.qt.io/qt-5/qtnetwork-index.html qcoro-0.4.0/docs/reference/network/qabstractsocket.md000066400000000000000000000075071416564147600227630ustar00rootroot00000000000000# QAbstractSocket {{ doctable("Network", "QCoroAbstractSocket", ("core/qiodevice", "QCoroIODevice")) }} [`QAbstractSocket`][qtdoc-qabstractsocket] is a base class for [`QTcpSocket`][qtdoc-qtcpsocket] and [`QUdpSocket`][qtdoc-qudpsocket] and has some potentially asynchronous operations. In addition to reading and writing, which are provided by [`QIODevice`][qtdoc-qiodevice] baseclass and can be used with coroutines thanks to QCoro's [`QCoroIODevice`][qcoro-qcoroiodevice]. Those operations are connecting to and disconnecting from the server. Since `QAbstractSocket` doesn't provide the ability to `co_await` those operations, QCoro provides a wrapper calss `QCoroAbstractSocket`. To wrap a `QAbstractSocket` object into the `QCoroAbstractSocket` wrapper, use [`qCoro()`][qcoro-coro]: ```cpp QCoroAbstractSocket qCoro(QAbstractSocket &); QCoroAbstractSocket qCoro(QAbstractSocket *); ``` Same as `QAbstractSocket` is a subclass of `QIODevice`, `QCoroAbstractSocket` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected `QIODevice` functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details. ## `waitForConnected()` Waits for the socket to connect or until it times out. Returns `bool` indicating whether connection has been established or whether the operation has timed out. The coroutine is not suspended if the socket is already connected. See documentation for [`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected] for details. ```cpp Awaitable auto QCoroAbstractSocket::waitForConnected(int timeout_msecs = 30'000); Awaitable auto QCoroAbstractSocket::waitForConnected(std::chrono::milliseconds timeout); ``` ## `waitForDisconnected()` Waits for the socket to disconnect from the server or until the operation times out. The coroutine is not suspended if the socket is already disconnected. See documentation for [`QAbstractSocket::waitForDisconnected()`][qtdoc-qabstractsocket-waitForDisconnected] for details. ```cpp Awaitable auto QCoroAbstractSocket::waitForDisconnected(timeout_msecs = 30'000); Awaitable auto QCoroAbstractSocket::waitForDisconnected(std::chrono::milliseconds timeout); ``` ## `connectToHost()` `QCoroAbstractSocket` provides an additional method called `connectToHost()` which is equivalent to calling `QAbstractSocket::connectToHost()` followed by `QAbstractSocket::waitForConnected()`. This operation is co_awaitable as well. See the documentation for [`QAbstractSocket::connectToHost()`][qtdoc-qabstractsocket-connectToHost] and [`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected] for details. ```cpp Awaitable auto QCoroAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, QIODevice::OpenMode openMode = QIODevice::ReadOnly); Awaitable auto QCoroAbstractSocket::connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode = QIODevice::ReadOnly, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol); ``` ## Examples ```cpp {% include "../../examples/qtcpsocket.cpp" %} ``` [qtdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html [qtdoc-qtcpsocket]: https://doc.qt.io/qt-5/qtcpsocket.html [qtdoc-qudpsocket]: https://doc.qt.io/qt-5/qudpsocket.html [qtdoc-qabstractsocket]: https://doc.qt.io/qt-5/qabstractsocket.html [qtdoc-qabstractsocket-connectToServer]: https://doc.qt.io/qt-5/qabstractsocket.html#connectToServer [qtdoc-qabstractsocket-waitForConnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForConnected [qtdoc-qabstractsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForDisconnected [qcoro-coro]: ../coro/coro.md [qcoro-qcoroiodevice]: ../core/qiodevice.md qcoro-0.4.0/docs/reference/network/qlocalsocket.md000066400000000000000000000071611416564147600222460ustar00rootroot00000000000000# QLocalSocket {{ doctable("Network", "QCoroLocalSocket", ("core/qiodevice", "QCoroIODevice")) }} [`QLocalSocket`][qtdoc-qlocalsocket] has several potentially asynchronous operations in addition to reading and writing, which are provided by [`QIODevice`][qtdoc-qiodevice] baseclass and can be used with coroutines thanks to QCoro's [`QCoroIODevice`][qcoro-qcoroiodevice]. Those operations are connecting to and disconnecting from the server. Since `QLocalSocket` doesn't provide the ability to `co_await` those operations, QCoro provides a wrapper calss `QCoroLocalSocket`. To wrap a `QLocalSocket` object into the `QCoroLocalSocket` wrapper, use [`qCoro()`][qcoro-coro]: ```cpp QCoroLocalSocket qCoro(QLocalSocket &); QCoroLocalSocket qCoro(QLocalSocket *); ``` Same as `QLocalSocket` is a subclass of `QIODevice`, `QCoroLocalSocket` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected `QIODevice` functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details. ## `waitForConnected()` Waits for the socket to connect or until it times out. Returns `bool` indicating whether connection has been established or whether the operation has timed out. The coroutine is not suspended if the socket is already connected. See documentation for [`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected] for details. ```cpp Awaitable auto QCoroLocalSocket::waitForConnected(int timeout_msecs = 30'000); Awaitable auto QCoroLocalSocket::waitForConnected(std::chrono::milliseconds timeout); ``` ## `waitForDisconnected()` Waits for the socket to disconnect from the server or until the operation times out. The coroutine is not suspended if the socket is already disconnected. See documentation for [`QLocalSocket::waitForDisconnected()`][qtdoc-qlocalsocket-waitForDisconnected] for details. ```cpp Awaitable auto QCoroLocalSocket::waitForDisconnected(timeout_msecs = 30'000); Awaitable auto QCoroLocalSocket::waitForDisconnected(std::chrono::milliseconds timeout); ``` ## `connectToServer()` `QCoroLocalSocket` provides an additional method called `connectToServer()` which is equivalent to calling `QLocalSocket::connectToServer()` followed by `QLocalSocket::waitForConnected()`. This operation is co_awaitable as well. See the documentation for [`QLocalSocket::connectToServer()`][qtdoc-qlocalsocket-connectToServer] and [`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected] for details. ```cpp Awaitable auto QCoroLocalSocket::connectToServer(QIODevice::OpenMode openMode = QIODevice::ReadOnly); Awaitable auto QCoroLocalSocket::connectToServer(const QString &name, QIODevice::OpenMode openMode = QIODevice::ReadOnly); ``` ## Examples ```cpp QCoro::Task requestDataFromServer(const QString &serverName) { QLocalSocket socket; if (!co_await qCoro(socket).connectToServer(serverName)) { qWarning() << "Failed to connect to the server"; co_return QByteArray{}; } socket.write("SEND ME DATA!"); QByteArray data; while (!data.endsWith("\r\n.\r\n")) { data += co_await qCoro(socket).readAll(); } co_return data; } ``` [qtdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html [qtdoc-qlocalsocket]: https://doc.qt.io/qt-5/qlocalsocket.html [qtdoc-qlocalsocket-connectToServer]: https://doc.qt.io/qt-5/qlocalsocket.html#connectToServer [qtdoc-qlocalsocket-waitForConnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForConnected [qtdoc-qlocalsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForDisconnected [qcoro-coro]: ../coro/coro.md [qcoro-qcoroiodevice]: ../core/qiodevice.md qcoro-0.4.0/docs/reference/network/qnetworkreply.md000066400000000000000000000023431416564147600225050ustar00rootroot00000000000000# QNetworkReply {{ doctable("Network", "QCoroNetworkReply", ("network/qiodevice", "QCoroIODevice")) }} [`QNetworkReply`][qdoc-qnetworkreply] has two asynchronous aspects: one is waiting for the reply to finish, and one for reading the response data as they arrive. QCoro supports both. `QNetworkReply` is a subclass of [`QIODevice`][qdoc-qiodevice], so you can leverage all the features of [`QCoroIODevice`][qcoro-iodevice] to asynchronously read data from the underlying `QIODevice` using coroutines. To wait for the reply to finish, one can simply `co_await` the reply object: ```cpp QNetworkAccessManager nam; auto *reply = co_await nam.get(request); ``` The QCoro frameworks allows `co_await`ing on [QNetworkReply][qdoc-qnetworkreply] objects. The co-awaiting coroutine is suspended, until [`QNetworkReply::finished()`][qdoc-qnetworkreply-finished] signal is emitted. To make it work, include `QCoroNetworkReply` in your implementation. ```cpp {% include "../../examples/qnetworkreply.cpp" %} ``` [qdoc-qnetworkreply]: https://doc.qt.io/qt-5/qnetworkreply.html [qdoc-qnetworkreply-finished]: https://doc.qt.io/qt-5/qnetworkreply.html#finished [qdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html [qcoro-iodevice]: ../core/qiodevice.md qcoro-0.4.0/docs/reference/network/qtcpserver.md000066400000000000000000000023621416564147600217560ustar00rootroot00000000000000# QTcpServer {{ doctable("Network", "QCoroTcpServer") }} [`QTcpServer`][qtdoc-qtcpserver] really only has one asynchronous operation worth `co_await`ing, and that's `waitForNewConnection()`. Since `QTcpServer` doesn't provide the ability to `co_await` those operations, QCoro provides a wrapper class `QCoroTcpServer`. To wrap a `QTcpServer` object into the `QCoroTcpServer` wrapper, use [`qCoro()`][qcoro-coro]: ```cpp QCoroTcpServer qCoro(QTcpServer &); QCoroTcpServer qCoro(QTcpServer *); ``` ## `waitForNewConnection()` Waits until a new incoming connection is available or until it times out. Returns pointer to `QTcpSocket` or `nullptr` if the operation timed out or another error has occured. See documentation for [`QTcpServer::waitForNewConnection()`][qtdoc-qtcpserver-waitForNewConnection] for details. ```cpp Awaitable auto QCoroTcpServer::waitForNewConnection(int timeout_msecs = 30'000); Awaitable auto QCoroTcpServer::waitForNewConnection(std::chrono::milliseconds timeout); ``` ## Examples ```cpp {% include "../../examples/qtcpserver.cpp" %} ``` [qtdoc-qtcpserver]: https://doc.qt.io/qt-5/qtcpserver.html [qtdoc-qtcpserver-waitForNewConnection]: https://doc.qt.io/qt-5/qtcpserver.html#waitForNewConnection [qcoro-coro]: ../coro/coro.md qcoro-0.4.0/docs/stylesheets/000077500000000000000000000000001416564147600161605ustar00rootroot00000000000000qcoro-0.4.0/docs/stylesheets/doctable.css000066400000000000000000000002701416564147600204460ustar00rootroot00000000000000.doctable { width: 100% } .doctable .md-typeset__table { width: 100% } .doctable td { width: 100% } .doctable .md-typeset__table table th { vertical-align: middle } qcoro-0.4.0/examples/000077500000000000000000000000001416564147600144725ustar00rootroot00000000000000qcoro-0.4.0/examples/CMakeLists.txt000066400000000000000000000004421416564147600172320ustar00rootroot00000000000000add_subdirectory(basics) if (QCORO_WITH_QTDBUS) add_subdirectory(dbus) endif() if (QCORO_WITH_QTNETWORK) add_subdirectory(network) endif() if (QCORO_QT_HAS_COMPAT_ABI) add_subdirectory(future) endif() add_subdirectory(iodevice) add_subdirectory(timer) add_subdirectory(chained) qcoro-0.4.0/examples/basics/000077500000000000000000000000001416564147600157365ustar00rootroot00000000000000qcoro-0.4.0/examples/basics/CMakeLists.txt000066400000000000000000000004401416564147600204740ustar00rootroot00000000000000add_executable(await-sync-string await-sync-string.cpp) target_link_libraries(await-sync-string QCoro${QT_VERSION_MAJOR}::Coro) add_executable(await-async-string await-async-string.cpp) target_link_libraries(await-async-string QCoro${QT_VERSION_MAJOR}::Coro Qt${QT_VERSION_MAJOR}::Core) qcoro-0.4.0/examples/basics/await-async-string.cpp000066400000000000000000000163241416564147600221740ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoro/coroutine.h" #include #include #include #include #include #include #include using namespace std::chrono_literals; class FutureString : public QObject { Q_OBJECT public: explicit FutureString(const QString &str) : mStr(str) { QTimer::singleShot(1s, this, [this]() { mReady = true; Q_EMIT ready(); }); } bool isReady() const { return mReady; } QString result() const { return mStr; } Q_SIGNALS: void ready(); private: bool mReady = false; QString mStr; }; // Awaiter is a concept that provides the await_* methods below, which are used by the // co_await expression. // Type is Awaitable if it supports the `co_await` operator. // // When compiler sees a `co_await `, it first tries to obtain an Awaitable type for // the expression result result type: // - first by checking if the current coroutine's promise type has `await_transform()` // that for given type returns an Awaitable // - if it does not have await_transform, it treats the result type as awaitable. // Thus, if the current promise type doesn't have compatible `await_transform()` and the // type itself is not Awaitable, it cannot be `co_await`ed. // // If the Awaitable object has `operator co_await` overload, it calls it to obtain the // Awaiter object. Otherwise the Awaitable object is used as an Awaiter. // class FutureStringAwaiter { public: explicit FutureStringAwaiter(const std::shared_ptr value) noexcept : mFuture(value) { std::cout << "FutureStringAwaiter constructed." << std::endl; } ~FutureStringAwaiter() { std::cout << "FutureStringAwaiter destroyed." << std::endl; } // Called by compiler when starting co_await to check whether the awaited object is by any // chance already ready, so that we could avoid the suspend-resume dance. bool await_ready() noexcept { std::cout << "FutureStringAwaiter::await_ready() called." << std::endl; return mFuture->isReady(); } // Called to tell us that the awaiting coroutine was suspended. // We use the awaitingCoroutine handle to resume the suspended coroutine once the // co_awaited coroutine is finished. void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { std::cout << "FutureStringAwaiter::await_suspend() called." << std::endl; QObject::connect(mFuture.get(), &FutureString::ready, [awaitingCoroutine]() mutable { awaitingCoroutine.resume(); }); } // Called when the co_awaiting coroutine is resumed. Returns result of the // co_awaited expression. QString await_resume() noexcept { std::cout << "FutureStringAwaiter::await_resume() called." << std::endl; return mFuture->result(); } private: std::shared_ptr mFuture; }; class FutureStringAwaitable { public: FutureStringAwaitable(const std::shared_ptr value) noexcept : mFuture(value) { std::cout << "FutureStringAwaitable constructed." << std::endl; } ~FutureStringAwaitable() { std::cout << "FutureStringAwaitable destroyed." << std::endl; } FutureStringAwaiter operator co_await() { std::cout << "FutureStringAwaitable::operator co_await() called." << std::endl; return FutureStringAwaiter{mFuture}; } private: std::shared_ptr mFuture; }; class VoidPromise { public: explicit VoidPromise() { std::cout << "VoidPromise constructed." << std::endl; } ~VoidPromise() { std::cout << "VoidPromise destroyed." << std::endl; } struct promise_type { explicit promise_type() { std::cout << "VoidPromise::promise_type constructed." << std::endl; } ~promise_type() { std::cout << "VoidPromise::promise_type destroyed." << std::endl; } // Says whether the coroutine body should be executed immediately (`suspend_never`) // or whether it should be executed only once the coroutine is co_awaited. std::suspend_never initial_suspend() const noexcept { return {}; } // Says whether the coroutine should be suspended after returning a result // (`suspend_always`) or whether it should just end and the frame pointer and everything // should be destroyed. std::suspend_never final_suspend() const noexcept { return {}; } // Called by the compiler during initial coroutine setup to obtain the object that // will be returned from the coroutine when it is suspended. // Sicne this is a promise type for VoidPromise, we return a VoidPromise. VoidPromise get_return_object() { std::cout << "VoidPromise::get_return_object() called." << std::endl; return VoidPromise(); } // Called by the compiler when an exception propagates from the coroutine. // Alternatively, we could declare `set_exception()` which the compiler would // call instead to let us handle the exception (e.g. propagate it) void unhandled_exception() { std::terminate(); } // The result of the promise. Since our promise is void, we must implement `return_void()`. // If our promise would be returning a value, we would have to implement `return_value()` // instead. void return_void() const noexcept {}; FutureStringAwaitable await_transform(const std::shared_ptr &future) { std::cout << "VoidPromise::await_transform called." << std::endl; return FutureStringAwaitable{future}; } }; }; std::shared_ptr regularFunction() { return std::make_shared(QStringLiteral("Hello World!")); } // This function co_awaits, therefore it's a co-routine and must // have a promise type to return to the caller. VoidPromise myCoroutine() { // 1. Compiler creates a new coroutine frame `f` // 2. Compiler obtains a return object from the promise. // The promise is of type `std::coroutine_traits::promise_type`, // which is `CurrentFunctionReturnType::promise_type` (if there is no specialization for // `std::coroutine_traits`) // 3. Compiler starts execution of the coroutine body by calling `resume()` on the // current coroutine's std::coroutine_handle (obtained from the promise by // `std::coroutine_handlepromise)>::from_promise(f->promise) std::cout << "myCoroutine() started." << std::endl; const auto result = co_await regularFunction(); std::cout << "Result successfully co_await-ed: " << result.toStdString() << std::endl; qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); QMetaObject::invokeMethod(&app, myCoroutine); QTimer t; QObject::connect(&t, &QTimer::timeout, &app, []() { std::cout << "Tick" << std::endl; }); t.start(100ms); return app.exec(); return 0; } #include "await-async-string.moc" qcoro-0.4.0/examples/basics/await-sync-string.cpp000066400000000000000000000132311416564147600220250ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoro/coroutine.h" #include #include // Awaiter is a concept that provides the await_* methods below, which are used by the // co_await expression. // Type is Awaitable if it supports the `co_await` operator. // // When compiler sees a `co_await `, it first tries to obtain an Awaitable type for // the expression result result type: // - first by checking if the current coroutine's promise type has `await_transform()` // that for given type returns an Awaitable // - if it does not have await_transform, it treats the result type as awaitable. // Thus, if the current promise type doesn't have compatible `await_transform()` and the // type itself is not Awaitable, it cannot be `co_await`ed. // // If the Awaitable object has `operator co_await` overload, it calls it to obtain the // Awaiter object. Otherwise the Awaitable object is used as an Awaiter. // class StringAwaiter { public: explicit StringAwaiter(const std::string &value) noexcept : mValue(value) { std::cout << "StringAwaiter constructed with value '" << value << "'." << std::endl; } ~StringAwaiter() { std::cout << "StringAwaiter destroyed." << std::endl; } bool await_ready() noexcept { std::cout << "StringAwaiter::await_ready() called." << std::endl; return false; } void await_suspend(std::coroutine_handle<>) noexcept { std::cout << "StringAwaiter::await_suspend() called." << std::endl; } std::string await_resume() noexcept { std::cout << "StringAwaiter::await_resume() called." << std::endl; return mValue; } private: std::string mValue; }; class StringAwaitable { public: StringAwaitable(std::string str) noexcept : mStr(std::move(str)) { std::cout << "StringAwaitable constructored with value '" << mStr << "'." << std::endl; } ~StringAwaitable() { std::cout << "StringAwaitable destroyed." << std::endl; } StringAwaiter operator co_await() { std::cout << "StringAwaitable::operator co_await() called." << std::endl; return StringAwaiter{mStr}; } private: std::string mStr; }; class VoidPromise { public: explicit VoidPromise() { std::cout << "VoidPromise constructed." << std::endl; } ~VoidPromise() { std::cout << "VoidPromise destroyed." << std::endl; } struct promise_type { explicit promise_type() { std::cout << "VoidPromise::promise_type constructed." << std::endl; } ~promise_type() { std::cout << "VoidPromise::promise_type destroyed." << std::endl; } // Says whether the coroutine body should be executed immediately (`suspend_never`) // or whether it should be executed only once the coroutine is co_awaited. std::suspend_never initial_suspend() const noexcept { return {}; } // Says whether the coroutine should be suspended after returning a result // (`suspend_always`) or whether it should just end and the frame pointer and everything // should be destroyed. std::suspend_never final_suspend() const noexcept { return {}; } // Called by the compiler during initial coroutine setup to obtain the object that // will be returned from the coroutine when it is suspended. // Sicne this is a promise type for VoidPromise, we return a VoidPromise. VoidPromise get_return_object() { std::cout << "VoidPromise::get_return_object() called." << std::endl; return VoidPromise(); } // Called by the compiler when an exception propagates from the coroutine. // Alternatively, we could declare `set_exception()` which the compiler would // call instead to let us handle the exception (e.g. propagate it) void unhandled_exception() { std::terminate(); } // The result of the promise. Since our promise is void, we must implement `return_void()`. // If our promise would be returning a value, we would have to implement `return_value()` // instead. void return_void() const noexcept {}; StringAwaitable await_transform(std::string str) { std::cout << "VoidPromise::await_transform for string '" << str << "' called." << std::endl; return StringAwaitable{std::move(str)}; } }; }; std::string regularFunction() { return "Hello World!"; } // This function co_awaits, therefore it's a co-routine and must // have a promise type to return to the caller. VoidPromise myCoroutine() { // 1. Compiler creates a new coroutine frame `f` // 2. Compiler obtains a return object from the promise. // The promise is of type `std::coroutine_traits::promise_type`, // which is `CurrentFunctionReturnType::promise_type` (if there is no specialization for // `std::coroutine_traits`) // 3. Compiler starts execution of the coroutine body by calling `resume()` on the // current coroutine's std::coroutine_handle (obtained from the promise by // `std::coroutine_handlepromise)>::from_promise(f->promise) std::cout << "myCoroutine() started." << std::endl; const auto result = co_await regularFunction(); std::cout << "Result successfully co_await-ed: " << result << std::endl; } int main() { std::cout << "Calling myCoroutine() from main()." << std::endl; myCoroutine(); std::cout << "Returned from myCoroutine() to main()." << std::endl; return 0; } qcoro-0.4.0/examples/chained/000077500000000000000000000000001416564147600160655ustar00rootroot00000000000000qcoro-0.4.0/examples/chained/CMakeLists.txt000066400000000000000000000002221416564147600206210ustar00rootroot00000000000000add_executable(chained-example main.cpp) target_link_libraries(chained-example QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core ) qcoro-0.4.0/examples/chained/main.cpp000066400000000000000000000031531416564147600175170ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "task.h" #include "qcorotimer.h" #include #include #include #include using namespace std::chrono_literals; QCoro::Task generateRandomString() { std::cout << "GenerateRandomString started" << std::endl; QTimer timer; timer.start(1s); std::cout << "GenerateRandomString \"generating\"..." << std::endl; co_await timer; std::cout << "GenerateRandomString finished \"generating\"" << std::endl; std::cout << "GenerateRandomString co_returning to caller" << std::endl; co_return QStringLiteral("RandomString!"); } QCoro::Task generateRandomNumber() { std::cout << "GenerateRandomNumber started" << std::endl; std::cout << "GenerateRandomNumber co_awaiting on generateRandomString()" << std::endl; const QString string = co_await generateRandomString(); std::cout << "GenerateRandomNumber successfully co_awaited on generateRandomString() and " "co_returns result" << std::endl; co_return string.size(); } QCoro::Task<> logRandomNumber() { std::cout << "LogRandomNumber started" << std::endl; std::cout << "LogRandomNumber co_awaiting on generateRandomNumber()" << std::endl; const int number = co_await generateRandomNumber(); std::cout << "Random number for today is: " << number << std::endl; qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app{argc, argv}; QTimer::singleShot(0, qApp, logRandomNumber); return app.exec(); } qcoro-0.4.0/examples/dbus/000077500000000000000000000000001416564147600154275ustar00rootroot00000000000000qcoro-0.4.0/examples/dbus/CMakeLists.txt000066400000000000000000000001301416564147600201610ustar00rootroot00000000000000add_subdirectory(common) add_subdirectory(regular-blocking) add_subdirectory(coroutine) qcoro-0.4.0/examples/dbus/common/000077500000000000000000000000001416564147600167175ustar00rootroot00000000000000qcoro-0.4.0/examples/dbus/common/CMakeLists.txt000066400000000000000000000014651416564147600214650ustar00rootroot00000000000000 # Build the dbus-server as a stand-alone executable to workaround QTBUG-92107 add_executable(dbusserver dbusserver.cpp) set_target_properties(dbusserver PROPERTIES COMPILE_DEFINITIONS STANDALONE ) target_link_libraries(dbusserver Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) #-----------------------------------------------------# set(common_SRCS dbusserver.cpp ) add_library(examples-dbus-common STATIC ${common_SRCS}) set_target_properties(examples-dbus-common PROPERTIES COMPILE_DEFINITIONS SERVER_EXEC_PATH=\"$\") target_include_directories(examples-dbus-common INTERFACE $ ) target_link_libraries(examples-dbus-common PUBLIC Qt${QT_VERSION_MAJOR}::Core PRIVATE Qt${QT_VERSION_MAJOR}::DBus ) qcoro-0.4.0/examples/dbus/common/dbusserver.cpp000066400000000000000000000030651416564147600216130ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "dbusserver.h" #include #include #include #include #include const QString DBusServer::serviceName = QStringLiteral("org.kde.qoro.dbustest"); const QString DBusServer::objectPath = QStringLiteral("/"); const QString DBusServer::interfaceName = QStringLiteral("org.kde.qoro.dbuserver"); DBusServer::DBusServer() { qInfo() << "DBusServer started"; auto bus = QDBusConnection::sessionBus(); bus.registerService(serviceName); bus.registerObject(objectPath, interfaceName, this, QDBusConnection::ExportAllSlots); } QString DBusServer::blockingPing(int seconds) const { qInfo() << "S: Received ping request..."; std::this_thread::sleep_for(std::chrono::seconds{seconds}); qInfo() << "S: sending PONG response"; return QStringLiteral("PONG!"); } std::unique_ptr DBusServer::runStadaloneServer() { #ifdef SERVER_EXEC_PATH auto process = std::make_unique(); process->setProcessChannelMode(QProcess::ForwardedChannels); process->start(QStringLiteral(SERVER_EXEC_PATH), {}, QIODevice::ReadOnly); process->waitForStarted(); if (process->state() != QProcess::Running) { qCritical() << "Failed to start server process:" << process->error(); } return process; #else return {}; #endif } #ifdef STANDALONE int main(int argc, char **argv) { QCoreApplication app(argc, argv); DBusServer server; return app.exec(); } #endif qcoro-0.4.0/examples/dbus/common/dbusserver.h000066400000000000000000000007621416564147600212610ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include #include class DBusServer : public QObject { Q_OBJECT public: static const QString serviceName; static const QString objectPath; static const QString interfaceName; explicit DBusServer(); static std::unique_ptr runStadaloneServer(); public Q_SLOTS: QString blockingPing(int seconds) const; }; qcoro-0.4.0/examples/dbus/coroutine/000077500000000000000000000000001416564147600174365ustar00rootroot00000000000000qcoro-0.4.0/examples/dbus/coroutine/CMakeLists.txt000066400000000000000000000004061416564147600221760ustar00rootroot00000000000000set(dbustest_SRCS main.cpp ) add_executable(dbustest-coro ${dbustest_SRCS}) target_link_libraries(dbustest-coro QCoro${QT_VERSION_MAJOR}DBus examples-dbus-common Threads::Threads Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) qcoro-0.4.0/examples/dbus/coroutine/main.cpp000066400000000000000000000030351416564147600210670ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "coroutine.h" #include "qcorodbus.h" #include "task.h" #include "common/dbusserver.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; QCoro::Task<> dbusWorker() { auto bus = QDBusConnection::sessionBus(); auto iface = QDBusInterface{DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName, bus}; qInfo() << "Sending PING"; QDBusReply response = co_await iface.asyncCall(QStringLiteral("blockingPing"), 1); if (const auto &err = response.error(); err.isValid()) { qWarning() << "DBus call failed:" << err.message(); } qInfo() << "Received response:" << response.value(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); auto process = DBusServer::runStadaloneServer(); QTimer tickTimer; QObject::connect(&tickTimer, &QTimer::timeout, &app, []() { std::cout << QDateTime::currentDateTime().toString(Qt::ISODateWithMs).toStdString() << " Tick!" << std::endl; }); tickTimer.start(400ms); QTimer dbusTimer; QObject::connect(&dbusTimer, &QTimer::timeout, &app, dbusWorker); dbusTimer.start(2s); return app.exec(); } qcoro-0.4.0/examples/dbus/regular-blocking/000077500000000000000000000000001416564147600206565ustar00rootroot00000000000000qcoro-0.4.0/examples/dbus/regular-blocking/CMakeLists.txt000066400000000000000000000003331416564147600234150ustar00rootroot00000000000000set(dbustest_SRCS main.cpp ) add_executable(dbustest ${dbustest_SRCS}) target_link_libraries(dbustest examples-dbus-common Threads::Threads Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) qcoro-0.4.0/examples/dbus/regular-blocking/main.cpp000066400000000000000000000026261416564147600223140ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "common/dbusserver.h" #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; void dbusWorker() { auto bus = QDBusConnection::sessionBus(); auto iface = QDBusInterface{DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName, bus}; qInfo() << "Sending PING"; QDBusReply response = iface.call(QStringLiteral("blockingPing"), 1); if (const auto &err = response.error(); err.isValid()) { qWarning() << "DBus call failed:" << err.message(); } qInfo() << "Received response:" << response.value(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); auto process = DBusServer::runStadaloneServer(); QTimer tickTimer; QObject::connect(&tickTimer, &QTimer::timeout, &app, []() { std::cout << QDateTime::currentDateTime().toString(Qt::ISODateWithMs).toStdString() << " Tick!" << std::endl; }); tickTimer.start(200ms); QTimer dbusTimer; QObject::connect(&dbusTimer, &QTimer::timeout, &app, dbusWorker); dbusTimer.start(2s); return app.exec(); } qcoro-0.4.0/examples/future/000077500000000000000000000000001416564147600160045ustar00rootroot00000000000000qcoro-0.4.0/examples/future/CMakeLists.txt000066400000000000000000000002661416564147600205500ustar00rootroot00000000000000add_executable(future-example main.cpp) target_link_libraries(future-example QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Concurrent ) qcoro-0.4.0/examples/future/main.cpp000066400000000000000000000021331416564147600174330ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorofuture.h" #include #include #include #include #include QCoro::Task<> startTask() { const auto data = co_await QtConcurrent::run([]() { QVector data; std::random_device rd{}; std::mt19937 gen{rd()}; data.reserve(10'000'000); for (int i = 0; i < 10'000'000; ++i) { data.push_back(gen()); } return data; }); std::cout << "Generated " << data.size() << " random numbers" << std::endl; const auto sum = co_await QtConcurrent::filteredReduced( data, [](const auto &) { return true; }, [](std::uint64_t &interm, std::uint64_t val) { interm += val; }, QtConcurrent::UnorderedReduce); std::cout << "Calculated result: " << sum << std::endl; qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); QTimer::singleShot(0, startTask); return app.exec(); } qcoro-0.4.0/examples/iodevice/000077500000000000000000000000001416564147600162615ustar00rootroot00000000000000qcoro-0.4.0/examples/iodevice/CMakeLists.txt000066400000000000000000000003331416564147600210200ustar00rootroot00000000000000add_executable(iodevice-example main.cpp) target_link_libraries(iodevice-example QCoro${QT_VERSION_MAJOR}Core QCoro${QT_VERSION_MAJOR}Network Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network ) qcoro-0.4.0/examples/iodevice/main.cpp000066400000000000000000000031421416564147600177110ustar00rootroot00000000000000#include "qcoroiodevice.h" #include "qcoro/task.h" #include #include #include #include #include #include #include using namespace std::chrono_literals; class Server : public QObject { Q_OBJECT public: explicit Server(QHostAddress addr, uint16_t port) { mServer.listen(addr, port); connect(&mServer, &QTcpServer::newConnection, this, &Server::handleConnection); } private Q_SLOTS: QCoro::Task<> handleConnection() { auto socket = mServer.nextPendingConnection(); while (socket->isOpen()) { const auto data = co_await socket; socket->write("PONG: " + data); } } private: QTcpServer mServer; }; class Client : public QObject { Q_OBJECT public: explicit Client(QHostAddress addr, uint16_t port) { mSocket.connectToHost(addr, port); connect(&mTimer, &QTimer::timeout, this, &Client::sendPing); mTimer.start(300ms); } private Q_SLOTS: QCoro::Task<> sendPing() { std::cout << "Sending ping..." << std::endl; mSocket.write(QByteArray("PING #") + QByteArray::number(++mPing)); const auto response = co_await mSocket; std::cout << "Received pong: " << response.constData() << std::endl; } private: int mPing = 0; QTcpSocket mSocket; QTimer mTimer; }; int main(int argc, char **argv) { QCoreApplication app{argc, argv}; Server server{QHostAddress::LocalHost, 6666}; Client client{QHostAddress::LocalHost, 6666}; return app.exec(); } #include "main.moc" qcoro-0.4.0/examples/network/000077500000000000000000000000001416564147600161635ustar00rootroot00000000000000qcoro-0.4.0/examples/network/CMakeLists.txt000066400000000000000000000003331416564147600207220ustar00rootroot00000000000000add_executable(network-example main.cpp) target_link_libraries(network-example QCoro${QT_VERSION_MAJOR}Network Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network ) qcoro-0.4.0/examples/network/main.cpp000066400000000000000000000042131416564147600176130ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoro/network/qcoronetworkreply.h" #include "qcoro/task.h" #include #include #include #include #include #include #include #include #include #include // Programming langauges static const QUrl wikiUrl = QUrl{QStringLiteral("https://www.wikidata.org/wiki/Special:EntityData/Q9143.json")}; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow() { mPb = new QProgressBar(); mPb->setVisible(false); mPb->setMinimumWidth(200); mPb->setMinimum(0); mPb->setMaximum(0); mBtn = new QPushButton(tr("Start Download")); connect(mBtn, &QPushButton::clicked, this, &MainWindow::start); auto *vbox = new QVBoxLayout(); vbox->addStretch(1); vbox->addWidget(mPb); vbox->addWidget(mBtn); vbox->addStretch(1); auto *hbox = new QHBoxLayout(); hbox->addStretch(1); hbox->addLayout(vbox); hbox->addStretch(1); QWidget *w = new QWidget; w->setLayout(hbox); setCentralWidget(w); } private Q_SLOTS: QCoro::Task<> start() { mPb->setVisible(true); mBtn->setEnabled(false); mBtn->setText(tr("Downloading ...")); auto *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); mBtn->setText(tr("Done, download again")); } private: QNetworkAccessManager mNam; QPushButton *mBtn = {}; QProgressBar *mPb = {}; }; int main(int argc, char **argv) { QApplication app(argc, argv); MainWindow window; window.showNormal(); return app.exec(); } #include "main.moc" qcoro-0.4.0/examples/timer/000077500000000000000000000000001416564147600156125ustar00rootroot00000000000000qcoro-0.4.0/examples/timer/CMakeLists.txt000066400000000000000000000002161416564147600203510ustar00rootroot00000000000000add_executable(timer-example main.cpp) target_link_libraries(timer-example QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core ) qcoro-0.4.0/examples/timer/main.cpp000066400000000000000000000017451416564147600172510ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "task.h" #include "qcorotimer.h" #include #include #include #include #include using namespace std::chrono_literals; QCoro::Task<> runMainTimer() { std::cout << "runMainTimer started" << std::endl; QTimer timer; timer.setInterval(2s); timer.start(); std::cout << "Waiting for main timer..." << std::endl; co_await timer; std::cout << "Main timer ticked!" << std::endl; qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app{argc, argv}; QTimer ticker; QObject::connect(&ticker, &QTimer::timeout, &app, []() { std::cout << QDateTime::currentDateTime().toString(Qt::ISODateWithMs).toStdString() << " Secondary timer tick!" << std::endl; }); ticker.start(200ms); QTimer::singleShot(0, runMainTimer); return app.exec(); } qcoro-0.4.0/mkdocs.yml000066400000000000000000000043141416564147600146610ustar00rootroot00000000000000site_name: QCoro site_description: QCoro is a C++ framework for using coroutines with Qt site_author: Daniel Vrátil copyright: Copyright © Daniel Vrátil, all contents published under GNU FDL 1.3, unless stated otherwise. repo_url: https://github.com/danvratil/qcoro repo_name: 'QCoro on GitHub' edit_uri: '' theme: name: material custom_dir: docs/overrides features: - navigation.expand - navigation.tracking - navigation.instant - navigation.tabs - navigation.tabs.sticky - navigation.indexes markdown_extensions: - pymdownx.highlight - pymdownx.superfences - pymdownx.inlinehilite - pymdownx.extra - admonition plugins: - search - include-markdown - macros: module_name: docs/macros extra_css: - stylesheets/doctable.css extra: social: - icon: fontawesome/brands/twitter link: https://twitter.com/danvratil - icon: fontawesome/brands/github link: https://github.com/danvratil nav: - Home: index.md - Building and Using QCoro: building-and-using.md - Coroutines: - Qt vs. co_await: coroutines/qt-vs-coawait.md - co_await Explained: coroutines/coawait.md - Further Reading: coroutines/reading.md - Reference: - Coro: - reference/coro/index.md - QCoro::Task: reference/coro/task.md - QCoro::coro(): reference/coro/coro.md - Core: - reference/core/index.md - QFuture: reference/core/qfuture.md - QIODevice: reference/core/qiodevice.md - QProcess: reference/core/qprocess.md - QTimer: reference/core/qtimer.md - Network: - reference/network/index.md - QAbstractSocket: reference/network/qabstractsocket.md - QLocalSocket: reference/network/qlocalsocket.md - QNetworkReply: reference/network/qnetworkreply.md - QTcpServer: reference/network/qtcpserver.md - DBus: - reference/dbus/index.md - QDBusPendingCall: reference/dbus/qdbuspendingcall.md - QDBusPendingReply: reference/dbus/qdbuspendingreply.md - Changelog: changelog.md - About: - License: about/license.md qcoro-0.4.0/qcoro/000077500000000000000000000000001416564147600137775ustar00rootroot00000000000000qcoro-0.4.0/qcoro/CMakeLists.txt000066400000000000000000000013031416564147600165340ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2021 Daniel Vrátil # # SPDX-License-Identifier: MIT add_qcoro_library( NAME Coro INTERFACE INCLUDEDIR Coro CAMELCASE_HEADERS Task QCoro HEADERS concepts_p.h coroutine.h macros_p.h task.h waitoperationbase_p.h QT_LINK_LIBRARIES INTERFACE Core ) # Install the extra macros file install( FILES "QCoroMacros.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${QCORO_TARGET_PREFIX}Coro/" ) ############################ add_subdirectory(core) if (QCORO_WITH_QTDBUS) add_subdirectory(dbus) endif() if (QCORO_WITH_QTNETWORK) add_subdirectory(network) endif() qcoro-0.4.0/qcoro/QCoroCoroConfig.cmake.in000066400000000000000000000010301416564147600203740ustar00rootroot00000000000000@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Qt@QT_VERSION_MAJOR@Core) include("${CMAKE_CURRENT_LIST_DIR}/QCoro@QT_VERSION_MAJOR@CoroTargets.cmake") # Custom macros include("${CMAKE_CURRENT_LIST_DIR}/QCoroMacros.cmake") # Versionless target, for compatiblity with Qt6 if (TARGET QCoro@QT_VERSION_MAJOR@::Coro AND NOT TARGET QCoro::Coro) add_library(QCoro::Coro INTERFACE IMPORTED) set_target_properties(QCoro::Coro PROPERTIES INTERFACE_LINK_LIBRARIES "QCoro@QT_VERSION_MAJOR@::Coro" ) endif() qcoro-0.4.0/qcoro/QCoroMacros.cmake000066400000000000000000000010471416564147600171730ustar00rootroot00000000000000macro(qcoro_enable_coroutines) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines-ts") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") # MSVC auto-enables coroutine support when C++20 is enabled else() message(FATAL_ERROR "Compiler ${CMAKE_CXX_COMPILER_ID} is not currently supported.") endif() endmacro() qcoro-0.4.0/qcoro/concepts_p.h000066400000000000000000000012501416564147600163030ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #ifndef Q_MOC_RUN #include #if defined(__clang__) // Sadly, libc++ doesn't currently implement any concepts, so // we need to implement them ourselves. namespace QCoro::concepts { template concept destructible = std::is_nothrow_destructible_v; template concept constructible_from = destructible && std::is_constructible_v; } // namespace QCoro::concepts #else namespace QCoro::concepts { using namespace std; } // namespace QCoro::concepts #endif // clang #endif // Q_MOC_RUN qcoro-0.4.0/qcoro/core/000077500000000000000000000000001416564147600147275ustar00rootroot00000000000000qcoro-0.4.0/qcoro/core/CMakeLists.txt000066400000000000000000000006501416564147600174700ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2021 Daniel Vrátil # # SPDX-License-Identifier: MIT add_qcoro_library( NAME Core INCLUDEDIR Core SOURCES qcoroiodevice.cpp qcoroprocess.cpp qcorotimer.cpp CAMELCASE_HEADERS QCoroCore QCoroIODevice QCoroProcess QCoroSignal QCoroTimer QCoroFuture QT_LINK_LIBRARIES PUBLIC Core ) qcoro-0.4.0/qcoro/core/QCoroCoreConfig.cmake.in000066400000000000000000000007761416564147600213320ustar00rootroot00000000000000@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Qt@QT_VERSION_MAJOR@Core) find_dependency(QCoro@QT_VERSION_MAJOR@Coro) include("${CMAKE_CURRENT_LIST_DIR}/QCoro@QT_VERSION_MAJOR@CoreTargets.cmake") # Versionless target, for compatiblity with Qt6 if (TARGET QCoro@QT_VERSION_MAJOR@::Core AND NOT TARGET QCoro::Core) add_library(QCoro::Core INTERFACE IMPORTED) set_target_properties(QCoro::Core PROPERTIES INTERFACE_LINK_LIBRARIES "QCoro@QT_VERSION_MAJOR@::Core" ) endif() qcoro-0.4.0/qcoro/core/qcorocore.h000066400000000000000000000004451416564147600170770ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "config.h" #include "qcoroiodevice.h" #include "qcoroprocess.h" #include "qcorosignal.h" #include "qcorotimer.h" #ifdef QCORO_QT_HAS_COMPAT_ABI #include "qcorofuture.h" #endif qcoro-0.4.0/qcoro/core/qcorofuture.h000066400000000000000000000077041416564147600174660ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "task.h" #include "macros_p.h" #include #include #include /*! \cond internal */ namespace QCoro::detail { template class QCoroFuture final { private: template class WaitForFinishedOperationBase { public: explicit WaitForFinishedOperationBase(const QFuture &future) : mFuture(future) {} Q_DISABLE_COPY(WaitForFinishedOperationBase) QCORO_DEFAULT_MOVE(WaitForFinishedOperationBase) bool await_ready() const noexcept { return mFuture.isFinished() || mFuture.isCanceled(); } void await_suspend(std::coroutine_handle<> awaitingCoroutine) { auto *watcher = new QFutureWatcher(); auto cb = [watcher, awaitingCoroutine]() mutable { watcher->deleteLater(); awaitingCoroutine.resume(); }; QObject::connect(watcher, &QFutureWatcher::finished, cb); QObject::connect(watcher, &QFutureWatcher::canceled, cb); watcher->setFuture(mFuture); } protected: QFuture mFuture; }; class WaitForFinishedOperationImplT : public WaitForFinishedOperationBase { public: using WaitForFinishedOperationBase::WaitForFinishedOperationBase; T await_resume() const noexcept { return this->mFuture.result(); } }; class WaitForFinishedOperationImplVoid : public WaitForFinishedOperationBase { public: using WaitForFinishedOperationBase::WaitForFinishedOperationBase; void await_resume() const noexcept {} }; using WaitForFinishedOperation = std::conditional_t< std::is_void_v, WaitForFinishedOperationImplVoid, WaitForFinishedOperationImplT>; friend struct awaiter_type>; QFuture mFuture; public: explicit QCoroFuture(const QFuture &future) : mFuture(future) {} /*! \brief Operation that allows co_awaiting completion of the running future. Waits until the future is finished and then returns the result of the future (or nothing, if the future is a `QFuture`. If the call is already finished or has an error, the coroutine will not suspend and the `co_await` expression will return immediatelly. This is a coroutine-friendly equivalent to using [`QFutureWatcher`][qdoc-qfuturewatcher]: ```cpp QFuture future = QtConcurrent::run([]() { ... }); QFutureWatcher *watcher = new QFutureWatcher(); QObject::connect(watcher, &QFutureWatcher::finished, this, [watcher]() { watcher->deleteLater(); const QStrign result = watcher->result(); ... }); ``` You can also await completion of the future without using `QCoroFuture` at all by directly co-awaiting the `QFuture` object: ```cpp const QString result = co_await QtConcurrent::run([]() { ... }); ``` [qfuturewatcher]: https://doc.qt.io/qt-5/qfuturewatcher.html */ WaitForFinishedOperation waitForFinished() { return WaitForFinishedOperation{mFuture}; } }; template struct awaiter_type> { using type = typename QCoroFuture::WaitForFinishedOperation; }; } // namespace QCoro::detail /*! \endcond */ //! Returns a coroutine-friendly wrapper for QFuture object. /*! * Returns a wrapper for the QFuture \c f that provides coroutine-friendly * way to co_await the completion of the future. * * @see docs/reference/qfuture.md */ template inline auto qCoro(const QFuture &f) noexcept { return QCoro::detail::QCoroFuture{f}; } qcoro-0.4.0/qcoro/core/qcoroiodevice.cpp000066400000000000000000000071211416564147600202670ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroiodevice.h" #include #include #include using namespace QCoro::detail; QCoroIODevice::OperationBase::OperationBase(QIODevice *device) : mDevice(device) {} void QCoroIODevice::OperationBase::finish(std::coroutine_handle<> awaitingCoroutine) { QObject::disconnect(mConn); QObject::disconnect(mCloseConn); // Delayed trigger QTimer::singleShot(0, [awaitingCoroutine]() mutable { awaitingCoroutine.resume(); }); } QCoroIODevice::ReadOperation::ReadOperation(QIODevice *device, std::function &&resultCb) : OperationBase(device), mResultCb(std::move(resultCb)) {} bool QCoroIODevice::ReadOperation::await_ready() const noexcept { return !mDevice || !mDevice->isOpen() || !mDevice->isReadable() || mDevice->bytesAvailable() > 0; } void QCoroIODevice::ReadOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { Q_ASSERT(mDevice); mConn = QObject::connect(mDevice, &QIODevice::readyRead, std::bind(&ReadOperation::finish, this, awaitingCoroutine)); mCloseConn = QObject::connect(mDevice, &QIODevice::aboutToClose, std::bind(&ReadOperation::finish, this, awaitingCoroutine)); } QByteArray QCoroIODevice::ReadOperation::await_resume() { return mResultCb(mDevice); } QCoroIODevice::WriteOperation::WriteOperation(QIODevice *device, const QByteArray &data) : OperationBase(device), mBytesToBeWritten(device->write(data)) {} bool QCoroIODevice::WriteOperation::await_ready() const noexcept { if (!mDevice || !mDevice->isOpen() || !mDevice->isWritable()) { return true; } if (mBytesWritten == 0) { return true; } if (mDevice->bytesToWrite() == 0) { return true; } return false; } void QCoroIODevice::WriteOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { Q_ASSERT(mDevice); mConn = QObject::connect(mDevice, &QIODevice::bytesWritten, [this, awaitingCoroutine](qint64 written) { mBytesWritten += written; if (mBytesWritten >= mBytesToBeWritten) { finish(awaitingCoroutine); } }); mCloseConn = QObject::connect(mDevice, &QIODevice::aboutToClose, std::bind(&WriteOperation::finish, this, awaitingCoroutine)); } qint64 QCoroIODevice::WriteOperation::await_resume() noexcept { return mBytesWritten; } QCoroIODevice::ReadAllOperation::ReadAllOperation(QIODevice *device) : ReadOperation(device, [](QIODevice *d) { return d->readAll(); }) {} QCoroIODevice::ReadAllOperation::ReadAllOperation(QIODevice &device) : ReadAllOperation(&device) {} QCoroIODevice::QCoroIODevice(QIODevice *device) : mDevice{device} {} QCoroIODevice::ReadOperation QCoroIODevice::readAll() { return ReadOperation(mDevice, [](QIODevice *dev) { return dev->readAll(); }); } QCoroIODevice::ReadOperation QCoroIODevice::read(qint64 maxSize) { return ReadOperation(mDevice, [maxSize](QIODevice *dev) { return dev->read(maxSize); }); } QCoroIODevice::ReadOperation QCoroIODevice::readLine(qint64 maxSize) { return ReadOperation(mDevice, [maxSize](QIODevice *dev) { return dev->readLine(maxSize); }); } QCoroIODevice::WriteOperation QCoroIODevice::write(const QByteArray &buffer) { return WriteOperation(mDevice, buffer); } qcoro-0.4.0/qcoro/core/qcoroiodevice.h000066400000000000000000000126761416564147600177470ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "task.h" #include "coroutine.h" #include "macros_p.h" #include class QIODevice; /*! \cond internal */ namespace QCoro::detail { class QCoroIODevice { private: class OperationBase { public: Q_DISABLE_COPY(OperationBase) QCORO_DEFAULT_MOVE(OperationBase) virtual ~OperationBase() = default; protected: explicit OperationBase(QIODevice *device); virtual void finish(std::coroutine_handle<> awaitingCoroutine); QPointer mDevice; QMetaObject::Connection mConn; QMetaObject::Connection mCloseConn; QMetaObject::Connection mFinishedConn; }; protected: class ReadOperation : public OperationBase { public: ReadOperation(QIODevice *device, std::function &&resultCb); Q_DISABLE_COPY(ReadOperation) QCORO_DEFAULT_MOVE(ReadOperation) virtual bool await_ready() const noexcept; virtual void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; QByteArray await_resume(); private: std::function mResultCb; }; class WriteOperation : public OperationBase { public: WriteOperation(QIODevice *device, const QByteArray &data); Q_DISABLE_COPY(WriteOperation) QCORO_DEFAULT_MOVE(WriteOperation) bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; qint64 await_resume() noexcept; private: qint64 mBytesToBeWritten = 0; qint64 mBytesWritten = 0; }; class ReadAllOperation final : public ReadOperation { public: explicit ReadAllOperation(QIODevice *device); explicit ReadAllOperation(QIODevice &device); }; template friend struct awaiter_type; public: //! Constructor. explicit QCoroIODevice(QIODevice *device); /*! * \brief Co_awaitable equivalent to [`QIODevice::readAll()`][qdoc-qiodevice-readall]. * * Waits until the `QIODevice` emits [`readyRead()`][qdoc-qiodevice-readyRead] and * then calls `readAll()`. * * Identical to asynchronously calling * ```cpp * device.waitForReadyRead(); * device.readAll(); * ``` * * [qdoc-qiodevice-readall]: https://doc.qt.io/qt-5/qiodevice.html#readAll * [qdoc-qiodevice-readyRead]: https://doc.qt.io/qt-5/qiodevice.html#readyRead */ ReadOperation readAll(); /*! * \brief Co_awaitable equivalent to [`QIODevice::read()`][qdoc-qiodevice-read]. * * Waits until the `QIODevice` emits [`readyRead()`][qdoc-qiodevice-readyRead] and * then calls [`read()`][qdoc-qiodevice-read] to read up to \c maxSize bytes. * * Identical to asynchronously calling * ```cpp * device.waitForReadyRead(); * device.read(); * ``` * * [qdoc-qiodevice-read]: https://doc.qt.io/qt-5/qiodevice.html#read-1 * [qdoc-qiodevice-readyRead]: https://doc.qt.io/qt-5/qiodevice.html#readyRead */ ReadOperation read(qint64 maxSize); /*! * \brief Co_awaitable equivalent to [`QIODevice::readLine()`][qdoc-qiodevice-readLine]. * * Waits until the `QIODevice` emits [`readyRead()`][qdoc-qiodevice-readyRead] and * then calls [`readLine()`][qdoc-qiodevice-readLIne] to read until the end-of-line * characte is reached or up to \c maxSize characters are read. * * Identical to asynchronously calling * ```cpp * device.waitForReadyRead(); * device.readLine(); * ``` * * [qdoc-qiodevice-readLine]: https://doc.qt.io/qt-5/qiodevice.html#readLine * [qdoc-qiodevice-readyRead]: https://doc.qt.io/qt-5/qiodevice.html#readyRead */ ReadOperation readLine(qint64 maxSize = 0); // TODO //auto bytesAvailable(qint64 minBytes) { /*! * \brief Co_awaitable equivalent to [`QIODevice::write`][qdoc-qiodevice-write]. * * Returns immediately if the `QIODevice` is unbuffered, blocks until the `QIODevice` * emits [`bytesWritten()`][qdoc-qiodevice-bytesWritten] signal with total bytes equal * to the size of the input \c buffer. * * Identical to asynchronously calling * ```cpp * device.write(data); * device.waitForBytesWritten(); * ``` * * [qdoc-qiodevice-write]: https://doc.qt.io/qt-5/qiodevice.html#write-2 * [qdoc-qiodevice-bytesWritten]: https://doc.qt.io/qt-5/qiodevice.html#bytesWritten */ WriteOperation write(const QByteArray &buffer); protected: QPointer mDevice = {}; }; template requires std::is_base_of_v struct awaiter_type { using type = QCoroIODevice::ReadAllOperation; }; template requires std::is_base_of_v struct awaiter_type { using type = QCoroIODevice::ReadAllOperation; }; } // namespace QCoro::detail /*! \endcond */ //! Returns a coroutine-friendly wrapper for a QIODevice-derived object. /*! * Returns a wrapper for QIODevice \c d that provides coroutine-friendly way * of co_awaiting reading and writing operation. * * @see docs/reference/qiodevice.md */ inline auto qCoro(QIODevice &d) noexcept { return QCoro::detail::QCoroIODevice{&d}; } //! \copydoc qCoro(QIODevice *d) noexcept inline auto qCoro(QIODevice *d) noexcept { return QCoro::detail::QCoroIODevice{d}; } qcoro-0.4.0/qcoro/core/qcoroprocess.cpp000066400000000000000000000066251416564147600201660ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroprocess.h" #include using namespace QCoro::detail; QCoroProcess::WaitForStartedOperation::WaitForStartedOperation(QProcess *process, int timeout_msecs) : WaitOperationBase(process, timeout_msecs) {} bool QCoroProcess::WaitForStartedOperation::await_ready() const noexcept { return !mObj || mObj->state() == QProcess::Running; } void QCoroProcess::WaitForStartedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { mConn = QObject::connect(mObj, &QProcess::stateChanged, [this, awaitingCoroutine](auto newState) mutable { switch (newState) { case QProcess::NotRunning: // State changed from Starting or Running to NotRunning, which means // there was some error. Wake up the coroutine. resume(awaitingCoroutine); break; case QProcess::Starting: // Wait for it... break; case QProcess::Running: resume(awaitingCoroutine); break; } }); startTimeoutTimer(awaitingCoroutine); } QCoroProcess::WaitForFinishedOperation::WaitForFinishedOperation(QProcess *process, int timeout_msecs) : WaitOperationBase(process, timeout_msecs) {} bool QCoroProcess::WaitForFinishedOperation::await_ready() const noexcept { return !mObj || mObj->state() == QProcess::NotRunning; } void QCoroProcess::WaitForFinishedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) { mConn = QObject::connect(mObj, qOverload(&QProcess::finished), std::bind(&WaitForFinishedOperation::resume, this, awaitingCoroutine)); startTimeoutTimer(awaitingCoroutine); } QCoroProcess::QCoroProcess(QProcess *process) : QCoroIODevice(process) {} QCoroProcess::WaitForStartedOperation QCoroProcess::waitForStarted(int timeout_msecs) { return WaitForStartedOperation{static_cast(mDevice.data()), timeout_msecs}; } QCoroProcess::WaitForStartedOperation QCoroProcess::waitForStarted(std::chrono::milliseconds timeout) { return waitForStarted(static_cast(timeout.count())); } QCoroProcess::WaitForFinishedOperation QCoroProcess::waitForFinished(int timeout_msecs) { return WaitForFinishedOperation{static_cast(mDevice.data()), timeout_msecs}; } QCoroProcess::WaitForFinishedOperation QCoroProcess::waitForFinished(std::chrono::milliseconds timeout) { return waitForFinished(static_cast(timeout.count())); } QCoroProcess::WaitForStartedOperation QCoroProcess::start(QIODevice::OpenMode mode) { static_cast(mDevice.data())->start(mode); return waitForStarted(); } QCoroProcess::WaitForStartedOperation QCoroProcess::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode) { static_cast(mDevice.data())->start(program, arguments, mode); return waitForStarted(); } qcoro-0.4.0/qcoro/core/qcoroprocess.h000066400000000000000000000102501416564147600176200ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "waitoperationbase_p.h" #include "qcoroiodevice.h" #include #include class QProcess; namespace QCoro::detail { using namespace std::chrono_literals; //! QProcess wrapper with co_awaitable-friendly API. class QCoroProcess : public QCoroIODevice { //! An Awaitable that suspends the coroutine until the process is started. class WaitForStartedOperation : public WaitOperationBase { public: WaitForStartedOperation(QProcess *process, int timeout_msecs = 30'000); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; }; //! An Awaitable that suspends the coroutine until the process is finished. class WaitForFinishedOperation : public WaitOperationBase { public: WaitForFinishedOperation(QProcess *process, int timeout_msecs); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine); }; public: explicit QCoroProcess(QProcess *process); /*! * \brief Co_awaitable equivalent to [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted]. * * [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted */ WaitForStartedOperation waitForStarted(int timeout_msecs = 30'000); /*! * \brief Co_awaitable equivalent to [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted]. * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. * * [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted */ WaitForStartedOperation waitForStarted(std::chrono::milliseconds timeout); /*! * \brief Co_awaitable equivalent to [`QProcess::waitForFinished()`][qtdoc-qprocess-waitForFinished]. * * [qtdoc-qprocess-waitForFinished]: https://doc.qt.io/qt-5/qprocess.html#waitForFinished */ WaitForFinishedOperation waitForFinished(int timeout_msecs = 30'000); /*! * \brief Co_awaitable equivalent to [`QProcess::waitForFinished()`][qtdoc-qprocess-waitForFinished]. * * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. * * [qtdoc-qprocess-waitForFinished]: https://doc.qt.io/qt-4/qprocess.html#waitForFinished */ WaitForFinishedOperation waitForFinished(std::chrono::milliseconds timeout); /*! * \brief Executes a new process and waits for it to start * * Co_awaitable equivalent to calling [`QProcess::start()`][qtdoc-qprocess-start-2] * followed by [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted]. * * [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted * [qtdoc-qprocess-start-2]: https://doc.qt.io/qt-5/qprocess.html#start-2 */ WaitForStartedOperation start(QIODevice::OpenMode mode = QIODevice::ReadWrite); /*! * \brief Executes a new process and waits for it to start * * Co_awaitable equivalent to calling [`QProcess::start()`][qtdoc-qprocess-start] * followed by [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted]. * * [qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted * [qtdoc-qprocess-start]: https://doc.qt.io/qt-5/qprocess.html#start-2 */ WaitForStartedOperation start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode = QIODevice::ReadWrite); }; } // namespace QCoro::detail //! Returns a coroutine-friendly wrapper for QProcess object. /*! * Returns a wrapper for the QProcess \c p that provides coroutine-friendly * way to co_await the process to start or finish. * * @see docs/reference/qprocess.md */ inline auto qCoro(QProcess &p) noexcept { return QCoro::detail::QCoroProcess{&p}; } //! \copydoc qCoro(QProcess &p) noexcept inline auto qCoro(QProcess *p) noexcept { return QCoro::detail::QCoroProcess{p}; } qcoro-0.4.0/qcoro/core/qcorosignal.h000066400000000000000000000051341416564147600174240ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "coroutine.h" #include "macros_p.h" #include "concepts_p.h" #include #include #include #include namespace QCoro::detail { namespace concepts { //! Simplistic QObject concept. template concept QObject = requires(T *obj) { requires std::is_base_of_v; requires std::is_same_v; }; } // namespace concepts template struct args_tuple; template struct args_tuple { using types = std::tuple; }; template struct args_tuple { using types = std::tuple; }; template class QCoroSignal { using ArgsTuple = typename args_tuple::types; public: QCoroSignal(T *obj, FuncPtr &&funcPtr) : mObj(obj), mFuncPtr(std::forward(funcPtr)) {} bool await_ready() const noexcept { return mObj.isNull(); } void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { mConn = QObject::connect( mObj, mFuncPtr, mObj, [this, awaitingCoroutine](auto &&...args) mutable { QObject::disconnect(mConn); mResult.emplace(std::forward(args)...); awaitingCoroutine.resume(); }, Qt::QueuedConnection); } auto await_resume() { // TODO: Ignore QPrivateSignal... if constexpr (std::tuple_size_v == 1) { assert(mResult.has_value()); return std::move(std::get<0>(mResult.value())); } else if constexpr (std::tuple_size_v > 0) { assert(mResult.has_value()); return std::move(mResult.value()); } } private: QPointer mObj; FuncPtr mFuncPtr; QMetaObject::Connection mConn; std::optional mResult; }; template QCoroSignal(T *, FuncPtr &&) -> QCoroSignal; } // namespace QCoro::detail //! Allows co_awaiting on signal emission. /*! * Returns an Awaitable object that allows co_awaiting for a signal to * be emitted. The result of the co_awaiting is a tuple with the signal * arguments. * * @see docs/reference/coro.md */ template inline auto qCoro(T *obj, FuncPtr &&ptr) { return QCoro::detail::QCoroSignal(obj, std::forward(ptr)); } qcoro-0.4.0/qcoro/core/qcorotimer.cpp000066400000000000000000000022311416564147600176150ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorotimer.h" #include #include using namespace QCoro::detail; QCoroTimer::WaitForTimeoutOperation::WaitForTimeoutOperation(QTimer *timer) : mTimer(timer) {} QCoroTimer::WaitForTimeoutOperation::WaitForTimeoutOperation(QTimer &timer) : WaitForTimeoutOperation(&timer) {} bool QCoroTimer::WaitForTimeoutOperation::await_ready() const noexcept { return !mTimer || !mTimer->isActive(); } void QCoroTimer::WaitForTimeoutOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) { if (mTimer && mTimer->isActive()) { mConn = QObject::connect(mTimer, &QTimer::timeout, [this, awaitingCoroutine]() mutable { QObject::disconnect(mConn); awaitingCoroutine.resume(); }); } else { awaitingCoroutine.resume(); } } void QCoroTimer::WaitForTimeoutOperation::await_resume() const {} QCoroTimer::QCoroTimer(QTimer *timer) : mTimer(timer) {} QCoroTimer::WaitForTimeoutOperation QCoroTimer::waitForTimeout() const { return WaitForTimeoutOperation{*mTimer}; } qcoro-0.4.0/qcoro/core/qcorotimer.h000066400000000000000000000030431416564147600172640ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "task.h" #include #include #include /*! \cond internal */ namespace QCoro::detail { class QCoroTimer { private: class WaitForTimeoutOperation { public: explicit WaitForTimeoutOperation(QTimer *timer); explicit WaitForTimeoutOperation(QTimer &timer); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine); void await_resume() const; private: QMetaObject::Connection mConn; QPointer mTimer; }; friend struct awaiter_type; friend struct awaiter_type; QPointer mTimer; public: explicit QCoroTimer(QTimer *timer); WaitForTimeoutOperation waitForTimeout() const; }; template<> struct awaiter_type { using type = QCoroTimer::WaitForTimeoutOperation; }; template<> struct awaiter_type { using type = QCoroTimer::WaitForTimeoutOperation; }; } // namespace QCoro::detail /*! \endcond */ //! Returns a coroutine-friendly wrapper for QTimer object. /*! * Returns a wrapper for the QTimer \c timer that provides coroutine-friendly * way to co_await the timeout. * * @see docs/reference/qtimer.md */ inline auto qCoro(QTimer *timer) noexcept { return QCoro::detail::QCoroTimer{timer}; } //! \copydoc qCoro(QTimer *) inline auto qCoro(QTimer &timer) noexcept{ return QCoro::detail::QCoroTimer{&timer}; } qcoro-0.4.0/qcoro/coroutine.h000066400000000000000000000205161416564147600161630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include // __cpp_lib_coroutine is not defined if the compiler doesn't support coroutines // (__cpp_impl_coroutine), e.g. clang as of 13.0. #if defined(__cpp_lib_coroutine) #include #elif defined(__clang__) // Implement our own header in a way that is compatible with the standard. // See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4849.pdf #include // void_t #include // size_t // Intrinsincs for Clang // https://clang.llvm.org/docs/LanguageExtensions.html#c-coroutines-support-builtins extern "C" { void __builtin_coro_destroy(void *addr); void __builtin_coro_resume(void *addr); bool __builtin_coro_done(void *addr); void* __builtin_coro_promise(void *addr, int alignment, bool from_promise); void *__builtin_coro_noop(); } // 17.12.1 Header synopsis namespace std { // 17.12.2, coroutine traits // (omitted, because we implement them in std::experimental namespace and import them into the std // namespace). // template // struct coroutine_traits; // 17.12.3, coroutine traits template struct coroutine_handle; // 17.12.3.6, comparison operators constexpr bool operator==(coroutine_handle<> x, coroutine_handle<> y) noexcept; // constexpr strong_ordering operator<=>(coroutine_handle<> x, coroutine_handle<> y) noexcept; // 17.12.3.7, hash support //template struct hash; //template struct hash>; // 17.12.4, n-op- coroutines struct noop_coroutine_promise; template<> struct coroutine_handle; using noop_coroutine_handle = coroutine_handle; noop_coroutine_handle noop_coroutine() noexcept; // 17.12.5, trivial awaitables struct suspend_never; struct suspend_always; } // namespace std // Implementation namespace std { // Clang checks for std::experimental::coroutine_traits explicitly, so we must define the types // in the experimental namespace. namespace experimental { template struct __coroutine_traits_base {}; template struct __coroutine_traits_base> { using promise_type = typename R::promise_type; }; // 17.12.2, coroutine traits template struct coroutine_traits : __coroutine_traits_base {}; // Clang requires that std::experimental::coroutine_handle is a class template template struct coroutine_handle : public std::coroutine_handle {}; } // namespace experimental // Import std::experimental::coroutine_traits into the std namespace template using coroutine_traits = std::experimental::coroutine_traits; // 17.12.3, coroutine handle template<> struct coroutine_handle { // 17.12.3.1, construct/reset constexpr coroutine_handle() noexcept {} constexpr coroutine_handle(nullptr_t) noexcept {} coroutine_handle &operator=(nullptr_t) noexcept { m_ptr = nullptr; return *this; } // 17.12.3.2, export/import constexpr void *address() const noexcept { return m_ptr; } static constexpr coroutine_handle from_address(void *addr) noexcept { coroutine_handle handle; handle.m_ptr = addr; return handle; } // 17.12.3.3, observers constexpr explicit operator bool() const noexcept { return m_ptr != nullptr; } bool done() const { return __builtin_coro_done(m_ptr); } // 17.12.3.4, resumption void operator()() const { resume(); } void resume() const { __builtin_coro_resume(m_ptr); } void destroy() const { __builtin_coro_destroy(m_ptr); } protected: void *m_ptr = nullptr; }; template struct coroutine_handle : public coroutine_handle<> { // 17.12.3.1, construct, reset using coroutine_handle<>::coroutine_handle; static coroutine_handle from_promise(Promise &promise) { coroutine_handle handle; handle.m_ptr = __builtin_coro_promise(&promise, alignof(Promise), /* from-promise=*/ true); return handle; } coroutine_handle &operator=(nullptr_t) noexcept { this->m_ptr = nullptr; return *this; } // 17.12.3.2, export/import static constexpr coroutine_handle from_address(void *addr) noexcept { coroutine_handle handle; handle.m_ptr = addr; return handle; } //17.12.3.5, promise access Promise &promise() const { return *reinterpret_cast( __builtin_coro_promise(m_ptr, alignof(Promise), /*from-promise=*/false)); } }; // 17.12.3.6, comparison operators constexpr bool operator==(coroutine_handle<> x, coroutine_handle<> y) noexcept { return x.address() == y.address(); } //constexpr strong_ordering operator<=>(coroutine_handle<> x, coroutine_handle<> y) noexcept; // 17.12.4, no-op coroutines struct noop_coroutine_promise {}; template<> struct coroutine_handle; using noop_coroutine_handle = coroutine_handle; template<> struct coroutine_handle : public coroutine_handle<> { // 17.12.4.2.1, observers constexpr explicit operator bool() const noexcept { return true; } constexpr bool done() const noexcept { return false; } constexpr void operator()() const noexcept {} constexpr void resume() const noexcept {} constexpr void destroy() const noexcept {} noop_coroutine_promise &promise() const noexcept { return *reinterpret_cast( __builtin_coro_promise(__builtin_coro_noop(), alignof(noop_coroutine_promise), false)); } private: coroutine_handle() noexcept : coroutine_handle<>(from_address(__builtin_coro_noop())) {} friend noop_coroutine_handle noop_coroutine() noexcept; }; inline noop_coroutine_handle noop_coroutine() noexcept { return {}; } // 17.12.5, trivial awaitables struct suspend_never { constexpr bool await_ready() const noexcept { return true; } constexpr void await_resume() const noexcept {} constexpr void await_suspend(coroutine_handle<>) const noexcept {} }; struct suspend_always { constexpr bool await_ready() const noexcept { return false; } constexpr void await_suspend(coroutine_handle<>) const noexcept {} constexpr void await_resume() const noexcept {} }; } // namespace std #else // defined(__clang__) #pragma error "Current compiler does not support coroutines, or is not supported by QCoro." #endif // defined(__cpp_lib_coroutine) // The QCORO_STD macro is no longer needed (with the code above), but keep it for backwards // compatibility. #ifdef QCORO_NO_DEPRECATED_QCOROSTD #define QCORO_STD std #else // QCORO_NO_DEPRECATED_QCOROSTD #ifdef _MSC_VER #define _QCORO_STRINGIFY2(x) #x #define _QCORO_STRINGIFY(x) _QCORO_STRINGIFY2(x) #define QCORO_STD \ __pragma(message(__FILE__ "(" _QCORO_STRINGIFY(__LINE__) ") QCORO_STD macro is deprecated, use regular 'std' namespace instead, or pass /DQCORO_NO_DEPRECATED_QCOROSTD to suppress this warning.")) \ std #else // GCC, clang #define QCORO_STD \ _Pragma("GCC warning \"QCORO_STD macro is deprecated, use regular 'std' namespace instead, or pass -DQCORO_NO_DEPRECATED_QCOROSTD to suppress this warning.\"") \ std #endif // _MSC_VER #endif // QCORO_NO_DEPRECATED_QCOROSTD // Moc doesn't seem to understand something in the header... #ifndef Q_MOC_RUN #include "concepts_p.h" namespace QCoro { namespace detail { template concept has_await_methods = requires(T t) { { t.await_ready() } -> std::same_as; {t.await_suspend(std::declval>())}; {t.await_resume()}; }; template concept has_operator_coawait = requires(T t) { // TODO: Check that result of co_await() satisfies Awaitable again { t.operator co_await() }; }; } // namespace detail //! A concept describing the Awaitable type /*! * Awaitable type is a type that can be passed as an argument to co_await. */ template concept Awaitable = detail::has_operator_coawait || detail::has_await_methods; } // namespace QCoro #endif // Q_MOC_RUN qcoro-0.4.0/qcoro/dbus/000077500000000000000000000000001416564147600147345ustar00rootroot00000000000000qcoro-0.4.0/qcoro/dbus/CMakeLists.txt000066400000000000000000000005551416564147600175010ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2021 Daniel Vrátil # # SPDX-License-Identifier: MIT add_qcoro_library( NAME DBus SOURCES qcorodbuspendingcall.cpp CAMELCASE_HEADERS QCoroDBus QCoroDBusPendingCall QCoroDBusPendingReply QCORO_LINK_LIBRARIES PUBLIC Core QT_LINK_LIBRARIES PUBLIC DBus ) qcoro-0.4.0/qcoro/dbus/QCoroDBusConfig.cmake.in000066400000000000000000000011251416564147600212710ustar00rootroot00000000000000@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Qt@QT_VERSION_MAJOR@Core) find_dependency(Qt@QT_VERSION_MAJOR@DBus) find_dependency(QCoro@QT_VERSION_MAJOR@Coro) find_dependency(QCoro@QT_VERSION_MAJOR@Core) include("${CMAKE_CURRENT_LIST_DIR}/QCoro@QT_VERSION_MAJOR@DBusTargets.cmake") # Versionless target, for compatiblity with Qt6 if (TARGET QCoro@QT_VERSION_MAJOR@::DBus AND NOT TARGET QCoro::DBus) add_library(QCoro::DBus INTERFACE IMPORTED) set_target_properties(QCoro::DBus PROPERTIES INTERFACE_LINK_LIBRARIES "QCoro@QT_VERSION_MAJOR@::DBus" ) endif() qcoro-0.4.0/qcoro/dbus/qcorodbus.h000066400000000000000000000002531416564147600171060ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorodbuspendingcall.h" #include "qcorodbuspendingreply.h" qcoro-0.4.0/qcoro/dbus/qcorodbuspendingcall.cpp000066400000000000000000000023451416564147600216460ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorodbuspendingcall.h" #include using namespace QCoro::detail; QCoroDBusPendingCall::WaitForFinishedOperation::WaitForFinishedOperation(const QDBusPendingCall &call) : mCall(call) {} bool QCoroDBusPendingCall::WaitForFinishedOperation::await_ready() const noexcept { return mCall.isFinished(); } void QCoroDBusPendingCall::WaitForFinishedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { auto *watcher = new QDBusPendingCallWatcher{mCall}; QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [awaitingCoroutine](auto *watcher) mutable { awaitingCoroutine.resume(); watcher->deleteLater(); }); } QDBusMessage QCoroDBusPendingCall::WaitForFinishedOperation::await_resume() const { Q_ASSERT(mCall.isFinished()); return mCall.reply(); } QCoroDBusPendingCall::QCoroDBusPendingCall(const QDBusPendingCall &call) : mCall(call) {} QCoroDBusPendingCall::WaitForFinishedOperation QCoroDBusPendingCall::waitForFinished() { return WaitForFinishedOperation{mCall}; } qcoro-0.4.0/qcoro/dbus/qcorodbuspendingcall.h000066400000000000000000000063251416564147600213150ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "task.h" #include class QDBusMessage; class QDBusPendingCall; /*! \cond internal */ namespace QCoro::detail { class QCoroDBusPendingCall { private: class WaitForFinishedOperation { public: explicit WaitForFinishedOperation(const QDBusPendingCall &call); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; QDBusMessage await_resume() const; private: const QDBusPendingCall &mCall; }; const QDBusPendingCall &mCall; friend struct awaiter_type; public: //! Constructor. explicit QCoroDBusPendingCall(const QDBusPendingCall &call); /*! \brief Operation that allows co_awaiting completion of the pending DBus call. Waits until the DBus call is finished. This is equivalent to using [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher] and waiting for it to emit the [`finished()`][qdoc-qdbuspendingcallwatcher-finished] signal. Returns a `QDBusMessage` representing the reply to the call. If the call is already finished or has an error, the coroutine will not suspend and the `co_await` expression will return immediatelly. It is also possible to just directly use a `QDBusPendingCall` in a `co_await` expression to await its completion: ```cpp QDBusPendingCall pendingCall = interface.asyncCall(...); const auto reply = co_await pendingCall; ``` The above is equivalent to: ```cpp QDBusPendingCall pendingCall = interface.asyncCall(...); const auto reply = co_await qCoro(pendingCall).waitForFinished(); ``` This is a coroutine-friendly equivalent to using [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]: ```cpp QDBusPendingCall call = interface.asyncCall(...); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [](QDBusPendingCallWatcher *watcher) { watcher->deleteLater(); const QDBusReply<...> reply = *watcher; ... }); ``` [qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html [qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished @see docs/reference/qdbuspendingcall.md */ WaitForFinishedOperation waitForFinished(); }; template<> struct awaiter_type { using type = QCoroDBusPendingCall::WaitForFinishedOperation; }; } // namespace QCoro::detail /*! \endcond */ //! Returns a co_await-friendly wrapper for QDBusPendingCall object /*! * Returns a wrapper for QDBusPendingCall \c call that provides coroutine-friendly * way to co_await completion of the call. * * @see docs/reference/qdbuspendingcall.md */ inline auto qCoro(const QDBusPendingCall &call) { return QCoro::detail::QCoroDBusPendingCall{call}; } qcoro-0.4.0/qcoro/dbus/qcorodbuspendingreply.h000066400000000000000000000111321416564147600215250ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "task.h" #include #include class QDBusMessage; /*! \cond internal */ namespace QCoro::detail { template class QCoroDBusPendingReply { private: #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // QDBusPendingReply is a variadic template since Qt6, but in Qt5 the // maximum number of template arguments was 8, so we simulate the Qt5 // behavior here. static_assert(sizeof...(Args) <= 8, "In Qt5 QDBusPendingReply has maximum 8 arguments."); #endif class WaitForFinishedOperation { public: explicit WaitForFinishedOperation(const QDBusPendingReply &reply) : mReply(reply) {} bool await_ready() const noexcept { return mReply.isFinished(); } void await_suspend(std::coroutine_handle<> awaitingCoroutine) { auto *watcher = new QDBusPendingCallWatcher{mReply}; QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [awaitingCoroutine](auto *watcher) mutable { awaitingCoroutine.resume(); watcher->deleteLater(); }); } QDBusPendingReply await_resume() const { Q_ASSERT(mReply.isFinished()); return mReply; } private: QDBusPendingReply mReply; }; QDBusPendingReply mReply; friend struct awaiter_type>; public: //! Constructor. explicit QCoroDBusPendingReply(const QDBusPendingReply &reply) : mReply(reply) {} /*! \brief Operation that allows co_awaiting completion of the pending DBus reply. Waits until the DBus call is finished. This is equivalent to using [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher] and waiting for it to emit the [`finished()`][qdoc-qdbuspendingcallwatcher-finished] signal. Returns a `QDBusMessage` representing the received reply. If the reply is already finished or an error has occurred the coroutine will not suspend and will return a result immediatelly. This is a coroutine-friendly equivalent to using [`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]: ```cpp QDBusPendingCall call = interface.asyncCall(...); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [](QDBusPendingCallWatcher *watcher) { watcher->deleteLater(); const QDBusReply<...> reply = *watcher; ... }); ``` It is also possible to just directly use a `QDBusPendingReply` in a `co_await` expression to await its completion: ```cpp QDBusPendingReply<...> pendingReply = interface.asyncCall(...); const auto reply = co_await pendingReply; ``` The above is equivalent to: ```cpp QDBusPendingReply<...> pendingReply = interface.asyncCall(...); const auto reply = co_await qCoro(pendingReply).waitForFinished(); ``` [qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html [qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished @see docs/reference/qdbuspendingreply.md */ WaitForFinishedOperation waitForFinished() { return WaitForFinishedOperation{mReply}; } }; template struct awaiter_type> { using type = typename QCoroDBusPendingReply::WaitForFinishedOperation; }; } // namespace QCoro::detail /*! \endcond */ //! Returns a coroutine-friendly wrapper for a QDBusPendingReply object. /*! * Returns a wrapper for the QDBusPendingReply \c reply that provides * a coroutine-friendly way to await the completion of the pending reply. * * Note that is is also possible to just directly `co_await` the `QDBusPendingReply` * completion without using the wrapper class: * * ``` * QDBusPendingReply<...> pendingReply = interface.asyncCall(...); * const auto reply = co_await pendingReply; * ``` * * @see docs/reference/qdbuspendingreply.md */ template inline auto qCoro(const QDBusPendingReply &reply) { return QCoro::detail::QCoroDBusPendingReply{reply}; } qcoro-0.4.0/qcoro/macros_p.h000066400000000000000000000006221416564147600157530ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #ifndef QCORO_DEFAULT_MOVE #define QCORO_DEFAULT_MOVE(Class) \ Class(Class &&) noexcept = default; \ Class &operator=(Class &&) noexcept = default; #endif qcoro-0.4.0/qcoro/network/000077500000000000000000000000001416564147600154705ustar00rootroot00000000000000qcoro-0.4.0/qcoro/network/CMakeLists.txt000066400000000000000000000007661416564147600202410ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2021 Daniel Vrátil # # SPDX-License-Identifier: MIT add_qcoro_library( NAME Network SOURCES qcoroabstractsocket.cpp qcorolocalsocket.cpp qcoronetworkreply.cpp qcorotcpserver.cpp CAMELCASE_HEADERS QCoroNetwork QCoroAbstractSocket QCoroLocalSocket QCoroNetworkReply QCoroTcpServer QCORO_LINK_LIBRARIES PUBLIC Core QT_LINK_LIBRARIES PUBLIC Network ) qcoro-0.4.0/qcoro/network/QCoroNetworkConfig.cmake.in000066400000000000000000000011521416564147600226210ustar00rootroot00000000000000@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Qt@QT_VERSION_MAJOR@Core) find_dependency(Qt@QT_VERSION_MAJOR@Network) find_dependency(QCoro@QT_VERSION_MAJOR@Coro) find_dependency(QCoro@QT_VERSION_MAJOR@Core) include("${CMAKE_CURRENT_LIST_DIR}/QCoro@QT_VERSION_MAJOR@NetworkTargets.cmake") # Versionless target, for compatiblity with Qt6 if (TARGET QCoro@QT_VERSION_MAJOR@::Network AND NOT TARGET QCoro::Network) add_library(QCoro::Network INTERFACE IMPORTED) set_target_properties(QCoro::Network PROPERTIES INTERFACE_LINK_LIBRARIES "QCoro@QT_VERSION_MAJOR@::Network" ) endif() qcoro-0.4.0/qcoro/network/qcoroabstractsocket.cpp000066400000000000000000000130041416564147600222520ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroabstractsocket.h" using namespace QCoro::detail; using namespace std::chrono_literals; QCoroAbstractSocket::WaitForConnectedOperation::WaitForConnectedOperation(QAbstractSocket *socket, int timeout_msecs) : WaitOperationBase(socket, timeout_msecs) {} bool QCoroAbstractSocket::WaitForConnectedOperation::await_ready() const noexcept { return !mObj || mObj->state() == QAbstractSocket::ConnectedState; } void QCoroAbstractSocket::WaitForConnectedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { mConn = QObject::connect(mObj, &QAbstractSocket::stateChanged, [this, awaitingCoroutine](auto newState) mutable { switch (newState) { case QAbstractSocket::UnconnectedState: case QAbstractSocket::HostLookupState: case QAbstractSocket::ConnectingState: case QAbstractSocket::BoundState: // Almost there... break; case QAbstractSocket::ClosingState: case QAbstractSocket::ListeningState: // We shouldn't be here when waiting for Connected state... resume(awaitingCoroutine); break; case QAbstractSocket::ConnectedState: resume(awaitingCoroutine); break; } }); startTimeoutTimer(awaitingCoroutine); } QCoroAbstractSocket::WaitForDisconnectedOperation::WaitForDisconnectedOperation(QAbstractSocket *socket, int timeout_msecs) : WaitOperationBase(socket, timeout_msecs) {} bool QCoroAbstractSocket::WaitForDisconnectedOperation::await_ready() const noexcept { return !mObj || mObj->state() == QAbstractSocket::UnconnectedState; } void QCoroAbstractSocket::WaitForDisconnectedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept{ mConn = QObject::connect( mObj, &QAbstractSocket::disconnected, [this, awaitingCoroutine]() mutable { resume(awaitingCoroutine); }); startTimeoutTimer(awaitingCoroutine); } bool QCoroAbstractSocket::ReadOperation::await_ready() const noexcept { return QCoroIODevice::ReadOperation::await_ready() || static_cast(mDevice.data())->state() == QAbstractSocket::UnconnectedState; } void QCoroAbstractSocket::ReadOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { QCoroIODevice::ReadOperation::await_suspend(awaitingCoroutine); mStateConn = QObject::connect( static_cast(mDevice.data()), &QAbstractSocket::stateChanged, [this, awaitingCoroutine]() { if (static_cast(mDevice.data())->state() == QAbstractSocket::UnconnectedState) { finish(awaitingCoroutine); } }); } void QCoroAbstractSocket::ReadOperation::finish(std::coroutine_handle<> awaitingCoroutine) { QObject::disconnect(mStateConn); QCoroIODevice::ReadOperation::finish(awaitingCoroutine); } QCoroAbstractSocket::QCoroAbstractSocket(QAbstractSocket *socket) : QCoroIODevice(socket) {} QCoroAbstractSocket::WaitForConnectedOperation QCoroAbstractSocket::waitForConnected(int timeout_msecs) { return WaitForConnectedOperation{static_cast(mDevice.data()), timeout_msecs}; } QCoroAbstractSocket::WaitForConnectedOperation QCoroAbstractSocket::waitForConnected(std::chrono::milliseconds timeout) { return waitForConnected(static_cast(timeout.count())); } QCoroAbstractSocket::WaitForDisconnectedOperation QCoroAbstractSocket::waitForDisconnected(int timeout_msecs) { return WaitForDisconnectedOperation{static_cast(mDevice.data()), timeout_msecs}; } QCoroAbstractSocket::WaitForDisconnectedOperation QCoroAbstractSocket::waitForDisconnected(std::chrono::milliseconds timeout) { return waitForDisconnected(static_cast(timeout.count())); } QCoroAbstractSocket::WaitForConnectedOperation QCoroAbstractSocket::connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode, QAbstractSocket::NetworkLayerProtocol protocol) { static_cast(mDevice.data())->connectToHost(hostName, port, openMode, protocol); return waitForConnected(); } QCoroAbstractSocket::WaitForConnectedOperation QCoroAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, QIODevice::OpenMode openMode) { static_cast(mDevice.data())->connectToHost(address, port, openMode); return waitForConnected(); } QCoroAbstractSocket::ReadOperation QCoroAbstractSocket::readAll() { return ReadOperation(mDevice, [](QIODevice *dev) { return dev->readAll(); }); } QCoroAbstractSocket::ReadOperation QCoroAbstractSocket::read(qint64 maxSize) { return ReadOperation(mDevice, [maxSize](QIODevice *dev) { return dev->read(maxSize); }); } QCoroAbstractSocket::ReadOperation QCoroAbstractSocket::readLine(qint64 maxSize) { return ReadOperation(mDevice, [maxSize](QIODevice *dev) { return dev->readLine(maxSize); }); } qcoro-0.4.0/qcoro/network/qcoroabstractsocket.h000066400000000000000000000113511416564147600217220ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "waitoperationbase_p.h" #include "qcoroiodevice.h" #include #include #include class QAbstractSocket; namespace QCoro::detail { using namespace std::chrono_literals; //! QAbstractSocket wrapper with co_awaitable-friendly API. class QCoroAbstractSocket final : private QCoroIODevice { class ReadOperation final : public QCoroIODevice::ReadOperation { public: using QCoroIODevice::ReadOperation::ReadOperation; bool await_ready() const noexcept final; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept final; private: void finish(std::coroutine_handle<> awaitingCoroutine); QMetaObject::Connection mStateConn; }; class WaitForConnectedOperation final : public WaitOperationBase { public: WaitForConnectedOperation(QAbstractSocket *socket, int timeout_msecs = 30'000); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; }; class WaitForDisconnectedOperation final : public WaitOperationBase { public: WaitForDisconnectedOperation(QAbstractSocket *socket, int timeout_msecs); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; }; public: explicit QCoroAbstractSocket(QAbstractSocket *socket); //! Co_awaitable equivalent to [`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected]. WaitForConnectedOperation waitForConnected(int timeout_msecs = 30'000); //! Co_awaitable equivalent to [`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected]. /*! * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. */ WaitForConnectedOperation waitForConnected(std::chrono::milliseconds timeout); //! Co_awaitable equivalent to [`QAbstractSocket::waitForDisconnected()`][qtdoc-qabstractsocket-waitForDisconnected]. WaitForDisconnectedOperation waitForDisconnected(int timeout_msecs = 30'000); //! Co_awaitable equivalent to [`QAbstractSocket::waitForDisconnected()`][qtdoc-qabstractsocket-waitForDisconnected]. /*! * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. */ WaitForDisconnectedOperation waitForDisconnected(std::chrono::milliseconds timeout); //! Connects to server and waits until the connection is established. /*! * Equivalent to calling [`QAbstractSocket::connecToServer`][qdoc-qabstractsocket-connecToHost] * followed by [`QAbstractSocket::waitForConnected`][qdoc-qabstractsocket-waitForConnected]. */ WaitForConnectedOperation connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode = QIODevice::ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol); //! Connects to server and waits until the connection is established. /*! * Equivalent to calling [`QAbstractSocket::connecToServer`][qdoc-qabstractsocket-connecToHost-1] * followed by [`QAbstractSocket::waitForConnected`][qdoc-qabstractsocket-waitForConnected]. */ WaitForConnectedOperation connectToHost(const QHostAddress &address, quint16 port, QIODevice::OpenMode openMode = QIODevice::ReadWrite); //! \copydoc QCoroIODevice::readAll ReadOperation readAll(); //! \copydoc QCoroIODevice::read ReadOperation read(qint64 maxSize); //! \copydoc QCoroIODevice::readLine ReadOperation readLine(qint64 maxSize = 0); }; } // namespace QCoro::detail /*! * [qtdoc-qabstractsocket-waitForConnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForConnected * [qtdoc-qabstractsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qabstractsocket.hmtl#waitForDisconnected * [qtdoc-qabstractsocket-connectToHost]: https://doc.qt.io/qt-5/qabstractsocket.html#connectToHost * [qtdoc-qabstractsocket-connectTo-1]: https://doc.qt.io/qt-5/qabstractsocket.html#connectToHost-1 */ //! Returns a coroutine-friendly wrapper for QAbstractSocket object. /*! * Returns a wrapper for the QAbstractSocket \c s that provides coroutine-friendly * way to co_await the socket to connect and disconnect. * * @see docs/reference/qabstractsocket.md */ inline auto qCoro(QAbstractSocket &s) noexcept { return QCoro::detail::QCoroAbstractSocket{&s}; } //! \copydoc qCoro(QAbstractSocket &s) noexcept inline auto qCoro(QAbstractSocket *s) noexcept { return QCoro::detail::QCoroAbstractSocket{s}; } qcoro-0.4.0/qcoro/network/qcorolocalsocket.cpp000066400000000000000000000121661416564147600215510ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorolocalsocket.h" #include #include using namespace QCoro::detail; using namespace std::chrono_literals; QCoroLocalSocket::WaitForConnectedOperation::WaitForConnectedOperation(QLocalSocket *socket, int timeout_msecs) : WaitOperationBase(socket, timeout_msecs) {} bool QCoroLocalSocket::WaitForConnectedOperation::await_ready() const noexcept { return !mObj || mObj->state() == QLocalSocket::ConnectedState; } void QCoroLocalSocket::WaitForConnectedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { mConn = QObject::connect(mObj, &QLocalSocket::stateChanged, [this, awaitingCoroutine](auto newState) mutable { switch (newState) { case QLocalSocket::UnconnectedState: case QLocalSocket::ConnectingState: // Almost there... break; case QLocalSocket::ClosingState: // We shouldn't be here when waiting for Connected state... resume(awaitingCoroutine); break; case QLocalSocket::ConnectedState: resume(awaitingCoroutine); break; } }); startTimeoutTimer(awaitingCoroutine); } QCoroLocalSocket::WaitForDisconnectedOperation::WaitForDisconnectedOperation(QLocalSocket *socket, int timeout_msecs) : WaitOperationBase(socket, timeout_msecs) {} bool QCoroLocalSocket::WaitForDisconnectedOperation::await_ready() const noexcept { return !mObj || mObj->state() == QLocalSocket::UnconnectedState; } void QCoroLocalSocket::WaitForDisconnectedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) { mConn = QObject::connect(mObj, &QLocalSocket::disconnected, std::bind(&WaitForDisconnectedOperation::resume, this, awaitingCoroutine)); startTimeoutTimer(awaitingCoroutine); } bool QCoroLocalSocket::ReadOperation::await_ready() const noexcept { return QCoroIODevice::ReadOperation::await_ready() || static_cast(mDevice.data())->state() == QLocalSocket::UnconnectedState; } void QCoroLocalSocket::ReadOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { QCoroIODevice::ReadOperation::await_suspend(awaitingCoroutine); mStateConn = QObject::connect( static_cast(mDevice.data()), &QLocalSocket::stateChanged, [this, awaitingCoroutine]() { if (static_cast(mDevice.data())->state() == QLocalSocket::UnconnectedState) { finish(awaitingCoroutine); } }); } void QCoroLocalSocket::ReadOperation::finish(std::coroutine_handle<> awaitingCoroutine) { QObject::disconnect(mStateConn); QCoroIODevice::ReadOperation::finish(awaitingCoroutine); } QCoroLocalSocket::QCoroLocalSocket(QLocalSocket *socket) : QCoroIODevice(socket) {} QCoroLocalSocket::WaitForConnectedOperation QCoroLocalSocket::waitForConnected(int timeout_msecs) { return WaitForConnectedOperation{static_cast(mDevice.data()), timeout_msecs}; } QCoroLocalSocket::WaitForConnectedOperation QCoroLocalSocket::waitForConnected(std::chrono::milliseconds timeout) { return waitForConnected(static_cast(timeout.count())); } QCoroLocalSocket::WaitForDisconnectedOperation QCoroLocalSocket::waitForDisconnected(int timeout_msecs) { return WaitForDisconnectedOperation{static_cast(mDevice.data()), timeout_msecs}; } QCoroLocalSocket::WaitForDisconnectedOperation QCoroLocalSocket::waitForDisconnected(std::chrono::milliseconds timeout) { return waitForDisconnected(static_cast(timeout.count())); } QCoroLocalSocket::WaitForConnectedOperation QCoroLocalSocket::connectToServer(QIODevice::OpenMode openMode) { static_cast(mDevice.data())->connectToServer(openMode); return waitForConnected(); } QCoroLocalSocket::WaitForConnectedOperation QCoroLocalSocket::connectToServer(const QString &name, QIODevice::OpenMode openMode) { static_cast(mDevice.data())->connectToServer(name, openMode); return waitForConnected(); } QCoroLocalSocket::ReadOperation QCoroLocalSocket::readAll() { return ReadOperation(mDevice, [](QIODevice *dev) { return dev->readAll(); }); } //! \copydoc QIODevice::read QCoroLocalSocket::ReadOperation QCoroLocalSocket::read(qint64 maxSize) { return ReadOperation(mDevice, [maxSize](QIODevice *dev) { return dev->read(maxSize); }); } //! \copydoc QIODevice::readLine QCoroLocalSocket::ReadOperation QCoroLocalSocket::readLine(qint64 maxSize) { return ReadOperation(mDevice, [maxSize](QIODevice *dev) { return dev->readLine(maxSize); }); } qcoro-0.4.0/qcoro/network/qcorolocalsocket.h000066400000000000000000000111041416564147600212050ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "waitoperationbase_p.h" #include "qcoroiodevice.h" #include #include namespace QCoro::detail { using namespace std::chrono_literals; //! QLocalSocket wrapper with co_awaitable-friendly API. class QCoroLocalSocket : private QCoroIODevice { //! An Awaitable that suspends the coroutine until the socket is connected class WaitForConnectedOperation final : public WaitOperationBase { public: explicit WaitForConnectedOperation(QLocalSocket *socket, int timeout_msecs = 30'000); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; }; //! An Awaitable that suspends the coroutine until the socket is disconnected class WaitForDisconnectedOperation final : public WaitOperationBase { public: WaitForDisconnectedOperation(QLocalSocket *socket, int timeout_msecs); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine); }; class ReadOperation final : public QCoroIODevice::ReadOperation { public: using QCoroIODevice::ReadOperation::ReadOperation; bool await_ready() const noexcept final; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept final; private: void finish(std::coroutine_handle<> awaitingCoroutine) final; QMetaObject::Connection mStateConn; }; public: explicit QCoroLocalSocket(QLocalSocket *socket); //! Co_awaitable equivalent to [`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected]. WaitForConnectedOperation waitForConnected(int timeout_msecs = 30'000); //! Co_awaitable equivalent to [`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected]. /*! * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. */ WaitForConnectedOperation waitForConnected(std::chrono::milliseconds timeout); //! Co_awaitable equivalent to [`QLocalSocket::waitForDisconnected()`][qtdoc-qlocalsocket-waitForDisconnected]. WaitForDisconnectedOperation waitForDisconnected(int timeout_msecs = 30'000); //! Co_awaitable equivalent to [`QLocalSocket::waitForDisconnected()`][qtdoc-qlocalsocket-waitForDisconnected]. /*! * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. */ WaitForDisconnectedOperation waitForDisconnected(std::chrono::milliseconds timeout); //! Connects to server and waits until the connection is established. /*! * Equivalent to calling [`QLocalSocket::connecToServer`][qdoc-qlocalsocket-connecToServer] * followed by [`QLocalSocket::waitForConnected`][qdoc-qlocalsocket-waitForConnected]. */ WaitForConnectedOperation connectToServer(QIODevice::OpenMode openMode = QIODevice::ReadWrite); //! Connects to server and waits until the connection is established. /*! * Equivalent to calling [`QLocalSocket::connecToServer`][qdoc-qlocalsocket-connecToServer] * followed by [`QLocalSocket::waitForConnected`][qdoc-qlocalsocket-waitForConnected]. */ WaitForConnectedOperation connectToServer(const QString &name, QIODevice::OpenMode openMode = QIODevice::ReadWrite); //! \copydoc QIODevice::readAll ReadOperation readAll(); //! \copydoc QIODevice::read ReadOperation read(qint64 maxSize); //! \copydoc QIODevice::readLine ReadOperation readLine(qint64 maxSize = 0); }; } // namespace QCoro::detail /*! * [qtdoc-qlocalsocket-waitForConnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForConnected * [qtdoc-qlocalsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qlocalsocket.hmtl#waitForDisconnected * [qtdoc-qlocalsocket-connectToServer]: https://doc.qt.io/qt-5/qlocalsocket.html#connectToServer * [qtdoc-qlocalsocket-connectToServer-1]: https://doc.qt.io/qt-5/qlocalsocket.html#connectToServer-1 */ //! Returns a coroutine-friendly wrapper for QLocalSocket object. /*! * Returns a wrapper for the QLocalSocket \c s that provides coroutine-friendly * way to co_await the socket to connect and disconnect. * * @see docs/reference/qlocalsocket.md */ inline auto qCoro(QLocalSocket &s) noexcept { return QCoro::detail::QCoroLocalSocket{&s}; } //! \copydoc qCoro(QLocalSocket &s) noexcept inline auto qCoro(QLocalSocket *s) noexcept { return QCoro::detail::QCoroLocalSocket{s}; } qcoro-0.4.0/qcoro/network/qcoronetwork.h000066400000000000000000000003371416564147600204010ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoroabstractsocket.h" #include "qcorolocalsocket.h" #include "qcoronetworkreply.h" #include "qcorotcpserver.h" qcoro-0.4.0/qcoro/network/qcoronetworkreply.cpp000066400000000000000000000043751416564147600220160ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcoronetworkreply.h" using namespace QCoro::detail; bool QCoroNetworkReply::ReadOperation::await_ready() const noexcept { return QCoroIODevice::ReadOperation::await_ready() || static_cast(mDevice.data())->isFinished(); } void QCoroNetworkReply::ReadOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { QCoroIODevice::ReadOperation::await_suspend(awaitingCoroutine); mFinishedConn = QObject::connect( static_cast(mDevice.data()), &QNetworkReply::finished, std::bind(&ReadOperation::finish, this, awaitingCoroutine)); } void QCoroNetworkReply::ReadOperation::finish(std::coroutine_handle<> awaitingCoroutine) { QObject::disconnect(mFinishedConn); QCoroIODevice::ReadOperation::finish(awaitingCoroutine); } QCoroNetworkReply::WaitForFinishedOperation::WaitForFinishedOperation(QPointer reply) : mReply(reply) {} bool QCoroNetworkReply::WaitForFinishedOperation::await_ready() const noexcept { return !mReply || mReply->isFinished(); } void QCoroNetworkReply::WaitForFinishedOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) { if (mReply) { QObject::connect(mReply, &QNetworkReply::finished, [awaitingCoroutine]() mutable { awaitingCoroutine.resume(); }); } else { awaitingCoroutine.resume(); } } QNetworkReply *QCoroNetworkReply::WaitForFinishedOperation::await_resume() const noexcept { return mReply; } QCoroNetworkReply::ReadOperation QCoroNetworkReply::readAll() { return ReadOperation(mDevice, [](QIODevice *dev) { return dev->readAll(); }); } QCoroNetworkReply::ReadOperation QCoroNetworkReply::read(qint64 maxSize) { return ReadOperation(mDevice, [maxSize](QIODevice *dev) { return dev->read(maxSize); }); } QCoroNetworkReply::ReadOperation QCoroNetworkReply::readLine(qint64 maxSize) { return ReadOperation(mDevice, [maxSize](QIODevice *dev) { return dev->readLine(maxSize); }); } QCoroNetworkReply::WaitForFinishedOperation QCoroNetworkReply::waitForFinished() { return WaitForFinishedOperation(static_cast(mDevice.data())); } qcoro-0.4.0/qcoro/network/qcoronetworkreply.h000066400000000000000000000040271416564147600214550ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "task.h" #include "qcoroiodevice.h" #include "waitoperationbase_p.h" #include namespace QCoro::detail { class QCoroNetworkReply final : private QCoroIODevice { private: class ReadOperation final : public QCoroIODevice::ReadOperation { public: using QCoroIODevice::ReadOperation::ReadOperation; bool await_ready() const noexcept final; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept final; private: void finish(std::coroutine_handle<> awaitingCoroutine) final; QMetaObject::Connection mFinishedConn; }; class WaitForFinishedOperation final { public: explicit WaitForFinishedOperation(QPointer reply); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine); QNetworkReply *await_resume() const noexcept; private: QPointer mReply; }; friend struct awaiter_type; public: using QCoroIODevice::QCoroIODevice; ReadOperation readAll(); ReadOperation read(qint64 maxSize); ReadOperation readLine(qint64 maxSize = 0); WaitForFinishedOperation waitForFinished(); }; } // namespace QCoro::detail //! Returns a coroutine-friendly wrapper for QNetworkReply object. /*! * Returns a wrapper for the QNetworkReply \c s that provides coroutine-friendly * way to co_await read and write operations. * * @see docs/reference/qnetworkreply.md */ inline auto qCoro(QNetworkReply &s) noexcept { return QCoro::detail::QCoroNetworkReply{&s}; } //! \copydoc qCoro(QAbstractSocket &s) noexcept inline auto qCoro(QNetworkReply *s) noexcept { return QCoro::detail::QCoroNetworkReply{s}; } /*! \cond internal */ namespace QCoro::detail { template<> struct awaiter_type { using type = QCoroNetworkReply::WaitForFinishedOperation; }; } // namespace QCoro::detail qcoro-0.4.0/qcoro/network/qcorotcpserver.cpp000066400000000000000000000026371416564147600212650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorotcpserver.h" #include using namespace QCoro::detail; QCoroTcpServer::WaitForNewConnectionOperation::WaitForNewConnectionOperation(QTcpServer *server, int timeout_msecs) : WaitOperationBase(server, timeout_msecs) {} bool QCoroTcpServer::WaitForNewConnectionOperation::await_ready() const noexcept { return !mObj || mObj->hasPendingConnections(); } void QCoroTcpServer::WaitForNewConnectionOperation::await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { mConn = QObject::connect(mObj, &QTcpServer::newConnection, std::bind(&WaitForNewConnectionOperation::resume, this, awaitingCoroutine)); startTimeoutTimer(awaitingCoroutine); } QTcpSocket *QCoroTcpServer::WaitForNewConnectionOperation::await_resume() { return mTimedOut ? nullptr : mObj->nextPendingConnection(); } QCoroTcpServer::QCoroTcpServer(QTcpServer *server) : mServer(server) {} QCoroTcpServer::WaitForNewConnectionOperation QCoroTcpServer::waitForNewConnection(int timeout_msecs) { return WaitForNewConnectionOperation{mServer.data(), timeout_msecs}; } QCoroTcpServer::WaitForNewConnectionOperation QCoroTcpServer::waitForNewConnection(std::chrono::milliseconds timeout) { return WaitForNewConnectionOperation{mServer.data(), static_cast(timeout.count())}; } qcoro-0.4.0/qcoro/network/qcorotcpserver.h000066400000000000000000000040411416564147600207210ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "waitoperationbase_p.h" #include #include class QTcpServer; class QTcpSocket; namespace QCoro::detail { using namespace std::chrono_literals; //! QTcpServer wrapper with co_awaitable-friendly API. class QCoroTcpServer { //! An Awaitable that suspends the coroutine until new connection is available class WaitForNewConnectionOperation final : public WaitOperationBase { public: WaitForNewConnectionOperation(QTcpServer *server, int timeout_msecs = 30'000); bool await_ready() const noexcept; void await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept; QTcpSocket *await_resume(); }; public: ///! Constructor. explicit QCoroTcpServer(QTcpServer *server); //! Co_awaitable equivalent to [`QTcpServer::waitForNewConnection()`][qtdoc-qtcpserver-waitForNewConnection]. WaitForNewConnectionOperation waitForNewConnection(int timeout_msecs = 30'000); //! Co_awaitable equivalent to [`QTcpServer::waitForNewConnection()`][qtdoc-qtcpserver-waitForNewConnection]. /*! * Unlike the Qt version, this overload uses `std::chrono::milliseconds` to express the * timeout rather than plain `int`. */ WaitForNewConnectionOperation waitForNewConnection(std::chrono::milliseconds timeout); private: QPointer mServer; }; } // namespace QCoro::detail /*! * [qtdoc-qtcpserver-waitForNewConnection]: https://doc.qt.io/qt-5/qtcpserver.html#waitForNewConnection */ //! Returns a coroutine-friendly wrapper for QTcpServer object. /*! * Returns a wrapper for QTcpServer \c s that provides coroutine-friendly way * of co_awaiting new connections. * * @see docs/reference/qtcpserver.md */ inline auto qCoro(QTcpServer &s) noexcept { return QCoro::detail::QCoroTcpServer{&s}; } //! \copydoc qCoro(QTcpServer &s) noexcept inline auto qCoro(QTcpServer *s) noexcept { return QCoro::detail::QCoroTcpServer{s}; } qcoro-0.4.0/qcoro/qcoro.h000066400000000000000000000003151416564147600152720ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "task.h" #include "qcorocore.h" #include "qcorodbus.h" #include "qcoronetwork.h" qcoro-0.4.0/qcoro/task.h000066400000000000000000000570141416564147600151210ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "coroutine.h" #include "concepts_p.h" #include #include #include #include #include #include #include namespace QCoro { template class Task; /*! \cond internal */ namespace detail { template struct awaiter_type; template using awaiter_type_t = typename awaiter_type::type; //! Continuation that resumes a coroutine co_awaiting on currently finished coroutine. class TaskFinalSuspend { public: //! Constructs the awaitable, passing it a handle to the co_awaiting coroutine /*! * \param[in] awaitingCoroutine handle of the coroutine that is co_awaiting the current * coroutine (continuation). */ explicit TaskFinalSuspend(std::coroutine_handle<> awaitingCoroutine) : mAwaitingCoroutine(awaitingCoroutine) {} //! Returns whether the just finishing coroutine should do final suspend or not /*! * If the coroutine is not being co_awaited by another coroutine, then don't * suspend and let the code reach the end of the coroutine, which will take * care of cleaning everything up. Otherwise we suspend and let the awaiting * coroutine to clean up for us. */ bool await_ready() const noexcept { return false; } //! Called by the compiler when the just-finished coroutine is suspended. for the very last time. /*! * It is given handle of the coroutine that is being co_awaited (that is the current * coroutine). If there is a co_awaiting coroutine and it has not been resumed yet, * it resumes it. * * Finally, it destroys the just finished coroutine and frees all allocated resources. * * \param[in] finishedCoroutine handle of the just finished coroutine */ template void await_suspend(std::coroutine_handle<_Promise> finishedCoroutine) noexcept { auto &promise = finishedCoroutine.promise(); if (promise.mResumeAwaiter.exchange(true, std::memory_order_acq_rel)) { promise.mAwaitingCoroutine.resume(); } // The handle will be destroyed here only if the associated Task has already been destroyed if (promise.setDestroyHandle()) { finishedCoroutine.destroy(); } } //! Called by the compiler when the just-finished coroutine should be resumed. /*! * In reality this should never be called, as our coroutine is done, so it won't be resumed. * In any case, this method does nothing. * */ constexpr void await_resume() const noexcept {} private: //! Handle of the coroutine co_awaiting the current coroutine. std::coroutine_handle<> mAwaitingCoroutine; }; //! Base class for the \c Task promise_type. /*! * This is a promise_type for a Task returned from a coroutine. When a coroutine * is constructed, it looks at its return type, which will be \c Task, and constructs * new object of type \c Task::promise_type (which will be \c TaskPromise). Using * \c TaskPromise::get_return_object() it obtains a new object of Task. Then the * coroutine user code is executed and runs until the it reaches a suspend point - either * a co_await keyword, co_return or until it reaches the end of user code. * * You can think about promise as an interface that is callee-facing, while Task is * an caller-facing interface (in respect to the current coroutine). * * Promise interface must provide several methods: * * get_return_object() - it is called by the compiler at the very beginning of a coroutine * and is used to obtain the object that will be returned from the coroutine whenever it is * suspended. * * initial_suspend() - it is co_awaited by the code generated immediately before user * code. Depending on the Awaitable that it returns, the coroutine will either suspend * and the user code will only be executed once it is co_awaited by some other coroutine, * or it will begin executing the user code immediately. In case of QCoro, the promise always * returns std::suspend_never, which is a standard library awaitable, which prevents the * coroutine from being suspended at the beginning. * * final_suspend() - it is co_awaited when the coroutine co_returns, or when it reaches * the end of user code. Same as with initial_suspend(), depending on the type of Awaitable * it returns, it either suspends the coroutine (and then it must be destroyed explicitly * by the Awaiter), or resumes and takes care of destroying the frame pointer. In case of * QCoro, the promise returns a custom Awaitable called TaskFinalSuspend, which, when * co_awaited by the compiler-generated code will make sure that if there is a coroutine * co_awaiting on the current corutine, that the co_awaiting coroutine is resumed. * * unhandled_exception() - called by the compiler if the coroutine throws an unhandled * exception. The promise will store the exception and it will be rethrown when the * co_awaiting coroutine tries to retrieve a result of the coroutine that has thrown. * * return_value() - called by the compiler to store co_returned result of the function. * It must only be present if the coroutine is not void. * * return_void() - called by the compiler when the coroutine co_returns or flows of the * end of user code. It must only be present if the coroutine return type is void. * * await_transform() - this one is optional and is used by co_awaits inside the coroutine. * It allows the promise to transform the co_awaited type to an Awaitable. */ class TaskPromiseBase { public: //! Called when the coroutine is started to decide whether it should be suspended or not. /*! * We want coroutines that return QCoro::Task to start automatically, because it will * likely be executed from Qt's event loop, which will not co_await it, but rather call * it as a regular function, therefore it returns `std::suspend_never` awaitable, which * indicates that the coroutine should not be suspended. * */ std::suspend_never initial_suspend() const noexcept { return {}; } //! Called when the coroutine co_returns or reaches the end of user code. /*! * This decides what should happen when the coroutine is finished. */ auto final_suspend() const noexcept { return TaskFinalSuspend{mAwaitingCoroutine}; } //! Called by co_await to obtain an Awaitable for type \c T. /*! * When co_awaiting on a value of type \c T, the type \c T must an Awaitable. To allow * to co_await even on types that are not Awaitable (e.g. 3rd party types like QNetworkReply), * C++ allows promise_type to provide \c await_transform() function that can transform * the type \c T into an Awaitable. This is a very powerful mechanism in C++ coroutines. * * For types \c T for which there is no valid await_transform() overload, the C++ attempts * to use those types directly as Awaitables. This is a perfectly valid scenario in cases * when co_awaiting a type that implements the neccessary Awaitable interface. * * In our implementation, the await_transform() is overloaded only for Qt types for which * a specialiation of the \c QCoro::detail::awaiter_type template class exists. The * specialization returns type of the Awaiter for the given type \c T. */ template>> auto await_transform(T &&value) { return Awaiter{value}; } //! Specialized overload of await_transform() for Task. /*! * * When co_awaiting on a value of type \c Task, the co_await will try to obtain * an Awaitable object for the \c Task. Since \c Task already implements the * Awaitable interface it can be directly used as an Awaitable, thus this specialization * only acts as an identity operation. * * The reason we need to specialize it is that co_await will always call promise's await_transform() * overload if it exists. Since our generic await_transform() overload exists only for Qt types * that specialize the \c QCoro::detail::awaiter_type template, the compilation would fail * due to no suitable overload for \c QCoro::Task being found. * * The reason the compiler doesn't check for an existence of await_transform() overload for type T * and falling back to using the T as an Awaitable, but instead only checks for existence of * any overload of await_transform() and failing compilation if non of the overloads is suitable * for type T is, that it allows us to delete await_transform() overload for a particular type, * and thus disallow that type to be co_awaited, even if the type itself provides the Awaitable * interface. This would not be possible if the compiler would always fall back to using the T * as an Awaitable type. */ template auto await_transform(QCoro::Task &&task) { return std::forward>(task); } //! \copydoc template QCoro::TaskPromiseBase::await_transform(QCoro::Task &&) template auto &await_transform(QCoro::Task &task) { return task; } //! If the type T is already an awaitable, then just forward it as it is. template auto && await_transform(T &&awaitable) { return std::forward(awaitable); } //! \copydoc template QCoro::TaskPromiseBase::await_transform(T &&) template auto await_transform(T &awaitable) { return awaitable; } //! Called by \c TaskAwaiter when co_awaited. /*! * This function is called by a TaskAwaiter, e.g. an object obtain by co_await * when a value of Task is co_awaited (in other words, when a coroutine co_awaits on * another coroutine returning Task). * * \param awaitingCoroutine handle for the coroutine that is co_awaiting on a coroutine that * represented by this promise. When our coroutine finishes, it's * our job to resume the awaiting coroutine. */ bool setAwaitingCoroutine(std::coroutine_handle<> awaitingCoroutine) { mAwaitingCoroutine = awaitingCoroutine; return !mResumeAwaiter.exchange(true, std::memory_order_acq_rel); } bool hasAwaitingCoroutine() const { return mAwaitingCoroutine != nullptr; } bool setDestroyHandle() noexcept { return mDestroyHandle.exchange(true, std::memory_order_acq_rel); } private: friend class TaskFinalSuspend; //! Handle of the coroutine that is currently co_awaiting this Awaitable std::coroutine_handle<> mAwaitingCoroutine; //! Indicates whether the awaiter should be resumed when it tries to co_await on us. std::atomic mResumeAwaiter{false}; //! Indicates whether we can destroy the coroutine handle std::atomic mDestroyHandle{false}; }; //! The promise_type for Task /*! * See \ref TaskPromiseBase documentation for explanation about promise_type. */ template class TaskPromise final : public TaskPromiseBase { public: explicit TaskPromise() = default; ~TaskPromise() = default; //! Constructs a Task for this promise. Task get_return_object() noexcept; //! Called by the compiler when user code throws an unhandled exception. /*! * When user code throws but doesn't catch, it is ultimately caught by the code generated by * the compiler (effectively the entire user code is wrapped in try...catch ) and this method * is called. This method stores the exception. The exception is re-thrown when the calling * coroutine is resumed and tries to retrieve result of the finished coroutine that has thrown. */ void unhandled_exception() { mValue = std::current_exception(); } //! Called form co_return statement to store result of the coroutine. /*! * \param[in] value the value returned by the coroutine. It is stored in the * promise, later can be retrieved by the calling coroutine. */ void return_value(T &&value) noexcept { mValue = std::forward(value); } //! \copydoc template TaskPromise::return_value(T &&value) noexcept void return_value(const T &value) noexcept { mValue = value; } template requires concepts::constructible_from void return_value(U &&value) noexcept { mValue = std::move(value); } template requires concepts::constructible_from void return_value(const U &value) noexcept { mValue = value; } //! Retrieves the result of the coroutine. /*! * \return the value co_returned by the finished coroutine. If the coroutine has * thrown an exception, this method will instead rethrow the exception. */ T &result() & { if (std::holds_alternative(mValue)) { std::rethrow_exception(std::get(mValue)); } return std::get(mValue); } //! \copydoc T &QCoro::TaskPromise::result() & T &&result() && { if (std::holds_alternative(mValue)) { std::rethrow_exception(std::get(mValue)); } return std::move(std::get(mValue)); } private: //! Holds either the return value of the coroutine or exception thrown by the coroutine. std::variant mValue; }; //! Specialization of TaskPromise for coroutines returning \c void. template<> class TaskPromise final : public TaskPromiseBase { public: // Constructor. explicit TaskPromise() = default; //! Destructor. ~TaskPromise() = default; //! \copydoc TaskPromise::get_return_object() Task get_return_object() noexcept; //! \copydoc TaskPromise::unhandled_exception() void unhandled_exception() { mException = std::current_exception(); } //! Promise type must have this function when the coroutine return type is void. void return_void() noexcept {}; //! Provides access to the result of the coroutine. /*! * Since this is a promise type for a void coroutine, the only result that * this can return is re-throwing an exception thrown by the coroutine, if * there's any. */ void result() { if (mException) { std::rethrow_exception(mException); } } private: //! Exception thrown by the coroutine. std::exception_ptr mException; }; //! Base-class for Awaiter objects returned by the \c Task operator co_await(). template class TaskAwaiterBase { public: //! Returns whether to co_await bool await_ready() const noexcept { return !mAwaitedCoroutine || mAwaitedCoroutine.done(); } //! Called by co_await in a coroutine that co_awaits our awaited coroutine managed by the current task. /*! * In other words, let's have a case like this: * \code{.cpp} * Task<> doSomething() { * ... * co_return result; * }; * * Task<> getSomething() { * ... * const auto something = co_await doSomething(); * ... * } * \endcode * * If this Awaiter object is an awaiter of the doSomething() coroutine (e.g. has been constructed * by the co_await), then \c mAwaitedCoroutine is the handle of the doSomething() coroutine, * and \c awaitingCoroutine is a handle of the getSomething() coroutine which is awaiting the * completion of the doSomething() coroutine. * * This is implemented by passing the awaiting coroutine handle to the promise of the * awaited coroutine. When the awaited coroutine finishes, the promise will take care of * resuming the awaiting coroutine. At the same time this function resumes the awaited * coroutine. * * \param[in] awaitingCoroutine handle of the coroutine that is currently co_awaiting the * coroutine represented by this Tak. * \return returns whether the awaiting coroutine should be suspended, or whether the * co_awaited coroutine has finished synchronously and the co_awaiting coroutine doesn't * have to suspend. */ bool await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept { return mAwaitedCoroutine.promise().setAwaitingCoroutine(awaitingCoroutine); } protected: //! Constucts a new Awaiter object. /*! * \param[in] coroutine hande for the coroutine that is being co_awaited. */ TaskAwaiterBase(std::coroutine_handle<_Promise> awaitedCoroutine) : mAwaitedCoroutine(awaitedCoroutine) {} //! Handle of the coroutine that is being co_awaited by this awaiter std::coroutine_handle<_Promise> mAwaitedCoroutine = {}; }; } // namespace detail /*! \endcond */ //! An asynchronously executed task. /*! * When a coroutine is called which has return type Task, the coroutine will * construct a new instance of Task which will be returned to the caller when * the coroutine is suspended - that is either when it co_awaits another coroutine * of finishes executing user code. * * In the sense of the interface that the task implements, it is an Awaitable. * * Task is constructed by a code generated at the beginning of the coroutine by * the compiler (i.e. before the user code). The code first creates a new frame * pointer, which internally holds the promise. The promise is of type \c R::promise_type, * where \c R is the return type of the function (so \c Task, in our case. * * One can think about it as Task being the caller-facing interface and promise being * the callee-facing interface. */ template class Task { public: //! Promise type of the coroutine. This is required by the C++ standard. using promise_type = detail::TaskPromise; //! The type of the coroutine return value. using value_type = T; //! Constructs a new empty task. explicit Task() noexcept = default; //! Constructs a task bound to a coroutine. /*! * \param[in] coroutine handle of the coroutine that has constructed the task. */ explicit Task(std::coroutine_handle coroutine) : mCoroutine(coroutine) {} //! Task cannot be copy-constructed. Task(const Task &) = delete; //! Task cannot be copy-assigned. Task &operator=(const Task &) = delete; //! The task can be move-constructed. Task(Task &&other) noexcept : mCoroutine(other.mCoroutine) { other.mCoroutine = nullptr; } //! The task can be move-assigned. Task &operator=(Task &&other) noexcept { if (std::addressof(other) != this) { if (mCoroutine) { // The coroutine handle will be destroyed only after TaskFinalSuspend if (mCoroutine.promise().setDestroyHandle()) { mCoroutine.destroy(); } } mCoroutine = other.mCoroutine; other.mCoroutine = nullptr; } return *this; } //! Destructor. ~Task() { if (!mCoroutine) return; // The coroutine handle will be destroyed only after TaskFinalSuspend if (mCoroutine.promise().setDestroyHandle()) { mCoroutine.destroy(); } }; //! Returns whether the task has finished. /*! * A task that is ready (represents a finished coroutine) must not attempt * to suspend the coroutine again. */ bool isReady() const { return !mCoroutine || mCoroutine.done(); } //! Provides an Awaiter for the coroutine machinery. /*! * The coroutine machinery looks for a suitable operator co_await overload * for the current Awaitable (this Task). It calls it to obtain an Awaiter * object, that is an object that the co_await keyword uses to suspend and * resume the coroutine. */ auto operator co_await() const noexcept { //! Specialization of the TaskAwaiterBase that returns the promise result by value class TaskAwaiter : public detail::TaskAwaiterBase { public: TaskAwaiter(std::coroutine_handle awaitedCoroutine) : detail::TaskAwaiterBase{awaitedCoroutine} {} //! Called when the co_awaited coroutine is resumed. /* * \return the result from the coroutine's promise, factically the * value co_returned by the coroutine. */ auto await_resume() { Q_ASSERT(this->mAwaitedCoroutine != nullptr); if constexpr (!std::is_void_v) { return std::move(this->mAwaitedCoroutine.promise().result()); } else { // Wil re-throw exception, if any is stored this->mAwaitedCoroutine.promise().result(); } } }; return TaskAwaiter{mCoroutine}; } private: //! The coroutine represented by this task /*! * In other words, this is a handle to the coroutine that has constructed and * returned this Task. * */ std::coroutine_handle mCoroutine = {}; }; namespace detail { template inline Task TaskPromise::get_return_object() noexcept { return Task{std::coroutine_handle::from_promise(*this)}; } Task inline TaskPromise::get_return_object() noexcept { return Task{std::coroutine_handle::from_promise(*this)}; } } // namespace detail namespace detail { //! Helper class to run a coroutine in a nested event loop. /*! * We cannot just use QTimer or QMetaObject::invokeMethod() to schedule the func lambda to be * invoked from an event loop, because internally, Qt deallocates some structures when the * lambda returns, which causes invalid memory access and potentially double-free corruption * because the coroutine returns twice - once on suspend and once when it really finishes. * 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 Task<> runCoroutine(QEventLoop &loop, Coroutine &&coroutine) { co_await coroutine; loop.quit(); } template Task<> runCoroutine(QEventLoop &loop, T &result, Coroutine &&coroutine) { result = co_await coroutine; loop.quit(); } template T waitFor(Coroutine &&coro) { QEventLoop loop; if constexpr (std::is_void_v) { runCoroutine(loop, std::forward(coro)); loop.exec(); } else { T result; runCoroutine(loop, result, std::forward(coro)); loop.exec(); return result; } } } // namespace detail //! Waits for a coroutine to complete in a blocking manner. /*! * Sometimes you may need to wait for a coroutine to finish without co_awaiting it - that is, * you want to wait for the coroutine in a blocking mode. This function does exactly that. * The function creates a nested QEventLoop and executes it until the coroutine has finished. * * \param task Coroutine to blockingly wait for. * \returns Result of the coroutine. */ template inline T waitFor(QCoro::Task &task) { return detail::waitFor(task); } // \overload template inline T waitFor(QCoro::Task &&task) { return detail::waitFor(task); } } // namespace QCoro qcoro-0.4.0/qcoro/waitoperationbase_p.h000066400000000000000000000032061416564147600202100ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include "macros_p.h" #include "coroutine.h" #include #include namespace QCoro::detail { //! Base class for co_awaitable waitFor* operations. template class WaitOperationBase { public: Q_DISABLE_COPY(WaitOperationBase) QCORO_DEFAULT_MOVE(WaitOperationBase) ~WaitOperationBase() = default; bool await_resume() noexcept { return !mTimedOut; } protected: WaitOperationBase(T *obj, int timeout_msecs) : mObj{obj} { if (timeout_msecs > -1) { mTimeoutTimer = std::make_unique(); mTimeoutTimer->setInterval(timeout_msecs); mTimeoutTimer->setSingleShot(true); } } void startTimeoutTimer(std::coroutine_handle<> awaitingCoroutine) { if (!mTimeoutTimer) { return; } QObject::connect(mTimeoutTimer.get(), &QTimer::timeout, [this, awaitingCoroutine]() mutable { mTimedOut = true; resume(awaitingCoroutine); }); mTimeoutTimer->start(); } void resume(std::coroutine_handle<> awaitingCoroutine) { if (mTimeoutTimer) { mTimeoutTimer->stop(); } QObject::disconnect(mConn); QTimer::singleShot(0, [awaitingCoroutine]() mutable { awaitingCoroutine.resume(); }); } QPointer mObj; std::unique_ptr mTimeoutTimer; QMetaObject::Connection mConn; bool mTimedOut = false; }; } // namespace QCoro::detail qcoro-0.4.0/requirements.txt000066400000000000000000000001611416564147600161360ustar00rootroot00000000000000setuptools wheel mkdocs mkdocs-material mkdocs-section-index mkdocs-include-markdown-plugin mkdocs-macros-plugin qcoro-0.4.0/tests/000077500000000000000000000000001416564147600140165ustar00rootroot00000000000000qcoro-0.4.0/tests/CMakeLists.txt000066400000000000000000000071611416564147600165630ustar00rootroot00000000000000include(CMakeParseArguments) add_library(qcoro_test STATIC testobject.cpp) target_link_libraries(qcoro_test PUBLIC QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Test ) if (QCORO_WITH_QTDBUS) add_executable(testdbusserver EXCLUDE_FROM_ALL testdbusserver.cpp) target_link_libraries(testdbusserver Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) set(qcoro_test_dbus_SRCS) if (${QT_VERSION_MAJOR} EQUAL 5) qt5_add_dbus_interface(qcoro_test_dbus_SRCS cz.dvratil.qcorodbustest.xml qcorodbustestinterface) else() qt_add_dbus_interface(qcoro_test_dbus_SRCS cz.dvratil.qcorodbustest.xml qcorodbustestinterface) endif() add_library(qcoro_test_dbus OBJECT ${qcoro_test_dbus_SRCS}) target_link_libraries(qcoro_test_dbus PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::DBus ) endif() function(qcoro_add_test _name) set(options) set(oneValueArgs) set(multiValueAgs LINK_LIBRARIES) cmake_parse_arguments(TEST "${options}" "${oneValueArgs}" "${multiValueAgs}" ${ARGN}) add_executable(test-${_name} ${_name}.cpp) target_link_libraries( test-${_name} qcoro_test QCoro${QT_VERSION_MAJOR}Core Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Test ${TEST_LINK_LIBRARIES} Threads::Threads ) add_test(NAME test-${_name} COMMAND test-${_name}) endfunction() function(qcoro_add_network_test _name) set(options) set(oneValueArgs) set(multiValueAgs LINK_LIBRARIES) cmake_parse_arguments(TEST "${options}" "${oneValueArgs}" "${multiValueAgs}" ${ARGN}) add_executable(test-${_name} ${_name}.cpp) target_link_libraries( test-${_name} QCoro${QT_VERSION_MAJOR}Network qcoro_test Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Test Qt${QT_VERSION_MAJOR}::Network Threads::Threads ${TEST_LINK_LIBRARIES} ) add_test(NAME test-${_name} COMMAND test-${_name}) endfunction() function(qcoro_add_dbus_test _name) set(options) set(oneValueArgs) set(multiValueAgs LINK_LIBRARIES) cmake_parse_arguments(TEST "${options}" "${oneValueArgs}" "${multiValueAgs}" ${ARGN}) set(test_SRCS ${_name}.cpp) add_executable(test-${_name} ${test_SRCS}) add_dependencies(test-${_name} testdbusserver qcoro_test_dbus) target_link_libraries( test-${_name} QCoro${QT_VERSION_MAJOR}DBus qcoro_test qcoro_test_dbus ${TEST_LINK_LIBRARIES} ) target_include_directories(test-${_name} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_compile_definitions(test-${_name} PRIVATE TESTDBUSSERVER_EXECUTABLE=\"$\") if (APPLE) # On MacOS dbus-launch doesn't work, so we rely on the session dbus running add_test(NAME test-${_name} COMMAND test-${_name}) else() add_test(NAME test-${_name} COMMAND dbus-launch $) endif() endfunction() qcoro_add_test(qtimer) qcoro_add_test(qcoroprocess) qcoro_add_test(qcorosignal) qcoro_add_test(task) qcoro_add_test(testconstraints) qcoro_add_test(qfuture LINK_LIBRARIES Qt${QT_VERSION_MAJOR}::Concurrent) if (QCORO_WITH_QTDBUS) qcoro_add_dbus_test(qdbuspendingcall) qcoro_add_dbus_test(qdbuspendingreply) endif() if (QCORO_WITH_QTNETWORK) qcoro_add_network_test(qcorolocalsocket) qcoro_add_network_test(qcoroabstractsocket) qcoro_add_network_test(qcoronetworkreply) qcoro_add_network_test(qcorotcpserver) # Tests for test utilities qcoro_add_network_test(testhttpserver) endif() qcoro-0.4.0/tests/cz.dvratil.qcorodbustest.xml000066400000000000000000000020241416564147600215160ustar00rootroot00000000000000 qcoro-0.4.0/tests/qcoroabstractsocket.cpp000066400000000000000000000131651416564147600206100ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testhttpserver.h" #include "testobject.h" #include "qcoro/network/qcoroabstractsocket.h" #include #include #include class QCoroAbstractSocketTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testWaitForConnectedTriggers_coro(QCoro::TestContext) { QTcpSocket socket; QTimer::singleShot(10ms, [this, &socket]() mutable { socket.connectToHost(QHostAddress::LocalHost, mServer.port()); }); co_await qCoro(socket).waitForConnected(); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); } QCoro::Task<> testWaitForDisconnectedTriggers_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); QTimer::singleShot(10ms, [&socket]() mutable { socket.disconnectFromHost(); }); co_await qCoro(socket).waitForDisconnected(); QCORO_COMPARE(socket.state(), QAbstractSocket::UnconnectedState); } QCoro::Task<> testDoesntCoAwaitConnectedSocket_coro(QCoro::TestContext context) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); context.setShouldNotSuspend(); co_await qCoro(socket).waitForConnected(); } QCoro::Task<> testDoesntCoAwaitDisconnectedSocket_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); mServer.setExpectTimeout(true); // no-one actually connects, so the server times out. QTcpSocket socket; QCORO_COMPARE(socket.state(), QAbstractSocket::UnconnectedState); co_await qCoro(socket).waitForDisconnected(); } QCoro::Task<> testConnectToServerWithArgs_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); } QCoro::Task<> testWaitForConnectedTimeout_coro(QCoro::TestContext) { mServer.setExpectTimeout(true); QTcpSocket socket; const auto start = std::chrono::steady_clock::now(); const bool ok = co_await qCoro(socket).waitForConnected(10ms); const auto end = std::chrono::steady_clock::now(); QCORO_VERIFY(!ok); QCORO_VERIFY(end - start < 500ms); // give some leeway } QCoro::Task<> testWaitForDisconnectedTimeout_coro(QCoro::TestContext) { mServer.setExpectTimeout(true); QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); const auto start = std::chrono::steady_clock::now(); const bool ok = co_await qCoro(socket).waitForDisconnected(10ms); const auto end = std::chrono::steady_clock::now(); QCORO_VERIFY(!ok); QCORO_VERIFY(end - start < 500ms); } QCoro::Task<> testReadAllTriggers_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QByteArray data; while (socket.state() == QAbstractSocket::ConnectedState) { data += co_await qCoro(socket).readAll(); } QCORO_VERIFY(!data.isEmpty()); data += socket.readAll(); // read what's left in the buffer QCORO_VERIFY(!data.isEmpty()); } QCoro::Task<> testReadTriggers_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QByteArray data; while (socket.state() == QAbstractSocket::ConnectedState) { data += co_await qCoro(socket).read(1); } QCORO_VERIFY(!data.isEmpty()); data += socket.readAll(); // read what's left in the buffer QCORO_VERIFY(!data.isEmpty()); } QCoro::Task<> testReadLineTriggers_coro(QCoro::TestContext) { QTcpSocket socket; co_await qCoro(socket).connectToHost(QHostAddress::LocalHost, mServer.port()); QCORO_COMPARE(socket.state(), QAbstractSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QByteArrayList lines; while (socket.state() == QAbstractSocket::ConnectedState) { const auto line = co_await qCoro(socket).readLine(); if (!line.isNull()) { lines.push_back(line); } } QCORO_COMPARE(lines.size(), 14); } private Q_SLOTS: void init() { mServer.start(QHostAddress::LocalHost); } void cleanup() { mServer.stop(); } addTest(WaitForConnectedTriggers) addTest(WaitForConnectedTimeout) addTest(WaitForDisconnectedTriggers) addTest(WaitForDisconnectedTimeout) addTest(DoesntCoAwaitConnectedSocket) addTest(DoesntCoAwaitDisconnectedSocket) addTest(ConnectToServerWithArgs) addTest(ReadAllTriggers) addTest(ReadTriggers) addTest(ReadLineTriggers) private: TestHttpServer mServer; }; QTEST_GUILESS_MAIN(QCoroAbstractSocketTest) #include "qcoroabstractsocket.moc" qcoro-0.4.0/tests/qcorolocalsocket.cpp000066400000000000000000000141001416564147600200650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testhttpserver.h" #include "testobject.h" #include "qcoro/network/qcorolocalsocket.h" #include #include #include class QCoroLocalSocketTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testWaitForConnectedTriggers_coro(QCoro::TestContext) { QLocalSocket socket; QTimer::singleShot(10ms, [&socket]() mutable { socket.connectToServer(QCoroLocalSocketTest::getSocketName()); }); co_await qCoro(socket).waitForConnected(); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); } QCoro::Task<> testWaitForDisconnectedTriggers_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); QTimer::singleShot(10ms, [&socket]() mutable { socket.disconnectFromServer(); }); co_await qCoro(socket).waitForDisconnected(); QCORO_COMPARE(socket.state(), QLocalSocket::UnconnectedState); } // On Linux at least, QLocalSocket connects immediately and synchronously QCoro::Task<> testDoesntCoAwaitConnectedSocket_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); co_await qCoro(socket).waitForConnected(); } QCoro::Task<> testDoesntCoAwaitDisconnectedSocket_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); mServer.setExpectTimeout(true); QLocalSocket socket; QCORO_COMPARE(socket.state(), QLocalSocket::UnconnectedState); co_await qCoro(socket).waitForDisconnected(); } QCoro::Task<> testConnectToServerWithArgs_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); QLocalSocket socket; co_await qCoro(socket).connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); } QCoro::Task<> testConnectToServer_coro(QCoro::TestContext context) { context.setShouldNotSuspend(); QLocalSocket socket; socket.setServerName(QCoroLocalSocketTest::getSocketName()); co_await qCoro(socket).connectToServer(); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); } QCoro::Task<> testWaitForConnectedTimeout_coro(QCoro::TestContext) { mServer.setExpectTimeout(true); QLocalSocket socket; const auto start = std::chrono::steady_clock::now(); const bool ok = co_await qCoro(socket).waitForConnected(10ms); const auto end = std::chrono::steady_clock::now(); QCORO_VERIFY(!ok); QCORO_VERIFY(end - start < 500ms); } QCoro::Task<> testWaitForDisconnectedTimeout_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); const auto start = std::chrono::steady_clock::now(); const bool ok = co_await qCoro(socket).waitForDisconnected(10ms); const auto end = std::chrono::steady_clock::now(); QCORO_VERIFY(!ok); QCORO_VERIFY(end - start < 500ms); } QCoro::Task<> testReadAllTriggers_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QByteArray data; while (socket.state() == QLocalSocket::ConnectedState) { data += co_await qCoro(socket).readAll(); } QCORO_VERIFY(!data.isEmpty()); data += socket.readAll(); // read what's left in the buffer QCORO_VERIFY(!data.isEmpty()); } QCoro::Task<> testReadTriggers_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QByteArray data; while (socket.state() == QLocalSocket::ConnectedState) { data += co_await qCoro(socket).read(1); } QCORO_VERIFY(!data.isEmpty()); data += socket.readAll(); // read what's left in the buffer QCORO_VERIFY(!data.isEmpty()); } QCoro::Task<> testReadLineTriggers_coro(QCoro::TestContext) { QLocalSocket socket; socket.connectToServer(QCoroLocalSocketTest::getSocketName()); QCORO_COMPARE(socket.state(), QLocalSocket::ConnectedState); socket.write("GET /stream HTTP/1.1\r\n"); QByteArrayList lines; while (socket.state() == QLocalSocket::ConnectedState) { const auto line = co_await qCoro(socket).readLine(); if (!line.isNull()) { lines.push_back(line); } } QCORO_COMPARE(lines.size(), 14); } private Q_SLOTS: void init() { mServer.start(QCoroLocalSocketTest::getSocketName()); } void cleanup() { mServer.stop(); } addTest(WaitForConnectedTriggers) addTest(WaitForConnectedTimeout) addTest(WaitForDisconnectedTriggers) addTest(WaitForDisconnectedTimeout) addTest(DoesntCoAwaitConnectedSocket) addTest(DoesntCoAwaitDisconnectedSocket) addTest(ConnectToServerWithArgs) addTest(ConnectToServer) addTest(ReadAllTriggers) addTest(ReadTriggers) addTest(ReadLineTriggers) private: static QString getSocketName() { return QStringLiteral("%1-%2") .arg(QCoreApplication::applicationName()) .arg(QCoreApplication::applicationPid()); } TestHttpServer mServer; }; QTEST_GUILESS_MAIN(QCoroLocalSocketTest) #include "qcorolocalsocket.moc" qcoro-0.4.0/tests/qcoronetworkreply.cpp000066400000000000000000000106361416564147600203410ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testhttpserver.h" #include "testobject.h" #include "qcoro/network/qcoronetworkreply.h" #include #include #include class QCoroNetworkReplyTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto *reply = nam.get(QNetworkRequest{QStringLiteral("http://localhost:%1").arg(mServer.port())}); co_await reply; QCORO_VERIFY(reply->isFinished()); QCORO_COMPARE(reply->error(), QNetworkReply::NoError); QCORO_COMPARE(reply->readAll(), "abcdef"); delete reply; } QCoro::Task<> testQCoroWrapperTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto *reply = nam.get(QNetworkRequest{QStringLiteral("http://localhost:%1").arg(mServer.port())}); co_await qCoro(reply).waitForFinished(); QCORO_VERIFY(reply->isFinished()); QCORO_COMPARE(reply->error(), QNetworkReply::NoError); QCORO_COMPARE(reply->readAll(), "abcdef"); delete reply; } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; QNetworkAccessManager nam; auto *reply = nam.get( QNetworkRequest{QStringLiteral("http://localhost:%1/block").arg(mServer.port())}); co_await reply; QCORO_VERIFY(eventLoopResponsive); QCORO_VERIFY(reply->isFinished()); QCORO_COMPARE(reply->error(), QNetworkReply::NoError); QCORO_COMPARE(reply->readAll(), "abcdef"); delete reply; } QCoro::Task<> testDoesntCoAwaitNullReply_coro(QCoro::TestContext test) { test.setShouldNotSuspend(); mServer.setExpectTimeout(true); QNetworkReply *reply = nullptr; co_await reply; delete reply; } QCoro::Task<> testDoesntCoAwaitFinishedReply_coro(QCoro::TestContext test) { QNetworkAccessManager nam; auto *reply = nam.get(QNetworkRequest{QStringLiteral("http://localhost:%1").arg(mServer.port())}); co_await reply; QCORO_VERIFY(reply->isFinished()); test.setShouldNotSuspend(); co_await reply; delete reply; } QCoro::Task<> testReadAllTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto *reply = nam.get( QNetworkRequest{QStringLiteral("http://127.0.0.1:%1/stream").arg(mServer.port())}); QByteArray data; while (!reply->isFinished()) { data += co_await qCoro(reply).readAll(); } data += reply->readAll(); // read what's left in the buffer QCORO_COMPARE(data.size(), reply->rawHeader("Content-Length").toInt()); } QCoro::Task<> testReadTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto *reply = nam.get( QNetworkRequest{QStringLiteral("http://127.0.0.1:%1/stream").arg(mServer.port())}); QByteArray data; while (!reply->isFinished()) { data += co_await qCoro(reply).read(1); } data += reply->readAll(); // read what's left in the buffer QCORO_COMPARE(data.size(), reply->rawHeader("Content-Length").toInt()); } QCoro::Task<> testReadLineTriggers_coro(QCoro::TestContext) { QNetworkAccessManager nam; auto *reply = nam.get( QNetworkRequest{QStringLiteral("http://127.0.0.1:%1/stream").arg(mServer.port())}); QByteArrayList lines; while (!reply->isFinished()) { const auto line = co_await qCoro(reply).readLine(); if (!line.isNull()) { lines.push_back(line); } } QCORO_COMPARE(lines.size(), 10); } private Q_SLOTS: void init() { mServer.start(QHostAddress::LocalHost); } void cleanup() { mServer.stop(); } addTest(Triggers) addTest(QCoroWrapperTriggers) addTest(DoesntBlockEventLoop) addTest(DoesntCoAwaitNullReply) addTest(DoesntCoAwaitFinishedReply) addTest(ReadAllTriggers) addTest(ReadTriggers) addTest(ReadLineTriggers) private: TestHttpServer mServer; }; QTEST_GUILESS_MAIN(QCoroNetworkReplyTest) #include "qcoronetworkreply.moc" qcoro-0.4.0/tests/qcoroprocess.cpp000066400000000000000000000115701416564147600172500ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcoro/core/qcoroprocess.h" #include #ifdef Q_OS_WIN // There's no equivalent to "true" command on Windows, so do a single ping to localhost instead, // which terminates almost immediately. #define DUMMY_EXEC QStringLiteral("ping") #define DUMMY_ARGS \ { QStringLiteral("127.0.0.1"), QStringLiteral("-n"), QStringLiteral("1") } // On windows, the equivalent to Linux "sleep" is "timeout", but it fails due to QProcess redirecting // stdin, which "timeout" doesn't support (it waits for keypress to interrupt). However, "ping" pings // every second, so specifying number of pings to the desired timeout makes it behave basically like // the Linux "sleep". #define SLEEP_EXEC QStringLiteral("ping") #define SLEEP_ARGS(timeout) \ { QStringLiteral("127.0.0.1"), QStringLiteral("-n"), QString::number(timeout) } #else #define DUMMY_EXEC QStringLiteral("true") #define DUMMY_ARGS {} #define SLEEP_EXEC QStringLiteral("sleep") #define SLEEP_ARGS(timeout) { QString::number(timeout) } #endif class QCoroProcessTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testStartTriggers_coro(QCoro::TestContext context) { #ifdef Q_OS_WIN // QProcess::start() on Windows is synchronous, despite what the documentation says, // so the coroutine will not suspend. context.setShouldNotSuspend(); #else Q_UNUSED(context); #endif QProcess process; co_await qCoro(process).start(DUMMY_EXEC, DUMMY_ARGS); QCORO_COMPARE(process.state(), QProcess::Running); process.waitForFinished(); } QCoro::Task<> testStartNoArgsTriggers_coro(QCoro::TestContext context) { #ifdef Q_OS_WIN context.setShouldNotSuspend(); #else Q_UNUSED(context); #endif QProcess process; process.setProgram(DUMMY_EXEC); process.setArguments(DUMMY_ARGS); co_await qCoro(process).start(); QCORO_COMPARE(process.state(), QProcess::Running); process.waitForFinished(); } QCoro::Task<> testStartDoesntBlock_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive{1, 0ms}; QProcess process; co_await qCoro(process).start(DUMMY_EXEC, DUMMY_ARGS); QCORO_VERIFY(eventLoopResponsive); process.waitForFinished(); } QCoro::Task<> testStartDoesntCoAwaitRunningProcess_coro(QCoro::TestContext ctx) { QProcess process; #if defined(__GNUC__) && !defined(__clang__) #pragma message "Workaround for GCC ICE!" // Workaround GCC bug https://bugzilla.redhat.com/1952671 // GCC ICEs at the end of this function due to presence of two co_await statements. process.start(SLEEP_EXEC, SLEEP_ARGS(1)); process.waitForStarted(); #else co_await qCoro(process).start(SLEEP_EXEC, SLEEP_ARGS(1)); #endif QCORO_COMPARE(process.state(), QProcess::Running); ctx.setShouldNotSuspend(); QTest::ignoreMessage(QtWarningMsg, "QProcess::start: Process is already running"); co_await qCoro(process).start(); process.waitForFinished(); } QCoro::Task<> testFinishTriggers_coro(QCoro::TestContext) { QProcess process; process.start(SLEEP_EXEC, SLEEP_ARGS(1)); process.waitForStarted(); QCORO_COMPARE(process.state(), QProcess::Running); const auto ok = co_await qCoro(process).waitForFinished(); QCORO_VERIFY(ok); QCORO_COMPARE(process.state(), QProcess::NotRunning); } QCoro::Task<> testFinishDoesntCoAwaitFinishedProcess_coro(QCoro::TestContext ctx) { QProcess process; process.start(DUMMY_EXEC, QStringList DUMMY_ARGS); process.waitForFinished(); ctx.setShouldNotSuspend(); const auto ok = co_await qCoro(process).waitForFinished(); QCORO_VERIFY(ok); } QCoro::Task<> testFinishCoAwaitTimeout_coro(QCoro::TestContext) { QProcess process; process.start(SLEEP_EXEC, SLEEP_ARGS(2)); process.waitForStarted(); QCORO_COMPARE(process.state(), QProcess::Running); const auto ok = co_await qCoro(process).waitForFinished(1s); QCORO_VERIFY(!ok); QCORO_COMPARE(process.state(), QProcess::Running); process.waitForFinished(); } private Q_SLOTS: addTest(StartTriggers) addTest(StartNoArgsTriggers) #ifndef Q_OS_WIN // start always blocks on Windows addTest(StartDoesntBlock) #endif addTest(StartDoesntCoAwaitRunningProcess) addTest(FinishTriggers) addTest(FinishDoesntCoAwaitFinishedProcess) addTest(FinishCoAwaitTimeout) }; QTEST_GUILESS_MAIN(QCoroProcessTest) #include "qcoroprocess.moc" qcoro-0.4.0/tests/qcorosignal.cpp000066400000000000000000000035541416564147600170520ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcoro/core/qcorosignal.h" #include class SignalTest : public QObject { Q_OBJECT public: explicit SignalTest() { QTimer::singleShot(100ms, this, [this]() { Q_EMIT voidSignal(); Q_EMIT singleArg(QStringLiteral("YAY!")); Q_EMIT multiArg(QStringLiteral("YAY!"), 42, this); }); } Q_SIGNALS: void voidSignal(); void singleArg(const QString &value); void multiArg(const QString &value, int number, QObject *ptr); }; class QCoroSignalTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { SignalTest obj; co_await qCoro(&obj, &SignalTest::voidSignal); static_assert( std::is_void_v); } QCoro::Task<> testReturnsValue_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::singleArg); static_assert(std::is_same_v); QCORO_COMPARE(result, QStringLiteral("YAY!")); } QCoro::Task<> testReturnsTuple_coro(QCoro::TestContext) { SignalTest obj; const auto result = co_await qCoro(&obj, &SignalTest::multiArg); static_assert( std::is_same_v>); const auto [value, number, ptr] = result; QCORO_COMPARE(value, QStringLiteral("YAY!")); QCORO_COMPARE(number, 42); QCORO_COMPARE(ptr, &obj); } private Q_SLOTS: addTest(Triggers) addTest(ReturnsValue) addTest(ReturnsTuple) }; QTEST_GUILESS_MAIN(QCoroSignalTest) #include "qcorosignal.moc" qcoro-0.4.0/tests/qcorotcpserver.cpp000066400000000000000000000057221416564147600176110ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcoro/network/qcorotcpserver.h" #include "qcoro/core/qcoroiodevice.h" #include #include #include #include using namespace std::chrono_literals; class QCoroTcpServerTest: public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testWaitForNewConnectionTriggers_coro(QCoro::TestContext) { QTcpServer server; QCORO_VERIFY(server.listen(QHostAddress::LocalHost)); const int serverPort = server.serverPort(); std::mutex mutex; bool ok = false; std::thread clientThread{[&]() mutable { std::this_thread::sleep_for(500ms); QTcpSocket socket; socket.connectToHost(QHostAddress::LocalHost, serverPort); std::lock_guard lock{mutex}; if (!socket.waitForConnected(10'000)) { ok = false; return; } socket.write("Hello World!"); socket.flush(); socket.close(); ok = true; }}; auto *connection = co_await qCoro(server).waitForNewConnection(10s); QCORO_VERIFY(connection != nullptr); const auto data = co_await qCoro(connection).readAll(); QCORO_COMPARE(data, QByteArray{"Hello World!"}); std::lock_guard lock{mutex}; QCORO_VERIFY(ok); clientThread.join(); } QCoro::Task<> testDoesntCoAwaitPendingConnection_coro(QCoro::TestContext testContext) { testContext.setShouldNotSuspend(); QTcpServer server; QCORO_VERIFY(server.listen(QHostAddress::LocalHost)); const int serverPort = server.serverPort(); bool ok = false; std::mutex mutex; std::thread clientThread{[&]() mutable { QTcpSocket socket; socket.connectToHost(QHostAddress::LocalHost, serverPort); std::lock_guard lock{mutex}; if (!socket.waitForConnected(10'000)) { ok = false; return; } socket.write("Hello World!"); socket.flush(); socket.close(); ok = true; }}; QCORO_VERIFY(server.waitForNewConnection(10'000)); auto *connection = co_await qCoro(server).waitForNewConnection(10s); connection->waitForReadyRead(); // can't use coroutine, it might suspend or not, depending on how eventloop // gets triggered, which fails the test since it's setShouldNotSuspend() QCORO_COMPARE(connection->readAll(), QByteArray{"Hello World!"}); std::lock_guard lock{mutex}; QCORO_VERIFY(ok); clientThread.join(); } private Q_SLOTS: addTest(WaitForNewConnectionTriggers) addTest(DoesntCoAwaitPendingConnection) }; QTEST_GUILESS_MAIN(QCoroTcpServerTest) #include "qcorotcpserver.moc" qcoro-0.4.0/tests/qdbuspendingcall.cpp000066400000000000000000000060031416564147600200400ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testdbusserver.h" #include "testobject.h" #include "qcorodbuspendingcall.h" #include #include #include #include class QCoroDBusPendingCallTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); QCORO_VERIFY(iface.isValid()); const QDBusReply reply = co_await iface.asyncCall(QStringLiteral("foo")); QCORO_VERIFY(reply.isValid()); } QCoro::Task<> testReturnsResult_coro(QCoro::TestContext) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); QCORO_VERIFY(iface.isValid()); const QDBusReply reply = co_await iface.asyncCall(QStringLiteral("ping"), QStringLiteral("Hello there!")); QCORO_VERIFY(reply.isValid()); QCORO_COMPARE(reply.value(), QStringLiteral("Hello there!")); } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); QCORO_VERIFY(iface.isValid()); const QDBusReply reply = co_await iface.asyncCall(QStringLiteral("blockFor"), 1); QCORO_VERIFY(reply.isValid()); QCORO_VERIFY(eventLoopResponsive); } QCoro::Task<> testDoesntCoAwaitFinishedCall_coro(QCoro::TestContext test) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); QCORO_VERIFY(iface.isValid()); auto call = iface.asyncCall(QStringLiteral("foo")); QDBusReply reply = co_await call; QCORO_VERIFY(reply.isValid()); test.setShouldNotSuspend(); reply = co_await call; QCORO_VERIFY(reply.isValid()); } private Q_SLOTS: void initTestCase() { for (int i = 0; i < 10; ++i) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); if (iface.isValid()) { return; } QTest::qWait(100); } QFAIL("Failed to obtain a valid dbus interface"); } void cleanupTestCase() { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); iface.call(QStringLiteral("quit")); } addTest(Triggers) addTest(ReturnsResult) addTest(DoesntBlockEventLoop) addTest(DoesntCoAwaitFinishedCall) }; DBUS_TEST_MAIN(QCoroDBusPendingCallTest) #include "qdbuspendingcall.moc" qcoro-0.4.0/tests/qdbuspendingreply.cpp000066400000000000000000000107031416564147600202620ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "qcorodbustestinterface.h" #include "testdbusserver.h" #include "testobject.h" #include "qcorodbuspendingreply.h" #include #include #include #include #include #include class QCoroDBusPendingCallTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const auto resp = co_await iface.foo(); QCORO_VERIFY(resp.isFinished()); } QCoro::Task<> testQCoroWrapperTriggers_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const auto resp = co_await qCoro(iface.foo()).waitForFinished(); QCORO_VERIFY(resp.isFinished()); } QCoro::Task<> testReturnsResult_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const QString reply = co_await iface.ping("Hello there!"); QCORO_COMPARE(reply, QStringLiteral("Hello there!")); } QCoro::Task<> testReturnsBlockingResult_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const QString reply = co_await iface.blockAndReturn(1); QCORO_COMPARE(reply, QStringLiteral("Slept for 1 seconds")); } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); const auto result = co_await iface.blockFor(1); QCORO_VERIFY(result.isFinished()); QCORO_VERIFY(eventLoopResponsive); } QCoro::Task<> testDoesntCoAwaitFinishedCall_coro(QCoro::TestContext test) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); auto call = iface.foo(); QDBusReply reply = co_await call; QCORO_VERIFY(reply.isValid()); test.setShouldNotSuspend(); reply = co_await call; QCORO_VERIFY(reply.isValid()); } QCoro::Task<> testHandlesMultipleArguments_coro(QCoro::TestContext) { cz::dvratil::qcorodbustest iface(DBusServer::serviceName, DBusServer::objectPath, QDBusConnection::sessionBus()); QCORO_VERIFY(iface.isValid()); QDBusPendingReply reply = iface.asyncCall("blockAndReturnMultipleArguments", 1); co_await reply; QCORO_VERIFY(reply.isFinished()); QCORO_COMPARE(reply.argumentAt<0>(), QStringLiteral("Hello World!")); QCORO_COMPARE(reply.argumentAt<1>(), true); } private Q_SLOTS: void initTestCase() { for (int i = 0; i < 10; ++i) { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); if (iface.isValid()) { return; } QTest::qWait(100); } QFAIL("Failed to obtain a valid dbus interface"); } void cleanupTestCase() { QDBusInterface iface(DBusServer::serviceName, DBusServer::objectPath, DBusServer::interfaceName); iface.call(QStringLiteral("quit")); } addTest(Triggers) addTest(QCoroWrapperTriggers) addTest(ReturnsResult) addTest(ReturnsBlockingResult) addTest(DoesntBlockEventLoop) addTest(DoesntCoAwaitFinishedCall) addTest(HandlesMultipleArguments) }; DBUS_TEST_MAIN(QCoroDBusPendingCallTest) #include "qdbuspendingreply.moc" qcoro-0.4.0/tests/qfuture.cpp000066400000000000000000000040311416564147600162130ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcorofuture.h" #include #include class QCoroFutureTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { auto future = QtConcurrent::run([] { std::this_thread::sleep_for(100ms); }); co_await future; QCORO_VERIFY(future.isFinished()); } QCoro::Task<> testQCoroWrapperTriggers_coro(QCoro::TestContext) { auto future = QtConcurrent::run([] { std::this_thread::sleep_for(100ms); }); co_await qCoro(future).waitForFinished(); QCORO_VERIFY(future.isFinished()); } QCoro::Task<> testReturnsResult_coro(QCoro::TestContext) { const QString result = co_await QtConcurrent::run([] { std::this_thread::sleep_for(100ms); return QStringLiteral("42"); }); QCORO_COMPARE(result, QStringLiteral("42")); } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; co_await QtConcurrent::run([] { std::this_thread::sleep_for(500ms); }); QCORO_VERIFY(eventLoopResponsive); } QCoro::Task<> testDoesntCoAwaitFinishedFuture_coro(QCoro::TestContext test) { auto future = QtConcurrent::run([] { std::this_thread::sleep_for(100ms); }); co_await future; QCORO_VERIFY(future.isFinished()); test.setShouldNotSuspend(); co_await future; } QCoro::Task<> testDoesntCoAwaitCanceledFuture_coro(QCoro::TestContext test) { test.setShouldNotSuspend(); QFuture future; co_await future; } private Q_SLOTS: addTest(Triggers) addTest(ReturnsResult) addTest(DoesntBlockEventLoop) addTest(DoesntCoAwaitFinishedFuture) addTest(DoesntCoAwaitCanceledFuture) addTest(QCoroWrapperTriggers) }; QTEST_GUILESS_MAIN(QCoroFutureTest) #include "qfuture.moc" qcoro-0.4.0/tests/qtimer.cpp000066400000000000000000000030161416564147600160230ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "qcorotimer.h" class QCoroTimerTest : public QCoro::TestObject { Q_OBJECT private: QCoro::Task<> testTriggers_coro(QCoro::TestContext) { QTimer timer; timer.setInterval(100ms); timer.start(); co_await timer; } QCoro::Task<> testQCoroWrapperTriggers_coro(QCoro::TestContext) { QTimer timer; timer.setInterval(100ms); timer.start(); co_await qCoro(timer).waitForTimeout(); } QCoro::Task<> testDoesntBlockEventLoop_coro(QCoro::TestContext) { QCoro::EventLoopChecker eventLoopResponsive; QTimer timer; timer.setInterval(500ms); timer.start(); co_await timer; QCORO_VERIFY(eventLoopResponsive); } QCoro::Task<> testDoesntCoAwaitInactiveTimer_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); QTimer timer; timer.setInterval(1s); // Don't start the timer! co_await timer; } QCoro::Task<> testDoesntCoAwaitNullTimer_coro(QCoro::TestContext ctx) { ctx.setShouldNotSuspend(); QTimer *timer = nullptr; co_await timer; } private Q_SLOTS: addTest(Triggers) addTest(QCoroWrapperTriggers) addTest(DoesntBlockEventLoop) addTest(DoesntCoAwaitInactiveTimer) addTest(DoesntCoAwaitNullTimer) }; QTEST_GUILESS_MAIN(QCoroTimerTest) #include "qtimer.moc" qcoro-0.4.0/tests/task.cpp000066400000000000000000000131601416564147600154650ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" #include "task.h" #include "qcorotimer.h" #include #include #include #include #include using namespace std::chrono_literals; namespace { QCoro::Task<> timer(std::chrono::milliseconds timeout) { QTimer timer; timer.setSingleShot(true); timer.start(timeout); co_await timer; } } // namespace class QCoroTaskTest : public QCoro::TestObject { Q_OBJECT private: template void ignoreCoroutineResult(QEventLoop &el, Coro &&coro) { QTimer::singleShot(5s, &el, [&el]() mutable { el.exit(1); }); coro(); const int timeout = el.exec(); QCOMPARE(timeout, 0); } QCoro::Task<> testSimpleCoroutine_coro(QCoro::TestContext) { co_await timer(100ms); } QCoro::Task<> testCoroutineValue_coro(QCoro::TestContext) { const auto coro = [](const QString &result) -> QCoro::Task { co_await timer(100ms); co_return result; }; const auto value = QStringLiteral("Done!"); const auto result = co_await coro(value); QCORO_COMPARE(result, value); } QCoro::Task<> testCoroutineMoveValue_coro(QCoro::TestContext) { const auto coro = [](const QString &result) -> QCoro::Task> { co_await timer(100ms); co_return std::make_unique(result); }; const auto value = QStringLiteral("Done ptr!"); const auto result = co_await coro(value); QCORO_COMPARE(*result.get(), value); } QCoro::Task<> testSyncCoroutine_coro(QCoro::TestContext) { const auto coro = []() -> QCoro::Task { co_return 42; }; const auto result = co_await coro(); QCORO_COMPARE(result, 42); } QCoro::Task<> testCoroutineWithException_coro(QCoro::TestContext) { const auto coro = []() -> QCoro::Task { co_await timer(100ms); throw std::runtime_error("Invalid result"); co_return 42; }; try { const auto result = co_await coro(); QCORO_FAIL("Exception was not propagated."); Q_UNUSED(result); } catch (const std::runtime_error &) { // OK } catch (...) { QCORO_FAIL("Exception type was not propagated, or other exception was thrown."); } } QCoro::Task<> testVoidCoroutineWithException_coro(QCoro::TestContext) { const auto coro = []() -> QCoro::Task<> { co_await timer(100ms); throw std::runtime_error("Error"); }; try { co_await coro(); QCORO_FAIL("Exception was not propagated."); } catch (const std::runtime_error &) { // OK } catch (...) { QCORO_FAIL("Exception type was not propagated, or other exception was thrown."); } } QCoro::Task<> testCoroutineFrameDestroyed_coro(QCoro::TestContext) { bool destroyed = false; const auto coro = [&destroyed]() -> QCoro::Task<> { const auto guard = qScopeGuard([&destroyed]() mutable { destroyed = true; }); QCORO_VERIFY(!destroyed); co_await timer(100ms); QCORO_VERIFY(!destroyed); }; co_await coro(); QCORO_VERIFY(destroyed); } private Q_SLOTS: addTest(SimpleCoroutine) addTest(CoroutineValue) addTest(CoroutineMoveValue) // FIXME: Crashes with ASAN due to use-after-free //addTest(SyncCoroutine) addTest(CoroutineWithException) addTest(VoidCoroutineWithException) addTest(CoroutineFrameDestroyed) // See https://github.com/danvratil/qcoro/issues/24 void testEarlyReturn() { QEventLoop loop; const auto testReturn = [](bool immediate) -> QCoro::Task { if (immediate) { co_return true; } else { co_await timer(100ms); co_return true; } }; bool immediateResult = false; bool delayedResult = false; const auto testImmediate = [&]() -> QCoro::Task<> { immediateResult = co_await testReturn(true); }; const auto testDelayed = [&]() -> QCoro::Task<> { delayedResult = co_await testReturn(false); loop.quit(); }; QMetaObject::invokeMethod( &loop, [&]() { testImmediate(); }, Qt::QueuedConnection); QMetaObject::invokeMethod( &loop, [&]() { testDelayed(); }, Qt::QueuedConnection); loop.exec(); QVERIFY(immediateResult); QVERIFY(delayedResult); } // TODO: Test timeout void testWaitFor() { QCoro::waitFor(timer(500ms)); } // TODO: Test timeout void testWaitForWithValue() { const auto result = QCoro::waitFor([]() -> QCoro::Task { co_await timer(100ms); co_return 42; }()); QCOMPARE(result, 42); } void testIgnoredVoidTaskResult() { QEventLoop el; ignoreCoroutineResult(el, [&el]() -> QCoro::Task<> { co_await timer(300ms); el.quit(); }); } void testIgnoredValueTaskResult() { QEventLoop el; ignoreCoroutineResult(el, [&el]() -> QCoro::Task { co_await timer(300ms); el.quit(); co_return QStringLiteral("Result"); }); } }; QTEST_GUILESS_MAIN(QCoroTaskTest) #include "task.moc" qcoro-0.4.0/tests/testconstraints.cpp000066400000000000000000000016641416564147600200000ustar00rootroot00000000000000#include #include #include "qcoro/coroutine.h" #include "qcoro/task.h" namespace helper { template struct is_awaitable { static constexpr bool value = true; }; template static constexpr bool is_awaitable_v = is_awaitable::value; } struct TestAwaitable { bool await_ready() const { return true; } void await_suspend(std::coroutine_handle<>); void await_resume() const; }; class TestConstraints : public QObject { Q_OBJECT private Q_SLOTS: void testAwaitableConcept() { static_assert(helper::is_awaitable_v>, "Awaitable concept doesn't accept Task, although it should."); static_assert(helper::is_awaitable_v, "Awaitable concept doesn't accept TestAwaitable, although it should."); } }; QTEST_GUILESS_MAIN(TestConstraints) #include "testconstraints.moc" qcoro-0.4.0/tests/testdbusserver.cpp000066400000000000000000000041351416564147600176110ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testdbusserver.h" #include #include #include #include #include #include #include using namespace std::chrono_literals; DBusServer::DBusServer() { QTimer::singleShot(0, this, &DBusServer::run); // Self-terminate if there's no interaction from a client in 30 seconds. // Prevents leaking the test server in case the test crashes. mSuicideTimer.setInterval(30s); mSuicideTimer.setSingleShot(true); connect(&mSuicideTimer, &QTimer::timeout, this, []() { std::cerr << "No call in 30 seconds, terminating!" << std::endl; qApp->exit(1); }); } void DBusServer::run() { auto conn = QDBusConnection::sessionBus(); if (!conn.registerService(serviceName)) { qWarning() << "Failed to register service to DBus:" << conn.lastError().message(); } if (!conn.registerObject(objectPath, interfaceName, this, QDBusConnection::ExportAllSlots)) { qWarning() << "Failed to register object to DBus" << conn.lastError().message(); } mSuicideTimer.start(); } void DBusServer::foo() { mSuicideTimer.start(); } void DBusServer::blockFor(int seconds) { std::this_thread::sleep_for(std::chrono::seconds(seconds)); mSuicideTimer.start(); } QString DBusServer::blockAndReturn(int seconds) { std::this_thread::sleep_for(std::chrono::seconds(seconds)); mSuicideTimer.start(); return QStringLiteral("Slept for %1 seconds").arg(seconds); } QString DBusServer::blockAndReturnMultipleArguments(int seconds, bool &out) { std::this_thread::sleep_for(std::chrono::seconds{seconds}); mSuicideTimer.start(); out = true; return QStringLiteral("Hello World!"); } QString DBusServer::ping(const QString &ping) { mSuicideTimer.start(); return ping; } void DBusServer::quit() { mSuicideTimer.stop(); qApp->quit(); } int main(int argc, char **argv) { QCoreApplication app(argc, argv); DBusServer server; return app.exec(); } qcoro-0.4.0/tests/testdbusserver.h000066400000000000000000000101231416564147600172500ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include #include #include #include #include #include class DBusServer : public QObject { Q_OBJECT public: inline static const QString serviceName = QStringLiteral("cz.dvratil.qcorodbustest"); inline static const QString interfaceName = QStringLiteral("cz.dvratil.qcorodbustest"); inline static const QString objectPath = QStringLiteral("/"); explicit DBusServer(); void run(); public Q_SLOTS: void foo(); QString ping(const QString &ping); void blockFor(int seconds); QString blockAndReturn(int seconds); QString blockAndReturnMultipleArguments(int seconds, bool &out); void quit(); private: QTimer mSuicideTimer; }; // We must run the DBus server into its own process due to QTBUG-92107 (asyncCall blocks if the // remote service is registered in the same process (even if it lives in a different thread) #define DBUS_TEST_MAIN(TestClass) \ bool startDBusServer(QProcess &process) { \ process.start(QStringLiteral(TESTDBUSSERVER_EXECUTABLE), QStringList{}); \ if (!process.waitForStarted()) { \ std::cerr << "Failed to start testdbusserver" << std::endl; \ return false; \ } \ return true; \ } \ \ bool stopDBusServer(QProcess &process) { \ process.waitForFinished(); \ if (process.exitCode() != 0) { \ std::cerr << "testdbuserver terminated with exit code " << process.exitCode() \ << std::endl; \ return false; \ } \ return true; \ } \ \ int main(int argc, char **argv) { \ QCoreApplication app(argc, argv); \ QProcess dbusServer; \ if (!startDBusServer(dbusServer)) \ return 1; \ TestClass testClass; \ QTEST_SET_MAIN_SOURCE_PATH \ const int result = QTest::qExec(&testClass, argc, argv); \ if (!stopDBusServer(dbusServer)) \ return 1; \ return result; \ } qcoro-0.4.0/tests/testhttpserver.cpp000066400000000000000000000036571416564147600176430ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testhttpserver.h" #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(std::chrono::seconds) using namespace std::chrono_literals; class TestHttpServerTest : public QObject { Q_OBJECT private Q_SLOTS: void init() { mServer.start(QHostAddress::LocalHost); } void cleanup() { mServer.stop(); } void testGet_data() { QTest::addColumn("url"); QTest::addColumn("expectedData"); QTest::addColumn("timeout"); QTest::newRow("/") << QStringLiteral("http://localhost:%1/") << QByteArray{"abcdef"} << 5s; QTest::newRow("/block") << QStringLiteral("http://localhost:%1/block") << QByteArray{"abcdef"} << 5s; QTest::newRow("/stream") << QStringLiteral("http://localhost:%1/stream") << QByteArray{"Hola 0\nHola 1\nHola 2\nHola 3\nHola 4\nHola " "5\nHola 6\nHola 7\nHola 8\nHola 9\n"} << 15s; } void testGet() { QFETCH(QString, url); QFETCH(QByteArray, expectedData); QFETCH(std::chrono::seconds, timeout); QNetworkAccessManager nam; QEventLoop el; auto *reply = nam.get(QNetworkRequest{url.arg(mServer.port())}); connect(reply, &QNetworkReply::finished, &el, &QEventLoop::quit); QTimer::singleShot(timeout, &el, [&el]() mutable { el.exit(1); }); QCOMPARE(el.exec(), 0); QCOMPARE(reply->readAll(), expectedData); } private: TestHttpServer mServer; }; QTEST_GUILESS_MAIN(TestHttpServerTest) #include "testhttpserver.moc" qcoro-0.4.0/tests/testhttpserver.h000066400000000000000000000106201416564147600172740ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include #include #include #include #include class QTcpServer; class QLocalServer; template class Thread : public QThread { public: explicit Thread(Func &&f) : mFunc(std::forward(f)) {} ~Thread() = default; void run() { mFunc(); } private: Func mFunc; }; template Thread(Func &&) -> Thread; template class TestHttpServer { public: template void start(const T &name) { mStop = false; mExpectTimeout = false; // Can't use QThread::create, it's only available when Qt is built with C++17, // which some distros don't have :( mThread.reset(new Thread([this, name]() { run(name); })); mThread->start(); std::unique_lock lock(mReadyMutex); mServerReady.wait(lock, [this]() { return mPort != 0; }); } void stop() { mStop = true; if (mThread->isRunning()) { mThread->wait(); } mThread.reset(); mPort = 0; } uint16_t port() const { return mPort; } void setExpectTimeout(bool expectTimeout) { mExpectTimeout = expectTimeout; } private: template void run(const T &name) { using namespace std::chrono_literals; ServerType server{}; if (!server.listen(name)) { qDebug() << "Error listening:" << server.serverError(); return; } assert(server.isListening()); { std::scoped_lock lock(mReadyMutex); if constexpr (std::is_same_v) { mPort = server.serverPort(); } else { mPort = 1; } } mServerReady.notify_all(); for (int i = 0; i < 10 && !mStop; ++i) { if (server.waitForNewConnection(1000)) { break; } } if (!server.hasPendingConnections()) { if (!mExpectTimeout) { QFAIL("No incoming connection within timeout!"); } mPort = 0; return; } auto *conn = server.nextPendingConnection(); if (conn->waitForReadyRead(1000)) { const auto request = conn->readLine(); qDebug() << request; if (request == "GET /stream HTTP/1.1\r\n") { QStringList lines; for (int i = 0; i < 10; ++i) { lines.push_back(QStringLiteral("Hola %1\n").arg(i)); } const auto len = std::accumulate(lines.cbegin(), lines.cend(), 0, [](int l, const QString &s) { return l + s.size(); }); conn->write("HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Content-Length: " + QByteArray::number(len) + "\r\n" "\r\n"); conn->flush(); for (const auto &line : lines) { conn->write(line.toUtf8()); conn->flush(); std::this_thread::sleep_for(100ms); } } else { if (request == "GET /block HTTP/1.1\r\n") { std::this_thread::sleep_for(500ms); } conn->write("HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "\r\n" "abcdef"); } conn->flush(); conn->close(); } else if (!mStop) { if (conn->state() == std::remove_cvref_t::ConnectedState) { if (!mExpectTimeout) { QFAIL("No request within 1 second"); } } else { qDebug() << "Client disconnected without sending request"; } } delete conn; mPort = 0; } std::unique_ptr mThread; std::mutex mReadyMutex; std::condition_variable mServerReady; uint16_t mPort = 0; std::atomic_bool mStop = false; std::atomic_bool mExpectTimeout = false; }; qcoro-0.4.0/tests/testobject.cpp000066400000000000000000000014441416564147600166730ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #include "testobject.h" using namespace QCoro; TestContext::TestContext(QEventLoop &el) : mEventLoop(&el) { mEventLoop->setProperty("testFinished", false); mEventLoop->setProperty("shouldNotSuspend", false); } TestContext::TestContext(TestContext &&other) noexcept { std::swap(mEventLoop, other.mEventLoop); } TestContext::~TestContext() { if (mEventLoop) { mEventLoop->setProperty("testFinished", true); mEventLoop->quit(); } } TestContext &TestContext::operator=(TestContext &&other) noexcept { std::swap(mEventLoop, other.mEventLoop); return *this; } void TestContext::setShouldNotSuspend() { mEventLoop->setProperty("shouldNotSuspend", true); } qcoro-0.4.0/tests/testobject.h000066400000000000000000000070401416564147600163360ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2021 Daniel Vrátil // // SPDX-License-Identifier: MIT #pragma once #include #include #include #include #include #include "qcoro/task.h" #include using namespace std::chrono_literals; namespace QCoro { class TestContext { public: TestContext(QEventLoop &el); TestContext(TestContext &&) noexcept; TestContext(const TestContext &) = delete; ~TestContext(); TestContext &operator=(TestContext &&) noexcept; TestContext &operator=(const TestContext &) = delete; void setShouldNotSuspend(); private: QEventLoop *mEventLoop = {}; }; class EventLoopChecker : public QTimer { Q_OBJECT public: explicit EventLoopChecker(int minTicks = 10, std::chrono::milliseconds interval = 10ms) : mMinTicks{minTicks} { connect(this, &EventLoopChecker::timeout, this, &EventLoopChecker::timeoutCheck); setInterval(interval); start(); } operator bool() const { return mTick > mMinTicks; } private Q_SLOTS: void timeoutCheck() { ++mTick; } private: int mTick = 0; int mMinTicks = 10; }; template class TestObject : public QObject { protected: void coroWrapper(QCoro::Task<> (TestClass::*testFunction)(TestContext)) { QEventLoop el; QTimer::singleShot(5s, &el, [&el]() mutable { el.exit(1); }); (static_cast(this)->*testFunction)(el); bool testFinished = el.property("testFinished").toBool(); const bool shouldNotSuspend = el.property("shouldNotSuspend").toBool(); if (testFinished) { QVERIFY(shouldNotSuspend); } else { QVERIFY(!shouldNotSuspend); const auto result = el.exec(); QVERIFY2(result == 0, "Test function has timed out"); testFinished = el.property("testFinished").toBool(); QVERIFY(testFinished); } } }; #define addTest(name) \ void test##name() { \ coroWrapper(&std::remove_cvref_t::test##name##_coro); \ } } // namespace QCoro #define QCORO_VERIFY(statement) \ do { \ if (!QTest::qVerify(static_cast(statement), #statement, "", __FILE__, __LINE__)) \ co_return; \ } while (false) #define QCORO_COMPARE(actual, expected) \ do { \ if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ co_return; \ } while (false) #define QCORO_FAIL(message) \ do { \ QTest::qFail(static_cast(message), __FILE__, __LINE__); \ co_return; \ } while (false)