* Improve the level sending mechanism in the game.

Instead of sending a request for each chunk one by one, and in sequence, send all the level data in one block.

I know this is not the best solution since we are working with UDP here, but it's way faster than loading the level chunk by chunk. In a blink of an eye, the level has already loaded.
This commit is contained in:
iProgramInCpp
2023-08-12 23:14:13 +03:00
parent 944eb3ace4
commit 498fe048cd
12 changed files with 246 additions and 10 deletions

View File

@@ -373,6 +373,11 @@ uint8_t* ZlibInflateToMemory(uint8_t* pInput, size_t compressedSize, size_t deco
}
uint8_t* ZlibDeflateToMemory(uint8_t* pInput, size_t sizeBytes, size_t* compressedSizeOut)
{
return ZlibDeflateToMemoryLvl(pInput, sizeBytes, compressedSizeOut, Z_DEFAULT_COMPRESSION);
}
uint8_t* ZlibDeflateToMemoryLvl(uint8_t* pInput, size_t sizeBytes, size_t* compressedSizeOut, int level)
{
z_stream strm;
memset(&strm, 0, sizeof strm);
@@ -384,7 +389,7 @@ uint8_t* ZlibDeflateToMemory(uint8_t* pInput, size_t sizeBytes, size_t* compress
strm.opaque = Z_NULL;
// initialize deflation state machine
ret = deflateInit(&strm, Z_DEFAULT_COMPRESSION);
ret = deflateInit(&strm, level);
if (ret != Z_OK)
return nullptr;
@@ -394,7 +399,7 @@ uint8_t* ZlibDeflateToMemory(uint8_t* pInput, size_t sizeBytes, size_t* compress
uint8_t* pOut = new uint8_t[sizeBytes + ZLIB_PADDING_BYTES];
strm.avail_in = sizeBytes;
strm.next_in = pInput;
strm.avail_in = sizeBytes + ZLIB_PADDING_BYTES;
strm.avail_out = sizeBytes + ZLIB_PADDING_BYTES;
strm.next_out = pOut;
ret = deflate(&strm, Z_FINISH);

View File

@@ -567,6 +567,7 @@ bool DeleteDirectory(const std::string& name, bool unused);
// compress and decompress stuff with zlib: ( you must SAFE_DELETE_ARRAY what it returns )
uint8_t* ZlibInflateToMemory(uint8_t* pInput, size_t compressedSize, size_t decompressedSize);
uint8_t* ZlibDeflateToMemory(uint8_t* pInput, size_t sizeBytes, size_t *compressedSizeOut);
uint8_t* ZlibDeflateToMemoryLvl(uint8_t* pInput, size_t sizeBytes, size_t* compressedSizeOut, int level);
// things that we added:
#ifndef ORIGINAL_CODE

View File

@@ -26,6 +26,8 @@ ClientSideNetworkHandler::ClientSideNetworkHandler(Minecraft* pMinecraft, RakNet
m_pMinecraft = pMinecraft;
m_pRakNetInstance = pRakNetInstance;
m_pServerPeer = m_pRakNetInstance->getPeer();
m_chunksRequested = 0;
m_serverProtocolVersion = 0;
}
void ClientSideNetworkHandler::levelGenerated(Level* level)
@@ -108,6 +110,8 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, StartGa
m_pLevel->setTime(pStartGamePkt->m_time);
m_serverProtocolVersion = pStartGamePkt->m_version;
m_pMinecraft->setLevel(m_pLevel, "ClientSideNetworkHandler -> setLevel", pLocalPlayer);
}
@@ -326,10 +330,13 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, ChunkDa
pChunk->m_bUnsaved = true;
if (areAllChunksLoaded())
flushAllBufferedUpdates();
else
requestNextChunk();
if (m_serverProtocolVersion < 2)
{
if (areAllChunksLoaded())
flushAllBufferedUpdates();
else
requestNextChunk();
}
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, PlayerEquipmentPacket* pPlayerEquipmentPkt)
@@ -355,6 +362,99 @@ void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& rakGuid, PlayerE
pPlayer->m_pInventory->selectItemById(pPlayerEquipmentPkt->m_itemID);
}
void ClientSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, LevelDataPacket* packet)
{
int uncompMagic = 12847812, compMagic = 58712758, chunkSepMagic = 284787658;
RakNet::BitStream* bs = &packet->m_data, bs2;
int thing = 0;
bs->Read(thing);
if (thing != compMagic && thing != uncompMagic)
{
LogMsg("Error, invalid level data packet with magic %d", thing);
return;
}
if (thing == compMagic)
{
int uncompSize = 0, compSize = 0;
bs->Read(uncompSize);
bs->Read(compSize);
LogMsg("Decompressing level data. Compressed, uncompressed sizes: %d bytes, %d bytes", compSize, uncompSize);
uint8_t* pCompData = new uint8_t[compSize];
bs->Read((char*)pCompData, compSize);
uint8_t* pUncompData = ZlibInflateToMemory(pCompData, compSize, uncompSize);
delete[] pCompData;
if (!pUncompData)
{
LogMsg("Error, can't decompress level data");
return;
}
bs2.Reset();
bs2.Write((const char*)pUncompData, uncompSize);
bs2.ResetReadPointer();
bs = &bs2;
bs->Read(thing);
}
int chunksX = 0, chunksZ = 0;
bs->Read(chunksX);
bs->Read(chunksZ);
if (chunksX != C_MAX_CHUNKS_X || chunksZ != C_MAX_CHUNKS_Z)
{
LogMsg("Error, we don't yet support a level of size %d x %d chunks. Some chunks may disappear or be regenerated.", chunksX, chunksZ);
}
for (int x = 0; x < chunksX; x++)
{
for (int z = 0; z < chunksZ; z++)
{
uint8_t ptype = 0;
bs->Read(thing);
int dataSize = 0;
bs->Read(dataSize);
if (thing != chunkSepMagic)
{
_FAIL_BECAUSE_INVALID:
LogMsg("Error, aborting because level data is invalid. Reading chunk %d,%d. Thing=%d PType=%d",x,z,thing,ptype);
return;
}
LevelChunk* pChunk = m_pLevel->getChunk(x, z);
if (!pChunk)
LogMsg("No chunk at %d,%d", x, z);
RakNet::BitStream bs2;
bs2.Write(*bs, 8 * dataSize);
bs2.Read(ptype);
if (ptype != PACKET_CHUNK_DATA)
goto _FAIL_BECAUSE_INVALID;
ChunkDataPacket cdp(x, z, pChunk);
cdp.read(&bs2);
LogMsg("Chunk %d,%d", cdp.m_x, cdp.m_z);
if (pChunk)
handle(guid, &cdp);
while (m_pLevel->updateLights());
}
}
m_chunksRequested = 256;// all chunks are loaded!!
flushAllBufferedUpdates();
}
bool ClientSideNetworkHandler::areAllChunksLoaded()
{
return m_chunksRequested > 255;
@@ -368,8 +468,15 @@ void ClientSideNetworkHandler::requestNextChunk()
// @BUG: The return value of areAllChunksLoaded() is actually true even before the
// 256th chunk is loaded.
m_pRakNetInstance->send(new RequestChunkPacket(m_chunksRequested % 16, m_chunksRequested / 16));
m_chunksRequested++;
if (m_serverProtocolVersion < 2)
{
m_pRakNetInstance->send(new RequestChunkPacket(m_chunksRequested % 16, m_chunksRequested / 16));
m_chunksRequested++;
}
else
{
m_pRakNetInstance->send(new RequestChunkPacket(-9999, -9999));
}
}
void ClientSideNetworkHandler::flushAllBufferedUpdates()

