1
0

ChunkDataSerializer: don't repeatedly mallocate

+ Store an instance of the class, the way it seems it is meant to be used.
This commit is contained in:
Tiger Wang 2020-08-21 18:22:04 +01:00
parent 440523b022
commit a473e8ba52
5 changed files with 216 additions and 171 deletions

View File

@ -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<cCli
return;
}
{
// Send:
cChunkDataSerializer Data(a_ChunkX, a_ChunkZ, m_Data, m_BiomeMap, m_World.GetDimension());
Data.SendToClients(a_Clients);
}
// Send:
m_Serializer.SendToClients(a_ChunkX, a_ChunkZ, m_Data, m_BiomeMap, a_Clients);
for (const auto Client : a_Clients)
{

View File

@ -27,6 +27,7 @@ Note that it may be called by world's BroadcastToChunk() if the client is still
#include "OSSupport/IsThread.h"
#include "ChunkDataCallback.h"
#include "Protocol/ChunkDataSerializer.h"
@ -109,6 +110,9 @@ protected:
cWorld & m_World;
/** An instance of a chunk serializer, held to maintain its internal cache. */
cChunkDataSerializer m_Serializer;
cCriticalSection m_CS;
std::priority_queue<sChunkQueue> m_SendChunks;
std::unordered_map<cChunkCoords, sSendChunk, cChunkCoordsHash> m_ChunkInfo;

View File

@ -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;
}

View File

@ -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<cClientHandle *> & 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<cProtocol::Version, std::vector<cClientHandle *>> ClientProtocolVersions;
for (const auto Client : a_SendTo)
{
const auto ClientProtocol = static_cast<cProtocol::Version>(Client->GetProtocolVersion());
ClientProtocolVersions[ClientProtocol].emplace_back(Client);
}
for (const auto & Entry : ClientProtocolVersions)
{
switch (Entry.first)
switch (static_cast<cProtocol::Version>(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<cClientHandle
case cProtocol::Version::v1_12_1:
case cProtocol::Version::v1_12_2:
{
Serialize110(Entry.second);
Serialize(Client, a_ChunkX, a_ChunkZ, a_Data, a_BiomeData, CacheVersion::v110);
continue;
}
case cProtocol::Version::v1_13:
{
Serialize393<&Palette393>(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<cClientHandle *> & 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<size_t>(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<unsigned char>(BlockType << 4) | BlockMeta);
Packet.WriteBEUInt8(static_cast<unsigned char>(BlockType >> 4));
m_Packet.WriteBEUInt8(static_cast<unsigned char>(BlockType << 4) | BlockMeta);
m_Packet.WriteBEUInt8(static_cast<unsigned char>(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<cClientHandle *> & 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<cClientHandle *> & 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<UInt32>(ChunkSize));
m_Packet.WriteVarInt32(static_cast<UInt32>(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<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<&PaletteLegacy>(Packet, a_Section, BitsPerEntry);
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(0); // Palette length is 0
m_Packet.WriteVarInt32(static_cast<UInt32>(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<cClientHandle *> & 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<cClientHandle *> & 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<UInt32>(ChunkSize));
m_Packet.WriteVarInt32(static_cast<UInt32>(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<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<&PaletteLegacy>(Packet, a_Section, BitsPerEntry);
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(0); // Palette length is 0
m_Packet.WriteVarInt32(static_cast<UInt32>(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<cClientHandle *> & a_S
template <auto Palette>
void cChunkDataSerializer::Serialize393(const std::vector<cClientHandle *> & 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<UInt32>(ChunkSectionDataArraySize)) + // Field containing "size of whole section", VarInt32, variable size
m_Packet.GetVarIntSize(static_cast<UInt32>(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<cClientHandle *> & 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<UInt32>(ChunkSize));
m_Packet.WriteVarInt32(static_cast<UInt32>(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<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<Palette>(Packet, a_Section, BitsPerEntry);
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<Palette>(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<cClientHandle *> & a_S
// Write the biome data
for (size_t i = 0; i != BiomeDataSize; i++)
{
Packet.WriteBEUInt32(static_cast<UInt32>(m_BiomeData[i]) & 0xff);
m_Packet.WriteBEUInt32(static_cast<UInt32>(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<cClientHandle *> & 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<cClientHandle *> & a_S
// std::array<Int64, 36> 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<cClientHandle *> & a_S
const size_t ChunkSectionSize = (
2 + // Block count, BEInt16, 2 bytes
1 + // Bits per entry, BEUInt8, 1 byte
Packet.GetVarIntSize(static_cast<UInt32>(ChunkSectionDataArraySize)) + // Field containing "size of whole section", VarInt32, variable size
m_Packet.GetVarIntSize(static_cast<UInt32>(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<UInt32>(ChunkSize));
m_Packet.WriteVarInt32(static_cast<UInt32>(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<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<&Palette477>(Packet, a_Section, BitsPerEntry);
m_Packet.WriteBEInt16(-1);
m_Packet.WriteBEUInt8(BitsPerEntry);
m_Packet.WriteVarInt32(static_cast<UInt32>(ChunkSectionDataArraySize));
WriteSectionDataSeamless<&Palette477>(a_Section, BitsPerEntry);
}
);
// Write the biome data
for (size_t i = 0; i != BiomeDataSize; i++)
{
Packet.WriteBEUInt32(static_cast<UInt32>(m_BiomeData[i]) & 0xff);
m_Packet.WriteBEUInt32(static_cast<UInt32>(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<cClientHandle *> & a_S
template <auto Palette>
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<cClientHandle *> & 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;
}

View File

@ -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<cClientHandle *>;
/** 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<cClientHandle *> & 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<cClientHandle *> & a_SendTo); // Release 1.8
void Serialize107(const std::vector<cClientHandle *> & a_SendTo); // Release 1.9
void Serialize110(const std::vector<cClientHandle *> & 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 <auto Palette>
void Serialize393(const std::vector<cClientHandle *> & a_SendTo); // Release 1.13 - 1.13.2
void Serialize477(const std::vector<cClientHandle *> & 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 <auto Palette>
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<cClientHandle *> & 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<ChunkDataCache, static_cast<size_t>(CacheVersion::Count)> m_Cache;
} ;