PocketMine-MP 5.18.1 git-9381fc4172e5dce4cada1cb356050c8a2ab57b94
LightUpdate.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\world\light;
25
27use pocketmine\world\format\LightArray;
32use function assert;
33use function max;
34
35//TODO: make light updates asynchronous
36abstract class LightUpdate{
37 public const BASE_LIGHT_FILTER = 1;
38
43 protected array $updateNodes = [];
44
49 public function __construct(
50 protected SubChunkExplorer $subChunkExplorer,
51 protected array $lightFilters
52 ){}
53
54 abstract protected function getCurrentLightArray() : LightArray;
55
56 abstract public function recalculateNode(int $x, int $y, int $z) : void;
57
62 abstract public function recalculateChunk(int $chunkX, int $chunkZ) : int;
63
64 protected function getEffectiveLight(int $x, int $y, int $z) : int{
65 if($this->subChunkExplorer->moveTo($x, $y, $z) !== SubChunkExplorerStatus::INVALID){
66 return $this->getCurrentLightArray()->get($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK);
67 }
68 return 0;
69 }
70
71 protected function getHighestAdjacentLight(int $x, int $y, int $z) : int{
72 $adjacent = 0;
73 foreach(Facing::OFFSET as [$ox, $oy, $oz]){
74 if(($adjacent = max($adjacent, $this->getEffectiveLight($x + $ox, $y + $oy, $z + $oz))) === 15){
75 break;
76 }
77 }
78 return $adjacent;
79 }
80
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];
83 }
84
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);
91
92 if($oldLevel !== $newLevel){
93 $lightArray->set($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK, $newLevel);
94 if($oldLevel < $newLevel){ //light increased
95 $context->spreadVisited[$blockHash] = true;
96 $context->spreadQueue->enqueue([$x, $y, $z]);
97 }else{ //light removed
98 $context->removalVisited[$blockHash] = true;
99 $context->removalQueue->enqueue([$x, $y, $z, $oldLevel]);
100 }
101 }
102 }
103 }
104 return $context;
105 }
106
107 public function execute() : int{
108 $context = $this->prepareNodes();
109
110 $touched = 0;
111 $lightArray = null;
112 $subChunkExplorer = $this->subChunkExplorer;
113 $subChunkExplorer->invalidate();
114 while(!$context->removalQueue->isEmpty()){
115 $touched++;
116 [$x, $y, $z, $oldAdjacentLight] = $context->removalQueue->dequeue();
117
118 foreach(Facing::OFFSET as [$ox, $oy, $oz]){
119 $cx = $x + $ox;
120 $cy = $y + $oy;
121 $cz = $z + $oz;
122
123 $moveStatus = $subChunkExplorer->moveTo($cx, $cy, $cz);
124 if($moveStatus === SubChunkExplorerStatus::INVALID){
125 continue;
126 }
127 if($moveStatus === SubChunkExplorerStatus::MOVED){
128 $lightArray = $this->getCurrentLightArray();
129 }
130 assert($lightArray !== null);
131 $this->computeRemoveLight($cx, $cy, $cz, $oldAdjacentLight, $context, $lightArray);
132 }
133 }
134
135 $subChunk = null;
136 $subChunkExplorer->invalidate();
137 while(!$context->spreadQueue->isEmpty()){
138 $touched++;
139 [$x, $y, $z] = $context->spreadQueue->dequeue();
140 $from = $context->spreadVisited[World::blockHash($x, $y, $z)];
141
142 unset($context->spreadVisited[World::blockHash($x, $y, $z)]);
143
144 $moveStatus = $subChunkExplorer->moveTo($x, $y, $z);
145 if($moveStatus === SubChunkExplorerStatus::INVALID){
146 continue;
147 }
148 if($moveStatus === SubChunkExplorerStatus::MOVED){
149 $subChunk = $subChunkExplorer->currentSubChunk;
150 $lightArray = $this->getCurrentLightArray();
151 }
152 assert($lightArray !== null);
153
154 $newAdjacentLight = $lightArray->get($x & SubChunk::COORD_MASK, $y & SubChunk::COORD_MASK, $z & SubChunk::COORD_MASK);
155 if($newAdjacentLight <= 0){
156 continue;
157 }
158
159 foreach(Facing::OFFSET as $side => [$ox, $oy, $oz]){
160 if($from === $side){
161 //don't check the side that this node received its initial light from
162 continue;
163 }
164 $cx = $x + $ox;
165 $cy = $y + $oy;
166 $cz = $z + $oz;
167
168 $moveStatus = $subChunkExplorer->moveTo($cx, $cy, $cz);
169 if($moveStatus === SubChunkExplorerStatus::INVALID){
170 continue;
171 }
172 if($moveStatus === SubChunkExplorerStatus::MOVED){
173 $subChunk = $subChunkExplorer->currentSubChunk;
174 $lightArray = $this->getCurrentLightArray();
175 }
176 assert($subChunk !== null);
177 $this->computeSpreadLight($cx, $cy, $cz, $newAdjacentLight, $context, $lightArray, $subChunk, $side);
178 }
179 }
180
181 return $touched;
182 }
183
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);
189
190 if($current !== 0 && $current < $oldAdjacentLevel){
191 $lightArray->set($lx, $ly, $lz, 0);
192
193 if(!isset($context->removalVisited[$index = World::blockHash($x, $y, $z)])){
194 $context->removalVisited[$index] = true;
195 if($current > 1){
196 $context->removalQueue->enqueue([$x, $y, $z, $current]);
197 }
198 }
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]);
203 }
204 }
205 }
206
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);
213
214 if($current < $potentialLight){
215 $lightArray->set($lx, $ly, $lz, $potentialLight);
216
217 if(!isset($context->spreadVisited[$index = World::blockHash($x, $y, $z)]) && $potentialLight > 1){
218 //Track where this node was lit from, to avoid checking the source again when we propagate from here
219 //TODO: In the future it might be worth tracking more than one adjacent source face in case multiple
220 //nodes try to light the same node. However, this is a rare case since the vast majority of calls are
221 //basic propagation with only one source anyway.
222 $context->spreadVisited[$index] = Facing::opposite($side);
223 $context->spreadQueue->enqueue([$x, $y, $z]);
224 }
225 }
226 }
227}
recalculateChunk(int $chunkX, int $chunkZ)
__construct(protected SubChunkExplorer $subChunkExplorer, protected array $lightFilters)
Definition: LightUpdate.php:49