Files
LuaVox/Src/Server/RemoteClient.hpp
2025-08-14 17:52:24 +06:00

449 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <TOSLib.hpp>
#include <Common/Lockable.hpp>
#include <Common/Net.hpp>
#include "Abstract.hpp"
#include "Common/Packets.hpp"
#include "Server/AssetsManager.hpp"
#include "Server/ContentEventController.hpp"
#include "assets.hpp"
#include <Common/Abstract.hpp>
#include <atomic>
#include <bitset>
#include <initializer_list>
#include <queue>
#include <set>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
namespace LV::Server {
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
class CSChunkedMapper {
std::unordered_map<uint32_t, std::tuple<std::bitset<64>, std::array<ServerKey, 64>>> Chunks;
public:
ServerKey toServer(ClientKey cKey) {
int chunkIndex = cKey >> 6;
int subIndex = cKey & 0x3f;
auto iChunk = Chunks.find(chunkIndex);
assert(iChunk != Chunks.end() && "Идентификатор уже занят");
std::bitset<64> &bits = std::get<0>(iChunk.second);
std::array<ServerKey, 64> &keys = std::get<1>(iChunk.second);
assert(bits.test(subIndex) && "Идентификатор уже занят");
return keys[subIndex];
}
void erase(ClientKey cKey) {
int chunkIndex = cKey >> 6;
int subIndex = cKey & 0x3f;
auto iChunk = Chunks.find(chunkIndex);
if(iChunk == Chunks.end())
MAKE_ERROR("Идентификатор не привязан");
std::bitset<64> &bits = std::get<0>(iChunk->second);
std::array<ServerKey, 64> &keys = std::get<1>(iChunk->second);
assert(bits.test(subIndex) && "Идентификатор уже занят");
bits.reset(subIndex);
}
void link(ClientKey cKey, ServerKey sKey) {
int chunkIndex = cKey >> 6;
int subIndex = cKey & 0x3f;
std::tuple<std::bitset<64>, std::array<ServerKey, 64>> &chunk = Chunks[chunkIndex];
std::bitset<64> &bits = std::get<0>(chunk);
std::array<ServerKey, 64> &keys = std::get<1>(chunk);
assert(!bits.test(subIndex) && "Идентификатор уже занят");
bits.set(subIndex);
keys[subIndex] = sKey;
}
};
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
class SCSKeyRemapper {
std::bitset<sizeof(ClientKey)*8-1> FreeClientKeys;
std::map<ServerKey, ClientKey> Map;
CSChunkedMapper<ServerKey, ClientKey> CSmapper;
public:
SCSKeyRemapper() {
FreeClientKeys.set();
}
// Соотнести идентификатор на стороне сервера с идентификатором на стороне клиента
ClientKey toClient(ServerKey skey) {
auto iter = Map.find(skey);
if(iter == Map.end()) {
// Идентификатор отсутствует, нужно его занять
// Ищет позицию ближайшего бита 1
size_t pos = FreeClientKeys._Find_first();
if(pos == FreeClientKeys.size())
return ClientKey(0); // Свободные идентификаторы отсутствуют
ClientKey ckey = ClientKey(pos+1);
Map[skey] = ckey;
CSmapper.link(ckey, skey);
FreeClientKeys.reset(pos);
return ClientKey(pos);
}
return iter->second;
}
// Соотнести идентификатор на стороне клиента с идентификатором на стороне сервера
ServerKey toServer(ClientKey ckey) {
return CSmapper.toServer(ckey);
}
// Удаляет серверный идентификатор, освобождая идентификатор клиента
ClientKey erase(ServerKey skey) {
auto iter = Map.find(skey);
if(iter == Map.end())
return 0;
ClientKey ckey = iter->second;
CSmapper.erase(ckey);
Map.erase(iter);
FreeClientKeys.set(ckey-1);
return ckey;
}
void rebindClientKey(ServerKey prev, ServerKey next) {
auto iter = Map.find(prev);
assert(iter != Map.end() && "Идентификатор не найден");
ClientKey ckey = iter->second;
CSmapper.erase(ckey);
CSmapper.link(ckey, next);
Map.erase(iter);
Map[next] = ckey;
}
};
/*
Шаблоны игрового контента, которые необходимо поддерживать в актуальном
состоянии для клиента и шаблоны, которые клиенту уже не нужны.
Соответствующие менеджеры ресурсов будут следить за изменениями
этих ресурсов и переотправлять их клиенту
Информация о двоичных ресурсах будет получена сразу же при их запросе.
Действительная отправка ресурсов будет только по запросу клиента.
*/
struct ResourceRequest {
std::vector<Hash_t> Hashes;
std::vector<ResourceId> BinToHash[5 /*EnumBinResource*/];
std::vector<DefVoxelId> Voxel;
std::vector<DefNodeId> Node;
std::vector<DefWorldId_t> World;
std::vector<DefPortalId_t> Portal;
std::vector<DefEntityId_t> Entity;
std::vector<DefItemId_t> Item;
void insert(const ResourceRequest &obj) {
Hashes.insert(Hashes.end(), obj.Hashes.begin(), obj.Hashes.end());
for(int iter = 0; iter < 5; iter++)
BinToHash[iter].insert(BinToHash[iter].end(), obj.BinToHash[iter].begin(), obj.BinToHash[iter].end());
Voxel.insert(Voxel.end(), obj.Voxel.begin(), obj.Voxel.end());
Node.insert(Node.end(), obj.Node.begin(), obj.Node.end());
World.insert(World.end(), obj.World.begin(), obj.World.end());
Portal.insert(Portal.end(), obj.Portal.begin(), obj.Portal.end());
Entity.insert(Entity.end(), obj.Entity.begin(), obj.Entity.end());
Item.insert(Item.end(), obj.Item.begin(), obj.Item.end());
}
void uniq() {
for(std::vector<ResourceId> *vec : {&Voxel, &Node, &World,
&Portal, &Entity, &Item
})
{
std::sort(vec->begin(), vec->end());
auto last = std::unique(vec->begin(), vec->end());
vec->erase(last, vec->end());
}
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++)
{
std::sort(BinToHash[type].begin(), BinToHash[type].end());
auto last = std::unique(BinToHash[type].begin(), BinToHash[type].end());
BinToHash[type].erase(last, BinToHash[type].end());
}
std::sort(Hashes.begin(), Hashes.end());
auto last = std::unique(Hashes.begin(), Hashes.end());
Hashes.erase(last, Hashes.end());
}
};
// using EntityKey = std::tuple<WorldId_c, Pos::GlobalRegion>;
/*
Обработчик сокета клиента.
Подписывает клиента на отслеживание необходимых ресурсов
на основе передаваемых клиенту данных
*/
class RemoteClient {
TOS::Logger LOG;
DestroyLock UseLock;
Net::AsyncSocket Socket;
bool IsConnected = true, IsGoingShutdown = false;
std::vector<Hash_t> ClientBinaryCache, // Хеши ресурсов которые есть у клиента
NeedToSend; // Хеши которые нужно получить и отправить
/*
При обнаружении нового контента составляется запрос (ResourceRequest)
на полное описание ресурса. Это описание отправляется клиенту и используется
чтобы выстроить зависимость какие базовые ресурсы использует контент.
Если базовые ресурсы не известны, то они также запрашиваются.
*/
struct NetworkAndResource_t {
struct ResUsesObj {
// Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору
std::map<ResourceId, std::tuple<uint32_t, Hash_t>> AssetsUse[(int) EnumAssets::MAX_ENUM];
// Зависимость профилей контента от профилей ресурсов
// Нужно чтобы пересчитать зависимости к профилям ресурсов
struct RefAssets_t {
std::vector<ResourceId> Resources[(int) EnumAssets::MAX_ENUM];
};
std::map<DefVoxelId, RefAssets_t> RefDefVoxel;
std::map<DefNodeId, RefAssets_t> RefDefNode;
std::map<WorldId_t, RefAssets_t> RefDefWorld;
std::map<DefPortalId, RefAssets_t> RefDefPortal;
std::map<DefEntityId, RefAssets_t> RefDefEntity;
std::map<DefItemId, RefAssets_t> RefDefItem;
// Счётчики использование профилей контента
std::map<DefVoxelId, uint32_t> DefVoxel; // Один чанк, одно использование
std::map<DefNodeId, uint32_t> DefNode;
std::map<DefWorldId, uint32_t> DefWorld;
std::map<DefPortalId, uint32_t> DefPortal;
std::map<DefEntityId, uint32_t> DefEntity;
std::map<DefItemId, uint32_t> DefItem; // При передаче инвентарей?
// Зависимость наблюдаемых чанков от профилей нод и вокселей
struct ChunkRef {
// Отсортированные списки уникальных вокселей
std::vector<DefVoxelId> Voxel;
std::vector<DefNodeId> Node;
};
std::map<WorldId_t, std::map<Pos::GlobalRegion, std::array<ChunkRef, 4*4*4>>> RefChunk;
// Модификационные зависимости экземпляров профилей контента
// У сущностей в мире могут дополнительно изменятся свойства, переписывая их профиль
struct RefWorld_t {
DefWorldId Profile;
RefAssets_t Assets;
};
std::map<WorldId_t, RefWorld_t> RefWorld;
struct RefPortal_t {
DefPortalId Profile;
RefAssets_t Assets;
};
// std::map<PortalId, RefPortal_t> RefPortal;
struct RefEntity_t {
DefEntityId Profile;
RefAssets_t Assets;
};
std::map<ServerEntityId_t, RefEntity_t> RefEntity;
} ResUses;
// Смена идентификаторов сервера на клиентские
SCSKeyRemapper<ServerEntityId_t, ClientEntityId_t> ReMapEntities;
Net::Packet NextPacket;
std::vector<Net::Packet> SimplePackets;
ResourceRequest NextRequest;
};
struct {
// Ресурсы, отправленные на клиент в этой сессии
std::vector<Hash_t> OnClient;
std::vector<std::tuple<EnumAssets, ResourceId, AssetsManager::Resource>> ToSend;
} AssetsInWork;
TOS::SpinlockObject<NetworkAndResource_t> NetworkAndResource;
public:
const std::string Username;
Pos::Object CameraPos = {0, 0, 0};
ToServer::PacketQuat CameraQuat = {0};
TOS::SpinlockObject<std::queue<uint8_t>> Actions;
ResourceId RecievedAssets[(int) EnumAssets::MAX_ENUM] = {0};
// Регионы, наблюдаемые клиентом
ContentViewInfo ContentViewState;
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
bool CrossedRegion = true;
public:
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, std::vector<ResourceFile::Hash_t> &&client_cache)
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), ClientBinaryCache(std::move(client_cache))
{
}
~RemoteClient();
coro<> run();
void shutdown(EnumDisconnect type, 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);
}
// Возвращает список точек наблюдений клиентом с радиусом в регионах
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> getViewPoints();
/*
Сервер собирает изменения миров, сжимает их и раздаёт на отправку игрокам
*/
// Функции подготавливают пакеты к отправке
// Отслеживаемое игроком использование контента
TOS::Spinlock MurkyLock;
Net::Packet MurkyNextPacket;
std::vector<Net::Packet> MurkySimplePackets;
void murkyCheckPacketBorder(uint16_t size) {
if(64000-MurkyNextPacket.size() < size || (MurkyNextPacket.size() != 0 && size == 0)) {
MurkySimplePackets.push_back(std::move(MurkyNextPacket));
}
}
// marky используются в BackingChunkPressure_t в GameServer во время заморозки мира от записи.
// В это время просматриваются изменённые объекты и рассылаются изменения клиентам
/*
Все пробегаются по игрокам, и смотрят наблюдаемые миры.
Если идентификатор мира % количество потоков == 0, то проверяем что
этот мир наблюдается игроком и готовим информацию о нём для отправки,
отправляем
Синхронизация этапа с группой
Потоки рассылки изменений соблюдают пакетность изменений.
Изменение чанков, потеря регионов, изменения чанков, потеря регионов
игрок % потоки == 0
Информируем о потерянных регионах
Информируем о потерянных мирах
Синхронизация этапа с группой
*/
/*
Использует пакеты
Используемые ресурсы
Запросы на ресурсы
Объекты можно удалять когда это будет определено.
Потом при попытке отправить чанк будет проверка наблюдения
объекта клиентом
*/
// В зоне видимости добавился чанк или изменились его воксели
bool murky_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels,
const std::vector<DefVoxelId>& uniq_sorted_defines);
// В зоне видимости добавился чанк или изменились его ноды
bool murky_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes,
const std::vector<DefNodeId>& uniq_sorted_defines);
// void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
// Мир удалён из зоны видимости
void prepareWorldRemove(WorldId_t worldId);
// Регион удалён из зоны видимости
void prepareRegionRemove(WorldId_t worldId, std::vector<Pos::GlobalRegion> regionPoses);
// Клиент перестал наблюдать за сущностью
void prepareEntityRemove(ServerEntityId_t entityId);
// В зоне видимости добавилась новая сущность или она изменилась
void prepareEntityUpdate(ServerEntityId_t entityId, const Entity *entity);
void prepareEntityUpdate_Dynamic(ServerEntityId_t entityId, const Entity *entity);
// Мир появился в зоне видимости или изменился
void prepareWorldUpdate(WorldId_t worldId, World* world);
// Наблюдаемая сущность пересекла границы региона, у неё изменился серверный идентификатор
void prepareEntitySwap(ServerEntityId_t prevEntityId, ServerEntityId_t nextEntityId);
// В зоне видимости добавился порта или он изменился
// void preparePortalUpdate(PortalId_t portalId, void* portal);
// Клиент перестал наблюдать за порталом
// void preparePortalRemove(PortalId_t portalId);
// Прочие моменты
void prepareCameraSetEntity(ServerEntityId_t entityId);
// Отправка подготовленных пакетов
ResourceRequest pushPreparedPackets();
// Сообщить о ресурсах
// Сюда приходят все обновления ресурсов движка
// Глобально их можно запросить в выдаче pushPreparedPackets()
// Оповещение о ресурсе для отправки клиентам
void informateBinary(const std::vector<std::shared_ptr<ResourceFile>>& resources);
// Привязывает локальный идентификатор с хешем. Если его нет у клиента,
// то делается запрос на получение ресурсы для последующей отправки клиенту
void informateIdToHash(const std::unordered_map<ResourceId, ResourceFile::Hash_t>* resourcesLink);
// Игровые определения
void informateDefVoxel(const std::unordered_map<DefVoxelId, DefVoxel_t*> &voxels);
void informateDefNode(const std::unordered_map<DefNodeId, DefNode_t*> &nodes);
void informateDefWorld(const std::unordered_map<DefWorldId_t, DefWorld_t*> &worlds);
void informateDefPortal(const std::unordered_map<DefPortalId_t, DefPortal_t*> &portals);
void informateDefEntity(const std::unordered_map<DefEntityId_t, DefEntity_t*> &entityes);
void informateDefItem(const std::unordered_map<DefItemId_t, DefItem_t*> &items);
private:
void checkPacketBorder(uint16_t size);
void protocolError();
coro<> readPacket(Net::AsyncSocket &sock);
coro<> rP_System(Net::AsyncSocket &sock);
void incrementBinary(const ResUsesObj::RefDefBin_t& bin);
void decrementBinary(ResUsesObj::RefDefBin_t&& bin);
// void incrementProfile(const std::vector<TextureId_t> &textures, const std::vector<ModelId_t> &model,
// const std::vector<SoundId_t> &sounds, const std::vector<FontId_t> &font
// );
// void decrementProfile(std::vector<TextureId_t> &&textures, std::vector<ModelId_t> &&model,
// std::vector<SoundId_t> &&sounds, std::vector<FontId_t> &&font
// );
};
}