├── .gitignore ├── run_queue.cpp ├── README.md ├── cb_as_coro.cpp ├── run_queue.hpp ├── colorrect.h ├── basic_awaiter.cpp ├── qt_basic.cpp ├── callbacks.cpp ├── colorrect.cpp ├── qt_coro.cpp ├── meta.hpp ├── CMakeLists.txt ├── asio_coro.cpp ├── my_awaitable.hpp ├── manual_generator.cpp ├── co_awaiter.hpp └── qtcoro.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Emacs backup files 35 | *~ 36 | 37 | # Qt Creator user's project settings 38 | CMakeLists.txt.user 39 | 40 | # Build folder from the README 41 | build/ 42 | -------------------------------------------------------------------------------- /run_queue.cpp: -------------------------------------------------------------------------------- 1 | // The world's lamest event loop/run queue implementation 2 | /* 3 | Copyright (c) 2018 Jeff Trull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include "run_queue.hpp" 25 | 26 | void run_queue::run() { 27 | while (!tasks_.empty()) { 28 | tasks_.front()(this); 29 | tasks_.pop(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # My Coroutine Experiments 2 | This repo contains the results of some experiments using the Coroutines TS - both by itself, and with libraries like Boost.Asio and Qt, to help me better understand its capabilities. 3 | 4 | With the exception of the Qt examples I implemented a really basic coroutine using an integer multiply as an example "asynchronous" task. We wait for it to asynchronously execute and then complete the computation by adding another integer. I figured out the necessary infrastructure (awaitable and promise types) necessary to get this working in each case. 5 | 6 | The Qt examples are a bit more complex and involve coroutines that use 0, 1, and 2 asynchronous results delivered via signal. The "normal" Qt implementation is in [qt_basic.cpp](qt_basic.cpp) while the Coroutine version is in [qt_coro.cpp](qt_coro.cpp). 7 | 8 | ## To Build 9 | 10 | It's the usual CMake flow, but your compiler needs to be a recent Clang or MSVC 2017+: 11 | 12 | mkdir build; cd build; cmake .. 13 | 14 | Common options to cmake: 15 | 16 | - Path to the compiler, usually needed for clang: `-DCMAKE_CXX_COMPILER=/path/to/clang++` 17 | 18 | - Path to Boost - needed when Boost is in a less-than-common location: `-DCMAKE_PREFIX_PATH=/path/to/boost/lib/cmake/Boost-1.XX.Y`. This variable is cached and needs to be given only once per build location. 19 | 20 | - Building a release build with debug information: `-DCMAKE_BUILD_TYPE=relwithdebinfo` 21 | 22 | ### Platform Notes 23 | 24 | - It is not necessary to pass the path to the MSVC compiler, but the build has to start from the Visual Studio Command Line. 25 | 26 | - No additional cmake options are needed on macOS. Once macPorts updates their boost to 1.77.0, it will be detected by the build and used. 27 | 28 | - gcc is just beginning to support coroutines; I haven't experimented with it yet. 29 | -------------------------------------------------------------------------------- /cb_as_coro.cpp: -------------------------------------------------------------------------------- 1 | // My callback example refactored into coroutines 2 | /* 3 | Copyright (c) 2018 Jeff Trull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | // compute a*b+c in a coroutine in two stages, awaiting the multiply result 25 | 26 | // This is the coroutine equivalent of my multiply callback code in callbacks.cpp 27 | 28 | #include 29 | #include "my_awaitable.hpp" 30 | #include "co_awaiter.hpp" 31 | 32 | await_return_object<> muladd() { 33 | int a = 2; 34 | int b = 3; 35 | int c = 4; 36 | // wait for our "long-running" task (previously handled by a callback) 37 | int product = co_await make_my_awaitable([a,b]() { return a*b; }); 38 | int result = product + c; 39 | std::cout << "result: " << result << "\n"; 40 | } 41 | 42 | int main() { 43 | auto coro = muladd(); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /run_queue.hpp: -------------------------------------------------------------------------------- 1 | // The world's lamest event loop/run queue 2 | /* 3 | Copyright (c) 2018 Jeff Trull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef RUN_QUEUE_HPP 25 | #define RUN_QUEUE_HPP 26 | 27 | #include 28 | #include 29 | 30 | // solely for the purpose of queueing up work to run later, as a way to test callbacks etc. 31 | // This run queue does as little as possible: 32 | // all you can do is queue tasks; a single thread runs them, and when there are none remaining 33 | // it exits 34 | 35 | struct run_queue { 36 | using task = std::function; 37 | 38 | template 39 | void add_task(F f) { 40 | tasks_.push(std::move(f)); 41 | } 42 | 43 | void run(); 44 | 45 | private: 46 | std::queue tasks_; 47 | }; 48 | 49 | #endif // RUN_QUEUE_HPP 50 | -------------------------------------------------------------------------------- /colorrect.h: -------------------------------------------------------------------------------- 1 | // a simple Qt colored rectangle widget, for experiments 2 | /* 3 | Copyright (c) 2018 Jeff Trull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef COLORRECT_H 25 | #define COLORRECT_H 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | class ColorRect : public QWidget 33 | { 34 | Q_OBJECT 35 | 36 | public: 37 | ColorRect(QWidget *parent = 0); 38 | 39 | public slots: 40 | void changeColor(); 41 | void setLine(QPointF, QPointF); 42 | 43 | signals: 44 | void click(QPointF); 45 | void lineCreated(QPointF, QPointF); 46 | 47 | protected: 48 | void mousePressEvent(QMouseEvent *) override; 49 | void paintEvent(QPaintEvent *event) override; 50 | 51 | private: 52 | void setColor(std::string const&); 53 | std::vector colorList; 54 | std::size_t curColor; 55 | std::optional line; 56 | }; 57 | 58 | #endif // COLORRECT_H 59 | -------------------------------------------------------------------------------- /basic_awaiter.cpp: -------------------------------------------------------------------------------- 1 | // Incorporating an Awaitable 2 | 3 | /* 4 | Copyright (c) 2018 Jeff Trull 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | #include "co_awaiter.hpp" 27 | #include "my_awaitable.hpp" 28 | 29 | namespace detail 30 | { 31 | // a piece of state accessible to all generators 32 | static int counter = 0; 33 | } 34 | 35 | // now we need a coroutine to perform co_await on my awaitable 36 | // co_await must be inside a coroutine, and main cannot be a coroutine, so... 37 | 38 | 39 | await_return_object<> try_awaiting() { 40 | auto awaitable = make_my_awaitable([](){ return detail::counter++; }); 41 | // count to five 42 | // this causes a stack overflow for large values; see my_awaitable.hpp 43 | // for an explanation 44 | for (auto i = co_await awaitable; i != 5;i = co_await awaitable) { 45 | std::cout << "not there yet: " << i << "\n"; 46 | } 47 | std::cout << "done!\n"; 48 | } 49 | 50 | int main() { 51 | auto coro = try_awaiting(); 52 | } 53 | -------------------------------------------------------------------------------- /qt_basic.cpp: -------------------------------------------------------------------------------- 1 | // Implementation of simple Qt example 2 | // cycles through background colors while display user-input lines 3 | /* 4 | Copyright (c) 2018 Jeff Trull 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include "colorrect.h" 29 | 30 | int main(int argc, char *argv[]) 31 | { 32 | QApplication app(argc, argv); 33 | 34 | // a really simple widget 35 | ColorRect cr; 36 | cr.setWindowTitle("Color Cycler"); 37 | cr.show(); 38 | 39 | // change widget color every 500ms 40 | QTimer * changeTimer = new QTimer(&app); 41 | QObject::connect(changeTimer, &QTimer::timeout, 42 | [&]() { cr.changeColor(); }); 43 | changeTimer->start(500); 44 | 45 | // draw lines from clicks 46 | bool got_first_point{false}; 47 | QPointF first_point; 48 | 49 | QObject::connect(&cr, &ColorRect::click, [&](QPointF p) { 50 | if (got_first_point) { 51 | // draw 52 | cr.setLine(first_point, p); 53 | got_first_point = false; 54 | } else { 55 | first_point = p; 56 | got_first_point = true; 57 | } 58 | }); 59 | 60 | return app.exec(); 61 | } 62 | -------------------------------------------------------------------------------- /callbacks.cpp: -------------------------------------------------------------------------------- 1 | // Doing some simple work with callbacks on a run queue 2 | /* 3 | Copyright (c) 2018 Jeff Trull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include 25 | #include "run_queue.hpp" 26 | 27 | // a representative expensive/high latency calculation 28 | // that delivers results with a callback 29 | template 30 | void multiply(int a, int b, Callback cb) { 31 | int result = a * b; 32 | cb(result); 33 | } 34 | 35 | int main() { 36 | run_queue work; 37 | 38 | // use the work queue to calculate 2*3+4 in a really dumb way 39 | // we launch a task to calculate 2*3, with a callback that adds 4 40 | // this models a long-running task (the multiply) with a finishing callback 41 | // the coroutine version is in cb_as_coro.cpp 42 | 43 | work.add_task([](run_queue* tasks) { 44 | // future coroutine 45 | int a = 2; 46 | int b = 3; 47 | int c = 4; 48 | // queue long-running task 49 | tasks->add_task([=](run_queue*) { 50 | multiply(a, b, 51 | [c](int product) { 52 | int result = product + c; 53 | std::cout << "result: " << result << "\n"; 54 | }); 55 | }); 56 | }); 57 | work.run(); 58 | } 59 | -------------------------------------------------------------------------------- /colorrect.cpp: -------------------------------------------------------------------------------- 1 | // Implementation of a simple Qt colored rectangle widget, for experiments 2 | /* 3 | Copyright (c) 2018 Jeff Trull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #include 25 | #include 26 | 27 | #include "colorrect.h" 28 | 29 | ColorRect::ColorRect(QWidget *parent) : 30 | QWidget{parent}, 31 | colorList{{"#111111", "#113311", 32 | "#111133", "#331111", 33 | "#333311", "#331133", 34 | "#661111", "#116611", 35 | "#111166", "#663311", 36 | "#661133", "#336611", 37 | "#331166", "#113366"}}, 38 | curColor{0} 39 | {} 40 | 41 | void 42 | ColorRect::setColor(std::string const& col) 43 | { 44 | setStyleSheet(("background-color:" + col).c_str()); 45 | } 46 | 47 | void 48 | ColorRect::changeColor() 49 | { 50 | curColor++; 51 | if (curColor >= colorList.size()) 52 | { 53 | curColor = 0; 54 | } 55 | setColor(colorList[curColor]); 56 | } 57 | 58 | void 59 | ColorRect::mousePressEvent(QMouseEvent *e) 60 | { 61 | emit click(e->windowPos()); 62 | } 63 | 64 | void 65 | ColorRect::paintEvent(QPaintEvent *) 66 | { 67 | if (line) { 68 | QPainter painter(this); 69 | painter.setPen(QPen{QColor{"yellow"}}); 70 | painter.drawLine(*line); 71 | } 72 | } 73 | 74 | void 75 | ColorRect::setLine(QPointF p1, QPointF p2) 76 | { 77 | line = QLineF{p1, p2}; 78 | emit lineCreated(p1, p2); 79 | } 80 | -------------------------------------------------------------------------------- /qt_coro.cpp: -------------------------------------------------------------------------------- 1 | // example use of the Coroutines TS with Qt signals and slots 2 | // This is qt_basic.cpp ported to use coroutines 3 | /* 4 | Copyright (c) 2018 Jeff Trull 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include "colorrect.h" 29 | #include "qtcoro.hpp" 30 | 31 | int main(int argc, char *argv[]) 32 | { 33 | QApplication app(argc, argv); 34 | 35 | // a really simple widget 36 | ColorRect cr; 37 | cr.setWindowTitle("Color Cycler"); 38 | cr.show(); 39 | 40 | // change widget color every 500ms 41 | QTimer * changeTimer = new QTimer(&app); 42 | auto ro = [&]() -> qtcoro::return_object<> { 43 | while (true) { 44 | co_await qtcoro::make_awaitable_signal(changeTimer, &QTimer::timeout); 45 | cr.changeColor(); 46 | } 47 | }(); 48 | 49 | changeTimer->start(500); 50 | 51 | // draw lines from clicks 52 | auto ptclick_ro = [&]() -> qtcoro::return_object<> { 53 | while (true) { 54 | QPointF first_point = co_await qtcoro::make_awaitable_signal(&cr, &ColorRect::click); 55 | QPointF second_point = co_await qtcoro::make_awaitable_signal(&cr, &ColorRect::click); 56 | cr.setLine(first_point, second_point); 57 | } 58 | }(); 59 | 60 | // listen for line creation (tests the tuple code) 61 | auto line_ro = [&]() -> qtcoro::return_object<> { 62 | while (true) { 63 | auto [p1, p2] = co_await qtcoro::make_awaitable_signal(&cr, &ColorRect::lineCreated); 64 | std::cout << "we drew a line from ("; 65 | std::cout << p1.x() << ", " << p1.y() << ") to ("; 66 | std::cout << p2.x() << ", " << p2.y() << ")\n"; 67 | } 68 | }(); 69 | 70 | return app.exec(); 71 | } 72 | 73 | -------------------------------------------------------------------------------- /meta.hpp: -------------------------------------------------------------------------------- 1 | // Metaprogramming utilities for my coroutine stuff 2 | /* 3 | Copyright (c) 2018 Jeff Trull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef UTIL_HPP 25 | #define UTIL_HPP 26 | 27 | #include 28 | #include 29 | 30 | // 31 | // Extract object type, return type, and parameters from member function 32 | // 33 | 34 | template 35 | struct member_fn_t; 36 | 37 | template 38 | struct member_fn_t { 39 | using arglist_t = std::tuple; 40 | using cls_t = C; 41 | using ret_t = R; 42 | }; 43 | 44 | // 45 | // Apply a template template "function" to all types in a std::tuple 46 | // 47 | 48 | template class MF, 49 | typename Tuple> 50 | struct apply_to_tuple; 51 | 52 | template class MF, 53 | typename... Elements> 54 | struct apply_to_tuple> { 55 | using type = std::tuple...>; 56 | }; 57 | 58 | // 59 | // Filter types in a std::tuple by a template template "predicate" 60 | // 61 | 62 | template class Predicate, 63 | typename Sequence> 64 | struct filter; 65 | 66 | template class Predicate, 67 | typename Head, 68 | typename... Elements> 69 | struct filter> { 70 | using type=std::conditional_t::value, 71 | decltype(std::tuple_cat(std::declval>(), 72 | std::declval>::type>())), 73 | typename filter>::type>; 74 | }; 75 | 76 | // terminate recursion 77 | template class Predicate> 78 | struct filter> { 79 | using type = std::tuple<>; 80 | }; 81 | 82 | #endif // UTIL_HPP 83 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required( VERSION 3.5.0 ) 2 | 3 | project( coroutine_experiments ) 4 | 5 | if ( ${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.15.0 ) 6 | # Do not insert "/W3" by default into MSVC compile options. 7 | # This quietens warning level override warnings 8 | cmake_policy( SET CMP0092 NEW ) 9 | endif() 10 | 11 | # create compile_commands.json for tools 12 | set( CMAKE_EXPORT_COMPILE_COMMANDS ON ) 13 | 14 | set( CMAKE_CXX_STANDARD 17 ) 15 | 16 | # dependencies 17 | if ( MSVC ) 18 | # default prebuilt boost installation directory 19 | set( BOOST_ROOT "c:/local/boost_1_70_0" CACHE PATH "Path to the boost install directory.") 20 | endif() 21 | find_package( Boost 1.70 COMPONENTS system ) 22 | find_package( Qt5Widgets REQUIRED ) 23 | 24 | if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 25 | add_compile_options( -Wall -Wextra -Werror ) 26 | add_compile_options( -stdlib=libc++ ) 27 | add_link_options( -stdlib=libc++ ) 28 | set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_LIBCPP_DEBUG=1" ) 29 | set( WITH_COROUTINES -fcoroutines-ts ) 30 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 31 | add_compile_options( /W4 /WX ) 32 | set( WITH_COROUTINES /await ) 33 | if ( MSVC_VERSION LESS 1920 ) 34 | add_compile_definitions( INTERNAL_VOID_SPECIALIZATION ) 35 | endif() 36 | endif() 37 | 38 | # a simple generator 39 | add_executable( mg manual_generator.cpp ) 40 | # a simple thing-that-awaits 41 | add_executable( ba basic_awaiter.cpp ) 42 | # a long-running task modeled with an execution queue with completion callbacks 43 | add_executable( cb callbacks.cpp run_queue.cpp ) 44 | # the same task done as a coroutine with co_await 45 | add_executable( cac cb_as_coro.cpp ) 46 | 47 | # Qt basic example, no coroutines 48 | QT5_WRAP_CPP( CR_MOC_SRC colorrect.h ) 49 | add_executable( qb qt_basic.cpp ${CR_MOC_SRC} colorrect.cpp ) 50 | target_link_libraries( qb Qt5::Widgets ) 51 | 52 | # Qt with coroutines 53 | add_executable( qc qt_coro.cpp ${CR_MOC_SRC} colorrect.cpp ) 54 | target_link_libraries( qc Qt5::Widgets ) 55 | 56 | foreach( target mg ba cb cac qc ) 57 | target_compile_options( ${target} PUBLIC ${WITH_COROUTINES} ) 58 | endforeach() 59 | 60 | if (${Boost_FOUND}) 61 | # Asio as the execution queue *and* co_await 62 | find_package( Threads REQUIRED ) 63 | add_executable( ac asio_coro.cpp ) 64 | target_compile_options( ac PUBLIC ${WITH_COROUTINES} ) 65 | target_link_libraries( ac PRIVATE Boost::boost Threads::Threads Boost::system ) 66 | if ( MSVC ) 67 | # work around packaging glitches in official prebuilt boost packages 68 | target_link_directories( ac PRIVATE $,${Boost_LIBRARY_DIR_DEBUG},${Boost_LIBRARY_DIR_RELEASE}> ) 69 | # silence deprecated allocator warnings originating in Boost headers 70 | target_compile_definitions( ac PRIVATE _SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING ) 71 | endif() 72 | else() 73 | message( WARNING "Boost not found; Asio example will not be built" ) 74 | endif() 75 | -------------------------------------------------------------------------------- /asio_coro.cpp: -------------------------------------------------------------------------------- 1 | // My callback example using co_await AND Asio's io_service as a run queue 2 | 3 | /* 4 | Copyright (c) 2018 Jeff Trull 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | // compute a*b+c on an Asio run queue with the multiply awaited 26 | // In a way this is a combination of the original callback example (with 27 | // lame hand-crafted run queue) and the co_awaited multiply (handled 28 | // synchronously) in cb_as_coro.cpp. 29 | 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | using boost::asio::awaitable; 39 | using boost::asio::co_spawn; 40 | using boost::asio::io_context; 41 | namespace this_coro = boost::asio::this_coro; 42 | 43 | awaitable multiply(int x, int y) { 44 | // arbitrary async operation so we suspend and resume from the run queue 45 | auto token = co_await this_coro::executor_t(); 46 | boost::asio::steady_timer t(token, 47 | boost::asio::chrono::milliseconds(50)); 48 | co_await t.async_wait(boost::asio::use_awaitable); // suspend and run something else 49 | co_return x * y; 50 | } 51 | 52 | awaitable muladd() { 53 | int a = 2; 54 | int b = 3; 55 | int c = 4; 56 | int product = co_await multiply(a, b); // runs directly, not through io_context 57 | int result = product + c; 58 | co_return result; 59 | } 60 | 61 | int main() { 62 | io_context io; 63 | 64 | // our main work 65 | co_spawn(io, // where to run 66 | [](){ return muladd(); }, // what to do 67 | [](std::exception_ptr, int result) { // completion handler 68 | std::cout << "result: " << result << "\n"; 69 | }); 70 | 71 | // something to do to show that we return to the run queue when we co_await the timer 72 | // inserted after the above, so lower priority, but gets finished first. 73 | boost::asio::post(io, []() { std::cout << "intermediate run queue task\n"; }); 74 | 75 | io.run(); 76 | 77 | } 78 | -------------------------------------------------------------------------------- /my_awaitable.hpp: -------------------------------------------------------------------------------- 1 | // A simple Awaitable type for experiments 2 | /* 3 | Copyright (c) 2018 Jeff Trull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef MY_AWAITABLE_HPP 25 | #define MY_AWAITABLE_HPP 26 | 27 | #include 28 | #include 29 | 30 | template>> 32 | struct my_awaitable { 33 | // construct with a nullary function that does something and returns a value 34 | my_awaitable(F work_fn) : work_(work_fn) {} 35 | 36 | struct awaiter { 37 | awaiter(my_awaitable* awaitable) : awaitable_(awaitable) {} 38 | 39 | bool await_ready() const noexcept { return false; } // pretend to not be ready 40 | 41 | ReturnType await_resume() noexcept { return awaitable_->work_(); } 42 | 43 | template 44 | void await_suspend(std::experimental::coroutine_handle

