diff --git a/src/ChunkSender.cpp b/src/ChunkSender.cpp index ec05258ae..e00b86795 100644 --- a/src/ChunkSender.cpp +++ b/src/ChunkSender.cpp @@ -11,7 +11,6 @@ #include "ChunkSender.h" #include "World.h" #include "BlockEntities/BlockEntity.h" -#include "Protocol/ChunkDataSerializer.h" #include "ClientHandle.h" #include "Chunk.h" @@ -61,7 +60,8 @@ public: cChunkSender::cChunkSender(cWorld & a_World) : Super("ChunkSender"), - m_World(a_World) + m_World(a_World), + m_Serializer(m_World.GetDimension()) { } @@ -246,11 +246,8 @@ void cChunkSender::SendChunk(int a_ChunkX, int a_ChunkZ, std::unordered_set m_SendChunks; std::unordered_map m_ChunkInfo; diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index c12e11f45..040c3a306 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -2479,7 +2479,8 @@ void cClientHandle::SendChunkData(int a_ChunkX, int a_ChunkZ, const std::string_ { // This just sometimes happens. If you have a reliably replicatable situation for this, go ahead and fix it // It's not a big issue anyway, just means that some chunks may be compressed several times - // LOGD("Refusing to send chunk [%d, %d] to client \"%s\" at [%d, %d].", ChunkX, ChunkZ, m_Username.c_str(), m_Player->GetChunkX(), m_Player->GetChunkZ()); + // LOG("Refusing to send chunk [%d, %d] to client \"%s\" at [%d, %d].", a_ChunkX, a_ChunkZ, m_Username.c_str(), m_Player->GetChunkX(), m_Player->GetChunkZ()); + // 2020 08 21: seems to happen going through nether portals on 1.8.9 return; } diff --git a/src/Protocol/ChunkDataSerializer.cpp b/src/Protocol/ChunkDataSerializer.cpp index 680b9139b..2fd9e1cc2 100644 --- a/src/Protocol/ChunkDataSerializer.cpp +++ b/src/Protocol/ChunkDataSerializer.cpp @@ -3,7 +3,6 @@ #include "zlib/zlib.h" #include "Protocol_1_8.h" #include "Protocol_1_9.h" -#include "../ByteBuffer.h" #include "../ClientHandle.h" #include "../WorldStorage/FastNBT.h" @@ -64,17 +63,8 @@ namespace //////////////////////////////////////////////////////////////////////////////// // cChunkDataSerializer: -cChunkDataSerializer::cChunkDataSerializer( - int a_ChunkX, - int a_ChunkZ, - const cChunkData & a_Data, - const unsigned char * a_BiomeData, - const eDimension a_Dimension -) : - m_ChunkX(a_ChunkX), - m_ChunkZ(a_ChunkZ), - m_Data(a_Data), - m_BiomeData(a_BiomeData), +cChunkDataSerializer::cChunkDataSerializer(const eDimension a_Dimension) : + m_Packet(512 KiB), m_Dimension(a_Dimension) { } @@ -83,30 +73,22 @@ cChunkDataSerializer::cChunkDataSerializer( -void cChunkDataSerializer::SendToClients(const std::unordered_set & a_SendTo) +void cChunkDataSerializer::SendToClients(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, const ClientHandles & a_SendTo) { - std::unordered_map> ClientProtocolVersions; - for (const auto Client : a_SendTo) { - const auto ClientProtocol = static_cast(Client->GetProtocolVersion()); - ClientProtocolVersions[ClientProtocol].emplace_back(Client); - } - - for (const auto & Entry : ClientProtocolVersions) - { - switch (Entry.first) + switch (static_cast(Client->GetProtocolVersion())) { case cProtocol::Version::v1_8_0: { - Serialize47(Entry.second); + Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v47); continue; } case cProtocol::Version::v1_9_0: case cProtocol::Version::v1_9_1: case cProtocol::Version::v1_9_2: { - Serialize107(Entry.second); + Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v107); continue; } case cProtocol::Version::v1_9_4: @@ -117,106 +99,161 @@ void cChunkDataSerializer::SendToClients(const std::unordered_set(Entry.second); // This version didn't last very long xD + Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v393); // This version didn't last very long xD continue; } case cProtocol::Version::v1_13_1: case cProtocol::Version::v1_13_2: { - Serialize393<&Palette401>(Entry.second); + Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v401); continue; } case cProtocol::Version::v1_14: { - Serialize477(Entry.second); + Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v477); continue; } } UNREACHABLE("Unknown chunk data serialization version"); } + + // Our cache is only persistent during the function call: + for (auto & Cache : m_Cache) + { + Cache.Engaged = false; + } } -void cChunkDataSerializer::Serialize47(const std::vector & a_SendTo) +inline void cChunkDataSerializer::Serialize(cClientHandle * a_Client, const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, const CacheVersion a_CacheVersion) +{ + auto & Cache = m_Cache[static_cast(a_CacheVersion)]; + if (Cache.Engaged) + { + // Success! We've done it already, just re-use: + a_Client->SendChunkData(a_ChunkX, a_ChunkZ, Cache.ToSend); + return; + } + + switch (a_CacheVersion) + { + case CacheVersion::v47: + { + Serialize47(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData); + break; + } + case CacheVersion::v107: + { + Serialize107(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData); + break; + } + case CacheVersion::v110: + { + Serialize110(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData); + break; + } + case CacheVersion::v393: + { + Serialize393<&Palette393>(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData); + break; + } + case CacheVersion::v401: + { + Serialize393<&Palette401>(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData); + break; + } + case CacheVersion::v477: + { + Serialize477(a_ChunkX, a_ChunkZ, a_Data, a_BiomeData); + break; + } + } + + CompressPacketInto(Cache); + ASSERT(Cache.Engaged); // Cache must be populated now + a_Client->SendChunkData(a_ChunkX, a_ChunkZ, Cache.ToSend); +} + + + + + +inline void cChunkDataSerializer::Serialize47(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData) { // This function returns the fully compressed packet (including packet size), not the raw packet! // Create the packet: - cByteBuffer Packet(512 KiB); - Packet.WriteVarInt32(0x21); // Packet id (Chunk Data packet) - Packet.WriteBEInt32(m_ChunkX); - Packet.WriteBEInt32(m_ChunkZ); - Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag - Packet.WriteBEUInt16(m_Data.GetSectionBitmask()); + m_Packet.WriteVarInt32(0x21); // Packet id (Chunk Data packet) + m_Packet.WriteBEInt32(a_ChunkX); + m_Packet.WriteBEInt32(a_ChunkZ); + m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag + m_Packet.WriteBEUInt16(a_Data.GetSectionBitmask()); // Write the chunk size: const int BiomeDataSize = cChunkDef::Width * cChunkDef::Width; UInt32 ChunkSize = ( - m_Data.NumPresentSections() * cChunkData::SectionBlockCount * 3 + // Blocks and lighting + a_Data.NumPresentSections() * cChunkData::SectionBlockCount * 3 + // Blocks and lighting BiomeDataSize // Biome data ); - Packet.WriteVarInt32(ChunkSize); + m_Packet.WriteVarInt32(ChunkSize); // Chunk written as seperate arrays of (blocktype + meta), blocklight and skylight // each array stores all present sections of the same kind packed together // Write the block types to the packet: - ForEachSection(m_Data, [&](const cChunkData::sChunkSection & a_Section) + ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section) { for (size_t BlockIdx = 0; BlockIdx != cChunkData::SectionBlockCount; ++BlockIdx) { BLOCKTYPE BlockType = a_Section.m_BlockTypes[BlockIdx] & 0xFF; NIBBLETYPE BlockMeta = a_Section.m_BlockMetas[BlockIdx / 2] >> ((BlockIdx & 1) * 4) & 0x0f; - Packet.WriteBEUInt8(static_cast(BlockType << 4) | BlockMeta); - Packet.WriteBEUInt8(static_cast(BlockType >> 4)); + m_Packet.WriteBEUInt8(static_cast(BlockType << 4) | BlockMeta); + m_Packet.WriteBEUInt8(static_cast(BlockType >> 4)); } } ); // Write the block lights: - ForEachSection(m_Data, [&](const cChunkData::sChunkSection & a_Section) + ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section) { - Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight)); + m_Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight)); } ); // Write the sky lights: - ForEachSection(m_Data, [&](const cChunkData::sChunkSection & a_Section) + ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section) { - Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight)); + m_Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight)); } ); // Write the biome data: - Packet.WriteBuf(m_BiomeData, BiomeDataSize); - - CompressAndSend(Packet, a_SendTo); + m_Packet.WriteBuf(a_BiomeData, BiomeDataSize); } -void cChunkDataSerializer::Serialize107(const std::vector & a_SendTo) +inline void cChunkDataSerializer::Serialize107(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData) { // This function returns the fully compressed packet (including packet size), not the raw packet! // Create the packet: - cByteBuffer Packet(512 KiB); - Packet.WriteVarInt32(0x20); // Packet id (Chunk Data packet) - Packet.WriteBEInt32(m_ChunkX); - Packet.WriteBEInt32(m_ChunkZ); - Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag - Packet.WriteVarInt32(m_Data.GetSectionBitmask()); + m_Packet.WriteVarInt32(0x20); // Packet id (Chunk Data packet) + m_Packet.WriteBEInt32(a_ChunkX); + m_Packet.WriteBEInt32(a_ChunkZ); + m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag + m_Packet.WriteVarInt32(a_Data.GetSectionBitmask()); // Write the chunk size: const UInt8 BitsPerEntry = 13; const size_t ChunkSectionDataArraySize = (cChunkData::SectionBlockCount * BitsPerEntry) / 8 / 8; // Convert from bit count to long count @@ -236,50 +273,47 @@ void cChunkDataSerializer::Serialize107(const std::vector & a_S const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width; size_t ChunkSize = ( - ChunkSectionSize * m_Data.NumPresentSections() + + ChunkSectionSize * a_Data.NumPresentSections() + BiomeDataSize ); - Packet.WriteVarInt32(static_cast(ChunkSize)); + m_Packet.WriteVarInt32(static_cast(ChunkSize)); // Write each chunk section... - ForEachSection(m_Data, [&](const cChunkData::sChunkSection & a_Section) + ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section) { - Packet.WriteBEUInt8(BitsPerEntry); - Packet.WriteVarInt32(0); // Palette length is 0 - Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); - WriteSectionDataSeamless<&PaletteLegacy>(Packet, a_Section, BitsPerEntry); + m_Packet.WriteBEUInt8(BitsPerEntry); + m_Packet.WriteVarInt32(0); // Palette length is 0 + m_Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); + WriteSectionDataSeamless<&PaletteLegacy>(a_Section, BitsPerEntry); // Write lighting: - Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight)); + m_Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight)); if (m_Dimension == dimOverworld) { // Skylight is only sent in the overworld; the nether and end do not use it - Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight)); + m_Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight)); } } ); // Write the biome data - Packet.WriteBuf(m_BiomeData, BiomeDataSize); - - CompressAndSend(Packet, a_SendTo); + m_Packet.WriteBuf(a_BiomeData, BiomeDataSize); } -void cChunkDataSerializer::Serialize110(const std::vector & a_SendTo) +inline void cChunkDataSerializer::Serialize110(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData) { // This function returns the fully compressed packet (including packet size), not the raw packet! // Create the packet: - cByteBuffer Packet(512 KiB); - Packet.WriteVarInt32(0x20); // Packet id (Chunk Data packet) - Packet.WriteBEInt32(m_ChunkX); - Packet.WriteBEInt32(m_ChunkZ); - Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag - Packet.WriteVarInt32(m_Data.GetSectionBitmask()); + m_Packet.WriteVarInt32(0x20); // Packet id (Chunk Data packet) + m_Packet.WriteBEInt32(a_ChunkX); + m_Packet.WriteBEInt32(a_ChunkZ); + m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag + m_Packet.WriteVarInt32(a_Data.GetSectionBitmask()); // Write the chunk size: const UInt8 BitsPerEntry = 13; const size_t ChunkSectionDataArraySize = (cChunkData::SectionBlockCount * BitsPerEntry) / 8 / 8; // Convert from bit count to long count @@ -299,36 +333,34 @@ void cChunkDataSerializer::Serialize110(const std::vector & a_S const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width; size_t ChunkSize = ( - ChunkSectionSize * m_Data.NumPresentSections() + + ChunkSectionSize * a_Data.NumPresentSections() + BiomeDataSize ); - Packet.WriteVarInt32(static_cast(ChunkSize)); + m_Packet.WriteVarInt32(static_cast(ChunkSize)); // Write each chunk section... - ForEachSection(m_Data, [&](const cChunkData::sChunkSection & a_Section) + ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section) { - Packet.WriteBEUInt8(BitsPerEntry); - Packet.WriteVarInt32(0); // Palette length is 0 - Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); - WriteSectionDataSeamless<&PaletteLegacy>(Packet, a_Section, BitsPerEntry); + m_Packet.WriteBEUInt8(BitsPerEntry); + m_Packet.WriteVarInt32(0); // Palette length is 0 + m_Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); + WriteSectionDataSeamless<&PaletteLegacy>(a_Section, BitsPerEntry); // Write lighting: - Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight)); + m_Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight)); if (m_Dimension == dimOverworld) { // Skylight is only sent in the overworld; the nether and end do not use it - Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight)); + m_Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight)); } } ); // Write the biome data - Packet.WriteBuf(m_BiomeData, BiomeDataSize); + m_Packet.WriteBuf(a_BiomeData, BiomeDataSize); // Identify 1.9.4's tile entity list as empty - Packet.WriteBEUInt8(0); - - CompressAndSend(Packet, a_SendTo); + m_Packet.WriteBEUInt8(0); } @@ -336,24 +368,23 @@ void cChunkDataSerializer::Serialize110(const std::vector & a_S template -void cChunkDataSerializer::Serialize393(const std::vector & a_SendTo) +inline void cChunkDataSerializer::Serialize393(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData) { // This function returns the fully compressed packet (including packet size), not the raw packet! // Create the packet: - cByteBuffer Packet(512 KiB); - Packet.WriteVarInt32(0x22); // Packet id (Chunk Data packet) - Packet.WriteBEInt32(m_ChunkX); - Packet.WriteBEInt32(m_ChunkZ); - Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag - Packet.WriteVarInt32(m_Data.GetSectionBitmask()); + m_Packet.WriteVarInt32(0x22); // Packet id (Chunk Data packet) + m_Packet.WriteBEInt32(a_ChunkX); + m_Packet.WriteBEInt32(a_ChunkZ); + m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag + m_Packet.WriteVarInt32(a_Data.GetSectionBitmask()); // Write the chunk size in bytes: const UInt8 BitsPerEntry = 14; const size_t ChunkSectionDataArraySize = (cChunkData::SectionBlockCount * BitsPerEntry) / 8 / 8; size_t ChunkSectionSize = ( 1 + // Bits per entry, BEUInt8, 1 byte - Packet.GetVarIntSize(static_cast(ChunkSectionDataArraySize)) + // Field containing "size of whole section", VarInt32, variable size + m_Packet.GetVarIntSize(static_cast(ChunkSectionDataArraySize)) + // Field containing "size of whole section", VarInt32, variable size ChunkSectionDataArraySize * 8 + // Actual section data, lots of bytes (multiplier 1 long = 8 bytes) cChunkData::SectionBlockCount / 2 // Size of blocklight which is always sent ); @@ -366,24 +397,24 @@ void cChunkDataSerializer::Serialize393(const std::vector & a_S const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width; size_t ChunkSize = ( - ChunkSectionSize * m_Data.NumPresentSections() + + ChunkSectionSize * a_Data.NumPresentSections() + BiomeDataSize * 4 // Biome data now BE ints ); - Packet.WriteVarInt32(static_cast(ChunkSize)); + m_Packet.WriteVarInt32(static_cast(ChunkSize)); // Write each chunk section... - ForEachSection(m_Data, [&](const cChunkData::sChunkSection & a_Section) + ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section) { - Packet.WriteBEUInt8(BitsPerEntry); - Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); - WriteSectionDataSeamless(Packet, a_Section, BitsPerEntry); + m_Packet.WriteBEUInt8(BitsPerEntry); + m_Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); + WriteSectionDataSeamless(a_Section, BitsPerEntry); // Write lighting: - Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight)); + m_Packet.WriteBuf(a_Section.m_BlockLight, sizeof(a_Section.m_BlockLight)); if (m_Dimension == dimOverworld) { // Skylight is only sent in the overworld; the nether and end do not use it - Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight)); + m_Packet.WriteBuf(a_Section.m_BlockSkyLight, sizeof(a_Section.m_BlockSkyLight)); } } ); @@ -391,30 +422,27 @@ void cChunkDataSerializer::Serialize393(const std::vector & a_S // Write the biome data for (size_t i = 0; i != BiomeDataSize; i++) { - Packet.WriteBEUInt32(static_cast(m_BiomeData[i]) & 0xff); + m_Packet.WriteBEUInt32(static_cast(a_BiomeData[i]) & 0xff); } // Identify 1.9.4's tile entity list as empty - Packet.WriteVarInt32(0); - - CompressAndSend(Packet, a_SendTo); + m_Packet.WriteVarInt32(0); } -void cChunkDataSerializer::Serialize477(const std::vector & a_SendTo) +inline void cChunkDataSerializer::Serialize477(const int a_ChunkX, const int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData) { // This function returns the fully compressed packet (including packet size), not the raw packet! // Create the packet: - cByteBuffer Packet(512 KiB); - Packet.WriteVarInt32(0x21); // Packet id (Chunk Data packet) - Packet.WriteBEInt32(m_ChunkX); - Packet.WriteBEInt32(m_ChunkZ); - Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag - Packet.WriteVarInt32(m_Data.GetSectionBitmask()); + m_Packet.WriteVarInt32(0x21); // Packet id (Chunk Data packet) + m_Packet.WriteBEInt32(a_ChunkX); + m_Packet.WriteBEInt32(a_ChunkZ); + m_Packet.WriteBool(true); // "Ground-up continuous", or rather, "biome data present" flag + m_Packet.WriteVarInt32(a_Data.GetSectionBitmask()); { cFastNBTWriter Writer; @@ -422,7 +450,7 @@ void cChunkDataSerializer::Serialize477(const std::vector & a_S // std::array Longz = {}; // Writer.AddLongArray("MOTION_BLOCKING", Longz.data(), Longz.size()); Writer.Finish(); - Packet.Write(Writer.GetResult().data(), Writer.GetResult().size()); + m_Packet.Write(Writer.GetResult().data(), Writer.GetResult().size()); } // Write the chunk size in bytes: @@ -431,37 +459,35 @@ void cChunkDataSerializer::Serialize477(const std::vector & a_S const size_t ChunkSectionSize = ( 2 + // Block count, BEInt16, 2 bytes 1 + // Bits per entry, BEUInt8, 1 byte - Packet.GetVarIntSize(static_cast(ChunkSectionDataArraySize)) + // Field containing "size of whole section", VarInt32, variable size + m_Packet.GetVarIntSize(static_cast(ChunkSectionDataArraySize)) + // Field containing "size of whole section", VarInt32, variable size ChunkSectionDataArraySize * 8 // Actual section data, lots of bytes (multiplier 1 long = 8 bytes) ); const size_t BiomeDataSize = cChunkDef::Width * cChunkDef::Width; const size_t ChunkSize = ( - ChunkSectionSize * m_Data.NumPresentSections() + + ChunkSectionSize * a_Data.NumPresentSections() + BiomeDataSize * 4 // Biome data now BE ints ); - Packet.WriteVarInt32(static_cast(ChunkSize)); + m_Packet.WriteVarInt32(static_cast(ChunkSize)); // Write each chunk section... - ForEachSection(m_Data, [&](const cChunkData::sChunkSection & a_Section) + ForEachSection(a_Data, [&](const cChunkData::sChunkSection & a_Section) { - Packet.WriteBEInt16(-1); - Packet.WriteBEUInt8(BitsPerEntry); - Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); - WriteSectionDataSeamless<&Palette477>(Packet, a_Section, BitsPerEntry); + m_Packet.WriteBEInt16(-1); + m_Packet.WriteBEUInt8(BitsPerEntry); + m_Packet.WriteVarInt32(static_cast(ChunkSectionDataArraySize)); + WriteSectionDataSeamless<&Palette477>(a_Section, BitsPerEntry); } ); // Write the biome data for (size_t i = 0; i != BiomeDataSize; i++) { - Packet.WriteBEUInt32(static_cast(m_BiomeData[i]) & 0xff); + m_Packet.WriteBEUInt32(static_cast(a_BiomeData[i]) & 0xff); } // Identify 1.9.4's tile entity list as empty - Packet.WriteVarInt32(0); - - CompressAndSend(Packet, a_SendTo); + m_Packet.WriteVarInt32(0); } @@ -469,7 +495,7 @@ void cChunkDataSerializer::Serialize477(const std::vector & a_S template -void cChunkDataSerializer::WriteSectionDataSeamless(cByteBuffer & a_Packet, const cChunkData::sChunkSection & a_Section, const UInt8 a_BitsPerEntry) +inline void cChunkDataSerializer::WriteSectionDataSeamless(const cChunkData::sChunkSection & a_Section, const UInt8 a_BitsPerEntry) { // https://wiki.vg/Chunk_Format#Data_structure @@ -493,7 +519,7 @@ void cChunkDataSerializer::WriteSectionDataSeamless(cByteBuffer & a_Packet, cons if (Remaining >= 0) { // There were some bits remaining: we've filled the buffer. Flush it: - a_Packet.WriteBEUInt64(Buffer); + m_Packet.WriteBEUInt64(Buffer); // And write the remaining bits, setting the new BitIndex: Buffer = Value >> (a_BitsPerEntry - Remaining); @@ -515,20 +541,16 @@ void cChunkDataSerializer::WriteSectionDataSeamless(cByteBuffer & a_Packet, cons -void cChunkDataSerializer::CompressAndSend(cByteBuffer & a_Packet, const std::vector & a_SendTo) +inline void cChunkDataSerializer::CompressPacketInto(ChunkDataCache & a_Cache) { - AString PacketData; - a_Packet.ReadAll(PacketData); + m_Packet.ReadAll(a_Cache.PacketData); + m_Packet.CommitRead(); - AString ToSend; - if (!cProtocol_1_8_0::CompressPacket(PacketData, ToSend)) + if (!cProtocol_1_8_0::CompressPacket(a_Cache.PacketData, a_Cache.ToSend)) { ASSERT(!"Packet compression failed."); return; } - for (const auto Client : a_SendTo) - { - Client->SendChunkData(m_ChunkX, m_ChunkZ, ToSend); - } + a_Cache.Engaged = true; } diff --git a/src/Protocol/ChunkDataSerializer.h b/src/Protocol/ChunkDataSerializer.h index f02bdd4d0..cb39e27d6 100644 --- a/src/Protocol/ChunkDataSerializer.h +++ b/src/Protocol/ChunkDataSerializer.h @@ -1,5 +1,6 @@ #pragma once +#include "../ByteBuffer.h" #include "../ChunkData.h" #include "../Defines.h" @@ -18,47 +19,67 @@ Caches the serialized data for as long as this object lives, so that the same da other clients using the same protocol. */ class cChunkDataSerializer { + using ClientHandles = std::unordered_set; + + /** Enum to collapse protocol versions into a contiguous index. */ + enum class CacheVersion + { + v47, + v107, + v110, + v393, + v401, + v477, + + Count + }; + + /** A single cache entry containing the raw data, compressed data, and a validity flag. */ + struct ChunkDataCache + { + std::string PacketData; + std::string ToSend; + bool Engaged = false; + }; + public: - cChunkDataSerializer( - int a_ChunkX, - int a_ChunkZ, - const cChunkData & a_Data, - const unsigned char * a_BiomeData, - const eDimension a_Dimension - ); + cChunkDataSerializer(eDimension a_Dimension); - /** For each client, serializes the chunk into their protocol version and sends it. */ - void SendToClients(const std::unordered_set & a_SendTo); + /** For each client, serializes the chunk into their protocol version and sends it. + Parameters are the coordinates of the chunk to serialise, and the data and biome data read from the chunk. */ + void SendToClients(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, const ClientHandles & a_SendTo); protected: - void Serialize47 (const std::vector & a_SendTo); // Release 1.8 - void Serialize107(const std::vector & a_SendTo); // Release 1.9 - void Serialize110(const std::vector & a_SendTo); // Release 1.9.4 + /** Serialises the given chunk, storing the result into the given cache entry, and sends the data. + If the cache entry is already present, simply re-uses it. */ + inline void Serialize(cClientHandle * a_Client, int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData, CacheVersion a_CacheVersion); + + inline void Serialize47 (int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.8 + inline void Serialize107(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.9 + inline void Serialize110(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.9.4 template - void Serialize393(const std::vector & a_SendTo); // Release 1.13 - 1.13.2 - void Serialize477(const std::vector & a_SendTo); // Release 1.14 - 1.14.4 + inline void Serialize393(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.13 - 1.13.2 + inline void Serialize477(int a_ChunkX, int a_ChunkZ, const cChunkData & a_Data, const unsigned char * a_BiomeData); // Release 1.14 - 1.14.4 /** Writes all blocks in a chunk section into a series of Int64. Writes start from the bit directly subsequent to the previous write's end, possibly crossing over to the next Int64. */ template - inline void WriteSectionDataSeamless(cByteBuffer & a_Packet, const cChunkData::sChunkSection & a_Section, const UInt8 a_BitsPerEntry); + inline void WriteSectionDataSeamless(const cChunkData::sChunkSection & a_Section, const UInt8 a_BitsPerEntry); - /** Finalises the data, compresses it if required, and delivers it to all clients. */ - void CompressAndSend(cByteBuffer & a_Packet, const std::vector & a_SendTo); + /** Finalises the data, compresses it if required, and stores it into cache. */ + inline void CompressPacketInto(ChunkDataCache & a_Cache); - /** The coordinates of the chunk to serialise. */ - int m_ChunkX, m_ChunkZ; + /** A staging area used to construct the chunk packet, persistent to avoid reallocating. */ + cByteBuffer m_Packet; - /** The data read from the chunk, to be serialized. */ - const cChunkData & m_Data; - - /** The biomes in the chunk, to be serialized. */ - const unsigned char * m_BiomeData; - - /** The dimension where the chunk resides. */ + /** The dimension for the World this Serializer is tied to. */ const eDimension m_Dimension; + + /** A cache, mapping protocol version to a fully serialised chunk. + It is used during a single invocation of SendToClients with more than one client. */ + std::array(CacheVersion::Count)> m_Cache; } ;