*/ private array $fullList = []; /** * Index of default states for every block type * @var Block[] * @phpstan-var array */ private array $typeIndex = []; /** * @var int[] * @phpstan-var array */ public array $light = []; /** * @var int[] * @phpstan-var array */ public array $lightFilter = []; /** * @var true[] * @phpstan-var array */ public array $blocksDirectSkyLight = []; /** * @var float[] * @phpstan-var array */ public array $blastResistance = []; /** * Map of state ID -> useful AABB info to avoid unnecessary block allocations * @var int[] * @phpstan-var array */ public array $collisionInfo = []; public function __construct(){ foreach(VanillaBlocks::getAll() as $block){ $this->register($block); } } /** * Maps a block type's state permutations to its corresponding state IDs. This is necessary for the block to be * recognized when fetching it by its state ID from chunks at runtime. * * @throws \InvalidArgumentException if the desired block type ID is already registered */ public function register(Block $block) : void{ $typeId = $block->getTypeId(); if(isset($this->typeIndex[$typeId])){ throw new \InvalidArgumentException("Block ID $typeId is already used by another block"); } $this->typeIndex[$typeId] = clone $block; foreach($block->generateStatePermutations() as $v){ $this->fillStaticArrays($v->getStateId(), $v); } } /** * 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){ throw new AssumptionFailedError("Cannot fill static arrays for an invalid blockstate"); }else{ $this->fullList[$index] = $block; $this->blastResistance[$index] = $block->getBreakInfo()->getBlastResistance(); $this->light[$index] = $block->getLightLevel(); $this->lightFilter[$index] = min(15, $block->getLightFilter() + LightUpdate::BASE_LIGHT_FILTER); if($block->blocksDirectSkyLight()){ $this->blocksDirectSkyLight[$index] = true; } $this->collisionInfo[$index] = self::calculateCollisionInfo($block); } } public function fromStateId(int $stateId) : Block{ if($stateId < 0){ throw new \InvalidArgumentException("Block state ID cannot be negative"); } if(isset($this->fullList[$stateId])) { //hot $block = clone $this->fullList[$stateId]; }else{ $typeId = $stateId >> Block::INTERNAL_STATE_DATA_BITS; $stateData = ($stateId ^ $typeId) & Block::INTERNAL_STATE_DATA_MASK; $block = new UnknownBlock(new BID($typeId), new BlockTypeInfo(BreakInfo::instant()), $stateData); } return $block; } public function hasStateId(int $stateId) : bool{ return isset($this->fullList[$stateId]); } /** * @return Block[] * @phpstan-return array */ public function getAllKnownStates() : array{ return $this->fullList; } }