remove using of Event API

This commit is contained in:
ShockedPlot7560 2024-01-21 11:25:34 +01:00
parent aaa37baf2e
commit f82c422f64
No known key found for this signature in database
GPG Key ID: 9A66EBFAA7CD3601
9 changed files with 249 additions and 219 deletions

View File

@ -24,20 +24,133 @@ declare(strict_types=1);
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 assert;
use function count;
/**
* This interface is implemented by an Event subclass if and only if it can be called asynchronously.
* This class is used to permit asynchronous event handling.
*
* 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{
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;
/**
* 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-return Promise<null>
*/
public function callAsync() : Promise;
final public function call() : Promise{
$this->promises = new ObjectSet();
if(!isset(self::$delegatesCallDepth[$class = static::class])){
self::$delegatesCallDepth[$class] = 0;
}
if(self::$delegatesCallDepth[$class] >= 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)");
}
$timings = Timings::getAsyncEventTimings($this);
$timings->startTiming();
++self::$delegatesCallDepth[$class];
try{
return $this->callAsyncDepth();
}finally{
--self::$delegatesCallDepth[$class];
$timings->stopTiming();
}
}
/**
* @phpstan-return Promise<null>
*/
private function callAsyncDepth() : Promise{
/** @phpstan-var PromiseResolver<null> $globalResolver */
$globalResolver = new PromiseResolver();
$priorities = EventPriority::ALL;
$testResolve = function () use (&$testResolve, &$priorities, $globalResolver){
if(count($priorities) === 0){
$globalResolver->resolve(null);
}else{
$this->callPriority(array_shift($priorities))->onCompletion(function() use ($testResolve) : void{
$testResolve();
}, function () use ($globalResolver) {
$globalResolver->reject();
});
}
};
$testResolve();
return $globalResolver->getPromise();
}
/**
* @phpstan-return Promise<null>
*/
private function callPriority(int $priority) : Promise{
$handlers = HandlerListManager::global()->getListFor(static::class)->getListenersByPriority($priority);
/** @phpstan-var PromiseResolver<null> $resolver */
$resolver = new PromiseResolver();
$nonConcurrentHandlers = [];
foreach($handlers as $registration){
assert($registration instanceof RegisteredAsyncListener);
if($registration->canBeCalledConcurrently()){
$result = $registration->callAsync($this);
if($result !== null) {
$this->promises->add($result);
}
}else{
$nonConcurrentHandlers[] = $registration;
}
}
$testResolve = function() use (&$nonConcurrentHandlers, &$testResolve, $resolver){
if(count($nonConcurrentHandlers) === 0){
$this->waitForPromises()->onCompletion(function() use ($resolver){
$resolver->resolve(null);
}, function() use ($resolver){
$resolver->reject();
});
}else{
$this->waitForPromises()->onCompletion(function() use (&$nonConcurrentHandlers, $testResolve){
$handler = array_shift($nonConcurrentHandlers);
assert($handler instanceof RegisteredAsyncListener);
$result = $handler->callAsync($this);
if($result !== null) {
$this->promises->add($result);
}
$testResolve();
}, function() use ($resolver) {
$resolver->reject();
});
}
};
$testResolve();
return $resolver->getPromise();
}
/**
* @phpstan-return Promise<array<int, null>>
*/
private function waitForPromises() : Promise{
$array = $this->promises->toArray();
$this->promises->clear();
return Promise::all($array);
}
}

View File

