From 07a9c35ee29a9aedde6bbc603cc6b47d0e798ffc Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 5 Mar 2019 13:10:17 +0000 Subject: [PATCH] RegionLoader: Use objects instead of arrays --- .../level/format/io/region/RegionLoader.php | 72 +++++++------- .../io/region/RegionLocationTableEntry.php | 98 +++++++++++++++++++ 2 files changed, 133 insertions(+), 37 deletions(-) create mode 100644 src/pocketmine/level/format/io/region/RegionLocationTableEntry.php diff --git a/src/pocketmine/level/format/io/region/RegionLoader.php b/src/pocketmine/level/format/io/region/RegionLoader.php index 3bf7acedb..7e8662b5f 100644 --- a/src/pocketmine/level/format/io/region/RegionLoader.php +++ b/src/pocketmine/level/format/io/region/RegionLoader.php @@ -26,7 +26,6 @@ namespace pocketmine\level\format\io\region; use pocketmine\level\format\ChunkException; use pocketmine\level\format\io\exception\CorruptedChunkException; use pocketmine\utils\Binary; -use function array_fill; use function ceil; use function chr; use function fclose; @@ -39,6 +38,7 @@ use function fseek; use function ftruncate; use function fwrite; use function is_resource; +use function max; use function ord; use function pack; use function str_pad; @@ -66,7 +66,7 @@ class RegionLoader{ protected $filePointer; /** @var int */ protected $lastSector; - /** @var int[][] [offset in sectors, chunk size in sectors, timestamp] */ + /** @var RegionLocationTableEntry[] */ protected $locationTable = []; /** @var int */ public $lastUsed = 0; @@ -106,7 +106,7 @@ class RegionLoader{ } protected function isChunkGenerated(int $index) : bool{ - return !($this->locationTable[$index][0] === 0 or $this->locationTable[$index][1] === 0); + return !$this->locationTable[$index]->isNull(); } /** @@ -126,23 +126,25 @@ class RegionLoader{ return null; } - fseek($this->filePointer, $this->locationTable[$index][0] << 12); + fseek($this->filePointer, $this->locationTable[$index]->getFirstSector() << 12); + $prefix = fread($this->filePointer, 4); if($prefix === false or strlen($prefix) !== 4){ throw new CorruptedChunkException("Corrupted chunk header detected (unexpected end of file reading length prefix)"); } $length = Binary::readInt($prefix); - if($length <= 0 or $length > self::MAX_SECTOR_LENGTH){ //Not yet generated / corrupted - if($length >= self::MAX_SECTOR_LENGTH){ - throw new CorruptedChunkException("Corrupted chunk header detected (sector count $length larger than max " . self::MAX_SECTOR_LENGTH . ")"); - } + if($length <= 0){ //TODO: if we reached here, the locationTable probably needs updating return null; } + if($length > self::MAX_SECTOR_LENGTH){ //corrupted + throw new CorruptedChunkException("Length for chunk x=$x,z=$z ($length) is larger than maximum " . self::MAX_SECTOR_LENGTH); + } - if($length > ($this->locationTable[$index][1] << 12)){ //Invalid chunk, bigger than defined number of sectors - \GlobalLogger::get()->error("Chunk x=$x,z=$z length mismatch (expected " . ($this->locationTable[$index][1] << 12) . " sectors, got $length sectors)"); - $this->locationTable[$index][1] = $length >> 12; + if($length > ($this->locationTable[$index]->getSectorCount() << 12)){ //Invalid chunk, bigger than defined number of sectors + \GlobalLogger::get()->error("Chunk x=$x,z=$z length mismatch (expected " . ($this->locationTable[$index]->getSectorCount() << 12) . " sectors, got $length sectors)"); + $old = $this->locationTable[$index]; + $this->locationTable[$index] = new RegionLocationTableEntry($old->getFirstSector(), $length >> 12, time()); $this->writeLocationIndex($index); } @@ -185,26 +187,22 @@ class RegionLoader{ if($length + 4 > self::MAX_SECTOR_LENGTH){ throw new ChunkException("Chunk is too big! " . ($length + 4) . " > " . self::MAX_SECTOR_LENGTH); } - $sectors = (int) ceil(($length + 4) / 4096); + + $newSize = (int) ceil(($length + 4) / 4096); $index = self::getChunkOffset($x, $z); - $indexChanged = false; - if($this->locationTable[$index][1] < $sectors){ - $this->locationTable[$index][0] = $this->lastSector + 1; - $this->lastSector += $sectors; //The GC will clean this shift "later" - $indexChanged = true; - }elseif($this->locationTable[$index][1] != $sectors){ - $indexChanged = true; + $offset = $this->locationTable[$index]->getFirstSector(); + + if($this->locationTable[$index]->getSectorCount() < $newSize){ + $offset = $this->lastSector + 1; } - $this->locationTable[$index][1] = $sectors; - $this->locationTable[$index][2] = time(); + $this->locationTable[$index] = new RegionLocationTableEntry($offset, $newSize, time()); + $this->lastSector = max($this->lastSector, $this->locationTable[$index]->getLastSector()); - fseek($this->filePointer, $this->locationTable[$index][0] << 12); - fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $sectors << 12, "\x00", STR_PAD_RIGHT)); + fseek($this->filePointer, $offset << 12); + fwrite($this->filePointer, str_pad(Binary::writeInt($length) . chr(self::COMPRESSION_ZLIB) . $chunkData, $newSize << 12, "\x00", STR_PAD_RIGHT)); - if($indexChanged){ - $this->writeLocationIndex($index); - } + $this->writeLocationIndex($index); } /** @@ -215,8 +213,7 @@ class RegionLoader{ */ public function removeChunk(int $x, int $z) : void{ $index = self::getChunkOffset($x, $z); - $this->locationTable[$index][0] = 0; - $this->locationTable[$index][1] = 0; + $this->locationTable[$index] = new RegionLocationTableEntry(0, 0, 0); } /** @@ -276,6 +273,7 @@ class RegionLoader{ for($i = 0; $i < 1024; ++$i){ $index = $data[$i + 1]; $offset = $index >> 8; + $timestamp = $data[$i + 1025]; if($offset !== 0){ self::getChunkCoords($i, $x, $z); $fileOffset = $offset << 12; @@ -291,10 +289,8 @@ class RegionLoader{ } } - $this->locationTable[$i] = [$index >> 8, $index & 0xff, $data[1024 + $i + 1]]; - if(($this->locationTable[$i][0] + $this->locationTable[$i][1] - 1) > $this->lastSector){ - $this->lastSector = $this->locationTable[$i][0] + $this->locationTable[$i][1] - 1; - } + $this->locationTable[$i] = new RegionLocationTableEntry($offset, $index & 0xff, $timestamp); + $this->lastSector = max($this->lastSector, $this->locationTable[$i]->getLastSector()); } fseek($this->filePointer, 0); @@ -304,10 +300,10 @@ class RegionLoader{ $write = []; for($i = 0; $i < 1024; ++$i){ - $write[] = (($this->locationTable[$i][0] << 8) | $this->locationTable[$i][1]); + $write[] = (($this->locationTable[$i]->getFirstSector() << 8) | $this->locationTable[$i]->getSectorCount()); } for($i = 0; $i < 1024; ++$i){ - $write[] = $this->locationTable[$i][2]; + $write[] = $this->locationTable[$i]->getTimestamp(); } fseek($this->filePointer, 0); fwrite($this->filePointer, pack("N*", ...$write), 4096 * 2); @@ -315,16 +311,18 @@ class RegionLoader{ protected function writeLocationIndex(int $index) : void{ fseek($this->filePointer, $index << 2); - fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index][0] << 8) | $this->locationTable[$index][1]), 4); + fwrite($this->filePointer, Binary::writeInt(($this->locationTable[$index]->getFirstSector() << 8) | $this->locationTable[$index]->getSectorCount()), 4); fseek($this->filePointer, 4096 + ($index << 2)); - fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index][2]), 4); + fwrite($this->filePointer, Binary::writeInt($this->locationTable[$index]->getTimestamp()), 4); } protected function createBlank() : void{ fseek($this->filePointer, 0); ftruncate($this->filePointer, 8192); // this fills the file with the null byte $this->lastSector = 1; - $this->locationTable = array_fill(0, 1024, [0, 0, 0]); + for($i = 0; $i < 1024; ++$i){ + $this->locationTable[$i] = new RegionLocationTableEntry(0, 0, 0); + } } public function getFilePath() : string{ diff --git a/src/pocketmine/level/format/io/region/RegionLocationTableEntry.php b/src/pocketmine/level/format/io/region/RegionLocationTableEntry.php new file mode 100644 index 000000000..f17ae5bdc --- /dev/null +++ b/src/pocketmine/level/format/io/region/RegionLocationTableEntry.php @@ -0,0 +1,98 @@ +firstSector = $firstSector; + if($sectorCount < 0 or $sectorCount > 255){ + throw new \InvalidArgumentException("Sector count must be in range 0...255, got $sectorCount"); + } + $this->sectorCount = $sectorCount; + $this->timestamp = $timestamp; + } + + /** + * @return int + */ + public function getFirstSector() : int{ + return $this->firstSector; + } + + /** + * @return int + */ + public function getLastSector() : int{ + return $this->firstSector + $this->sectorCount - 1; + } + + /** + * Returns an array of sector offsets reserved by this chunk. + * @return int[] + */ + public function getUsedSectors() : array{ + return range($this->getFirstSector(), $this->getLastSector()); + } + + /** + * @return int + */ + public function getSectorCount() : int{ + return $this->sectorCount; + } + + /** + * @return int + */ + public function getTimestamp() : int{ + return $this->timestamp; + } + + /** + * @return bool + */ + public function isNull() : bool{ + return $this->firstSector === 0 or $this->sectorCount === 0; + } +}