Separate block legacy data upgrading from block deserialization

this commit provides a central place where all block data can go to be upgraded to the latest version (currently 1.19), irrespective of how old it is.

Previously I had issues during debugging, because it wasn't possible to just upgrade a block without deserializing it into a Block object, which isn't currently supported for many blocks.
This commit solves that problem by separating the upgrading from the deserialization.
This commit is contained in:
Dylan K. Taylor 2022-06-23 16:45:02 +01:00
parent 301b0aba82
commit 1533fcf8f6
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
6 changed files with 38 additions and 96 deletions

View File

@ -46,11 +46,13 @@ class FlowerPot extends Spawnable{
public function readSaveData(CompoundTag $nbt) : void{
$blockStateData = null;
$blockDataUpgrader = GlobalBlockStateHandlers::getUpgrader();
if(($itemIdTag = $nbt->getTag(self::TAG_ITEM)) instanceof ShortTag && ($itemMetaTag = $nbt->getTag(self::TAG_ITEM_DATA)) instanceof IntTag){
$blockStateData = GlobalBlockStateHandlers::getLegacyBlockStateMapper()->fromIntIdMeta($itemIdTag->getValue(), $itemMetaTag->getValue());
$blockStateData = $blockDataUpgrader->upgradeIntIdMeta($itemIdTag->getValue(), $itemMetaTag->getValue());
}elseif(($plantBlockTag = $nbt->getCompoundTag(self::TAG_PLANT_BLOCK)) !== null){
try{
$blockStateData = GlobalBlockStateHandlers::nbtToBlockStateData($plantBlockTag);
$blockStateData = $blockDataUpgrader->upgradeBlockStateNbt($plantBlockTag);
}catch(BlockStateDeserializeException $e){
throw new SavedDataLoadingException("Error loading " . self::TAG_PLANT_BLOCK . " tag for flower pot: " . $e->getMessage(), 0, $e);
}

View File

@ -1,40 +0,0 @@
<?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\data\bedrock\block;
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgrader;
final class UpgradingBlockStateDeserializer implements BlockStateDeserializer{
public function __construct(
private BlockStateUpgrader $blockStateUpgrader,
private BlockStateDeserializer $realDeserializer
){}
public function deserialize(BlockStateData $stateData) : int{
return $this->realDeserializer->deserialize($this->blockStateUpgrader->upgrade($stateData));
}
public function getRealDeserializer() : BlockStateDeserializer{ return $this->realDeserializer; }
}

View File

@ -65,12 +65,9 @@ class FallingBlock extends Entity{
public static function parseBlockNBT(BlockFactory $factory, CompoundTag $nbt) : Block{
//TODO: 1.8+ save format
$blockDataUpgrader = GlobalBlockStateHandlers::getUpgrader();
if(($fallingBlockTag = $nbt->getCompoundTag(self::TAG_FALLING_BLOCK)) !== null){
try{
$blockStateData = GlobalBlockStateHandlers::nbtToBlockStateData($fallingBlockTag);
}catch(BlockStateDeserializeException $e){
throw new SavedDataLoadingException($e->getMessage(), 0, $e);
}
$blockStateData = $blockDataUpgrader->upgradeBlockStateNbt($fallingBlockTag);
}else{
if(($tileIdTag = $nbt->getTag("TileID")) instanceof IntTag){
$blockId = $tileIdTag->getValue();
@ -81,10 +78,10 @@ class FallingBlock extends Entity{
}
$damage = $nbt->getByte("Data", 0);
$blockStateData = GlobalBlockStateHandlers::getLegacyBlockStateMapper()->fromIntIdMeta($blockId, $damage);
if($blockStateData === null){
throw new SavedDataLoadingException("Invalid legacy falling block");
}
$blockStateData = $blockDataUpgrader->upgradeIntIdMeta($blockId, $damage);
}
if($blockStateData === null){
throw new SavedDataLoadingException("Invalid legacy falling block");
}
try{

View File

@ -56,11 +56,11 @@ abstract class BaseWorldProvider implements WorldProvider{
//TODO: this should be dependency-injected so it can be replaced, but that would break BC
//also, we want it to be lazy-loaded ...
$legacyBlockStateMapper = GlobalBlockStateHandlers::getLegacyBlockStateMapper();
$blockDataUpgrader = GlobalBlockStateHandlers::getUpgrader();
$blockStateDeserializer = GlobalBlockStateHandlers::getDeserializer();
$newPalette = [];
foreach($palette as $k => $legacyIdMeta){
$newStateData = $legacyBlockStateMapper->fromIntIdMeta($legacyIdMeta >> 4, $legacyIdMeta & 0xf);
$newStateData = $blockDataUpgrader->upgradeIntIdMeta($legacyIdMeta >> 4, $legacyIdMeta & 0xf);
if($newStateData === null){
//TODO: remember data for unknown states so we can implement them later
$newStateData = new BlockStateData(BlockTypeNames::INFO_UPDATE, CompoundTag::create(), BlockStateData::CURRENT_VERSION);

View File

@ -26,18 +26,16 @@ namespace pocketmine\world\format\io;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\data\bedrock\block\BlockStateDeserializer;
use pocketmine\data\bedrock\block\BlockStateSerializer;
use pocketmine\data\bedrock\block\BlockTypeNames;
use pocketmine\data\bedrock\block\CachingBlockStateDeserializer;
use pocketmine\data\bedrock\block\CachingBlockStateSerializer;
use pocketmine\data\bedrock\block\convert\BlockObjectToBlockStateSerializer;
use pocketmine\data\bedrock\block\convert\BlockStateToBlockObjectDeserializer;
use pocketmine\data\bedrock\block\upgrade\BlockDataUpgrader;
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgrader;
use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaUtils;
use pocketmine\data\bedrock\block\upgrade\LegacyBlockStateMapper;
use pocketmine\data\bedrock\block\UpgradingBlockStateDeserializer;
use pocketmine\data\bedrock\block\upgrade\LegacyBlockIdToStringIdMap;
use pocketmine\data\bedrock\block\upgrade\LegacyBlockStateMapper;
use pocketmine\errorhandler\ErrorToExceptionHandler;
use pocketmine\nbt\tag\CompoundTag;
use Webmozart\PathUtil\Path;
use function file_get_contents;
use const pocketmine\BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH;
@ -54,50 +52,29 @@ final class GlobalBlockStateHandlers{
private static ?BlockStateDeserializer $blockStateDeserializer;
private static ?LegacyBlockStateMapper $legacyBlockStateMapper;
private static ?BlockDataUpgrader $blockDataUpgrader;
public static function getDeserializer() : BlockStateDeserializer{
return self::$blockStateDeserializer ??= new CachingBlockStateDeserializer(
new UpgradingBlockStateDeserializer(
new BlockStateUpgrader(BlockStateUpgradeSchemaUtils::loadSchemas(
Path::join(BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH, 'nbt_upgrade_schema'),
BlockStateData::CURRENT_VERSION
)),
new BlockStateToBlockObjectDeserializer()
)
);
return self::$blockStateDeserializer ??= new CachingBlockStateDeserializer(new BlockStateToBlockObjectDeserializer());
}
public static function getSerializer() : BlockStateSerializer{
return self::$blockStateSerializer ??= new CachingBlockStateSerializer(new BlockObjectToBlockStateSerializer());
}
public static function getLegacyBlockStateMapper() : LegacyBlockStateMapper{
return self::$legacyBlockStateMapper ??= LegacyBlockStateMapper::loadFromString(
ErrorToExceptionHandler::trapAndRemoveFalse(fn() => file_get_contents(Path::join(
BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH,
'1.12.0_to_1.18.10_blockstate_map.bin'
))),
LegacyBlockIdToStringIdMap::getInstance()
public static function getUpgrader() : BlockDataUpgrader{
return self::$blockDataUpgrader ??= new BlockDataUpgrader(
LegacyBlockStateMapper::loadFromString(
ErrorToExceptionHandler::trapAndRemoveFalse(fn() => file_get_contents(Path::join(
BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH,
'1.12.0_to_1.18.10_blockstate_map.bin'
))),
LegacyBlockIdToStringIdMap::getInstance()
),
new BlockStateUpgrader(BlockStateUpgradeSchemaUtils::loadSchemas(
Path::join(BEDROCK_BLOCK_UPGRADE_SCHEMA_PATH, 'nbt_upgrade_schema'),
BlockStateData::CURRENT_VERSION
))
);
}
public static function nbtToBlockStateData(CompoundTag $tag) : BlockStateData{
if($tag->getTag("name") !== null && $tag->getTag("val") !== null){
//Legacy (pre-1.13) blockstate - upgrade it to a version we understand
$id = $tag->getString("name");
$data = $tag->getShort("val");
$blockStateData = GlobalBlockStateHandlers::getLegacyBlockStateMapper()->fromStringIdMeta($id, $data);
if($blockStateData === null){
//unknown block, invalid ID
$blockStateData = new BlockStateData(BlockTypeNames::INFO_UPDATE, CompoundTag::create(), BlockStateData::CURRENT_VERSION);
}
}else{
//Modern (post-1.13) blockstate
$blockStateData = BlockStateData::fromNbt($tag);
}
return $blockStateData;
}
}

View File

@ -160,12 +160,18 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$paletteSize = $bitsPerBlock === 0 ? 1 : $stream->getLInt();
$blockDataUpgrader = GlobalBlockStateHandlers::getUpgrader();
$blockStateDeserializer = GlobalBlockStateHandlers::getDeserializer();
for($i = 0; $i < $paletteSize; ++$i){
try{
$offset = $stream->getOffset();
$blockStateData = GlobalBlockStateHandlers::nbtToBlockStateData($nbt->read($stream->getBuffer(), $offset)->mustGetCompoundTag());
$blockStateNbt = $nbt->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$blockStateData = $blockDataUpgrader->upgradeBlockStateNbt($blockStateNbt);
if($blockStateData === null){
//upgrading blockstates should always succeed, regardless of whether they've been implemented or not
throw new BlockStateDeserializeException("Invalid or improperly mapped legacy blockstate: " . $blockStateNbt->toString());
}
$stream->setOffset($offset);
try{
@ -210,7 +216,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$binaryStream = new BinaryStream($extraRawData);
$count = $binaryStream->getLInt();
$legacyMapper = GlobalBlockStateHandlers::getLegacyBlockStateMapper();
$blockDataUpgrader = GlobalBlockStateHandlers::getUpgrader();
$blockStateDeserializer = GlobalBlockStateHandlers::getDeserializer();
for($i = 0; $i < $count; ++$i){
$key = $binaryStream->getLInt();
@ -223,7 +229,7 @@ class LevelDB extends BaseWorldProvider implements WritableWorldProvider{
$blockId = $value & 0xff;
$blockData = ($value >> 8) & 0xf;
$blockStateData = $legacyMapper->fromIntIdMeta($blockId, $blockData);
$blockStateData = $blockDataUpgrader->upgradeIntIdMeta($blockId, $blockData);
if($blockStateData === null){
//TODO: we could preserve this in case it's supported in the future, but this was historically only
//used for grass anyway, so we probably don't need to care