#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include "Abstract.hpp" #include "Common/Abstract.hpp" /* Класс отвечает за отслеживание изменений и подгрузки медиаресурсов в указанных директориях. Медиаресурсы, собранные из папки assets или зарегистрированные модами. */ static constexpr const char* EnumAssetsToDirectory(LV::EnumAssets value) { switch(value) { case LV::EnumAssets::Nodestate: return "nodestate"; case LV::EnumAssets::Particle: return "particle"; case LV::EnumAssets::Animation: return "animation"; case LV::EnumAssets::Model: return "model"; case LV::EnumAssets::Texture: return "texture"; case LV::EnumAssets::Sound: return "sound"; case LV::EnumAssets::Font: return "font"; default: break; } assert(!"Неизвестный тип медиаресурса"); return ""; } namespace LV { namespace fs = std::filesystem; using AssetType = EnumAssets; class AssetsPreloader { public: using Ptr = std::shared_ptr; /* Ресурс имеет бинарную часть, из который вырезаны все зависимости. Вторая часть это заголовок, которые всегда динамично передаётся с сервера. В заголовке хранятся зависимости от ресурсов. */ struct MediaResource { std::string Domain, Key; fs::file_time_type Timestamp; // Обезличенный ресурс std::shared_ptr Resource; // Хэш ресурса ResourceFile::Hash_t Hash; // Скомпилированный заголовок std::u8string Header; }; struct PendingResource { uint32_t Id; std::string Key; fs::file_time_type Timestamp; // Обезличенный ресурс std::u8string Resource; // Его хеш ResourceFile::Hash_t Hash; // Заголовок std::u8string Header; }; struct ReloadStatus { /// TODO: callback'и для обновления статусов /// TODO: многоуровневый статус std::vector. Этапы/Шаги/Объекты }; struct AssetsRegister { /* Пути до активных папок assets, соответствую порядку загруженным модам. От последнего мода к первому. Тот файл, что был загружен раньше и будет использоваться */ std::vector Assets; /* У этих ресурсов приоритет выше, если их удастся получить, то использоваться будут именно они Domain -> {key + data} */ std::array< std::unordered_map< std::string, std::unordered_map >, static_cast(AssetType::MAX_ENUM) > Custom; }; public: AssetsPreloader(); ~AssetsPreloader() = default; AssetsPreloader(const AssetsPreloader&) = delete; AssetsPreloader(AssetsPreloader&&) = delete; AssetsPreloader& operator=(const AssetsPreloader&) = delete; AssetsPreloader& operator=(AssetsPreloader&&) = delete; /* Перепроверка изменений ресурсов по дате изменения, пересчёт хешей. Обнаруженные изменения должны быть отправлены всем клиентам. Ресурсы будут обработаны в подходящий формат и сохранены в кеше. Используется в GameServer. ! Одновременно можно работать только один такой вызов. ! Бронирует идентификаторы используя getId(); instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему. idResolver -> функция получения идентификатора по Тип+Домен+Ключ onNewResourceParsed -> Callback на обработку распаршенных ресурсов без заголовков (на стороне сервера хранится в другой сущности, на стороне клиента игнорируется). status -> обратный отклик о процессе обновления ресурсов. ReloadStatus <- новые и потерянные ресурсы. */ 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 = nullptr, ReloadStatus* status = nullptr ); /* Применяет расчитанные изменения. Out_applyResourceUpdate <- Нужно отправить клиентам новые привязки ресурсов id -> hash+header */ struct BindHashHeaderInfo { ResourceId Id; ResourceFile::Hash_t Hash; ResourceHeader Header; }; struct Out_applyResourcesUpdate { std::array< std::vector, static_cast(EnumAssets::MAX_ENUM) > NewOrUpdates; }; Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr); std::array< std::vector, static_cast(EnumAssets::MAX_ENUM) > collectHashBindings() const { std::array< std::vector, static_cast(EnumAssets::MAX_ENUM) > result; for(size_t type = 0; type < static_cast(EnumAssets::MAX_ENUM); ++type) { result[type].reserve(ResourceLinks[type].size()); ResourceId counter = 0; for(const auto& [hash, header, _1, _2, _3] : ResourceLinks[type]) { ResourceId id = counter++; result[type].emplace_back(id, hash, header); } } 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 { // Путь к архиву (если есть), и путь до ресурса fs::path ArchivePath, Path; // Время изменения файла fs::file_time_type Timestamp; // Идентификатор ресурса ResourceId Id; }; struct HashHasher { std::size_t operator()(const ResourceFile::Hash_t& hash) const noexcept { std::size_t v = 14695981039346656037ULL; for (const auto& byte : hash) { v ^= static_cast(byte); v *= 1099511628211ULL; } return v; } }; #ifndef NDEBUG // Текущее состояние reloadResources 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, const std::function& onNewResourceParsed, ReloadStatus& status ); // Привязка Id -> Hash + Header + Timestamp + Path std::array< std::vector, static_cast(AssetType::MAX_ENUM) > ResourceLinks; }; }