1use std::fmt::{self, Debug, Formatter};
4use std::marker::PhantomData;
5
6use rustc_hash::FxHashMap;
7use steel_utils::{DowncastType, DowncastTypeKey, ErasedType, Identifier};
8
9use super::RecipeProperties;
10
11pub trait RecipeData: ErasedType + Debug + Send + Sync + 'static {
13 fn properties(&self) -> Option<&RecipeProperties> {
14 None
15 }
16}
17
18pub trait RecipeInput: DowncastType + Debug + Send + Sync + 'static {
20 #[must_use]
21 fn is_empty(&self) -> bool;
22}
23
24pub trait RecipeMatches<I: RecipeInput>: RecipeData {
26 #[must_use]
28 fn matches(&self, input: &I) -> bool;
29}
30
31#[derive(Debug, PartialEq, Eq)]
33pub struct RecipeTypeEntry {
34 pub key: Identifier,
35 data_type_key: DowncastTypeKey,
36 input_type_key: DowncastTypeKey,
37}
38
39impl RecipeTypeEntry {
40 #[must_use]
41 pub const fn data_type_key(&self) -> DowncastTypeKey {
42 self.data_type_key
43 }
44
45 #[must_use]
46 pub const fn input_type_key(&self) -> DowncastTypeKey {
47 self.input_type_key
48 }
49}
50
51pub type RecipeTypeEntryRef = &'static RecipeTypeEntry;
52
53pub struct RecipeType<D: RecipeMatches<I> + DowncastType, I: RecipeInput> {
55 entry: RecipeTypeEntry,
56 _marker: PhantomData<fn(&D, &I)>,
57}
58
59impl<D: RecipeMatches<I> + DowncastType, I: RecipeInput> RecipeType<D, I> {
60 #[must_use]
61 pub const fn new(key: Identifier) -> Self {
62 Self {
63 entry: RecipeTypeEntry {
64 key,
65 data_type_key: D::TYPE_KEY,
66 input_type_key: I::TYPE_KEY,
67 },
68 _marker: PhantomData,
69 }
70 }
71
72 #[must_use]
73 pub const fn entry(&'static self) -> RecipeTypeEntryRef {
74 &self.entry
75 }
76
77 #[must_use]
78 pub const fn key(&self) -> &Identifier {
79 &self.entry.key
80 }
81}
82
83impl<D: RecipeMatches<I> + DowncastType, I: RecipeInput> Debug for RecipeType<D, I> {
84 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
85 formatter
86 .debug_struct("RecipeType")
87 .field("key", &self.entry.key)
88 .field("data_type_key", &self.entry.data_type_key)
89 .field("input_type_key", &self.entry.input_type_key)
90 .finish_non_exhaustive()
91 }
92}
93
94pub struct Recipe<D: RecipeMatches<I> + DowncastType, I: RecipeInput> {
96 key: Identifier,
97 recipe_type: &'static RecipeType<D, I>,
98 data: D,
99}
100
101impl<D: RecipeMatches<I> + DowncastType, I: RecipeInput> Recipe<D, I> {
102 #[must_use]
103 pub const fn new(key: Identifier, recipe_type: &'static RecipeType<D, I>, data: D) -> Self {
104 Self {
105 key,
106 recipe_type,
107 data,
108 }
109 }
110
111 #[must_use]
112 pub const fn key(&self) -> &Identifier {
113 &self.key
114 }
115
116 #[must_use]
117 pub const fn recipe_type(&self) -> &'static RecipeType<D, I> {
118 self.recipe_type
119 }
120
121 #[must_use]
122 pub const fn data(&self) -> &D {
123 &self.data
124 }
125}
126
127impl<D: RecipeMatches<I> + DowncastType, I: RecipeInput> Debug for Recipe<D, I> {
128 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
129 formatter
130 .debug_struct("Recipe")
131 .field("key", &self.key)
132 .field("recipe_type", &self.recipe_type.key())
133 .field("data", &self.data)
134 .finish()
135 }
136}
137
138pub(crate) trait ErasedRecipe: Debug + Send + Sync {
139 fn key(&self) -> &Identifier;
140 fn recipe_type(&self) -> RecipeTypeEntryRef;
141 fn data(&self) -> &dyn RecipeData;
142}
143
144impl<D: RecipeMatches<I> + DowncastType, I: RecipeInput> ErasedRecipe for Recipe<D, I> {
145 fn key(&self) -> &Identifier {
146 self.key()
147 }
148
149 fn recipe_type(&self) -> RecipeTypeEntryRef {
150 self.recipe_type.entry()
151 }
152
153 fn data(&self) -> &dyn RecipeData {
154 &self.data
155 }
156}
157
158pub struct RecipeTypeRegistry {
160 types: Vec<RecipeTypeEntryRef>,
161 by_key: FxHashMap<Identifier, usize>,
162 allows_registering: bool,
163}
164
165impl RecipeTypeRegistry {
166 #[must_use]
167 pub fn new() -> Self {
168 Self {
169 types: Vec::new(),
170 by_key: FxHashMap::default(),
171 allows_registering: true,
172 }
173 }
174
175 pub fn register<D: RecipeMatches<I> + DowncastType, I: RecipeInput>(
176 &mut self,
177 recipe_type: &'static RecipeType<D, I>,
178 ) {
179 self.register_entry(recipe_type.entry());
180 }
181
182 fn register_entry(&mut self, entry: RecipeTypeEntryRef) {
183 assert!(
184 self.allows_registering,
185 "Cannot register recipe types after the registry has been frozen"
186 );
187 assert!(
188 !self.by_key.contains_key(&entry.key),
189 "Cannot register duplicate recipe type key: {}",
190 entry.key
191 );
192 let id = self.types.len();
193 self.types.push(entry);
194 self.by_key.insert(entry.key.clone(), id);
195 }
196
197 pub const fn freeze(&mut self) {
198 self.allows_registering = false;
199 }
200
201 #[must_use]
202 pub fn by_key(&self, key: &Identifier) -> Option<RecipeTypeEntryRef> {
203 self.by_key
204 .get(key)
205 .and_then(|id| self.types.get(*id))
206 .copied()
207 }
208
209 #[must_use]
210 pub fn contains<D: RecipeMatches<I> + DowncastType, I: RecipeInput>(
211 &self,
212 recipe_type: &'static RecipeType<D, I>,
213 ) -> bool {
214 self.by_key(recipe_type.key())
215 .is_some_and(|registered| std::ptr::eq(registered, recipe_type.entry()))
216 }
217
218 pub fn iter(&self) -> impl Iterator<Item = RecipeTypeEntryRef> + '_ {
219 self.types.iter().copied()
220 }
221
222 #[must_use]
223 pub const fn len(&self) -> usize {
224 self.types.len()
225 }
226
227 #[must_use]
228 pub const fn is_empty(&self) -> bool {
229 self.types.is_empty()
230 }
231}
232
233impl Default for RecipeTypeRegistry {
234 fn default() -> Self {
235 Self::new()
236 }
237}
238
239pub mod vanilla_recipe_types {
241 use steel_utils::Identifier;
242
243 use super::{RecipeType, RecipeTypeRegistry};
244 use crate::recipe::{
245 CookingRecipe, CraftingInput, CraftingRecipe, SingleItemRecipeInput, SmithingRecipe,
246 SmithingRecipeInput, StonecuttingRecipe,
247 };
248
249 const fn cooking_type(key: &'static str) -> RecipeType<CookingRecipe, SingleItemRecipeInput> {
250 RecipeType::new(Identifier::vanilla_static(key))
251 }
252
253 pub static CRAFTING: RecipeType<CraftingRecipe, CraftingInput> =
254 RecipeType::new(Identifier::vanilla_static("crafting"));
255 pub static SMELTING: RecipeType<CookingRecipe, SingleItemRecipeInput> =
256 cooking_type("smelting");
257 pub static BLASTING: RecipeType<CookingRecipe, SingleItemRecipeInput> =
258 cooking_type("blasting");
259 pub static SMOKING: RecipeType<CookingRecipe, SingleItemRecipeInput> = cooking_type("smoking");
260 pub static CAMPFIRE_COOKING: RecipeType<CookingRecipe, SingleItemRecipeInput> =
261 cooking_type("campfire_cooking");
262 pub static STONECUTTING: RecipeType<StonecuttingRecipe, SingleItemRecipeInput> =
263 RecipeType::new(Identifier::vanilla_static("stonecutting"));
264 pub static SMITHING: RecipeType<SmithingRecipe, SmithingRecipeInput> =
265 RecipeType::new(Identifier::vanilla_static("smithing"));
266
267 pub(crate) fn register(registry: &mut RecipeTypeRegistry) {
268 registry.register(&CRAFTING);
269 registry.register(&SMELTING);
270 registry.register(&BLASTING);
271 registry.register(&SMOKING);
272 registry.register(&CAMPFIRE_COOKING);
273 registry.register(&STONECUTTING);
274 registry.register(&SMITHING);
275 }
276}