Added support for using and generating blockstate upgrade schemas using newFlattenedName rules

see pmmp/BedrockBlockUpgradeSchema@f426fccbee
This commit is contained in:
Dylan K. Taylor 2023-06-26 16:17:00 +01:00
parent 882d50b14e
commit c165670e0a
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
7 changed files with 264 additions and 58 deletions

View File

@ -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;
}
}

View File

@ -0,0 +1,39 @@
<?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\upgrade;
final class BlockStateUpgradeSchemaFlattenedName{
public function __construct(
public string $prefix,
public string $flattenedProperty,
public string $suffix
){}
public function equals(self $that) : bool{
return $this->prefix === $that->prefix &&
$this->flattenedProperty === $that->flattenedProperty &&
$this->suffix === $that->suffix;
}
}

View File

@ -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)){

View File

@ -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
}
}

View File

@ -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<string, BlockStateUpgradeSchemaModelTag> $newState
* @phpstan-param list<string> $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;
}

View File

@ -0,0 +1,40 @@
<?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\upgrade\model;
final class BlockStateUpgradeSchemaModelFlattenedName{
/** @required */
public string $prefix;
/** @required */
public string $flattenedProperty;
/** @required */
public string $suffix;
public function __construct(string $prefix, string $flattenedProperty, string $suffix){
$this->prefix = $prefix;
$this->flattenedProperty = $flattenedProperty;
$this->suffix = $suffix;
}
}

View File

@ -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);
}
/**