Merge 'minor-next' into 'major-next'

Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13191240897
This commit is contained in:
pmmp-admin-bot[bot] 2025-02-07 01:23:45 +00:00
commit b0ac8863c4
2 changed files with 155 additions and 22 deletions

View File

@ -28,6 +28,7 @@ use pocketmine\block\BlockIdentifier as BID;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use pocketmine\world\light\LightUpdate;
use function count;
use function min;
/**
@ -40,6 +41,11 @@ use function min;
class RuntimeBlockStateRegistry{
use SingletonTrait;
public const COLLISION_CUSTOM = 0;
public const COLLISION_CUBE = 1;
public const COLLISION_NONE = 2;
public const COLLISION_MAY_OVERFLOW = 3;
/**
* @var Block[]
* @phpstan-var array<int, Block>
@ -74,6 +80,13 @@ class RuntimeBlockStateRegistry{
*/
public array $blastResistance = [];
/**
* Map of state ID -> useful AABB info to avoid unnecessary block allocations
* @var int[]
* @phpstan-var array<int, int>
*/
public array $collisionInfo = [];
public function __construct(){
foreach(VanillaBlocks::getAll() as $block){
$this->register($block);
@ -100,6 +113,70 @@ class RuntimeBlockStateRegistry{
}
}
/**
* Checks if the given class method overrides a method in Block.
* Used to determine if a block might need to disable fast path optimizations.
*
* @phpstan-param anyClosure $closure
*/
private static function overridesBlockMethod(\Closure $closure) : bool{
$declarer = (new \ReflectionFunction($closure))->getClosureScopeClass();
return $declarer !== null && $declarer->getName() !== Block::class;
}
/**
* A big ugly hack to set up fast paths for handling collisions on blocks with common shapes.
* The information returned here is stored in RuntimeBlockStateRegistry->collisionInfo, and is used during entity
* collision box calculations to avoid complex logic and unnecessary block object allocations.
* This hack allows significant performance improvements.
*
* TODO: We'll want to redesign block collision box handling and block shapes in the future, but that's a job for a
* major version. For now, this hack nets major performance wins.
*/
private static function calculateCollisionInfo(Block $block) : int{
if(
self::overridesBlockMethod($block->getModelPositionOffset(...)) ||
self::overridesBlockMethod($block->readStateFromWorld(...))
){
//getModelPositionOffset() might cause AABBs to shift outside the cell
//readStateFromWorld() might cause overflow in ways we can't predict just by looking at known states
//TODO: excluding overriders of readStateFromWorld() also excludes blocks with tiles that don't do anything
//weird with their AABBs, but for now this is the best we can do.
return self::COLLISION_MAY_OVERFLOW;
}
//TODO: this could blow up if any recalculateCollisionBoxes() uses the world
//it shouldn't, but that doesn't mean that custom blocks won't...
$boxes = $block->getCollisionBoxes();
if(count($boxes) === 0){
return self::COLLISION_NONE;
}
if(
count($boxes) === 1 &&
$boxes[0]->minX === 0.0 &&
$boxes[0]->minY === 0.0 &&
$boxes[0]->minZ === 0.0 &&
$boxes[0]->maxX === 1.0 &&
$boxes[0]->maxY === 1.0 &&
$boxes[0]->maxZ === 1.0
){
return self::COLLISION_CUBE;
}
foreach($boxes as $box){
if(
$box->minX < 0 || $box->maxX > 1 ||
$box->minY < 0 || $box->maxY > 1 ||
$box->minZ < 0 || $box->maxZ > 1
){
return self::COLLISION_MAY_OVERFLOW;
}
}
return self::COLLISION_CUSTOM;
}
private function fillStaticArrays(int $index, Block $block) : void{
$fullId = $block->getStateId();
if($index !== $fullId){
@ -112,6 +189,8 @@ class RuntimeBlockStateRegistry{
if($block->blocksDirectSkyLight()){
$this->blocksDirectSkyLight[$index] = true;
}
$this->collisionInfo[$index] = self::calculateCollisionInfo($block);
}
}

View File

@ -375,6 +375,8 @@ class World implements ChunkManager{
private \Logger $logger;
private RuntimeBlockStateRegistry $blockStateRegistry;
/**
* @phpstan-return ChunkPosHash
*/
@ -488,6 +490,7 @@ class World implements ChunkManager{
$this->displayName = $this->provider->getWorldData()->getName();
$this->logger = new \PrefixedLogger($server->getLogger(), "World: $this->displayName");
$this->blockStateRegistry = RuntimeBlockStateRegistry::getInstance();
$this->minY = $this->provider->getWorldMinY();
$this->maxY = $this->provider->getWorldMaxY();
@ -559,7 +562,7 @@ class World implements ChunkManager{
}catch(BlockStateDeserializeException){
continue;
}
$block = RuntimeBlockStateRegistry::getInstance()->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData));
$block = $this->blockStateRegistry->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData));
}else{
//TODO: we probably ought to log an error here
continue;
@ -570,7 +573,7 @@ class World implements ChunkManager{
}
}
foreach(RuntimeBlockStateRegistry::getInstance()->getAllKnownStates() as $state){
foreach($this->blockStateRegistry->getAllKnownStates() as $state){
$dontTickName = $dontTickBlocks[$state->getTypeId()] ?? null;
if($dontTickName === null && $state->ticksRandomly()){
$this->randomTickBlocks[$state->getStateId()] = true;
@ -1394,7 +1397,7 @@ class World implements ChunkManager{
$entity->onRandomUpdate();
}
$blockFactory = RuntimeBlockStateRegistry::getInstance();
$blockFactory = $this->blockStateRegistry;
foreach($chunk->getSubChunks() as $Y => $subChunk){
if(!$subChunk->isEmptyFast()){
$k = 0;
@ -1528,13 +1531,18 @@ class World implements ChunkManager{
$collides = [];
$collisionInfo = $this->blockStateRegistry->collisionInfo;
if($targetFirst){
for($z = $minZ; $z <= $maxZ; ++$z){
for($x = $minX; $x <= $maxX; ++$x){
for($y = $minY; $y <= $maxY; ++$y){
$block = $this->getBlockAt($x, $y, $z);
if($block->collidesWithBB($bb)){
return [$block];
$stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
if(match($stateCollisionInfo){
RuntimeBlockStateRegistry::COLLISION_CUBE => true,
RuntimeBlockStateRegistry::COLLISION_NONE => false,
default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb)
}){
return [$this->getBlockAt($x, $y, $z)];
}
}
}
@ -1543,9 +1551,13 @@ class World implements ChunkManager{
for($z = $minZ; $z <= $maxZ; ++$z){
for($x = $minX; $x <= $maxX; ++$x){
for($y = $minY; $y <= $maxY; ++$y){
$block = $this->getBlockAt($x, $y, $z);
if($block->collidesWithBB($bb)){
$collides[] = $block;
$stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
if(match($stateCollisionInfo){
RuntimeBlockStateRegistry::COLLISION_CUBE => true,
RuntimeBlockStateRegistry::COLLISION_NONE => false,
default => $this->getBlockAt($x, $y, $z)->collidesWithBB($bb)
}){
$collides[] = $this->getBlockAt($x, $y, $z);
}
}
}
@ -1555,24 +1567,64 @@ class World implements ChunkManager{
return $collides;
}
/**
* @param int[] $collisionInfo
* @phpstan-param array<int, int> $collisionInfo
*/
private function getBlockCollisionInfo(int $x, int $y, int $z, array $collisionInfo) : int{
if(!$this->isInWorld($x, $y, $z)){
return RuntimeBlockStateRegistry::COLLISION_NONE;
}
$chunk = $this->getChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
if($chunk === null){
return RuntimeBlockStateRegistry::COLLISION_NONE;
}
$stateId = $chunk
->getSubChunk($y >> SubChunk::COORD_BIT_SIZE)
->getBlockStateId(
$x & SubChunk::COORD_MASK,
$y & SubChunk::COORD_MASK,
$z & SubChunk::COORD_MASK
);
return $collisionInfo[$stateId];
}
/**
* Returns a list of all block AABBs which overlap the full block area at the given coordinates.
* This checks a padding of 1 block around the coordinates to account for oversized AABBs of blocks like fences.
* Larger AABBs (>= 2 blocks on any axis) are not accounted for.
*
* @param int[] $collisionInfo
* @phpstan-param array<int, int> $collisionInfo
*
* @return AxisAlignedBB[]
* @phpstan-return list<AxisAlignedBB>
*/
private function getBlockCollisionBoxesForCell(int $x, int $y, int $z) : array{
$block = $this->getBlockAt($x, $y, $z);
$boxes = $block->getCollisionBoxes();
private function getBlockCollisionBoxesForCell(int $x, int $y, int $z, array $collisionInfo) : array{
$stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
$boxes = match($stateCollisionInfo){
RuntimeBlockStateRegistry::COLLISION_NONE => [],
RuntimeBlockStateRegistry::COLLISION_CUBE => [AxisAlignedBB::one()->offset($x, $y, $z)],
default => $this->getBlockAt($x, $y, $z)->getCollisionBoxes()
};
$cellBB = AxisAlignedBB::one()->offset($x, $y, $z);
foreach(Facing::OFFSET as [$dx, $dy, $dz]){
$extraBoxes = $this->getBlockAt($x + $dx, $y + $dy, $z + $dz)->getCollisionBoxes();
foreach($extraBoxes as $extraBox){
if($extraBox->intersectsWith($cellBB)){
$boxes[] = $extraBox;
//overlapping AABBs can't make any difference if this is a cube, so we can save some CPU cycles in this common case
if($stateCollisionInfo !== RuntimeBlockStateRegistry::COLLISION_CUBE){
$cellBB = null;
foreach(Facing::OFFSET as [$dx, $dy, $dz]){
$offsetX = $x + $dx;
$offsetY = $y + $dy;
$offsetZ = $z + $dz;
$stateCollisionInfo = $this->getBlockCollisionInfo($offsetX, $offsetY, $offsetZ, $collisionInfo);
if($stateCollisionInfo === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW){
//avoid allocating this unless it's needed
$cellBB ??= AxisAlignedBB::one()->offset($x, $y, $z);
$extraBoxes = $this->getBlockAt($offsetX, $offsetY, $offsetZ)->getCollisionBoxes();
foreach($extraBoxes as $extraBox){
if($extraBox->intersectsWith($cellBB)){
$boxes[] = $extraBox;
}
}
}
}
}
@ -1594,13 +1646,15 @@ class World implements ChunkManager{
$collides = [];
$collisionInfo = $this->blockStateRegistry->collisionInfo;
for($z = $minZ; $z <= $maxZ; ++$z){
for($x = $minX; $x <= $maxX; ++$x){
$chunkPosHash = World::chunkHash($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
for($y = $minY; $y <= $maxY; ++$y){
$relativeBlockHash = World::chunkBlockHash($x, $y, $z);
$boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z);
$boxes = $this->blockCollisionBoxCache[$chunkPosHash][$relativeBlockHash] ??= $this->getBlockCollisionBoxesForCell($x, $y, $z, $collisionInfo);
foreach($boxes as $blockBB){
if($blockBB->intersectsWith($bb)){
@ -1776,7 +1830,7 @@ class World implements ChunkManager{
return;
}
$blockFactory = RuntimeBlockStateRegistry::getInstance();
$blockFactory = $this->blockStateRegistry;
$this->timings->doBlockSkyLightUpdates->startTiming();
if($this->skyLightUpdate === null){
$this->skyLightUpdate = new SkyLightUpdate(new SubChunkExplorer($this), $blockFactory->lightFilter, $blockFactory->blocksDirectSkyLight);
@ -1895,7 +1949,7 @@ class World implements ChunkManager{
$chunk = $this->chunks[$chunkHash] ?? null;
if($chunk !== null){
$block = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK));
$block = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK));
}else{
$addToCache = false;
$block = VanillaBlocks::AIR();
@ -2554,7 +2608,7 @@ class World implements ChunkManager{
$localY = $tilePosition->getFloorY();
$localZ = $tilePosition->getFloorZ() & Chunk::COORD_MASK;
$newBlock = RuntimeBlockStateRegistry::getInstance()->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ));
$newBlock = $this->blockStateRegistry->fromStateId($chunk->getBlockStateId($localX, $localY, $localZ));
$expectedTileClass = $newBlock->getIdInfo()->getTileClass();
if(
$expectedTileClass === null || //new block doesn't expect a tile