View File

@@ -42,6 +42,7 @@ public:
void handle(const RakNet::RakNetGUID&, UpdateBlockPacket*) override;
void handle(const RakNet::RakNetGUID&, ChunkDataPacket*) override;
void handle(const RakNet::RakNetGUID&, PlayerEquipmentPacket*) override;
void handle(const RakNet::RakNetGUID&, LevelDataPacket*) override;
bool areAllChunksLoaded();
void requestNextChunk();
@@ -56,6 +57,7 @@ private:
RakNet::RakNetGUID m_serverGUID;
int m_field_24;
std::vector<SBufferedBlockUpdate> m_bufferedBlockUpdates;
int m_chunksRequested = 0;
int m_chunksRequested;
int m_serverProtocolVersion;
};

View File

@@ -36,6 +36,9 @@ Packet* MinecraftPackets::createPacket(int type)
return new ChunkDataPacket;
case PACKET_PLAYER_EQUIPMENT:
return new PlayerEquipmentPacket;
case PACKET_LEVEL_DATA:
return new LevelDataPacket;
}
return nullptr;

View File

@@ -76,3 +76,7 @@ void NetEventCallback::handle(const RakNet::RakNetGUID& guid, ChunkDataPacket* p
void NetEventCallback::handle(const RakNet::RakNetGUID& guid, PlayerEquipmentPacket* packet)
{
}
void NetEventCallback::handle(const RakNet::RakNetGUID& guid, LevelDataPacket* packet)
{
}

View File

@@ -23,8 +23,10 @@ class UpdateBlockPacket;
class RequestChunkPacket;
class ChunkDataPacket;
class PlayerEquipmentPacket;
class LevelDataPacket;
#include "world/level/Level.hpp"
class Level;
class LevelChunk;
class NetEventCallback
{
@@ -47,5 +49,6 @@ public:
virtual void handle(const RakNet::RakNetGUID&, RequestChunkPacket*);
virtual void handle(const RakNet::RakNetGUID&, ChunkDataPacket*);
virtual void handle(const RakNet::RakNetGUID&, PlayerEquipmentPacket*);
virtual void handle(const RakNet::RakNetGUID&, LevelDataPacket*);
};

View File

@@ -16,6 +16,7 @@
#include "NetEventCallback.hpp"
class NetEventCallback;
class Level;
class LevelChunk;
enum ePacketType : uint8_t
@@ -32,6 +33,8 @@ enum ePacketType : uint8_t
PACKET_REQUEST_CHUNK,
PACKET_CHUNK_DATA,
PACKET_PLAYER_EQUIPMENT,
PACKET_LEVEL_DATA = 200,
};
class Packet
@@ -227,6 +230,19 @@ public:
LevelChunk* m_pChunk;
};
class LevelDataPacket : public Packet
{
public:
LevelDataPacket() {}
LevelDataPacket(Level* level) : m_pLevel(level) {}
void handle(const RakNet::RakNetGUID&, NetEventCallback* pCallback) override;
void write(RakNet::BitStream*);
void read(RakNet::BitStream*);
public:
RakNet::BitStream m_data;
Level* m_pLevel;
};
class PlayerEquipmentPacket : public Packet
{
public:

View File

@@ -113,7 +113,7 @@ void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, LoginPacke
sgp.field_10 = pPlayer->m_pos.x;
sgp.field_14 = pPlayer->m_pos.y - pPlayer->field_84;
sgp.field_18 = pPlayer->m_pos.z;
sgp.m_version = 1;
sgp.m_version = 2;
sgp.m_time = m_pLevel->getTime();
RakNet::BitStream sgpbs;
@@ -292,6 +292,12 @@ void ServerSideNetworkHandler::handle(const RakNet::RakNetGUID& guid, RequestChu
{
puts_ignorable("RequestChunkPacket");
if (packet->m_x == -9999)
{
m_pRakNetInstance->send(guid, new LevelDataPacket(m_pLevel));
return;
}
LevelChunk* pChunk = m_pLevel->getChunk(packet->m_x, packet->m_z);
if (!pChunk)
{

View File

@@ -44,6 +44,7 @@ void ChunkDataPacket::write(RakNet::BitStream* bs)
}
}
m_data.ResetReadPointer();
bs->Write(m_data);
}
@@ -52,4 +53,5 @@ void ChunkDataPacket::read(RakNet::BitStream* bs)
bs->Read(m_x);
bs->Read(m_z);
bs->Read(m_data);
m_data.ResetReadPointer();
}

