From c165670e0a065e2f6aa67d9bddd8d46b74dca6f3 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 26 Jun 2023 16:17:00 +0100 Subject: [PATCH] Added support for using and generating blockstate upgrade schemas using newFlattenedName rules see pmmp/BedrockBlockUpgradeSchema@f426fccbee17f1a54c776b85df371e12333134d8 --- .../BlockStateUpgradeSchemaBlockRemap.php | 40 +++++- .../BlockStateUpgradeSchemaFlattenedName.php | 39 +++++ .../upgrade/BlockStateUpgradeSchemaUtils.php | 31 +++- .../block/upgrade/BlockStateUpgrader.php | 19 ++- ...BlockStateUpgradeSchemaModelBlockRemap.php | 18 ++- ...ckStateUpgradeSchemaModelFlattenedName.php | 40 ++++++ tools/generate-blockstate-upgrade-schema.php | 135 +++++++++++------- 7 files changed, 264 insertions(+), 58 deletions(-) create mode 100644 src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php create mode 100644 src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php index 7c57e1f1f..611ad04e2 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php @@ -24,6 +24,9 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\upgrade; use pocketmine\nbt\tag\Tag; +use pocketmine\utils\Utils; +use function array_diff; +use function count; final class BlockStateUpgradeSchemaBlockRemap{ /** @@ -37,8 +40,43 @@ final class BlockStateUpgradeSchemaBlockRemap{ */ public function __construct( public array $oldState, - public string $newName, + public string|BlockStateUpgradeSchemaFlattenedName $newName, public array $newState, public array $copiedState ){} + + public function equals(self $that) : bool{ + $sameName = $this->newName === $that->newName || + ( + $this->newName instanceof BlockStateUpgradeSchemaFlattenedName && + $that->newName instanceof BlockStateUpgradeSchemaFlattenedName && + $this->newName->equals($that->newName) + ); + if(!$sameName){ + return false; + } + + if( + count($this->oldState) !== count($that->oldState) || + count($this->newState) !== count($that->newState) || + count($this->copiedState) !== count($that->copiedState) || + count(array_diff($this->copiedState, $that->copiedState)) !== 0 + ){ + return false; + } + foreach(Utils::stringifyKeys($this->oldState) as $propertyName => $propertyValue){ + if(!isset($that->oldState[$propertyName]) || !$that->oldState[$propertyName]->equals($propertyValue)){ + //different filter value + return false; + } + } + foreach(Utils::stringifyKeys($this->newState) as $propertyName => $propertyValue){ + if(!isset($that->newState[$propertyName]) || !$that->newState[$propertyName]->equals($propertyValue)){ + //different replacement value + return false; + } + } + + return true; + } } diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php new file mode 100644 index 000000000..d9cbc780e --- /dev/null +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaFlattenedName.php @@ -0,0 +1,39 @@ +prefix === $that->prefix && + $this->flattenedProperty === $that->flattenedProperty && + $this->suffix === $that->suffix; + } +} diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php index c104b3668..82e777134 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php @@ -25,6 +25,7 @@ namespace pocketmine\data\bedrock\block\upgrade; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModel; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelBlockRemap; +use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelFlattenedName; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelTag; use pocketmine\data\bedrock\block\upgrade\model\BlockStateUpgradeSchemaModelValueRemap; use pocketmine\nbt\tag\ByteTag; @@ -43,12 +44,14 @@ use function get_debug_type; use function gettype; use function implode; use function is_object; +use function is_string; use function json_decode; use function json_encode; use function ksort; use function sort; use function str_pad; use function strval; +use function usort; use const JSON_THROW_ON_ERROR; use const SORT_NUMERIC; use const STR_PAD_LEFT; @@ -154,9 +157,17 @@ final class BlockStateUpgradeSchemaUtils{ foreach(Utils::stringifyKeys($model->remappedStates ?? []) as $oldBlockName => $remaps){ foreach($remaps as $remap){ + if(isset($remap->newName) === isset($remap->newFlattenedName)){ + throw new \UnexpectedValueException("Expected exactly one of 'newName' or 'newFlattenedName' properties to be set"); + } + $result->remappedStates[$oldBlockName][] = new BlockStateUpgradeSchemaBlockRemap( array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->oldState ?? []), - $remap->newName, + $remap->newName ?? new BlockStateUpgradeSchemaFlattenedName( + $remap->newFlattenedName->prefix, + $remap->newFlattenedName->flattenedProperty, + $remap->newFlattenedName->suffix + ), array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->newState ?? []), $remap->copiedState ?? [] ); @@ -285,7 +296,13 @@ final class BlockStateUpgradeSchemaUtils{ foreach($remaps as $remap){ $modelRemap = new BlockStateUpgradeSchemaModelBlockRemap( array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->oldState), - $remap->newName, + is_string($remap->newName) ? + $remap->newName : + new BlockStateUpgradeSchemaModelFlattenedName( + $remap->newName->prefix, + $remap->newName->flattenedProperty, + $remap->newName->suffix + ), array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState), $remap->copiedState ); @@ -299,7 +316,15 @@ final class BlockStateUpgradeSchemaUtils{ } $keyedRemaps[$key] = $modelRemap; } - ksort($keyedRemaps); + usort($keyedRemaps, function(BlockStateUpgradeSchemaModelBlockRemap $a, BlockStateUpgradeSchemaModelBlockRemap $b) : int{ + //remaps with more specific criteria must come first + $filterSizeCompare = count($b->oldState ?? []) <=> count($a->oldState ?? []); + if($filterSizeCompare !== 0){ + return $filterSizeCompare; + } + //remaps with the same number of criteria should be sorted alphabetically, but this is not strictly necessary + return json_encode($a->oldState ?? []) <=> json_encode($b->oldState ?? []); + }); $result->remappedStates[$oldBlockName] = array_values($keyedRemaps); } if(isset($result->remappedStates)){ diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index ab0424a33..5c84cd383 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -24,11 +24,14 @@ declare(strict_types=1); namespace pocketmine\data\bedrock\block\upgrade; use pocketmine\data\bedrock\block\BlockStateData; +use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\Tag; use pocketmine\utils\Utils; use function count; +use function is_string; use function ksort; use function max; +use function sprintf; use const SORT_NUMERIC; final class BlockStateUpgrader{ @@ -79,6 +82,20 @@ final class BlockStateUpgrader{ continue 2; //try next state } } + + if(is_string($remap->newName)){ + $newName = $remap->newName; + }else{ + $flattenedValue = $oldState[$remap->newName->flattenedProperty]; + if($flattenedValue instanceof StringTag){ + $newName = sprintf("%s%s%s", $remap->newName->prefix, $flattenedValue->getValue(), $remap->newName->suffix); + unset($oldState[$remap->newName->flattenedProperty]); + }else{ + //flattened property is not a TAG_String, so this transformation is not applicable + continue; + } + } + $newState = $remap->newState; foreach($remap->copiedState as $stateName){ if(isset($oldState[$stateName])){ @@ -86,7 +103,7 @@ final class BlockStateUpgrader{ } } - $blockStateData = new BlockStateData($remap->newName, $newState, $resultVersion); + $blockStateData = new BlockStateData($newName, $newState, $resultVersion); continue 2; //try next schema } } diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php index d37a72fb8..0f518479e 100644 --- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php @@ -34,8 +34,16 @@ final class BlockStateUpgradeSchemaModelBlockRemap{ */ public ?array $oldState; - /** @required */ + /** + * Either this or newFlattenedName must be present + * Due to technical limitations of jsonmapper, we can't use a union type here + */ public string $newName; + /** + * Either this or newName must be present + * Due to technical limitations of jsonmapper, we can't use a union type here + */ + public BlockStateUpgradeSchemaModelFlattenedName $newFlattenedName; /** * @var BlockStateUpgradeSchemaModelTag[]|null @@ -59,9 +67,13 @@ final class BlockStateUpgradeSchemaModelBlockRemap{ * @phpstan-param array $newState * @phpstan-param list $copiedState */ - public function __construct(array $oldState, string $newName, array $newState, array $copiedState){ + public function __construct(array $oldState, string|BlockStateUpgradeSchemaModelFlattenedName $newNameRule, array $newState, array $copiedState){ $this->oldState = count($oldState) === 0 ? null : $oldState; - $this->newName = $newName; + if($newNameRule instanceof BlockStateUpgradeSchemaModelFlattenedName){ + $this->newFlattenedName = $newNameRule; + }else{ + $this->newName = $newNameRule; + } $this->newState = count($newState) === 0 ? null : $newState; $this->copiedState = $copiedState; } diff --git a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php new file mode 100644 index 000000000..4508d9a3b --- /dev/null +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelFlattenedName.php @@ -0,0 +1,40 @@ +prefix = $prefix; + $this->flattenedProperty = $flattenedProperty; + $this->suffix = $suffix; + } +} diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index 70591fa28..f247d6112 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -26,9 +26,11 @@ namespace pocketmine\tools\generate_blockstate_upgrade_schema; use pocketmine\data\bedrock\block\BlockStateData; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchema; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaBlockRemap; +use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaFlattenedName; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaUtils; use pocketmine\data\bedrock\block\upgrade\BlockStateUpgradeSchemaValueRemap; use pocketmine\nbt\LittleEndianNbtSerializer; +use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\Tag; use pocketmine\nbt\TreeRoot; use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; @@ -39,12 +41,11 @@ use function array_filter; use function array_key_first; use function array_keys; use function array_map; -use function array_merge; use function array_shift; use function array_values; -use function assert; use function count; use function dirname; +use function explode; use function file_put_contents; use function fwrite; use function implode; @@ -291,7 +292,7 @@ function processRemappedStates(array $upgradeTable) : array{ foreach($upgradeTable as $pair){ if(count($pair->old->getStates()) === 0 || count($pair->new->getStates()) === 0){ - //all states have changed in some way - compression not possible + //all states have changed in some way - no states are copied over $unchangedStatesByNewName[$pair->new->getName()] = []; continue; } @@ -327,76 +328,110 @@ function processRemappedStates(array $upgradeTable) : array{ $unchangedStatesByNewName[$newName] = $unchangedStates; } - $compressedRemaps = []; + $flattenedProperties = []; + $notFlattenedProperties = []; + $notFlattenedPropertyValues = []; + foreach($upgradeTable as $pair){ + foreach(Utils::stringifyKeys($pair->old->getStates()) as $propertyName => $propertyValue){ + if(isset($notFlattenedProperties[$propertyName])){ + continue; + } + if(!$propertyValue instanceof StringTag){ + $notFlattenedProperties[$propertyName] = true; + continue; + } + $rawValue = $propertyValue->getValue(); + if($rawValue === ""){ + $notFlattenedProperties[$propertyName] = true; + continue; + } + $parts = explode($rawValue, $pair->new->getName(), 2); + if(count($parts) !== 2){ + //the new name does not contain the property value, but it may still be able to be flattened in other cases + $notFlattenedPropertyValues[$propertyName][$rawValue] = $rawValue; + continue; + } + [$prefix, $suffix] = $parts; - foreach($upgradeTable as $remap){ - $oldState = $remap->old->getStates(); - $newState = $remap->new->getStates(); - - if(count($oldState) === 0 || count($newState) === 0){ - //all states have changed in some way - compression not possible - assert(!isset($unchangedStatesByNewName[$remap->new->getName()])); - $compressedRemaps[$remap->new->getName()][] = new BlockStateUpgradeSchemaBlockRemap( - $oldState, - $remap->new->getName(), - $newState, - [] + $filter = $pair->old->getStates(); + foreach($unchangedStatesByNewName[$pair->new->getName()] as $unchangedPropertyName){ + unset($filter[$unchangedPropertyName]); + } + unset($filter[$propertyName]); + $rawFilter = encodeOrderedProperties($filter); + $flattenRule = new BlockStateUpgradeSchemaFlattenedName( + prefix: $prefix, + flattenedProperty: $propertyName, + suffix: $suffix ); - continue; + if(!isset($flattenedProperties[$propertyName][$rawFilter])){ + $flattenedProperties[$propertyName][$rawFilter] = $flattenRule; + }elseif(!$flattenRule->equals($flattenedProperties[$propertyName][$rawFilter])){ + $notFlattenedProperties[$propertyName] = true; + } } + } + foreach(Utils::stringifyKeys($notFlattenedProperties) as $propertyName => $_){ + unset($flattenedProperties[$propertyName]); + } + + ksort($flattenedProperties, SORT_STRING); + $flattenProperty = array_key_first($flattenedProperties); + + $list = []; + + foreach($upgradeTable as $pair){ + $oldState = $pair->old->getStates(); + $newState = $pair->new->getStates(); $cleanedOldState = $oldState; $cleanedNewState = $newState; + $newName = $pair->new->getName(); - foreach($unchangedStatesByNewName[$remap->new->getName()] as $propertyName){ + foreach($unchangedStatesByNewName[$newName] as $propertyName){ unset($cleanedOldState[$propertyName]); unset($cleanedNewState[$propertyName]); } ksort($cleanedOldState); ksort($cleanedNewState); - - $duplicate = false; - $compressedRemaps[$remap->new->getName()] ??= []; - foreach($compressedRemaps[$remap->new->getName()] as $k => $compressedRemap){ - if( - count($compressedRemap->oldState) !== count($cleanedOldState) || - count($compressedRemap->newState) !== count($cleanedNewState) - ){ - continue; + $flattened = false; + if($flattenProperty !== null){ + $flattenedValue = $cleanedOldState[$flattenProperty] ?? null; + if(!$flattenedValue instanceof StringTag){ + throw new AssumptionFailedError("This should always be a TAG_String"); } - foreach(Utils::stringifyKeys($cleanedOldState) as $propertyName => $propertyValue){ - if(!isset($compressedRemap->oldState[$propertyName]) || !$compressedRemap->oldState[$propertyName]->equals($propertyValue)){ - //different filter value - continue 2; - } + if(!isset($notFlattenedPropertyValues[$flattenProperty][$flattenedValue->getValue()])){ + unset($cleanedOldState[$flattenProperty]); + $flattened = true; } - foreach(Utils::stringifyKeys($cleanedNewState) as $propertyName => $propertyValue){ - if(!isset($compressedRemap->newState[$propertyName]) || !$compressedRemap->newState[$propertyName]->equals($propertyValue)){ - //different replacement value - continue 2; - } - } - $duplicate = true; - break; } - if(!$duplicate){ - $compressedRemaps[$remap->new->getName()][] = new BlockStateUpgradeSchemaBlockRemap( - $cleanedOldState, - $remap->new->getName(), - $cleanedNewState, - $unchangedStatesByNewName[$remap->new->getName()] - ); + $rawOldState = encodeOrderedProperties($cleanedOldState); + $newNameRule = $flattenProperty !== null && $flattened ? + $flattenedProperties[$flattenProperty][$rawOldState] ?? throw new AssumptionFailedError("This should always be set") : + $newName; + + $remap = new BlockStateUpgradeSchemaBlockRemap( + $cleanedOldState, + $newNameRule, + $cleanedNewState, + $unchangedStatesByNewName[$pair->new->getName()] + ); + + $existing = $list[$rawOldState] ?? null; + if($existing === null || $existing->equals($remap)){ + $list[$rawOldState] = $remap; + }else{ + //match criteria is borked + throw new AssumptionFailedError("Match criteria resulted in two ambiguous remaps"); } } - $list = array_merge(...array_values($compressedRemaps)); - //more specific filters must come before less specific ones, in case of a remap on a certain value which is //otherwise unchanged usort($list, function(BlockStateUpgradeSchemaBlockRemap $a, BlockStateUpgradeSchemaBlockRemap $b) : int{ return count($b->oldState) <=> count($a->oldState); }); - return $list; + return array_values($list); } /**