From 14f141fab27f6517d0799b9c5351dc1352c2fd13 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 14 Mar 2023 19:00:15 +0000 Subject: [PATCH 01/12] NetworkSession: Stop counting DataPacketReceiveEvent in handler timings we want it to be included in receive timings, but not handler timings. Handler timings should reflect the time spent in the actual session PacketHandler, not in the event. --- src/network/mcpe/NetworkSession.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index abc84f616..56b0622d8 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -447,18 +447,18 @@ class NetworkSession{ $decodeTimings->stopTiming(); } - $handlerTimings = Timings::getHandleDataPacketTimings($packet); - $handlerTimings->startTiming(); - try{ - //TODO: I'm not sure DataPacketReceiveEvent should be included in the handler timings, but it needs to be - //included for now to ensure the receivePacket timings are counted the way they were before - $ev = new DataPacketReceiveEvent($this, $packet); - $ev->call(); - if(!$ev->isCancelled() && ($this->handler === null || !$packet->handle($this->handler))){ - $this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer())); + $ev = new DataPacketReceiveEvent($this, $packet); + $ev->call(); + if(!$ev->isCancelled()){ + $handlerTimings = Timings::getHandleDataPacketTimings($packet); + $handlerTimings->startTiming(); + try{ + if($this->handler === null || !$packet->handle($this->handler)){ + $this->logger->debug("Unhandled " . $packet->getName() . ": " . base64_encode($stream->getBuffer())); + } + }finally{ + $handlerTimings->stopTiming(); } - }finally{ - $handlerTimings->stopTiming(); } }finally{ $timings->stopTiming(); From b266f45152bc8fa07388f8dc6caec27049903e5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Mar 2023 13:44:20 +0000 Subject: [PATCH 02/12] Bump build/php from `71b9f9d` to `a464454` (#5637) Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `71b9f9d` to `a464454`. - [Release notes](https://github.com/pmmp/php-build-scripts/releases) - [Commits](https://github.com/pmmp/php-build-scripts/compare/71b9f9d2d7b3ee45d9475803b96c080f5f22f373...a464454d1ed946baabd32a02ddcf66361374b99c) --- updated-dependencies: - dependency-name: build/php dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index 71b9f9d2d..a464454d1 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 71b9f9d2d7b3ee45d9475803b96c080f5f22f373 +Subproject commit a464454d1ed946baabd32a02ddcf66361374b99c From 5f9e0081fdde243bad9ebd3853d05ae48dabb20a Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 16:36:05 +0000 Subject: [PATCH 03/12] Fixed mushroom block silk-touch drops and block picking behaviour fixes #5284 --- src/block/RedMushroomBlock.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/block/RedMushroomBlock.php b/src/block/RedMushroomBlock.php index 820a0e5f8..1aa602bd9 100644 --- a/src/block/RedMushroomBlock.php +++ b/src/block/RedMushroomBlock.php @@ -34,7 +34,7 @@ class RedMushroomBlock extends Opaque{ protected MushroomBlockType $mushroomBlockType; public function __construct(BlockIdentifier $idInfo, string $name, BlockBreakInfo $breakInfo){ - $this->mushroomBlockType = MushroomBlockType::PORES(); + $this->mushroomBlockType = MushroomBlockType::ALL_CAP(); parent::__construct($idInfo, $name, $breakInfo); } @@ -42,6 +42,11 @@ class RedMushroomBlock extends Opaque{ return MushroomBlockTypeIdMap::getInstance()->toId($this->mushroomBlockType); } + protected function writeStateToItemMeta() : int{ + //these blocks always drop as all-cap, but may exist in other forms in the inventory (particularly creative) + return BlockLegacyMetadata::MUSHROOM_BLOCK_ALL_CAP; + } + public function readStateFromData(int $id, int $stateMeta) : void{ $type = MushroomBlockTypeIdMap::getInstance()->fromId($stateMeta); if($type === null){ From e7e19abe85a98d1c4b66a92e3b0ac574bde888ca Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 17:17:56 +0000 Subject: [PATCH 04/12] IPv4 and IPv6 RakLibInterface instances now both use the same broadcaster and context fixes #5625 --- src/Server.php | 14 ++++++++++---- src/network/mcpe/raklib/RakLibInterface.php | 8 ++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Server.php b/src/Server.php index e5f312f4e..43460e944 100644 --- a/src/Server.php +++ b/src/Server.php @@ -54,13 +54,16 @@ use pocketmine\network\mcpe\compression\CompressBatchPromise; use pocketmine\network\mcpe\compression\CompressBatchTask; use pocketmine\network\mcpe\compression\Compressor; use pocketmine\network\mcpe\compression\ZlibCompressor; +use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\encryption\EncryptionContext; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\PacketBroadcaster; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; +use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\raklib\RakLibInterface; +use pocketmine\network\mcpe\StandardPacketBroadcaster; use pocketmine\network\Network; use pocketmine\network\NetworkInterfaceStartException; use pocketmine\network\query\DedicatedQueryNetworkInterface; @@ -1169,10 +1172,10 @@ class Server{ return !$anyWorldFailedToLoad; } - private function startupPrepareConnectableNetworkInterfaces(string $ip, int $port, bool $ipV6, bool $useQuery) : bool{ + private function startupPrepareConnectableNetworkInterfaces(string $ip, int $port, bool $ipV6, bool $useQuery, PacketBroadcaster $packetBroadcaster, PacketSerializerContext $packetSerializerContext) : bool{ $prettyIp = $ipV6 ? "[$ip]" : $ip; try{ - $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6)); + $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $packetSerializerContext)); }catch(NetworkInterfaceStartException $e){ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed( $ip, @@ -1198,11 +1201,14 @@ class Server{ private function startupPrepareNetworkInterfaces() : bool{ $useQuery = $this->configGroup->getConfigBool("enable-query", true); + $packetSerializerContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); + $broadcaster = new StandardPacketBroadcaster($this, $packetSerializerContext); + if( - !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery) || + !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $broadcaster, $packetSerializerContext) || ( $this->configGroup->getConfigBool("enable-ipv6", true) && - !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery) + !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $broadcaster, $packetSerializerContext) ) ){ return false; diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index 993566f85..1f7ae9767 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -83,8 +83,11 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ private PacketBroadcaster $broadcaster; private PacketSerializerContext $packetSerializerContext; - public function __construct(Server $server, string $ip, int $port, bool $ipV6){ + public function __construct(Server $server, string $ip, int $port, bool $ipV6, PacketBroadcaster $packetBroadcaster, PacketSerializerContext $packetSerializerContext){ $this->server = $server; + $this->broadcaster = $packetBroadcaster; + $this->packetSerializerContext = $packetSerializerContext; + $this->rakServerId = mt_rand(0, PHP_INT_MAX); $this->sleeper = new SleeperNotifier(); @@ -108,9 +111,6 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ $this->interface = new UserToRakLibThreadMessageSender( new PthreadsChannelWriter($mainToThreadBuffer) ); - - $this->packetSerializerContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); - $this->broadcaster = new StandardPacketBroadcaster($this->server, $this->packetSerializerContext); } public function start() : void{ From cc8660629b53e8687adeaed2f6c896582dd8e39e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 18:17:45 +0000 Subject: [PATCH 05/12] First look at shared EntityEventBroadcaster, this improves performance in PvP servers and other areas where lots of players or entities exist in one space. fixes #5622 --- src/Server.php | 21 ++- src/entity/Entity.php | 16 +- src/entity/Human.php | 48 +++--- src/entity/Living.php | 16 +- src/entity/object/ItemEntity.php | 9 +- src/entity/projectile/Arrow.php | 9 +- src/network/mcpe/EntityEventBroadcaster.php | 93 ++++++++++++ src/network/mcpe/NetworkBroadcastUtils.php | 54 +++++++ src/network/mcpe/NetworkSession.php | 101 +------------ .../mcpe/StandardEntityEventBroadcaster.php | 143 ++++++++++++++++++ .../mcpe/handler/PreSpawnPacketHandler.php | 4 +- src/network/mcpe/raklib/RakLibInterface.php | 14 +- tests/phpstan/configs/actual-problems.neon | 35 ++--- 13 files changed, 393 insertions(+), 170 deletions(-) create mode 100644 src/network/mcpe/EntityEventBroadcaster.php create mode 100644 src/network/mcpe/NetworkBroadcastUtils.php create mode 100644 src/network/mcpe/StandardEntityEventBroadcaster.php diff --git a/src/Server.php b/src/Server.php index 43460e944..dbccda2da 100644 --- a/src/Server.php +++ b/src/Server.php @@ -56,6 +56,7 @@ use pocketmine\network\mcpe\compression\Compressor; use pocketmine\network\mcpe\compression\ZlibCompressor; use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\encryption\EncryptionContext; +use pocketmine\network\mcpe\EntityEventBroadcaster; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\PacketBroadcaster; use pocketmine\network\mcpe\protocol\ClientboundPacket; @@ -63,6 +64,7 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\raklib\RakLibInterface; +use pocketmine\network\mcpe\StandardEntityEventBroadcaster; use pocketmine\network\mcpe\StandardPacketBroadcaster; use pocketmine\network\Network; use pocketmine\network\NetworkInterfaceStartException; @@ -1172,10 +1174,18 @@ class Server{ return !$anyWorldFailedToLoad; } - private function startupPrepareConnectableNetworkInterfaces(string $ip, int $port, bool $ipV6, bool $useQuery, PacketBroadcaster $packetBroadcaster, PacketSerializerContext $packetSerializerContext) : bool{ + private function startupPrepareConnectableNetworkInterfaces( + string $ip, + int $port, + bool $ipV6, + bool $useQuery, + PacketBroadcaster $packetBroadcaster, + EntityEventBroadcaster $entityEventBroadcaster, + PacketSerializerContext $packetSerializerContext + ) : bool{ $prettyIp = $ipV6 ? "[$ip]" : $ip; try{ - $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $packetSerializerContext)); + $rakLibRegistered = $this->network->registerInterface(new RakLibInterface($this, $ip, $port, $ipV6, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext)); }catch(NetworkInterfaceStartException $e){ $this->logger->emergency($this->language->translate(KnownTranslationFactory::pocketmine_server_networkStartFailed( $ip, @@ -1202,13 +1212,14 @@ class Server{ $useQuery = $this->configGroup->getConfigBool("enable-query", true); $packetSerializerContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary()); - $broadcaster = new StandardPacketBroadcaster($this, $packetSerializerContext); + $packetBroadcaster = new StandardPacketBroadcaster($this, $packetSerializerContext); + $entityEventBroadcaster = new StandardEntityEventBroadcaster($packetBroadcaster); if( - !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $broadcaster, $packetSerializerContext) || + !$this->startupPrepareConnectableNetworkInterfaces($this->getIp(), $this->getPort(), false, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext) || ( $this->configGroup->getConfigBool("enable-ipv6", true) && - !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $broadcaster, $packetSerializerContext) + !$this->startupPrepareConnectableNetworkInterfaces($this->getIpV6(), $this->getPortV6(), true, $useQuery, $packetBroadcaster, $entityEventBroadcaster, $packetSerializerContext) ) ){ return false; diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 46e60feaa..c206b81d3 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -44,6 +44,8 @@ use pocketmine\nbt\tag\DoubleTag; use pocketmine\nbt\tag\FloatTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\StringTag; +use pocketmine\network\mcpe\EntityEventBroadcaster; +use pocketmine\network\mcpe\NetworkBroadcastUtils; use pocketmine\network\mcpe\protocol\AddActorPacket; use pocketmine\network\mcpe\protocol\MoveActorAbsolutePacket; use pocketmine\network\mcpe\protocol\SetActorMotionPacket; @@ -1537,7 +1539,7 @@ abstract class Entity{ $id = spl_object_id($player); if(isset($this->hasSpawned[$id])){ if($send){ - $player->getNetworkSession()->onEntityRemoved($this); + $player->getNetworkSession()->getEntityEventBroadcaster()->onEntityRemoved([$player->getNetworkSession()], $this); } unset($this->hasSpawned[$id]); } @@ -1548,9 +1550,11 @@ abstract class Entity{ * player moves, viewers will once again be able to see the entity. */ public function despawnFromAll() : void{ - foreach($this->hasSpawned as $player){ - $this->despawnFrom($player); - } + NetworkBroadcastUtils::broadcastEntityEvent( + $this->hasSpawned, + fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onEntityRemoved($recipients, $this) + ); + $this->hasSpawned = []; } /** @@ -1624,9 +1628,7 @@ abstract class Entity{ $targets = $targets ?? $this->hasSpawned; $data = $data ?? $this->getAllNetworkData(); - foreach($targets as $p){ - $p->getNetworkSession()->syncActorData($this, $data); - } + NetworkBroadcastUtils::broadcastEntityEvent($targets, fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->syncActorData($recipients, $this, $data)); } /** diff --git a/src/entity/Human.php b/src/entity/Human.php index 5ede1abf8..695777613 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -47,6 +47,8 @@ use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\StringTag; use pocketmine\network\mcpe\convert\SkinAdapterSingleton; use pocketmine\network\mcpe\convert\TypeConverter; +use pocketmine\network\mcpe\EntityEventBroadcaster; +use pocketmine\network\mcpe\NetworkBroadcastUtils; use pocketmine\network\mcpe\protocol\AddPlayerPacket; use pocketmine\network\mcpe\protocol\PlayerListPacket; use pocketmine\network\mcpe\protocol\PlayerSkinPacket; @@ -189,9 +191,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ } public function emote(string $emoteId) : void{ - foreach($this->getViewers() as $player){ - $player->getNetworkSession()->onEmote($this, $emoteId); - } + NetworkBroadcastUtils::broadcastEntityEvent( + $this->getViewers(), + fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onEmote($recipients, $this, $emoteId) + ); } public function getHungerManager() : HungerManager{ @@ -270,11 +273,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ $this->xpManager = new ExperienceManager($this); $this->inventory = new PlayerInventory($this); - $syncHeldItem = function() : void{ - foreach($this->getViewers() as $viewer){ - $viewer->getNetworkSession()->onMobMainHandItemChange($this); - } - }; + $syncHeldItem = fn() => NetworkBroadcastUtils::broadcastEntityEvent( + $this->getViewers(), + fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this) + ); $this->inventory->getListeners()->add(new CallbackInventoryListener( function(Inventory $unused, int $slot, Item $unused2) use ($syncHeldItem) : void{ if($slot === $this->inventory->getHeldItemIndex()){ @@ -315,11 +317,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ if($offHand !== null){ $this->offHandInventory->setItem(0, Item::nbtDeserialize($offHand)); } - $this->offHandInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(function() : void{ - foreach($this->getViewers() as $viewer){ - $viewer->getNetworkSession()->onMobOffHandItemChange($this); - } - })); + $this->offHandInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent( + $this->getViewers(), + fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobOffHandItemChange($recipients, $this) + ))); $enderChestInventoryTag = $nbt->getListTag(self::TAG_ENDER_CHEST_INVENTORY); if($enderChestInventoryTag !== null){ @@ -333,11 +334,10 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ } $this->inventory->setHeldItemIndex($nbt->getInt(self::TAG_SELECTED_INVENTORY_SLOT, 0)); - $this->inventory->getHeldItemIndexChangeListeners()->add(function(int $oldIndex) : void{ - foreach($this->getViewers() as $viewer){ - $viewer->getNetworkSession()->onMobMainHandItemChange($this); - } - }); + $this->inventory->getHeldItemIndexChangeListeners()->add(fn() => NetworkBroadcastUtils::broadcastEntityEvent( + $this->getViewers(), + fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobMainHandItemChange($recipients, $this) + )); $this->hungerManager->setFood((float) $nbt->getInt(self::TAG_FOOD_LEVEL, (int) $this->hungerManager->getFood())); $this->hungerManager->setExhaustion($nbt->getFloat(self::TAG_FOOD_EXHAUSTION_LEVEL, $this->hungerManager->getExhaustion())); @@ -490,11 +490,12 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ } protected function sendSpawnPacket(Player $player) : void{ + $networkSession = $player->getNetworkSession(); if(!($this instanceof Player)){ - $player->getNetworkSession()->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), SkinAdapterSingleton::get()->toSkinData($this->skin))])); + $networkSession->sendDataPacket(PlayerListPacket::add([PlayerListEntry::createAdditionEntry($this->uuid, $this->id, $this->getName(), SkinAdapterSingleton::get()->toSkinData($this->skin))])); } - $player->getNetworkSession()->sendDataPacket(AddPlayerPacket::create( + $networkSession->sendDataPacket(AddPlayerPacket::create( $this->getUniqueId(), $this->getName(), $this->getId(), @@ -524,11 +525,12 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ //TODO: Hack for MCPE 1.2.13: DATA_NAMETAG is useless in AddPlayerPacket, so it has to be sent separately $this->sendData([$player], [EntityMetadataProperties::NAMETAG => new StringMetadataProperty($this->getNameTag())]); - $player->getNetworkSession()->onMobArmorChange($this); - $player->getNetworkSession()->onMobOffHandItemChange($this); + $entityEventBroadcaster = $networkSession->getEntityEventBroadcaster(); + $entityEventBroadcaster->onMobArmorChange([$networkSession], $this); + $entityEventBroadcaster->onMobOffHandItemChange([$networkSession], $this); if(!($this instanceof Player)){ - $player->getNetworkSession()->sendDataPacket(PlayerListPacket::remove([PlayerListEntry::createRemovalEntry($this->uuid)])); + $networkSession->sendDataPacket(PlayerListPacket::remove([PlayerListEntry::createRemovalEntry($this->uuid)])); } } diff --git a/src/entity/Living.php b/src/entity/Living.php index 86457321e..ab84ec1ca 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -50,6 +50,8 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\FloatTag; use pocketmine\nbt\tag\ListTag; use pocketmine\nbt\tag\ShortTag; +use pocketmine\network\mcpe\EntityEventBroadcaster; +use pocketmine\network\mcpe\NetworkBroadcastUtils; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties; @@ -143,13 +145,10 @@ abstract class Living extends Entity{ $this->armorInventory = new ArmorInventory($this); //TODO: load/save armor inventory contents - $this->armorInventory->getListeners()->add(CallbackInventoryListener::onAnyChange( - function(Inventory $unused) : void{ - foreach($this->getViewers() as $viewer){ - $viewer->getNetworkSession()->onMobArmorChange($this); - } - } - )); + $this->armorInventory->getListeners()->add(CallbackInventoryListener::onAnyChange(fn() => NetworkBroadcastUtils::broadcastEntityEvent( + $this->getViewers(), + fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onMobArmorChange($recipients, $this) + ))); $health = $this->getMaxHealth(); @@ -850,7 +849,8 @@ abstract class Living extends Entity{ protected function sendSpawnPacket(Player $player) : void{ parent::sendSpawnPacket($player); - $player->getNetworkSession()->onMobArmorChange($this); + $networkSession = $player->getNetworkSession(); + $networkSession->getEntityEventBroadcaster()->onMobArmorChange([$networkSession], $this); } protected function syncNetworkData(EntityMetadataCollection $properties) : void{ diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index 08dea0659..109b0629c 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -35,6 +35,8 @@ use pocketmine\item\Item; use pocketmine\math\Vector3; use pocketmine\nbt\tag\CompoundTag; use pocketmine\network\mcpe\convert\TypeConverter; +use pocketmine\network\mcpe\EntityEventBroadcaster; +use pocketmine\network\mcpe\NetworkBroadcastUtils; use pocketmine\network\mcpe\protocol\AddItemActorPacket; use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper; @@ -328,9 +330,10 @@ class ItemEntity extends Entity{ return; } - foreach($this->getViewers() as $viewer){ - $viewer->getNetworkSession()->onPlayerPickUpItem($player, $this); - } + NetworkBroadcastUtils::broadcastEntityEvent( + $this->getViewers(), + fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onPickUpItem($recipients, $player, $this) + ); $inventory = $ev->getInventory(); if($inventory !== null){ diff --git a/src/entity/projectile/Arrow.php b/src/entity/projectile/Arrow.php index 0d284d496..92e7f91c3 100644 --- a/src/entity/projectile/Arrow.php +++ b/src/entity/projectile/Arrow.php @@ -33,6 +33,8 @@ use pocketmine\event\entity\ProjectileHitEvent; use pocketmine\item\VanillaItems; use pocketmine\math\RayTraceResult; use pocketmine\nbt\tag\CompoundTag; +use pocketmine\network\mcpe\EntityEventBroadcaster; +use pocketmine\network\mcpe\NetworkBroadcastUtils; use pocketmine\network\mcpe\protocol\types\entity\EntityIds; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection; use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataFlags; @@ -196,9 +198,10 @@ class Arrow extends Projectile{ return; } - foreach($this->getViewers() as $viewer){ - $viewer->getNetworkSession()->onPlayerPickUpItem($player, $this); - } + NetworkBroadcastUtils::broadcastEntityEvent( + $this->getViewers(), + fn(EntityEventBroadcaster $broadcaster, array $recipients) => $broadcaster->onPickUpItem($recipients, $player, $this) + ); $ev->getInventory()?->addItem($ev->getItem()); $this->flagForDespawn(); diff --git a/src/network/mcpe/EntityEventBroadcaster.php b/src/network/mcpe/EntityEventBroadcaster.php new file mode 100644 index 000000000..252563149 --- /dev/null +++ b/src/network/mcpe/EntityEventBroadcaster.php @@ -0,0 +1,93 @@ + $properties + */ + public function syncActorData(array $recipients, Entity $entity, array $properties) : void; + + /** + * @param NetworkSession[] $recipients + */ + public function onEntityEffectAdded(array $recipients, Living $entity, EffectInstance $effect, bool $replacesOldEffect) : void; + + /** + * @param NetworkSession[] $recipients + */ + public function onEntityEffectRemoved(array $recipients, Living $entity, EffectInstance $effect) : void; + + /** + * @param NetworkSession[] $recipients + */ + public function onEntityRemoved(array $recipients, Entity $entity) : void; + + /** + * TODO: expand this to more than just humans + * + * @param NetworkSession[] $recipients + */ + public function onMobMainHandItemChange(array $recipients,Human $mob) : void; + + /** + * @param NetworkSession[] $recipients + */ + public function onMobOffHandItemChange(array $recipients, Human $mob) : void; + + /** + * @param NetworkSession[] $recipients + */ + public function onMobArmorChange(array $recipients, Living $mob) : void; + + /** + * @param NetworkSession[] $recipients + */ + public function onPickUpItem(array $recipients, Entity $collector, Entity $pickedUp) : void; + + /** + * @param NetworkSession[] $recipients + */ + public function onEmote(array $recipients, Human $from, string $emoteId) : void; +} diff --git a/src/network/mcpe/NetworkBroadcastUtils.php b/src/network/mcpe/NetworkBroadcastUtils.php new file mode 100644 index 000000000..3a0253450 --- /dev/null +++ b/src/network/mcpe/NetworkBroadcastUtils.php @@ -0,0 +1,54 @@ +) : void $callback + */ + public static function broadcastEntityEvent(array $recipients, \Closure $callback) : void{ + $uniqueBroadcasters = []; + $broadcasterTargets = []; + + foreach($recipients as $recipient){ + $session = $recipient->getNetworkSession(); + $broadcaster = $session->getEntityEventBroadcaster(); + $uniqueBroadcasters[spl_object_id($broadcaster)] = $broadcaster; + $broadcasterTargets[spl_object_id($broadcaster)][spl_object_id($session)] = $session; + } + + foreach($uniqueBroadcasters as $k => $broadcaster){ + $callback($broadcaster, $broadcasterTargets[$k]); + } + } +} diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 56b0622d8..e0b1e07ec 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -23,12 +23,7 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; -use pocketmine\data\bedrock\EffectIdMap; -use pocketmine\entity\Attribute; use pocketmine\entity\effect\EffectInstance; -use pocketmine\entity\Entity; -use pocketmine\entity\Human; -use pocketmine\entity\Living; use pocketmine\event\player\PlayerDuplicateLoginEvent; use pocketmine\event\server\DataPacketReceiveEvent; use pocketmine\event\server\DataPacketSendEvent; @@ -61,10 +56,6 @@ use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; use pocketmine\network\mcpe\protocol\ChunkRadiusUpdatedPacket; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\DisconnectPacket; -use pocketmine\network\mcpe\protocol\EmotePacket; -use pocketmine\network\mcpe\protocol\MobArmorEquipmentPacket; -use pocketmine\network\mcpe\protocol\MobEffectPacket; -use pocketmine\network\mcpe\protocol\MobEquipmentPacket; use pocketmine\network\mcpe\protocol\ModalFormRequestPacket; use pocketmine\network\mcpe\protocol\MovePlayerPacket; use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket; @@ -73,19 +64,16 @@ use pocketmine\network\mcpe\protocol\PacketDecodeException; use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\PlayerListPacket; use pocketmine\network\mcpe\protocol\PlayStatusPacket; -use pocketmine\network\mcpe\protocol\RemoveActorPacket; use pocketmine\network\mcpe\protocol\serializer\PacketBatch; use pocketmine\network\mcpe\protocol\serializer\PacketSerializer; use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; use pocketmine\network\mcpe\protocol\ServerboundPacket; use pocketmine\network\mcpe\protocol\ServerToClientHandshakePacket; -use pocketmine\network\mcpe\protocol\SetActorDataPacket; use pocketmine\network\mcpe\protocol\SetDifficultyPacket; use pocketmine\network\mcpe\protocol\SetPlayerGameTypePacket; use pocketmine\network\mcpe\protocol\SetSpawnPositionPacket; use pocketmine\network\mcpe\protocol\SetTimePacket; use pocketmine\network\mcpe\protocol\SetTitlePacket; -use pocketmine\network\mcpe\protocol\TakeItemActorPacket; use pocketmine\network\mcpe\protocol\TextPacket; use pocketmine\network\mcpe\protocol\ToastRequestPacket; use pocketmine\network\mcpe\protocol\TransferPacket; @@ -97,16 +85,10 @@ use pocketmine\network\mcpe\protocol\types\command\CommandEnum; use pocketmine\network\mcpe\protocol\types\command\CommandParameter; use pocketmine\network\mcpe\protocol\types\command\CommandPermissions; use pocketmine\network\mcpe\protocol\types\DimensionIds; -use pocketmine\network\mcpe\protocol\types\entity\Attribute as NetworkAttribute; -use pocketmine\network\mcpe\protocol\types\entity\MetadataProperty; -use pocketmine\network\mcpe\protocol\types\entity\PropertySyncData; -use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds; -use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper; use pocketmine\network\mcpe\protocol\types\PlayerListEntry; use pocketmine\network\mcpe\protocol\types\PlayerPermissions; use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket; use pocketmine\network\mcpe\protocol\UpdateAdventureSettingsPacket; -use pocketmine\network\mcpe\protocol\UpdateAttributesPacket; use pocketmine\network\NetworkSessionManager; use pocketmine\network\PacketHandlingException; use pocketmine\permission\DefaultPermissionNames; @@ -134,7 +116,6 @@ use function hrtime; use function in_array; use function intdiv; use function json_encode; -use function ksort; use function min; use function strcasecmp; use function strlen; @@ -143,7 +124,6 @@ use function substr; use function time; use function ucfirst; use const JSON_THROW_ON_ERROR; -use const SORT_NUMERIC; class NetworkSession{ private const INCOMING_PACKET_BATCH_PER_TICK = 2; //usually max 1 per tick, but transactions may arrive separately @@ -202,6 +182,7 @@ class NetworkSession{ private PacketSerializerContext $packetSerializerContext, private PacketSender $sender, private PacketBroadcaster $broadcaster, + private EntityEventBroadcaster $entityEventBroadcaster, private Compressor $compressor, private string $ip, private int $port @@ -223,6 +204,7 @@ class NetworkSession{ $this->manager->add($this); $this->logger->info("Session opened"); + $this->entityEventBroadcaster = $entityEventBroadcaster; } private function getLogPrefix() : string{ @@ -273,10 +255,10 @@ class NetworkSession{ $effectManager = $this->player->getEffects(); $effectManager->getEffectAddHooks()->add($effectAddHook = function(EffectInstance $effect, bool $replacesOldEffect) : void{ - $this->onEntityEffectAdded($this->player, $effect, $replacesOldEffect); + $this->entityEventBroadcaster->onEntityEffectAdded([$this], $this->player, $effect, $replacesOldEffect); }); $effectManager->getEffectRemoveHooks()->add($effectRemoveHook = function(EffectInstance $effect) : void{ - $this->onEntityEffectRemoved($this->player, $effect); + $this->entityEventBroadcaster->onEntityEffectRemoved([$this], $this->player, $effect); }); $this->disposeHooks->add(static function() use ($effectManager, $effectAddHook, $effectRemoveHook) : void{ $effectManager->getEffectAddHooks()->remove($effectAddHook); @@ -547,6 +529,8 @@ class NetworkSession{ public function getBroadcaster() : PacketBroadcaster{ return $this->broadcaster; } + public function getEntityEventBroadcaster() : EntityEventBroadcaster{ return $this->entityEventBroadcaster; } + public function getCompressor() : Compressor{ return $this->compressor; } @@ -817,7 +801,7 @@ class NetworkSession{ } public function onServerRespawn() : void{ - $this->syncAttributes($this->player, $this->player->getAttributeMap()->getAll()); + $this->entityEventBroadcaster->syncAttributes([$this], $this->player, $this->player->getAttributeMap()->getAll()); $this->player->sendData(null); $this->syncAbilities($this->player); @@ -926,41 +910,6 @@ class NetworkSession{ )); } - /** - * @param Attribute[] $attributes - */ - public function syncAttributes(Living $entity, array $attributes) : void{ - if(count($attributes) > 0){ - $this->sendDataPacket(UpdateAttributesPacket::create($entity->getId(), array_map(function(Attribute $attr) : NetworkAttribute{ - return new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []); - }, $attributes), 0)); - } - } - - /** - * @param MetadataProperty[] $properties - * @phpstan-param array $properties - */ - public function syncActorData(Entity $entity, array $properties) : void{ - //TODO: HACK! as of 1.18.10, the client responds differently to the same data ordered in different orders - for - //example, sending HEIGHT in the list before FLAGS when unsetting the SWIMMING flag results in a hitbox glitch - ksort($properties, SORT_NUMERIC); - $this->sendDataPacket(SetActorDataPacket::create($entity->getId(), $properties, new PropertySyncData([], []), 0)); - } - - public function onEntityEffectAdded(Living $entity, EffectInstance $effect, bool $replacesOldEffect) : void{ - //TODO: we may need yet another effect <=> ID map in the future depending on protocol changes - $this->sendDataPacket(MobEffectPacket::add($entity->getId(), $replacesOldEffect, EffectIdMap::getInstance()->toId($effect->getType()), $effect->getAmplifier(), $effect->isVisible(), $effect->getDuration())); - } - - public function onEntityEffectRemoved(Living $entity, EffectInstance $effect) : void{ - $this->sendDataPacket(MobEffectPacket::remove($entity->getId(), EffectIdMap::getInstance()->toId($effect->getType()))); - } - - public function onEntityRemoved(Entity $entity) : void{ - $this->sendDataPacket(RemoveActorPacket::create($entity->getId())); - } - public function syncAvailableCommands() : void{ $commandData = []; foreach($this->server->getCommandMap()->getCommands() as $name => $command){ @@ -1096,36 +1045,6 @@ class NetworkSession{ return $this->invManager; } - /** - * TODO: expand this to more than just humans - */ - public function onMobMainHandItemChange(Human $mob) : void{ - //TODO: we could send zero for slot here because remote players don't need to know which slot was selected - $inv = $mob->getInventory(); - $this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand())), $inv->getHeldItemIndex(), $inv->getHeldItemIndex(), ContainerIds::INVENTORY)); - } - - public function onMobOffHandItemChange(Human $mob) : void{ - $inv = $mob->getOffHandInventory(); - $this->sendDataPacket(MobEquipmentPacket::create($mob->getId(), ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItem(0))), 0, 0, ContainerIds::OFFHAND)); - } - - public function onMobArmorChange(Living $mob) : void{ - $inv = $mob->getArmorInventory(); - $converter = TypeConverter::getInstance(); - $this->sendDataPacket(MobArmorEquipmentPacket::create( - $mob->getId(), - ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getHelmet())), - ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getChestplate())), - ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getLeggings())), - ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getBoots())) - )); - } - - public function onPlayerPickUpItem(Player $collector, Entity $pickedUp) : void{ - $this->sendDataPacket(TakeItemActorPacket::create($collector->getId(), $pickedUp->getId())); - } - /** * @param Player[] $players */ @@ -1169,10 +1088,6 @@ class NetworkSession{ $this->sendDataPacket(SetTitlePacket::setAnimationTimes($fadeIn, $stay, $fadeOut)); } - public function onEmote(Human $from, string $emoteId) : void{ - $this->sendDataPacket(EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER)); - } - public function onToastNotification(string $title, string $body) : void{ $this->sendDataPacket(ToastRequestPacket::create($title, $body)); } @@ -1212,7 +1127,7 @@ class NetworkSession{ $this->player->doChunkRequests(); $dirtyAttributes = $this->player->getAttributeMap()->needSend(); - $this->syncAttributes($this->player, $dirtyAttributes); + $this->entityEventBroadcaster->syncAttributes([$this], $this->player, $dirtyAttributes); foreach($dirtyAttributes as $attribute){ //TODO: we might need to send these to other players in the future //if that happens, this will need to become more complex than a flag on the attribute itself diff --git a/src/network/mcpe/StandardEntityEventBroadcaster.php b/src/network/mcpe/StandardEntityEventBroadcaster.php new file mode 100644 index 000000000..da0fac6ef --- /dev/null +++ b/src/network/mcpe/StandardEntityEventBroadcaster.php @@ -0,0 +1,143 @@ +broadcaster->broadcastPackets($recipients, [$packet]); + } + + public function syncAttributes(array $recipients, Living $entity, array $attributes) : void{ + if(count($attributes) > 0){ + $this->sendDataPacket($recipients, UpdateAttributesPacket::create( + $entity->getId(), + array_map(fn(Attribute $attr) => new NetworkAttribute($attr->getId(), $attr->getMinValue(), $attr->getMaxValue(), $attr->getValue(), $attr->getDefaultValue(), []), $attributes), + 0 + )); + } + } + + public function syncActorData(array $recipients, Entity $entity, array $properties) : void{ + //TODO: HACK! as of 1.18.10, the client responds differently to the same data ordered in different orders - for + //example, sending HEIGHT in the list before FLAGS when unsetting the SWIMMING flag results in a hitbox glitch + ksort($properties, SORT_NUMERIC); + $this->sendDataPacket($recipients, SetActorDataPacket::create($entity->getId(), $properties, new PropertySyncData([], []), 0)); + } + + public function onEntityEffectAdded(array $recipients, Living $entity, EffectInstance $effect, bool $replacesOldEffect) : void{ + //TODO: we may need yet another effect <=> ID map in the future depending on protocol changes + $this->sendDataPacket($recipients, MobEffectPacket::add( + $entity->getId(), + $replacesOldEffect, + EffectIdMap::getInstance()->toId($effect->getType()), + $effect->getAmplifier(), + $effect->isVisible(), + $effect->getDuration() + )); + } + + public function onEntityEffectRemoved(array $recipients, Living $entity, EffectInstance $effect) : void{ + $this->sendDataPacket($recipients, MobEffectPacket::remove($entity->getId(), EffectIdMap::getInstance()->toId($effect->getType()))); + } + + public function onEntityRemoved(array $recipients, Entity $entity) : void{ + $this->sendDataPacket($recipients, RemoveActorPacket::create($entity->getId())); + } + + public function onMobMainHandItemChange(array $recipients, Human $mob) : void{ + //TODO: we could send zero for slot here because remote players don't need to know which slot was selected + $inv = $mob->getInventory(); + $this->sendDataPacket($recipients, MobEquipmentPacket::create( + $mob->getId(), + ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItemInHand())), + $inv->getHeldItemIndex(), + $inv->getHeldItemIndex(), + ContainerIds::INVENTORY + )); + } + + public function onMobOffHandItemChange(array $recipients, Human $mob) : void{ + $inv = $mob->getOffHandInventory(); + $this->sendDataPacket($recipients, MobEquipmentPacket::create( + $mob->getId(), + ItemStackWrapper::legacy(TypeConverter::getInstance()->coreItemStackToNet($inv->getItem(0))), + 0, + 0, + ContainerIds::OFFHAND + )); + } + + public function onMobArmorChange(array $recipients, Living $mob) : void{ + $inv = $mob->getArmorInventory(); + $converter = TypeConverter::getInstance(); + $this->sendDataPacket($recipients, MobArmorEquipmentPacket::create( + $mob->getId(), + ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getHelmet())), + ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getChestplate())), + ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getLeggings())), + ItemStackWrapper::legacy($converter->coreItemStackToNet($inv->getBoots())) + )); + } + + public function onPickUpItem(array $recipients, Entity $collector, Entity $pickedUp) : void{ + $this->sendDataPacket($recipients, TakeItemActorPacket::create($collector->getId(), $pickedUp->getId())); + } + + public function onEmote(array $recipients, Human $from, string $emoteId) : void{ + $this->sendDataPacket($recipients, EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER)); + } +} diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index 04c51d3fb..3eeecbb86 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -114,7 +114,7 @@ class PreSpawnPacketHandler extends PacketHandler{ $this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs()); $this->session->getLogger()->debug("Sending attributes"); - $this->session->syncAttributes($this->player, $this->player->getAttributeMap()->getAll()); + $this->session->getEntityEventBroadcaster()->syncAttributes([$this->session], $this->player, $this->player->getAttributeMap()->getAll()); $this->session->getLogger()->debug("Sending available commands"); $this->session->syncAvailableCommands(); @@ -125,7 +125,7 @@ class PreSpawnPacketHandler extends PacketHandler{ $this->session->getLogger()->debug("Sending effects"); foreach($this->player->getEffects()->all() as $effect){ - $this->session->onEntityEffectAdded($this->player, $effect, false); + $this->session->getEntityEventBroadcaster()->onEntityEffectAdded([$this->session], $this->player, $effect, false); } $this->session->getLogger()->debug("Sending actor metadata"); diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index 1f7ae9767..d58819938 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -25,14 +25,13 @@ namespace pocketmine\network\mcpe\raklib; use pocketmine\network\AdvancedNetworkInterface; use pocketmine\network\mcpe\compression\ZlibCompressor; -use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\convert\TypeConverter; +use pocketmine\network\mcpe\EntityEventBroadcaster; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\PacketBroadcaster; use pocketmine\network\mcpe\protocol\PacketPool; use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext; -use pocketmine\network\mcpe\StandardPacketBroadcaster; use pocketmine\network\Network; use pocketmine\network\NetworkInterfaceStartException; use pocketmine\network\PacketHandlingException; @@ -80,13 +79,15 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ private SleeperNotifier $sleeper; - private PacketBroadcaster $broadcaster; + private PacketBroadcaster $packetBroadcaster; + private EntityEventBroadcaster $entityEventBroadcaster; private PacketSerializerContext $packetSerializerContext; - public function __construct(Server $server, string $ip, int $port, bool $ipV6, PacketBroadcaster $packetBroadcaster, PacketSerializerContext $packetSerializerContext){ + public function __construct(Server $server, string $ip, int $port, bool $ipV6, PacketBroadcaster $packetBroadcaster, EntityEventBroadcaster $entityEventBroadcaster, PacketSerializerContext $packetSerializerContext){ $this->server = $server; - $this->broadcaster = $packetBroadcaster; + $this->packetBroadcaster = $packetBroadcaster; $this->packetSerializerContext = $packetSerializerContext; + $this->entityEventBroadcaster = $entityEventBroadcaster; $this->rakServerId = mt_rand(0, PHP_INT_MAX); @@ -172,7 +173,8 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ PacketPool::getInstance(), $this->packetSerializerContext, new RakLibPacketSender($sessionId, $this), - $this->broadcaster, + $this->packetBroadcaster, + $this->entityEventBroadcaster, ZlibCompressor::getInstance(), //TODO: this shouldn't be hardcoded, but we might need the RakNet protocol version to select it $address, $port diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 90ec78b19..8e28edaa5 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -490,11 +490,6 @@ parameters: count: 1 path: ../../../src/command/defaults/TimingsCommand.php - - - message: "#^Call to function is_resource\\(\\) with resource will always evaluate to true\\.$#" - count: 2 - path: ../../../src/console/ConsoleReader.php - - message: "#^Parameter \\#1 \\$path of static method pocketmine\\\\utils\\\\Filesystem\\:\\:cleanPath\\(\\) expects string, mixed given\\.$#" count: 1 @@ -650,21 +645,6 @@ parameters: count: 1 path: ../../../src/network/mcpe/NetworkSession.php - - - message: "#^Parameter \\#1 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:onEntityEffectAdded\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" - count: 1 - path: ../../../src/network/mcpe/NetworkSession.php - - - - message: "#^Parameter \\#1 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:onEntityEffectRemoved\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" - count: 1 - path: ../../../src/network/mcpe/NetworkSession.php - - - - message: "#^Parameter \\#1 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAttributes\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" - count: 1 - path: ../../../src/network/mcpe/NetworkSession.php - - message: "#^Parameter \\#1 \\$for of method pocketmine\\\\network\\\\mcpe\\\\NetworkSession\\:\\:syncAbilities\\(\\) expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" count: 2 @@ -685,6 +665,21 @@ parameters: count: 1 path: ../../../src/network/mcpe/NetworkSession.php + - + message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:onEntityEffectAdded\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" + count: 1 + path: ../../../src/network/mcpe/NetworkSession.php + + - + message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:onEntityEffectRemoved\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" + count: 1 + path: ../../../src/network/mcpe/NetworkSession.php + + - + message: "#^Parameter \\#2 \\$entity of method pocketmine\\\\network\\\\mcpe\\\\EntityEventBroadcaster\\:\\:syncAttributes\\(\\) expects pocketmine\\\\entity\\\\Living, pocketmine\\\\player\\\\Player\\|null given\\.$#" + count: 1 + path: ../../../src/network/mcpe/NetworkSession.php + - message: "#^Parameter \\#2 \\$player of class pocketmine\\\\network\\\\mcpe\\\\handler\\\\PreSpawnPacketHandler constructor expects pocketmine\\\\player\\\\Player, pocketmine\\\\player\\\\Player\\|null given\\.$#" count: 1 From e0fdbe6eb1b689e0d2e65503c78734582369b0d9 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 20:46:42 +0000 Subject: [PATCH 06/12] make-release: don't automatically push this is rather obnoxious when attempting to push test releases to a fork. --- build/make-release.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build/make-release.php b/build/make-release.php index 4466e7b6f..7a570eb35 100644 --- a/build/make-release.php +++ b/build/make-release.php @@ -38,7 +38,6 @@ use function is_string; use function max; use function preg_match; use function preg_replace; -use function sleep; use function sprintf; use function str_pad; use function strlen; @@ -160,9 +159,6 @@ function main() : void{ replaceVersion($versionInfoPath, $nextVer->getBaseVersion(), true, $channel); systemWrapper('git add "' . $versionInfoPath . '"', "failed to stage changes for post-release commit"); systemWrapper('git commit -m "' . $nextVer->getBaseVersion() . ' is next" --include "' . $versionInfoPath . '"', "failed to create post-release commit"); - echo "pushing changes in 5 seconds\n"; - sleep(5); - systemWrapper('git push origin HEAD ' . $currentVer->getBaseVersion(), "failed to push changes to remote"); } main(); From acebbeed1674c26cfc004146eabbeeeb1c63d06d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 20:59:36 +0000 Subject: [PATCH 07/12] Added version channels for update.pmmp.io --- .github/workflows/update-updater-api.yml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/update-updater-api.yml b/.github/workflows/update-updater-api.yml index dc40d17e9..906278ef3 100644 --- a/.github/workflows/update-updater-api.yml +++ b/.github/workflows/update-updater-api.yml @@ -25,13 +25,25 @@ jobs: - name: Download new release information run: curl -f -L ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ steps.tag-name.outputs.TAG_NAME }}/build_info.json -o new_build_info.json - - name: Detect channel + - name: Detect channels id: channel - run: echo CHANNEL=$(jq -r '.channel' new_build_info.json) >> $GITHUB_OUTPUT - - - name: Copy release information run: | - cp new_build_info.json channels/${{ steps.channel.outputs.CHANNEL }}.json + CHANNEL=$(jq -r '.channel' new_build_info.json) + VERSION=${{ steps.tag-name.outputs.TAG_NAME }} + echo CHANNEL=$CHANNEL >> $GITHUB_OUTPUT + if [ "$CHANNEL" == "stable" ]; then + echo MAJOR=$(echo $VERSION | cut -d. -f1) >> $GITHUB_OUTPUT + echo MINOR=$(echo $VERSION | cut -d. -f1-2) >> $GITHUB_OUTPUT + else + echo MAJOR=$(echo $VERSION | cut -d. -f1)-$CHANNEL >> $GITHUB_OUTPUT + echo MINOR=$(echo $VERSION | cut -d. -f1-2)-$CHANNEL >> $GITHUB_OUTPUT + fi + + - name: Update channel info + run: | + cp new_build_info.json "channels/${{ steps.channel.outputs.CHANNEL }}.json" + cp new_build_info.json "channels/${{ steps.channel.outputs.MAJOR }}.json" + cp new_build_info.json "channels/${{ steps.channel.outputs.MINOR }}.json" rm new_build_info.json - name: Commit changes From a31e3331fd0b7dcd499d31030e685bdf7a1a8129 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 22:25:23 +0000 Subject: [PATCH 08/12] Move Server::broadcastPackets() to NetworkBroadcastUtils::broadcastPackets() this has no business being in Server, and it also doesn't need to be an instance method, since it never uses $this. --- src/Server.php | 41 +----------------- src/network/mcpe/NetworkBroadcastUtils.php | 49 ++++++++++++++++++++++ 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src/Server.php b/src/Server.php index dbccda2da..ee509696e 100644 --- a/src/Server.php +++ b/src/Server.php @@ -43,7 +43,6 @@ use pocketmine\event\player\PlayerCreationEvent; use pocketmine\event\player\PlayerDataSaveEvent; use pocketmine\event\player\PlayerLoginEvent; use pocketmine\event\server\CommandEvent; -use pocketmine\event\server\DataPacketSendEvent; use pocketmine\event\server\QueryRegenerateEvent; use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\Language; @@ -57,6 +56,7 @@ use pocketmine\network\mcpe\compression\ZlibCompressor; use pocketmine\network\mcpe\convert\GlobalItemTypeDictionary; use pocketmine\network\mcpe\encryption\EncryptionContext; use pocketmine\network\mcpe\EntityEventBroadcaster; +use pocketmine\network\mcpe\NetworkBroadcastUtils; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\PacketBroadcaster; use pocketmine\network\mcpe\protocol\ClientboundPacket; @@ -1353,44 +1353,7 @@ class Server{ * @param ClientboundPacket[] $packets */ public function broadcastPackets(array $players, array $packets) : bool{ - if(count($packets) === 0){ - throw new \InvalidArgumentException("Cannot broadcast empty list of packets"); - } - - return Timings::$broadcastPackets->time(function() use ($players, $packets) : bool{ - /** @var NetworkSession[] $recipients */ - $recipients = []; - foreach($players as $player){ - if($player->isConnected()){ - $recipients[] = $player->getNetworkSession(); - } - } - if(count($recipients) === 0){ - return false; - } - - $ev = new DataPacketSendEvent($recipients, $packets); - $ev->call(); - if($ev->isCancelled()){ - return false; - } - $recipients = $ev->getTargets(); - - /** @var PacketBroadcaster[] $broadcasters */ - $broadcasters = []; - /** @var NetworkSession[][] $broadcasterTargets */ - $broadcasterTargets = []; - foreach($recipients as $recipient){ - $broadcaster = $recipient->getBroadcaster(); - $broadcasters[spl_object_id($broadcaster)] = $broadcaster; - $broadcasterTargets[spl_object_id($broadcaster)][] = $recipient; - } - foreach($broadcasters as $broadcaster){ - $broadcaster->broadcastPackets($broadcasterTargets[spl_object_id($broadcaster)], $packets); - } - - return true; - }); + return NetworkBroadcastUtils::broadcastPackets($players, $packets); } /** diff --git a/src/network/mcpe/NetworkBroadcastUtils.php b/src/network/mcpe/NetworkBroadcastUtils.php index 3a0253450..8d04d35c5 100644 --- a/src/network/mcpe/NetworkBroadcastUtils.php +++ b/src/network/mcpe/NetworkBroadcastUtils.php @@ -23,7 +23,11 @@ declare(strict_types=1); namespace pocketmine\network\mcpe; +use pocketmine\event\server\DataPacketSendEvent; +use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\player\Player; +use pocketmine\timings\Timings; +use function count; use function spl_object_id; final class NetworkBroadcastUtils{ @@ -32,6 +36,51 @@ final class NetworkBroadcastUtils{ //NOOP } + /** + * @param Player[] $recipients + * @param ClientboundPacket[] $packets + */ + public static function broadcastPackets(array $recipients, array $packets) : bool{ + if(count($packets) === 0){ + throw new \InvalidArgumentException("Cannot broadcast empty list of packets"); + } + + return Timings::$broadcastPackets->time(function() use ($recipients, $packets) : bool{ + /** @var NetworkSession[] $sessions */ + $sessions = []; + foreach($recipients as $player){ + if($player->isConnected()){ + $sessions[] = $player->getNetworkSession(); + } + } + if(count($sessions) === 0){ + return false; + } + + $ev = new DataPacketSendEvent($sessions, $packets); + $ev->call(); + if($ev->isCancelled()){ + return false; + } + $sessions = $ev->getTargets(); + + /** @var PacketBroadcaster[] $uniqueBroadcasters */ + $uniqueBroadcasters = []; + /** @var NetworkSession[][] $broadcasterTargets */ + $broadcasterTargets = []; + foreach($sessions as $recipient){ + $broadcaster = $recipient->getBroadcaster(); + $uniqueBroadcasters[spl_object_id($broadcaster)] = $broadcaster; + $broadcasterTargets[spl_object_id($broadcaster)][spl_object_id($recipient)] = $recipient; + } + foreach($uniqueBroadcasters as $broadcaster){ + $broadcaster->broadcastPackets($broadcasterTargets[spl_object_id($broadcaster)], $packets); + } + + return true; + }); + } + /** * @param Player[] $recipients * @phpstan-param \Closure(EntityEventBroadcaster, array) : void $callback From 337a2547682cdcb0dc36f212ebe3b8fe9f66ae83 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 22:28:51 +0000 Subject: [PATCH 09/12] Use NetworkBroadcastUtils for broadcasting packets this eradicates all but 4 usages of Server in Entity, which is extremely cool. --- src/Server.php | 1 + src/entity/Entity.php | 8 ++++---- src/entity/Human.php | 2 +- src/world/World.php | 7 ++++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Server.php b/src/Server.php index ee509696e..a81b1d74b 100644 --- a/src/Server.php +++ b/src/Server.php @@ -1351,6 +1351,7 @@ class Server{ /** * @param Player[] $players * @param ClientboundPacket[] $packets + * @deprecated */ public function broadcastPackets(array $players, array $packets) : bool{ return NetworkBroadcastUtils::broadcastPackets($players, $packets); diff --git a/src/entity/Entity.php b/src/entity/Entity.php index c206b81d3..c60f2c1c4 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -787,7 +787,7 @@ abstract class Entity{ $this->spawnTo($player); } }else{ - $this->server->broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create( + NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create( $this->id, $this->getOffsetPosition($this->location), $this->location->pitch, @@ -802,7 +802,7 @@ abstract class Entity{ } protected function broadcastMotion() : void{ - $this->server->broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion())]); + NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [SetActorMotionPacket::create($this->id, $this->getMotion())]); } public function getGravity() : float{ @@ -1682,7 +1682,7 @@ abstract class Entity{ * @param Player[]|null $targets */ public function broadcastAnimation(Animation $animation, ?array $targets = null) : void{ - $this->server->broadcastPackets($targets ?? $this->getViewers(), $animation->encode()); + NetworkBroadcastUtils::broadcastPackets($targets ?? $this->getViewers(), $animation->encode()); } /** @@ -1691,7 +1691,7 @@ abstract class Entity{ */ public function broadcastSound(Sound $sound, ?array $targets = null) : void{ if(!$this->silent){ - $this->server->broadcastPackets($targets ?? $this->getViewers(), $sound->encode($this->location)); + NetworkBroadcastUtils::broadcastPackets($targets ?? $this->getViewers(), $sound->encode($this->location)); } } diff --git a/src/entity/Human.php b/src/entity/Human.php index 695777613..cdbd70bd7 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -176,7 +176,7 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ * @param Player[]|null $targets */ public function sendSkin(?array $targets = null) : void{ - $this->server->broadcastPackets($targets ?? $this->hasSpawned, [ + NetworkBroadcastUtils::broadcastPackets($targets ?? $this->hasSpawned, [ PlayerSkinPacket::create($this->getUniqueId(), "", "", SkinAdapterSingleton::get()->toSkinData($this->skin)) ]); } diff --git a/src/world/World.php b/src/world/World.php index cd134f577..34cc33bd7 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -65,6 +65,7 @@ use pocketmine\math\Vector3; use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\StringTag; use pocketmine\network\mcpe\convert\RuntimeBlockMapping; +use pocketmine\network\mcpe\NetworkBroadcastUtils; use pocketmine\network\mcpe\protocol\BlockActorDataPacket; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; @@ -685,7 +686,7 @@ class World implements ChunkManager{ $this->broadcastPacketToViewers($pos, $e); } }else{ - $this->server->broadcastPackets($this->filterViewersForPosition($pos, $players), $pk); + NetworkBroadcastUtils::broadcastPackets($this->filterViewersForPosition($pos, $players), $pk); } } } @@ -711,7 +712,7 @@ class World implements ChunkManager{ $this->broadcastPacketToViewers($pos, $e); } }else{ - $this->server->broadcastPackets($this->filterViewersForPosition($pos, $ev->getRecipients()), $pk); + NetworkBroadcastUtils::broadcastPackets($this->filterViewersForPosition($pos, $ev->getRecipients()), $pk); } } } @@ -1021,7 +1022,7 @@ class World implements ChunkManager{ World::getXZ($index, $chunkX, $chunkZ); $chunkPlayers = $this->getChunkPlayers($chunkX, $chunkZ); if(count($chunkPlayers) > 0){ - $this->server->broadcastPackets($chunkPlayers, $entries); + NetworkBroadcastUtils::broadcastPackets($chunkPlayers, $entries); } } From 08ee825d9183c0492e5ba20e2655eb17e15778e7 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 22:41:19 +0000 Subject: [PATCH 10/12] StandardPacketBroadcaster: Include varint length prefix in length calculation varints encode 7 bits per byte, so a log with base 128 will tell us how many bytes are required to encode the length of the packet. --- src/network/mcpe/StandardPacketBroadcaster.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php index f83cff64d..1ed0f16ae 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -30,6 +30,7 @@ use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\utils\BinaryStream; use function count; +use function log; use function spl_object_id; use function strlen; @@ -60,7 +61,8 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ $packetBuffers = []; foreach($packets as $packet){ $buffer = NetworkSession::encodePacketTimed(PacketSerializer::encoder($this->protocolContext), $packet); - $totalLength += strlen($buffer); + //varint length prefix + packet buffer + $totalLength += (((int) log(strlen($buffer), 128)) + 1) + strlen($buffer); $packetBuffers[] = $buffer; } From 183d1f40389abf369ce164b974b7ede78700c9c3 Mon Sep 17 00:00:00 2001 From: Armen Deroian Date: Wed, 15 Mar 2023 18:47:19 -0400 Subject: [PATCH 11/12] Implement DataPacketPreReceiveEvent (#5559) closes #5554 This is called just before the packet is decoded, allowing the event to be used to drop packets from clients without wasting CPU time decoding them. This can be particularly useful for mitigating denial-of-service attacks. --- .../server/DataPacketPreReceiveEvent.php | 54 +++++++++++++++++++ src/network/mcpe/NetworkSession.php | 7 +++ 2 files changed, 61 insertions(+) create mode 100644 src/event/server/DataPacketPreReceiveEvent.php diff --git a/src/event/server/DataPacketPreReceiveEvent.php b/src/event/server/DataPacketPreReceiveEvent.php new file mode 100644 index 000000000..8369fa257 --- /dev/null +++ b/src/event/server/DataPacketPreReceiveEvent.php @@ -0,0 +1,54 @@ +origin; + } + + public function getPacketId() : int{ + return $this->packetId; + } + + public function getPacketBuffer() : string{ + return $this->packetBuffer; + } +} diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index e0b1e07ec..de7d4436f 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -25,6 +25,7 @@ namespace pocketmine\network\mcpe; use pocketmine\entity\effect\EffectInstance; use pocketmine\event\player\PlayerDuplicateLoginEvent; +use pocketmine\event\server\DataPacketPreReceiveEvent; use pocketmine\event\server\DataPacketReceiveEvent; use pocketmine\event\server\DataPacketSendEvent; use pocketmine\form\Form; @@ -412,6 +413,12 @@ class NetworkSession{ $timings->startTiming(); try{ + $ev = new DataPacketPreReceiveEvent($this, $packet->pid(), $buffer); + $ev->call(); + if($ev->isCancelled()){ + return; + } + $decodeTimings = Timings::getDecodeDataPacketTimings($packet); $decodeTimings->startTiming(); try{ From 941fd03998b53e22126d5183d59aeee104dc7a22 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 22:58:10 +0000 Subject: [PATCH 12/12] Remove useless code --- src/network/mcpe/NetworkSession.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index de7d4436f..4c96ea1ea 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -205,7 +205,6 @@ class NetworkSession{ $this->manager->add($this); $this->logger->info("Session opened"); - $this->entityEventBroadcaster = $entityEventBroadcaster; } private function getLogPrefix() : string{