@ -1,153 +0,0 @@
<?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\timings\Timings;
use pocketmine\utils\ObjectSet;
use function array_shift;
use function count;
final class AsyncEventDelegate{
/** @phpstan-var ObjectSet<Promise<null>> $promises */
private ObjectSet $promises;
/** @var array<class-string<AsyncEvent&Event>, int> $delegatesCallDepth */
private static array $delegatesCallDepth = [];
private const MAX_EVENT_CALL_DEPTH = 50;
public function __construct(
private AsyncEvent&Event $event
){
$this->promises = new ObjectSet();
}
/**
* @phpstan-return Promise<null>
*/
public function call() : Promise{
$this->promises->clear();
if(!isset(self::$delegatesCallDepth[$class = $this->event::class])){
self::$delegatesCallDepth[$class] = 0;
}
if(self::$delegatesCallDepth[$class] >= 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)");
}
$timings = Timings::getAsyncEventTimings($this->event);
$timings->startTiming();
++self::$delegatesCallDepth[$class];
try{
return $this->callAsyncDepth();
}finally{
--self::$delegatesCallDepth[$class];
$timings->stopTiming();
}
}
/**
* @phpstan-return Promise<null>
*/
private function callAsyncDepth() : Promise{
/** @phpstan-var PromiseResolver<null> $globalResolver */
$globalResolver = new PromiseResolver();
$priorities = EventPriority::ALL;
$testResolve = function () use (&$testResolve, &$priorities, $globalResolver){
if(count($priorities) === 0){
$globalResolver->resolve(null);
}else{
$this->callPriority(array_shift($priorities))->onCompletion(function() use ($testResolve) : void{
$testResolve();
}, function () use ($globalResolver) {
$globalResolver->reject();
});
}
};
$testResolve();
return $globalResolver->getPromise();
}
/**
* @phpstan-return Promise<null>
*/
private function callPriority(int $priority) : Promise{
$handlers = HandlerListManager::global()->getListFor($this->event::class)->getListenersByPriority($priority);
/** @phpstan-var PromiseResolver<null> $resolver */
$resolver = new PromiseResolver();
$nonConcurrentHandlers = [];
foreach($handlers as $registration){
if($registration instanceof RegisteredAsyncListener){
if($registration->canBeCalledConcurrently()){
$this->promises->add($registration->callAsync($this->event));
}else{
$nonConcurrentHandlers[] = $registration;
}
}else{
$registration->callEvent($this->event);
}
}
$testResolve = function() use (&$nonConcurrentHandlers, &$testResolve, $resolver){
if(count($nonConcurrentHandlers) === 0){
$this->waitForPromises()->onCompletion(function() use ($resolver){
$resolver->resolve(null);
}, function() use ($resolver){
$resolver->reject();
});
}else{
$this->waitForPromises()->onCompletion(function() use (&$nonConcurrentHandlers, $testResolve){
$handler = array_shift($nonConcurrentHandlers);
if($handler instanceof RegisteredAsyncListener){
$this->promises->add($handler->callAsync($this->event));
}
$testResolve();
}, function() use ($resolver) {
$resolver->reject();
});
}
};
$testResolve();
return $resolver->getPromise();
}
/**
* @phpstan-return Promise<array<int, null>>
*/
private function waitForPromises() : Promise{
$array = $this->promises->toArray();
$this->promises->clear();
return Promise::all($array);
}
}

View File

@ -1,35 +0,0 @@
<?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;
trait AsyncEventTrait {
/**
* @phpstan-return Promise<null>
*/
final public function callAsync() : Promise{
return (new AsyncEventDelegate($this))->call();
}
}

View File

