diff --git a/src/event/AsyncHandlerList.php b/src/event/AsyncHandlerList.php index 6a78f0f65..db5c612ff 100644 --- a/src/event/AsyncHandlerList.php +++ b/src/event/AsyncHandlerList.php @@ -23,134 +23,21 @@ declare(strict_types=1); namespace pocketmine\event; -use pocketmine\plugin\Plugin; -use function array_merge; -use function krsort; -use function spl_object_id; use function uasort; -use const SORT_NUMERIC; -class AsyncHandlerList{ - //TODO: we can probably deduplicate most of this code with the sync side if we throw in some generics - - /** @var AsyncRegisteredListener[][] */ - private array $handlerSlots = []; - - /** - * @var RegisteredListenerCache[] - * @phpstan-var array> - */ - private array $affectedHandlerCaches = []; - - /** - * @phpstan-param class-string $class - * @phpstan-param RegisteredListenerCache $handlerCache - */ - public function __construct( - private string $class, - private ?AsyncHandlerList $parentList, - private RegisteredListenerCache $handlerCache = new RegisteredListenerCache() - ){ - for($list = $this; $list !== null; $list = $list->parentList){ - $list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache; - } - } - - /** - * @throws \Exception - */ - public function register(AsyncRegisteredListener $listener) : void{ - if(isset($this->handlerSlots[$listener->getPriority()][spl_object_id($listener)])){ - throw new \InvalidArgumentException("This listener is already registered to priority {$listener->getPriority()} of event {$this->class}"); - } - $this->handlerSlots[$listener->getPriority()][spl_object_id($listener)] = $listener; - $this->invalidateAffectedCaches(); - } - - /** - * @param AsyncRegisteredListener[] $listeners - */ - public function registerAll(array $listeners) : void{ - foreach($listeners as $listener){ - $this->register($listener); - } - $this->invalidateAffectedCaches(); - } - - public function unregister(AsyncRegisteredListener|Plugin|Listener $object) : void{ - if($object instanceof Plugin || $object instanceof Listener){ - foreach($this->handlerSlots as $priority => $list){ - foreach($list as $hash => $listener){ - if(($object instanceof Plugin && $listener->getPlugin() === $object) - || ($object instanceof Listener && (new \ReflectionFunction($listener->getHandler()))->getClosureThis() === $object) //this doesn't even need to be a listener :D - ){ - unset($this->handlerSlots[$priority][$hash]); - } - } +/** + * @phpstan-extends BaseHandlerList + */ +class AsyncHandlerList extends BaseHandlerList{ + protected function sortSamePriorityListeners(array $listeners) : array{ + uasort($listeners, function(AsyncRegisteredListener $left, AsyncRegisteredListener $right) : int{ + //While the system can handle these in any order, it's better for latency if concurrent handlers + //are processed together. It doesn't matter whether they are processed before or after exclusive handlers. + if($right->canBeCalledConcurrently()){ + return $left->canBeCalledConcurrently() ? 0 : 1; } - }else{ - unset($this->handlerSlots[$object->getPriority()][spl_object_id($object)]); - } - $this->invalidateAffectedCaches(); - } - - public function clear() : void{ - $this->handlerSlots = []; - $this->invalidateAffectedCaches(); - } - - /** - * @return AsyncRegisteredListener[] - */ - public function getListenersByPriority(int $priority) : array{ - return $this->handlerSlots[$priority] ?? []; - } - - public function getParent() : ?AsyncHandlerList{ - return $this->parentList; - } - - /** - * Invalidates all known caches which might be affected by this list's contents. - */ - private function invalidateAffectedCaches() : void{ - foreach($this->affectedHandlerCaches as $cache){ - $cache->list = null; - } - } - - /** - * @return AsyncRegisteredListener[] - * @phpstan-return list - */ - public function getListenerList() : array{ - if($this->handlerCache->list !== null){ - return $this->handlerCache->list; - } - - $handlerLists = []; - for($currentList = $this; $currentList !== null; $currentList = $currentList->parentList){ - $handlerLists[] = $currentList; - } - - $listenersByPriority = []; - foreach($handlerLists as $currentList){ - foreach($currentList->handlerSlots as $priority => $listeners){ - uasort($listeners, function(AsyncRegisteredListener $left, AsyncRegisteredListener $right) : int{ - //While the system can handle these in any order, it's better for latency if concurrent handlers - //are processed together. It doesn't matter whether they are processed before or after exclusive handlers. - if($right->canBeCalledConcurrently()){ - return $left->canBeCalledConcurrently() ? 0 : 1; - } - return -1; - }); - $listenersByPriority[$priority] = array_merge($listenersByPriority[$priority] ?? [], $listeners); - } - } - - //TODO: why on earth do the priorities have higher values for lower priority? - krsort($listenersByPriority, SORT_NUMERIC); - - return $this->handlerCache->list = array_merge(...$listenersByPriority); + return -1; + }); + return $listeners; } } diff --git a/src/event/AsyncRegisteredListener.php b/src/event/AsyncRegisteredListener.php index 4e6158e9e..4c5c0b957 100644 --- a/src/event/AsyncRegisteredListener.php +++ b/src/event/AsyncRegisteredListener.php @@ -26,32 +26,17 @@ namespace pocketmine\event; use pocketmine\plugin\Plugin; use pocketmine\promise\Promise; use pocketmine\timings\TimingsHandler; -use function in_array; -class AsyncRegisteredListener{ +class AsyncRegisteredListener extends BaseRegisteredListener{ public function __construct( - private \Closure $handler, - private int $priority, - private Plugin $plugin, - private bool $handleCancelled, + \Closure $handler, + int $priority, + Plugin $plugin, + bool $handleCancelled, private bool $exclusiveCall, - private TimingsHandler $timings + TimingsHandler $timings ){ - if(!in_array($priority, EventPriority::ALL, true)){ - throw new \InvalidArgumentException("Invalid priority number $priority"); - } - } - - public function getHandler() : \Closure{ - return $this->handler; - } - - public function getPlugin() : Plugin{ - return $this->plugin; - } - - public function getPriority() : int{ - return $this->priority; + parent::__construct($handler, $priority, $plugin, $handleCancelled, $timings); } /** @@ -69,9 +54,6 @@ class AsyncRegisteredListener{ } } - public function isHandlingCancelled() : bool{ - return $this->handleCancelled; - } public function canBeCalledConcurrently() : bool{ return !$this->exclusiveCall; } diff --git a/src/event/BaseHandlerList.php b/src/event/BaseHandlerList.php new file mode 100644 index 000000000..ebdd7c890 --- /dev/null +++ b/src/event/BaseHandlerList.php @@ -0,0 +1,170 @@ +> + */ + private array $handlerSlots = []; + + /** + * @var RegisteredListenerCache[] + * @phpstan-var array> + */ + private array $affectedHandlerCaches = []; + + /** + * @phpstan-param class-string $class + * @phpstan-param ?static $parentList + * @phpstan-param RegisteredListenerCache $handlerCache + */ + public function __construct( + private string $class, + private ?BaseHandlerList $parentList, + private RegisteredListenerCache $handlerCache = new RegisteredListenerCache() + ){ + for($list = $this; $list !== null; $list = $list->parentList){ + $list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache; + } + } + + /** + * @phpstan-param TListener $listener + */ + public function register(BaseRegisteredListener $listener) : void{ + if(isset($this->handlerSlots[$listener->getPriority()][spl_object_id($listener)])){ + throw new \InvalidArgumentException("This listener is already registered to priority {$listener->getPriority()} of event {$this->class}"); + } + $this->handlerSlots[$listener->getPriority()][spl_object_id($listener)] = $listener; + $this->invalidateAffectedCaches(); + } + + /** + * @param BaseRegisteredListener[] $listeners + * @phpstan-param array $listeners + */ + public function registerAll(array $listeners) : void{ + foreach($listeners as $listener){ + $this->register($listener); + } + $this->invalidateAffectedCaches(); + } + + /** + * @phpstan-param TListener|Plugin|Listener $object + */ + public function unregister(BaseRegisteredListener|Plugin|Listener $object) : void{ + if($object instanceof Plugin || $object instanceof Listener){ + foreach($this->handlerSlots as $priority => $list){ + foreach($list as $hash => $listener){ + if(($object instanceof Plugin && $listener->getPlugin() === $object) + || ($object instanceof Listener && (new \ReflectionFunction($listener->getHandler()))->getClosureThis() === $object) //this doesn't even need to be a listener :D + ){ + unset($this->handlerSlots[$priority][$hash]); + } + } + } + }else{ + unset($this->handlerSlots[$object->getPriority()][spl_object_id($object)]); + } + $this->invalidateAffectedCaches(); + } + + public function clear() : void{ + $this->handlerSlots = []; + $this->invalidateAffectedCaches(); + } + + /** + * @return BaseRegisteredListener[] + * @phpstan-return array + */ + public function getListenersByPriority(int $priority) : array{ + return $this->handlerSlots[$priority] ?? []; + } + + /** + * @phpstan-return static + */ + public function getParent() : ?BaseHandlerList{ + return $this->parentList; + } + + /** + * Invalidates all known caches which might be affected by this list's contents. + */ + private function invalidateAffectedCaches() : void{ + foreach($this->affectedHandlerCaches as $cache){ + $cache->list = null; + } + } + + /** + * @param BaseRegisteredListener[] $listeners + * @phpstan-param array $listeners + * + * @return BaseRegisteredListener[] + * @phpstan-return array + */ + abstract protected function sortSamePriorityListeners(array $listeners) : array; + + /** + * @return BaseRegisteredListener[] + * @phpstan-return list + */ + public function getListenerList() : array{ + if($this->handlerCache->list !== null){ + return $this->handlerCache->list; + } + + $handlerLists = []; + for($currentList = $this; $currentList !== null; $currentList = $currentList->parentList){ + $handlerLists[] = $currentList; + } + + $listenersByPriority = []; + foreach($handlerLists as $currentList){ + foreach($currentList->handlerSlots as $priority => $listeners){ + $listenersByPriority[$priority] = array_merge($listenersByPriority[$priority] ?? [], $this->sortSamePriorityListeners($listeners)); + } + } + + //TODO: why on earth do the priorities have higher values for lower priority? + krsort($listenersByPriority, SORT_NUMERIC); + + return $this->handlerCache->list = array_merge(...$listenersByPriority); + } +} diff --git a/src/event/BaseRegisteredListener.php b/src/event/BaseRegisteredListener.php new file mode 100644 index 000000000..0c685f09b --- /dev/null +++ b/src/event/BaseRegisteredListener.php @@ -0,0 +1,58 @@ +handler; + } + + public function getPlugin() : Plugin{ + return $this->plugin; + } + + public function getPriority() : int{ + return $this->priority; + } + + public function isHandlingCancelled() : bool{ + return $this->handleCancelled; + } +} diff --git a/src/event/HandlerList.php b/src/event/HandlerList.php index 0c6b0f66e..53c5148fa 100644 --- a/src/event/HandlerList.php +++ b/src/event/HandlerList.php @@ -23,123 +23,11 @@ declare(strict_types=1); namespace pocketmine\event; -use pocketmine\plugin\Plugin; -use function array_merge; -use function krsort; -use function spl_object_id; -use const SORT_NUMERIC; - -class HandlerList{ - /** @var RegisteredListener[][] */ - private array $handlerSlots = []; - - /** - * @var RegisteredListenerCache[] - * @phpstan-var array> - */ - private array $affectedHandlerCaches = []; - - /** - * @phpstan-param class-string $class - * @phpstan-param RegisteredListenerCache $handlerCache - */ - public function __construct( - private string $class, - private ?HandlerList $parentList, - private RegisteredListenerCache $handlerCache = new RegisteredListenerCache() - ){ - for($list = $this; $list !== null; $list = $list->parentList){ - $list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache; - } - } - - /** - * @throws \Exception - */ - public function register(RegisteredListener $listener) : void{ - if(isset($this->handlerSlots[$listener->getPriority()][spl_object_id($listener)])){ - throw new \InvalidArgumentException("This listener is already registered to priority {$listener->getPriority()} of event {$this->class}"); - } - $this->handlerSlots[$listener->getPriority()][spl_object_id($listener)] = $listener; - $this->invalidateAffectedCaches(); - } - - /** - * @param RegisteredListener[] $listeners - */ - public function registerAll(array $listeners) : void{ - foreach($listeners as $listener){ - $this->register($listener); - } - $this->invalidateAffectedCaches(); - } - - public function unregister(RegisteredListener|Plugin|Listener $object) : void{ - if($object instanceof Plugin || $object instanceof Listener){ - foreach($this->handlerSlots as $priority => $list){ - foreach($list as $hash => $listener){ - if(($object instanceof Plugin && $listener->getPlugin() === $object) - || ($object instanceof Listener && (new \ReflectionFunction($listener->getHandler()))->getClosureThis() === $object) //this doesn't even need to be a listener :D - ){ - unset($this->handlerSlots[$priority][$hash]); - } - } - } - }else{ - unset($this->handlerSlots[$object->getPriority()][spl_object_id($object)]); - } - $this->invalidateAffectedCaches(); - } - - public function clear() : void{ - $this->handlerSlots = []; - $this->invalidateAffectedCaches(); - } - - /** - * @return RegisteredListener[] - */ - public function getListenersByPriority(int $priority) : array{ - return $this->handlerSlots[$priority] ?? []; - } - - public function getParent() : ?HandlerList{ - return $this->parentList; - } - - /** - * Invalidates all known caches which might be affected by this list's contents. - */ - private function invalidateAffectedCaches() : void{ - foreach($this->affectedHandlerCaches as $cache){ - $cache->list = null; - } - } - - /** - * @return RegisteredListener[] - * @phpstan-return list - */ - public function getListenerList() : array{ - if($this->handlerCache->list !== null){ - return $this->handlerCache->list; - } - - $handlerLists = []; - for($currentList = $this; $currentList !== null; $currentList = $currentList->parentList){ - $handlerLists[] = $currentList; - } - - $listenersByPriority = []; - foreach($handlerLists as $currentList){ - foreach($currentList->handlerSlots as $priority => $listeners){ - $listenersByPriority[$priority] = array_merge($listenersByPriority[$priority] ?? [], $listeners); - } - } - - //TODO: why on earth do the priorities have higher values for lower priority? - krsort($listenersByPriority, SORT_NUMERIC); - - return $this->handlerCache->list = array_merge(...$listenersByPriority); +/** + * @phpstan-extends BaseHandlerList + */ +class HandlerList extends BaseHandlerList{ + protected function sortSamePriorityListeners(array $listeners) : array{ + return $listeners; } } diff --git a/src/event/RegisteredListener.php b/src/event/RegisteredListener.php index 6b29dfec3..a2217c299 100644 --- a/src/event/RegisteredListener.php +++ b/src/event/RegisteredListener.php @@ -23,34 +23,7 @@ declare(strict_types=1); namespace pocketmine\event; -use pocketmine\plugin\Plugin; -use pocketmine\timings\TimingsHandler; -use function in_array; - -class RegisteredListener{ - public function __construct( - private \Closure $handler, - private int $priority, - private Plugin $plugin, - private bool $handleCancelled, - private TimingsHandler $timings - ){ - if(!in_array($priority, EventPriority::ALL, true)){ - throw new \InvalidArgumentException("Invalid priority number $priority"); - } - } - - public function getHandler() : \Closure{ - return $this->handler; - } - - public function getPlugin() : Plugin{ - return $this->plugin; - } - - public function getPriority() : int{ - return $this->priority; - } +class RegisteredListener extends BaseRegisteredListener{ public function callEvent(Event $event) : void{ if($event instanceof Cancellable && $event->isCancelled() && !$this->isHandlingCancelled()){ @@ -63,8 +36,4 @@ class RegisteredListener{ $this->timings->stopTiming(); } } - - public function isHandlingCancelled() : bool{ - return $this->handleCancelled; - } } diff --git a/tests/phpstan/configs/impossible-generics.neon b/tests/phpstan/configs/impossible-generics.neon index a2d10becf..9b57d6604 100644 --- a/tests/phpstan/configs/impossible-generics.neon +++ b/tests/phpstan/configs/impossible-generics.neon @@ -6,17 +6,12 @@ parameters: path: ../../../src/event/AsyncRegisteredListener.php - - message: "#^Method pocketmine\\\\event\\\\AsyncRegisteredListener\\:\\:getHandler\\(\\) return type has no signature specified for Closure\\.$#" + message: "#^Method pocketmine\\\\event\\\\BaseRegisteredListener\\:\\:__construct\\(\\) has parameter \\$handler with no signature specified for Closure\\.$#" count: 1 - path: ../../../src/event/AsyncRegisteredListener.php + path: ../../../src/event/BaseRegisteredListener.php - - message: "#^Method pocketmine\\\\event\\\\RegisteredListener\\:\\:__construct\\(\\) has parameter \\$handler with no signature specified for Closure\\.$#" + message: "#^Method pocketmine\\\\event\\\\BaseRegisteredListener\\:\\:getHandler\\(\\) return type has no signature specified for Closure\\.$#" count: 1 - path: ../../../src/event/RegisteredListener.php - - - - message: "#^Method pocketmine\\\\event\\\\RegisteredListener\\:\\:getHandler\\(\\) return type has no signature specified for Closure\\.$#" - count: 1 - path: ../../../src/event/RegisteredListener.php + path: ../../../src/event/BaseRegisteredListener.php