View File

@@ -0,0 +1,86 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#include "../Packet.hpp"
#include "world/level/levelgen/chunk/LevelChunk.hpp"
void LevelDataPacket::handle(const RakNet::RakNetGUID& guid, NetEventCallback* pCallback)
{
pCallback->handle(guid, this);
}
void LevelDataPacket::write(RakNet::BitStream* pbs)
{
// @TODO: Maybe offload this to a different 'worker thread'? Or maybe just the compression job?
// send a crapton of them in a raw packet(why? Laziness)
int chunksX = C_MAX_CHUNKS_X;
int chunksZ = C_MAX_CHUNKS_Z;
int minus9999 = -9999;
RakNet::BitStream bs;
pbs->Write(PACKET_LEVEL_DATA);
int uncompMagic = 12847812, compMagic = 58712758, chunkSepMagic = 284787658;
bs.Write(uncompMagic);
bs.Write(chunksX);
bs.Write(chunksZ);
for (int x = 0; x < chunksX; x++)
{
for (int z = 0; z < chunksZ; z++)
{
bs.Write(chunkSepMagic);
RakNet::BitStream bs2;
LevelChunk* pChunk = m_pLevel->getChunk(x, z);
ChunkDataPacket cdp(x, z, pChunk);
cdp.write(&bs2);
int dataSize = int(bs2.GetNumberOfBytesUsed());
bs.Write(dataSize);
bs.Write((const char*)bs2.GetData(), dataSize);
}
}
// compress it
size_t compSize = 0;
size_t uncompSize = bs.GetNumberOfBytesUsed();
uint8_t* pCompressedData = nullptr;
if (uncompSize > 1024)
{
// takes about 5ms on release to compress everything on L4, compression ratio is around 14-15%.
// Increase or decrease this depending on your server's load.
pCompressedData = ZlibDeflateToMemoryLvl(bs.GetData(), uncompSize, &compSize, 4);
}
if (pCompressedData)
{
float ratio = 100.0f * float(compSize) / float(uncompSize);
LogMsg("Compression ratio: %.2f (%d comp, %d uncomp)", ratio, int(compSize), int(uncompSize));
int cs2 = int(compSize), us2 = int(uncompSize);
bs.Reset();
bs.Write(compMagic);
bs.Write(us2);
bs.Write(cs2);
bs.Write((const char*)pCompressedData, compSize);
SAFE_DELETE_ARRAY(pCompressedData);
}
else
{
LogMsg("Level not compressed.");
}
pbs->Write(bs);
return;
}
void LevelDataPacket::read(RakNet::BitStream* bs)
{
bs->Read(m_data);
}

View File

@@ -433,6 +433,7 @@
<ClCompile Include="..\source\client\network\NetEventCallback.cpp" />
<ClCompile Include="..\source\client\network\packets\AddPlayerPacket.cpp" />
<ClCompile Include="..\source\client\network\packets\ChunkDataPacket.cpp" />
<ClCompile Include="..\source\client\network\packets\LevelDataPacket.cpp" />
<ClCompile Include="..\source\client\network\packets\LoginPacket.cpp" />
<ClCompile Include="..\source\client\network\packets\MessagePacket.cpp" />
<ClCompile Include="..\source\client\network\packets\MovePlayerPacket.cpp" />