mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-17 08:54:22 +00:00
Handler inheritance is now working
this code should also perform somewhat better
This commit is contained in:
parent
fa796535ff
commit
8aed5d6b27
@ -26,7 +26,6 @@ namespace pocketmine\event;
|
|||||||
use pocketmine\promise\Promise;
|
use pocketmine\promise\Promise;
|
||||||
use pocketmine\promise\PromiseResolver;
|
use pocketmine\promise\PromiseResolver;
|
||||||
use pocketmine\timings\Timings;
|
use pocketmine\timings\Timings;
|
||||||
use function array_shift;
|
|
||||||
use function count;
|
use function count;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,7 +60,7 @@ abstract class AsyncEvent{
|
|||||||
/** @phpstan-var PromiseResolver<static> $globalResolver */
|
/** @phpstan-var PromiseResolver<static> $globalResolver */
|
||||||
$globalResolver = new PromiseResolver();
|
$globalResolver = new PromiseResolver();
|
||||||
|
|
||||||
$this->asyncEachPriority(HandlerListManager::global()->getAsyncListFor(static::class), EventPriority::ALL, $globalResolver);
|
$this->processRemainingHandlers(HandlerListManager::global()->getAsyncHandlersFor(static::class), $globalResolver);
|
||||||
|
|
||||||
return $globalResolver->getPromise();
|
return $globalResolver->getPromise();
|
||||||
}finally{
|
}finally{
|
||||||
@ -71,84 +70,51 @@ abstract class AsyncEvent{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: this should use EventPriority constants for the list type but it's inconvenient with the current design
|
* @param AsyncRegisteredListener[] $handlers
|
||||||
* @phpstan-param list<int> $remaining
|
|
||||||
* @phpstan-param PromiseResolver<static> $globalResolver
|
* @phpstan-param PromiseResolver<static> $globalResolver
|
||||||
*/
|
*/
|
||||||
private function asyncEachPriority(AsyncHandlerList $handlerList, array $remaining, PromiseResolver $globalResolver) : void{
|
private function processRemainingHandlers(array $handlers, PromiseResolver $globalResolver) : void{
|
||||||
while(true){
|
$currentPriority = null;
|
||||||
$nextPriority = array_shift($remaining);
|
$awaitPromises = [];
|
||||||
if($nextPriority === null){
|
foreach($handlers as $k => $handler){
|
||||||
$globalResolver->resolve($this);
|
$priority = $handler->getPriority();
|
||||||
|
if(count($awaitPromises) > 0 && $currentPriority !== null && $currentPriority !== $priority){
|
||||||
|
//wait for concurrent promises from previous priority to complete
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$promise = $this->callPriority($handlerList, $nextPriority);
|
$currentPriority = $priority;
|
||||||
|
if($handler->canBeCalledConcurrently()){
|
||||||
|
unset($handlers[$k]);
|
||||||
|
$promise = $handler->callAsync($this);
|
||||||
if($promise !== null){
|
if($promise !== null){
|
||||||
$promise->onCompletion(
|
$awaitPromises[] = $promise;
|
||||||
onSuccess: fn() => $this->asyncEachPriority($handlerList, $remaining, $globalResolver),
|
|
||||||
onFailure: $globalResolver->reject(...)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @phpstan-return Promise<null>
|
|
||||||
*/
|
|
||||||
private function callPriority(AsyncHandlerList $handlerList, int $priority) : ?Promise{
|
|
||||||
$handlers = $handlerList->getListenersByPriority($priority);
|
|
||||||
if(count($handlers) === 0){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @phpstan-var PromiseResolver<null> $resolver */
|
|
||||||
$resolver = new PromiseResolver();
|
|
||||||
|
|
||||||
$concurrentPromises = [];
|
|
||||||
$nonConcurrentHandlers = [];
|
|
||||||
foreach($handlers as $registration){
|
|
||||||
if($registration->canBeCalledConcurrently()){
|
|
||||||
$result = $registration->callAsync($this);
|
|
||||||
if($result !== null) {
|
|
||||||
$concurrentPromises[] = $result;
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
$nonConcurrentHandlers[] = $registration;
|
if(count($awaitPromises) > 0){
|
||||||
}
|
//wait for concurrent promises to complete
|
||||||
}
|
|
||||||
|
|
||||||
Promise::all($concurrentPromises)->onCompletion(
|
|
||||||
onSuccess: fn() => $this->processExclusiveHandlers($nonConcurrentHandlers, $resolver),
|
|
||||||
onFailure: $resolver->reject(...)
|
|
||||||
);
|
|
||||||
|
|
||||||
return $resolver->getPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param AsyncRegisteredListener[] $handlers
|
|
||||||
* @phpstan-param PromiseResolver<null> $resolver
|
|
||||||
*/
|
|
||||||
private function processExclusiveHandlers(array $handlers, PromiseResolver $resolver) : void{
|
|
||||||
while(true){
|
|
||||||
$handler = array_shift($handlers);
|
|
||||||
if($handler === null){
|
|
||||||
$resolver->resolve(null);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
$result = $handler->callAsync($this);
|
|
||||||
if($result instanceof Promise){
|
|
||||||
//wait for this promise to resolve before calling the next handler
|
|
||||||
$result->onCompletion(
|
|
||||||
onSuccess: fn() => $this->processExclusiveHandlers($handlers, $resolver),
|
|
||||||
onFailure: $resolver->reject(...)
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//this handler didn't return a promise - continue directly to the next one
|
unset($handlers[$k]);
|
||||||
|
$promise = $handler->callAsync($this);
|
||||||
|
if($promise !== null){
|
||||||
|
$promise->onCompletion(
|
||||||
|
onSuccess: fn() => $this->processRemainingHandlers($handlers, $globalResolver),
|
||||||
|
onFailure: $globalResolver->reject(...)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(count($awaitPromises) > 0){
|
||||||
|
Promise::all($awaitPromises)->onCompletion(
|
||||||
|
onSuccess: fn() => $this->processRemainingHandlers($handlers, $globalResolver),
|
||||||
|
onFailure: $globalResolver->reject(...)
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
$globalResolver->resolve($this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,25 +24,47 @@ declare(strict_types=1);
|
|||||||
namespace pocketmine\event;
|
namespace pocketmine\event;
|
||||||
|
|
||||||
use pocketmine\plugin\Plugin;
|
use pocketmine\plugin\Plugin;
|
||||||
|
use function array_merge;
|
||||||
|
use function krsort;
|
||||||
use function spl_object_id;
|
use function spl_object_id;
|
||||||
|
use function uasort;
|
||||||
|
use const SORT_NUMERIC;
|
||||||
|
|
||||||
class AsyncHandlerList{
|
class AsyncHandlerList{
|
||||||
|
//TODO: we can probably deduplicate most of this code with the sync side if we throw in some generics
|
||||||
|
|
||||||
/** @var AsyncRegisteredListener[][] */
|
/** @var AsyncRegisteredListener[][] */
|
||||||
private array $handlerSlots = [];
|
private array $handlerSlots = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @phpstan-param class-string<AsyncEvent> $class
|
* @var RegisteredListenerCache[]
|
||||||
|
* @phpstan-var array<int, RegisteredListenerCache<AsyncRegisteredListener>>
|
||||||
|
*/
|
||||||
|
private array $affectedHandlerCaches = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phpstan-param class-string<covariant AsyncEvent> $class
|
||||||
|
* @phpstan-param RegisteredListenerCache<AsyncRegisteredListener> $handlerCache
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $class,
|
private string $class,
|
||||||
private ?AsyncHandlerList $parentList,
|
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{
|
public function register(AsyncRegisteredListener $listener) : void{
|
||||||
if(isset($this->handlerSlots[$listener->getPriority()][spl_object_id($listener)])){
|
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}");
|
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->handlerSlots[$listener->getPriority()][spl_object_id($listener)] = $listener;
|
||||||
|
$this->invalidateAffectedCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,10 +74,10 @@ class AsyncHandlerList{
|
|||||||
foreach($listeners as $listener){
|
foreach($listeners as $listener){
|
||||||
$this->register($listener);
|
$this->register($listener);
|
||||||
}
|
}
|
||||||
|
$this->invalidateAffectedCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function unregister(AsyncRegisteredListener|Plugin|Listener $object) : void{
|
public function unregister(AsyncRegisteredListener|Plugin|Listener $object) : void{
|
||||||
//TODO: Not loving the duplication here
|
|
||||||
if($object instanceof Plugin || $object instanceof Listener){
|
if($object instanceof Plugin || $object instanceof Listener){
|
||||||
foreach($this->handlerSlots as $priority => $list){
|
foreach($this->handlerSlots as $priority => $list){
|
||||||
foreach($list as $hash => $listener){
|
foreach($list as $hash => $listener){
|
||||||
@ -69,10 +91,12 @@ class AsyncHandlerList{
|
|||||||
}else{
|
}else{
|
||||||
unset($this->handlerSlots[$object->getPriority()][spl_object_id($object)]);
|
unset($this->handlerSlots[$object->getPriority()][spl_object_id($object)]);
|
||||||
}
|
}
|
||||||
|
$this->invalidateAffectedCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clear() : void{
|
public function clear() : void{
|
||||||
$this->handlerSlots = [];
|
$this->handlerSlots = [];
|
||||||
|
$this->invalidateAffectedCaches();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,4 +109,48 @@ class AsyncHandlerList{
|
|||||||
public function getParent() : ?AsyncHandlerList{
|
public function getParent() : ?AsyncHandlerList{
|
||||||
return $this->parentList;
|
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<AsyncRegisteredListener>
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,11 +33,15 @@ class HandlerList{
|
|||||||
/** @var RegisteredListener[][] */
|
/** @var RegisteredListener[][] */
|
||||||
private array $handlerSlots = [];
|
private array $handlerSlots = [];
|
||||||
|
|
||||||
/** @var RegisteredListenerCache[] */
|
/**
|
||||||
|
* @var RegisteredListenerCache[]
|
||||||
|
* @phpstan-var array<int, RegisteredListenerCache<RegisteredListener>>
|
||||||
|
*/
|
||||||
private array $affectedHandlerCaches = [];
|
private array $affectedHandlerCaches = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @phpstan-param class-string<covariant Event> $class
|
* @phpstan-param class-string<covariant Event> $class
|
||||||
|
* @phpstan-param RegisteredListenerCache<RegisteredListener> $handlerCache
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private string $class,
|
private string $class,
|
||||||
|
@ -38,12 +38,17 @@ class HandlerListManager{
|
|||||||
private array $allSyncLists = [];
|
private array $allSyncLists = [];
|
||||||
/**
|
/**
|
||||||
* @var RegisteredListenerCache[] event class name => cache
|
* @var RegisteredListenerCache[] event class name => cache
|
||||||
* @phpstan-var array<class-string<Event|AsyncEvent>, RegisteredListenerCache>
|
* @phpstan-var array<class-string<Event>, RegisteredListenerCache<RegisteredListener>>
|
||||||
*/
|
*/
|
||||||
private array $syncHandlerCaches = [];
|
private array $syncHandlerCaches = [];
|
||||||
|
|
||||||
/** @var AsyncHandlerList[] classname => AsyncHandlerList */
|
/** @var AsyncHandlerList[] classname => AsyncHandlerList */
|
||||||
private array $allAsyncLists = [];
|
private array $allAsyncLists = [];
|
||||||
|
/**
|
||||||
|
* @var RegisteredListenerCache[] event class name => cache
|
||||||
|
* @phpstan-var array<class-string<AsyncEvent>, RegisteredListenerCache<AsyncRegisteredListener>>
|
||||||
|
*/
|
||||||
|
private array $asyncHandlerCaches = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregisters all the listeners
|
* Unregisters all the listeners
|
||||||
@ -119,6 +124,7 @@ class HandlerListManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$parent = self::resolveNearestHandleableParent($class);
|
$parent = self::resolveNearestHandleableParent($class);
|
||||||
|
/** @phpstan-var RegisteredListenerCache<RegisteredListener> $cache */
|
||||||
$cache = new RegisteredListenerCache();
|
$cache = new RegisteredListenerCache();
|
||||||
$this->syncHandlerCaches[$event] = $cache;
|
$this->syncHandlerCaches[$event] = $cache;
|
||||||
return $this->allSyncLists[$event] = new HandlerList(
|
return $this->allSyncLists[$event] = new HandlerList(
|
||||||
@ -150,9 +156,13 @@ class HandlerListManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$parent = self::resolveNearestHandleableParent($class);
|
$parent = self::resolveNearestHandleableParent($class);
|
||||||
|
/** @phpstan-var RegisteredListenerCache<AsyncRegisteredListener> $cache */
|
||||||
|
$cache = new RegisteredListenerCache();
|
||||||
|
$this->asyncHandlerCaches[$event] = $cache;
|
||||||
return $this->allAsyncLists[$event] = new AsyncHandlerList(
|
return $this->allAsyncLists[$event] = new AsyncHandlerList(
|
||||||
$event,
|
$event,
|
||||||
parentList: $parent !== null ? $this->getAsyncListFor($parent->getName()) : null,
|
parentList: $parent !== null ? $this->getAsyncListFor($parent->getName()) : null,
|
||||||
|
handlerCache: $cache
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +177,17 @@ class HandlerListManager{
|
|||||||
return $cache?->list ?? $this->getListFor($event)->getListenerList();
|
return $cache?->list ?? $this->getListFor($event)->getListenerList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phpstan-param class-string<covariant AsyncEvent> $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[]
|
* @return HandlerList[]
|
||||||
*/
|
*/
|
||||||
|
@ -25,14 +25,14 @@ namespace pocketmine\event;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
* @phpstan-template TListener
|
||||||
*/
|
*/
|
||||||
final class RegisteredListenerCache{
|
final class RegisteredListenerCache{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of all handlers that will be called for a particular event, ordered by execution order.
|
* List of all handlers that will be called for a particular event, ordered by execution order.
|
||||||
*
|
*
|
||||||
* @var RegisteredListener[]
|
* @phpstan-var list<TListener>
|
||||||
* @phpstan-var list<RegisteredListener>
|
|
||||||
*/
|
*/
|
||||||
public ?array $list = null;
|
public ?array $list = null;
|
||||||
}
|
}
|
||||||
|
@ -58,11 +58,12 @@ final class AsyncEventInheritanceTest extends Test{
|
|||||||
$plugin = $this->getPlugin();
|
$plugin = $this->getPlugin();
|
||||||
$classes = self::EXPECTED_ORDER;
|
$classes = self::EXPECTED_ORDER;
|
||||||
shuffle($classes);
|
shuffle($classes);
|
||||||
foreach($classes as $event){
|
foreach($classes as $class){
|
||||||
$plugin->getServer()->getPluginManager()->registerAsyncEvent(
|
$plugin->getServer()->getPluginManager()->registerAsyncEvent(
|
||||||
$event,
|
$class,
|
||||||
function(AsyncEvent $event) : ?Promise{
|
function(AsyncEvent $event) use ($class) : ?Promise{
|
||||||
$this->callOrder[] = $event::class;
|
var_dump($class);
|
||||||
|
$this->callOrder[] = $class;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
EventPriority::NORMAL,
|
EventPriority::NORMAL,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user