Skip to main content

steel_registry/recipe/
registry.rs

1//! Recipe registry for looking up recipes.
2
3use rustc_hash::FxHashMap;
4use steel_utils::Identifier;
5
6use super::crafting::{CraftingInput, CraftingRecipe, ShapedRecipe, ShapelessRecipe};
7
8/// Registry for all recipes.
9pub struct RecipeRegistry {
10    /// All recipes in registration order (unified storage for RegistryExt).
11    recipes_by_id: Vec<&'static CraftingRecipe>,
12    /// Map from recipe key to index in `recipes_by_id`.
13    recipes_by_key: FxHashMap<Identifier, usize>,
14    /// All shaped crafting recipes (for type-specific iteration).
15    shaped_recipes: Vec<&'static ShapedRecipe>,
16    /// All shapeless crafting recipes (for type-specific iteration).
17    shapeless_recipes: Vec<&'static ShapelessRecipe>,
18    /// Whether registration is still allowed.
19    allows_registering: bool,
20}
21
22impl Default for RecipeRegistry {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl RecipeRegistry {
29    /// Creates a new empty recipe registry.
30    #[must_use]
31    pub fn new() -> Self {
32        Self {
33            recipes_by_id: Vec::new(),
34            recipes_by_key: FxHashMap::default(),
35            shaped_recipes: Vec::new(),
36            shapeless_recipes: Vec::new(),
37            allows_registering: true,
38        }
39    }
40
41    /// Registers a shaped recipe.
42    pub fn register_shaped(&mut self, recipe: &'static ShapedRecipe) {
43        assert!(
44            self.allows_registering,
45            "Cannot register recipes after the registry has been frozen"
46        );
47        let id = self.recipes_by_id.len();
48        self.recipes_by_key.insert(recipe.id.clone(), id);
49        self.recipes_by_id
50            .push(Box::leak(Box::new(CraftingRecipe::Shaped(recipe))));
51        self.shaped_recipes.push(recipe);
52    }
53
54    /// Registers a shapeless recipe.
55    pub fn register_shapeless(&mut self, recipe: &'static ShapelessRecipe) {
56        assert!(
57            self.allows_registering,
58            "Cannot register recipes after the registry has been frozen"
59        );
60        let id = self.recipes_by_id.len();
61        self.recipes_by_key.insert(recipe.id.clone(), id);
62        self.recipes_by_id
63            .push(Box::leak(Box::new(CraftingRecipe::Shapeless(recipe))));
64        self.shapeless_recipes.push(recipe);
65    }
66
67    /// Finds a matching crafting recipe for the given positioned input.
68    /// Returns the first matching recipe, or None if no recipe matches.
69    #[must_use]
70    pub fn find_crafting_recipe(&self, input: &CraftingInput) -> Option<CraftingRecipe> {
71        // Try shaped recipes first (they're more specific)
72        for recipe in &self.shaped_recipes {
73            if recipe.matches(input) {
74                return Some(CraftingRecipe::Shaped(recipe));
75            }
76        }
77
78        // Then try shapeless
79        for recipe in &self.shapeless_recipes {
80            if recipe.matches(input) {
81                return Some(CraftingRecipe::Shapeless(recipe));
82            }
83        }
84
85        None
86    }
87
88    /// Finds a matching crafting recipe for a 2x2 grid.
89    /// Only checks recipes that can fit in a 2x2 grid.
90    #[must_use]
91    pub fn find_crafting_recipe_2x2(&self, input: &CraftingInput) -> Option<CraftingRecipe> {
92        // Try shaped recipes first (they're more specific)
93        for recipe in &self.shaped_recipes {
94            if recipe.fits_in_2x2() && recipe.matches(input) {
95                return Some(CraftingRecipe::Shaped(recipe));
96            }
97        }
98
99        // Then try shapeless
100        for recipe in &self.shapeless_recipes {
101            if recipe.fits_in_2x2() && recipe.matches(input) {
102                return Some(CraftingRecipe::Shapeless(recipe));
103            }
104        }
105
106        None
107    }
108
109    /// Gets a shaped recipe by its identifier.
110    #[must_use]
111    pub fn get_shaped(&self, id: &Identifier) -> Option<&'static ShapedRecipe> {
112        self.shaped_recipes.iter().find(|r| &r.id == id).copied()
113    }
114
115    /// Gets a shapeless recipe by its identifier.
116    #[must_use]
117    pub fn get_shapeless(&self, id: &Identifier) -> Option<&'static ShapelessRecipe> {
118        self.shapeless_recipes.iter().find(|r| &r.id == id).copied()
119    }
120
121    /// Returns the number of shaped recipes.
122    #[must_use]
123    pub fn shaped_count(&self) -> usize {
124        self.shaped_recipes.len()
125    }
126
127    /// Returns the number of shapeless recipes.
128    #[must_use]
129    pub fn shapeless_count(&self) -> usize {
130        self.shapeless_recipes.len()
131    }
132
133    /// Iterates over all shaped recipes.
134    pub fn iter_shaped(&self) -> impl Iterator<Item = &'static ShapedRecipe> + '_ {
135        self.shaped_recipes.iter().copied()
136    }
137
138    /// Iterates over all shapeless recipes.
139    pub fn iter_shapeless(&self) -> impl Iterator<Item = &'static ShapelessRecipe> + '_ {
140        self.shapeless_recipes.iter().copied()
141    }
142}
143
144impl crate::RegistryExt for RecipeRegistry {
145    type Entry = CraftingRecipe;
146
147    fn freeze(&mut self) {
148        self.allows_registering = false;
149    }
150
151    fn by_id(&self, id: usize) -> Option<&'static CraftingRecipe> {
152        self.recipes_by_id.get(id).copied()
153    }
154
155    fn by_key(&self, key: &Identifier) -> Option<&'static CraftingRecipe> {
156        self.recipes_by_key
157            .get(key)
158            .and_then(|&id| self.recipes_by_id.get(id).copied())
159    }
160
161    fn id_from_key(&self, key: &Identifier) -> Option<usize> {
162        self.recipes_by_key.get(key).copied()
163    }
164
165    fn len(&self) -> usize {
166        self.recipes_by_id.len()
167    }
168
169    fn is_empty(&self) -> bool {
170        self.recipes_by_id.is_empty()
171    }
172}
173
174impl crate::RegistryEntry for CraftingRecipe {
175    fn key(&self) -> &Identifier {
176        self.id()
177    }
178
179    fn try_id(&self) -> Option<usize> {
180        use crate::RegistryExt;
181        crate::REGISTRY.recipes.id_from_key(self.id())
182    }
183}