PocketMine-MP  1.4 - API 1.10.0
 All Classes Namespaces Functions Variables Pages
LevelDB.php
1 <?php
2 
3 /*
4  *
5  * ____ _ _ __ __ _ __ __ ____
6  * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7  * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8  * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9  * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * @author PocketMine Team
17  * @link http://www.pocketmine.net/
18  *
19  *
20 */
21 
22 namespace pocketmine\level\format\leveldb;
23 
38 
39 class LevelDB extends BaseLevelProvider{
40 
42  protected $chunks = [];
43 
45  protected $db;
46 
47  public function __construct(Level $level, $path){
48  $this->level = $level;
49  $this->path = $path;
50  @mkdir($this->path, 0777, true);
51  $nbt = new NBT(NBT::LITTLE_ENDIAN);
52  $nbt->read(substr(file_get_contents($this->getPath() . "level.dat"), 8));
53  $levelData = $nbt->getData();
54  if($levelData instanceof Compound){
55  $this->levelData = $levelData;
56  }else{
57  throw new LevelException("Invalid level.dat");
58  }
59 
60  if(!isset($this->levelData->generatorName)){
61  $this->levelData->generatorName = new String("generatorName", Generator::getGenerator("DEFAULT"));
62  }
63 
64  if(!isset($this->levelData->generatorOptions)){
65  $this->levelData->generatorOptions = new String("generatorOptions", "");
66  }
67 
68  $this->db = new \LevelDB($this->path . "/db", [
69  "compression" => LEVELDB_ZLIB_COMPRESSION
70  ]);
71  }
72 
73  public static function getProviderName(){
74  return "leveldb";
75  }
76 
77  public static function getProviderOrder(){
78  return self::ORDER_ZXY;
79  }
80 
81  public static function usesChunkSection(){
82  return false;
83  }
84 
85  public static function isValid($path){
86  return file_exists($path . "/level.dat") and is_dir($path . "/db/");
87  }
88 
89  public static function generate($path, $name, $seed, $generator, array $options = []){
90  @mkdir($path, 0777, true);
91  @mkdir($path . "/db", 0777);
92  //TODO, add extra details
93  $levelData = new Compound(null, [
94  "hardcore" => new Byte("hardcore", 0),
95  "initialized" => new Byte("initialized", 1),
96  "GameType" => new Int("GameType", 0),
97  "generatorVersion" => new Int("generatorVersion", 1), //2 in MCPE
98  "SpawnX" => new Int("SpawnX", 128),
99  "SpawnY" => new Int("SpawnY", 70),
100  "SpawnZ" => new Int("SpawnZ", 128),
101  "version" => new Int("version", 19133),
102  "DayTime" => new Int("DayTime", 0),
103  "LastPlayed" => new Long("LastPlayed", microtime(true) * 1000),
104  "RandomSeed" => new Long("RandomSeed", $seed),
105  "SizeOnDisk" => new Long("SizeOnDisk", 0),
106  "Time" => new Long("Time", 0),
107  "generatorName" => new String("generatorName", Generator::getGeneratorName($generator)),
108  "generatorOptions" => new String("generatorOptions", isset($options["preset"]) ? $options["preset"] : ""),
109  "LevelName" => new String("LevelName", $name),
110  "GameRules" => new Compound("GameRules", [])
111  ]);
112  $nbt = new NBT(NBT::LITTLE_ENDIAN);
113  $nbt->setData($levelData);
114  $buffer = $nbt->write();
115  file_put_contents($path . "level.dat", Binary::writeLInt(3) . Binary::writeLInt(strlen($buffer)) . $buffer);
116 
117  $db = new \LevelDB($path . "/db");
118  $db->close();
119  }
120 
121  public function saveLevelData(){
122  $nbt = new NBT(NBT::LITTLE_ENDIAN);
123  $nbt->setData($this->levelData);
124  $buffer = $nbt->write();
125  file_put_contents($this->getPath() . "level.dat", Binary::writeLInt(3) . Binary::writeLInt(strlen($buffer)) . $buffer);
126  }
127 
128  public function requestChunkTask($x, $z){
129  $chunk = $this->getChunk($x, $z, false);
130  if(!($chunk instanceof Chunk)){
131  throw new ChunkException("Invalid Chunk sent");
132  }
133 
134  $tiles = "";
135  $nbt = new NBT(NBT::LITTLE_ENDIAN);
136  foreach($chunk->getTiles() as $tile){
137  if($tile instanceof Spawnable){
138  $nbt->setData($tile->getSpawnCompound());
139  $tiles .= $nbt->write();
140  }
141  }
142 
143  $biomeColors = pack("N*", ...$chunk->getBiomeColorArray());
144 
145  $ordered = zlib_encode(
146  Binary::writeLInt($x) . Binary::writeLInt($z) .
147  $chunk->getBlockIdArray() .
148  $chunk->getBlockDataArray() .
149  $chunk->getBlockSkyLightArray() .
150  $chunk->getBlockLightArray() .
151  $chunk->getBiomeIdArray() .
152  $biomeColors .
153  $tiles
154  , ZLIB_ENCODING_DEFLATE, Level::$COMPRESSION_LEVEL);
155 
156  $this->getLevel()->chunkRequestCallback($x, $z, $ordered);
157 
158  return null;
159  }
160 
161  public function unloadChunks(){
162  foreach($this->chunks as $chunk){
163  $this->unloadChunk($chunk->getX(), $chunk->getZ(), false);
164  }
165  $this->chunks = [];
166  }
167 
168  public function getGenerator(){
169  return $this->levelData["generatorName"];
170  }
171 
172  public function getGeneratorOptions(){
173  return ["preset" => $this->levelData["generatorOptions"]];
174  }
175 
176  public function getLoadedChunks(){
177  return $this->chunks;
178  }
179 
180  public function isChunkLoaded($x, $z){
181  return isset($this->chunks[Level::chunkHash($x, $z)]);
182  }
183 
184  public function saveChunks(){
185  foreach($this->chunks as $chunk){
186  $this->saveChunk($chunk->getX(), $chunk->getZ());
187  }
188  }
189 
190  public function loadChunk($chunkX, $chunkZ, $create = false){
191  if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)])){
192  return true;
193  }
194 
195  $this->level->timings->syncChunkLoadDataTimer->startTiming();
196  $chunk = $this->readChunk($chunkX, $chunkZ, $create); //generate empty chunk if not loaded
197  $this->level->timings->syncChunkLoadDataTimer->stopTiming();
198 
199  if($chunk instanceof Chunk){
200  $this->chunks[$index] = $chunk;
201  return true;
202  }else{
203  return false;
204  }
205  }
206 
214  private function readChunk($chunkX, $chunkZ, $create = false){
215  $index = LevelDB::chunkIndex($chunkX, $chunkZ);
216 
217  if(!$this->chunkExists($chunkX, $chunkZ) or ($data = $this->db->get($index . "\x30")) === false){
218  return $create ? $this->generateChunk($chunkX, $chunkZ) : null;
219  }
220 
221  $flags = $this->db->get($index . "f");
222  if($flags === false){
223  $flags = "\x03";
224  }
225 
226  return Chunk::fromBinary($index . $data . $flags, $this);
227  }
228 
229  private function generateChunk($chunkX, $chunkZ){
230  return new Chunk($this, $chunkX, $chunkZ, str_repeat("\x00", 32768) .
231  str_repeat("\x00", 16384) . str_repeat("\xff", 16384) . str_repeat("\x00", 16384) .
232  str_repeat("\x01", 256) .
233  str_repeat("\x00\x85\xb2\x4a", 256));
234  }
235 
236  private function writeChunk(Chunk $chunk){
237  $binary = $chunk->toBinary(true);
238  $index = LevelDB::chunkIndex($chunk->getX(), $chunk->getZ());
239  $this->db->put($index . "\x30", substr($binary, 8, -1));
240  $this->db->put($index . "f", substr($binary, -1));
241  $this->db->put($index . "v", "\x02");
242  }
243 
244  public function unloadChunk($x, $z, $safe = true){
245  $chunk = isset($this->chunks[$index = Level::chunkHash($x, $z)]) ? $this->chunks[$index] : null;
246  if($chunk instanceof FullChunk and $chunk->unload(false, $safe)){
247  unset($this->chunks[$index]);
248  return true;
249  }
250 
251  return false;
252  }
253 
254  public function saveChunk($x, $z){
255  if($this->isChunkLoaded($x, $z)){
256  $this->writeChunk($this->getChunk($x, $z));
257 
258  return true;
259  }
260 
261  return false;
262  }
263 
271  public function getChunk($chunkX, $chunkZ, $create = false){
272  $index = Level::chunkHash($chunkX, $chunkZ);
273  if(isset($this->chunks[$index])){
274  return $this->chunks[$index];
275  }else{
276  $this->loadChunk($chunkX, $chunkZ, $create);
277 
278  return isset($this->chunks[$index]) ? $this->chunks[$index] : null;
279  }
280  }
281 
285  public function getDatabase(){
286  return $this->db;
287  }
288 
289  public function setChunk($chunkX, $chunkZ, FullChunk $chunk){
290  if(!($chunk instanceof Chunk)){
291  throw new ChunkException("Invalid Chunk class");
292  }
293 
294  $chunk->setProvider($this);
295 
296  $chunk->setX($chunkX);
297  $chunk->setZ($chunkZ);
298 
299  if(isset($this->chunks[$index = Level::chunkHash($chunkX, $chunkZ)]) and $this->chunks[$index] !== $chunk){
300  $this->unloadChunk($chunkX, $chunkZ, false);
301  }
302 
303  $this->chunks[$index] = $chunk;
304  }
305 
306  public static function createChunkSection($Y){
307  return null;
308  }
309 
310  public static function chunkIndex($chunkX, $chunkZ){
311  return Binary::writeLInt($chunkX) . Binary::writeLInt($chunkZ);
312  }
313 
314  private function chunkExists($chunkX, $chunkZ){
315  return $this->db->get(LevelDB::chunkIndex($chunkX, $chunkZ) . "v") !== false;
316  }
317 
318  public function isChunkGenerated($chunkX, $chunkZ){
319  if($this->chunkExists($chunkX, $chunkZ) and ($chunk = $this->getChunk($chunkX, $chunkZ, false)) !== null){
320  return true;
321  }
322 
323  return false;
324  }
325 
326  public function isChunkPopulated($chunkX, $chunkZ){
327  $chunk = $this->getChunk($chunkX, $chunkZ);
328  if($chunk instanceof FullChunk){
329  return $chunk->isPopulated();
330  }else{
331  return false;
332  }
333  }
334 
335  public function close(){
336  $this->unloadChunks();
337  $this->db->close();
338  $this->level = null;
339  }
340 }
loadChunk($chunkX, $chunkZ, $create=false)
Definition: LevelDB.php:190
setChunk($chunkX, $chunkZ, FullChunk $chunk)
Definition: LevelDB.php:289
unload($save=true, $safe=true)
__construct(Level $level, $path)
Definition: LevelDB.php:47
setProvider(LevelProvider $provider)
getChunk($chunkX, $chunkZ, $create=false)
Definition: LevelDB.php:271
static chunkHash($x, $z)
Definition: Level.php:230
static generate($path, $name, $seed, $generator, array $options=[])
Definition: LevelDB.php:89
static fromBinary($data, LevelProvider $provider=null)