steel_core/chunk/chunk_map/
light_updates.rs1use super::{
2 Arc, BlockPos, ChunkHolder, ChunkMap, ChunkPos, ChunkStatus, InFlightLightUpdates,
3 LightCacheLayout, LightCacheSetupRadius, LightLayer, LightSectionEmptinessChange,
4 LightSectionRange, LightUpdateState, LightWorkset, PendingChunkLightUpdates, SectionPos,
5 propagate_block_light_changes_with_empty_sections,
6 propagate_sky_light_changes_with_empty_sections,
7};
8
9impl ChunkMap {
10 pub fn block_changed(&self, pos: BlockPos) {
13 let chunk_pos = ChunkPos::new(
14 SectionPos::block_to_section_coord(pos.0.x),
15 SectionPos::block_to_section_coord(pos.0.z),
16 );
17
18 if let Some(holder) = self.lookup_active_holder(chunk_pos)
19 && holder.block_changed(pos)
20 {
21 self.chunks_to_broadcast.lock().push(holder);
23 }
24 }
25
26 pub fn packet_content_changed(&self, chunk_pos: ChunkPos) {
28 if let Some(holder) = self.lookup_active_holder(chunk_pos) {
29 holder.mark_packet_content_changed();
30 }
31 }
32
33 pub fn light_changed(&self, layer: LightLayer, section_pos: SectionPos) {
35 let chunk_pos = ChunkPos::new(section_pos.x(), section_pos.z());
36
37 if let Some(holder) = self.lookup_active_holder(chunk_pos) {
38 if holder.light_changed(layer, section_pos) {
39 self.chunks_to_broadcast.lock().push(holder);
40 }
41 return;
42 }
43
44 if let Some(holder) = self
45 .unloading_chunks
46 .read_sync(&chunk_pos, |_, h| Arc::clone(h))
47 {
48 holder.mark_light_section_dirty(section_pos);
49 }
50 }
51
52 pub fn queue_light_change(
54 &self,
55 pos: BlockPos,
56 check_block: bool,
57 empty_section_change: Option<LightSectionEmptinessChange>,
58 ) {
59 if !check_block && empty_section_change.is_none() {
60 return;
61 }
62
63 let chunk_pos = ChunkPos::new(
64 SectionPos::block_to_section_coord(pos.0.x),
65 SectionPos::block_to_section_coord(pos.0.z),
66 );
67
68 let mut light_updates = self.light_updates.lock();
69 if !self.light_update_center_is_available(chunk_pos) {
70 return;
71 }
72
73 light_updates
74 .pending
75 .queue_change(chunk_pos, pos, check_block, empty_section_change);
76 }
77
78 pub fn propagate_queued_light_changes(&self) {
80 let Some((tasks, in_flight_updates)) = self.drain_pending_light_updates() else {
81 return;
82 };
83
84 let mut blocked_tasks = Vec::new();
85 for (center, task) in tasks {
86 if task.is_empty() {
87 continue;
88 }
89 let Some(light_work_window_reservation) =
90 self.light_work_window_gate.try_reserve_centered(center)
91 else {
92 blocked_tasks.push((center, task));
93 continue;
94 };
95
96 self.propagate_queued_light_change(center, task);
97 drop(light_work_window_reservation);
98 }
99
100 if !blocked_tasks.is_empty() {
101 self.light_updates
102 .lock()
103 .pending
104 .prepend_drained(blocked_tasks);
105 }
106 drop(in_flight_updates);
107 }
108
109 pub(super) async fn flush_queued_light_changes_for_save(&self) {
110 loop {
111 let Some(center) = self.next_pending_light_update_center() else {
112 if !self.has_in_flight_light_updates() {
113 return;
114 }
115 self.wait_for_in_flight_light_updates().await;
116 continue;
117 };
118
119 let light_work_window_reservation =
120 self.light_work_window_gate.reserve_centered(center).await;
121
122 let Some((task, in_flight_updates)) =
123 self.drain_pending_light_update_for_center(center)
124 else {
125 drop(light_work_window_reservation);
126 continue;
127 };
128
129 if task.is_empty() {
130 drop(light_work_window_reservation);
131 drop(in_flight_updates);
132 continue;
133 }
134
135 self.propagate_queued_light_change(center, task);
136 drop(light_work_window_reservation);
137 drop(in_flight_updates);
138 }
139 }
140
141 pub(super) fn drain_pending_light_updates(
142 &self,
143 ) -> Option<(
144 Vec<(ChunkPos, PendingChunkLightUpdates)>,
145 InFlightLightUpdates<'_>,
146 )> {
147 let mut light_updates = self.light_updates.lock();
148 if light_updates.pending.is_empty() {
149 return None;
150 }
151 let tasks = light_updates.pending.drain();
152 let centers = tasks
153 .iter()
154 .map(|(chunk_pos, _)| *chunk_pos)
155 .collect::<Vec<_>>();
156 let in_flight = self.track_in_flight_light_updates(&mut light_updates, centers);
157 Some((tasks, in_flight))
158 }
159
160 pub(super) fn next_pending_light_update_center(&self) -> Option<ChunkPos> {
161 self.light_updates.lock().pending.next_center()
162 }
163
164 pub(super) fn next_pending_light_update_center_touching_chunk(
165 &self,
166 chunk_pos: ChunkPos,
167 ) -> Option<ChunkPos> {
168 self.light_updates
169 .lock()
170 .pending
171 .next_center_touching_chunk(chunk_pos)
172 }
173
174 pub(super) fn drain_pending_light_update_for_center(
175 &self,
176 center: ChunkPos,
177 ) -> Option<(PendingChunkLightUpdates, InFlightLightUpdates<'_>)> {
178 let mut light_updates = self.light_updates.lock();
179 let task = light_updates.pending.drain_center(center)?;
180 let in_flight = self.track_in_flight_light_updates(&mut light_updates, vec![center]);
181 Some((task, in_flight))
182 }
183
184 pub(super) fn track_in_flight_light_updates(
185 &self,
186 light_updates: &mut LightUpdateState,
187 centers: Vec<ChunkPos>,
188 ) -> InFlightLightUpdates<'_> {
189 light_updates.track_in_flight(¢ers);
190 InFlightLightUpdates {
191 centers,
192 light_updates: &self.light_updates,
193 progress_notify: &self.light_updates_progress_notify,
194 }
195 }
196
197 pub(super) fn has_in_flight_light_updates(&self) -> bool {
198 self.light_updates.lock().has_in_flight_updates()
199 }
200
201 pub(super) fn has_in_flight_light_update_touching_chunk(&self, chunk_pos: ChunkPos) -> bool {
202 self.light_updates
203 .lock()
204 .has_in_flight_update_touching_chunk(chunk_pos)
205 }
206
207 pub(super) async fn wait_for_in_flight_light_updates(&self) {
208 loop {
209 if !self.has_in_flight_light_updates() {
210 return;
211 }
212
213 let progress = self.light_updates_progress_notify.notified();
214 if !self.has_in_flight_light_updates() {
215 return;
216 }
217 progress.await;
218 }
219 }
220
221 pub(super) async fn wait_for_in_flight_light_update_touching_chunk(&self, chunk_pos: ChunkPos) {
222 loop {
223 if !self.has_in_flight_light_update_touching_chunk(chunk_pos) {
224 return;
225 }
226
227 let progress = self.light_updates_progress_notify.notified();
228 if !self.has_in_flight_light_update_touching_chunk(chunk_pos) {
229 return;
230 }
231 progress.await;
232 }
233 }
234
235 pub(super) async fn flush_queued_light_changes_touching_chunk_for_save(
236 &self,
237 chunk_pos: ChunkPos,
238 ) {
239 loop {
240 let Some(center) = self.next_pending_light_update_center_touching_chunk(chunk_pos)
241 else {
242 if !self.has_in_flight_light_update_touching_chunk(chunk_pos) {
243 return;
244 }
245 self.wait_for_in_flight_light_update_touching_chunk(chunk_pos)
246 .await;
247 continue;
248 };
249
250 let light_work_window_reservation =
251 self.light_work_window_gate.reserve_centered(center).await;
252
253 let Some((task, in_flight_updates)) =
254 self.drain_pending_light_update_for_center(center)
255 else {
256 drop(light_work_window_reservation);
257 continue;
258 };
259
260 if task.is_empty() {
261 drop(light_work_window_reservation);
262 drop(in_flight_updates);
263 continue;
264 }
265
266 self.propagate_queued_light_change(center, task);
267 drop(light_work_window_reservation);
268 drop(in_flight_updates);
269 }
270 }
271
272 #[cfg(test)]
273 pub(super) fn has_pending_light_updates(&self) -> bool {
274 !self.light_updates.lock().is_idle()
275 }
276
277 #[cfg(test)]
278 pub(super) fn light_update_touches_chunk(&self, chunk_pos: ChunkPos) -> bool {
279 self.light_updates.lock().touches_chunk(chunk_pos)
280 }
281
282 pub(super) fn light_update_center_is_available(&self, center: ChunkPos) -> bool {
283 self.light_update_holder(center)
284 .is_some_and(|holder| holder.try_chunk(ChunkStatus::Light).is_some())
285 }
286
287 pub(super) fn light_update_holder(&self, chunk_pos: ChunkPos) -> Option<Arc<ChunkHolder>> {
288 self.chunks
289 .read_sync(&chunk_pos, |_, holder| Arc::clone(holder))
290 .or_else(|| {
291 self.unloading_chunks
292 .read_sync(&chunk_pos, |_, holder| Arc::clone(holder))
293 })
294 }
295
296 pub(super) fn propagate_queued_light_change(
297 &self,
298 center: ChunkPos,
299 task: PendingChunkLightUpdates,
300 ) {
301 let Some(workset) = self.light_workset_for_change(center) else {
302 log::warn!("Failed to set up light workset for queued light update at {center:?}");
303 return;
304 };
305
306 let empty_sections = task.empty_section_changes();
307 let positions = task.changed_positions.into_iter().collect::<Vec<_>>();
308 let world = self.world_gen_context.world();
309
310 if world.dimension_type.has_skylight {
311 match propagate_sky_light_changes_with_empty_sections(
312 &workset,
313 positions.iter().copied(),
314 empty_sections.iter().copied(),
315 ) {
316 Ok(result) => {
317 for section_pos in result.updated_sections {
318 self.light_changed(LightLayer::Sky, section_pos);
319 }
320 }
321 Err(error) => {
322 log::warn!(
323 "Failed to propagate queued sky-light change for {center:?}: {error:?}"
324 );
325 }
326 }
327 }
328
329 let Ok(result) =
330 propagate_block_light_changes_with_empty_sections(&workset, positions, empty_sections)
331 else {
332 log::warn!("Failed to propagate queued block-light change for {center:?}");
333 return;
334 };
335
336 for section_pos in result.updated_sections {
337 self.light_changed(LightLayer::Block, section_pos);
338 }
339 }
340
341 pub(super) fn light_workset_for_change(&self, center: ChunkPos) -> Option<LightWorkset> {
342 let Ok(range) = LightSectionRange::from_world_height(
343 self.world_gen_context.min_y(),
344 self.world_gen_context.height(),
345 ) else {
346 return None;
347 };
348
349 let layout = LightCacheLayout::new(center, range);
350 LightWorkset::setup(
351 layout,
352 LightCacheSetupRadius::Full,
353 true,
354 |chunk_pos| {
355 let holder = self.light_update_holder(chunk_pos)?;
356 holder.try_chunk(ChunkStatus::Light)?;
357 Some(holder)
358 },
359 |_| true,
360 )
361 .ok()
362 }
363}