Первый коммит

This commit is contained in:
2025-02-03 15:16:12 +06:00
commit d40c3bad86
287 changed files with 124575 additions and 0 deletions

36
Src/Client/Abstract.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "Abstract.hpp"
namespace AL::Client {
void IRenderSession::onChunksChange(WorldId_t worldId, const std::vector<Pos::GlobalChunk> &changeOrAddList, const std::vector<Pos::GlobalChunk> &remove) {}
void IRenderSession::attachCameraToEntity(EntityId_t id) {}
void IRenderSession::onWorldAdd(WorldId_t id) {}
void IRenderSession::onWorldRemove(WorldId_t id) {}
void IRenderSession::onWorldChange(WorldId_t id) {}
void IRenderSession::onEntitysAdd(const std::vector<EntityId_t> &list) {}
void IRenderSession::onEntitysRemove(const std::vector<EntityId_t> &list) {}
void IRenderSession::onEntitysPosQuatChanges(const std::vector<EntityId_t> &list) {}
void IRenderSession::onEntitysStateChanges(const std::vector<EntityId_t> &list) {}
TextureId_t IRenderSession::allocateTexture() { return 0; }
void IRenderSession::freeTexture(TextureId_t id) {}
void IRenderSession::setTexture(TextureId_t id, TextureInfo info) {}
ModelId_t IRenderSession::allocateModel() { return 0; }
void IRenderSession::freeModel(ModelId_t id) {}
void IRenderSession::setModel(ModelId_t id, ModelInfo info) {}
IRenderSession::~IRenderSession() = default;
IServerSession::~IServerSession() = default;
void ISurfaceEventListener::onResize(uint32_t width, uint32_t height) {}
void ISurfaceEventListener::onChangeFocusState(bool isFocused) {}
void ISurfaceEventListener::onCursorPosChange(int32_t width, int32_t height) {}
void ISurfaceEventListener::onCursorMove(float xMove, float yMove) {}
void ISurfaceEventListener::onFrameRendering() {}
void ISurfaceEventListener::onFrameRenderEnd() {}
void ISurfaceEventListener::onCursorBtn(EnumCursorBtn btn, bool state) {}
void ISurfaceEventListener::onKeyboardBtn(int btn, int state) {}
void ISurfaceEventListener::onJoystick() {}
ISurfaceEventListener::~ISurfaceEventListener() = default;
}

174
Src/Client/Abstract.hpp Normal file
View File

@@ -0,0 +1,174 @@
#pragma once
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
#include <Common/Abstract.hpp>
namespace AL::Client {
using VoxelId_t = uint16_t;
struct VoxelCube {
Pos::Local256 Left, Right;
VoxelId_t Material;
};
using NodeId_t = uint16_t;
struct Node {
NodeId_t NodeId;
uint8_t Rotate : 6;
};
// 16 метров ребро
// 256 вокселей ребро
struct Chunk {
// Кубы вокселей в чанке
std::vector<VoxelCube> Voxels;
// Ноды
std::unordered_map<Pos::Local16_u, Node> Nodes;
// Ограничения прохождения света, идущего от солнца (от верха карты до верхней плоскости чанка)
LightPrism Lights[16][16];
};
using WorldId_t = uint8_t;
using PortalId_t = uint16_t;
using EntityId_t = uint16_t;
class Entity {
public:
// PosQuat
WorldId_t WorldId;
PortalId_t LastUsedPortal;
Pos::Object Pos;
glm::quat Quat;
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
uint32_t HP = 0;
// State
std::unordered_map<std::string, float> Tags;
// m_attached_particle_spawners
// states
};
using TextureId_t = uint16_t;
struct TextureInfo {
};
using ModelId_t = uint16_t;
struct ModelInfo {
};
/* Интерфейс рендера текущего подключения к серверу */
class IRenderSession {
public:
// Сообщаем об изменившихся чанках
virtual void onChunksChange(WorldId_t worldId, const std::vector<Pos::GlobalChunk> &changeOrAddList, const std::vector<Pos::GlobalChunk> &remove);
// Подключаем камеру к сущности
virtual void attachCameraToEntity(EntityId_t id);
//
// Мир уже есть в глобальном списке
virtual void onWorldAdd(WorldId_t id);
// Мира уже нет в списке
virtual void onWorldRemove(WorldId_t id);
// Изменение состояния мира
virtual void onWorldChange(WorldId_t id);
// Сущности уже есть в глобальном списке
virtual void onEntitysAdd(const std::vector<EntityId_t> &list);
//
virtual void onEntitysRemove(const std::vector<EntityId_t> &list);
//
virtual void onEntitysPosQuatChanges(const std::vector<EntityId_t> &list);
//
virtual void onEntitysStateChanges(const std::vector<EntityId_t> &list);
virtual TextureId_t allocateTexture();
virtual void freeTexture(TextureId_t id);
virtual void setTexture(TextureId_t id, TextureInfo info);
virtual ModelId_t allocateModel();
virtual void freeModel(ModelId_t id);
virtual void setModel(ModelId_t id, ModelInfo info);
virtual ~IRenderSession();
};
struct Region {
std::unordered_map<Pos::Local4_u::Key, Chunk[4][4][4]> Subs;
};
struct World {
std::vector<EntityId_t> Entitys;
std::unordered_map<Pos::GlobalRegion::Key, Region> Regions;
};
class ChunksIterator {
public:
};
struct VoxelInfo {
};
struct NodeInfo {
};
/* Интерфейс обработчика сессии с сервером */
class IServerSession {
public:
std::unordered_map<EntityId_t, Entity> Entitys;
std::unordered_map<WorldId_t, World> Worlds;
std::unordered_map<VoxelId_t, VoxelInfo> VoxelRegistry;
std::unordered_map<NodeId_t, NodeInfo> NodeRegistry;
virtual ~IServerSession();
};
/* Интерфейс получателя событий от модуля вывода графики в ОС */
class ISurfaceEventListener {
public:
enum struct EnumCursorMoveMode {
Default, MoveAndHidden
} CursorMode = EnumCursorMoveMode::Default;
enum struct EnumCursorBtn {
Left, Middle, Right, One, Two
};
public:
// Изменение размера окна вывода графики
virtual void onResize(uint32_t width, uint32_t height);
// Приобретение или потеря фокуса приложением
virtual void onChangeFocusState(bool isFocused);
// Абсолютное изменение позиции курсора (вызывается только если CursorMode == EnumCursorMoveMode::Default)
virtual void onCursorPosChange(int32_t width, int32_t height);
// Относительное перемещение курсора (вызывается только если CursorMode == EnumCursorMoveMode::MoveAndHidden)
virtual void onCursorMove(float xMove, float yMove);
// Когда на GPU отправлены команды на отрисовку мира и мир рисуется
virtual void onFrameRendering(); // Здесь пока неизвестно что можно делать, но тут есть свободное время
// Когда GPU завершил кадр
virtual void onFrameRenderEnd(); // Изменять игровые данные можно только здесь
virtual void onCursorBtn(EnumCursorBtn btn, bool state);
virtual void onKeyboardBtn(int btn, int state);
virtual void onJoystick();
virtual ~ISurfaceEventListener();
};
}

View File

@@ -0,0 +1,112 @@
#include "ServerSession.hpp"
#include "Common/Net.hpp"
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/this_coro.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#include <functional>
#include <memory>
namespace AL::Client {
using namespace TOS;
ServerSession::~ServerSession() = default;
coro<> ServerSession::asyncAuthorizeWithServer(tcp::socket &socket, const std::string username, const std::string token, int a_ar_r, std::function<void(const std::string&)> onProgress) {
assert(a_ar_r >= 0 && a_ar_r <= 2);
std::string progress;
auto addLog = [&](const std::string &msg) {
progress += '\n';
progress += msg;
if(onProgress)
onProgress('\n'+msg);
};
if(username.size() > 255) {
addLog("Имя пользователя слишком велико (>255)");
MAKE_ERROR(progress);
}
if(token.size() > 255) {
addLog("Пароль слишком велик (>255)");
MAKE_ERROR(progress);
}
Net::Packet packet;
packet.write((const std::byte*) "AlterLuanti", 11);
packet << uint8_t(0) << uint8_t(a_ar_r) << username << token;
addLog("Отправляем первый пакет, авторизация или регистрация");
co_await packet.sendAndFastClear(socket);
addLog("Ожидаем код ответа");
uint8_t code = co_await Net::AsyncSocket::read<uint8_t>(socket);
if(code == 0) {
addLog("Код = Авторизированы");
} else if(code == 1) {
addLog("Код = Зарегистрированы и авторизированы");
} else if(code == 2 || code == 3) {
if(code == 2)
addLog("Код = Не удалось зарегистрироваться");
else
addLog("Код = Не удалось авторизоваться");
std::string reason = co_await Net::AsyncSocket::read<std::string>(socket);
addLog(reason);
if(code == 2)
MAKE_ERROR("Не удалось зарегистрироваться, причина: " << reason);
else
MAKE_ERROR("Не удалось авторизоваться, причина: " << reason);
} else {
addLog("Получен неизвестный код ответа (может это не игровой сервер?), прерываем");
MAKE_ERROR(progress);
}
}
coro<std::unique_ptr<Net::AsyncSocket>> ServerSession::asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, 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);
};
addLog("Инициализируем игровой протокол");
uint8_t code = 0;
co_await Net::AsyncSocket::write<>(socket, code);
asio::deadline_timer timer(socket.get_executor());
while(true) {
code = co_await Net::AsyncSocket::read<uint8_t>(socket);
if(code == 0) {
addLog("Код = Успешно");
break;
} else if(code == 1) {
addLog("Код = Ошибка с причиной");
addLog(co_await Net::AsyncSocket::read<std::string>(socket));
MAKE_ERROR(progress);
} else if(code == 2) {
addLog("Код = Подождать 4 секунды");
timer.expires_from_now(boost::posix_time::seconds(4));
co_await timer.async_wait();
addLog("Ожидаем новый код");
} else {
addLog("Получен неизвестный код ответа (может это не игровой сервер?), прерываем");
MAKE_ERROR(progress);
}
}
co_return std::make_unique<Net::AsyncSocket>(ioc, std::move(socket));
}
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include "Abstract.hpp"
#include "Common/Net.hpp"
#include <TOSLib.hpp>
#include <boost/asio/io_context.hpp>
namespace AL::Client {
class ServerSession : public AsyncObject, public IServerSession, public ISurfaceEventListener {
std::unique_ptr<Net::AsyncSocket> _Socket;
Net::AsyncSocket &Socket;
IRenderSession *RS = nullptr;
public:
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket, IRenderSession *rs = nullptr)
: AsyncObject(ioc), _Socket(std::move(socket)), Socket(*socket), RS(rs)
{
assert(socket.get());
co_spawn(run());
}
virtual ~ServerSession();
// Авторизоваться или (зарегистрироваться и авторизоваться) или зарегистрироваться
static coro<> asyncAuthorizeWithServer(tcp::socket &socket, const std::string username, const std::string token, int a_ar_r, std::function<void(const std::string&)> onProgress = nullptr);
// Начать игровой протокол в авторизированном сокете
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
// ISurfaceEventListener
// virtual void onResize(uint32_t width, uint32_t height) override;
// virtual void onChangeFocusState(bool isFocused) override;
// virtual void onCursorPosChange(int32_t width, int32_t height) override;
// virtual void onCursorMove(float xMove, float yMove) override;
// virtual void onFrameRendering() override;
// virtual void onFrameRenderEnd() override;
// virtual void onCursorBtn(EnumCursorBtn btn, bool state) override;
// virtual void onKeyboardBtn(int btn, int state) override;
// virtual void onJoystick() override;
private:
coro<> run();
};
}

File diff suppressed because it is too large Load Diff

4287
Src/Client/Vulkan/Vulkan.cpp Normal file

File diff suppressed because it is too large Load Diff

1008
Src/Client/Vulkan/Vulkan.hpp Normal file

File diff suppressed because it is too large Load Diff

136
Src/Common/Abstract.hpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}

355
Src/Server/Abstract.hpp Normal file
View File

