Первый коммит
This commit is contained in:
136
Src/Common/Abstract.hpp
Normal file
136
Src/Common/Abstract.hpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <glm/ext.hpp>
|
||||
|
||||
namespace AL {
|
||||
namespace Pos {
|
||||
|
||||
struct Local4_u {
|
||||
uint8_t X : 2, Y : 2, Z : 2;
|
||||
|
||||
using Key = uint8_t;
|
||||
operator Key() const {
|
||||
return Key(X) | (Key(Y) << 2) | (Key(Z) << 4);
|
||||
};
|
||||
};
|
||||
|
||||
struct Local16_u {
|
||||
uint8_t X : 4, Y : 4, Z : 4;
|
||||
|
||||
using Key = uint16_t;
|
||||
operator Key() const {
|
||||
return Key(X) | (Key(Y) << 4) | (Key(Z) << 8);
|
||||
};
|
||||
|
||||
Local4_u left() const { return Local4_u{uint8_t(uint16_t(X) >> 2), uint8_t(uint16_t(Y) >> 2), uint8_t(uint16_t(Z) >> 2)}; }
|
||||
Local4_u right() const { return Local4_u{uint8_t(uint16_t(X) & 0b11), uint8_t(uint16_t(Y) & 0b11), uint8_t(uint16_t(Z) & 0b11)}; }
|
||||
};
|
||||
|
||||
struct Local16 {
|
||||
int8_t X : 4, Y : 4, Z : 4;
|
||||
|
||||
using Key = uint16_t;
|
||||
operator Key() const {
|
||||
return Key(X) | (Key(Y) << 4) | (Key(Z) << 8);
|
||||
};
|
||||
|
||||
Local4_u left() const { return Local4_u{uint8_t(uint16_t(X) >> 2), uint8_t(uint16_t(Y) >> 2), uint8_t(uint16_t(Z) >> 2)}; }
|
||||
Local4_u right() const { return Local4_u{uint8_t(uint16_t(X) & 0b11), uint8_t(uint16_t(Y) & 0b11), uint8_t(uint16_t(Z) & 0b11)}; }
|
||||
};
|
||||
|
||||
struct Local256 {
|
||||
int8_t X : 8, Y : 8, Z : 8;
|
||||
|
||||
auto operator<=>(const Local256&) const = default;
|
||||
};
|
||||
|
||||
struct Local256_u {
|
||||
uint8_t X : 8, Y : 8, Z : 8;
|
||||
|
||||
auto operator<=>(const Local256_u&) const = default;
|
||||
};
|
||||
|
||||
struct Local4096 {
|
||||
int16_t X : 12, Y : 12, Z : 12;
|
||||
|
||||
auto operator<=>(const Local4096&) const = default;
|
||||
};
|
||||
|
||||
struct Local4096_u {
|
||||
uint16_t X : 12, Y : 12, Z : 12;
|
||||
|
||||
auto operator<=>(const Local4096_u&) const = default;
|
||||
};
|
||||
|
||||
struct GlobalVoxel {
|
||||
int32_t X : 24, Y : 24, Z : 24;
|
||||
|
||||
auto operator<=>(const GlobalVoxel&) const = default;
|
||||
};
|
||||
|
||||
struct GlobalNode {
|
||||
int32_t X : 20, Y : 20, Z : 20;
|
||||
|
||||
using Key = uint64_t;
|
||||
operator Key() const {
|
||||
return Key(X) | (Key(Y) << 20) | (Key(Z) << 40);
|
||||
};
|
||||
|
||||
auto operator<=>(const GlobalNode&) const = default;
|
||||
};
|
||||
|
||||
struct GlobalChunk {
|
||||
int16_t X : 16, Y : 16, Z : 16;
|
||||
|
||||
using Key = uint64_t;
|
||||
operator Key() const {
|
||||
return Key(X) | (Key(Y) << 16) | (Key(Z) << 32);
|
||||
};
|
||||
|
||||
auto operator<=>(const GlobalChunk&) const = default;
|
||||
};
|
||||
|
||||
struct GlobalRegion {
|
||||
int16_t X : 12, Y : 12, Z : 12;
|
||||
|
||||
using Key = uint64_t;
|
||||
operator Key() const {
|
||||
return Key(X) | (Key(Y) << 12) | (Key(Z) << 24);
|
||||
};
|
||||
|
||||
auto operator<=>(const GlobalRegion&) const = default;
|
||||
};
|
||||
|
||||
using Object = glm::i32vec3;
|
||||
|
||||
struct Object_t {
|
||||
// Позиции объектов целочисленные, BS единиц это один метр
|
||||
static constexpr int32_t BS = 4096, BS_Bit = 12;
|
||||
|
||||
static glm::vec3 asFloatVec(Object &obj) { return glm::vec3(float(obj.x)/float(BS), float(obj.y)/float(BS), float(obj.z)/float(BS)); }
|
||||
static GlobalNode asNodePos(Object &obj) { return GlobalNode(obj.x >> BS_Bit, obj.y >> BS_Bit, obj.z >> BS_Bit); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
struct LightPrism {
|
||||
uint8_t R : 2, G : 2, B : 2;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace std {
|
||||
|
||||
#define hash_for_pos(type) template <> struct hash<AL::Pos::type> { std::size_t operator()(const AL::Pos::type& obj) const { return std::hash<AL::Pos::type::Key>()((AL::Pos::type::Key) obj); } };
|
||||
hash_for_pos(Local4_u)
|
||||
hash_for_pos(Local16_u)
|
||||
hash_for_pos(Local16)
|
||||
hash_for_pos(GlobalChunk)
|
||||
hash_for_pos(GlobalRegion)
|
||||
#undef hash_for_pos
|
||||
|
||||
}
|
||||
171
Src/Common/Async.hpp
Normal file
171
Src/Common/Async.hpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/this_coro.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/date_time/posix_time/ptime.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
|
||||
|
||||
namespace AL {
|
||||
|
||||
using namespace boost::asio::experimental::awaitable_operators;
|
||||
|
||||
namespace asio = boost::asio;
|
||||
using tcp = asio::ip::tcp;
|
||||
template<typename T = void>
|
||||
using coro = asio::awaitable<T>;
|
||||
|
||||
|
||||
class AsyncObject {
|
||||
protected:
|
||||
asio::io_context &IOC;
|
||||
asio::deadline_timer WorkDeadline;
|
||||
|
||||
public:
|
||||
AsyncObject(asio::io_context &ioc)
|
||||
: IOC(ioc), WorkDeadline(ioc, boost::posix_time::pos_infin)
|
||||
{
|
||||
}
|
||||
|
||||
inline asio::io_context& EXEC()
|
||||
{
|
||||
return IOC;
|
||||
}
|
||||
|
||||
protected:
|
||||
template<typename Coroutine>
|
||||
void co_spawn(Coroutine &&coroutine) {
|
||||
asio::co_spawn(IOC, WorkDeadline.async_wait(asio::use_awaitable) || std::move(coroutine), asio::detached);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename ValueType>
|
||||
class AsyncAtomic : public AsyncObject {
|
||||
protected:
|
||||
asio::deadline_timer Deadline;
|
||||
ValueType Value;
|
||||
boost::mutex Mtx;
|
||||
|
||||
public:
|
||||
AsyncAtomic(asio::io_context &ioc, ValueType &&value)
|
||||
: AsyncObject(ioc), Deadline(ioc), Value(std::move(value))
|
||||
{
|
||||
}
|
||||
|
||||
AsyncAtomic& operator=(ValueType &&value) {
|
||||
boost::unique_lock lock(Mtx);
|
||||
Value = std::move(value);
|
||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator ValueType() const {
|
||||
return Value;
|
||||
}
|
||||
|
||||
ValueType operator*() const {
|
||||
return Value;
|
||||
}
|
||||
|
||||
AsyncAtomic& operator++() {
|
||||
boost::unique_lock lock(Mtx);
|
||||
Value--;
|
||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncAtomic& operator--() {
|
||||
boost::unique_lock lock(Mtx);
|
||||
Value--;
|
||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncAtomic& operator+=(ValueType value) {
|
||||
boost::unique_lock lock(Mtx);
|
||||
Value += value;
|
||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncAtomic& operator-=(ValueType value) {
|
||||
boost::unique_lock lock(Mtx);
|
||||
Value -= value;
|
||||
Deadline.expires_from_now(boost::posix_time::pos_infin);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void wait(ValueType oldValue) {
|
||||
while(true) {
|
||||
if(oldValue != Value)
|
||||
return;
|
||||
|
||||
boost::unique_lock lock(Mtx);
|
||||
|
||||
if(oldValue != Value)
|
||||
return;
|
||||
|
||||
std::atomic_bool flag = false;
|
||||
Deadline.async_wait([&](boost::system::error_code errc) { flag.store(true); });
|
||||
lock.unlock();
|
||||
flag.wait(false);
|
||||
}
|
||||
}
|
||||
|
||||
void await(ValueType needValue) {
|
||||
while(true) {
|
||||
if(needValue == Value)
|
||||
return;
|
||||
|
||||
boost::unique_lock lock(Mtx);
|
||||
|
||||
if(needValue == Value)
|
||||
return;
|
||||
|
||||
std::atomic_bool flag = false;
|
||||
Deadline.async_wait([&](boost::system::error_code errc) { flag.store(true); });
|
||||
lock.unlock();
|
||||
flag.wait(false);
|
||||
}
|
||||
}
|
||||
|
||||
coro<> async_wait(ValueType oldValue) {
|
||||
while(true) {
|
||||
if(oldValue != Value)
|
||||
co_return;
|
||||
|
||||
boost::unique_lock lock(Mtx);
|
||||
|
||||
if(oldValue != Value)
|
||||
co_return;
|
||||
|
||||
auto coroutine = Deadline.async_wait();
|
||||
lock.unlock();
|
||||
try { co_await std::move(coroutine); } catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
coro<> async_await(ValueType needValue) {
|
||||
while(true) {
|
||||
if(needValue == Value)
|
||||
co_return;
|
||||
|
||||
boost::unique_lock lock(Mtx);
|
||||
|
||||
if(needValue == Value)
|
||||
co_return;
|
||||
|
||||
auto coroutine = Deadline.async_wait();
|
||||
lock.unlock();
|
||||
try { co_await std::move(coroutine); } catch(...) {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
60
Src/Common/Collide.hpp
Normal file
60
Src/Common/Collide.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "Common/Abstract.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace AL {
|
||||
|
||||
template<typename VecType>
|
||||
bool calcBoxToBoxCollide(const VecType vec1_min, const VecType vec1_max,
|
||||
const VecType vec2_min, const VecType vec2_max, bool axis[VecType::length()] = nullptr
|
||||
) {
|
||||
|
||||
using ValType = VecType::value_type;
|
||||
|
||||
ValType max_delta = 0;
|
||||
ValType result = 0;
|
||||
|
||||
for(int iter = 0; iter < VecType::length(); iter++) {
|
||||
ValType left = vec2_min[iter]-vec1_max[iter];
|
||||
ValType right = vec1_min[iter]-vec2_max[iter];
|
||||
result = std::max(result, std::max(left, right));
|
||||
|
||||
if(axis)
|
||||
axis[iter] = std::min(left, right) < ValType(0);
|
||||
}
|
||||
|
||||
return result <= ValType(0);
|
||||
}
|
||||
|
||||
|
||||
template<typename VecType>
|
||||
bool calcBoxToBoxCollideWithDelta(const VecType vec1_min, const VecType vec1_max,
|
||||
const VecType vec2_min, const VecType vec2_max, VecType vec1_speed,
|
||||
typename VecType::value_type *delta, typename VecType::value_type deltaBias, bool axis[VecType::length()]
|
||||
) {
|
||||
using ValType = VecType::value_type;
|
||||
|
||||
ValType max_delta = 0;
|
||||
|
||||
for(int iter = 0; iter < VecType::length(); iter++) {
|
||||
ValType left = vec2_min[iter]-vec1_max[iter];
|
||||
ValType right = vec1_min[iter]-vec2_max[iter];
|
||||
ValType new_detla = (right > left ? -right : left)*deltaBias/vec1_speed[iter];
|
||||
max_delta = std::max(max_delta, new_detla);
|
||||
}
|
||||
|
||||
ValType result = 0;
|
||||
for(int iter = 0; iter < VecType::length(); iter++) {
|
||||
ValType left = vec2_min[iter]-(vec1_max[iter]+vec1_speed[iter]*max_delta/deltaBias);
|
||||
ValType right = (vec1_min[iter]+vec1_speed[iter]*max_delta/deltaBias)-vec2_max[iter];
|
||||
|
||||
if(axis)
|
||||
axis[iter] = std::min(left, right) < ValType(0);
|
||||
|
||||
result = std::min(std::max(left, right), result);
|
||||
}
|
||||
|
||||
*delta = max_delta;
|
||||
return result < ValType(0);
|
||||
}
|
||||
|
||||
}
|
||||
136
Src/Common/Lockable.hpp
Normal file
136
Src/Common/Lockable.hpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/thread/lock_types.hpp>
|
||||
#include <boost/thread/pthread/mutex.hpp>
|
||||
#include <mutex>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace AL {
|
||||
|
||||
template<typename T>
|
||||
class Lockable {
|
||||
public:
|
||||
template<typename ...Args>
|
||||
Lockable(Args&& ...args)
|
||||
: Obj(std::forward<Args>(args)...)
|
||||
{}
|
||||
|
||||
class ReadLockGuard {
|
||||
public:
|
||||
template<typename ...Args>
|
||||
ReadLockGuard(T& obj, Args&& ...args)
|
||||
: Lock(std::forward<Args>(args)...), Ref(obj) {}
|
||||
|
||||
const T& operator*() const { assert(Lock.owns_lock()); return Ref; }
|
||||
const T* operator->() const { assert(Lock.owns_lock()); return &Ref; }
|
||||
|
||||
bool owns_lock() {
|
||||
return Lock.owns_lock();
|
||||
}
|
||||
|
||||
operator bool() {
|
||||
return Lock.owns_lock();
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
Lock.unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
boost::shared_lock<boost::shared_mutex> Lock;
|
||||
T& Ref;
|
||||
};
|
||||
|
||||
class WriteLockGuard {
|
||||
public:
|
||||
template<typename ...Args>
|
||||
WriteLockGuard(T& obj, Args&& ...args)
|
||||
: Lock(std::forward<Args>(args)...), Ref(obj) {}
|
||||
|
||||
T& operator*() const { assert(Lock.owns_lock()); return Ref; }
|
||||
T* operator->() const { assert(Lock.owns_lock()); return &Ref; }
|
||||
|
||||
bool owns_lock() {
|
||||
return Lock.owns_lock();
|
||||
}
|
||||
|
||||
operator bool() {
|
||||
return Lock.owns_lock();
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
Lock.unlock();
|
||||
}
|
||||
private:
|
||||
std::unique_lock<boost::shared_mutex> Lock;
|
||||
T& Ref;
|
||||
};
|
||||
|
||||
ReadLockGuard lock_read() {
|
||||
return ReadLockGuard(Obj, Mtx);
|
||||
}
|
||||
|
||||
ReadLockGuard try_lock_read() {
|
||||
return ReadLockGuard(Obj, Mtx, boost::try_to_lock);
|
||||
}
|
||||
|
||||
template<typename Clock, typename Duration>
|
||||
ReadLockGuard try_lock_read(const boost::chrono::time_point<Clock, Duration>& atime) {
|
||||
return ReadLockGuard(Obj, Mtx, atime);
|
||||
}
|
||||
|
||||
WriteLockGuard lock_write() {
|
||||
return WriteLockGuard(Obj, Mtx);
|
||||
}
|
||||
|
||||
WriteLockGuard try_lock_write() {
|
||||
return WriteLockGuard(Obj, Mtx, boost::try_to_lock);
|
||||
}
|
||||
|
||||
template<typename Clock, typename Duration>
|
||||
WriteLockGuard try_lock_write(const boost::chrono::time_point<Clock, Duration>& atime) {
|
||||
return WriteLockGuard(Obj, Mtx, atime);
|
||||
}
|
||||
|
||||
const T& no_lock_readable() { return Obj; }
|
||||
T& no_lock_writeable() { return Obj; }
|
||||
|
||||
private:
|
||||
T Obj;
|
||||
boost::shared_mutex Mtx;
|
||||
};
|
||||
|
||||
class DestroyLock {
|
||||
public:
|
||||
DestroyLock() = default;
|
||||
|
||||
struct Guard {
|
||||
Guard(DestroyLock &lock)
|
||||
: Lock(lock)
|
||||
{
|
||||
lock.UseCount++;
|
||||
}
|
||||
|
||||
~Guard() {
|
||||
Lock.UseCount--;
|
||||
}
|
||||
|
||||
private:
|
||||
DestroyLock &Lock;
|
||||
};
|
||||
|
||||
void wait_no_use() {
|
||||
while(int val = UseCount)
|
||||
UseCount.wait(val);
|
||||
}
|
||||
|
||||
Guard lock() {
|
||||
return Guard(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<int> UseCount;
|
||||
};
|
||||
|
||||
}
|
||||
131
Src/Common/MemoryPool.hpp
Normal file
131
Src/Common/MemoryPool.hpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/pool/pool_alloc.hpp>
|
||||
|
||||
|
||||
namespace AL {
|
||||
|
||||
template<unsigned PageSize_PowOf2, unsigned CountPageInChunk_PowOf2, unsigned MaxSize = 0, typename Mutex = boost::details::pool::default_mutex, typename UserAllocator = boost::default_user_allocator_new_delete>
|
||||
struct BoostPool {
|
||||
static constexpr unsigned
|
||||
PageSize = 1 << PageSize_PowOf2,
|
||||
PagesInChunk = 1 << CountPageInChunk_PowOf2,
|
||||
ChunkSize = PageSize << CountPageInChunk_PowOf2;
|
||||
|
||||
using Page = std::array<std::byte, PageSize>;
|
||||
using Allocator = boost::fast_pool_allocator<Page, UserAllocator, Mutex, PagesInChunk, MaxSize>;
|
||||
using SingletonPool = boost::singleton_pool<boost::fast_pool_allocator_tag, PageSize, UserAllocator, Mutex, PagesInChunk, MaxSize>;
|
||||
|
||||
using vector = std::vector<Page, Allocator>;
|
||||
|
||||
template<uint16_t PagesNum>
|
||||
class Array {
|
||||
void *Ptr = nullptr;
|
||||
|
||||
public:
|
||||
Array() {
|
||||
Ptr = SingletonPool::ordered_malloc(PagesNum);
|
||||
}
|
||||
|
||||
~Array() {
|
||||
if(Ptr)
|
||||
SingletonPool::ordered_free(Ptr, PagesNum);
|
||||
}
|
||||
|
||||
Array(const Array &array) {
|
||||
Ptr = SingletonPool::ordered_malloc(PagesNum);
|
||||
std::copy((const std::byte*) array.Ptr, (const std::byte*) array.Ptr + PageSize*PagesNum, (std::byte*) Ptr);
|
||||
}
|
||||
|
||||
Array(Array &&pages) {
|
||||
Ptr = pages.Ptr;
|
||||
pages.Ptr = nullptr;
|
||||
}
|
||||
|
||||
Array& operator=(const Array &pages) {
|
||||
if(this == &pages)
|
||||
return *this;
|
||||
|
||||
std::copy((const std::byte*) pages.Ptr, (const std::byte*) pages.Ptr + PageSize*PagesNum, (std::byte*) Ptr);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Array& operator=(Array &&pages) {
|
||||
if(this == &pages)
|
||||
return *this;
|
||||
|
||||
std::swap(Ptr, pages.Ptr);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::byte* data() { return (std::byte*) Ptr; }
|
||||
const std::byte* data() const { return (std::byte*) Ptr; }
|
||||
constexpr size_t size() const { return PageSize*PagesNum; }
|
||||
|
||||
std::byte& front() { return *(std::byte*) Ptr; }
|
||||
const std::byte& front() const { return *(const std::byte*) Ptr; }
|
||||
std::byte& back() { return *((std::byte*) Ptr + size()); }
|
||||
const std::byte& back() const { return *((const std::byte*) Ptr + size()); }
|
||||
|
||||
std::byte& operator[](size_t index) { return ((std::byte*) Ptr)[index]; }
|
||||
const std::byte& operator[](size_t index) const { return ((const std::byte*) Ptr)[index]; }
|
||||
};
|
||||
|
||||
class PagePtr {
|
||||
void *Ptr = nullptr;
|
||||
|
||||
public:
|
||||
PagePtr() {
|
||||
Ptr = SingletonPool::malloc();
|
||||
}
|
||||
|
||||
~PagePtr() {
|
||||
if(Ptr)
|
||||
SingletonPool::free(Ptr);
|
||||
}
|
||||
|
||||
PagePtr(const PagePtr &array) {
|
||||
Ptr = SingletonPool::malloc();
|
||||
std::copy((const std::byte*) array.Ptr, (const std::byte*) array.Ptr + PageSize, (std::byte*) Ptr);
|
||||
}
|
||||
|
||||
PagePtr(PagePtr &&pages) {
|
||||
Ptr = pages.Ptr;
|
||||
pages.Ptr = nullptr;
|
||||
}
|
||||
|
||||
PagePtr& operator=(const PagePtr &pages) {
|
||||
if(this == &pages)
|
||||
return *this;
|
||||
|
||||
std::copy((const std::byte*) pages.Ptr, (const std::byte*) pages.Ptr + PageSize, (std::byte*) Ptr);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
PagePtr& operator=(PagePtr &&pages) {
|
||||
if(this == &pages)
|
||||
return *this;
|
||||
|
||||
std::swap(Ptr, pages.Ptr);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::byte* data() { return (std::byte*) Ptr; }
|
||||
const std::byte* data() const { return (std::byte*) Ptr; }
|
||||
constexpr size_t size() const { return PageSize; }
|
||||
|
||||
std::byte& front() { return *(std::byte*) Ptr; }
|
||||
const std::byte& front() const { return *(const std::byte*) Ptr; }
|
||||
std::byte& back() { return *((std::byte*) Ptr + size()); }
|
||||
const std::byte& back() const { return *((const std::byte*) Ptr + size()); }
|
||||
|
||||
std::byte& operator[](size_t index) { return ((std::byte*) Ptr)[index]; }
|
||||
const std::byte& operator[](size_t index) const { return ((const std::byte*) Ptr)[index]; }
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
295
Src/Common/Net.cpp
Normal file
295
Src/Common/Net.cpp
Normal file
@@ -0,0 +1,295 @@
|
||||
#include "Net.hpp"
|
||||
#include <TOSLib.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
|
||||
namespace AL::Net {
|
||||
|
||||
using namespace TOS;
|
||||
|
||||
Server::~Server() {
|
||||
stop();
|
||||
wait();
|
||||
}
|
||||
|
||||
bool Server::isStopped() {
|
||||
return !IsAlive;
|
||||
}
|
||||
|
||||
void Server::stop() {
|
||||
NeedClose = true;
|
||||
if(Acceptor.is_open())
|
||||
Acceptor.close();
|
||||
}
|
||||
|
||||
void Server::wait() {
|
||||
if(!IsAlive)
|
||||
return;
|
||||
|
||||
Lock.wait();
|
||||
}
|
||||
|
||||
coro<void> Server::async_wait() {
|
||||
co_await Lock.async_wait();
|
||||
}
|
||||
|
||||
coro<void> Server::run() {
|
||||
IsAlive.store(true);
|
||||
|
||||
try {
|
||||
while(true) { // TODO: ловить ошибки на async_accept
|
||||
co_spawn(OnConnect(co_await Acceptor.async_accept()));
|
||||
}
|
||||
} catch(const std::exception &exc) {
|
||||
//if(!NeedClose)
|
||||
// TODO: std::cout << exc.what() << std::endl;
|
||||
}
|
||||
|
||||
IsAlive.store(false);
|
||||
Lock.cancel();
|
||||
}
|
||||
|
||||
|
||||
AsyncSocket::~AsyncSocket() {
|
||||
boost::lock_guard lock(SendPackets.Mtx);
|
||||
|
||||
if(SendPackets.Context)
|
||||
SendPackets.Context->NeedShutdown = true;
|
||||
|
||||
SendPackets.SenderGuard.cancel();
|
||||
}
|
||||
|
||||
void AsyncSocket::pushPackets(std::vector<Packet> *simplePackets, std::vector<SmartPacket> *smartPackets) {
|
||||
boost::unique_lock lock(SendPackets.Mtx);
|
||||
|
||||
if(Socket.is_open()
|
||||
&& (SendPackets.SimpleBuffer.size() + (simplePackets ? simplePackets->size() : 0) >= MAX_SIMPLE_PACKETS
|
||||
|| SendPackets.SmartBuffer.size() + (smartPackets ? smartPackets->size() : 0) >= MAX_SMART_PACKETS
|
||||
|| SendPackets.SizeInQueue >= MAX_PACKETS_SIZE_IN_WAIT))
|
||||
{
|
||||
Socket.close();
|
||||
// TODO: std::cout << "Передоз пакетами, сокет закрыт" << std::endl;
|
||||
}
|
||||
|
||||
if(!Socket.is_open()) {
|
||||
if(simplePackets)
|
||||
simplePackets->clear();
|
||||
|
||||
if(smartPackets)
|
||||
smartPackets->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
size_t addedSize = 0;
|
||||
|
||||
if(simplePackets) {
|
||||
for(Packet &packet : *simplePackets) {
|
||||
addedSize += packet.size();
|
||||
SendPackets.SimpleBuffer.push_back(std::move(packet));
|
||||
}
|
||||
|
||||
simplePackets->clear();
|
||||
}
|
||||
|
||||
if(smartPackets) {
|
||||
for(SmartPacket &packet : *smartPackets) {
|
||||
addedSize += packet.size();
|
||||
SendPackets.SmartBuffer.push_back(std::move(packet));
|
||||
}
|
||||
|
||||
smartPackets->clear();
|
||||
}
|
||||
|
||||
SendPackets.SizeInQueue += addedSize;
|
||||
|
||||
if(SendPackets.WaitForSemaphore) {
|
||||
SendPackets.WaitForSemaphore = false;
|
||||
SendPackets.Semaphore.cancel();
|
||||
SendPackets.Semaphore.expires_at(boost::posix_time::pos_infin);
|
||||
}
|
||||
}
|
||||
|
||||
std::string AsyncSocket::getError() const {
|
||||
return SendPackets.Context->Error;
|
||||
}
|
||||
|
||||
bool AsyncSocket::isAlive() const {
|
||||
return !SendPackets.Context->NeedShutdown
|
||||
&& !SendPackets.Context->RunSendShutdowned;
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::read(std::byte *data, uint32_t size) {
|
||||
while(size) {
|
||||
if(RecvSize == 0) {
|
||||
RecvSize = co_await Socket.async_receive(asio::buffer(RecvBuffer.data()+RecvPos, RecvBuffer.size()-RecvPos));
|
||||
}
|
||||
|
||||
uint32_t needRecv = std::min<size_t>(size, RecvSize);
|
||||
std::copy(RecvBuffer.data()+RecvPos, RecvBuffer.data()+RecvPos+needRecv, data);
|
||||
data += needRecv;
|
||||
RecvPos += needRecv;
|
||||
RecvSize -= needRecv;
|
||||
size -= needRecv;
|
||||
|
||||
if(RecvPos >= RecvBuffer.size())
|
||||
RecvPos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::waitForSend() {
|
||||
asio::deadline_timer waiter(IOC);
|
||||
|
||||
while(!SendPackets.SimpleBuffer.empty()
|
||||
|| !SendPackets.SmartBuffer.empty()
|
||||
|| SendSize)
|
||||
{
|
||||
waiter.expires_from_now(boost::posix_time::milliseconds(1));
|
||||
co_await waiter.async_wait();
|
||||
}
|
||||
}
|
||||
|
||||
coro<> AsyncSocket::runSender(std::shared_ptr<AsyncContext> context) {
|
||||
int NextBuffer = 0;
|
||||
|
||||
try {
|
||||
while(!context->NeedShutdown) {
|
||||
{
|
||||
boost::unique_lock lock(SendPackets.Mtx);
|
||||
if(SendPackets.SimpleBuffer.empty() && SendPackets.SmartBuffer.empty()) {
|
||||
SendPackets.WaitForSemaphore = true;
|
||||
auto coroutine = SendPackets.Semaphore.async_wait();
|
||||
lock.unlock();
|
||||
|
||||
try { co_await std::move(coroutine); } catch(...) {}
|
||||
continue;
|
||||
} else {
|
||||
for(int cycle = 0; cycle < 2; cycle++, NextBuffer++) {
|
||||
if(NextBuffer % 2) {
|
||||
while(!SendPackets.SimpleBuffer.empty()) {
|
||||
Packet &packet = SendPackets.SimpleBuffer.front();
|
||||
|
||||
if(SendSize+packet.size() >= SendBuffer.size())
|
||||
break;
|
||||
|
||||
size_t packetSize = packet.size();
|
||||
for(const auto &page : packet.getPages()) {
|
||||
size_t needCopy = std::min<size_t>(packetSize, NetPool::PageSize);
|
||||
std::copy(page.data(), page.data()+needCopy, SendBuffer.data()+SendSize);
|
||||
SendSize += needCopy;
|
||||
packetSize -= needCopy;
|
||||
}
|
||||
|
||||
SendPackets.SimpleBuffer.pop_front();
|
||||
}
|
||||
} else {
|
||||
while(!SendPackets.SmartBuffer.empty()) {
|
||||
SmartPacket &packet = SendPackets.SmartBuffer.front();
|
||||
|
||||
if(SendSize+packet.size() >= SendBuffer.size())
|
||||
break;
|
||||
|
||||
if(packet.IsStillRelevant && !packet.IsStillRelevant()) {
|
||||
SendPackets.SmartBuffer.pop_front();
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t packetSize = packet.size();
|
||||
for(const auto &page : packet.getPages()) {
|
||||
size_t needCopy = std::min<size_t>(packetSize, NetPool::PageSize);
|
||||
std::copy(page.data(), page.data()+needCopy, SendBuffer.data()+SendSize);
|
||||
SendSize += needCopy;
|
||||
packetSize -= needCopy;
|
||||
}
|
||||
|
||||
if(packet.OnSend) {
|
||||
std::optional<SmartPacket> nextPacket = packet.OnSend();
|
||||
if(nextPacket)
|
||||
SendPackets.SmartBuffer.push_back(std::move(*nextPacket));
|
||||
}
|
||||
|
||||
SendPackets.SmartBuffer.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!SendSize)
|
||||
continue;
|
||||
|
||||
try {
|
||||
co_await asio::async_write(Socket, asio::buffer(SendBuffer.data(), SendSize));
|
||||
SendSize = 0;
|
||||
} catch(const std::exception &exc) {
|
||||
context->Error = exc.what();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch(...) {}
|
||||
|
||||
context->RunSendShutdowned = true;
|
||||
}
|
||||
|
||||
coro<tcp::socket> asyncConnectTo(const std::string address, std::function<void(const std::string&)> onProgress) {
|
||||
std::string progress;
|
||||
auto addLog = [&](const std::string &msg) {
|
||||
progress += '\n';
|
||||
progress += msg;
|
||||
|
||||
if(onProgress)
|
||||
onProgress('\n'+msg);
|
||||
};
|
||||
|
||||
auto ioc = co_await asio::this_coro::executor;
|
||||
|
||||
addLog("Разбор адреса " + address);
|
||||
auto re = Str::match(address, "((?:\\[[\\d\\w:]+\\])|(?:[\\d\\.]+))(?:\\:(\\d+))?");
|
||||
|
||||
std::vector<std::tuple<tcp::endpoint, std::string>> eps;
|
||||
|
||||
if(!re) {
|
||||
re = Str::match(address, "([-_\\.\\w\\d]+)(?:\\:(\\d+))?");
|
||||
if(!re) {
|
||||
addLog("Не удалось разобрать адрес");
|
||||
co_return nullptr;
|
||||
}
|
||||
|
||||
tcp::resolver resv{ioc};
|
||||
tcp::resolver::results_type result;
|
||||
|
||||
addLog("Разрешение имён...");
|
||||
|
||||
result = co_await resv.async_resolve(*re->at(1), re->at(2) ? *re->at(2) : "7890");
|
||||
|
||||
addLog("Получено " + std::to_string(result.size()) + " точек");
|
||||
for(auto iter : result) {
|
||||
std::string addr = iter.endpoint().address().to_string() + ':' + std::to_string(iter.endpoint().port());
|
||||
std::string hostname = iter.host_name();
|
||||
if(hostname == addr)
|
||||
addLog("ep: " + addr);
|
||||
else
|
||||
addLog("ep: " + hostname + " (" + addr + ')');
|
||||
|
||||
eps.emplace_back(iter.endpoint(), iter.host_name());
|
||||
}
|
||||
} else {
|
||||
eps.emplace_back(tcp::endpoint{asio::ip::make_address(*re->at(1)), (uint16_t) (re->at(2) ? Str::toVal<int>(*re->at(2)) : 7890)}, *re->at(1));
|
||||
}
|
||||
|
||||
for(auto [ep, hostname] : eps) {
|
||||
addLog("Подключение к " + hostname +" (" + ep.address().to_string() + ':' + std::to_string(ep.port()) + ")");
|
||||
|
||||
try {
|
||||
tcp::socket sock{ioc};
|
||||
co_await sock.async_connect(ep);
|
||||
addLog("Подключились");
|
||||
co_return sock;
|
||||
} catch(const std::exception &exc) {
|
||||
addLog(std::string("Сокет не смог установить соединение: ") + exc.what());
|
||||
}
|
||||
}
|
||||
|
||||
addLog("Не удалось подключится к серверу");
|
||||
MAKE_ERROR(progress);
|
||||
}
|
||||
|
||||
}
|
||||
289
Src/Common/Net.hpp
Normal file
289
Src/Common/Net.hpp
Normal file
@@ -0,0 +1,289 @@
|
||||
#pragma once
|
||||
|
||||
#include "MemoryPool.hpp"
|
||||
#include "Async.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
#include <boost/circular_buffer.hpp>
|
||||
|
||||
namespace AL::Net {
|
||||
|
||||
class Server : public AsyncObject {
|
||||
protected:
|
||||
std::atomic_bool IsAlive = false, NeedClose = false;
|
||||
tcp::acceptor Acceptor;
|
||||
asio::deadline_timer Lock;
|
||||
std::function<coro<>(tcp::socket)> OnConnect;
|
||||
|
||||
public:
|
||||
Server(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port = 0)
|
||||
: AsyncObject(ioc), Acceptor(ioc, tcp::endpoint(tcp::v4(), port)), Lock(ioc, boost::posix_time::pos_infin), OnConnect(std::move(onConnect))
|
||||
{
|
||||
assert(OnConnect);
|
||||
|
||||
co_spawn(run());
|
||||
IsAlive.store(true);
|
||||
}
|
||||
|
||||
~Server();
|
||||
|
||||
bool isStopped();
|
||||
uint16_t getPort() {
|
||||
return Acceptor.local_endpoint().port();
|
||||
}
|
||||
|
||||
void stop();
|
||||
void wait();
|
||||
|
||||
coro<void> async_wait();
|
||||
|
||||
protected:
|
||||
coro<void> run();
|
||||
};
|
||||
|
||||
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||
static inline T swapEndian(const T &u) { return u; }
|
||||
#else
|
||||
template <typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||
static inline T swapEndian(const T &u) {
|
||||
if constexpr (sizeof(T) == 1) {
|
||||
return u;
|
||||
} else if constexpr (sizeof(T) == 2) {
|
||||
return __builtin_bswap16(u);
|
||||
} else if constexpr (sizeof(T) == 4) {
|
||||
return __builtin_bswap32(u);
|
||||
} else if constexpr (sizeof(T) == 8) {
|
||||
return __builtin_bswap64(u);
|
||||
} else {
|
||||
static_assert(sizeof(T) <= 8, "Неподдерживаемый размер для перестановки порядка байтов (Swap Endian)");
|
||||
return u;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Запись в сторону сокета производится пакетами
|
||||
// Считывание потоком
|
||||
|
||||
using NetPool = BoostPool<12, 14>;
|
||||
|
||||
class Packet {
|
||||
static constexpr size_t MAX_PACKET_SIZE = 1 << 16;
|
||||
uint16_t Size = 0;
|
||||
std::vector<NetPool::PagePtr> Pages;
|
||||
|
||||
public:
|
||||
Packet() = default;
|
||||
Packet(const Packet&) = default;
|
||||
Packet(Packet&&) = default;
|
||||
Packet& operator=(const Packet&) = default;
|
||||
Packet& operator=(Packet&&) = default;
|
||||
|
||||
inline Packet& write(const std::byte *data, uint16_t size) {
|
||||
assert(Size+size < MAX_PACKET_SIZE);
|
||||
|
||||
while(size) {
|
||||
if(Pages.size()*NetPool::PageSize == Size)
|
||||
Pages.emplace_back();
|
||||
|
||||
uint16_t needWrite = std::min<uint16_t>(Pages.size()*NetPool::PageSize-Size, size);
|
||||
std::byte *ptr = &Pages.back().front() + (Size % NetPool::PageSize);
|
||||
std::copy(data, data+needWrite, ptr);
|
||||
Size += needWrite;
|
||||
size -= needWrite;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||
inline Packet& write(T u) {
|
||||
u = swapEndian(u);
|
||||
write((const std::byte*) &u, sizeof(u));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Packet& write(std::string_view str) {
|
||||
assert(Size+str.size()+2 < MAX_PACKET_SIZE);
|
||||
write((uint16_t) str.size());
|
||||
write((const std::byte*) str.data(), str.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Packet& write(const std::string &str) {
|
||||
return write(std::string_view(str));
|
||||
}
|
||||
|
||||
inline uint16_t size() const { return Size; }
|
||||
inline const std::vector<NetPool::PagePtr>& getPages() const { return Pages; }
|
||||
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_convertible_v<T, std::string_view>, int> = 0>
|
||||
inline Packet& operator<<(const T &value) {
|
||||
if constexpr (std::is_convertible_v<T, std::string_view>)
|
||||
return write((std::string_view) value);
|
||||
else
|
||||
return write(value);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
clearFast();
|
||||
Pages.clear();
|
||||
}
|
||||
|
||||
void clearFast() {
|
||||
Size = 0;
|
||||
}
|
||||
|
||||
Packet& complite(std::vector<std::byte> &out) {
|
||||
out.resize(Size);
|
||||
|
||||
for(size_t pos = 0; pos < Size; pos += NetPool::PageSize) {
|
||||
const char *data = (const char*) Pages[pos / NetPool::PageSize].data();
|
||||
std::copy(data, data+std::min<size_t>(Size-pos, NetPool::PageSize), (char*) &out[pos]);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<std::byte> complite() {
|
||||
std::vector<std::byte> out;
|
||||
complite(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
coro<> sendAndFastClear(tcp::socket &socket) {
|
||||
for(size_t pos = 0; pos < Size; pos += NetPool::PageSize) {
|
||||
const char *data = (const char*) Pages[pos / NetPool::PageSize].data();
|
||||
size_t size = std::min<size_t>(Size-pos, NetPool::PageSize);
|
||||
co_await asio::async_write(socket, asio::const_buffer(data, size));
|
||||
}
|
||||
|
||||
clearFast();
|
||||
}
|
||||
};
|
||||
|
||||
class SmartPacket : public Packet {
|
||||
public:
|
||||
std::function<bool()> IsStillRelevant;
|
||||
std::function<std::optional<SmartPacket>()> OnSend;
|
||||
};
|
||||
|
||||
class AsyncSocket : public AsyncObject {
|
||||
NetPool::Array<32> RecvBuffer, SendBuffer;
|
||||
size_t RecvPos = 0, RecvSize = 0, SendSize = 0;
|
||||
tcp::socket Socket;
|
||||
|
||||
static constexpr uint32_t
|
||||
MAX_SIMPLE_PACKETS = 8192,
|
||||
MAX_SMART_PACKETS = MAX_SIMPLE_PACKETS/4,
|
||||
MAX_PACKETS_SIZE_IN_WAIT = 1 << 24;
|
||||
|
||||
struct AsyncContext {
|
||||
volatile bool NeedShutdown = false, RunSendShutdowned = false;
|
||||
std::string Error;
|
||||
};
|
||||
|
||||
struct SendPacketsObj {
|
||||
boost::mutex Mtx;
|
||||
bool WaitForSemaphore = false;
|
||||
asio::deadline_timer Semaphore, SenderGuard;
|
||||
boost::circular_buffer_space_optimized<Packet> SimpleBuffer;
|
||||
boost::circular_buffer_space_optimized<SmartPacket> SmartBuffer;
|
||||
size_t SizeInQueue = 0;
|
||||
std::shared_ptr<AsyncContext> Context;
|
||||
|
||||
SendPacketsObj(asio::io_context &ioc)
|
||||
: Semaphore(ioc, boost::posix_time::pos_infin), SenderGuard(ioc, boost::posix_time::pos_infin)
|
||||
{}
|
||||
} SendPackets;
|
||||
|
||||
public:
|
||||
AsyncSocket(asio::io_context &ioc, tcp::socket &&socket)
|
||||
: AsyncObject(ioc), Socket(std::move(socket)), SendPackets(ioc)
|
||||
{
|
||||
SendPackets.SimpleBuffer.set_capacity(512);
|
||||
SendPackets.SmartBuffer.set_capacity(SendPackets.SimpleBuffer.capacity()/4);
|
||||
SendPackets.Context = std::make_shared<AsyncContext>();
|
||||
|
||||
boost::asio::socket_base::linger optionLinger(true, 4); // После закрытия сокета оставшиеся данные будут доставлены
|
||||
Socket.set_option(optionLinger);
|
||||
boost::asio::ip::tcp::no_delay optionNoDelay(true); // Отключает попытки объёденить данные в крупные пакеты
|
||||
Socket.set_option(optionNoDelay);
|
||||
|
||||
asio::co_spawn(ioc, runSender(SendPackets.Context), asio::detached);
|
||||
}
|
||||
|
||||
~AsyncSocket();
|
||||
|
||||
void pushPackets(std::vector<Packet> *simplePackets, std::vector<SmartPacket> *smartPackets = nullptr);
|
||||
|
||||
std::string getError() const;
|
||||
bool isAlive() const;
|
||||
|
||||
coro<> read(std::byte *data, uint32_t size);
|
||||
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
|
||||
coro<T> read() {
|
||||
if constexpr(std::is_integral_v<T>) {
|
||||
T value;
|
||||
co_await read((std::byte*) &value, sizeof(value));
|
||||
co_return swapEndian(value);
|
||||
} else {
|
||||
uint16_t size = co_await read<uint16_t>();
|
||||
T value(size, ' ');
|
||||
co_await read((std::byte*) value.data(), size);
|
||||
co_return value;}
|
||||
}
|
||||
|
||||
coro<> waitForSend();
|
||||
|
||||
|
||||
static inline coro<> read(tcp::socket &socket, std::byte *data, uint32_t size) {
|
||||
co_await asio::async_read(socket, asio::mutable_buffer(data, size));
|
||||
}
|
||||
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T> or std::is_same_v<T, std::string>, int> = 0>
|
||||
static inline coro<T> read(tcp::socket &socket) {
|
||||
if constexpr(std::is_integral_v<T>) {
|
||||
T value;
|
||||
co_await read(socket, (std::byte*) &value, sizeof(value));
|
||||
co_return swapEndian(value);
|
||||
} else {
|
||||
uint16_t size = co_await read<uint16_t>(socket);
|
||||
T value(size, ' ');
|
||||
co_await read(socket, (std::byte*) value.data(), size);
|
||||
co_return value;}
|
||||
}
|
||||
|
||||
static inline coro<> write(tcp::socket &socket, const std::byte *data, uint16_t size) {
|
||||
co_await asio::async_write(socket, asio::const_buffer(data, size));
|
||||
}
|
||||
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||
static inline coro<> write(tcp::socket &socket, T u) {
|
||||
u = swapEndian(u);
|
||||
co_await write(socket, (const std::byte*) &u, sizeof(u));
|
||||
}
|
||||
|
||||
static inline coro<> write(tcp::socket &socket, std::string_view str) {
|
||||
co_await write(socket, (uint16_t) str.size());
|
||||
co_await write(socket, (const std::byte*) str.data(), str.size());
|
||||
}
|
||||
|
||||
static inline coro<> write(tcp::socket &socket, const std::string &str) {
|
||||
return write(socket, std::string_view(str));
|
||||
}
|
||||
|
||||
static inline coro<> write(tcp::socket &socket, const char *str) {
|
||||
return write(socket, std::string_view(str));
|
||||
}
|
||||
|
||||
private:
|
||||
coro<> runSender(std::shared_ptr<AsyncContext> context);
|
||||
};
|
||||
|
||||
coro<tcp::socket> asyncConnectTo(const std::string address, std::function<void(const std::string&)> onProgress = nullptr);
|
||||
}
|
||||
Reference in New Issue
Block a user