Merge branch 'minor-next' into major-next

This commit is contained in:
Dylan K. Taylor
2023-04-11 22:51:50 +01:00
14 changed files with 478 additions and 26 deletions

View File

@@ -23,6 +23,9 @@ declare(strict_types=1);
namespace pocketmine\event;
/**
* @internal
*/
final class RegisteredListenerCache{
/**

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;
@@ -239,12 +241,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 = [];
@@ -310,8 +316,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;
@@ -749,6 +755,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{
@@ -800,6 +808,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{
@@ -897,16 +908,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]);
}
@@ -914,10 +932,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){}

34
src/world/ChunkTicker.php Normal file
View File

@@ -0,0 +1,34 @@
<?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\world;
/**
* Used to signal to the World that a chunk should be ticked.
*
* @see World::registerTickingChunk()
* @see World::unregisterTickingChunk()
*/
final class ChunkTicker{
}

View File

@@ -0,0 +1,37 @@
<?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\world;
/**
* @internal
*/
final class TickingChunkEntry{
/**
* @var ChunkTicker[] spl_object_id => ChunkTicker
* @phpstan-var array<int, ChunkTicker>
*/
public array $tickers = [];
public bool $ready = false;
}

View File

@@ -27,6 +27,10 @@ namespace pocketmine\world;
* TickingChunkLoader includes all of the same functionality as ChunkLoader (it can be used in the same way).
* However, using this version will also cause chunks around the loader's reported coordinates to get random block
* updates.
*
* @deprecated
* @see World::registerTickingChunk()
* @see World::unregisterTickingChunk()
*/
interface TickingChunkLoader extends ChunkLoader{

View File

@@ -212,13 +212,24 @@ class World implements ChunkManager{
/**
* @var TickingChunkLoader[] spl_object_id => TickingChunkLoader
* @phpstan-var array<int, TickingChunkLoader>
*
* @deprecated
*/
private array $tickingLoaders = [];
/**
* @var int[] spl_object_id => number of chunks
* @phpstan-var array<int, int>
*
* @deprecated
*/
private array $tickingLoaderCounter = [];
/**
* @var TickingChunkEntry[] chunkHash => TickingChunkEntry
* @phpstan-var array<ChunkPosHash, TickingChunkEntry>
*/
private array $tickingChunks = [];
/**
* @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader]
* @phpstan-var array<ChunkPosHash, array<int, ChunkLoader>>
@@ -1152,32 +1163,61 @@ class World implements ChunkManager{
}
/**
* Returns the radius of chunks to be ticked around each ticking chunk loader (usually players). This is referred to
* as "simulation distance" in the Minecraft: Bedrock world options screen.
* Returns the radius of chunks to be ticked around each player. This is referred to as "simulation distance" in the
* Minecraft: Bedrock world options screen.
*/
public function getChunkTickRadius() : int{
return $this->chunkTickRadius;
}
/**
* Sets the radius of chunks ticked around each ticking chunk loader (usually players).
* Sets the radius of chunks ticked around each player. This may not take effect immediately, since each player
* needs to recalculate their tick radius.
*/
public function setChunkTickRadius(int $radius) : void{
$this->chunkTickRadius = $radius;
}
private function tickChunks() : void{
if($this->chunkTickRadius <= 0 || count($this->tickingLoaders) === 0){
return;
/**
* Instructs the World to tick the specified chunk, for as long as this chunk ticker (or any other chunk ticker) is
* registered to it.
*/
public function registerTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
$chunkPosHash = World::chunkHash($chunkX, $chunkZ);
$entry = $this->tickingChunks[$chunkPosHash] ?? null;
if($entry === null){
$entry = $this->tickingChunks[$chunkPosHash] = new TickingChunkEntry();
}
$entry->tickers[spl_object_id($ticker)] = $ticker;
}
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
/** @var bool[] $chunkTickList chunkhash => dummy */
$chunkTickList = [];
$chunkTickableCache = [];
/**
* Unregisters the given chunk ticker from the specified chunk. If there are other tickers still registered to the
* chunk, it will continue to be ticked.
*/
public function unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
$tickerId = spl_object_id($ticker);
if(isset($this->tickingChunks[$chunkHash]->tickers[$tickerId])){
unset($this->tickingChunks[$chunkHash]->tickers[$tickerId]);
if(count($this->tickingChunks[$chunkHash]->tickers) === 0){
unset($this->tickingChunks[$chunkHash]);
}
}
}
/**
* @deprecated
*
* @param true[] $chunkTickList
* @param bool[] $chunkTickableCache
*
* @phpstan-param array<int, true> $chunkTickList
* @phpstan-param array<int, bool> $chunkTickableCache
* @phpstan-param-out array<int, true> $chunkTickList
* @phpstan-param-out array<int, bool> $chunkTickableCache
*/
private function selectTickableChunksLegacy(array &$chunkTickList, array &$chunkTickableCache) : void{
$centerChunks = [];
$selector = new ChunkSelector();
@@ -1202,6 +1242,38 @@ class World implements ChunkManager{
}
}
}
}
private function tickChunks() : void{
if($this->chunkTickRadius <= 0 || (count($this->tickingChunks) === 0 && count($this->tickingLoaders) === 0)){
return;
}
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
/** @var bool[] $chunkTickList chunkhash => dummy */
$chunkTickList = [];
$chunkTickableCache = [];
foreach($this->tickingChunks as $hash => $entry){
if(!$entry->ready){
World::getXZ($hash, $chunkX, $chunkZ);
if($this->isChunkTickable($chunkX, $chunkZ, $chunkTickableCache)){
$entry->ready = true;
}else{
//the chunk has been flagged as temporarily not tickable, so we don't want to tick it this time
continue;
}
}
$chunkTickList[$hash] = true;
}
//TODO: REMOVE THIS
//backwards compatibility for TickingChunkLoader, although I'm not sure this is really necessary in practice
if(count($this->tickingLoaders) !== 0){
$this->selectTickableChunksLegacy($chunkTickList, $chunkTickableCache);
}
$this->timings->randomChunkUpdatesChunkSelection->stopTiming();
@@ -1253,11 +1325,28 @@ class World implements ChunkManager{
return true;
}
/**
* Marks the 3x3 chunks around the specified chunk as not ready to be ticked. This is used to prevent chunk ticking
* while a chunk is being populated, light-populated, or unloaded.
* Each chunk will be rechecked every tick until it is ready to be ticked again.
*/
private function markTickingChunkUnavailable(int $chunkX, int $chunkZ) : void{
for($cx = -1; $cx <= 1; ++$cx){
for($cz = -1; $cz <= 1; ++$cz){
$chunkHash = World::chunkHash($chunkX + $cx, $chunkZ + $cz);
if(isset($this->tickingChunks[$chunkHash])){
$this->tickingChunks[$chunkHash]->ready = false;
}
}
}
}
private function orderLightPopulation(int $chunkX, int $chunkZ) : void{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
$lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated();
if($lightPopulatedState === false){
$this->chunks[$chunkHash]->setLightPopulated(null);
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
$this->workerPool->submitTask(new LightPopulationTask(
$this->chunks[$chunkHash],
@@ -2332,6 +2421,7 @@ class World implements ChunkManager{
throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked");
}
$this->chunkLock[$chunkHash] = $lockId;
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
}
/**
@@ -2397,6 +2487,7 @@ class World implements ChunkManager{
unset($this->blockCache[$chunkHash]);
unset($this->changedBlocks[$chunkHash]);
$chunk->setTerrainDirty();
$this->markTickingChunkUnavailable($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking
if(!$this->isChunkInUse($chunkX, $chunkZ)){
$this->unloadChunkRequest($chunkX, $chunkZ);
@@ -2819,6 +2910,8 @@ class World implements ChunkManager{
unset($this->chunks[$chunkHash]);
unset($this->blockCache[$chunkHash]);
unset($this->changedBlocks[$chunkHash]);
unset($this->tickingChunks[$chunkHash]);
$this->markTickingChunkUnavailable($x, $z);
if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){
$this->logger->debug("Rejecting population promise for chunk $x $z");