mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-22 00:33:59 +00:00
remove using of Event API
This commit is contained in:
parent
aaa37baf2e
commit
f82c422f64
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
92
src/event/player/PlayerChatAsyncEvent.php
Normal file
92
src/event/player/PlayerChatAsyncEvent.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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{
|
||||
|
Loading…
x
Reference in New Issue
Block a user