mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-12 22:45:28 +00:00
First look at enum-overloaded registry members
this allows us to reference registry members with dynamic enum cases, which has a lot of potential applications. this enables us to keep the blocks as unique types, but with some of the power of dynamic state properties. this is a rough initial proof of concept; it will need to be improved for further integration.
This commit is contained in:
parent
51cf6817b1
commit
1415e2492a
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\build\update_registry_annotations;
|
||||
|
||||
use pocketmine\utils\OverloadedRegistryMember;
|
||||
use pocketmine\utils\Utils;
|
||||
use function basename;
|
||||
use function class_exists;
|
||||
@ -47,11 +48,16 @@ if(count($argv) !== 2){
|
||||
exit(1);
|
||||
}
|
||||
|
||||
function makeTypehint(string $namespaceName, \ReflectionClass $class) : string{
|
||||
return $class->getNamespaceName() === $namespaceName ? $class->getShortName() : '\\' . $class->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object[] $members
|
||||
* @phpstan-param array<string, object> $members
|
||||
* @phpstan-param array<string, OverloadedRegistryMember> $overloadedMembers
|
||||
*/
|
||||
function generateMethodAnnotations(string $namespaceName, array $members) : string{
|
||||
function generateMethodAnnotations(string $namespaceName, array $members, array $overloadedMembers) : string{
|
||||
$selfName = basename(__FILE__);
|
||||
$lines = ["/**"];
|
||||
$lines[] = " * This doc-block is generated automatically, do not modify it manually.";
|
||||
@ -69,14 +75,19 @@ function generateMethodAnnotations(string $namespaceName, array $members) : stri
|
||||
}
|
||||
if($reflect === false){
|
||||
$typehint = "object";
|
||||
}elseif($reflect->getNamespaceName() === $namespaceName){
|
||||
$typehint = $reflect->getShortName();
|
||||
}else{
|
||||
$typehint = '\\' . $reflect->getName();
|
||||
$typehint = makeTypehint($namespaceName, $reflect);
|
||||
}
|
||||
$accessor = mb_strtoupper($name);
|
||||
$memberLines[$accessor] = sprintf($lineTmpl, $accessor, $typehint);
|
||||
}
|
||||
foreach(Utils::stringifyKeys($overloadedMembers) as $baseName => $member){
|
||||
$accessor = mb_strtoupper($baseName);
|
||||
$returnTypehint = makeTypehint($namespaceName, new \ReflectionClass($member->memberClass));
|
||||
$paramTypehint = makeTypehint($namespaceName, new \ReflectionClass($member->enumClass));
|
||||
|
||||
$memberLines[] = sprintf(" * @method static %s %s(%s)", $returnTypehint, $accessor, $paramTypehint);
|
||||
}
|
||||
ksort($memberLines, SORT_STRING);
|
||||
|
||||
foreach($memberLines as $line){
|
||||
@ -107,7 +118,7 @@ function processFile(string $file) : void{
|
||||
}
|
||||
echo "Found registry in $file\n";
|
||||
|
||||
$replacement = generateMethodAnnotations($matches[1], $className::getAll());
|
||||
$replacement = generateMethodAnnotations($matches[1], $className::getAll(), $className::getAllOverloaded());
|
||||
|
||||
$newContents = str_replace($docComment, $replacement, $contents);
|
||||
if($newContents !== $contents){
|
||||
|
@ -79,6 +79,10 @@ use function strtolower;
|
||||
* @see build/generate-registry-annotations.php
|
||||
* @generate-registry-docblock
|
||||
*
|
||||
* @method static Sapling SAPLING(\pocketmine\block\utils\SaplingType)
|
||||
* @method static Leaves LEAVES(\pocketmine\block\utils\LeavesType)
|
||||
* @method static FloorSign SIGN(\pocketmine\block\utils\WoodType)
|
||||
* @method static WallSign WALL_SIGN(\pocketmine\block\utils\WoodType)
|
||||
* @method static WoodenButton ACACIA_BUTTON()
|
||||
* @method static WoodenDoor ACACIA_DOOR()
|
||||
* @method static WoodenFence ACACIA_FENCE()
|
||||
@ -1231,10 +1235,12 @@ final class VanillaBlocks{
|
||||
$name = $saplingType->getDisplayName();
|
||||
self::register(strtolower($saplingType->name) . "_sapling", fn(BID $id) => new Sapling($id, $name . " Sapling", $saplingTypeInfo, $saplingType));
|
||||
}
|
||||
self::registerOverloaded("sapling", SaplingType::class, Sapling::class);
|
||||
foreach(LeavesType::cases() as $leavesType){
|
||||
$name = $leavesType->getDisplayName();
|
||||
self::register(strtolower($leavesType->name) . "_leaves", fn(BID $id) => new Leaves($id, $name . " Leaves", $leavesBreakInfo, $leavesType));
|
||||
}
|
||||
self::registerOverloaded("leaves", LeavesType::class, Leaves::class);
|
||||
|
||||
$sandstoneBreakInfo = new Info(BreakInfo::pickaxe(0.8, ToolTier::WOOD));
|
||||
self::register("red_sandstone_stairs", fn(BID $id) => new Stair($id, "Red Sandstone Stairs", $sandstoneBreakInfo));
|
||||
@ -1370,22 +1376,12 @@ final class VanillaBlocks{
|
||||
self::register($idName("pressure_plate"), fn(BID $id) => new WoodenPressurePlate($id, $name . " Pressure Plate", $woodenPressurePlateBreakInfo, $woodType, 20));
|
||||
self::register($idName("trapdoor"), fn(BID $id) => new WoodenTrapdoor($id, $name . " Trapdoor", $woodenDoorBreakInfo, $woodType));
|
||||
|
||||
$signAsItem = match($woodType){
|
||||
WoodType::OAK => VanillaItems::OAK_SIGN(...),
|
||||
WoodType::SPRUCE => VanillaItems::SPRUCE_SIGN(...),
|
||||
WoodType::BIRCH => VanillaItems::BIRCH_SIGN(...),
|
||||
WoodType::JUNGLE => VanillaItems::JUNGLE_SIGN(...),
|
||||
WoodType::ACACIA => VanillaItems::ACACIA_SIGN(...),
|
||||
WoodType::DARK_OAK => VanillaItems::DARK_OAK_SIGN(...),
|
||||
WoodType::MANGROVE => VanillaItems::MANGROVE_SIGN(...),
|
||||
WoodType::CRIMSON => VanillaItems::CRIMSON_SIGN(...),
|
||||
WoodType::WARPED => VanillaItems::WARPED_SIGN(...),
|
||||
WoodType::CHERRY => VanillaItems::CHERRY_SIGN(...),
|
||||
WoodType::PALE_OAK => VanillaItems::PALE_OAK_SIGN(...),
|
||||
};
|
||||
$signAsItem = fn() => VanillaItems::SIGN($woodType);
|
||||
self::register($idName("sign"), fn(BID $id) => new FloorSign($id, $name . " Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class);
|
||||
self::register($idName("wall_sign"), fn(BID $id) => new WallSign($id, $name . " Wall Sign", $signBreakInfo, $woodType, $signAsItem), TileSign::class);
|
||||
}
|
||||
self::registerOverloaded("sign", WoodType::class, FloorSign::class);
|
||||
self::registerOverloaded("wall_sign", WoodType::class, WallSign::class);
|
||||
}
|
||||
|
||||
private static function registerMushroomBlocks() : void{
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\item;
|
||||
|
||||
use pocketmine\block\utils\RecordType;
|
||||
use pocketmine\block\utils\WoodType;
|
||||
use pocketmine\block\VanillaBlocks as Blocks;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\entity\Location;
|
||||
@ -47,6 +48,8 @@ use function strtolower;
|
||||
* @see build/generate-registry-annotations.php
|
||||
* @generate-registry-docblock
|
||||
*
|
||||
* @method static Boat BOAT(BoatType)
|
||||
* @method static ItemBlockWallOrFloor SIGN(\pocketmine\block\utils\WoodType)
|
||||
* @method static Boat ACACIA_BOAT()
|
||||
* @method static ItemBlockWallOrFloor ACACIA_SIGN()
|
||||
* @method static ItemBlock AIR()
|
||||
@ -396,7 +399,6 @@ final class VanillaItems{
|
||||
//in the future we'll probably want to dissociate this from the air block and make a proper null item
|
||||
self::_registryRegister("air", Blocks::AIR()->asItem()->setCount(0));
|
||||
|
||||
self::register("acacia_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::ACACIA_SIGN(), Blocks::ACACIA_WALL_SIGN()));
|
||||
self::register("amethyst_shard", fn(IID $id) => new Item($id, "Amethyst Shard"));
|
||||
self::register("apple", fn(IID $id) => new Apple($id, "Apple"));
|
||||
self::register("arrow", fn(IID $id) => new Arrow($id, "Arrow"));
|
||||
@ -406,7 +408,6 @@ final class VanillaItems{
|
||||
self::register("beetroot", fn(IID $id) => new Beetroot($id, "Beetroot"));
|
||||
self::register("beetroot_seeds", fn(IID $id) => new BeetrootSeeds($id, "Beetroot Seeds"));
|
||||
self::register("beetroot_soup", fn(IID $id) => new BeetrootSoup($id, "Beetroot Soup"));
|
||||
self::register("birch_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::BIRCH_SIGN(), Blocks::BIRCH_WALL_SIGN()));
|
||||
self::register("blaze_powder", fn(IID $id) => new Item($id, "Blaze Powder"));
|
||||
self::register("blaze_rod", fn(IID $id) => new BlazeRod($id, "Blaze Rod"));
|
||||
self::register("bleach", fn(IID $id) => new Item($id, "Bleach"));
|
||||
@ -420,7 +421,6 @@ final class VanillaItems{
|
||||
self::register("bucket", fn(IID $id) => new Bucket($id, "Bucket"));
|
||||
self::register("carrot", fn(IID $id) => new Carrot($id, "Carrot"));
|
||||
self::register("charcoal", fn(IID $id) => new Coal($id, "Charcoal"));
|
||||
self::register("cherry_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CHERRY_SIGN(), Blocks::CHERRY_WALL_SIGN()));
|
||||
self::register("chemical_aluminium_oxide", fn(IID $id) => new Item($id, "Aluminium Oxide"));
|
||||
self::register("chemical_ammonia", fn(IID $id) => new Item($id, "Ammonia"));
|
||||
self::register("chemical_barium_sulphate", fn(IID $id) => new Item($id, "Barium Sulphate"));
|
||||
@ -475,8 +475,6 @@ final class VanillaItems{
|
||||
self::register("cookie", fn(IID $id) => new Cookie($id, "Cookie"));
|
||||
self::register("copper_ingot", fn(IID $id) => new Item($id, "Copper Ingot"));
|
||||
self::register("coral_fan", fn(IID $id) => new CoralFan($id));
|
||||
self::register("crimson_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::CRIMSON_SIGN(), Blocks::CRIMSON_WALL_SIGN()));
|
||||
self::register("dark_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::DARK_OAK_SIGN(), Blocks::DARK_OAK_WALL_SIGN()));
|
||||
self::register("diamond", fn(IID $id) => new Item($id, "Diamond"));
|
||||
self::register("disc_fragment_5", fn(IID $id) => new Item($id, "Disc Fragment (5)"));
|
||||
self::register("dragon_breath", fn(IID $id) => new Item($id, "Dragon's Breath"));
|
||||
@ -516,12 +514,10 @@ final class VanillaItems{
|
||||
self::register("ink_sac", fn(IID $id) => new Item($id, "Ink Sac"));
|
||||
self::register("iron_ingot", fn(IID $id) => new Item($id, "Iron Ingot"));
|
||||
self::register("iron_nugget", fn(IID $id) => new Item($id, "Iron Nugget"));
|
||||
self::register("jungle_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::JUNGLE_SIGN(), Blocks::JUNGLE_WALL_SIGN()));
|
||||
self::register("lapis_lazuli", fn(IID $id) => new Item($id, "Lapis Lazuli"));
|
||||
self::register("lava_bucket", fn(IID $id) => new LiquidBucket($id, "Lava Bucket", Blocks::LAVA()));
|
||||
self::register("leather", fn(IID $id) => new Item($id, "Leather"));
|
||||
self::register("magma_cream", fn(IID $id) => new Item($id, "Magma Cream"));
|
||||
self::register("mangrove_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::MANGROVE_SIGN(), Blocks::MANGROVE_WALL_SIGN()));
|
||||
self::register("medicine", fn(IID $id) => new Medicine($id, "Medicine"));
|
||||
self::register("melon", fn(IID $id) => new Melon($id, "Melon"));
|
||||
self::register("melon_seeds", fn(IID $id) => new MelonSeeds($id, "Melon Seeds"));
|
||||
@ -539,9 +535,7 @@ final class VanillaItems{
|
||||
self::register("netherite_scrap", fn(IID $id) => new class($id, "Netherite Scrap") extends Item{
|
||||
public function isFireProof() : bool{ return true; }
|
||||
});
|
||||
self::register("oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::OAK_SIGN(), Blocks::OAK_WALL_SIGN()));
|
||||
self::register("painting", fn(IID $id) => new PaintingItem($id, "Painting"));
|
||||
self::register("pale_oak_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::PALE_OAK_SIGN(), Blocks::PALE_OAK_WALL_SIGN()));
|
||||
self::register("paper", fn(IID $id) => new Item($id, "Paper"));
|
||||
self::register("phantom_membrane", fn(IID $id) => new Item($id, "Phantom Membrane"));
|
||||
self::register("pitcher_pod", fn(IID $id) => new PitcherPod($id, "Pitcher Pod"));
|
||||
@ -597,7 +591,6 @@ final class VanillaItems{
|
||||
self::register("snowball", fn(IID $id) => new Snowball($id, "Snowball"));
|
||||
self::register("spider_eye", fn(IID $id) => new SpiderEye($id, "Spider Eye"));
|
||||
self::register("splash_potion", fn(IID $id) => new SplashPotion($id, "Splash Potion"));
|
||||
self::register("spruce_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::SPRUCE_SIGN(), Blocks::SPRUCE_WALL_SIGN()));
|
||||
self::register("spyglass", fn(IID $id) => new Spyglass($id, "Spyglass"));
|
||||
self::register("steak", fn(IID $id) => new Steak($id, "Steak"));
|
||||
self::register("stick", fn(IID $id) => new Stick($id, "Stick"));
|
||||
@ -607,7 +600,6 @@ final class VanillaItems{
|
||||
self::register("sweet_berries", fn(IID $id) => new SweetBerries($id, "Sweet Berries"));
|
||||
self::register("torchflower_seeds", fn(IID $id) => new TorchflowerSeeds($id, "Torchflower Seeds"));
|
||||
self::register("totem", fn(IID $id) => new Totem($id, "Totem of Undying"));
|
||||
self::register("warped_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::WARPED_SIGN(), Blocks::WARPED_WALL_SIGN()));
|
||||
self::register("water_bucket", fn(IID $id) => new LiquidBucket($id, "Water Bucket", Blocks::WATER()));
|
||||
self::register("wheat", fn(IID $id) => new Item($id, "Wheat"));
|
||||
self::register("wheat_seeds", fn(IID $id) => new WheatSeeds($id, "Wheat Seeds"));
|
||||
@ -618,6 +610,12 @@ final class VanillaItems{
|
||||
//boat type is static, because different types of wood may have different properties
|
||||
self::register(strtolower($type->name) . "_boat", fn(IID $id) => new Boat($id, $type->getDisplayName() . " Boat", $type));
|
||||
}
|
||||
self::registerOverloaded("boat", BoatType::class, Boat::class);
|
||||
|
||||
foreach(WoodType::cases() as $woodType){
|
||||
self::register("{$woodType->name}_sign", fn(IID $id) => new ItemBlockWallOrFloor($id, Blocks::SIGN($woodType), Blocks::WALL_SIGN($woodType)));
|
||||
}
|
||||
self::registerOverloaded("sign", WoodType::class, ItemBlockWallOrFloor::class);
|
||||
}
|
||||
|
||||
private static function registerSpawnEggs() : void{
|
||||
|
39
src/utils/OverloadedRegistryMember.php
Normal file
39
src/utils/OverloadedRegistryMember.php
Normal 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\utils;
|
||||
|
||||
final class OverloadedRegistryMember{
|
||||
|
||||
/**
|
||||
* @phpstan-template TMember of object
|
||||
* @phpstan-param class-string<covariant \UnitEnum> $enumClass
|
||||
* @phpstan-param class-string<TMember> $memberClass
|
||||
* @phpstan-param array<string, string> $enumToMemberMap
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $enumClass,
|
||||
public readonly string $memberClass,
|
||||
public readonly array $enumToMemberMap
|
||||
){}
|
||||
}
|
@ -25,6 +25,7 @@ namespace pocketmine\utils;
|
||||
|
||||
use function array_map;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function mb_strtoupper;
|
||||
use function preg_match;
|
||||
|
||||
@ -42,6 +43,12 @@ trait RegistryTrait{
|
||||
*/
|
||||
private static $members = null;
|
||||
|
||||
/**
|
||||
* @var OverloadedRegistryMember[]
|
||||
* @phpstan-var array<string, OverloadedRegistryMember>
|
||||
*/
|
||||
private static $overloadedMembers = [];
|
||||
|
||||
private static function verifyName(string $name) : void{
|
||||
if(preg_match('/^(?!\d)[A-Za-z\d_]+$/u', $name) === 0){
|
||||
throw new \InvalidArgumentException("Invalid member name \"$name\", should only contain letters, numbers and underscores, and must not start with a number");
|
||||
@ -65,6 +72,31 @@ trait RegistryTrait{
|
||||
self::$members[$upperName] = $member;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-template TEnum of \UnitEnum
|
||||
* @phpstan-param class-string<TEnum> $enumClass
|
||||
* @phpstan-param class-string $returnClass
|
||||
*/
|
||||
private static function registerOverloaded(string $baseName, string $enumClass, string $returnClass) : void{
|
||||
self::verifyName($baseName);
|
||||
$upperName = mb_strtoupper($baseName);
|
||||
if(isset(self::$overloadedMembers[$upperName])){
|
||||
throw new \InvalidArgumentException("Overloaded member name \"$upperName\" is already reserved");
|
||||
}
|
||||
$enumToMemberMap = [];
|
||||
foreach($enumClass::cases() as $case){
|
||||
$memberName = mb_strtoupper($case->name . "_" . $baseName);
|
||||
if(!isset(self::$members[$memberName])){
|
||||
throw new \LogicException("\"$memberName\" needs to be registered to defined overloaded member with enum $enumClass");
|
||||
}
|
||||
if(!self::$members[$memberName] instanceof $returnClass){
|
||||
throw new \LogicException("\"$memberName\" doesn't satisfy the desired type $returnClass");
|
||||
}
|
||||
$enumToMemberMap[$case->name] = $memberName;
|
||||
}
|
||||
self::$overloadedMembers[$upperName] = new OverloadedRegistryMember($enumClass, $returnClass, $enumToMemberMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts default entries into the registry.
|
||||
*
|
||||
@ -111,20 +143,35 @@ trait RegistryTrait{
|
||||
* @return object
|
||||
*/
|
||||
public static function __callStatic($name, $arguments){
|
||||
if(count($arguments) > 0){
|
||||
throw new \ArgumentCountError("Expected exactly 0 arguments, " . count($arguments) . " passed");
|
||||
}
|
||||
if(count($arguments) === 0){
|
||||
//fast path
|
||||
if(self::$members !== null && isset(self::$members[$name])){
|
||||
return self::preprocessMember(self::$members[$name]);
|
||||
}
|
||||
|
||||
//fast path
|
||||
if(self::$members !== null && isset(self::$members[$name])){
|
||||
return self::preprocessMember(self::$members[$name]);
|
||||
}
|
||||
//fallback
|
||||
try{
|
||||
return self::_registryFromString($name);
|
||||
}catch(\InvalidArgumentException $e){
|
||||
throw new \Error($e->getMessage(), 0, $e);
|
||||
}
|
||||
}elseif(count($arguments) === 1 && $arguments[0] instanceof \UnitEnum){
|
||||
$enum = $arguments[0];
|
||||
self::checkInit();
|
||||
|
||||
//fallback
|
||||
try{
|
||||
return self::_registryFromString($name);
|
||||
}catch(\InvalidArgumentException $e){
|
||||
throw new \Error($e->getMessage(), 0, $e);
|
||||
$overloadInfo = self::$overloadedMembers[$name] ?? self::$overloadedMembers[mb_strtoupper($name)] ?? null;
|
||||
|
||||
if($overloadInfo !== null){
|
||||
if($enum::class !== $overloadInfo->enumClass){
|
||||
throw new \Error("Wrong enum type for overloaded registry member " . self::class . "::" . mb_strtoupper($name) . "($overloadInfo->enumClass)");
|
||||
}
|
||||
$memberName = $overloadInfo->enumToMemberMap[$enum->name];
|
||||
return self::preprocessMember(self::$members[$memberName]);
|
||||
}
|
||||
|
||||
throw new \Error("No such overloaded registry member: " . self::class . "::" . mb_strtoupper($name) . "(" . get_class($enum) . ")");
|
||||
}else{
|
||||
throw new \LogicException("Incorrect arguments passed to overloaded registry member: " . self::class . "::" . mb_strtoupper($name));
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,4 +183,12 @@ trait RegistryTrait{
|
||||
self::checkInit();
|
||||
return array_map(self::preprocessMember(...), self::$members ?? throw new AssumptionFailedError(self::class . "::checkInit() did not initialize self::\$members correctly"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
* @phpstan-return array<string, OverloadedRegistryMember>
|
||||
*/
|
||||
public static function getAllOverloaded() : array{
|
||||
return self::$overloadedMembers;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user