@@ -0,0 +1,355 @@
#pragma once
#include <cstdint>
#include <Common/Abstract.hpp>
#include <Common/Collide.hpp>
#include <boost/uuid/detail/sha1.hpp>
namespace AL::Server {
using VoxelId_c = uint16_t;
using NodeId_c = uint16_t;
using WorldId_c = uint8_t;
using PortalId_c = uint8_t;
using EntityId_c = uint16_t;
using TextureId_c = uint16_t;
using ModelId_c = uint16_t;
using ResourceId_t = uint32_t;
using VoxelId_t = ResourceId_t;
using NodeId_t = ResourceId_t;
using WorldId_t = ResourceId_t;
using PortalId_t = uint16_t;
// В одном регионе может быть максимум 2^16 сущностей. Клиенту адресуются сущности в формате <позиция региона>+<uint16_t>
// И если сущность перешла из одного региона в другой адресация сохраняется
using EntityId_t = uint16_t;
using TextureId_t = ResourceId_t;
using ModelId_t = ResourceId_t;
using SoundId_t = ResourceId_t;
using MediaStreamId_t = uint16_t;
using ContentBridgeId_t = uint16_t;
using PlayerId_t = uint32_t;
/*
Сервер загружает информацию о локальных текстурах
Синхронизация часто используемых текстур?
Пересмотр списка текстур?
Динамичные текстуры?
*/
struct ResourceFile {
using Hash_t = boost::uuids::detail::sha1::digest_type;
Hash_t Hash;
std::vector<std::byte> Data;
void calcHash() {
boost::uuids::detail::sha1 hash;
hash.process_bytes(Data.data(), Data.size());
hash.get_digest(Hash);
}
};
struct ServerTime {
uint32_t Seconds : 24, Sub : 8;
};
struct VoxelCube {
Pos::Local256_u Left, Right;
VoxelId_t Material;
auto operator<=>(const VoxelCube&) const = default;
};
struct VoxelCube_Region {
Pos::Local4096_u Left, Right;
VoxelId_t Material;
auto operator<=>(const VoxelCube_Region&) const = default;
};
struct Node {
NodeId_t NodeId;
uint8_t Rotate : 6;
};
struct AABB {
Pos::Object VecMin, VecMax;
void sortMinMax() {
Pos::Object::value_type left, right;
for(int iter = 0; iter < 3; iter++) {
left = std::min(VecMin[iter], VecMax[iter]);
right = std::max(VecMin[iter], VecMax[iter]);
VecMin[iter] = left;
VecMax[iter] = right;
}
}
bool isCollideWith(const AABB &other, bool axis[3] = nullptr) {
return calcBoxToBoxCollide(VecMin, VecMax, other.VecMin, other.VecMax, axis);
}
bool collideWithDelta(const AABB &other, const Pos::Object &my_speed, int32_t &delta, bool axis[3] = nullptr) {
return calcBoxToBoxCollideWithDelta(VecMin, VecMax, other.VecMin, other.VecMax, my_speed, &delta, Pos::Object_t::BS, axis);
}
};
struct LocalAABB {
uint64_t x : 20, y : 20, z : 20;
AABB atPos(const Pos::Object &pos) const {
return {pos-Pos::Object(x/2, y/2, z/2), pos+Pos::Object(x/2, y/2, z/2)};
}
};
struct CollisionAABB : public AABB {
enum struct EnumType {
Voxel, Node, Entity, Barrier, Portal, Another
} Type;
union {
struct {
EntityId_t Index;
} Entity;
struct {
Pos::Local16_u Pos;
} Node;
struct {
Pos::Local16_u Chunk;
uint32_t Index;
VoxelId_t Id;
} Voxel;
struct {
} Barrier;
struct {
} Portal;
struct {
} Another;
};
bool Skip = false;
};
class Entity {
public:
LocalAABB ABBOX;
// PosQuat
WorldId_t WorldId;
Pos::Object Pos, Speed, Acceleration;
glm::quat Quat;
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
uint32_t HP = 0;
Pos::GlobalRegion InRegionPos;
// State
std::unordered_map<std::string, float> Tags;
// m_attached_particle_spawners
// states
bool
// Сущность будет удалена в слудующем такте
NeedRemove = false,
// Сущность была удалена или не действительна
IsRemoved = false;
public:
Entity();
AABB aabbAtPos() {
return {Pos-Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2), Pos+Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2)};
}
};
template<typename Vec>
struct VoxelCuboidsFuncs {
// Кубы должны быть отсортированы
static bool canMerge(const Vec& a, const Vec& b) {
if (a.Material != b.Material) return false;
// Проверяем, что кубы смежны по одной из осей
bool xAdjacent = (a.Right.X == b.Left.X) && (a.Left.Y == b.Left.Y) && (a.Right.Y == b.Right.Y) && (a.Left.Z == b.Left.Z) && (a.Right.Z == b.Right.Z);
bool yAdjacent = (a.Right.Y == b.Left.Y) && (a.Left.X == b.Left.X) && (a.Right.X == b.Right.X) && (a.Left.Z == b.Left.Z) && (a.Right.Z == b.Right.Z);
bool zAdjacent = (a.Right.Z == b.Left.Z) && (a.Left.X == b.Left.X) && (a.Right.X == b.Right.X) && (a.Left.Y == b.Left.Y) && (a.Right.Y == b.Right.Y);
return xAdjacent || yAdjacent || zAdjacent;
}
static Vec mergeCubes(const Vec& a, const Vec& b) {
Vec merged;
merged.Material = a.Material;
// Объединяем кубы по минимальным и максимальным координатам
merged.Left.X = std::min(a.Left.X, b.Left.X);
merged.Left.Y = std::min(a.Left.Y, b.Left.Y);
merged.Left.Z = std::min(a.Left.Z, b.Left.Z);
merged.Right.X = std::max(a.Right.X, b.Right.X);
merged.Right.Y = std::max(a.Right.Y, b.Right.Y);
merged.Right.Z = std::max(a.Right.Z, b.Right.Z);
return merged;
}
static std::vector<Vec> optimizeVoxelRegions(std::vector<Vec> regions) {
bool changed;
do {
changed = false;
for (size_t i = 0; i < regions.size(); ++i) {
for (size_t j = i + 1; j < regions.size(); ++j) {
if (canMerge(regions[i], regions[j])) {
regions[i] = mergeCubes(regions[i], regions[j]);
regions.erase(regions.begin() + j);
changed = true;
--j;
}
}
}
} while (changed);
return regions;
}
static bool isCubesIntersect(const Vec& a, const Vec& b) {
return !(a.Right.X < b.Left.X || a.Left.X > b.Right.X ||
a.Right.Y < b.Left.Y || a.Left.Y > b.Right.Y ||
a.Right.Z < b.Left.Z || a.Left.Z > b.Right.Z);
}
static std::vector<Vec> subtractCube(const Vec& a, const Vec& b) {
std::vector<Vec> result;
if (!isCubesIntersect(a, b)) {
result.push_back(a);
return result;
}
decltype(a.Left) intersectLeft = {
std::max(a.Left.X, b.Left.X),
std::max(a.Left.Y, b.Left.Y),
std::max(a.Left.Z, b.Left.Z)
};
decltype(a.Left) intersectRight = {
std::min(a.Right.X, b.Right.X),
std::min(a.Right.Y, b.Right.Y),
std::min(a.Right.Z, b.Right.Z)
};
// Разделяем куб a на меньшие кубы, исключая пересечение
if (a.Left.X < intersectLeft.X) {
result.push_back({a.Left, decltype(a.Left)(intersectLeft.X - 1, a.Right.Y, a.Right.Z), a.Material});
}
if (a.Right.X > intersectRight.X) {
result.push_back({decltype(a.Left)(intersectRight.X + 1, a.Left.Y, a.Left.Z), a.Right, a.Material});
}
if (a.Left.Y < intersectLeft.Y) {
result.push_back({
{intersectLeft.X, a.Left.Y, a.Left.Z},
decltype(a.Left)(intersectRight.X, intersectLeft.Y - 1, a.Right.Z),
a.Material
});
}
if (a.Right.Y > intersectRight.Y) {
result.push_back({
decltype(a.Left)(intersectLeft.X, intersectRight.Y + 1, a.Left.Z),
{intersectRight.X, a.Right.Y, a.Right.Z},
a.Material
});
}
if (a.Left.Z < intersectLeft.Z) {
result.push_back({
{intersectLeft.X, intersectLeft.Y, a.Left.Z},
decltype(a.Left)(intersectRight.X, intersectRight.Y, intersectLeft.Z - 1),
a.Material
});
}
if (a.Right.Z > intersectRight.Z) {
result.push_back({
decltype(a.Left)(intersectLeft.X, intersectLeft.Y, intersectRight.Z + 1),
{intersectRight.X, intersectRight.Y, a.Right.Z},
a.Material
});
}
return result;
}
};
inline void convertRegionVoxelsToChunks(const std::vector<VoxelCube_Region>& regions, std::vector<VoxelCube> *chunks) {
for (const auto& region : regions) {
int minX = region.Left.X >> 8;
int minY = region.Left.Y >> 8;
int minZ = region.Left.Z >> 8;
int maxX = region.Right.X >> 8;
int maxY = region.Right.Y >> 8;
int maxZ = region.Right.Z >> 8;
for (int x = minX; x <= maxX; ++x) {
for (int y = minY; y <= maxY; ++y) {
for (int z = minZ; z <= maxZ; ++z) {
Pos::Local256_u left {
static_cast<uint8_t>(std::max<uint16_t>((x << 8), region.Left.X) - (x << 8)),
static_cast<uint8_t>(std::max<uint16_t>((y << 8), region.Left.Y) - (y << 8)),
static_cast<uint8_t>(std::max<uint16_t>((z << 8), region.Left.Z) - (z << 8))
};
Pos::Local256_u right {
static_cast<uint8_t>(std::min<uint16_t>(((x+1) << 8)-1, region.Right.X) - (x << 8)),
static_cast<uint8_t>(std::min<uint16_t>(((y+1) << 8)-1, region.Right.Y) - (y << 8)),
static_cast<uint8_t>(std::min<uint16_t>(((z+1) << 8)-1, region.Right.Z) - (z << 8))
};
int chunkIndex = z * 16 * 16 + y * 16 + x;
chunks[chunkIndex].emplace_back(left, right, region.Material);
}
}
}
}
}
inline void convertChunkVoxelsToRegion(const std::vector<VoxelCube> *chunks, std::vector<VoxelCube_Region> &regions) {
for (int x = 0; x < 16; ++x) {
for (int y = 0; y < 16; ++y) {
for (int z = 0; z < 16; ++z) {
int chunkIndex = z * 16 * 16 + y * 16 + x;
Pos::Local4096_u left(x << 8, y << 8, z << 8);
for (const auto& cube : chunks[chunkIndex]) {
regions.emplace_back(
Pos::Local4096_u(left.X+cube.Left.X, left.Y+cube.Left.Y, left.Z+cube.Left.Z),
Pos::Local4096_u(left.X+cube.Right.X, left.Y+cube.Right.Y, left.Z+cube.Right.Z),
cube.Material
);
}
}
}
}
std::sort(regions.begin(), regions.end());
regions = VoxelCuboidsFuncs<VoxelCube_Region>::optimizeVoxelRegions(regions);
}
}

View File

@@ -0,0 +1,122 @@
#define BOOST_ASIO_HAS_IO_URING 1
#include "BinaryResourceManager.hpp"
#include <memory>
#include <boost/system.hpp>
#include <boost/asio.hpp>
#include <boost/asio/stream_file.hpp>
#include <TOSLib.hpp>
namespace AL::Server {
BinaryResourceManager::BinaryResourceManager(asio::io_context &ioc,
std::shared_ptr<ResourceFile> zeroResource)
: AsyncObject(ioc), ZeroResource(std::move(zeroResource))
{
}
BinaryResourceManager::~BinaryResourceManager() {
}
void BinaryResourceManager::recheckResources() {
}
ResourceId_t BinaryResourceManager::mapUriToId(const std::string &uri) {
UriParse parse = parseUri(uri);
if(parse.Protocol != "assets")
MAKE_ERROR("Неизвестный протокол ресурса '" << parse.Protocol << "'. Полный путь: " << parse.Orig);
return getResource_Assets(parse.Path);
}
void BinaryResourceManager::needResourceResponse(const std::vector<ResourceId_t> &resources) {
UpdatedResources.lock_write()->insert(resources.end(), resources.begin(), resources.end());
}
void BinaryResourceManager::update(float dtime) {
if(UpdatedResources.no_lock_readable().empty())
return;
auto lock = UpdatedResources.lock_write();
for(ResourceId_t resId : *lock) {
std::shared_ptr<ResourceFile> &objRes = PreparedInformation[resId];
if(objRes)
continue;
auto iter = ResourcesInfo.find(resId);
if(iter == ResourcesInfo.end()) {
objRes = ZeroResource;
} else {
objRes = iter->second->Loaded;
}
}
}
BinaryResourceManager::UriParse BinaryResourceManager::parseUri(const std::string &uri) {
size_t pos = uri.find("://");
if(pos == std::string::npos)
return {uri, "assets", uri};
else
return {uri, uri.substr(0, pos), uri.substr(pos+3)};
}
ResourceId_t BinaryResourceManager::getResource_Assets(std::string path) {
size_t pos = path.find("/");
if(pos == std::string::npos)
MAKE_ERROR("Не действительный путь assets: '" << path << "'");
std::string domain = path.substr(0, pos);
std::string inDomainPath = path.substr(pos+1);
ResourceId_t &resId = KnownResource[path];
if(!resId)
resId = NextId++;
std::shared_ptr<Resource> &res = ResourcesInfo[resId];
if(!res) {
res = std::make_shared<Resource>();
res->Loaded = ZeroResource;
auto iter = Domains.find("domain");
if(iter == Domains.end()) {
UpdatedResources.lock_write()->push_back(resId);
} else {
res->IsLoading = true;
co_spawn(checkResource_Assets(resId, iter->second / inDomainPath, res));
}
}
return resId;
}
coro<> BinaryResourceManager::checkResource_Assets(ResourceId_t id, fs::path path, std::shared_ptr<Resource> res) {
try {
asio::stream_file fd(IOC, path, asio::stream_file::flags::read_only);
if(fd.size() > 1024*1024*16)
MAKE_ERROR("Превышен лимит размера файла: " << fd.size() << " > " << 1024*1024*16);
std::shared_ptr<ResourceFile> file = std::make_shared<ResourceFile>();
file->Data.resize(fd.size());
co_await asio::async_read(fd, asio::mutable_buffer(file->Data.data(), file->Data.size()));
file->calcHash();
res->LastError.clear();
} catch(const std::exception &exc) {
res->LastError = exc.what();
res->IsLoading = false;
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc); errc && errc->code() == asio::error::operation_aborted)
co_return;
}
res->IsLoading = false;
UpdatedResources.lock_write()->push_back(id);
}
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include "Common/Lockable.hpp"
#include "Server/RemoteClient.hpp"
#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#include <filesystem>
#include <vector>
#include <Common/Async.hpp>
#include "Abstract.hpp"
namespace AL::Server {
namespace fs = std::filesystem;
class BinaryResourceManager : public AsyncObject {
public:
private:
struct Resource {
// Файл загруженный на диск
std::shared_ptr<ResourceFile> Loaded;
// Источник
std::string Uri;
bool IsLoading = false;
std::string LastError;
};
struct UriParse {
std::string Orig, Protocol, Path;
};
// Нулевой ресурс
std::shared_ptr<ResourceFile> ZeroResource;
// Домены поиска ресурсов
std::unordered_map<std::string, fs::path> Domains;
// Известные ресурсы
std::map<std::string, ResourceId_t> KnownResource;
std::map<ResourceId_t, std::shared_ptr<Resource>> ResourcesInfo;
// Последовательная регистрация ресурсов
ResourceId_t NextId = 1;
// Накапливаем идентификаторы готовых ресурсов
Lockable<std::vector<ResourceId_t>> UpdatedResources;
// Подготовленая таблица оповещения об изменениях ресурсов
// Должна забираться сервером и отчищаться
std::unordered_map<ResourceId_t, std::shared_ptr<ResourceFile>> PreparedInformation;
public:
// Если ресурс будет обновлён или загружен будет вызвано onResourceUpdate
BinaryResourceManager(asio::io_context &ioc, std::shared_ptr<ResourceFile> zeroResource);
virtual ~BinaryResourceManager();
// Перепроверка изменений ресурсов
void recheckResources();
// Домен мода -> путь к папке с ресурсами
void setAssetsDomain(std::unordered_map<std::string, fs::path> &&domains) { Domains = std::move(domains); }
// Идентификатор ресурса по его uri
ResourceId_t mapUriToId(const std::string &uri);
// Запросить ресурсы через onResourceUpdate
void needResourceResponse(const std::vector<ResourceId_t> &resources);
// Серверный такт
void update(float dtime);
bool hasPreparedInformation() { return !PreparedInformation.empty(); }
std::unordered_map<ResourceId_t, std::shared_ptr<ResourceFile>> takePreparedInformation() {
return std::move(PreparedInformation);
}
protected:
UriParse parseUri(const std::string &uri);
ResourceId_t getResource_Assets(std::string path);
coro<> checkResource_Assets(ResourceId_t id, fs::path path, std::shared_ptr<Resource> res);
};
}

