diff --git a/src/event/Event.php b/src/event/Event.php index 5488285b2..21b8ae36a 100644 --- a/src/event/Event.php +++ b/src/event/Event.php @@ -27,6 +27,7 @@ declare(strict_types=1); namespace pocketmine\event; use pocketmine\timings\Timings; +use function count; use function get_class; abstract class Event{ @@ -54,11 +55,11 @@ abstract class Event{ $timings = Timings::getEventTimings($this); $timings->startTiming(); - $handlerList = HandlerListManager::global()->getListFor(get_class($this)); + $handlers = HandlerListManager::global()->getHandlersFor(static::class); ++self::$eventCallDepth; try{ - foreach($handlerList->getListenerList() as $registration){ + foreach($handlers as $registration){ $registration->callEvent($this); } }finally{ @@ -66,4 +67,14 @@ abstract class Event{ $timings->stopTiming(); } } + + /** + * Returns whether the current class context has any registered global handlers. + * This can be used in hot code paths to avoid unnecessary event object creation. + * + * Usage: SomeEventClass::hasHandlers() + */ + public static function hasHandlers() : bool{ + return count(HandlerListManager::global()->getHandlersFor(static::class)) > 0; + } } diff --git a/src/event/HandlerList.php b/src/event/HandlerList.php index 37811e959..74eedf3a4 100644 --- a/src/event/HandlerList.php +++ b/src/event/HandlerList.php @@ -33,8 +33,6 @@ class HandlerList{ /** @var RegisteredListener[][] */ private array $handlerSlots = []; - private RegisteredListenerCache $handlerCache; - /** @var RegisteredListenerCache[] */ private array $affectedHandlerCaches = []; @@ -44,9 +42,9 @@ class HandlerList{ */ public function __construct( private string $class, - private ?HandlerList $parentList + private ?HandlerList $parentList, + private RegisteredListenerCache $handlerCache = new RegisteredListenerCache() ){ - $this->handlerCache = new RegisteredListenerCache(); for($list = $this; $list !== null; $list = $list->parentList){ $list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache; } diff --git a/src/event/HandlerListManager.php b/src/event/HandlerListManager.php index ab94674cf..047632f54 100644 --- a/src/event/HandlerListManager.php +++ b/src/event/HandlerListManager.php @@ -36,6 +36,11 @@ class HandlerListManager{ /** @var HandlerList[] classname => HandlerList */ private array $allLists = []; + /** + * @var RegisteredListenerCache[] event class name => cache + * @phpstan-var array, RegisteredListenerCache> + */ + private array $handlerCaches = []; /** * Unregisters all the listeners @@ -98,7 +103,25 @@ class HandlerListManager{ } $parent = self::resolveNearestHandleableParent($class); - return $this->allLists[$event] = new HandlerList($event, $parent !== null ? $this->getListFor($parent->getName()) : null); + $cache = new RegisteredListenerCache(); + $this->handlerCaches[$event] = $cache; + return $this->allLists[$event] = new HandlerList( + $event, + parentList: $parent !== null ? $this->getListFor($parent->getName()) : null, + handlerCache: $cache + ); + } + + /** + * @phpstan-template TEvent of Event + * @phpstan-param class-string $event + * + * @return RegisteredListener[] + */ + public function getHandlersFor(string $event) : array{ + $cache = $this->handlerCaches[$event] ?? null; + //getListFor() will populate the cache for the next call + return $cache?->list ?? $this->getListFor($event)->getListenerList(); } /** diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index dbd857b0f..100d9c989 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -406,10 +406,12 @@ class NetworkSession{ $timings->startTiming(); try{ - $ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer); - $ev->call(); - if($ev->isCancelled()){ - return; + if(DataPacketDecodeEvent::hasHandlers()){ + $ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer); + $ev->call(); + if($ev->isCancelled()){ + return; + } } $decodeTimings = Timings::getDecodeDataPacketTimings($packet); @@ -429,19 +431,22 @@ class NetworkSession{ $decodeTimings->stopTiming(); } - $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(); + if(DataPacketReceiveEvent::hasHandlers()){ + $ev = new DataPacketReceiveEvent($this, $packet); + $ev->call(); + if($ev->isCancelled()){ + return; } } + $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{ $timings->stopTiming(); } @@ -459,12 +464,16 @@ class NetworkSession{ $timings = Timings::getSendDataPacketTimings($packet); $timings->startTiming(); try{ - $ev = new DataPacketSendEvent([$this], [$packet]); - $ev->call(); - if($ev->isCancelled()){ - return false; + if(DataPacketSendEvent::hasHandlers()){ + $ev = new DataPacketSendEvent([$this], [$packet]); + $ev->call(); + if($ev->isCancelled()){ + return false; + } + $packets = $ev->getPackets(); + }else{ + $packets = [$packet]; } - $packets = $ev->getPackets(); foreach($packets as $evPacket){ $this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $evPacket)); diff --git a/src/network/mcpe/StandardPacketBroadcaster.php b/src/network/mcpe/StandardPacketBroadcaster.php index 9de3d214a..c200859fd 100644 --- a/src/network/mcpe/StandardPacketBroadcaster.php +++ b/src/network/mcpe/StandardPacketBroadcaster.php @@ -44,12 +44,14 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{ public function broadcastPackets(array $recipients, array $packets) : void{ //TODO: this shouldn't really be called here, since the broadcaster might be replaced by an alternative //implementation that doesn't fire events - $ev = new DataPacketSendEvent($recipients, $packets); - $ev->call(); - if($ev->isCancelled()){ - return; + if(DataPacketSendEvent::hasHandlers()){ + $ev = new DataPacketSendEvent($recipients, $packets); + $ev->call(); + if($ev->isCancelled()){ + return; + } + $packets = $ev->getPackets(); } - $packets = $ev->getPackets(); $compressors = [];