steel_worldgen/noise/
preliminary_surface_store.rs1use std::sync::atomic::{AtomicU64, Ordering};
6
7use crate::density::DimensionNoises;
8use crate::noise::aquifer::preliminary_surface_level;
9
10const AXIS_BITS: u32 = 9;
12const AXIS_MASK: i32 = (1 << AXIS_BITS) - 1;
13
14const COORD_BITS: u32 = 24;
16const LEVEL_BITS: u32 = 15;
17const COORD_MASK: u64 = (1 << COORD_BITS) - 1;
18const LEVEL_MASK: u64 = (1 << LEVEL_BITS) - 1;
19const VALID: u64 = 1 << (u64::BITS - 1);
20const COORD_MIN: i32 = -(1 << (COORD_BITS - 1));
21const COORD_MAX: i32 = (1 << (COORD_BITS - 1)) - 1;
22const LEVEL_MIN: i32 = -(1 << (LEVEL_BITS - 1));
23const LEVEL_MAX: i32 = (1 << (LEVEL_BITS - 1)) - 1;
24
25pub struct PreliminarySurfaceStore {
28 slots: Box<[AtomicU64]>,
29}
30
31impl Default for PreliminarySurfaceStore {
32 fn default() -> Self {
33 Self {
34 slots: (0..1usize << (2 * AXIS_BITS))
35 .map(|_| AtomicU64::new(0))
36 .collect(),
37 }
38 }
39}
40
41impl PreliminarySurfaceStore {
42 #[inline]
45 pub fn level<N: DimensionNoises>(
46 &self,
47 noises: &N,
48 cache: &mut N::ColumnCache,
49 x: i32,
50 z: i32,
51 ) -> i32 {
52 let quart_x = x >> 2;
53 let quart_z = z >> 2;
54 let Some(tag) = Self::tag(quart_x, quart_z) else {
55 return preliminary_surface_level(noises, cache, x, z);
56 };
57
58 let slot =
59 &self.slots[(((quart_x & AXIS_MASK) << AXIS_BITS) | (quart_z & AXIS_MASK)) as usize];
60
61 let packed = slot.load(Ordering::Relaxed);
62 if packed & !LEVEL_MASK == tag {
63 return unpack_level(packed);
64 }
65
66 let level = preliminary_surface_level(noises, cache, x, z);
67 if let Some(level_bits) = pack_level(level) {
68 slot.store(tag | level_bits, Ordering::Relaxed);
69 }
70
71 level
72 }
73
74 #[inline]
76 fn tag(quart_x: i32, quart_z: i32) -> Option<u64> {
77 if !(COORD_MIN..=COORD_MAX).contains(&quart_x)
78 || !(COORD_MIN..=COORD_MAX).contains(&quart_z)
79 {
80 return None;
81 }
82
83 Some(
84 VALID
85 | ((quart_x as u64 & COORD_MASK) << (COORD_BITS + LEVEL_BITS))
86 | ((quart_z as u64 & COORD_MASK) << LEVEL_BITS),
87 )
88 }
89}
90
91#[inline]
93fn pack_level(level: i32) -> Option<u64> {
94 (LEVEL_MIN..=LEVEL_MAX)
95 .contains(&level)
96 .then_some(level as u64 & LEVEL_MASK)
97}
98
99#[inline]
101const fn unpack_level(packed: u64) -> i32 {
102 (((packed & LEVEL_MASK) as i32) << (i32::BITS - LEVEL_BITS)) >> (i32::BITS - LEVEL_BITS)
103}
104
105#[cfg(test)]
106mod tests {
107 use steel_utils::random::{Random, xoroshiro::Xoroshiro};
108
109 use super::{
110 AXIS_BITS, LEVEL_MAX, LEVEL_MIN, PreliminarySurfaceStore, pack_level, unpack_level,
111 };
112 use crate::density_functions::overworld::{OverworldColumnCache, OverworldNoises};
113 use crate::noise::aquifer::preliminary_surface_level;
114 use crate::noise_parameters::get_noise_parameters;
115
116 #[test]
117 fn packed_levels_round_trip_through_colliding_slots() {
118 let seed = 1;
119 let splitter = Xoroshiro::from_seed(seed).next_positional();
120 let noises = OverworldNoises::create(seed, &splitter, &get_noise_parameters());
121 let store = PreliminarySurfaceStore::default();
122 let mut cache = OverworldColumnCache::default();
123
124 let wrap = 4 << AXIS_BITS;
126 let mut columns = vec![(0, 0), (wrap, 0), (0, -wrap), (-wrap, wrap)];
127 for x in (-3_000..3_000).step_by(373) {
129 for z in (-3_000..3_000).step_by(419) {
130 columns.push((x, z));
131 }
132 }
133
134 for _ in 0..2 {
136 for &(x, z) in &columns {
137 let expected = preliminary_surface_level(&noises, &mut cache, x, z);
138 assert_eq!(
139 store.level(&noises, &mut cache, x, z),
140 expected,
141 "column ({x}, {z})"
142 );
143 }
144 }
145 }
146
147 #[test]
148 fn levels_keep_sign_and_reject_out_of_range() {
149 for level in [LEVEL_MIN, -64, -1, 0, 63, 320, LEVEL_MAX] {
150 let packed = pack_level(level).expect("in range");
151 assert_eq!(unpack_level(packed), level);
152 }
153 assert_eq!(pack_level(LEVEL_MIN - 1), None);
154 assert_eq!(pack_level(LEVEL_MAX + 1), None);
155 }
156}