mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-13 17:29:44 +00:00
Mostly phase out EmptySubChunk
copy-on-write and zero-layer SubChunk objects are much easier to manage and have less cognitive overhead. obviously, the goal is to get rid of EmptySubChunk completely, but right now it does still serve a purpose (filling in dummy values for reading out-of-bounds on chunks), and that's a problem that takes a little more work to fix.
This commit is contained in:
parent
b7d1d11eb4
commit
02ff8d671b
@ -64,7 +64,6 @@ use pocketmine\utils\Limits;
|
|||||||
use pocketmine\utils\ReversePriorityQueue;
|
use pocketmine\utils\ReversePriorityQueue;
|
||||||
use pocketmine\world\biome\Biome;
|
use pocketmine\world\biome\Biome;
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\EmptySubChunk;
|
|
||||||
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
use pocketmine\world\format\io\exception\CorruptedChunkException;
|
||||||
use pocketmine\world\format\io\WritableWorldProvider;
|
use pocketmine\world\format\io\WritableWorldProvider;
|
||||||
use pocketmine\world\generator\Generator;
|
use pocketmine\world\generator\Generator;
|
||||||
@ -983,7 +982,7 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
|
|
||||||
foreach($chunk->getSubChunks() as $Y => $subChunk){
|
foreach($chunk->getSubChunks() as $Y => $subChunk){
|
||||||
if(!($subChunk instanceof EmptySubChunk)){
|
if(!$subChunk->isEmptyFast()){
|
||||||
$k = mt_rand(0, 0xfffffffff); //36 bits
|
$k = mt_rand(0, 0xfffffffff); //36 bits
|
||||||
for($i = 0; $i < 3; ++$i){
|
for($i = 0; $i < 3; ++$i){
|
||||||
$x = $k & 0x0f;
|
$x = $k & 0x0f;
|
||||||
|
@ -68,7 +68,7 @@ class Chunk{
|
|||||||
/** @var bool */
|
/** @var bool */
|
||||||
protected $terrainPopulated = false;
|
protected $terrainPopulated = false;
|
||||||
|
|
||||||
/** @var \SplFixedArray|SubChunkInterface[] */
|
/** @var \SplFixedArray|SubChunk[] */
|
||||||
protected $subChunks;
|
protected $subChunks;
|
||||||
|
|
||||||
/** @var Tile[] */
|
/** @var Tile[] */
|
||||||
@ -90,13 +90,13 @@ class Chunk{
|
|||||||
protected $NBTentities = [];
|
protected $NBTentities = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $chunkX
|
* @param int $chunkX
|
||||||
* @param int $chunkZ
|
* @param int $chunkZ
|
||||||
* @param SubChunkInterface[] $subChunks
|
* @param SubChunk[] $subChunks
|
||||||
* @param CompoundTag[] $entities
|
* @param CompoundTag[] $entities
|
||||||
* @param CompoundTag[] $tiles
|
* @param CompoundTag[] $tiles
|
||||||
* @param string $biomeIds
|
* @param string $biomeIds
|
||||||
* @param int[] $heightMap
|
* @param int[] $heightMap
|
||||||
*/
|
*/
|
||||||
public function __construct(int $chunkX, int $chunkZ, array $subChunks = [], ?array $entities = null, ?array $tiles = null, string $biomeIds = "", array $heightMap = []){
|
public function __construct(int $chunkX, int $chunkZ, array $subChunks = [], ?array $entities = null, ?array $tiles = null, string $biomeIds = "", array $heightMap = []){
|
||||||
$this->x = $chunkX;
|
$this->x = $chunkX;
|
||||||
@ -105,7 +105,7 @@ class Chunk{
|
|||||||
$this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS);
|
$this->subChunks = new \SplFixedArray(Chunk::MAX_SUBCHUNKS);
|
||||||
|
|
||||||
foreach($this->subChunks as $y => $null){
|
foreach($this->subChunks as $y => $null){
|
||||||
$this->subChunks[$y] = $subChunks[$y] ?? EmptySubChunk::getInstance();
|
$this->subChunks[$y] = $subChunks[$y] ?? new SubChunk(BlockLegacyIds::AIR << 4, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(count($heightMap) === 256){
|
if(count($heightMap) === 256){
|
||||||
@ -183,7 +183,7 @@ class Chunk{
|
|||||||
* @param int $block
|
* @param int $block
|
||||||
*/
|
*/
|
||||||
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)->setFullBlock($x, $y & 0xf, $z, $block);
|
||||||
$this->dirtyFlags |= self::DIRTY_FLAG_TERRAIN;
|
$this->dirtyFlags |= self::DIRTY_FLAG_TERRAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ class Chunk{
|
|||||||
* @param int $level 0-15
|
* @param int $level 0-15
|
||||||
*/
|
*/
|
||||||
public function setBlockSkyLight(int $x, int $y, int $z, int $level) : void{
|
public function setBlockSkyLight(int $x, int $y, int $z, int $level) : void{
|
||||||
$this->getSubChunk($y >> 4, true)->getBlockSkyLightArray()->set($x & 0xf, $y & 0x0f, $z & 0xf, $level);
|
$this->getSubChunk($y >> 4)->getBlockSkyLightArray()->set($x & 0xf, $y & 0x0f, $z & 0xf, $level);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,7 +217,7 @@ class Chunk{
|
|||||||
*/
|
*/
|
||||||
public function setAllBlockSkyLight(int $level) : void{
|
public function setAllBlockSkyLight(int $level) : void{
|
||||||
for($y = $this->getHighestSubChunkIndex(); $y >= 0; --$y){
|
for($y = $this->getHighestSubChunkIndex(); $y >= 0; --$y){
|
||||||
$this->getSubChunk($y, true)->setBlockSkyLightArray(LightArray::fill($level));
|
$this->getSubChunk($y)->setBlockSkyLightArray(LightArray::fill($level));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +243,7 @@ class Chunk{
|
|||||||
* @param int $level 0-15
|
* @param int $level 0-15
|
||||||
*/
|
*/
|
||||||
public function setBlockLight(int $x, int $y, int $z, int $level) : void{
|
public function setBlockLight(int $x, int $y, int $z, int $level) : void{
|
||||||
$this->getSubChunk($y >> 4, true)->getBlockLightArray()->set($x & 0xf, $y & 0x0f, $z & 0xf, $level);
|
$this->getSubChunk($y >> 4)->getBlockLightArray()->set($x & 0xf, $y & 0x0f, $z & 0xf, $level);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -251,7 +251,7 @@ class Chunk{
|
|||||||
*/
|
*/
|
||||||
public function setAllBlockLight(int $level) : void{
|
public function setAllBlockLight(int $level) : void{
|
||||||
for($y = $this->getHighestSubChunkIndex(); $y >= 0; --$y){
|
for($y = $this->getHighestSubChunkIndex(); $y >= 0; --$y){
|
||||||
$this->getSubChunk($y, true)->setBlockLightArray(LightArray::fill($level));
|
$this->getSubChunk($y)->setBlockLightArray(LightArray::fill($level));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,16 +659,13 @@ class Chunk{
|
|||||||
/**
|
/**
|
||||||
* Returns the subchunk at the specified subchunk Y coordinate, or an empty, unmodifiable stub if it does not exist or the coordinate is out of range.
|
* Returns the subchunk at the specified subchunk Y coordinate, or an empty, unmodifiable stub if it does not exist or the coordinate is out of range.
|
||||||
*
|
*
|
||||||
* @param int $y
|
* @param int $y
|
||||||
* @param bool $generateNew Whether to create a new, modifiable subchunk if there is not one in place
|
|
||||||
*
|
*
|
||||||
* @return SubChunkInterface
|
* @return SubChunkInterface
|
||||||
*/
|
*/
|
||||||
public function getSubChunk(int $y, bool $generateNew = false) : SubChunkInterface{
|
public function getSubChunk(int $y) : SubChunkInterface{
|
||||||
if($y < 0 or $y >= $this->subChunks->getSize()){
|
if($y < 0 or $y >= $this->subChunks->getSize()){
|
||||||
return EmptySubChunk::getInstance();
|
return EmptySubChunk::getInstance(); //TODO: drop this and throw an exception here
|
||||||
}elseif($generateNew and $this->subChunks[$y] instanceof EmptySubChunk){
|
|
||||||
$this->subChunks[$y] = new SubChunk(BlockLegacyIds::AIR << 4, []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->subChunks[$y];
|
return $this->subChunks[$y];
|
||||||
@ -677,24 +674,20 @@ class Chunk{
|
|||||||
/**
|
/**
|
||||||
* Sets a subchunk in the chunk index
|
* Sets a subchunk in the chunk index
|
||||||
*
|
*
|
||||||
* @param int $y
|
* @param int $y
|
||||||
* @param SubChunkInterface|null $subChunk
|
* @param SubChunk|null $subChunk
|
||||||
* @param bool $allowEmpty Whether to check if the chunk is empty, and if so replace it with an empty stub
|
|
||||||
*/
|
*/
|
||||||
public function setSubChunk(int $y, ?SubChunkInterface $subChunk, bool $allowEmpty = false) : void{
|
public function setSubChunk(int $y, ?SubChunk $subChunk) : void{
|
||||||
if($y < 0 or $y >= $this->subChunks->getSize()){
|
if($y < 0 or $y >= $this->subChunks->getSize()){
|
||||||
throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y");
|
throw new \InvalidArgumentException("Invalid subchunk Y coordinate $y");
|
||||||
}
|
}
|
||||||
if($subChunk === null or ($subChunk->isEmpty() and !$allowEmpty)){
|
|
||||||
$this->subChunks[$y] = EmptySubChunk::getInstance();
|
$this->subChunks[$y] = $subChunk ?? new SubChunk(BlockLegacyIds::AIR << 4, []);
|
||||||
}else{
|
|
||||||
$this->subChunks[$y] = $subChunk;
|
|
||||||
}
|
|
||||||
$this->setDirtyFlag(self::DIRTY_FLAG_TERRAIN, true);
|
$this->setDirtyFlag(self::DIRTY_FLAG_TERRAIN, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \SplFixedArray|SubChunkInterface[]
|
* @return \SplFixedArray|SubChunk[]
|
||||||
*/
|
*/
|
||||||
public function getSubChunks() : \SplFixedArray{
|
public function getSubChunks() : \SplFixedArray{
|
||||||
return $this->subChunks;
|
return $this->subChunks;
|
||||||
@ -707,8 +700,7 @@ class Chunk{
|
|||||||
*/
|
*/
|
||||||
public function getHighestSubChunkIndex() : int{
|
public function getHighestSubChunkIndex() : int{
|
||||||
for($y = $this->subChunks->count() - 1; $y >= 0; --$y){
|
for($y = $this->subChunks->count() - 1; $y >= 0; --$y){
|
||||||
if($this->subChunks[$y] instanceof EmptySubChunk){
|
if($this->subChunks[$y]->isEmptyFast()){
|
||||||
//No need to thoroughly prune empties at runtime, this will just reduce performance.
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -733,9 +725,6 @@ class Chunk{
|
|||||||
foreach($this->subChunks as $y => $subChunk){
|
foreach($this->subChunks as $y => $subChunk){
|
||||||
if($subChunk instanceof SubChunk){
|
if($subChunk instanceof SubChunk){
|
||||||
$subChunk->collectGarbage();
|
$subChunk->collectGarbage();
|
||||||
if($subChunk->isEmpty()){
|
|
||||||
$this->subChunks[$y] = EmptySubChunk::getInstance();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,11 @@ class EmptySubChunk implements SubChunkInterface{
|
|||||||
return self::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isEmpty(bool $checkLight = true) : bool{
|
public function isEmptyAuthoritative() : bool{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isEmptyFast() : bool{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,21 +52,13 @@ class SubChunk implements SubChunkInterface{
|
|||||||
$this->blockLight = $blockLight ?? new LightArray(LightArray::ZERO);
|
$this->blockLight = $blockLight ?? new LightArray(LightArray::ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isEmpty(bool $checkLight = true) : bool{
|
public function isEmptyAuthoritative() : bool{
|
||||||
foreach($this->blockLayers as $layer){
|
$this->collectGarbage();
|
||||||
$palette = $layer->getPalette();
|
return $this->isEmptyFast();
|
||||||
foreach($palette as $p){
|
}
|
||||||
if($p !== $this->defaultBlock){
|
|
||||||
return false;
|
public function isEmptyFast() : bool{
|
||||||
}
|
return empty($this->blockLayers);
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
(!$checkLight or (
|
|
||||||
$this->skyLight->getData() === LightArray::FIFTEEN and
|
|
||||||
$this->blockLight->getData() === LightArray::ZERO
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFullBlock(int $x, int $y, int $z) : int{
|
public function getFullBlock(int $x, int $y, int $z) : int{
|
||||||
|
@ -26,11 +26,21 @@ namespace pocketmine\world\format;
|
|||||||
interface SubChunkInterface{
|
interface SubChunkInterface{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $checkLight
|
* Returns whether this subchunk contains any non-air blocks.
|
||||||
|
* This function will do a slow check, usually by garbage collecting first.
|
||||||
|
* This is typically useful for disk saving.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function isEmpty(bool $checkLight = true) : bool;
|
public function isEmptyAuthoritative() : bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a non-authoritative bool to indicate whether the chunk contains any blocks.
|
||||||
|
* This is a fast check, but may be inaccurate if the chunk has been modified and not garbage-collected.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isEmptyFast() : bool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $x
|
* @param int $x
|
||||||
|
@ -26,7 +26,6 @@ namespace pocketmine\world\format\io;
|
|||||||
use pocketmine\block\BlockLegacyIds;
|
use pocketmine\block\BlockLegacyIds;
|
||||||
use pocketmine\utils\BinaryStream;
|
use pocketmine\utils\BinaryStream;
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\EmptySubChunk;
|
|
||||||
use pocketmine\world\format\LightArray;
|
use pocketmine\world\format\LightArray;
|
||||||
use pocketmine\world\format\PalettedBlockArray;
|
use pocketmine\world\format\PalettedBlockArray;
|
||||||
use pocketmine\world\format\SubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
@ -64,9 +63,6 @@ final class FastChunkSerializer{
|
|||||||
$count = 0;
|
$count = 0;
|
||||||
$subStream = new BinaryStream();
|
$subStream = new BinaryStream();
|
||||||
foreach($chunk->getSubChunks() as $y => $subChunk){
|
foreach($chunk->getSubChunks() as $y => $subChunk){
|
||||||
if($subChunk instanceof EmptySubChunk){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
++$count;
|
++$count;
|
||||||
|
|
||||||
$subStream->putByte($y);
|
$subStream->putByte($y);
|
||||||
|
@ -438,7 +438,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
|
|||||||
$subChunks = $chunk->getSubChunks();
|
$subChunks = $chunk->getSubChunks();
|
||||||
foreach($subChunks as $y => $subChunk){
|
foreach($subChunks as $y => $subChunk){
|
||||||
$key = $index . self::TAG_SUBCHUNK_PREFIX . chr($y);
|
$key = $index . self::TAG_SUBCHUNK_PREFIX . chr($y);
|
||||||
if($subChunk->isEmpty(false)){ //MCPE doesn't save light anymore as of 1.1
|
if($subChunk->isEmptyAuthoritative()){
|
||||||
$write->delete($key);
|
$write->delete($key);
|
||||||
}else{
|
}else{
|
||||||
$subStream = new BinaryStream();
|
$subStream = new BinaryStream();
|
||||||
|
@ -156,7 +156,7 @@ class Flat extends Generator{
|
|||||||
|
|
||||||
$count = count($this->structure);
|
$count = count($this->structure);
|
||||||
for($sy = 0; $sy < $count; $sy += 16){
|
for($sy = 0; $sy < $count; $sy += 16){
|
||||||
$subchunk = $this->chunk->getSubChunk($sy >> 4, true);
|
$subchunk = $this->chunk->getSubChunk($sy >> 4);
|
||||||
for($y = 0; $y < 16 and isset($this->structure[$y | $sy]); ++$y){
|
for($y = 0; $y < 16 and isset($this->structure[$y | $sy]); ++$y){
|
||||||
$id = $this->structure[$y | $sy];
|
$id = $this->structure[$y | $sy];
|
||||||
|
|
||||||
|
@ -95,10 +95,10 @@ class LightPopulationTask extends AsyncTask{
|
|||||||
$blockLightArrays = igbinary_unserialize($this->resultBlockLightArrays);
|
$blockLightArrays = igbinary_unserialize($this->resultBlockLightArrays);
|
||||||
|
|
||||||
foreach($skyLightArrays as $y => $array){
|
foreach($skyLightArrays as $y => $array){
|
||||||
$chunk->getSubChunk($y, true)->setBlockSkyLightArray($array);
|
$chunk->getSubChunk($y)->setBlockSkyLightArray($array);
|
||||||
}
|
}
|
||||||
foreach($blockLightArrays as $y => $array){
|
foreach($blockLightArrays as $y => $array){
|
||||||
$chunk->getSubChunk($y, true)->setBlockLightArray($array);
|
$chunk->getSubChunk($y)->setBlockLightArray($array);
|
||||||
}
|
}
|
||||||
$chunk->setLightPopulated();
|
$chunk->setLightPopulated();
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,7 @@ namespace pocketmine\world\utils;
|
|||||||
use pocketmine\utils\Utils;
|
use pocketmine\utils\Utils;
|
||||||
use pocketmine\world\ChunkManager;
|
use pocketmine\world\ChunkManager;
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\EmptySubChunk;
|
use pocketmine\world\format\SubChunk;
|
||||||
use pocketmine\world\format\SubChunkInterface;
|
|
||||||
|
|
||||||
class SubChunkIteratorManager{
|
class SubChunkIteratorManager{
|
||||||
/** @var ChunkManager */
|
/** @var ChunkManager */
|
||||||
@ -35,7 +34,7 @@ class SubChunkIteratorManager{
|
|||||||
|
|
||||||
/** @var Chunk|null */
|
/** @var Chunk|null */
|
||||||
public $currentChunk;
|
public $currentChunk;
|
||||||
/** @var SubChunkInterface|null */
|
/** @var SubChunk|null */
|
||||||
public $currentSubChunk;
|
public $currentSubChunk;
|
||||||
|
|
||||||
/** @var int */
|
/** @var int */
|
||||||
@ -67,11 +66,12 @@ class SubChunkIteratorManager{
|
|||||||
if($this->currentSubChunk === null or $this->currentY !== ($y >> 4)){
|
if($this->currentSubChunk === null or $this->currentY !== ($y >> 4)){
|
||||||
$this->currentY = $y >> 4;
|
$this->currentY = $y >> 4;
|
||||||
|
|
||||||
$this->currentSubChunk = $this->currentChunk->getSubChunk($y >> 4, $create);
|
if($this->currentY < 0 or $this->currentY >= $this->currentChunk->getHeight()){
|
||||||
if($this->currentSubChunk instanceof EmptySubChunk){
|
|
||||||
$this->currentSubChunk = null;
|
$this->currentSubChunk = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->currentSubChunk = $this->currentChunk->getSubChunk($y >> 4);
|
||||||
if($this->onSubChunkChangeFunc !== null){
|
if($this->onSubChunkChangeFunc !== null){
|
||||||
($this->onSubChunkChangeFunc)();
|
($this->onSubChunkChangeFunc)();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user