RegionLoader: Use objects instead of arrays

This commit is contained in:
Dylan K. Taylor 2019-03-05 13:10:17 +00:00
parent 4d9b074641
commit 07a9c35ee2
2 changed files with 133 additions and 37 deletions

View File

@ -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,27 +187,23 @@ 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);
}
}
/**
* @param int $x
@ -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{

View File

@ -0,0 +1,98 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\level\format\io\region;
use function range;
class RegionLocationTableEntry{
/** @var int */
private $firstSector;
/** @var int */
private $sectorCount;
/** @var int */
private $timestamp;
/**
* @param int $firstSector
* @param int $sectorCount
* @param int $timestamp
*
* @throws \InvalidArgumentException
*/
public function __construct(int $firstSector, int $sectorCount, int $timestamp){
if($firstSector < 0){
throw new \InvalidArgumentException("Start sector must be positive, got $firstSector");
}
$this->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;
}
}