Chunk: Drop dirty flags for tiles and entities

instead, just ungate this and allow the provider to decide what to do.
Any chunk that contains entities or tiles is already always considered dirty, so the only thing the flags are good for is flagging chunks that previously had tiles and/or entities but no longer do.
In those cases, it's just removing keys from LevelDB anyway, so it's already very cheap.
Avoiding these redundant deletions is not worth the extra complexity and fragility of relying on flags to track this stuff.
This commit is contained in:
Dylan K. Taylor 2021-08-30 00:09:36 +01:00
parent 4b06e19d28
commit 0289b45202
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
6 changed files with 37 additions and 52 deletions

View File

@ -1126,15 +1126,13 @@ class World implements ChunkManager{
$this->timings->syncChunkSave->startTiming();
try{
foreach($this->chunks as $chunkHash => $chunk){
if($chunk->isDirty()){
self::getXZ($chunkHash, $chunkX, $chunkZ);
$this->provider->saveChunk($chunkX, $chunkZ, new ChunkData(
$chunk,
array_map(fn(Entity $e) => $e->saveNBT(), $chunk->getSavableEntities()),
array_map(fn(Tile $t) => $t->saveNBT(), $chunk->getTiles()),
));
$chunk->clearDirtyFlags();
}
self::getXZ($chunkHash, $chunkX, $chunkZ);
$this->provider->saveChunk($chunkX, $chunkZ, new ChunkData(
$chunk,
array_map(fn(Entity $e) => $e->saveNBT(), $chunk->getSavableEntities()),
array_map(fn(Tile $t) => $t->saveNBT(), $chunk->getTiles()),
));
$chunk->clearTerrainDirtyFlags();
}
}finally{
$this->timings->syncChunkSave->stopTiming();
@ -2186,7 +2184,7 @@ class World implements ChunkManager{
unset($this->blockCache[$chunkHash]);
unset($this->changedBlocks[$chunkHash]);
$chunk->setDirty();
$chunk->setTerrainDirty();
if(!$this->isChunkInUse($chunkX, $chunkZ)){
$this->unloadChunkRequest($chunkX, $chunkZ);
@ -2494,7 +2492,6 @@ class World implements ChunkManager{
//here, because entities currently add themselves to the world
}
$this->getChunk($chunkX, $chunkZ)?->setDirtyFlag(Chunk::DIRTY_FLAG_ENTITIES, true);
$this->timings->syncChunkLoadEntities->stopTiming();
}
@ -2518,7 +2515,6 @@ class World implements ChunkManager{
}
}
$this->getChunk($chunkX, $chunkZ)?->setDirtyFlag(Chunk::DIRTY_FLAG_TILES, true);
$this->timings->syncChunkLoadTileEntities->stopTiming();
}
}
@ -2565,7 +2561,7 @@ class World implements ChunkManager{
return false;
}
if($trySave and $this->getAutoSave() and $chunk->isDirty()){
if($trySave and $this->getAutoSave()){
$this->timings->syncChunkSave->startTiming();
try{
$this->provider->saveChunk($x, $z, new ChunkData(

View File

@ -35,18 +35,15 @@ use pocketmine\player\Player;
use function array_fill;
use function array_filter;
use function array_map;
use function count;
class Chunk{
public const DIRTY_FLAG_TERRAIN = 1 << 0;
public const DIRTY_FLAG_ENTITIES = 1 << 1;
public const DIRTY_FLAG_TILES = 1 << 2;
public const DIRTY_FLAG_BIOMES = 1 << 3;
public const MAX_SUBCHUNKS = 16;
/** @var int */
private $dirtyFlags = 0;
private $terrainDirtyFlags = 0;
/** @var bool|null */
protected $lightPopulated = false;
@ -111,7 +108,7 @@ class Chunk{
*/
public function setFullBlock(int $x, int $y, int $z, int $block) : void{
$this->getSubChunk($y >> 4)->setFullBlock($x, $y & 0xf, $z, $block);
$this->dirtyFlags |= self::DIRTY_FLAG_TERRAIN;
$this->terrainDirtyFlags |= self::DIRTY_FLAG_TERRAIN;
}
/**
@ -174,7 +171,7 @@ class Chunk{
*/
public function setBiomeId(int $x, int $z, int $biomeId) : void{
$this->biomeIds->set($x, $z, $biomeId);
$this->dirtyFlags |= self::DIRTY_FLAG_BIOMES;
$this->terrainDirtyFlags |= self::DIRTY_FLAG_BIOMES;
}
public function isLightPopulated() : ?bool{
@ -191,7 +188,7 @@ class Chunk{
public function setPopulated(bool $value = true) : void{
$this->terrainPopulated = $value;
$this->dirtyFlags |= self::DIRTY_FLAG_TERRAIN;
$this->terrainDirtyFlags |= self::DIRTY_FLAG_TERRAIN;
}
public function addEntity(Entity $entity) : void{
@ -199,16 +196,10 @@ class Chunk{
throw new \InvalidArgumentException("Attempted to add a garbage closed Entity to a chunk");
}
$this->entities[$entity->getId()] = $entity;
if(!($entity instanceof Player)){
$this->dirtyFlags |= self::DIRTY_FLAG_ENTITIES;
}
}
public function removeEntity(Entity $entity) : void{
unset($this->entities[$entity->getId()]);
if(!($entity instanceof Player)){
$this->dirtyFlags |= self::DIRTY_FLAG_ENTITIES;
}
}
public function addTile(Tile $tile) : void{
@ -221,13 +212,11 @@ class Chunk{
throw new \InvalidArgumentException("Another tile is already at this location");
}
$this->tiles[$index] = $tile;
$this->dirtyFlags |= self::DIRTY_FLAG_TILES;
}
public function removeTile(Tile $tile) : void{
$pos = $tile->getPosition();
unset($this->tiles[Chunk::blockHash($pos->x, $pos->y, $pos->z)]);
$this->dirtyFlags |= self::DIRTY_FLAG_TILES;
}
/**
@ -298,32 +287,32 @@ class Chunk{
$this->heightMap = new HeightArray($values);
}
public function isDirty() : bool{
return $this->dirtyFlags !== 0 or count($this->tiles) > 0 or count($this->getSavableEntities()) > 0;
public function isTerrainDirty() : bool{
return $this->terrainDirtyFlags !== 0;
}
public function getDirtyFlag(int $flag) : bool{
return ($this->dirtyFlags & $flag) !== 0;
public function getTerrainDirtyFlag(int $flag) : bool{
return ($this->terrainDirtyFlags & $flag) !== 0;
}
public function getDirtyFlags() : int{
return $this->dirtyFlags;
public function getTerrainDirtyFlags() : int{
return $this->terrainDirtyFlags;
}
public function setDirtyFlag(int $flag, bool $value) : void{
public function setTerrainDirtyFlag(int $flag, bool $value) : void{
if($value){
$this->dirtyFlags |= $flag;
$this->terrainDirtyFlags |= $flag;
}else{
$this->dirtyFlags &= ~$flag;
$this->terrainDirtyFlags &= ~$flag;
}
}
public function setDirty() : void{
$this->dirtyFlags = ~0;
public function setTerrainDirty() : void{
$this->terrainDirtyFlags = ~0;
}
public function clearDirtyFlags() : void{
$this->dirtyFlags = 0;
public function clearTerrainDirtyFlags() : void{
$this->terrainDirtyFlags = 0;
}
public function getSubChunk(int $y) : SubChunk{
@ -342,7 +331,7 @@ class Chunk{
}
$this->subChunks[$y] = $subChunk ?? new SubChunk(BlockLegacyIds::AIR << Block::INTERNAL_METADATA_BITS, []);
$this->setDirtyFlag(self::DIRTY_FLAG_TERRAIN, true);
$this->setTerrainDirtyFlag(self::DIRTY_FLAG_TERRAIN, true);
}
/**

View File

@ -146,7 +146,7 @@ final class FastChunkSerializer{
$chunk = new Chunk($subChunks, $biomeIds, $heightMap);
$chunk->setPopulated($terrainPopulated);
$chunk->setLightPopulated($lightPopulated);
$chunk->clearDirtyFlags();
$chunk->clearTerrainDirtyFlags();
return $chunk;
}

View File

@ -149,7 +149,7 @@ class FormatConverter{
$thisRound = $start;
foreach($this->oldProvider->getAllChunks(true, $this->logger) as $coords => $chunk){
[$chunkX, $chunkZ] = $coords;
$chunk->getChunk()->setDirty();
$chunk->getChunk()->setTerrainDirty();
$new->saveChunk($chunkX, $chunkZ, $chunk);
$counter++;
if(($counter % $this->chunksPerProgressUpdate) === 0){

View File

@ -419,7 +419,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$chunk->setPopulated();
}
if($hasBeenUpgraded){
$chunk->setDirty(); //trigger rewriting chunk to disk if it was converted from an older format
$chunk->setTerrainDirty(); //trigger rewriting chunk to disk if it was converted from an older format
}
return new ChunkData($chunk, $entities, $tiles);
@ -433,7 +433,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$write->put($index . self::TAG_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
$chunk = $chunkData->getChunk();
if($chunk->getDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN)){
if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN)){
$subChunks = $chunk->getSubChunks();
foreach($subChunks as $y => $subChunk){
$key = $index . self::TAG_SUBCHUNK_PREFIX . chr($y);
@ -467,7 +467,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
}
}
if($chunk->getDirtyFlag(Chunk::DIRTY_FLAG_BIOMES)){
if($chunk->getTerrainDirtyFlag(Chunk::DIRTY_FLAG_BIOMES)){
$write->put($index . self::TAG_DATA_2D, str_repeat("\x00", 512) . $chunk->getBiomeIdArray());
}

View File

@ -106,8 +106,8 @@ class PopulationTask extends AsyncTask{
if($chunk === null){
$generator->generateChunk($manager, $this->chunkX, $this->chunkZ);
$chunk = $manager->getChunk($this->chunkX, $this->chunkZ);
$chunk->setDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN, true);
$chunk->setDirtyFlag(Chunk::DIRTY_FLAG_BIOMES, true);
$chunk->setTerrainDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN, true);
$chunk->setTerrainDirtyFlag(Chunk::DIRTY_FLAG_BIOMES, true);
}
foreach($chunks as $i => $c){
@ -117,8 +117,8 @@ class PopulationTask extends AsyncTask{
if($c === null){
$generator->generateChunk($manager, $cX, $cZ);
$chunks[$i] = $manager->getChunk($cX, $cZ);
$chunks[$i]->setDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN, true);
$chunks[$i]->setDirtyFlag(Chunk::DIRTY_FLAG_BIOMES, true);
$chunks[$i]->setTerrainDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN, true);
$chunks[$i]->setTerrainDirtyFlag(Chunk::DIRTY_FLAG_BIOMES, true);
}
}
@ -129,7 +129,7 @@ class PopulationTask extends AsyncTask{
$this->chunk = FastChunkSerializer::serializeWithoutLight($chunk);
foreach($chunks as $i => $c){
$this->{"chunk$i"} = $c->isDirty() ? FastChunkSerializer::serializeWithoutLight($c) : null;
$this->{"chunk$i"} = $c->isTerrainDirty() ? FastChunkSerializer::serializeWithoutLight($c) : null;
}
}