View File

@@ -0,0 +1,256 @@
#include "ContentEventController.hpp"
#include "Common/Abstract.hpp"
#include "RemoteClient.hpp"
#include "Server/Abstract.hpp"
namespace AL::Server {
ContentEventController::ContentEventController(std::unique_ptr<RemoteClient> &&remote)
: Remote(std::move(remote))
{
}
uint8_t ContentEventController::getViewRange() const {
return 3;
}
ServerObjectPos ContentEventController::getLastPos() const {
return {0, {0, 0, 0}};
}
ServerObjectPos ContentEventController::getPos() const {
return {0, {0, 0, 0}};
}
void ContentEventController::onRegionsLost(WorldId_t worldId, const std::vector<Pos::GlobalRegion> &lost) {
auto pWorld = Subscribed.Chunks.find(worldId);
if(pWorld == Subscribed.Chunks.end())
return;
for(Pos::GlobalRegion rPos : lost) {
auto pRegion = pWorld->second.find(rPos);
if(pRegion != pWorld->second.end()) {
for(Pos::Local16_u lChunkPos : pRegion->second) {
Pos::GlobalChunk gChunkPos(
(pRegion->first.X << 4) | lChunkPos.X,
(pRegion->first.Y << 4) | lChunkPos.Y,
(pRegion->first.Z << 4) | lChunkPos.Z
);
Remote->prepareChunkRemove(worldId, gChunkPos);
}
pWorld->second.erase(pRegion);
}
}
if(pWorld->second.empty()) {
Subscribed.Chunks.erase(pWorld);
Remote->prepareWorldRemove(worldId);
}
}
void ContentEventController::onChunksEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<Pos::Local16_u> &enter, const std::unordered_set<Pos::Local16_u> &lost) {
std::unordered_set<Pos::Local16_u> &chunks = Subscribed.Chunks[worldId][regionPos];
chunks.insert(enter.begin(), enter.end());
for(Pos::Local16_u cPos : lost) {
chunks.erase(cPos);
Pos::GlobalChunk chunkPos(
(regionPos.X << 4) | cPos.X,
(regionPos.Y << 4) | cPos.Y,
(regionPos.Z << 4) | cPos.Z
);
Remote->prepareChunkRemove(worldId, chunkPos);
}
}
void ContentEventController::onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> &chunks)
{
auto pWorld = Subscribed.Chunks.find(worldId);
if(pWorld == Subscribed.Chunks.end())
return;
auto pRegion = pWorld->second.find(regionPos);
if(pRegion == pWorld->second.end())
return;
for(auto pChunk : chunks) {
if(!pRegion->second.contains(pChunk.first))
continue;
Pos::GlobalChunk chunkPos(
(regionPos.X << 4) | pChunk.first.X,
(regionPos.Y << 4) | pChunk.first.Y,
(regionPos.Z << 4) | pChunk.first.Z
);
Remote->prepareChunkUpdate_Voxels(worldId, chunkPos, *pChunk.second);
}
}
void ContentEventController::onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> &chunks)
{
auto pWorld = Subscribed.Chunks.find(worldId);
if(pWorld == Subscribed.Chunks.end())
return;
auto pRegion = pWorld->second.find(regionPos);
if(pRegion == pWorld->second.end())
return;
for(auto pChunk : chunks) {
if(!pRegion->second.contains(pChunk.first))
continue;
Pos::GlobalChunk chunkPos(
(regionPos.X << 4) | pChunk.first.X,
(regionPos.Y << 4) | pChunk.first.Y,
(regionPos.Z << 4) | pChunk.first.Z
);
Remote->prepareChunkUpdate_Nodes(worldId, chunkPos, *pChunk.second);
}
}
void ContentEventController::onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_map<Pos::Local16_u, const LightPrism*> &chunks)
{
auto pWorld = Subscribed.Chunks.find(worldId);
if(pWorld == Subscribed.Chunks.end())
return;
auto pRegion = pWorld->second.find(regionPos);
if(pRegion == pWorld->second.end())
return;
for(auto pChunk : chunks) {
if(!pRegion->second.contains(pChunk.first))
continue;
Pos::GlobalChunk chunkPos(
(regionPos.X << 4) | pChunk.first.X,
(regionPos.Y << 4) | pChunk.first.Y,
(regionPos.Z << 4) | pChunk.first.Z
);
Remote->prepareChunkUpdate_LightPrism(worldId, chunkPos, pChunk.second);
}
}
void ContentEventController::onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_set<EntityId_t> &enter, const std::unordered_set<EntityId_t> &lost)
{
auto pWorld = Subscribed.Entities.find(worldId);
if(pWorld == Subscribed.Entities.end()) {
// pWorld = std::get<0>(Subscribed.Entities.emplace(std::make_pair(worldId, decltype(Subscribed.Entities)::value_type())));
Subscribed.Entities[worldId];
pWorld = Subscribed.Entities.find(worldId);
}
auto pRegion = pWorld->second.find(regionPos);
if(pRegion == pWorld->second.end()) {
// pRegion = std::get<0>(pWorld->second.emplace(std::make_pair(worldId, decltype(pWorld->second)::value_type())));
pWorld->second[regionPos];
pRegion = pWorld->second.find(regionPos);
}
std::unordered_set<EntityId_t> &entityesId = pRegion->second;
for(EntityId_t eId : lost) {
entityesId.erase(eId);
}
entityesId.insert(enter.begin(), enter.end());
if(entityesId.empty()) {
pWorld->second.erase(pRegion);
if(pWorld->second.empty())
Subscribed.Entities.erase(pWorld);
}
// Сообщить Remote
for(EntityId_t eId : lost) {
Remote->prepareEntityRemove(worldId, regionPos, eId);
}
}
void ContentEventController::onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos,
EntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, EntityId_t newId)
{
// Проверим отслеживается ли эта сущность нами
auto lpWorld = Subscribed.Entities.find(lastWorldId);
if(lpWorld == Subscribed.Entities.end())
// Исходный мир нами не отслеживается
return;
auto lpRegion = lpWorld->second.find(lastRegionPos);
if(lpRegion == lpWorld->second.end())
// Исходный регион нами не отслеживается
return;
auto lpceId = lpRegion->second.find(lastId);
if(lpceId == lpRegion->second.end())
// Сущность нами не отслеживается
return;
// Проверим отслеживается ли регион, в который будет перемещена сущность
auto npWorld = Subscribed.Entities.find(newWorldId);
if(npWorld != Subscribed.Entities.end()) {
auto npRegion = npWorld->second.find(newRegionPos);
if(npRegion != npWorld->second.end()) {
// Следующий регион отслеживается, перекинем сущность
lpRegion->second.erase(lpceId);
npRegion->second.insert(newId);
Remote->prepareEntitySwap(lastWorldId, lastRegionPos, lastId, newWorldId, newRegionPos, newId);
goto entitySwaped;
}
}
Remote->prepareEntityRemove(lastWorldId, lastRegionPos, lastId);
entitySwaped:
return;
}
void ContentEventController::onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::vector<Entity> &entities)
{
auto lpWorld = Subscribed.Entities.find(worldId);
if(lpWorld == Subscribed.Entities.end())
// Исходный мир нами не отслеживается
return;
auto lpRegion = lpWorld->second.find(regionPos);
if(lpRegion == lpWorld->second.end())
// Исходный регион нами не отслеживается
return;
for(size_t eId = 0; eId < entities.size(); eId++) {
if(!lpRegion->second.contains(eId))
continue;
Remote->prepareEntityUpdate(worldId, regionPos, eId, &entities[eId]);
}
}
void ContentEventController::onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost)
{
}
void ContentEventController::onPortalUpdates(const std::vector<void*> &portals)
{
}
}

View File

@@ -0,0 +1,148 @@
#pragma once
#include <Common/Abstract.hpp>
#include "Abstract.hpp"
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace AL::Server {
class RemoteClient;
class GameServer;
struct GlobalEntityId {
WorldId_t WorldId;
Pos::GlobalChunk ChunkPos;
EntityId_t EntityId;
};
struct ServerObjectPos {
WorldId_t WorldId;
Pos::Object ObjectPos;
};
/*
Сфера в которой отслеживаются события игроком
*/
struct ContentViewCircle {
WorldId_t WorldId;
// Позиция в чанках
glm::i16vec3 Pos;
// (Единица равна размеру чанку) в квадрате
int32_t Range;
inline int32_t sqrDistance(Pos::GlobalRegion regionPos) const {
glm::i32vec3 vec = {Pos.x-((regionPos.X << 4) | 0b1000), Pos.y-((regionPos.Y << 4) | 0b1000), Pos.z-((regionPos.Z << 4) | 0b1000)};
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
};
inline int32_t sqrDistance(Pos::GlobalChunk chunkPos) const {
glm::i32vec3 vec = {Pos.x-chunkPos.X, Pos.y-chunkPos.Y, Pos.z-chunkPos.Z};
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
};
inline int64_t sqrDistance(Pos::Object objectPos) const {
glm::i32vec3 vec = {Pos.x-(objectPos.x >> 20), Pos.y-(objectPos.y >> 20), Pos.z-(objectPos.z >> 20)};
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
};
bool isIn(Pos::GlobalRegion regionPos) const {
return sqrDistance(regionPos) < Range+192; // (8×sqrt(3))^2
}
bool isIn(Pos::GlobalChunk chunkPos) const {
return sqrDistance(chunkPos) < Range+3; // (1×sqrt(3))^2
}
bool isIn(Pos::Object objectPos, int32_t size = 0) const {
return sqrDistance(objectPos) < Range+3+size;
}
};
/*
Мост контента, для отслеживания событий из удалённх точек
По типу портала, через который можно видеть контент на расстоянии
*/
struct ContentBridge {
/*
false -> Из точки From видно контент из точки To
true -> Контент виден в обе стороны
*/
bool IsTwoWay = false;
WorldId_t LeftWorld;
// Позиция в чанках
glm::i16vec3 LeftPos;
WorldId_t RightWorld;
// Позиция в чанках
glm::i16vec3 RightPos;
};
/* Игрок */
class ContentEventController {
private:
struct SubscribedObj {
// Используется регионами
std::vector<PortalId_t> Portals;
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_set<Pos::Local16_u>>> Chunks;
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_set<EntityId_t>>> Entities;
} Subscribed;
public:
// Управляется сервером
std::unique_ptr<RemoteClient> Remote;
// Регионы сюда заглядывают
std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> ContentViewCircles;
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> SubscribedRegions;
public:
ContentEventController(std::unique_ptr<RemoteClient> &&remote);
// Измеряется в чанках в длину
uint8_t getViewRange() const;
ServerObjectPos getLastPos() const;
ServerObjectPos getPos() const;
// Навешивается слушателем событий на регионы
// Здесь приходят частично фильтрованные события
// Регионы следят за чанками, которые видят игроки
void onRegionsLost(WorldId_t worldId, const std::vector<Pos::GlobalRegion> &lost);
void onChunksEnterLost(WorldId_t worldId, Pos::GlobalRegion regionId, const std::unordered_set<Pos::Local16_u> &enter, const std::unordered_set<Pos::Local16_u> &lost);
// Нужно фильтровать неотслеживаемые чанки
void onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> &chunks);
void onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> &chunks);
void onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const LightPrism*> &chunks);
void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<EntityId_t> &enter, const std::unordered_set<EntityId_t> &lost);
void onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos, EntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, EntityId_t newId);
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::vector<Entity> &entities);
void onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost);
void onPortalUpdates(const std::vector<void*> &portals);
inline const SubscribedObj& getSubscribed() { return Subscribed; };
};
}
namespace std {
template <>
struct hash<AL::Server::ServerObjectPos> {
std::size_t operator()(const AL::Server::ServerObjectPos& obj) const {
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<int32_t>()(obj.ObjectPos.x) ^ std::hash<int32_t>()(obj.ObjectPos.y) ^ std::hash<int32_t>()(obj.ObjectPos.z);
}
};
}

968
Src/Server/GameServer.cpp Normal file
View File

