diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 53297b9c2..14ef602a7 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -345,7 +345,7 @@ class Level implements ChunkManager, Metadatable{ /** @var LevelProvider $provider */ if(is_subclass_of($provider, LevelProvider::class, true)){ - $this->provider = new $provider($this, $path); + $this->provider = new $provider($path); }else{ throw new LevelException("Provider is not a subclass of LevelProvider"); } @@ -1052,8 +1052,7 @@ class Level implements ChunkManager, Metadatable{ public function saveChunks(){ foreach($this->chunks as $chunk){ if($chunk->hasChanged() and $chunk->isGenerated()){ - $this->provider->setChunk($chunk->getX(), $chunk->getZ(), $chunk); - $this->provider->saveChunk($chunk->getX(), $chunk->getZ()); + $this->provider->saveChunk($chunk); $chunk->setChanged(false); } } @@ -2413,6 +2412,10 @@ class Level implements ChunkManager, Metadatable{ if($chunk === null){ return; } + + $chunk->setX($chunkX); + $chunk->setZ($chunkZ); + $chunkHash = Level::chunkHash($chunkX, $chunkZ); $oldChunk = $this->getChunk($chunkX, $chunkZ, false); if($unload and $oldChunk !== null){ @@ -2434,7 +2437,6 @@ class Level implements ChunkManager, Metadatable{ } } - $this->provider->setChunk($chunkX, $chunkZ, $chunk); $this->chunks[$chunkHash] = $chunk; unset($this->blockCache[$chunkHash]); @@ -2469,7 +2471,7 @@ class Level implements ChunkManager, Metadatable{ * @return bool */ public function isChunkLoaded(int $x, int $z) : bool{ - return isset($this->chunks[Level::chunkHash($x, $z)]) or $this->provider->isChunkLoaded($x, $z); + return isset($this->chunks[Level::chunkHash($x, $z)]); } /** @@ -2552,7 +2554,7 @@ class Level implements ChunkManager, Metadatable{ } $this->timings->syncChunkSendPrepareTimer->startTiming(); - $chunk = $this->provider->getChunk($x, $z, false); + $chunk = $this->chunks[$index] ?? null; if(!($chunk instanceof Chunk)){ throw new ChunkException("Invalid Chunk sent"); } @@ -2692,7 +2694,12 @@ class Level implements ChunkManager, Metadatable{ $this->cancelUnloadChunkRequest($x, $z); - $chunk = $this->provider->getChunk($x, $z, $generate); + $this->timings->syncChunkLoadDataTimer->startTiming(); + + $chunk = $this->provider->loadChunk($x, $z, $generate); + + $this->timings->syncChunkLoadDataTimer->stopTiming(); + if($chunk === null){ if($generate){ throw new \InvalidStateException("Could not create new Chunk"); @@ -2762,28 +2769,27 @@ class Level implements ChunkManager, Metadatable{ $this->server->getPluginManager()->callEvent($ev = new ChunkUnloadEvent($this, $chunk)); if($ev->isCancelled()){ $this->timings->doChunkUnload->stopTiming(); + return false; } - } - try{ - if($chunk !== null){ + try{ if($trySave and $this->getAutoSave() and $chunk->isGenerated()){ if($chunk->hasChanged() or count($chunk->getTiles()) > 0 or count($chunk->getSavableEntities()) > 0){ - $this->provider->setChunk($x, $z, $chunk); - $this->provider->saveChunk($x, $z); + $this->provider->saveChunk($chunk); } } foreach($this->getChunkLoaders($x, $z) as $loader){ $loader->onChunkUnloaded($chunk); } + + $chunk->onUnload(); + }catch(\Throwable $e){ + $logger = $this->server->getLogger(); + $logger->error($this->server->getLanguage()->translateString("pocketmine.level.chunkUnloadError", [$e->getMessage()])); + $logger->logException($e); } - $this->provider->unloadChunk($x, $z, $safe); - }catch(\Throwable $e){ - $logger = $this->server->getLogger(); - $logger->error($this->server->getLanguage()->translateString("pocketmine.level.chunkUnloadError", [$e->getMessage()])); - $logger->logException($e); } unset($this->chunks[$chunkHash]); @@ -3039,12 +3045,6 @@ class Level implements ChunkManager, Metadatable{ } } - foreach($this->provider->getLoadedChunks() as $chunk){ - if(!isset($this->chunks[Level::chunkHash($chunk->getX(), $chunk->getZ())])){ - $this->provider->unloadChunk($chunk->getX(), $chunk->getZ(), false); - } - } - $this->provider->doGarbageCollection(); $this->timings->doChunkGC->stopTiming(); diff --git a/src/pocketmine/level/format/Chunk.php b/src/pocketmine/level/format/Chunk.php index bef570be9..3336a341a 100644 --- a/src/pocketmine/level/format/Chunk.php +++ b/src/pocketmine/level/format/Chunk.php @@ -691,21 +691,9 @@ class Chunk{ } /** - * Unloads the chunk, closing entities and tiles. - * - * @param bool $safe Whether to check if there are still players using this chunk - * - * @return bool + * Called when the chunk is unloaded, closing entities and tiles. */ - public function unload(bool $safe = true) : bool{ - if($safe){ - foreach($this->getEntities() as $entity){ - if($entity instanceof Player){ - return false; - } - } - } - + public function onUnload() : void{ foreach($this->getEntities() as $entity){ if($entity instanceof Player){ continue; @@ -716,8 +704,6 @@ class Chunk{ foreach($this->getTiles() as $tile){ $tile->close(); } - - return true; } /** diff --git a/src/pocketmine/level/format/io/BaseLevelProvider.php b/src/pocketmine/level/format/io/BaseLevelProvider.php index 24fc3e86b..3e04f0711 100644 --- a/src/pocketmine/level/format/io/BaseLevelProvider.php +++ b/src/pocketmine/level/format/io/BaseLevelProvider.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace pocketmine\level\format\io; +use pocketmine\level\format\Chunk; use pocketmine\level\generator\Generator; use pocketmine\level\Level; use pocketmine\level\LevelException; @@ -32,15 +33,12 @@ use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\StringTag; abstract class BaseLevelProvider implements LevelProvider{ - /** @var Level */ - protected $level; /** @var string */ protected $path; /** @var CompoundTag */ protected $levelData; - public function __construct(Level $level, string $path){ - $this->level = $level; + public function __construct(string $path){ $this->path = $path; if(!file_exists($this->path)){ mkdir($this->path, 0777, true); @@ -67,14 +65,6 @@ abstract class BaseLevelProvider implements LevelProvider{ return $this->path; } - public function getServer(){ - return $this->level->getServer(); - } - - public function getLevel() : Level{ - return $this->level; - } - public function getName() : string{ return $this->levelData->getString("LevelName"); } @@ -124,4 +114,24 @@ abstract class BaseLevelProvider implements LevelProvider{ $buffer = $nbt->writeCompressed(); file_put_contents($this->getPath() . "level.dat", $buffer); } + + public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : ?Chunk{ + $chunk = $this->readChunk($chunkX, $chunkZ); + if($chunk === null and $create){ + $chunk = new Chunk($chunkX, $chunkZ); + } + + return $chunk; + } + + public function saveChunk(Chunk $chunk) : void{ + if(!$chunk->isGenerated()){ + throw new \InvalidStateException("Cannot save un-generated chunk"); + } + $this->writeChunk($chunk); + } + + abstract protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk; + + abstract protected function writeChunk(Chunk $chunk) : void; } diff --git a/src/pocketmine/level/format/io/LevelProvider.php b/src/pocketmine/level/format/io/LevelProvider.php index bdc3a75c4..c914084fc 100644 --- a/src/pocketmine/level/format/io/LevelProvider.php +++ b/src/pocketmine/level/format/io/LevelProvider.php @@ -24,16 +24,14 @@ declare(strict_types=1); namespace pocketmine\level\format\io; use pocketmine\level\format\Chunk; -use pocketmine\level\Level; use pocketmine\math\Vector3; interface LevelProvider{ /** - * @param Level $level * @param string $path */ - public function __construct(Level $level, string $path); + public function __construct(string $path); /** * Returns the full provider name, like "anvil" or "mcregion", will be used to find the correct format. @@ -88,77 +86,23 @@ interface LevelProvider{ public function getGeneratorOptions() : array; /** - * Gets the Chunk object - * This method must be implemented by all the level formats. + * Saves a chunk (usually to disk). * - * @param int $chunkX - * @param int $chunkZ - * @param bool $create - * - * @return Chunk|null - */ - public function getChunk(int $chunkX, int $chunkZ, bool $create = false); - - /** - * @param int $chunkX - * @param int $chunkZ * @param Chunk $chunk */ - public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk); + public function saveChunk(Chunk $chunk) : void; /** - * @param int $chunkX - * @param int $chunkZ + * Loads a chunk (usually from disk storage) and returns it. If the chunk does not exist, null is returned, or an + * empty Chunk if $create is specified. * - * @return bool - */ - public function saveChunk(int $chunkX, int $chunkZ) : bool; - - public function saveChunks(); - - /** * @param int $chunkX * @param int $chunkZ * @param bool $create * - * @return bool + * @return null|Chunk */ - public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : bool; - - /** - * @param int $chunkX - * @param int $chunkZ - * @param bool $safe - * - * @return bool - */ - public function unloadChunk(int $chunkX, int $chunkZ, bool $safe = true) : bool; - - public function unloadChunks(); - - /** - * @param int $chunkX - * @param int $chunkZ - * - * @return bool - */ - public function isChunkLoaded(int $chunkX, int $chunkZ) : bool; - - /** - * @param int $chunkX - * @param int $chunkZ - * - * @return bool - */ - public function isChunkGenerated(int $chunkX, int $chunkZ) : bool; - - /** - * @param int $chunkX - * @param int $chunkZ - * - * @return bool - */ - public function isChunkPopulated(int $chunkX, int $chunkZ) : bool; + public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : ?Chunk; /** * @return string @@ -208,17 +152,13 @@ interface LevelProvider{ public function setDifficulty(int $difficulty); /** - * @return Chunk[] + * Performs garbage collection in the level provider, such as cleaning up regions in Region-based worlds. */ - public function getLoadedChunks() : array; - public function doGarbageCollection(); /** - * @return Level + * Performs cleanups necessary when the level provider is closed and no longer needed. */ - public function getLevel() : Level; - public function close(); } diff --git a/src/pocketmine/level/format/io/leveldb/LevelDB.php b/src/pocketmine/level/format/io/leveldb/LevelDB.php index 3c58396dd..c13292af6 100644 --- a/src/pocketmine/level/format/io/leveldb/LevelDB.php +++ b/src/pocketmine/level/format/io/leveldb/LevelDB.php @@ -74,9 +74,6 @@ class LevelDB extends BaseLevelProvider{ public const CURRENT_LEVEL_CHUNK_VERSION = 7; public const CURRENT_LEVEL_SUBCHUNK_VERSION = 0; - /** @var Chunk[] */ - protected $chunks = []; - /** @var \LevelDB */ protected $db; @@ -90,10 +87,9 @@ class LevelDB extends BaseLevelProvider{ } } - public function __construct(Level $level, string $path){ + public function __construct(string $path){ self::checkForLevelDBExtension(); - $this->level = $level; $this->path = $path; if(!file_exists($this->path)){ mkdir($this->path, 0777, true); @@ -249,13 +245,6 @@ class LevelDB extends BaseLevelProvider{ file_put_contents($this->getPath() . "level.dat", Binary::writeLInt(self::CURRENT_STORAGE_VERSION) . Binary::writeLInt(strlen($buffer)) . $buffer); } - public function unloadChunks(){ - foreach($this->chunks as $chunk){ - $this->unloadChunk($chunk->getX(), $chunk->getZ(), false); - } - $this->chunks = []; - } - public function getGenerator() : string{ return (string) $this->levelData["generatorName"]; } @@ -272,48 +261,13 @@ class LevelDB extends BaseLevelProvider{ $this->levelData->setInt("Difficulty", $difficulty); //yes, this is intended! (in PE: int, PC: byte) } - public function getLoadedChunks() : array{ - return $this->chunks; - } - - public function isChunkLoaded(int $x, int $z) : bool{ - return isset($this->chunks[Level::chunkHash($x, $z)]); - } - - public function saveChunks(){ - foreach($this->chunks as $chunk){ - $this->saveChunk($chunk->getX(), $chunk->getZ()); - } - } - - public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : bool{ - if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)])){ - return true; - } - - $this->level->timings->syncChunkLoadDataTimer->startTiming(); - $chunk = $this->readChunk($chunkX, $chunkZ); - if($chunk === null and $create){ - $chunk = new Chunk($chunkX, $chunkZ); - } - $this->level->timings->syncChunkLoadDataTimer->stopTiming(); - - if($chunk !== null){ - $this->chunks[$index] = $chunk; - - return true; - }else{ - return false; - } - } - /** * @param int $chunkX * @param int $chunkZ * * @return Chunk|null */ - private function readChunk($chunkX, $chunkZ){ + protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk{ $index = LevelDB::chunkIndex($chunkX, $chunkZ); if(!$this->chunkExists($chunkX, $chunkZ)){ @@ -484,7 +438,7 @@ class LevelDB extends BaseLevelProvider{ } } - private function writeChunk(Chunk $chunk){ + protected function writeChunk(Chunk $chunk) : void{ $index = LevelDB::chunkIndex($chunk->getX(), $chunk->getZ()); $this->db->put($index . self::TAG_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION)); @@ -557,49 +511,6 @@ class LevelDB extends BaseLevelProvider{ } } - public function unloadChunk(int $x, int $z, bool $safe = true) : bool{ - $chunk = $this->chunks[$index = Level::chunkHash($x, $z)] ?? null; - if($chunk instanceof Chunk and $chunk->unload($safe)){ - unset($this->chunks[$index]); - - return true; - } - - return false; - } - - public function saveChunk(int $chunkX, int $chunkZ) : bool{ - if($this->isChunkLoaded($chunkX, $chunkZ)){ - $chunk = $this->getChunk($chunkX, $chunkZ); - if(!$chunk->isGenerated()){ - throw new \InvalidStateException("Cannot save un-generated chunk"); - } - $this->writeChunk($chunk); - - return true; - } - - return false; - } - - /** - * @param int $chunkX - * @param int $chunkZ - * @param bool $create - * - * @return Chunk|null - */ - public function getChunk(int $chunkX, int $chunkZ, bool $create = false){ - $index = Level::chunkHash($chunkX, $chunkZ); - if(isset($this->chunks[$index])){ - return $this->chunks[$index]; - }else{ - $this->loadChunk($chunkX, $chunkZ, $create); - - return $this->chunks[$index] ?? null; - } - } - /** * @return \LevelDB */ @@ -607,17 +518,6 @@ class LevelDB extends BaseLevelProvider{ return $this->db; } - public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk){ - $chunk->setX($chunkX); - $chunk->setZ($chunkZ); - - if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)]) and $this->chunks[$index] !== $chunk){ - $this->unloadChunk($chunkX, $chunkZ, false); - } - - $this->chunks[$index] = $chunk; - } - public static function chunkIndex(int $chunkX, int $chunkZ) : string{ return Binary::writeLInt($chunkX) . Binary::writeLInt($chunkZ); } @@ -626,22 +526,7 @@ class LevelDB extends BaseLevelProvider{ return $this->db->get(LevelDB::chunkIndex($chunkX, $chunkZ) . self::TAG_VERSION) !== false; } - public function isChunkGenerated(int $chunkX, int $chunkZ) : bool{ - return $this->chunkExists($chunkX, $chunkZ) and ($chunk = $this->getChunk($chunkX, $chunkZ, false)) !== null; - } - - public function isChunkPopulated(int $chunkX, int $chunkZ) : bool{ - $chunk = $this->getChunk($chunkX, $chunkZ); - if($chunk instanceof Chunk){ - return $chunk->isPopulated(); - }else{ - return false; - } - } - public function close(){ - $this->unloadChunks(); $this->db->close(); - $this->level = null; } } diff --git a/src/pocketmine/level/format/io/region/McRegion.php b/src/pocketmine/level/format/io/region/McRegion.php index 4d31e7eec..01401da2e 100644 --- a/src/pocketmine/level/format/io/region/McRegion.php +++ b/src/pocketmine/level/format/io/region/McRegion.php @@ -44,9 +44,6 @@ class McRegion extends BaseLevelProvider{ /** @var RegionLoader[] */ protected $regions = []; - /** @var Chunk[] */ - protected $chunks = []; - /** * @param Chunk $chunk * @@ -283,126 +280,6 @@ class McRegion extends BaseLevelProvider{ $this->levelData->setByte("Difficulty", $difficulty); } - public function getChunk(int $chunkX, int $chunkZ, bool $create = false){ - $index = Level::chunkHash($chunkX, $chunkZ); - if(isset($this->chunks[$index])){ - return $this->chunks[$index]; - }else{ - $this->loadChunk($chunkX, $chunkZ, $create); - - return $this->chunks[$index] ?? null; - } - } - - public function setChunk(int $chunkX, int $chunkZ, Chunk $chunk){ - self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); - $this->loadRegion($regionX, $regionZ); - - $chunk->setX($chunkX); - $chunk->setZ($chunkZ); - - - if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)]) and $this->chunks[$index] !== $chunk){ - $this->unloadChunk($chunkX, $chunkZ, false); - } - - $this->chunks[$index] = $chunk; - } - - public function saveChunk(int $chunkX, int $chunkZ) : bool{ - if($this->isChunkLoaded($chunkX, $chunkZ)){ - $chunk = $this->getChunk($chunkX, $chunkZ); - if(!$chunk->isGenerated()){ - throw new \InvalidStateException("Cannot save un-generated chunk"); - } - $this->getRegion($chunkX >> 5, $chunkZ >> 5)->writeChunk($chunkX & 0x1f, $chunkZ & 0x1f, $this->nbtSerialize($chunk)); - - return true; - } - - return false; - } - - public function saveChunks(){ - foreach($this->chunks as $chunk){ - $this->saveChunk($chunk->getX(), $chunk->getZ()); - } - } - - public function loadChunk(int $chunkX, int $chunkZ, bool $create = false) : bool{ - $index = Level::chunkHash($chunkX, $chunkZ); - if(isset($this->chunks[$index])){ - return true; - } - $regionX = $regionZ = null; - self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); - assert(is_int($regionX) and is_int($regionZ)); - - $this->loadRegion($regionX, $regionZ); - $this->level->timings->syncChunkLoadDataTimer->startTiming(); - - $chunk = null; - - $chunkData = $this->getRegion($regionX, $regionZ)->readChunk($chunkX & 0x1f, $chunkZ & 0x1f); - if($chunkData !== null){ - $chunk = $this->nbtDeserialize($chunkData); - } - - if($chunk === null and $create){ - $chunk = new Chunk($chunkX, $chunkZ); - } - $this->level->timings->syncChunkLoadDataTimer->stopTiming(); - - if($chunk !== null){ - $this->chunks[$index] = $chunk; - return true; - }else{ - return false; - } - } - - public function unloadChunk(int $chunkX, int $chunkZ, bool $safe = true) : bool{ - $chunk = $this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)] ?? null; - if($chunk instanceof Chunk and $chunk->unload($safe)){ - unset($this->chunks[$index]); - return true; - } - - return false; - } - - public function unloadChunks(){ - foreach($this->chunks as $chunk){ - $this->unloadChunk($chunk->getX(), $chunk->getZ(), false); - } - $this->chunks = []; - } - - public function isChunkLoaded(int $chunkX, int $chunkZ) : bool{ - return isset($this->chunks[Level::chunkHash($chunkX, $chunkZ)]); - } - - public function isChunkGenerated(int $chunkX, int $chunkZ) : bool{ - if(($region = $this->getRegion($chunkX >> 5, $chunkZ >> 5)) !== null){ - return $region->chunkExists($chunkX & 0x1f, $chunkZ & 0x1f) and $this->getChunk($chunkX & 0x1f, $chunkZ & 0x1f, true)->isGenerated(); - } - - return false; - } - - public function isChunkPopulated(int $chunkX, int $chunkZ) : bool{ - $chunk = $this->getChunk($chunkX, $chunkZ); - if($chunk !== null){ - return $chunk->isPopulated(); - }else{ - return false; - } - } - - public function getLoadedChunks() : array{ - return $this->chunks; - } - public function doGarbageCollection(){ $limit = time() - 300; foreach($this->regions as $index => $region){ @@ -473,11 +350,34 @@ class McRegion extends BaseLevelProvider{ } public function close(){ - $this->unloadChunks(); foreach($this->regions as $index => $region){ $region->close(); unset($this->regions[$index]); } - $this->level = null; + } + + protected function readChunk(int $chunkX, int $chunkZ) : ?Chunk{ + $regionX = $regionZ = null; + self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); + assert(is_int($regionX) and is_int($regionZ)); + + $this->loadRegion($regionX, $regionZ); + + $chunkData = $this->getRegion($regionX, $regionZ)->readChunk($chunkX & 0x1f, $chunkZ & 0x1f); + if($chunkData !== null){ + return $this->nbtDeserialize($chunkData); + } + + return null; + } + + protected function writeChunk(Chunk $chunk) : void{ + $chunkX = $chunk->getX(); + $chunkZ = $chunk->getZ(); + + self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); + $this->loadRegion($regionX, $regionZ); + + $this->getRegion($regionX, $regionZ)->writeChunk($chunkX & 0x1f, $chunkZ & 0x1f, $this->nbtSerialize($chunk)); } } diff --git a/tests/preprocessor b/tests/preprocessor index 316fd5606..726930e59 160000 --- a/tests/preprocessor +++ b/tests/preprocessor @@ -1 +1 @@ -Subproject commit 316fd5606fef1191e7f228d3156dd1518c33aba4 +Subproject commit 726930e597fd4dd4c9941a8e3dfdb7b99d78937b