37    public const BASE_LIGHT_FILTER = 1;
 
   43    protected array $updateNodes = [];
 
   51        protected array $lightFilters
 
 
   54    abstract protected function getCurrentLightArray() : LightArray;
 
   56    abstract public function recalculateNode(
int $x, 
int $y, 
int $z) : void;
 
   64    protected function getEffectiveLight(
int $x, 
int $y, 
int $z) : int{
 
   66            return $this->getCurrentLightArray()->get($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK);
 
   71    protected function getHighestAdjacentLight(
int $x, 
int $y, 
int $z) : int{
 
   73        foreach(Facing::OFFSET as [$ox, $oy, $oz]){
 
   74            if(($adjacent = max($adjacent, $this->getEffectiveLight($x + $ox, $y + $oy, $z + $oz))) === 15){
 
   81    public function setAndUpdateLight(
int $x, 
int $y, 
int $z, 
int $newLevel) : void{
 
   82        $this->updateNodes[World::blockHash($x, $y, $z)] = [$x, $y, $z, $newLevel];
 
   85    private function prepareNodes() : LightPropagationContext{
 
   86        $context = new LightPropagationContext();
 
   87        foreach($this->updateNodes as $blockHash => [$x, $y, $z, $newLevel]){
 
   88            if($this->subChunkExplorer->moveTo($x, $y, $z) !== SubChunkExplorerStatus::INVALID){
 
   89                $lightArray = $this->getCurrentLightArray();
 
   90                $oldLevel = $lightArray->get($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK);
 
   92                if($oldLevel !== $newLevel){
 
   93                    $lightArray->set($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK, $newLevel);
 
   94                    if($oldLevel < $newLevel){ 
 
   95                        $context->spreadVisited[$blockHash] = 
true;
 
   96                        $context->spreadQueue->enqueue([$x, $y, $z]);
 
   98                        $context->removalVisited[$blockHash] = 
true;
 
   99                        $context->removalQueue->enqueue([$x, $y, $z, $oldLevel]);
 
  107    public function execute() : int{
 
  108        $context = $this->prepareNodes();
 
  112        $subChunkExplorer = $this->subChunkExplorer;
 
  113        $subChunkExplorer->invalidate();
 
  114        while(!$context->removalQueue->isEmpty()){
 
  116            [$x, $y, $z, $oldAdjacentLight] = $context->removalQueue->dequeue();
 
  118            foreach(Facing::OFFSET as [$ox, $oy, $oz]){
 
  123                $moveStatus = $subChunkExplorer->moveTo($cx, $cy, $cz);
 
  124                if($moveStatus === SubChunkExplorerStatus::INVALID){
 
  127                if($moveStatus === SubChunkExplorerStatus::MOVED){
 
  128                    $lightArray = $this->getCurrentLightArray();
 
  130                assert($lightArray !== null);
 
  131                $this->computeRemoveLight($cx, $cy, $cz, $oldAdjacentLight, $context, $lightArray);
 
  136        $subChunkExplorer->invalidate();
 
  137        while(!$context->spreadQueue->isEmpty()){
 
  139            [$x, $y, $z] = $context->spreadQueue->dequeue();
 
  140            $from = $context->spreadVisited[World::blockHash($x, $y, $z)];
 
  142            unset($context->spreadVisited[World::blockHash($x, $y, $z)]);
 
  144            $moveStatus = $subChunkExplorer->moveTo($x, $y, $z);
 
  145            if($moveStatus === SubChunkExplorerStatus::INVALID){
 
  148            if($moveStatus === SubChunkExplorerStatus::MOVED){
 
  149                $subChunk = $subChunkExplorer->currentSubChunk;
 
  150                $lightArray = $this->getCurrentLightArray();
 
  152            assert($lightArray !== 
null);
 
  154            $newAdjacentLight = $lightArray->get($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK);
 
  155            if($newAdjacentLight <= 0){
 
  159            foreach(Facing::OFFSET as $side => [$ox, $oy, $oz]){
 
  168                $moveStatus = $subChunkExplorer->moveTo($cx, $cy, $cz);
 
  169                if($moveStatus === SubChunkExplorerStatus::INVALID){
 
  172                if($moveStatus === SubChunkExplorerStatus::MOVED){
 
  173                    $subChunk = $subChunkExplorer->currentSubChunk;
 
  174                    $lightArray = $this->getCurrentLightArray();
 
  176                assert($subChunk !== 
null);
 
  177                $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight, $context, $lightArray, $subChunk, $side);
 
  184    protected function computeRemoveLight(
int $x, 
int $y, 
int $z, 
int $oldAdjacentLevel, LightPropagationContext $context, LightArray $lightArray) : void{
 
  185        $lx = $x & SubChunk::COORD_MASK;
 
  186        $ly = $y & SubChunk::COORD_MASK;
 
  187        $lz = $z & SubChunk::COORD_MASK;
 
  188        $current = $lightArray->get($lx, $ly, $lz);
 
  190        if($current !== 0 && $current < $oldAdjacentLevel){
 
  191            $lightArray->set($lx, $ly, $lz, 0);
 
  193            if(!isset($context->removalVisited[$index = World::blockHash($x, $y, $z)])){
 
  194                $context->removalVisited[$index] = 
true;
 
  196                    $context->removalQueue->enqueue([$x, $y, $z, $current]);
 
  199        }elseif($current >= $oldAdjacentLevel){
 
  200            if(!isset($context->spreadVisited[$index = World::blockHash($x, $y, $z)])){
 
  201                $context->spreadVisited[$index] = 
true;
 
  202                $context->spreadQueue->enqueue([$x, $y, $z]);
 
  207    protected function computeSpreadLight(
int $x, 
int $y, 
int $z, 
int $newAdjacentLevel, LightPropagationContext $context, LightArray $lightArray, SubChunk $subChunk, 
int $side) : void{
 
  208        $lx = $x & SubChunk::COORD_MASK;
 
  209        $ly = $y & SubChunk::COORD_MASK;
 
  210        $lz = $z & SubChunk::COORD_MASK;
 
  211        $current = $lightArray->get($lx, $ly, $lz);
 
  212        $potentialLight = $newAdjacentLevel - ($this->lightFilters[$subChunk->getBlockStateId($lx, $ly, $lz)] ?? self::BASE_LIGHT_FILTER);
 
  214        if($current < $potentialLight){
 
  215            $lightArray->set($lx, $ly, $lz, $potentialLight);
 
  217            if(!isset($context->spreadVisited[$index = World::blockHash($x, $y, $z)]) && $potentialLight > 1){
 
  222                $context->spreadVisited[$index] = Facing::opposite($side);
 
  223                $context->spreadQueue->enqueue([$x, $y, $z]);