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\AssumptionFailedError;
use pocketmine\utils\SingletonTrait; use pocketmine\utils\SingletonTrait;
use pocketmine\world\light\LightUpdate; use pocketmine\world\light\LightUpdate;
use function count;
use function min; use function min;
/** /**
@ -40,6 +41,11 @@ use function min;
class RuntimeBlockStateRegistry{ class RuntimeBlockStateRegistry{
use SingletonTrait; 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[] * @var Block[]
* @phpstan-var array<int, Block> * @phpstan-var array<int, Block>
@ -74,6 +80,13 @@ class RuntimeBlockStateRegistry{
*/ */
public array $blastResistance = []; 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(){ public function __construct(){
foreach(VanillaBlocks::getAll() as $block){ foreach(VanillaBlocks::getAll() as $block){
$this->register($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{ private function fillStaticArrays(int $index, Block $block) : void{
$fullId = $block->getStateId(); $fullId = $block->getStateId();
if($index !== $fullId){ if($index !== $fullId){
@ -112,6 +189,8 @@ class RuntimeBlockStateRegistry{
if($block->blocksDirectSkyLight()){ if($block->blocksDirectSkyLight()){
$this->blocksDirectSkyLight[$index] = true; $this->blocksDirectSkyLight[$index] = true;
} }
$this->collisionInfo[$index] = self::calculateCollisionInfo($block);
} }
} }

View File

@ -375,6 +375,8 @@ class World implements ChunkManager{
private \Logger $logger; private \Logger $logger;
private RuntimeBlockStateRegistry $blockStateRegistry;
/** /**
* @phpstan-return ChunkPosHash * @phpstan-return ChunkPosHash
*/ */
@ -488,6 +490,7 @@ class World implements ChunkManager{
$this->displayName = $this->provider->getWorldData()->getName(); $this->displayName = $this->provider->getWorldData()->getName();
$this->logger = new \PrefixedLogger($server->getLogger(), "World: $this->displayName"); $this->logger = new \PrefixedLogger($server->getLogger(), "World: $this->displayName");
$this->blockStateRegistry = RuntimeBlockStateRegistry::getInstance();
$this->minY = $this->provider->getWorldMinY(); $this->minY = $this->provider->getWorldMinY();
$this->maxY = $this->provider->getWorldMaxY(); $this->maxY = $this->provider->getWorldMaxY();
@ -559,7 +562,7 @@ class World implements ChunkManager{
}catch(BlockStateDeserializeException){ }catch(BlockStateDeserializeException){
continue; continue;
} }
$block = RuntimeBlockStateRegistry::getInstance()->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData)); $block = $this->blockStateRegistry->fromStateId(GlobalBlockStateHandlers::getDeserializer()->deserialize($blockStateData));
}else{ }else{
//TODO: we probably ought to log an error here //TODO: we probably ought to log an error here
continue; 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; $dontTickName = $dontTickBlocks[$state->getTypeId()] ?? null;
if($dontTickName === null && $state->ticksRandomly()){ if($dontTickName === null && $state->ticksRandomly()){
$this->randomTickBlocks[$state->getStateId()] = true; $this->randomTickBlocks[$state->getStateId()] = true;
@ -1394,7 +1397,7 @@ class World implements ChunkManager{
$entity->onRandomUpdate(); $entity->onRandomUpdate();
} }
$blockFactory = RuntimeBlockStateRegistry::getInstance(); $blockFactory = $this->blockStateRegistry;
foreach($chunk->getSubChunks() as $Y => $subChunk){ foreach($chunk->getSubChunks() as $Y => $subChunk){
if(!$subChunk->isEmptyFast()){ if(!$subChunk->isEmptyFast()){
$k = 0; $k = 0;
@ -1528,13 +1531,18 @@ class World implements ChunkManager{
$collides = []; $collides = [];
$collisionInfo = $this->blockStateRegistry->collisionInfo;
if($targetFirst){ if($targetFirst){
for($z = $minZ; $z <= $maxZ; ++$z){ for($z = $minZ; $z <= $maxZ; ++$z){
for($x = $minX; $x <= $maxX; ++$x){ for($x = $minX; $x <= $maxX; ++$x){
for($y = $minY; $y <= $maxY; ++$y){ for($y = $minY; $y <= $maxY; ++$y){
$block = $this->getBlockAt($x, $y, $z); $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
if($block->collidesWithBB($bb)){ if(match($stateCollisionInfo){
return [$block]; 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($z = $minZ; $z <= $maxZ; ++$z){
for($x = $minX; $x <= $maxX; ++$x){ for($x = $minX; $x <= $maxX; ++$x){
for($y = $minY; $y <= $maxY; ++$y){ for($y = $minY; $y <= $maxY; ++$y){
$block = $this->getBlockAt($x, $y, $z); $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
if($block->collidesWithBB($bb)){ if(match($stateCollisionInfo){
$collides[] = $block; 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; 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. * 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. * 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. * Larger AABBs (>= 2 blocks on any axis) are not accounted for.
* *
* @param int[] $collisionInfo
* @phpstan-param array<int, int> $collisionInfo
*
* @return AxisAlignedBB[] * @return AxisAlignedBB[]
* @phpstan-return list<AxisAlignedBB> * @phpstan-return list<AxisAlignedBB>
*/ */
private function getBlockCollisionBoxesForCell(int $x, int $y, int $z) : array{ private function getBlockCollisionBoxesForCell(int $x, int $y, int $z, array $collisionInfo) : array{
$block = $this->getBlockAt($x, $y, $z); $stateCollisionInfo = $this->getBlockCollisionInfo($x, $y, $z, $collisionInfo);
$boxes = $block->getCollisionBoxes(); $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); //overlapping AABBs can't make any difference if this is a cube, so we can save some CPU cycles in this common case
foreach(Facing::OFFSET as [$dx, $dy, $dz]){ if($stateCollisionInfo !== RuntimeBlockStateRegistry::COLLISION_CUBE){
$extraBoxes = $this->getBlockAt($x + $dx, $y + $dy, $z + $dz)->getCollisionBoxes(); $cellBB = null;
foreach($extraBoxes as $extraBox){ foreach(Facing::OFFSET as [$dx, $dy, $dz]){
if($extraBox->intersectsWith($cellBB)){ $offsetX = $x + $dx;
$boxes[] = $extraBox; $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 = []; $collides = [];
$collisionInfo = $this->blockStateRegistry->collisionInfo;
for($z = $minZ; $z <= $maxZ; ++$z){ for($z = $minZ; $z <= $maxZ; ++$z){
for($x = $minX; $x <= $maxX; ++$x){ for($x = $minX; $x <= $maxX; ++$x){
$chunkPosHash = World::chunkHash($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE); $chunkPosHash = World::chunkHash($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE);
for($y = $minY; $y <= $maxY; ++$y){ for($y = $minY; $y <= $maxY; ++$y){
$relativeBlockHash = World::chunkBlockHash($x, $y, $z); $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){ foreach($boxes as $blockBB){
if($blockBB->intersectsWith($bb)){ if($blockBB->intersectsWith($bb)){
@ -1776,7 +1830,7 @@ class World implements ChunkManager{
return; return;
} }
$blockFactory = RuntimeBlockStateRegistry::getInstance(); $blockFactory = $this->blockStateRegistry;
$this->timings->doBlockSkyLightUpdates->startTiming(); $this->timings->doBlockSkyLightUpdates->startTiming();
if($this->skyLightUpdate === null){ if($this->skyLightUpdate === null){
$this->skyLightUpdate = new SkyLightUpdate(new SubChunkExplorer($this), $blockFactory->lightFilter, $blockFactory->blocksDirectSkyLight); $this->skyLightUpdate = new SkyLightUpdate(new SubChunkExplorer($this), $blockFactory->lightFilter, $blockFactory->blocksDirectSkyLight);
@ -1895,7 +1949,7 @@ class World implements ChunkManager{
$chunk = $this->chunks[$chunkHash] ?? null; $chunk = $this->chunks[$chunkHash] ?? null;
if($chunk !== 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{ }else{
$addToCache = false; $addToCache = false;
$block = VanillaBlocks::AIR(); $block = VanillaBlocks::AIR();
@ -2554,7 +2608,7 @@ class World implements ChunkManager{
$localY = $tilePosition->getFloorY(); $localY = $tilePosition->getFloorY();
$localZ = $tilePosition->getFloorZ() & Chunk::COORD_MASK; $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(); $expectedTileClass = $newBlock->getIdInfo()->getTileClass();
if( if(
$expectedTileClass === null || //new block doesn't expect a tile $expectedTileClass === null || //new block doesn't expect a tile