From a1d44de4874539a367588f3e3ecd5da31778e870 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 28 Apr 2023 20:34:06 +0100 Subject: [PATCH] Added support for compressing blockstate remaps using copiedState this significantly reduces the size of schemas when state remaps are used (see pmmp/BedrockBlockUpgradeSchema@85b83b360eec4377e40cc9ab6c6f09d99d109527). in addition, this will likely offer a substantial performance and memory saving when walls get flattened, which will eventually happen. --- .../BlockStateUpgradeSchemaBlockRemap.php | 10 +- .../upgrade/BlockStateUpgradeSchemaUtils.php | 5 + .../block/upgrade/BlockStateUpgrader.php | 15 +- ...BlockStateUpgradeSchemaModelBlockRemap.php | 12 +- tools/generate-blockstate-upgrade-schema.php | 134 +++++++++++++++++- 5 files changed, 166 insertions(+), 10 deletions(-) diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php index a0659b40a..7c57e1f1f 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaBlockRemap.php @@ -27,14 +27,18 @@ use pocketmine\nbt\tag\Tag; final class BlockStateUpgradeSchemaBlockRemap{ /** - * @param Tag[] $oldState - * @param Tag[] $newState + * @param Tag[] $oldState + * @param Tag[] $newState + * @param string[] $copiedState + * * @phpstan-param array $oldState * @phpstan-param array $newState + * @phpstan-param list $copiedState */ public function __construct( public array $oldState, public string $newName, - public array $newState + public array $newState, + public array $copiedState ){} } diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php index 6af100090..833b2a05a 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgradeSchemaUtils.php @@ -157,6 +157,7 @@ final class BlockStateUpgradeSchemaUtils{ array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->oldState ?? []), $remap->newName, array_map(fn(BlockStateUpgradeSchemaModelTag $tag) => self::jsonModelToTag($tag), $remap->newState ?? []), + $remap->copiedState ?? [] ); } } @@ -277,7 +278,11 @@ final class BlockStateUpgradeSchemaUtils{ array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->oldState), $remap->newName, array_map(fn(Tag $tag) => self::tagToJsonModel($tag), $remap->newState), + $remap->copiedState ); + if(count($modelRemap->copiedState) === 0){ + unset($modelRemap->copiedState); //avoid polluting the JSON + } $key = json_encode($modelRemap); assert(!isset($keyedRemaps[$key])); if(isset($keyedRemaps[$key])){ diff --git a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php index 16c58fb27..ab0424a33 100644 --- a/src/data/bedrock/block/upgrade/BlockStateUpgrader.php +++ b/src/data/bedrock/block/upgrade/BlockStateUpgrader.php @@ -70,16 +70,23 @@ final class BlockStateUpgrader{ $oldState = $blockStateData->getStates(); if(isset($schema->remappedStates[$oldName])){ foreach($schema->remappedStates[$oldName] as $remap){ - if(count($oldState) !== count($remap->oldState)){ + if(count($remap->oldState) > count($oldState)){ + //match criteria has more requirements than we have state properties continue; //try next state } - foreach(Utils::stringifyKeys($oldState) as $k => $v){ - if(!isset($remap->oldState[$k]) || !$remap->oldState[$k]->equals($v)){ + foreach(Utils::stringifyKeys($remap->oldState) as $k => $v){ + if(!isset($oldState[$k]) || !$oldState[$k]->equals($v)){ continue 2; //try next state } } + $newState = $remap->newState; + foreach($remap->copiedState as $stateName){ + if(isset($oldState[$stateName])){ + $newState[$stateName] = $oldState[$stateName]; + } + } - $blockStateData = new BlockStateData($remap->newName, $remap->newState, $resultVersion); + $blockStateData = new BlockStateData($remap->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 0991e5469..49b2e0f28 100644 --- a/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php +++ b/src/data/bedrock/block/upgrade/model/BlockStateUpgradeSchemaModelBlockRemap.php @@ -43,15 +43,25 @@ final class BlockStateUpgradeSchemaModelBlockRemap{ */ public ?array $newState; + /** + * @var string[] + * @phpstan-var list + * May not be present in older schemas + */ + public array $copiedState; + /** * @param BlockStateUpgradeSchemaModelTag[] $oldState * @param BlockStateUpgradeSchemaModelTag[] $newState + * @param string[] $copiedState * @phpstan-param array $oldState * @phpstan-param array $newState + * @phpstan-param list $copiedState */ - public function __construct(array $oldState, string $newName, array $newState){ + public function __construct(array $oldState, string $newName, array $newState, array $copiedState){ $this->oldState = count($oldState) === 0 ? null : $oldState; $this->newName = $newName; $this->newState = count($newState) === 0 ? null : $newState; + $this->copiedState = $copiedState; } } diff --git a/tools/generate-blockstate-upgrade-schema.php b/tools/generate-blockstate-upgrade-schema.php index 85d42a35b..8bb271ddf 100644 --- a/tools/generate-blockstate-upgrade-schema.php +++ b/tools/generate-blockstate-upgrade-schema.php @@ -33,12 +33,16 @@ use pocketmine\network\mcpe\protocol\serializer\NetworkNbtSerializer; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; use function array_key_first; +use function array_merge; +use function array_values; +use function assert; use function count; use function dirname; use function file_put_contents; use function fwrite; use function json_encode; use function ksort; +use function usort; use const JSON_PRETTY_PRINT; use const SORT_STRING; use const STDERR; @@ -177,13 +181,134 @@ function processState(BlockStateData $old, BlockStateData $new, BlockStateUpgrad $result->remappedStates[$oldName][] = new BlockStateUpgradeSchemaBlockRemap( $oldStates, $new->getName(), - $newStates + $newStates, + [] ); \GlobalLogger::get()->warning("warning: multiple properties added and removed for $oldName; added full state remap");; } } } +/** + * Attempts to compress a list of remapped states by looking at which state properties were consistently unchanged. + * This significantly reduces the output size during flattening when the flattened block has many permutations + * (e.g. walls). + * + * @param BlockStateUpgradeSchemaBlockRemap[] $stateRemaps + * @param BlockStateMapping[] $upgradeTable + * + * @return BlockStateUpgradeSchemaBlockRemap[] + */ +function compressRemappedStates(array $upgradeTable, array $stateRemaps) : array{ + $unchangedStatesByNewName = []; + + 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 + $unchangedStatesByNewName[$pair->new->getName()] = []; + continue; + } + + $oldStates = $pair->old->getStates(); + $newStates = $pair->new->getStates(); + if(!isset($unchangedStatesByNewName[$pair->new->getName()])){ + //build list of unchanged states for this new ID + $unchangedStatesByNewName[$pair->new->getName()] = []; + foreach(Utils::stringifyKeys($oldStates) as $propertyName => $propertyValue){ + if(isset($newStates[$propertyName]) && $newStates[$propertyName]->equals($propertyValue)){ + $unchangedStatesByNewName[$pair->new->getName()][] = $propertyName; + } + } + }else{ + //we already have a list of stuff that probably didn't change - verify that this is the case, and remove + //any that changed in later states with the same ID + foreach($unchangedStatesByNewName[$pair->new->getName()] as $k => $propertyName){ + if( + !isset($oldStates[$propertyName]) || + !isset($newStates[$propertyName]) || + !$oldStates[$propertyName]->equals($newStates[$propertyName]) + ){ + //this property disappeared or changed its value in another state with the same ID - we can't + //compress this state + unset($unchangedStatesByNewName[$pair->new->getName()][$k]); + } + } + } + } + foreach(Utils::stringifyKeys($unchangedStatesByNewName) as $newName => $unchangedStates){ + ksort($unchangedStates); + $unchangedStatesByNewName[$newName] = $unchangedStates; + } + + $compressedRemaps = []; + + foreach($stateRemaps as $remap){ + $oldState = $remap->oldState; + $newState = $remap->newState; + + if($oldState === null || $newState === null){ + //no unchanged states - no compression possible + assert(!isset($unchangedStatesByNewName[$remap->newName])); + $compressedRemaps[$remap->newName][] = $remap; + continue; + } + + $cleanedOldState = $oldState; + $cleanedNewState = $newState; + + foreach($unchangedStatesByNewName[$remap->newName] as $propertyName){ + unset($cleanedOldState[$propertyName]); + unset($cleanedNewState[$propertyName]); + } + ksort($cleanedOldState); + ksort($cleanedNewState); + + $duplicate = false; + $compressedRemaps[$remap->newName] ??= []; + foreach($compressedRemaps[$remap->newName] as $k => $compressedRemap){ + assert($compressedRemap->oldState !== null && $compressedRemap->newState !== null); + + if( + count($compressedRemap->oldState) !== count($cleanedOldState) || + count($compressedRemap->newState) !== count($cleanedNewState) + ){ + continue; + } + foreach(Utils::stringifyKeys($cleanedOldState) as $propertyName => $propertyValue){ + if(!isset($compressedRemap->oldState[$propertyName]) || !$compressedRemap->oldState[$propertyName]->equals($propertyValue)){ + //different filter value + continue 2; + } + } + 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->newName][] = new BlockStateUpgradeSchemaBlockRemap( + $cleanedOldState, + $remap->newName, + $cleanedNewState, + $unchangedStatesByNewName[$remap->newName] + ); + } + } + + $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; +} + /** * @param BlockStateMapping[][] $upgradeTable * @phpstan-param array> $upgradeTable @@ -197,6 +322,7 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad }else{ $logger = \GlobalLogger::get(); $logger->emergency("Mismatched upgraded versions found: $foundVersion and " . $mapping->new->getVersion()); + $logger->emergency("Mismatched old state: " . $mapping->old->toNbt()); $logger->emergency("Mismatched new state: " . $mapping->new->toNbt()); $logger->emergency("This is probably because the game didn't recognize the input blockstate, so it was returned unchanged."); $logger->emergency("This is usually because the block is locked behind an experimental toggle that isn't enabled on the world you used when generating this upgrade table."); @@ -238,12 +364,16 @@ function generateBlockStateUpgradeSchema(array $upgradeTable) : BlockStateUpgrad $result->remappedStates[$mapping->old->getName()][] = new BlockStateUpgradeSchemaBlockRemap( $mapping->old->getStates(), $mapping->new->getName(), - $mapping->new->getStates() + $mapping->new->getStates(), + [] ); } } } } + foreach(Utils::stringifyKeys($result->remappedStates) as $oldName => $remap){ + $result->remappedStates[$oldName] = compressRemappedStates($upgradeTable[$oldName], $remap); + } return $result; }