From 667656b1c61cf57394b45c6125ea0e000bf60baf Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 13 Nov 2024 22:08:28 +0000 Subject: [PATCH] Split AsyncHandlerListManager this allows further code deduplication at the expense of needing 2 calls to unregister all handlers --- src/Server.php | 2 + src/event/AsyncEvent.php | 2 +- src/event/AsyncHandlerListManager.php | 43 +++++ src/event/BaseHandlerListManager.php | 156 +++++++++++++++ src/event/HandlerListManager.php | 177 +----------------- src/plugin/PluginManager.php | 4 +- .../src/AsyncEventConcurrencyTest.php | 4 +- .../src/AsyncEventInheritanceTest.php | 4 +- .../src/AsyncEventPriorityTest.php | 4 +- 9 files changed, 219 insertions(+), 177 deletions(-) create mode 100644 src/event/AsyncHandlerListManager.php create mode 100644 src/event/BaseHandlerListManager.php diff --git a/src/Server.php b/src/Server.php index a34349bb5cd..046e4a4819d 100644 --- a/src/Server.php +++ b/src/Server.php @@ -38,6 +38,7 @@ use pocketmine\crash\CrashDumpRenderer; use pocketmine\entity\EntityDataHelper; use pocketmine\entity\Location; +use pocketmine\event\AsyncHandlerListManager; use pocketmine\event\HandlerListManager; use pocketmine\event\player\PlayerCreationEvent; use pocketmine\event\player\PlayerDataSaveEvent; @@ -1485,6 +1486,7 @@ public function forceShutdown() : void{ $this->logger->debug("Removing event handlers"); HandlerListManager::global()->unregisterAll(); + AsyncHandlerListManager::global()->unregisterAll(); if(isset($this->asyncPool)){ $this->logger->debug("Shutting down async task worker pool"); diff --git a/src/event/AsyncEvent.php b/src/event/AsyncEvent.php index 2db63a0df5d..6f5c6fc1437 100644 --- a/src/event/AsyncEvent.php +++ b/src/event/AsyncEvent.php @@ -60,7 +60,7 @@ final public function call() : Promise{ /** @phpstan-var PromiseResolver $globalResolver */ $globalResolver = new PromiseResolver(); - $this->processRemainingHandlers(HandlerListManager::global()->getAsyncHandlersFor(static::class), $globalResolver); + $this->processRemainingHandlers(AsyncHandlerListManager::global()->getHandlersFor(static::class), $globalResolver); return $globalResolver->getPromise(); }finally{ diff --git a/src/event/AsyncHandlerListManager.php b/src/event/AsyncHandlerListManager.php new file mode 100644 index 00000000000..8dd9461c955 --- /dev/null +++ b/src/event/AsyncHandlerListManager.php @@ -0,0 +1,43 @@ + + */ +final class AsyncHandlerListManager extends BaseHandlerListManager{ + private static ?self $globalInstance = null; + + public static function global() : self{ + return self::$globalInstance ?? (self::$globalInstance = new self()); + } + + protected function getBaseEventClass() : string{ + return AsyncEvent::class; + } + + protected function createHandlerList(string $event, ?BaseHandlerList $parentList, RegisteredListenerCache $handlerCache) : BaseHandlerList{ + return new AsyncHandlerList($event, $parentList, $handlerCache); + } +} diff --git a/src/event/BaseHandlerListManager.php b/src/event/BaseHandlerListManager.php new file mode 100644 index 00000000000..c8f0a59e7e6 --- /dev/null +++ b/src/event/BaseHandlerListManager.php @@ -0,0 +1,156 @@ + + */ +abstract class BaseHandlerListManager{ + /** + * @var BaseHandlerList[] classname => BaseHandlerList + * @phpstan-var array, THandlerList> + */ + private array $allLists = []; + /** + * @var RegisteredListenerCache[] event class name => cache + * @phpstan-var array, RegisteredListenerCache> + */ + private array $handlerCaches = []; + + /** + * Unregisters all the listeners + * If a Plugin or Listener is passed, all the listeners with that object will be removed + * + * @phpstan-param TRegisteredListener|Plugin|Listener|null $object + */ + public function unregisterAll(BaseRegisteredListener|Plugin|Listener|null $object = null) : void{ + if($object !== null){ + foreach($this->allLists as $h){ + $h->unregister($object); + } + }else{ + foreach($this->allLists as $h){ + $h->clear(); + } + } + } + + /** + * @phpstan-param \ReflectionClass $class + */ + private static function isValidClass(\ReflectionClass $class) : bool{ + $tags = Utils::parseDocComment((string) $class->getDocComment()); + return !$class->isAbstract() || isset($tags["allowHandle"]); + } + + /** + * @phpstan-param \ReflectionClass $class + * + * @phpstan-return \ReflectionClass|null + */ + private static function resolveNearestHandleableParent(\ReflectionClass $class) : ?\ReflectionClass{ + for($parent = $class->getParentClass(); $parent !== false; $parent = $parent->getParentClass()){ + if(self::isValidClass($parent)){ + return $parent; + } + //NOOP + } + return null; + } + + /** + * @phpstan-return class-string + */ + abstract protected function getBaseEventClass() : string; + + /** + * @phpstan-param class-string $event + * @phpstan-param THandlerList|null $parentList + * @phpstan-param RegisteredListenerCache $handlerCache + * + * @phpstan-return THandlerList + */ + abstract protected function createHandlerList(string $event, ?BaseHandlerList $parentList, RegisteredListenerCache $handlerCache) : BaseHandlerList; + + /** + * Returns the HandlerList for listeners that explicitly handle this event. + * + * Calling this method also lazily initializes the $classMap inheritance tree of handler lists. + * + * @phpstan-param class-string $event + * @phpstan-return THandlerList + * + * @throws \ReflectionException + * @throws \InvalidArgumentException + */ + public function getListFor(string $event) : BaseHandlerList{ + if(isset($this->allLists[$event])){ + return $this->allLists[$event]; + } + + $class = new \ReflectionClass($event); + if(!$class->isSubclassOf($this->getBaseEventClass())){ + throw new \InvalidArgumentException("Cannot get sync handler list for async event"); + } + if(!self::isValidClass($class)){ + throw new \InvalidArgumentException("Event must be non-abstract or have the @allowHandle annotation"); + } + + $parent = self::resolveNearestHandleableParent($class); + /** @phpstan-var RegisteredListenerCache $cache */ + $cache = new RegisteredListenerCache(); + $this->handlerCaches[$event] = $cache; + return $this->allLists[$event] = $this->createHandlerList( + $event, + parentList: $parent !== null ? $this->getListFor($parent->getName()) : null, + handlerCache: $cache + ); + } + + /** + * @phpstan-param class-string $event + * + * @return RegisteredListener[] + * @phpstan-return list + */ + 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(); + } + + /** + * @return HandlerList[] + * @phpstan-return array, THandlerList> + */ + public function getAll() : array{ + return $this->allLists; + } +} diff --git a/src/event/HandlerListManager.php b/src/event/HandlerListManager.php index ecfe073f6a3..85be5238703 100644 --- a/src/event/HandlerListManager.php +++ b/src/event/HandlerListManager.php @@ -23,182 +23,21 @@ namespace pocketmine\event; -use pocketmine\plugin\Plugin; -use pocketmine\utils\Utils; - -class HandlerListManager{ - +/** + * @phpstan-extends BaseHandlerListManager + */ +class HandlerListManager extends BaseHandlerListManager{ private static ?self $globalInstance = null; public static function global() : self{ return self::$globalInstance ?? (self::$globalInstance = new self()); } - /** @var HandlerList[] classname => HandlerList */ - private array $allSyncLists = []; - /** - * @var RegisteredListenerCache[] event class name => cache - * @phpstan-var array, RegisteredListenerCache> - */ - private array $syncHandlerCaches = []; - - /** @var AsyncHandlerList[] classname => AsyncHandlerList */ - private array $allAsyncLists = []; - /** - * @var RegisteredListenerCache[] event class name => cache - * @phpstan-var array, RegisteredListenerCache> - */ - private array $asyncHandlerCaches = []; - - /** - * Unregisters all the listeners - * If a Plugin or Listener is passed, all the listeners with that object will be removed - */ - public function unregisterAll(RegisteredListener|AsyncRegisteredListener|Plugin|Listener|null $object = null) : void{ - if($object !== null){ - if(!$object instanceof AsyncRegisteredListener){ - foreach($this->allSyncLists as $h){ - $h->unregister($object); - } - } - if(!$object instanceof RegisteredListener){ - foreach($this->allAsyncLists as $h){ - $h->unregister($object); - } - } - }else{ - foreach($this->allSyncLists as $h){ - $h->clear(); - } - foreach($this->allAsyncLists as $h){ - $h->clear(); - } - } - } - - /** - * @phpstan-param \ReflectionClass $class - */ - private static function isValidClass(\ReflectionClass $class) : bool{ - $tags = Utils::parseDocComment((string) $class->getDocComment()); - return !$class->isAbstract() || isset($tags["allowHandle"]); - } - - /** - * @phpstan-template TEvent of Event|AsyncEvent - * @phpstan-param \ReflectionClass $class - * - * @phpstan-return \ReflectionClass|null - */ - private static function resolveNearestHandleableParent(\ReflectionClass $class) : ?\ReflectionClass{ - for($parent = $class->getParentClass(); $parent !== false; $parent = $parent->getParentClass()){ - if(self::isValidClass($parent)){ - return $parent; - } - //NOOP - } - return null; - } - - /** - * Returns the HandlerList for listeners that explicitly handle this event. - * - * Calling this method also lazily initializes the $classMap inheritance tree of handler lists. - * - * @phpstan-param class-string $event - * - * @throws \ReflectionException - * @throws \InvalidArgumentException - */ - public function getListFor(string $event) : HandlerList{ - if(isset($this->allSyncLists[$event])){ - return $this->allSyncLists[$event]; - } - - $class = new \ReflectionClass($event); - if(!$class->isSubclassOf(Event::class)){ - throw new \InvalidArgumentException("Cannot get sync handler list for async event"); - } - if(!self::isValidClass($class)){ - throw new \InvalidArgumentException("Event must be non-abstract or have the @allowHandle annotation"); - } - - $parent = self::resolveNearestHandleableParent($class); - /** @phpstan-var RegisteredListenerCache $cache */ - $cache = new RegisteredListenerCache(); - $this->syncHandlerCaches[$event] = $cache; - return $this->allSyncLists[$event] = new HandlerList( - $event, - parentList: $parent !== null ? $this->getListFor($parent->getName()) : null, - handlerCache: $cache - ); - } - - /** - * Returns the HandlerList for async listeners that explicitly handle this event. - * - * @phpstan-param class-string $event - * - * @throws \ReflectionException - * @throws \InvalidArgumentException - */ - public function getAsyncListFor(string $event) : AsyncHandlerList{ - if(isset($this->allAsyncLists[$event])){ - return $this->allAsyncLists[$event]; - } - - $class = new \ReflectionClass($event); - if(!$class->isSubclassOf(AsyncEvent::class)){ - throw new \InvalidArgumentException("Cannot get async handler list for sync event"); - } - if(!self::isValidClass($class)){ - throw new \InvalidArgumentException("Event must be non-abstract or have the @allowHandle annotation"); - } - - $parent = self::resolveNearestHandleableParent($class); - /** @phpstan-var RegisteredListenerCache $cache */ - $cache = new RegisteredListenerCache(); - $this->asyncHandlerCaches[$event] = $cache; - return $this->allAsyncLists[$event] = new AsyncHandlerList( - $event, - parentList: $parent !== null ? $this->getAsyncListFor($parent->getName()) : null, - handlerCache: $cache - ); - } - - /** - * @phpstan-param class-string $event - * - * @return RegisteredListener[] - */ - public function getHandlersFor(string $event) : array{ - $cache = $this->syncHandlerCaches[$event] ?? null; - //getListFor() will populate the cache for the next call - return $cache?->list ?? $this->getListFor($event)->getListenerList(); - } - - /** - * @phpstan-param class-string $event - * - * @return AsyncRegisteredListener[] - */ - public function getAsyncHandlersFor(string $event) : array{ - $cache = $this->asyncHandlerCaches[$event] ?? null; - //getListFor() will populate the cache for the next call - return $cache?->list ?? $this->getAsyncListFor($event)->getListenerList(); - } - - /** - * @return HandlerList[] - */ - public function getAll() : array{ - return $this->allSyncLists; + protected function getBaseEventClass() : string{ + return Event::class; } - /** - * @return AsyncHandlerList[] - */ - public function getAllAsync() : array{ - return $this->allAsyncLists; + protected function createHandlerList(string $event, ?BaseHandlerList $parentList, RegisteredListenerCache $handlerCache) : BaseHandlerList{ + return new HandlerList($event, $parentList, $handlerCache); } } diff --git a/src/plugin/PluginManager.php b/src/plugin/PluginManager.php index 355d1eeaeb3..6e793e04f2c 100644 --- a/src/plugin/PluginManager.php +++ b/src/plugin/PluginManager.php @@ -24,6 +24,7 @@ namespace pocketmine\plugin; use pocketmine\event\AsyncEvent; +use pocketmine\event\AsyncHandlerListManager; use pocketmine\event\AsyncRegisteredListener; use pocketmine\event\Cancellable; use pocketmine\event\Event; @@ -525,6 +526,7 @@ public function disablePlugin(Plugin $plugin) : void{ $plugin->onEnableStateChange(false); $plugin->getScheduler()->shutdown(); HandlerListManager::global()->unregisterAll($plugin); + AsyncHandlerListManager::global()->unregisterAll($plugin); } } @@ -721,7 +723,7 @@ public function registerAsyncEvent(string $event, \Closure $handler, int $priori $timings = Timings::getEventHandlerTimings($event, $handlerName, $plugin->getDescription()->getFullName()); $registeredListener = new AsyncRegisteredListener($handler, $priority, $plugin, $handleCancelled, $exclusiveCall, $timings); - HandlerListManager::global()->getAsyncListFor($event)->register($registeredListener); + AsyncHandlerListManager::global()->getListFor($event)->register($registeredListener); return $registeredListener; } diff --git a/tests/plugins/TesterPlugin/src/AsyncEventConcurrencyTest.php b/tests/plugins/TesterPlugin/src/AsyncEventConcurrencyTest.php index 2eed286ae4c..856ee45adcd 100644 --- a/tests/plugins/TesterPlugin/src/AsyncEventConcurrencyTest.php +++ b/tests/plugins/TesterPlugin/src/AsyncEventConcurrencyTest.php @@ -24,8 +24,8 @@ namespace pmmp\TesterPlugin; use pmmp\TesterPlugin\event\GrandchildAsyncEvent; +use pocketmine\event\AsyncHandlerListManager; use pocketmine\event\EventPriority; -use pocketmine\event\HandlerListManager; use pocketmine\promise\Promise; use pocketmine\promise\PromiseResolver; @@ -50,7 +50,7 @@ public function getDescription() : string{ } public function run() : void{ - HandlerListManager::global()->unregisterAll(); + AsyncHandlerListManager::global()->unregisterAll(); $main = $this->getPlugin(); $pluginManager = $main->getServer()->getPluginManager(); diff --git a/tests/plugins/TesterPlugin/src/AsyncEventInheritanceTest.php b/tests/plugins/TesterPlugin/src/AsyncEventInheritanceTest.php index b55bd081283..d952e36b10e 100644 --- a/tests/plugins/TesterPlugin/src/AsyncEventInheritanceTest.php +++ b/tests/plugins/TesterPlugin/src/AsyncEventInheritanceTest.php @@ -27,8 +27,8 @@ use pmmp\TesterPlugin\event\GrandchildAsyncEvent; use pmmp\TesterPlugin\event\ParentAsyncEvent; use pocketmine\event\AsyncEvent; +use pocketmine\event\AsyncHandlerListManager; use pocketmine\event\EventPriority; -use pocketmine\event\HandlerListManager; use pocketmine\promise\Promise; use function implode; use function shuffle; @@ -51,7 +51,7 @@ public function getDescription() : string{ } public function run() : void{ - HandlerListManager::global()->unregisterAll(); + AsyncHandlerListManager::global()->unregisterAll(); $plugin = $this->getPlugin(); $classes = self::EXPECTED_ORDER; diff --git a/tests/plugins/TesterPlugin/src/AsyncEventPriorityTest.php b/tests/plugins/TesterPlugin/src/AsyncEventPriorityTest.php index ac8b56407c4..4ac5766d1e5 100644 --- a/tests/plugins/TesterPlugin/src/AsyncEventPriorityTest.php +++ b/tests/plugins/TesterPlugin/src/AsyncEventPriorityTest.php @@ -24,8 +24,8 @@ namespace pmmp\TesterPlugin; use pmmp\TesterPlugin\event\GrandchildAsyncEvent; +use pocketmine\event\AsyncHandlerListManager; use pocketmine\event\EventPriority; -use pocketmine\event\HandlerListManager; use pocketmine\promise\Promise; use pocketmine\promise\PromiseResolver; @@ -47,7 +47,7 @@ public function getDescription() : string{ } public function run() : void{ - HandlerListManager::global()->unregisterAll(); + AsyncHandlerListManager::global()->unregisterAll(); $main = $this->getPlugin(); $pluginManager = $main->getServer()->getPluginManager();