Compare commits

..

29 Commits

Author SHA1 Message Date
11e34b3e5c Release 4.19.3 2023-04-21 15:53:02 +01:00
f9318bf286 TimingsHandler: stop throwing exceptions when timers aren't stopped in the right order
this is usually because of an uncaught exception interacting with a try...finally block.
This will normally result in a crash anyway, and we don't want to obscure the real error.
2023-04-21 15:38:11 +01:00
674b65f789 Item: optimise serializeCompoundTag() a little 2023-04-18 16:18:34 +01:00
a77fc8109f TypeConverter: avoid repeated calls to getId() and getMeta() 2023-04-18 15:02:52 +01:00
6102740ee3 TypeConverter: use a less slow hack to restore meta values on items sent by the client
this isn't even really needed anymore, since we don't decode items from the client since 4.18.

However, this may still be useful for tools.
2023-04-18 15:00:34 +01:00
40168a457e TypeConverter: fixed coreItemStackToNet() causing item NBT to be prepared twice
hasNamedTag() calls getNamedTag(), which calls serializeCompoundTag(), which writes the item's properties into the given NBT tag.
2023-04-18 14:43:25 +01:00
d07acd0013 RakLibInterface: split error ID into 4-character chunks
this makes it easier to read, since the error ID can't be copy-pasted from the disconnection screen on the client.
2023-04-17 14:05:46 +01:00
9561ae5af7 Entity: micro optimisation for checkBlockIntersections() 2023-04-16 17:57:51 +01:00
56fbd45dd5 Entity: avoid double-checking block intersections for moving entities
fixes #1824
2023-04-16 17:38:26 +01:00
b5dc72b0ee tools/simulate-chunk-selector: fixed the script being completely broken
getopt() behaviour is really, really dumb
2023-04-16 16:47:17 +01:00
4ba57f2b03 RegisteredListener: use try...finally to stop timings
While event handlers should not throw exceptions, we need to make sure the timings get stopped in the correct order, because the parent Event timer will be stopped due to using a finally block.
If this happens while the handler timing is still running, a second exception will occur, obscuring the real error.
2023-04-16 00:40:43 +01:00
df0d72bf61 4.19.3 is next 2023-04-14 18:43:44 +01:00
a534ac759a Release 4.19.2 2023-04-14 18:43:41 +01:00
5ab954b7a0 Update composer dependencies 2023-04-14 18:31:57 +01:00
6c6f686f8e Timings: be more intelligent about shortening timer names
non-pocketmine classes may reuse the names of pocketmine core classes. We don't want timers to get erroneously reused in this case.
2023-04-14 18:24:53 +01:00
bf7975da57 Timings: use class to index packet timings, not IDs
on multi version servers, the same packet may have different IDs, or different packets might use the same ID. In these cases, we don't want the timings to get split up or erroneously reused.
2023-04-14 18:22:35 +01:00
7aeedd8220 Timings: fixed every player getting its own timings
we need a more consistent way to deal with this
2023-04-14 17:32:56 +01:00
3dd1ce2d02 4.19.2 is next 2023-04-14 16:47:46 +01:00
5a29d07021 Release 4.19.1 2023-04-14 16:47:43 +01:00
692e1253c6 Fixed AABB height for farmland and grass paths (fixed in 1.19.50) 2023-04-14 16:02:28 +01:00
ab0c444823 Player: fixed ticking chunks not being re-registered if they never left the view distance
closes #5699
2023-04-14 16:02:28 +01:00
e48a4aaa55 World: fixed chunk ticking not being disabled by setting chunk ticking radius to 0
I can't believe I missed this ...
2023-04-14 16:02:28 +01:00
6fc4ce0f86 ÂTimingsCommand: include HTTP response code in debug message
we should probably show this in the regular response message, but we need new translations for that.
2023-04-14 16:02:27 +01:00
10243c7b2c Bump phpstan/phpstan from 1.10.11 to 1.10.13 (#5697)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.11 to 1.10.13.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.11...1.10.13)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-13 13:19:24 +01:00
18b528f72d ItemStackRequestExecutor: fixed borked taking of created items
closes #5695
2023-04-12 21:01:46 +01:00
7e92da126d DelegateInventory: fixed slots being synced twice and breaking ItemStackRequests
the second time the slot is synced, there is no prediction, so the slot update isn't associated with a request anymore. This causes subsequent requests in the same packet to fail, since the dependency request ID isn't associated with the slot anymore.

This change fixes the problem by only allowing the backing inventory to trigger a call to DelegateInventory->on*Change(). While we could have removed and re-added the listener instead, this way is safer since it doesn't assume that the backing inventory won't modify the given item in setItem().

closes #5692
2023-04-12 15:43:51 +01:00
ba62e0f9cb WorldManager: fixed borked pre-generation for new worlds' spawn terrain
perhaps directly altering the behaviour of selectChunks() wasn't a good
idea? ...
2023-04-11 23:55:53 +01:00
858d4a2ed2 changelog: fixed indentation
I have no idea what happened here...
2023-04-11 23:35:14 +01:00
87d8c1ea11 4.19.1 is next 2023-04-11 22:48:05 +01:00
20 changed files with 217 additions and 123 deletions

View File

@ -54,13 +54,13 @@ Released 11th April 2023.
### `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`)
- `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
- `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
- `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`.
- `TimingsHandler->__construct()` now accepts an additional, optional `string $group` parameter, which defaults to `Minecraft`.
### `pocketmine\world`
#### Highlights
@ -75,3 +75,37 @@ It works similarly to the `ChunkLoader` system, in that chunks will be ticked as
- 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.

View File

@ -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
View File

@ -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",

View File

@ -31,7 +31,7 @@ use function str_repeat;
final class VersionInfo{
public const NAME = "PocketMine-MP";
public const BASE_VERSION = "4.19.0";
public const BASE_VERSION = "4.19.3";
public const IS_DEVELOPMENT_BUILD = false;
public const BUILD_CHANNEL = "stable";

View File

@ -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{

View File

@ -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{

View File

@ -167,7 +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->getBody());
$sender->getServer()->getLogger()->debug("Invalid response from timings server (" . $result->getCode() . "): " . $result->getBody());
Command::broadcastCommandMessage($sender, KnownTranslationFactory::pocketmine_command_timings_pasteError());
}
}

View File

@ -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));
}
}
}

