Compare commits

..

3 Commits

18 changed files with 3260 additions and 2366 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<
@@ -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) или новые ресурсы // 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;
};
}

483
Src/Common/Net2.cpp Normal file
View File

@@ -0,0 +1,483 @@
#include "Net2.hpp"
#include <boost/asio/buffer.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/write.hpp>
#include <boost/system/system_error.hpp>
#include <algorithm>
#include <tuple>
namespace LV::Net2 {
using namespace TOS;
namespace {
struct HeaderFields {
uint32_t size = 0;
uint16_t type = 0;
Priority priority = Priority::Normal;
FrameFlags flags = FrameFlags::None;
uint32_t streamId = 0;
};
std::array<std::byte, AsyncSocket::kHeaderSize> encodeHeader(const HeaderFields &h) {
std::array<std::byte, AsyncSocket::kHeaderSize> out{};
uint32_t sizeNet = detail::toNetwork(h.size);
uint16_t typeNet = detail::toNetwork(h.type);
uint32_t streamNet = detail::toNetwork(h.streamId);
std::memcpy(out.data(), &sizeNet, sizeof(sizeNet));
std::memcpy(out.data() + 4, &typeNet, sizeof(typeNet));
out[6] = std::byte(static_cast<uint8_t>(h.priority));
out[7] = std::byte(static_cast<uint8_t>(h.flags));
std::memcpy(out.data() + 8, &streamNet, sizeof(streamNet));
return out;
}
HeaderFields decodeHeader(const std::array<std::byte, AsyncSocket::kHeaderSize> &in) {
HeaderFields h{};
std::memcpy(&h.size, in.data(), sizeof(h.size));
std::memcpy(&h.type, in.data() + 4, sizeof(h.type));
h.priority = static_cast<Priority>(std::to_integer<uint8_t>(in[6]));
h.flags = static_cast<FrameFlags>(std::to_integer<uint8_t>(in[7]));
std::memcpy(&h.streamId, in.data() + 8, sizeof(h.streamId));
h.size = detail::fromNetwork(h.size);
h.type = detail::fromNetwork(h.type);
h.streamId = detail::fromNetwork(h.streamId);
return h;
}
} // namespace
PacketWriter& PacketWriter::writeBytes(std::span<const std::byte> data) {
Buffer.insert(Buffer.end(), data.begin(), data.end());
return *this;
}
PacketWriter& PacketWriter::writeString(std::string_view str) {
write<uint32_t>(static_cast<uint32_t>(str.size()));
auto bytes = std::as_bytes(std::span<const char>(str.data(), str.size()));
Buffer.insert(Buffer.end(), bytes.begin(), bytes.end());
return *this;
}
std::vector<std::byte> PacketWriter::release() {
std::vector<std::byte> out = std::move(Buffer);
Buffer.clear();
return out;
}
void PacketWriter::clear() {
Buffer.clear();
}
PacketReader::PacketReader(std::span<const std::byte> data)
: Data(data)
{
}
void PacketReader::readBytes(std::span<std::byte> out) {
require(out.size());
std::memcpy(out.data(), Data.data() + Pos, out.size());
Pos += out.size();
}
std::string PacketReader::readString() {
uint32_t size = read<uint32_t>();
require(size);
std::string out(size, '\0');
std::memcpy(out.data(), Data.data() + Pos, size);
Pos += size;
return out;
}
void PacketReader::require(size_t size) {
if(Data.size() - Pos < size)
MAKE_ERROR("Net2::PacketReader: not enough data");
}
SocketServer::SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port)
: AsyncObject(ioc), Acceptor(ioc, tcp::endpoint(tcp::v4(), port))
{
assert(onConnect);
co_spawn(run(std::move(onConnect)));
}
bool SocketServer::isStopped() const {
return !Acceptor.is_open();
}
uint16_t SocketServer::getPort() const {
return Acceptor.local_endpoint().port();
}
coro<void> SocketServer::run(std::function<coro<>(tcp::socket)> onConnect) {
while(true) {
try {
co_spawn(onConnect(co_await Acceptor.async_accept()));
} catch(const std::exception &exc) {
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc);
errc && (errc->code() == asio::error::operation_aborted || errc->code() == asio::error::bad_descriptor))
break;
}
}
}
AsyncSocket::SendQueue::SendQueue(asio::io_context &ioc)
: semaphore(ioc)
{
semaphore.expires_at(std::chrono::steady_clock::time_point::max());
}
bool AsyncSocket::SendQueue::empty() const {
for(const auto &queue : queues) {
if(!queue.empty())
return false;
}
return true;
}
AsyncSocket::AsyncSocket(asio::io_context &ioc, tcp::socket &&socket, Limits limits)
: AsyncObject(ioc), LimitsCfg(limits), Socket(std::move(socket)), Outgoing(ioc)
{
Context = std::make_shared<AsyncContext>();
boost::asio::socket_base::linger optionLinger(true, 4);
Socket.set_option(optionLinger);
boost::asio::ip::tcp::no_delay optionNoDelay(true);
Socket.set_option(optionNoDelay);
co_spawn(sendLoop());
}
AsyncSocket::~AsyncSocket() {
if(Context)
Context->needShutdown.store(true);
{
boost::lock_guard lock(Outgoing.mtx);
Outgoing.semaphore.cancel();
WorkDeadline.cancel();
}
if(Socket.is_open())
try { Socket.close(); } catch(...) {}
}
void AsyncSocket::enqueue(OutgoingMessage &&msg) {
if(msg.payload.size() > LimitsCfg.maxMessageSize) {
setError("Net2::AsyncSocket: message too large");
close();
return;
}
boost::unique_lock lock(Outgoing.mtx);
const size_t msgSize = msg.payload.size();
const size_t lowIndex = static_cast<size_t>(Priority::Low);
if(msg.priority == Priority::Low) {
while(Outgoing.bytesInLow + msgSize > LimitsCfg.maxLowPriorityBytes && !Outgoing.queues[lowIndex].empty()) {
Outgoing.bytesInQueue -= Outgoing.queues[lowIndex].front().payload.size();
Outgoing.bytesInLow -= Outgoing.queues[lowIndex].front().payload.size();
Outgoing.queues[lowIndex].pop_front();
}
if(Outgoing.bytesInLow + msgSize > LimitsCfg.maxLowPriorityBytes) {
return;
}
}
if(Outgoing.bytesInQueue + msgSize > LimitsCfg.maxQueueBytes) {
dropLow(msgSize);
if(Outgoing.bytesInQueue + msgSize > LimitsCfg.maxQueueBytes) {
if(msg.dropIfOverloaded)
return;
setError("Net2::AsyncSocket: send queue overflow");
close();
return;
}
}
const size_t idx = static_cast<size_t>(msg.priority);
Outgoing.bytesInQueue += msgSize;
if(msg.priority == Priority::Low)
Outgoing.bytesInLow += msgSize;
Outgoing.queues[idx].push_back(std::move(msg));
if(Outgoing.waiting) {
Outgoing.waiting = false;
Outgoing.semaphore.cancel();
Outgoing.semaphore.expires_at(std::chrono::steady_clock::time_point::max());
}
}
coro<IncomingMessage> AsyncSocket::readMessage() {
while(true) {
std::array<std::byte, kHeaderSize> headerBytes{};
co_await readExact(headerBytes.data(), headerBytes.size());
HeaderFields header = decodeHeader(headerBytes);
if(header.size > LimitsCfg.maxFrameSize)
MAKE_ERROR("Net2::AsyncSocket: frame too large");
std::vector<std::byte> chunk(header.size);
if(header.size)
co_await readExact(chunk.data(), chunk.size());
if(header.streamId != 0) {
if(Fragments.size() >= LimitsCfg.maxOpenStreams && !Fragments.contains(header.streamId))
MAKE_ERROR("Net2::AsyncSocket: too many open streams");
FragmentState &state = Fragments[header.streamId];
if(state.data.empty()) {
state.type = header.type;
state.priority = header.priority;
}
if(state.data.size() + chunk.size() > LimitsCfg.maxMessageSize)
MAKE_ERROR("Net2::AsyncSocket: reassembled message too large");
state.data.insert(state.data.end(), chunk.begin(), chunk.end());
if(!hasFlag(header.flags, FrameFlags::HasMore)) {
IncomingMessage msg{state.type, state.priority, std::move(state.data)};
Fragments.erase(header.streamId);
co_return msg;
}
continue;
}
if(hasFlag(header.flags, FrameFlags::HasMore))
MAKE_ERROR("Net2::AsyncSocket: stream id missing for fragmented frame");
IncomingMessage msg{header.type, header.priority, std::move(chunk)};
co_return msg;
}
}
coro<> AsyncSocket::readLoop(std::function<coro<>(IncomingMessage&&)> onMessage) {
while(isAlive()) {
IncomingMessage msg = co_await readMessage();
co_await onMessage(std::move(msg));
}
}
void AsyncSocket::closeRead() {
if(Socket.is_open() && !Context->readClosed.exchange(true)) {
try { Socket.shutdown(boost::asio::socket_base::shutdown_receive); } catch(...) {}
}
}
void AsyncSocket::close() {
if(Context)
Context->needShutdown.store(true);
if(Socket.is_open())
try { Socket.close(); } catch(...) {}
}
bool AsyncSocket::isAlive() const {
return Context && !Context->needShutdown.load() && !Context->senderStopped.load() && Socket.is_open();
}
std::string AsyncSocket::getError() const {
boost::lock_guard lock(Context->errorMtx);
return Context->error;
}
coro<> AsyncSocket::sendLoop() {
try {
while(!Context->needShutdown.load()) {
OutgoingMessage msg;
{
boost::unique_lock lock(Outgoing.mtx);
if(Outgoing.empty()) {
Outgoing.waiting = true;
auto coroutine = Outgoing.semaphore.async_wait();
lock.unlock();
try { co_await std::move(coroutine); } catch(...) {}
continue;
}
if(!popNext(msg))
continue;
}
co_await sendMessage(std::move(msg));
}
} catch(const std::exception &exc) {
setError(exc.what());
} catch(...) {
setError("Net2::AsyncSocket: send loop stopped");
}
Context->senderStopped.store(true);
}
coro<> AsyncSocket::sendMessage(OutgoingMessage &&msg) {
const size_t total = msg.payload.size();
if(total <= LimitsCfg.maxFrameSize) {
co_await sendFrame(msg.type, msg.priority, FrameFlags::None, 0, msg.payload);
co_return;
}
if(!msg.allowFragment) {
setError("Net2::AsyncSocket: message requires fragmentation");
close();
co_return;
}
uint32_t streamId = NextStreamId++;
if(streamId == 0)
streamId = NextStreamId++;
size_t offset = 0;
while(offset < total) {
const size_t chunk = std::min(LimitsCfg.maxFrameSize, total - offset);
const bool more = (offset + chunk) < total;
FrameFlags flags = more ? FrameFlags::HasMore : FrameFlags::None;
std::span<const std::byte> view(msg.payload.data() + offset, chunk);
co_await sendFrame(msg.type, msg.priority, flags, streamId, view);
offset += chunk;
}
}
coro<> AsyncSocket::sendFrame(uint16_t type, Priority priority, FrameFlags flags, uint32_t streamId,
std::span<const std::byte> payload) {
HeaderFields header{
.size = static_cast<uint32_t>(payload.size()),
.type = type,
.priority = priority,
.flags = flags,
.streamId = streamId
};
auto headerBytes = encodeHeader(header);
std::array<asio::const_buffer, 2> buffers{
asio::buffer(headerBytes),
asio::buffer(payload.data(), payload.size())
};
if(payload.empty())
co_await asio::async_write(Socket, asio::buffer(headerBytes));
else
co_await asio::async_write(Socket, buffers);
}
coro<> AsyncSocket::readExact(std::byte *data, size_t size) {
if(size == 0)
co_return;
co_await asio::async_read(Socket, asio::buffer(data, size));
}
bool AsyncSocket::popNext(OutgoingMessage &out) {
static constexpr int kWeights[4] = {8, 4, 2, 1};
for(int attempt = 0; attempt < 4; ++attempt) {
const uint8_t idx = static_cast<uint8_t>((Outgoing.nextIndex + attempt) % 4);
auto &queue = Outgoing.queues[idx];
if(queue.empty())
continue;
if(Outgoing.credits[idx] <= 0)
Outgoing.credits[idx] = kWeights[idx];
if(Outgoing.credits[idx] <= 0)
continue;
out = std::move(queue.front());
queue.pop_front();
Outgoing.credits[idx]--;
Outgoing.nextIndex = idx;
const size_t msgSize = out.payload.size();
Outgoing.bytesInQueue -= msgSize;
if(idx == static_cast<uint8_t>(Priority::Low))
Outgoing.bytesInLow -= msgSize;
return true;
}
for(int i = 0; i < 4; ++i)
Outgoing.credits[i] = kWeights[i];
return false;
}
void AsyncSocket::dropLow(size_t needBytes) {
const size_t lowIndex = static_cast<size_t>(Priority::Low);
while(Outgoing.bytesInQueue + needBytes > LimitsCfg.maxQueueBytes && !Outgoing.queues[lowIndex].empty()) {
const size_t size = Outgoing.queues[lowIndex].front().payload.size();
Outgoing.bytesInQueue -= size;
Outgoing.bytesInLow -= size;
Outgoing.queues[lowIndex].pop_front();
}
}
void AsyncSocket::setError(const std::string &msg) {
if(!Context)
return;
boost::lock_guard lock(Context->errorMtx);
Context->error = msg;
}
coro<tcp::socket> asyncConnectTo(const std::string &address,
std::function<void(const std::string&)> onProgress) {
std::string progress;
auto addLog = [&](const std::string &msg) {
progress += '\n';
progress += msg;
if(onProgress)
onProgress('\n' + msg);
};
auto ioc = co_await asio::this_coro::executor;
addLog("Parsing address " + address);
auto re = Str::match(address, "((?:\\[[\\d\\w:]+\\])|(?:[\\d\\.]+))(?:\\:(\\d+))?");
std::vector<std::tuple<tcp::endpoint, std::string>> eps;
if(!re) {
re = Str::match(address, "([-_\\.\\w\\d]+)(?:\\:(\\d+))?");
if(!re)
MAKE_ERROR("Failed to parse address");
tcp::resolver resv{ioc};
tcp::resolver::results_type result;
addLog("Resolving name...");
result = co_await resv.async_resolve(*re->at(1), re->at(2) ? *re->at(2) : "7890");
addLog("Got " + std::to_string(result.size()) + " endpoints");
for(auto iter : result) {
std::string addr = iter.endpoint().address().to_string() + ':' + std::to_string(iter.endpoint().port());
std::string hostname = iter.host_name();
if(hostname == addr)
addLog("ep: " + addr);
else
addLog("ep: " + hostname + " (" + addr + ')');
eps.emplace_back(iter.endpoint(), iter.host_name());
}
} else {
eps.emplace_back(tcp::endpoint{asio::ip::make_address(*re->at(1)),
static_cast<uint16_t>(re->at(2) ? Str::toVal<int>(*re->at(2)) : 7890)},
*re->at(1));
}
for(auto [ep, hostname] : eps) {
addLog("Connecting to " + hostname + " (" + ep.address().to_string() + ':'
+ std::to_string(ep.port()) + ")");
try {
tcp::socket sock{ioc};
co_await sock.async_connect(ep);
addLog("Connected");
co_return sock;
} catch(const std::exception &exc) {
addLog(std::string("Connect failed: ") + exc.what());
}
}
MAKE_ERROR("Unable to connect to server");
}
} // namespace LV::Net2

