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.
This commit is contained in:
Dylan K. Taylor 2023-05-29 17:44:00 +01:00
parent d57954dff0
commit a49842278a
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
8 changed files with 39 additions and 32 deletions

View File

@ -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();
}

View File

@ -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; }

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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) : [],
),

View File

@ -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) : [],
),

View File

@ -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));
}