Moved chunk population to async tasks, and no more cut trees!

This commit is contained in:
Shoghi Cervantes 2015-03-27 01:56:49 +01:00
parent 72c4c01542
commit 08f2b7f291
No known key found for this signature in database
GPG Key ID: 78464DB0A7837F89
13 changed files with 359 additions and 43 deletions

View File

@ -68,6 +68,7 @@ use pocketmine\level\format\generic\EmptyChunkSection;
use pocketmine\level\format\LevelProvider;
use pocketmine\level\generator\GenerationTask;
use pocketmine\level\generator\Generator;
use pocketmine\level\generator\PopulationTask;
use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Math;
use pocketmine\math\Vector2;
@ -173,8 +174,11 @@ class Level implements ChunkManager, Metadatable{
private $chunkSendQueue = [];
private $chunkSendTasks = [];
private $chunkPopulationQueue = [];
private $chunkPopulationLock = [];
private $chunkGenerationQueue = [];
private $chunkGenerationQueueSize = 16;
private $chunkGenerationQueueSize = 8;
private $chunkPopulationQueueSize = 2;
private $autoSave = true;
@ -195,8 +199,6 @@ class Level implements ChunkManager, Metadatable{
protected $chunkTickRadius;
protected $chunkTickList = [];
protected $chunksPerTick;
protected $chunksPopulatedPerTick;
protected $chunksPopulated = 0;
protected $clearChunksOnTick;
protected $randomTickBlocks = [
Block::GRASS => Grass::class,
@ -306,9 +308,9 @@ class Level implements ChunkManager, Metadatable{
$this->time = (int) $this->provider->getTime();
$this->chunkTickRadius = min($this->server->getViewDistance(), max(1, (int) $this->server->getProperty("chunk-ticking.tick-radius", 4)));
$this->chunksPerTick = (int) $this->server->getProperty("chunk-ticking.per-tick", 260);
$this->chunksPopulatedPerTick = (int) $this->server->getProperty("chunk-generation.populations-per-tick", 1);
$this->chunkGenerationQueueSize = (int) $this->server->getProperty("chunk-generation.queue-size", 16);
$this->chunksPerTick = (int) $this->server->getProperty("chunk-ticking.per-tick", 40);
$this->chunkGenerationQueueSize = (int) $this->server->getProperty("chunk-generation.queue-size", 8);
$this->chunkPopulationQueueSize = (int) $this->server->getProperty("chunk-generation.population-queue-size", 2);
$this->chunkTickList = [];
$this->clearChunksOnTick = (bool) $this->server->getProperty("chunk-ticking.clear-tick-list", false);
@ -599,8 +601,6 @@ class Level implements ChunkManager, Metadatable{
$this->processChunkRequest();
$this->chunksPopulated = 0;
$this->timings->doTick->stopTiming();
}
@ -1820,15 +1820,25 @@ class Level implements ChunkManager, Metadatable{
public function generateChunkCallback($x, $z, FullChunk $chunk){
Timings::$generationTimer->startTiming();
if(isset($this->chunkGenerationQueue[$index = Level::chunkHash($x, $z)])){
if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)])){
$oldChunk = $this->getChunk($x, $z, false);
unset($this->chunkGenerationQueue[$index = Level::chunkHash($x, $z)]);
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
unset($this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)]);
}
}
unset($this->chunkPopulationQueue[$index]);
$chunk->setProvider($this->provider);
$this->setChunk($x, $z, $chunk);
$chunk = $this->getChunk($x, $z, false);
if($chunk !== null and ($oldChunk === null or $oldChunk->isPopulated() === false) and $chunk->isPopulated() and $chunk->getProvider() !== null){
$this->server->getPluginManager()->callEvent(new ChunkPopulateEvent($chunk));
}
}elseif(isset($this->chunkGenerationQueue[$index]) or isset($this->chunkPopulationLock[$index])){
unset($this->chunkGenerationQueue[$index]);
unset($this->chunkPopulationLock[$index]);
$chunk->setProvider($this->provider);
$this->setChunk($x, $z, $chunk);
}
Timings::$generationTimer->stopTiming();
}
@ -2285,34 +2295,48 @@ class Level implements ChunkManager, Metadatable{
public function populateChunk($x, $z, $force = false){
if(isset($this->chunkPopulationQueue[$index = Level::chunkHash($x, $z)])){
return false;
}
Timings::$generationTimer->startTiming();
if(!$this->isChunkPopulated($x, $z)){
$populate = true;
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
if(!$this->isChunkGenerated($x + $xx, $z + $zz)){
if(isset($this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)])){
$populate = false;
}elseif(!$this->isChunkGenerated($x + $xx, $z + $zz)){
$populate = false;
$this->generateChunk($x + $xx, $z + $zz, $force);
}
}
}
if($this->chunksPopulated < $this->chunksPopulatedPerTick and $populate){
Timings::$generationTimer->startTiming();
$this->generatorInstance->populateChunk($x, $z);
$chunk = $this->getChunk($x, $z);
$chunk->setPopulated(true);
if($chunk->getProvider() !== null){
$this->server->getPluginManager()->callEvent(new ChunkPopulateEvent($chunk));
}else{
$this->unloadChunk($x, $z, false);
if($populate){
if(count($this->chunkPopulationQueue) >= $this->chunkPopulationQueueSize and !$force){
Timings::$generationTimer->stopTiming();
return false;
}
if(!isset($this->chunkPopulationQueue[$index])){
$this->chunkPopulationQueue[$index] = true;
for($xx = -1; $xx <= 1; ++$xx){
for($zz = -1; $zz <= 1; ++$zz){
$this->chunkPopulationLock[Level::chunkHash($x + $xx, $z + $zz)] = true;
}
}
$task = new PopulationTask($this, $this->generatorInstance, $this->getChunk($x, $z, true));
$this->server->getScheduler()->scheduleAsyncTask($task);
}
++$this->chunksPopulated;
Timings::$generationTimer->stopTiming();
return true;
return false;
}
Timings::$generationTimer->stopTiming();
return false;
}
Timings::$generationTimer->stopTiming();
return true;
}
@ -2322,9 +2346,11 @@ class Level implements ChunkManager, Metadatable{
}
if(!isset($this->chunkGenerationQueue[$index = Level::chunkHash($x, $z)])){
Timings::$generationTimer->startTiming();
$this->chunkGenerationQueue[$index] = true;
$task = new GenerationTask($this, $this->generatorInstance, $this->getChunk($x, $z, true));
$this->server->getScheduler()->scheduleAsyncTask($task);
Timings::$generationTimer->stopTiming();
}
}

