nucleation/selection/
visited.rs1use std::collections::HashMap;
8
9const CHUNK_BITS: u32 = 4;
10const CHUNK_SIDE: i32 = 1 << CHUNK_BITS; const CHUNK_MASK: i32 = CHUNK_SIDE - 1; const CHUNK_VOLUME: usize = 1 << (CHUNK_BITS * 3); const WORDS_PER_CHUNK: usize = CHUNK_VOLUME / 64; type ChunkKey = (i32, i32, i32);
16
17#[derive(Default)]
22pub struct VisitedSet {
23 chunks: HashMap<ChunkKey, [u64; WORDS_PER_CHUNK]>,
24}
25
26impl VisitedSet {
27 pub fn new() -> Self {
28 Self {
29 chunks: HashMap::new(),
30 }
31 }
32
33 pub fn approx_bytes(&self) -> usize {
35 self.chunks.len() * (std::mem::size_of::<ChunkKey>() + WORDS_PER_CHUNK * 8)
37 }
38
39 pub fn chunk_count(&self) -> usize {
41 self.chunks.len()
42 }
43
44 #[inline]
46 pub fn contains(&self, x: i32, y: i32, z: i32) -> bool {
47 let key = chunk_key(x, y, z);
48 match self.chunks.get(&key) {
49 Some(bits) => {
50 let (word, bit) = index_in_chunk(x, y, z);
51 (bits[word] >> bit) & 1 != 0
52 }
53 None => false,
54 }
55 }
56
57 #[inline]
59 pub fn insert(&mut self, x: i32, y: i32, z: i32) -> bool {
60 let key = chunk_key(x, y, z);
61 let chunk = self
62 .chunks
63 .entry(key)
64 .or_insert_with(|| [0u64; WORDS_PER_CHUNK]);
65 let (word, bit) = index_in_chunk(x, y, z);
66 let mask = 1u64 << bit;
67 let was = chunk[word] & mask != 0;
68 chunk[word] |= mask;
69 !was
70 }
71}
72
73#[inline]
74fn chunk_key(x: i32, y: i32, z: i32) -> ChunkKey {
75 (x >> CHUNK_BITS, y >> CHUNK_BITS, z >> CHUNK_BITS)
78}
79
80#[inline]
81fn index_in_chunk(x: i32, y: i32, z: i32) -> (usize, u32) {
82 let lx = (x & CHUNK_MASK) as u32;
83 let ly = (y & CHUNK_MASK) as u32;
84 let lz = (z & CHUNK_MASK) as u32;
85 let linear = (ly << 8) | (lz << 4) | lx;
89 let word = (linear >> 6) as usize; let bit = linear & 63;
91 (word, bit)
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn insert_returns_true_only_first_time() {
100 let mut s = VisitedSet::new();
101 assert!(s.insert(0, 0, 0));
102 assert!(!s.insert(0, 0, 0));
103 assert!(s.insert(1, 0, 0));
104 }
105
106 #[test]
107 fn contains_matches_insert() {
108 let mut s = VisitedSet::new();
109 for &p in &[(0, 0, 0), (5, 6, 7), (-1, -2, -3), (1023, 0, -512)] {
110 assert!(!s.contains(p.0, p.1, p.2));
111 s.insert(p.0, p.1, p.2);
112 assert!(s.contains(p.0, p.1, p.2));
113 }
114 assert!(!s.contains(1, 0, 0));
116 }
117
118 #[test]
119 fn negative_coordinates_share_correct_chunk() {
120 let mut s = VisitedSet::new();
122 s.insert(-1, -1, -1);
123 assert!(s.contains(-1, -1, -1));
124 s.insert(-16, -16, -16);
126 assert!(s.contains(-16, -16, -16));
127 assert_eq!(s.chunk_count(), 1);
129 s.insert(-17, -1, -1);
131 assert_eq!(s.chunk_count(), 2);
132 }
133
134 #[test]
135 fn distinct_chunks_dont_alias() {
136 let mut s = VisitedSet::new();
139 s.insert(0, 0, 0);
140 assert!(!s.contains(16, 0, 0));
141 assert!(!s.contains(0, 16, 0));
142 assert!(!s.contains(0, 0, 16));
143 }
144
145 #[test]
146 fn fills_a_chunk_completely() {
147 let mut s = VisitedSet::new();
148 for y in 0..16 {
149 for z in 0..16 {
150 for x in 0..16 {
151 assert!(s.insert(x, y, z));
152 }
153 }
154 }
155 assert_eq!(s.chunk_count(), 1);
156 for y in 0..16 {
157 for z in 0..16 {
158 for x in 0..16 {
159 assert!(s.contains(x, y, z));
160 }
161 }
162 }
163 for y in 0..16 {
165 for z in 0..16 {
166 for x in 0..16 {
167 assert!(!s.insert(x, y, z));
168 }
169 }
170 }
171 }
172}