Skip to main content

steel_core/chunk/light/sky_propagation/
queue_engine.rs

1use super::{
2    BlockPos, BlockStateId, CachedLightBlock, ChunkPos, Direction, LIGHT_BLOCKED,
3    LightAxisDirection, LightDirectionSet, LightQueueFlags, MAX_LIGHT_LEVEL, PackedLightQueueEntry,
4    SectionPos, SkyLightPropagationContext, get_light_block_into, get_light_opacity,
5    light_occlusion_shape, vanilla_blocks,
6};
7
8impl SkyLightPropagationContext<'_, '_, '_> {
9    pub(super) fn process_delayed_increases(&mut self, entries: &[PackedLightQueueEntry]) {
10        for entry in entries {
11            let Some(source_block) = self.cached_block_from_entry(*entry) else {
12                continue;
13            };
14            self.light.set(source_block, entry.level());
15        }
16    }
17
18    pub(super) fn process_delayed_decreases(&mut self, entries: &[PackedLightQueueEntry]) {
19        for entry in entries {
20            let Some(source_block) = self.cached_block_from_entry(*entry) else {
21                continue;
22            };
23            self.light.set(source_block, 0);
24        }
25    }
26
27    pub(super) fn get_light_level_extruded(&self, block_pos: BlockPos) -> u8 {
28        let mut section_y = SectionPos::block_to_section_coord(block_pos.y());
29        let section_x = SectionPos::block_to_section_coord(block_pos.x());
30        let section_z = SectionPos::block_to_section_coord(block_pos.z());
31
32        if let Some(cached_block) = self.layout.cached_block(block_pos)
33            && self
34                .light
35                .has_non_missing_section(SectionPos::new(section_x, section_y, section_z))
36        {
37            return self.light.get(cached_block);
38        }
39
40        loop {
41            section_y += 1;
42            if section_y >= self.layout.range().max_section_y_exclusive() {
43                return MAX_LIGHT_LEVEL;
44            }
45
46            let section_pos = SectionPos::new(section_x, section_y, section_z);
47            if !self.light.has_non_missing_section(section_pos) {
48                continue;
49            }
50            let block_above = BlockPos::new(block_pos.x(), section_y << 4, block_pos.z());
51            let Some(cached_block) = self.layout.cached_block(block_above) else {
52                continue;
53            };
54            return self.light.get(cached_block);
55        }
56    }
57
58    pub(super) fn propagate_neighbor_levels(
59        &mut self,
60        chunk_pos: ChunkPos,
61        from_section: i32,
62        to_section: i32,
63    ) {
64        for section_y in (from_section..=to_section).rev() {
65            let section_pos = SectionPos::new(chunk_pos.0.x, section_y, chunk_pos.0.y);
66            if !self.light.has_non_missing_section(section_pos) {
67                continue;
68            }
69
70            for direction in LightAxisDirection::HORIZONTAL {
71                self.propagate_neighbor_level_section(chunk_pos, section_y, direction);
72            }
73        }
74    }
75
76    fn propagate_neighbor_level_section(
77        &mut self,
78        chunk_pos: ChunkPos,
79        section_y: i32,
80        direction: LightAxisDirection,
81    ) {
82        let (neighbor_offset_x, _, neighbor_offset_z) = direction.offset();
83        let neighbor_section_pos = SectionPos::new(
84            chunk_pos.0.x + neighbor_offset_x,
85            section_y,
86            chunk_pos.0.y + neighbor_offset_z,
87        );
88        if !self.light.has_light_data_section(neighbor_section_pos) {
89            return;
90        }
91
92        let (increment_x, increment_z, start_x, start_z) =
93            Self::neighbor_edge_scan(chunk_pos, direction);
94        let directions = LightDirectionSet::only(direction.opposite());
95        let flags = LightQueueFlags::EMPTY.with(LightQueueFlags::HAS_SIDED_TRANSPARENT_BLOCKS);
96
97        let min_y = section_y << 4;
98        let max_y = min_y | 15;
99        for y in min_y..=max_y {
100            let mut x = start_x;
101            let mut z = start_z;
102            for _ in 0..16 {
103                let source_pos = BlockPos::new(x, y, z);
104                let Some(source_block) = self.layout.cached_block(source_pos) else {
105                    x += increment_x;
106                    z += increment_z;
107                    continue;
108                };
109                let level = self.light.get(source_block);
110                if level > 1 {
111                    self.enqueue_increase(source_pos, level, directions, flags);
112                }
113                x += increment_x;
114                z += increment_z;
115            }
116        }
117    }
118
119    pub(super) fn check_chunk_edges(
120        &mut self,
121        chunk_pos: ChunkPos,
122        from_section: i32,
123        to_section: i32,
124    ) {
125        for section_y in (from_section..=to_section).rev() {
126            self.check_chunk_edge(chunk_pos, section_y);
127        }
128
129        self.perform_light_decrease();
130    }
131
132    fn check_chunk_edge(&mut self, chunk_pos: ChunkPos, section_y: i32) {
133        let current_section_pos = SectionPos::new(chunk_pos.0.x, section_y, chunk_pos.0.y);
134        if !self.light.has_non_missing_section(current_section_pos) {
135            return;
136        }
137
138        for direction in LightAxisDirection::HORIZONTAL {
139            let (neighbor_offset_x, _, neighbor_offset_z) = direction.offset();
140            let neighbor_chunk_pos = ChunkPos::new(
141                chunk_pos.0.x + neighbor_offset_x,
142                chunk_pos.0.y + neighbor_offset_z,
143            );
144            let neighbor_section_pos =
145                SectionPos::new(neighbor_chunk_pos.0.x, section_y, neighbor_chunk_pos.0.y);
146            if !self.light.has_non_missing_section(neighbor_section_pos) {
147                continue;
148            }
149            if !self.light.has_light_data_section(current_section_pos)
150                && !self.light.has_light_data_section(neighbor_section_pos)
151            {
152                continue;
153            }
154
155            self.check_chunk_edge_direction(chunk_pos, neighbor_chunk_pos, section_y, direction);
156        }
157    }
158
159    fn check_chunk_edge_direction(
160        &mut self,
161        chunk_pos: ChunkPos,
162        neighbor_chunk_pos: ChunkPos,
163        section_y: i32,
164        direction: LightAxisDirection,
165    ) {
166        let (neighbor_offset_x, _, neighbor_offset_z) = direction.offset();
167        let (increment_x, increment_z, start_x, start_z) =
168            Self::current_edge_scan(chunk_pos, direction);
169        let mut center_delayed_checks = [0usize; 16 * 16];
170        let mut neighbor_delayed_checks = [0usize; 16 * 16];
171        let mut center_delayed_check_count = 0;
172        let mut neighbor_delayed_check_count = 0;
173
174        let min_y = section_y << 4;
175        let max_y = min_y | 15;
176        for y in min_y..=max_y {
177            let mut x = start_x;
178            let mut z = start_z;
179            for _ in 0..16 {
180                let current_pos = BlockPos::new(x, y, z);
181                let neighbor_pos = BlockPos::new(x + neighbor_offset_x, y, z + neighbor_offset_z);
182                let Some(current_block) = self.layout.cached_block(current_pos) else {
183                    x += increment_x;
184                    z += increment_z;
185                    continue;
186                };
187                let Some(neighbor_block) = self.layout.cached_block(neighbor_pos) else {
188                    x += increment_x;
189                    z += increment_z;
190                    continue;
191                };
192
193                let current_level = self.light.get(current_block);
194                if self
195                    .calculate_light_value(current_pos, current_level)
196                    .is_some_and(|calculated| calculated != current_level)
197                {
198                    center_delayed_checks[center_delayed_check_count] = current_block.local_index;
199                    center_delayed_check_count += 1;
200                }
201
202                let neighbor_level = self.light.get(neighbor_block);
203                if self
204                    .calculate_light_value(neighbor_pos, neighbor_level)
205                    .is_some_and(|calculated| calculated != neighbor_level)
206                {
207                    neighbor_delayed_checks[neighbor_delayed_check_count] =
208                        neighbor_block.local_index;
209                    neighbor_delayed_check_count += 1;
210                }
211
212                x += increment_x;
213                z += increment_z;
214            }
215        }
216
217        let current_chunk_offset_x = chunk_pos.0.x << 4;
218        let current_chunk_offset_z = chunk_pos.0.y << 4;
219        let neighbor_chunk_offset_x = neighbor_chunk_pos.0.x << 4;
220        let neighbor_chunk_offset_z = neighbor_chunk_pos.0.y << 4;
221        let chunk_offset_y = section_y << 4;
222        let delayed_check_count = center_delayed_check_count.max(neighbor_delayed_check_count);
223        for delayed_check_index in 0..delayed_check_count {
224            if delayed_check_index < center_delayed_check_count {
225                let local_index = center_delayed_checks[delayed_check_index];
226                self.check_block(Self::block_pos_from_local_index(
227                    current_chunk_offset_x,
228                    chunk_offset_y,
229                    current_chunk_offset_z,
230                    local_index,
231                ));
232            }
233            if delayed_check_index < neighbor_delayed_check_count {
234                let local_index = neighbor_delayed_checks[delayed_check_index];
235                self.check_block(Self::block_pos_from_local_index(
236                    neighbor_chunk_offset_x,
237                    chunk_offset_y,
238                    neighbor_chunk_offset_z,
239                    local_index,
240                ));
241            }
242        }
243    }
244
245    pub(super) fn perform_light_increase(&mut self) {
246        while let Some(entry) = self.queues.dequeue_increase() {
247            let Some(source_block) = self.cached_block_from_entry(entry) else {
248                continue;
249            };
250            if entry.should_recheck_level() {
251                if self.light.get(source_block) != entry.level() {
252                    continue;
253                }
254            } else if entry.should_write_level() {
255                self.light.set(source_block, entry.level());
256            }
257
258            let source_state = if entry.has_sided_transparent_blocks() {
259                Some(self.sections.get_block_state(source_block))
260            } else {
261                None
262            };
263
264            for axis_direction in entry.directions().directions() {
265                let neighbor_pos = Self::offset(source_block.block_pos, axis_direction);
266                let Some(neighbor_block) = self.layout.cached_block(neighbor_pos) else {
267                    continue;
268                };
269                if !self.light.has_non_missing(neighbor_block) {
270                    continue;
271                }
272                let current_level = self.light.get(neighbor_block);
273                if current_level >= entry.level().saturating_sub(1) {
274                    continue;
275                }
276
277                let neighbor_state = self.sections.get_block_state(neighbor_block);
278                let Some((target_level, flags)) = Self::target_level(
279                    entry.level(),
280                    source_state,
281                    neighbor_state,
282                    axis_direction.direction(),
283                ) else {
284                    continue;
285                };
286                if target_level <= current_level {
287                    continue;
288                }
289
290                self.light.set(neighbor_block, target_level);
291                if target_level > 1 {
292                    self.enqueue_increase(
293                        neighbor_pos,
294                        target_level,
295                        LightDirectionSet::all_except_opposite(axis_direction),
296                        flags,
297                    );
298                }
299            }
300        }
301    }
302
303    pub(super) fn perform_light_decrease(&mut self) {
304        while let Some(entry) = self.queues.dequeue_decrease() {
305            let Some(source_block) = self.cached_block_from_entry(entry) else {
306                continue;
307            };
308            let source_state = if entry.has_sided_transparent_blocks() {
309                Some(self.sections.get_block_state(source_block))
310            } else {
311                None
312            };
313
314            for axis_direction in entry.directions().directions() {
315                let neighbor_pos = Self::offset(source_block.block_pos, axis_direction);
316                let Some(neighbor_block) = self.layout.cached_block(neighbor_pos) else {
317                    continue;
318                };
319                if !self.light.has_non_missing(neighbor_block) {
320                    continue;
321                }
322                let current_level = self.light.get(neighbor_block);
323                if current_level == 0 {
324                    continue;
325                }
326
327                let neighbor_state = self.sections.get_block_state(neighbor_block);
328                let Some((target_level, flags)) = Self::target_level_saturating(
329                    entry.level(),
330                    source_state,
331                    neighbor_state,
332                    axis_direction.direction(),
333                ) else {
334                    continue;
335                };
336
337                if current_level > target_level {
338                    self.enqueue_increase(
339                        neighbor_pos,
340                        current_level,
341                        LightDirectionSet::all(),
342                        flags.with(LightQueueFlags::RECHECK_LEVEL),
343                    );
344                    continue;
345                }
346
347                self.light.set(neighbor_block, 0);
348                if target_level > 0 {
349                    self.enqueue_decrease(
350                        neighbor_pos,
351                        target_level,
352                        LightDirectionSet::all_except_opposite(axis_direction),
353                        flags,
354                    );
355                }
356            }
357        }
358
359        self.perform_light_increase();
360    }
361
362    fn target_level(
363        propagated_level: u8,
364        source_state: Option<BlockStateId>,
365        target_state: BlockStateId,
366        direction: Direction,
367    ) -> Option<(u8, LightQueueFlags)> {
368        let source_state = match source_state {
369            Some(source_state) => source_state,
370            None => Self::air(),
371        };
372        let opacity = get_light_block_into(
373            source_state,
374            target_state,
375            direction,
376            get_light_opacity(target_state),
377        );
378        if opacity == LIGHT_BLOCKED || opacity >= propagated_level {
379            return None;
380        }
381
382        Some((propagated_level - opacity, Self::shape_flags(target_state)))
383    }
384
385    fn target_level_saturating(
386        propagated_level: u8,
387        source_state: Option<BlockStateId>,
388        target_state: BlockStateId,
389        direction: Direction,
390    ) -> Option<(u8, LightQueueFlags)> {
391        let source_state = match source_state {
392            Some(source_state) => source_state,
393            None => Self::air(),
394        };
395        let opacity = get_light_block_into(
396            source_state,
397            target_state,
398            direction,
399            get_light_opacity(target_state),
400        );
401        if opacity == LIGHT_BLOCKED {
402            return None;
403        }
404
405        Some((
406            propagated_level.saturating_sub(opacity),
407            Self::shape_flags(target_state),
408        ))
409    }
410
411    fn cached_block_from_entry(&self, entry: PackedLightQueueEntry) -> Option<CachedLightBlock> {
412        self.layout.cached_block_from_packed(entry.block_pos())
413    }
414
415    pub(super) fn enqueue_decrease(
416        &mut self,
417        block_pos: BlockPos,
418        level: u8,
419        directions: LightDirectionSet,
420        flags: LightQueueFlags,
421    ) -> Option<PackedLightQueueEntry> {
422        let packed_pos = self.layout.encode_block_pos(block_pos)?;
423        let entry = PackedLightQueueEntry::from_parts(packed_pos, level, directions, flags);
424        self.queues.enqueue_decrease(entry);
425        Some(entry)
426    }
427
428    pub(super) fn enqueue_increase(
429        &mut self,
430        block_pos: BlockPos,
431        level: u8,
432        directions: LightDirectionSet,
433        flags: LightQueueFlags,
434    ) -> Option<PackedLightQueueEntry> {
435        let packed_pos = self.layout.encode_block_pos(block_pos)?;
436        let entry = PackedLightQueueEntry::from_parts(packed_pos, level, directions, flags);
437        self.queues.enqueue_increase(entry);
438        Some(entry)
439    }
440
441    pub(super) fn block_state(&self, block_pos: BlockPos) -> BlockStateId {
442        let Some(cached_block) = self.layout.cached_block(block_pos) else {
443            return Self::air();
444        };
445        self.sections.get_block_state(cached_block)
446    }
447
448    pub(super) const fn current_edge_scan(
449        chunk_pos: ChunkPos,
450        direction: LightAxisDirection,
451    ) -> (i32, i32, i32, i32) {
452        let (offset_x, _, offset_z) = direction.offset();
453        if offset_x != 0 {
454            let start_x = if offset_x < 0 {
455                chunk_pos.0.x << 4
456            } else {
457                (chunk_pos.0.x << 4) | 15
458            };
459            return (0, 1, start_x, chunk_pos.0.y << 4);
460        }
461
462        let start_z = if offset_z < 0 {
463            chunk_pos.0.y << 4
464        } else {
465            (chunk_pos.0.y << 4) | 15
466        };
467        (1, 0, chunk_pos.0.x << 4, start_z)
468    }
469
470    const fn neighbor_edge_scan(
471        chunk_pos: ChunkPos,
472        direction: LightAxisDirection,
473    ) -> (i32, i32, i32, i32) {
474        let (offset_x, _, offset_z) = direction.offset();
475        if offset_x != 0 {
476            let start_x = if offset_x < 0 {
477                (chunk_pos.0.x << 4) - 1
478            } else {
479                (chunk_pos.0.x << 4) + 16
480            };
481            return (0, 1, start_x, chunk_pos.0.y << 4);
482        }
483
484        let start_z = if offset_z < 0 {
485            (chunk_pos.0.y << 4) - 1
486        } else {
487            (chunk_pos.0.y << 4) + 16
488        };
489        (1, 0, chunk_pos.0.x << 4, start_z)
490    }
491
492    const fn block_pos_from_local_index(
493        chunk_offset_x: i32,
494        chunk_offset_y: i32,
495        chunk_offset_z: i32,
496        local_index: usize,
497    ) -> BlockPos {
498        BlockPos::new(
499            chunk_offset_x | (local_index & 15) as i32,
500            chunk_offset_y | (local_index >> 8) as i32,
501            chunk_offset_z | ((local_index >> 4) & 15) as i32,
502        )
503    }
504
505    pub(super) fn shape_flags(block_state: BlockStateId) -> LightQueueFlags {
506        if light_occlusion_shape(block_state).is_empty() {
507            LightQueueFlags::EMPTY
508        } else {
509            LightQueueFlags::EMPTY.with(LightQueueFlags::HAS_SIDED_TRANSPARENT_BLOCKS)
510        }
511    }
512
513    pub(super) const fn offset(block_pos: BlockPos, direction: LightAxisDirection) -> BlockPos {
514        let (dx, dy, dz) = direction.offset();
515        block_pos.offset(dx, dy, dz)
516    }
517
518    pub(super) fn air() -> BlockStateId {
519        vanilla_blocks::AIR.default_state()
520    }
521}