steel_core/java_block_pos_set/
mod.rs1use std::iter::from_fn;
9use std::mem;
10use std::vec::IntoIter;
11
12use rustc_hash::FxHashMap;
13use steel_utils::BlockPos;
14
15mod removal;
16#[cfg(test)]
17mod tests;
18mod tree;
19#[cfg(test)]
20mod worldgen_tests;
21
22const EMPTY: u32 = u32::MAX;
23const TREEIFY_THRESHOLD: usize = 8;
24const UNTREEIFY_THRESHOLD: usize = 6;
25const MIN_TREEIFY_CAPACITY: usize = 64;
26
27#[derive(Default)]
28pub(crate) enum EqualHashOrder {
29 #[default]
30 Coordinates,
31 Insertion,
32}
33
34#[derive(Default)]
36pub(crate) struct JavaBlockPosSet {
37 buckets: Vec<Bucket>,
38 entries: Vec<Entry>,
39 trees: FxHashMap<u32, tree::Links>,
41 len: usize,
42 equal_hash_order: EqualHashOrder,
43}
44
45#[derive(Clone, Copy)]
46struct Bucket {
47 head: u32,
48 tail: u32,
50}
51
52impl Bucket {
53 const EMPTY: Self = Self {
54 head: EMPTY,
55 tail: 0,
56 };
57
58 const fn is_tree(self) -> bool {
59 self.tail == EMPTY
60 }
61}
62
63struct Entry {
64 pos: BlockPos,
65 next: u32,
66}
67
68impl JavaBlockPosSet {
69 pub(crate) fn new(equal_hash_order: EqualHashOrder) -> Self {
70 Self {
71 equal_hash_order,
72 ..Self::default()
73 }
74 }
75
76 #[inline]
77 pub(crate) fn insert(&mut self, pos: BlockPos) -> bool {
78 if self.buckets.is_empty() {
79 self.buckets.resize(16, Bucket::EMPTY);
80 self.entries.reserve(16);
81 }
82 let hash = spread(pos);
83 let index = hash as u32 as usize & (self.buckets.len() - 1);
84 let bucket = self.buckets[index];
85 let mut bin_len = 0;
86 if bucket.is_tree() {
87 if self.find_in_tree(self.tree_root(bucket.head), hash, pos) {
88 return false;
89 }
90 } else {
91 let mut current = bucket.head;
92 while current != EMPTY {
93 let entry = &self.entries[current as usize];
94 if entry.pos == pos {
95 return false;
96 }
97 current = entry.next;
98 bin_len += 1;
99 }
100 }
101
102 let Ok(entry_index) = u32::try_from(self.entries.len()) else {
103 panic!("JavaBlockPosSet entry arena exceeded its u32 index space");
104 };
105 assert_ne!(
106 entry_index, EMPTY,
107 "JavaBlockPosSet entry arena exhausted its u32 index space"
108 );
109 self.entries.push(Entry { pos, next: EMPTY });
110 self.len += 1;
111 if bucket.is_tree() {
112 self.insert_tree(index, entry_index);
113 } else {
114 self.append(index, entry_index);
115 if bin_len >= TREEIFY_THRESHOLD {
117 if self.buckets.len() < MIN_TREEIFY_CAPACITY {
118 self.resize();
119 } else {
120 self.treeify(index);
121 }
122 }
123 }
124 if self.len > self.buckets.len() * 3 / 4 {
125 self.resize();
126 }
127 true
128 }
129
130 pub(crate) fn contains(&self, pos: BlockPos) -> bool {
131 if self.buckets.is_empty() {
132 return false;
133 }
134 let hash = spread(pos);
135 let bucket = self.buckets[hash as u32 as usize & (self.buckets.len() - 1)];
136 if bucket.is_tree() {
137 return self.find_in_tree(self.tree_root(bucket.head), hash, pos);
138 }
139 let mut current = bucket.head;
140 while current != EMPTY {
141 let entry = &self.entries[current as usize];
142 if entry.pos == pos {
143 return true;
144 }
145 current = entry.next;
146 }
147 false
148 }
149
150 pub(crate) const fn is_empty(&self) -> bool {
151 self.len == 0
152 }
153
154 pub(crate) fn java_order(&self) -> impl Iterator<Item = &BlockPos> {
155 self.buckets.iter().flat_map(move |bucket| {
156 let mut current = bucket.head;
157 from_fn(move || {
158 if current == EMPTY {
159 return None;
160 }
161 let entry = &self.entries[current as usize];
162 current = entry.next;
163 Some(&entry.pos)
164 })
165 })
166 }
167
168 pub(crate) fn java_ordered_positions(&self) -> Vec<BlockPos> {
169 self.java_order().copied().collect()
170 }
171
172 pub(crate) fn pop_java_ordered_position(&mut self) -> Option<BlockPos> {
173 let index = self
174 .buckets
175 .iter()
176 .position(|bucket| bucket.head != EMPTY)?;
177 let bucket = self.buckets[index];
178 let node = bucket.head;
179 let entry = &self.entries[node as usize];
180 let pos = entry.pos;
181 self.buckets[index].head = entry.next;
182 self.len -= 1;
183 if self.buckets[index].head == EMPTY {
184 self.buckets[index].tail = 0;
185 } else if bucket.is_tree() {
186 let next = self.buckets[index].head;
187 self.links_mut(next).prev = EMPTY;
188 self.remove_from_tree(node);
190 }
191 self.trees.remove(&node);
192 Some(pos)
193 }
194
195 #[cfg(test)]
196 pub(crate) const fn bucket_count(&self) -> usize {
197 self.buckets.len()
198 }
199
200 fn append(&mut self, bucket_index: usize, entry_index: u32) {
201 debug_assert!(!self.buckets[bucket_index].is_tree());
202 let bucket = &mut self.buckets[bucket_index];
203 self.entries[entry_index as usize].next = EMPTY;
204 if bucket.head == EMPTY {
205 bucket.head = entry_index;
206 } else {
207 self.entries[bucket.tail as usize].next = entry_index;
208 }
209 bucket.tail = entry_index;
210 }
211
212 fn resize(&mut self) {
213 let old_capacity = self.buckets.len();
214 if old_capacity >= 1 << 30 {
216 return;
217 }
218 let old_buckets = mem::replace(&mut self.buckets, vec![Bucket::EMPTY; old_capacity * 2]);
219 for (index, bucket) in old_buckets.into_iter().enumerate() {
220 if bucket.is_tree() {
221 self.split_tree(index, bucket.head, old_capacity);
222 continue;
223 }
224 let mut current = bucket.head;
225 while current != EMPTY {
226 let next = self.entries[current as usize].next;
227 let target = spread(self.entries[current as usize].pos) as u32 as usize
228 & (self.buckets.len() - 1);
229 self.append(target, current);
230 current = next;
231 }
232 }
233 }
234}
235
236impl IntoIterator for JavaBlockPosSet {
237 type Item = BlockPos;
238 type IntoIter = IntoIter<BlockPos>;
239
240 fn into_iter(self) -> Self::IntoIter {
241 let mut ordered = Vec::with_capacity(self.len);
242 for bucket in self.buckets {
243 let mut current = bucket.head;
244 while current != EMPTY {
245 let entry = &self.entries[current as usize];
246 ordered.push(entry.pos);
247 current = entry.next;
248 }
249 }
250 ordered.into_iter()
251 }
252}
253
254const fn spread(pos: BlockPos) -> i32 {
255 let hash = pos.java_hash_code() as u32;
256 (hash ^ (hash >> 16)) as i32
257}