@ -86,7 +86,7 @@ class HandlerListManager{
*
* Calling this method also lazily initializes the $classMap inheritance tree of handler lists.
*
* @phpstan-template TEvent of Event
* @phpstan-template TEvent of Event|AsyncEvent
* @phpstan-param class-string<TEvent> $event
*
* @throws \ReflectionException

View File

@ -28,26 +28,17 @@ use pocketmine\promise\Promise;
use pocketmine\timings\TimingsHandler;
class RegisteredAsyncListener extends RegisteredListener{
/** @phpstan-var Promise<null> $returnPromise */
private Promise $returnPromise;
/**
* @phpstan-param \Closure(AsyncEvent&Event) : Promise<null> $handler
* @phpstan-param \Closure(AsyncEvent) : Promise<null> $handler
*/
public function __construct(
\Closure $handler,
protected \Closure $handler,
int $priority,
Plugin $plugin,
bool $handleCancelled,
private bool $exclusiveCall,
TimingsHandler $timings
protected TimingsHandler $timings
){
$handler = function(AsyncEvent&Event $event) use($handler) : void{
$this->returnPromise = $handler($event);
if(!$this->returnPromise instanceof Promise){
throw new \TypeError("Async event handler must return a Promise");
}
};
parent::__construct($handler, $priority, $plugin, $handleCancelled, $timings);
}
@ -55,11 +46,22 @@ class RegisteredAsyncListener extends RegisteredListener{
return !$this->exclusiveCall;
}
public function callEvent(Event $event) : void{
throw new \BadMethodCallException("Cannot call async event synchronously, use callAsync() instead");
}
/**
* @phpstan-return Promise<null>
* @phpstan-return Promise<null>|null
*/
public function callAsync(AsyncEvent&Event $event) : Promise{
$this->callEvent($event);
return $this->returnPromise;
public function callAsync(AsyncEvent $event) : ?Promise{
if($event instanceof Cancellable && $event->isCancelled() && !$this->isHandlingCancelled()){
return null;
}
$this->timings->startTiming();
try{
return ($this->handler)($event);
}finally{
$this->timings->stopTiming();
}
}
}

View File

@ -0,0 +1,92 @@
<?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\player;
use pocketmine\command\CommandSender;
use pocketmine\event\AsyncEvent;
use pocketmine\event\Cancellable;
use pocketmine\event\CancellableTrait;
use pocketmine\player\chat\ChatFormatter;
use pocketmine\player\Player;
use pocketmine\utils\Utils;
/**
* Called when a player chats something
*/
class PlayerChatAsyncEvent extends AsyncEvent implements Cancellable{
use CancellableTrait;
/**
* @param CommandSender[] $recipients
*/
public function __construct(
protected Player $player,
protected string $message,
protected array $recipients,
protected ChatFormatter $formatter
){
}
public function getMessage() : string{
return $this->message;
}
public function setMessage(string $message) : void{
$this->message = $message;
}
/**
* Changes the player that is sending the message
*/
public function setPlayer(Player $player) : void{
$this->player = $player;
}
public function getPlayer() : Player{
return $this->player;
}
public function getFormatter() : ChatFormatter{
return $this->formatter;
}
public function setFormatter(ChatFormatter $formatter) : void{
$this->formatter = $formatter;
}
/**
* @return CommandSender[]
*/
public function getRecipients() : array{
return $this->recipients;
}
/**
* @param CommandSender[] $recipients
*/
public function setRecipients(array $recipients) : void{
Utils::validateArrayValueType($recipients, function(CommandSender $_) : void{});
$this->recipients = $recipients;
}
}

View File

@ -51,6 +51,7 @@ use pocketmine\event\player\PlayerBedEnterEvent;
use pocketmine\event\player\PlayerBedLeaveEvent;
use pocketmine\event\player\PlayerBlockPickEvent;
use pocketmine\event\player\PlayerChangeSkinEvent;
use pocketmine\event\player\PlayerChatAsyncEvent;
use pocketmine\event\player\PlayerChatEvent;
use pocketmine\event\player\PlayerDeathEvent;
use pocketmine\event\player\PlayerDisplayNameChangeEvent;
@ -158,6 +159,7 @@ use function strlen;
use function strtolower;
use function substr;
use function trim;
use function var_dump;
use const M_PI;
use const M_SQRT3;
use const PHP_INT_MAX;
@ -1517,6 +1519,19 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
if(!$ev->isCancelled()){
$this->server->broadcastMessage($ev->getFormatter()->format($ev->getPlayer()->getDisplayName(), $ev->getMessage()), $ev->getRecipients());
}
$ev = new PlayerChatAsyncEvent(
$this, $messagePart,
$this->server->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_USERS),
new StandardChatFormatter()
);
$ev->call()->onCompletion(function () use ($ev) {
if(!$ev->isCancelled()){
$this->server->broadcastMessage($ev->getFormatter()->format($ev->getPlayer()->getDisplayName(), $ev->getMessage()), $ev->getRecipients());
}
}, function () {
var_dump("Failed to send chat message");
});
}
}
}

View File

@ -689,17 +689,13 @@ class PluginManager{
/**
* @param string $event Class name that extends Event and AsyncEvent
*
* @phpstan-template TEvent of Event&AsyncEvent
* @phpstan-template TEvent of AsyncEvent
* @phpstan-param class-string<TEvent> $event
* @phpstan-param \Closure(TEvent) : Promise<null> $handler
*
* @throws \ReflectionException
*/
public function registerAsyncEvent(string $event, \Closure $handler, int $priority, Plugin $plugin, bool $handleCancelled = false, bool $exclusiveCall = false) : RegisteredAsyncListener{
if(!is_subclass_of($event, Event::class)){
throw new PluginException($event . " is not an Event");
}
if(!is_subclass_of($event, AsyncEvent::class)){
throw new PluginException($event . " is not an AsyncEvent");
}

View File

@ -307,7 +307,7 @@ abstract class Timings{
return self::$events[$eventClass];
}
public static function getAsyncEventTimings(AsyncEvent&Event $event) : TimingsHandler{
public static function getAsyncEventTimings(AsyncEvent $event) : TimingsHandler{
$eventClass = get_class($event);
if(!isset(self::$asyncEvents[$eventClass])){
self::$asyncEvents[$eventClass] = new TimingsHandler(self::shortenCoreClassName($eventClass, "pocketmine\\event\\"), group: "Events");
@ -317,7 +317,7 @@ abstract class Timings{
}
/**
* @phpstan-template TEvent of Event
* @phpstan-template TEvent of Event|AsyncEvent
* @phpstan-param class-string<TEvent> $event
*/
public static function getEventHandlerTimings(string $event, string $handlerName, string $group) : TimingsHandler{