From b9a1ef1357e6b784f5f2de15e5a0b7bc31e296a5 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 1 Mar 2024 17:07:19 +0000 Subject: [PATCH] Throttle resource pack sending using ack receipts this isn't the best solution, as it limits the download speed somewhat, but it's relatively simple and works quite well. closes #3127 --- .../handler/ResourcePacksPacketHandler.php | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index 3d413ee5a..b9089d146 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -50,11 +50,22 @@ use function substr; * packs to the client. */ class ResourcePacksPacketHandler extends PacketHandler{ - private const PACK_CHUNK_SIZE = 128 * 1024; //128KB + private const PACK_CHUNK_SIZE = 256 * 1024; //256KB + + /** + * Larger values allow downloading more chunks at the same time, increasing download speed, but the client may choke + * and cause the download speed to drop (due to ACKs taking too long to arrive). + */ + private const MAX_CONCURRENT_CHUNK_REQUESTS = 1; /** @var bool[][] uuid => [chunk index => hasSent] */ private array $downloadedChunks = []; + /** @phpstan-var \SplQueue */ + private \SplQueue $requestQueue; + + private int $activeRequests = 0; + /** * @phpstan-param \Closure() : void $completionCallback */ @@ -62,7 +73,9 @@ class ResourcePacksPacketHandler extends PacketHandler{ private NetworkSession $session, private ResourcePackManager $resourcePackManager, private \Closure $completionCallback - ){} + ){ + $this->requestQueue = new \SplQueue(); + } public function setUp() : void{ $resourcePackEntries = array_map(function(ResourcePack $pack) : ResourcePackInfoEntry{ @@ -176,8 +189,38 @@ class ResourcePacksPacketHandler extends PacketHandler{ $this->downloadedChunks[$packId][$packet->chunkIndex] = true; } - $this->session->sendDataPacket(ResourcePackChunkDataPacket::create($packId, $packet->chunkIndex, $offset, $pack->getPackChunk($offset, self::PACK_CHUNK_SIZE))); + $this->requestQueue->enqueue([$pack, $packet->chunkIndex]); + $this->processChunkRequestQueue(); return true; } + + private function processChunkRequestQueue() : void{ + if($this->activeRequests >= self::MAX_CONCURRENT_CHUNK_REQUESTS || $this->requestQueue->isEmpty()){ + return; + } + /** + * @var ResourcePack $pack + * @var int $chunkIndex + */ + [$pack, $chunkIndex] = $this->requestQueue->dequeue(); + + $packId = $pack->getPackId(); + $offset = $chunkIndex * self::PACK_CHUNK_SIZE; + $chunkData = $pack->getPackChunk($offset, self::PACK_CHUNK_SIZE); + $this->activeRequests++; + $this->session + ->sendDataPacketWithReceipt(ResourcePackChunkDataPacket::create($packId, $chunkIndex, $offset, $chunkData)) + ->onCompletion( + function() use ($packId, $chunkIndex) : void{ + $this->activeRequests--; + $this->processChunkRequestQueue(); + }, + function() : void{ + //this may have been rejected because of a disconnection - this will do nothing in that case + $this->disconnectWithError("Plugin interrupted sending of resource packs"); + } + ); + $this->session->getLogger()->debug("Sent resource pack $packId chunk $chunkIndex"); + } }