mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-21 08:17:34 +00:00
introduce AsyncEvent and ::callAsync()
An asynchronous event is one that allows the addition of promises to be resolved before being completed. This implementation integrates priority levels, allowing you to wait for all promises added to one level before moving on to the next. This is made possible by separating the event call logic into several functions, which can then be integrated into AsyncEventTrait::callAsync()
This commit is contained in:
parent
5fe57a8f6f
commit
a84fc2b901
53
src/event/AsyncEvent.php
Normal file
53
src/event/AsyncEvent.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event;
|
||||
|
||||
use pocketmine\promise\Promise;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
|
||||
/**
|
||||
* This interface is implemented by an Event subclass if and only if it can be called asynchronously.
|
||||
*
|
||||
* Used with {@see AsyncEventTrait} to provide a way to call an event asynchronously.
|
||||
* When an event is called asynchronously, the event handlers are called by priority level.
|
||||
* When all the promises of a priority level have been resolved, the next priority level is called.
|
||||
*/
|
||||
interface AsyncEvent{
|
||||
/**
|
||||
* Add a promise to the set of promises that will be awaited before the next priority level is called.
|
||||
*
|
||||
* @phpstan-param Promise<null> $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<Promise<null>> $promiseSet
|
||||
*
|
||||
* @phpstan-return Promise<null>
|
||||
*/
|
||||
public static function callAsync(AsyncEvent&Event $event, ObjectSet $promiseSet) : Promise;
|
||||
}
|
84
src/event/AsyncEventTrait.php
Normal file
84
src/event/AsyncEventTrait.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event;
|
||||
|
||||
use pocketmine\promise\Promise;
|
||||
use pocketmine\promise\PromiseResolver;
|
||||
use pocketmine\utils\ObjectSet;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
|
||||
trait AsyncEventTrait {
|
||||
/** @phpstan-var ObjectSet<Promise<null>> */
|
||||
private ObjectSet $promises;
|
||||
|
||||
/**
|
||||
* @phpstan-param ObjectSet<Promise<null>>|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<null> $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();
|
||||
}
|
||||
}
|
@ -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{
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user