mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-12 20:35:11 +00:00
Compare commits
24 Commits
5.0.0-ALPH
...
5.0.0-ALPH
Author | SHA1 | Date | |
---|---|---|---|
5aa8b953a8 | |||
375ec8e00c | |||
002f7d6826 | |||
06ad1a2d2b | |||
222415859a | |||
14b250c63f | |||
644881372d | |||
a12aac71fd | |||
f948cb0086 | |||
07a30ea1f9 | |||
6c52723d97 | |||
d5b7bf77b0 | |||
b1a5b02d3a | |||
74e052de51 | |||
a5397d55fe | |||
65ef929d22 | |||
441919c5e3 | |||
448aeec780 | |||
b8f6b66e42 | |||
365cce9d0c | |||
78aea5c34c | |||
d7f40f75d2 | |||
41e60cb62c | |||
5d2ac214a8 |
25
.github/workflows/main.yml
vendored
25
.github/workflows/main.yml
vendored
@ -13,14 +13,15 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.23, 8.1.10]
|
||||
php: [8.0.27, 8.1.14, 8.2.1]
|
||||
|
||||
steps:
|
||||
- name: Build and prepare PHP cache
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
pm-version-major: "5"
|
||||
|
||||
phpstan:
|
||||
name: PHPStan analysis
|
||||
@ -31,16 +32,17 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.23, 8.1.10]
|
||||
php: [8.0.27, 8.1.14, 8.2.1]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
pm-version-major: "5"
|
||||
|
||||
- name: Install Composer
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
@ -69,16 +71,17 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.23, 8.1.10]
|
||||
php: [8.0.27, 8.1.14, 8.2.1]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
pm-version-major: "5"
|
||||
|
||||
- name: Install Composer
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
@ -107,7 +110,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.23, 8.1.10]
|
||||
php: [8.0.27, 8.1.14, 8.2.1]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -115,10 +118,11 @@ jobs:
|
||||
submodules: true
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
pm-version-major: "5"
|
||||
|
||||
- name: Install Composer
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
@ -147,16 +151,17 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image: [ubuntu-20.04]
|
||||
php: [8.0.23, 8.1.10]
|
||||
php: [8.0.27, 8.1.14, 8.2.1]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup PHP
|
||||
uses: pmmp/setup-php-action@82a44d659bf5046612c69f036af3e14dc32e3fa8
|
||||
uses: pmmp/setup-php-action@6dd74c145250109942b08fc63cd5118edd2fd256
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
install-path: "./bin"
|
||||
pm-version-major: "5"
|
||||
|
||||
- name: Install Composer
|
||||
run: curl -sS https://getcomposer.org/installer | php
|
||||
|
3
.github/workflows/update-php-versions.php
vendored
3
.github/workflows/update-php-versions.php
vendored
@ -23,7 +23,8 @@ declare(strict_types=1);
|
||||
|
||||
const VERSIONS = [
|
||||
"8.0",
|
||||
"8.1"
|
||||
"8.1",
|
||||
"8.2"
|
||||
];
|
||||
|
||||
$workflowFile = file_get_contents(__DIR__ . '/main.yml');
|
||||
|
Submodule build/php updated: 6b605ed7c4...b479ec438f
@ -869,3 +869,43 @@ Released 18th January 2023.
|
||||
- Built-in commands now declare their names inside the class constructor, rather than accepting them as parameters. This improves code consistency.
|
||||
- `NetworkSession` disconnect APIs now accept `Translatable|string` instead of `string` to allow localized disconnect messages.
|
||||
- All external usages of `KnownTranslationKeys` are now removed. All localized messages are now sent using `Translatable` objects (usually from `KnownTranslationFactory`).
|
||||
|
||||
# 5.0.0-ALPHA8
|
||||
Released 23rd January 2023.
|
||||
|
||||
## Core
|
||||
- Updated `ext-pthreads` requirement to `^5.1.0`. This version improves performance, memory usage, includes BC-breaking API changes, and removes a lot of confusing behaviour.
|
||||
- See [`ext-pthreads` 5.0.0 release](https://github.com/pmmp/pthreads/releases/tag/5.0.0) for more information.
|
||||
- For the most part, plugins will be unaffected, unless using `Threaded` objects directly, or directly interacting with other pthreads APIs.
|
||||
|
||||
## API
|
||||
### Overview
|
||||
- It's now possible to specify a different disconnect reason and disconnection screen message. This is useful if you want to display a fancy disconnect screen, but don't want to spam the server log with useless information.
|
||||
|
||||
### `pocketmine\event\player`
|
||||
- The following API methods have been removed:
|
||||
- `PlayerKickEvent->getReason()` - replaced by `getDisconnectReason()` and `getDisconnectScreenMessage()`
|
||||
- `PlayerKickEvent->setReason()` - replaced by `setDisconnectReason()` and `setDisconnectScreenMessage()`
|
||||
- `PlayerDuplicateLoginEvent->getDisconnectMessage()` - replaced by `getDisconnectReason()` and `getDisconnectScreenMessage()`
|
||||
- `PlayerDuplicateLoginEvent->setDisconnectMessage()` - replaced by `setDisconnectReason()` and `setDisconnectScreenMessage()`
|
||||
- The following new API methods have been added:
|
||||
- `public PlayerKickEvent->getDisconnectReason() : Translatable|string` - returns the reason for the disconnection displayed in the console and server log
|
||||
- `public PlayerKickEvent->setDisconnectReason(Translatable|string $disconnectReason) : void` - sets the reason for the disconnection displayed in the console and server log
|
||||
- `public PlayerKickEvent->getDisconnectScreenMessage() : Translatable|string|null` - returns the message to be displayed on the disconnect screen (the message in `getDisconnectReason()` is used if null is returned)
|
||||
- `public PlayerKickEvent->setDisconnectScreenMessage(Translatable|string|null $disconnectScreenMessage) : void` - sets the message to be displayed on the disconnect screen (the message in `setDisconnectReason()` is used if null is passed)
|
||||
- `public PlayerDuplicateLoginEvent->getDisconnectReason() : Translatable|string` - returns the reason for the disconnection displayed in the console and server log
|
||||
- `public PlayerDuplicateLoginEvent->setDisconnectReason(Translatable|string $disconnectReason) : void` - sets the reason for the disconnection displayed in the console and server log
|
||||
- `public PlayerDuplicateLoginEvent->getDisconnectScreenMessage() : Translatable|string|null` - returns the message to be displayed on the disconnect screen (the message in `getDisconnectReason()` is used if null is returned)
|
||||
- `public PlayerDuplicateLoginEvent->setDisconnectScreenMessage(Translatable|string|null $disconnectScreenMessage) : void` - sets the message to be displayed on the disconnect screen (the message in `setDisconnectReason()` is used if null is passed)
|
||||
|
||||
### `pocketmine\network`
|
||||
- The following API methods have changed signatures:
|
||||
- `NetworkSessionManager->close()` now accepts an additional `Translatable|string|null $disconnectScreenMessage` parameter.
|
||||
|
||||
### `pocketmine\player`
|
||||
- The following API methods have changed signatures:
|
||||
- `Player->kick()` now accepts an additional `Translatable|string|null $disconnectScreenMessage` parameter, which is the message to be displayed on the disconnect screen (the message in `$reason` is used if null is passed)
|
||||
- `Player->disconnect()` now accepts an additional `Translatable|string|null $disconnectScreenMessage` parameter, which is the message to be displayed on the disconnect screen (the message in `$reason` is used if null is passed)
|
||||
|
||||
## Internals
|
||||
- `NetworkSession` disconnect methods have been altered to allow specifying a different disconnect reason and disconnection screen message.
|
@ -22,7 +22,7 @@
|
||||
"ext-openssl": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-phar": "*",
|
||||
"ext-pthreads": "^4.0",
|
||||
"ext-pthreads": "^5.1",
|
||||
"ext-reflection": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-sockets": "*",
|
||||
@ -40,22 +40,22 @@
|
||||
"pocketmine/bedrock-protocol": "~18.0.0+bedrock-1.19.50",
|
||||
"pocketmine/binaryutils": "^0.2.1",
|
||||
"pocketmine/callback-validator": "^1.0.2",
|
||||
"pocketmine/classloader": "^0.2.0",
|
||||
"pocketmine/classloader": "^0.3.0",
|
||||
"pocketmine/color": "^0.3.0",
|
||||
"pocketmine/errorhandler": "^0.6.0",
|
||||
"pocketmine/locale-data": "~2.18.0",
|
||||
"pocketmine/log": "^0.4.0",
|
||||
"pocketmine/log-pthreads": "^0.4.0",
|
||||
"pocketmine/log-pthreads": "^0.5.0",
|
||||
"pocketmine/math": "^0.4.0",
|
||||
"pocketmine/nbt": "^0.3.2",
|
||||
"pocketmine/raklib": "^0.14.2",
|
||||
"pocketmine/raklib-ipc": "^0.1.0",
|
||||
"pocketmine/snooze": "^0.3.0",
|
||||
"pocketmine/snooze": "^0.4.0",
|
||||
"ramsey/uuid": "^4.1",
|
||||
"symfony/filesystem": "^5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.9.12",
|
||||
"phpstan/phpstan": "1.9.13",
|
||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||
"phpunit/phpunit": "^9.2"
|
||||
|
98
composer.lock
generated
98
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "d6cafa9f9e236ad8d55c2eebdb51416a",
|
||||
"content-hash": "17ca826af1a32c9768cd162e75d10ad5",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/json-comment",
|
||||
@ -469,20 +469,20 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/classloader",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/ClassLoader.git",
|
||||
"reference": "49ea303993efdfb39cd302e2156d50aa78209e78"
|
||||
"reference": "407caf521186ec1f03024f39031cc681ad491026"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/ClassLoader/zipball/49ea303993efdfb39cd302e2156d50aa78209e78",
|
||||
"reference": "49ea303993efdfb39cd302e2156d50aa78209e78",
|
||||
"url": "https://api.github.com/repos/pmmp/ClassLoader/zipball/407caf521186ec1f03024f39031cc681ad491026",
|
||||
"reference": "407caf521186ec1f03024f39031cc681ad491026",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pthreads": "~3.2.0 || ^4.0",
|
||||
"ext-pthreads": "^5.0",
|
||||
"ext-reflection": "*",
|
||||
"php": "^8.0"
|
||||
},
|
||||
@ -491,8 +491,8 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "0.12.99",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.4",
|
||||
"phpstan/phpstan": "1.9.4",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"type": "library",
|
||||
@ -508,9 +508,9 @@
|
||||
"description": "Ad-hoc autoloading components used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/ClassLoader/issues",
|
||||
"source": "https://github.com/pmmp/ClassLoader/tree/0.2.0"
|
||||
"source": "https://github.com/pmmp/ClassLoader/tree/0.3.0"
|
||||
},
|
||||
"time": "2021-11-01T20:17:27+00:00"
|
||||
"time": "2023-01-23T19:46:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/color",
|
||||
@ -591,16 +591,16 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/locale-data",
|
||||
"version": "2.18.0",
|
||||
"version": "2.18.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Language.git",
|
||||
"reference": "0f50afc3d0fec29f769a62e93c71f8a0fb968f76"
|
||||
"reference": "da25bfe9ee4822a84feb9b7e620c56ad4000aed0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/0f50afc3d0fec29f769a62e93c71f8a0fb968f76",
|
||||
"reference": "0f50afc3d0fec29f769a62e93c71f8a0fb968f76",
|
||||
"url": "https://api.github.com/repos/pmmp/Language/zipball/da25bfe9ee4822a84feb9b7e620c56ad4000aed0",
|
||||
"reference": "da25bfe9ee4822a84feb9b7e620c56ad4000aed0",
|
||||
"shasum": ""
|
||||
},
|
||||
"type": "library",
|
||||
@ -608,9 +608,9 @@
|
||||
"description": "Language resources used by PocketMine-MP",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Language/issues",
|
||||
"source": "https://github.com/pmmp/Language/tree/2.18.0"
|
||||
"source": "https://github.com/pmmp/Language/tree/2.18.3"
|
||||
},
|
||||
"time": "2023-01-14T17:52:46+00:00"
|
||||
"time": "2023-01-17T21:43:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log",
|
||||
@ -655,21 +655,21 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/log-pthreads",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/LogPthreads.git",
|
||||
"reference": "61f709e8cf36bcc24e4efe02acded680a1ce23cd"
|
||||
"reference": "0ecfea6dcfc9a9f5c86e126ac1661732de5c5666"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/LogPthreads/zipball/61f709e8cf36bcc24e4efe02acded680a1ce23cd",
|
||||
"reference": "61f709e8cf36bcc24e4efe02acded680a1ce23cd",
|
||||
"url": "https://api.github.com/repos/pmmp/LogPthreads/zipball/0ecfea6dcfc9a9f5c86e126ac1661732de5c5666",
|
||||
"reference": "0ecfea6dcfc9a9f5c86e126ac1661732de5c5666",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pthreads": "~3.2.0 || ^4.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"ext-pthreads": "^5.0",
|
||||
"php": "^8.0",
|
||||
"pocketmine/log": "^0.4.0"
|
||||
},
|
||||
"conflict": {
|
||||
@ -677,8 +677,8 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "0.12.88",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.4"
|
||||
"phpstan/phpstan": "1.8.11",
|
||||
"phpstan/phpstan-strict-rules": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -693,9 +693,9 @@
|
||||
"description": "Logging components specialized for pthreads used by PocketMine-MP and related projects",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/LogPthreads/issues",
|
||||
"source": "https://github.com/pmmp/LogPthreads/tree/0.4.0"
|
||||
"source": "https://github.com/pmmp/LogPthreads/tree/0.5.0"
|
||||
},
|
||||
"time": "2021-11-01T21:42:09+00:00"
|
||||
"time": "2023-01-23T19:52:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/math",
|
||||
@ -866,26 +866,26 @@
|
||||
},
|
||||
{
|
||||
"name": "pocketmine/snooze",
|
||||
"version": "0.3.1",
|
||||
"version": "0.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pmmp/Snooze.git",
|
||||
"reference": "0ac8fc2a781c419a1f64ebca4d5835028f59e29b"
|
||||
"reference": "6b1d6cc645d674590ff9be2438ac00032f9ee292"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pmmp/Snooze/zipball/0ac8fc2a781c419a1f64ebca4d5835028f59e29b",
|
||||
"reference": "0ac8fc2a781c419a1f64ebca4d5835028f59e29b",
|
||||
"url": "https://api.github.com/repos/pmmp/Snooze/zipball/6b1d6cc645d674590ff9be2438ac00032f9ee292",
|
||||
"reference": "6b1d6cc645d674590ff9be2438ac00032f9ee292",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pthreads": "~3.2.0 || ^4.0",
|
||||
"php-64bit": "^7.3 || ^8.0"
|
||||
"ext-pthreads": "^5.0",
|
||||
"php-64bit": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "0.12.99",
|
||||
"phpstan/phpstan-strict-rules": "^0.12.4"
|
||||
"phpstan/phpstan": "1.9.14",
|
||||
"phpstan/phpstan-strict-rules": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@ -900,9 +900,9 @@
|
||||
"description": "Thread notification management library for code using the pthreads extension",
|
||||
"support": {
|
||||
"issues": "https://github.com/pmmp/Snooze/issues",
|
||||
"source": "https://github.com/pmmp/Snooze/tree/0.3.1"
|
||||
"source": "https://github.com/pmmp/Snooze/tree/0.4.0"
|
||||
},
|
||||
"time": "2021-11-01T20:50:08+00:00"
|
||||
"time": "2023-01-23T19:43:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ramsey/collection",
|
||||
@ -1610,16 +1610,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nikic/php-parser",
|
||||
"version": "v4.15.2",
|
||||
"version": "v4.15.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
|
||||
"reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
|
||||
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
|
||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039",
|
||||
"reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1660,9 +1660,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
|
||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3"
|
||||
},
|
||||
"time": "2022-11-12T15:38:23+00:00"
|
||||
"time": "2023-01-16T22:05:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phar-io/manifest",
|
||||
@ -1777,16 +1777,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.9.12",
|
||||
"version": "1.9.13",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "44a338ff0d5572c13fd77dfd91addb96e48c29f8"
|
||||
"reference": "a0922426da3a7d0d9334e99a363f7f9f6e23e84f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/44a338ff0d5572c13fd77dfd91addb96e48c29f8",
|
||||
"reference": "44a338ff0d5572c13fd77dfd91addb96e48c29f8",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a0922426da3a7d0d9334e99a363f7f9f6e23e84f",
|
||||
"reference": "a0922426da3a7d0d9334e99a363f7f9f6e23e84f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1816,7 +1816,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||
"source": "https://github.com/phpstan/phpstan/tree/1.9.12"
|
||||
"source": "https://github.com/phpstan/phpstan/tree/1.9.13"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -1832,7 +1832,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-17T10:44:04+00:00"
|
||||
"time": "2023-01-18T15:26:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan-phpunit",
|
||||
@ -3396,7 +3396,7 @@
|
||||
"ext-openssl": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-phar": "*",
|
||||
"ext-pthreads": "^4.0",
|
||||
"ext-pthreads": "^5.1",
|
||||
"ext-reflection": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-sockets": "*",
|
||||
|
@ -122,8 +122,8 @@ namespace pocketmine {
|
||||
if(substr_count($pthreads_version, ".") < 2){
|
||||
$pthreads_version = "0.$pthreads_version";
|
||||
}
|
||||
if(version_compare($pthreads_version, "4.0.0") < 0 || version_compare($pthreads_version, "5.0.0") > 0){
|
||||
$messages[] = "pthreads ^4.0.0 is required, while you have $pthreads_version.";
|
||||
if(version_compare($pthreads_version, "5.1.0") < 0 || version_compare($pthreads_version, "6.0.0") >= 0){
|
||||
$messages[] = "pthreads ^5.0.0 is required, while you have $pthreads_version.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
||||
|
||||
final class VersionInfo{
|
||||
public const NAME = "PocketMine-MP";
|
||||
public const BASE_VERSION = "5.0.0-ALPHA7";
|
||||
public const BASE_VERSION = "5.0.0-ALPHA8";
|
||||
public const IS_DEVELOPMENT_BUILD = false;
|
||||
public const BUILD_CHANNEL = "alpha";
|
||||
|
||||
|
@ -46,13 +46,17 @@ if($socket === false){
|
||||
throw new \RuntimeException("Failed to connect to server process ($errCode): $errMessage");
|
||||
}
|
||||
|
||||
$channel = new \Threaded();
|
||||
/** @phpstan-var \ThreadedArray<int, string> $channel */
|
||||
$channel = new \ThreadedArray();
|
||||
$thread = new class($channel) extends \Thread{
|
||||
/**
|
||||
* @phpstan-param \ThreadedArray<int, string> $channel
|
||||
*/
|
||||
public function __construct(
|
||||
private \Threaded $channel,
|
||||
private \ThreadedArray $channel,
|
||||
){}
|
||||
|
||||
public function run(){
|
||||
public function run() : void{
|
||||
require dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
|
||||
$channel = $this->channel;
|
||||
@ -75,7 +79,6 @@ while(!feof($socket)){
|
||||
if(count($channel) === 0){
|
||||
$channel->wait(1_000_000);
|
||||
}
|
||||
/** @var string|null $line */
|
||||
$line = $channel->shift();
|
||||
return $line;
|
||||
});
|
||||
|
62
src/event/player/PlayerDisconnectEventTrait.php
Normal file
62
src/event/player/PlayerDisconnectEventTrait.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\event\player;
|
||||
|
||||
use pocketmine\lang\Translatable;
|
||||
|
||||
trait PlayerDisconnectEventTrait{
|
||||
/**
|
||||
* Sets the kick reason shown in the server log and on the console.
|
||||
*
|
||||
* This should be a **short, simple, single-line** message.
|
||||
* Do not use long or multi-line messages here - they will spam the log and server console with useless information.
|
||||
*/
|
||||
public function setDisconnectReason(Translatable|string $disconnectReason) : void{
|
||||
$this->disconnectReason = $disconnectReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the kick reason shown in the server log and on the console.
|
||||
* When kicked by the /kick command, the default is something like "Kicked by admin.".
|
||||
*/
|
||||
public function getDisconnectReason() : Translatable|string{
|
||||
return $this->disconnectReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message shown on the player's disconnection screen.
|
||||
* This can be as long as you like, and may contain formatting and newlines.
|
||||
* If this is set to null, the kick reason will be used as the disconnect screen message directly.
|
||||
*/
|
||||
public function setDisconnectScreenMessage(Translatable|string|null $disconnectScreenMessage) : void{
|
||||
$this->disconnectScreenMessage = $disconnectScreenMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message shown on the player's disconnection screen.
|
||||
* When kicked by the /kick command, the default is something like "Kicked by admin.".
|
||||
* If this is null, the kick reason will be used as the disconnect screen message directly.
|
||||
*/
|
||||
public function getDisconnectScreenMessage() : Translatable|string|null{ return $this->disconnectScreenMessage ?? $this->disconnectReason; }
|
||||
}
|
@ -26,7 +26,6 @@ namespace pocketmine\event\player;
|
||||
use pocketmine\event\Cancellable;
|
||||
use pocketmine\event\CancellableTrait;
|
||||
use pocketmine\event\Event;
|
||||
use pocketmine\lang\KnownTranslationFactory;
|
||||
use pocketmine\lang\Translatable;
|
||||
use pocketmine\network\mcpe\NetworkSession;
|
||||
|
||||
@ -36,15 +35,14 @@ use pocketmine\network\mcpe\NetworkSession;
|
||||
*/
|
||||
class PlayerDuplicateLoginEvent extends Event implements Cancellable{
|
||||
use CancellableTrait;
|
||||
|
||||
private Translatable|string $disconnectMessage;
|
||||
use PlayerDisconnectEventTrait;
|
||||
|
||||
public function __construct(
|
||||
private NetworkSession $connectingSession,
|
||||
private NetworkSession $existingSession
|
||||
){
|
||||
$this->disconnectMessage = KnownTranslationFactory::disconnectionScreen_loggedinOtherLocation();
|
||||
}
|
||||
private NetworkSession $existingSession,
|
||||
private Translatable|string $disconnectReason,
|
||||
private Translatable|string|null $disconnectScreenMessage
|
||||
){}
|
||||
|
||||
public function getConnectingSession() : NetworkSession{
|
||||
return $this->connectingSession;
|
||||
@ -53,15 +51,4 @@ class PlayerDuplicateLoginEvent extends Event implements Cancellable{
|
||||
public function getExistingSession() : NetworkSession{
|
||||
return $this->existingSession;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message shown to the session which gets disconnected.
|
||||
*/
|
||||
public function getDisconnectMessage() : Translatable|string{
|
||||
return $this->disconnectMessage;
|
||||
}
|
||||
|
||||
public function setDisconnectMessage(Translatable|string $message) : void{
|
||||
$this->disconnectMessage = $message;
|
||||
}
|
||||
}
|
||||
|
@ -33,32 +33,17 @@ use pocketmine\player\Player;
|
||||
*/
|
||||
class PlayerKickEvent extends PlayerEvent implements Cancellable{
|
||||
use CancellableTrait;
|
||||
use PlayerDisconnectEventTrait;
|
||||
|
||||
public function __construct(
|
||||
Player $player,
|
||||
protected Translatable|string $reason,
|
||||
protected Translatable|string $quitMessage
|
||||
protected Translatable|string $disconnectReason,
|
||||
protected Translatable|string $quitMessage,
|
||||
protected Translatable|string|null $disconnectScreenMessage
|
||||
){
|
||||
$this->player = $player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message shown on the kicked player's disconnection screen.
|
||||
* This message is also displayed in the console and server log.
|
||||
*/
|
||||
public function setReason(Translatable|string $reason) : void{
|
||||
$this->reason = $reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message shown on the kicked player's disconnection screen.
|
||||
* This message is also displayed in the console and server log.
|
||||
* When kicked by the /kick command, the default is something like "Kicked by admin.".
|
||||
*/
|
||||
public function getReason() : Translatable|string{
|
||||
return $this->reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the quit message broadcasted to other players.
|
||||
*/
|
||||
|
@ -65,8 +65,8 @@ class PlayerPreLoginEvent extends Event implements Cancellable{
|
||||
|
||||
/**
|
||||
* Returns an object containing self-proclaimed information about the connecting player.
|
||||
* WARNING: THE PLAYER IS NOT VERIFIED DURING THIS EVENT. At this point, it's unknown if the player is real or a
|
||||
* hacker.
|
||||
* WARNING: THE PLAYER IS NOT VERIFIED DURING THIS EVENT. At this point, this could be a hacker posing as another
|
||||
* player.
|
||||
*/
|
||||
public function getPlayerInfo() : PlayerInfo{
|
||||
return $this->playerInfo;
|
||||
@ -105,7 +105,7 @@ class PlayerPreLoginEvent extends Event implements Cancellable{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a reason to disallow the player to continue continue authenticating, with a message.
|
||||
* Sets a reason to disallow the player to continue authenticating, with a message.
|
||||
* This can also be used to change kick messages for already-set flags.
|
||||
*/
|
||||
public function setKickReason(int $flag, Translatable|string $message) : void{
|
||||
|
@ -96,10 +96,13 @@ class NetworkSessionManager{
|
||||
|
||||
/**
|
||||
* Terminates all connected sessions with the given reason.
|
||||
*
|
||||
* @param Translatable|string $reason Shown in the server log - this should be a short one-line message
|
||||
* @param Translatable|string|null $disconnectScreenMessage Shown on the player's disconnection screen (null will use the reason)
|
||||
*/
|
||||
public function close(Translatable|string $reason = "") : void{
|
||||
public function close(Translatable|string $reason = "", Translatable|string|null $disconnectScreenMessage = null) : void{
|
||||
foreach($this->sessions as $session){
|
||||
$session->disconnect($reason);
|
||||
$session->disconnect($reason, $disconnectScreenMessage);
|
||||
}
|
||||
$this->sessions = [];
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
|
||||
use pocketmine\network\mcpe\protocol\types\ChunkPosition;
|
||||
use pocketmine\network\mcpe\serializer\ChunkSerializer;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\thread\NonThreadSafeValue;
|
||||
use pocketmine\world\format\Chunk;
|
||||
use pocketmine\world\format\io\FastChunkSerializer;
|
||||
|
||||
@ -43,14 +44,15 @@ class ChunkRequestTask extends AsyncTask{
|
||||
protected string $chunk;
|
||||
protected int $chunkX;
|
||||
protected int $chunkZ;
|
||||
protected Compressor $compressor;
|
||||
/** @phpstan-var NonThreadSafeValue<Compressor> */
|
||||
protected NonThreadSafeValue $compressor;
|
||||
private string $tiles;
|
||||
|
||||
/**
|
||||
* @phpstan-param (\Closure() : void)|null $onError
|
||||
*/
|
||||
public function __construct(int $chunkX, int $chunkZ, Chunk $chunk, CompressBatchPromise $promise, Compressor $compressor, ?\Closure $onError = null){
|
||||
$this->compressor = $compressor;
|
||||
$this->compressor = new NonThreadSafeValue($compressor);
|
||||
|
||||
$this->chunk = FastChunkSerializer::serializeTerrain($chunk);
|
||||
$this->chunkX = $chunkX;
|
||||
@ -66,7 +68,7 @@ class ChunkRequestTask extends AsyncTask{
|
||||
$subCount = ChunkSerializer::getSubChunkCount($chunk);
|
||||
$encoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary());
|
||||
$payload = ChunkSerializer::serializeFullChunk($chunk, RuntimeBlockMapping::getInstance(), $encoderContext, $this->tiles);
|
||||
$this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload))->getBuffer()));
|
||||
$this->setResult($this->compressor->deserialize()->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload))->getBuffer()));
|
||||
}
|
||||
|
||||
public function onError() : void{
|
||||
|
@ -602,22 +602,25 @@ class NetworkSession{
|
||||
$this->invManager = null;
|
||||
}
|
||||
|
||||
private function sendDisconnectPacket(Translatable|string $reason) : void{
|
||||
if($reason instanceof Translatable){
|
||||
$translated = $this->server->getLanguage()->translate($reason);
|
||||
private function sendDisconnectPacket(Translatable|string $message) : void{
|
||||
if($message instanceof Translatable){
|
||||
$translated = $this->server->getLanguage()->translate($message);
|
||||
}else{
|
||||
$translated = $reason;
|
||||
$translated = $message;
|
||||
}
|
||||
$this->sendDataPacket(DisconnectPacket::create($translated));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the session, destroying the associated player (if it exists).
|
||||
*
|
||||
* @param Translatable|string $reason Shown in the server log - this should be a short one-line message
|
||||
* @param Translatable|string|null $disconnectScreenMessage Shown on the player's disconnection screen (null will use the reason)
|
||||
*/
|
||||
public function disconnect(Translatable|string $reason, bool $notify = true) : void{
|
||||
$this->tryDisconnect(function() use ($reason, $notify) : void{
|
||||
public function disconnect(Translatable|string $reason, Translatable|string|null $disconnectScreenMessage = null, bool $notify = true) : void{
|
||||
$this->tryDisconnect(function() use ($reason, $disconnectScreenMessage, $notify) : void{
|
||||
if($notify){
|
||||
$this->sendDisconnectPacket($reason);
|
||||
$this->sendDisconnectPacket($disconnectScreenMessage ?? $reason);
|
||||
}
|
||||
if($this->player !== null){
|
||||
$this->player->onPostDisconnect($reason, null);
|
||||
@ -634,7 +637,7 @@ class NetworkSession{
|
||||
function() use ($protocolVersion) : void{
|
||||
$this->sendDataPacket(PlayStatusPacket::create($protocolVersion < ProtocolInfo::CURRENT_PROTOCOL ? PlayStatusPacket::LOGIN_FAILED_CLIENT : PlayStatusPacket::LOGIN_FAILED_SERVER), true);
|
||||
},
|
||||
$this->server->getLanguage()->translate(KnownTranslationFactory::pocketmine_disconnect_incompatibleProtocol((string) $protocolVersion))
|
||||
KnownTranslationFactory::pocketmine_disconnect_incompatibleProtocol((string) $protocolVersion)
|
||||
);
|
||||
}
|
||||
|
||||
@ -654,9 +657,9 @@ class NetworkSession{
|
||||
/**
|
||||
* Called by the Player when it is closed (for example due to getting kicked).
|
||||
*/
|
||||
public function onPlayerDestroyed(Translatable|string $reason) : void{
|
||||
$this->tryDisconnect(function() use ($reason) : void{
|
||||
$this->sendDisconnectPacket($reason);
|
||||
public function onPlayerDestroyed(Translatable|string $reason, Translatable|string $disconnectScreenMessage) : void{
|
||||
$this->tryDisconnect(function() use ($disconnectScreenMessage) : void{
|
||||
$this->sendDisconnectPacket($disconnectScreenMessage);
|
||||
}, $reason);
|
||||
}
|
||||
|
||||
@ -694,7 +697,7 @@ class NetworkSession{
|
||||
|
||||
if(!$this->authenticated){
|
||||
if($authRequired){
|
||||
$this->disconnect(KnownTranslationFactory::disconnectionScreen_notAuthenticated());
|
||||
$this->disconnect("Not authenticated", KnownTranslationFactory::disconnectionScreen_notAuthenticated());
|
||||
return;
|
||||
}
|
||||
if($this->info instanceof XboxLivePlayerInfo){
|
||||
@ -728,14 +731,14 @@ class NetworkSession{
|
||||
if($kickForXUIDMismatch($info instanceof XboxLivePlayerInfo ? $info->getXuid() : "")){
|
||||
return;
|
||||
}
|
||||
$ev = new PlayerDuplicateLoginEvent($this, $existingSession);
|
||||
$ev = new PlayerDuplicateLoginEvent($this, $existingSession, KnownTranslationFactory::disconnectionScreen_loggedinOtherLocation(), null);
|
||||
$ev->call();
|
||||
if($ev->isCancelled()){
|
||||
$this->disconnect($ev->getDisconnectMessage());
|
||||
$this->disconnect($ev->getDisconnectReason(), $ev->getDisconnectScreenMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
$existingSession->disconnect($ev->getDisconnectMessage());
|
||||
$existingSession->disconnect($ev->getDisconnectReason(), $ev->getDisconnectScreenMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ use pocketmine\network\mcpe\JwtUtils;
|
||||
use pocketmine\network\mcpe\protocol\types\login\JwtChainLinkBody;
|
||||
use pocketmine\network\mcpe\protocol\types\login\JwtHeader;
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\thread\NonThreadSafeValue;
|
||||
use function base64_decode;
|
||||
use function igbinary_serialize;
|
||||
use function igbinary_unserialize;
|
||||
@ -49,8 +50,10 @@ class ProcessLoginTask extends AsyncTask{
|
||||
* Whether the keychain signatures were validated correctly. This will be set to an error message if any link in the
|
||||
* keychain is invalid for whatever reason (bad signature, not in nbf-exp window, etc). If this is non-null, the
|
||||
* keychain might have been tampered with. The player will always be disconnected if this is non-null.
|
||||
*
|
||||
* @phpstan-var NonThreadSafeValue<Translatable>|string|null
|
||||
*/
|
||||
private Translatable|string|null $error = "Unknown";
|
||||
private NonThreadSafeValue|string|null $error = "Unknown";
|
||||
/**
|
||||
* Whether the player is logged into Xbox Live. This is true if any link in the keychain is signed with the Mojang
|
||||
* root public key.
|
||||
@ -77,7 +80,8 @@ class ProcessLoginTask extends AsyncTask{
|
||||
$this->clientPublicKey = $this->validateChain();
|
||||
$this->error = null;
|
||||
}catch(VerifyLoginException $e){
|
||||
$this->error = $e->getDisconnectMessage();
|
||||
$disconnectMessage = $e->getDisconnectMessage();
|
||||
$this->error = $disconnectMessage instanceof Translatable ? new NonThreadSafeValue($disconnectMessage) : $disconnectMessage;
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,6 +199,6 @@ class ProcessLoginTask extends AsyncTask{
|
||||
* @phpstan-var \Closure(bool, bool, Translatable|string|null, ?string) : void $callback
|
||||
*/
|
||||
$callback = $this->fetchLocal(self::TLS_KEY_ON_COMPLETION);
|
||||
$callback($this->authenticated, $this->authRequired, $this->error, $this->clientPublicKey);
|
||||
$callback($this->authenticated, $this->authRequired, $this->error instanceof NonThreadSafeValue ? $this->error->deserialize() : $this->error, $this->clientPublicKey);
|
||||
}
|
||||
}
|
||||
|
@ -24,21 +24,26 @@ declare(strict_types=1);
|
||||
namespace pocketmine\network\mcpe\compression;
|
||||
|
||||
use pocketmine\scheduler\AsyncTask;
|
||||
use pocketmine\thread\NonThreadSafeValue;
|
||||
|
||||
class CompressBatchTask extends AsyncTask{
|
||||
|
||||
private const TLS_KEY_PROMISE = "promise";
|
||||
|
||||
/** @phpstan-var NonThreadSafeValue<Compressor> */
|
||||
private NonThreadSafeValue $compressor;
|
||||
|
||||
public function __construct(
|
||||
private string $data,
|
||||
CompressBatchPromise $promise,
|
||||
private Compressor $compressor
|
||||
Compressor $compressor
|
||||
){
|
||||
$this->compressor = new NonThreadSafeValue($compressor);
|
||||
$this->storeLocal(self::TLS_KEY_PROMISE, $promise);
|
||||
}
|
||||
|
||||
public function onRun() : void{
|
||||
$this->setResult($this->compressor->compress($this->data));
|
||||
$this->setResult($this->compressor->deserialize()->compress($this->data));
|
||||
}
|
||||
|
||||
public function onCompletion() : void{
|
||||
|
@ -93,7 +93,7 @@ class ResourcePacksPacketHandler extends PacketHandler{
|
||||
switch($packet->status){
|
||||
case ResourcePackClientResponsePacket::STATUS_REFUSED:
|
||||
//TODO: add lang strings for this
|
||||
$this->session->disconnect("You must accept resource packs to join this server.", true);
|
||||
$this->session->disconnect("Refused resource packs", "You must accept resource packs to join this server.", true);
|
||||
break;
|
||||
case ResourcePackClientResponsePacket::STATUS_SEND_PACKS:
|
||||
foreach($packet->packIds as $uuid){
|
||||
|
@ -26,7 +26,10 @@ namespace pocketmine\network\mcpe\raklib;
|
||||
use raklib\server\ipc\InterThreadChannelReader;
|
||||
|
||||
final class PthreadsChannelReader implements InterThreadChannelReader{
|
||||
public function __construct(private \Threaded $buffer){}
|
||||
/**
|
||||
* @phpstan-param \ThreadedArray<int, string> $buffer
|
||||
*/
|
||||
public function __construct(private \ThreadedArray $buffer){}
|
||||
|
||||
public function read() : ?string{
|
||||
return $this->buffer->shift();
|
||||
|
@ -26,7 +26,10 @@ namespace pocketmine\network\mcpe\raklib;
|
||||
use raklib\server\ipc\InterThreadChannelWriter;
|
||||
|
||||
final class PthreadsChannelWriter implements InterThreadChannelWriter{
|
||||
public function __construct(private \Threaded $buffer){}
|
||||
/**
|
||||
* @phpstan-param \ThreadedArray<int, string> $buffer
|
||||
*/
|
||||
public function __construct(private \ThreadedArray $buffer){}
|
||||
|
||||
public function write(string $str) : void{
|
||||
$this->buffer[] = $str;
|
||||
|
@ -85,8 +85,10 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
|
||||
|
||||
$this->sleeper = new SleeperNotifier();
|
||||
|
||||
$mainToThreadBuffer = new \Threaded();
|
||||
$threadToMainBuffer = new \Threaded();
|
||||
/** @phpstan-var \ThreadedArray<int, string> $mainToThreadBuffer */
|
||||
$mainToThreadBuffer = new \ThreadedArray();
|
||||
/** @phpstan-var \ThreadedArray<int, string> $threadToMainBuffer */
|
||||
$threadToMainBuffer = new \ThreadedArray();
|
||||
|
||||
$this->rakLib = new RakLibServer(
|
||||
$this->server->getLogger(),
|
||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace pocketmine\network\mcpe\raklib;
|
||||
|
||||
use pocketmine\snooze\SleeperNotifier;
|
||||
use pocketmine\thread\NonThreadSafeValue;
|
||||
use pocketmine\thread\Thread;
|
||||
use raklib\generic\Socket;
|
||||
use raklib\generic\SocketException;
|
||||
@ -43,19 +44,27 @@ class RakLibServer extends Thread{
|
||||
protected bool $cleanShutdown = false;
|
||||
protected bool $ready = false;
|
||||
protected string $mainPath;
|
||||
public ?RakLibThreadCrashInfo $crashInfo = null;
|
||||
/** @phpstan-var NonThreadSafeValue<RakLibThreadCrashInfo>|null */
|
||||
public ?NonThreadSafeValue $crashInfo = null;
|
||||
/** @phpstan-var NonThreadSafeValue<InternetAddress> */
|
||||
protected NonThreadSafeValue $address;
|
||||
|
||||
/**
|
||||
* @phpstan-param \ThreadedArray<int, string> $mainToThreadBuffer
|
||||
* @phpstan-param \ThreadedArray<int, string> $threadToMainBuffer
|
||||
*/
|
||||
public function __construct(
|
||||
protected \ThreadedLogger $logger,
|
||||
protected \Threaded $mainToThreadBuffer,
|
||||
protected \Threaded $threadToMainBuffer,
|
||||
protected InternetAddress $address,
|
||||
protected \ThreadedArray $mainToThreadBuffer,
|
||||
protected \ThreadedArray $threadToMainBuffer,
|
||||
InternetAddress $address,
|
||||
protected int $serverId,
|
||||
protected int $maxMtuSize,
|
||||
protected int $protocolVersion,
|
||||
protected SleeperNotifier $mainThreadNotifier
|
||||
){
|
||||
$this->mainPath = \pocketmine\PATH;
|
||||
$this->address = new NonThreadSafeValue($address);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,12 +84,12 @@ class RakLibServer extends Thread{
|
||||
}
|
||||
|
||||
public function getCrashInfo() : ?RakLibThreadCrashInfo{
|
||||
return $this->crashInfo;
|
||||
return $this->crashInfo?->deserialize();
|
||||
}
|
||||
|
||||
private function setCrashInfo(RakLibThreadCrashInfo $info) : void{
|
||||
$this->synchronized(function(RakLibThreadCrashInfo $info) : void{
|
||||
$this->crashInfo = $info;
|
||||
$this->crashInfo = new NonThreadSafeValue($info);
|
||||
$this->notify();
|
||||
}, $info);
|
||||
}
|
||||
@ -91,7 +100,7 @@ class RakLibServer extends Thread{
|
||||
while(!$this->ready && $this->crashInfo === null){
|
||||
$this->wait();
|
||||
}
|
||||
$crashInfo = $this->crashInfo;
|
||||
$crashInfo = $this->crashInfo?->deserialize();
|
||||
if($crashInfo !== null){
|
||||
if($crashInfo->getClass() === SocketException::class){
|
||||
throw new SocketException($crashInfo->getMessage());
|
||||
@ -110,7 +119,7 @@ class RakLibServer extends Thread{
|
||||
register_shutdown_function([$this, "shutdownHandler"]);
|
||||
|
||||
try{
|
||||
$socket = new Socket($this->address);
|
||||
$socket = new Socket($this->address->deserialize());
|
||||
}catch(SocketException $e){
|
||||
$this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e));
|
||||
return;
|
||||
|
@ -27,8 +27,11 @@ use pocketmine\snooze\SleeperNotifier;
|
||||
use raklib\server\ipc\InterThreadChannelWriter;
|
||||
|
||||
final class SnoozeAwarePthreadsChannelWriter implements InterThreadChannelWriter{
|
||||
/**
|
||||
* @phpstan-param \ThreadedArray<int, string> $buffer
|
||||
*/
|
||||
public function __construct(
|
||||
private \Threaded $buffer,
|
||||
private \ThreadedArray $buffer,
|
||||
private SleeperNotifier $notifier
|
||||
){}
|
||||
|
||||
|
@ -2090,16 +2090,24 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
|
||||
/**
|
||||
* Kicks a player from the server
|
||||
*
|
||||
* @param Translatable|string $reason Shown in the server log - this should be a short one-line message
|
||||
* @param Translatable|string|null $quitMessage Message to broadcast to online players (null will use default)
|
||||
* @param Translatable|string|null $disconnectScreenMessage Shown on the player's disconnection screen (null will use the reason)
|
||||
*/
|
||||
public function kick(Translatable|string $reason = "", Translatable|string|null $quitMessage = null) : bool{
|
||||
$ev = new PlayerKickEvent($this, $reason, $quitMessage ?? $this->getLeaveMessage());
|
||||
public function kick(Translatable|string $reason = "", Translatable|string|null $quitMessage = null, Translatable|string|null $disconnectScreenMessage = null) : bool{
|
||||
$ev = new PlayerKickEvent($this, $reason, $quitMessage ?? $this->getLeaveMessage(), $disconnectScreenMessage);
|
||||
$ev->call();
|
||||
if(!$ev->isCancelled()){
|
||||
$reason = $ev->getReason();
|
||||
$reason = $ev->getDisconnectReason();
|
||||
if($reason === ""){
|
||||
$reason = KnownTranslationFactory::disconnectionScreen_noReason();
|
||||
}
|
||||
$this->disconnect($reason, $ev->getQuitMessage());
|
||||
$disconnectScreenMessage = $ev->getDisconnectScreenMessage() ?? $reason;
|
||||
if($disconnectScreenMessage === ""){
|
||||
$disconnectScreenMessage = KnownTranslationFactory::disconnectionScreen_noReason();
|
||||
}
|
||||
$this->disconnect($reason, $ev->getQuitMessage(), $disconnectScreenMessage);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -2116,15 +2124,16 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
*
|
||||
* Note for internals developers: Do not call this from network sessions. It will cause a feedback loop.
|
||||
*
|
||||
* @param Translatable|string $reason Shown on the disconnect screen, and in the server log
|
||||
* @param Translatable|string|null $quitMessage Message to broadcast to online players (null will use default)
|
||||
* @param Translatable|string $reason Shown in the server log - this should be a short one-line message
|
||||
* @param Translatable|string|null $quitMessage Message to broadcast to online players (null will use default)
|
||||
* @param Translatable|string|null $disconnectScreenMessage Shown on the player's disconnection screen (null will use the reason)
|
||||
*/
|
||||
public function disconnect(Translatable|string $reason, Translatable|string|null $quitMessage = null) : void{
|
||||
public function disconnect(Translatable|string $reason, Translatable|string|null $quitMessage = null, Translatable|string|null $disconnectScreenMessage = null) : void{
|
||||
if(!$this->isConnected()){
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getNetworkSession()->onPlayerDestroyed($reason);
|
||||
$this->getNetworkSession()->onPlayerDestroyed($reason, $disconnectScreenMessage ?? $reason);
|
||||
$this->onPostDisconnect($reason, $quitMessage);
|
||||
}
|
||||
|
||||
@ -2132,6 +2141,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
||||
* @internal
|
||||
* This method executes post-disconnect actions and cleanups.
|
||||
*
|
||||
* @param Translatable|string $reason Shown in the server log - this should be a short one-line message
|
||||
* @param Translatable|string|null $quitMessage Message to broadcast to online players (null will use default)
|
||||
*/
|
||||
public function onPostDisconnect(Translatable|string $reason, Translatable|string|null $quitMessage) : void{
|
||||
|
@ -158,7 +158,7 @@ class AsyncPool{
|
||||
throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once");
|
||||
}
|
||||
|
||||
$task->progressUpdates = new \Threaded();
|
||||
$task->progressUpdates = new \ThreadedArray();
|
||||
$task->setSubmitted();
|
||||
|
||||
$this->getWorker($worker)->stack($task);
|
||||
|
@ -23,11 +23,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\scheduler;
|
||||
|
||||
use pocketmine\utils\AssumptionFailedError;
|
||||
use pocketmine\thread\NonThreadSafeValue;
|
||||
use function igbinary_serialize;
|
||||
use function igbinary_unserialize;
|
||||
use function is_null;
|
||||
use function is_scalar;
|
||||
use function is_string;
|
||||
use function spl_object_id;
|
||||
|
||||
/**
|
||||
@ -51,15 +51,10 @@ use function spl_object_id;
|
||||
* thread, e.g. during {@link AsyncTask::onCompletion()} or {@link AsyncTask::onProgressUpdate()}. This means that
|
||||
* whatever you do in onRun() must be able to work without the Server instance.
|
||||
*
|
||||
* WARNING: Any non-Threaded objects WILL BE SERIALIZED when assigned to members of AsyncTasks or other Threaded object.
|
||||
* If later accessed from said Threaded object, you will be operating on a COPY OF THE OBJECT, NOT THE ORIGINAL OBJECT.
|
||||
* If you want to store non-serializable objects to access when the task completes, store them using
|
||||
* If you want to store non-thread-safe objects to access when the task completes, store them using
|
||||
* {@link AsyncTask::storeLocal}.
|
||||
*
|
||||
* WARNING: Arrays are converted to Volatile objects when assigned as members of Threaded objects.
|
||||
* Keep this in mind when using arrays stored as members of your AsyncTask.
|
||||
*/
|
||||
abstract class AsyncTask extends \Threaded{
|
||||
abstract class AsyncTask extends \ThreadedRunnable{
|
||||
/**
|
||||
* @var \ArrayObject|mixed[]|null object hash => mixed data
|
||||
* @phpstan-var \ArrayObject<int, array<string, mixed>>|null
|
||||
@ -71,10 +66,11 @@ abstract class AsyncTask extends \Threaded{
|
||||
/** @var AsyncWorker|null $worker */
|
||||
public $worker = null;
|
||||
|
||||
public \Threaded $progressUpdates;
|
||||
/** @phpstan-var \ThreadedArray<int, string> */
|
||||
public \ThreadedArray $progressUpdates;
|
||||
|
||||
private string|int|bool|null|float $result = null;
|
||||
private bool $serialized = false;
|
||||
/** @phpstan-var NonThreadSafeValue<mixed>|string|int|bool|float|null */
|
||||
private NonThreadSafeValue|string|int|bool|null|float $result = null;
|
||||
private bool $cancelRun = false;
|
||||
private bool $submitted = false;
|
||||
|
||||
@ -117,15 +113,14 @@ abstract class AsyncTask extends \Threaded{
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResult(){
|
||||
if($this->serialized){
|
||||
if(!is_string($this->result)) throw new AssumptionFailedError("Result expected to be a serialized string");
|
||||
return igbinary_unserialize($this->result);
|
||||
if($this->result instanceof NonThreadSafeValue){
|
||||
return $this->result->deserialize();
|
||||
}
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
public function setResult(mixed $result) : void{
|
||||
$this->result = ($this->serialized = !is_scalar($result)) ? igbinary_serialize($result) : $result;
|
||||
$this->result = is_scalar($result) || is_null($result) ? $result : new NonThreadSafeValue($result);
|
||||
}
|
||||
|
||||
public function cancelRun() : void{
|
||||
@ -164,15 +159,14 @@ abstract class AsyncTask extends \Threaded{
|
||||
* @param mixed $progress A value that can be safely serialize()'ed.
|
||||
*/
|
||||
public function publishProgress(mixed $progress) : void{
|
||||
$this->progressUpdates[] = igbinary_serialize($progress);
|
||||
$this->progressUpdates[] = igbinary_serialize($progress) ?? throw new \InvalidArgumentException("Progress must be serializable");
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Only call from AsyncPool.php on the main thread
|
||||
*/
|
||||
public function checkProgressUpdates() : void{
|
||||
while($this->progressUpdates->count() !== 0){
|
||||
$progress = $this->progressUpdates->shift();
|
||||
while(($progress = $this->progressUpdates->shift()) !== null){
|
||||
$this->onProgressUpdate(igbinary_unserialize($progress));
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,11 @@ use pocketmine\Server;
|
||||
use function error_reporting;
|
||||
|
||||
trait CommonThreadPartsTrait{
|
||||
/** @var \Threaded|\ClassLoader[]|null */
|
||||
private ?\Threaded $classLoaders = null;
|
||||
/**
|
||||
* @var \ThreadedArray|\ClassLoader[]|null
|
||||
* @phpstan-var \ThreadedArray<int, \ClassLoader>|null
|
||||
*/
|
||||
private ?\ThreadedArray $classLoaders = null;
|
||||
protected ?string $composerAutoloaderPath = null;
|
||||
|
||||
protected bool $isKilled = false;
|
||||
@ -52,7 +55,7 @@ trait CommonThreadPartsTrait{
|
||||
}
|
||||
|
||||
if($this->classLoaders === null){
|
||||
$this->classLoaders = new \Threaded();
|
||||
$this->classLoaders = new \ThreadedArray();
|
||||
}else{
|
||||
foreach($this->classLoaders as $k => $autoloader){
|
||||
unset($this->classLoaders[$k]);
|
||||
|
57
src/thread/NonThreadSafeValue.php
Normal file
57
src/thread/NonThreadSafeValue.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\thread;
|
||||
|
||||
use function get_debug_type;
|
||||
use function igbinary_serialize;
|
||||
use function igbinary_unserialize;
|
||||
|
||||
/**
|
||||
* This class automatically serializes values which can't be shared between threads.
|
||||
* This class does NOT enable sharing the variable between threads. Each call to deserialize() will return a new copy
|
||||
* of the variable.
|
||||
*
|
||||
* @phpstan-template TValue
|
||||
*/
|
||||
final class NonThreadSafeValue extends \ThreadedBase{
|
||||
private string $variable;
|
||||
|
||||
/**
|
||||
* @phpstan-param TValue $variable
|
||||
*/
|
||||
public function __construct(
|
||||
mixed $variable
|
||||
){
|
||||
$this->variable = igbinary_serialize($variable) ?? throw new \InvalidArgumentException("Cannot serialize variable of type " . get_debug_type($variable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deserialized copy of the original variable.
|
||||
*
|
||||
* @phpstan-return TValue
|
||||
*/
|
||||
public function deserialize() : mixed{
|
||||
return igbinary_unserialize($this->variable);
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ namespace pocketmine\thread;
|
||||
|
||||
use function spl_object_id;
|
||||
|
||||
class ThreadManager extends \Volatile{
|
||||
class ThreadManager extends \ThreadedBase{
|
||||
|
||||
private static ?self $instance = null;
|
||||
|
||||
@ -40,12 +40,19 @@ class ThreadManager extends \Volatile{
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/** @phpstan-var \ThreadedArray<int, Thread|Worker> */
|
||||
private \ThreadedArray $threads;
|
||||
|
||||
private function __construct(){
|
||||
$this->threads = new \ThreadedArray();
|
||||
}
|
||||
|
||||
public function add(Worker|Thread $thread) : void{
|
||||
$this[spl_object_id($thread)] = $thread;
|
||||
$this->threads[spl_object_id($thread)] = $thread;
|
||||
}
|
||||
|
||||
public function remove(Worker|Thread $thread) : void{
|
||||
unset($this[spl_object_id($thread)]);
|
||||
unset($this->threads[spl_object_id($thread)]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,7 +63,7 @@ class ThreadManager extends \Volatile{
|
||||
/**
|
||||
* @var Worker|Thread $thread
|
||||
*/
|
||||
foreach($this as $key => $thread){
|
||||
foreach($this->threads as $key => $thread){
|
||||
$array[$key] = $thread;
|
||||
}
|
||||
|
||||
|
@ -194,6 +194,9 @@ class MainLogger extends \AttachableThreadedLogger implements \BufferedLogger{
|
||||
Terminal::writeLine($message);
|
||||
$this->logWriterThread->write($time->format("Y-m-d") . " " . TextFormat::clean($message) . PHP_EOL);
|
||||
|
||||
/**
|
||||
* @var \ThreadedLoggerAttachment $attachment
|
||||
*/
|
||||
foreach($this->attachments as $attachment){
|
||||
$attachment->log($level, $message);
|
||||
}
|
||||
|
@ -30,14 +30,15 @@ use function is_resource;
|
||||
use function touch;
|
||||
|
||||
final class MainLoggerThread extends \Thread{
|
||||
private \Threaded $buffer;
|
||||
/** @phpstan-var \ThreadedArray<int, string> */
|
||||
private \ThreadedArray $buffer;
|
||||
private bool $syncFlush = false;
|
||||
private bool $shutdown = false;
|
||||
|
||||
public function __construct(
|
||||
private string $logFile
|
||||
){
|
||||
$this->buffer = new \Threaded();
|
||||
$this->buffer = new \ThreadedArray();
|
||||
touch($this->logFile);
|
||||
}
|
||||
|
||||
@ -72,9 +73,7 @@ final class MainLoggerThread extends \Thread{
|
||||
* @param resource $logResource
|
||||
*/
|
||||
private function writeLogStream($logResource) : void{
|
||||
while($this->buffer->count() > 0){
|
||||
/** @var string $chunk */
|
||||
$chunk = $this->buffer->shift();
|
||||
while(($chunk = $this->buffer->shift()) !== null){
|
||||
fwrite($logResource, $chunk);
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ abstract class Terminal{
|
||||
|
||||
self::$FORMAT_RESET = "\x1b[m";
|
||||
|
||||
$color = fn(int $code) => "\x1b[38;5;${code}m";
|
||||
$color = fn(int $code) => "\x1b[38;5;{$code}m";
|
||||
|
||||
self::$COLOR_BLACK = $color(16);
|
||||
self::$COLOR_DARK_BLUE = $color(19);
|
||||
|
@ -46,9 +46,9 @@ parameters:
|
||||
path: ../../../src/plugin/PluginManager.php
|
||||
|
||||
-
|
||||
message: "#^Offset \\(int\\|string\\) on array\\<pocketmine\\\\plugin\\\\Plugin\\> in isset\\(\\) always exists and is not nullable\\.$#"
|
||||
message: "#^Parameter \\#1 \\$work of method Worker\\:\\:stack\\(\\) expects Threaded, pocketmine\\\\scheduler\\\\AsyncTask given\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/plugin/PluginManager.php
|
||||
path: ../../../src/scheduler/AsyncPool.php
|
||||
|
||||
-
|
||||
message: "#^Static property pocketmine\\\\scheduler\\\\AsyncTask\\:\\:\\$threadLocalStorage \\(ArrayObject\\<int, array\\<string, mixed\\>\\>\\|null\\) does not accept non\\-empty\\-array\\<int, non\\-empty\\-array\\<string, mixed\\>\\>\\|ArrayObject\\<int, array\\<string, mixed\\>\\>\\.$#"
|
||||
@ -56,12 +56,12 @@ parameters:
|
||||
path: ../../../src/scheduler/AsyncTask.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\thread\\\\Thread\\:\\:\\$classLoaders \\(\\(iterable\\<ClassLoader\\>&Threaded\\)\\|null\\) does not accept array\\<int, ClassLoader\\>\\|\\(iterable\\<ClassLoader\\>&Threaded\\)\\.$#"
|
||||
message: "#^Property pocketmine\\\\thread\\\\Thread\\:\\:\\$classLoaders \\(ThreadedArray\\<int, ClassLoader\\>\\|null\\) does not accept array\\{ClassLoader\\}\\|ThreadedArray\\<int, ClassLoader\\>\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/thread/Thread.php
|
||||
|
||||
-
|
||||
message: "#^Property pocketmine\\\\thread\\\\Worker\\:\\:\\$classLoaders \\(\\(iterable\\<ClassLoader\\>&Threaded\\)\\|null\\) does not accept array\\<int, ClassLoader\\>\\|\\(iterable\\<ClassLoader\\>&Threaded\\)\\.$#"
|
||||
message: "#^Property pocketmine\\\\thread\\\\Worker\\:\\:\\$classLoaders \\(ThreadedArray\\<int, ClassLoader\\>\\|null\\) does not accept array\\{ClassLoader\\}\\|ThreadedArray\\<int, ClassLoader\\>\\.$#"
|
||||
count: 1
|
||||
path: ../../../src/thread/Worker.php
|
||||
|
||||
|
@ -1,6 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @implements \Traversable<array-key, mixed>
|
||||
* @implements \IteratorAggregate<array-key, mixed>
|
||||
*/
|
||||
class Threaded implements \Traversable{}
|
||||
abstract class ThreadedBase implements \IteratorAggregate{
|
||||
|
||||
/**
|
||||
* @template TReturn
|
||||
* @param \Closure() : TReturn $function
|
||||
* @param mixed ...$args
|
||||
* @return TReturn
|
||||
*/
|
||||
public function synchronized(\Closure $function, mixed ...$args) : mixed{}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template TKey of array-key
|
||||
* @template TValue
|
||||
* @implements ArrayAccess<TKey, TValue>
|
||||
*/
|
||||
final class ThreadedArray extends ThreadedBase implements Countable, ArrayAccess{
|
||||
|
||||
/**
|
||||
* @return TValue|null
|
||||
*/
|
||||
public function pop() : mixed{}
|
||||
|
||||
/**
|
||||
* @return TValue|null
|
||||
*/
|
||||
public function shift() : mixed{}
|
||||
}
|
Submodule tests/plugins/DevTools updated: 29324aad5a...ff5a4fb830
Reference in New Issue
Block a user