steel_core/chunk/chunk_map/
light_update_state.rs1use 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 ¢er 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(|(§ion_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}