Переработка менеджера ресурсов на стороне клиентов
This commit is contained in:
@@ -158,15 +158,6 @@ struct DefNode_t {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AssetEntry {
|
|
||||||
EnumAssets Type;
|
|
||||||
ResourceId Id;
|
|
||||||
std::string Domain, Key;
|
|
||||||
Resource Res;
|
|
||||||
Hash_t Hash = {};
|
|
||||||
std::vector<uint8_t> Dependencies;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Интерфейс обработчика сессии с сервером.
|
Интерфейс обработчика сессии с сервером.
|
||||||
|
|
||||||
@@ -181,7 +172,7 @@ public:
|
|||||||
bool DebugLogPackets = false;
|
bool DebugLogPackets = false;
|
||||||
|
|
||||||
// Используемые двоичные ресурсы
|
// Используемые двоичные ресурсы
|
||||||
std::unordered_map<EnumAssets, std::unordered_map<ResourceId, AssetEntry>> Assets;
|
// std::unordered_map<EnumAssets, std::unordered_map<ResourceId, AssetEntry>> Assets;
|
||||||
|
|
||||||
// Используемые профили контента
|
// Используемые профили контента
|
||||||
struct {
|
struct {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -9,285 +10,365 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "Client/AssetsCacheManager.hpp"
|
#include "Client/AssetsCacheManager.hpp"
|
||||||
#include "Client/AssetsHeaderCodec.hpp"
|
#include "Client/AssetsHeaderCodec.hpp"
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
|
#include "Common/IdProvider.hpp"
|
||||||
|
#include "Common/AssetsPreloader.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
|
#include "boost/asio/io_context.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
class AssetsManager {
|
class AssetsManager : public IdProvider<EnumAssets> {
|
||||||
public:
|
public:
|
||||||
using Ptr = std::shared_ptr<AssetsManager>;
|
struct ResourceUpdates {
|
||||||
using AssetType = EnumAssets;
|
/// TODO: Добавить анимацию из меты
|
||||||
using AssetId = ResourceId;
|
std::vector<std::tuple<ResourceId, uint16_t, uint16_t, std::vector<uint32_t>>> Textures;
|
||||||
|
|
||||||
// Ключ запроса ресурса (идентификация + хеш для поиска источника).
|
|
||||||
struct ResourceKey {
|
|
||||||
// Хеш ресурса, используемый для поиска в источниках и кэше.
|
|
||||||
Hash_t Hash{};
|
|
||||||
// Тип ресурса (модель, текстура и т.д.).
|
|
||||||
AssetType Type{};
|
|
||||||
// Домен ресурса.
|
|
||||||
std::string Domain;
|
|
||||||
// Ключ ресурса внутри домена.
|
|
||||||
std::string Key;
|
|
||||||
// Идентификатор ресурса на стороне клиента/локальный.
|
|
||||||
AssetId Id = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Информация о биндинге серверного ресурса на локальный id.
|
public:
|
||||||
struct BindInfo {
|
AssetsManager(asio::io_context& ioc, fs::path cachePath)
|
||||||
// Тип ресурса.
|
: Cache(AssetsCacheManager::Create(ioc, cachePath)) {
|
||||||
AssetType Type{};
|
|
||||||
// Локальный идентификатор.
|
|
||||||
AssetId LocalId = 0;
|
|
||||||
// Домен ресурса.
|
|
||||||
std::string Domain;
|
|
||||||
// Ключ ресурса.
|
|
||||||
std::string Key;
|
|
||||||
// Хеш ресурса.
|
|
||||||
Hash_t Hash{};
|
|
||||||
// Бинарный заголовок с зависимостями.
|
|
||||||
std::vector<uint8_t> Header;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Результат биндинга ресурса сервера.
|
|
||||||
struct BindResult {
|
|
||||||
// Итоговый локальный идентификатор.
|
|
||||||
AssetId LocalId = 0;
|
|
||||||
// Признак изменения бинда (хеш/заголовок).
|
|
||||||
bool Changed = false;
|
|
||||||
// Признак новой привязки.
|
|
||||||
bool NewBinding = false;
|
|
||||||
// Идентификатор, от которого произошёл ребинд (если был).
|
|
||||||
std::optional<AssetId> ReboundFrom;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Регистрация набора ресурспаков.
|
|
||||||
struct PackRegister {
|
|
||||||
// Пути до паков (директории/архивы).
|
|
||||||
std::vector<fs::path> 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<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> ChangeOrAdd;
|
|
||||||
// Потерянные ресурсы по типам.
|
|
||||||
std::array<std::vector<AssetId>, static_cast<size_t>(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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Пересканировать ресурспаки и вернуть изменившиеся/утраченные ресурсы.
|
// Ручные обновления
|
||||||
PackReloadResult reloadPacks(const PackRegister& reg);
|
struct Out_checkAndPrepareResourcesUpdate {
|
||||||
|
AssetsPreloader::Out_checkAndPrepareResourcesUpdate RP, ES;
|
||||||
|
|
||||||
// Связать серверный ресурс с локальным id и записать метаданные.
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> Files;
|
||||||
BindResult bindServerResource(AssetType type, AssetId serverId, std::string domain, std::string key,
|
};
|
||||||
const Hash_t& hash, std::vector<uint8_t> header);
|
|
||||||
// Отвязать серверный id и вернуть актуальный локальный id (если был).
|
|
||||||
std::optional<AssetId> unbindServerResource(AssetType type, AssetId serverId);
|
|
||||||
// Сбросить все серверные бинды.
|
|
||||||
void clearServerBindings();
|
|
||||||
|
|
||||||
// Получить данные бинда по локальному id.
|
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||||
const BindInfo* getBind(AssetType type, AssetId localId) const;
|
const std::vector<fs::path>& resourcePacks,
|
||||||
|
const std::vector<fs::path>& extraSources
|
||||||
|
) {
|
||||||
|
Out_checkAndPrepareResourcesUpdate result;
|
||||||
|
|
||||||
// Перебиндить хедер, заменив id зависимостей.
|
result.RP = ResourcePacks.checkAndPrepareResourcesUpdate(
|
||||||
std::vector<uint8_t> rebindHeader(AssetType type, const std::vector<uint8_t>& header, bool serverIds = true);
|
AssetsPreloader::AssetsRegister{resourcePacks},
|
||||||
// Распарсить хедер ресурса.
|
[&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId {
|
||||||
static std::optional<ParsedHeader> parseHeader(AssetType type, const std::vector<uint8_t>& header);
|
return getId(type, domain, key);
|
||||||
|
},
|
||||||
|
[&](std::u8string&& data, ResourceFile::Hash_t hash, fs::path path) {
|
||||||
|
result.Files.emplace(hash, std::move(data));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Протолкнуть новые ресурсы в память и кэш.
|
result.ES = ExtraSource.checkAndPrepareResourcesUpdate(
|
||||||
void pushResources(std::vector<Resource> resources);
|
AssetsPreloader::AssetsRegister{resourcePacks},
|
||||||
|
[&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId {
|
||||||
|
return getId(type, domain, key);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Поставить запросы чтения ресурсов.
|
return result;
|
||||||
void pushReads(std::vector<ResourceKey> reads);
|
}
|
||||||
// Получить готовые результаты чтения.
|
|
||||||
std::vector<std::pair<ResourceKey, std::optional<Resource>>> pullReads();
|
|
||||||
// Продвинуть асинхронные источники (кэш).
|
|
||||||
void tickSources();
|
|
||||||
|
|
||||||
// Получить или создать локальный id по домену/ключу.
|
struct Out_applyResourcesUpdate {
|
||||||
AssetId getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key);
|
|
||||||
// Получить локальный id по серверному id (если есть).
|
};
|
||||||
std::optional<AssetId> getLocalIdFromServer(AssetType type, AssetId serverId) const;
|
|
||||||
|
Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) {
|
||||||
|
Out_applyResourcesUpdate result;
|
||||||
|
|
||||||
|
ResourcePacks.applyResourcesUpdate(orr.RP);
|
||||||
|
ExtraSource.applyResourcesUpdate(orr.ES);
|
||||||
|
|
||||||
|
std::unordered_set<ResourceFile::Hash_t> needHashes;
|
||||||
|
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
|
for(const auto& res : orr.RP.ResourceUpdates[type]) {
|
||||||
|
// Помечаем ресурс для обновления
|
||||||
|
PendingUpdateFromAsync[type].push_back(std::get<ResourceId>(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<ResourceFile::Hash_t>(hh[id]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
for(const auto& [hash, data] : orr.Files) {
|
||||||
|
WaitingHashes.insert(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& hash : WaitingHashes)
|
||||||
|
needHashes.erase(hash);
|
||||||
|
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> toDisk;
|
||||||
|
std::vector<ResourceFile::Hash_t> 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<std::string>& domains,
|
||||||
|
const std::array<
|
||||||
|
std::vector<std::vector<std::string>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
>& keys
|
||||||
|
) {
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(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<std::tuple<ResourceId, ResourceFile::Hash_t, ResourceHeader>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
>&& hash_and_headers
|
||||||
|
) {
|
||||||
|
std::array<
|
||||||
|
std::vector<std::tuple<ResourceId, ResourceFile::Hash_t, ResourceHeader>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> hah = std::move(hash_and_headers);
|
||||||
|
|
||||||
|
std::unordered_set<ResourceFile::Hash_t> needHashes;
|
||||||
|
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(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<AssetsPreloader::Out_Resource> 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<std::tuple<ResourceFile::Hash_t, fs::path>> toDisk;
|
||||||
|
std::vector<ResourceFile::Hash_t> 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<std::tuple<ResourceFile::Hash_t, std::u8string>> &&resources
|
||||||
|
) {
|
||||||
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> files;
|
||||||
|
std::vector<Resource> 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<ResourceFile::Hash_t> pollNeededResources() {
|
||||||
|
return std::move(NeedToRequestFromServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить изменённые ресурсы (для передачи другим модулям).
|
||||||
|
ResourceUpdates pullResourceUpdates() {
|
||||||
|
return std::move(RU);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tick() {
|
||||||
|
// Проверим кеш
|
||||||
|
std::vector<std::pair<Hash_t, std::optional<Resource>>> resources = Cache->pullReads();
|
||||||
|
if(!resources.empty()) {
|
||||||
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> 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<ResourceFile::Hash_t, std::u8string> 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_t>(size));
|
||||||
|
if(size > 0) {
|
||||||
|
file.read(reinterpret_cast<char*>(data.data()), size);
|
||||||
|
if(!file)
|
||||||
|
data.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
files.emplace(hash, std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
NeedToReadFromDisk.clear();
|
||||||
|
_onHashLoad(files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Связка домен/ключ для локального id.
|
// Менеджеры учёта дисковых ресурсов
|
||||||
struct DomainKey {
|
AssetsPreloader
|
||||||
// Домен ресурса.
|
// В приоритете ищутся ресурсы из ресурспаков по Domain+Key.
|
||||||
std::string Domain;
|
ResourcePacks,
|
||||||
// Ключ ресурса.
|
/*
|
||||||
std::string Key;
|
Дополнительные источники ресурсов.
|
||||||
// Признак валидности записи.
|
Используется для поиска ресурса по хешу от сервера (может стоит тот же мод с совпадающими ресурсами),
|
||||||
bool Known = false;
|
или для временной подгрузки ресурса по Domain+Key пока ресурс не был получен с сервера.
|
||||||
};
|
*/
|
||||||
|
ExtraSource;
|
||||||
|
|
||||||
using IdTable = std::unordered_map<
|
|
||||||
std::string,
|
|
||||||
std::unordered_map<std::string, AssetId, detail::TSVHash, detail::TSVEq>,
|
|
||||||
detail::TSVHash,
|
|
||||||
detail::TSVEq>;
|
|
||||||
|
|
||||||
using PackTable = std::unordered_map<
|
|
||||||
std::string,
|
|
||||||
std::unordered_map<std::string, PackResource, detail::TSVHash, detail::TSVEq>,
|
|
||||||
detail::TSVHash,
|
|
||||||
detail::TSVEq>;
|
|
||||||
|
|
||||||
struct PerType {
|
|
||||||
// Таблица домен/ключ -> локальный id.
|
|
||||||
IdTable DKToLocal;
|
|
||||||
// Таблица локальный id -> домен/ключ.
|
|
||||||
std::vector<DomainKey> LocalToDK;
|
|
||||||
// Union-Find родительские ссылки для ребиндов.
|
|
||||||
std::vector<AssetId> LocalParent;
|
|
||||||
// Таблица серверный id -> локальный id.
|
|
||||||
std::vector<AssetId> ServerToLocal;
|
|
||||||
// Бинды с сервером по локальному id.
|
|
||||||
std::vector<std::optional<BindInfo>> BindInfos;
|
|
||||||
// Ресурсы, собранные из паков.
|
|
||||||
PackTable PackResources;
|
|
||||||
// Следующий локальный id.
|
|
||||||
AssetId NextLocalId = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class SourceStatus {
|
|
||||||
Hit,
|
|
||||||
Miss,
|
|
||||||
Pending
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SourceResult {
|
|
||||||
// Статус ответа источника.
|
|
||||||
SourceStatus Status = SourceStatus::Miss;
|
|
||||||
// Значение ресурса, если найден.
|
|
||||||
std::optional<Resource> Value;
|
|
||||||
// Индекс источника.
|
|
||||||
size_t SourceIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SourceReady {
|
|
||||||
// Хеш готового ресурса.
|
|
||||||
Hash_t Hash{};
|
|
||||||
// Значение ресурса, если найден.
|
|
||||||
std::optional<Resource> Value;
|
|
||||||
// Индекс источника.
|
|
||||||
size_t SourceIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IResourceSource {
|
|
||||||
public:
|
|
||||||
virtual ~IResourceSource() = default;
|
|
||||||
// Попытка получить ресурс синхронно.
|
|
||||||
virtual SourceResult tryGet(const ResourceKey& key) = 0;
|
|
||||||
// Забрать готовые результаты асинхронных запросов.
|
|
||||||
virtual void collectReady(std::vector<SourceReady>& out) = 0;
|
|
||||||
// Признак асинхронности источника.
|
|
||||||
virtual bool isAsync() const = 0;
|
|
||||||
// Запустить асинхронные запросы по хешам.
|
|
||||||
virtual void startPending(std::vector<Hash_t> hashes) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SourceEntry {
|
|
||||||
// Экземпляр источника.
|
|
||||||
std::unique_ptr<IResourceSource> 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<AssetId>* reboundFrom);
|
|
||||||
|
|
||||||
// Найти ресурс в паке по домену/ключу.
|
|
||||||
std::optional<PackResource> findPackResource(AssetType type, std::string_view domain, std::string_view key) const;
|
|
||||||
|
|
||||||
// Логгер подсистемы.
|
|
||||||
Logger LOG = "Client>AssetsManager";
|
|
||||||
// Менеджер файлового кэша.
|
// Менеджер файлового кэша.
|
||||||
AssetsCacheManager::Ptr Cache;
|
AssetsCacheManager::Ptr Cache;
|
||||||
|
|
||||||
// Таблицы данных по каждому типу ресурсов.
|
// Указатели на доступные ресурсы
|
||||||
std::array<PerType, static_cast<size_t>(AssetType::MAX_ENUM)> Types;
|
std::unordered_map<ResourceFile::Hash_t, std::vector<fs::path>> HashToPath;
|
||||||
|
|
||||||
// Список источников ресурсов.
|
// Таблица релинковки ассетов с идентификаторов сервера на клиентские.
|
||||||
std::vector<SourceEntry> Sources;
|
std::array<
|
||||||
// Кэш попаданий по хешу.
|
std::vector<ResourceId>,
|
||||||
std::unordered_map<Hash_t, SourceCacheEntry> SourceCacheByHash;
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
// Индекс источника паков.
|
> ServerToClientMap;
|
||||||
size_t PackSourceIndex = 0;
|
|
||||||
// Индекс памяти (RAM) как источника.
|
|
||||||
size_t MemorySourceIndex = 0;
|
|
||||||
// Индекс файлового кэша.
|
|
||||||
size_t CacheSourceIndex = 0;
|
|
||||||
|
|
||||||
// Ресурсы в памяти по хешу.
|
// Таблица серверных привязок HH (id клиентские)
|
||||||
std::unordered_map<Hash_t, Resource> MemoryResourcesByHash;
|
std::array<
|
||||||
// Ожидающие запросы, сгруппированные по хешу.
|
std::vector<std::tuple<ResourceFile::Hash_t, ResourceHeader>>,
|
||||||
std::unordered_map<Hash_t, std::vector<ResourceKey>> PendingReadsByHash;
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
// Готовые ответы на чтение.
|
> ServerIdToHH;
|
||||||
std::vector<std::pair<ResourceKey, std::optional<Resource>>> ReadyReads;
|
|
||||||
|
// Ресурсы в ожидании данных по хешу для обновления (с диска, кеша, сервера).
|
||||||
|
std::array<
|
||||||
|
std::vector<ResourceId>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> PendingUpdateFromAsync;
|
||||||
|
|
||||||
|
// Хеши, для которых где-то висит задача на загрузку.
|
||||||
|
std::unordered_set<ResourceFile::Hash_t> WaitingHashes;
|
||||||
|
|
||||||
|
// Хеши, которые необходимо запросить с сервера.
|
||||||
|
std::vector<ResourceFile::Hash_t> NeedToRequestFromServer;
|
||||||
|
|
||||||
|
// Ресурсы, которые нужно считать с диска
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> NeedToReadFromDisk;
|
||||||
|
|
||||||
|
// Обновлённые ресурсы
|
||||||
|
ResourceUpdates RU;
|
||||||
|
|
||||||
|
// Когда данные были получены с диска, кеша или сервера
|
||||||
|
void _onHashLoad(const std::unordered_map<ResourceFile::Hash_t, std::u8string>& files) {
|
||||||
|
/// TODO: скомпилировать ресурсы
|
||||||
|
|
||||||
|
for(const auto& [hash, res] : files)
|
||||||
|
WaitingHashes.erase(hash);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace LV::Client
|
} // namespace LV::Client
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const char* toClientPacketName(ToClient type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket>&& socket)
|
ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket>&& socket)
|
||||||
: IAsyncDestructible(ioc), Socket(std::move(socket)) //, NetInputPackets(1024)
|
: IAsyncDestructible(ioc), Socket(std::move(socket)), AM(ioc, "Cache")
|
||||||
{
|
{
|
||||||
assert(Socket.get());
|
assert(Socket.get());
|
||||||
|
|
||||||
@@ -71,12 +71,7 @@ ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSo
|
|||||||
Profiles.DefNode[3] = {3};
|
Profiles.DefNode[3] = {3};
|
||||||
Profiles.DefNode[4] = {4};
|
Profiles.DefNode[4] = {4};
|
||||||
|
|
||||||
std::fill(NextServerId.begin(), NextServerId.end(), 0);
|
|
||||||
for(auto& vec : ServerIdToDK)
|
|
||||||
vec.emplace_back();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AM = AssetsManager::Create(ioc, "Cache");
|
|
||||||
asio::co_spawn(ioc, run(AUC.use()), asio::detached);
|
asio::co_spawn(ioc, run(AUC.use()), asio::detached);
|
||||||
// TODO: добавить оптимизацию для подключения клиента к внутреннему серверу
|
// TODO: добавить оптимизацию для подключения клиента к внутреннему серверу
|
||||||
} catch(const std::exception &exc) {
|
} catch(const std::exception &exc) {
|
||||||
@@ -312,287 +307,28 @@ void ServerSession::onJoystick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ServerSession::update(GlobalTime gTime, float dTime) {
|
void ServerSession::update(GlobalTime gTime, float dTime) {
|
||||||
// Если были получены ресурсы, отправим их на запись в кеш
|
// Если AssetsManager запрашивает ресурсы с сервера
|
||||||
if(!AsyncContext.LoadedAssets.get_read().empty()) {
|
|
||||||
std::vector<AssetEntry> assets = std::move(*AsyncContext.LoadedAssets.lock());
|
|
||||||
std::vector<Resource> 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
|
|
||||||
{
|
{
|
||||||
static std::atomic<uint32_t> debugAssetReadLogCount = 0;
|
std::vector<Hash_t> needRequest = AM.pullNeededResources();
|
||||||
std::vector<std::pair<AssetsManager::ResourceKey, std::optional<Resource>>> resources = AM->pullReads();
|
Net::Packet pack;
|
||||||
std::vector<Hash_t> needRequest;
|
std::vector<Net::Packet> packets;
|
||||||
|
|
||||||
for(auto& [key, res] : resources) {
|
auto check = [&]() {
|
||||||
bool cacheHit = false;
|
if(pack.size() > 64000)
|
||||||
Hash_t actualHash = {};
|
packets.emplace_back(std::move(pack));
|
||||||
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<std::string, Hash_t>& entry) {
|
|
||||||
return entry.first == key.Key && entry.second == key.Hash;
|
|
||||||
}),
|
|
||||||
entries.end());
|
|
||||||
if(entries.empty())
|
|
||||||
waitingByDomain.erase(iterDomain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(key.Domain == "test"
|
pack << (uint8_t) ToServer::L1::System << (uint8_t) ToServer::L2System::ResourceRequest;
|
||||||
&& (key.Type == EnumAssets::Nodestate
|
pack << (uint16_t) needRequest.size();
|
||||||
|| key.Type == EnumAssets::Model
|
for(const Hash_t& hash : needRequest) {
|
||||||
|| key.Type == EnumAssets::Texture))
|
pack.write((const std::byte*) hash.data(), 32);
|
||||||
{
|
check();
|
||||||
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<uint8_t> 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!needRequest.empty()) {
|
if(pack.size())
|
||||||
assert(needRequest.size() < (1 << 16));
|
packets.emplace_back(std::move(pack));
|
||||||
|
|
||||||
uint32_t idx = debugAssetReadLogCount.fetch_add(1);
|
Socket->pushPackets(&packets);
|
||||||
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<AssetsBindsChange> 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<AssetsManager::ResourceKey> 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<uint32_t> 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!AsyncContext.TickSequence.get_read().empty()) {
|
if(!AsyncContext.TickSequence.get_read().empty()) {
|
||||||
@@ -624,6 +360,20 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
for(TickData& data : ticks) {
|
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) {
|
for(auto& [id, profile] : data.Profile_Voxel_AddOrChange) {
|
||||||
auto iter = std::lower_bound(profile_Voxel_Lost.begin(), profile_Voxel_Lost.end(), id);
|
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();
|
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<uint8_t> 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<std::string> 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) {
|
for(auto& [resId, def] : profile_Node_AddOrChange) {
|
||||||
@@ -1140,8 +768,8 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
|
|
||||||
|
|
||||||
// Оповещение модуля рендера об изменениях ресурсов
|
// Оповещение модуля рендера об изменениях ресурсов
|
||||||
std::unordered_map<EnumAssets, std::unordered_map<ResourceId, AssetEntry>> changedResources;
|
// std::unordered_map<EnumAssets, std::unordered_map<ResourceId, AssetEntry>> changedResources;
|
||||||
std::unordered_map<EnumAssets, std::unordered_set<ResourceId>> lostResources;
|
// std::unordered_map<EnumAssets, std::unordered_set<ResourceId>> lostResources;
|
||||||
|
|
||||||
// Обработка полученных ресурсов
|
// Обработка полученных ресурсов
|
||||||
|
|
||||||
@@ -1215,21 +843,8 @@ void ServerSession::setRenderSession(IRenderSession* session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ServerSession::resetResourceSyncState() {
|
void ServerSession::resetResourceSyncState() {
|
||||||
AM->clearServerBindings();
|
|
||||||
AsyncContext.AssetsLoading.clear();
|
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.ThisTickEntry = {};
|
||||||
AsyncContext.LoadedAssets.lock()->clear();
|
|
||||||
AsyncContext.AssetsBinds.lock()->clear();
|
|
||||||
AsyncContext.TickSequence.lock()->clear();
|
AsyncContext.TickSequence.lock()->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1323,212 +938,68 @@ coro<> ServerSession::rP_Disconnect(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
coro<> ServerSession::rP_AssetsBindDK(Net::AsyncSocket &sock) {
|
coro<> ServerSession::rP_AssetsBindDK(Net::AsyncSocket &sock) {
|
||||||
|
UpdateAssetsBindsDK update;
|
||||||
|
|
||||||
std::string compressed = co_await sock.read<std::string>();
|
std::string compressed = co_await sock.read<std::string>();
|
||||||
std::u8string in((const char8_t*) compressed.data(), compressed.size());
|
std::u8string in((const char8_t*) compressed.data(), compressed.size());
|
||||||
std::u8string data = unCompressLinear(in);
|
std::u8string data = unCompressLinear(in);
|
||||||
Net::LinearReader lr(data);
|
Net::LinearReader lr(data);
|
||||||
|
|
||||||
uint16_t domainsCount = lr.read<uint16_t>();
|
uint16_t domainsCount = lr.read<uint16_t>();
|
||||||
std::vector<std::string> domains;
|
update.Domains.reserve(domainsCount);
|
||||||
domains.reserve(domainsCount);
|
|
||||||
for(uint16_t i = 0; i < domainsCount; ++i)
|
for(uint16_t i = 0; i < domainsCount; ++i)
|
||||||
domains.push_back(lr.read<std::string>());
|
update.Domains.push_back(lr.read<std::string>());
|
||||||
|
|
||||||
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
|
update.Keys[type].resize(update.Domains.size());
|
||||||
|
|
||||||
uint32_t count = lr.read<uint32_t>();
|
uint32_t count = lr.read<uint32_t>();
|
||||||
for(uint32_t i = 0; i < count; ++i) {
|
for(uint32_t i = 0; i < count; ++i) {
|
||||||
uint16_t domainId = lr.read<uint16_t>();
|
uint16_t domainId = lr.read<uint16_t>();
|
||||||
std::string key = lr.read<std::string>();
|
std::string key = lr.read<std::string>();
|
||||||
if(domainId >= domains.size())
|
if(domainId >= update.Domains.size())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ResourceId serverId = NextServerId[type]++;
|
update.Keys.at(type).at(domainId).push_back(key);
|
||||||
auto& table = ServerIdToDK[type];
|
|
||||||
if(table.size() <= serverId)
|
|
||||||
table.resize(serverId + 1);
|
|
||||||
table[serverId] = {domains[domainId], std::move(key)};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
co_return;
|
AsyncContext.ThisTickEntry.BindsDK.emplace_back(std::move(update));
|
||||||
}
|
}
|
||||||
|
|
||||||
coro<> ServerSession::rP_AssetsBindHH(Net::AsyncSocket &sock) {
|
coro<> ServerSession::rP_AssetsBindHH(Net::AsyncSocket &sock) {
|
||||||
static std::atomic<uint32_t> debugResourceLogCount = 0;
|
UpdateAssetsBindsHH update;
|
||||||
AssetsBindsChange abc;
|
|
||||||
|
|
||||||
for(size_t typeIndex = 0; typeIndex < static_cast<size_t>(EnumAssets::MAX_ENUM); ++typeIndex) {
|
for(size_t typeIndex = 0; typeIndex < static_cast<size_t>(EnumAssets::MAX_ENUM); ++typeIndex) {
|
||||||
uint32_t count = co_await sock.read<uint32_t>();
|
uint32_t count = co_await sock.read<uint32_t>();
|
||||||
if(count == 0)
|
if(count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::vector<AssetBindEntry> binds;
|
|
||||||
binds.reserve(count);
|
|
||||||
|
|
||||||
for(size_t iter = 0; iter < count; ++iter) {
|
for(size_t iter = 0; iter < count; ++iter) {
|
||||||
uint32_t id = co_await sock.read<uint32_t>();
|
uint32_t id = co_await sock.read<uint32_t>();
|
||||||
Hash_t hash;
|
ResourceFile::Hash_t hash;
|
||||||
co_await sock.read((std::byte*) hash.data(), hash.size());
|
co_await sock.read((std::byte*) hash.data(), hash.size());
|
||||||
std::string headerStr = co_await sock.read<std::string>();
|
std::string headerStr = co_await sock.read<std::string>();
|
||||||
std::vector<uint8_t> header(headerStr.begin(), headerStr.end());
|
|
||||||
|
|
||||||
auto& table = ServerIdToDK[typeIndex];
|
update.HashAndHeaders[typeIndex].emplace_back(id, hash, std::u8string((const char8_t*) headerStr.data(), headerStr.size()));
|
||||||
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<EnumAssets>(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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasLost = false;
|
AsyncContext.ThisTickEntry.BindsHH.emplace_back(std::move(update));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
coro<> ServerSession::rP_AssetsInitSend(Net::AsyncSocket &sock) {
|
coro<> ServerSession::rP_AssetsInitSend(Net::AsyncSocket &sock) {
|
||||||
static std::atomic<uint32_t> debugResourceLogCount = 0;
|
|
||||||
uint32_t size = co_await sock.read<uint32_t>();
|
uint32_t size = co_await sock.read<uint32_t>();
|
||||||
Hash_t hash;
|
Hash_t hash;
|
||||||
co_await sock.read((std::byte*) hash.data(), hash.size());
|
co_await sock.read((std::byte*) hash.data(), hash.size());
|
||||||
|
|
||||||
std::vector<AssetLoadingEntry> 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<EnumAssets>(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{
|
AsyncContext.AssetsLoading[hash] = AssetLoading{
|
||||||
.Entries = std::move(matches),
|
|
||||||
.Data = std::u8string(size, '\0'),
|
.Data = std::u8string(size, '\0'),
|
||||||
.Offset = 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) {
|
coro<> ServerSession::rP_AssetsNextSend(Net::AsyncSocket &sock) {
|
||||||
static std::atomic<uint32_t> debugResourceLogCount = 0;
|
|
||||||
Hash_t hash;
|
Hash_t hash;
|
||||||
co_await sock.read((std::byte*) hash.data(), hash.size());
|
co_await sock.read((std::byte*) hash.data(), hash.size());
|
||||||
uint32_t size = co_await sock.read<uint32_t>();
|
uint32_t size = co_await sock.read<uint32_t>();
|
||||||
@@ -1549,56 +1020,8 @@ coro<> ServerSession::rP_AssetsNextSend(Net::AsyncSocket &sock) {
|
|||||||
if(al.Offset != al.Data.size())
|
if(al.Offset != al.Data.size())
|
||||||
co_return;
|
co_return;
|
||||||
|
|
||||||
if(!al.Entries.empty()) {
|
AsyncContext.ThisTickEntry.ReceivedAssets.emplace_back(hash, std::move(al.Data));
|
||||||
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.AssetsLoading.erase(AsyncContext.AssetsLoading.find(hash));
|
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) {
|
coro<> ServerSession::rP_DefinitionsUpdate(Net::AsyncSocket &sock) {
|
||||||
|
|||||||
@@ -69,14 +69,14 @@ private:
|
|||||||
IRenderSession *RS = nullptr;
|
IRenderSession *RS = nullptr;
|
||||||
|
|
||||||
// Обработчик кеша ресурсов сервера
|
// Обработчик кеша ресурсов сервера
|
||||||
AssetsManager::Ptr AM;
|
AssetsManager AM;
|
||||||
|
|
||||||
static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180;
|
static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180;
|
||||||
struct {
|
struct {
|
||||||
// Существующие привязки ресурсов
|
// Существующие привязки ресурсов
|
||||||
std::unordered_set<ResourceId> ExistBinds[(int) EnumAssets::MAX_ENUM];
|
// std::unordered_set<ResourceId> ExistBinds[(int) EnumAssets::MAX_ENUM];
|
||||||
// Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд
|
// Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд
|
||||||
std::unordered_map<std::string, std::pair<AssetEntry, uint64_t>> NotInUse[(int) EnumAssets::MAX_ENUM];
|
// std::unordered_map<std::string, std::pair<AssetEntry, uint64_t>> NotInUse[(int) EnumAssets::MAX_ENUM];
|
||||||
} MyAssets;
|
} MyAssets;
|
||||||
|
|
||||||
struct AssetLoadingEntry {
|
struct AssetLoadingEntry {
|
||||||
@@ -87,7 +87,6 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AssetLoading {
|
struct AssetLoading {
|
||||||
std::vector<AssetLoadingEntry> Entries;
|
|
||||||
std::u8string Data;
|
std::u8string Data;
|
||||||
size_t Offset = 0;
|
size_t Offset = 0;
|
||||||
};
|
};
|
||||||
@@ -100,10 +99,29 @@ private:
|
|||||||
std::vector<uint8_t> Header;
|
std::vector<uint8_t> Header;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::array<std::vector<std::pair<std::string, std::string>>, (int) EnumAssets::MAX_ENUM> ServerIdToDK;
|
struct UpdateAssetsBindsDK {
|
||||||
std::array<ResourceId, (int) EnumAssets::MAX_ENUM> NextServerId = {};
|
std::vector<std::string> Domains;
|
||||||
|
std::array<
|
||||||
|
std::vector<std::vector<std::string>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> Keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UpdateAssetsBindsHH {
|
||||||
|
std::array<
|
||||||
|
std::vector<std::tuple<ResourceId, ResourceFile::Hash_t, ResourceHeader>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> HashAndHeaders;
|
||||||
|
};
|
||||||
|
|
||||||
struct TickData {
|
struct TickData {
|
||||||
|
// Полученные изменения привязок Domain+Key
|
||||||
|
std::vector<UpdateAssetsBindsDK> BindsDK;
|
||||||
|
// Полученные изменения привязок Hash+Header
|
||||||
|
std::vector<UpdateAssetsBindsHH> BindsHH;
|
||||||
|
// Полученные с сервера ресурсы
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, std::u8string>> ReceivedAssets;
|
||||||
|
|
||||||
std::vector<std::pair<DefVoxelId, void*>> Profile_Voxel_AddOrChange;
|
std::vector<std::pair<DefVoxelId, void*>> Profile_Voxel_AddOrChange;
|
||||||
std::vector<DefVoxelId> Profile_Voxel_Lost;
|
std::vector<DefVoxelId> Profile_Voxel_Lost;
|
||||||
std::vector<std::pair<DefNodeId, DefNode_t>> Profile_Node_AddOrChange;
|
std::vector<std::pair<DefNodeId, DefNode_t>> Profile_Node_AddOrChange;
|
||||||
@@ -132,13 +150,6 @@ private:
|
|||||||
uint32_t Nodes = 0;
|
uint32_t Nodes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AssetsBindsChange {
|
|
||||||
// Новые привязки ресурсов
|
|
||||||
std::vector<AssetBindEntry> Binds;
|
|
||||||
// Потерянные из видимости ресурсы
|
|
||||||
std::vector<ResourceId> Lost[(int) EnumAssets::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
// Сюда обращается ветка, обрабатывающая сокет; run()
|
// Сюда обращается ветка, обрабатывающая сокет; run()
|
||||||
// Получение ресурсов с сервера
|
// Получение ресурсов с сервера
|
||||||
@@ -146,22 +157,7 @@ private:
|
|||||||
// Накопление данных за такт сервера
|
// Накопление данных за такт сервера
|
||||||
TickData ThisTickEntry;
|
TickData ThisTickEntry;
|
||||||
|
|
||||||
// Сюда обращается ветка обновления IServerSession, накапливая данные до SyncTick
|
|
||||||
// Ресурсы, ожидающие либо кеш, либо сервер; используются для сопоставления hash->domain/key
|
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, Hash_t>>> ResourceWait[(int) EnumAssets::MAX_ENUM];
|
|
||||||
// Полученные изменения связок в ожидании стадии синхронизации такта
|
|
||||||
std::vector<AssetsBindsChange> Binds;
|
|
||||||
// Подгруженные или принятые меж тактами ресурсы
|
|
||||||
std::vector<AssetEntry> LoadedResources;
|
|
||||||
// Список ресурсов на которые уже был отправлен запрос на загрузку ресурса
|
|
||||||
std::vector<Hash_t> AlreadyLoading;
|
|
||||||
|
|
||||||
|
|
||||||
// Обменный пункт
|
// Обменный пункт
|
||||||
// Полученные ресурсы с сервера
|
|
||||||
TOS::SpinlockObject<std::vector<AssetEntry>> LoadedAssets;
|
|
||||||
// Изменения в наблюдаемых ресурсах
|
|
||||||
TOS::SpinlockObject<std::vector<AssetsBindsChange>> AssetsBinds;
|
|
||||||
// Пакеты обновлений игрового мира
|
// Пакеты обновлений игрового мира
|
||||||
TOS::SpinlockObject<std::vector<TickData>> TickSequence;
|
TOS::SpinlockObject<std::vector<TickData>> TickSequence;
|
||||||
} AsyncContext;
|
} AsyncContext;
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::checkAndPre
|
|||||||
ReloadStatus* status
|
ReloadStatus* status
|
||||||
) {
|
) {
|
||||||
assert(idResolver);
|
assert(idResolver);
|
||||||
assert(onNewResourceParsed);
|
|
||||||
|
|
||||||
bool expected = false;
|
bool expected = false;
|
||||||
assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
|
assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
|
||||||
@@ -287,35 +286,36 @@ AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::_checkAndPr
|
|||||||
for(const auto& [key, res] : keys) {
|
for(const auto& [key, res] : keys) {
|
||||||
uniqueExistsTypes.insert(res.Id);
|
uniqueExistsTypes.insert(res.Id);
|
||||||
|
|
||||||
if(res.Id >= resourceLinksTyped.size() || !std::get<bool>(resourceLinksTyped[res.Id]))
|
if(res.Id >= resourceLinksTyped.size() || !resourceLinksTyped[res.Id].IsExist)
|
||||||
{ // Если идентификатора нет в таблице или ресурс не привязан
|
{ // Если идентификатора нет в таблице или ресурс не привязан
|
||||||
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
PendingResource resource = buildResource(static_cast<AssetType>(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);
|
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||||
|
|
||||||
if(res.Id >= result.MaxNewSize[type])
|
if(res.Id >= result.MaxNewSize[type])
|
||||||
result.MaxNewSize[type] = res.Id+1;
|
result.MaxNewSize[type] = res.Id+1;
|
||||||
|
|
||||||
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
|
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
|
||||||
} else if(
|
} else if(resourceLinksTyped[res.Id].Path != res.Path
|
||||||
std::get<fs::path>(resourceLinksTyped[res.Id]) != res.Path
|
|| resourceLinksTyped[res.Id].LastWrite != res.Timestamp
|
||||||
|| std::get<fs::file_time_type>(resourceLinksTyped[res.Id]) != res.Timestamp
|
|
||||||
) { // Если ресурс теперь берётся с другого места или изменилось время изменения файла
|
) { // Если ресурс теперь берётся с другого места или изменилось время изменения файла
|
||||||
const auto& lastResource = resourceLinksTyped[res.Id];
|
const auto& lastResource = resourceLinksTyped[res.Id];
|
||||||
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
||||||
|
|
||||||
if(auto lastHash = std::get<ResourceFile::Hash_t>(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<fs::path>(resourceLinksTyped[res.Id]));
|
result.HashToPathLost[lastResource.Hash].push_back(resourceLinksTyped[res.Id].Path);
|
||||||
// Новый хеш стал доступен по этому расположению.
|
// Новый хеш стал доступен по этому расположению.
|
||||||
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||||
} else if(std::get<fs::path>(resourceLinksTyped[res.Id]) != res.Path) {
|
} else if(resourceLinksTyped[res.Id].Path != res.Path) {
|
||||||
// Изменился конечный путь.
|
// Изменился конечный путь.
|
||||||
// Хэш более не доступен по этому расположению.
|
// Хэш более не доступен по этому расположению.
|
||||||
result.HashToPathLost[resource.Hash].push_back(std::get<fs::path>(resourceLinksTyped[res.Id]));
|
result.HashToPathLost[resource.Hash].push_back(resourceLinksTyped[res.Id].Path);
|
||||||
// Хеш теперь доступен по этому расположению.
|
// Хеш теперь доступен по этому расположению.
|
||||||
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||||
} else {
|
} else {
|
||||||
@@ -373,13 +373,7 @@ AssetsPreloader::Out_applyResourcesUpdate AssetsPreloader::applyResourcesUpdate(
|
|||||||
|
|
||||||
// Увеличиваем размер, если необходимо
|
// Увеличиваем размер, если необходимо
|
||||||
if(orr.MaxNewSize[type] > ResourceLinks[type].size()) {
|
if(orr.MaxNewSize[type] > ResourceLinks[type].size()) {
|
||||||
std::tuple<
|
ResourceLink def{
|
||||||
ResourceFile::Hash_t,
|
|
||||||
ResourceHeader,
|
|
||||||
fs::file_time_type,
|
|
||||||
fs::path,
|
|
||||||
bool
|
|
||||||
> def{
|
|
||||||
ResourceFile::Hash_t{0},
|
ResourceFile::Hash_t{0},
|
||||||
ResourceHeader(),
|
ResourceHeader(),
|
||||||
fs::file_time_type(),
|
fs::file_time_type(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
@@ -175,7 +176,7 @@ public:
|
|||||||
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||||
const AssetsRegister& instances,
|
const AssetsRegister& instances,
|
||||||
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
||||||
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed,
|
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed = nullptr,
|
||||||
ReloadStatus* status = nullptr
|
ReloadStatus* status = nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -223,6 +224,23 @@ public:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& getResourceLinks() const {
|
||||||
|
return ResourceLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Out_Resource {
|
||||||
|
ResourceFile::Hash_t Hash;
|
||||||
|
fs::path Path;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<Out_Resource> getResource(EnumAssets type, ResourceId id) const {
|
||||||
|
const auto& rl = ResourceLinks[static_cast<size_t>(type)];
|
||||||
|
if(id >= rl.size() || !rl[id].IsExist)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return Out_Resource{rl[id].Hash, rl[id].Path};
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ResourceFindInfo {
|
struct ResourceFindInfo {
|
||||||
// Путь к архиву (если есть), и путь до ресурса
|
// Путь к архиву (если есть), и путь до ресурса
|
||||||
@@ -249,6 +267,15 @@ private:
|
|||||||
std::atomic<bool> _Reloading = false;
|
std::atomic<bool> _Reloading = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct ResourceLink {
|
||||||
|
ResourceFile::Hash_t Hash; // Хэш ресурса на диске
|
||||||
|
/// TODO: клиенту не нужны хедеры
|
||||||
|
ResourceHeader Header; // Хедер ресурса (со всеми зависимостями)
|
||||||
|
fs::file_time_type LastWrite; // Время изменения ресурса на диске
|
||||||
|
fs::path Path; // Путь до ресурса
|
||||||
|
bool IsExist;
|
||||||
|
};
|
||||||
|
|
||||||
Out_checkAndPrepareResourcesUpdate _checkAndPrepareResourcesUpdate(
|
Out_checkAndPrepareResourcesUpdate _checkAndPrepareResourcesUpdate(
|
||||||
const AssetsRegister& instances,
|
const AssetsRegister& instances,
|
||||||
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
||||||
@@ -258,15 +285,7 @@ private:
|
|||||||
|
|
||||||
// Привязка Id -> Hash + Header + Timestamp + Path
|
// Привязка Id -> Hash + Header + Timestamp + Path
|
||||||
std::array<
|
std::array<
|
||||||
std::vector<
|
std::vector<ResourceLink>,
|
||||||
std::tuple<
|
|
||||||
ResourceFile::Hash_t, // Хэш ресурса на диске
|
|
||||||
ResourceHeader, // Хедер ресурса (со всеми зависимостями)
|
|
||||||
fs::file_time_type, // Время изменения ресурса на диске
|
|
||||||
fs::path, // Путь до ресурса
|
|
||||||
bool // IsExist
|
|
||||||
>
|
|
||||||
>,
|
|
||||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||||
> ResourceLinks;
|
> ResourceLinks;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user