steel_core/chunk/light/sky_propagation/
mod.rs1use steel_registry::{blocks::block_state_ext::BlockStateExt, vanilla_blocks};
2use steel_utils::{BlockPos, BlockStateId, ChunkPos, Direction, SectionPos};
3
4use super::{
5 CachedLightBlock, LIGHT_BLOCKED, LightAxisDirection, LightCacheLayout, LightDirectionSet,
6 LightLayer, LightLayerEdit, LightQueueFlags, LightSectionEmptinessChange,
7 LightSectionReadCache, LightWorkset, MAX_LIGHT_LEVEL, PackedLightPropagationQueues,
8 PackedLightQueueEntry, PooledPackedLightQueues, get_light_block_into, get_light_opacity,
9 light_occlusion_shape,
10};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum SkyLightPropagationContextError {
15 WrongLayer {
17 layer: LightLayer,
19 },
20 LayoutMismatch {
22 section_layout: Box<LightCacheLayout>,
24 light_layout: Box<LightCacheLayout>,
26 },
27 MissingCenterChunk {
29 chunk_pos: ChunkPos,
31 },
32}
33
34impl SkyLightPropagationContextError {
35 fn layout_mismatch(section_layout: LightCacheLayout, light_layout: LightCacheLayout) -> Self {
36 Self::LayoutMismatch {
37 section_layout: Box::new(section_layout),
38 light_layout: Box::new(light_layout),
39 }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct SkyLightUpdateResult {
46 pub updated_sections: Vec<SectionPos>,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum SkyLightChunkEdgeChecks {
53 Required,
55 Skipped,
57}
58
59pub fn propagate_sky_light_chunk_without_edge_checks(
61 workset: &LightWorkset,
62) -> Result<SkyLightUpdateResult, SkyLightPropagationContextError> {
63 propagate_sky_light_chunk(workset, SkyLightChunkEdgeChecks::Skipped)
64}
65
66pub fn propagate_sky_light_chunk(
73 workset: &LightWorkset,
74 edge_checks: SkyLightChunkEdgeChecks,
75) -> Result<SkyLightUpdateResult, SkyLightPropagationContextError> {
76 workset.with_chunk_read_cache(|chunk_cache| {
77 let layout = chunk_cache.layout();
78 let Some(center_slot) = layout.cached_chunk(layout.center_chunk()) else {
79 return Err(SkyLightPropagationContextError::MissingCenterChunk {
80 chunk_pos: layout.center_chunk(),
81 });
82 };
83 if chunk_cache.chunk(center_slot).is_none() {
84 return Err(SkyLightPropagationContextError::MissingCenterChunk {
85 chunk_pos: layout.center_chunk(),
86 });
87 }
88
89 chunk_cache.with_section_read_cache(|section_cache| {
90 chunk_cache.with_light_edit(LightLayer::Sky, |mut light_edit| {
91 let mut queues = PooledPackedLightQueues::take();
92
93 {
94 let mut context = SkyLightPropagationContext::new(
95 section_cache,
96 &mut light_edit,
97 &mut queues,
98 )?;
99 context.reset_center_chunk_sections();
100 context.handle_unlit_empty_section_changes(layout.center_chunk());
101 context.light_chunk(layout.center_chunk(), edge_checks);
102 if edge_checks == SkyLightChunkEdgeChecks::Required {
103 context.deinit_and_lazy_init_empty_sections(layout.center_chunk(), true);
104 }
105 }
106
107 let mut updated_sections = Vec::new();
108 light_edit.commit(None, |section_pos| updated_sections.push(section_pos));
109 Ok(SkyLightUpdateResult { updated_sections })
110 })
111 })
112 })
113}
114
115pub fn force_load_sky_light_chunk(
117 workset: &LightWorkset,
118) -> Result<SkyLightUpdateResult, SkyLightPropagationContextError> {
119 workset.with_chunk_read_cache(|chunk_cache| {
120 let layout = ensure_center_chunk(chunk_cache)?;
121
122 chunk_cache.with_section_read_cache(|section_cache| {
123 chunk_cache.with_light_edit(LightLayer::Sky, |mut light_edit| {
124 let mut queues = PooledPackedLightQueues::take();
125
126 {
127 let mut context = SkyLightPropagationContext::new(
128 section_cache,
129 &mut light_edit,
130 &mut queues,
131 )?;
132 context.handle_loaded_empty_section_changes(layout.center_chunk());
133 }
134
135 let mut updated_sections = Vec::new();
136 light_edit.commit(None, |section_pos| updated_sections.push(section_pos));
137 Ok(SkyLightUpdateResult { updated_sections })
138 })
139 })
140 })
141}
142
143pub fn check_sky_light_chunk_edges(
145 workset: &LightWorkset,
146) -> Result<SkyLightUpdateResult, SkyLightPropagationContextError> {
147 workset.with_chunk_read_cache(|chunk_cache| {
148 let layout = ensure_center_chunk(chunk_cache)?;
149
150 chunk_cache.with_section_read_cache(|section_cache| {
151 chunk_cache.with_light_edit(LightLayer::Sky, |mut light_edit| {
152 let mut queues = PooledPackedLightQueues::take();
153
154 {
155 let mut context = SkyLightPropagationContext::new(
156 section_cache,
157 &mut light_edit,
158 &mut queues,
159 )?;
160 context.light.rewrite_missing_sections_for_skylight();
161 for section_y in (layout.range().min_section_y()
162 ..layout.range().max_section_y_exclusive())
163 .rev()
164 {
165 context.check_missing_section(layout.center_chunk(), section_y, true);
166 }
167 context.check_chunk_edges(
168 layout.center_chunk(),
169 layout.range().min_section_y(),
170 layout.range().max_section_y_exclusive() - 1,
171 );
172 }
173
174 let mut updated_sections = Vec::new();
175 light_edit.commit(None, |section_pos| updated_sections.push(section_pos));
176 Ok(SkyLightUpdateResult { updated_sections })
177 })
178 })
179 })
180}
181
182pub fn load_sky_light_chunk(
184 workset: &LightWorkset,
185) -> Result<SkyLightUpdateResult, SkyLightPropagationContextError> {
186 let mut updated_sections = force_load_sky_light_chunk(workset)?.updated_sections;
187 updated_sections.extend(check_sky_light_chunk_edges(workset)?.updated_sections);
188 Ok(SkyLightUpdateResult { updated_sections })
189}
190
191fn ensure_center_chunk(
192 chunk_cache: &super::LightChunkReadCache<'_>,
193) -> Result<LightCacheLayout, SkyLightPropagationContextError> {
194 let layout = chunk_cache.layout();
195 let Some(center_slot) = layout.cached_chunk(layout.center_chunk()) else {
196 return Err(SkyLightPropagationContextError::MissingCenterChunk {
197 chunk_pos: layout.center_chunk(),
198 });
199 };
200 if chunk_cache.chunk(center_slot).is_none() {
201 return Err(SkyLightPropagationContextError::MissingCenterChunk {
202 chunk_pos: layout.center_chunk(),
203 });
204 }
205
206 Ok(layout)
207}
208
209pub fn propagate_sky_light_changes(
211 workset: &LightWorkset,
212 positions: impl IntoIterator<Item = BlockPos>,
213) -> Result<SkyLightUpdateResult, SkyLightPropagationContextError> {
214 propagate_sky_light_changes_with_empty_sections(workset, positions, [])
215}
216
217pub fn propagate_sky_light_changes_with_empty_sections(
219 workset: &LightWorkset,
220 positions: impl IntoIterator<Item = BlockPos>,
221 empty_sections: impl IntoIterator<Item = LightSectionEmptinessChange>,
222) -> Result<SkyLightUpdateResult, SkyLightPropagationContextError> {
223 let positions = positions.into_iter().collect::<Vec<_>>();
224 let empty_sections = empty_sections.into_iter().collect::<Vec<_>>();
225
226 workset.with_chunk_read_cache(|chunk_cache| {
227 let layout = chunk_cache.layout();
228 let Some(center_slot) = layout.cached_chunk(layout.center_chunk()) else {
230 return Ok(SkyLightUpdateResult {
231 updated_sections: Vec::new(),
232 });
233 };
234 if chunk_cache.chunk(center_slot).is_none() {
235 return Ok(SkyLightUpdateResult {
236 updated_sections: Vec::new(),
237 });
238 }
239
240 chunk_cache.with_section_read_cache(|section_cache| {
241 chunk_cache.with_light_edit(LightLayer::Sky, |mut light_edit| {
242 let mut queues = PooledPackedLightQueues::take();
243
244 {
245 let mut context = SkyLightPropagationContext::new(
246 section_cache,
247 &mut light_edit,
248 &mut queues,
249 )?;
250 let mut changed_chunks = Vec::new();
251 for change in &empty_sections {
252 let chunk_pos =
253 ChunkPos::new(change.section_pos.x(), change.section_pos.z());
254 context
255 .light
256 .set_section_empty(change.section_pos, change.empty);
257 if !changed_chunks.contains(&chunk_pos) {
258 changed_chunks.push(chunk_pos);
259 }
260 }
261 for chunk_pos in changed_chunks {
262 context.deinit_and_lazy_init_empty_sections(chunk_pos, false);
263 }
264 context.propagate_block_changes(&positions);
265 }
266
267 let mut updated_sections = Vec::new();
268 light_edit.commit(None, |section_pos| updated_sections.push(section_pos));
269 Ok(SkyLightUpdateResult { updated_sections })
270 })
271 })
272 })
273}
274
275mod context;
276
277pub use context::SkyLightPropagationContext;
278
279mod algorithms;
280mod queue_engine;
281
282#[cfg(test)]
283mod tests;