@@ -0,0 +1,968 @@
#include "GameServer.hpp"
#include "Common/Abstract.hpp"
#include "Common/Net.hpp"
#include "Server/Abstract.hpp"
#include "Server/ContentEventController.hpp"
#include <boost/json/parse.hpp>
#include <chrono>
#include <glm/geometric.hpp>
#include <memory>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include "SaveBackends/Filesystem.hpp"
namespace AL::Server {
GameServer::~GameServer() {
shutdown("on ~GameServer");
RunThread.join();
WorkDeadline.cancel();
UseLock.wait_no_use();
}
static thread_local std::vector<ContentViewCircle> TL_Circles;
std::vector<ContentViewCircle> GameServer::WorldObj::calcCVCs(ContentViewCircle circle, int depth)
{
TL_Circles.reserve(4096);
TL_Circles.push_back(circle);
_calcContentViewCircles(TL_Circles.front(), depth);
return TL_Circles;
}
void GameServer::WorldObj::_calcContentViewCircles(ContentViewCircle circle, int depth) {
for(const auto &pair : ContentBridges) {
auto &br = pair.second;
if(br.LeftWorld == circle.WorldId) {
glm::i32vec3 vec = circle.Pos-br.LeftPos;
ContentViewCircle circleNew = {br.RightWorld, br.RightPos, circle.Range-(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+16)};
if(circleNew.Range >= 0) {
bool isIn = false;
for(ContentViewCircle &exCircle : TL_Circles) {
if(exCircle.WorldId != circleNew.WorldId)
continue;
vec = exCircle.Pos-circleNew.Pos;
if(exCircle.Range >= vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+circleNew.Range) {
isIn = true;
break;
}
}
if(isIn)
continue;
TL_Circles.push_back(circleNew);
if(depth > 1)
_calcContentViewCircles(circleNew, depth-1);
}
}
if(br.IsTwoWay && br.RightWorld == circle.WorldId) {
glm::i32vec3 vec = circle.Pos-br.RightPos;
ContentViewCircle circleNew = {br.LeftWorld, br.LeftPos, circle.Range-(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+16)};
if(circleNew.Range >= 0) {
bool isIn = false;
for(ContentViewCircle &exCircle : TL_Circles) {
if(exCircle.WorldId != circleNew.WorldId)
continue;
vec = exCircle.Pos-circleNew.Pos;
if(exCircle.Range >= vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+circleNew.Range) {
isIn = true;
break;
}
}
if(isIn)
continue;
TL_Circles.push_back(circleNew);
if(depth > 1)
_calcContentViewCircles(circleNew, depth-1);
}
}
}
}
std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> GameServer::WorldObj::remapCVCsByWorld(const std::vector<ContentViewCircle> &list) {
std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> out;
for(const ContentViewCircle &circle : list) {
out[circle.WorldId].push_back(circle);
}
return out;
}
coro<> GameServer::pushSocketConnect(tcp::socket socket) {
auto useLock = UseLock.lock();
try {
std::string magic = "AlterLuanti";
co_await Net::AsyncSocket::read(socket, (std::byte*) magic.data(), magic.size());
if(magic != "AlterLuanti") {
co_return;
}
uint8_t mver = co_await Net::AsyncSocket::read<uint8_t>(socket);
if(mver != 0) {
co_return;
}
uint8_t a_ar_r = co_await Net::AsyncSocket::read<uint8_t>(socket);
std::string username = co_await Net::AsyncSocket::read<std::string>(socket);
if(username.size() > 255)
co_return;
std::string token = co_await Net::AsyncSocket::read<std::string>(socket);
if(token.size() > 255)
co_return;
uint8_t response_code;
std::string response_message;
if(a_ar_r < 0 || a_ar_r > 2)
co_return;
bool authorized = false;
// Авторизация
if (a_ar_r == 0 || a_ar_r == 1) {
authorized = true;
response_code = 0;
// Авторизация
}
bool justRegistered = false;
if (!authorized && (a_ar_r == 1 || a_ar_r == 2)) {
// Регистрация
response_code = 1;
}
co_await Net::AsyncSocket::write<uint8_t>(socket, response_code);
if(response_code > 1) {
co_await Net::AsyncSocket::write(socket, "Неизвестный протокол");
} else
co_await pushSocketAuthorized(std::move(socket), username);
} catch (const std::exception& e) {
}
}
coro<> GameServer::pushSocketAuthorized(tcp::socket socket, const std::string username) {
auto useLock = UseLock.lock();
uint8_t code = co_await Net::AsyncSocket::read<uint8_t>(socket);
if(code == 0) {
co_await pushSocketGameProtocol(std::move(socket), username);
} else {
co_await Net::AsyncSocket::write<uint8_t>(socket, 1);
co_await Net::AsyncSocket::write(socket, "Неизвестный протокол");
}
}
coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string username) {
auto useLock = UseLock.lock();
// Проверить не подключен ли уже игрок
std::string ep = socket.remote_endpoint().address().to_string() + ':' + std::to_string(socket.remote_endpoint().port());
bool isConnected = External.ConnectedPlayersSet.lock_read()->contains(username);
if(isConnected) {
LOG.info() << "Игрок не смог подключится (уже в игре) " << username;
co_await Net::AsyncSocket::write<uint8_t>(socket, 1);
co_await Net::AsyncSocket::write(socket, "Вы уже подключены к игре");
} else if(IsGoingShutdown) {
LOG.info() << "Игрок не смог подключится (сервер завершает работу) " << username;
co_await Net::AsyncSocket::write<uint8_t>(socket, 1);
if(ShutdownReason.empty())
co_await Net::AsyncSocket::write(socket, "Сервер завершает работу");
else
co_await Net::AsyncSocket::write(socket, "Сервер завершает работу, причина: "+ShutdownReason);
} else {
auto lock = External.ConnectedPlayersSet.lock_write();
isConnected = lock->contains(username);
if(isConnected) {
lock.unlock();
LOG.info() << "Игрок не смог подключится (уже в игре) " << username;
co_await Net::AsyncSocket::write<uint8_t>(socket, 1);
co_await Net::AsyncSocket::write(socket, "Вы уже подключены к игре");
} else {
LOG.info() << "Подключился к игре " << username;
lock->insert(username);
lock.unlock();
co_await Net::AsyncSocket::write<uint8_t>(socket, 0);
External.NewConnectedPlayers.lock_write()
->push_back(std::make_unique<RemoteClient>(IOC, std::move(socket), username));
}
}
}
void GameServer::init(fs::path worldPath) {
Expanse.Worlds[0] = std::make_unique<World>(0);
SaveBackends::Filesystem fsbc;
SaveBackend.World = fsbc.createWorld(boost::json::parse("{\"Path\": \"data/world\"}").as_object());
SaveBackend.Player = fsbc.createPlayer(boost::json::parse("{\"Path\": \"data/player\"}").as_object());
SaveBackend.Auth = fsbc.createAuth(boost::json::parse("{\"Path\": \"data/auth\"}").as_object());
SaveBackend.ModStorage = fsbc.createModStorage(boost::json::parse("{\"Path\": \"data/mod_storage\"}").as_object());
RunThread = std::thread(&GameServer::prerun, this);
}
void GameServer::prerun() {
try {
auto useLock = UseLock.lock();
run();
} catch(...) {
}
IsAlive = false;
}
void GameServer::run() {
while(true) {
((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256);
std::chrono::steady_clock::time_point atTickStart = std::chrono::steady_clock::now();
if(IsGoingShutdown) {
// Отключить игроков
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
cec->Remote->shutdown(ShutdownReason);
}
// Сохранить данные
save();
{
// Отключить вновь подключившихся
auto lock = External.NewConnectedPlayers.lock_write();
for(std::unique_ptr<RemoteClient> &client : *lock) {
client->shutdown(ShutdownReason);
}
bool hasNewConnected = !lock->empty();
lock.unlock();
// Если были ещё подключившиеся сделать паузу на 1 секунду
if(hasNewConnected)
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// Конец
return;
}
//
stepContent();
// Принять события от игроков
stepPlayers();
// Обновить регионы
stepWorlds();
// Проверить видимый контент
stepViewContent();
// Отправить пакеты игрокам && Проверить контент необходимый для отправки
stepSendPlayersPackets();
// Выставить регионы на прогрузку
stepLoadRegions();
// Lua globalStep
stepGlobal();
// Сохранение
stepSave();
// Сон или подгонка длительности такта при высоких нагрузках
std::chrono::steady_clock::time_point atTickEnd = std::chrono::steady_clock::now();
float currentWastedTime = double((atTickEnd-atTickStart).count() * std::chrono::steady_clock::duration::period::num) / std::chrono::steady_clock::duration::period::den;
float freeTime = CurrentTickDuration-currentWastedTime-GlobalTickLagTime;
if(freeTime > 0) {
CurrentTickDuration -= PerTickAdjustment;
if(CurrentTickDuration < PerTickDuration)
CurrentTickDuration = PerTickDuration;
std::this_thread::sleep_for(std::chrono::milliseconds(uint32_t(1000*freeTime)));
} else {
GlobalTickLagTime = freeTime;
CurrentTickDuration += PerTickAdjustment;
}
}
}
void GameServer::stepContent() {
Content.TextureM.update(CurrentTickDuration);
if(Content.TextureM.hasPreparedInformation()) {
auto table = Content.TextureM.takePreparedInformation();
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
cec->Remote->informateDefTexture(table);
}
}
Content.ModelM.update(CurrentTickDuration);
if(Content.ModelM.hasPreparedInformation()) {
auto table = Content.ModelM.takePreparedInformation();
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
cec->Remote->informateDefTexture(table);
}
}
Content.SoundM.update(CurrentTickDuration);
if(Content.SoundM.hasPreparedInformation()) {
auto table = Content.SoundM.takePreparedInformation();
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
cec->Remote->informateDefTexture(table);
}
}
}
void GameServer::stepPlayers() {
// Подключить новых игроков
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
auto lock = External.NewConnectedPlayers.lock_write();
for(std::unique_ptr<RemoteClient> &client : *lock) {
co_spawn(client->run());
Game.CECs.push_back(std::make_unique<ContentEventController>(std::move(client)));
}
lock->clear();
}
// Обработка игроков
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
// Убрать отключившихся
if(!cec->Remote->isConnected()) {
cec.reset();
}
}
// Вычистить невалидные ссылки на игроков
Game.CECs.erase(std::remove_if(Game.CECs.begin(), Game.CECs.end(),
[](const std::unique_ptr<ContentEventController>& ptr) { return !ptr; }),
Game.CECs.end());
}
void GameServer::stepWorlds() {
for(auto &pair : Expanse.Worlds)
pair.second->onUpdate(this, CurrentTickDuration);
for(auto &pWorld : Expanse.Worlds) {
World &wobj = *pWorld.second;
assert(pWorld.first == 0 && "Требуется WorldManager");
std::string worldStringId = "unexisten";
std::vector<Pos::GlobalRegion> regionsToRemove;
for(auto &pRegion : wobj.Regions) {
Region &region = *pRegion.second;
// Позиции исчисляются в целых числах
// Вместо умножения на dtime, используется *dTimeMul/dTimeDiv
int32_t dTimeDiv = Pos::Object_t::BS;
int32_t dTimeMul = dTimeDiv*CurrentTickDuration;
// Обновить сущностей
for(size_t entityIndex = 0; entityIndex < region.Entityes.size(); entityIndex++) {
Entity &entity = region.Entityes[entityIndex];
if(entity.IsRemoved)
continue;
// Если нет ни скорости, ни ускорения, то пропускаем расчёт
// Ускорение свободного падения?
if((entity.Speed.x != 0 || entity.Speed.y != 0 || entity.Speed.z != 0)
|| (entity.Acceleration.x != 0 || entity.Acceleration.y != 0 || entity.Acceleration.z != 0))
{
Pos::Object &eSpeed = entity.Speed;
// Ограничение на 256 м/с
static constexpr int32_t MAX_SPEED_PER_SECOND = 256*Pos::Object_t::BS;
{
uint32_t linearSpeed = std::sqrt(eSpeed.x*eSpeed.x + eSpeed.y*eSpeed.y + eSpeed.z*eSpeed.z);
if(linearSpeed > MAX_SPEED_PER_SECOND) {
eSpeed *= MAX_SPEED_PER_SECOND;
eSpeed /= linearSpeed;
}
Pos::Object &eAcc = entity.Acceleration;
linearSpeed = std::sqrt(eAcc.x*eAcc.x + eAcc.y*eAcc.y + eAcc.z*eAcc.z);
if(linearSpeed > MAX_SPEED_PER_SECOND/2) {
eAcc *= MAX_SPEED_PER_SECOND/2;
eAcc /= linearSpeed;
}
}
// Потенциальное изменение позиции сущности в пустой области
// vt+(at^2)/2 = (v+at/2)*t = (Скорость + Ускорение/2*dtime)*dtime
Pos::Object dpos = (eSpeed + entity.Acceleration/2*dTimeMul/dTimeDiv)*dTimeMul/dTimeDiv;
// Стартовая и конечная позиции
Pos::Object &startPos = entity.Pos, endPos = entity.Pos + dpos;
// Новая скорость
Pos::Object nSpeed = entity.Speed + entity.Acceleration*dTimeMul/dTimeDiv;
// Зона расчёта коллизии
AABB collideZone = {startPos, endPos};
collideZone.sortMinMax();
collideZone.VecMin -= Pos::Object(entity.ABBOX.x, entity.ABBOX.y, entity.ABBOX.z)/2+Pos::Object(1);
collideZone.VecMax += Pos::Object(entity.ABBOX.x, entity.ABBOX.y, entity.ABBOX.z)/2+Pos::Object(1);
// Сбор ближайших коробок
std::vector<CollisionAABB> Boxes;
{
glm::ivec3 beg = collideZone.VecMin >> 20, end = (collideZone.VecMax + 0xfffff) >> 20;
for(; beg.z <= end.z; beg.z++)
for(; beg.y <= end.y; beg.y++)
for(; beg.x <= end.x; beg.x++) {
Pos::GlobalRegion rPos(beg.x, beg.y, beg.z);
auto iterChunk = wobj.Regions.find(rPos);
if(iterChunk == wobj.Regions.end())
continue;
iterChunk->second->getCollideBoxes(rPos, collideZone, Boxes);
}
}
// Коробка сущности
AABB entityAABB = entity.aabbAtPos();
// Симулируем физику
// Оставшееся время симуляции
int32_t remainingSimulationTime = dTimeMul;
// Оси, по которым было пересечение
bool axis[3]; // x y z
// Симулируем пока не будет просчитано выделенное время
while(remainingSimulationTime > 0) {
if(nSpeed.x == 0 && nSpeed.y == 0 && nSpeed.z == 0)
break; // Скорости больше нет
entityAABB = entity.aabbAtPos();
// Ближайшее время пересечения с боксом
int32_t minSimulationTime = remainingSimulationTime;
// Ближайший бокс в пересечении
int nearest_boxindex = -1;
for(size_t index = 0; index < Boxes.size(); index++) {
CollisionAABB &caabb = Boxes[index];
if(caabb.Skip)
continue;
int32_t delta;
if(!entityAABB.collideWithDelta(caabb, nSpeed, delta, axis))
continue;
if(delta > remainingSimulationTime)
continue;
nearest_boxindex = index;
minSimulationTime = delta;
}
if(nearest_boxindex == -1) {
// Свободный ход
startPos += nSpeed*dTimeDiv/minSimulationTime;
remainingSimulationTime = 0;
break;
} else {
if(minSimulationTime == 0) {
// Уже где-то застряли
// Да и хрен бы с этим
} else {
// Где-то встрянем через minSimulationTime
startPos += nSpeed*dTimeDiv/minSimulationTime;
remainingSimulationTime -= minSimulationTime;
nSpeed.x = nSpeed.y = nSpeed.z = 0;
break;
}
if(axis[0] == 0) {
nSpeed.x = 0;
}
if(axis[1] == 0) {
nSpeed.y = 0;
}
if(axis[2] == 0) {
nSpeed.z = 0;
}
CollisionAABB &caabb = Boxes[nearest_boxindex];
caabb.Skip = true;
}
}
// Симуляция завершена
}
// Сущность будет вычищена
if(entity.NeedRemove) {
entity.NeedRemove = false;
entity.IsRemoved = true;
}
// Проверим необходимость перемещения сущности в другой регион
// Вынести в отдельный этап обновления сервера, иначе будут происходить двойные симуляции
// сущностей при пересечении регионов/миров
{
Pos::Object temp = entity.Pos >> 20;
Pos::GlobalRegion rPos(temp.x, temp.y, temp.z);
if(rPos != pRegion.first || pWorld.first != entity.WorldId) {
Region *toRegion = Expanse.Worlds[entity.WorldId]->forceLoadOrGetRegion(rPos);
EntityId_t newId = toRegion->pushEntity(entity);
// toRegion->Entityes[newId].WorldId = Если мир изменился
if(newId == EntityId_t(-1)) {
// В другом регионе нет места
} else {
entity.IsRemoved = true;
// Сообщаем о перемещении сущности
for(ContentEventController *cec : region.CECs) {
cec->onEntitySwap(pWorld.first, pRegion.first, entityIndex, entity.WorldId, rPos, newId);
}
}
}
}
}
// Проверить необходимость перерасчёта вертикальной проходимости света
std::unordered_map<Pos::Local16_u, const LightPrism*> ChangedLightPrism;
{
for(int big = 0; big < 64; big++) {
uint64_t bits = region.IsChunkChanged_Voxels[big] | region.IsChunkChanged_Nodes[big];
if(!bits)
continue;
for(int little = 0; little < 64; little++) {
if(((bits >> little) & 1) == 0)
continue;
}
}
}
// Сбор данных об изменившихся чанках
std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> ChangedVoxels;
std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> ChangedNodes;
{
for(int big = 0; big < 64; big++) {
uint64_t bits_voxels = region.IsChunkChanged_Voxels[big];
uint64_t bits_nodes = region.IsChunkChanged_Nodes[big];
if(!bits_voxels && !bits_nodes)
continue;
for(int little = 0; little < 64; little++) {
Pos::Local16_u pos(little & 0xf, ((big & 0x3) << 2) | (little >> 4), big >> 2);
if(((bits_voxels >> little) & 1) == 1) {
ChangedVoxels[pos] = &region.Voxels[pos.X][pos.Y][pos.Z];
}
if(((bits_nodes >> little) & 1) == 1) {
ChangedNodes[pos] = &region.Nodes[pos.X][pos.Y][pos.Z];
}
}
}
}
// Об изменившихся сущностях
{
}
// Пробегаемся по всем наблюдателям
for(ContentEventController *cec : region.CECs) {
// То, что уже отслеживает наблюдатель
const auto &subs = cec->getSubscribed();
auto cvc = cec->ContentViewCircles.find(pWorld.first);
if(cvc == cec->ContentViewCircles.end())
// Ничего не должно отслеживаться
continue;
// Проверка отслеживания чанков
{
// Чанки, которые игрок уже не видит и которые только что увидел
std::vector<Pos::Local16_u> lostChunks, newChunks;
// Проверим чанки которые наблюдатель может наблюдать
for(int z = 0; z < 16; z++)
for(int y = 0; y < 16; y++)
for(int x = 0; x < 16; x++) {
Pos::GlobalChunk gcPos((pRegion.first.X << 4) | x,
(pRegion.first.Y << 4) | y,
(pRegion.first.Z << 4) | z);
for(const ContentViewCircle &circle : cvc->second) {
if(circle.isIn(gcPos))
newChunks.push_back(Pos::Local16_u(x, y, z));
}
}
std::unordered_set<Pos::Local16_u> newChunksSet(newChunks.begin(), newChunks.end());
{
auto iterR_W = subs.Chunks.find(pWorld.first);
if(iterR_W == subs.Chunks.end())
// Если мир не отслеживается наблюдателем
goto doesNotObserve;
auto iterR_W_R = iterR_W->second.find(pRegion.first);
if(iterR_W_R == iterR_W->second.end())
// Если регион не отслеживается наблюдателем
goto doesNotObserve;
// Подходят ли уже наблюдаемые чанки под наблюдательные области
for(Pos::Local16_u cPos : iterR_W_R->second) {
Pos::GlobalChunk gcPos((pRegion.first.X << 4) | cPos.X,
(pRegion.first.Y << 4) | cPos.Y,
(pRegion.first.Z << 4) | cPos.Z);
for(const ContentViewCircle &circle : cvc->second) {
if(!circle.isIn(gcPos))
lostChunks.push_back(cPos);
}
}
// Удалим чанки которые наблюдатель уже видит
for(Pos::Local16_u cPos : iterR_W_R->second)
newChunksSet.erase(cPos);
}
doesNotObserve:
if(!newChunksSet.empty() || !lostChunks.empty())
cec->onChunksEnterLost(pWorld.first, pRegion.first, newChunksSet, std::unordered_set<Pos::Local16_u>(lostChunks.begin(), lostChunks.end()));
// Нужно отправить полную информацию о новых наблюдаемых чанках наблюдателю
if(!newChunksSet.empty()) {
std::unordered_map<Pos::Local16_u, const LightPrism*> newLightPrism;
std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> newVoxels;
std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> newNodes;
for(Pos::Local16_u cPos : newChunksSet) {
newLightPrism[cPos] = &region.Lights[0][0][cPos.X][cPos.Y][cPos.Z];
newVoxels[cPos] = &region.Voxels[cPos.X][cPos.Y][cPos.Z];
newNodes[cPos] = &region.Nodes[cPos.X][cPos.Y][cPos.Z];
}
cec->onChunksUpdate_LightPrism(pWorld.first, pRegion.first, newLightPrism);
cec->onChunksUpdate_Voxels(pWorld.first, pRegion.first, newVoxels);
cec->onChunksUpdate_Nodes(pWorld.first, pRegion.first, newNodes);
}
if(!ChangedLightPrism.empty())
cec->onChunksUpdate_LightPrism(pWorld.first, pRegion.first, ChangedLightPrism);
if(!ChangedVoxels.empty())
cec->onChunksUpdate_Voxels(pWorld.first, pRegion.first, ChangedVoxels);
if(!ChangedNodes.empty())
cec->onChunksUpdate_Nodes(pWorld.first, pRegion.first, ChangedNodes);
}
// Проверка отслеживания сущностей
{
std::vector<EntityId_t> newEntityes, lostEntityes;
for(size_t iter = 0; iter < region.Entityes.size(); iter++) {
Entity &entity = region.Entityes[iter];
if(entity.IsRemoved)
continue;
for(const ContentViewCircle &circle : cvc->second) {
int x = entity.ABBOX.x >> 17;
int y = entity.ABBOX.y >> 17;
int z = entity.ABBOX.z >> 17;
uint32_t size = 0;
if(circle.isIn(entity.Pos, x*x+y*y+z*z))
newEntityes.push_back(iter);
}
}
std::unordered_set<EntityId_t> newEntityesSet(newEntityes.begin(), newEntityes.end());
{
auto iterR_W = subs.Entities.find(pWorld.first);
if(iterR_W == subs.Entities.end())
// Если мир не отслеживается наблюдателем
goto doesNotObserveEntityes;
auto iterR_W_R = iterR_W->second.find(pRegion.first);
if(iterR_W_R == iterR_W->second.end())
// Если регион не отслеживается наблюдателем
goto doesNotObserveEntityes;
// Подходят ли уже наблюдаемые сущности под наблюдательные области
for(EntityId_t eId : iterR_W_R->second) {
if(eId >= region.Entityes.size()) {
lostEntityes.push_back(eId);
break;
}
Entity &entity = region.Entityes[eId];
if(entity.IsRemoved) {
lostEntityes.push_back(eId);
break;
}
int x = entity.ABBOX.x >> 17;
int y = entity.ABBOX.y >> 17;
int z = entity.ABBOX.z >> 17;
for(const ContentViewCircle &circle : cvc->second) {
if(!circle.isIn(entity.Pos, x*x+y*y+z*z))
lostEntityes.push_back(eId);
}
}
// Удалим чанки которые наблюдатель уже видит
for(EntityId_t eId : iterR_W_R->second)
newEntityesSet.erase(eId);
}
doesNotObserveEntityes:
cec->onEntityEnterLost(pWorld.first, pRegion.first, newEntityesSet, std::unordered_set<EntityId_t>(lostEntityes.begin(), lostEntityes.end()));
// Отправить полную информацию о новых наблюдаемых сущностях наблюдателю
}
if(!region.Entityes.empty())
cec->onEntityUpdates(pWorld.first, pRegion.first, region.Entityes);
}
// Сохраняем регионы
region.LastSaveTime += CurrentTickDuration;
bool needToUnload = region.CECs.empty() && region.LastSaveTime > 60;
bool needToSave = region.IsChanged && region.LastSaveTime > 15;
if(needToUnload || needToSave) {
region.LastSaveTime = 0;
region.IsChanged = false;
SB_Region data;
convertChunkVoxelsToRegion((const std::vector<VoxelCube>*) region.Voxels, data.Voxels);
SaveBackend.World->save(worldStringId, pRegion.first, &data);
}
// Выгрузим регионы
if(needToUnload) {
regionsToRemove.push_back(pRegion.first);
}
}
for(Pos::GlobalRegion regionPos : regionsToRemove) {
auto iter = wobj.Regions.find(regionPos);
if(iter == wobj.Regions.end())
continue;
wobj.Regions.erase(iter);
}
// Загрузить регионы
if(wobj.NeedToLoad.empty())
continue;
for(Pos::GlobalRegion &regionPos : wobj.NeedToLoad) {
if(!SaveBackend.World->isExist(worldStringId, regionPos)) {
wobj.Regions[regionPos]->IsLoaded = true;
} else {
SB_Region data;
SaveBackend.World->load(worldStringId, regionPos, &data);
Region &robj = *wobj.Regions[regionPos];
// TODO: Передефайнить идентификаторы нод
convertRegionVoxelsToChunks(data.Voxels, (std::vector<VoxelCube>*) robj.Voxels);
}
}
wobj.NeedToLoad.clear();
}
// Проверить отслеживание порталов
}
void GameServer::stepViewContent() {
if(Game.CECs.empty())
return;
// Обновления поля зрения
for(int iter = 0; iter < 1; iter++) {
if(++Game.CEC_NextRebuildViewCircles >= Game.CECs.size())
Game.CEC_NextRebuildViewCircles = 0;
ContentEventController &cec = *Game.CECs[Game.CEC_NextRebuildViewCircles];
ServerObjectPos oPos = cec.getPos();
ContentViewCircle cvc;
cvc.WorldId = oPos.WorldId;
cvc.Pos = {oPos.ObjectPos.x >> (Pos::Object_t::BS_Bit+4), oPos.ObjectPos.y >> (Pos::Object_t::BS_Bit+4), oPos.ObjectPos.z >> (Pos::Object_t::BS_Bit+4)};
cvc.Range = cec.getViewRange();
cec.ContentViewCircles = Expanse.calcAndRemapCVC(cvc);
}
// Прогрузить то, что видят игроки
for(int iter = 0; iter < 1; iter++) {
if(++Game.CEC_NextCheckRegions >= Game.CECs.size())
Game.CEC_NextCheckRegions = 0;
ContentEventController &cec = *Game.CECs[Game.CEC_NextRebuildViewCircles];
ServerObjectPos oLPos = cec.getLastPos(), oPos = cec.getPos();
std::vector<Pos::GlobalRegion> lost;
// Снимаем подписки с регионов
for(auto &pairSR : cec.SubscribedRegions) {
auto CVCs = cec.ContentViewCircles.find(pairSR.first);
if(CVCs == cec.ContentViewCircles.end()) {
lost = pairSR.second;
} else {
for(Pos::GlobalRegion &region : pairSR.second) {
for(ContentViewCircle &circle : CVCs->second) {
if(!circle.isIn(region)) {
lost.push_back(region);
break;
}
}
}
}
cec.onRegionsLost(pairSR.first, lost);
auto world = Expanse.Worlds.find(pairSR.first);
if(world != Expanse.Worlds.end())
world->second->onCEC_RegionsLost(&cec, lost);
lost.clear();
}
// Проверяем отслеживаемые регионы
std::vector<Pos::GlobalRegion> regionsResult;
for(auto &pair : cec.ContentViewCircles) {
auto world = Expanse.Worlds.find(pair.first);
if(world == Expanse.Worlds.end())
continue;
std::vector<Pos::GlobalRegion> regionsLeft;
for(ContentViewCircle &circle : pair.second) {
int16_t offset = (circle.Range >> __builtin_popcount(circle.Range))+1;
glm::i16vec3 left = (circle.Pos >> int16_t(4))-int16_t(offset);
glm::i16vec3 right = (circle.Pos >> int16_t(4))+int16_t(offset);
for(int x = left.x; x <= right.x; x++)
for(int y = left.y; y <= right.y; y++)
for(int z = left.z; z <= right.z; z++) {
if(circle.isIn(Pos::GlobalRegion(x, y, z)))
regionsLeft.emplace_back(x, y, z);
}
}
std::sort(regionsLeft.begin(), regionsLeft.end());
auto last = std::unique(regionsLeft.begin(), regionsLeft.end());
regionsLeft.erase(last, regionsLeft.end());
std::vector<Pos::GlobalRegion> &regionsRight = cec.SubscribedRegions[pair.first];
std::sort(regionsRight.begin(), regionsRight.end());
std::set_difference(regionsLeft.begin(), regionsLeft.end(),
regionsRight.begin(), regionsRight.end(),
std::back_inserter(regionsResult));
if(!regionsResult.empty()) {
regionsRight.insert(regionsRight.end(), regionsResult.begin(), regionsResult.end());
world->second->onCEC_RegionsEnter(&cec, regionsResult);
regionsResult.clear();
}
}
}
}
void GameServer::stepSendPlayersPackets() {
ResourceRequest full;
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
full.insert(cec->Remote->pushPreparedPackets());
}
full.uniq();
if(!full.NewTextures.empty())
Content.TextureM.needResourceResponse(full.NewTextures);
if(!full.NewModels.empty())
Content.ModelM.needResourceResponse(full.NewModels);
if(!full.NewSounds.empty())
Content.SoundM.needResourceResponse(full.NewSounds);
}
void GameServer::stepLoadRegions() {
}
void GameServer::stepGlobal() {
}
void GameServer::stepSave() {
}
void GameServer::save() {
}
}

