Merge 'minor-next' into 'major-next'

Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/12475368381
This commit is contained in:
pmmp-admin-bot[bot] 2024-12-24 01:24:08 +00:00
commit 882d8c4ab9
2 changed files with 55 additions and 32 deletions

View File

@ -115,6 +115,7 @@ use pocketmine\utils\ObjectSet;
use pocketmine\utils\TextFormat; use pocketmine\utils\TextFormat;
use pocketmine\world\format\io\GlobalItemDataHandlers; use pocketmine\world\format\io\GlobalItemDataHandlers;
use pocketmine\world\Position; use pocketmine\world\Position;
use pocketmine\world\World;
use pocketmine\YmlServerProperties; use pocketmine\YmlServerProperties;
use function array_map; use function array_map;
use function array_values; use function array_values;
@ -1178,6 +1179,19 @@ class NetworkSession{
$this->sendDataPacket(ClientboundCloseFormPacket::create()); $this->sendDataPacket(ClientboundCloseFormPacket::create());
} }
/**
* @phpstan-param \Closure() : void $onCompletion
*/
private function sendChunkPacket(string $chunkPacket, \Closure $onCompletion, World $world) : void{
$world->timings->syncChunkSend->startTiming();
try{
$this->queueCompressed($chunkPacket);
$onCompletion();
}finally{
$world->timings->syncChunkSend->stopTiming();
}
}
/** /**
* Instructs the networksession to start using the chunk at the given coordinates. This may occur asynchronously. * Instructs the networksession to start using the chunk at the given coordinates. This may occur asynchronously.
* @param \Closure $onCompletion To be called when chunk sending has completed. * @param \Closure $onCompletion To be called when chunk sending has completed.
@ -1185,8 +1199,12 @@ class NetworkSession{
*/ */
public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{ public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{
$world = $this->player->getLocation()->getWorld(); $world = $this->player->getLocation()->getWorld();
ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ)->onResolve( $promiseOrPacket = ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ);
if(is_string($promiseOrPacket)){
$this->sendChunkPacket($promiseOrPacket, $onCompletion, $world);
return;
}
$promiseOrPacket->onResolve(
//this callback may be called synchronously or asynchronously, depending on whether the promise is resolved yet //this callback may be called synchronously or asynchronously, depending on whether the promise is resolved yet
function(CompressBatchPromise $promise) use ($world, $onCompletion, $chunkX, $chunkZ) : void{ function(CompressBatchPromise $promise) use ($world, $onCompletion, $chunkX, $chunkZ) : void{
if(!$this->isConnected()){ if(!$this->isConnected()){
@ -1204,13 +1222,7 @@ class NetworkSession{
//to NEEDED if they want to be resent. //to NEEDED if they want to be resent.
return; return;
} }
$world->timings->syncChunkSend->startTiming(); $this->sendChunkPacket($promise->getResult(), $onCompletion, $world);
try{
$this->queueCompressed($promise);
$onCompletion();
}finally{
$world->timings->syncChunkSend->stopTiming();
}
} }
); );
} }

View File

