Ticking chunks rewrite (#5689)

This API is much more flexible than the old, allowing any arbitrary set of chunks to be ticked.

These changes also improve the performance of random chunk ticking by almost entirely eliminating the cost of chunk selection. Ticking chunks are now reevaluated when a player moves, instead of every tick.

The system also does not attempt to check the same chunks twice, leading to further improvements.

Overall, the overhead of random chunk selection is reduced anywhere from 80-96%. In practice, this can offer a 5-10% performance gain for servers with sparsely distributed players.
This commit is contained in:
Dylan T
2023-04-11 20:01:19 +01:00
committed by GitHub
parent 1c0eed56f1
commit 946c2fbacc
7 changed files with 220 additions and 25 deletions

View File

@ -54,23 +54,23 @@ final class ChunkSelector{
//If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be.
/* Top right quadrant */
yield World::chunkHash($centerX + $x, $centerZ + $z);
yield $subRadius => World::chunkHash($centerX + $x, $centerZ + $z);
/* Top left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ + $z);
yield $subRadius => World::chunkHash($centerX - $x - 1, $centerZ + $z);
/* Bottom right quadrant */
yield World::chunkHash($centerX + $x, $centerZ - $z - 1);
yield $subRadius => World::chunkHash($centerX + $x, $centerZ - $z - 1);
/* Bottom left quadrant */
yield World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
yield $subRadius => World::chunkHash($centerX - $x - 1, $centerZ - $z - 1);
if($x !== $z){
/* Top right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ + $x);
yield $subRadius => World::chunkHash($centerX + $z, $centerZ + $x);
/* Top left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ + $x);
yield $subRadius => World::chunkHash($centerX - $z - 1, $centerZ + $x);
/* Bottom right quadrant mirror */
yield World::chunkHash($centerX + $z, $centerZ - $x - 1);
yield $subRadius => World::chunkHash($centerX + $z, $centerZ - $x - 1);
/* Bottom left quadrant mirror */
yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
yield $subRadius => World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
}
}
}

View File

@ -121,6 +121,8 @@ use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\TextFormat;
use pocketmine\world\ChunkListener;
use pocketmine\world\ChunkListenerNoOpTrait;
use pocketmine\world\ChunkLoader;
use pocketmine\world\ChunkTicker;
use pocketmine\world\format\Chunk;
use pocketmine\world\Position;
use pocketmine\world\sound\EntityAttackNoDamageSound;
@ -237,12 +239,16 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
protected array $loadQueue = [];
protected int $nextChunkOrderRun = 5;
/** @var true[] */
private array $tickingChunks = [];
protected int $viewDistance = -1;
protected int $spawnThreshold;
protected int $spawnChunkLoadCount = 0;
protected int $chunksPerTick;
protected ChunkSelector $chunkSelector;
protected PlayerChunkLoader $chunkLoader;
protected ChunkLoader $chunkLoader;
protected ChunkTicker $chunkTicker;
/** @var bool[] map: raw UUID (string) => bool */
protected array $hiddenPlayers = [];
@ -308,8 +314,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$this->spawnThreshold = (int) (($this->server->getConfigGroup()->getPropertyInt("chunk-sending.spawn-radius", 4) ** 2) * M_PI);
$this->chunkSelector = new ChunkSelector();
$this->chunkLoader = new PlayerChunkLoader($spawnLocation);
$this->chunkLoader = new class implements ChunkLoader{};
$this->chunkTicker = new ChunkTicker();
$world = $spawnLocation->getWorld();
//load the spawn chunk so we can see the terrain
$xSpawnChunk = $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE;
@ -747,6 +753,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$world->unregisterChunkLoader($this->chunkLoader, $x, $z);
$world->unregisterChunkListener($this, $x, $z);
unset($this->loadQueue[$index]);
$world->unregisterTickingChunk($this->chunkTicker, $x, $z);
unset($this->tickingChunks[$index]);
}
protected function spawnEntitiesOnAllChunks() : void{
@ -798,6 +806,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
unset($this->loadQueue[$index]);
$this->getWorld()->registerChunkLoader($this->chunkLoader, $X, $Z, true);
$this->getWorld()->registerChunkListener($this, $X, $Z);
if(isset($this->tickingChunks[$index])){
$this->getWorld()->registerTickingChunk($this->chunkTicker, $X, $Z);
}
$this->getWorld()->requestChunkPopulation($X, $Z, $this->chunkLoader)->onCompletion(
function() use ($X, $Z, $index, $world) : void{
@ -895,16 +906,23 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
Timings::$playerChunkOrder->startTiming();
$newOrder = [];
$tickingChunks = [];
$unloadChunks = $this->usedChunks;
$world = $this->getWorld();
$tickingChunkRadius = $world->getChunkTickRadius();
foreach($this->chunkSelector->selectChunks(
$this->server->getAllowedViewDistance($this->viewDistance),
$this->location->getFloorX() >> Chunk::COORD_BIT_SIZE,
$this->location->getFloorZ() >> Chunk::COORD_BIT_SIZE
) as $hash){
) as $radius => $hash){
if(!isset($this->usedChunks[$hash]) || $this->usedChunks[$hash]->equals(UsedChunkStatus::NEEDED())){
$newOrder[$hash] = true;
}
if($radius < $tickingChunkRadius){
$tickingChunks[$hash] = true;
}
unset($unloadChunks[$hash]);
}
@ -912,10 +930,18 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
World::getXZ($index, $X, $Z);
$this->unloadChunk($X, $Z);
}
foreach($this->tickingChunks as $hash => $_){
//any chunks we encounter here are still used by the player, but may no longer be within ticking range
if(!isset($tickingChunks[$hash]) && !isset($newOrder[$hash])){
World::getXZ($hash, $tickingChunkX, $tickingChunkZ);
$world->unregisterTickingChunk($this->chunkTicker, $tickingChunkX, $tickingChunkZ);
}
}
$this->loadQueue = $newOrder;
$this->tickingChunks = $tickingChunks;
if(count($this->loadQueue) > 0 || count($unloadChunks) > 0){
$this->chunkLoader->setCurrentLocation($this->location);
$this->getNetworkSession()->syncViewAreaCenterPoint($this->location, $this->viewDistance);
}

View File

@ -26,6 +26,10 @@ namespace pocketmine\player;
use pocketmine\math\Vector3;
use pocketmine\world\TickingChunkLoader;
/**
* @deprecated This class was only needed to implement TickingChunkLoader, which is now deprecated.
* ChunkTicker should be registered on ticking chunks to make them tick instead.
*/
final class PlayerChunkLoader implements TickingChunkLoader{
public function __construct(private Vector3 $currentLocation){}