152
Src/Server/GameServer.hpp Normal file
View File

@@ -0,0 +1,152 @@
#pragma once
#include <Common/Net.hpp>
#include <Common/Lockable.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/io_context.hpp>
#include <filesystem>
#include "RemoteClient.hpp"
#include "Server/Abstract.hpp"
#include <TOSLib.hpp>
#include <memory>
#include <set>
#include <thread>
#include <unordered_map>
#include "ContentEventController.hpp"
#include "WorldDefManager.hpp"
#include "BinaryResourceManager.hpp"
#include "World.hpp"
#include "SaveBackend.hpp"
namespace AL::Server {
namespace fs = std::filesystem;
class GameServer : public AsyncObject {
TOS::Logger LOG = "GameServer";
DestroyLock UseLock;
std::thread RunThread;
bool IsAlive = true, IsGoingShutdown = false;
std::string ShutdownReason;
static constexpr float
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
float
CurrentTickDuration = PerTickDuration, // Текущая длительность такта
GlobalTickLagTime = 0; // На сколько глобально запаздываем по симуляции
struct {
Lockable<std::set<std::string>> ConnectedPlayersSet;
Lockable<std::list<std::unique_ptr<RemoteClient>>> NewConnectedPlayers;
} External;
struct ContentObj {
public:
// WorldDefManager WorldDM;
// VoxelDefManager VoxelDM;
// NodeDefManager NodeDM;
BinaryResourceManager TextureM;
BinaryResourceManager ModelM;
BinaryResourceManager SoundM;
ContentObj(asio::io_context &ioc,
std::shared_ptr<ResourceFile> zeroTexture,
std::shared_ptr<ResourceFile> zeroModel,
std::shared_ptr<ResourceFile> zeroSound)
: TextureM(ioc, zeroTexture),
ModelM(ioc, zeroModel),
SoundM(ioc, zeroSound)
{
}
} Content;
struct {
std::vector<std::unique_ptr<ContentEventController>> CECs;
// Индекс игрока, у которого в следующем такте будет пересмотрен ContentEventController->ContentViewCircles
uint16_t CEC_NextRebuildViewCircles = 0, CEC_NextCheckRegions = 0;
ServerTime AfterStartTime = {0, 0};
} Game;
struct WorldObj {
std::unordered_map<ContentBridgeId_t, ContentBridge> ContentBridges;
std::vector<ContentViewCircle> calcCVCs(ContentViewCircle circle, int depth = 2);
std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> remapCVCsByWorld(const std::vector<ContentViewCircle> &list);
std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> calcAndRemapCVC(ContentViewCircle circle, int depth = 2) {
return remapCVCsByWorld(calcCVCs(circle, depth));
}
std::unordered_map<WorldId_t, std::unique_ptr<World>> Worlds;
/*
Регистрация миров по строке
*/
private:
void _calcContentViewCircles(ContentViewCircle circle, int depth);
} Expanse;
struct {
std::unique_ptr<IWorldSaveBackend> World;
std::unique_ptr<IPlayerSaveBackend> Player;
std::unique_ptr<IAuthSaveBackend> Auth;
std::unique_ptr<IModStorageSaveBackend> ModStorage;
} SaveBackend;
public:
GameServer(asio::io_context &ioc, fs::path worldPath)
: AsyncObject(ioc),
Content(ioc, nullptr, nullptr, nullptr)
{
init(worldPath);
}
virtual ~GameServer();
void shutdown(const std::string reason) {
ShutdownReason = reason;
IsGoingShutdown = true;
}
bool isAlive() {
return IsAlive;
}
void waitShutdown() {
UseLock.wait_no_use();
}
// Подключение tcp сокета
coro<> pushSocketConnect(tcp::socket socket);
// Сокет, прошедший авторизацию (onSocketConnect() передаёт его в onSocketAuthorized())
coro<> pushSocketAuthorized(tcp::socket socket, const std::string username);
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
private:
void init(fs::path worldPath);
void prerun();
void run();
void stepContent();
void stepPlayers();
void stepWorlds();
void stepViewContent();
void stepSendPlayersPackets();
void stepLoadRegions();
void stepGlobal();
void stepSave();
void save();
};
}

