mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-20 16:00:20 +00:00
Merge 'minor-next' into 'major-next'
Automatic merge performed by: https://github.com/pmmp/RestrictedActions/actions/runs/13191240897
This commit is contained in:
commit
b0ac8863c4
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user