From a49842278abd6742ec652f954bf9c6f0766a4532 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 29 May 2023 17:44:00 +0100 Subject: [PATCH] WorldProvider subsystem no longer depends on Chunk Instead, it provides the data needed to construct the chunk, which doesn't require the provider to be aware of anywhere near as much logic. --- src/world/World.php | 15 ++++++++----- src/world/format/io/ChunkData.php | 13 ++++++++--- src/world/format/io/FormatConverter.php | 5 ++--- src/world/format/io/WritableWorldProvider.php | 2 +- src/world/format/io/leveldb/LevelDB.php | 22 ++++++++++--------- .../io/region/LegacyAnvilChunkTrait.php | 6 ++--- src/world/format/io/region/McRegion.php | 6 ++--- .../io/region/WritableRegionWorldProvider.php | 2 +- 8 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/world/World.php b/src/world/World.php index b6da5e95d..b2b6dfac2 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -1400,10 +1400,11 @@ class World implements ChunkManager{ foreach($this->chunks as $chunkHash => $chunk){ self::getXZ($chunkHash, $chunkX, $chunkZ); $this->provider->saveChunk($chunkX, $chunkZ, new ChunkData( - $chunk, + $chunk->getSubChunks(), + $chunk->isPopulated(), array_map(fn(Entity $e) => $e->saveNBT(), array_filter($this->getChunkEntities($chunkX, $chunkZ), fn(Entity $e) => $e->canSaveWithChunk())), array_map(fn(Tile $t) => $t->saveNBT(), $chunk->getTiles()), - )); + ), $chunk->getTerrainDirtyFlags()); $chunk->clearTerrainDirtyFlags(); } }finally{ @@ -2717,7 +2718,8 @@ class World implements ChunkManager{ return null; } - $chunk = $loadedChunkData->getData()->getChunk(); + $chunkData = $loadedChunkData->getData(); + $chunk = new Chunk($chunkData->getSubChunks(), $chunkData->isPopulated()); if(!$loadedChunkData->isUpgraded()){ $chunk->clearTerrainDirtyFlags(); }else{ @@ -2726,7 +2728,7 @@ class World implements ChunkManager{ $this->chunks[$chunkHash] = $chunk; unset($this->blockCache[$chunkHash]); - $this->initChunk($x, $z, $loadedChunkData->getData()); + $this->initChunk($x, $z, $chunkData); (new ChunkLoadEvent($this, $x, $z, $this->chunks[$chunkHash], false))->call(); @@ -2853,10 +2855,11 @@ class World implements ChunkManager{ $this->timings->syncChunkSave->startTiming(); try{ $this->provider->saveChunk($x, $z, new ChunkData( - $chunk, + $chunk->getSubChunks(), + $chunk->isPopulated(), array_map(fn(Entity $e) => $e->saveNBT(), array_filter($this->getChunkEntities($x, $z), fn(Entity $e) => $e->canSaveWithChunk())), array_map(fn(Tile $t) => $t->saveNBT(), $chunk->getTiles()), - )); + ), $chunk->getTerrainDirtyFlags()); }finally{ $this->timings->syncChunkSave->stopTiming(); } diff --git a/src/world/format/io/ChunkData.php b/src/world/format/io/ChunkData.php index edd28dcbf..458e00196 100644 --- a/src/world/format/io/ChunkData.php +++ b/src/world/format/io/ChunkData.php @@ -24,21 +24,28 @@ declare(strict_types=1); namespace pocketmine\world\format\io; use pocketmine\nbt\tag\CompoundTag; -use pocketmine\world\format\Chunk; +use pocketmine\world\format\SubChunk; final class ChunkData{ /** + * @param SubChunk[] $subChunks * @param CompoundTag[] $entityNBT * @param CompoundTag[] $tileNBT */ public function __construct( - private Chunk $chunk, + private array $subChunks, + private bool $populated, private array $entityNBT, private array $tileNBT ){} - public function getChunk() : Chunk{ return $this->chunk; } + /** + * @return SubChunk[] + */ + public function getSubChunks() : array{ return $this->subChunks; } + + public function isPopulated() : bool{ return $this->populated; } /** @return CompoundTag[] */ public function getEntityNBT() : array{ return $this->entityNBT; } diff --git a/src/world/format/io/FormatConverter.php b/src/world/format/io/FormatConverter.php index e318d7c62..2dcbe398e 100644 --- a/src/world/format/io/FormatConverter.php +++ b/src/world/format/io/FormatConverter.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace pocketmine\world\format\io; use pocketmine\utils\Filesystem; +use pocketmine\world\format\Chunk; use pocketmine\world\generator\GeneratorManager; use pocketmine\world\generator\normal\Normal; use pocketmine\world\WorldCreationOptions; @@ -142,9 +143,7 @@ class FormatConverter{ $thisRound = $start; foreach($this->oldProvider->getAllChunks(true, $this->logger) as $coords => $loadedChunkData){ [$chunkX, $chunkZ] = $coords; - $chunkData = $loadedChunkData->getData(); - $chunkData->getChunk()->setTerrainDirty(); - $new->saveChunk($chunkX, $chunkZ, $chunkData); + $new->saveChunk($chunkX, $chunkZ, $loadedChunkData->getData(), ~0); $counter++; if(($counter % $this->chunksPerProgressUpdate) === 0){ $time = microtime(true); diff --git a/src/world/format/io/WritableWorldProvider.php b/src/world/format/io/WritableWorldProvider.php index 8ed1b0c09..5c6344b52 100644 --- a/src/world/format/io/WritableWorldProvider.php +++ b/src/world/format/io/WritableWorldProvider.php @@ -27,5 +27,5 @@ interface WritableWorldProvider extends WorldProvider{ /** * Saves a chunk (usually to disk). */ - public function saveChunk(int $chunkX, int $chunkZ, ChunkData $chunkData) : void; + public function saveChunk(int $chunkX, int $chunkZ, ChunkData $chunkData, int $dirtyFlags) : void; } diff --git a/src/world/format/io/leveldb/LevelDB.php b/src/world/format/io/leveldb/LevelDB.php index 5f89800cc..c0ae51673 100644 --- a/src/world/format/io/leveldb/LevelDB.php +++ b/src/world/format/io/leveldb/LevelDB.php @@ -290,12 +290,15 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ return $result; } - private static function serialize3dBiomes(BinaryStream $stream, Chunk $chunk) : void{ + /** + * @param SubChunk[] $subChunks + */ + private static function serialize3dBiomes(BinaryStream $stream, array $subChunks) : void{ //TODO: the server-side min/max may not coincide with the world storage min/max - we may need additional logic to handle this for($y = Chunk::MIN_SUBCHUNK_INDEX; $y <= Chunk::MAX_SUBCHUNK_INDEX; $y++){ //TODO: is it worth trying to use the previous palette if it's the same as the current one? vanilla supports //this, but it's not clear if it's worth the effort to implement. - self::serializeBiomePalette($stream, $chunk->getSubChunk($y)->getBiomeArray()); + self::serializeBiomePalette($stream, $subChunks[$y]->getBiomeArray()); } } @@ -696,13 +699,13 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ //TODO: tile ticks, biome states (?) return new LoadedChunkData( - data: new ChunkData(new Chunk($subChunks, $terrainPopulated), $entities, $tiles), + data: new ChunkData($subChunks, $terrainPopulated, $entities, $tiles), upgraded: $hasBeenUpgraded, fixerFlags: LoadedChunkData::FIXER_FLAG_ALL //TODO: fill this by version rather than just setting all flags ); } - public function saveChunk(int $chunkX, int $chunkZ, ChunkData $chunkData) : void{ + public function saveChunk(int $chunkX, int $chunkZ, ChunkData $chunkData, int $dirtyFlags) : void{ $index = LevelDB::chunkIndex($chunkX, $chunkZ); $write = new \LevelDBWriteBatch(); @@ -710,10 +713,9 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ $write->put($index . ChunkDataKey::NEW_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION)); $write->put($index . ChunkDataKey::PM_DATA_VERSION, Binary::writeLLong(VersionInfo::WORLD_DATA_VERSION)); - $chunk = $chunkData->getChunk(); + $subChunks = $chunkData->getSubChunks(); - if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BLOCKS)){ - $subChunks = $chunk->getSubChunks(); + if(($dirtyFlags & Chunk::DIRTY_FLAG_BLOCKS) !== 0){ foreach($subChunks as $y => $subChunk){ $key = $index . ChunkDataKey::SUBCHUNK . chr($y); @@ -734,16 +736,16 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{ } } - if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BIOMES)){ + if(($dirtyFlags & Chunk::DIRTY_FLAG_BIOMES) !== 0){ $write->delete($index . ChunkDataKey::HEIGHTMAP_AND_2D_BIOMES); $stream = new BinaryStream(); $stream->put(str_repeat("\x00", 512)); //fake heightmap - self::serialize3dBiomes($stream, $chunk); + self::serialize3dBiomes($stream, $subChunks); $write->put($index . ChunkDataKey::HEIGHTMAP_AND_3D_BIOMES, $stream->getBuffer()); } //TODO: use this properly - $write->put($index . ChunkDataKey::FINALIZATION, chr($chunk->isPopulated() ? self::FINALISATION_DONE : self::FINALISATION_NEEDS_POPULATION)); + $write->put($index . ChunkDataKey::FINALIZATION, chr($chunkData->isPopulated() ? self::FINALISATION_DONE : self::FINALISATION_NEEDS_POPULATION)); $this->writeTags($chunkData->getTileNBT(), $index . ChunkDataKey::BLOCK_ENTITIES, $write); $this->writeTags($chunkData->getEntityNBT(), $index . ChunkDataKey::ENTITIES, $write); diff --git a/src/world/format/io/region/LegacyAnvilChunkTrait.php b/src/world/format/io/region/LegacyAnvilChunkTrait.php index 84bbb72f7..ffb7b585a 100644 --- a/src/world/format/io/region/LegacyAnvilChunkTrait.php +++ b/src/world/format/io/region/LegacyAnvilChunkTrait.php @@ -102,10 +102,8 @@ trait LegacyAnvilChunkTrait{ return new LoadedChunkData( data: new ChunkData( - new Chunk( - $subChunks, - $chunk->getByte("TerrainPopulated", 0) !== 0 - ), + $subChunks, + $chunk->getByte("TerrainPopulated", 0) !== 0, ($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [], ($tilesTag = $chunk->getTag("TileEntities")) instanceof ListTag ? self::getCompoundList("TileEntities", $tilesTag) : [], ), diff --git a/src/world/format/io/region/McRegion.php b/src/world/format/io/region/McRegion.php index 3ed185b1f..b596e33af 100644 --- a/src/world/format/io/region/McRegion.php +++ b/src/world/format/io/region/McRegion.php @@ -101,10 +101,8 @@ class McRegion extends RegionWorldProvider{ return new LoadedChunkData( data: new ChunkData( - new Chunk( - $subChunks, - $chunk->getByte("TerrainPopulated", 0) !== 0 - ), + $subChunks, + $chunk->getByte("TerrainPopulated", 0) !== 0, ($entitiesTag = $chunk->getTag("Entities")) instanceof ListTag ? self::getCompoundList("Entities", $entitiesTag) : [], ($tilesTag = $chunk->getTag("TileEntities")) instanceof ListTag ? self::getCompoundList("TileEntities", $tilesTag) : [], ), diff --git a/src/world/format/io/region/WritableRegionWorldProvider.php b/src/world/format/io/region/WritableRegionWorldProvider.php index 56cc2ff71..40353f5f9 100644 --- a/src/world/format/io/region/WritableRegionWorldProvider.php +++ b/src/world/format/io/region/WritableRegionWorldProvider.php @@ -53,7 +53,7 @@ abstract class WritableRegionWorldProvider extends RegionWorldProvider implement abstract protected function serializeChunk(ChunkData $chunk) : string; - public function saveChunk(int $chunkX, int $chunkZ, ChunkData $chunkData) : void{ + public function saveChunk(int $chunkX, int $chunkZ, ChunkData $chunkData, int $dirtyFlags) : void{ self::getRegionIndex($chunkX, $chunkZ, $regionX, $regionZ); $this->loadRegion($regionX, $regionZ)->writeChunk($chunkX & 0x1f, $chunkZ & 0x1f, $this->serializeChunk($chunkData)); }