diff --git a/Src/Client/Abstract.hpp b/Src/Client/Abstract.hpp index 76f3864..1a78ec6 100644 --- a/Src/Client/Abstract.hpp +++ b/Src/Client/Abstract.hpp @@ -43,9 +43,12 @@ struct VoxelCube { Pos::bvec256u Left, Size; }; -struct Node { - DefNodeId_t NodeId; - uint8_t Rotate : 6; +union Node { + struct { + DefNodeId_t NodeId : 24, Meta : 8; + }; + + DefNodeId_t Data; }; // 16 метров ребро @@ -54,7 +57,7 @@ struct Chunk { // Кубы вокселей в чанке std::vector Voxels; // Ноды - std::unordered_map Nodes; + Node Nodes[16][16][16]; // Ограничения прохождения света, идущего от солнца (от верха карты до верхней плоскости чанка) // LightPrism Lights[16][16]; }; @@ -86,7 +89,7 @@ public: virtual void onContentDefinesLost(std::unordered_map>) = 0; // Сообщаем об изменившихся чанках - virtual void onChunksChange(WorldId_t worldId, const std::unordered_set &changeOrAddList, const std::unordered_set &remove) = 0; + virtual void onChunksChange(WorldId_t worldId, const std::unordered_set &changeOrAddList, const std::unordered_set &remove) = 0; // Установить позицию для камеры virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) = 0; @@ -94,7 +97,7 @@ public: }; struct Region { - std::unordered_map Chunks; + Chunk Chunks[4][4][4]; }; struct World { diff --git a/Src/Client/ServerSession.cpp b/Src/Client/ServerSession.cpp index 3b3f4ec..914bb17 100644 --- a/Src/Client/ServerSession.cpp +++ b/Src/Client/ServerSession.cpp @@ -29,11 +29,23 @@ struct PP_Content_ChunkVoxels : public ParsedPacket { {} }; -struct PP_Content_ChunkRemove : public ParsedPacket { +struct PP_Content_ChunkNodes : public ParsedPacket { WorldId_t Id; Pos::GlobalChunk Pos; + Node Nodes[16][16][16]; - PP_Content_ChunkRemove(ToClient::L1 l1, uint8_t l2, WorldId_t id, Pos::GlobalChunk pos) + PP_Content_ChunkNodes(ToClient::L1 l1, uint8_t l2, WorldId_t id, Pos::GlobalChunk pos, Node* nodes) + : ParsedPacket(l1, l2), Id(id), Pos(pos) + { + std::copy(nodes, nodes+16*16*16, (Node*) Nodes); + } +}; + +struct PP_Content_RegionRemove : public ParsedPacket { + WorldId_t Id; + Pos::GlobalRegion Pos; + + PP_Content_RegionRemove(ToClient::L1 l1, uint8_t l2, WorldId_t id, Pos::GlobalRegion pos) : ParsedPacket(l1, l2), Id(id), Pos(pos) {} }; @@ -252,7 +264,7 @@ void ServerSession::atFreeDrawTime(GlobalTime gTime, float dTime) { Speed += glm::vec3(0, 1, 0)*float(Keys.SPACE)*mltpl; { - std::unordered_map, std::unordered_set>> changeOrAddList_removeList; + std::unordered_map, std::unordered_set>> changeOrAddList_removeList; // Пакеты ParsedPacket *pack; @@ -264,19 +276,26 @@ 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].Voxels = std::move(p.Cubes); + Data.Worlds[p.Id].Regions[rPos].Chunks[cPos.x][cPos.y][cPos.z].Voxels = std::move(p.Cubes); auto &pair = changeOrAddList_removeList[p.Id]; std::get<0>(pair).insert(p.Pos); - } else if(l2 == ToClient::L2Content::RemoveChunk) { - PP_Content_ChunkRemove &p = *dynamic_cast(pack); - + } else if(l2 == ToClient::L2Content::ChunkNodes) { + PP_Content_ChunkNodes &p = *dynamic_cast(pack); Pos::GlobalRegion rPos = p.Pos >> 2; Pos::bvec4u cPos = p.Pos & 0x3; - auto &obj = Data.Worlds[p.Id].Regions[rPos].Chunks; - auto iter = obj.find(cPos); - if(iter != obj.end()) - obj.erase(iter); + + 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); + auto &pair = changeOrAddList_removeList[p.Id]; + std::get<0>(pair).insert(p.Pos); + } else if(l2 == ToClient::L2Content::RemoveRegion) { + PP_Content_RegionRemove &p = *dynamic_cast(pack); + + auto ®ions = Data.Worlds[p.Id].Regions; + auto obj = regions.find(p.Pos); + assert(obj != regions.end()); + regions.erase(obj); auto &pair = changeOrAddList_removeList[p.Id]; std::get<1>(pair).insert(p.Pos); @@ -514,11 +533,6 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) { pos.unpack(co_await sock.read()); std::vector cubes(co_await sock.read()); - uint16_t debugCubesCount = cubes.size(); - if(debugCubesCount > 1) { - int g = 0; - g++; - } for(size_t iter = 0; iter < cubes.size(); iter++) { VoxelCube &cube = cubes[iter]; @@ -545,19 +559,39 @@ coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) { } case ToClient::L2Content::ChunkNodes: - + { + WorldId_t wcId = co_await sock.read(); + Pos::GlobalChunk pos; + pos.unpack(co_await sock.read()); + std::array nodes; + + for(Node& node : nodes) { + node.Data = co_await sock.read(); + } + + PP_Content_ChunkNodes *packet = new PP_Content_ChunkNodes( + ToClient::L1::Content, + (uint8_t) ToClient::L2Content::ChunkVoxels, + wcId, + pos, + nodes.data() + ); + + while(!NetInputPackets.push(packet)); + co_return; + } case ToClient::L2Content::ChunkLightPrism: co_return; - case ToClient::L2Content::RemoveChunk: { + case ToClient::L2Content::RemoveRegion: { WorldId_t wcId = co_await sock.read(); Pos::GlobalChunk pos; - pos.unpack(co_await sock.read()); + pos.unpack(co_await sock.read()); - PP_Content_ChunkRemove *packet = new PP_Content_ChunkRemove( + PP_Content_RegionRemove *packet = new PP_Content_RegionRemove( ToClient::L1::Content, - (uint8_t) ToClient::L2Content::RemoveChunk, + (uint8_t) ToClient::L2Content::RemoveRegion, wcId, pos ); diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index a43d777..6603b63 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -611,14 +611,14 @@ void VulkanRenderSession::onContentDefinesLost(std::unordered_map &changeOrAddList, const std::unordered_set &remove) { +void VulkanRenderSession::onChunksChange(WorldId_t worldId, const std::unordered_set& changeOrAddList, const std::unordered_set& remove) { auto &table = External.ChunkVoxelMesh[worldId]; for(Pos::GlobalChunk pos : changeOrAddList) { Pos::GlobalRegion rPos = pos >> 4; Pos::bvec16u cPos = pos & 0xf; - const auto &voxels = ServerSession->Data.Worlds[worldId].Regions[rPos].Chunks[cPos].Voxels; + const auto &voxels = ServerSession->Data.Worlds[worldId].Regions[rPos].Chunks[cPos.x][cPos.y][cPos.z].Voxels; if(voxels.empty()) { auto iter = table.find(pos); @@ -639,10 +639,14 @@ auto &table = External.ChunkVoxelMesh[worldId]; } } - for(Pos::GlobalChunk pos : remove) { - auto iter = table.find(pos); - if(iter != table.end()) - table.erase(iter); + for(Pos::GlobalRegion pos : remove) { + for(int z = 0; z < 4; z++) + for(int y = 0; y < 4; y++) + for(int x = 0; x < 4; x++) { + auto iter = table.find((Pos::GlobalChunk(pos) << 2) + Pos::GlobalChunk(x, y, z)); + if(iter != table.end()) + table.erase(iter); + } } if(table.empty()) diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index b5ac284..a4145d0 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -155,7 +155,7 @@ public: virtual void onBinaryResourceLost(std::unordered_map>) override; virtual void onContentDefinesAdd(std::unordered_map>) override; virtual void onContentDefinesLost(std::unordered_map>) override; - virtual void onChunksChange(WorldId_t worldId, const std::unordered_set &changeOrAddList, const std::unordered_set &remove) override; + virtual void onChunksChange(WorldId_t worldId, const std::unordered_set& changeOrAddList, const std::unordered_set& remove) override; virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) override; glm::mat4 calcViewMatrix(glm::quat quat, glm::vec3 camOffset = glm::vec3(0)) { diff --git a/Src/Common/Abstract.cpp b/Src/Common/Abstract.cpp new file mode 100644 index 0000000..0bffc87 --- /dev/null +++ b/Src/Common/Abstract.cpp @@ -0,0 +1,763 @@ +#include "Abstract.hpp" +#include + + + +namespace LV { + + +CompressedVoxels compressVoxels_byte(const std::vector& voxels) { + std::u8string compressed; + std::vector defines; + DefVoxelId_t maxValue = 0; + defines.reserve(voxels.size()); + + compressed.push_back(1); + + assert(voxels.size() <= 65535); + + for(const VoxelCube& cube : voxels) { + defines.push_back(cube.VoxelId); + if(cube.VoxelId > maxValue) + maxValue = cube.VoxelId; + } + + { + std::sort(defines.begin(), defines.end()); + auto last = std::unique(defines.begin(), defines.end()); + defines.erase(last, defines.end()); + defines.shrink_to_fit(); + } + + // Количество байт на идентификатор в сыром виде + uint8_t bytes_raw = std::ceil(std::log2(maxValue)/8); + assert(bytes_raw >= 1 && bytes_raw <= 3); + // Количество байт без таблицы индексов + size_t size_in_raw = (bytes_raw+6)*voxels.size(); + // Количество байт на индекс + uint8_t bytes_per_define = std::ceil(std::log2(defines.size())/8); + assert(bytes_per_define == 1 || bytes_per_define == 2); + // Количество байт после таблицы индексов + size_t size_after_indices = (bytes_per_define+6)*voxels.size(); + // Размер таблицы индексов + size_t indeces_size = 2+bytes_raw*defines.size(); + + if(indeces_size+size_after_indices < size_in_raw) { + // Выгодней писать с таблицей индексов + + // Индексы, размер идентификатора ключа к таблице, размер значения таблицы + compressed.push_back(1 | (bytes_per_define << 1) | (bytes_raw << 2)); + compressed.push_back(defines.size() & 0xff); + compressed.push_back((defines.size() >> 8) & 0xff); + + // Таблица + for(DefVoxelId_t id : defines) { + compressed.push_back(id & 0xff); + if(bytes_raw > 1) + compressed.push_back((id >> 8) & 0xff); + if(bytes_raw > 2) + compressed.push_back((id >> 16) & 0xff); + } + + compressed.push_back(voxels.size() & 0xff); + compressed.push_back((voxels.size() >> 8) & 0xff); + + for(const VoxelCube& cube : voxels) { + size_t index = std::binary_search(defines.begin(), defines.end(), cube.VoxelId); + compressed.push_back(index & 0xff); + if(bytes_per_define > 1) + compressed.push_back((index >> 8) & 0xff); + + compressed.push_back(cube.Meta); + compressed.push_back(cube.Pos.x); + compressed.push_back(cube.Pos.y); + compressed.push_back(cube.Pos.z); + compressed.push_back(cube.Size.x); + compressed.push_back(cube.Size.y); + compressed.push_back(cube.Size.z); + } + } else { + compressed.push_back(0 | (0 << 1) | (bytes_raw << 2)); + + compressed.push_back(voxels.size() & 0xff); + compressed.push_back((voxels.size() >> 8) & 0xff); + + for(const VoxelCube& cube : voxels) { + compressed.push_back(cube.VoxelId & 0xff); + if(bytes_raw > 1) + compressed.push_back((cube.VoxelId >> 8) & 0xff); + if(bytes_raw > 2) + compressed.push_back((cube.VoxelId >> 16) & 0xff); + + compressed.push_back(cube.Meta); + compressed.push_back(cube.Pos.x); + compressed.push_back(cube.Pos.y); + compressed.push_back(cube.Pos.z); + compressed.push_back(cube.Size.x); + compressed.push_back(cube.Size.y); + compressed.push_back(cube.Size.z); + } + } + + return {compressed, defines}; +} + +CompressedVoxels compressVoxels_bit(const std::vector& voxels) { + std::vector profile; + std::vector one_byte[7]; + + DefVoxelId_t maxValueProfile = 0; + DefVoxelId_t maxValues[7] = {0}; + + profile.reserve(voxels.size()); + for(int iter = 0; iter < 7; iter++) + one_byte[iter].reserve(voxels.size()); + + assert(voxels.size() <= 65535); + for(const VoxelCube& cube : voxels) { + profile.push_back(cube.VoxelId); + + one_byte[0].push_back(cube.Meta); + one_byte[1].push_back(cube.Pos.x); + one_byte[2].push_back(cube.Pos.y); + one_byte[3].push_back(cube.Pos.z); + one_byte[4].push_back(cube.Size.x); + one_byte[5].push_back(cube.Size.y); + one_byte[6].push_back(cube.Size.z); + + if(cube.VoxelId > maxValueProfile) + maxValueProfile = cube.VoxelId; + if(cube.Meta > maxValues[0]) + maxValues[0] = cube.Meta; + if(cube.Pos.x > maxValues[1]) + maxValues[1] = cube.Pos.x; + if(cube.Pos.y > maxValues[2]) + maxValues[2] = cube.Pos.y; + if(cube.Pos.z > maxValues[3]) + maxValues[3] = cube.Pos.z; + if(cube.Size.x > maxValues[4]) + maxValues[4] = cube.Size.x; + if(cube.Size.y > maxValues[5]) + maxValues[5] = cube.Size.y; + if(cube.Size.z > maxValues[6]) + maxValues[6] = cube.Size.z; + } + + { + std::sort(profile.begin(), profile.end()); + auto last = std::unique(profile.begin(), profile.end()); + profile.erase(last, profile.end()); + profile.shrink_to_fit(); + } + + for (int iter = 0; iter < 7; iter++) { + std::sort(one_byte[iter].begin(), one_byte[iter].end()); + auto last = std::unique(one_byte[iter].begin(), one_byte[iter].end()); + one_byte[iter].erase(last, one_byte[iter].end()); + } + + // Количество бит на идентификатор в сыром виде + size_t bits_raw_profile = std::ceil(std::log2(maxValueProfile)); + assert(bits_raw_profile >= 1 && bits_raw_profile <= 24); + size_t bits_index_profile = std::ceil(std::log2(profile.size())); + bool indices_profile = 16+bits_raw_profile*profile.size()+bits_index_profile*voxels.size() < bits_raw_profile*voxels.size(); + + size_t bits_raw[7]; + size_t bits_index[7]; + bool indices[7]; + + for(int iter = 0; iter < 7; iter++) { + bits_raw[iter] = std::ceil(std::log2(maxValues[iter])); + assert(bits_raw[iter] >= 1 && bits_raw[iter] <= 8); + bits_index[iter] = std::ceil(std::log2(one_byte[iter].size())); + } + + std::vector buff; + + buff.push_back(indices_profile); + for(int iter = 0; iter < 7; iter++) + buff.push_back(indices[iter]); + + auto write = [&](size_t value, int count) { + for(int iter = 0; iter < count; iter++) + buff.push_back((value >> iter) & 0x1); + }; + + write(0, 8); + + // Таблицы + if(indices_profile) { + write(profile.size(), 16); + write(bits_raw_profile, 5); + write(bits_index_profile, 4); + + for(DefNodeId_t id : profile) + write(id, bits_raw_profile); + } else { + write(bits_raw_profile, 5); + } + + for(int iter = 0; iter < 7; iter++) { + if(indices[iter]) { + write(one_byte[iter].size(), 16); + write(bits_raw[iter], 3); + write(bits_index[iter], 3); + + for(uint8_t id : one_byte[iter]) + write(id, bits_raw[iter]); + } else { + write(bits_raw[iter], 3); + } + } + + // Данные + + write(voxels.size(), 16); + + for(const VoxelCube& cube : voxels) { + if(indices_profile) + write(std::binary_search(profile.begin(), profile.end(), cube.VoxelId), bits_index_profile); + else + write(cube.VoxelId, bits_raw_profile); + + for(int iter = 0; iter < 7; iter++) { + uint8_t val; + if(iter == 0) val = cube.Meta; + else if(iter == 1) val = cube.Pos.x; + else if(iter == 2) val = cube.Pos.y; + else if(iter == 3) val = cube.Pos.z; + else if(iter == 4) val = cube.Size.x; + else if(iter == 5) val = cube.Size.y; + else if(iter == 6) val = cube.Size.z; + + if(indices[iter]) + write(std::binary_search(one_byte[iter].begin(), one_byte[iter].end(), val), bits_index[iter]); + else + write(val, bits_raw[iter]); + } + } + + std::u8string compressed((buff.size()+7)/8, '\0'); + + for(int begin = 0, end = compressed.size()*8-buff.size(); begin < end; begin++) + compressed.push_back(0); + + for(size_t iter = 0; iter < buff.size(); iter++) + compressed[iter / 8] |= (buff[iter] << (iter % 8)); + + return {compressed, profile}; +} + +CompressedVoxels compressVoxels(const std::vector& voxels, bool fast) { + if(fast) + return compressVoxels_byte(voxels); + else + return compressVoxels_bit(voxels); +} + +std::vector unCompressVoxels_byte(const std::u8string& compressed) { + size_t pos = 1; + + auto read = [&]() -> size_t { + assert(pos < compressed.size()); + return compressed[pos++]; + }; + + uint8_t cmd = read(); + + if(cmd & 0x1) { + // Таблица + uint8_t bytes_per_define = (cmd >> 1) & 0x1; + uint8_t bytes_raw = (cmd >> 2) & 0x3; + + std::vector defines; + defines.resize(read() | (read() << 8)); + + for(size_t iter = 0; iter < defines.size(); iter++) { + DefVoxelId_t id = read(); + if(bytes_raw > 1) + id |= read() << 8; + if(bytes_raw > 2) + id |= read() << 16; + } + + std::vector voxels; + voxels.resize(read() | (read() << 8)); + + for(size_t iter = 0; iter < voxels.size(); iter++) { + size_t index = read(); + + if(bytes_per_define > 1) + index |= read() << 8; + + VoxelCube &cube = voxels[iter]; + + assert(index < defines.size()); + + cube.VoxelId = defines[index]; + cube.Meta = read(); + cube.Pos.x = read(); + cube.Pos.y = read(); + cube.Pos.z = read(); + cube.Size.x = read(); + cube.Size.y = read(); + cube.Size.z = read(); + } + + return voxels; + } else { + uint8_t bytes_raw = (cmd >> 2) & 0x3; + + std::vector voxels; + voxels.resize(read() | (read() << 8)); + + for(size_t iter = 0; iter < voxels.size(); iter++) { + VoxelCube &cube = voxels[iter]; + + cube.VoxelId = read(); + if(bytes_raw > 1) + cube.VoxelId |= read() << 8; + if(bytes_raw > 2) + cube.VoxelId |= read() << 16; + + cube.Meta = read(); + cube.Pos.x = read(); + cube.Pos.y = read(); + cube.Pos.z = read(); + cube.Size.x = read(); + cube.Size.y = read(); + cube.Size.z = read(); + } + + return voxels; + } +} + +std::vector unCompressVoxels_bit(const std::u8string& compressed) { + size_t pos = 1; + + auto read = [&](int bits) -> size_t { + size_t out = 0; + for(int iter = 0; iter < bits; iter++, pos++) { + assert(pos < compressed.size()*8); + + out |= (compressed[pos / 8] >> (pos % 8)) << iter; + } + + return out; + }; + + bool indices_profile = read(1); + bool indices[7]; + + for(int iter = 0; iter < 7; iter++) + indices[iter] = read(1); + + std::vector profile; + std::vector one_byte[7]; + uint8_t bits_raw_profile; + uint8_t bits_index_profile; + size_t bits_raw[7]; + size_t bits_index[7]; + + // Таблицы + if(indices_profile) { + profile.resize(read(16)); + bits_raw_profile = read(5); + bits_index_profile = read(4); + + for(size_t iter = 0; iter < profile.size(); iter++) + profile[iter] = read(bits_raw_profile); + } else { + bits_raw_profile = read(5); + } + + for(int iter = 0; iter < 7; iter++) { + if(indices[iter]) { + one_byte[iter].resize(read(16)); + bits_raw[iter] = read(3); + bits_index[iter] = read(3); + + for(size_t iter2 = 0; iter2 < one_byte[iter].size(); iter2++) + one_byte[iter][iter2] = read(bits_raw[iter]); + } else { + bits_raw[iter] = read(3); + } + } + + // Данные + std::vector voxels; + voxels.resize(read(16)); + + for(size_t iter = 0; iter < voxels.size(); iter++) { + VoxelCube &cube = voxels[iter]; + + if(indices_profile) + cube.VoxelId = profile[read(bits_index_profile)]; + else + cube.VoxelId = read(bits_raw_profile); + + for(int iter = 0; iter < 7; iter++) { + uint8_t val; + + if(indices[iter]) + val = one_byte[iter][read(bits_index[iter])]; + else + val = read(bits_raw[iter]); + + if(iter == 0) cube.Meta = val; + else if(iter == 1) cube.Pos.x = val; + else if(iter == 2) cube.Pos.y = val; + else if(iter == 3) cube.Pos.z = val; + else if(iter == 4) cube.Size.x = val; + else if(iter == 5) cube.Size.y = val; + else if(iter == 6) cube.Size.z = val; + } + } + + return voxels; +} + +std::vector unCompressVoxels(const std::u8string& compressed) { + if(compressed.front()) + return unCompressVoxels_byte(compressed); + else + return unCompressVoxels_bit(compressed); +} + + + +CompressedNodes compressNodes_byte(const Node* nodes) { + std::u8string compressed; + + std::vector profiles; + + profiles.reserve(16*16*16); + + compressed.push_back(1); + + DefNodeId_t maxValueProfile = 0; + + for(size_t iter = 0; iter < 16*16*16; iter++) { + const Node &node = nodes[iter]; + + profiles.push_back(node.NodeId); + + if(node.NodeId > maxValueProfile) + maxValueProfile = node.NodeId; + } + + { + std::sort(profiles.begin(), profiles.end()); + auto last = std::unique(profiles.begin(), profiles.end()); + profiles.erase(last, profiles.end()); + profiles.shrink_to_fit(); + } + + + // Количество байт на идентификатор в сыром виде + uint8_t bytes_raw_profile = std::ceil(std::log2(maxValueProfile)/8); + assert(bytes_raw_profile >= 1 && bytes_raw_profile <= 3); + // Количество байт на индекс + uint8_t bytes_indices_profile = std::ceil(std::log2(profiles.size())/8); + assert(bytes_indices_profile >= 1 && bytes_indices_profile <= 2); + + bool indices_profile = 3+bytes_raw_profile*profiles.size()+bytes_indices_profile*16*16*16 < bytes_raw_profile*16*16*16; + + compressed.push_back(indices_profile | (bytes_raw_profile << 1) | (bytes_indices_profile << 3)); + + if(indices_profile) { + // Таблица + compressed.push_back(profiles.size() & 0xff); + compressed.push_back((profiles.size() >> 8) & 0xff); + compressed.push_back((profiles.size() >> 16) & 0xff); + + for(DefNodeId_t id : profiles) { + compressed.push_back(id & 0xff); + if(bytes_raw_profile > 1) + compressed.push_back((id >> 8) & 0xff); + if(bytes_raw_profile > 2) + compressed.push_back((id >> 16) & 0xff); + } + + // Данные + for(size_t iter = 0; iter < 16*16*16; iter++) { + const Node &node = nodes[iter]; + + size_t index = std::binary_search(profiles.begin(), profiles.end(), node.NodeId); + compressed.push_back(index & 0xff); + if(bytes_indices_profile > 1) + compressed.push_back((index >> 8) & 0xff); + + compressed.push_back(node.Meta); + } + } else { + for(size_t iter = 0; iter < 16*16*16; iter++) { + const Node &node = nodes[iter]; + + compressed.push_back(node.NodeId & 0xff); + if(bytes_raw_profile > 1) + compressed.push_back((node.NodeId >> 8) & 0xff); + if(bytes_raw_profile > 2) + compressed.push_back((node.NodeId >> 8) & 0xff); + + compressed.push_back(node.Meta); + } + } + + profiles.shrink_to_fit(); + + return {compressed, profiles}; +} + +CompressedNodes compressNodes_bit(const Node* nodes) { + std::u8string compressed; + + std::vector profiles; + std::vector meta; + + profiles.reserve(16*16*16); + meta.reserve(16*16*16); + + compressed.push_back(1); + + DefNodeId_t maxValueProfile = 0, + maxValueMeta = 0; + + for(size_t iter = 0; iter < 16*16*16; iter++) { + const Node &node = nodes[iter]; + + profiles.push_back(node.NodeId); + meta.push_back(node.Meta); + + if(node.NodeId > maxValueProfile) + maxValueProfile = node.NodeId; + + if(node.Meta > maxValueMeta) + maxValueMeta = node.Meta; + } + + { + std::sort(profiles.begin(), profiles.end()); + auto last = std::unique(profiles.begin(), profiles.end()); + profiles.erase(last, profiles.end()); + profiles.shrink_to_fit(); + } + + { + std::sort(meta.begin(), meta.end()); + auto last = std::unique(meta.begin(), meta.end()); + meta.erase(last, meta.end()); + meta.shrink_to_fit(); + } + + + // Количество бит на идентификатор в сыром виде + uint8_t bits_raw_profile = std::ceil(std::log2(maxValueProfile)); + assert(bits_raw_profile >= 1 && bits_raw_profile <= 24); + // Количество бит на индекс + uint8_t bits_indices_profile = std::ceil(std::log2(profiles.size())); + assert(bits_indices_profile >= 1 && bits_indices_profile <= 16); + + bool indices_profile = 3*8+bits_raw_profile*profiles.size()+bits_indices_profile*16*16*16 < bits_raw_profile*16*16*16; + + + std::vector buff; + + auto write = [&](size_t value, int count) { + for(int iter = 0; iter < count; iter++) + buff.push_back((value >> iter) & 0x1); + }; + + + write(indices_profile, 1); + write(bits_raw_profile, 5); + write(bits_indices_profile, 4); + + // Количество бит на идентификатор в сыром виде + uint8_t bits_raw_meta = std::ceil(std::log2(maxValueMeta)); + assert(bits_raw_meta >= 1 && bits_raw_meta <= 8); + // Количество бит на индекс + uint8_t bits_indices_meta = std::ceil(std::log2(meta.size())); + assert(bits_indices_meta >= 1 && bits_indices_meta <= 8); + + bool indices_meta = 3*8+bits_raw_meta*profiles.size()+bits_indices_meta*16*16*16 < bits_raw_meta*16*16*16; + + write(indices_meta, 1); + write(bits_raw_meta, 3); + write(bits_indices_meta, 3); + + // Таблицы + if(indices_profile) { + write(profiles.size(), 12); + + for(DefNodeId_t id : profiles) { + write(id, bits_raw_profile); + } + } + + + if(indices_meta) { + write(meta.size(), 8); + + for(DefNodeId_t id : meta) { + write(id, bits_raw_meta); + } + } + + + // Данные + for(size_t iter = 0; iter < 16*16*16; iter++) { + const Node &node = nodes[iter]; + + if(indices_profile) { + size_t index = std::binary_search(profiles.begin(), profiles.end(), node.NodeId); + write(index, bits_indices_profile); + } else { + write(node.NodeId, bits_raw_profile); + } + + if(indices_meta) { + size_t index = std::binary_search(meta.begin(), meta.end(), node.Meta); + write(index, bits_indices_meta); + } else { + write(node.Meta, bits_raw_meta); + } + } + + return {compressed, profiles}; +} + + +CompressedNodes compressNodes(const Node* nodes, bool fast) { + if(fast) + return compressNodes_byte(nodes); + else + return compressNodes_bit(nodes); +} + +void unCompressNodes_byte(const std::u8string& compressed, Node* ptr) { + size_t pos = 1; + + auto read = [&]() -> size_t { + assert(pos < compressed.size()); + return compressed[pos++]; + }; + + uint8_t value = read(); + + uint8_t bytes_raw_profile = (value >> 1) & 0x3; + uint8_t bytes_indices_profile = (value >> 3) & 0x3; + bool indices_profile = value & 0x1; + + if(indices_profile) { + std::vector profiles; + profiles.resize(read() | (read() << 8) | (read() << 16)); + + for(size_t iter = 0; iter < profiles.size(); iter++) { + DefNodeId_t id = read(); + + if(bytes_raw_profile > 1) + id |= read() << 8; + if(bytes_raw_profile > 2) + id |= read() << 16; + } + + for(size_t iter = 0; iter < 16*16*16; iter++) { + Node &node = ptr[iter]; + + DefNodeId_t index = read(); + if(bytes_indices_profile > 1) + index |= read() << 8; + + node.NodeId = profiles[index]; + node.Meta = read(); + } + } else { + for(size_t iter = 0; iter < 16*16*16; iter++) { + Node &node = ptr[iter]; + + node.NodeId = read(); + + if(bytes_raw_profile > 1) + node.NodeId |= read() << 8; + if(bytes_raw_profile > 2) + node.NodeId |= read() << 16; + + node.Meta = read(); + } + } +} + +void unCompressNodes_bit(const std::u8string& compressed, Node* ptr) { + size_t pos = 1; + + auto read = [&](int bits) -> size_t { + size_t out = 0; + for(int iter = 0; iter < bits; iter++, pos++) { + assert(pos < compressed.size()*8); + + out |= (compressed[pos / 8] >> (pos % 8)) << iter; + } + + return out; + }; + + std::vector meta; + + + bool indices_profile = read(1); + uint8_t bits_raw_profile = read(5); + uint8_t bits_indices_profile = read(4); + + bool indices_meta = read(1); + uint8_t bits_raw_meta = read(3); + uint8_t bits_indices_meta = read(3); + + std::vector profiles; + + // Таблицы + if(indices_profile) { + profiles.resize(read(12)); + + for(size_t iter = 0; iter < profiles.size(); iter++) { + profiles[iter] = read(bits_raw_profile); + } + } + + + if(indices_meta) { + meta.resize(read(8)); + + for(size_t iter = 0; iter < meta.size(); iter++) { + meta[iter] = read(bits_raw_meta); + } + } + + + // Данные + for(size_t iter = 0; iter < 16*16*16; iter++) { + Node &node = ptr[iter]; + + if(indices_profile) { + node.NodeId = profiles[read(bits_indices_profile)]; + } else { + node.NodeId = read(bits_raw_profile); + } + + if(indices_meta) { + node.Meta = meta[read(bits_indices_meta)]; + } else { + node.Meta = read(bits_raw_meta); + } + } +} + +void unCompressNodes(const std::u8string& compressed, Node* ptr) { + if(compressed.front()) + return unCompressNodes_byte(compressed, ptr); + else + return unCompressNodes_bit(compressed, ptr); +} + +} \ No newline at end of file diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index aea81b0..93a6678 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -1,9 +1,13 @@ #pragma once +#include "Common/Net.hpp" +#include #include #include +#include #include #include +#include namespace LV { @@ -422,6 +426,61 @@ using PortalId_t = ResourceId_t; // uint8_t R : 2, G : 2, B : 2; // }; + + +struct VoxelCube { + union { + struct { + DefVoxelId_t VoxelId : 24, Meta : 8; + }; + + DefVoxelId_t Data = 0; + }; + + Pos::bvec256u Pos, Size; // Размер+1, 0 это единичный размер + + auto operator<=>(const VoxelCube& other) const { + if (auto cmp = Pos <=> other.Pos; cmp != 0) + return cmp; + + if (auto cmp = Size <=> other.Size; cmp != 0) + return cmp; + + return Data <=> other.Data; + } + + bool operator==(const VoxelCube& other) const { + return Pos == other.Pos && Size == other.Size && Data == other.Data; + } +}; + +struct CompressedVoxels { + std::u8string Compressed; + // Уникальный сортированный список идентификаторов вокселей + std::vector Defines; +}; + +CompressedVoxels compressVoxels(const std::vector& voxels, bool fast = true); +std::vector unCompressVoxels(const std::u8string& compressed); + +struct Node { + union { + struct { + DefNodeId_t NodeId : 24, Meta : 8; + }; + DefNodeId_t Data; + }; +}; + +struct CompressedNodes { + std::u8string Compressed; + // Уникальный сортированный список идентификаторов нод + std::vector Defines; +}; + +CompressedNodes compressNodes(const Node* nodes, bool fast = true); +void unCompressNodes(const std::u8string& compressed, Node* ptr); + } diff --git a/Src/Common/Net.hpp b/Src/Common/Net.hpp index 4578398..40550f5 100644 --- a/Src/Common/Net.hpp +++ b/Src/Common/Net.hpp @@ -1,5 +1,8 @@ #pragma once +// TODO: Всё это надо переписать + + #include "MemoryPool.hpp" #include "Async.hpp" #include "TOSLib.hpp" diff --git a/Src/Server/Abstract.hpp b/Src/Server/Abstract.hpp index 9982540..954da48 100644 --- a/Src/Server/Abstract.hpp +++ b/Src/Server/Abstract.hpp @@ -55,32 +55,6 @@ struct ServerTime { uint32_t Seconds : 24, Sub : 8; }; -struct VoxelCube { - union { - struct { - DefVoxelId_t VoxelId : 24, Meta : 8; - }; - - DefVoxelId_t Data = 0; - }; - - Pos::bvec256u Left, Right; // TODO: заменить на позицию и размер - - auto operator<=>(const VoxelCube& other) const { - if (auto cmp = Left <=> other.Left; cmp != 0) - return cmp; - - if (auto cmp = Right <=> other.Right; cmp != 0) - return cmp; - - return Data <=> other.Data; - } - - bool operator==(const VoxelCube& other) const { - return Left == other.Left && Right == other.Right && Data == other.Data; - } -}; - struct VoxelCube_Region { union { struct { @@ -107,15 +81,6 @@ struct VoxelCube_Region { } }; -struct Node { - union { - struct { - DefNodeId_t NodeId : 24, Meta : 8; - }; - DefNodeId_t Data; - }; -}; - struct AABB { Pos::Object VecMin, VecMax; @@ -382,8 +347,8 @@ inline void convertChunkVoxelsToRegion(const std::unordered_mapprepareWorldUpdate(worldId, worldObj); } -void ContentEventController::onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, - const std::unordered_map*>& chunks) -{ - auto pWorld = ContentViewState.Regions.find(worldId); - if(pWorld == ContentViewState.Regions.end()) - return; - - if(!std::binary_search(pWorld->second.begin(), pWorld->second.end(), regionPos)) - return; - - for(auto pChunk : chunks) { - Pos::GlobalChunk chunkPos = (Pos::GlobalChunk(regionPos) << 2) + pChunk.first; - Remote->prepareChunkUpdate_Voxels(worldId, chunkPos, pChunk.second); - } -} - -void ContentEventController::onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos, - const std::unordered_map &chunks) -{ - auto pWorld = ContentViewState.Regions.find(worldId); - if(pWorld == ContentViewState.Regions.end()) - return; - - if(!std::binary_search(pWorld->second.begin(), pWorld->second.end(), regionPos)) - return; - - for(auto pChunk : chunks) { - Pos::GlobalChunk chunkPos = (Pos::GlobalChunk(regionPos) << 2) + Pos::GlobalChunk(pChunk.first); - Remote->prepareChunkUpdate_Nodes(worldId, chunkPos, pChunk.second); - } -} - -// void ContentEventController::onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos, -// const std::unordered_map &chunks) -// { -// auto pWorld = ContentViewState.find(worldId); -// if(pWorld == ContentViewState.end()) -// return; - -// auto pRegion = pWorld->second.find(regionPos); -// if(pRegion == pWorld->second.end()) -// return; - -// const std::bitset<4096> &chunkBitset = pRegion->second; - -// for(auto pChunk : chunks) { -// if(!chunkBitset.test(pChunk.first)) -// continue; - -// Pos::GlobalChunk chunkPos = regionPos.toChunk(pChunk.first); -// Remote->prepareChunkUpdate_LightPrism(worldId, chunkPos, pChunk.second); -// } -// } - void ContentEventController::onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set &enter, const std::unordered_set &lost) { diff --git a/Src/Server/ContentEventController.hpp b/Src/Server/ContentEventController.hpp index 8c50a72..a8a0295 100644 --- a/Src/Server/ContentEventController.hpp +++ b/Src/Server/ContentEventController.hpp @@ -151,11 +151,6 @@ public: // Фильтровать не отслеживаемые миры void onWorldUpdate(WorldId_t worldId, World *worldObj); - // Нужно фильтровать неотслеживаемые чанки - void onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map*> &chunks); - void onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map &chunks); - //void onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map &chunks); - void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set &enter, const std::unordered_set &lost); void onEntitySwap(ServerEntityId_t prevId, ServerEntityId_t newId); void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map &entities); diff --git a/Src/Server/GameServer.cpp b/Src/Server/GameServer.cpp index b4a3909..e1f8b78 100644 --- a/Src/Server/GameServer.cpp +++ b/Src/Server/GameServer.cpp @@ -11,24 +11,62 @@ #include #include #include +#include #include #include #include #include "SaveBackends/Filesystem.hpp" #include "Server/SaveBackend.hpp" #include "Server/World.hpp" +#include "TOSLib.hpp" #include "glm/gtc/noise.hpp" namespace LV::Server { GameServer::~GameServer() { shutdown("on ~GameServer"); + Backing.NeedShutdown = true; + Backing.Symaphore.notify_all(); + RunThread.join(); WorkDeadline.cancel(); UseLock.wait_no_use(); + + Backing.stop(); + LOG.info() << "Сервер уничтожен"; } +void GameServer::Backing_t::run(int id) { + LOG.debug() << "Старт фонового потока " << id; + + try { + while(true) { + { + std::unique_lock lock(Mutex); + Symaphore.wait(lock, [&](){ return Run != 0 || NeedShutdown; }); + if(NeedShutdown) { + LOG.debug() << "Завершение выполнения фонового потока " << id; + } + } + + // Работа + + { + std::unique_lock lock(Mutex); + Run--; + Symaphore.notify_all(); + } + } + } catch(const std::exception& exc) { + std::unique_lock lock(Mutex); + NeedShutdown = true; + LOG.error() << "Ошибка выполнения фонового потока " << id << ":\n" << exc.what(); + } + + Symaphore.notify_all(); +} + static thread_local std::vector TL_Circles; std::vector GameServer::Expanse_t::accumulateContentViewCircles(ContentViewCircle circle, int depth) @@ -356,6 +394,8 @@ void GameServer::stepConnections() { lock->clear(); } + Backing.end(); + // Отключение игроков for(std::unique_ptr &cec : Game.CECs) { // Убрать отключившихся @@ -425,7 +465,7 @@ IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() { auto iterWorld = Expanse.Worlds.find(worldId); assert(iterWorld != Expanse.Worlds.end()); - std::vector notLoaded = iterWorld->second->onCEC_RegionsEnter(cec.get(), regions); + std::vector notLoaded = iterWorld->second->onCEC_RegionsEnter(cec.get(), regions, worldId); if(!notLoaded.empty()) { // Добавляем к списку на загрузку std::vector &tl = toDB.Load[worldId]; @@ -483,6 +523,9 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db for(auto& [worldId, regions] : db.NotExisten) { auto &r = calculatedNoise[worldId]; for(Pos::GlobalRegion pos : regions) { + if(IsGoingShutdown) + break; + r.emplace_back(); std::get<0>(r.back()) = pos; auto ®ion = std::get<1>(r.back()); @@ -494,11 +537,14 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db for(int z = 0; z < 64; z++) for(int y = 0; y < 64; y++) for(int x = 0; x < 64; x++, ptr++) { - *ptr = glm::perlin(glm::dvec3(posNode.x+x, posNode.y+y, posNode.z+z)); + *ptr = TOS::genRand(); //glm::perlin(glm::vec3(posNode.x+x, posNode.y+y, posNode.z+z)); } } } + if(IsGoingShutdown) + return; + std::unordered_map>> toLoadRegions; // Синхронизация с контроллером асинхронных обработчиков луа @@ -565,7 +611,7 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db iterWorld->second->pushRegions(std::move(regions)); for(auto& [cec, poses] : toSubscribe) { - iterWorld->second->onCEC_RegionsEnter(cec, poses); + iterWorld->second->onCEC_RegionsEnter(cec, poses, worldId); } } } @@ -1068,6 +1114,8 @@ void GameServer::stepSyncContent() { full.insert(cec->Remote->pushPreparedPackets()); } + Backing.start(); + full.uniq(); if(!full.BinTexture.empty()) diff --git a/Src/Server/GameServer.hpp b/Src/Server/GameServer.hpp index a161d34..ec1ea66 100644 --- a/Src/Server/GameServer.hpp +++ b/Src/Server/GameServer.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "RemoteClient.hpp" #include "Server/Abstract.hpp" @@ -116,12 +117,78 @@ class GameServer : public AsyncObject { std::unique_ptr ModStorage; } SaveBackend; + enum class EnumBackingScenario { + ChunksChanges // Сжатие изменённых чанков и отправка клиентам + }; + + /* + Обязательно между тактами + После окончания такта пул копирует изменённые чанки + - синхронизация сбора в stepDatabaseSync - + сжимает их и отправляет клиентам + - синхронизация в начале stepPlayerProceed - + ^ к этому моменту все данные должны быть отправлены + Далее при подписании на новые регионы они будут добавлены в пул на обработку + + Генерация шума + OpenCL или пул + + Конвертация ресурсов игры, их хранение в кеше и загрузка в память для отправки клиентам + io_uring или последовательное чтение + + Исполнение асинхронного луа + Пул для постоянной работы и синхронизации времени с главным потоком + + Сжатие/расжатие регионов в базе + Локальный поток должен собирать ключи профилей для базы + Остальное внутри базы + */ + struct Backing_t { + TOS::Logger LOG = "Backing"; + bool NeedShutdown = false; + std::vector Threads; + std::mutex Mutex; + std::atomic_int Run = 0; + std::condition_variable Symaphore; + std::unordered_map> *Worlds; + + void start() { + std::lock_guard lock(Mutex); + Run = Threads.size(); + Symaphore.notify_all(); + } + + void end() { + std::unique_lock lock(Mutex); + Symaphore.wait(lock, [&](){ return Run == 0 || NeedShutdown; }); + } + + void stop() { + { + std::unique_lock lock(Mutex); + NeedShutdown = true; + Symaphore.notify_all(); + } + + for(std::thread& thread : Threads) + thread.join(); + } + + void run(int id); + } Backing; + public: GameServer(asio::io_context &ioc, fs::path worldPath) : AsyncObject(ioc), Content(ioc, nullptr, nullptr, nullptr, nullptr, nullptr) { init(worldPath); + + Backing.Threads.resize(4); + Backing.Worlds = &Expanse.Worlds; + for(size_t iter = 0; iter < Backing.Threads.size(); iter++) { + Backing.Threads[iter] = std::thread(Backing.Run, &Backing, iter); + } } virtual ~GameServer(); @@ -211,6 +278,7 @@ private: /* Обработка запросов двоичных ресурсов и определений Отправка пакетов игрокам + Запуск задачи ChunksChanges */ void stepSyncContent(); }; diff --git a/Src/Server/RemoteClient.cpp b/Src/Server/RemoteClient.cpp index 39dcc52..ffa629c 100644 --- a/Src/Server/RemoteClient.cpp +++ b/Src/Server/RemoteClient.cpp @@ -67,9 +67,13 @@ void RemoteClient::shutdown(EnumDisconnect type, const std::string reason) { LOG.info() << "Игрок '" << Username << "' отключился " << info; } -void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, - const std::vector* voxels) +bool RemoteClient::maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels, + const std::vector& uniq_sorted_defines) { + bool lock = ResUses.RefChunkLock.exchange(1); + if(lock) + return false; + /* Обновить зависимости Запросить недостающие @@ -80,39 +84,23 @@ void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk newTypes, /* Новые типы вокселей */ lostTypes /* Потерянные типы вокселей */; - // Обновить зависимости вокселей - std::vector v; - if(voxels) { - v.reserve(voxels->size()); - for(const VoxelCube& value : *voxels) { - v.push_back(value.VoxelId); - } - - std::sort(v.begin(), v.end()); - auto last = std::unique(v.begin(), v.end()); - v.erase(last, v.end()); - - // В v отсортированный список уникальных вокселей в чанке - - - // Отметим использование этих вокселей - for(const DefVoxelId_t& id : v) { - auto iter = ResUses.DefVoxel.find(id); - if(iter == ResUses.DefVoxel.end()) { - // Новый тип - newTypes.push_back(id); - ResUses.DefVoxel[id] = 1; - } else { - // Увеличиваем счётчик - iter->second++; - } + // Отметим использование этих вокселей + for(const DefVoxelId_t& id : uniq_sorted_defines) { + auto iter = ResUses.DefVoxel.find(id); + if(iter == ResUses.DefVoxel.end()) { + // Новый тип + newTypes.push_back(id); + ResUses.DefVoxel[id] = 1; + } else { + // Увеличиваем счётчик + iter->second++; } } - // Исключим зависимости предыдущей версии чанка auto iterWorld = ResUses.RefChunk.find(worldId); - assert(iterWorld != ResUses.RefChunk.end()); Pos::bvec4u lChunk = (chunkPos & 0xf); + + if(iterWorld != ResUses.RefChunk.end()) // Исключим зависимости предыдущей версии чанка { auto iterRegion = iterWorld->second.find(chunkPos); @@ -128,9 +116,12 @@ void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk } } } + } else { + ResUses.RefChunk[worldId] = {}; + iterWorld = ResUses.RefChunk.find(worldId); } - iterWorld->second[chunkPos][lChunk.pack()].Voxel = v; + iterWorld->second[chunkPos][lChunk.pack()].Voxel = uniq_sorted_defines; if(!newTypes.empty()) { // Добавляем новые типы в запрос @@ -146,46 +137,44 @@ void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk } } - // TODO: отправить чанк + checkPacketBorder(1+4+8+2+4+compressed_voxels.size()); + NextPacket << (uint8_t) ToClient::L1::Content + << (uint8_t) ToClient::L2Content::ChunkVoxels + << worldId << chunkPos.pack() << uint32_t(compressed_voxels.size()); + NextPacket.write((const std::byte*) compressed_voxels.data(), compressed_voxels.size()); + + ResUses.RefChunkLock.exchange(0); + return true; } -void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const Node* nodes) { +bool RemoteClient::maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes, + const std::vector& uniq_sorted_defines) +{ + bool lock = ResUses.RefChunkLock.exchange(1); + if(lock) + return false; + std::vector newTypes, /* Новые типы нод */ lostTypes /* Потерянные типы нод */; - // Обновить зависимости нод - std::vector n; - { - n.reserve(16*16*16); - for(size_t iter = 0; iter < 16*16*16; iter++) { - n.push_back(nodes[iter].NodeId); - } - - std::sort(n.begin(), n.end()); - auto last = std::unique(n.begin(), n.end()); - n.erase(last, n.end()); - - // В n отсортированный список уникальных нод в чанке - - // Отметим использование этих нод - for(const DefNodeId_t& id : n) { - auto iter = ResUses.DefNode.find(id); - if(iter == ResUses.DefNode.end()) { - // Новый тип - newTypes.push_back(id); - ResUses.DefNode[id] = 1; - } else { - // Увеличиваем счётчик - iter->second++; - LOG.debug() << "id = " << id << ' ' << iter->second; - } + // Отметим использование этих нод + for(const DefNodeId_t& id : uniq_sorted_defines) { + auto iter = ResUses.DefNode.find(id); + if(iter == ResUses.DefNode.end()) { + // Новый тип + newTypes.push_back(id); + ResUses.DefNode[id] = 1; + } else { + // Увеличиваем счётчик + iter->second++; } } auto iterWorld = ResUses.RefChunk.find(worldId); - assert(iterWorld != ResUses.RefChunk.end()); Pos::bvec4u lChunk = (chunkPos & 0xf); + + if(iterWorld != ResUses.RefChunk.end()) // Исключим зависимости предыдущей версии чанка { auto iterRegion = iterWorld->second.find(chunkPos); @@ -201,9 +190,12 @@ void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk } } } + } else { + ResUses.RefChunk[worldId] = {}; + iterWorld = ResUses.RefChunk.find(worldId); } - iterWorld->second[chunkPos][lChunk.pack()].Node = n; + iterWorld->second[chunkPos][lChunk.pack()].Node = uniq_sorted_defines; if(!newTypes.empty()) { // Добавляем новые типы в запрос @@ -219,8 +211,14 @@ void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk } } - // TODO: отправить чанк - LOG.debug() << "Увидели " << chunkPos.x << ' ' << chunkPos.y << ' ' << chunkPos.z; + checkPacketBorder(1+4+8+4+compressed_nodes.size()); + NextPacket << (uint8_t) ToClient::L1::Content + << (uint8_t) ToClient::L2Content::ChunkNodes + << worldId << chunkPos.pack() << uint32_t(compressed_nodes.size()); + NextPacket.write((const std::byte*) compressed_nodes.data(), compressed_nodes.size()); + + ResUses.RefChunkLock.exchange(0); + return true; } void RemoteClient::prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regionPos) { diff --git a/Src/Server/RemoteClient.hpp b/Src/Server/RemoteClient.hpp index 8b965d3..1716c12 100644 --- a/Src/Server/RemoteClient.hpp +++ b/Src/Server/RemoteClient.hpp @@ -7,6 +7,7 @@ #include "Common/Packets.hpp" #include "Server/ContentEventController.hpp" #include +#include #include #include #include @@ -264,6 +265,7 @@ class RemoteClient { std::vector Voxel; std::vector Node; }; + std::atomic_bool RefChunkLock = 0; std::map>> RefChunk; struct RefWorld_t { DefWorldId_t Profile; @@ -314,15 +316,24 @@ public: Socket.pushPackets(simplePackets, smartPackets); } - // Функции подготавливают пакеты к отправке + /* + Сервер собирает изменения миров, сжимает их и раздаёт на отправку игрокам + + */ + // Функции подготавливают пакеты к отправке // Отслеживаемое игроком использование контента + + // maybe созданны для использования в многопотоке, если ресурс сейчас занят вернёт false, потом нужно повторить запрос // В зоне видимости добавился чанк или изменились его воксели - void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector* voxels); + bool maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels, + const std::vector& uniq_sorted_defines); // В зоне видимости добавился чанк или изменились его ноды - void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const Node* nodes); - void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::unordered_map &nodes); - //void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights); + bool maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes, + const std::vector& uniq_sorted_defines); + // void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights); + + // Регион удалён из зоны видимости void prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regionPos); diff --git a/Src/Server/SaveBackends/Filesystem.cpp b/Src/Server/SaveBackends/Filesystem.cpp index 7458462..749e795 100644 --- a/Src/Server/SaveBackends/Filesystem.cpp +++ b/Src/Server/SaveBackends/Filesystem.cpp @@ -33,81 +33,77 @@ public: return Dir / worldId / std::to_string(regionPos.x) / std::to_string(regionPos.y) / std::to_string(regionPos.z); } - virtual bool isAsync() { return false; }; - - virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) { - return fs::exists(getPath(worldId, regionPos)); + virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override { + TickSyncInfo_Out out; + out.NotExisten = std::move(data.Load); + return out; } - virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) { - std::ifstream fd(getPath(worldId, regionPos)); - js::object jobj = js::parse(fd).as_object(); + virtual void changePreloadDistance(uint8_t value) override { - { - js::array &jaVoxels = jobj.at("Voxels").as_array(); - for(js::value &jvVoxel : jaVoxels) { - js::object &joVoxel = jvVoxel.as_object(); - VoxelCube_Region cube; - cube.Data = joVoxel.at("Data").as_uint64(); - cube.Left.x = joVoxel.at("LeftX").as_uint64(); - cube.Left.y = joVoxel.at("LeftY").as_uint64(); - cube.Left.z = joVoxel.at("LeftZ").as_uint64(); - cube.Right.x = joVoxel.at("RightX").as_uint64(); - cube.Right.y = joVoxel.at("RightY").as_uint64(); - cube.Right.z = joVoxel.at("RightZ").as_uint64(); - data->Voxels.push_back(cube); - } - } - - { - js::object &joVoxelMap = jobj.at("VoxelsMap").as_object(); - for(js::key_value_pair &jkvp : joVoxelMap) { - data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string(); - } - } } - virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) { - js::object jobj; + // virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) { + // std::ifstream fd(getPath(worldId, regionPos)); + // js::object jobj = js::parse(fd).as_object(); - { - js::array jaVoxels; - for(const VoxelCube_Region &cube : data->Voxels) { - js::object joVoxel; - joVoxel["Data"] = cube.Data; - joVoxel["LeftX"] = cube.Left.x; - joVoxel["LeftY"] = cube.Left.y; - joVoxel["LeftZ"] = cube.Left.z; - joVoxel["RightX"] = cube.Right.x; - joVoxel["RightY"] = cube.Right.y; - joVoxel["RightZ"] = cube.Right.z; - jaVoxels.push_back(std::move(joVoxel)); - } + // { + // js::array &jaVoxels = jobj.at("Voxels").as_array(); + // for(js::value &jvVoxel : jaVoxels) { + // js::object &joVoxel = jvVoxel.as_object(); + // VoxelCube_Region cube; + // cube.Data = joVoxel.at("Data").as_uint64(); + // cube.Left.x = joVoxel.at("LeftX").as_uint64(); + // cube.Left.y = joVoxel.at("LeftY").as_uint64(); + // cube.Left.z = joVoxel.at("LeftZ").as_uint64(); + // cube.Right.x = joVoxel.at("RightX").as_uint64(); + // cube.Right.y = joVoxel.at("RightY").as_uint64(); + // cube.Right.z = joVoxel.at("RightZ").as_uint64(); + // data->Voxels.push_back(cube); + // } + // } - jobj["Voxels"] = std::move(jaVoxels); - } + // { + // js::object &joVoxelMap = jobj.at("VoxelsMap").as_object(); + // for(js::key_value_pair &jkvp : joVoxelMap) { + // data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string(); + // } + // } + // } - { - js::object joVoxelMap; - for(const auto &pair : data->VoxelsMap) { - joVoxelMap[std::to_string(pair.first)] = pair.second; - } + // virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) { + // js::object jobj; - jobj["VoxelsMap"] = std::move(joVoxelMap); - } + // { + // js::array jaVoxels; + // for(const VoxelCube_Region &cube : data->Voxels) { + // js::object joVoxel; + // joVoxel["Data"] = cube.Data; + // joVoxel["LeftX"] = cube.Left.x; + // joVoxel["LeftY"] = cube.Left.y; + // joVoxel["LeftZ"] = cube.Left.z; + // joVoxel["RightX"] = cube.Right.x; + // joVoxel["RightY"] = cube.Right.y; + // joVoxel["RightZ"] = cube.Right.z; + // jaVoxels.push_back(std::move(joVoxel)); + // } - fs::create_directories(getPath(worldId, regionPos).parent_path()); - std::ofstream fd(getPath(worldId, regionPos)); - fd << js::serialize(jobj); - } + // jobj["Voxels"] = std::move(jaVoxels); + // } - virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) { - fs::remove(getPath(worldId, regionPos)); - } + // { + // js::object joVoxelMap; + // for(const auto &pair : data->VoxelsMap) { + // joVoxelMap[std::to_string(pair.first)] = pair.second; + // } - virtual void remove(std::string worldId) { - fs::remove_all(Dir / worldId); - } + // jobj["VoxelsMap"] = std::move(joVoxelMap); + // } + + // fs::create_directories(getPath(worldId, regionPos).parent_path()); + // std::ofstream fd(getPath(worldId, regionPos)); + // fd << js::serialize(jobj); + // } }; class PSB_Filesystem : public IPlayerSaveBackend { @@ -163,35 +159,37 @@ public: virtual bool isAsync() { return false; }; - virtual bool isExist(std::string playerId) { - return fs::exists(getPath(playerId)); + virtual coro isExist(std::string useranme) override { + co_return fs::exists(getPath(useranme)); } - virtual void rename(std::string fromPlayerId, std::string toPlayerId) { - fs::rename(getPath(fromPlayerId), getPath(toPlayerId)); + virtual coro<> rename(std::string prevUsername, std::string newUsername) override { + fs::rename(getPath(prevUsername), getPath(newUsername)); + co_return; } - virtual void load(std::string playerId, SB_Auth *data) { - std::ifstream fd(getPath(playerId)); + virtual coro load(std::string useranme, SB_Auth& data) override { + std::ifstream fd(getPath(useranme)); js::object jobj = js::parse(fd).as_object(); - data->Id = jobj.at("Id").as_uint64(); - data->PasswordHash = jobj.at("PasswordHash").as_string(); + data.Id = jobj.at("Id").as_uint64(); + data.PasswordHash = jobj.at("PasswordHash").as_string(); } - virtual void save(std::string playerId, const SB_Auth *data) { + virtual coro<> save(std::string playerId, const SB_Auth& data) override { js::object jobj; - jobj["Id"] = data->Id; - jobj["PasswordHash"] = data->PasswordHash; + jobj["Id"] = data.Id; + jobj["PasswordHash"] = data.PasswordHash; fs::create_directories(getPath(playerId).parent_path()); std::ofstream fd(getPath(playerId)); fd << js::serialize(jobj); } - virtual void remove(std::string playerId) { - fs::remove(getPath(playerId)); + virtual coro<> remove(std::string username) override { + fs::remove(getPath(username)); + co_return; } }; diff --git a/Src/Server/World.cpp b/Src/Server/World.cpp index adcb1ae..7a736f5 100644 --- a/Src/Server/World.cpp +++ b/Src/Server/World.cpp @@ -1,4 +1,6 @@ #include "World.hpp" +#include "TOSLib.hpp" +#include namespace LV::Server { @@ -14,23 +16,40 @@ World::~World() { } -void World::onUpdate(GameServer *server, float dtime) { - -} - -std::vector World::onCEC_RegionsEnter(ContentEventController *cec, const std::vector &enter) { +std::vector World::onCEC_RegionsEnter(ContentEventController *cec, const std::vector& enter, WorldId_t wId) { std::vector out; + + TOS::Logger("Test").debug() << "Start"; for(const Pos::GlobalRegion &pos : enter) { auto iterRegion = Regions.find(pos); if(iterRegion == Regions.end()) { out.push_back(pos); + continue; } - iterRegion->second->CECs.push_back(cec); + auto ®ion = *iterRegion->second; + region.CECs.push_back(cec); // Отправить клиенту информацию о чанках и сущностях + std::unordered_map*> voxels; + std::unordered_map nodes; + + for(auto& [key, value] : region.Voxels) { + voxels[key] = &value; + } + + for(int z = 0; z < 4; z++) + for(int y = 0; y < 4; y++) + for(int x = 0; x < 4; x++) { + nodes[Pos::bvec4u(x, y, z)] = (const Node*) ®ion.Nodes[0][0][0][x][y][z]; + } + + cec->onChunksUpdate_Voxels(wId, pos, voxels); + cec->onChunksUpdate_Nodes(wId, pos, nodes); } + TOS::Logger("Test").debug() << "End"; + return out; } @@ -50,4 +69,26 @@ void World::onCEC_RegionsLost(ContentEventController *cec, const std::vector> regions) { + for(auto& [key, value] : regions) { + Region ®ion = *(Regions[key] = std::make_unique()); + region.Voxels = std::move(value.Voxels); + + Node *ptr = (Node*) region.Nodes; + for(std::array& nodes : value.Nodes) { + + std::copy(nodes.data(), nodes.data()+16*16*16, ptr); + ptr += 16*16*16; + } + } +} + +void World::onUpdate(GameServer *server, float dtime) { + +} + } \ No newline at end of file diff --git a/Src/Server/World.hpp b/Src/Server/World.hpp index 7aedc93..764a4fb 100644 --- a/Src/Server/World.hpp +++ b/Src/Server/World.hpp @@ -85,13 +85,13 @@ public: aabbInfo.VecMin.set(axis, aabbInfo.VecMin[axis] & ~0xff00); aabbInfo.VecMax = aabbInfo.VecMin; - aabbInfo.VecMin.x |= int(cube.Left.x) << 6; - aabbInfo.VecMin.y |= int(cube.Left.y) << 6; - aabbInfo.VecMin.z |= int(cube.Left.z) << 6; + aabbInfo.VecMin.x |= int(cube.Pos.x) << 6; + aabbInfo.VecMin.y |= int(cube.Pos.y) << 6; + aabbInfo.VecMin.z |= int(cube.Pos.z) << 6; - aabbInfo.VecMax.x |= int(cube.Right.x) << 6; - aabbInfo.VecMax.y |= int(cube.Right.y) << 6; - aabbInfo.VecMax.z |= int(cube.Right.z) << 6; + aabbInfo.VecMax.x |= int(cube.Pos.x+cube.Size.x+1) << 6; + aabbInfo.VecMax.y |= int(cube.Pos.y+cube.Size.y+1) << 6; + aabbInfo.VecMax.z |= int(cube.Pos.z+cube.Size.z+1) << 6; if(aabb.isCollideWith(aabbInfo)) { aabbInfo = { @@ -149,8 +149,8 @@ public: Возвращает список не загруженных регионов, на которые соответственно игрока не получилось подписать При подписи происходит отправка всех чанков и сущностей региона */ - std::vector onCEC_RegionsEnter(ContentEventController *cec, const std::vector &enter); - void onCEC_RegionsLost(ContentEventController *cec, const std::vector &lost); + std::vector onCEC_RegionsEnter(ContentEventController *cec, const std::vector &enter, WorldId_t wId); + void onCEC_RegionsLost(ContentEventController *cec, const std::vector& lost); struct SaveUnloadInfo { std::vector ToUnload; std::vector> ToSave;