View File

@@ -0,0 +1 @@
#pragma once

275
Src/Server/RemoteClient.cpp Normal file
View File

@@ -0,0 +1,275 @@
#include <TOSLib.hpp>
#include "RemoteClient.hpp"
#include "Common/Net.hpp"
#include "Server/Abstract.hpp"
#include <boost/asio/error.hpp>
#include <boost/system/system_error.hpp>
#include <exception>
#include <unordered_map>
#include <unordered_set>
namespace AL::Server {
RemoteClient::~RemoteClient() {
shutdown("~RemoteClient()");
UseLock.wait_no_use();
}
coro<> RemoteClient::run() {
auto useLock = UseLock.lock();
try {
while(!IsGoingShutdown && IsConnected) {
co_await Socket.read<uint8_t>();
}
} catch(const std::exception &exc) {
if(const auto *errc = dynamic_cast<const boost::system::system_error*>(&exc);
errc && errc->code() == boost::asio::error::operation_aborted)
{
co_return;
}
TOS::Logger("PlayerSocket").warn() << Username << ": " << exc.what();
}
IsConnected = false;
co_return;
}
void RemoteClient::shutdown(const std::string reason) {
if(IsGoingShutdown)
return;
IsGoingShutdown = true;
// Отправить пакет о завершении работы
}
void RemoteClient::prepareDefWorld(WorldId_t worldId, void* world) {
}
void RemoteClient::prepareDefVoxel(VoxelId_t voxelId, void* voxel) {
}
void RemoteClient::prepareDefNode(NodeId_t worldId, void* node) {
}
void RemoteClient::prepareDefMediaStream(MediaStreamId_t modelId, void* mediaStream) {
}
// Может прийти событие на чанк, про который ещё ничего не знаем
void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos,
const std::vector<VoxelCube> &voxels)
{
WorldId_c wcId = rentWorldRemapId(worldId);
if(wcId == WorldId_c(-1))
return;
// Перебиндить идентификаторы вокселей
std::vector<VoxelId_t> NeedVoxels;
NeedVoxels.reserve(voxels.size());
for(const VoxelCube &cube : voxels) {
NeedVoxels.push_back(cube.Material);
}
std::unordered_set<VoxelId_t> NeedVoxelsSet(NeedVoxels.begin(), NeedVoxels.end());
// Собираем информацию о конвертации идентификаторов
std::unordered_map<VoxelId_t, VoxelId_c> LocalRemapper;
for(VoxelId_t vId : NeedVoxelsSet) {
auto cvId = Remap.STC_Voxels.find(vId);
if(cvId == Remap.STC_Voxels.end()) {
// Нужно забронировать идентификатор
VoxelId_c cvnId = Remap.UsedVoxelIdC._Find_first();
if(cvnId == VoxelId_c(-1))
// Нет свободных идентификаторов
LocalRemapper[vId] = 0;
else {
NextRequest.NewVoxels.push_back(vId);
Remap.UsedVoxelIdC.reset(cvnId);
Remap.STC_Voxels[vId] = cvnId;
LocalRemapper[vId] = cvnId;
}
} else {
LocalRemapper[vId] = cvId->second;
}
}
Net::Packet packet;
// Packet Id
packet << uint16_t(0);
packet << wcId << Pos::GlobalChunk::Key(chunkPos);
packet << uint16_t(voxels.size());
for(const VoxelCube &cube : voxels) {
packet << LocalRemapper[cube.Material]
<< cube.Left.X << cube.Left.Y << cube.Left.Z
<< cube.Right.X << cube.Right.Y << cube.Right.Z;
}
SimplePackets.push_back(std::move(packet));
}
void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos,
const std::unordered_map<Pos::Local16_u, Node> &nodes)
{
// Перебиндить идентификаторы нод
}
void RemoteClient::prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos,
const LightPrism *lights)
{
}
void RemoteClient::prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkPos)
{
}
void RemoteClient::prepareWorldRemove(WorldId_t worldId)
{
}
void RemoteClient::prepareEntitySwap(WorldId_t prevWorldId, Pos::GlobalRegion prevRegionPos, EntityId_t prevEntityId,
WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, EntityId_t newEntityId)
{
}
void RemoteClient::prepareEntityUpdate(WorldId_t worldId, Pos::GlobalRegion regionPos,
EntityId_t entityId, const Entity *entity)
{
// Может прийти событие на сущность, про которую ещё ничего не знаем
// Сопоставим с идентификатором клиента
EntityId_c ceId = -1;
auto pWorld = Remap.STC_Entityes.find(worldId);
if(pWorld != Remap.STC_Entityes.end()) {
auto pRegion = pWorld->second.find(regionPos);
if(pRegion != pWorld->second.end()) {
auto pId = pRegion->second.find(entityId);
if(pId != pRegion->second.end()) {
ceId = pId->second;
}
}
}
if(ceId == EntityId_c(-1)) {
// Клиент ещё не знает о сущности
// Выделяем идентификатор на стороне клиента для сущностей
ceId = Remap.UsedEntityIdC._Find_first();
if(ceId != EntityId_c(-1)) {
Remap.UsedEntityIdC.reset(ceId);
Remap.CTS_Entityes[ceId] = {worldId, regionPos, entityId};
Remap.STC_Entityes[worldId][regionPos][entityId] = ceId;
}
}
if(ceId == EntityId_c(-1))
return; // У клиента закончились идентификаторы
// Перебиндить ресурсы скомпилированных конвейеров
// Отправить информацию о сущности
// entity ceId
}
void RemoteClient::prepareEntityRemove(WorldId_t worldId, Pos::GlobalRegion regionPos, EntityId_t entityId)
{
// Освобождаем идентификатор на стороне клиента
auto pWorld = Remap.STC_Entityes.find(worldId);
if(pWorld == Remap.STC_Entityes.end())
return;
auto pRegion = pWorld->second.find(regionPos);
if(pRegion == pWorld->second.end())
return;
auto pId = pRegion->second.find(entityId);
if(pId == pRegion->second.end())
return;
EntityId_c ceId = pId->second;
Remap.UsedEntityIdC.set(ceId);
{
auto pceid = Remap.CTS_Entityes.find(ceId);
if(pceid != Remap.CTS_Entityes.end())
Remap.CTS_Entityes.erase(pceid);
}
pRegion->second.erase(pId);
if(pRegion->second.empty()) {
pWorld->second.erase(pRegion);
if(pWorld->second.empty())
Remap.STC_Entityes.erase(pWorld);
}
// Пакет об удалении сущности
// ceId
}
void RemoteClient::preparePortalNew(PortalId_t portalId, void* portal) {}
void RemoteClient::preparePortalUpdate(PortalId_t portalId, void* portal) {}
void RemoteClient::preparePortalRemove(PortalId_t portalId) {}
void RemoteClient::prepareCameraSetEntity(WorldId_t worldId, Pos::GlobalChunk chunkPos, EntityId_t entityId) {}
ResourceRequest RemoteClient::pushPreparedPackets() {
Socket.pushPackets(&SimplePackets);
SimplePackets.clear();
NextRequest.uniq();
return std::move(NextRequest);
}
void RemoteClient::informateDefTexture(const std::unordered_map<TextureId_t, std::shared_ptr<ResourceFile>> &textures) {
}
void RemoteClient::informateDefModel(const std::unordered_map<ModelId_t, std::shared_ptr<ResourceFile>> &models) {
}
void RemoteClient::informateDefSound(const std::unordered_map<SoundId_t, std::shared_ptr<ResourceFile>> &sounds) {
}
WorldId_c RemoteClient::rentWorldRemapId(WorldId_t wId)
{
WorldId_c wcId;
auto cwId = Remap.STC_Worlds.find(wId);
if(cwId == Remap.STC_Worlds.end()) {
// Нужно забронировать идентификатор
wcId = Remap.UsedWorldIdC._Find_first();
if(wcId == WorldId_c(-1))
// Нет свободных идентификаторов
return wcId;
else {
NextRequest.NewWorlds.push_back(wId);
Remap.UsedWorldIdC.reset(wcId);
Remap.STC_Worlds[wId] = wcId;
}
} else {
wcId = cwId->second;
}
return wcId;
}
}

160
Src/Server/RemoteClient.hpp Normal file
View File

