AsyncEvent: make the code easier to make sense of

This commit is contained in:
Dylan K. Taylor 2024-11-13 18:49:15 +00:00
parent cb2fadeb26
commit 409066c8f5
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D

View File

@ -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 pocketmine\utils\ObjectSet;
use function array_shift; use function array_shift;
use function count; use function count;
@ -37,17 +36,14 @@ use function count;
* When all the promises of a priority level have been resolved, the next priority level is called. * When all the promises of a priority level have been resolved, the next priority level is called.
*/ */
abstract class AsyncEvent{ abstract class AsyncEvent{
/** @phpstan-var ObjectSet<Promise<null>> $promises */
private ObjectSet $promises;
/** @var array<class-string<AsyncEvent>, int> $delegatesCallDepth */ /** @var array<class-string<AsyncEvent>, int> $delegatesCallDepth */
private static array $delegatesCallDepth = []; private static array $delegatesCallDepth = [];
private const MAX_EVENT_CALL_DEPTH = 50; private const MAX_EVENT_CALL_DEPTH = 50;
/** /**
* @phpstan-return Promise<self> * @phpstan-return Promise<static>
*/ */
final public function call() : Promise{ final public function call() : Promise{
$this->promises = new ObjectSet();
if(!isset(self::$delegatesCallDepth[$class = static::class])){ if(!isset(self::$delegatesCallDepth[$class = static::class])){
self::$delegatesCallDepth[$class] = 0; self::$delegatesCallDepth[$class] = 0;
} }
@ -62,7 +58,12 @@ abstract class AsyncEvent{
++self::$delegatesCallDepth[$class]; ++self::$delegatesCallDepth[$class];
try{ try{
return $this->callAsyncDepth(); /** @phpstan-var PromiseResolver<static> $globalResolver */
$globalResolver = new PromiseResolver();
$this->asyncEachPriority(HandlerListManager::global()->getAsyncListFor(static::class), EventPriority::ALL, $globalResolver);
return $globalResolver->getPromise();
}finally{ }finally{
--self::$delegatesCallDepth[$class]; --self::$delegatesCallDepth[$class];
$timings->stopTiming(); $timings->stopTiming();
@ -70,81 +71,84 @@ abstract class AsyncEvent{
} }
/** /**
* @phpstan-return Promise<self> * TODO: this should use EventPriority constants for the list type but it's inconvenient with the current design
* @phpstan-param list<int> $remaining
* @phpstan-param PromiseResolver<static> $globalResolver
*/ */
private function callAsyncDepth() : Promise{ private function asyncEachPriority(AsyncHandlerList $handlerList, array $remaining, PromiseResolver $globalResolver) : void{
/** @phpstan-var PromiseResolver<self> $globalResolver */ while(true){
$globalResolver = new PromiseResolver(); $nextPriority = array_shift($remaining);
if($nextPriority === null){
$handlerList = HandlerListManager::global()->getAsyncListFor(static::class);
$priorities = EventPriority::ALL;
$testResolve = function () use ($handlerList, &$testResolve, &$priorities, $globalResolver){
if(count($priorities) === 0){
$globalResolver->resolve($this); $globalResolver->resolve($this);
}else{ break;
$this->callPriority($handlerList, array_shift($priorities))->onCompletion(function() use ($testResolve) : void{
$testResolve();
}, function () use ($globalResolver) {
$globalResolver->reject();
});
} }
};
$testResolve(); $promise = $this->callPriority($handlerList, $nextPriority);
if($promise !== null){
return $globalResolver->getPromise(); $promise->onCompletion(
onSuccess: fn() => $this->asyncEachPriority($handlerList, $remaining, $globalResolver),
onFailure: $globalResolver->reject(...)
);
break;
}
}
} }
/** /**
* @phpstan-return Promise<null> * @phpstan-return Promise<null>
*/ */
private function callPriority(AsyncHandlerList $handlerList, int $priority) : Promise{ private function callPriority(AsyncHandlerList $handlerList, int $priority) : ?Promise{
$handlers = $handlerList->getListenersByPriority($priority); $handlers = $handlerList->getListenersByPriority($priority);
if(count($handlers) === 0){
return null;
}
/** @phpstan-var PromiseResolver<null> $resolver */ /** @phpstan-var PromiseResolver<null> $resolver */
$resolver = new PromiseResolver(); $resolver = new PromiseResolver();
$concurrentPromises = [];
$nonConcurrentHandlers = []; $nonConcurrentHandlers = [];
foreach($handlers as $registration){ foreach($handlers as $registration){
if($registration->canBeCalledConcurrently()){ if($registration->canBeCalledConcurrently()){
$result = $registration->callAsync($this); $result = $registration->callAsync($this);
if($result !== null) { if($result !== null) {
$this->promises->add($result); $concurrentPromises[] = $result;
} }
}else{ }else{
$nonConcurrentHandlers[] = $registration; $nonConcurrentHandlers[] = $registration;
} }
} }
$testResolve = function() use (&$nonConcurrentHandlers, &$testResolve, $resolver){ Promise::all($concurrentPromises)->onCompletion(
$this->waitForPromises()->onCompletion(function() use (&$nonConcurrentHandlers, $testResolve, $resolver){ onSuccess: fn() => $this->processExclusiveHandlers($nonConcurrentHandlers, $resolver),
$handler = array_shift($nonConcurrentHandlers); onFailure: $resolver->reject(...)
if($handler !== null){ );
$result = $handler->callAsync($this);
if($result !== null) {
$this->promises->add($result);
}
$testResolve();
}else{
$resolver->resolve(null);
}
}, function() use ($resolver) {
$resolver->reject();
});
};
$testResolve();
return $resolver->getPromise(); return $resolver->getPromise();
} }
/** /**
* @phpstan-return Promise<array<int, null>> * @param AsyncRegisteredListener[] $handlers
* @phpstan-param PromiseResolver<null> $resolver
*/ */
private function waitForPromises() : Promise{ private function processExclusiveHandlers(array $handlers, PromiseResolver $resolver) : void{
$array = $this->promises->toArray(); while(true){
$this->promises->clear(); $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;
}
return Promise::all($array); //this handler didn't return a promise - continue directly to the next one
}
} }
} }