├── .gitignore ├── README.md ├── index.hxx ├── package.json └── test └── index.cxx /.gitignore: -------------------------------------------------------------------------------- 1 | /deps 2 | /test/index -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SYNOPSIS 2 | Provides coroutine-based async/await and minimal promises. 3 | 4 | # ASYNC/AWAIT 5 | ## USAGE 6 | - Any function that returns an `Async` type becomes a coroutine. 7 | - Use `co_return` instead of `return` for coroutines. 8 | - Only coroutines can use `co_await`, and only coroutines are awaitable. 9 | - Awaitable functions have a `get` method to retrieve the value from the promise. 10 | 11 | ## EXAMPLE 12 | 13 | ```c++ 14 | auto answer = []() -> Async { 15 | // 16 | // ...possibly do something async here, 17 | // this could also be a regular function. 18 | // 19 | co_return 42; 20 | }; 21 | 22 | auto question = []() -> Async { 23 | // 24 | // await the promise created by the answer function. 25 | // 26 | auto n = co_await answer(); 27 | 28 | // 29 | // use co_return instead of return! 30 | // 31 | co_return n; 32 | }; 33 | 34 | auto v = question().get(); // v == 42 35 | ``` 36 | 37 | Refer to [this][0] paper for specifics about coroutines in C++. 38 | 39 | # PROMISE 40 | ```c++ 41 | Promise p; 42 | Timeout timeout; 43 | 44 | timeout.start([&] { 45 | p.resolve(42); 46 | }, 512); 47 | 48 | auto v = p.await(); // blocks until resolved is called. 49 | ``` 50 | 51 | [0]:https://isocpp.org/files/papers/N4663.pdf 52 | -------------------------------------------------------------------------------- /index.hxx: -------------------------------------------------------------------------------- 1 | #ifndef HYPER_UTIL_ASYNC_H 2 | #define HYPER_UTIL_ASYNC_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Hyper { 8 | namespace Util { 9 | template struct Async { 10 | struct promise_type; 11 | using handle_type = std::experimental::coroutine_handle; 12 | handle_type coro; 13 | 14 | Async(handle_type h) : coro(h) {} 15 | 16 | Async(const Async &) = delete; 17 | Async(Async &&s) : coro(s.coro) { 18 | s.coro = nullptr; 19 | } 20 | 21 | ~Async() { 22 | if (coro) { 23 | coro.destroy(); 24 | } 25 | } 26 | 27 | Async &operator = (const Async &) = delete; 28 | Async &operator = (Async &&s) { 29 | coro = s.coro; 30 | s.coro = nullptr; 31 | return *this; 32 | } 33 | 34 | T get() { 35 | if (!this->coro.done()) { 36 | this->coro.resume(); 37 | } 38 | 39 | auto p = coro.promise(); 40 | return p.value; 41 | } 42 | 43 | auto operator co_await() { 44 | struct awaitable_type { 45 | handle_type coro; 46 | 47 | bool await_ready() { 48 | const auto ready = coro.done(); 49 | return coro.done(); 50 | } 51 | 52 | void await_suspend(std::experimental::coroutine_handle<> awaiting) { 53 | coro.resume(); 54 | awaiting.resume(); 55 | } 56 | 57 | auto await_resume() { 58 | const auto r = coro.promise().value; 59 | return r; 60 | } 61 | }; 62 | 63 | return awaitable_type{coro}; 64 | } 65 | 66 | 67 | struct promise_type { 68 | T value; 69 | 70 | promise_type() {} 71 | ~promise_type() {} 72 | 73 | auto get_return_object() { 74 | return Async{handle_type::from_promise(*this)}; 75 | } 76 | 77 | // lazy, not lazy 78 | auto initial_suspend() { 79 | // return std::experimental::suspend_never{}; 80 | return std::experimental::suspend_always{}; 81 | } 82 | 83 | auto return_value(T v) { 84 | value = v; 85 | return std::experimental::suspend_never{}; 86 | } 87 | 88 | auto final_suspend() { 89 | return std::experimental::suspend_always{}; 90 | } 91 | 92 | void unhandled_exception() { 93 | std::exit(1); 94 | } 95 | }; 96 | }; 97 | 98 | template struct Promise { 99 | std::promise p; 100 | std::shared_future f; 101 | 102 | T await () { 103 | return f.get(); 104 | } 105 | 106 | void resolve (T val) { 107 | p.set_value(val); 108 | } 109 | 110 | Promise () { 111 | this->f = p.get_future(); 112 | } 113 | }; 114 | } // namespace Util 115 | } // namespace Hypercore 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async", 3 | "description": "Provides coroutine-based async/await and minimal promises.", 4 | "repository": { 5 | "type": "git", 6 | "url": "git@github.com:datcxx/cxx-async.git" 7 | }, 8 | "dependencies": { 9 | "git@github.com:datcxx/cxx-timers": "7d2ff2b7" 10 | }, 11 | "license": "MIT", 12 | "scripts": { 13 | "test": [ 14 | "clang++ -stdlib=libc++ -std=c++2a", 15 | "-fcoroutines-ts -lpthread", 16 | "test/index.cxx -o test/index && ./test/index" 17 | ], 18 | "install": "" 19 | }, 20 | "flags": [ 21 | "-std=c++2a", 22 | "-fcoroutines-ts", 23 | "-O3" 24 | ], 25 | "files": [ 26 | "index.hxx" 27 | ], 28 | "devDependencies": { 29 | "git@github.com:heapwolf/cxx-tap": "07821de0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/index.cxx: -------------------------------------------------------------------------------- 1 | #include "../deps/heapwolf/cxx-tap/index.hxx" 2 | #include "../index.hxx" 3 | 4 | #include 5 | 6 | int main() { 7 | TAP::Test t; 8 | 9 | t.test("sanity", [](auto t) { 10 | t->ok(true, "true is true"); 11 | t->end(); 12 | }); 13 | 14 | t.test("await", [] (auto t) { 15 | using namespace Hyper::Util; 16 | using namespace std::chrono; 17 | 18 | auto answer = []() -> Async { 19 | // 20 | // for fun let's do something async in here 21 | // using std::async, wait, get and return. 22 | // 23 | auto a = std::async(std::launch::async, [&] { 24 | std::this_thread::sleep_for(seconds(2)); 25 | return 42; 26 | }); 27 | 28 | a.wait(); 29 | auto n = a.get(); 30 | 31 | co_return n; 32 | }; 33 | 34 | auto question = [&]() -> Async { 35 | auto a = answer(); 36 | auto v1 = co_await a; 37 | auto v2 = co_await a; 38 | 39 | t->equal(v1, v2, "awaited values are the same every time"); 40 | 41 | co_return v2; 42 | }; 43 | 44 | auto n = question().get(); 45 | t->equal(n, 42, "final value of async functions is correct"); 46 | 47 | t->end(); 48 | }); 49 | 50 | t.test("promise", [] (auto t) { 51 | Hyper::Util::Promise p; 52 | Util::Timeout timeout; 53 | 54 | std::vector order; 55 | 56 | order.push_back(0); 57 | 58 | timeout.start([&] { 59 | order.push_back(2); 60 | t->ok(true, "promise resolved from timer thread"); 61 | p.resolve(42); 62 | }, 512); 63 | 64 | order.push_back(1); 65 | 66 | auto v = p.await(); 67 | 68 | t->equal(order[0], 0); 69 | t->equal(order[1], 1); 70 | t->equal(order[2], 2); 71 | t->equal(v, 42); 72 | t->end(); 73 | }); 74 | 75 | t.test("another sanity check", [](auto t) { 76 | using namespace std::chrono; 77 | 78 | auto f1 = std::async(std::launch::async, [&] { 79 | std::this_thread::sleep_for(seconds(2)); 80 | return 1; 81 | }); 82 | 83 | auto f2 = std::async(std::launch::async, [&] { 84 | std::this_thread::sleep_for(seconds(2)); 85 | return 1; 86 | }); 87 | 88 | Util::Timer t1; 89 | f1.wait(); 90 | f2.wait(); 91 | 92 | auto round = [](int number) { 93 | return ((number + 1000 / 2) / 1000) * 1000; 94 | }; 95 | 96 | t->equal(2000, round(t1.ms())); 97 | 98 | Util::Timer t2; 99 | std::this_thread::sleep_for(seconds(2)); 100 | std::this_thread::sleep_for(seconds(2)); 101 | 102 | t->equal(4000, round(t2.ms())); 103 | t->end(); 104 | }); 105 | } 106 | --------------------------------------------------------------------------------