Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimise collision box checks by caching some basic info #6606

Open
wants to merge 8 commits into
base: minor-next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/block/RuntimeBlockStateRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\SingletonTrait;
use pocketmine\world\light\LightUpdate;
use function count;
use function min;

/**
Expand All @@ -40,6 +41,11 @@
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>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -112,6 +125,46 @@ private function fillStaticArrays(int $index, Block $block) : void{
if($block->blocksDirectSkyLight()){
$this->blocksDirectSkyLight[$index] = true;
}

$declarer = (new \ReflectionFunction($block->getModelPositionOffset(...)))->getClosureScopeClass();
if($declarer === null){
throw new AssumptionFailedError("We know this is a class method");
}
if($declarer->getName() !== Block::class){
$this->collisionInfo[$index] = self::COLLISION_MAY_OVERFLOW;
}else{
$boxes = $block->getCollisionBoxes();
if(count($boxes) === 0){
$this->collisionInfo[$index] = self::COLLISION_NONE;
}elseif(
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
){
$this->collisionInfo[$index] = self::COLLISION_CUBE;
}else{
$info = self::COLLISION_CUSTOM;

//TODO: this could blow up if any recalculateCollisionBoxes() uses the world
//it shouldn't, but that doesn't mean that custom blocks won't...
foreach($block->getCollisionBoxes() as $box){
if(
$box->minX < 0 || $box->maxX > 1 ||
$box->minY < 0 || $box->maxY > 1 ||
$box->minZ < 0 || $box->maxZ > 1
){
$info = self::COLLISION_MAY_OVERFLOW;
break;
}
}

$this->collisionInfo[$index] = $info;
}
}
}
}

Expand Down
52 changes: 41 additions & 11 deletions src/world/World.php
Original file line number Diff line number Diff line change
Expand Up @@ -1560,19 +1560,47 @@ public function getCollisionBlocks(AxisAlignedBB $bb, bool $targetFirst = false)
* 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();

$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;
private function getBlockCollisionBoxesForCell(int $x, int $y, int $z, array $collisionInfo) : array{
if($y < $this->minY || $y > $this->maxY){
return [];
}
$stateId = $this
->getChunk($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE)
?->getBlockStateId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK) ?? Block::EMPTY_STATE_ID;

$stateCollisionInfo = $collisionInfo[$stateId] ?? throw new AssumptionFailedError("This should always exist");
$boxes = match($stateCollisionInfo){
RuntimeBlockStateRegistry::COLLISION_NONE => [],
RuntimeBlockStateRegistry::COLLISION_CUBE => [AxisAlignedBB::one()->offset($x, $y, $z)],
default => $this->getBlockAt($x, $y, $z)->getCollisionBoxes()
};

//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]){
$offsetY = $y + $dy;
if($offsetY < $this->minY || $offsetY > $this->maxY){
continue;
}
$stateId = $this
->getChunk(($x + $dx) >> Chunk::COORD_BIT_SIZE, ($z + $dz) >> Chunk::COORD_BIT_SIZE)
?->getBlockStateId(($x + $dx) & Chunk::COORD_MASK, $offsetY, ($z + $dz) & Chunk::COORD_MASK) ?? Block::EMPTY_STATE_ID;
if($collisionInfo[$stateId] === RuntimeBlockStateRegistry::COLLISION_MAY_OVERFLOW){
//avoid allocating this unless it's needed
$cellBB ??= AxisAlignedBB::one()->offset($x, $y, $z);
$extraBoxes = $this->getBlockAt($x + $dx, $offsetY, $z + $dz)->getCollisionBoxes();
foreach($extraBoxes as $extraBox){
if($extraBox->intersectsWith($cellBB)){
$boxes[] = $extraBox;
}
}
}
}
}
Expand All @@ -1594,13 +1622,15 @@ public function getBlockCollisionBoxes(AxisAlignedBB $bb) : array{

$collides = [];

$collisionInfo = RuntimeBlockStateRegistry::getInstance()->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)){
Expand Down
Loading