diff --git a/src/Server.php b/src/Server.php index b38111dd6..f3f1e4e6a 100644 --- a/src/Server.php +++ b/src/Server.php @@ -70,7 +70,6 @@ use pocketmine\permission\DefaultPermissions; use pocketmine\player\GameMode; use pocketmine\player\OfflinePlayer; use pocketmine\player\Player; -use pocketmine\player\PlayerCreationPromise; use pocketmine\player\PlayerInfo; use pocketmine\plugin\PharPluginLoader; use pocketmine\plugin\Plugin; @@ -93,6 +92,7 @@ use pocketmine\utils\Filesystem; use pocketmine\utils\Internet; use pocketmine\utils\MainLogger; use pocketmine\utils\Process; +use pocketmine\utils\Promise; use pocketmine\utils\Terminal; use pocketmine\utils\TextFormat; use pocketmine\utils\Utils; @@ -580,7 +580,10 @@ class Server{ } } - public function createPlayer(NetworkSession $session, PlayerInfo $playerInfo, bool $authenticated, ?CompoundTag $offlinePlayerData) : PlayerCreationPromise{ + /** + * @phpstan-return Promise + */ + public function createPlayer(NetworkSession $session, PlayerInfo $playerInfo, bool $authenticated, ?CompoundTag $offlinePlayerData) : Promise{ $ev = new PlayerCreationEvent($session); $ev->call(); $class = $ev->getPlayerClass(); @@ -596,7 +599,7 @@ class Server{ $playerPos = null; $spawn = $world->getSpawnLocation(); } - $playerPromise = new PlayerCreationPromise(); + $playerPromise = new Promise(); $world->requestChunkPopulation($spawn->getFloorX() >> 4, $spawn->getFloorZ() >> 4, null)->onCompletion( function() use ($playerPromise, $class, $session, $playerInfo, $authenticated, $world, $playerPos, $spawn, $offlinePlayerData) : void{ if(!$session->isConnected()){ diff --git a/src/player/PlayerCreationPromise.php b/src/utils/Promise.php similarity index 71% rename from src/player/PlayerCreationPromise.php rename to src/utils/Promise.php index 1d43b7677..4ccfc4bc5 100644 --- a/src/player/PlayerCreationPromise.php +++ b/src/utils/Promise.php @@ -21,14 +21,17 @@ declare(strict_types=1); -namespace pocketmine\player; +namespace pocketmine\utils; use function spl_object_id; -final class PlayerCreationPromise{ +/** + * @phpstan-template TValue + */ +final class Promise{ /** * @var \Closure[] - * @phpstan-var array + * @phpstan-var array */ private array $onSuccess = []; @@ -39,10 +42,15 @@ final class PlayerCreationPromise{ private array $onFailure = []; private bool $resolved = false; - private ?Player $result = null; /** - * @phpstan-param \Closure(Player) : void $onSuccess + * @var mixed + * @phpstan-var TValue|null + */ + private $result = null; + + /** + * @phpstan-param \Closure(TValue) : void $onSuccess * @phpstan-param \Closure() : void $onFailure */ public function onCompletion(\Closure $onSuccess, \Closure $onFailure) : void{ @@ -54,17 +62,27 @@ final class PlayerCreationPromise{ } } - public function resolve(Player $player) : void{ + /** + * @param mixed $value + * @phpstan-param TValue $value + */ + public function resolve($value) : void{ + if($this->resolved){ + throw new \InvalidStateException("Promise has already been resolved/rejected"); + } $this->resolved = true; - $this->result = $player; + $this->result = $value; foreach($this->onSuccess as $c){ - $c($player); + $c($value); } $this->onSuccess = []; $this->onFailure = []; } public function reject() : void{ + if($this->resolved){ + throw new \InvalidStateException("Promise has already been resolved/rejected"); + } $this->resolved = true; foreach($this->onFailure as $c){ $c(); diff --git a/src/world/ChunkPopulationPromise.php b/src/world/ChunkPopulationPromise.php deleted file mode 100644 index 4feee5f75..000000000 --- a/src/world/ChunkPopulationPromise.php +++ /dev/null @@ -1,76 +0,0 @@ - - */ - private array $onSuccess = []; - /** - * @var \Closure[] - * @phpstan-var array - */ - private array $onFailure = []; - - private ?bool $success = null; - - /** - * @phpstan-param \Closure() : void $onSuccess - * @phpstan-param \Closure() : void $onFailure - */ - public function onCompletion(\Closure $onSuccess, \Closure $onFailure) : void{ - if($this->success !== null){ - $this->success ? $onSuccess() : $onFailure(); - }else{ - $this->onSuccess[spl_object_id($onSuccess)] = $onSuccess; - $this->onFailure[spl_object_id($onFailure)] = $onFailure; - } - } - - public function resolve() : void{ - $this->success = true; - foreach($this->onSuccess as $callback){ - $callback(); - } - $this->onSuccess = []; - $this->onFailure = []; - } - - public function reject() : void{ - $this->success = false; - foreach($this->onFailure as $callback){ - $callback(); - } - $this->onSuccess = []; - $this->onFailure = []; - } - - public function isCompleted() : bool{ - return $this->success !== null; - } -} diff --git a/src/world/World.php b/src/world/World.php index 213b769d1..b27e30c8f 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -68,6 +68,7 @@ use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; +use pocketmine\utils\Promise; use pocketmine\utils\ReversePriorityQueue; use pocketmine\world\biome\Biome; use pocketmine\world\biome\BiomeRegistry; @@ -240,8 +241,8 @@ class World implements ChunkManager{ /** @var int */ private $maxConcurrentChunkPopulationTasks = 2; /** - * @var ChunkPopulationPromise[] chunkHash => promise - * @phpstan-var array + * @var Promise[] chunkHash => promise + * @phpstan-var array> */ private array $chunkPopulationRequestMap = []; /** @@ -2110,7 +2111,7 @@ class World implements ChunkManager{ } } unset($this->activeChunkPopulationTasks[$index]); - $this->chunkPopulationRequestMap[$index]->resolve(); + $this->chunkPopulationRequestMap[$index]->resolve($chunk); unset($this->chunkPopulationRequestMap[$index]); $this->drainPopulationRequestQueue(); @@ -2698,10 +2699,13 @@ class World implements ChunkManager{ } } - private function enqueuePopulationRequest(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : ChunkPopulationPromise{ + /** + * @phpstan-return Promise + */ + private function enqueuePopulationRequest(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise{ $chunkHash = World::chunkHash($chunkX, $chunkZ); $this->chunkPopulationRequestQueue->enqueue($chunkHash); - $promise = $this->chunkPopulationRequestMap[$chunkHash] = new ChunkPopulationPromise(); + $promise = $this->chunkPopulationRequestMap[$chunkHash] = new Promise(); if($associatedChunkLoader === null){ $temporaryLoader = new class implements ChunkLoader{}; $this->registerChunkLoader($temporaryLoader, $chunkX, $chunkZ); @@ -2721,8 +2725,10 @@ class World implements ChunkManager{ * A ChunkLoader can be associated with the generation request to ensure that the generation request is cancelled if * no loaders are attached to the target chunk. If no loader is provided, one will be assigned (and automatically * removed when the generation request completes). + * + * @phpstan-return Promise */ - public function requestChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : ChunkPopulationPromise{ + public function requestChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader) : Promise{ $chunkHash = World::chunkHash($chunkX, $chunkZ); $promise = $this->chunkPopulationRequestMap[$chunkHash] ?? null; if($promise !== null && isset($this->activeChunkPopulationTasks[$chunkHash])){ @@ -2743,8 +2749,10 @@ class World implements ChunkManager{ * * If the chunk is currently locked (for example due to another chunk using it for async generation), the request * will be queued and executed at the earliest opportunity. + * + * @phpstan-return Promise */ - public function orderChunkPopulation(int $x, int $z, ?ChunkLoader $associatedChunkLoader) : ChunkPopulationPromise{ + public function orderChunkPopulation(int $x, int $z, ?ChunkLoader $associatedChunkLoader) : Promise{ $index = World::chunkHash($x, $z); $promise = $this->chunkPopulationRequestMap[$index] ?? null; if($promise !== null && isset($this->activeChunkPopulationTasks[$index])){ @@ -2766,7 +2774,7 @@ class World implements ChunkManager{ $this->activeChunkPopulationTasks[$index] = true; if($promise === null){ - $promise = new ChunkPopulationPromise(); + $promise = new Promise(); $this->chunkPopulationRequestMap[$index] = $promise; } @@ -2788,8 +2796,8 @@ class World implements ChunkManager{ } //chunk is already populated; return a pre-resolved promise that will directly fire callbacks assigned - $result = new ChunkPopulationPromise(); - $result->resolve(); + $result = new Promise(); + $result->resolve($chunk); return $result; } diff --git a/tests/phpstan/configs/phpstan-bugs.neon b/tests/phpstan/configs/phpstan-bugs.neon index a5f9c1b6b..d4a774353 100644 --- a/tests/phpstan/configs/phpstan-bugs.neon +++ b/tests/phpstan/configs/phpstan-bugs.neon @@ -20,6 +20,16 @@ parameters: count: 1 path: ../../../src/entity/projectile/Projectile.php + - + message: "#^Parameter \\#1 \\$ of closure expects TValue, TValue given\\.$#" + count: 2 + path: ../../../src/utils/Promise.php + + - + message: "#^Property pocketmine\\\\utils\\\\Promise\\\\:\\:\\$result \\(TValue\\|null\\) does not accept TValue\\.$#" + count: 1 + path: ../../../src/utils/Promise.php + - message: "#^Parameter \\#1 \\$ of closure expects TMemberType, TMemberType given\\.$#" count: 1