mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-06 09:56:06 +00:00
Merge remote-tracking branch 'origin/stable' into minor-next
This commit is contained in:
@ -30,12 +30,14 @@ use pocketmine\network\mcpe\protocol\LevelChunkPacket;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
|
||||
use pocketmine\network\mcpe\protocol\types\ChunkPosition;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\network\mcpe\serializer\ChunkSerializer;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\thread\NonThreadSafeValue;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\io\FastChunkSerializer;
|
||||
use function chr;
|
||||
|
||||
class ChunkRequestTask extends AsyncTask{
|
||||
private const TLS_KEY_PROMISE = "promise";
|
||||
@ -43,16 +45,22 @@ class ChunkRequestTask extends AsyncTask{
|
||||
protected string $chunk;
|
||||
protected int $chunkX;
|
||||
protected int $chunkZ;
|
||||
/** @phpstan-var DimensionIds::* */
|
||||
private int $dimensionId;
|
||||
/** @phpstan-var NonThreadSafeValue<Compressor> */
|
||||
protected NonThreadSafeValue $compressor;
|
||||
private string $tiles;
|
||||
|
||||
public function __construct(int $chunkX, int $chunkZ, Chunk $chunk, CompressBatchPromise $promise, Compressor $compressor){
|
||||
/**
|
||||
* @phpstan-param DimensionIds::* $dimensionId
|
||||
*/
|
||||
public function __construct(int $chunkX, int $chunkZ, int $dimensionId, Chunk $chunk, CompressBatchPromise $promise, Compressor $compressor){
|
||||
$this->compressor = new NonThreadSafeValue($compressor);
|
||||
|
||||
$this->chunk = FastChunkSerializer::serializeTerrain($chunk);
|
||||
$this->chunkX = $chunkX;
|
||||
$this->chunkZ = $chunkZ;
|
||||
$this->dimensionId = $dimensionId;
|
||||
$this->tiles = ChunkSerializer::serializeTiles($chunk);
|
||||
|
||||
$this->storeLocal(self::TLS_KEY_PROMISE, $promise);
|
||||
@ -60,14 +68,18 @@ class ChunkRequestTask extends AsyncTask{
|
||||
|
||||
public function onRun() : void{
|
||||
$chunk = FastChunkSerializer::deserializeTerrain($this->chunk);
|
||||
$subCount = ChunkSerializer::getSubChunkCount($chunk);
|
||||
$dimensionId = $this->dimensionId;
|
||||
|
||||
$subCount = ChunkSerializer::getSubChunkCount($chunk, $dimensionId);
|
||||
$converter = TypeConverter::getInstance();
|
||||
$encoderContext = new PacketSerializerContext($converter->getItemTypeDictionary());
|
||||
$payload = ChunkSerializer::serializeFullChunk($chunk, $converter->getBlockTranslator(), $encoderContext, $this->tiles);
|
||||
$payload = ChunkSerializer::serializeFullChunk($chunk, $dimensionId, $converter->getBlockTranslator(), $encoderContext, $this->tiles);
|
||||
|
||||
$stream = new BinaryStream();
|
||||
PacketBatch::encodePackets($stream, $encoderContext, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload)]);
|
||||
$this->setResult($this->compressor->deserialize()->compress($stream->getBuffer()));
|
||||
PacketBatch::encodePackets($stream, $encoderContext, [LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $dimensionId, $subCount, false, null, $payload)]);
|
||||
|
||||
$compressor = $this->compressor->deserialize();
|
||||
$this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($stream->getBuffer()));
|
||||
}
|
||||
|
||||
public function onCompletion() : void{
|
||||
|
@ -86,6 +86,7 @@ use pocketmine\network\mcpe\protocol\types\command\CommandEnum;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandOverload;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandParameter;
|
||||
use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
|
||||
use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerListEntry;
|
||||
use pocketmine\network\mcpe\protocol\types\PlayerPermissions;
|
||||
@ -119,6 +120,7 @@ use function implode;
|
||||
use function in_array;
|
||||
use function is_string;
|
||||
use function json_encode;
|
||||
use function ord;
|
||||
use function random_bytes;
|
||||
use function str_split;
|
||||
use function strcasecmp;
|
||||
@ -355,15 +357,27 @@ class NetworkSession{
|
||||
}
|
||||
}
|
||||
|
||||
if(strlen($payload) < 1){
|
||||
throw new PacketHandlingException("No bytes in payload");
|
||||
}
|
||||
|
||||
if($this->enableCompression){
|
||||
Timings::$playerNetworkReceiveDecompress->startTiming();
|
||||
try{
|
||||
$decompressed = $this->compressor->decompress($payload);
|
||||
}catch(DecompressionException $e){
|
||||
$this->logger->debug("Failed to decompress packet: " . base64_encode($payload));
|
||||
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
|
||||
}finally{
|
||||
Timings::$playerNetworkReceiveDecompress->stopTiming();
|
||||
$compressionType = ord($payload[0]);
|
||||
$compressed = substr($payload, 1);
|
||||
if($compressionType === CompressionAlgorithm::NONE){
|
||||
$decompressed = $compressed;
|
||||
}elseif($compressionType === $this->compressor->getNetworkId()){
|
||||
try{
|
||||
$decompressed = $this->compressor->decompress($compressed);
|
||||
}catch(DecompressionException $e){
|
||||
$this->logger->debug("Failed to decompress packet: " . base64_encode($compressed));
|
||||
throw PacketHandlingException::wrap($e, "Compressed packet batch decode error");
|
||||
}finally{
|
||||
Timings::$playerNetworkReceiveDecompress->stopTiming();
|
||||
}
|
||||
}else{
|
||||
throw new PacketHandlingException("Packet compressed with unexpected compression type $compressionType");
|
||||
}
|
||||
}else{
|
||||
$decompressed = $payload;
|
||||
|
2
src/network/mcpe/cache/ChunkCache.php
vendored
2
src/network/mcpe/cache/ChunkCache.php
vendored
@ -27,6 +27,7 @@ use pocketmine\math\Vector3;
|
||||
use pocketmine\network\mcpe\ChunkRequestTask;
|
||||
use pocketmine\network\mcpe\compression\CompressBatchPromise;
|
||||
use pocketmine\network\mcpe\compression\Compressor;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\world\ChunkListener;
|
||||
use pocketmine\world\ChunkListenerNoOpTrait;
|
||||
use pocketmine\world\format\Chunk;
|
||||
@ -116,6 +117,7 @@ class ChunkCache implements ChunkListener{
|
||||
new ChunkRequestTask(
|
||||
$chunkX,
|
||||
$chunkZ,
|
||||
DimensionIds::OVERWORLD, //TODO: not hardcode this
|
||||
$chunk,
|
||||
$this->caches[$chunkHash],
|
||||
$this->compressor
|
||||
|
@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe\compression;
|
||||
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\thread\NonThreadSafeValue;
|
||||
use function chr;
|
||||
|
||||
class CompressBatchTask extends AsyncTask{
|
||||
|
||||
@ -43,7 +44,8 @@ class CompressBatchTask extends AsyncTask{
|
||||
}
|
||||
|
||||
public function onRun() : void{
|
||||
$this->setResult($this->compressor->deserialize()->compress($this->data));
|
||||
$compressor = $this->compressor->deserialize();
|
||||
$this->setResult(chr($compressor->getNetworkId()) . $compressor->compress($this->data));
|
||||
}
|
||||
|
||||
public function onCompletion() : void{
|
||||
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\compression;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
|
||||
|
||||
interface Compressor{
|
||||
/**
|
||||
* @throws DecompressionException
|
||||
@ -31,6 +33,14 @@ interface Compressor{
|
||||
|
||||
public function compress(string $payload) : string;
|
||||
|
||||
/**
|
||||
* Returns the canonical ID of this compressor, used to tell the remote end how to decompress a packet compressed
|
||||
* with this compressor.
|
||||
*
|
||||
* @return CompressionAlgorithm::*
|
||||
*/
|
||||
public function getNetworkId() : int;
|
||||
|
||||
/**
|
||||
* Returns the minimum size of packet batch that the compressor will attempt to compress.
|
||||
*
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\compression;
|
||||
|
||||
use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
|
||||
use pocketmine\utils\SingletonTrait;
|
||||
use pocketmine\utils\Utils;
|
||||
use function function_exists;
|
||||
@ -75,4 +76,8 @@ final class ZlibCompressor implements Compressor{
|
||||
libdeflate_deflate_compress($payload, $level) :
|
||||
Utils::assumeNotFalse(zlib_encode($payload, ZLIB_ENCODING_RAW, $level), "ZLIB compression failed");
|
||||
}
|
||||
|
||||
public function getNetworkId() : int{
|
||||
return CompressionAlgorithm::ZLIB;
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ use pocketmine\network\mcpe\NetworkSession;
|
||||
use pocketmine\network\mcpe\protocol\NetworkSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\ProtocolInfo;
|
||||
use pocketmine\network\mcpe\protocol\RequestNetworkSettingsPacket;
|
||||
use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm;
|
||||
|
||||
final class SessionStartPacketHandler extends PacketHandler{
|
||||
|
||||
@ -50,7 +49,7 @@ final class SessionStartPacketHandler extends PacketHandler{
|
||||
//TODO: we're filling in the defaults to get pre-1.19.30 behaviour back for now, but we should explore the new options in the future
|
||||
$this->session->sendDataPacket(NetworkSettingsPacket::create(
|
||||
NetworkSettingsPacket::COMPRESS_EVERYTHING,
|
||||
CompressionAlgorithm::ZLIB,
|
||||
$this->session->getCompressor()->getNetworkId(),
|
||||
false,
|
||||
0,
|
||||
0
|
||||
|
@ -31,6 +31,7 @@ use pocketmine\network\mcpe\convert\BlockTranslator;
|
||||
use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||
use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
|
||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||
use pocketmine\utils\Binary;
|
||||
use pocketmine\utils\BinaryStream;
|
||||
use pocketmine\world\format\Chunk;
|
||||
@ -43,12 +44,34 @@ final class ChunkSerializer{
|
||||
//NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the min/max subchunk index expected in the protocol.
|
||||
* This has no relation to the world height supported by PM.
|
||||
*
|
||||
* @phpstan-param DimensionIds::* $dimensionId
|
||||
* @return int[]
|
||||
* @phpstan-return array{int, int}
|
||||
*/
|
||||
public static function getDimensionChunkBounds(int $dimensionId) : array{
|
||||
return match($dimensionId){
|
||||
DimensionIds::OVERWORLD => [-4, 19],
|
||||
DimensionIds::NETHER => [0, 7],
|
||||
DimensionIds::THE_END => [0, 15],
|
||||
default => throw new \InvalidArgumentException("Unknown dimension ID $dimensionId"),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of subchunks that will be sent from the given chunk.
|
||||
* Chunks are sent in a stack, so every chunk below the top non-empty one must be sent.
|
||||
*
|
||||
* @phpstan-param DimensionIds::* $dimensionId
|
||||
*/
|
||||
public static function getSubChunkCount(Chunk $chunk) : int{
|
||||
for($y = Chunk::MAX_SUBCHUNK_INDEX, $count = count($chunk->getSubChunks()); $y >= Chunk::MIN_SUBCHUNK_INDEX; --$y, --$count){
|
||||
public static function getSubChunkCount(Chunk $chunk, int $dimensionId) : int{
|
||||
//if the protocol world bounds ever exceed the PM supported bounds again in the future, we might need to
|
||||
//polyfill some stuff here
|
||||
[$minSubChunkIndex, $maxSubChunkIndex] = self::getDimensionChunkBounds($dimensionId);
|
||||
for($y = $maxSubChunkIndex, $count = $maxSubChunkIndex - $minSubChunkIndex + 1; $y >= $minSubChunkIndex; --$y, --$count){
|
||||
if($chunk->getSubChunk($y)->isEmptyFast()){
|
||||
continue;
|
||||
}
|
||||
@ -58,18 +81,23 @@ final class ChunkSerializer{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function serializeFullChunk(Chunk $chunk, BlockTranslator $blockTranslator, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{
|
||||
/**
|
||||
* @phpstan-param DimensionIds::* $dimensionId
|
||||
*/
|
||||
public static function serializeFullChunk(Chunk $chunk, int $dimensionId, BlockTranslator $blockTranslator, PacketSerializerContext $encoderContext, ?string $tiles = null) : string{
|
||||
$stream = PacketSerializer::encoder($encoderContext);
|
||||
|
||||
$subChunkCount = self::getSubChunkCount($chunk);
|
||||
$subChunkCount = self::getSubChunkCount($chunk, $dimensionId);
|
||||
$writtenCount = 0;
|
||||
for($y = Chunk::MIN_SUBCHUNK_INDEX; $writtenCount < $subChunkCount; ++$y, ++$writtenCount){
|
||||
|
||||
[$minSubChunkIndex, $maxSubChunkIndex] = self::getDimensionChunkBounds($dimensionId);
|
||||
for($y = $minSubChunkIndex; $writtenCount < $subChunkCount; ++$y, ++$writtenCount){
|
||||
self::serializeSubChunk($chunk->getSubChunk($y), $blockTranslator, $stream, false);
|
||||
}
|
||||
|
||||
$biomeIdMap = LegacyBiomeIdToStringIdMap::getInstance();
|
||||
//all biomes must always be written :(
|
||||
for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; ++$y){
|
||||
for($y = $minSubChunkIndex; $y <= $maxSubChunkIndex; ++$y){
|
||||
self::serializeBiomePalette($chunk->getSubChunk($y)->getBiomeArray(), $biomeIdMap, $stream);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user