start making network compressors dynamic

this will facilitate future multi version support where compression types are different between versions
This commit is contained in:
Dylan K. Taylor 2020-04-28 14:47:01 +01:00
parent fe258740e3
commit d9e4783b24
8 changed files with 110 additions and 87 deletions

View File

@ -48,7 +48,7 @@ use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\TreeRoot;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\CompressBatchTask;
use pocketmine\network\mcpe\compression\Zlib as ZlibNetworkCompression;
use pocketmine\network\mcpe\compression\ZlibCompressor as ZlibNetworkCompression;
use pocketmine\network\mcpe\encryption\NetworkCipher;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
@ -912,17 +912,18 @@ class Server{
$this->asyncPool = new AsyncPool($poolSize, max(-1, (int) $this->getProperty("memory.async-worker-hard-limit", 256)), $this->autoloader, $this->logger);
$netCompressionThreshold = -1;
if($this->getProperty("network.batch-threshold", 256) >= 0){
ZlibNetworkCompression::$THRESHOLD = (int) $this->getProperty("network.batch-threshold", 256);
}else{
ZlibNetworkCompression::$THRESHOLD = -1;
$netCompressionThreshold = (int) $this->getProperty("network.batch-threshold", 256);
}
ZlibNetworkCompression::$LEVEL = $this->getProperty("network.compression-level", 7);
if(ZlibNetworkCompression::$LEVEL < 1 or ZlibNetworkCompression::$LEVEL > 9){
$this->logger->warning("Invalid network compression level " . ZlibNetworkCompression::$LEVEL . " set, setting to default 7");
ZlibNetworkCompression::$LEVEL = 7;
$netCompressionLevel = $this->getProperty("network.compression-level", 7);
if($netCompressionLevel < 1 or $netCompressionLevel > 9){
$this->logger->warning("Invalid network compression level $netCompressionLevel set, setting to default 7");
$netCompressionLevel = 7;
}
ZlibNetworkCompression::setInstance(new ZlibNetworkCompression($netCompressionLevel, $netCompressionThreshold, ZlibNetworkCompression::DEFAULT_MAX_DECOMPRESSION_SIZE));
$this->networkCompressionAsync = (bool) $this->getProperty("network.async-compression", true);
NetworkCipher::$ENABLED = (bool) $this->getProperty("network.enable-encryption", true);
@ -1251,7 +1252,7 @@ class Server{
$stream = PacketBatch::fromPackets(...$ev->getPackets());
if(ZlibNetworkCompression::$THRESHOLD < 0 or strlen($stream->getBuffer()) < ZlibNetworkCompression::$THRESHOLD){
if(!ZlibNetworkCompression::getInstance()->willCompress($stream->getBuffer())){
foreach($recipients as $target){
foreach($ev->getPackets() as $pk){
$target->addToSendBuffer($pk);
@ -1274,19 +1275,18 @@ class Server{
try{
Timings::$playerNetworkSendCompressTimer->startTiming();
$compressionLevel = ZlibNetworkCompression::$LEVEL;
$compressor = ZlibNetworkCompression::getInstance();
$buffer = $stream->getBuffer();
if(ZlibNetworkCompression::$THRESHOLD < 0 or strlen($buffer) < ZlibNetworkCompression::$THRESHOLD){
$compressionLevel = 0; //Do not compress packets under the threshold
if(!$compressor->willCompress($buffer)){
$forceSync = true;
}
$promise = new CompressBatchPromise();
if(!$forceSync and $this->networkCompressionAsync){
$task = new CompressBatchTask($buffer, $compressionLevel, $promise);
$task = new CompressBatchTask($buffer, $promise, $compressor);
$this->asyncPool->submitTask($task);
}else{
$promise->resolve(ZlibNetworkCompression::compress($buffer, $compressionLevel));
$promise->resolve($compressor->compress($buffer));
}
return $promise;

View File

@ -25,7 +25,7 @@ namespace pocketmine\crafting;
use pocketmine\item\Item;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\Zlib;
use pocketmine\network\mcpe\compression\ZlibCompressor;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\CraftingDataPacket;
@ -165,7 +165,7 @@ class CraftingManager{
}
$this->craftingDataCache = new CompressBatchPromise();
$this->craftingDataCache->resolve(Zlib::compress(PacketBatch::fromPackets($pk)->getBuffer()));
$this->craftingDataCache->resolve(ZlibCompressor::getInstance()->compress(PacketBatch::fromPackets($pk)->getBuffer()));
Timings::$craftingDataCacheRebuildTimer->stopTiming();
}

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\Zlib;
use pocketmine\network\mcpe\compression\ZlibCompressor;
use pocketmine\network\mcpe\protocol\LevelChunkPacket;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\serializer\ChunkSerializer;
@ -43,8 +43,8 @@ class ChunkRequestTask extends AsyncTask{
/** @var int */
protected $chunkZ;
/** @var int */
protected $compressionLevel;
/** @var ZlibCompressor */
protected $compressor;
/** @var string */
private $tiles = "";
@ -53,7 +53,7 @@ class ChunkRequestTask extends AsyncTask{
* @phpstan-param (\Closure() : void)|null $onError
*/
public function __construct(int $chunkX, int $chunkZ, Chunk $chunk, CompressBatchPromise $promise, ?\Closure $onError = null){
$this->compressionLevel = Zlib::$LEVEL;
$this->compressor = ZlibCompressor::getInstance(); //TODO: this should be injectable
$this->chunk = FastChunkSerializer::serializeWithoutLight($chunk);
$this->chunkX = $chunkX;
@ -68,7 +68,7 @@ class ChunkRequestTask extends AsyncTask{
$chunk = FastChunkSerializer::deserialize($this->chunk);
$subCount = ChunkSerializer::getSubChunkCount($chunk);
$payload = ChunkSerializer::serialize($chunk, $this->tiles);
$this->setResult(Zlib::compress(PacketBatch::fromPackets(LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $subCount, $payload))->getBuffer(), $this->compressionLevel));
$this->setResult($this->compressor->compress(PacketBatch::fromPackets(LevelChunkPacket::withoutCache($this->chunkX, $this->chunkZ, $subCount, $payload))->getBuffer()));
}
public function onError() : void{

View File

@ -36,7 +36,7 @@ use pocketmine\math\Vector3;
use pocketmine\network\BadPacketException;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\DecompressionException;
use pocketmine\network\mcpe\compression\Zlib;
use pocketmine\network\mcpe\compression\ZlibCompressor;
use pocketmine\network\mcpe\convert\SkinAdapterSingleton;
use pocketmine\network\mcpe\convert\TypeConverter;
use pocketmine\network\mcpe\encryption\DecryptionException;
@ -283,7 +283,7 @@ class NetworkSession{
Timings::$playerNetworkReceiveDecompressTimer->startTiming();
try{
$stream = new PacketBatch(Zlib::decompress($payload));
$stream = new PacketBatch(ZlibCompressor::getInstance()->decompress($payload)); //TODO: make this dynamic
}catch(DecompressionException $e){
$this->logger->debug("Failed to decompress packet: " . base64_encode($payload));
//TODO: this isn't incompatible game version if we already established protocol version

View File

@ -29,19 +29,19 @@ class CompressBatchTask extends AsyncTask{
private const TLS_KEY_PROMISE = "promise";
/** @var int */
private $level;
/** @var string */
private $data;
/** @var ZlibCompressor */
private $compressor;
public function __construct(string $data, int $compressionLevel, CompressBatchPromise $promise){
public function __construct(string $data, CompressBatchPromise $promise, ZlibCompressor $compressor){
$this->data = $data;
$this->level = $compressionLevel;
$this->compressor = $compressor;
$this->storeLocal(self::TLS_KEY_PROMISE, $promise);
}
public function onRun() : void{
$this->setResult(Zlib::compress($this->data, $this->level));
$this->setResult($this->compressor->compress($this->data));
}
public function onCompletion() : void{

View File

@ -1,59 +0,0 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\compression;
use function zlib_decode;
use function zlib_encode;
use const ZLIB_ENCODING_DEFLATE;
final class Zlib{
/** @var int */
public static $LEVEL = 7;
/** @var int */
public static $THRESHOLD = 256;
private function __construct(){
}
/**
* @param int $maxDecodedLength default 2MB
*
* @throws DecompressionException
*/
public static function decompress(string $payload, int $maxDecodedLength = 1024 * 1024 * 2) : string{
$result = @zlib_decode($payload, $maxDecodedLength);
if($result === false){
throw new DecompressionException("Failed to decompress data");
}
return $result;
}
/**
* @param int $compressionLevel
*/
public static function compress(string $payload, ?int $compressionLevel = null) : string{
return zlib_encode($payload, ZLIB_ENCODING_DEFLATE, $compressionLevel ?? self::$LEVEL);
}
}

View File

@ -0,0 +1,77 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network\mcpe\compression;
use pocketmine\utils\SingletonTrait;
use function strlen;
use function zlib_decode;
use function zlib_encode;
use const ZLIB_ENCODING_DEFLATE;
final class ZlibCompressor{
use SingletonTrait;
public const DEFAULT_LEVEL = 7;
public const DEFAULT_THRESHOLD = 256;
public const DEFAULT_MAX_DECOMPRESSION_SIZE = 2 * 1024 * 1024;
/**
* @see SingletonTrait::make()
*/
private static function make() : self{
return new self(self::DEFAULT_LEVEL, self::DEFAULT_THRESHOLD, self::DEFAULT_MAX_DECOMPRESSION_SIZE);
}
/** @var int */
private $level;
/** @var int */
private $threshold;
/** @var int */
private $maxDecompressionSize;
public function __construct(int $level, int $minCompressionSize, int $maxDecompressionSize){
$this->level = $level;
$this->threshold = $minCompressionSize;
$this->maxDecompressionSize = $maxDecompressionSize;
}
public function willCompress(string $data) : bool{
return $this->threshold > -1 and strlen($data) >= $this->threshold;
}
/**
* @throws DecompressionException
*/
public function decompress(string $payload) : string{
$result = @zlib_decode($payload, $this->maxDecompressionSize);
if($result === false){
throw new DecompressionException("Failed to decompress data");
}
return $result;
}
public function compress(string $payload) : string{
return zlib_encode($payload, ZLIB_ENCODING_DEFLATE, $this->willCompress($payload) ? $this->level : 0);
}
}

View File

@ -70,6 +70,11 @@ parameters:
count: 1
path: ../../../src/network/mcpe/protocol/types/entity/EntityMetadataCollection.php
-
message: "#^Class pocketmine\\\\network\\\\mcpe\\\\compression\\\\ZlibCompressor constructor invoked with 0 parameters, 3 required\\.$#"
count: 1
path: ../../../src/network/mcpe/compression/ZlibCompressor.php
-
message: "#^Strict comparison using \\!\\=\\= between string and false will always evaluate to true\\.$#"
count: 1