@@ -0,0 +1,160 @@
#pragma once
#include <Common/Lockable.hpp>
#include <Common/Net.hpp>
#include "Abstract.hpp"
#include <Common/Abstract.hpp>
#include <bitset>
#include <initializer_list>
#include <unordered_map>
#include <unordered_set>
namespace AL::Server {
/*
Шаблоны игрового контента, которые необходимо поддерживать в актуальном
состоянии для клиента и шаблоны, которые клиенту уже не нужны.
Соответствующие менеджеры ресурсов будут следить за изменениями
этих ресурсов и переотправлять их клиенту
*/
struct ResourceRequest {
std::vector<WorldId_t> NewWorlds;
std::vector<VoxelId_t> NewVoxels;
std::vector<NodeId_t> NewNodes;
std::vector<TextureId_t> NewTextures;
std::vector<ModelId_t> NewModels;
std::vector<SoundId_t> NewSounds;
void insert(const ResourceRequest &obj) {
NewWorlds.insert(NewWorlds.end(), obj.NewWorlds.begin(), obj.NewWorlds.end());
NewVoxels.insert(NewVoxels.end(), obj.NewVoxels.begin(), obj.NewVoxels.end());
NewNodes.insert(NewNodes.end(), obj.NewNodes.begin(), obj.NewNodes.end());
NewTextures.insert(NewTextures.end(), obj.NewTextures.begin(), obj.NewTextures.end());
NewModels.insert(NewModels.end(), obj.NewModels.begin(), obj.NewModels.end());
NewSounds.insert(NewSounds.end(), obj.NewSounds.begin(), obj.NewSounds.end());
}
void uniq() {
for(std::vector<ResourceId_t> *vec : {&NewWorlds, &NewVoxels, &NewNodes, &NewTextures, &NewModels, &NewSounds}) {
std::sort(vec->begin(), vec->end());
auto last = std::unique(vec->begin(), vec->end());
vec->erase(last, vec->end());
}
}
};
/*
Обработчик сокета клиента.
Подписывает клиента на отслеживание необходимых ресурсов
на основе передаваемых клиенту данных
*/
class RemoteClient {
DestroyLock UseLock;
Net::AsyncSocket Socket;
bool IsConnected = true, IsGoingShutdown = false;
struct {
std::bitset<(1 << sizeof(EntityId_c)*8) - 1> UsedEntityIdC; // 1 - идентификатор свободен, 0 - занят
std::unordered_map<EntityId_c, std::tuple<WorldId_t, Pos::GlobalRegion, EntityId_t>> CTS_Entityes;
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_map<EntityId_t, EntityId_c>>> STC_Entityes;
std::unordered_map<TextureId_t, TextureId_c> STC_Textures;
std::unordered_map<ModelId_t, ModelId_c> STC_Models;
//std::unordered_map<SoundId_t, SoundId_c> STC_Sounds;
std::bitset<(1 << sizeof(VoxelId_c)*8) - 1> UsedVoxelIdC; // 1 - идентификатор свободен, 0 - занят
std::unordered_map<VoxelId_t, VoxelId_c> STC_Voxels;
std::bitset<(1 << sizeof(NodeId_c)*8) - 1> UsedNodeIdC; // 1 - идентификатор свободен, 0 - занят
std::unordered_map<NodeId_t, NodeId_c> STC_Nodes;
std::bitset<(1 << sizeof(VoxelId_c)*8) - 1> UsedWorldIdC; // 1 - идентификатор свободен, 0 - занят
std::unordered_map<WorldId_t, WorldId_c> STC_Worlds;
} Remap;
struct {
//std::unordered_map<EntityId_c, > EntityTextures;
} BinaryResourceUsedIds;
// Вести учёт использования идентификаторов
struct {
// Использованные идентификаторы вокселей чанками
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::unordered_set<VoxelId_t>>> Voxels;
// Количество зависимостей к ресурсу
std::unordered_map<VoxelId_t, uint32_t> VoxelsUsedCount;
// Использованные идентификаторы нод чанками
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::unordered_set<NodeId_c>>> Nodes;
// Количество зависимостей к ресурсу
std::unordered_map<NodeId_c, uint32_t> NodesUsedCount;
} ChunkUsedIds;
struct {
} WorldUsedIds;
ResourceRequest NextRequest;
std::vector<Net::Packet> SimplePackets;
public:
const std::string Username;
public:
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
: Socket(ioc, std::move(socket)), Username(username)
{
}
~RemoteClient();
coro<> run();
void shutdown(const std::string reason);
bool isConnected() { return IsConnected; }
void pushPackets(std::vector<Net::Packet> *simplePackets, std::vector<Net::SmartPacket> *smartPackets = nullptr) {
if(IsGoingShutdown)
return;
Socket.pushPackets(simplePackets, smartPackets);
}
// Функции подготавливают пакеты к отправке
// Необходимые определения шаблонов игрового контента
void prepareDefWorld(WorldId_t worldId, void* world);
void prepareDefVoxel(VoxelId_t voxelId, void* voxel);
void prepareDefNode(NodeId_t worldId, void* node);
void prepareDefMediaStream(MediaStreamId_t modelId, void* mediaStream);
// Отслеживаемое игроком использование контента
void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector<VoxelCube> &voxels);
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::unordered_map<Pos::Local16_u, Node> &nodes);
void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
void prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkPos);
void prepareWorldRemove(WorldId_t worldId);
void prepareEntitySwap(WorldId_t prevWorldId, Pos::GlobalRegion prevRegionPos, EntityId_t prevEntityId,
WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, EntityId_t newEntityId);
void prepareEntityUpdate(WorldId_t worldId, Pos::GlobalRegion regionPos, EntityId_t entityId, const Entity *entity);
void prepareEntityRemove(WorldId_t worldId, Pos::GlobalRegion regionPos, EntityId_t entityId);
void preparePortalNew(PortalId_t portalId, void* portal);
void preparePortalUpdate(PortalId_t portalId, void* portal);
void preparePortalRemove(PortalId_t portalId);
// Прочие моменты
void prepareCameraSetEntity(WorldId_t worldId, Pos::GlobalChunk chunkPos, EntityId_t entityId);
// Отправка подготовленных пакетов
ResourceRequest pushPreparedPackets();
// Сообщить о ресурсах
// Сюда приходят все обновления ресурсов движка
// Глобально их можно запросить в выдаче pushPreparedPackets()
void informateDefTexture(const std::unordered_map<TextureId_t, std::shared_ptr<ResourceFile>> &textures);
void informateDefModel(const std::unordered_map<ModelId_t, std::shared_ptr<ResourceFile>> &models);
void informateDefSound(const std::unordered_map<SoundId_t, std::shared_ptr<ResourceFile>> &sounds);
private:
WorldId_c rentWorldRemapId(WorldId_t wId);
};
}

View File

@@ -0,0 +1,11 @@
#include "SaveBackend.hpp"
namespace AL::Server {
IWorldSaveBackend::~IWorldSaveBackend() = default;
IPlayerSaveBackend::~IPlayerSaveBackend() = default;
IAuthSaveBackend::~IAuthSaveBackend() = default;
IModStorageSaveBackend::~IModStorageSaveBackend() = default;
ISaveBackendProvider::~ISaveBackendProvider() = default;
}

111
Src/Server/SaveBackend.hpp Normal file
View File

@@ -0,0 +1,111 @@
#pragma once
#include "Abstract.hpp"
#include "Common/Abstract.hpp"
#include <boost/json.hpp>
#include <boost/json/object.hpp>
#include <memory>
#include <unordered_map>
namespace AL::Server {
struct SB_Region {
std::vector<VoxelCube_Region> Voxels;
std::unordered_map<VoxelId_t, std::string> VoxelsMap;
std::unordered_map<Pos::Local16_u, Node> Nodes;
std::unordered_map<NodeId_t, std::string> NodeMap;
std::vector<Entity> Entityes;
};
class IWorldSaveBackend {
public:
virtual ~IWorldSaveBackend();
// Может ли использоваться параллельно
virtual bool isAsync() { return false; };
// Существует ли регион
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) = 0;
// Загрузить регион
virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) = 0;
// Сохранить регион
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) = 0;
// Удалить регион
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) = 0;
// Удалить мир
virtual void remove(std::string worldId) = 0;
};
struct SB_Player {
};
class IPlayerSaveBackend {
public:
virtual ~IPlayerSaveBackend();
// Может ли использоваться параллельно
virtual bool isAsync() { return false; };
// Существует ли игрок
virtual bool isExist(PlayerId_t playerId) = 0;
// Загрузить игрока
virtual void load(PlayerId_t playerId, SB_Player *data) = 0;
// Сохранить игрока
virtual void save(PlayerId_t playerId, const SB_Player *data) = 0;
// Удалить игрока
virtual void remove(PlayerId_t playerId) = 0;
};
struct SB_Auth {
uint32_t Id;
std::string PasswordHash;
};
class IAuthSaveBackend {
public:
virtual ~IAuthSaveBackend();
// Может ли использоваться параллельно
virtual bool isAsync() { return false; };
// Существует ли игрок
virtual bool isExist(std::string playerId) = 0;
// Переименовать игрока
virtual void rename(std::string fromPlayerId, std::string toPlayerId) = 0;
// Загрузить игрока
virtual void load(std::string playerId, SB_Auth *data) = 0;
// Сохранить игрока
virtual void save(std::string playerId, const SB_Auth *data) = 0;
// Удалить игрока
virtual void remove(std::string playerId) = 0;
};
class IModStorageSaveBackend {
public:
virtual ~IModStorageSaveBackend();
// Может ли использоваться параллельно
virtual bool isAsync() { return false; };
// Загрузить запись
virtual void load(std::string domain, std::string key, std::string *data) = 0;
// Сохранить запись
virtual void save(std::string domain, std::string key, const std::string *data) = 0;
// Удалить запись
virtual void remove(std::string domain, std::string key) = 0;
// Удалить домен
virtual void remove(std::string domain) = 0;
};
class ISaveBackendProvider {
public:
virtual ~ISaveBackendProvider();
virtual bool isAvailable() = 0;
virtual std::string getName() = 0;
virtual std::unique_ptr<IWorldSaveBackend> createWorld(boost::json::object data) = 0;
virtual std::unique_ptr<IPlayerSaveBackend> createPlayer(boost::json::object data) = 0;
virtual std::unique_ptr<IAuthSaveBackend> createAuth(boost::json::object data) = 0;
virtual std::unique_ptr<IModStorageSaveBackend> createModStorage(boost::json::object data) = 0;
};
}

View File

@@ -0,0 +1,256 @@
#include "Filesystem.hpp"
#include "Server/Abstract.hpp"
#include "Server/SaveBackend.hpp"
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/parser.hpp>
#include <boost/json/serializer.hpp>
#include <boost/json/value.hpp>
#include <memory>
#include <filesystem>
#include <fstream>
#include <string>
namespace AL::Server::SaveBackends {
namespace fs = std::filesystem;
namespace js = boost::json;
class WSB_Filesystem : public IWorldSaveBackend {
fs::path Dir;
public:
WSB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("Path").as_string();
}
virtual ~WSB_Filesystem() {
}
fs::path getPath(std::string worldId, Pos::GlobalRegion regionPos) {
return Dir / worldId / std::to_string(regionPos.X) / std::to_string(regionPos.Y) / std::to_string(regionPos.Z);
}
virtual bool isAsync() { return false; };
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) {
return fs::exists(getPath(worldId, regionPos));
}
virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) {
std::ifstream fd(getPath(worldId, regionPos));
js::object jobj = js::parse(fd).as_object();
{
js::array &jaVoxels = jobj.at("Voxels").as_array();
for(js::value &jvVoxel : jaVoxels) {
js::object &joVoxel = jvVoxel.as_object();
VoxelCube_Region cube;
cube.Material = joVoxel.at("Material").as_uint64();
cube.Left.X = joVoxel.at("LeftX").as_uint64();
cube.Left.Y = joVoxel.at("LeftY").as_uint64();
cube.Left.Z = joVoxel.at("LeftZ").as_uint64();
cube.Right.X = joVoxel.at("RightX").as_uint64();
cube.Right.Y = joVoxel.at("RightY").as_uint64();
cube.Right.Z = joVoxel.at("RightZ").as_uint64();
data->Voxels.push_back(cube);
}
}
{
js::object &joVoxelMap = jobj.at("VoxelsMap").as_object();
for(js::key_value_pair &jkvp : joVoxelMap) {
data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
}
}
}
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
js::object jobj;
{
js::array jaVoxels;
for(const VoxelCube_Region &cube : data->Voxels) {
js::object joVoxel;
joVoxel["Material"] = cube.Material;
joVoxel["LeftX"] = cube.Left.X;
joVoxel["LeftY"] = cube.Left.Y;
joVoxel["LeftZ"] = cube.Left.Z;
joVoxel["RightX"] = cube.Right.X;
joVoxel["RightY"] = cube.Right.Y;
joVoxel["RightZ"] = cube.Right.Z;
jaVoxels.push_back(std::move(joVoxel));
}
jobj["Voxels"] = std::move(jaVoxels);
}
{
js::object joVoxelMap;
for(const auto &pair : data->VoxelsMap) {
joVoxelMap[std::to_string(pair.first)] = pair.second;
}
jobj["VoxelsMap"] = std::move(joVoxelMap);
}
fs::create_directories(getPath(worldId, regionPos).parent_path());
std::ofstream fd(getPath(worldId, regionPos));
fd << js::serialize(jobj);
}
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) {
fs::remove(getPath(worldId, regionPos));
}
virtual void remove(std::string worldId) {
fs::remove_all(Dir / worldId);
}
};
class PSB_Filesystem : public IPlayerSaveBackend {
fs::path Dir;
public:
PSB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("Path").as_string();
}
virtual ~PSB_Filesystem() {
}
fs::path getPath(PlayerId_t playerId) {
return Dir / std::to_string(playerId);
}
virtual bool isAsync() { return false; };
virtual bool isExist(PlayerId_t playerId) {
return fs::exists(getPath(playerId));
}
virtual void load(PlayerId_t playerId, SB_Player *data) {
}
virtual void save(PlayerId_t playerId, const SB_Player *data) {
}
virtual void remove(PlayerId_t playerId) {
}
};
class ASB_Filesystem : public IAuthSaveBackend {
fs::path Dir;
public:
ASB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("Path").as_string();
}
virtual ~ASB_Filesystem() {
}
fs::path getPath(std::string playerId) {
return Dir / playerId;
}
virtual bool isAsync() { return false; };
virtual bool isExist(std::string playerId) {
return fs::exists(getPath(playerId));
}
virtual void rename(std::string fromPlayerId, std::string toPlayerId) {
fs::rename(getPath(fromPlayerId), getPath(toPlayerId));
}
virtual void load(std::string playerId, SB_Auth *data) {
std::ifstream fd(getPath(playerId));
js::object jobj = js::parse(fd).as_object();
data->Id = jobj.at("Id").as_uint64();
data->PasswordHash = jobj.at("PasswordHash").as_string();
}
virtual void save(std::string playerId, const SB_Auth *data) {
js::object jobj;
jobj["Id"] = data->Id;
jobj["PasswordHash"] = data->PasswordHash;
fs::create_directories(getPath(playerId).parent_path());
std::ofstream fd(getPath(playerId));
fd << js::serialize(jobj);
}
virtual void remove(std::string playerId) {
fs::remove(getPath(playerId));
}
};
class MSSB_Filesystem : public IModStorageSaveBackend {
fs::path Dir;
public:
MSSB_Filesystem(const boost::json::object &data) {
Dir = (std::string) data.at("Path").as_string();
}
virtual ~MSSB_Filesystem() {
}
virtual bool isAsync() { return false; };
virtual void load(std::string domain, std::string key, std::string *data) {
}
virtual void save(std::string domain, std::string key, const std::string *data) {
}
virtual void remove(std::string domain, std::string key) {
}
virtual void remove(std::string domain) {
}
};
Filesystem::~Filesystem() {
}
bool Filesystem::isAvailable() {
return true;
}
std::string Filesystem::getName() {
return "Filesystem";
}
std::unique_ptr<IWorldSaveBackend> Filesystem::createWorld(boost::json::object data) {
return std::make_unique<WSB_Filesystem>(data);
}
std::unique_ptr<IPlayerSaveBackend> Filesystem::createPlayer(boost::json::object data) {
return std::make_unique<PSB_Filesystem>(data);
}
std::unique_ptr<IAuthSaveBackend> Filesystem::createAuth(boost::json::object data) {
return std::make_unique<ASB_Filesystem>(data);
}
std::unique_ptr<IModStorageSaveBackend> Filesystem::createModStorage(boost::json::object data) {
return std::make_unique<MSSB_Filesystem>(data);
}
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include <Server/SaveBackend.hpp>
namespace AL::Server::SaveBackends {
class Filesystem : public ISaveBackendProvider {
public:
virtual ~Filesystem();
virtual bool isAvailable() override;
virtual std::string getName() override;
virtual std::unique_ptr<IWorldSaveBackend> createWorld(boost::json::object data) override;
virtual std::unique_ptr<IPlayerSaveBackend> createPlayer(boost::json::object data) override;
virtual std::unique_ptr<IAuthSaveBackend> createAuth(boost::json::object data) override;
virtual std::unique_ptr<IModStorageSaveBackend> createModStorage(boost::json::object data) override;
};
}