View File

@ -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{

View File

@ -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);
}
}
}

View File

@ -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){

View File

@ -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;

View File

@ -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;
}
/**

View File

@ -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());

View File

@ -894,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.
@ -930,15 +955,10 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
World::getXZ($index, $X, $Z);
$this->unloadChunk($X, $Z);
}
foreach($this->tickingChunks as $hash => $_){
//any chunks we encounter here are still used by the player, but may no longer be within ticking range
if(!isset($tickingChunks[$hash]) && !isset($newOrder[$hash])){
World::getXZ($hash, $tickingChunkX, $tickingChunkZ);
$world->unregisterTickingChunk($this->chunkTicker, $tickingChunkX, $tickingChunkZ);
}
}
$this->loadQueue = $newOrder;
$this->updateTickingChunkRegistrations($this->tickingChunks, $tickingChunks);
$this->tickingChunks = $tickingChunks;
if(count($this->loadQueue) > 0 || count($unloadChunks) > 0){

View File

@ -250,42 +250,54 @@ abstract class Timings{
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("Entity Tick - " . $entityType, self::$tickEntity, group: self::GROUP_BREAKDOWN);
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("Block Entity Tick - " . $tileType, self::$tickTileEntity, group: self::GROUP_BREAKDOWN);
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("Receive - " . $pk->getName(), self::$playerNetworkReceive, group: self::GROUP_BREAKDOWN);
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(
return self::$packetDecodeTimingMap[$pk::class] ??= new TimingsHandler(
"Decode - " . $pk->getName(),
self::getReceiveDataPacketTimings($pk),
group: self::GROUP_BREAKDOWN
@ -293,8 +305,7 @@ abstract class Timings{
}
public static function getHandleDataPacketTimings(ServerboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
return self::$packetHandleTimingMap[$pid] ??= new TimingsHandler(
return self::$packetHandleTimingMap[$pk::class] ??= new TimingsHandler(
"Handler - " . $pk->getName(),
self::getReceiveDataPacketTimings($pk),
group: self::GROUP_BREAKDOWN
@ -302,8 +313,7 @@ abstract class Timings{
}
public static function getEncodeDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
return self::$packetEncodeTimingMap[$pid] ??= new TimingsHandler(
return self::$packetEncodeTimingMap[$pk::class] ??= new TimingsHandler(
"Encode - " . $pk->getName(),
self::getSendDataPacketTimings($pk),
group: self::GROUP_BREAKDOWN
@ -311,23 +321,17 @@ abstract class Timings{
}
public static function getSendDataPacketTimings(ClientboundPacket $pk) : TimingsHandler{
$pid = $pk->pid();
if(!isset(self::$packetSendTimingMap[$pid])){
self::$packetSendTimingMap[$pid] = new TimingsHandler("Send - " . $pk->getName(), self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
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])){
if(str_starts_with($eventClass, "pocketmine\\event\\")){
$name = (new \ReflectionClass($event))->getShortName();
}else{
$name = $eventClass;
}
self::$events[$eventClass] = new TimingsHandler($name, group: "Events");
self::$events[$eventClass] = new TimingsHandler(self::shortenCoreClassName($eventClass, "pocketmine\\event\\"), group: "Events");
}
return self::$events[$eventClass];

View File

@ -189,12 +189,12 @@ class TimingsHandler{
}
$record = TimingsRecord::getCurrentRecord();
if($record !== null){
if($record->getTimerId() !== spl_object_id($this)){
throw new \LogicException("Timer \"" . $record->getName() . "\" should have been stopped before stopping timer \"" . $this->name . "\"");
}
$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);
}

View File

@ -507,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.");

View File

@ -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){

View File

@ -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)){