├── .github └── workflows │ └── build-test.yml ├── .gitignore ├── CMakeLists.txt ├── CMakePresets.json ├── build.py ├── cmake ├── Config.cmake.in ├── macoroBuildOptions.cmake ├── macoroBuildOptions.cmake.in ├── macoroConfig.cmake ├── macoroConfigVersion.cmake ├── macoroConfigVersion.cmake.in ├── macoroFindBuildDir.cmake ├── macoroFindDeps.cmake ├── macoroPreamble.cmake └── testProject │ ├── CMakeLists.txt │ └── src.cpp ├── frontend ├── CMakeLists.txt └── main.cpp ├── macoro ├── CMakeLists.txt ├── async_scope.h ├── awaiter.h ├── barrier.h ├── blocking.h ├── channel.h ├── channel_spsc.h ├── config.h.in ├── coro_frame.h ├── coroutine_handle.h ├── detail │ ├── manual_lifetime.h │ ├── operation_cancelled.h │ ├── scoped_task_promise.h │ ├── stop_callback.h │ ├── stop_source.cpp │ ├── stop_source.h │ ├── stop_state.cpp │ ├── stop_state.h │ ├── stop_token.cpp │ ├── stop_token.h │ ├── when_all_awaitable.h │ ├── when_all_counter.h │ ├── when_all_task.h │ ├── win32.cpp │ ├── win32.h │ └── win32_overlapped_operation.h ├── error_code.h ├── inline_scheduler.h ├── macros.h ├── macros_undef.h ├── manual_reset_event.h ├── optional.h ├── result.h ├── sequence_barrier.h ├── sequence_mpsc.h ├── sequence_range.h ├── sequence_spsc.h ├── sequence_traits.h ├── start_on.h ├── stop.h ├── sync_wait.h ├── take_until.h ├── task.h ├── thread_pool.cpp ├── thread_pool.h ├── timeout.h ├── todo.txt ├── trace.h ├── transfer_to.h ├── type_traits.h ├── variant.h ├── when_all.h ├── when_all_scope.h └── wrap.h ├── readme.md ├── tests ├── CLP.cpp ├── CLP.h ├── CMakeLists.txt ├── async_scope_tests.cpp ├── async_scope_tests.h ├── await_lifetime_tests.cpp ├── await_lifetime_tests.h ├── channel_mpsc_tests.cpp ├── channel_mpsc_tests.h ├── channel_spsc_tests.cpp ├── channel_spsc_tests.h ├── eager_task_tests.cpp ├── eager_task_tests.h ├── result_tests.cpp ├── result_tests.h ├── sequence_tests.cpp ├── sequence_tests.h ├── take_until_tests.cpp ├── take_until_tests.h ├── task_tests.cpp ├── task_tests.h ├── tests.cpp ├── tests.h ├── when_all_tests.cpp └── when_all_tests.h └── thirdparty ├── fetch.cmake ├── getOptionalLite.cmake └── getVariantLite.cmake /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master, cpp20 ] 10 | pull_request: {} 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 16 | jobs: 17 | # This workflow contains a single job called "build" 18 | build-ubuntu: 19 | # The type of runner that the job will run on 20 | runs-on: ubuntu-latest 21 | 22 | # Steps represent a sequence of tasks that will be executed as part of the job 23 | steps: 24 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 25 | - uses: actions/checkout@v2 26 | with: 27 | submodules: recursive 28 | 29 | # Runs a set of commands using the runners shell 30 | - name: build library cpp204 31 | run: | 32 | python3 build.py -DMACORO_CPP_VER=20 33 | 34 | - name: unit test cpp20 35 | run: | 36 | ./out/build/linux/frontend/macoroFrontend -u 37 | 38 | - name: find source tree 39 | run: | 40 | cd cmake/testProject 41 | cmake -S . -B out/ -DCMAKE_BUILD_TYPE=Release -D CMAKE_PREFIX_PATH=../../ 42 | cmake --build out/ 43 | ./out/main 44 | rm -rf out/ 45 | cd ../.. 46 | 47 | - name: hint test 48 | run: | 49 | cd cmake/testProject 50 | cmake -S . -B out/ -D MACORO_HINT=../.. 51 | cmake --build out/ 52 | ./out/main 53 | rm -rf out/ 54 | cd ../.. 55 | 56 | - name: install prefix test 57 | run: | 58 | python3 build.py --install=~/install 59 | cd cmake/testProject 60 | cmake -S . -B out/ -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=~/install 61 | cmake --build out/ 62 | ./out/main 63 | rm -rf out/ 64 | cd ../.. 65 | 66 | 67 | - name: install test 68 | run: | 69 | python3 build.py --install --sudo 70 | cd cmake/testProject 71 | cmake -S . -B out/ -DCMAKE_BUILD_TYPE=Release 72 | cmake --build out/ 73 | ./out/main 74 | rm -rf out/ 75 | cd ../.. 76 | 77 | # This workflow contains a single job called "build" 78 | build-osx: 79 | # The type of runner that the job will run on 80 | runs-on: macos-latest 81 | 82 | # Steps represent a sequence of tasks that will be executed as part of the job 83 | steps: 84 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 85 | - uses: actions/checkout@v2 86 | with: 87 | submodules: recursive 88 | 89 | # Runs a set of commands using the runners shell 90 | - name: build library 20 91 | run: | 92 | python3 build.py -DMACORO_CPP_VER=20 93 | 94 | - name: unit test 20 95 | run: | 96 | ./out/build/osx/frontend/macoroFrontend -u 97 | 98 | - name: find source tree 99 | run: | 100 | cd cmake/testProject 101 | cmake -S . -B out/ -DCMAKE_BUILD_TYPE=Release -D CMAKE_PREFIX_PATH=../../ 102 | cmake --build out/ 103 | ./out/main 104 | rm -rf out/ 105 | cd ../.. 106 | 107 | - name: hint test 108 | run: | 109 | cd cmake/testProject 110 | cmake -S . -B out/ -D MACORO_HINT=../.. 111 | cmake --build out/ 112 | ./out/main 113 | rm -rf out/ 114 | cd ../.. 115 | 116 | - name: install prefix test 117 | run: | 118 | python3 build.py --install=~/install 119 | cd cmake/testProject 120 | cmake -S . -B out/ -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=~/install 121 | cmake --build out/ 122 | ./out/main 123 | rm -rf out/ 124 | cd ../.. 125 | 126 | 127 | - name: install test 128 | run: | 129 | python3 build.py --install --sudo 130 | cd cmake/testProject 131 | cmake -S . -B out/ -DCMAKE_BUILD_TYPE=Release 132 | cmake --build out/ 133 | ./out/main 134 | rm -rf out/ 135 | cd ../.. 136 | 137 | build-windows: 138 | # The type of runner that the job will run on 139 | runs-on: windows-2022 140 | 141 | # Steps represent a sequence of tasks that will be executed as part of the job 142 | steps: 143 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 144 | - uses: actions/checkout@v2 145 | with: 146 | submodules: recursive 147 | - uses: seanmiddleditch/gha-setup-ninja@v3 148 | - uses: ilammy/msvc-dev-cmd@v1 149 | 150 | 151 | # Runs a set of commands using the runners shell 152 | - name: build library cpp20 153 | run: | 154 | python3 build.py -G Ninja -DMACORO_CPP_VER=20 -DMACORO_ASAN=true -DCMAKE_BUILD_TYPE=Release -DMACORO_COMPILER_DEFINES=_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR 155 | 156 | - name: unit test cpp20 157 | run: | 158 | ./out/build/x64-Release/frontend/macoroFrontend.exe -u 159 | 160 | - name: find source tree 161 | run: | 162 | cd cmake/testProject 163 | cmake -S . -B out/ -DCMAKE_BUILD_TYPE=Release -D MACORO_ENABLE_BOOST=ON -D CMAKE_PREFIX_PATH=../../ 164 | cmake --build out/ --config Release 165 | ./out/Release/main.exe 166 | rm -r -fo out/ 167 | cd ../.. 168 | 169 | - name: hint test 170 | run: | 171 | python3 build.py 172 | cd cmake/testProject 173 | cmake -S . -B out/ -D MACORO_HINT=../.. 174 | cmake --build out/ --config Release 175 | ./out/Release/main.exe 176 | rm -r -fo out/ 177 | cd ../.. 178 | 179 | - name: install prefix test 180 | run: | 181 | python3 build.py --install=~/install 182 | cd cmake/testProject 183 | cmake -S . -B out/ -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=~/install 184 | cmake --build out/ --config Release 185 | ./out/Release/main.exe 186 | rm -r -fo out/ 187 | cd ../.. 188 | 189 | 190 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | build/ 3 | ~/ 4 | .vs/ 5 | 6 | thirdparty/log*.txt 7 | thirdparty/*/ 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.15) 3 | project(macoro VERSION 0.1.2) 4 | include(cmake/macoroBuildOptions.cmake) 5 | include(cmake/macoroFindDeps.cmake) 6 | 7 | add_subdirectory(macoro) 8 | 9 | if(MACORO_TESTS) 10 | add_subdirectory(tests) 11 | add_subdirectory(frontend) 12 | endif() -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "configurePresets": [ 4 | { 5 | "name": "linux", 6 | "displayName": "Linux", 7 | "description": "Target the Windows Subsystem for Linux (WSL) or a remote Linux system.", 8 | "generator": "Ninja", 9 | "binaryDir": "${sourceDir}/out/build/${presetName}", 10 | "cacheVariables": { 11 | "CMAKE_BUILD_TYPE": "Debug", 12 | "MACORO_FETCH_AUTO": "ON", 13 | "MACORO_CPP_VER": "20", 14 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 15 | //"CMAKE_C_COMPILER": "clang-12", 16 | //"CMAKE_CXX_COMPILER": "clang++-12" 17 | }, 18 | "vendor": { 19 | "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Linux" ] }, 20 | "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" } 21 | } 22 | }, 23 | { 24 | "name": "x64-Debug", 25 | "displayName": "Windows x64 Debug", 26 | "description": "Target Windows with the Visual Studio development environment.", 27 | "generator": "Ninja", 28 | "binaryDir": "${sourceDir}/out/build/${presetName}", 29 | "architecture": { 30 | "value": "x64", 31 | "strategy": "external" 32 | }, 33 | "cacheVariables": { 34 | "CMAKE_BUILD_TYPE": "Debug", 35 | "MACORO_FETCH_AUTO": "ON", 36 | "MACORO_CPP_VER": "20", 37 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 38 | }, 39 | "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Windows" ] } } 40 | }, 41 | { 42 | "name": "x64-Release", 43 | "displayName": "Windows x64 Release", 44 | "description": "Target Windows with the Visual Studio development environment.", 45 | "generator": "Ninja", 46 | "binaryDir": "${sourceDir}/out/build/${presetName}", 47 | "architecture": { 48 | "value": "x64", 49 | "strategy": "external" 50 | }, 51 | "cacheVariables": { 52 | "CMAKE_BUILD_TYPE": "RelWithDebInfo", 53 | "MACORO_FETCH_AUTO": "ON", 54 | "MACORO_CPP_VER": "20", 55 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" 56 | }, 57 | "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Windows" ] } } 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | import sys 4 | import multiprocessing 5 | 6 | def getParallel(args): 7 | par = multiprocessing.cpu_count() 8 | for x in args: 9 | if x.startswith("--par="): 10 | val = x.split("=",1)[1] 11 | par = int(val) 12 | if par < 1: 13 | par = 1 14 | idx = args.index(x) 15 | args[idx] = "" 16 | return (args,par) 17 | 18 | 19 | def replace(list, find, replace): 20 | if find in list: 21 | idx = list.index(find) 22 | list[idx] = replace; 23 | return list 24 | 25 | def getSuffix(X, prefix): 26 | for x in X: 27 | if x.startswith(prefix): 28 | return x[len(prefix):] 29 | return "" 30 | 31 | def Build(projectName, argv, install, par, sudo, noConfig): 32 | 33 | osStr = (platform.system()) 34 | buildDir = "" 35 | config = "" 36 | buildType = getSuffix(argv, "-DCMAKE_BUILD_TYPE=") 37 | setup = "--setup" in argv; 38 | argv = replace(argv, "--setup", "") 39 | 40 | if buildType == "": 41 | if "--debug" in argv: 42 | buildType = "Debug" 43 | else: 44 | buildType = "Release" 45 | argv.append("-DCMAKE_BUILD_TYPE={0}".format(buildType)) 46 | 47 | argv = replace(argv, "--debug", "") 48 | 49 | 50 | if osStr == "Windows": 51 | buildDir = "out/build/x64-{0}".format(buildType) 52 | config = "--config {0}".format(buildType) 53 | elif osStr == "Darwin": 54 | buildDir = "out/build/osx" 55 | else: 56 | buildDir = "out/build/linux" 57 | 58 | 59 | argStr = "" 60 | for a in argv: 61 | argStr = argStr + " " + a 62 | 63 | parallel = "" 64 | if par != 1: 65 | parallel = " --parallel " + str(par) 66 | 67 | mkDirCmd = "mkdir -p {0}".format(buildDir); 68 | CMakeCmd = "cmake -S . -B {0} {1}".format(buildDir, argStr) 69 | BuildCmd = "cmake --build {0} {1} {2} ".format(buildDir, config, parallel) 70 | 71 | 72 | InstallCmd = "" 73 | if sudo: 74 | sudo = "sudo " 75 | else: 76 | sudo = "" 77 | 78 | 79 | if install: 80 | InstallCmd = sudo 81 | InstallCmd += "cmake --install {0} {1} ".format(buildDir, config) 82 | 83 | 84 | print("\n\n====== build.py ("+projectName+") ========") 85 | if not noConfig: 86 | print(mkDirCmd) 87 | print(CMakeCmd) 88 | 89 | if not setup: 90 | print(BuildCmd) 91 | if len(InstallCmd): 92 | print(InstallCmd) 93 | print("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n\n") 94 | 95 | if not noConfig: 96 | os.system(mkDirCmd) 97 | os.system(CMakeCmd) 98 | 99 | if not setup: 100 | os.system(BuildCmd) 101 | 102 | if len(sudo) > 0: 103 | print("installing "+projectName+": {0}\n".format(InstallCmd)) 104 | 105 | os.system(InstallCmd) 106 | 107 | 108 | 109 | def help(): 110 | 111 | print(" --install \n\tInstructs the script to install whatever is currently being built to the default location.") 112 | print(" --install=prefix \n\tinstall to the provided predix.") 113 | print(" --sudo \n\twhen installing, use sudo. May require password.") 114 | print(" --par=n \n\twhen building do use parallel builds with n threads. default = num cores.") 115 | print(" --noauto \n\twhen building do not automaticly fetch dependancies.") 116 | print(" --par=n \n\twhen building do use parallel builds with n threads. default = num cores.") 117 | print(" --debug \n\tdebug build.") 118 | print("any additioanl arguments are forwared to cmake.\n") 119 | 120 | print("-build the library") 121 | print(" python build.py") 122 | print("-build the library with cmake configurations") 123 | print(" python build.py --debug -DENABLE_SSE=ON") 124 | print("-build the library and install with sudo") 125 | print(" python build.py --install --sudo") 126 | print("-build the library and install to prefix") 127 | print(" python build.py --install=~/my/install/dir ") 128 | 129 | 130 | 131 | def parseInstallArgs(args): 132 | prefix = "" 133 | doInstall = False 134 | for x in args: 135 | if x.startswith("--install="): 136 | prefix = x.split("=",1)[1] 137 | prefix = os.path.abspath(os.path.expanduser(prefix)) 138 | idx = args.index(x) 139 | args[idx] = "-DCMAKE_INSTALL_PREFIX=" + prefix 140 | doInstall = True 141 | if x == "--install": 142 | idx = args.index(x) 143 | osStr = (platform.system()) 144 | if osStr == "Windows": 145 | args[idx] = "-DCMAKE_INSTALL_PREFIX=c:/lib" 146 | else: 147 | args[idx] = "-DCMAKE_INSTALL_PREFIX=/usr/local" 148 | doInstall = True 149 | 150 | return (args, doInstall) 151 | 152 | def main(projectName, argv): 153 | 154 | if "--help" in argv: 155 | help() 156 | return 157 | 158 | sudo = "--sudo" in argv; 159 | 160 | if "--noauto" in argv: 161 | argv = replace(argv, "--noauto", "") 162 | argv.append("-DMACORO_FETCH_AUTO=OFF") 163 | else: 164 | argv.append("-DMACORO_FETCH_AUTO=ON") 165 | 166 | argv = replace(argv, "--sudo", "-DSUDO_FETCH=ON") 167 | 168 | argv, install = parseInstallArgs(argv) 169 | argv, par = getParallel(argv) 170 | 171 | noConfig = "--nc" in argv 172 | argv = replace(argv, "--nc", "") 173 | 174 | 175 | Build(projectName, argv, install, par, sudo, noConfig) 176 | 177 | if __name__ == "__main__": 178 | 179 | main("macoro", sys.argv[1:]) 180 | -------------------------------------------------------------------------------- /cmake/Config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | 4 | if(NOT MACORO_FIND_QUIETLY) 5 | message("macoroConfig.cmake: ${CMAKE_CURRENT_LIST_FILE}") 6 | endif() 7 | 8 | include("${CMAKE_CURRENT_LIST_DIR}/macoroTargets.cmake") 9 | 10 | include("${CMAKE_CURRENT_LIST_DIR}/macoroBuildOptions.cmake") 11 | 12 | include("${CMAKE_CURRENT_LIST_DIR}/macoroFindDeps.cmake") -------------------------------------------------------------------------------- /cmake/macoroBuildOptions.cmake: -------------------------------------------------------------------------------- 1 | 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/macoroPreamble.cmake") 4 | 5 | 6 | set(MACORO_BUILD ON) 7 | 8 | 9 | option(MACORO_FETCH_AUTO "" OFF) 10 | option(MACORO_PIC "build with -FPIC on unix" OFF) 11 | option(MACORO_INSTALL_THIRDPARTY "dont install third party" ON) 12 | option(MACORO_ASAN "build with asan" OFF) 13 | option(MACORO_TESTS "build with tests" ON) 14 | 15 | if(NOT DEFINED MACORO_THIRDPARTY_CLONE_DIR) 16 | set(MACORO_THIRDPARTY_CLONE_DIR "${CMAKE_CURRENT_LIST_DIR}/../out/") 17 | endif() 18 | 19 | if(NOT DEFINED MACORO_CPP_VER) 20 | set(MACORO_CPP_VER 20) 21 | endif() 22 | if(${MACORO_CPP_VER} EQUAL 20) 23 | set(MACORO_CPP_20 ON) 24 | else() 25 | set(MACORO_CPP_20 OFF) 26 | endif() 27 | 28 | if(DEFINED MACORO_OPTIONAL_LITE) 29 | message("setting MACORO_OPTIONAL_LITE_V=MACORO_OPTIONAL_LITE=${MACORO_OPTIONAL_LITE}") 30 | set(MACORO_OPTIONAL_LITE_V ${MACORO_OPTIONAL_LITE}) 31 | else() 32 | if(${MACORO_CPP_VER} EQUAL 14) 33 | message("setting MACORO_OPTIONAL_LITE_V=ON due to cpp=14") 34 | set(MACORO_OPTIONAL_LITE_V ON) 35 | else() 36 | set(MACORO_OPTIONAL_LITE_V OFF) 37 | endif() 38 | endif() 39 | if(DEFINED MACORO_VARIANT_LITE) 40 | message("setting MACORO_VARIANT_LITE_V=MACORO_VARIANT_LITE=${MACORO_VARIANT_LITE}") 41 | 42 | set(MACORO_VARIANT_LITE_V ${MACORO_VARIANT_LITE}) 43 | else() 44 | 45 | if(${MACORO_CPP_VER} EQUAL 14) 46 | message("setting MACORO_VARIANT_LITE_V=ON due to cpp=14") 47 | set(MACORO_VARIANT_LITE_V ON) 48 | else() 49 | set(MACORO_VARIANT_LITE_V OFF) 50 | endif() 51 | endif() 52 | 53 | 54 | if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 55 | # apple clang currently doesnt have stop_token 56 | option(MACORO_HAS_STD_STOP_TOKEN "use standard stop token" false) 57 | else() 58 | option(MACORO_HAS_STD_STOP_TOKEN "use standard stop token" true ) 59 | endif() 60 | 61 | message("\nOptions\n---------------------------") 62 | 63 | message("CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}\t ~ build type") 64 | message("MACORO_NO_SYSTEM_PATH = ${MACORO_NO_SYSTEM_PATH}\t ~ do not look in system paths for dependencies") 65 | message("MACORO_FETCH_AUTO = ${MACORO_FETCH_AUTO}\t ~ automatically fetch dependencies as needed") 66 | message("MACORO_FETCH_OPTIONAL = ${MACORO_FETCH_OPTIONAL} \t ~ fetch optional-lite") 67 | message("MACORO_FETCH_VARIANT = ${MACORO_FETCH_VARIANT} \t ~ fetch variant-lite") 68 | message("MACORO_CPP_VER = ${MACORO_CPP_VER}\t ~ cpp standard version") 69 | 70 | message("MACORO_OPTIONAL_LITE =${MACORO_OPTIONAL_LITE_V}\t ~ use optional lite") 71 | message("MACORO_VARIANT_LITE =${MACORO_VARIANT_LITE_V}\t ~ use variant lite") 72 | message("MACORO_PIC =${MACORO_PIC}\t ~ compile with -fPIC on unix") 73 | message("MACORO_ASAN =${MACORO_ASAN}\t ~ compile with asan") 74 | message("MACORO_TESTS =${MACORO_TESTS}\t ~ compile the tests and frontend") 75 | #message("MACORO_HAS_STD_STOP_TOKEN=${MACORO_HAS_STD_STOP_TOKEN}") 76 | message("---------------------------\n") -------------------------------------------------------------------------------- /cmake/macoroBuildOptions.cmake.in: -------------------------------------------------------------------------------- 1 | 2 | set(macoro_VERSION_MAJOR @macoro_VERSION_MAJOR@) 3 | set(macoro_VERSION_MINOR @macoro_VERSION_MINOR@) 4 | set(macoro_VERSION_PATCH @macoro_VERSION_PATCH@) 5 | 6 | # compile the library with c++20 support 7 | set(MACORO_CPP_20 @MACORO_CPP_20@) 8 | 9 | 10 | set(MACORO_CPP_VER @MACORO_CPP_VER@) 11 | set(MACORO_ENABLE_ASSERTS @MACORO_ENABLE_ASSERTS@) 12 | 13 | set(MACORO_ASAN @MACORO_ASAN@) 14 | set(MACORO_PIC @MACORO_PIC@) 15 | 16 | set(MACORO_VARIANT_LITE_V @MACORO_VARIANT_LITE_V@) 17 | set(MACORO_OPTIONAL_LITE_V @MACORO_OPTIONAL_LITE_V@) 18 | 19 | 20 | 21 | unset(macoro_debug_FOUND CACHE) 22 | unset(macoro_Debug_FOUND CACHE) 23 | unset(macoro_DEBUG_FOUND CACHE) 24 | unset(macoro_release_FOUND CACHE) 25 | unset(macoro_Release_FOUND CACHE) 26 | unset(macoro_RELEASE_FOUND CACHE) 27 | unset(macoro_relwithdebinfo_FOUND CACHE) 28 | unset(macoro_RelWithDebInfo_FOUND CACHE) 29 | unset(macoro_RELWITHDEBINFO_FOUND CACHE) 30 | string( TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_lower ) 31 | if(CMAKE_BUILD_TYPE_lower STREQUAL "debug") 32 | set(macoro_debug_FOUND true) 33 | set(macoro_Debug_FOUND true) 34 | set(macoro_DEBUG_FOUND true) 35 | endif() 36 | if(CMAKE_BUILD_TYPE_lower STREQUAL "release") 37 | set(macoro_release_FOUND true) 38 | set(macoro_Release_FOUND true) 39 | set(macoro_RELEASE_FOUND true) 40 | endif() 41 | if(CMAKE_BUILD_TYPE_lower STREQUAL "relwithdebinfo") 42 | set(macoro_relwithdebinfo_FOUND true) 43 | set(macoro_RelWithDebInfo_FOUND true) 44 | set(macoro_RELWITHDEBINFO_FOUND true) 45 | endif() 46 | 47 | 48 | 49 | if(${MACORO_CPP_VER} EQUAL 14) 50 | set(macoro_cpp_14_FOUND true) 51 | set(macoro_cpp_17_FOUND false) 52 | elseif(${MACORO_CPP_VER} EQUAL 17) 53 | set(macoro_cpp_14_FOUND false) 54 | set(macoro_cpp_17_FOUND true) 55 | else() 56 | set(macoro_cpp_14_FOUND false) 57 | set(macoro_cpp_17_FOUND false) 58 | endif() 59 | set(macoro_cpp_20_FOUND ${MACORO_CPP_20}) 60 | 61 | 62 | set(macoro_asan_FOUND ${MACORO_ASAN}) 63 | set(macoro_pic_FOUND ${MACORO_PIC}) 64 | set(macoro_optional_lite_FOUND ${MACORO_OPTIONAL_LITE_V}) 65 | set(macoro_variant_lite_FOUND ${MACORO_VARIANT_LITE_V}) 66 | 67 | 68 | if(NOT MACORO_PIC) 69 | set(macoro_no_pic_FOUND true) 70 | else() 71 | set(macoro_no_pic_FOUND false) 72 | endif() 73 | 74 | if(NOT MACORO_ASAN) 75 | set(macoro_no_asan_FOUND true) 76 | else() 77 | set(macoro_no_asan_FOUND false) 78 | endif() 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /cmake/macoroConfig.cmake: -------------------------------------------------------------------------------- 1 | 2 | 3 | # these are just pass through config file for the ones that are placed in the build directory. 4 | include("${CMAKE_CURRENT_LIST_DIR}/macoroFindBuildDir.cmake") 5 | include("${CMAKE_CURRENT_LIST_DIR}/macoroFindDeps.cmake") 6 | include("${MACORO_BUILD_DIR}/macoro/macoroConfig.cmake") 7 | -------------------------------------------------------------------------------- /cmake/macoroConfigVersion.cmake: -------------------------------------------------------------------------------- 1 | 2 | # these are just pass through config file for the ones that are placed in 3 | include("${CMAKE_CURRENT_LIST_DIR}/macoroFindBuildDir.cmake") 4 | include("${MACORO_BUILD_DIR}/macoro/macoroConfigVersion.cmake") 5 | -------------------------------------------------------------------------------- /cmake/macoroConfigVersion.cmake.in: -------------------------------------------------------------------------------- 1 | 2 | include("${CMAKE_CURRENT_LIST_DIR}/macoroBuildOptions.cmake") 3 | 4 | set(PACKAGE_VERSION "${macoro_VERSION_MAJOR}.${macoro_VERSION_MINOR}.${macoro_VERSION_PATCH}") 5 | 6 | if (PACKAGE_FIND_VERSION_RANGE) 7 | # Package version must be in the requested version range 8 | if ((PACKAGE_FIND_VERSION_RANGE_MIN STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MIN) 9 | OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_GREATER PACKAGE_FIND_VERSION_MAX) 10 | OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_GREATER_EQUAL PACKAGE_FIND_VERSION_MAX))) 11 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 12 | else() 13 | set(PACKAGE_VERSION_COMPATIBLE TRUE) 14 | endif() 15 | else() 16 | if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) 17 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 18 | else() 19 | set(PACKAGE_VERSION_COMPATIBLE TRUE) 20 | if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) 21 | set(PACKAGE_VERSION_EXACT TRUE) 22 | endif() 23 | endif() 24 | endif() 25 | 26 | 27 | 28 | set(missing_components) 29 | foreach(comp ${macoro_FIND_COMPONENTS}) 30 | if(NOT macoro_${comp}_FOUND) 31 | if(macoro_FIND_REQUIRED_${comp}) 32 | set(PACKAGE_VERSION_UNSUITABLE TRUE) 33 | set(missing_components ${missing_components} ${comp}) 34 | endif() 35 | endif() 36 | endforeach() 37 | 38 | 39 | if(PACKAGE_VERSION_UNSUITABLE AND NOT macoro_FIND_QUIETLY) 40 | message("Found incompatible macoro at ${CMAKE_CURRENT_LIST_DIR}. Missing components: ${missing_components}") 41 | endif() 42 | 43 | 44 | # if the installed project requested no architecture check, don't perform the check 45 | if("FALSE") 46 | return() 47 | endif() 48 | 49 | # if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it: 50 | if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "") 51 | return() 52 | endif() 53 | 54 | # check that the installed version has the same 32/64bit-ness as the one which is currently searching: 55 | if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") 56 | math(EXPR installedBits "8 * 8") 57 | set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)") 58 | set(PACKAGE_VERSION_UNSUITABLE TRUE) 59 | endif() 60 | -------------------------------------------------------------------------------- /cmake/macoroFindBuildDir.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/macoroPreamble.cmake") 2 | 3 | if(NOT MACORO_BUILD_DIR) 4 | set(MACORO_BUILD_DIR "${CMAKE_CURRENT_LIST_DIR}/../out/build/${MACORO_CONFIG}") 5 | else() 6 | message(STATUS "MACORO_BUILD_DIR preset to ${MACORO_BUILD_DIR}") 7 | endif() 8 | 9 | if(DEFINED MACORO_BUILD AND NOT EXISTS "${MACORO_BUILD_DIR}") 10 | message(FATAL_ERROR "failed to find the macoro build directory. Looked at: ${MACORO_BUILD_DIR}") 11 | endif() -------------------------------------------------------------------------------- /cmake/macoroFindDeps.cmake: -------------------------------------------------------------------------------- 1 | cmake_policy(PUSH) 2 | cmake_policy(SET CMP0057 NEW) 3 | cmake_policy(SET CMP0045 NEW) 4 | cmake_policy(SET CMP0074 NEW) 5 | 6 | include("${CMAKE_CURRENT_LIST_DIR}/macoroPreamble.cmake") 7 | 8 | 9 | set(PUSHED_CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH}) 10 | set(CMAKE_PREFIX_PATH "${MACORO_STAGE};${CMAKE_PREFIX_PATH}") 11 | 12 | ## optional-lite 13 | ########################################################################### 14 | macro(FIND_OPTIONAL) 15 | set(ARGS ${ARGN}) 16 | 17 | #explicitly asked to fetch optional 18 | if(MACORO_FETCH_OPTIONAL) 19 | list(APPEND ARGS NO_DEFAULT_PATH PATHS ${MACORO_STAGE}) 20 | elseif(${MACORO_NO_SYSTEM_PATH}) 21 | list(APPEND ARGS NO_DEFAULT_PATH PATHS ${CMAKE_PREFIX_PATH}) 22 | endif() 23 | 24 | find_package(optional-lite ${ARGS}) 25 | 26 | endmacro() 27 | 28 | if(MACORO_OPTIONAL_LITE_V) 29 | if((MACORO_FETCH_AUTO OR MACORO_FETCH_OPTIONAL) AND MACORO_BUILD) 30 | if(NOT MACORO_FETCH_OPTIONAL) 31 | FIND_OPTIONAL(QUIET) 32 | endif() 33 | include("${CMAKE_CURRENT_LIST_DIR}/../thirdparty/getOptionalLite.cmake") 34 | endif() 35 | 36 | FIND_OPTIONAL(REQUIRED) 37 | endif() 38 | 39 | ## variant-lite 40 | ########################################################################### 41 | 42 | macro(FIND_VARIANT) 43 | set(ARGS ${ARGN}) 44 | 45 | #explicitly asked to fetch variant 46 | if(MACORO_FETCH_VARIANT) 47 | list(APPEND ARGS NO_DEFAULT_PATH PATHS ${MACORO_STAGE}) 48 | elseif(${MACORO_NO_SYSTEM_PATH}) 49 | list(APPEND ARGS NO_DEFAULT_PATH PATHS ${CMAKE_PREFIX_PATH}) 50 | endif() 51 | 52 | find_package(variant-lite ${ARGS}) 53 | 54 | endmacro() 55 | 56 | if(MACORO_VARIANT_LITE_V) 57 | if((MACORO_FETCH_AUTO OR MACORO_FETCH_OPTIONAL) AND MACORO_BUILD) 58 | if(NOT MACORO_FETCH_VARIANT) 59 | FIND_VARIANT(QUIET) 60 | endif() 61 | 62 | include("${CMAKE_CURRENT_LIST_DIR}/../thirdparty/getVariantLite.cmake") 63 | endif() 64 | 65 | FIND_VARIANT(REQUIRED) 66 | endif() 67 | 68 | 69 | 70 | ########################################################################### 71 | 72 | # resort the previous prefix path 73 | set(CMAKE_PREFIX_PATH ${PUSHED_CMAKE_PREFIX_PATH}) 74 | cmake_policy(POP) 75 | 76 | find_package(Threads REQUIRED) -------------------------------------------------------------------------------- /cmake/macoroPreamble.cmake: -------------------------------------------------------------------------------- 1 | 2 | if(NOT DEFINED CMAKE_BUILD_TYPE) 3 | set(CMAKE_BUILD_TYPE "Release") 4 | endif() 5 | 6 | if(MSVC) 7 | if("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") 8 | set(MACORO_CONFIG "x64-Release") 9 | else() 10 | set(MACORO_CONFIG "x64-${CMAKE_BUILD_TYPE}") 11 | endif() 12 | elseif(APPLE) 13 | set(MACORO_CONFIG "osx") 14 | else() 15 | set(MACORO_CONFIG "linux") 16 | endif() 17 | 18 | 19 | 20 | if(NOT DEFINED MACORO_STAGE) 21 | 22 | if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/Config.cmake.in) 23 | # we currenty are in the macoro source tree, macoro/cmake 24 | set(MACORO_STAGE "${CMAKE_CURRENT_LIST_DIR}/../out/install/${MACORO_CONFIG}") 25 | else() 26 | # we currenty are in install tree, /lib/cmake/macoro 27 | set(MACORO_STAGE "${CMAKE_CURRENT_LIST_DIR}/../../..") 28 | endif() 29 | 30 | get_filename_component(MACORO_STAGE ${MACORO_STAGE} ABSOLUTE) 31 | endif() 32 | 33 | if(DEFINED MACORO_BUILD) 34 | message(STATUS "Option: MACORO_STAGE = ${MACORO_STAGE}") 35 | endif() -------------------------------------------------------------------------------- /cmake/testProject/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required (VERSION 3.15) 3 | 4 | project (TestProj VERSION 1.0.0) 5 | 6 | add_executable(main "src.cpp") 7 | 8 | message("CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH}") 9 | 10 | find_package(macoro REQUIRED HINTS ${MACORO_HINT}) 11 | 12 | target_link_libraries(main macoro::macoro) 13 | 14 | 15 | option(MACORO_CPP_VER "cpp version" 20) 16 | 17 | if(MSVC) 18 | target_compile_options( main PRIVATE 19 | $<$:/std:c++${MACORO_CPP_VER}> 20 | ) 21 | else() 22 | target_compile_options( main PRIVATE 23 | $<$:-std=c++${MACORO_CPP_VER}> 24 | ) 25 | endif() -------------------------------------------------------------------------------- /cmake/testProject/src.cpp: -------------------------------------------------------------------------------- 1 | #include "macoro/stop.h" 2 | int main() 3 | { 4 | macoro::stop_token t; 5 | return 0; 6 | } -------------------------------------------------------------------------------- /frontend/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | set(SRC 4 | main.cpp 5 | ) 6 | 7 | 8 | 9 | add_executable(macoroFrontend ${SRC}) 10 | target_link_libraries(macoroFrontend macoroTests macoro) 11 | 12 | 13 | if(MSVC) 14 | 15 | target_compile_options( macoroFrontend PRIVATE 16 | $<$:/std:c++${MACORO_CPP_VER}> 17 | ) 18 | else() 19 | 20 | 21 | 22 | target_compile_options( macoroFrontend PRIVATE 23 | $<$:-std=c++${MACORO_CPP_VER}> 24 | ) 25 | endif() -------------------------------------------------------------------------------- /frontend/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "macoro/type_traits.h" 3 | #include "macoro/macros.h" 4 | #include "macoro/optional.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "tests/tests.h" 11 | 12 | using namespace macoro; 13 | 14 | 15 | int main(int argc, char** argv) 16 | { 17 | macoro::CLP cmd(argc, argv); 18 | macoro::testCollection.runIf(cmd); 19 | return 0; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /macoro/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(SRC 3 | thread_pool.cpp 4 | ) 5 | 6 | if (NOT MACORO_HAS_STD_STOP_TOKEN) 7 | message("adding stop_token impl, ${MACORO_HAS_STD_STOP_TOKEN}") 8 | LIST(APPEND SRC 9 | detail/stop_source.cpp 10 | detail/stop_state.cpp 11 | detail/stop_token.cpp 12 | ) 13 | endif() 14 | if(MSVC) 15 | LIST(APPEND SRC 16 | detail/win32.cpp) 17 | endif() 18 | 19 | add_library(macoro STATIC ${SRC} "trace.h" "barrier.h" ) 20 | 21 | 22 | 23 | target_include_directories( macoro PUBLIC 24 | $ 25 | $) 26 | target_include_directories( macoro PUBLIC 27 | $ 28 | $) 29 | 30 | configure_file(config.h.in config.h) 31 | 32 | 33 | if(MACORO_OPTIONAL_LITE_V) 34 | target_link_Libraries(macoro PUBLIC nonstd::optional-lite) 35 | endif() 36 | 37 | if(MACORO_VARIANT_LITE_V) 38 | target_link_Libraries(macoro PUBLIC nonstd::variant-lite) 39 | endif() 40 | 41 | if(APPLE OR UNIX) 42 | target_compile_options( macoro PRIVATE 43 | $<$:-std=c++${MACORO_CPP_VER}> 44 | ) 45 | target_compile_options(macoro PUBLIC $<$:-Wno-error=switch>) 46 | if(MACORO_PIC) 47 | target_compile_options(macoro PUBLIC 48 | "-fPIC" 49 | ) 50 | endif() 51 | elseif(MSVC) 52 | 53 | target_compile_options( macoro PRIVATE 54 | $<$:/std:c++${MACORO_CPP_VER}> 55 | ) 56 | target_compile_options( macoro PUBLIC 57 | "/D_ENABLE_EXTENDED_ALIGNED_STORAGE" 58 | ) 59 | 60 | 61 | target_link_libraries(macoro PUBLIC ws2_32.lib) 62 | else() 63 | set_target_properties( macoro 64 | PROPERTIES 65 | CXX_STANDARD ${MACORO_CPP_VER} 66 | CXX_STANDARD_REQUIRED YES 67 | CXX_EXTENSIONS NO 68 | ) 69 | endif() 70 | #if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND MACORO_CPP_20) 71 | # target_compile_options( macoro PUBLIC 72 | # "-fcoroutines" 73 | # ) 74 | #endif() 75 | 76 | if(MACORO_ASAN) 77 | message("Warning: ASAN is enabled for macoro.") 78 | if(MSVC) 79 | target_compile_options(macoro PUBLIC 80 | "/fsanitize=address") 81 | else() 82 | target_compile_options(macoro PRIVATE 83 | "-fsanitize=address") 84 | target_link_options(macoro PUBLIC "-fsanitize=address") 85 | endif() 86 | endif() 87 | 88 | find_package (Threads) 89 | target_link_libraries(macoro PUBLIC Threads::Threads) 90 | 91 | 92 | 93 | ############################################# 94 | # Install # 95 | ############################################# 96 | 97 | # make cache variables for install destinations 98 | include(GNUInstallDirs) 99 | include(CMakePackageConfigHelpers) 100 | 101 | configure_file("${CMAKE_CURRENT_LIST_DIR}/../cmake/macoroConfigVersion.cmake.in" "macoroConfigVersion.cmake" COPYONLY) 102 | configure_file("${CMAKE_CURRENT_LIST_DIR}/../cmake/macoroFindDeps.cmake" "macoroFindDeps.cmake" COPYONLY) 103 | configure_file("${CMAKE_CURRENT_LIST_DIR}/../cmake/macoroPreamble.cmake" "macoroPreamble.cmake" COPYONLY) 104 | configure_file("${CMAKE_CURRENT_LIST_DIR}/../cmake/macoroBuildOptions.cmake.in" "macoroBuildOptions.cmake") 105 | 106 | # generate the config file that is includes the exports 107 | configure_package_config_file( 108 | "${CMAKE_CURRENT_LIST_DIR}/../cmake/Config.cmake.in" 109 | "${CMAKE_CURRENT_BINARY_DIR}/macoroConfig.cmake" 110 | INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/macoro 111 | NO_SET_AND_CHECK_MACRO 112 | NO_CHECK_REQUIRED_COMPONENTS_MACRO 113 | ) 114 | 115 | if(NOT DEFINED macoro_VERSION_MAJOR) 116 | message("\n\n\n\n warning, macoro_VERSION_MAJOR not defined ${macoro_VERSION_MAJOR}") 117 | endif() 118 | 119 | set_property(TARGET macoro PROPERTY VERSION ${macoro_VERSION}) 120 | 121 | # install the configuration file 122 | install(FILES 123 | "${CMAKE_CURRENT_BINARY_DIR}/macoroBuildOptions.cmake" 124 | "${CMAKE_CURRENT_BINARY_DIR}/macoroConfig.cmake" 125 | "${CMAKE_CURRENT_BINARY_DIR}/macoroConfigVersion.cmake" 126 | "${CMAKE_CURRENT_BINARY_DIR}/macoroFindDeps.cmake" 127 | "${CMAKE_CURRENT_BINARY_DIR}/macoroPreamble.cmake" 128 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/macoro 129 | ) 130 | 131 | # install library 132 | install( 133 | TARGETS macoro 134 | DESTINATION ${CMAKE_INSTALL_LIBDIR} 135 | EXPORT macoroTargets) 136 | 137 | # install headers 138 | install(DIRECTORY . DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/macoro FILES_MATCHING PATTERN "*.h") 139 | 140 | 141 | #install config header 142 | install( FILES 143 | "${CMAKE_CURRENT_BINARY_DIR}/config.h" 144 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/macoro") 145 | 146 | # install config 147 | install(EXPORT macoroTargets 148 | FILE macoroTargets.cmake 149 | NAMESPACE macoro:: 150 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/macoro 151 | ) 152 | export(EXPORT macoroTargets 153 | FILE "${CMAKE_CURRENT_BINARY_DIR}/macoroTargets.cmake" 154 | NAMESPACE macoro:: 155 | ) 156 | 157 | -------------------------------------------------------------------------------- /macoro/awaiter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "macoro/coroutine_handle.h" 3 | #include "macoro/type_traits.h" 4 | #include 5 | #include 6 | 7 | namespace macoro 8 | { 9 | 10 | 11 | namespace detail 12 | { 13 | 14 | template 15 | enable_if_t::value> 16 | set_continuation(continuation&& c, T&& mTask) 17 | { 18 | c.promise().set_continuation(mTask); 19 | } 20 | 21 | template 22 | enable_if_t::value> 23 | set_continuation(continuation&& c, T&& mTask) 24 | {} 25 | 26 | template 27 | auto convert_handle(const continuation& c) 28 | { 29 | #ifdef MACORO_CPP_20 30 | using traits_T = coroutine_handle_traits::type>; 31 | using traits_C = coroutine_handle_traits; 32 | using promise_type = typename traits_C::promise_type; 33 | using handle = typename std::conditional< 34 | // if we have a macoro handle 35 | std::is_same, coroutine_handle>::value&& 36 | // and we are requesting a std handle 37 | std::is_same, std::coroutine_handle>::value, 38 | // then we give a void std handle 39 | std::coroutine_handle, 40 | // otherwise we give back the requesting handle type 41 | typename traits_T::template coroutine_handle 42 | >::type; 43 | 44 | return static_cast(c); 45 | #else 46 | return c; 47 | #endif 48 | } 49 | 50 | 51 | // a basic awaiter that will get the caller to 52 | // run the continuation that it contains. continuation 53 | // should be std::coroutine_handle or macoro::coroutine_handle 54 | // depending on the use. 55 | template, bool check_ready = false> 56 | struct continuation_awaiter 57 | { 58 | continuation cont; 59 | bool await_ready() const noexcept { 60 | if (check_ready) 61 | return !cont || cont.done(); 62 | else 63 | return false; 64 | } 65 | 66 | template 67 | auto await_suspend(T&&mTask) noexcept 68 | { 69 | set_continuation(cont, mTask); 70 | return convert_handle(cont); 71 | } 72 | 73 | void await_resume() const noexcept {} 74 | }; 75 | } 76 | 77 | struct broken_promise : public std::logic_error 78 | { 79 | 80 | broken_promise() 81 | : std::logic_error("broken promise") 82 | {} 83 | 84 | broken_promise(const char* str) 85 | : std::logic_error(std::string("broken promise: ") + str) 86 | {} 87 | }; 88 | 89 | } 90 | 91 | -------------------------------------------------------------------------------- /macoro/barrier.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "macoro/config.h" 4 | #include "macoro/coroutine_handle.h" 5 | #include "macoro/trace.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace macoro 11 | { 12 | class barrier; 13 | 14 | class barrier_awaiter : basic_traceable 15 | { 16 | barrier& m_barrier; 17 | 18 | public: 19 | 20 | barrier_awaiter(barrier& barrier) 21 | : m_barrier(barrier) 22 | {} 23 | 24 | bool await_ready() const noexcept { return false; } 25 | 26 | template 27 | coroutine_handle<> await_suspend( 28 | coroutine_handle continuation, 29 | std::source_location loc = std::source_location::current()) noexcept; 30 | 31 | template 32 | std::coroutine_handle<> await_suspend( 33 | std::coroutine_handle continuation, 34 | std::source_location loc = std::source_location::current()) noexcept 35 | { 36 | return await_suspend(coroutine_handle(continuation), loc).std_cast(); 37 | } 38 | 39 | void await_resume() const noexcept {} 40 | }; 41 | 42 | // a barrier is used to release a waiting coroutine 43 | // once a count reaches 0. The count can be initialy 44 | // set and the incremented and decremented. Once it 45 | // hits zero, an await coroutine is resumed. 46 | // 47 | // for exmaple: 48 | // 49 | // barrier b(1); // count = 1 50 | // b.add(2); // count = 3 51 | // b.increment();// count = 4 52 | // co_await b; // suspend 53 | // b.decrement();// count = 3 54 | // b.decrement();// count = 2 55 | // b.decrement();// count = 1 56 | // b.decrement();// count = 0 57 | // resumed... 58 | // 59 | // The barrior can be reused once it reaches zero. 60 | // 61 | class barrier 62 | { 63 | friend barrier_awaiter; 64 | 65 | // the coro that is awaiting the barrier 66 | coroutine_handle<> m_continuation; 67 | 68 | // the current count, once it decrements to 0, 69 | // m_continuation is called if set. 70 | std::atomic m_count; 71 | public: 72 | 73 | // construct a new barrier with the given initial value. 74 | explicit barrier(std::size_t initial_count = 0) noexcept 75 | : m_count(initial_count) 76 | {} 77 | 78 | // increament the count by amount and return the new count. 79 | std::size_t add(size_t amount) noexcept 80 | { 81 | auto old = m_count.fetch_add(amount, std::memory_order_relaxed); 82 | auto ret = old + amount; 83 | assert(ret >= old); 84 | return ret; 85 | } 86 | 87 | // increment the count by 1 88 | std::size_t increment() noexcept 89 | { 90 | return add(1); 91 | } 92 | 93 | // returns the current count. 94 | std::size_t count() const noexcept 95 | { 96 | return m_count.load(std::memory_order_acquire); 97 | } 98 | 99 | // decrease the count by 1. Returns the new count. 100 | std::size_t decrement() noexcept 101 | { 102 | const std::size_t old_count = m_count.fetch_sub(1, std::memory_order_acq_rel); 103 | 104 | assert(old_count); 105 | 106 | if (old_count == 1 && m_continuation) 107 | { 108 | std::exchange(m_continuation, nullptr).resume(); 109 | } 110 | return old_count - 1; 111 | } 112 | 113 | // suspend until the count reached zero. Only 114 | // one caller can await the barrier at a time. 115 | // if needed, the impl could be extended to support 116 | // more callers, see async_manual_reset_event. 117 | auto MACORO_OPERATOR_COAWAIT() noexcept 118 | { 119 | return barrier_awaiter{ *this }; 120 | } 121 | }; 122 | 123 | template 124 | coroutine_handle<> barrier_awaiter::await_suspend( 125 | coroutine_handle continuation, 126 | std::source_location loc) noexcept 127 | { 128 | coroutine_handle<> ret = continuation; 129 | if (m_barrier.increment() > 1) 130 | { 131 | set_parent(get_traceable(continuation), loc); 132 | assert(m_barrier.m_continuation == nullptr); 133 | m_barrier.m_continuation = std::exchange(ret, noop_coroutine()); 134 | } 135 | m_barrier.decrement(); 136 | 137 | return ret; 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /macoro/blocking.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "macoro/type_traits.h" 3 | #include "macoro/coro_frame.h" 4 | #include "macoro/optional.h" 5 | #include "macoro/coroutine_handle.h" 6 | #include 7 | #include "macros.h" 8 | namespace macoro 9 | { 10 | 11 | namespace detail 12 | { 13 | template 14 | struct blocking_task; 15 | 16 | template 17 | struct blocking_promise_base 18 | { 19 | 20 | std::exception_ptr exception; 21 | std::mutex mutex; 22 | std::condition_variable cv; 23 | bool is_set = false; 24 | 25 | void wait() 26 | { 27 | std::unique_lock lock(mutex); 28 | cv.wait(lock, [this] { return is_set; }); 29 | } 30 | 31 | void set() 32 | { 33 | assert(is_set == false); 34 | std::lock_guard lock(this->mutex); 35 | this->is_set = true; 36 | this->cv.notify_all(); 37 | } 38 | 39 | 40 | suspend_always initial_suspend() noexcept { return{}; } 41 | suspend_always final_suspend() noexcept { 42 | set(); 43 | return { }; 44 | } 45 | 46 | void return_void() {} 47 | 48 | 49 | void unhandled_exception() noexcept 50 | { 51 | exception = std::current_exception(); 52 | } 53 | }; 54 | 55 | template 56 | struct blocking_promise : public blocking_promise_base 57 | { 58 | typename std::remove_reference::type* mVal = nullptr; 59 | 60 | blocking_task get_return_object() noexcept; 61 | blocking_task macoro_get_return_object() noexcept; 62 | 63 | using reference_type = T&&; 64 | suspend_always yield_value(reference_type v) noexcept 65 | { 66 | mVal = std::addressof(v); 67 | this->set(); 68 | return { }; 69 | } 70 | 71 | reference_type value() 72 | { 73 | if (this->exception) 74 | std::rethrow_exception(this->exception); 75 | return static_cast(*mVal); 76 | } 77 | 78 | 79 | 80 | template 81 | decltype(auto) await_transform(TT&& mTask) 82 | { 83 | return static_cast(mTask); 84 | } 85 | 86 | struct get_promise 87 | { 88 | blocking_promise* promise = nullptr; 89 | bool await_ready() { return true; } 90 | template 91 | void await_suspend(TT&&) {} 92 | blocking_promise* await_resume() 93 | { 94 | return promise; 95 | } 96 | }; 97 | 98 | get_promise await_transform(get_promise) 99 | { 100 | return { this }; 101 | } 102 | }; 103 | 104 | template<> 105 | struct blocking_promise : public blocking_promise_base 106 | { 107 | blocking_task get_return_object() noexcept; 108 | blocking_task macoro_get_return_object() noexcept; 109 | 110 | void value() 111 | { 112 | if (this->exception) 113 | std::rethrow_exception(this->exception); 114 | } 115 | }; 116 | 117 | 118 | template 119 | struct blocking_task 120 | { 121 | using promise_type = blocking_promise; 122 | coroutine_handle handle; 123 | blocking_task(coroutine_handle h) 124 | : handle(h) 125 | {} 126 | 127 | blocking_task() = delete; 128 | blocking_task(blocking_task&& h) noexcept :handle(std::exchange(h.handle, std::nullptr_t{})) {} 129 | blocking_task& operator=(blocking_task&& h) { handle = std::exchange(h.handle, std::nullptr_t{}); } 130 | 131 | ~blocking_task() 132 | { 133 | if (handle) 134 | handle.destroy(); 135 | } 136 | 137 | void start() 138 | { 139 | handle.resume(); 140 | } 141 | 142 | decltype(auto) get() 143 | { 144 | handle.promise().wait(); 145 | return handle.promise().value(); 146 | } 147 | }; 148 | 149 | 150 | 151 | template 152 | inline blocking_task blocking_promise::get_return_object() noexcept { return { coroutine_handle>::from_promise(*this, coroutine_handle_type::std) }; } 153 | template 154 | inline blocking_task blocking_promise::macoro_get_return_object() noexcept { return { coroutine_handle>::from_promise(*this, coroutine_handle_type::mocoro) }; } 155 | inline blocking_task blocking_promise::get_return_object() noexcept { return { coroutine_handle>::from_promise(*this, coroutine_handle_type::std) }; } 156 | inline blocking_task blocking_promise::macoro_get_return_object() noexcept { return { coroutine_handle>::from_promise(*this, coroutine_handle_type::mocoro) }; } 157 | 158 | template< 159 | typename Awaitable, 160 | typename ResultType = typename awaitable_traits::await_result> 161 | enable_if_t::value, 162 | blocking_task 163 | > 164 | make_blocking_task(Awaitable&& awaitable) 165 | { 166 | #if 0 167 | 168 | auto promise = co_await typename blocking_promise::get_promise{}; 169 | auto awaiter = promise->yield_value(co_await std::forward(a)); 170 | co_await awaiter; 171 | #elif 1 172 | MC_BEGIN(blocking_task, &awaitable); 173 | 174 | // co_yield co_await static_cast(awaitable) 175 | MC_YIELD_AWAIT(static_cast(awaitable)); 176 | MC_END(); 177 | 178 | #endif 179 | } 180 | 181 | template< 182 | typename Awaitable, 183 | typename ResultType = typename awaitable_traits::await_result> 184 | enable_if_t::value, 185 | blocking_task 186 | > 187 | make_blocking_task(Awaitable&& awaitable) 188 | { 189 | #if 0 190 | co_await std::forward(awaitable); 191 | #else 192 | MC_BEGIN(blocking_task, &awaitable); 193 | MC_AWAIT(static_cast(awaitable)); 194 | MC_END(); 195 | #endif 196 | } 197 | 198 | } 199 | 200 | 201 | template 202 | typename awaitable_traits::await_result 203 | blocking_get(Awaitable&& awaitable) 204 | { 205 | auto task = detail::make_blocking_task(std::forward(awaitable)); 206 | task.start(); 207 | 208 | return task.get(); 209 | } 210 | 211 | 212 | } -------------------------------------------------------------------------------- /macoro/channel_spsc.h: -------------------------------------------------------------------------------- 1 | 2 | #include "macoro/channel.h" 3 | -------------------------------------------------------------------------------- /macoro/config.h.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | #cmakedefine MACORO_CPP_20 @MACORO_CPP_20@ 4 | #cmakedefine MACORO_VARIANT_LITE_V @MACORO_VARIANT_LITE_V@ 5 | #cmakedefine MACORO_OPTIONAL_LITE_V @MACORO_OPTIONAL_LITE_V@ 6 | #cmakedefine MACORO_HAS_STD_STOP_TOKEN @MACORO_HAS_STD_STOP_TOKEN@ 7 | 8 | 9 | #ifdef MACORO_CPP_20 10 | #define MACORO_NODISCARD [[nodiscard]] 11 | #define MACORO_FALLTHROUGH [[fallthrough]] 12 | #define MACORO_OPERATOR_COAWAIT operator co_await 13 | #else 14 | #define MACORO_NODISCARD 15 | #define MACORO_FALLTHROUGH 16 | #define MACORO_OPERATOR_COAWAIT operator_co_await 17 | #endif 18 | 19 | 20 | 21 | 22 | #define MACORO_STRINGIZE_DETAIL(x) #x 23 | #define MACORO_STRINGIZE(x) MACORO_STRINGIZE_DETAIL(x) 24 | #define MACORO_LOCATION __FILE__ ":" MACORO_STRINGIZE(__LINE__) 25 | #define MACORO_RTE_LOC std::runtime_error(MACORO_LOCATION) 26 | #define MACORO_ASSUME(x) 27 | 28 | 29 | #if _MSC_VER 30 | # define MACORO_WINDOWS_OS 1 31 | # define MACORO_NOINLINE __declspec(noinline) 32 | #else 33 | # define MACORO_WINDOWS_OS 0 34 | # define MACORO_NOINLINE __attribute__((noinline)) 35 | #endif 36 | # define MACORO_CPU_CACHE_LINE 64 37 | 38 | -------------------------------------------------------------------------------- /macoro/detail/manual_lifetime.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace macoro 11 | { 12 | namespace detail { 13 | template 14 | struct manual_lifetime 15 | { 16 | public: 17 | manual_lifetime() noexcept {} 18 | ~manual_lifetime() noexcept {} 19 | 20 | manual_lifetime(const manual_lifetime&) = delete; 21 | manual_lifetime(manual_lifetime&&) = delete; 22 | manual_lifetime& operator=(const manual_lifetime&) = delete; 23 | manual_lifetime& operator=(manual_lifetime&&) = delete; 24 | 25 | template 26 | enable_if_t::value> construct(Args&&... args) 27 | noexcept(std::is_nothrow_constructible::value) 28 | { 29 | ::new (static_cast(std::addressof(m_value))) T(static_cast(args)...); 30 | } 31 | 32 | void destruct() noexcept(std::is_nothrow_destructible::value) 33 | { 34 | m_value.~T(); 35 | } 36 | 37 | std::add_pointer_t operator->() noexcept { return std::addressof(**this); } 38 | std::add_pointer_t operator->() const noexcept { return std::addressof(**this); } 39 | 40 | T& operator*() & noexcept { return m_value; } 41 | const T& operator*() const& noexcept { return m_value; } 42 | T&& operator*() && noexcept { return static_cast(m_value); } 43 | const T&& operator*() const&& noexcept { return static_cast(m_value); } 44 | 45 | private: 46 | union { 47 | T m_value; 48 | }; 49 | }; 50 | 51 | template 52 | struct manual_lifetime 53 | { 54 | public: 55 | manual_lifetime() noexcept {} 56 | ~manual_lifetime() noexcept {} 57 | 58 | manual_lifetime(const manual_lifetime&) = delete; 59 | manual_lifetime(manual_lifetime&&) = delete; 60 | manual_lifetime& operator=(const manual_lifetime&) = delete; 61 | manual_lifetime& operator=(manual_lifetime&&) = delete; 62 | 63 | void construct(T& value) noexcept 64 | { 65 | m_value = std::addressof(value); 66 | } 67 | 68 | void destruct() noexcept {} 69 | 70 | T* operator->() noexcept { return m_value; } 71 | const T* operator->() const noexcept { return m_value; } 72 | 73 | T& operator*() noexcept { return *m_value; } 74 | const T& operator*() const noexcept { return *m_value; } 75 | 76 | private: 77 | T* m_value; 78 | }; 79 | 80 | template 81 | struct manual_lifetime 82 | { 83 | public: 84 | manual_lifetime() noexcept {} 85 | ~manual_lifetime() noexcept {} 86 | 87 | manual_lifetime(const manual_lifetime&) = delete; 88 | manual_lifetime(manual_lifetime&&) = delete; 89 | manual_lifetime& operator=(const manual_lifetime&) = delete; 90 | manual_lifetime& operator=(manual_lifetime&&) = delete; 91 | 92 | void construct(T&& value) noexcept 93 | { 94 | m_value = std::addressof(value); 95 | } 96 | 97 | void destruct() noexcept {} 98 | 99 | T* operator->() noexcept { return m_value; } 100 | const T* operator->() const noexcept { return m_value; } 101 | 102 | T& operator*() & noexcept { return *m_value; } 103 | const T& operator*() const& noexcept { return *m_value; } 104 | T&& operator*() && noexcept { return static_cast(*m_value); } 105 | const T&& operator*() const&& noexcept { return static_cast(*m_value); } 106 | 107 | private: 108 | T* m_value; 109 | }; 110 | 111 | template<> 112 | struct manual_lifetime 113 | { 114 | void construct() noexcept {} 115 | void destruct() noexcept {} 116 | void operator*() const noexcept {} 117 | }; 118 | } 119 | } 120 | 121 | -------------------------------------------------------------------------------- /macoro/detail/operation_cancelled.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace macoro 10 | { 11 | class operation_cancelled : public std::exception 12 | { 13 | public: 14 | 15 | operation_cancelled() noexcept 16 | : std::exception() 17 | {} 18 | 19 | const char* what() const noexcept override { return "operation cancelled"; } 20 | }; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /macoro/detail/stop_callback.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | #include "macoro/config.h" 7 | #ifndef MACORO_HAS_STD_STOP_TOKEN 8 | 9 | #include "macoro/detail/stop_token.h" 10 | #include "macoro/detail/stop_state.h" 11 | 12 | #include 13 | #include 14 | #include "macoro/type_traits.h" 15 | #include "macoro/optional.h" 16 | #include 17 | #include 18 | #include 19 | 20 | namespace macoro 21 | { 22 | namespace detail 23 | { 24 | class stop_state; 25 | struct stop_callback_list_chunk; 26 | struct stop_callback_state; 27 | } 28 | 29 | //template 30 | class stop_callback 31 | { 32 | public: 33 | using Callback = std::function; 34 | 35 | /// Registers the callback to be executed when cancellation is requested 36 | /// on the stop_token. 37 | /// 38 | /// The callback will be executed if cancellation is requested for the 39 | /// specified cancellation token. If cancellation has already been requested 40 | /// then the callback will be executed immediately, before the constructor 41 | /// returns. If cancellation has not yet been requested then the callback 42 | /// will be executed on the first thread to request cancellation inside 43 | /// the call to stop_source::request_stop(). 44 | /// 45 | /// \param token 46 | /// The cancellation token to register the callback with. 47 | /// 48 | /// \param callback 49 | /// The callback to be executed when cancellation is requested on the 50 | /// the stop_token. Note that callback must not throw an exception 51 | /// if called when cancellation is requested otherwise std::terminate() 52 | /// will be called. 53 | /// 54 | /// \throw std::bad_alloc 55 | /// If registration failed due to insufficient memory available. 56 | template< 57 | typename FUNC, 58 | typename = enable_if_t::value>> 59 | stop_callback(stop_token token, FUNC&& callback) 60 | : m_callback(std::forward(callback)) 61 | { 62 | register_callback(std::move(token)); 63 | } 64 | 65 | stop_callback(const stop_callback& other) = delete; 66 | stop_callback& operator=(const stop_callback& other) = delete; 67 | stop_callback(stop_callback&& other) = delete; 68 | stop_callback& operator=(stop_callback&& other) = delete; 69 | 70 | /// Deregisters the callback. 71 | /// 72 | /// After the destructor returns it is guaranteed that the callback 73 | /// will not be subsequently called during a call to request_stop() 74 | /// on the stop_source. 75 | /// 76 | /// This may block if cancellation has been requested on another thread 77 | /// is it will need to wait until this callback has finished executing 78 | /// before the callback can be destroyed. 79 | ~stop_callback() 80 | { 81 | if (m_state != nullptr) 82 | { 83 | m_state->deregister_callback(this); 84 | m_state->release_token_ref(); 85 | } 86 | } 87 | 88 | private: 89 | 90 | friend class detail::stop_state; 91 | friend struct detail::stop_callback_state; 92 | 93 | void register_callback(stop_token&& token) 94 | { 95 | auto* state = token.m_state; 96 | if (state != nullptr && state->stop_possible()) 97 | { 98 | m_state = state; 99 | if (state->try_register_callback(this)) 100 | { 101 | token.m_state = nullptr; 102 | } 103 | else 104 | { 105 | m_state = nullptr; 106 | m_callback(); 107 | } 108 | } 109 | else 110 | { 111 | m_state = nullptr; 112 | } 113 | } 114 | 115 | detail::stop_state* m_state; 116 | Callback m_callback; 117 | detail::stop_callback_list_chunk* m_chunk; 118 | std::uint32_t m_entryIndex; 119 | }; 120 | } 121 | 122 | #endif -------------------------------------------------------------------------------- /macoro/detail/stop_source.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "macoro/detail/stop_source.h" 7 | #include "macoro/detail/stop_state.h" 8 | 9 | #include 10 | 11 | macoro::stop_source::stop_source() 12 | : m_state(detail::stop_state::create()) 13 | { 14 | } 15 | 16 | macoro::stop_source::stop_source(const stop_source& other) noexcept 17 | : m_state(other.m_state) 18 | { 19 | if (m_state != nullptr) 20 | { 21 | m_state->add_source_ref(); 22 | } 23 | } 24 | 25 | macoro::stop_source::stop_source(stop_source&& other) noexcept 26 | : m_state(other.m_state) 27 | { 28 | other.m_state = nullptr; 29 | } 30 | 31 | macoro::stop_source::~stop_source() 32 | { 33 | if (m_state != nullptr) 34 | { 35 | m_state->release_source_ref(); 36 | } 37 | } 38 | 39 | macoro::stop_source& macoro::stop_source::operator=(const stop_source& other) noexcept 40 | { 41 | if (m_state != other.m_state) 42 | { 43 | if (m_state != nullptr) 44 | { 45 | m_state->release_source_ref(); 46 | } 47 | 48 | m_state = other.m_state; 49 | 50 | if (m_state != nullptr) 51 | { 52 | m_state->add_source_ref(); 53 | } 54 | } 55 | 56 | return *this; 57 | } 58 | 59 | macoro::stop_source& macoro::stop_source::operator=(stop_source&& other) noexcept 60 | { 61 | if (this != &other) 62 | { 63 | if (m_state != nullptr) 64 | { 65 | m_state->release_source_ref(); 66 | } 67 | 68 | m_state = other.m_state; 69 | other.m_state = nullptr; 70 | } 71 | 72 | return *this; 73 | } 74 | 75 | bool macoro::stop_source::stop_possible() const noexcept 76 | { 77 | return m_state != nullptr; 78 | } 79 | 80 | macoro::stop_token macoro::stop_source::get_token() const noexcept 81 | { 82 | return stop_token(m_state); 83 | } 84 | 85 | void macoro::stop_source::request_stop() 86 | { 87 | if (m_state != nullptr) 88 | { 89 | m_state->request_stop(); 90 | } 91 | } 92 | 93 | bool macoro::stop_source::stop_requested() const noexcept 94 | { 95 | return m_state != nullptr && m_state->stop_requested(); 96 | } 97 | -------------------------------------------------------------------------------- /macoro/detail/stop_source.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | #include "macoro/config.h" 7 | #ifndef MACORO_HAS_STD_STOP_TOKEN 8 | 9 | namespace macoro 10 | { 11 | class stop_token; 12 | 13 | namespace detail 14 | { 15 | class stop_state; 16 | } 17 | 18 | class stop_source 19 | { 20 | public: 21 | 22 | /// Construct to a new cancellation source. 23 | stop_source(); 24 | 25 | /// Create a new reference to the same underlying cancellation 26 | /// source as \p other. 27 | stop_source(const stop_source& other) noexcept; 28 | 29 | stop_source(stop_source&& other) noexcept; 30 | 31 | ~stop_source(); 32 | 33 | stop_source& operator=(const stop_source& other) noexcept; 34 | 35 | stop_source& operator=(stop_source&& other) noexcept; 36 | 37 | /// Query if this cancellation source can be cancelled. 38 | /// 39 | /// A cancellation source object will not be cancellable if it has 40 | /// previously been moved into another stop_source instance 41 | /// or was copied from a stop_source that was not cancellable. 42 | bool stop_possible() const noexcept; 43 | 44 | /// Obtain a cancellation token that can be used to query if 45 | /// cancellation has been requested on this source. 46 | /// 47 | /// The cancellation token can be passed into functions that you 48 | /// may want to later be able to request cancellation. 49 | stop_token get_token() const noexcept; 50 | 51 | /// Request cancellation of operations that were passed an associated 52 | /// cancellation token. 53 | /// 54 | /// Any cancellation callback registered via a stop_callback 55 | /// object will be called inside this function by the first thread to 56 | /// call this method. 57 | /// 58 | /// This operation is a no-op if stop_possible() returns false. 59 | void request_stop(); 60 | 61 | /// Query if some thread has called 'request_stop()' on this 62 | /// stop_source. 63 | bool stop_requested() const noexcept; 64 | 65 | private: 66 | 67 | detail::stop_state* m_state; 68 | 69 | }; 70 | } 71 | 72 | #endif -------------------------------------------------------------------------------- /macoro/detail/stop_state.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | #include "macoro/config.h" 7 | #ifndef MACORO_HAS_STD_STOP_TOKEN 8 | 9 | #include "macoro/detail/stop_token.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace macoro 16 | { 17 | namespace detail 18 | { 19 | struct stop_callback_state; 20 | 21 | class stop_state 22 | { 23 | public: 24 | 25 | /// Allocates a new stop_state object. 26 | /// 27 | /// \throw std::bad_alloc 28 | /// If there was insufficient memory to allocate one. 29 | static stop_state* create(); 30 | 31 | ~stop_state(); 32 | 33 | /// Increment the reference count of stop_token and 34 | /// stop_callback objects referencing this state. 35 | void add_token_ref() noexcept; 36 | 37 | /// Decrement the reference count of stop_token and 38 | /// stop_callback objects referencing this state. 39 | void release_token_ref() noexcept; 40 | 41 | /// Increment the reference count of stop_source objects. 42 | void add_source_ref() noexcept; 43 | 44 | /// Decrement the reference count of cancellation_souce objects. 45 | /// 46 | /// The stop_state will no longer be cancellable once the 47 | /// stop_source ref count reaches zero. 48 | void release_source_ref() noexcept; 49 | 50 | /// Query if the stop_state can have cancellation requested. 51 | /// 52 | /// \return 53 | /// Returns true if there are no more references to a stop_source 54 | /// object. 55 | bool stop_possible() const noexcept; 56 | 57 | /// Query if some thread has called request_stop(). 58 | bool stop_requested() const noexcept; 59 | 60 | /// Flag state has having cancellation_requested and execute any 61 | /// registered callbacks. 62 | void request_stop(); 63 | 64 | /// Try to register the stop_callback as a callback to be executed 65 | /// when cancellation is requested. 66 | /// 67 | /// \return 68 | /// true if the callback was successfully registered, false if the callback was 69 | /// not registered because cancellation had already been requested. 70 | /// 71 | /// \throw std::bad_alloc 72 | /// If callback was unable to be registered due to insufficient memory. 73 | bool try_register_callback(stop_callback* registration); 74 | 75 | /// Deregister a callback previously registered successfully in a call to try_register_callback(). 76 | /// 77 | /// If the callback is currently being executed on another 78 | /// thread that is concurrently calling request_stop() 79 | /// then this call will block until the callback has finished executing. 80 | void deregister_callback(stop_callback* registration) noexcept; 81 | 82 | private: 83 | 84 | stop_state() noexcept; 85 | 86 | bool is_cancellation_notification_complete() const noexcept; 87 | 88 | static constexpr std::uint64_t cancellation_requested_flag = 1; 89 | static constexpr std::uint64_t cancellation_notification_complete_flag = 2; 90 | static constexpr std::uint64_t stop_source_ref_increment = 4; 91 | static constexpr std::uint64_t stop_token_ref_increment = UINT64_C(1) << 33; 92 | static constexpr std::uint64_t stop_possible_mask = stop_token_ref_increment - 1; 93 | static constexpr std::uint64_t cancellation_ref_count_mask = 94 | ~(cancellation_requested_flag | cancellation_notification_complete_flag); 95 | 96 | // A value that has: 97 | // - bit 0 - indicates whether cancellation has been requested. 98 | // - bit 1 - indicates whether cancellation notification is complete. 99 | // - bits 2-32 - ref-count for stop_source instances. 100 | // - bits 33-63 - ref-count for stop_token/stop_callback instances. 101 | std::atomic m_state; 102 | 103 | std::atomic m_registrationState; 104 | 105 | }; 106 | } 107 | 108 | } 109 | 110 | #endif -------------------------------------------------------------------------------- /macoro/detail/stop_token.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "stop_token.h" 7 | #include "operation_cancelled.h" 8 | 9 | #include "stop_state.h" 10 | 11 | #include 12 | #include 13 | 14 | macoro::stop_token::stop_token() noexcept 15 | : m_state(nullptr) 16 | { 17 | } 18 | 19 | macoro::stop_token::stop_token(const stop_token& other) noexcept 20 | : m_state(other.m_state) 21 | { 22 | if (m_state != nullptr) 23 | { 24 | m_state->add_token_ref(); 25 | } 26 | } 27 | 28 | macoro::stop_token::stop_token(stop_token&& other) noexcept 29 | : m_state(other.m_state) 30 | { 31 | other.m_state = nullptr; 32 | } 33 | 34 | macoro::stop_token::~stop_token() 35 | { 36 | if (m_state != nullptr) 37 | { 38 | m_state->release_token_ref(); 39 | } 40 | } 41 | 42 | macoro::stop_token& macoro::stop_token::operator=(const stop_token& other) noexcept 43 | { 44 | if (other.m_state != m_state) 45 | { 46 | if (m_state != nullptr) 47 | { 48 | m_state->release_token_ref(); 49 | } 50 | 51 | m_state = other.m_state; 52 | 53 | if (m_state != nullptr) 54 | { 55 | m_state->add_token_ref(); 56 | } 57 | } 58 | 59 | return *this; 60 | } 61 | 62 | macoro::stop_token& macoro::stop_token::operator=(stop_token&& other) noexcept 63 | { 64 | if (this != &other) 65 | { 66 | if (m_state != nullptr) 67 | { 68 | m_state->release_token_ref(); 69 | } 70 | 71 | m_state = other.m_state; 72 | other.m_state = nullptr; 73 | } 74 | 75 | return *this; 76 | } 77 | 78 | void macoro::stop_token::swap(stop_token& other) noexcept 79 | { 80 | std::swap(m_state, other.m_state); 81 | } 82 | 83 | bool macoro::stop_token::stop_possible() const noexcept 84 | { 85 | return m_state != nullptr && m_state->stop_possible(); 86 | } 87 | 88 | bool macoro::stop_token::stop_requested() const noexcept 89 | { 90 | return m_state != nullptr && m_state->stop_requested(); 91 | } 92 | 93 | void macoro::stop_token::throw_if_stop_requested() const 94 | { 95 | if (stop_requested()) 96 | { 97 | throw operation_cancelled{}; 98 | } 99 | } 100 | 101 | macoro::stop_token::stop_token(detail::stop_state* state) noexcept 102 | : m_state(state) 103 | { 104 | if (m_state != nullptr) 105 | { 106 | m_state->add_token_ref(); 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /macoro/detail/stop_token.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | #include "macoro/config.h" 7 | #ifndef MACORO_HAS_STD_STOP_TOKEN 8 | 9 | namespace macoro 10 | { 11 | class stop_source; 12 | class stop_callback; 13 | 14 | namespace detail 15 | { 16 | class stop_state; 17 | } 18 | 19 | class stop_token 20 | { 21 | public: 22 | 23 | /// Construct to a cancellation token that can't be cancelled. 24 | stop_token() noexcept; 25 | 26 | /// Copy another cancellation token. 27 | /// 28 | /// New token will refer to the same underlying state. 29 | stop_token(const stop_token& other) noexcept; 30 | 31 | stop_token(stop_token&& other) noexcept; 32 | 33 | ~stop_token(); 34 | 35 | stop_token& operator=(const stop_token& other) noexcept; 36 | 37 | stop_token& operator=(stop_token&& other) noexcept; 38 | 39 | void swap(stop_token& other) noexcept; 40 | 41 | /// Query if it is possible that this operation will be cancelled 42 | /// or not. 43 | /// 44 | /// Cancellable operations may be able to take more efficient code-paths 45 | /// if they don't need to handle cancellation requests. 46 | bool stop_possible() const noexcept; 47 | 48 | /// Query if some thread has requested cancellation on an associated 49 | /// stop_source object. 50 | bool stop_requested() const noexcept; 51 | 52 | /// Throws macoro::operation_cancelled exception if cancellation 53 | /// has been requested for the associated operation. 54 | void throw_if_stop_requested() const; 55 | 56 | 57 | private: 58 | 59 | friend class stop_source; 60 | 61 | //template 62 | friend class stop_callback; 63 | 64 | stop_token(detail::stop_state* state) noexcept; 65 | 66 | detail::stop_state* m_state; 67 | 68 | }; 69 | 70 | inline void swap(stop_token& a, stop_token& b) noexcept 71 | { 72 | a.swap(b); 73 | } 74 | } 75 | 76 | #endif -------------------------------------------------------------------------------- /macoro/detail/when_all_awaitable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /////////////////////////////////////////////////////////////////////////////// 3 | // Copyright (c) Lewis Baker 4 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 5 | /////////////////////////////////////////////////////////////////////////////// 6 | 7 | #include "macoro/type_traits.h" 8 | #include 9 | #include 10 | #include "macoro/coroutine_handle.h" 11 | #include "when_all_counter.h" 12 | #include "macoro/trace.h" 13 | 14 | #ifdef MACORO_CPP_20 15 | #include 16 | #endif 17 | 18 | namespace macoro 19 | { 20 | namespace detail 21 | { 22 | 23 | 24 | class when_all_ready_awaitable_base : public traceable 25 | { 26 | public: 27 | 28 | when_all_ready_awaitable_base(std::size_t count) noexcept 29 | : m_count(count + 1) 30 | , m_awaitingCoroutine(nullptr) 31 | {} 32 | 33 | bool is_ready() const noexcept 34 | { 35 | // We consider this complete if we're asking whether it's ready 36 | // after a coroutine has already been registered. 37 | return static_cast(m_awaitingCoroutine); 38 | } 39 | 40 | bool try_await(coroutine_handle<> awaitingCoroutine) noexcept 41 | { 42 | m_awaitingCoroutine = awaitingCoroutine; 43 | return m_count.fetch_sub(1, std::memory_order_acq_rel) > 1; 44 | } 45 | 46 | void notify_awaitable_completed() noexcept 47 | { 48 | if (m_count.fetch_sub(1, std::memory_order_acq_rel) == 1) 49 | { 50 | m_awaitingCoroutine.resume(); 51 | } 52 | } 53 | 54 | protected: 55 | 56 | std::atomic m_count; 57 | coroutine_handle<> m_awaitingCoroutine; 58 | 59 | }; 60 | 61 | template struct is_tuple : std::false_type {}; 62 | 63 | template struct is_tuple> : std::true_type {}; 64 | 65 | template 66 | class when_all_ready_awaitable; 67 | 68 | template<> 69 | class when_all_ready_awaitable> 70 | { 71 | public: 72 | 73 | constexpr when_all_ready_awaitable() noexcept {} 74 | explicit constexpr when_all_ready_awaitable(std::tuple<>) noexcept {} 75 | 76 | constexpr bool await_ready() const noexcept { return true; } 77 | void await_suspend(coroutine_handle<>) noexcept {} 78 | #ifdef MACORO_CPP_20 79 | void await_suspend(std::coroutine_handle<>) noexcept {} 80 | #endif 81 | std::tuple<> await_resume() const noexcept { return {}; } 82 | 83 | }; 84 | 85 | // the when_all_read awaitable that holds all the tasks. 86 | // TASK_CONTAINER should be either an std::tuple or vector like 87 | // container that contains when_all_task. 88 | template 89 | class when_all_ready_awaitable final : public when_all_ready_awaitable_base 90 | { 91 | public: 92 | 93 | size_t tasks_size(TASK_CONTAINER& tasks) 94 | { 95 | if constexpr (is_tuple::value) 96 | return std::tuple_size::value; 97 | else 98 | return tasks.size(); 99 | } 100 | 101 | 102 | explicit when_all_ready_awaitable(TASK_CONTAINER&& tasks) noexcept 103 | : when_all_ready_awaitable_base(tasks_size(tasks)) 104 | , m_tasks(std::forward(tasks)) 105 | {} 106 | 107 | when_all_ready_awaitable(when_all_ready_awaitable&& other) 108 | noexcept(std::is_nothrow_move_constructible::value) 109 | : when_all_ready_awaitable_base(tasks_size(other.m_tasks)) 110 | , m_tasks(std::move(other.m_tasks)) 111 | {} 112 | 113 | when_all_ready_awaitable(const when_all_ready_awaitable&) = delete; 114 | when_all_ready_awaitable& operator=(const when_all_ready_awaitable&) = delete; 115 | 116 | class lval_awaiter 117 | { 118 | public: 119 | 120 | lval_awaiter(when_all_ready_awaitable& awaitable) 121 | : m_awaitable(awaitable) 122 | {} 123 | 124 | bool await_ready() const noexcept 125 | { 126 | return m_awaitable.is_ready(); 127 | } 128 | 129 | #ifdef MACORO_CPP_20 130 | template 131 | bool await_suspend(std::coroutine_handle awaitingCoroutine, std::source_location loc = std::source_location::current()) noexcept 132 | { 133 | m_awaitable.set_parent(get_traceable(awaitingCoroutine), loc); 134 | return await_suspend(coroutine_handle<>(awaitingCoroutine), loc); 135 | } 136 | #endif 137 | template 138 | bool await_suspend(coroutine_handle awaitingCoroutine, std::source_location loc = std::source_location::current()) noexcept 139 | { 140 | m_awaitable.set_parent(get_traceable(awaitingCoroutine), loc); 141 | return m_awaitable.try_await(awaitingCoroutine, loc); 142 | } 143 | 144 | TASK_CONTAINER& await_resume() noexcept 145 | { 146 | return m_awaitable.m_tasks; 147 | } 148 | 149 | private: 150 | 151 | when_all_ready_awaitable& m_awaitable; 152 | 153 | }; 154 | 155 | auto MACORO_OPERATOR_COAWAIT() & noexcept 156 | { 157 | return lval_awaiter{ *this }; 158 | } 159 | 160 | 161 | class rval_awaiter 162 | { 163 | public: 164 | 165 | rval_awaiter(when_all_ready_awaitable& awaitable) 166 | : m_awaitable(awaitable) 167 | {} 168 | 169 | bool await_ready() const noexcept 170 | { 171 | return m_awaitable.is_ready(); 172 | } 173 | 174 | #ifdef MACORO_CPP_20 175 | template 176 | bool await_suspend(std::coroutine_handle awaitingCoroutine, std::source_location loc = std::source_location::current()) noexcept 177 | { 178 | m_awaitable.set_parent(get_traceable(awaitingCoroutine), loc); 179 | return m_awaitable.try_await(coroutine_handle<>(awaitingCoroutine)); 180 | 181 | //return await_suspend(coroutine_handle<>(awaitingCoroutine), loc); 182 | } 183 | #endif 184 | template 185 | bool await_suspend(coroutine_handle awaitingCoroutine, std::source_location loc = std::source_location::current()) noexcept 186 | { 187 | m_awaitable.set_parent(get_traceable(awaitingCoroutine), loc); 188 | return m_awaitable.try_await(awaitingCoroutine); 189 | } 190 | 191 | TASK_CONTAINER&& await_resume() noexcept 192 | { 193 | return std::move(m_awaitable.m_tasks); 194 | } 195 | 196 | private: 197 | 198 | when_all_ready_awaitable& m_awaitable; 199 | 200 | }; 201 | 202 | auto MACORO_OPERATOR_COAWAIT() && noexcept 203 | { 204 | return rval_awaiter{ *this }; 205 | } 206 | 207 | 208 | 209 | void add_child(traceable* child) final override 210 | { 211 | assert(0); 212 | } 213 | 214 | void remove_child(traceable* child) final override 215 | { 216 | assert(0); 217 | } 218 | 219 | //private: 220 | 221 | //bool is_ready() const noexcept 222 | //{ 223 | // return m_counter.is_ready(); 224 | //} 225 | 226 | template 227 | void start_tasks(std::integer_sequence) noexcept 228 | { 229 | if constexpr (is_tuple::value) 230 | { 231 | (void)std::initializer_list{ 232 | (std::get(m_tasks).start(*this), 0)... 233 | }; 234 | } 235 | } 236 | 237 | bool try_await(coroutine_handle<> awaitingCoroutine) noexcept 238 | { 239 | if constexpr (is_tuple::value) 240 | { 241 | start_tasks( 242 | std::make_integer_sequence::value>{}); 243 | } 244 | else 245 | { 246 | for (auto&& task : m_tasks) 247 | { 248 | task.start(*this); 249 | } 250 | } 251 | 252 | return when_all_ready_awaitable_base::try_await(awaitingCoroutine); 253 | } 254 | 255 | TASK_CONTAINER m_tasks; 256 | 257 | }; 258 | } 259 | } 260 | 261 | -------------------------------------------------------------------------------- /macoro/detail/when_all_counter.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | 7 | #include "macoro/coroutine_handle.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace macoro 13 | { 14 | namespace detail 15 | { 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /macoro/detail/when_all_task.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | 7 | 8 | #include "macoro/coroutine_handle.h" 9 | #include "when_all_awaitable.h" 10 | 11 | #include "macoro/type_traits.h" 12 | #include "macoro/macros.h" 13 | #include "macoro/trace.h" 14 | 15 | #include 16 | #include 17 | 18 | #ifdef MACORO_CPP_20 19 | #define MACORO_CPP_20_WHEN_ALL 1 20 | #endif 21 | 22 | namespace macoro 23 | { 24 | namespace detail 25 | { 26 | template 27 | class when_all_ready_awaitable; 28 | 29 | template 30 | class when_all_task; 31 | 32 | template 33 | class when_all_task_promise final 34 | { 35 | public: 36 | #ifdef MACORO_CPP_20 37 | using std_coroutine_handle_t = std::coroutine_handle>; 38 | #endif 39 | using coroutine_handle_t = coroutine_handle>; 40 | 41 | when_all_task_promise() noexcept 42 | {} 43 | 44 | auto get_return_object() noexcept 45 | { 46 | m_type = coroutine_handle_type::std; 47 | return coroutine_handle_t::from_promise(*this, m_type); 48 | } 49 | auto macoro_get_return_object() noexcept 50 | { 51 | m_type = coroutine_handle_type::macoro; 52 | return coroutine_handle_t::from_promise(*this, m_type); 53 | } 54 | 55 | suspend_always initial_suspend() noexcept 56 | { 57 | return{}; 58 | } 59 | 60 | auto final_suspend() noexcept 61 | { 62 | class completion_notifier 63 | { 64 | public: 65 | 66 | bool await_ready() const noexcept { return false; } 67 | #ifdef MACORO_CPP_20 68 | void await_suspend(std_coroutine_handle_t coro) const noexcept 69 | { 70 | await_suspend(coroutine_handle_t(coro)); 71 | } 72 | #endif 73 | 74 | void await_suspend(coroutine_handle_t coro) const noexcept 75 | { 76 | coro.promise().m_awaitable->notify_awaitable_completed(); 77 | } 78 | 79 | void await_resume() const noexcept {} 80 | 81 | }; 82 | 83 | return completion_notifier{}; 84 | } 85 | 86 | void unhandled_exception() noexcept 87 | { 88 | m_exception = std::current_exception(); 89 | } 90 | 91 | void return_void() noexcept 92 | { 93 | // We should have either suspended at co_yield point or 94 | // an exception was thrown before running off the end of 95 | // the coroutine. 96 | assert(false); 97 | } 98 | 99 | auto yield_value(RESULT&& result) noexcept 100 | { 101 | m_result = std::addressof(result); 102 | return final_suspend(); 103 | } 104 | 105 | void start(when_all_ready_awaitable_base& awaitable) noexcept 106 | { 107 | m_awaitable = &awaitable; 108 | coroutine_handle_t::from_promise(*this, m_type).resume(); 109 | } 110 | 111 | RESULT& result()& 112 | { 113 | rethrow_if_exception(); 114 | return *m_result; 115 | } 116 | 117 | RESULT&& result()&& 118 | { 119 | rethrow_if_exception(); 120 | return std::forward(*m_result); 121 | } 122 | 123 | 124 | //private: 125 | 126 | void rethrow_if_exception() 127 | { 128 | if (m_exception) 129 | { 130 | std::rethrow_exception(m_exception); 131 | } 132 | } 133 | 134 | traceable* get_traceable() 135 | { 136 | return m_awaitable; 137 | } 138 | 139 | coroutine_handle_type m_type; 140 | when_all_ready_awaitable_base* m_awaitable = nullptr; 141 | std::exception_ptr m_exception; 142 | std::add_pointer_t m_result; 143 | 144 | }; 145 | 146 | template<> 147 | class when_all_task_promise final 148 | { 149 | public: 150 | #ifdef MACORO_CPP_20 151 | using std_coroutine_handle_t = std::coroutine_handle>; 152 | #endif 153 | using coroutine_handle_t = coroutine_handle>; 154 | 155 | when_all_task_promise() noexcept 156 | {} 157 | 158 | auto get_return_object() noexcept 159 | { 160 | m_type = coroutine_handle_type::std; 161 | return coroutine_handle_t::from_promise(*this, m_type); 162 | } 163 | auto macoro_get_return_object() noexcept 164 | { 165 | m_type = coroutine_handle_type::macoro; 166 | return coroutine_handle_t::from_promise(*this, m_type); 167 | } 168 | 169 | suspend_always initial_suspend() noexcept 170 | { 171 | return{}; 172 | } 173 | 174 | auto final_suspend() noexcept 175 | { 176 | class completion_notifier 177 | { 178 | public: 179 | 180 | bool await_ready() const noexcept { return false; } 181 | #ifdef MACORO_CPP_20 182 | void await_suspend(std_coroutine_handle_t coro) const noexcept 183 | { 184 | coro.promise().m_awaitable->notify_awaitable_completed(); 185 | } 186 | #endif 187 | 188 | void await_suspend(coroutine_handle_t coro) const noexcept 189 | { 190 | coro.promise().m_awaitable->notify_awaitable_completed(); 191 | } 192 | 193 | void await_resume() const noexcept {} 194 | 195 | }; 196 | 197 | return completion_notifier{}; 198 | } 199 | 200 | void unhandled_exception() noexcept 201 | { 202 | m_exception = std::current_exception(); 203 | } 204 | 205 | void return_void() noexcept 206 | { 207 | } 208 | 209 | void start(when_all_ready_awaitable_base& awaitable) noexcept 210 | { 211 | m_awaitable = &awaitable; 212 | coroutine_handle_t::from_promise(*this, m_type).resume(); 213 | } 214 | 215 | void result() 216 | { 217 | if (m_exception) 218 | { 219 | std::rethrow_exception(m_exception); 220 | } 221 | } 222 | 223 | //private: 224 | 225 | when_all_ready_awaitable_base* m_awaitable; 226 | coroutine_handle_type m_type; 227 | //when_all_counter* m_counter; 228 | std::exception_ptr m_exception; 229 | 230 | }; 231 | 232 | template 233 | class when_all_task final 234 | { 235 | public: 236 | 237 | using promise_type = when_all_task_promise; 238 | #ifdef MACORO_CPP_20 239 | using std_coroutine_handle_t = typename promise_type::std_coroutine_handle_t; 240 | #endif 241 | using coroutine_handle_t = typename promise_type::coroutine_handle_t; 242 | 243 | when_all_task(coroutine_handle_t coroutine) noexcept 244 | : m_coroutine(coroutine) 245 | {} 246 | #ifdef MACORO_CPP_20 247 | when_all_task(std_coroutine_handle_t coroutine) noexcept 248 | : m_coroutine(coroutine_handle_t(coroutine)) 249 | {} 250 | #endif 251 | 252 | when_all_task(when_all_task&& other) noexcept 253 | : m_coroutine(std::exchange(other.m_coroutine, coroutine_handle_t{})) 254 | {} 255 | 256 | ~when_all_task() 257 | { 258 | if (m_coroutine) m_coroutine.destroy(); 259 | } 260 | 261 | when_all_task(const when_all_task&) = delete; 262 | when_all_task& operator=(const when_all_task&) = delete; 263 | 264 | decltype(auto) result()& 265 | { 266 | return m_coroutine.promise().result(); 267 | } 268 | 269 | decltype(auto) result()&& 270 | { 271 | return std::move(m_coroutine.promise()).result(); 272 | } 273 | 274 | //private: 275 | 276 | template 277 | friend class when_all_ready_awaitable; 278 | 279 | void start(when_all_ready_awaitable_base& awaiter) noexcept 280 | { 281 | m_coroutine.promise().start(awaiter); 282 | } 283 | 284 | coroutine_handle_t m_coroutine; 285 | 286 | }; 287 | 288 | #ifdef MACORO_CPP_20_WHEN_ALL 289 | template< 290 | typename AWAITABLE> 291 | when_all_task::await_result_t> make_when_all_task(AWAITABLE awaitable) 292 | { 293 | static_assert(std::is_rvalue_reference_v == false, "awaitable must be an value or lvalue reference"); 294 | using RESULT = typename awaitable_traits::await_result_t; 295 | if constexpr(std::is_lvalue_reference_v) 296 | { 297 | if constexpr (!std::is_void::value) 298 | { 299 | co_yield co_await awaitable; 300 | } 301 | else 302 | { 303 | co_await awaitable; 304 | } 305 | } 306 | else 307 | { 308 | if constexpr (!std::is_void::value) 309 | { 310 | co_yield co_await static_cast(awaitable); 311 | } 312 | else 313 | { 314 | co_await static_cast(awaitable); 315 | } 316 | } 317 | } 318 | 319 | #else 320 | template< 321 | typename AWAITABLE, 322 | typename RESULT = typename awaitable_traits::await_result_t, 323 | enable_if_t::value, int> = 0> 324 | when_all_task make_when_all_task(AWAITABLE a) 325 | { 326 | MC_BEGIN(when_all_task, awaitable = std::move(a)); 327 | MC_YIELD_AWAIT(static_cast(awaitable)); 328 | MC_END(); 329 | } 330 | 331 | template< 332 | typename AWAITABLE, 333 | typename RESULT = typename awaitable_traits::await_result_t, 334 | enable_if_t::value, int> = 0> 335 | when_all_task make_when_all_task(AWAITABLE a) 336 | { 337 | MC_BEGIN(when_all_task, awaitable = std::move(a)); 338 | MC_AWAIT(static_cast(awaitable)); 339 | MC_END(); 340 | } 341 | 342 | template< 343 | typename AWAITABLE, 344 | typename RESULT = typename awaitable_traits::await_result_t, 345 | enable_if_t::value, int> = 0> 346 | when_all_task make_when_all_task(std::reference_wrapper awaitable) 347 | { 348 | 349 | MC_BEGIN(when_all_task, awaitable); 350 | MC_YIELD_AWAIT(awaitable.get()); 351 | MC_END(); 352 | //co_yield co_await awaitable.get(); 353 | } 354 | 355 | template< 356 | typename AWAITABLE, 357 | typename RESULT = typename awaitable_traits::await_result_t, 358 | enable_if_t::value, int> = 0> 359 | when_all_task make_when_all_task(std::reference_wrapper awaitable) 360 | { 361 | MC_BEGIN(when_all_task, awaitable); 362 | MC_AWAIT(awaitable.get()); 363 | MC_END(); 364 | //co_await awaitable.get(); 365 | } 366 | #endif 367 | 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /macoro/detail/win32.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include "macoro/detail/win32.h" 7 | 8 | #ifndef WIN32_LEAN_AND_MEAN 9 | # define WIN32_LEAN_AND_MEAN 10 | #endif 11 | #include 12 | 13 | #pragma comment(lib, "ws2_32.lib") 14 | void macoro::detail::win32::safe_handle::close() noexcept 15 | { 16 | if (m_handle != nullptr && m_handle != INVALID_HANDLE_VALUE) 17 | { 18 | ::CloseHandle(m_handle); 19 | m_handle = nullptr; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /macoro/detail/win32.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | 7 | #include "macoro/config.h" 8 | 9 | #if !MACORO_WINDOWS_OS 10 | # error "macoro/detail/win32.h" is only supported on the Windows platform. 11 | #endif 12 | 13 | #include 14 | #include 15 | 16 | struct _OVERLAPPED; 17 | 18 | namespace macoro 19 | { 20 | namespace detail 21 | { 22 | namespace win32 23 | { 24 | using handle_t = void*; 25 | using ulongptr_t = std::uintptr_t; 26 | using longptr_t = std::intptr_t; 27 | using dword_t = unsigned long; 28 | using socket_t = std::uintptr_t; 29 | using ulong_t = unsigned long; 30 | 31 | #if MACORO_WINDOWS_OS 32 | # pragma warning(push) 33 | # pragma warning(disable : 4201) // Non-standard anonymous struct/union 34 | #endif 35 | 36 | /// Structure needs to correspond exactly to the builtin 37 | /// _OVERLAPPED structure from Windows.h. 38 | struct overlapped 39 | { 40 | ulongptr_t Internal; 41 | ulongptr_t InternalHigh; 42 | union 43 | { 44 | struct 45 | { 46 | dword_t Offset; 47 | dword_t OffsetHigh; 48 | }; 49 | void* Pointer; 50 | }; 51 | handle_t hEvent; 52 | }; 53 | 54 | #if MACORO_WINDOWS_OS 55 | # pragma warning(pop) 56 | #endif 57 | 58 | struct wsabuf 59 | { 60 | constexpr wsabuf() noexcept 61 | : len(0) 62 | , buf(nullptr) 63 | {} 64 | 65 | constexpr wsabuf(void* ptr, std::size_t size) 66 | : len(size <= ulong_t(-1) ? ulong_t(size) : ulong_t(-1)) 67 | , buf(static_cast(ptr)) 68 | {} 69 | 70 | ulong_t len; 71 | char* buf; 72 | }; 73 | 74 | struct io_state : win32::overlapped 75 | { 76 | using callback_type = void( 77 | io_state* state, 78 | win32::dword_t errorCode, 79 | win32::dword_t numberOfBytesTransferred, 80 | win32::ulongptr_t completionKey); 81 | 82 | io_state(callback_type* callback = nullptr) noexcept 83 | : io_state(std::uint64_t(0), callback) 84 | {} 85 | 86 | io_state(void* pointer, callback_type* callback) noexcept 87 | : m_callback(callback) 88 | { 89 | this->Internal = 0; 90 | this->InternalHigh = 0; 91 | this->Pointer = pointer; 92 | this->hEvent = nullptr; 93 | } 94 | 95 | io_state(std::uint64_t offset, callback_type* callback) noexcept 96 | : m_callback(callback) 97 | { 98 | this->Internal = 0; 99 | this->InternalHigh = 0; 100 | this->Offset = static_cast(offset); 101 | this->OffsetHigh = static_cast(offset >> 32); 102 | this->hEvent = nullptr; 103 | } 104 | 105 | callback_type* m_callback; 106 | }; 107 | 108 | class safe_handle 109 | { 110 | public: 111 | 112 | safe_handle() 113 | : m_handle(nullptr) 114 | {} 115 | 116 | explicit safe_handle(handle_t handle) 117 | : m_handle(handle) 118 | {} 119 | 120 | safe_handle(const safe_handle& other) = delete; 121 | 122 | safe_handle(safe_handle&& other) noexcept 123 | : m_handle(other.m_handle) 124 | { 125 | other.m_handle = nullptr; 126 | } 127 | 128 | ~safe_handle() 129 | { 130 | close(); 131 | } 132 | 133 | safe_handle& operator=(safe_handle handle) noexcept 134 | { 135 | swap(handle); 136 | return *this; 137 | } 138 | 139 | constexpr handle_t handle() const { return m_handle; } 140 | 141 | /// Calls CloseHandle() and sets the handle to NULL. 142 | void close() noexcept; 143 | 144 | void swap(safe_handle& other) noexcept 145 | { 146 | std::swap(m_handle, other.m_handle); 147 | } 148 | 149 | bool operator==(const safe_handle& other) const 150 | { 151 | return m_handle == other.m_handle; 152 | } 153 | 154 | bool operator!=(const safe_handle& other) const 155 | { 156 | return m_handle != other.m_handle; 157 | } 158 | 159 | bool operator==(handle_t handle) const 160 | { 161 | return m_handle == handle; 162 | } 163 | 164 | bool operator!=(handle_t handle) const 165 | { 166 | return m_handle != handle; 167 | } 168 | 169 | private: 170 | 171 | handle_t m_handle; 172 | 173 | }; 174 | } 175 | } 176 | } 177 | 178 | -------------------------------------------------------------------------------- /macoro/error_code.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace macoro 6 | { 7 | using error_code = std::error_code; 8 | 9 | 10 | } -------------------------------------------------------------------------------- /macoro/inline_scheduler.h: -------------------------------------------------------------------------------- 1 | 2 | #include "macoro/coroutine_handle.h" 3 | 4 | namespace macoro 5 | { 6 | struct inline_scheduler 7 | { 8 | suspend_never schedule() 9 | { 10 | return { }; 11 | } 12 | }; 13 | } -------------------------------------------------------------------------------- /macoro/macros_undef.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef MACORO_HANDLE_TYPE 4 | #undef MACORO_HANDLE_TYPE 5 | #endif 6 | 7 | #ifdef MC_BEGIN 8 | #undef MC_BEGIN 9 | #endif 10 | 11 | #ifdef IMPL_MC_AWAIT 12 | #undef IMPL_MC_AWAIT 13 | #endif 14 | 15 | #ifdef MC_RETURN_VOID 16 | #undef MC_RETURN_VOID 17 | #endif // MC_RETURN_VOID 18 | 19 | #ifdef MC_RETURN 20 | #undef MC_RETURN 21 | #endif // MC_RETURN 22 | 23 | #ifdef MC_AWAIT 24 | #undef MC_AWAIT 25 | #endif // MC_AWAIT 26 | #ifdef MC_AWAIT_SET 27 | #undef MC_AWAIT_SET 28 | #endif // MC_AWAIT_SET 29 | #ifdef MC_AWAIT_RETURN 30 | #undef MC_AWAIT_RETURN 31 | #endif // MC_AWAIT_RETURN 32 | 33 | #ifdef MC_YIELD 34 | #undef MC_YIELD 35 | #endif // MC_YIELD 36 | #ifdef MC_YIELD_SET 37 | #undef MC_YIELD_SET 38 | #endif // MC_YIELD_SET 39 | #ifdef MC_YIELD_RETURN 40 | #undef MC_YIELD_RETURN 41 | #endif // MC_YIELD_RETURN 42 | 43 | #ifdef MC_END 44 | #undef MC_END 45 | #endif // MC_END 46 | 47 | -------------------------------------------------------------------------------- /macoro/manual_reset_event.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED 6 | #define CPPCORO_ASYNC_MANUAL_RESET_EVENT_HPP_INCLUDED 7 | 8 | #include "macoro/coroutine_handle.h" 9 | #include 10 | #include 11 | 12 | namespace macoro 13 | { 14 | class async_manual_reset_event_operation; 15 | 16 | /// An async manual-reset event is a coroutine synchronisation abstraction 17 | /// that allows one or more coroutines to wait until some thread calls 18 | /// set() on the event. 19 | /// 20 | /// When a coroutine awaits a 'set' event the coroutine continues without 21 | /// suspending. Otherwise, if it awaits a 'not set' event the coroutine is 22 | /// suspended and is later resumed inside the call to 'set()'. 23 | /// 24 | /// \seealso async_auto_reset_event 25 | class async_manual_reset_event 26 | { 27 | public: 28 | 29 | /// Initialise the event to either 'set' or 'not set' state. 30 | /// 31 | /// \param initiallySet 32 | /// If 'true' then initialises the event to the 'set' state, otherwise 33 | /// initialises the event to the 'not set' state. 34 | async_manual_reset_event(bool initiallySet = false) noexcept; 35 | 36 | ~async_manual_reset_event(); 37 | 38 | /// Wait for the event to enter the 'set' state. 39 | /// 40 | /// If the event is already 'set' then the coroutine continues without 41 | /// suspending. 42 | /// 43 | /// Otherwise, the coroutine is suspended and later resumed when some 44 | /// thread calls 'set()'. The coroutine will be resumed inside the next 45 | /// call to 'set()'. 46 | async_manual_reset_event_operation MACORO_OPERATOR_COAWAIT() const noexcept; 47 | 48 | /// Query if the event is currently in the 'set' state. 49 | bool is_set() const noexcept; 50 | 51 | /// Set the state of the event to 'set'. 52 | /// 53 | /// If there are pending coroutines awaiting the event then all 54 | /// pending coroutines are resumed within this call. 55 | /// Any coroutines that subsequently await the event will continue 56 | /// without suspending. 57 | /// 58 | /// This operation is a no-op if the event was already 'set'. 59 | void set() noexcept; 60 | 61 | /// Set the state of the event to 'not-set'. 62 | /// 63 | /// Any coroutines that subsequently await the event will suspend 64 | /// until some thread calls 'set()'. 65 | /// 66 | /// This is a no-op if the state was already 'not set'. 67 | void reset() noexcept; 68 | 69 | private: 70 | 71 | friend class async_manual_reset_event_operation; 72 | 73 | // This variable has 3 states: 74 | // - this - The state is 'set'. 75 | // - nullptr - The state is 'not set' with no waiters. 76 | // - other - The state is 'not set'. 77 | // Points to an 'async_manual_reset_event_operation' that is 78 | // the head of a linked-list of waiters. 79 | mutable std::atomic m_state; 80 | 81 | }; 82 | 83 | class async_manual_reset_event_operation 84 | { 85 | public: 86 | 87 | explicit async_manual_reset_event_operation(const async_manual_reset_event& event) noexcept; 88 | 89 | bool await_ready() const noexcept; 90 | bool await_suspend(coroutine_handle<> awaiter) noexcept; 91 | bool await_suspend(std::coroutine_handle<> awaiter) noexcept; 92 | void await_resume() const noexcept {} 93 | 94 | private: 95 | 96 | friend class async_manual_reset_event; 97 | 98 | const async_manual_reset_event& m_event; 99 | async_manual_reset_event_operation* m_next; 100 | coroutine_handle<> m_awaiter; 101 | 102 | }; 103 | 104 | 105 | 106 | inline async_manual_reset_event::async_manual_reset_event(bool initiallySet) noexcept 107 | : m_state(initiallySet ? static_cast(this) : nullptr) 108 | {} 109 | 110 | inline async_manual_reset_event::~async_manual_reset_event() 111 | { 112 | // There should be no coroutines still awaiting the event. 113 | assert( 114 | m_state.load(std::memory_order_relaxed) == nullptr || 115 | m_state.load(std::memory_order_relaxed) == static_cast(this)); 116 | } 117 | 118 | inline bool async_manual_reset_event::is_set() const noexcept 119 | { 120 | return m_state.load(std::memory_order_acquire) == static_cast(this); 121 | } 122 | 123 | inline async_manual_reset_event_operation 124 | async_manual_reset_event::MACORO_OPERATOR_COAWAIT() const noexcept 125 | { 126 | return async_manual_reset_event_operation{ *this }; 127 | } 128 | 129 | inline void async_manual_reset_event::set() noexcept 130 | { 131 | void* const setState = static_cast(this); 132 | 133 | // Needs 'release' semantics so that prior writes are visible to event awaiters 134 | // that synchronise either via 'is_set()' or 'operator co_await()'. 135 | // Needs 'acquire' semantics in case there are any waiters so that we see 136 | // prior writes to the waiting coroutine's state and to the contents of 137 | // the queued async_manual_reset_event_operation objects. 138 | void* oldState = m_state.exchange(setState, std::memory_order_acq_rel); 139 | if (oldState != setState) 140 | { 141 | auto* current = static_cast(oldState); 142 | while (current != nullptr) 143 | { 144 | auto* next = current->m_next; 145 | current->m_awaiter.resume(); 146 | current = next; 147 | } 148 | } 149 | } 150 | 151 | inline void async_manual_reset_event::reset() noexcept 152 | { 153 | void* oldState = static_cast(this); 154 | m_state.compare_exchange_strong(oldState, nullptr, std::memory_order_relaxed); 155 | } 156 | 157 | inline async_manual_reset_event_operation::async_manual_reset_event_operation( 158 | const async_manual_reset_event& event) noexcept 159 | : m_event(event) 160 | { 161 | } 162 | 163 | inline bool async_manual_reset_event_operation::await_ready() const noexcept 164 | { 165 | return m_event.is_set(); 166 | } 167 | 168 | inline bool async_manual_reset_event_operation::await_suspend( 169 | coroutine_handle<> awaiter) noexcept 170 | { 171 | m_awaiter = awaiter; 172 | 173 | const void* const setState = static_cast(&m_event); 174 | 175 | void* oldState = m_event.m_state.load(std::memory_order_acquire); 176 | do 177 | { 178 | if (oldState == setState) 179 | { 180 | // State is now 'set' no need to suspend. 181 | return false; 182 | } 183 | 184 | m_next = static_cast(oldState); 185 | } while (!m_event.m_state.compare_exchange_weak( 186 | oldState, 187 | static_cast(this), 188 | std::memory_order_release, 189 | std::memory_order_acquire)); 190 | 191 | // Successfully queued this waiter to the list. 192 | return true; 193 | } 194 | 195 | 196 | inline bool async_manual_reset_event_operation::await_suspend( 197 | std::coroutine_handle<> awaiter) noexcept 198 | { 199 | return await_suspend(coroutine_handle<>(awaiter)); 200 | } 201 | } 202 | 203 | #endif 204 | -------------------------------------------------------------------------------- /macoro/optional.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "macoro/config.h" 4 | #ifdef MACORO_OPTIONAL_LITE_V 5 | #ifndef optional_CONFIG_SELECT_OPTIONAL 6 | #define optional_CONFIG_SELECT_OPTIONAL 1 7 | #endif 8 | #include "nonstd/optional.hpp" 9 | #else 10 | #include 11 | #endif 12 | 13 | namespace macoro 14 | { 15 | #ifdef MACORO_OPTIONAL_LITE_V 16 | using nonstd::optional; 17 | using nonstd::nullopt; 18 | #else 19 | using std::optional; 20 | using std::nullopt; 21 | #endif 22 | } -------------------------------------------------------------------------------- /macoro/sequence_range.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | 7 | #include "macoro/sequence_traits.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace macoro 13 | { 14 | template> 15 | class sequence_range 16 | { 17 | public: 18 | 19 | using value_type = SEQUENCE; 20 | using difference_type = typename TRAITS::difference_type; 21 | using size_type = typename TRAITS::size_type; 22 | 23 | class const_iterator 24 | { 25 | public: 26 | 27 | using iterator_category = std::random_access_iterator_tag; 28 | using value_type = SEQUENCE; 29 | using difference_type = typename TRAITS::difference_type; 30 | using reference = const SEQUENCE&; 31 | using pointer = const SEQUENCE*; 32 | 33 | explicit constexpr const_iterator(SEQUENCE value) noexcept : m_value(value) {} 34 | 35 | const SEQUENCE& operator*() const noexcept { return m_value; } 36 | const SEQUENCE* operator->() const noexcept { return std::addressof(m_value); } 37 | 38 | const_iterator& operator++() noexcept { ++m_value; return *this; } 39 | const_iterator& operator--() noexcept { --m_value; return *this; } 40 | 41 | const_iterator operator++(int) noexcept { return const_iterator(m_value++); } 42 | const_iterator operator--(int) noexcept { return const_iterator(m_value--); } 43 | 44 | constexpr difference_type operator-(const_iterator other) const noexcept { return TRAITS::difference(m_value, other.m_value); } 45 | constexpr const_iterator operator-(difference_type delta) const noexcept { return const_iterator{ static_cast(m_value - delta) }; } 46 | constexpr const_iterator operator+(difference_type delta) const noexcept { return const_iterator{ static_cast(m_value + delta) }; } 47 | 48 | constexpr bool operator==(const_iterator other) const noexcept { return m_value == other.m_value; } 49 | constexpr bool operator!=(const_iterator other) const noexcept { return m_value != other.m_value; } 50 | 51 | private: 52 | 53 | SEQUENCE m_value; 54 | 55 | }; 56 | 57 | constexpr sequence_range() noexcept 58 | : m_begin() 59 | , m_end() 60 | {} 61 | 62 | constexpr sequence_range(SEQUENCE begin, SEQUENCE end) noexcept 63 | : m_begin(begin) 64 | , m_end(end) 65 | {} 66 | 67 | constexpr const_iterator begin() const noexcept { return const_iterator(m_begin); } 68 | constexpr const_iterator end() const noexcept { return const_iterator(m_end); } 69 | 70 | constexpr SEQUENCE front() const noexcept { return m_begin; } 71 | constexpr SEQUENCE back() const noexcept { return m_end - 1; } 72 | 73 | constexpr size_type size() const noexcept 74 | { 75 | return static_cast(TRAITS::difference(m_end, m_begin)); 76 | } 77 | 78 | constexpr bool empty() const noexcept 79 | { 80 | return m_begin == m_end; 81 | } 82 | 83 | constexpr SEQUENCE operator[](size_type index) const noexcept 84 | { 85 | return m_begin + index; 86 | } 87 | 88 | constexpr sequence_range first(size_type count) const noexcept 89 | { 90 | return sequence_range{ m_begin, static_cast(m_begin + std::min(size(), count)) }; 91 | } 92 | 93 | constexpr sequence_range skip(size_type count) const noexcept 94 | { 95 | return sequence_range{ m_begin + std::min(size(), count), m_end }; 96 | } 97 | 98 | private: 99 | 100 | SEQUENCE m_begin; 101 | SEQUENCE m_end; 102 | 103 | }; 104 | } 105 | 106 | -------------------------------------------------------------------------------- /macoro/sequence_spsc.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #pragma once 6 | 7 | #include "macoro/config.h" 8 | #include "macoro/sequence_barrier.h" 9 | #include "macoro/sequence_range.h" 10 | 11 | namespace macoro 12 | { 13 | template 14 | class sequence_spsc_claim_one_operation; 15 | 16 | template 17 | class sequence_spsc_claim_operation; 18 | 19 | template< 20 | typename SEQUENCE = std::size_t, 21 | typename TRAITS = sequence_traits> 22 | class sequence_spsc 23 | { 24 | public: 25 | static constexpr bool multi_sender = false; 26 | using size_type = typename sequence_range::size_type; 27 | 28 | sequence_spsc( 29 | const sequence_barrier& consumerBarrier, 30 | std::size_t bufferSize, 31 | SEQUENCE initialSequence = TRAITS::initial_sequence) noexcept 32 | : m_consumerBarrier(consumerBarrier) 33 | , m_bufferSize(bufferSize) 34 | , m_nextToClaim(initialSequence + 1) 35 | , m_producerBarrier(initialSequence) 36 | {} 37 | 38 | /// Claim a slot in the ring buffer asynchronously. 39 | /// 40 | /// \return 41 | /// Returns an operation that when awaited will suspend the coroutine until 42 | /// a slot is available for writing in the ring buffer. The result of the 43 | /// co_await expression will be the sequence number of the slot. 44 | /// The caller must publish() the claimed sequence number once they have written to 45 | /// the ring-buffer. 46 | [[nodiscard]] 47 | sequence_spsc_claim_one_operation 48 | claim_one() noexcept; 49 | 50 | /// Claim one or more contiguous slots in the ring-buffer. 51 | /// 52 | /// Use this method over many calls to claim_one() when you have multiple elements to 53 | /// enqueue. This will claim as many slots as are available up to the specified count 54 | /// but may claim as few as one slot if only one slot is available. 55 | /// 56 | /// \param count 57 | /// The maximum number of slots to claim. 58 | /// 59 | /// \return 60 | /// Returns an awaitable object that when awaited returns a sequence_range that contains 61 | /// the range of sequence numbers that were claimed. Once you have written element values 62 | /// to all of the claimed slots you must publish() the sequence range in order to make 63 | /// the elements available to consumers. 64 | [[nodiscard]] 65 | sequence_spsc_claim_operation claim_up_to( 66 | std::size_t count) noexcept; 67 | 68 | /// Publish the specified sequence number. 69 | /// 70 | /// This also implies that all prior sequence numbers have already been published. 71 | void publish(SEQUENCE sequence) noexcept 72 | { 73 | m_producerBarrier.publish(sequence); 74 | } 75 | 76 | /// Publish a contiguous range of sequence numbers. 77 | /// 78 | /// You must have already published all prior sequence numbers. 79 | /// 80 | /// This is equivalent to just publishing the last sequence number in the range. 81 | void publish(const sequence_range& sequences) noexcept 82 | { 83 | m_producerBarrier.publish(sequences.back()); 84 | } 85 | 86 | /// Query what the last-published sequence number is. 87 | /// 88 | /// You can assume that all prior sequence numbers are also published. 89 | SEQUENCE last_published() const noexcept 90 | { 91 | return m_producerBarrier.last_published(); 92 | } 93 | 94 | /// Asynchronously wait until the specified sequence number is published. 95 | /// 96 | /// \param targetSequence 97 | /// The sequence number to wait for. 98 | /// 99 | /// \return 100 | /// Returns an Awaitable type that, when awaited, will suspend the awaiting coroutine until the 101 | /// specified sequence number has been published. 102 | /// 103 | /// The result of the 'co_await barrier.wait_until_published(seq)' expression will be the 104 | /// last-published sequence number, which is guaranteed to be at least 'seq' but may be some 105 | /// subsequent sequence number if additional items were published while waiting for the 106 | /// the requested sequence number to be published. 107 | [[nodiscard]] 108 | auto wait_until_published(SEQUENCE targetSequence) const noexcept 109 | { 110 | return m_producerBarrier.wait_until_published(targetSequence); 111 | } 112 | 113 | [[nodiscard]] 114 | auto wait_until_published(SEQUENCE targetSequence, SEQUENCE lastKnown) const noexcept 115 | { 116 | return wait_until_published(targetSequence); 117 | } 118 | 119 | 120 | private: 121 | 122 | template 123 | friend class sequence_spsc_claim_operation; 124 | 125 | template 126 | friend class sequence_spsc_claim_one_awaiter; 127 | 128 | #if _MSC_VER 129 | # pragma warning(push) 130 | # pragma warning(disable : 4324) // C4324: structure was padded due to alignment specifier 131 | #endif 132 | 133 | const sequence_barrier& m_consumerBarrier; 134 | const std::size_t m_bufferSize; 135 | 136 | alignas(MACORO_CPU_CACHE_LINE) 137 | SEQUENCE m_nextToClaim; 138 | 139 | sequence_barrier m_producerBarrier; 140 | 141 | #if _MSC_VER 142 | # pragma warning(pop) 143 | #endif 144 | }; 145 | 146 | 147 | template 148 | class sequence_spsc_claim_one_awaiter 149 | { 150 | public: 151 | 152 | sequence_spsc_claim_one_awaiter( 153 | sequence_spsc& sequencer) noexcept 154 | : m_consumerWaitOperation( 155 | sequencer.m_consumerBarrier, 156 | static_cast(sequencer.m_nextToClaim - sequencer.m_bufferSize)) 157 | , m_sequencer(sequencer) 158 | {} 159 | 160 | 161 | bool await_ready() const noexcept 162 | { 163 | return m_consumerWaitOperation.await_ready(); 164 | } 165 | 166 | #ifdef MACORO_CPP_20 167 | auto await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept 168 | { 169 | return await_suspend(coroutine_handle<>(awaitingCoroutine)); 170 | } 171 | #endif 172 | auto await_suspend(coroutine_handle<> awaitingCoroutine) noexcept 173 | { 174 | return m_consumerWaitOperation.await_suspend(awaitingCoroutine); 175 | } 176 | 177 | SEQUENCE await_resume() const noexcept 178 | { 179 | return m_sequencer.m_nextToClaim++; 180 | } 181 | 182 | private: 183 | 184 | sequence_barrier_wait_operation_base m_consumerWaitOperation; 185 | sequence_spsc& m_sequencer; 186 | 187 | }; 188 | 189 | template 190 | class sequence_spsc_claim_one_operation 191 | { 192 | public: 193 | 194 | sequence_spsc_claim_one_operation( 195 | sequence_spsc& sequencer) noexcept 196 | : m_sequencer(sequencer) 197 | {} 198 | 199 | 200 | sequence_spsc_claim_one_awaiter MACORO_OPERATOR_COAWAIT() noexcept 201 | { 202 | return m_sequencer; 203 | } 204 | 205 | private: 206 | sequence_spsc& m_sequencer; 207 | }; 208 | 209 | template 210 | class sequence_spsc_claim_operation 211 | { 212 | public: 213 | 214 | explicit sequence_spsc_claim_operation( 215 | sequence_spsc& sequencer, 216 | std::size_t count) noexcept 217 | : m_consumerWaitOperation( 218 | sequencer.m_consumerBarrier, 219 | static_cast(sequencer.m_nextToClaim - sequencer.m_bufferSize)) 220 | , m_sequencer(sequencer) 221 | , m_count(count) 222 | {} 223 | 224 | bool await_ready() const noexcept 225 | { 226 | return m_consumerWaitOperation.await_ready(); 227 | } 228 | 229 | #ifdef MACORO_CPP_20 230 | auto await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept 231 | { 232 | return await_suspend(coroutine_handle<>(awaitingCoroutine)); 233 | } 234 | #endif 235 | auto await_suspend(coroutine_handle<> awaitingCoroutine) noexcept 236 | { 237 | return m_consumerWaitOperation.await_suspend(awaitingCoroutine); 238 | } 239 | 240 | sequence_range await_resume() noexcept 241 | { 242 | const SEQUENCE lastAvailableSequence = 243 | static_cast(m_consumerWaitOperation.await_resume() + m_sequencer.m_bufferSize); 244 | const SEQUENCE begin = m_sequencer.m_nextToClaim; 245 | const std::size_t availableCount = static_cast(lastAvailableSequence - begin) + 1; 246 | const std::size_t countToClaim = std::min(m_count, availableCount); 247 | const SEQUENCE end = static_cast(begin + countToClaim); 248 | m_sequencer.m_nextToClaim = end; 249 | return sequence_range(begin, end); 250 | } 251 | 252 | private: 253 | 254 | sequence_barrier_wait_operation_base m_consumerWaitOperation; 255 | sequence_spsc& m_sequencer; 256 | std::size_t m_count; 257 | 258 | }; 259 | 260 | template 261 | [[nodiscard]] 262 | sequence_spsc_claim_one_operation 263 | sequence_spsc::claim_one() noexcept 264 | { 265 | return sequence_spsc_claim_one_operation{ *this }; 266 | } 267 | 268 | template 269 | [[nodiscard]] 270 | sequence_spsc_claim_operation 271 | sequence_spsc::claim_up_to(std::size_t count) noexcept 272 | { 273 | return sequence_spsc_claim_operation(*this, count); 274 | } 275 | } 276 | 277 | -------------------------------------------------------------------------------- /macoro/sequence_traits.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | #ifndef CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED 6 | #define CPPCORO_SEQUENCE_TRAITS_HPP_INCLUDED 7 | 8 | #include 9 | 10 | namespace macoro 11 | { 12 | template 13 | struct sequence_traits 14 | { 15 | using value_type = SEQUENCE; 16 | using difference_type = std::make_signed_t; 17 | using size_type = std::make_unsigned_t; 18 | 19 | static constexpr value_type initial_sequence = static_cast(-1); 20 | 21 | static constexpr difference_type difference(value_type a, value_type b) 22 | { 23 | return static_cast(a - b); 24 | } 25 | 26 | static constexpr bool precedes(value_type a, value_type b) 27 | { 28 | return difference(a, b) < 0; 29 | } 30 | }; 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /macoro/start_on.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "macoro/task.h" 3 | #include "macoro/type_traits.h" 4 | #include "macoro/transfer_to.h" 5 | 6 | namespace macoro 7 | { 8 | template 9 | auto start_on(scheduler& s, awaitable& a) 10 | -> eager_task>> 11 | { 12 | co_await transfer_to(s); 13 | co_return co_await a; 14 | } 15 | template 16 | auto start_on(scheduler& s, awaitable a) 17 | -> eager_task>> 18 | { 19 | co_await transfer_to(s); 20 | co_return co_await std::move(a); 21 | } 22 | 23 | template 24 | struct start_on_t 25 | { 26 | template 27 | start_on_t(S&& s) 28 | : scheduler(std::forward(s)) 29 | {} 30 | 31 | Scheduler scheduler; 32 | }; 33 | 34 | template 35 | start_on_t start_on(scheduler& s) 36 | { 37 | return start_on_t{ s }; 38 | } 39 | 40 | template 41 | start_on_t start_on(scheduler&& s) 42 | { 43 | return start_on_t{ s }; 44 | } 45 | 46 | template 47 | decltype(auto) operator|(awaitable&& a, start_on_t s) 48 | { 49 | return start_on(s.scheduler, std::forward(a)); 50 | } 51 | 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /macoro/stop.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include "macoro/coroutine_handle.h" 7 | #ifdef MACORO_HAS_STD_STOP_TOKEN 8 | #include 9 | #else 10 | #include "macoro/detail/stop_callback.h" 11 | #include "macoro/detail/stop_source.h" 12 | #include "macoro/detail/stop_state.h" 13 | #include "macoro/detail/stop_token.h" 14 | #endif 15 | 16 | namespace macoro 17 | { 18 | #ifdef MACORO_HAS_STD_STOP_TOKEN 19 | using stop_token = std::stop_token; 20 | using stop_source = std::stop_source; 21 | using stop_callback = std::stop_callback>; 22 | #endif 23 | 24 | using optional_stop_callback = std::optional; 25 | 26 | class stop_awaiter 27 | { 28 | public: 29 | stop_awaiter(stop_token&& t) 30 | :mtoken(std::move(t)) 31 | {} 32 | stop_awaiter() = default; 33 | stop_awaiter(const stop_awaiter&) = delete; 34 | stop_awaiter(stop_awaiter&&) = delete; 35 | 36 | stop_token mtoken; 37 | optional_stop_callback mReg; 38 | bool await_ready() { return false; } 39 | 40 | std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) 41 | { 42 | return await_suspend(coroutine_handle<>(h)).std_cast(); 43 | } 44 | coroutine_handle<> await_suspend(coroutine_handle<> h) 45 | { 46 | if (mtoken.stop_possible() && !mtoken.stop_requested()) 47 | { 48 | mReg.emplace(mtoken, [h] { 49 | h.resume(); 50 | }); 51 | 52 | return noop_coroutine(); 53 | } 54 | return h; 55 | } 56 | void await_resume() {} 57 | }; 58 | 59 | 60 | inline stop_awaiter MACORO_OPERATOR_COAWAIT(stop_token t) 61 | { 62 | return std::move(t); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /macoro/sync_wait.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "macoro/type_traits.h" 4 | #include "macoro/coro_frame.h" 5 | #include "macoro/optional.h" 6 | #include "macoro/coroutine_handle.h" 7 | #include 8 | #include "macros.h" 9 | #include 10 | 11 | namespace macoro 12 | { 13 | 14 | #ifdef MACORO_CPP_20 15 | #define MACORO_MAKE_BLOCKING_20 1 16 | #endif 17 | 18 | template 19 | struct blocking_task; 20 | 21 | namespace detail 22 | { 23 | 24 | 25 | template 26 | struct blocking_promise : basic_traceable 27 | { 28 | 29 | // can be used to check if it allocates 30 | //void* operator new(std::size_t n) noexcept 31 | //{ 32 | // return std::malloc(n); 33 | //} 34 | //void operator delete(void* ptr, std::size_t sz) 35 | //{ 36 | // std::free(ptr); 37 | //} 38 | 39 | // construct the awaiter in the promise 40 | template 41 | blocking_promise(A&& a, std::source_location& loc) 42 | : m_awaiter(get_awaiter(std::forward(a))) 43 | { 44 | //std::cout << "blocking_promise" << std::endl; 45 | set_parent(nullptr, loc); 46 | } 47 | 48 | //~blocking_promise() 49 | //{ 50 | // std::cout << "~blocking_promise " << (size_t)this << std::endl; 51 | //} 52 | 53 | using inner_awaiter = decltype(get_awaiter(std::declval())); 54 | 55 | // the promise will hold the awaiter. this way when 56 | // we return the value, you can directly get it 57 | // by calling m_awaiter.await_resume() 58 | inner_awaiter m_awaiter; 59 | 60 | // any active exceptions that is thrown. 61 | std::exception_ptr m_exception; 62 | 63 | // mutex and cv to block until the caller until the result is ready. 64 | std::mutex m_mutex; 65 | std::condition_variable m_cv; 66 | bool m_is_set = false; 67 | 68 | // block the caller. 69 | void wait() 70 | { 71 | std::unique_lock lock(m_mutex); 72 | m_cv.wait(lock, [this] { return m_is_set; }); 73 | } 74 | 75 | // notify the caller. 76 | void set() 77 | { 78 | assert(m_is_set == false); 79 | std::lock_guard lock(this->m_mutex); 80 | this->m_is_set = true; 81 | this->m_cv.notify_all(); 82 | } 83 | 84 | void return_void() {} 85 | 86 | struct final_awaiter 87 | { 88 | bool await_ready() noexcept { return false; }; 89 | 90 | template 91 | void await_suspend(C c) noexcept { 92 | c.promise().set(); } 93 | 94 | void await_resume() noexcept {} 95 | }; 96 | 97 | suspend_never initial_suspend() noexcept { return{}; } 98 | final_awaiter final_suspend() noexcept { 99 | return {}; 100 | } 101 | 102 | void unhandled_exception() noexcept 103 | { 104 | m_exception = std::current_exception(); 105 | } 106 | 107 | blocking_task get_return_object() noexcept; 108 | blocking_task macoro_get_return_object() noexcept; 109 | 110 | // intercept the dummy co_await and manually co_await m_awaiter. 111 | auto& await_transform(Awaitable&& a) 112 | { 113 | return *this; 114 | } 115 | 116 | // forward to m_awaiter 117 | bool await_ready() //noexcept(std::declval().await_ready()) 118 | { 119 | return m_awaiter.await_ready(); 120 | } 121 | 122 | // forward to m_awaiter 123 | auto await_suspend(std::coroutine_handle c) //noexcept(macoro::await_suspend(m_awaiter, c)) 124 | { 125 | return m_awaiter.await_suspend(c); 126 | } 127 | 128 | // onces m_awaiter completes, notify the caller that they 129 | // can get the result. this will happen by calling m_awaiter.await_resume(); 130 | void await_resume() { } 131 | 132 | // get the value out of m_awaiter. 133 | decltype(auto) value() 134 | { 135 | if (this->m_exception) 136 | std::rethrow_exception(this->m_exception); 137 | assert(m_is_set); 138 | 139 | return m_awaiter.await_resume(); 140 | } 141 | }; 142 | } 143 | 144 | template 145 | struct blocking_task 146 | { 147 | using promise_type = detail::blocking_promise; 148 | coroutine_handle m_handle; 149 | blocking_task(coroutine_handle h) 150 | : m_handle(h) 151 | {} 152 | 153 | blocking_task() = default; 154 | blocking_task(blocking_task&& other) 155 | : m_handle(std::exchange(other.m_handle, nullptr)) 156 | {} 157 | blocking_task& operator=(blocking_task&& h) { 158 | this->~blocking_task(); 159 | m_handle = std::exchange(h.m_handle, std::nullptr_t{}); 160 | return *this; 161 | } 162 | 163 | ~blocking_task() 164 | { 165 | if (m_handle) 166 | m_handle.destroy(); 167 | } 168 | 169 | decltype(auto) get() 170 | { 171 | assert(m_handle); 172 | m_handle.promise().wait(); 173 | return m_handle.promise().value(); 174 | } 175 | }; 176 | 177 | template 178 | blocking_task detail::blocking_promise::get_return_object() noexcept { return { coroutine_handle::from_promise(*this, coroutine_handle_type::std) }; } 179 | template 180 | blocking_task detail::blocking_promise::macoro_get_return_object() noexcept { return { coroutine_handle::from_promise(*this, coroutine_handle_type::macoro) }; } 181 | 182 | template 183 | auto make_blocking(Awaitable awaitable, std::source_location loc = std::source_location::current()) 184 | -> blocking_task 185 | { 186 | #if MACORO_MAKE_BLOCKING_20 187 | co_await static_cast(awaitable); 188 | #else 189 | MC_BEGIN(blocking_task, &awaitable); 190 | MC_AWAIT(static_cast(awaitable)); 191 | MC_END(); 192 | #endif 193 | } 194 | 195 | 196 | struct make_blocking_t { }; 197 | inline make_blocking_t make_blocking() { return {}; } 198 | 199 | template 200 | decltype(auto) operator|(awaitable&& a, make_blocking_t) 201 | { 202 | return make_blocking(std::forward(a)); 203 | } 204 | 205 | template 206 | decltype(auto) sync_wait(Awaitable&& awaitable, std::source_location loc = std::source_location::current()) 207 | { 208 | if constexpr (std::is_reference_v) 209 | { 210 | return make_blocking&>( 211 | std::forward(awaitable), loc).get(); 212 | } 213 | else 214 | { 215 | return make_blocking&&>( 216 | std::forward(awaitable), loc).get(); 217 | } 218 | } 219 | 220 | struct sync_wait_t { }; 221 | inline sync_wait_t sync_wait() { return {}; } 222 | 223 | 224 | template 225 | decltype(auto) operator|(awaitable&& a, sync_wait_t) 226 | { 227 | return sync_wait(std::forward(a)); 228 | } 229 | } -------------------------------------------------------------------------------- /macoro/take_until.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stop.h" 4 | #include "task.h" 5 | #include 6 | #include "type_traits.h" 7 | #include "result.h" 8 | #include "macoro/macros.h" 9 | #include "macoro/wrap.h" 10 | 11 | namespace macoro 12 | { 13 | 14 | template 15 | void request_stop(T&& t) 16 | { 17 | t.request_stop(); 18 | } 19 | 20 | template 21 | task>> take_until(AWAITABLE awaitable, UNTIL until) 22 | { 23 | using return_type = awaitable_result_t; 24 | constexpr bool is_void = std::is_void>::value; 25 | using storage = std::conditional_t< is_void, int, std::optional>; 26 | storage v; 27 | std::exception_ptr ex; 28 | try { 29 | if constexpr (is_void) 30 | co_await std::move(awaitable); 31 | else 32 | v.emplace(co_await(std::move(awaitable))); 33 | } 34 | catch (...) { 35 | ex = std::current_exception(); 36 | } 37 | 38 | 39 | request_stop(until); 40 | 41 | try { 42 | co_await std::move(until); 43 | } 44 | catch(...) 45 | { } 46 | 47 | if (ex) 48 | std::rethrow_exception(ex); 49 | 50 | if constexpr (is_void == false) 51 | co_return static_cast>(v.value()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /macoro/thread_pool.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_pool.h" 2 | 3 | 4 | 5 | thread_local macoro::detail::thread_pool_state * macoro::detail::thread_pool_state::mCurrentExecutor; -------------------------------------------------------------------------------- /macoro/timeout.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stop.h" 4 | #include "task.h" 5 | #include 6 | #include "type_traits.h" 7 | #include "result.h" 8 | #include "macoro/macros.h" 9 | #include "macoro/wrap.h" 10 | 11 | namespace macoro 12 | { 13 | 14 | struct timeout 15 | { 16 | optional mSrc; 17 | std::unique_ptr mReg; 18 | eager_task<> mTask; 19 | 20 | timeout() = default; 21 | timeout(const timeout&) = delete; 22 | timeout(timeout&& o) 23 | : mSrc(o.mSrc) // required to be copy so that the token can still be used. 24 | , mReg(std::move(o.mReg)) 25 | , mTask(std::move(o.mTask)) 26 | {} 27 | 28 | timeout& operator=(const timeout&) = delete; 29 | timeout& operator=(timeout&& o) { 30 | mSrc = o.mSrc; // required to be copy so that the token can still be used. 31 | mReg = std::move(o.mReg); 32 | mTask = std::move(o.mTask); 33 | return *this; 34 | }; 35 | 36 | 37 | template 38 | timeout(Scheduler& s, 39 | std::chrono::duration d, 40 | stop_token token = {}) 41 | { 42 | reset(s, d, std::move(token)); 43 | } 44 | 45 | 46 | template 47 | void reset(Scheduler& s, 48 | std::chrono::duration d, 49 | stop_token token = {}) 50 | { 51 | mSrc.emplace(); 52 | if (token.stop_possible()) 53 | { 54 | mReg.reset(new stop_callback(token, [src = mSrc.value()]() mutable { 55 | src.request_stop(); 56 | })); 57 | } 58 | 59 | mTask = make_task(s, mSrc.value(), d); 60 | } 61 | 62 | void request_stop() { 63 | mSrc.value().request_stop(); 64 | } 65 | 66 | stop_token get_token() { return mSrc.value().get_token(); } 67 | 68 | stop_source get_source() { return mSrc.value(); } 69 | 70 | auto MACORO_OPERATOR_COAWAIT() 71 | { 72 | return mTask.MACORO_OPERATOR_COAWAIT(); 73 | } 74 | 75 | private: 76 | 77 | template 78 | static eager_task<> make_task(Scheduler& s, 79 | stop_source mSrc, 80 | std::chrono::duration d) 81 | { 82 | try { 83 | 84 | co_await(s.schedule_after(d, mSrc.get_token())); 85 | } 86 | catch (...) { } 87 | mSrc.request_stop(); 88 | 89 | } 90 | }; 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /macoro/todo.txt: -------------------------------------------------------------------------------- 1 | - remove share_ptr from coro_frame 2 | - remove unique_ptr from timer::item 3 | - awaiter no allocation 4 | - fairly scheduled thread_pool delay op vs normal ops? 5 | - only one thread wake up for thread_pool delay ops? 6 | - pipeline trasnfer_to -------------------------------------------------------------------------------- /macoro/trace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include "macoro/config.h" 5 | #include 6 | #include 7 | #include "macoro/coroutine_handle.h" 8 | #include 9 | #include 10 | 11 | namespace macoro 12 | { 13 | struct traceable 14 | { 15 | // where in the parent this frame was started. 16 | std::source_location m_location; 17 | 18 | // the caller. Can be null. We will set this value 19 | traceable* m_parent = nullptr; 20 | 21 | void set_parent(traceable* parent, const std::source_location& l) 22 | { 23 | m_location = l; 24 | m_parent = parent; 25 | } 26 | 27 | virtual void get_call_stack(std::vector& stack) 28 | { 29 | stack.push_back(m_location); 30 | if (m_parent) 31 | m_parent->get_call_stack(stack); 32 | } 33 | 34 | 35 | virtual void add_child(traceable* child) = 0; 36 | virtual void remove_child(traceable* child) = 0; 37 | }; 38 | 39 | // a traceable that can only have one parent and child. 40 | // This is not thread safe. 41 | struct basic_traceable : public traceable 42 | { 43 | traceable* m_child = nullptr; 44 | 45 | void get_call_stack(std::vector& stack) final override 46 | { 47 | stack.push_back(m_location); 48 | if (m_parent) 49 | m_parent->get_call_stack(stack); 50 | } 51 | 52 | void add_child(traceable* child) final override 53 | { 54 | assert(m_child == nullptr); 55 | m_child = child; 56 | } 57 | 58 | void remove_child(traceable* child) final override 59 | { 60 | assert(m_child == child); 61 | m_child = nullptr; 62 | } 63 | 64 | }; 65 | 66 | 67 | template 68 | concept is_traceable = 69 | requires(T t) { 70 | { t } -> std::convertible_to; 71 | }; 72 | 73 | template 74 | concept has_traceable = 75 | requires(T t) { 76 | { t.m_trace } -> std::convertible_to; 77 | }; 78 | 79 | template 80 | concept has_traceable_fn = 81 | requires(T t) { 82 | { t.get_traceable() } -> std::convertible_to; 83 | }; 84 | 85 | template 86 | concept has_traceable_ptr = 87 | requires(T t) { 88 | { t.m_trace } -> std::convertible_to; 89 | }; 90 | 91 | namespace detail 92 | { 93 | 94 | template 95 | traceable* get_traceable(Promise& p) 96 | { 97 | if constexpr (is_traceable) 98 | return &p; 99 | if constexpr (has_traceable_fn) 100 | return p.get_traceable(); 101 | if constexpr (has_traceable) 102 | return &p.m_trace; 103 | if constexpr (has_traceable_ptr) 104 | return p.m_trace; 105 | else 106 | return nullptr; 107 | } 108 | 109 | template 110 | traceable* get_traceable(std::coroutine_handle p) 111 | { 112 | if constexpr (!std::is_same_v) 113 | return get_traceable(p.promise()); 114 | else 115 | return nullptr; 116 | 117 | } 118 | 119 | template 120 | traceable* get_traceable(coroutine_handle p) 121 | { 122 | if constexpr (!std::is_same_v) 123 | return get_traceable(p.promise()); 124 | else 125 | return nullptr; 126 | 127 | } 128 | } 129 | 130 | 131 | 132 | struct trace { 133 | 134 | trace(std::source_location l, traceable& c) 135 | { 136 | stack.push_back(l); 137 | c.get_call_stack(stack); 138 | } 139 | 140 | std::vector stack; 141 | 142 | std::string str() 143 | { 144 | std::stringstream ss; 145 | for(auto i = 0ull; i < stack.size(); ++i) 146 | { 147 | ss << i << " " << stack[i].file_name() << ":" << stack[i].line() << "\n"; 148 | } 149 | return ss.str(); 150 | } 151 | }; 152 | 153 | struct get_trace { 154 | 155 | get_trace(std::source_location l = std::source_location::current()) 156 | :location(l) 157 | { } 158 | std::source_location location; 159 | 160 | struct awaitable 161 | { 162 | awaitable(trace&& t) 163 | : mTrace(std::move(t)) 164 | {} 165 | 166 | bool await_ready() const noexcept { return true; } 167 | #ifdef MACORO_CPP_20 168 | void await_suspend(std::coroutine_handle<> coro) noexcept 169 | { 170 | assert(0); 171 | } 172 | #endif 173 | 174 | void await_suspend( 175 | coroutine_handle<> coro) noexcept 176 | { 177 | assert(0); 178 | } 179 | 180 | 181 | trace await_resume() noexcept { return mTrace; } 182 | trace mTrace; 183 | }; 184 | }; 185 | //template 186 | //void set_trace(Promise& p, async_stack_frame* a) 187 | //{ 188 | // if constexpr (has_async_stack_frame) 189 | // return p.async_stack_frame = a; 190 | // else 191 | // return; 192 | //} 193 | 194 | //template 195 | //void connect_trace(Promise1& patent, Promise2 child) noexcept 196 | //{ 197 | // set_trace(child, get_trace(patent); 198 | //} 199 | 200 | //thread_local async_stack_frame* current_async_stack_frame = nullptr; 201 | } 202 | -------------------------------------------------------------------------------- /macoro/transfer_to.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace macoro 5 | { 6 | template 7 | auto transfer_to(scheduler& s) 8 | { 9 | return s.schedule(); 10 | } 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /macoro/type_traits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "macoro/config.h" 4 | 5 | namespace macoro 6 | { 7 | 8 | template< class T > 9 | struct remove_cvref { 10 | using type = typename std::remove_cv::type>::type; 11 | }; 12 | 13 | 14 | template< class T > 15 | using remove_cvref_t = typename remove_cvref::type; 16 | 17 | 18 | template 19 | using enable_if_t = typename std::enable_if::type; 20 | 21 | template 22 | struct _void_t_impl 23 | { 24 | typedef void type; 25 | }; 26 | 27 | template 28 | using void_t = typename _void_t_impl::type; 29 | 30 | using false_type = std::false_type; 31 | using true_type = std::true_type; 32 | 33 | template struct conjunction : true_type { }; 34 | template struct conjunction : B1 { }; 35 | template 36 | struct conjunction 37 | : std::conditional, B1>::type {}; 38 | 39 | template 40 | struct has_await_transform_member : false_type 41 | {}; 42 | 43 | template 44 | struct has_await_transform_member ().await_transform(std::declval())) 48 | 49 | >> 50 | : true_type{}; 51 | 52 | template 53 | struct has_member_operator_co_await : false_type 54 | {}; 55 | 56 | template 57 | struct has_member_operator_co_await ().operator co_await()) 62 | #else 63 | // must have a operator_co_await() member fn 64 | decltype(std::declval().operator_co_await()) 65 | #endif 66 | >> 67 | : true_type{}; 68 | 69 | 70 | template 71 | struct has_free_operator_co_await : false_type 72 | {}; 73 | 74 | 75 | template 76 | struct has_free_operator_co_await ())) 80 | #else 81 | // must have a operator_co_await() member fn 82 | decltype(operator_co_await(std::declval())) 83 | #endif 84 | >> 85 | : true_type{}; 86 | 87 | 88 | template 89 | struct has_void_await_suspend : false_type 90 | {}; 91 | 92 | template 93 | struct has_void_await_suspend ().await_suspend(std::declval())), 97 | 98 | // must return void 99 | enable_if_t().await_suspend(std::declval())), 101 | void 102 | >::value, void> 103 | 104 | >> 105 | : true_type{}; 106 | 107 | 108 | template 109 | struct has_bool_await_suspend : false_type 110 | {}; 111 | 112 | template 113 | struct has_bool_await_suspend ().await_suspend(std::declval())), 117 | 118 | // must return bool 119 | enable_if_t().await_suspend(std::declval())), 121 | bool 122 | >::value, void> 123 | 124 | >> 125 | : true_type{}; 126 | 127 | 128 | template 129 | struct has_any_await_suspend : false_type 130 | {}; 131 | 132 | template 133 | struct has_any_await_suspend ().await_suspend(std::declval())) 137 | >> 138 | : true_type{}; 139 | 140 | struct empty_state {}; 141 | 142 | template 143 | inline decltype(auto) get_awaitable( 144 | P& promise, T&& expr, enable_if_t::value, empty_state> m = {}) 145 | { 146 | return promise.await_transform(static_cast(expr)); 147 | } 148 | 149 | template 150 | inline decltype(auto) get_awaitable( 151 | P& promise, T&& expr, enable_if_t::value, empty_state> = {}) 152 | { 153 | return static_cast(expr); 154 | } 155 | 156 | template 157 | inline decltype(auto) get_awaiter( 158 | Awaitable&& awaitable, 159 | enable_if_t< 160 | has_member_operator_co_await::value, empty_state> = {}) 161 | { 162 | #ifdef MACORO_CPP_20 163 | return static_cast(awaitable).operator co_await(); 164 | #else 165 | return static_cast(awaitable).operator_co_await(); 166 | #endif 167 | } 168 | 169 | template 170 | inline decltype(auto) get_awaiter( 171 | Awaitable&& awaitable, 172 | enable_if_t< 173 | !has_member_operator_co_await::value&& 174 | has_free_operator_co_await::value, empty_state> = {}) 175 | { 176 | #ifdef MACORO_CPP_20 177 | return operator co_await(static_cast(awaitable)); 178 | #else 179 | return operator_co_await(static_cast(awaitable)); 180 | #endif 181 | } 182 | 183 | template 184 | inline decltype(auto) get_awaiter( 185 | Awaitable&& awaitable, 186 | enable_if_t< 187 | !has_member_operator_co_await::value && 188 | !has_free_operator_co_await::value, empty_state> = {}) 189 | { 190 | return static_cast(awaitable); 191 | } 192 | 193 | template 194 | struct awaiter_for 195 | { 196 | using expr = Expr; 197 | using awaitable = decltype(get_awaitable(std::declval(), std::declval())); 198 | using awaiter = decltype(get_awaiter(std::declval())); 199 | using type = awaiter; 200 | 201 | using value_type = typename std::remove_reference::type; 202 | using pointer = value_type*; 203 | using reference = value_type&; 204 | }; 205 | 206 | 207 | template> 208 | struct is_awaitable : std::false_type {}; 209 | 210 | template 211 | struct is_awaitable()))>> 212 | : std::true_type 213 | {}; 214 | 215 | template 216 | constexpr bool is_awaitable_v = is_awaitable::value; 217 | 218 | template 219 | struct awaitable_traits 220 | {}; 221 | 222 | //template 223 | //struct awaitable_traits()))>> 224 | //{ 225 | // using awaiter_t = decltype(macoro::detail::get_awaiter(std::declval())); 226 | 227 | // using await_result_t = decltype(std::declval().await_resume()); 228 | //}; 229 | 230 | template 231 | struct awaitable_traits()))>> 232 | { 233 | using awaiter = decltype(get_awaiter(std::declval())); 234 | using await_result = decltype(std::declval().await_resume()); 235 | 236 | using awaiter_t = awaiter; 237 | using await_result_t = await_result; 238 | }; 239 | 240 | template 241 | using awaitable_result_t = typename awaitable_traits::await_result; 242 | 243 | template 244 | struct has_set_continuation_member : false_type 245 | {}; 246 | 247 | template 248 | struct has_set_continuation_member ().promise().set_continuation(std::declval())) 252 | 253 | >> 254 | : true_type{}; 255 | 256 | 257 | template 258 | using remove_reference_t = typename std::remove_reference::type; 259 | 260 | template 261 | using remove_rvalue_reference_t = typename std::conditional::value, 262 | remove_reference_t, 263 | T >::type; 264 | 265 | 266 | template 267 | struct unwrap_reference 268 | { 269 | using type = T; 270 | }; 271 | 272 | template 273 | struct unwrap_reference> 274 | { 275 | using type = T; 276 | }; 277 | 278 | template 279 | using remove_reference_wrapper_t = typename unwrap_reference::type; 280 | 281 | 282 | template 283 | using remove_reference_and_wrapper_t = remove_reference_wrapper_t>; 284 | } -------------------------------------------------------------------------------- /macoro/variant.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "macoro/config.h" 4 | #ifdef MACORO_VARIANT_LITE_V 5 | #ifndef variant_CONFIG_SELECT_VARIANT 6 | #define variant_CONFIG_SELECT_VARIANT 1 7 | #endif 8 | #include "nonstd/variant.hpp" 9 | #else 10 | #include 11 | #endif 12 | 13 | namespace macoro 14 | { 15 | #ifdef MACORO_VARIANT_LITE_V 16 | using nonstd::variant; 17 | using nonstd::in_place_index; 18 | using nonstd::get; 19 | 20 | #define MACORO_VARIANT_NAMESPACE nonstd 21 | 22 | #else 23 | using std::variant; 24 | using std::in_place_index; 25 | using std::get; 26 | #define MACORO_VARIANT_NAMESPACE std 27 | 28 | #endif 29 | } -------------------------------------------------------------------------------- /macoro/when_all.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /////////////////////////////////////////////////////////////////////////////// 3 | // Copyright (c) Lewis Baker 4 | // Licenced under MIT license. See github.com/lewissbaker/cppcoro LICENSE.txt for details. 5 | /////////////////////////////////////////////////////////////////////////////// 6 | 7 | #include "macoro/type_traits.h" 8 | #include 9 | #include 10 | #include "macoro/coroutine_handle.h" 11 | #include "macoro/detail/when_all_awaitable.h" 12 | #include "macoro/detail/when_all_task.h" 13 | #include 14 | 15 | namespace macoro 16 | { 17 | 18 | template 23 | >... 24 | >::value, int> = 0 25 | > 26 | MACORO_NODISCARD 27 | auto when_all_ready(Awaitables&&... awaitables) 28 | { 29 | return detail::when_all_ready_awaitable< 30 | std::tuple< 31 | detail::when_all_task< 32 | awaitable_result_t< 33 | remove_reference_and_wrapper_t 34 | > 35 | >... 36 | > 37 | > 38 | (std::make_tuple( 39 | detail::make_when_all_task>( 40 | std::forward(awaitables) 41 | )... 42 | ) 43 | ); 44 | } 45 | 46 | 47 | 48 | // TODO: Generalise this from vector to arbitrary sequence of awaitable. 49 | 50 | template< 51 | typename AWAITABLE, 52 | typename RESULT = awaitable_result_t>> 53 | MACORO_NODISCARD 54 | auto when_all_ready(std::vector awaitables) 55 | { 56 | std::vector> tasks; 57 | 58 | tasks.reserve(awaitables.size()); 59 | 60 | for (auto& awaitable : awaitables) 61 | { 62 | tasks.emplace_back(detail::make_when_all_task(std::move(awaitable))); 63 | } 64 | 65 | return detail::when_all_ready_awaitable>>( 66 | std::move(tasks)); 67 | } 68 | } -------------------------------------------------------------------------------- /macoro/when_all_scope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "macoro/async_scope.h" 3 | #include "macoro/manual_reset_event.h" 4 | #include "macoro/coroutine_handle.h" 5 | 6 | namespace macoro 7 | { 8 | template 9 | struct scoped_t; 10 | 11 | template<> 12 | struct scoped_t 13 | {}; 14 | 15 | template 16 | struct scoped_t 17 | { 18 | using awaitable_type = awaitable; 19 | using storage_type = remove_rvalue_reference_t; 20 | 21 | storage_type m_awaitable; 22 | 23 | template 24 | scoped_t(awaitable2&& a) 25 | : m_awaitable(std::forward(a)) 26 | {} 27 | }; 28 | 29 | inline scoped_t scoped() { return {}; } 30 | 31 | // wraps an awaitable so that it is started as an eager 32 | // scoped_task. 33 | template 34 | auto scoped(awaitable&& a) 35 | { 36 | return scoped_t(std::forward(a)); 37 | } 38 | 39 | template 40 | decltype(auto) operator|(awaitable&& a, scoped_t) 41 | { 42 | return scoped(std::forward(a)); 43 | } 44 | 45 | // when_all_scope allows the caller to await a scoped(awaitable). 46 | // when awaited, a scoped_task is eagerly started but has its lifetime 47 | // tied to some scoped object when_all_scope. scoped_task can be 48 | // fire and forget but will always be joined then the scope 49 | // object is awaited. For example, 50 | // 51 | // co_await [&]()->when_all_scope{ 52 | // scoped_task t0 = co_await scoped(foo()); 53 | // scoped_task t1 = co_await scoped(foo()); 54 | // ... 55 | // int i = co_await std::move(t0); 56 | // 57 | // // t1 has not been joined. 58 | // }(); 59 | // 60 | // // t1 has been joined 61 | // 62 | // 63 | // If an unjoined scoped_task throws, or an exception is thrown 64 | // in the body, then they are caught, collected, and rethrow in 65 | // an async_scope_exception. 66 | struct when_all_scope 67 | { 68 | struct promise_type 69 | { 70 | promise_type() 71 | { 72 | m_scope.m_barrier.increment(); 73 | } 74 | 75 | struct final_awaitable 76 | { 77 | bool await_ready() noexcept { return false; } 78 | void await_suspend(std::coroutine_handle h) noexcept 79 | { 80 | h.promise().m_scope.m_barrier.decrement(); 81 | } 82 | void await_resume() noexcept {} 83 | }; 84 | 85 | suspend_always initial_suspend() noexcept { return {}; } 86 | final_awaitable final_suspend() noexcept { return {}; } 87 | 88 | when_all_scope get_return_object() noexcept { 89 | return { coroutine_handle::from_promise(*this, macoro::coroutine_handle_type::std) }; 90 | } 91 | 92 | void unhandled_exception() noexcept { 93 | 94 | std::lock_guard lock(m_scope.m_exception_mutex); 95 | m_scope.m_exceptions.push_back(std::current_exception()); 96 | } 97 | 98 | void return_void() noexcept {}; 99 | 100 | template 101 | decltype(auto) await_transform(A&& a) noexcept 102 | { 103 | return std::forward(a); 104 | } 105 | 106 | template 107 | decltype(auto) await_transform(scoped_t&& a, std::source_location loc = std::source_location::current()) noexcept 108 | { 109 | using scoped_task_of_a = decltype(m_scope.add(std::forward::awaitable_type>(a.m_awaitable), loc)); 110 | struct awaiter 111 | { 112 | scoped_task_of_a m_scoped_task; 113 | bool await_ready() noexcept { return true; } 114 | void await_suspend(std::coroutine_handle) noexcept {} 115 | scoped_task_of_a await_resume()noexcept 116 | { 117 | return std::move(m_scoped_task); 118 | } 119 | }; 120 | return awaiter{ m_scope.add(std::forward::awaitable_type>(a.m_awaitable), loc) }; 121 | } 122 | 123 | traceable* get_traceable() 124 | { 125 | return &m_scope; 126 | } 127 | 128 | async_scope m_scope; 129 | }; 130 | 131 | when_all_scope(coroutine_handle h) 132 | : m_handle(h) 133 | {} 134 | 135 | struct awaiter 136 | { 137 | coroutine_handle m_handle; 138 | async_scope::join_awaiter m_join; 139 | 140 | bool await_ready() { return m_join.await_ready(); } 141 | 142 | template 143 | std::coroutine_handle<> await_suspend( 144 | std::coroutine_handle p, 145 | std::source_location loc = std::source_location::current()) 146 | { 147 | m_handle.promise().m_scope.set_parent(get_traceable(p.promise()), loc); 148 | m_handle.resume(); 149 | return m_join.await_suspend(p); 150 | } 151 | 152 | void await_resume() 153 | { 154 | m_join.await_resume(); 155 | } 156 | }; 157 | 158 | auto MACORO_OPERATOR_COAWAIT() const& noexcept 159 | { 160 | return awaiter{ m_handle, m_handle.promise().m_scope.MACORO_OPERATOR_COAWAIT() }; 161 | } 162 | 163 | 164 | coroutine_handle m_handle; 165 | }; 166 | } -------------------------------------------------------------------------------- /macoro/wrap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "task.h" 4 | #include "result.h" 5 | #include "macoro/macros.h" 6 | namespace macoro 7 | { 8 | struct wrap_t 9 | { 10 | }; 11 | 12 | template 13 | struct wrapped_awaitable 14 | { 15 | using traits = awaitable_traits; 16 | using awaitable_storage = remove_rvalue_reference_t; 17 | using awaiter_storage = remove_rvalue_reference_t; 18 | using awaitar_result = remove_rvalue_reference_t; 19 | 20 | struct awaiter 21 | { 22 | 23 | awaiter_storage m_awaiter; 24 | std::source_location m_loc; 25 | 26 | bool await_ready(std::source_location loc = std::source_location::current()) 27 | { 28 | m_loc = loc; 29 | return m_awaiter.await_ready(); 30 | } 31 | 32 | template 33 | auto await_suspend(Handle h) 34 | { 35 | if constexpr (requires(awaiter_storage m_awaiter) { { m_awaiter.await_suspend(h, m_loc) } -> std::convertible_to; }) 36 | { 37 | return m_awaiter.await_suspend(h, m_loc); 38 | } 39 | else 40 | { 41 | return m_awaiter.await_suspend(h); 42 | } 43 | } 44 | 45 | result await_resume() noexcept 46 | { 47 | try 48 | { 49 | if constexpr (std::is_same_v) 50 | { 51 | m_awaiter.await_resume(); 52 | return Ok(); 53 | } 54 | else 55 | { 56 | return Ok(m_awaiter.await_resume()); 57 | } 58 | } 59 | catch (...) 60 | { 61 | return Err(std::current_exception()); 62 | } 63 | } 64 | }; 65 | 66 | //wrapped_awaitable(awaitable&& a) 67 | // : m_awaitable(std::forward(a)) 68 | //{} 69 | 70 | //wrapped_awaitable() = default; 71 | //wrapped_awaitable(const wrapped_awaitable&) = default; 72 | //wrapped_awaitable(wrapped_awaitable&&) = default; 73 | //wrapped_awaitable& operator=(const wrapped_awaitable&) = default; 74 | //wrapped_awaitable& operator=(wrapped_awaitable&&) = default; 75 | 76 | 77 | awaitable_storage m_awaitable; 78 | 79 | awaiter operator co_await()& 80 | { 81 | return awaiter{ get_awaiter(m_awaitable) }; 82 | } 83 | 84 | awaiter operator co_await()&& 85 | { 86 | return awaiter{ get_awaiter(std::forward(m_awaitable)) }; 87 | } 88 | }; 89 | 90 | inline auto wrap() { return wrap_t{}; } 91 | 92 | template 93 | wrapped_awaitable wrap(awaitable&& a) { 94 | return { std::forward(a) }; 95 | } 96 | 97 | template 98 | decltype(auto) operator|(awaitable&& a, wrap_t) 99 | { 100 | return wrap(std::forward(a)); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Macoro - A C++20 coroutine libray 2 | 3 | This library is a subset of [cppcoro](https://github.com/lewissbaker/cppcoro). 4 | 5 | Currently library contains a general purpose `task, eager_tast` types and various helper algorithms such as `when_all` and cancellations. More to come soon. 6 | -------------------------------------------------------------------------------- /tests/CLP.cpp: -------------------------------------------------------------------------------- 1 | #include "CLP.h" 2 | #include 3 | #include 4 | 5 | namespace macoro 6 | { 7 | 8 | void CLP::parse(int argc, char const*const* argv) 9 | { 10 | if (argc > 0) 11 | { 12 | std::stringstream ss; 13 | auto ptr = argv[0]; 14 | while (*ptr != 0) 15 | ss << *ptr++; 16 | mProgramName = ss.str(); 17 | } 18 | 19 | for (int i = 1; i < argc;) 20 | { 21 | mFullStr += std::string(argv[i]) + " "; 22 | 23 | auto ptr = argv[i]; 24 | if (*ptr++ != '-') 25 | { 26 | throw CommandLineParserError("While parsing the argv string, one of the leading terms did not start with a - indicator."); 27 | } 28 | 29 | std::stringstream ss; 30 | 31 | while (*ptr != 0) 32 | ss << *ptr++; 33 | 34 | ++i; 35 | ptr = argv[i]; 36 | 37 | std::pair> keyValues; 38 | keyValues.first = ss.str();; 39 | 40 | while (i < argc && (ptr[0] != '-' || (ptr[0] == '-' && ptr[1] >= '0' && ptr[1] <= '9'))) 41 | { 42 | ss.str(""); 43 | 44 | while (*ptr != 0) 45 | ss << *ptr++; 46 | 47 | keyValues.second.push_back(ss.str()); 48 | 49 | ++i; 50 | ptr = argv[i]; 51 | } 52 | 53 | mKeyValues.emplace(keyValues); 54 | } 55 | } 56 | std::vector split(std::string s, std::string delimiter) 57 | { 58 | std::vector ret; 59 | size_t pos = 0; 60 | while ((pos = s.find(delimiter)) != std::string::npos) { 61 | ret.push_back(s.substr(0, pos)); 62 | s.erase(0, pos + delimiter.length()); 63 | } 64 | ret.push_back(s); 65 | return ret; 66 | } 67 | 68 | void CLP::setDefault(std::string key, std::string value) 69 | { 70 | if (hasValue(key) == false) 71 | { 72 | if (isSet(key)) 73 | { 74 | mKeyValues[key].emplace_back(value); 75 | } 76 | else 77 | { 78 | auto parts = split(value, " "); 79 | mKeyValues.emplace(std::make_pair(key, std::list{ parts.begin(), parts.end()})); 80 | } 81 | } 82 | 83 | } 84 | void CLP::setDefault(std::vector keys, std::string value) 85 | { 86 | if (hasValue(keys) == false) 87 | { 88 | setDefault(keys[0], value); 89 | } 90 | } 91 | 92 | void CLP::set(std::string name) 93 | { 94 | mKeyValues[name]; 95 | } 96 | 97 | bool CLP::isSet(std::string name)const 98 | { 99 | return mKeyValues.find(name) != mKeyValues.end(); 100 | } 101 | bool CLP::isSet(std::vector names)const 102 | { 103 | for (auto name : names) 104 | { 105 | if (isSet(name)) 106 | { 107 | return true; 108 | } 109 | } 110 | return false; 111 | } 112 | 113 | bool CLP::hasValue(std::string name)const 114 | { 115 | return mKeyValues.find(name) != mKeyValues.end() && mKeyValues.at(name).size(); 116 | } 117 | bool CLP::hasValue(std::vector names)const 118 | { 119 | for (auto name : names) 120 | { 121 | if (hasValue(name)) 122 | { 123 | return true; 124 | } 125 | } 126 | return false; 127 | } 128 | 129 | const std::list& CLP::getList(std::vector names) const 130 | { 131 | for (auto name : names) 132 | { 133 | if (isSet(name)) 134 | { 135 | return mKeyValues.find(name)->second; 136 | } 137 | } 138 | throw CommandLineParserError("key not set"); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tests/CLP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace macoro 13 | { 14 | // An error that is thrown when the input isn't of the correct form. 15 | class CommandLineParserError : public std::exception 16 | { 17 | public: 18 | explicit CommandLineParserError(const char* message) : msg_(message) { } 19 | explicit CommandLineParserError(const std::string& message) : msg_(message) {} 20 | virtual ~CommandLineParserError() throw () {} 21 | virtual const char* what() const throw () { return msg_.c_str(); } 22 | protected: 23 | std::string msg_; 24 | }; 25 | 26 | 27 | // Command Line Parser class. 28 | // Expecting the input to be of form 29 | // -key_1 val_1 val_2 -key_2 val_3 val_4 ... 30 | // The values are optional but require a preceeding key denoted by - 31 | class CLP 32 | { 33 | public: 34 | 35 | // Default Constructor 36 | CLP() = default; 37 | 38 | // Parse the provided arguments. 39 | CLP(int argc, char** argv) { parse(argc, argv); } 40 | 41 | // Internal variable denoting the name of the program. 42 | std::string mProgramName; 43 | 44 | std::string mFullStr; 45 | 46 | // The key value store of the parsed arguments. 47 | std::unordered_map> mKeyValues; 48 | 49 | // parse the command line arguments. 50 | void parse(int argc, char const* const* argv); 51 | 52 | // Set the default for the provided key. Keys do not include the leading `-`. 53 | void setDefault(std::string key, std::string value); 54 | 55 | // Set the default for the provided key. Keys do not include the leading `-`. 56 | void setDefault(std::vector keys, std::string value); 57 | 58 | // Set the default for the provided key. Keys do not include the leading `-`. 59 | void setDefault(std::string key, std::int64_t value) { setDefault(key, std::to_string(value)); } 60 | 61 | // Set the default for the provided key. Keys do not include the leading `-`. 62 | void setDefault(std::vector keys, std::int64_t value) { setDefault(keys, std::to_string(value)); } 63 | 64 | // Manually set a flag. 65 | void set(std::string name); 66 | 67 | // Return weather the key was provided on the command line or has a default. 68 | bool isSet(std::string name)const; 69 | 70 | // Return weather the key was provided on the command line or has a default. 71 | bool isSet(std::vector names)const; 72 | 73 | // Return weather the key was provided on the command line has an associated value or has a default. 74 | bool hasValue(std::string name)const; 75 | 76 | // Return weather the key was provided on the command line has an associated value or has a default. 77 | bool hasValue(std::vector names)const; 78 | 79 | // Return the first value associated with the key. 80 | template 81 | T get(const std::string& name)const 82 | { 83 | if (hasValue(name) == false) 84 | throw error({ name }); 85 | 86 | std::stringstream ss; 87 | ss << *mKeyValues.at(name).begin(); 88 | 89 | T ret; 90 | ss >> ret; 91 | 92 | return ret; 93 | } 94 | template 95 | T getOr(const std::string& name, T alternative) const 96 | { 97 | if (hasValue(name)) 98 | return get(name); 99 | 100 | return alternative; 101 | } 102 | 103 | template 104 | T getOr(const std::vector& name, T alternative)const 105 | { 106 | if (hasValue(name)) 107 | return get(name); 108 | 109 | return alternative; 110 | } 111 | 112 | CommandLineParserError error(std::vector names) const 113 | { 114 | if (names.size() == 0) 115 | return CommandLineParserError("No tags provided."); 116 | else 117 | { 118 | std::stringstream ss; 119 | ss << "{ " << names[0]; 120 | for (std::size_t i = 1; i < static_cast(names.size()); ++i) 121 | ss << ", " << names[i]; 122 | ss << " }"; 123 | 124 | return CommandLineParserError("No values were set for tags " + ss.str()); 125 | } 126 | } 127 | 128 | 129 | // Return the first value associated with the key. 130 | template 131 | T get(const std::vector& names, const std::string& failMessage = "")const 132 | { 133 | for (auto name : names) 134 | if (hasValue(name)) 135 | return get(name); 136 | 137 | if (failMessage != "") 138 | std::cout << failMessage << std::endl; 139 | 140 | throw error(names); 141 | 142 | } 143 | 144 | 145 | template 146 | typename std::enable_if::value, std::vector>::type 147 | getManyOr(const std::string& name, std::vectoralt)const 148 | 149 | { 150 | if (isSet(name)) 151 | { 152 | auto& vs = mKeyValues.at(name); 153 | //if(vs.size()) 154 | std::vector ret; ret.reserve(vs.size()); 155 | auto iter = vs.begin(); 156 | T x; 157 | for (std::size_t i = 0; i < vs.size(); ++i) 158 | { 159 | std::stringstream ss(*iter++); 160 | ss >> x; 161 | ret.push_back(x); 162 | char d0 = 0, d1 = 0; 163 | ss >> d0; 164 | ss >> d1; 165 | if (d0 == '.' && d1 == '.') 166 | { 167 | T end; 168 | ss >> end; 169 | 170 | T step = end > x ? 1 : -1; 171 | x += step; 172 | while (x < end) 173 | { 174 | ret.push_back(x); 175 | x += step; 176 | } 177 | } 178 | } 179 | return ret; 180 | } 181 | return alt; 182 | } 183 | 184 | 185 | 186 | // Return the values associated with the key. 187 | template 188 | typename std::enable_if::value, std::vector>::type 189 | getManyOr(const std::string& name, std::vectoralt)const 190 | { 191 | if (isSet(name)) 192 | { 193 | auto& vs = mKeyValues.at(name); 194 | std::vector ret(vs.size()); 195 | auto iter = vs.begin(); 196 | for (std::size_t i = 0; i < ret.size(); ++i) 197 | { 198 | std::stringstream ss(*iter++); 199 | ss >> ret[i]; 200 | } 201 | return ret; 202 | } 203 | return alt; 204 | } 205 | 206 | // Return the values associated with the key. 207 | template 208 | std::vector getMany(const std::string& name)const 209 | { 210 | return getManyOr(name, {}); 211 | } 212 | 213 | 214 | 215 | // Return the values associated with the key. 216 | template 217 | std::vector getMany(const std::vector& names)const 218 | { 219 | for (auto name : names) 220 | if (hasValue(name)) 221 | return getMany(name); 222 | 223 | return {}; 224 | } 225 | 226 | 227 | // Return the values associated with the key. 228 | template 229 | std::vector getMany(const std::vector& names, const std::string& failMessage)const 230 | { 231 | for (auto name : names) 232 | if (hasValue(name)) 233 | return getMany(name); 234 | 235 | if (failMessage != "") 236 | std::cout << failMessage << std::endl; 237 | 238 | throw error(names); 239 | } 240 | 241 | 242 | const std::list& getList(std::vector names) const; 243 | 244 | }; 245 | } 246 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | add_library(macoroTests 5 | "tests.cpp" 6 | "CLP.cpp" 7 | "task_tests.cpp" 8 | "eager_task_tests.cpp" 9 | "await_lifetime_tests.cpp" 10 | "result_tests.cpp" 11 | "when_all_tests.cpp" 12 | "take_until_tests.cpp" 13 | "sequence_tests.cpp" 14 | "CLP.cpp" 15 | "CLP.h" 16 | "channel_spsc_tests.cpp" 17 | "channel_mpsc_tests.cpp" "async_scope_tests.cpp" "async_scope_tests.h") 18 | 19 | target_link_libraries(macoroTests macoro) 20 | 21 | 22 | if(MSVC) 23 | target_compile_options( macoroTests PRIVATE 24 | $<$:/std:c++${MACORO_CPP_VER}> 25 | ) 26 | else() 27 | 28 | 29 | 30 | target_compile_options( macoroTests PRIVATE 31 | $<$:-std=c++${MACORO_CPP_VER}> 32 | ) 33 | endif() 34 | 35 | target_compile_definitions(macoro PUBLIC ${MACORO_COMPILER_DEFINES}) 36 | -------------------------------------------------------------------------------- /tests/async_scope_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "macoro/async_scope.h" 2 | #include "macoro/task.h" 3 | #include "macoro/sync_wait.h" 4 | #include "macoro/manual_reset_event.h" 5 | 6 | namespace macoro 7 | { 8 | 9 | namespace tests 10 | { 11 | 12 | //struct task_of 13 | //{ 14 | // struct promise_type 15 | // { 16 | // struct final_suspend 17 | // { 18 | // bool await_ready() noexcept { return false; } 19 | 20 | // std::coroutine_handle<> await_suspend(std::coroutine_handle c)noexcept 21 | // { 22 | // c.destroy(); 23 | // return std::noop_coroutine(); 24 | // } 25 | 26 | // void await_resume() noexcept{} 27 | // }; 28 | 29 | // std::suspend_always initial_suspend() noexcept { return {}; } 30 | // std::suspend_always final_suspend() noexcept { return {}; } 31 | // void unhandled_exception() noexcept {} 32 | // task_of get_return_object() noexcept { return {}; } 33 | 34 | // void return_void() {} 35 | // }; 36 | 37 | //}; 38 | 39 | //task_of foo() 40 | //{ 41 | // co_return; 42 | //} 43 | 44 | void scoped_task_test() 45 | { 46 | 47 | struct scope_guard 48 | { 49 | bool* destroyed = nullptr; 50 | scope_guard(bool& d) 51 | :destroyed(&d) 52 | {} 53 | 54 | scope_guard(scope_guard&& g) 55 | :destroyed(std::exchange(g.destroyed, nullptr)) 56 | {} 57 | 58 | ~scope_guard() 59 | { 60 | if (destroyed) 61 | *destroyed = true; 62 | } 63 | }; 64 | 65 | // continuation after completion 66 | { 67 | bool destroyed = false; 68 | auto f = [&](scope_guard g) -> task { 69 | co_return 42; 70 | }; 71 | 72 | async_scope scope; 73 | scoped_task t = scope.add(f({ destroyed })); 74 | 75 | if (t.is_ready() == false) 76 | throw std::runtime_error(MACORO_LOCATION); 77 | 78 | //auto a = sync_wait(std::move(f({ destroyed }))); 79 | auto v = sync_wait(std::move(t)); 80 | 81 | if (v != 42) 82 | throw std::runtime_error(MACORO_LOCATION); 83 | 84 | if (!destroyed) 85 | throw std::runtime_error(MACORO_LOCATION); 86 | } 87 | 88 | // continuation before completion. 89 | { 90 | async_manual_reset_event e; 91 | 92 | 93 | auto f = [&](scope_guard g) -> task { 94 | co_await e; 95 | co_return 42; 96 | }; 97 | async_scope scope; 98 | bool destroyed = false; 99 | scoped_task t = scope.add(f({ destroyed })); 100 | 101 | if (t.is_ready() == true) 102 | throw std::runtime_error(MACORO_LOCATION); 103 | 104 | if (destroyed) 105 | throw std::runtime_error(MACORO_LOCATION); 106 | 107 | auto eager = [&]() -> eager_task { 108 | auto v = co_await std::move(t); 109 | co_return v; 110 | } 111 | (); 112 | 113 | 114 | if (destroyed) 115 | throw std::runtime_error(MACORO_LOCATION); 116 | 117 | if (eager.is_ready() == true) 118 | throw std::runtime_error(MACORO_LOCATION); 119 | 120 | e.set(); 121 | 122 | if (eager.is_ready() == false) 123 | throw std::runtime_error(MACORO_LOCATION); 124 | 125 | if (destroyed == false) 126 | throw std::runtime_error(MACORO_LOCATION); 127 | 128 | auto v = sync_wait(eager); 129 | 130 | if (v != 42) 131 | throw std::runtime_error(MACORO_LOCATION); 132 | } 133 | 134 | 135 | // drops before completion 136 | { 137 | async_manual_reset_event e; 138 | 139 | bool called = false, destroyed = false; 140 | auto f = [&](scope_guard g) -> task { 141 | co_await e; 142 | called = true; 143 | co_return 42; 144 | }; 145 | async_scope scope; 146 | scope.add(f({ destroyed })); 147 | 148 | if (called) 149 | throw std::runtime_error(MACORO_LOCATION); 150 | 151 | e.set(); 152 | 153 | if (!called) 154 | throw std::runtime_error(MACORO_LOCATION); 155 | 156 | if (destroyed == false) 157 | throw std::runtime_error(MACORO_LOCATION); 158 | 159 | sync_wait(scope); 160 | } 161 | 162 | 163 | // joins before completion 164 | { 165 | async_manual_reset_event e; 166 | 167 | bool destroyed = false; 168 | auto f = [&](scope_guard g) -> task { 169 | co_await e; 170 | co_return 42; 171 | }; 172 | async_scope scope; 173 | scoped_task t = scope.add(f({ destroyed })); 174 | 175 | if (t.is_ready() == true) 176 | throw std::runtime_error(MACORO_LOCATION); 177 | 178 | auto eager = [&]() -> eager_task<> { 179 | co_await scope; 180 | } 181 | (); 182 | 183 | if (eager.is_ready() == true) 184 | throw std::runtime_error(MACORO_LOCATION); 185 | 186 | e.set(); 187 | 188 | if (t.is_ready() == false) 189 | throw std::runtime_error(MACORO_LOCATION); 190 | if (eager.is_ready() == false) 191 | throw std::runtime_error(MACORO_LOCATION); 192 | 193 | sync_wait(eager); 194 | 195 | 196 | if (destroyed) 197 | throw std::runtime_error(MACORO_LOCATION); 198 | 199 | auto v = sync_wait(std::move(t)); 200 | 201 | if(v != 42) 202 | throw std::runtime_error(MACORO_LOCATION); 203 | 204 | if (!destroyed) 205 | throw std::runtime_error(MACORO_LOCATION); 206 | } 207 | 208 | 209 | // joins before completion and then discard 210 | { 211 | async_manual_reset_event e; 212 | 213 | bool destroyed = false; 214 | auto f = [&](scope_guard g) -> task { 215 | co_await e; 216 | co_return 42; 217 | }; 218 | async_scope scope; 219 | scoped_task t = scope.add(f({ destroyed })); 220 | 221 | if (t.is_ready() == true) 222 | throw std::runtime_error(MACORO_LOCATION); 223 | 224 | auto eager = [&]() -> eager_task<> { 225 | co_await scope; 226 | } 227 | (); 228 | 229 | if (eager.is_ready() == true) 230 | throw std::runtime_error(MACORO_LOCATION); 231 | 232 | e.set(); 233 | 234 | if (t.is_ready() == false) 235 | throw std::runtime_error(MACORO_LOCATION); 236 | if (eager.is_ready() == false) 237 | throw std::runtime_error(MACORO_LOCATION); 238 | 239 | sync_wait(eager); 240 | 241 | if (destroyed) 242 | throw std::runtime_error(MACORO_LOCATION); 243 | 244 | t = {}; 245 | 246 | if (!destroyed) 247 | throw std::runtime_error(MACORO_LOCATION); 248 | } 249 | 250 | 251 | struct test_exception : std::exception 252 | { 253 | 254 | }; 255 | 256 | // get exception 257 | { 258 | bool destroyed = false; 259 | auto f = [&](scope_guard g) -> task { 260 | throw test_exception{}; 261 | co_return 42; 262 | }; 263 | async_scope scope; 264 | 265 | { 266 | scoped_task t = scope.add(f({ destroyed })); 267 | 268 | if (t.is_ready() == false) 269 | throw std::runtime_error(MACORO_LOCATION); 270 | 271 | if (destroyed == true) 272 | throw std::runtime_error(MACORO_LOCATION); 273 | 274 | try 275 | { 276 | sync_wait(std::move(t)); 277 | throw std::runtime_error("failed"); 278 | } 279 | catch (test_exception& e) 280 | { 281 | } 282 | } 283 | 284 | 285 | if (destroyed == false) 286 | throw std::runtime_error(MACORO_LOCATION); 287 | 288 | } 289 | 290 | 291 | // get exception join 292 | { 293 | bool destroyed = false; 294 | auto f = [&](scope_guard g) -> task { 295 | throw test_exception{}; 296 | co_return 42; 297 | }; 298 | async_scope scope; 299 | 300 | { 301 | scoped_task t = scope.add(f({ destroyed })); 302 | 303 | if (t.is_ready() == false) 304 | throw std::runtime_error(MACORO_LOCATION); 305 | 306 | if (destroyed == true) 307 | throw std::runtime_error(MACORO_LOCATION); 308 | } 309 | 310 | if (destroyed == false) 311 | throw std::runtime_error(MACORO_LOCATION); 312 | 313 | try 314 | { 315 | sync_wait(scope); 316 | throw std::runtime_error("failed"); 317 | } 318 | catch (async_scope_exception& e) 319 | { 320 | if(e.m_exceptions.size() != 1) 321 | throw std::runtime_error(MACORO_LOCATION); 322 | 323 | try 324 | { 325 | std::rethrow_exception(e.m_exceptions[0]); 326 | } 327 | catch (test_exception& e) 328 | { 329 | } 330 | } 331 | } 332 | 333 | // reference type 334 | { 335 | int x = 0; 336 | bool destroyed = false; 337 | auto f = [&](scope_guard g) -> task { 338 | co_return x; 339 | }; 340 | 341 | async_scope scope; 342 | scoped_task t = scope.add(f({ destroyed })); 343 | 344 | if (t.is_ready() == false) 345 | throw std::runtime_error(MACORO_LOCATION); 346 | 347 | auto& v = sync_wait(std::move(t)); 348 | 349 | if (&v != &x) 350 | throw std::runtime_error(MACORO_LOCATION); 351 | 352 | if (!destroyed) 353 | throw std::runtime_error(MACORO_LOCATION); 354 | } 355 | 356 | // move only type 357 | { 358 | std::unique_ptr x(new int{ 42 }); 359 | auto addr = x.get(); 360 | bool destroyed = false; 361 | auto f = [&](scope_guard g) -> task> { 362 | co_return std::move(x); 363 | }; 364 | 365 | async_scope scope; 366 | scoped_task> t = scope.add(f({ destroyed })); 367 | 368 | if (t.is_ready() == false) 369 | throw std::runtime_error(MACORO_LOCATION); 370 | 371 | auto v = sync_wait(std::move(t)); 372 | 373 | if (v.get() != addr) 374 | throw std::runtime_error(MACORO_LOCATION); 375 | 376 | if (!destroyed) 377 | throw std::runtime_error(MACORO_LOCATION); 378 | } 379 | 380 | 381 | // pointer type 382 | { 383 | int X = 42; 384 | int* x = &X; 385 | bool destroyed = false; 386 | auto f = [&](scope_guard g) -> task { 387 | co_return x; 388 | }; 389 | 390 | async_scope scope; 391 | scoped_task t = scope.add(f({ destroyed })); 392 | 393 | if (t.is_ready() == false) 394 | throw std::runtime_error(MACORO_LOCATION); 395 | 396 | auto v = sync_wait(std::move(t)); 397 | 398 | if (v != x) 399 | throw std::runtime_error(MACORO_LOCATION); 400 | 401 | if (!destroyed) 402 | throw std::runtime_error(MACORO_LOCATION); 403 | } 404 | 405 | 406 | 407 | // void type 408 | { 409 | bool destroyed = false; 410 | auto f = [&](scope_guard g) -> task<> { 411 | co_return; 412 | }; 413 | 414 | async_scope scope; 415 | scoped_task<> t = scope.add(f({ destroyed })); 416 | 417 | if (t.is_ready() == false) 418 | throw std::runtime_error(MACORO_LOCATION); 419 | 420 | sync_wait(std::move(t)); 421 | 422 | if (!destroyed) 423 | throw std::runtime_error(MACORO_LOCATION); 424 | } 425 | } 426 | 427 | 428 | void async_scope_test() 429 | { 430 | 431 | 432 | } 433 | } 434 | } -------------------------------------------------------------------------------- /tests/async_scope_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace macoro 4 | { 5 | 6 | namespace tests 7 | { 8 | 9 | void scoped_task_test(); 10 | void async_scope_test(); 11 | } 12 | 13 | 14 | 15 | } -------------------------------------------------------------------------------- /tests/await_lifetime_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace macoro 4 | { 5 | 6 | namespace tests 7 | { 8 | 9 | void store_as_t_lifetime_test(); 10 | void await_lifetime_refOnly_test(); 11 | void await_lifetime_NoCopyMove_test(); 12 | void await_lifetime_MovOnly_test(); 13 | void await_lifetime_fn_test(); 14 | void yield_await_lifetime_test20(); 15 | void yield_await_lifetime_test14(); 16 | } 17 | 18 | 19 | 20 | } -------------------------------------------------------------------------------- /tests/channel_mpsc_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "channel_mpsc_tests.h" 2 | #include "macoro/channel.h" 3 | #include "macoro/task.h" 4 | #include "macoro/thread_pool.h" 5 | #include "macoro/when_all.h" 6 | #include "macoro/sync_wait.h" 7 | #include "macoro/result.h" 8 | #include "macoro/transfer_to.h" 9 | #include "macoro/inline_scheduler.h" 10 | #include "macoro/start_on.h" 11 | 12 | namespace macoro 13 | { 14 | namespace tests 15 | { 16 | namespace 17 | { 18 | template 19 | R&& move2(R&&r) 20 | { 21 | return std::move(r); 22 | } 23 | 24 | std::size_t n = 1000; 25 | struct message 26 | { 27 | size_t tIdx; 28 | size_t id; 29 | float data; 30 | }; 31 | 32 | template 33 | task producer2( 34 | mpsc::channel_sender& chl, 35 | Scheduler& sched, 36 | size_t tIdx) 37 | { 38 | ; 39 | MC_BEGIN(task<>, &chl, &sched, tIdx 40 | , i = size_t{} 41 | , slot = move2(mpsc::channel_sender::push_wrapper{}) 42 | ); 43 | for (i = 0; i < n; ++i) 44 | { 45 | // Wait until a slot is free in the buffer. 46 | MC_AWAIT_SET(slot, chl.push()); 47 | MC_AWAIT(transfer_to(sched)); 48 | 49 | //std::cout << "pushing "<< tIdx << " " << i << std::endl; 50 | slot = message{ tIdx, i, 123 }; 51 | 52 | slot.publish(); 53 | } 54 | 55 | MC_END(); 56 | } 57 | 58 | template 59 | task producer( 60 | size_t numThreads, 61 | mpsc::channel_sender& chl, 62 | Scheduler& sched) 63 | { 64 | ; 65 | MC_BEGIN(task<>, &chl, &sched, numThreads 66 | , i = size_t{} 67 | , tasks = std::vector>{} 68 | ); 69 | tasks.resize(numThreads); 70 | for (i = 0; i < numThreads; ++i) 71 | { 72 | tasks[i] = producer2(chl, sched, i) 73 | | start_on(sched) 74 | | make_eager(); 75 | } 76 | for (i = 0; i < numThreads; ++i) 77 | { 78 | MC_AWAIT(tasks[i]); 79 | } 80 | 81 | // Publish a sentinel 82 | MC_AWAIT(chl.close()); 83 | MC_AWAIT(transfer_to(sched)); 84 | //std::cout << "returning producer" << std::endl; 85 | 86 | 87 | MC_END(); 88 | } 89 | 90 | template 91 | task consumer( 92 | size_t numProducers, 93 | mpsc::channel_receiver& chl, 94 | Scheduler& sched) 95 | { 96 | MC_BEGIN(task<>, &chl, &sched, numProducers 97 | , i = std::vector( numProducers ) 98 | , msg = macoro::result{} 99 | , msg2 = move2(macoro::mpsc::channel::pop_wrapper{}) 100 | ); 101 | while (true) 102 | { 103 | // Wait until the next message is available 104 | // There may be more than one available. 105 | MC_AWAIT_TRY(msg, chl.front()); 106 | MC_AWAIT(transfer_to(sched)); 107 | if (msg.has_error()) 108 | { 109 | for (size_t j = 0; j < numProducers; ++j) 110 | { 111 | 112 | if (i[j] != n) 113 | { 114 | std::cout << "failed at " << j << " " << i[j] << std::endl; 115 | throw MACORO_RTE_LOC; 116 | } 117 | } 118 | //std::cout << "returning consumer" << std::endl; 119 | MC_RETURN_VOID(); 120 | } 121 | //std::cout << "popping " << i << std::endl; 122 | 123 | //msgPtr = &msg.value(); 124 | if (msg.value().tIdx > numProducers) 125 | throw MACORO_RTE_LOC; 126 | if (msg.value().id != i[msg.value().tIdx]) 127 | throw MACORO_RTE_LOC; 128 | if (msg.value().data != 123) 129 | throw MACORO_RTE_LOC; 130 | 131 | MC_AWAIT_SET(msg2, chl.pop()); 132 | MC_AWAIT(transfer_to(sched)); 133 | 134 | if (msg.value().id != msg2->id) 135 | throw MACORO_RTE_LOC; 136 | if (msg.value().data != msg2->data) 137 | throw MACORO_RTE_LOC; 138 | 139 | msg2.publish(); 140 | 141 | //std::cout << "popped " << i << std::endl; 142 | ++i[msg.value().tIdx]; 143 | 144 | } 145 | 146 | MC_END(); 147 | } 148 | 149 | template 150 | task example(Scheduler& sched, size_t numThreads) 151 | { 152 | 153 | auto s_r = mpsc::make_channel(8); 154 | 155 | MC_BEGIN(task, &sched, numThreads 156 | , sender = std::move(std::get<0>(s_r)) 157 | , receiver = std::move(std::get<1>(s_r)) 158 | ); 159 | 160 | MC_AWAIT(when_all_ready( 161 | producer(numThreads, sender, sched), 162 | consumer(numThreads, receiver, sched)) 163 | ); 164 | 165 | MC_END(); 166 | } 167 | } 168 | void mpsc_channel_test() 169 | { 170 | size_t numThreads = 10; 171 | inline_scheduler sched; 172 | sync_wait(example(sched, numThreads)); 173 | } 174 | 175 | 176 | void mpsc_channel_ex_test() 177 | { 178 | size_t numThreads = 10; 179 | thread_pool sched; 180 | auto w = sched.make_work(); 181 | sched.create_threads(numThreads); 182 | sync_wait(example(sched, numThreads)); 183 | 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /tests/channel_mpsc_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace macoro 4 | { 5 | namespace tests 6 | { 7 | void mpsc_channel_test(); 8 | void mpsc_channel_ex_test(); 9 | } 10 | } -------------------------------------------------------------------------------- /tests/channel_spsc_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "channel_spsc_tests.h" 3 | #include "macoro/channel_spsc.h" 4 | #include "macoro/task.h" 5 | #include "macoro/thread_pool.h" 6 | #include "macoro/when_all.h" 7 | #include "macoro/sync_wait.h" 8 | #include "macoro/result.h" 9 | #include "macoro/transfer_to.h" 10 | #include "macoro/inline_scheduler.h" 11 | #include "macoro/wrap.h" 12 | 13 | namespace macoro 14 | { 15 | namespace tests 16 | { 17 | namespace 18 | { 19 | std::size_t n = 100'000; 20 | struct message 21 | { 22 | size_t id; 23 | float data; 24 | }; 25 | 26 | template 27 | task producer( 28 | spsc::channel_sender& chl, 29 | Scheduler& sched) 30 | { 31 | auto i = std::size_t{}; 32 | auto slot = spsc::channel_sender::push_wrapper{}; 33 | for (i = 0; i < n; ++i) 34 | { 35 | // Wait until a slot is free in the buffer. 36 | slot = co_await chl.push(); 37 | co_await transfer_to(sched); 38 | 39 | //std::cout << "pushing " << i << std::endl; 40 | slot = message{ i, 123 }; 41 | 42 | slot.publish(); 43 | } 44 | 45 | // Publish a sentinel 46 | co_await chl.close(); 47 | co_await transfer_to(sched); 48 | //std::cout << "returning producer" << std::endl; 49 | } 50 | 51 | template 52 | task consumer( 53 | spsc::channel_receiver& chl, 54 | Scheduler& sched) 55 | { 56 | auto i = std::size_t{ 0 }; 57 | auto msg = macoro::result{}; 58 | auto msg2 = macoro::spsc::channel::pop_wrapper{}; 59 | 60 | while (true) 61 | { 62 | // Wait until the next message is available 63 | // There may be more than one available. 64 | try{ 65 | msg = Ok(co_await chl.front()); 66 | } 67 | catch (...) 68 | { 69 | msg = Err(std::current_exception()); 70 | } 71 | 72 | co_await(transfer_to(sched)); 73 | if (msg.has_error()) 74 | { 75 | if (i != n) 76 | throw MACORO_RTE_LOC; 77 | //std::cout << "returning consumer" << std::endl; 78 | co_return; 79 | } 80 | //std::cout << "popping " << i << std::endl; 81 | 82 | if (msg.value().id != i++) 83 | throw MACORO_RTE_LOC; 84 | if (msg.value().data != 123) 85 | throw MACORO_RTE_LOC; 86 | 87 | msg2 = co_await(chl.pop()); 88 | co_await(transfer_to(sched)); 89 | 90 | if (msg.value().id != msg2->id) 91 | throw MACORO_RTE_LOC; 92 | if (msg.value().data != msg2->data) 93 | throw MACORO_RTE_LOC; 94 | 95 | msg2.publish(); 96 | 97 | } 98 | 99 | } 100 | 101 | template 102 | task example(Scheduler& sched) 103 | { 104 | 105 | auto s_r = spsc::make_channel(8); 106 | 107 | MC_BEGIN(task, &sched 108 | , sender = std::move(std::get<0>(s_r)) 109 | , receiver = std::move(std::get<1>(s_r)) 110 | ); 111 | 112 | MC_AWAIT(when_all_ready( 113 | producer(sender, sched), 114 | consumer(receiver, sched)) 115 | ); 116 | 117 | MC_END(); 118 | } 119 | } 120 | void spsc_channel_test() 121 | { 122 | inline_scheduler sched; 123 | sync_wait(example(sched)); 124 | } 125 | 126 | 127 | void spsc_channel_ex_test() 128 | { 129 | thread_pool sched; 130 | auto w = sched.make_work(); 131 | sched.create_thread(); 132 | sync_wait(example(sched)); 133 | 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /tests/channel_spsc_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace macoro 4 | { 5 | namespace tests 6 | { 7 | void spsc_channel_test(); 8 | void spsc_channel_ex_test(); 9 | } 10 | } -------------------------------------------------------------------------------- /tests/eager_task_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | 5 | namespace macoro 6 | { 7 | namespace tests 8 | { 9 | 10 | void make_eager_test(); 11 | void thread_pool_post_test(); 12 | void thread_pool_dispatch_test(); 13 | void thread_pool_start_on_test(); 14 | void eager_task_int_test(); 15 | void eager_task_void_test(); 16 | void eager_task_ref_test(); 17 | void eager_task_move_test(); 18 | void eager_task_ex_test(); 19 | void eager_task_slow_test(); 20 | void eager_task_fast_test(); 21 | void eager_task_stress_test(); 22 | } 23 | } -------------------------------------------------------------------------------- /tests/result_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "result_tests.h" 2 | 3 | #include "macoro/result.h" 4 | #include "macoro/wrap.h" 5 | #include "macoro/sync_wait.h" 6 | namespace macoro 7 | { 8 | namespace tests 9 | { 10 | void result_basic_store_test() 11 | { 12 | result r = Ok(42); 13 | 14 | if (!r.has_value()) 15 | throw MACORO_RTE_LOC; 16 | if(r.value() != 42) 17 | throw MACORO_RTE_LOC; 18 | 19 | try { 20 | r.error(); 21 | } 22 | catch (...) 23 | { 24 | r = Err(std::current_exception()); 25 | } 26 | 27 | if(!r.has_error()) 28 | throw MACORO_RTE_LOC; 29 | } 30 | 31 | 32 | void result_co_await_test() 33 | { 34 | auto foo = []()->result 35 | { 36 | auto b = result{}; 37 | bool bb = co_await b; 38 | co_return Ok(bb ? 1 : 42); 39 | }; 40 | 41 | auto r = foo(); 42 | 43 | if(!r.has_value()) 44 | throw MACORO_RTE_LOC; 45 | if(r.value() != 42) 46 | throw MACORO_RTE_LOC; 47 | try { 48 | r.error(); 49 | } 50 | catch (...) 51 | { 52 | r = Err(std::current_exception()); 53 | } 54 | 55 | if(!r.has_error()) 56 | throw MACORO_RTE_LOC; 57 | } 58 | 59 | void result_task_wrap_test() 60 | { 61 | 62 | auto foo = []()->task 63 | { 64 | auto b = result{}; 65 | auto bb = b.error(); 66 | co_return 42; 67 | }; 68 | 69 | result r = sync_wait(wrap(foo())); 70 | if(!r.has_error()) 71 | throw MACORO_RTE_LOC; 72 | } 73 | 74 | 75 | void result_task_wrap_pipe_test() 76 | { 77 | auto foo = []()->task 78 | { 79 | auto b = result{}; 80 | auto bb = b.error(); 81 | co_return 42; 82 | }; 83 | 84 | result r = sync_wait(foo() | wrap()); 85 | if (!r.has_error()) 86 | throw MACORO_RTE_LOC; 87 | } 88 | 89 | 90 | void result_void_test() 91 | { 92 | auto foo = []()->result 93 | { 94 | co_return Ok(); 95 | }; 96 | 97 | result r = sync_wait(wrap(foo())); 98 | if(r.has_error()) 99 | throw MACORO_RTE_LOC; 100 | } 101 | 102 | void result_unique_awaiter_test() 103 | { 104 | struct awaitable 105 | { 106 | struct awaiter 107 | { 108 | awaiter() = delete; 109 | awaiter(const awaiter&) = delete; 110 | awaiter(awaiter&&) = delete; 111 | 112 | awaiter(int) {} 113 | 114 | bool await_ready() { return true; } 115 | void await_suspend(std::coroutine_handle<> h) {} 116 | void await_resume() {} 117 | }; 118 | 119 | awaitable() = delete; 120 | awaitable(const awaitable&) = delete; 121 | awaitable(awaitable&&) = default; 122 | awaitable(int) {} 123 | 124 | awaiter operator co_await()&& 125 | { 126 | return { 0 }; 127 | } 128 | }; 129 | 130 | auto foo = []()->task{ 131 | //co_await wrapped_awaitable{awaitable(0)}; 132 | //co_await wrap(awaitable(0)); 133 | co_await(awaitable(0) | wrap()); 134 | }; 135 | 136 | sync_wait(foo()); 137 | } 138 | 139 | 140 | void result_ref_awaiter_test() 141 | { 142 | struct awaitable 143 | { 144 | struct awaiter 145 | { 146 | awaiter() = delete; 147 | awaiter(const awaiter&) = delete; 148 | awaiter(awaiter&&) = delete; 149 | 150 | int m_val; 151 | awaiter(int i) : m_val(i) {} 152 | 153 | bool await_ready() { return true; } 154 | void await_suspend(std::coroutine_handle<> h) {} 155 | auto await_resume() { return m_val; } 156 | }; 157 | 158 | awaitable() = delete; 159 | awaitable(const awaitable&) = delete; 160 | awaitable(awaitable&&) = delete; 161 | awaitable(int i) : a(i) {} 162 | 163 | awaiter a; 164 | awaiter& operator co_await()& 165 | { 166 | return a; 167 | } 168 | }; 169 | 170 | auto foo = []()->task { 171 | auto a = awaitable(42); 172 | auto r = co_await(a | wrap()); 173 | 174 | if (r.value() != 42) 175 | throw MACORO_RTE_LOC; 176 | }; 177 | sync_wait(foo()); 178 | } 179 | 180 | 181 | template 182 | decltype(auto) wrap2(awaitable&& a) { 183 | return wrapped_awaitable(std::forward(a)); 184 | } 185 | 186 | 187 | void result_suspend_test() 188 | { 189 | struct awaitable 190 | { 191 | struct awaiter 192 | { 193 | awaiter() = delete; 194 | awaiter(const awaiter&) = delete; 195 | awaiter(awaiter&&) = delete; 196 | 197 | std::unique_ptr m_d; 198 | std::coroutine_handle<> m_h; 199 | awaiter(std::unique_ptrd) : m_d(std::move(d)) {} 200 | ~awaiter() 201 | { 202 | *m_d = 0; 203 | } 204 | 205 | 206 | bool await_ready() { return false; } 207 | void await_suspend(std::coroutine_handle<> h) { 208 | m_h = h; 209 | } 210 | auto await_resume() { return m_d.get(); } 211 | }; 212 | 213 | awaitable() = delete; 214 | awaitable(const awaitable&) = delete; 215 | awaitable(awaitable&&) = default; 216 | std::unique_ptr m_d; 217 | awaitable(std::unique_ptr i) : m_d(std::move(i)) {} 218 | 219 | awaiter operator co_await() 220 | { 221 | return {std::move(m_d)}; 222 | } 223 | }; 224 | 225 | //auto a = ; 226 | auto foo = [&]()->task { 227 | //auto tt = wrapped_awaitable(awaitable(std::unique_ptr{ new int(42) })); 228 | //auto bb = wrapped_awaitable(std::forward(awaitable(std::unique_ptr{ new int(42) }))); 229 | //auto dd = wrap2(awaitable(std::unique_ptr{ new int(42) })); 230 | auto r = co_await(awaitable(std::unique_ptr{ new int(42) }) | wrap()); 231 | if (*r.value() != 42) 232 | throw MACORO_RTE_LOC; 233 | co_return; 234 | }; 235 | auto task = foo(); 236 | decltype(auto) awaiter = task.operator co_await(); 237 | awaiter.await_ready(); 238 | awaiter.await_suspend(std::noop_coroutine()); 239 | awaiter.m_coroutine.resume(); 240 | } 241 | 242 | 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /tests/result_tests.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace macoro 4 | { 5 | namespace tests 6 | { 7 | 8 | void result_basic_store_test(); 9 | void result_co_await_test(); 10 | void result_task_wrap_test(); 11 | void result_task_wrap_pipe_test(); 12 | void result_void_test(); 13 | 14 | void result_unique_awaiter_test(); 15 | void result_ref_awaiter_test(); 16 | void result_suspend_test(); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /tests/sequence_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "macoro/task.h" 4 | #include "macoro/thread_pool.h" 5 | #include "macoro/when_all.h" 6 | #include "macoro/sync_wait.h" 7 | #include "macoro/sequence_mpsc.h" 8 | #include "macoro/sequence_spsc.h" 9 | #include "macoro/macros.h" 10 | using namespace std::chrono; 11 | 12 | namespace macoro 13 | { 14 | namespace tests 15 | { 16 | namespace 17 | { 18 | std::size_t n = 100'000; 19 | struct message 20 | { 21 | size_t id; 22 | steady_clock::time_point timestamp; 23 | float data; 24 | }; 25 | 26 | constexpr size_t bufferSize = 8; // Must be power-of-two 27 | constexpr size_t indexMask = bufferSize - 1; 28 | message buffer[bufferSize]; 29 | 30 | task producer( 31 | sequence_spsc& sequencer) 32 | { 33 | MC_BEGIN(task<>, &sequencer 34 | , i = size_t{} 35 | , seq = size_t{} 36 | , msg = (message*)nullptr 37 | ); 38 | for (i = 0; i < n; ++i) 39 | { 40 | // Wait until a slot is free in the buffer. 41 | MC_AWAIT_SET(seq, sequencer.claim_one()); 42 | 43 | // Populate the message. 44 | msg = &buffer[seq & indexMask]; 45 | msg->id = i; 46 | msg->timestamp = steady_clock::now(); 47 | msg->data = 123; 48 | 49 | //std::cout << "push " << i << std::endl; 50 | // Publish the message. 51 | sequencer.publish(seq); 52 | 53 | } 54 | 55 | // Publish a sentinel 56 | MC_AWAIT_SET(seq, sequencer.claim_one()); 57 | msg = &buffer[seq & indexMask]; 58 | msg->id = -1; 59 | //std::cout << "push end " << i << std::endl; 60 | sequencer.publish(seq); 61 | 62 | MC_END(); 63 | } 64 | 65 | task consumer( 66 | const sequence_spsc& sequencer, 67 | sequence_barrier& consumerBarrier) 68 | { 69 | MC_BEGIN(task<>, &sequencer, &consumerBarrier 70 | , nextToRead = size_t{ 0 } 71 | , msg = (message*)nullptr 72 | , available = size_t{} 73 | ); 74 | while (true) 75 | { 76 | // Wait until the next message is available 77 | // There may be more than one available. 78 | MC_AWAIT_SET(available, sequencer.wait_until_published(nextToRead)); 79 | do { 80 | msg = &buffer[nextToRead & indexMask]; 81 | if (msg->id == size_t(-1)) 82 | { 83 | consumerBarrier.publish(nextToRead); 84 | //std::cout << "pop done " << nextToRead << std::endl; 85 | MC_RETURN_VOID(); 86 | } 87 | //std::cout << "pop " << nextToRead << std::endl; 88 | 89 | if (msg->id != nextToRead) 90 | throw MACORO_RTE_LOC; 91 | if (msg->data != 123) 92 | throw MACORO_RTE_LOC; 93 | 94 | } while (nextToRead++ != available); 95 | 96 | // Notify the producer that we've finished processing 97 | // up to 'nextToRead - 1'. 98 | consumerBarrier.publish(available); 99 | } 100 | 101 | MC_END(); 102 | } 103 | 104 | task example() 105 | { 106 | struct State 107 | { 108 | sequence_barrier barrier; 109 | sequence_spsc sequencer; 110 | State() 111 | : sequencer(barrier, bufferSize) 112 | {} 113 | }; 114 | 115 | MC_BEGIN(task 116 | , state = new State{} 117 | ); 118 | 119 | MC_AWAIT(when_all_ready( 120 | producer(state->sequencer), 121 | consumer(state->sequencer, state->barrier)) 122 | ); 123 | 124 | delete state; 125 | 126 | MC_END(); 127 | } 128 | 129 | } 130 | 131 | void sequence_spsc_test() 132 | { 133 | sync_wait(example()); 134 | } 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /tests/sequence_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace macoro 4 | { 5 | namespace tests 6 | { 7 | void sequence_spsc_test(); 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /tests/take_until_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "macoro/take_until.h" 2 | #include "macoro/timeout.h" 3 | #include "macoro/sync_wait.h" 4 | #include "macoro/thread_pool.h" 5 | #include "take_until_tests.h" 6 | 7 | using namespace std::chrono; 8 | using namespace std::chrono_literals; 9 | namespace macoro 10 | { 11 | namespace tests 12 | { 13 | namespace 14 | { 15 | time_point begin, end, wake; 16 | } 17 | 18 | task makeTask(thread_pool& ex, stop_source s, bool wait) 19 | { 20 | 21 | 22 | // wait for cancel. 23 | if (wait) 24 | { 25 | co_await (s.get_token()); 26 | wake = steady_clock::now(); 27 | 28 | } 29 | else 30 | { 31 | co_await(ex.schedule_after(milliseconds(15))); 32 | wake = steady_clock::now(); 33 | s.request_stop(); 34 | } 35 | 36 | co_return(43); 37 | } 38 | 39 | task use(thread_pool& ex, bool wait) 40 | { 41 | auto to = timeout(ex, wait ? 10ms : 100000ms); 42 | 43 | begin = steady_clock::now(); 44 | co_await take_until(makeTask(ex, to.get_source(), wait), std::move(to)); 45 | end = steady_clock::now(); 46 | } 47 | 48 | 49 | void timeout_test() 50 | { 51 | //io_service s; 52 | //auto thrd = std::thread([&] {s.process_events(); }); 53 | auto maxLag = 40; 54 | thread_pool s; 55 | auto work = s.make_work(); 56 | s.create_thread(); 57 | 58 | sync_wait(use(s, true)); 59 | 60 | auto t0 = duration_cast(end - begin).count(); 61 | //auto w0 = duration_cast(wake - begin).count(); 62 | if (t0 < 10 || t0 > 10 + maxLag) 63 | { 64 | std::cout << t0 << "ms vs 10 ~ max " << 10 + maxLag << std::endl; 65 | //std::cout << w0 << "ms " << std::endl; 66 | throw MACORO_RTE_LOC; 67 | } 68 | 69 | sync_wait(use(s, false)); 70 | 71 | auto t1 = duration_cast(end - begin).count(); 72 | //auto w1 = duration_cast(wake - begin).count(); 73 | //std::cout << t1 << "ms vs 15 " << std::endl; 74 | //std::cout << w1 << "ms " << std::endl; 75 | if (t1 < 15 || t1 > 15 + maxLag) 76 | { 77 | std::cout << t1 << "ms vs 15 ~ max " << 15 + maxLag << std::endl; 78 | throw MACORO_RTE_LOC; 79 | } 80 | 81 | work = {}; 82 | s.join(); 83 | } 84 | 85 | 86 | 87 | void schedule_after() 88 | { 89 | //auto start = steady_clock::now(); 90 | auto maxLag = 30; 91 | thread_pool s; 92 | auto work = s.make_work(); 93 | s.create_thread(); 94 | 95 | for (int i = 0; i < 10; ++i) 96 | { 97 | 98 | std::promise p; 99 | 100 | auto init = [](std::promise& p, thread_pool& s) -> task<> 101 | { 102 | MC_BEGIN(task<>, &p, &s); 103 | begin = steady_clock::now(); 104 | MC_AWAIT(s.schedule_after(milliseconds(15))); 105 | end = steady_clock::now(); 106 | p.set_value(); 107 | MC_END(); 108 | }(p, s); 109 | 110 | sync_wait(init); 111 | 112 | auto t1 = duration_cast(end - begin).count(); 113 | if (t1 > 15 + maxLag) 114 | throw MACORO_RTE_LOC; 115 | //std::cout << t1 << "ms vs 5 " << std::endl; 116 | } 117 | //std::cout << s.print_log(start) << std::endl; 118 | work = {}; 119 | s.join(); 120 | } 121 | 122 | void schedule_after_cancaled() 123 | { 124 | 125 | macoro::thread_pool s; 126 | auto w = s.make_work(); 127 | s.create_thread(); 128 | stop_source src; 129 | auto token = src.get_token(); 130 | src.request_stop(); 131 | auto t = [](thread_pool& s, stop_token token) 132 | { 133 | MC_BEGIN(task<>, &s, token); 134 | 135 | MC_AWAIT(s.schedule_after(std::chrono::milliseconds(1), token)); 136 | 137 | MC_END(); 138 | }(s, token); 139 | 140 | sync_wait(t); 141 | 142 | } 143 | 144 | 145 | 146 | void take_until_tests() 147 | { 148 | timeout_test(); 149 | } 150 | 151 | } 152 | } -------------------------------------------------------------------------------- /tests/take_until_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace macoro 5 | { 6 | namespace tests 7 | { 8 | void schedule_after(); 9 | void schedule_after_cancaled(); 10 | void take_until_tests(); 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /tests/task_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace macoro 5 | { 6 | namespace tests 7 | { 8 | void task_int_test(); 9 | void task_void_test(); 10 | void task_ref_test(); 11 | void task_move_test(); 12 | void task_ex_test(); 13 | void task_blocking_int_test(); 14 | void task_blocking_void_test(); 15 | void task_blocking_ref_test(); 16 | void task_blocking_move_test(); 17 | void task_blocking_ex_test(); 18 | void task_blocking_cancel_test(); 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /tests/tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "CLP.h" 8 | 9 | namespace macoro 10 | { 11 | class TestCollection 12 | { 13 | public: 14 | struct Test 15 | { 16 | std::string mName; 17 | std::function mTest; 18 | }; 19 | TestCollection() = default; 20 | TestCollection(std::function init) 21 | { 22 | init(*this); 23 | } 24 | 25 | std::vector mTests; 26 | 27 | enum class Result 28 | { 29 | passed, 30 | skipped, 31 | failed 32 | }; 33 | 34 | 35 | Result runOne(std::size_t idx, CLP const* cmd = nullptr); 36 | Result run(std::vector testIdxs, std::size_t repeatCount = 1, CLP const* cmd = nullptr); 37 | Result runAll(uint64_t repeatCount = 1, CLP const* cmd = nullptr); 38 | Result runIf(CLP& cmd); 39 | Result run(CLP& cmd); 40 | void list(); 41 | 42 | std::vector search(const std::list& s); 43 | 44 | 45 | void add(std::string name, std::function test); 46 | void add(std::string name, std::function test); 47 | 48 | void operator+=(const TestCollection& add); 49 | }; 50 | 51 | 52 | class UnitTestFail : public std::exception 53 | { 54 | std::string mWhat; 55 | public: 56 | explicit UnitTestFail(std::string reason) 57 | :std::exception(), 58 | mWhat(reason) 59 | {} 60 | 61 | explicit UnitTestFail() 62 | :std::exception(), 63 | mWhat("UnitTestFailed exception") 64 | { 65 | } 66 | 67 | virtual const char* what() const throw() 68 | { 69 | return mWhat.c_str(); 70 | } 71 | }; 72 | 73 | class UnitTestSkipped : public std::runtime_error 74 | { 75 | public: 76 | UnitTestSkipped() 77 | : std::runtime_error("skipping test") 78 | {} 79 | 80 | UnitTestSkipped(std::string r) 81 | : std::runtime_error(r) 82 | {} 83 | }; 84 | 85 | enum class Color { 86 | LightGreen = 2, 87 | LightGrey = 3, 88 | LightRed = 4, 89 | OffWhite1 = 5, 90 | OffWhite2 = 6, 91 | Grey = 8, 92 | Green = 10, 93 | Blue = 11, 94 | Red = 12, 95 | Pink = 13, 96 | Yellow = 14, 97 | White = 15, 98 | Default 99 | }; 100 | 101 | extern const Color ColorDefault; 102 | 103 | 104 | std::ostream& operator<<(std::ostream& out, Color color); 105 | 106 | extern TestCollection testCollection; 107 | } 108 | -------------------------------------------------------------------------------- /tests/when_all_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "when_all_tests.h" 2 | #include "macoro/when_all.h" 3 | #include "macoro/task.h" 4 | #include "macoro/sync_wait.h" 5 | #include "macoro/manual_reset_event.h" 6 | #include "macoro/async_scope.h" 7 | #include "macoro/when_all_scope.h" 8 | 9 | namespace macoro 10 | { 11 | namespace tests 12 | { 13 | 14 | auto f = []() -> task { 15 | //std::cout << "trace\n" << (co_await get_trace{}).str() << std::endl; 16 | co_return(42); 17 | }; 18 | 19 | 20 | auto g = []() -> task { 21 | MC_BEGIN(task); 22 | MC_RETURN(true); 23 | MC_END(); 24 | }; 25 | 26 | bool b = true; 27 | auto h = []() -> task { 28 | MC_BEGIN(task); 29 | MC_RETURN(b); 30 | MC_END(); 31 | }; 32 | 33 | void when_all_basic_tests() 34 | { 35 | static_assert( 36 | is_awaitable< 37 | task 38 | >::value 39 | , ""); 40 | 41 | static_assert(std::is_same< 42 | remove_reference_and_wrapper_t>, 43 | task 44 | >::value, ""); 45 | 46 | 47 | static_assert( 48 | is_awaitable< 49 | remove_reference_and_wrapper_t> 50 | >::value 51 | , ""); 52 | 53 | static_assert( 54 | conjunction< 55 | is_awaitable< 56 | remove_reference_and_wrapper_t> 57 | >, 58 | is_awaitable< 59 | remove_reference_and_wrapper_t> 60 | > 61 | >::value 62 | , ""); 63 | 64 | auto ff = f(); 65 | auto gg = g(); 66 | 67 | auto t = [&]()->task< 68 | std::tuple < 69 | detail::when_all_task, 70 | detail::when_all_task 71 | >> 72 | { 73 | co_return co_await when_all_ready(std::move(ff), h()); 74 | }(); 75 | 76 | auto r2 = sync_wait(std::move(t)); 77 | 78 | detail::when_all_task r0 = std::move(std::get<0>(r2)); 79 | detail::when_all_task r1 = std::move(std::get<1>(r2)); 80 | 81 | if (r0.result() != 42) 82 | throw std::runtime_error(MACORO_LOCATION); 83 | if (&r1.result() != &b) 84 | throw std::runtime_error(MACORO_LOCATION); 85 | 86 | } 87 | 88 | 89 | void when_all_scope_test() 90 | { 91 | auto f = []() -> task { 92 | co_return 42; 93 | }; 94 | 95 | auto g = []() -> task> { 96 | co_return new int{ 10 }; 97 | }; 98 | 99 | // a coroutine with RAII eager subtasks. 100 | // the eager subtasks will be joined before 101 | // the scope exits. 102 | auto t = [&]()->task<> { 103 | 104 | co_await f(); 105 | 106 | // start a eager scope. All scoped 107 | // tasks in the when_all_scope will 108 | // be joined before scope exit. 109 | co_await [&]()->when_all_scope { 110 | 111 | // start the work eagerly 112 | scoped_task r = co_await scoped(f()); 113 | 114 | // start the work eagerly 115 | scoped_task> h = co_await scoped(g()); 116 | 117 | // join one of them 118 | int i = co_await std::move(r); 119 | 120 | if (i == 42) 121 | throw std::runtime_error("meaning of life"); 122 | 123 | // h wont be joined explicitly if i == 42. However, 124 | // when_all_scope will suspend until h completes. 125 | // exceptions will be propegated out of the scope. 126 | std::unique_ptr j = co_await std::move(h); 127 | } 128 | (); 129 | 130 | // h has been joined and an exception containing 131 | // "meaning of life" will be thrown. 132 | } 133 | (); 134 | 135 | sync_wait(std::move(t)); 136 | } 137 | } 138 | } 139 | 140 | -------------------------------------------------------------------------------- /tests/when_all_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | namespace macoro 5 | { 6 | 7 | namespace tests 8 | { 9 | void when_all_basic_tests(); 10 | 11 | void when_all_scope_test(); 12 | } 13 | 14 | 15 | } -------------------------------------------------------------------------------- /thirdparty/fetch.cmake: -------------------------------------------------------------------------------- 1 | 2 | 3 | function(RUN) 4 | cmake_parse_arguments( 5 | PARSED_ARGS # prefix of parameters 6 | "" # list of names of the boolean arguments (only defined ones will be true) 7 | "WD" # list of names of mono-valued arguments 8 | "CMD;NAME" # list of names of multi-valued arguments (output variables are lists) 9 | ${ARGN} # arguments of the function to parse, here we take the all original ones 10 | ) 11 | message("${PARSED_ARGS_NAME}") 12 | file(APPEND ${LOG_FILE} 13 | "############# ${PARSED_ARGS_NAME} ###########" 14 | "${PARSED_ARGS_CMD}" 15 | "#############################################" 16 | ) 17 | 18 | execute_process( 19 | COMMAND ${PARSED_ARGS_CMD} 20 | WORKING_DIRECTORY ${PARSED_ARGS_WD} 21 | RESULT_VARIABLE RESULT 22 | COMMAND_ECHO STDOUT 23 | OUTPUT_FILE ${LOG_FILE} 24 | ERROR_FILE ${LOG_FILE} 25 | OUTPUT_QUIET 26 | ) 27 | if(RESULT) 28 | message(FATAL_ERROR "${PARSED_ARGS_NAME} failed (${RESULT}). See ${LOG_FILE}") 29 | endif() 30 | endfunction() 31 | 32 | if(NOT MSVC AND SUDO_FETCH) 33 | set(SUDO "sudo ") 34 | endif() 35 | 36 | if(NOT DEFINED PARALLEL_FETCH) 37 | include(ProcessorCount) 38 | ProcessorCount(NUM_PROCESSORS) 39 | if(NOT NUM_PROCESSORS EQUAL 0) 40 | set(PARALLEL_FETCH ${NUM_PROCESSORS}) 41 | else() 42 | set(PARALLEL_FETCH 1) 43 | endif() 44 | endif() -------------------------------------------------------------------------------- /thirdparty/getOptionalLite.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(DEP_NAME optional-lite) 3 | set(GIT_REPOSITORY https://github.com/martinmoene/optional-lite.git) 4 | set(GIT_TAG "0854335c64461d07d00f85b068075ed8871859ec" ) 5 | 6 | set(CLONE_DIR "${MACORO_THIRDPARTY_CLONE_DIR}/${DEP_NAME}") 7 | set(BUILD_DIR "${CLONE_DIR}/out/build/${MACORO_CONFIG}") 8 | set(LOG_FILE "${CMAKE_CURRENT_LIST_DIR}/log-${DEP_NAME}.txt") 9 | set(CONFIG --config ${CMAKE_BUILD_TYPE}) 10 | 11 | 12 | include("${CMAKE_CURRENT_LIST_DIR}/fetch.cmake") 13 | 14 | if(NOT optional-lite_FOUND) 15 | find_program(GIT git REQUIRED) 16 | set(DOWNLOAD_CMD ${GIT} clone ${GIT_REPOSITORY}) 17 | set(CHECKOUT_CMD ${GIT} checkout ${GIT_TAG}) 18 | set(CONFIGURE_CMD ${CMAKE_COMMAND} -S ${CLONE_DIR} -B ${BUILD_DIR} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} 19 | -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DOPTIONAL_LITE_OPT_BUILD_TESTS=OFF) 20 | set(BUILD_CMD ${CMAKE_COMMAND} --build ${BUILD_DIR} ${CONFIG}) 21 | set(INSTALL_CMD ${CMAKE_COMMAND} --install ${BUILD_DIR} ${CONFIG} --prefix ${MACORO_STAGE}) 22 | 23 | 24 | message("============= Building ${DEP_NAME} =============") 25 | if(NOT EXISTS ${CLONE_DIR}) 26 | run(NAME "Cloning ${GIT_REPOSITORY}" CMD ${DOWNLOAD_CMD} WD ${MACORO_THIRDPARTY_CLONE_DIR}) 27 | endif() 28 | 29 | run(NAME "Checkout ${GIT_TAG} " CMD ${CHECKOUT_CMD} WD ${CLONE_DIR}) 30 | run(NAME "Configure" CMD ${CONFIGURE_CMD} WD ${CLONE_DIR}) 31 | run(NAME "Build" CMD ${BUILD_CMD} WD ${CLONE_DIR}) 32 | run(NAME "Install" CMD ${INSTALL_CMD} WD ${CLONE_DIR}) 33 | 34 | message("log ${LOG_FILE}\n==========================================") 35 | else() 36 | message("${DEP_NAME} already fetched.") 37 | endif() 38 | 39 | install(CODE " 40 | 41 | 42 | if(NOT CMAKE_INSTALL_PREFIX STREQUAL \"${MACORO_STAGE}\") 43 | execute_process( 44 | COMMAND ${SUDO} \"${CMAKE_COMMAND}\" --install \"${BUILD_DIR}\" --prefix \${CMAKE_INSTALL_PREFIX} 45 | WORKING_DIRECTORY ${CLONE_DIR} 46 | RESULT_VARIABLE RESULT 47 | COMMAND_ECHO STDOUT 48 | ) 49 | endif() 50 | ") 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /thirdparty/getVariantLite.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(DEP_NAME variant-lite) 3 | set(GIT_REPOSITORY https://github.com/martinmoene/variant-lite.git) 4 | set(GIT_TAG "9499655b9c263eaef735efeeb53892c770d447e1" ) 5 | 6 | set(CLONE_DIR "${MACORO_THIRDPARTY_CLONE_DIR}/${DEP_NAME}") 7 | set(BUILD_DIR "${CLONE_DIR}/out/build/${MACORO_CONFIG}") 8 | set(LOG_FILE "${CMAKE_CURRENT_LIST_DIR}/log-${DEP_NAME}.txt") 9 | set(CONFIG --config ${CMAKE_BUILD_TYPE}) 10 | 11 | include("${CMAKE_CURRENT_LIST_DIR}/fetch.cmake") 12 | 13 | if(NOT variant-lite_FOUND) 14 | find_program(GIT git REQUIRED) 15 | set(DOWNLOAD_CMD ${GIT} clone ${GIT_REPOSITORY}) 16 | set(CHECKOUT_CMD ${GIT} checkout ${GIT_TAG}) 17 | set(CONFIGURE_CMD ${CMAKE_COMMAND} -S ${CLONE_DIR} -B ${BUILD_DIR} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} 18 | -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} -DVARIANT_LITE_OPT_BUILD_TESTS=OFF) 19 | set(BUILD_CMD ${CMAKE_COMMAND} --build ${BUILD_DIR} ${CONFIG}) 20 | set(INSTALL_CMD ${CMAKE_COMMAND} --install ${BUILD_DIR} ${CONFIG} --prefix ${MACORO_STAGE}) 21 | 22 | 23 | message("============= Building ${DEP_NAME} =============") 24 | if(NOT EXISTS ${CLONE_DIR}) 25 | run(NAME "Cloning ${GIT_REPOSITORY}" CMD ${DOWNLOAD_CMD} WD ${MACORO_THIRDPARTY_CLONE_DIR}) 26 | endif() 27 | 28 | run(NAME "Checkout ${GIT_TAG} " CMD ${CHECKOUT_CMD} WD ${CLONE_DIR}) 29 | run(NAME "Configure" CMD ${CONFIGURE_CMD} WD ${CLONE_DIR}) 30 | run(NAME "Build" CMD ${BUILD_CMD} WD ${CLONE_DIR}) 31 | run(NAME "Install" CMD ${INSTALL_CMD} WD ${CLONE_DIR}) 32 | 33 | message("log ${LOG_FILE}\n==========================================") 34 | else() 35 | message("${DEP_NAME} already fetched.") 36 | endif() 37 | 38 | install(CODE " 39 | if(NOT CMAKE_INSTALL_PREFIX STREQUAL \"${MACORO_STAGE}\") 40 | 41 | execute_process( 42 | COMMAND ${SUDO} \"${CMAKE_COMMAND}\" --install \"${BUILD_DIR}\" --prefix \${CMAKE_INSTALL_PREFIX} 43 | WORKING_DIRECTORY ${CLONE_DIR} 44 | RESULT_VARIABLE RESULT 45 | COMMAND_ECHO STDOUT 46 | ) 47 | endif() 48 | ") 49 | 50 | 51 | 52 | --------------------------------------------------------------------------------