Using folly (instead of boost promise/future) for coroutines

This commit is contained in:
Selim Mustafaev 2022-04-23 17:12:00 +03:00
parent 46a8b84869
commit f4c656ef89
9 changed files with 66 additions and 247 deletions

View File

@ -6,31 +6,24 @@ set(CMAKE_CXX_STANDARD 20)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
find_package(nlohmann_json REQUIRED) find_package(nlohmann_json REQUIRED)
find_package(Boost 1.70 COMPONENTS thread REQUIRED) find_package(folly REQUIRED)
pkg_check_modules(GTKMM REQUIRED gtkmm-4.0) pkg_check_modules(GTKMM REQUIRED gtkmm-4.0)
pkg_check_modules(GLIBMM REQUIRED glibmm-2.68) pkg_check_modules(GLIBMM REQUIRED glibmm-2.68)
pkg_check_modules(LIBSOUP REQUIRED libsoup-2.4) pkg_check_modules(LIBSOUP REQUIRED libsoup-2.4)
pkg_check_modules(LIBADWAITA REQUIRED libadwaita-1) #pkg_check_modules(LIBADWAITA REQUIRED libadwaita-1)
include_directories(${GTKMM_INCLUDE_DIRS} include_directories(${GTKMM_INCLUDE_DIRS}
${GLIBMM_INCLUDE_DIRS} ${GLIBMM_INCLUDE_DIRS}
${LIBSOUP_INCLUDE_DIRS} ${LIBSOUP_INCLUDE_DIRS})
${Boost_INCLUDE_DIR}
${LIBADWAITA_INCLUDE_DIRS})
link_directories(${GTKMM_LIBRARY_DIRS} link_directories(${GTKMM_LIBRARY_DIRS}
${GLIBMM_LIBRARY_DIRS} ${GLIBMM_LIBRARY_DIRS}
${LIBSOUP_LIBRARY_DIRS} ${LIBSOUP_LIBRARY_DIRS})
${Boost_LIBRARY_DIRS}
${LIBADWAITA_LIBRARY_DIRS})
add_definitions(-DBOOST_THREAD_PROVIDES_FUTURE -DBOOST_THREAD_PROVIDES_FUTURE_CONTINUATION -DBOOST_THREAD_PROVIDES_PROMISE_LAZY -DBOOST_THREAD_VERSION=4) add_executable(autocat_gnome main.cpp gui/MainWindow.cpp gui/MainWindow.h gui/LoginWindow.cpp gui/LoginWindow.h services/Api.cpp services/Api.h models/User.cpp models/User.h services/Settings.cpp services/Settings.h gui/TitleBar.cpp gui/TitleBar.h coro/Coro.h)
add_executable(autocat_gnome main.cpp gui/MainWindow.cpp gui/MainWindow.h gui/LoginWindow.cpp gui/LoginWindow.h services/Api.cpp services/Api.h coro/Task.h models/User.cpp models/User.h services/Settings.cpp services/Settings.h app/App.cpp app/App.h gui/TitleBar.cpp gui/TitleBar.h)
target_link_libraries(autocat_gnome ${GTKMM_LIBRARIES} target_link_libraries(autocat_gnome ${GTKMM_LIBRARIES}
${GLIBMM_LIBRARIES} ${GLIBMM_LIBRARIES}
${LIBSOUP_LIBRARIES} ${LIBSOUP_LIBRARIES}
nlohmann_json::nlohmann_json nlohmann_json::nlohmann_json
${Boost_LIBRARIES} Folly::folly)
${LIBADWAITA_LIBRARIES})

View File

@ -1,48 +0,0 @@
//
// Created by selim on 02.02.2022.
//
#include "App.h"
#include "../gui/MainWindow.h"
static void activateApp(GtkApplication *gtkApp, void* data) {
auto app = reinterpret_cast<App*>(data);
int i = 0;
auto win = std::make_shared<MainWindow>();
app->setWindow(win);
// GtkWidget *window = gtk_application_window_new (gtkApp);
// GtkWidget *label = gtk_label_new ("Hello World");
//
// gtk_window_set_title (GTK_WINDOW (window), "Hello");
// gtk_window_set_default_size (GTK_WINDOW (window), 200, 200);
// gtk_window_set_child (GTK_WINDOW (window), label);
// gtk_window_present (GTK_WINDOW (window));
}
App::App(): _app(nullptr), _window(nullptr) {
_app = adw_application_new("pro.aliencat.aliencat", G_APPLICATION_FLAGS_NONE);
g_signal_connect_data(_app, "activate", G_CALLBACK(activateApp), this, nullptr, G_CONNECT_AFTER);
}
App::~App() {
glib_autoptr_cleanup_AdwApplication(&_app);
}
int App::run(int argc, char **argv) {
return g_application_run(G_APPLICATION(_app), argc, argv);
}
void App::setWindow(std::shared_ptr<Gtk::Window> window) {
gtk_application_add_window(GTK_APPLICATION(_app), window->gobj());
if(_window) {
_window->hide();
gtk_application_remove_window(GTK_APPLICATION(_app), _window->gobj());
}
window->show();
_window = window;
}

