From 8ce820569ac4f56cd44380725001e0c975b6fbca Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Mon, 5 Jan 2026 00:35:52 +0600 Subject: [PATCH] =?UTF-8?q?codex-5.2:=20=D0=BA=D0=BE=D0=B5=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=BA=20=D0=B4=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=B4=D0=BE=20=D0=BF=D0=BE=D1=87=D1=82=D0=B8=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=87=D0=B5=D0=B3=D0=BE=20=D1=81=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=8F=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Client/AssetsManager.cpp | 127 ++++++++++++------ Src/Client/ServerSession.cpp | 149 ++++++++++++---------- Src/Client/ServerSession.hpp | 11 +- Src/Client/Vulkan/VulkanRenderSession.cpp | 48 +++++-- Src/Client/Vulkan/VulkanRenderSession.hpp | 123 ++++++++++++++---- Src/Common/Abstract.cpp | 51 -------- Src/Common/Abstract.hpp | 55 -------- Src/Common/AssetsPreloader.cpp | 130 ++++++++++++++++++- 8 files changed, 438 insertions(+), 256 deletions(-) diff --git a/Src/Client/AssetsManager.cpp b/Src/Client/AssetsManager.cpp index 195f228..f1e2cd3 100644 --- a/Src/Client/AssetsManager.cpp +++ b/Src/Client/AssetsManager.cpp @@ -73,6 +73,68 @@ static std::u8string readOptionalMeta(const fs::path& path) { return readFileBytes(metaPath); } +static std::vector collectTexturePipelineIds(const std::vector& code); + +struct ParsedModelHeader { + std::vector ModelDeps; + std::vector> TexturePipelines; + std::vector TextureDeps; +}; + +std::optional> parseNodestateHeaderBytes(const std::vector& header) { + if(header.empty() || header.size() % sizeof(AssetsManager::AssetId) != 0) + return std::nullopt; + + const size_t count = header.size() / sizeof(AssetsManager::AssetId); + std::vector deps; + deps.resize(count); + for(size_t i = 0; i < count; ++i) { + AssetsManager::AssetId raw = 0; + std::memcpy(&raw, header.data() + i * sizeof(AssetsManager::AssetId), sizeof(AssetsManager::AssetId)); + deps[i] = raw; + } + return deps; +} + +std::optional parseModelHeaderBytes(const std::vector& header) { + if(header.empty()) + return std::nullopt; + + ParsedModelHeader result; + try { + TOS::ByteBuffer buffer(header.size(), header.data()); + auto reader = buffer.reader(); + + uint16_t modelCount = reader.readUInt16(); + result.ModelDeps.reserve(modelCount); + for(uint16_t i = 0; i < modelCount; ++i) + result.ModelDeps.push_back(reader.readUInt32()); + + uint16_t texCount = reader.readUInt16(); + result.TexturePipelines.reserve(texCount); + for(uint16_t i = 0; i < texCount; ++i) { + uint32_t size32 = reader.readUInt32(); + TOS::ByteBuffer pipe; + reader.readBuffer(pipe); + if(pipe.size() != size32) + return std::nullopt; + result.TexturePipelines.emplace_back(pipe.begin(), pipe.end()); + } + + std::unordered_set seen; + for(const auto& pipe : result.TexturePipelines) { + for(uint32_t id : collectTexturePipelineIds(pipe)) { + if(seen.insert(id).second) + result.TextureDeps.push_back(id); + } + } + } catch(const std::exception&) { + return std::nullopt; + } + + return result; +} + struct PipelineRemapResult { bool Ok = true; std::string Error; @@ -508,9 +570,23 @@ AssetsManager::PackReloadResult AssetsManager::reloadPacks(const PackRegister& r auto [mDomain, mKey] = parseDomainKey(model, entry.Domain); return getOrCreateLocalId(AssetType::Model, mDomain, mKey); }; + auto normalizeTexturePipelineSrc = [](std::string_view src) -> std::string { + std::string out(src); + auto isSpace = [](unsigned char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; }; + size_t start = 0; + while(start < out.size() && isSpace(static_cast(out[start]))) + ++start; + if(out.compare(start, 3, "tex") != 0) { + std::string pref = "tex "; + pref += out.substr(start); + return pref; + } + return out; + }; + auto textureResolver = [&](std::string_view textureSrc) -> std::vector { TexturePipelineProgram tpp; - if(!tpp.compile(std::string(textureSrc))) + if(!tpp.compile(normalizeTexturePipelineSrc(textureSrc))) return {}; auto textureIdResolver = [&](std::string_view name) -> std::optional { auto [tDomain, tKey] = parseDomainKey(name, entry.Domain); @@ -759,52 +835,21 @@ std::optional AssetsManager::parseHeader(AssetType result.Type = type; if(type == AssetType::Nodestate) { - if(header.size() % sizeof(AssetId) != 0) + auto deps = parseNodestateHeaderBytes(header); + if(!deps) return std::nullopt; - const size_t count = header.size() / sizeof(AssetId); - result.ModelDeps.resize(count); - for(size_t i = 0; i < count; ++i) { - AssetId raw = 0; - std::memcpy(&raw, header.data() + i * sizeof(AssetId), sizeof(AssetId)); - result.ModelDeps[i] = raw; - } + result.ModelDeps = std::move(*deps); return result; } if(type == AssetType::Model) { - try { - TOS::ByteBuffer buffer(header.size(), header.data()); - auto reader = buffer.reader(); - - uint16_t modelCount = reader.readUInt16(); - result.ModelDeps.reserve(modelCount); - for(uint16_t i = 0; i < modelCount; ++i) - result.ModelDeps.push_back(reader.readUInt32()); - - uint16_t texCount = reader.readUInt16(); - result.TexturePipelines.reserve(texCount); - for(uint16_t i = 0; i < texCount; ++i) { - uint32_t size32 = reader.readUInt32(); - TOS::ByteBuffer pipe; - reader.readBuffer(pipe); - if(pipe.size() != size32) { - return std::nullopt; - } - result.TexturePipelines.emplace_back(pipe.begin(), pipe.end()); - } - - std::unordered_set seen; - for(const auto& pipe : result.TexturePipelines) { - for(uint32_t id : collectTexturePipelineIds(pipe)) { - if(seen.insert(id).second) - result.TextureDeps.push_back(id); - } - } - - return result; - } catch(const std::exception&) { + auto parsed = parseModelHeaderBytes(header); + if(!parsed) return std::nullopt; - } + result.ModelDeps = std::move(parsed->ModelDeps); + result.TexturePipelines = std::move(parsed->TexturePipelines); + result.TextureDeps = std::move(parsed->TextureDeps); + return result; } return std::nullopt; diff --git a/Src/Client/ServerSession.cpp b/Src/Client/ServerSession.cpp index b13e46c..510427b 100644 --- a/Src/Client/ServerSession.cpp +++ b/Src/Client/ServerSession.cpp @@ -1424,68 +1424,73 @@ coro<> ServerSession::rP_AssetsInitSend(Net::AsyncSocket &sock) { Hash_t hash; co_await sock.read((std::byte*) hash.data(), hash.size()); - bool found = false; - EnumAssets type = EnumAssets::Texture; - std::string domain; - std::string key; + std::vector matches; - for(int typeIndex = 0; typeIndex < (int) EnumAssets::MAX_ENUM && !found; ++typeIndex) { + for(int typeIndex = 0; typeIndex < (int) EnumAssets::MAX_ENUM; ++typeIndex) { auto& waitingByDomain = AsyncContext.ResourceWait[typeIndex]; - for(auto iterDomain = waitingByDomain.begin(); iterDomain != waitingByDomain.end() && !found; ) { + for(auto iterDomain = waitingByDomain.begin(); iterDomain != waitingByDomain.end(); ) { auto& entries = iterDomain->second; - for(size_t i = 0; i < entries.size(); ++i) { + for(size_t i = 0; i < entries.size(); ) { if(entries[i].second == hash) { - type = static_cast(typeIndex); - domain = iterDomain->first; - key = entries[i].first; + EnumAssets type = static_cast(typeIndex); + const std::string& domain = iterDomain->first; + const std::string& key = entries[i].first; + ResourceId localId = AM->getOrCreateLocalId(type, domain, key); + matches.push_back(AssetLoadingEntry{ + .Type = type, + .Id = localId, + .Domain = domain, + .Key = key + }); entries.erase(entries.begin() + i); - if(entries.empty()) - iterDomain = waitingByDomain.erase(iterDomain); - else - ++iterDomain; - found = true; - break; + } else { + ++i; } } - if(!found) + if(entries.empty()) + iterDomain = waitingByDomain.erase(iterDomain); + else ++iterDomain; } } - if(!found) { + if(matches.empty()) { LOG.warn() << "AssetsInitSend for unknown hash " << int(hash[0]) << '.' << int(hash[1]) << '.' << int(hash[2]) << '.' << int(hash[3]); AsyncContext.AssetsLoading[hash] = AssetLoading{ - EnumAssets::Texture, 0, {}, {}, - std::u8string(size, '\0'), 0 + .Entries = {}, + .Data = std::u8string(size, '\0'), + .Offset = 0 }; co_return; } - ResourceId localId = AM->getOrCreateLocalId(type, domain, key); + AssetLoadingEntry first = matches.front(); - if(domain == "test" - && (type == EnumAssets::Nodestate - || type == EnumAssets::Model - || type == EnumAssets::Texture)) + if(first.Domain == "test" + && (first.Type == EnumAssets::Nodestate + || first.Type == EnumAssets::Model + || first.Type == EnumAssets::Texture)) { uint32_t idx = debugResourceLogCount.fetch_add(1); if(idx < 128) { - LOG.debug() << "AssetsInitSend type=" << assetTypeName(type) - << " id=" << localId - << " key=" << domain << ':' << key - << " size=" << size; + LOG.debug() << "AssetsInitSend type=" << assetTypeName(first.Type) + << " id=" << first.Id + << " key=" << first.Domain << ':' << first.Key + << " size=" << size + << " matches=" << matches.size(); } } AsyncContext.AssetsLoading[hash] = AssetLoading{ - type, localId, std::move(domain), std::move(key), - std::u8string(size, '\0'), 0 + .Entries = std::move(matches), + .Data = std::u8string(size, '\0'), + .Offset = 0 }; - LOG.debug() << "Server started sending type=" << assetTypeName(type) - << " id=" << localId - << " key=" << AsyncContext.AssetsLoading[hash].Domain << ':' - << AsyncContext.AssetsLoading[hash].Key + LOG.debug() << "Server started sending type=" << assetTypeName(first.Type) + << " id=" << first.Id + << " key=" << first.Domain << ':' + << first.Key << " hash=" << int(hash[0]) << '.' << int(hash[1]) << '.' << int(hash[2]) << '.' @@ -1517,43 +1522,47 @@ coro<> ServerSession::rP_AssetsNextSend(Net::AsyncSocket &sock) { if(al.Offset != al.Data.size()) co_return; - if(!al.Domain.empty() || !al.Key.empty()) { - if(al.Domain == "test" - && (al.Type == EnumAssets::Nodestate - || al.Type == EnumAssets::Model - || al.Type == EnumAssets::Texture)) - { - uint32_t idx = debugResourceLogCount.fetch_add(1); - if(idx < 128) { - LOG.debug() << "Resource loaded type=" << assetTypeName(al.Type) - << " id=" << al.Id - << " key=" << al.Domain << ':' << al.Key - << " size=" << al.Data.size(); - } - } - - const EnumAssets type = al.Type; - const ResourceId id = al.Id; - const std::string domain = al.Domain; - const std::string key = al.Key; + if(!al.Entries.empty()) { const size_t resSize = al.Data.size(); + Resource res(std::move(al.Data)); - AsyncContext.LoadedAssets.lock()->emplace_back(AssetEntry{ - .Type = type, - .Id = id, - .Domain = std::move(al.Domain), - .Key = std::move(al.Key), - .Res = std::move(al.Data), - .Hash = hash - }); - LOG.debug() << "Client received type=" << assetTypeName(type) - << " id=" << id - << " key=" << domain << ':' << key - << " hash=" << int(hash[0]) << '.' - << int(hash[1]) << '.' - << int(hash[2]) << '.' - << int(hash[3]) - << " size=" << resSize; + for(AssetLoadingEntry& entry : al.Entries) { + if(entry.Domain == "test" + && (entry.Type == EnumAssets::Nodestate + || entry.Type == EnumAssets::Model + || entry.Type == EnumAssets::Texture)) + { + uint32_t idx = debugResourceLogCount.fetch_add(1); + if(idx < 128) { + LOG.debug() << "Resource loaded type=" << assetTypeName(entry.Type) + << " id=" << entry.Id + << " key=" << entry.Domain << ':' << entry.Key + << " size=" << resSize; + } + } + + const EnumAssets type = entry.Type; + const ResourceId id = entry.Id; + const std::string domain = entry.Domain; + const std::string key = entry.Key; + + AsyncContext.LoadedAssets.lock()->emplace_back(AssetEntry{ + .Type = type, + .Id = id, + .Domain = std::move(entry.Domain), + .Key = std::move(entry.Key), + .Res = res, + .Hash = hash + }); + LOG.debug() << "Client received type=" << assetTypeName(type) + << " id=" << id + << " key=" << domain << ':' << key + << " hash=" << int(hash[0]) << '.' + << int(hash[1]) << '.' + << int(hash[2]) << '.' + << int(hash[3]) + << " size=" << resSize; + } } AsyncContext.AssetsLoading.erase(AsyncContext.AssetsLoading.find(hash)); diff --git a/Src/Client/ServerSession.hpp b/Src/Client/ServerSession.hpp index a305470..7651fc0 100644 --- a/Src/Client/ServerSession.hpp +++ b/Src/Client/ServerSession.hpp @@ -79,12 +79,17 @@ private: std::unordered_map> NotInUse[(int) EnumAssets::MAX_ENUM]; } MyAssets; - struct AssetLoading { + struct AssetLoadingEntry { EnumAssets Type; ResourceId Id; - std::string Domain, Key; + std::string Domain; + std::string Key; + }; + + struct AssetLoading { + std::vector Entries; std::u8string Data; - size_t Offset; + size_t Offset = 0; }; struct AssetBindEntry { diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index cd37448..9f48d8c 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -140,6 +140,14 @@ void ChunkMeshGenerator::run(uint8_t id) { } }; + std::vector metaStatesInfo; + { + NodeStateInfo info; + info.Name = "meta"; + info.Variations = 256; + metaStatesInfo.push_back(std::move(info)); + } + // Воксели пока не рендерим if(auto iterWorld = SS->Content.Worlds.find(wId); iterWorld != SS->Content.Worlds.end()) { Pos::GlobalRegion rPos = pos >> 2; @@ -188,6 +196,38 @@ void ChunkMeshGenerator::run(uint8_t id) { return (nodeFullCuboidCache[node.Data] = true); } + if(NSP && profile->NodestateId != 0 && NSP->hasNodestate(profile->NodestateId)) { + std::unordered_map states; + int32_t meta = node.Meta; + states.emplace("meta", meta); + const auto routes = NSP->getModelsForNode(profile->NodestateId, metaStatesInfo, states); + bool isFull = !routes.empty(); + if(isFull) { + for(const auto& variants : routes) { + for(const auto& [weight, faces] : variants) { + (void)weight; + auto hasFace = [&](EnumFace face) -> bool { + auto iterFace = faces.find(face); + return iterFace != faces.end() && !iterFace->second.empty(); + }; + if(!hasFace(EnumFace::Up) + || !hasFace(EnumFace::Down) + || !hasFace(EnumFace::East) + || !hasFace(EnumFace::West) + || !hasFace(EnumFace::South) + || !hasFace(EnumFace::North)) + { + isFull = false; + break; + } + } + if(!isFull) + break; + } + } + return (nodeFullCuboidCache[node.Data] = isFull); + } + return (nodeFullCuboidCache[node.Data] = false); } else { return iterCache->second; @@ -325,14 +365,6 @@ void ChunkMeshGenerator::run(uint8_t id) { std::unordered_map modelCache; std::unordered_map baseTextureCache; - std::vector metaStatesInfo; - { - NodeStateInfo info; - info.Name = "meta"; - info.Variations = 256; - metaStatesInfo.push_back(std::move(info)); - } - auto isFaceCovered = [&](EnumFace face, int covered) -> bool { switch(face) { case EnumFace::Up: return covered & (1 << 2); diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index 0578c7a..ae3d688 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -5,6 +5,8 @@ #include "Common/Abstract.hpp" #include #include +#include +#include #include #include #include @@ -134,14 +136,6 @@ public: Models.erase(iterModel); } - std::unordered_map modelKeyToId; - if(modelAssets) { - modelKeyToId.reserve(modelAssets->size()); - for(const auto& [id, entry] : *modelAssets) { - modelKeyToId.emplace(entry.Domain + ':' + entry.Key, id); - } - } - for(const auto& [key, resource, deps] : newOrChanged) { result.push_back(key); @@ -173,19 +167,39 @@ public: return pipe; }; + size_t dataSize = 0; + std::array prefix = {}; try { std::u8string_view data((const char8_t*) resource.data(), resource.size()); + dataSize = data.size(); + if(!data.empty()) { + const size_t prefixLen = std::min(prefix.size(), data.size()); + for(size_t i = 0; i < prefixLen; ++i) + prefix[i] = static_cast(data[i]); + } if(data.starts_with((const char8_t*) "bm")) { type = "InternalBinary"; // Компилированная модель внутреннего формата - LV::PreparedModel pm((std::u8string) data); + HeadlessModel hm; + hm.load(data); model.TextureMap.clear(); - model.TextureMap.reserve(pm.CompiledTextures.size()); - for(auto& [tkey, pipe] : pm.CompiledTextures) - model.TextureMap.emplace(tkey, remapPipeline(std::move(pipe))); + model.TextureMap.reserve(hm.Textures.size()); + for(const auto& [tkey, id] : hm.Textures) { + TexturePipeline pipe; + if(header && id < header->TexturePipelines.size()) { + pipe.Pipeline = header->TexturePipelines[id]; + } else { + LOG.warn() << "Model texture pipeline id out of range: model=" << key + << " local=" << id + << " pipelines=" << (header ? header->TexturePipelines.size() : 0); + pipe.BinTextures.push_back(id); + pipe = remapPipeline(std::move(pipe)); + } + model.TextureMap.emplace(tkey, std::move(pipe)); + } model.TextureKeys = {}; - for(const PreparedModel::Cuboid& cb : pm.Cuboids) { + for(const HeadlessModel::Cuboid& cb : hm.Cuboids) { glm::vec3 min = glm::min(cb.From, cb.To), max = glm::max(cb.From, cb.To); for(const auto& [face, params] : cb.Faces) { @@ -265,13 +279,16 @@ public: } } - if(!pm.SubModels.empty() && modelAssets) { - model.Depends.reserve(pm.SubModels.size()); - for(const auto& sub : pm.SubModels) { - auto iter = modelKeyToId.find(sub.Domain + ':' + sub.Key); - if(iter == modelKeyToId.end()) + if(!hm.SubModels.empty()) { + model.Depends.reserve(hm.SubModels.size()); + for(const auto& sub : hm.SubModels) { + if(!header || sub.Id >= header->ModelDeps.size()) { + LOG.warn() << "Model sub-model id out of range: model=" << key + << " local=" << sub.Id + << " deps=" << (header ? header->ModelDeps.size() : 0); continue; - model.Depends.emplace_back(iter->second, Transformations{}); + } + model.Depends.emplace_back(header->ModelDeps[sub.Id], Transformations{}); } } @@ -297,6 +314,35 @@ public: continue; } + { + static std::atomic debugModelLogCount = 0; + uint32_t idx = debugModelLogCount.fetch_add(1); + if(idx < 128) { + size_t vertexCount = 0; + for(const auto& [_, verts] : model.Vertecies) + vertexCount += verts.size(); + size_t texDepsCount = textureDeps ? textureDeps->size() : 0; + LOG.debug() << "Model loaded id=" << key + << " verts=" << vertexCount + << " texKeys=" << model.TextureKeys.size() + << " texDeps=" << texDepsCount; + } + } + + if(model.Vertecies.empty()) { + static std::atomic debugEmptyModelLogCount = 0; + uint32_t idx = debugEmptyModelLogCount.fetch_add(1); + if(idx < 128) { + LOG.warn() << "Model has empty geometry id=" << key + << " type=" << type + << " size=" << dataSize + << " prefix=" << int(prefix[0]) << '.' + << int(prefix[1]) << '.' + << int(prefix[2]) << '.' + << int(prefix[3]); + } + } + Models.insert_or_assign(key, std::move(model)); } @@ -625,13 +671,24 @@ public: std::move(pixels) )); + bool animated = false; if(auto anim = getDefaultAnimation(update.Key, width, height)) { AnimatedSources[key] = *anim; + animated = true; } else { AnimatedSources.erase(key); } NeedsUpload = true; + + static std::atomic debugTextureLogCount = 0; + uint32_t idx = debugTextureLogCount.fetch_add(1); + if(idx < 128) { + LOG.debug() << "Texture loaded id=" << key + << " key=" << update.Domain << ':' << update.Key + << " size=" << width << 'x' << height + << " animated=" << (animated ? 1 : 0); + } } for(AssetsTexture key : lost) { @@ -897,7 +954,7 @@ public: } if(deps && !deps->empty()) { - auto header = AssetsManager::parseHeader(EnumAssets::Model, *deps); + auto header = AssetsManager::parseHeader(EnumAssets::Nodestate, *deps); if(header && header->Type == EnumAssets::Nodestate) { nodestate.LocalToModel.assign(header->ModelDeps.begin(), header->ModelDeps.end()); } @@ -968,6 +1025,11 @@ public: auto appendModel = [&](AssetsModel modelId, const std::vector& transforms, std::unordered_map>& out) { ModelProvider::Model model = MP.getModel(modelId); + if(model.Vertecies.empty()) { + if(MissingModelGeometryLogged.insert(modelId).second) { + LOG.warn() << "Model has no geometry id=" << modelId; + } + } Transformations trf{transforms}; for(auto& [l, r] : model.Vertecies) { @@ -1015,13 +1077,28 @@ public: if(const PreparedNodeState::Model* ptr = std::get_if(&m)) { AssetsModel modelId; - if(resolveModelId(ptr->Id, modelId)) + if(resolveModelId(ptr->Id, modelId)) { appendModel(modelId, ptr->Transforms, out); + } else { + uint64_t missKey = (uint64_t(id) << 32) | ptr->Id; + if(MissingLocalModelMapLogged.insert(missKey).second) { + LOG.warn() << "Missing model mapping nodestate=" << id + << " local=" << ptr->Id + << " locals=" << nodestate.LocalToModel.size(); + } + } } else if(const PreparedNodeState::VectorModel* ptr = std::get_if(&m)) { for(const auto& sub : ptr->Models) { AssetsModel modelId; - if(!resolveModelId(sub.Id, modelId)) + if(!resolveModelId(sub.Id, modelId)) { + uint64_t missKey = (uint64_t(id) << 32) | sub.Id; + if(MissingLocalModelMapLogged.insert(missKey).second) { + LOG.warn() << "Missing model mapping nodestate=" << id + << " local=" << sub.Id + << " locals=" << nodestate.LocalToModel.size(); + } continue; + } std::vector transforms = sub.Transforms; transforms.insert(transforms.end(), ptr->Transforms.begin(), ptr->Transforms.end()); @@ -1058,6 +1135,8 @@ private: TextureProvider& TP; std::unordered_map Nodestates; std::unordered_set MissingNodestateLogged; + std::unordered_set MissingLocalModelMapLogged; + std::unordered_set MissingModelGeometryLogged; std::unordered_set EmptyRouteLogged; }; diff --git a/Src/Common/Abstract.cpp b/Src/Common/Abstract.cpp index ad748e0..5a555c0 100644 --- a/Src/Common/Abstract.cpp +++ b/Src/Common/Abstract.cpp @@ -1741,26 +1741,6 @@ ResourceHeader HeadlessModel::parse( } } - if(boost::system::result subModels_val = profile.try_at("sub_models")) { - const js::array& subModels = subModels_val->as_array(); - - for(const js::value& sub_val : subModels) { - SubModel result; - - if(auto path = sub_val.try_as_string()) { - result.Id = headerResolverModel(path.value()); - } else { - const js::object& sub = sub_val.as_object(); - result.Id = headerResolverModel(sub.at("path").as_string()); - - if(boost::system::result scene_val = profile.try_at("scene")) - result.Scene = scene_val->to_number(); - } - - SubModels.emplace_back(std::move(result)); - } - } - // Заголовок TOS::ByteBuffer rh; @@ -2004,37 +1984,6 @@ std::u8string HeadlessModel::dump() const { return result.complite(); } -PreparedGLTF::PreparedGLTF(const std::string_view modid, const js::object& gltf) { - // gltf - - // Сцена по умолчанию - // Сцены -> Ноды - // Ноды -> Ноды, меши, матрицы, translation, rotation - // Меши -> Примитивы - // Примитивы -> Материал, вершинные данные - // Материалы -> текстуры - // Текстуры - // Буферы -} - -PreparedGLTF::PreparedGLTF(const std::string_view modid, Resource glb) { - -} - -PreparedGLTF::PreparedGLTF(std::u8string_view data) { - - // lr.checkUnreaded(); -} - - -std::u8string PreparedGLTF::dump() const { - std::unreachable(); -} - -void PreparedGLTF::load(std::u8string_view data) { - -} - struct Resource::InlineMMap { boost::interprocess::file_mapping MMap; boost::interprocess::mapped_region Region; diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index 82493a0..6475c96 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -982,37 +982,6 @@ struct TexturePipeline { } }; -struct PreparedModel { - struct SubModel { - std::string Domain; - std::string Key; - }; - - using Cuboid = HeadlessModel::Cuboid; - - std::unordered_map CompiledTextures; - std::vector Cuboids; - std::vector SubModels; - - PreparedModel() = default; - PreparedModel(const std::u8string& data) { load(data); } - PreparedModel(std::u8string_view data) { load(data); } - - void load(std::u8string_view data) { - HeadlessModel model; - model.load(data); - Cuboids = model.Cuboids; - - CompiledTextures.clear(); - CompiledTextures.reserve(model.Textures.size()); - for(const auto& [key, id] : model.Textures) { - TexturePipeline pipe; - pipe.BinTextures.push_back(id); - CompiledTextures.emplace(key, std::move(pipe)); - } - } -}; - struct PreparedNodeState : public HeadlessNodeState { using HeadlessNodeState::Model; using HeadlessNodeState::VectorModel; @@ -1024,30 +993,6 @@ struct PreparedNodeState : public HeadlessNodeState { PreparedNodeState(const std::u8string& data) { load(data); } }; -struct PreparedGLTF { - std::vector TextureKey; - std::unordered_map Textures; - std::vector Vertices; - - - PreparedGLTF(const std::string_view modid, const js::object& gltf); - PreparedGLTF(const std::string_view modid, Resource glb); - PreparedGLTF(std::u8string_view data); - - PreparedGLTF() = default; - PreparedGLTF(const PreparedGLTF&) = default; - PreparedGLTF(PreparedGLTF&&) = default; - - PreparedGLTF& operator=(const PreparedGLTF&) = default; - PreparedGLTF& operator=(PreparedGLTF&&) = default; - - // Пишет в сжатый двоичный формат - std::u8string dump() const; - -private: - void load(std::u8string_view data); -}; - enum struct TexturePipelineCMD : uint8_t { Texture, // Указание текстуры Combine, // Комбинирование diff --git a/Src/Common/AssetsPreloader.cpp b/Src/Common/AssetsPreloader.cpp index ed056b4..5be489c 100644 --- a/Src/Common/AssetsPreloader.cpp +++ b/Src/Common/AssetsPreloader.cpp @@ -1,10 +1,13 @@ #include "AssetsPreloader.hpp" +#include #include #include #include namespace LV { +static TOS::Logger LOG = "AssetsPreloader"; + static ResourceFile readFileBytes(const fs::path& path) { std::ifstream file(path, std::ios::binary); if(!file) @@ -155,6 +158,92 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass } } + auto resolveModelInfo = [&](std::string_view domain, std::string_view key) -> const ResourceFindInfo* { + auto& table = resourcesFirstStage[static_cast(AssetType::Model)]; + auto iterDomain = table.find(std::string(domain)); + if(iterDomain == table.end()) + return nullptr; + auto iterKey = iterDomain->second.find(std::string(key)); + if(iterKey == iterDomain->second.end()) + return nullptr; + return &iterKey->second; + }; + + std::function(std::string_view, std::string_view, std::unordered_set&)> loadModelProfile; + loadModelProfile = [&](std::string_view domain, std::string_view key, std::unordered_set& visiting) + -> std::optional + { + std::string fullKey = std::string(domain) + ':' + std::string(key); + if(!visiting.insert(fullKey).second) { + LOG.warn() << "Model parent cycle: " << fullKey; + return std::nullopt; + } + + const ResourceFindInfo* info = resolveModelInfo(domain, key); + if(!info) { + LOG.warn() << "Model file not found for parent: " << fullKey; + visiting.erase(fullKey); + return std::nullopt; + } + + ResourceFile file = readFileBytes(info->Path); + std::string_view view(reinterpret_cast(file.Data.data()), file.Data.size()); + js::object obj = js::parse(view).as_object(); + + if(auto parentVal = obj.if_contains("parent")) { + if(parentVal->is_string()) { + std::string parentStr = std::string(parentVal->as_string()); + auto [pDomain, pKeyRaw] = parseDomainKey(parentStr, domain); + fs::path pKeyPath = fs::path(pKeyRaw); + if(pKeyPath.extension().empty()) + pKeyPath += ".json"; + std::string pKey = pKeyPath.string(); + + std::optional parent = loadModelProfile(pDomain, pKey, visiting); + if(parent) { + auto mergeFieldIfMissing = [&](const char* field) { + if(!obj.contains(field) && parent->contains(field)) + obj[field] = parent->at(field); + }; + + mergeFieldIfMissing("cuboids"); + mergeFieldIfMissing("sub_models"); + mergeFieldIfMissing("gui_light"); + mergeFieldIfMissing("ambient_occlusion"); + + if(auto parentTextures = parent->if_contains("textures"); parentTextures && parentTextures->is_object()) { + if(auto childTextures = obj.if_contains("textures"); childTextures && childTextures->is_object()) { + auto& childObj = childTextures->as_object(); + const auto& parentObj = parentTextures->as_object(); + for(const auto& [tkey, tval] : parentObj) { + if(!childObj.contains(tkey)) + childObj.emplace(tkey, tval); + } + } else if(!obj.contains("textures")) { + obj["textures"] = parentTextures->as_object(); + } + } + + if(auto parentDisplay = parent->if_contains("display"); parentDisplay && parentDisplay->is_object()) { + if(auto childDisplay = obj.if_contains("display"); childDisplay && childDisplay->is_object()) { + auto& childObj = childDisplay->as_object(); + const auto& parentObj = parentDisplay->as_object(); + for(const auto& [dkey, dval] : parentObj) { + if(!childObj.contains(dkey)) + childObj.emplace(dkey, dval); + } + } else if(!obj.contains("display")) { + obj["display"] = parentDisplay->as_object(); + } + } + } + } + } + + visiting.erase(fullKey); + return obj; + }; + // Функция парсинга ресурсов auto buildResource = [&](AssetType type, std::string_view domain, std::string_view key, const ResourceFindInfo& info) -> PendingResource { PendingResource out; @@ -175,11 +264,25 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass return getId(AssetType::Texture, mDomain, mKey); }; + auto normalizeTexturePipelineSrc = [](std::string_view src) -> std::string { + std::string out(src); + auto isSpace = [](unsigned char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; }; + size_t start = 0; + while(start < out.size() && isSpace(static_cast(out[start]))) + ++start; + if(out.compare(start, 3, "tex") != 0) { + std::string pref = "tex "; + pref += out.substr(start); + return pref; + } + return out; + }; + std::function(const std::string_view)> textureResolver = [&](const std::string_view texturePipelineSrc) -> std::vector { TexturePipelineProgram tpp; - bool flag = tpp.compile((std::string) texturePipelineSrc); + bool flag = tpp.compile(normalizeTexturePipelineSrc(texturePipelineSrc)); if(!flag) return {}; @@ -200,13 +303,28 @@ AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const Ass } else if (type == AssetType::Model) { const std::string ext = info.Path.extension().string(); if (ext == ".json") { - ResourceFile file = readFileBytes(info.Path); - std::string_view view(reinterpret_cast(file.Data.data()), file.Data.size()); - js::object obj = js::parse(view).as_object(); + std::unordered_set visiting; + std::optional objOpt = loadModelProfile(domain, key, visiting); + if(!objOpt) { + LOG.warn() << "Не удалось загрузить модель: " << info.Path.string(); + throw std::runtime_error("Model profile load failed"); + } + js::object obj = std::move(*objOpt); HeadlessModel hm; out.Header = hm.parse(obj, modelResolver, textureResolver); - out.Resource = std::make_shared(hm.dump()); + std::u8string compiled = hm.dump(); + if(hm.Cuboids.empty()) { + static std::atomic debugEmptyModelLogCount = 0; + uint32_t idx = debugEmptyModelLogCount.fetch_add(1); + if(idx < 128) { + LOG.warn() << "Model compiled with empty cuboids: " + << domain << ':' << key + << " file=" << info.Path.string() + << " size=" << compiled.size(); + } + } + out.Resource = std::make_shared(std::move(compiled)); out.Hash = sha2::sha256((const uint8_t*) out.Resource->data(), out.Resource->size()); // } else if (ext == ".gltf" || ext == ".glb") { // /// TODO: добавить поддержку gltf @@ -361,7 +479,7 @@ AssetsPreloader::Out_applyResourceChange AssetsPreloader::applyResourceChange(co // Не должно быть ресурсов, которые были помечены как потерянные #ifndef NDEBUG std::unordered_set changed; - for(const auto& [id, _, _] : result.NewOrChange[type]) + for(const auto& [id, _, _2] : result.NewOrChange[type]) changed.insert(id); auto& lost = result.Lost[type];