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
6 changed files with 38 additions and 96 deletions

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