Skip to content

Commit f416cb8

Browse files
authored
Merge pull request #6658 from pmmp/minor-next-release
2 parents 1c6a4bd + f123df5 commit f416cb8

21 files changed

+878
-372
lines changed

changelogs/5.26.md

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# 5.26.0
2+
Released 22nd March 2025.
3+
4+
This is a minor feature release focused on performance improvements.
5+
6+
**Plugin compatibility:** Plugins for previous 5.x versions will run unchanged on this release, unless they use internal APIs, reflection, or packages like the `pocketmine\network\mcpe` or `pocketmine\data` namespace.
7+
Do not update plugin minimum API versions unless you need new features added in this release.
8+
9+
**WARNING: If your plugin uses the `pocketmine\network\mcpe` namespace, you're not shielded by API change constraints.**
10+
Consider using the `mcpe-protocol` directive in `plugin.yml` as a constraint if you're using packets directly.
11+
12+
## Performance
13+
- Significantly improved performance of entity movement. Load testing with item entities showed a 3x increase in the number of entities supported without lag.
14+
- Significantly improved performance of on-ground checks for player movement. This still needs further work, but optimisations implemented in this version should improve performance substantially.
15+
- Updated `pocketmine/nbt` dependency with performance improvements to `TAG_Compound` and `TAG_List` comparison. This should improve performance of inventory-related actions.
16+
- `InventoryTransaction` now avoids useless item clones when processing transactions, which should improve performance of inventory-related actions.
17+
18+
## Dependencies
19+
- `pocketmine/bedrock-protocol` has been updated to `36.2.0`, which adds new functions to access some packet fields.
20+
- `pocketmine/nbt` has been updated to `1.1.0`, which improves performance when comparing NBT object trees.
21+
22+
## Gameplay
23+
- Block breaking animation speed now takes into account the following: jumping, being in water, haste, mining fatigue
24+
25+
## Tools
26+
- `blockstate-upgrade-schema-utils.php` now has a new `dump-table` command, which turns a `.bin` palette table file into human-readable text for debugging.
27+
28+
## API
29+
### `pocketmine\block`
30+
- The following methods have been added:
31+
- `public RuntimeBlockStateRegistry->hasStateId(int $stateId) : bool` - checks whether the given state ID is registered
32+
33+
### `pocketmine\crafting`
34+
- The following methods have been deprecated:
35+
- `CraftingManager::sort()` - this was implicitly internal anyway
36+
37+
### `pocketmine\utils`
38+
- The following constants have been added:
39+
- `TextFormat::MATERIAL_RESIN`
40+
- The following static properties have been added:
41+
- `Terminal::$COLOR_MATERIAL_RESIN`
42+
43+
### `pocketmine\data\bedrock\block`
44+
- `BlockStateToObjectDeserializer` now permits overriding **deserializers** for Bedrock IDs. This may be useful to implement custom state handling, or to implement missing block variants (such as snow cauldron).
45+
- This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**.
46+
- Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts.
47+
- If you want to make a custom version of a vanilla block, create a custom type ID for it, exactly as you would for a regular custom block.
48+
- The following methods have been added:
49+
- `public BlockStateToObjectDeserializer->getDeserializerForId(string $id) : ?(\Closure(BlockStateReader) : Block)`
50+
51+
### `pocketmine\data\bedrock\item`
52+
- `ItemDeserializer` now permits overriding **deserializers** for Bedrock IDs. As above, this may be useful to implement custom data handling, or to implement missing variants of existing items.
53+
- This was originally prohibited since 5.0.0. However, there is no technical reason to disallow overriding **deserializers**.
54+
- Overriding **serializers** is still **not permitted**. Reusing type IDs doesn't make any sense and would break internal design contracts.
55+
- As above, if you want to make a custom version of a vanilla item, create a custom type ID for it, exactly as you would for a regular custom item.
56+
- The following methods have been added:
57+
- `public ItemDeserializer->getDeserializerForId(string $id) : ?(\Closure(SavedItemData) : Item)`
58+
59+
## Internals
60+
- `new $class` is now banned on new internals code by a PHPStan rule. Closures or factory objects should be used instead for greater flexibility and better static analysis.
61+
- `CraftingManager` now uses a more stable hash function for recipe output filtering.
62+
- `ChunkCache` now accepts `int $dimensionId` in the constructor. This may be useful for plugins which implement the nether.
63+
- `RuntimeBlockStateRegistry` now precomputes basic collision info about known states for fast paths.
64+
- This permits specialization for common shapes like cubes and collisionless blocks, which allows skipping complex logic in entity movement calculation. This vastly improves performance.
65+
- Any block whose class overrides `readStateFromWorld()` or `getModelPositionOffset()` will *not* be optimised.
66+
- `Block->recalculateCollisionBoxes()` now has a hard requirement not to depend on anything other than available properties. It must not use `World` or its position.
67+
- This change was problematic for `ChorusPlant`, which used nearby blocks to calculate its collision boxes.
68+
- Blocks which need nearby blocks should override `readStateFromWorld()` and set dynamic state properties, similar to fences.
69+
- This design flaw will be corrected with a major change to `Block` internals currently in planning for a future major version.
70+
- `Block->getCollisionBoxes()` may not be called at all during gameplay for blocks with shapes determined to be simple, like cubes and collisionless blocks.
71+
- `BlockStateToObjectDeserializer` now checks if the returned blockstate is registered in `RuntimeBlockStateRegistry` to promote earlier error detection (instead of crashing in random code paths).

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@
3636
"pocketmine/bedrock-block-upgrade-schema": "~5.1.0+bedrock-1.21.60",
3737
"pocketmine/bedrock-data": "~4.0.0+bedrock-1.21.60",
3838
"pocketmine/bedrock-item-upgrade-schema": "~1.14.0+bedrock-1.21.50",
39-
"pocketmine/bedrock-protocol": "~36.0.0+bedrock-1.21.60",
39+
"pocketmine/bedrock-protocol": "~36.2.0+bedrock-1.21.60",
4040
"pocketmine/binaryutils": "^0.2.1",
4141
"pocketmine/callback-validator": "^1.0.2",
4242
"pocketmine/color": "^0.3.0",
4343
"pocketmine/errorhandler": "^0.7.0",
4444
"pocketmine/locale-data": "~2.24.0",
4545
"pocketmine/log": "^0.4.0",
4646
"pocketmine/math": "~1.0.0",
47-
"pocketmine/nbt": "~1.0.0",
47+
"pocketmine/nbt": "~1.1.0",
4848
"pocketmine/raklib": "~1.1.0",
4949
"pocketmine/raklib-ipc": "~1.0.0",
5050
"pocketmine/snooze": "^0.5.0",

