From cc8660629b53e8687adeaed2f6c896582dd8e39e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 15 Mar 2023 18:17:45 +0000 Subject: [PATCH] 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