diff --git a/Src/Client/Abstract.hpp b/Src/Client/Abstract.hpp index b75fd7f..2e9a9c0 100644 --- a/Src/Client/Abstract.hpp +++ b/Src/Client/Abstract.hpp @@ -158,15 +158,6 @@ struct DefNode_t { }; -struct AssetEntry { - EnumAssets Type; - ResourceId Id; - std::string Domain, Key; - Resource Res; - Hash_t Hash = {}; - std::vector Dependencies; -}; - /* Интерфейс обработчика сессии с сервером. @@ -181,7 +172,7 @@ public: bool DebugLogPackets = false; // Используемые двоичные ресурсы - std::unordered_map> Assets; + // std::unordered_map> Assets; // Используемые профили контента struct { diff --git a/Src/Client/AssetsManager.cpp b/Src/Client/AssetsManager.cpp_ similarity index 100% rename from Src/Client/AssetsManager.cpp rename to Src/Client/AssetsManager.cpp_ diff --git a/Src/Client/AssetsManager.hpp b/Src/Client/AssetsManager.hpp index be56591..a2e0bb1 100644 --- a/Src/Client/AssetsManager.hpp +++ b/Src/Client/AssetsManager.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -9,285 +10,365 @@ #include #include #include +#include #include #include #include "Client/AssetsCacheManager.hpp" #include "Client/AssetsHeaderCodec.hpp" #include "Common/Abstract.hpp" +#include "Common/IdProvider.hpp" +#include "Common/AssetsPreloader.hpp" #include "TOSLib.hpp" +#include "boost/asio/io_context.hpp" +#include namespace LV::Client { namespace fs = std::filesystem; -class AssetsManager { +class AssetsManager : public IdProvider { public: - using Ptr = std::shared_ptr; - using AssetType = EnumAssets; - using AssetId = ResourceId; - - // Ключ запроса ресурса (идентификация + хеш для поиска источника). - struct ResourceKey { - // Хеш ресурса, используемый для поиска в источниках и кэше. - Hash_t Hash{}; - // Тип ресурса (модель, текстура и т.д.). - AssetType Type{}; - // Домен ресурса. - std::string Domain; - // Ключ ресурса внутри домена. - std::string Key; - // Идентификатор ресурса на стороне клиента/локальный. - AssetId Id = 0; + struct ResourceUpdates { + /// TODO: Добавить анимацию из меты + std::vector>> Textures; }; - // Информация о биндинге серверного ресурса на локальный id. - struct BindInfo { - // Тип ресурса. - AssetType Type{}; - // Локальный идентификатор. - AssetId LocalId = 0; - // Домен ресурса. - std::string Domain; - // Ключ ресурса. - std::string Key; - // Хеш ресурса. - Hash_t Hash{}; - // Бинарный заголовок с зависимостями. - std::vector Header; - }; - - // Результат биндинга ресурса сервера. - struct BindResult { - // Итоговый локальный идентификатор. - AssetId LocalId = 0; - // Признак изменения бинда (хеш/заголовок). - bool Changed = false; - // Признак новой привязки. - bool NewBinding = false; - // Идентификатор, от которого произошёл ребинд (если был). - std::optional ReboundFrom; - }; - - // Регистрация набора ресурспаков. - struct PackRegister { - // Пути до паков (директории/архивы). - std::vector Packs; - }; - - // Ресурс, собранный из пака. - struct PackResource { - // Тип ресурса. - AssetType Type{}; - // Локальный идентификатор. - AssetId LocalId = 0; - // Домен ресурса. - std::string Domain; - // Ключ ресурса. - std::string Key; - // Тело ресурса. - Resource Res; - // Хеш ресурса. - Hash_t Hash{}; - // Заголовок ресурса (например, зависимости). - std::u8string Header; - }; - - // Результат пересканирования паков. - struct PackReloadResult { - // Добавленные/изменённые ресурсы по типам. - std::array, static_cast(AssetType::MAX_ENUM)> ChangeOrAdd; - // Потерянные ресурсы по типам. - std::array, static_cast(AssetType::MAX_ENUM)> Lost; - }; - - using ParsedHeader = AssetsHeaderCodec::ParsedHeader; - - // Фабрика с настройкой лимитов кэша. - static Ptr Create(asio::io_context& ioc, const fs::path& cachePath, - size_t maxCacheDirectorySize = 8 * 1024 * 1024 * 1024ULL, - size_t maxLifeTime = 7 * 24 * 60 * 60) { - return Ptr(new AssetsManager(ioc, cachePath, maxCacheDirectorySize, maxLifeTime)); +public: + AssetsManager(asio::io_context& ioc, fs::path cachePath) + : Cache(AssetsCacheManager::Create(ioc, cachePath)) { } - // Пересканировать ресурспаки и вернуть изменившиеся/утраченные ресурсы. - PackReloadResult reloadPacks(const PackRegister& reg); +// Ручные обновления + struct Out_checkAndPrepareResourcesUpdate { + AssetsPreloader::Out_checkAndPrepareResourcesUpdate RP, ES; - // Связать серверный ресурс с локальным id и записать метаданные. - BindResult bindServerResource(AssetType type, AssetId serverId, std::string domain, std::string key, - const Hash_t& hash, std::vector header); - // Отвязать серверный id и вернуть актуальный локальный id (если был). - std::optional unbindServerResource(AssetType type, AssetId serverId); - // Сбросить все серверные бинды. - void clearServerBindings(); + std::unordered_map Files; + }; - // Получить данные бинда по локальному id. - const BindInfo* getBind(AssetType type, AssetId localId) const; + Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate( + const std::vector& resourcePacks, + const std::vector& extraSources + ) { + Out_checkAndPrepareResourcesUpdate result; - // Перебиндить хедер, заменив id зависимостей. - std::vector rebindHeader(AssetType type, const std::vector& header, bool serverIds = true); - // Распарсить хедер ресурса. - static std::optional parseHeader(AssetType type, const std::vector& header); + result.RP = ResourcePacks.checkAndPrepareResourcesUpdate( + AssetsPreloader::AssetsRegister{resourcePacks}, + [&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId { + return getId(type, domain, key); + }, + [&](std::u8string&& data, ResourceFile::Hash_t hash, fs::path path) { + result.Files.emplace(hash, std::move(data)); + } + ); - // Протолкнуть новые ресурсы в память и кэш. - void pushResources(std::vector resources); + result.ES = ExtraSource.checkAndPrepareResourcesUpdate( + AssetsPreloader::AssetsRegister{resourcePacks}, + [&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId { + return getId(type, domain, key); + } + ); - // Поставить запросы чтения ресурсов. - void pushReads(std::vector reads); - // Получить готовые результаты чтения. - std::vector>> pullReads(); - // Продвинуть асинхронные источники (кэш). - void tickSources(); + return result; + } - // Получить или создать локальный id по домену/ключу. - AssetId getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key); - // Получить локальный id по серверному id (если есть). - std::optional getLocalIdFromServer(AssetType type, AssetId serverId) const; + struct Out_applyResourcesUpdate { + + }; + + Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) { + Out_applyResourcesUpdate result; + + ResourcePacks.applyResourcesUpdate(orr.RP); + ExtraSource.applyResourcesUpdate(orr.ES); + + std::unordered_set needHashes; + + for(size_t type = 0; type < static_cast(EnumAssets::MAX_ENUM); ++type) { + for(const auto& res : orr.RP.ResourceUpdates[type]) { + // Помечаем ресурс для обновления + PendingUpdateFromAsync[type].push_back(std::get(res)); + } + + for(ResourceId id : orr.RP.LostLinks[type]) { + // Помечаем ресурс для обновления + PendingUpdateFromAsync[type].push_back(id); + + auto& hh = ServerIdToHH[type]; + if(id < hh.size()) + needHashes.insert(std::get(hh[id])); + } + } + + { + for(const auto& [hash, data] : orr.Files) { + WaitingHashes.insert(hash); + } + + for(const auto& hash : WaitingHashes) + needHashes.erase(hash); + + std::vector> toDisk; + std::vector toCache; + + // Теперь раскидаем хеши по доступным источникам. + for(const auto& hash : needHashes) { + auto iter = HashToPath.find(hash); + if(iter != HashToPath.end()) { + // Ставим задачу загрузить с диска. + toDisk.emplace_back(hash, iter->second.front()); + } else { + // Сделаем запрос в кеш. + toCache.push_back(hash); + } + } + + // Запоминаем, что эти ресурсы уже ожидаются. + WaitingHashes.insert_range(needHashes); + + // Запрос в кеш (если там не найдётся, то запрос уйдёт на сервер). + if(!toCache.empty()) + Cache->pushReads(std::move(toCache)); + + // Запрос к диску. + if(!toDisk.empty()) + NeedToReadFromDisk.append_range(std::move(toDisk)); + + _onHashLoad(orr.Files); + } + + return result; + } + +// ServerSession + // Новые привязки ассетов к Домен+Ключ. + void pushAssetsBindDK( + const std::vector& domains, + const std::array< + std::vector>, + static_cast(EnumAssets::MAX_ENUM) + >& keys + ) { + for(size_t type = 0; type < static_cast(EnumAssets::MAX_ENUM); ++type) { + for(size_t forDomainIter = 0; forDomainIter < keys[type].size(); ++forDomainIter) { + for(const std::string& key : keys[type][forDomainIter]) { + ServerToClientMap[type].push_back(getId((EnumAssets) type, domains[forDomainIter], key)); + } + } + } + } + + // Новые привязки ассетов к Hash+Header. + void pushAssetsBindHH( + std::array< + std::vector>, + static_cast(EnumAssets::MAX_ENUM) + >&& hash_and_headers + ) { + std::array< + std::vector>, + static_cast(EnumAssets::MAX_ENUM) + > hah = std::move(hash_and_headers); + + std::unordered_set needHashes; + + for(size_t type = 0; type < static_cast(EnumAssets::MAX_ENUM); ++type) { + size_t maxSize = 0; + + for(auto& [id, hash, header] : hash_and_headers[type]) { + assert(id < ServerToClientMap[type].size()); + id = ServerToClientMap[type][id]; + + if(id > maxSize) + maxSize = id+1; + + // Добавляем идентификатор в таблицу ожидающих обновлений. + PendingUpdateFromAsync[type].push_back(id); + + // Поискать есть ли ресурс в ресурспаках. + std::optional res = ResourcePacks.getResource((EnumAssets) type, id); + if(res) { + needHashes.insert(res->Hash); + } else { + needHashes.insert(hash); + } + } + + { + // Уберём повторения в идентификаторах. + auto& vec = PendingUpdateFromAsync[type]; + std::sort(vec.begin(), vec.end()); + vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); + } + + if(ServerIdToHH[type].size() < maxSize) + ServerIdToHH[type].resize(maxSize); + + for(auto& [id, hash, header] : hash_and_headers[type]) { + ServerIdToHH[type][id] = {hash, std::move(header)}; + } + } + + // Нужно убрать хеши, которые уже запрошены + // needHashes ^ WaitingHashes. + + for(const auto& hash : WaitingHashes) + needHashes.erase(hash); + + std::vector> toDisk; + std::vector toCache; + + // Теперь раскидаем хеши по доступным источникам. + for(const auto& hash : needHashes) { + auto iter = HashToPath.find(hash); + if(iter != HashToPath.end()) { + // Ставим задачу загрузить с диска. + toDisk.emplace_back(hash, iter->second.front()); + } else { + // Сделаем запрос в кеш. + toCache.push_back(hash); + } + } + + // Запоминаем, что эти ресурсы уже ожидаются. + WaitingHashes.insert_range(needHashes); + + // Запрос к диску. + if(!toDisk.empty()) + NeedToReadFromDisk.append_range(std::move(toDisk)); + + // Запрос в кеш (если там не найдётся, то запрос уйдёт на сервер). + if(!toCache.empty()) + Cache->pushReads(std::move(toCache)); + } + + // Новые ресурсы, полученные с сервера. + void pushNewResources( + std::vector> &&resources + ) { + std::unordered_map files; + std::vector vec; + files.reserve(resources.size()); + vec.reserve(resources.size()); + + for(auto& [hash, res] : resources) { + vec.emplace_back(std::move(res)); + files.emplace(hash, res); + } + + _onHashLoad(files); + Cache->pushResources(std::move(vec)); + } + + // Для запроса отсутствующих ресурсов с сервера на клиент. + std::vector pollNeededResources() { + return std::move(NeedToRequestFromServer); + } + + // Получить изменённые ресурсы (для передачи другим модулям). + ResourceUpdates pullResourceUpdates() { + return std::move(RU); + } + + void tick() { + // Проверим кеш + std::vector>> resources = Cache->pullReads(); + if(!resources.empty()) { + std::unordered_map needToProceed; + needToProceed.reserve(resources.size()); + + for(auto& [hash, res] : resources) { + if(!res) + NeedToRequestFromServer.push_back(hash); + else + needToProceed.emplace(hash, std::u8string{(const char8_t*) res->data(), res->size()}); + } + + if(!needToProceed.empty()) + _onHashLoad(needToProceed); + } + + // Почитаем с диска + if(!NeedToReadFromDisk.empty()) { + std::unordered_map files; + for(const auto& [hash, path] : NeedToReadFromDisk) { + std::u8string data; + std::ifstream file(path, std::ios::binary); + if(file) { + file.seekg(0, std::ios::end); + std::streamoff size = file.tellg(); + if(size < 0) + size = 0; + file.seekg(0, std::ios::beg); + data.resize(static_cast(size)); + if(size > 0) { + file.read(reinterpret_cast(data.data()), size); + if(!file) + data.clear(); + } + } + files.emplace(hash, std::move(data)); + } + + NeedToReadFromDisk.clear(); + _onHashLoad(files); + } + } private: - // Связка домен/ключ для локального id. - struct DomainKey { - // Домен ресурса. - std::string Domain; - // Ключ ресурса. - std::string Key; - // Признак валидности записи. - bool Known = false; - }; + // Менеджеры учёта дисковых ресурсов + AssetsPreloader + // В приоритете ищутся ресурсы из ресурспаков по Domain+Key. + ResourcePacks, + /* + Дополнительные источники ресурсов. + Используется для поиска ресурса по хешу от сервера (может стоит тот же мод с совпадающими ресурсами), + или для временной подгрузки ресурса по Domain+Key пока ресурс не был получен с сервера. + */ + ExtraSource; - using IdTable = std::unordered_map< - std::string, - std::unordered_map, - detail::TSVHash, - detail::TSVEq>; - - using PackTable = std::unordered_map< - std::string, - std::unordered_map, - detail::TSVHash, - detail::TSVEq>; - - struct PerType { - // Таблица домен/ключ -> локальный id. - IdTable DKToLocal; - // Таблица локальный id -> домен/ключ. - std::vector LocalToDK; - // Union-Find родительские ссылки для ребиндов. - std::vector LocalParent; - // Таблица серверный id -> локальный id. - std::vector ServerToLocal; - // Бинды с сервером по локальному id. - std::vector> BindInfos; - // Ресурсы, собранные из паков. - PackTable PackResources; - // Следующий локальный id. - AssetId NextLocalId = 1; - }; - - enum class SourceStatus { - Hit, - Miss, - Pending - }; - - struct SourceResult { - // Статус ответа источника. - SourceStatus Status = SourceStatus::Miss; - // Значение ресурса, если найден. - std::optional Value; - // Индекс источника. - size_t SourceIndex = 0; - }; - - struct SourceReady { - // Хеш готового ресурса. - Hash_t Hash{}; - // Значение ресурса, если найден. - std::optional Value; - // Индекс источника. - size_t SourceIndex = 0; - }; - - class IResourceSource { - public: - virtual ~IResourceSource() = default; - // Попытка получить ресурс синхронно. - virtual SourceResult tryGet(const ResourceKey& key) = 0; - // Забрать готовые результаты асинхронных запросов. - virtual void collectReady(std::vector& out) = 0; - // Признак асинхронности источника. - virtual bool isAsync() const = 0; - // Запустить асинхронные запросы по хешам. - virtual void startPending(std::vector hashes) = 0; - }; - - struct SourceEntry { - // Экземпляр источника. - std::unique_ptr Source; - // Поколение для инвалидирования кэша. - size_t Generation = 0; - }; - - struct SourceCacheEntry { - // Индекс источника, где был найден хеш. - size_t SourceIndex = 0; - // Поколение источника на момент кэширования. - size_t Generation = 0; - }; - - // Конструктор с зависимостью от io_context и кэш-пути. - AssetsManager(asio::io_context& ioc, const fs::path& cachePath, - size_t maxCacheDirectorySize, size_t maxLifeTime); - - // Инициализация списка источников. - void initSources(); - // Забрать готовые результаты из источников. - void collectReadyFromSources(); - // Запросить ресурс в источниках, с учётом кэша. - SourceResult querySources(const ResourceKey& key); - // Запомнить успешный источник для хеша. - void registerSourceHit(const Hash_t& hash, size_t sourceIndex); - // Инвалидировать кэш по конкретному источнику. - void invalidateSourceCache(size_t sourceIndex); - // Инвалидировать весь кэш источников. - void invalidateAllSourceCache(); - - // Выделить новый локальный id. - AssetId allocateLocalId(AssetType type); - // Получить корневой локальный id с компрессией пути. - AssetId resolveLocalIdMutable(AssetType type, AssetId localId); - // Получить корневой локальный id без мутаций. - AssetId resolveLocalId(AssetType type, AssetId localId) const; - // Объединить два локальных id в один. - void unionLocalIds(AssetType type, AssetId fromId, AssetId toId, std::optional* reboundFrom); - - // Найти ресурс в паке по домену/ключу. - std::optional findPackResource(AssetType type, std::string_view domain, std::string_view key) const; - - // Логгер подсистемы. - Logger LOG = "Client>AssetsManager"; // Менеджер файлового кэша. AssetsCacheManager::Ptr Cache; - // Таблицы данных по каждому типу ресурсов. - std::array(AssetType::MAX_ENUM)> Types; + // Указатели на доступные ресурсы + std::unordered_map> HashToPath; - // Список источников ресурсов. - std::vector Sources; - // Кэш попаданий по хешу. - std::unordered_map SourceCacheByHash; - // Индекс источника паков. - size_t PackSourceIndex = 0; - // Индекс памяти (RAM) как источника. - size_t MemorySourceIndex = 0; - // Индекс файлового кэша. - size_t CacheSourceIndex = 0; + // Таблица релинковки ассетов с идентификаторов сервера на клиентские. + std::array< + std::vector, + static_cast(EnumAssets::MAX_ENUM) + > ServerToClientMap; + + // Таблица серверных привязок HH (id клиентские) + std::array< + std::vector>, + static_cast(EnumAssets::MAX_ENUM) + > ServerIdToHH; - // Ресурсы в памяти по хешу. - std::unordered_map MemoryResourcesByHash; - // Ожидающие запросы, сгруппированные по хешу. - std::unordered_map> PendingReadsByHash; - // Готовые ответы на чтение. - std::vector>> ReadyReads; + // Ресурсы в ожидании данных по хешу для обновления (с диска, кеша, сервера). + std::array< + std::vector, + static_cast(EnumAssets::MAX_ENUM) + > PendingUpdateFromAsync; + + // Хеши, для которых где-то висит задача на загрузку. + std::unordered_set WaitingHashes; + + // Хеши, которые необходимо запросить с сервера. + std::vector NeedToRequestFromServer; + + // Ресурсы, которые нужно считать с диска + std::vector> NeedToReadFromDisk; + + // Обновлённые ресурсы + ResourceUpdates RU; + + // Когда данные были получены с диска, кеша или сервера + void _onHashLoad(const std::unordered_map& files) { + /// TODO: скомпилировать ресурсы + + for(const auto& [hash, res] : files) + WaitingHashes.erase(hash); + } }; } // namespace LV::Client diff --git a/Src/Client/ServerSession.cpp b/Src/Client/ServerSession.cpp index 81dca0f..030e149 100644 --- a/Src/Client/ServerSession.cpp +++ b/Src/Client/ServerSession.cpp @@ -61,7 +61,7 @@ const char* toClientPacketName(ToClient type) { } ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr&& socket) - : IAsyncDestructible(ioc), Socket(std::move(socket)) //, NetInputPackets(1024) + : IAsyncDestructible(ioc), Socket(std::move(socket)), AM(ioc, "Cache") { assert(Socket.get()); @@ -71,12 +71,7 @@ ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr assets = std::move(*AsyncContext.LoadedAssets.lock()); - std::vector resources; - resources.reserve(assets.size()); - - for(AssetEntry& entry : assets) { - entry.Hash = entry.Res.hash(); - if(const AssetsManager::BindInfo* bind = AM->getBind(entry.Type, entry.Id)) - entry.Dependencies = AM->rebindHeader(entry.Type, bind->Header); - else - entry.Dependencies.clear(); - - resources.push_back(entry.Res); - AsyncContext.LoadedResources.emplace_back(std::move(entry)); - - // // Проверяем используется ли сейчас ресурс - // auto iter = MyAssets.ExistBinds[(int) entry.Type].find(entry.Id); - // if(iter == MyAssets.ExistBinds[(int) entry.Type].end()) { - // // Не используется - // MyAssets.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)); - } - - // Получить ресурсы с AssetsManager + // Если AssetsManager запрашивает ресурсы с сервера { - static std::atomic debugAssetReadLogCount = 0; - std::vector>> resources = AM->pullReads(); - std::vector needRequest; + std::vector needRequest = AM.pullNeededResources(); + Net::Packet pack; + std::vector packets; - for(auto& [key, res] : resources) { - bool cacheHit = false; - Hash_t actualHash = {}; - if(res) { - actualHash = res->hash(); - cacheHit = actualHash == key.Hash; - } - if(cacheHit) { - auto& waitingByDomain = AsyncContext.ResourceWait[(int) key.Type]; - auto iterDomain = waitingByDomain.find(key.Domain); - if(iterDomain != waitingByDomain.end()) { - auto& entries = iterDomain->second; - entries.erase(std::remove_if(entries.begin(), entries.end(), - [&](const std::pair& entry) { - return entry.first == key.Key && entry.second == key.Hash; - }), - entries.end()); - if(entries.empty()) - waitingByDomain.erase(iterDomain); - } - } + auto check = [&]() { + if(pack.size() > 64000) + packets.emplace_back(std::move(pack)); + }; - if(key.Domain == "test" - && (key.Type == EnumAssets::Nodestate - || key.Type == EnumAssets::Model - || key.Type == EnumAssets::Texture)) - { - uint32_t idx = debugAssetReadLogCount.fetch_add(1); - if(idx < 128) { - if(res) { - LOG.debug() << "Cache hit type=" << assetTypeName(key.Type) - << " id=" << key.Id - << " key=" << key.Domain << ':' << key.Key - << " size=" << res->size(); - } else { - LOG.debug() << "Cache miss type=" << assetTypeName(key.Type) - << " id=" << key.Id - << " key=" << key.Domain << ':' << key.Key - << " hash=" << int(key.Hash[0]) << '.' - << int(key.Hash[1]) << '.' - << int(key.Hash[2]) << '.' - << int(key.Hash[3]); - } - } - } - - if(!res) { - // Проверить не был ли уже отправлен запрос на получение этого хеша - auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), key.Hash); - if(iter == AsyncContext.AlreadyLoading.end() || *iter != key.Hash) { - AsyncContext.AlreadyLoading.insert(iter, key.Hash); - needRequest.push_back(key.Hash); - } - } else { - if(actualHash != key.Hash) { - auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), key.Hash); - if(iter == AsyncContext.AlreadyLoading.end() || *iter != key.Hash) { - AsyncContext.AlreadyLoading.insert(iter, key.Hash); - needRequest.push_back(key.Hash); - } - } - - std::vector deps; - if(const AssetsManager::BindInfo* bind = AM->getBind(key.Type, key.Id)) - deps = AM->rebindHeader(key.Type, bind->Header); - - AssetEntry entry { - .Type = key.Type, - .Id = key.Id, - .Domain = key.Domain, - .Key = key.Key, - .Res = std::move(*res), - .Hash = actualHash, - .Dependencies = std::move(deps) - }; - - AsyncContext.LoadedResources.emplace_back(std::move(entry)); - } + pack << (uint8_t) ToServer::L1::System << (uint8_t) ToServer::L2System::ResourceRequest; + pack << (uint16_t) needRequest.size(); + for(const Hash_t& hash : needRequest) { + pack.write((const std::byte*) hash.data(), 32); + check(); } - if(!needRequest.empty()) { - assert(needRequest.size() < (1 << 16)); + if(pack.size()) + packets.emplace_back(std::move(pack)); - uint32_t idx = debugAssetReadLogCount.fetch_add(1); - if(idx < 128) { - LOG.debug() << "Send ResourceRequest count=" << needRequest.size(); - } - for(const auto& hash : needRequest) { - LOG.debug() << "Client request hash=" - << int(hash[0]) << '.' - << int(hash[1]) << '.' - << int(hash[2]) << '.' - << int(hash[3]); - } - - Net::Packet p; - p << (uint8_t) ToServer::L1::System << (uint8_t) ToServer::L2System::ResourceRequest; - p << (uint16_t) needRequest.size(); - for(const Hash_t& hash : needRequest) - p.write((const std::byte*) hash.data(), 32); - - Socket->pushPacket(std::move(p)); - } - } - - // Разбираемся с полученными меж тактами привязками ресурсов - if(!AsyncContext.AssetsBinds.get_read().empty()) { - AssetsBindsChange abc; - - // Нужно объеденить изменения в один AssetsBindsChange (abc) - { - std::vector list = std::move(*AsyncContext.AssetsBinds.lock()); - - for(AssetsBindsChange entry : list) { - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) - std::sort(entry.Lost[type].begin(), entry.Lost[type].end()); - - // Если до этого была объявлена привязка, а теперь она потеряна, то просто сокращаем значения. - // Иначе дописываем в lost - for(ssize_t iter = abc.Binds.size()-1; iter >= 0; iter--) { - const AssetBindEntry& abe = abc.Binds[iter]; - auto& lost = entry.Lost[(int) abe.Type]; - auto iterator = std::lower_bound(lost.begin(), lost.end(), abe.Id); - if(iterator != lost.end() && *iterator == abe.Id) { - // Привязка будет удалена - lost.erase(iterator); - abc.Binds.erase(abc.Binds.begin()+iter); - } - } - - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - abc.Lost[type].append_range(entry.Lost[type]); - entry.Lost[type].clear(); - std::sort(abc.Lost[type].begin(), abc.Lost[type].end()); - } - - for(AssetBindEntry& abe : entry.Binds) { - auto iterator = std::lower_bound(entry.Lost[(int) abe.Type].begin(), entry.Lost[(int) abe.Type].end(), abe.Id); - if(iterator != entry.Lost[(int) abe.Type].end() && *iterator == abe.Id) { - // Получили новую привязку, которая была удалена в предыдущем такте - entry.Lost[(int) abe.Type].erase(iterator); - } else { - // Данная привязка не удалялась, может она была изменена? - bool hasChanged = false; - for(AssetBindEntry& abe2 : abc.Binds) { - if(abe2.Type == abe.Type && abe2.Id == abe.Id) { - // Привязка была изменена - abe2 = std::move(abe); - hasChanged = true; - break; - } - } - - if(!hasChanged) - // Изменения не было, это просто новая привязка - abc.Binds.emplace_back(std::move(abe)); - } - } - - entry.Binds.clear(); - } - } - - // Запрос к дисковому кешу новых ресурсов - std::vector needToLoad; - for(const AssetBindEntry& bind : abc.Binds) { - bool needQuery = true; - // Проверить in memory кеш по домену+ключу - { - std::string dk = bind.Domain + ':' + bind.Key; - auto &niubdk = MyAssets.NotInUse[(int) bind.Type]; - auto iter = niubdk.find(dk); - if(iter != niubdk.end()) { - // Есть ресурс - needQuery = iter->second.first.Hash != bind.Hash; - } - } - - // Проверить если такой запрос уже был отправлен в AssetsManager и ожидает ответа - if(needQuery) { - auto& list = AsyncContext.ResourceWait[(int) bind.Type]; - auto iterDomain = list.find(bind.Domain); - if(iterDomain != list.end()) { - for(const auto& [key, hash] : iterDomain->second) { - if(key == bind.Key && hash == bind.Hash) { - needQuery = false; - break; - } - } - } - } - - // Под рукой нет ресурса, отправим на проверку в AssetsManager - if(needQuery) { - AsyncContext.ResourceWait[(int) bind.Type][bind.Domain].emplace_back(bind.Key, bind.Hash); - needToLoad.push_back(AssetsManager::ResourceKey{ - .Hash = bind.Hash, - .Type = bind.Type, - .Domain = bind.Domain, - .Key = bind.Key, - .Id = bind.Id - }); - } - } - - // Отправляем запрос на получение ресурсов - if(!needToLoad.empty()) { - static std::atomic debugReadRequestLogCount = 0; - AssetsManager::ResourceKey firstDebug; - bool hasDebug = false; - for(const auto& entry : needToLoad) { - if(entry.Domain == "test" - && (entry.Type == EnumAssets::Nodestate - || entry.Type == EnumAssets::Model - || entry.Type == EnumAssets::Texture)) - { - firstDebug = entry; - hasDebug = true; - break; - } - } - if(hasDebug && debugReadRequestLogCount.fetch_add(1) < 64) { - LOG.debug() << "Queue asset read count=" << needToLoad.size() - << " type=" << assetTypeName(firstDebug.Type) - << " id=" << firstDebug.Id - << " key=" << firstDebug.Domain << ':' << firstDebug.Key - << " hash=" << int(firstDebug.Hash[0]) << '.' - << int(firstDebug.Hash[1]) << '.' - << int(firstDebug.Hash[2]) << '.' - << int(firstDebug.Hash[3]); - } - for(const auto& entry : needToLoad) { - LOG.debug() << "Client wants type=" << assetTypeName(entry.Type) - << " id=" << entry.Id - << " key=" << entry.Domain << ':' << entry.Key - << " hash=" << int(entry.Hash[0]) << '.' - << int(entry.Hash[1]) << '.' - << int(entry.Hash[2]) << '.' - << int(entry.Hash[3]); - } - AM->pushReads(std::move(needToLoad)); - } - - AsyncContext.Binds.push_back(std::move(abc)); + Socket->pushPackets(&packets); } if(!AsyncContext.TickSequence.get_read().empty()) { @@ -624,6 +360,20 @@ void ServerSession::update(GlobalTime gTime, float dTime) { { for(TickData& data : ticks) { + // Привязки Id -> Domain+Key + for(const auto& binds : data.BindsDK) { + AM.pushAssetsBindDK(binds.Domains, binds.Keys); + } + + // Привязки Id -> Hash+Header + for(auto& binds : data.BindsHH) { + AM.pushAssetsBindHH(std::move(binds.HashAndHeaders)); + } + + // Полученные ресурсы с сервера + if(!data.ReceivedAssets.empty()) + AM.pushNewResources(std::move(data.ReceivedAssets)); + { for(auto& [id, profile] : data.Profile_Voxel_AddOrChange) { auto iter = std::lower_bound(profile_Voxel_Lost.begin(), profile_Voxel_Lost.end(), id); @@ -908,128 +658,6 @@ void ServerSession::update(GlobalTime gTime, float dTime) { RS->pushStageTickSync(); // Применяем изменения по ресурсам, профилям и контенту - // Разбираемся с изменениями в привязках ресурсов - { - AssetsBindsChange abc; - - for(AssetsBindsChange entry : AsyncContext.Binds) { - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) - std::sort(entry.Lost[type].begin(), entry.Lost[type].end()); - - for(ssize_t iter = abc.Binds.size()-1; iter >= 0; iter--) { - const AssetBindEntry& abe = abc.Binds[iter]; - auto& lost = entry.Lost[(int) abe.Type]; - auto iterator = std::lower_bound(lost.begin(), lost.end(), abe.Id); - if(iterator != lost.end() && *iterator == abe.Id) { - lost.erase(iterator); - abc.Binds.erase(abc.Binds.begin()+iter); - } - } - - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - abc.Lost[type].append_range(entry.Lost[type]); - entry.Lost[type].clear(); - std::sort(abc.Lost[type].begin(), abc.Lost[type].end()); - } - - for(AssetBindEntry& abe : entry.Binds) { - auto iterator = std::lower_bound(entry.Lost[(int) abe.Type].begin(), entry.Lost[(int) abe.Type].end(), abe.Id); - if(iterator != entry.Lost[(int) abe.Type].end() && *iterator == abe.Id) { - // Получили новую привязку, которая была удалена в предыдущем такте - entry.Lost[(int) abe.Type].erase(iterator); - } else { - // Данная привязка не удалялась, может она была изменена? - bool hasChanged = false; - for(AssetBindEntry& abe2 : abc.Binds) { - if(abe2.Type == abe.Type && abe2.Id == abe.Id) { - // Привязка была изменена - abe2 = std::move(abe); - hasChanged = true; - break; - } - } - - if(!hasChanged) - // Изменения не было, это просто новая привязка - abc.Binds.emplace_back(std::move(abe)); - } - } - - entry.Binds.clear(); - } - - AsyncContext.Binds.clear(); - - for(AssetBindEntry& entry : abc.Binds) { - std::vector deps; - if(!entry.Header.empty()) - deps = AM->rebindHeader(entry.Type, entry.Header); - - MyAssets.ExistBinds[(int) entry.Type].insert(entry.Id); - result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id); - - auto iterLoaded = IServerSession::Assets[entry.Type].find(entry.Id); - if(iterLoaded != IServerSession::Assets[entry.Type].end()) - iterLoaded->second.Dependencies = deps; - - // Если ресурс был в кеше, то достаётся от туда - auto iter = MyAssets.NotInUse[(int) entry.Type].find(entry.Domain+':'+entry.Key); - if(iter != MyAssets.NotInUse[(int) entry.Type].end()) { - iter->second.first.Dependencies = deps; - IServerSession::Assets[entry.Type][entry.Id] = std::get<0>(iter->second); - result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id); - MyAssets.NotInUse[(int) entry.Type].erase(iter); - } - } - - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - for(ResourceId id : abc.Lost[type]) { - MyAssets.ExistBinds[type].erase(id); - - // Потерянные ресурсы уходят в кеш - auto iter = IServerSession::Assets[(EnumAssets) type].find(id); - if(iter != IServerSession::Assets[(EnumAssets) type].end()) { - MyAssets.NotInUse[(int) iter->second.Type][iter->second.Domain+':'+iter->second.Key] = {iter->second, TIME_BEFORE_UNLOAD_RESOURCE+time(nullptr)}; - IServerSession::Assets[(EnumAssets) type].erase(iter); - result.Assets_Lost[iter->second.Type].push_back(iter->second.Id); - } - } - - result.Assets_Lost[(EnumAssets) type] = std::move(abc.Lost[type]); - } - } - - // Получаем ресурсы - { - for(AssetEntry& entry : AsyncContext.LoadedResources) { - if(MyAssets.ExistBinds[(int) entry.Type].contains(entry.Id)) { - // Ресурс ещё нужен - IServerSession::Assets[entry.Type][entry.Id] = entry; - result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id); - } else { - // Ресурс уже не нужен, отправляем в кеш - MyAssets.NotInUse[(int) entry.Type][entry.Domain+':'+entry.Key] = {entry, TIME_BEFORE_UNLOAD_RESOURCE+time(nullptr)}; - } - } - - AsyncContext.LoadedResources.clear(); - } - - // Чистим кеш ресурсов - { - uint64_t now = time(nullptr); - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - std::vector toDelete; - for(auto& [key, value] : MyAssets.NotInUse[type]) { - if(std::get<1>(value) < now) - toDelete.push_back(key); - } - - for(std::string& key : toDelete) - MyAssets.NotInUse[type].erase(MyAssets.NotInUse[type].find(key)); - } - } - // Определения { for(auto& [resId, def] : profile_Node_AddOrChange) { @@ -1140,8 +768,8 @@ void ServerSession::update(GlobalTime gTime, float dTime) { // Оповещение модуля рендера об изменениях ресурсов - std::unordered_map> changedResources; - std::unordered_map> lostResources; + // std::unordered_map> changedResources; + // std::unordered_map> lostResources; // Обработка полученных ресурсов @@ -1215,21 +843,8 @@ void ServerSession::setRenderSession(IRenderSession* session) { } void ServerSession::resetResourceSyncState() { - AM->clearServerBindings(); AsyncContext.AssetsLoading.clear(); - AsyncContext.AlreadyLoading.clear(); - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) - AsyncContext.ResourceWait[type].clear(); - for(auto& vec : ServerIdToDK) - vec.clear(); - std::fill(NextServerId.begin(), NextServerId.end(), 1); - for(auto& vec : ServerIdToDK) - vec.emplace_back(); - AsyncContext.Binds.clear(); - AsyncContext.LoadedResources.clear(); AsyncContext.ThisTickEntry = {}; - AsyncContext.LoadedAssets.lock()->clear(); - AsyncContext.AssetsBinds.lock()->clear(); AsyncContext.TickSequence.lock()->clear(); } @@ -1323,212 +938,68 @@ coro<> ServerSession::rP_Disconnect(Net::AsyncSocket &sock) { } coro<> ServerSession::rP_AssetsBindDK(Net::AsyncSocket &sock) { + UpdateAssetsBindsDK update; + std::string compressed = co_await sock.read(); std::u8string in((const char8_t*) compressed.data(), compressed.size()); std::u8string data = unCompressLinear(in); Net::LinearReader lr(data); uint16_t domainsCount = lr.read(); - std::vector domains; - domains.reserve(domainsCount); + update.Domains.reserve(domainsCount); for(uint16_t i = 0; i < domainsCount; ++i) - domains.push_back(lr.read()); + update.Domains.push_back(lr.read()); for(size_t type = 0; type < static_cast(EnumAssets::MAX_ENUM); ++type) { + update.Keys[type].resize(update.Domains.size()); + uint32_t count = lr.read(); for(uint32_t i = 0; i < count; ++i) { uint16_t domainId = lr.read(); std::string key = lr.read(); - if(domainId >= domains.size()) + if(domainId >= update.Domains.size()) continue; - ResourceId serverId = NextServerId[type]++; - auto& table = ServerIdToDK[type]; - if(table.size() <= serverId) - table.resize(serverId + 1); - table[serverId] = {domains[domainId], std::move(key)}; + update.Keys.at(type).at(domainId).push_back(key); } } - co_return; + AsyncContext.ThisTickEntry.BindsDK.emplace_back(std::move(update)); } coro<> ServerSession::rP_AssetsBindHH(Net::AsyncSocket &sock) { - static std::atomic debugResourceLogCount = 0; - AssetsBindsChange abc; + UpdateAssetsBindsHH update; for(size_t typeIndex = 0; typeIndex < static_cast(EnumAssets::MAX_ENUM); ++typeIndex) { uint32_t count = co_await sock.read(); if(count == 0) continue; - std::vector binds; - binds.reserve(count); - for(size_t iter = 0; iter < count; ++iter) { uint32_t id = co_await sock.read(); - Hash_t hash; + ResourceFile::Hash_t hash; co_await sock.read((std::byte*) hash.data(), hash.size()); std::string headerStr = co_await sock.read(); - std::vector header(headerStr.begin(), headerStr.end()); - auto& table = ServerIdToDK[typeIndex]; - assert(id <= table.size()); - if(id >= table.size()) { - LOG.warn() << "AssetsBindHH without domain/key for id=" << id; - continue; - } - - const auto& [domain, key] = table[id]; - assert(!domain.empty() && !key.empty()); - if(domain.empty() && key.empty()) { - LOG.warn() << "AssetsBindHH missing domain/key for id=" << id; - continue; - } - - EnumAssets type = static_cast(typeIndex); - AssetsManager::BindResult bindResult = AM->bindServerResource( - type, (ResourceId) id, domain, key, hash, header); - - if(bindResult.ReboundFrom) - abc.Lost[typeIndex].push_back(*bindResult.ReboundFrom); - - if(!bindResult.Changed && !bindResult.ReboundFrom) - continue; - - binds.emplace_back(AssetBindEntry{ - .Type = type, - .Id = bindResult.LocalId, - .Domain = domain, - .Key = key, - .Hash = hash, - .Header = std::move(header) - }); - - if(binds.back().Domain == "test" - && (binds.back().Type == EnumAssets::Nodestate - || binds.back().Type == EnumAssets::Model - || binds.back().Type == EnumAssets::Texture)) - { - uint32_t idx = debugResourceLogCount.fetch_add(1); - if(idx < 128) { - LOG.debug() << "Bind asset type=" << assetTypeName(binds.back().Type) - << " id=" << binds.back().Id - << " key=" << binds.back().Domain << ':' << binds.back().Key - << " hash=" << int(binds.back().Hash[0]) << '.' - << int(binds.back().Hash[1]) << '.' - << int(binds.back().Hash[2]) << '.' - << int(binds.back().Hash[3]); - } - } - } - - if(!binds.empty()) { - abc.Binds.append_range(binds); - binds.clear(); + update.HashAndHeaders[typeIndex].emplace_back(id, hash, std::u8string((const char8_t*) headerStr.data(), headerStr.size())); } } - bool hasLost = false; - for(const auto& list : abc.Lost) { - if(!list.empty()) { - hasLost = true; - break; - } - } - - if(!abc.Binds.empty() || hasLost) - AsyncContext.AssetsBinds.lock()->push_back(std::move(abc)); - else - co_return; - - co_return; + AsyncContext.ThisTickEntry.BindsHH.emplace_back(std::move(update)); } coro<> ServerSession::rP_AssetsInitSend(Net::AsyncSocket &sock) { - static std::atomic debugResourceLogCount = 0; uint32_t size = co_await sock.read(); Hash_t hash; co_await sock.read((std::byte*) hash.data(), hash.size()); - std::vector matches; - - for(int typeIndex = 0; typeIndex < (int) EnumAssets::MAX_ENUM; ++typeIndex) { - auto& waitingByDomain = AsyncContext.ResourceWait[typeIndex]; - for(auto iterDomain = waitingByDomain.begin(); iterDomain != waitingByDomain.end(); ) { - auto& entries = iterDomain->second; - for(size_t i = 0; i < entries.size(); ) { - if(entries[i].second == hash) { - EnumAssets type = static_cast(typeIndex); - const std::string& domain = iterDomain->first; - const std::string& key = entries[i].first; - ResourceId localId = AM->getOrCreateLocalId(type, domain, key); - matches.push_back(AssetLoadingEntry{ - .Type = type, - .Id = localId, - .Domain = domain, - .Key = key - }); - entries.erase(entries.begin() + i); - } else { - ++i; - } - } - if(entries.empty()) - iterDomain = waitingByDomain.erase(iterDomain); - else - ++iterDomain; - } - } - - if(matches.empty()) { - LOG.warn() << "AssetsInitSend for unknown hash " << int(hash[0]) << '.' - << int(hash[1]) << '.' << int(hash[2]) << '.' << int(hash[3]); - AsyncContext.AssetsLoading[hash] = AssetLoading{ - .Entries = {}, - .Data = std::u8string(size, '\0'), - .Offset = 0 - }; - co_return; - } - - AssetLoadingEntry first = matches.front(); - - if(first.Domain == "test" - && (first.Type == EnumAssets::Nodestate - || first.Type == EnumAssets::Model - || first.Type == EnumAssets::Texture)) - { - uint32_t idx = debugResourceLogCount.fetch_add(1); - if(idx < 128) { - LOG.debug() << "AssetsInitSend type=" << assetTypeName(first.Type) - << " id=" << first.Id - << " key=" << first.Domain << ':' << first.Key - << " size=" << size - << " matches=" << matches.size(); - } - } - AsyncContext.AssetsLoading[hash] = AssetLoading{ - .Entries = std::move(matches), .Data = std::u8string(size, '\0'), .Offset = 0 }; - LOG.debug() << "Server started sending type=" << assetTypeName(first.Type) - << " id=" << first.Id - << " key=" << first.Domain << ':' - << first.Key - << " hash=" << int(hash[0]) << '.' - << int(hash[1]) << '.' - << int(hash[2]) << '.' - << int(hash[3]) - << " size=" << size; - - co_return; } coro<> ServerSession::rP_AssetsNextSend(Net::AsyncSocket &sock) { - static std::atomic debugResourceLogCount = 0; Hash_t hash; co_await sock.read((std::byte*) hash.data(), hash.size()); uint32_t size = co_await sock.read(); @@ -1549,56 +1020,8 @@ coro<> ServerSession::rP_AssetsNextSend(Net::AsyncSocket &sock) { if(al.Offset != al.Data.size()) co_return; - if(!al.Entries.empty()) { - const size_t resSize = al.Data.size(); - Resource res(std::move(al.Data)); - - for(AssetLoadingEntry& entry : al.Entries) { - if(entry.Domain == "test" - && (entry.Type == EnumAssets::Nodestate - || entry.Type == EnumAssets::Model - || entry.Type == EnumAssets::Texture)) - { - uint32_t idx = debugResourceLogCount.fetch_add(1); - if(idx < 128) { - LOG.debug() << "Resource loaded type=" << assetTypeName(entry.Type) - << " id=" << entry.Id - << " key=" << entry.Domain << ':' << entry.Key - << " size=" << resSize; - } - } - - const EnumAssets type = entry.Type; - const ResourceId id = entry.Id; - const std::string domain = entry.Domain; - const std::string key = entry.Key; - - AsyncContext.LoadedAssets.lock()->emplace_back(AssetEntry{ - .Type = type, - .Id = id, - .Domain = std::move(entry.Domain), - .Key = std::move(entry.Key), - .Res = res, - .Hash = hash - }); - LOG.debug() << "Client received type=" << assetTypeName(type) - << " id=" << id - << " key=" << domain << ':' << key - << " hash=" << int(hash[0]) << '.' - << int(hash[1]) << '.' - << int(hash[2]) << '.' - << int(hash[3]) - << " size=" << resSize; - } - } - + AsyncContext.ThisTickEntry.ReceivedAssets.emplace_back(hash, std::move(al.Data)); AsyncContext.AssetsLoading.erase(AsyncContext.AssetsLoading.find(hash)); - - auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), hash); - if(iter != AsyncContext.AlreadyLoading.end() && *iter == hash) - AsyncContext.AlreadyLoading.erase(iter); - - co_return; } coro<> ServerSession::rP_DefinitionsUpdate(Net::AsyncSocket &sock) { diff --git a/Src/Client/ServerSession.hpp b/Src/Client/ServerSession.hpp index 7651fc0..2c04a63 100644 --- a/Src/Client/ServerSession.hpp +++ b/Src/Client/ServerSession.hpp @@ -69,14 +69,14 @@ private: IRenderSession *RS = nullptr; // Обработчик кеша ресурсов сервера - AssetsManager::Ptr AM; + AssetsManager AM; static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180; struct { // Существующие привязки ресурсов - std::unordered_set ExistBinds[(int) EnumAssets::MAX_ENUM]; + // std::unordered_set ExistBinds[(int) EnumAssets::MAX_ENUM]; // Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд - std::unordered_map> NotInUse[(int) EnumAssets::MAX_ENUM]; + // std::unordered_map> NotInUse[(int) EnumAssets::MAX_ENUM]; } MyAssets; struct AssetLoadingEntry { @@ -87,7 +87,6 @@ private: }; struct AssetLoading { - std::vector Entries; std::u8string Data; size_t Offset = 0; }; @@ -100,10 +99,29 @@ private: std::vector Header; }; - std::array>, (int) EnumAssets::MAX_ENUM> ServerIdToDK; - std::array NextServerId = {}; + struct UpdateAssetsBindsDK { + std::vector Domains; + std::array< + std::vector>, + static_cast(EnumAssets::MAX_ENUM) + > Keys; + }; + + struct UpdateAssetsBindsHH { + std::array< + std::vector>, + static_cast(EnumAssets::MAX_ENUM) + > HashAndHeaders; + }; struct TickData { + // Полученные изменения привязок Domain+Key + std::vector BindsDK; + // Полученные изменения привязок Hash+Header + std::vector BindsHH; + // Полученные с сервера ресурсы + std::vector> ReceivedAssets; + std::vector> Profile_Voxel_AddOrChange; std::vector Profile_Voxel_Lost; std::vector> Profile_Node_AddOrChange; @@ -132,13 +150,6 @@ private: uint32_t Nodes = 0; }; - struct AssetsBindsChange { - // Новые привязки ресурсов - std::vector Binds; - // Потерянные из видимости ресурсы - std::vector Lost[(int) EnumAssets::MAX_ENUM]; - }; - struct { // Сюда обращается ветка, обрабатывающая сокет; run() // Получение ресурсов с сервера @@ -146,22 +157,7 @@ private: // Накопление данных за такт сервера TickData ThisTickEntry; - // Сюда обращается ветка обновления IServerSession, накапливая данные до SyncTick - // Ресурсы, ожидающие либо кеш, либо сервер; используются для сопоставления hash->domain/key - std::unordered_map>> ResourceWait[(int) EnumAssets::MAX_ENUM]; - // Полученные изменения связок в ожидании стадии синхронизации такта - std::vector Binds; - // Подгруженные или принятые меж тактами ресурсы - std::vector LoadedResources; - // Список ресурсов на которые уже был отправлен запрос на загрузку ресурса - std::vector AlreadyLoading; - - // Обменный пункт - // Полученные ресурсы с сервера - TOS::SpinlockObject> LoadedAssets; - // Изменения в наблюдаемых ресурсах - TOS::SpinlockObject> AssetsBinds; // Пакеты обновлений игрового мира TOS::SpinlockObject> TickSequence; } AsyncContext; diff --git a/Src/Common/AssetsPreloader.cpp b/Src/Common/AssetsPreloader.cpp index 0f391e7..de3c160 100644 --- a/Src/Common/AssetsPreloader.cpp +++ b/Src/Common/AssetsPreloader.cpp @@ -64,7 +64,6 @@ AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::checkAndPre ReloadStatus* status ) { assert(idResolver); - assert(onNewResourceParsed); bool expected = false; assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources"); @@ -287,35 +286,36 @@ AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::_checkAndPr for(const auto& [key, res] : keys) { uniqueExistsTypes.insert(res.Id); - if(res.Id >= resourceLinksTyped.size() || !std::get(resourceLinksTyped[res.Id])) + if(res.Id >= resourceLinksTyped.size() || !resourceLinksTyped[res.Id].IsExist) { // Если идентификатора нет в таблице или ресурс не привязан PendingResource resource = buildResource(static_cast(type), domain, key, res); - onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path); + if(onNewResourceParsed) + 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 + } else if(resourceLinksTyped[res.Id].Path != res.Path + || resourceLinksTyped[res.Id].LastWrite != 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) { + if(lastResource.Hash != resource.Hash) { // Хэш изменился // Сообщаем о новом ресурсе - onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path); + if(onNewResourceParsed) + onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path); // Старый хэш более не доступен по этому расположению. - result.HashToPathLost[lastHash].push_back(std::get(resourceLinksTyped[res.Id])); + result.HashToPathLost[lastResource.Hash].push_back(resourceLinksTyped[res.Id].Path); // Новый хеш стал доступен по этому расположению. result.HashToPathNew[resource.Hash].push_back(res.Path); - } else if(std::get(resourceLinksTyped[res.Id]) != res.Path) { + } else if(resourceLinksTyped[res.Id].Path != res.Path) { // Изменился конечный путь. // Хэш более не доступен по этому расположению. - result.HashToPathLost[resource.Hash].push_back(std::get(resourceLinksTyped[res.Id])); + result.HashToPathLost[resource.Hash].push_back(resourceLinksTyped[res.Id].Path); // Хеш теперь доступен по этому расположению. result.HashToPathNew[resource.Hash].push_back(res.Path); } else { @@ -373,13 +373,7 @@ AssetsPreloader::Out_applyResourcesUpdate AssetsPreloader::applyResourcesUpdate( // Увеличиваем размер, если необходимо if(orr.MaxNewSize[type] > ResourceLinks[type].size()) { - std::tuple< - ResourceFile::Hash_t, - ResourceHeader, - fs::file_time_type, - fs::path, - bool - > def{ + ResourceLink def{ ResourceFile::Hash_t{0}, ResourceHeader(), fs::file_time_type(), diff --git a/Src/Common/AssetsPreloader.hpp b/Src/Common/AssetsPreloader.hpp index e11b117..a779a3b 100644 --- a/Src/Common/AssetsPreloader.hpp +++ b/Src/Common/AssetsPreloader.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -175,7 +176,7 @@ public: Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate( const AssetsRegister& instances, const std::function& idResolver, - const std::function& onNewResourceParsed, + const std::function& onNewResourceParsed = nullptr, ReloadStatus* status = nullptr ); @@ -223,6 +224,23 @@ public: return result; } + const auto& getResourceLinks() const { + return ResourceLinks; + } + + struct Out_Resource { + ResourceFile::Hash_t Hash; + fs::path Path; + }; + + std::optional getResource(EnumAssets type, ResourceId id) const { + const auto& rl = ResourceLinks[static_cast(type)]; + if(id >= rl.size() || !rl[id].IsExist) + return std::nullopt; + + return Out_Resource{rl[id].Hash, rl[id].Path}; + } + private: struct ResourceFindInfo { // Путь к архиву (если есть), и путь до ресурса @@ -249,6 +267,15 @@ private: std::atomic _Reloading = false; #endif + struct ResourceLink { + ResourceFile::Hash_t Hash; // Хэш ресурса на диске + /// TODO: клиенту не нужны хедеры + ResourceHeader Header; // Хедер ресурса (со всеми зависимостями) + fs::file_time_type LastWrite; // Время изменения ресурса на диске + fs::path Path; // Путь до ресурса + bool IsExist; + }; + Out_checkAndPrepareResourcesUpdate _checkAndPrepareResourcesUpdate( const AssetsRegister& instances, const std::function& idResolver, @@ -258,15 +285,7 @@ private: // Привязка Id -> Hash + Header + Timestamp + Path std::array< - std::vector< - std::tuple< - ResourceFile::Hash_t, // Хэш ресурса на диске - ResourceHeader, // Хедер ресурса (со всеми зависимостями) - fs::file_time_type, // Время изменения ресурса на диске - fs::path, // Путь до ресурса - bool // IsExist - > - >, + std::vector, static_cast(AssetType::MAX_ENUM) > ResourceLinks; };