View File

@ -310,6 +310,8 @@ interface FullChunk{
public function toBinary();
public function toFastBinary();
/**
* @return boolean
*/
@ -328,4 +330,12 @@ interface FullChunk{
*/
public static function fromBinary($data, LevelProvider $provider = null);
/**
* @param string $data
* @param LevelProvider $provider
*
* @return FullChunk
*/
public static function fromFastBinary($data, LevelProvider $provider = null);
}

View File

@ -357,4 +357,12 @@ abstract class BaseFullChunk implements FullChunk{
$this->hasChanged = (bool) $changed;
}
public static function fromFastBinary($data, LevelProvider $provider = null){
self::fromBinary($data, $provider);
}
public function toFastBinary(){
return $this->toBinary();
}
}

View File

@ -221,6 +221,10 @@ class Chunk extends BaseFullChunk{
$this->isGenerated = (bool) $value;
}
public static function fromFastBinary($data, LevelProvider $provider = null){
return self::fromBinary($data, $provider);
}
/**
* @param string $data
* @param LevelProvider $provider
@ -272,6 +276,10 @@ class Chunk extends BaseFullChunk{
}
}
public function toFastBinary(){
return $this->toBinary(false);
}
public function toBinary($saveExtra = false){
$chunkIndex = LevelDB::chunkIndex($this->getX(), $this->getZ());

View File

@ -38,7 +38,12 @@ class Chunk extends BaseFullChunk{
/** @var Compound */
protected $nbt;
public function __construct($level, Compound $nbt){
public function __construct($level, Compound $nbt = null){
if($nbt === null){
$this->nbt = new Compound("Level", []);
return;
}
$this->nbt = $nbt;
if(isset($this->nbt->Entities) and $this->nbt->Entities instanceof Enum){
@ -89,6 +94,9 @@ class Chunk extends BaseFullChunk{
unset($this->nbt->Data);
unset($this->nbt->SkyLight);
unset($this->nbt->BlockLight);
unset($this->nbt->BiomeColors);
unset($this->nbt->HeightMap);
unset($this->nbt->Biomes);
}
public function getBlockId($x, $y, $z){
@ -231,28 +239,35 @@ class Chunk extends BaseFullChunk{
* @return bool
*/
public function isPopulated(){
return $this->nbt["TerrainPopulated"] > 0;
return isset($this->nbt->TerrainPopulated) and $this->nbt->TerrainPopulated->getValue() > 0;
}
/**
* @param int $value
*/
public function setPopulated($value = 1){
$this->nbt->TerrainPopulated = new Byte("TerrainPopulated", $value);
$this->nbt->TerrainPopulated = new Byte("TerrainPopulated", (int) $value);
$this->hasChanged = true;
}
/**
* @return bool
*/
public function isGenerated(){
return $this->nbt["TerrainPopulated"] > 0 or (isset($this->nbt->TerrainGenerated) and $this->nbt["TerrainGenerated"] > 0);
if(isset($this->nbt->TerrainGenerated)){
return $this->nbt->TerrainGenerated->getValue() > 0;
}elseif(isset($this->nbt->TerrainPopulated)){
return $this->nbt->TerrainPopulated->getValue() > 0;
}
return false;
}
/**
* @param int $value
*/
public function setGenerated($value = 1){
$this->nbt->TerrainGenerated = new Byte("TerrainGenerated", $value);
$this->nbt->TerrainGenerated = new Byte("TerrainGenerated", (int) $value);
$this->hasChanged = true;
}
/**
@ -277,6 +292,70 @@ class Chunk extends BaseFullChunk{
return null;
}
}
public static function fromFastBinary($data, LevelProvider $provider = null){
try{
$offset = 0;
$chunk = new Chunk($provider instanceof LevelProvider ? $provider : McRegion::class, null);
$chunk->provider = $provider;
$chunk->x = Binary::readInt(substr($data, $offset, 4));
$offset += 4;
$chunk->z = Binary::readInt(substr($data, $offset, 4));
$offset += 4;
$chunk->blocks = substr($data, $offset, 32768);
$offset += 32768;
$chunk->data = substr($data, $offset, 16384);
$offset += 16384;
$chunk->skyLight = substr($data, $offset, 16384);
$offset += 16384;
$chunk->blockLight = substr($data, $offset, 16384);
$offset += 16384;
$chunk->biomeIds = substr($data, $offset, 256);
$offset += 256;
$chunk->biomeColors = [];
$chunk->heightMap = [];
$bc = unpack("N*", substr($data, $offset, 1024));
$offset += 1024;
$hm = unpack("N*", substr($data, $offset, 1024));
$offset += 1024;
for($i = 0; $i < 256; ++$i){
$chunk->biomeColors[$i] = $bc[$i + 1];
$chunk->heightMap[$i] = $hm[$i + 1];
}
$flags = ord($data{$offset++});
$chunk->nbt->TerrainGenerated = new Byte("TerrainGenerated", $flags & 0b1);
$chunk->nbt->TerrainPopulated = new Byte("TerrainPopulated", $flags >> 1);
return $chunk;
}catch(\Exception $e){
return null;
}
}
public function toFastBinary(){
$biomeColors = pack("N*", ...$this->getBiomeColorArray());
$heightMap = pack("N*", ...$this->getHeightMapArray());
return
Binary::writeInt($this->x) .
Binary::writeInt($this->z) .
$this->getBlockIdArray() .
$this->getBlockDataArray() .
$this->getBlockSkyLightArray() .
$this->getBlockLightArray() .
$this->getBiomeIdArray() .
$biomeColors .
$heightMap .
chr(($this->isPopulated() ? 1 << 1 : 0) + ($this->isGenerated() ? 1 : 0));
}
public function toBinary(){
$nbt = clone $this->getNBT();

View File

@ -44,7 +44,7 @@ class GenerationTask extends AsyncTask{
$this->settings = $generator->getSettings();
$this->seed = $level->getSeed();
$this->levelId = $level->getId();
$this->chunk = $chunk->toBinary();
$this->chunk = $chunk->toFastBinary();
$this->chunkClass = get_class($chunk);
}
@ -67,7 +67,7 @@ class GenerationTask extends AsyncTask{
/** @var FullChunk $chunk */
$chunk = $this->chunkClass;
$chunk = $chunk::fromBinary($this->chunk);
$chunk = $chunk::fromFastBinary($this->chunk);
if($chunk === null){
//TODO error
return;
@ -79,7 +79,7 @@ class GenerationTask extends AsyncTask{
$chunk = $manager->getChunk($chunk->getX(), $chunk->getZ());
$chunk->setGenerated(true);
$this->chunk = $chunk->toBinary();
$this->chunk = $chunk->toFastBinary();
$manager->setChunk($chunk->getX(), $chunk->getZ(), null);
}
@ -89,7 +89,7 @@ class GenerationTask extends AsyncTask{
if($level !== null){
/** @var FullChunk $chunk */
$chunk = $this->chunkClass;
$chunk = $chunk::fromBinary($this->chunk);
$chunk = $chunk::fromFastBinary($this->chunk, $level->getProvider());
if($chunk === null){
//TODO error
return;

View File

@ -0,0 +1,181 @@
<?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/
*
*
*/
namespace pocketmine\level\generator;
use pocketmine\block\Block;
use pocketmine\level\format\FullChunk;
use pocketmine\level\generator\biome\Biome;
use pocketmine\level\Level;
use pocketmine\level\SimpleChunkManager;
use pocketmine\scheduler\AsyncTask;
use pocketmine\Server;
use pocketmine\utils\Random;
class PopulationTask extends AsyncTask{
public $generator;
public $settings;
public $seed;
public $levelId;
public $chunk;
public $chunkClass;
public $chunk00;
public $chunk01;
public $chunk02;
public $chunk10;
//center chunk
public $chunk12;
public $chunk20;
public $chunk21;
public $chunk22;
public function __construct(Level $level, Generator $generator, FullChunk $chunk){
$this->generator = get_class($generator);
$this->settings = $generator->getSettings();
$this->seed = $level->getSeed();
$this->levelId = $level->getId();
$this->chunk = $chunk->toFastBinary();
$this->chunkClass = get_class($chunk);
$this->chunk00 = $level->getChunk($chunk->getX() - 1, $chunk->getZ() - 1, true)->toFastBinary();
$this->chunk01 = $level->getChunk($chunk->getX() - 1, $chunk->getZ(), true)->toFastBinary();
$this->chunk02 = $level->getChunk($chunk->getX() - 1, $chunk->getZ() + 1, true)->toFastBinary();
$this->chunk10 = $level->getChunk($chunk->getX(), $chunk->getZ() - 1, true)->toFastBinary();
$this->chunk12 = $level->getChunk($chunk->getX(), $chunk->getZ() + 1, true)->toFastBinary();
$this->chunk20 = $level->getChunk($chunk->getX() + 1, $chunk->getZ() - 1, true)->toFastBinary();
$this->chunk21 = $level->getChunk($chunk->getX() + 1, $chunk->getZ(), true)->toFastBinary();
$this->chunk22 = $level->getChunk($chunk->getX() + 1, $chunk->getZ() + 1, true)->toFastBinary();
}
public function onRun(){
/** @var SimpleChunkManager $manager */
$manager = $this->getFromThreadStore($key = "generation.level{$this->levelId}.manager");
/** @var Generator $generator */
$generator = $this->getFromThreadStore($gKey = "generation.level{$this->levelId}.generator");
if($manager === null or $generator === null){
Block::init();
Biome::init();
$manager = new SimpleChunkManager($this->seed);
$this->saveToThreadStore($key, $manager);
/** @var Generator $generator */
$generator = $this->generator;
$generator = new $generator($this->settings);
$generator->init($manager, new Random($manager->getSeed()));
$this->saveToThreadStore($gKey, $generator);
}
/** @var FullChunk[] $chunks */
$chunks = [];
/** @var FullChunk $chunkC */
$chunkC = $this->chunkClass;
$chunks[0] = $chunkC::fromFastBinary($this->chunk00);
$chunks[1] = $chunkC::fromFastBinary($this->chunk01);
$chunks[2] = $chunkC::fromFastBinary($this->chunk02);
$chunks[3] = $chunkC::fromFastBinary($this->chunk10);
$chunk = $chunkC::fromFastBinary($this->chunk);
$chunks[5] = $chunkC::fromFastBinary($this->chunk12);
$chunks[6] = $chunkC::fromFastBinary($this->chunk20);
$chunks[7] = $chunkC::fromFastBinary($this->chunk21);
$chunks[8] = $chunkC::fromFastBinary($this->chunk22);
if($chunk === null){
//TODO error
return;
}
$manager->setChunk($chunk->getX(), $chunk->getZ(), $chunk);
foreach($chunks as $c){
if($c !== null){
$manager->setChunk($c->getX(), $c->getZ(), $c);
}
}
$generator->populateChunk($chunk->getX(), $chunk->getZ());
$chunk = $manager->getChunk($chunk->getX(), $chunk->getZ());
$chunk->setPopulated(true);
$this->chunk = $chunk->toFastBinary();
$manager->setChunk($chunk->getX(), $chunk->getZ(), null);
foreach($chunks as $i => $c){
if($c !== null){
$c = $chunks[$i] = $manager->getChunk($c->getX(), $c->getZ());
if(!$c->hasChanged()){
$chunks[$i] = null;
}
$manager->setChunk($c->getX(), $c->getZ(), null);
}else{
//This way non-changed chunks are not set
$chunks[$i] = null;
}
}
$this->chunk00 = $chunks[0] !== null ? $chunks[0]->toFastBinary() : null;
$this->chunk01 = $chunks[1] !== null ? $chunks[1]->toFastBinary() : null;
$this->chunk02 = $chunks[2] !== null ? $chunks[2]->toFastBinary() : null;
$this->chunk10 = $chunks[3] !== null ? $chunks[3]->toFastBinary() : null;
$this->chunk12 = $chunks[5] !== null ? $chunks[5]->toFastBinary() : null;
$this->chunk20 = $chunks[6] !== null ? $chunks[6]->toFastBinary() : null;
$this->chunk21 = $chunks[7] !== null ? $chunks[7]->toFastBinary() : null;
$this->chunk22 = $chunks[8] !== null ? $chunks[8]->toFastBinary() : null;
}
public function onCompletion(Server $server){
$level = $server->getLevel($this->levelId);
if($level !== null){
/** @var FullChunk[] $chunks */
$chunks = [];
/** @var FullChunk $chunkC */
$chunkC = $this->chunkClass;
$chunks[0] = $chunkC::fromFastBinary($this->chunk00, $level->getProvider());
$chunks[1] = $chunkC::fromFastBinary($this->chunk01, $level->getProvider());
$chunks[2] = $chunkC::fromFastBinary($this->chunk02, $level->getProvider());
$chunks[3] = $chunkC::fromFastBinary($this->chunk10, $level->getProvider());
$chunk = $chunkC::fromFastBinary($this->chunk, $level->getProvider());
$chunks[5] = $chunkC::fromFastBinary($this->chunk12, $level->getProvider());
$chunks[6] = $chunkC::fromFastBinary($this->chunk20, $level->getProvider());
$chunks[7] = $chunkC::fromFastBinary($this->chunk21, $level->getProvider());
$chunks[8] = $chunkC::fromFastBinary($this->chunk22, $level->getProvider());
foreach($chunks as $c){
if($c !== null){
$level->generateChunkCallback($c->getX(), $c->getZ(), $c);
}
}
if($chunk === null){
//TODO error
return;
}
$level->generateChunkCallback($chunk->getX(), $chunk->getZ(), $chunk);
}
}
}

View File

@ -158,7 +158,6 @@ class Normal extends Generator{
$cover = new GroundCover();
$this->generationPopulators[] = $cover;
$ores = new Ore();
$ores->setOreTypes([
new OreType(new CoalOre(), 20, 16, 0, 128),
@ -168,7 +167,7 @@ class Normal extends Generator{
new OreType(new GoldOre(), 2, 8, 0, 32),
new OreType(new DiamondOre(), 1, 7, 0, 16),
new OreType(new Dirt(), 20, 32, 0, 128),
new OreType(new Gravel(), 10, 16, 0, 128),
new OreType(new Gravel(), 10, 16, 0, 128)
]);
$this->populators[] = $ores;
}
@ -252,7 +251,7 @@ class Normal extends Generator{
}
public function getSpawn(){
return $this->level->getSafeSpawn(new Vector3(127.5, 128, 127.5));
return new Vector3(127.5, 128, 127.5);
}
}

View File

@ -81,7 +81,9 @@ class Ore{
if(($sizeX + $sizeY + $sizeZ) < 1 and $level->getBlockIdAt($x, $y, $z) === 1){
$level->setBlockIdAt($x, $y, $z, $this->type->material->getId());
$level->setBlockDataAt($x, $y, $z, $this->type->material->getDamage());
if($this->type->material->getDamage() !== 0){
$level->setBlockDataAt($x, $y, $z, $this->type->material->getDamage());
}
}
}
}

View File

@ -114,6 +114,9 @@ class NBT{
$this->read(zlib_decode($buffer));
}
/**
* @return string|bool
*/
public function write(){
$this->offset = 0;
if($this->data instanceof Compound){

View File

@ -265,7 +265,7 @@ class RakLibInterface implements ServerInstance, AdvancedSourceInterface{
$pk = $packet->__encapsulatedPacket;
}
if(!$needACK and $packet->pid() !== ProtocolInfo::BATCH_PACKET
if(!$immediate and !$needACK and $packet->pid() !== ProtocolInfo::BATCH_PACKET
and Network::$BATCH_THRESHOLD >= 0
and strlen($packet->buffer) >= Network::$BATCH_THRESHOLD){
$this->batchedPackets[$this->identifiers[$player]] .= $packet->buffer;

View File

@ -51,7 +51,7 @@ chunk-sending:
chunk-ticking:
#Max amount of chunks processed each tick
per-tick: 24
per-tick: 40
#Radius of chunks around a player to tick
tick-radius: 3
#NOTE: This is currently not implemented
@ -59,11 +59,11 @@ chunk-ticking:
clear-tick-list: false
chunk-generation:
#Max. amount of chunks in the waiting queue ot be generated
#It's recommended to set this to 8 * settings.async-workers
queue-size: 16
#Max. amount of chunks to populate per tick
populations-per-tick: 1
#Max. amount of chunks in the waiting queue to be generated
#It's recommended to set this to 4 * settings.async-workers
queue-size: 8
#Max. amount of chunks in the waiting queue to be populated
population-queue-size: 2
chunk-gc:
period-in-ticks: 600

View File

@ -31,7 +31,7 @@ use pocketmine\utils\PluginException;
use pocketmine\utils\ReversePriorityQueue;
class ServerScheduler{
public static $WORKERS = 1;
public static $WORKERS = 2;
/**
* @var ReversePriorityQueue<Task>
*/