Merge pull request #5707 from pmmp/hot-events-optimisation

Avoid unnecessary event-related work in hot paths when the events have no registered handlers
This commit is contained in:
Dylan T 2023-08-01 18:19:10 +01:00 committed by GitHub
commit 6000bcccdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 75 additions and 32 deletions

View File

@ -27,6 +27,7 @@ declare(strict_types=1);
namespace pocketmine\event; namespace pocketmine\event;
use pocketmine\timings\Timings; use pocketmine\timings\Timings;
use function count;
use function get_class; use function get_class;
abstract class Event{ abstract class Event{
@ -54,11 +55,11 @@ abstract class Event{
$timings = Timings::getEventTimings($this); $timings = Timings::getEventTimings($this);
$timings->startTiming(); $timings->startTiming();
$handlerList = HandlerListManager::global()->getListFor(get_class($this)); $handlers = HandlerListManager::global()->getHandlersFor(static::class);
++self::$eventCallDepth; ++self::$eventCallDepth;
try{ try{
foreach($handlerList->getListenerList() as $registration){ foreach($handlers as $registration){
$registration->callEvent($this); $registration->callEvent($this);
} }
}finally{ }finally{
@ -66,4 +67,14 @@ abstract class Event{
$timings->stopTiming(); $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;
}
} }

View File

@ -33,8 +33,6 @@ class HandlerList{
/** @var RegisteredListener[][] */ /** @var RegisteredListener[][] */
private array $handlerSlots = []; private array $handlerSlots = [];
private RegisteredListenerCache $handlerCache;
/** @var RegisteredListenerCache[] */ /** @var RegisteredListenerCache[] */
private array $affectedHandlerCaches = []; private array $affectedHandlerCaches = [];
@ -44,9 +42,9 @@ class HandlerList{
*/ */
public function __construct( public function __construct(
private string $class, 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){ for($list = $this; $list !== null; $list = $list->parentList){
$list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache; $list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache;
} }

View File

@ -36,6 +36,11 @@ class HandlerListManager{
/** @var HandlerList[] classname => HandlerList */ /** @var HandlerList[] classname => HandlerList */
private array $allLists = []; private array $allLists = [];
/**
* @var RegisteredListenerCache[] event class name => cache
* @phpstan-var array<class-string<Event>, RegisteredListenerCache>
*/
private array $handlerCaches = [];
/** /**
* Unregisters all the listeners * Unregisters all the listeners
@ -98,7 +103,25 @@ class HandlerListManager{
} }
$parent = self::resolveNearestHandleableParent($class); $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<TEvent> $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();
} }
/** /**

View File

@ -406,11 +406,13 @@ class NetworkSession{
$timings->startTiming(); $timings->startTiming();
try{ try{
if(DataPacketDecodeEvent::hasHandlers()){
$ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer); $ev = new DataPacketDecodeEvent($this, $packet->pid(), $buffer);
$ev->call(); $ev->call();
if($ev->isCancelled()){ if($ev->isCancelled()){
return; return;
} }
}
$decodeTimings = Timings::getDecodeDataPacketTimings($packet); $decodeTimings = Timings::getDecodeDataPacketTimings($packet);
$decodeTimings->startTiming(); $decodeTimings->startTiming();
@ -429,9 +431,13 @@ class NetworkSession{
$decodeTimings->stopTiming(); $decodeTimings->stopTiming();
} }
if(DataPacketReceiveEvent::hasHandlers()){
$ev = new DataPacketReceiveEvent($this, $packet); $ev = new DataPacketReceiveEvent($this, $packet);
$ev->call(); $ev->call();
if(!$ev->isCancelled()){ if($ev->isCancelled()){
return;
}
}
$handlerTimings = Timings::getHandleDataPacketTimings($packet); $handlerTimings = Timings::getHandleDataPacketTimings($packet);
$handlerTimings->startTiming(); $handlerTimings->startTiming();
try{ try{
@ -441,7 +447,6 @@ class NetworkSession{
}finally{ }finally{
$handlerTimings->stopTiming(); $handlerTimings->stopTiming();
} }
}
}finally{ }finally{
$timings->stopTiming(); $timings->stopTiming();
} }
@ -459,12 +464,16 @@ class NetworkSession{
$timings = Timings::getSendDataPacketTimings($packet); $timings = Timings::getSendDataPacketTimings($packet);
$timings->startTiming(); $timings->startTiming();
try{ try{
if(DataPacketSendEvent::hasHandlers()){
$ev = new DataPacketSendEvent([$this], [$packet]); $ev = new DataPacketSendEvent([$this], [$packet]);
$ev->call(); $ev->call();
if($ev->isCancelled()){ if($ev->isCancelled()){
return false; return false;
} }
$packets = $ev->getPackets(); $packets = $ev->getPackets();
}else{
$packets = [$packet];
}
foreach($packets as $evPacket){ foreach($packets as $evPacket){
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $evPacket)); $this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $evPacket));

View File

@ -44,12 +44,14 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
public function broadcastPackets(array $recipients, array $packets) : void{ 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 //TODO: this shouldn't really be called here, since the broadcaster might be replaced by an alternative
//implementation that doesn't fire events //implementation that doesn't fire events
if(DataPacketSendEvent::hasHandlers()){
$ev = new DataPacketSendEvent($recipients, $packets); $ev = new DataPacketSendEvent($recipients, $packets);
$ev->call(); $ev->call();
if($ev->isCancelled()){ if($ev->isCancelled()){
return; return;
} }
$packets = $ev->getPackets(); $packets = $ev->getPackets();
}
$compressors = []; $compressors = [];