From a6e79c50042d28ac64a4094b67fcc6a9fd9faff6 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 11 Apr 2023 23:45:01 +0100 Subject: [PATCH 01/23] TimingsHandler: remove useless paste metadata these fields are not used by any version of timings, so this code is redundant. --- src/timings/TimingsHandler.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/timings/TimingsHandler.php b/src/timings/TimingsHandler.php index b77610a65..d43b432bc 100644 --- a/src/timings/TimingsHandler.php +++ b/src/timings/TimingsHandler.php @@ -23,10 +23,8 @@ declare(strict_types=1); 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; @@ -77,19 +75,6 @@ class TimingsHandler{ $result[] = "# Version " . Server::getInstance()->getVersion(); $result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion(); - $entities = 0; - $livingEntities = 0; - foreach(Server::getInstance()->getWorldManager()->getWorlds() as $world){ - $entities += count($world->getEntities()); - foreach($world->getEntities() as $e){ - if($e instanceof Living){ - ++$livingEntities; - } - } - } - - $result[] = "# Entities " . $entities; - $result[] = "# LivingEntities " . $livingEntities; $result[] = "# FormatVersion " . self::FORMAT_VERSION; $sampleTime = hrtime(true) - self::$timingStart; From 03363940988a4662a73ccd42628430471c613073 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 12 Apr 2023 16:44:10 +0100 Subject: [PATCH 02/23] Human: remove useless NameTag tag this is not written anywhere, so this code never does anything. --- src/entity/Human.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/entity/Human.php b/src/entity/Human.php index cdbd70bd7..707c816f8 100644 --- a/src/entity/Human.php +++ b/src/entity/Human.php @@ -92,7 +92,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ private const TAG_XP_PROGRESS = "XpP"; //TAG_Float private const TAG_LIFETIME_XP_TOTAL = "XpTotal"; //TAG_Int private const TAG_XP_SEED = "XpSeed"; //TAG_Int - private const TAG_NAME_TAG = "NameTag"; //TAG_String private const TAG_SKIN = "Skin"; //TAG_Compound private const TAG_SKIN_NAME = "Name"; //TAG_String private const TAG_SKIN_DATA = "Data"; //TAG_ByteArray @@ -245,10 +244,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{ * For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT. */ protected function initHumanData(CompoundTag $nbt) : void{ - if(($nameTagTag = $nbt->getTag(self::TAG_NAME_TAG)) instanceof StringTag){ - $this->setNameTag($nameTagTag->getValue()); - } - //TODO: use of NIL UUID for namespace is a hack; we should provide a proper UUID for the namespace $this->uuid = Uuid::uuid3(Uuid::NIL, ((string) $this->getId()) . $this->skin->getSkinData() . $this->getNameTag()); } From eb130f2906321590ef2f02ac1a030aba68291399 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 26 Apr 2023 16:03:33 +0100 Subject: [PATCH 03/23] Move primary version to PHP 8.1 8.0 is still supported for now, but won't be updated any longer. --- .github/workflows/discord-release-notify.yml | 2 +- .github/workflows/draft-release.yml | 2 +- .github/workflows/main.yml | 2 +- build/php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index deb8e9ff0..bc3658fd1 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -15,7 +15,7 @@ jobs: - name: Setup PHP and tools uses: shivammathur/setup-php@2.24.0 with: - php-version: 8.0 + php-version: 8.1 - name: Restore Composer package cache uses: actions/cache@v3 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index cce750118..604a69bba 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -20,7 +20,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@2.24.0 with: - php-version: 8.0 + php-version: 8.1 - name: Restore Composer package cache uses: actions/cache@v3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c347b8436..d2d0ed1a0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -205,7 +205,7 @@ jobs: - name: Setup PHP and tools uses: shivammathur/setup-php@2.24.0 with: - php-version: 8.0 + php-version: 8.1 tools: php-cs-fixer:3.11 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/build/php b/build/php index 9d8807be8..a3c40579a 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 9d8807be825b3fafd420534f2c29351c1bda6398 +Subproject commit a3c40579ad91246b07053fc2c8f085efd442943a From 8102616ff469a44a488bafc11cce20a6b3125372 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 26 Apr 2023 17:05:31 +0100 Subject: [PATCH 04/23] Added ticking chunk count to /status closes #5716 --- src/command/defaults/StatusCommand.php | 3 ++- src/world/World.php | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/command/defaults/StatusCommand.php b/src/command/defaults/StatusCommand.php index 7c2884993..1d7545f0b 100644 --- a/src/command/defaults/StatusCommand.php +++ b/src/command/defaults/StatusCommand.php @@ -110,7 +110,8 @@ class StatusCommand extends VanillaCommand{ $worldName = $world->getFolderName() !== $world->getDisplayName() ? " (" . $world->getDisplayName() . ")" : ""; $timeColor = $world->getTickRateTime() > 40 ? TextFormat::RED : TextFormat::YELLOW; $sender->sendMessage(TextFormat::GOLD . "World \"{$world->getFolderName()}\"$worldName: " . - TextFormat::RED . number_format(count($world->getLoadedChunks())) . TextFormat::GREEN . " chunks, " . + TextFormat::RED . number_format(count($world->getLoadedChunks())) . TextFormat::GREEN . " loaded chunks, " . + TextFormat::RED . number_format(count($world->getTickingChunks())) . TextFormat::GREEN . " ticking chunks, " . TextFormat::RED . number_format(count($world->getEntities())) . TextFormat::GREEN . " entities. " . "Time $timeColor" . round($world->getTickRateTime(), 2) . "ms" ); diff --git a/src/world/World.php b/src/world/World.php index 607da30cb..b66e76348 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -104,6 +104,7 @@ use pocketmine\world\utils\SubChunkExplorer; use function abs; use function array_filter; use function array_key_exists; +use function array_keys; use function array_map; use function array_merge; use function array_sum; @@ -1155,6 +1156,16 @@ class World implements ChunkManager{ $this->chunkTickRadius = $radius; } + /** + * Returns a list of chunk position hashes (as returned by World::chunkHash()) which are currently registered for + * ticking. + * + * @return int[] + */ + public function getTickingChunks() : array{ + return array_keys($this->tickingChunks); + } + /** * Instructs the World to tick the specified chunk, for as long as this chunk ticker (or any other chunk ticker) is * registered to it. From 9bfcd39f2a8b3e1ff1afe5323b46c0bc43349e19 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 26 Apr 2023 17:06:52 +0100 Subject: [PATCH 05/23] World: improve type info for getTickingChunks() --- src/world/World.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/world/World.php b/src/world/World.php index b66e76348..3a09be176 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -1161,6 +1161,7 @@ class World implements ChunkManager{ * ticking. * * @return int[] + * @phpstan-return list */ public function getTickingChunks() : array{ return array_keys($this->tickingChunks); From 7f6269c432e5341c7ad128c065a3bcda5060cf17 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 27 Apr 2023 16:52:52 +0100 Subject: [PATCH 06/23] Introduce and use optimised versions of Inventory->isSlotEmpty() this avoids useless cloning, improving the performance of several functions. --- src/inventory/BaseInventory.php | 26 ++++++++++++++------------ src/inventory/DelegateInventory.php | 4 ++++ src/inventory/SimpleInventory.php | 4 ++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 9968c283e..d92ee92db 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -152,8 +152,8 @@ abstract class BaseInventory implements Inventory{ } public function firstEmpty() : int{ - foreach($this->getContents(true) as $i => $slot){ - if($slot->isNull()){ + for($i = 0, $size = $this->getSize(); $i < $size; $i++){ + if($this->isSlotEmpty($i)){ return $i; } } @@ -172,13 +172,15 @@ abstract class BaseInventory implements Inventory{ public function getAddableItemQuantity(Item $item) : int{ $count = $item->getCount(); for($i = 0, $size = $this->getSize(); $i < $size; ++$i){ - $slot = $this->getItem($i); - if($item->canStackWith($slot)){ - if(($diff = min($slot->getMaxStackSize(), $item->getMaxStackSize()) - $slot->getCount()) > 0){ - $count -= $diff; - } - }elseif($slot->isNull()){ + if($this->isSlotEmpty($i)){ $count -= min($this->getMaxStackSize(), $item->getMaxStackSize()); + }else{ + $slot = $this->getItem($i); + if($item->canStackWith($slot)){ + if(($diff = min($slot->getMaxStackSize(), $item->getMaxStackSize()) - $slot->getCount()) > 0){ + $count -= $diff; + } + } } if($count <= 0){ @@ -216,11 +218,11 @@ abstract class BaseInventory implements Inventory{ $emptySlots = []; for($i = 0, $size = $this->getSize(); $i < $size; ++$i){ - $item = $this->getItem($i); - if($item->isNull()){ + if($this->isSlotEmpty($i)){ $emptySlots[] = $i; continue; } + $item = $this->getItem($i); if($slot->canStackWith($item) && $item->getCount() < $item->getMaxStackSize()){ $amount = min($item->getMaxStackSize() - $item->getCount(), $slot->getCount(), $this->getMaxStackSize()); @@ -273,10 +275,10 @@ abstract class BaseInventory implements Inventory{ } for($i = 0, $size = $this->getSize(); $i < $size; ++$i){ - $item = $this->getItem($i); - if($item->isNull()){ + if($this->isSlotEmpty($i)){ continue; } + $item = $this->getItem($i); foreach($itemSlots as $index => $slot){ if($slot->equals($item, !$slot->hasAnyDamageValue(), $slot->hasNamedTag())){ diff --git a/src/inventory/DelegateInventory.php b/src/inventory/DelegateInventory.php index ba9e5a983..a211732cf 100644 --- a/src/inventory/DelegateInventory.php +++ b/src/inventory/DelegateInventory.php @@ -85,6 +85,10 @@ class DelegateInventory extends BaseInventory{ $this->backingInventory->setContents($items); } + public function isSlotEmpty(int $index) : bool{ + return $this->backingInventory->isSlotEmpty($index); + } + protected function onSlotChange(int $index, Item $before) : void{ if($this->backingInventoryChanging){ parent::onSlotChange($index, $before); diff --git a/src/inventory/SimpleInventory.php b/src/inventory/SimpleInventory.php index aae11c84c..19fbdbb17 100644 --- a/src/inventory/SimpleInventory.php +++ b/src/inventory/SimpleInventory.php @@ -83,4 +83,8 @@ class SimpleInventory extends BaseInventory{ } } } + + public function isSlotEmpty(int $index) : bool{ + return $this->slots[$index] === null || $this->slots[$index]->isNull(); + } } From 07dc10d6e6a06ccc941627f4eff7c6d982be7244 Mon Sep 17 00:00:00 2001 From: Dylan T Date: Thu, 27 Apr 2023 16:59:29 +0100 Subject: [PATCH 07/23] World: improve performance of tickChunks() selection process (#5721) Since light population is required to make a chunk tickable, a chunk may not be tickable for some time if the async workers get backlogged. The previous version of this system only cached the eligibility result if the result was a "yes", but we can also track it when it's a "no", rather than rechecking it every tick. This change should improve performance in factions and similar gamemodes, which involve large maps with sparsely distributed players, where each player likely has an independent, non-overlapping ticking chunk circle. We also ditch TickingChunkEntry in favour of multiple arrays to track the eligibility states. This allows us to avoid rechecking the (even cached) readiness of potentially thousands of chunks. If there are no ticking chunks to recheck, this reduces the cost of the selection process to zero. --- src/world/TickingChunkEntry.php | 37 ---------- src/world/World.php | 116 +++++++++++++++++++------------- 2 files changed, 71 insertions(+), 82 deletions(-) delete mode 100644 src/world/TickingChunkEntry.php diff --git a/src/world/TickingChunkEntry.php b/src/world/TickingChunkEntry.php deleted file mode 100644 index ca965463d..000000000 --- a/src/world/TickingChunkEntry.php +++ /dev/null @@ -1,37 +0,0 @@ - ChunkTicker - * @phpstan-var array - */ - public array $tickers = []; - - public bool $ready = false; -} diff --git a/src/world/World.php b/src/world/World.php index 3a09be176..1803c03f1 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -224,10 +224,25 @@ class World implements ChunkManager{ private array $tickingLoaderCounter = []; /** - * @var TickingChunkEntry[] chunkHash => TickingChunkEntry - * @phpstan-var array + * @var ChunkTicker[][] chunkHash => [spl_object_id => ChunkTicker] + * @phpstan-var array> */ - private array $tickingChunks = []; + private array $registeredTickingChunks = []; + + /** + * Set of chunks which are definitely ready for ticking. + * + * @var int[] + * @phpstan-var array + */ + private array $validTickingChunks = []; + + /** + * Set of chunks which might be ready for ticking. These will be checked at the next tick. + * @var int[] + * @phpstan-var array + */ + private array $recheckTickingChunks = []; /** * @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader] @@ -1157,14 +1172,14 @@ class World implements ChunkManager{ } /** - * Returns a list of chunk position hashes (as returned by World::chunkHash()) which are currently registered for + * Returns a list of chunk position hashes (as returned by World::chunkHash()) which are currently valid for * ticking. * * @return int[] * @phpstan-return list */ public function getTickingChunks() : array{ - return array_keys($this->tickingChunks); + return array_keys($this->validTickingChunks); } /** @@ -1173,11 +1188,8 @@ class World implements ChunkManager{ */ 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->registeredTickingChunks[$chunkPosHash][spl_object_id($ticker)] = $ticker; + $this->recheckTickingChunks[$chunkPosHash] = $chunkPosHash; } /** @@ -1187,10 +1199,14 @@ class World implements ChunkManager{ 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]); + if(isset($this->registeredTickingChunks[$chunkHash][$tickerId])){ + unset($this->registeredTickingChunks[$chunkHash][$tickerId]); + if(count($this->registeredTickingChunks[$chunkHash]) === 0){ + unset( + $this->registeredTickingChunks[$chunkHash], + $this->recheckTickingChunks[$chunkHash], + $this->validTickingChunks[$chunkHash] + ); } } } @@ -1234,37 +1250,37 @@ class World implements ChunkManager{ } private function tickChunks() : void{ - if($this->chunkTickRadius <= 0 || (count($this->tickingChunks) === 0 && count($this->tickingLoaders) === 0)){ + if($this->chunkTickRadius <= 0 || (count($this->registeredTickingChunks) === 0 && count($this->tickingLoaders) === 0)){ return; } - $this->timings->randomChunkUpdatesChunkSelection->startTiming(); + if(count($this->recheckTickingChunks) > 0 || count($this->tickingLoaders) > 0){ + $this->timings->randomChunkUpdatesChunkSelection->startTiming(); - /** @var bool[] $chunkTickList chunkhash => dummy */ - $chunkTickList = []; + $chunkTickableCache = []; - $chunkTickableCache = []; - - foreach($this->tickingChunks as $hash => $entry){ - if(!$entry->ready){ + foreach($this->recheckTickingChunks as $hash => $_){ 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; + $this->validTickingChunks[$hash] = $hash; } } - $chunkTickList[$hash] = true; - } + $this->recheckTickingChunks = []; - //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); - } + //TODO: REMOVE THIS - we need a local var to add extra chunks to if we have legacy ticking loaders + //this is copy-on-write, so it won't have any performance impact if there are no legacy ticking loaders + $chunkTickList = $this->validTickingChunks; - $this->timings->randomChunkUpdatesChunkSelection->stopTiming(); + //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(); + }else{ + $chunkTickList = $this->validTickingChunks; + } foreach($chunkTickList as $index => $_){ World::getXZ($index, $chunkX, $chunkZ); @@ -1315,16 +1331,23 @@ class World implements ChunkManager{ } /** - * 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. + * Marks the 3x3 square of chunks centered on the specified chunk for chunk ticking eligibility recheck. + * + * This should be used whenever the chunk's eligibility to be ticked is changed. This includes: + * - Loading/unloading the chunk (the chunk may be registered for ticking before it is loaded) + * - Locking/unlocking the chunk (e.g. world population) + * - Light populated state change (i.e. scheduled for light population, or light population completed) + * - Arbitrary chunk replacement (i.e. setChunk() or similar) */ - private function markTickingChunkUnavailable(int $chunkX, int $chunkZ) : void{ + private function markTickingChunkForRecheck(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; + unset($this->validTickingChunks[$chunkHash]); + if(isset($this->registeredTickingChunks[$chunkHash])){ + $this->recheckTickingChunks[$chunkHash] = $chunkHash; + }else{ + unset($this->recheckTickingChunks[$chunkHash]); } } } @@ -1335,7 +1358,7 @@ class World implements ChunkManager{ $lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated(); if($lightPopulatedState === false){ $this->chunks[$chunkHash]->setLightPopulated(null); - $this->markTickingChunkUnavailable($chunkX, $chunkZ); + $this->markTickingChunkForRecheck($chunkX, $chunkZ); $this->workerPool->submitTask(new LightPopulationTask( $this->chunks[$chunkHash], @@ -1359,6 +1382,7 @@ class World implements ChunkManager{ $chunk->getSubChunk($y)->setBlockSkyLightArray($lightArray); } $chunk->setLightPopulated(true); + $this->markTickingChunkForRecheck($chunkX, $chunkZ); } )); } @@ -2403,7 +2427,7 @@ class World implements ChunkManager{ throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked"); } $this->chunkLock[$chunkHash] = $lockId; - $this->markTickingChunkUnavailable($chunkX, $chunkZ); + $this->markTickingChunkForRecheck($chunkX, $chunkZ); } /** @@ -2418,6 +2442,7 @@ class World implements ChunkManager{ $chunkHash = World::chunkHash($chunkX, $chunkZ); if(isset($this->chunkLock[$chunkHash]) && ($lockId === null || $this->chunkLock[$chunkHash] === $lockId)){ unset($this->chunkLock[$chunkHash]); + $this->markTickingChunkForRecheck($chunkX, $chunkZ); return true; } return false; @@ -2469,7 +2494,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 + $this->markTickingChunkForRecheck($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking if(!$this->isChunkInUse($chunkX, $chunkZ)){ $this->unloadChunkRequest($chunkX, $chunkZ); @@ -2751,6 +2776,7 @@ class World implements ChunkManager{ foreach($this->getChunkListeners($x, $z) as $listener){ $listener->onChunkLoaded($x, $z, $this->chunks[$chunkHash]); } + $this->markTickingChunkForRecheck($x, $z); //tickers may have been registered before the chunk was loaded $this->timings->syncChunkLoad->stopTiming(); @@ -2912,8 +2938,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); + unset($this->registeredTickingChunks[$chunkHash]); + $this->markTickingChunkForRecheck($x, $z); if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){ $this->logger->debug("Rejecting population promise for chunk $x $z"); From 709d874204845fb8831b17da29d382643391af0d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 27 Apr 2023 20:27:05 +0100 Subject: [PATCH 08/23] BaseInventory: clean up max stack size handling we can safely assume that: - the inventory's max stack size won't change during the operation - two items which stack together have the same max stack size - the item's max stack size won't change during the operation --- src/inventory/BaseInventory.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index d92ee92db..a90a4e2b9 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -171,13 +171,15 @@ abstract class BaseInventory implements Inventory{ public function getAddableItemQuantity(Item $item) : int{ $count = $item->getCount(); + $maxStackSize = min($this->getMaxStackSize(), $item->getMaxStackSize()); + for($i = 0, $size = $this->getSize(); $i < $size; ++$i){ if($this->isSlotEmpty($i)){ - $count -= min($this->getMaxStackSize(), $item->getMaxStackSize()); + $count -= $maxStackSize; }else{ $slot = $this->getItem($i); if($item->canStackWith($slot)){ - if(($diff = min($slot->getMaxStackSize(), $item->getMaxStackSize()) - $slot->getCount()) > 0){ + if(($diff = $maxStackSize - $slot->getCount()) > 0){ $count -= $diff; } } @@ -217,6 +219,8 @@ abstract class BaseInventory implements Inventory{ private function internalAddItem(Item $slot) : Item{ $emptySlots = []; + $maxStackSize = min($this->getMaxStackSize(), $slot->getMaxStackSize()); + for($i = 0, $size = $this->getSize(); $i < $size; ++$i){ if($this->isSlotEmpty($i)){ $emptySlots[] = $i; @@ -224,8 +228,8 @@ abstract class BaseInventory implements Inventory{ } $item = $this->getItem($i); - if($slot->canStackWith($item) && $item->getCount() < $item->getMaxStackSize()){ - $amount = min($item->getMaxStackSize() - $item->getCount(), $slot->getCount(), $this->getMaxStackSize()); + if($slot->canStackWith($item) && $item->getCount() < $maxStackSize){ + $amount = min($maxStackSize - $item->getCount(), $slot->getCount()); if($amount > 0){ $slot->setCount($slot->getCount() - $amount); $item->setCount($item->getCount() + $amount); @@ -239,7 +243,7 @@ abstract class BaseInventory implements Inventory{ if(count($emptySlots) > 0){ foreach($emptySlots as $slotIndex){ - $amount = min($slot->getMaxStackSize(), $slot->getCount(), $this->getMaxStackSize()); + $amount = min($maxStackSize, $slot->getCount()); $slot->setCount($slot->getCount() - $amount); $item = clone $slot; $item->setCount($amount); From 42288805097dae66aeec288030b8767ac5b36c1e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 27 Apr 2023 20:29:02 +0100 Subject: [PATCH 09/23] BaseInventory: change dumb variable names in internalAddItem() --- src/inventory/BaseInventory.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index a90a4e2b9..6154bcb90 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -216,25 +216,25 @@ abstract class BaseInventory implements Inventory{ return $returnSlots; } - private function internalAddItem(Item $slot) : Item{ + private function internalAddItem(Item $newItem) : Item{ $emptySlots = []; - $maxStackSize = min($this->getMaxStackSize(), $slot->getMaxStackSize()); + $maxStackSize = min($this->getMaxStackSize(), $newItem->getMaxStackSize()); for($i = 0, $size = $this->getSize(); $i < $size; ++$i){ if($this->isSlotEmpty($i)){ $emptySlots[] = $i; continue; } - $item = $this->getItem($i); + $slotItem = $this->getItem($i); - if($slot->canStackWith($item) && $item->getCount() < $maxStackSize){ - $amount = min($maxStackSize - $item->getCount(), $slot->getCount()); + if($newItem->canStackWith($slotItem) && $slotItem->getCount() < $maxStackSize){ + $amount = min($maxStackSize - $slotItem->getCount(), $newItem->getCount()); if($amount > 0){ - $slot->setCount($slot->getCount() - $amount); - $item->setCount($item->getCount() + $amount); - $this->setItem($i, $item); - if($slot->getCount() <= 0){ + $newItem->setCount($newItem->getCount() - $amount); + $slotItem->setCount($slotItem->getCount() + $amount); + $this->setItem($i, $slotItem); + if($newItem->getCount() <= 0){ break; } } @@ -243,18 +243,18 @@ abstract class BaseInventory implements Inventory{ if(count($emptySlots) > 0){ foreach($emptySlots as $slotIndex){ - $amount = min($maxStackSize, $slot->getCount()); - $slot->setCount($slot->getCount() - $amount); - $item = clone $slot; - $item->setCount($amount); - $this->setItem($slotIndex, $item); - if($slot->getCount() <= 0){ + $amount = min($maxStackSize, $newItem->getCount()); + $newItem->setCount($newItem->getCount() - $amount); + $slotItem = clone $newItem; + $slotItem->setCount($amount); + $this->setItem($slotIndex, $slotItem); + if($newItem->getCount() <= 0){ break; } } } - return $slot; + return $newItem; } public function remove(Item $item) : void{ From eb136e60c8011548e0d84f5def1cc1063e83559e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 27 Apr 2023 21:08:35 +0100 Subject: [PATCH 10/23] BaseInventory: added getMatchingItemCount() helper this eliminates the performance issues described by #5719. closes #5719 we may want to consider exposing a public API for this in the future, since it might be useful for plugins. --- src/inventory/BaseInventory.php | 83 ++++++++++++++++++------------- src/inventory/SimpleInventory.php | 5 ++ 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 6154bcb90..433fcbf87 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -108,13 +108,23 @@ abstract class BaseInventory implements Inventory{ $this->onContentChange($oldContents); } + /** + * Helper for utility functions which search the inventory. + * TODO: make this abstract instead of providing a slow default implementation (BC break) + */ + protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{ + $item = $this->getItem($slot); + return $item->equals($test, $checkDamage, $checkTags) ? $item->getCount() : 0; + } + public function contains(Item $item) : bool{ $count = max(1, $item->getCount()); $checkDamage = !$item->hasAnyDamageValue(); $checkTags = $item->hasNamedTag(); - foreach($this->getContents() as $i){ - if($item->equals($i, $checkDamage, $checkTags)){ - $count -= $i->getCount(); + for($i = 0, $size = $this->getSize(); $i < $size; $i++){ + $slotCount = $this->getMatchingItemCount($i, $item, $checkDamage, $checkTags); + if($slotCount > 0){ + $count -= $slotCount; if($count <= 0){ return true; } @@ -128,9 +138,9 @@ abstract class BaseInventory implements Inventory{ $slots = []; $checkDamage = !$item->hasAnyDamageValue(); $checkTags = $item->hasNamedTag(); - foreach($this->getContents() as $index => $i){ - if($item->equals($i, $checkDamage, $checkTags)){ - $slots[$index] = $i; + for($i = 0, $size = $this->getSize(); $i < $size; $i++){ + if($this->getMatchingItemCount($i, $item, $checkDamage, $checkTags) > 0){ + $slots[$i] = $this->getItem($i); } } @@ -142,9 +152,10 @@ abstract class BaseInventory implements Inventory{ $checkDamage = $exact || !$item->hasAnyDamageValue(); $checkTags = $exact || $item->hasNamedTag(); - foreach($this->getContents() as $index => $i){ - if($item->equals($i, $checkDamage, $checkTags) && ($i->getCount() === $count || (!$exact && $i->getCount() > $count))){ - return $index; + for($i = 0, $size = $this->getSize(); $i < $size; $i++){ + $slotCount = $this->getMatchingItemCount($i, $item, $checkDamage, $checkTags); + if($slotCount > 0 && ($slotCount === $count || (!$exact && $slotCount > $count))){ + return $i; } } @@ -177,11 +188,9 @@ abstract class BaseInventory implements Inventory{ if($this->isSlotEmpty($i)){ $count -= $maxStackSize; }else{ - $slot = $this->getItem($i); - if($item->canStackWith($slot)){ - if(($diff = $maxStackSize - $slot->getCount()) > 0){ - $count -= $diff; - } + $slotCount = $this->getMatchingItemCount($i, $item, true, true); + if($slotCount > 0 && ($diff = $maxStackSize - $slotCount) > 0){ + $count -= $diff; } } @@ -226,12 +235,16 @@ abstract class BaseInventory implements Inventory{ $emptySlots[] = $i; continue; } - $slotItem = $this->getItem($i); + $slotCount = $this->getMatchingItemCount($i, $newItem, true, true); + if($slotCount === 0){ + continue; + } - if($newItem->canStackWith($slotItem) && $slotItem->getCount() < $maxStackSize){ - $amount = min($maxStackSize - $slotItem->getCount(), $newItem->getCount()); + if($slotCount < $maxStackSize){ + $amount = min($maxStackSize - $slotCount, $newItem->getCount()); if($amount > 0){ $newItem->setCount($newItem->getCount() - $amount); + $slotItem = $this->getItem($i); $slotItem->setCount($slotItem->getCount() + $amount); $this->setItem($i, $slotItem); if($newItem->getCount() <= 0){ @@ -261,20 +274,20 @@ abstract class BaseInventory implements Inventory{ $checkDamage = !$item->hasAnyDamageValue(); $checkTags = $item->hasNamedTag(); - foreach($this->getContents() as $index => $i){ - if($item->equals($i, $checkDamage, $checkTags)){ - $this->clear($index); + for($i = 0, $size = $this->getSize(); $i < $size; $i++){ + if($this->getMatchingItemCount($i, $item, $checkDamage, $checkTags) > 0){ + $this->clear($i); } } } public function removeItem(Item ...$slots) : array{ - /** @var Item[] $itemSlots */ + /** @var Item[] $searchItems */ /** @var Item[] $slots */ - $itemSlots = []; + $searchItems = []; foreach($slots as $slot){ if(!$slot->isNull()){ - $itemSlots[] = clone $slot; + $searchItems[] = clone $slot; } } @@ -282,26 +295,28 @@ abstract class BaseInventory implements Inventory{ if($this->isSlotEmpty($i)){ continue; } - $item = $this->getItem($i); - foreach($itemSlots as $index => $slot){ - if($slot->equals($item, !$slot->hasAnyDamageValue(), $slot->hasNamedTag())){ - $amount = min($item->getCount(), $slot->getCount()); - $slot->setCount($slot->getCount() - $amount); - $item->setCount($item->getCount() - $amount); - $this->setItem($i, $item); - if($slot->getCount() <= 0){ - unset($itemSlots[$index]); + foreach($searchItems as $index => $search){ + $slotCount = $this->getMatchingItemCount($i, $search, !$search->hasAnyDamageValue(), $search->hasNamedTag()); + if($slotCount > 0){ + $amount = min($slotCount, $search->getCount()); + $search->setCount($search->getCount() - $amount); + + $slotItem = $this->getItem($i); + $slotItem->setCount($slotItem->getCount() - $amount); + $this->setItem($i, $slotItem); + if($search->getCount() <= 0){ + unset($searchItems[$index]); } } } - if(count($itemSlots) === 0){ + if(count($searchItems) === 0){ break; } } - return $itemSlots; + return $searchItems; } public function clear(int $index) : void{ diff --git a/src/inventory/SimpleInventory.php b/src/inventory/SimpleInventory.php index 19fbdbb17..c1f352b7b 100644 --- a/src/inventory/SimpleInventory.php +++ b/src/inventory/SimpleInventory.php @@ -84,6 +84,11 @@ class SimpleInventory extends BaseInventory{ } } + protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{ + $slotItem = $this->slots[$slot]; + return $slotItem !== null && $slotItem->equals($test, $checkDamage, $checkTags) ? $slotItem->getCount() : 0; + } + public function isSlotEmpty(int $index) : bool{ return $this->slots[$index] === null || $this->slots[$index]->isNull(); } From 73bf5d4b29f6e64edc28aee900614b78a62192bb Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Thu, 27 Apr 2023 21:17:55 +0100 Subject: [PATCH 11/23] DoubleChestInventory: specialize isSlotEmpty() and getMatchingItemCount() --- src/block/inventory/DoubleChestInventory.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/block/inventory/DoubleChestInventory.php b/src/block/inventory/DoubleChestInventory.php index 92c75ef9f..8c38e190d 100644 --- a/src/block/inventory/DoubleChestInventory.php +++ b/src/block/inventory/DoubleChestInventory.php @@ -85,6 +85,20 @@ class DoubleChestInventory extends BaseInventory implements BlockInventory, Inve $this->right->setContents($rightContents); } + protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{ + $leftSize = $this->left->getSize(); + return $slot < $leftSize ? + $this->left->getMatchingItemCount($slot, $test, $checkDamage, $checkTags) : + $this->right->getMatchingItemCount($slot - $leftSize, $test, $checkDamage, $checkTags); + } + + public function isSlotEmpty(int $index) : bool{ + $leftSize = $this->left->getSize(); + return $index < $leftSize ? + $this->left->isSlotEmpty($index) : + $this->right->isSlotEmpty($index - $leftSize); + } + protected function getOpenSound() : Sound{ return new ChestOpenSound(); } protected function getCloseSound() : Sound{ return new ChestCloseSound(); } From 84a943bcec2d3f79a5edbc4acc345f7c20ab8b9d Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 5 May 2023 16:06:37 +0100 Subject: [PATCH 12/23] BaseInventory: slap a TODO on isSlotEmpty() --- src/inventory/BaseInventory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/inventory/BaseInventory.php b/src/inventory/BaseInventory.php index 433fcbf87..308850ffd 100644 --- a/src/inventory/BaseInventory.php +++ b/src/inventory/BaseInventory.php @@ -172,6 +172,10 @@ abstract class BaseInventory implements Inventory{ return -1; } + /** + * TODO: make this abstract and force implementations to implement it properly (BC break) + * This default implementation works, but is slow. + */ public function isSlotEmpty(int $index) : bool{ return $this->getItem($index)->isNull(); } From 02cf5ed388c246e1177e4b3fe07eed0d5a3ac405 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 5 May 2023 16:18:03 +0100 Subject: [PATCH 13/23] RuntimeBlockMapping: lazy-load NBT blockstates this saves a considerable amount of memory. we don't actually need this state array in PM4 anyway, since we don't support the client-side chunk cache yet. when the time comes to support it, it'll be much more practical to cache binary states and copy bytes anyway, instead of doing it the current way, which is both slow and memory-intensive. Measured footprint change: 9 MB -> 400 KB. --- .../mcpe/convert/RuntimeBlockMapping.php | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/src/network/mcpe/convert/RuntimeBlockMapping.php b/src/network/mcpe/convert/RuntimeBlockMapping.php index c2123364b..1ad2c5ff7 100644 --- a/src/network/mcpe/convert/RuntimeBlockMapping.php +++ b/src/network/mcpe/convert/RuntimeBlockMapping.php @@ -48,8 +48,8 @@ final class RuntimeBlockMapping{ private array $legacyToRuntimeMap = []; /** @var int[] */ private array $runtimeToLegacyMap = []; - /** @var CompoundTag[] */ - private array $bedrockKnownStates; + /** @var CompoundTag[]|null */ + private ?array $bedrockKnownStates = null; private static function make() : self{ return new self( @@ -85,25 +85,13 @@ final class RuntimeBlockMapping{ return $newTag; } - public function __construct(string $canonicalBlockStatesFile, string $r12ToCurrentBlockMapFile){ - $stream = new BinaryStream(Filesystem::fileGetContents($canonicalBlockStatesFile)); - $list = []; - $nbtReader = new NetworkNbtSerializer(); + public function __construct( + private string $canonicalBlockStatesFile, + string $r12ToCurrentBlockMapFile + ){ + //do not cache this - we only need it to set up mappings under normal circumstances + $bedrockKnownStates = $this->loadBedrockKnownStates(); - $keyIndex = []; - $valueIndex = []; - while(!$stream->feof()){ - $offset = $stream->getOffset(); - $blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag(); - $stream->setOffset($offset); - $list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex); - } - $this->bedrockKnownStates = $list; - - $this->setupLegacyMappings($r12ToCurrentBlockMapFile); - } - - private function setupLegacyMappings(string $r12ToCurrentBlockMapFile) : void{ $legacyIdMap = LegacyBlockIdToStringIdMap::getInstance(); /** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */ $legacyStateMap = []; @@ -123,7 +111,7 @@ final class RuntimeBlockMapping{ * @var int[][] $idToStatesMap string id -> int[] list of candidate state indices */ $idToStatesMap = []; - foreach($this->bedrockKnownStates as $k => $state){ + foreach($bedrockKnownStates as $k => $state){ $idToStatesMap[$state->getString("name")][] = $k; } foreach($legacyStateMap as $pair){ @@ -142,7 +130,7 @@ final class RuntimeBlockMapping{ throw new \RuntimeException("Mapped new state does not appear in network table"); } foreach($idToStatesMap[$mappedName] as $k){ - $networkState = $this->bedrockKnownStates[$k]; + $networkState = $bedrockKnownStates[$k]; if($mappedState->equals($networkState)){ $this->registerMapping($k, $id, $data); continue 2; @@ -152,6 +140,25 @@ final class RuntimeBlockMapping{ } } + /** + * @return CompoundTag[] + */ + private function loadBedrockKnownStates() : array{ + $stream = new BinaryStream(Filesystem::fileGetContents($this->canonicalBlockStatesFile)); + $list = []; + $nbtReader = new NetworkNbtSerializer(); + + $keyIndex = []; + $valueIndex = []; + while(!$stream->feof()){ + $offset = $stream->getOffset(); + $blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag(); + $stream->setOffset($offset); + $list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex); + } + return $list; + } + public function toRuntimeId(int $internalStateId) : int{ return $this->legacyToRuntimeMap[$internalStateId] ?? $this->legacyToRuntimeMap[BlockLegacyIds::INFO_UPDATE << Block::INTERNAL_METADATA_BITS]; } @@ -166,9 +173,14 @@ final class RuntimeBlockMapping{ } /** + * WARNING: This method may load the palette from disk, which is a slow operation. + * Afterwards, it will cache the palette in memory, which requires (in some cases) tens of MB of memory. + * Avoid using this where possible. + * + * @deprecated * @return CompoundTag[] */ public function getBedrockKnownStates() : array{ - return $this->bedrockKnownStates; + return $this->bedrockKnownStates ??= $this->loadBedrockKnownStates(); } } From 077fac84bfebc35c0c8cf9d97ad9ae637f1eaca3 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 8 May 2023 16:27:46 +0100 Subject: [PATCH 14/23] Added aggregate timers for all world timings this allows timings list view to display totals for these sections. It does make the tree view a bit more annoying in some cases though. --- src/timings/Timings.php | 22 +------------ src/world/World.php | 18 ++++++----- src/world/WorldManager.php | 3 -- src/world/WorldTimings.php | 63 ++++++++++++++++++++++++++------------ 4 files changed, 55 insertions(+), 51 deletions(-) diff --git a/src/timings/Timings.php b/src/timings/Timings.php index 74fa40dde..70a2a03cf 100644 --- a/src/timings/Timings.php +++ b/src/timings/Timings.php @@ -87,14 +87,6 @@ abstract class Timings{ /** @var TimingsHandler */ public static $serverCommand; /** @var TimingsHandler */ - public static $worldLoad; - /** @var TimingsHandler */ - public static $worldSave; - /** @var TimingsHandler */ - public static $population; - /** @var TimingsHandler */ - public static $generationCallback; - /** @var TimingsHandler */ public static $permissibleCalculation; /** @var TimingsHandler */ public static $permissibleCalculationDiff; @@ -111,10 +103,6 @@ abstract class Timings{ /** @var TimingsHandler */ public static $playerCheckNearEntities; - /** @var TimingsHandler */ - public static $tickEntity; - /** @var TimingsHandler */ - public static $tickTileEntity; /** @var TimingsHandler */ public static $entityBaseTick; @@ -203,10 +191,6 @@ abstract class Timings{ 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"); - self::$worldSave = new TimingsHandler("World Save"); - self::$population = new TimingsHandler("World Population"); - self::$generationCallback = new TimingsHandler("World Generation Callback"); self::$permissibleCalculation = new TimingsHandler("Permissible Calculation"); 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); @@ -221,9 +205,6 @@ abstract class Timings{ self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN); 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("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); @@ -272,7 +253,7 @@ abstract class Timings{ }else{ $displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\"); } - self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, self::$tickEntity, group: self::GROUP_BREAKDOWN); + self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, group: self::GROUP_BREAKDOWN); } return self::$entityTypeTimingMap[$entity::class]; @@ -282,7 +263,6 @@ abstract class Timings{ 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 ); } diff --git a/src/world/World.php b/src/world/World.php index 1803c03f1..ef62cb987 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -77,7 +77,6 @@ use pocketmine\promise\PromiseResolver; use pocketmine\scheduler\AsyncPool; use pocketmine\Server; use pocketmine\ServerConfigGroup; -use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; use pocketmine\utils\ReversePriorityQueue; @@ -999,7 +998,6 @@ class World implements ChunkManager{ $this->timings->entityTick->startTiming(); //Update entities that need update - Timings::$tickEntity->startTiming(); foreach($this->updateEntities as $id => $entity){ if($entity->isClosed() || $entity->isFlaggedForDespawn() || !$entity->onUpdate($currentTick)){ unset($this->updateEntities[$id]); @@ -1008,7 +1006,6 @@ class World implements ChunkManager{ $entity->close(); } } - Timings::$tickEntity->stopTiming(); $this->timings->entityTick->stopTiming(); $this->timings->randomChunkUpdates->startTiming(); @@ -1439,10 +1436,15 @@ class World implements ChunkManager{ (new WorldSaveEvent($this))->call(); + $timings = $this->timings->syncDataSave; + $timings->startTiming(); + $this->provider->getWorldData()->setTime($this->time); $this->saveChunks(); $this->provider->getWorldData()->save(); + $timings->stopTiming(); + return true; } @@ -3248,7 +3250,8 @@ class World implements ChunkManager{ private function internalOrderChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader, ?PromiseResolver $resolver) : Promise{ $chunkHash = World::chunkHash($chunkX, $chunkZ); - Timings::$population->startTiming(); + $timings = $this->timings->chunkPopulationOrder; + $timings->startTiming(); try{ for($xx = -1; $xx <= 1; ++$xx){ @@ -3305,7 +3308,7 @@ class World implements ChunkManager{ return $resolver->getPromise(); }finally{ - Timings::$population->stopTiming(); + $timings->stopTiming(); } } @@ -3314,7 +3317,8 @@ class World implements ChunkManager{ * @phpstan-param array $adjacentChunks */ private function generateChunkCallback(ChunkLockId $chunkLockId, int $x, int $z, Chunk $chunk, array $adjacentChunks, ChunkLoader $temporaryChunkLoader) : void{ - Timings::$generationCallback->startTiming(); + $timings = $this->timings->chunkPopulationCompletion; + $timings->startTiming(); $dirtyChunks = 0; for($xx = -1; $xx <= 1; ++$xx){ @@ -3383,7 +3387,7 @@ class World implements ChunkManager{ $this->drainPopulationRequestQueue(); } - Timings::$generationCallback->stopTiming(); + $timings->stopTiming(); } public function doChunkGarbageCollection() : void{ diff --git a/src/world/WorldManager.php b/src/world/WorldManager.php index 1124d513d..f056608d7 100644 --- a/src/world/WorldManager.php +++ b/src/world/WorldManager.php @@ -30,7 +30,6 @@ use pocketmine\event\world\WorldUnloadEvent; use pocketmine\lang\KnownTranslationFactory; use pocketmine\player\ChunkSelector; use pocketmine\Server; -use pocketmine\timings\Timings; use pocketmine\world\format\Chunk; use pocketmine\world\format\io\exception\CorruptedWorldException; use pocketmine\world\format\io\exception\UnsupportedWorldFormatException; @@ -391,7 +390,6 @@ class WorldManager{ } private function doAutoSave() : void{ - Timings::$worldSave->startTiming(); foreach($this->worlds as $world){ foreach($world->getPlayers() as $player){ if($player->spawned){ @@ -400,6 +398,5 @@ class WorldManager{ } $world->save(false); } - Timings::$worldSave->stopTiming(); } } diff --git a/src/world/WorldTimings.php b/src/world/WorldTimings.php index 5a51f920b..97ab70709 100644 --- a/src/world/WorldTimings.php +++ b/src/world/WorldTimings.php @@ -38,6 +38,7 @@ class WorldTimings{ public TimingsHandler $randomChunkUpdatesChunkSelection; public TimingsHandler $doChunkGC; public TimingsHandler $entityTick; + public TimingsHandler $tileTick; public TimingsHandler $doTick; public TimingsHandler $syncChunkSend; @@ -48,33 +49,55 @@ class WorldTimings{ public TimingsHandler $syncChunkLoadFixInvalidBlocks; public TimingsHandler $syncChunkLoadEntities; public TimingsHandler $syncChunkLoadTileEntities; + + public TimingsHandler $syncDataSave; public TimingsHandler $syncChunkSave; + public TimingsHandler $chunkPopulationOrder; + public TimingsHandler $chunkPopulationCompletion; + + /** + * @var TimingsHandler[] + * @phpstan-var array + */ + private static array $aggregators = []; + + private static function newTimer(string $worldName, string $timerName) : TimingsHandler{ + $aggregator = self::$aggregators[$timerName] ??= new TimingsHandler("Worlds - $timerName"); //displayed in Minecraft primary table + + //TODO: maybe a dedicated group per world would be better? + return new TimingsHandler("$worldName - $timerName", $aggregator, Timings::GROUP_BREAKDOWN); + } + public function __construct(World $world){ - $name = $world->getFolderName() . " - "; + $name = $world->getFolderName(); - $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->setBlock = self::newTimer($name, "Set Blocks"); + $this->doBlockLightUpdates = self::newTimer($name, "Block Light Updates"); + $this->doBlockSkyLightUpdates = self::newTimer($name, "Sky Light Updates"); - $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); + $this->doChunkUnload = self::newTimer($name, "Unload Chunks"); + $this->scheduledBlockUpdates = self::newTimer($name, "Scheduled Block Updates"); + $this->randomChunkUpdates = self::newTimer($name, "Random Chunk Updates"); + $this->randomChunkUpdatesChunkSelection = self::newTimer($name, "Random Chunk Updates - Chunk Selection"); + $this->doChunkGC = self::newTimer($name, "Garbage Collection"); + $this->entityTick = self::newTimer($name, "Entity Tick"); + $this->tileTick = self::newTimer($name, "Block Entity Tick"); + $this->doTick = self::newTimer($name, "World Tick"); - Timings::init(); //make sure the timers we want are available - $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->syncChunkSend = self::newTimer($name, "Player Send Chunks"); + $this->syncChunkSendPrepare = self::newTimer($name, "Player Send Chunk Prepare"); - $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->syncChunkLoad = self::newTimer($name, "Chunk Load"); + $this->syncChunkLoadData = self::newTimer($name, "Chunk Load - Data"); + $this->syncChunkLoadFixInvalidBlocks = self::newTimer($name, "Chunk Load - Fix Invalid Blocks"); + $this->syncChunkLoadEntities = self::newTimer($name, "Chunk Load - Entities"); + $this->syncChunkLoadTileEntities = self::newTimer($name, "Chunk Load - Block Entities"); - $this->doTick = new TimingsHandler($name . "World Tick"); + $this->syncDataSave = self::newTimer($name, "Data Save"); + $this->syncChunkSave = self::newTimer($name, "Chunk Save"); + + $this->chunkPopulationOrder = self::newTimer($name, "Chunk Population - Order"); + $this->chunkPopulationCompletion = self::newTimer($name, "Chunk Population - Completion"); } } From d317347a9b4c50283ad0dafd55f82b35eade0538 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 8 May 2023 16:35:30 +0100 Subject: [PATCH 15/23] WorldTimings: remove TODO I tried this, and it didn't really provide any information that the tree table didn't already show. --- src/world/WorldTimings.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/world/WorldTimings.php b/src/world/WorldTimings.php index 97ab70709..5c1a56011 100644 --- a/src/world/WorldTimings.php +++ b/src/world/WorldTimings.php @@ -65,7 +65,6 @@ class WorldTimings{ private static function newTimer(string $worldName, string $timerName) : TimingsHandler{ $aggregator = self::$aggregators[$timerName] ??= new TimingsHandler("Worlds - $timerName"); //displayed in Minecraft primary table - //TODO: maybe a dedicated group per world would be better? return new TimingsHandler("$worldName - $timerName", $aggregator, Timings::GROUP_BREAKDOWN); } From a4fea1444a50f2382ba9b8643fb041db781036d0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 16 May 2023 14:21:32 +0100 Subject: [PATCH 16/23] Remove validateCallableSignature() calls from network hot paths we rely on phpstan for validation of this internally, and plugins shouldn't be calling these methods anyway. this significantly reduces the overhead of CompressBatchPromise. --- src/network/mcpe/NetworkSession.php | 2 -- src/network/mcpe/compression/CompressBatchPromise.php | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 92a992ffb..dfb2e1aba 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -1000,8 +1000,6 @@ class NetworkSession{ * @phpstan-param \Closure() : void $onCompletion */ public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{ - Utils::validateCallableSignature(function() : void{}, $onCompletion); - $world = $this->player->getLocation()->getWorld(); ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ)->onResolve( diff --git a/src/network/mcpe/compression/CompressBatchPromise.php b/src/network/mcpe/compression/CompressBatchPromise.php index 3b8c9680b..6c8333db6 100644 --- a/src/network/mcpe/compression/CompressBatchPromise.php +++ b/src/network/mcpe/compression/CompressBatchPromise.php @@ -42,9 +42,6 @@ class CompressBatchPromise{ */ public function onResolve(\Closure ...$callbacks) : void{ $this->checkCancelled(); - foreach($callbacks as $callback){ - Utils::validateCallableSignature(function(CompressBatchPromise $promise) : void{}, $callback); - } if($this->result !== null){ foreach($callbacks as $callback){ $callback($this); From 9499e2e5955e92e486a603c00d1df170097e70a2 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 16 May 2023 14:21:59 +0100 Subject: [PATCH 17/23] always the CS... --- src/network/mcpe/NetworkSession.php | 1 - src/network/mcpe/compression/CompressBatchPromise.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index dfb2e1aba..d87c1b9a8 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -107,7 +107,6 @@ use pocketmine\utils\BinaryDataException; use pocketmine\utils\BinaryStream; use pocketmine\utils\ObjectSet; use pocketmine\utils\TextFormat; -use pocketmine\utils\Utils; use pocketmine\world\Position; use function array_map; use function array_values; diff --git a/src/network/mcpe/compression/CompressBatchPromise.php b/src/network/mcpe/compression/CompressBatchPromise.php index 6c8333db6..12ac35c60 100644 --- a/src/network/mcpe/compression/CompressBatchPromise.php +++ b/src/network/mcpe/compression/CompressBatchPromise.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace pocketmine\network\mcpe\compression; -use pocketmine\utils\Utils; use function array_push; class CompressBatchPromise{ From 599c4284f560ce09aac1cda996743daf9d543aff Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 16 May 2023 22:54:06 +0100 Subject: [PATCH 18/23] Introduce 10 KB threshold for async compression due to the extremely large performance cost of instantiating AsyncTasks, it's usually not worth bothering with async compression except for very large packets. While this large overhead can be significantly reduced by using specialized threads, it's early days in the testing stages for such improvements, and for now, we still have this to deal with. Since async compression is always used prior to player spawn, this change may slightly improve the performance of the pre-join stage of the game. --- resources/pocketmine.yml | 5 ++++- src/Server.php | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/resources/pocketmine.yml b/resources/pocketmine.yml index ac60afe53..408b5b95b 100644 --- a/resources/pocketmine.yml +++ b/resources/pocketmine.yml @@ -85,8 +85,11 @@ network: batch-threshold: 256 #Compression level used when sending batched packets. Higher = more CPU, less bandwidth usage compression-level: 6 - #Use AsyncTasks for compression. Adds half/one tick delay, less CPU load on main thread + #Use AsyncTasks for compression during the main game session. Increases latency, but may reduce main thread load async-compression: false + #Threshold for async compression, in bytes. Only packets larger than this will be compressed asynchronously + #Due to large overhead of AsyncTask, async compression isn't worth it except for large packets + async-compression-threshold: 10000 #Experimental. Use UPnP to automatically port forward upnp-forwarding: false #Maximum size in bytes of packets sent over the network (default 1492 bytes). Packets larger than this will be diff --git a/src/Server.php b/src/Server.php index a81b1d74b..11a2b157a 100644 --- a/src/Server.php +++ b/src/Server.php @@ -208,6 +208,8 @@ class Server{ private const TICKS_PER_TPS_OVERLOAD_WARNING = 5 * self::TARGET_TICKS_PER_SECOND; private const TICKS_PER_STATS_REPORT = 300 * self::TARGET_TICKS_PER_SECOND; + private const DEFAULT_ASYNC_COMPRESSION_THRESHOLD = 10_000; + private static ?Server $instance = null; private TimeTrackingSleeperHandler $tickSleeper; @@ -266,6 +268,7 @@ class Server{ private Network $network; private bool $networkCompressionAsync = true; + private int $networkCompressionAsyncThreshold = self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD; private Language $language; private bool $forceLanguage = false; @@ -908,6 +911,10 @@ class Server{ ZlibCompressor::setInstance(new ZlibCompressor($netCompressionLevel, $netCompressionThreshold, ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE)); $this->networkCompressionAsync = $this->configGroup->getPropertyBool("network.async-compression", true); + $this->networkCompressionAsyncThreshold = max( + $this->configGroup->getPropertyInt("network.async-compression-threshold", self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD), + $netCompressionThreshold ?? self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD + ); EncryptionContext::$ENABLED = $this->configGroup->getPropertyBool("network.enable-encryption", true); @@ -1375,7 +1382,7 @@ class Server{ } $promise = new CompressBatchPromise(); - if(!$sync){ + if(!$sync && strlen($buffer) >= $this->networkCompressionAsyncThreshold){ $task = new CompressBatchTask($buffer, $promise, $compressor); $this->asyncPool->submitTask($task); }else{ From 008a022ec16e9d2f7986febcefab26a9b3f3ab51 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 16 May 2023 23:02:33 +0100 Subject: [PATCH 19/23] Players now have finite resources in spectator mode this seems like the logical solution for the block picking issues. --- src/player/Player.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/player/Player.php b/src/player/Player.php index 279dd1ebf..e8dae37f4 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -1191,7 +1191,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ * TODO: make this a dynamic ability instead of being hardcoded */ public function hasFiniteResources() : bool{ - return $this->gamemode->equals(GameMode::SURVIVAL()) || $this->gamemode->equals(GameMode::ADVENTURE()); + return !$this->gamemode->equals(GameMode::CREATIVE()); } public function isFireProof() : bool{ @@ -1657,7 +1657,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ $ev = new PlayerBlockPickEvent($this, $block, $item); $existingSlot = $this->inventory->first($item); - if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){ + if($existingSlot === -1 && $this->hasFiniteResources()){ $ev->cancel(); } $ev->call(); From 5a0cde49cc80e395a85204f9e9a15dd1884acc1e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 16 May 2023 23:37:58 +0100 Subject: [PATCH 20/23] AsyncPool: do not double-check progress updates on finished tasks checkProgressUpdates is called directly before onCompletion, so we only need to call it again if the task isn't finished yet. --- src/scheduler/AsyncPool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scheduler/AsyncPool.php b/src/scheduler/AsyncPool.php index 9029eb593..99175651d 100644 --- a/src/scheduler/AsyncPool.php +++ b/src/scheduler/AsyncPool.php @@ -247,7 +247,6 @@ class AsyncPool{ while(!$queue->isEmpty()){ /** @var AsyncTask $task */ $task = $queue->bottom(); - $task->checkProgressUpdates(); if($task->isFinished()){ //make sure the task actually executed before trying to collect $queue->dequeue(); @@ -268,6 +267,7 @@ class AsyncPool{ $task->onCompletion(); } }else{ + $task->checkProgressUpdates(); $more = true; break; //current task is still running, skip to next worker } From 043350753b9c93a6a7e508fbaec0744e5d2d4beb Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 17 May 2023 13:53:57 +0100 Subject: [PATCH 21/23] Drop PHP 8.0, 8.1 is now minimum version --- .github/workflows/main.yml | 10 +- .github/workflows/update-php-versions.php | 1 - BUILDING.md | 10 +- build/php | 2 +- composer.json | 4 +- composer.lock | 126 ++++------------------ src/PocketMine.php | 2 +- 7 files changed, 34 insertions(+), 121 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e3a2b1640..da8468a81 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: image: [ubuntu-20.04] - php: [8.0.28, 8.1.18, 8.2.5] + php: [8.1.19, 8.2.6] steps: - name: Build and prepare PHP cache @@ -32,7 +32,7 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.28, 8.1.18, 8.2.5] + php: [8.1.19, 8.2.6] steps: - uses: actions/checkout@v3 @@ -71,7 +71,7 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.28, 8.1.18, 8.2.5] + php: [8.1.19, 8.2.6] steps: - uses: actions/checkout@v3 @@ -110,7 +110,7 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.28, 8.1.18, 8.2.5] + php: [8.1.19, 8.2.6] steps: - uses: actions/checkout@v3 @@ -151,7 +151,7 @@ jobs: fail-fast: false matrix: image: [ubuntu-20.04] - php: [8.0.28, 8.1.18, 8.2.5] + php: [8.1.19, 8.2.6] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/update-php-versions.php b/.github/workflows/update-php-versions.php index 92e79a6de..2455ba101 100644 --- a/.github/workflows/update-php-versions.php +++ b/.github/workflows/update-php-versions.php @@ -22,7 +22,6 @@ declare(strict_types=1); const VERSIONS = [ - "8.0", "8.1", "8.2" ]; diff --git a/BUILDING.md b/BUILDING.md index d6e97e05c..95197de6b 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -2,13 +2,13 @@ ## Pre-requisites - A bash shell (git bash is sufficient for Windows) - [`git`](https://git-scm.com) available in your shell -- PHP 8.0 or newer available in your shell +- PHP 8.1 or newer available in your shell - [`composer`](https://getcomposer.org) available in your shell ## Custom PHP binaries Because PocketMine-MP requires several non-standard PHP extensions and configuration, PMMP provides scripts to build custom binaries for running PocketMine-MP, as well as prebuilt binaries. -- [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-8.0-Aggregate) +- [Prebuilt binaries](https://github.com/pmmp/PHP-Binaries/releases) - [Compile scripts](https://github.com/pmmp/php-build-scripts) are provided as a submodule in the path `build/php` If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`. @@ -29,11 +29,5 @@ Run `composer make-server` using your preferred PHP binary. It'll drop a `Pocket You can also use the `--out` option to change the output filename. -There is a bug in PHP that might cause an error which looks like this: -``` -Fatal error: Uncaught BadMethodCallException: unable to create temporary file in PocketMine-MP/build/server-phar.php:119 -``` -You can work around it by setting `ulimit -n` to some bigger number, e.g. `8192`, or by updating your PHP version to at least 8.0.3. - ## Running PocketMine-MP from source code Run `src/PocketMine.php` using your preferred PHP binary. diff --git a/build/php b/build/php index a3c40579a..07f3d90fa 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit a3c40579ad91246b07053fc2c8f085efd442943a +Subproject commit 07f3d90faa1edcbd9c5adc7d17a1bb64a06dc346 diff --git a/composer.json b/composer.json index 630b31c17..0c1470be3 100644 --- a/composer.json +++ b/composer.json @@ -5,7 +5,7 @@ "homepage": "https://pmmp.io", "license": "LGPL-3.0", "require": { - "php": "^8.0", + "php": "^8.1", "php-64bit": "*", "ext-chunkutils2": "^0.3.1", "ext-crypto": "^0.3.1", @@ -77,7 +77,7 @@ }, "config": { "platform": { - "php": "8.0.0" + "php": "8.1.0" }, "sort-packages": true }, diff --git a/composer.lock b/composer.lock index a0e4aa8e4..fa28f6b62 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "a5ffe862f4e6376eaf78593a4bb8aeb6", + "content-hash": "ecb1e46a4410fdc7efb7e3dac60b5322", "packages": [ { "name": "adhocore/json-comment", @@ -903,21 +903,20 @@ }, { "name": "ramsey/collection", - "version": "1.3.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4" + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/ad7475d1c9e70b190ecffc58f2d989416af339b4", - "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0", - "symfony/polyfill-php81": "^1.23" + "php": "^8.1" }, "require-dev": { "captainhook/plugin-composer": "^5.3", @@ -977,7 +976,7 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.3.0" + "source": "https://github.com/ramsey/collection/tree/2.0.0" }, "funding": [ { @@ -989,7 +988,7 @@ "type": "tidelift" } ], - "time": "2022-12-27T19:12:24+00:00" + "time": "2022-12-31T21:50:55+00:00" }, { "name": "ramsey/uuid", @@ -1395,85 +1394,6 @@ ], "time": "2022-11-03T14:55:06+00:00" }, - { - "name": "symfony/polyfill-php81", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, { "name": "webmozart/assert", "version": "1.11.0", @@ -1587,30 +1507,30 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "1.5.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -1637,7 +1557,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -1653,7 +1573,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "myclabs/deep-copy", @@ -3486,7 +3406,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^8.0", + "php": "^8.1", "php-64bit": "*", "ext-chunkutils2": "^0.3.1", "ext-crypto": "^0.3.1", @@ -3515,7 +3435,7 @@ }, "platform-dev": [], "platform-overrides": { - "php": "8.0.0" + "php": "8.1.0" }, "plugin-api-version": "2.3.0" } diff --git a/src/PocketMine.php b/src/PocketMine.php index 4b0b644ec..7b47449d5 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -50,7 +50,7 @@ namespace pocketmine { require_once __DIR__ . '/VersionInfo.php'; - const MIN_PHP_VERSION = "8.0.0"; + const MIN_PHP_VERSION = "8.1.0"; /** * @param string $message From c7dff9ea40519eb8c1f85e47ff2b6e710591fd6c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 17 May 2023 14:11:43 +0100 Subject: [PATCH 22/23] bootstrap: remove ext-parallel bootstrapping code I have no intention of using parallel, so this code is not necessary. --- src/PocketMine.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/PocketMine.php b/src/PocketMine.php index 7b47449d5..c653d33ea 100644 --- a/src/PocketMine.php +++ b/src/PocketMine.php @@ -265,9 +265,6 @@ JIT_WARNING exit(1); } } - if(extension_loaded('parallel')){ - \parallel\bootstrap(\pocketmine\COMPOSER_AUTOLOADER_PATH); - } ErrorToExceptionHandler::set(); From 0547383296e17131c23b6a8a827e87a40fffc3d3 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 17 May 2023 15:08:05 +0100 Subject: [PATCH 23/23] Update build/php submodule to pmmp/PHP-Binaries@f860ade30acc074a98bbf5ff286f35b5eda10c86 --- build/php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/php b/build/php index 07f3d90fa..f860ade30 160000 --- a/build/php +++ b/build/php @@ -1 +1 @@ -Subproject commit 07f3d90faa1edcbd9c5adc7d17a1bb64a06dc346 +Subproject commit f860ade30acc074a98bbf5ff286f35b5eda10c86