From e74e623c0ba45c91c3c353e9876fd4490faeb76e Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Wed, 10 Sep 2025 09:51:17 +0600 Subject: [PATCH] * --- CMakeLists.txt | 6 +- Src/Client/Vulkan/VulkanRenderSession.cpp | 150 +++++++ Src/Client/Vulkan/VulkanRenderSession.hpp | 452 +++++++++++++++------- Src/Common/Abstract.cpp | 105 +++-- Src/Common/Abstract.hpp | 141 ++++--- Src/Server/AssetsManager.cpp | 194 ++++++---- Src/Server/AssetsManager.hpp | 10 +- 7 files changed, 737 insertions(+), 321 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b1d21d..33fce95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,9 +32,9 @@ add_library(luavox_common INTERFACE) target_compile_features(luavox_common INTERFACE cxx_std_23) if(CMAKE_BUILD_TYPE STREQUAL "Debug") - target_compile_options(luavox_common INTERFACE -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all) - target_link_options(luavox_common INTERFACE -fsanitize=address,undefined) - set(ENV{ASAN_OPTIONS} detect_leaks=0) + # target_compile_options(luavox_common INTERFACE -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all) + # target_link_options(luavox_common INTERFACE -fsanitize=address,undefined) + # set(ENV{ASAN_OPTIONS} detect_leaks=0) endif() if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index eb193f5..ed9d31f 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -36,6 +36,26 @@ namespace std { namespace LV::Client::VK { +void ChunkMeshGenerator::changeThreadsCount(uint8_t threads) { + Sync.NeedShutdown = true; + std::unique_lock lock(Sync.Mutex); + Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == 0; }); + + for(std::thread& thr : Threads) + thr.join(); + + Sync.NeedShutdown = false; + + Threads.resize(threads); + for(int iter = 0; iter < threads; iter++) + Threads[iter] = std::thread(&ChunkMeshGenerator::run, this, iter); + + Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == Threads.size() || Sync.NeedShutdown; }); + + if(Sync.NeedShutdown) + MAKE_ERROR("Ошибка обработчика вершин чанков"); +} + void ChunkMeshGenerator::run(uint8_t id) { Logger LOG = "ChunkMeshGenerator<"+std::to_string(id)+'>'; @@ -562,6 +582,136 @@ void ChunkMeshGenerator::run(uint8_t id) { LOG.debug() << "Завершение потока верширования чанков"; } + +void ChunkPreparator::tickSync(const TickSyncData& data) { + // Обработать изменения в чанках + // Пересчёт соседних чанков + // Проверить необходимость пересчёта чанков при изменении профилей + + // Добавляем к изменёным чанкам пересчёт соседей + { + std::vector> toBuild; + for(auto& [wId, chunks] : data.ChangedChunks) { + std::vector list; + for(const Pos::GlobalChunk& pos : chunks) { + list.push_back(pos); + list.push_back(pos+Pos::GlobalChunk(1, 0, 0)); + list.push_back(pos+Pos::GlobalChunk(-1, 0, 0)); + list.push_back(pos+Pos::GlobalChunk(0, 1, 0)); + list.push_back(pos+Pos::GlobalChunk(0, -1, 0)); + list.push_back(pos+Pos::GlobalChunk(0, 0, 1)); + list.push_back(pos+Pos::GlobalChunk(0, 0, -1)); + } + + std::sort(list.begin(), list.end()); + auto eraseIter = std::unique(list.begin(), list.end()); + list.erase(eraseIter, list.end()); + + + for(Pos::GlobalChunk& pos : list) { + Pos::GlobalRegion rPos = pos >> 2; + auto iterRegion = Requests[wId].find(rPos); + if(iterRegion != Requests[wId].end()) + toBuild.emplace_back(wId, pos, iterRegion->second); + else + toBuild.emplace_back(wId, pos, Requests[wId][rPos] = NextRequest++); + } + } + + CMG.Input.lock()->push_range(toBuild); + } + + // Чистим запросы и чанки + { + uint8_t frameRetirement = (FrameRoulette+FRAME_COUNT_RESOURCE_LATENCY) % FRAME_COUNT_RESOURCE_LATENCY; + for(auto& [wId, regions] : data.LostRegions) { + if(auto iterWorld = Requests.find(wId); iterWorld != Requests.end()) { + for(const Pos::GlobalRegion& rPos : regions) + if(auto iterRegion = iterWorld->second.find(rPos); iterRegion != iterWorld->second.end()) + iterWorld->second.erase(iterRegion); + } + + if(auto iterWorld = ChunksMesh.find(wId); iterWorld != ChunksMesh.end()) { + for(const Pos::GlobalRegion& rPos : regions) + if(auto iterRegion = iterWorld->second.find(rPos); iterRegion != iterWorld->second.end()) { + for(int iter = 0; iter < 4*4*4; iter++) { + auto& chunk = iterRegion->second[iter]; + if(chunk.VoxelPointer) + VPV_ToFree[frameRetirement].emplace_back(std::move(chunk.VoxelPointer)); + if(chunk.NodePointer) { + VPN_ToFree[frameRetirement].emplace_back(std::move(chunk.NodePointer), std::move(chunk.NodeIndexes)); + } + } + + iterWorld->second.erase(iterRegion); + } + } + } + } + + // Получаем готовые чанки + { + std::vector chunks = std::move(*CMG.Output.lock()); + for(auto& chunk : chunks) { + auto iterWorld = Requests.find(chunk.WId); + if(iterWorld == Requests.end()) + continue; + + auto iterRegion = iterWorld->second.find(chunk.Pos >> 2); + if(iterRegion == iterWorld->second.end()) + continue; + + if(iterRegion->second != chunk.RequestId) + continue; + + // Чанк ожидаем + auto& rChunk = ChunksMesh[chunk.WId][chunk.Pos >> 2][Pos::bvec4u(chunk.Pos & 0x3).pack()]; + rChunk.Voxels = std::move(chunk.VoxelDefines); + if(!chunk.VoxelVertexs.empty()) + rChunk.VoxelPointer = VertexPool_Voxels.pushVertexs(std::move(chunk.VoxelVertexs)); + rChunk.Nodes = std::move(chunk.NodeDefines); + if(!chunk.NodeVertexs.empty()) + rChunk.NodePointer = VertexPool_Nodes.pushVertexs(std::move(chunk.NodeVertexs)); + + if(std::vector* ptr = std::get_if>(&chunk.NodeIndexes)) { + if(!ptr->empty()) + rChunk.NodeIndexes = IndexPool_Nodes_16.pushVertexs(std::move(*ptr)); + } else if(std::vector* ptr = std::get_if>(&chunk.NodeIndexes)) { + if(!ptr->empty()) + rChunk.NodeIndexes = IndexPool_Nodes_32.pushVertexs(std::move(*ptr)); + } + } + } + + VertexPool_Voxels.update(CMDPool); + VertexPool_Nodes.update(CMDPool); + IndexPool_Nodes_16.update(CMDPool); + IndexPool_Nodes_32.update(CMDPool); + + CMG.endTickSync(); +} + +void ChunkPreparator::pushFrame() { + FrameRoulette = (FrameRoulette+1) % FRAME_COUNT_RESOURCE_LATENCY; + + for(auto pointer : VPV_ToFree[FrameRoulette]) { + VertexPool_Voxels.dropVertexs(pointer); + } + + VPV_ToFree[FrameRoulette].clear(); + + for(auto& pointer : VPN_ToFree[FrameRoulette]) { + VertexPool_Nodes.dropVertexs(std::get<0>(pointer)); + if(IndexPool::Pointer* ind = std::get_if::Pointer>(&std::get<1>(pointer))) { + IndexPool_Nodes_16.dropVertexs(*ind); + } else if(IndexPool::Pointer* ind = std::get_if::Pointer>(&std::get<1>(pointer))) { + IndexPool_Nodes_32.dropVertexs(*ind); + } + } + + VPN_ToFree[FrameRoulette].clear(); +} + std::pair< std::vector, uint32_t>>, std::vector, std::pair, bool, uint32_t>> diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index 1d942c0..a917bd1 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -5,10 +5,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -20,6 +22,7 @@ #include "glm/fwd.hpp" #include "../FrustumCull.h" #include "glm/geometric.hpp" +#include /* У движка есть один текстурный атлас VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGBA_UINT) и к нему Storage с инфой о положении текстур @@ -49,6 +52,302 @@ struct WorldPCO { static_assert(sizeof(WorldPCO) == 128); +class ModelProvider { + struct Transformations { + std::vector OPs; + + void apply(std::vector& vertices) const { + if (vertices.empty() || OPs.empty()) + return; + + glm::mat4 transform(1.0f); + + for (const auto& op : OPs) { + switch (op.Op) { + case Transformation::MoveX: transform = glm::translate(transform, glm::vec3(op.Value, 0.0f, 0.0f)); break; + case Transformation::MoveY: transform = glm::translate(transform, glm::vec3(0.0f, op.Value, 0.0f)); break; + case Transformation::MoveZ: transform = glm::translate(transform, glm::vec3(0.0f, 0.0f, op.Value)); break; + case Transformation::ScaleX: transform = glm::scale(transform, glm::vec3(op.Value, 1.0f, 1.0f)); break; + case Transformation::ScaleY: transform = glm::scale(transform, glm::vec3(1.0f, op.Value, 1.0f)); break; + case Transformation::ScaleZ: transform = glm::scale(transform, glm::vec3(1.0f, 1.0f, op.Value)); break; + case Transformation::RotateX: transform = glm::rotate(transform, op.Value, glm::vec3(1.0f, 0.0f, 0.0f)); break; + case Transformation::RotateY: transform = glm::rotate(transform, op.Value, glm::vec3(0.0f, 1.0f, 0.0f)); break; + case Transformation::RotateZ: transform = glm::rotate(transform, op.Value, glm::vec3(0.0f, 0.0f, 1.0f)); break; + default: break; + } + } + + std::transform( + std::execution::unseq, + vertices.begin(), + vertices.end(), + vertices.begin(), + [transform](Vertex v) -> Vertex { + glm::vec4 pos_h(v.Pos, 1.0f); + pos_h = transform * pos_h; + v.Pos = glm::vec3(pos_h) / pos_h.w; + return v; + } + ); + } + + std::vector apply(const std::vector& vertices) const { + std::vector result = vertices; + apply(result); + return result; + } + }; + + struct Model { + // В вершинах текущей модели TexId ссылается на локальный текстурный ключ + // 0 -> default_texture -> luavox:grass.png + std::vector TextureKeys; + // Привязка локальных ключей к глобальным + std::unordered_map TextureMap; + // Вершины со всеми применёнными трансформациями, с CullFace + std::unordered_map> Vertecies; + // Текстуры этой модели не будут переписаны вышестоящими + bool UniqueTextures = false; + }; + + struct ModelObject : public Model { + // Зависимости, их трансформации (здесь может повторятся одна и таже модель) + // и перезаписи идентификаторов текстур + std::vector> Depends; + + // Те кто использовали модель как зависимость в ней отметятся + std::vector UpUse; + // При изменении/удалении модели убрать метки с зависимостей + std::vector DownUse; + // Для постройки зависимостей + bool Ready = false; + + // Если модель использовалась для рендера нод, то для неё надо переформировать вершины + // std::optional> NodeVertecies; + }; + +public: + // Предкомпилирует модель + Model getModel(ResourceId id) { + auto lock = Models.lock(); + std::vector used; + return getModelSynced(*lock, id, used); + } + + // Применяет изменения, возвращая все затронутые модели + std::vector onModelChanges(std::vector> newOrChanged, std::vector lost) { + auto lock = Models.lock(); + std::vector result; + + std::move_only_function makeUnready; + makeUnready = [&](ResourceId id) { + auto iterModel = lock->find(id); + if(iterModel == lock->end()) + return; + + if(!iterModel->second.Ready) + return; + + result.push_back(id); + + for(ResourceId downId : iterModel->second.DownUse) { + auto iterModel = lock->find(downId); + if(iterModel == lock->end()) + return; + + auto iter = std::find(iterModel->second.UpUse.begin(), iterModel->second.UpUse.end(), id); + assert(iter != iterModel->second.UpUse.end()); + iterModel->second.UpUse.erase(iter); + } + + for(ResourceId upId : iterModel->second.UpUse) { + makeUnready(upId); + } + + assert(iterModel->second.UpUse.empty()); + + iterModel->second.Ready = false; + }; + + for(ResourceId lostId : lost) { + makeUnready(lostId); + } + + for(ResourceId lostId : lost) { + auto iterModel = lock->find(lostId); + if(iterModel == lock->end()) + continue; + + lock->erase(iterModel); + } + + for(const auto& [key, resource] : newOrChanged) { + makeUnready(key); + ModelObject model; + std::string type = "unknown"; + + try { + std::u8string_view data((const char8_t*) resource.data(), resource.size()); + if(data.starts_with((const char8_t*) "bm")) { + type = "InternalBinary"; + // Компилированная модель внутреннего формата + LV::PreparedModel pm((std::u8string) data.substr(2)); + model.TextureKeys = {}; + + for(const PreparedModel::Cuboid& cb : pm.Cuboids) { + for(const auto& [face, params] : cb.Faces) { + // params. + } + } + + // glm::vec3 From, To; + + // struct Face { + // glm::vec4 UV; + // std::string Texture; + // std::optional Cullface; + // int TintIndex = -1; + // int16_t Rotation = 0; + // }; + + // std::unordered_map Faces; + + // std::vector Transformations; + + } else if(data.starts_with((const char8_t*) "glTF")) { + type = "glb"; + + } else if(data.starts_with((const char8_t*) "bgl")) { + type = "InternalGLTF"; + + } else if(data.starts_with((const char8_t*) "{")) { + type = "InternalJson или glTF"; + // Модель внутреннего формата или glTF + } + } catch(const std::exception& exc) { + LOG.warn() << "Не удалось распарсить модель " << type << ":\n\t" << exc.what(); + } + + lock->insert({key, std::move(model)}); + } + + + std::sort(result.begin(), result.end()); + auto eraseIter = std::unique(result.begin(), result.end()); + result.erase(eraseIter, result.end()); + return result; + } + +private: + Logger LOG = "Client>ModelProvider"; + // Таблица моделей + TOS::SpinlockObject> Models; + uint64_t UniqId = 0; + + Model getModelSynced(std::unordered_map& models, ResourceId id, std::vector& used) { + auto iterModel = models.find(id); + if(iterModel == models.end()) { + // Нет такой модели, ну и хрен с ним + return {}; + } + + ModelObject& model = iterModel->second; + if(!model.Ready) { + std::vector deps; + for(const auto&[id, _] : model.Depends) { + deps.push_back(id); + } + + std::sort(deps.begin(), deps.end()); + auto eraseIter = std::unique(deps.begin(), deps.end()); + deps.erase(eraseIter, deps.end()); + + // Отмечаемся в зависимостях + for(ResourceId subId : deps) { + auto iterModel = models.find(subId); + if(iterModel == models.end()) + continue; + + iterModel->second.UpUse.push_back(id); + } + + model.Ready = true; + } + + // Собрать зависимости + std::vector subModels; + used.push_back(id); + + for(const auto&[id, trans] : model.Depends) { + if(std::find(used.begin(), used.end(), id) != used.end()) { + // Цикл зависимостей + continue; + } + + Model model = getModelSynced(models, id, used); + + for(auto& [face, vertecies] : model.Vertecies) + trans.apply(vertecies); + + subModels.emplace_back(std::move(model)); + } + + subModels.push_back(model); + used.pop_back(); + + // Собрать всё воедино + Model result; + + for(Model& subModel : subModels) { + std::vector localRelocate; + + if(subModel.UniqueTextures) { + std::string extraKey = "#" + std::to_string(UniqId++); + for(std::string& key : subModel.TextureKeys) { + key += extraKey; + } + + std::unordered_map newTable; + for(auto& [key, _] : subModel.TextureMap) { + newTable[key + extraKey] = _; + } + + subModel.TextureMap = std::move(newTable); + } + + for(const std::string& key : subModel.TextureKeys) { + auto iterKey = std::find(result.TextureKeys.begin(), result.TextureKeys.end(), key); + if(iterKey == result.TextureKeys.end()) { + localRelocate.push_back(result.TextureKeys.size()); + result.TextureKeys.push_back(key); + } else { + localRelocate.push_back(iterKey-result.TextureKeys.begin()); + } + } + + for(const auto& [face, vertecies] : subModel.Vertecies) { + auto& resVerts = result.Vertecies[face]; + + for(Vertex v : vertecies) { + v.TexId = localRelocate[v.TexId]; + resVerts.push_back(v); + } + } + + for(auto& [key, dk] : subModel.TextureMap) { + result.TextureMap[key] = dk; + } + } + + return result; + } +}; + +class ModelProviderForChunkMeshGeneretaor { +public: + +}; + /* Объект, занимающийся генерацией меша на основе нод и вокселей Требует доступ к профилям в ServerSession (ServerSession должен быть заблокирован только на чтение) @@ -81,7 +380,6 @@ struct ChunkMeshGenerator { // Выход TOS::SpinlockObject> Output; - public: ChunkMeshGenerator(IServerSession* serverSession) : SS(serverSession) @@ -94,25 +392,7 @@ public: } // Меняет количество обрабатывающих потоков - void changeThreadsCount(uint8_t threads) { - Sync.NeedShutdown = true; - std::unique_lock lock(Sync.Mutex); - Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == 0; }); - - for(std::thread& thr : Threads) - thr.join(); - - Sync.NeedShutdown = false; - - Threads.resize(threads); - for(int iter = 0; iter < threads; iter++) - Threads[iter] = std::thread(&ChunkMeshGenerator::run, this, iter); - - Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == Threads.size() || Sync.NeedShutdown; }); - - if(Sync.NeedShutdown) - MAKE_ERROR("Ошибка обработчика вершин чанков"); - } + void changeThreadsCount(uint8_t threads); void prepareTickSync() { Sync.Stop = true; @@ -128,7 +408,6 @@ public: Sync.CV_CountInRun.notify_all(); } - private: struct { std::mutex Mutex; @@ -139,8 +418,6 @@ private: } Sync; IServerSession *SS; - - // Потоки std::vector Threads; void run(uint8_t id); @@ -201,135 +478,10 @@ public: CMG.pushStageTickSync(); } - void tickSync(const TickSyncData& data) { - // Обработать изменения в чанках - // Пересчёт соседних чанков - // Проверить необходимость пересчёта чанков при изменении профилей - - // Добавляем к изменёным чанкам пересчёт соседей - { - std::vector> toBuild; - for(auto& [wId, chunks] : data.ChangedChunks) { - std::vector list; - for(const Pos::GlobalChunk& pos : chunks) { - list.push_back(pos); - list.push_back(pos+Pos::GlobalChunk(1, 0, 0)); - list.push_back(pos+Pos::GlobalChunk(-1, 0, 0)); - list.push_back(pos+Pos::GlobalChunk(0, 1, 0)); - list.push_back(pos+Pos::GlobalChunk(0, -1, 0)); - list.push_back(pos+Pos::GlobalChunk(0, 0, 1)); - list.push_back(pos+Pos::GlobalChunk(0, 0, -1)); - } - - std::sort(list.begin(), list.end()); - auto eraseIter = std::unique(list.begin(), list.end()); - list.erase(eraseIter, list.end()); - - - for(Pos::GlobalChunk& pos : list) { - Pos::GlobalRegion rPos = pos >> 2; - auto iterRegion = Requests[wId].find(rPos); - if(iterRegion != Requests[wId].end()) - toBuild.emplace_back(wId, pos, iterRegion->second); - else - toBuild.emplace_back(wId, pos, Requests[wId][rPos] = NextRequest++); - } - } - - CMG.Input.lock()->push_range(toBuild); - } - - // Чистим запросы и чанки - { - uint8_t frameRetirement = (FrameRoulette+FRAME_COUNT_RESOURCE_LATENCY) % FRAME_COUNT_RESOURCE_LATENCY; - for(auto& [wId, regions] : data.LostRegions) { - if(auto iterWorld = Requests.find(wId); iterWorld != Requests.end()) { - for(const Pos::GlobalRegion& rPos : regions) - if(auto iterRegion = iterWorld->second.find(rPos); iterRegion != iterWorld->second.end()) - iterWorld->second.erase(iterRegion); - } - - if(auto iterWorld = ChunksMesh.find(wId); iterWorld != ChunksMesh.end()) { - for(const Pos::GlobalRegion& rPos : regions) - if(auto iterRegion = iterWorld->second.find(rPos); iterRegion != iterWorld->second.end()) { - for(int iter = 0; iter < 4*4*4; iter++) { - auto& chunk = iterRegion->second[iter]; - if(chunk.VoxelPointer) - VPV_ToFree[frameRetirement].emplace_back(std::move(chunk.VoxelPointer)); - if(chunk.NodePointer) { - VPN_ToFree[frameRetirement].emplace_back(std::move(chunk.NodePointer), std::move(chunk.NodeIndexes)); - } - } - - iterWorld->second.erase(iterRegion); - } - } - } - } - - // Получаем готовые чанки - { - std::vector chunks = std::move(*CMG.Output.lock()); - for(auto& chunk : chunks) { - auto iterWorld = Requests.find(chunk.WId); - if(iterWorld == Requests.end()) - continue; - - auto iterRegion = iterWorld->second.find(chunk.Pos >> 2); - if(iterRegion == iterWorld->second.end()) - continue; - - if(iterRegion->second != chunk.RequestId) - continue; - - // Чанк ожидаем - auto& rChunk = ChunksMesh[chunk.WId][chunk.Pos >> 2][Pos::bvec4u(chunk.Pos & 0x3).pack()]; - rChunk.Voxels = std::move(chunk.VoxelDefines); - if(!chunk.VoxelVertexs.empty()) - rChunk.VoxelPointer = VertexPool_Voxels.pushVertexs(std::move(chunk.VoxelVertexs)); - rChunk.Nodes = std::move(chunk.NodeDefines); - if(!chunk.NodeVertexs.empty()) - rChunk.NodePointer = VertexPool_Nodes.pushVertexs(std::move(chunk.NodeVertexs)); - - if(std::vector* ptr = std::get_if>(&chunk.NodeIndexes)) { - if(!ptr->empty()) - rChunk.NodeIndexes = IndexPool_Nodes_16.pushVertexs(std::move(*ptr)); - } else if(std::vector* ptr = std::get_if>(&chunk.NodeIndexes)) { - if(!ptr->empty()) - rChunk.NodeIndexes = IndexPool_Nodes_32.pushVertexs(std::move(*ptr)); - } - } - } - - VertexPool_Voxels.update(CMDPool); - VertexPool_Nodes.update(CMDPool); - IndexPool_Nodes_16.update(CMDPool); - IndexPool_Nodes_32.update(CMDPool); - - CMG.endTickSync(); - } + void tickSync(const TickSyncData& data); // Готовность кадров определяет когда можно удалять ненужные ресурсы, которые ещё используются в рендере - void pushFrame() { - FrameRoulette = (FrameRoulette+1) % FRAME_COUNT_RESOURCE_LATENCY; - - for(auto pointer : VPV_ToFree[FrameRoulette]) { - VertexPool_Voxels.dropVertexs(pointer); - } - - VPV_ToFree[FrameRoulette].clear(); - - for(auto& pointer : VPN_ToFree[FrameRoulette]) { - VertexPool_Nodes.dropVertexs(std::get<0>(pointer)); - if(IndexPool::Pointer* ind = std::get_if::Pointer>(&std::get<1>(pointer))) { - IndexPool_Nodes_16.dropVertexs(*ind); - } else if(IndexPool::Pointer* ind = std::get_if::Pointer>(&std::get<1>(pointer))) { - IndexPool_Nodes_32.dropVertexs(*ind); - } - } - - VPN_ToFree[FrameRoulette].clear(); - } + void pushFrame(); // Выдаёт буферы для рендера в порядке от ближнего к дальнему. distance - радиус в регионах std::pair< diff --git a/Src/Common/Abstract.cpp b/Src/Common/Abstract.cpp index bcde7bb..db9c139 100644 --- a/Src/Common/Abstract.cpp +++ b/Src/Common/Abstract.cpp @@ -1594,7 +1594,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] = value.as_string(); + Textures[key] = compileTexturePipeline((std::string) value.as_string(), modid); } } @@ -1634,20 +1634,20 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro const js::object& faces = cuboid.at("faces").as_object(); for(const auto& [key, value] : faces) { - Cuboid::EnumFace type; + EnumFace type; if(key == "down") - type = Cuboid::EnumFace::Down; + type = EnumFace::Down; else if(key == "up") - type = Cuboid::EnumFace::Up; + type = EnumFace::Up; else if(key == "north") - type = Cuboid::EnumFace::North; + type = EnumFace::North; else if(key == "south") - type = Cuboid::EnumFace::South; + type = EnumFace::South; else if(key == "west") - type = Cuboid::EnumFace::West; + type = EnumFace::West; else if(key == "east") - type = Cuboid::EnumFace::East; + type = EnumFace::East; else MAKE_ERROR("Unknown face"); @@ -1671,17 +1671,17 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro const std::string_view cullface = cullface_val->as_string(); if(cullface == "down") - face.Cullface = Cuboid::EnumFace::Down; + face.Cullface = EnumFace::Down; else if(cullface == "up") - face.Cullface = Cuboid::EnumFace::Up; + face.Cullface = EnumFace::Up; else if(cullface == "north") - face.Cullface = Cuboid::EnumFace::North; + face.Cullface = EnumFace::North; else if(cullface == "south") - face.Cullface = Cuboid::EnumFace::South; + face.Cullface = EnumFace::South; else if(cullface == "west") - face.Cullface = Cuboid::EnumFace::West; + face.Cullface = EnumFace::West; else if(cullface == "east") - face.Cullface = Cuboid::EnumFace::East; + face.Cullface = EnumFace::East; else MAKE_ERROR("Unknown face"); } @@ -1721,17 +1721,17 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro } if(key == "x") - result.Transformations.emplace_back(Cuboid::Transformation::MoveX, f_value); + result.Transformations.emplace_back(Transformation::MoveX, f_value); else if(key == "y") - result.Transformations.emplace_back(Cuboid::Transformation::MoveY, f_value); + result.Transformations.emplace_back(Transformation::MoveY, f_value); else if(key == "z") - result.Transformations.emplace_back(Cuboid::Transformation::MoveZ, f_value); + result.Transformations.emplace_back(Transformation::MoveZ, f_value); else if(key == "rx") - result.Transformations.emplace_back(Cuboid::Transformation::RotateX, f_value); + result.Transformations.emplace_back(Transformation::RotateX, f_value); else if(key == "ry") - result.Transformations.emplace_back(Cuboid::Transformation::RotateY, f_value); + result.Transformations.emplace_back(Transformation::RotateY, f_value); else if(key == "rz") - result.Transformations.emplace_back(Cuboid::Transformation::RotateZ, f_value); + result.Transformations.emplace_back(Transformation::RotateZ, f_value); else MAKE_ERROR("Неизвестный ключ трансформации"); } @@ -1813,7 +1813,17 @@ PreparedModel::PreparedModel(const std::u8string& data) { for(int counter = 0; counter < size; counter++) { std::string tkey, pipeline; lr >> tkey >> pipeline; - Textures.insert({tkey, pipeline}); + TexturePipeline pipe; + + 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)}); } lr >> size; @@ -1843,12 +1853,12 @@ PreparedModel::PreparedModel(const std::u8string& data) { lr >> face.Texture; uint8_t val = lr.read(); if(val != uint8_t(-1)) { - face.Cullface = Cuboid::EnumFace(val); + face.Cullface = EnumFace(val); } lr >> face.TintIndex >> face.Rotation; - cuboid.Faces.insert({(Cuboid::EnumFace) type, face}); + cuboid.Faces.insert({(EnumFace) type, face}); } uint16_t transformationsSize; @@ -1856,8 +1866,8 @@ PreparedModel::PreparedModel(const std::u8string& data) { cuboid.Transformations.reserve(transformationsSize); for(int counter2 = 0; counter2 < transformationsSize; counter2++) { - Cuboid::Transformation tsf; - tsf.Op = (Cuboid::Transformation::EnumTransform) lr.read(); + Transformation tsf; + tsf.Op = (Transformation::EnumTransform) lr.read(); lr >> tsf.Value; cuboid.Transformations.emplace_back(tsf); } @@ -1882,6 +1892,8 @@ PreparedModel::PreparedModel(const std::u8string& data) { std::u8string PreparedModel::dump() const { Net::Packet result; + result << 'b' << 'm'; + if(GuiLight.has_value()) { result << uint8_t(1); result << uint8_t(GuiLight.value()); @@ -1916,12 +1928,19 @@ std::u8string PreparedModel::dump() const { assert(Textures.size() < (1 << 16)); result << uint16_t(Textures.size()); - for(const auto& [tkey, dk] : Textures) { + assert(CompiledTextures.size() == Textures.size()); + + for(const auto& [tkey, dk] : CompiledTextures) { assert(tkey.size() < 32); result << tkey; - assert(dk.size() < 512); - result << dk; + 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; } assert(Cuboids.size() < (1 << 16)); @@ -1976,6 +1995,36 @@ std::u8string PreparedModel::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) { + +} + + +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 05a0f67..f9aad47 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -391,6 +391,7 @@ struct Object_t { using ResourceId = uint32_t; +struct Resource; /* Объекты, собранные из папки assets или зарегистрированные модами. @@ -495,6 +496,43 @@ void unCompressNodes(const std::u8string& compressed, Node* ptr); std::u8string compressLinear(const std::u8string& data); std::u8string unCompressLinear(const std::u8string& data); +inline std::pair parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") { + auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d/_.]+)"); + if(!regResult) + MAKE_ERROR("Недействительный домен:ключ"); + + if(regResult->at(1)) { + return std::pair{*regResult->at(1), *regResult->at(2)}; + } else { + return std::pair{defaultDomain, *regResult->at(2)}; + } +} + +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 NodestateEntry { std::string Name; int Variability = 0; // Количество возможный значений состояния @@ -585,6 +623,22 @@ private: std::vector parseTransormations(const js::array& arr); }; + +enum class EnumFace { + Down, Up, North, South, West, East, None +}; + +struct Transformation { + enum EnumTransform { + MoveX, MoveY, MoveZ, + RotateX, RotateY, RotateZ, + ScaleX, ScaleY, ScaleZ, + MAX_ENUM + } Op; + + float Value; +}; + /* Парсит json модель */ @@ -604,16 +658,13 @@ struct PreparedModel { }; std::unordered_map Display; - std::unordered_map Textures; + std::unordered_map Textures; + std::unordered_map CompiledTextures; struct Cuboid { bool Shade; glm::vec3 From, To; - enum class EnumFace { - Down, Up, North, South, West, East - }; - struct Face { glm::vec4 UV; std::string Texture; @@ -623,16 +674,6 @@ struct PreparedModel { }; std::unordered_map Faces; - - struct Transformation { - enum EnumTransform { - MoveX, MoveY, MoveZ, - RotateX, RotateY, RotateZ, - MAX_ENUM - } Op; - - float Value; - }; std::vector Transformations; }; @@ -662,7 +703,38 @@ struct PreparedModel { std::u8string dump() const; private: - bool load(const std::u8string& data) noexcept; + void load(std::u8string_view data); +}; + +struct Vertex { + glm::vec3 Pos; + glm::vec2 UV; + uint32_t TexId; +}; + +struct PreparedGLTF { + std::vector TextureKey; + std::unordered_map Textures; + std::unordered_map CompiledTextures; + 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 { @@ -672,43 +744,6 @@ enum struct TexturePipelineCMD : uint8_t { using Hash_t = std::array; -inline std::pair parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") { - auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d/_.]+)"); - if(!regResult) - MAKE_ERROR("Недействительный домен:ключ"); - - if(regResult->at(1)) { - return std::pair{*regResult->at(1), *regResult->at(2)}; - } else { - return std::pair{defaultDomain, *regResult->at(2)}; - } -} - -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: struct InlineMMap; diff --git a/Src/Server/AssetsManager.cpp b/Src/Server/AssetsManager.cpp index b0c98f5..25e2617 100644 --- a/Src/Server/AssetsManager.cpp +++ b/Src/Server/AssetsManager.cpp @@ -19,8 +19,7 @@ PreparedModel::PreparedModel(const std::string& domain, const LV::PreparedModel& Cuboids.reserve(model.Cuboids.size()); for(auto& [key, cmd] : model.Textures) { - PrecompiledTexturePipeline ptp = compileTexturePipeline(cmd, domain); - for(auto& [domain, key] : ptp.Assets) { + for(auto& [domain, key] : cmd.Assets) { TextureDependencies[domain].push_back(key); } } @@ -42,20 +41,7 @@ PreparedModel::PreparedModel(const std::string& domain, const LV::PreparedModel& // } } -PreparedModel::PreparedModel(const std::string& domain, const js::object& glTF) { - // gltf - - // Сцена по умолчанию - // Сцены -> Ноды - // Ноды -> Ноды, меши, матрицы, translation, rotation - // Меши -> Примитивы - // Примитивы -> Материал, вершинные данные - // Материалы -> текстуры - // Текстуры - // Буферы -} - -PreparedModel::PreparedModel(const std::string& domain, Resource glb) { +PreparedModel::PreparedModel(const std::string& domain, const PreparedGLTF& glTF) { } @@ -110,32 +96,24 @@ void AssetsManager::loadResourceFromFile_Model(ResourceChangeObj& out, const std json, glTF, glB */ - // Либо это внутренний формат, либо glTF - Resource res(path); std::filesystem::file_time_type ftt = fs::last_write_time(path); - PreparedModel pmc; - 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); - std::u8string data = pm.dump(); - pmc = PreparedModel(domain, pm); - out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, Resource((const uint8_t*) data.data(), data.size()), ftt); + 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(); - pmc = PreparedModel(domain, obj); - out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt); + PreparedGLTF gltf(domain, obj); + out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), ftt); } else if(extension == ".glb") { - pmc = PreparedModel(domain, res); - out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt); + PreparedGLTF gltf(domain, res); + out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), 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 { @@ -513,6 +491,110 @@ 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& [domain2, key2] : value.ModelToLocalId) { + models.push_back(lock->getId(EnumAssets::Model, domain2, key2)); + } + + { + 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.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](auto& val) { + for(const auto& [key, pipeline] : val.Textures) { + TexturePipeline pipe; + 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; @@ -546,60 +628,6 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const LOG.debug() << "Дамп ассетов:\n" << result.str(); } - 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& [domain2, key2] : value.ModelToLocalId) { - models.push_back(lock->getId(EnumAssets::Model, domain2, key2)); - } - - { - 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& [domain2, list] : value.ModelDependencies) { - for(const std::string& key2 : list) { - ResourceId subResId = lock->getId(EnumAssets::Model, domain2, key2); - deps.ModelDeps.push_back(subResId); - } - } - - for(auto& [domain2, list] : value.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); - } - } - } - // Вычислить зависимости моделей { // Затираем старые данные diff --git a/Src/Server/AssetsManager.hpp b/Src/Server/AssetsManager.hpp index fe975e6..fb3ab2e 100644 --- a/Src/Server/AssetsManager.hpp +++ b/Src/Server/AssetsManager.hpp @@ -29,8 +29,7 @@ struct PreparedModel { std::unordered_map> ModelDependencies; 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); + PreparedModel(const std::string& domain, const PreparedGLTF& glTF); PreparedModel() = default; PreparedModel(const PreparedModel&) = default; @@ -66,10 +65,13 @@ public: std::unordered_map> Lost[(int) EnumAssets::MAX_ENUM]; // Домен и ключ ресурса std::unordered_map>> NewOrChange[(int) EnumAssets::MAX_ENUM]; - + std::unordered_map, fs::file_time_type>>> NewOrChange_Models; std::unordered_map>> Nodestates; - std::unordered_map>> Models; + // std::unordered_map>> Models; }; private: