diff --git a/build/php b/build/php index fb297eb51..b2207cf70 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit fb297eb511862ef3d4ca0aff5256a8caf5513cb4 +Subproject commit b2207cf70d3fc5b81f50a655aba4730fcf40a37c diff --git a/changelogs/4.15.md b/changelogs/4.15.md index b80115e88..c8543a0f1 100644 --- a/changelogs/4.15.md +++ b/changelogs/4.15.md @@ -12,3 +12,11 @@ Released 17th February 2023. ## General - Added support for Minecraft: Bedrock Edition 1.19.62. - Removed support for older versions. + +# 4.15.1 +Released 21st February 2023. + +## Fixes +- Fixed dropped items not despawning when in non-ticking chunks. +- Fixed dropped items not despawning if an infinite pickup delay is set. +- Fixed infinite despawn delay (never despawn) being ignored for dropped items. diff --git a/composer.json b/composer.json index ed15c3171..b3b8ea688 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "pocketmine/bedrock-block-upgrade-schema": "^1.0.0", "pocketmine/bedrock-data": "dev-modern-world-support@dev", "pocketmine/bedrock-item-upgrade-schema": "^1.0.0", - "pocketmine/bedrock-protocol": "~19.2.0+bedrock-1.19.62", + "pocketmine/bedrock-protocol": "~19.3.0+bedrock-1.19.62", "pocketmine/binaryutils": "^0.2.1", "pocketmine/callback-validator": "^1.0.2", "pocketmine/classloader": "^0.3.0", @@ -55,7 +55,7 @@ "symfony/filesystem": "^5.4" }, "require-dev": { - "phpstan/phpstan": "1.9.17", + "phpstan/phpstan": "1.9.18", "phpstan/phpstan-phpunit": "^1.1.0", "phpstan/phpstan-strict-rules": "^1.2.0", "phpunit/phpunit": "^9.2" diff --git a/composer.lock b/composer.lock index ab7e807e0..370372c9f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3f98a352bac38a5e4cac66484ea8f8c7", + "content-hash": "ea992cf9b9be39c9d5251b7769f9d044", "packages": [ { "name": "adhocore/json-comment", @@ -328,16 +328,16 @@ }, { "name": "pocketmine/bedrock-protocol", - "version": "19.2.0+bedrock-1.19.62", + "version": "19.3.0+bedrock-1.19.62", "source": { "type": "git", "url": "https://github.com/pmmp/BedrockProtocol.git", - "reference": "a156db582d0b1a6c20c9d9cc9b1df7ef907efd0b" + "reference": "a5bf4753c7f30f781c4541918e238f5bb637e7ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a156db582d0b1a6c20c9d9cc9b1df7ef907efd0b", - "reference": "a156db582d0b1a6c20c9d9cc9b1df7ef907efd0b", + "url": "https://api.github.com/repos/pmmp/BedrockProtocol/zipball/a5bf4753c7f30f781c4541918e238f5bb637e7ad", + "reference": "a5bf4753c7f30f781c4541918e238f5bb637e7ad", "shasum": "" }, "require": { @@ -369,9 +369,9 @@ "description": "An implementation of the Minecraft: Bedrock Edition protocol in PHP", "support": { "issues": "https://github.com/pmmp/BedrockProtocol/issues", - "source": "https://github.com/pmmp/BedrockProtocol/tree/19.2.0+bedrock-1.19.62" + "source": "https://github.com/pmmp/BedrockProtocol/tree/19.3.0+bedrock-1.19.62" }, - "time": "2023-02-17T16:32:49+00:00" + "time": "2023-02-19T16:11:03+00:00" }, { "name": "pocketmine/binaryutils", @@ -1775,16 +1775,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.9.17", + "version": "1.9.18", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2" + "reference": "f2d5cf71be91172a57c649770b73c20ebcffb0bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/204e459e7822f2c586463029f5ecec31bb45a1f2", - "reference": "204e459e7822f2c586463029f5ecec31bb45a1f2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f2d5cf71be91172a57c649770b73c20ebcffb0bf", + "reference": "f2d5cf71be91172a57c649770b73c20ebcffb0bf", "shasum": "" }, "require": { @@ -1814,7 +1814,7 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.9.17" + "source": "https://github.com/phpstan/phpstan/tree/1.9.18" }, "funding": [ { @@ -1830,7 +1830,7 @@ "type": "tidelift" } ], - "time": "2023-02-08T12:25:00+00:00" + "time": "2023-02-17T15:01:27+00:00" }, { "name": "phpstan/phpstan-phpunit", diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 1397ee3a1..b4cc56cfc 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -774,6 +774,15 @@ abstract class Entity{ $this->server->broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion())]); } + public function getGravity() : float{ + return $this->gravity; + } + + public function setGravity(float $gravity) : void{ + Utils::checkFloatNotInfOrNaN("gravity", $gravity); + $this->gravity = $gravity; + } + public function hasGravity() : bool{ return $this->gravityEnabled; } diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index 97aa227f9..4a9eef0d4 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -106,33 +106,42 @@ class ItemEntity extends Entity{ $hasUpdate = parent::entityBaseTick($tickDiff); - if(!$this->isFlaggedForDespawn() && $this->pickupDelay !== self::NEVER_DESPAWN){ //Infinite delay + if($this->isFlaggedForDespawn()){ + return $hasUpdate; + } + + if($this->pickupDelay !== self::NEVER_DESPAWN && $this->pickupDelay > 0){ //Infinite delay + $hasUpdate = true; $this->pickupDelay -= $tickDiff; if($this->pickupDelay < 0){ $this->pickupDelay = 0; } - if($this->hasMovementUpdate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){ - $mergeable = [$this]; //in case the merge target ends up not being this - $mergeTarget = $this; - foreach($this->getWorld()->getNearbyEntities($this->boundingBox->expandedCopy(0.5, 0.5, 0.5), $this) as $entity){ - if(!$entity instanceof ItemEntity || $entity->isFlaggedForDespawn()){ - continue; - } + } - if($entity->isMergeable($this)){ - $mergeable[] = $entity; - if($entity->item->getCount() > $mergeTarget->item->getCount()){ - $mergeTarget = $entity; - } - } + if($this->hasMovementUpdate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){ + $mergeable = [$this]; //in case the merge target ends up not being this + $mergeTarget = $this; + foreach($this->getWorld()->getNearbyEntities($this->boundingBox->expandedCopy(0.5, 0.5, 0.5), $this) as $entity){ + if(!$entity instanceof ItemEntity || $entity->isFlaggedForDespawn()){ + continue; } - foreach($mergeable as $itemEntity){ - if($itemEntity !== $mergeTarget){ - $itemEntity->tryMergeInto($mergeTarget); + + if($entity->isMergeable($this)){ + $mergeable[] = $entity; + if($entity->item->getCount() > $mergeTarget->item->getCount()){ + $mergeTarget = $entity; } } } + foreach($mergeable as $itemEntity){ + if($itemEntity !== $mergeTarget){ + $itemEntity->tryMergeInto($mergeTarget); + } + } + } + if(!$this->isFlaggedForDespawn() && $this->despawnDelay !== self::NEVER_DESPAWN){ + $hasUpdate = true; $this->despawnDelay -= $tickDiff; if($this->despawnDelay <= 0){ $ev = new ItemDespawnEvent($this); @@ -141,7 +150,6 @@ class ItemEntity extends Entity{ $this->despawnDelay = self::DEFAULT_DESPAWN_DELAY; }else{ $this->flagForDespawn(); - $hasUpdate = true; } } } diff --git a/src/network/mcpe/ChunkRequestTask.php b/src/network/mcpe/ChunkRequestTask.php index b466618c9..fe30126ae 100644 --- a/src/network/mcpe/ChunkRequestTask.php +++ b/src/network/mcpe/ChunkRequestTask.php @@ -34,6 +34,7 @@ use pocketmine\network\mcpe\protocol\types\ChunkPosition; 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; @@ -68,7 +69,10 @@ class ChunkRequestTask extends AsyncTask{ $subCount = ChunkSerializer::getSubChunkCount($chunk); $encoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); $payload = ChunkSerializer::serializeFullChunk($chunk, RuntimeBlockMapping::getInstance(), $encoderContext, $this->tiles); - $this->setResult($this->compressor->deserialize()->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload))->getBuffer())); + + $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())); } public function onError() : void{ diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index fc34275c4..b8e047c46 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -120,6 +120,7 @@ use pocketmine\player\XboxLivePlayerInfo; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; +use pocketmine\utils\BinaryStream; use pocketmine\utils\ObjectSet; use pocketmine\utils\TextFormat; use pocketmine\utils\Utils; @@ -177,7 +178,7 @@ class NetworkSession{ private ?EncryptionContext $cipher = null; - /** @var Packet[] */ + /** @var ClientboundPacket[] */ private array $sendBuffer = []; /** @@ -265,6 +266,23 @@ 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{ if(!$this->isConnected()){ //the remote player might have disconnected before spawn terrain generation was finished @@ -360,56 +378,67 @@ class NetworkSession{ return; } - if($this->incomingPacketBatchBudget <= 0){ - $this->updatePacketBudget(); - if($this->incomingPacketBatchBudget <= 0){ - throw new PacketHandlingException("Receiving packets too fast"); - } - } - $this->incomingPacketBatchBudget--; - - if($this->cipher !== null){ - Timings::$playerNetworkReceiveDecrypt->startTiming(); - try{ - $payload = $this->cipher->decrypt($payload); - }catch(DecryptionException $e){ - $this->logger->debug("Encrypted packet: " . base64_encode($payload)); - throw PacketHandlingException::wrap($e, "Packet decryption error"); - }finally{ - Timings::$playerNetworkReceiveDecrypt->stopTiming(); - } - } - - 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(); - } - }else{ - $decompressed = $payload; - } - + Timings::$playerNetworkReceive->startTiming(); try{ - foreach((new PacketBatch($decompressed))->getPackets($this->packetPool, $this->packetSerializerContext, 1300) as [$packet, $buffer]){ - if($packet === null){ - $this->logger->debug("Unknown packet: " . base64_encode($buffer)); - throw new PacketHandlingException("Unknown packet received"); - } - try{ - $this->handleDataPacket($packet, $buffer); - }catch(PacketHandlingException $e){ - $this->logger->debug($packet->getName() . ": " . base64_encode($buffer)); - throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName()); + if($this->incomingPacketBatchBudget <= 0){ + $this->updatePacketBudget(); + if($this->incomingPacketBatchBudget <= 0){ + throw new PacketHandlingException("Receiving packets too fast"); } } - }catch(PacketDecodeException $e){ - $this->logger->logException($e); - throw PacketHandlingException::wrap($e, "Packet batch decode error"); + $this->incomingPacketBatchBudget--; + + if($this->cipher !== null){ + Timings::$playerNetworkReceiveDecrypt->startTiming(); + try{ + $payload = $this->cipher->decrypt($payload); + }catch(DecryptionException $e){ + $this->logger->debug("Encrypted packet: " . base64_encode($payload)); + throw PacketHandlingException::wrap($e, "Packet decryption error"); + }finally{ + Timings::$playerNetworkReceiveDecrypt->stopTiming(); + } + } + + 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(); + } + }else{ + $decompressed = $payload; + } + + try{ + $stream = new BinaryStream($decompressed); + $count = 0; + foreach(PacketBatch::decodeRaw($stream) as $buffer){ + if(++$count > 1300){ + throw new PacketHandlingException("Too many packets in batch"); + } + $packet = $this->packetPool->getPacket($buffer); + if($packet === null){ + $this->logger->debug("Unknown packet: " . base64_encode($buffer)); + throw new PacketHandlingException("Unknown packet received"); + } + try{ + $this->handleDataPacket($packet, $buffer); + }catch(PacketHandlingException $e){ + $this->logger->debug($packet->getName() . ": " . base64_encode($buffer)); + throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName()); + } + } + }catch(PacketDecodeException $e){ + $this->logger->logException($e); + throw PacketHandlingException::wrap($e, "Packet batch decode error"); + } + }finally{ + Timings::$playerNetworkReceive->stopTiming(); } } @@ -500,22 +529,29 @@ class NetworkSession{ private function flushSendBuffer(bool $immediate = false) : void{ if(count($this->sendBuffer) > 0){ - $syncMode = null; //automatic - if($immediate){ - $syncMode = true; - }elseif($this->forceAsyncCompression){ - $syncMode = false; - } + Timings::$playerNetworkSend->startTiming(); + try{ + $syncMode = null; //automatic + if($immediate){ + $syncMode = true; + }elseif($this->forceAsyncCompression){ + $syncMode = false; + } - $batch = PacketBatch::fromPackets($this->packetSerializerContext, ...$this->sendBuffer); - if($this->enableCompression){ - $promise = $this->server->prepareBatch($batch, $this->compressor, $syncMode); - }else{ - $promise = new CompressBatchPromise(); - $promise->resolve($batch->getBuffer()); + $stream = new BinaryStream(); + self::encodePacketBatchTimed($stream, $this->packetSerializerContext, $this->sendBuffer); + + if($this->enableCompression){ + $promise = $this->server->prepareBatch(new PacketBatch($stream->getBuffer()), $this->compressor, $syncMode); + }else{ + $promise = new CompressBatchPromise(); + $promise->resolve($stream->getBuffer()); + } + $this->sendBuffer = []; + $this->queueCompressedNoBufferFlush($promise, $immediate); + }finally{ + Timings::$playerNetworkSend->stopTiming(); } - $this->sendBuffer = []; - $this->queueCompressedNoBufferFlush($promise, $immediate); } } @@ -528,35 +564,45 @@ class NetworkSession{ } public function queueCompressed(CompressBatchPromise $payload, bool $immediate = false) : void{ - $this->flushSendBuffer($immediate); //Maintain ordering if possible - $this->queueCompressedNoBufferFlush($payload, $immediate); + Timings::$playerNetworkSend->startTiming(); + try{ + $this->flushSendBuffer($immediate); //Maintain ordering if possible + $this->queueCompressedNoBufferFlush($payload, $immediate); + }finally{ + Timings::$playerNetworkSend->stopTiming(); + } } private function queueCompressedNoBufferFlush(CompressBatchPromise $payload, bool $immediate = false) : void{ - if($immediate){ - //Skips all queues - $this->sendEncoded($payload->getResult(), true); - }else{ - $this->compressedQueue->enqueue($payload); - $payload->onResolve(function(CompressBatchPromise $payload) : void{ - if($this->connected && $this->compressedQueue->bottom() === $payload){ - $this->compressedQueue->dequeue(); //result unused - $this->sendEncoded($payload->getResult()); + Timings::$playerNetworkSend->startTiming(); + try{ + if($immediate){ + //Skips all queues + $this->sendEncoded($payload->getResult(), true); + }else{ + $this->compressedQueue->enqueue($payload); + $payload->onResolve(function(CompressBatchPromise $payload) : void{ + if($this->connected && $this->compressedQueue->bottom() === $payload){ + $this->compressedQueue->dequeue(); //result unused + $this->sendEncoded($payload->getResult()); - while(!$this->compressedQueue->isEmpty()){ - /** @var CompressBatchPromise $current */ - $current = $this->compressedQueue->bottom(); - if($current->hasResult()){ - $this->compressedQueue->dequeue(); + while(!$this->compressedQueue->isEmpty()){ + /** @var CompressBatchPromise $current */ + $current = $this->compressedQueue->bottom(); + if($current->hasResult()){ + $this->compressedQueue->dequeue(); - $this->sendEncoded($current->getResult()); - }else{ - //can't send any more queued until this one is ready - break; + $this->sendEncoded($current->getResult()); + }else{ + //can't send any more queued until this one is ready + break; + } } } - } - }); + }); + } + }finally{ + Timings::$playerNetworkSend->stopTiming(); } } diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php index 9cba70739..73848ccae 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\Server; +use pocketmine\utils\BinaryStream; use function spl_object_id; final class StandardPacketBroadcaster implements PacketBroadcaster{ @@ -38,7 +39,9 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ $serializerContext = $recipient->getPacketSerializerContext(); $bufferId = spl_object_id($serializerContext); if(!isset($buffers[$bufferId])){ - $buffers[$bufferId] = PacketBatch::fromPackets($serializerContext, ...$packets); + $stream = new BinaryStream(); + NetworkSession::encodePacketBatchTimed($stream, $serializerContext, $packets); + $buffers[$bufferId] = $stream->getBuffer(); } //TODO: different compressors might be compatible, it might not be necessary to split them up by object @@ -52,14 +55,14 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ $buffer = $buffers[$bufferId]; foreach($compressorMap as $compressorId => $compressorTargets){ $compressor = $compressors[$compressorId]; - if(!$compressor->willCompress($buffer->getBuffer())){ + if(!$compressor->willCompress($buffer)){ foreach($compressorTargets as $target){ foreach($packets as $pk){ $target->addToSendBuffer($pk); } } }else{ - $promise = $this->server->prepareBatch($buffer, $compressor); + $promise = $this->server->prepareBatch(new PacketBatch($buffer), $compressor); foreach($compressorTargets as $target){ $target->queueCompressed($promise); } diff --git a/src/timings/Timings.php b/src/timings/Timings.php index 3e76a919e..1ec032fb7 100644 --- a/src/timings/Timings.php +++ b/src/timings/Timings.php @@ -89,6 +89,9 @@ abstract class Timings{ /** @var TimingsHandler[] */ private static array $packetHandleTimingMap = []; + /** @var TimingsHandler[] */ + private static array $packetEncodeTimingMap = []; + /** @var TimingsHandler[] */ public static array $packetSendTimingMap = []; /** @var TimingsHandler[] */ @@ -202,8 +205,7 @@ abstract class Timings{ self::init(); $pid = $pk->pid(); if(!isset(self::$packetReceiveTimingMap[$pid])){ - $pkName = (new \ReflectionClass($pk))->getShortName(); - self::$packetReceiveTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "receivePacket - " . $pkName . " [0x" . dechex($pid) . "]", self::$playerNetworkReceive); + self::$packetReceiveTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "receivePacket - " . $pk->getName() . " [0x" . dechex($pid) . "]", self::$playerNetworkReceive); } return self::$packetReceiveTimingMap[$pid]; @@ -225,12 +227,19 @@ abstract class Timings{ ); } + public static function getEncodeDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{ + $pid = $pk->pid(); + return self::$packetEncodeTimingMap[$pid] ??= new TimingsHandler( + self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Encode - " . $pk->getName() . " [0x" . dechex($pid) . "]", + self::getSendDataPacketTimings($pk) + ); + } + public static function getSendDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{ self::init(); $pid = $pk->pid(); if(!isset(self::$packetSendTimingMap[$pid])){ - $pkName = (new \ReflectionClass($pk))->getShortName(); - self::$packetSendTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "sendPacket - " . $pkName . " [0x" . dechex($pid) . "]", self::$playerNetworkSend); + self::$packetSendTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "sendPacket - " . $pk->getName() . " [0x" . dechex($pid) . "]", self::$playerNetworkSend); } return self::$packetSendTimingMap[$pid];