steel_core/worldgen/carver/
mask.rs1use std::cell::RefCell;
9use std::mem;
10
11thread_local! {
12 static CARVING_MASK_WORDS: RefCell<Vec<u64>> = const { RefCell::new(Vec::new()) };
13}
14
15#[derive(Debug, Clone)]
17pub struct CarvingMask {
18 min_y: i32,
19 height: i32,
20 bits: Vec<u64>,
21}
22
23impl CarvingMask {
24 #[must_use]
30 pub fn new(min_y: i32, max_y: i32) -> Self {
31 assert!(min_y <= max_y, "carving mask range must not be empty");
32 let height = max_y - min_y + 1;
33 let total_bits = (256 * height) as usize;
34 let lanes = total_bits.div_ceil(64);
35 let mut bits = CARVING_MASK_WORDS.with(|cache| mem::take(&mut *cache.borrow_mut()));
36 bits.resize(lanes, 0);
37 Self {
38 min_y,
39 height,
40 bits,
41 }
42 }
43
44 #[must_use]
49 pub fn for_worldgen_context(min_y: i32, height: i32) -> Self {
50 Self::new(min_y + 1, min_y + height - 8)
51 }
52
53 #[inline]
55 const fn index(&self, x: i32, y: i32, z: i32) -> usize {
56 let column = ((z & 15) + ((x & 15) << 4)) as usize;
57 (y - self.min_y) as usize + column * self.height as usize
58 }
59
60 #[inline]
62 pub fn set(&mut self, x: i32, y: i32, z: i32) {
63 let idx = self.index(x, y, z);
64 let lane = idx / 64;
65 let bit = idx % 64;
66 self.bits[lane] |= 1u64 << bit;
67 }
68
69 #[inline]
74 pub fn set_if_unset(&mut self, x: i32, y: i32, z: i32) -> bool {
75 let idx = self.index(x, y, z);
76 let lane = idx / 64;
77 let bit = 1u64 << (idx % 64);
78 if self.bits[lane] & bit != 0 {
79 return false;
80 }
81 self.bits[lane] |= bit;
82 true
83 }
84
85 #[inline]
87 #[must_use]
88 pub fn get(&self, x: i32, y: i32, z: i32) -> bool {
89 let idx = self.index(x, y, z);
90 let lane = idx / 64;
91 let bit = idx % 64;
92 (self.bits[lane] >> bit) & 1 != 0
93 }
94
95 #[must_use]
97 pub fn is_empty(&self) -> bool {
98 self.bits.iter().all(|&word| word == 0)
99 }
100
101 pub fn visit(&self, mut visitor: impl FnMut(i32, i32, i32, i32)) {
103 let Some(mut start_index) = self.next_set_bit(0) else {
104 return;
105 };
106
107 loop {
108 let end_index = self.next_clear_bit(start_index) - 1;
109 self.visit_segment(&mut visitor, start_index, end_index);
110
111 let Some(next_start) = self.next_set_bit(end_index + 1) else {
112 return;
113 };
114 start_index = next_start;
115 }
116 }
117
118 fn visit_segment(
119 &self,
120 visitor: &mut impl FnMut(i32, i32, i32, i32),
121 start_index: usize,
122 end_index: usize,
123 ) {
124 let height = self.height as usize;
125 for column in start_index / height..=end_index / height {
126 let column_base = column * height;
127 let bottom_y = (start_index.saturating_sub(column_base)).min(height - 1) as i32;
128 let top_y = (end_index.saturating_sub(column_base)).min(height - 1) as i32;
129 visitor(
130 ((column >> 4) & 15) as i32,
131 (column & 15) as i32,
132 self.min_y + bottom_y,
133 self.min_y + top_y,
134 );
135 }
136 }
137
138 fn next_set_bit(&self, from: usize) -> Option<usize> {
139 let mut lane = from / 64;
140 if lane >= self.bits.len() {
141 return None;
142 }
143 let mut word = self.bits[lane] & (u64::MAX << (from % 64));
144 loop {
145 if word != 0 {
146 return Some(lane * 64 + word.trailing_zeros() as usize);
147 }
148 lane += 1;
149 word = *self.bits.get(lane)?;
150 }
151 }
152
153 fn next_clear_bit(&self, from: usize) -> usize {
154 let mut lane = from / 64;
155 if lane >= self.bits.len() {
156 return from;
157 }
158 let mut word = !self.bits[lane] & (u64::MAX << (from % 64));
159 loop {
160 if word != 0 {
161 return lane * 64 + word.trailing_zeros() as usize;
162 }
163 lane += 1;
164 let Some(next_word) = self.bits.get(lane) else {
165 return self.bits.len() * 64;
166 };
167 word = !next_word;
168 }
169 }
170
171 #[must_use]
173 pub const fn min_y(&self) -> i32 {
174 self.min_y
175 }
176
177 #[must_use]
179 pub const fn max_y(&self) -> i32 {
180 self.min_y + self.height - 1
181 }
182}
183
184impl Drop for CarvingMask {
185 fn drop(&mut self) {
186 CARVING_MASK_WORDS.with(|cache| {
187 let mut cached = cache.borrow_mut();
188 if self.bits.capacity() > cached.capacity() {
189 self.bits.clear();
190 mem::swap(&mut self.bits, &mut *cached);
191 }
192 });
193 }
194}
195
196#[cfg(test)]
197mod test {
198 use steel_worldgen::density_functions::{
199 nether::NetherNoiseSettings, overworld::OverworldNoiseSettings,
200 };
201
202 use super::*;
203
204 const OVERWORLD_CARVER_MIN_Y: i32 = OverworldNoiseSettings::MIN_Y + 1;
205 const OVERWORLD_CARVER_MAX_Y: i32 =
206 OverworldNoiseSettings::MIN_Y + OverworldNoiseSettings::HEIGHT - 8;
207 const OVERWORLD_CARVER_HEIGHT: usize = OverworldNoiseSettings::HEIGHT as usize - 8;
208
209 #[test]
210 fn set_and_get_roundtrip() {
211 let mut mask = CarvingMask::new(OVERWORLD_CARVER_MIN_Y, OVERWORLD_CARVER_MAX_Y);
212 assert!(!mask.get(5, 10, 7));
213 mask.set(5, 10, 7);
214 assert!(mask.get(5, 10, 7));
215 assert!(!mask.get(4, 10, 7));
217 assert!(!mask.get(5, 11, 7));
218 assert!(!mask.get(5, 10, 8));
219 }
220
221 #[test]
222 fn reused_mask_is_clear_across_height_ranges() {
223 let mut first = CarvingMask::new(OVERWORLD_CARVER_MIN_Y, OVERWORLD_CARVER_MAX_Y);
224 first.set(5, 10, 7);
225 drop(first);
226
227 let mut nether = CarvingMask::for_worldgen_context(
228 NetherNoiseSettings::MIN_Y,
229 NetherNoiseSettings::HEIGHT,
230 );
231 assert!(nether.is_empty());
232 nether.set(5, 10, 7);
233 drop(nether);
234
235 let reused = CarvingMask::new(OVERWORLD_CARVER_MIN_Y, OVERWORLD_CARVER_MAX_Y);
236 assert!(reused.is_empty());
237 assert!(!reused.get(5, 10, 7));
238 }
239
240 #[test]
241 fn set_if_unset_reports_first_visit() {
242 let mut mask = CarvingMask::new(OVERWORLD_CARVER_MIN_Y, OVERWORLD_CARVER_MAX_Y);
243 assert!(mask.set_if_unset(5, 10, 7));
244 assert!(!mask.set_if_unset(5, 10, 7));
245 assert!(mask.get(5, 10, 7));
246 }
247
248 #[test]
249 fn indexing_matches_vanilla_layout() {
250 let mask = CarvingMask::new(OVERWORLD_CARVER_MIN_Y, OVERWORLD_CARVER_MAX_Y);
251 assert_eq!(mask.index(0, OVERWORLD_CARVER_MIN_Y, 0), 0);
253 assert_eq!(
255 mask.index(15, OVERWORLD_CARVER_MIN_Y, 0),
256 240 * OVERWORLD_CARVER_HEIGHT
257 );
258 assert_eq!(
260 mask.index(0, OVERWORLD_CARVER_MIN_Y, 1),
261 OVERWORLD_CARVER_HEIGHT
262 );
263 assert_eq!(mask.index(0, OVERWORLD_CARVER_MIN_Y + 1, 0), 1);
265 }
266
267 #[test]
268 fn x_and_z_are_masked_to_chunk_local() {
269 let mut mask = CarvingMask::new(OVERWORLD_CARVER_MIN_Y, OVERWORLD_CARVER_MAX_Y);
270 mask.set(17, 0, 18);
272 assert!(mask.get(1, 0, 2));
273 assert!(mask.get(17, 0, 18));
274 }
275
276 #[test]
277 fn worldgen_context_excludes_protected_edges() {
278 let mask = CarvingMask::for_worldgen_context(
279 OverworldNoiseSettings::MIN_Y,
280 OverworldNoiseSettings::HEIGHT,
281 );
282 assert_eq!(mask.min_y(), OverworldNoiseSettings::MIN_Y + 1);
283 assert_eq!(mask.max_y(), OVERWORLD_CARVER_MAX_Y);
284
285 let nether = CarvingMask::for_worldgen_context(
286 NetherNoiseSettings::MIN_Y,
287 NetherNoiseSettings::HEIGHT,
288 );
289 assert_eq!(nether.min_y(), NetherNoiseSettings::MIN_Y + 1);
290 assert_eq!(
291 nether.max_y(),
292 NetherNoiseSettings::MIN_Y + NetherNoiseSettings::HEIGHT - 8
293 );
294 }
295
296 #[test]
297 fn visit_splits_ranges_at_clear_bits_and_columns() {
298 let mut mask = CarvingMask::new(-4, 3);
299 mask.set(0, 3, 0);
300 mask.set(0, -4, 1);
301 mask.set(1, -3, 2);
302 mask.set(1, -2, 2);
303 mask.set(1, 0, 2);
304
305 let mut visited = Vec::new();
306 mask.visit(|x, z, bottom, top| visited.push((x, z, bottom, top)));
307
308 assert_eq!(
309 visited,
310 vec![(0, 0, 3, 3), (0, 1, -4, -4), (1, 2, -3, -2), (1, 2, 0, 0)]
311 );
312 }
313}