Extract a LightArray unit from SubChunk

This commit is contained in:
Dylan K. Taylor 2019-07-08 14:37:48 +01:00
parent 2cab22fd38
commit cdab3e967a
6 changed files with 124 additions and 84 deletions

View File

@ -224,7 +224,7 @@ class Chunk{
$char = chr(($level & 0x0f) | ($level << 4)); $char = chr(($level & 0x0f) | ($level << 4));
$data = str_repeat($char, 2048); $data = str_repeat($char, 2048);
for($y = $this->getHighestSubChunkIndex(); $y >= 0; --$y){ for($y = $this->getHighestSubChunkIndex(); $y >= 0; --$y){
$this->getSubChunk($y, true)->setBlockSkyLightArray($data); $this->getSubChunk($y, true)->setBlockSkyLightArray(new LightArray($data));
} }
} }
@ -260,7 +260,7 @@ class Chunk{
$char = chr(($level & 0x0f) | ($level << 4)); $char = chr(($level & 0x0f) | ($level << 4));
$data = str_repeat($char, 2048); $data = str_repeat($char, 2048);
for($y = $this->getHighestSubChunkIndex(); $y >= 0; --$y){ for($y = $this->getHighestSubChunkIndex(); $y >= 0; --$y){
$this->getSubChunk($y, true)->setBlockLightArray($data); $this->getSubChunk($y, true)->setBlockLightArray(new LightArray($data));
} }
} }

View File

@ -73,19 +73,19 @@ class EmptySubChunk implements SubChunkInterface{
return -1; return -1;
} }
public function getBlockLightArray() : string{ public function getBlockLightArray() : LightArray{
return str_repeat("\x00", 2048); return new LightArray(str_repeat("\x00", 2048));
} }
public function setBlockLightArray(string $data) : void{ public function setBlockLightArray(LightArray $data) : void{
} }
public function getBlockSkyLightArray() : string{ public function getBlockSkyLightArray() : LightArray{
return str_repeat("\xff", 2048); return new LightArray(str_repeat("\xff", 2048));
} }
public function setBlockSkyLightArray(string $data) : void{ public function setBlockSkyLightArray(LightArray $data) : void{
} }
} }

View File

