Move block permutation generation into Block

this allows sealing off a whole bunch of internal APIs.
This commit is contained in:
Dylan K. Taylor 2023-04-21 20:38:28 +01:00
parent 6c0ad9589b
commit d4ca566fd0
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
3 changed files with 34 additions and 47 deletions

View File

@ -29,6 +29,7 @@ namespace pocketmine\block;
use pocketmine\block\tile\Spawnable;
use pocketmine\block\tile\Tile;
use pocketmine\block\utils\SupportType;
use pocketmine\data\runtime\InvalidSerializedRuntimeDataException;
use pocketmine\data\runtime\RuntimeDataDescriber;
use pocketmine\data\runtime\RuntimeDataReader;
use pocketmine\data\runtime\RuntimeDataSizeCalculator;
@ -185,14 +186,6 @@ class Block{
return new ItemBlock($normalized);
}
final public function getRequiredTypeDataBits() : int{
return $this->requiredTypeDataBits;
}
final public function getRequiredStateDataBits() : int{
return $this->requiredStateDataBits;
}
private function decodeTypeData(int $data) : void{
$reader = new RuntimeDataReader($this->requiredTypeDataBits, $data);
@ -213,10 +206,7 @@ class Block{
}
}
/**
* @internal
*/
final public function decodeTypeAndStateData(int $data) : void{
private function decodeTypeAndStateData(int $data) : void{
$reader = new RuntimeDataReader($this->requiredTypeDataBits + $this->requiredStateDataBits, $data);
$this->decodeTypeData($reader->readInt($this->requiredTypeDataBits));
$this->decodeStateData($reader->readInt($this->requiredStateDataBits));
@ -246,10 +236,7 @@ class Block{
return $writer->getValue();
}
/**
* @internal
*/
final public function computeTypeAndStateData() : int{
private function computeTypeAndStateData() : int{
$writer = new RuntimeDataWriter($this->requiredTypeDataBits + $this->requiredStateDataBits);
$writer->writeInt($this->requiredTypeDataBits, $this->computeTypeData());
$writer->writeInt($this->requiredStateDataBits, $this->computeStateData());
@ -281,6 +268,35 @@ class Block{
//NOOP
}
/**
* Generates copies of this Block in all possible state permutations.
* Every possible combination of known properties (e.g. facing, open/closed, powered/unpowered, on/off) will be
* generated.
*
* @phpstan-return \Generator<int, Block, void, void>
*/
public function generateStatePermutations() : \Generator{
//TODO: this bruteforce approach to discovering all valid states is very inefficient for larger state data sizes
//at some point we'll need to find a better way to do this
$bits = $this->requiredTypeDataBits + $this->requiredStateDataBits;
if($bits > Block::INTERNAL_STATE_DATA_BITS){
throw new \LogicException("Block state data cannot use more than " . Block::INTERNAL_STATE_DATA_BITS . " bits");
}
for($stateData = 0; $stateData < (1 << $bits); ++$stateData){
$v = clone $this;
try{
$v->decodeTypeAndStateData($stateData);
if($v->computeTypeAndStateData() !== $stateData){
throw new \LogicException(static::class . "::decodeStateData() accepts invalid state data (returned " . $v->computeTypeAndStateData() . " for input $stateData)");
}
}catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
continue;
}
yield $v;
}
}
/**
* Called when this block is created, set, or has a neighbouring block update, to re-detect dynamic properties which
* are not saved on the world.

View File

@ -25,11 +25,9 @@ namespace pocketmine\block;
use pocketmine\block\BlockBreakInfo as BreakInfo;
use pocketmine\block\BlockIdentifier as BID;
use pocketmine\data\runtime\InvalidSerializedRuntimeDataException;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use pocketmine\world\light\LightUpdate;
use function get_class;
use function min;
/**
@ -82,33 +80,6 @@ class RuntimeBlockStateRegistry{
}
}
/**
* Generates all the possible valid blockstates for a given block type.
*
* @phpstan-return \Generator<int, Block, void, void>
*/
private static function generateAllStatesForType(Block $block) : \Generator{
//TODO: this bruteforce approach to discovering all valid states is very inefficient for larger state data sizes
//at some point we'll need to find a better way to do this
$bits = $block->getRequiredTypeDataBits() + $block->getRequiredStateDataBits();
if($bits > Block::INTERNAL_STATE_DATA_BITS){
throw new \InvalidArgumentException("Block state data cannot use more than " . Block::INTERNAL_STATE_DATA_BITS . " bits");
}
for($stateData = 0; $stateData < (1 << $bits); ++$stateData){
$v = clone $block;
try{
$v->decodeTypeAndStateData($stateData);
if($v->computeTypeAndStateData() !== $stateData){
throw new \LogicException(get_class($block) . "::decodeStateData() accepts invalid state data (returned " . $v->computeTypeAndStateData() . " for input $stateData)");
}
}catch(InvalidSerializedRuntimeDataException){ //invalid property combination, leave it
continue;
}
yield $v;
}
}
/**
* Maps a block type to its corresponding type ID. This is necessary for the block to be recognized when loading
* from disk, and also when being read at runtime.
@ -130,7 +101,7 @@ class RuntimeBlockStateRegistry{
$this->typeIndex[$typeId] = clone $block;
foreach(self::generateAllStatesForType($block) as $v){
foreach($block->generateStatePermutations() as $v){
$this->fillStaticArrays($v->getStateId(), $v);
}
}

View File

@ -119,7 +119,7 @@ class BlockTest extends TestCase{
$states = $this->blockFactory->getAllKnownStates();
foreach($states as $stateId => $state){
self::assertArrayHasKey($stateId, $knownStates, "New block state $stateId (" . $state->getTypeId() . ":" . $state->computeTypeAndStateData() . ", " . print_r($state, true) . ") - consistency check may need regenerating");
self::assertArrayHasKey($stateId, $knownStates, "New block state $stateId (" . print_r($state, true) . ") - consistency check may need regenerating");
self::assertSame($knownStates[$stateId], $state->getName());
}
asort($knownStates, SORT_STRING);