mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-12 20:35:11 +00:00
Compare commits
110 Commits
Author | SHA1 | Date | |
---|---|---|---|
948875b025 | |||
2a4909d328 | |||
70dd8732e2 | |||
cdf72563f4 | |||
d65d8c3356 | |||
9b43ddecbd | |||
4bdd6410db | |||
6ea7fd7d6b | |||
5e7f18cbcf | |||
cff4a8d2bc | |||
20b7e8d702 | |||
c6110be051 | |||
86bd6777a3 | |||
935df62006 | |||
2709dd359c | |||
4e646d19a4 | |||
2a11762e61 | |||
7e0b5cf73d | |||
e903da8998 | |||
f2193d1ba7 | |||
f7977c9668 | |||
cfd9950b02 | |||
8ebcdb452d | |||
aacc00a911 | |||
0c250a2ef0 | |||
70dd9c7371 | |||
f8e6f036af | |||
bbabccfc89 | |||
1698eac6dc | |||
321972b87b | |||
c86c9b3ead | |||
249ef9c534 | |||
f4dab17a1b | |||
e85605af7f | |||
dfd70615ad | |||
ee903cad1f | |||
9a04481bec | |||
5d514a274f | |||
2220dc557e | |||
b5fc31a781 | |||
179eec9754 | |||
441f1f534f | |||
e747478afd | |||
92c45dd7e1 | |||
2538880408 | |||
c715efb18e | |||
6678360c00 | |||
3db45b6a68 | |||
3e87ad281f | |||
b72da777eb | |||
1101f35c17 | |||
3948dc4f75 | |||
20942b37eb | |||
d343db8750 | |||
f2df702c67 | |||
481270e6aa | |||
e7bdaa8579 | |||
76749cbaa7 | |||
a897bdfaa0 | |||
09668a37d6 | |||
ea92a23d0d | |||
691e67018d | |||
fe2140a716 | |||
57330a7186 | |||
9ddac21de0 | |||
c91aa24daa | |||
6186fc0bfe | |||
ef40934d24 | |||
69b668355f | |||
0547383296 | |||
c7dff9ea40 | |||
043350753b | |||
5ad8016b99 | |||
2e5b2eed6e | |||
5a0cde49cc | |||
008a022ec1 | |||
5c85a7c306 | |||
599c4284f5 | |||
9499e2e595 | |||
a4fea1444a | |||
1ba47802a8 | |||
9d111e13f1 | |||
44bc4d8c7c | |||
d317347a9b | |||
077fac84bf | |||
fdb3a5b121 | |||
e3bc36ab5b | |||
283ff28aa9 | |||
bb60a9057f | |||
02cf5ed388 | |||
6cad559dbe | |||
84a943bcec | |||
73bf5d4b29 | |||
eb136e60c8 | |||
4228880509 | |||
709d874204 | |||
07dc10d6e6 | |||
7f6269c432 | |||
194714b448 | |||
4def4d52d9 | |||
9bfcd39f2a | |||
8102616ff4 | |||
eb130f2906 | |||
eb4679fefd | |||
71b78b02d3 | |||
84cb070d56 | |||
8102586ee0 | |||
0336394098 | |||
1569bed37a | |||
a6e79c5004 |
4
.github/workflows/discord-release-notify.yml
vendored
4
.github/workflows/discord-release-notify.yml
vendored
@ -13,9 +13,9 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP and tools
|
- name: Setup PHP and tools
|
||||||
uses: shivammathur/setup-php@2.24.0
|
uses: shivammathur/setup-php@2.25.2
|
||||||
with:
|
with:
|
||||||
php-version: 8.0
|
php-version: 8.1
|
||||||
|
|
||||||
- name: Restore Composer package cache
|
- name: Restore Composer package cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
6
.github/workflows/draft-release.yml
vendored
6
.github/workflows/draft-release.yml
vendored
@ -18,9 +18,9 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: shivammathur/setup-php@2.24.0
|
uses: shivammathur/setup-php@2.25.2
|
||||||
with:
|
with:
|
||||||
php-version: 8.0
|
php-version: 8.1
|
||||||
|
|
||||||
- name: Restore Composer package cache
|
- name: Restore Composer package cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
@ -84,3 +84,5 @@ jobs:
|
|||||||
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
|
**For Minecraft: Bedrock Edition ${{ steps.get-pm-version.outputs.MCPE_VERSION }}**
|
||||||
|
|
||||||
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}${{ steps.get-pm-version.outputs.CHANGELOG_SUFFIX }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details.
|
Please see the [changelogs](${{ github.server_url }}/${{ github.repository }}/blob/${{ steps.get-pm-version.outputs.PM_VERSION }}/changelogs/${{ steps.get-pm-version.outputs.PM_VERSION_SHORT }}${{ steps.get-pm-version.outputs.CHANGELOG_SUFFIX }}.md#${{ steps.get-pm-version.outputs.PM_VERSION_MD }}) for details.
|
||||||
|
|
||||||
|
:warning: **4.x is now deprecated. Please read https://github.com/pmmp/PocketMine-MP/issues/5784 for details, and prepare to upgrade to 5.x.**
|
||||||
|
63
.github/workflows/main.yml
vendored
63
.github/workflows/main.yml
vendored
@ -6,47 +6,26 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-php:
|
|
||||||
name: Prepare PHP
|
|
||||||
runs-on: ${{ matrix.image }}
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
image: [ubuntu-20.04]
|
|
||||||
php: [8.0.28, 8.1.18, 8.2.5]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Build and prepare PHP cache
|
|
||||||
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php }}
|
|
||||||
install-path: "./bin"
|
|
||||||
pm-version-major: "4"
|
|
||||||
|
|
||||||
phpstan:
|
phpstan:
|
||||||
name: PHPStan analysis
|
name: PHPStan analysis
|
||||||
needs: build-php
|
|
||||||
runs-on: ${{ matrix.image }}
|
runs-on: ${{ matrix.image }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
image: [ubuntu-20.04]
|
image: [ubuntu-20.04]
|
||||||
php: [8.0.28, 8.1.18, 8.2.5]
|
php: ["8.1", "8.2"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
|
uses: pmmp/setup-php-action@2.0.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
install-path: "./bin"
|
install-path: "./bin"
|
||||||
pm-version-major: "4"
|
pm-version-major: "4"
|
||||||
|
|
||||||
- name: Install Composer
|
|
||||||
run: curl -sS https://getcomposer.org/installer | php
|
|
||||||
|
|
||||||
- name: Restore Composer package cache
|
- name: Restore Composer package cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@ -58,34 +37,30 @@ jobs:
|
|||||||
composer-v2-cache-
|
composer-v2-cache-
|
||||||
|
|
||||||
- name: Install Composer dependencies
|
- name: Install Composer dependencies
|
||||||
run: php composer.phar install --prefer-dist --no-interaction
|
run: composer install --prefer-dist --no-interaction
|
||||||
|
|
||||||
- name: Run PHPStan
|
- name: Run PHPStan
|
||||||
run: ./vendor/bin/phpstan analyze --no-progress --memory-limit=2G
|
run: ./vendor/bin/phpstan analyze --no-progress --memory-limit=2G
|
||||||
|
|
||||||
phpunit:
|
phpunit:
|
||||||
name: PHPUnit tests
|
name: PHPUnit tests
|
||||||
needs: build-php
|
|
||||||
runs-on: ${{ matrix.image }}
|
runs-on: ${{ matrix.image }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
image: [ubuntu-20.04]
|
image: [ubuntu-20.04]
|
||||||
php: [8.0.28, 8.1.18, 8.2.5]
|
php: ["8.1", "8.2"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
|
uses: pmmp/setup-php-action@2.0.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
install-path: "./bin"
|
install-path: "./bin"
|
||||||
pm-version-major: "4"
|
pm-version-major: "4"
|
||||||
|
|
||||||
- name: Install Composer
|
|
||||||
run: curl -sS https://getcomposer.org/installer | php
|
|
||||||
|
|
||||||
- name: Restore Composer package cache
|
- name: Restore Composer package cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@ -97,20 +72,19 @@ jobs:
|
|||||||
composer-v2-cache-
|
composer-v2-cache-
|
||||||
|
|
||||||
- name: Install Composer dependencies
|
- name: Install Composer dependencies
|
||||||
run: php composer.phar install --prefer-dist --no-interaction
|
run: composer install --prefer-dist --no-interaction
|
||||||
|
|
||||||
- name: Run PHPUnit tests
|
- name: Run PHPUnit tests
|
||||||
run: ./vendor/bin/phpunit --bootstrap vendor/autoload.php --fail-on-warning tests/phpunit
|
run: ./vendor/bin/phpunit --bootstrap vendor/autoload.php --fail-on-warning tests/phpunit
|
||||||
|
|
||||||
integration:
|
integration:
|
||||||
name: Integration tests
|
name: Integration tests
|
||||||
needs: build-php
|
|
||||||
runs-on: ${{ matrix.image }}
|
runs-on: ${{ matrix.image }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
image: [ubuntu-20.04]
|
image: [ubuntu-20.04]
|
||||||
php: [8.0.28, 8.1.18, 8.2.5]
|
php: ["8.1", "8.2"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@ -118,15 +92,12 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
|
uses: pmmp/setup-php-action@2.0.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
install-path: "./bin"
|
install-path: "./bin"
|
||||||
pm-version-major: "4"
|
pm-version-major: "4"
|
||||||
|
|
||||||
- name: Install Composer
|
|
||||||
run: curl -sS https://getcomposer.org/installer | php
|
|
||||||
|
|
||||||
- name: Restore Composer package cache
|
- name: Restore Composer package cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@ -138,34 +109,30 @@ jobs:
|
|||||||
composer-v2-cache-
|
composer-v2-cache-
|
||||||
|
|
||||||
- name: Install Composer dependencies
|
- name: Install Composer dependencies
|
||||||
run: php composer.phar install --no-dev --prefer-dist --no-interaction
|
run: composer install --no-dev --prefer-dist --no-interaction
|
||||||
|
|
||||||
- name: Run integration tests
|
- name: Run integration tests
|
||||||
run: ./tests/travis.sh -t4
|
run: ./tests/travis.sh -t4
|
||||||
|
|
||||||
codegen:
|
codegen:
|
||||||
name: Generated Code consistency checks
|
name: Generated Code consistency checks
|
||||||
needs: build-php
|
|
||||||
runs-on: ${{ matrix.image }}
|
runs-on: ${{ matrix.image }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
image: [ubuntu-20.04]
|
image: [ubuntu-20.04]
|
||||||
php: [8.0.28, 8.1.18, 8.2.5]
|
php: ["8.1", "8.2"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
uses: pmmp/setup-php-action@c7fb29d83535320922068087c7285bdedbbfa3c2
|
uses: pmmp/setup-php-action@2.0.0
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
install-path: "./bin"
|
install-path: "./bin"
|
||||||
pm-version-major: "4"
|
pm-version-major: "4"
|
||||||
|
|
||||||
- name: Install Composer
|
|
||||||
run: curl -sS https://getcomposer.org/installer | php
|
|
||||||
|
|
||||||
- name: Restore Composer package cache
|
- name: Restore Composer package cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@ -177,7 +144,7 @@ jobs:
|
|||||||
composer-v2-cache-
|
composer-v2-cache-
|
||||||
|
|
||||||
- name: Install Composer dependencies
|
- name: Install Composer dependencies
|
||||||
run: php composer.phar install --no-dev --prefer-dist --no-interaction
|
run: composer install --no-dev --prefer-dist --no-interaction
|
||||||
|
|
||||||
- name: Regenerate registry annotations
|
- name: Regenerate registry annotations
|
||||||
run: php build/generate-registry-annotations.php src
|
run: php build/generate-registry-annotations.php src
|
||||||
@ -203,10 +170,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup PHP and tools
|
- name: Setup PHP and tools
|
||||||
uses: shivammathur/setup-php@2.24.0
|
uses: shivammathur/setup-php@2.25.2
|
||||||
with:
|
with:
|
||||||
php-version: 8.0
|
php-version: 8.1
|
||||||
tools: php-cs-fixer:3.16
|
tools: php-cs-fixer:3.17
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
51
.github/workflows/update-php-versions.php
vendored
51
.github/workflows/update-php-versions.php
vendored
@ -1,51 +0,0 @@
|
|||||||
<?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);
|
|
||||||
|
|
||||||
const VERSIONS = [
|
|
||||||
"8.0",
|
|
||||||
"8.1",
|
|
||||||
"8.2"
|
|
||||||
];
|
|
||||||
|
|
||||||
$workflowFile = file_get_contents(__DIR__ . '/main.yml');
|
|
||||||
$newWorkflowFile = $workflowFile;
|
|
||||||
foreach(VERSIONS as $v){
|
|
||||||
$releaseInfo = file_get_contents("https://secure.php.net/releases?json&version=$v");
|
|
||||||
if($releaseInfo === false){
|
|
||||||
throw new \RuntimeException("Failed to contact php.net API");
|
|
||||||
}
|
|
||||||
$data = json_decode($releaseInfo, true);
|
|
||||||
if(!is_array($data) || !isset($data["version"]) || !is_string($data["version"]) || preg_match('/^\d+\.\d+\.\d+(-[A-Za-z\d]+)?$/', $data["version"]) === 0){
|
|
||||||
throw new \RuntimeException("Invalid data returned by API");
|
|
||||||
}
|
|
||||||
$updated = preg_replace("/$v\.\d+/", $data["version"], $newWorkflowFile);
|
|
||||||
if($updated !== $newWorkflowFile){
|
|
||||||
echo "Updated $v revision to " . $data["version"] . "\n";
|
|
||||||
}
|
|
||||||
$newWorkflowFile = $updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($workflowFile !== $newWorkflowFile){
|
|
||||||
echo "Writing modified workflow file\n";
|
|
||||||
file_put_contents(__DIR__ . '/main.yml', $newWorkflowFile);
|
|
||||||
}
|
|
1
.github/workflows/update-updater-api.yml
vendored
1
.github/workflows/update-updater-api.yml
vendored
@ -8,6 +8,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
concurrency: update-updater-api # only one job can run at a time, to avoid git conflicts when updating the repository
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install jq
|
- name: Install jq
|
||||||
|
7
.idea/codeStyles/Project.xml
generated
7
.idea/codeStyles/Project.xml
generated
@ -30,6 +30,13 @@
|
|||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Markdown">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="PHP">
|
<codeStyleSettings language="PHP">
|
||||||
<option name="CLASS_BRACE_STYLE" value="1" />
|
<option name="CLASS_BRACE_STYLE" value="1" />
|
||||||
<option name="METHOD_BRACE_STYLE" value="1" />
|
<option name="METHOD_BRACE_STYLE" value="1" />
|
||||||
|
10
BUILDING.md
10
BUILDING.md
@ -2,13 +2,13 @@
|
|||||||
## Pre-requisites
|
## Pre-requisites
|
||||||
- A bash shell (git bash is sufficient for Windows)
|
- A bash shell (git bash is sufficient for Windows)
|
||||||
- [`git`](https://git-scm.com) available in your shell
|
- [`git`](https://git-scm.com) available in your shell
|
||||||
- PHP 8.0 or newer available in your shell
|
- PHP 8.1 or newer available in your shell
|
||||||
- [`composer`](https://getcomposer.org) available in your shell
|
- [`composer`](https://getcomposer.org) available in your shell
|
||||||
|
|
||||||
## Custom PHP binaries
|
## Custom PHP binaries
|
||||||
Because PocketMine-MP requires several non-standard PHP extensions and configuration, PMMP provides scripts to build custom binaries for running PocketMine-MP, as well as prebuilt binaries.
|
Because PocketMine-MP requires several non-standard PHP extensions and configuration, PMMP provides scripts to build custom binaries for running PocketMine-MP, as well as prebuilt binaries.
|
||||||
|
|
||||||
- [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-8.0-Aggregate)
|
- [Prebuilt binaries](https://github.com/pmmp/PHP-Binaries/releases)
|
||||||
- [Compile scripts](https://github.com/pmmp/php-build-scripts) are provided as a submodule in the path `build/php`
|
- [Compile scripts](https://github.com/pmmp/php-build-scripts) are provided as a submodule in the path `build/php`
|
||||||
|
|
||||||
If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`.
|
If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`.
|
||||||
@ -29,11 +29,5 @@ Run `composer make-server` using your preferred PHP binary. It'll drop a `Pocket
|
|||||||
|
|
||||||
You can also use the `--out` option to change the output filename.
|
You can also use the `--out` option to change the output filename.
|
||||||
|
|
||||||
There is a bug in PHP that might cause an error which looks like this:
|
|
||||||
```
|
|
||||||
Fatal error: Uncaught BadMethodCallException: unable to create temporary file in PocketMine-MP/build/server-phar.php:119
|
|
||||||
```
|
|
||||||
You can work around it by setting `ulimit -n` to some bigger number, e.g. `8192`, or by updating your PHP version to at least 8.0.3.
|
|
||||||
|
|
||||||
## Running PocketMine-MP from source code
|
## Running PocketMine-MP from source code
|
||||||
Run `src/PocketMine.php` using your preferred PHP binary.
|
Run `src/PocketMine.php` using your preferred PHP binary.
|
||||||
|
@ -18,6 +18,30 @@ Larger contributions like feature additions should be preceded by a [Change Prop
|
|||||||
## Other things you'll need
|
## Other things you'll need
|
||||||
- [git](https://git-scm.com/)
|
- [git](https://git-scm.com/)
|
||||||
|
|
||||||
|
## List of `pocketmine` namespaces which are in other repos
|
||||||
|
PocketMine-MP has several dependencies which are independent from the main server code. Most of them use the `pocketmine` namespace.
|
||||||
|
Some of these add extra classes to packages which already exist in PocketMine-MP.
|
||||||
|
|
||||||
|
Take a look at the table below if you can't find the class or function you're looking for.
|
||||||
|
|
||||||
|
| Source URL | Namespace, class or function |
|
||||||
|
|:----------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| [pmmp/BedrockProtocol](https://github.com/pmmp/BedrockProtocol) | `pocketmine\network\mcpe\protocol` |
|
||||||
|
| [pmmp/BinaryUtils](https://github.com/pmmp/BinaryUtils) | `pocketmine\utils\BinaryDataException`</br>`pocketmine\utils\BinaryStream`</br>`pocketmine\utils\Binary` |
|
||||||
|
| [pmmp/ClassLoader](https://github.com/pmmp/`ClassLoader`) | `BaseClassLoader`</br>`ClassLoader`</br>`DynamicClassLoader` |
|
||||||
|
| [pmmp/Color](https://github.com/pmmp/Color) | `pocketmine\color` |
|
||||||
|
| [pmmp/ErrorHandler](https://github.com/pmmp/ErrorHandler) | `pocketmine\errorhandler` |
|
||||||
|
| [pmmp/LogPthreads](https://github.com/pmmp/LogPthreads) | `ThreadedLoggerAttachment`</br>`ThreadedLogger`</br>`AttachableThreadedLogger` |
|
||||||
|
| [pmmp/Log](https://github.com/pmmp/Log) | `AttachableLogger`</br>`BufferedLogger`</br>`GlobalLogger`</br>`LogLevel`</br>`Logger`</br>`PrefixedLogger`</br>`SimpleLogger` |
|
||||||
|
| [pmmp/Math](https://github.com/pmmp/Math) | `pocketmine\math` |
|
||||||
|
| [pmmp/NBT](https://github.com/pmmp/NBT) | `pocketmine\nbt` |
|
||||||
|
| [pmmp/RakLibIpc](https://github.com/pmmp/RakLibIpc) | `raklib\server\ipc` |
|
||||||
|
| [pmmp/RakLib](https://github.com/pmmp/RakLib) | `raklib` |
|
||||||
|
| [pmmp/Snooze](https://github.com/pmmp/Snooze) | `pocketmine\snooze` |
|
||||||
|
| [pmmp/ext-chunkutils2](https://github.com/pmmp/ext-chunkutils2) | `pocketmine\world\format\LightArray`</br>`pocketmine\world\format\PalettedBlockArray`</br>`pocketmine\world\format\io\SubChunkConverter` |
|
||||||
|
| [pmmp/ext-morton](https://github.com/pmmp/ext-morton) | `morton2d_decode`</br>`morton2d_encode`</br>`morton3d_decode`</br>`morton3d_encode` |
|
||||||
|
| [pmmp/ext-libdeflate](https://github.com/pmmp/ext-libdeflate) | `libdeflate_deflate_compress`</br>`libdeflate_gzip_compress`</br>`libdeflate_zlib_compress` |
|
||||||
|
|
||||||
## Choosing a target branch
|
## Choosing a target branch
|
||||||
PocketMine-MP has three primary branches of development.
|
PocketMine-MP has three primary branches of development.
|
||||||
|
|
||||||
|
Submodule build/php updated: 9d8807be82...46604f2f6a
@ -71,3 +71,9 @@ Released 6th May 2023.
|
|||||||
## Fixes
|
## Fixes
|
||||||
- Fixed players being forced into flight mode in every game mode.
|
- Fixed players being forced into flight mode in every game mode.
|
||||||
- Moral of the story: do not assume anything in Mojang internals does what its name suggests...
|
- Moral of the story: do not assume anything in Mojang internals does what its name suggests...
|
||||||
|
|
||||||
|
# 4.20.5
|
||||||
|
Released 30th May 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed server crash due to a bug in upstream dependency [`netresearch/jsonmapper`](https://github.com/cweiske/JsonMapper).
|
||||||
|
77
changelogs/4.21.md
Normal file
77
changelogs/4.21.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
**For Minecraft: Bedrock Edition 1.19.80**
|
||||||
|
|
||||||
|
### Note about API versions
|
||||||
|
Plugins which don't touch the `pocketmine\network\mcpe` namespace, and don't use reflection or any internal methods,
|
||||||
|
will also run on these releases and do not need API bumps.
|
||||||
|
Plugin developers should **only** update their required API to this version if you need the changes in this build.
|
||||||
|
|
||||||
|
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
|
||||||
|
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
|
||||||
|
|
||||||
|
# 4.21.0
|
||||||
|
Released 17th May 2023.
|
||||||
|
|
||||||
|
## General
|
||||||
|
- PHP 8.1 is now required. Most plugins should run without changes, but some might need to be updated due to language-level deprecations.
|
||||||
|
- Ticking chunk count is now shown separately from loaded chunk count in the `/status` command, providing useful performance information.
|
||||||
|
- Further improved performance of ticking chunk selection.
|
||||||
|
- Improved performance of some inventory functions.
|
||||||
|
- Reduced server memory footprint in most cases by ~9 MB per thread.
|
||||||
|
- Due to large overhead, async network compression is now only used for packets larger than 10 KB by default.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
- Added the following new `pocketmine.yml` configuration options:
|
||||||
|
- `network.async-compression-threshold` - minimum size of packet which will be compressed using `AsyncTask`
|
||||||
|
- Default is 10 KB, which means that very few packets will use async compression in practice. This is because the overhead of compressing async is currently so high that it's not worth it for smaller packets.
|
||||||
|
|
||||||
|
## Timings
|
||||||
|
- Timings reports no longer include the unused metadata fields `Entities` and `LivingEntities`.
|
||||||
|
- Timings reports now correctly calculate the peak time of a timer.
|
||||||
|
- Previously it was incorrectly recorded as the longest time spent in a single tick, rather than the longest time spent in a single activation.
|
||||||
|
- Timings report version has been bumped to `2` to reflect this change.
|
||||||
|
- All world-specific timers now have generic aggregate timings, making it much easier to locate performance patterns across all worlds.
|
||||||
|
|
||||||
|
## Gameplay
|
||||||
|
- Players in spectator mode are no longer able to pick blocks, and now have finite resources similar to survival mode.
|
||||||
|
|
||||||
|
## API
|
||||||
|
### `pocketmine\world`
|
||||||
|
- The following API methods have been added:
|
||||||
|
- `public World->getTickingChunks() : list<int>` - returns a list of chunk position hashes (a la `World::chunkHash()`) which are currently valid for random ticking
|
||||||
|
|
||||||
|
### `pocketmine\inventory`
|
||||||
|
- The following API methods have been added:
|
||||||
|
- `protected BaseInventory->getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int` - returns the number of items in the given stack if the content of the slot matches the test item, or zero otherwise
|
||||||
|
- This should be overridden if directly extending `BaseInventory` to provide a performance-optimised version. A slow default implementation is provided, but it will be removed in the future.
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
### Entity
|
||||||
|
- Unused `NameTag` tag is no longer saved for `Human` entities.
|
||||||
|
|
||||||
|
### Inventory
|
||||||
|
- `BaseInventory` now uses a new internal method `getMatchingItemCount()` to locate items in the inventory without useless cloning. This improves performance of various API methods, such as `addItem()`, `contains()`, and more.
|
||||||
|
- Specialization of `Inventory->isSlotEmpty()` in `BaseInventory` subclasses has been added to improve performance of some API methods.
|
||||||
|
|
||||||
|
### Network
|
||||||
|
- `RuntimeBlockMapping` no longer keeps all block palette NBT data in memory.
|
||||||
|
- This significantly reduces server idle memory footprint.
|
||||||
|
- For multi-version implementations, this will have a significant impact on memory usage, since a different block palette is often required to support each version.
|
||||||
|
- NBT will be lazy-loaded into memory and cached if `getBedrockKnownStates()` is called. However, this is not used by PocketMine-MP under normal circumstances.
|
||||||
|
- Removed unnecessary usage of `Utils::validateCallableSignature()` from some internal network pathways, improving performance.
|
||||||
|
|
||||||
|
### Scheduler
|
||||||
|
- `AsyncPool` no longer double-checks progress updates on completed tasks.
|
||||||
|
|
||||||
|
### World
|
||||||
|
- Ticking chunks are now tracked in `World->validTickingChunks` and `World->recheckTickingChunks`.
|
||||||
|
- This allows avoiding rechecking every ticking chunk for validity during ticking chunk selection, improving performance.
|
||||||
|
- In some cases, this allows bypassing chunk selection entirely, reducing selection cost to zero.
|
||||||
|
- Registered but ineligible ticking chunks are no longer rechecked every tick.
|
||||||
|
- This was causing wasted cycles during async worker backlog.
|
||||||
|
- The internal system must call `markTickingChunkForRecheck()` whenever a ticking chunk's eligibility for ticking has potentially changed, rather than just when it has changed from a yes to a no.
|
||||||
|
|
||||||
|
# 4.21.1
|
||||||
|
Released 30th May 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed server crash due to a bug in upstream dependency [`netresearch/jsonmapper`](https://github.com/cweiske/JsonMapper).
|
58
changelogs/4.22.md
Normal file
58
changelogs/4.22.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# 4.22.0
|
||||||
|
Released 7th June 2023.
|
||||||
|
|
||||||
|
**For Minecraft: Bedrock Edition 1.20.0**
|
||||||
|
|
||||||
|
This is a support release for Minecraft: Bedrock Edition 1.20.0.
|
||||||
|
|
||||||
|
**Plugin compatibility:** Plugins for previous 4.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` 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.
|
||||||
|
|
||||||
|
## Interim releases
|
||||||
|
If you're upgrading from 4.20.x directly to 4.22.x, please also read the following changelogs, as the interim releases contain important changes:
|
||||||
|
- [4.21.0](https://github.com/pmmp/PocketMine-MP/blob/4.22.0/changelogs/4.21.md#4210) - PHP 8.1 minimum version, minor performance improvements
|
||||||
|
|
||||||
|
## General
|
||||||
|
- Added support for Minecraft: Bedrock Edition 1.20.0.
|
||||||
|
- Removed support for older versions.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Removed deprecated `ReflectionProperty::setAccessible()` calls.
|
||||||
|
- Fixed jukebox music not stopping when destroyed by an explosion.
|
||||||
|
|
||||||
|
# 4.22.1
|
||||||
|
Released 9th June 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Replaced workaround for an old teleporting client bug:
|
||||||
|
- This workaround broke due to an additional client bug introduced by 1.20, causing players to become frozen to observers when teleported.
|
||||||
|
- The original client bug has still not been fixed, meaning a new workaround was needed, but no perfect solution could be found.
|
||||||
|
- The new workaround involves broadcasting teleport movements as regular movements, which causes unwanted interpolation between the old and new positions, but otherwise works correctly. This solution is not ideal, but it is the best we can do for now.
|
||||||
|
- See issues [#4394](https://github.com/pmmp/PocketMine-MP/issues/4394) and [#5810](https://github.com/pmmp/PocketMine-MP/issues/5810) for more details.
|
||||||
|
|
||||||
|
# 4.22.2
|
||||||
|
Released 1st July 2023.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
- Added obsoletion warnings to the server log at the end of the startup sequence.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed players being disconnected en masse with "Not authenticated" messages.
|
||||||
|
- This occurred due to a check intended to disable the old authentication key after July 1st.
|
||||||
|
- We expected that the new key would have been deployed by Mojang by now, but it seems like that has not yet happened.
|
||||||
|
- Due to the lack of a hard date for the key changeover, we guessed that July 1st would be a safe bet, but this appears to have backfired.
|
||||||
|
- This version will accept both old and new keys indefinitely.
|
||||||
|
- A security release will be published to remove the old key after the transition occurs.
|
||||||
|
|
||||||
|
# 4.22.3
|
||||||
|
Released 11th July 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed mishandling of NBT leading to a server crash when editing signs.
|
||||||
|
- Fixed an edge case crash that could occur in `AsyncTask->__destruct()` when thread-local storage referenced other `AsyncTask` objects.
|
||||||
|
|
||||||
|
## Internals
|
||||||
|
- Added a concurrency lock to prevent the `update-updater-api` GitHub Action from running for multiple releases at the same time (which would have caused one of them to fail due to git conflicts).
|
45
changelogs/4.23.md
Normal file
45
changelogs/4.23.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# 4.23.0
|
||||||
|
Released 12th July 2023.
|
||||||
|
|
||||||
|
**For Minecraft: Bedrock Edition 1.20.10**
|
||||||
|
|
||||||
|
This is a support release for Minecraft: Bedrock Edition 1.20.10.
|
||||||
|
|
||||||
|
**Plugin compatibility:** Plugins for previous 4.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` 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
|
||||||
|
- Added support for Minecraft: Bedrock Edition 1.20.10.
|
||||||
|
- Removed support for older versions.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Fixed Docker image build failure due to outdated `build/php` submodule.
|
||||||
|
|
||||||
|
# 4.23.1
|
||||||
|
Released 14th July 2023.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
- Hardened validation of JWT signing keys in `LoginPacket`.
|
||||||
|
- Fixed server crash due to a bug in upstream dependency [`netresearch/jsonmapper`](https://github.com/cweiske/JsonMapper).
|
||||||
|
|
||||||
|
# 4.23.2
|
||||||
|
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.
|
@ -5,7 +5,7 @@
|
|||||||
"homepage": "https://pmmp.io",
|
"homepage": "https://pmmp.io",
|
||||||
"license": "LGPL-3.0",
|
"license": "LGPL-3.0",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.0",
|
"php": "^8.1",
|
||||||
"php-64bit": "*",
|
"php-64bit": "*",
|
||||||
"ext-chunkutils2": "^0.3.1",
|
"ext-chunkutils2": "^0.3.1",
|
||||||
"ext-crypto": "^0.3.1",
|
"ext-crypto": "^0.3.1",
|
||||||
@ -31,13 +31,13 @@
|
|||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
"ext-zlib": ">=1.2.11",
|
"ext-zlib": ">=1.2.11",
|
||||||
"composer-runtime-api": "^2.0",
|
"composer-runtime-api": "^2.0",
|
||||||
"adhocore/json-comment": "^1.1",
|
"adhocore/json-comment": "~1.2.0",
|
||||||
"fgrosse/phpasn1": "^2.3",
|
"fgrosse/phpasn1": "~2.5.0",
|
||||||
"netresearch/jsonmapper": "^4.0",
|
"pocketmine/netresearch-jsonmapper": "~v4.2.1000",
|
||||||
"pocketmine/bedrock-block-upgrade-schema": "~2.1.0+bedrock-1.19.80",
|
"pocketmine/bedrock-block-upgrade-schema": "~3.1.0+bedrock-1.20.10",
|
||||||
"pocketmine/bedrock-data": "~2.2.0+bedrock-1.19.80",
|
"pocketmine/bedrock-data": "~2.4.0+bedrock-1.20.10",
|
||||||
"pocketmine/bedrock-item-upgrade-schema": "~1.2.0+bedrock-1.19.80",
|
"pocketmine/bedrock-item-upgrade-schema": "~1.4.0+bedrock-1.20.10",
|
||||||
"pocketmine/bedrock-protocol": "~21.0.0+bedrock-1.19.80",
|
"pocketmine/bedrock-protocol": "~23.0.2+bedrock-1.20.10",
|
||||||
"pocketmine/binaryutils": "^0.2.1",
|
"pocketmine/binaryutils": "^0.2.1",
|
||||||
"pocketmine/callback-validator": "^1.0.2",
|
"pocketmine/callback-validator": "^1.0.2",
|
||||||
"pocketmine/classloader": "^0.2.0",
|
"pocketmine/classloader": "^0.2.0",
|
||||||
@ -51,15 +51,15 @@
|
|||||||
"pocketmine/raklib": "^0.14.2",
|
"pocketmine/raklib": "^0.14.2",
|
||||||
"pocketmine/raklib-ipc": "^0.1.0",
|
"pocketmine/raklib-ipc": "^0.1.0",
|
||||||
"pocketmine/snooze": "^0.3.0",
|
"pocketmine/snooze": "^0.3.0",
|
||||||
"ramsey/uuid": "^4.1",
|
"ramsey/uuid": "~4.7.0",
|
||||||
"symfony/filesystem": "^5.4",
|
"symfony/filesystem": "~5.4.0",
|
||||||
"webmozart/path-util": "^2.3"
|
"webmozart/path-util": "~2.3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "1.10.14",
|
"phpstan/phpstan": "1.10.15",
|
||||||
"phpstan/phpstan-phpunit": "^1.1.0",
|
"phpstan/phpstan-phpunit": "^1.1.0",
|
||||||
"phpstan/phpstan-strict-rules": "^1.2.0",
|
"phpstan/phpstan-strict-rules": "^1.2.0",
|
||||||
"phpunit/phpunit": "^9.2"
|
"phpunit/phpunit": "^10.1"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@ -77,7 +77,7 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "8.0.0"
|
"php": "8.1.0"
|
||||||
},
|
},
|
||||||
"sort-packages": true
|
"sort-packages": true
|
||||||
},
|
},
|
||||||
|
882
composer.lock
generated
882
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -85,8 +85,11 @@ network:
|
|||||||
batch-threshold: 256
|
batch-threshold: 256
|
||||||
#Compression level used when sending batched packets. Higher = more CPU, less bandwidth usage
|
#Compression level used when sending batched packets. Higher = more CPU, less bandwidth usage
|
||||||
compression-level: 6
|
compression-level: 6
|
||||||
#Use AsyncTasks for compression. Adds half/one tick delay, less CPU load on main thread
|
#Use AsyncTasks for compression during the main game session. Increases latency, but may reduce main thread load
|
||||||
async-compression: false
|
async-compression: false
|
||||||
|
#Threshold for async compression, in bytes. Only packets larger than this will be compressed asynchronously
|
||||||
|
#Due to large overhead of AsyncTask, async compression isn't worth it except for large packets
|
||||||
|
async-compression-threshold: 10000
|
||||||
#Experimental. Use UPnP to automatically port forward
|
#Experimental. Use UPnP to automatically port forward
|
||||||
upnp-forwarding: false
|
upnp-forwarding: false
|
||||||
#Maximum size in bytes of packets sent over the network (default 1492 bytes). Packets larger than this will be
|
#Maximum size in bytes of packets sent over the network (default 1492 bytes). Packets larger than this will be
|
||||||
|
@ -316,9 +316,6 @@ class MemoryManager{
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!$property->isPublic()){
|
|
||||||
$property->setAccessible(true);
|
|
||||||
}
|
|
||||||
if(!$property->isInitialized()){
|
if(!$property->isInitialized()){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -442,9 +439,6 @@ class MemoryManager{
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!$property->isPublic()){
|
|
||||||
$property->setAccessible(true);
|
|
||||||
}
|
|
||||||
if(!$property->isInitialized($object)){
|
if(!$property->isInitialized($object)){
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ namespace pocketmine {
|
|||||||
|
|
||||||
require_once __DIR__ . '/VersionInfo.php';
|
require_once __DIR__ . '/VersionInfo.php';
|
||||||
|
|
||||||
const MIN_PHP_VERSION = "8.0.0";
|
const MIN_PHP_VERSION = "8.1.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $message
|
* @param string $message
|
||||||
@ -265,9 +265,6 @@ JIT_WARNING
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(extension_loaded('parallel')){
|
|
||||||
\parallel\bootstrap(\pocketmine\COMPOSER_AUTOLOADER_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorToExceptionHandler::set();
|
ErrorToExceptionHandler::set();
|
||||||
|
|
||||||
@ -346,7 +343,7 @@ JIT_WARNING
|
|||||||
|
|
||||||
if(ThreadManager::getInstance()->stopAll() > 0){
|
if(ThreadManager::getInstance()->stopAll() > 0){
|
||||||
$logger->debug("Some threads could not be stopped, performing a force-kill");
|
$logger->debug("Some threads could not be stopped, performing a force-kill");
|
||||||
Process::kill(Process::pid(), true);
|
Process::kill(Process::pid());
|
||||||
}
|
}
|
||||||
}while(false);
|
}while(false);
|
||||||
|
|
||||||
|
@ -208,6 +208,8 @@ class Server{
|
|||||||
private const TICKS_PER_TPS_OVERLOAD_WARNING = 5 * self::TARGET_TICKS_PER_SECOND;
|
private const TICKS_PER_TPS_OVERLOAD_WARNING = 5 * self::TARGET_TICKS_PER_SECOND;
|
||||||
private const TICKS_PER_STATS_REPORT = 300 * self::TARGET_TICKS_PER_SECOND;
|
private const TICKS_PER_STATS_REPORT = 300 * self::TARGET_TICKS_PER_SECOND;
|
||||||
|
|
||||||
|
private const DEFAULT_ASYNC_COMPRESSION_THRESHOLD = 10_000;
|
||||||
|
|
||||||
private static ?Server $instance = null;
|
private static ?Server $instance = null;
|
||||||
|
|
||||||
private TimeTrackingSleeperHandler $tickSleeper;
|
private TimeTrackingSleeperHandler $tickSleeper;
|
||||||
@ -266,6 +268,7 @@ class Server{
|
|||||||
|
|
||||||
private Network $network;
|
private Network $network;
|
||||||
private bool $networkCompressionAsync = true;
|
private bool $networkCompressionAsync = true;
|
||||||
|
private int $networkCompressionAsyncThreshold = self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD;
|
||||||
|
|
||||||
private Language $language;
|
private Language $language;
|
||||||
private bool $forceLanguage = false;
|
private bool $forceLanguage = false;
|
||||||
@ -908,6 +911,10 @@ class Server{
|
|||||||
ZlibCompressor::setInstance(new ZlibCompressor($netCompressionLevel, $netCompressionThreshold, ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE));
|
ZlibCompressor::setInstance(new ZlibCompressor($netCompressionLevel, $netCompressionThreshold, ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE));
|
||||||
|
|
||||||
$this->networkCompressionAsync = $this->configGroup->getPropertyBool("network.async-compression", true);
|
$this->networkCompressionAsync = $this->configGroup->getPropertyBool("network.async-compression", true);
|
||||||
|
$this->networkCompressionAsyncThreshold = max(
|
||||||
|
$this->configGroup->getPropertyInt("network.async-compression-threshold", self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD),
|
||||||
|
$netCompressionThreshold ?? self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD
|
||||||
|
);
|
||||||
|
|
||||||
EncryptionContext::$ENABLED = $this->configGroup->getPropertyBool("network.enable-encryption", true);
|
EncryptionContext::$ENABLED = $this->configGroup->getPropertyBool("network.enable-encryption", true);
|
||||||
|
|
||||||
@ -1047,6 +1054,11 @@ class Server{
|
|||||||
|
|
||||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_defaultGameMode($this->getGamemode()->getTranslatableName())));
|
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_defaultGameMode($this->getGamemode()->getTranslatableName())));
|
||||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
|
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_donate(TextFormat::AQUA . "https://patreon.com/pocketminemp" . TextFormat::RESET)));
|
||||||
|
|
||||||
|
$this->logger->alert($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_obsolete_warning1("4.x", "5.0")));
|
||||||
|
$this->logger->alert($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_obsolete_warning2("4.x", "2023-09-01")));
|
||||||
|
$this->logger->alert($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_obsolete_warning3("https://github.com/pmmp/PocketMine-MP/issues/5784")));
|
||||||
|
|
||||||
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
|
$this->logger->info($this->getLanguage()->translate(KnownTranslationFactory::pocketmine_server_startFinished(strval(round(microtime(true) - $this->startTime, 3)))));
|
||||||
|
|
||||||
$forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);
|
$forwarder = new BroadcastLoggerForwarder($this, $this->logger, $this->language);
|
||||||
@ -1375,7 +1387,7 @@ class Server{
|
|||||||
}
|
}
|
||||||
|
|
||||||
$promise = new CompressBatchPromise();
|
$promise = new CompressBatchPromise();
|
||||||
if(!$sync){
|
if(!$sync && strlen($buffer) >= $this->networkCompressionAsyncThreshold){
|
||||||
$task = new CompressBatchTask($buffer, $promise, $compressor);
|
$task = new CompressBatchTask($buffer, $promise, $compressor);
|
||||||
$this->asyncPool->submitTask($task);
|
$this->asyncPool->submitTask($task);
|
||||||
}else{
|
}else{
|
||||||
@ -1434,7 +1446,7 @@ class Server{
|
|||||||
|
|
||||||
private function forceShutdownExit() : void{
|
private function forceShutdownExit() : void{
|
||||||
$this->forceShutdown();
|
$this->forceShutdown();
|
||||||
Process::kill(Process::pid(), true);
|
Process::kill(Process::pid());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function forceShutdown() : void{
|
public function forceShutdown() : void{
|
||||||
@ -1502,7 +1514,7 @@ class Server{
|
|||||||
}catch(\Throwable $e){
|
}catch(\Throwable $e){
|
||||||
$this->logger->logException($e);
|
$this->logger->logException($e);
|
||||||
$this->logger->emergency("Crashed while crashing, killing process");
|
$this->logger->emergency("Crashed while crashing, killing process");
|
||||||
@Process::kill(Process::pid(), true);
|
@Process::kill(Process::pid());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1656,7 +1668,7 @@ class Server{
|
|||||||
echo "--- Waiting $spacing seconds to throttle automatic restart (you can kill the process safely now) ---" . PHP_EOL;
|
echo "--- Waiting $spacing seconds to throttle automatic restart (you can kill the process safely now) ---" . PHP_EOL;
|
||||||
sleep($spacing);
|
sleep($spacing);
|
||||||
}
|
}
|
||||||
@Process::kill(Process::pid(), true);
|
@Process::kill(Process::pid());
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ use function str_repeat;
|
|||||||
|
|
||||||
final class VersionInfo{
|
final class VersionInfo{
|
||||||
public const NAME = "PocketMine-MP";
|
public const NAME = "PocketMine-MP";
|
||||||
public const BASE_VERSION = "4.20.4";
|
public const BASE_VERSION = "4.23.3";
|
||||||
public const IS_DEVELOPMENT_BUILD = false;
|
public const IS_DEVELOPMENT_BUILD = false;
|
||||||
public const BUILD_CHANNEL = "stable";
|
public const BUILD_CHANNEL = "stable";
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
|||||||
namespace pocketmine\block;
|
namespace pocketmine\block;
|
||||||
|
|
||||||
use pocketmine\block\utils\BlockDataSerializer;
|
use pocketmine\block\utils\BlockDataSerializer;
|
||||||
|
use pocketmine\block\utils\SupportType;
|
||||||
use pocketmine\entity\Entity;
|
use pocketmine\entity\Entity;
|
||||||
use pocketmine\entity\projectile\Arrow;
|
use pocketmine\entity\projectile\Arrow;
|
||||||
use pocketmine\event\block\BlockBurnEvent;
|
use pocketmine\event\block\BlockBurnEvent;
|
||||||
@ -99,9 +100,13 @@ class Fire extends Flowable{
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function canBeSupportedBy(Block $block) : bool{
|
||||||
|
return $block->getSupportType(Facing::UP)->equals(SupportType::FULL());
|
||||||
|
}
|
||||||
|
|
||||||
public function onNearbyBlockChange() : void{
|
public function onNearbyBlockChange() : void{
|
||||||
$world = $this->position->getWorld();
|
$world = $this->position->getWorld();
|
||||||
if($this->getSide(Facing::DOWN)->isTransparent() && !$this->hasAdjacentFlammableBlocks()){
|
if(!$this->canBeSupportedBy($this->getSide(Facing::DOWN)) && !$this->hasAdjacentFlammableBlocks()){
|
||||||
$world->setBlock($this->position, VanillaBlocks::AIR());
|
$world->setBlock($this->position, VanillaBlocks::AIR());
|
||||||
}else{
|
}else{
|
||||||
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
$world->scheduleDelayedBlockUpdate($this->position, mt_rand(30, 40));
|
||||||
|
@ -25,6 +25,7 @@ namespace pocketmine\block;
|
|||||||
|
|
||||||
use pocketmine\block\tile\ShulkerBox as TileShulkerBox;
|
use pocketmine\block\tile\ShulkerBox as TileShulkerBox;
|
||||||
use pocketmine\block\utils\AnyFacingTrait;
|
use pocketmine\block\utils\AnyFacingTrait;
|
||||||
|
use pocketmine\block\utils\SupportType;
|
||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\math\Vector3;
|
use pocketmine\math\Vector3;
|
||||||
use pocketmine\player\Player;
|
use pocketmine\player\Player;
|
||||||
@ -103,4 +104,8 @@ class ShulkerBox extends Opaque{
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSupportType(int $facing) : SupportType{
|
||||||
|
return SupportType::NONE();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,20 @@ class DoubleChestInventory extends BaseInventory implements BlockInventory, Inve
|
|||||||
$this->right->setContents($rightContents);
|
$this->right->setContents($rightContents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{
|
||||||
|
$leftSize = $this->left->getSize();
|
||||||
|
return $slot < $leftSize ?
|
||||||
|
$this->left->getMatchingItemCount($slot, $test, $checkDamage, $checkTags) :
|
||||||
|
$this->right->getMatchingItemCount($slot - $leftSize, $test, $checkDamage, $checkTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSlotEmpty(int $index) : bool{
|
||||||
|
$leftSize = $this->left->getSize();
|
||||||
|
return $index < $leftSize ?
|
||||||
|
$this->left->isSlotEmpty($index) :
|
||||||
|
$this->right->isSlotEmpty($index - $leftSize);
|
||||||
|
}
|
||||||
|
|
||||||
protected function getOpenSound() : Sound{ return new ChestOpenSound(); }
|
protected function getOpenSound() : Sound{ return new ChestOpenSound(); }
|
||||||
|
|
||||||
protected function getCloseSound() : Sound{ return new ChestCloseSound(); }
|
protected function getCloseSound() : Sound{ return new ChestCloseSound(); }
|
||||||
|
@ -26,6 +26,7 @@ namespace pocketmine\block\tile;
|
|||||||
use pocketmine\item\Item;
|
use pocketmine\item\Item;
|
||||||
use pocketmine\item\Record;
|
use pocketmine\item\Record;
|
||||||
use pocketmine\nbt\tag\CompoundTag;
|
use pocketmine\nbt\tag\CompoundTag;
|
||||||
|
use pocketmine\world\sound\RecordStopSound;
|
||||||
|
|
||||||
class Jukebox extends Spawnable{
|
class Jukebox extends Spawnable{
|
||||||
private const TAG_RECORD = "RecordItem"; //Item CompoundTag
|
private const TAG_RECORD = "RecordItem"; //Item CompoundTag
|
||||||
@ -61,4 +62,8 @@ class Jukebox extends Spawnable{
|
|||||||
$nbt->setTag(self::TAG_RECORD, $this->record->nbtSerialize());
|
$nbt->setTag(self::TAG_RECORD, $this->record->nbtSerialize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function onBlockDestroyedHook() : void{
|
||||||
|
$this->position->getWorld()->addSound($this->position, new RecordStopSound());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,8 @@ class StatusCommand extends VanillaCommand{
|
|||||||
$worldName = $world->getFolderName() !== $world->getDisplayName() ? " (" . $world->getDisplayName() . ")" : "";
|
$worldName = $world->getFolderName() !== $world->getDisplayName() ? " (" . $world->getDisplayName() . ")" : "";
|
||||||
$timeColor = $world->getTickRateTime() > 40 ? TextFormat::RED : TextFormat::YELLOW;
|
$timeColor = $world->getTickRateTime() > 40 ? TextFormat::RED : TextFormat::YELLOW;
|
||||||
$sender->sendMessage(TextFormat::GOLD . "World \"{$world->getFolderName()}\"$worldName: " .
|
$sender->sendMessage(TextFormat::GOLD . "World \"{$world->getFolderName()}\"$worldName: " .
|
||||||
TextFormat::RED . number_format(count($world->getLoadedChunks())) . TextFormat::GREEN . " chunks, " .
|
TextFormat::RED . number_format(count($world->getLoadedChunks())) . TextFormat::GREEN . " loaded chunks, " .
|
||||||
|
TextFormat::RED . number_format(count($world->getTickingChunks())) . TextFormat::GREEN . " ticking chunks, " .
|
||||||
TextFormat::RED . number_format(count($world->getEntities())) . TextFormat::GREEN . " entities. " .
|
TextFormat::RED . number_format(count($world->getEntities())) . TextFormat::GREEN . " entities. " .
|
||||||
"Time $timeColor" . round($world->getTickRateTime(), 2) . "ms"
|
"Time $timeColor" . round($world->getTickRateTime(), 2) . "ms"
|
||||||
);
|
);
|
||||||
|
@ -90,4 +90,4 @@ while(!feof($socket)){
|
|||||||
//For simplicity's sake, we don't bother with a graceful shutdown here.
|
//For simplicity's sake, we don't bother with a graceful shutdown here.
|
||||||
//The parent process would normally forcibly terminate the child process anyway, so we only reach this point if the
|
//The parent process would normally forcibly terminate the child process anyway, so we only reach this point if the
|
||||||
//parent process was terminated forcibly and didn't clean up after itself.
|
//parent process was terminated forcibly and didn't clean up after itself.
|
||||||
Process::kill(Process::pid(), false);
|
Process::kill(Process::pid());
|
||||||
|
@ -248,7 +248,6 @@ class CrashDump{
|
|||||||
if(file_exists($filePath)){
|
if(file_exists($filePath)){
|
||||||
$reflection = new \ReflectionClass(PluginBase::class);
|
$reflection = new \ReflectionClass(PluginBase::class);
|
||||||
$file = $reflection->getProperty("file");
|
$file = $reflection->getProperty("file");
|
||||||
$file->setAccessible(true);
|
|
||||||
foreach($this->server->getPluginManager()->getPlugins() as $plugin){
|
foreach($this->server->getPluginManager()->getPlugins() as $plugin){
|
||||||
$filePath = Filesystem::cleanPath($file->getValue($plugin));
|
$filePath = Filesystem::cleanPath($file->getValue($plugin));
|
||||||
if(str_starts_with($frameCleanPath, $filePath)){
|
if(str_starts_with($frameCleanPath, $filePath)){
|
||||||
|
@ -781,29 +781,21 @@ abstract class Entity{
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected function broadcastMovement(bool $teleport = false) : void{
|
protected function broadcastMovement(bool $teleport = false) : void{
|
||||||
if($teleport){
|
NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
|
||||||
//TODO: HACK! workaround for https://github.com/pmmp/PocketMine-MP/issues/4394
|
$this->id,
|
||||||
//this happens because MoveActor*Packet doesn't clear interpolation targets on the client, so the entity
|
$this->getOffsetPosition($this->location),
|
||||||
//snaps to the teleport position, but then lerps back to the original position if a normal movement for the
|
$this->location->pitch,
|
||||||
//entity was recently broadcasted. This can be seen with players throwing ender pearls.
|
$this->location->yaw,
|
||||||
//TODO: remove this if the bug ever gets fixed (lol)
|
$this->location->yaw,
|
||||||
foreach($this->hasSpawned as $player){
|
(
|
||||||
$this->despawnFrom($player);
|
//TODO: We should be setting FLAG_TELEPORT here to disable client-side movement interpolation, but it
|
||||||
$this->spawnTo($player);
|
//breaks player teleporting (observers see the player rubberband back to the pre-teleport position while
|
||||||
}
|
//the teleported player sees themselves at the correct position), and does nothing whatsoever for
|
||||||
}else{
|
//non-player entities (movement is still interpolated). Both of these are client bugs.
|
||||||
NetworkBroadcastUtils::broadcastPackets($this->hasSpawned, [MoveActorAbsolutePacket::create(
|
//See https://github.com/pmmp/PocketMine-MP/issues/4394
|
||||||
$this->id,
|
($this->onGround ? MoveActorAbsolutePacket::FLAG_GROUND : 0)
|
||||||
$this->getOffsetPosition($this->location),
|
)
|
||||||
$this->location->pitch,
|
)]);
|
||||||
$this->location->yaw,
|
|
||||||
$this->location->yaw,
|
|
||||||
(
|
|
||||||
//TODO: if the above hack for #4394 gets removed, we should be setting FLAG_TELEPORT here
|
|
||||||
($this->onGround ? MoveActorAbsolutePacket::FLAG_GROUND : 0)
|
|
||||||
)
|
|
||||||
)]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function broadcastMotion() : void{
|
protected function broadcastMotion() : void{
|
||||||
|
@ -92,7 +92,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
|||||||
private const TAG_XP_PROGRESS = "XpP"; //TAG_Float
|
private const TAG_XP_PROGRESS = "XpP"; //TAG_Float
|
||||||
private const TAG_LIFETIME_XP_TOTAL = "XpTotal"; //TAG_Int
|
private const TAG_LIFETIME_XP_TOTAL = "XpTotal"; //TAG_Int
|
||||||
private const TAG_XP_SEED = "XpSeed"; //TAG_Int
|
private const TAG_XP_SEED = "XpSeed"; //TAG_Int
|
||||||
private const TAG_NAME_TAG = "NameTag"; //TAG_String
|
|
||||||
private const TAG_SKIN = "Skin"; //TAG_Compound
|
private const TAG_SKIN = "Skin"; //TAG_Compound
|
||||||
private const TAG_SKIN_NAME = "Name"; //TAG_String
|
private const TAG_SKIN_NAME = "Name"; //TAG_String
|
||||||
private const TAG_SKIN_DATA = "Data"; //TAG_ByteArray
|
private const TAG_SKIN_DATA = "Data"; //TAG_ByteArray
|
||||||
@ -245,10 +244,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
|
|||||||
* For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
|
* For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
|
||||||
*/
|
*/
|
||||||
protected function initHumanData(CompoundTag $nbt) : void{
|
protected function initHumanData(CompoundTag $nbt) : void{
|
||||||
if(($nameTagTag = $nbt->getTag(self::TAG_NAME_TAG)) instanceof StringTag){
|
|
||||||
$this->setNameTag($nameTagTag->getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: use of NIL UUID for namespace is a hack; we should provide a proper UUID for the namespace
|
//TODO: use of NIL UUID for namespace is a hack; we should provide a proper UUID for the namespace
|
||||||
$this->uuid = Uuid::uuid3(Uuid::NIL, ((string) $this->getId()) . $this->skin->getSkinData() . $this->getNameTag());
|
$this->uuid = Uuid::uuid3(Uuid::NIL, ((string) $this->getId()) . $this->skin->getSkinData() . $this->getNameTag());
|
||||||
}
|
}
|
||||||
|
@ -108,13 +108,23 @@ abstract class BaseInventory implements Inventory{
|
|||||||
$this->onContentChange($oldContents);
|
$this->onContentChange($oldContents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for utility functions which search the inventory.
|
||||||
|
* TODO: make this abstract instead of providing a slow default implementation (BC break)
|
||||||
|
*/
|
||||||
|
protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{
|
||||||
|
$item = $this->getItem($slot);
|
||||||
|
return $item->equals($test, $checkDamage, $checkTags) ? $item->getCount() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
public function contains(Item $item) : bool{
|
public function contains(Item $item) : bool{
|
||||||
$count = max(1, $item->getCount());
|
$count = max(1, $item->getCount());
|
||||||
$checkDamage = !$item->hasAnyDamageValue();
|
$checkDamage = !$item->hasAnyDamageValue();
|
||||||
$checkTags = $item->hasNamedTag();
|
$checkTags = $item->hasNamedTag();
|
||||||
foreach($this->getContents() as $i){
|
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
|
||||||
if($item->equals($i, $checkDamage, $checkTags)){
|
$slotCount = $this->getMatchingItemCount($i, $item, $checkDamage, $checkTags);
|
||||||
$count -= $i->getCount();
|
if($slotCount > 0){
|
||||||
|
$count -= $slotCount;
|
||||||
if($count <= 0){
|
if($count <= 0){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -128,9 +138,9 @@ abstract class BaseInventory implements Inventory{
|
|||||||
$slots = [];
|
$slots = [];
|
||||||
$checkDamage = !$item->hasAnyDamageValue();
|
$checkDamage = !$item->hasAnyDamageValue();
|
||||||
$checkTags = $item->hasNamedTag();
|
$checkTags = $item->hasNamedTag();
|
||||||
foreach($this->getContents() as $index => $i){
|
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
|
||||||
if($item->equals($i, $checkDamage, $checkTags)){
|
if($this->getMatchingItemCount($i, $item, $checkDamage, $checkTags) > 0){
|
||||||
$slots[$index] = $i;
|
$slots[$i] = $this->getItem($i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,18 +152,9 @@ abstract class BaseInventory implements Inventory{
|
|||||||
$checkDamage = $exact || !$item->hasAnyDamageValue();
|
$checkDamage = $exact || !$item->hasAnyDamageValue();
|
||||||
$checkTags = $exact || $item->hasNamedTag();
|
$checkTags = $exact || $item->hasNamedTag();
|
||||||
|
|
||||||
foreach($this->getContents() as $index => $i){
|
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
|
||||||
if($item->equals($i, $checkDamage, $checkTags) && ($i->getCount() === $count || (!$exact && $i->getCount() > $count))){
|
$slotCount = $this->getMatchingItemCount($i, $item, $checkDamage, $checkTags);
|
||||||
return $index;
|
if($slotCount > 0 && ($slotCount === $count || (!$exact && $slotCount > $count))){
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function firstEmpty() : int{
|
|
||||||
foreach($this->getContents(true) as $i => $slot){
|
|
||||||
if($slot->isNull()){
|
|
||||||
return $i;
|
return $i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,6 +162,20 @@ abstract class BaseInventory implements Inventory{
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function firstEmpty() : int{
|
||||||
|
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
|
||||||
|
if($this->isSlotEmpty($i)){
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: make this abstract and force implementations to implement it properly (BC break)
|
||||||
|
* This default implementation works, but is slow.
|
||||||
|
*/
|
||||||
public function isSlotEmpty(int $index) : bool{
|
public function isSlotEmpty(int $index) : bool{
|
||||||
return $this->getItem($index)->isNull();
|
return $this->getItem($index)->isNull();
|
||||||
}
|
}
|
||||||
@ -171,14 +186,16 @@ abstract class BaseInventory implements Inventory{
|
|||||||
|
|
||||||
public function getAddableItemQuantity(Item $item) : int{
|
public function getAddableItemQuantity(Item $item) : int{
|
||||||
$count = $item->getCount();
|
$count = $item->getCount();
|
||||||
|
$maxStackSize = min($this->getMaxStackSize(), $item->getMaxStackSize());
|
||||||
|
|
||||||
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
||||||
$slot = $this->getItem($i);
|
if($this->isSlotEmpty($i)){
|
||||||
if($item->canStackWith($slot)){
|
$count -= $maxStackSize;
|
||||||
if(($diff = min($slot->getMaxStackSize(), $item->getMaxStackSize()) - $slot->getCount()) > 0){
|
}else{
|
||||||
|
$slotCount = $this->getMatchingItemCount($i, $item, true, true);
|
||||||
|
if($slotCount > 0 && ($diff = $maxStackSize - $slotCount) > 0){
|
||||||
$count -= $diff;
|
$count -= $diff;
|
||||||
}
|
}
|
||||||
}elseif($slot->isNull()){
|
|
||||||
$count -= min($this->getMaxStackSize(), $item->getMaxStackSize());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if($count <= 0){
|
if($count <= 0){
|
||||||
@ -212,23 +229,29 @@ abstract class BaseInventory implements Inventory{
|
|||||||
return $returnSlots;
|
return $returnSlots;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function internalAddItem(Item $slot) : Item{
|
private function internalAddItem(Item $newItem) : Item{
|
||||||
$emptySlots = [];
|
$emptySlots = [];
|
||||||
|
|
||||||
|
$maxStackSize = min($this->getMaxStackSize(), $newItem->getMaxStackSize());
|
||||||
|
|
||||||
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
||||||
$item = $this->getItem($i);
|
if($this->isSlotEmpty($i)){
|
||||||
if($item->isNull()){
|
|
||||||
$emptySlots[] = $i;
|
$emptySlots[] = $i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
$slotCount = $this->getMatchingItemCount($i, $newItem, true, true);
|
||||||
|
if($slotCount === 0){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if($slot->canStackWith($item) && $item->getCount() < $item->getMaxStackSize()){
|
if($slotCount < $maxStackSize){
|
||||||
$amount = min($item->getMaxStackSize() - $item->getCount(), $slot->getCount(), $this->getMaxStackSize());
|
$amount = min($maxStackSize - $slotCount, $newItem->getCount());
|
||||||
if($amount > 0){
|
if($amount > 0){
|
||||||
$slot->setCount($slot->getCount() - $amount);
|
$newItem->setCount($newItem->getCount() - $amount);
|
||||||
$item->setCount($item->getCount() + $amount);
|
$slotItem = $this->getItem($i);
|
||||||
$this->setItem($i, $item);
|
$slotItem->setCount($slotItem->getCount() + $amount);
|
||||||
if($slot->getCount() <= 0){
|
$this->setItem($i, $slotItem);
|
||||||
|
if($newItem->getCount() <= 0){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,65 +260,67 @@ abstract class BaseInventory implements Inventory{
|
|||||||
|
|
||||||
if(count($emptySlots) > 0){
|
if(count($emptySlots) > 0){
|
||||||
foreach($emptySlots as $slotIndex){
|
foreach($emptySlots as $slotIndex){
|
||||||
$amount = min($slot->getMaxStackSize(), $slot->getCount(), $this->getMaxStackSize());
|
$amount = min($maxStackSize, $newItem->getCount());
|
||||||
$slot->setCount($slot->getCount() - $amount);
|
$newItem->setCount($newItem->getCount() - $amount);
|
||||||
$item = clone $slot;
|
$slotItem = clone $newItem;
|
||||||
$item->setCount($amount);
|
$slotItem->setCount($amount);
|
||||||
$this->setItem($slotIndex, $item);
|
$this->setItem($slotIndex, $slotItem);
|
||||||
if($slot->getCount() <= 0){
|
if($newItem->getCount() <= 0){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $slot;
|
return $newItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function remove(Item $item) : void{
|
public function remove(Item $item) : void{
|
||||||
$checkDamage = !$item->hasAnyDamageValue();
|
$checkDamage = !$item->hasAnyDamageValue();
|
||||||
$checkTags = $item->hasNamedTag();
|
$checkTags = $item->hasNamedTag();
|
||||||
|
|
||||||
foreach($this->getContents() as $index => $i){
|
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
|
||||||
if($item->equals($i, $checkDamage, $checkTags)){
|
if($this->getMatchingItemCount($i, $item, $checkDamage, $checkTags) > 0){
|
||||||
$this->clear($index);
|
$this->clear($i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function removeItem(Item ...$slots) : array{
|
public function removeItem(Item ...$slots) : array{
|
||||||
/** @var Item[] $itemSlots */
|
/** @var Item[] $searchItems */
|
||||||
/** @var Item[] $slots */
|
/** @var Item[] $slots */
|
||||||
$itemSlots = [];
|
$searchItems = [];
|
||||||
foreach($slots as $slot){
|
foreach($slots as $slot){
|
||||||
if(!$slot->isNull()){
|
if(!$slot->isNull()){
|
||||||
$itemSlots[] = clone $slot;
|
$searchItems[] = clone $slot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
|
||||||
$item = $this->getItem($i);
|
if($this->isSlotEmpty($i)){
|
||||||
if($item->isNull()){
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach($itemSlots as $index => $slot){
|
foreach($searchItems as $index => $search){
|
||||||
if($slot->equals($item, !$slot->hasAnyDamageValue(), $slot->hasNamedTag())){
|
$slotCount = $this->getMatchingItemCount($i, $search, !$search->hasAnyDamageValue(), $search->hasNamedTag());
|
||||||
$amount = min($item->getCount(), $slot->getCount());
|
if($slotCount > 0){
|
||||||
$slot->setCount($slot->getCount() - $amount);
|
$amount = min($slotCount, $search->getCount());
|
||||||
$item->setCount($item->getCount() - $amount);
|
$search->setCount($search->getCount() - $amount);
|
||||||
$this->setItem($i, $item);
|
|
||||||
if($slot->getCount() <= 0){
|
$slotItem = $this->getItem($i);
|
||||||
unset($itemSlots[$index]);
|
$slotItem->setCount($slotItem->getCount() - $amount);
|
||||||
|
$this->setItem($i, $slotItem);
|
||||||
|
if($search->getCount() <= 0){
|
||||||
|
unset($searchItems[$index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(count($itemSlots) === 0){
|
if(count($searchItems) === 0){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $itemSlots;
|
return $searchItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clear(int $index) : void{
|
public function clear(int $index) : void{
|
||||||
|
@ -85,6 +85,10 @@ class DelegateInventory extends BaseInventory{
|
|||||||
$this->backingInventory->setContents($items);
|
$this->backingInventory->setContents($items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isSlotEmpty(int $index) : bool{
|
||||||
|
return $this->backingInventory->isSlotEmpty($index);
|
||||||
|
}
|
||||||
|
|
||||||
protected function onSlotChange(int $index, Item $before) : void{
|
protected function onSlotChange(int $index, Item $before) : void{
|
||||||
if($this->backingInventoryChanging){
|
if($this->backingInventoryChanging){
|
||||||
parent::onSlotChange($index, $before);
|
parent::onSlotChange($index, $before);
|
||||||
|
@ -83,4 +83,13 @@ class SimpleInventory extends BaseInventory{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{
|
||||||
|
$slotItem = $this->slots[$slot];
|
||||||
|
return $slotItem !== null && $slotItem->equals($test, $checkDamage, $checkTags) ? $slotItem->getCount() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSlotEmpty(int $index) : bool{
|
||||||
|
return $this->slots[$index] === null || $this->slots[$index]->isNull();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,7 @@ use const OPENSSL_ALGO_SHA384;
|
|||||||
use const STR_PAD_LEFT;
|
use const STR_PAD_LEFT;
|
||||||
|
|
||||||
final class JwtUtils{
|
final class JwtUtils{
|
||||||
|
public const BEDROCK_SIGNING_KEY_CURVE_NAME = "secp384r1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string[]
|
* @return string[]
|
||||||
@ -203,6 +204,17 @@ final class JwtUtils{
|
|||||||
if($signingKeyOpenSSL === false){
|
if($signingKeyOpenSSL === false){
|
||||||
throw new JwtException("OpenSSL failed to parse key: " . openssl_error_string());
|
throw new JwtException("OpenSSL failed to parse key: " . openssl_error_string());
|
||||||
}
|
}
|
||||||
|
$details = openssl_pkey_get_details($signingKeyOpenSSL);
|
||||||
|
if($details === false){
|
||||||
|
throw new JwtException("OpenSSL failed to get details from key: " . openssl_error_string());
|
||||||
|
}
|
||||||
|
if(!isset($details['ec']['curve_name'])){
|
||||||
|
throw new JwtException("Expected an EC key");
|
||||||
|
}
|
||||||
|
$curve = $details['ec']['curve_name'];
|
||||||
|
if($curve !== self::BEDROCK_SIGNING_KEY_CURVE_NAME){
|
||||||
|
throw new JwtException("Key must belong to curve " . self::BEDROCK_SIGNING_KEY_CURVE_NAME . ", got $curve");
|
||||||
|
}
|
||||||
return $signingKeyOpenSSL;
|
return $signingKeyOpenSSL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,7 @@ use pocketmine\network\mcpe\protocol\types\AbilitiesLayer;
|
|||||||
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
use pocketmine\network\mcpe\protocol\types\BlockPosition;
|
||||||
use pocketmine\network\mcpe\protocol\types\command\CommandData;
|
use pocketmine\network\mcpe\protocol\types\command\CommandData;
|
||||||
use pocketmine\network\mcpe\protocol\types\command\CommandEnum;
|
use pocketmine\network\mcpe\protocol\types\command\CommandEnum;
|
||||||
|
use pocketmine\network\mcpe\protocol\types\command\CommandOverload;
|
||||||
use pocketmine\network\mcpe\protocol\types\command\CommandParameter;
|
use pocketmine\network\mcpe\protocol\types\command\CommandParameter;
|
||||||
use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
|
use pocketmine\network\mcpe\protocol\types\command\CommandPermissions;
|
||||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||||
@ -107,7 +108,6 @@ use pocketmine\utils\BinaryDataException;
|
|||||||
use pocketmine\utils\BinaryStream;
|
use pocketmine\utils\BinaryStream;
|
||||||
use pocketmine\utils\ObjectSet;
|
use pocketmine\utils\ObjectSet;
|
||||||
use pocketmine\utils\TextFormat;
|
use pocketmine\utils\TextFormat;
|
||||||
use pocketmine\utils\Utils;
|
|
||||||
use pocketmine\world\Position;
|
use pocketmine\world\Position;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
use function array_values;
|
use function array_values;
|
||||||
@ -950,8 +950,9 @@ class NetworkSession{
|
|||||||
0,
|
0,
|
||||||
$aliasObj,
|
$aliasObj,
|
||||||
[
|
[
|
||||||
[CommandParameter::standard("args", AvailableCommandsPacket::ARG_TYPE_RAWTEXT, 0, true)]
|
new CommandOverload(chaining: false, parameters: [CommandParameter::standard("args", AvailableCommandsPacket::ARG_TYPE_RAWTEXT, 0, true)])
|
||||||
]
|
],
|
||||||
|
chainedSubCommandData: []
|
||||||
);
|
);
|
||||||
|
|
||||||
$commandData[$command->getLabel()] = $data;
|
$commandData[$command->getLabel()] = $data;
|
||||||
@ -1000,8 +1001,6 @@ class NetworkSession{
|
|||||||
* @phpstan-param \Closure() : void $onCompletion
|
* @phpstan-param \Closure() : void $onCompletion
|
||||||
*/
|
*/
|
||||||
public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{
|
public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{
|
||||||
Utils::validateCallableSignature(function() : void{}, $onCompletion);
|
|
||||||
|
|
||||||
$world = $this->player->getLocation()->getWorld();
|
$world = $this->player->getLocation()->getWorld();
|
||||||
ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ)->onResolve(
|
ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ)->onResolve(
|
||||||
|
|
||||||
|
@ -138,6 +138,6 @@ final class StandardEntityEventBroadcaster implements EntityEventBroadcaster{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onEmote(array $recipients, Human $from, string $emoteId) : void{
|
public function onEmote(array $recipients, Human $from, string $emoteId) : void{
|
||||||
$this->sendDataPacket($recipients, EmotePacket::create($from->getId(), $emoteId, EmotePacket::FLAG_SERVER));
|
$this->sendDataPacket($recipients, EmotePacket::create($from->getId(), $emoteId, "", "", EmotePacket::FLAG_SERVER | EmotePacket::FLAG_MUTE_ANNOUNCEMENT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,13 +32,27 @@ use pocketmine\scheduler\AsyncTask;
|
|||||||
use function base64_decode;
|
use function base64_decode;
|
||||||
use function igbinary_serialize;
|
use function igbinary_serialize;
|
||||||
use function igbinary_unserialize;
|
use function igbinary_unserialize;
|
||||||
use function openssl_error_string;
|
|
||||||
use function time;
|
use function time;
|
||||||
|
|
||||||
class ProcessLoginTask extends AsyncTask{
|
class ProcessLoginTask extends AsyncTask{
|
||||||
private const TLS_KEY_ON_COMPLETION = "completion";
|
private const TLS_KEY_ON_COMPLETION = "completion";
|
||||||
|
|
||||||
public const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
|
/**
|
||||||
|
* Old Mojang root auth key. This was used since the introduction of Xbox Live authentication in 0.15.0.
|
||||||
|
* This key is expected to be replaced by the key below in the future, but this has not yet happened as of
|
||||||
|
* 2023-07-01.
|
||||||
|
* Ideally we would place a time expiry on this key, but since Mojang have not given a hard date for the key change,
|
||||||
|
* and one bad guess has already caused a major outage, we can't do this.
|
||||||
|
* TODO: This needs to be removed as soon as the new key is deployed by Mojang's authentication servers.
|
||||||
|
*/
|
||||||
|
public const MOJANG_OLD_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New Mojang root auth key. Mojang notified third-party developers of this change prior to the release of 1.20.0.
|
||||||
|
* Expectations were that this would be used starting a "couple of weeks" after the release, but as of 2023-07-01,
|
||||||
|
* it has not yet been deployed.
|
||||||
|
*/
|
||||||
|
public const MOJANG_ROOT_PUBLIC_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp";
|
||||||
|
|
||||||
private const CLOCK_DRIFT_MAX = 60;
|
private const CLOCK_DRIFT_MAX = 60;
|
||||||
|
|
||||||
@ -141,7 +155,8 @@ class ProcessLoginTask extends AsyncTask{
|
|||||||
try{
|
try{
|
||||||
$signingKeyOpenSSL = JwtUtils::parseDerPublicKey($headerDerKey);
|
$signingKeyOpenSSL = JwtUtils::parseDerPublicKey($headerDerKey);
|
||||||
}catch(JwtException $e){
|
}catch(JwtException $e){
|
||||||
throw new VerifyLoginException("Invalid JWT public key: " . openssl_error_string());
|
//TODO: we shouldn't be showing this internal information to the client
|
||||||
|
throw new VerifyLoginException("Invalid JWT public key: " . $e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
if(!JwtUtils::verify($jwt, $signingKeyOpenSSL)){
|
if(!JwtUtils::verify($jwt, $signingKeyOpenSSL)){
|
||||||
@ -151,7 +166,7 @@ class ProcessLoginTask extends AsyncTask{
|
|||||||
throw new VerifyLoginException($e->getMessage(), 0, $e);
|
throw new VerifyLoginException($e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY){
|
if($headers->x5u === self::MOJANG_ROOT_PUBLIC_KEY || $headers->x5u === self::MOJANG_OLD_ROOT_PUBLIC_KEY){
|
||||||
$this->authenticated = true; //we're signed into xbox live
|
$this->authenticated = true; //we're signed into xbox live
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +196,12 @@ class ProcessLoginTask extends AsyncTask{
|
|||||||
if($identityPublicKey === false){
|
if($identityPublicKey === false){
|
||||||
throw new VerifyLoginException("Invalid identityPublicKey: base64 error decoding");
|
throw new VerifyLoginException("Invalid identityPublicKey: base64 error decoding");
|
||||||
}
|
}
|
||||||
|
try{
|
||||||
|
//verify key format and parameters
|
||||||
|
JwtUtils::parseDerPublicKey($identityPublicKey);
|
||||||
|
}catch(JwtException $e){
|
||||||
|
throw new VerifyLoginException("Invalid identityPublicKey: " . $e->getMessage(), 0, $e);
|
||||||
|
}
|
||||||
$currentPublicKey = $identityPublicKey; //if there are further links, the next link should be signed with this
|
$currentPublicKey = $identityPublicKey; //if there are further links, the next link should be signed with this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\network\mcpe\compression;
|
namespace pocketmine\network\mcpe\compression;
|
||||||
|
|
||||||
use pocketmine\utils\Utils;
|
|
||||||
use function array_push;
|
use function array_push;
|
||||||
|
|
||||||
class CompressBatchPromise{
|
class CompressBatchPromise{
|
||||||
@ -42,9 +41,6 @@ class CompressBatchPromise{
|
|||||||
*/
|
*/
|
||||||
public function onResolve(\Closure ...$callbacks) : void{
|
public function onResolve(\Closure ...$callbacks) : void{
|
||||||
$this->checkCancelled();
|
$this->checkCancelled();
|
||||||
foreach($callbacks as $callback){
|
|
||||||
Utils::validateCallableSignature(function(CompressBatchPromise $promise) : void{}, $callback);
|
|
||||||
}
|
|
||||||
if($this->result !== null){
|
if($this->result !== null){
|
||||||
foreach($callbacks as $callback){
|
foreach($callbacks as $callback){
|
||||||
$callback($this);
|
$callback($this);
|
||||||
|
@ -48,8 +48,8 @@ final class RuntimeBlockMapping{
|
|||||||
private array $legacyToRuntimeMap = [];
|
private array $legacyToRuntimeMap = [];
|
||||||
/** @var int[] */
|
/** @var int[] */
|
||||||
private array $runtimeToLegacyMap = [];
|
private array $runtimeToLegacyMap = [];
|
||||||
/** @var CompoundTag[] */
|
/** @var CompoundTag[]|null */
|
||||||
private array $bedrockKnownStates;
|
private ?array $bedrockKnownStates = null;
|
||||||
|
|
||||||
private static function make() : self{
|
private static function make() : self{
|
||||||
return new self(
|
return new self(
|
||||||
@ -59,7 +59,7 @@ final class RuntimeBlockMapping{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $keyIndex
|
* @param string[] $keyIndex
|
||||||
* @param (ByteTag|StringTag|IntTag)[][] $valueIndex
|
* @param (ByteTag|StringTag|IntTag)[][] $valueIndex
|
||||||
* @phpstan-param array<string, string> $keyIndex
|
* @phpstan-param array<string, string> $keyIndex
|
||||||
* @phpstan-param array<int, array<int|string, ByteTag|IntTag|StringTag>> $valueIndex
|
* @phpstan-param array<int, array<int|string, ByteTag|IntTag|StringTag>> $valueIndex
|
||||||
@ -85,25 +85,13 @@ final class RuntimeBlockMapping{
|
|||||||
return $newTag;
|
return $newTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(string $canonicalBlockStatesFile, string $r12ToCurrentBlockMapFile){
|
public function __construct(
|
||||||
$stream = new BinaryStream(Filesystem::fileGetContents($canonicalBlockStatesFile));
|
private string $canonicalBlockStatesFile,
|
||||||
$list = [];
|
string $r12ToCurrentBlockMapFile
|
||||||
$nbtReader = new NetworkNbtSerializer();
|
){
|
||||||
|
//do not cache this - we only need it to set up mappings under normal circumstances
|
||||||
|
$bedrockKnownStates = $this->loadBedrockKnownStates();
|
||||||
|
|
||||||
$keyIndex = [];
|
|
||||||
$valueIndex = [];
|
|
||||||
while(!$stream->feof()){
|
|
||||||
$offset = $stream->getOffset();
|
|
||||||
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
|
|
||||||
$stream->setOffset($offset);
|
|
||||||
$list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex);
|
|
||||||
}
|
|
||||||
$this->bedrockKnownStates = $list;
|
|
||||||
|
|
||||||
$this->setupLegacyMappings($r12ToCurrentBlockMapFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setupLegacyMappings(string $r12ToCurrentBlockMapFile) : void{
|
|
||||||
$legacyIdMap = LegacyBlockIdToStringIdMap::getInstance();
|
$legacyIdMap = LegacyBlockIdToStringIdMap::getInstance();
|
||||||
/** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
|
/** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
|
||||||
$legacyStateMap = [];
|
$legacyStateMap = [];
|
||||||
@ -123,7 +111,7 @@ final class RuntimeBlockMapping{
|
|||||||
* @var int[][] $idToStatesMap string id -> int[] list of candidate state indices
|
* @var int[][] $idToStatesMap string id -> int[] list of candidate state indices
|
||||||
*/
|
*/
|
||||||
$idToStatesMap = [];
|
$idToStatesMap = [];
|
||||||
foreach($this->bedrockKnownStates as $k => $state){
|
foreach($bedrockKnownStates as $k => $state){
|
||||||
$idToStatesMap[$state->getString("name")][] = $k;
|
$idToStatesMap[$state->getString("name")][] = $k;
|
||||||
}
|
}
|
||||||
foreach($legacyStateMap as $pair){
|
foreach($legacyStateMap as $pair){
|
||||||
@ -142,7 +130,7 @@ final class RuntimeBlockMapping{
|
|||||||
throw new \RuntimeException("Mapped new state does not appear in network table");
|
throw new \RuntimeException("Mapped new state does not appear in network table");
|
||||||
}
|
}
|
||||||
foreach($idToStatesMap[$mappedName] as $k){
|
foreach($idToStatesMap[$mappedName] as $k){
|
||||||
$networkState = $this->bedrockKnownStates[$k];
|
$networkState = $bedrockKnownStates[$k];
|
||||||
if($mappedState->equals($networkState)){
|
if($mappedState->equals($networkState)){
|
||||||
$this->registerMapping($k, $id, $data);
|
$this->registerMapping($k, $id, $data);
|
||||||
continue 2;
|
continue 2;
|
||||||
@ -152,6 +140,25 @@ final class RuntimeBlockMapping{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return CompoundTag[]
|
||||||
|
*/
|
||||||
|
private function loadBedrockKnownStates() : array{
|
||||||
|
$stream = new BinaryStream(Filesystem::fileGetContents($this->canonicalBlockStatesFile));
|
||||||
|
$list = [];
|
||||||
|
$nbtReader = new NetworkNbtSerializer();
|
||||||
|
|
||||||
|
$keyIndex = [];
|
||||||
|
$valueIndex = [];
|
||||||
|
while(!$stream->feof()){
|
||||||
|
$offset = $stream->getOffset();
|
||||||
|
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
|
||||||
|
$stream->setOffset($offset);
|
||||||
|
$list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex);
|
||||||
|
}
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
public function toRuntimeId(int $internalStateId) : int{
|
public function toRuntimeId(int $internalStateId) : int{
|
||||||
return $this->legacyToRuntimeMap[$internalStateId] ?? $this->legacyToRuntimeMap[BlockLegacyIds::INFO_UPDATE << Block::INTERNAL_METADATA_BITS];
|
return $this->legacyToRuntimeMap[$internalStateId] ?? $this->legacyToRuntimeMap[BlockLegacyIds::INFO_UPDATE << Block::INTERNAL_METADATA_BITS];
|
||||||
}
|
}
|
||||||
@ -166,9 +173,14 @@ final class RuntimeBlockMapping{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* WARNING: This method may load the palette from disk, which is a slow operation.
|
||||||
|
* Afterwards, it will cache the palette in memory, which requires (in some cases) tens of MB of memory.
|
||||||
|
* Avoid using this where possible.
|
||||||
|
*
|
||||||
|
* @deprecated
|
||||||
* @return CompoundTag[]
|
* @return CompoundTag[]
|
||||||
*/
|
*/
|
||||||
public function getBedrockKnownStates() : array{
|
public function getBedrockKnownStates() : array{
|
||||||
return $this->bedrockKnownStates;
|
return $this->bedrockKnownStates ??= $this->loadBedrockKnownStates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ use function hex2bin;
|
|||||||
use function openssl_digest;
|
use function openssl_digest;
|
||||||
use function openssl_error_string;
|
use function openssl_error_string;
|
||||||
use function openssl_pkey_derive;
|
use function openssl_pkey_derive;
|
||||||
|
use function openssl_pkey_get_details;
|
||||||
use function str_pad;
|
use function str_pad;
|
||||||
use const STR_PAD_LEFT;
|
use const STR_PAD_LEFT;
|
||||||
|
|
||||||
@ -42,7 +43,20 @@ final class EncryptionUtils{
|
|||||||
//NOOP
|
//NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function validateKey(\OpenSSLAsymmetricKey $key) : void{
|
||||||
|
$keyDetails = Utils::assumeNotFalse(openssl_pkey_get_details($key));
|
||||||
|
if(!isset($keyDetails["ec"]["curve_name"])){
|
||||||
|
throw new \InvalidArgumentException("Key must be an EC key");
|
||||||
|
}
|
||||||
|
$curveName = $keyDetails["ec"]["curve_name"];
|
||||||
|
if($curveName !== JwtUtils::BEDROCK_SIGNING_KEY_CURVE_NAME){
|
||||||
|
throw new \InvalidArgumentException("Key must belong to the " . JwtUtils::BEDROCK_SIGNING_KEY_CURVE_NAME . " elliptic curve, got $curveName");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function generateSharedSecret(\OpenSSLAsymmetricKey $localPriv, \OpenSSLAsymmetricKey $remotePub) : \GMP{
|
public static function generateSharedSecret(\OpenSSLAsymmetricKey $localPriv, \OpenSSLAsymmetricKey $remotePub) : \GMP{
|
||||||
|
self::validateKey($localPriv);
|
||||||
|
self::validateKey($remotePub);
|
||||||
$hexSecret = openssl_pkey_derive($remotePub, $localPriv, 48);
|
$hexSecret = openssl_pkey_derive($remotePub, $localPriv, 48);
|
||||||
if($hexSecret === false){
|
if($hexSecret === false){
|
||||||
throw new \InvalidArgumentException("Failed to derive shared secret: " . openssl_error_string());
|
throw new \InvalidArgumentException("Failed to derive shared secret: " . openssl_error_string());
|
||||||
|
@ -113,9 +113,9 @@ use pocketmine\utils\TextFormat;
|
|||||||
use pocketmine\utils\Utils;
|
use pocketmine\utils\Utils;
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use function array_push;
|
use function array_push;
|
||||||
use function base64_encode;
|
|
||||||
use function count;
|
use function count;
|
||||||
use function fmod;
|
use function fmod;
|
||||||
|
use function get_debug_type;
|
||||||
use function implode;
|
use function implode;
|
||||||
use function in_array;
|
use function in_array;
|
||||||
use function is_bool;
|
use function is_bool;
|
||||||
@ -748,27 +748,32 @@ class InGamePacketHandler extends PacketHandler{
|
|||||||
if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
|
if(!($nbt instanceof CompoundTag)) throw new AssumptionFailedError("PHPStan should ensure this is a CompoundTag"); //for phpstorm's benefit
|
||||||
|
|
||||||
if($block instanceof BaseSign){
|
if($block instanceof BaseSign){
|
||||||
if(($textBlobTag = $nbt->getCompoundTag(Sign::TAG_FRONT_TEXT)?->getTag(Sign::TAG_TEXT_BLOB)) instanceof StringTag){
|
$frontTextTag = $nbt->getTag(Sign::TAG_FRONT_TEXT);
|
||||||
try{
|
if(!$frontTextTag instanceof CompoundTag){
|
||||||
$text = SignText::fromBlob($textBlobTag->getValue());
|
throw new PacketHandlingException("Invalid tag type " . get_debug_type($frontTextTag) . " for tag \"" . Sign::TAG_FRONT_TEXT . "\" in sign update data");
|
||||||
}catch(\InvalidArgumentException $e){
|
}
|
||||||
throw PacketHandlingException::wrap($e, "Invalid sign text update");
|
$textBlobTag = $frontTextTag->getTag(Sign::TAG_TEXT_BLOB);
|
||||||
}
|
if(!$textBlobTag instanceof StringTag){
|
||||||
|
throw new PacketHandlingException("Invalid tag type " . get_debug_type($textBlobTag) . " for tag \"" . Sign::TAG_TEXT_BLOB . "\" in sign update data");
|
||||||
try{
|
|
||||||
if(!$block->updateText($this->player, $text)){
|
|
||||||
foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){
|
|
||||||
$this->session->sendDataPacket($updatePacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch(\UnexpectedValueException $e){
|
|
||||||
throw PacketHandlingException::wrap($e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->session->getLogger()->debug("Invalid sign update data: " . base64_encode($packet->nbt->getEncodedNbt()));
|
try{
|
||||||
|
$text = SignText::fromBlob($textBlobTag->getValue());
|
||||||
|
}catch(\InvalidArgumentException $e){
|
||||||
|
throw PacketHandlingException::wrap($e, "Invalid sign text update");
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
if(!$block->updateText($this->player, $text)){
|
||||||
|
foreach($this->player->getWorld()->createBlockUpdatePackets([$pos]) as $updatePacket){
|
||||||
|
$this->session->sendDataPacket($updatePacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(\UnexpectedValueException $e){
|
||||||
|
throw PacketHandlingException::wrap($e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -70,6 +70,7 @@ final class ItemStackContainerIdTranslator{
|
|||||||
ContainerUIIds::MATERIAL_REDUCER_OUTPUT,
|
ContainerUIIds::MATERIAL_REDUCER_OUTPUT,
|
||||||
ContainerUIIds::SMITHING_TABLE_INPUT,
|
ContainerUIIds::SMITHING_TABLE_INPUT,
|
||||||
ContainerUIIds::SMITHING_TABLE_MATERIAL,
|
ContainerUIIds::SMITHING_TABLE_MATERIAL,
|
||||||
|
ContainerUIIds::SMITHING_TABLE_TEMPLATE,
|
||||||
ContainerUIIds::STONECUTTER_INPUT,
|
ContainerUIIds::STONECUTTER_INPUT,
|
||||||
ContainerUIIds::TRADE2_INGREDIENT1,
|
ContainerUIIds::TRADE2_INGREDIENT1,
|
||||||
ContainerUIIds::TRADE2_INGREDIENT2,
|
ContainerUIIds::TRADE2_INGREDIENT2,
|
||||||
@ -84,6 +85,7 @@ final class ItemStackContainerIdTranslator{
|
|||||||
ContainerUIIds::FURNACE_FUEL,
|
ContainerUIIds::FURNACE_FUEL,
|
||||||
ContainerUIIds::FURNACE_INGREDIENT,
|
ContainerUIIds::FURNACE_INGREDIENT,
|
||||||
ContainerUIIds::FURNACE_RESULT,
|
ContainerUIIds::FURNACE_RESULT,
|
||||||
|
ContainerUIIds::HORSE_EQUIP,
|
||||||
ContainerUIIds::LEVEL_ENTITY, //chest
|
ContainerUIIds::LEVEL_ENTITY, //chest
|
||||||
ContainerUIIds::SHULKER_BOX,
|
ContainerUIIds::SHULKER_BOX,
|
||||||
ContainerUIIds::SMOKER_INGREDIENT => [$currentWindowId, $slotId],
|
ContainerUIIds::SMOKER_INGREDIENT => [$currentWindowId, $slotId],
|
||||||
|
@ -39,6 +39,7 @@ use pocketmine\network\mcpe\protocol\types\CacheableNbt;
|
|||||||
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
use pocketmine\network\mcpe\protocol\types\DimensionIds;
|
||||||
use pocketmine\network\mcpe\protocol\types\Experiments;
|
use pocketmine\network\mcpe\protocol\types\Experiments;
|
||||||
use pocketmine\network\mcpe\protocol\types\LevelSettings;
|
use pocketmine\network\mcpe\protocol\types\LevelSettings;
|
||||||
|
use pocketmine\network\mcpe\protocol\types\NetworkPermissions;
|
||||||
use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings;
|
use pocketmine\network\mcpe\protocol\types\PlayerMovementSettings;
|
||||||
use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
|
use pocketmine\network\mcpe\protocol\types\PlayerMovementType;
|
||||||
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
|
use pocketmine\network\mcpe\protocol\types\SpawnSettings;
|
||||||
@ -106,6 +107,7 @@ class PreSpawnPacketHandler extends PacketHandler{
|
|||||||
Uuid::fromString(Uuid::NIL),
|
Uuid::fromString(Uuid::NIL),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
new NetworkPermissions(disableClientSounds: true),
|
||||||
[],
|
[],
|
||||||
0,
|
0,
|
||||||
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),
|
GlobalItemTypeDictionary::getInstance()->getDictionary()->getEntries(),
|
||||||
|
@ -30,7 +30,7 @@ use const M_SQRT2;
|
|||||||
final class ChunkSelector{
|
final class ChunkSelector{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @preturn \Generator|int[]
|
* @return \Generator|int[]
|
||||||
* @phpstan-return \Generator<int, int, void, void>
|
* @phpstan-return \Generator<int, int, void, void>
|
||||||
*/
|
*/
|
||||||
public function selectChunks(int $radius, int $centerX, int $centerZ) : \Generator{
|
public function selectChunks(int $radius, int $centerX, int $centerZ) : \Generator{
|
||||||
|
@ -1191,7 +1191,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
* TODO: make this a dynamic ability instead of being hardcoded
|
* TODO: make this a dynamic ability instead of being hardcoded
|
||||||
*/
|
*/
|
||||||
public function hasFiniteResources() : bool{
|
public function hasFiniteResources() : bool{
|
||||||
return $this->gamemode->equals(GameMode::SURVIVAL()) || $this->gamemode->equals(GameMode::ADVENTURE());
|
return !$this->gamemode->equals(GameMode::CREATIVE());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isFireProof() : bool{
|
public function isFireProof() : bool{
|
||||||
@ -1657,7 +1657,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
|
|||||||
|
|
||||||
$ev = new PlayerBlockPickEvent($this, $block, $item);
|
$ev = new PlayerBlockPickEvent($this, $block, $item);
|
||||||
$existingSlot = $this->inventory->first($item);
|
$existingSlot = $this->inventory->first($item);
|
||||||
if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){
|
if($existingSlot === -1 && $this->hasFiniteResources()){
|
||||||
$ev->cancel();
|
$ev->cancel();
|
||||||
}
|
}
|
||||||
$ev->call();
|
$ev->call();
|
||||||
|
@ -247,7 +247,6 @@ class AsyncPool{
|
|||||||
while(!$queue->isEmpty()){
|
while(!$queue->isEmpty()){
|
||||||
/** @var AsyncTask $task */
|
/** @var AsyncTask $task */
|
||||||
$task = $queue->bottom();
|
$task = $queue->bottom();
|
||||||
$task->checkProgressUpdates();
|
|
||||||
if($task->isFinished()){ //make sure the task actually executed before trying to collect
|
if($task->isFinished()){ //make sure the task actually executed before trying to collect
|
||||||
$queue->dequeue();
|
$queue->dequeue();
|
||||||
|
|
||||||
@ -268,6 +267,7 @@ class AsyncPool{
|
|||||||
$task->onCompletion();
|
$task->onCompletion();
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
$task->checkProgressUpdates();
|
||||||
$more = true;
|
$more = true;
|
||||||
break; //current task is still running, skip to next worker
|
break; //current task is still running, skip to next worker
|
||||||
}
|
}
|
||||||
|
@ -252,9 +252,15 @@ abstract class AsyncTask extends \Threaded{
|
|||||||
final public function __destruct(){
|
final public function __destruct(){
|
||||||
$this->reallyDestruct();
|
$this->reallyDestruct();
|
||||||
if(self::$threadLocalStorage !== null && isset(self::$threadLocalStorage[$h = spl_object_id($this)])){
|
if(self::$threadLocalStorage !== null && isset(self::$threadLocalStorage[$h = spl_object_id($this)])){
|
||||||
unset(self::$threadLocalStorage[$h]);
|
//Beware changing this code!
|
||||||
if(self::$threadLocalStorage->count() === 0){
|
//This code may cause the GC to be triggered, causing destruction of other AsyncTasks (which may or may not
|
||||||
|
//have been indirectly referenced by the TLS).
|
||||||
|
//This may cause the code to be re-entered from a different context unexpectedly, causing a crash if handled
|
||||||
|
//incorrectly.
|
||||||
|
if(self::$threadLocalStorage->count() === 1){
|
||||||
self::$threadLocalStorage = null;
|
self::$threadLocalStorage = null;
|
||||||
|
}else{
|
||||||
|
unset(self::$threadLocalStorage[$h]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,14 +87,6 @@ abstract class Timings{
|
|||||||
/** @var TimingsHandler */
|
/** @var TimingsHandler */
|
||||||
public static $serverCommand;
|
public static $serverCommand;
|
||||||
/** @var TimingsHandler */
|
/** @var TimingsHandler */
|
||||||
public static $worldLoad;
|
|
||||||
/** @var TimingsHandler */
|
|
||||||
public static $worldSave;
|
|
||||||
/** @var TimingsHandler */
|
|
||||||
public static $population;
|
|
||||||
/** @var TimingsHandler */
|
|
||||||
public static $generationCallback;
|
|
||||||
/** @var TimingsHandler */
|
|
||||||
public static $permissibleCalculation;
|
public static $permissibleCalculation;
|
||||||
/** @var TimingsHandler */
|
/** @var TimingsHandler */
|
||||||
public static $permissibleCalculationDiff;
|
public static $permissibleCalculationDiff;
|
||||||
@ -111,10 +103,6 @@ abstract class Timings{
|
|||||||
|
|
||||||
/** @var TimingsHandler */
|
/** @var TimingsHandler */
|
||||||
public static $playerCheckNearEntities;
|
public static $playerCheckNearEntities;
|
||||||
/** @var TimingsHandler */
|
|
||||||
public static $tickEntity;
|
|
||||||
/** @var TimingsHandler */
|
|
||||||
public static $tickTileEntity;
|
|
||||||
|
|
||||||
/** @var TimingsHandler */
|
/** @var TimingsHandler */
|
||||||
public static $entityBaseTick;
|
public static $entityBaseTick;
|
||||||
@ -203,10 +191,6 @@ abstract class Timings{
|
|||||||
self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
|
||||||
self::$scheduler = new TimingsHandler("Scheduler");
|
self::$scheduler = new TimingsHandler("Scheduler");
|
||||||
self::$serverCommand = new TimingsHandler("Server Command");
|
self::$serverCommand = new TimingsHandler("Server Command");
|
||||||
self::$worldLoad = new TimingsHandler("World Load");
|
|
||||||
self::$worldSave = new TimingsHandler("World Save");
|
|
||||||
self::$population = new TimingsHandler("World Population");
|
|
||||||
self::$generationCallback = new TimingsHandler("World Generation Callback");
|
|
||||||
self::$permissibleCalculation = new TimingsHandler("Permissible Calculation");
|
self::$permissibleCalculation = new TimingsHandler("Permissible Calculation");
|
||||||
self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
|
self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
|
||||||
self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
|
self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
|
||||||
@ -221,9 +205,6 @@ abstract class Timings{
|
|||||||
self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN);
|
self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN);
|
||||||
|
|
||||||
self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities", group: self::GROUP_BREAKDOWN);
|
self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities", group: self::GROUP_BREAKDOWN);
|
||||||
self::$tickEntity = new TimingsHandler("Entity Tick", group: self::GROUP_BREAKDOWN);
|
|
||||||
self::$tickTileEntity = new TimingsHandler("Block Entity Tick", group: self::GROUP_BREAKDOWN);
|
|
||||||
|
|
||||||
self::$entityBaseTick = new TimingsHandler("Entity Base Tick", group: self::GROUP_BREAKDOWN);
|
self::$entityBaseTick = new TimingsHandler("Entity Base Tick", group: self::GROUP_BREAKDOWN);
|
||||||
self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living", group: self::GROUP_BREAKDOWN);
|
self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living", group: self::GROUP_BREAKDOWN);
|
||||||
self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity", group: self::GROUP_BREAKDOWN);
|
self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity", group: self::GROUP_BREAKDOWN);
|
||||||
@ -272,7 +253,7 @@ abstract class Timings{
|
|||||||
}else{
|
}else{
|
||||||
$displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\");
|
$displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\");
|
||||||
}
|
}
|
||||||
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, self::$tickEntity, group: self::GROUP_BREAKDOWN);
|
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, group: self::GROUP_BREAKDOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::$entityTypeTimingMap[$entity::class];
|
return self::$entityTypeTimingMap[$entity::class];
|
||||||
@ -282,7 +263,6 @@ abstract class Timings{
|
|||||||
if(!isset(self::$tileEntityTypeTimingMap[$tile::class])){
|
if(!isset(self::$tileEntityTypeTimingMap[$tile::class])){
|
||||||
self::$tileEntityTypeTimingMap[$tile::class] = new TimingsHandler(
|
self::$tileEntityTypeTimingMap[$tile::class] = new TimingsHandler(
|
||||||
"Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\"),
|
"Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\"),
|
||||||
self::$tickTileEntity,
|
|
||||||
group: self::GROUP_BREAKDOWN
|
group: self::GROUP_BREAKDOWN
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,16 +23,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace pocketmine\timings;
|
namespace pocketmine\timings;
|
||||||
|
|
||||||
use pocketmine\entity\Living;
|
|
||||||
use pocketmine\Server;
|
use pocketmine\Server;
|
||||||
use pocketmine\utils\Utils;
|
use pocketmine\utils\Utils;
|
||||||
use function count;
|
|
||||||
use function hrtime;
|
use function hrtime;
|
||||||
use function implode;
|
use function implode;
|
||||||
use function spl_object_id;
|
use function spl_object_id;
|
||||||
|
|
||||||
class TimingsHandler{
|
class TimingsHandler{
|
||||||
private const FORMAT_VERSION = 1;
|
private const FORMAT_VERSION = 2; //peak timings fix
|
||||||
|
|
||||||
private static bool $enabled = false;
|
private static bool $enabled = false;
|
||||||
private static int $timingStart = 0;
|
private static int $timingStart = 0;
|
||||||
@ -77,19 +75,6 @@ class TimingsHandler{
|
|||||||
$result[] = "# Version " . Server::getInstance()->getVersion();
|
$result[] = "# Version " . Server::getInstance()->getVersion();
|
||||||
$result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();
|
$result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();
|
||||||
|
|
||||||
$entities = 0;
|
|
||||||
$livingEntities = 0;
|
|
||||||
foreach(Server::getInstance()->getWorldManager()->getWorlds() as $world){
|
|
||||||
$entities += count($world->getEntities());
|
|
||||||
foreach($world->getEntities() as $e){
|
|
||||||
if($e instanceof Living){
|
|
||||||
++$livingEntities;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$result[] = "# Entities " . $entities;
|
|
||||||
$result[] = "# LivingEntities " . $livingEntities;
|
|
||||||
$result[] = "# FormatVersion " . self::FORMAT_VERSION;
|
$result[] = "# FormatVersion " . self::FORMAT_VERSION;
|
||||||
|
|
||||||
$sampleTime = hrtime(true) - self::$timingStart;
|
$sampleTime = hrtime(true) - self::$timingStart;
|
||||||
|
@ -66,9 +66,6 @@ final class TimingsRecord{
|
|||||||
if($record->curTickTotal > Server::TARGET_NANOSECONDS_PER_TICK){
|
if($record->curTickTotal > Server::TARGET_NANOSECONDS_PER_TICK){
|
||||||
$record->violations += (int) floor($record->curTickTotal / Server::TARGET_NANOSECONDS_PER_TICK);
|
$record->violations += (int) floor($record->curTickTotal / Server::TARGET_NANOSECONDS_PER_TICK);
|
||||||
}
|
}
|
||||||
if($record->curTickTotal > $record->peakTime){
|
|
||||||
$record->peakTime = $record->curTickTotal;
|
|
||||||
}
|
|
||||||
$record->curTickTotal = 0;
|
$record->curTickTotal = 0;
|
||||||
$record->curCount = 0;
|
$record->curCount = 0;
|
||||||
$record->ticksActive++;
|
$record->ticksActive++;
|
||||||
@ -126,7 +123,7 @@ final class TimingsRecord{
|
|||||||
|
|
||||||
public function getTicksActive() : int{ return $this->ticksActive; }
|
public function getTicksActive() : int{ return $this->ticksActive; }
|
||||||
|
|
||||||
public function getPeakTime() : float{ return $this->peakTime; }
|
public function getPeakTime() : int{ return $this->peakTime; }
|
||||||
|
|
||||||
public function startTiming(int $now) : void{
|
public function startTiming(int $now) : void{
|
||||||
$this->start = $now;
|
$this->start = $now;
|
||||||
@ -152,6 +149,9 @@ final class TimingsRecord{
|
|||||||
++$this->curCount;
|
++$this->curCount;
|
||||||
++$this->count;
|
++$this->count;
|
||||||
$this->start = 0;
|
$this->start = 0;
|
||||||
|
if($diff > $this->peakTime){
|
||||||
|
$this->peakTime = $diff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getCurrentRecord() : ?self{
|
public static function getCurrentRecord() : ?self{
|
||||||
|
@ -125,7 +125,10 @@ final class Process{
|
|||||||
return count(ThreadManager::getInstance()->getAll()) + 2; //MainLogger + Main Thread
|
return count(ThreadManager::getInstance()->getAll()) + 2; //MainLogger + Main Thread
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function kill(int $pid, bool $subprocesses) : void{
|
/**
|
||||||
|
* @param bool $subprocesses @deprecated
|
||||||
|
*/
|
||||||
|
public static function kill(int $pid, bool $subprocesses = false) : void{
|
||||||
$logger = \GlobalLogger::get();
|
$logger = \GlobalLogger::get();
|
||||||
if($logger instanceof MainLogger){
|
if($logger instanceof MainLogger){
|
||||||
$logger->syncFlushBuffer();
|
$logger->syncFlushBuffer();
|
||||||
|
@ -49,7 +49,7 @@ class ServerKiller extends Thread{
|
|||||||
});
|
});
|
||||||
if(time() - $start >= $this->time){
|
if(time() - $start >= $this->time){
|
||||||
echo "\nTook too long to stop, server was killed forcefully!\n";
|
echo "\nTook too long to stop, server was killed forcefully!\n";
|
||||||
@Process::kill(Process::pid(), true);
|
@Process::kill(Process::pid());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* ____ _ _ __ __ _ __ __ ____
|
|
||||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
|
||||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
|
||||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
|
||||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* @author PocketMine Team
|
|
||||||
* @link http://www.pocketmine.net/
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace pocketmine\world;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
final class TickingChunkEntry{
|
|
||||||
/**
|
|
||||||
* @var ChunkTicker[] spl_object_id => ChunkTicker
|
|
||||||
* @phpstan-var array<int, ChunkTicker>
|
|
||||||
*/
|
|
||||||
public array $tickers = [];
|
|
||||||
|
|
||||||
public bool $ready = false;
|
|
||||||
}
|
|
@ -77,7 +77,6 @@ use pocketmine\promise\PromiseResolver;
|
|||||||
use pocketmine\scheduler\AsyncPool;
|
use pocketmine\scheduler\AsyncPool;
|
||||||
use pocketmine\Server;
|
use pocketmine\Server;
|
||||||
use pocketmine\ServerConfigGroup;
|
use pocketmine\ServerConfigGroup;
|
||||||
use pocketmine\timings\Timings;
|
|
||||||
use pocketmine\utils\AssumptionFailedError;
|
use pocketmine\utils\AssumptionFailedError;
|
||||||
use pocketmine\utils\Limits;
|
use pocketmine\utils\Limits;
|
||||||
use pocketmine\utils\ReversePriorityQueue;
|
use pocketmine\utils\ReversePriorityQueue;
|
||||||
@ -104,6 +103,7 @@ use pocketmine\world\utils\SubChunkExplorer;
|
|||||||
use function abs;
|
use function abs;
|
||||||
use function array_filter;
|
use function array_filter;
|
||||||
use function array_key_exists;
|
use function array_key_exists;
|
||||||
|
use function array_keys;
|
||||||
use function array_map;
|
use function array_map;
|
||||||
use function array_merge;
|
use function array_merge;
|
||||||
use function array_sum;
|
use function array_sum;
|
||||||
@ -223,10 +223,25 @@ class World implements ChunkManager{
|
|||||||
private array $tickingLoaderCounter = [];
|
private array $tickingLoaderCounter = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TickingChunkEntry[] chunkHash => TickingChunkEntry
|
* @var ChunkTicker[][] chunkHash => [spl_object_id => ChunkTicker]
|
||||||
* @phpstan-var array<ChunkPosHash, TickingChunkEntry>
|
* @phpstan-var array<ChunkPosHash, array<int, ChunkTicker>>
|
||||||
*/
|
*/
|
||||||
private array $tickingChunks = [];
|
private array $registeredTickingChunks = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of chunks which are definitely ready for ticking.
|
||||||
|
*
|
||||||
|
* @var int[]
|
||||||
|
* @phpstan-var array<ChunkPosHash, ChunkPosHash>
|
||||||
|
*/
|
||||||
|
private array $validTickingChunks = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of chunks which might be ready for ticking. These will be checked at the next tick.
|
||||||
|
* @var int[]
|
||||||
|
* @phpstan-var array<ChunkPosHash, ChunkPosHash>
|
||||||
|
*/
|
||||||
|
private array $recheckTickingChunks = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader]
|
* @var ChunkLoader[][] chunkHash => [spl_object_id => ChunkLoader]
|
||||||
@ -983,7 +998,6 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
$this->timings->entityTick->startTiming();
|
$this->timings->entityTick->startTiming();
|
||||||
//Update entities that need update
|
//Update entities that need update
|
||||||
Timings::$tickEntity->startTiming();
|
|
||||||
foreach($this->updateEntities as $id => $entity){
|
foreach($this->updateEntities as $id => $entity){
|
||||||
if($entity->isClosed() || $entity->isFlaggedForDespawn() || !$entity->onUpdate($currentTick)){
|
if($entity->isClosed() || $entity->isFlaggedForDespawn() || !$entity->onUpdate($currentTick)){
|
||||||
unset($this->updateEntities[$id]);
|
unset($this->updateEntities[$id]);
|
||||||
@ -992,7 +1006,6 @@ class World implements ChunkManager{
|
|||||||
$entity->close();
|
$entity->close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timings::$tickEntity->stopTiming();
|
|
||||||
$this->timings->entityTick->stopTiming();
|
$this->timings->entityTick->stopTiming();
|
||||||
|
|
||||||
$this->timings->randomChunkUpdates->startTiming();
|
$this->timings->randomChunkUpdates->startTiming();
|
||||||
@ -1155,17 +1168,25 @@ class World implements ChunkManager{
|
|||||||
$this->chunkTickRadius = $radius;
|
$this->chunkTickRadius = $radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of chunk position hashes (as returned by World::chunkHash()) which are currently valid for
|
||||||
|
* ticking.
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
* @phpstan-return list<ChunkPosHash>
|
||||||
|
*/
|
||||||
|
public function getTickingChunks() : array{
|
||||||
|
return array_keys($this->validTickingChunks);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instructs the World to tick the specified chunk, for as long as this chunk ticker (or any other chunk ticker) is
|
* Instructs the World to tick the specified chunk, for as long as this chunk ticker (or any other chunk ticker) is
|
||||||
* registered to it.
|
* registered to it.
|
||||||
*/
|
*/
|
||||||
public function registerTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
|
public function registerTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
|
||||||
$chunkPosHash = World::chunkHash($chunkX, $chunkZ);
|
$chunkPosHash = World::chunkHash($chunkX, $chunkZ);
|
||||||
$entry = $this->tickingChunks[$chunkPosHash] ?? null;
|
$this->registeredTickingChunks[$chunkPosHash][spl_object_id($ticker)] = $ticker;
|
||||||
if($entry === null){
|
$this->recheckTickingChunks[$chunkPosHash] = $chunkPosHash;
|
||||||
$entry = $this->tickingChunks[$chunkPosHash] = new TickingChunkEntry();
|
|
||||||
}
|
|
||||||
$entry->tickers[spl_object_id($ticker)] = $ticker;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1175,10 +1196,14 @@ class World implements ChunkManager{
|
|||||||
public function unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
|
public function unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
|
||||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||||
$tickerId = spl_object_id($ticker);
|
$tickerId = spl_object_id($ticker);
|
||||||
if(isset($this->tickingChunks[$chunkHash]->tickers[$tickerId])){
|
if(isset($this->registeredTickingChunks[$chunkHash][$tickerId])){
|
||||||
unset($this->tickingChunks[$chunkHash]->tickers[$tickerId]);
|
unset($this->registeredTickingChunks[$chunkHash][$tickerId]);
|
||||||
if(count($this->tickingChunks[$chunkHash]->tickers) === 0){
|
if(count($this->registeredTickingChunks[$chunkHash]) === 0){
|
||||||
unset($this->tickingChunks[$chunkHash]);
|
unset(
|
||||||
|
$this->registeredTickingChunks[$chunkHash],
|
||||||
|
$this->recheckTickingChunks[$chunkHash],
|
||||||
|
$this->validTickingChunks[$chunkHash]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1222,37 +1247,37 @@ class World implements ChunkManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function tickChunks() : void{
|
private function tickChunks() : void{
|
||||||
if($this->chunkTickRadius <= 0 || (count($this->tickingChunks) === 0 && count($this->tickingLoaders) === 0)){
|
if($this->chunkTickRadius <= 0 || (count($this->registeredTickingChunks) === 0 && count($this->tickingLoaders) === 0)){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
|
if(count($this->recheckTickingChunks) > 0 || count($this->tickingLoaders) > 0){
|
||||||
|
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
|
||||||
|
|
||||||
/** @var bool[] $chunkTickList chunkhash => dummy */
|
$chunkTickableCache = [];
|
||||||
$chunkTickList = [];
|
|
||||||
|
|
||||||
$chunkTickableCache = [];
|
foreach($this->recheckTickingChunks as $hash => $_){
|
||||||
|
|
||||||
foreach($this->tickingChunks as $hash => $entry){
|
|
||||||
if(!$entry->ready){
|
|
||||||
World::getXZ($hash, $chunkX, $chunkZ);
|
World::getXZ($hash, $chunkX, $chunkZ);
|
||||||
if($this->isChunkTickable($chunkX, $chunkZ, $chunkTickableCache)){
|
if($this->isChunkTickable($chunkX, $chunkZ, $chunkTickableCache)){
|
||||||
$entry->ready = true;
|
$this->validTickingChunks[$hash] = $hash;
|
||||||
}else{
|
|
||||||
//the chunk has been flagged as temporarily not tickable, so we don't want to tick it this time
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$chunkTickList[$hash] = true;
|
$this->recheckTickingChunks = [];
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: REMOVE THIS
|
//TODO: REMOVE THIS - we need a local var to add extra chunks to if we have legacy ticking loaders
|
||||||
//backwards compatibility for TickingChunkLoader, although I'm not sure this is really necessary in practice
|
//this is copy-on-write, so it won't have any performance impact if there are no legacy ticking loaders
|
||||||
if(count($this->tickingLoaders) !== 0){
|
$chunkTickList = $this->validTickingChunks;
|
||||||
$this->selectTickableChunksLegacy($chunkTickList, $chunkTickableCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->timings->randomChunkUpdatesChunkSelection->stopTiming();
|
//TODO: REMOVE THIS
|
||||||
|
//backwards compatibility for TickingChunkLoader, although I'm not sure this is really necessary in practice
|
||||||
|
if(count($this->tickingLoaders) !== 0){
|
||||||
|
$this->selectTickableChunksLegacy($chunkTickList, $chunkTickableCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->timings->randomChunkUpdatesChunkSelection->stopTiming();
|
||||||
|
}else{
|
||||||
|
$chunkTickList = $this->validTickingChunks;
|
||||||
|
}
|
||||||
|
|
||||||
foreach($chunkTickList as $index => $_){
|
foreach($chunkTickList as $index => $_){
|
||||||
World::getXZ($index, $chunkX, $chunkZ);
|
World::getXZ($index, $chunkX, $chunkZ);
|
||||||
@ -1303,16 +1328,23 @@ class World implements ChunkManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the 3x3 chunks around the specified chunk as not ready to be ticked. This is used to prevent chunk ticking
|
* Marks the 3x3 square of chunks centered on the specified chunk for chunk ticking eligibility recheck.
|
||||||
* while a chunk is being populated, light-populated, or unloaded.
|
*
|
||||||
* Each chunk will be rechecked every tick until it is ready to be ticked again.
|
* This should be used whenever the chunk's eligibility to be ticked is changed. This includes:
|
||||||
|
* - Loading/unloading the chunk (the chunk may be registered for ticking before it is loaded)
|
||||||
|
* - Locking/unlocking the chunk (e.g. world population)
|
||||||
|
* - Light populated state change (i.e. scheduled for light population, or light population completed)
|
||||||
|
* - Arbitrary chunk replacement (i.e. setChunk() or similar)
|
||||||
*/
|
*/
|
||||||
private function markTickingChunkUnavailable(int $chunkX, int $chunkZ) : void{
|
private function markTickingChunkForRecheck(int $chunkX, int $chunkZ) : void{
|
||||||
for($cx = -1; $cx <= 1; ++$cx){
|
for($cx = -1; $cx <= 1; ++$cx){
|
||||||
for($cz = -1; $cz <= 1; ++$cz){
|
for($cz = -1; $cz <= 1; ++$cz){
|
||||||
$chunkHash = World::chunkHash($chunkX + $cx, $chunkZ + $cz);
|
$chunkHash = World::chunkHash($chunkX + $cx, $chunkZ + $cz);
|
||||||
if(isset($this->tickingChunks[$chunkHash])){
|
unset($this->validTickingChunks[$chunkHash]);
|
||||||
$this->tickingChunks[$chunkHash]->ready = false;
|
if(isset($this->registeredTickingChunks[$chunkHash])){
|
||||||
|
$this->recheckTickingChunks[$chunkHash] = $chunkHash;
|
||||||
|
}else{
|
||||||
|
unset($this->recheckTickingChunks[$chunkHash]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1323,7 +1355,7 @@ class World implements ChunkManager{
|
|||||||
$lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated();
|
$lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated();
|
||||||
if($lightPopulatedState === false){
|
if($lightPopulatedState === false){
|
||||||
$this->chunks[$chunkHash]->setLightPopulated(null);
|
$this->chunks[$chunkHash]->setLightPopulated(null);
|
||||||
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
|
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
|
||||||
|
|
||||||
$this->workerPool->submitTask(new LightPopulationTask(
|
$this->workerPool->submitTask(new LightPopulationTask(
|
||||||
$this->chunks[$chunkHash],
|
$this->chunks[$chunkHash],
|
||||||
@ -1347,6 +1379,7 @@ class World implements ChunkManager{
|
|||||||
$chunk->getSubChunk($y)->setBlockSkyLightArray($lightArray);
|
$chunk->getSubChunk($y)->setBlockSkyLightArray($lightArray);
|
||||||
}
|
}
|
||||||
$chunk->setLightPopulated(true);
|
$chunk->setLightPopulated(true);
|
||||||
|
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -1403,10 +1436,15 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
(new WorldSaveEvent($this))->call();
|
(new WorldSaveEvent($this))->call();
|
||||||
|
|
||||||
|
$timings = $this->timings->syncDataSave;
|
||||||
|
$timings->startTiming();
|
||||||
|
|
||||||
$this->provider->getWorldData()->setTime($this->time);
|
$this->provider->getWorldData()->setTime($this->time);
|
||||||
$this->saveChunks();
|
$this->saveChunks();
|
||||||
$this->provider->getWorldData()->save();
|
$this->provider->getWorldData()->save();
|
||||||
|
|
||||||
|
$timings->stopTiming();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2391,7 +2429,7 @@ class World implements ChunkManager{
|
|||||||
throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked");
|
throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked");
|
||||||
}
|
}
|
||||||
$this->chunkLock[$chunkHash] = $lockId;
|
$this->chunkLock[$chunkHash] = $lockId;
|
||||||
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
|
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2406,6 +2444,7 @@ class World implements ChunkManager{
|
|||||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||||
if(isset($this->chunkLock[$chunkHash]) && ($lockId === null || $this->chunkLock[$chunkHash] === $lockId)){
|
if(isset($this->chunkLock[$chunkHash]) && ($lockId === null || $this->chunkLock[$chunkHash] === $lockId)){
|
||||||
unset($this->chunkLock[$chunkHash]);
|
unset($this->chunkLock[$chunkHash]);
|
||||||
|
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -2457,7 +2496,7 @@ class World implements ChunkManager{
|
|||||||
unset($this->blockCache[$chunkHash]);
|
unset($this->blockCache[$chunkHash]);
|
||||||
unset($this->changedBlocks[$chunkHash]);
|
unset($this->changedBlocks[$chunkHash]);
|
||||||
$chunk->setTerrainDirty();
|
$chunk->setTerrainDirty();
|
||||||
$this->markTickingChunkUnavailable($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking
|
$this->markTickingChunkForRecheck($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking
|
||||||
|
|
||||||
if(!$this->isChunkInUse($chunkX, $chunkZ)){
|
if(!$this->isChunkInUse($chunkX, $chunkZ)){
|
||||||
$this->unloadChunkRequest($chunkX, $chunkZ);
|
$this->unloadChunkRequest($chunkX, $chunkZ);
|
||||||
@ -2739,6 +2778,7 @@ class World implements ChunkManager{
|
|||||||
foreach($this->getChunkListeners($x, $z) as $listener){
|
foreach($this->getChunkListeners($x, $z) as $listener){
|
||||||
$listener->onChunkLoaded($x, $z, $this->chunks[$chunkHash]);
|
$listener->onChunkLoaded($x, $z, $this->chunks[$chunkHash]);
|
||||||
}
|
}
|
||||||
|
$this->markTickingChunkForRecheck($x, $z); //tickers may have been registered before the chunk was loaded
|
||||||
|
|
||||||
$this->timings->syncChunkLoad->stopTiming();
|
$this->timings->syncChunkLoad->stopTiming();
|
||||||
|
|
||||||
@ -2900,8 +2940,8 @@ class World implements ChunkManager{
|
|||||||
unset($this->chunks[$chunkHash]);
|
unset($this->chunks[$chunkHash]);
|
||||||
unset($this->blockCache[$chunkHash]);
|
unset($this->blockCache[$chunkHash]);
|
||||||
unset($this->changedBlocks[$chunkHash]);
|
unset($this->changedBlocks[$chunkHash]);
|
||||||
unset($this->tickingChunks[$chunkHash]);
|
unset($this->registeredTickingChunks[$chunkHash]);
|
||||||
$this->markTickingChunkUnavailable($x, $z);
|
$this->markTickingChunkForRecheck($x, $z);
|
||||||
|
|
||||||
if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){
|
if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){
|
||||||
$this->logger->debug("Rejecting population promise for chunk $x $z");
|
$this->logger->debug("Rejecting population promise for chunk $x $z");
|
||||||
@ -3210,7 +3250,8 @@ class World implements ChunkManager{
|
|||||||
private function internalOrderChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader, ?PromiseResolver $resolver) : Promise{
|
private function internalOrderChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader, ?PromiseResolver $resolver) : Promise{
|
||||||
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
$chunkHash = World::chunkHash($chunkX, $chunkZ);
|
||||||
|
|
||||||
Timings::$population->startTiming();
|
$timings = $this->timings->chunkPopulationOrder;
|
||||||
|
$timings->startTiming();
|
||||||
|
|
||||||
try{
|
try{
|
||||||
for($xx = -1; $xx <= 1; ++$xx){
|
for($xx = -1; $xx <= 1; ++$xx){
|
||||||
@ -3267,7 +3308,7 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
return $resolver->getPromise();
|
return $resolver->getPromise();
|
||||||
}finally{
|
}finally{
|
||||||
Timings::$population->stopTiming();
|
$timings->stopTiming();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3276,7 +3317,8 @@ class World implements ChunkManager{
|
|||||||
* @phpstan-param array<int, Chunk> $adjacentChunks
|
* @phpstan-param array<int, Chunk> $adjacentChunks
|
||||||
*/
|
*/
|
||||||
private function generateChunkCallback(ChunkLockId $chunkLockId, int $x, int $z, Chunk $chunk, array $adjacentChunks, ChunkLoader $temporaryChunkLoader) : void{
|
private function generateChunkCallback(ChunkLockId $chunkLockId, int $x, int $z, Chunk $chunk, array $adjacentChunks, ChunkLoader $temporaryChunkLoader) : void{
|
||||||
Timings::$generationCallback->startTiming();
|
$timings = $this->timings->chunkPopulationCompletion;
|
||||||
|
$timings->startTiming();
|
||||||
|
|
||||||
$dirtyChunks = 0;
|
$dirtyChunks = 0;
|
||||||
for($xx = -1; $xx <= 1; ++$xx){
|
for($xx = -1; $xx <= 1; ++$xx){
|
||||||
@ -3345,7 +3387,7 @@ class World implements ChunkManager{
|
|||||||
|
|
||||||
$this->drainPopulationRequestQueue();
|
$this->drainPopulationRequestQueue();
|
||||||
}
|
}
|
||||||
Timings::$generationCallback->stopTiming();
|
$timings->stopTiming();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function doChunkGarbageCollection() : void{
|
public function doChunkGarbageCollection() : void{
|
||||||
|
@ -30,7 +30,6 @@ use pocketmine\event\world\WorldUnloadEvent;
|
|||||||
use pocketmine\lang\KnownTranslationFactory;
|
use pocketmine\lang\KnownTranslationFactory;
|
||||||
use pocketmine\player\ChunkSelector;
|
use pocketmine\player\ChunkSelector;
|
||||||
use pocketmine\Server;
|
use pocketmine\Server;
|
||||||
use pocketmine\timings\Timings;
|
|
||||||
use pocketmine\world\format\Chunk;
|
use pocketmine\world\format\Chunk;
|
||||||
use pocketmine\world\format\io\exception\CorruptedWorldException;
|
use pocketmine\world\format\io\exception\CorruptedWorldException;
|
||||||
use pocketmine\world\format\io\exception\UnsupportedWorldFormatException;
|
use pocketmine\world\format\io\exception\UnsupportedWorldFormatException;
|
||||||
@ -391,7 +390,6 @@ class WorldManager{
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function doAutoSave() : void{
|
private function doAutoSave() : void{
|
||||||
Timings::$worldSave->startTiming();
|
|
||||||
foreach($this->worlds as $world){
|
foreach($this->worlds as $world){
|
||||||
foreach($world->getPlayers() as $player){
|
foreach($world->getPlayers() as $player){
|
||||||
if($player->spawned){
|
if($player->spawned){
|
||||||
@ -400,6 +398,5 @@ class WorldManager{
|
|||||||
}
|
}
|
||||||
$world->save(false);
|
$world->save(false);
|
||||||
}
|
}
|
||||||
Timings::$worldSave->stopTiming();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ class WorldTimings{
|
|||||||
public TimingsHandler $randomChunkUpdatesChunkSelection;
|
public TimingsHandler $randomChunkUpdatesChunkSelection;
|
||||||
public TimingsHandler $doChunkGC;
|
public TimingsHandler $doChunkGC;
|
||||||
public TimingsHandler $entityTick;
|
public TimingsHandler $entityTick;
|
||||||
|
public TimingsHandler $tileTick;
|
||||||
public TimingsHandler $doTick;
|
public TimingsHandler $doTick;
|
||||||
|
|
||||||
public TimingsHandler $syncChunkSend;
|
public TimingsHandler $syncChunkSend;
|
||||||
@ -48,33 +49,54 @@ class WorldTimings{
|
|||||||
public TimingsHandler $syncChunkLoadFixInvalidBlocks;
|
public TimingsHandler $syncChunkLoadFixInvalidBlocks;
|
||||||
public TimingsHandler $syncChunkLoadEntities;
|
public TimingsHandler $syncChunkLoadEntities;
|
||||||
public TimingsHandler $syncChunkLoadTileEntities;
|
public TimingsHandler $syncChunkLoadTileEntities;
|
||||||
|
|
||||||
|
public TimingsHandler $syncDataSave;
|
||||||
public TimingsHandler $syncChunkSave;
|
public TimingsHandler $syncChunkSave;
|
||||||
|
|
||||||
|
public TimingsHandler $chunkPopulationOrder;
|
||||||
|
public TimingsHandler $chunkPopulationCompletion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var TimingsHandler[]
|
||||||
|
* @phpstan-var array<string, TimingsHandler>
|
||||||
|
*/
|
||||||
|
private static array $aggregators = [];
|
||||||
|
|
||||||
|
private static function newTimer(string $worldName, string $timerName) : TimingsHandler{
|
||||||
|
$aggregator = self::$aggregators[$timerName] ??= new TimingsHandler("Worlds - $timerName"); //displayed in Minecraft primary table
|
||||||
|
|
||||||
|
return new TimingsHandler("$worldName - $timerName", $aggregator, Timings::GROUP_BREAKDOWN);
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(World $world){
|
public function __construct(World $world){
|
||||||
$name = $world->getFolderName() . " - ";
|
$name = $world->getFolderName();
|
||||||
|
|
||||||
$this->setBlock = new TimingsHandler($name . "setBlock", group: Timings::GROUP_BREAKDOWN);
|
$this->setBlock = self::newTimer($name, "Set Blocks");
|
||||||
$this->doBlockLightUpdates = new TimingsHandler($name . "Block Light Updates", group: Timings::GROUP_BREAKDOWN);
|
$this->doBlockLightUpdates = self::newTimer($name, "Block Light Updates");
|
||||||
$this->doBlockSkyLightUpdates = new TimingsHandler($name . "Sky Light Updates", group: Timings::GROUP_BREAKDOWN);
|
$this->doBlockSkyLightUpdates = self::newTimer($name, "Sky Light Updates");
|
||||||
|
|
||||||
$this->doChunkUnload = new TimingsHandler($name . "Unload Chunks", group: Timings::GROUP_BREAKDOWN);
|
$this->doChunkUnload = self::newTimer($name, "Unload Chunks");
|
||||||
$this->scheduledBlockUpdates = new TimingsHandler($name . "Scheduled Block Updates", group: Timings::GROUP_BREAKDOWN);
|
$this->scheduledBlockUpdates = self::newTimer($name, "Scheduled Block Updates");
|
||||||
$this->randomChunkUpdates = new TimingsHandler($name . "Random Chunk Updates", group: Timings::GROUP_BREAKDOWN);
|
$this->randomChunkUpdates = self::newTimer($name, "Random Chunk Updates");
|
||||||
$this->randomChunkUpdatesChunkSelection = new TimingsHandler($name . "Random Chunk Updates - Chunk Selection", group: Timings::GROUP_BREAKDOWN);
|
$this->randomChunkUpdatesChunkSelection = self::newTimer($name, "Random Chunk Updates - Chunk Selection");
|
||||||
$this->doChunkGC = new TimingsHandler($name . "Garbage Collection", group: Timings::GROUP_BREAKDOWN);
|
$this->doChunkGC = self::newTimer($name, "Garbage Collection");
|
||||||
$this->entityTick = new TimingsHandler($name . "Tick Entities", group: Timings::GROUP_BREAKDOWN);
|
$this->entityTick = self::newTimer($name, "Entity Tick");
|
||||||
|
$this->tileTick = self::newTimer($name, "Block Entity Tick");
|
||||||
|
$this->doTick = self::newTimer($name, "World Tick");
|
||||||
|
|
||||||
Timings::init(); //make sure the timers we want are available
|
$this->syncChunkSend = self::newTimer($name, "Player Send Chunks");
|
||||||
$this->syncChunkSend = new TimingsHandler($name . "Player Send Chunks", Timings::$playerChunkSend, group: Timings::GROUP_BREAKDOWN);
|
$this->syncChunkSendPrepare = self::newTimer($name, "Player Send Chunk Prepare");
|
||||||
$this->syncChunkSendPrepare = new TimingsHandler($name . "Player Send Chunk Prepare", Timings::$playerChunkSend, group: Timings::GROUP_BREAKDOWN);
|
|
||||||
|
|
||||||
$this->syncChunkLoad = new TimingsHandler($name . "Chunk Load", Timings::$worldLoad, group: Timings::GROUP_BREAKDOWN);
|
$this->syncChunkLoad = self::newTimer($name, "Chunk Load");
|
||||||
$this->syncChunkLoadData = new TimingsHandler($name . "Chunk Load - Data", group: Timings::GROUP_BREAKDOWN);
|
$this->syncChunkLoadData = self::newTimer($name, "Chunk Load - Data");
|
||||||
$this->syncChunkLoadFixInvalidBlocks = new TimingsHandler($name . "Chunk Load - Fix Invalid Blocks", group: Timings::GROUP_BREAKDOWN);
|
$this->syncChunkLoadFixInvalidBlocks = self::newTimer($name, "Chunk Load - Fix Invalid Blocks");
|
||||||
$this->syncChunkLoadEntities = new TimingsHandler($name . "Chunk Load - Entities", group: Timings::GROUP_BREAKDOWN);
|
$this->syncChunkLoadEntities = self::newTimer($name, "Chunk Load - Entities");
|
||||||
$this->syncChunkLoadTileEntities = new TimingsHandler($name . "Chunk Load - TileEntities", group: Timings::GROUP_BREAKDOWN);
|
$this->syncChunkLoadTileEntities = self::newTimer($name, "Chunk Load - Block Entities");
|
||||||
$this->syncChunkSave = new TimingsHandler($name . "Chunk Save", Timings::$worldSave, group: Timings::GROUP_BREAKDOWN);
|
|
||||||
|
|
||||||
$this->doTick = new TimingsHandler($name . "World Tick");
|
$this->syncDataSave = self::newTimer($name, "Data Save");
|
||||||
|
$this->syncChunkSave = self::newTimer($name, "Chunk Save");
|
||||||
|
|
||||||
|
$this->chunkPopulationOrder = self::newTimer($name, "Chunk Population - Order");
|
||||||
|
$this->chunkPopulationCompletion = self::newTimer($name, "Chunk Population - Completion");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ class BlockTest extends TestCase{
|
|||||||
* @return int[][]
|
* @return int[][]
|
||||||
* @phpstan-return list<array{int,int}>
|
* @phpstan-return list<array{int,int}>
|
||||||
*/
|
*/
|
||||||
public function blockGetProvider() : array{
|
public static function blockGetProvider() : array{
|
||||||
return [
|
return [
|
||||||
[BlockLegacyIds::STONE, 5],
|
[BlockLegacyIds::STONE, 5],
|
||||||
[BlockLegacyIds::GOLD_BLOCK, 0],
|
[BlockLegacyIds::GOLD_BLOCK, 0],
|
||||||
|
@ -33,7 +33,7 @@ class BrewingStandTest extends TestCase{
|
|||||||
/**
|
/**
|
||||||
* @phpstan-return \Generator<int, array{list<BrewingStandSlot>}, void, void>
|
* @phpstan-return \Generator<int, array{list<BrewingStandSlot>}, void, void>
|
||||||
*/
|
*/
|
||||||
public function slotsProvider() : \Generator{
|
public static function slotsProvider() : \Generator{
|
||||||
yield [array_values(BrewingStandSlot::getAll())];
|
yield [array_values(BrewingStandSlot::getAll())];
|
||||||
yield [[BrewingStandSlot::EAST()]];
|
yield [[BrewingStandSlot::EAST()]];
|
||||||
yield [[BrewingStandSlot::EAST(), BrewingStandSlot::NORTHWEST()]];
|
yield [[BrewingStandSlot::EAST(), BrewingStandSlot::NORTHWEST()]];
|
||||||
|
@ -27,7 +27,7 @@ use PHPUnit\Framework\TestCase;
|
|||||||
|
|
||||||
class CommandStringHelperTest extends TestCase{
|
class CommandStringHelperTest extends TestCase{
|
||||||
|
|
||||||
public function parseQuoteAwareProvider() : \Generator{
|
public static function parseQuoteAwareProvider() : \Generator{
|
||||||
yield [
|
yield [
|
||||||
'give "steve jobs" apple',
|
'give "steve jobs" apple',
|
||||||
['give', 'steve jobs', 'apple']
|
['give', 'steve jobs', 'apple']
|
||||||
|
@ -49,7 +49,7 @@ class HandlerListManagerTest extends TestCase{
|
|||||||
* @return \Generator|mixed[][]
|
* @return \Generator|mixed[][]
|
||||||
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, bool, string}, void, void>
|
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, bool, string}, void, void>
|
||||||
*/
|
*/
|
||||||
public function isValidClassProvider() : \Generator{
|
public static function isValidClassProvider() : \Generator{
|
||||||
yield [new \ReflectionClass(Event::class), false, "event base should not be handleable"];
|
yield [new \ReflectionClass(Event::class), false, "event base should not be handleable"];
|
||||||
yield [new \ReflectionClass(TestConcreteEvent::class), true, ""];
|
yield [new \ReflectionClass(TestConcreteEvent::class), true, ""];
|
||||||
yield [new \ReflectionClass(TestAbstractEvent::class), false, "abstract event cannot be handled"];
|
yield [new \ReflectionClass(TestAbstractEvent::class), false, "abstract event cannot be handled"];
|
||||||
@ -69,7 +69,7 @@ class HandlerListManagerTest extends TestCase{
|
|||||||
* @return \Generator|\ReflectionClass[][]
|
* @return \Generator|\ReflectionClass[][]
|
||||||
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, \ReflectionClass<Event>|null}, void, void>
|
* @phpstan-return \Generator<int, array{\ReflectionClass<Event>, \ReflectionClass<Event>|null}, void, void>
|
||||||
*/
|
*/
|
||||||
public function resolveParentClassProvider() : \Generator{
|
public static function resolveParentClassProvider() : \Generator{
|
||||||
yield [new \ReflectionClass(TestConcreteExtendsAllowHandleEvent::class), new \ReflectionClass(TestAbstractAllowHandleEvent::class)];
|
yield [new \ReflectionClass(TestConcreteExtendsAllowHandleEvent::class), new \ReflectionClass(TestAbstractAllowHandleEvent::class)];
|
||||||
yield [new \ReflectionClass(TestConcreteEvent::class), null];
|
yield [new \ReflectionClass(TestConcreteEvent::class), null];
|
||||||
yield [new \ReflectionClass(TestConcreteExtendsAbstractEvent::class), null];
|
yield [new \ReflectionClass(TestConcreteExtendsAbstractEvent::class), null];
|
||||||
|
@ -31,7 +31,7 @@ class LegacyStringToItemParserTest extends TestCase{
|
|||||||
* @return mixed[][]
|
* @return mixed[][]
|
||||||
* @phpstan-return list<array{string,int,int}>
|
* @phpstan-return list<array{string,int,int}>
|
||||||
*/
|
*/
|
||||||
public function itemFromStringProvider() : array{
|
public static function itemFromStringProvider() : array{
|
||||||
return [
|
return [
|
||||||
["dye:4", ItemIds::DYE, 4],
|
["dye:4", ItemIds::DYE, 4],
|
||||||
["351", ItemIds::DYE, 0],
|
["351", ItemIds::DYE, 0],
|
||||||
|
@ -32,7 +32,7 @@ class ApiVersionTest extends TestCase{
|
|||||||
* @return \Generator|mixed[][]
|
* @return \Generator|mixed[][]
|
||||||
* @phpstan-return \Generator<int, array{string, string, bool}, void, void>
|
* @phpstan-return \Generator<int, array{string, string, bool}, void, void>
|
||||||
*/
|
*/
|
||||||
public function compatibleApiProvider() : \Generator{
|
public static function compatibleApiProvider() : \Generator{
|
||||||
yield ["3.0.0", "3.0.0", true];
|
yield ["3.0.0", "3.0.0", true];
|
||||||
yield ["3.1.0", "3.0.0", true];
|
yield ["3.1.0", "3.0.0", true];
|
||||||
yield ["3.0.0", "3.1.0", false];
|
yield ["3.0.0", "3.1.0", false];
|
||||||
@ -58,7 +58,7 @@ class ApiVersionTest extends TestCase{
|
|||||||
* @return mixed[][][]
|
* @return mixed[][][]
|
||||||
* @phpstan-return \Generator<int, array{list<string>, list<string>}, void, void>
|
* @phpstan-return \Generator<int, array{list<string>, list<string>}, void, void>
|
||||||
*/
|
*/
|
||||||
public function ambiguousVersionsProvider() : \Generator{
|
public static function ambiguousVersionsProvider() : \Generator{
|
||||||
yield [["3.0.0"], []];
|
yield [["3.0.0"], []];
|
||||||
yield [["3.0.0", "3.0.1"], ["3.0.0", "3.0.1"]];
|
yield [["3.0.0", "3.0.1"], ["3.0.0", "3.0.1"]];
|
||||||
yield [["3.0.0", "3.1.0", "4.0.0"], ["3.0.0", "3.1.0"]];
|
yield [["3.0.0", "3.1.0", "4.0.0"], ["3.0.0", "3.1.0"]];
|
||||||
|
@ -69,4 +69,36 @@ class AsyncPoolTest extends TestCase{
|
|||||||
}
|
}
|
||||||
self::assertTrue(PublishProgressRaceAsyncTask::$success, "Progress was not reported before task completion");
|
self::assertTrue(PublishProgressRaceAsyncTask::$success, "Progress was not reported before task completion");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test ensures that the fix for an exotic AsyncTask::__destruct() reentrancy bug has not regressed.
|
||||||
|
*
|
||||||
|
* Due to an unset() in the function body, other AsyncTask::__destruct() calls could be triggered during
|
||||||
|
* an AsyncTask's destruction. If done in the wrong way, this could lead to a crash.
|
||||||
|
*
|
||||||
|
* @doesNotPerformAssertions This test is checking for a crash condition, not a specific output.
|
||||||
|
*/
|
||||||
|
public function testTaskDestructorReentrancy() : void{
|
||||||
|
$this->pool->submitTask(new class extends AsyncTask{
|
||||||
|
public function __construct(){
|
||||||
|
$this->storeLocal("task", new class extends AsyncTask{
|
||||||
|
|
||||||
|
public function __construct(){
|
||||||
|
$this->storeLocal("dummy", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onRun() : void{
|
||||||
|
//dummy
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onRun() : void{
|
||||||
|
//dummy
|
||||||
|
}
|
||||||
|
});
|
||||||
|
while($this->pool->collectTasks()){
|
||||||
|
usleep(50 * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ final class CloningRegistryTraitTest extends TestCase{
|
|||||||
/**
|
/**
|
||||||
* @phpstan-return \Generator<int, array{\Closure() : \stdClass}, void, void>
|
* @phpstan-return \Generator<int, array{\Closure() : \stdClass}, void, void>
|
||||||
*/
|
*/
|
||||||
public function cloningRegistryMembersProvider() : \Generator{
|
public static function cloningRegistryMembersProvider() : \Generator{
|
||||||
yield [function() : \stdClass{ return TestCloningRegistry::TEST1(); }];
|
yield [function() : \stdClass{ return TestCloningRegistry::TEST1(); }];
|
||||||
yield [function() : \stdClass{ return TestCloningRegistry::TEST2(); }];
|
yield [function() : \stdClass{ return TestCloningRegistry::TEST2(); }];
|
||||||
yield [function() : \stdClass{ return TestCloningRegistry::TEST3(); }];
|
yield [function() : \stdClass{ return TestCloningRegistry::TEST3(); }];
|
||||||
|
@ -32,7 +32,7 @@ class ConfigTest extends TestCase{
|
|||||||
* @return \Generator|mixed[][]
|
* @return \Generator|mixed[][]
|
||||||
* @phpstan-return \Generator<int, array{string, mixed[]}, void, void>
|
* @phpstan-return \Generator<int, array{string, mixed[]}, void, void>
|
||||||
*/
|
*/
|
||||||
public function fixYamlIndexesProvider() : \Generator{
|
public static function fixYamlIndexesProvider() : \Generator{
|
||||||
yield ["x: 1\ny: 2\nz: 3\n", [
|
yield ["x: 1\ny: 2\nz: 3\n", [
|
||||||
"x" => 1,
|
"x" => 1,
|
||||||
"y" => 2,
|
"y" => 2,
|
||||||
|
@ -44,7 +44,7 @@ class UtilsTest extends TestCase{
|
|||||||
* @return string[][]
|
* @return string[][]
|
||||||
* @phpstan-return list<array{string}>
|
* @phpstan-return list<array{string}>
|
||||||
*/
|
*/
|
||||||
public function parseDocCommentNewlineProvider() : array{
|
public static function parseDocCommentNewlineProvider() : array{
|
||||||
return [
|
return [
|
||||||
["\t/**\r\n\t * @param PlayerJoinEvent \$event\r\n\t * @priority HIGHEST\r\n\t * @notHandler\r\n\t */"],
|
["\t/**\r\n\t * @param PlayerJoinEvent \$event\r\n\t * @priority HIGHEST\r\n\t * @notHandler\r\n\t */"],
|
||||||
["\t/**\n\t * @param PlayerJoinEvent \$event\n\t * @priority HIGHEST\n\t * @notHandler\n\t */"],
|
["\t/**\n\t * @param PlayerJoinEvent \$event\n\t * @priority HIGHEST\n\t * @notHandler\n\t */"],
|
||||||
@ -68,7 +68,7 @@ class UtilsTest extends TestCase{
|
|||||||
* @return string[][]
|
* @return string[][]
|
||||||
* @phpstan-return list<array{string}>
|
* @phpstan-return list<array{string}>
|
||||||
*/
|
*/
|
||||||
public function parseDocCommentOneLineProvider() : array{
|
public static function parseDocCommentOneLineProvider() : array{
|
||||||
return [
|
return [
|
||||||
["/** @ignoreCancelled true dummy */"],
|
["/** @ignoreCancelled true dummy */"],
|
||||||
["/**@ignoreCancelled true dummy*/"],
|
["/**@ignoreCancelled true dummy*/"],
|
||||||
@ -105,7 +105,7 @@ class UtilsTest extends TestCase{
|
|||||||
* @return string[][]
|
* @return string[][]
|
||||||
* @return list<array{class-string, class-string}>
|
* @return list<array{class-string, class-string}>
|
||||||
*/
|
*/
|
||||||
public function validInstanceProvider() : array{
|
public static function validInstanceProvider() : array{
|
||||||
return [
|
return [
|
||||||
//direct instance / implement / extend
|
//direct instance / implement / extend
|
||||||
[TestInstantiableClass::class, TestInstantiableClass::class],
|
[TestInstantiableClass::class, TestInstantiableClass::class],
|
||||||
@ -133,7 +133,7 @@ class UtilsTest extends TestCase{
|
|||||||
* @return string[][]
|
* @return string[][]
|
||||||
* @return list<array{string, string}>
|
* @return list<array{string, string}>
|
||||||
*/
|
*/
|
||||||
public function validInstanceInvalidCombinationsProvider() : array{
|
public static function validInstanceInvalidCombinationsProvider() : array{
|
||||||
return [
|
return [
|
||||||
["iDontExist abc", TestInstantiableClass::class],
|
["iDontExist abc", TestInstantiableClass::class],
|
||||||
[TestInstantiableClass::class, "iDon'tExist abc"],
|
[TestInstantiableClass::class, "iDon'tExist abc"],
|
||||||
|
@ -74,7 +74,7 @@ class RegionLoaderTest extends TestCase{
|
|||||||
* @return \Generator|int[][]
|
* @return \Generator|int[][]
|
||||||
* @phpstan-return \Generator<int, array{int,int}, void, void>
|
* @phpstan-return \Generator<int, array{int,int}, void, void>
|
||||||
*/
|
*/
|
||||||
public function outOfBoundsCoordsProvider() : \Generator{
|
public static function outOfBoundsCoordsProvider() : \Generator{
|
||||||
yield [-1, -1];
|
yield [-1, -1];
|
||||||
yield [32, 32];
|
yield [32, 32];
|
||||||
yield [-1, 32];
|
yield [-1, 32];
|
||||||
|
@ -31,7 +31,7 @@ class RegionLocationTableEntryTest extends TestCase{
|
|||||||
/**
|
/**
|
||||||
* @phpstan-return \Generator<int, array{RegionLocationTableEntry, RegionLocationTableEntry, bool}, void, void>
|
* @phpstan-return \Generator<int, array{RegionLocationTableEntry, RegionLocationTableEntry, bool}, void, void>
|
||||||
*/
|
*/
|
||||||
public function overlapDataProvider() : \Generator{
|
public static function overlapDataProvider() : \Generator{
|
||||||
yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(2, 1, 0), true];
|
yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(2, 1, 0), true];
|
||||||
yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(3, 1, 0), false];
|
yield [new RegionLocationTableEntry(2, 1, 0), new RegionLocationTableEntry(3, 1, 0), false];
|
||||||
yield [new RegionLocationTableEntry(2, 2, 0), new RegionLocationTableEntry(3, 2, 0), true];
|
yield [new RegionLocationTableEntry(2, 2, 0), new RegionLocationTableEntry(3, 2, 0), true];
|
||||||
|
Submodule tests/plugins/DevTools updated: a67f9af8d6...a2f36e8dbf
Reference in New Issue
Block a user