diff --git a/Src/Client/ServerSession.cpp b/Src/Client/ServerSession.cpp index f77654d..48e349d 100644 --- a/Src/Client/ServerSession.cpp +++ b/Src/Client/ServerSession.cpp @@ -8,6 +8,7 @@ #include "glm/ext/quaternion_geometric.hpp" #include #include +#include #include #include #include @@ -15,12 +16,93 @@ #include #include #include +#include #include #include namespace LV::Client { +namespace { + +const char* assetTypeName(EnumAssets type) { + switch(type) { + case EnumAssets::Nodestate: return "nodestate"; + case EnumAssets::Model: return "model"; + case EnumAssets::Texture: return "texture"; + case EnumAssets::Particle: return "particle"; + case EnumAssets::Animation: return "animation"; + case EnumAssets::Sound: return "sound"; + case EnumAssets::Font: return "font"; + default: return "unknown"; + } +} + +std::optional debugExpectedGeneratedNodeId(int rx, int ry, int rz) { + if(ry == 1 && rz == 0) + return DefNodeId(0); + if(rx == 0 && ry == 1) + return DefNodeId(0); + if(rx == 0 && rz == 0) + return DefNodeId(1); + if(ry == 0 && rz == 0) + return DefNodeId(2); + if(rx == 0 && ry == 0) + return DefNodeId(3); + return std::nullopt; +} + +void debugCheckGeneratedChunkNodes(WorldId_t worldId, + Pos::GlobalChunk chunkPos, + const std::array& chunk) +{ + if(chunkPos[0] != 0 && chunkPos[1] != 0 && chunkPos[2] != 0) + return; + + static std::atomic warnCount = 0; + if(warnCount.load() >= 16) + return; + + Pos::bvec4u localChunk = chunkPos & 0x3; + const int baseX = int(localChunk[0]) * 16; + const int baseY = int(localChunk[1]) * 16; + const int baseZ = int(localChunk[2]) * 16; + const int globalBaseX = int(chunkPos[0]) * 16; + const int globalBaseY = int(chunkPos[1]) * 16; + const int globalBaseZ = int(chunkPos[2]) * 16; + + for(int z = 0; z < 16; z++) + for(int y = 0; y < 16; y++) + for(int x = 0; x < 16; x++) { + int rx = baseX + x; + int ry = baseY + y; + int rz = baseZ + z; + int gx = globalBaseX + x; + int gy = globalBaseY + y; + int gz = globalBaseZ + z; + std::optional expected = debugExpectedGeneratedNodeId(rx, ry, rz); + if(!expected) + continue; + + const Node& node = chunk[x + y * 16 + z * 16 * 16]; + if(node.NodeId != *expected) { + uint32_t index = warnCount.fetch_add(1); + if(index < 16) { + TOS::Logger("Client>WorldDebug").warn() + << "Generated node mismatch world " << worldId + << " chunk " << int(chunkPos[0]) << ',' << int(chunkPos[1]) << ',' << int(chunkPos[2]) + << " at local " << rx << ',' << ry << ',' << rz + << " global " << gx << ',' << gy << ',' << gz + << " expected " << *expected + << " got " << node.NodeId; + } + return; + } + } +} + +} + ServerSession::ServerSession(asio::io_context &ioc, std::unique_ptr&& socket) : IAsyncDestructible(ioc), Socket(std::move(socket)) //, NetInputPackets(1024) { @@ -170,6 +252,18 @@ void ServerSession::shutdown(EnumDisconnect type) { LOG.info() << "Отключение от сервера: " << reason; } +void ServerSession::requestModsReload() { + if(!Socket || !isConnected()) + return; + + Net::Packet packet; + packet << (uint8_t) ToServer::L1::System + << (uint8_t) ToServer::L2System::ReloadMods; + + Socket->pushPacket(std::move(packet)); + LOG.info() << "Запрос на перезагрузку модов отправлен"; +} + void ServerSession::onResize(uint32_t width, uint32_t height) { } @@ -284,10 +378,50 @@ void ServerSession::update(GlobalTime gTime, float dTime) { // Получить ресурсы с AssetsManager { + static std::atomic debugAssetReadLogCount = 0; std::vector>> resources = AM->pullReads(); std::vector needRequest; for(auto& [key, res] : resources) { + { + auto& waitingByDomain = AsyncContext.ResourceWait[(int) key.Type]; + auto iterDomain = waitingByDomain.find(key.Domain); + if(iterDomain != waitingByDomain.end()) { + auto& entries = iterDomain->second; + entries.erase(std::remove_if(entries.begin(), entries.end(), + [&](const std::pair& entry) { + return entry.first == key.Key && entry.second == key.Hash; + }), + entries.end()); + if(entries.empty()) + waitingByDomain.erase(iterDomain); + } + } + + if(key.Domain == "test" + && (key.Type == EnumAssets::Nodestate + || key.Type == EnumAssets::Model + || key.Type == EnumAssets::Texture)) + { + uint32_t idx = debugAssetReadLogCount.fetch_add(1); + if(idx < 128) { + if(res) { + LOG.debug() << "Cache hit type=" << assetTypeName(key.Type) + << " id=" << key.Id + << " key=" << key.Domain << ':' << key.Key + << " size=" << res->size(); + } else { + LOG.debug() << "Cache miss type=" << assetTypeName(key.Type) + << " id=" << key.Id + << " key=" << key.Domain << ':' << key.Key + << " hash=" << int(key.Hash[0]) << '.' + << int(key.Hash[1]) << '.' + << int(key.Hash[2]) << '.' + << int(key.Hash[3]); + } + } + } + if(!res) { // Проверить не был ли уже отправлен запрос на получение этого хеша auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), key.Hash); @@ -311,6 +445,11 @@ void ServerSession::update(GlobalTime gTime, float dTime) { if(!needRequest.empty()) { assert(needRequest.size() < (1 << 16)); + uint32_t idx = debugAssetReadLogCount.fetch_add(1); + if(idx < 128) { + LOG.debug() << "Send ResourceRequest count=" << needRequest.size(); + } + Net::Packet p; p << (uint8_t) ToServer::L1::System << (uint8_t) ToServer::L2System::ResourceRequest; p << (uint16_t) needRequest.size(); @@ -416,8 +555,33 @@ void ServerSession::update(GlobalTime gTime, float dTime) { } // Отправляем запрос на получение ресурсов - if(!needToLoad.empty()) + if(!needToLoad.empty()) { + static std::atomic debugReadRequestLogCount = 0; + AssetsManager::ResourceKey firstDebug; + bool hasDebug = false; + for(const auto& entry : needToLoad) { + if(entry.Domain == "test" + && (entry.Type == EnumAssets::Nodestate + || entry.Type == EnumAssets::Model + || entry.Type == EnumAssets::Texture)) + { + firstDebug = entry; + hasDebug = true; + break; + } + } + if(hasDebug && debugReadRequestLogCount.fetch_add(1) < 64) { + LOG.debug() << "Queue asset read count=" << needToLoad.size() + << " type=" << assetTypeName(firstDebug.Type) + << " id=" << firstDebug.Id + << " key=" << firstDebug.Domain << ':' << firstDebug.Key + << " hash=" << int(firstDebug.Hash[0]) << '.' + << int(firstDebug.Hash[1]) << '.' + << int(firstDebug.Hash[2]) << '.' + << int(firstDebug.Hash[3]); + } AM->pushReads(std::move(needToLoad)); + } AsyncContext.Binds.push_back(std::move(abc)); } @@ -675,7 +839,9 @@ void ServerSession::update(GlobalTime gTime, float dTime) { auto& c = chunks_Changed[wId]; for(auto& [pos, val] : list) { - unCompressNodes(val, caocvr[pos].data()); + auto& chunkNodes = caocvr[pos]; + unCompressNodes(val, chunkNodes.data()); + debugCheckGeneratedChunkNodes(wId, pos, chunkNodes); c.push_back(pos); } } @@ -940,6 +1106,19 @@ void ServerSession::setRenderSession(IRenderSession* session) { RS = session; } +void ServerSession::resetResourceSyncState() { + AsyncContext.AssetsLoading.clear(); + AsyncContext.AlreadyLoading.clear(); + for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) + AsyncContext.ResourceWait[type].clear(); + AsyncContext.Binds.clear(); + AsyncContext.LoadedResources.clear(); + AsyncContext.ThisTickEntry = {}; + AsyncContext.LoadedAssets.lock()->clear(); + AsyncContext.AssetsBinds.lock()->clear(); + AsyncContext.TickSequence.lock()->clear(); +} + coro<> ServerSession::run(AsyncUseControl::Lock) { try { while(!IsGoingShutdown && IsConnected) { @@ -950,6 +1129,7 @@ coro<> ServerSession::run(AsyncUseControl::Lock) { } IsConnected = false; + resetResourceSyncState(); co_return; } @@ -1009,6 +1189,7 @@ coro<> ServerSession::rP_System(Net::AsyncSocket &sock) { } coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { + static std::atomic debugResourceLogCount = 0; uint8_t second = co_await sock.read(); switch((ToClient::L2Resource) second) { @@ -1035,6 +1216,23 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { (EnumAssets) type, (ResourceId) id, std::move(domain), std::move(key), hash ); + + if(binds.back().Domain == "test" + && (binds.back().Type == EnumAssets::Nodestate + || binds.back().Type == EnumAssets::Model + || binds.back().Type == EnumAssets::Texture)) + { + uint32_t idx = debugResourceLogCount.fetch_add(1); + if(idx < 128) { + LOG.debug() << "Bind asset type=" << assetTypeName(binds.back().Type) + << " id=" << binds.back().Id + << " key=" << binds.back().Domain << ':' << binds.back().Key + << " hash=" << int(binds.back().Hash[0]) << '.' + << int(binds.back().Hash[1]) << '.' + << int(binds.back().Hash[2]) << '.' + << int(binds.back().Hash[3]); + } + } } AsyncContext.AssetsBinds.lock()->push_back(AssetsBindsChange(binds, {})); @@ -1072,6 +1270,20 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { std::string domain = co_await sock.read(); std::string key = co_await sock.read(); + if(domain == "test" + && (type == EnumAssets::Nodestate + || type == EnumAssets::Model + || type == EnumAssets::Texture)) + { + uint32_t idx = debugResourceLogCount.fetch_add(1); + if(idx < 128) { + LOG.debug() << "InitResSend type=" << assetTypeName(type) + << " id=" << id + << " key=" << domain << ':' << key + << " size=" << size; + } + } + AsyncContext.AssetsLoading[hash] = AssetLoading{ type, id, std::move(domain), std::move(key), std::u8string(size, '\0'), 0 @@ -1095,6 +1307,20 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { if(al.Offset == al.Data.size()) { // Ресурс полностью загружен + 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(); + } + } + AsyncContext.LoadedAssets.lock()->emplace_back( al.Type, al.Id, std::move(al.Domain), std::move(al.Key), std::move(al.Data) ); @@ -1118,6 +1344,7 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) { } coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) { + static std::atomic debugDefLogCount = 0; uint8_t second = co_await sock.read(); switch((ToClient::L2Definition) second) { @@ -1148,6 +1375,15 @@ coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) { def.NodestateId = co_await sock.read(); def.TexId = id; + if(id < 32) { + uint32_t idx = debugDefLogCount.fetch_add(1); + if(idx < 64) { + LOG.debug() << "DefNode id=" << id + << " nodestate=" << def.NodestateId + << " tex=" << def.TexId; + } + } + AsyncContext.ThisTickEntry.Profile_Node_AddOrChange.emplace_back(id, def); co_return; diff --git a/Src/Client/ServerSession.hpp b/Src/Client/ServerSession.hpp index 4b59385..6d5aba8 100644 --- a/Src/Client/ServerSession.hpp +++ b/Src/Client/ServerSession.hpp @@ -36,6 +36,7 @@ public: static coro> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function onProgress = nullptr); void shutdown(EnumDisconnect type); + void requestModsReload(); bool isConnected() { return Socket->isAlive() && IsConnected; @@ -182,6 +183,7 @@ private: ServerSession(asio::io_context &ioc, std::unique_ptr &&socket); virtual coro<> asyncDestructor() override; + void resetResourceSyncState(); }; -} \ No newline at end of file +} diff --git a/Src/Client/Vulkan/Vulkan.cpp b/Src/Client/Vulkan/Vulkan.cpp index c2d4a1e..4ee5861 100644 --- a/Src/Client/Vulkan/Vulkan.cpp +++ b/Src/Client/Vulkan/Vulkan.cpp @@ -2305,6 +2305,10 @@ void Vulkan::gui_ConnectedToServer() { if(ImGui::Button("Delimeter")) LOG.debug(); + if(ImGui::Button("Перезагрузить моды")) { + Game.Session->requestModsReload(); + } + if(ImGui::Button("Выйти")) { Game.Выйти = true; Game.ImGuiInterfaces.pop_back(); diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index 1a61513..a46661f 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -9,11 +9,13 @@ #include "glm/ext/scalar_constants.hpp" #include "glm/matrix.hpp" #include "glm/trigonometric.hpp" +#include #include #include #include #include #include +#include #include #include #include @@ -307,6 +309,15 @@ void ChunkMeshGenerator::run(uint8_t id) { NodeVertexStatic v; std::memset(&v, 0, sizeof(v)); + static std::atomic debugMeshWarnCount = 0; + const bool debugMeshEnabled = debugMeshWarnCount.load() < 16; + std::array expectedColumnX = {}; + std::array expectedColumnY = {}; + std::array expectedColumnZ = {}; + std::array generatedColumnX = {}; + std::array generatedColumnY = {}; + std::array generatedColumnZ = {}; + struct ModelCacheEntry { std::vector>>>> Routes; }; @@ -377,6 +388,7 @@ void ChunkMeshGenerator::run(uint8_t id) { for(int z = 0; z < 16; z++) for(int y = 0; y < 16; y++) for(int x = 0; x < 16; x++) { + const size_t vertexStart = result.NodeVertexs.size(); int fullCovered = 0; fullCovered |= fullNodes[x+1+1][y+1][z+1]; @@ -392,9 +404,18 @@ void ChunkMeshGenerator::run(uint8_t id) { const Node& nodeData = (*chunk)[x+y*16+z*16*16]; const DefNode_t* node = getNodeProfile(nodeData.NodeId); + if(debugMeshEnabled) { + const bool hasRenderable = (node->NodestateId != 0) || (node->TexId != 0); + if(hasRenderable && fullCovered != 0b111111) { + expectedColumnX[x] = 1; + expectedColumnY[y] = 1; + expectedColumnZ[z] = 1; + } + } + bool usedModel = false; - if(NSP && node->NodestateId != 0) { + if(NSP && (node->NodestateId != 0 || NSP->hasNodestate(node->NodestateId))) { auto iterCache = modelCache.find(nodeData.Data); if(iterCache == modelCache.end()) { std::unordered_map states; @@ -423,7 +444,7 @@ void ChunkMeshGenerator::run(uint8_t id) { } if(usedModel) - continue; + goto node_done; if(NSP && node->TexId != 0) { auto iterTex = baseTextureCache.find(node->TexId); @@ -439,7 +460,7 @@ void ChunkMeshGenerator::run(uint8_t id) { } if(v.Tex == 0) - continue; + goto node_done; // Рендерим обычный кубоид // XZ+Y @@ -645,6 +666,60 @@ void ChunkMeshGenerator::run(uint8_t id) { v.TV = 0; result.NodeVertexs.push_back(v); } + + node_done: + if(debugMeshEnabled) { + const bool emitted = result.NodeVertexs.size() > vertexStart; + if(emitted) { + generatedColumnX[x] = 1; + generatedColumnY[y] = 1; + generatedColumnZ[z] = 1; + } else { + const bool hasRenderable = (node->NodestateId != 0) || (node->TexId != 0); + if(hasRenderable && fullCovered != 0b111111) { + uint32_t warnIndex = debugMeshWarnCount.fetch_add(1); + if(warnIndex < 16) { + LOG.warn() << "Missing node geometry at chunk " << int(pos[0]) << ',' + << int(pos[1]) << ',' << int(pos[2]) + << " local " << x << ',' << y << ',' << z + << " nodeId " << nodeData.NodeId + << " meta " << int(nodeData.Meta) + << " covered " << fullCovered + << " tex " << node->TexId + << " nodestate " << node->NodestateId; + } + } + } + } + } + + if(debugMeshEnabled) { + auto collectMissing = [](const std::array& expected, + const std::array& generated) { + std::string res; + for(int i = 0; i < 16; i++) { + if(expected[i] && !generated[i]) { + if(!res.empty()) + res += ','; + res += std::to_string(i); + } + } + return res; + }; + + std::string missingX = collectMissing(expectedColumnX, generatedColumnX); + std::string missingY = collectMissing(expectedColumnY, generatedColumnY); + std::string missingZ = collectMissing(expectedColumnZ, generatedColumnZ); + if(!missingX.empty() || !missingY.empty() || !missingZ.empty()) { + uint32_t warnIndex = debugMeshWarnCount.fetch_add(1); + if(warnIndex < 16) { + LOG.warn() << "Missing mesh columns at chunk " << int(pos[0]) << ',' + << int(pos[1]) << ',' << int(pos[2]) + << " missingX[" << missingX << "]" + << " missingY[" << missingY << "]" + << " missingZ[" << missingZ << "]"; + } + } } // Вычислить индексы и сократить вершины @@ -1589,8 +1664,10 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) { modelLost.insert(modelLost.end(), iter->second.begin(), iter->second.end()); std::vector changedModels; - if(!modelResources.empty() || !modelLost.empty()) - changedModels = MP.onModelChanges(std::move(modelResources), std::move(modelLost)); + if(!modelResources.empty() || !modelLost.empty()) { + const auto& modelAssets = ServerSession->Assets[EnumAssets::Model]; + changedModels = MP.onModelChanges(std::move(modelResources), std::move(modelLost), &modelAssets); + } if(TP) { std::vector> textureResources; diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index ceec9b1..872b9b9 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -85,7 +85,9 @@ public: } // Применяет изменения, возвращая все затронутые модели - std::vector onModelChanges(std::vector> newOrChanged, std::vector lost) { + std::vector onModelChanges(std::vector> newOrChanged, + std::vector lost, + const std::unordered_map* modelAssets) { std::vector result; std::move_only_function makeUnready; @@ -130,6 +132,14 @@ 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] : newOrChanged) { result.push_back(key); @@ -164,56 +174,59 @@ public: } std::vector v; - + + auto addQuad = [&](const glm::vec3& p0, + const glm::vec3& p1, + const glm::vec3& p2, + const glm::vec3& p3, + const glm::vec2& uv0, + const glm::vec2& uv1, + const glm::vec2& uv2, + const glm::vec2& uv3) { + v.emplace_back(p0, uv0, texId); + v.emplace_back(p1, uv1, texId); + v.emplace_back(p2, uv2, texId); + v.emplace_back(p0, uv0, texId); + v.emplace_back(p2, uv2, texId); + v.emplace_back(p3, uv3, texId); + }; + + const float x0 = min.x; + const float x1 = max.x; + const float y0 = min.y; + const float y1 = max.y; + const float z0 = min.z; + const float z1 = max.z; + const float u0 = from_uv.x; + const float v0 = from_uv.y; + const float u1 = to_uv.x; + const float v1 = to_uv.y; + switch(face) { - case EnumFace::Down: - v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId); - v.emplace_back(glm::vec3{max.x, min.y, min.z}, to_uv, texId); - v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{min.x, min.y, max.z}, to_uv, texId); - v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId); - break; case EnumFace::Up: - v.emplace_back(glm::vec3{min.x, max.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{max.x, max.y, min.z}, glm::vec2{from_uv.x, to_uv.y}, texId); - v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); - v.emplace_back(glm::vec3{min.x, max.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); - v.emplace_back(glm::vec3{min.x, max.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId); - break; - case EnumFace::North: - v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{max.x, min.y, min.z}, glm::vec2{from_uv.x, to_uv.y}, texId); - v.emplace_back(glm::vec3{max.x, max.y, min.z}, to_uv, texId); - v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{max.x, max.y, min.z}, to_uv, texId); - v.emplace_back(glm::vec3{min.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId); - break; - case EnumFace::South: - v.emplace_back(glm::vec3{min.x, min.y, max.z}, from_uv, texId); - v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId); - v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); - v.emplace_back(glm::vec3{min.x, min.y, max.z}, from_uv, texId); - v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); - v.emplace_back(glm::vec3{min.x, max.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId); - break; - case EnumFace::West: - v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{min.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId); - v.emplace_back(glm::vec3{min.x, max.y, max.z}, to_uv, texId); - v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{min.x, max.y, max.z}, to_uv, texId); - v.emplace_back(glm::vec3{min.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId); - break; + addQuad({x0, y1, z1}, {x1, y1, z1}, {x1, y1, z0}, {x0, y1, z0}, + {u0, v0}, {u1, v0}, {u1, v1}, {u0, v1}); + break; + case EnumFace::Down: + addQuad({x0, y0, z1}, {x0, y0, z0}, {x1, y0, z0}, {x1, y0, z1}, + {u0, v0}, {u0, v1}, {u1, v1}, {u1, v0}); + break; case EnumFace::East: - v.emplace_back(glm::vec3{max.x, min.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId); - v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); - v.emplace_back(glm::vec3{max.x, min.y, min.z}, from_uv, texId); - v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId); - v.emplace_back(glm::vec3{max.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId); - break; + addQuad({x1, y0, z1}, {x1, y0, z0}, {x1, y1, z0}, {x1, y1, z1}, + {u0, v0}, {u0, v1}, {u1, v1}, {u1, v0}); + break; + case EnumFace::West: + addQuad({x0, y0, z1}, {x0, y1, z1}, {x0, y1, z0}, {x0, y0, z0}, + {u0, v0}, {u1, v0}, {u1, v1}, {u0, v1}); + break; + case EnumFace::South: + addQuad({x0, y0, z1}, {x1, y0, z1}, {x1, y1, z1}, {x0, y1, z1}, + {u0, v0}, {u1, v0}, {u1, v1}, {u0, v1}); + break; + case EnumFace::North: + addQuad({x0, y0, z0}, {x0, y1, z0}, {x1, y1, z0}, {x1, y0, z0}, + {u0, v0}, {u0, v1}, {u1, v1}, {u1, v0}); + break; default: MAKE_ERROR("EnumFace::None"); } @@ -223,6 +236,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()) + continue; + model.Depends.emplace_back(iter->second, Transformations{}); + } + } + // struct Face { // int TintIndex = -1; // int16_t Rotation = 0; @@ -271,12 +294,16 @@ private: Logger LOG = "Client>ModelProvider"; // Таблица моделей std::unordered_map Models; + std::unordered_set MissingModelsLogged; uint64_t UniqId = 0; Model getModel(ResourceId id, std::vector& used) { auto iterModel = Models.find(id); if(iterModel == Models.end()) { // Нет такой модели, ну и хрен с ним + if(MissingModelsLogged.insert(id).second) { + LOG.warn() << "Missing model id=" << id; + } return {}; } @@ -708,6 +735,15 @@ public: } else if(data.starts_with((const char8_t*) "{")) { type = "InternalJson"; // nodestate в json формате + } else { + type = "InternalBinaryLegacy"; + // Старый двоичный формат без заголовка "bn" + std::u8string patched; + patched.reserve(data.size() + 2); + patched.push_back(u8'b'); + patched.push_back(u8'n'); + patched.append(data); + nodestate = PreparedNodeState(patched); } } catch(const std::exception& exc) { LOG.warn() << "Не удалось распарсить nodestate " << type << ":\n\t" << exc.what(); @@ -715,6 +751,14 @@ public: } Nodestates.insert_or_assign(key, std::move(nodestate)); + if(key < 64) { + auto iter = Nodestates.find(key); + if(iter != Nodestates.end()) { + LOG.debug() << "Nodestate loaded id=" << key + << " routes=" << iter->second.Routes.size() + << " models=" << iter->second.LocalToModel.size(); + } + } } if(!changedModels.empty()) { @@ -745,11 +789,26 @@ public: // states - Текущие значения состояний ноды std::vector>>>> getModelsForNode(AssetsNodestate id, const std::vector& statesInfo, const std::unordered_map& states) { auto iterNodestate = Nodestates.find(id); - if(iterNodestate == Nodestates.end()) + if(iterNodestate == Nodestates.end()) { + if(MissingNodestateLogged.insert(id).second) { + LOG.warn() << "Missing nodestate id=" << id; + } return {}; + } PreparedNodeState& nodestate = iterNodestate->second; std::vector routes = nodestate.getModelsForState(statesInfo, states); + if(routes.empty()) { + int32_t metaValue = 0; + if(auto iterMeta = states.find("meta"); iterMeta != states.end()) + metaValue = iterMeta->second; + uint64_t key = (uint64_t(id) << 32) | (uint32_t(metaValue) & 0xffffffffu); + if(EmptyRouteLogged.insert(key).second) { + LOG.warn() << "No nodestate routes id=" << id + << " meta=" << metaValue + << " total_routes=" << nodestate.Routes.size(); + } + } std::vector>>>> result; std::unordered_map pipelineResolveCache; @@ -765,12 +824,12 @@ public: for(const Vertex& v : r) { NodeVertexStatic vert; - vert.FX = (v.Pos.x+0.5f)*64+224; - vert.FY = (v.Pos.y+0.5f)*64+224; - vert.FZ = (v.Pos.z+0.5f)*64+224; + vert.FX = (v.Pos.x + 16.0f) * 2.0f + 224.0f; + vert.FY = (v.Pos.y + 16.0f) * 2.0f + 224.0f; + vert.FZ = (v.Pos.z + 16.0f) * 2.0f + 224.0f; - vert.TU = std::clamp(v.UV.x * (1 << 16), 0, (1 << 16) - 1); - vert.TV = std::clamp(v.UV.y * (1 << 16), 0, (1 << 16) - 1); + vert.TU = std::clamp(v.UV.x * (1 << 11), 0, (1 << 16) - 1); + vert.TV = std::clamp(v.UV.y * (1 << 11), 0, (1 << 16) - 1); const TexturePipeline& pipe = model.TextureMap[model.TextureKeys[v.TexId]]; if(auto iterPipe = pipelineResolveCache.find(pipe); iterPipe != pipelineResolveCache.end()) { @@ -836,11 +895,17 @@ public: return TP.getTextureId(pipe); } + bool hasNodestate(AssetsNodestate id) const { + return Nodestates.contains(id); + } + private: Logger LOG = "Client>NodestateProvider"; ModelProvider& MP; TextureProvider& TP; std::unordered_map Nodestates; + std::unordered_set MissingNodestateLogged; + std::unordered_set EmptyRouteLogged; }; /* diff --git a/Src/Common/Abstract.cpp b/Src/Common/Abstract.cpp index 7785ba0..5ce7114 100644 --- a/Src/Common/Abstract.cpp +++ b/Src/Common/Abstract.cpp @@ -997,6 +997,9 @@ PreparedNodeState::PreparedNodeState(const std::u8string_view data) { std::u8string PreparedNodeState::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()); @@ -1357,6 +1360,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) { bin.rhs = *nodeId; } + node.v = bin; Nodes.emplace_back(std::move(node)); assert(Nodes.size() < std::pow(2, 16)-64); leftToken = uint16_t(Nodes.size()-1); @@ -1756,6 +1760,29 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro } } + if(boost::system::result submodels_val = profile.try_at("sub_models")) { + const js::array& submodels = submodels_val->as_array(); + SubModels.reserve(submodels.size()); + + 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}); + } 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}); + } + } + } + if(boost::system::result subModels_val = profile.try_at("sub_models")) { const js::array& subModels = subModels_val->as_array(); diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index 6e64c5f..2a13aa8 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -713,13 +713,15 @@ struct PreparedNodeState { } }; + for(const auto& route : Routes) + lambda(route.first); + std::sort(variables.begin(), variables.end()); auto eraseIter = std::unique(variables.begin(), variables.end()); variables.erase(eraseIter, variables.end()); - bool ok = false; - for(const std::string_view key : variables) { + bool ok = false; if(size_t pos = key.find(':'); pos != std::string::npos) { std::string_view state, value; state = key.substr(0, pos); diff --git a/Src/Common/Packets.hpp b/Src/Common/Packets.hpp index 9f7e220..4ad0ec2 100644 --- a/Src/Common/Packets.hpp +++ b/Src/Common/Packets.hpp @@ -77,7 +77,8 @@ enum struct L2System : uint8_t { Disconnect, Test_CAM_PYR_POS, BlockChange, - ResourceRequest + ResourceRequest, + ReloadMods }; } @@ -184,4 +185,4 @@ enum struct L2Content : uint8_t { } -} \ No newline at end of file +} diff --git a/Src/Server/AssetsManager.cpp b/Src/Server/AssetsManager.cpp index 92cc2aa..f119dec 100644 --- a/Src/Server/AssetsManager.cpp +++ b/Src/Server/AssetsManager.cpp @@ -221,11 +221,18 @@ std::tuple&> AssetsManager:: 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()) @@ -233,13 +240,23 @@ std::tuple&> AssetsManager:: id = index*TableEntry::ChunkSize + pos; data = &entry.Entries[pos]; + break; } if(!data) { table.emplace_back(std::make_unique>()); - id = (table.size()-1)*TableEntry::ChunkSize; - data = &table.back()->Entries[0]; - table.back()->Empty.reset(0); + 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) diff --git a/Src/Server/ContentManager.cpp b/Src/Server/ContentManager.cpp index 096fa76..9d63d77 100644 --- a/Src/Server/ContentManager.cpp +++ b/Src/Server/ContentManager.cpp @@ -7,7 +7,7 @@ namespace LV::Server { ContentManager::ContentManager(AssetsManager &am) : AM(am) { - + std::fill(std::begin(NextId), std::end(NextId), 1); } ContentManager::~ContentManager() = default; @@ -111,6 +111,31 @@ void ContentManager::unRegisterModifier(EnumDefContent type, const std::string& ProfileChanges[(int) type].push_back(id); } +void ContentManager::markAllProfilesDirty(EnumDefContent type) { + const auto &table = ContentKeyToId[(int) type]; + for(const auto& domainPair : table) { + for(const auto& keyPair : domainPair.second) { + ProfileChanges[(int) type].push_back(keyPair.second); + } + } +} + +std::vector ContentManager::collectProfileIds(EnumDefContent type) const { + std::vector ids; + const auto &table = ContentKeyToId[(int) type]; + + for(const auto& domainPair : table) { + for(const auto& keyPair : domainPair.second) { + ids.push_back(keyPair.second); + } + } + + std::sort(ids.begin(), ids.end()); + auto last = std::unique(ids.begin(), ids.end()); + ids.erase(last, ids.end()); + return ids; +} + ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() { Out_buildEndProfiles result; @@ -138,4 +163,4 @@ ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() { return result; } -} \ No newline at end of file +} diff --git a/Src/Server/ContentManager.hpp b/Src/Server/ContentManager.hpp index f6b0337..e12c581 100644 --- a/Src/Server/ContentManager.hpp +++ b/Src/Server/ContentManager.hpp @@ -48,7 +48,7 @@ class ContentManager { // Следующие идентификаторы регистрации контента - ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {0}; + ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {}; // Домен -> {ключ -> идентификатор} std::unordered_map> ContentKeyToId[(int) EnumDefContent::MAX_ENUM]; @@ -143,6 +143,10 @@ public: // Регистрация модификатора предмета модом void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile); void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key); + // Пометить все профили типа как изменённые (например, после перезагрузки ассетов) + void markAllProfilesDirty(EnumDefContent type); + // Список всех зарегистрированных профилей выбранного типа + std::vector collectProfileIds(EnumDefContent type) const; // Компилирует изменённые профили struct Out_buildEndProfiles { std::vector ChangedProfiles[(int) EnumDefContent::MAX_ENUM]; @@ -209,4 +213,4 @@ private: AssetsManager& AM; }; -} \ No newline at end of file +} diff --git a/Src/Server/GameServer.cpp b/Src/Server/GameServer.cpp index f578c2a..1231b76 100644 --- a/Src/Server/GameServer.cpp +++ b/Src/Server/GameServer.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -1107,7 +1108,7 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string co_await Net::AsyncSocket::write(socket, 0); External.NewConnectedPlayers.lock_write() - ->push_back(std::make_shared(IOC, std::move(socket), username)); + ->push_back(std::make_shared(IOC, std::move(socket), username, this)); } } } @@ -1444,7 +1445,7 @@ void GameServer::init(fs::path worldPath) { { sol::table t = LuaMainState.create_table(); - Content.CM.registerBase(EnumDefContent::Node, "core", "none", t); + // Content.CM.registerBase(EnumDefContent::Node, "core", "none", t); Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t); } @@ -1453,11 +1454,11 @@ void GameServer::init(fs::path worldPath) { // TODO: регистрация контента из mod/content/* - Content.CM.buildEndProfiles(); - pushEvent("preInit"); pushEvent("highPreInit"); + Content.CM.buildEndProfiles(); + LOG.info() << "Инициализация"; initLua(); @@ -1674,6 +1675,13 @@ void GameServer::initLuaPost() { } +void GameServer::requestModsReload() { + bool expected = false; + if(ModsReloadRequested.compare_exchange_strong(expected, true)) { + LOG.info() << "Запрошена перезагрузка модов"; + } +} + void GameServer::stepConnections() { // Подключить новых игроков if(!External.NewConnectedPlayers.no_lock_readable().empty()) { @@ -1715,9 +1723,42 @@ void GameServer::stepConnections() { } void GameServer::stepModInitializations() { + if(ModsReloadRequested.exchange(false)) { + reloadMods(); + } BackingChunkPressure.endWithResults(); } +void GameServer::reloadMods() { + LOG.info() << "Перезагрузка модов: ассеты и зависимости"; + + AssetsManager::ResourceChangeObj changes = Content.AM.recheckResources(AssetsInit); + AssetsManager::Out_applyResourceChange applied = Content.AM.applyResourceChange(changes); + + size_t changedCount = 0; + size_t lostCount = 0; + for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) { + for(const auto& entry : applied.NewOrChange[type]) { + Content.OnContentChanges.AssetsInfo[type].push_back(entry.first); + changedCount++; + } + + lostCount += applied.Lost[type].size(); + } + + Content.CM.markAllProfilesDirty(EnumDefContent::Node); + Content.CM.buildEndProfiles(); + + std::vector nodeIds = Content.CM.collectProfileIds(EnumDefContent::Node); + if(!nodeIds.empty()) { + Content.OnContentChanges.Node.append_range(nodeIds); + } + + LOG.info() << "Перезагрузка завершена: обновлено ассетов=" << changedCount + << " удалено=" << lostCount + << " нод=" << nodeIds.size(); +} + IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() { IWorldSaveBackend::TickSyncInfo_In toDB; diff --git a/Src/Server/GameServer.hpp b/Src/Server/GameServer.hpp index 6e5f4af..7e59eec 100644 --- a/Src/Server/GameServer.hpp +++ b/Src/Server/GameServer.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,7 @@ class GameServer : public AsyncObject { bool IsAlive = true, IsGoingShutdown = false; std::string ShutdownReason; + std::atomic ModsReloadRequested = false; static constexpr float PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний @@ -283,6 +285,7 @@ public: void waitShutdown() { UseLock.wait_no_use(); } + void requestModsReload(); // Подключение tcp сокета coro<> pushSocketConnect(tcp::socket socket); @@ -315,6 +318,7 @@ private: */ void stepModInitializations(); + void reloadMods(); /* Пересчёт зон видимости игроков, если необходимо @@ -364,4 +368,4 @@ private: void stepSyncContent(); }; -} \ No newline at end of file +} diff --git a/Src/Server/RemoteClient.cpp b/Src/Server/RemoteClient.cpp index 1b5e98e..0a6b723 100644 --- a/Src/Server/RemoteClient.cpp +++ b/Src/Server/RemoteClient.cpp @@ -3,8 +3,10 @@ #include "Common/Abstract.hpp" #include "Common/Net.hpp" #include "Server/Abstract.hpp" +#include "Server/GameServer.hpp" #include "Server/World.hpp" #include +#include #include #include #include @@ -13,6 +15,23 @@ namespace LV::Server { +namespace { + +const char* assetTypeName(EnumAssets type) { + switch(type) { + case EnumAssets::Nodestate: return "nodestate"; + case EnumAssets::Model: return "model"; + case EnumAssets::Texture: return "texture"; + case EnumAssets::Particle: return "particle"; + case EnumAssets::Animation: return "animation"; + case EnumAssets::Sound: return "sound"; + case EnumAssets::Font: return "font"; + default: return "unknown"; + } +} + +} + RemoteClient::~RemoteClient() { shutdown(EnumDisconnect::ByInterface, "~RemoteClient()"); if(Socket.isAlive()) { @@ -487,9 +506,13 @@ ResourceRequest RemoteClient::pushPreparedPackets() { nextRequest = std::move(lock->NextRequest); } - if(AssetsInWork.AssetsPacket.size()) { - toSend.push_back(std::move(AssetsInWork.AssetsPacket)); + if(!AssetsInWork.AssetsPackets.empty()) { + for(Net::Packet& packet : AssetsInWork.AssetsPackets) + toSend.push_back(std::move(packet)); + AssetsInWork.AssetsPackets.clear(); } + if(AssetsInWork.AssetsPacket.size()) + toSend.push_back(std::move(AssetsInWork.AssetsPacket)); { Net::Packet p; @@ -508,6 +531,7 @@ ResourceRequest RemoteClient::pushPreparedPackets() { void RemoteClient::informateAssets(const std::vector>& resources) { std::vector> newForClient; + static std::atomic debugSendLogCount = 0; for(auto& [type, resId, domain, key, resource] : resources) { auto hash = resource.hash(); @@ -526,6 +550,22 @@ void RemoteClient::informateAssets(const std::vector RemoteClient::rP_System(Net::AsyncSocket &sock) { } case ToServer::L2System::ResourceRequest: { + static std::atomic debugRequestLogCount = 0; uint16_t count = co_await sock.read(); std::vector hashes; hashes.reserve(count); @@ -733,6 +774,29 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) { auto lock = NetworkAndResource.lock(); lock->NextRequest.Hashes.append_range(hashes); lock->ClientRequested.append_range(hashes); + + if(debugRequestLogCount.fetch_add(1) < 64) { + if(!hashes.empty()) { + const auto& h = hashes.front(); + LOG.debug() << "ResourceRequest count=" << count + << " first=" << int(h[0]) << '.' + << int(h[1]) << '.' + << int(h[2]) << '.' + << int(h[3]); + } else { + LOG.debug() << "ResourceRequest count=" << count; + } + } + co_return; + } + case ToServer::L2System::ReloadMods: + { + if(Server) { + Server->requestModsReload(); + LOG.info() << "Запрос на перезагрузку модов"; + } else { + LOG.warn() << "Запрос на перезагрузку модов отклонён: сервер не назначен"; + } co_return; } default: @@ -818,24 +882,59 @@ void RemoteClient::onUpdate() { // Отправка ресурсов if(!AssetsInWork.ToSend.empty()) { auto& toSend = AssetsInWork.ToSend; + constexpr uint16_t kMaxAssetPacketSize = 64000; + const size_t maxChunkPayload = std::max(1, kMaxAssetPacketSize - 1 - 1 - 32 - 4); size_t chunkSize = std::max(1'024'000 / toSend.size(), 4096); + chunkSize = std::min(chunkSize, maxChunkPayload); + static std::atomic debugInitSendLogCount = 0; Net::Packet& p = AssetsInWork.AssetsPacket; + auto flushAssetsPacket = [&]() { + if(p.size() == 0) + return; + AssetsInWork.AssetsPackets.push_back(std::move(p)); + }; + bool hasFullSended = false; for(auto& [type, domain, key, id, res, sended] : toSend) { if(sended == 0) { // Оповещаем о начале отправки ресурса + const size_t initSize = 1 + 1 + 4 + 32 + 4 + 1 + + 2 + domain.size() + + 2 + key.size(); + if(p.size() + initSize > kMaxAssetPacketSize) + flushAssetsPacket(); p << (uint8_t) ToClient::L1::Resource << (uint8_t) ToClient::L2Resource::InitResSend << uint32_t(res.size()); p.write((const std::byte*) res.hash().data(), 32); p << uint32_t(id) << uint8_t(type) << domain << key; + if(domain == "test" + && (type == EnumAssets::Nodestate + || type == EnumAssets::Model + || type == EnumAssets::Texture)) + { + if(debugInitSendLogCount.fetch_add(1) < 64) { + const auto hash = res.hash(); + LOG.debug() << "Send InitResSend type=" << assetTypeName(type) + << " id=" << id + << " key=" << domain << ':' << key + << " size=" << res.size() + << " hash=" << int(hash[0]) << '.' + << int(hash[1]) << '.' + << int(hash[2]) << '.' + << int(hash[3]); + } + } } // Отправляем чанк size_t willSend = std::min(chunkSize, res.size()-sended); + const size_t chunkMsgSize = 1 + 1 + 32 + 4 + willSend; + if(p.size() + chunkMsgSize > kMaxAssetPacketSize) + flushAssetsPacket(); p << (uint8_t) ToClient::L1::Resource << (uint8_t) ToClient::L2Resource::ChunkSend; p.write((const std::byte*) res.hash().data(), 32); @@ -862,4 +961,4 @@ std::vector> RemoteClient::getViewPo return {{0, CameraPos, 1}}; } -} \ No newline at end of file +} diff --git a/Src/Server/RemoteClient.hpp b/Src/Server/RemoteClient.hpp index 3c999b1..9ea0102 100644 --- a/Src/Server/RemoteClient.hpp +++ b/Src/Server/RemoteClient.hpp @@ -17,6 +17,7 @@ namespace LV::Server { class World; +class GameServer; template= sizeof(ClientKey), int> = 0> class CSChunkedMapper { @@ -316,6 +317,7 @@ class RemoteClient { // Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт std::vector> ToSend; // Пакет с ресурсами + std::vector AssetsPackets; Net::Packet AssetsPacket; } AssetsInWork; @@ -336,8 +338,8 @@ public: std::queue Build, Break; public: - RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username) - : LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username) + RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, GameServer* server) + : LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), Server(server) {} ~RemoteClient(); @@ -434,6 +436,7 @@ public: void onUpdate(); private: + GameServer* Server = nullptr; void protocolError(); coro<> readPacket(Net::AsyncSocket &sock); coro<> rP_System(Net::AsyncSocket &sock); @@ -447,4 +450,4 @@ private: }; -} \ No newline at end of file +} diff --git a/assets/shaders/chunk/node_opaque.frag b/assets/shaders/chunk/node_opaque.frag index b32a60c..0bb5042 100644 --- a/assets/shaders/chunk/node_opaque.frag +++ b/assets/shaders/chunk/node_opaque.frag @@ -58,6 +58,7 @@ void main() { Frame = atlasColor(Fragment.Texture, Fragment.UV); Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8)))); // Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w); + if(Frame.w == 0) discard; }