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;
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;
}
}

View File

@ -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;
}

View File

@ -36,6 +36,11 @@ class HandlerListManager{
/** @var HandlerList[] classname => HandlerList */
private array $allLists = [];
/**
* @var RegisteredListenerCache[] event class name => cache
* @phpstan-var array<class-string<Event>, 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<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,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));

View File

@ -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 = [];