composer.lock

+13-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

phpstan.neon.dist

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ includes:
1111

1212
rules:
1313
- pocketmine\phpstan\rules\DeprecatedLegacyEnumAccessRule
14+
- pocketmine\phpstan\rules\DisallowDynamicNewRule
1415
- pocketmine\phpstan\rules\DisallowEnumComparisonRule
1516
- pocketmine\phpstan\rules\DisallowForeachByReferenceRule
1617
- pocketmine\phpstan\rules\ExplodeLimitRule

src/VersionInfo.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131

3232
final class VersionInfo{
3333
public const NAME = "PocketMine-MP";
34-
public const BASE_VERSION = "5.25.3";
35-
public const IS_DEVELOPMENT_BUILD = true;
34+
public const BASE_VERSION = "5.26.0";
35+
public const IS_DEVELOPMENT_BUILD = false;
3636
public const BUILD_CHANNEL = "stable";
3737

3838
/**

src/block/ChorusPlant.php

+28-3
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,43 @@
3434
final class ChorusPlant extends Flowable{
3535
use StaticSupportTrait;
3636

37+
/**
38+
* @var true[]
39+
* @phpstan-var array<int, true>
40+
*/
41+
protected array $connections = [];
42+
3743
protected function recalculateCollisionBoxes() : array{
3844
$bb = AxisAlignedBB::one();
39-
foreach($this->getAllSides() as $facing => $block){
40-
$id = $block->getTypeId();
41-
if($id !== BlockTypeIds::END_STONE && $id !== BlockTypeIds::CHORUS_FLOWER && !$block->hasSameTypeId($this)){
45+
foreach(Facing::ALL as $facing){
46+
if(!isset($this->connections[$facing])){
4247
$bb->trim($facing, 2 / 16);
4348
}
4449
}
4550

4651
return [$bb];
4752
}
4853

54+
public function readStateFromWorld() : Block{
55+
parent::readStateFromWorld();
56+
57+
$this->collisionBoxes = null;
58+
59+
foreach(Facing::ALL as $facing){
60+
$block = $this->getSide($facing);
61+
if(match($block->getTypeId()){
62+
BlockTypeIds::END_STONE, BlockTypeIds::CHORUS_FLOWER, $this->getTypeId() => true,
63+
default => false
64+
}){
65+
$this->connections[$facing] = true;
66+
}else{
67+
unset($this->connections[$facing]);
68+
}
69+
}
70+
71+
return $this;
72+
}
73+
4974
private function canBeSupportedBy(Block $block) : bool{
5075
return $block->hasSameTypeId($this) || $block->getTypeId() === BlockTypeIds::END_STONE;
5176
}

src/block/RuntimeBlockStateRegistry.php

+83
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use pocketmine\utils\AssumptionFailedError;
2929
use pocketmine\utils\SingletonTrait;
3030
use pocketmine\world\light\LightUpdate;
31+
use function count;
3132
use function min;
3233

3334
/**
@@ -40,6 +41,11 @@
4041
class RuntimeBlockStateRegistry{
4142
use SingletonTrait;
4243

44+
public const COLLISION_CUSTOM = 0;
45+
public const COLLISION_CUBE = 1;
46+
public const COLLISION_NONE = 2;
47+
public const COLLISION_MAY_OVERFLOW = 3;
48+
4349
/**
4450
* @var Block[]
4551
* @phpstan-var array<int, Block>
@@ -74,6 +80,13 @@ class RuntimeBlockStateRegistry{
7480
*/
7581
public array $blastResistance = [];
7682

83+
/**
84+
* Map of state ID -> useful AABB info to avoid unnecessary block allocations
85+
* @var int[]
86+
* @phpstan-var array<int, int>
87+
*/
88+
public array $collisionInfo = [];
89+
7790
public function __construct(){
7891
foreach(VanillaBlocks::getAll() as $block){
7992
$this->register($block);
@@ -100,6 +113,70 @@ public function register(Block $block) : void{
100113
}
101114
}
102115

116+
/**
117+
* Checks if the given class method overrides a method in Block.
118+
* Used to determine if a block might need to disable fast path optimizations.
119+
*
120+
* @phpstan-param anyClosure $closure
121+
*/
122+
private static function overridesBlockMethod(\Closure $closure) : bool{
123+
$declarer = (new \ReflectionFunction($closure))->getClosureScopeClass();
124+
return $declarer !== null && $declarer->getName() !== Block::class;
125+
}
126+
127+
/**
128+
* A big ugly hack to set up fast paths for handling collisions on blocks with common shapes.
129+
* The information returned here is stored in RuntimeBlockStateRegistry->collisionInfo, and is used during entity
130+
* collision box calculations to avoid complex logic and unnecessary block object allocations.
131+
* This hack allows significant performance improvements.
132+
*
133+
* TODO: We'll want to redesign block collision box handling and block shapes in the future, but that's a job for a
134+
* major version. For now, this hack nets major performance wins.
135+
*/
136+
private static function calculateCollisionInfo(Block $block) : int{
137+
if(
138+
self::overridesBlockMethod($block->getModelPositionOffset(...)) ||
139+
self::overridesBlockMethod($block->readStateFromWorld(...))
140+
){
141+
//getModelPositionOffset() might cause AABBs to shift outside the cell
142+
//readStateFromWorld() might cause overflow in ways we can't predict just by looking at known states
143+
//TODO: excluding overriders of readStateFromWorld() also excludes blocks with tiles that don't do anything
144+
//weird with their AABBs, but for now this is the best we can do.
145+
return self::COLLISION_MAY_OVERFLOW;
146+
}
147+
148+
//TODO: this could blow up if any recalculateCollisionBoxes() uses the world
149+
//it shouldn't, but that doesn't mean that custom blocks won't...
150+
$boxes = $block->getCollisionBoxes();
151+
if(count($boxes) === 0){
152+
return self::COLLISION_NONE;
153+
}
154+
155+
if(
156+
count($boxes) === 1 &&
157+
$boxes[0]->minX === 0.0 &&
158+
$boxes[0]->minY === 0.0 &&
159+
$boxes[0]->minZ === 0.0 &&
160+
$boxes[0]->maxX === 1.0 &&
161+
$boxes[0]->maxY === 1.0 &&
162+
$boxes[0]->maxZ === 1.0
163+
){
164+
return self::COLLISION_CUBE;
165+
}
166+
167+
foreach($boxes as $box){
168+
if(
169+
$box->minX < 0 || $box->maxX > 1 ||
170+
$box->minY < 0 || $box->maxY > 1 ||
171+
$box->minZ < 0 || $box->maxZ > 1
172+
){
173+
return self::COLLISION_MAY_OVERFLOW;
174+
}
175+
}
176+
177+
return self::COLLISION_CUSTOM;
178+
}
179+
103180
private function fillStaticArrays(int $index, Block $block) : void{
104181
$fullId = $block->getStateId();
105182
if($index !== $fullId){
@@ -112,6 +189,8 @@ private function fillStaticArrays(int $index, Block $block) : void{
112189
if($block->blocksDirectSkyLight()){
113190
$this->blocksDirectSkyLight[$index] = true;
114191
}
192+
193+
$this->collisionInfo[$index] = self::calculateCollisionInfo($block);
115194
}
116195
}
117196

@@ -130,6 +209,10 @@ public function fromStateId(int $stateId) : Block{
130209
return $block;
131210
}
132211

212+
public function hasStateId(int $stateId) : bool{
213+
return isset($this->fullList[$stateId]);
214+
}
215+
133216
/**
134217
* @return Block[]
135218
* @phpstan-return array<int, Block>

0 commit comments

Comments
 (0)