diff --git a/src/pocketmine/Player.php b/src/pocketmine/Player.php index 9e9eb9da3..cbb2ed3e3 100644 --- a/src/pocketmine/Player.php +++ b/src/pocketmine/Player.php @@ -84,6 +84,7 @@ use pocketmine\item\WritableBook; use pocketmine\item\WrittenBook; use pocketmine\lang\TextContainer; use pocketmine\lang\TranslationContainer; +use pocketmine\level\ChunkListener; use pocketmine\level\ChunkLoader; use pocketmine\level\format\Chunk; use pocketmine\level\Level; @@ -174,7 +175,7 @@ use const PHP_INT_MAX; /** * Main class that handles networking, recovery, and packet sending to the server part */ -class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ +class Player extends Human implements CommandSender, ChunkLoader, ChunkListener, IPlayer{ /** * Checks a supplied username and checks it is valid. @@ -920,6 +921,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ unset($this->usedChunks[$index]); } $level->unregisterChunkLoader($this, $x, $z); + $level->unregisterChunkListener($this, $x, $z); unset($this->loadQueue[$index]); } @@ -977,6 +979,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ $this->usedChunks[$index] = false; $this->level->registerChunkLoader($this, $X, $Z, true); + $this->level->registerChunkListener($this, $X, $Z); if(!$this->level->populateChunk($X, $Z)){ continue; @@ -1840,6 +1843,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ //load the spawn chunk so we can see the terrain $level->registerChunkLoader($this, $spawn->getFloorX() >> 4, $spawn->getFloorZ() >> 4, true); + $level->registerChunkListener($this, $spawn->getFloorX() >> 4, $spawn->getFloorZ() >> 4); if($spawnReset){ $spawn = $level->getSafeSpawn($spawn); } @@ -2880,6 +2884,7 @@ class Player extends Human implements CommandSender, ChunkLoader, IPlayer{ foreach($this->usedChunks as $index => $d){ Level::getXZ($index, $chunkX, $chunkZ); $this->level->unregisterChunkLoader($this, $chunkX, $chunkZ); + $this->level->unregisterChunkListener($this, $chunkX, $chunkZ); foreach($this->level->getChunkEntities($chunkX, $chunkZ) as $entity){ $entity->despawnFrom($this); } diff --git a/src/pocketmine/level/ChunkListener.php b/src/pocketmine/level/ChunkListener.php new file mode 100644 index 000000000..ffa7127de --- /dev/null +++ b/src/pocketmine/level/ChunkListener.php @@ -0,0 +1,80 @@ +registerChunkLoader($this, $chunkX, $chunkZ) - * Unregister Level->unregisterChunkLoader($this, $chunkX, $chunkZ) + * @see Level::registerChunkLoader() + * @see Level::unregisterChunkLoader() * * WARNING: When moving this object around in the world or destroying it, - * be sure to free the existing references from Level, otherwise you'll leak memory. + * be sure to unregister the loader from chunks you're not using, otherwise you'll leak memory. */ interface ChunkLoader{ @@ -48,42 +43,4 @@ interface ChunkLoader{ * @return float */ public function getZ(); - - /** - * This method will be called when a Chunk is replaced by a new one - * - * @param Chunk $chunk - */ - public function onChunkChanged(Chunk $chunk); - - /** - * This method will be called when a registered chunk is loaded - * - * @param Chunk $chunk - */ - public function onChunkLoaded(Chunk $chunk); - - - /** - * This method will be called when a registered chunk is unloaded - * - * @param Chunk $chunk - */ - public function onChunkUnloaded(Chunk $chunk); - - /** - * This method will be called when a registered chunk is populated - * Usually it'll be sent with another call to onChunkChanged() - * - * @param Chunk $chunk - */ - public function onChunkPopulated(Chunk $chunk); - - /** - * This method will be called when a block changes in a registered chunk - * - * @param Block|Vector3 $block - */ - public function onBlockChanged(Vector3 $block); - } diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index 96d77defd..ebe325583 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -179,6 +179,9 @@ class Level implements ChunkManager, Metadatable{ /** @var Player[][] */ private $playerLoaders = []; + /** @var ChunkListener[][] */ + private $chunkListeners = []; + /** @var ClientboundPacket[][] */ private $chunkPackets = []; /** @var ClientboundPacket[] */ @@ -680,6 +683,52 @@ class Level implements ChunkManager, Metadatable{ } } + /** + * Registers a listener to receive events on a chunk. + * + * @param ChunkListener $listener + * @param int $chunkX + * @param int $chunkZ + */ + public function registerChunkListener(ChunkListener $listener, int $chunkX, int $chunkZ) : void{ + $hash = Level::chunkHash($chunkX, $chunkZ); + if(isset($this->chunkListeners[$hash])){ + $this->chunkListeners[$hash][spl_object_id($listener)] = $listener; + }else{ + $this->chunkListeners[$hash] = [spl_object_id($listener) => $listener]; + } + } + + /** + * Unregisters a chunk listener previously registered. + * @see Level::registerChunkListener() + * + * @param ChunkListener $listener + * @param int $chunkX + * @param int $chunkZ + */ + public function unregisterChunkListener(ChunkListener $listener, int $chunkX, int $chunkZ) : void{ + $hash = Level::chunkHash($chunkX, $chunkZ); + if(isset($this->chunkListeners[$hash])){ + unset($this->chunkListeners[$hash][spl_object_id($listener)]); + if(empty($this->chunkListeners[$hash])){ + unset($this->chunkListeners[$hash]); + } + } + } + + /** + * Returns all the listeners attached to this chunk. + * + * @param int $chunkX + * @param int $chunkZ + * + * @return ChunkListener[] + */ + public function getChunkListeners(int $chunkX, int $chunkZ) : array{ + return $this->chunkListeners[Level::chunkHash($chunkX, $chunkZ)] ?? []; + } + /** * @internal * @@ -1539,8 +1588,8 @@ class Level implements ChunkManager, Metadatable{ } $this->changedBlocks[$chunkHash][$relativeBlockHash] = $block; - foreach($this->getChunkLoaders($x >> 4, $z >> 4) as $loader){ - $loader->onBlockChanged($block); + foreach($this->getChunkListeners($x >> 4, $z >> 4) as $listener){ + $listener->onBlockChanged($block); } if($update){ @@ -2229,8 +2278,8 @@ class Level implements ChunkManager, Metadatable{ if(($oldChunk === null or !$oldChunk->isPopulated()) and $chunk->isPopulated()){ (new ChunkPopulateEvent($this, $chunk))->call(); - foreach($this->getChunkLoaders($x, $z) as $loader){ - $loader->onChunkPopulated($chunk); + foreach($this->getChunkListeners($x, $z) as $listener){ + $listener->onChunkPopulated($chunk); } } } @@ -2301,8 +2350,8 @@ class Level implements ChunkManager, Metadatable{ if(!$this->isChunkInUse($chunkX, $chunkZ)){ $this->unloadChunkRequest($chunkX, $chunkZ); }else{ - foreach($this->getChunkLoaders($chunkX, $chunkZ) as $loader){ - $loader->onChunkChanged($chunk); + foreach($this->getChunkListeners($chunkX, $chunkZ) as $listener){ + $listener->onChunkChanged($chunk); } } } @@ -2617,8 +2666,8 @@ class Level implements ChunkManager, Metadatable{ } if($this->isChunkInUse($x, $z)){ - foreach($this->getChunkLoaders($x, $z) as $loader){ - $loader->onChunkLoaded($chunk); + foreach($this->getChunkListeners($x, $z) as $listener){ + $listener->onChunkLoaded($chunk); } }else{ $this->server->getLogger()->debug("Newly loaded chunk $x $z has no loaders registered, will be unloaded at next available opportunity"); @@ -2678,8 +2727,8 @@ class Level implements ChunkManager, Metadatable{ } } - foreach($this->getChunkLoaders($x, $z) as $loader){ - $loader->onChunkUnloaded($chunk); + foreach($this->getChunkListeners($x, $z) as $listener){ + $listener->onChunkUnloaded($chunk); } $chunk->onUnload();