Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
KrystianD committed Dec 15, 2021
0 parents commit 812a865
Show file tree
Hide file tree
Showing 24 changed files with 777 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
BasedOnStyle: Google

IndentWidth: 2
ColumnLimit: 120

SpaceAfterTemplateKeyword: false
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.idea/
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.10)
project(asyncpp)

option(BUILD_EXAMPLES "Build examples" OFF)

add_subdirectory(asyncpp)
add_subdirectory(asyncpp_uv)
add_subdirectory(asyncpp_uv_curl)

if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
asynccpp
=====

C++20 event-loop agnostic coroutines (`co_await`/`co_return`) implementation + support for libuv and curl.

## Modules

### asyncpp

Event loop-agnostic implementation of C++20 coroutines (`co_await`/`co_return`).

Provides `makeTask` function which takes a callback with `resolve` and `reject` passed as arguments. Similar to how JavaScript `new Promise` constructor works.

### asyncpp_uv

libuv library wrappers to be used in coroutine-based program.

Implemented functions:

* `uvSleep` - waits passed amount of time before continuing.

### asyncpp_uv_curl

libuv based, coroutine API for curl.

## Example

```c++
#include <asyncpp_uv/asyncpp_uv_sleep.h>
#include <uv.h>

asyncpp::task<void> asyncMain() {
printf("asyncMain BEGIN\n");
auto timer1 = asyncpp_uv::uvSleep(1000);
auto timer2 = asyncpp_uv::uvSleep(1000);
co_await timer1;
co_await timer2;
printf("asyncMain END\n");
}

int main() {
asyncMain();
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
return 0;
}
```
4 changes: 4 additions & 0 deletions asyncpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
add_library(asyncpp STATIC src/asyncpp.cpp)
target_include_directories(asyncpp PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
target_compile_options(asyncpp PUBLIC -fcoroutines)
set_property(TARGET asyncpp PROPERTY CXX_STANDARD 20)
2 changes: 2 additions & 0 deletions asyncpp/include/asyncpp/asyncpp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#include "asyncpp_base.h"
#include "asyncpp_make_task.h"
178 changes: 178 additions & 0 deletions asyncpp/include/asyncpp/asyncpp_base.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Based on https://github.com/jimspr/awaituv
*/

#pragma once

#include <cassert>
#include <coroutine>
#include <functional>
#include <memory>

namespace asyncpp {
enum struct future_error {
not_ready, // get_value called when value not available
};

struct future_exception : std::exception {
future_error _error;
explicit future_exception(future_error fe) : _error(fe) {}
};

struct awaitable_state_base {
std::function<void(void)> _coro;
bool _ready = false;

awaitable_state_base() = default;
awaitable_state_base(awaitable_state_base&&) = delete;
awaitable_state_base(const awaitable_state_base&) = delete;

void set_coroutine_callback(const std::function<void(void)>& cb) {
// Test to make sure nothing else is waiting on this future.
assert(((cb == nullptr) || (_coro == nullptr)) && "This future is already being awaited.");
_coro = cb;
}

void set_value() {
// Set all members first as calling coroutine may reset stuff here.
_ready = true;
auto coro = _coro;
_coro = nullptr;
if (coro != nullptr) coro();
}

[[nodiscard]] bool ready() const { return _ready; }
};

template<typename TValue>
struct awaitable_state : public awaitable_state_base {
TValue _value;

void set_value(TValue&& t) {
_value = std::move(t);
awaitable_state_base::set_value();
}

TValue&& get_value() {
if (!_ready) throw future_exception(future_error::not_ready);
return std::move(_value);
}
};

// specialization of awaitable_state<void>
template<>
struct awaitable_state<void> : public awaitable_state_base {
void get_value() const {
if (!_ready) throw future_exception(future_error::not_ready);
}
};

template<typename T>
struct promise_t;

template<typename T>
struct task {
// promise_type declaration required by C++20 coroutines
typedef promise_t<T> promise_type;

std::shared_ptr<awaitable_state<T>> _state;
std::shared_ptr<void> _customState;

task() = default;

explicit task(std::shared_ptr<awaitable_state<T>>& state) : _state(state) {}

task(std::shared_ptr<awaitable_state<T>>& state, std::shared_ptr<void> custom_state)
: _state(state), _customState(std::move(custom_state)) {}

// not copyable
task(const task&) = delete;
task& operator=(const task&) = delete;
// movable
task(task&&) noexcept = default;
task& operator=(task&&) noexcept = default;

T await_resume() const { return std::move(_state->get_value()); }

[[nodiscard]] bool await_ready() const { return _state->_ready; }

void await_suspend(std::coroutine_handle<> resume_cb) { _state->set_coroutine_callback(resume_cb); }
};

template<>
struct task<void> {
// promise_type declaration required by C++20 coroutines
typedef promise_t<void> promise_type;

std::shared_ptr<awaitable_state<void>> _state;
std::shared_ptr<void> _customState;

task() = default;

explicit task(std::shared_ptr<awaitable_state<void>>& state) : _state(state) {}

task(std::shared_ptr<awaitable_state<void>>& state, std::shared_ptr<void> custom_state)
: _state(state), _customState(std::move(custom_state)) {}

// not copyable
task(const task&) = delete;
task& operator=(const task&) = delete;
// movable
task(task&&) noexcept = default;
task& operator=(task&&) noexcept = default;

void await_resume() const {}

[[nodiscard]] bool await_ready() const { return _state->_ready; }

void await_suspend(std::coroutine_handle<> resume_cb) { _state->set_coroutine_callback(resume_cb); }
};

template<typename T>
struct promise_t {
std::shared_ptr<awaitable_state<T>> state;

promise_t() : state(std::make_shared<awaitable_state<T>>()) {}

// not copyable
promise_t(const promise_t&) = delete;
promise_t& operator=(const promise_t&) = delete;
// movable
promise_t(promise_t&&) noexcept = default;
promise_t& operator=(promise_t&&) noexcept = default;

task<T> get_return_object() { return task(state); }

[[nodiscard]] std::suspend_never initial_suspend() const noexcept { return {}; }
[[nodiscard]] std::suspend_never final_suspend() const noexcept { return {}; }

void return_value(T&& val) { state->set_value(std::move(val)); }

void return_value(const T& val) { state->set_value(val); }

[[noreturn]] void unhandled_exception() { std::terminate(); }
};

template<>
struct promise_t<void> {
std::shared_ptr<awaitable_state<void>> state;

explicit promise_t() : state(std::make_shared<awaitable_state<void>>()) {}

// not copyable
promise_t(const promise_t&) = delete;
promise_t& operator=(const promise_t&) = delete;
// movable
promise_t(promise_t&&) = default;
promise_t& operator=(promise_t&&) = default;

task<void> get_return_object() { return task(state); }

[[nodiscard]] std::suspend_never initial_suspend() const noexcept { return {}; }
[[nodiscard]] std::suspend_never final_suspend() const noexcept { return {}; }

void return_void() { state->set_value(); }

[[noreturn]] void unhandled_exception() { std::terminate(); }
};
} // namespace asyncpp
57 changes: 57 additions & 0 deletions asyncpp/include/asyncpp/asyncpp_make_task.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#pragma once

#include <functional>

namespace asyncpp {
template<typename T>
using ResolveResultCb = std::function<void(T&&)>;

using ResolveVoidCb = std::function<void()>;

template<typename T, typename TState>
[[maybe_unused]] task<T> makeTask(const std::function<void(TState&, ResolveResultCb<T>)>& cb) {
auto state = std::make_shared<awaitable_state<T>>();

std::shared_ptr<TState> customState = std::make_shared<TState>();

ResolveResultCb<T> resolve = [state](const T& value) { state->set_value(value); };

cb(*customState, resolve);

return task<T>(state, customState);
}

template<typename TState>
[[maybe_unused]] task<void> makeTask(const std::function<void(TState&, ResolveVoidCb)>& cb) {
auto state = std::make_shared<awaitable_state<void>>();

std::shared_ptr<TState> customState = std::make_shared<TState>();

ResolveVoidCb resolve = [state]() { state->set_value(); };

cb(*customState, resolve);

return task<void>(state, customState);
}

template<typename T>
[[maybe_unused]] task<T> makeTask(const std::function<void(ResolveResultCb<T>&)>& cb) {
auto state = std::make_shared<awaitable_state<T>>();

ResolveResultCb<T> resolve = [state](T&& value) { state->set_value(std::move(value)); };

cb(resolve);

return task<T>(state);
}

[[maybe_unused]] static task<void> makeTask(const std::function<void(ResolveVoidCb)>& cb) {
auto state = std::make_shared<awaitable_state<void>>();

ResolveVoidCb resolve = [state]() { state->set_value(); };

cb(resolve);

return task<void>(state);
}
} // namespace asyncpp
Empty file added asyncpp/src/asyncpp.cpp
Empty file.
7 changes: 7 additions & 0 deletions asyncpp_uv/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
add_library(asyncpp_uv STATIC
src/asyncpp_uv_wrapper.cpp
src/asyncpp_uv_sleep.cpp
)
target_link_libraries(asyncpp_uv PUBLIC asyncpp -luv)
target_include_directories(asyncpp_uv PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
set_property(TARGET asyncpp_uv PROPERTY CXX_STANDARD 20)
2 changes: 2 additions & 0 deletions asyncpp_uv/include/asyncpp_uv/asyncpp_uv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#include "asyncpp_uv_sleep.h"
#include "asyncpp_uv_wrapper.h"
11 changes: 11 additions & 0 deletions asyncpp_uv/include/asyncpp_uv/asyncpp_uv_sleep.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <asyncpp/asyncpp.h>

#include <chrono>
#include <cstdint>

namespace asyncpp_uv {
asyncpp::task<void> uvSleep(uint64_t timeoutMs);
asyncpp::task<void> uvSleep(const std::chrono::milliseconds& duration);
} // namespace asyncpp_uv
10 changes: 10 additions & 0 deletions asyncpp_uv/include/asyncpp_uv/asyncpp_uv_wrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include <uv.h>

#include <cstdint>
#include <functional>

namespace asyncpp_uv::wrapper {
void uvTimerStart(uv_loop_t* loop, uint64_t timeoutMs, const std::function<void()>& cb);
}
10 changes: 10 additions & 0 deletions asyncpp_uv/src/asyncpp_uv_sleep.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <asyncpp_uv/asyncpp_uv.h>
#include <asyncpp_uv/asyncpp_uv_sleep.h>

namespace asyncpp_uv {
asyncpp::task<void> uvSleep(uint64_t timeoutMs) {
return asyncpp::makeTask([timeoutMs](auto resolve) { wrapper::uvTimerStart(uv_default_loop(), timeoutMs, resolve); });
}

asyncpp::task<void> uvSleep(const std::chrono::milliseconds& duration) { return uvSleep((uint64_t)duration.count()); }
} // namespace asyncpp_uv
27 changes: 27 additions & 0 deletions asyncpp_uv/src/asyncpp_uv_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include <asyncpp_uv/asyncpp_uv_wrapper.h>
#include <uv.h>

#include <functional>

namespace asyncpp_uv::wrapper {
void uvCloseDelete(void* handle) {
uv_close((uv_handle_t*)handle, [](uv_handle_t* handleInner) { delete handleInner; });
}

void uvTimerStart(uv_loop_t* loop, uint64_t timeoutMs, const std::function<void()>& cb) {
uv_timer_t* handle = new uv_timer_t();
handle->data = new std::function<void()>(cb);

uv_timer_init(loop, handle);
uv_timer_start(
handle,
[](uv_timer_t* handleInner) {
std::function<void()>* data = (std::function<void()>*)handleInner->data;
(*data)();
uv_timer_stop(handleInner);
delete data;
uvCloseDelete(handleInner);
},
timeoutMs, 0);
}
} // namespace asyncpp_uv::wrapper
Loading

0 comments on commit 812a865

Please sign in to comment.