diff --git a/src/event/AsyncEvent.php b/src/event/AsyncEvent.php index 14fc208b2..28b9a6d56 100644 --- a/src/event/AsyncEvent.php +++ b/src/event/AsyncEvent.php @@ -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> $promises */ - private ObjectSet $promises; /** @var array, int> $delegatesCallDepth */ private static array $delegatesCallDepth = []; private const MAX_EVENT_CALL_DEPTH = 50; /** - * @phpstan-return Promise + * @phpstan-return Promise */ 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 $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 + * TODO: this should use EventPriority constants for the list type but it's inconvenient with the current design + * @phpstan-param list $remaining + * @phpstan-param PromiseResolver $globalResolver */ - private function callAsyncDepth() : Promise{ - /** @phpstan-var PromiseResolver $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 */ - 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 $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> + * @param AsyncRegisteredListener[] $handlers + * @phpstan-param PromiseResolver $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 + } } }