pax_global_header 0000666 0000000 0000000 00000000064 14165641476 0014530 g ustar 00root root 0000000 0000000 52 comment=3f74afd37d463ba84d337f86f5c324c829ec0744
qcoro-0.4.0/ 0000775 0000000 0000000 00000000000 14165641476 0012654 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/.clang-format 0000664 0000000 0000000 00000010225 14165641476 0015227 0 ustar 00root root 0000000 0000000 ---
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-revs 0000664 0000000 0000000 00000000107 14165641476 0016752 0 ustar 00root root 0000000 0000000 # Initial run of clang-format
0c01ca40fd8bd337ecae3ea21be1e5a97ba40e4b
qcoro-0.4.0/.github/ 0000775 0000000 0000000 00000000000 14165641476 0014214 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/.github/actions/ 0000775 0000000 0000000 00000000000 14165641476 0015654 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/.github/actions/install-qt/ 0000775 0000000 0000000 00000000000 14165641476 0017744 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/.github/actions/install-qt/action.yml 0000664 0000000 0000000 00000003105 14165641476 0021743 0 ustar 00root root 0000000 0000000 name: '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.ps1 0000664 0000000 0000000 00000001057 14165641476 0022775 0 ustar 00root root 0000000 0000000 [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/ 0000775 0000000 0000000 00000000000 14165641476 0016251 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/.github/workflows/build.yml 0000664 0000000 0000000 00000007501 14165641476 0020076 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000002125 14165641476 0022756 0 ustar 00root root 0000000 0000000 name: 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.yml 0000664 0000000 0000000 00000003575 14165641476 0021216 0 ustar 00root root 0000000 0000000 name: 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/.gitignore 0000664 0000000 0000000 00000000171 14165641476 0014643 0 ustar 00root root 0000000 0000000 build
build-*
.*.swp
.ccls-cache
compile_commands.json
/.vs
/CMakeSettings.json
# Python (from mkdocs)
__pycache__
venv
qcoro-0.4.0/.reuse/ 0000775 0000000 0000000 00000000000 14165641476 0014055 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/.reuse/dep5 0000664 0000000 0000000 00000000433 14165641476 0014635 0 ustar 00root root 0000000 0000000 Format: 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.txt 0000664 0000000 0000000 00000012226 14165641476 0015417 0 ustar 00root root 0000000 0000000 cmake_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/ 0000775 0000000 0000000 00000000000 14165641476 0014061 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/LICENSES/MIT.txt 0000664 0000000 0000000 00000002101 14165641476 0015245 0 ustar 00root root 0000000 0000000 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/QCoroConfig.cmake.in 0000664 0000000 0000000 00000003161 14165641476 0016435 0 ustar 00root root 0000000 0000000 if (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.md 0000664 0000000 0000000 00000007625 14165641476 0014145 0 ustar 00root root 0000000 0000000 [](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/ 0000775 0000000 0000000 00000000000 14165641476 0013734 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/cmake/AddQCoroLibrary.cmake 0000664 0000000 0000000 00000010530 14165641476 0017716 0 ustar 00root root 0000000 0000000 # 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.cmake 0000664 0000000 0000000 00000002264 14165641476 0017770 0 ustar 00root root 0000000 0000000 function(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.in 0000664 0000000 0000000 00000000045 14165641476 0015527 0 ustar 00root root 0000000 0000000 #cmakedefine QCORO_QT_HAS_COMPAT_ABI
qcoro-0.4.0/docs/ 0000775 0000000 0000000 00000000000 14165641476 0013604 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/about/ 0000775 0000000 0000000 00000000000 14165641476 0014716 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/about/license.md 0000664 0000000 0000000 00000002176 14165641476 0016670 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000003277 14165641476 0017617 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000013575 14165641476 0016070 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14165641476 0015776 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/coroutines/coawait.md 0000664 0000000 0000000 00000004432 14165641476 0017752 0 ustar 00root root 0000000 0000000 # `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.md 0000664 0000000 0000000 00000002570 14165641476 0021023 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000001442 14165641476 0017732 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14165641476 0015422 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/examples/qdbus.cpp 0000664 0000000 0000000 00000002675 14165641476 0017256 0 ustar 00root root 0000000 0000000 #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.cpp 0000664 0000000 0000000 00000001042 14165641476 0017616 0 ustar 00root root 0000000 0000000 #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.cpp 0000664 0000000 0000000 00000001344 14165641476 0021056 0 ustar 00root root 0000000 0000000 #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.cpp 0000664 0000000 0000000 00000000626 14165641476 0017771 0 ustar 00root root 0000000 0000000 #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.cpp 0000664 0000000 0000000 00000000527 14165641476 0020330 0 ustar 00root root 0000000 0000000 #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.cpp 0000664 0000000 0000000 00000000712 14165641476 0020306 0 ustar 00root root 0000000 0000000 #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.md 0000664 0000000 0000000 00000010677 14165641476 0015250 0 ustar 00root root 0000000 0000000 # 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.py 0000664 0000000 0000000 00000001447 14165641476 0015450 0 ustar 00root root 0000000 0000000 def 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/ 0000775 0000000 0000000 00000000000 14165641476 0015606 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/overrides/.gitkeep 0000664 0000000 0000000 00000000000 14165641476 0017225 0 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/reference/ 0000775 0000000 0000000 00000000000 14165641476 0015542 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/reference/core/ 0000775 0000000 0000000 00000000000 14165641476 0016472 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/reference/core/index.md 0000664 0000000 0000000 00000000425 14165641476 0020124 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000002012 14165641476 0020502 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000006374 14165641476 0020776 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000005202 14165641476 0020652 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000002202 14165641476 0020311 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14165641476 0016504 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/reference/coro/coro.md 0000664 0000000 0000000 00000005277 14165641476 0020003 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000001401 14165641476 0020131 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000005217 14165641476 0017775 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14165641476 0016477 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/reference/dbus/index.md 0000664 0000000 0000000 00000000426 14165641476 0020132 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000005055 14165641476 0022345 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000004422 14165641476 0022562 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14165641476 0017233 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/reference/network/index.md 0000664 0000000 0000000 00000000454 14165641476 0020667 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000007507 14165641476 0022763 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000007161 14165641476 0022246 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000002343 14165641476 0022505 0 ustar 00root root 0000000 0000000 # 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.md 0000664 0000000 0000000 00000002362 14165641476 0021756 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14165641476 0016160 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/docs/stylesheets/doctable.css 0000664 0000000 0000000 00000000270 14165641476 0020446 0 ustar 00root root 0000000 0000000 .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/ 0000775 0000000 0000000 00000000000 14165641476 0014472 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/CMakeLists.txt 0000664 0000000 0000000 00000000442 14165641476 0017232 0 ustar 00root root 0000000 0000000 add_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/ 0000775 0000000 0000000 00000000000 14165641476 0015736 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/basics/CMakeLists.txt 0000664 0000000 0000000 00000000440 14165641476 0020474 0 ustar 00root root 0000000 0000000 add_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.cpp 0000664 0000000 0000000 00000016324 14165641476 0022174 0 ustar 00root root 0000000 0000000 // 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.cpp 0000664 0000000 0000000 00000013231 14165641476 0022025 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14165641476 0016065 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/chained/CMakeLists.txt 0000664 0000000 0000000 00000000222 14165641476 0020621 0 ustar 00root root 0000000 0000000 add_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.cpp 0000664 0000000 0000000 00000003153 14165641476 0017517 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14165641476 0015427 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/dbus/CMakeLists.txt 0000664 0000000 0000000 00000000130 14165641476 0020161 0 ustar 00root root 0000000 0000000 add_subdirectory(common)
add_subdirectory(regular-blocking)
add_subdirectory(coroutine)
qcoro-0.4.0/examples/dbus/common/ 0000775 0000000 0000000 00000000000 14165641476 0016717 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/dbus/common/CMakeLists.txt 0000664 0000000 0000000 00000001465 14165641476 0021465 0 ustar 00root root 0000000 0000000
# 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.cpp 0000664 0000000 0000000 00000003065 14165641476 0021613 0 ustar 00root root 0000000 0000000 // 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.h 0000664 0000000 0000000 00000000762 14165641476 0021261 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14165641476 0017436 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/dbus/coroutine/CMakeLists.txt 0000664 0000000 0000000 00000000406 14165641476 0022176 0 ustar 00root root 0000000 0000000 set(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.cpp 0000664 0000000 0000000 00000003035 14165641476 0021067 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14165641476 0020656 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/dbus/regular-blocking/CMakeLists.txt 0000664 0000000 0000000 00000000333 14165641476 0023415 0 ustar 00root root 0000000 0000000 set(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.cpp 0000664 0000000 0000000 00000002626 14165641476 0022314 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14165641476 0016004 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/future/CMakeLists.txt 0000664 0000000 0000000 00000000266 14165641476 0020550 0 ustar 00root root 0000000 0000000 add_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.cpp 0000664 0000000 0000000 00000002133 14165641476 0017433 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14165641476 0016261 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/iodevice/CMakeLists.txt 0000664 0000000 0000000 00000000333 14165641476 0021020 0 ustar 00root root 0000000 0000000 add_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.cpp 0000664 0000000 0000000 00000003142 14165641476 0017711 0 ustar 00root root 0000000 0000000 #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/ 0000775 0000000 0000000 00000000000 14165641476 0016163 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/network/CMakeLists.txt 0000664 0000000 0000000 00000000333 14165641476 0020722 0 ustar 00root root 0000000 0000000 add_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.cpp 0000664 0000000 0000000 00000004213 14165641476 0017613 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14165641476 0015612 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/examples/timer/CMakeLists.txt 0000664 0000000 0000000 00000000216 14165641476 0020351 0 ustar 00root root 0000000 0000000 add_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.cpp 0000664 0000000 0000000 00000001745 14165641476 0017251 0 ustar 00root root 0000000 0000000 // 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.yml 0000664 0000000 0000000 00000004314 14165641476 0014661 0 ustar 00root root 0000000 0000000 site_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/ 0000775 0000000 0000000 00000000000 14165641476 0013777 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/qcoro/CMakeLists.txt 0000664 0000000 0000000 00000001303 14165641476 0016534 0 ustar 00root root 0000000 0000000 # 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.in 0000664 0000000 0000000 00000001030 14165641476 0020374 0 ustar 00root root 0000000 0000000 @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.cmake 0000664 0000000 0000000 00000001047 14165641476 0017173 0 ustar 00root root 0000000 0000000 macro(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.h 0000664 0000000 0000000 00000001250 14165641476 0016303 0 ustar 00root root 0000000 0000000 // 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/ 0000775 0000000 0000000 00000000000 14165641476 0014727 5 ustar 00root root 0000000 0000000 qcoro-0.4.0/qcoro/core/CMakeLists.txt 0000664 0000000 0000000 00000000650 14165641476 0017470 0 ustar 00root root 0000000 0000000 # 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.in 0000664 0000000 0000000 00000000776 14165641476 0021332 0 ustar 00root root 0000000 0000000 @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.h 0000664 0000000 0000000 00000000445 14165641476 0017077 0 ustar 00root root 0000000 0000000 // 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.h 0000664 0000000 0000000 00000007704 14165641476 0017466 0 ustar 00root root 0000000 0000000 // 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.cpp 0000664 0000000 0000000 00000007121 14165641476 0020267 0 ustar 00root root 0000000 0000000 // 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.h 0000664 0000000 0000000 00000012676 14165641476 0017747 0 ustar 00root root 0000000 0000000 // 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.cpp 0000664 0000000 0000000 00000006625 14165641476 0020166 0 ustar 00root root 0000000 0000000 // 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.h 0000664 0000000 0000000 00000010250 14165641476 0017620 0 ustar 00root root 0000000 0000000 // 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.h 0000664 0000000 0000000 00000005134 14165641476 0017424 0 ustar 00root root 0000000 0000000 // 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.cpp 0000664 0000000 0000000 00000002231 14165641476 0017615 0 ustar 00root root 0000000 0000000 // 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.h 0000664 0000000 0000000 00000003043 14165641476 0017264 0 ustar 00root root 0000000 0000000 // SPDX-FileCopyrightText: 2021 Daniel Vrátil
//
// SPDX-License-Identifier: MIT
#pragma once
#include "task.h"
#include