Рефакторинг кода работы с ресурсами игры на стороне сервера

This commit is contained in:
2026-01-06 18:21:25 +06:00
parent 83530a6c15
commit c13ad06ba9
14 changed files with 656 additions and 578 deletions

View File

@@ -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();

View File

@@ -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;

View File

@@ -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; // Количество возможный значений состояния

View File

@@ -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<
@@ -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) или новые ресурсы // 2) Определяем какие ресурсы изменились (новый timestamp) или новые ресурсы
// Определяем каких ресурсов не стало Out_checkAndPrepareResourcesUpdate result;
// Собираем идентификаторы, чтобы потом определить какие ресурсы пропали
std::array<
std::unordered_set<ResourceId>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> uniqueExists;
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; {
size_t allIds = 0;
for(const auto& [domain, keys] : resourcesFirstStage[type])
allIds += keys.size();
uniqueExistsTypes.reserve(allIds);
} }
auto iterDomain = tableResourcesFirstStage.find(resource.Domain); for(const auto& [domain, keys] : resourcesFirstStage[type]) {
if(iterDomain == tableResourcesFirstStage.end()) { for(const auto& [key, res] : keys) {
result.Lost[type][resource.Domain].push_back(resource.Key); uniqueExistsTypes.insert(res.Id);
continue;
}
if(!iterDomain->second.contains(resource.Key)) { if(res.Id >= resourceLinksTyped.size() || !std::get<bool>(resourceLinksTyped[res.Id]))
result.Lost[type][resource.Domain].push_back(resource.Key); { // Если идентификатора нет в таблице или ресурс не привязан
} PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
} onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
} result.HashToPathNew[resource.Hash].push_back(res.Path);
// Определение новых или изменённых ресурсов if(res.Id >= result.MaxNewSize[type])
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) { result.MaxNewSize[type] = res.Id+1;
for(const auto& [domain, table] : resourcesFirstStage[type]) {
auto iterTableDomain = DKToId[type].find(domain); result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
if(iterTableDomain == DKToId[type].end()) { } else if(
// Домен неизвестен движку, все ресурсы в нём новые std::get<fs::path>(resourceLinksTyped[res.Id]) != res.Path
for(const auto& [key, info] : table) { || std::get<fs::file_time_type>(resourceLinksTyped[res.Id]) != res.Timestamp
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, info); ) { // Если ресурс теперь берётся с другого места или изменилось время изменения файла
result.NewOrChange[type][domain].push_back(std::move(resource)); 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 { } else {
for(const auto& [key, info] : table) { // Ресурс без заголовка никак не изменился.
bool needsUpdate = true;
if(auto iterKey = iterTableDomain->second.find(key); iterKey != iterTableDomain->second.end()) {
// Идентификатор найден
auto iterRes = MediaResources[type].find(iterKey->second);
// Если нашли ресурс по идентификатору и время изменения не поменялось, то он не новый и не изменился
if(iterRes != MediaResources[type].end() && iterRes->second.Timestamp == info.Timestamp)
needsUpdate = false;
} }
if(!needsUpdate) // Чтобы там не поменялось, мог поменятся заголовок. Уведомляем о новой привязке.
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
} else {
// Ресурс не изменился
}
}
}
}
// 3) Определяем какие ресурсы пропали
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
const auto& resourceLinksTyped = ResourceLinks[type];
size_t counter = 0;
for(const auto& [hash, header, timestamp, path, isExist] : resourceLinksTyped) {
size_t id = counter++;
if(!isExist)
continue; continue;
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, info); if(uniqueExists[type].contains(id))
result.NewOrChange[(int) type][domain].push_back(std::move(resource)); continue;
}
} // Ресурс потерян
// Хэш более не доступен по этому расположению.
result.HashToPathLost[hash].push_back(path);
result.LostLinks[type].push_back(id);
} }
} }
return result; return result;
} }
AssetsPreloader::Out_applyResourceChange AssetsPreloader::applyResourceChange(const Out_reloadResources& orr) { AssetsPreloader::Out_applyResourcesUpdate AssetsPreloader::applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) {
Out_applyResourceChange result; Out_applyResourcesUpdate result;
// Удаляем ресурсы for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
/* // Затираем потерянные
Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор for(ResourceId id : orr.LostLinks[type]) {
Уже скомпилированные зависимости к ресурсам не будут assert(id < ResourceLinks[type].size());
перекомпилироваться для смены идентификатора. auto& [hash, header, timestamp, path, isExist] = ResourceLinks[type][id];
Если нужный ресурс появится, то привязка останется. hash = {0};
Новые клиенты не получат ресурс которого нет, header = {};
но он может использоваться timestamp = fs::file_time_type();
*/ path.clear();
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); type++) { isExist = false;
for(const auto& [domain, keys] : orr.Lost[type]) {
auto iterDomain = DKToId[type].find(domain);
// Если уже было решено, что ресурсы были, и стали потерянными, то так и должно быть result.NewOrUpdates[type].emplace_back(id, hash, header);
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++) { if(orr.MaxNewSize[type] > ResourceLinks[type].size()) {
auto& typeTable = DKToId[type]; std::tuple<
for(const auto& [domain, resources] : orr.NewOrChange[type]) { ResourceFile::Hash_t,
auto& domainTable = typeTable[domain]; ResourceHeader,
for(const PendingResource& pending : resources) { fs::file_time_type,
MediaResource resource { fs::path,
.Domain = domain, bool
.Key = std::move(pending.Key), > def{
.Timestamp = pending.Timestamp, ResourceFile::Hash_t{0},
.Resource = std::move(pending.Resource), ResourceHeader(),
.Hash = pending.Hash, fs::file_time_type(),
.Header = std::move(pending.Header) fs::path{""},
false
}; };
auto& table = MediaResources[type]; ResourceLinks[type].resize(orr.MaxNewSize[type], def);
// Нужно затереть старую ссылку хеша на данный ресурс
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 for(auto& [id, hash, header, timestamp, path] : orr.ResourceUpdates[type]) {
std::unordered_set<uint32_t> changed; ResourceLinks[type][id] = {hash, std::move(header), timestamp, std::move(path), true};
for(const auto& [id, _, _2] : result.NewOrChange[type]) result.NewOrUpdates[type].emplace_back(id, hash, header);
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) {
// домен+ключ -> 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.IdToDK[type] = std::move(*lock);
lock->clear();
idToDK.append_range(result.IdToDK[type]);
// result.LastSendId[type] = LastSendId[type];
LastSendId[type] = NextId[type];
} }
} }
return result; return result;
} }
AssetsPreloader::Out_fullSync AssetsPreloader::collectFullSync() const {
Out_fullSync out;
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
out.IdToDK[type] = IdToDK[type];
}
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
for(const auto& [id, resource] : MediaResources[type]) {
out.HashHeaders[type].push_back(BindHashHeaderInfo{
.Id = id,
.Hash = resource.Hash,
.Header = resource.Header
});
out.Resources.emplace_back(
static_cast<AssetType>(type),
id,
&resource
);
}
}
return out;
}
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, {}, {}};
}
} }

View File

@@ -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,59 +120,108 @@ 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
// Тот же пакет для обновления идентификаторов
std::unreachable();
} }
// Выдаёт ресурс по идентификатору return result;
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 {
@@ -262,6 +229,8 @@ private:
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
View 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;
};
}

View File

@@ -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;
}; };

View 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;
};
}

View File

@@ -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;
} }

View File

@@ -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;
}; };
} }

View File

@@ -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())

View File

@@ -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;
// Если контент был перерегистрирован (исключая двоичные ресурсы), то профили будут повторно разосланы // Если контент был перерегистрирован (исключая двоичные ресурсы), то профили будут повторно разосланы

View File

@@ -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);
} }
} }

View File

@@ -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
); );
// Создаёт пакет об обновлении игровых профилей // Создаёт пакет об обновлении игровых профилей