From abe7b987c4785117e5d6ddb28f7144284f507389 Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Sun, 4 Jan 2026 15:49:40 +0600 Subject: [PATCH] =?UTF-8?q?=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B0=D1=80=D1=85?= =?UTF-8?q?=D0=B8=D1=82=D0=B5=D0=BA=D1=82=D1=83=D1=80=D1=8B=20=D0=BD=D0=B0?= =?UTF-8?q?=20=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=BD=D0=B5=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Common/Abstract.cpp | 309 +++++-------- Src/Common/Abstract.hpp | 89 ++-- Src/Common/AssetsPreloader.hpp | 38 -- Src/Server/AssetsManager.cpp | 763 --------------------------------- Src/Server/AssetsManager.hpp | 308 ------------- Src/Server/GameServer.cpp | 144 ------- Src/Server/GameServer.hpp | 3 - 7 files changed, 161 insertions(+), 1493 deletions(-) delete mode 100644 Src/Server/AssetsManager.cpp delete mode 100644 Src/Server/AssetsManager.hpp diff --git a/Src/Common/Abstract.cpp b/Src/Common/Abstract.cpp index 29c4683..3273814 100644 --- a/Src/Common/Abstract.cpp +++ b/Src/Common/Abstract.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -26,51 +27,6 @@ namespace LV { namespace fs = std::filesystem; -PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, std::string_view defaultDomain) { - PrecompiledTexturePipeline result; - - std::string_view view(cmd); - const size_t trimPos = view.find_first_not_of(" \t\r\n"); - if(trimPos == std::string_view::npos) - MAKE_ERROR("Пустая текстурная команда"); - - view = view.substr(trimPos); - - const bool isPipeline = view.size() >= 3 - && view.compare(0, 3, "tex") == 0 - && (view.size() == 3 || std::isspace(static_cast(view[3]))); - - if(!isPipeline) { - auto [domain, key] = parseDomainKey(std::string(view), defaultDomain); - result.Assets.emplace_back(std::move(domain), std::move(key)); - return result; - } - - TexturePipelineProgram program; - std::string err; - if(!program.compile(std::string(view), &err)) { - MAKE_ERROR("Ошибка разбора pipeline: " << err); - } - - result.IsSource = true; - result.Pipeline.assign(reinterpret_cast(view.data()), view.size()); - - std::unordered_set seen; - for(const auto& patch : program.patches()) { - auto [domain, key] = parseDomainKey(patch.Name, defaultDomain); - std::string token; - token.reserve(domain.size() + key.size() + 1); - token.append(domain); - token.push_back(':'); - token.append(key); - if(seen.insert(token).second) - result.Assets.emplace_back(std::move(domain), std::move(key)); - } - - return result; -} - - std::u8string compressVoxels_byte(const std::vector& voxels) { std::u8string compressed; std::vector defines; @@ -868,7 +824,21 @@ std::u8string unCompressLinear(std::u8string_view data) { return *(std::u8string*) &outString; } -PreparedNodeState::PreparedNodeState(const std::string_view modid, const js::object& profile) { +ResourceHeader HeadlessNodeState::parse(const js::object& profile, const std::function& modelResolver) { + std::vector headerIds; + + std::function headerResolver = + [&](const std::string_view model) -> uint16_t { + AssetsModel id = modelResolver(model); + auto iter = std::find(headerIds.begin(), headerIds.end(), id); + if(iter == headerIds.end()) { + headerIds.push_back(id); + return headerIds.size()-1; + } + + return iter-headerIds.begin(); + }; + for(auto& [condition, variability] : profile) { // Распарсить условие uint16_t node = parseCondition(condition); @@ -881,39 +851,40 @@ PreparedNodeState::PreparedNodeState(const std::string_view modid, const js::obj if(variability.is_array()) { // Варианты условия for(const js::value& model : variability.as_array()) { - models.push_back(parseModel(modid, model.as_object())); + models.push_back(parseModel(model.as_object(), headerResolver)); } HasVariability = true; } else if (variability.is_object()) { // Один список моделей на условие - models.push_back(parseModel(modid, variability.as_object())); + models.push_back(parseModel(variability.as_object(), headerResolver)); } else { MAKE_ERROR("Условию должен соответствовать список или объект"); } Routes.emplace_back(node, std::move(models)); } + + ResourceHeader rh; + rh.reserve(headerIds.size()*sizeof(AssetsModel)); + + for(AssetsModel id : headerIds) { + rh += std::u8string_view((const char8_t*) &id, sizeof(AssetsModel)); + } + + return rh; } -PreparedNodeState::PreparedNodeState(const std::string_view modid, const sol::table& profile) { - +ResourceHeader HeadlessNodeState::parse(const sol::table& profile, const std::function& modelResolver) { + return std::u8string(); } -PreparedNodeState::PreparedNodeState(const std::u8string_view data) { +void HeadlessNodeState::load(const std::u8string_view data) { Net::LinearReader lr(data); lr.read(); uint16_t size; - lr >> size; - - LocalToModel.reserve(size); - for(int counter = 0; counter < size; counter++) { - AssetsModel modelId; - lr >> modelId; - LocalToModel.push_back(modelId); - } lr >> size; Nodes.reserve(size); @@ -1026,21 +997,12 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) { lr.checkUnreaded(); } -std::u8string PreparedNodeState::dump() const { +std::u8string HeadlessNodeState::dump() const { Net::Packet result; const char magic[] = "bn"; result.write(reinterpret_cast(magic), 2); - // ResourceToLocalId - assert(LocalToModelKD.size() < (1 << 16)); - assert(LocalToModelKD.size() == LocalToModel.size()); - result << uint16_t(LocalToModel.size()); - - for(AssetsModel modelId : LocalToModel) { - result << modelId; - } - // Nodes assert(Nodes.size() < (1 << 16)); result << uint16_t(Nodes.size()); @@ -1112,7 +1074,7 @@ std::u8string PreparedNodeState::dump() const { return result.complite(); } -uint16_t PreparedNodeState::parseCondition(const std::string_view expression) { +uint16_t HeadlessNodeState::parseCondition(const std::string_view expression) { enum class EnumTokenKind { LParen, RParen, Plus, Minus, Star, Slash, Percent, @@ -1424,55 +1386,11 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) { }; return lambdaParse(0); - - // std::unordered_map vars; - // std::function lambdaCalcNode = [&](uint16_t nodeId) -> int { - // const Node& node = Nodes[nodeId]; - // if(const Node::Num* value = std::get_if(&node.v)) { - // return value->v; - // } else if(const Node::Var* value = std::get_if(&node.v)) { - // auto iter = vars.find(value->name); - // if(iter == vars.end()) - // MAKE_ERROR("Неопознанное состояние"); - - // return iter->second; - // } else if(const Node::Unary* value = std::get_if(&node.v)) { - // int rNodeValue = lambdaCalcNode(value->rhs); - // switch(value->op) { - // case Op::Not: return !rNodeValue; - // case Op::Pos: return +rNodeValue; - // case Op::Neg: return -rNodeValue; - // default: - // std::unreachable(); - // } - // } else if(const Node::Binary* value = std::get_if(&node.v)) { - // int lNodeValue = lambdaCalcNode(value->lhs); - // int rNodeValue = lambdaCalcNode(value->rhs); - - // switch(value->op) { - // case Op::Add: return lNodeValue+rNodeValue; - // case Op::Sub: return lNodeValue-rNodeValue; - // case Op::Mul: return lNodeValue*rNodeValue; - // case Op::Div: return lNodeValue/rNodeValue; - // case Op::Mod: return lNodeValue%rNodeValue; - // case Op::LT: return lNodeValuerNodeValue; - // case Op::GE: return lNodeValue>=rNodeValue; - // case Op::EQ: return lNodeValue==rNodeValue; - // case Op::NE: return lNodeValue!=rNodeValue; - // case Op::And: return lNodeValue&&rNodeValue; - // case Op::Or: return lNodeValue||rNodeValue; - // default: - // std::unreachable(); - // } - // } else { - // std::unreachable(); - // } - // }; } -std::pair> HeadlessNodeState::parseModel(const std::string_view modid, const js::object& obj) { +std::pair> + HeadlessNodeState::parseModel(const js::object& obj, const std::function& modelResolver) +{ // ModelToLocalId bool uvlock; @@ -1497,22 +1415,7 @@ std::pairas_array()); } - auto [domain, key] = parseDomainKey((std::string) js_obj.at("model").as_string(), modid); - - uint16_t resId = 0; - for(auto& [lDomain, lKey] : LocalToModelKD) { - if(lDomain == domain && lKey == key) - break; - - resId++; - } - - if(resId == LocalToModelKD.size()) { - LocalToModelKD.emplace_back(domain, key); - } - - subModel.Id = resId; + subModel.Id = modelResolver((std::string) js_obj.at("model").as_string()); result.Models.push_back(std::move(subModel)); } @@ -1557,7 +1446,7 @@ std::pair PreparedNodeState::parseTransormations(const js::array& arr) { +std::vector HeadlessNodeState::parseTransormations(const js::array& arr) { std::vector result; for(const js::value& js_value : arr) { @@ -1596,7 +1485,42 @@ std::vector PreparedNodeState::parseTransormations(const js::arr } -PreparedModel::PreparedModel(const std::string_view modid, const js::object& profile) { +ResourceHeader HeadlessModel::parse( + const js::object& profile, + const std::function& modelResolver, + const std::function(const std::string_view texturePipelineSrc)>& textureResolver +) { + std::vector headerIdsModels; + + std::function headerResolverModel = + [&](const std::string_view model) -> uint16_t { + AssetsModel id = modelResolver(model); + auto iter = std::find(headerIdsModels.begin(), headerIdsModels.end(), id); + if(iter == headerIdsModels.end()) { + headerIdsModels.push_back(id); + return headerIdsModels.size()-1; + } + + return iter-headerIdsModels.begin(); + }; + + std::vector> headerIdsTextures; + std::unordered_map textureToLocal; + + std::function headerResolverTexture = + [&](const std::string_view texturePipelineSrc) -> uint16_t { + auto iter = textureToLocal.find(texturePipelineSrc); + if(iter != textureToLocal.end()) { + return iter->second; + } + + std::vector program = textureResolver(texturePipelineSrc); + headerIdsTextures.push_back(program); + uint16_t id = textureToLocal[(std::string) texturePipelineSrc] = headerIdsTextures.size()-1; + return id; + }; + + if(profile.contains("gui_light")) { std::string_view gui_light = profile.at("gui_light").as_string(); @@ -1649,7 +1573,7 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro const js::object& textures = textures_val->as_object(); for(const auto& [key, value] : textures) { - Textures[key] = compileTexturePipeline((std::string) value.as_string(), modid); + Textures[key] = headerResolverTexture(value.as_string()); } } @@ -1802,19 +1726,17 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro for(const js::value& value : submodels) { if(const auto model_key = value.try_as_string()) { - auto [domain, key] = parseDomainKey((std::string) *model_key, modid); - SubModels.push_back({std::move(domain), std::move(key), std::nullopt}); + SubModels.emplace_back(headerResolverModel(*model_key), std::nullopt); } else { const js::object& obj = value.as_object(); const std::string model_key_str = (std::string) obj.at("model").as_string(); - auto [domain, key] = parseDomainKey(model_key_str, modid); std::optional scene; if(const auto scene_val = obj.try_at("scene")) { scene = static_cast(scene_val->to_number()); } - SubModels.push_back({std::move(domain), std::move(key), scene}); + SubModels.emplace_back(headerResolverModel(model_key_str), scene); } } } @@ -1826,14 +1748,10 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro SubModel result; if(auto path = sub_val.try_as_string()) { - auto [domain, key] = parseDomainKey((std::string) path.value(), modid); - result.Domain = std::move(domain); - result.Key = std::move(key); + result.Id = headerResolverModel(path.value()); } else { const js::object& sub = sub_val.as_object(); - auto [domain, key] = parseDomainKey((std::string) sub.at("path").as_string(), modid); - result.Domain = std::move(domain); - result.Key = std::move(key); + result.Id = headerResolverModel(sub.at("path").as_string()); if(boost::system::result scene_val = profile.try_at("scene")) result.Scene = scene_val->to_number(); @@ -1842,13 +1760,42 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro SubModels.emplace_back(std::move(result)); } } + + // Заголовок + TOS::ByteBuffer rh; + + { + uint32_t fullSize = 0; + for(const auto& vector : headerIdsTextures) + fullSize += vector.size(); + rh.reserve(2+headerIdsModels.size()*sizeof(AssetsModel)+2+(4)*headerIdsTextures.size()+fullSize); + } + + TOS::ByteBuffer::Writer wr; + wr << uint16_t(headerIdsModels.size()); + for(AssetsModel id : headerIdsModels) + wr << id; + + wr << uint16_t(headerIdsTextures.size()); + for(const auto& pipe : headerIdsTextures) { + wr << uint32_t(pipe.size()); + wr << pipe; + } + + TOS::ByteBuffer buff = wr.complite(); + + return std::u8string((const char8_t*) buff.data(), buff.size()); } -PreparedModel::PreparedModel(const std::string_view modid, const sol::table& profile) { +ResourceHeader HeadlessModel::parse( + const sol::table& profile, + const std::function& modelResolver, + const std::function(const std::string_view texturePipelineSrc)>& textureResolver +) { std::unreachable(); } -PreparedModel::PreparedModel(const std::u8string& data) { +void HeadlessModel::load(const std::u8string_view data) { Net::LinearReader lr(data); lr.read(); @@ -1895,17 +1842,10 @@ PreparedModel::PreparedModel(const std::u8string& data) { for(int counter = 0; counter < size; counter++) { std::string tkey; lr >> tkey; - TexturePipeline pipe; + uint16_t id; + lr >> id; - uint16_t size; - lr >> size; - pipe.BinTextures.reserve(size); - for(int iter = 0; iter < size; iter++) - pipe.BinTextures.push_back(lr.read()); - - lr >> (std::string&) pipe.Pipeline; - - CompiledTextures.insert({tkey, std::move(pipe)}); + Textures.insert({tkey, id}); } lr >> size; @@ -1962,7 +1902,7 @@ PreparedModel::PreparedModel(const std::u8string& data) { SubModels.reserve(size8); for(int counter = 0; counter < size8; counter++) { SubModel sub; - lr >> sub.Domain >> sub.Key; + lr >> sub.Id; uint16_t val = lr.read(); if(val != uint16_t(-1)) { sub.Scene = val; @@ -1974,7 +1914,7 @@ PreparedModel::PreparedModel(const std::u8string& data) { lr.checkUnreaded(); } -std::u8string PreparedModel::dump() const { +std::u8string HeadlessModel::dump() const { Net::Packet result; result << 'b' << 'm'; @@ -2013,19 +1953,9 @@ std::u8string PreparedModel::dump() const { assert(Textures.size() < (1 << 16)); result << uint16_t(Textures.size()); - assert(CompiledTextures.size() == Textures.size()); - - for(const auto& [tkey, dk] : CompiledTextures) { + for(const auto& [tkey, id] : Textures) { assert(tkey.size() < 32); - result << tkey; - - assert(dk.BinTextures.size() < 512); - result << uint16_t(dk.BinTextures.size()); - for(size_t iter = 0; iter < dk.BinTextures.size(); iter++) { - result << dk.BinTextures[iter]; - } - - result << (const std::string&) dk.Pipeline; + result << tkey << id; } assert(Cuboids.size() < (1 << 16)); @@ -2064,10 +1994,7 @@ std::u8string PreparedModel::dump() const { assert(SubModels.size() < 256); result << uint8_t(SubModels.size()); for(const SubModel& model : SubModels) { - assert(model.Domain.size() < 32); - assert(model.Key.size() < 32); - - result << model.Domain << model.Key; + result << model.Id; if(model.Scene) result << uint16_t(*model.Scene); else diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index 4d287a6..fe3bcc4 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -19,6 +19,45 @@ namespace LV { + +namespace detail { + +// Позволяет использовать как std::string так и std::string_view в хэш таблицах +struct TSVHash { + using is_transparent = void; + + size_t operator()(std::string_view sv) const noexcept { + return std::hash{}(sv); + } + + size_t operator()(const std::string& s) const noexcept { + return std::hash{}(s); + } +}; + +// Позволяет использовать как std::string так и std::string_view в хэш таблицах +struct TSVEq { + using is_transparent = void; + + bool operator()(std::string_view a, std::string_view b) const noexcept { + return a == b; + } + + bool operator()(const std::string& a, std::string_view b) const noexcept { + return std::string_view(a) == b; + } + + bool operator()(std::string_view a, const std::string& b) const noexcept { + return a == std::string_view(b); + } + + bool operator()(const std::string& a, const std::string& b) const noexcept { + return a == b; + } +}; + +} + namespace js = boost::json; namespace Pos { @@ -514,29 +553,6 @@ inline std::pair parseDomainKey(const std::string& val } } -struct PrecompiledTexturePipeline { - // Локальные идентификаторы пайплайна в домен+ключ - std::vector> Assets; - // Чистый код текстурных преобразований, локальные идентификаторы связаны с Assets - std::u8string Pipeline; - // Pipeline содержит исходный текст (tex ...), нужен для компиляции на сервере - bool IsSource = false; -}; - -struct TexturePipeline { - // Разыменованые идентификаторы - std::vector BinTextures; - // Чистый код текстурных преобразований, локальные идентификаторы связаны с BinTextures - std::u8string Pipeline; - - bool operator==(const TexturePipeline& other) const { - return BinTextures == other.BinTextures && Pipeline == other.Pipeline; - } -}; - -// Компилятор текстурных потоков -PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, std::string_view defaultDomain = "core"); - struct NodestateEntry { std::string Name; int Variability = 0; // Количество возможный значений состояния @@ -646,8 +662,6 @@ struct HeadlessNodeState { std::vector Transforms; }; - // Локальный идентификатор в именной ресурс - std::vector LocalToModelKD; // Ноды выражений std::vector Nodes; // Условия -> вариации модели + веса @@ -865,7 +879,7 @@ private: bool HasVariability = false; uint16_t parseCondition(const std::string_view condition); - std::pair> parseModel(const std::string_view modid, const js::object& obj); + std::pair> parseModel(const js::object& obj, const std::function& modelResolver); std::vector parseTransormations(const js::array& arr); }; @@ -893,8 +907,7 @@ struct HeadlessModel { }; std::unordered_map Display; - std::unordered_map Textures; - std::unordered_map CompiledTextures; + std::unordered_map Textures; struct Cuboid { bool Shade; @@ -916,7 +929,7 @@ struct HeadlessModel { std::vector Cuboids; struct SubModel { - std::string Domain, Key; + uint16_t Id; std::optional Scene; }; @@ -962,8 +975,7 @@ struct HeadlessModel { struct PreparedGLTF { std::vector TextureKey; - std::unordered_map Textures; - std::unordered_map CompiledTextures; + std::unordered_map Textures; std::vector Vertices; @@ -1060,19 +1072,4 @@ struct hash { return v; } }; - -template <> -struct hash { - std::size_t operator()(const LV::TexturePipeline& tp) const noexcept { - size_t seed = 0; - - for (const auto& tex : tp.BinTextures) - seed ^= std::hash{}(tex) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - - std::string_view sv(reinterpret_cast(tp.Pipeline.data()), tp.Pipeline.size()); - seed ^= std::hash{}(sv) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - - return seed; - } -}; } diff --git a/Src/Common/AssetsPreloader.hpp b/Src/Common/AssetsPreloader.hpp index 47059b2..9be9c22 100644 --- a/Src/Common/AssetsPreloader.hpp +++ b/Src/Common/AssetsPreloader.hpp @@ -46,44 +46,6 @@ static constexpr const char* EnumAssetsToDirectory(LV::EnumAssets value) { namespace LV { -namespace detail { - -// Позволяет использовать как std::string так и std::string_view в хэш таблицах -struct TSVHash { - using is_transparent = void; - - size_t operator()(std::string_view sv) const noexcept { - return std::hash{}(sv); - } - - size_t operator()(const std::string& s) const noexcept { - return std::hash{}(s); - } -}; - -// Позволяет использовать как std::string так и std::string_view в хэш таблицах -struct TSVEq { - using is_transparent = void; - - bool operator()(std::string_view a, std::string_view b) const noexcept { - return a == b; - } - - bool operator()(const std::string& a, std::string_view b) const noexcept { - return std::string_view(a) == b; - } - - bool operator()(std::string_view a, const std::string& b) const noexcept { - return a == std::string_view(b); - } - - bool operator()(const std::string& a, const std::string& b) const noexcept { - return a == b; - } -}; - -} - namespace fs = std::filesystem; using AssetType = EnumAssets; diff --git a/Src/Server/AssetsManager.cpp b/Src/Server/AssetsManager.cpp deleted file mode 100644 index 2576ec9..0000000 --- a/Src/Server/AssetsManager.cpp +++ /dev/null @@ -1,763 +0,0 @@ -#include "AssetsManager.hpp" -#include "Common/Abstract.hpp" -#include "Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp" -#include "boost/json.hpp" -#include "png++/rgb_pixel.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "sol/sol.hpp" - - -namespace LV::Server { - -PreparedModel::PreparedModel(const std::string& domain, const LV::PreparedModel& model) { - Cuboids.reserve(model.Cuboids.size()); - - for(auto& [key, cmd] : model.Textures) { - for(auto& [domain, key] : cmd.Assets) { - TextureDependencies[domain].push_back(key); - } - } - - 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; - // } -} - -PreparedModel::PreparedModel(const std::string& domain, const PreparedGLTF& glTF) { - -} - -void AssetsManager::loadResourceFromFile(EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const { - switch(type) { - case EnumAssets::Nodestate: loadResourceFromFile_Nodestate (out, domain, key, path); return; - case EnumAssets::Particle: loadResourceFromFile_Particle (out, domain, key, path); return; - case EnumAssets::Animation: loadResourceFromFile_Animation (out, domain, key, path); return; - case EnumAssets::Model: loadResourceFromFile_Model (out, domain, key, path); return; - case EnumAssets::Texture: loadResourceFromFile_Texture (out, domain, key, path); return; - case EnumAssets::Sound: loadResourceFromFile_Sound (out, domain, key, path); return; - case EnumAssets::Font: loadResourceFromFile_Font (out, domain, key, path); return; - default: - std::unreachable(); - } -} - -void AssetsManager::loadResourceFromLua(EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const { - switch(type) { - case EnumAssets::Nodestate: loadResourceFromLua_Nodestate(out, domain, key, profile); return; - case EnumAssets::Particle: loadResourceFromLua_Particle(out, domain, key, profile); return; - case EnumAssets::Animation: loadResourceFromLua_Animation(out, domain, key, profile); return; - case EnumAssets::Model: loadResourceFromLua_Model(out, domain, key, profile); return; - case EnumAssets::Texture: loadResourceFromLua_Texture(out, domain, key, profile); return; - case EnumAssets::Sound: loadResourceFromLua_Sound(out, domain, key, profile); return; - case EnumAssets::Font: loadResourceFromLua_Font(out, domain, key, profile); return; - default: - std::unreachable(); - } -} - -void AssetsManager::loadResourceFromFile_Nodestate(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const { - Resource res(path); - js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object(); - PreparedNodeState pns(domain, obj); - out.NewOrChange_Nodestates[domain].emplace_back(key, std::move(pns), fs::last_write_time(path)); -} - -void AssetsManager::loadResourceFromFile_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const { - std::unreachable(); -} - -void AssetsManager::loadResourceFromFile_Animation(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const { - std::unreachable(); -} - -void AssetsManager::loadResourceFromFile_Model(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const { - /* - json, glTF, glB - */ - - Resource res(path); - std::filesystem::file_time_type ftt = fs::last_write_time(path); - auto extension = path.extension(); - - if(extension == ".json") { - js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object(); - LV::PreparedModel pm(domain, obj); - out.NewOrChange_Models[domain].emplace_back(key, std::move(pm), ftt); - } else if(extension == ".gltf") { - js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object(); - PreparedGLTF gltf(domain, obj); - out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), ftt); - } else if(extension == ".glb") { - PreparedGLTF gltf(domain, res); - out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), ftt); - } else { - MAKE_ERROR("Не поддерживаемый формат файла"); - } -} - -void AssetsManager::loadResourceFromFile_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const { - Resource res(path); - - if(res.size() < 8) - MAKE_ERROR("Файл не является текстурой png или jpeg (недостаточный размер файла)"); - - if(png_check_sig(reinterpret_cast((unsigned char*) res.data()), 8)) { - // Это png - fs::file_time_type lwt = fs::last_write_time(path); - out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, res, lwt); - return; - } else if((int) res.data()[0] == 0xFF && (int) res.data()[1] == 0xD8) { - // Это jpeg - fs::file_time_type lwt = fs::last_write_time(path); - out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, res, lwt); - return; - } else { - MAKE_ERROR("Файл не является текстурой png или jpeg"); - } - - std::unreachable(); -} - -void AssetsManager::loadResourceFromFile_Sound(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const { - std::unreachable(); -} - -void AssetsManager::loadResourceFromFile_Font(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const { - std::unreachable(); -} - -void AssetsManager::loadResourceFromLua_Nodestate(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const { - if(std::optional path = profile.get>("path")) { - out.NewOrChange[(int) EnumAssets::Nodestate][domain].emplace_back(key, Resource(*path), fs::file_time_type::min()); - return; - } - - std::unreachable(); -} - -void AssetsManager::loadResourceFromLua_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const { - if(std::optional path = profile.get>("path")) { - out.NewOrChange[(int) EnumAssets::Particle][domain].emplace_back(key, Resource(*path), fs::file_time_type::min()); - return; - } - - std::unreachable(); -} - -void AssetsManager::loadResourceFromLua_Animation(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const { - if(std::optional path = profile.get>("path")) { - out.NewOrChange[(int) EnumAssets::Animation][domain].emplace_back(key, Resource(*path), fs::file_time_type::min()); - return; - } - - std::unreachable(); -} - -void AssetsManager::loadResourceFromLua_Model(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const { - if(std::optional path = profile.get>("path")) { - out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, Resource(*path), fs::file_time_type::min()); - return; - } - - std::unreachable(); -} - -void AssetsManager::loadResourceFromLua_Texture(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const { - if(std::optional path = profile.get>("path")) { - out.NewOrChange[(int) EnumAssets::Texture][domain].emplace_back(key, Resource(*path), fs::file_time_type::min()); - return; - } - - std::unreachable(); -} - -void AssetsManager::loadResourceFromLua_Sound(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const { - if(std::optional path = profile.get>("path")) { - out.NewOrChange[(int) EnumAssets::Sound][domain].emplace_back(key, Resource(*path), fs::file_time_type::min()); - return; - } - - std::unreachable(); -} - -void AssetsManager::loadResourceFromLua_Font(ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const { - if(std::optional path = profile.get>("path")) { - out.NewOrChange[(int) EnumAssets::Font][domain].emplace_back(key, Resource(*path), fs::file_time_type::min()); - return; - } - - std::unreachable(); -} - -AssetsManager::AssetsManager(asio::io_context& ioc) -{ - -} - -AssetsManager::~AssetsManager() = default; - -std::tuple&> AssetsManager::Local::nextId(EnumAssets type) { - auto& table = Table[(int) type]; - ResourceId id = -1; - std::optional *data = nullptr; - - for(size_t index = 0; index < table.size(); index++) { - auto& entry = *table[index]; - if(index == 0 && entry.Empty.test(0)) { - entry.Empty.reset(0); - } - - if(entry.IsFull) - continue; - - uint32_t pos = entry.Empty._Find_first(); - if(pos == entry.Empty.size()) { - entry.IsFull = true; - continue; - } - entry.Empty.reset(pos); - - if(entry.Empty._Find_next(pos) == entry.Empty.size()) - entry.IsFull = true; - - id = index*TableEntry::ChunkSize + pos; - data = &entry.Entries[pos]; - break; - } - - if(!data) { - table.emplace_back(std::make_unique>()); - auto& entry = *table.back(); - if(table.size() == 1 && entry.Empty.test(0)) { - entry.Empty.reset(0); - } - - uint32_t pos = entry.Empty._Find_first(); - entry.Empty.reset(pos); - if(entry.Empty._Find_next(pos) == entry.Empty.size()) - entry.IsFull = true; - - id = (table.size()-1)*TableEntry::ChunkSize + pos; - data = &entry.Entries[pos]; - - // Расширяем таблицу с ресурсами, если необходимо - 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, *data}; -} - -AssetsManager::ResourceChangeObj AssetsManager::recheckResources(const AssetsRegister& info) { - ResourceChangeObj result; - - // Найти пропавшие ресурсы - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - auto lock = LocalObj.lock(); - for(auto& [domain, resources] : lock->KeyToId[type]) { - for(auto& [key, id] : resources) { - if(!lock->Table[type][id / TableEntry::ChunkSize]->Entries[id % TableEntry::ChunkSize]) - continue; - - bool exists = false; - - for(const fs::path& path : info.Assets) { - fs::path file = path / domain; - - switch ((EnumAssets) type) { - case EnumAssets::Nodestate: file /= "nodestate"; break; - case EnumAssets::Particle: file /= "particle"; break; - case EnumAssets::Animation: file /= "animation"; break; - case EnumAssets::Model: file /= "model"; break; - case EnumAssets::Texture: file /= "texture"; break; - case EnumAssets::Sound: file /= "sound"; break; - case EnumAssets::Font: file /= "font"; break; - default: - std::unreachable(); - } - - file /= key; - - if(fs::exists(file) && !fs::is_directory(file)) { - exists = true; - break; - } - } - - if(exists) continue; - - auto iterDomain = info.Custom[type].find(domain); - if(iterDomain == info.Custom[type].end()) { - result.Lost[type][domain].push_back(key); - } else { - auto iterData = iterDomain->second.find(key); - if(iterData == iterDomain->second.end()) { - result.Lost[type][domain].push_back(key); - } - } - } - } - } - - // Если ресурс уже был найден более приоритетными директориями, то пропускаем его - std::unordered_map> findedResources[(int) EnumAssets::MAX_ENUM]; - - // Найти новые или изменённые ресурсы - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - for(auto& [domain, resources] : info.Custom[type]) { - auto lock = LocalObj.lock(); - const auto& keyToId = lock->KeyToId[type]; - auto iterDomain = keyToId.find(domain); - auto& findList = findedResources[type][domain]; - - if(iterDomain == keyToId.end()) { - // Ресурсы данного домена неизвестны - auto& domainList = result.NewOrChange[type][domain]; - for(auto& [key, id] : resources) { - // Подобрать идентификатор - // TODO: реализовать регистрации ресурсов из lua - domainList.emplace_back(key, Resource("assets/null"), fs::file_time_type::min()); - findList.insert(key); - } - } else { - for(auto& [key, id] : resources) { - if(findList.contains(key)) - // Ресурс уже был найден в вышестоящей директории - continue; - else if(iterDomain->second.contains(key)) { - // Ресурс уже есть, TODO: нужно проверить его изменение - loadResourceFromFile((EnumAssets) type, result, domain, key, "assets/null"); - } else { - // Ресурс не был известен - loadResourceFromFile((EnumAssets) type, result, domain, key, "assets/null"); - } - - findList.insert(key); - } - } - } - } - - for(const fs::path& path : info.Assets) { - if(!fs::exists(path)) - continue; - - for(auto begin = fs::directory_iterator(path), end = fs::directory_iterator(); begin != end; begin++) { - if(!begin->is_directory()) - continue; - - fs::path domainPath = begin->path(); - std::string domain = domainPath.filename(); - - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - fs::path resourcesPath = domainPath; - - switch ((EnumAssets) type) { - case EnumAssets::Nodestate: resourcesPath /= "nodestate"; break; - case EnumAssets::Particle: resourcesPath /= "particle"; break; - case EnumAssets::Animation: resourcesPath /= "animation"; break; - case EnumAssets::Model: resourcesPath /= "model"; break; - case EnumAssets::Texture: resourcesPath /= "texture"; break; - case EnumAssets::Sound: resourcesPath /= "sound"; break; - case EnumAssets::Font: resourcesPath /= "font"; break; - default: - std::unreachable(); - } - - auto& findList = findedResources[type][domain]; - auto lock = LocalObj.lock(); - auto iterDomain = lock->KeyToId[type].find(domain); - - if(!fs::exists(resourcesPath) || !fs::is_directory(resourcesPath)) - continue; - - // Рекурсивно загрузить ресурсы внутри папки resourcesPath - for(auto begin = fs::recursive_directory_iterator(resourcesPath), end = fs::recursive_directory_iterator(); begin != end; begin++) { - if(begin->is_directory()) - continue; - - fs::path file = begin->path(); - std::string key = fs::relative(begin->path(), resourcesPath).string(); - if(findList.contains(key)) - // Ресурс уже был найден в вышестоящей директории - continue; - else if(iterDomain != lock->KeyToId[type].end() && iterDomain->second.contains(key)) { - // Ресурс уже есть, TODO: нужно проверить его изменение - ResourceId id = iterDomain->second.at(key); - DataEntry& entry = *lock->Table[type][id / TableEntry::ChunkSize]->Entries[id % TableEntry::ChunkSize]; - - fs::file_time_type lwt = fs::last_write_time(file); - if(lwt != entry.FileChangeTime) - // Будем считать что ресурс изменился - loadResourceFromFile((EnumAssets) type, result, domain, key, file); - } else { - // Ресурс не был известен - loadResourceFromFile((EnumAssets) type, result, domain, key, file); - } - - findList.insert(key); - } - } - } - - } - - return result; -} - -AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const ResourceChangeObj& orr) { - // Потерянные и обновлённые идентификаторы - Out_applyResourceChange result; - - // Удаляем ресурсы - /* - Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор - Уже скомпилированные зависимости к ресурсам не будут - перекомпилироваться для смены идентификатора. Если нужный ресурс - появится, то привязка останется. Новые клиенты не получат ресурс - которого нет, но он может использоваться - */ - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - for(auto& [domain, resources] : orr.Lost[type]) { - auto lock = LocalObj.lock(); - auto& keyToIdDomain = lock->KeyToId[type].at(domain); - - for(const std::string& key : resources) { - auto iter = keyToIdDomain.find(key); - assert(iter != keyToIdDomain.end()); - - ResourceId resId = iter->second; - - 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->Entries[resId % TableEntry::ChunkSize].reset(); - } - } - } - - // Добавляем - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - for(auto& [domain, resources] : orr.NewOrChange[type]) { - auto lock = LocalObj.lock(); - auto& keyToIdDomain = lock->KeyToId[type][domain]; - - for(auto& [key, resource, lwt] : resources) { - ResourceId id = -1; - std::optional* data = nullptr; - - if(auto iterId = keyToIdDomain.find(key); iterId != keyToIdDomain.end()) { - id = iterId->second; - data = &lock->Table[(int) type][id / TableEntry::ChunkSize]->Entries[id % TableEntry::ChunkSize]; - } else { - auto [_id, _data] = lock->nextId((EnumAssets) type); - id = _id; - data = &_data; - } - - result.NewOrChange[type].push_back({id, resource}); - keyToIdDomain[key] = id; - - data->emplace(lwt, resource, domain, key); - - lock->HashToId[resource.hash()] = {(EnumAssets) type, id}; - } - } - - // Удалённые идентификаторы не считаются удалёнными, если были изменены - std::unordered_set noc; - for(auto& [id, _] : result.NewOrChange[type]) - noc.insert(id); - - std::unordered_set l(result.Lost[type].begin(), result.Lost[type].end()); - result.Lost[type].clear(); - std::set_difference(l.begin(), l.end(), noc.begin(), noc.end(), std::back_inserter(result.Lost[type])); - } - - // Приёмка новых/изменённых описаний состояний нод - if(!orr.NewOrChange_Nodestates.empty()) - { - auto lock = LocalObj.lock(); - for(auto& [domain, table] : orr.NewOrChange_Nodestates) { - for(auto& [key, _nodestate, ftt] : table) { - ResourceId resId = lock->getId(EnumAssets::Nodestate, domain, key); - std::optional& data = lock->Table[(int) EnumAssets::Nodestate][resId / TableEntry::ChunkSize]->Entries[resId % TableEntry::ChunkSize]; - PreparedNodeState nodestate = _nodestate; - - // Ресолвим модели - for(const auto& [lDomain, lKey] : nodestate.LocalToModelKD) { - nodestate.LocalToModel.push_back(lock->getId(EnumAssets::Model, lDomain, lKey)); - } - - // Сдампим для отправки клиенту (Кеш в пролёте?) - Resource res(nodestate.dump()); - - // На оповещение - result.NewOrChange[(int) EnumAssets::Nodestate].push_back({resId, res}); - - // Запись в таблице ресурсов - data.emplace(ftt, res, domain, key); - lock->HashToId[res.hash()] = {EnumAssets::Nodestate, resId}; - - lock->Table_NodeState[resId / TableEntry::ChunkSize] - ->Entries[resId % TableEntry::ChunkSize] = nodestate.LocalToModel; - } - } - } - - // Приёмка новых/изменённых моделей - if(!orr.NewOrChange_Models.empty()) - { - auto lock = LocalObj.lock(); - for(auto& [domain, table] : orr.NewOrChange_Models) { - auto& keyToIdDomain = lock->KeyToId[(int) EnumAssets::Model][domain]; - - for(auto& [key, _model, ftt] : table) { - ResourceId resId = -1; - std::optional* data = nullptr; - - if(auto iterId = keyToIdDomain.find(key); iterId != keyToIdDomain.end()) { - resId = iterId->second; - data = &lock->Table[(int) EnumAssets::Model][resId / TableEntry::ChunkSize]->Entries[resId % TableEntry::ChunkSize]; - } else { - auto [_id, _data] = lock->nextId((EnumAssets) EnumAssets::Model); - resId = _id; - data = &_data; - } - - keyToIdDomain[key] = resId; - - // Ресолвим текстуры - std::variant model = _model; - std::visit([&lock, &domain](auto& val) { - for(const auto& [key, pipeline] : val.Textures) { - TexturePipeline pipe; - if(pipeline.IsSource) { - std::string source(reinterpret_cast(pipeline.Pipeline.data()), pipeline.Pipeline.size()); - TexturePipelineProgram program; - std::string err; - if(!program.compile(source, &err)) { - MAKE_ERROR("Ошибка компиляции pipeline: " << err); - } - - auto resolver = [&](std::string_view name) -> std::optional { - auto [texDomain, texKey] = parseDomainKey(std::string(name), domain); - return lock->getId(EnumAssets::Texture, texDomain, texKey); - }; - - if(!program.link(resolver, &err)) { - MAKE_ERROR("Ошибка линковки pipeline: " << err); - } - - const std::vector bytes = program.toBytes(); - pipe.Pipeline.resize(bytes.size()); - if(!bytes.empty()) { - std::memcpy(pipe.Pipeline.data(), bytes.data(), bytes.size()); - } - } else { - pipe.Pipeline = pipeline.Pipeline; - } - - for(const auto& [domain, key] : pipeline.Assets) { - ResourceId texId = lock->getId(EnumAssets::Texture, domain, key); - pipe.BinTextures.push_back(texId); - } - - val.CompiledTextures[key] = std::move(pipe); - } - }, model); - - // Сдампим для отправки клиенту (Кеш в пролёте?) - std::u8string dump = std::visit([&lock](auto& val) { - return val.dump(); - }, model); - Resource res(std::move(dump)); - - // На оповещение - result.NewOrChange[(int) EnumAssets::Model].push_back({resId, res}); - - // Запись в таблице ресурсов - data->emplace(ftt, res, domain, key); - - lock->HashToId[res.hash()] = {EnumAssets::Model, resId}; - - // Для нужд сервера, ресолвим зависимости - PreparedModel pm = std::visit([&domain](auto& val) { - return PreparedModel(domain, val); - }, model); - - ModelDependency deps; - for(auto& [domain2, list] : pm.ModelDependencies) { - for(const std::string& key2 : list) { - ResourceId subResId = lock->getId(EnumAssets::Model, domain2, key2); - deps.ModelDeps.push_back(subResId); - } - } - - for(auto& [domain2, list] : pm.TextureDependencies) { - for(const std::string& key2 : list) { - ResourceId subResId = lock->getId(EnumAssets::Texture, domain2, key2); - deps.TextureDeps.push_back(subResId); - } - } - - lock->Table_Model[resId / TableEntry::ChunkSize] - ->Entries[resId % TableEntry::ChunkSize] = std::move(deps); - } - } - } - - // Дамп ключей assets - { - std::stringstream result; - - auto lock = LocalObj.lock(); - for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { - if(type == 0) - result << "Nodestate:\n"; - else if(type == 1) - result << "Particle:\n"; - else if(type == 2) - result << "Animation:\n"; - else if(type == 3) - result << "Model:\n"; - else if(type == 4) - result << "Texture:\n"; - else if(type == 5) - result << "Sound:\n"; - else if(type == 6) - result << "Font:\n"; - - for(const auto& [domain, list] : lock->KeyToId[type]) { - result << "\t" << domain << ":\n"; - - for(const auto& [key, id] : list) { - result << "\t\t" << key << " = " << id << '\n'; - } - } - } - - LOG.debug() << "Дамп ассетов:\n" << result.str(); - } - - // Вычислить зависимости моделей - { - // Затираем старые данные - 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; - 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(resId == subResId) { - const auto object1 = lock->getResource(EnumAssets::Model, resId); - const auto object2 = lock->getResource(EnumAssets::Model, subResId); - LOG.warn() << "В моделе " << std::get<1>(*object1) << ':' << std::get<2>(*object1) - << " обнаружена циклическая зависимость с самой собою"; - 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); - } - } - - entry.FullSubTextureDeps.append_range(entry.TextureDeps); - { - 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 deleted file mode 100644 index b5614c9..0000000 --- a/Src/Server/AssetsManager.hpp +++ /dev/null @@ -1,308 +0,0 @@ -#pragma once - -#include "Common/Abstract.hpp" -#include "TOSLib.hpp" -#include "Common/Net.hpp" -#include "sha2.hpp" -#include -#include -#include -#include -#include -#include - - -namespace LV::Server { - -namespace fs = std::filesystem; - -/* - Используется для расчёта коллизии, - если это необходимо, а также зависимостей к ассетам. -*/ -struct PreparedModel { - // Упрощённая коллизия - std::vector> Cuboids; - // Зависимости от текстур, которые нужно сообщить клиенту - std::unordered_map> TextureDependencies; - // Зависимости от моделей - std::unordered_map> ModelDependencies; - - PreparedModel(const std::string& domain, const LV::PreparedModel& model); - PreparedModel(const std::string& domain, const PreparedGLTF& glTF); - - PreparedModel() = default; - PreparedModel(const PreparedModel&) = default; - PreparedModel(PreparedModel&&) = default; - - PreparedModel& operator=(const PreparedModel&) = default; - PreparedModel& operator=(PreparedModel&&) = default; -}; - -struct ModelDependency { - // Прямые зависимости к тестурам и моделям - std::vector TextureDeps; - std::vector ModelDeps; - // Коллизия - std::vector> Cuboids; - - // - bool Ready = false; - // Полный список зависимостей рекурсивно - std::vector FullSubTextureDeps; - std::vector FullSubModelDeps; -}; - -/* - Работает с ресурсами из папок assets. - Использует папку server_cache/assets для хранения - преобразованных ресурсов -*/ -class AssetsManager { -public: - struct ResourceChangeObj { - // Потерянные ресурсы - std::unordered_map> Lost[(int) EnumAssets::MAX_ENUM]; - // Домен и ключ ресурса - std::unordered_map>> NewOrChange[(int) EnumAssets::MAX_ENUM]; - std::unordered_map>> NewOrChange_Nodestates; - std::unordered_map, fs::file_time_type>>> NewOrChange_Models; - - // std::unordered_map>> Models; - }; - -private: - // Данные об отслеживаемых файлах - struct DataEntry { - // Время последнего изменения файла - fs::file_time_type FileChangeTime; - Resource Res; - std::string Domain, Key; - }; - - template - struct TableEntry { - static constexpr size_t ChunkSize = 4096; - bool IsFull = false; - std::bitset Empty; - std::array, ChunkSize> Entries; - - TableEntry() { - Empty.set(); - } - }; - - struct Local { - // Связь ресурсов по идентификаторам - std::vector>> Table[(int) EnumAssets::MAX_ENUM]; - - // Распаршенные ресурсы, для использования сервером (сбор зависимостей профиля нод и расчёт коллизии если нужно) - // Первичные зависимости Nodestate к моделям - std::vector>>> Table_NodeState; - // Упрощённые модели для коллизии - std::vector>> Table_Model; - - // Связь домены -> {ключ -> идентификатор} - std::unordered_map> KeyToId[(int) EnumAssets::MAX_ENUM]; - std::unordered_map> HashToId; - - 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; - - 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; - } - - std::optional> getResource(const Hash_t& hash) { - auto iter = HashToId.find(hash); - if(iter == HashToId.end()) - return std::nullopt; - - auto [type, id] = iter->second; - std::optional> res = getResource(type, id); - if(!res) { - HashToId.erase(iter); - return std::nullopt; - } - - if(std::get(*res).hash() == hash) { - auto& [resource, domain, key] = *res; - return std::tuple{resource, domain, key, type, id}; - } - - - HashToId.erase(iter); - 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; - - /* - Загрузка ресурса с файла. При необходимости приводится - к внутреннему формату и сохраняется в кеше - */ - void loadResourceFromFile (EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const; - void loadResourceFromLua (EnumAssets type, ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const; - - void loadResourceFromFile_Nodestate (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const; - void loadResourceFromFile_Particle (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const; - void loadResourceFromFile_Animation (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const; - void loadResourceFromFile_Model (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const; - void loadResourceFromFile_Texture (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const; - void loadResourceFromFile_Sound (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const; - void loadResourceFromFile_Font (ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const; - - void loadResourceFromLua_Nodestate (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const; - void loadResourceFromLua_Particle (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const; - void loadResourceFromLua_Animation (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const; - void loadResourceFromLua_Model (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const; - void loadResourceFromLua_Texture (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const; - void loadResourceFromLua_Sound (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const; - void loadResourceFromLua_Font (ResourceChangeObj& out, const std::string& domain, const std::string& key, const sol::table& profile) const; - -public: - AssetsManager(asio::io_context& ioc); - ~AssetsManager(); - - /* - Перепроверка изменений ресурсов по дате изменения, пересчёт хешей. - Обнаруженные изменения должны быть отправлены всем клиентам. - Ресурсы будут обработаны в подходящий формат и сохранены в кеше. - Одновременно может выполнятся только одна такая функция - Используется в GameServer - */ - - struct AssetsRegister { - /* - Пути до активных папок assets, соответствую порядку загруженным модам. - От последнего мода к первому. - Тот файл, что был загружен раньше и будет использоваться - */ - std::vector Assets; - /* - У этих ресурсов приоритет выше, если их удастся получить, - то использоваться будут именно они - Domain -> {key + data} - */ - std::unordered_map> Custom[(int) EnumAssets::MAX_ENUM]; - }; - - ResourceChangeObj recheckResources(const AssetsRegister&); - - /* - Применяет расчитанные изменения. - Раздаёт идентификаторы ресурсам и записывает их в таблицу - */ - struct Out_applyResourceChange { - std::vector Lost[(int) EnumAssets::MAX_ENUM]; - std::vector> NewOrChange[(int) EnumAssets::MAX_ENUM]; - }; - - Out_applyResourceChange applyResourceChange(const ResourceChangeObj& orr); - - /* - Выдаёт идентификатор ресурса, даже если он не существует или был удалён. - 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::optional> getResource(const Hash_t& hash) { - return LocalObj.lock()->getResource(hash); - } - - // Выдаёт зависимости к ресурсам профиля ноды - std::tuple, std::vector> - getNodeDependency(const std::string& domain, const std::string& key) - { - if(domain == "core" && key == "none") { - return {0, {}, {}}; - } - - auto lock = LocalObj.lock(); - AssetsNodestate nodestateId = lock->getId(EnumAssets::Nodestate, domain, key+".json"); - - 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); - } - } else { - LOG.debug() << "Для ноды " << domain << ':' << key << " отсутствует описание Nodestate"; - } - - { - 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)}; - } - -private: - TOS::Logger LOG = "Server>AssetsManager"; - -}; - -} diff --git a/Src/Server/GameServer.cpp b/Src/Server/GameServer.cpp index 09330e2..c4298bd 100644 --- a/Src/Server/GameServer.cpp +++ b/Src/Server/GameServer.cpp @@ -1154,150 +1154,6 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string } } -TexturePipeline GameServer::buildTexturePipeline(const std::string& pl) { - /* - ^ объединение текстур, вторая поверх первой. - При наложении текстуры будут автоматически увеличины до размера - самой большой текстуры из участвующих. По умолчанию ближайший соседний - default:dirt.png^our_tech:machine.png - - Текстурные команды описываются в [] <- предоставляет текстуру. - Разделитель пробелом - default:dirt.png^[create 2 2 r ffaabbcc] - - Если перед командой будет использован $, то первым аргументом будет - предыдущая текстура, если это поддерживает команда - default:dirt.png$[resize 16 16] или [resize default:dirt.png 16 16] - - Группировка () - default:empty^(default:dirt.png^our_tech:machine.png) - */ - - std::map stbt; - std::unordered_set btis; - std::string alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - // Парсер группы. Возвращает позицию на которой закончил и скомпилированный код - std::move_only_function(size_t pos)> parse_obj; - std::move_only_function(size_t pos, std::u8string maybe)> parse_cmd; - - parse_cmd = [&](size_t pos, std::u8string maybe) -> std::tuple { - std::string cmd_name; - std::vector args; - size_t startPos = pos; - - for(pos++; pos < pl.size(); pos++) { - if(pl[pos] == ']') { - // Команда завершилась - // return {pos+1, cmd_name}; - } else if(pl[pos] == ' ') { - // Аргументы - // Здесь нужно получить либо кастомные значения, либо объект - auto [next_pos, subcmd] = parse_obj(pos+1); - args.push_back(subcmd); - if(next_pos == pl.size()) - MAKE_ERROR("Ожидался конец команды объявленной на " << startPos << ", наткнулись на конец потока"); - - pos = next_pos-1; - } else if(alpha.find(pl[pos]) == std::string::npos) { - MAKE_ERROR("Ошибка в имени команды"); - } else { - cmd_name += pl[pos]; - } - } - - MAKE_ERROR("Ожидался конец команды объявленной на " << startPos << ", наткнулись на конец потока"); - }; - - parse_obj = [&](size_t pos) -> std::pair { - std::u8string out; - - for(; pos < pl.size(); pos++) { - if(pl[pos] == '[') { - // Начало команды - if(!out.empty()) { - MAKE_ERROR("Отсутствует связь между текстурой и текущей командой " << pos); - } - - // out.push_back(TexturePipelineCMD::Combine); - auto [next_size, subcmd] = parse_cmd(pos+1, {}); - pos = next_size-1; - out = subcmd; - } else if(pl[pos] == '^') { - // Объединение - if(out.empty()) { - MAKE_ERROR("Отсутствует текстура для комбинирования " << pos); - - auto [next_pos, subcmd] = parse_obj(pos+1); - std::u8string cmd; - cmd.push_back(uint8_t(TexturePipelineCMD::Combine)); - cmd.insert(cmd.end(), out.begin(), out.end()); - cmd.insert(cmd.end(), subcmd.begin(), subcmd.end()); - - return {next_pos, cmd}; - } - } else if(pl[pos] == '$') { - // Готовый набор команд будет использован как аргумент - pos++; - if(pos >= pl.size() || pl[pos] != '[') - MAKE_ERROR("Ожидалось объявление команды " << pos); - auto [next_pos, subcmd] = parse_cmd(pos, out); - pos = next_pos-1; - out = subcmd; - } else if(pl[pos] == '(') { - if(!out.empty()) { - MAKE_ERROR("Начато определение группы после текстуры, вероятно пропущен знак объединения ^ " << pos); - } - - // Начало группы - auto [next_pos, subcmd] = parse_obj(pos+1); - pos = next_pos-1; - out = subcmd; - } else if(pl[pos] == ')') { - return {pos+1, out}; - } else { - // Это текстура, нужно её имя - if(!out.empty()) - MAKE_ERROR("Отсутствует связь между текстурой и текущим объявлением текстуры " << pos); - - out.push_back(uint8_t(TexturePipelineCMD::Texture)); - std::string texture_name; - for(; pos < pl.size(); pos++) { - if(pl[pos] == '^' || pl[pos] == ')' || pl[pos] == ']') - break; - else if(pl[pos] != '.' && pl[pos] != ':' && alpha.find_first_of(pl[pos]) != std::string::npos) - MAKE_ERROR("Недействительные символы в объявлении текстуры " << pos); - else - texture_name += pl[pos]; - } - - AssetsTexture id = stbt[texture_name]; - btis.insert(id); - - for(int iter = 0; iter < 4; iter++) - out.push_back((id >> (iter * 8)) & 0xff); - - if(pos < pl.size()) - pos--; - } - } - - return {pos, out}; - }; - - auto [pos, cmd] = parse_obj(0); - - if(pos < pl.size()) { - MAKE_ERROR("Неожиданное продолжение " << pos); - } - - return {std::vector(btis.begin(), btis.end()), cmd}; -} - -std::string GameServer::deBuildTexturePipeline(const TexturePipeline& pipeline) { - return ""; -} - int my_exception_handler(lua_State* lua, sol::optional maybe_exception, sol::string_view description) { std::cout << "An exception occurred in a function, here's what it says "; if (maybe_exception) { diff --git a/Src/Server/GameServer.hpp b/Src/Server/GameServer.hpp index 1a28f15..4d1fa2a 100644 --- a/Src/Server/GameServer.hpp +++ b/Src/Server/GameServer.hpp @@ -294,9 +294,6 @@ public: // Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame()) coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username); - TexturePipeline buildTexturePipeline(const std::string& pipeline); - std::string deBuildTexturePipeline(const TexturePipeline& pipeline); - private: void init(fs::path worldPath); void prerun();