From aca2724e0cae20157cb44ead3acb0ab435a3c631 Mon Sep 17 00:00:00 2001 From: marcelb Date: Fri, 28 Mar 2025 23:00:01 +0100 Subject: [PATCH 1/7] Support Boost.Asio coroutine, clean, rename --- CMakeLists.txt | 2 +- README.md | 51 ++++++++- lib/asynco.hpp | 63 ++++++++--- lib/define.hpp | 4 + lib/timers.hpp | 28 +++-- lib/trigger.hpp | 12 +-- src/timers.cpp | 77 ++++---------- test/main.cpp | 273 ++++++++++++++++++++++++++++++++---------------- 8 files changed, 322 insertions(+), 188 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 681174e..b89631e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(Asynco) # Postavi verziju projekta -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Pronađi Boost biblioteku (ako nije uobičajeni direktorijum, postavi put) diff --git a/README.md b/README.md index 900a4b9..ffec742 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ The asynchronous filesystem is provided solely to guide users on how to wrap any - Multiple parallel execution loops - Asynchronous file IO - Based on ASIO (Boost Asio) +- On C++20 support Boost.Asio coroutines ## Installation Just download the latest release and unzip it into your project. @@ -36,7 +37,6 @@ Just download the latest release and unzip it into your project. using namespace marcelb; using namespace asynco; -using namespace triggers; // At the end of the main function, always set _asynco_engine.run(); @@ -419,6 +419,55 @@ try { ``` +## Coroutine + +If `define.hpp` is included, you can initialize coroutines using `coroutine`; if not, just use `boost::asio::awaitable`. + +```c++ + +coroutine c2(int a) { + co_return a * 2; +} + +``` +To run the coroutine at runtime, simply call: +```c++ + +async_(c2(4)); + +``` +Or using a lambda expression: + +```c++ + +async_([]() -> coroutine { + std::cout << "Hello" << std::endl; + co_await c2(4); + co_return; +}()); + +``` +To retrieve results from coroutines, you can do so as you would from classical functions by calling `await_`: +```c++ + +int r = await_( + async_( + c2(10) +)); + +``` +Timers and triggers work the same with coroutines; it is important to call the coroutine with `async_` in the callback, and to call `async_`, wrap it with a lambda expression: + +```c++ + +Periodic p([]() { + async_(c2(34)); +}, 2000); + +``` +If you need a result, you can also retrieve it with `await_`. + + ## License [APACHE 2.0](http://www.apache.org/licenses/LICENSE-2.0/) diff --git a/lib/asynco.hpp b/lib/asynco.hpp index f06a492..8ab5253 100644 --- a/lib/asynco.hpp +++ b/lib/asynco.hpp @@ -3,9 +3,14 @@ #include "engine.hpp" #include - using namespace std; +#if __cplusplus >= 202002L +#include +#include +#include +#endif + namespace marcelb { namespace asynco { @@ -19,6 +24,34 @@ auto async_(F&& f, Args&&... args) -> future::typ return res; } +#if __cplusplus >= 202002L +/** + * Run the coroutine asynchronously +*/ +template +std::future async_(boost::asio::awaitable _coroutine) { + std::promise promise; + auto future = promise.get_future(); + + co_spawn(_asynco_engine.io_context, [_coroutine = std::move(_coroutine), promise = std::move(promise)]() mutable -> boost::asio::awaitable { + try { + if constexpr (!std::is_void_v) { + T result = co_await std::move(_coroutine); + promise.set_value(std::move(result)); + } else { + co_await std::move(_coroutine); + promise.set_value(); // Za void ne postavljamo rezultat + } + } catch (...) { + promise.set_exception(std::current_exception()); // Postavljamo izuzetak + } + }, boost::asio::detached); + + return future; +} + +#endif + /** * Block until the asynchronous call completes */ @@ -38,24 +71,24 @@ T await_(future&& r) { /** * Block until the asynchronous call completes or time expired */ -template -T await_(future& r, uint64_t time) { - if (r.wait_for(chrono::milliseconds(time)) == std::future_status::timeout) { - throw runtime_error("Asynchronous execution timed out"); - } - return r.get(); -} +// template +// T await_(future& r, uint64_t time) { +// if (r.wait_for(chrono::milliseconds(time)) == std::future_status::timeout) { +// throw runtime_error("Asynchronous execution timed out"); +// } +// return r.get(); +// } /** * Block until the asynchronous call completes or time expired */ -template -T await_(future&& r, uint64_t time) { - if (r.wait_for(chrono::milliseconds(time)) == std::future_status::timeout) { - throw runtime_error("Asynchronous execution timed out"); - } - return move(r).get(); -} +// template +// T await_(future&& r, uint64_t time) { +// if (r.wait_for(chrono::milliseconds(time)) == std::future_status::timeout) { +// throw runtime_error("Asynchronous execution timed out"); +// } +// return move(r).get(); +// } } } diff --git a/lib/define.hpp b/lib/define.hpp index bf2629a..721f484 100644 --- a/lib/define.hpp +++ b/lib/define.hpp @@ -11,6 +11,10 @@ namespace asynco { #define async_ marcelb::asynco::async_ #define await_ marcelb::asynco::await_ +#if __cplusplus >= 202002L +#define coroutine boost::asio::awaitable +#endif + } } diff --git a/lib/timers.hpp b/lib/timers.hpp index c972166..7dc6fe5 100644 --- a/lib/timers.hpp +++ b/lib/timers.hpp @@ -24,7 +24,7 @@ int64_t rtime_us(); /** * Core timer class for construct time async functions */ -class timer { +class Timer { boost::asio::steady_timer st; bool _stop = false; bool repeate; @@ -35,14 +35,13 @@ class timer { /** * A method to assign a callback wrapper and a reinitialization algorithm */ - void init(); - + void init(); public: /** * The constructor creates the steady_timer and accompanying variables and runs a method to initialize the timer */ - timer (function _callback, uint64_t _time, bool _repeate); + Timer (function _callback, uint64_t _time, bool _repeate); /** * Stop timer @@ -68,21 +67,21 @@ class timer { /** * The destructor stops the timer */ - ~timer(); + ~Timer(); }; /** * Class periodic for periodic execution of the callback in time in ms */ -class periodic { - shared_ptr _timer; +class Periodic { + shared_ptr _timer; public: /** * Constructor initializes a shared pointer of type timer */ - periodic(function callback, uint64_t time); + Periodic(function callback, uint64_t time); /** * Stop periodic @@ -108,21 +107,21 @@ class periodic { /** * The destructor stops the periodic */ - ~periodic(); + ~Periodic(); }; /** * Class delayed for delayed callback execution in ms */ -class delayed { - shared_ptr _timer; +class Delayed { + shared_ptr _timer; public: /** * Constructor initializes a shared pointer of type timer */ - delayed(function callback, uint64_t time); + Delayed(function callback, uint64_t time); /** * Stop delayed @@ -147,13 +146,10 @@ class delayed { /** * The destructor stops the delayed */ - ~delayed(); + ~Delayed(); }; -shared_ptr Periodic(function callback, uint64_t time); -shared_ptr Delayed(function callback, uint64_t time); - } } diff --git a/lib/trigger.hpp b/lib/trigger.hpp index 30c43ca..0b37ca1 100644 --- a/lib/trigger.hpp +++ b/lib/trigger.hpp @@ -11,14 +11,13 @@ using namespace std; #include "engine.hpp" namespace marcelb { namespace asynco { -namespace triggers { /** - * trigger class, for event-driven programming. + * Trigger class, for event-driven programming. * These events are typed according to the arguments of the callback function */ template -class trigger { +class Trigger { private: mutex m_eve; unordered_map>> triggers; @@ -48,7 +47,7 @@ class trigger { } /** - * Remove an trigger listener from an event + * Remove an Trigger listener from an event */ void off(const string& key) { lock_guard _off(m_eve); @@ -56,7 +55,7 @@ class trigger { } /** - * Remove all trigger listener + * Remove all Trigger listener */ void off() { lock_guard _off(m_eve); @@ -65,7 +64,7 @@ class trigger { /** - * Get num of listeners by an trigger key + * Get num of listeners by an Trigger key */ unsigned int listeners(const string& key) { return triggers[key].size(); @@ -85,7 +84,6 @@ class trigger { }; -} } } diff --git a/src/timers.cpp b/src/timers.cpp index 747f785..04ff1fd 100644 --- a/src/timers.cpp +++ b/src/timers.cpp @@ -14,7 +14,7 @@ int64_t rtime_us() { .count(); } -void timer::init() { +void Timer::init() { st.async_wait( [this] (const boost::system::error_code&) { if (!_stop) { callback(); @@ -27,7 +27,7 @@ void timer::init() { }); } -timer::timer (function _callback, uint64_t _time, bool _repeate) : +Timer::Timer (function _callback, uint64_t _time, bool _repeate) : st(_asynco_engine.io_context, boost::asio::chrono::milliseconds(_time)), _stop(false), repeate(_repeate), @@ -37,109 +37,74 @@ timer::timer (function _callback, uint64_t _time, bool _repeate) : init(); } -void timer::stop() { +void Timer::stop() { _stop = true; st.cancel(); } -void timer::now() { +void Timer::now() { st.cancel(); } -uint64_t timer::ticks() { +uint64_t Timer::ticks() { return _ticks; } -bool timer::stoped() { +bool Timer::stoped() { return _stop; } -timer::~timer() { +Timer::~Timer() { stop(); } -periodic::periodic(function callback, uint64_t time) : - _timer(make_shared (callback, time, true)) { +Periodic::Periodic(function callback, uint64_t time) : + _timer(make_shared (callback, time, true)) { } -void periodic::stop() { +void Periodic::stop() { _timer->stop(); } -void periodic::now() { +void Periodic::now() { _timer->now(); } -uint64_t periodic::ticks() { +uint64_t Periodic::ticks() { return _timer->ticks(); } -bool periodic::stoped() { +bool Periodic::stoped() { return _timer->stoped(); } -periodic::~periodic() { +Periodic::~Periodic() { stop(); } -delayed::delayed(function callback, uint64_t time) : - _timer(make_shared (callback, time, false)) { +Delayed::Delayed(function callback, uint64_t time) : + _timer(make_shared (callback, time, false)) { } -void delayed::stop() { +void Delayed::stop() { _timer->stop(); } -void delayed::now() { +void Delayed::now() { _timer->now(); } -bool delayed::expired() { +bool Delayed::expired() { return bool(_timer->ticks()); } -bool delayed::stoped() { +bool Delayed::stoped() { return _timer->stoped(); } -delayed::~delayed() { +Delayed::~Delayed() { stop(); } -mutex p_io, d_io; -vector> periodic_calls_container; -vector> delayed_calls_container; - -shared_ptr Periodic(function callback, uint64_t time) { - shared_ptr periodic_ptr(make_shared(callback, time)); - async_ ( [&, periodic_ptr](){ - lock_guard lock(p_io); - periodic_calls_container.push_back(periodic_ptr); - for (uint32_t i=0; istoped()) { - periodic_calls_container.erase(periodic_calls_container.begin()+i); - i--; - } - } - }); - return periodic_ptr; -} - -shared_ptr Delayed(function callback, uint64_t time) { - shared_ptr delayed_ptr(make_shared(callback, time)); - async_ ( [&, delayed_ptr](){ - lock_guard lock(p_io); - delayed_calls_container.push_back(delayed_ptr); - for (uint32_t i=0; istoped() || delayed_calls_container[i]->expired()) { - delayed_calls_container.erase(delayed_calls_container.begin()+i); - i--; - } - } - }); - return delayed_ptr; -} - - }; diff --git a/test/main.cpp b/test/main.cpp index 98defce..5902bc2 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -7,7 +7,6 @@ #include "define.hpp" using namespace marcelb::asynco; -using namespace triggers; #include #include @@ -18,80 +17,92 @@ using namespace triggers; using namespace std; using namespace this_thread; +coroutine c2 (int a) { + co_return a*2; +} -void sleep_to (int _time) { - promise _promise; - delayed t( [&]() { - _promise.set_value(); - }, _time); - return _promise.get_future().get(); -} +coroutine c () { + cout << "Ispisi" << endl; + co_await c2(0); + co_return; +} -void promise_reject (int _time) { - promise _promise; - delayed t( [&]() { - try { - // simulate except - throw runtime_error("Error simulation"); - _promise.set_value(); - } catch (...) { - _promise.set_exception(current_exception()); - } - }, _time); - return _promise.get_future().get(); -} -void notLambdaFunction() { - cout << "Call to not lambda function" << endl; -} +// void sleep_to (int _time) { +// promise _promise; +// Delayed t( [&]() { +// _promise.set_value(); +// }, _time); -class clm { - public: - void classMethode() { - cout << "Call class method" << endl; - } -}; +// return _promise.get_future().get(); +// } -// ------------------ EXTEND OWN CLASS WITH EVENTS ------------------- +// void promise_reject (int _time) { +// promise _promise; +// Delayed t( [&]() { +// try { +// // simulate except +// throw runtime_error("Error simulation"); +// _promise.set_value(); +// } catch (...) { +// _promise.set_exception(current_exception()); +// } +// }, _time); -class myOwnClass : public trigger { - public: - myOwnClass() : trigger() {}; -}; +// return _promise.get_future().get(); +// } + +// void notLambdaFunction() { +// cout << "Call to not lambda function" << endl; +// } + +// class clm { +// public: +// void classMethode() { +// cout << "Call class method" << endl; +// } +// }; + +// // ------------------ EXTEND OWN CLASS WITH EVENTS ------------------- + +// class myOwnClass : public Trigger { +// public: +// myOwnClass() : Trigger() {}; +// }; // ----------------- MULTIPLE TRIGGERS IN ONE CLASS ------------------ -class ClassWithTriggers { - trigger emitter1; - trigger emitter2; +// class ClassWithTriggers { +// Trigger emitter1; +// Trigger emitter2; -public: - template - void on(const string& key, function callback) { - if constexpr (sizeof...(T) == 1 && is_same_v>, int>) { - emitter1.on(key, callback); - } - else if constexpr (sizeof...(T) == 1 && is_same_v>, string>) { - emitter2.on(key, callback); - } - } +// public: +// template +// void on(const string& key, function callback) { +// if constexpr (sizeof...(T) == 1 && is_same_v>, int>) { +// emitter1.on(key, callback); +// } +// else if constexpr (sizeof...(T) == 1 && is_same_v>, string>) { +// emitter2.on(key, callback); +// } +// } - template - void tick(const string& key, Args&&... args) { - if constexpr (sizeof...(Args) == 1 && is_same_v>, int>) { - emitter1.tick(key, forward(args)...); - } - else if constexpr (sizeof...(Args) == 1 && is_same_v>, string>) { - emitter2.tick(key, forward(args)...); - } - else { - static_assert(sizeof...(Args) == 0, "Unsupported number or types of arguments"); - } - } -}; +// template +// void tick(const string& key, Args&&... args) { +// if constexpr (sizeof...(Args) == 1 && is_same_v>, int>) { +// emitter1.tick(key, forward(args)...); +// } +// else if constexpr (sizeof...(Args) == 1 && is_same_v>, string>) { +// emitter2.tick(key, forward(args)...); +// } +// else { +// static_assert(sizeof...(Args) == 0, "Unsupported number or types of arguments"); +// } +// } +// }; int main () { @@ -101,36 +112,36 @@ int main () { // --------------- TIME ASYNCHRONOUS FUNCTIONS -------------- /** - * Init periodic and delayed; clear periodic and delayed + * Init Periodic and delayed; clear Periodic and delayed */ - // periodic inter1 ([&]() { - // cout << "periodic prvi " << rtime_ms() - start << endl; + // Periodic inter1 ([&]() { + // cout << "Periodic prvi " << rtime_ms() - start << endl; // }, 1000); - // periodic inter2 ([&]() { - // cout << "periodic drugi " << rtime_ms() - start << endl; + // Periodic inter2 ([&]() { + // cout << "Periodic drugi " << rtime_ms() - start << endl; // }, 2000); - // periodic inter3 ([&]() { - // cout << "periodic treći " << rtime_ms() - start << endl; + // Periodic inter3 ([&]() { + // cout << "Periodic treći " << rtime_ms() - start << endl; // }, 1000); - // periodic inter4 ([&]() { - // // cout << "periodic cetvrti " << rtime_ms() - start << endl; + // Periodic inter4 ([&]() { + // // cout << "Periodic cetvrti " << rtime_ms() - start << endl; // cout << "Ticks " << inter3.ticks() << endl; // }, 500); - // periodic inter5 ([&]() { - // cout << "periodic peti " << rtime_ms() - start << endl; + // Periodic inter5 ([&]() { + // cout << "Periodic peti " << rtime_ms() - start << endl; // }, 2000); - // periodic inter6 ([&]() { - // cout << "periodic sesti " << rtime_ms() - start << endl; + // Periodic inter6 ([&]() { + // cout << "Periodic sesti " << rtime_ms() - start << endl; // }, 3000); - // delayed time1 ( [&] () { - // cout << "Close periodic 1 i 2 " << rtime_ms() - start << endl; + // Delayed time1 ( [&] () { + // cout << "Close Periodic 1 i 2 " << rtime_ms() - start << endl; // inter1.stop(); // cout << "inter1.stop " << endl; // inter2.stop(); @@ -138,8 +149,8 @@ int main () { // }, 8000); - // delayed time2 ([&] () { - // cout << "Close periodic 3 " << rtime_ms() - start << endl; + // Delayed time2 ([&] () { + // cout << "Close Periodic 3 " << rtime_ms() - start << endl; // inter3.stop(); // cout << "Stoped " << inter3.stoped() << endl; // // time1.stop(); @@ -244,7 +255,7 @@ int main () { // })) << endl; // /** - // * Sleep with delayed sleep implement + // * Sleep with Delayed sleep implement // */ // sleep_to(3000); @@ -335,9 +346,9 @@ int main () { // * initialization of typed events // */ - // trigger ev2int; - // trigger evintString; - // trigger<> evoid; + // Trigger ev2int; + // Trigger evintString; + // Trigger<> evoid; // ev2int.on("sum", [](int a, int b) { // cout << "Sum " << a+b << endl; @@ -390,7 +401,7 @@ int main () { // myOwnClass myclass; - // delayed t( [&] { + // Delayed t( [&] { // myclass.tick("constructed", 1); // }, 200); @@ -404,18 +415,18 @@ int main () { // * // */ - ClassWithTriggers mt; + // ClassWithTriggers mt; - mt.on("int", function([&](int i) { - cout << "Emit int " << i << endl; - })); + // mt.on("int", function([&](int i) { + // cout << "Emit int " << i << endl; + // })); - mt.on("string", function([&](string s) { - cout << "Emit string " << s << endl; - })); + // mt.on("string", function([&](string s) { + // cout << "Emit string " << s << endl; + // })); - mt.tick("int", 5); - mt.tick("string", string("Hello world")); + // mt.tick("int", 5); + // mt.tick("string", string("Hello world")); // auto status = fs::read("test1.txt"); @@ -446,6 +457,84 @@ int main () { // // ---------------------------------------------------------------------------------------------------- + + // auto i = async_ ( []() -> coroutine { + // cout << "aaaa" << endl; + // co_return 5; + // }); + + // auto i = async_ (retint()); + + // auto i_ = await_(i); + + // cout << i_ << endl; + + + // Periodic a( []() -> coroutine { + // cout << "corutina" << endl; + // // co_await retint(); + // }, 2000); + + + // Periodic b( []() { + // cout << "funckija" << endl; + // }, 2000); + + + // Trigger ev2int; + // Trigger evintString; + Trigger<> evoid; + + // ev2int.on("sum", [](int a, int b) -> coroutine { + // cout << "Sum " << a+b << endl; + // }); + + // ev2int.on("sum", [](int a, int b) -> coroutine { + // cout << "Sum done" << endl; + // }); + + // evintString.on("substract", [](int a, string b) -> coroutine { + // cout << "Substract " << a-stoi(b) << endl; + // }); + + evoid.on("void", []() { + auto a = await_ (async_ (c2(34))); + cout << "A " << a << endl; + }); + + + // auto c1 = []() -> coroutine { + // cout << "Roge " << endl; + // co_return; + + // }; + + // async_ ( c2(3)); + + async_ ([]() -> coroutine { + cout << "Hello" << endl; + co_await c2(4); + co_return; + }()); + + Periodic p( []() { + auto a = await_ (async_ (c2(34))); + cout << "A " << a << endl; + }, 2000); + + // await_( async_co2 ( [c1 = move(c1)]() -> coroutine { + // cout << "Baba roga" << endl; + // co_await c1(); + // })); + + // string emited2 = "2"; + + // evoid.on("void", [&]() -> coroutine { + // cout << "Void emited " << emited2 << endl; + // }); + + evoid.tick("void"); + cout << "Run" << endl; _asynco_engine.run(); From 9965f1e1c7e8c67ddef0e6109f8a1e862aebdc52 Mon Sep 17 00:00:00 2001 From: marcelb Date: Fri, 28 Mar 2025 23:14:34 +0100 Subject: [PATCH 2/7] Rename definition for awaitable coruotines --- README.md | 6 +++--- lib/define.hpp | 2 +- test/main.cpp | 34 ++++++++++++++++++---------------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index ffec742..67e9fe0 100644 --- a/README.md +++ b/README.md @@ -421,11 +421,11 @@ try { ## Coroutine -If `define.hpp` is included, you can initialize coroutines using `coroutine`; if not, just use `boost::asio::awaitable`. +If `define.hpp` is included, you can initialize coroutines using `asyncable`; if not, just use `boost::asio::awaitable`. ```c++ -coroutine c2(int a) { +asyncable c2(int a) { co_return a * 2; } @@ -440,7 +440,7 @@ Or using a lambda expression: ```c++ -async_([]() -> coroutine { +async_([]() -> asyncable { std::cout << "Hello" << std::endl; co_await c2(4); co_return; diff --git a/lib/define.hpp b/lib/define.hpp index 721f484..b14acf4 100644 --- a/lib/define.hpp +++ b/lib/define.hpp @@ -12,7 +12,7 @@ namespace asynco { #define await_ marcelb::asynco::await_ #if __cplusplus >= 202002L -#define coroutine boost::asio::awaitable +#define asyncable boost::asio::awaitable #endif } diff --git a/test/main.cpp b/test/main.cpp index 5902bc2..d6a7c4b 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -17,13 +17,13 @@ using namespace marcelb::asynco; using namespace std; using namespace this_thread; -coroutine c2 (int a) { +asyncable c2 (int a) { co_return a*2; } -coroutine c () { +asyncable c () { cout << "Ispisi" << endl; co_await c2(0); co_return; @@ -458,7 +458,7 @@ int main () { // // ---------------------------------------------------------------------------------------------------- - // auto i = async_ ( []() -> coroutine { + // auto i = async_ ( []() -> asyncable { // cout << "aaaa" << endl; // co_return 5; // }); @@ -470,7 +470,7 @@ int main () { // cout << i_ << endl; - // Periodic a( []() -> coroutine { + // Periodic a( []() -> asyncable { // cout << "corutina" << endl; // // co_await retint(); // }, 2000); @@ -485,15 +485,15 @@ int main () { // Trigger evintString; Trigger<> evoid; - // ev2int.on("sum", [](int a, int b) -> coroutine { + // ev2int.on("sum", [](int a, int b) -> asyncable { // cout << "Sum " << a+b << endl; // }); - // ev2int.on("sum", [](int a, int b) -> coroutine { + // ev2int.on("sum", [](int a, int b) -> asyncable { // cout << "Sum done" << endl; // }); - // evintString.on("substract", [](int a, string b) -> coroutine { + // evintString.on("substract", [](int a, string b) -> asyncable { // cout << "Substract " << a-stoi(b) << endl; // }); @@ -503,33 +503,35 @@ int main () { }); - // auto c1 = []() -> coroutine { + // auto c1 = []() -> asyncable { // cout << "Roge " << endl; // co_return; // }; - // async_ ( c2(3)); + async_ ( c2(3)); - async_ ([]() -> coroutine { + + async_ ([]() -> asyncable { cout << "Hello" << endl; co_await c2(4); co_return; }()); - Periodic p( []() { - auto a = await_ (async_ (c2(34))); - cout << "A " << a << endl; - }, 2000); + // Periodic p( []() { + // async_ ( + // c2(34) + // ); + // }, 2000); - // await_( async_co2 ( [c1 = move(c1)]() -> coroutine { + // await_( async_co2 ( [c1 = move(c1)]() -> asyncable { // cout << "Baba roga" << endl; // co_await c1(); // })); // string emited2 = "2"; - // evoid.on("void", [&]() -> coroutine { + // evoid.on("void", [&]() -> asyncable { // cout << "Void emited " << emited2 << endl; // }); From ce2c22676baa234630d84ce045d03b5884a7a9af Mon Sep 17 00:00:00 2001 From: marcelb Date: Fri, 28 Mar 2025 23:18:49 +0100 Subject: [PATCH 3/7] Rename in readme --- README.md | 40 +++++++++------------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 67e9fe0..b0b2db5 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ int t = inter1.ticks(); bool stoped = inter1.stoped(); // start delayed -delayed time1 ( [] () { +Delayed time1 ( [] () { cout << "Timeout 1 " << endl; }, 10000); @@ -77,28 +77,6 @@ int t = time1.expired(); // is it stopped bool stoped = time1.stoped(); -// If you don't want to save in a variable, but you want to start a timer, use these functions -// And you can also save them, they are only of the shared pointer type - -auto d = Delayed( [](){ - cout << "Delayed" << endl; -}, 2000); - -auto p = Periodic( [](){ - cout << "Periodic" << endl; -}, 700); - -Periodic( [&] (){ - cout << "Delayed expire " << d->expired() << endl; - cout << "Periodic ticks " << p->ticks() << endl; - cout << "Delayed stoped " << d->stoped() << endl; - cout << "Periodic stoped " << p->stoped() << endl; -}, 1000); - -Delayed( [&](){ - p->stop(); -}, 10000); - ``` Make functions asynchronous @@ -265,9 +243,9 @@ Events * initialization of typed events */ -trigger ev2int; -trigger evintString; -trigger<> evoid; +Trigger ev2int; +Trigger evintString; +Trigger<> evoid; ev2int.on("sum", [](int a, int b) { cout << "Sum " << a+b << endl; @@ -312,14 +290,14 @@ evoid.tick("void"); // nothing is happening Extend own class whit events ```c++ -class myOwnClass : public trigger { +class myOwnClass : public Trigger { public: - myOwnClass() : trigger() {}; + myOwnClass() : Trigger() {}; }; myOwnClass myclass; -delayed t( [&] { +Delayed t( [&] { myclass.tick("constructed", 1); }, 200); @@ -334,8 +312,8 @@ Implementing a class with multiple triggers of different types ```c++ class ClassWithTriggers { - trigger emitter1; - trigger emitter2; + Trigger emitter1; + Trigger emitter2; public: template From d546ca9db89a605f612537f8b438a6a10e6dc00a Mon Sep 17 00:00:00 2001 From: marcelb Date: Fri, 28 Mar 2025 23:20:13 +0100 Subject: [PATCH 4/7] Rename in readme 2 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b0b2db5..8b67d29 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Time asynchronous functions ```c++ // start periodic -periodic inter1 ([]() { +Periodic inter1 ([]() { cout << "Interval 1" << endl; }, 1000); @@ -202,7 +202,7 @@ auto await_all = [&] () { void sleep_to (int _time) { promise _promise; - delayed t( [&]() { + Delayed t( [&]() { _promise.set_value(); }, _time); @@ -217,7 +217,7 @@ sleep_to(3000); void promise_reject (int _time) { promise _promise; - delayed t( [&]() { + Delayed t( [&]() { try { // simulate except throw runtime_error("Error simulation"); From 5608eab8eb85397204a0fbbafd9b7f5d6335a08f Mon Sep 17 00:00:00 2001 From: marcelb Date: Sat, 29 Mar 2025 08:57:30 +0100 Subject: [PATCH 5/7] Add await_ to run on runtime and await_ - short code --- README.md | 77 +++++++++++++++++++++++++++++++++++++------------- lib/asynco.hpp | 29 ++++++++++++++++++- test/main.cpp | 62 ++++++++++++++++++++++++++++++++-------- 3 files changed, 137 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 8b67d29..6d36554 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # Asynco -A C++ library for event-driven asynchronous multi-threaded programming. +A C++ library for event-driven asynchronous multi-threaded programming that serves as a runtime for asynchronous operations. It acts as a wrapper around the Boost.Asio library, providing a cleaner way to write asynchronous, concurrent, and parallel code utilizing a set of threads and an event loops. It offers features for event-driven programming, timers, and coroutine support. ## Motivation -The original concept was to create an interface capable of asynchronously calling any function. It has since evolved into a library that incorporates a thread pool, each with its own event loop, event-driven programming, and functions inherently designed for asynchronous operation (including periodic and delayed functions). +The initial goal was to create an interface that makes it easy and clean to asynchronously invoke any function in C++ without resorting to complex calls. Initially, the library was built around a custom implementation of a scheduling loop for queuing functions. However, this part was later replaced with Boost.Asio, mainly for its timer functionality. As the library evolved, it expanded to include a thread pool, each with its own event loop, and adopted event-driven programming. This enhancement also introduced functions specifically designed for asynchronous operations, including periodic and delayed execution. -The asynchronous filesystem is provided solely to guide users on how to wrap any time- or IO-intensive function for asynchronous execution. +The asynchronous filesystem was included solely to demonstrate how users can wrap any time- or I/O-intensive functions for asynchronous execution. ## Features @@ -16,7 +16,7 @@ The asynchronous filesystem is provided solely to guide users on how to wrap any - Header only - Asynchronous programming - Multithread -- Asynchronous timer functions: periodic, delayed (like setInterval and setTimeout from JS) +- Asynchronous timer functions: Periodic, Delayed (like setInterval and setTimeout from JS) - Typed events (on, tick, off) (like EventEmitter from JS: on, emit, etc) - Event loops - Multiple parallel execution loops @@ -31,9 +31,10 @@ Just download the latest release and unzip it into your project. #define NUM_OF_RUNNERS 8 // To change the number of threads used by asynco, without this it runs according to the number of cores #include "asynco/lib/asynco.hpp" // async_ (), await_() -#include "asynco/lib/triggers.hpp" // trigger (event emitter) -#include "asynco/lib/timers.hpp" // periodic, delayed (like setInterval and setTimeout from JS) +#include "asynco/lib/triggers.hpp" // Trigger (event emitter) +#include "asynco/lib/timers.hpp" // Periodic, Delayed (like setInterval and setTimeout from JS) #include "asynco/lib/filesystem.hpp" // for async read and write files +#include "asynco/lib/define.hpp" // async_, await_, asyncable_ defines using namespace marcelb; using namespace asynco; @@ -46,7 +47,11 @@ return 0; ## Usage -Time asynchronous functions +In the following sections, we will explore timers, function execution via the runtime, asynchronous invocation, and waiting for results. We will cover essential use cases involving triggers, file handling, and coroutines. + +### Timers + +We have two timer classes, Periodic (which runs a callback function periodically), and Delayed (delayed runs a callback function only once). ```c++ // start periodic @@ -78,7 +83,9 @@ int t = time1.expired(); bool stoped = time1.stoped(); ``` -Make functions asynchronous +### Make functions asynchronous + +Running functions at runtime, asynchronous execution, uses the `async_` call and its return type is `std::future` ```c++ /** @@ -117,12 +124,11 @@ clm classes; async_ ( [&classes] () { classes.classMethode(); }); +``` +To wait for the result (blocking the flow) use `await_` (basically nothing more than a `.get()` call on a future object) - -/** -* await_ after runned as async -*/ +```c++ auto a = async_ ( []() { sleep_for(2s); // only for simulating long duration function @@ -142,10 +148,20 @@ cout << await_(async_ ( [] () { return 4; })) << endl; +``` -/** - * Await all - **/ +If you want to run asynchronously but need the result immediately, you can use a shorter notation + +```c++ + +await_ ([]() { + cout << "Hello" << endl; +}); + +``` +If multiple function calls do not depend on each other, you can call them and wait for the results later, better concurrency. + +```c++ auto a = async_ ( []() { cout << "A" << endl; @@ -196,9 +212,14 @@ auto await_all = [&] () { } }; +``` +Just an example: + +```c++ + /** -* Sleep with delayed sleep implement -*/ + * Sleep with delayed sleep implement + **/ void sleep_to (int _time) { promise _promise; @@ -236,7 +257,10 @@ try { cout<< err.what() << endl; } ``` -Events + +### Triggers + +The library implements Triggers, which are basically typed Events. ```c++ /** @@ -355,7 +379,7 @@ mt.tick("string", string("Hello world")); ``` - +Another example: Asynchronous file IO ```c++ @@ -434,6 +458,21 @@ int r = await_( )); ``` + +If you need the result immediately, you can use a shorter notation + +```c++ + +auto a = await_ ( c2(3)); +cout << a << endl; + +await_ ([]() -> asyncable { + cout << "Hello" << endl; + co_return; +}()); + +``` + Timers and triggers work the same with coroutines; it is important to call the coroutine with `async_` in the callback, and to call `async_`, wrap it with a lambda expression: ```c++ diff --git a/lib/asynco.hpp b/lib/asynco.hpp index 8ab5253..dbe0de1 100644 --- a/lib/asynco.hpp +++ b/lib/asynco.hpp @@ -26,7 +26,7 @@ auto async_(F&& f, Args&&... args) -> future::typ #if __cplusplus >= 202002L /** - * Run the coroutine asynchronously + * Run the coroutine */ template std::future async_(boost::asio::awaitable _coroutine) { @@ -68,6 +68,33 @@ T await_(future&& r) { return move(r).get(); } +/** + * Run the function asynchronously an block until completes +*/ +template +auto await_(F&& f, Args&&... args) -> typename result_of::type { + return await_( + async_(f, args...) + ); +} + + +#if __cplusplus >= 202002L + +/** + * Run the coruotine and wait + */ +template +T await_(boost::asio::awaitable _coroutine) { + return await_( + async_( + move(_coroutine) + )); +} + +#endif + + /** * Block until the asynchronous call completes or time expired */ diff --git a/test/main.cpp b/test/main.cpp index d6a7c4b..ecc5fa5 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -21,6 +21,11 @@ asyncable c2 (int a) { co_return a*2; } +asyncable sleep_co (int a) { + sleep(a); + cout << "Gotov" << endl; + co_return; +} asyncable c () { @@ -483,7 +488,7 @@ int main () { // Trigger ev2int; // Trigger evintString; - Trigger<> evoid; + // Trigger<> evoid; // ev2int.on("sum", [](int a, int b) -> asyncable { // cout << "Sum " << a+b << endl; @@ -497,10 +502,10 @@ int main () { // cout << "Substract " << a-stoi(b) << endl; // }); - evoid.on("void", []() { - auto a = await_ (async_ (c2(34))); - cout << "A " << a << endl; - }); + // evoid.on("void", []() { + // auto a = await_ (async_ (c2(34))); + // cout << "A " << a << endl; + // }); // auto c1 = []() -> asyncable { @@ -509,15 +514,51 @@ int main () { // }; - async_ ( c2(3)); + // auto a = await_ ( c2(3)); + // cout << a << endl; + // await_ ([]() -> asyncable { + // cout << "Hello" << endl; + // co_await c2(4); + // co_return; + // }()); + async_ ([]() -> asyncable { - cout << "Hello" << endl; - co_await c2(4); + cout << "1" << endl; + co_await sleep_co(1); co_return; }()); + async_ ([]() -> asyncable { + cout << "2" << endl; + co_await sleep_co(1); + co_return; + }()); + + async_ ([]() -> asyncable { + cout << "3" << endl; + co_await sleep_co(1); + co_return; + }()); + + async_ ([]() -> asyncable { + cout << "4" << endl; + co_await sleep_co(1); + co_return; + }()); + + async_ ([]() -> asyncable { + cout << "5" << endl; + co_await sleep_co(1); + co_return; + }()); + + + // await_ ([]() { + // cout << "Hello" << endl; + // }); + // Periodic p( []() { // async_ ( // c2(34) @@ -535,11 +576,10 @@ int main () { // cout << "Void emited " << emited2 << endl; // }); - evoid.tick("void"); + // evoid.tick("void"); - cout << "Run" << endl; + cout << "-------------end main-------------" << endl; _asynco_engine.run(); - return 0; } From d37f85f728554f7d8910ad00f9906b312e9433b5 Mon Sep 17 00:00:00 2001 From: marcelb Date: Tue, 10 Jun 2025 11:32:21 +0200 Subject: [PATCH 6/7] Limit Nonblocking loop await --- lib/asynco.hpp | 22 +++++++++++++++ test/main.cpp | 76 +++++++++++++++++++++++++------------------------- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/lib/asynco.hpp b/lib/asynco.hpp index dbe0de1..3fac408 100644 --- a/lib/asynco.hpp +++ b/lib/asynco.hpp @@ -68,6 +68,28 @@ T await_(future&& r) { return move(r).get(); } +/** + * Block until the asynchronous call completes +*/ +template +T await_(future& r, uint32_t time_us = 10) { + while (r.wait_for(std::chrono::microseconds(time_us)) != std::future_status::ready) { + _asynco_engine.io_context.poll_one(); + } + return r.get(); +} + +/** + * Block until the asynchronous call completes +*/ +template +T await_(future&& r, uint32_t time_us = 10) { + while (r.wait_for(std::chrono::microseconds(time_us)) != std::future_status::ready) { + _asynco_engine.io_context.poll_one(); + } + return move(r).get(); +} + /** * Run the function asynchronously an block until completes */ diff --git a/test/main.cpp b/test/main.cpp index ecc5fa5..0088a6e 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -17,22 +17,22 @@ using namespace marcelb::asynco; using namespace std; using namespace this_thread; -asyncable c2 (int a) { - co_return a*2; -} +// asyncable c2 (int a) { +// co_return a*2; +// } -asyncable sleep_co (int a) { - sleep(a); - cout << "Gotov" << endl; - co_return; -} +// asyncable sleep_co (int a) { +// sleep(a); +// cout << "Gotov" << endl; +// co_return; +// } -asyncable c () { - cout << "Ispisi" << endl; - co_await c2(0); - co_return; -} +// asyncable c () { +// cout << "Ispisi" << endl; +// co_await c2(0); +// co_return; +// } @@ -524,35 +524,35 @@ int main () { // co_return; // }()); - async_ ([]() -> asyncable { - cout << "1" << endl; - co_await sleep_co(1); - co_return; - }()); + // async_ ([]() -> asyncable { + // cout << "1" << endl; + // co_await sleep_co(1); + // co_return; + // }()); - async_ ([]() -> asyncable { - cout << "2" << endl; - co_await sleep_co(1); - co_return; - }()); + // async_ ([]() -> asyncable { + // cout << "2" << endl; + // co_await sleep_co(1); + // co_return; + // }()); - async_ ([]() -> asyncable { - cout << "3" << endl; - co_await sleep_co(1); - co_return; - }()); + // async_ ([]() -> asyncable { + // cout << "3" << endl; + // co_await sleep_co(1); + // co_return; + // }()); - async_ ([]() -> asyncable { - cout << "4" << endl; - co_await sleep_co(1); - co_return; - }()); + // async_ ([]() -> asyncable { + // cout << "4" << endl; + // co_await sleep_co(1); + // co_return; + // }()); - async_ ([]() -> asyncable { - cout << "5" << endl; - co_await sleep_co(1); - co_return; - }()); + // async_ ([]() -> asyncable { + // cout << "5" << endl; + // co_await sleep_co(1); + // co_return; + // }()); // await_ ([]() { From 8d7796998f8f195806479cb77b99b47b7744fc69 Mon Sep 17 00:00:00 2001 From: marcelb Date: Tue, 10 Jun 2025 15:07:54 +0200 Subject: [PATCH 7/7] Fix block loop in await_ --- lib/asynco.hpp | 47 +++---------------------------- test/main.cpp | 75 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 54 insertions(+), 68 deletions(-) diff --git a/lib/asynco.hpp b/lib/asynco.hpp index 3fac408..eed2375 100644 --- a/lib/asynco.hpp +++ b/lib/asynco.hpp @@ -53,26 +53,10 @@ std::future async_(boost::asio::awaitable _coroutine) { #endif /** - * Block until the asynchronous call completes + * Block until the asynchronous call completes - dont block asynco engine loop */ template -T await_(future& r) { - return r.get(); -} - -/** - * Block until the asynchronous call completes -*/ -template -T await_(future&& r) { - return move(r).get(); -} - -/** - * Block until the asynchronous call completes -*/ -template -T await_(future& r, uint32_t time_us = 10) { +T await_(future& r, uint16_t time_us = 10) { while (r.wait_for(std::chrono::microseconds(time_us)) != std::future_status::ready) { _asynco_engine.io_context.poll_one(); } @@ -80,10 +64,10 @@ T await_(future& r, uint32_t time_us = 10) { } /** - * Block until the asynchronous call completes + * Block until the asynchronous call completes - dont block asynco engine loop */ template -T await_(future&& r, uint32_t time_us = 10) { +T await_(future&& r, uint16_t time_us = 10) { while (r.wait_for(std::chrono::microseconds(time_us)) != std::future_status::ready) { _asynco_engine.io_context.poll_one(); } @@ -116,29 +100,6 @@ T await_(boost::asio::awaitable _coroutine) { #endif - -/** - * Block until the asynchronous call completes or time expired -*/ -// template -// T await_(future& r, uint64_t time) { -// if (r.wait_for(chrono::milliseconds(time)) == std::future_status::timeout) { -// throw runtime_error("Asynchronous execution timed out"); -// } -// return r.get(); -// } - -/** - * Block until the asynchronous call completes or time expired -*/ -// template -// T await_(future&& r, uint64_t time) { -// if (r.wait_for(chrono::milliseconds(time)) == std::future_status::timeout) { -// throw runtime_error("Asynchronous execution timed out"); -// } -// return move(r).get(); -// } - } } diff --git a/test/main.cpp b/test/main.cpp index 0088a6e..839d367 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -45,6 +45,25 @@ using namespace this_thread; // return _promise.get_future().get(); // } +// void sleep_to (int _time) { +// promise _promise; +// Delayed t( [&]() { +// _promise.set_value(); +// }, _time); +// await_ (_promise.get_future(), 100); +// } + +// future sleep_to (int _time) { +// promise _promise; +// future _future = _promise.get_future(); + +// Delayed t( [&]() { +// _promise.set_value(); +// }, _time); + +// return _future; +// } + // void promise_reject (int _time) { // promise _promise; // Delayed t( [&]() { @@ -71,14 +90,14 @@ using namespace this_thread; // } // }; -// // ------------------ EXTEND OWN CLASS WITH EVENTS ------------------- +// // // ------------------ EXTEND OWN CLASS WITH EVENTS ------------------- // class myOwnClass : public Trigger { // public: // myOwnClass() : Trigger() {}; // }; -// ----------------- MULTIPLE TRIGGERS IN ONE CLASS ------------------ +// // ----------------- MULTIPLE TRIGGERS IN ONE CLASS ------------------ // class ClassWithTriggers { // Trigger emitter1; @@ -176,25 +195,6 @@ int main () { // cout << "nije isteko " << endl; // } - // auto d = Delayed( [](){ - // cout << "Delayed" << endl; - // }, 2000); - - // auto p = Periodic( [](){ - // cout << "Periodic" << endl; - // }, 700); - - // Periodic( [&] (){ - // cout << "Delayed expire " << d->expired() << endl; - // cout << "Periodic ticks " << p->ticks() << endl; - // cout << "Delayed stoped " << d->stoped() << endl; - // cout << "Periodic stoped " << p->stoped() << endl; - // }, 1000); - - // Delayed( [&](){ - // p->stop(); - // }, 10000); - // // // // ------------------------ MAKE FUNCTIONS ASYNCHRONOUS ------------------------- // // /** @@ -460,7 +460,7 @@ int main () { // }); - // // ---------------------------------------------------------------------------------------------------- + // ---------------------------------------------------------------------------------------------------- // auto i = async_ ( []() -> asyncable { @@ -481,7 +481,7 @@ int main () { // }, 2000); - // Periodic b( []() { + // Periodic b_( []() { // cout << "funckija" << endl; // }, 2000); @@ -565,7 +565,7 @@ int main () { // ); // }, 2000); - // await_( async_co2 ( [c1 = move(c1)]() -> asyncable { + // await_( async_ ( [c1 = move(c1)]() -> asyncable { // cout << "Baba roga" << endl; // co_await c1(); // })); @@ -578,7 +578,32 @@ int main () { // evoid.tick("void"); - cout << "-------------end main-------------" << endl; + + + + // vector> futures; + + // for (int i=0; i<20; i++) { + // futures.push_back( + // async_([a = i](){ + // for (int i=0; i<1000; i++) { + // cout << a << " " << i << endl; + // // sleep_to(i); + // } + // }) + // ); + // } + + // for (int i=0; i<20; i++) { + // await_(futures[i]); + // // await_(futures[i]); + // } + + + + + + cout << "-------------end main------------- " << rtime_ms() - start << endl; _asynco_engine.run(); return 0; }