RuntimeDataDescriber: Support dynamically describing arbitrary enums (#6039)

Previously, we were using codegen to support describing a fixed set of enums.

Instead, we implement an enum() function, allowing any native PHP enum to be described.
All enums used in runtime data have been migrated to native PHP 8.1 enums in minor-next to facilitate this.

This implementation:

- is faster (in extreme cases by 40x, such as with PotionType)
- requires way less code
- does not require a build step
- is way more flexible

This fixes #5877, increasing the range of stuff that plugins are now able to do.

EnumTrait enums are not supported, as it's easier and cleaner to just support native enums. Most core EnumTrait enums have been migrated to native enums by now to facilitate this.
This commit is contained in:
Dylan T 2023-09-07 20:07:14 +01:00 committed by GitHub
parent 6887fcd590
commit 7dcd2592d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 188 additions and 795 deletions

View File

@ -152,9 +152,6 @@ jobs:
- name: Regenerate KnownTranslation APIs
run: php build/generate-known-translation-apis.php
- name: Regenerate RuntimeEnum(De)serializer
run: php build/generate-runtime-enum-serializers.php
- name: Regenerate BedrockData available files constants
run: php build/generate-bedrockdata-path-consts.php

View File

@ -1,265 +0,0 @@
<?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\build\generate_runtime_enum_serializers;
use pocketmine\block\utils\BellAttachmentType;
use pocketmine\block\utils\CopperOxidation;
use pocketmine\block\utils\CoralType;
use pocketmine\block\utils\DirtType;
use pocketmine\block\utils\DripleafState;
use pocketmine\block\utils\DyeColor;
use pocketmine\block\utils\FroglightType;
use pocketmine\block\utils\LeverFacing;
use pocketmine\block\utils\MobHeadType;
use pocketmine\block\utils\MushroomBlockType;
use pocketmine\block\utils\SlabType;
use pocketmine\item\MedicineType;
use pocketmine\item\PotionType;
use pocketmine\item\SuspiciousStewType;
use function array_key_first;
use function array_map;
use function ceil;
use function count;
use function dirname;
use function file_put_contents;
use function implode;
use function ksort;
use function lcfirst;
use function log;
use function ob_get_clean;
use function ob_start;
use function usort;
use const SORT_STRING;
require dirname(__DIR__) . '/vendor/autoload.php';
/**
* @param string[] $memberNames
* @phpstan-param list<string> $memberNames
*
* @return string[]
* @phpstan-return list<string>
*/
function buildWriterFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string $functionName) : array{
$bits = getBitsRequired($memberNames);
$lines = [];
$lines[] = "public function $functionName(\\$nativeTypeName &\$value) : void{";
$lines[] = "\t\$this->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<string> $memberNames
*
* @return string[]
* @phpstan-return list<string>
*/
function buildReaderFunc(string $virtualTypeName, string $nativeTypeName, array $memberNames, string $functionName) : array{
$bits = getBitsRequired($memberNames);
$lines = [];
$lines[] = "public function $functionName(\\$nativeTypeName &\$value) : void{";
$lines[] = "\t\$value = match(\$this->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;
}
function buildInterfaceFunc(string $nativeTypeName, string $functionName) : string{
return "public function $functionName(\\$nativeTypeName &\$value) : void;";
}
/**
* @param string[] $memberNames
* @phpstan-param list<string> $memberNames
*
* @return string[]
* @phpstan-return list<string>
*/
function buildSizeCalculationFunc(string $nativeTypeName, string $functionName, array $memberNames) : array{
$lines = [];
$lines[] = "public function $functionName(\\$nativeTypeName &\$value) : void{";
$lines[] = "\t\$this->addBits(" . getBitsRequired($memberNames) . ");";
$lines[] = "}";
return $lines;
}
/**
* @param mixed[] $members
*/
function getBitsRequired(array $members) : int{
return (int) ceil(log(count($members), 2));
}
/**
* @param \UnitEnum[] $members
* @phpstan-param list<\UnitEnum> $members
*
* @return string[]
* @phpstan-return list<string>
*/
function stringifyEnumMembers(array $members, string $enumClass) : array{
usort($members, fn(\UnitEnum $a, \UnitEnum $b) => $a->name <=> $b->name);
return array_map(fn(\UnitEnum $case) => "\\$enumClass::$case->name", $members);
}
$enumsUsed = [
BellAttachmentType::cases(),
CopperOxidation::cases(),
CoralType::cases(),
DirtType::cases(),
DripleafState::cases(),
DyeColor::cases(),
FroglightType::cases(),
LeverFacing::cases(),
MedicineType::cases(),
MushroomBlockType::cases(),
MobHeadType::cases(),
SlabType::cases(),
SuspiciousStewType::cases(),
PotionType::cases()
];
$readerFuncs = [
"" => [
"abstract protected function readInt(int \$bits) : int;"
]
];
$writerFuncs = [
"" => [
"abstract protected function writeInt(int \$bits, int \$value) : void;"
]
];
$interfaceFuncs = [];
$sizeCalculationFuncs = [
"" => [
"abstract protected function addBits(int \$bits) : void;"
]
];
foreach($enumsUsed as $enumMembers){
if(count($enumMembers) === 0){
throw new \InvalidArgumentException("Enum members cannot be empty");
}
$reflect = new \ReflectionClass($enumMembers[array_key_first($enumMembers)]);
$virtualTypeName = $reflect->getShortName();
$nativeTypeName = $reflect->getName();
$functionName = lcfirst($virtualTypeName);
$stringifiedMembers = stringifyEnumMembers($enumMembers, $nativeTypeName);
$writerFuncs[$functionName] = buildWriterFunc(
$virtualTypeName,
$nativeTypeName,
$stringifiedMembers,
$functionName
);
$readerFuncs[$functionName] = buildReaderFunc(
$virtualTypeName,
$nativeTypeName,
$stringifiedMembers,
$functionName
);
$interfaceFuncs[$functionName] = [buildInterfaceFunc(
$nativeTypeName,
$functionName
)];
$sizeCalculationFuncs[$functionName] = buildSizeCalculationFunc(
$nativeTypeName,
$functionName,
$stringifiedMembers
);
}
/**
* @param string[][] $functions
* @phpstan-param array<string, list<string>> $functions
*/
function printFunctions(array $functions, string $className, string $classType) : void{
ksort($functions, SORT_STRING);
ob_start();
echo <<<'HEADER'
<?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\runtime;
/**
* This class is auto-generated. Do not edit it manually.
* @see build/generate-runtime-enum-serializers.php
*/
HEADER;
echo "$classType $className{\n\n";
echo implode("\n\n", array_map(fn(array $functionLines) => "\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, "RuntimeEnumSerializerTrait", "trait");
printFunctions($readerFuncs, "RuntimeEnumDeserializerTrait", "trait");
printFunctions($interfaceFuncs, "RuntimeEnumDescriber", "interface");
printFunctions($sizeCalculationFuncs, "RuntimeEnumSizeCalculatorTrait", "trait");
echo "Done. Don't forget to run CS fixup after generating code.\n";

View File

@ -44,7 +44,7 @@ final class Bell extends Transparent{
private BellAttachmentType $attachmentType = BellAttachmentType::FLOOR;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->bellAttachmentType($this->attachmentType);
$w->enum($this->attachmentType);
$w->horizontalFacing($this->facing);
}

View File

@ -39,7 +39,7 @@ class BigDripleafHead extends BaseBigDripleaf{
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
parent::describeBlockOnlyState($w);
$w->dripleafState($this->leafState);
$w->enum($this->leafState);
}
protected function isHead() : bool{

View File

@ -41,7 +41,7 @@ class Dirt extends Opaque{
protected DirtType $dirtType = DirtType::NORMAL;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->dirtType($this->dirtType);
$w->enum($this->dirtType);
}
public function getDirtType() : DirtType{ return $this->dirtType; }

View File

@ -31,7 +31,7 @@ final class Froglight extends SimplePillar{
private FroglightType $froglightType = FroglightType::OCHRE;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->froglightType($this->froglightType);
$w->enum($this->froglightType);
}
public function getFroglightType() : FroglightType{ return $this->froglightType; }

View File

@ -40,7 +40,7 @@ class Lever extends Flowable{
protected bool $activated = false;
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->leverFacing($this->facing);
$w->enum($this->facing);
$w->bool($this->activated);
}

View File

@ -45,7 +45,7 @@ class MobHead extends Flowable{
protected int $rotation = self::MIN_ROTATION; //TODO: split this into floor skull and wall skull handling
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->mobHeadType($this->mobHeadType);
$w->enum($this->mobHeadType);
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{

View File

@ -34,7 +34,7 @@ class RedMushroomBlock extends Opaque{
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
//these blocks always drop as all-cap, but may exist in other forms in the inventory (particularly creative),
//so this information needs to be kept in the type info
$w->mushroomBlockType($this->mushroomBlockType);
$w->enum($this->mushroomBlockType);
}
public function getMushroomBlockType() : MushroomBlockType{ return $this->mushroomBlockType; }

View File

@ -41,7 +41,7 @@ class Slab extends Transparent{
}
protected function describeBlockOnlyState(RuntimeDataDescriber $w) : void{
$w->slabType($this->slabType);
$w->enum($this->slabType);
}
public function isTransparent() : bool{

View File

@ -32,7 +32,7 @@ trait ColoredTrait{
/** @see Block::describeBlockItemState() */
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->dyeColor($this->color);
$w->enum($this->color);
}
public function getColor() : DyeColor{ return $this->color; }

View File

@ -38,7 +38,7 @@ trait CopperTrait{
private bool $waxed = false;
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->copperOxidation($this->oxidation);
$w->enum($this->oxidation);
$w->bool($this->waxed);
}

View File

@ -32,7 +32,7 @@ trait CoralTypeTrait{
/** @see Block::describeBlockItemState() */
public function describeBlockItemState(RuntimeDataDescriber $w) : void{
$w->coralType($this->coralType);
$w->enum($this->coralType);
$w->bool($this->dead);
}

View File

@ -24,67 +24,71 @@ declare(strict_types=1);
namespace pocketmine\data\runtime;
/**
* This class is auto-generated. Do not edit it manually.
* @see build/generate-runtime-enum-serializers.php
* Provides backwards-compatible shims for the old codegen'd enum describer methods.
* This is kept for plugin backwards compatibility, but these functions should not be used in new code.
* @deprecated
*/
trait RuntimeEnumSizeCalculatorTrait{
trait LegacyRuntimeEnumDescriberTrait{
abstract protected function addBits(int $bits) : void;
/**
* @phpstan-template T of \UnitEnum
* @phpstan-param T $case
*/
abstract protected function enum(\UnitEnum &$case) : void;
public function bellAttachmentType(\pocketmine\block\utils\BellAttachmentType &$value) : void{
$this->addBits(2);
$this->enum($value);
}
public function copperOxidation(\pocketmine\block\utils\CopperOxidation &$value) : void{
$this->addBits(2);
$this->enum($value);
}
public function coralType(\pocketmine\block\utils\CoralType &$value) : void{
$this->addBits(3);
$this->enum($value);
}
public function dirtType(\pocketmine\block\utils\DirtType &$value) : void{
$this->addBits(2);
$this->enum($value);
}
public function dripleafState(\pocketmine\block\utils\DripleafState &$value) : void{
$this->addBits(2);
$this->enum($value);
}
public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void{
$this->addBits(4);
$this->enum($value);
}
public function froglightType(\pocketmine\block\utils\FroglightType &$value) : void{
$this->addBits(2);
$this->enum($value);
}
public function leverFacing(\pocketmine\block\utils\LeverFacing &$value) : void{
$this->addBits(3);
$this->enum($value);
}
public function medicineType(\pocketmine\item\MedicineType &$value) : void{
$this->addBits(2);
$this->enum($value);
}
public function mobHeadType(\pocketmine\block\utils\MobHeadType &$value) : void{
$this->addBits(3);
$this->enum($value);
}
public function mushroomBlockType(\pocketmine\block\utils\MushroomBlockType &$value) : void{
$this->addBits(4);
$this->enum($value);
}
public function potionType(\pocketmine\item\PotionType &$value) : void{
$this->addBits(6);
$this->enum($value);
}
public function slabType(\pocketmine\block\utils\SlabType &$value) : void{
$this->addBits(2);
$this->enum($value);
}
public function suspiciousStewType(\pocketmine\item\SuspiciousStewType &$value) : void{
$this->addBits(4);
$this->enum($value);
}
}

View File

@ -77,4 +77,10 @@ interface RuntimeDataDescriber extends RuntimeEnumDescriber{
public function railShape(int &$railShape) : void;
public function straightOnlyRailShape(int &$railShape) : void;
/**
* @phpstan-template T of \UnitEnum
* @phpstan-param T $case
*/
public function enum(\UnitEnum &$case) : void;
}

View File

@ -29,11 +29,12 @@ use pocketmine\block\utils\WallConnectionType;
use pocketmine\math\Axis;
use pocketmine\math\Facing;
use pocketmine\utils\AssumptionFailedError;
use function get_class;
use function intdiv;
use function spl_object_id;
final class RuntimeDataReader implements RuntimeDataDescriber{
use RuntimeEnumDeserializerTrait;
use LegacyRuntimeEnumDescriberTrait;
private int $offset = 0;
@ -210,5 +211,16 @@ final class RuntimeDataReader implements RuntimeDataDescriber{
$railShape = $result;
}
public function enum(\UnitEnum &$case) : void{
$metadata = RuntimeEnumMetadata::from($case);
$raw = $this->readInt($metadata->bits);
$result = $metadata->intToEnum($raw);
if($result === null){
throw new InvalidSerializedRuntimeDataException("Invalid serialized value $raw for " . get_class($case));
}
$case = $result;
}
public function getOffset() : int{ return $this->offset; }
}

View File

@ -28,7 +28,7 @@ use pocketmine\math\Facing;
use function count;
final class RuntimeDataSizeCalculator implements RuntimeDataDescriber{
use RuntimeEnumSizeCalculatorTrait;
use LegacyRuntimeEnumDescriberTrait;
private int $bits = 0;
@ -95,4 +95,9 @@ final class RuntimeDataSizeCalculator implements RuntimeDataDescriber{
public function straightOnlyRailShape(int &$railShape) : void{
$this->addBits(3);
}
public function enum(\UnitEnum &$case) : void{
$metadata = RuntimeEnumMetadata::from($case);
$this->addBits($metadata->bits);
}
}

View File

@ -31,7 +31,7 @@ use function array_flip;
use function spl_object_id;
final class RuntimeDataWriter implements RuntimeDataDescriber{
use RuntimeEnumSerializerTrait;
use LegacyRuntimeEnumDescriberTrait;
private int $value = 0;
private int $offset = 0;
@ -174,6 +174,11 @@ final class RuntimeDataWriter implements RuntimeDataDescriber{
$this->int(3, $railShape);
}
public function enum(\UnitEnum &$case) : void{
$metadata = RuntimeEnumMetadata::from($case);
$this->writeInt($metadata->bits, $metadata->enumToInt($case));
}
public function getValue() : int{ return $this->value; }
public function getOffset() : int{ return $this->offset; }

View File

@ -24,8 +24,9 @@ declare(strict_types=1);
namespace pocketmine\data\runtime;
/**
* This class is auto-generated. Do not edit it manually.
* @see build/generate-runtime-enum-serializers.php
* Provides backwards-compatible shims for the old codegen'd enum describer methods.
* This is kept for plugin backwards compatibility, but these functions should not be used in new code.
* @deprecated
*/
interface RuntimeEnumDescriber{

View File

@ -1,243 +0,0 @@
<?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\runtime;
/**
* This class is auto-generated. Do not edit it manually.
* @see build/generate-runtime-enum-serializers.php
*/
trait RuntimeEnumDeserializerTrait{
abstract protected function readInt(int $bits) : int;
public function bellAttachmentType(\pocketmine\block\utils\BellAttachmentType &$value) : void{
$value = match($this->readInt(2)){
0 => \pocketmine\block\utils\BellAttachmentType::CEILING,
1 => \pocketmine\block\utils\BellAttachmentType::FLOOR,
2 => \pocketmine\block\utils\BellAttachmentType::ONE_WALL,
3 => \pocketmine\block\utils\BellAttachmentType::TWO_WALLS,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for BellAttachmentType")
};
}
public function copperOxidation(\pocketmine\block\utils\CopperOxidation &$value) : void{
$value = match($this->readInt(2)){
0 => \pocketmine\block\utils\CopperOxidation::EXPOSED,
1 => \pocketmine\block\utils\CopperOxidation::NONE,
2 => \pocketmine\block\utils\CopperOxidation::OXIDIZED,
3 => \pocketmine\block\utils\CopperOxidation::WEATHERED,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for CopperOxidation")
};
}
public function coralType(\pocketmine\block\utils\CoralType &$value) : void{
$value = match($this->readInt(3)){
0 => \pocketmine\block\utils\CoralType::BRAIN,
1 => \pocketmine\block\utils\CoralType::BUBBLE,
2 => \pocketmine\block\utils\CoralType::FIRE,
3 => \pocketmine\block\utils\CoralType::HORN,
4 => \pocketmine\block\utils\CoralType::TUBE,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for CoralType")
};
}
public function dirtType(\pocketmine\block\utils\DirtType &$value) : void{
$value = match($this->readInt(2)){
0 => \pocketmine\block\utils\DirtType::COARSE,
1 => \pocketmine\block\utils\DirtType::NORMAL,
2 => \pocketmine\block\utils\DirtType::ROOTED,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for DirtType")
};
}
public function dripleafState(\pocketmine\block\utils\DripleafState &$value) : void{
$value = match($this->readInt(2)){
0 => \pocketmine\block\utils\DripleafState::FULL_TILT,
1 => \pocketmine\block\utils\DripleafState::PARTIAL_TILT,
2 => \pocketmine\block\utils\DripleafState::STABLE,
3 => \pocketmine\block\utils\DripleafState::UNSTABLE,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for DripleafState")
};
}
public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void{
$value = match($this->readInt(4)){
0 => \pocketmine\block\utils\DyeColor::BLACK,
1 => \pocketmine\block\utils\DyeColor::BLUE,
2 => \pocketmine\block\utils\DyeColor::BROWN,
3 => \pocketmine\block\utils\DyeColor::CYAN,
4 => \pocketmine\block\utils\DyeColor::GRAY,
5 => \pocketmine\block\utils\DyeColor::GREEN,
6 => \pocketmine\block\utils\DyeColor::LIGHT_BLUE,
7 => \pocketmine\block\utils\DyeColor::LIGHT_GRAY,
8 => \pocketmine\block\utils\DyeColor::LIME,
9 => \pocketmine\block\utils\DyeColor::MAGENTA,
10 => \pocketmine\block\utils\DyeColor::ORANGE,
11 => \pocketmine\block\utils\DyeColor::PINK,
12 => \pocketmine\block\utils\DyeColor::PURPLE,
13 => \pocketmine\block\utils\DyeColor::RED,
14 => \pocketmine\block\utils\DyeColor::WHITE,
15 => \pocketmine\block\utils\DyeColor::YELLOW,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for DyeColor")
};
}
public function froglightType(\pocketmine\block\utils\FroglightType &$value) : void{
$value = match($this->readInt(2)){
0 => \pocketmine\block\utils\FroglightType::OCHRE,
1 => \pocketmine\block\utils\FroglightType::PEARLESCENT,
2 => \pocketmine\block\utils\FroglightType::VERDANT,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for FroglightType")
};
}
public function leverFacing(\pocketmine\block\utils\LeverFacing &$value) : void{
$value = match($this->readInt(3)){
0 => \pocketmine\block\utils\LeverFacing::DOWN_AXIS_X,
1 => \pocketmine\block\utils\LeverFacing::DOWN_AXIS_Z,
2 => \pocketmine\block\utils\LeverFacing::EAST,
3 => \pocketmine\block\utils\LeverFacing::NORTH,
4 => \pocketmine\block\utils\LeverFacing::SOUTH,
5 => \pocketmine\block\utils\LeverFacing::UP_AXIS_X,
6 => \pocketmine\block\utils\LeverFacing::UP_AXIS_Z,
7 => \pocketmine\block\utils\LeverFacing::WEST,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for LeverFacing")
};
}
public function medicineType(\pocketmine\item\MedicineType &$value) : void{
$value = match($this->readInt(2)){
0 => \pocketmine\item\MedicineType::ANTIDOTE,
1 => \pocketmine\item\MedicineType::ELIXIR,
2 => \pocketmine\item\MedicineType::EYE_DROPS,
3 => \pocketmine\item\MedicineType::TONIC,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for MedicineType")
};
}
public function mobHeadType(\pocketmine\block\utils\MobHeadType &$value) : void{
$value = match($this->readInt(3)){
0 => \pocketmine\block\utils\MobHeadType::CREEPER,
1 => \pocketmine\block\utils\MobHeadType::DRAGON,
2 => \pocketmine\block\utils\MobHeadType::PIGLIN,
3 => \pocketmine\block\utils\MobHeadType::PLAYER,
4 => \pocketmine\block\utils\MobHeadType::SKELETON,
5 => \pocketmine\block\utils\MobHeadType::WITHER_SKELETON,
6 => \pocketmine\block\utils\MobHeadType::ZOMBIE,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for MobHeadType")
};
}
public function mushroomBlockType(\pocketmine\block\utils\MushroomBlockType &$value) : void{
$value = match($this->readInt(4)){
0 => \pocketmine\block\utils\MushroomBlockType::ALL_CAP,
1 => \pocketmine\block\utils\MushroomBlockType::CAP_EAST,
2 => \pocketmine\block\utils\MushroomBlockType::CAP_MIDDLE,
3 => \pocketmine\block\utils\MushroomBlockType::CAP_NORTH,
4 => \pocketmine\block\utils\MushroomBlockType::CAP_NORTHEAST,
5 => \pocketmine\block\utils\MushroomBlockType::CAP_NORTHWEST,
6 => \pocketmine\block\utils\MushroomBlockType::CAP_SOUTH,
7 => \pocketmine\block\utils\MushroomBlockType::CAP_SOUTHEAST,
8 => \pocketmine\block\utils\MushroomBlockType::CAP_SOUTHWEST,
9 => \pocketmine\block\utils\MushroomBlockType::CAP_WEST,
10 => \pocketmine\block\utils\MushroomBlockType::PORES,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for MushroomBlockType")
};
}
public function potionType(\pocketmine\item\PotionType &$value) : void{
$value = match($this->readInt(6)){
0 => \pocketmine\item\PotionType::AWKWARD,
1 => \pocketmine\item\PotionType::FIRE_RESISTANCE,
2 => \pocketmine\item\PotionType::HARMING,
3 => \pocketmine\item\PotionType::HEALING,
4 => \pocketmine\item\PotionType::INVISIBILITY,
5 => \pocketmine\item\PotionType::LEAPING,
6 => \pocketmine\item\PotionType::LONG_FIRE_RESISTANCE,
7 => \pocketmine\item\PotionType::LONG_INVISIBILITY,
8 => \pocketmine\item\PotionType::LONG_LEAPING,
9 => \pocketmine\item\PotionType::LONG_MUNDANE,
10 => \pocketmine\item\PotionType::LONG_NIGHT_VISION,
11 => \pocketmine\item\PotionType::LONG_POISON,
12 => \pocketmine\item\PotionType::LONG_REGENERATION,
13 => \pocketmine\item\PotionType::LONG_SLOWNESS,
14 => \pocketmine\item\PotionType::LONG_SLOW_FALLING,
15 => \pocketmine\item\PotionType::LONG_STRENGTH,
16 => \pocketmine\item\PotionType::LONG_SWIFTNESS,
17 => \pocketmine\item\PotionType::LONG_TURTLE_MASTER,
18 => \pocketmine\item\PotionType::LONG_WATER_BREATHING,
19 => \pocketmine\item\PotionType::LONG_WEAKNESS,
20 => \pocketmine\item\PotionType::MUNDANE,
21 => \pocketmine\item\PotionType::NIGHT_VISION,
22 => \pocketmine\item\PotionType::POISON,
23 => \pocketmine\item\PotionType::REGENERATION,
24 => \pocketmine\item\PotionType::SLOWNESS,
25 => \pocketmine\item\PotionType::SLOW_FALLING,
26 => \pocketmine\item\PotionType::STRENGTH,
27 => \pocketmine\item\PotionType::STRONG_HARMING,
28 => \pocketmine\item\PotionType::STRONG_HEALING,
29 => \pocketmine\item\PotionType::STRONG_LEAPING,
30 => \pocketmine\item\PotionType::STRONG_POISON,
31 => \pocketmine\item\PotionType::STRONG_REGENERATION,
32 => \pocketmine\item\PotionType::STRONG_SLOWNESS,
33 => \pocketmine\item\PotionType::STRONG_STRENGTH,
34 => \pocketmine\item\PotionType::STRONG_SWIFTNESS,
35 => \pocketmine\item\PotionType::STRONG_TURTLE_MASTER,
36 => \pocketmine\item\PotionType::SWIFTNESS,
37 => \pocketmine\item\PotionType::THICK,
38 => \pocketmine\item\PotionType::TURTLE_MASTER,
39 => \pocketmine\item\PotionType::WATER,
40 => \pocketmine\item\PotionType::WATER_BREATHING,
41 => \pocketmine\item\PotionType::WEAKNESS,
42 => \pocketmine\item\PotionType::WITHER,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for PotionType")
};
}
public function slabType(\pocketmine\block\utils\SlabType &$value) : void{
$value = match($this->readInt(2)){
0 => \pocketmine\block\utils\SlabType::BOTTOM,
1 => \pocketmine\block\utils\SlabType::DOUBLE,
2 => \pocketmine\block\utils\SlabType::TOP,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for SlabType")
};
}
public function suspiciousStewType(\pocketmine\item\SuspiciousStewType &$value) : void{
$value = match($this->readInt(4)){
0 => \pocketmine\item\SuspiciousStewType::ALLIUM,
1 => \pocketmine\item\SuspiciousStewType::AZURE_BLUET,
2 => \pocketmine\item\SuspiciousStewType::BLUE_ORCHID,
3 => \pocketmine\item\SuspiciousStewType::CORNFLOWER,
4 => \pocketmine\item\SuspiciousStewType::DANDELION,
5 => \pocketmine\item\SuspiciousStewType::LILY_OF_THE_VALLEY,
6 => \pocketmine\item\SuspiciousStewType::OXEYE_DAISY,
7 => \pocketmine\item\SuspiciousStewType::POPPY,
8 => \pocketmine\item\SuspiciousStewType::TULIP,
9 => \pocketmine\item\SuspiciousStewType::WITHER_ROSE,
default => throw new InvalidSerializedRuntimeDataException("Invalid serialized value for SuspiciousStewType")
};
}
}

View File

@ -0,0 +1,114 @@
<?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\runtime;
use function array_values;
use function ceil;
use function count;
use function log;
use function spl_object_id;
use function usort;
/**
* A big hack to allow lazily associating enum cases with packed bit values for RuntimeDataDescriber :)
*
* @internal
* @phpstan-template T of \UnitEnum
*/
final class RuntimeEnumMetadata{
public readonly int $bits;
/**
* @var object[]
* @phpstan-var list<T>
*/
private readonly array $intToEnum;
/**
* @var int[]
* @phpstan-var array<int, int>
*/
private readonly array $enumToInt;
/**
* @param \UnitEnum[] $members
* @phpstan-param list<T> $members
*/
public function __construct(
array $members
){
usort($members, fn(\UnitEnum $a, \UnitEnum $b) => $a->name <=> $b->name); //sort by name to ensure consistent ordering (and thus consistent bit assignments)
$this->bits = (int) ceil(log(count($members), 2));
$this->intToEnum = array_values($members);
$reversed = [];
foreach($this->intToEnum as $int => $enum){
$reversed[spl_object_id($enum)] = $int;
}
$this->enumToInt = $reversed;
}
/**
* @phpstan-return T|null
*/
public function intToEnum(int $value) : ?object{
return $this->intToEnum[$value] ?? null;
}
/**
* @phpstan-param T $enum
*/
public function enumToInt(object $enum) : int{
return $this->enumToInt[spl_object_id($enum)];
}
/**
* @var self[]
* @phpstan-var array<class-string, object>
*/
private static array $cache = [];
/**
* @phpstan-template TEnum of \UnitEnum
* @phpstan-param TEnum $case
*
* @phpstan-return self<TEnum>
*/
public static function from(\UnitEnum $case) : self{
$class = $case::class;
/** @phpstan-var self<TEnum>|null $metadata */
$metadata = self::$cache[$class] ?? null;
if($metadata === null){
/**
* PHPStan can't infer this correctly :( https://github.com/phpstan/phpstan/issues/7162
* @phpstan-var list<TEnum> $cases
*/
$cases = $case::cases();
self::$cache[$class] = $metadata = new self($cases);
}
return $metadata;
}
}

View File

@ -1,243 +0,0 @@
<?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\runtime;
/**
* This class is auto-generated. Do not edit it manually.
* @see build/generate-runtime-enum-serializers.php
*/
trait RuntimeEnumSerializerTrait{
abstract protected function writeInt(int $bits, int $value) : void;
public function bellAttachmentType(\pocketmine\block\utils\BellAttachmentType &$value) : void{
$this->writeInt(2, match($value){
\pocketmine\block\utils\BellAttachmentType::CEILING => 0,
\pocketmine\block\utils\BellAttachmentType::FLOOR => 1,
\pocketmine\block\utils\BellAttachmentType::ONE_WALL => 2,
\pocketmine\block\utils\BellAttachmentType::TWO_WALLS => 3,
default => throw new \pocketmine\utils\AssumptionFailedError("All BellAttachmentType cases should be covered")
});
}
public function copperOxidation(\pocketmine\block\utils\CopperOxidation &$value) : void{
$this->writeInt(2, match($value){
\pocketmine\block\utils\CopperOxidation::EXPOSED => 0,
\pocketmine\block\utils\CopperOxidation::NONE => 1,
\pocketmine\block\utils\CopperOxidation::OXIDIZED => 2,
\pocketmine\block\utils\CopperOxidation::WEATHERED => 3,
default => throw new \pocketmine\utils\AssumptionFailedError("All CopperOxidation cases should be covered")
});
}
public function coralType(\pocketmine\block\utils\CoralType &$value) : void{
$this->writeInt(3, match($value){
\pocketmine\block\utils\CoralType::BRAIN => 0,
\pocketmine\block\utils\CoralType::BUBBLE => 1,
\pocketmine\block\utils\CoralType::FIRE => 2,
\pocketmine\block\utils\CoralType::HORN => 3,
\pocketmine\block\utils\CoralType::TUBE => 4,
default => throw new \pocketmine\utils\AssumptionFailedError("All CoralType cases should be covered")
});
}
public function dirtType(\pocketmine\block\utils\DirtType &$value) : void{
$this->writeInt(2, match($value){
\pocketmine\block\utils\DirtType::COARSE => 0,
\pocketmine\block\utils\DirtType::NORMAL => 1,
\pocketmine\block\utils\DirtType::ROOTED => 2,
default => throw new \pocketmine\utils\AssumptionFailedError("All DirtType cases should be covered")
});
}
public function dripleafState(\pocketmine\block\utils\DripleafState &$value) : void{
$this->writeInt(2, match($value){
\pocketmine\block\utils\DripleafState::FULL_TILT => 0,
\pocketmine\block\utils\DripleafState::PARTIAL_TILT => 1,
\pocketmine\block\utils\DripleafState::STABLE => 2,
\pocketmine\block\utils\DripleafState::UNSTABLE => 3,
default => throw new \pocketmine\utils\AssumptionFailedError("All DripleafState cases should be covered")
});
}
public function dyeColor(\pocketmine\block\utils\DyeColor &$value) : void{
$this->writeInt(4, match($value){
\pocketmine\block\utils\DyeColor::BLACK => 0,
\pocketmine\block\utils\DyeColor::BLUE => 1,
\pocketmine\block\utils\DyeColor::BROWN => 2,
\pocketmine\block\utils\DyeColor::CYAN => 3,
\pocketmine\block\utils\DyeColor::GRAY => 4,
\pocketmine\block\utils\DyeColor::GREEN => 5,
\pocketmine\block\utils\DyeColor::LIGHT_BLUE => 6,
\pocketmine\block\utils\DyeColor::LIGHT_GRAY => 7,
\pocketmine\block\utils\DyeColor::LIME => 8,
\pocketmine\block\utils\DyeColor::MAGENTA => 9,
\pocketmine\block\utils\DyeColor::ORANGE => 10,
\pocketmine\block\utils\DyeColor::PINK => 11,
\pocketmine\block\utils\DyeColor::PURPLE => 12,
\pocketmine\block\utils\DyeColor::RED => 13,
\pocketmine\block\utils\DyeColor::WHITE => 14,
\pocketmine\block\utils\DyeColor::YELLOW => 15,
default => throw new \pocketmine\utils\AssumptionFailedError("All DyeColor cases should be covered")
});
}
public function froglightType(\pocketmine\block\utils\FroglightType &$value) : void{
$this->writeInt(2, match($value){
\pocketmine\block\utils\FroglightType::OCHRE => 0,
\pocketmine\block\utils\FroglightType::PEARLESCENT => 1,
\pocketmine\block\utils\FroglightType::VERDANT => 2,
default => throw new \pocketmine\utils\AssumptionFailedError("All FroglightType cases should be covered")
});
}
public function leverFacing(\pocketmine\block\utils\LeverFacing &$value) : void{
$this->writeInt(3, match($value){
\pocketmine\block\utils\LeverFacing::DOWN_AXIS_X => 0,
\pocketmine\block\utils\LeverFacing::DOWN_AXIS_Z => 1,
\pocketmine\block\utils\LeverFacing::EAST => 2,
\pocketmine\block\utils\LeverFacing::NORTH => 3,
\pocketmine\block\utils\LeverFacing::SOUTH => 4,
\pocketmine\block\utils\LeverFacing::UP_AXIS_X => 5,
\pocketmine\block\utils\LeverFacing::UP_AXIS_Z => 6,
\pocketmine\block\utils\LeverFacing::WEST => 7,
default => throw new \pocketmine\utils\AssumptionFailedError("All LeverFacing cases should be covered")
});
}
public function medicineType(\pocketmine\item\MedicineType &$value) : void{
$this->writeInt(2, match($value){
\pocketmine\item\MedicineType::ANTIDOTE => 0,
\pocketmine\item\MedicineType::ELIXIR => 1,
\pocketmine\item\MedicineType::EYE_DROPS => 2,
\pocketmine\item\MedicineType::TONIC => 3,
default => throw new \pocketmine\utils\AssumptionFailedError("All MedicineType cases should be covered")
});
}
public function mobHeadType(\pocketmine\block\utils\MobHeadType &$value) : void{
$this->writeInt(3, match($value){
\pocketmine\block\utils\MobHeadType::CREEPER => 0,
\pocketmine\block\utils\MobHeadType::DRAGON => 1,
\pocketmine\block\utils\MobHeadType::PIGLIN => 2,
\pocketmine\block\utils\MobHeadType::PLAYER => 3,
\pocketmine\block\utils\MobHeadType::SKELETON => 4,
\pocketmine\block\utils\MobHeadType::WITHER_SKELETON => 5,
\pocketmine\block\utils\MobHeadType::ZOMBIE => 6,
default => throw new \pocketmine\utils\AssumptionFailedError("All MobHeadType cases should be covered")
});
}
public function mushroomBlockType(\pocketmine\block\utils\MushroomBlockType &$value) : void{
$this->writeInt(4, match($value){
\pocketmine\block\utils\MushroomBlockType::ALL_CAP => 0,
\pocketmine\block\utils\MushroomBlockType::CAP_EAST => 1,
\pocketmine\block\utils\MushroomBlockType::CAP_MIDDLE => 2,
\pocketmine\block\utils\MushroomBlockType::CAP_NORTH => 3,
\pocketmine\block\utils\MushroomBlockType::CAP_NORTHEAST => 4,
\pocketmine\block\utils\MushroomBlockType::CAP_NORTHWEST => 5,
\pocketmine\block\utils\MushroomBlockType::CAP_SOUTH => 6,
\pocketmine\block\utils\MushroomBlockType::CAP_SOUTHEAST => 7,
\pocketmine\block\utils\MushroomBlockType::CAP_SOUTHWEST => 8,
\pocketmine\block\utils\MushroomBlockType::CAP_WEST => 9,
\pocketmine\block\utils\MushroomBlockType::PORES => 10,
default => throw new \pocketmine\utils\AssumptionFailedError("All MushroomBlockType cases should be covered")
});
}
public function potionType(\pocketmine\item\PotionType &$value) : void{
$this->writeInt(6, match($value){
\pocketmine\item\PotionType::AWKWARD => 0,
\pocketmine\item\PotionType::FIRE_RESISTANCE => 1,
\pocketmine\item\PotionType::HARMING => 2,
\pocketmine\item\PotionType::HEALING => 3,
\pocketmine\item\PotionType::INVISIBILITY => 4,
\pocketmine\item\PotionType::LEAPING => 5,
\pocketmine\item\PotionType::LONG_FIRE_RESISTANCE => 6,
\pocketmine\item\PotionType::LONG_INVISIBILITY => 7,
\pocketmine\item\PotionType::LONG_LEAPING => 8,
\pocketmine\item\PotionType::LONG_MUNDANE => 9,
\pocketmine\item\PotionType::LONG_NIGHT_VISION => 10,
\pocketmine\item\PotionType::LONG_POISON => 11,
\pocketmine\item\PotionType::LONG_REGENERATION => 12,
\pocketmine\item\PotionType::LONG_SLOWNESS => 13,
\pocketmine\item\PotionType::LONG_SLOW_FALLING => 14,
\pocketmine\item\PotionType::LONG_STRENGTH => 15,
\pocketmine\item\PotionType::LONG_SWIFTNESS => 16,
\pocketmine\item\PotionType::LONG_TURTLE_MASTER => 17,
\pocketmine\item\PotionType::LONG_WATER_BREATHING => 18,
\pocketmine\item\PotionType::LONG_WEAKNESS => 19,
\pocketmine\item\PotionType::MUNDANE => 20,
\pocketmine\item\PotionType::NIGHT_VISION => 21,
\pocketmine\item\PotionType::POISON => 22,
\pocketmine\item\PotionType::REGENERATION => 23,
\pocketmine\item\PotionType::SLOWNESS => 24,
\pocketmine\item\PotionType::SLOW_FALLING => 25,
\pocketmine\item\PotionType::STRENGTH => 26,
\pocketmine\item\PotionType::STRONG_HARMING => 27,
\pocketmine\item\PotionType::STRONG_HEALING => 28,
\pocketmine\item\PotionType::STRONG_LEAPING => 29,
\pocketmine\item\PotionType::STRONG_POISON => 30,
\pocketmine\item\PotionType::STRONG_REGENERATION => 31,
\pocketmine\item\PotionType::STRONG_SLOWNESS => 32,
\pocketmine\item\PotionType::STRONG_STRENGTH => 33,
\pocketmine\item\PotionType::STRONG_SWIFTNESS => 34,
\pocketmine\item\PotionType::STRONG_TURTLE_MASTER => 35,
\pocketmine\item\PotionType::SWIFTNESS => 36,
\pocketmine\item\PotionType::THICK => 37,
\pocketmine\item\PotionType::TURTLE_MASTER => 38,
\pocketmine\item\PotionType::WATER => 39,
\pocketmine\item\PotionType::WATER_BREATHING => 40,
\pocketmine\item\PotionType::WEAKNESS => 41,
\pocketmine\item\PotionType::WITHER => 42,
default => throw new \pocketmine\utils\AssumptionFailedError("All PotionType cases should be covered")
});
}
public function slabType(\pocketmine\block\utils\SlabType &$value) : void{
$this->writeInt(2, match($value){
\pocketmine\block\utils\SlabType::BOTTOM => 0,
\pocketmine\block\utils\SlabType::DOUBLE => 1,
\pocketmine\block\utils\SlabType::TOP => 2,
default => throw new \pocketmine\utils\AssumptionFailedError("All SlabType cases should be covered")
});
}
public function suspiciousStewType(\pocketmine\item\SuspiciousStewType &$value) : void{
$this->writeInt(4, match($value){
\pocketmine\item\SuspiciousStewType::ALLIUM => 0,
\pocketmine\item\SuspiciousStewType::AZURE_BLUET => 1,
\pocketmine\item\SuspiciousStewType::BLUE_ORCHID => 2,
\pocketmine\item\SuspiciousStewType::CORNFLOWER => 3,
\pocketmine\item\SuspiciousStewType::DANDELION => 4,
\pocketmine\item\SuspiciousStewType::LILY_OF_THE_VALLEY => 5,
\pocketmine\item\SuspiciousStewType::OXEYE_DAISY => 6,
\pocketmine\item\SuspiciousStewType::POPPY => 7,
\pocketmine\item\SuspiciousStewType::TULIP => 8,
\pocketmine\item\SuspiciousStewType::WITHER_ROSE => 9,
default => throw new \pocketmine\utils\AssumptionFailedError("All SuspiciousStewType cases should be covered")
});
}
}

View File

@ -58,7 +58,7 @@ class Banner extends ItemBlockWallOrFloor{
}
protected function describeState(RuntimeDataDescriber $w) : void{
$w->dyeColor($this->color);
$w->enum($this->color);
}
/**

View File

@ -30,7 +30,7 @@ class Dye extends Item{
private DyeColor $color = DyeColor::BLACK;
protected function describeState(RuntimeDataDescriber $w) : void{
$w->dyeColor($this->color);
$w->enum($this->color);
}
public function getColor() : DyeColor{

View File

@ -32,7 +32,7 @@ class Medicine extends Item implements ConsumableItem{
private MedicineType $medicineType = MedicineType::EYE_DROPS;
protected function describeState(RuntimeDataDescriber $w) : void{
$w->medicineType($this->medicineType);
$w->enum($this->medicineType);
}
public function getType() : MedicineType{ return $this->medicineType; }

View File

@ -32,7 +32,7 @@ class Potion extends Item implements ConsumableItem{
private PotionType $potionType = PotionType::WATER;
protected function describeState(RuntimeDataDescriber $w) : void{
$w->potionType($this->potionType);
$w->enum($this->potionType);
}
public function getType() : PotionType{ return $this->potionType; }

View File

@ -34,7 +34,7 @@ class SplashPotion extends ProjectileItem{
private PotionType $potionType = PotionType::WATER;
protected function describeState(RuntimeDataDescriber $w) : void{
$w->potionType($this->potionType);
$w->enum($this->potionType);
}
public function getType() : PotionType{ return $this->potionType; }

View File

@ -30,7 +30,7 @@ class SuspiciousStew extends Food{
private SuspiciousStewType $suspiciousStewType = SuspiciousStewType::POPPY;
protected function describeState(RuntimeDataDescriber $w) : void{
$w->suspiciousStewType($this->suspiciousStewType);
$w->enum($this->suspiciousStewType);
}
public function getType() : SuspiciousStewType{ return $this->suspiciousStewType; }