steel_core/chunk/light/
work_gate.rs1use std::sync::Arc;
4
5use steel_utils::{ChunkPos, locks::SyncMutex};
6use tokio::sync::Notify;
7
8use super::LIGHT_CACHE_RADIUS;
9
10const LIGHT_WORK_CENTER_EXCLUSION_RADIUS: i32 = LIGHT_CACHE_RADIUS * 2;
11
12#[derive(Debug)]
20pub(crate) struct LightWorkWindowGate {
21 active_centers: SyncMutex<Vec<ChunkPos>>,
22 released: Notify,
23}
24
25#[derive(Debug)]
27pub(crate) struct LightWorkWindowReservation {
28 gate: Arc<LightWorkWindowGate>,
29 center: ChunkPos,
30}
31
32impl LightWorkWindowGate {
33 #[must_use]
35 pub(crate) fn new() -> Self {
36 Self {
37 active_centers: SyncMutex::new(Vec::new()),
38 released: Notify::new(),
39 }
40 }
41
42 pub(crate) async fn reserve_centered(
44 self: &Arc<Self>,
45 center: ChunkPos,
46 ) -> LightWorkWindowReservation {
47 loop {
48 let released = self.released.notified();
49 if let Some(reservation) = self.try_reserve_centered(center) {
50 return reservation;
51 }
52 released.await;
53 }
54 }
55
56 pub(crate) fn try_reserve_centered(
58 self: &Arc<Self>,
59 center: ChunkPos,
60 ) -> Option<LightWorkWindowReservation> {
61 let mut active_centers = self.active_centers.lock();
62 if active_centers
63 .iter()
64 .any(|&active| Self::windows_overlap(center, active))
65 {
66 return None;
67 }
68
69 active_centers.push(center);
70 Some(LightWorkWindowReservation {
71 gate: Arc::clone(self),
72 center,
73 })
74 }
75
76 const fn windows_overlap(left: ChunkPos, right: ChunkPos) -> bool {
77 let dx = left.0.x.abs_diff(right.0.x);
78 let dz = left.0.y.abs_diff(right.0.y);
79 dx <= LIGHT_WORK_CENTER_EXCLUSION_RADIUS as u32
80 && dz <= LIGHT_WORK_CENTER_EXCLUSION_RADIUS as u32
81 }
82}
83
84impl Default for LightWorkWindowGate {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl Drop for LightWorkWindowReservation {
91 fn drop(&mut self) {
92 let removed = {
93 let mut active_centers = self.gate.active_centers.lock();
94 if let Some(index) = active_centers
95 .iter()
96 .position(|¢er| center == self.center)
97 {
98 active_centers.swap_remove(index);
99 true
100 } else {
101 false
102 }
103 };
104
105 debug_assert!(removed, "light work reservation missing active center");
106 if removed {
107 self.gate.released.notify_waiters();
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn overlapping_windows_cannot_be_reserved_together() {
118 let gate = Arc::new(LightWorkWindowGate::new());
119 let _first = gate
120 .try_reserve_centered(ChunkPos::new(0, 0))
121 .expect("first light window should reserve");
122
123 assert!(gate.try_reserve_centered(ChunkPos::new(1, 0)).is_none());
124 assert!(gate.try_reserve_centered(ChunkPos::new(4, 4)).is_none());
125 }
126
127 #[test]
128 fn non_overlapping_windows_can_be_reserved_together() {
129 let gate = Arc::new(LightWorkWindowGate::new());
130 let _first = gate
131 .try_reserve_centered(ChunkPos::new(0, 0))
132 .expect("first light window should reserve");
133
134 assert!(gate.try_reserve_centered(ChunkPos::new(5, 0)).is_some());
135 assert!(gate.try_reserve_centered(ChunkPos::new(0, 5)).is_some());
136 }
137
138 #[test]
139 fn dropping_reservation_releases_window() {
140 let gate = Arc::new(LightWorkWindowGate::new());
141 let first = gate
142 .try_reserve_centered(ChunkPos::new(0, 0))
143 .expect("first light window should reserve");
144
145 assert!(gate.try_reserve_centered(ChunkPos::new(1, 0)).is_none());
146
147 drop(first);
148 assert!(gate.try_reserve_centered(ChunkPos::new(1, 0)).is_some());
149 }
150}