From 9c64b893cfd2da69b8d004be98ea7f71fbcf1609 Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Sat, 12 Jul 2025 03:27:20 +0600 Subject: [PATCH] * --- CMakeLists.txt | 7 +- Src/Client/Abstract.hpp | 6 +- Src/Client/ServerSession.cpp | 45 ++-- Src/Client/Vulkan/Vulkan.cpp | 10 +- Src/Client/Vulkan/VulkanRenderSession.cpp | 241 +++++++++++++--------- Src/Client/Vulkan/VulkanRenderSession.hpp | 17 +- Src/Common/Abstract.cpp | 89 ++++++-- Src/Common/Abstract.hpp | 8 +- Src/Common/Packets.hpp | 4 +- Src/Server/ContentEventController.cpp | 20 ++ Src/Server/ContentEventController.hpp | 18 +- Src/Server/GameServer.cpp | 94 ++++++--- Src/Server/GameServer.hpp | 8 +- Src/Server/RemoteClient.cpp | 16 +- Src/Server/RemoteClient.hpp | 2 + Src/TOSLib.hpp | 1 + Src/main.cpp | 4 +- assets/textures/0.png | Bin 0 -> 14212 bytes assets/textures/8.png | Bin 5940 -> 0 bytes assets/textures/grass.png | Bin 14212 -> 10253 bytes assets/textures/xnether_purple_wood.png | Bin 10253 -> 5940 bytes 21 files changed, 408 insertions(+), 182 deletions(-) create mode 100644 assets/textures/0.png delete mode 100644 assets/textures/8.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 29a37bc..bd2bd3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,8 @@ set(Boost_USE_STATIC_LIBS ON) set(BOOST_INCLUDE_LIBRARIES asio thread json) set(BOOST_ENABLE_CMAKE ON) +set(BOOST_IOSTREAMS_ENABLE_ZLIB ON) +set(BOOST_INCLUDE_LIBRARIES asio thread json iostreams) FetchContent_Declare( Boost URL https://github.com/boostorg/boost/releases/download/boost-1.87.0/boost-1.87.0-cmake.7z @@ -73,7 +75,7 @@ FetchContent_Declare( DOWNLOAD_NO_EXTRACT FALSE ) FetchContent_MakeAvailable(Boost) -target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json) +target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json Boost::iostreams) # glm # find_package(glm REQUIRED) @@ -116,7 +118,8 @@ target_link_libraries(luavox_common INTERFACE SQLite::SQLite3) # Static Assets file(GLOB_RECURSE ASSETS RELATIVE "${PROJECT_SOURCE_DIR}/assets" "assets/*.*") -add_custom_command(OUTPUT assets.o resources.cpp INPUT ${ASSETS} +file(GLOB_RECURSE ASSETS_A "${PROJECT_SOURCE_DIR}/assets" "assets/*.*") +add_custom_command(OUTPUT assets.o resources.cpp INPUT ${ASSETS_A} COMMAND cd ${CMAKE_CURRENT_BINARY_DIR} && ${CMAKE_CURRENT_SOURCE_DIR}/Src/assets.py ${ASSETS} COMMAND cd "${CMAKE_CURRENT_SOURCE_DIR}/assets" && ld -r -b binary -o '${CMAKE_CURRENT_BINARY_DIR}/assets.o' ${ASSETS} COMMAND objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents ${CMAKE_CURRENT_BINARY_DIR}/assets.o ${CMAKE_CURRENT_BINARY_DIR}/assets.o) diff --git a/Src/Client/Abstract.hpp b/Src/Client/Abstract.hpp index 29c6bf1..97b9f22 100644 --- a/Src/Client/Abstract.hpp +++ b/Src/Client/Abstract.hpp @@ -39,7 +39,7 @@ struct Chunk { // Кубы вокселей в чанке std::vector Voxels; // Ноды - Node Nodes[16][16][16]; + std::array Nodes; // Ограничения прохождения света, идущего от солнца (от верха карты до верхней плоскости чанка) // LightPrism Lights[16][16]; }; @@ -79,7 +79,7 @@ public: }; struct Region { - Chunk Chunks[4][4][4]; + std::array Chunks; }; struct World { @@ -172,7 +172,7 @@ public: } CursorMode = EnumCursorMoveMode::Default; enum struct EnumCursorBtn { - Left, Middle, Right, One, Two + Left, Right, Middle, One, Two }; public: diff --git a/Src/Client/ServerSession.cpp b/Src/Client/ServerSession.cpp index a5ca791..9087e2a 100644 --- a/Src/Client/ServerSession.cpp +++ b/Src/Client/ServerSession.cpp @@ -32,7 +32,7 @@ struct PP_Content_ChunkVoxels : public ParsedPacket { struct PP_Content_ChunkNodes : public ParsedPacket { WorldId_t Id; Pos::GlobalChunk Pos; - Node Nodes[16][16][16]; + std::array Nodes; PP_Content_ChunkNodes(ToClient::L1 l1, uint8_t l2, WorldId_t id, Pos::GlobalChunk pos) : ParsedPacket(l1, l2), Id(id), Pos(pos) @@ -195,8 +195,8 @@ void ServerSession::onCursorMove(float xMove, float yMove) { static constexpr float PI = glm::pi(), PI2 = PI*2, PI_HALF = PI/2, PI_DEG = PI/180; - deltaPYR.x = std::clamp(PYR.x + yMove*PI_DEG, -PI_HALF+PI_DEG, PI_HALF-PI_DEG)-PYR.x; - deltaPYR.y = std::fmod(PYR.y + xMove*PI_DEG, PI2)-PYR.y; + deltaPYR.x = std::clamp(PYR.x - yMove*PI_DEG, -PI_HALF+PI_DEG, PI_HALF-PI_DEG)-PYR.x; + deltaPYR.y = std::fmod(PYR.y - xMove*PI_DEG, PI2)-PYR.y; deltaPYR.z = 0; double gTime = GTime; @@ -208,7 +208,26 @@ void ServerSession::onCursorMove(float xMove, float yMove) { } void ServerSession::onCursorBtn(ISurfaceEventListener::EnumCursorBtn btn, bool state) { + if(!state) + return; + if(btn == EnumCursorBtn::Left) { + Net::Packet packet; + + packet << (uint8_t) ToServer::L1::System + << (uint8_t) ToServer::L2System::BlockChange + << uint8_t(0); + + Socket->pushPacket(std::move(packet)); + } else if(btn == EnumCursorBtn::Right) { + Net::Packet packet; + + packet << (uint8_t) ToServer::L1::System + << (uint8_t) ToServer::L2System::BlockChange + << uint8_t(1); + + Socket->pushPacket(std::move(packet)); + } } void ServerSession::onKeyboardBtn(int btn, int state) { @@ -255,9 +274,9 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) { if(Keys.CTRL) mltpl *= 16; - Speed += glm::vec3(rot*glm::vec4(0, 0, 1, 1)*float(Keys.W))*mltpl; + Speed += glm::vec3(rot*glm::vec4(0, 0, -1, 1)*float(Keys.W))*mltpl; Speed += glm::vec3(rot*glm::vec4(-1, 0, 0, 1)*float(Keys.A))*mltpl; - Speed += glm::vec3(rot*glm::vec4(0, 0, -1, 1)*float(Keys.S))*mltpl; + Speed += glm::vec3(rot*glm::vec4(0, 0, 1, 1)*float(Keys.S))*mltpl; Speed += glm::vec3(rot*glm::vec4(1, 0, 0, 1)*float(Keys.D))*mltpl; Speed += glm::vec3(0, -1, 0)*float(Keys.SHIFT)*mltpl; Speed += glm::vec3(0, 1, 0)*float(Keys.SPACE)*mltpl; @@ -275,7 +294,7 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) { Pos::GlobalRegion rPos = p.Pos >> 2; Pos::bvec4u cPos = p.Pos & 0x3; - Data.Worlds[p.Id].Regions[rPos].Chunks[cPos.x][cPos.y][cPos.z].Voxels = std::move(p.Cubes); + Data.Worlds[p.Id].Regions[rPos].Chunks[cPos.pack()].Voxels = std::move(p.Cubes); auto &pair = changeOrAddList_removeList[p.Id]; std::get<0>(pair).insert(p.Pos); @@ -284,8 +303,8 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) { Pos::GlobalRegion rPos = p.Pos >> 2; Pos::bvec4u cPos = p.Pos & 0x3; - Node *nodes = (Node*) Data.Worlds[p.Id].Regions[rPos].Chunks[cPos.x][cPos.y][cPos.z].Nodes; - std::copy((const Node*) p.Nodes, ((const Node*) p.Nodes)+16*16*16, nodes); + Node *nodes = (Node*) Data.Worlds[p.Id].Regions[rPos].Chunks[cPos.pack()].Nodes.data(); + std::copy(p.Nodes.begin(), p.Nodes.end(), nodes); auto &pair = changeOrAddList_removeList[p.Id]; std::get<0>(pair).insert(p.Pos); @@ -542,14 +561,14 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) { uint32_t compressedSize = co_await sock.read(); assert(compressedSize <= std::pow(2, 24)); std::u8string compressed(compressedSize, '\0'); - co_await sock.read((std::byte*) compressed.data(), compressedSize); + co_await sock.read((std::byte*) compressed.data(), compressedSize); PP_Content_ChunkVoxels *packet = new PP_Content_ChunkVoxels( ToClient::L1::Content, (uint8_t) ToClient::L2Content::ChunkVoxels, wcId, pos, - unCompressVoxels(compressed) + unCompressVoxels(compressed) // TODO: вынести в отдельный поток ); while(!NetInputPackets.push(packet)); @@ -566,7 +585,7 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) { uint32_t compressedSize = co_await sock.read(); assert(compressedSize <= std::pow(2, 24)); std::u8string compressed(compressedSize, '\0'); - co_await sock.read((std::byte*) compressed.data(), compressedSize); + co_await sock.read((std::byte*) compressed.data(), compressedSize); PP_Content_ChunkNodes *packet = new PP_Content_ChunkNodes( ToClient::L1::Content, @@ -575,7 +594,7 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) { pos ); - unCompressNodes(compressed, (Node*) packet->Nodes); + unCompressNodes(compressed, (Node*) packet->Nodes.data()); // TODO: вынести в отдельный поток while(!NetInputPackets.push(packet)); @@ -586,7 +605,7 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) { co_return; case ToClient::L2Content::RemoveRegion: { WorldId_t wcId = co_await sock.read(); - Pos::GlobalChunk pos; + Pos::GlobalRegion pos; pos.unpack(co_await sock.read()); PP_Content_RegionRemove *packet = new PP_Content_RegionRemove( diff --git a/Src/Client/Vulkan/Vulkan.cpp b/Src/Client/Vulkan/Vulkan.cpp index ffd93bb..b33e204 100644 --- a/Src/Client/Vulkan/Vulkan.cpp +++ b/Src/Client/Vulkan/Vulkan.cpp @@ -2081,7 +2081,7 @@ void Vulkan::gui_MainMenu() { ImGui::InputText("Username", ConnectionProgress.Username, sizeof(ConnectionProgress.Username)); ImGui::InputText("Password", ConnectionProgress.Password, sizeof(ConnectionProgress.Password), ImGuiInputTextFlags_Password); - static bool flag = false; + static bool flag = true; if(!flag) { flag = true; Game.Server = std::make_unique(IOC); @@ -2686,12 +2686,16 @@ Buffer::Buffer(Buffer &&obj) obj.Instance = nullptr; } -Buffer& Buffer::operator=(Buffer &&obj) -{ +Buffer& Buffer::operator=(Buffer &&obj) { + if(this == &obj) + return *this; + std::swap(Instance, obj.Instance); std::swap(Buff, obj.Buff); std::swap(Memory, obj.Memory); std::swap(Size, obj.Size); + obj.Instance = nullptr; + return *this; } diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index 4adb3ee..070b7b7 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -251,6 +251,13 @@ void VulkanRenderSession::init(Vulkan *instance) { array[15] = {135, 135, 135-64, 0, 0, 0, 0, 0, 0}; array[16] = {135+16, 135, 135-64-16, 0, 0, 0, 0, 65535, 65535}; array[17] = {135, 135, 135-64-16, 0, 0, 0, 0, 0, 65535}; + + for(int iter = 0; iter < 18; iter++) { + array[18+iter] = array[iter]; + array[18+iter].FZ -= 32; + } + + VKCTX->TestQuad.unMapMemory(); } @@ -431,7 +438,7 @@ void VulkanRenderSession::init(Vulkan *instance) { .flags = 0, .depthClampEnable = false, .rasterizerDiscardEnable = false, - .polygonMode = VK_POLYGON_MODE_LINE, + .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_BACK_BIT, .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .depthBiasEnable = false, @@ -636,6 +643,7 @@ void VulkanRenderSession::onContentDefinesLost(std::unordered_map& changeOrAddList, const std::unordered_set& remove) { auto &table = External.ChunkVoxelMesh[worldId]; @@ -645,7 +653,7 @@ void VulkanRenderSession::onChunksChange(WorldId_t worldId, const std::unordered auto &buffers = table[pos]; - const auto &chunk = ServerSession->Data.Worlds[worldId].Regions[rPos].Chunks[cPos.x][cPos.y][cPos.z]; + const auto &chunk = ServerSession->Data.Worlds[worldId].Regions[rPos].Chunks[cPos.pack()]; if(chunk.Voxels.empty()) { VKCTX->VertexPool_Voxels.dropVertexs(std::get<0>(buffers)); @@ -655,11 +663,12 @@ void VulkanRenderSession::onChunksChange(WorldId_t worldId, const std::unordered VKCTX->VertexPool_Voxels.relocate(voxels, std::move(vertexs)); } - std::vector vertexs2 = generateMeshForNodeChunks(chunk.Nodes); + std::vector vertexs2 = generateMeshForNodeChunks(chunk.Nodes.data()); if(vertexs2.empty()) { VKCTX->VertexPool_Nodes.dropVertexs(std::get<1>(buffers)); } else { + changed++; auto &nodes = std::get<1>(buffers); VKCTX->VertexPool_Nodes.relocate(nodes, std::move(vertexs2)); } @@ -669,6 +678,8 @@ void VulkanRenderSession::onChunksChange(WorldId_t worldId, const std::unordered if(iter != table.end()) table.erase(iter); } + + TOS::Logger("Vul").debug() << "Обработано " << changed; } for(Pos::GlobalRegion pos : remove) { @@ -704,7 +715,12 @@ void VulkanRenderSession::beforeDraw() { } void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd) { - + { + X64Offset = Pos & ~((1 << Pos::Object_t::BS_Bit << 4 << 2)-1); + X64Offset_f = glm::vec3(X64Offset) / float(Pos::Object_t::BS); + X64Delta = glm::vec3(Pos-X64Offset) / float(Pos::Object_t::BS); + } + // Сместить в координаты игрока, повернуть относительно взгляда проецировать на экран // Изначально взгляд в z-1 // PCO.ProjView = glm::mat4(1); @@ -800,16 +816,27 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff static float Delta = 0; Delta += dTime; - glm::mat4 projView = glm::perspective(glm::radians(75.f), float(VkInst->Screen.Width)/float(VkInst->Screen.Height), 0.5, std::pow(2, 17)); - projView[1][1] *= -1; - glm::mat4 rotate = glm::mat4(1); - rotate = glm::translate(rotate, {0, 0, -4}); - rotate = glm::rotate(rotate, 45.f/360*(2*glm::pi()), {1, 0, 0}); - rotate = glm::rotate(rotate, Delta/16*(2*glm::pi()), {0, 1, 0}); - rotate = glm::translate(rotate, {0, 0, 4}); - - PCO.ProjView = projView*rotate; PCO.Model = glm::mat4(1); + PCO.Model = glm::translate(PCO.Model, -X64Offset_f); + + { + glm::mat4 proj = glm::perspective(glm::radians(75.f), float(VkInst->Screen.Width)/float(VkInst->Screen.Height), 0.5, std::pow(2, 17)); + proj[1][1] *= -1; + + // Получили область рендера от левого верхнего угла + // x -1 -> 1; y 1 -> -1; z 0 -> -1 + // Правило левой руки + // Перед полигонов определяется обходом против часовой стрелки + + glm::mat4 view = glm::mat4(1); + // Смещаем мир относительно позиции игрока, чтобы игрок в пространстве рендера оказался в нулевых координатах + view = glm::translate(view, -X64Delta); + // Поворачиваем мир обратно взгляду игрока, чтобы его взгляд стал по направлению оси -z + view = glm::mat4(-Quat)*view; + + // Сначала применяется матрица вида, потом проекции + PCO.ProjView = proj*view; + } vkCmdBindPipeline(drawCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, NodeStaticOpaquePipeline); vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, @@ -818,11 +845,36 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff MainAtlas_LightMap_PipelineLayout, 0, 2, (const VkDescriptorSet[]) {MainAtlasDescriptor, VoxelLightMapDescriptor}, 0, nullptr); + PCO.Model = glm::mat4(1); VkBuffer vkBuffer = VKCTX->TestQuad; - VkDeviceSize vkOffset = 0; + VkDeviceSize vkOffsets = 0; - vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBuffer, &vkOffset); - vkCmdDraw(drawCmd, 6*3, 1, 0, 0); + vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBuffer, &vkOffsets); + vkCmdDraw(drawCmd, 6*3*2, 1, 0, 0); + + { + Pos::GlobalChunk x64offset = X64Offset >> Pos::Object_t::BS_Bit >> 4; + + auto iterWorld = External.ChunkVoxelMesh.find(WorldId); + if(iterWorld != External.ChunkVoxelMesh.end()) { + glm::mat4 orig = PCO.Model; + + for(auto &pair : iterWorld->second) { + if(auto& nodes = std::get<1>(pair.second)) { + glm::vec3 cpos(pair.first-x64offset); + PCO.Model = glm::translate(orig, cpos*16.f); + auto [vkBuffer, offset] = VKCTX->VertexPool_Nodes.map(nodes); + + vkCmdPushConstants(drawCmd, MainAtlas_LightMap_PipelineLayout, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT, offsetof(WorldPCO, Model), sizeof(WorldPCO::Model), &PCO.Model); + vkCmdBindVertexBuffers(drawCmd, 0, 1, &vkBuffer, &vkOffsets); + vkCmdDraw(drawCmd, nodes.VertexCount, 1, offset, 0); + } + } + + PCO.Model = orig; + } + } } std::vector VulkanRenderSession::generateMeshForVoxelChunks(const std::vector cubes) { @@ -912,7 +964,7 @@ std::vector VulkanRenderSession::generateMeshForVoxelChunks(co return out; } -std::vector VulkanRenderSession::generateMeshForNodeChunks(const Node nodes[16][16][16]) { +std::vector VulkanRenderSession::generateMeshForNodeChunks(const Node* nodes) { std::vector out; NodeVertexStatic v; @@ -920,15 +972,16 @@ std::vector VulkanRenderSession::generateMeshForNodeChunks(con for(int y = 0; y < 16; y++) for(int x = 0; x < 16; x++) { - if(nodes[x][y][z].Data == 0) + size_t index = Pos::bvec16u(x, y, z).pack(); + if(nodes[index].Data == 0) continue; - v.Tex = nodes[x][y][z].NodeId; + v.Tex = nodes[index].NodeId; - if((y+1) >= 16 || nodes[x][y+1][z].NodeId == 0) { + if((y+1) >= 16 || nodes[Pos::bvec16u(x, y+1, z).pack()].NodeId == 0) { v.FX = 135+x*16; v.FY = 135+y*16+16; - v.FZ = 135+z*16; + v.FZ = 135+z*16+16; v.TU = 0; v.TV = 0; out.push_back(v); @@ -937,18 +990,18 @@ std::vector VulkanRenderSession::generateMeshForNodeChunks(con v.TU = 65535; out.push_back(v); - v.FZ += 16; + v.FZ -= 16; v.TV = 65535; out.push_back(v); v.FX = 135+x*16; - v.FZ = 135+z*16; + v.FZ = 135+z*16+16; v.TU = 0; v.TV = 0; out.push_back(v); v.FX += 16; - v.FZ += 16; + v.FZ -= 16; v.TV = 65535; v.TU = 65535; out.push_back(v); @@ -958,106 +1011,73 @@ std::vector VulkanRenderSession::generateMeshForNodeChunks(con out.push_back(v); } - if((y-1) < 0 || nodes[x][y-1][z].NodeId == 0) { + if((y-1) < 0 || nodes[Pos::bvec16u(x, y-1, z).pack()].NodeId == 0) { v.FX = 135+x*16; v.FY = 135+y*16; - v.FZ = 135+z*16; + v.FZ = 135+z*16+16; v.TU = 0; v.TV = 0; out.push_back(v); - v.FZ += 16; - v.TV = 65535; - out.push_back(v); - - v.FX += 16; - v.TU = 65535; - out.push_back(v); - - v.FX = 135+x*16; - v.FZ = 135+z*16; - v.TU = 0; - v.TV = 0; - out.push_back(v); - - v.FX += 16; - v.FZ += 16; - v.TV = 65535; - v.TU = 65535; - out.push_back(v); - v.FZ -= 16; + v.TV = 65535; + out.push_back(v); + + v.FX += 16; + v.TU = 65535; + out.push_back(v); + + v.FX = 135+x*16; + v.FZ = 135+z*16+16; + v.TU = 0; + v.TV = 0; + out.push_back(v); + + v.FX += 16; + v.FZ -= 16; + v.TV = 65535; + v.TU = 65535; + out.push_back(v); + + v.FZ += 16; v.TV = 0; out.push_back(v); } - if((x+1) >= 16 || nodes[x+1][y][z].NodeId == 0) { + if((x+1) >= 16 || nodes[Pos::bvec16u(x+1, y, z).pack()].NodeId == 0) { v.FX = 135+x*16+16; v.FY = 135+y*16; - v.FZ = 135+z*16; + v.FZ = 135+z*16+16; v.TU = 0; v.TV = 0; out.push_back(v); - v.FZ += 16; - v.TV = 65535; - out.push_back(v); - - v.FY += 16; - v.TU = 65535; - out.push_back(v); - - v.FY = 135+y*16; - v.FZ = 135+z*16; - v.TU = 0; - v.TV = 0; - out.push_back(v); - - v.FY += 16; - v.FZ += 16; - v.TV = 65535; - v.TU = 65535; - out.push_back(v); - v.FZ -= 16; - v.TV = 0; - out.push_back(v); - } - - if((x-1) < 0 || nodes[x-1][y][z].NodeId == 0) { - v.FX = 135+x*16; - v.FY = 135+y*16; - v.FZ = 135+z*16; - v.TU = 0; - v.TV = 0; + v.TV = 65535; out.push_back(v); v.FY += 16; v.TU = 65535; out.push_back(v); - v.FZ += 16; - v.TV = 65535; - out.push_back(v); - v.FY = 135+y*16; - v.FZ = 135+z*16; + v.FZ = 135+z*16+16; v.TU = 0; v.TV = 0; out.push_back(v); v.FY += 16; - v.FZ += 16; + v.FZ -= 16; v.TV = 65535; v.TU = 65535; out.push_back(v); - v.FY -= 16; - v.TU = 0; + v.FZ += 16; + v.TV = 0; out.push_back(v); } - if((z+1) >= 16 || nodes[x][y][z+1].NodeId == 0) { + if((x-1) < 0 || nodes[Pos::bvec16u(x-1, y, z).pack()].NodeId == 0) { v.FX = 135+x*16; v.FY = 135+y*16; v.FZ = 135+z*16+16; @@ -1069,18 +1089,18 @@ std::vector VulkanRenderSession::generateMeshForNodeChunks(con v.TU = 65535; out.push_back(v); - v.FX += 16; + v.FZ -= 16; v.TV = 65535; out.push_back(v); - v.FX = 135+x*16; v.FY = 135+y*16; + v.FZ = 135+z*16+16; v.TU = 0; v.TV = 0; out.push_back(v); - v.FX += 16; v.FY += 16; + v.FZ -= 16; v.TV = 65535; v.TU = 65535; out.push_back(v); @@ -1090,20 +1110,20 @@ std::vector VulkanRenderSession::generateMeshForNodeChunks(con out.push_back(v); } - if((z-1) < 0 || nodes[x][y][z-1].NodeId == 0) { + if((z+1) >= 16 || nodes[Pos::bvec16u(x, y, z+1).pack()].NodeId == 0) { v.FX = 135+x*16; v.FY = 135+y*16; - v.FZ = 135+z*16; + v.FZ = 135+z*16+16; v.TU = 0; v.TV = 0; out.push_back(v); v.FX += 16; - v.TV = 65535; + v.TU = 65535; out.push_back(v); v.FY += 16; - v.TU = 65535; + v.TV = 65535; out.push_back(v); v.FX = 135+x*16; @@ -1119,6 +1139,39 @@ std::vector VulkanRenderSession::generateMeshForNodeChunks(con out.push_back(v); v.FX -= 16; + v.TU = 0; + out.push_back(v); + } + + if((z-1) < 0 || nodes[Pos::bvec16u(x, y, z-1).pack()].NodeId == 0) { + v.FX = 135+x*16; + v.FY = 135+y*16; + v.FZ = 135+z*16; + v.TU = 0; + v.TV = 0; + out.push_back(v); + + v.FY += 16; + v.TV = 65535; + out.push_back(v); + + v.FX += 16; + v.TU = 65535; + out.push_back(v); + + v.FX = 135+x*16; + v.FY = 135+y*16; + v.TU = 0; + v.TV = 0; + out.push_back(v); + + v.FX += 16; + v.FY += 16; + v.TV = 65535; + v.TU = 65535; + out.push_back(v); + + v.FY -= 16; v.TV = 0; out.push_back(v); } diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index 231c0b7..c34088a 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -48,6 +48,19 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { // Положение камеры WorldId_t WorldId; Pos::Object Pos; + /* + Графический конвейер оперирует числами с плавающей запятой + Для сохранения точности матрица модели хранит смещения близкие к нулю (X64Delta) + глобальные смещения на уровне региона исключаются из смещения ещё при задании матрицы модели + + X64Offset = позиция игрока на уровне регионов + X64Delta = позиция игрока в рамках региона + + Внутри графического конвейера будут числа приблежённые к 0 + */ + // Смещение дочерних объекто на стороне хоста перед рендером + Pos::Object X64Offset; + glm::vec3 X64Offset_f, X64Delta; // Смещение мира относительно игрока в матрице вида (0 -> 64) glm::quat Quat; struct VulkanContext { @@ -61,7 +74,7 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { VulkanContext(Vulkan *vkInst) : MainTest(vkInst), LightDummy(vkInst), - TestQuad(vkInst, sizeof(NodeVertexStatic)*6*3), + TestQuad(vkInst, sizeof(NodeVertexStatic)*6*3*2), VertexPool_Voxels(vkInst), VertexPool_Nodes(vkInst) {} @@ -143,7 +156,7 @@ public: void drawWorld(GlobalTime gTime, float dTime, VkCommandBuffer drawCmd); static std::vector generateMeshForVoxelChunks(const std::vector cubes); - static std::vector generateMeshForNodeChunks(const Node nodes[16][16][16]); + static std::vector generateMeshForNodeChunks(const Node* nodes); private: void updateDescriptor_MainAtlas(); diff --git a/Src/Common/Abstract.cpp b/Src/Common/Abstract.cpp index ae9a7ec..99b5c73 100644 --- a/Src/Common/Abstract.cpp +++ b/Src/Common/Abstract.cpp @@ -1,6 +1,10 @@ #include "Abstract.hpp" #include - +#include +#include +#include +#include +#include namespace LV { @@ -99,7 +103,7 @@ CompressedVoxels compressVoxels_byte(const std::vector& voxels) { } } - return {compressed, defines}; + return {compressLinear(compressed), defines}; } CompressedVoxels compressVoxels_bit(const std::vector& voxels) { @@ -245,7 +249,7 @@ CompressedVoxels compressVoxels_bit(const std::vector& voxels) { for(size_t iter = 0; iter < buff.size(); iter++) compressed[iter / 8] |= (buff[iter] << (iter % 8)); - return {compressed, profile}; + return {compressLinear(compressed), profile}; } CompressedVoxels compressVoxels(const std::vector& voxels, bool fast) { @@ -419,10 +423,11 @@ std::vector unCompressVoxels_bit(const std::u8string& compressed) { } std::vector unCompressVoxels(const std::u8string& compressed) { - if(compressed.front()) - return unCompressVoxels_byte(compressed); + const std::u8string& next = unCompressLinear(compressed); + if(next.front()) + return unCompressVoxels_byte(next); else - return unCompressVoxels_bit(compressed); + return unCompressVoxels_bit(next); } @@ -510,7 +515,7 @@ CompressedNodes compressNodes_byte(const Node* nodes) { profiles.shrink_to_fit(); - return {compressed, profiles}; + return {compressLinear(compressed), profiles}; } CompressedNodes compressNodes_bit(const Node* nodes) { @@ -628,15 +633,33 @@ CompressedNodes compressNodes_bit(const Node* nodes) { } } - return {compressed, profiles}; + return {compressLinear(compressed), profiles}; } CompressedNodes compressNodes(const Node* nodes, bool fast) { - if(fast) - return compressNodes_byte(nodes); - else - return compressNodes_bit(nodes); + std::u8string data(16*16*16*sizeof(Node), '\0'); + const char8_t *ptr = (const char8_t*) nodes; + std::copy(ptr, ptr+16*16*16*4, data.data()); + + std::vector node(16*16*16); + for(int iter = 0; iter < 16*16*16; iter++) { + node[iter] = nodes[iter].NodeId; + } + + { + std::sort(node.begin(), node.end()); + auto last = std::unique(node.begin(), node.end()); + node.erase(last, node.end()); + node.shrink_to_fit(); + } + + return {compressLinear(data), std::move(node)}; + + // if(fast) + // return compressNodes_byte(nodes); + // else + // return compressNodes_bit(nodes); } void unCompressNodes_byte(const std::u8string& compressed, Node* ptr) { @@ -761,10 +784,44 @@ void unCompressNodes_bit(const std::u8string& compressed, Node* ptr) { } void unCompressNodes(const std::u8string& compressed, Node* ptr) { - if(compressed.front()) - return unCompressNodes_byte(compressed, ptr); - else - return unCompressNodes_bit(compressed, ptr); + const std::u8string& next = unCompressLinear(compressed); + const Node *lPtr = (const Node*) next.data(); + std::copy(lPtr, lPtr+16*16*16, ptr); + + // if(next.front()) + // return unCompressNodes_byte(next, ptr); + // else + // return unCompressNodes_bit(next, ptr); +} + +std::u8string compressLinear(const std::u8string& data) { + std::stringstream in; + in.write((const char*) data.data(), data.size()); + + boost::iostreams::filtering_streambuf out; + out.push(boost::iostreams::zlib_compressor()); + out.push(in); + + std::stringstream compressed; + boost::iostreams::copy(out, compressed); + std::string outString = compressed.str(); + + return *(std::u8string*) &outString; +} + +std::u8string unCompressLinear(const std::u8string& data) { + std::stringstream in; + in.write((const char*) data.data(), data.size()); + + boost::iostreams::filtering_streambuf out; + out.push(boost::iostreams::zlib_decompressor()); + out.push(in); + + std::stringstream compressed; + boost::iostreams::copy(out, compressed); + std::string outString = compressed.str(); + + return *(std::u8string*) &outString; } } \ No newline at end of file diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index 6dd0017..ec53dcd 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -1,6 +1,7 @@ #pragma once #include "Common/Net.hpp" +#include "TOSLib.hpp" #include #include #include @@ -90,7 +91,7 @@ public: using U = std::make_unsigned_t; for(size_t iter = 0; iter < N; iter++) { - out |= Pack(U(get(iter))) << BitsPerComponent*iter; + out |= Pack(U(get(iter)) & U((Pack(1) << BitsPerComponent)-1)) << BitsPerComponent*iter; } return out; @@ -100,7 +101,7 @@ public: using U = std::make_unsigned_t; for(size_t iter = 0; iter < N; iter++) { - set(iter, U((pack >> BitsPerComponent*iter) & ((Pack(1) << BitsPerComponent)-1))); + set(iter, T(U((pack >> BitsPerComponent*iter) & U((Pack(1) << BitsPerComponent)-1)))); } } @@ -481,6 +482,9 @@ struct CompressedNodes { CompressedNodes compressNodes(const Node* nodes, bool fast = true); void unCompressNodes(const std::u8string& compressed, Node* ptr); +std::u8string compressLinear(const std::u8string& data); +std::u8string unCompressLinear(const std::u8string& data); + } diff --git a/Src/Common/Packets.hpp b/Src/Common/Packets.hpp index 8deb7bc..64d65b0 100644 --- a/Src/Common/Packets.hpp +++ b/Src/Common/Packets.hpp @@ -62,6 +62,7 @@ struct PacketQuat { 0 - 1 - 2 - Новая позиция камеры WorldId_c+ObjectPos+PacketQuat + 3 - Изменение блока */ @@ -74,7 +75,8 @@ enum struct L1 : uint8_t { enum struct L2System : uint8_t { InitEnd, Disconnect, - Test_CAM_PYR_POS + Test_CAM_PYR_POS, + BlockChange }; } diff --git a/Src/Server/ContentEventController.cpp b/Src/Server/ContentEventController.cpp index 5e17a2d..a0ab52a 100644 --- a/Src/Server/ContentEventController.cpp +++ b/Src/Server/ContentEventController.cpp @@ -107,6 +107,26 @@ void ContentEventController::onUpdate() { if(r1 != r2) { CrossedBorder = true; } + + if(!Remote->Actions.get_read().empty()) { + auto lock = Remote->Actions.lock(); + while(!lock->empty()) { + uint8_t action = lock->front(); + lock->pop(); + + Pos::GlobalNode pos = (Pos::GlobalNode) (glm::vec3) (glm::mat4(Remote->CameraQuat.toQuat())*glm::vec4(0, 0, -1, 1)); + pos = Pos.ObjectPos >> Pos::Object_t::BS_Bit; + + if(action == 0) { + // Break + Break.push(pos); + + } else if(action == 1) { + // Build + Build.push(pos); + } + } + } } } \ No newline at end of file diff --git a/Src/Server/ContentEventController.hpp b/Src/Server/ContentEventController.hpp index 139e55d..7b113d2 100644 --- a/Src/Server/ContentEventController.hpp +++ b/Src/Server/ContentEventController.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -57,10 +58,6 @@ struct ContentViewInfo { if(iterWorld == obj.Regions.end()) { out.WorldsNew.push_back(key); out.RegionsNew[key] = regions; - - for(const Pos::GlobalRegion& rp : regions) { - TOS::Logger("New").debug() << rp.x << ' ' << rp.y << ' ' << rp.z; - } } else { auto &vec = out.RegionsNew[key]; vec.reserve(8*8); @@ -69,10 +66,6 @@ struct ContentViewInfo { iterWorld->second.begin(), iterWorld->second.end(), std::back_inserter(vec) ); - - for(Pos::GlobalRegion& rp : vec) { - TOS::Logger("New").debug() << rp.x << ' ' << rp.y << ' ' << rp.z; - } } } @@ -83,10 +76,6 @@ struct ContentViewInfo { if(iterWorld == Regions.end()) { out.WorldsLost.push_back(key); out.RegionsLost[key] = regions; - - for(const Pos::GlobalRegion& rp : regions) { - TOS::Logger("Lost").debug() << rp.x << ' ' << rp.y << ' ' << rp.z; - } } else { auto &vec = out.RegionsLost[key]; vec.reserve(8*8); @@ -95,10 +84,6 @@ struct ContentViewInfo { iterWorld->second.begin(), iterWorld->second.end(), std::back_inserter(vec) ); - - for(Pos::GlobalRegion& rp : vec) { - TOS::Logger("Lost").debug() << rp.x << ' ' << rp.y << ' ' << rp.z; - } } } @@ -153,6 +138,7 @@ public: bool CrossedBorder = true; ServerObjectPos Pos, LastPos; + std::queue Build, Break; public: ContentEventController(std::unique_ptr&& remote); diff --git a/Src/Server/GameServer.cpp b/Src/Server/GameServer.cpp index 75fd472..fecf24b 100644 --- a/Src/Server/GameServer.cpp +++ b/Src/Server/GameServer.cpp @@ -29,18 +29,18 @@ GameServer::GameServer(asio::io_context &ioc, fs::path worldPath) { init(worldPath); - BackingChunkPressure.Threads.resize(1); + BackingChunkPressure.Threads.resize(4); BackingChunkPressure.Worlds = &Expanse.Worlds; for(size_t iter = 0; iter < BackingChunkPressure.Threads.size(); iter++) { BackingChunkPressure.Threads[iter] = std::thread(&BackingChunkPressure_t::run, &BackingChunkPressure, iter); } - BackingNoiseGenerator.Threads.resize(1); + BackingNoiseGenerator.Threads.resize(4); for(size_t iter = 0; iter < BackingNoiseGenerator.Threads.size(); iter++) { BackingNoiseGenerator.Threads[iter] = std::thread(&BackingNoiseGenerator_t::run, &BackingNoiseGenerator, iter); } - BackingAsyncLua.Threads.resize(2); + BackingAsyncLua.Threads.resize(4); for(size_t iter = 0; iter < BackingAsyncLua.Threads.size(); iter++) { BackingAsyncLua.Threads[iter] = std::thread(&BackingAsyncLua_t::run, &BackingAsyncLua, iter); } @@ -65,17 +65,23 @@ GameServer::~GameServer() { } void GameServer::BackingChunkPressure_t::run(int id) { + // static thread_local int local_counter = -1; + int iteration = 0; LOG.debug() << "Старт потока " << id; try { while(true) { + // local_counter++; + // LOG.debug() << "Ожидаю начала " << id << ' ' << local_counter; { std::unique_lock lock(Mutex); - Symaphore.wait(lock, [&](){ return RunCollect != 0 || NeedShutdown; }); + Symaphore.wait(lock, [&](){ return iteration != Iteration || NeedShutdown; }); if(NeedShutdown) { LOG.debug() << "Завершение выполнения потока " << id; break; } + + iteration = Iteration; } // Сбор данных @@ -97,20 +103,19 @@ void GameServer::BackingChunkPressure_t::run(int id) { for(const auto& [regionPos, region] : worldObj.Regions) { auto& regionObj = *region; - if(counter++ % pullSize != 0) { - counter %= pullSize; + if(counter++ % pullSize != id) { continue; } Dump dumpRegion; + dumpRegion.CECs = regionObj.CECs; dumpRegion.IsChunkChanged_Voxels = regionObj.IsChunkChanged_Voxels; regionObj.IsChunkChanged_Voxels = 0; dumpRegion.IsChunkChanged_Nodes = regionObj.IsChunkChanged_Nodes; regionObj.IsChunkChanged_Nodes = 0; if(!regionObj.NewCECs.empty()) { - dumpRegion.CECs = regionObj.CECs; dumpRegion.NewCECs = std::move(regionObj.NewCECs); dumpRegion.Voxels = regionObj.Voxels; @@ -123,9 +128,9 @@ void GameServer::BackingChunkPressure_t::run(int id) { std::copy(fromPtr, fromPtr+16*16*16, toPtr.data()); } } else { - if(regionObj.IsChunkChanged_Voxels) { + if(dumpRegion.IsChunkChanged_Voxels) { for(int index = 0; index < 64; index++) { - if((regionObj.IsChunkChanged_Voxels >> index) & 0x1) + if(((dumpRegion.IsChunkChanged_Voxels >> index) & 0x1) == 0) continue; Pos::bvec4u chunkPos; @@ -140,9 +145,9 @@ void GameServer::BackingChunkPressure_t::run(int id) { } } - if(regionObj.IsChunkChanged_Nodes) { + if(dumpRegion.IsChunkChanged_Nodes) { for(int index = 0; index < 64; index++) { - if((regionObj.IsChunkChanged_Nodes >> index) & 0x1) + if(((dumpRegion.IsChunkChanged_Nodes >> index) & 0x1) == 0) continue; Pos::bvec4u chunkPos; @@ -166,9 +171,10 @@ void GameServer::BackingChunkPressure_t::run(int id) { } // Синхронизация + // LOG.debug() << "Синхронизирую " << id << ' ' << local_counter; { std::unique_lock lock(Mutex); - RunCollect--; + RunCollect -= 1; Symaphore.notify_all(); } @@ -207,7 +213,7 @@ void GameServer::BackingChunkPressure_t::run(int id) { if((region.IsChunkChanged_Voxels >> chunkPos.pack()) & 0x1) { for(auto& ptr : region.CECs) { bool skip = false; - for(auto& ptr2 : region.CECs) { + for(auto& ptr2 : region.NewCECs) { if(ptr == ptr2) { skip = true; break; @@ -246,7 +252,7 @@ void GameServer::BackingChunkPressure_t::run(int id) { if((region.IsChunkChanged_Nodes >> chunkPos.pack()) & 0x1) { for(auto& ptr : region.CECs) { bool skip = false; - for(auto& ptr2 : region.CECs) { + for(auto& ptr2 : region.NewCECs) { if(ptr == ptr2) { skip = true; break; @@ -315,9 +321,10 @@ void GameServer::BackingChunkPressure_t::run(int id) { } // Синхронизация + // LOG.debug() << "Конец " << id << ' ' << local_counter; { std::unique_lock lock(Mutex); - RunCompress--; + RunCompress -= 1; Symaphore.notify_all(); } } @@ -364,7 +371,9 @@ void GameServer::BackingNoiseGenerator_t::run(int id) { for(int z = 0; z < 64; z++) for(int y = 0; y < 64; y++) for(int x = 0; x < 64; x++, ptr++) { - *ptr = TOS::genRand(); //glm::perlin(glm::vec3(posNode.x+x, posNode.y+y, posNode.z+z)); + // *ptr = TOS::genRand(); + *ptr = glm::perlin(glm::vec3(posNode.x+x, posNode.y+y, posNode.z+z) / 16.13f); + //*ptr = std::pow(*ptr, 0.75f)*1.5f; } Output.lock()->push_back({key, std::move(data)}); @@ -403,15 +412,17 @@ void GameServer::BackingAsyncLua_t::run(int id) { lock->pop(); } + //if(key.RegionPos == Pos::GlobalRegion(0, 0, 0)) { + float *ptr = noise.data(); for(int z = 0; z < 64; z++) for(int y = 0; y < 64; y++) - for(int x = 0; x < 64; x++) { - // DefVoxelId_t id = *ptr > 0.9 ? 1 : 0; + for(int x = 0; x < 64; x++, ptr++) { + DefVoxelId_t id = std::clamp(*ptr, 0.f, 1.f) * 3; //> 0.9 ? 1 : 0; Pos::bvec64u nodePos(x, y, z); auto &node = out.Nodes[Pos::bvec4u(nodePos >> 4).pack()][Pos::bvec16u(nodePos & 0xf).pack()]; - // node.NodeId = id; - // node.Meta = 0; + node.NodeId = id; + node.Meta = 0; if(x == 0 && z == 0) node.NodeId = 1; @@ -419,12 +430,21 @@ void GameServer::BackingAsyncLua_t::run(int id) { node.NodeId = 2; else if(x == 0 && y == 0) node.NodeId = 3; - else + + if(y == 1 && z == 0) + node.NodeId = 0; + else if(x == 0 && y == 1) node.NodeId = 0; - node.Meta = 0; + // node.Meta = 0; } - } + } + // else { + // Node *ptr = (Node*) &out.Nodes[0][0]; + // Node node; + // node.Data = 0; + // std::fill(ptr, ptr+64*64*64, node); + // } RegionOut.lock()->push_back({key, out}); } @@ -799,7 +819,7 @@ void GameServer::stepConnections() { } void GameServer::stepModInitializations() { - + BackingChunkPressure.endWithResults(); } IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() { @@ -1422,6 +1442,32 @@ void GameServer::stepGlobalStep() { void GameServer::stepSyncContent() { for(std::shared_ptr& cec : Game.CECs) { cec->onUpdate(); + + while(!cec->Build.empty()) { + Pos::GlobalNode node = cec->Build.front(); + cec->Build.pop(); + + Pos::GlobalRegion rPos = node >> 6; + Pos::bvec4u cPos = (node >> 4) & 0x3; + Pos::bvec16u nPos = node & 0xf; + + auto ®ion = Expanse.Worlds[0]->Regions[rPos]; + region->Nodes[cPos.pack()][nPos.pack()].NodeId = 4; + region->IsChunkChanged_Nodes |= 1ull << cPos.pack(); + } + + while(!cec->Break.empty()) { + Pos::GlobalNode node = cec->Break.front(); + cec->Break.pop(); + + Pos::GlobalRegion rPos = node >> 6; + Pos::bvec4u cPos = (node >> 4) & 0x3; + Pos::bvec16u nPos = node & 0xf; + + auto ®ion = Expanse.Worlds[0]->Regions[rPos]; + region->Nodes[cPos.pack()][nPos.pack()].NodeId = 0; + region->IsChunkChanged_Nodes |= 1ull << cPos.pack(); + } } // Оповещения о ресурсах и профилях diff --git a/Src/Server/GameServer.hpp b/Src/Server/GameServer.hpp index 608e2a2..301cc22 100644 --- a/Src/Server/GameServer.hpp +++ b/Src/Server/GameServer.hpp @@ -145,16 +145,18 @@ class GameServer : public AsyncObject { */ struct BackingChunkPressure_t { TOS::Logger LOG = "BackingChunkPressure"; - bool NeedShutdown = false; + volatile bool NeedShutdown = false; std::vector Threads; std::mutex Mutex; - int RunCollect = 0, RunCompress = 0; + volatile int RunCollect = 0, RunCompress = 0, Iteration = 0; std::condition_variable Symaphore; std::unordered_map> *Worlds; void startCollectChanges() { std::lock_guard lock(Mutex); - RunCompress = RunCollect = Threads.size(); + RunCollect = Threads.size(); + RunCompress = Threads.size(); + Iteration += 1; Symaphore.notify_all(); } diff --git a/Src/Server/RemoteClient.cpp b/Src/Server/RemoteClient.cpp index 452d706..c514b1e 100644 --- a/Src/Server/RemoteClient.cpp +++ b/Src/Server/RemoteClient.cpp @@ -105,7 +105,7 @@ bool RemoteClient::maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::Globa if(iterWorld != ResUses.RefChunk.end()) // Исключим зависимости предыдущей версии чанка { - auto iterRegion = iterWorld->second.find(chunkPos); + auto iterRegion = iterWorld->second.find(regionPos); if(iterRegion != iterWorld->second.end()) { // Уменьшим счётчик зависимостей for(const DefVoxelId_t& id : iterRegion->second[localChunk.pack()].Voxel) { @@ -128,6 +128,8 @@ bool RemoteClient::maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::Globa if(!newTypes.empty()) { // Добавляем новые типы в запрос NextRequest.Voxel.insert(NextRequest.Voxel.end(), newTypes.begin(), newTypes.end()); + for(DefVoxelId_t voxel : newTypes) + ResUses.RefDefVoxel[voxel] = {}; } if(!lostTypes.empty()) { @@ -181,7 +183,7 @@ bool RemoteClient::maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::Global if(iterWorld != ResUses.RefChunk.end()) // Исключим зависимости предыдущей версии чанка { - auto iterRegion = iterWorld->second.find(chunkPos); + auto iterRegion = iterWorld->second.find(regionPos); if(iterRegion != iterWorld->second.end()) { // Уменьшим счётчик зависимостей for(const DefNodeId_t& id : iterRegion->second[localChunk.pack()].Node) { @@ -204,6 +206,8 @@ bool RemoteClient::maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::Global if(!newTypes.empty()) { // Добавляем новые типы в запрос NextRequest.Node.insert(NextRequest.Node.end(), newTypes.begin(), newTypes.end()); + for(DefNodeId_t node : newTypes) + ResUses.RefDefNode[node] = {}; } if(!lostTypes.empty()) { @@ -259,6 +263,8 @@ void RemoteClient::prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regi } } } + + iterWorld->second.erase(iterRegion); } } @@ -675,6 +681,12 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) { co_return; } + case ToServer::L2System::BlockChange: + { + uint8_t action = co_await sock.read(); + Actions.lock()->push(action); + co_return; + } default: protocolError(); } diff --git a/Src/Server/RemoteClient.hpp b/Src/Server/RemoteClient.hpp index 1716c12..cf45b95 100644 --- a/Src/Server/RemoteClient.hpp +++ b/Src/Server/RemoteClient.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -296,6 +297,7 @@ public: const std::string Username; Pos::Object CameraPos = {0, 0, 0}; ToServer::PacketQuat CameraQuat = {0}; + TOS::SpinlockObject> Actions; public: RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, std::vector &&client_cache) diff --git a/Src/TOSLib.hpp b/Src/TOSLib.hpp index 0bb3dff..598a055 100644 --- a/Src/TOSLib.hpp +++ b/Src/TOSLib.hpp @@ -13,6 +13,7 @@ #define _USE_MATH_DEFINES #include #include +#include namespace TOS { diff --git a/Src/main.cpp b/Src/main.cpp index 1b9714d..657bba3 100644 --- a/Src/main.cpp +++ b/Src/main.cpp @@ -1,3 +1,4 @@ +#include "Common/Abstract.hpp" #include #include #include @@ -20,9 +21,10 @@ int main() { // LuaVox asio::io_context ioc; + Logger LOG = "main"; LV::Client::VK::Vulkan vkInst(ioc); - + ioc.run(); return 0; diff --git a/assets/textures/0.png b/assets/textures/0.png new file mode 100644 index 0000000000000000000000000000000000000000..4b48a99b87611000f95bb0cf6dd09a4aebf35e37 GIT binary patch literal 14212 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 literal 0 HcmV?d00001 diff --git a/assets/textures/8.png b/assets/textures/8.png deleted file mode 100644 index 6a989b8d2aa94ca8c23e4cd1fc43a1f832f68271..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5940 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 diff --git a/assets/textures/grass.png b/assets/textures/grass.png index 4b48a99b87611000f95bb0cf6dd09a4aebf35e37..3ec069ce303798e6c58657e59686b681b19d768c 100644 GIT binary patch literal 10253 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 T2w=;e00000NkvXXu0mjfFoh(3 literal 14212 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 diff --git a/assets/textures/xnether_purple_wood.png b/assets/textures/xnether_purple_wood.png index 3ec069ce303798e6c58657e59686b681b19d768c..6a989b8d2aa94ca8c23e4cd1fc43a1f832f68271 100644 GIT binary patch literal 5940 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 literal 10253 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 T2w=;e00000NkvXXu0mjfFoh(3