diff --git a/src/pocketmine/MemoryManager.php b/src/pocketmine/MemoryManager.php index b93f4b25a..b7e149b2a 100644 --- a/src/pocketmine/MemoryManager.php +++ b/src/pocketmine/MemoryManager.php @@ -47,7 +47,7 @@ class MemoryManager{ private $garbageCollectionTrigger; private $garbageCollectionAsync; - private $chunkLimit; + private $chunkRadiusOverride; private $chunkCollect; private $chunkTrigger; @@ -104,7 +104,7 @@ class MemoryManager{ $this->garbageCollectionTrigger = (bool) $this->server->getProperty("memory.garbage-collection.low-memory-trigger", true); $this->garbageCollectionAsync = (bool) $this->server->getProperty("memory.garbage-collection.collect-async-worker", true); - $this->chunkLimit = (int) $this->server->getProperty("memory.max-chunks.trigger-limit", 96); + $this->chunkRadiusOverride = (int) $this->server->getProperty("memory.max-chunks.chunk-radius", 4); $this->chunkCollect = (bool) $this->server->getProperty("memory.max-chunks.trigger-chunk-collect", true); $this->chunkTrigger = (bool) $this->server->getProperty("memory.max-chunks.low-memory-trigger", true); @@ -122,8 +122,15 @@ class MemoryManager{ return !($this->lowMemory and $this->chunkTrigger); } - public function getViewDistance($distance){ - return $this->lowMemory ? min($this->chunkLimit, $distance) : $distance; + /** + * Returns the allowed chunk radius based on the current memory usage. + * + * @param int $distance + * + * @return int + */ + public function getViewDistance(int $distance) : int{ + return $this->lowMemory ? min($this->chunkRadiusOverride, $distance) : $distance; } public function trigger($memory, $limit, $global = false, $triggerCount = 0){ diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index a40452605..ab0aca78c 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -216,7 +216,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade /** @var Vector3 */ protected $newPosition; - protected $viewDistance; + protected $viewDistance = -1; protected $chunksPerTick; protected $spawnThreshold; /** @var null|WeakPosition */ @@ -421,6 +421,17 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade $this->inAirTicks = 0; } + public function getViewDistance() : int{ + return $this->viewDistance; + } + + public function setViewDistance(int $distance){ + $this->viewDistance = $this->server->getAllowedViewDistance($distance); + $pk = new ChunkRadiusUpdatedPacket(); + $pk->radius = $this->viewDistance; + $this->dataPacket($pk); + } + /** * @return bool */ @@ -556,11 +567,10 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade $this->clientID = $clientID; $this->loaderId = Level::generateChunkLoaderId($this); $this->chunksPerTick = (int) $this->server->getProperty("chunk-sending.per-tick", 4); - $this->spawnThreshold = (int) $this->server->getProperty("chunk-sending.spawn-threshold", 56); + $this->spawnThreshold = (int) (($this->server->getProperty("chunk-sending.spawn-radius", 4) ** 2) * M_PI); $this->spawnPosition = null; $this->gamemode = $this->server->getGamemode(); $this->setLevel($this->server->getDefaultLevel()); - $this->viewDistance = $this->server->getViewDistance(); $this->newPosition = new Vector3(0, 0, 0); $this->boundingBox = new AxisAlignedBB(0, 0, 0, 0, 0, 0); @@ -859,7 +869,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade } protected function orderChunks(){ - if($this->connected === false){ + if($this->connected === false or $this->viewDistance === -1){ return false; } @@ -867,59 +877,77 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade $this->nextChunkOrderRun = 200; - $viewDistance = $this->server->getMemoryManager()->getViewDistance($this->viewDistance); + $radius = $this->server->getAllowedViewDistance($this->viewDistance); + $radiusSquared = $radius ** 2; $newOrder = []; - $lastChunk = $this->usedChunks; + $unloadChunks = $this->usedChunks; $centerX = $this->x >> 4; $centerZ = $this->z >> 4; - $layer = 1; - $leg = 0; - $x = 0; - $z = 0; + for($x = 0; $x < $radius; ++$x){ + for($z = 0; $z <= $x; ++$z){ + if(($x ** 2 + $z ** 2) > $radiusSquared){ + break; //skip to next band + } - for($i = 0; $i < $viewDistance; ++$i){ + //If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be. - $chunkX = $x + $centerX; - $chunkZ = $z + $centerZ; + /* Top right quadrant */ + if(!isset($this->usedChunks[$index = Level::chunkHash($centerX + $x, $centerZ + $z)]) or $this->usedChunks[$index] === false){ + $newOrder[$index] = true; + } + unset($unloadChunks[$index]); - if(!isset($this->usedChunks[$index = Level::chunkHash($chunkX, $chunkZ)]) or $this->usedChunks[$index] === false){ - $newOrder[$index] = true; - } - unset($lastChunk[$index]); + /* Top left quadrant */ + if(!isset($this->usedChunks[$index = Level::chunkHash($centerX - $x - 1, $centerZ + $z)]) or $this->usedChunks[$index] === false){ + $newOrder[$index] = true; + } + unset($unloadChunks[$index]); - switch($leg){ - case 0: - ++$x; - if($x === $layer){ - ++$leg; + /* Bottom right quadrant */ + if(!isset($this->usedChunks[$index = Level::chunkHash($centerX + $x, $centerZ - $z - 1)]) or $this->usedChunks[$index] === false){ + $newOrder[$index] = true; + } + unset($unloadChunks[$index]); + + + /* Bottom left quadrant */ + if(!isset($this->usedChunks[$index = Level::chunkHash($centerX - $x - 1, $centerZ - $z - 1)]) or $this->usedChunks[$index] === false){ + $newOrder[$index] = true; + } + unset($unloadChunks[$index]); + + if($x !== $z){ + /* Top right quadrant mirror */ + if(!isset($this->usedChunks[$index = Level::chunkHash($centerX + $z, $centerZ + $x)]) or $this->usedChunks[$index] === false){ + $newOrder[$index] = true; } - break; - case 1: - ++$z; - if($z === $layer){ - ++$leg; + unset($unloadChunks[$index]); + + /* Top left quadrant mirror */ + if(!isset($this->usedChunks[$index = Level::chunkHash($centerX - $z - 1, $centerZ + $x)]) or $this->usedChunks[$index] === false){ + $newOrder[$index] = true; } - break; - case 2: - --$x; - if(-$x === $layer){ - ++$leg; + unset($unloadChunks[$index]); + + /* Bottom right quadrant mirror */ + if(!isset($this->usedChunks[$index = Level::chunkHash($centerX + $z, $centerZ - $x - 1)]) or $this->usedChunks[$index] === false){ + $newOrder[$index] = true; } - break; - case 3: - --$z; - if(-$z === $layer){ - $leg = 0; - ++$layer; + unset($unloadChunks[$index]); + + /* Bottom left quadrant mirror */ + if(!isset($this->usedChunks[$index = Level::chunkHash($centerX - $z - 1, $centerZ - $x - 1)]) or $this->usedChunks[$index] === false){ + $newOrder[$index] = true; } - break; + unset($unloadChunks[$index]); + } } } - foreach($lastChunk as $index => $bool){ + foreach($unloadChunks as $index => $bool){ Level::getXZ($index, $X, $Z); $this->unloadChunk($X, $Z); } @@ -2932,12 +2960,7 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade } break; case ProtocolInfo::REQUEST_CHUNK_RADIUS_PACKET: - if($this->spawned){ - $this->viewDistance = $packet->radius ** 2; - } - $pk = new ChunkRadiusUpdatedPacket(); - $pk->radius = $packet->radius; - $this->dataPacket($pk); + $this->setViewDistance($packet->radius); break; case ProtocolInfo::SET_PLAYER_GAME_TYPE_PACKET: if($packet->gamemode !== ($this->gamemode & 0x01)){ diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 353c6900c..b386ce6d5 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -333,8 +333,19 @@ class Server{ /** * @return int */ - public function getViewDistance(){ - return max(56, $this->getProperty("chunk-sending.max-chunks", 256)); + public function getViewDistance() : int{ + return max(2, $this->getConfigInt("view-distance", 8)); + } + + /** + * Returns a view distance up to the currently-allowed limit. + * + * @param int $distance + * + * @return int + */ + public function getAllowedViewDistance(int $distance) : int{ + return max(2, min($distance, $this->memoryManager->getViewDistance($this->getViewDistance()))); } /** @@ -1384,6 +1395,7 @@ class Server{ "enable-rcon" => false, "rcon.password" => substr(base64_encode(random_bytes(20)), 3, 10), "auto-save" => true, + "view-distance" => 8 ]); $this->forceLanguage = $this->getProperty("settings.force-language", false); diff --git a/src/pocketmine/resources/pocketmine.yml b/src/pocketmine/resources/pocketmine.yml index f0ecf96d9..3adaaa1ff 100644 --- a/src/pocketmine/resources/pocketmine.yml +++ b/src/pocketmine/resources/pocketmine.yml @@ -58,8 +58,8 @@ memory: low-memory-trigger: true max-chunks: - #Limit of chunks to load per player, overrides chunk-sending.max-chunks - trigger-limit: 96 + #Maximum render distance per player when low memory is triggered + chunk-radius: 4 #Do chunk garbage collection on trigger trigger-chunk-collect: true @@ -117,12 +117,11 @@ level-settings: always-tick-players: false chunk-sending: + #To change server normal render distance, change view-distance in server.properties. #Amount of chunks sent to players per tick per-tick: 4 - #Amount of chunks sent around each player - max-chunks: 192 - #Amount of chunks that need to be sent before spawning the player - spawn-threshold: 56 + #Radius of chunks that need to be sent before spawning the player + spawn-radius: 4 #Save a serialized copy of the chunk in memory for faster sending #Useful in mostly-static worlds where lots of players join at the same time cache-chunks: false