mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-07 12:18:46 +00:00
StandardPacketBroadcaster: Improve performance when broadcasting small packets
In refactors during PM4, I stripped out packet buffer caching, as it was problematic when events alter packets in undetectable ways. However, I never cleaned this part of the code up properly after enabling DataPacketSendEvent to include multiple packets and multiple targets, so we were still individually encoding the packet(s) for every single session if the sum total of the sizes was below 256 bytes. This change encodes packets once in the StandardPacketBroadcaster and retains their buffers to post to the session's send buffer directly if the resulting batch is below compression threshold. This code is still not optimal (see ##5589), but fixing this brings broadcasting performance back to PM3 levels, without any of PM3's problems.
This commit is contained in:
parent
75bb4f8da6
commit
6a64486f55
@ -177,7 +177,7 @@ class NetworkSession{
|
|||||||
|
|
||||||
private ?EncryptionContext $cipher = null;
|
private ?EncryptionContext $cipher = null;
|
||||||
|
|
||||||
/** @var ClientboundPacket[] */
|
/** @var string[] */
|
||||||
private array $sendBuffer = [];
|
private array $sendBuffer = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -264,23 +264,6 @@ class NetworkSession{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ClientboundPacket[] $packets
|
|
||||||
*/
|
|
||||||
public static function encodePacketBatchTimed(BinaryStream $stream, PacketSerializerContext $context, array $packets) : void{
|
|
||||||
PacketBatch::encodeRaw($stream, array_map(function(ClientboundPacket $packet) use ($context) : string{
|
|
||||||
$timings = Timings::getEncodeDataPacketTimings($packet);
|
|
||||||
$timings->startTiming();
|
|
||||||
try{
|
|
||||||
$stream = PacketSerializer::encoder($context);
|
|
||||||
$packet->encode($stream);
|
|
||||||
return $stream->getBuffer();
|
|
||||||
}finally{
|
|
||||||
$timings->stopTiming();
|
|
||||||
}
|
|
||||||
}, $packets));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function onPlayerCreated(Player $player) : void{
|
private function onPlayerCreated(Player $player) : void{
|
||||||
if(!$this->isConnected()){
|
if(!$this->isConnected()){
|
||||||
//the remote player might have disconnected before spawn terrain generation was finished
|
//the remote player might have disconnected before spawn terrain generation was finished
|
||||||
@ -498,7 +481,7 @@ class NetworkSession{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->addToSendBuffer($packet);
|
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $packet));
|
||||||
if($immediate){
|
if($immediate){
|
||||||
$this->flushSendBuffer(true);
|
$this->flushSendBuffer(true);
|
||||||
}
|
}
|
||||||
@ -512,16 +495,24 @@ class NetworkSession{
|
|||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public function addToSendBuffer(ClientboundPacket $packet) : void{
|
public static function encodePacketTimed(PacketSerializer $serializer, ClientboundPacket $packet) : string{
|
||||||
$timings = Timings::getSendDataPacketTimings($packet);
|
$timings = Timings::getEncodeDataPacketTimings($packet);
|
||||||
$timings->startTiming();
|
$timings->startTiming();
|
||||||
try{
|
try{
|
||||||
$this->sendBuffer[] = $packet;
|
$packet->encode($serializer);
|
||||||
|
return $serializer->getBuffer();
|
||||||
}finally{
|
}finally{
|
||||||
$timings->stopTiming();
|
$timings->stopTiming();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function addToSendBuffer(string $buffer) : void{
|
||||||
|
$this->sendBuffer[] = $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
private function flushSendBuffer(bool $immediate = false) : void{
|
private function flushSendBuffer(bool $immediate = false) : void{
|
||||||
if(count($this->sendBuffer) > 0){
|
if(count($this->sendBuffer) > 0){
|
||||||
Timings::$playerNetworkSend->startTiming();
|
Timings::$playerNetworkSend->startTiming();
|
||||||
@ -534,7 +525,7 @@ class NetworkSession{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$stream = new BinaryStream();
|
$stream = new BinaryStream();
|
||||||
self::encodePacketBatchTimed($stream, $this->packetSerializerContext, $this->sendBuffer);
|
PacketBatch::encodeRaw($stream, $this->sendBuffer);
|
||||||
|
|
||||||
if($this->enableCompression){
|
if($this->enableCompression){
|
||||||
$promise = $this->server->prepareBatch(new PacketBatch($stream->getBuffer()), $this->compressor, $syncMode);
|
$promise = $this->server->prepareBatch(new PacketBatch($stream->getBuffer()), $this->compressor, $syncMode);
|
||||||
|
@ -23,25 +23,35 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\network\mcpe;
|
namespace pocketmine\network\mcpe;
|
||||||
|
|
||||||
|
use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||||
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
|
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
|
||||||
|
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
|
||||||
use pocketmine\Server;
|
use pocketmine\Server;
|
||||||
use pocketmine\utils\BinaryStream;
|
use pocketmine\utils\BinaryStream;
|
||||||
|
use function array_map;
|
||||||
use function spl_object_id;
|
use function spl_object_id;
|
||||||
|
|
||||||
final class StandardPacketBroadcaster implements PacketBroadcaster{
|
final class StandardPacketBroadcaster implements PacketBroadcaster{
|
||||||
public function __construct(private Server $server){}
|
public function __construct(private Server $server){}
|
||||||
|
|
||||||
public function broadcastPackets(array $recipients, array $packets) : void{
|
public function broadcastPackets(array $recipients, array $packets) : void{
|
||||||
$buffers = [];
|
$batchBuffers = [];
|
||||||
|
|
||||||
|
/** @var string[][] $packetBuffers */
|
||||||
|
$packetBuffers = [];
|
||||||
$compressors = [];
|
$compressors = [];
|
||||||
|
/** @var NetworkSession[][][] $targetMap */
|
||||||
$targetMap = [];
|
$targetMap = [];
|
||||||
foreach($recipients as $recipient){
|
foreach($recipients as $recipient){
|
||||||
$serializerContext = $recipient->getPacketSerializerContext();
|
$serializerContext = $recipient->getPacketSerializerContext();
|
||||||
$bufferId = spl_object_id($serializerContext);
|
$bufferId = spl_object_id($serializerContext);
|
||||||
if(!isset($buffers[$bufferId])){
|
if(!isset($batchBuffers[$bufferId])){
|
||||||
|
$packetBuffers[$bufferId] = array_map(function(ClientboundPacket $packet) use ($serializerContext) : string{
|
||||||
|
return NetworkSession::encodePacketTimed(PacketSerializer::encoder($serializerContext), $packet);
|
||||||
|
}, $packets);
|
||||||
$stream = new BinaryStream();
|
$stream = new BinaryStream();
|
||||||
NetworkSession::encodePacketBatchTimed($stream, $serializerContext, $packets);
|
PacketBatch::encodeRaw($stream, $packetBuffers[$bufferId]);
|
||||||
$buffers[$bufferId] = $stream->getBuffer();
|
$batchBuffers[$bufferId] = $stream->getBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: different compressors might be compatible, it might not be necessary to split them up by object
|
//TODO: different compressors might be compatible, it might not be necessary to split them up by object
|
||||||
@ -52,17 +62,17 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach($targetMap as $bufferId => $compressorMap){
|
foreach($targetMap as $bufferId => $compressorMap){
|
||||||
$buffer = $buffers[$bufferId];
|
$batchBuffer = $batchBuffers[$bufferId];
|
||||||
foreach($compressorMap as $compressorId => $compressorTargets){
|
foreach($compressorMap as $compressorId => $compressorTargets){
|
||||||
$compressor = $compressors[$compressorId];
|
$compressor = $compressors[$compressorId];
|
||||||
if(!$compressor->willCompress($buffer)){
|
if(!$compressor->willCompress($batchBuffer)){
|
||||||
foreach($compressorTargets as $target){
|
foreach($compressorTargets as $target){
|
||||||
foreach($packets as $pk){
|
foreach($packetBuffers[$bufferId] as $packetBuffer){
|
||||||
$target->addToSendBuffer($pk);
|
$target->addToSendBuffer($packetBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
$promise = $this->server->prepareBatch(new PacketBatch($buffer), $compressor);
|
$promise = $this->server->prepareBatch(new PacketBatch($batchBuffer), $compressor);
|
||||||
foreach($compressorTargets as $target){
|
foreach($compressorTargets as $target){
|
||||||
$target->queueCompressed($promise);
|
$target->queueCompressed($promise);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user