steel_registry/poi/
mod.rs1#![cfg_attr(
7 test,
8 expect(
9 clippy::unwrap_used,
10 reason = "poi tests assert extracted vanilla state references exist"
11 )
12)]
13
14use rustc_hash::FxHashMap;
15use steel_utils::{BlockStateId, Identifier};
16
17use crate::RegistryTags;
18use crate::blocks::{BlockRef, BlockRegistry};
19
20#[derive(Debug)]
26pub struct PoiBlockMatcher {
27 pub block: BlockRef,
28 pub properties: &'static [(&'static str, &'static str)],
29}
30
31#[derive(Debug)]
36pub struct PointOfInterestType {
37 pub key: Identifier,
38 pub blocks: &'static [PoiBlockMatcher],
39 pub ticket_count: u32,
40 pub search_distance: u32,
41}
42
43pub type PoiTypeRef = &'static PointOfInterestType;
45
46pub struct PoiTypeRegistry {
48 types_by_id: Vec<PoiTypeRef>,
49 types_by_key: FxHashMap<Identifier, usize>,
50 state_to_type: FxHashMap<BlockStateId, usize>,
52 tags: RegistryTags,
53 allows_registering: bool,
54}
55
56impl Default for PoiTypeRegistry {
57 fn default() -> Self {
58 Self::new()
59 }
60}
61
62impl PoiTypeRegistry {
63 #[must_use]
64 pub fn new() -> Self {
65 Self {
66 types_by_id: Vec::new(),
67 types_by_key: FxHashMap::default(),
68 state_to_type: FxHashMap::default(),
69 tags: RegistryTags::default(),
70 allows_registering: true,
71 }
72 }
73
74 pub fn register(&mut self, poi_type: PoiTypeRef) -> usize {
75 assert!(
76 self.allows_registering,
77 "Cannot register POI types after the registry has been frozen"
78 );
79
80 let id = self.types_by_id.len();
81 self.types_by_key.insert(poi_type.key.clone(), id);
82 self.types_by_id.push(poi_type);
83 id
84 }
85
86 pub fn build_state_index(&mut self, blocks: &BlockRegistry) {
90 self.state_to_type.clear();
91 for (id, poi_type) in self.types_by_id.iter().enumerate() {
92 for matcher in poi_type.blocks {
93 for state_id in blocks.matching_states(matcher.block, matcher.properties) {
94 self.state_to_type.insert(state_id, id);
95 }
96 }
97 }
98 }
99
100 #[must_use]
101 pub fn type_for_state(&self, state_id: BlockStateId) -> Option<PoiTypeRef> {
102 use crate::RegistryExt;
103 self.state_to_type
104 .get(&state_id)
105 .and_then(|id| self.by_id(*id))
106 }
107
108 #[must_use]
109 pub fn type_id_for_state(&self, state_id: BlockStateId) -> Option<usize> {
110 self.state_to_type.get(&state_id).copied()
111 }
112
113 #[must_use]
114 pub fn is_poi_state(&self, state_id: BlockStateId) -> bool {
115 self.state_to_type.contains_key(&state_id)
116 }
117
118 pub fn iter(&self) -> impl Iterator<Item = (usize, PoiTypeRef)> + '_ {
119 self.types_by_id
120 .iter()
121 .enumerate()
122 .map(|(id, &poi_type)| (id, poi_type))
123 }
124}
125
126crate::impl_registry!(
127 PoiTypeRegistry,
128 PointOfInterestType,
129 types_by_id,
130 types_by_key,
131 poi_types
132);
133crate::impl_tagged_registry!(PoiTypeRegistry, types_by_key, "POI type");
134
135#[cfg(test)]
136mod tests {
137 use std::collections::{BTreeMap, BTreeSet};
138
139 use steel_utils::BlockStateId;
140
141 use crate::init_vanilla_registry;
142 use crate::{REGISTRY, vanilla_blocks};
143
144 #[test]
145 fn matching_states_respects_property_filter() {
146 init_vanilla_registry();
147 let blocks = ®ISTRY.blocks;
148
149 let furnace = blocks.matching_states(&vanilla_blocks::BLAST_FURNACE, &[]);
150 assert_eq!(
151 furnace.len(),
152 usize::from(vanilla_blocks::BLAST_FURNACE.state_count())
153 );
154
155 let heads = blocks.matching_states(&vanilla_blocks::WHITE_BED, &[("part", "head")]);
156 let all_beds = blocks.matching_states(&vanilla_blocks::WHITE_BED, &[]);
157 assert_eq!(heads.len() * 2, all_beds.len());
158 for state in heads {
159 assert!(blocks.get_properties(state).contains(&("part", "head")));
160 }
161 }
162
163 #[test]
167 fn matchers_reproduce_extracted_vanilla_states() {
168 init_vanilla_registry();
169
170 #[derive(serde::Deserialize)]
171 struct PoiFile {
172 poi_types: Vec<PoiJson>,
173 }
174 #[derive(serde::Deserialize)]
175 struct PoiJson {
176 name: String,
177 block_states: Vec<StateJson>,
178 }
179 #[derive(serde::Deserialize)]
180 struct StateJson {
181 state_id: u16,
182 }
183
184 let json = include_str!(concat!(
185 env!("CARGO_MANIFEST_DIR"),
186 "/build_assets/poi_types.json"
187 ));
188 let file: PoiFile = serde_json::from_str(json).unwrap();
189
190 let mut expected: BTreeMap<String, BTreeSet<u16>> = BTreeMap::new();
191 for poi in &file.poi_types {
192 let set = expected.entry(poi.name.clone()).or_default();
193 set.extend(poi.block_states.iter().map(|s| s.state_id));
194 }
195
196 let mut actual: BTreeMap<String, BTreeSet<u16>> = BTreeMap::new();
197 for raw in 0..REGISTRY.blocks.next_state_id {
198 if let Some(poi) = REGISTRY.poi_types.type_for_state(BlockStateId(raw)) {
199 actual
200 .entry(poi.key.path.to_string())
201 .or_default()
202 .insert(raw);
203 }
204 }
205
206 assert_eq!(actual, expected);
207 }
208}