View File

@@ -0,0 +1 @@
#pragma once

60
Src/Server/World.cpp Normal file
View File

@@ -0,0 +1,60 @@
#include "World.hpp"
namespace AL::Server {
World::World(WorldId_t id) {
}
World::~World() {
}
void World::onUpdate(GameServer *server, float dtime) {
}
void World::onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter) {
for(const Pos::GlobalRegion &pos : enter) {
std::unique_ptr<Region> &region = Regions[pos];
if(!region) {
region = std::make_unique<Region>();
NeedToLoad.push_back(pos);
}
region->CECs.push_back(cec);
}
}
void World::onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost) {
for(const Pos::GlobalRegion &pos : lost) {
auto region = Regions.find(pos);
if(region == Regions.end())
continue;
std::vector<ContentEventController*> &CECs = region->second->CECs;
for(size_t iter = 0; iter < CECs.size(); iter++) {
if(CECs[iter] == cec) {
CECs.erase(CECs.begin()+iter);
break;
}
}
}
}
Region* World::forceLoadOrGetRegion(Pos::GlobalRegion pos) {
std::unique_ptr<Region> &region = Regions[pos];
if(!region)
region = std::make_unique<Region>();
if(!region->IsLoaded) {
region->IsLoaded = true;
}
return region.get();
}
}

162
Src/Server/World.hpp Normal file
View File

@@ -0,0 +1,162 @@
#pragma once
#include "Common/Abstract.hpp"
#include "Server/Abstract.hpp"
#include "Server/ContentEventController.hpp"
#include <memory>
#include <unordered_map>
namespace AL::Server {
class GameServer;
class Region {
public:
uint64_t IsChunkChanged_Voxels[64] = {0};
uint64_t IsChunkChanged_Nodes[64] = {0};
bool IsChanged = false;
// cx cy cz
std::vector<VoxelCube> Voxels[16][16][16];
// x y cx cy cz
LightPrism Lights[16][16][16][16][16];
std::unordered_map<Pos::Local16_u, Node> Nodes[16][16][16];
std::vector<Entity> Entityes;
std::vector<ContentEventController*> CECs;
bool IsLoaded = false;
float LastSaveTime = 0;
void getCollideBoxes(Pos::GlobalRegion rPos, AABB aabb, std::vector<CollisionAABB> &boxes) {
// Абсолютная позиция начала региона
Pos::Object raPos(rPos.X, rPos.Y, rPos.Z);
raPos <<= Pos::Object_t::BS_Bit;
// Бокс региона
AABB regionAABB(raPos, raPos+Pos::Object(Pos::Object_t::BS*256));
// Если регион не загружен, то он весь непроходим
if(!IsLoaded) {
boxes.emplace_back(regionAABB);
return;
}
// Собираем коробки сущностей
for(size_t iter = 0; iter < Entityes.size(); iter++) {
Entity &entity = Entityes[iter];
if(entity.IsRemoved)
continue;
CollisionAABB aabbInfo = CollisionAABB(entity.aabbAtPos());
if(aabbInfo.isCollideWith(aabb)) {
aabbInfo.Type = CollisionAABB::EnumType::Entity;
aabbInfo.Entity.Index = iter;
boxes.push_back(aabbInfo);
}
}
// Собираем коробки вокселей
if(aabb.isCollideWith(regionAABB)) {
glm::ivec3 beg, end;
for(int axis = 0; axis < 3; axis++)
beg[axis] = std::max(aabb.VecMin[axis], regionAABB.VecMin[axis]) >> 16;
for(int axis = 0; axis < 3; axis++)
end[axis] = (std::min(aabb.VecMax[axis], regionAABB.VecMax[axis])+0xffff) >> 16;
for(; beg.z <= end.z; beg.z++)
for(; beg.y <= end.y; beg.y++)
for(; beg.x <= end.x; beg.x++) {
std::vector<VoxelCube> &voxels = Voxels[beg.x][beg.y][beg.z];
if(voxels.empty())
continue;
CollisionAABB aabbInfo = CollisionAABB(regionAABB);
for(int axis = 0; axis < 3; axis++)
aabbInfo.VecMin[axis] |= beg[axis] << 16;
for(size_t iter = 0; iter < voxels.size(); iter++) {
VoxelCube &cube = voxels[iter];
for(int axis = 0; axis < 3; axis++)
aabbInfo.VecMin[axis] &= ~0xff00;
aabbInfo.VecMax = aabbInfo.VecMin;
aabbInfo.VecMin.x |= int(cube.Left.X) << 8;
aabbInfo.VecMin.y |= int(cube.Left.Y) << 8;
aabbInfo.VecMin.z |= int(cube.Left.Z) << 8;
aabbInfo.VecMax.x |= int(cube.Right.X) << 8;
aabbInfo.VecMax.y |= int(cube.Right.Y) << 8;
aabbInfo.VecMax.z |= int(cube.Right.Z) << 8;
if(aabb.isCollideWith(aabbInfo)) {
aabbInfo = {
.Type = CollisionAABB::EnumType::Voxel,
.Voxel = {
.Chunk = Pos::Local16_u(beg.x, beg.y, beg.z),
.Index = static_cast<uint32_t>(iter),
.Id = cube.Material
}
};
boxes.push_back(aabbInfo);
}
}
}
}
// Собираем коробки нод
}
EntityId_t pushEntity(Entity &entity) {
for(size_t iter = 0; iter < Entityes.size(); iter++) {
Entity &obj = Entityes[iter];
if(!obj.IsRemoved)
continue;
obj = std::move(entity);
return iter;
}
if(Entityes.size() < 0xfffe) {
Entityes.emplace_back(std::move(entity));
return Entityes.size()-1;
}
return EntityId_t(-1);
}
};
class World {
WorldId_t Id;
public:
std::vector<Pos::GlobalRegion> NeedToLoad;
std::unordered_map<Pos::GlobalRegion, std::unique_ptr<Region>> Regions;
public:
World(WorldId_t id);
~World();
/*
Обновить регионы
*/
void onUpdate(GameServer *server, float dtime);
// Игрок начал отслеживать регионы
void onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter);
void onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost);
Region* forceLoadOrGetRegion(Pos::GlobalRegion pos);
};
}

View File

72
Src/assets.cpp Normal file
View File

@@ -0,0 +1,72 @@
#include "assets.hpp"
#include <TOSLib.hpp>
#include <boost/smart_ptr/scoped_array.hpp>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <unordered_map>
extern std::unordered_map<std::string, std::tuple<const char*, const char*>> _binary_assets_symbols;
namespace fs = std::filesystem;
namespace AL {
Resource::Resource() = default;
Resource::~Resource() = default;
static std::mutex ResourceCacheMtx;
static std::unordered_map<std::string, std::weak_ptr<Resource>> ResourceCache;
class FS_Resource : public Resource {
boost::scoped_array<uint8_t> Array;
public:
FS_Resource(const std::filesystem::path &path)
{
std::ifstream fd(path);
if(!fd)
MAKE_ERROR("Ошибка чтения ресурса: " << path);
fd.seekg(0, std::ios::end);
Size = fd.tellg();
fd.seekg(0, std::ios::beg);
Array.reset(new uint8_t[Size]);
fd.read((char*) Array.get(), Size);
Data = Array.get();
}
virtual ~FS_Resource() = default;
};
std::shared_ptr<Resource> getResource(const std::string &path) {
std::unique_lock<std::mutex> lock(ResourceCacheMtx);
if(auto iter = ResourceCache.find(path); iter != ResourceCache.end()) {
std::shared_ptr<Resource> resource = iter->second.lock();
if(!resource) {
ResourceCache.erase(iter);
} else {
return resource;
}
}
fs::path fs_path("assets");
fs_path /= path;
if(fs::exists(fs_path)) {
std::shared_ptr<Resource> resource = std::make_shared<FS_Resource>(fs_path);
ResourceCache.emplace(path, resource);
TOS::Logger("Resources").debug() << "Ресурс " << fs_path << " найден в фс";
return resource;
}
if(auto iter = _binary_assets_symbols.find(path); iter != _binary_assets_symbols.end()) {
TOS::Logger("Resources").debug() << "Ресурс " << fs_path << " is inlined";
return std::make_shared<Resource>((const uint8_t*) std::get<0>(iter->second), std::get<1>(iter->second)-std::get<0>(iter->second));
}
MAKE_ERROR("Ресурс " << path << " не найден");
}
}

54
Src/assets.hpp Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
#include <istream>
#include <memory>
namespace AL {
namespace detail {
struct membuf : std::streambuf {
membuf(char* begin, size_t size) {
setg(begin, begin, begin+size);
}
};
}
struct iBinaryStream : detail::membuf {
std::istream Stream;
iBinaryStream(const char* begin, size_t size)
: detail::membuf(const_cast<char*>(begin), size), Stream(this)
{}
};
class Resource {
protected:
const uint8_t* Data;
size_t Size;
public:
Resource();
Resource(const uint8_t* data, size_t size)
: Data(data), Size(size)
{}
virtual ~Resource();
Resource(const Resource&) = delete;
Resource(Resource&&) = delete;
Resource& operator=(const Resource&) = delete;
Resource& operator=(Resource&&) = delete;
const uint8_t* getData() const { return Data; }
size_t getSize() const { return Size; }
std::string_view makeView() const { return std::string_view((const char*) Data, Size); }
iBinaryStream makeStream() const { return iBinaryStream((const char*) Data, Size); }
};
std::shared_ptr<Resource> getResource(const std::string &path);
}

23
Src/assets.py Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/python3
import sys
import re
output_file = "resources.cpp"
with open(output_file, "w") as f:
f.write("#include <unordered_map>\n#include <string>\n#include <tuple>\n\nextern \"C\" {\n")
for symbol in sys.argv[1:]:
var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol)
f.write(f"\textern const char {var_name}_start[];\n\textern const char {var_name}_end[];\n")
f.write("}")
f.write("\n\nstd::unordered_map<std::string, std::tuple<const char*, const char*>> _binary_assets_symbols = {\n")
for symbol in sys.argv[1:]:
var_name = "_binary_" + re.sub('[^a-zA-Z0-9]', '_', symbol)
f.write(f"\t{{\"{symbol}\", {{(const char*) &{var_name}_start, (const char*) &{var_name}_end}}}},\n")
f.write("};\n")
print(f"File {output_file} is generated.")

61
Src/main.cpp Normal file
View File

@@ -0,0 +1,61 @@
#include <boost/asio/buffer.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/write.hpp>
#include <iostream>
#include <memory>
#include "Client/ServerSession.hpp"
#include "Common/Net.hpp"
#include "Server/GameServer.hpp"
namespace AL {
coro<> runClient(asio::io_context &ioc, uint16_t port) {
try {
tcp::socket sock = co_await Net::asyncConnectTo("localhost:"+std::to_string(port));
co_await Client::ServerSession::asyncAuthorizeWithServer(sock, "DrSocalkwe3n", "1password2", 1);
std::unique_ptr<Net::AsyncSocket> asock = co_await Client::ServerSession::asyncInitGameProtocol(ioc, std::move(sock));
} catch(const std::exception &exc) {
std::cout << exc.what() << std::endl;
}
}
int main() {
// VK::Vulkan VkInst;
// VkInst.getSettingsNext() = VkInst.getBestSettings();
// VkInst.reInit();
// VkInst.start([&](VK::Vulkan *instance, int subpass, VkCommandBuffer &renderCmd)
// {
// });
// LuaVox
asio::io_context ioc;
Server::GameServer gs(ioc, "");
Net::Server server(ioc, [&](tcp::socket sock) -> coro<> {
server.stop();
co_await gs.pushSocketConnect(std::move(sock));
}, 6666);
std::cout << server.getPort() << std::endl;
asio::co_spawn(ioc, runClient(ioc, server.getPort()), asio::detached);
ioc.run();
return 0;
}
}
int main() {
TOS::Logger::addLogOutput(".*", TOS::EnumLogType::All);
std::cout << "Hello world!" << std::endl;
return AL::main();
}