From c13ad06ba94fdb937241d0d80d2603fcdf8d90ed Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Tue, 6 Jan 2026 18:21:25 +0600 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BD=D0=B3=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D1=80=D0=B5=D1=81?= =?UTF-8?q?=D1=83=D1=80=D1=81=D0=B0=D0=BC=D0=B8=20=D0=B8=D0=B3=D1=80=D1=8B?= =?UTF-8?q?=20=D0=BD=D0=B0=20=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=BD=D0=B5=20?= =?UTF-8?q?=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Client/ServerSession.cpp | 2 +- Src/Common/Abstract.cpp | 4 + Src/Common/Abstract.hpp | 13 ++ Src/Common/AssetsPreloader.cpp | 389 ++++++++++++++------------------- Src/Common/AssetsPreloader.hpp | 356 ++++++++++-------------------- Src/Common/IdProvider.hpp | 207 ++++++++++++++++++ Src/Server/Abstract.hpp | 18 -- Src/Server/AssetsManager.hpp | 107 +++++++++ Src/Server/ContentManager.cpp | 16 +- Src/Server/ContentManager.hpp | 8 +- Src/Server/GameServer.cpp | 64 +++--- Src/Server/GameServer.hpp | 4 +- Src/Server/RemoteClient.cpp | 37 ++-- Src/Server/RemoteClient.hpp | 9 +- 14 files changed, 656 insertions(+), 578 deletions(-) create mode 100644 Src/Common/IdProvider.hpp create mode 100644 Src/Server/AssetsManager.hpp diff --git a/Src/Client/ServerSession.cpp b/Src/Client/ServerSession.cpp index 857fdc2..81dca0f 100644 --- a/Src/Client/ServerSession.cpp +++ b/Src/Client/ServerSession.cpp @@ -71,7 +71,7 @@ ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr& modelResolver) { std::vector headerIds; diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index 6475c96..8f17bea 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -553,6 +553,19 @@ inline std::pair parseDomainKey(const std::string& val } } +struct ResourceFile { + using Hash_t = std::array; + + Hash_t Hash; + std::u8string Data; + + static Hash_t calcHash(const char8_t* data, size_t size); + + void calcHash() { + Hash = calcHash(Data.data(), Data.size()); + } +}; + struct NodestateEntry { std::string Name; int Variability = 0; // Количество возможный значений состояния diff --git a/Src/Common/AssetsPreloader.cpp b/Src/Common/AssetsPreloader.cpp index 998410f..0f391e7 100644 --- a/Src/Common/AssetsPreloader.cpp +++ b/Src/Common/AssetsPreloader.cpp @@ -1,5 +1,9 @@ #include "AssetsPreloader.hpp" +#include "Common/Abstract.hpp" +#include "Common/TexturePipelineProgram.hpp" +#include "sha2.hpp" #include +#include #include #include #include @@ -42,11 +46,26 @@ static std::u8string readOptionalMeta(const fs::path& path) { } AssetsPreloader::AssetsPreloader() { - std::fill(NextId.begin(), NextId.end(), 1); - std::fill(LastSendId.begin(), LastSendId.end(), 1); + for(size_t type = 0; type < static_cast(EnumAssets::MAX_ENUM); type++) { + ResourceLinks[type].emplace_back( + ResourceFile::Hash_t{0}, + ResourceHeader(), + fs::file_time_type(), + fs::path{""}, + false + ); + } } -AssetsPreloader::Out_reloadResources AssetsPreloader::reloadResources(const AssetsRegister& instances, ReloadStatus* status) { +AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::checkAndPrepareResourcesUpdate( + const AssetsRegister& instances, + const std::function& idResolver, + const std::function& onNewResourceParsed, + ReloadStatus* status +) { + assert(idResolver); + assert(onNewResourceParsed); + bool expected = false; assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources"); struct ReloadGuard { @@ -56,7 +75,7 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::reloadResources(const Asse try { ReloadStatus secondStatus; - return _reloadResources(instances, status ? *status : secondStatus); + return _checkAndPrepareResourcesUpdate(instances, idResolver, onNewResourceParsed, status ? *status : secondStatus); } catch(const std::exception& exc) { LOG.error() << exc.what(); assert(!"reloadResources: здесь не должно быть ошибок"); @@ -67,9 +86,12 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::reloadResources(const Asse } } -AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const AssetsRegister& instances, ReloadStatus& status) { - Out_reloadResources result; - +AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::_checkAndPrepareResourcesUpdate( + const AssetsRegister& instances, + const std::function& idResolver, + const std::function& onNewResourceParsed, + ReloadStatus& status +) { // 1) Поиск всех ресурсов и построение конечной карты ресурсов (timestamps, path, name, size) // Карта найденных ресурсов std::array< @@ -87,12 +109,12 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass static_cast(AssetType::MAX_ENUM) > resourcesFirstStage; - for (const fs::path& instance : instances.Assets) { + for(const fs::path& instance : instances.Assets) { try { - if (fs::is_regular_file(instance)) { + if(fs::is_regular_file(instance)) { // Может архив /// TODO: пока не поддерживается - } else if (fs::is_directory(instance)) { + } else if(fs::is_directory(instance)) { // Директория fs::path assetsRoot = instance; fs::path assetsCandidate = instance / "assets"; @@ -122,20 +144,20 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass >& firstStage = resourcesFirstStage[static_cast(assetType)][domain]; // Исследуем все ресурсы одного типа - for (auto begin = fs::recursive_directory_iterator(assetPath), end = fs::recursive_directory_iterator(); begin != end; begin++) { - if (begin->is_directory()) + for(auto begin = fs::recursive_directory_iterator(assetPath), end = fs::recursive_directory_iterator(); begin != end; begin++) { + if(begin->is_directory()) continue; fs::path file = begin->path(); - if (assetType == AssetType::Texture && file.extension() == ".meta") + if(assetType == AssetType::Texture && file.extension() == ".meta") continue; std::string key = fs::relative(file, assetPath).generic_string(); - if (firstStage.contains(key)) + if(firstStage.contains(key)) continue; fs::file_time_type timestamp = fs::last_write_time(file); - if (assetType == AssetType::Texture) { + if(assetType == AssetType::Texture) { fs::path metaPath = file; metaPath += ".meta"; if (fs::exists(metaPath) && fs::is_regular_file(metaPath)) { @@ -148,7 +170,8 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass // Работаем с ресурсом firstStage[key] = ResourceFindInfo{ .Path = file, - .Timestamp = timestamp + .Timestamp = timestamp, + .Id = idResolver(assetType, domain, key) }; } } @@ -158,7 +181,6 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass } } catch (const std::exception& exc) { /// TODO: Логгировать в статусе - } } @@ -172,14 +194,14 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass = [&](const std::string_view model) -> uint32_t { auto [mDomain, mKey] = parseDomainKey(model, domain); - return getId(AssetType::Model, mDomain, mKey); + return idResolver(AssetType::Model, mDomain, mKey); }; std::function(std::string_view)> textureIdResolver = [&](std::string_view texture) -> std::optional { auto [mDomain, mKey] = parseDomainKey(texture, domain); - return getId(AssetType::Texture, mDomain, mKey); + return idResolver(AssetType::Texture, mDomain, mKey); }; std::function(const std::string_view)> textureResolver @@ -202,8 +224,8 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass HeadlessNodeState hns; out.Header = hns.parse(obj, modelResolver); - out.Resource = std::make_shared(hns.dump()); - out.Hash = sha2::sha256((const uint8_t*) out.Resource->data(), out.Resource->size()); + out.Resource = hns.dump(); + out.Hash = sha2::sha256((const uint8_t*) out.Resource.data(), out.Resource.size()); } else if (type == AssetType::Model) { const std::string ext = info.Path.extension().string(); if (ext == ".json") { @@ -213,19 +235,8 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass HeadlessModel hm; out.Header = hm.parse(obj, modelResolver, textureResolver); - std::u8string compiled = hm.dump(); - if(hm.Cuboids.empty()) { - static std::atomic debugEmptyModelLogCount = 0; - uint32_t idx = debugEmptyModelLogCount.fetch_add(1); - if(idx < 128) { - LOG.warn() << "Model compiled with empty cuboids: " - << domain << ':' << key - << " file=" << info.Path.string() - << " size=" << compiled.size(); - } - } - out.Resource = std::make_shared(std::move(compiled)); - out.Hash = sha2::sha256((const uint8_t*) out.Resource->data(), out.Resource->size()); + out.Resource = hm.dump(); + out.Hash = sha2::sha256((const uint8_t*) out.Resource.data(), out.Resource.size()); // } else if (ext == ".gltf" || ext == ".glb") { // /// TODO: добавить поддержку gltf // ResourceFile file = readFileBytes(info.Path); @@ -236,239 +247,157 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass } } else if (type == AssetType::Texture) { ResourceFile file = readFileBytes(info.Path); - out.Resource = std::make_shared(std::move(file.Data)); + out.Resource = std::move(file.Data); out.Hash = file.Hash; out.Header = readOptionalMeta(info.Path); } else { ResourceFile file = readFileBytes(info.Path); - out.Resource = std::make_shared(std::move(file.Data)); + out.Resource = std::move(file.Data); out.Hash = file.Hash; } - out.Id = getId(type, domain, key); + out.Id = idResolver(type, domain, key); return out; - }; + }; + + // 2) Определяем какие ресурсы изменились (новый timestamp) или новые ресурсы + Out_checkAndPrepareResourcesUpdate result; + + // Собираем идентификаторы, чтобы потом определить какие ресурсы пропали + std::array< + std::unordered_set, + static_cast(EnumAssets::MAX_ENUM) + > uniqueExists; - // 2) Обрабатываться будут только изменённые (новый timestamp) или новые ресурсы - // Определяем каких ресурсов не стало for(size_t type = 0; type < static_cast(AssetType::MAX_ENUM); ++type) { - auto& tableResourcesFirstStage = resourcesFirstStage[type]; - for(const auto& [id, resource] : MediaResources[type]) { - if(tableResourcesFirstStage.empty()) { - result.Lost[type][resource.Domain].push_back(resource.Key); - continue; - } + auto& uniqueExistsTypes = uniqueExists[type]; + const auto& resourceLinksTyped = ResourceLinks[type]; + result.MaxNewSize[type] = resourceLinksTyped.size(); - auto iterDomain = tableResourcesFirstStage.find(resource.Domain); - if(iterDomain == tableResourcesFirstStage.end()) { - result.Lost[type][resource.Domain].push_back(resource.Key); - continue; - } + { + size_t allIds = 0; + for(const auto& [domain, keys] : resourcesFirstStage[type]) + allIds += keys.size(); - if(!iterDomain->second.contains(resource.Key)) { - result.Lost[type][resource.Domain].push_back(resource.Key); - } + uniqueExistsTypes.reserve(allIds); } - } - // Определение новых или изменённых ресурсов - for(size_t type = 0; type < static_cast(AssetType::MAX_ENUM); ++type) { - for(const auto& [domain, table] : resourcesFirstStage[type]) { - auto iterTableDomain = DKToId[type].find(domain); - if(iterTableDomain == DKToId[type].end()) { - // Домен неизвестен движку, все ресурсы в нём новые - for(const auto& [key, info] : table) { - PendingResource resource = buildResource(static_cast(type), domain, key, info); - result.NewOrChange[type][domain].push_back(std::move(resource)); - } - } else { - for(const auto& [key, info] : table) { - bool needsUpdate = true; - if(auto iterKey = iterTableDomain->second.find(key); iterKey != iterTableDomain->second.end()) { - // Идентификатор найден - auto iterRes = MediaResources[type].find(iterKey->second); - // Если нашли ресурс по идентификатору и время изменения не поменялось, то он не новый и не изменился - if(iterRes != MediaResources[type].end() && iterRes->second.Timestamp == info.Timestamp) - needsUpdate = false; + for(const auto& [domain, keys] : resourcesFirstStage[type]) { + for(const auto& [key, res] : keys) { + uniqueExistsTypes.insert(res.Id); + + if(res.Id >= resourceLinksTyped.size() || !std::get(resourceLinksTyped[res.Id])) + { // Если идентификатора нет в таблице или ресурс не привязан + PendingResource resource = buildResource(static_cast(type), domain, key, res); + onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path); + result.HashToPathNew[resource.Hash].push_back(res.Path); + + if(res.Id >= result.MaxNewSize[type]) + result.MaxNewSize[type] = res.Id+1; + + result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path); + } else if( + std::get(resourceLinksTyped[res.Id]) != res.Path + || std::get(resourceLinksTyped[res.Id]) != res.Timestamp + ) { // Если ресурс теперь берётся с другого места или изменилось время изменения файла + const auto& lastResource = resourceLinksTyped[res.Id]; + PendingResource resource = buildResource(static_cast(type), domain, key, res); + + if(auto lastHash = std::get(lastResource); lastHash != resource.Hash) { + // Хэш изменился + // Сообщаем о новом ресурсе + onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path); + // Старый хэш более не доступен по этому расположению. + result.HashToPathLost[lastHash].push_back(std::get(resourceLinksTyped[res.Id])); + // Новый хеш стал доступен по этому расположению. + result.HashToPathNew[resource.Hash].push_back(res.Path); + } else if(std::get(resourceLinksTyped[res.Id]) != res.Path) { + // Изменился конечный путь. + // Хэш более не доступен по этому расположению. + result.HashToPathLost[resource.Hash].push_back(std::get(resourceLinksTyped[res.Id])); + // Хеш теперь доступен по этому расположению. + result.HashToPathNew[resource.Hash].push_back(res.Path); + } else { + // Ресурс без заголовка никак не изменился. } - if(!needsUpdate) - continue; - - PendingResource resource = buildResource(static_cast(type), domain, key, info); - result.NewOrChange[(int) type][domain].push_back(std::move(resource)); + // Чтобы там не поменялось, мог поменятся заголовок. Уведомляем о новой привязке. + result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path); + } else { + // Ресурс не изменился } } } } - return result; -} - -AssetsPreloader::Out_applyResourceChange AssetsPreloader::applyResourceChange(const Out_reloadResources& orr) { - Out_applyResourceChange result; - - // Удаляем ресурсы - /* - Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор - Уже скомпилированные зависимости к ресурсам не будут - перекомпилироваться для смены идентификатора. - Если нужный ресурс появится, то привязка останется. - Новые клиенты не получат ресурс которого нет, - но он может использоваться - */ - for(size_t type = 0; type < static_cast(AssetType::MAX_ENUM); type++) { - for(const auto& [domain, keys] : orr.Lost[type]) { - auto iterDomain = DKToId[type].find(domain); - - // Если уже было решено, что ресурсы были, и стали потерянными, то так и должно быть - assert(iterDomain != DKToId[type].end()); - - for(const auto& key : keys) { - auto iterKey = iterDomain->second.find(key); - - // Ресурс был и должен быть - assert(iterKey != iterDomain->second.end()); - - uint32_t id = iterKey->second; - auto& resType = MediaResources[type]; - auto iterRes = resType.find(id); - if(iterRes == resType.end()) - continue; - - // Ресурс был потерян - result.Lost[type].push_back(id); - // Hash более нам неизвестен - HashToId.erase(iterRes->second.Hash); - // Затираем ресурс - resType.erase(iterRes); - } - } - } - - // Добавляем - for(int type = 0; type < (int) AssetType::MAX_ENUM; type++) { - auto& typeTable = DKToId[type]; - for(const auto& [domain, resources] : orr.NewOrChange[type]) { - auto& domainTable = typeTable[domain]; - for(const PendingResource& pending : resources) { - MediaResource resource { - .Domain = domain, - .Key = std::move(pending.Key), - .Timestamp = pending.Timestamp, - .Resource = std::move(pending.Resource), - .Hash = pending.Hash, - .Header = std::move(pending.Header) - }; - - auto& table = MediaResources[type]; - // Нужно затереть старую ссылку хеша на данный ресурс - if(auto iter = table.find(pending.Id); iter != table.end()) - HashToId.erase(iter->second.Hash); - - // Добавили ресурс - table[pending.Id] = resource; - // Связали с хешем - HashToId[resource.Hash] = {static_cast(type), pending.Id}; - // Осведомили о новом/изменённом ресурсе - result.NewOrChange[type].emplace_back(pending.Id, resource.Hash, std::move(resource.Header)); - } - } - - // Не должно быть ресурсов, которые были помечены как потерянные - #ifndef NDEBUG - std::unordered_set changed; - for(const auto& [id, _, _2] : result.NewOrChange[type]) - changed.insert(id); - - auto& lost = result.Lost[type]; - for(auto iter : lost) - assert(!changed.contains(iter)); - #endif - } - - return result; -} - -AssetsPreloader::Out_bakeId AssetsPreloader::bakeIdTables() { - #ifndef NDEBUG - - assert(!DKToIdInBakingMode); - DKToIdInBakingMode = true; - struct _tempStruct { - AssetsPreloader* handler; - ~_tempStruct() { handler->DKToIdInBakingMode = false; } - } _lock{this}; - - #endif - - Out_bakeId result; - + // 3) Определяем какие ресурсы пропали for(size_t type = 0; type < static_cast(AssetType::MAX_ENUM); ++type) { - // домен+ключ -> id - { - auto lock = NewDKToId[type].lock(); - auto& dkToId = DKToId[type]; - for(auto& [domain, keys] : *lock) { - // Если домен не существует, просто воткнёт новые ключи - auto [iterDomain, inserted] = dkToId.try_emplace(domain, std::move(keys)); - if(!inserted) { - // Домен уже существует, сливаем новые ключи - iterDomain->second.merge(keys); - } - } + const auto& resourceLinksTyped = ResourceLinks[type]; - lock->clear(); - } + size_t counter = 0; + for(const auto& [hash, header, timestamp, path, isExist] : resourceLinksTyped) { + size_t id = counter++; + if(!isExist) + continue; - // id -> домен+ключ - { - auto lock = NewIdToDK[type].lock(); + if(uniqueExists[type].contains(id)) + continue; - auto& idToDK = IdToDK[type]; - result.IdToDK[type] = std::move(*lock); - lock->clear(); - idToDK.append_range(result.IdToDK[type]); - - // result.LastSendId[type] = LastSendId[type]; - LastSendId[type] = NextId[type]; + // Ресурс потерян + // Хэш более не доступен по этому расположению. + result.HashToPathLost[hash].push_back(path); + result.LostLinks[type].push_back(id); } } return result; } -AssetsPreloader::Out_fullSync AssetsPreloader::collectFullSync() const { - Out_fullSync out; +AssetsPreloader::Out_applyResourcesUpdate AssetsPreloader::applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) { + Out_applyResourcesUpdate result; - for(size_t type = 0; type < static_cast(AssetType::MAX_ENUM); ++type) { - out.IdToDK[type] = IdToDK[type]; - } + for(size_t type = 0; type < static_cast(EnumAssets::MAX_ENUM); ++type) { + // Затираем потерянные + for(ResourceId id : orr.LostLinks[type]) { + assert(id < ResourceLinks[type].size()); + auto& [hash, header, timestamp, path, isExist] = ResourceLinks[type][id]; + hash = {0}; + header = {}; + timestamp = fs::file_time_type(); + path.clear(); + isExist = false; - for(size_t type = 0; type < static_cast(AssetType::MAX_ENUM); ++type) { - for(const auto& [id, resource] : MediaResources[type]) { - out.HashHeaders[type].push_back(BindHashHeaderInfo{ - .Id = id, - .Hash = resource.Hash, - .Header = resource.Header - }); - out.Resources.emplace_back( - static_cast(type), - id, - &resource - ); + result.NewOrUpdates[type].emplace_back(id, hash, header); + } + + // Увеличиваем размер, если необходимо + if(orr.MaxNewSize[type] > ResourceLinks[type].size()) { + std::tuple< + ResourceFile::Hash_t, + ResourceHeader, + fs::file_time_type, + fs::path, + bool + > def{ + ResourceFile::Hash_t{0}, + ResourceHeader(), + fs::file_time_type(), + fs::path{""}, + false + }; + + ResourceLinks[type].resize(orr.MaxNewSize[type], def); + } + + // Обновляем / добавляем + for(auto& [id, hash, header, timestamp, path] : orr.ResourceUpdates[type]) { + ResourceLinks[type][id] = {hash, std::move(header), timestamp, std::move(path), true}; + result.NewOrUpdates[type].emplace_back(id, hash, header); } } - return out; -} - -std::tuple, std::vector> -AssetsPreloader::getNodeDependency(const std::string& domain, const std::string& key) { - (void)domain; - (void)key; - return {0, {}, {}}; + return result; } } diff --git a/Src/Common/AssetsPreloader.hpp b/Src/Common/AssetsPreloader.hpp index 319514b..e11b117 100644 --- a/Src/Common/AssetsPreloader.hpp +++ b/Src/Common/AssetsPreloader.hpp @@ -1,30 +1,22 @@ #pragma once -#include #include #include #include #include #include #include -#include #include #include #include #include -#include #include -#include "Common/TexturePipelineProgram.hpp" +#include "Abstract.hpp" #include "Common/Abstract.hpp" -#include "Common/Async.hpp" -#include "TOSAsync.hpp" -#include "TOSLib.hpp" -#include "sha2.hpp" /* Класс отвечает за отслеживание изменений и подгрузки медиаресурсов в указанных директориях. Медиаресурсы, собранные из папки assets или зарегистрированные модами. - Хранит все данные в оперативной памяти. */ static constexpr const char* EnumAssetsToDirectory(LV::EnumAssets value) { @@ -49,34 +41,10 @@ namespace LV { namespace fs = std::filesystem; using AssetType = EnumAssets; -struct ResourceFile { - using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type; - - Hash_t Hash; - std::u8string Data; - - void calcHash() { - Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size()); - } -}; - class AssetsPreloader { public: using Ptr = std::shared_ptr; - using IdTable = - std::unordered_map< - std::string, // Domain - std::unordered_map< - std::string, // Key - uint32_t, // ResourceId - detail::TSVHash, - detail::TSVEq - >, - detail::TSVHash, - detail::TSVEq - >; - // /* Ресурс имеет бинарную часть, из который вырезаны все зависимости. Вторая часть это заголовок, которые всегда динамично передаётся с сервера. @@ -100,63 +68,13 @@ public: std::string Key; fs::file_time_type Timestamp; // Обезличенный ресурс - std::shared_ptr Resource; + std::u8string Resource; // Его хеш ResourceFile::Hash_t Hash; // Заголовок std::u8string Header; }; - struct BindDomainKeyInfo { - std::string Domain; - std::string Key; - }; - - struct BindHashHeaderInfo { - ResourceId Id; - Hash_t Hash; - std::u8string Header; - }; - - struct Out_reloadResources { - std::unordered_map> NewOrChange[(int) AssetType::MAX_ENUM]; - std::unordered_map> Lost[(int) AssetType::MAX_ENUM]; - }; - - struct Out_applyResourceChange { - std::array< - std::vector, - static_cast(AssetType::MAX_ENUM) - > NewOrChange; - - std::array< - std::vector, - static_cast(AssetType::MAX_ENUM) - > Lost; - }; - - struct Out_bakeId { - // Новые привязки - std::array< - std::vector, - static_cast(AssetType::MAX_ENUM) - > IdToDK; - }; - - struct Out_fullSync { - std::array< - std::vector, - static_cast(AssetType::MAX_ENUM) - > IdToDK; - - std::array< - std::vector, - static_cast(AssetType::MAX_ENUM) - > HashHeaders; - - std::vector> Resources; - }; - struct ReloadStatus { /// TODO: callback'и для обновления статусов /// TODO: многоуровневый статус std::vector. Этапы/Шаги/Объекты @@ -202,66 +120,117 @@ public: ! Бронирует идентификаторы используя getId(); instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему. + idResolver -> функция получения идентификатора по Тип+Домен+Ключ + onNewResourceParsed -> Callback на обработку распаршенных ресурсов без заголовков + (на стороне сервера хранится в другой сущности, на стороне клиента игнорируется). status -> обратный отклик о процессе обновления ресурсов. ReloadStatus <- новые и потерянные ресурсы. */ - Out_reloadResources reloadResources(const AssetsRegister& instances, ReloadStatus* status = nullptr); + struct Out_checkAndPrepareResourcesUpdate { + // Новые связки Id -> Hash + Header + Timestamp + Path (ресурс новый или изменён) + std::array< + std::vector< + std::tuple< + ResourceId, // Ресурс + ResourceFile::Hash_t, // Хэш ресурса на диске + ResourceHeader, // Хедер ресурса (со всеми зависимостями) + fs::file_time_type, // Время изменения ресурса на диске + fs::path // Путь до ресурса + > + >, + static_cast(AssetType::MAX_ENUM) + > ResourceUpdates; + + // Используется чтобы эффективно увеличить размер таблиц + std::array< + ResourceId, + static_cast(AssetType::MAX_ENUM) + > MaxNewSize; + + // Потерянные связки Id (ресурс физически потерян) + std::array< + std::vector, + static_cast(AssetType::MAX_ENUM) + > LostLinks; + + /* + Новые пути предоставляющие хеш + (по каким путям можно получить ресурс определённого хеша). + */ + std::unordered_map< + ResourceFile::Hash_t, + std::vector + > HashToPathNew; + + /* + Потерянные пути, предоставлявшые ресурсы с данным хешем + (пути по которым уже нельзя получить заданных хеш). + */ + std::unordered_map< + ResourceFile::Hash_t, + std::vector + > HashToPathLost; + }; + + Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate( + const AssetsRegister& instances, + const std::function& idResolver, + const std::function& onNewResourceParsed, + ReloadStatus* status = nullptr + ); /* Применяет расчитанные изменения. - Out_applyResourceChange <- Нужно отправить клиентам новые привязки ресурсов + Out_applyResourceUpdate <- Нужно отправить клиентам новые привязки ресурсов id -> hash+header */ - Out_applyResourceChange applyResourceChange(const Out_reloadResources& orr); + struct BindHashHeaderInfo { + ResourceId Id; + ResourceFile::Hash_t Hash; + ResourceHeader Header; + }; - /* - Выдаёт идентификатор ресурса. - Многопоточно. - Иногда нужно вызывать bakeIdTables чтобы оптимизировать таблицы - идентификаторов. При этом никто не должен использовать getId - */ - ResourceId getId(AssetType type, std::string_view domain, std::string_view key); + struct Out_applyResourcesUpdate { + std::array< + std::vector, + static_cast(EnumAssets::MAX_ENUM) + > NewOrUpdates; + }; - /* - Оптимизирует таблицы идентификаторов. - Нельзя использовать пока есть вероятность что кто-то использует getId(). - Такжке нельзя при выполнении reloadResources(). + Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr); - Out_bakeId <- Нужно отправить подключенным клиентам новые привязки id -> домен+ключ - */ - Out_bakeId bakeIdTables(); + std::array< + std::vector, + static_cast(EnumAssets::MAX_ENUM) + > collectHashBindings() const + { + std::array< + std::vector, + static_cast(EnumAssets::MAX_ENUM) + > result; - // Выдаёт полный список привязок и ресурсов для новых клиентов. - Out_fullSync collectFullSync() const; + for(size_t type = 0; type < static_cast(EnumAssets::MAX_ENUM); ++type) { + result[type].reserve(ResourceLinks[type].size()); - /* - Выдаёт пакет со всеми текущими привязками id -> домен+ключ. - Используется при подключении новых клиентов. - */ - void makeGlobalLinkagePacket() { - /// TODO: Собрать пакет с IdToDK и сжать его домены и ключи и id -> hash+header + ResourceId counter = 0; + for(const auto& [hash, header, _1, _2, _3] : ResourceLinks[type]) { + ResourceId id = counter++; + result[type].emplace_back(id, hash, header); + } + } - // Тот же пакет для обновления идентификаторов - std::unreachable(); + return result; } - // Выдаёт ресурс по идентификатору - const MediaResource* getResource(AssetType type, uint32_t id) const; - - // Выдаёт ресурс по хешу - std::optional> getResource(const ResourceFile::Hash_t& hash); - - // Выдаёт зависимости к ресурсам профиля ноды - std::tuple, std::vector> - getNodeDependency(const std::string& domain, const std::string& key); - private: struct ResourceFindInfo { // Путь к архиву (если есть), и путь до ресурса fs::path ArchivePath, Path; // Время изменения файла fs::file_time_type Timestamp; + // Идентификатор ресурса + ResourceId Id; }; struct HashHasher { @@ -275,136 +244,31 @@ private: } }; + #ifndef NDEBUG // Текущее состояние reloadResources std::atomic _Reloading = false; - - // Если идентификатор не найден в асинхронной таблице, переходим к работе с синхронной - ResourceId _getIdNew(AssetType type, std::string_view domain, std::string_view key); - - Out_reloadResources _reloadResources(const AssetsRegister& instances, ReloadStatus& status); - - #ifndef NDEBUG - // Для контроля за режимом слияния ключей - bool DKToIdInBakingMode = false; #endif - /* - Многопоточная таблица идентификаторов. Новые идентификаторы выделяются в NewDKToId, - и далее вливаются в основную таблицу при вызове bakeIdTables() - */ - std::array(AssetType::MAX_ENUM)> DKToId; - /* - Многопоточная таблица обратного резолва. - Идентификатор -> домен+ключ - */ - std::array, static_cast(AssetType::MAX_ENUM)> IdToDK; + Out_checkAndPrepareResourcesUpdate _checkAndPrepareResourcesUpdate( + const AssetsRegister& instances, + const std::function& idResolver, + const std::function& onNewResourceParsed, + ReloadStatus& status + ); - /* - Таблица в которой выделяются новые идентификаторы, которых не нашлось в DKToId. - Данный объект одновременно может работать только с одним потоком. - */ - std::array, static_cast(AssetType::MAX_ENUM)> NewDKToId; - /* - Конец поля идентификаторов, известный клиентам. - Если NextId продвинулся дальше, нужно уведомить клиентов о новых привязках. - */ - std::array(AssetType::MAX_ENUM)> LastSendId; - /* - Списки в которых пишутся новые привязки. Начала спиской исходят из LastSendId. - Id + LastSendId -> домен+ключ - */ - std::array>, static_cast(AssetType::MAX_ENUM)> NewIdToDK; - - // Загруженные ресурсы - std::array, static_cast(AssetType::MAX_ENUM)> MediaResources; - // Hash -> ресурс - std::unordered_map, HashHasher> HashToId; - // Для последовательного выделения идентификаторов - std::array(AssetType::MAX_ENUM)> NextId; + // Привязка Id -> Hash + Header + Timestamp + Path + std::array< + std::vector< + std::tuple< + ResourceFile::Hash_t, // Хэш ресурса на диске + ResourceHeader, // Хедер ресурса (со всеми зависимостями) + fs::file_time_type, // Время изменения ресурса на диске + fs::path, // Путь до ресурса + bool // IsExist + > + >, + static_cast(AssetType::MAX_ENUM) + > ResourceLinks; }; -inline ResourceId AssetsPreloader::getId(AssetType type, std::string_view domain, std::string_view key) { - #ifndef NDEBUG - assert(!DKToIdInBakingMode); - #endif - - const auto& typeTable = DKToId[static_cast(type)]; - auto domainTable = typeTable.find(domain); - - #ifndef NDEBUG - assert(!DKToIdInBakingMode); - #endif - - if(domainTable == typeTable.end()) - return _getIdNew(type, domain, key); - - auto keyTable = domainTable->second.find(key); - - if (keyTable == domainTable->second.end()) - return _getIdNew(type, domain, key); - - return keyTable->second; - - return 0; -} - -inline ResourceId AssetsPreloader::_getIdNew(AssetType type, std::string_view domain, std::string_view key) { - auto lock = NewDKToId[static_cast(type)].lock(); - - auto iterDomainNewTable = lock->find(domain); - if(iterDomainNewTable == lock->end()) { - iterDomainNewTable = lock->emplace_hint( - iterDomainNewTable, - (std::string) domain, - std::unordered_map{} - ); - } - - auto& domainNewTable = iterDomainNewTable->second; - - if(auto iter = domainNewTable.find(key); iter != domainNewTable.end()) - return iter->second; - - uint32_t id = domainNewTable[(std::string) key] = NextId[static_cast(type)]++; - - auto lock2 = NewIdToDK[static_cast(type)].lock(); - lock.unlock(); - - lock2->emplace_back((std::string) domain, (std::string) key); - - return id; -} - -inline const AssetsPreloader::MediaResource* AssetsPreloader::getResource(AssetType type, uint32_t id) const { - auto& iterType = MediaResources[static_cast(type)]; - - auto iterRes = iterType.find(id); - if(iterRes == iterType.end()) - return nullptr; - - return &iterRes->second; -} - -inline std::optional> - AssetsPreloader::getResource(const ResourceFile::Hash_t& hash) -{ - auto iter = HashToId.find(hash); - if(iter == HashToId.end()) - return std::nullopt; - - auto [type, id] = iter->second; - const MediaResource* res = getResource(type, id); - if(!res) { - HashToId.erase(iter); - return std::nullopt; - } - - if(res->Hash != hash) { - HashToId.erase(iter); - return std::nullopt; - } - - return std::tuple{type, id, res}; -} - } diff --git a/Src/Common/IdProvider.hpp b/Src/Common/IdProvider.hpp new file mode 100644 index 0000000..287a63f --- /dev/null +++ b/Src/Common/IdProvider.hpp @@ -0,0 +1,207 @@ +#pragma once + +#include "Common/Abstract.hpp" + + +namespace LV { + +template +class IdProvider { +public: + static constexpr size_t MAX_ENUM = static_cast(Enum::MAX_ENUM); + using IdTable = + std::unordered_map< + std::string, // Domain + std::unordered_map< + std::string, // Key + uint32_t, // ResourceId + detail::TSVHash, + detail::TSVEq + >, + detail::TSVHash, + detail::TSVEq + >; + + struct BindDomainKeyInfo { + std::string Domain, Key; + }; + +public: + IdProvider() { + std::fill(NextId.begin(), NextId.end(), 1); + for(size_t type = 0; type < static_cast(Enum::MAX_ENUM); ++type) { + DKToId[type]["core"]["none"] = 0; + IdToDK[type].emplace_back("core", "none"); + } + } + /* + Находит или выдаёт идентификатор на запрошенный ресурс. + Функция не требует внешней синхронизации. + Требуется периодически вызывать bake(). + */ + inline ResourceId getId(EnumAssets type, std::string_view domain, std::string_view key) { + #ifndef NDEBUG + assert(!DKToIdInBakingMode); + #endif + + const auto& typeTable = DKToId[static_cast(type)]; + auto domainTable = typeTable.find(domain); + + #ifndef NDEBUG + assert(!DKToIdInBakingMode); + #endif + + if(domainTable == typeTable.end()) + return _getIdNew(type, domain, key); + + auto keyTable = domainTable->second.find(key); + + if (keyTable == domainTable->second.end()) + return _getIdNew(type, domain, key); + + return keyTable->second; + + return 0; + } + + /* + Переносит все новые идентификаторы в основную таблицу. + Нельзя использовать пока есть вероятность что кто-то использует getId(). + + Out_bakeId <- Возвращает все новые привязки. + */ + std::array< + std::vector, + MAX_ENUM + > bake() { + #ifndef NDEBUG + + assert(!DKToIdInBakingMode); + DKToIdInBakingMode = true; + struct _tempStruct { + IdProvider* handler; + ~_tempStruct() { handler->DKToIdInBakingMode = false; } + } _lock{this}; + + #endif + + std::array< + std::vector, + MAX_ENUM + > result; + + for(size_t type = 0; type < MAX_ENUM; ++type) { + // Домен+Ключ -> Id + { + auto lock = NewDKToId[type].lock(); + auto& dkToId = DKToId[type]; + for(auto& [domain, keys] : *lock) { + // Если домен не существует, просто воткнёт новые ключи + auto [iterDomain, inserted] = dkToId.try_emplace(domain, std::move(keys)); + if(!inserted) { + // Домен уже существует, сливаем новые ключи + iterDomain->second.merge(keys); + } + } + + lock->clear(); + } + + // Id -> Домен+Ключ + { + auto lock = NewIdToDK[type].lock(); + + auto& idToDK = IdToDK[type]; + result[type] = std::move(*lock); + lock->clear(); + idToDK.append_range(result[type]); + } + } + + return result; + } + + // Для отправки новым подключенным клиентам + const std::array< + std::vector, + static_cast(EnumAssets::MAX_ENUM) + >& idToDK() const { + return IdToDK; + } + +protected: + #ifndef NDEBUG + // Для контроля за режимом слияния ключей + bool DKToIdInBakingMode = false; + #endif + + /* + Работает с таблицами для новых идентификаторов, в синхронном режиме. + Используется когда в основных таблицах не нашлось привязки, + она будет найдена или создана здесь синхронно. + */ + inline ResourceId _getIdNew(EnumAssets type, std::string_view domain, std::string_view key) { + // Блокировка по нужному типу ресурса + auto lock = NewDKToId[static_cast(type)].lock(); + + auto iterDomainNewTable = lock->find(domain); + // Если домена не нашлось, сразу вставляем его на подходящее место + if(iterDomainNewTable == lock->end()) { + iterDomainNewTable = lock->emplace_hint( + iterDomainNewTable, + (std::string) domain, + std::unordered_map{} + ); + } + + auto& domainNewTable = iterDomainNewTable->second; + + + if(auto iter = domainNewTable.find(key); iter != domainNewTable.end()) + return iter->second; + else { + uint32_t id = NextId[static_cast(type)]++; + domainNewTable.emplace_hint(iter, (std::string) key, id); + + // Добавился новый идентификатор, теперь добавим обратную связку + auto lock2 = NewIdToDK[static_cast(type)].lock(); + lock.unlock(); + + lock2->emplace_back((std::string) domain, (std::string) key); + return id; + } + } + +// Условно многопоточные объекты + /* + Таблица идентификаторов. Новые идентификаторы выделяются в NewDKToId, + и далее вливаются в основную таблицу при вызове bakeIdTables(). + + Домен+Ключ -> Id + */ + std::array DKToId; + + /* + Таблица обратного резолва. + Id -> Домен+Ключ. + */ + std::array, MAX_ENUM> IdToDK; + +// Требующие синхронизации + /* + Таблица в которой выделяются новые идентификаторы, перед вливанием в DKToId. + Домен+Ключ -> Id. + */ + std::array, MAX_ENUM> NewDKToId; + + /* + Списки в которых пишутся новые привязки. + Id + LastMaxId -> Домен+Ключ. + */ + std::array>, MAX_ENUM> NewIdToDK; + + // Для последовательного выделения идентификаторов + std::array NextId; +}; + +} \ No newline at end of file diff --git a/Src/Server/Abstract.hpp b/Src/Server/Abstract.hpp index b556e62..3678761 100644 --- a/Src/Server/Abstract.hpp +++ b/Src/Server/Abstract.hpp @@ -34,24 +34,6 @@ using PlayerId_t = ResourceId; using DefGeneratorId_t = ResourceId; -/* - Сервер загружает информацию о локальных текстурах - Пересмотр списка текстур? - Динамичные текстуры? - -*/ - -struct ResourceFile { - using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type; - - Hash_t Hash; - std::vector Data; - - void calcHash() { - Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size()); - } -}; - struct ServerTime { uint32_t Seconds : 24, Sub : 8; }; diff --git a/Src/Server/AssetsManager.hpp b/Src/Server/AssetsManager.hpp new file mode 100644 index 0000000..678fc7c --- /dev/null +++ b/Src/Server/AssetsManager.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include "Common/Abstract.hpp" +#include "Common/IdProvider.hpp" +#include "Common/AssetsPreloader.hpp" +#include + +namespace LV::Server { + +class AssetsManager : public IdProvider, protected AssetsPreloader { +public: + using BindHashHeaderInfo = AssetsManager::BindHashHeaderInfo; + + struct Out_checkAndPrepareResourcesUpdate : public AssetsPreloader::Out_checkAndPrepareResourcesUpdate { + Out_checkAndPrepareResourcesUpdate(AssetsPreloader::Out_checkAndPrepareResourcesUpdate&& obj) + : AssetsPreloader::Out_checkAndPrepareResourcesUpdate(std::move(obj)) + {} + + std::unordered_map NewHeadless; + }; + + Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate( + const AssetsRegister& instances, + ReloadStatus* status = nullptr + ) { + std::unordered_map newHeadless; + + Out_checkAndPrepareResourcesUpdate result = AssetsPreloader::checkAndPrepareResourcesUpdate( + instances, + [&](EnumAssets type, std::string_view domain, std::string_view key) { return getId(type, domain, key); }, + [&](std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath) { newHeadless.emplace(hash, std::move(resource)); }, + status + ); + + result.NewHeadless = std::move(newHeadless); + + return result; + } + + struct Out_applyResourcesUpdate : public AssetsPreloader::Out_applyResourcesUpdate { + Out_applyResourcesUpdate(AssetsPreloader::Out_applyResourcesUpdate&& obj) + : AssetsPreloader::Out_applyResourcesUpdate(std::move(obj)) + {} + }; + + Out_applyResourcesUpdate applyResourcesUpdate(Out_checkAndPrepareResourcesUpdate& orr) { + Out_applyResourcesUpdate result = AssetsPreloader::applyResourcesUpdate(orr); + + for(auto& [hash, data] : orr.NewHeadless) { + Resources.emplace(hash, ResourceHashData{0, std::make_shared(std::move(data))}); + } + + for(auto& [hash, pathes] : orr.HashToPathNew) { + auto iter = Resources.find(hash); + assert(iter != Resources.end()); + iter->second.RefCount += pathes.size(); + } + + for(auto& [hash, pathes] : orr.HashToPathLost) { + auto iter = Resources.find(hash); + assert(iter != Resources.end()); + iter->second.RefCount -= pathes.size(); + + if(iter->second.RefCount == 0) + Resources.erase(iter); + } + + return result; + } + + std::vector>> + getResources(const std::vector& hashes) const + { + std::vector>> result; + result.reserve(hashes.size()); + + for(const auto& hash : hashes) { + auto iter = Resources.find(hash); + if(iter == Resources.end()) + continue; + + result.emplace_back(hash, iter->second.Data); + } + + return result; + } + + std::array< + std::vector, + static_cast(EnumAssets::MAX_ENUM) + > collectHashBindings() const { + return AssetsPreloader::collectHashBindings(); + } + +private: + struct ResourceHashData { + size_t RefCount; + std::shared_ptr Data; + }; + + std::unordered_map< + ResourceFile::Hash_t, + ResourceHashData + > Resources; +}; + +} \ No newline at end of file diff --git a/Src/Server/ContentManager.cpp b/Src/Server/ContentManager.cpp index 7547b55..72abeb5 100644 --- a/Src/Server/ContentManager.cpp +++ b/Src/Server/ContentManager.cpp @@ -4,7 +4,7 @@ namespace LV::Server { -ContentManager::ContentManager(AssetsPreloader& am) +ContentManager::ContentManager(AssetsManager& am) : AM(am) { std::fill(std::begin(NextId), std::end(NextId), 1); @@ -158,20 +158,6 @@ ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() { keys.erase(iterErase, keys.end()); } - for(ResourceId id : ProfileChanges[(int) EnumDefContent::Node]) { - std::optional& node = getEntry_Node(id); - if(!node) { - continue; - } - - auto [nodestateId, assetsModel, assetsTexture] - = AM.getNodeDependency(node->Domain, node->Key); - - node->NodestateId = nodestateId; - node->ModelDeps = std::move(assetsModel); - node->TextureDeps = std::move(assetsTexture); - } - return result; } diff --git a/Src/Server/ContentManager.hpp b/Src/Server/ContentManager.hpp index ec9c20e..828ad9b 100644 --- a/Src/Server/ContentManager.hpp +++ b/Src/Server/ContentManager.hpp @@ -1,11 +1,9 @@ #pragma once #include "Common/Abstract.hpp" -#include "Server/Abstract.hpp" -#include "Common/AssetsPreloader.hpp" +#include "AssetsManager.hpp" #include #include -#include namespace LV::Server { @@ -135,7 +133,7 @@ class ContentManager { void registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile); public: - ContentManager(AssetsPreloader &am); + ContentManager(AssetsManager &am); ~ContentManager(); // Регистрирует определение контента @@ -215,7 +213,7 @@ public: private: TOS::Logger LOG = "Server>ContentManager"; - AssetsPreloader& AM; + AssetsManager& AM; }; } diff --git a/Src/Server/GameServer.cpp b/Src/Server/GameServer.cpp index ce9e661..060847c 100644 --- a/Src/Server/GameServer.cpp +++ b/Src/Server/GameServer.cpp @@ -1345,7 +1345,8 @@ void GameServer::init(fs::path worldPath) { AssetsInit.Assets.push_back(mlt.LoadChain[index].Path / "assets"); } - Content.AM.applyResourceChange(Content.AM.reloadResources(AssetsInit)); + auto capru = Content.AM.checkAndPrepareResourcesUpdate(AssetsInit); + Content.AM.applyResourcesUpdate(capru); LOG.info() << "Пре Инициализация"; @@ -1606,12 +1607,11 @@ void GameServer::stepConnections() { } if(!newClients.empty()) { - AssetsPreloader::Out_fullSync fullSync = Content.AM.collectFullSync(); std::array, static_cast(EnumAssets::MAX_ENUM)> lost{}; std::vector packets; - packets.push_back(RemoteClient::makePacket_informateAssets_DK(fullSync.IdToDK)); - packets.push_back(RemoteClient::makePacket_informateAssets_HH(fullSync.HashHeaders, lost)); + packets.push_back(RemoteClient::makePacket_informateAssets_DK(Content.AM.idToDK())); + packets.push_back(RemoteClient::makePacket_informateAssets_HH(Content.AM.collectHashBindings(), lost)); for(const std::shared_ptr& client : newClients) { if(!packets.empty()) { @@ -1688,23 +1688,27 @@ void GameServer::reloadMods() { LOG.info() << "Перезагрузка ассетов"; { { - AssetsPreloader::Out_applyResourceChange applied - = Content.AM.applyResourceChange(Content.AM.reloadResources(AssetsInit)); + AssetsManager::Out_checkAndPrepareResourcesUpdate capru = Content.AM.checkAndPrepareResourcesUpdate(AssetsInit); + AssetsManager::Out_applyResourcesUpdate aru = Content.AM.applyResourcesUpdate(capru); - if(!applied.NewOrChange.empty() || !applied.Lost.empty()) + if(!capru.ResourceUpdates.empty() || !capru.LostLinks.empty()) packetsToSend.push_back( RemoteClient::makePacket_informateAssets_HH( - applied.NewOrChange, - applied.Lost + aru.NewOrUpdates, + capru.LostLinks ) ); } { - AssetsPreloader::Out_bakeId baked = Content.AM.bakeIdTables(); - if(hasAnyBindings(baked.IdToDK)) { - packetsToSend.push_back(RemoteClient::makePacket_informateAssets_DK(baked.IdToDK)); + std::array< + std::vector, + static_cast(EnumAssets::MAX_ENUM) + > baked = Content.AM.bake(); + + if(hasAnyBindings(baked)) { + packetsToSend.push_back(RemoteClient::makePacket_informateAssets_DK(baked)); } } } @@ -2495,9 +2499,13 @@ void GameServer::stepSyncContent() { std::vector packetsToAll; { - AssetsPreloader::Out_bakeId baked = Content.AM.bakeIdTables(); - if(hasAnyBindings(baked.IdToDK)) { - packetsToAll.push_back(RemoteClient::makePacket_informateAssets_DK(baked.IdToDK)); + std::array< + std::vector, + static_cast(EnumAssets::MAX_ENUM) + > baked = Content.AM.bake(); + + if(hasAnyBindings(baked)) { + packetsToAll.push_back(RemoteClient::makePacket_informateAssets_DK(baked)); } } @@ -2515,30 +2523,8 @@ void GameServer::stepSyncContent() { } }; - std::vector binaryResources; - for(const Hash_t& hash : full.Hashes) { - std::optional< - std::tuple - > result = Content.AM.getResource(hash); - - if(!result) - continue; - - auto& [type, id, media] = *result; - LOG.debug() << "Server sending type=" << assetTypeName(type) - << " id=" << id - << " key=" << media->Domain << ':' << media->Key - << " hash=" << int(media->Hash[0]) << '.' - << int(media->Hash[1]) << '.' - << int(media->Hash[2]) << '.' - << int(media->Hash[3]) - << " size=" << media->Resource->size(); - Resource resource(*media->Resource); - binaryResources.push_back(AssetBinaryInfo{ - .Data = std::move(resource), - .Hash = media->Hash - }); - } + std::vector>> binaryResources + = Content.AM.getResources(full.Hashes); for(std::shared_ptr& remoteClient : Game.RemoteClients) { if(!binaryResources.empty()) diff --git a/Src/Server/GameServer.hpp b/Src/Server/GameServer.hpp index 4d1fa2a..ff62d5b 100644 --- a/Src/Server/GameServer.hpp +++ b/Src/Server/GameServer.hpp @@ -14,7 +14,6 @@ #include "RemoteClient.hpp" #include "Server/Abstract.hpp" #include -#include #include #include #include @@ -25,6 +24,7 @@ #include "WorldDefManager.hpp" #include "ContentManager.hpp" +#include "AssetsManager.hpp" #include "World.hpp" #include "SaveBackend.hpp" @@ -73,7 +73,7 @@ class GameServer : public AsyncObject { struct ContentObj { public: - AssetsPreloader AM; + AssetsManager AM; ContentManager CM; // Если контент был перерегистрирован (исключая двоичные ресурсы), то профили будут повторно разосланы diff --git a/Src/Server/RemoteClient.cpp b/Src/Server/RemoteClient.cpp index 3dffa49..cfb3c93 100644 --- a/Src/Server/RemoteClient.cpp +++ b/Src/Server/RemoteClient.cpp @@ -11,13 +11,14 @@ #include #include #include +#include namespace LV::Server { Net::Packet RemoteClient::makePacket_informateAssets_DK( const std::array< - std::vector, + std::vector, static_cast(EnumAssets::MAX_ENUM) >& dkVector ) { @@ -46,7 +47,7 @@ Net::Packet RemoteClient::makePacket_informateAssets_DK( // Запись связок домен+ключ for(size_t type = 0; type < static_cast(EnumAssets::MAX_ENUM); type++) { - const std::vector& binds = dkVector[type]; + const std::vector& binds = dkVector[type]; pack << uint32_t(binds.size()); for(const auto& bind : binds) { @@ -67,7 +68,7 @@ Net::Packet RemoteClient::makePacket_informateAssets_DK( Net::Packet RemoteClient::makePacket_informateAssets_HH( const std::array< - std::vector, + std::vector, static_cast(EnumAssets::MAX_ENUM) >& hhVector, const std::array< @@ -430,21 +431,21 @@ ResourceRequest RemoteClient::pushPreparedPackets() { return std::move(nextRequest); } -void RemoteClient::informateBinaryAssets(const std::vector& resources) +void RemoteClient::informateBinaryAssets(const std::vector>>& resources) { - for(const AssetBinaryInfo& resource : resources) { + for(const auto& [hash, resource] : resources) { auto lock = NetworkAndResource.lock(); - auto iter = std::find(lock->ClientRequested.begin(), lock->ClientRequested.end(), resource.Hash); + auto iter = std::find(lock->ClientRequested.begin(), lock->ClientRequested.end(), hash); if(iter == lock->ClientRequested.end()) continue; lock->ClientRequested.erase(iter); lock.unlock(); - auto it = std::lower_bound(AssetsInWork.OnClient.begin(), AssetsInWork.OnClient.end(), resource.Hash); - if(it == AssetsInWork.OnClient.end() || *it != resource.Hash) { - AssetsInWork.OnClient.insert(it, resource.Hash); - AssetsInWork.ToSend.emplace_back(resource.Data, 0); + 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(hash, resource, 0); } else { LOG.warn() << "Клиент повторно запросил имеющийся у него ресурс"; } @@ -611,36 +612,36 @@ void RemoteClient::onUpdate() { bool hasFullSended = false; - for(auto& [res, sended] : toSend) { + for(auto& [hash, res, sended] : toSend) { if(sended == 0) { // Оповещаем о начале отправки ресурса const size_t initSize = 1 + 1 + 4 + 32 + 4 + 1; if(p.size() + initSize > kMaxAssetPacketSize) flushAssetsPacket(); p << (uint8_t) ToClient::AssetsInitSend - << uint32_t(res.size()); - p.write((const std::byte*) res.hash().data(), 32); + << uint32_t(res->size()); + p.write((const std::byte*) hash.data(), 32); } // Отправляем чанк - size_t willSend = std::min(chunkSize, res.size()-sended); + size_t willSend = std::min(chunkSize, res->size()-sended); const size_t chunkMsgSize = 1 + 1 + 32 + 4 + willSend; if(p.size() + chunkMsgSize > kMaxAssetPacketSize) flushAssetsPacket(); p << (uint8_t) ToClient::AssetsNextSend; - p.write((const std::byte*) res.hash().data(), 32); + p.write((const std::byte*) hash.data(), 32); p << uint32_t(willSend); - p.write(res.data() + sended, willSend); + p.write((const std::byte*) res->data() + sended, willSend); sended += willSend; - if(sended == res.size()) { + if(sended == res->size()) { hasFullSended = true; } } if(hasFullSended) { for(ssize_t iter = toSend.size()-1; iter >= 0; iter--) { - if(std::get<0>(toSend[iter]).size() == std::get<1>(toSend[iter])) { + if(std::get<1>(toSend[iter])->size() == std::get<2>(toSend[iter])) { toSend.erase(toSend.begin()+iter); } } diff --git a/Src/Server/RemoteClient.hpp b/Src/Server/RemoteClient.hpp index 99c3148..09235b3 100644 --- a/Src/Server/RemoteClient.hpp +++ b/Src/Server/RemoteClient.hpp @@ -5,6 +5,7 @@ #include #include "Abstract.hpp" #include "Common/Packets.hpp" +#include "Server/AssetsManager.hpp" #include "Server/ContentManager.hpp" #include #include @@ -256,7 +257,7 @@ class RemoteClient { std::vector OnClient; // Отправляемые на клиент ресурсы // Ресурс, количество отправленных байт - std::vector> ToSend; + std::vector, size_t>> ToSend; // Пакет с ресурсами std::vector AssetsPackets; Net::Packet AssetsPacket; @@ -361,7 +362,7 @@ public: // Создаёт пакет для всех игроков с оповещением о новых идентификаторах (id -> domain+key) static Net::Packet makePacket_informateAssets_DK( const std::array< - std::vector, + std::vector, static_cast(EnumAssets::MAX_ENUM) >& dkVector ); @@ -369,7 +370,7 @@ public: // Создаёт пакет для всех игроков с оповещением об изменении файлов ресурсов (id -> hash+header) static Net::Packet makePacket_informateAssets_HH( const std::array< - std::vector, + std::vector, static_cast(EnumAssets::MAX_ENUM) >& hhVector, const std::array< @@ -380,7 +381,7 @@ public: // Оповещение о двоичных ресурсах (стриминг по запросу) void informateBinaryAssets( - const std::vector& resources + const std::vector>>& resources ); // Создаёт пакет об обновлении игровых профилей