diff --git a/Src/Client/Abstract.hpp b/Src/Client/Abstract.hpp index 60491fd..287ba73 100644 --- a/Src/Client/Abstract.hpp +++ b/Src/Client/Abstract.hpp @@ -60,11 +60,20 @@ public: // states }; +struct AssetEntry { + EnumAssets Type; + ResourceId Id; + std::string Domain, Key; + Resource Res; +}; + /* Интерфейс рендера текущего подключения к серверу */ class IRenderSession { public: - // Подгрузка двоичных ресурсов - virtual void onBinaryResourceAdd(std::vector) = 0; + // Изменённые ресурсы (кроме звуков) + virtual void onAssetsChanges(std::unordered_map> resources) = 0; + // Потерянные ресурсы + virtual void onAssetsLost(std::unordered_map> resources) = 0; virtual void onContentDefinesAdd(std::unordered_map>) = 0; virtual void onContentDefinesLost(std::unordered_map>) = 0; @@ -147,21 +156,6 @@ class IServerSession { // }; public: - struct AssetEntry { - EnumAssets Type; - ResourceId Id; - std::string Domain, Key; - Resource Res; - }; - - static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180; - struct { - // Оперируемые ресурсы - std::unordered_map Assets; - // Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд - std::unordered_map> NotInUseAssets; - } Binary; - struct { std::unordered_map DefVoxel; std::unordered_map DefNode; diff --git a/Src/Client/AssetsManager.cpp b/Src/Client/AssetsManager.cpp index ad59e33..4da54bb 100644 --- a/Src/Client/AssetsManager.cpp +++ b/Src/Client/AssetsManager.cpp @@ -301,7 +301,7 @@ void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) { } finded = true; - ReadyQueue.lock()->emplace_back(rk.Hash, PathFiles / hashKey.substr(0, 2) / hashKey.substr(2)); + ReadyQueue.lock()->emplace_back(rk, PathFiles / hashKey.substr(0, 2) / hashKey.substr(2)); } else if(errc != SQLITE_DONE) { sqlite3_reset(STMT_DISK_CONTAINS); MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB)); diff --git a/Src/Client/AssetsManager.hpp b/Src/Client/AssetsManager.hpp index 8e78539..2ad541b 100644 --- a/Src/Client/AssetsManager.hpp +++ b/Src/Client/AssetsManager.hpp @@ -95,6 +95,7 @@ public: Hash_t Hash; EnumAssets Type; std::string Domain, Key; + ResourceId Id; }; public: @@ -115,7 +116,7 @@ public: } // Получить считанные данные - std::vector>> pullReads() { + std::vector>> pullReads() { return std::move(*ReadyQueue.lock()); } @@ -181,7 +182,7 @@ private: // Очередь на запись ресурсов TOS::SpinlockObject> WriteQueue; // Очередь на выдачу результатов чтения - TOS::SpinlockObject>>> ReadyQueue; + TOS::SpinlockObject>>> ReadyQueue; struct Changes_t { std::vector Assets; diff --git a/Src/Client/ServerSession.cpp b/Src/Client/ServerSession.cpp index 9a6de8d..73e02ab 100644 --- a/Src/Client/ServerSession.cpp +++ b/Src/Client/ServerSession.cpp @@ -321,6 +321,147 @@ void ServerSession::onJoystick() { } void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) { + // Оповещение модуля рендера об изменениях ресурсов + std::unordered_map> changedResources; + std::unordered_map> lostResources; + + // Обработка полученных ресурсов + if(!AsyncContext.LoadedAssets.get_read().empty()) { + std::vector assets = std::move(*AsyncContext.LoadedAssets.lock()); + // Для сохранения ресурсов в кеше + std::vector resources; + resources.reserve(assets.size()); + + + for(AssetEntry& entry : assets) { + resources.push_back(entry.Res); + + // Проверяем используется ли сейчас ресурс + auto iter = Assets.ExistBinds[(int) entry.Type].find(entry.Id); + if(iter == Assets.ExistBinds[(int) entry.Type].end()) { + // Не используется + Assets.NotInUse[(int) entry.Type][entry.Domain + ':' + entry.Key] = {entry, TIME_BEFORE_UNLOAD_RESOURCE+time(nullptr)}; + } else { + // Используется + Assets.InUse[(int) entry.Type][entry.Id] = entry; + changedResources[entry.Type].insert({entry.Id, entry}); + } + } + + // Сохраняем в кеш + AM->pushResources(std::move(resources)); + } + + // Обработка полученных тактов + while(!AsyncContext.TickSequence.get_read().empty()) { + TickData tick; + + { + auto lock = AsyncContext.TickSequence.lock(); + tick = lock->front(); + lock->pop(); + } + + // Потерянные привязки ресурсов + for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { + for(ResourceId id : tick.AssetsLost[type]) { + Assets.ExistBinds[type].erase(id); + changedResources[(EnumAssets) type].erase(id); + } + // Assets.ExistBinds[type].erase(tick.AssetsLost[type].begin(), tick.AssetsLost[type].end()); + lostResources[(EnumAssets) type].insert_range(tick.AssetsLost[type]); + } + + // Запрос к дисковому кешу + std::vector needToLoad; + // Новые привязки ресурсов + for(const AssetBindEntry& bind : tick.AssetsBinds) { + Assets.ExistBinds[(int) bind.Type].insert(bind.Id); + + // Проверить in memory кеш по домену+ключу + { + std::string dk = bind.Domain + ':' + bind.Key; + auto &niubdk = Assets.NotInUse[(int) bind.Type]; + auto iter = niubdk.find(dk); + if(iter != niubdk.end()) { + // Есть ресурс + Assets.InUse[(int) bind.Type][bind.Id] = std::get<0>(iter->second); + changedResources[bind.Type].insert({bind.Id, std::get<0>(iter->second)}); + lostResources[bind.Type].erase(bind.Id); + continue; + } + } + + // Под рукой нет ресурса, отправим на проверку в AssetsManager + needToLoad.emplace_back(bind.Hash, bind.Type, bind.Domain, bind.Key, bind.Id); + } + + if(!needToLoad.empty()) + AM->pushReads(std::move(needToLoad)); + } + + // Получаем ресурсы, загруженные с дискового кеша + { + std::vector request; + std::vector>> resources = AM->pullReads(); + for(auto& [key, res] : resources) { + if(!res) { + // Нужно запросить ресурс с сервера + request.push_back(key.Hash); + } else { + auto& a = Assets.ExistBinds[(int) key.Type]; + AssetEntry entry = {key.Type, key.Id, key.Domain, key.Key, *res}; + + if(a.contains(key.Id)) { + // Ресурс ещё нужен + Assets.InUse[(int) key.Type][key.Id] = entry; + changedResources[key.Type].insert({key.Id, entry}); + } else { + // Ресурс уже не нужен + Assets.NotInUse[(int) key.Type][key.Domain + ':' + key.Key] = {entry, TIME_BEFORE_UNLOAD_RESOURCE+time(nullptr)}; + } + } + } + + if(!request.empty()) { + assert(request.size() < (1 << 16)); + Net::Packet p; + p << (uint8_t) ToServer::L1::System << (uint8_t) ToServer::L2System::ResourceRequest + << uint16_t(request.size()); + + for(Hash_t& hash : request) + p.write((const std::byte*) hash.data(), 32); + + Socket->pushPacket(std::move(p)); + } + } + + if(RS) { + // Уведомляем рендер опотерянных и изменённых ресурсах + if(!lostResources.empty()) { + std::unordered_map> lostResources2; + + for(auto& [type, list] : lostResources) + lostResources2[type].append_range(list); + + lostResources.clear(); + RS->onAssetsLost(std::move(lostResources2)); + } + + if(!changedResources.empty()) { + std::unordered_map> changedResources2; + + for(auto& [type, list] : changedResources) { + auto& a = changedResources2[type]; + for(auto& [key, val] : list) + a.push_back(val); + } + + changedResources.clear(); + RS->onAssetsChanges(std::move(changedResources2)); + } + } + GTime = gTime; Pos += glm::vec3(Speed) * dTime; @@ -539,6 +680,9 @@ coro<> ServerSession::rP_System(Net::AsyncSocket &sock) { co_return; case ToClient::L2System::UnlinkCamera: + co_return; + case ToClient::L2System::SyncTick: + AsyncContext.TickSequence.lock()->push(std::move(AsyncContext.ThisTickEntry)); co_return; default: protocolError(); @@ -572,13 +716,12 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { case ToClient::L2Resource::Lost: { uint32_t count = co_await sock.read(); - AsyncContext.ThisTickEntry.AssetsLost.reserve(AsyncContext.ThisTickEntry.AssetsLost.size()+count); for(size_t iter = 0; iter < count; iter++) { - // uint8_t type = co_await sock.read(); + uint8_t type = co_await sock.read(); uint32_t id = co_await sock.read(); - AsyncContext.ThisTickEntry.AssetsLost.push_back(id); + AsyncContext.ThisTickEntry.AssetsLost[(int) type].push_back(id); } } case ToClient::L2Resource::InitResSend: diff --git a/Src/Client/ServerSession.hpp b/Src/Client/ServerSession.hpp index be3a5bc..616228d 100644 --- a/Src/Client/ServerSession.hpp +++ b/Src/Client/ServerSession.hpp @@ -76,6 +76,16 @@ private: // Обработчик кеша ресурсов сервера AssetsManager::Ptr AM; + static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180; + struct { + // Существующие привязки ресурсов + std::unordered_set ExistBinds[(int) EnumAssets::MAX_ENUM]; + // Используемые в данных момент ресурсы (определяется по действующей привязке) + std::unordered_map InUse[(int) EnumAssets::MAX_ENUM]; + // Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд + std::unordered_map> NotInUse[(int) EnumAssets::MAX_ENUM]; + } Assets; + struct AssetLoading { EnumAssets Type; ResourceId Id; @@ -95,10 +105,10 @@ private: std::vector LostWorld; // std::vector> - // Потерянные из видимости ресурсы - std::vector AssetsLost; // Новые привязки ресурсов std::vector AssetsBinds; + // Потерянные из видимости ресурсы + std::vector AssetsLost[(int) EnumAssets::MAX_ENUM]; }; struct { @@ -111,8 +121,6 @@ private: TickData ThisTickEntry; // Обменный пункт - // Привязки ресурсов - TOS::SpinlockObject> AssetsBindings; // Полученные ресурсы с сервера TOS::SpinlockObject> LoadedAssets; // Пакеты обновлений игрового мира diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index 5ad692f..1419b9f 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -987,7 +987,11 @@ void VulkanRenderSession::init(Vulkan *instance) { } } -void VulkanRenderSession::onBinaryResourceAdd(std::vector) { +void VulkanRenderSession::onAssetsChanges(std::unordered_map> resources) { + +} + +void VulkanRenderSession::onAssetsLost(std::unordered_map> resources) { } diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index 8b577b8..da2dfa5 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -340,7 +340,8 @@ public: assert(serverSession); } - virtual void onBinaryResourceAdd(std::vector) override; + virtual void onAssetsChanges(std::unordered_map> resources) override; + virtual void onAssetsLost(std::unordered_map> resources) override; virtual void onContentDefinesAdd(std::unordered_map>) override; virtual void onContentDefinesLost(std::unordered_map>) override; virtual void onChunksChange(WorldId_t worldId, const std::unordered_set& changeOrAddList, const std::unordered_set& remove) override; diff --git a/Src/Common/Packets.hpp b/Src/Common/Packets.hpp index 543920a..9f7e220 100644 --- a/Src/Common/Packets.hpp +++ b/Src/Common/Packets.hpp @@ -76,7 +76,8 @@ enum struct L2System : uint8_t { InitEnd, Disconnect, Test_CAM_PYR_POS, - BlockChange + BlockChange, + ResourceRequest }; } @@ -140,7 +141,8 @@ enum struct L2System : uint8_t { Init, Disconnect, LinkCameraToEntity, - UnlinkCamera + UnlinkCamera, + SyncTick }; enum struct L2Resource : uint8_t { diff --git a/Src/Server/RemoteClient.cpp b/Src/Server/RemoteClient.cpp index 2e23f22..a610828 100644 --- a/Src/Server/RemoteClient.cpp +++ b/Src/Server/RemoteClient.cpp @@ -491,6 +491,12 @@ ResourceRequest RemoteClient::pushPreparedPackets() { toSend.push_back(std::move(AssetsInWork.AssetsPacket)); } + { + Net::Packet p; + p << (uint8_t) ToClient::L1::System << (uint8_t) ToClient::L2System::SyncTick; + toSend.push_back(std::move(p)); + } + Socket.pushPackets(&toSend); toSend.clear(); @@ -505,22 +511,27 @@ void RemoteClient::informateAssets(const std::vectorClientRequested.begin(), lock->ClientRequested.end(), hash); + if(iter != lock->ClientRequested.end()) { + lock->ClientRequested.erase(iter); + lock.unlock(); + auto it = std::lower_bound(AssetsInWork.OnClient.begin(), AssetsInWork.OnClient.end(), hash); if(it == AssetsInWork.OnClient.end() || *it != hash) AssetsInWork.OnClient.insert(it, hash); AssetsInWork.ToSend.emplace_back(type, domain, key, resId, resource, 0); + + lock = NetworkAndResource.lock(); } } - auto lock = NetworkAndResource.lock(); // Информирование клиента о привязках ресурсов к идентификатору { // Посмотрим что известно клиенту @@ -528,6 +539,7 @@ void RemoteClient::informateAssets(const std::vectorResUses.AssetsUse[(int) type].end() && std::get(iter->second) != hash ) { + lock.unlock(); // Требуется перепривязать идентификатор к новому хешу newForClient.push_back({(EnumAssets) type, resId, domain, key, hash, resource.size()}); std::get(iter->second) = hash; @@ -720,6 +732,23 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) { Actions.lock()->push(action); co_return; } + case ToServer::L2System::ResourceRequest: + { + uint16_t count = co_await sock.read(); + std::vector hashes; + hashes.reserve(count); + + for(int iter = 0; iter < count; iter++) { + Hash_t hash; + co_await sock.read((std::byte*) hash.data(), 32); + hashes.push_back(hash); + } + + auto lock = NetworkAndResource.lock(); + lock->NextRequest.Hashes.append_range(hashes); + lock->ClientRequested.append_range(hashes); + co_return; + } default: protocolError(); } @@ -763,7 +792,7 @@ void RemoteClient::NetworkAndResource_t::decrementAssets(ResUses_t::RefAssets_t& << uint32_t(lost.size()); for(auto& [type, id] : lost) - NextPacket /* << uint8_t(type)*/ << uint32_t(id); + NextPacket << uint8_t(type) << uint32_t(id); } } diff --git a/Src/Server/RemoteClient.hpp b/Src/Server/RemoteClient.hpp index f7f6fdb..31a0b08 100644 --- a/Src/Server/RemoteClient.hpp +++ b/Src/Server/RemoteClient.hpp @@ -268,6 +268,9 @@ class RemoteClient { // Запрос информации об ассетах и профилях контента ResourceRequest NextRequest; + // Запрошенные клиентом ресурсы + /// TODO: здесь может быть засор + std::vector ClientRequested; void incrementAssets(const ResUses_t::RefAssets_t& bin); void decrementAssets(ResUses_t::RefAssets_t&& bin); @@ -307,9 +310,8 @@ class RemoteClient { + хеш. Если у клиента не окажется этого ресурса, он может его запросить */ - // Ресурсы, отправленные на клиент в этой сессии и запрошенные клиентом - /// TODO: ClientRequested здесь может быть засор - std::vector OnClient, ClientRequested; + // Ресурсы, отправленные на клиент в этой сессии + std::vector OnClient; // Отправляемые на клиент ресурсы // Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт std::vector> ToSend;