From 948875b0257ff3542878f660daa74661ee0b9b81 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 24 Jul 2023 16:59:45 +0100 Subject: [PATCH 01/21] Release 4.23.3 --- changelogs/4.23.md | 13 +++++++++++++ src/VersionInfo.php | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/changelogs/4.23.md b/changelogs/4.23.md index d51413e82..6cd261ba1 100644 --- a/changelogs/4.23.md +++ b/changelogs/4.23.md @@ -30,3 +30,16 @@ Released 18th July 2023. ## Fixes - Fixed login errors due to a new `sandboxId` field appearing in the Xbox Live authentication data in `LoginPacket`. All clients, regardless of version, are affected by this change. + +# 4.23.3 +Released 24th July 2023. + +## Documentation +- Fixed typo in `ChunkSelector::selectChunks()` documentation. + +## Fixes +- Fixed the server not stopping properly during crash conditions on *nix platforms. +- Fixed `HORSE_EQUIP` and `SMITHING_TABLE_TEMPLATE` container UI types not being handled by `ItemStackContainerIdTranslator`. This bug prevented plugins from implementing missing inventory types. +- Player emotes no longer broadcast messages to other players. This was unintended behaviour caused by a client-side behavioural change. +- Shulker boxes no longer support the placement of torches or other similar blocks. +- Fire can now be placed on upper slabs and the top of upside-down stairs. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 5ef12e2c4..1025e0721 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "4.23.3"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; private function __construct(){ From 1c611a03e6c8afb2da57147f41225646cdd2f8e0 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 24 Jul 2023 16:59:48 +0100 Subject: [PATCH 02/21] 4.23.4 is next --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 1025e0721..490300ff5 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "4.23.3"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "4.23.4"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; private function __construct(){ From a1f34a460bd8206c552935ed5170f2ad85cefc37 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 24 Jul 2023 17:29:56 +0100 Subject: [PATCH 03/21] Release 5.3.3 --- changelogs/5.3.md | 23 +++++++++++++++++++++++ src/VersionInfo.php | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/changelogs/5.3.md b/changelogs/5.3.md index 3dfd035de..173e73cb8 100644 --- a/changelogs/5.3.md +++ b/changelogs/5.3.md @@ -47,3 +47,26 @@ Released 18th July 2023. ## Internals - Armor pieces are no longer set back into the armor inventory if no change was made. This reduces the number of slot updates sent to clients, as well as avoiding unnecessary updates for armor pieces which have Unbreaking enchantments. + +# 5.3.3 +Released 24th July 2023. + +## Included releases +**This release includes changes from the following releases:** +- [4.23.3](https://github.com/pmmp/PocketMine-MP/blob/4.23.3/changelogs/4.23.md#4233) - Various bug fixes + +## Fixes +- Added a workaround for PM4 worlds with chunks corrupted by [Refaltor77/CustomItemAPI](https://github.com/Refaltor77/CustomItemAPI). + - While this was not the fault of PocketMine-MP itself, a significant number of users were affected by this problem. + - This error was not detected by PM4 due to missing validation of certain data which should not have existed in 1.12. + - An error will now be logged when this corruption is detected, but the affected chunks should otherwise load normally. +- Relaxed validation of expected 3D biome array counts per chunk in LevelDB worlds. + - Vanilla Bedrock currently saves 24 palettes (and only 24 are required), but it saved 25, 32, or 64 biome palettes per chunk in older versions. + - Core validation for these padding biomes was very strict, enforcing exact compliance with vanilla. + - Since only 24 palettes are actually required, the remaining palettes may now be omitted irrespective of the chunk version. + - An error will still be logged when this mistake is detected, but the affected chunks will otherwise load normally. +- Fixed `/kill` not working on players who had (re)spawned in the 3 seconds immediately after (re)spawning (due to `noDamageTicks`). +- Fixed `/kill` not working correctly for players with high levels of Health Boost or other health-altering effects. +- Fixed netherite items being destroyed by lava. +- Fireproof entities no longer display the burning animation when in fire or lava. This does not apply to creative players, who are immortal rather than being fireproof. +- Fixed frosted ice melting in certain conditions which didn't match vanilla Bedrock. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 7cbd05735..3887af7dc 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.3.3"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 486d4099dfc4445834f6a1f89e71e69c3cd90dcf Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Mon, 24 Jul 2023 17:29:59 +0100 Subject: [PATCH 04/21] 5.3.4 is next --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 3887af7dc..578f9de2d 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.3.3"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.3.4"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 6086ef667ce2b145545c14871e671d172030229a Mon Sep 17 00:00:00 2001 From: ipad54 <63200545+ipad54@users.noreply.github.com> Date: Tue, 25 Jul 2023 16:50:28 +0300 Subject: [PATCH 05/21] Added handling for attack-air action (#5912) --- src/event/player/PlayerMissedSwingEvent.php | 39 +++++++++++++++++++ .../mcpe/handler/InGamePacketHandler.php | 3 ++ src/player/Player.php | 13 +++++++ 3 files changed, 55 insertions(+) create mode 100644 src/event/player/PlayerMissedSwingEvent.php diff --git a/src/event/player/PlayerMissedSwingEvent.php b/src/event/player/PlayerMissedSwingEvent.php new file mode 100644 index 000000000..3157f62d1 --- /dev/null +++ b/src/event/player/PlayerMissedSwingEvent.php @@ -0,0 +1,39 @@ +player = $player; + } +} diff --git a/src/network/mcpe/handler/InGamePacketHandler.php b/src/network/mcpe/handler/InGamePacketHandler.php index 22eb91935..729b5b51b 100644 --- a/src/network/mcpe/handler/InGamePacketHandler.php +++ b/src/network/mcpe/handler/InGamePacketHandler.php @@ -236,6 +236,9 @@ class InGamePacketHandler extends PacketHandler{ if($packet->hasFlag(PlayerAuthInputFlags::START_JUMPING)){ $this->player->jump(); } + if($packet->hasFlag(PlayerAuthInputFlags::MISSED_SWING)){ + $this->player->missSwing(); + } } if(!$this->forceMoveSync && $hasMoved){ diff --git a/src/player/Player.php b/src/player/Player.php index f8d4923f0..9b6c321e8 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -66,6 +66,7 @@ use pocketmine\event\player\PlayerItemUseEvent; use pocketmine\event\player\PlayerJoinEvent; use pocketmine\event\player\PlayerJumpEvent; use pocketmine\event\player\PlayerKickEvent; +use pocketmine\event\player\PlayerMissedSwingEvent; use pocketmine\event\player\PlayerMoveEvent; use pocketmine\event\player\PlayerPostChunkSendEvent; use pocketmine\event\player\PlayerQuitEvent; @@ -1894,6 +1895,18 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ return true; } + /** + * Performs actions associated with the attack action (left-click) without a target entity. + * Under normal circumstances, this will play the no-damage attack sound and nothing else. + */ + public function missSwing() : void{ + $ev = new PlayerMissedSwingEvent($this); + $ev->call(); + if(!$ev->isCancelled()){ + $this->broadcastSound(new EntityAttackNoDamageSound()); + } + } + /** * Interacts with the given entity using the currently-held item. */ From bbdcab727726225dca6d353ed4dc48815cd90b8e Mon Sep 17 00:00:00 2001 From: rasu3n Date: Wed, 26 Jul 2023 18:04:36 +0900 Subject: [PATCH 06/21] Player: Added animation to missSwing() (#5942) --- src/player/Player.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/player/Player.php b/src/player/Player.php index 9b6c321e8..292ab98b3 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -1897,13 +1897,14 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ /** * Performs actions associated with the attack action (left-click) without a target entity. - * Under normal circumstances, this will play the no-damage attack sound and nothing else. + * Under normal circumstances, this will just play the no-damage attack sound and the arm-swing animation. */ public function missSwing() : void{ $ev = new PlayerMissedSwingEvent($this); $ev->call(); if(!$ev->isCancelled()){ $this->broadcastSound(new EntityAttackNoDamageSound()); + $this->broadcastAnimation(new ArmSwingAnimation($this), $this->getViewers()); } } From 82a5ea9ed3d4aab82e7389b50c550c9ea4aa80b4 Mon Sep 17 00:00:00 2001 From: Dylan T Date: Wed, 26 Jul 2023 16:26:03 +0100 Subject: [PATCH 07/21] Allow thread errors and their traces to be properly recorded in crashdumps (#5910) until now, any thread crash would show as a generic crash since we aren't able to get the trace from the crashed thread directly. This uses some dirty tricks to export a partially serialized stack trace to the main thread, where it can be written into a crashdump. This enables us to see proper crash information for async tasks in the crash archive (finally!!!) as well as being able to capture RakLib errors properly. --- src/Server.php | 38 +++++-- src/crash/CrashDump.php | 15 +-- src/crash/CrashDumpData.php | 2 + src/crash/CrashDumpRenderer.php | 1 + src/network/mcpe/raklib/RakLibInterface.php | 3 +- src/network/mcpe/raklib/RakLibServer.php | 101 ++++++------------ .../mcpe/raklib/RakLibThreadCrashInfo.php | 61 ----------- src/scheduler/AsyncPool.php | 14 ++- src/scheduler/AsyncWorker.php | 12 +-- src/thread/CommonThreadPartsTrait.php | 41 +++++++ src/thread/ThreadCrashException.php | 38 +++++++ src/thread/ThreadCrashInfo.php | 89 +++++++++++++++ src/thread/ThreadCrashInfoFrame.php | 41 +++++++ src/utils/Utils.php | 25 +++++ tests/phpstan/configs/actual-problems.neon | 10 ++ 15 files changed, 329 insertions(+), 162 deletions(-) delete mode 100644 src/network/mcpe/raklib/RakLibThreadCrashInfo.php create mode 100644 src/thread/ThreadCrashException.php create mode 100644 src/thread/ThreadCrashInfo.php create mode 100644 src/thread/ThreadCrashInfoFrame.php diff --git a/src/Server.php b/src/Server.php index e8f2d2726..148c93b8e 100644 --- a/src/Server.php +++ b/src/Server.php @@ -93,6 +93,7 @@ use pocketmine\scheduler\AsyncPool; use pocketmine\snooze\SleeperHandler; use pocketmine\stats\SendUsageTask; use pocketmine\thread\log\AttachableThreadSafeLogger; +use pocketmine\thread\ThreadCrashException; use pocketmine\thread\ThreadSafeClassLoader; use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; @@ -1516,23 +1517,38 @@ class Server{ $trace = $e->getTrace(); } - $errstr = $e->getMessage(); - $errfile = $e->getFile(); - $errline = $e->getLine(); + //If this is a thread crash, this logs where the exception came from on the main thread, as opposed to the + //crashed thread. This is intentional, and might be useful for debugging + //Assume that the thread already logged the original exception with the correct stack trace + $this->logger->logException($e, $trace); + + if($e instanceof ThreadCrashException){ + $info = $e->getCrashInfo(); + $type = $info->getType(); + $errstr = $info->getMessage(); + $errfile = $info->getFile(); + $errline = $info->getLine(); + $printableTrace = $info->getTrace(); + $thread = $info->getThreadName(); + }else{ + $type = get_class($e); + $errstr = $e->getMessage(); + $errfile = $e->getFile(); + $errline = $e->getLine(); + $printableTrace = Utils::printableTraceWithMetadata($trace); + $thread = "Main"; + } $errstr = preg_replace('/\s+/', ' ', trim($errstr)); - $errfile = Filesystem::cleanPath($errfile); - - $this->logger->logException($e, $trace); - $lastError = [ - "type" => get_class($e), + "type" => $type, "message" => $errstr, - "fullFile" => $e->getFile(), - "file" => $errfile, + "fullFile" => $errfile, + "file" => Filesystem::cleanPath($errfile), "line" => $errline, - "trace" => $trace + "trace" => $printableTrace, + "thread" => $thread ]; global $lastExceptionError, $lastError; diff --git a/src/crash/CrashDump.php b/src/crash/CrashDump.php index d7223eb2f..40af53fd0 100644 --- a/src/crash/CrashDump.php +++ b/src/crash/CrashDump.php @@ -29,11 +29,13 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\plugin\PluginBase; use pocketmine\plugin\PluginManager; use pocketmine\Server; +use pocketmine\thread\ThreadCrashInfoFrame; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Filesystem; use pocketmine\utils\Utils; use pocketmine\VersionInfo; use Symfony\Component\Filesystem\Path; +use function array_map; use function base64_encode; use function error_get_last; use function file; @@ -186,7 +188,7 @@ class CrashDump{ if($error === null){ throw new \RuntimeException("Crash error information missing - did something use exit()?"); } - $error["trace"] = Utils::currentTrace(3); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump + $error["trace"] = Utils::printableTrace(Utils::currentTrace(3)); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump $error["fullFile"] = $error["file"]; $error["file"] = Filesystem::cleanPath($error["file"]); try{ @@ -201,9 +203,6 @@ class CrashDump{ $error["message"] = mb_scrub($error["message"], 'UTF-8'); if(isset($lastError)){ - if(isset($lastError["trace"])){ - $lastError["trace"] = Utils::printableTrace($lastError["trace"]); - } $this->data->lastError = $lastError; $this->data->lastError["message"] = mb_scrub($this->data->lastError["message"], 'UTF-8'); } @@ -215,10 +214,11 @@ class CrashDump{ $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_NONE; if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace foreach($error["trace"] as $frame){ - if(!isset($frame["file"])){ + $frameFile = $frame->getFile(); + if($frameFile === null){ continue; //PHP core } - if($this->determinePluginFromFile($frame["file"], false)){ + if($this->determinePluginFromFile($frameFile, false)){ break; } } @@ -233,7 +233,8 @@ class CrashDump{ } } - $this->data->trace = Utils::printableTrace($error["trace"]); + $this->data->trace = array_map(array: $error["trace"], callback: fn(ThreadCrashInfoFrame $frame) => $frame->getPrintableFrame()); + $this->data->thread = $error["thread"]; } private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{ diff --git a/src/crash/CrashDumpData.php b/src/crash/CrashDumpData.php index 0f5358be5..b71e6f405 100644 --- a/src/crash/CrashDumpData.php +++ b/src/crash/CrashDumpData.php @@ -37,6 +37,8 @@ final class CrashDumpData implements \JsonSerializable{ /** @var mixed[] */ public array $error; + public string $thread; + public string $plugin_involvement; public string $plugin = ""; diff --git a/src/crash/CrashDumpRenderer.php b/src/crash/CrashDumpRenderer.php index 2858f43ec..617dcb7ab 100644 --- a/src/crash/CrashDumpRenderer.php +++ b/src/crash/CrashDumpRenderer.php @@ -64,6 +64,7 @@ final class CrashDumpRenderer{ $this->addLine(); + $this->addLine("Thread: " . $this->data->thread); $this->addLine("Error: " . $this->data->error["message"]); $this->addLine("File: " . $this->data->error["file"]); $this->addLine("Line: " . $this->data->error["line"]); diff --git a/src/network/mcpe/raklib/RakLibInterface.php b/src/network/mcpe/raklib/RakLibInterface.php index 93dff68a2..4bf8ffb15 100644 --- a/src/network/mcpe/raklib/RakLibInterface.php +++ b/src/network/mcpe/raklib/RakLibInterface.php @@ -39,6 +39,7 @@ use pocketmine\network\NetworkInterfaceStartException; use pocketmine\network\PacketHandlingException; use pocketmine\player\GameMode; use pocketmine\Server; +use pocketmine\thread\ThreadCrashException; use pocketmine\timings\Timings; use pocketmine\utils\Utils; use raklib\generic\DisconnectReason; @@ -154,7 +155,7 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{ if(!$this->rakLib->isRunning()){ $e = $this->rakLib->getCrashInfo(); if($e !== null){ - throw new \RuntimeException("RakLib crashed: " . $e->makePrettyMessage()); + throw new ThreadCrashException("RakLib crashed", $e); } throw new \Exception("RakLib Thread crashed without crash information"); } diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php index a3f7c1609..e59b6971f 100644 --- a/src/network/mcpe/raklib/RakLibServer.php +++ b/src/network/mcpe/raklib/RakLibServer.php @@ -29,6 +29,7 @@ use pocketmine\snooze\SleeperHandlerEntry; use pocketmine\thread\log\ThreadSafeLogger; use pocketmine\thread\NonThreadSafeValue; use pocketmine\thread\Thread; +use pocketmine\thread\ThreadCrashException; use raklib\generic\SocketException; use raklib\server\ipc\RakLibToUserThreadMessageSender; use raklib\server\ipc\UserToRakLibThreadMessageReceiver; @@ -37,17 +38,12 @@ use raklib\server\ServerSocket; use raklib\server\SimpleProtocolAcceptor; use raklib\utils\ExceptionTraceCleaner; use raklib\utils\InternetAddress; -use function error_get_last; use function gc_enable; use function ini_set; -use function register_shutdown_function; class RakLibServer extends Thread{ - protected bool $cleanShutdown = false; protected bool $ready = false; protected string $mainPath; - /** @phpstan-var NonThreadSafeValue|null */ - public ?NonThreadSafeValue $crashInfo = null; /** @phpstan-var NonThreadSafeValue */ protected NonThreadSafeValue $address; @@ -69,86 +65,51 @@ class RakLibServer extends Thread{ $this->address = new NonThreadSafeValue($address); } - /** - * @return void - */ - public function shutdownHandler(){ - if($this->cleanShutdown !== true && $this->crashInfo === null){ - $error = error_get_last(); - - if($error !== null){ - $this->logger->emergency("Fatal error: " . $error["message"] . " in " . $error["file"] . " on line " . $error["line"]); - $this->setCrashInfo(RakLibThreadCrashInfo::fromLastErrorInfo($error)); - }else{ - $this->logger->emergency("RakLib shutdown unexpectedly"); - } - } - } - - public function getCrashInfo() : ?RakLibThreadCrashInfo{ - return $this->crashInfo?->deserialize(); - } - - private function setCrashInfo(RakLibThreadCrashInfo $info) : void{ - $this->synchronized(function() use ($info) : void{ - $this->crashInfo = new NonThreadSafeValue($info); - $this->notify(); - }); - } - public function startAndWait(int $options = NativeThread::INHERIT_NONE) : void{ $this->start($options); $this->synchronized(function() : void{ - while(!$this->ready && $this->crashInfo === null){ + while(!$this->ready && $this->getCrashInfo() === null){ $this->wait(); } - $crashInfo = $this->crashInfo?->deserialize(); + $crashInfo = $this->getCrashInfo(); if($crashInfo !== null){ - if($crashInfo->getClass() === SocketException::class){ + if($crashInfo->getType() === SocketException::class){ throw new SocketException($crashInfo->getMessage()); } - throw new \RuntimeException("RakLib failed to start: " . $crashInfo->makePrettyMessage()); + throw new ThreadCrashException("RakLib failed to start", $crashInfo); } }); } protected function onRun() : void{ - try{ - gc_enable(); - ini_set("display_errors", '1'); - ini_set("display_startup_errors", '1'); + gc_enable(); + ini_set("display_errors", '1'); + ini_set("display_startup_errors", '1'); - register_shutdown_function([$this, "shutdownHandler"]); - - try{ - $socket = new ServerSocket($this->address->deserialize()); - }catch(SocketException $e){ - $this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e)); - return; - } - $manager = new Server( - $this->serverId, - $this->logger, - $socket, - $this->maxMtuSize, - new SimpleProtocolAcceptor($this->protocolVersion), - new UserToRakLibThreadMessageReceiver(new PthreadsChannelReader($this->mainToThreadBuffer)), - new RakLibToUserThreadMessageSender(new SnoozeAwarePthreadsChannelWriter($this->threadToMainBuffer, $this->sleeperEntry->createNotifier())), - new ExceptionTraceCleaner($this->mainPath) - ); - $this->synchronized(function() : void{ - $this->ready = true; - $this->notify(); - }); - while(!$this->isKilled){ - $manager->tickProcessor(); - } - $manager->waitShutdown(); - $this->cleanShutdown = true; - }catch(\Throwable $e){ - $this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e)); - $this->logger->logException($e); + $socket = new ServerSocket($this->address->deserialize()); + $manager = new Server( + $this->serverId, + $this->logger, + $socket, + $this->maxMtuSize, + new SimpleProtocolAcceptor($this->protocolVersion), + new UserToRakLibThreadMessageReceiver(new PthreadsChannelReader($this->mainToThreadBuffer)), + new RakLibToUserThreadMessageSender(new SnoozeAwarePthreadsChannelWriter($this->threadToMainBuffer, $this->sleeperEntry->createNotifier())), + new ExceptionTraceCleaner($this->mainPath) + ); + $this->synchronized(function() : void{ + $this->ready = true; + $this->notify(); + }); + while(!$this->isKilled){ + $manager->tickProcessor(); } + $manager->waitShutdown(); + } + + protected function onUncaughtException(\Throwable $e) : void{ + parent::onUncaughtException($e); + $this->logger->logException($e); } public function getThreadName() : string{ diff --git a/src/network/mcpe/raklib/RakLibThreadCrashInfo.php b/src/network/mcpe/raklib/RakLibThreadCrashInfo.php deleted file mode 100644 index 60e04b4b4..000000000 --- a/src/network/mcpe/raklib/RakLibThreadCrashInfo.php +++ /dev/null @@ -1,61 +0,0 @@ -getMessage(), $e->getFile(), $e->getLine()); - } - - /** - * @phpstan-param array{message: string, file: string, line: int} $info - */ - public static function fromLastErrorInfo(array $info) : self{ - return new self(null, $info["message"], $info["file"], $info["line"]); - } - - public function getClass() : ?string{ return $this->class; } - - public function getMessage() : string{ return $this->message; } - - public function getFile() : string{ return $this->file; } - - public function getLine() : int{ return $this->line; } - - public function makePrettyMessage() : string{ - return sprintf("%s: \"%s\" in %s on line %d", $this->class ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line); - } -} diff --git a/src/scheduler/AsyncPool.php b/src/scheduler/AsyncPool.php index 7540294ef..bb79df507 100644 --- a/src/scheduler/AsyncPool.php +++ b/src/scheduler/AsyncPool.php @@ -26,6 +26,7 @@ namespace pocketmine\scheduler; use pmmp\thread\Thread as NativeThread; use pocketmine\snooze\SleeperHandler; use pocketmine\thread\log\ThreadSafeLogger; +use pocketmine\thread\ThreadCrashException; use pocketmine\thread\ThreadSafeClassLoader; use pocketmine\timings\Timings; use pocketmine\utils\AssumptionFailedError; @@ -215,12 +216,17 @@ class AsyncPool{ } } } - if($crashedTask !== null){ - $message = "Worker $workerId crashed while running task " . get_class($crashedTask) . "#" . spl_object_id($crashedTask); + $info = $entry->worker->getCrashInfo(); + if($info !== null){ + if($crashedTask !== null){ + $message = "Worker $workerId crashed while running task " . get_class($crashedTask) . "#" . spl_object_id($crashedTask); + }else{ + $message = "Worker $workerId crashed while doing unknown work"; + } + throw new ThreadCrashException($message, $info); }else{ - $message = "Worker $workerId crashed for unknown reason"; + throw new \RuntimeException("Worker $workerId crashed for unknown reason"); } - throw new \RuntimeException($message); } } diff --git a/src/scheduler/AsyncWorker.php b/src/scheduler/AsyncWorker.php index 19a19b102..b26afc29b 100644 --- a/src/scheduler/AsyncWorker.php +++ b/src/scheduler/AsyncWorker.php @@ -31,7 +31,6 @@ use pocketmine\thread\Worker; use pocketmine\utils\AssumptionFailedError; use function gc_enable; use function ini_set; -use function set_exception_handler; class AsyncWorker extends Worker{ /** @var mixed[] */ @@ -68,20 +67,17 @@ class AsyncWorker extends Worker{ } $this->saveToThreadStore(self::TLS_KEY_NOTIFIER, $this->sleeperEntry->createNotifier()); + } - set_exception_handler(function(\Throwable $e){ - $this->logger->logException($e); - }); + protected function onUncaughtException(\Throwable $e) : void{ + parent::onUncaughtException($e); + $this->logger->logException($e); } public function getLogger() : ThreadSafeLogger{ return $this->logger; } - public function handleException(\Throwable $e) : void{ - $this->logger->logException($e); - } - public function getThreadName() : string{ return "AsyncWorker#" . $this->id; } diff --git a/src/thread/CommonThreadPartsTrait.php b/src/thread/CommonThreadPartsTrait.php index c35dd7791..340ce554d 100644 --- a/src/thread/CommonThreadPartsTrait.php +++ b/src/thread/CommonThreadPartsTrait.php @@ -26,7 +26,10 @@ namespace pocketmine\thread; use pmmp\thread\ThreadSafeArray; use pocketmine\errorhandler\ErrorToExceptionHandler; use pocketmine\Server; +use function error_get_last; use function error_reporting; +use function register_shutdown_function; +use function set_exception_handler; trait CommonThreadPartsTrait{ /** @@ -38,6 +41,8 @@ trait CommonThreadPartsTrait{ protected bool $isKilled = false; + private ?ThreadCrashInfo $crashInfo = null; + /** * @return ThreadSafeClassLoader[] */ @@ -88,12 +93,48 @@ trait CommonThreadPartsTrait{ } } + public function getCrashInfo() : ?ThreadCrashInfo{ return $this->crashInfo; } + final public function run() : void{ error_reporting(-1); $this->registerClassLoaders(); //set this after the autoloader is registered ErrorToExceptionHandler::set(); + + //this permits adding extra functionality to the exception and shutdown handlers via overriding + set_exception_handler($this->onUncaughtException(...)); + register_shutdown_function($this->onShutdown(...)); + $this->onRun(); + $this->isKilled = true; + } + + /** + * Called by set_exception_handler() when an uncaught exception is thrown. + */ + protected function onUncaughtException(\Throwable $e) : void{ + $this->synchronized(function() use ($e) : void{ + $this->crashInfo = ThreadCrashInfo::fromThrowable($e, $this->getThreadName()); + }); + } + + /** + * Called by register_shutdown_function() when the thread shuts down. This may be because of a benign shutdown, or + * because of a fatal error. Use isKilled to determine which. + */ + protected function onShutdown() : void{ + $this->synchronized(function() : void{ + if(!$this->isKilled && $this->crashInfo === null){ + $last = error_get_last(); + if($last !== null){ + //fatal error + $this->crashInfo = ThreadCrashInfo::fromLastErrorInfo($last, $this->getThreadName()); + }else{ + //probably misused exit() + $this->crashInfo = ThreadCrashInfo::fromThrowable(new \RuntimeException("Thread crashed without an error - perhaps exit() was called?"), $this->getThreadName()); + } + } + }); } /** diff --git a/src/thread/ThreadCrashException.php b/src/thread/ThreadCrashException.php new file mode 100644 index 000000000..0eba37934 --- /dev/null +++ b/src/thread/ThreadCrashException.php @@ -0,0 +1,38 @@ +crashInfo = $crashInfo; + } + + public function getCrashInfo() : ThreadCrashInfo{ + return $this->crashInfo; + } +} diff --git a/src/thread/ThreadCrashInfo.php b/src/thread/ThreadCrashInfo.php new file mode 100644 index 000000000..66aae927a --- /dev/null +++ b/src/thread/ThreadCrashInfo.php @@ -0,0 +1,89 @@ + */ + private ThreadSafeArray $trace; + + /** + * @param ThreadCrashInfoFrame[] $trace + */ + public function __construct( + private string $type, + private string $message, + private string $file, + private int $line, + array $trace, + private string $threadName + ){ + $this->trace = ThreadSafeArray::fromArray($trace); + } + + public static function fromThrowable(\Throwable $e, string $threadName) : self{ + return new self(get_class($e), $e->getMessage(), $e->getFile(), $e->getLine(), Utils::printableTraceWithMetadata($e->getTrace()), $threadName); + } + + /** + * @phpstan-param array{type: int, message: string, file: string, line: int} $info + */ + public static function fromLastErrorInfo(array $info, string $threadName) : self{ + try{ + $class = ErrorTypeToStringMap::get($info["type"]); + }catch(\InvalidArgumentException){ + $class = "Unknown error type (" . $info["type"] . ")"; + } + return new self($class, $info["message"], $info["file"], $info["line"], Utils::printableTraceWithMetadata(Utils::currentTrace()), $threadName); + } + + public function getType() : string{ return $this->type; } + + public function getMessage() : string{ return $this->message; } + + public function getFile() : string{ return $this->file; } + + public function getLine() : int{ return $this->line; } + + /** + * @return ThreadCrashInfoFrame[] + */ + public function getTrace() : array{ + return (array) $this->trace; + } + + public function getThreadName() : string{ return $this->threadName; } + + public function makePrettyMessage() : string{ + return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line); + } +} diff --git a/src/thread/ThreadCrashInfoFrame.php b/src/thread/ThreadCrashInfoFrame.php new file mode 100644 index 000000000..27f470387 --- /dev/null +++ b/src/thread/ThreadCrashInfoFrame.php @@ -0,0 +1,41 @@ +printableFrame; } + + public function getFile() : ?string{ return $this->file; } + + public function getLine() : int{ return $this->line; } +} diff --git a/src/utils/Utils.php b/src/utils/Utils.php index 11548e193..f5ec5f8e4 100644 --- a/src/utils/Utils.php +++ b/src/utils/Utils.php @@ -31,6 +31,7 @@ use DaveRandom\CallbackValidator\CallbackType; use pocketmine\entity\Location; use pocketmine\errorhandler\ErrorTypeToStringMap; use pocketmine\math\Vector3; +use pocketmine\thread\ThreadCrashInfoFrame; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use function array_combine; @@ -469,6 +470,30 @@ final class Utils{ return $messages; } + /** + * Similar to {@link Utils::printableTrace()}, but associates metadata such as file and line number with each frame. + * This is used to transmit thread-safe information about crash traces to the main thread when a thread crashes. + * + * @param mixed[][] $rawTrace + * @phpstan-param list> $rawTrace + * + * @return ThreadCrashInfoFrame[] + */ + public static function printableTraceWithMetadata(array $rawTrace, int $maxStringLength = 80) : array{ + $printableTrace = self::printableTrace($rawTrace, $maxStringLength); + $safeTrace = []; + foreach($printableTrace as $frameId => $printableFrame){ + $rawFrame = $rawTrace[$frameId]; + $safeTrace[$frameId] = new ThreadCrashInfoFrame( + $printableFrame, + $rawFrame["file"] ?? "unknown", + $rawFrame["line"] ?? 0 + ); + } + + return $safeTrace; + } + /** * @return mixed[][] * @phpstan-return list> diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 27e881153..75118cc93 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -845,6 +845,16 @@ parameters: count: 1 path: ../../../src/utils/Utils.php + - + message: "#^Parameter \\#2 \\$file of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects string\\|null, mixed given\\.$#" + count: 1 + path: ../../../src/utils/Utils.php + + - + message: "#^Parameter \\#3 \\$line of class pocketmine\\\\thread\\\\ThreadCrashInfoFrame constructor expects int, mixed given\\.$#" + count: 1 + path: ../../../src/utils/Utils.php + - message: "#^Parameter \\#1 \\$x of method pocketmine\\\\world\\\\World\\:\\:getTileAt\\(\\) expects int, float\\|int given\\.$#" count: 1 From a45763328bdb6703304ac97dd5164665e5ab180e Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 28 Jul 2023 12:36:46 +0100 Subject: [PATCH 08/21] Added constants for default knockback force and vertical limit --- src/entity/Living.php | 13 ++++++++++++- src/event/entity/EntityDamageByEntityEvent.php | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/entity/Living.php b/src/entity/Living.php index d1fbf4b8e..1b5b582da 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -80,6 +80,17 @@ use const M_PI; abstract class Living extends Entity{ protected const DEFAULT_BREATH_TICKS = 300; + /** + * The default knockback multiplier when an entity is hit by another entity. + * Larger values knock the entity back with increased velocity. + */ + public const DEFAULT_KNOCKBACK_FORCE = 0.4; + /** + * Limit of an entity's vertical knockback velocity when hit by another entity. Without this limit, the entity + * may be knocked far up into the air with large knockback forces. + */ + public const DEFAULT_KNOCKBACK_VERTICAL_LIMIT = 0.4; + private const TAG_LEGACY_HEALTH = "HealF"; //TAG_Float private const TAG_HEALTH = "Health"; //TAG_Float private const TAG_BREATH_TICKS = "Air"; //TAG_Short @@ -567,7 +578,7 @@ abstract class Living extends Entity{ $this->broadcastAnimation(new HurtAnimation($this)); } - public function knockBack(float $x, float $z, float $force = 0.4, ?float $verticalLimit = 0.4) : void{ + public function knockBack(float $x, float $z, float $force = self::DEFAULT_KNOCKBACK_FORCE, ?float $verticalLimit = self::DEFAULT_KNOCKBACK_VERTICAL_LIMIT) : void{ $f = sqrt($x * $x + $z * $z); if($f <= 0){ return; diff --git a/src/event/entity/EntityDamageByEntityEvent.php b/src/event/entity/EntityDamageByEntityEvent.php index 6264375bb..11214a3e0 100644 --- a/src/event/entity/EntityDamageByEntityEvent.php +++ b/src/event/entity/EntityDamageByEntityEvent.php @@ -36,7 +36,7 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{ /** * @param float[] $modifiers */ - public function __construct(Entity $damager, Entity $entity, int $cause, float $damage, array $modifiers = [], private float $knockBack = 0.4){ + public function __construct(Entity $damager, Entity $entity, int $cause, float $damage, array $modifiers = [], private float $knockBack = Living::DEFAULT_KNOCKBACK_FORCE){ $this->damagerEntityId = $damager->getId(); parent::__construct($entity, $cause, $damage, $modifiers); $this->addAttackerModifiers($damager); From c972e657418d29b66f53f42e82e65a47b8a7a10f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 28 Jul 2023 12:41:27 +0100 Subject: [PATCH 09/21] EntityDamageByEntityEvent: document methods --- src/event/entity/EntityDamageByEntityEvent.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/event/entity/EntityDamageByEntityEvent.php b/src/event/entity/EntityDamageByEntityEvent.php index 11214a3e0..b8eb95def 100644 --- a/src/event/entity/EntityDamageByEntityEvent.php +++ b/src/event/entity/EntityDamageByEntityEvent.php @@ -62,10 +62,20 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{ return $this->getEntity()->getWorld()->getServer()->getWorldManager()->findEntity($this->damagerEntityId); } + /** + * Returns the force with which the victim will be knocked back from the attacking entity. + * + * @see Living::DEFAULT_KNOCKBACK_FORCE + */ public function getKnockBack() : float{ return $this->knockBack; } + /** + * Sets the force with which the victim will be knocked back from the attacking entity. + * Larger values will knock the victim back further. + * Negative values will pull the victim towards the attacker. + */ public function setKnockBack(float $knockBack) : void{ $this->knockBack = $knockBack; } From 5ec3f4655fe30a63827570877875b0f50c42901c Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 28 Jul 2023 12:52:15 +0100 Subject: [PATCH 10/21] EntityDamageByEntityEvent: added APIs to get and set vertical knockback limits this was requested and PR'd as far back as 2020 (see #3782). Since no issue was filed about this, it became forgotten until #5946. However, #5946 overcomplicates the solution to the problem, and breaks BC without an obvious reason. --- src/entity/Living.php | 4 +-- .../entity/EntityDamageByEntityEvent.php | 28 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/entity/Living.php b/src/entity/Living.php index 1b5b582da..4d5e10cb3 100644 --- a/src/entity/Living.php +++ b/src/entity/Living.php @@ -557,14 +557,14 @@ abstract class Living extends Entity{ $e = $source->getChild(); if($e !== null){ $motion = $e->getMotion(); - $this->knockBack($motion->x, $motion->z, $source->getKnockBack()); + $this->knockBack($motion->x, $motion->z, $source->getKnockBack(), $source->getVerticalKnockBackLimit()); } }elseif($source instanceof EntityDamageByEntityEvent){ $e = $source->getDamager(); if($e !== null){ $deltaX = $this->location->x - $e->location->x; $deltaZ = $this->location->z - $e->location->z; - $this->knockBack($deltaX, $deltaZ, $source->getKnockBack()); + $this->knockBack($deltaX, $deltaZ, $source->getKnockBack(), $source->getVerticalKnockBackLimit()); } } diff --git a/src/event/entity/EntityDamageByEntityEvent.php b/src/event/entity/EntityDamageByEntityEvent.php index b8eb95def..5ef6c4b8e 100644 --- a/src/event/entity/EntityDamageByEntityEvent.php +++ b/src/event/entity/EntityDamageByEntityEvent.php @@ -36,7 +36,15 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{ /** * @param float[] $modifiers */ - public function __construct(Entity $damager, Entity $entity, int $cause, float $damage, array $modifiers = [], private float $knockBack = Living::DEFAULT_KNOCKBACK_FORCE){ + public function __construct( + Entity $damager, + Entity $entity, + int $cause, + float $damage, + array $modifiers = [], + private float $knockBack = Living::DEFAULT_KNOCKBACK_FORCE, + private float $verticalKnockBackLimit = Living::DEFAULT_KNOCKBACK_VERTICAL_LIMIT + ){ $this->damagerEntityId = $damager->getId(); parent::__construct($entity, $cause, $damage, $modifiers); $this->addAttackerModifiers($damager); @@ -79,4 +87,22 @@ class EntityDamageByEntityEvent extends EntityDamageEvent{ public function setKnockBack(float $knockBack) : void{ $this->knockBack = $knockBack; } + + /** + * Returns the maximum upwards velocity the victim may have after being knocked back. + * This ensures that the victim doesn't fly up into the sky when high levels of knockback are applied. + * + * @see Living::DEFAULT_KNOCKBACK_VERTICAL_LIMIT + */ + public function getVerticalKnockBackLimit() : float{ + return $this->verticalKnockBackLimit; + } + + /** + * Sets the maximum upwards velocity the victim may have after being knocked back. + * Larger values will allow the victim to fly higher if the knockback force is also large. + */ + public function setVerticalKnockBackLimit(float $verticalKnockBackLimit) : void{ + $this->verticalKnockBackLimit = $verticalKnockBackLimit; + } } From 9b2a7b43c2985ba46a759d272dd5f3157a39d7f1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 28 Jul 2023 16:06:29 +0100 Subject: [PATCH 11/21] ItemEntity: fixed O(n^2) performance issue when many of the same unstackable item are in the same place this produced a 40% performance improvement in a simulation with 800 item entities. If the items were all different, then this would still be a problem. However, many of the same unstackable items occupying the same space is a problem for SkyBlock farms, so this should improve performance for SkyBlock quite a bit. --- src/entity/object/ItemEntity.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index 6e9fdcdcf..90eeece67 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -123,7 +123,7 @@ class ItemEntity extends Entity{ } } - if($this->hasMovementUpdate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){ + if($this->hasMovementUpdate() && $this->isMergeCandidate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){ $mergeable = [$this]; //in case the merge target ends up not being this $mergeTarget = $this; foreach($this->getWorld()->getNearbyEntities($this->boundingBox->expandedCopy(0.5, 0.5, 0.5), $this) as $entity){ @@ -165,12 +165,19 @@ class ItemEntity extends Entity{ } } + private function isMergeCandidate() : bool{ + return $this->pickupDelay !== self::NEVER_DESPAWN && $this->item->getCount() < $this->item->getMaxStackSize(); + } + /** * Returns whether this item entity can merge with the given one. */ public function isMergeable(ItemEntity $entity) : bool{ + if(!$this->isMergeCandidate() || !$entity->isMergeCandidate()){ + return false; + } $item = $entity->item; - return $entity !== $this && $entity->pickupDelay !== self::NEVER_DESPAWN && $item->canStackWith($this->item) && $item->getCount() + $this->item->getCount() <= $item->getMaxStackSize(); + return $entity !== $this && $item->canStackWith($this->item) && $item->getCount() + $this->item->getCount() <= $item->getMaxStackSize(); } /** From befd3637f69a40939ff5398e7d2597ff35f7ac7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:13:51 +0100 Subject: [PATCH 12/21] Bump shivammathur/setup-php from 2.25.4 to 2.25.5 (#5951) Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.25.4 to 2.25.5. - [Release notes](https://github.com/shivammathur/setup-php/releases) - [Commits](https://github.com/shivammathur/setup-php/compare/2.25.4...2.25.5) --- updated-dependencies: - dependency-name: shivammathur/setup-php dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/discord-release-notify.yml | 2 +- .github/workflows/draft-release.yml | 2 +- .github/workflows/main.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/discord-release-notify.yml b/.github/workflows/discord-release-notify.yml index 5275c445b..a8282f542 100644 --- a/.github/workflows/discord-release-notify.yml +++ b/.github/workflows/discord-release-notify.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.25.4 + uses: shivammathur/setup-php@2.25.5 with: php-version: 8.1 diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml index b5376f3ba..5b76a9d05 100644 --- a/.github/workflows/draft-release.yml +++ b/.github/workflows/draft-release.yml @@ -18,7 +18,7 @@ jobs: submodules: true - name: Setup PHP - uses: shivammathur/setup-php@2.25.4 + uses: shivammathur/setup-php@2.25.5 with: php-version: 8.1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 60e513d80..022e0ab3a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -173,7 +173,7 @@ jobs: - uses: actions/checkout@v3 - name: Setup PHP and tools - uses: shivammathur/setup-php@2.25.4 + uses: shivammathur/setup-php@2.25.5 with: php-version: 8.1 tools: php-cs-fixer:3.17 From eb53b795d53164c6a29a98af2726835317005df1 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Fri, 28 Jul 2023 16:06:29 +0100 Subject: [PATCH 13/21] ItemEntity: fixed O(n^2) performance issue when many of the same unstackable item are in the same place this produced a 40% performance improvement in a simulation with 800 item entities. If the items were all different, then this would still be a problem. However, many of the same unstackable items occupying the same space is a problem for SkyBlock farms, so this should improve performance for SkyBlock quite a bit. --- src/entity/object/ItemEntity.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/entity/object/ItemEntity.php b/src/entity/object/ItemEntity.php index d28d70773..233b54112 100644 --- a/src/entity/object/ItemEntity.php +++ b/src/entity/object/ItemEntity.php @@ -131,7 +131,7 @@ class ItemEntity extends Entity{ } } - if($this->hasMovementUpdate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){ + if($this->hasMovementUpdate() && $this->isMergeCandidate() && $this->despawnDelay % self::MERGE_CHECK_PERIOD === 0){ $mergeable = [$this]; //in case the merge target ends up not being this $mergeTarget = $this; foreach($this->getWorld()->getNearbyEntities($this->boundingBox->expandedCopy(0.5, 0.5, 0.5), $this) as $entity){ @@ -173,12 +173,19 @@ class ItemEntity extends Entity{ } } + private function isMergeCandidate() : bool{ + return $this->pickupDelay !== self::NEVER_DESPAWN && $this->item->getCount() < $this->item->getMaxStackSize(); + } + /** * Returns whether this item entity can merge with the given one. */ public function isMergeable(ItemEntity $entity) : bool{ + if(!$this->isMergeCandidate() || !$entity->isMergeCandidate()){ + return false; + } $item = $entity->item; - return $entity !== $this && $entity->pickupDelay !== self::NEVER_DESPAWN && $item->canStackWith($this->item) && $item->getCount() + $this->item->getCount() <= $item->getMaxStackSize(); + return $entity !== $this && $item->canStackWith($this->item) && $item->getCount() + $this->item->getCount() <= $item->getMaxStackSize(); } /** From 49a9da147bc99db0a00f8d8551e941fc02ff0fe8 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 1 Aug 2023 11:12:41 +0100 Subject: [PATCH 14/21] Release 4.23.4 --- changelogs/4.23.md | 6 ++++++ src/VersionInfo.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/changelogs/4.23.md b/changelogs/4.23.md index 6cd261ba1..031eaa1cc 100644 --- a/changelogs/4.23.md +++ b/changelogs/4.23.md @@ -43,3 +43,9 @@ Released 24th July 2023. - Player emotes no longer broadcast messages to other players. This was unintended behaviour caused by a client-side behavioural change. - Shulker boxes no longer support the placement of torches or other similar blocks. - Fire can now be placed on upper slabs and the top of upside-down stairs. + +# 4.23.4 +Released 1st August 2023. + +## Fixes +- Fixed exponentially increasing lag when many hundreds of non-mergeable dropped items occupied the same space. This disproportionately affected SkyBlock servers due to large cactus farms using water to collect items together. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 490300ff5..458707c83 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "4.23.4"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; private function __construct(){ From e9e5923639ab578d691980126cf093627681affe Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 1 Aug 2023 11:12:52 +0100 Subject: [PATCH 15/21] 4.23.5 is next --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 458707c83..3109b3b1b 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "4.23.4"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "4.23.5"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; private function __construct(){ From cd8219d9fd6d5385a6e3f3791ceff99fa8fccc1f Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 1 Aug 2023 11:16:50 +0100 Subject: [PATCH 16/21] Release 5.3.4 --- changelogs/5.3.md | 9 +++++++++ src/VersionInfo.php | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/changelogs/5.3.md b/changelogs/5.3.md index 173e73cb8..7d789165d 100644 --- a/changelogs/5.3.md +++ b/changelogs/5.3.md @@ -70,3 +70,12 @@ Released 24th July 2023. - Fixed netherite items being destroyed by lava. - Fireproof entities no longer display the burning animation when in fire or lava. This does not apply to creative players, who are immortal rather than being fireproof. - Fixed frosted ice melting in certain conditions which didn't match vanilla Bedrock. + +# 5.3.4 +Released 1st August 2023. + +## Included releases +This release includes changes from the following releases: +- [4.23.4](https://github.com/pmmp/PocketMine-MP/blob/4.23.4/changelogs/4.23.md#4234) - Item entity lag fix + +This release contains no other significant changes. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 578f9de2d..38fc2e0e4 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -32,7 +32,7 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; public const BASE_VERSION = "5.3.4"; - public const IS_DEVELOPMENT_BUILD = true; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 774eb3e72b5bcf9872f8325f976d4a0bc2df73cc Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 1 Aug 2023 11:16:51 +0100 Subject: [PATCH 17/21] 5.3.5 is next --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 38fc2e0e4..9c7b0f4f1 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.3.4"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.3.5"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /** From 46f24b165af164cc834c8e96d43d1052c4c198df Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 1 Aug 2023 12:21:39 +0100 Subject: [PATCH 18/21] Rename PlayerMissedSwingEvent -> PlayerMissSwingEvent all the other events are present tense, so it doesn't make sense for this one to be past tense. --- .../{PlayerMissedSwingEvent.php => PlayerMissSwingEvent.php} | 2 +- src/player/Player.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/event/player/{PlayerMissedSwingEvent.php => PlayerMissSwingEvent.php} (93%) diff --git a/src/event/player/PlayerMissedSwingEvent.php b/src/event/player/PlayerMissSwingEvent.php similarity index 93% rename from src/event/player/PlayerMissedSwingEvent.php rename to src/event/player/PlayerMissSwingEvent.php index 3157f62d1..2f7ffda9c 100644 --- a/src/event/player/PlayerMissedSwingEvent.php +++ b/src/event/player/PlayerMissSwingEvent.php @@ -30,7 +30,7 @@ use pocketmine\player\Player; /** * Called when a player attempts to perform the attack action (left-click) without a target entity. */ -class PlayerMissedSwingEvent extends PlayerEvent implements Cancellable{ +class PlayerMissSwingEvent extends PlayerEvent implements Cancellable{ use CancellableTrait; public function __construct(Player $player){ diff --git a/src/player/Player.php b/src/player/Player.php index 292ab98b3..f62485242 100644 --- a/src/player/Player.php +++ b/src/player/Player.php @@ -66,7 +66,7 @@ use pocketmine\event\player\PlayerItemUseEvent; use pocketmine\event\player\PlayerJoinEvent; use pocketmine\event\player\PlayerJumpEvent; use pocketmine\event\player\PlayerKickEvent; -use pocketmine\event\player\PlayerMissedSwingEvent; +use pocketmine\event\player\PlayerMissSwingEvent; use pocketmine\event\player\PlayerMoveEvent; use pocketmine\event\player\PlayerPostChunkSendEvent; use pocketmine\event\player\PlayerQuitEvent; @@ -1900,7 +1900,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{ * Under normal circumstances, this will just play the no-damage attack sound and the arm-swing animation. */ public function missSwing() : void{ - $ev = new PlayerMissedSwingEvent($this); + $ev = new PlayerMissSwingEvent($this); $ev->call(); if(!$ev->isCancelled()){ $this->broadcastSound(new EntityAttackNoDamageSound()); From 0a90a5928a86552003a3dcbceb2c019b0d2ccaa8 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 1 Aug 2023 12:33:36 +0100 Subject: [PATCH 19/21] Added TallGrassTrait, remove weirdly specific logic from FortuneDropHelper this needs to be dealt with before release otherwise we'll be stuck with FortuneDropHelper::grass() this is the obvious solution and should have been done some time ago - stuff like flammability was already a problem for double tall grass anyway --- src/block/DoubleTallGrass.php | 7 +++- src/block/TallGrass.php | 19 +--------- src/block/utils/FortuneDropHelper.php | 21 ----------- src/block/utils/TallGrassTrait.php | 54 +++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 src/block/utils/TallGrassTrait.php diff --git a/src/block/DoubleTallGrass.php b/src/block/DoubleTallGrass.php index e90f2ec61..42a6fb4dc 100644 --- a/src/block/DoubleTallGrass.php +++ b/src/block/DoubleTallGrass.php @@ -23,10 +23,13 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\utils\FortuneDropHelper; +use pocketmine\block\utils\TallGrassTrait; use pocketmine\item\Item; class DoubleTallGrass extends DoublePlant{ + use TallGrassTrait { + getDropsForIncompatibleTool as traitGetDropsForIncompatibleTool; + } public function canBeReplaced() : bool{ return true; @@ -34,7 +37,7 @@ class DoubleTallGrass extends DoublePlant{ public function getDropsForIncompatibleTool(Item $item) : array{ if($this->top){ - return FortuneDropHelper::grass($item); + return $this->traitGetDropsForIncompatibleTool($item); } return []; } diff --git a/src/block/TallGrass.php b/src/block/TallGrass.php index 019b911bc..d8c34b001 100644 --- a/src/block/TallGrass.php +++ b/src/block/TallGrass.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace pocketmine\block; -use pocketmine\block\utils\FortuneDropHelper; +use pocketmine\block\utils\TallGrassTrait; use pocketmine\item\Item; use pocketmine\math\Facing; use pocketmine\math\Vector3; @@ -31,10 +31,7 @@ use pocketmine\player\Player; use pocketmine\world\BlockTransaction; class TallGrass extends Flowable{ - - public function canBeReplaced() : bool{ - return true; - } + use TallGrassTrait; private function canBeSupportedBy(Block $block) : bool{ return $block->hasTypeTag(BlockTypeTags::DIRT) || $block->hasTypeTag(BlockTypeTags::MUD); @@ -53,16 +50,4 @@ class TallGrass extends Flowable{ $this->position->getWorld()->useBreakOn($this->position); } } - - public function getDropsForIncompatibleTool(Item $item) : array{ - return FortuneDropHelper::grass($item); - } - - public function getFlameEncouragement() : int{ - return 60; - } - - public function getFlammability() : int{ - return 100; - } } diff --git a/src/block/utils/FortuneDropHelper.php b/src/block/utils/FortuneDropHelper.php index 4bce36138..4cf9b0249 100644 --- a/src/block/utils/FortuneDropHelper.php +++ b/src/block/utils/FortuneDropHelper.php @@ -25,7 +25,6 @@ namespace pocketmine\block\utils; use pocketmine\item\enchantment\VanillaEnchantments; use pocketmine\item\Item; -use pocketmine\item\VanillaItems; use function max; use function min; use function mt_getrandmax; @@ -85,26 +84,6 @@ final class FortuneDropHelper{ return $count; } - /** - * Grass have a fixed chance to drop wheat seed. - * Fortune level increases the maximum number of seeds that can be dropped. - * A discrete uniform distribution is used to determine the number of seeds dropped. - * - * TODO: I'm not sure this really belongs here, but it's preferable not to duplicate this code between grass and - * tall grass. - * - * @return Item[] - */ - public static function grass(Item $usedItem) : array{ - if(FortuneDropHelper::bonusChanceDivisor($usedItem, 8, 2)){ - return [ - VanillaItems::WHEAT_SEEDS() - ]; - } - - return []; - } - /** * Adds the fortune level to the base max and picks a random number between the minimim and adjusted maximum. * Each amount in the range has an equal chance of being picked. diff --git a/src/block/utils/TallGrassTrait.php b/src/block/utils/TallGrassTrait.php new file mode 100644 index 000000000..88fc36e12 --- /dev/null +++ b/src/block/utils/TallGrassTrait.php @@ -0,0 +1,54 @@ + Date: Tue, 1 Aug 2023 12:46:53 +0100 Subject: [PATCH 20/21] Release 5.4.0 --- changelogs/5.4.md | 83 +++++++++++++++++++++++++++++++++++++++++++++ src/VersionInfo.php | 4 +-- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 changelogs/5.4.md diff --git a/changelogs/5.4.md b/changelogs/5.4.md new file mode 100644 index 000000000..a40fdfcea --- /dev/null +++ b/changelogs/5.4.md @@ -0,0 +1,83 @@ +# 5.4.0 +Released 1st August 2023. + +**For Minecraft: Bedrock Edition 1.20.10** + +This is a minor feature update, including a handful of new gameplay features, new plugin APIs and improvements to error reporting. + +**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace. +Do not update plugin minimum API versions unless you need new features added in this release. + +**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.** +Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly. + +## General +- Improved error reporting for async task and thread crashes. +- Players may now have different creative inventories. + +## Gameplay +### General +- Added support for 1.5-block height sneaking. +- Fixed missing player arm swing and sounds when punching the air. + +### Blocks +- Implemented the following new blocks: + - Big Dripleaf Head + - Big Dripleaf Stem + - Small Dripleaf +- Acacia saplings now grow into acacia trees. +- Fixed melon and pumpkin stems not attaching to the correct block when growing. +- Various blocks now drop more items when mined with a compatible tool enchanted with Fortune. + +### Items +- Implemented Strong Slowness potion. +- Implemented Fortune enchantment. + +## API +### `pocketmine\block` +- The following new classes have been added: + - `utils\FortuneDropHelper` - utility methods for calculating the drop counts for Fortune-affected blocks +- The following new API methods have been added: + - `protected Block->getAdjacentSupportType(int $facing) : utils\SupportType` - returns the type of support provided by the block in the given direction on the adjacent face + +### `pocketmine\entity` +- The following new API constants have been added: + - `Living::DEFAULT_KNOCKBACK_FORCE` + - `Living::DEFAULT_KNOCKBACK_VERTICAL_LIMIT` + +### `pocketmine\entity\animation` +- `ConsumingItemAnimation` now accepts `Living` instances instead of just `Human`. + +### `pocketmine\event` +- The following new classes have been added: + - `PlayerMissSwingEvent` - called when the player attempts the attack action (left click on desktop) without any target + - This is possible thanks to the introduction of new flags in `PlayerAuthInputPacket` in Bedrock 1.20.10 +- The following new API methods have been added: + - `public EntityDamageByEntityEvent->getVerticalKnockBackLimit() : float` + - `public EntityDamageByEntityEvent->setVerticalKnockBackLimit(float $verticalKnockBackLimit) : void` - sets the max vertical velocity that can result from the victim being knocked back + +### `pocketmine\player` +- The following new API methods have been added: + - `public Player->getCreativeInventory() : pocketmine\inventory\CreativeInventory` + - `public Player->setCreativeInventory(pocketmine\inventory\CreativeInventory $inventory) : void` + - `public Player->missSwing() : void` - performs actions associated with the attack action when there is no target (see `PlayerMissSwingEvent`) + +### `pocketmine\scheduler` +- Cancellation functionality has been removed from `AsyncTask`, as it didn't make any sense and wasn't used by anything for what it was intended for. + - It broke sequential task execution - later tasks might depend on state from earlier tasks + - It didn't actually cancel the task anyway - at best, it prevented it from running, but couldn't interrupt it (though interrupting a task does not make sense either) +- The following API methods have been deprecated, and their functionality has been removed: + - `AsyncTask->hasCancelledRun()` + - `AsyncTask->cancelRun()` + +## Internals +- Uncaught exceptions and fatal errors in `AsyncTask`, threads extending `pocketmine\thread\Thread`, and `pocketmine\thread\Worker` are now recorded in crashdumps, making it significantly easier to debug errors in these areas. +- JWT signature DER <-> raw conversions are now handled in-house using code in `JwtUtils` + - Due to the simplicity of the conversion and only requiring a tiny subset of the ASN.1 spec, it didn't make much sense to introduce another dependency. + - `fgrosse/phpasn1` is no longer required. This package was abandoned by its author and only used by PocketMine-MP for this one purpose. +- Various usages of `Closure::fromCallable()` have been replaced by PHP 8.1 first-class callable syntax. +- Blocks requiring support shifted to a "can be supported at" model, rather than "can be supported by". + - This model reduces repeated logic when placing and performing nearby block updates (no need to hardcode facing everywhere). + - In addition, this change facilitates the use of the newly introduced `Block->getAdjacentSupportType()` API method, reducing boilerplate support-type checking code. +- Bell block code has been simplified and cleaned up. +- `TallGrass` and `DoubleTallGrass` now use a shared `TallGrassTrait` to reduce code duplication. diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 9c7b0f4f1..1ad9f2bf7 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.3.5"; - public const IS_DEVELOPMENT_BUILD = true; + public const BASE_VERSION = "5.4.0"; + public const IS_DEVELOPMENT_BUILD = false; public const BUILD_CHANNEL = "stable"; /** From 53de55dcde472eb293fb853574f1b4a74120a3bd Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Tue, 1 Aug 2023 12:46:56 +0100 Subject: [PATCH 21/21] 5.4.1 is next --- src/VersionInfo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VersionInfo.php b/src/VersionInfo.php index 1ad9f2bf7..0b40f80e4 100644 --- a/src/VersionInfo.php +++ b/src/VersionInfo.php @@ -31,8 +31,8 @@ use function str_repeat; final class VersionInfo{ public const NAME = "PocketMine-MP"; - public const BASE_VERSION = "5.4.0"; - public const IS_DEVELOPMENT_BUILD = false; + public const BASE_VERSION = "5.4.1"; + public const IS_DEVELOPMENT_BUILD = true; public const BUILD_CHANNEL = "stable"; /**