From 0d9561c93fdea99f22f829c4f9efbd5131a2c69c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 12 Nov 2020 21:30:59 +0000 Subject: [PATCH] Removed crafting data cache from CraftingManager --- src/crafting/CraftingManager.php | 115 +++------------ src/network/mcpe/CraftingDataCache.php | 132 ++++++++++++++++++ .../mcpe/handler/PreSpawnPacketHandler.php | 3 +- src/utils/DestructorCallbackTrait.php | 52 +++++++ 4 files changed, 207 insertions(+), 95 deletions(-) create mode 100644 src/network/mcpe/CraftingDataCache.php create mode 100644 src/utils/DestructorCallbackTrait.php diff --git a/src/crafting/CraftingManager.php b/src/crafting/CraftingManager.php index 14d312de6..b6720a3e9 100644 --- a/src/crafting/CraftingManager.php +++ b/src/crafting/CraftingManager.php @@ -23,23 +23,15 @@ declare(strict_types=1); namespace pocketmine\crafting; +use Ds\Set; use pocketmine\item\Item; -use pocketmine\network\mcpe\convert\TypeConverter; -use pocketmine\network\mcpe\protocol\CraftingDataPacket; -use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; -use pocketmine\network\mcpe\protocol\types\recipe\FurnaceRecipe as ProtocolFurnaceRecipe; -use pocketmine\network\mcpe\protocol\types\recipe\RecipeIngredient; -use pocketmine\network\mcpe\protocol\types\recipe\ShapedRecipe as ProtocolShapedRecipe; -use pocketmine\network\mcpe\protocol\types\recipe\ShapelessRecipe as ProtocolShapelessRecipe; -use pocketmine\timings\Timings; -use pocketmine\utils\Binary; -use pocketmine\uuid\UUID; -use function array_map; +use pocketmine\utils\DestructorCallbackTrait; use function json_encode; -use function str_repeat; use function usort; class CraftingManager{ + use DestructorCallbackTrait; + /** @var ShapedRecipe[][] */ protected $shapedRecipes = []; /** @var ShapelessRecipe[][] */ @@ -48,93 +40,24 @@ class CraftingManager{ /** @var FurnaceRecipeManager */ protected $furnaceRecipeManager; - /** @var CraftingDataPacket|null */ - private $craftingDataCache = null; + /** + * @var Set + * @phpstan-var Set<\Closure() : void> + */ + private $recipeRegisteredCallbacks; public function __construct(){ + $this->recipeRegisteredCallbacks = new Set(); $this->furnaceRecipeManager = new FurnaceRecipeManager(); $this->furnaceRecipeManager->getRecipeRegisteredCallbacks()->add(function(FurnaceRecipe $recipe) : void{ - $this->craftingDataCache = null; + foreach($this->recipeRegisteredCallbacks as $callback){ + $callback(); + } }); } - /** - * Rebuilds the cached CraftingDataPacket. - */ - private function buildCraftingDataCache() : CraftingDataPacket{ - Timings::$craftingDataCacheRebuildTimer->startTiming(); - $pk = new CraftingDataPacket(); - $pk->cleanRecipes = true; - - $counter = 0; - $nullUUID = UUID::fromData(str_repeat("\x00", 16)); - $converter = TypeConverter::getInstance(); - foreach($this->shapelessRecipes as $list){ - foreach($list as $recipe){ - $pk->entries[] = new ProtocolShapelessRecipe( - CraftingDataPacket::ENTRY_SHAPELESS, - Binary::writeInt($counter++), - array_map(function(Item $item) use ($converter) : RecipeIngredient{ - return $converter->coreItemStackToRecipeIngredient($item); - }, $recipe->getIngredientList()), - array_map(function(Item $item) use ($converter) : ItemStack{ - return $converter->coreItemStackToNet($item); - }, $recipe->getResults()), - $nullUUID, - "crafting_table", - 50, - $counter - ); - } - } - foreach($this->shapedRecipes as $list){ - foreach($list as $recipe){ - $inputs = []; - - for($row = 0, $height = $recipe->getHeight(); $row < $height; ++$row){ - for($column = 0, $width = $recipe->getWidth(); $column < $width; ++$column){ - $inputs[$row][$column] = $converter->coreItemStackToRecipeIngredient($recipe->getIngredient($column, $row)); - } - } - $pk->entries[] = $r = new ProtocolShapedRecipe( - CraftingDataPacket::ENTRY_SHAPED, - Binary::writeInt($counter++), - $inputs, - array_map(function(Item $item) use ($converter) : ItemStack{ - return $converter->coreItemStackToNet($item); - }, $recipe->getResults()), - $nullUUID, - "crafting_table", - 50, - $counter - ); - } - } - - foreach($this->furnaceRecipeManager->getAll() as $recipe){ - $input = $converter->coreItemStackToNet($recipe->getInput()); - $pk->entries[] = new ProtocolFurnaceRecipe( - CraftingDataPacket::ENTRY_FURNACE_DATA, - $input->getId(), - $input->getMeta(), - $converter->coreItemStackToNet($recipe->getResult()), - "furnace" - ); - } - - return $pk; - } - - /** - * Returns a pre-compressed CraftingDataPacket for sending to players. Rebuilds the cache if it is not found. - */ - public function getCraftingDataPacket() : CraftingDataPacket{ - if($this->craftingDataCache === null){ - $this->craftingDataCache = $this->buildCraftingDataCache(); - } - - return $this->craftingDataCache; - } + /** @phpstan-return Set<\Closure() : void> */ + public function getRecipeRegisteredCallbacks() : Set{ return $this->recipeRegisteredCallbacks; } /** * Function used to arrange Shapeless Recipe ingredient lists into a consistent order. @@ -205,13 +128,17 @@ class CraftingManager{ public function registerShapedRecipe(ShapedRecipe $recipe) : void{ $this->shapedRecipes[self::hashOutputs($recipe->getResults())][] = $recipe; - $this->craftingDataCache = null; + foreach($this->recipeRegisteredCallbacks as $callback){ + $callback(); + } } public function registerShapelessRecipe(ShapelessRecipe $recipe) : void{ $this->shapelessRecipes[self::hashOutputs($recipe->getResults())][] = $recipe; - $this->craftingDataCache = null; + foreach($this->recipeRegisteredCallbacks as $callback){ + $callback(); + } } /** diff --git a/src/network/mcpe/CraftingDataCache.php b/src/network/mcpe/CraftingDataCache.php new file mode 100644 index 000000000..84b7e490c --- /dev/null +++ b/src/network/mcpe/CraftingDataCache.php @@ -0,0 +1,132 @@ + + */ + private $caches = []; + + public function getCache(CraftingManager $manager) : CraftingDataPacket{ + $id = spl_object_id($manager); + if(!isset($this->caches[$id])){ + $manager->getDestructorCallbacks()->add(function() use ($id) : void{ + unset($this->caches[$id]); + }); + $manager->getRecipeRegisteredCallbacks()->add(function() use ($id) : void{ + unset($this->caches[$id]); + }); + $this->caches[$id] = $this->buildCraftingDataCache($manager); + } + return $this->caches[$id]; + } + + /** + * Rebuilds the cached CraftingDataPacket. + */ + private function buildCraftingDataCache(CraftingManager $manager) : CraftingDataPacket{ + Timings::$craftingDataCacheRebuildTimer->startTiming(); + $pk = new CraftingDataPacket(); + $pk->cleanRecipes = true; + + $counter = 0; + $nullUUID = UUID::fromData(str_repeat("\x00", 16)); + $converter = TypeConverter::getInstance(); + foreach($manager->getShapelessRecipes() as $list){ + foreach($list as $recipe){ + $pk->entries[] = new ProtocolShapelessRecipe( + CraftingDataPacket::ENTRY_SHAPELESS, + Binary::writeInt($counter++), + array_map(function(Item $item) use ($converter) : RecipeIngredient{ + return $converter->coreItemStackToRecipeIngredient($item); + }, $recipe->getIngredientList()), + array_map(function(Item $item) use ($converter) : ItemStack{ + return $converter->coreItemStackToNet($item); + }, $recipe->getResults()), + $nullUUID, + "crafting_table", + 50, + $counter + ); + } + } + foreach($manager->getShapedRecipes() as $list){ + foreach($list as $recipe){ + $inputs = []; + + for($row = 0, $height = $recipe->getHeight(); $row < $height; ++$row){ + for($column = 0, $width = $recipe->getWidth(); $column < $width; ++$column){ + $inputs[$row][$column] = $converter->coreItemStackToRecipeIngredient($recipe->getIngredient($column, $row)); + } + } + $pk->entries[] = $r = new ProtocolShapedRecipe( + CraftingDataPacket::ENTRY_SHAPED, + Binary::writeInt($counter++), + $inputs, + array_map(function(Item $item) use ($converter) : ItemStack{ + return $converter->coreItemStackToNet($item); + }, $recipe->getResults()), + $nullUUID, + "crafting_table", + 50, + $counter + ); + } + } + + foreach($manager->getFurnaceRecipeManager()->getAll() as $recipe){ + $input = $converter->coreItemStackToNet($recipe->getInput()); + $pk->entries[] = new ProtocolFurnaceRecipe( + CraftingDataPacket::ENTRY_FURNACE_DATA, + $input->getId(), + $input->getMeta(), + $converter->coreItemStackToNet($recipe->getResult()), + "furnace" + ); + } + + return $pk; + } +} diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index 0e20bc847..f40dc13a1 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\handler; use pocketmine\data\bedrock\LegacyItemIdToStringIdMap; use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\convert\TypeConverter; +use pocketmine\network\mcpe\CraftingDataCache; use pocketmine\network\mcpe\NetworkSession; use pocketmine\network\mcpe\protocol\RequestChunkRadiusPacket; use pocketmine\network\mcpe\protocol\StartGamePacket; @@ -100,7 +101,7 @@ class PreSpawnPacketHandler extends PacketHandler{ $this->session->getInvManager()->syncAll(); $this->session->getInvManager()->syncCreative(); $this->session->getInvManager()->syncSelectedHotbarSlot(); - $this->session->sendDataPacket($this->server->getCraftingManager()->getCraftingDataPacket()); + $this->session->sendDataPacket(CraftingDataCache::getInstance()->getCache($this->server->getCraftingManager())); $this->session->syncPlayerList($this->server->getOnlinePlayers()); } diff --git a/src/utils/DestructorCallbackTrait.php b/src/utils/DestructorCallbackTrait.php new file mode 100644 index 000000000..63e3e3402 --- /dev/null +++ b/src/utils/DestructorCallbackTrait.php @@ -0,0 +1,52 @@ +|null + */ + private $destructorCallbacks = null; + + /** @phpstan-return Set<\Closure() : void> */ + public function getDestructorCallbacks() : Set{ + return $this->destructorCallbacks === null ? ($this->destructorCallbacks = new Set()) : $this->destructorCallbacks; + } + + public function __destruct(){ + if($this->destructorCallbacks !== null){ + foreach($this->destructorCallbacks as $callback){ + $callback(); + } + } + } +}