coro) noexcept { 45 | // Per Lewis Baker, the variant of await_suspend() that returns bool means: 46 | // true: execution returns to the caller of *our* resume() 47 | // false: execution continues immediately on the calling thread 48 | 49 | // the void-returning version which we are using is the same as returning "true" 50 | 51 | // decide we are ready after all, so resume caller of co_await 52 | coro.resume(); 53 | 54 | // we could also leave off the call to resume() and return false from a bool 55 | // version of this function. This has a crucial advantage: the awaiting coroutine 56 | // resumes without creating a new stack frame for the resume() method. 57 | // This can fix stack overflow situations when co_await is called in a loop, 58 | // but doesn't work for non-synchronous situations. 59 | // See https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer 60 | // for a detailed explanation. 61 | 62 | // Alternatively, with "symmetric transfer" (P0913R0) we can return a coroutine_handle<> 63 | // from this method and the compiler will arrange for a tail call like transfer 64 | // of control. No new stack frame is created, and it's thread-safe too: 65 | // return coro; 66 | 67 | // A final option is to return true from await_ready(), and this won't run at all. 68 | } 69 | 70 | my_awaitable* awaitable_; // remember parent 71 | 72 | }; 73 | awaiter operator co_await () { return awaiter{this}; } 74 | 75 | private: 76 | F work_; 77 | }; 78 | 79 | // type deduction helper 80 | template 81 | my_awaitable 82 | make_my_awaitable(F fn) { 83 | return my_awaitable{fn}; 84 | } 85 | 86 | #endif // MY_AWAITABLE_HPP 87 | -------------------------------------------------------------------------------- /manual_generator.cpp: -------------------------------------------------------------------------------- 1 | // My attempt at creating a simple generator that does *not* store its coroutine state using 2 | // coroutine_handle etc. but instead has its own mechanism 3 | 4 | /* 5 | Copyright (c) 2018 Jeff Trull 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | 29 | namespace detail 30 | { 31 | // a piece of state accessible to all generators 32 | static int counter = 0; 33 | } 34 | 35 | // basic generator-type coroutine (no co_await) 36 | 37 | // the return object 38 | // this is the "return value" of our coroutine, but it never gets returned 39 | // instead what happens is the coroutine is created and we are handed an instance 40 | // of this type to use to interact with the coroutine using whatever methods we 41 | // have defined here 42 | struct my_return { 43 | 44 | // the "promise type" has to be defined or declared here - it is a requirement 45 | // of the coroutine machinery and must have certain specific methods 46 | struct promise_type { 47 | 48 | promise_type() : m_current_value(-1) {} 49 | 50 | // coroutine promise requirements: 51 | 52 | auto initial_suspend() const noexcept { 53 | // we don't need to return to the creator of the coroutine prior to 54 | // doing work so: 55 | return std::experimental::suspend_never(); // produce at least one value 56 | } 57 | 58 | auto final_suspend() const noexcept { 59 | // suspend "always" if someone else destroys the coroutine 60 | // (as we do in the my_return destructor) 61 | // choose "never" if it runs off the end and destroys itself 62 | // our coroutine has an infinite loop so we will never get here but 63 | // for the sake of form: 64 | return std::experimental::suspend_always(); 65 | } 66 | 67 | void return_void() const noexcept {} 68 | 69 | my_return get_return_object() { 70 | return my_return(*this); 71 | } 72 | 73 | auto yield_value(int value) { 74 | m_current_value = value; 75 | return std::experimental::suspend_always(); 76 | } 77 | 78 | void unhandled_exception() {} // do nothing :) 79 | 80 | int m_current_value; 81 | 82 | }; 83 | 84 | // end promise requirements 85 | 86 | // my API 87 | 88 | my_return(promise_type & p) : m_coro(std::experimental::coroutine_handle::from_promise(p)) {} 89 | 90 | my_return(my_return const&) = delete; 91 | 92 | my_return(my_return && other) : m_coro(other.m_coro) { 93 | other.m_coro = nullptr; 94 | } 95 | 96 | int value() const { 97 | return m_coro.promise().m_current_value; 98 | } 99 | 100 | void advance() { 101 | // advance coroutine to next co_yield 102 | m_coro.resume(); 103 | } 104 | 105 | ~my_return() { 106 | if (m_coro) 107 | m_coro.destroy(); 108 | } 109 | 110 | private: 111 | std::experimental::coroutine_handle m_coro; 112 | }; 113 | 114 | my_return my_coro() { 115 | while (1) { 116 | co_yield detail::counter++; 117 | } 118 | } 119 | 120 | int main() { 121 | auto p = my_coro(); // immediate suspension with promise returned 122 | std::cout << p.value() << "\n"; 123 | p.advance(); 124 | std::cout << p.value() << "\n"; 125 | p.advance(); 126 | std::cout << p.value() << "\n"; 127 | } 128 | -------------------------------------------------------------------------------- /co_awaiter.hpp: -------------------------------------------------------------------------------- 1 | // A basic return object and promise type for a coroutine that co_awaits 2 | /* 3 | Copyright (c) 2018 Jeff Trull 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef CO_AWAITER_HPP 25 | #define CO_AWAITER_HPP 26 | 27 | #include 28 | #include 29 | 30 | template 31 | struct await_return_object { 32 | struct promise_type; 33 | await_return_object(promise_type & p) : m_coro(std::experimental::coroutine_handle::from_promise(p)) {} 34 | 35 | await_return_object(await_return_object const &) = delete; 36 | await_return_object(await_return_object && other) { 37 | m_coro = other.m_coro; 38 | other.m_coro = nullptr; 39 | } 40 | 41 | ~await_return_object() { 42 | if (m_coro) { 43 | m_coro.destroy(); 44 | } 45 | } 46 | 47 | 48 | // promise type must have either return_void or return_value member but not both 49 | // not even if one is SFINAEd out - you cannot have both names present, per Lewis Baker 50 | template 51 | struct promise_base { 52 | void return_value(U const&) const noexcept { 53 | } 54 | }; 55 | 56 | // void specialization to replace with return_void() is below at namespace scope 57 | // this is a workaround for certain MSVC versions: 58 | #ifdef INTERNAL_VOID_SPECIALIZATION 59 | template<> 60 | struct promise_base { 61 | void return_void() const noexcept {} 62 | }; 63 | #endif // INTERNAL_VOID_SPECIALIZATION 64 | 65 | struct promise_type : promise_base { 66 | // coroutine promise requirements: 67 | 68 | auto initial_suspend() const noexcept { 69 | // Do *not* suspend initially, because this coroutine needs to run 70 | // to establish e.g. signal/slot connections or to yield a first generator value 71 | return std::experimental::suspend_never(); 72 | 73 | // Lewis Baker suggests suspend_always here to create a "lazy" coroutine, 74 | // which removes a race between the execution of the coroutine and 75 | // attaching the continuation. We could do that here if we intentionally 76 | // added null continuations for coroutines we didn't need to await (i.e. 77 | // whose purpose was just to encapsulate co_await to make them usable 78 | // in regular functions). 79 | // See https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer 80 | // or his CppCon 2019 talk on Structured Concurrency 81 | } 82 | 83 | auto final_suspend() const noexcept { 84 | // after we "fall off" the end of the coroutine, and after locals are 85 | // destroyed, suspend before destroying the coroutine itself. We are 86 | // letting the await_return_object destructor do that, via the handle's 87 | // destroy() method 88 | return std::experimental::suspend_always(); 89 | } 90 | 91 | // either return_void or return_value will exist, depending on T 92 | 93 | await_return_object get_return_object() { 94 | return await_return_object(*this); 95 | } 96 | 97 | void unhandled_exception() {} // do nothing :) 98 | 99 | }; 100 | 101 | 102 | private: 103 | std::experimental::coroutine_handle m_coro; 104 | 105 | }; 106 | 107 | #ifndef INTERNAL_VOID_SPECIALIZATION 108 | template<> 109 | template<> 110 | struct await_return_object::promise_base { 111 | void return_void() const noexcept { 112 | } 113 | }; 114 | #endif // INTERNAL_VOID_SPECIALIZATION 115 | 116 | #endif // CO_AWAITER_HPP 117 | -------------------------------------------------------------------------------- /qtcoro.hpp: -------------------------------------------------------------------------------- 1 | // classes for using coroutines with Qt signals and slots 2 | // The goal: from within a slot, co_await on a one-shot signal 3 | 4 | /* 5 | Copyright (c) 2018 Jeff Trull 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #ifndef QTCORO_HPP 27 | #define QTCORO_HPP 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include "meta.hpp" 34 | 35 | namespace qtcoro { 36 | 37 | // 38 | // Special TS-required types 39 | // 40 | 41 | // first, the type "returned" by (more accurately, "supplied by the call expression from") our coroutines 42 | template 43 | struct return_object { 44 | struct promise_type; 45 | return_object(promise_type & p) 46 | : m_coro(std::experimental::coroutine_handle::from_promise(p)) {} 47 | 48 | return_object(return_object const &) = delete; 49 | return_object(return_object && other) { 50 | m_coro = other.m_coro; 51 | other.m_coro = nullptr; 52 | } 53 | 54 | ~return_object() { 55 | if (m_coro) { 56 | m_coro.destroy(); 57 | } 58 | } 59 | 60 | // promise type must have either return_void or return_value member but not both 61 | // not even if one is SFINAEd out - you cannot have both names present, per Lewis Baker 62 | // So this is the fix: we specialize the base and inherit 63 | template 64 | struct promise_base { 65 | void return_value(U const&) const noexcept { 66 | } 67 | }; 68 | // void specialization to replace with return_void() is below at namespace scope 69 | #ifdef INTERNAL_VOID_SPECIALIZATION 70 | template<> 71 | struct promise_base { 72 | void return_void() const noexcept {} 73 | }; 74 | #endif // INTERNAL_VOID_SPECIALIZATION 75 | 76 | struct promise_type : promise_base { 77 | // coroutine promise requirements: 78 | 79 | auto initial_suspend() const noexcept { 80 | return std::experimental::suspend_never(); // produce at least one value 81 | } 82 | 83 | auto final_suspend() const noexcept { 84 | return std::experimental::suspend_always(); // ?? not sure 85 | } 86 | 87 | // either return_void or return_value will exist, depending on T 88 | // we inherit it from promise_base 89 | 90 | return_object get_return_object() { 91 | return return_object(*this); 92 | } 93 | 94 | void unhandled_exception() {} // do nothing :) 95 | 96 | }; 97 | 98 | private: 99 | std::experimental::coroutine_handle m_coro; 100 | 101 | }; 102 | 103 | // that specialization for void values 104 | #ifndef INTERNAL_VOID_SPECIALIZATION 105 | template<> 106 | template<> 107 | struct return_object::promise_base { 108 | void return_void() const noexcept { 109 | } 110 | }; 111 | #endif // INTERNAL_VOID_SPECIALIZATION 112 | 113 | // 114 | // Slot "factory" 115 | // Gives us something to connect() signals to, that integrates with coroutines 116 | // 117 | 118 | template 119 | struct make_slot; 120 | 121 | // for two or more arguments we supply a std::tuple from the awaiter 122 | template 123 | struct make_slot, Args...> { 124 | using Result = std::tuple; 125 | auto operator()(QMetaObject::Connection& signal_conn, 126 | Result& result, 127 | std::experimental::coroutine_handle<>& coro_handle) { 128 | return [&signal_conn, &coro_handle, &result] 129 | (Args... a) { 130 | // all our awaits are one-shot, so we immediately disconnect 131 | QObject::disconnect(signal_conn); 132 | // put the result where the awaiter can supply it from await_resume() 133 | result = std::make_tuple(a...); 134 | // resume execution inside the coroutine at the co_await 135 | coro_handle.resume(); 136 | }; 137 | } 138 | }; 139 | 140 | // for a single argument it's just that particular type 141 | template 142 | struct make_slot { 143 | auto operator()(QMetaObject::Connection& signal_conn, 144 | Arg& result, 145 | std::experimental::coroutine_handle<>& coro_handle) { 146 | return [&signal_conn, &coro_handle, &result] 147 | (Arg a) { 148 | QObject::disconnect(signal_conn); 149 | result = a; 150 | coro_handle.resume(); 151 | }; 152 | } 153 | }; 154 | 155 | // no argument - result is void, don't set anything 156 | template<> 157 | struct make_slot 158 | { 159 | auto operator()(QMetaObject::Connection& signal_conn, 160 | std::experimental::coroutine_handle<>& coro_handle) { 161 | return [&signal_conn, &coro_handle]() { 162 | QObject::disconnect(signal_conn); 163 | coro_handle.resume(); 164 | }; 165 | } 166 | }; 167 | 168 | // 169 | // Awaitable class 170 | // 171 | 172 | // first, a special base to handle possibly nullary signals 173 | template 174 | struct awaitable_signal_base { 175 | // not nullary - we have a real value to set when the signal arrives 176 | template 177 | awaitable_signal_base(Object* src, void (Object::*method)(Args...), 178 | std::experimental::coroutine_handle<>& coro_handle) { 179 | signal_conn_ = 180 | QObject::connect(src, method, 181 | make_slot()(signal_conn_, derived()->signal_args_, coro_handle)); 182 | 183 | } 184 | 185 | Derived * derived() { return static_cast(this); } 186 | 187 | protected: 188 | Result signal_args_; 189 | QMetaObject::Connection signal_conn_; 190 | }; 191 | 192 | template 193 | struct awaitable_signal_base { 194 | // nullary, i.e., no arguments to signal and nothing to supply to co_await 195 | template 196 | awaitable_signal_base(Object* src, void (Object::*method)(Args...), 197 | std::experimental::coroutine_handle<>& coro_handle) { 198 | // hook up the slot version that doesn't try to store signal args 199 | signal_conn_ = QObject::connect(src, method, 200 | make_slot()(signal_conn_, coro_handle)); 201 | } 202 | 203 | protected: 204 | QMetaObject::Connection signal_conn_; 205 | // no need to store signal arguments since there are none 206 | }; 207 | 208 | 209 | // forward reference for our "signal args -> co_await result" TMP code 210 | template 211 | struct signal_args_t; 212 | 213 | // The rest of our awaitable 214 | template::type> 215 | struct awaitable_signal : awaitable_signal_base, Result> { 216 | using obj_t = typename member_fn_t::cls_t; 217 | 218 | awaitable_signal(obj_t * src, Signal method) 219 | : awaitable_signal_base(src, method, coro_handle_) {} 220 | 221 | struct awaiter { 222 | awaiter(awaitable_signal * awaitable) : awaitable_(awaitable) {} 223 | 224 | bool await_ready() const noexcept { 225 | return false; // we are waiting for the signal to arrive 226 | } 227 | 228 | template 229 | void await_suspend(std::experimental::coroutine_handle

handle) noexcept { 230 | // we have now been suspended but are able to do something before 231 | // returning to caller-or-resumer 232 | // such as storing the coroutine handle! 233 | awaitable_->coro_handle_ = handle; // store for later resumption 234 | } 235 | 236 | template 237 | typename std::enable_if_t, R> 238 | await_resume() noexcept { 239 | return awaitable_->signal_args_; 240 | } 241 | 242 | template 243 | typename std::enable_if_t, void> 244 | await_resume() noexcept {} 245 | 246 | private: 247 | awaitable_signal* awaitable_; 248 | 249 | }; 250 | 251 | awaiter operator co_await () { return awaiter{this}; } 252 | 253 | private: 254 | std::experimental::coroutine_handle<> coro_handle_; 255 | 256 | }; 257 | 258 | template 259 | awaitable_signal 260 | make_awaitable_signal(T * t, F fn) { 261 | return awaitable_signal{t, fn}; 262 | } 263 | 264 | // 265 | // some light metaprogramming 266 | // 267 | 268 | // deduce the type we want to return from co_await from the signal's signature 269 | // result of co_await should be void, one value, or a tuple of values 270 | // depending on how many parameters the signal has 271 | 272 | // produce void or T for small tuples 273 | template 274 | struct special_case_tuple { 275 | using type = T; 276 | }; 277 | 278 | // just one type 279 | template 280 | struct special_case_tuple> { 281 | using type = T; 282 | }; 283 | 284 | // empty list 285 | template<> 286 | struct special_case_tuple> { 287 | using type = void; 288 | }; 289 | 290 | // 291 | // Use a simple predicate and the filter metafunction 292 | // to make a list of non-empty types 293 | // 294 | 295 | template 296 | using not_empty = std::negation>; 297 | 298 | template 299 | struct filter_empty_types { 300 | using type = typename filter::type; 301 | }; 302 | 303 | // now put it all together: 304 | 305 | template 306 | struct signal_args_t { 307 | // get argument list 308 | using args_t = typename member_fn_t::arglist_t; 309 | using classname_t = typename member_fn_t::cls_t; 310 | // remove any empty (including "QPrivateSignal") parameters from the list 311 | using no_empty_t = typename filter_empty_types::type; 312 | // apply std::decay_t to all arg types 313 | using decayed_args_t = typename apply_to_tuple::type; 314 | // special case 0 and 1 argument 315 | using type = typename special_case_tuple::type; 316 | }; 317 | 318 | } 319 | 320 | #endif // QTCORO_HPP 321 | --------------------------------------------------------------------------------