From 32f55d6590c91cbad7ff34798edb3511d3804d70 Mon Sep 17 00:00:00 2001 From: kostamax27 Date: Thu, 12 Feb 2026 17:49:42 +0300 Subject: [PATCH 1/2] First look at #8 --- src/NhanAZ/BlockData/BlockData.php | 29 ++++++++-- .../BlockData/BlockDataChunkListener.php | 37 ++++++++++++ src/NhanAZ/BlockData/BlockDataListener.php | 57 ------------------- 3 files changed, 62 insertions(+), 61 deletions(-) create mode 100644 src/NhanAZ/BlockData/BlockDataChunkListener.php diff --git a/src/NhanAZ/BlockData/BlockData.php b/src/NhanAZ/BlockData/BlockData.php index 25905e5..09e5caf 100644 --- a/src/NhanAZ/BlockData/BlockData.php +++ b/src/NhanAZ/BlockData/BlockData.php @@ -25,9 +25,12 @@ final class BlockData{ /** @var array */ private array $worlds = []; + /** @var array */ + private array $chunk_listeners = []; private function __construct( private string $dataPath, + private bool $autoCleanup ){} /** @@ -51,11 +54,11 @@ public static function create(PluginBase $plugin, bool $autoCleanup = false, ?st ); } - $instance = new self($dataPath ?? Path::join($plugin->getDataFolder(), "blockdata")); + $instance = new self($dataPath ?? Path::join($plugin->getDataFolder(), "blockdata"), $autoCleanup); $server = $plugin->getServer(); $server->getPluginManager()->registerEvents( - new BlockDataListener($instance, $plugin, $autoCleanup), + new BlockDataListener($instance, $plugin), $plugin ); @@ -151,6 +154,13 @@ public function removeAt(World $world, int $x, int $y, int $z) : void{ public function loadWorld(World $world) : void{ if(!isset($this->worlds[$world->getId()])){ $this->worlds[$world->getId()] = new BlockDataWorld($this->dataPath, $world); + if($this->autoCleanup){ + $listener = ($this->chunk_listeners[$world->getId()] ??= new BlockDataChunkListener($this, $world)); + foreach($world->getLoadedChunks() as $hash => $_){ + World::getXZ($hash, $x, $z); + $world->registerChunkListener($listener, $x, $z); + } + } } } @@ -158,6 +168,10 @@ public function loadWorld(World $world) : void{ public function unloadWorld(World $world) : void{ $id = $world->getId(); if(isset($this->worlds[$id])){ + if($this->autoCleanup && isset($this->chunk_listeners[$id])){ + $world->unregisterChunkListenerFromAll($this->chunk_listeners[$id]); + unset($this->chunk_listeners[$id]); + } $this->worlds[$id]->close(); unset($this->worlds[$id]); } @@ -173,8 +187,12 @@ public function saveWorld(World $world) : void{ /** @internal */ public function onChunkLoad(World $world, int $chunkX, int $chunkZ) : void{ - if(isset($this->worlds[$world->getId()])){ - $this->worlds[$world->getId()]->loadChunk($chunkX, $chunkZ); + $id = $world->getId(); + if(isset($this->worlds[$id])){ + if($this->autoCleanup && isset($this->chunk_listeners[$id])){ + $world->registerChunkListener($this->chunk_listeners[$id], $chunkX, $chunkZ); + } + $this->worlds[$id]->loadChunk($chunkX, $chunkZ); } } @@ -182,6 +200,9 @@ public function onChunkLoad(World $world, int $chunkX, int $chunkZ) : void{ public function onChunkUnload(World $world, int $chunkX, int $chunkZ) : void{ $id = $world->getId(); if(isset($this->worlds[$id])){ + if($this->autoCleanup && isset($this->chunk_listeners[$id])){ + $world->unregisterChunkListener($this->chunk_listeners[$id], $chunkX, $chunkZ); + } $this->worlds[$id]->unloadChunk($chunkX, $chunkZ); } } diff --git a/src/NhanAZ/BlockData/BlockDataChunkListener.php b/src/NhanAZ/BlockData/BlockDataChunkListener.php new file mode 100644 index 0000000..d6034c0 --- /dev/null +++ b/src/NhanAZ/BlockData/BlockDataChunkListener.php @@ -0,0 +1,37 @@ +blockData->removeAt( + $this->world, + $block->getFloorX(), + $block->getFloorY(), + $block->getFloorZ(), + ); + } +} diff --git a/src/NhanAZ/BlockData/BlockDataListener.php b/src/NhanAZ/BlockData/BlockDataListener.php index 35af8e3..646d30b 100644 --- a/src/NhanAZ/BlockData/BlockDataListener.php +++ b/src/NhanAZ/BlockData/BlockDataListener.php @@ -4,10 +4,6 @@ namespace NhanAZ\BlockData; -use pocketmine\event\block\BlockBreakEvent; -use pocketmine\event\block\BlockBurnEvent; -use pocketmine\event\block\LeavesDecayEvent; -use pocketmine\event\entity\EntityExplodeEvent; use pocketmine\event\Listener; use pocketmine\event\plugin\PluginDisableEvent; use pocketmine\event\world\ChunkLoadEvent; @@ -25,7 +21,6 @@ final class BlockDataListener implements Listener{ public function __construct( private BlockData $blockData, private PluginBase $plugin, - private bool $autoCleanup, ){} // ── World & Chunk Lifecycle ────────────────────────────────────── @@ -89,56 +84,4 @@ public function onPluginDisable(PluginDisableEvent $event) : void{ $this->blockData->closeAll(); } } - - // ── Auto Cleanup (only active when $autoCleanup is true) ───────── - - /** - * Removes block data when a player breaks the block. - * - * @param BlockBreakEvent $event - * @priority MONITOR - */ - public function onBlockBreak(BlockBreakEvent $event) : void{ - if($this->autoCleanup){ - $this->blockData->remove($event->getBlock()); - } - } - - /** - * Removes block data for all blocks destroyed by an explosion. - * - * @param EntityExplodeEvent $event - * @priority MONITOR - */ - public function onEntityExplode(EntityExplodeEvent $event) : void{ - if($this->autoCleanup){ - foreach($event->getBlockList() as $block){ - $this->blockData->remove($block); - } - } - } - - /** - * Removes block data when a block is burned by fire. - * - * @param BlockBurnEvent $event - * @priority MONITOR - */ - public function onBlockBurn(BlockBurnEvent $event) : void{ - if($this->autoCleanup){ - $this->blockData->remove($event->getBlock()); - } - } - - /** - * Removes block data when leaves decay naturally. - * - * @param LeavesDecayEvent $event - * @priority MONITOR - */ - public function onLeavesDecay(LeavesDecayEvent $event) : void{ - if($this->autoCleanup){ - $this->blockData->remove($event->getBlock()); - } - } } From 83a9b6203da3788ff41a528ecee0410b88815f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A0nh=20Nh=C3=A2n?= <60387689+NhanAZ@users.noreply.github.com> Date: Fri, 13 Feb 2026 22:15:26 +0700 Subject: [PATCH 2/2] Avoid caching null values to prevent dead cache (#10) - Change BlockDataWorld::get() to only cache non-null values - Prevent unbounded growth of null entries when getAt() is called frequently on empty blocks - Clarify cache phpdoc to reflect the new behavior - Closes #10 --- src/NhanAZ/BlockData/BlockDataWorld.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/NhanAZ/BlockData/BlockDataWorld.php b/src/NhanAZ/BlockData/BlockDataWorld.php index dc82b94..2d52c00 100644 --- a/src/NhanAZ/BlockData/BlockDataWorld.php +++ b/src/NhanAZ/BlockData/BlockDataWorld.php @@ -22,8 +22,8 @@ final class BlockDataWorld{ private LevelDB $db; /** - * In-memory cache: blockHash => stored data (or null if confirmed empty). - * Entries are populated on first read and evicted on chunk unload. + * In-memory cache: blockHash => stored data. + * Only non-null values are cached; cache entries are evicted on chunk unload. * @var array */ private array $cache = []; @@ -79,8 +79,11 @@ public function get(int $x, int $y, int $z) : mixed{ $data = null; } - $this->cache[$hash] = $data; - $this->trackBlock($x, $z, $hash); + // Only cache non-null values to avoid unbounded "dead cache" growth + if($data !== null){ + $this->cache[$hash] = $data; + $this->trackBlock($x, $z, $hash); + } return $data; }