From 2759073bb36f54e2498bbd72563aaffbbb2ac7bf Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Sat, 3 Jan 2026 22:17:39 +0600 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B9=D0=BA=D0=B8=20=D0=BA=D0=BE=D0=B4=D0=B5=D0=BA=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 4 +- Src/Client/Abstract.hpp | 2 + Src/Client/AssetsManager.cpp | 207 ++++++++++++++++++++++ Src/Client/AssetsManager.hpp | 47 ++++- Src/Client/ServerSession.cpp | 93 ++++++++-- Src/Client/ServerSession.hpp | 1 + Src/Client/Vulkan/VulkanRenderSession.cpp | 8 +- Src/Client/Vulkan/VulkanRenderSession.hpp | 49 ++++- Src/Common/Abstract.hpp | 96 +++++++--- Src/Server/ContentManager.cpp | 2 +- Src/Server/GameServer.cpp | 12 +- Src/Server/RemoteClient.cpp | 24 ++- Src/Server/RemoteClient.hpp | 16 +- 13 files changed, 492 insertions(+), 69 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ad8a9f..be0a9f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -DGL set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic # gprof -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") +# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") # sanitizer # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") diff --git a/Src/Client/Abstract.hpp b/Src/Client/Abstract.hpp index 446bc4a..72da0d9 100644 --- a/Src/Client/Abstract.hpp +++ b/Src/Client/Abstract.hpp @@ -163,6 +163,8 @@ struct AssetEntry { ResourceId Id; std::string Domain, Key; Resource Res; + Hash_t Hash = {}; + std::vector Dependencies; }; /* diff --git a/Src/Client/AssetsManager.cpp b/Src/Client/AssetsManager.cpp index fbb0765..ee50576 100644 --- a/Src/Client/AssetsManager.cpp +++ b/Src/Client/AssetsManager.cpp @@ -17,6 +17,7 @@ AssetsManager::AssetsManager(boost::asio::io_context &ioc, const fs::path &cache size_t maxCacheDirectorySize, size_t maxLifeTime) : IAsyncDestructible(ioc), CachePath(cachePath) { + NextId.fill(0); { auto lock = Changes.lock(); lock->MaxCacheDatabaseSize = maxCacheDirectorySize; @@ -172,6 +173,212 @@ AssetsManager::~AssetsManager() { LOG.info() << "Хранилище кеша закрыто"; } +ResourceId AssetsManager::getId(EnumAssets type, const std::string& domain, const std::string& key) { + std::lock_guard lock(MapMutex); + auto& typeTable = DKToId[type]; + auto& domainTable = typeTable[domain]; + if(auto iter = domainTable.find(key); iter != domainTable.end()) + return iter->second; + + ResourceId id = NextId[(int) type]++; + domainTable[key] = id; + return id; +} + +std::optional AssetsManager::getLocalIdFromServer(EnumAssets type, ResourceId serverId) const { + std::lock_guard lock(MapMutex); + auto iterType = ServerToLocal.find(type); + if(iterType == ServerToLocal.end()) + return std::nullopt; + auto iter = iterType->second.find(serverId); + if(iter == iterType->second.end()) + return std::nullopt; + return iter->second; +} + +const AssetsManager::BindInfo* AssetsManager::getBind(EnumAssets type, ResourceId localId) const { + std::lock_guard lock(MapMutex); + auto iterType = LocalBinds.find(type); + if(iterType == LocalBinds.end()) + return nullptr; + auto iter = iterType->second.find(localId); + if(iter == iterType->second.end()) + return nullptr; + return &iter->second; +} + +AssetsManager::BindResult AssetsManager::bindServerResource(EnumAssets type, ResourceId serverId, const std::string& domain, + const std::string& key, const Hash_t& hash, std::vector header) +{ + BindResult result; + result.LocalId = getId(type, domain, key); + + std::lock_guard lock(MapMutex); + ServerToLocal[type][serverId] = result.LocalId; + + auto& binds = LocalBinds[type]; + auto iter = binds.find(result.LocalId); + if(iter == binds.end()) { + result.Changed = true; + binds.emplace(result.LocalId, BindInfo{ + .LocalId = result.LocalId, + .ServerId = serverId, + .Domain = domain, + .Key = key, + .Hash = hash, + .Header = std::move(header) + }); + return result; + } + + BindInfo& info = iter->second; + bool hashChanged = info.Hash != hash; + bool headerChanged = info.Header != header; + result.Changed = hashChanged || headerChanged || info.ServerId != serverId; + info.ServerId = serverId; + info.Domain = domain; + info.Key = key; + info.Hash = hash; + info.Header = std::move(header); + return result; +} + +std::optional AssetsManager::unbindServerResource(EnumAssets type, ResourceId serverId) { + std::lock_guard lock(MapMutex); + auto iterType = ServerToLocal.find(type); + if(iterType == ServerToLocal.end()) + return std::nullopt; + auto iter = iterType->second.find(serverId); + if(iter == iterType->second.end()) + return std::nullopt; + + ResourceId localId = iter->second; + iterType->second.erase(iter); + + auto iterBindType = LocalBinds.find(type); + if(iterBindType != LocalBinds.end()) + iterBindType->second.erase(localId); + + return localId; +} + +void AssetsManager::clearServerBindings() { + std::lock_guard lock(MapMutex); + ServerToLocal.clear(); + LocalBinds.clear(); +} + +std::optional AssetsManager::parseHeader(const std::vector& data) { + size_t pos = 0; + auto readU8 = [&](uint8_t& out) -> bool { + if(pos + 1 > data.size()) + return false; + out = data[pos++]; + return true; + }; + auto readU32 = [&](uint32_t& out) -> bool { + if(pos + 4 > data.size()) + return false; + out = uint32_t(data[pos]) | + (uint32_t(data[pos + 1]) << 8) | + (uint32_t(data[pos + 2]) << 16) | + (uint32_t(data[pos + 3]) << 24); + pos += 4; + return true; + }; + + ParsedHeader out; + uint8_t c0, c1, version, type; + if(!readU8(c0) || !readU8(c1) || !readU8(version) || !readU8(type)) + return std::nullopt; + if(c0 != 'a' || c1 != 'h' || version != 1) + return std::nullopt; + out.Type = static_cast(type); + + uint32_t count = 0; + if(!readU32(count)) + return std::nullopt; + out.ModelDeps.reserve(count); + for(uint32_t i = 0; i < count; i++) { + uint32_t id; + if(!readU32(id)) + return std::nullopt; + out.ModelDeps.push_back(id); + } + + if(!readU32(count)) + return std::nullopt; + out.TextureDeps.reserve(count); + for(uint32_t i = 0; i < count; i++) { + uint32_t id; + if(!readU32(id)) + return std::nullopt; + out.TextureDeps.push_back(id); + } + + uint32_t extraSize = 0; + if(!readU32(extraSize)) + return std::nullopt; + if(pos + extraSize > data.size()) + return std::nullopt; + out.Extra.assign(data.begin() + pos, data.begin() + pos + extraSize); + return out; +} + +std::vector AssetsManager::buildHeader(EnumAssets type, const std::vector& modelDeps, + const std::vector& textureDeps, const std::vector& extra) +{ + std::vector data; + data.reserve(4 + 4 + modelDeps.size() * 4 + 4 + textureDeps.size() * 4 + 4 + extra.size()); + data.push_back('a'); + data.push_back('h'); + data.push_back(1); + data.push_back(static_cast(type)); + + auto writeU32 = [&](uint32_t value) { + data.push_back(uint8_t(value & 0xff)); + data.push_back(uint8_t((value >> 8) & 0xff)); + data.push_back(uint8_t((value >> 16) & 0xff)); + data.push_back(uint8_t((value >> 24) & 0xff)); + }; + + writeU32(static_cast(modelDeps.size())); + for(uint32_t id : modelDeps) + writeU32(id); + + writeU32(static_cast(textureDeps.size())); + for(uint32_t id : textureDeps) + writeU32(id); + + writeU32(static_cast(extra.size())); + if(!extra.empty()) + data.insert(data.end(), extra.begin(), extra.end()); + + return data; +} + +std::vector AssetsManager::rebindHeader(const std::vector& header) const { + auto parsed = parseHeader(header); + if(!parsed) + return header; + + std::vector modelDeps; + modelDeps.reserve(parsed->ModelDeps.size()); + for(uint32_t serverId : parsed->ModelDeps) { + auto localId = getLocalIdFromServer(EnumAssets::Model, serverId); + modelDeps.push_back(localId.value_or(0)); + } + + std::vector textureDeps; + textureDeps.reserve(parsed->TextureDeps.size()); + for(uint32_t serverId : parsed->TextureDeps) { + auto localId = getLocalIdFromServer(EnumAssets::Texture, serverId); + textureDeps.push_back(localId.value_or(0)); + } + + return buildHeader(parsed->Type, modelDeps, textureDeps, parsed->Extra); +} + coro<> AssetsManager::asyncDestructor() { NeedShutdown = true; co_await IAsyncDestructible::asyncDestructor(); diff --git a/Src/Client/AssetsManager.hpp b/Src/Client/AssetsManager.hpp index 4ebb0d2..e505ab7 100644 --- a/Src/Client/AssetsManager.hpp +++ b/Src/Client/AssetsManager.hpp @@ -1,17 +1,22 @@ #pragma once #include "Common/Abstract.hpp" +#include #include #include #include +#include +#include #include #include +#include #include #include #include #include #include #include +#include namespace LV::Client { @@ -91,6 +96,22 @@ class AssetsManager : public IAsyncDestructible { public: using Ptr = std::shared_ptr; + struct ParsedHeader { + EnumAssets Type = EnumAssets::MAX_ENUM; + std::vector ModelDeps; + std::vector TextureDeps; + std::vector Extra; + }; + + struct BindInfo { + ResourceId LocalId = 0; + ResourceId ServerId = 0; + std::string Domain; + std::string Key; + Hash_t Hash = {}; + std::vector Header; + }; + struct ResourceKey { Hash_t Hash; EnumAssets Type; @@ -98,6 +119,11 @@ public: ResourceId Id; }; + struct BindResult { + ResourceId LocalId = 0; + bool Changed = false; + }; + public: virtual ~AssetsManager(); static std::shared_ptr Create(asio::io_context &ioc, const fs::path& cachePath, @@ -151,6 +177,20 @@ public: return IssuedAnError; } + // Получить или создать локальный идентификатор ресурса + ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key); + std::optional getLocalIdFromServer(EnumAssets type, ResourceId serverId) const; + const BindInfo* getBind(EnumAssets type, ResourceId localId) const; + BindResult bindServerResource(EnumAssets type, ResourceId serverId, const std::string& domain, const std::string& key, + const Hash_t& hash, std::vector header); + std::optional unbindServerResource(EnumAssets type, ResourceId serverId); + void clearServerBindings(); + + static std::optional parseHeader(const std::vector& data); + static std::vector buildHeader(EnumAssets type, const std::vector& modelDeps, + const std::vector& textureDeps, const std::vector& extra); + std::vector rebindHeader(const std::vector& header) const; + private: Logger LOG = "Client>ResourceHandler"; const fs::path @@ -197,6 +237,11 @@ private: bool NeedShutdown = false, IssuedAnError = false; std::thread OffThread; + mutable std::mutex MapMutex; + std::unordered_map>> DKToId; + std::unordered_map> ServerToLocal; + std::unordered_map> LocalBinds; + std::array NextId = {}; virtual coro<> asyncDestructor(); @@ -207,4 +252,4 @@ private: std::string hashToString(const Hash_t& hash); }; -} \ No newline at end of file +} diff --git a/Src/Client/ServerSession.cpp b/Src/Client/ServerSession.cpp index e83286e..e2dcaa7 100644 --- a/Src/Client/ServerSession.cpp +++ b/Src/Client/ServerSession.cpp @@ -358,6 +358,12 @@ void ServerSession::update(GlobalTime gTime, float dTime) { resources.reserve(assets.size()); for(AssetEntry& entry : assets) { + entry.Hash = entry.Res.hash(); + if(const AssetsManager::BindInfo* bind = AM->getBind(entry.Type, entry.Id)) + entry.Dependencies = AM->rebindHeader(bind->Header); + else + entry.Dependencies.clear(); + resources.push_back(entry.Res); AsyncContext.LoadedResources.emplace_back(std::move(entry)); @@ -430,12 +436,27 @@ void ServerSession::update(GlobalTime gTime, float dTime) { needRequest.push_back(key.Hash); } } else { + Hash_t actualHash = res->hash(); + if(actualHash != key.Hash) { + auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), key.Hash); + if(iter == AsyncContext.AlreadyLoading.end() || *iter != key.Hash) { + AsyncContext.AlreadyLoading.insert(iter, key.Hash); + needRequest.push_back(key.Hash); + } + } + + std::vector deps; + if(const AssetsManager::BindInfo* bind = AM->getBind(key.Type, key.Id)) + deps = AM->rebindHeader(bind->Header); + AssetEntry entry { .Type = key.Type, .Id = key.Id, .Domain = key.Domain, .Key = key.Key, - .Res = *res + .Res = std::move(*res), + .Hash = actualHash, + .Dependencies = std::move(deps) }; AsyncContext.LoadedResources.emplace_back(std::move(entry)); @@ -529,7 +550,7 @@ void ServerSession::update(GlobalTime gTime, float dTime) { auto iter = niubdk.find(dk); if(iter != niubdk.end()) { // Есть ресурс - needQuery = false; + needQuery = iter->second.first.Hash != bind.Hash; } } @@ -951,12 +972,21 @@ void ServerSession::update(GlobalTime gTime, float dTime) { AsyncContext.Binds.clear(); for(AssetBindEntry& entry : abc.Binds) { + std::vector deps; + if(!entry.Header.empty()) + deps = AM->rebindHeader(entry.Header); + MyAssets.ExistBinds[(int) entry.Type].insert(entry.Id); result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id); + auto iterLoaded = IServerSession::Assets[entry.Type].find(entry.Id); + if(iterLoaded != IServerSession::Assets[entry.Type].end()) + iterLoaded->second.Dependencies = deps; + // Если ресурс был в кеше, то достаётся от туда auto iter = MyAssets.NotInUse[(int) entry.Type].find(entry.Domain+':'+entry.Key); if(iter != MyAssets.NotInUse[(int) entry.Type].end()) { + iter->second.first.Dependencies = deps; IServerSession::Assets[entry.Type][entry.Id] = std::get<0>(iter->second); result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id); MyAssets.NotInUse[(int) entry.Type].erase(iter); @@ -1196,6 +1226,7 @@ void ServerSession::setRenderSession(IRenderSession* session) { } void ServerSession::resetResourceSyncState() { + AM->clearServerBindings(); AsyncContext.AssetsLoading.clear(); AsyncContext.AlreadyLoading.clear(); for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) @@ -1300,11 +1331,27 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { key = co_await sock.read(); Hash_t hash; co_await sock.read((std::byte*) hash.data(), hash.size()); + uint32_t headerSize = co_await sock.read(); + std::vector header; + if(headerSize > 0) { + header.resize(headerSize); + co_await sock.read((std::byte*) header.data(), header.size()); + } - binds.emplace_back( - (EnumAssets) type, (ResourceId) id, std::move(domain), - std::move(key), hash - ); + AssetsManager::BindResult bindResult = AM->bindServerResource( + (EnumAssets) type, (ResourceId) id, domain, key, hash, header); + + if(!bindResult.Changed) + continue; + + binds.emplace_back(AssetBindEntry{ + .Type = (EnumAssets) type, + .Id = bindResult.LocalId, + .Domain = std::move(domain), + .Key = std::move(key), + .Hash = hash, + .Header = std::move(header) + }); if(binds.back().Domain == "test" && (binds.back().Type == EnumAssets::Nodestate @@ -1339,7 +1386,11 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { if(type >= (int) EnumAssets::MAX_ENUM) protocolError(); - abc.Lost[(int) type].push_back(id); + auto localId = AM->unbindServerResource((EnumAssets) type, id); + if(!localId) + continue; + + abc.Lost[(int) type].push_back(*localId); } AsyncContext.AssetsBinds.lock()->emplace_back(std::move(abc)); @@ -1358,6 +1409,13 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { std::string domain = co_await sock.read(); std::string key = co_await sock.read(); + ResourceId localId = 0; + if(auto mapped = AM->getLocalIdFromServer(type, id)) { + localId = *mapped; + } else { + localId = AM->getId(type, domain, key); + AM->bindServerResource(type, id, domain, key, hash, {}); + } if(domain == "test" && (type == EnumAssets::Nodestate @@ -1367,14 +1425,14 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { uint32_t idx = debugResourceLogCount.fetch_add(1); if(idx < 128) { LOG.debug() << "InitResSend type=" << assetTypeName(type) - << " id=" << id + << " id=" << localId << " key=" << domain << ':' << key << " size=" << size; } } AsyncContext.AssetsLoading[hash] = AssetLoading{ - type, id, std::move(domain), std::move(key), + type, localId, std::move(domain), std::move(key), std::u8string(size, '\0'), 0 }; @@ -1410,9 +1468,14 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { } } - AsyncContext.LoadedAssets.lock()->emplace_back( - al.Type, al.Id, std::move(al.Domain), std::move(al.Key), std::move(al.Data) - ); + AsyncContext.LoadedAssets.lock()->emplace_back(AssetEntry{ + .Type = al.Type, + .Id = al.Id, + .Domain = std::move(al.Domain), + .Key = std::move(al.Key), + .Res = std::move(al.Data), + .Hash = hash + }); AsyncContext.AssetsLoading.erase(AsyncContext.AssetsLoading.find(hash)); @@ -1461,7 +1524,11 @@ coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) { { DefNode_t def; DefNodeId id = co_await sock.read(); - def.NodestateId = co_await sock.read(); + ResourceId serverNodestate = co_await sock.read(); + if(auto localId = AM->getLocalIdFromServer(EnumAssets::Nodestate, serverNodestate)) + def.NodestateId = *localId; + else + def.NodestateId = 0; def.TexId = id; if(id < 32) { diff --git a/Src/Client/ServerSession.hpp b/Src/Client/ServerSession.hpp index 31c3ece..b0a30d0 100644 --- a/Src/Client/ServerSession.hpp +++ b/Src/Client/ServerSession.hpp @@ -92,6 +92,7 @@ private: ResourceId Id; std::string Domain, Key; Hash_t Hash; + std::vector Header; }; struct TickData { diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index 3ec3509..cd37448 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -1648,7 +1648,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) { if(auto iter = data.Profiles_Lost.find(EnumDefContent::Voxel); iter != data.Profiles_Lost.end()) mcpData.ChangedVoxels.insert(mcpData.ChangedVoxels.end(), iter->second.begin(), iter->second.end()); - std::vector> modelResources; + std::vector*>> modelResources; std::vector modelLost; if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Model); iter != data.Assets_ChangeOrAdd.end()) { const auto& list = ServerSession->Assets[EnumAssets::Model]; @@ -1657,7 +1657,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) { if(entryIter == list.end()) continue; - modelResources.emplace_back(id, entryIter->second.Res); + modelResources.emplace_back(id, entryIter->second.Res, &entryIter->second.Dependencies); } } if(auto iter = data.Assets_Lost.find(EnumAssets::Model); iter != data.Assets_Lost.end()) @@ -1698,7 +1698,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) { std::vector changedNodestates; if(NSP) { - std::vector> nodestateResources; + std::vector*>> nodestateResources; std::vector nodestateLost; if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Nodestate); iter != data.Assets_ChangeOrAdd.end()) { @@ -1708,7 +1708,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) { if(entryIter == list.end()) continue; - nodestateResources.emplace_back(id, entryIter->second.Res); + nodestateResources.emplace_back(id, entryIter->second.Res, &entryIter->second.Dependencies); } } diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index 1d3978f..7253522 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -1,11 +1,13 @@ #pragma once +#include "Client/AssetsManager.hpp" #include "Client/Abstract.hpp" #include "Common/Abstract.hpp" #include #include #include #include +#include #include #include #include @@ -85,7 +87,7 @@ public: } // Применяет изменения, возвращая все затронутые модели - std::vector onModelChanges(std::vector> newOrChanged, + std::vector onModelChanges(std::vector*>> newOrChanged, std::vector lost, const std::unordered_map* modelAssets) { std::vector result; @@ -140,12 +142,39 @@ public: } } - for(const auto& [key, resource] : newOrChanged) { + for(const auto& [key, resource, deps] : newOrChanged) { result.push_back(key); makeUnready(key); ModelObject model; std::string type = "unknown"; + std::optional header; + if(deps && !deps->empty()) { + header = AssetsManager::parseHeader(*deps); + if(header && header->Type != EnumAssets::Model) + header.reset(); + } + const std::vector* textureDeps = header ? &header->TextureDeps : nullptr; + auto remapTextureId = [&](uint32_t placeholder) -> uint32_t { + if(!textureDeps || placeholder >= textureDeps->size()) + return 0; + return (*textureDeps)[placeholder]; + }; + auto remapPipeline = [&](TexturePipeline pipe) { + if(textureDeps) { + for(auto& texId : pipe.BinTextures) + texId = remapTextureId(texId); + if(!pipe.Pipeline.empty()) { + std::vector code; + code.resize(pipe.Pipeline.size()); + std::memcpy(code.data(), pipe.Pipeline.data(), code.size()); + TexturePipelineProgram::remapTexIds(code, *textureDeps, nullptr); + pipe.Pipeline.resize(code.size()); + std::memcpy(pipe.Pipeline.data(), code.data(), code.size()); + } + } + return pipe; + }; try { std::u8string_view data((const char8_t*) resource.data(), resource.size()); @@ -153,7 +182,10 @@ public: type = "InternalBinary"; // Компилированная модель внутреннего формата LV::PreparedModel pm((std::u8string) data); - model.TextureMap = pm.CompiledTextures; + model.TextureMap.clear(); + model.TextureMap.reserve(pm.CompiledTextures.size()); + for(auto& [tkey, pipe] : pm.CompiledTextures) + model.TextureMap.emplace(tkey, remapPipeline(std::move(pipe))); model.TextureKeys = {}; for(const PreparedModel::Cuboid& cb : pm.Cuboids) { @@ -825,7 +857,7 @@ public: {} // Применяет изменения, возвращает изменённые описания состояний - std::vector onNodestateChanges(std::vector> newOrChanged, std::vector lost, std::vector changedModels) { + std::vector onNodestateChanges(std::vector*>> newOrChanged, std::vector lost, std::vector changedModels) { std::vector result; for(ResourceId lostId : lost) { @@ -837,7 +869,7 @@ public: Nodestates.erase(iterNodestate); } - for(const auto& [key, resource] : newOrChanged) { + for(const auto& [key, resource, deps] : newOrChanged) { result.push_back(key); PreparedNodeState nodestate; @@ -867,6 +899,13 @@ public: continue; } + if(deps && !deps->empty()) { + auto header = AssetsManager::parseHeader(*deps); + if(header && header->Type == EnumAssets::Nodestate) { + nodestate.LocalToModel.assign(header->ModelDeps.begin(), header->ModelDeps.end()); + } + } + Nodestates.insert_or_assign(key, std::move(nodestate)); if(key < 64) { auto iter = Nodestates.find(key); diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index de877dd..51c93d1 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -499,6 +499,15 @@ void unCompressNodes(const std::u8string& compressed, Node* ptr); std::u8string compressLinear(const std::u8string& data); std::u8string unCompressLinear(const std::u8string& data); +inline std::pair parseDomainKey(const std::string_view value, const std::string_view defaultDomain = "core") { + size_t pos = value.find(':'); + + if(pos == std::string_view::npos) + return {defaultDomain, value}; + else + return {value.substr(0, pos), value.substr(pos+1)}; +} + inline std::pair parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") { auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d/_.]+)"); if(!regResult) @@ -608,11 +617,13 @@ struct NodeStateInfo { int Variations = 0; }; +using ResourceHeader = std::u8string; + /* Хранит распаршенное определение состояний нод. Не привязано ни к какому окружению. */ -struct PreparedNodeState { +struct HeadlessNodeState { enum class Op { Add, Sub, Mul, Div, Mod, LT, LE, GT, GE, EQ, NE, @@ -642,9 +653,7 @@ struct PreparedNodeState { }; // Локальный идентификатор в именной ресурс - std::vector> LocalToModelKD; - // Локальный идентификатор в глобальный идентификатор - std::vector LocalToModel; + std::vector LocalToModelKD; // Ноды выражений std::vector Nodes; // Условия -> вариации модели + веса @@ -657,18 +666,33 @@ struct PreparedNodeState { > , 1> Routes; - PreparedNodeState(const std::string_view modid, const js::object& profile); - PreparedNodeState(const std::string_view modid, const sol::table& profile); - PreparedNodeState(const std::u8string_view data); + HeadlessNodeState() = default; + HeadlessNodeState(const HeadlessNodeState&) = default; + HeadlessNodeState(HeadlessNodeState&&) = default; - PreparedNodeState() = default; - PreparedNodeState(const PreparedNodeState&) = default; - PreparedNodeState(PreparedNodeState&&) = default; + HeadlessNodeState& operator=(const HeadlessNodeState&) = default; + HeadlessNodeState& operator=(HeadlessNodeState&&) = default; - PreparedNodeState& operator=(const PreparedNodeState&) = default; - PreparedNodeState& operator=(PreparedNodeState&&) = default; + /* + Парсит json формат с выделением все зависимостей в заголовок. + Требуется ресолвер идентификаторов моделей. + */ + ResourceHeader parse(const js::object& profile, const std::function& modelResolver); - // Пишет в сжатый двоичный формат + /* + Парсит lua формат с выделением зависимостей в заголовок. + Требуется ресолвер идентификаторов моделей. + */ + ResourceHeader parse(const sol::table& profile, const std::function& modelResolver); + + /* + Загружает ресурс из двоичного формата. + */ + void load(std::u8string_view data); + + /* + Транслирует в двоичный формат. + */ std::u8string dump() const; // Если зависит от случайного распределения по миру @@ -761,7 +785,6 @@ struct PreparedNodeState { } } - std::move_only_function calcNode; calcNode = [&](uint16_t nodeId) -> int32_t { @@ -860,7 +883,7 @@ enum class EnumFace { /* Парсит json модель */ -struct PreparedModel { +struct HeadlessModel { enum class EnumGuiLight { Default }; @@ -905,23 +928,42 @@ struct PreparedModel { std::vector SubModels; - // Json - PreparedModel(const std::string_view modid, const js::object& profile); - PreparedModel(const std::string_view modid, const sol::table& profile); - PreparedModel(const std::u8string& data); + HeadlessModel() = default; + HeadlessModel(const HeadlessModel&) = default; + HeadlessModel(HeadlessModel&&) = default; - PreparedModel() = default; - PreparedModel(const PreparedModel&) = default; - PreparedModel(PreparedModel&&) = default; + HeadlessModel& operator=(const HeadlessModel&) = default; + HeadlessModel& operator=(HeadlessModel&&) = default; - PreparedModel& operator=(const PreparedModel&) = default; - PreparedModel& operator=(PreparedModel&&) = default; + /* + Парсит json формат с выделением все зависимостей в заголовок. + Требуется ресолвер идентификаторов моделей. + */ + ResourceHeader parse( + const js::object& profile, + const std::function& modelResolver, + const std::function(const std::string_view texturePipelineSrc)>& textureResolver + ); - // Пишет в сжатый двоичный формат - std::u8string dump() const; + /* + Парсит lua формат с выделением зависимостей в заголовок. + Требуется ресолвер идентификаторов моделей. + */ + ResourceHeader parse( + const sol::table& profile, + const std::function& modelResolver, + const std::function(const std::string_view texturePipelineSrc)>& textureResolver + ); -private: + /* + Загружает ресурс из двоичного формата. + */ void load(std::u8string_view data); + + /* + Транслирует в двоичный формат. + */ + std::u8string dump() const; }; struct PreparedGLTF { diff --git a/Src/Server/ContentManager.cpp b/Src/Server/ContentManager.cpp index 7000163..7547b55 100644 --- a/Src/Server/ContentManager.cpp +++ b/Src/Server/ContentManager.cpp @@ -4,7 +4,7 @@ namespace LV::Server { -ContentManager::ContentManager(AssetsPreloader &am) +ContentManager::ContentManager(AssetsPreloader& am) : AM(am) { std::fill(std::begin(NextId), std::end(NextId), 1); diff --git a/Src/Server/GameServer.cpp b/Src/Server/GameServer.cpp index 4875cef..e7f9843 100644 --- a/Src/Server/GameServer.cpp +++ b/Src/Server/GameServer.cpp @@ -1567,7 +1567,7 @@ void GameServer::init(fs::path worldPath) { AssetsInit.Assets.push_back(mlt.LoadChain[index].Path / "assets"); } - Content.AM.applyResourceChange(Content.AM.recheckResourcesSync(AssetsInit)); + Content.AM.applyResourceChange(Content.AM.reloadResources(AssetsInit)); LOG.info() << "Пре Инициализация"; @@ -1882,7 +1882,7 @@ void GameServer::stepModInitializations() { void GameServer::reloadMods() { LOG.info() << "Перезагрузка модов: ассеты и зависимости"; - AssetsPreloader::ResourceChangeObj changes = Content.AM.recheckResourcesSync(AssetsInit); + AssetsPreloader::Out_reloadResources changes = Content.AM.reloadResources(AssetsInit); AssetsPreloader::Out_applyResourceChange applied = Content.AM.applyResourceChange(changes); size_t changedCount = 0; @@ -2687,7 +2687,7 @@ void GameServer::stepSyncContent() { full.uniq(); // Информируем о запрошенных ассетах - std::vector> resources; + std::vector> resources; for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { for(ResourceId resId : full.AssetsInfo[type]) { const AssetsPreloader::MediaResource* media = Content.AM.getResource((EnumAssets) type, resId); @@ -2695,7 +2695,7 @@ void GameServer::stepSyncContent() { continue; Resource resource(media->Resource->data(), media->Resource->size()); - resources.emplace_back((EnumAssets) type, resId, media->Domain, media->Key, std::move(resource)); + resources.emplace_back((EnumAssets) type, resId, media->Domain, media->Key, std::move(resource), media->Dependencies); } } @@ -2705,8 +2705,8 @@ void GameServer::stepSyncContent() { continue; auto& [type, id, media] = *result; - Resource resource(media->Resource->data(), media->Resource->size()); - resources.emplace_back(type, id, media->Domain, media->Key, std::move(resource)); + Resource resource(*media->Resource); + resources.emplace_back(type, id, media->Domain, media->Key, std::move(resource), media->Header); } // Информируем о запрошенных профилях diff --git a/Src/Server/RemoteClient.cpp b/Src/Server/RemoteClient.cpp index 06900d9..326dbb8 100644 --- a/Src/Server/RemoteClient.cpp +++ b/Src/Server/RemoteClient.cpp @@ -11,6 +11,7 @@ #include #include #include +#include "sha2.hpp" namespace LV::Server { @@ -562,13 +563,14 @@ ResourceRequest RemoteClient::pushPreparedPackets() { return std::move(nextRequest); } -void RemoteClient::informateAssets(const std::vector>& resources) +void RemoteClient::informateAssets(const std::vector>>& resources) { - std::vector> newForClient; + std::vector>> newForClient; static std::atomic debugSendLogCount = 0; - for(auto& [type, resId, domain, key, resource] : resources) { + for(auto& [type, resId, domain, key, resource, header] : resources) { auto hash = resource.hash(); + Hash_t headerHash = sha2::sha256(header.data(), header.size()); auto lock = NetworkAndResource.lock(); // Проверка запрашиваемых клиентом ресурсов @@ -613,12 +615,13 @@ void RemoteClient::informateAssets(const std::vectorResUses.AssetsUse[(int) type].find(resId); iter != lock->ResUses.AssetsUse[(int) type].end() - && std::get(iter->second) != hash + && (iter->second.second.Hash != hash || iter->second.second.HeaderHash != headerHash) ) { lock.unlock(); // Требуется перепривязать идентификатор к новому хешу - newForClient.push_back({(EnumAssets) type, resId, domain, key, hash, resource.size()}); - std::get(iter->second) = hash; + newForClient.push_back({(EnumAssets) type, resId, domain, key, hash, header}); + iter->second.second.Hash = hash; + iter->second.second.HeaderHash = headerHash; } } } @@ -628,14 +631,19 @@ void RemoteClient::informateAssets(const std::vectorcheckPacketBorder(2+1+4+newForClient.size()*(1+4+64+32)); + lock->checkPacketBorder(2+1+4); lock->NextPacket << (uint8_t) ToClient::L1::Resource // Оповещение << ((uint8_t) ToClient::L2Resource::Bind) << uint32_t(newForClient.size()); - for(auto& [type, resId, domain, key, hash, size] : newForClient) { + for(auto& [type, resId, domain, key, hash, header] : newForClient) { // TODO: может внести ограничение на длину домена и ключа? + const size_t entrySize = 1 + 4 + 2 + domain.size() + 2 + key.size() + 32 + 4 + header.size(); + lock->checkPacketBorder(entrySize); lock->NextPacket << uint8_t(type) << uint32_t(resId) << domain << key; lock->NextPacket.write((const std::byte*) hash.data(), hash.size()); + lock->NextPacket << uint32_t(header.size()); + if(!header.empty()) + lock->NextPacket.write((const std::byte*) header.data(), header.size()); } } } diff --git a/Src/Server/RemoteClient.hpp b/Src/Server/RemoteClient.hpp index d02b422..cfe7829 100644 --- a/Src/Server/RemoteClient.hpp +++ b/Src/Server/RemoteClient.hpp @@ -212,7 +212,11 @@ class RemoteClient { struct ResUses_t { // Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору // Хэш используется для того, чтобы исключить повторные объявления неизменившихся ресурсов - std::map> AssetsUse[(int) EnumAssets::MAX_ENUM]; + struct AssetBindState { + Hash_t Hash; + Hash_t HeaderHash; + }; + std::map> AssetsUse[(int) EnumAssets::MAX_ENUM]; // Зависимость профилей контента от профилей ресурсов // Нужно чтобы пересчитать зависимости к профилям ресурсов @@ -426,8 +430,16 @@ public: // Сюда приходят все обновления ресурсов движка // Глобально их можно запросить в выдаче pushPreparedPackets() + // Нужно передавать клиенту информацию о новых привязках + // id -> домен+ключ + // id -> hash+header + + // По запросу клиента отправлять нужные ресурсы по hash + + /// TODO: новый void informateAssets(); + // Оповещение о запрошенных (и не только) ассетах - void informateAssets(const std::vector>& resources); + void informateAssets(const std::vector>>& resources); // Игровые определения void informateDefVoxel(const std::vector>& voxels) { NetworkAndResource.lock()->informateDefVoxel(voxels); }