Рефакторинг кода работы с ресурсами игры на стороне сервера
This commit is contained in:
@@ -71,7 +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(), 1);
|
std::fill(NextServerId.begin(), NextServerId.end(), 0);
|
||||||
for(auto& vec : ServerIdToDK)
|
for(auto& vec : ServerIdToDK)
|
||||||
vec.emplace_back();
|
vec.emplace_back();
|
||||||
|
|
||||||
|
|||||||
@@ -824,6 +824,10 @@ std::u8string unCompressLinear(std::u8string_view data) {
|
|||||||
return *(std::u8string*) &outString;
|
return *(std::u8string*) &outString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Hash_t ResourceFile::calcHash(const char8_t* data, size_t size) {
|
||||||
|
return sha2::sha256((const uint8_t*) data, size);
|
||||||
|
}
|
||||||
|
|
||||||
ResourceHeader HeadlessNodeState::parse(const js::object& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver) {
|
ResourceHeader HeadlessNodeState::parse(const js::object& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver) {
|
||||||
std::vector<AssetsModel> headerIds;
|
std::vector<AssetsModel> headerIds;
|
||||||
|
|
||||||
|
|||||||
@@ -553,6 +553,19 @@ inline std::pair<std::string, std::string> parseDomainKey(const std::string& val
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ResourceFile {
|
||||||
|
using Hash_t = std::array<uint8_t, 32>;
|
||||||
|
|
||||||
|
Hash_t Hash;
|
||||||
|
std::u8string Data;
|
||||||
|
|
||||||
|
static Hash_t calcHash(const char8_t* data, size_t size);
|
||||||
|
|
||||||
|
void calcHash() {
|
||||||
|
Hash = calcHash(Data.data(), Data.size());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct NodestateEntry {
|
struct NodestateEntry {
|
||||||
std::string Name;
|
std::string Name;
|
||||||
int Variability = 0; // Количество возможный значений состояния
|
int Variability = 0; // Количество возможный значений состояния
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
#include "AssetsPreloader.hpp"
|
#include "AssetsPreloader.hpp"
|
||||||
|
#include "Common/Abstract.hpp"
|
||||||
|
#include "Common/TexturePipelineProgram.hpp"
|
||||||
|
#include "sha2.hpp"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -42,11 +46,26 @@ static std::u8string readOptionalMeta(const fs::path& path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AssetsPreloader::AssetsPreloader() {
|
AssetsPreloader::AssetsPreloader() {
|
||||||
std::fill(NextId.begin(), NextId.end(), 1);
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); type++) {
|
||||||
std::fill(LastSendId.begin(), LastSendId.end(), 1);
|
ResourceLinks[type].emplace_back(
|
||||||
|
ResourceFile::Hash_t{0},
|
||||||
|
ResourceHeader(),
|
||||||
|
fs::file_time_type(),
|
||||||
|
fs::path{""},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetsPreloader::Out_reloadResources AssetsPreloader::reloadResources(const AssetsRegister& instances, ReloadStatus* status) {
|
AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::checkAndPrepareResourcesUpdate(
|
||||||
|
const AssetsRegister& instances,
|
||||||
|
const std::function<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,
|
||||||
|
ReloadStatus* status
|
||||||
|
) {
|
||||||
|
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");
|
||||||
struct ReloadGuard {
|
struct ReloadGuard {
|
||||||
@@ -56,7 +75,7 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::reloadResources(const Asse
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
ReloadStatus secondStatus;
|
ReloadStatus secondStatus;
|
||||||
return _reloadResources(instances, status ? *status : secondStatus);
|
return _checkAndPrepareResourcesUpdate(instances, idResolver, onNewResourceParsed, status ? *status : secondStatus);
|
||||||
} catch(const std::exception& exc) {
|
} catch(const std::exception& exc) {
|
||||||
LOG.error() << exc.what();
|
LOG.error() << exc.what();
|
||||||
assert(!"reloadResources: здесь не должно быть ошибок");
|
assert(!"reloadResources: здесь не должно быть ошибок");
|
||||||
@@ -67,9 +86,12 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::reloadResources(const Asse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const AssetsRegister& instances, ReloadStatus& status) {
|
AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::_checkAndPrepareResourcesUpdate(
|
||||||
Out_reloadResources result;
|
const AssetsRegister& instances,
|
||||||
|
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,
|
||||||
|
ReloadStatus& status
|
||||||
|
) {
|
||||||
// 1) Поиск всех ресурсов и построение конечной карты ресурсов (timestamps, path, name, size)
|
// 1) Поиск всех ресурсов и построение конечной карты ресурсов (timestamps, path, name, size)
|
||||||
// Карта найденных ресурсов
|
// Карта найденных ресурсов
|
||||||
std::array<
|
std::array<
|
||||||
@@ -87,12 +109,12 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass
|
|||||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||||
> resourcesFirstStage;
|
> resourcesFirstStage;
|
||||||
|
|
||||||
for (const fs::path& instance : instances.Assets) {
|
for(const fs::path& instance : instances.Assets) {
|
||||||
try {
|
try {
|
||||||
if (fs::is_regular_file(instance)) {
|
if(fs::is_regular_file(instance)) {
|
||||||
// Может архив
|
// Может архив
|
||||||
/// TODO: пока не поддерживается
|
/// TODO: пока не поддерживается
|
||||||
} else if (fs::is_directory(instance)) {
|
} else if(fs::is_directory(instance)) {
|
||||||
// Директория
|
// Директория
|
||||||
fs::path assetsRoot = instance;
|
fs::path assetsRoot = instance;
|
||||||
fs::path assetsCandidate = instance / "assets";
|
fs::path assetsCandidate = instance / "assets";
|
||||||
@@ -122,20 +144,20 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass
|
|||||||
>& firstStage = resourcesFirstStage[static_cast<size_t>(assetType)][domain];
|
>& firstStage = resourcesFirstStage[static_cast<size_t>(assetType)][domain];
|
||||||
|
|
||||||
// Исследуем все ресурсы одного типа
|
// Исследуем все ресурсы одного типа
|
||||||
for (auto begin = fs::recursive_directory_iterator(assetPath), end = fs::recursive_directory_iterator(); begin != end; begin++) {
|
for(auto begin = fs::recursive_directory_iterator(assetPath), end = fs::recursive_directory_iterator(); begin != end; begin++) {
|
||||||
if (begin->is_directory())
|
if(begin->is_directory())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
fs::path file = begin->path();
|
fs::path file = begin->path();
|
||||||
if (assetType == AssetType::Texture && file.extension() == ".meta")
|
if(assetType == AssetType::Texture && file.extension() == ".meta")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::string key = fs::relative(file, assetPath).generic_string();
|
std::string key = fs::relative(file, assetPath).generic_string();
|
||||||
if (firstStage.contains(key))
|
if(firstStage.contains(key))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
fs::file_time_type timestamp = fs::last_write_time(file);
|
fs::file_time_type timestamp = fs::last_write_time(file);
|
||||||
if (assetType == AssetType::Texture) {
|
if(assetType == AssetType::Texture) {
|
||||||
fs::path metaPath = file;
|
fs::path metaPath = file;
|
||||||
metaPath += ".meta";
|
metaPath += ".meta";
|
||||||
if (fs::exists(metaPath) && fs::is_regular_file(metaPath)) {
|
if (fs::exists(metaPath) && fs::is_regular_file(metaPath)) {
|
||||||
@@ -148,7 +170,8 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass
|
|||||||
// Работаем с ресурсом
|
// Работаем с ресурсом
|
||||||
firstStage[key] = ResourceFindInfo{
|
firstStage[key] = ResourceFindInfo{
|
||||||
.Path = file,
|
.Path = file,
|
||||||
.Timestamp = timestamp
|
.Timestamp = timestamp,
|
||||||
|
.Id = idResolver(assetType, domain, key)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +181,6 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass
|
|||||||
}
|
}
|
||||||
} catch (const std::exception& exc) {
|
} catch (const std::exception& exc) {
|
||||||
/// TODO: Логгировать в статусе
|
/// TODO: Логгировать в статусе
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,14 +194,14 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass
|
|||||||
= [&](const std::string_view model) -> uint32_t
|
= [&](const std::string_view model) -> uint32_t
|
||||||
{
|
{
|
||||||
auto [mDomain, mKey] = parseDomainKey(model, domain);
|
auto [mDomain, mKey] = parseDomainKey(model, domain);
|
||||||
return getId(AssetType::Model, mDomain, mKey);
|
return idResolver(AssetType::Model, mDomain, mKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::function<std::optional<uint32_t>(std::string_view)> textureIdResolver
|
std::function<std::optional<uint32_t>(std::string_view)> textureIdResolver
|
||||||
= [&](std::string_view texture) -> std::optional<uint32_t>
|
= [&](std::string_view texture) -> std::optional<uint32_t>
|
||||||
{
|
{
|
||||||
auto [mDomain, mKey] = parseDomainKey(texture, domain);
|
auto [mDomain, mKey] = parseDomainKey(texture, domain);
|
||||||
return getId(AssetType::Texture, mDomain, mKey);
|
return idResolver(AssetType::Texture, mDomain, mKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::function<std::vector<uint8_t>(const std::string_view)> textureResolver
|
std::function<std::vector<uint8_t>(const std::string_view)> textureResolver
|
||||||
@@ -202,8 +224,8 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass
|
|||||||
|
|
||||||
HeadlessNodeState hns;
|
HeadlessNodeState hns;
|
||||||
out.Header = hns.parse(obj, modelResolver);
|
out.Header = hns.parse(obj, modelResolver);
|
||||||
out.Resource = std::make_shared<std::u8string>(hns.dump());
|
out.Resource = hns.dump();
|
||||||
out.Hash = sha2::sha256((const uint8_t*) out.Resource->data(), out.Resource->size());
|
out.Hash = sha2::sha256((const uint8_t*) out.Resource.data(), out.Resource.size());
|
||||||
} else if (type == AssetType::Model) {
|
} else if (type == AssetType::Model) {
|
||||||
const std::string ext = info.Path.extension().string();
|
const std::string ext = info.Path.extension().string();
|
||||||
if (ext == ".json") {
|
if (ext == ".json") {
|
||||||
@@ -213,19 +235,8 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass
|
|||||||
|
|
||||||
HeadlessModel hm;
|
HeadlessModel hm;
|
||||||
out.Header = hm.parse(obj, modelResolver, textureResolver);
|
out.Header = hm.parse(obj, modelResolver, textureResolver);
|
||||||
std::u8string compiled = hm.dump();
|
out.Resource = hm.dump();
|
||||||
if(hm.Cuboids.empty()) {
|
out.Hash = sha2::sha256((const uint8_t*) out.Resource.data(), out.Resource.size());
|
||||||
static std::atomic<uint32_t> debugEmptyModelLogCount = 0;
|
|
||||||
uint32_t idx = debugEmptyModelLogCount.fetch_add(1);
|
|
||||||
if(idx < 128) {
|
|
||||||
LOG.warn() << "Model compiled with empty cuboids: "
|
|
||||||
<< domain << ':' << key
|
|
||||||
<< " file=" << info.Path.string()
|
|
||||||
<< " size=" << compiled.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.Resource = std::make_shared<std::u8string>(std::move(compiled));
|
|
||||||
out.Hash = sha2::sha256((const uint8_t*) out.Resource->data(), out.Resource->size());
|
|
||||||
// } else if (ext == ".gltf" || ext == ".glb") {
|
// } else if (ext == ".gltf" || ext == ".glb") {
|
||||||
// /// TODO: добавить поддержку gltf
|
// /// TODO: добавить поддержку gltf
|
||||||
// ResourceFile file = readFileBytes(info.Path);
|
// ResourceFile file = readFileBytes(info.Path);
|
||||||
@@ -236,239 +247,157 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass
|
|||||||
}
|
}
|
||||||
} else if (type == AssetType::Texture) {
|
} else if (type == AssetType::Texture) {
|
||||||
ResourceFile file = readFileBytes(info.Path);
|
ResourceFile file = readFileBytes(info.Path);
|
||||||
out.Resource = std::make_shared<std::u8string>(std::move(file.Data));
|
out.Resource = std::move(file.Data);
|
||||||
out.Hash = file.Hash;
|
out.Hash = file.Hash;
|
||||||
out.Header = readOptionalMeta(info.Path);
|
out.Header = readOptionalMeta(info.Path);
|
||||||
} else {
|
} else {
|
||||||
ResourceFile file = readFileBytes(info.Path);
|
ResourceFile file = readFileBytes(info.Path);
|
||||||
out.Resource = std::make_shared<std::u8string>(std::move(file.Data));
|
out.Resource = std::move(file.Data);
|
||||||
out.Hash = file.Hash;
|
out.Hash = file.Hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Id = getId(type, domain, key);
|
out.Id = idResolver(type, domain, key);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 2) Определяем какие ресурсы изменились (новый timestamp) или новые ресурсы
|
||||||
|
Out_checkAndPrepareResourcesUpdate result;
|
||||||
|
|
||||||
|
// Собираем идентификаторы, чтобы потом определить какие ресурсы пропали
|
||||||
|
std::array<
|
||||||
|
std::unordered_set<ResourceId>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> uniqueExists;
|
||||||
|
|
||||||
// 2) Обрабатываться будут только изменённые (новый timestamp) или новые ресурсы
|
|
||||||
// Определяем каких ресурсов не стало
|
|
||||||
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||||
auto& tableResourcesFirstStage = resourcesFirstStage[type];
|
auto& uniqueExistsTypes = uniqueExists[type];
|
||||||
for(const auto& [id, resource] : MediaResources[type]) {
|
const auto& resourceLinksTyped = ResourceLinks[type];
|
||||||
if(tableResourcesFirstStage.empty()) {
|
result.MaxNewSize[type] = resourceLinksTyped.size();
|
||||||
result.Lost[type][resource.Domain].push_back(resource.Key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto iterDomain = tableResourcesFirstStage.find(resource.Domain);
|
{
|
||||||
if(iterDomain == tableResourcesFirstStage.end()) {
|
size_t allIds = 0;
|
||||||
result.Lost[type][resource.Domain].push_back(resource.Key);
|
for(const auto& [domain, keys] : resourcesFirstStage[type])
|
||||||
continue;
|
allIds += keys.size();
|
||||||
}
|
|
||||||
|
|
||||||
if(!iterDomain->second.contains(resource.Key)) {
|
uniqueExistsTypes.reserve(allIds);
|
||||||
result.Lost[type][resource.Domain].push_back(resource.Key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Определение новых или изменённых ресурсов
|
for(const auto& [domain, keys] : resourcesFirstStage[type]) {
|
||||||
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
for(const auto& [key, res] : keys) {
|
||||||
for(const auto& [domain, table] : resourcesFirstStage[type]) {
|
uniqueExistsTypes.insert(res.Id);
|
||||||
auto iterTableDomain = DKToId[type].find(domain);
|
|
||||||
if(iterTableDomain == DKToId[type].end()) {
|
if(res.Id >= resourceLinksTyped.size() || !std::get<bool>(resourceLinksTyped[res.Id]))
|
||||||
// Домен неизвестен движку, все ресурсы в нём новые
|
{ // Если идентификатора нет в таблице или ресурс не привязан
|
||||||
for(const auto& [key, info] : table) {
|
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
||||||
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, info);
|
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
|
||||||
result.NewOrChange[type][domain].push_back(std::move(resource));
|
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||||
}
|
|
||||||
} else {
|
if(res.Id >= result.MaxNewSize[type])
|
||||||
for(const auto& [key, info] : table) {
|
result.MaxNewSize[type] = res.Id+1;
|
||||||
bool needsUpdate = true;
|
|
||||||
if(auto iterKey = iterTableDomain->second.find(key); iterKey != iterTableDomain->second.end()) {
|
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
|
||||||
// Идентификатор найден
|
} else if(
|
||||||
auto iterRes = MediaResources[type].find(iterKey->second);
|
std::get<fs::path>(resourceLinksTyped[res.Id]) != res.Path
|
||||||
// Если нашли ресурс по идентификатору и время изменения не поменялось, то он не новый и не изменился
|
|| std::get<fs::file_time_type>(resourceLinksTyped[res.Id]) != res.Timestamp
|
||||||
if(iterRes != MediaResources[type].end() && iterRes->second.Timestamp == info.Timestamp)
|
) { // Если ресурс теперь берётся с другого места или изменилось время изменения файла
|
||||||
needsUpdate = false;
|
const auto& lastResource = resourceLinksTyped[res.Id];
|
||||||
|
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
||||||
|
|
||||||
|
if(auto lastHash = std::get<ResourceFile::Hash_t>(lastResource); lastHash != resource.Hash) {
|
||||||
|
// Хэш изменился
|
||||||
|
// Сообщаем о новом ресурсе
|
||||||
|
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
|
||||||
|
// Старый хэш более не доступен по этому расположению.
|
||||||
|
result.HashToPathLost[lastHash].push_back(std::get<fs::path>(resourceLinksTyped[res.Id]));
|
||||||
|
// Новый хеш стал доступен по этому расположению.
|
||||||
|
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||||
|
} else if(std::get<fs::path>(resourceLinksTyped[res.Id]) != res.Path) {
|
||||||
|
// Изменился конечный путь.
|
||||||
|
// Хэш более не доступен по этому расположению.
|
||||||
|
result.HashToPathLost[resource.Hash].push_back(std::get<fs::path>(resourceLinksTyped[res.Id]));
|
||||||
|
// Хеш теперь доступен по этому расположению.
|
||||||
|
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||||
|
} else {
|
||||||
|
// Ресурс без заголовка никак не изменился.
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!needsUpdate)
|
// Чтобы там не поменялось, мог поменятся заголовок. Уведомляем о новой привязке.
|
||||||
continue;
|
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
|
||||||
|
} else {
|
||||||
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, info);
|
// Ресурс не изменился
|
||||||
result.NewOrChange[(int) type][domain].push_back(std::move(resource));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
// 3) Определяем какие ресурсы пропали
|
||||||
}
|
|
||||||
|
|
||||||
AssetsPreloader::Out_applyResourceChange AssetsPreloader::applyResourceChange(const Out_reloadResources& orr) {
|
|
||||||
Out_applyResourceChange result;
|
|
||||||
|
|
||||||
// Удаляем ресурсы
|
|
||||||
/*
|
|
||||||
Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор
|
|
||||||
Уже скомпилированные зависимости к ресурсам не будут
|
|
||||||
перекомпилироваться для смены идентификатора.
|
|
||||||
Если нужный ресурс появится, то привязка останется.
|
|
||||||
Новые клиенты не получат ресурс которого нет,
|
|
||||||
но он может использоваться
|
|
||||||
*/
|
|
||||||
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); type++) {
|
|
||||||
for(const auto& [domain, keys] : orr.Lost[type]) {
|
|
||||||
auto iterDomain = DKToId[type].find(domain);
|
|
||||||
|
|
||||||
// Если уже было решено, что ресурсы были, и стали потерянными, то так и должно быть
|
|
||||||
assert(iterDomain != DKToId[type].end());
|
|
||||||
|
|
||||||
for(const auto& key : keys) {
|
|
||||||
auto iterKey = iterDomain->second.find(key);
|
|
||||||
|
|
||||||
// Ресурс был и должен быть
|
|
||||||
assert(iterKey != iterDomain->second.end());
|
|
||||||
|
|
||||||
uint32_t id = iterKey->second;
|
|
||||||
auto& resType = MediaResources[type];
|
|
||||||
auto iterRes = resType.find(id);
|
|
||||||
if(iterRes == resType.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Ресурс был потерян
|
|
||||||
result.Lost[type].push_back(id);
|
|
||||||
// Hash более нам неизвестен
|
|
||||||
HashToId.erase(iterRes->second.Hash);
|
|
||||||
// Затираем ресурс
|
|
||||||
resType.erase(iterRes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем
|
|
||||||
for(int type = 0; type < (int) AssetType::MAX_ENUM; type++) {
|
|
||||||
auto& typeTable = DKToId[type];
|
|
||||||
for(const auto& [domain, resources] : orr.NewOrChange[type]) {
|
|
||||||
auto& domainTable = typeTable[domain];
|
|
||||||
for(const PendingResource& pending : resources) {
|
|
||||||
MediaResource resource {
|
|
||||||
.Domain = domain,
|
|
||||||
.Key = std::move(pending.Key),
|
|
||||||
.Timestamp = pending.Timestamp,
|
|
||||||
.Resource = std::move(pending.Resource),
|
|
||||||
.Hash = pending.Hash,
|
|
||||||
.Header = std::move(pending.Header)
|
|
||||||
};
|
|
||||||
|
|
||||||
auto& table = MediaResources[type];
|
|
||||||
// Нужно затереть старую ссылку хеша на данный ресурс
|
|
||||||
if(auto iter = table.find(pending.Id); iter != table.end())
|
|
||||||
HashToId.erase(iter->second.Hash);
|
|
||||||
|
|
||||||
// Добавили ресурс
|
|
||||||
table[pending.Id] = resource;
|
|
||||||
// Связали с хешем
|
|
||||||
HashToId[resource.Hash] = {static_cast<AssetType>(type), pending.Id};
|
|
||||||
// Осведомили о новом/изменённом ресурсе
|
|
||||||
result.NewOrChange[type].emplace_back(pending.Id, resource.Hash, std::move(resource.Header));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Не должно быть ресурсов, которые были помечены как потерянные
|
|
||||||
#ifndef NDEBUG
|
|
||||||
std::unordered_set<uint32_t> changed;
|
|
||||||
for(const auto& [id, _, _2] : result.NewOrChange[type])
|
|
||||||
changed.insert(id);
|
|
||||||
|
|
||||||
auto& lost = result.Lost[type];
|
|
||||||
for(auto iter : lost)
|
|
||||||
assert(!changed.contains(iter));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsPreloader::Out_bakeId AssetsPreloader::bakeIdTables() {
|
|
||||||
#ifndef NDEBUG
|
|
||||||
|
|
||||||
assert(!DKToIdInBakingMode);
|
|
||||||
DKToIdInBakingMode = true;
|
|
||||||
struct _tempStruct {
|
|
||||||
AssetsPreloader* handler;
|
|
||||||
~_tempStruct() { handler->DKToIdInBakingMode = false; }
|
|
||||||
} _lock{this};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Out_bakeId result;
|
|
||||||
|
|
||||||
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||||
// домен+ключ -> id
|
const auto& resourceLinksTyped = ResourceLinks[type];
|
||||||
{
|
|
||||||
auto lock = NewDKToId[type].lock();
|
|
||||||
auto& dkToId = DKToId[type];
|
|
||||||
for(auto& [domain, keys] : *lock) {
|
|
||||||
// Если домен не существует, просто воткнёт новые ключи
|
|
||||||
auto [iterDomain, inserted] = dkToId.try_emplace(domain, std::move(keys));
|
|
||||||
if(!inserted) {
|
|
||||||
// Домен уже существует, сливаем новые ключи
|
|
||||||
iterDomain->second.merge(keys);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lock->clear();
|
size_t counter = 0;
|
||||||
}
|
for(const auto& [hash, header, timestamp, path, isExist] : resourceLinksTyped) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(!isExist)
|
||||||
|
continue;
|
||||||
|
|
||||||
// id -> домен+ключ
|
if(uniqueExists[type].contains(id))
|
||||||
{
|
continue;
|
||||||
auto lock = NewIdToDK[type].lock();
|
|
||||||
|
|
||||||
auto& idToDK = IdToDK[type];
|
// Ресурс потерян
|
||||||
result.IdToDK[type] = std::move(*lock);
|
// Хэш более не доступен по этому расположению.
|
||||||
lock->clear();
|
result.HashToPathLost[hash].push_back(path);
|
||||||
idToDK.append_range(result.IdToDK[type]);
|
result.LostLinks[type].push_back(id);
|
||||||
|
|
||||||
// result.LastSendId[type] = LastSendId[type];
|
|
||||||
LastSendId[type] = NextId[type];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetsPreloader::Out_fullSync AssetsPreloader::collectFullSync() const {
|
AssetsPreloader::Out_applyResourcesUpdate AssetsPreloader::applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) {
|
||||||
Out_fullSync out;
|
Out_applyResourcesUpdate result;
|
||||||
|
|
||||||
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
out.IdToDK[type] = IdToDK[type];
|
// Затираем потерянные
|
||||||
}
|
for(ResourceId id : orr.LostLinks[type]) {
|
||||||
|
assert(id < ResourceLinks[type].size());
|
||||||
|
auto& [hash, header, timestamp, path, isExist] = ResourceLinks[type][id];
|
||||||
|
hash = {0};
|
||||||
|
header = {};
|
||||||
|
timestamp = fs::file_time_type();
|
||||||
|
path.clear();
|
||||||
|
isExist = false;
|
||||||
|
|
||||||
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
result.NewOrUpdates[type].emplace_back(id, hash, header);
|
||||||
for(const auto& [id, resource] : MediaResources[type]) {
|
}
|
||||||
out.HashHeaders[type].push_back(BindHashHeaderInfo{
|
|
||||||
.Id = id,
|
// Увеличиваем размер, если необходимо
|
||||||
.Hash = resource.Hash,
|
if(orr.MaxNewSize[type] > ResourceLinks[type].size()) {
|
||||||
.Header = resource.Header
|
std::tuple<
|
||||||
});
|
ResourceFile::Hash_t,
|
||||||
out.Resources.emplace_back(
|
ResourceHeader,
|
||||||
static_cast<AssetType>(type),
|
fs::file_time_type,
|
||||||
id,
|
fs::path,
|
||||||
&resource
|
bool
|
||||||
);
|
> def{
|
||||||
|
ResourceFile::Hash_t{0},
|
||||||
|
ResourceHeader(),
|
||||||
|
fs::file_time_type(),
|
||||||
|
fs::path{""},
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
ResourceLinks[type].resize(orr.MaxNewSize[type], def);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем / добавляем
|
||||||
|
for(auto& [id, hash, header, timestamp, path] : orr.ResourceUpdates[type]) {
|
||||||
|
ResourceLinks[type][id] = {hash, std::move(header), timestamp, std::move(path), true};
|
||||||
|
result.NewOrUpdates[type].emplace_back(id, hash, header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return result;
|
||||||
}
|
|
||||||
|
|
||||||
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
|
|
||||||
AssetsPreloader::getNodeDependency(const std::string& domain, const std::string& key) {
|
|
||||||
(void)domain;
|
|
||||||
(void)key;
|
|
||||||
return {0, {}, {}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#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>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "Common/TexturePipelineProgram.hpp"
|
#include "Abstract.hpp"
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "Common/Async.hpp"
|
|
||||||
#include "TOSAsync.hpp"
|
|
||||||
#include "TOSLib.hpp"
|
|
||||||
#include "sha2.hpp"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Класс отвечает за отслеживание изменений и подгрузки медиаресурсов в указанных директориях.
|
Класс отвечает за отслеживание изменений и подгрузки медиаресурсов в указанных директориях.
|
||||||
Медиаресурсы, собранные из папки assets или зарегистрированные модами.
|
Медиаресурсы, собранные из папки assets или зарегистрированные модами.
|
||||||
Хранит все данные в оперативной памяти.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static constexpr const char* EnumAssetsToDirectory(LV::EnumAssets value) {
|
static constexpr const char* EnumAssetsToDirectory(LV::EnumAssets value) {
|
||||||
@@ -49,34 +41,10 @@ namespace LV {
|
|||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
using AssetType = EnumAssets;
|
using AssetType = EnumAssets;
|
||||||
|
|
||||||
struct ResourceFile {
|
|
||||||
using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type;
|
|
||||||
|
|
||||||
Hash_t Hash;
|
|
||||||
std::u8string Data;
|
|
||||||
|
|
||||||
void calcHash() {
|
|
||||||
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class AssetsPreloader {
|
class AssetsPreloader {
|
||||||
public:
|
public:
|
||||||
using Ptr = std::shared_ptr<AssetsPreloader>;
|
using Ptr = std::shared_ptr<AssetsPreloader>;
|
||||||
using IdTable =
|
|
||||||
std::unordered_map<
|
|
||||||
std::string, // Domain
|
|
||||||
std::unordered_map<
|
|
||||||
std::string, // Key
|
|
||||||
uint32_t, // ResourceId
|
|
||||||
detail::TSVHash,
|
|
||||||
detail::TSVEq
|
|
||||||
>,
|
|
||||||
detail::TSVHash,
|
|
||||||
detail::TSVEq
|
|
||||||
>;
|
|
||||||
|
|
||||||
//
|
|
||||||
/*
|
/*
|
||||||
Ресурс имеет бинарную часть, из который вырезаны все зависимости.
|
Ресурс имеет бинарную часть, из который вырезаны все зависимости.
|
||||||
Вторая часть это заголовок, которые всегда динамично передаётся с сервера.
|
Вторая часть это заголовок, которые всегда динамично передаётся с сервера.
|
||||||
@@ -100,63 +68,13 @@ public:
|
|||||||
std::string Key;
|
std::string Key;
|
||||||
fs::file_time_type Timestamp;
|
fs::file_time_type Timestamp;
|
||||||
// Обезличенный ресурс
|
// Обезличенный ресурс
|
||||||
std::shared_ptr<std::u8string> Resource;
|
std::u8string Resource;
|
||||||
// Его хеш
|
// Его хеш
|
||||||
ResourceFile::Hash_t Hash;
|
ResourceFile::Hash_t Hash;
|
||||||
// Заголовок
|
// Заголовок
|
||||||
std::u8string Header;
|
std::u8string Header;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BindDomainKeyInfo {
|
|
||||||
std::string Domain;
|
|
||||||
std::string Key;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BindHashHeaderInfo {
|
|
||||||
ResourceId Id;
|
|
||||||
Hash_t Hash;
|
|
||||||
std::u8string Header;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Out_reloadResources {
|
|
||||||
std::unordered_map<std::string, std::vector<PendingResource>> NewOrChange[(int) AssetType::MAX_ENUM];
|
|
||||||
std::unordered_map<std::string, std::vector<std::string>> Lost[(int) AssetType::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Out_applyResourceChange {
|
|
||||||
std::array<
|
|
||||||
std::vector<AssetsPreloader::BindHashHeaderInfo>,
|
|
||||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
|
||||||
> NewOrChange;
|
|
||||||
|
|
||||||
std::array<
|
|
||||||
std::vector<ResourceId>,
|
|
||||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
|
||||||
> Lost;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Out_bakeId {
|
|
||||||
// Новые привязки
|
|
||||||
std::array<
|
|
||||||
std::vector<BindDomainKeyInfo>,
|
|
||||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
|
||||||
> IdToDK;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Out_fullSync {
|
|
||||||
std::array<
|
|
||||||
std::vector<BindDomainKeyInfo>,
|
|
||||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
|
||||||
> IdToDK;
|
|
||||||
|
|
||||||
std::array<
|
|
||||||
std::vector<BindHashHeaderInfo>,
|
|
||||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
|
||||||
> HashHeaders;
|
|
||||||
|
|
||||||
std::vector<std::tuple<AssetType, ResourceId, const MediaResource*>> Resources;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ReloadStatus {
|
struct ReloadStatus {
|
||||||
/// TODO: callback'и для обновления статусов
|
/// TODO: callback'и для обновления статусов
|
||||||
/// TODO: многоуровневый статус std::vector<std::string>. Этапы/Шаги/Объекты
|
/// TODO: многоуровневый статус std::vector<std::string>. Этапы/Шаги/Объекты
|
||||||
@@ -202,66 +120,117 @@ public:
|
|||||||
! Бронирует идентификаторы используя getId();
|
! Бронирует идентификаторы используя getId();
|
||||||
|
|
||||||
instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему.
|
instances -> пути к директории с assets или архивы с assets внутри. От низшего приоритета к высшему.
|
||||||
|
idResolver -> функция получения идентификатора по Тип+Домен+Ключ
|
||||||
|
onNewResourceParsed -> Callback на обработку распаршенных ресурсов без заголовков
|
||||||
|
(на стороне сервера хранится в другой сущности, на стороне клиента игнорируется).
|
||||||
status -> обратный отклик о процессе обновления ресурсов.
|
status -> обратный отклик о процессе обновления ресурсов.
|
||||||
ReloadStatus <- новые и потерянные ресурсы.
|
ReloadStatus <- новые и потерянные ресурсы.
|
||||||
*/
|
*/
|
||||||
Out_reloadResources reloadResources(const AssetsRegister& instances, ReloadStatus* status = nullptr);
|
struct Out_checkAndPrepareResourcesUpdate {
|
||||||
|
// Новые связки Id -> Hash + Header + Timestamp + Path (ресурс новый или изменён)
|
||||||
|
std::array<
|
||||||
|
std::vector<
|
||||||
|
std::tuple<
|
||||||
|
ResourceId, // Ресурс
|
||||||
|
ResourceFile::Hash_t, // Хэш ресурса на диске
|
||||||
|
ResourceHeader, // Хедер ресурса (со всеми зависимостями)
|
||||||
|
fs::file_time_type, // Время изменения ресурса на диске
|
||||||
|
fs::path // Путь до ресурса
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||||
|
> ResourceUpdates;
|
||||||
|
|
||||||
|
// Используется чтобы эффективно увеличить размер таблиц
|
||||||
|
std::array<
|
||||||
|
ResourceId,
|
||||||
|
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||||
|
> MaxNewSize;
|
||||||
|
|
||||||
|
// Потерянные связки Id (ресурс физически потерян)
|
||||||
|
std::array<
|
||||||
|
std::vector<ResourceId>,
|
||||||
|
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||||
|
> LostLinks;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Новые пути предоставляющие хеш
|
||||||
|
(по каким путям можно получить ресурс определённого хеша).
|
||||||
|
*/
|
||||||
|
std::unordered_map<
|
||||||
|
ResourceFile::Hash_t,
|
||||||
|
std::vector<fs::path>
|
||||||
|
> HashToPathNew;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Потерянные пути, предоставлявшые ресурсы с данным хешем
|
||||||
|
(пути по которым уже нельзя получить заданных хеш).
|
||||||
|
*/
|
||||||
|
std::unordered_map<
|
||||||
|
ResourceFile::Hash_t,
|
||||||
|
std::vector<fs::path>
|
||||||
|
> HashToPathLost;
|
||||||
|
};
|
||||||
|
|
||||||
|
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||||
|
const AssetsRegister& instances,
|
||||||
|
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,
|
||||||
|
ReloadStatus* status = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Применяет расчитанные изменения.
|
Применяет расчитанные изменения.
|
||||||
|
|
||||||
Out_applyResourceChange <- Нужно отправить клиентам новые привязки ресурсов
|
Out_applyResourceUpdate <- Нужно отправить клиентам новые привязки ресурсов
|
||||||
id -> hash+header
|
id -> hash+header
|
||||||
*/
|
*/
|
||||||
Out_applyResourceChange applyResourceChange(const Out_reloadResources& orr);
|
struct BindHashHeaderInfo {
|
||||||
|
ResourceId Id;
|
||||||
|
ResourceFile::Hash_t Hash;
|
||||||
|
ResourceHeader Header;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
struct Out_applyResourcesUpdate {
|
||||||
Выдаёт идентификатор ресурса.
|
std::array<
|
||||||
Многопоточно.
|
std::vector<BindHashHeaderInfo>,
|
||||||
Иногда нужно вызывать bakeIdTables чтобы оптимизировать таблицы
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
идентификаторов. При этом никто не должен использовать getId
|
> NewOrUpdates;
|
||||||
*/
|
};
|
||||||
ResourceId getId(AssetType type, std::string_view domain, std::string_view key);
|
|
||||||
|
|
||||||
/*
|
Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr);
|
||||||
Оптимизирует таблицы идентификаторов.
|
|
||||||
Нельзя использовать пока есть вероятность что кто-то использует getId().
|
|
||||||
Такжке нельзя при выполнении reloadResources().
|
|
||||||
|
|
||||||
Out_bakeId <- Нужно отправить подключенным клиентам новые привязки id -> домен+ключ
|
std::array<
|
||||||
*/
|
std::vector<BindHashHeaderInfo>,
|
||||||
Out_bakeId bakeIdTables();
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> collectHashBindings() const
|
||||||
|
{
|
||||||
|
std::array<
|
||||||
|
std::vector<BindHashHeaderInfo>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> result;
|
||||||
|
|
||||||
// Выдаёт полный список привязок и ресурсов для новых клиентов.
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
Out_fullSync collectFullSync() const;
|
result[type].reserve(ResourceLinks[type].size());
|
||||||
|
|
||||||
/*
|
ResourceId counter = 0;
|
||||||
Выдаёт пакет со всеми текущими привязками id -> домен+ключ.
|
for(const auto& [hash, header, _1, _2, _3] : ResourceLinks[type]) {
|
||||||
Используется при подключении новых клиентов.
|
ResourceId id = counter++;
|
||||||
*/
|
result[type].emplace_back(id, hash, header);
|
||||||
void makeGlobalLinkagePacket() {
|
}
|
||||||
/// TODO: Собрать пакет с IdToDK и сжать его домены и ключи и id -> hash+header
|
}
|
||||||
|
|
||||||
// Тот же пакет для обновления идентификаторов
|
return result;
|
||||||
std::unreachable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Выдаёт ресурс по идентификатору
|
|
||||||
const MediaResource* getResource(AssetType type, uint32_t id) const;
|
|
||||||
|
|
||||||
// Выдаёт ресурс по хешу
|
|
||||||
std::optional<std::tuple<AssetType, uint32_t, const MediaResource*>> getResource(const ResourceFile::Hash_t& hash);
|
|
||||||
|
|
||||||
// Выдаёт зависимости к ресурсам профиля ноды
|
|
||||||
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
|
|
||||||
getNodeDependency(const std::string& domain, const std::string& key);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ResourceFindInfo {
|
struct ResourceFindInfo {
|
||||||
// Путь к архиву (если есть), и путь до ресурса
|
// Путь к архиву (если есть), и путь до ресурса
|
||||||
fs::path ArchivePath, Path;
|
fs::path ArchivePath, Path;
|
||||||
// Время изменения файла
|
// Время изменения файла
|
||||||
fs::file_time_type Timestamp;
|
fs::file_time_type Timestamp;
|
||||||
|
// Идентификатор ресурса
|
||||||
|
ResourceId Id;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HashHasher {
|
struct HashHasher {
|
||||||
@@ -275,136 +244,31 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
// Текущее состояние reloadResources
|
// Текущее состояние reloadResources
|
||||||
std::atomic<bool> _Reloading = false;
|
std::atomic<bool> _Reloading = false;
|
||||||
|
|
||||||
// Если идентификатор не найден в асинхронной таблице, переходим к работе с синхронной
|
|
||||||
ResourceId _getIdNew(AssetType type, std::string_view domain, std::string_view key);
|
|
||||||
|
|
||||||
Out_reloadResources _reloadResources(const AssetsRegister& instances, ReloadStatus& status);
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
// Для контроля за режимом слияния ключей
|
|
||||||
bool DKToIdInBakingMode = false;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
Out_checkAndPrepareResourcesUpdate _checkAndPrepareResourcesUpdate(
|
||||||
Многопоточная таблица идентификаторов. Новые идентификаторы выделяются в NewDKToId,
|
const AssetsRegister& instances,
|
||||||
и далее вливаются в основную таблицу при вызове bakeIdTables()
|
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,
|
||||||
std::array<IdTable, static_cast<size_t>(AssetType::MAX_ENUM)> DKToId;
|
ReloadStatus& status
|
||||||
/*
|
);
|
||||||
Многопоточная таблица обратного резолва.
|
|
||||||
Идентификатор -> домен+ключ
|
|
||||||
*/
|
|
||||||
std::array<std::vector<BindDomainKeyInfo>, static_cast<size_t>(AssetType::MAX_ENUM)> IdToDK;
|
|
||||||
|
|
||||||
/*
|
// Привязка Id -> Hash + Header + Timestamp + Path
|
||||||
Таблица в которой выделяются новые идентификаторы, которых не нашлось в DKToId.
|
std::array<
|
||||||
Данный объект одновременно может работать только с одним потоком.
|
std::vector<
|
||||||
*/
|
std::tuple<
|
||||||
std::array<TOS::SpinlockObject<IdTable>, static_cast<size_t>(AssetType::MAX_ENUM)> NewDKToId;
|
ResourceFile::Hash_t, // Хэш ресурса на диске
|
||||||
/*
|
ResourceHeader, // Хедер ресурса (со всеми зависимостями)
|
||||||
Конец поля идентификаторов, известный клиентам.
|
fs::file_time_type, // Время изменения ресурса на диске
|
||||||
Если NextId продвинулся дальше, нужно уведомить клиентов о новых привязках.
|
fs::path, // Путь до ресурса
|
||||||
*/
|
bool // IsExist
|
||||||
std::array<ResourceId, static_cast<size_t>(AssetType::MAX_ENUM)> LastSendId;
|
>
|
||||||
/*
|
>,
|
||||||
Списки в которых пишутся новые привязки. Начала спиской исходят из LastSendId.
|
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||||
Id + LastSendId -> домен+ключ
|
> ResourceLinks;
|
||||||
*/
|
|
||||||
std::array<TOS::SpinlockObject<std::vector<BindDomainKeyInfo>>, static_cast<size_t>(AssetType::MAX_ENUM)> NewIdToDK;
|
|
||||||
|
|
||||||
// Загруженные ресурсы
|
|
||||||
std::array<std::unordered_map<ResourceId, MediaResource>, static_cast<size_t>(AssetType::MAX_ENUM)> MediaResources;
|
|
||||||
// Hash -> ресурс
|
|
||||||
std::unordered_map<ResourceFile::Hash_t, std::pair<AssetType, ResourceId>, HashHasher> HashToId;
|
|
||||||
// Для последовательного выделения идентификаторов
|
|
||||||
std::array<ResourceId, static_cast<size_t>(AssetType::MAX_ENUM)> NextId;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline ResourceId AssetsPreloader::getId(AssetType type, std::string_view domain, std::string_view key) {
|
|
||||||
#ifndef NDEBUG
|
|
||||||
assert(!DKToIdInBakingMode);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const auto& typeTable = DKToId[static_cast<size_t>(type)];
|
|
||||||
auto domainTable = typeTable.find(domain);
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
assert(!DKToIdInBakingMode);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(domainTable == typeTable.end())
|
|
||||||
return _getIdNew(type, domain, key);
|
|
||||||
|
|
||||||
auto keyTable = domainTable->second.find(key);
|
|
||||||
|
|
||||||
if (keyTable == domainTable->second.end())
|
|
||||||
return _getIdNew(type, domain, key);
|
|
||||||
|
|
||||||
return keyTable->second;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline ResourceId AssetsPreloader::_getIdNew(AssetType type, std::string_view domain, std::string_view key) {
|
|
||||||
auto lock = NewDKToId[static_cast<size_t>(type)].lock();
|
|
||||||
|
|
||||||
auto iterDomainNewTable = lock->find(domain);
|
|
||||||
if(iterDomainNewTable == lock->end()) {
|
|
||||||
iterDomainNewTable = lock->emplace_hint(
|
|
||||||
iterDomainNewTable,
|
|
||||||
(std::string) domain,
|
|
||||||
std::unordered_map<std::string, uint32_t, detail::TSVHash, detail::TSVEq>{}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& domainNewTable = iterDomainNewTable->second;
|
|
||||||
|
|
||||||
if(auto iter = domainNewTable.find(key); iter != domainNewTable.end())
|
|
||||||
return iter->second;
|
|
||||||
|
|
||||||
uint32_t id = domainNewTable[(std::string) key] = NextId[static_cast<size_t>(type)]++;
|
|
||||||
|
|
||||||
auto lock2 = NewIdToDK[static_cast<size_t>(type)].lock();
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
lock2->emplace_back((std::string) domain, (std::string) key);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline const AssetsPreloader::MediaResource* AssetsPreloader::getResource(AssetType type, uint32_t id) const {
|
|
||||||
auto& iterType = MediaResources[static_cast<size_t>(type)];
|
|
||||||
|
|
||||||
auto iterRes = iterType.find(id);
|
|
||||||
if(iterRes == iterType.end())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return &iterRes->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::optional<std::tuple<AssetType, uint32_t, const AssetsPreloader::MediaResource*>>
|
|
||||||
AssetsPreloader::getResource(const ResourceFile::Hash_t& hash)
|
|
||||||
{
|
|
||||||
auto iter = HashToId.find(hash);
|
|
||||||
if(iter == HashToId.end())
|
|
||||||
return std::nullopt;
|
|
||||||
|
|
||||||
auto [type, id] = iter->second;
|
|
||||||
const MediaResource* res = getResource(type, id);
|
|
||||||
if(!res) {
|
|
||||||
HashToId.erase(iter);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(res->Hash != hash) {
|
|
||||||
HashToId.erase(iter);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::tuple<AssetType, uint32_t, const MediaResource*>{type, id, res};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
207
Src/Common/IdProvider.hpp
Normal file
207
Src/Common/IdProvider.hpp
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common/Abstract.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
namespace LV {
|
||||||
|
|
||||||
|
template<class Enum = EnumAssets>
|
||||||
|
class IdProvider {
|
||||||
|
public:
|
||||||
|
static constexpr size_t MAX_ENUM = static_cast<size_t>(Enum::MAX_ENUM);
|
||||||
|
using IdTable =
|
||||||
|
std::unordered_map<
|
||||||
|
std::string, // Domain
|
||||||
|
std::unordered_map<
|
||||||
|
std::string, // Key
|
||||||
|
uint32_t, // ResourceId
|
||||||
|
detail::TSVHash,
|
||||||
|
detail::TSVEq
|
||||||
|
>,
|
||||||
|
detail::TSVHash,
|
||||||
|
detail::TSVEq
|
||||||
|
>;
|
||||||
|
|
||||||
|
struct BindDomainKeyInfo {
|
||||||
|
std::string Domain, Key;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
IdProvider() {
|
||||||
|
std::fill(NextId.begin(), NextId.end(), 1);
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(Enum::MAX_ENUM); ++type) {
|
||||||
|
DKToId[type]["core"]["none"] = 0;
|
||||||
|
IdToDK[type].emplace_back("core", "none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Находит или выдаёт идентификатор на запрошенный ресурс.
|
||||||
|
Функция не требует внешней синхронизации.
|
||||||
|
Требуется периодически вызывать bake().
|
||||||
|
*/
|
||||||
|
inline ResourceId getId(EnumAssets type, std::string_view domain, std::string_view key) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(!DKToIdInBakingMode);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const auto& typeTable = DKToId[static_cast<size_t>(type)];
|
||||||
|
auto domainTable = typeTable.find(domain);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(!DKToIdInBakingMode);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(domainTable == typeTable.end())
|
||||||
|
return _getIdNew(type, domain, key);
|
||||||
|
|
||||||
|
auto keyTable = domainTable->second.find(key);
|
||||||
|
|
||||||
|
if (keyTable == domainTable->second.end())
|
||||||
|
return _getIdNew(type, domain, key);
|
||||||
|
|
||||||
|
return keyTable->second;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Переносит все новые идентификаторы в основную таблицу.
|
||||||
|
Нельзя использовать пока есть вероятность что кто-то использует getId().
|
||||||
|
|
||||||
|
Out_bakeId <- Возвращает все новые привязки.
|
||||||
|
*/
|
||||||
|
std::array<
|
||||||
|
std::vector<BindDomainKeyInfo>,
|
||||||
|
MAX_ENUM
|
||||||
|
> bake() {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
assert(!DKToIdInBakingMode);
|
||||||
|
DKToIdInBakingMode = true;
|
||||||
|
struct _tempStruct {
|
||||||
|
IdProvider* handler;
|
||||||
|
~_tempStruct() { handler->DKToIdInBakingMode = false; }
|
||||||
|
} _lock{this};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::array<
|
||||||
|
std::vector<BindDomainKeyInfo>,
|
||||||
|
MAX_ENUM
|
||||||
|
> result;
|
||||||
|
|
||||||
|
for(size_t type = 0; type < MAX_ENUM; ++type) {
|
||||||
|
// Домен+Ключ -> Id
|
||||||
|
{
|
||||||
|
auto lock = NewDKToId[type].lock();
|
||||||
|
auto& dkToId = DKToId[type];
|
||||||
|
for(auto& [domain, keys] : *lock) {
|
||||||
|
// Если домен не существует, просто воткнёт новые ключи
|
||||||
|
auto [iterDomain, inserted] = dkToId.try_emplace(domain, std::move(keys));
|
||||||
|
if(!inserted) {
|
||||||
|
// Домен уже существует, сливаем новые ключи
|
||||||
|
iterDomain->second.merge(keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id -> Домен+Ключ
|
||||||
|
{
|
||||||
|
auto lock = NewIdToDK[type].lock();
|
||||||
|
|
||||||
|
auto& idToDK = IdToDK[type];
|
||||||
|
result[type] = std::move(*lock);
|
||||||
|
lock->clear();
|
||||||
|
idToDK.append_range(result[type]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для отправки новым подключенным клиентам
|
||||||
|
const std::array<
|
||||||
|
std::vector<BindDomainKeyInfo>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
>& idToDK() const {
|
||||||
|
return IdToDK;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// Для контроля за режимом слияния ключей
|
||||||
|
bool DKToIdInBakingMode = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
Работает с таблицами для новых идентификаторов, в синхронном режиме.
|
||||||
|
Используется когда в основных таблицах не нашлось привязки,
|
||||||
|
она будет найдена или создана здесь синхронно.
|
||||||
|
*/
|
||||||
|
inline ResourceId _getIdNew(EnumAssets type, std::string_view domain, std::string_view key) {
|
||||||
|
// Блокировка по нужному типу ресурса
|
||||||
|
auto lock = NewDKToId[static_cast<size_t>(type)].lock();
|
||||||
|
|
||||||
|
auto iterDomainNewTable = lock->find(domain);
|
||||||
|
// Если домена не нашлось, сразу вставляем его на подходящее место
|
||||||
|
if(iterDomainNewTable == lock->end()) {
|
||||||
|
iterDomainNewTable = lock->emplace_hint(
|
||||||
|
iterDomainNewTable,
|
||||||
|
(std::string) domain,
|
||||||
|
std::unordered_map<std::string, uint32_t, detail::TSVHash, detail::TSVEq>{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& domainNewTable = iterDomainNewTable->second;
|
||||||
|
|
||||||
|
|
||||||
|
if(auto iter = domainNewTable.find(key); iter != domainNewTable.end())
|
||||||
|
return iter->second;
|
||||||
|
else {
|
||||||
|
uint32_t id = NextId[static_cast<size_t>(type)]++;
|
||||||
|
domainNewTable.emplace_hint(iter, (std::string) key, id);
|
||||||
|
|
||||||
|
// Добавился новый идентификатор, теперь добавим обратную связку
|
||||||
|
auto lock2 = NewIdToDK[static_cast<size_t>(type)].lock();
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
lock2->emplace_back((std::string) domain, (std::string) key);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Условно многопоточные объекты
|
||||||
|
/*
|
||||||
|
Таблица идентификаторов. Новые идентификаторы выделяются в NewDKToId,
|
||||||
|
и далее вливаются в основную таблицу при вызове bakeIdTables().
|
||||||
|
|
||||||
|
Домен+Ключ -> Id
|
||||||
|
*/
|
||||||
|
std::array<IdTable, MAX_ENUM> DKToId;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Таблица обратного резолва.
|
||||||
|
Id -> Домен+Ключ.
|
||||||
|
*/
|
||||||
|
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> IdToDK;
|
||||||
|
|
||||||
|
// Требующие синхронизации
|
||||||
|
/*
|
||||||
|
Таблица в которой выделяются новые идентификаторы, перед вливанием в DKToId.
|
||||||
|
Домен+Ключ -> Id.
|
||||||
|
*/
|
||||||
|
std::array<TOS::SpinlockObject<IdTable>, MAX_ENUM> NewDKToId;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Списки в которых пишутся новые привязки.
|
||||||
|
Id + LastMaxId -> Домен+Ключ.
|
||||||
|
*/
|
||||||
|
std::array<TOS::SpinlockObject<std::vector<BindDomainKeyInfo>>, MAX_ENUM> NewIdToDK;
|
||||||
|
|
||||||
|
// Для последовательного выделения идентификаторов
|
||||||
|
std::array<ResourceId, MAX_ENUM> NextId;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -34,24 +34,6 @@ using PlayerId_t = ResourceId;
|
|||||||
using DefGeneratorId_t = ResourceId;
|
using DefGeneratorId_t = ResourceId;
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Сервер загружает информацию о локальных текстурах
|
|
||||||
Пересмотр списка текстур?
|
|
||||||
Динамичные текстуры?
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct ResourceFile {
|
|
||||||
using Hash_t = sha2::sha256_hash; // boost::uuids::detail::sha1::digest_type;
|
|
||||||
|
|
||||||
Hash_t Hash;
|
|
||||||
std::vector<std::byte> Data;
|
|
||||||
|
|
||||||
void calcHash() {
|
|
||||||
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ServerTime {
|
struct ServerTime {
|
||||||
uint32_t Seconds : 24, Sub : 8;
|
uint32_t Seconds : 24, Sub : 8;
|
||||||
};
|
};
|
||||||
|
|||||||
107
Src/Server/AssetsManager.hpp
Normal file
107
Src/Server/AssetsManager.hpp
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common/Abstract.hpp"
|
||||||
|
#include "Common/IdProvider.hpp"
|
||||||
|
#include "Common/AssetsPreloader.hpp"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace LV::Server {
|
||||||
|
|
||||||
|
class AssetsManager : public IdProvider<EnumAssets>, protected AssetsPreloader {
|
||||||
|
public:
|
||||||
|
using BindHashHeaderInfo = AssetsManager::BindHashHeaderInfo;
|
||||||
|
|
||||||
|
struct Out_checkAndPrepareResourcesUpdate : public AssetsPreloader::Out_checkAndPrepareResourcesUpdate {
|
||||||
|
Out_checkAndPrepareResourcesUpdate(AssetsPreloader::Out_checkAndPrepareResourcesUpdate&& obj)
|
||||||
|
: AssetsPreloader::Out_checkAndPrepareResourcesUpdate(std::move(obj))
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> NewHeadless;
|
||||||
|
};
|
||||||
|
|
||||||
|
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||||
|
const AssetsRegister& instances,
|
||||||
|
ReloadStatus* status = nullptr
|
||||||
|
) {
|
||||||
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> newHeadless;
|
||||||
|
|
||||||
|
Out_checkAndPrepareResourcesUpdate result = AssetsPreloader::checkAndPrepareResourcesUpdate(
|
||||||
|
instances,
|
||||||
|
[&](EnumAssets type, std::string_view domain, std::string_view key) { return getId(type, domain, key); },
|
||||||
|
[&](std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath) { newHeadless.emplace(hash, std::move(resource)); },
|
||||||
|
status
|
||||||
|
);
|
||||||
|
|
||||||
|
result.NewHeadless = std::move(newHeadless);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Out_applyResourcesUpdate : public AssetsPreloader::Out_applyResourcesUpdate {
|
||||||
|
Out_applyResourcesUpdate(AssetsPreloader::Out_applyResourcesUpdate&& obj)
|
||||||
|
: AssetsPreloader::Out_applyResourcesUpdate(std::move(obj))
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
Out_applyResourcesUpdate applyResourcesUpdate(Out_checkAndPrepareResourcesUpdate& orr) {
|
||||||
|
Out_applyResourcesUpdate result = AssetsPreloader::applyResourcesUpdate(orr);
|
||||||
|
|
||||||
|
for(auto& [hash, data] : orr.NewHeadless) {
|
||||||
|
Resources.emplace(hash, ResourceHashData{0, std::make_shared<std::u8string>(std::move(data))});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& [hash, pathes] : orr.HashToPathNew) {
|
||||||
|
auto iter = Resources.find(hash);
|
||||||
|
assert(iter != Resources.end());
|
||||||
|
iter->second.RefCount += pathes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& [hash, pathes] : orr.HashToPathLost) {
|
||||||
|
auto iter = Resources.find(hash);
|
||||||
|
assert(iter != Resources.end());
|
||||||
|
iter->second.RefCount -= pathes.size();
|
||||||
|
|
||||||
|
if(iter->second.RefCount == 0)
|
||||||
|
Resources.erase(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>>
|
||||||
|
getResources(const std::vector<ResourceFile::Hash_t>& hashes) const
|
||||||
|
{
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>> result;
|
||||||
|
result.reserve(hashes.size());
|
||||||
|
|
||||||
|
for(const auto& hash : hashes) {
|
||||||
|
auto iter = Resources.find(hash);
|
||||||
|
if(iter == Resources.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result.emplace_back(hash, iter->second.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<
|
||||||
|
std::vector<BindHashHeaderInfo>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> collectHashBindings() const {
|
||||||
|
return AssetsPreloader::collectHashBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ResourceHashData {
|
||||||
|
size_t RefCount;
|
||||||
|
std::shared_ptr<std::u8string> Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<
|
||||||
|
ResourceFile::Hash_t,
|
||||||
|
ResourceHashData
|
||||||
|
> Resources;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
ContentManager::ContentManager(AssetsPreloader& am)
|
ContentManager::ContentManager(AssetsManager& am)
|
||||||
: AM(am)
|
: AM(am)
|
||||||
{
|
{
|
||||||
std::fill(std::begin(NextId), std::end(NextId), 1);
|
std::fill(std::begin(NextId), std::end(NextId), 1);
|
||||||
@@ -158,20 +158,6 @@ ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
|||||||
keys.erase(iterErase, keys.end());
|
keys.erase(iterErase, keys.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
for(ResourceId id : ProfileChanges[(int) EnumDefContent::Node]) {
|
|
||||||
std::optional<DefNode>& node = getEntry_Node(id);
|
|
||||||
if(!node) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto [nodestateId, assetsModel, assetsTexture]
|
|
||||||
= AM.getNodeDependency(node->Domain, node->Key);
|
|
||||||
|
|
||||||
node->NodestateId = nodestateId;
|
|
||||||
node->ModelDeps = std::move(assetsModel);
|
|
||||||
node->TextureDeps = std::move(assetsTexture);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "AssetsManager.hpp"
|
||||||
#include "Common/AssetsPreloader.hpp"
|
|
||||||
#include <sol/table.hpp>
|
#include <sol/table.hpp>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
@@ -135,7 +133,7 @@ class ContentManager {
|
|||||||
void registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
void registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ContentManager(AssetsPreloader &am);
|
ContentManager(AssetsManager &am);
|
||||||
~ContentManager();
|
~ContentManager();
|
||||||
|
|
||||||
// Регистрирует определение контента
|
// Регистрирует определение контента
|
||||||
@@ -215,7 +213,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
TOS::Logger LOG = "Server>ContentManager";
|
TOS::Logger LOG = "Server>ContentManager";
|
||||||
AssetsPreloader& AM;
|
AssetsManager& AM;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1345,7 +1345,8 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
AssetsInit.Assets.push_back(mlt.LoadChain[index].Path / "assets");
|
AssetsInit.Assets.push_back(mlt.LoadChain[index].Path / "assets");
|
||||||
}
|
}
|
||||||
|
|
||||||
Content.AM.applyResourceChange(Content.AM.reloadResources(AssetsInit));
|
auto capru = Content.AM.checkAndPrepareResourcesUpdate(AssetsInit);
|
||||||
|
Content.AM.applyResourcesUpdate(capru);
|
||||||
|
|
||||||
LOG.info() << "Пре Инициализация";
|
LOG.info() << "Пре Инициализация";
|
||||||
|
|
||||||
@@ -1606,12 +1607,11 @@ void GameServer::stepConnections() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!newClients.empty()) {
|
if(!newClients.empty()) {
|
||||||
AssetsPreloader::Out_fullSync fullSync = Content.AM.collectFullSync();
|
|
||||||
std::array<std::vector<ResourceId>, static_cast<size_t>(EnumAssets::MAX_ENUM)> lost{};
|
std::array<std::vector<ResourceId>, static_cast<size_t>(EnumAssets::MAX_ENUM)> lost{};
|
||||||
|
|
||||||
std::vector<Net::Packet> packets;
|
std::vector<Net::Packet> packets;
|
||||||
packets.push_back(RemoteClient::makePacket_informateAssets_DK(fullSync.IdToDK));
|
packets.push_back(RemoteClient::makePacket_informateAssets_DK(Content.AM.idToDK()));
|
||||||
packets.push_back(RemoteClient::makePacket_informateAssets_HH(fullSync.HashHeaders, lost));
|
packets.push_back(RemoteClient::makePacket_informateAssets_HH(Content.AM.collectHashBindings(), lost));
|
||||||
|
|
||||||
for(const std::shared_ptr<RemoteClient>& client : newClients) {
|
for(const std::shared_ptr<RemoteClient>& client : newClients) {
|
||||||
if(!packets.empty()) {
|
if(!packets.empty()) {
|
||||||
@@ -1688,23 +1688,27 @@ void GameServer::reloadMods() {
|
|||||||
LOG.info() << "Перезагрузка ассетов";
|
LOG.info() << "Перезагрузка ассетов";
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
AssetsPreloader::Out_applyResourceChange applied
|
AssetsManager::Out_checkAndPrepareResourcesUpdate capru = Content.AM.checkAndPrepareResourcesUpdate(AssetsInit);
|
||||||
= Content.AM.applyResourceChange(Content.AM.reloadResources(AssetsInit));
|
AssetsManager::Out_applyResourcesUpdate aru = Content.AM.applyResourcesUpdate(capru);
|
||||||
|
|
||||||
if(!applied.NewOrChange.empty() || !applied.Lost.empty())
|
if(!capru.ResourceUpdates.empty() || !capru.LostLinks.empty())
|
||||||
packetsToSend.push_back(
|
packetsToSend.push_back(
|
||||||
RemoteClient::makePacket_informateAssets_HH(
|
RemoteClient::makePacket_informateAssets_HH(
|
||||||
applied.NewOrChange,
|
aru.NewOrUpdates,
|
||||||
applied.Lost
|
capru.LostLinks
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
AssetsPreloader::Out_bakeId baked = Content.AM.bakeIdTables();
|
std::array<
|
||||||
if(hasAnyBindings(baked.IdToDK)) {
|
std::vector<AssetsManager::BindDomainKeyInfo>,
|
||||||
packetsToSend.push_back(RemoteClient::makePacket_informateAssets_DK(baked.IdToDK));
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> baked = Content.AM.bake();
|
||||||
|
|
||||||
|
if(hasAnyBindings(baked)) {
|
||||||
|
packetsToSend.push_back(RemoteClient::makePacket_informateAssets_DK(baked));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2495,9 +2499,13 @@ void GameServer::stepSyncContent() {
|
|||||||
|
|
||||||
std::vector<Net::Packet> packetsToAll;
|
std::vector<Net::Packet> packetsToAll;
|
||||||
{
|
{
|
||||||
AssetsPreloader::Out_bakeId baked = Content.AM.bakeIdTables();
|
std::array<
|
||||||
if(hasAnyBindings(baked.IdToDK)) {
|
std::vector<AssetsManager::BindDomainKeyInfo>,
|
||||||
packetsToAll.push_back(RemoteClient::makePacket_informateAssets_DK(baked.IdToDK));
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> baked = Content.AM.bake();
|
||||||
|
|
||||||
|
if(hasAnyBindings(baked)) {
|
||||||
|
packetsToAll.push_back(RemoteClient::makePacket_informateAssets_DK(baked));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2515,30 +2523,8 @@ void GameServer::stepSyncContent() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<AssetBinaryInfo> binaryResources;
|
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>> binaryResources
|
||||||
for(const Hash_t& hash : full.Hashes) {
|
= Content.AM.getResources(full.Hashes);
|
||||||
std::optional<
|
|
||||||
std::tuple<EnumAssets, uint32_t, const AssetsPreloader::MediaResource*>
|
|
||||||
> result = Content.AM.getResource(hash);
|
|
||||||
|
|
||||||
if(!result)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto& [type, id, media] = *result;
|
|
||||||
LOG.debug() << "Server sending type=" << assetTypeName(type)
|
|
||||||
<< " id=" << id
|
|
||||||
<< " key=" << media->Domain << ':' << media->Key
|
|
||||||
<< " hash=" << int(media->Hash[0]) << '.'
|
|
||||||
<< int(media->Hash[1]) << '.'
|
|
||||||
<< int(media->Hash[2]) << '.'
|
|
||||||
<< int(media->Hash[3])
|
|
||||||
<< " size=" << media->Resource->size();
|
|
||||||
Resource resource(*media->Resource);
|
|
||||||
binaryResources.push_back(AssetBinaryInfo{
|
|
||||||
.Data = std::move(resource),
|
|
||||||
.Hash = media->Hash
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||||||
if(!binaryResources.empty())
|
if(!binaryResources.empty())
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include "RemoteClient.hpp"
|
#include "RemoteClient.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "Server/Abstract.hpp"
|
||||||
#include <TOSLib.hpp>
|
#include <TOSLib.hpp>
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <set>
|
#include <set>
|
||||||
@@ -25,6 +24,7 @@
|
|||||||
|
|
||||||
#include "WorldDefManager.hpp"
|
#include "WorldDefManager.hpp"
|
||||||
#include "ContentManager.hpp"
|
#include "ContentManager.hpp"
|
||||||
|
#include "AssetsManager.hpp"
|
||||||
#include "World.hpp"
|
#include "World.hpp"
|
||||||
|
|
||||||
#include "SaveBackend.hpp"
|
#include "SaveBackend.hpp"
|
||||||
@@ -73,7 +73,7 @@ class GameServer : public AsyncObject {
|
|||||||
|
|
||||||
struct ContentObj {
|
struct ContentObj {
|
||||||
public:
|
public:
|
||||||
AssetsPreloader AM;
|
AssetsManager AM;
|
||||||
ContentManager CM;
|
ContentManager CM;
|
||||||
|
|
||||||
// Если контент был перерегистрирован (исключая двоичные ресурсы), то профили будут повторно разосланы
|
// Если контент был перерегистрирован (исключая двоичные ресурсы), то профили будут повторно разосланы
|
||||||
|
|||||||
@@ -11,13 +11,14 @@
|
|||||||
#include <boost/system/system_error.hpp>
|
#include <boost/system/system_error.hpp>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <Common/Packets.hpp>
|
#include <Common/Packets.hpp>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
Net::Packet RemoteClient::makePacket_informateAssets_DK(
|
Net::Packet RemoteClient::makePacket_informateAssets_DK(
|
||||||
const std::array<
|
const std::array<
|
||||||
std::vector<AssetsPreloader::BindDomainKeyInfo>,
|
std::vector<AssetsManager::BindDomainKeyInfo>,
|
||||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
>& dkVector
|
>& dkVector
|
||||||
) {
|
) {
|
||||||
@@ -46,7 +47,7 @@ Net::Packet RemoteClient::makePacket_informateAssets_DK(
|
|||||||
|
|
||||||
// Запись связок домен+ключ
|
// Запись связок домен+ключ
|
||||||
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++) {
|
||||||
const std::vector<AssetsPreloader::BindDomainKeyInfo>& binds = dkVector[type];
|
const std::vector<AssetsManager::BindDomainKeyInfo>& binds = dkVector[type];
|
||||||
pack << uint32_t(binds.size());
|
pack << uint32_t(binds.size());
|
||||||
|
|
||||||
for(const auto& bind : binds) {
|
for(const auto& bind : binds) {
|
||||||
@@ -67,7 +68,7 @@ Net::Packet RemoteClient::makePacket_informateAssets_DK(
|
|||||||
|
|
||||||
Net::Packet RemoteClient::makePacket_informateAssets_HH(
|
Net::Packet RemoteClient::makePacket_informateAssets_HH(
|
||||||
const std::array<
|
const std::array<
|
||||||
std::vector<AssetsPreloader::BindHashHeaderInfo>,
|
std::vector<AssetsManager::BindHashHeaderInfo>,
|
||||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
>& hhVector,
|
>& hhVector,
|
||||||
const std::array<
|
const std::array<
|
||||||
@@ -430,21 +431,21 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
|
|||||||
return std::move(nextRequest);
|
return std::move(nextRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoteClient::informateBinaryAssets(const std::vector<AssetBinaryInfo>& resources)
|
void RemoteClient::informateBinaryAssets(const std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>>& resources)
|
||||||
{
|
{
|
||||||
for(const AssetBinaryInfo& resource : resources) {
|
for(const auto& [hash, resource] : resources) {
|
||||||
auto lock = NetworkAndResource.lock();
|
auto lock = NetworkAndResource.lock();
|
||||||
auto iter = std::find(lock->ClientRequested.begin(), lock->ClientRequested.end(), resource.Hash);
|
auto iter = std::find(lock->ClientRequested.begin(), lock->ClientRequested.end(), hash);
|
||||||
if(iter == lock->ClientRequested.end())
|
if(iter == lock->ClientRequested.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
lock->ClientRequested.erase(iter);
|
lock->ClientRequested.erase(iter);
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
auto it = std::lower_bound(AssetsInWork.OnClient.begin(), AssetsInWork.OnClient.end(), resource.Hash);
|
auto it = std::lower_bound(AssetsInWork.OnClient.begin(), AssetsInWork.OnClient.end(), hash);
|
||||||
if(it == AssetsInWork.OnClient.end() || *it != resource.Hash) {
|
if(it == AssetsInWork.OnClient.end() || *it != hash) {
|
||||||
AssetsInWork.OnClient.insert(it, resource.Hash);
|
AssetsInWork.OnClient.insert(it, hash);
|
||||||
AssetsInWork.ToSend.emplace_back(resource.Data, 0);
|
AssetsInWork.ToSend.emplace_back(hash, resource, 0);
|
||||||
} else {
|
} else {
|
||||||
LOG.warn() << "Клиент повторно запросил имеющийся у него ресурс";
|
LOG.warn() << "Клиент повторно запросил имеющийся у него ресурс";
|
||||||
}
|
}
|
||||||
@@ -611,36 +612,36 @@ void RemoteClient::onUpdate() {
|
|||||||
|
|
||||||
bool hasFullSended = false;
|
bool hasFullSended = false;
|
||||||
|
|
||||||
for(auto& [res, sended] : toSend) {
|
for(auto& [hash, res, sended] : toSend) {
|
||||||
if(sended == 0) {
|
if(sended == 0) {
|
||||||
// Оповещаем о начале отправки ресурса
|
// Оповещаем о начале отправки ресурса
|
||||||
const size_t initSize = 1 + 1 + 4 + 32 + 4 + 1;
|
const size_t initSize = 1 + 1 + 4 + 32 + 4 + 1;
|
||||||
if(p.size() + initSize > kMaxAssetPacketSize)
|
if(p.size() + initSize > kMaxAssetPacketSize)
|
||||||
flushAssetsPacket();
|
flushAssetsPacket();
|
||||||
p << (uint8_t) ToClient::AssetsInitSend
|
p << (uint8_t) ToClient::AssetsInitSend
|
||||||
<< uint32_t(res.size());
|
<< uint32_t(res->size());
|
||||||
p.write((const std::byte*) res.hash().data(), 32);
|
p.write((const std::byte*) hash.data(), 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отправляем чанк
|
// Отправляем чанк
|
||||||
size_t willSend = std::min(chunkSize, res.size()-sended);
|
size_t willSend = std::min(chunkSize, res->size()-sended);
|
||||||
const size_t chunkMsgSize = 1 + 1 + 32 + 4 + willSend;
|
const size_t chunkMsgSize = 1 + 1 + 32 + 4 + willSend;
|
||||||
if(p.size() + chunkMsgSize > kMaxAssetPacketSize)
|
if(p.size() + chunkMsgSize > kMaxAssetPacketSize)
|
||||||
flushAssetsPacket();
|
flushAssetsPacket();
|
||||||
p << (uint8_t) ToClient::AssetsNextSend;
|
p << (uint8_t) ToClient::AssetsNextSend;
|
||||||
p.write((const std::byte*) res.hash().data(), 32);
|
p.write((const std::byte*) hash.data(), 32);
|
||||||
p << uint32_t(willSend);
|
p << uint32_t(willSend);
|
||||||
p.write(res.data() + sended, willSend);
|
p.write((const std::byte*) res->data() + sended, willSend);
|
||||||
sended += willSend;
|
sended += willSend;
|
||||||
|
|
||||||
if(sended == res.size()) {
|
if(sended == res->size()) {
|
||||||
hasFullSended = true;
|
hasFullSended = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(hasFullSended) {
|
if(hasFullSended) {
|
||||||
for(ssize_t iter = toSend.size()-1; iter >= 0; iter--) {
|
for(ssize_t iter = toSend.size()-1; iter >= 0; iter--) {
|
||||||
if(std::get<0>(toSend[iter]).size() == std::get<1>(toSend[iter])) {
|
if(std::get<1>(toSend[iter])->size() == std::get<2>(toSend[iter])) {
|
||||||
toSend.erase(toSend.begin()+iter);
|
toSend.erase(toSend.begin()+iter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <Common/Net.hpp>
|
#include <Common/Net.hpp>
|
||||||
#include "Abstract.hpp"
|
#include "Abstract.hpp"
|
||||||
#include "Common/Packets.hpp"
|
#include "Common/Packets.hpp"
|
||||||
|
#include "Server/AssetsManager.hpp"
|
||||||
#include "Server/ContentManager.hpp"
|
#include "Server/ContentManager.hpp"
|
||||||
#include <Common/Abstract.hpp>
|
#include <Common/Abstract.hpp>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
@@ -256,7 +257,7 @@ class RemoteClient {
|
|||||||
std::vector<Hash_t> OnClient;
|
std::vector<Hash_t> OnClient;
|
||||||
// Отправляемые на клиент ресурсы
|
// Отправляемые на клиент ресурсы
|
||||||
// Ресурс, количество отправленных байт
|
// Ресурс, количество отправленных байт
|
||||||
std::vector<std::tuple<Resource, size_t>> ToSend;
|
std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>, size_t>> ToSend;
|
||||||
// Пакет с ресурсами
|
// Пакет с ресурсами
|
||||||
std::vector<Net::Packet> AssetsPackets;
|
std::vector<Net::Packet> AssetsPackets;
|
||||||
Net::Packet AssetsPacket;
|
Net::Packet AssetsPacket;
|
||||||
@@ -361,7 +362,7 @@ public:
|
|||||||
// Создаёт пакет для всех игроков с оповещением о новых идентификаторах (id -> domain+key)
|
// Создаёт пакет для всех игроков с оповещением о новых идентификаторах (id -> domain+key)
|
||||||
static Net::Packet makePacket_informateAssets_DK(
|
static Net::Packet makePacket_informateAssets_DK(
|
||||||
const std::array<
|
const std::array<
|
||||||
std::vector<AssetsPreloader::BindDomainKeyInfo>,
|
std::vector<AssetsManager::BindDomainKeyInfo>,
|
||||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
>& dkVector
|
>& dkVector
|
||||||
);
|
);
|
||||||
@@ -369,7 +370,7 @@ public:
|
|||||||
// Создаёт пакет для всех игроков с оповещением об изменении файлов ресурсов (id -> hash+header)
|
// Создаёт пакет для всех игроков с оповещением об изменении файлов ресурсов (id -> hash+header)
|
||||||
static Net::Packet makePacket_informateAssets_HH(
|
static Net::Packet makePacket_informateAssets_HH(
|
||||||
const std::array<
|
const std::array<
|
||||||
std::vector<AssetsPreloader::BindHashHeaderInfo>,
|
std::vector<AssetsManager::BindHashHeaderInfo>,
|
||||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
>& hhVector,
|
>& hhVector,
|
||||||
const std::array<
|
const std::array<
|
||||||
@@ -380,7 +381,7 @@ public:
|
|||||||
|
|
||||||
// Оповещение о двоичных ресурсах (стриминг по запросу)
|
// Оповещение о двоичных ресурсах (стриминг по запросу)
|
||||||
void informateBinaryAssets(
|
void informateBinaryAssets(
|
||||||
const std::vector<AssetBinaryInfo>& resources
|
const std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>>& resources
|
||||||
);
|
);
|
||||||
|
|
||||||
// Создаёт пакет об обновлении игровых профилей
|
// Создаёт пакет об обновлении игровых профилей
|
||||||
|
|||||||
Reference in New Issue
Block a user