@ -0,0 +1,85 @@
<?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\world\format;
use function chr;
use function define;
use function defined;
use function ord;
use function str_repeat;
use function strlen;
if(!defined(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY')){
define(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY', str_repeat("\x00", 2048));
}
if(!defined(__NAMESPACE__ . '\FIFTEEN_NIBBLE_ARRAY')){
define(__NAMESPACE__ . '\FIFTEEN_NIBBLE_ARRAY', str_repeat("\xff", 2048));
}
final class LightArray{
public const ZERO = ZERO_NIBBLE_ARRAY;
public const FIFTEEN = FIFTEEN_NIBBLE_ARRAY;
/** @var string */
private $data;
public function __construct(?string $payload){
if($payload !== null and ($len = strlen($payload)) !== 2048){
throw new \InvalidArgumentException("Payload size must be 2048 bytes, but got $len bytes");
}
$this->data = $payload ?? self::ZERO;
$this->collectGarbage();
}
public function get(int $x, int $y, int $z) : int{
return (ord($this->data{($x << 7) | ($z << 3) | ($y >> 1)}) >> (($y & 1) << 2)) & 0xf;
}
public function set(int $x, int $y, int $z, int $level) : void{
$i = ($x << 7) | ($z << 3) | ($y >> 1);
$shift = ($y & 1) << 2;
$byte = ord($this->data{$i});
$this->data{$i} = chr(($byte & ~(0xf << $shift)) | (($level & 0xf) << $shift));
}
public function collectGarbage() : void{
/*
* This strange looking code is designed to exploit PHP's copy-on-write behaviour. Assigning will copy a
* reference to the const instead of duplicating the whole string. The string will only be duplicated when
* modified, which is perfect for this purpose.
*/
if($this->data === ZERO_NIBBLE_ARRAY){
$this->data = ZERO_NIBBLE_ARRAY;
}elseif($this->data === FIFTEEN_NIBBLE_ARRAY){
$this->data = FIFTEEN_NIBBLE_ARRAY;
}
}
public function getData() : string{
return $this->data;
}
}

View File

@ -25,53 +25,28 @@ namespace pocketmine\world\format;
use pocketmine\block\BlockLegacyIds; use pocketmine\block\BlockLegacyIds;
use function array_values; use function array_values;
use function assert;
use function chr;
use function define;
use function defined;
use function ord;
use function str_repeat;
use function strlen;
if(!defined(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY')){
define(__NAMESPACE__ . '\ZERO_NIBBLE_ARRAY', str_repeat("\x00", 2048));
}
if(!defined(__NAMESPACE__ . '\FIFTEEN_NIBBLE_ARRAY')){
define(__NAMESPACE__ . '\FIFTEEN_NIBBLE_ARRAY', str_repeat("\xff", 2048));
}
class SubChunk implements SubChunkInterface{ class SubChunk implements SubChunkInterface{
/** @var PalettedBlockArray[] */ /** @var PalettedBlockArray[] */
private $blockLayers; private $blockLayers;
/** @var string */ /** @var LightArray */
protected $blockLight; protected $blockLight;
/** @var string */ /** @var LightArray */
protected $skyLight; protected $skyLight;
private static function assignData(&$target, string $data, string $default) : void{
if($data === "" or $data === $default){
$target = $default;
}elseif(strlen($data) !== 2048){
assert(false, "Invalid length given, expected 2048, got " . strlen($data));
$target = $default;
}else{
$target = $data;
}
}
/** /**
* SubChunk constructor. * SubChunk constructor.
* *
* @param PalettedBlockArray[] $blocks * @param PalettedBlockArray[] $blocks
* @param string $skyLight * @param LightArray|null $skyLight
* @param string $blockLight * @param LightArray|null $blockLight
*/ */
public function __construct(array $blocks, string $skyLight = "", string $blockLight = ""){ public function __construct(array $blocks, ?LightArray $skyLight = null, ?LightArray $blockLight = null){
$this->blockLayers = $blocks; $this->blockLayers = $blocks;
self::assignData($this->skyLight, $skyLight, FIFTEEN_NIBBLE_ARRAY); $this->skyLight = $skyLight ?? new LightArray(LightArray::FIFTEEN);
self::assignData($this->blockLight, $blockLight, ZERO_NIBBLE_ARRAY); $this->blockLight = $blockLight ?? new LightArray(LightArray::ZERO);
} }
public function isEmpty(bool $checkLight = true) : bool{ public function isEmpty(bool $checkLight = true) : bool{
@ -85,8 +60,8 @@ class SubChunk implements SubChunkInterface{
} }
return return
(!$checkLight or ( (!$checkLight or (
$this->skyLight === FIFTEEN_NIBBLE_ARRAY and $this->skyLight->getData() === LightArray::FIFTEEN and
$this->blockLight === ZERO_NIBBLE_ARRAY $this->blockLight->getData() === LightArray::ZERO
) )
); );
} }
@ -113,29 +88,21 @@ class SubChunk implements SubChunkInterface{
} }
public function getBlockLight(int $x, int $y, int $z) : int{ public function getBlockLight(int $x, int $y, int $z) : int{
return (ord($this->blockLight{($x << 7) | ($z << 3) | ($y >> 1)}) >> (($y & 1) << 2)) & 0xf; return $this->blockLight->get($x, $y, $z);
} }
public function setBlockLight(int $x, int $y, int $z, int $level) : bool{ public function setBlockLight(int $x, int $y, int $z, int $level) : bool{
$i = ($x << 7) | ($z << 3) | ($y >> 1); $this->blockLight->set($x, $y, $z, $level);
$shift = ($y & 1) << 2;
$byte = ord($this->blockLight{$i});
$this->blockLight{$i} = chr(($byte & ~(0xf << $shift)) | (($level & 0xf) << $shift));
return true; return true;
} }
public function getBlockSkyLight(int $x, int $y, int $z) : int{ public function getBlockSkyLight(int $x, int $y, int $z) : int{
return (ord($this->skyLight{($x << 7) | ($z << 3) | ($y >> 1)}) >> (($y & 1) << 2)) & 0xf; return $this->skyLight->get($x, $y, $z);
} }
public function setBlockSkyLight(int $x, int $y, int $z, int $level) : bool{ public function setBlockSkyLight(int $x, int $y, int $z, int $level) : bool{
$i = ($x << 7) | ($z << 3) | ($y >> 1); $this->skyLight->set($x, $y, $z, $level);
$shift = ($y & 1) << 2;
$byte = ord($this->skyLight{$i});
$this->skyLight{$i} = chr(($byte & ~(0xf << $shift)) | (($level & 0xf) << $shift));
return true; return true;
} }
@ -153,23 +120,19 @@ class SubChunk implements SubChunkInterface{
return -1; //highest block not in this subchunk return -1; //highest block not in this subchunk
} }
public function getBlockSkyLightArray() : string{ public function getBlockSkyLightArray() : LightArray{
assert(strlen($this->skyLight) === 2048, "Wrong length of skylight array, expecting 2048 bytes, got " . strlen($this->skyLight));
return $this->skyLight; return $this->skyLight;
} }
public function setBlockSkyLightArray(string $data) : void{ public function setBlockSkyLightArray(LightArray $data) : void{
assert(strlen($data) === 2048, "Wrong length of skylight array, expecting 2048 bytes, got " . strlen($data));
$this->skyLight = $data; $this->skyLight = $data;
} }
public function getBlockLightArray() : string{ public function getBlockLightArray() : LightArray{
assert(strlen($this->blockLight) === 2048, "Wrong length of light array, expecting 2048 bytes, got " . strlen($this->blockLight));
return $this->blockLight; return $this->blockLight;
} }
public function setBlockLightArray(string $data) : void{ public function setBlockLightArray(LightArray $data) : void{
assert(strlen($data) === 2048, "Wrong length of light array, expecting 2048 bytes, got " . strlen($data));
$this->blockLight = $data; $this->blockLight = $data;
} }
@ -190,16 +153,7 @@ class SubChunk implements SubChunkInterface{
} }
$this->blockLayers = array_values($this->blockLayers); $this->blockLayers = array_values($this->blockLayers);
/* $this->skyLight->collectGarbage();
* This strange looking code is designed to exploit PHP's copy-on-write behaviour. Assigning will copy a $this->blockLight->collectGarbage();
* reference to the const instead of duplicating the whole string. The string will only be duplicated when
* modified, which is perfect for this purpose.
*/
if($this->skyLight === ZERO_NIBBLE_ARRAY){
$this->skyLight = ZERO_NIBBLE_ARRAY;
}
if($this->blockLight === ZERO_NIBBLE_ARRAY){
$this->blockLight = ZERO_NIBBLE_ARRAY;
}
} }
} }

