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