227
Src/Common/Net2.hpp Normal file
View File

@@ -0,0 +1,227 @@
#pragma once
#include "Async.hpp"
#include "TOSLib.hpp"
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <array>
#include <bit>
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <deque>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <vector>
namespace LV::Net2 {
namespace detail {
constexpr bool kLittleEndian = (std::endian::native == std::endian::little);
template<typename T>
requires std::is_integral_v<T>
inline T toNetwork(T value) {
if constexpr (kLittleEndian && sizeof(T) > 1)
return std::byteswap(value);
return value;
}
template<typename T>
requires std::is_floating_point_v<T>
inline T toNetwork(T value) {
using U = std::conditional_t<sizeof(T) == 4, uint32_t, uint64_t>;
U u = std::bit_cast<U>(value);
u = toNetwork(u);
return std::bit_cast<T>(u);
}
template<typename T>
inline T fromNetwork(T value) {
return toNetwork(value);
}
} // namespace detail
enum class Priority : uint8_t {
Realtime = 0,
High = 1,
Normal = 2,
Low = 3
};
enum class FrameFlags : uint8_t {
None = 0,
HasMore = 1
};
inline FrameFlags operator|(FrameFlags a, FrameFlags b) {
return static_cast<FrameFlags>(static_cast<uint8_t>(a) | static_cast<uint8_t>(b));
}
inline bool hasFlag(FrameFlags value, FrameFlags flag) {
return (static_cast<uint8_t>(value) & static_cast<uint8_t>(flag)) != 0;
}
struct Limits {
size_t maxFrameSize = 1 << 24;
size_t maxMessageSize = 1 << 26;
size_t maxQueueBytes = 1 << 27;
size_t maxLowPriorityBytes = 1 << 26;
size_t maxOpenStreams = 64;
};
struct OutgoingMessage {
uint16_t type = 0;
Priority priority = Priority::Normal;
bool dropIfOverloaded = false;
bool allowFragment = true;
std::vector<std::byte> payload;
};
struct IncomingMessage {
uint16_t type = 0;
Priority priority = Priority::Normal;
std::vector<std::byte> payload;
};
class PacketWriter {
public:
PacketWriter& writeBytes(std::span<const std::byte> data);
template<typename T>
requires (std::is_integral_v<T> || std::is_floating_point_v<T>)
PacketWriter& write(T value) {
T net = detail::toNetwork(value);
std::array<std::byte, sizeof(T)> bytes{};
std::memcpy(bytes.data(), &net, sizeof(T));
Buffer.insert(Buffer.end(), bytes.begin(), bytes.end());
return *this;
}
PacketWriter& writeString(std::string_view str);
const std::vector<std::byte>& data() const { return Buffer; }
std::vector<std::byte> release();
void clear();
private:
std::vector<std::byte> Buffer;
};
class PacketReader {
public:
explicit PacketReader(std::span<const std::byte> data);
template<typename T>
requires (std::is_integral_v<T> || std::is_floating_point_v<T>)
T read() {
require(sizeof(T));
T net{};
std::memcpy(&net, Data.data() + Pos, sizeof(T));
Pos += sizeof(T);
return detail::fromNetwork(net);
}
void readBytes(std::span<std::byte> out);
std::string readString();
bool empty() const { return Pos >= Data.size(); }
size_t remaining() const { return Data.size() - Pos; }
private:
void require(size_t size);
size_t Pos = 0;
std::span<const std::byte> Data;
};
class SocketServer : public AsyncObject {
public:
SocketServer(asio::io_context &ioc, std::function<coro<>(tcp::socket)> &&onConnect, uint16_t port = 0);
bool isStopped() const;
uint16_t getPort() const;
private:
coro<void> run(std::function<coro<>(tcp::socket)> onConnect);
tcp::acceptor Acceptor;
};
class AsyncSocket : public AsyncObject {
public:
static constexpr size_t kHeaderSize = 12;
AsyncSocket(asio::io_context &ioc, tcp::socket &&socket, Limits limits = {});
~AsyncSocket();
void enqueue(OutgoingMessage &&msg);
coro<IncomingMessage> readMessage();
coro<> readLoop(std::function<coro<>(IncomingMessage&&)> onMessage);
void closeRead();
void close();
bool isAlive() const;
std::string getError() const;
private:
struct FragmentState {
uint16_t type = 0;
Priority priority = Priority::Normal;
std::vector<std::byte> data;
};
struct AsyncContext {
std::atomic_bool needShutdown{false};
std::atomic_bool senderStopped{false};
std::atomic_bool readClosed{false};
boost::mutex errorMtx;
std::string error;
};
struct SendQueue {
boost::mutex mtx;
bool waiting = false;
asio::steady_timer semaphore;
std::deque<OutgoingMessage> queues[4];
size_t bytesInQueue = 0;
size_t bytesInLow = 0;
uint8_t nextIndex = 0;
int credits[4] = {8, 4, 2, 1};
explicit SendQueue(asio::io_context &ioc);
bool empty() const;
};
coro<> sendLoop();
coro<> sendMessage(OutgoingMessage &&msg);
coro<> sendFrame(uint16_t type, Priority priority, FrameFlags flags, uint32_t streamId,
std::span<const std::byte> payload);
coro<> readExact(std::byte *data, size_t size);
bool popNext(OutgoingMessage &out);
void dropLow(size_t needBytes);
void setError(const std::string &msg);
Limits LimitsCfg;
tcp::socket Socket;
SendQueue Outgoing;
std::shared_ptr<AsyncContext> Context;
std::unordered_map<uint32_t, FragmentState> Fragments;
uint32_t NextStreamId = 1;
};
coro<tcp::socket> asyncConnectTo(const std::string &address,
std::function<void(const std::string&)> onProgress = nullptr);
} // namespace LV::Net2

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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