mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-06 20:07:09 +00:00
Merge branch 'minor-next' into major-next
This commit is contained in:
commit
9bddcc72f7
77
changelogs/4.19.md
Normal file
77
changelogs/4.19.md
Normal file
@ -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<RegisteredListener>` - 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`
|
@ -23,6 +23,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\event;
|
namespace pocketmine\event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
final class RegisteredListenerCache{
|
final class RegisteredListenerCache{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.
|
//If the chunk is in the radius, others at the same offsets in different quadrants are also guaranteed to be.
|
||||||
|
|
||||||
/* Top right quadrant */
|
/* Top right quadrant */
|
||||||
yield World::chunkHash($centerX + $x, $centerZ + $z);
|
yield $subRadius => World::chunkHash($centerX + $x, $centerZ + $z);
|
||||||
/* Top left quadrant */
|
/* Top left quadrant */
|
||||||
yield World::chunkHash($centerX - $x - 1, $centerZ + $z);
|
yield $subRadius => World::chunkHash($centerX - $x - 1, $centerZ + $z);
|
||||||
/* Bottom right quadrant */
|
/* Bottom right quadrant */
|
||||||
yield World::chunkHash($centerX + $x, $centerZ - $z - 1);
|
yield $subRadius => World::chunkHash($centerX + $x, $centerZ - $z - 1);
|
||||||
/* Bottom left quadrant */
|
/* 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){
|
if($x !== $z){
|
||||||
/* Top right quadrant mirror */
|
/* Top right quadrant mirror */
|
||||||
yield World::chunkHash($centerX + $z, $centerZ + $x);
|
yield $subRadius => World::chunkHash($centerX + $z, $centerZ + $x);
|
||||||
/* Top left quadrant mirror */
|
/* 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 */
|
/* 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 */
|
/* Bottom left quadrant mirror */
|
||||||
yield World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
|
yield $subRadius => World::chunkHash($centerX - $z - 1, $centerZ - $x - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,6 +121,8 @@ use pocketmine\utils\AssumptionFailedError;
|
|||||||
use pocketmine\utils\TextFormat;
|
use pocketmine\utils\TextFormat;
|
||||||
use pocketmine\world\ChunkListener;
|
use pocketmine\world\ChunkListener;
|
||||||
use pocketmine\world\ChunkListenerNoOpTrait;
|
use pocketmine\world\ChunkListenerNoOpTrait;
|
||||||
|
use pocketmine\world\ChunkLoader;
|
||||||
|
use pocketmine\world\ChunkTicker;
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\Position;
|
use pocketmine\world\Position;
|
||||||
use pocketmine\world\sound\EntityAttackNoDamageSound;
|
use pocketmine\world\sound\EntityAttackNoDamageSound;
|
||||||
@ -239,12 +241,16 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
protected array $loadQueue = [];
|
protected array $loadQueue = [];
|
||||||
protected int $nextChunkOrderRun = 5;
|
protected int $nextChunkOrderRun = 5;
|
||||||
|
|
||||||
|
/** @var true[] */
|
||||||
|
private array $tickingChunks = [];
|
||||||
|
|
||||||
protected int $viewDistance = -1;
|
protected int $viewDistance = -1;
|
||||||
protected int $spawnThreshold;
|
protected int $spawnThreshold;
|
||||||
protected int $spawnChunkLoadCount = 0;
|
protected int $spawnChunkLoadCount = 0;
|
||||||
protected int $chunksPerTick;
|
protected int $chunksPerTick;
|
||||||
protected ChunkSelector $chunkSelector;
|
protected ChunkSelector $chunkSelector;
|
||||||
protected PlayerChunkLoader $chunkLoader;
|
protected ChunkLoader $chunkLoader;
|
||||||
|
protected ChunkTicker $chunkTicker;
|
||||||
|
|
||||||
/** @var bool[] map: raw UUID (string) => bool */
|
/** @var bool[] map: raw UUID (string) => bool */
|
||||||
protected array $hiddenPlayers = [];
|
protected array $hiddenPlayers = [];
|
||||||
@ -310,8 +316,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->spawnThreshold = (int) (($this->server->getConfigGroup()->getPropertyInt("chunk-sending.spawn-radius", 4) ** 2) * M_PI);
|
||||||
$this->chunkSelector = new ChunkSelector();
|
$this->chunkSelector = new ChunkSelector();
|
||||||
|
|
||||||
$this->chunkLoader = new PlayerChunkLoader($spawnLocation);
|
$this->chunkLoader = new class implements ChunkLoader{};
|
||||||
|
$this->chunkTicker = new ChunkTicker();
|
||||||
$world = $spawnLocation->getWorld();
|
$world = $spawnLocation->getWorld();
|
||||||
//load the spawn chunk so we can see the terrain
|
//load the spawn chunk so we can see the terrain
|
||||||
$xSpawnChunk = $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE;
|
$xSpawnChunk = $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE;
|
||||||
@ -749,6 +755,8 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
$world->unregisterChunkLoader($this->chunkLoader, $x, $z);
|
$world->unregisterChunkLoader($this->chunkLoader, $x, $z);
|
||||||
$world->unregisterChunkListener($this, $x, $z);
|
$world->unregisterChunkListener($this, $x, $z);
|
||||||
unset($this->loadQueue[$index]);
|
unset($this->loadQueue[$index]);
|
||||||
|
$world->unregisterTickingChunk($this->chunkTicker, $x, $z);
|
||||||
|
unset($this->tickingChunks[$index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function spawnEntitiesOnAllChunks() : void{
|
protected function spawnEntitiesOnAllChunks() : void{
|
||||||
@ -800,6 +808,9 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
unset($this->loadQueue[$index]);
|
unset($this->loadQueue[$index]);
|
||||||
$this->getWorld()->registerChunkLoader($this->chunkLoader, $X, $Z, true);
|
$this->getWorld()->registerChunkLoader($this->chunkLoader, $X, $Z, true);
|
||||||
$this->getWorld()->registerChunkListener($this, $X, $Z);
|
$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(
|
$this->getWorld()->requestChunkPopulation($X, $Z, $this->chunkLoader)->onCompletion(
|
||||||
function() use ($X, $Z, $index, $world) : void{
|
function() use ($X, $Z, $index, $world) : void{
|
||||||
@ -897,16 +908,23 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
Timings::$playerChunkOrder->startTiming();
|
Timings::$playerChunkOrder->startTiming();
|
||||||
|
|
||||||
$newOrder = [];
|
$newOrder = [];
|
||||||
|
$tickingChunks = [];
|
||||||
$unloadChunks = $this->usedChunks;
|
$unloadChunks = $this->usedChunks;
|
||||||
|
|
||||||
|
$world = $this->getWorld();
|
||||||
|
$tickingChunkRadius = $world->getChunkTickRadius();
|
||||||
|
|
||||||
foreach($this->chunkSelector->selectChunks(
|
foreach($this->chunkSelector->selectChunks(
|
||||||
$this->server->getAllowedViewDistance($this->viewDistance),
|
$this->server->getAllowedViewDistance($this->viewDistance),
|
||||||
$this->location->getFloorX() >> Chunk::COORD_BIT_SIZE,
|
$this->location->getFloorX() >> Chunk::COORD_BIT_SIZE,
|
||||||
$this->location->getFloorZ() >> 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())){
|
if(!isset($this->usedChunks[$hash]) || $this->usedChunks[$hash]->equals(UsedChunkStatus::NEEDED())){
|
||||||
$newOrder[$hash] = true;
|
$newOrder[$hash] = true;
|
||||||
}
|
}
|
||||||
|
if($radius < $tickingChunkRadius){
|
||||||
|
$tickingChunks[$hash] = true;
|
||||||
|
}
|
||||||
unset($unloadChunks[$hash]);
|
unset($unloadChunks[$hash]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -914,10 +932,18 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
World::getXZ($index, $X, $Z);
|
World::getXZ($index, $X, $Z);
|
||||||
$this->unloadChunk($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->loadQueue = $newOrder;
|
||||||
|
$this->tickingChunks = $tickingChunks;
|
||||||
|
|
||||||
if(count($this->loadQueue) > 0 || count($unloadChunks) > 0){
|
if(count($this->loadQueue) > 0 || count($unloadChunks) > 0){
|
||||||
$this->chunkLoader->setCurrentLocation($this->location);
|
|
||||||
$this->getNetworkSession()->syncViewAreaCenterPoint($this->location, $this->viewDistance);
|
$this->getNetworkSession()->syncViewAreaCenterPoint($this->location, $this->viewDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,10 @@ namespace pocketmine\player;
|
|||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\world\TickingChunkLoader;
|
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{
|
final class PlayerChunkLoader implements TickingChunkLoader{
|
||||||
public function __construct(private Vector3 $currentLocation){}
|
public function __construct(private Vector3 $currentLocation){}
|
||||||
|
|
||||||
|
34
src/world/ChunkTicker.php
Normal file
34
src/world/ChunkTicker.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\world;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to signal to the World that a chunk should be ticked.
|
||||||
|
*
|
||||||
|
* @see World::registerTickingChunk()
|
||||||
|
* @see World::unregisterTickingChunk()
|
||||||
|
*/
|
||||||
|
final class ChunkTicker{
|
||||||
|
|
||||||
|
}
|
37
src/world/TickingChunkEntry.php
Normal file
37
src/world/TickingChunkEntry.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pocketmine\world;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class TickingChunkEntry{
|
||||||
|
/**
|
||||||
|
* @var ChunkTicker[] spl_object_id => ChunkTicker
|
||||||
|
* @phpstan-var array<int, ChunkTicker>
|
||||||
|
*/
|
||||||
|
public array $tickers = [];
|
||||||
|
|
||||||
|
public bool $ready = false;
|
||||||
|
}
|
@ -27,6 +27,10 @@ namespace pocketmine\world;
|
|||||||
* TickingChunkLoader includes all of the same functionality as ChunkLoader (it can be used in the same way).
|
* 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
|
* However, using this version will also cause chunks around the loader's reported coordinates to get random block
|
||||||
* updates.
|
* updates.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
|
* @see World::registerTickingChunk()
|
||||||
|
* @see World::unregisterTickingChunk()
|
||||||
*/
|
*/
|
||||||
interface TickingChunkLoader extends ChunkLoader{
|
interface TickingChunkLoader extends ChunkLoader{
|
||||||
|
|
||||||
|
@ -212,13 +212,24 @@ class World implements ChunkManager{
|
|||||||
/**
|
/**
|
||||||
* @var TickingChunkLoader[] spl_object_id => TickingChunkLoader
|
* @var TickingChunkLoader[] spl_object_id => TickingChunkLoader
|
||||||
* @phpstan-var array<int, TickingChunkLoader>
|
* @phpstan-var array<int, TickingChunkLoader>
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
private array $tickingLoaders = [];
|
private array $tickingLoaders = [];
|
||||||
/**
|
/**
|
||||||
* @var int[] spl_object_id => number of chunks
|
* @var int[] spl_object_id => number of chunks
|
||||||
* @phpstan-var array<int, int>
|
* @phpstan-var array<int, int>
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
private array $tickingLoaderCounter = [];
|
private array $tickingLoaderCounter = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TickingChunkEntry[] chunkHash => TickingChunkEntry
|
||||||
|
* @phpstan-var array<ChunkPosHash, TickingChunkEntry>
|
||||||
|
*/
|
||||||
|
private array $tickingChunks = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader]
|
* @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader]
|
||||||
* @phpstan-var array<ChunkPosHash, array<int, ChunkLoader>>
|
* @phpstan-var array<ChunkPosHash, array<int, ChunkLoader>>
|
||||||
@ -1152,32 +1163,61 @@ class World implements ChunkManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the radius of chunks to be ticked around each ticking chunk loader (usually players). This is referred to
|
* Returns the radius of chunks to be ticked around each player. This is referred to as "simulation distance" in the
|
||||||
* as "simulation distance" in the Minecraft: Bedrock world options screen.
|
* Minecraft: Bedrock world options screen.
|
||||||
*/
|
*/
|
||||||
public function getChunkTickRadius() : int{
|
public function getChunkTickRadius() : int{
|
||||||
return $this->chunkTickRadius;
|
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{
|
public function setChunkTickRadius(int $radius) : void{
|
||||||
$this->chunkTickRadius = $radius;
|
$this->chunkTickRadius = $radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function tickChunks() : void{
|
/**
|
||||||
if($this->chunkTickRadius <= 0 || count($this->tickingLoaders) === 0){
|
* Instructs the World to tick the specified chunk, for as long as this chunk ticker (or any other chunk ticker) is
|
||||||
return;
|
* 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();
|
/**
|
||||||
|
* Unregisters the given chunk ticker from the specified chunk. If there are other tickers still registered to the
|
||||||
/** @var bool[] $chunkTickList chunkhash => dummy */
|
* chunk, it will continue to be ticked.
|
||||||
$chunkTickList = [];
|
*/
|
||||||
|
public function unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
|
||||||
$chunkTickableCache = [];
|
$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<int, true> $chunkTickList
|
||||||
|
* @phpstan-param array<int, bool> $chunkTickableCache
|
||||||
|
* @phpstan-param-out array<int, true> $chunkTickList
|
||||||
|
* @phpstan-param-out array<int, bool> $chunkTickableCache
|
||||||
|
*/
|
||||||
|
private function selectTickableChunksLegacy(array &$chunkTickList, array &$chunkTickableCache) : void{
|
||||||
$centerChunks = [];
|
$centerChunks = [];
|
||||||
|
|
||||||
$selector = new ChunkSelector();
|
$selector = new ChunkSelector();
|
||||||
@ -1202,6 +1242,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();
|
$this->timings->randomChunkUpdatesChunkSelection->stopTiming();
|
||||||
|
|
||||||
@ -1253,11 +1325,28 @@ class World implements ChunkManager{
|
|||||||
return true;
|
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{
|
private function orderLightPopulation(int $chunkX, int $chunkZ) : void{
|
||||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||||
$lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated();
|
$lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated();
|
||||||
if($lightPopulatedState === false){
|
if($lightPopulatedState === false){
|
||||||
$this->chunks[$chunkHash]->setLightPopulated(null);
|
$this->chunks[$chunkHash]->setLightPopulated(null);
|
||||||
|
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
|
||||||
|
|
||||||
$this->workerPool->submitTask(new LightPopulationTask(
|
$this->workerPool->submitTask(new LightPopulationTask(
|
||||||
$this->chunks[$chunkHash],
|
$this->chunks[$chunkHash],
|
||||||
@ -2332,6 +2421,7 @@ class World implements ChunkManager{
|
|||||||
throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked");
|
throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked");
|
||||||
}
|
}
|
||||||
$this->chunkLock[$chunkHash] = $lockId;
|
$this->chunkLock[$chunkHash] = $lockId;
|
||||||
|
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2397,6 +2487,7 @@ class World implements ChunkManager{
|
|||||||
unset($this->blockCache[$chunkHash]);
|
unset($this->blockCache[$chunkHash]);
|
||||||
unset($this->changedBlocks[$chunkHash]);
|
unset($this->changedBlocks[$chunkHash]);
|
||||||
$chunk->setTerrainDirty();
|
$chunk->setTerrainDirty();
|
||||||
|
$this->markTickingChunkUnavailable($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking
|
||||||
|
|
||||||
if(!$this->isChunkInUse($chunkX, $chunkZ)){
|
if(!$this->isChunkInUse($chunkX, $chunkZ)){
|
||||||
$this->unloadChunkRequest($chunkX, $chunkZ);
|
$this->unloadChunkRequest($chunkX, $chunkZ);
|
||||||
@ -2819,6 +2910,8 @@ class World implements ChunkManager{
|
|||||||
unset($this->chunks[$chunkHash]);
|
unset($this->chunks[$chunkHash]);
|
||||||
unset($this->blockCache[$chunkHash]);
|
unset($this->blockCache[$chunkHash]);
|
||||||
unset($this->changedBlocks[$chunkHash]);
|
unset($this->changedBlocks[$chunkHash]);
|
||||||
|
unset($this->tickingChunks[$chunkHash]);
|
||||||
|
$this->markTickingChunkUnavailable($x, $z);
|
||||||
|
|
||||||
if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){
|
if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){
|
||||||
$this->logger->debug("Rejecting population promise for chunk $x $z");
|
$this->logger->debug("Rejecting population promise for chunk $x $z");
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pmmp\TesterPlugin;
|
||||||
|
|
||||||
|
use pmmp\TesterPlugin\event\ChildEvent;
|
||||||
|
use pmmp\TesterPlugin\event\GrandchildEvent;
|
||||||
|
use pmmp\TesterPlugin\event\ParentEvent;
|
||||||
|
use pocketmine\event\EventPriority;
|
||||||
|
use function implode;
|
||||||
|
|
||||||
|
final class EventHandlerInheritanceTest extends Test{
|
||||||
|
|
||||||
|
private const EXPECTED_ORDER = [
|
||||||
|
GrandchildEvent::class,
|
||||||
|
ChildEvent::class,
|
||||||
|
ParentEvent::class
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
private array $callOrder = [];
|
||||||
|
|
||||||
|
public function getName() : string{
|
||||||
|
return "Event Handler Inheritance Test";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription() : string{
|
||||||
|
return "Tests that child events are correctly passed to parent event handlers";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run() : void{
|
||||||
|
$plugin = $this->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,9 @@ class Main extends PluginBase implements Listener{
|
|||||||
}
|
}
|
||||||
}), 10);
|
}), 10);
|
||||||
|
|
||||||
$this->waitingTests = [];
|
$this->waitingTests = [
|
||||||
|
new EventHandlerInheritanceTest($this),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onServerCommand(CommandEvent $event) : void{
|
public function onServerCommand(CommandEvent $event) : void{
|
||||||
|
28
tests/plugins/TesterPlugin/src/event/ChildEvent.php
Normal file
28
tests/plugins/TesterPlugin/src/event/ChildEvent.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pmmp\TesterPlugin\event;
|
||||||
|
|
||||||
|
class ChildEvent extends ParentEvent{
|
||||||
|
|
||||||
|
}
|
28
tests/plugins/TesterPlugin/src/event/GrandchildEvent.php
Normal file
28
tests/plugins/TesterPlugin/src/event/GrandchildEvent.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pmmp\TesterPlugin\event;
|
||||||
|
|
||||||
|
class GrandchildEvent extends ChildEvent{
|
||||||
|
|
||||||
|
}
|
28
tests/plugins/TesterPlugin/src/event/ParentEvent.php
Normal file
28
tests/plugins/TesterPlugin/src/event/ParentEvent.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ____ _ _ __ __ _ __ __ ____
|
||||||
|
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||||
|
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||||
|
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||||
|
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* @author PocketMine Team
|
||||||
|
* @link http://www.pocketmine.net/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace pmmp\TesterPlugin\event;
|
||||||
|
|
||||||
|
class ParentEvent extends \pocketmine\event\Event{
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user