@ -32,6 +32,7 @@ use pocketmine\world\ChunkListener;
use pocketmine\world\ChunkListenerNoOpTrait; use pocketmine\world\ChunkListenerNoOpTrait;
use pocketmine\world\format\Chunk; use pocketmine\world\format\Chunk;
use pocketmine\world\World; use pocketmine\world\World;
use function is_string;
use function spl_object_id; use function spl_object_id;
use function strlen; use function strlen;
@ -69,7 +70,7 @@ class ChunkCache implements ChunkListener{
foreach(self::$instances as $compressorMap){ foreach(self::$instances as $compressorMap){
foreach($compressorMap as $chunkCache){ foreach($compressorMap as $chunkCache){
foreach($chunkCache->caches as $chunkHash => $promise){ foreach($chunkCache->caches as $chunkHash => $promise){
if($promise->hasResult()){ if(is_string($promise)){
//Do not clear promises that are not yet fulfilled; they will have requesters waiting on them //Do not clear promises that are not yet fulfilled; they will have requesters waiting on them
unset($chunkCache->caches[$chunkHash]); unset($chunkCache->caches[$chunkHash]);
} }
@ -79,8 +80,8 @@ class ChunkCache implements ChunkListener{
} }
/** /**
* @var CompressBatchPromise[] * @var CompressBatchPromise[]|string[]
* @phpstan-var array<int, CompressBatchPromise> * @phpstan-var array<int, CompressBatchPromise|string>
*/ */
private array $caches = []; private array $caches = [];
@ -92,29 +93,17 @@ class ChunkCache implements ChunkListener{
private Compressor $compressor private Compressor $compressor
){} ){}
/** private function prepareChunkAsync(int $chunkX, int $chunkZ, int $chunkHash) : CompressBatchPromise{
* Requests asynchronous preparation of the chunk at the given coordinates.
*
* @return CompressBatchPromise a promise of resolution which will contain a compressed chunk packet.
*/
public function request(int $chunkX, int $chunkZ) : CompressBatchPromise{
$this->world->registerChunkListener($this, $chunkX, $chunkZ); $this->world->registerChunkListener($this, $chunkX, $chunkZ);
$chunk = $this->world->getChunk($chunkX, $chunkZ); $chunk = $this->world->getChunk($chunkX, $chunkZ);
if($chunk === null){ if($chunk === null){
throw new \InvalidArgumentException("Cannot request an unloaded chunk"); throw new \InvalidArgumentException("Cannot request an unloaded chunk");
} }
$chunkHash = World::chunkHash($chunkX, $chunkZ);
if(isset($this->caches[$chunkHash])){
++$this->hits;
return $this->caches[$chunkHash];
}
++$this->misses; ++$this->misses;
$this->world->timings->syncChunkSendPrepare->startTiming(); $this->world->timings->syncChunkSendPrepare->startTiming();
try{ try{
$this->caches[$chunkHash] = new CompressBatchPromise(); $promise = new CompressBatchPromise();
$this->world->getServer()->getAsyncPool()->submitTask( $this->world->getServer()->getAsyncPool()->submitTask(
new ChunkRequestTask( new ChunkRequestTask(
@ -122,17 +111,39 @@ class ChunkCache implements ChunkListener{
$chunkZ, $chunkZ,
DimensionIds::OVERWORLD, //TODO: not hardcode this DimensionIds::OVERWORLD, //TODO: not hardcode this
$chunk, $chunk,
$this->caches[$chunkHash], $promise,
$this->compressor $this->compressor
) )
); );
$this->caches[$chunkHash] = $promise;
$promise->onResolve(function(CompressBatchPromise $promise) use ($chunkHash) : void{
//the promise may have been discarded or replaced if the chunk was unloaded or modified in the meantime
if(($this->caches[$chunkHash] ?? null) === $promise){
$this->caches[$chunkHash] = $promise->getResult();
}
});
return $this->caches[$chunkHash]; return $promise;
}finally{ }finally{
$this->world->timings->syncChunkSendPrepare->stopTiming(); $this->world->timings->syncChunkSendPrepare->stopTiming();
} }
} }
/**
* Requests asynchronous preparation of the chunk at the given coordinates.
*
* @return CompressBatchPromise|string Compressed chunk packet, or a promise for one to be resolved asynchronously.
*/
public function request(int $chunkX, int $chunkZ) : CompressBatchPromise|string{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
if(isset($this->caches[$chunkHash])){
++$this->hits;
return $this->caches[$chunkHash];
}
return $this->prepareChunkAsync($chunkX, $chunkZ, $chunkHash);
}
private function destroy(int $chunkX, int $chunkZ) : bool{ private function destroy(int $chunkX, int $chunkZ) : bool{
$chunkHash = World::chunkHash($chunkX, $chunkZ); $chunkHash = World::chunkHash($chunkX, $chunkZ);
$existing = $this->caches[$chunkHash] ?? null; $existing = $this->caches[$chunkHash] ?? null;
@ -148,12 +159,12 @@ class ChunkCache implements ChunkListener{
$chunkPosHash = World::chunkHash($chunkX, $chunkZ); $chunkPosHash = World::chunkHash($chunkX, $chunkZ);
$cache = $this->caches[$chunkPosHash] ?? null; $cache = $this->caches[$chunkPosHash] ?? null;
if($cache !== null){ if($cache !== null){
if(!$cache->hasResult()){ if(!is_string($cache)){
//some requesters are waiting for this chunk, so their request needs to be fulfilled //some requesters are waiting for this chunk, so their request needs to be fulfilled
$cache->cancel(); $cache->cancel();
unset($this->caches[$chunkPosHash]); unset($this->caches[$chunkPosHash]);
$this->request($chunkX, $chunkZ)->onResolve(...$cache->getResolveCallbacks()); $this->prepareChunkAsync($chunkX, $chunkZ, $chunkPosHash)->onResolve(...$cache->getResolveCallbacks());
}else{ }else{
//dump the cache, it'll be regenerated the next time it's requested //dump the cache, it'll be regenerated the next time it's requested
$this->destroy($chunkX, $chunkZ); $this->destroy($chunkX, $chunkZ);
@ -199,8 +210,8 @@ class ChunkCache implements ChunkListener{
public function calculateCacheSize() : int{ public function calculateCacheSize() : int{
$result = 0; $result = 0;
foreach($this->caches as $cache){ foreach($this->caches as $cache){
if($cache->hasResult()){ if(is_string($cache)){
$result += strlen($cache->getResult()); $result += strlen($cache);
} }
} }
return $result; return $result;