diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index b7a7f9def..492a58d8b 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -977,12 +977,27 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener, assert(isset($this->usedChunks[World::chunkHash($x, $z)])); $this->usedChunks[World::chunkHash($x, $z)] = true; - $spawn = $this->spawnChunkLoadCount++ === $this->spawnThreshold; - $this->networkSession->startUsingChunk($x, $z, $spawn); + $this->networkSession->startUsingChunk($x, $z, function(int $chunkX, int $chunkZ) : void{ + if($this->spawned){ + $this->spawnEntitiesOnChunk($chunkX, $chunkZ); + }elseif($this->spawnChunkLoadCount++ === $this->spawnThreshold){ + $this->spawned = true; - if($spawn){ - //TODO: not sure this should be here - $this->spawned = true; + foreach($this->usedChunks as $chunkHash => $_){ + World::getXZ($chunkHash, $_x, $_z); + $this->spawnEntitiesOnChunk($_x, $_z); + } + + $this->networkSession->onTerrainReady(); + } + }); + } + + protected function spawnEntitiesOnChunk(int $chunkX, int $chunkZ) : void{ + foreach($this->world->getChunkEntities($chunkX, $chunkZ) as $entity){ + if($entity !== $this and !$entity->isClosed() and !$entity->isFlaggedForDespawn()){ + $entity->spawnTo($this); + } } } diff --git a/src/pocketmine/network/mcpe/NetworkSession.php b/src/pocketmine/network/mcpe/NetworkSession.php index 508f3b266..ba9e465b0 100644 --- a/src/pocketmine/network/mcpe/NetworkSession.php +++ b/src/pocketmine/network/mcpe/NetworkSession.php @@ -69,6 +69,7 @@ use pocketmine\PlayerInfo; use pocketmine\Server; use pocketmine\timings\Timings; use pocketmine\utils\BinaryDataException; +use pocketmine\utils\Utils; use pocketmine\world\Position; use function bin2hex; use function count; @@ -760,28 +761,26 @@ class NetworkSession{ return $this->sendDataPacket($pk); } - public function startUsingChunk(int $chunkX, int $chunkZ, bool $spawn = false) : void{ + /** + * Instructs the networksession to start using the chunk at the given coordinates. This may occur asynchronously. + * @param int $chunkX + * @param int $chunkZ + * @param \Closure $onCompletion To be called when chunk sending has completed. + */ + public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{ + Utils::validateCallableSignature(function(int $chunkX, int $chunkZ){}, $onCompletion); + ChunkCache::getInstance($this->player->getWorld())->request($chunkX, $chunkZ)->onResolve( //this callback may be called synchronously or asynchronously, depending on whether the promise is resolved yet - function(CompressBatchPromise $promise) use($chunkX, $chunkZ, $spawn){ + function(CompressBatchPromise $promise) use($chunkX, $chunkZ, $onCompletion){ if(!$this->isConnected()){ return; } $this->player->world->timings->syncChunkSendTimer->startTiming(); try{ $this->queueCompressed($promise); - - foreach($this->player->getWorld()->getChunkEntities($chunkX, $chunkZ) as $entity){ - if($entity !== $this->player and !$entity->isClosed() and !$entity->isFlaggedForDespawn()){ - $entity->spawnTo($this->player); - } - } - - if($spawn){ - //TODO: potential race condition during chunk sending could cause this to be called too early - $this->onTerrainReady(); - } + $onCompletion($chunkX, $chunkZ); }finally{ $this->player->world->timings->syncChunkSendTimer->stopTiming(); }