Added support for compressing blockstate remaps using copiedState

this significantly reduces the size of schemas when state remaps are used (see pmmp/BedrockBlockUpgradeSchema@85b83b360e).

in addition, this will likely offer a substantial performance and memory saving when walls get flattened, which will eventually happen.
This commit is contained in:
Dylan K. Taylor
2023-04-28 20:34:06 +01:00
parent 263e1e9950
commit a1d44de487
5 changed files with 166 additions and 10 deletions

View File

@ -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<string, list<BlockStateMapping>> $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;
}