1use alloc::collections::BTreeSet;
4use core::fmt;
5use euclid::Vector3D;
6
7use hashbrown::hash_map::Entry;
8use hashbrown::HashMap as HbHashMap;
9
10use crate::math::{Cube, GridAab, GridCoordinate, GridIter, GridPoint};
11use crate::space::light::PackedLightScalar;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub(crate) struct LightUpdateRequest {
16 pub(crate) priority: Priority,
17 pub(crate) cube: Cube,
18}
19
20impl LightUpdateRequest {
21 fn fallback_priority(&self) -> GridCoordinate {
26 const COORD_OFFSET: GridCoordinate = 0;
27
28 let cube = GridPoint::from(self.cube);
29
30 let bits = cube.to_vector().map(|c| c.rem_euclid(2) == 0);
33 #[rustfmt::skip]
34 let boost = match bits {
35 Vector3D {x: false, y: false, z: false, _unit } => 1_000_000,
36 Vector3D {x: true, y: true, z: true, _unit } => 900_000,
37 Vector3D {x: true, y: false, z: true, _unit } => 500_000,
39 Vector3D {x: true, y: true, z: false, _unit } => 400_000,
40 Vector3D {x: true, y: false, z: false, _unit } => 300_000,
41 Vector3D {x: false, y: false, z: true, _unit } => 200_000,
42 Vector3D {x: false, y: true, z: false, _unit } => 100_000,
43 Vector3D {x: false, y: true, z: true, _unit } => 0,
44 };
45
46 let GridPoint { x, y, z, _unit } = cube.map(|c| if c > 0 { -c } else { c } + COORD_OFFSET);
47 x.saturating_add(y).saturating_add(z).saturating_add(boost)
48 }
49}
50
51impl Ord for LightUpdateRequest {
52 fn cmp(&self, other: &LightUpdateRequest) -> core::cmp::Ordering {
53 self.priority
54 .cmp(&other.priority)
55 .then_with(|| self.fallback_priority().cmp(&other.fallback_priority()))
56 .then_with(|| self.cube.x.cmp(&other.cube.x))
59 .then_with(|| self.cube.y.cmp(&other.cube.y))
60 .then_with(|| self.cube.z.cmp(&other.cube.z))
61 }
62}
63
64impl PartialOrd for LightUpdateRequest {
65 fn partial_cmp(&self, other: &LightUpdateRequest) -> Option<core::cmp::Ordering> {
66 Some(self.cmp(other))
67 }
68}
69
70#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
72pub(crate) struct Priority(PackedLightScalar);
73impl Priority {
74 pub const NEWLY_VISIBLE: Self = Self(250);
78
79 pub const UNINIT: Self = Self(210);
81
82 pub const ESTIMATED: Self = Self(200);
84
85 pub const MIN: Self = Self(0);
90
91 pub fn from_difference(d: PackedLightScalar) -> Self {
92 Self(d / 2 + 1)
94 }
95}
96
97impl fmt::Debug for Priority {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 if *self == Self::NEWLY_VISIBLE {
100 write!(f, "NEWLY_VISIBLE")
101 } else if *self == Self::UNINIT {
102 write!(f, "UNINIT")
103 } else if *self == Self::ESTIMATED {
104 write!(f, "ESTIMATED")
105 } else {
106 fmt::Display::fmt(&self.0, f)
107 }
108 }
109}
110
111pub(crate) struct LightUpdateQueue {
114 queue: BTreeSet<LightUpdateRequest>,
117
118 table: HbHashMap<Cube, Priority>,
121
122 sweep: Option<GridIter>,
125
126 sweep_priority: Priority,
128
129 sweep_again: bool,
131}
132
133impl LightUpdateQueue {
134 pub fn new() -> Self {
135 Self {
136 queue: BTreeSet::new(),
137 table: HbHashMap::new(),
138 sweep: None,
139 sweep_priority: Priority::MIN,
140 sweep_again: false,
141 }
142 }
143
144 #[inline]
145 pub fn contains(&self, cube: Cube) -> bool {
146 self.table.contains_key(&cube)
147 || self
148 .sweep
149 .as_ref()
150 .is_some_and(|sweep| sweep.contains_cube(cube))
151 }
152
153 #[inline]
155 pub(crate) fn insert(&mut self, request: LightUpdateRequest) {
156 match self.table.entry(request.cube) {
157 Entry::Occupied(mut e) => {
158 let existing_priority = *e.get();
159 if request.priority > existing_priority {
160 let removed = self.queue.remove(&LightUpdateRequest {
161 cube: request.cube,
162 priority: existing_priority,
163 });
164 debug_assert!(removed);
165 e.insert(request.priority);
166 self.queue.insert(request);
167 }
168 }
169 Entry::Vacant(e) => {
170 e.insert(request.priority);
171 self.queue.insert(request);
172 }
173 }
174 }
175
176 pub(crate) fn sweep(&mut self, bounds: GridAab, priority: Priority) {
179 if self
180 .sweep
181 .as_ref()
182 .is_some_and(|it| it.bounds().contains_box(bounds))
183 && self.sweep_priority >= priority
184 {
185 self.sweep_again = true;
186 self.sweep_priority = Ord::max(self.sweep_priority, priority);
187 } else if self.sweep.is_some() {
188 self.sweep = Some(bounds.interior_iter());
191 self.sweep_priority = Ord::max(self.sweep_priority, priority);
192 } else {
193 self.sweep = Some(bounds.interior_iter());
195 self.sweep_priority = priority;
196 }
197 }
198
199 pub fn remove(&mut self, cube: Cube) -> bool {
203 if let Some(priority) = self.table.remove(&cube) {
204 let q_removed = self.queue.remove(&LightUpdateRequest { priority, cube });
205 debug_assert!(q_removed);
206 true
207 } else {
208 false
209 }
210 }
211
212 #[inline]
214 #[mutants::skip] pub fn pop(&mut self) -> Option<LightUpdateRequest> {
216 if let Some(sweep) = &mut self.sweep {
217 if peek_priority(&self.queue).is_none_or(|p| self.sweep_priority > p) {
218 if let Some(cube) = sweep.next() {
219 return Some(LightUpdateRequest {
220 cube,
221 priority: self.sweep_priority,
222 });
223 } else {
224 self.sweep = None;
226 self.sweep_priority = Priority::MIN;
227 }
228 }
229 }
230
231 let result = self.queue.pop_last();
232 if let Some(request) = result {
233 let removed = self.table.remove(&request.cube);
234 debug_assert!(removed.is_some());
235 }
236 result
237 }
238
239 pub fn clear(&mut self) {
240 self.queue.clear();
241 self.table.clear();
242 self.sweep = None;
243 self.sweep_priority = Priority::MIN;
244 self.sweep_again = false;
245 }
246
247 #[inline]
249 #[mutants::skip] pub fn len(&self) -> usize {
251 let sweep_items = match &self.sweep {
252 Some(sweep) => {
253 sweep.len()
254 + if self.sweep_again {
255 sweep.bounds().volume().unwrap_or(usize::MAX)
256 } else {
257 0
258 }
259 }
260 None => 0,
261 };
262 self.queue.len() + sweep_items
263 }
264
265 #[inline]
266 pub fn peek_priority(&self) -> Priority {
267 peek_priority(&self.queue).unwrap_or(Priority::MIN)
268 }
269}
270
271#[inline]
272fn peek_priority(queue: &BTreeSet<LightUpdateRequest>) -> Option<Priority> {
273 queue.last().copied().map(|r| r.priority)
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use alloc::vec::Vec;
280
281 fn drain(queue: &mut LightUpdateQueue) -> Vec<LightUpdateRequest> {
282 Vec::from_iter(std::iter::from_fn(|| queue.pop()))
283 }
284
285 fn r(cube: [GridCoordinate; 3], priority: PackedLightScalar) -> LightUpdateRequest {
286 let priority = Priority(priority);
287 LightUpdateRequest {
288 cube: Cube::from(cube),
289 priority,
290 }
291 }
292
293 #[test]
294 fn priority_relations() {
295 let least_special_priority = [
296 Priority::ESTIMATED,
297 Priority::NEWLY_VISIBLE,
298 Priority::UNINIT,
299 ]
300 .into_iter()
301 .min()
302 .unwrap();
303
304 assert!(Priority::MIN < Priority::from_difference(0));
305 assert!(Priority::from_difference(255) < least_special_priority);
306 }
307
308 #[test]
309 fn queue_ordering() {
310 let mut queue = LightUpdateQueue::new();
311 queue.insert(r([0, 0, 0], 1));
312 queue.insert(r([2, 0, 0], 1));
313 queue.insert(r([1, 0, 0], 1));
314 queue.insert(r([3, 0, 0], 1));
315 queue.insert(r([4, 0, 0], 1));
316 queue.insert(r([3, 0, 0], 1)); queue.insert(r([0, 0, 2], 200));
318 queue.insert(r([0, 0, 1], 100));
319
320 assert_eq!(queue.len(), 7);
321 assert_eq!(
322 drain(&mut queue),
323 vec![
324 r([0, 0, 2], 200),
326 r([0, 0, 1], 100),
327 r([0, 0, 0], 1),
329 r([2, 0, 0], 1),
330 r([4, 0, 0], 1),
331 r([1, 0, 0], 1),
332 r([3, 0, 0], 1),
333 ]
334 );
335 assert_eq!(queue.len(), 0);
336 }
337
338 #[test]
339 fn sweep_basic() {
340 let mut queue = LightUpdateQueue::new();
341
342 queue.insert(LightUpdateRequest {
343 priority: Priority(101),
344 cube: Cube::new(0, 101, 0),
345 });
346 queue.insert(LightUpdateRequest {
347 priority: Priority(100),
348 cube: Cube::new(0, 100, 0),
349 });
350 queue.insert(LightUpdateRequest {
351 priority: Priority(99),
352 cube: Cube::new(0, 99, 0),
353 });
354 queue.sweep(
355 GridAab::from_lower_upper([0, 0, 0], [3, 1, 1]),
356 Priority(100),
357 );
358
359 assert_eq!(queue.len(), 6);
360 assert_eq!(
361 drain(&mut queue),
362 vec![
363 r([0, 101, 0], 101),
365 r([0, 100, 0], 100),
367 r([0, 0, 0], 100),
371 r([1, 0, 0], 100),
372 r([2, 0, 0], 100),
373 r([0, 99, 0], 99),
375 ]
376 )
377 }
378
379 #[test]
380 fn sweep_then_clear() {
381 let mut queue = LightUpdateQueue::new();
382 queue.sweep(
383 GridAab::from_lower_upper([0, 0, 0], [3, 1, 1]),
384 Priority(100),
385 );
386
387 queue.clear();
388
389 assert_eq!(queue.len(), 0);
390 assert_eq!(queue.pop(), None);
391 }
392
393 }