1919
2020import com .mojang .datafixers .util .Either ;
2121import it .unimi .dsi .fastutil .objects .ObjectArrayList ;
22- import lombok .AccessLevel ;
23- import lombok .RequiredArgsConstructor ;
2422import org .jetbrains .annotations .*;
2523
2624import java .util .*;
@@ -63,7 +61,7 @@ public void clear() {
6361 if (list == null ) {
6462 return null ;
6563 }
66- return findRecursive (list , predicate );
64+ return find (list , predicate );
6765 }
6866
6967 /**
@@ -77,7 +75,7 @@ public void clear() {
7775 @ VisibleForTesting
7876 public @ Nullable GTRecipe find (@ NotNull List <List <AbstractMapIngredient >> list ,
7977 @ NotNull Predicate <GTRecipe > predicate ) {
80- return findRecursive ( list , predicate );
78+ return ( new RecipeIterator ( this , list , predicate )). next ( );
8179 }
8280
8381 /**
@@ -99,7 +97,7 @@ public void clear() {
9997 list .add (MapIngredientTypeManager .getFrom (ingredient , cap ));
10098 }
10199 });
102- return findRecursive (list , predicate );
100+ return find (list , predicate );
103101 }
104102
105103 /**
@@ -118,98 +116,6 @@ public void clear() {
118116 return new RecipeIterator (this , list , predicate );
119117 }
120118
121- /**
122- * Recursively finds a recipe.
123- *
124- * @param ingredients the ingredients to search with
125- * @param predicate if the found recipe is valid
126- * @return the recipe
127- */
128- private @ Nullable GTRecipe findRecursive (@ NotNull List <List <AbstractMapIngredient >> ingredients ,
129- @ NotNull Predicate <GTRecipe > predicate ) {
130- // Try each ingredient as a starting point, adding it to the skip-list.
131- // The skip-list is a packed long, where each 1 bit represents an index to skip
132- for (int i = 0 ; i < ingredients .size (); i ++) {
133- BitSet skipSet = new BitSet (ingredients .size ());
134- skipSet .set (i );
135- GTRecipe r = findRecursive (ingredients , rootBranch , predicate , i , 0 , skipSet );
136- if (r != null ) {
137- return r ;
138- }
139- }
140- return null ;
141- }
142-
143- /**
144- * Recursively finds a recipe by checking the current branch's nodes.
145- *
146- * @param ingredients the ingredients to search with
147- * @param branch the branch to search
148- * @param predicate if the found recipe is valid
149- * @param index the index of the ingredient list to check
150- * @param count how deep we are in recursion, < ingredients.length
151- * @param skip bitmask of ingredients already checked
152- * @return the recipe
153- */
154- private @ Nullable GTRecipe findRecursive (@ NotNull List <List <AbstractMapIngredient >> ingredients ,
155- @ NotNull Branch branch , @ NotNull Predicate <GTRecipe > predicate ,
156- int index , int count , @ NotNull BitSet skip ) {
157- // exhausted all the ingredients, and didn't find anything
158- if (count == ingredients .size ()) {
159- return null ;
160- }
161-
162- // Iterate over current level of nodes.
163- for (AbstractMapIngredient obj : ingredients .get (index )) {
164- // determine the root nodes
165- var nodes = nodesForIngredient (obj , branch );
166- var result = nodes .get (obj );
167- if (result == null ) {
168- continue ;
169- }
170- // if there is a recipe (left mapping), return it immediately as found, if it can be handled
171- // Otherwise, recurse and go to the next branch.
172- GTRecipe recipe = result .map (r -> predicate .test (r ) ? r : null ,
173- b -> findRecursiveDive (ingredients , b , predicate , index , count , skip ));
174- if (recipe != null ) {
175- return recipe ;
176- }
177- }
178- return null ;
179- }
180-
181- /**
182- * Recursively finds a recipe by diving deeper down a path.
183- *
184- * @param ingredients the ingredients to search with
185- * @param branch the branch to search
186- * @param predicate if the found recipe is valid
187- * @param index the index of the ingredient list to check
188- * @param count how deep we are in recursion, must be < ingredients.length
189- * @param skip bitmask of ingredients already checked
190- * @return the recipe
191- */
192- private @ Nullable GTRecipe findRecursiveDive (@ NotNull List <List <AbstractMapIngredient >> ingredients ,
193- @ NotNull Branch branch , @ NotNull Predicate <GTRecipe > predicate ,
194- int index , int count , @ NotNull BitSet skip ) {
195- // loop through all ingredients, wrapping around the end until all are tried.
196- for (int i = (index + 1 ) % ingredients .size (); i != index ; i = (i + 1 ) % ingredients .size ()) {
197- if (skip .get (i )) {
198- continue ;
199- }
200- // Recursive call
201- // Append the current index to the skip list
202- skip .set (i );
203- // Increase the count, so the recursion can terminate if needed (ingredients is exhausted)
204- GTRecipe r = findRecursive (ingredients , branch , predicate , i , count + 1 , skip );
205- if (r != null ) {
206- return r ;
207- }
208- skip .clear (i );
209- }
210- return null ;
211- }
212-
213119 /**
214120 * Converts a Recipe Capability holder's handlers into a list of {@link AbstractMapIngredient}
215121 *
@@ -358,38 +264,93 @@ private boolean addRecursive(@NotNull GTRecipe recipe,
358264 return true ;
359265 }
360266
361- @ RequiredArgsConstructor (access = AccessLevel .PRIVATE )
267+ private static class SearchFrame {
268+
269+ int index ; // ingredient slot we’re exploring
270+ int ingredientIndex ; // position within ingredients[index]
271+ Branch branch ; // branch in the recipe DB
272+
273+ public SearchFrame (int index , Branch branch ) {
274+ this .index = index ;
275+ this .ingredientIndex = 0 ;
276+ this .branch = branch ;
277+ }
278+ }
279+
362280 public static class RecipeIterator implements Iterator <GTRecipe > {
363281
364282 private final @ NotNull RecipeDB db ;
365283 private final @ NotNull List <List <AbstractMapIngredient >> ingredients ;
366284 private final @ NotNull Predicate <GTRecipe > predicate ;
367- private int index ;
285+
286+ private final Deque <SearchFrame > stack = new ArrayDeque <>();
287+
288+ @ VisibleForTesting
289+ public RecipeIterator (@ NotNull RecipeDB db ,
290+ @ NotNull List <List <AbstractMapIngredient >> ingredients ,
291+ @ NotNull Predicate <GTRecipe > predicate ) {
292+ this .db = db ;
293+ this .ingredients = ingredients ;
294+ this .predicate = predicate ;
295+
296+ for (int i = ingredients .size () - 1 ; i >= 0 ; i --) {
297+ stack .push (new SearchFrame (i , db .rootBranch ));
298+ }
299+ }
368300
369301 @ Override
370302 public boolean hasNext () {
371- return index < ingredients . size ();
303+ return ! stack . isEmpty ();
372304 }
373305
374306 @ Override
375- public @ Nullable GTRecipe next () {
376- while (index < ingredients . size ()) {
377- BitSet skipSet = new BitSet ( ingredients . size ());
378- skipSet . set ( index );
379- GTRecipe r = db . findRecursive ( ingredients , db . rootBranch , predicate , index , 0 , skipSet );
380- index ++;
381- if ( r != null ) {
382- return r ;
307+ public GTRecipe next () {
308+ while (! stack . isEmpty ()) {
309+ // We stay on one frame until all ingredients have been checked
310+ SearchFrame frame = stack . peek ( );
311+
312+ if ( frame . ingredientIndex >= ingredients . get ( frame . index ). size ()) {
313+ stack . pop ();
314+ continue ;
383315 }
316+
317+ List <AbstractMapIngredient > ingredientList = ingredients .get (frame .index );
318+ AbstractMapIngredient ingredient = ingredientList .get (frame .ingredientIndex );
319+ // Increment candidate pos for next iteration
320+ frame .ingredientIndex ++;
321+ var nodes = nodesForIngredient (ingredient , frame .branch );
322+ var result = nodes .get (ingredient );
323+ if (result == null ) {
324+ continue ;
325+ }
326+
327+ // Option 1: It's a recipe
328+ if (result .left ().isPresent ()) {
329+ var recipe = result .left ().get ();
330+ if (predicate .test (recipe )) {
331+ return recipe ;
332+ }
333+ }
334+
335+ // Option 2: It's a branch, dive deeper
336+ result .ifRight (b -> {
337+ for (int j = ingredients .size () - 1 ; j >= 0 ; j --) {
338+ stack .push (new SearchFrame (j , b ));
339+ }
340+ });
384341 }
385- return null ;
342+
343+ return null ; // no more recipes
386344 }
387345
388346 /**
389347 * Reset the iterator
390348 */
391349 public void reset () {
392- this .index = 0 ;
350+ stack .clear ();
351+ for (int i = ingredients .size () - 1 ; i >= 0 ; i --) {
352+ stack .push (new SearchFrame (i , db .rootBranch ));
353+ }
393354 }
394355 }
395356}
0 commit comments