#pragma once #include #include #include #include "Abstract.hpp" #include "Common/Packets.hpp" #include "Server/ContentEventController.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]; 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 : {&BinToHash, &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()); } 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 ResUsesObj { // Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору std::map> BinUse[5]; // Счётчики использование профилей контента std::map DefVoxel; // Один чанк, одно использование std::map DefNode; std::map DefWorld; std::map DefPortal; std::map DefEntity; std::map DefItem; // При передаче инвентарей? // Зависимость профилей контента от профилей ресурсов // Нужно чтобы пересчитать зависимости к профилям ресурсов struct RefDefBin_t { std::vector Resources[5]; }; std::map RefDefVoxel; std::map RefDefNode; std::map RefDefWorld; std::map RefDefPortal; std::map RefDefEntity; std::map RefDefItem; // Модификационные зависимости экземпляров профилей контента struct ChunkRef { // Отсортированные списки уникальных вокселей std::vector Voxel; std::vector Node; }; std::atomic_bool RefChunkLock = 0; std::map>> RefChunk; struct RefWorld_t { DefWorldId_t Profile; }; std::map RefWorld; struct RefPortal_t { DefPortalId_t Profile; }; std::map RefPortal; struct RefEntity_t { DefEntityId_t Profile; }; std::map RefEntity; } ResUses; // Смена идентификаторов сервера на клиентские struct { SCSKeyRemapper Entityes; SCSKeyRemapper FuncEntityes; } ResRemap; Net::Packet NextPacket; std::vector SimplePackets; ResourceRequest NextRequest; public: const std::string Username; Pos::Object CameraPos = {0, 0, 0}; ToServer::PacketQuat CameraQuat = {0}; TOS::SpinlockObject> Actions; 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); } /* Сервер собирает изменения миров, сжимает их и раздаёт на отправку игрокам */ // Функции подготавливают пакеты к отправке // Отслеживаемое игроком использование контента // maybe созданны для использования в многопотоке, если ресурс сейчас занят вернёт false, потом нужно повторить запрос // В зоне видимости добавился чанк или изменились его воксели bool maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels, const std::vector& uniq_sorted_defines); // В зоне видимости добавился чанк или изменились его ноды bool maybe_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 prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regionPos); // В зоне видимости добавилась новая сущность или она изменилась void prepareEntityUpdate(ServerEntityId_t entityId, const Entity *entity); // Наблюдаемая сущность пересекла границы региона, у неё изменился серверный идентификатор void prepareEntitySwap(ServerEntityId_t prevEntityId, ServerEntityId_t nextEntityId); // Клиент перестал наблюдать за сущностью void prepareEntityRemove(ServerEntityId_t entityId); // В зоне видимости добавился мир или он изменился void prepareWorldUpdate(WorldId_t worldId, World* world); // Клиент перестал наблюдать за миром void prepareWorldRemove(WorldId_t worldId); // В зоне видимости добавился порта или он изменился 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::vector>& 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 // ); }; }