7c521b45创建于 2025年8月24日历史提交
<?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\convert;

use pocketmine\block\Block;
use pocketmine\block\Slab;
use pocketmine\block\Stair;
use pocketmine\block\utils\Colored;
use pocketmine\data\bedrock\block\BlockStateData;
use pocketmine\data\bedrock\block\convert\BlockStateReader as Reader;
use pocketmine\data\bedrock\block\convert\BlockStateWriter as Writer;
use pocketmine\data\bedrock\block\convert\property\CommonProperties;
use pocketmine\data\bedrock\block\convert\property\StringProperty;
use function array_map;
use function count;
use function implode;
use function is_string;

/**
 * Registers serializers and deserializers for block data in a unified style, to avoid code duplication.
 * Not all blocks can be registered this way, but we can avoid a lot of repetition for the ones that can.
 */
final class BlockSerializerDeserializerRegistrar{

	public function __construct(
		public readonly BlockStateToObjectDeserializer $deserializer,
		public readonly BlockObjectToStateSerializer $serializer
	){}

	/**
	 * @param string[]|StringProperty[] $components
	 *
	 * @phpstan-param list<string|StringProperty<*>> $components
	 *
	 * @return string[][]
	 * @phpstan-return list<list<string>>
	 */
	private static function compileFlattenedIdPartMatrix(array $components) : array{
		$result = [];
		foreach($components as $component){
			$column = is_string($component) ? [$component] : $component->getPossibleValues();

			if(count($result) === 0){
				$result = array_map(fn($value) => [$value], $column);
			}else{
				$stepResult = [];
				foreach($result as $parts){
					foreach($column as $value){
						$stepPart = $parts;
						$stepPart[] = $value;
						$stepResult[] = $stepPart;
					}
				}

				$result = $stepResult;
			}
		}

		return $result;
	}

	/**
	 * @param string[]|StringProperty[] $idComponents
	 *
	 * @phpstan-template TBlock of Block
	 *
	 * @phpstan-param TBlock            $block
	 * @phpstan-param list<string|StringProperty<contravariant TBlock>> $idComponents
	 */
	private static function serializeFlattenedId(Block $block, array $idComponents) : string{
		$id = "";
		foreach($idComponents as $infix){
			$id .= is_string($infix) ? $infix : $infix->serializePlain($block);
		}
		return $id;
	}

	/**
	 * @param string[]|StringProperty[] $idComponents
	 * @param string[]                  $idPropertyValues
	 *
	 * @phpstan-template TBlock of Block
	 *
	 * @phpstan-param TBlock            $baseBlock
	 * @phpstan-param list<string|StringProperty<contravariant TBlock>> $idComponents
	 * @phpstan-param list<string>      $idPropertyValues
	 *
	 * @phpstan-return TBlock
	 */
	private static function deserializeFlattenedId(Block $baseBlock, array $idComponents, array $idPropertyValues) : Block{
		$preparedBlock = clone $baseBlock;
		foreach($idComponents as $k => $component){
			if($component instanceof StringProperty){
				$fakeValue = $idPropertyValues[$k];
				$component->deserializePlain($preparedBlock, $fakeValue);
			}
		}

		return $preparedBlock;
	}

	public function mapSimple(Block $block, string $id) : void{
		$this->deserializer->mapSimple($id, fn() => clone $block);
		$this->serializer->mapSimple($block, $id);
	}

	/**
	 * @phpstan-template TBlock of Block
	 * @phpstan-param FlattenedIdModel<TBlock, true> $model
	 */
	public function mapFlattenedId(FlattenedIdModel $model) : void{
		$block = $model->getBlock();

		$idComponents = $model->getIdComponents();
		if(count($idComponents) === 0){
			throw new \InvalidArgumentException("No ID components provided");
		}
		$properties = $model->getProperties();

		//This is a really cursed hack that lets us essentially write flattened properties as blockstate properties, and
		//then pull them out to compile an ID :D
		//This works surprisingly well and is much more elegant than I would've expected

		if(count($properties) > 0){
			$this->serializer->map($block, function(Block $block) use ($idComponents, $properties) : Writer{
				$id = self::serializeFlattenedId($block, $idComponents);

				$writer = new Writer($id);
				foreach($properties as $property){
					$property->serialize($block, $writer);
				}

				return $writer;
			});
		}else{
			$this->serializer->map($block, function(Block $block) use ($idComponents) : BlockStateData{
				//fast path for blocks with no state properties
				$id = self::serializeFlattenedId($block, $idComponents);
				return BlockStateData::current($id, []);
			});
		}

		$idPermutations = self::compileFlattenedIdPartMatrix($idComponents);
		foreach($idPermutations as $idParts){
			//deconstruct the ID into a partial state
			//we can do this at registration time since there will be multiple deserializers
			$preparedBlock = self::deserializeFlattenedId($block, $idComponents, $idParts);
			$id = implode("", $idParts);

			if(count($properties) > 0){
				$this->deserializer->map($id, function(Reader $reader) use ($preparedBlock, $properties) : Block{
					$block = clone $preparedBlock;

					foreach($properties as $property){
						$property->deserialize($block, $reader);
					}
					return $block;
				});
			}else{
				//fast path for blocks with no state properties
				$this->deserializer->map($id, fn() => clone $preparedBlock);
			}
		}
	}

	/**
	 * @phpstan-template TBlock of Block&Colored
	 * @phpstan-param TBlock $block
	 */
	public function mapColored(Block $block, string $idPrefix, string $idSuffix) : void{
		$this->mapFlattenedId(FlattenedIdModel::create($block)
			->idComponents([
				$idPrefix,
				CommonProperties::getInstance()->dyeColorIdInfix,
				$idSuffix
			])
		);
	}

	public function mapSlab(Slab $block, string $type) : void{
		$commonProperties = CommonProperties::getInstance();
		$this->mapFlattenedId(FlattenedIdModel::create($block)
			->idComponents(["minecraft:", $type, "_", $commonProperties->slabIdInfix, "slab"])
			->properties([$commonProperties->slabPositionProperty])
		);
	}

	public function mapStairs(Stair $block, string $id) : void{
		$this->mapModel(Model::create($block, $id)->properties(CommonProperties::getInstance()->stairProperties));
	}

	/**
	 * @phpstan-template TBlock of Block
	 * @phpstan-param Model<TBlock> $model
	 */
	public function mapModel(Model $model) : void{
		$id = $model->getId();
		$block = $model->getBlock();
		$propertyDescriptors = $model->getProperties();

		$this->deserializer->map($id, static function(Reader $in) use ($block, $propertyDescriptors) : Block{
			$newBlock = clone $block;
			foreach($propertyDescriptors as $descriptor){
				$descriptor->deserialize($newBlock, $in);
			}
			return $newBlock;
		});
		$this->serializer->map($block, static function(Block $block) use ($id, $propertyDescriptors) : Writer{
			$writer = new Writer($id);
			foreach($propertyDescriptors as $descriptor){
				$descriptor->serialize($block, $writer);
			}
			return $writer;
		});
	}
}