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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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