diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 5f61aadfb..f90a4bcc3 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -46,6 +46,7 @@ use pocketmine\level\format\anvil\Anvil; use pocketmine\level\format\LevelProviderManager; use pocketmine\level\format\mcregion\McRegion; use pocketmine\level\generator\Flat; +use pocketmine\level\generator\GenerationInstanceManager; use pocketmine\level\generator\GenerationRequestManager; use pocketmine\level\generator\Generator; use pocketmine\level\generator\Normal; @@ -947,6 +948,8 @@ class Server{ $this->levels[$level->getID()] = $level; + $level->initLevel(); + $this->getPluginManager()->callEvent(new LevelLoadEvent($level)); /*foreach($entities->getAll() as $entity){ @@ -1080,6 +1083,8 @@ class Server{ $level = new Level($this, $name, $path, $provider); $this->levels[$level->getID()] = $level; + $level->initLevel(); + $this->getPluginManager()->callEvent(new LevelInitEvent($level)); $this->getPluginManager()->callEvent(new LevelLoadEvent($level)); @@ -1541,7 +1546,13 @@ class Server{ $this->enablePlugins(PluginLoadOrder::STARTUP); - $this->generationManager = new GenerationRequestManager($this); + if($this->getProperty("chunk-generation.use-async", true)){ + $this->getLogger()->info("Started on thread"); + $this->generationManager = new GenerationRequestManager($this); + }else{ + $this->getLogger()->info("Started on main"); + $this->generationManager = new GenerationInstanceManager($this); + } LevelProviderManager::addProvider($this, Anvil::class); LevelProviderManager::addProvider($this, McRegion::class); @@ -2068,7 +2079,7 @@ class Server{ } } - $this->generationManager->handlePackets(); + $this->generationManager->process(); Timings::$serverTickTimer->stopTiming(); diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 90389ada4..ed22c5316 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -203,6 +203,8 @@ class Level implements ChunkManager, Metadatable{ /** @var LevelTimings */ public $timings; + protected $generator; + /** * Returns the chunk unique hash/key * @@ -244,8 +246,7 @@ class Level implements ChunkManager, Metadatable{ throw new \Exception("Provider is not a subclass of LevelProvider"); } $this->server->getLogger()->info("Preparing level \"" . $this->provider->getName() . "\""); - $generator = Generator::getGenerator($this->provider->getGenerator()); - $this->server->getGenerationManager()->openLevel($this, $generator, $this->provider->getGeneratorOptions()); + $this->generator = Generator::getGenerator($this->provider->getGenerator()); $this->blockOrder = $provider::getProviderOrder(); $this->useSections = $provider::usesChunkSection(); @@ -260,7 +261,10 @@ class Level implements ChunkManager, Metadatable{ $this->chunkTickList = []; $this->clearChunksOnTick = (bool) $this->server->getProperty("chunk-ticking.clear-tick-list", false); $this->timings = new LevelTimings($this); + } + public function initLevel(){ + $this->server->getGenerationManager()->openLevel($this, $this->generator, $this->provider->getGeneratorOptions()); } /** diff --git a/src/pocketmine/level/generator/GenerationChunkManager.php b/src/pocketmine/level/generator/GenerationChunkManager.php index b66b36fc1..21db6b20c 100644 --- a/src/pocketmine/level/generator/GenerationChunkManager.php +++ b/src/pocketmine/level/generator/GenerationChunkManager.php @@ -81,7 +81,7 @@ class GenerationChunkManager implements ChunkManager{ $index = Level::chunkHash($chunkX, $chunkZ); $chunk = !isset($this->chunks[$index]) ? $this->requestChunk($chunkX, $chunkZ) : $this->chunks[$index]; if($chunk === null){ - throw new \Exception("null chunk received"); + throw new \Exception("null Chunk received"); } return $chunk; diff --git a/src/pocketmine/level/generator/GenerationInstanceManager.php b/src/pocketmine/level/generator/GenerationInstanceManager.php new file mode 100644 index 000000000..01d5ddd3e --- /dev/null +++ b/src/pocketmine/level/generator/GenerationInstanceManager.php @@ -0,0 +1,99 @@ +server = $server; + $this->generationManager = new GenerationLevelManager($this->server, $this); + } + + public function process(){ + $this->generationManager->process(); + } + + public function shutdown(){ + $this->generationManager->shutdown(); + } + + /** + * @param Level $level + * @param string $generator + * @param array $options + */ + public function openLevel(Level $level, $generator, array $options = []){ + $this->generationManager->openLevel($level->getID(), $level->getSeed(), $generator, $options); + } + + /** + * @param Level $level + */ + public function closeLevel(Level $level){ + $this->generationManager->closeLevel($level->getID()); + } + + public function addNamespace($namespace, $path){ + + } + + public function requestChunk(Level $level, $chunkX, $chunkZ){ + $this->generationManager->enqueueChunk($level->getID(), $chunkX, $chunkZ); + } + + public function getChunk($levelID, $chunkX, $chunkZ){ + if(($level = $this->server->getLevel($levelID)) instanceof Level){ + $chunk = $level->getChunk($chunkX, $chunkZ, true); + if($chunk instanceof FullChunk){ + return $chunk; + }else{ + throw new \Exception("Invalid Chunk given"); + } + }else{ + $this->generationManager->closeLevel($levelID); + return null; + } + } + + public function receiveChunk($levelID, FullChunk $chunk){ + if(($level = $this->server->getLevel($levelID)) instanceof Level){ + $level->generateChunkCallback($chunk->getX(), $chunk->getZ(), $chunk); + }else{ + $this->generationManager->closeLevel($levelID); + } + } + + +} \ No newline at end of file diff --git a/src/pocketmine/level/generator/GenerationLevelManager.php b/src/pocketmine/level/generator/GenerationLevelManager.php new file mode 100644 index 000000000..7e382920c --- /dev/null +++ b/src/pocketmine/level/generator/GenerationLevelManager.php @@ -0,0 +1,146 @@ +server = $server; + $this->manager = $manager; + $this->maxCount = $this->server->getProperty("chunk-generation.per-tick", 1); + } + + public function openLevel($levelID, $seed, $class, array $options){ + if(!isset($this->levels[$levelID])){ + $this->levels[$levelID] = new GenerationChunkManager($this, $levelID, $seed, $class, $options); + } + } + + public function generateChunk($levelID, $chunkX, $chunkZ){ + if(isset($this->levels[$levelID])){ + $this->levels[$levelID]->populateChunk($chunkX, $chunkZ); //Request population directly + if(isset($this->levels[$levelID])){ + foreach($this->levels[$levelID]->getChangedChunks() as $index => $chunk){ + if($chunk->isPopulated()){ + $this->sendChunk($levelID, $chunk); + $this->levels[$levelID]->cleanChangedChunk($index); + } + } + + $this->levels[$levelID]->doGarbageCollection(); + $this->levels[$levelID]->cleanChangedChunks(); + } + } + } + + public function process(){ + if(count($this->requestQueue) > 0){ + $count = 0; + foreach($this->requestQueue as $levelID => $chunks){ + if($count >= $this->maxCount){ + break; + } + + if(count($chunks) === 0){ + unset($this->requestQueue[$levelID]); + }else{ + Level::getXZ($key = key($chunks), $chunkX, $chunkZ); + unset($this->requestQueue[$levelID][$key]); + $this->generateChunk($levelID, $chunkX, $chunkZ); + ++$count; + } + } + } + } + + public function shutdown(){ + foreach($this->levels as $level){ + $level->shutdown(); + } + $this->levels = []; + } + + public function closeLevel($levelID){ + if(isset($this->levels[$levelID])){ + $this->levels[$levelID]->shutdown(); + unset($this->levels[$levelID]); + } + } + + public function enqueueChunk($levelID, $chunkX, $chunkZ){ + if(!isset($this->requestQueue[$levelID])){ + $this->requestQueue[$levelID] = []; + } + if(!isset($this->requestQueue[$levelID][$index = "$chunkX:$chunkZ"])){ + $this->requestQueue[$levelID][$index] = 1; + }else{ + $this->requestQueue[$levelID][$index]++; + arsort($this->requestQueue[$levelID]); + } + } + + /** + * @param $levelID + * @param $chunkX + * @param $chunkZ + * + * @return FullChunk + */ + public function requestChunk($levelID, $chunkX, $chunkZ){ + return $this->manager->getChunk($levelID, $chunkX, $chunkZ); + } + + public function sendChunk($levelID, FullChunk $chunk){ + $this->manager->receiveChunk($levelID, $chunk); + } + + /** + * @return \Logger + */ + public function getLogger(){ + return $this->server->getLogger(); + } + +} diff --git a/src/pocketmine/level/generator/GenerationManager.php b/src/pocketmine/level/generator/GenerationManager.php index 04b94d987..0c4b1af61 100644 --- a/src/pocketmine/level/generator/GenerationManager.php +++ b/src/pocketmine/level/generator/GenerationManager.php @@ -164,7 +164,7 @@ class GenerationManager{ } protected function closeLevel($levelID){ - if(!isset($this->levels[$levelID])){ + if(isset($this->levels[$levelID])){ $this->levels[$levelID]->shutdown(); unset($this->levels[$levelID]); } diff --git a/src/pocketmine/level/generator/GenerationRequestManager.php b/src/pocketmine/level/generator/GenerationRequestManager.php index 3cf1c1286..855a15323 100644 --- a/src/pocketmine/level/generator/GenerationRequestManager.php +++ b/src/pocketmine/level/generator/GenerationRequestManager.php @@ -99,6 +99,10 @@ class GenerationRequestManager{ } } + public function process(){ + $this->handlePackets(); + } + public function handlePackets(){ while(strlen($packet = $this->generationThread->readThreadToMainPacket()) > 0){ $pid = ord($packet{0}); diff --git a/src/pocketmine/resources/pocketmine.yml b/src/pocketmine/resources/pocketmine.yml index be7cf6b9a..4d697013e 100644 --- a/src/pocketmine/resources/pocketmine.yml +++ b/src/pocketmine/resources/pocketmine.yml @@ -46,6 +46,15 @@ chunk-ticking: light-updates: false clear-tick-list: false +chunk-generation: + #Whether to run the generation on a different thread or on the main thread + #Generation will be less glitchy on the main thread, but will lag more + #Using this with fast generators is recommended + #If enabled, the dedicated generation thread may leak memory + use-async: false + #Max. amount of chunks to generate per tick, only for use-async: true + per-tick: 1 + chunk-gc: period-in-ticks: 600