Compare commits

...

54 Commits

Author SHA1 Message Date
f2df702c67 Release 4.21.1 2023-05-30 14:42:59 +01:00
481270e6aa Merge tag '4.20.5' into stable 2023-05-30 14:42:11 +01:00
a897bdfaa0 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-05-30 14:17:21 +01:00
09668a37d6 Use fork of JsonMapper to solve cweiske/JsonMapper#210 2023-05-30 14:17:09 +01:00
ea92a23d0d Bump build/php from b1d5c0d to f2ece7b (#5765)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `b1d5c0d` to `f2ece7b`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](b1d5c0d737...f2ece7b30d)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 21:37:18 +01:00
691e67018d Bump phpstan/phpstan-phpunit from 1.3.11 to 1.3.13 (#5772)
Bumps [phpstan/phpstan-phpunit](https://github.com/phpstan/phpstan-phpunit) from 1.3.11 to 1.3.13.
- [Release notes](https://github.com/phpstan/phpstan-phpunit/releases)
- [Commits](https://github.com/phpstan/phpstan-phpunit/compare/1.3.11...1.3.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 21:36:58 +01:00
fe2140a716 Bump shivammathur/setup-php from 2.25.1 to 2.25.2 (#5766)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.25.1 to 2.25.2.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.25.1...2.25.2)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-29 21:36:46 +01:00
57330a7186 Bump build/php from f860ade to b1d5c0d (#5760)
Bumps [build/php](https://github.com/pmmp/php-build-scripts) from `f860ade` to `b1d5c0d`.
- [Release notes](https://github.com/pmmp/php-build-scripts/releases)
- [Commits](f860ade30a...b1d5c0d737)

---
updated-dependencies:
- dependency-name: build/php
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 12:27:12 +01:00
9ddac21de0 Bump shivammathur/setup-php from 2.24.0 to 2.25.1 (#5711)
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.24.0 to 2.25.1.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.24.0...2.25.1)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-21 00:13:22 +01:00
c91aa24daa Bump phpunit/phpunit from 9.6.8 to 10.1.3 (#5753)
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 9.6.8 to 10.1.3.
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/10.1.3/ChangeLog-10.1.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/9.6.8...10.1.3)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-19 15:09:24 +01:00
6186fc0bfe 4.21.1 is next 2023-05-17 16:45:35 +01:00
ef40934d24 Release 4.21.0 2023-05-17 16:45:32 +01:00
69b668355f Merge branch 'minor-next' into stable 2023-05-17 16:12:24 +01:00
0547383296 Update build/php submodule to pmmp/PHP-Binaries@f860ade30a 2023-05-17 15:08:05 +01:00
c7dff9ea40 bootstrap: remove ext-parallel bootstrapping code
I have no intention of using parallel, so this code is not necessary.
2023-05-17 14:11:43 +01:00
043350753b Drop PHP 8.0, 8.1 is now minimum version 2023-05-17 13:53:57 +01:00
5ad8016b99 Merge branch 'stable' into minor-next 2023-05-17 13:44:45 +01:00
2e5b2eed6e Update composer dependencies 2023-05-17 13:43:28 +01:00
5a0cde49cc AsyncPool: do not double-check progress updates on finished tasks
checkProgressUpdates is called directly before onCompletion, so we only need to call it again if the task isn't finished yet.
2023-05-16 23:37:58 +01:00
008a022ec1 Players now have finite resources in spectator mode
this seems like the logical solution for the block picking issues.
2023-05-16 23:02:33 +01:00
5c85a7c306 Merge remote-tracking branch 'origin/stable' into minor-next 2023-05-16 22:54:53 +01:00
599c4284f5 Introduce 10 KB threshold for async compression
due to the extremely large performance cost of instantiating AsyncTasks, it's usually not worth bothering with async compression except for very large packets.
While this large overhead can be significantly reduced by using specialized threads, it's early days in the testing stages for such improvements, and for now, we still have this to deal with.

Since async compression is always used prior to player spawn, this change may slightly improve the performance of the pre-join stage of the game.
2023-05-16 22:54:06 +01:00
9499e2e595 always the CS... 2023-05-16 14:22:03 +01:00
a4fea1444a Remove validateCallableSignature() calls from network hot paths
we rely on phpstan for validation of this internally, and plugins shouldn't be calling these methods anyway.
this significantly reduces the overhead of CompressBatchPromise.
2023-05-16 14:21:32 +01:00
1ba47802a8 Merge branch 'stable' of github.com:pmmp/PocketMine-MP into stable 2023-05-15 14:59:02 +01:00
9d111e13f1 CONTRIBUTING: added table of in-house dependencies and which classes, functions or namespaces they contain 2023-05-15 14:58:31 +01:00
44bc4d8c7c Bump phpstan/phpstan from 1.10.14 to 1.10.15 (#5741)
Bumps [phpstan/phpstan](https://github.com/phpstan/phpstan) from 1.10.14 to 1.10.15.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.14...1.10.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-10 15:02:59 +01:00
d317347a9b WorldTimings: remove TODO
I tried this, and it didn't really provide any information that the tree table didn't already show.
2023-05-08 16:35:30 +01:00
077fac84bf Added aggregate timers for all world timings
this allows timings list view to display totals for these sections. It does make the tree view a bit more annoying in some cases though.
2023-05-08 16:27:46 +01:00
fdb3a5b121 Fixed incorrect implementation of peak timings 2023-05-07 18:29:37 +01:00
e3bc36ab5b Merge branch 'stable' into minor-next 2023-05-06 18:26:47 +01:00
283ff28aa9 4.20.5 is next 2023-05-06 18:20:19 +01:00
bb60a9057f Merge branch 'stable' into minor-next 2023-05-06 17:08:29 +01:00
02cf5ed388 RuntimeBlockMapping: lazy-load NBT blockstates
this saves a considerable amount of memory.

we don't actually need this state array in PM4 anyway, since we don't support the client-side chunk cache yet.
when the time comes to support it, it'll be much more practical to cache binary states and copy bytes anyway, instead of doing it the current way, which is both slow and memory-intensive.

Measured footprint change: 9 MB -> 400 KB.
2023-05-05 16:18:03 +01:00
6cad559dbe Merge branch 'stable' into minor-next 2023-05-05 16:08:30 +01:00
84a943bcec BaseInventory: slap a TODO on isSlotEmpty() 2023-05-05 16:06:37 +01:00
73bf5d4b29 DoubleChestInventory: specialize isSlotEmpty() and getMatchingItemCount() 2023-04-27 21:17:55 +01:00
eb136e60c8 BaseInventory: added getMatchingItemCount() helper
this eliminates the performance issues described by #5719.
closes #5719

we may want to consider exposing a public API for this in the future, since it might be useful for plugins.
2023-04-27 21:08:35 +01:00
4228880509 BaseInventory: change dumb variable names in internalAddItem() 2023-04-27 20:29:02 +01:00
709d874204 BaseInventory: clean up max stack size handling
we can safely assume that:
- the inventory's max stack size won't change during the operation
- two items which stack together have the same max stack size
- the item's max stack size won't change during the operation
2023-04-27 20:27:05 +01:00
07dc10d6e6 World: improve performance of tickChunks() selection process (#5721)
Since light population is required to make a chunk tickable, a chunk may not be tickable for some time if the async workers get backlogged.
The previous version of this system only cached the eligibility result if the result was a "yes", but we can also track it when it's a "no", rather than rechecking it every tick.

This change should improve performance in factions and similar gamemodes, which involve large maps with sparsely distributed players, where each player likely has an independent, non-overlapping ticking chunk circle.

We also ditch TickingChunkEntry in favour of multiple arrays to track the eligibility states. This allows us to avoid rechecking the (even cached) readiness of potentially thousands of chunks. If there are no ticking chunks to recheck, this reduces the cost of the selection process to zero.
2023-04-27 16:59:29 +01:00
7f6269c432 Introduce and use optimised versions of Inventory->isSlotEmpty()
this avoids useless cloning, improving the performance of several functions.
2023-04-27 16:52:52 +01:00
194714b448 Merge branch 'stable' into minor-next 2023-04-27 15:37:54 +01:00
4def4d52d9 Merge branch 'stable' into minor-next 2023-04-26 23:22:00 +01:00
9bfcd39f2a World: improve type info for getTickingChunks() 2023-04-26 17:06:52 +01:00
8102616ff4 Added ticking chunk count to /status
closes #5716
2023-04-26 17:05:31 +01:00
eb130f2906 Move primary version to PHP 8.1
8.0 is still supported for now, but won't be updated any longer.
2023-04-26 16:03:33 +01:00
eb4679fefd Merge remote-tracking branch 'origin/stable' into minor-next 2023-04-26 14:28:29 +01:00
71b78b02d3 Merge remote-tracking branch 'origin/stable' into minor-next 2023-04-19 23:57:26 +01:00
84cb070d56 Merge branch 'stable' into minor-next 2023-04-14 20:12:33 +01:00
8102586ee0 Merge branch 'stable' into minor-next 2023-04-12 21:04:03 +01:00
0336394098 Human: remove useless NameTag tag
this is not written anywhere, so this code never does anything.
2023-04-12 16:44:10 +01:00
1569bed37a Merge branch 'stable' into minor-next 2023-04-11 23:56:22 +01:00
a6e79c5004 TimingsHandler: remove useless paste metadata
these fields are not used by any version of timings, so this code is redundant.
2023-04-11 23:45:01 +01:00
32 changed files with 671 additions and 732 deletions

View File

@ -13,9 +13,9 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.24.0
uses: shivammathur/setup-php@2.25.2
with:
php-version: 8.0
php-version: 8.1
- name: Restore Composer package cache
uses: actions/cache@v3

View File

@ -18,9 +18,9 @@ jobs:
submodules: true
- name: Setup PHP
uses: shivammathur/setup-php@2.24.0
uses: shivammathur/setup-php@2.25.2
with:
php-version: 8.0
php-version: 8.1
- name: Restore Composer package cache
uses: actions/cache@v3

View File

@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.18, 8.2.5]
php: [8.1.19, 8.2.6]
steps:
- name: Build and prepare PHP cache
@ -32,7 +32,7 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.18, 8.2.5]
php: [8.1.19, 8.2.6]
steps:
- uses: actions/checkout@v3
@ -71,7 +71,7 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.18, 8.2.5]
php: [8.1.19, 8.2.6]
steps:
- uses: actions/checkout@v3
@ -110,7 +110,7 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.18, 8.2.5]
php: [8.1.19, 8.2.6]
steps:
- uses: actions/checkout@v3
@ -151,7 +151,7 @@ jobs:
fail-fast: false
matrix:
image: [ubuntu-20.04]
php: [8.0.28, 8.1.18, 8.2.5]
php: [8.1.19, 8.2.6]
steps:
- uses: actions/checkout@v3
@ -203,9 +203,9 @@ jobs:
- uses: actions/checkout@v3
- name: Setup PHP and tools
uses: shivammathur/setup-php@2.24.0
uses: shivammathur/setup-php@2.25.2
with:
php-version: 8.0
php-version: 8.1
tools: php-cs-fixer:3.16
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -22,7 +22,6 @@
declare(strict_types=1);
const VERSIONS = [
"8.0",
"8.1",
"8.2"
];

View File

@ -2,13 +2,13 @@
## Pre-requisites
- A bash shell (git bash is sufficient for Windows)
- [`git`](https://git-scm.com) available in your shell
- PHP 8.0 or newer available in your shell
- PHP 8.1 or newer available in your shell
- [`composer`](https://getcomposer.org) available in your shell
## Custom PHP binaries
Because PocketMine-MP requires several non-standard PHP extensions and configuration, PMMP provides scripts to build custom binaries for running PocketMine-MP, as well as prebuilt binaries.
- [Prebuilt binaries](https://jenkins.pmmp.io/job/PHP-8.0-Aggregate)
- [Prebuilt binaries](https://github.com/pmmp/PHP-Binaries/releases)
- [Compile scripts](https://github.com/pmmp/php-build-scripts) are provided as a submodule in the path `build/php`
If you use a custom binary, you'll need to replace `composer` usages in this guide with `path/to/your/php path/to/your/composer.phar`.
@ -29,11 +29,5 @@ Run `composer make-server` using your preferred PHP binary. It'll drop a `Pocket
You can also use the `--out` option to change the output filename.
There is a bug in PHP that might cause an error which looks like this:
```
Fatal error: Uncaught BadMethodCallException: unable to create temporary file in PocketMine-MP/build/server-phar.php:119
```
You can work around it by setting `ulimit -n` to some bigger number, e.g. `8192`, or by updating your PHP version to at least 8.0.3.
## Running PocketMine-MP from source code
Run `src/PocketMine.php` using your preferred PHP binary.

View File

@ -18,6 +18,30 @@ Larger contributions like feature additions should be preceded by a [Change Prop
## Other things you'll need
- [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
PocketMine-MP has three primary branches of development.

77
changelogs/4.21.md Normal file
View 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).

View File

@ -5,7 +5,7 @@
"homepage": "https://pmmp.io",
"license": "LGPL-3.0",
"require": {
"php": "^8.0",
"php": "^8.1",
"php-64bit": "*",
"ext-chunkutils2": "^0.3.1",
"ext-crypto": "^0.3.1",
@ -56,10 +56,10 @@
"webmozart/path-util": "^2.3"
},
"require-dev": {
"phpstan/phpstan": "1.10.14",
"phpstan/phpstan": "1.10.15",
"phpstan/phpstan-phpunit": "^1.1.0",
"phpstan/phpstan-strict-rules": "^1.2.0",
"phpunit/phpunit": "^9.2"
"phpunit/phpunit": "^10.1"
},
"autoload": {
"psr-4": {
@ -77,7 +77,7 @@
},
"config": {
"platform": {
"php": "8.0.0"
"php": "8.1.0"
},
"sort-packages": true
},

702
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -85,8 +85,11 @@ network:
batch-threshold: 256
#Compression level used when sending batched packets. Higher = more CPU, less bandwidth usage
compression-level: 6
#Use AsyncTasks for compression. Adds half/one tick delay, less CPU load on main thread
#Use AsyncTasks for compression during the main game session. Increases latency, but may reduce main thread load
async-compression: false
#Threshold for async compression, in bytes. Only packets larger than this will be compressed asynchronously
#Due to large overhead of AsyncTask, async compression isn't worth it except for large packets
async-compression-threshold: 10000
#Experimental. Use UPnP to automatically port forward
upnp-forwarding: false
#Maximum size in bytes of packets sent over the network (default 1492 bytes). Packets larger than this will be

View File

@ -50,7 +50,7 @@ namespace pocketmine {
require_once __DIR__ . '/VersionInfo.php';
const MIN_PHP_VERSION = "8.0.0";
const MIN_PHP_VERSION = "8.1.0";
/**
* @param string $message
@ -265,9 +265,6 @@ JIT_WARNING
exit(1);
}
}
if(extension_loaded('parallel')){
\parallel\bootstrap(\pocketmine\COMPOSER_AUTOLOADER_PATH);
}
ErrorToExceptionHandler::set();

View File

@ -208,6 +208,8 @@ class Server{
private const TICKS_PER_TPS_OVERLOAD_WARNING = 5 * self::TARGET_TICKS_PER_SECOND;
private const TICKS_PER_STATS_REPORT = 300 * self::TARGET_TICKS_PER_SECOND;
private const DEFAULT_ASYNC_COMPRESSION_THRESHOLD = 10_000;
private static ?Server $instance = null;
private TimeTrackingSleeperHandler $tickSleeper;
@ -266,6 +268,7 @@ class Server{
private Network $network;
private bool $networkCompressionAsync = true;
private int $networkCompressionAsyncThreshold = self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD;
private Language $language;
private bool $forceLanguage = false;
@ -908,6 +911,10 @@ class Server{
ZlibCompressor::setInstance(new ZlibCompressor($netCompressionLevel, $netCompressionThreshold, ZlibCompressor::DEFAULT_MAX_DECOMPRESSION_SIZE));
$this->networkCompressionAsync = $this->configGroup->getPropertyBool("network.async-compression", true);
$this->networkCompressionAsyncThreshold = max(
$this->configGroup->getPropertyInt("network.async-compression-threshold", self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD),
$netCompressionThreshold ?? self::DEFAULT_ASYNC_COMPRESSION_THRESHOLD
);
EncryptionContext::$ENABLED = $this->configGroup->getPropertyBool("network.enable-encryption", true);
@ -1375,7 +1382,7 @@ class Server{
}
$promise = new CompressBatchPromise();
if(!$sync){
if(!$sync && strlen($buffer) >= $this->networkCompressionAsyncThreshold){
$task = new CompressBatchTask($buffer, $promise, $compressor);
$this->asyncPool->submitTask($task);
}else{

View File

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

View File

@ -85,6 +85,20 @@ class DoubleChestInventory extends BaseInventory implements BlockInventory, Inve
$this->right->setContents($rightContents);
}
protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{
$leftSize = $this->left->getSize();
return $slot < $leftSize ?
$this->left->getMatchingItemCount($slot, $test, $checkDamage, $checkTags) :
$this->right->getMatchingItemCount($slot - $leftSize, $test, $checkDamage, $checkTags);
}
public function isSlotEmpty(int $index) : bool{
$leftSize = $this->left->getSize();
return $index < $leftSize ?
$this->left->isSlotEmpty($index) :
$this->right->isSlotEmpty($index - $leftSize);
}
protected function getOpenSound() : Sound{ return new ChestOpenSound(); }
protected function getCloseSound() : Sound{ return new ChestCloseSound(); }

View File

@ -110,7 +110,8 @@ class StatusCommand extends VanillaCommand{
$worldName = $world->getFolderName() !== $world->getDisplayName() ? " (" . $world->getDisplayName() . ")" : "";
$timeColor = $world->getTickRateTime() > 40 ? TextFormat::RED : TextFormat::YELLOW;
$sender->sendMessage(TextFormat::GOLD . "World \"{$world->getFolderName()}\"$worldName: " .
TextFormat::RED . number_format(count($world->getLoadedChunks())) . TextFormat::GREEN . " chunks, " .
TextFormat::RED . number_format(count($world->getLoadedChunks())) . TextFormat::GREEN . " loaded chunks, " .
TextFormat::RED . number_format(count($world->getTickingChunks())) . TextFormat::GREEN . " ticking chunks, " .
TextFormat::RED . number_format(count($world->getEntities())) . TextFormat::GREEN . " entities. " .
"Time $timeColor" . round($world->getTickRateTime(), 2) . "ms"
);

View File

@ -92,7 +92,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
private const TAG_XP_PROGRESS = "XpP"; //TAG_Float
private const TAG_LIFETIME_XP_TOTAL = "XpTotal"; //TAG_Int
private const TAG_XP_SEED = "XpSeed"; //TAG_Int
private const TAG_NAME_TAG = "NameTag"; //TAG_String
private const TAG_SKIN = "Skin"; //TAG_Compound
private const TAG_SKIN_NAME = "Name"; //TAG_String
private const TAG_SKIN_DATA = "Data"; //TAG_ByteArray
@ -245,10 +244,6 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
* For Human entities which are not players, sets their properties such as nametag, skin and UUID from NBT.
*/
protected function initHumanData(CompoundTag $nbt) : void{
if(($nameTagTag = $nbt->getTag(self::TAG_NAME_TAG)) instanceof StringTag){
$this->setNameTag($nameTagTag->getValue());
}
//TODO: use of NIL UUID for namespace is a hack; we should provide a proper UUID for the namespace
$this->uuid = Uuid::uuid3(Uuid::NIL, ((string) $this->getId()) . $this->skin->getSkinData() . $this->getNameTag());
}

View File

@ -108,13 +108,23 @@ abstract class BaseInventory implements Inventory{
$this->onContentChange($oldContents);
}
/**
* Helper for utility functions which search the inventory.
* TODO: make this abstract instead of providing a slow default implementation (BC break)
*/
protected function getMatchingItemCount(int $slot, Item $test, bool $checkDamage, bool $checkTags) : int{
$item = $this->getItem($slot);
return $item->equals($test, $checkDamage, $checkTags) ? $item->getCount() : 0;
}
public function contains(Item $item) : bool{
$count = max(1, $item->getCount());
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasNamedTag();
foreach($this->getContents() as $i){
if($item->equals($i, $checkDamage, $checkTags)){
$count -= $i->getCount();
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
$slotCount = $this->getMatchingItemCount($i, $item, $checkDamage, $checkTags);
if($slotCount > 0){
$count -= $slotCount;
if($count <= 0){
return true;
}
@ -128,9 +138,9 @@ abstract class BaseInventory implements Inventory{
$slots = [];
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasNamedTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags)){
$slots[$index] = $i;
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
if($this->getMatchingItemCount($i, $item, $checkDamage, $checkTags) > 0){
$slots[$i] = $this->getItem($i);
}
}
@ -142,18 +152,9 @@ abstract class BaseInventory implements Inventory{
$checkDamage = $exact || !$item->hasAnyDamageValue();
$checkTags = $exact || $item->hasNamedTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags) && ($i->getCount() === $count || (!$exact && $i->getCount() > $count))){
return $index;
}
}
return -1;
}
public function firstEmpty() : int{
foreach($this->getContents(true) as $i => $slot){
if($slot->isNull()){
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
$slotCount = $this->getMatchingItemCount($i, $item, $checkDamage, $checkTags);
if($slotCount > 0 && ($slotCount === $count || (!$exact && $slotCount > $count))){
return $i;
}
}
@ -161,6 +162,20 @@ abstract class BaseInventory implements Inventory{
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{
return $this->getItem($index)->isNull();
}
@ -171,14 +186,16 @@ abstract class BaseInventory implements Inventory{
public function getAddableItemQuantity(Item $item) : int{
$count = $item->getCount();
$maxStackSize = min($this->getMaxStackSize(), $item->getMaxStackSize());
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$slot = $this->getItem($i);
if($item->canStackWith($slot)){
if(($diff = min($slot->getMaxStackSize(), $item->getMaxStackSize()) - $slot->getCount()) > 0){
if($this->isSlotEmpty($i)){
$count -= $maxStackSize;
}else{
$slotCount = $this->getMatchingItemCount($i, $item, true, true);
if($slotCount > 0 && ($diff = $maxStackSize - $slotCount) > 0){
$count -= $diff;
}
}elseif($slot->isNull()){
$count -= min($this->getMaxStackSize(), $item->getMaxStackSize());
}
if($count <= 0){
@ -212,23 +229,29 @@ abstract class BaseInventory implements Inventory{
return $returnSlots;
}
private function internalAddItem(Item $slot) : Item{
private function internalAddItem(Item $newItem) : Item{
$emptySlots = [];
$maxStackSize = min($this->getMaxStackSize(), $newItem->getMaxStackSize());
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$item = $this->getItem($i);
if($item->isNull()){
if($this->isSlotEmpty($i)){
$emptySlots[] = $i;
continue;
}
$slotCount = $this->getMatchingItemCount($i, $newItem, true, true);
if($slotCount === 0){
continue;
}
if($slot->canStackWith($item) && $item->getCount() < $item->getMaxStackSize()){
$amount = min($item->getMaxStackSize() - $item->getCount(), $slot->getCount(), $this->getMaxStackSize());
if($slotCount < $maxStackSize){
$amount = min($maxStackSize - $slotCount, $newItem->getCount());
if($amount > 0){
$slot->setCount($slot->getCount() - $amount);
$item->setCount($item->getCount() + $amount);
$this->setItem($i, $item);
if($slot->getCount() <= 0){
$newItem->setCount($newItem->getCount() - $amount);
$slotItem = $this->getItem($i);
$slotItem->setCount($slotItem->getCount() + $amount);
$this->setItem($i, $slotItem);
if($newItem->getCount() <= 0){
break;
}
}
@ -237,65 +260,67 @@ abstract class BaseInventory implements Inventory{
if(count($emptySlots) > 0){
foreach($emptySlots as $slotIndex){
$amount = min($slot->getMaxStackSize(), $slot->getCount(), $this->getMaxStackSize());
$slot->setCount($slot->getCount() - $amount);
$item = clone $slot;
$item->setCount($amount);
$this->setItem($slotIndex, $item);
if($slot->getCount() <= 0){
$amount = min($maxStackSize, $newItem->getCount());
$newItem->setCount($newItem->getCount() - $amount);
$slotItem = clone $newItem;
$slotItem->setCount($amount);
$this->setItem($slotIndex, $slotItem);
if($newItem->getCount() <= 0){
break;
}
}
}
return $slot;
return $newItem;
}
public function remove(Item $item) : void{
$checkDamage = !$item->hasAnyDamageValue();
$checkTags = $item->hasNamedTag();
foreach($this->getContents() as $index => $i){
if($item->equals($i, $checkDamage, $checkTags)){
$this->clear($index);
for($i = 0, $size = $this->getSize(); $i < $size; $i++){
if($this->getMatchingItemCount($i, $item, $checkDamage, $checkTags) > 0){
$this->clear($i);
}
}
}
public function removeItem(Item ...$slots) : array{
/** @var Item[] $itemSlots */
/** @var Item[] $searchItems */
/** @var Item[] $slots */
$itemSlots = [];
$searchItems = [];
foreach($slots as $slot){
if(!$slot->isNull()){
$itemSlots[] = clone $slot;
$searchItems[] = clone $slot;
}
}
for($i = 0, $size = $this->getSize(); $i < $size; ++$i){
$item = $this->getItem($i);
if($item->isNull()){
if($this->isSlotEmpty($i)){
continue;
}
foreach($itemSlots as $index => $slot){
if($slot->equals($item, !$slot->hasAnyDamageValue(), $slot->hasNamedTag())){
$amount = min($item->getCount(), $slot->getCount());
$slot->setCount($slot->getCount() - $amount);
$item->setCount($item->getCount() - $amount);
$this->setItem($i, $item);
if($slot->getCount() <= 0){
unset($itemSlots[$index]);
foreach($searchItems as $index => $search){
$slotCount = $this->getMatchingItemCount($i, $search, !$search->hasAnyDamageValue(), $search->hasNamedTag());
if($slotCount > 0){
$amount = min($slotCount, $search->getCount());
$search->setCount($search->getCount() - $amount);
$slotItem = $this->getItem($i);
$slotItem->setCount($slotItem->getCount() - $amount);
$this->setItem($i, $slotItem);
if($search->getCount() <= 0){
unset($searchItems[$index]);
}
}
}
if(count($itemSlots) === 0){
if(count($searchItems) === 0){
break;
}
}
return $itemSlots;
return $searchItems;
}
public function clear(int $index) : void{

View File

@ -85,6 +85,10 @@ class DelegateInventory extends BaseInventory{
$this->backingInventory->setContents($items);
}
public function isSlotEmpty(int $index) : bool{
return $this->backingInventory->isSlotEmpty($index);
}
protected function onSlotChange(int $index, Item $before) : void{
if($this->backingInventoryChanging){
parent::onSlotChange($index, $before);

View File

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

View File

@ -107,7 +107,6 @@ use pocketmine\utils\BinaryDataException;
use pocketmine\utils\BinaryStream;
use pocketmine\utils\ObjectSet;
use pocketmine\utils\TextFormat;
use pocketmine\utils\Utils;
use pocketmine\world\Position;
use function array_map;
use function array_values;
@ -1000,8 +999,6 @@ class NetworkSession{
* @phpstan-param \Closure() : void $onCompletion
*/
public function startUsingChunk(int $chunkX, int $chunkZ, \Closure $onCompletion) : void{
Utils::validateCallableSignature(function() : void{}, $onCompletion);
$world = $this->player->getLocation()->getWorld();
ChunkCache::getInstance($world, $this->compressor)->request($chunkX, $chunkZ)->onResolve(

View File

@ -23,7 +23,6 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\compression;
use pocketmine\utils\Utils;
use function array_push;
class CompressBatchPromise{
@ -42,9 +41,6 @@ class CompressBatchPromise{
*/
public function onResolve(\Closure ...$callbacks) : void{
$this->checkCancelled();
foreach($callbacks as $callback){
Utils::validateCallableSignature(function(CompressBatchPromise $promise) : void{}, $callback);
}
if($this->result !== null){
foreach($callbacks as $callback){
$callback($this);

View File

@ -48,8 +48,8 @@ final class RuntimeBlockMapping{
private array $legacyToRuntimeMap = [];
/** @var int[] */
private array $runtimeToLegacyMap = [];
/** @var CompoundTag[] */
private array $bedrockKnownStates;
/** @var CompoundTag[]|null */
private ?array $bedrockKnownStates = null;
private static function make() : self{
return new self(
@ -85,25 +85,13 @@ final class RuntimeBlockMapping{
return $newTag;
}
public function __construct(string $canonicalBlockStatesFile, string $r12ToCurrentBlockMapFile){
$stream = new BinaryStream(Filesystem::fileGetContents($canonicalBlockStatesFile));
$list = [];
$nbtReader = new NetworkNbtSerializer();
public function __construct(
private string $canonicalBlockStatesFile,
string $r12ToCurrentBlockMapFile
){
//do not cache this - we only need it to set up mappings under normal circumstances
$bedrockKnownStates = $this->loadBedrockKnownStates();
$keyIndex = [];
$valueIndex = [];
while(!$stream->feof()){
$offset = $stream->getOffset();
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$stream->setOffset($offset);
$list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex);
}
$this->bedrockKnownStates = $list;
$this->setupLegacyMappings($r12ToCurrentBlockMapFile);
}
private function setupLegacyMappings(string $r12ToCurrentBlockMapFile) : void{
$legacyIdMap = LegacyBlockIdToStringIdMap::getInstance();
/** @var R12ToCurrentBlockMapEntry[] $legacyStateMap */
$legacyStateMap = [];
@ -123,7 +111,7 @@ final class RuntimeBlockMapping{
* @var int[][] $idToStatesMap string id -> int[] list of candidate state indices
*/
$idToStatesMap = [];
foreach($this->bedrockKnownStates as $k => $state){
foreach($bedrockKnownStates as $k => $state){
$idToStatesMap[$state->getString("name")][] = $k;
}
foreach($legacyStateMap as $pair){
@ -142,7 +130,7 @@ final class RuntimeBlockMapping{
throw new \RuntimeException("Mapped new state does not appear in network table");
}
foreach($idToStatesMap[$mappedName] as $k){
$networkState = $this->bedrockKnownStates[$k];
$networkState = $bedrockKnownStates[$k];
if($mappedState->equals($networkState)){
$this->registerMapping($k, $id, $data);
continue 2;
@ -152,6 +140,25 @@ final class RuntimeBlockMapping{
}
}
/**
* @return CompoundTag[]
*/
private function loadBedrockKnownStates() : array{
$stream = new BinaryStream(Filesystem::fileGetContents($this->canonicalBlockStatesFile));
$list = [];
$nbtReader = new NetworkNbtSerializer();
$keyIndex = [];
$valueIndex = [];
while(!$stream->feof()){
$offset = $stream->getOffset();
$blockState = $nbtReader->read($stream->getBuffer(), $offset)->mustGetCompoundTag();
$stream->setOffset($offset);
$list[] = self::deduplicateCompound($blockState, $keyIndex, $valueIndex);
}
return $list;
}
public function toRuntimeId(int $internalStateId) : int{
return $this->legacyToRuntimeMap[$internalStateId] ?? $this->legacyToRuntimeMap[BlockLegacyIds::INFO_UPDATE << Block::INTERNAL_METADATA_BITS];
}
@ -166,9 +173,14 @@ final class RuntimeBlockMapping{
}
/**
* WARNING: This method may load the palette from disk, which is a slow operation.
* Afterwards, it will cache the palette in memory, which requires (in some cases) tens of MB of memory.
* Avoid using this where possible.
*
* @deprecated
* @return CompoundTag[]
*/
public function getBedrockKnownStates() : array{
return $this->bedrockKnownStates;
return $this->bedrockKnownStates ??= $this->loadBedrockKnownStates();
}
}

View File

@ -1191,7 +1191,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
* TODO: make this a dynamic ability instead of being hardcoded
*/
public function hasFiniteResources() : bool{
return $this->gamemode->equals(GameMode::SURVIVAL()) || $this->gamemode->equals(GameMode::ADVENTURE());
return !$this->gamemode->equals(GameMode::CREATIVE());
}
public function isFireProof() : bool{
@ -1657,7 +1657,7 @@ class Player extends Human implements CommandSender, ChunkListener, IPlayer{
$ev = new PlayerBlockPickEvent($this, $block, $item);
$existingSlot = $this->inventory->first($item);
if($existingSlot === -1 && ($this->hasFiniteResources() || $this->isSpectator())){
if($existingSlot === -1 && $this->hasFiniteResources()){
$ev->cancel();
}
$ev->call();

View File

@ -247,7 +247,6 @@ class AsyncPool{
while(!$queue->isEmpty()){
/** @var AsyncTask $task */
$task = $queue->bottom();
$task->checkProgressUpdates();
if($task->isFinished()){ //make sure the task actually executed before trying to collect
$queue->dequeue();
@ -268,6 +267,7 @@ class AsyncPool{
$task->onCompletion();
}
}else{
$task->checkProgressUpdates();
$more = true;
break; //current task is still running, skip to next worker
}

View File

@ -87,14 +87,6 @@ abstract class Timings{
/** @var TimingsHandler */
public static $serverCommand;
/** @var TimingsHandler */
public static $worldLoad;
/** @var TimingsHandler */
public static $worldSave;
/** @var TimingsHandler */
public static $population;
/** @var TimingsHandler */
public static $generationCallback;
/** @var TimingsHandler */
public static $permissibleCalculation;
/** @var TimingsHandler */
public static $permissibleCalculationDiff;
@ -111,10 +103,6 @@ abstract class Timings{
/** @var TimingsHandler */
public static $playerCheckNearEntities;
/** @var TimingsHandler */
public static $tickEntity;
/** @var TimingsHandler */
public static $tickTileEntity;
/** @var TimingsHandler */
public static $entityBaseTick;
@ -203,10 +191,6 @@ abstract class Timings{
self::$playerChunkSend = new TimingsHandler("Player Network Send - Chunks", self::$playerNetworkSend, group: self::GROUP_BREAKDOWN);
self::$scheduler = new TimingsHandler("Scheduler");
self::$serverCommand = new TimingsHandler("Server Command");
self::$worldLoad = new TimingsHandler("World Load");
self::$worldSave = new TimingsHandler("World Save");
self::$population = new TimingsHandler("World Population");
self::$generationCallback = new TimingsHandler("World Generation Callback");
self::$permissibleCalculation = new TimingsHandler("Permissible Calculation");
self::$permissibleCalculationDiff = new TimingsHandler("Permissible Calculation - Diff", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
self::$permissibleCalculationCallback = new TimingsHandler("Permissible Calculation - Callbacks", self::$permissibleCalculation, group: self::GROUP_BREAKDOWN);
@ -221,9 +205,6 @@ abstract class Timings{
self::$projectileMoveRayTrace = new TimingsHandler("Projectile Movement - Ray Tracing", self::$projectileMove, group: self::GROUP_BREAKDOWN);
self::$playerCheckNearEntities = new TimingsHandler("checkNearEntities", group: self::GROUP_BREAKDOWN);
self::$tickEntity = new TimingsHandler("Entity Tick", group: self::GROUP_BREAKDOWN);
self::$tickTileEntity = new TimingsHandler("Block Entity Tick", group: self::GROUP_BREAKDOWN);
self::$entityBaseTick = new TimingsHandler("Entity Base Tick", group: self::GROUP_BREAKDOWN);
self::$livingEntityBaseTick = new TimingsHandler("Entity Base Tick - Living", group: self::GROUP_BREAKDOWN);
self::$itemEntityBaseTick = new TimingsHandler("Entity Base Tick - ItemEntity", group: self::GROUP_BREAKDOWN);
@ -272,7 +253,7 @@ abstract class Timings{
}else{
$displayName = self::shortenCoreClassName($entity::class, "pocketmine\\entity\\");
}
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, self::$tickEntity, group: self::GROUP_BREAKDOWN);
self::$entityTypeTimingMap[$entity::class] = new TimingsHandler("Entity Tick - " . $displayName, group: self::GROUP_BREAKDOWN);
}
return self::$entityTypeTimingMap[$entity::class];
@ -282,7 +263,6 @@ abstract class Timings{
if(!isset(self::$tileEntityTypeTimingMap[$tile::class])){
self::$tileEntityTypeTimingMap[$tile::class] = new TimingsHandler(
"Block Entity Tick - " . self::shortenCoreClassName($tile::class, "pocketmine\\block\\tile\\"),
self::$tickTileEntity,
group: self::GROUP_BREAKDOWN
);
}

View File

@ -23,16 +23,14 @@ declare(strict_types=1);
namespace pocketmine\timings;
use pocketmine\entity\Living;
use pocketmine\Server;
use pocketmine\utils\Utils;
use function count;
use function hrtime;
use function implode;
use function spl_object_id;
class TimingsHandler{
private const FORMAT_VERSION = 1;
private const FORMAT_VERSION = 2; //peak timings fix
private static bool $enabled = false;
private static int $timingStart = 0;
@ -77,19 +75,6 @@ class TimingsHandler{
$result[] = "# Version " . Server::getInstance()->getVersion();
$result[] = "# " . Server::getInstance()->getName() . " " . Server::getInstance()->getPocketMineVersion();
$entities = 0;
$livingEntities = 0;
foreach(Server::getInstance()->getWorldManager()->getWorlds() as $world){
$entities += count($world->getEntities());
foreach($world->getEntities() as $e){
if($e instanceof Living){
++$livingEntities;
}
}
}
$result[] = "# Entities " . $entities;
$result[] = "# LivingEntities " . $livingEntities;
$result[] = "# FormatVersion " . self::FORMAT_VERSION;
$sampleTime = hrtime(true) - self::$timingStart;

View File

@ -66,9 +66,6 @@ final class TimingsRecord{
if($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->curCount = 0;
$record->ticksActive++;
@ -126,7 +123,7 @@ final class TimingsRecord{
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{
$this->start = $now;
@ -152,6 +149,9 @@ final class TimingsRecord{
++$this->curCount;
++$this->count;
$this->start = 0;
if($diff > $this->peakTime){
$this->peakTime = $diff;
}
}
public static function getCurrentRecord() : ?self{

View File

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

View File

@ -77,7 +77,6 @@ use pocketmine\promise\PromiseResolver;
use pocketmine\scheduler\AsyncPool;
use pocketmine\Server;
use pocketmine\ServerConfigGroup;
use pocketmine\timings\Timings;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Limits;
use pocketmine\utils\ReversePriorityQueue;
@ -104,6 +103,7 @@ use pocketmine\world\utils\SubChunkExplorer;
use function abs;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_merge;
use function array_sum;
@ -223,10 +223,25 @@ class World implements ChunkManager{
private array $tickingLoaderCounter = [];
/**
* @var TickingChunkEntry[] chunkHash => TickingChunkEntry
* @phpstan-var array<ChunkPosHash, TickingChunkEntry>
* @var ChunkTicker[][] chunkHash => [spl_object_id => ChunkTicker]
* @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]
@ -983,7 +998,6 @@ class World implements ChunkManager{
$this->timings->entityTick->startTiming();
//Update entities that need update
Timings::$tickEntity->startTiming();
foreach($this->updateEntities as $id => $entity){
if($entity->isClosed() || $entity->isFlaggedForDespawn() || !$entity->onUpdate($currentTick)){
unset($this->updateEntities[$id]);
@ -992,7 +1006,6 @@ class World implements ChunkManager{
$entity->close();
}
}
Timings::$tickEntity->stopTiming();
$this->timings->entityTick->stopTiming();
$this->timings->randomChunkUpdates->startTiming();
@ -1155,17 +1168,25 @@ class World implements ChunkManager{
$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
* registered to it.
*/
public function registerTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
$chunkPosHash = World::chunkHash($chunkX, $chunkZ);
$entry = $this->tickingChunks[$chunkPosHash] ?? null;
if($entry === null){
$entry = $this->tickingChunks[$chunkPosHash] = new TickingChunkEntry();
}
$entry->tickers[spl_object_id($ticker)] = $ticker;
$this->registeredTickingChunks[$chunkPosHash][spl_object_id($ticker)] = $ticker;
$this->recheckTickingChunks[$chunkPosHash] = $chunkPosHash;
}
/**
@ -1175,10 +1196,14 @@ class World implements ChunkManager{
public function unregisterTickingChunk(ChunkTicker $ticker, int $chunkX, int $chunkZ) : void{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
$tickerId = spl_object_id($ticker);
if(isset($this->tickingChunks[$chunkHash]->tickers[$tickerId])){
unset($this->tickingChunks[$chunkHash]->tickers[$tickerId]);
if(count($this->tickingChunks[$chunkHash]->tickers) === 0){
unset($this->tickingChunks[$chunkHash]);
if(isset($this->registeredTickingChunks[$chunkHash][$tickerId])){
unset($this->registeredTickingChunks[$chunkHash][$tickerId]);
if(count($this->registeredTickingChunks[$chunkHash]) === 0){
unset(
$this->registeredTickingChunks[$chunkHash],
$this->recheckTickingChunks[$chunkHash],
$this->validTickingChunks[$chunkHash]
);
}
}
}
@ -1222,37 +1247,37 @@ class World implements ChunkManager{
}
private function tickChunks() : void{
if($this->chunkTickRadius <= 0 || (count($this->tickingChunks) === 0 && count($this->tickingLoaders) === 0)){
if($this->chunkTickRadius <= 0 || (count($this->registeredTickingChunks) === 0 && count($this->tickingLoaders) === 0)){
return;
}
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
if(count($this->recheckTickingChunks) > 0 || count($this->tickingLoaders) > 0){
$this->timings->randomChunkUpdatesChunkSelection->startTiming();
/** @var bool[] $chunkTickList chunkhash => dummy */
$chunkTickList = [];
$chunkTickableCache = [];
$chunkTickableCache = [];
foreach($this->tickingChunks as $hash => $entry){
if(!$entry->ready){
foreach($this->recheckTickingChunks as $hash => $_){
World::getXZ($hash, $chunkX, $chunkZ);
if($this->isChunkTickable($chunkX, $chunkZ, $chunkTickableCache)){
$entry->ready = true;
}else{
//the chunk has been flagged as temporarily not tickable, so we don't want to tick it this time
continue;
$this->validTickingChunks[$hash] = $hash;
}
}
$chunkTickList[$hash] = true;
}
$this->recheckTickingChunks = [];
//TODO: REMOVE THIS
//backwards compatibility for TickingChunkLoader, although I'm not sure this is really necessary in practice
if(count($this->tickingLoaders) !== 0){
$this->selectTickableChunksLegacy($chunkTickList, $chunkTickableCache);
}
//TODO: REMOVE THIS - we need a local var to add extra chunks to if we have legacy ticking loaders
//this is copy-on-write, so it won't have any performance impact if there are no legacy ticking loaders
$chunkTickList = $this->validTickingChunks;
$this->timings->randomChunkUpdatesChunkSelection->stopTiming();
//TODO: REMOVE THIS
//backwards compatibility for TickingChunkLoader, although I'm not sure this is really necessary in practice
if(count($this->tickingLoaders) !== 0){
$this->selectTickableChunksLegacy($chunkTickList, $chunkTickableCache);
}
$this->timings->randomChunkUpdatesChunkSelection->stopTiming();
}else{
$chunkTickList = $this->validTickingChunks;
}
foreach($chunkTickList as $index => $_){
World::getXZ($index, $chunkX, $chunkZ);
@ -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
* while a chunk is being populated, light-populated, or unloaded.
* Each chunk will be rechecked every tick until it is ready to be ticked again.
* Marks the 3x3 square of chunks centered on the specified chunk for chunk ticking eligibility recheck.
*
* This should be used whenever the chunk's eligibility to be ticked is changed. This includes:
* - Loading/unloading the chunk (the chunk may be registered for ticking before it is loaded)
* - Locking/unlocking the chunk (e.g. world population)
* - Light populated state change (i.e. scheduled for light population, or light population completed)
* - Arbitrary chunk replacement (i.e. setChunk() or similar)
*/
private function markTickingChunkUnavailable(int $chunkX, int $chunkZ) : void{
private function markTickingChunkForRecheck(int $chunkX, int $chunkZ) : void{
for($cx = -1; $cx <= 1; ++$cx){
for($cz = -1; $cz <= 1; ++$cz){
$chunkHash = World::chunkHash($chunkX + $cx, $chunkZ + $cz);
if(isset($this->tickingChunks[$chunkHash])){
$this->tickingChunks[$chunkHash]->ready = false;
unset($this->validTickingChunks[$chunkHash]);
if(isset($this->registeredTickingChunks[$chunkHash])){
$this->recheckTickingChunks[$chunkHash] = $chunkHash;
}else{
unset($this->recheckTickingChunks[$chunkHash]);
}
}
}
@ -1323,7 +1355,7 @@ class World implements ChunkManager{
$lightPopulatedState = $this->chunks[$chunkHash]->isLightPopulated();
if($lightPopulatedState === false){
$this->chunks[$chunkHash]->setLightPopulated(null);
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
$this->workerPool->submitTask(new LightPopulationTask(
$this->chunks[$chunkHash],
@ -1347,6 +1379,7 @@ class World implements ChunkManager{
$chunk->getSubChunk($y)->setBlockSkyLightArray($lightArray);
}
$chunk->setLightPopulated(true);
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
}
));
}
@ -1403,10 +1436,15 @@ class World implements ChunkManager{
(new WorldSaveEvent($this))->call();
$timings = $this->timings->syncDataSave;
$timings->startTiming();
$this->provider->getWorldData()->setTime($this->time);
$this->saveChunks();
$this->provider->getWorldData()->save();
$timings->stopTiming();
return true;
}
@ -2391,7 +2429,7 @@ class World implements ChunkManager{
throw new \InvalidArgumentException("Chunk $chunkX $chunkZ is already locked");
}
$this->chunkLock[$chunkHash] = $lockId;
$this->markTickingChunkUnavailable($chunkX, $chunkZ);
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
}
/**
@ -2406,6 +2444,7 @@ class World implements ChunkManager{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
if(isset($this->chunkLock[$chunkHash]) && ($lockId === null || $this->chunkLock[$chunkHash] === $lockId)){
unset($this->chunkLock[$chunkHash]);
$this->markTickingChunkForRecheck($chunkX, $chunkZ);
return true;
}
return false;
@ -2457,7 +2496,7 @@ class World implements ChunkManager{
unset($this->blockCache[$chunkHash]);
unset($this->changedBlocks[$chunkHash]);
$chunk->setTerrainDirty();
$this->markTickingChunkUnavailable($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking
$this->markTickingChunkForRecheck($chunkX, $chunkZ); //this replacement chunk may not meet the conditions for ticking
if(!$this->isChunkInUse($chunkX, $chunkZ)){
$this->unloadChunkRequest($chunkX, $chunkZ);
@ -2739,6 +2778,7 @@ class World implements ChunkManager{
foreach($this->getChunkListeners($x, $z) as $listener){
$listener->onChunkLoaded($x, $z, $this->chunks[$chunkHash]);
}
$this->markTickingChunkForRecheck($x, $z); //tickers may have been registered before the chunk was loaded
$this->timings->syncChunkLoad->stopTiming();
@ -2900,8 +2940,8 @@ class World implements ChunkManager{
unset($this->chunks[$chunkHash]);
unset($this->blockCache[$chunkHash]);
unset($this->changedBlocks[$chunkHash]);
unset($this->tickingChunks[$chunkHash]);
$this->markTickingChunkUnavailable($x, $z);
unset($this->registeredTickingChunks[$chunkHash]);
$this->markTickingChunkForRecheck($x, $z);
if(array_key_exists($chunkHash, $this->chunkPopulationRequestMap)){
$this->logger->debug("Rejecting population promise for chunk $x $z");
@ -3210,7 +3250,8 @@ class World implements ChunkManager{
private function internalOrderChunkPopulation(int $chunkX, int $chunkZ, ?ChunkLoader $associatedChunkLoader, ?PromiseResolver $resolver) : Promise{
$chunkHash = World::chunkHash($chunkX, $chunkZ);
Timings::$population->startTiming();
$timings = $this->timings->chunkPopulationOrder;
$timings->startTiming();
try{
for($xx = -1; $xx <= 1; ++$xx){
@ -3267,7 +3308,7 @@ class World implements ChunkManager{
return $resolver->getPromise();
}finally{
Timings::$population->stopTiming();
$timings->stopTiming();
}
}
@ -3276,7 +3317,8 @@ class World implements ChunkManager{
* @phpstan-param array<int, Chunk> $adjacentChunks
*/
private function generateChunkCallback(ChunkLockId $chunkLockId, int $x, int $z, Chunk $chunk, array $adjacentChunks, ChunkLoader $temporaryChunkLoader) : void{
Timings::$generationCallback->startTiming();
$timings = $this->timings->chunkPopulationCompletion;
$timings->startTiming();
$dirtyChunks = 0;
for($xx = -1; $xx <= 1; ++$xx){
@ -3345,7 +3387,7 @@ class World implements ChunkManager{
$this->drainPopulationRequestQueue();
}
Timings::$generationCallback->stopTiming();
$timings->stopTiming();
}
public function doChunkGarbageCollection() : void{

View File

@ -30,7 +30,6 @@ use pocketmine\event\world\WorldUnloadEvent;
use pocketmine\lang\KnownTranslationFactory;
use pocketmine\player\ChunkSelector;
use pocketmine\Server;
use pocketmine\timings\Timings;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\io\exception\CorruptedWorldException;
use pocketmine\world\format\io\exception\UnsupportedWorldFormatException;
@ -391,7 +390,6 @@ class WorldManager{
}
private function doAutoSave() : void{
Timings::$worldSave->startTiming();
foreach($this->worlds as $world){
foreach($world->getPlayers() as $player){
if($player->spawned){
@ -400,6 +398,5 @@ class WorldManager{
}
$world->save(false);
}
Timings::$worldSave->stopTiming();
}
}

View File

@ -38,6 +38,7 @@ class WorldTimings{
public TimingsHandler $randomChunkUpdatesChunkSelection;
public TimingsHandler $doChunkGC;
public TimingsHandler $entityTick;
public TimingsHandler $tileTick;
public TimingsHandler $doTick;
public TimingsHandler $syncChunkSend;
@ -48,33 +49,54 @@ class WorldTimings{
public TimingsHandler $syncChunkLoadFixInvalidBlocks;
public TimingsHandler $syncChunkLoadEntities;
public TimingsHandler $syncChunkLoadTileEntities;
public TimingsHandler $syncDataSave;
public TimingsHandler $syncChunkSave;
public TimingsHandler $chunkPopulationOrder;
public TimingsHandler $chunkPopulationCompletion;
/**
* @var TimingsHandler[]
* @phpstan-var array<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){
$name = $world->getFolderName() . " - ";
$name = $world->getFolderName();
$this->setBlock = new TimingsHandler($name . "setBlock", group: Timings::GROUP_BREAKDOWN);
$this->doBlockLightUpdates = new TimingsHandler($name . "Block Light Updates", group: Timings::GROUP_BREAKDOWN);
$this->doBlockSkyLightUpdates = new TimingsHandler($name . "Sky Light Updates", group: Timings::GROUP_BREAKDOWN);
$this->setBlock = self::newTimer($name, "Set Blocks");
$this->doBlockLightUpdates = self::newTimer($name, "Block Light Updates");
$this->doBlockSkyLightUpdates = self::newTimer($name, "Sky Light Updates");
$this->doChunkUnload = new TimingsHandler($name . "Unload Chunks", group: Timings::GROUP_BREAKDOWN);
$this->scheduledBlockUpdates = new TimingsHandler($name . "Scheduled Block Updates", group: Timings::GROUP_BREAKDOWN);
$this->randomChunkUpdates = new TimingsHandler($name . "Random Chunk Updates", group: Timings::GROUP_BREAKDOWN);
$this->randomChunkUpdatesChunkSelection = new TimingsHandler($name . "Random Chunk Updates - Chunk Selection", group: Timings::GROUP_BREAKDOWN);
$this->doChunkGC = new TimingsHandler($name . "Garbage Collection", group: Timings::GROUP_BREAKDOWN);
$this->entityTick = new TimingsHandler($name . "Tick Entities", group: Timings::GROUP_BREAKDOWN);
$this->doChunkUnload = self::newTimer($name, "Unload Chunks");
$this->scheduledBlockUpdates = self::newTimer($name, "Scheduled Block Updates");
$this->randomChunkUpdates = self::newTimer($name, "Random Chunk Updates");
$this->randomChunkUpdatesChunkSelection = self::newTimer($name, "Random Chunk Updates - Chunk Selection");
$this->doChunkGC = self::newTimer($name, "Garbage Collection");
$this->entityTick = self::newTimer($name, "Entity Tick");
$this->tileTick = self::newTimer($name, "Block Entity Tick");
$this->doTick = self::newTimer($name, "World Tick");
Timings::init(); //make sure the timers we want are available
$this->syncChunkSend = new TimingsHandler($name . "Player Send Chunks", Timings::$playerChunkSend, group: Timings::GROUP_BREAKDOWN);
$this->syncChunkSendPrepare = new TimingsHandler($name . "Player Send Chunk Prepare", Timings::$playerChunkSend, group: Timings::GROUP_BREAKDOWN);
$this->syncChunkSend = self::newTimer($name, "Player Send Chunks");
$this->syncChunkSendPrepare = self::newTimer($name, "Player Send Chunk Prepare");
$this->syncChunkLoad = new TimingsHandler($name . "Chunk Load", Timings::$worldLoad, group: Timings::GROUP_BREAKDOWN);
$this->syncChunkLoadData = new TimingsHandler($name . "Chunk Load - Data", group: Timings::GROUP_BREAKDOWN);
$this->syncChunkLoadFixInvalidBlocks = new TimingsHandler($name . "Chunk Load - Fix Invalid Blocks", group: Timings::GROUP_BREAKDOWN);
$this->syncChunkLoadEntities = new TimingsHandler($name . "Chunk Load - Entities", group: Timings::GROUP_BREAKDOWN);
$this->syncChunkLoadTileEntities = new TimingsHandler($name . "Chunk Load - TileEntities", group: Timings::GROUP_BREAKDOWN);
$this->syncChunkSave = new TimingsHandler($name . "Chunk Save", Timings::$worldSave, group: Timings::GROUP_BREAKDOWN);
$this->syncChunkLoad = self::newTimer($name, "Chunk Load");
$this->syncChunkLoadData = self::newTimer($name, "Chunk Load - Data");
$this->syncChunkLoadFixInvalidBlocks = self::newTimer($name, "Chunk Load - Fix Invalid Blocks");
$this->syncChunkLoadEntities = self::newTimer($name, "Chunk Load - Entities");
$this->syncChunkLoadTileEntities = self::newTimer($name, "Chunk Load - Block Entities");
$this->doTick = new TimingsHandler($name . "World Tick");
$this->syncDataSave = self::newTimer($name, "Data Save");
$this->syncChunkSave = self::newTimer($name, "Chunk Save");
$this->chunkPopulationOrder = self::newTimer($name, "Chunk Population - Order");
$this->chunkPopulationCompletion = self::newTimer($name, "Chunk Population - Completion");
}
}