Skip to main content

steel_core/chunk/chunk_map/
light_update_state.rs

1use super::{
2    BlockPos, ChunkPos, FxHashMap, FxHashSet, LIGHT_CACHE_RADIUS, LightSectionEmptinessChange,
3    Notify, SectionPos, SyncMutex, mem,
4};
5
6#[derive(Debug, Default)]
7pub(super) struct PendingLightUpdates {
8    pub(super) chunks: FxHashMap<ChunkPos, PendingChunkLightUpdates>,
9    pub(super) queued_chunks: Vec<ChunkPos>,
10}
11
12impl PendingLightUpdates {
13    pub(super) fn is_empty(&self) -> bool {
14        self.chunks.is_empty()
15    }
16
17    pub(super) fn next_center(&self) -> Option<ChunkPos> {
18        self.queued_chunks
19            .iter()
20            .copied()
21            .find(|chunk_pos| self.chunks.contains_key(chunk_pos))
22    }
23
24    pub(super) fn next_center_touching_chunk(&self, chunk_pos: ChunkPos) -> Option<ChunkPos> {
25        self.queued_chunks.iter().copied().find(|center| {
26            self.chunks.contains_key(center) && light_update_window_contains(*center, chunk_pos)
27        })
28    }
29
30    pub(super) fn queue_change(
31        &mut self,
32        chunk_pos: ChunkPos,
33        pos: BlockPos,
34        check_block: bool,
35        empty_section_change: Option<LightSectionEmptinessChange>,
36    ) {
37        if !self.chunks.contains_key(&chunk_pos) {
38            self.queued_chunks.push(chunk_pos);
39        }
40
41        let task = self.chunks.entry(chunk_pos).or_default();
42        if check_block {
43            task.changed_positions.insert(pos);
44        }
45        if let Some(change) = empty_section_change {
46            task.changed_sections
47                .insert(change.section_pos, change.empty);
48        }
49    }
50
51    pub(super) fn drain(&mut self) -> Vec<(ChunkPos, PendingChunkLightUpdates)> {
52        let mut chunks = mem::take(&mut self.chunks);
53        let queued_chunks = mem::take(&mut self.queued_chunks);
54        queued_chunks
55            .into_iter()
56            .filter_map(|chunk_pos| chunks.remove(&chunk_pos).map(|task| (chunk_pos, task)))
57            .collect()
58    }
59
60    pub(super) fn drain_center(&mut self, chunk_pos: ChunkPos) -> Option<PendingChunkLightUpdates> {
61        let task = self.chunks.remove(&chunk_pos)?;
62        self.queued_chunks.retain(|&queued| queued != chunk_pos);
63        Some(task)
64    }
65
66    pub(super) fn prepend_drained(&mut self, tasks: Vec<(ChunkPos, PendingChunkLightUpdates)>) {
67        let previous_queued_chunks = mem::take(&mut self.queued_chunks);
68        let mut prepended_chunks = FxHashSet::default();
69
70        for (chunk_pos, task) in tasks {
71            if task.is_empty() {
72                continue;
73            }
74
75            if let Some(existing) = self.chunks.get_mut(&chunk_pos) {
76                existing.merge_older(task);
77            } else {
78                self.chunks.insert(chunk_pos, task);
79            }
80
81            if prepended_chunks.insert(chunk_pos) {
82                self.queued_chunks.push(chunk_pos);
83            }
84        }
85
86        for chunk_pos in previous_queued_chunks {
87            if !prepended_chunks.contains(&chunk_pos) {
88                self.queued_chunks.push(chunk_pos);
89            }
90        }
91    }
92}
93
94#[derive(Debug, Default)]
95pub(super) struct LightUpdateState {
96    pub(super) pending: PendingLightUpdates,
97    pub(super) in_flight_centers: FxHashMap<ChunkPos, usize>,
98}
99
100impl LightUpdateState {
101    #[cfg(test)]
102    pub(super) fn is_idle(&self) -> bool {
103        self.pending.is_empty() && self.in_flight_centers.is_empty()
104    }
105
106    pub(super) fn has_in_flight_updates(&self) -> bool {
107        !self.in_flight_centers.is_empty()
108    }
109
110    pub(super) fn has_in_flight_update_touching_chunk(&self, chunk_pos: ChunkPos) -> bool {
111        self.in_flight_centers
112            .keys()
113            .copied()
114            .any(|center| light_update_window_contains(center, chunk_pos))
115    }
116
117    pub(super) fn track_in_flight(&mut self, centers: &[ChunkPos]) {
118        for &center in centers {
119            *self.in_flight_centers.entry(center).or_default() += 1;
120        }
121    }
122
123    pub(super) fn finish_in_flight(&mut self, centers: &[ChunkPos]) {
124        for center in centers {
125            let Some(count) = self.in_flight_centers.get_mut(center) else {
126                debug_assert!(false, "in-flight light update counter underflow");
127                continue;
128            };
129            *count -= 1;
130            if *count == 0 {
131                self.in_flight_centers.remove(center);
132            }
133        }
134    }
135
136    pub(super) fn touches_chunk(&self, chunk_pos: ChunkPos) -> bool {
137        self.pending
138            .chunks
139            .keys()
140            .copied()
141            .chain(self.in_flight_centers.keys().copied())
142            .any(|center| light_update_window_contains(center, chunk_pos))
143    }
144}
145
146pub(super) struct InFlightLightUpdates<'a> {
147    pub(super) centers: Vec<ChunkPos>,
148    pub(super) light_updates: &'a SyncMutex<LightUpdateState>,
149    pub(super) progress_notify: &'a Notify,
150}
151
152impl Drop for InFlightLightUpdates<'_> {
153    fn drop(&mut self) {
154        {
155            let mut light_updates = self.light_updates.lock();
156            light_updates.finish_in_flight(&self.centers);
157        }
158        self.progress_notify.notify_waiters();
159    }
160}
161
162pub(super) const fn light_update_window_contains(center: ChunkPos, chunk_pos: ChunkPos) -> bool {
163    let dx = center.0.x.abs_diff(chunk_pos.0.x);
164    let dz = center.0.y.abs_diff(chunk_pos.0.y);
165    dx <= LIGHT_CACHE_RADIUS as u32 && dz <= LIGHT_CACHE_RADIUS as u32
166}
167
168#[derive(Debug, Default)]
169pub(super) struct PendingChunkLightUpdates {
170    pub(super) changed_positions: FxHashSet<BlockPos>,
171    pub(super) changed_sections: FxHashMap<SectionPos, bool>,
172}
173
174impl PendingChunkLightUpdates {
175    pub(super) fn is_empty(&self) -> bool {
176        self.changed_positions.is_empty() && self.changed_sections.is_empty()
177    }
178
179    pub(super) fn merge_older(&mut self, older: Self) {
180        self.changed_positions.extend(older.changed_positions);
181        for (section_pos, empty) in older.changed_sections {
182            self.changed_sections.entry(section_pos).or_insert(empty);
183        }
184    }
185
186    pub(super) fn empty_section_changes(&self) -> Vec<LightSectionEmptinessChange> {
187        let mut changes = self
188            .changed_sections
189            .iter()
190            .map(|(&section_pos, &empty)| LightSectionEmptinessChange { section_pos, empty })
191            .collect::<Vec<_>>();
192        changes.sort_by(|left, right| {
193            left.section_pos
194                .x()
195                .cmp(&right.section_pos.x())
196                .then_with(|| left.section_pos.z().cmp(&right.section_pos.z()))
197                .then_with(|| right.section_pos.y().cmp(&left.section_pos.y()))
198        });
199        changes
200    }
201}