mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-25 14:57:08 +00:00
events: asynchandler is defined by their return type and event type
If the event is async and the handler return Promise, it will be handle as an async event. However, if the event is async and the handler return smth different than Promise, it will be handle synchronously. An async handler can specify if it wishes to be called with no concurrent handlers with the tag @noConcurrentCall
This commit is contained in:
parent
a84fc2b901
commit
7a4b9a0367
@ -24,7 +24,6 @@ declare(strict_types=1);
|
|||||||
namespace pocketmine\event;
|
namespace pocketmine\event;
|
||||||
|
|
||||||
use pocketmine\promise\Promise;
|
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.
|
* This interface is implemented by an Event subclass if and only if it can be called asynchronously.
|
||||||
@ -34,20 +33,11 @@ use pocketmine\utils\ObjectSet;
|
|||||||
* When all the promises of a priority level have been resolved, the next priority level is called.
|
* When all the promises of a priority level have been resolved, the next priority level is called.
|
||||||
*/
|
*/
|
||||||
interface AsyncEvent{
|
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.
|
* 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.
|
* 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>
|
* @phpstan-return Promise<null>
|
||||||
*/
|
*/
|
||||||
public static function callAsync(AsyncEvent&Event $event, ObjectSet $promiseSet) : Promise;
|
public function callAsync() : Promise;
|
||||||
}
|
}
|
||||||
|
131
src/event/AsyncEventDelegate.php
Normal file
131
src/event/AsyncEventDelegate.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
final class AsyncEventDelegate extends Event{
|
||||||
|
/** @phpstan-var ObjectSet<Promise<null>> $promises */
|
||||||
|
private ObjectSet $promises;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private AsyncEvent&Event $event
|
||||||
|
){
|
||||||
|
$this->promises = new ObjectSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @phpstan-return Promise<null>
|
||||||
|
*/
|
||||||
|
public function callAsync() : Promise {
|
||||||
|
$this->promises->clear();
|
||||||
|
return $this->callDepth($this->callAsyncDepth(...));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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(""); // TODO: see #6110
|
||||||
|
}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->canBeCallConcurrently()){
|
||||||
|
$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(""); // TODO: see #6110
|
||||||
|
}, 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<null>
|
||||||
|
*/
|
||||||
|
private function waitForPromises() : Promise {
|
||||||
|
$array = $this->promises->toArray();
|
||||||
|
$this->promises->clear();
|
||||||
|
|
||||||
|
return Promise::all($array);
|
||||||
|
}
|
||||||
|
}
|
@ -24,61 +24,17 @@ declare(strict_types=1);
|
|||||||
namespace pocketmine\event;
|
namespace pocketmine\event;
|
||||||
|
|
||||||
use pocketmine\promise\Promise;
|
use pocketmine\promise\Promise;
|
||||||
use pocketmine\promise\PromiseResolver;
|
|
||||||
use pocketmine\utils\ObjectSet;
|
|
||||||
use function array_shift;
|
|
||||||
use function count;
|
|
||||||
|
|
||||||
trait AsyncEventTrait {
|
trait AsyncEventTrait {
|
||||||
/** @phpstan-var ObjectSet<Promise<null>> */
|
private AsyncEventDelegate $delegate;
|
||||||
private ObjectSet $promises;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @phpstan-param ObjectSet<Promise<null>>|null $promises
|
* @phpstan-return Promise<null>
|
||||||
*/
|
*/
|
||||||
private function initializePromises(?ObjectSet &$promises) : void{
|
final public function callAsync() : Promise{
|
||||||
$promises ??= new ObjectSet();
|
if(!isset($this->delegate)){
|
||||||
$this->promises = $promises;
|
$this->delegate = new AsyncEventDelegate($this);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
return $this->delegate->callAsync();
|
||||||
}
|
|
||||||
|
|
||||||
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,36 +47,27 @@ abstract class Event{
|
|||||||
* @throws \RuntimeException if event call recursion reaches the max depth limit
|
* @throws \RuntimeException if event call recursion reaches the max depth limit
|
||||||
*/
|
*/
|
||||||
public function call() : void{
|
public function call() : void{
|
||||||
$this->checkMaxDepthCall();
|
$this->callDepth(function() {
|
||||||
$this->callHandlers(null);
|
$handlers = HandlerListManager::global()->getHandlersFor(static::class);
|
||||||
|
|
||||||
|
foreach($handlers as $registration){
|
||||||
|
$registration->callEvent($this);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
final protected function callDepth(\Closure $closure) : mixed{
|
||||||
* @internal used by AsyncEventTrait and Event
|
|
||||||
*/
|
|
||||||
final protected function checkMaxDepthCall() : void{
|
|
||||||
if(self::$eventCallDepth >= self::MAX_EVENT_CALL_DEPTH){
|
if(self::$eventCallDepth >= self::MAX_EVENT_CALL_DEPTH){
|
||||||
//this exception will be caught by the parent event call if all else fails
|
//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)");
|
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 = Timings::getEventTimings($this);
|
||||||
$timings->startTiming();
|
$timings->startTiming();
|
||||||
|
|
||||||
$handlers = $handlers ?? HandlerListManager::global()->getHandlersFor(static::class);
|
|
||||||
|
|
||||||
++self::$eventCallDepth;
|
++self::$eventCallDepth;
|
||||||
try{
|
try{
|
||||||
foreach($handlers as $registration){
|
return $closure();
|
||||||
$registration->callEvent($this);
|
|
||||||
}
|
|
||||||
}finally{
|
}finally{
|
||||||
--self::$eventCallDepth;
|
--self::$eventCallDepth;
|
||||||
$timings->stopTiming();
|
$timings->stopTiming();
|
||||||
|
@ -24,9 +24,11 @@ declare(strict_types=1);
|
|||||||
namespace pocketmine\event;
|
namespace pocketmine\event;
|
||||||
|
|
||||||
use pocketmine\plugin\Plugin;
|
use pocketmine\plugin\Plugin;
|
||||||
|
use function array_filter;
|
||||||
use function array_merge;
|
use function array_merge;
|
||||||
use function krsort;
|
use function krsort;
|
||||||
use function spl_object_id;
|
use function spl_object_id;
|
||||||
|
use function usort;
|
||||||
use const SORT_NUMERIC;
|
use const SORT_NUMERIC;
|
||||||
|
|
||||||
class HandlerList{
|
class HandlerList{
|
||||||
@ -128,11 +130,26 @@ class HandlerList{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$listenersByPriority = [];
|
$listenersByPriority = [];
|
||||||
|
$asyncListenersByPriority = [];
|
||||||
foreach($handlerLists as $currentList){
|
foreach($handlerLists as $currentList){
|
||||||
foreach($currentList->handlerSlots as $priority => $listeners){
|
foreach($currentList->handlerSlots as $priority => $listeners){
|
||||||
$listenersByPriority[$priority] = array_merge($listenersByPriority[$priority] ?? [], $listeners);
|
$syncListeners = array_filter($listeners, static function(RegisteredListener $listener) : bool{ return !($listener instanceof RegisteredAsyncListener); });
|
||||||
|
$listenersByPriority[$priority] = array_merge($listenersByPriority[$priority] ?? [], $syncListeners);
|
||||||
|
|
||||||
|
$asyncListeners = array_filter($listeners, static function(RegisteredListener $listener) : bool{ return $listener instanceof RegisteredAsyncListener; });
|
||||||
|
$asyncListenersByPriority[$priority] = array_merge($asyncListenersByPriority[$priority] ?? [], $asyncListeners);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
foreach($asyncListenersByPriority as $priority => $asyncListeners){
|
||||||
|
usort($asyncListeners, static function(RegisteredAsyncListener $a, RegisteredAsyncListener $b) : int{
|
||||||
|
if($a->canBeCallConcurrently()){
|
||||||
|
return $b->canBeCallConcurrently() ? 0 : -1;
|
||||||
|
}else{
|
||||||
|
return $b->canBeCallConcurrently() ? -1 : 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$listenersByPriority[$priority] = array_merge($listenersByPriority[$priority] ?? [], $asyncListeners);
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: why on earth do the priorities have higher values for lower priority?
|
//TODO: why on earth do the priorities have higher values for lower priority?
|
||||||
krsort($listenersByPriority, SORT_NUMERIC);
|
krsort($listenersByPriority, SORT_NUMERIC);
|
||||||
|
@ -31,4 +31,5 @@ final class ListenerMethodTags{
|
|||||||
public const HANDLE_CANCELLED = "handleCancelled";
|
public const HANDLE_CANCELLED = "handleCancelled";
|
||||||
public const NOT_HANDLER = "notHandler";
|
public const NOT_HANDLER = "notHandler";
|
||||||
public const PRIORITY = "priority";
|
public const PRIORITY = "priority";
|
||||||
|
public const NO_CONCURRENT_CALL = "noConcurrentCall";
|
||||||
}
|
}
|
||||||
|
58
src/event/RegisteredAsyncListener.php
Normal file
58
src/event/RegisteredAsyncListener.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?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\plugin\Plugin;
|
||||||
|
use pocketmine\promise\Promise;
|
||||||
|
use pocketmine\timings\TimingsHandler;
|
||||||
|
|
||||||
|
class RegisteredAsyncListener extends RegisteredListener{
|
||||||
|
private Promise $returnPromise;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
\Closure $handler,
|
||||||
|
int $priority,
|
||||||
|
Plugin $plugin,
|
||||||
|
bool $handleCancelled,
|
||||||
|
private bool $noConcurrentCall,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canBeCallConcurrently() : bool{
|
||||||
|
return !$this->noConcurrentCall;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function callAsync(AsyncEvent&Event $event) : Promise{
|
||||||
|
$this->callEvent($event);
|
||||||
|
return $this->returnPromise;
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,8 @@ declare(strict_types=1);
|
|||||||
namespace pocketmine\event\player;
|
namespace pocketmine\event\player;
|
||||||
|
|
||||||
use pocketmine\command\CommandSender;
|
use pocketmine\command\CommandSender;
|
||||||
|
use pocketmine\event\AsyncEvent;
|
||||||
|
use pocketmine\event\AsyncEventTrait;
|
||||||
use pocketmine\event\Cancellable;
|
use pocketmine\event\Cancellable;
|
||||||
use pocketmine\event\CancellableTrait;
|
use pocketmine\event\CancellableTrait;
|
||||||
use pocketmine\player\chat\ChatFormatter;
|
use pocketmine\player\chat\ChatFormatter;
|
||||||
@ -33,8 +35,9 @@ use pocketmine\utils\Utils;
|
|||||||
/**
|
/**
|
||||||
* Called when a player chats something
|
* Called when a player chats something
|
||||||
*/
|
*/
|
||||||
class PlayerChatEvent extends PlayerEvent implements Cancellable{
|
class PlayerChatEvent extends PlayerEvent implements Cancellable, AsyncEvent{
|
||||||
use CancellableTrait;
|
use CancellableTrait;
|
||||||
|
use AsyncEventTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CommandSender[] $recipients
|
* @param CommandSender[] $recipients
|
||||||
|
@ -1513,10 +1513,14 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
Timings::$playerCommand->stopTiming();
|
Timings::$playerCommand->stopTiming();
|
||||||
}else{
|
}else{
|
||||||
$ev = new PlayerChatEvent($this, $messagePart, $this->server->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_USERS), new StandardChatFormatter());
|
$ev = new PlayerChatEvent($this, $messagePart, $this->server->getBroadcastChannelSubscribers(Server::BROADCAST_CHANNEL_USERS), new StandardChatFormatter());
|
||||||
$ev->call();
|
$ev->callAsync()
|
||||||
if(!$ev->isCancelled()){
|
->onCompletion(function() use ($ev) {
|
||||||
$this->server->broadcastMessage($ev->getFormatter()->format($ev->getPlayer()->getDisplayName(), $ev->getMessage()), $ev->getRecipients());
|
if(!$ev->isCancelled()){
|
||||||
}
|
$this->server->broadcastMessage($ev->getFormatter()->format($ev->getPlayer()->getDisplayName(), $ev->getMessage()), $ev->getRecipients());
|
||||||
|
}
|
||||||
|
}, function (){
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\plugin;
|
namespace pocketmine\plugin;
|
||||||
|
|
||||||
|
use pocketmine\event\AsyncEvent;
|
||||||
use pocketmine\event\Cancellable;
|
use pocketmine\event\Cancellable;
|
||||||
use pocketmine\event\Event;
|
use pocketmine\event\Event;
|
||||||
use pocketmine\event\EventPriority;
|
use pocketmine\event\EventPriority;
|
||||||
@ -31,11 +32,13 @@ use pocketmine\event\Listener;
|
|||||||
use pocketmine\event\ListenerMethodTags;
|
use pocketmine\event\ListenerMethodTags;
|
||||||
use pocketmine\event\plugin\PluginDisableEvent;
|
use pocketmine\event\plugin\PluginDisableEvent;
|
||||||
use pocketmine\event\plugin\PluginEnableEvent;
|
use pocketmine\event\plugin\PluginEnableEvent;
|
||||||
|
use pocketmine\event\RegisteredAsyncListener;
|
||||||
use pocketmine\event\RegisteredListener;
|
use pocketmine\event\RegisteredListener;
|
||||||
use pocketmine\lang\KnownTranslationFactory;
|
use pocketmine\lang\KnownTranslationFactory;
|
||||||
use pocketmine\permission\DefaultPermissions;
|
use pocketmine\permission\DefaultPermissions;
|
||||||
use pocketmine\permission\PermissionManager;
|
use pocketmine\permission\PermissionManager;
|
||||||
use pocketmine\permission\PermissionParser;
|
use pocketmine\permission\PermissionParser;
|
||||||
|
use pocketmine\promise\Promise;
|
||||||
use pocketmine\Server;
|
use pocketmine\Server;
|
||||||
use pocketmine\timings\Timings;
|
use pocketmine\timings\Timings;
|
||||||
use pocketmine\utils\AssumptionFailedError;
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
@ -626,8 +629,29 @@ class PluginManager{
|
|||||||
throw new PluginException("Event handler " . Utils::getNiceClosureName($handlerClosure) . "() declares invalid @" . ListenerMethodTags::HANDLE_CANCELLED . " value \"" . $tags[ListenerMethodTags::HANDLE_CANCELLED] . "\"");
|
throw new PluginException("Event handler " . Utils::getNiceClosureName($handlerClosure) . "() declares invalid @" . ListenerMethodTags::HANDLE_CANCELLED . " value \"" . $tags[ListenerMethodTags::HANDLE_CANCELLED] . "\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$noConcurrentCall = false;
|
||||||
|
if(isset($tags[ListenerMethodTags::NO_CONCURRENT_CALL])){
|
||||||
|
if(!is_a($eventClass, AsyncEvent::class, true)){
|
||||||
|
throw new PluginException(sprintf(
|
||||||
|
"Event handler %s() declares @%s for non-async event of type %s",
|
||||||
|
Utils::getNiceClosureName($handlerClosure),
|
||||||
|
ListenerMethodTags::NO_CONCURRENT_CALL,
|
||||||
|
$eventClass
|
||||||
|
));
|
||||||
|
}
|
||||||
|
switch(strtolower($tags[ListenerMethodTags::NO_CONCURRENT_CALL])){
|
||||||
|
case "true":
|
||||||
|
case "":
|
||||||
|
$noConcurrentCall = true;
|
||||||
|
break;
|
||||||
|
case "false":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new PluginException("Event handler " . Utils::getNiceClosureName($handlerClosure) . "() declares invalid @" . ListenerMethodTags::NO_CONCURRENT_CALL . " value \"" . $tags[ListenerMethodTags::NO_CONCURRENT_CALL] . "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->registerEvent($eventClass, $handlerClosure, $priority, $plugin, $handleCancelled);
|
$this->registerEvent($eventClass, $handlerClosure, $priority, $plugin, $handleCancelled, $noConcurrentCall);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,11 +660,11 @@ class PluginManager{
|
|||||||
*
|
*
|
||||||
* @phpstan-template TEvent of Event
|
* @phpstan-template TEvent of Event
|
||||||
* @phpstan-param class-string<TEvent> $event
|
* @phpstan-param class-string<TEvent> $event
|
||||||
* @phpstan-param \Closure(TEvent) : void $handler
|
* @phpstan-param (\Closure(TEvent) : void)|(\Closure(AsyncEvent&TEvent) : Promise<null>) $handler
|
||||||
*
|
*
|
||||||
* @throws \ReflectionException
|
* @throws \ReflectionException
|
||||||
*/
|
*/
|
||||||
public function registerEvent(string $event, \Closure $handler, int $priority, Plugin $plugin, bool $handleCancelled = false) : RegisteredListener{
|
public function registerEvent(string $event, \Closure $handler, int $priority, Plugin $plugin, bool $handleCancelled = false, bool $noConcurrentCall = false) : RegisteredListener{
|
||||||
if(!is_subclass_of($event, Event::class)){
|
if(!is_subclass_of($event, Event::class)){
|
||||||
throw new PluginException($event . " is not an Event");
|
throw new PluginException($event . " is not an Event");
|
||||||
}
|
}
|
||||||
@ -653,8 +677,25 @@ class PluginManager{
|
|||||||
|
|
||||||
$timings = Timings::getEventHandlerTimings($event, $handlerName, $plugin->getDescription()->getFullName());
|
$timings = Timings::getEventHandlerTimings($event, $handlerName, $plugin->getDescription()->getFullName());
|
||||||
|
|
||||||
$registeredListener = new RegisteredListener($handler, $priority, $plugin, $handleCancelled, $timings);
|
if(is_subclass_of($event, AsyncEvent::class) && $this->canHandleAsyncEvent($handler)){
|
||||||
|
$registeredListener = new RegisteredAsyncListener($handler, $priority, $plugin, $handleCancelled, $noConcurrentCall, $timings);
|
||||||
|
}else{
|
||||||
|
$registeredListener = new RegisteredListener($handler, $priority, $plugin, $handleCancelled, $timings);
|
||||||
|
}
|
||||||
HandlerListManager::global()->getListFor($event)->register($registeredListener);
|
HandlerListManager::global()->getListFor($event)->register($registeredListener);
|
||||||
return $registeredListener;
|
return $registeredListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given handler return type is async-compatible (equal to Promise)
|
||||||
|
*
|
||||||
|
* @phpstan-template TEvent of Event
|
||||||
|
* @phpstan-param (\Closure(TEvent) : void)|(\Closure(AsyncEvent&TEvent) : Promise<null>) $handler
|
||||||
|
*/
|
||||||
|
private function canHandleAsyncEvent(\Closure $handler) : bool {
|
||||||
|
$reflection = new \ReflectionFunction($handler);
|
||||||
|
$return = $reflection->getReturnType();
|
||||||
|
|
||||||
|
return $return instanceof \ReflectionNamedType && $return->getName() === Promise::class;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user