mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-15 02:09:42 +00:00
Implemented partial chunk saving on LevelDB (#3078)
This commit is contained in:
parent
9598af7683
commit
c533f6a0bd
@ -61,7 +61,7 @@ class SimpleChunkManager implements ChunkManager{
|
|||||||
public function setBlockAt(int $x, int $y, int $z, Block $block) : void{
|
public function setBlockAt(int $x, int $y, int $z, Block $block) : void{
|
||||||
if($this->terrainPointer->moveTo($x, $y, $z, true)){
|
if($this->terrainPointer->moveTo($x, $y, $z, true)){
|
||||||
$this->terrainPointer->currentSubChunk->setFullBlock($x & 0xf, $y & 0xf, $z & 0xf, $block->getFullId());
|
$this->terrainPointer->currentSubChunk->setFullBlock($x & 0xf, $y & 0xf, $z & 0xf, $block->getFullId());
|
||||||
$this->terrainPointer->currentChunk->setChanged(true);
|
$this->terrainPointer->currentChunk->setDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN, true);
|
||||||
}else{
|
}else{
|
||||||
throw new \InvalidArgumentException("Cannot set block at coordinates x=$x,y=$y,z=$z, terrain is not loaded or out of bounds");
|
throw new \InvalidArgumentException("Cannot set block at coordinates x=$x,y=$y,z=$z, terrain is not loaded or out of bounds");
|
||||||
}
|
}
|
||||||
|
@ -1038,9 +1038,9 @@ class World implements ChunkManager{
|
|||||||
$this->timings->syncChunkSaveTimer->startTiming();
|
$this->timings->syncChunkSaveTimer->startTiming();
|
||||||
try{
|
try{
|
||||||
foreach($this->chunks as $chunk){
|
foreach($this->chunks as $chunk){
|
||||||
if($chunk->hasChanged() and $chunk->isGenerated()){
|
if($chunk->isDirty() and $chunk->isGenerated()){
|
||||||
$this->provider->saveChunk($chunk);
|
$this->provider->saveChunk($chunk);
|
||||||
$chunk->setChanged(false);
|
$chunk->clearDirtyFlags();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}finally{
|
}finally{
|
||||||
@ -2213,7 +2213,7 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
unset($this->blockCache[$chunkHash]);
|
unset($this->blockCache[$chunkHash]);
|
||||||
unset($this->changedBlocks[$chunkHash]);
|
unset($this->changedBlocks[$chunkHash]);
|
||||||
$chunk->setChanged();
|
$chunk->setDirty();
|
||||||
|
|
||||||
if(!$this->isChunkInUse($chunkX, $chunkZ)){
|
if(!$this->isChunkInUse($chunkX, $chunkZ)){
|
||||||
$this->unloadChunkRequest($chunkX, $chunkZ);
|
$this->unloadChunkRequest($chunkX, $chunkZ);
|
||||||
@ -2505,7 +2505,7 @@ class World implements ChunkManager{
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($trySave and $this->getAutoSave() and $chunk->isGenerated() and $chunk->hasChanged()){
|
if($trySave and $this->getAutoSave() and $chunk->isGenerated() and $chunk->isDirty()){
|
||||||
$this->timings->syncChunkSaveTimer->startTiming();
|
$this->timings->syncChunkSaveTimer->startTiming();
|
||||||
try{
|
try{
|
||||||
$this->provider->saveChunk($chunk);
|
$this->provider->saveChunk($chunk);
|
||||||
|
@ -46,6 +46,10 @@ use function str_repeat;
|
|||||||
use function strlen;
|
use function strlen;
|
||||||
|
|
||||||
class Chunk{
|
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;
|
public const MAX_SUBCHUNKS = 16;
|
||||||
|
|
||||||
@ -54,8 +58,8 @@ class Chunk{
|
|||||||
/** @var int */
|
/** @var int */
|
||||||
protected $z;
|
protected $z;
|
||||||
|
|
||||||
/** @var bool */
|
/** @var int */
|
||||||
protected $hasChanged = false;
|
private $dirtyFlags = 0;
|
||||||
|
|
||||||
/** @var bool */
|
/** @var bool */
|
||||||
protected $lightPopulated = false;
|
protected $lightPopulated = false;
|
||||||
@ -189,7 +193,7 @@ class Chunk{
|
|||||||
*/
|
*/
|
||||||
public function setFullBlock(int $x, int $y, int $z, int $block) : void{
|
public function setFullBlock(int $x, int $y, int $z, int $block) : void{
|
||||||
$this->getSubChunk($y >> 4, true)->setFullBlock($x, $y & 0xf, $z, $block);
|
$this->getSubChunk($y >> 4, true)->setFullBlock($x, $y & 0xf, $z, $block);
|
||||||
$this->hasChanged = true;
|
$this->dirtyFlags |= self::DIRTY_FLAG_TERRAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -396,8 +400,8 @@ class Chunk{
|
|||||||
* @param int $biomeId 0-255
|
* @param int $biomeId 0-255
|
||||||
*/
|
*/
|
||||||
public function setBiomeId(int $x, int $z, int $biomeId) : void{
|
public function setBiomeId(int $x, int $z, int $biomeId) : void{
|
||||||
$this->hasChanged = true;
|
|
||||||
$this->biomeIds[($z << 4) | $x] = chr($biomeId & 0xff);
|
$this->biomeIds[($z << 4) | $x] = chr($biomeId & 0xff);
|
||||||
|
$this->dirtyFlags |= self::DIRTY_FLAG_BIOMES;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -451,7 +455,7 @@ class Chunk{
|
|||||||
}
|
}
|
||||||
$this->entities[$entity->getId()] = $entity;
|
$this->entities[$entity->getId()] = $entity;
|
||||||
if(!($entity instanceof Player)){
|
if(!($entity instanceof Player)){
|
||||||
$this->hasChanged = true;
|
$this->dirtyFlags |= self::DIRTY_FLAG_ENTITIES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,7 +465,7 @@ class Chunk{
|
|||||||
public function removeEntity(Entity $entity) : void{
|
public function removeEntity(Entity $entity) : void{
|
||||||
unset($this->entities[$entity->getId()]);
|
unset($this->entities[$entity->getId()]);
|
||||||
if(!($entity instanceof Player)){
|
if(!($entity instanceof Player)){
|
||||||
$this->hasChanged = true;
|
$this->dirtyFlags |= self::DIRTY_FLAG_ENTITIES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,7 +482,7 @@ class Chunk{
|
|||||||
$this->tiles[$index]->close();
|
$this->tiles[$index]->close();
|
||||||
}
|
}
|
||||||
$this->tiles[$index] = $tile;
|
$this->tiles[$index] = $tile;
|
||||||
$this->hasChanged = true;
|
$this->dirtyFlags |= self::DIRTY_FLAG_TILES;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -487,7 +491,7 @@ class Chunk{
|
|||||||
public function removeTile(Tile $tile) : void{
|
public function removeTile(Tile $tile) : void{
|
||||||
$pos = $tile->getPos();
|
$pos = $tile->getPos();
|
||||||
unset($this->tiles[Chunk::blockHash($pos->x, $pos->y, $pos->z)]);
|
unset($this->tiles[Chunk::blockHash($pos->x, $pos->y, $pos->z)]);
|
||||||
$this->hasChanged = true;
|
$this->dirtyFlags |= self::DIRTY_FLAG_TILES;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -563,7 +567,7 @@ class Chunk{
|
|||||||
*/
|
*/
|
||||||
public function initChunk(World $world) : void{
|
public function initChunk(World $world) : void{
|
||||||
if($this->NBTentities !== null){
|
if($this->NBTentities !== null){
|
||||||
$this->hasChanged = true;
|
$this->dirtyFlags |= self::DIRTY_FLAG_ENTITIES;
|
||||||
$world->timings->syncChunkLoadEntitiesTimer->startTiming();
|
$world->timings->syncChunkLoadEntitiesTimer->startTiming();
|
||||||
foreach($this->NBTentities as $nbt){
|
foreach($this->NBTentities as $nbt){
|
||||||
if($nbt instanceof CompoundTag){
|
if($nbt instanceof CompoundTag){
|
||||||
@ -584,7 +588,7 @@ class Chunk{
|
|||||||
$world->timings->syncChunkLoadEntitiesTimer->stopTiming();
|
$world->timings->syncChunkLoadEntitiesTimer->stopTiming();
|
||||||
}
|
}
|
||||||
if($this->NBTtiles !== null){
|
if($this->NBTtiles !== null){
|
||||||
$this->hasChanged = true;
|
$this->dirtyFlags |= self::DIRTY_FLAG_TILES;
|
||||||
$world->timings->syncChunkLoadTileEntitiesTimer->startTiming();
|
$world->timings->syncChunkLoadTileEntitiesTimer->startTiming();
|
||||||
foreach($this->NBTtiles as $nbt){
|
foreach($this->NBTtiles as $nbt){
|
||||||
if($nbt instanceof CompoundTag){
|
if($nbt instanceof CompoundTag){
|
||||||
@ -619,15 +623,35 @@ class Chunk{
|
|||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function hasChanged() : bool{
|
public function isDirty() : bool{
|
||||||
return $this->hasChanged or !empty($this->tiles) or !empty($this->getSavableEntities());
|
return $this->dirtyFlags !== 0 or !empty($this->tiles) or !empty($this->getSavableEntities());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDirtyFlag(int $flag) : bool{
|
||||||
|
return ($this->dirtyFlags & $flag) !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $value
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function setChanged(bool $value = true) : void{
|
public function getDirtyFlags() : int{
|
||||||
$this->hasChanged = $value;
|
return $this->dirtyFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDirtyFlag(int $flag, bool $value) : void{
|
||||||
|
if($value){
|
||||||
|
$this->dirtyFlags |= $flag;
|
||||||
|
}else{
|
||||||
|
$this->dirtyFlags &= ~$flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDirty() : void{
|
||||||
|
$this->dirtyFlags = ~0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearDirtyFlags() : void{
|
||||||
|
$this->dirtyFlags = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -666,7 +690,7 @@ class Chunk{
|
|||||||
}else{
|
}else{
|
||||||
$this->subChunks[$y] = $subChunk;
|
$this->subChunks[$y] = $subChunk;
|
||||||
}
|
}
|
||||||
$this->hasChanged = true;
|
$this->setDirtyFlag(self::DIRTY_FLAG_TERRAIN, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +135,7 @@ class FormatConverter{
|
|||||||
$thisRound = $start;
|
$thisRound = $start;
|
||||||
static $reportInterval = 256;
|
static $reportInterval = 256;
|
||||||
foreach($this->oldProvider->getAllChunks(true, $this->logger) as $chunk){
|
foreach($this->oldProvider->getAllChunks(true, $this->logger) as $chunk){
|
||||||
|
$chunk->setDirty();
|
||||||
$new->saveChunk($chunk);
|
$new->saveChunk($chunk);
|
||||||
$counter++;
|
$counter++;
|
||||||
if(($counter % $reportInterval) === 0){
|
if(($counter % $reportInterval) === 0){
|
||||||
|
@ -416,7 +416,9 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
|
|
||||||
$chunk->setGenerated();
|
$chunk->setGenerated();
|
||||||
$chunk->setPopulated();
|
$chunk->setPopulated();
|
||||||
$chunk->setChanged($hasBeenUpgraded); //trigger rewriting chunk to disk if it was converted from an older format
|
if($hasBeenUpgraded){
|
||||||
|
$chunk->setDirty(); //trigger rewriting chunk to disk if it was converted from an older format
|
||||||
|
}
|
||||||
|
|
||||||
return $chunk;
|
return $chunk;
|
||||||
}
|
}
|
||||||
@ -431,39 +433,43 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
$write = new \LevelDBWriteBatch();
|
$write = new \LevelDBWriteBatch();
|
||||||
$write->put($index . self::TAG_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
|
$write->put($index . self::TAG_VERSION, chr(self::CURRENT_LEVEL_CHUNK_VERSION));
|
||||||
|
|
||||||
$subChunks = $chunk->getSubChunks();
|
if($chunk->getDirtyFlag(Chunk::DIRTY_FLAG_TERRAIN)){
|
||||||
foreach($subChunks as $y => $subChunk){
|
$subChunks = $chunk->getSubChunks();
|
||||||
$key = $index . self::TAG_SUBCHUNK_PREFIX . chr($y);
|
foreach($subChunks as $y => $subChunk){
|
||||||
if($subChunk->isEmpty(false)){ //MCPE doesn't save light anymore as of 1.1
|
$key = $index . self::TAG_SUBCHUNK_PREFIX . chr($y);
|
||||||
$write->delete($key);
|
if($subChunk->isEmpty(false)){ //MCPE doesn't save light anymore as of 1.1
|
||||||
}else{
|
$write->delete($key);
|
||||||
$subStream = new BinaryStream();
|
}else{
|
||||||
$subStream->putByte(self::CURRENT_LEVEL_SUBCHUNK_VERSION);
|
$subStream = new BinaryStream();
|
||||||
|
$subStream->putByte(self::CURRENT_LEVEL_SUBCHUNK_VERSION);
|
||||||
|
|
||||||
$layers = $subChunk->getBlockLayers();
|
$layers = $subChunk->getBlockLayers();
|
||||||
$subStream->putByte(count($layers));
|
$subStream->putByte(count($layers));
|
||||||
foreach($layers as $blocks){
|
foreach($layers as $blocks){
|
||||||
$subStream->putByte($blocks->getBitsPerBlock() << 1);
|
$subStream->putByte($blocks->getBitsPerBlock() << 1);
|
||||||
$subStream->put($blocks->getWordArray());
|
$subStream->put($blocks->getWordArray());
|
||||||
|
|
||||||
$palette = $blocks->getPalette();
|
$palette = $blocks->getPalette();
|
||||||
$subStream->putLInt(count($palette));
|
$subStream->putLInt(count($palette));
|
||||||
$tags = [];
|
$tags = [];
|
||||||
foreach($palette as $p){
|
foreach($palette as $p){
|
||||||
$tags[] = new TreeRoot(CompoundTag::create()
|
$tags[] = new TreeRoot(CompoundTag::create()
|
||||||
->setString("name", $idMap[$p >> 4] ?? "minecraft:info_update")
|
->setString("name", $idMap[$p >> 4] ?? "minecraft:info_update")
|
||||||
->setInt("oldid", $p >> 4) //PM only (debugging), vanilla doesn't have this
|
->setInt("oldid", $p >> 4) //PM only (debugging), vanilla doesn't have this
|
||||||
->setShort("val", $p & 0xf));
|
->setShort("val", $p & 0xf));
|
||||||
|
}
|
||||||
|
|
||||||
|
$subStream->put((new LittleEndianNbtSerializer())->writeMultiple($tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
$subStream->put((new LittleEndianNbtSerializer())->writeMultiple($tags));
|
$write->put($key, $subStream->getBuffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
$write->put($key, $subStream->getBuffer());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$write->put($index . self::TAG_DATA_2D, str_repeat("\x00", 512) . $chunk->getBiomeIdArray());
|
if($chunk->getDirtyFlag(Chunk::DIRTY_FLAG_BIOMES)){
|
||||||
|
$write->put($index . self::TAG_DATA_2D, str_repeat("\x00", 512) . $chunk->getBiomeIdArray());
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: use this properly
|
//TODO: use this properly
|
||||||
$write->put($index . self::TAG_STATE_FINALISATION, chr(self::FINALISATION_DONE));
|
$write->put($index . self::TAG_STATE_FINALISATION, chr(self::FINALISATION_DONE));
|
||||||
|
@ -118,7 +118,7 @@ class PopulationTask extends AsyncTask{
|
|||||||
foreach($chunks as $i => $c){
|
foreach($chunks as $i => $c){
|
||||||
if($c !== null){
|
if($c !== null){
|
||||||
$c = $chunks[$i] = $manager->getChunk($c->getX(), $c->getZ());
|
$c = $chunks[$i] = $manager->getChunk($c->getX(), $c->getZ());
|
||||||
if(!$c->hasChanged()){
|
if(!$c->isDirty()){
|
||||||
$chunks[$i] = null;
|
$chunks[$i] = null;
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user