#pragma once #include #include #include #include "Abstract.hpp" #include "Common/Packets.hpp" #include "Server/AssetsManager.hpp" #include "Server/ContentEventController.hpp" #include "assets.hpp" #include #include #include #include #include #include #include #include #include namespace LV::Server { template= sizeof(ClientKey), int> = 0> class CSChunkedMapper { std::unordered_map, std::array>> 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 &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 &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::array> &chunk = Chunks[chunkIndex]; std::bitset<64> &bits = std::get<0>(chunk); std::array &keys = std::get<1>(chunk); assert(!bits.test(subIndex) && "Идентификатор уже занят"); bits.set(subIndex); keys[subIndex] = sKey; } }; template= sizeof(ClientKey), int> = 0> class SCSKeyRemapper { std::bitset FreeClientKeys; std::map Map; CSChunkedMapper 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 Hashes; std::vector BinToHash[5 /*EnumBinResource*/]; std::vector Voxel; std::vector Node; std::vector World; std::vector Portal; std::vector Entity; std::vector 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 *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; /* Обработчик сокета клиента. Подписывает клиента на отслеживание необходимых ресурсов на основе передаваемых клиенту данных */ class RemoteClient { TOS::Logger LOG; DestroyLock UseLock; Net::AsyncSocket Socket; bool IsConnected = true, IsGoingShutdown = false; std::vector ClientBinaryCache, // Хеши ресурсов которые есть у клиента NeedToSend; // Хеши которые нужно получить и отправить /* При обнаружении нового контента составляется запрос (ResourceRequest) на полное описание ресурса. Это описание отправляется клиенту и используется чтобы выстроить зависимость какие базовые ресурсы использует контент. Если базовые ресурсы не известны, то они также запрашиваются. */ struct NetworkAndResource_t { struct ResUsesObj { // Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору std::map> AssetsUse[(int) EnumAssets::MAX_ENUM]; // Зависимость профилей контента от профилей ресурсов // Нужно чтобы пересчитать зависимости к профилям ресурсов struct RefAssets_t { std::vector Resources[(int) EnumAssets::MAX_ENUM]; }; std::map RefDefVoxel; std::map RefDefNode; std::map RefDefWorld; std::map RefDefPortal; std::map RefDefEntity; std::map RefDefItem; // Счётчики использование профилей контента std::map DefVoxel; // Один чанк, одно использование std::map DefNode; std::map DefWorld; std::map DefPortal; std::map DefEntity; std::map DefItem; // При передаче инвентарей? // Зависимость наблюдаемых чанков от профилей нод и вокселей struct ChunkRef { // Отсортированные списки уникальных вокселей std::vector Voxel; std::vector Node; }; std::map>> RefChunk; // Модификационные зависимости экземпляров профилей контента // У сущностей в мире могут дополнительно изменятся свойства, переписывая их профиль struct RefWorld_t { DefWorldId Profile; RefAssets_t Assets; }; std::map RefWorld; struct RefPortal_t { DefPortalId Profile; RefAssets_t Assets; }; // std::map RefPortal; struct RefEntity_t { DefEntityId Profile; RefAssets_t Assets; }; std::map RefEntity; } ResUses; // Смена идентификаторов сервера на клиентские SCSKeyRemapper ReMapEntities; Net::Packet NextPacket; std::vector SimplePackets; ResourceRequest NextRequest; }; struct { // Ресурсы, отправленные на клиент в этой сессии std::vector OnClient; std::vector> ToSend; } AssetsInWork; TOS::SpinlockObject NetworkAndResource; public: const std::string Username; Pos::Object CameraPos = {0, 0, 0}; ToServer::PacketQuat CameraQuat = {0}; TOS::SpinlockObject> 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 &&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 *simplePackets, std::vector *smartPackets = nullptr) { if(IsGoingShutdown) return; Socket.pushPackets(simplePackets, smartPackets); } // Возвращает список точек наблюдений клиентом с радиусом в регионах std::vector> getViewPoints(); /* Сервер собирает изменения миров, сжимает их и раздаёт на отправку игрокам */ // Функции подготавливают пакеты к отправке // Отслеживаемое игроком использование контента TOS::Spinlock MurkyLock; Net::Packet MurkyNextPacket; std::vector 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& uniq_sorted_defines); // В зоне видимости добавился чанк или изменились его ноды bool murky_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes, const std::vector& 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 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>& resources); // Привязывает локальный идентификатор с хешем. Если его нет у клиента, // то делается запрос на получение ресурсы для последующей отправки клиенту void informateIdToHash(const std::unordered_map* resourcesLink); // Игровые определения void informateDefVoxel(const std::unordered_map &voxels); void informateDefNode(const std::unordered_map &nodes); void informateDefWorld(const std::unordered_map &worlds); void informateDefPortal(const std::unordered_map &portals); void informateDefEntity(const std::unordered_map &entityes); void informateDefItem(const std::unordered_map &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 &textures, const std::vector &model, // const std::vector &sounds, const std::vector &font // ); // void decrementProfile(std::vector &&textures, std::vector &&model, // std::vector &&sounds, std::vector &&font // ); }; }