diff --git a/build/generate-runtime-enum-serializers.php b/build/generate-runtime-enum-serializers.php new file mode 100644 index 000000000..32429bad2 --- /dev/null +++ b/build/generate-runtime-enum-serializers.php @@ -0,0 +1,212 @@ + $memberNames + * + * @return string[] + * @phpstan-return list + */ +function buildWriterFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string &$functionName) : array{ + $bits = getBitsRequired($memberNames); + $lines = []; + + $functionName = "write$virtualTypeName"; + $lines[] = "public static function $functionName(RuntimeDataWriter \$w, \\$nativeTypeName \$value) : void{"; + $lines[] = "\t\$w->writeInt($bits, match(\$value){"; + + foreach($memberNames as $key => $memberName){ + $lines[] = "\t\t$memberName => $key,"; + } + $lines[] = "\t\tdefault => throw new \pocketmine\utils\AssumptionFailedError(\"All $virtualTypeName cases should be covered\")"; + $lines[] = "\t});"; + $lines[] = "}"; + + return $lines; +} + +/** + * @param string[] $memberNames + * @phpstan-param list $memberNames + * + * @return string[] + * @phpstan-return list + */ +function buildReaderFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string &$functionName) : array{ + $bits = getBitsRequired($memberNames); + $lines = []; + + $functionName = "read$virtualTypeName"; + $lines[] = "public static function $functionName(RuntimeDataReader \$r) : \\$nativeTypeName{"; + $lines[] = "\treturn match(\$r->readInt($bits)){"; + + foreach($memberNames as $key => $memberName){ + $lines[] = "\t\t$key => $memberName,"; + } + $lines[] = "\t\tdefault => throw new InvalidSerializedRuntimeDataException(\"Invalid serialized value for $virtualTypeName\")"; + $lines[] = "\t};"; + $lines[] = "}"; + + return $lines; +} + +/** + * @param mixed[] $members + */ +function getBitsRequired(array $members) : int{ + return (int) ceil(log(count($members), 2)); +} + +/** + * @param object[] $members + * @phpstan-param array $members + * + * @return string[] + * @phpstan-return list + */ +function stringifyEnumMembers(array $members, string $enumClass) : array{ + ksort($members, SORT_STRING); + return array_map(fn(string $enumCaseName) => "\\$enumClass::$enumCaseName()", array_keys($members)); +} + +/** + * @param object[] $enumMembers + * @phpstan-param array $enumMembers + * + * @return string[] + * @phpstan-return list + */ +function buildEnumWriterFunc(array $enumMembers, string &$functionName) : array{ + $reflect = new \ReflectionClass($enumMembers[array_key_first($enumMembers)]); + return buildWriterFunc( + $reflect->getShortName(), + $reflect->getName(), + stringifyEnumMembers($enumMembers, $reflect->getName()), + $functionName + ); +} + +/** + * @param object[] $enumMembers + * @phpstan-param array $enumMembers + * + * @return string[] + * @phpstan-return list + */ +function buildEnumReaderFunc(array $enumMembers, string &$functionName) : array{ + if(count($enumMembers) === 0){ + throw new \InvalidArgumentException("Enum members cannot be empty"); + } + $reflect = new \ReflectionClass($enumMembers[array_key_first($enumMembers)]); + return buildReaderFunc( + $reflect->getShortName(), + $reflect->getName(), + stringifyEnumMembers($enumMembers, $reflect->getName()), + $functionName + ); +} + +$enumsUsed = [ + BellAttachmentType::getAll(), + CoralType::getAll(), + DyeColor::getAll(), + LeverFacing::getAll(), + MushroomBlockType::getAll(), + SkullType::getAll(), + SlabType::getAll(), + PotionType::getAll() +]; + +$readerFuncs = []; +$writerFuncs = []; +$functionName = ""; + +foreach($enumsUsed as $enumMembers){ + $writerF = buildEnumWriterFunc($enumMembers, $functionName); + /** @var string $functionName */ + $writerFuncs[$functionName] = $writerF; + $readerF = buildEnumReaderFunc($enumMembers, $functionName); + /** @var string $functionName */ + $readerFuncs[$functionName] = $readerF; +} + +/** + * @param string[][] $functions + * @phpstan-param array> $functions + */ +function printFunctions(array $functions, string $className) : void{ + ksort($functions, SORT_STRING); + + ob_start(); + + echo <<<'HEADER' + "\t" . implode("\n\t", $functionLines), $functions)); + echo "\n\n}\n"; + + file_put_contents(dirname(__DIR__) . '/src/data/runtime/' . $className . '.php', ob_get_clean()); +} + +printFunctions($writerFuncs, "RuntimeEnumSerializer"); +printFunctions($readerFuncs, "RuntimeEnumDeserializer"); + +echo "Done. Don't forget to run CS fixup after generating code.\n";