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