From db95bf8b9b1c0f31a53fe368e82e6116c8386dec Mon Sep 17 00:00:00 2001 From: ShockedPlot7560 Date: Thu, 18 May 2023 15:11:28 +0200 Subject: [PATCH] Caching creative inventory entries (#5703) Due to the high cost of Item::serializeCompoundTag(), it's very costly to rebuild this every time we need it. This is sent during the pre-spawn step, where we need to minimize costs as much as possible. --- src/inventory/CreativeInventory.php | 22 ++++++ src/network/mcpe/InventoryManager.php | 15 +--- .../mcpe/cache/CreativeInventoryCache.php | 69 +++++++++++++++++++ 3 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 src/network/mcpe/cache/CreativeInventoryCache.php diff --git a/src/inventory/CreativeInventory.php b/src/inventory/CreativeInventory.php index 7d6754f8c..8892661a0 100644 --- a/src/inventory/CreativeInventory.php +++ b/src/inventory/CreativeInventory.php @@ -25,7 +25,9 @@ namespace pocketmine\inventory; use pocketmine\item\Durable; use pocketmine\item\Item; +use pocketmine\utils\DestructorCallbackTrait; use pocketmine\utils\Filesystem; +use pocketmine\utils\ObjectSet; use pocketmine\utils\SingletonTrait; use pocketmine\utils\Utils; use Symfony\Component\Filesystem\Path; @@ -33,11 +35,17 @@ use function json_decode; final class CreativeInventory{ use SingletonTrait; + use DestructorCallbackTrait; /** @var Item[] */ private array $creative = []; + /** @phpstan-var ObjectSet<\Closure() : void> */ + private ObjectSet $contentChangedCallbacks; + private function __construct(){ + $this->contentChangedCallbacks = new ObjectSet(); + $creativeItems = json_decode(Filesystem::fileGetContents(Path::join(\pocketmine\RESOURCE_PATH, "legacy_creativeitems.json")), true); foreach($creativeItems as $data){ @@ -55,6 +63,7 @@ final class CreativeInventory{ */ public function clear() : void{ $this->creative = []; + $this->onContentChange(); } /** @@ -84,6 +93,7 @@ final class CreativeInventory{ */ public function add(Item $item) : void{ $this->creative[] = clone $item; + $this->onContentChange(); } /** @@ -94,10 +104,22 @@ final class CreativeInventory{ $index = $this->getItemIndex($item); if($index !== -1){ unset($this->creative[$index]); + $this->onContentChange(); } } public function contains(Item $item) : bool{ return $this->getItemIndex($item) !== -1; } + + /** @phpstan-return ObjectSet<\Closure() : void> */ + public function getContentChangedCallbacks() : ObjectSet{ + return $this->contentChangedCallbacks; + } + + private function onContentChange() : void{ + foreach($this->contentChangedCallbacks as $callback){ + $callback(); + } + } } diff --git a/src/network/mcpe/InventoryManager.php b/src/network/mcpe/InventoryManager.php index c6d83c65e..38cd986de 100644 --- a/src/network/mcpe/InventoryManager.php +++ b/src/network/mcpe/InventoryManager.php @@ -37,19 +37,17 @@ use pocketmine\inventory\CreativeInventory; use pocketmine\inventory\Inventory; use pocketmine\inventory\transaction\action\SlotChangeAction; use pocketmine\inventory\transaction\InventoryTransaction; -use pocketmine\item\Item; +use pocketmine\network\mcpe\cache\CreativeInventoryCache; use pocketmine\network\mcpe\convert\TypeConverter; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\ContainerClosePacket; use pocketmine\network\mcpe\protocol\ContainerOpenPacket; use pocketmine\network\mcpe\protocol\ContainerSetDataPacket; -use pocketmine\network\mcpe\protocol\CreativeContentPacket; use pocketmine\network\mcpe\protocol\InventoryContentPacket; use pocketmine\network\mcpe\protocol\InventorySlotPacket; use pocketmine\network\mcpe\protocol\MobEquipmentPacket; use pocketmine\network\mcpe\protocol\types\BlockPosition; use pocketmine\network\mcpe\protocol\types\inventory\ContainerIds; -use pocketmine\network\mcpe\protocol\types\inventory\CreativeContentEntry; use pocketmine\network\mcpe\protocol\types\inventory\ItemStack; use pocketmine\network\mcpe\protocol\types\inventory\ItemStackWrapper; use pocketmine\network\mcpe\protocol\types\inventory\NetworkInventoryAction; @@ -599,16 +597,7 @@ class InventoryManager{ } public function syncCreative() : void{ - $typeConverter = TypeConverter::getInstance(); - - $entries = []; - if(!$this->player->isSpectator()){ - //creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent - foreach(CreativeInventory::getInstance()->getAll() as $k => $item){ - $entries[] = new CreativeContentEntry($k, $typeConverter->coreItemStackToNet($item)); - } - } - $this->session->sendDataPacket(CreativeContentPacket::create($entries)); + $this->session->sendDataPacket(CreativeInventoryCache::getInstance()->getCache(CreativeInventory::getInstance())); } private function newItemStackId() : int{ diff --git a/src/network/mcpe/cache/CreativeInventoryCache.php b/src/network/mcpe/cache/CreativeInventoryCache.php new file mode 100644 index 000000000..04fc52604 --- /dev/null +++ b/src/network/mcpe/cache/CreativeInventoryCache.php @@ -0,0 +1,69 @@ + + */ + private array $caches = []; + + public function getCache(CreativeInventory $inventory) : CreativeContentPacket{ + $id = spl_object_id($inventory); + if(!isset($this->caches[$id])){ + $inventory->getDestructorCallbacks()->add(function() use ($id) : void{ + unset($this->caches[$id]); + }); + $inventory->getContentChangedCallbacks()->add(function() use ($id) : void{ + unset($this->caches[$id]); + }); + $this->caches[$id] = $this->buildCreativeInventoryCache($inventory); + } + return $this->caches[$id]; + } + + /** + * Rebuild the cache for the given inventory. + */ + private function buildCreativeInventoryCache(CreativeInventory $inventory) : CreativeContentPacket{ + $entries = []; + $typeConverter = TypeConverter::getInstance(); + //creative inventory may have holes if items were unregistered - ensure network IDs used are always consistent + foreach($inventory->getAll() as $k => $item){ + $entries[] = new CreativeContentEntry($k, $typeConverter->coreItemStackToNet($item)); + } + + return CreativeContentPacket::create($entries); + } +}