From 1c0eed56f1eb2940c0b5ffdc9c4df794bd14eaa4 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 11 Apr 2023 13:52:37 +0100 Subject: [PATCH 1/6] Added runtime test for event handler inheritance, to ensure I don't accidentally break it with optimisations --- .../src/EventHandlerInheritanceTest.php | 88 +++++++++++++++++++ tests/plugins/TesterPlugin/src/Main.php | 4 +- .../TesterPlugin/src/event/ChildEvent.php | 28 ++++++ .../src/event/GrandchildEvent.php | 28 ++++++ .../TesterPlugin/src/event/ParentEvent.php | 28 ++++++ 5 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php create mode 100644 tests/plugins/TesterPlugin/src/event/ChildEvent.php create mode 100644 tests/plugins/TesterPlugin/src/event/GrandchildEvent.php create mode 100644 tests/plugins/TesterPlugin/src/event/ParentEvent.php diff --git a/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php b/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php new file mode 100644 index 000000000..efe20f8d8 --- /dev/null +++ b/tests/plugins/TesterPlugin/src/EventHandlerInheritanceTest.php @@ -0,0 +1,88 @@ +getPlugin(); + $plugin->getServer()->getPluginManager()->registerEvent( + ParentEvent::class, + function(ParentEvent $event) : void{ + $this->callOrder[] = ParentEvent::class; + }, + EventPriority::NORMAL, + $plugin + ); + $plugin->getServer()->getPluginManager()->registerEvent( + ChildEvent::class, + function(ChildEvent $event) : void{ + $this->callOrder[] = ChildEvent::class; + }, + EventPriority::NORMAL, + $plugin + ); + $plugin->getServer()->getPluginManager()->registerEvent( + GrandchildEvent::class, + function(GrandchildEvent $event) : void{ + $this->callOrder[] = GrandchildEvent::class; + }, + EventPriority::NORMAL, + $plugin + ); + + $event = new GrandchildEvent(); + $event->call(); + + if($this->callOrder === self::EXPECTED_ORDER){ + $this->setResult(Test::RESULT_OK); + }else{ + $plugin->getLogger()->error("Expected order: " . implode(", ", self::EXPECTED_ORDER) . ", got: " . implode(", ", $this->callOrder)); + $this->setResult(Test::RESULT_FAILED); + } + } +} diff --git a/tests/plugins/TesterPlugin/src/Main.php b/tests/plugins/TesterPlugin/src/Main.php index 2afd9d6ae..26d3441f4 100644 --- a/tests/plugins/TesterPlugin/src/Main.php +++ b/tests/plugins/TesterPlugin/src/Main.php @@ -56,7 +56,9 @@ class Main extends PluginBase implements Listener{ } }), 10); - $this->waitingTests = []; + $this->waitingTests = [ + new EventHandlerInheritanceTest($this), + ]; } public function onServerCommand(CommandEvent $event) : void{ diff --git a/tests/plugins/TesterPlugin/src/event/ChildEvent.php b/tests/plugins/TesterPlugin/src/event/ChildEvent.php new file mode 100644 index 000000000..b71d2627f --- /dev/null +++ b/tests/plugins/TesterPlugin/src/event/ChildEvent.php @@ -0,0 +1,28 @@ + Date: Tue, 11 Apr 2023 20:01:19 +0100 Subject: [PATCH 2/6] Ticking chunks rewrite (#5689) This API is much more flexible than the old, allowing any arbitrary set of chunks to be ticked. These changes also improve the performance of random chunk ticking by almost entirely eliminating the cost of chunk selection. Ticking chunks are now reevaluated when a player moves, instead of every tick. The system also does not attempt to check the same chunks twice, leading to further improvements. Overall, the overhead of random chunk selection is reduced anywhere from 80-96%. In practice, this can offer a 5-10% performance gain for servers with sparsely distributed players. --- src/player/ChunkSelector.php | 16 ++--- src/player/Player.php | 36 ++++++++-- src/player/PlayerChunkLoader.php | 4 ++ src/world/ChunkTicker.php | 34 +++++++++ src/world/TickingChunkEntry.php | 34 +++++++++ src/world/TickingChunkLoader.php | 4 ++ src/world/World.php | 117 +++++++++++++++++++++++++++---- 7 files changed, 220 insertions(+), 25 deletions(-) create mode 100644 src/world/ChunkTicker.php create mode 100644 src/world/TickingChunkEntry.php diff --git a/src/player/ChunkSelector.php b/src/player/ChunkSelector.php index d88ef5fa4..feb1b25e2 100644 --- a/src/player/ChunkSelector.php +++ b/src/player/ChunkSelector.php @@ -54,23 +54,23 @@ final class ChunkSelector{ //If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be. /* Top right quadrant */ - yield World::chunkHash($centerX + $x, $centerZ + $z); + yield $subRadius => World::chunkHash($centerX + $x, $centerZ + $z); /* Top left quadrant */ - yield World::chunkHash($centerX - $x - 1, $centerZ + $z); + yield $subRadius => World::chunkHash($centerX - $x - 1, $centerZ + $z); /* Bottom right quadrant */ - yield World::chunkHash($centerX + $x, $centerZ - $z - 1); + yield $subRadius => World::chunkHash($centerX + $x, $centerZ - $z - 1); /* Bottom left quadrant */ - yield World::chunkHash($centerX - $x - 1, $centerZ - $z - 1); + yield $subRadius => World::chunkHash($centerX - $x - 1, $centerZ - $z - 1); if($x !== $z){ /* Top right quadrant mirror */ - yield World::chunkHash($centerX + $z, $centerZ + $x); + yield $subRadius => World::chunkHash($centerX + $z, $centerZ + $x); /* Top left quadrant mirror */ - yield World::chunkHash($centerX - $z - 1, $centerZ + $x); + yield $subRadius => World::chunkHash($centerX - $z - 1, $centerZ + $x); /* Bottom right quadrant mirror */ - yield World::chunkHash($centerX + $z, $centerZ - $x - 1); + yield $subRadius => World::chunkHash($centerX + $z, $centerZ - $x - 1); /* Bottom left quadrant mirror */ - yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1); + yield $subRadius => World::chunkHash($centerX - $z - 1, $centerZ - $x - 1); } } } diff --git a/src/player/Player.php b/src/player/Player.php index 00f85b9ff..8b871bf3e 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -121,6 +121,8 @@ use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\TextFormat; use pocketmine\world\ChunkListener; use pocketmine\world\ChunkListenerNoOpTrait; +use pocketmine\world\ChunkLoader; +use pocketmine\world\ChunkTicker; use pocketmine\world\format\Chunk; use pocketmine\world\Position; use pocketmine\world\sound\EntityAttackNoDamageSound; @@ -237,12 +239,16 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ protected array $loadQueue = []; protected int $nextChunkOrderRun = 5; + /** @var true[] */ + private array $tickingChunks = []; + protected int $viewDistance = -1; protected int $spawnThreshold; protected int $spawnChunkLoadCount = 0; protected int $chunksPerTick; protected ChunkSelector $chunkSelector; - protected PlayerChunkLoader $chunkLoader; + protected ChunkLoader $chunkLoader; + protected ChunkTicker $chunkTicker; /** @var bool[] map: raw UUID (string) => bool */ protected array $hiddenPlayers = []; @@ -308,8 +314,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $this->spawnThreshold = (int) (($this->server->getConfigGroup()->getPropertyInt("chunk-sending.spawn-radius", 4) ** 2) * M_PI); $this->chunkSelector = new ChunkSelector(); - $this->chunkLoader = new PlayerChunkLoader($spawnLocation); - + $this->chunkLoader = new class implements ChunkLoader{}; + $this->chunkTicker = new ChunkTicker(); $world = $spawnLocation->getWorld(); //load the spawn chunk so we can see the terrain $xSpawnChunk = $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE; @@ -747,6 +753,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $world->unregisterChunkLoader($this->chunkLoader, $x, $z); $world->unregisterChunkListener($this, $x, $z); unset($this->loadQueue[$index]); + $world->unregisterTickingChunk($this->chunkTicker, $x, $z); + unset($this->tickingChunks[$index]); } protected function spawnEntitiesOnAllChunks() : void{ @@ -798,6 +806,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ unset($this->loadQueue[$index]); $this->getWorld()->registerChunkLoader($this->chunkLoader, $X, $Z, true); $this->getWorld()->registerChunkListener($this, $X, $Z); + if(isset($this->tickingChunks[$index])){ + $this->getWorld()->registerTickingChunk($this->chunkTicker, $X, $Z); + } $this->getWorld()->requestChunkPopulation($X, $Z, $this->chunkLoader)->onCompletion( function() use ($X, $Z, $index, $world) : void{ @@ -895,16 +906,23 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ Timings::$playerChunkOrder->startTiming(); $newOrder = []; + $tickingChunks = []; $unloadChunks = $this->usedChunks; + $world = $this->getWorld(); + $tickingChunkRadius = $world->getChunkTickRadius(); + foreach($this->chunkSelector->selectChunks( $this->server->getAllowedViewDistance($this->viewDistance), $this->location->getFloorX() >> Chunk::COORD_BIT_SIZE, $this->location->getFloorZ() >> Chunk::COORD_BIT_SIZE - ) as $hash){ + ) as $radius => $hash){ if(!isset($this->usedChunks[$hash]) || $this->usedChunks[$hash]->equals(UsedChunkStatus::NEEDED())){ $newOrder[$hash] = true; } + if($radius < $tickingChunkRadius){ + $tickingChunks[$hash] = true; + } unset($unloadChunks[$hash]); } @@ -912,10 +930,18 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ World::getXZ($index, $X, $Z); $this->unloadChunk($X, $Z); } + foreach($this->tickingChunks as $hash => $_){ + //any chunks we encounter here are still used by the player, but may no longer be within ticking range + if(!isset($tickingChunks[$hash]) && !isset($newOrder[$hash])){ + World::getXZ($hash, $tickingChunkX, $tickingChunkZ); + $world->unregisterTickingChunk($this->chunkTicker, $tickingChunkX, $tickingChunkZ); + } + } $this->loadQueue = $newOrder; + $this->tickingChunks = $tickingChunks; + if(count($this->loadQueue) > 0 || count($unloadChunks) > 0){ - $this->chunkLoader->setCurrentLocation($this->location); $this->getNetworkSession()->syncViewAreaCenterPoint($this->location, $this->viewDistance); } diff --git a/src/player/PlayerChunkLoader.php b/src/player/PlayerChunkLoader.php index 65267642c..175f242d3 100644 --- a/src/player/PlayerChunkLoader.php +++ b/src/player/PlayerChunkLoader.php @@ -26,6 +26,10 @@ namespace pocketmine\player; use pocketmine\math\Vector3; use pocketmine\world\TickingChunkLoader; +/** + * @deprecated This class was only needed to implement TickingChunkLoader, which is now deprecated. + * ChunkTicker should be registered on ticking chunks to make them tick instead. + */ final class PlayerChunkLoader implements TickingChunkLoader{ public function __construct(private Vector3 $currentLocation){} diff --git a/src/world/ChunkTicker.php b/src/world/ChunkTicker.php new file mode 100644 index 000000000..c85c5f0e6 --- /dev/null +++ b/src/world/ChunkTicker.php @@ -0,0 +1,34 @@ + ChunkTicker + * @phpstan-var array + */ + public array $tickers = []; + + public bool $ready = false; +} diff --git a/src/world/TickingChunkLoader.php b/src/world/TickingChunkLoader.php index c081571c3..a44173c33 100644 --- a/src/world/TickingChunkLoader.php +++ b/src/world/TickingChunkLoader.php @@ -27,6 +27,10 @@ namespace pocketmine\world; * TickingChunkLoader includes all of the same functionality as ChunkLoader (it can be used in the same way). * However, using this version will also cause chunks around the loader's reported coordinates to get random block * updates. + * + * @deprecated + * @see World::registerTickingChunk() + * @see World::unregisterTickingChunk() */ interface TickingChunkLoader extends ChunkLoader{ diff --git a/src/world/World.php b/src/world/World.php index 34cc33bd7..31e0ba897 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -210,13 +210,24 @@ class World implements ChunkManager{ /** * @var TickingChunkLoader[] spl_object_id => TickingChunkLoader * @phpstan-var array + * + * @deprecated */ private array $tickingLoaders = []; /** * @var int[] spl_object_id => number of chunks * @phpstan-var array + * + * @deprecated */ private array $tickingLoaderCounter = []; + + /** + * @var TickingChunkEntry[] chunkHash => TickingChunkEntry + * @phpstan-var array + */ + private array $tickingChunks = []; + /** * @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader] * @phpstan-var array> @@ -1129,32 +1140,61 @@ class World implements ChunkManager{ } /** - * Returns the radius of chunks to be ticked around each ticking chunk loader (usually players). This is referred to - * as "simulation distance" in the Minecraft: Bedrock world options screen. + * Returns the radius of chunks to be ticked around each player. This is referred to as "simulation distance" in the + * Minecraft: Bedrock world options screen. */ public function getChunkTickRadius() : int{ return $this->chunkTickRadius; } /** - * Sets the radius of chunks ticked around each ticking chunk loader (usually players). + * Sets the radius of chunks ticked around each player. This may not take effect immediately, since each player + * needs to recalculate their tick radius. */ public function setChunkTickRadius(int $radius) : void{ $this->chunkTickRadius = $radius; } - private function tickChunks() : void{ - if($this->chunkTickRadius <= 0 || count($this->tickingLoaders) === 0){ - return; + /** + * Instructs the World to tick the specified chunk, for as long as this chunk ticker (or any other chunk ticker) is + * registered to it. + */ + public function registerTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{ + $chunkPosHash = World::chunkHash($chunkX, $chunkZ); + $entry = $this->tickingChunks[$chunkPosHash] ?? null; + if($entry === null){ + $entry = $this->tickingChunks[$chunkPosHash] = new TickingChunkEntry(); } + $entry->tickers[spl_object_id($ticker)] = $ticker; + } - $this->timings->randomChunkUpdatesChunkSelection->startTiming(); - - /** @var bool[] $chunkTickList chunkhash => dummy */ - $chunkTickList = []; - - $chunkTickableCache = []; + /** + * Unregisters the given chunk ticker from the specified chunk. If there are other tickers still registered to the + * chunk, it will continue to be ticked. + */ + public function unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{ + $chunkHash = World::chunkHash($chunkX, $chunkZ); + $tickerId = spl_object_id($ticker); + if(isset($this->tickingChunks[$chunkHash]->tickers[$tickerId])){ + unset($this->tickingChunks[$chunkHash]->tickers[$tickerId]); + if(count($this->tickingChunks[$chunkHash]->tickers) === 0){ + unset($this->tickingChunks[$chunkHash]); + } + } + } + /** + * @deprecated + * + * @param true[] $chunkTickList + * @param bool[] $chunkTickableCache + * + * @phpstan-param array $chunkTickList + * @phpstan-param array $chunkTickableCache + * @phpstan-param-out array $chunkTickList + * @phpstan-param-out array $chunkTickableCache + */ + private function selectTickableChunksLegacy(array &$chunkTickList, array &$chunkTickableCache) : void{ $centerChunks = []; $selector = new ChunkSelector(); @@ -1179,6 +1219,38 @@ class World implements ChunkManager{ } } } + } + + private function tickChunks() : void{ + if($this->chunkTickRadius <= 0 || (count($this->tickingChunks) === 0 && count($this->tickingLoaders) === 0)){ + return; + } + + $this->timings->randomChunkUpdatesChunkSelection->startTiming(); + + /** @var bool[] $chunkTickList chunkhash => dummy */ + $chunkTickList = []; + + $chunkTickableCache = []; + + foreach($this->tickingChunks as $hash => $entry){ + if(!$entry->ready){ + World::getXZ($hash, $chunkX, $chunkZ); + if($this->isChunkTickable($chunkX, $chunkZ, $chunkTickableCache)){ + $entry->ready = true; + }else{ + //the chunk has been flagged as temporarily not tickable, so we don't want to tick it this time + continue; + } + } + $chunkTickList[$hash] = true; + } + + //TODO: REMOVE THIS + //backwards compatibility for TickingChunkLoader, although I'm not sure this is really necessary in practice + if(count($this->tickingLoaders) !== 0){ + $this->selectTickableChunksLegacy($chunkTickList, $chunkTickableCache); + } $this->timings->randomChunkUpdatesChunkSelection->stopTiming(); @@ -1230,11 +1302,28 @@ class World implements ChunkManager{ return true; } + /** + * Marks the 3x3 chunks around the specified chunk as not ready to be ticked. This is used to prevent chunk ticking + * while a chunk is being populated, light-populated, or unloaded. + * Each chunk will be rechecked every tick until it is ready to be ticked again. + */ + private function markTickingChunkUnavailable(int $chunkX, int $chunkZ) : void{ + for($cx = -1; $cx <= 1; ++$cx){ + for($cz = -1; $cz <= 1; ++$cz){ + $chunkHash = World::chunkHash($chunkX + $cx, $chunkZ + $cz); + if(isset($this->tickingChunks[$chunkHash])){ + $this->tickingChunks[$chunkHash]->ready = false; + } + } + } + } + private function orderLightPopulation(int $chunkX, int $chunkZ) : void{ $chunkHash = World::chunkHash($chunkX, $chunkZ); $lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated(); if($lightPopulatedState === false){ $this->chunks[$chunkHash]->setLightPopulated(null); + $this->markTickingChunkUnavailable($chunkX, $chunkZ); $this->workerPool->submitTask(new LightPopulationTask( $this->chunks[$chunkHash], @@ -2302,6 +2391,7 @@ class World implements ChunkManager{ throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked"); } $this->chunkLock[$chunkHash] = $lockId; + $this->markTickingChunkUnavailable($chunkX, $chunkZ); } /** @@ -2367,6 +2457,7 @@ class World implements ChunkManager{ unset($this->blockCache[$chunkHash]); unset($this->changedBlocks[$chunkHash]); $chunk->setTerrainDirty(); + $this->markTickingChunkUnavailable($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking if(!$this->isChunkInUse($chunkX, $chunkZ)){ $this->unloadChunkRequest($chunkX, $chunkZ); @@ -2809,6 +2900,8 @@ class World implements ChunkManager{ unset($this->chunks[$chunkHash]); unset($this->blockCache[$chunkHash]); unset($this->changedBlocks[$chunkHash]); + unset($this->tickingChunks[$chunkHash]); + $this->markTickingChunkUnavailable($x, $z); if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){ $this->logger->debug("Rejecting population promise for chunk $x $z"); From 3490e2b06a72909be27a684d8f607c9ab4c86977 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 11 Apr 2023 22:33:44 +0100 Subject: [PATCH 3/6] Mark RegisteredListenerCache as internal --- src/event/RegisteredListenerCache.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event/RegisteredListenerCache.php b/src/event/RegisteredListenerCache.php index 1b675c716..e5e315546 100644 --- a/src/event/RegisteredListenerCache.php +++ b/src/event/RegisteredListenerCache.php @@ -23,6 +23,9 @@ declare(strict_types=1); namespace pocketmine\event; +/** + * @internal + */ final class RegisteredListenerCache{ /** From ad88490e84ffc49d08eae8e2d7d845646617d93d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 11 Apr 2023 22:34:11 +0100 Subject: [PATCH 4/6] Mark TickingChunkEntry as internal --- src/world/TickingChunkEntry.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/world/TickingChunkEntry.php b/src/world/TickingChunkEntry.php index c255cf835..ca965463d 100644 --- a/src/world/TickingChunkEntry.php +++ b/src/world/TickingChunkEntry.php @@ -23,6 +23,9 @@ declare(strict_types=1); namespace pocketmine\world; +/** + * @internal + */ final class TickingChunkEntry{ /** * @var ChunkTicker[] spl_object_id => ChunkTicker From 89deb0fe18f86d83a1d190682b60ae72da9cfe0c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 11 Apr 2023 22:48:02 +0100 Subject: [PATCH 5/6] Release 4.19.0 --- changelogs/4.19.md | 77 +++++++++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 +-- 2 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 changelogs/4.19.md diff --git a/changelogs/4.19.md b/changelogs/4.19.md new file mode 100644 index 000000000..de5da5340 --- /dev/null +++ b/changelogs/4.19.md @@ -0,0 +1,77 @@ +**For Minecraft: Bedrock Edition 1.19.70** + +### Note about API versions +Plugins which don't touch the `pocketmine\network\mcpe` namespace are compatible with any previous 4.x.y version will also run on these releases and do not need API bumps. +Plugin developers should **only** update their required API to this version if you need the changes in this build. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +### Highlights +This version introduces support for a new, more advanced version of Timings. +This improved system provides more detail than the old system, and supports being displayed in a tree view, making it much easier to see which timers contribute to which other timers. + +In addition, some minor performance improvements have been made, along with a couple of minor API additions. + +# 4.19.0 +Released 11th April 2023. + +## General +- Updated the Timings system. + - Timings records now include parent information, allowing them to be displayed in a tree view (e.g. https://timings.pmmp.io/?id=303556). + - Timings records now include additional information, such as Peak (max time spent on any single tick), and Ticks (number of ticks the timer was active on). + - New timings have been added for every event. + - A new timer `Player Network Send - Pre-Spawn Game Data` has been added, and covers most of the time spent handling `ResourcePackClientResponsePacket`, giving a clearer picture of what's happening. +- Improved performance of the plugin event system. + - By introducing some caching, the event system now has 90% less overhead than in previous versions. +- Improved performance of the random chunk ticking system. + - The selection of ticked random chunks, and their validation for ticking, is now cached. This significantly reduces the overhead of chunk selection. + - Factions servers and other game modes with big maps and sparsely populated areas will see the most benefit from this change. + - Real-world performance benefit of this change is anywhere from 0-20%, depending on server type and configuration. +- The `timings paste` command now logs a debug message with the server response on failure to paste a timings report. + +## API +### `pocketmine\entity\object` +- The following API constants have been added: + - `ExperienceOrb::DEFAULT_DESPAWN_DELAY` - the default delay in ticks before an experience orb despawns + - `ExperienceOrb::NEVER_DESPAWN` - magic value for `setDespawnDelay()` to make an experience orb never despawn + - `ExperienceOrb::MAX_DESPAWN_DELAY` - the maximum delay in ticks before an experience orb despawns +- The following API methods have been added: + - `public ExperienceOrb->getDespawnDelay() : int` - returns the delay in ticks before this experience orb despawns + - `public ExperienceOrb->setDespawnDelay(int $despawnDelay) : void` - sets the delay in ticks before this experience orb despawns +- The following properties have been deprecated + - `ExperienceOrb->age` - superseded by despawn delay methods + +### `pocketmine\event` +- The following API methods have been added: + - `public HandlerList->getListenerList() : list` - returns an ordered list of handlers to be called for the event + +### `pocketmine\player` +- The following API methods have behavioural changes: + - `ChunkSelector->selectChunks()` now yields the distance in chunks from the center as the key, instead of an incrementing integer. +- The following classes have been deprecated: + - `PlayerChunkLoader` (this was technically internal, but never marked as such) + +### `pocketmine\timings` +- The following API constants have been deprecated: +- `Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX` - this is superseded by timings group support (see `Timings::GROUP_BREAKDOWN`) +- The following API constants have been added: +- `Timings::GROUP_BREAKDOWN` - this group makes a timer appear in the `Minecraft - Breakdown` section of a timings report +- The following API methods have been added: +- `public TimingsHandler->getGroup() : string` - returns the name of the table in which this timer will appear in a timings report +- The following API methods have changed signatures: +- `TimingsHandler->__construct()` now accepts an additional, optional `string $group` parameter, which defaults to `Minecraft`. + +### `pocketmine\world` +#### Highlights +Ticking chunks is now done using the `ChunkTicker` system, which has a much more fine-grained API than the old `TickingChunkLoader` system, as well as better performance. +It works similarly to the `ChunkLoader` system, in that chunks will be ticked as long as at least one `ChunkTicker` is registered for them. + +#### API changes +- The following classes have been deprecated: + - `TickingChunkLoader` - this has been superseded by the more powerful and performant `ChunkTicker` APIs +- The following classes have been added: + - `ChunkTicker` - an opaque object used for `registerTickingChunk()` to instruct the `World` that we want a chunk to be ticked +- The following API methods have been added: + - `public World->registerTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void` - registers a chunk to be ticked by the given `ChunkTicker` + - `public World->unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void` - unregisters a chunk from being ticked by the given `ChunkTicker` diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 723613b39..2624370f0 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "4.18.5"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "4.19.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; private function __construct(){ From 87d8c1ea11cdd34a3f4bbd0ec87437be9b76a76d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 11 Apr 2023 22:48:05 +0100 Subject: [PATCH 6/6] 4.19.1 is next --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 2624370f0..50ef8c377 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "4.19.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "4.19.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; private function __construct(){