Skip to main content

steel_core/worldgen/feature/
sorter.rs

1use std::cmp::Ordering;
2use std::collections::{BTreeMap, BTreeSet};
3
4use rustc_hash::FxHashMap;
5use steel_registry::biome::BiomeRef;
6use steel_registry::feature::PlacedFeatureEntryRef;
7use steel_registry::{Registry, RegistryEntry as _, RegistryExt as _};
8
9/// Cached vanilla ordering for all placed features reachable from a biome source.
10#[derive(Debug)]
11pub(super) struct FeatureSorter {
12    steps: Box<[FeatureStepData]>,
13}
14
15#[derive(Debug)]
16pub(super) struct FeatureStepData {
17    features: Box<[PlacedFeatureEntryRef]>,
18    index_by_placed_feature_id: FxHashMap<usize, usize>,
19    feature_indices_by_biome_id: FxHashMap<usize, Box<[usize]>>,
20}
21
22#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
23struct FeatureVertex {
24    step: usize,
25    order: usize,
26    placed_feature_id: usize,
27}
28
29impl Ord for FeatureVertex {
30    fn cmp(&self, other: &Self) -> Ordering {
31        (self.step, self.order, self.placed_feature_id).cmp(&(
32            other.step,
33            other.order,
34            other.placed_feature_id,
35        ))
36    }
37}
38
39impl PartialOrd for FeatureVertex {
40    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
41        Some(self.cmp(other))
42    }
43}
44
45impl FeatureSorter {
46    #[must_use]
47    pub(super) fn build(possible_biomes: &[BiomeRef], registry: &Registry) -> Self {
48        let mut feature_order_by_id = FxHashMap::default();
49        let mut next_feature_order = 0usize;
50        let mut edges = BTreeMap::<FeatureVertex, BTreeSet<FeatureVertex>>::new();
51
52        for biome in possible_biomes {
53            let mut biome_features = Vec::new();
54
55            for (step, feature_stage) in biome.features.iter().enumerate() {
56                for feature_key in feature_stage {
57                    let Some(placed_feature_id) = registry.placed_features.id_from_key(feature_key)
58                    else {
59                        panic!(
60                            "biome {} references unknown placed feature {}",
61                            biome.key, feature_key
62                        );
63                    };
64
65                    let feature_order =
66                        if let Some(&order) = feature_order_by_id.get(&placed_feature_id) {
67                            order
68                        } else {
69                            let order = next_feature_order;
70                            next_feature_order += 1;
71                            feature_order_by_id.insert(placed_feature_id, order);
72                            order
73                        };
74
75                    let vertex = FeatureVertex {
76                        step,
77                        order: feature_order,
78                        placed_feature_id,
79                    };
80                    edges.entry(vertex).or_default();
81                    biome_features.push(vertex);
82                }
83            }
84
85            for feature_pair in biome_features.windows(2) {
86                edges
87                    .entry(feature_pair[0])
88                    .or_default()
89                    .insert(feature_pair[1]);
90            }
91        }
92
93        let sorted_features = Self::topological_sort(&edges);
94        Self::from_sorted_features(&sorted_features, possible_biomes, registry)
95    }
96
97    #[must_use]
98    pub(super) fn step_count(&self) -> usize {
99        self.steps.len()
100    }
101
102    pub(super) fn step(&self, step: usize) -> Option<&FeatureStepData> {
103        self.steps.get(step)
104    }
105
106    fn topological_sort(
107        edges: &BTreeMap<FeatureVertex, BTreeSet<FeatureVertex>>,
108    ) -> Vec<FeatureVertex> {
109        let mut sorted = Vec::with_capacity(edges.len());
110        let mut discovered = BTreeSet::new();
111        let mut visiting = BTreeSet::new();
112        let vertices = edges.keys().copied().collect::<Vec<_>>();
113
114        for vertex in vertices {
115            assert!(
116                !Self::visit(vertex, edges, &mut discovered, &mut visiting, &mut sorted),
117                "biome decoration placed-feature order contains a cycle"
118            );
119        }
120
121        sorted.reverse();
122        sorted
123    }
124
125    fn visit(
126        vertex: FeatureVertex,
127        edges: &BTreeMap<FeatureVertex, BTreeSet<FeatureVertex>>,
128        discovered: &mut BTreeSet<FeatureVertex>,
129        visiting: &mut BTreeSet<FeatureVertex>,
130        sorted: &mut Vec<FeatureVertex>,
131    ) -> bool {
132        if discovered.contains(&vertex) {
133            return false;
134        }
135        if !visiting.insert(vertex) {
136            return true;
137        }
138
139        if let Some(neighbors) = edges.get(&vertex) {
140            for &neighbor in neighbors {
141                if Self::visit(neighbor, edges, discovered, visiting, sorted) {
142                    return true;
143                }
144            }
145        }
146
147        visiting.remove(&vertex);
148        discovered.insert(vertex);
149        sorted.push(vertex);
150        false
151    }
152
153    #[must_use]
154    fn from_sorted_features(
155        sorted_features: &[FeatureVertex],
156        possible_biomes: &[BiomeRef],
157        registry: &Registry,
158    ) -> Self {
159        let Some(max_step) = sorted_features.iter().map(|feature| feature.step).max() else {
160            return Self {
161                steps: Box::new([]),
162            };
163        };
164
165        let mut steps = Vec::with_capacity(max_step + 1);
166        for step in 0..=max_step {
167            let mut features = Vec::new();
168            let mut index_by_placed_feature_id = FxHashMap::default();
169
170            for feature in sorted_features
171                .iter()
172                .filter(|feature| feature.step == step)
173            {
174                let Some(placed_feature) =
175                    registry.placed_features.by_id(feature.placed_feature_id)
176                else {
177                    panic!(
178                        "feature sorter references unknown placed feature id {}",
179                        feature.placed_feature_id
180                    );
181                };
182                let index = features.len();
183                features.push(placed_feature);
184                index_by_placed_feature_id.insert(feature.placed_feature_id, index);
185            }
186
187            steps.push(FeatureStepData {
188                features: features.into_boxed_slice(),
189                index_by_placed_feature_id,
190                feature_indices_by_biome_id: FxHashMap::default(),
191            });
192        }
193
194        for biome in possible_biomes {
195            let Some(biome_id) = biome.try_id() else {
196                panic!("possible biome {} is not registered", biome.key);
197            };
198
199            for (step, feature_stage) in biome.features.iter().enumerate() {
200                let Some(step_data) = steps.get_mut(step) else {
201                    continue;
202                };
203
204                let mut indices = Vec::with_capacity(feature_stage.len());
205                for feature_key in feature_stage {
206                    let Some(placed_feature_id) = registry.placed_features.id_from_key(feature_key)
207                    else {
208                        panic!(
209                            "biome {} references unknown placed feature {}",
210                            biome.key, feature_key
211                        );
212                    };
213                    let Some(feature_index) = step_data.feature_index(placed_feature_id) else {
214                        panic!(
215                            "placed feature {} from biome {} was not included in decoration step {}",
216                            feature_key, biome.key, step
217                        );
218                    };
219                    indices.push(feature_index);
220                }
221
222                if indices.is_empty() {
223                    continue;
224                }
225
226                indices.sort_unstable();
227                indices.dedup();
228                step_data
229                    .feature_indices_by_biome_id
230                    .insert(biome_id, indices.into_boxed_slice());
231            }
232        }
233
234        Self {
235            steps: steps.into_boxed_slice(),
236        }
237    }
238}
239
240impl FeatureStepData {
241    pub(super) fn feature_index(&self, placed_feature_id: usize) -> Option<usize> {
242        self.index_by_placed_feature_id
243            .get(&placed_feature_id)
244            .copied()
245    }
246
247    pub(super) fn feature(&self, index: usize) -> Option<PlacedFeatureEntryRef> {
248        self.features.get(index).copied()
249    }
250
251    pub(super) fn feature_indices_for_biome(&self, biome_id: usize) -> Option<&[usize]> {
252        self.feature_indices_by_biome_id
253            .get(&biome_id)
254            .map(Box::as_ref)
255    }
256}