365 lines
9.7 KiB
C++
365 lines
9.7 KiB
C++
#pragma once
|
||
|
||
#include "TOSLib.hpp"
|
||
#include <functional>
|
||
#include "boost/system/detail/error_code.hpp"
|
||
#include "boost/system/system_error.hpp"
|
||
#include <boost/asio.hpp>
|
||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||
#include <exception>
|
||
#include <memory>
|
||
#include <type_traits>
|
||
#include <list>
|
||
|
||
|
||
|
||
namespace TOS {
|
||
|
||
using namespace boost::asio::experimental::awaitable_operators;
|
||
template<typename T = void>
|
||
using coro = boost::asio::awaitable<T>;
|
||
namespace asio = boost::asio;
|
||
|
||
class AsyncSemaphore
|
||
{
|
||
boost::asio::deadline_timer Deadline;
|
||
std::atomic<uint8_t> Lock = 0;
|
||
|
||
public:
|
||
AsyncSemaphore(
|
||
boost::asio::io_context& ioc)
|
||
: Deadline(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin))
|
||
{}
|
||
|
||
boost::asio::awaitable<void> async_wait() {
|
||
try {
|
||
co_await Deadline.async_wait(boost::asio::use_awaitable);
|
||
} catch(boost::system::system_error code) {
|
||
if(code.code() != boost::system::errc::operation_canceled)
|
||
throw;
|
||
}
|
||
|
||
co_await boost::asio::this_coro::throw_if_cancelled();
|
||
}
|
||
|
||
boost::asio::awaitable<void> async_wait(std::function<bool()> predicate) {
|
||
while(!predicate())
|
||
co_await async_wait();
|
||
}
|
||
|
||
void notify_one() {
|
||
Deadline.cancel_one();
|
||
}
|
||
|
||
void notify_all() {
|
||
Deadline.cancel();
|
||
}
|
||
};
|
||
|
||
|
||
/*
|
||
Многие могут уведомлять одного
|
||
Ждёт события. После доставки уведомления ждёт повторно
|
||
*/
|
||
class MultipleToOne_AsyncSymaphore {
|
||
asio::deadline_timer Timer;
|
||
|
||
public:
|
||
MultipleToOne_AsyncSymaphore(asio::io_context &ioc)
|
||
: Timer(ioc, boost::posix_time::ptime(boost::posix_time::pos_infin))
|
||
{}
|
||
|
||
void notify() {
|
||
Timer.cancel();
|
||
}
|
||
|
||
void wait() {
|
||
try { Timer.wait(); } catch(...) {}
|
||
Timer.expires_at(boost::posix_time::ptime(boost::posix_time::pos_infin));
|
||
}
|
||
|
||
coro<> async_wait() {
|
||
try { co_await Timer.async_wait(); } catch(...) {}
|
||
}
|
||
};
|
||
|
||
class WaitableCoro {
|
||
asio::io_context &IOC;
|
||
std::shared_ptr<MultipleToOne_AsyncSymaphore> Symaphore;
|
||
std::exception_ptr LastException;
|
||
|
||
public:
|
||
WaitableCoro(asio::io_context &ioc)
|
||
: IOC(ioc)
|
||
{}
|
||
|
||
void co_spawn(coro<> token) {
|
||
Symaphore = std::make_shared<MultipleToOne_AsyncSymaphore>(IOC);
|
||
asio::co_spawn(IOC, [token = std::move(token), symaphore = Symaphore]() -> coro<> {
|
||
try { co_await std::move(const_cast<coro<>&>(token)); } catch(...) {}
|
||
symaphore->notify();
|
||
}, asio::detached);
|
||
}
|
||
|
||
void wait() {
|
||
Symaphore->wait();
|
||
}
|
||
|
||
coro<> async_wait() {
|
||
return Symaphore->async_wait();
|
||
}
|
||
};
|
||
|
||
class AsyncUseControl {
|
||
public:
|
||
class Lock {
|
||
AsyncUseControl *AUC;
|
||
|
||
public:
|
||
Lock(AsyncUseControl *auc)
|
||
: AUC(auc)
|
||
{}
|
||
|
||
Lock()
|
||
: AUC(nullptr)
|
||
{}
|
||
|
||
~Lock() {
|
||
if(AUC)
|
||
unlock();
|
||
}
|
||
|
||
Lock(const Lock&) = delete;
|
||
Lock(Lock&& obj)
|
||
: AUC(obj.AUC)
|
||
{
|
||
obj.AUC = nullptr;
|
||
}
|
||
|
||
Lock& operator=(const Lock&) = delete;
|
||
Lock& operator=(Lock&& obj) {
|
||
if(&obj == this)
|
||
return *this;
|
||
|
||
if(AUC)
|
||
unlock();
|
||
|
||
AUC = obj.AUC;
|
||
obj.AUC = nullptr;
|
||
|
||
return *this;
|
||
}
|
||
|
||
void unlock() {
|
||
assert(AUC);
|
||
|
||
if(--AUC->Uses == 0 && AUC->OnNoUse) {
|
||
asio::post(AUC->IOC, std::move(AUC->OnNoUse));
|
||
}
|
||
|
||
AUC = nullptr;
|
||
}
|
||
};
|
||
|
||
private:
|
||
asio::io_context &IOC;
|
||
std::move_only_function<void()> OnNoUse;
|
||
std::atomic_int Uses = 0;
|
||
|
||
public:
|
||
AsyncUseControl(asio::io_context &ioc)
|
||
: IOC(ioc)
|
||
{
|
||
|
||
}
|
||
|
||
template<BOOST_ASIO_COMPLETION_TOKEN_FOR(void()) Token = asio::default_completion_token_t<asio::io_context>>
|
||
auto wait(Token&& token = asio::default_completion_token_t<asio::io_context>()) {
|
||
auto initiation = [this](auto&& token) {
|
||
int value;
|
||
do {
|
||
value = Uses.exchange(-1);
|
||
} while(value == -1);
|
||
|
||
OnNoUse = std::move(token);
|
||
|
||
if(value == 0)
|
||
OnNoUse();
|
||
|
||
Uses.exchange(value);
|
||
};
|
||
|
||
return asio::async_initiate<Token, void()>(initiation, token);
|
||
}
|
||
|
||
Lock use() {
|
||
int value;
|
||
do {
|
||
value = Uses.exchange(-1);
|
||
} while(value == -1);
|
||
|
||
if(OnNoUse)
|
||
throw boost::system::system_error(asio::error::operation_aborted, "OnNoUse");
|
||
|
||
Uses.exchange(++value);
|
||
return Lock(this);
|
||
}
|
||
};
|
||
|
||
/*
|
||
Используется, чтобы вместо уничтожения объекта в умной ссылке, вызвать корутину с co_await asyncDestructor()
|
||
*/
|
||
class IAsyncDestructible : public std::enable_shared_from_this<IAsyncDestructible> {
|
||
protected:
|
||
asio::io_context &IOC;
|
||
AsyncUseControl AUC;
|
||
|
||
virtual coro<> asyncDestructor() { co_await AUC.wait(); }
|
||
|
||
public:
|
||
IAsyncDestructible(asio::io_context &ioc)
|
||
: IOC(ioc), AUC(ioc)
|
||
{}
|
||
|
||
virtual ~IAsyncDestructible() {}
|
||
|
||
protected:
|
||
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
||
static std::shared_ptr<T> createShared(asio::io_context &ioc, T *ptr)
|
||
{
|
||
return std::shared_ptr<T>(ptr, [&ioc](T *ptr) {
|
||
boost::asio::co_spawn(ioc,
|
||
[ptr, &ioc]() mutable -> coro<> {
|
||
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||
asio::post(ioc, [ptr](){ delete ptr; });
|
||
},
|
||
boost::asio::detached);
|
||
});
|
||
}
|
||
|
||
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
||
static coro<std::shared_ptr<T>> createShared(T *ptr)
|
||
{
|
||
co_return std::shared_ptr<T>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
|
||
boost::asio::co_spawn(ioc,
|
||
[ptr, &ioc]() mutable -> coro<> {
|
||
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||
asio::post(ioc, [ptr](){ delete ptr; });
|
||
},
|
||
boost::asio::detached);
|
||
});
|
||
}
|
||
|
||
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
||
static std::unique_ptr<T, std::function<void(T*)>> createUnique(asio::io_context &ioc, T *ptr)
|
||
{
|
||
return std::unique_ptr<T, std::function<void(T*)>>(ptr, [&ioc](T *ptr) {
|
||
boost::asio::co_spawn(ioc,
|
||
[ptr, &ioc]() mutable -> coro<> {
|
||
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||
asio::post(ioc, [ptr](){ delete ptr; });
|
||
},
|
||
boost::asio::detached);
|
||
});
|
||
}
|
||
|
||
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
||
static coro<std::unique_ptr<T, std::function<void(T*)>>> createUnique(T *ptr)
|
||
{
|
||
co_return std::unique_ptr<T, std::function<void(T*)>>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
|
||
boost::asio::co_spawn(ioc,
|
||
[ptr, &ioc]() mutable -> coro<> {
|
||
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||
asio::post(ioc, [ptr](){ delete ptr; });
|
||
},
|
||
boost::asio::detached);
|
||
});
|
||
}
|
||
};
|
||
|
||
template<typename T>
|
||
class AsyncMutexObject {
|
||
public:
|
||
class Lock {
|
||
public:
|
||
Lock(AsyncMutexObject* obj)
|
||
: Obj(obj)
|
||
{}
|
||
|
||
Lock(const Lock& other) = delete;
|
||
Lock(Lock&& other)
|
||
: Obj(other.Obj)
|
||
{
|
||
other.Obj = nullptr;
|
||
}
|
||
|
||
~Lock() {
|
||
if(Obj)
|
||
unlock();
|
||
}
|
||
|
||
Lock& operator=(const Lock& other) = delete;
|
||
Lock& operator=(Lock& other) {
|
||
if(&other == this)
|
||
return *this;
|
||
|
||
if(Obj)
|
||
unlock();
|
||
|
||
Obj = other.Obj;
|
||
other.Obj = nullptr;
|
||
}
|
||
|
||
T& get() const { assert(Obj); return Obj->value; }
|
||
T* operator->() const { assert(Obj); return &Obj->value; }
|
||
T& operator*() const { assert(Obj); return Obj->value; }
|
||
|
||
void unlock() {
|
||
assert(Obj);
|
||
|
||
typename SpinlockObject<Context>::Lock ctx = Obj->Ctx.lock();
|
||
if(ctx->Chain.empty()) {
|
||
ctx->InExecution = false;
|
||
} else {
|
||
auto token = std::move(ctx->Chain.front());
|
||
ctx->Chain.pop_front();
|
||
ctx.unlock();
|
||
token(Lock(Obj));
|
||
}
|
||
|
||
Obj = nullptr;
|
||
}
|
||
|
||
private:
|
||
AsyncMutexObject *Obj;
|
||
};
|
||
|
||
private:
|
||
struct Context {
|
||
std::list<std::move_only_function<void(Lock)>> Chain;
|
||
bool InExecution = false;
|
||
};
|
||
|
||
SpinlockObject<Context> Ctx;
|
||
T value;
|
||
|
||
public:
|
||
template<BOOST_ASIO_COMPLETION_TOKEN_FOR(void(Lock)) Token = asio::default_completion_token_t<asio::io_context::executor_type>>
|
||
auto lock(Token&& token = Token()) {
|
||
auto initiation = [this](auto&& token) mutable {
|
||
typename SpinlockObject<Context>::Lock ctx = Ctx.lock();
|
||
|
||
if(ctx->InExecution) {
|
||
ctx->Chain.emplace_back(std::move(token));
|
||
} else {
|
||
ctx->InExecution = true;
|
||
ctx.unlock();
|
||
token(Lock(this));
|
||
}
|
||
};
|
||
|
||
return boost::asio::async_initiate<Token, void(Lock)>(std::move(initiation), token);
|
||
}
|
||
};
|
||
|
||
} |