View File

@ -1,25 +0,0 @@
//
// Created by selim on 02.02.2022.
//
#ifndef AUTOCAT_GNOME_APP_H
#define AUTOCAT_GNOME_APP_H
#include <gtkmm/window.h>
#include <adwaita.h>
#include <memory>
class App {
private:
AdwApplication* _app;
std::shared_ptr<Gtk::Window> _window;
public:
explicit App();
~App();
int run(int argc, char** argv);
void setWindow(std::shared_ptr<Gtk::Window> window);
};
#endif //AUTOCAT_GNOME_APP_H

36
coro/Coro.h Normal file
View File

@ -0,0 +1,36 @@
//
// Created by selim on 20.04.2022.
//
#ifndef AUTOCAT_GNOME_CORO_H
#define AUTOCAT_GNOME_CORO_H
#include <coroutine>
#include <exception>
#include <iostream>
template<typename... Args>
struct std::coroutine_traits<void, Args...> {
struct promise_type {
void get_return_object() noexcept {}
std::suspend_never initial_suspend() const noexcept {
return {};
}
std::suspend_never final_suspend() const noexcept {
return {};
}
void return_void() noexcept {}
void unhandled_exception() noexcept {
try {
std::rethrow_exception(std::current_exception());
} catch (std::exception& ex) {
std::cout << "Unhandled exception (in void coroutine) detected: " << ex.what() << std::endl;
}
}
};
};
#endif //AUTOCAT_GNOME_CORO_H

View File

@ -1,147 +0,0 @@
//
// Created by selim on 03.01.2022.
//
#ifndef AUTOCAT_GNOME_TASK_H
#define AUTOCAT_GNOME_TASK_H
#include <coroutine>
#include <iostream>
#include <optional>
#include <exception>
#include <boost/thread/future.hpp>
#include <boost/exception/errinfo_nested_exception.hpp>
#include <utility>
#include <glibmm.h>
class FutureException: public virtual std::exception, public virtual boost::exception {
private:
std::string _message;
public:
explicit FutureException(const char* msg): _message(msg) {}
explicit FutureException(std::string msg): _message(std::move(msg)) {}
const char* what() const noexcept {
return _message.c_str();
}
};
// Enable the use of std::future<T> as a coroutine type
// by using a std::promise<T> as the promise type.
template <typename T, typename... Args>
requires(!std::is_void_v<T> && !std::is_reference_v<T>)
struct std::coroutine_traits<boost::future<T>, Args...> {
struct promise_type : boost::promise<T> {
boost::future<T> get_return_object() noexcept {
return this->get_future();
}
std::suspend_never initial_suspend() const noexcept {
return {};
}
std::suspend_never final_suspend() const noexcept {
return {};
}
void return_value(const T &value)
noexcept(std::is_nothrow_copy_constructible_v<T>) {
this->set_value(value);
}
void return_value(T &&value)
noexcept(std::is_nothrow_move_constructible_v<T>) {
this->set_value(std::move(value));
}
void unhandled_exception() noexcept {
this->set_exception(boost::current_exception());
}
};
};
// Same for std::future<void>.
template <typename... Args>
struct std::coroutine_traits<boost::future<void>, Args...> {
struct promise_type : boost::promise<void> {
boost::future<void> get_return_object() noexcept {
return this->get_future();
}
std::suspend_never initial_suspend() const noexcept {
return {};
}
std::suspend_never final_suspend() const noexcept {
return {};
}
void return_void() noexcept {
this->set_value();
}
void unhandled_exception() noexcept {
this->set_exception(boost::current_exception());
}
};
};
template<typename... Args>
struct std::coroutine_traits<void, Args...> {
struct promise_type {
void get_return_object() noexcept {}
std::suspend_never initial_suspend() const noexcept {
return {};
}
std::suspend_never final_suspend() const noexcept {
return {};
}
void return_void() noexcept {}
void unhandled_exception() noexcept {
try {
boost::rethrow_exception(boost::current_exception());
} catch (std::exception& ex) {
std::cout << "Unhandled exception (in void coroutine) detected: " << ex.what() << std::endl;
}
}
};
};
template <typename T>
auto operator co_await(boost::future<T> future) noexcept requires(!std::is_reference_v<T>) {
struct awaiter : public boost::future<T> {
bool await_ready() const noexcept {
return this->is_ready();
}
void await_suspend(std::coroutine_handle<> cont) {
this->then([this, cont](boost::future<T>&& fut){
// future was moved out of awaiter to this lambda
// so, we need to bring it back
this->swap(fut);
// Resume coroutine on the main thread (i.e. GMainContext)
g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, awaiter::resumeContinuation, (gpointer)&cont, nullptr);
});
}
T await_resume() {
return this->get();
}
static int resumeContinuation(void* data) {
auto handler = reinterpret_cast<std::coroutine_handle<>*>(data);
if(*handler) {
handler->resume();
}
return G_SOURCE_REMOVE;
}
};
return awaiter{std::move(future)};
}
#endif //AUTOCAT_GNOME_TASK_H

View File

