mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-08 10:53:05 +00:00
Compare commits
64 Commits
Author | SHA1 | Date | |
---|---|---|---|
11e34b3e5c | |||
f9318bf286 | |||
674b65f789 | |||
a77fc8109f | |||
6102740ee3 | |||
40168a457e | |||
d07acd0013 | |||
9561ae5af7 | |||
56fbd45dd5 | |||
b5dc72b0ee | |||
4ba57f2b03 | |||
df0d72bf61 | |||
a534ac759a | |||
5ab954b7a0 | |||
6c6f686f8e | |||
bf7975da57 | |||
7aeedd8220 | |||
3dd1ce2d02 | |||
5a29d07021 | |||
692e1253c6 | |||
ab0c444823 | |||
e48a4aaa55 | |||
6fc4ce0f86 | |||
10243c7b2c | |||
18b528f72d | |||
7e92da126d | |||
ba62e0f9cb | |||
858d4a2ed2 | |||
87d8c1ea11 | |||
89deb0fe18 | |||
ad88490e84 | |||
3490e2b06a | |||
946c2fbacc | |||
1c0eed56f1 | |||
9e9b4db00f | |||
e667b5c7db | |||
76ebedff6a | |||
3ea8d27a3b | |||
d6c923b525 | |||
1683aa681d | |||
bf84caa02c | |||
734adec90d | |||
4724195791 | |||
f32a853bd4 | |||
61b0ad3e7f | |||
b2f755720d | |||
8ef2780dcd | |||
b19c7212ab | |||
73522d06ef | |||
a6a360d179 | |||
199ef7401f | |||
f63d349be4 | |||
02e11b5a60 | |||
4a770e5801 | |||
a2ff9649d5 | |||
821dd8885b | |||
bed218d1dd | |||
5e1f837a73 | |||
b49a9ae81d | |||
4c60e82110 | |||
1959d6dc9b | |||
9db7e5f0ca | |||
c1cef19f84 | |||
cebdb95265 |
111
changelogs/4.19.md
Normal file
111
changelogs/4.19.md
Normal file
@ -0,0 +1,111 @@
|
||||
**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`
|
||||
|
||||
# 4.19.1
|
||||
Released 14th April 2023.
|
||||
|
||||
## Fixes
|
||||
- Fixed inventory rollbacks when spreading items in ender chests.
|
||||
- Fixed inventory rollbacks when shift-clicking to craft and the outputs would have been split across multiple inventory slots.
|
||||
- Fixed incorrect spawn terrain generation for newly created worlds.
|
||||
- Fixed `chunk-ticking.tick-radius` not disabling chunk ticking when set to `0`.
|
||||
- Fixed chunks not being ticked if they previously left a player's simulation distance without leaving their view distance.
|
||||
- Fixed height of collision boxes for Grass Path and Farmland blocks.
|
||||
|
||||
# 4.19.2
|
||||
Released 14th April 2023.
|
||||
|
||||
## Fixes
|
||||
- Fixed player timings duplication leading to extremely large timings reports when timings runs for a long time with many players.
|
||||
- Packet timings are now indexed by class FQN instead of packet ID. This prevents erroneous timer reuse on packet ID reuse (e.g. multi version servers).
|
||||
- Fixed entity timings being shared by different classes with the same short name. This led to incorrect timings being reported for some entities when custom entities were used.
|
||||
|
||||
# 4.19.3
|
||||
Released 21st April 2023.
|
||||
|
||||
## General
|
||||
- Error IDs for `Packet processing error` disconnects are now split into 4-character chunks to make them easier to type (since they can't be copied from the disconnection screen of a client).
|
||||
|
||||
## Fixes
|
||||
- Fixed entity-block intersections being checked twice per tick. Besides wasting CPU time, this may have caused unexpected behaviour during entity-block interactions with blocks like water or cacti.
|
||||
- Fixed performance issue in network inventory synchronization due item NBT being prepared twice.
|
||||
- Fixed `tools/simulate-chunk-selector.php` argument parsing being completely broken (weird behaviour of PHP `getopt()`).
|
||||
|
||||
## Internals
|
||||
- `TimingsHandler->stopTiming()` now logs an error message if a subtimer wasn't stopped, rather than throwing an exception.
|
||||
- Due to interactions between `try...finally` and unexpected errors, throwing exceptions made it difficult for plugin developers to debug errors in their plugins, since it obscured the original error.
|
@ -56,7 +56,7 @@
|
||||
"webmozart/path-util": "^2.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.10.11",
|
||||
"phpstan/phpstan": "1.10.13",
|
||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||
"phpunit/phpunit": "^9.2"
|
||||
|
24
composer.lock
generated
24
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6cd5185a409af08d842a5e41ba3b877b",
|
||||
"content-hash": "d819da2b086b38c4dd2a5028084e251c",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -1884,16 +1884,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.10.11",
|
||||
"version": "1.10.13",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21"
|
||||
"reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/8aa62e6ea8b58ffb650e02940e55a788cbc3fe21",
|
||||
"reference": "8aa62e6ea8b58ffb650e02940e55a788cbc3fe21",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/f07bf8c6980b81bf9e49d44bd0caf2e737614a70",
|
||||
"reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1942,7 +1942,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-04-04T19:17:42+00:00"
|
||||
"time": "2023-04-12T19:29:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
@ -2365,16 +2365,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "9.6.6",
|
||||
"version": "9.6.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115"
|
||||
"reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115",
|
||||
"reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
|
||||
"reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -2448,7 +2448,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.6"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -2464,7 +2464,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-03-27T11:43:46+00:00"
|
||||
"time": "2023-04-14T08:58:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "4.18.4";
|
||||
public const BASE_VERSION = "4.19.3";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BUILD_CHANNEL = "stable";
|
||||
|
||||
|
@ -64,7 +64,7 @@ class Farmland extends Transparent{
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()]; //TODO: this should be trimmed at the top by 1/16, but MCPE currently treats them as a full block (https://bugs.mojang.com/browse/MCPE-12109)
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)];
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
|
@ -33,7 +33,7 @@ class GrassPath extends Transparent{
|
||||
* @return AxisAlignedBB[]
|
||||
*/
|
||||
protected function recalculateCollisionBoxes() : array{
|
||||
return [AxisAlignedBB::one()]; //TODO: this should be trimmed at the top by 1/16, but MCPE currently treats them as a full block (https://bugs.mojang.com/browse/MCPE-12109)
|
||||
return [AxisAlignedBB::one()->trim(Facing::UP, 1 / 16)];
|
||||
}
|
||||
|
||||
public function onNearbyBlockChange() : void{
|
||||
|
@ -139,7 +139,7 @@ abstract class Command{
|
||||
public function setLabel(string $name) : bool{
|
||||
$this->nextLabel = $name;
|
||||
if(!$this->isRegistered()){
|
||||
$this->timings = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Command: " . $name);
|
||||
$this->timings = new TimingsHandler("Command: " . $name, group: Timings::GROUP_BREAKDOWN);
|
||||
$this->label = $name;
|
||||
|
||||
return true;
|
||||
|
@ -167,6 +167,7 @@ class TimingsCommand extends VanillaCommand{
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_timingsRead(
|
||||
"https://" . $host . "/?id=" . $response["id"]));
|
||||
}else{
|
||||
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
|
||||
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +115,8 @@ abstract class Entity{
|
||||
/** @var Block[]|null */
|
||||
protected $blocksAround;
|
||||
|
||||
private bool $checkBlockIntersectionsNextTick = true;
|
||||
|
||||
/** @var Location */
|
||||
protected $location;
|
||||
/** @var Location */
|
||||
@ -649,7 +651,10 @@ abstract class Entity{
|
||||
|
||||
$hasUpdate = false;
|
||||
|
||||
$this->checkBlockIntersections();
|
||||
if($this->checkBlockIntersectionsNextTick){
|
||||
$this->checkBlockIntersections();
|
||||
}
|
||||
$this->checkBlockIntersectionsNextTick = true;
|
||||
|
||||
if($this->location->y <= World::Y_MIN - 16 && $this->isAlive()){
|
||||
$ev = new EntityDamageEvent($this, EntityDamageEvent::CAUSE_VOID, 10);
|
||||
@ -1308,6 +1313,7 @@ abstract class Entity{
|
||||
}
|
||||
|
||||
protected function checkBlockIntersections() : void{
|
||||
$this->checkBlockIntersectionsNextTick = false;
|
||||
$vectors = [];
|
||||
|
||||
foreach($this->getBlocksAroundWithEntityInsideActions() as $block){
|
||||
@ -1319,10 +1325,12 @@ abstract class Entity{
|
||||
}
|
||||
}
|
||||
|
||||
$vector = Vector3::sum(...$vectors);
|
||||
if($vector->lengthSquared() > 0){
|
||||
$d = 0.014;
|
||||
$this->motion = $this->motion->addVector($vector->normalize()->multiply($d));
|
||||
if(count($vectors) > 0){
|
||||
$vector = Vector3::sum(...$vectors);
|
||||
if($vector->lengthSquared() > 0){
|
||||
$d = 0.014;
|
||||
$this->motion = $this->motion->addVector($vector->normalize()->multiply($d));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ use pocketmine\network\mcpe\protocol\types\entity\EntityIds;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataCollection;
|
||||
use pocketmine\network\mcpe\protocol\types\entity\EntityMetadataProperties;
|
||||
use pocketmine\player\Player;
|
||||
use function max;
|
||||
use function sqrt;
|
||||
|
||||
class ExperienceOrb extends Entity{
|
||||
@ -48,6 +49,10 @@ class ExperienceOrb extends Entity{
|
||||
/** Split sizes used for dropping experience orbs. */
|
||||
public const ORB_SPLIT_SIZES = [2477, 1237, 617, 307, 149, 73, 37, 17, 7, 3, 1]; //This is indexed biggest to smallest so that we can return as soon as we found the biggest value.
|
||||
|
||||
public const DEFAULT_DESPAWN_DELAY = 6000;
|
||||
public const NEVER_DESPAWN = -1;
|
||||
public const MAX_DESPAWN_DELAY = 32767 + self::DEFAULT_DESPAWN_DELAY; //max value storable by mojang NBT :(
|
||||
|
||||
/**
|
||||
* Returns the largest size of normal XP orb that will be spawned for the specified amount of XP. Used to split XP
|
||||
* up into multiple orbs when an amount of XP is dropped.
|
||||
@ -82,7 +87,10 @@ class ExperienceOrb extends Entity{
|
||||
public $gravity = 0.04;
|
||||
public $drag = 0.02;
|
||||
|
||||
/** @var int */
|
||||
/**
|
||||
* @var int
|
||||
* @deprecated
|
||||
*/
|
||||
protected $age = 0;
|
||||
|
||||
/**
|
||||
@ -100,6 +108,8 @@ class ExperienceOrb extends Entity{
|
||||
/** @var int */
|
||||
protected $xpValue;
|
||||
|
||||
private int $despawnDelay = self::DEFAULT_DESPAWN_DELAY;
|
||||
|
||||
public function __construct(Location $location, int $xpValue, ?CompoundTag $nbt = null){
|
||||
$this->xpValue = $xpValue;
|
||||
parent::__construct($location, $nbt);
|
||||
@ -111,12 +121,22 @@ class ExperienceOrb extends Entity{
|
||||
parent::initEntity($nbt);
|
||||
|
||||
$this->age = $nbt->getShort(self::TAG_AGE, 0);
|
||||
if($this->age === -32768){
|
||||
$this->despawnDelay = self::NEVER_DESPAWN;
|
||||
}else{
|
||||
$this->despawnDelay = max(0, self::DEFAULT_DESPAWN_DELAY - $this->age);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveNBT() : CompoundTag{
|
||||
$nbt = parent::saveNBT();
|
||||
|
||||
$nbt->setShort(self::TAG_AGE, $this->age);
|
||||
if($this->despawnDelay === self::NEVER_DESPAWN){
|
||||
$age = -32768;
|
||||
}else{
|
||||
$age = self::DEFAULT_DESPAWN_DELAY - $this->despawnDelay;
|
||||
}
|
||||
$nbt->setShort(self::TAG_AGE, $age);
|
||||
|
||||
$nbt->setShort(self::TAG_VALUE_PC, $this->getXpValue());
|
||||
$nbt->setInt(self::TAG_VALUE_PE, $this->getXpValue());
|
||||
@ -124,6 +144,15 @@ class ExperienceOrb extends Entity{
|
||||
return $nbt;
|
||||
}
|
||||
|
||||
public function getDespawnDelay() : int{ return $this->despawnDelay; }
|
||||
|
||||
public function setDespawnDelay(int $despawnDelay) : void{
|
||||
if(($despawnDelay < 0 || $despawnDelay > self::MAX_DESPAWN_DELAY) && $despawnDelay !== self::NEVER_DESPAWN){
|
||||
throw new \InvalidArgumentException("Despawn ticker must be in range 0 ... " . self::MAX_DESPAWN_DELAY . " or " . self::NEVER_DESPAWN . ", got $despawnDelay");
|
||||
}
|
||||
$this->despawnDelay = $despawnDelay;
|
||||
}
|
||||
|
||||
public function getXpValue() : int{
|
||||
return $this->xpValue;
|
||||
}
|
||||
@ -161,7 +190,8 @@ class ExperienceOrb extends Entity{
|
||||
$hasUpdate = parent::entityBaseTick($tickDiff);
|
||||
|
||||
$this->age += $tickDiff;
|
||||
if($this->age > 6000){
|
||||
$this->despawnDelay -= $tickDiff;
|
||||
if($this->despawnDelay <= 0){
|
||||
$this->flagForDespawn();
|
||||
return true;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace pocketmine\event;
|
||||
|
||||
use pocketmine\timings\Timings;
|
||||
use function get_class;
|
||||
|
||||
abstract class Event{
|
||||
@ -51,22 +52,19 @@ abstract class Event{
|
||||
throw new \RuntimeException("Recursive event call detected (reached max depth of " . self::MAX_EVENT_CALL_DEPTH . " calls)");
|
||||
}
|
||||
|
||||
$timings = Timings::getEventTimings($this);
|
||||
$timings->startTiming();
|
||||
|
||||
$handlerList = HandlerListManager::global()->getListFor(get_class($this));
|
||||
|
||||
++self::$eventCallDepth;
|
||||
try{
|
||||
foreach(EventPriority::ALL as $priority){
|
||||
$currentList = $handlerList;
|
||||
while($currentList !== null){
|
||||
foreach($currentList->getListenersByPriority($priority) as $registration){
|
||||
$registration->callEvent($this);
|
||||
}
|
||||
|
||||
$currentList = $currentList->getParent();
|
||||
}
|
||||
foreach($handlerList->getListenerList() as $registration){
|
||||
$registration->callEvent($this);
|
||||
}
|
||||
}finally{
|
||||
--self::$eventCallDepth;
|
||||
$timings->stopTiming();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ use function mb_strtoupper;
|
||||
* LOWEST -> LOW -> NORMAL -> HIGH -> HIGHEST -> MONITOR
|
||||
*
|
||||
* MONITOR events should not change the event outcome or contents
|
||||
*
|
||||
* WARNING: If these values are changed, handler sorting in HandlerList::getListenerList() may need to be updated.
|
||||
*/
|
||||
final class EventPriority{
|
||||
|
||||
|
@ -24,13 +24,20 @@ declare(strict_types=1);
|
||||
namespace pocketmine\event;
|
||||
|
||||
use pocketmine\plugin\Plugin;
|
||||
use function array_fill_keys;
|
||||
use function array_merge;
|
||||
use function krsort;
|
||||
use function spl_object_id;
|
||||
use const SORT_NUMERIC;
|
||||
|
||||
class HandlerList{
|
||||
/** @var RegisteredListener[][] */
|
||||
private array $handlerSlots = [];
|
||||
|
||||
private RegisteredListenerCache $handlerCache;
|
||||
|
||||
/** @var RegisteredListenerCache[] */
|
||||
private array $affectedHandlerCaches = [];
|
||||
|
||||
/**
|
||||
* @phpstan-template TEvent of Event
|
||||
* @phpstan-param class-string<TEvent> $class
|
||||
@ -39,7 +46,10 @@ class HandlerList{
|
||||
private string $class,
|
||||
private ?HandlerList $parentList
|
||||
){
|
||||
$this->handlerSlots = array_fill_keys(EventPriority::ALL, []);
|
||||
$this->handlerCache = new RegisteredListenerCache();
|
||||
for($list = $this; $list !== null; $list = $list->parentList){
|
||||
$list->affectedHandlerCaches[spl_object_id($this->handlerCache)] = $this->handlerCache;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,6 +60,7 @@ class HandlerList{
|
||||
throw new \InvalidArgumentException("This listener is already registered to priority {$listener->getPriority()} of event {$this->class}");
|
||||
}
|
||||
$this->handlerSlots[$listener->getPriority()][spl_object_id($listener)] = $listener;
|
||||
$this->invalidateAffectedCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,6 +70,7 @@ class HandlerList{
|
||||
foreach($listeners as $listener){
|
||||
$this->register($listener);
|
||||
}
|
||||
$this->invalidateAffectedCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,14 +88,14 @@ class HandlerList{
|
||||
}
|
||||
}
|
||||
}elseif($object instanceof RegisteredListener){
|
||||
if(isset($this->handlerSlots[$object->getPriority()][spl_object_id($object)])){
|
||||
unset($this->handlerSlots[$object->getPriority()][spl_object_id($object)]);
|
||||
}
|
||||
unset($this->handlerSlots[$object->getPriority()][spl_object_id($object)]);
|
||||
}
|
||||
$this->invalidateAffectedCaches();
|
||||
}
|
||||
|
||||
public function clear() : void{
|
||||
$this->handlerSlots = array_fill_keys(EventPriority::ALL, []);
|
||||
$this->handlerSlots = [];
|
||||
$this->invalidateAffectedCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,4 +108,40 @@ class HandlerList{
|
||||
public function getParent() : ?HandlerList{
|
||||
return $this->parentList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all known caches which might be affected by this list's contents.
|
||||
*/
|
||||
private function invalidateAffectedCaches() : void{
|
||||
foreach($this->affectedHandlerCaches as $cache){
|
||||
$cache->list = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RegisteredListener[]
|
||||
* @phpstan-return list<RegisteredListener>
|
||||
*/
|
||||
public function getListenerList() : array{
|
||||
if($this->handlerCache->list !== null){
|
||||
return $this->handlerCache->list;
|
||||
}
|
||||
|
||||
$handlerLists = [];
|
||||
for($currentList = $this; $currentList !== null; $currentList = $currentList->parentList){
|
||||
$handlerLists[] = $currentList;
|
||||
}
|
||||
|
||||
$listenersByPriority = [];
|
||||
foreach($handlerLists as $currentList){
|
||||
foreach($currentList->handlerSlots as $priority => $listeners){
|
||||
$listenersByPriority[$priority] = array_merge($listenersByPriority[$priority] ?? [], $listeners);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: why on earth do the priorities have higher values for lower priority?
|
||||
krsort($listenersByPriority, SORT_NUMERIC);
|
||||
|
||||
return $this->handlerCache->list = array_merge(...$listenersByPriority);
|
||||
}
|
||||
}
|
||||
|
@ -57,8 +57,11 @@ class RegisteredListener{
|
||||
return;
|
||||
}
|
||||
$this->timings->startTiming();
|
||||
($this->handler)($event);
|
||||
$this->timings->stopTiming();
|
||||
try{
|
||||
($this->handler)($event);
|
||||
}finally{
|
||||
$this->timings->stopTiming();
|
||||
}
|
||||
}
|
||||
|
||||
public function isHandlingCancelled() : bool{
|
||||
|
38
src/event/RegisteredListenerCache.php
Normal file
38
src/event/RegisteredListenerCache.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?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\event;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class RegisteredListenerCache{
|
||||
|
||||
/**
|
||||
* List of all handlers that will be called for a particular event, ordered by execution order.
|
||||
*
|
||||
* @var RegisteredListener[]
|
||||
* @phpstan-var list<RegisteredListener>
|
||||
*/
|
||||
public ?array $list = null;
|
||||
}
|
@ -30,6 +30,7 @@ use pocketmine\item\Item;
|
||||
*/
|
||||
class DelegateInventory extends BaseInventory{
|
||||
private InventoryListener $inventoryListener;
|
||||
private bool $backingInventoryChanging = false;
|
||||
|
||||
public function __construct(
|
||||
private Inventory $backingInventory
|
||||
@ -39,12 +40,22 @@ class DelegateInventory extends BaseInventory{
|
||||
$this->backingInventory->getListeners()->add($this->inventoryListener = new CallbackInventoryListener(
|
||||
static function(Inventory $unused, int $slot, Item $oldItem) use ($weakThis) : void{
|
||||
if(($strongThis = $weakThis->get()) !== null){
|
||||
$strongThis->onSlotChange($slot, $oldItem);
|
||||
$strongThis->backingInventoryChanging = true;
|
||||
try{
|
||||
$strongThis->onSlotChange($slot, $oldItem);
|
||||
}finally{
|
||||
$strongThis->backingInventoryChanging = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
static function(Inventory $unused, array $oldContents) use ($weakThis) : void{
|
||||
if(($strongThis = $weakThis->get()) !== null){
|
||||
$strongThis->onContentChange($oldContents);
|
||||
$strongThis->backingInventoryChanging = true;
|
||||
try{
|
||||
$strongThis->onContentChange($oldContents);
|
||||
}finally{
|
||||
$strongThis->backingInventoryChanging = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
@ -73,4 +84,16 @@ class DelegateInventory extends BaseInventory{
|
||||
protected function internalSetContents(array $items) : void{
|
||||
$this->backingInventory->setContents($items);
|
||||
}
|
||||
|
||||
protected function onSlotChange(int $index, Item $before) : void{
|
||||
if($this->backingInventoryChanging){
|
||||
parent::onSlotChange($index, $before);
|
||||
}
|
||||
}
|
||||
|
||||
protected function onContentChange(array $itemsBefore) : void{
|
||||
if($this->backingInventoryChanging){
|
||||
parent::onContentChange($itemsBefore);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -353,30 +353,35 @@ class Item implements \JsonSerializable{
|
||||
}
|
||||
|
||||
protected function serializeCompoundTag(CompoundTag $tag) : void{
|
||||
$display = $tag->getCompoundTag(self::TAG_DISPLAY) ?? new CompoundTag();
|
||||
$display = $tag->getCompoundTag(self::TAG_DISPLAY);
|
||||
|
||||
$this->hasCustomName() ?
|
||||
$display->setString(self::TAG_DISPLAY_NAME, $this->getCustomName()) :
|
||||
$display->removeTag(self::TAG_DISPLAY_NAME);
|
||||
if($this->customName !== ""){
|
||||
$display ??= new CompoundTag();
|
||||
$display->setString(self::TAG_DISPLAY_NAME, $this->customName);
|
||||
}else{
|
||||
$display?->removeTag(self::TAG_DISPLAY_NAME);
|
||||
}
|
||||
|
||||
if(count($this->lore) > 0){
|
||||
$loreTag = new ListTag();
|
||||
foreach($this->lore as $line){
|
||||
$loreTag->push(new StringTag($line));
|
||||
}
|
||||
$display ??= new CompoundTag();
|
||||
$display->setTag(self::TAG_DISPLAY_LORE, $loreTag);
|
||||
}else{
|
||||
$display->removeTag(self::TAG_DISPLAY_LORE);
|
||||
$display?->removeTag(self::TAG_DISPLAY_LORE);
|
||||
}
|
||||
$display->count() > 0 ?
|
||||
$display !== null && $display->count() > 0 ?
|
||||
$tag->setTag(self::TAG_DISPLAY, $display) :
|
||||
$tag->removeTag(self::TAG_DISPLAY);
|
||||
|
||||
if($this->hasEnchantments()){
|
||||
if(count($this->enchantments) > 0){
|
||||
$ench = new ListTag();
|
||||
foreach($this->getEnchantments() as $enchantmentInstance){
|
||||
$enchantmentIdMap = EnchantmentIdMap::getInstance();
|
||||
foreach($this->enchantments as $enchantmentInstance){
|
||||
$ench->push(CompoundTag::create()
|
||||
->setShort(self::TAG_ENCH_ID, EnchantmentIdMap::getInstance()->toId($enchantmentInstance->getType()))
|
||||
->setShort(self::TAG_ENCH_ID, $enchantmentIdMap->toId($enchantmentInstance->getType()))
|
||||
->setShort(self::TAG_ENCH_LVL, $enchantmentInstance->getLevel())
|
||||
);
|
||||
}
|
||||
@ -385,8 +390,8 @@ class Item implements \JsonSerializable{
|
||||
$tag->removeTag(self::TAG_ENCH);
|
||||
}
|
||||
|
||||
($blockData = $this->getCustomBlockData()) !== null ?
|
||||
$tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $blockData) :
|
||||
$this->blockEntityTag !== null ?
|
||||
$tag->setTag(self::TAG_BLOCK_ENTITY_TAG, clone $this->blockEntityTag) :
|
||||
$tag->removeTag(self::TAG_BLOCK_ENTITY_TAG);
|
||||
|
||||
if(count($this->canPlaceOn) > 0){
|
||||
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\network\mcpe\convert;
|
||||
|
||||
use pocketmine\block\Block;
|
||||
use pocketmine\block\BlockLegacyIds;
|
||||
use pocketmine\item\Durable;
|
||||
use pocketmine\item\Item;
|
||||
@ -135,14 +136,16 @@ class TypeConverter{
|
||||
if($itemStack->isNull()){
|
||||
return ItemStack::null();
|
||||
}
|
||||
$nbt = null;
|
||||
if($itemStack->hasNamedTag()){
|
||||
$nbt = clone $itemStack->getNamedTag();
|
||||
$nbt = $itemStack->getNamedTag();
|
||||
if($nbt->count() === 0){
|
||||
$nbt = null;
|
||||
}else{
|
||||
$nbt = clone $nbt;
|
||||
}
|
||||
|
||||
$isBlockItem = $itemStack->getId() < 256;
|
||||
|
||||
$idMeta = ItemTranslator::getInstance()->toNetworkIdQuiet($itemStack->getId(), $itemStack->getMeta());
|
||||
$internalId = $itemStack->getId();
|
||||
$internalMeta = $itemStack->getMeta();
|
||||
$idMeta = ItemTranslator::getInstance()->toNetworkIdQuiet($internalId, $internalMeta);
|
||||
if($idMeta === null){
|
||||
//Display unmapped items as INFO_UPDATE, but stick something in their NBT to make sure they don't stack with
|
||||
//other unmapped items.
|
||||
@ -150,8 +153,8 @@ class TypeConverter{
|
||||
if($nbt === null){
|
||||
$nbt = new CompoundTag();
|
||||
}
|
||||
$nbt->setInt(self::PM_ID_TAG, $itemStack->getId());
|
||||
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
|
||||
$nbt->setInt(self::PM_ID_TAG, $internalId);
|
||||
$nbt->setInt(self::PM_META_TAG, $internalMeta);
|
||||
}else{
|
||||
[$id, $meta] = $idMeta;
|
||||
|
||||
@ -166,23 +169,15 @@ class TypeConverter{
|
||||
}
|
||||
$nbt->setInt(self::DAMAGE_TAG, $itemStack->getDamage());
|
||||
$meta = 0;
|
||||
}elseif($isBlockItem && $itemStack->getMeta() !== 0){
|
||||
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
|
||||
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
|
||||
//client-side. Aside from being very annoying, this also breaks various server-side behaviours.
|
||||
if($nbt === null){
|
||||
$nbt = new CompoundTag();
|
||||
}
|
||||
$nbt->setInt(self::PM_META_TAG, $itemStack->getMeta());
|
||||
$meta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$blockRuntimeId = 0;
|
||||
if($isBlockItem){
|
||||
if($internalId < 256){
|
||||
$block = $itemStack->getBlock();
|
||||
if($block->getId() !== BlockLegacyIds::AIR){
|
||||
$blockRuntimeId = RuntimeBlockMapping::getInstance()->toRuntimeId($block->getFullId());
|
||||
$meta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,6 +203,11 @@ class TypeConverter{
|
||||
$compound = $itemStack->getNbt();
|
||||
|
||||
[$id, $meta] = ItemTranslator::getInstance()->fromNetworkId($itemStack->getId(), $itemStack->getMeta());
|
||||
if($itemStack->getBlockRuntimeId() !== 0){
|
||||
//blockitem meta is zeroed out by the client, so we have to infer it from the block runtime ID
|
||||
$blockFullId = RuntimeBlockMapping::getInstance()->fromRuntimeId($itemStack->getBlockRuntimeId());
|
||||
$meta = $blockFullId & Block::INTERNAL_METADATA_MASK;
|
||||
}
|
||||
|
||||
if($compound !== null){
|
||||
$compound = clone $compound;
|
||||
@ -222,12 +222,6 @@ class TypeConverter{
|
||||
$compound->removeTag(self::DAMAGE_TAG_CONFLICT_RESOLUTION);
|
||||
$compound->setTag(self::DAMAGE_TAG, $conflicted);
|
||||
}
|
||||
}elseif(($metaTag = $compound->getTag(self::PM_META_TAG)) instanceof IntTag){
|
||||
//TODO HACK: This foul-smelling code ensures that we can correctly deserialize an item when the
|
||||
//client sends it back to us, because as of 1.16.220, blockitems quietly discard their metadata
|
||||
//client-side. Aside from being very annoying, this also breaks various server-side behaviours.
|
||||
$meta = $metaTag->getValue();
|
||||
$compound->removeTag(self::PM_META_TAG);
|
||||
}
|
||||
if($compound->count() === 0){
|
||||
$compound = null;
|
||||
|
@ -284,12 +284,12 @@ class ItemStackRequestExecutor{
|
||||
}
|
||||
|
||||
$this->createdItemsTakenCount += $count;
|
||||
$createdItem = clone $createdItem;
|
||||
$createdItem->setCount($count);
|
||||
$takenItem = clone $createdItem;
|
||||
$takenItem->setCount($count);
|
||||
if(!$this->createdItemFromCreativeInventory && $this->createdItemsTakenCount >= $createdItem->getCount()){
|
||||
$this->setNextCreatedItem(null);
|
||||
}
|
||||
return $createdItem;
|
||||
return $takenItem;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,6 +44,7 @@ use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
|
||||
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\timings\Timings;
|
||||
use pocketmine\VersionInfo;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use function sprintf;
|
||||
@ -60,89 +61,94 @@ class PreSpawnPacketHandler extends PacketHandler{
|
||||
){}
|
||||
|
||||
public function setUp() : void{
|
||||
$location = $this->player->getLocation();
|
||||
$world = $location->getWorld();
|
||||
Timings::$playerNetworkSendPreSpawnGameData->startTiming();
|
||||
try{
|
||||
$location = $this->player->getLocation();
|
||||
$world = $location->getWorld();
|
||||
|
||||
$this->session->getLogger()->debug("Preparing StartGamePacket");
|
||||
$levelSettings = new LevelSettings();
|
||||
$levelSettings->seed = -1;
|
||||
$levelSettings->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
|
||||
$levelSettings->worldGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->server->getGamemode());
|
||||
$levelSettings->difficulty = $world->getDifficulty();
|
||||
$levelSettings->spawnPosition = BlockPosition::fromVector3($world->getSpawnLocation());
|
||||
$levelSettings->hasAchievementsDisabled = true;
|
||||
$levelSettings->time = $world->getTime();
|
||||
$levelSettings->eduEditionOffer = 0;
|
||||
$levelSettings->rainLevel = 0; //TODO: implement these properly
|
||||
$levelSettings->lightningLevel = 0;
|
||||
$levelSettings->commandsEnabled = true;
|
||||
$levelSettings->gameRules = [
|
||||
"naturalregeneration" => new BoolGameRule(false, false) //Hack for client side regeneration
|
||||
];
|
||||
$levelSettings->experiments = new Experiments([], false);
|
||||
$this->session->getLogger()->debug("Preparing StartGamePacket");
|
||||
$levelSettings = new LevelSettings();
|
||||
$levelSettings->seed = -1;
|
||||
$levelSettings->spawnSettings = new SpawnSettings(SpawnSettings::BIOME_TYPE_DEFAULT, "", DimensionIds::OVERWORLD); //TODO: implement this properly
|
||||
$levelSettings->worldGamemode = TypeConverter::getInstance()->coreGameModeToProtocol($this->server->getGamemode());
|
||||
$levelSettings->difficulty = $world->getDifficulty();
|
||||
$levelSettings->spawnPosition = BlockPosition::fromVector3($world->getSpawnLocation());
|
||||
$levelSettings->hasAchievementsDisabled = true;
|
||||
$levelSettings->time = $world->getTime();
|
||||
$levelSettings->eduEditionOffer = 0;
|
||||
$levelSettings->rainLevel = 0; //TODO: implement these properly
|
||||
$levelSettings->lightningLevel = 0;
|
||||
$levelSettings->commandsEnabled = true;
|
||||
$levelSettings->gameRules = [
|
||||
"naturalregeneration" => new BoolGameRule(false, false) //Hack for client side regeneration
|
||||
];
|
||||
$levelSettings->experiments = new Experiments([], false);
|
||||
|
||||
$this->session->sendDataPacket(StartGamePacket::create(
|
||||
$this->player->getId(),
|
||||
$this->player->getId(),
|
||||
TypeConverter::getInstance()->coreGameModeToProtocol($this->player->getGamemode()),
|
||||
$this->player->getOffsetPosition($location),
|
||||
$location->pitch,
|
||||
$location->yaw,
|
||||
new CacheableNbt(CompoundTag::create()), //TODO: we don't care about this right now
|
||||
$levelSettings,
|
||||
"",
|
||||
$this->server->getMotd(),
|
||||
"",
|
||||
false,
|
||||
new PlayerMovementSettings(PlayerMovementType::SERVER_AUTHORITATIVE_V1, 0, false),
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
true,
|
||||
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
|
||||
Uuid::fromString(Uuid::NIL),
|
||||
false,
|
||||
[],
|
||||
0,
|
||||
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),
|
||||
));
|
||||
$this->session->sendDataPacket(StartGamePacket::create(
|
||||
$this->player->getId(),
|
||||
$this->player->getId(),
|
||||
TypeConverter::getInstance()->coreGameModeToProtocol($this->player->getGamemode()),
|
||||
$this->player->getOffsetPosition($location),
|
||||
$location->pitch,
|
||||
$location->yaw,
|
||||
new CacheableNbt(CompoundTag::create()), //TODO: we don't care about this right now
|
||||
$levelSettings,
|
||||
"",
|
||||
$this->server->getMotd(),
|
||||
"",
|
||||
false,
|
||||
new PlayerMovementSettings(PlayerMovementType::SERVER_AUTHORITATIVE_V1, 0, false),
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
true,
|
||||
sprintf("%s %s", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true)),
|
||||
Uuid::fromString(Uuid::NIL),
|
||||
false,
|
||||
[],
|
||||
0,
|
||||
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),
|
||||
));
|
||||
|
||||
$this->session->getLogger()->debug("Sending actor identifiers");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers());
|
||||
$this->session->getLogger()->debug("Sending actor identifiers");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getAvailableActorIdentifiers());
|
||||
|
||||
$this->session->getLogger()->debug("Sending biome definitions");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs());
|
||||
$this->session->getLogger()->debug("Sending biome definitions");
|
||||
$this->session->sendDataPacket(StaticPacketCache::getInstance()->getBiomeDefs());
|
||||
|
||||
$this->session->getLogger()->debug("Sending attributes");
|
||||
$this->session->getEntityEventBroadcaster()->syncAttributes([$this->session], $this->player, $this->player->getAttributeMap()->getAll());
|
||||
$this->session->getLogger()->debug("Sending attributes");
|
||||
$this->session->getEntityEventBroadcaster()->syncAttributes([$this->session], $this->player, $this->player->getAttributeMap()->getAll());
|
||||
|
||||
$this->session->getLogger()->debug("Sending available commands");
|
||||
$this->session->syncAvailableCommands();
|
||||
$this->session->getLogger()->debug("Sending available commands");
|
||||
$this->session->syncAvailableCommands();
|
||||
|
||||
$this->session->getLogger()->debug("Sending abilities");
|
||||
$this->session->syncAbilities($this->player);
|
||||
$this->session->syncAdventureSettings();
|
||||
$this->session->getLogger()->debug("Sending abilities");
|
||||
$this->session->syncAbilities($this->player);
|
||||
$this->session->syncAdventureSettings();
|
||||
|
||||
$this->session->getLogger()->debug("Sending effects");
|
||||
foreach($this->player->getEffects()->all() as $effect){
|
||||
$this->session->getEntityEventBroadcaster()->onEntityEffectAdded([$this->session], $this->player, $effect, false);
|
||||
$this->session->getLogger()->debug("Sending effects");
|
||||
foreach($this->player->getEffects()->all() as $effect){
|
||||
$this->session->getEntityEventBroadcaster()->onEntityEffectAdded([$this->session], $this->player, $effect, false);
|
||||
}
|
||||
|
||||
$this->session->getLogger()->debug("Sending actor metadata");
|
||||
$this->player->sendData([$this->player]);
|
||||
|
||||
$this->session->getLogger()->debug("Sending inventory");
|
||||
$this->inventoryManager->syncAll();
|
||||
$this->inventoryManager->syncSelectedHotbarSlot();
|
||||
|
||||
$this->session->getLogger()->debug("Sending creative inventory data");
|
||||
$this->inventoryManager->syncCreative();
|
||||
|
||||
$this->session->getLogger()->debug("Sending crafting data");
|
||||
$this->session->sendDataPacket(CraftingDataCache::getInstance()->getCache($this->server->getCraftingManager()));
|
||||
|
||||
$this->session->getLogger()->debug("Sending player list");
|
||||
$this->session->syncPlayerList($this->server->getOnlinePlayers());
|
||||
}finally{
|
||||
Timings::$playerNetworkSendPreSpawnGameData->stopTiming();
|
||||
}
|
||||
|
||||
$this->session->getLogger()->debug("Sending actor metadata");
|
||||
$this->player->sendData([$this->player]);
|
||||
|
||||
$this->session->getLogger()->debug("Sending inventory");
|
||||
$this->inventoryManager->syncAll();
|
||||
$this->inventoryManager->syncSelectedHotbarSlot();
|
||||
|
||||
$this->session->getLogger()->debug("Sending creative inventory data");
|
||||
$this->inventoryManager->syncCreative();
|
||||
|
||||
$this->session->getLogger()->debug("Sending crafting data");
|
||||
$this->session->sendDataPacket(CraftingDataCache::getInstance()->getCache($this->server->getCraftingManager()));
|
||||
|
||||
$this->session->getLogger()->debug("Sending player list");
|
||||
$this->session->syncPlayerList($this->server->getOnlinePlayers());
|
||||
}
|
||||
|
||||
public function handleRequestChunkRadius(RequestChunkRadiusPacket $packet) : bool{
|
||||
|
@ -53,6 +53,7 @@ use function implode;
|
||||
use function mt_rand;
|
||||
use function random_bytes;
|
||||
use function rtrim;
|
||||
use function str_split;
|
||||
use function substr;
|
||||
use const PHP_INT_MAX;
|
||||
|
||||
@ -196,7 +197,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
||||
try{
|
||||
$session->handleEncoded($buf);
|
||||
}catch(PacketHandlingException $e){
|
||||
$errorId = bin2hex(random_bytes(6));
|
||||
$errorId = implode("-", str_split(bin2hex(random_bytes(6)), 4));
|
||||
|
||||
$logger = $session->getLogger();
|
||||
$logger->error("Bad packet (error ID $errorId): " . $e->getMessage());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
@ -883,6 +894,31 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param true[] $oldTickingChunks
|
||||
* @param true[] $newTickingChunks
|
||||
*
|
||||
* @phpstan-param array<int, true> $oldTickingChunks
|
||||
* @phpstan-param array<int, true> $newTickingChunks
|
||||
*/
|
||||
private function updateTickingChunkRegistrations(array $oldTickingChunks, array $newTickingChunks) : void{
|
||||
$world = $this->getWorld();
|
||||
foreach($oldTickingChunks as $hash => $_){
|
||||
if(!isset($newTickingChunks[$hash]) && !isset($this->loadQueue[$hash])){
|
||||
//we are (probably) still using this chunk, but it's no longer within ticking range
|
||||
World::getXZ($hash, $tickingChunkX, $tickingChunkZ);
|
||||
$world->unregisterTickingChunk($this->chunkTicker, $tickingChunkX, $tickingChunkZ);
|
||||
}
|
||||
}
|
||||
foreach($newTickingChunks as $hash => $_){
|
||||
if(!isset($oldTickingChunks[$hash]) && !isset($this->loadQueue[$hash])){
|
||||
//we were already using this chunk, but it is now within ticking range
|
||||
World::getXZ($hash, $tickingChunkX, $tickingChunkZ);
|
||||
$world->registerTickingChunk($this->chunkTicker, $tickingChunkX, $tickingChunkZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates which new chunks this player needs to use, and which currently-used chunks it needs to stop using.
|
||||
* This is based on factors including the player's current render radius and current position.
|
||||
@ -895,16 +931,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]);
|
||||
}
|
||||
|
||||
@ -914,8 +957,11 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
}
|
||||
|
||||
$this->loadQueue = $newOrder;
|
||||
|
||||
$this->updateTickingChunkRegistrations($this->tickingChunks, $tickingChunks);
|
||||
$this->tickingChunks = $tickingChunks;
|
||||
|
||||
if(count($this->loadQueue) > 0 || count($unloadChunks) > 0){
|
||||
$this->chunkLoader->setCurrentLocation($this->location);
|
||||
$this->getNetworkSession()->syncViewAreaCenterPoint($this->location, $this->viewDistance);
|
||||
}
|
||||
|
||||
|
@ -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){}
|
||||
|
||||
|
@ -651,7 +651,7 @@ class PluginManager{
|
||||
throw new PluginException("Plugin attempted to register event handler " . $handlerName . "() to event " . $event . " while not enabled");
|
||||
}
|
||||
|
||||
$timings = new TimingsHandler("Plugin: " . $plugin->getDescription()->getFullName() . " Event: " . $handlerName . "(" . (new \ReflectionClass($event))->getShortName() . ")");
|
||||
$timings = new TimingsHandler($handlerName . "(" . (new \ReflectionClass($event))->getShortName() . ")", group: $plugin->getDescription()->getFullName());
|
||||
|
||||
$registeredListener = new RegisteredListener($handler, $priority, $plugin, $handleCancelled, $timings);
|
||||
HandlerListManager::global()->getListFor($event)->register($registeredListener);
|
||||
|
@ -25,13 +25,22 @@ namespace pocketmine\timings;
|
||||
|
||||
use pocketmine\block\tile\Tile;
|
||||
use pocketmine\entity\Entity;
|
||||
use pocketmine\event\Event;
|
||||
use pocketmine\network\mcpe\protocol\ClientboundPacket;
|
||||
use pocketmine\network\mcpe\protocol\ServerboundPacket;
|
||||
use pocketmine\player\Player;
|
||||
use pocketmine\scheduler\TaskHandler;
|
||||
use function get_class;
|
||||
use function str_starts_with;
|
||||
|
||||
abstract class Timings{
|
||||
/**
|
||||
* @deprecated This was used by the old timings viewer to make a timer appear in the Breakdown section of a timings
|
||||
* report. Provide a group to your timer's constructor instead.
|
||||
* @see Timings::GROUP_BREAKDOWN
|
||||
*/
|
||||
public const INCLUDED_BY_OTHER_TIMINGS_PREFIX = "** ";
|
||||
public const GROUP_BREAKDOWN = "Minecraft - Breakdown";
|
||||
|
||||
private static bool $initialized = false;
|
||||
|
||||
@ -59,6 +68,7 @@ abstract class Timings{
|
||||
public static $playerNetworkSendEncrypt;
|
||||
|
||||
public static TimingsHandler $playerNetworkSendInventorySync;
|
||||
public static TimingsHandler $playerNetworkSendPreSpawnGameData;
|
||||
|
||||
/** @var TimingsHandler */
|
||||
public static $playerNetworkReceive;
|
||||
@ -154,6 +164,9 @@ abstract class Timings{
|
||||
|
||||
public static TimingsHandler $playerMove;
|
||||
|
||||
/** @var TimingsHandler[] */
|
||||
private static array $events = [];
|
||||
|
||||
public static function init() : void{
|
||||
if(self::$initialized){
|
||||
return;
|
||||
@ -161,8 +174,8 @@ abstract class Timings{
|
||||
self::$initialized = true;
|
||||
|
||||
self::$fullTick = new TimingsHandler("Full Server Tick");
|
||||
self::$serverTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Server Tick Update Cycle", self::$fullTick);
|
||||
self::$serverInterrupts = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Server Mid-Tick Processing", self::$fullTick);
|
||||
self::$serverTick = new TimingsHandler("Server Tick Update Cycle", self::$fullTick, group: self::GROUP_BREAKDOWN);
|
||||
self::$serverInterrupts = new TimingsHandler("Server Mid-Tick Processing", self::$fullTick, group: self::GROUP_BREAKDOWN);
|
||||
self::$memoryManager = new TimingsHandler("Memory Manager");
|
||||
self::$garbageCollector = new TimingsHandler("Garbage Collector", self::$memoryManager);
|
||||
self::$titleTick = new TimingsHandler("Console Title Tick");
|
||||
@ -170,21 +183,22 @@ abstract class Timings{
|
||||
self::$connection = new TimingsHandler("Connection Handler");
|
||||
|
||||
self::$playerNetworkSend = new TimingsHandler("Player Network Send", self::$connection);
|
||||
self::$playerNetworkSendCompress = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Compression", self::$playerNetworkSend);
|
||||
self::$playerNetworkSendCompressBroadcast = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress);
|
||||
self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress);
|
||||
self::$playerNetworkSendEncrypt = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Encryption", self::$playerNetworkSend);
|
||||
self::$playerNetworkSendInventorySync = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Inventory Sync", self::$playerNetworkSend);
|
||||
self::$playerNetworkSendCompress = new TimingsHandler("Player Network Send - Compression", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendCompressBroadcast = new TimingsHandler("Player Network Send - Compression (Broadcast)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendCompressSessionBuffer = new TimingsHandler("Player Network Send - Compression (Session Buffer)", self::$playerNetworkSendCompress, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendEncrypt = new TimingsHandler("Player Network Send - Encryption", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendInventorySync = new TimingsHandler("Player Network Send - Inventory Sync", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkSendPreSpawnGameData = new TimingsHandler("Player Network Send - Pre-Spawn Game Data", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$playerNetworkReceive = new TimingsHandler("Player Network Receive", self::$connection);
|
||||
self::$playerNetworkReceiveDecompress = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Receive - Decompression", self::$playerNetworkReceive);
|
||||
self::$playerNetworkReceiveDecrypt = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Receive - Decryption", self::$playerNetworkReceive);
|
||||
self::$playerNetworkReceiveDecompress = new TimingsHandler("Player Network Receive - Decompression", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
|
||||
self::$playerNetworkReceiveDecrypt = new TimingsHandler("Player Network Receive - Decryption", self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$broadcastPackets = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Broadcast Packets", self::$playerNetworkSend);
|
||||
self::$broadcastPackets = new TimingsHandler("Broadcast Packets", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$playerMove = new TimingsHandler("Player Movement");
|
||||
self::$playerChunkOrder = new TimingsHandler("Player Order Chunks");
|
||||
self::$playerChunkSend = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Network Send - Chunks", self::$playerNetworkSend);
|
||||
self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
self::$scheduler = new TimingsHandler("Scheduler");
|
||||
self::$serverCommand = new TimingsHandler("Server Command");
|
||||
self::$worldLoad = new TimingsHandler("World Load");
|
||||
@ -192,36 +206,36 @@ abstract class Timings{
|
||||
self::$population = new TimingsHandler("World Population");
|
||||
self::$generationCallback = new TimingsHandler("World Generation Callback");
|
||||
self::$permissibleCalculation = new TimingsHandler("Permissible Calculation");
|
||||
self::$permissibleCalculationDiff = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Permissible Calculation - Diff", self::$permissibleCalculation);
|
||||
self::$permissibleCalculationCallback = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Permissible Calculation - Callbacks", self::$permissibleCalculation);
|
||||
self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
|
||||
self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$syncPlayerDataLoad = new TimingsHandler("Player Data Load");
|
||||
self::$syncPlayerDataSave = new TimingsHandler("Player Data Save");
|
||||
|
||||
self::$entityMove = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Movement");
|
||||
self::$entityMoveCollision = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Movement - Collision Checks", self::$entityMove);
|
||||
self::$entityMove = new TimingsHandler("Entity Movement", group: self::GROUP_BREAKDOWN);
|
||||
self::$entityMoveCollision = new TimingsHandler("Entity Movement - Collision Checks", self::$entityMove, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$projectileMove = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Projectile Movement", self::$entityMove);
|
||||
self::$projectileMoveRayTrace = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Projectile Movement - Ray Tracing", self::$projectileMove);
|
||||
self::$projectileMove = new TimingsHandler("Projectile Movement", self::$entityMove, group: self::GROUP_BREAKDOWN);
|
||||
self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$playerCheckNearEntities = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "checkNearEntities");
|
||||
self::$tickEntity = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Tick");
|
||||
self::$tickTileEntity = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Block Entity Tick");
|
||||
self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities", group: self::GROUP_BREAKDOWN);
|
||||
self::$tickEntity = new TimingsHandler("Entity Tick", group: self::GROUP_BREAKDOWN);
|
||||
self::$tickTileEntity = new TimingsHandler("Block Entity Tick", group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$entityBaseTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Base Tick");
|
||||
self::$livingEntityBaseTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Base Tick - Living");
|
||||
self::$itemEntityBaseTick = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Base Tick - ItemEntity");
|
||||
self::$entityBaseTick = new TimingsHandler("Entity Base Tick", group: self::GROUP_BREAKDOWN);
|
||||
self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living", group: self::GROUP_BREAKDOWN);
|
||||
self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity", group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$schedulerSync = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Scheduler - Sync Tasks");
|
||||
self::$schedulerAsync = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Scheduler - Async Tasks");
|
||||
self::$schedulerSync = new TimingsHandler("Scheduler - Sync Tasks", group: self::GROUP_BREAKDOWN);
|
||||
self::$schedulerAsync = new TimingsHandler("Scheduler - Async Tasks", group: self::GROUP_BREAKDOWN);
|
||||
|
||||
self::$playerCommand = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Player Command");
|
||||
self::$craftingDataCacheRebuild = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Build CraftingDataPacket Cache");
|
||||
self::$playerCommand = new TimingsHandler("Player Command", group: self::GROUP_BREAKDOWN);
|
||||
self::$craftingDataCacheRebuild = new TimingsHandler("Build CraftingDataPacket Cache", group: self::GROUP_BREAKDOWN);
|
||||
|
||||
}
|
||||
|
||||
public static function getScheduledTaskTimings(TaskHandler $task, int $period) : TimingsHandler{
|
||||
$name = "Task: " . $task->getOwnerName() . " Runnable: " . $task->getTaskName();
|
||||
$name = "Task: " . $task->getTaskName();
|
||||
|
||||
if($period > 0){
|
||||
$name .= "(interval:" . $period . ")";
|
||||
@ -230,75 +244,96 @@ abstract class Timings{
|
||||
}
|
||||
|
||||
if(!isset(self::$pluginTaskTimingMap[$name])){
|
||||
self::$pluginTaskTimingMap[$name] = new TimingsHandler($name, self::$schedulerSync);
|
||||
self::$pluginTaskTimingMap[$name] = new TimingsHandler($name, self::$schedulerSync, $task->getOwnerName());
|
||||
}
|
||||
|
||||
return self::$pluginTaskTimingMap[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-template T of object
|
||||
* @phpstan-param class-string<T> $class
|
||||
*/
|
||||
private static function shortenCoreClassName(string $class, string $prefix) : string{
|
||||
if(str_starts_with($class, $prefix)){
|
||||
return (new \ReflectionClass($class))->getShortName();
|
||||
}
|
||||
return $class;
|
||||
}
|
||||
|
||||
public static function getEntityTimings(Entity $entity) : TimingsHandler{
|
||||
$reflect = new \ReflectionClass($entity);
|
||||
$entityType = $reflect->getShortName();
|
||||
if(!isset(self::$entityTypeTimingMap[$entityType])){
|
||||
//the timings viewer calculates average player count by looking at this timer, so we need to ensure it has
|
||||
//a name it can identify. However, we also want to make it obvious if this is a custom Player class.
|
||||
if($entity instanceof Player && $reflect->getName() !== Player::class){
|
||||
$entityType = "Player (" . $reflect->getName() . ")";
|
||||
if(!isset(self::$entityTypeTimingMap[$entity::class])){
|
||||
if($entity instanceof Player){
|
||||
//the timings viewer calculates average player count by looking at this timer, so we need to ensure it has
|
||||
//a name it can identify. However, we also want to make it obvious if this is a custom Player class.
|
||||
$displayName = $entity::class !== Player::class ? "Player (" . $entity::class . ")" : "Player";
|
||||
}else{
|
||||
$displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\");
|
||||
}
|
||||
self::$entityTypeTimingMap[$entityType] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Entity Tick - " . $entityType, self::$tickEntity);
|
||||
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, self::$tickEntity, group: self::GROUP_BREAKDOWN);
|
||||
}
|
||||
|
||||
return self::$entityTypeTimingMap[$entityType];
|
||||
return self::$entityTypeTimingMap[$entity::class];
|
||||
}
|
||||
|
||||
public static function getTileEntityTimings(Tile $tile) : TimingsHandler{
|
||||
$tileType = (new \ReflectionClass($tile))->getShortName();
|
||||
if(!isset(self::$tileEntityTypeTimingMap[$tileType])){
|
||||
self::$tileEntityTypeTimingMap[$tileType] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Block Entity Tick - " . $tileType, self::$tickTileEntity);
|
||||
if(!isset(self::$tileEntityTypeTimingMap[$tile::class])){
|
||||
self::$tileEntityTypeTimingMap[$tile::class] = new TimingsHandler(
|
||||
"Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\"),
|
||||
self::$tickTileEntity,
|
||||
group: self::GROUP_BREAKDOWN
|
||||
);
|
||||
}
|
||||
|
||||
return self::$tileEntityTypeTimingMap[$tileType];
|
||||
return self::$tileEntityTypeTimingMap[$tile::class];
|
||||
}
|
||||
|
||||
public static function getReceiveDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
|
||||
$pid = $pk->pid();
|
||||
if(!isset(self::$packetReceiveTimingMap[$pid])){
|
||||
self::$packetReceiveTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Receive - " . $pk->getName(), self::$playerNetworkReceive);
|
||||
if(!isset(self::$packetReceiveTimingMap[$pk::class])){
|
||||
self::$packetReceiveTimingMap[$pk::class] = new TimingsHandler("Receive - " . $pk->getName(), self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
|
||||
}
|
||||
|
||||
return self::$packetReceiveTimingMap[$pid];
|
||||
return self::$packetReceiveTimingMap[$pk::class];
|
||||
}
|
||||
|
||||
public static function getDecodeDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
|
||||
$pid = $pk->pid();
|
||||
return self::$packetDecodeTimingMap[$pid] ??= new TimingsHandler(
|
||||
self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Decode - " . $pk->getName(),
|
||||
self::getReceiveDataPacketTimings($pk)
|
||||
return self::$packetDecodeTimingMap[$pk::class] ??= new TimingsHandler(
|
||||
"Decode - " . $pk->getName(),
|
||||
self::getReceiveDataPacketTimings($pk),
|
||||
group: self::GROUP_BREAKDOWN
|
||||
);
|
||||
}
|
||||
|
||||
public static function getHandleDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
|
||||
$pid = $pk->pid();
|
||||
return self::$packetHandleTimingMap[$pid] ??= new TimingsHandler(
|
||||
self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Handler - " . $pk->getName(),
|
||||
self::getReceiveDataPacketTimings($pk)
|
||||
return self::$packetHandleTimingMap[$pk::class] ??= new TimingsHandler(
|
||||
"Handler - " . $pk->getName(),
|
||||
self::getReceiveDataPacketTimings($pk),
|
||||
group: self::GROUP_BREAKDOWN
|
||||
);
|
||||
}
|
||||
|
||||
public static function getEncodeDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
|
||||
$pid = $pk->pid();
|
||||
return self::$packetEncodeTimingMap[$pid] ??= new TimingsHandler(
|
||||
self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Encode - " . $pk->getName(),
|
||||
self::getSendDataPacketTimings($pk)
|
||||
return self::$packetEncodeTimingMap[$pk::class] ??= new TimingsHandler(
|
||||
"Encode - " . $pk->getName(),
|
||||
self::getSendDataPacketTimings($pk),
|
||||
group: self::GROUP_BREAKDOWN
|
||||
);
|
||||
}
|
||||
|
||||
public static function getSendDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
|
||||
$pid = $pk->pid();
|
||||
if(!isset(self::$packetSendTimingMap[$pid])){
|
||||
self::$packetSendTimingMap[$pid] = new TimingsHandler(self::INCLUDED_BY_OTHER_TIMINGS_PREFIX . "Send - " . $pk->getName(), self::$playerNetworkSend);
|
||||
if(!isset(self::$packetSendTimingMap[$pk::class])){
|
||||
self::$packetSendTimingMap[$pk::class] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||
}
|
||||
|
||||
return self::$packetSendTimingMap[$pid];
|
||||
return self::$packetSendTimingMap[$pk::class];
|
||||
}
|
||||
|
||||
public static function getEventTimings(Event $event) : TimingsHandler{
|
||||
$eventClass = get_class($event);
|
||||
if(!isset(self::$events[$eventClass])){
|
||||
self::$events[$eventClass] = new TimingsHandler(self::shortenCoreClassName($eventClass, "pocketmine\\event\\"), group: "Events");
|
||||
}
|
||||
|
||||
return self::$events[$eventClass];
|
||||
}
|
||||
}
|
||||
|
@ -25,16 +25,21 @@ namespace pocketmine\timings;
|
||||
|
||||
use pocketmine\entity\Living;
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\Utils;
|
||||
use function count;
|
||||
use function hrtime;
|
||||
use function implode;
|
||||
use function spl_object_id;
|
||||
|
||||
class TimingsHandler{
|
||||
private const FORMAT_VERSION = 1;
|
||||
|
||||
private static bool $enabled = false;
|
||||
private static int $timingStart = 0;
|
||||
|
||||
/** @return string[] */
|
||||
public static function printTimings() : array{
|
||||
$result = ["Minecraft"];
|
||||
$groups = [];
|
||||
|
||||
foreach(TimingsRecord::getAll() as $timings){
|
||||
$time = $timings->getTotalTime();
|
||||
@ -46,7 +51,27 @@ class TimingsHandler{
|
||||
|
||||
$avg = $time / $count;
|
||||
|
||||
$result[] = " " . $timings->getName() . " Time: $time Count: " . $count . " Avg: $avg Violations: " . $timings->getViolations();
|
||||
$group = $timings->getGroup();
|
||||
$groups[$group][] = implode(" ", [
|
||||
$timings->getName(),
|
||||
"Time: $time",
|
||||
"Count: $count",
|
||||
"Avg: $avg",
|
||||
"Violations: " . $timings->getViolations(),
|
||||
"RecordId: " . $timings->getId(),
|
||||
"ParentRecordId: " . ($timings->getParentId() ?? "none"),
|
||||
"TimerId: " . $timings->getTimerId(),
|
||||
"Ticks: " . $timings->getTicksActive(),
|
||||
"Peak: " . $timings->getPeakTime(),
|
||||
]);
|
||||
}
|
||||
$result = [];
|
||||
|
||||
foreach(Utils::stringifyKeys($groups) as $groupName => $lines){
|
||||
$result[] = $groupName;
|
||||
foreach($lines as $line){
|
||||
$result[] = " $line";
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = "# Version " . Server::getInstance()->getVersion();
|
||||
@ -65,6 +90,7 @@ class TimingsHandler{
|
||||
|
||||
$result[] = "# Entities " . $entities;
|
||||
$result[] = "# LivingEntities " . $livingEntities;
|
||||
$result[] = "# FormatVersion " . self::FORMAT_VERSION;
|
||||
|
||||
$sampleTime = hrtime(true) - self::$timingStart;
|
||||
$result[] = "Sample time $sampleTime (" . ($sampleTime / 1000000000) . "s)";
|
||||
@ -97,16 +123,25 @@ class TimingsHandler{
|
||||
}
|
||||
}
|
||||
|
||||
private ?TimingsRecord $record = null;
|
||||
private ?TimingsRecord $rootRecord = null;
|
||||
private int $timingDepth = 0;
|
||||
|
||||
/**
|
||||
* @var TimingsRecord[]
|
||||
* @phpstan-var array<int, TimingsRecord>
|
||||
*/
|
||||
private array $recordsByParent = [];
|
||||
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private ?TimingsHandler $parent = null
|
||||
private ?TimingsHandler $parent = null,
|
||||
private string $group = "Minecraft"
|
||||
){}
|
||||
|
||||
public function getName() : string{ return $this->name; }
|
||||
|
||||
public function getGroup() : string{ return $this->group; }
|
||||
|
||||
public function startTiming() : void{
|
||||
if(self::$enabled){
|
||||
$this->internalStartTiming(hrtime(true));
|
||||
@ -115,13 +150,24 @@ class TimingsHandler{
|
||||
|
||||
private function internalStartTiming(int $now) : void{
|
||||
if(++$this->timingDepth === 1){
|
||||
if($this->record === null){
|
||||
$this->record = new TimingsRecord($this);
|
||||
}
|
||||
$this->record->startTiming($now);
|
||||
if($this->parent !== null){
|
||||
$this->parent->internalStartTiming($now);
|
||||
}
|
||||
|
||||
$current = TimingsRecord::getCurrentRecord();
|
||||
if($current !== null){
|
||||
$record = $this->recordsByParent[spl_object_id($current)] ?? null;
|
||||
if($record === null){
|
||||
$record = new TimingsRecord($this, $current);
|
||||
$this->recordsByParent[spl_object_id($current)] = $record;
|
||||
}
|
||||
}else{
|
||||
if($this->rootRecord === null){
|
||||
$this->rootRecord = new TimingsRecord($this, null);
|
||||
}
|
||||
$record = $this->rootRecord;
|
||||
}
|
||||
$record->startTiming($now);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,10 +188,13 @@ class TimingsHandler{
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->record !== null){
|
||||
//this might be null if a timings reset occurred while the timer was running
|
||||
$this->record->stopTiming($now);
|
||||
$record = TimingsRecord::getCurrentRecord();
|
||||
$timerId = spl_object_id($this);
|
||||
for(; $record !== null && $record->getTimerId() !== $timerId; $record = TimingsRecord::getCurrentRecord()){
|
||||
\GlobalLogger::get()->error("Timer \"" . $record->getName() . "\" should have been stopped before stopping timer \"" . $this->name . "\"");
|
||||
$record->stopTiming($now);
|
||||
}
|
||||
$record?->stopTiming($now);
|
||||
if($this->parent !== null){
|
||||
$this->parent->internalStopTiming($now);
|
||||
}
|
||||
@ -171,6 +220,7 @@ class TimingsHandler{
|
||||
* @internal
|
||||
*/
|
||||
public function destroyCycles() : void{
|
||||
$this->record = null;
|
||||
$this->rootRecord = null;
|
||||
$this->recordsByParent = [];
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\timings;
|
||||
|
||||
use pocketmine\Server;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use function floor;
|
||||
use function spl_object_id;
|
||||
|
||||
@ -39,11 +40,14 @@ final class TimingsRecord{
|
||||
*/
|
||||
private static array $records = [];
|
||||
|
||||
private static ?self $currentRecord = null;
|
||||
|
||||
public static function clearRecords() : void{
|
||||
foreach(self::$records as $record){
|
||||
$record->handler->destroyCycles();
|
||||
}
|
||||
self::$records = [];
|
||||
self::$currentRecord = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,11 +59,17 @@ final class TimingsRecord{
|
||||
public static function tick(bool $measure = true) : void{
|
||||
if($measure){
|
||||
foreach(self::$records as $record){
|
||||
if($record->curTickTotal > Server::TARGET_NANOSECONDS_PER_TICK){
|
||||
$record->violations += (int) floor($record->curTickTotal / Server::TARGET_NANOSECONDS_PER_TICK);
|
||||
if($record->curCount > 0){
|
||||
if($record->curTickTotal > Server::TARGET_NANOSECONDS_PER_TICK){
|
||||
$record->violations += (int) floor($record->curTickTotal / Server::TARGET_NANOSECONDS_PER_TICK);
|
||||
}
|
||||
if($record->curTickTotal > $record->peakTime){
|
||||
$record->peakTime = $record->curTickTotal;
|
||||
}
|
||||
$record->curTickTotal = 0;
|
||||
$record->curCount = 0;
|
||||
$record->ticksActive++;
|
||||
}
|
||||
$record->curTickTotal = 0;
|
||||
$record->curCount = 0;
|
||||
}
|
||||
}else{
|
||||
foreach(self::$records as $record){
|
||||
@ -78,16 +88,27 @@ final class TimingsRecord{
|
||||
private int $totalTime = 0;
|
||||
private int $curTickTotal = 0;
|
||||
private int $violations = 0;
|
||||
private int $ticksActive = 0;
|
||||
private int $peakTime = 0;
|
||||
|
||||
public function __construct(
|
||||
//I'm not the biggest fan of this cycle, but it seems to be the most effective way to avoid leaking anything.
|
||||
private TimingsHandler $handler
|
||||
private TimingsHandler $handler,
|
||||
private ?TimingsRecord $parentRecord
|
||||
){
|
||||
self::$records[spl_object_id($this)] = $this;
|
||||
}
|
||||
|
||||
public function getId() : int{ return spl_object_id($this); }
|
||||
|
||||
public function getParentId() : ?int{ return $this->parentRecord?->getId(); }
|
||||
|
||||
public function getTimerId() : int{ return spl_object_id($this->handler); }
|
||||
|
||||
public function getName() : string{ return $this->handler->getName(); }
|
||||
|
||||
public function getGroup() : string{ return $this->handler->getGroup(); }
|
||||
|
||||
public function getCount() : int{ return $this->count; }
|
||||
|
||||
public function getCurCount() : int{ return $this->curCount; }
|
||||
@ -100,14 +121,28 @@ final class TimingsRecord{
|
||||
|
||||
public function getViolations() : int{ return $this->violations; }
|
||||
|
||||
public function getTicksActive() : int{ return $this->ticksActive; }
|
||||
|
||||
public function getPeakTime() : float{ return $this->peakTime; }
|
||||
|
||||
public function startTiming(int $now) : void{
|
||||
$this->start = $now;
|
||||
self::$currentRecord = $this;
|
||||
}
|
||||
|
||||
public function stopTiming(int $now) : void{
|
||||
if($this->start == 0){
|
||||
return;
|
||||
}
|
||||
if(self::$currentRecord !== $this){
|
||||
if(self::$currentRecord === null){
|
||||
//timings may have been stopped while this timer was running
|
||||
return;
|
||||
}
|
||||
|
||||
throw new AssumptionFailedError("stopTiming() called on a non-current timer");
|
||||
}
|
||||
self::$currentRecord = $this->parentRecord;
|
||||
$diff = $now - $this->start;
|
||||
$this->totalTime += $diff;
|
||||
$this->curTickTotal += $diff;
|
||||
@ -115,4 +150,8 @@ final class TimingsRecord{
|
||||
++$this->count;
|
||||
$this->start = 0;
|
||||
}
|
||||
|
||||
public static function getCurrentRecord() : ?self{
|
||||
return self::$currentRecord;
|
||||
}
|
||||
}
|
||||
|
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).
|
||||
* 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{
|
||||
|
||||
|
@ -210,13 +210,24 @@ class World implements ChunkManager{
|
||||
/**
|
||||
* @var TickingChunkLoader[] spl_object_id => TickingChunkLoader
|
||||
* @phpstan-var array<int, TickingChunkLoader>
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
private array $tickingLoaders = [];
|
||||
/**
|
||||
* @var int[] spl_object_id => number of chunks
|
||||
* @phpstan-var array<int, int>
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
private array $tickingLoaderCounter = [];
|
||||
|
||||
/**
|
||||
* @var TickingChunkEntry[] chunkHash => TickingChunkEntry
|
||||
* @phpstan-var array<ChunkPosHash, TickingChunkEntry>
|
||||
*/
|
||||
private array $tickingChunks = [];
|
||||
|
||||
/**
|
||||
* @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader]
|
||||
* @phpstan-var array<ChunkPosHash, array<int, ChunkLoader>>
|
||||
@ -496,7 +507,7 @@ class World implements ChunkManager{
|
||||
$this->time = $this->provider->getWorldData()->getTime();
|
||||
|
||||
$cfg = $this->server->getConfigGroup();
|
||||
$this->chunkTickRadius = min($this->server->getViewDistance(), max(1, $cfg->getPropertyInt("chunk-ticking.tick-radius", 4)));
|
||||
$this->chunkTickRadius = min($this->server->getViewDistance(), max(0, $cfg->getPropertyInt("chunk-ticking.tick-radius", 4)));
|
||||
if($cfg->getPropertyInt("chunk-ticking.per-tick", 40) <= 0){
|
||||
//TODO: this needs l10n
|
||||
$this->logger->warning("\"chunk-ticking.per-tick\" setting is deprecated, but you've used it to disable chunk ticking. Set \"chunk-ticking.tick-radius\" to 0 in \"pocketmine.yml\" instead.");
|
||||
@ -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<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 = [];
|
||||
|
||||
$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");
|
||||
|
@ -286,7 +286,7 @@ class WorldManager{
|
||||
$centerX = $spawnLocation->getFloorX() >> Chunk::COORD_BIT_SIZE;
|
||||
$centerZ = $spawnLocation->getFloorZ() >> Chunk::COORD_BIT_SIZE;
|
||||
|
||||
$selected = iterator_to_array((new ChunkSelector())->selectChunks(8, $centerX, $centerZ));
|
||||
$selected = iterator_to_array((new ChunkSelector())->selectChunks(8, $centerX, $centerZ), preserve_keys: false);
|
||||
$done = 0;
|
||||
$total = count($selected);
|
||||
foreach($selected as $index){
|
||||
|
@ -53,27 +53,27 @@ class WorldTimings{
|
||||
public function __construct(World $world){
|
||||
$name = $world->getFolderName() . " - ";
|
||||
|
||||
$this->setBlock = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "setBlock");
|
||||
$this->doBlockLightUpdates = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Block Light Updates");
|
||||
$this->doBlockSkyLightUpdates = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Sky Light Updates");
|
||||
$this->setBlock = new TimingsHandler($name . "setBlock", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->doBlockLightUpdates = new TimingsHandler($name . "Block Light Updates", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->doBlockSkyLightUpdates = new TimingsHandler($name . "Sky Light Updates", group: Timings::GROUP_BREAKDOWN);
|
||||
|
||||
$this->doChunkUnload = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Unload Chunks");
|
||||
$this->scheduledBlockUpdates = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Scheduled Block Updates");
|
||||
$this->randomChunkUpdates = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Random Chunk Updates");
|
||||
$this->randomChunkUpdatesChunkSelection = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Random Chunk Updates - Chunk Selection");
|
||||
$this->doChunkGC = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Garbage Collection");
|
||||
$this->entityTick = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Tick Entities");
|
||||
$this->doChunkUnload = new TimingsHandler($name . "Unload Chunks", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->scheduledBlockUpdates = new TimingsHandler($name . "Scheduled Block Updates", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->randomChunkUpdates = new TimingsHandler($name . "Random Chunk Updates", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->randomChunkUpdatesChunkSelection = new TimingsHandler($name . "Random Chunk Updates - Chunk Selection", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->doChunkGC = new TimingsHandler($name . "Garbage Collection", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->entityTick = new TimingsHandler($name . "Tick Entities", group: Timings::GROUP_BREAKDOWN);
|
||||
|
||||
Timings::init(); //make sure the timers we want are available
|
||||
$this->syncChunkSend = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Player Send Chunks", Timings::$playerChunkSend);
|
||||
$this->syncChunkSendPrepare = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Player Send Chunk Prepare", Timings::$playerChunkSend);
|
||||
$this->syncChunkSend = new TimingsHandler($name . "Player Send Chunks", Timings::$playerChunkSend, group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkSendPrepare = new TimingsHandler($name . "Player Send Chunk Prepare", Timings::$playerChunkSend, group: Timings::GROUP_BREAKDOWN);
|
||||
|
||||
$this->syncChunkLoad = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load", Timings::$worldLoad);
|
||||
$this->syncChunkLoadData = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - Data");
|
||||
$this->syncChunkLoadFixInvalidBlocks = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - Fix Invalid Blocks");
|
||||
$this->syncChunkLoadEntities = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - Entities");
|
||||
$this->syncChunkLoadTileEntities = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Load - TileEntities");
|
||||
$this->syncChunkSave = new TimingsHandler(Timings::INCLUDED_BY_OTHER_TIMINGS_PREFIX . $name . "Chunk Save", Timings::$worldSave);
|
||||
$this->syncChunkLoad = new TimingsHandler($name . "Chunk Load", Timings::$worldLoad, group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkLoadData = new TimingsHandler($name . "Chunk Load - Data", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkLoadFixInvalidBlocks = new TimingsHandler($name . "Chunk Load - Fix Invalid Blocks", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkLoadEntities = new TimingsHandler($name . "Chunk Load - Entities", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkLoadTileEntities = new TimingsHandler($name . "Chunk Load - TileEntities", group: Timings::GROUP_BREAKDOWN);
|
||||
$this->syncChunkSave = new TimingsHandler($name . "Chunk Save", Timings::$worldSave, group: Timings::GROUP_BREAKDOWN);
|
||||
|
||||
$this->doTick = new TimingsHandler($name . "World Tick");
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
$this->waitingTests = [];
|
||||
$this->waitingTests = [
|
||||
new EventHandlerInheritanceTest($this),
|
||||
];
|
||||
}
|
||||
|
||||
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{
|
||||
|
||||
}
|
@ -24,12 +24,10 @@ declare(strict_types=1);
|
||||
namespace pocketmine\tools\simulate_chunk_selector;
|
||||
|
||||
use pocketmine\player\ChunkSelector;
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\utils\Utils;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\World;
|
||||
use Symfony\Component\Filesystem\Path;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function dirname;
|
||||
use function fwrite;
|
||||
@ -128,7 +126,12 @@ if(count(getopt("", ["help"])) !== 0){
|
||||
exit(0);
|
||||
}
|
||||
|
||||
foreach(Utils::stringifyKeys(getopt("", ["radius:", "baseX:", "baseZ:", "scale:", "chunksPerStep:"])) as $name => $value){
|
||||
$opts = getopt("", ["radius:", "baseX:", "baseZ:", "scale:", "chunksPerStep:", "output:"]);
|
||||
foreach(["radius", "baseX", "baseZ", "scale", "chunksPerStep"] as $name){
|
||||
$value = $opts[$name] ?? null;
|
||||
if($value === null){
|
||||
continue;
|
||||
}
|
||||
if(!is_string($value) || (string) ((int) $value) !== $value){
|
||||
fwrite(STDERR, "Value for --$name must be an integer\n");
|
||||
exit(1);
|
||||
@ -139,8 +142,7 @@ foreach(Utils::stringifyKeys(getopt("", ["radius:", "baseX:", "baseZ:", "scale:"
|
||||
"baseX" => ($baseX = $value),
|
||||
"baseZ" => ($baseZ = $value),
|
||||
"scale" => ($scale = $value),
|
||||
"chunksPerStep" => ($nChunksPerStep = $value),
|
||||
default => throw new AssumptionFailedError("getopt() returned unknown option")
|
||||
"chunksPerStep" => ($nChunksPerStep = $value)
|
||||
};
|
||||
}
|
||||
if($radius === null){
|
||||
@ -149,10 +151,10 @@ if($radius === null){
|
||||
}
|
||||
|
||||
$outputDirectory = null;
|
||||
foreach(Utils::stringifyKeys(getopt("", ["output:"])) as $name => $value){
|
||||
assert($name === "output");
|
||||
if(isset($opts["output"])){
|
||||
$value = $opts["output"];
|
||||
if(!is_string($value)){
|
||||
fwrite(STDERR, "Value for --$name must be a string\n");
|
||||
fwrite(STDERR, "Value for --output be a string\n");
|
||||
exit(1);
|
||||
}
|
||||
if(!@mkdir($value) && !is_dir($value)){
|
||||
|
Reference in New Issue
Block a user