diff --git a/src/event/AsyncEvent.php b/src/event/AsyncEvent.php new file mode 100644 index 000000000..3902a96ff --- /dev/null +++ b/src/event/AsyncEvent.php @@ -0,0 +1,53 @@ + $promise + */ + public function addPromise(Promise $promise) : void; + + /** + * Be prudent, calling an event asynchronously can produce unexpected results. + * During the execution of the event, the server, the player and the event context may have changed state. + * + * @phpstan-param ObjectSet> $promiseSet + * + * @phpstan-return Promise + */ + public static function callAsync(AsyncEvent&Event $event, ObjectSet $promiseSet) : Promise; +} diff --git a/src/event/AsyncEventTrait.php b/src/event/AsyncEventTrait.php new file mode 100644 index 000000000..b907ba5a3 --- /dev/null +++ b/src/event/AsyncEventTrait.php @@ -0,0 +1,84 @@ +> */ + private ObjectSet $promises; + + /** + * @phpstan-param ObjectSet>|null $promises + */ + private function initializePromises(?ObjectSet &$promises) : void{ + $promises ??= new ObjectSet(); + $this->promises = $promises; + } + + public function addPromise(Promise $promise) : void{ + if(!isset($this->promises)){ + throw new \RuntimeException("Cannot add promises, be sure to initialize the promises set in the constructor"); + } + $this->promises->add($promise); + } + + final public static function callAsync(AsyncEvent&Event $event, ObjectSet $promiseSet) : Promise{ + $event->checkMaxDepthCall(); + + /** @phpstan-var PromiseResolver $globalResolver */ + $globalResolver = new PromiseResolver(); + + $callable = function(int $priority) use ($event, $promiseSet) : Promise{ + $handlers = HandlerListManager::global()->getListFor(static::class)->getListenersByPriority($priority); + $event->callHandlers($handlers); + + $array = $promiseSet->toArray(); + $promiseSet->clear(); + + return Promise::all($array); + }; + + $priorities = EventPriority::ALL; + $testResolve = function () use (&$testResolve, &$priorities, $callable, $globalResolver){ + if(count($priorities) === 0){ + $globalResolver->resolve(null); + }else{ + $callable(array_shift($priorities))->onCompletion(function() use ($testResolve) : void{ + $testResolve(); + }, function () use ($globalResolver) { + $globalResolver->reject(); + }); + } + }; + + $testResolve(); + + return $globalResolver->getPromise(); + } +} diff --git a/src/event/Event.php b/src/event/Event.php index 21b8ae36a..6334c16b2 100644 --- a/src/event/Event.php +++ b/src/event/Event.php @@ -47,15 +47,30 @@ abstract class Event{ * @throws \RuntimeException if event call recursion reaches the max depth limit */ public function call() : void{ + $this->checkMaxDepthCall(); + $this->callHandlers(null); + } + + /** + * @internal used by AsyncEventTrait and Event + */ + final protected function checkMaxDepthCall() : void{ if(self::$eventCallDepth >= self::MAX_EVENT_CALL_DEPTH){ //this exception will be caught by the parent event call if all else fails throw new \RuntimeException("Recursive event call detected (reached max depth of " . self::MAX_EVENT_CALL_DEPTH . " calls)"); } + } + /** + * @param RegisteredListener[]|null $handlers + * + * @internal used by AsyncEventTrait and Event + */ + final protected function callHandlers(?array $handlers) : void{ $timings = Timings::getEventTimings($this); $timings->startTiming(); - $handlers = HandlerListManager::global()->getHandlersFor(static::class); + $handlers = $handlers ?? HandlerListManager::global()->getHandlersFor(static::class); ++self::$eventCallDepth; try{ diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 070858e80..4278cc3a8 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -1205,3 +1205,8 @@ parameters: count: 1 path: ../../phpunit/scheduler/AsyncPoolTest.php + - + message: "#^Right side of && is always true\\.$#" + count: 1 + path: ../../../src/promise/Promise.php +