@ -9,6 +9,8 @@
#include <gtkmm/application.h> #include <gtkmm/application.h>
#include <gtkmm/box.h> #include <gtkmm/box.h>
#include <iostream> #include <iostream>
#include <folly/experimental/coro/Task.h>
#include <folly/executors/IOThreadPoolExecutor.h>
LoginWindow::LoginWindow() { LoginWindow::LoginWindow() {
set_title("Login"); set_title("Login");
@ -48,7 +50,7 @@ void LoginWindow::loginClicked() {
_spinner.start(); _spinner.start();
try { try {
auto user = co_await Api::login(email, password); User user = co_await Api::login(email, password).scheduleOn(folly::getGlobalIOExecutor());
auto app = this->get_application(); auto app = this->get_application();
auto mainWindow = new MainWindow(); auto mainWindow = new MainWindow();
mainWindow->show(); mainWindow->show();

View File

@ -1,7 +1,6 @@
#include "gui/MainWindow.h" #include "gui/MainWindow.h"
#include "gui/LoginWindow.h" #include "gui/LoginWindow.h"
#include "services/Settings.h" #include "services/Settings.h"
#include "app/App.h"
#include <gtkmm/application.h> #include <gtkmm/application.h>
#include <glibmm.h> #include <glibmm.h>
#include <gtkmm.h> #include <gtkmm.h>

View File

@ -5,6 +5,7 @@
#include "Api.h" #include "Api.h"
#include "Settings.h" #include "Settings.h"
#include <memory> #include <memory>
#include <folly/futures/Promise.h>
template<class...Args> template<class...Args>
struct Callback { struct Callback {
@ -30,37 +31,41 @@ const std::string Api::_baseUrl = "https://vps.aliencat.pro:8443/";
SoupSession* Api::_session = soup_session_new(); SoupSession* Api::_session = soup_session_new();
template<typename T> template<typename T>
boost::future<T> Api::post(const std::string &method, const nlohmann::json& params) { folly::Future<T> Api::post(const std::string &method, const nlohmann::json& params) {
std::string url = _baseUrl + method; std::string url = _baseUrl + method;
auto msg = soup_message_new(SOUP_METHOD_POST, url.c_str()); auto msg = soup_message_new(SOUP_METHOD_POST, url.c_str());
auto promise = std::make_shared<boost::promise<T>>(); auto promise = std::make_shared<folly::Promise<T>>();
auto callback = voidify<SoupSession*, SoupMessage*>([&, promise](SoupSession* session, SoupMessage* message) { auto callback = voidify<SoupSession*, SoupMessage*>([&, promise](SoupSession* session, SoupMessage* message) {
if(message->status_code >= 200 && message->status_code < 300) { if(message->status_code >= 200 && message->status_code < 300) {
auto responseString = std::string(message->response_body->data, message->response_body->length); auto responseString = std::string(message->response_body->data, message->response_body->length);
auto json = nlohmann::json::parse(responseString); auto json = nlohmann::json::parse(responseString);
if(json["success"].get<bool>()) { if(json["success"].get<bool>()) {
std::cout << "response: " << responseString << std::endl; //std::cout << "response: " << responseString << std::endl;
auto user = json["data"].get<T>(); auto user = json["data"].get<T>();
promise->set_value(user); promise->setValue(user);
} else { } else {
auto error = json["error"].get<std::string>(); auto error = json["error"].get<std::string>();
promise->set_exception(FutureException(error)); promise->setException(std::runtime_error(error));
} }
} else { } else {
promise->set_exception(FutureException(message->reason_phrase)); promise->setException(std::runtime_error(message->reason_phrase));
} }
}); });
auto jsonStr = params.dump(); auto jsonStr = params.dump();
soup_message_set_request(msg, "application/json", SOUP_MEMORY_COPY, jsonStr.c_str(), jsonStr.size()); soup_message_set_request(msg, "application/json",
SOUP_MEMORY_COPY,
jsonStr.c_str(),
jsonStr.size());
soup_session_queue_message(_session, msg, callback.function, callback.state); soup_session_queue_message(_session, msg, callback.function, callback.state);
return promise->get_future(); return promise->getFuture();
} }
boost::future<User> Api::login(std::string email, std::string password) { fc::Task<User> Api::login(std::string email, std::string password) {
nlohmann::json params = { nlohmann::json params = {
{ "email", email }, { "email", email },

View File

@ -8,9 +8,13 @@
#include <string> #include <string>
#include <libsoup/soup.h> #include <libsoup/soup.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <folly/futures/Future.h>
#include <folly/experimental/coro/Task.h>
#include "../models/User.h" #include "../models/User.h"
#include "../coro/Task.h" #include "../coro/Coro.h"
namespace fc = folly::coro;
class Api { class Api {
private: private:
@ -19,10 +23,10 @@ private:
private: private:
template<typename T> template<typename T>
static boost::future<T> post(const std::string& method, const nlohmann::json& params); static folly::Future<T> post(const std::string& method, const nlohmann::json& params);
public: public:
static boost::future<User> login(std::string email, std::string password); static fc::Task<User> login(std::string email, std::string password);
}; };