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\PromiseResolver;
use pocketmine\timings\Timings;
use pocketmine\utils\ObjectSet;
use function array_shift;
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.
*/
abstract class AsyncEvent{
/** @phpstan-var ObjectSet<Promise<null>> $promises */
private ObjectSet $promises;
/** @var array<class-string<AsyncEvent>, int> $delegatesCallDepth */
private static array $delegatesCallDepth = [];
private const MAX_EVENT_CALL_DEPTH = 50;
/**
* @phpstan-return Promise<self>
* @phpstan-return Promise<static>
*/
final public function call() : Promise{
$this->promises = new ObjectSet();
if(!isset(self::$delegatesCallDepth[$class = static::class])){
self::$delegatesCallDepth[$class] = 0;
}
@ -62,7 +58,12 @@ abstract class AsyncEvent{
++self::$delegatesCallDepth[$class];
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{
--self::$delegatesCallDepth[$class];
$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{
/** @phpstan-var PromiseResolver<self> $globalResolver */
$globalResolver = new PromiseResolver();
$handlerList = HandlerListManager::global()->getAsyncListFor(static::class);
$priorities = EventPriority::ALL;
$testResolve = function () use ($handlerList, &$testResolve, &$priorities, $globalResolver){
if(count($priorities) === 0){
private function asyncEachPriority(AsyncHandlerList $handlerList, array $remaining, PromiseResolver $globalResolver) : void{
while(true){
$nextPriority = array_shift($remaining);
if($nextPriority === null){
$globalResolver->resolve($this);
}else{
$this->callPriority($handlerList, array_shift($priorities))->onCompletion(function() use ($testResolve) : void{
$testResolve();
}, function () use ($globalResolver) {
$globalResolver->reject();
});
break;
}
};
$testResolve();
return $globalResolver->getPromise();
$promise = $this->callPriority($handlerList, $nextPriority);
if($promise !== null){
$promise->onCompletion(
onSuccess: fn() => $this->asyncEachPriority($handlerList, $remaining, $globalResolver),
onFailure: $globalResolver->reject(...)
);
break;
}
}
}
/**
* @phpstan-return Promise<null>
*/
private function callPriority(AsyncHandlerList $handlerList, int $priority) : Promise{
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) {
$this->promises->add($result);
$concurrentPromises[] = $result;
}
}else{
$nonConcurrentHandlers[] = $registration;
}
}
$testResolve = function() use (&$nonConcurrentHandlers, &$testResolve, $resolver){
$this->waitForPromises()->onCompletion(function() use (&$nonConcurrentHandlers, $testResolve, $resolver){
$handler = array_shift($nonConcurrentHandlers);
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();
Promise::all($concurrentPromises)->onCompletion(
onSuccess: fn() => $this->processExclusiveHandlers($nonConcurrentHandlers, $resolver),
onFailure: $resolver->reject(...)
);
return $resolver->getPromise();
}
/**
* @phpstan-return Promise<array<int, null>>
* @param AsyncRegisteredListener[] $handlers
* @phpstan-param PromiseResolver<null> $resolver
*/
private function waitForPromises() : Promise{
$array = $this->promises->toArray();
$this->promises->clear();
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;
}
return Promise::all($array);
//this handler didn't return a promise - continue directly to the next one
}
}
}