From 2dd3ea60d7ae5f0ad59cefaa71ea02393ea91b29 Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Sun, 31 Aug 2025 20:22:59 +0600 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BD=D0=BE=D0=B4=20=D0=BD=D0=B0=20=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=BE=D0=BD=D0=B5=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Common/Abstract.cpp | 40 ++++--- Src/Common/Abstract.hpp | 43 +++++--- Src/Server/AssetsManager.cpp | 195 ++++++++++++++++++++++++++++++----- Src/Server/AssetsManager.hpp | 170 ++++++++++++++++++++++-------- Src/Server/GameServer.cpp | 9 ++ 5 files changed, 355 insertions(+), 102 deletions(-) diff --git a/Src/Common/Abstract.cpp b/Src/Common/Abstract.cpp index eec3ea8..0038e09 100644 --- a/Src/Common/Abstract.cpp +++ b/Src/Common/Abstract.cpp @@ -873,11 +873,11 @@ PreparedNodeState::PreparedNodeState(const std::u8string& data) { uint16_t size; lr >> size; - ResourceToLocalId.reserve(size); + ModelToLocalId.reserve(size); for(int counter = 0; counter < size; counter++) { std::string domain, key; lr >> domain >> key; - ResourceToLocalId.emplace_back(std::move(domain), std::move(key)); + ModelToLocalId.emplace_back(std::move(domain), std::move(key)); } lr >> size; @@ -987,10 +987,10 @@ std::u8string PreparedNodeState::dump() const { Net::Packet result; // ResourceToLocalId - assert(ResourceToLocalId.size() < (1 << 16)); - result << uint16_t(ResourceToLocalId.size()); + assert(ModelToLocalId.size() < (1 << 16)); + result << uint16_t(ModelToLocalId.size()); - for(const auto& [domain, key] : ResourceToLocalId) { + for(const auto& [domain, key] : ModelToLocalId) { assert(domain.size() < 32); result << domain; assert(key.size() < 32); @@ -1421,7 +1421,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) { } std::pair> PreparedNodeState::parseModel(const std::string_view modid, const js::object& obj) { - // ResourceToLocalId + // ModelToLocalId bool uvlock; float weight = 1; @@ -1449,15 +1449,15 @@ std::pairas_object(); for(const auto& [key, value] : textures) { - auto [domain, key2] = parseDomainKey((const std::string) value.as_string(), modid); - Textures[key] = {domain, key2}; + Textures[key] = value.as_string(); } } @@ -1807,9 +1806,9 @@ PreparedModel::PreparedModel(const std::u8string& data) { lr >> size; Textures.reserve(size); for(int counter = 0; counter < size; counter++) { - std::string tkey, domain, key; - lr >> tkey >> domain >> key; - Textures.insert({tkey, {std::move(domain), std::move(key)}}); + std::string tkey, pipeline; + lr >> tkey >> pipeline; + Textures.insert({tkey, pipeline}); } lr >> size; @@ -1916,11 +1915,8 @@ std::u8string PreparedModel::dump() const { assert(tkey.size() < 32); result << tkey; - assert(dk.first.size() < 32); - result << dk.first; - - assert(dk.second.size() < 32); - result << dk.second; + assert(dk.size() < 512); + result << dk; } assert(Cuboids.size() < (1 << 16)); diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index e0b1a01..e7488cf 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -494,13 +495,6 @@ void unCompressNodes(const std::u8string& compressed, Node* ptr); std::u8string compressLinear(const std::u8string& data); std::u8string unCompressLinear(const std::u8string& data); -enum struct TexturePipelineCMD : uint8_t { - Texture, // Указание текстуры - Combine, // Комбинирование - -}; - - struct NodestateEntry { std::string Name; int Variability = 0; // Количество возможный значений состояния @@ -551,7 +545,7 @@ struct PreparedNodeState { }; // Локальный идентификатор в именной ресурс - std::vector> ResourceToLocalId; + std::vector> ModelToLocalId; // Ноды выражений std::vector Nodes; // Условия -> вариации модели + веса @@ -578,6 +572,7 @@ struct PreparedNodeState { // Пишет в сжатый двоичный формат std::u8string dump() const; + // Если зависит от случайного распределения по миру bool hasVariability() const { return HasVariability; } @@ -609,7 +604,7 @@ struct PreparedModel { }; std::unordered_map Display; - std::unordered_map> Textures; + std::unordered_map Textures; struct Cuboid { bool Shade; @@ -670,9 +665,9 @@ private: bool load(const std::u8string& data) noexcept; }; -struct TexturePipeline { - std::vector BinTextures; - std::u8string Pipeline; +enum struct TexturePipelineCMD : uint8_t { + Texture, // Указание текстуры + Combine, // Комбинирование }; using Hash_t = std::array; @@ -689,6 +684,30 @@ inline std::pair parseDomainKey(const std::string& val } } +struct PrecompiledTexturePipeline { + // Локальные идентификаторы пайплайна в домен+ключ + std::vector> Assets; + // Чистый код текстурных преобразований, локальные идентификаторы связаны с Assets + std::u8string Pipeline; +}; + +struct TexturePipeline { + // Разыменованые идентификаторы + std::vector BinTextures; + // Чистый код текстурных преобразований, локальные идентификаторы связаны с BinTextures + std::u8string Pipeline; +}; + +// Компилятор текстурных потоков +inline PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, const std::string_view defaultDomain = "core") { + PrecompiledTexturePipeline result; + + auto [domain, key] = parseDomainKey(cmd, defaultDomain); + + result.Assets.emplace_back(domain, key); + + return result; +} struct Resource { private: diff --git a/Src/Server/AssetsManager.cpp b/Src/Server/AssetsManager.cpp index 6d1b6a6..809d295 100644 --- a/Src/Server/AssetsManager.cpp +++ b/Src/Server/AssetsManager.cpp @@ -2,6 +2,7 @@ #include "Common/Abstract.hpp" #include "boost/json.hpp" #include "png++/rgb_pixel.hpp" +#include #include #include #include @@ -14,25 +15,34 @@ namespace LV::Server { -PreparedModelCollision::PreparedModelCollision(const PreparedModel& model) { +PreparedModel::PreparedModel(const std::string& domain, const LV::PreparedModel& model) { Cuboids.reserve(model.Cuboids.size()); - for(const PreparedModel::Cuboid& cuboid : model.Cuboids) { - Cuboid result; - result.From = cuboid.From; - result.To = cuboid.To; - result.Faces = 0; - - for(const auto& [key, _] : cuboid.Faces) - result.Faces |= (1 << int(key)); - - result.Transformations = cuboid.Transformations; + for(auto& [key, cmd] : model.Textures) { + PrecompiledTexturePipeline ptp = compileTexturePipeline(cmd, domain); + for(auto& [domain, key] : ptp.Assets) { + TextureDependencies[domain].push_back(key); + } } - SubModels = model.SubModels; + for(auto& sub : model.SubModels) { + ModelDependencies[sub.Domain].push_back(sub.Key); + } + + // for(const PreparedModel::Cuboid& cuboid : model.Cuboids) { + // Cuboid result; + // result.From = cuboid.From; + // result.To = cuboid.To; + // result.Faces = 0; + + // for(const auto& [key, _] : cuboid.Faces) + // result.Faces |= (1 << int(key)); + + // result.Transformations = cuboid.Transformations; + // } } -PreparedModelCollision::PreparedModelCollision(const std::string& domain, const js::object& glTF) { +PreparedModel::PreparedModel(const std::string& domain, const js::object& glTF) { // gltf // Сцена по умолчанию @@ -45,7 +55,7 @@ PreparedModelCollision::PreparedModelCollision(const std::string& domain, const // Буферы } -PreparedModelCollision::PreparedModelCollision(const std::string& domain, Resource glb) { +PreparedModel::PreparedModel(const std::string& domain, Resource glb) { } @@ -104,26 +114,26 @@ void AssetsManager::loadResourceFromFile_Model(ResourceChangeObj& out, const std Resource res(path); std::filesystem::file_time_type ftt = fs::last_write_time(path); + PreparedModel pmc; if(path.extension() == "json") { js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object(); - PreparedModel pm(domain, obj); - PreparedModelCollision pmc(pm); + LV::PreparedModel pm(domain, obj); std::u8string data = pm.dump(); - out.Models[domain].emplace_back(key, pmc); + pmc = PreparedModel(domain, pm); out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, Resource((const uint8_t*) data.data(), data.size()), ftt); } else if(path.extension() == "gltf") { js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object(); - PreparedModelCollision pmc(domain, obj); - out.Models[domain].emplace_back(key, pmc); + pmc = PreparedModel(domain, obj); out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt); } else if(path.extension() == "glb") { - PreparedModelCollision pmc(domain, res); - out.Models[domain].emplace_back(key, pmc); + pmc = PreparedModel(domain, res); out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt); } else { MAKE_ERROR("Не поддерживаемый формат файла"); } + + out.Models[domain].emplace_back(key, pmc); } void AssetsManager::loadResourceFromFile_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const { @@ -436,14 +446,21 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const assert(iter != keyToIdDomain.end()); ResourceId resId = iter->second; - // keyToIdDomain.erase(iter); - // lost[type].push_back(resId); - uint32_t localId = resId % TableEntry::ChunkSize; + if(type == (int) EnumAssets::Nodestate) { + if(resId / TableEntry::ChunkSize < lock->Table_NodeState.size()) { + lock->Table_NodeState[resId / TableEntry::ChunkSize] + ->Entries[resId % TableEntry::ChunkSize].reset(); + } + } else if(type == (int) EnumAssets::Model) { + if(resId / TableEntry::ChunkSize < lock->Table_Model.size()) { + lock->Table_Model[resId / TableEntry::ChunkSize] + ->Entries[resId % TableEntry::ChunkSize].reset(); + } + } + auto& chunk = lock->Table[type][resId / TableEntry::ChunkSize]; - // chunk->IsFull = false; - // chunk->Empty.set(localId); - chunk->Entries[localId].reset(); + chunk->Entries[resId % TableEntry::ChunkSize].reset(); } } } @@ -484,6 +501,130 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const std::set_difference(l.begin(), l.end(), noc.begin(), noc.end(), std::back_inserter(result.Lost[type])); } + if(!orr.Nodestates.empty()) + { + auto lock = LocalObj.lock(); + for(auto& [domain, table] : orr.Nodestates) { + for(auto& [key, value] : table) { + ResourceId resId = lock->getId(EnumAssets::Nodestate, domain, key); + + std::vector models; + + for(auto& [domain, key] : value.ModelToLocalId) { + models.push_back(lock->getId(EnumAssets::Model, domain, key)); + } + + { + std::sort(models.begin(), models.end()); + auto iterErase = std::unique(models.begin(), models.end()); + models.erase(iterErase, models.end()); + models.shrink_to_fit(); + } + + lock->Table_NodeState[resId / TableEntry::ChunkSize] + ->Entries[resId % TableEntry::ChunkSize] = std::move(models); + } + } + } + + if(!orr.Models.empty()) + { + auto lock = LocalObj.lock(); + for(auto& [domain, table] : orr.Models) { + for(auto& [key, value] : table) { + ResourceId resId = lock->getId(EnumAssets::Model, domain, key); + + ModelDependency deps; + for(auto& [domain, list] : value.ModelDependencies) { + ResourceId subResId = lock->getId(EnumAssets::Model, domain, key); + deps.ModelDeps.push_back(subResId); + } + + for(auto& [domain, list] : value.TextureDependencies) { + ResourceId subResId = lock->getId(EnumAssets::Texture, domain, key); + deps.TextureDeps.push_back(subResId); + } + + lock->Table_Model[resId / TableEntry::ChunkSize] + ->Entries[resId % TableEntry::ChunkSize] = std::move(deps); + } + } + } + + // Вычислить зависимости моделей + { + // Затираем старые данные + auto lock = LocalObj.lock(); + for(auto& entriesChunk : lock->Table_Model) { + for(auto& entry : entriesChunk->Entries) { + if(!entry) + continue; + + entry->Ready = false; + entry->FullSubTextureDeps.clear(); + entry->FullSubModelDeps.clear(); + } + } + + // Вычисляем зависимости + std::function calcDeps = [&](AssetsModel resId, ModelDependency& entry) { + for(AssetsModel subResId : entry.ModelDeps) { + auto& model = lock->Table_Model[subResId / TableEntry::ChunkSize] + ->Entries[subResId % TableEntry::ChunkSize]; + + if(!model) + continue; + + if(!model->Ready) + calcDeps(subResId, *model); + + if(std::binary_search(model->FullSubModelDeps.begin(), model->FullSubModelDeps.end(), resId)) { + // Циклическая зависимость + const auto object1 = lock->getResource(EnumAssets::Model, resId); + const auto object2 = lock->getResource(EnumAssets::Model, subResId); + assert(object1); + + LOG.warn() << "В моделе " << std::get<1>(*object1) << ':' << std::get<2>(*object1) + << " обнаружена циклическая зависимость с " << std::get<1>(*object2) << ':' + << std::get<2>(*object2); + } else { + entry.FullSubTextureDeps.append_range(model->FullSubTextureDeps); + entry.FullSubModelDeps.push_back(subResId); + entry.FullSubModelDeps.append_range(model->FullSubModelDeps); + } + } + + { + std::sort(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end()); + auto eraseIter = std::unique(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end()); + entry.FullSubTextureDeps.erase(eraseIter, entry.FullSubTextureDeps.end()); + entry.FullSubTextureDeps.shrink_to_fit(); + } + + { + std::sort(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end()); + auto eraseIter = std::unique(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end()); + entry.FullSubModelDeps.erase(eraseIter, entry.FullSubModelDeps.end()); + entry.FullSubModelDeps.shrink_to_fit(); + } + + entry.Ready = true; + }; + + ssize_t iter = -1; + for(auto& entriesChunk : lock->Table_Model) { + for(auto& entry : entriesChunk->Entries) { + iter++; + + if(!entry || entry->Ready) + continue; + + // Собираем зависимости + calcDeps(iter, *entry); + } + } + } + return result; } diff --git a/Src/Server/AssetsManager.hpp b/Src/Server/AssetsManager.hpp index 6075f8e..a97288a 100644 --- a/Src/Server/AssetsManager.hpp +++ b/Src/Server/AssetsManager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -17,31 +18,40 @@ namespace fs = std::filesystem; /* Используется для расчёта коллизии, - если это необходимо. - - glTF конвертируется в кубы + если это необходимо, а также зависимостей к ассетам. */ -struct PreparedModelCollision { - struct Cuboid { - glm::vec3 From, To; - uint8_t Faces; - - std::vector Transformations; - }; +struct PreparedModel { + // Упрощённая коллизия + std::vector> Cuboids; + // Зависимости от текстур, которые нужно сообщить клиенту + std::unordered_map> TextureDependencies; + // Зависимости от моделей + std::unordered_map> ModelDependencies; - std::vector Cuboids; - std::vector SubModels; + PreparedModel(const std::string& domain, const LV::PreparedModel& model); + PreparedModel(const std::string& domain, const js::object& glTF); + PreparedModel(const std::string& domain, Resource glb); - PreparedModelCollision(const PreparedModel& model); - PreparedModelCollision(const std::string& domain, const js::object& glTF); - PreparedModelCollision(const std::string& domain, Resource glb); + PreparedModel() = default; + PreparedModel(const PreparedModel&) = default; + PreparedModel(PreparedModel&&) = default; - PreparedModelCollision() = default; - PreparedModelCollision(const PreparedModelCollision&) = default; - PreparedModelCollision(PreparedModelCollision&&) = default; + PreparedModel& operator=(const PreparedModel&) = default; + PreparedModel& operator=(PreparedModel&&) = default; +}; - PreparedModelCollision& operator=(const PreparedModelCollision&) = default; - PreparedModelCollision& operator=(PreparedModelCollision&&) = default; +struct ModelDependency { + // Прямые зависимости к тестурам и моделям + std::vector TextureDeps; + std::vector ModelDeps; + // Коллизия + std::vector> Cuboids; + + // + bool Ready = false; + // Полный список зависимостей рекурсивно + std::vector FullSubTextureDeps; + std::vector FullSubModelDeps; }; /* @@ -59,7 +69,7 @@ public: std::unordered_map>> Nodestates; - std::unordered_map>> Models; + std::unordered_map>> Models; }; private: @@ -76,7 +86,7 @@ private: static constexpr size_t ChunkSize = 4096; bool IsFull = false; std::bitset Empty; - std::array, ChunkSize> Entries; + std::array, ChunkSize> Entries; TableEntry() { Empty.set(); @@ -87,14 +97,64 @@ private: // Связь ресурсов по идентификаторам std::vector>> Table[(int) EnumAssets::MAX_ENUM]; - // Распаршенные ресурсы, для использования сервером - std::vector>> Table_NodeState; - std::vector>> Table_Model; + // Распаршенные ресурсы, для использования сервером (сбор зависимостей профиля нод и расчёт коллизии если нужно) + // Первичные зависимости Nodestate к моделям + std::vector>>> Table_NodeState; + // Упрощённые модели для коллизии + std::vector>> Table_Model; // Связь домены -> {ключ -> идентификатор} std::unordered_map> KeyToId[(int) EnumAssets::MAX_ENUM]; std::tuple&> nextId(EnumAssets type); + + + ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) { + auto& keyToId = KeyToId[(int) type]; + if(auto iterKTI = keyToId.find(domain); iterKTI != keyToId.end()) { + if(auto iterKey = iterKTI->second.find(key); iterKey != iterKTI->second.end()) { + return iterKey->second; + } + } + + auto [id, entry] = nextId(type); + keyToId[domain][key] = id; + + // Расширяем таблицу с ресурсами, если необходимо + ssize_t tableChunks = (ssize_t) (id/TableEntry::ChunkSize)-(ssize_t) Table[(int) type].size()+1; + for(; tableChunks > 0; tableChunks--) { + Table[(int) type].emplace_back(std::make_unique>()); + + if(type == EnumAssets::Nodestate) + Table_NodeState.emplace_back(std::make_unique>>()); + else if(type == EnumAssets::Model) + Table_Model.emplace_back(std::make_unique>()); + } + + return id; + } + + std::optional> getResource(EnumAssets type, ResourceId id) { + assert(id < Table[(int) type].size()*TableEntry::ChunkSize); + auto& value = Table[(int) type][id / TableEntry::ChunkSize]->Entries[id % TableEntry::ChunkSize]; + if(value) + return {{value->Res, value->Domain, value->Key}}; + else + return std::nullopt; + } + + const std::optional>& getResourceNodestate(ResourceId id) { + assert(id < Table_NodeState.size()*TableEntry::ChunkSize); + return Table_NodeState[id / TableEntry::ChunkSize] + ->Entries[id % TableEntry::ChunkSize]; + } + + + const std::optional& getResourceModel(ResourceId id) { + assert(id < Table_Model.size()*TableEntry::ChunkSize); + return Table_Model[id / TableEntry::ChunkSize] + ->Entries[id % TableEntry::ChunkSize]; + } }; TOS::SpinlockObject LocalObj; @@ -167,28 +227,56 @@ public: resource должен содержать домен и путь */ ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) { + return LocalObj.lock()->getId(type, domain, key); + } + + // Выдаёт ресурс по идентификатору + std::optional> getResource(EnumAssets type, ResourceId id) { + return LocalObj.lock()->getResource(type, id); + } + + // Выдаёт зависимости к ресурсам профиля ноды + std::tuple, std::vector> + getNodeDependency(const std::string& domain, const std::string& key) + { auto lock = LocalObj.lock(); - auto& keyToId = lock->KeyToId[(int) type]; - if(auto iterKTI = keyToId.find(domain); iterKTI != keyToId.end()) { - if(auto iterKey = iterKTI->second.find(key); iterKey != iterKTI->second.end()) { - return iterKey->second; + AssetsNodestate nodestateId = lock->getId(EnumAssets::Nodestate, domain, key); + + std::vector models; + std::vector textures; + + if(auto subModelsPtr = lock->getResourceNodestate(nodestateId)) { + for(AssetsModel resId : *subModelsPtr) { + const auto& subModel = lock->getResourceModel(resId); + + if(!subModel) + continue; + + models.push_back(resId); + models.append_range(subModel->FullSubModelDeps); + textures.append_range(subModel->FullSubTextureDeps); } } - auto [id, entry] = lock->nextId(type); - keyToId[domain][key] = id; - return id; + { + std::sort(models.begin(), models.end()); + auto eraseIter = std::unique(models.begin(), models.end()); + models.erase(eraseIter, models.end()); + models.shrink_to_fit(); + } + + { + std::sort(textures.begin(), textures.end()); + auto eraseIter = std::unique(textures.begin(), textures.end()); + textures.erase(eraseIter, textures.end()); + textures.shrink_to_fit(); + } + return {nodestateId, std::move(models), std::move(textures)}; } - std::optional> getResource(EnumAssets type, ResourceId id) { - auto lock = LocalObj.lock(); - assert(id < lock->Table[(int) type].size()*TableEntry::ChunkSize); - auto& value = lock->Table[(int) type][id / TableEntry::ChunkSize]->Entries[id % TableEntry::ChunkSize]; - if(value) - return {{value->Res, value->Domain, value->Key}}; - else - return std::nullopt; - } +private: + TOS::Logger LOG = "Server>AssetsManager"; + }; } \ No newline at end of file diff --git a/Src/Server/GameServer.cpp b/Src/Server/GameServer.cpp index c5646aa..8de01f5 100644 --- a/Src/Server/GameServer.cpp +++ b/Src/Server/GameServer.cpp @@ -1512,6 +1512,15 @@ void GameServer::prerun() { } void GameServer::run() { + { + IWorldSaveBackend::TickSyncInfo_In in; + for(int x = -1; x <= 1; x++) + for(int y = -1; y <= 1; y++) + for(int z = -1; z <= 1; z++) + in.Load[0].push_back(Pos::GlobalChunk(x, y, z)); + + stepGeneratorAndLuaAsync(SaveBackend.World->tickSync(std::move(in))); + } while(true) { ((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256);