Skip to main content

steel_core/worldgen/feature/
vanilla_collections.rs

1use super::prelude::*;
2
3/// Block-position set for feature code that vanilla models as `HashSet<BlockPos>`.
4///
5/// Java `HashSet` iteration order is implementation-defined, so the extractor normalizes these
6/// worldgen sets to insertion order. Steel follows that deterministic oracle instead of depending
7/// on JVM bucket ordering.
8#[derive(Default)]
9pub(super) struct JavaBlockPosSet {
10    entries: Vec<BlockPos>,
11    present: FxHashSet<BlockPos>,
12}
13
14impl JavaBlockPosSet {
15    pub(super) fn insert(&mut self, pos: BlockPos) -> bool {
16        if !self.present.insert(pos) {
17            return false;
18        }
19
20        self.entries.push(pos);
21        true
22    }
23
24    pub(super) fn remove(&mut self, pos: BlockPos) -> bool {
25        if !self.present.remove(&pos) {
26            return false;
27        }
28
29        self.entries.retain(|entry| *entry != pos);
30        true
31    }
32
33    pub(super) fn contains(&self, pos: BlockPos) -> bool {
34        self.present.contains(&pos)
35    }
36
37    pub(super) fn is_empty(&self) -> bool {
38        self.present.is_empty()
39    }
40
41    pub(super) fn insertion_order(&self) -> impl Iterator<Item = &BlockPos> {
42        self.entries
43            .iter()
44            .filter(|pos| self.present.contains(*pos))
45    }
46
47    pub(super) fn java_ordered_positions(&self) -> Vec<BlockPos> {
48        self.insertion_order().copied().collect()
49    }
50
51    pub(super) fn pop_java_ordered_position(&mut self) -> Option<BlockPos> {
52        let pos = self.java_ordered_positions().into_iter().next()?;
53        self.remove(pos);
54        Some(pos)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn duplicate_positions_keep_first_insertion() {
64        let mut set = JavaBlockPosSet::default();
65        assert!(set.insert(BlockPos::new(1, 2, 3)));
66        assert!(!set.insert(BlockPos::new(1, 2, 3)));
67        assert_eq!(set.java_ordered_positions(), [BlockPos::new(1, 2, 3)]);
68    }
69
70    #[test]
71    fn removed_positions_do_not_iterate() {
72        let mut set = JavaBlockPosSet::default();
73        for x in 0..4 {
74            assert!(set.insert(BlockPos::new(x, 0, 0)));
75        }
76
77        assert!(set.remove(BlockPos::new(1, 0, 0)));
78
79        assert_eq!(
80            set.java_ordered_positions(),
81            [
82                BlockPos::new(0, 0, 0),
83                BlockPos::new(2, 0, 0),
84                BlockPos::new(3, 0, 0)
85            ]
86        );
87    }
88
89    #[test]
90    fn reinserted_position_uses_new_insertion_position() {
91        let mut set = JavaBlockPosSet::default();
92        let first = BlockPos::new(1, 0, 0);
93        let second = BlockPos::new(17, 0, 0);
94
95        assert!(set.insert(first));
96        assert!(set.insert(second));
97        assert!(set.remove(first));
98        assert!(set.insert(first));
99
100        assert_eq!(set.java_ordered_positions(), [second, first]);
101    }
102
103    #[test]
104    fn pop_uses_insertion_order() {
105        let mut set = JavaBlockPosSet::default();
106        let first = BlockPos::new(1, 0, 0);
107        let second = BlockPos::new(2, 0, 0);
108        assert!(set.insert(first));
109        assert!(set.insert(second));
110
111        assert_eq!(set.pop_java_ordered_position(), Some(first));
112        assert_eq!(set.pop_java_ordered_position(), Some(second));
113        assert_eq!(set.pop_java_ordered_position(), None);
114    }
115}