View File

@ -101,22 +101,22 @@ interface SubChunkInterface{
public function getHighestBlockAt(int $x, int $z) : int; public function getHighestBlockAt(int $x, int $z) : int;
/** /**
* @return string * @return LightArray
*/ */
public function getBlockSkyLightArray() : string; public function getBlockSkyLightArray() : LightArray;
/** /**
* @param string $data * @param LightArray $data
*/ */
public function setBlockSkyLightArray(string $data) : void; public function setBlockSkyLightArray(LightArray $data) : void;
/** /**
* @return string * @return LightArray
*/ */
public function getBlockLightArray() : string; public function getBlockLightArray() : LightArray;
/** /**
* @param string $data * @param LightArray $data
*/ */
public function setBlockLightArray(string $data) : void; public function setBlockLightArray(LightArray $data) : void;
} }

View File

@ -26,6 +26,7 @@ namespace pocketmine\world\format\io;
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\EmptySubChunk;
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;
use function array_values; use function array_values;
@ -82,8 +83,8 @@ final class FastChunkSerializer{
} }
if($chunk->isLightPopulated()){ if($chunk->isLightPopulated()){
$subStream->put($subChunk->getBlockSkyLightArray()); $subStream->put($subChunk->getBlockSkyLightArray()->getData());
$subStream->put($subChunk->getBlockLightArray()); $subStream->put($subChunk->getBlockLightArray()->getData());
} }
} }
$stream->putByte($count); $stream->putByte($count);
@ -134,7 +135,7 @@ final class FastChunkSerializer{
$layers[] = PalettedBlockArray::fromData($bitsPerBlock, $words, $palette); $layers[] = PalettedBlockArray::fromData($bitsPerBlock, $words, $palette);
} }
$subChunks[$y] = new SubChunk( $subChunks[$y] = new SubChunk(
$layers, $lightPopulated ? $stream->get(2048) : "", $lightPopulated ? $stream->get(2048) : "" //blocklight $layers, $lightPopulated ? new LightArray($stream->get(2048)) : null, $lightPopulated ? new LightArray($stream->get(2048)) : null
); );
} }