diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java index cd9b5189fc5..8f2e15d80e7 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java @@ -40,6 +40,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; +import org.jetbrains.annotations.Unmodifiable; import java.util.*; import java.util.stream.Collectors; @@ -72,7 +73,7 @@ public FluidIngredient copyWithModifier(FluidIngredient content, ContentModifier } @Override - public List compressIngredients(Collection ingredients) { + public List compressIngredients(@Unmodifiable Collection ingredients) { List list = new ObjectArrayList<>(ingredients.size()); for (Object item : ingredients) { if (item instanceof FluidIngredient fluid) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/ItemRecipeCapability.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/ItemRecipeCapability.java index c8494570625..fb37334542b 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/ItemRecipeCapability.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/ItemRecipeCapability.java @@ -46,6 +46,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; +import org.jetbrains.annotations.Unmodifiable; import java.util.*; import java.util.stream.Collectors; @@ -78,7 +79,7 @@ public Ingredient copyWithModifier(Ingredient content, ContentModifier modifier) } @Override - public List compressIngredients(Collection ingredients) { + public List compressIngredients(@Unmodifiable Collection ingredients) { List list = new ObjectArrayList<>(ingredients.size()); for (Object item : ingredients) { if (item instanceof Ingredient ingredient) { diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/RecipeCapability.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/RecipeCapability.java index 5c737562916..28df149687e 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/RecipeCapability.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/RecipeCapability.java @@ -25,6 +25,7 @@ import org.apache.commons.lang3.mutable.MutableInt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; import java.util.*; @@ -115,7 +116,7 @@ public boolean isRecipeSearchFilter() { return false; } - public List compressIngredients(Collection ingredients) { + public List compressIngredients(@Unmodifiable Collection ingredients) { return new ArrayList<>(ingredients); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SmartItemFilter.java b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SmartItemFilter.java index b6485f0ab7d..c26624f1c2d 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SmartItemFilter.java +++ b/src/main/java/com/gregtechceu/gtceu/api/cover/filter/SmartItemFilter.java @@ -4,8 +4,6 @@ import com.gregtechceu.gtceu.api.gui.widget.EnumSelectorWidget; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; import com.gregtechceu.gtceu.api.recipe.content.Content; -import com.gregtechceu.gtceu.api.recipe.lookup.GTRecipeLookup; -import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.MapIngredientTypeManager; import com.gregtechceu.gtceu.common.data.GTRecipeTypes; import com.gregtechceu.gtceu.utils.ItemStackHashStrategy; @@ -19,7 +17,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; -import java.util.List; +import java.util.Collections; import java.util.function.Consumer; public class SmartItemFilter implements ItemFilter { @@ -90,10 +88,11 @@ public int testItemCount(ItemStack itemStack) { private int lookup(ItemStack itemStack) { ItemStack copy = itemStack.copyWithCount(Integer.MAX_VALUE); - var ingredients = MapIngredientTypeManager.getFrom(copy, ItemRecipeCapability.CAP); - var recipe = filterMode.lookup.recurseIngredientTreeFindRecipe(List.of(ingredients), - filterMode.lookup.getLookup(), r -> true); - if (recipe == null) return 0; + var recipe = filterMode.recipeType.db() + .find(Collections.singletonMap(ItemRecipeCapability.CAP, Collections.singletonList(copy)), r -> true); + if (recipe == null) { + return 0; + } for (Content content : recipe.getInputContents(ItemRecipeCapability.CAP)) { var stacks = ItemRecipeCapability.CAP.of(content.getContent()).getItems(); for (var stack : stacks) { @@ -121,13 +120,13 @@ private enum SmartFilteringMode implements EnumSelectorWidget.SelectableEnum { private static final SmartFilteringMode[] VALUES = values(); private final String localeName; - private final GTRecipeLookup lookup; + private final GTRecipeType recipeType; private final Object2IntOpenCustomHashMap cache = new Object2IntOpenCustomHashMap<>( ItemStackHashStrategy.comparingAllButCount()); SmartFilteringMode(String localeName, GTRecipeType type) { this.localeName = localeName; - this.lookup = type.getLookup(); + this.recipeType = type; } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/GTRecipeType.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/GTRecipeType.java index 8ed88d3e3a2..f07bfe7fa34 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/recipe/GTRecipeType.java +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/GTRecipeType.java @@ -5,7 +5,8 @@ import com.gregtechceu.gtceu.api.gui.SteamTexture; import com.gregtechceu.gtceu.api.recipe.category.GTRecipeCategory; import com.gregtechceu.gtceu.api.recipe.chance.boost.ChanceBoostFunction; -import com.gregtechceu.gtceu.api.recipe.lookup.GTRecipeLookup; +import com.gregtechceu.gtceu.api.recipe.lookup.RecipeAdditionHandler; +import com.gregtechceu.gtceu.api.recipe.lookup.RecipeDB; import com.gregtechceu.gtceu.api.recipe.ui.GTRecipeTypeUI; import com.gregtechceu.gtceu.api.sound.SoundEntry; import com.gregtechceu.gtceu.data.recipe.builder.GTRecipeBuilder; @@ -30,6 +31,7 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -79,8 +81,10 @@ public class GTRecipeType implements RecipeType { private final GTRecipeCategory category; @Getter private final Map> categoryMap = new Object2ObjectOpenHashMap<>(); + private final RecipeDB db = new RecipeDB(); + @ApiStatus.Internal @Getter - private final GTRecipeLookup lookup = new GTRecipeLookup(this); + private final RecipeAdditionHandler additionHandler = new RecipeAdditionHandler(db); @Setter @Getter private boolean offsetVoltageText = false; @@ -195,7 +199,10 @@ public String toString() { public @NotNull Iterator searchRecipe(IRecipeCapabilityHolder holder, Predicate canHandle) { if (!holder.hasCapabilityProxies()) return Collections.emptyIterator(); - var iterator = getLookup().getRecipeIterator(holder, canHandle); + var iterator = db.iterator(holder, canHandle); + if (iterator == null) { + return Collections.emptyIterator(); + } boolean any = false; while (iterator.hasNext()) { GTRecipe recipe = iterator.next(); @@ -321,6 +328,10 @@ public Set getRecipesInCategory(GTRecipeCategory category) { return Collections.unmodifiableSet(categoryMap.getOrDefault(category, Set.of())); } + public @NotNull RecipeDB db() { + return db; + } + public interface ICustomRecipeLogic { /** diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/Branch.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/Branch.java index e3dfa9df558..2280ea25554 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/Branch.java +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/Branch.java @@ -5,42 +5,19 @@ import com.mojang.datafixers.util.Either; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.util.Map; -import java.util.stream.Stream; -public class Branch { +@ApiStatus.Internal +final class Branch { // Keys on this have *(should)* have unique hashcodes. private Map> nodes; // Keys on this have collisions, and must be differentiated by equality. private Map> specialNodes; - public Stream getRecipes(boolean filterHidden) { - Stream stream = null; - if (nodes != null) { - stream = nodes.values().stream() - .flatMap(either -> either.map(Stream::of, right -> right.getRecipes(filterHidden))); - } - if (specialNodes != null) { - if (stream == null) { - stream = specialNodes.values().stream() - .flatMap(either -> either.map(Stream::of, right -> right.getRecipes(filterHidden))); - } else { - stream = Stream.concat(stream, specialNodes.values().stream() - .flatMap(either -> either.map(Stream::of, right -> right.getRecipes(filterHidden)))); - } - } - if (stream == null) { - return Stream.empty(); - } - if (filterHidden) { - // stream = stream.filter(t -> !t.isHidden()); - } - return stream; - } - public boolean isEmptyBranch() { return (nodes == null || nodes.isEmpty()) && (specialNodes == null || specialNodes.isEmpty()); } @@ -60,4 +37,12 @@ public Map> getSpecialNodes() { } return specialNodes; } + + /** + * Removes all nodes in the branch + */ + public void clear() { + this.specialNodes = null; + this.nodes = null; + } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookup.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookup.java deleted file mode 100644 index 01be07758b7..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookup.java +++ /dev/null @@ -1,609 +0,0 @@ -package com.gregtechceu.gtceu.api.recipe.lookup; - -import com.gregtechceu.gtceu.GTCEu; -import com.gregtechceu.gtceu.api.capability.recipe.*; -import com.gregtechceu.gtceu.api.machine.trait.RecipeHandlerList; -import com.gregtechceu.gtceu.api.recipe.GTRecipe; -import com.gregtechceu.gtceu.api.recipe.GTRecipeType; -import com.gregtechceu.gtceu.api.recipe.RecipeHelper; -import com.gregtechceu.gtceu.api.recipe.content.Content; -import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient; -import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.AbstractMapIngredient; -import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.MapIngredientTypeManager; -import com.gregtechceu.gtceu.common.data.GTRecipeTypes; -import com.gregtechceu.gtceu.common.item.armor.PowerlessJetpack; -import com.gregtechceu.gtceu.config.ConfigHolder; -import com.gregtechceu.gtceu.utils.GTUtil; - -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.world.item.ItemStack; - -import com.mojang.datafixers.util.Either; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.ref.WeakReference; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Predicate; - -@RequiredArgsConstructor -public class GTRecipeLookup { - - private final GTRecipeType recipeType; - - @Getter - private final Branch lookup = new Branch(); - - private static final WeakHashMap> ingredientRoot = new WeakHashMap<>(); - - /** - * Finds a GTRecipe matching the Fluid and/or ItemStack Inputs in the holder. - * - * @return the GTRecipe it has found or null for no matching GTRecipe - */ - @Nullable - public GTRecipe findRecipe(final IRecipeCapabilityHolder holder) { - return find(holder, recipe -> RecipeHelper.matchRecipe(holder, recipe).isSuccess()); - } - - /** - * Prepares Items and Fluids for use in recipe search - * - * @param holder the recipe holder (usually machine) to prepare - * @return a List of Lists of AbstractMapIngredients used for finding recipes - */ - @Nullable - protected List> prepareRecipeFind(@NotNull IRecipeCapabilityHolder holder) { - // First, check if items and fluids are valid. - int totalSize = 0; - List handlers = holder.getCapabilitiesForIO(IO.IN); - - for (var handler : handlers) { - for (var entries : handler.getHandlerMap().entrySet()) { - int size = 0; - if (!entries.getKey().isRecipeSearchFilter()) { - continue; - } - for (IRecipeHandler entry : entries.getValue()) { - if (entry.getSize() != -1) { - size += entry.getSize(); - } - } - if (size == Integer.MAX_VALUE) { - return null; - } - totalSize += size; - } - } - - if (totalSize == 0) { - return null; - } - - // Build input. - List> list = new ObjectArrayList<>(totalSize); - list.addAll(fromHolder(holder)); - - // nothing was added, so return nothing - if (list.isEmpty()) return null; - return list; - } - - /** - * Finds a recipe using Items and Fluids. - * - * @param holder the holder to find recipes for. - * @param canHandle a predicate for determining if a recipe is valid - * @return the recipe found - */ - @Nullable - public GTRecipe find(@NotNull IRecipeCapabilityHolder holder, @NotNull Predicate canHandle) { - List> list = prepareRecipeFind(holder); - // couldn't build any inputs to use for search, so no recipe could be found - if (list == null) return null; - return recurseIngredientTreeFindRecipe(list, lookup, canHandle); - } - - /** - * Creates an Iterator of Recipes using Items and Fluids. - * - * @param holder the holder to find recipes for. - * @param canHandle a predicate for determining if a recipe is valid - * @return the Recipe Iterator - */ - @NotNull - public RecipeIterator getRecipeIterator(@NotNull IRecipeCapabilityHolder holder, - @NotNull Predicate canHandle) { - List> list = prepareRecipeFind(holder); - return new RecipeIterator(this.recipeType, list, canHandle); - } - - /** - * Builds a list of unique ItemStacks from the given Collection of ItemStacks. - * Used to reduce the number inputs, if for example there is more than one of the same input, - * pack them into one. - * This uses a strict comparison, so it will not pack the same item with different NBT tags, - * to allow the presence of, for example, more than one configured circuit in the input. - * - * @param inputs The Collection of GTRecipeInputs. - * @return an array of unique itemstacks. - */ - @NotNull - public static ItemStack[] uniqueItems(@NotNull Collection inputs) { - int index = 0; - ItemStack[] uniqueItems = new ItemStack[inputs.size()]; - main: - for (ItemStack input : inputs) { - if (input.isEmpty()) { - continue; - } - if (index > 0) { - for (ItemStack unique : uniqueItems) { - if (unique == null) break; - else if (GTUtil.isSameItemSameTags(input, unique)) { - continue main; - } - } - } - uniqueItems[index++] = input; - } - if (index == uniqueItems.length) { - return uniqueItems; - } - ItemStack[] retUniqueItems = new ItemStack[index]; - System.arraycopy(uniqueItems, 0, retUniqueItems, 0, index); - return retUniqueItems; - } - - /** - * Recursively finds a recipe, top level. - * - * @param ingredients the ingredients part - * @param branchRoot the root branch to search from. - * @param canHandle if the found recipe is valid - * @return a recipe - */ - @Nullable - public GTRecipe recurseIngredientTreeFindRecipe(@NotNull List> ingredients, - @NotNull Branch branchRoot, - @NotNull Predicate canHandle) { - // Try each ingredient as a starting point, adding it to the skip-list. - // The skip-list is a packed long, where each 1 bit represents an index to skip - for (int i = 0; i < ingredients.size(); i++) { - BitSet skipSet = new BitSet(); - skipSet.set(i); - GTRecipe r = recurseIngredientTreeFindRecipe(ingredients, branchRoot, canHandle, i, 0, skipSet); - if (r != null) { - return r; - } - } - return null; - } - - /** - * Recursively finds a recipe - * - * @param ingredients the ingredients part - * @param branchMap the current branch of the tree - * @param canHandle predicate to test found recipe. - * @param index the index of the wrapper to get - * @param count how deep we are in recursion, < ingredients.length - * @param skip bitmap of ingredients to skip, i.e. which ingredients are already used in the recursion. - * @return a recipe - */ - @Nullable - public GTRecipe recurseIngredientTreeFindRecipe(@NotNull List> ingredients, - @NotNull Branch branchMap, @NotNull Predicate canHandle, - int index, int count, BitSet skip) { - // exhausted all the ingredients, and didn't find anything - if (count == ingredients.size()) return null; - - // Iterate over current level of nodes. - for (AbstractMapIngredient obj : ingredients.get(index)) { - // determine the root nodes - Map> targetMap = determineRootNodes(obj, branchMap); - - Either result = targetMap.get(obj); - if (result != null) { - // if there is a recipe (left mapping), return it immediately as found, if it can be handled - // Otherwise, recurse and go to the next branch. - GTRecipe r = result.map(potentialRecipe -> canHandle.test(potentialRecipe) ? potentialRecipe : null, - potentialBranch -> diveIngredientTreeFindRecipe(ingredients, potentialBranch, canHandle, index, - count, skip)); - if (r != null) { - return r; - } - } - } - return null; - } - - /** - * Recursively finds a recipe - * - * @param ingredients the ingredients part - * @param map the current branch of the tree - * @param canHandle predicate to test found recipe. - * @param currentIndex the index of the wrapper to get - * @param count how deep we are in recursion, < ingredients.length - * @param skip bitmap of ingredients to skip, i.e. which ingredients are already used in the recursion. - * @return a recipe - */ - @Nullable - private GTRecipe diveIngredientTreeFindRecipe(@NotNull List> ingredients, - @NotNull Branch map, - @NotNull Predicate canHandle, int currentIndex, int count, - BitSet skip) { - // We loop around ingredients.size() if we reach the end. - // only end when all ingredients are exhausted, or a recipe is found - int i = (currentIndex + 1) % ingredients.size(); - while (i != currentIndex) { - // Have we already used this ingredient? If so, skip this one. - if (!(skip.get(i))) { - // Recursive call - // Increase the count, so the recursion can terminate if needed (ingredients is exhausted) - // Append the current index to the skip list - BitSet copy = (BitSet) skip.clone(); - copy.set(i); - GTRecipe found = recurseIngredientTreeFindRecipe(ingredients, map, canHandle, i, count + 1, - copy); - if (found != null) { - return found; - } - } - // increment the index if the current index is skipped, or the recipe is not found - i = (i + 1) % ingredients.size(); - } - return null; - } - - /** - * Exhaustively gathers all recipes that can be crafted with the given ingredients, into a Set. - * - * @return a Set of recipes that can be crafted with the given ingredients - */ - @Nullable - public Set findRecipeCollisions(IRecipeCapabilityHolder holder) { - List> list = prepareRecipeFind(holder); - if (list == null) return null; - Set collidingRecipes = new ObjectOpenHashSet<>(); - recurseIngredientTreeFindRecipeCollisions(list, lookup, collidingRecipes); - return collidingRecipes; - } - - /** - * @param ingredients the ingredients to search with - * @param branchRoot the root branch to start searching from - * @param collidingRecipes the list to store recipe collisions - */ - private void recurseIngredientTreeFindRecipeCollisions(@NotNull List> ingredients, - @NotNull Branch branchRoot, - @NotNull Set collidingRecipes) { - // Try each ingredient as a starting point, adding it to the skip-list. - // The skip-list is a packed long, where each 1 bit represents an index to skip - for (int i = 0; i < ingredients.size(); i++) { - recurseIngredientTreeFindRecipeCollisions(ingredients, branchRoot, i, 0, (1L << i), collidingRecipes); - } - } - - /** - * Recursively finds all colliding recipes - * - * @param ingredients the ingredients part - * @param branchMap the current branch of the tree - * @param index the index of the wrapper to get - * @param count how deep we are in recursion, < ingredients.length - * @param skip bitmap of ingredients to skip, i.e. which ingredients are already used in the recursion. - * @param collidingRecipes the set to store the recipes in - */ - @Nullable - private GTRecipe recurseIngredientTreeFindRecipeCollisions(@NotNull List> ingredients, - @NotNull Branch branchMap, int index, int count, - long skip, - @NotNull Set collidingRecipes) { - // exhausted all the ingredients, and didn't find anything - if (count == ingredients.size()) return null; - - List wr = ingredients.get(index); - // Iterate over current level of nodes. - for (AbstractMapIngredient obj : wr) { - // determine the root nodes - Map> targetMap = determineRootNodes(obj, branchMap); - - Either result = targetMap.get(obj); - if (result != null) { - // if there is a recipe (left mapping), return it immediately as found - // Otherwise, recurse and go to the next branch. - GTRecipe r = result.map(recipe -> recipe, - right -> diveIngredientTreeFindRecipeCollisions(ingredients, right, index, count, skip, - collidingRecipes)); - if (r != null) { - collidingRecipes.add(r); - } - } - } - return null; - } - - /** - * Recursively finds a recipe - * - * @param ingredients the ingredients part - * @param map the current branch of the tree - * @param currentIndex the index of the wrapper to get - * @param count how deep we are in recursion, < ingredients.length - * @param skip bitmap of ingredients to skip, i.e. which ingredients are already used in the recursion. - * @param collidingRecipes the set to store the recipes in - * @return a recipe - */ - @Nullable - private GTRecipe diveIngredientTreeFindRecipeCollisions(@NotNull List> ingredients, - @NotNull Branch map, int currentIndex, int count, long skip, - @NotNull Set collidingRecipes) { - // We loop around ingredients.size() if we reach the end. - // only end when all ingredients are exhausted, or a recipe is found - int i = (currentIndex + 1) % ingredients.size(); - while (i != currentIndex) { - // Have we already used this ingredient? If so, skip this one. - if (((skip & (1L << i)) == 0)) { - // Recursive call - // Increase the count, so the recursion can terminate if needed (ingredients is exhausted) - // Append the current index to the skip list - GTRecipe r = recurseIngredientTreeFindRecipeCollisions(ingredients, map, i, count + 1, skip | (1L << i), - collidingRecipes); - if (r != null) { - return r; - } - } - // increment the index if the current index is skipped, or the recipe is not found - i = (i + 1) % ingredients.size(); - } - return null; - } - - /** - * Retrieves a cached ingredient, or inserts a default one - * - * @param list the list to append to - * @param ingredients the ingredient to use as a default value, if not cached - * @param cache the ingredient root to retrieve from - */ - protected static void retrieveCachedIngredient(@NotNull List> list, - @NotNull List ingredients, - @NotNull WeakHashMap> cache) { - for (int i = 0; i < ingredients.size(); i++) { - AbstractMapIngredient mappedIngredient = ingredients.get(i); - // attempt to use the cached value if possible, otherwise cache for the next time - WeakReference cached = cache.get(mappedIngredient); - if (cached != null && cached.get() != null) { - ingredients.set(i, cached.get()); - } else { - cache.put(mappedIngredient, new WeakReference<>(mappedIngredient)); - } - } - list.add(ingredients); - } - - /** - * Converts a GTRecipe's {@link RecipeCapability RecipeCapabilities} into - * a list of {@link AbstractMapIngredient AbstractMapIngredients} - * - * @param recipe the recipe to use - * @return a list of all the AbstractMapIngredients comprising the recipe - */ - @NotNull - protected List> fromRecipe(@NotNull GTRecipe recipe) { - int initialCapacity = (recipe.inputs.size() + recipe.tickInputs.size()) * 2; - List> list = new ObjectArrayList<>(initialCapacity); - recipe.inputs.forEach(processCapabilityIngredients(list)); - recipe.tickInputs.forEach(processCapabilityIngredients(list)); - return list; - } - - // spotless:off - protected BiConsumer, List> processCapabilityIngredients(List> list) { - return (cap, contents) -> { - if (cap.isRecipeSearchFilter() && !contents.isEmpty()) { - List ingredients = new ArrayList<>(); - for (Content content : contents) { - ingredients.add(content.getContent()); - } - ingredients = cap.compressIngredients(ingredients); - for (Object ingredient : ingredients) { - // use the cached ingredient, if possible - retrieveCachedIngredient(list, MapIngredientTypeManager.getFrom(ingredient, cap), ingredientRoot); - } - } - }; - } - // spotless:on - - /** - * Converts a Recipe Capability holder's handlers into - * a list of {@link AbstractMapIngredient AbstractMapIngredients} - * - * @param holder the capability holder to query handlers from - * @return a list of all the AbstractMapIngredients in the handlers - */ - @NotNull - protected List> fromHolder(@NotNull IRecipeCapabilityHolder holder) { - var handlerMap = holder.getCapabilitiesFlat().getOrDefault(IO.IN, Collections.emptyMap()); - // the initial capacity is a "feel-good" value because it's faster to just grow the list - // than to calculate an accurate value. - List> list = new ObjectArrayList<>(handlerMap.size() * 8); - for (var entry : handlerMap.entrySet()) { - var cap = entry.getKey(); - var handlers = entry.getValue(); - if (!cap.isRecipeSearchFilter()) continue; - for (var handler : handlers) { - var compressed = cap.compressIngredients(handler.getContents()); - for (var ingredient : compressed) { - list.add(MapIngredientTypeManager.getFrom(ingredient, cap)); - } - } - } - return list; - } - - /** - * Removes all recipes. - */ - @ApiStatus.Internal - public void removeAllRecipes() { - this.lookup.getNodes().clear(); - this.lookup.getSpecialNodes().clear(); - this.recipeType.getCategoryMap().clear(); - } - - /** - * Compiles a recipe and adds it to the ingredient tree - * - * @param recipe the recipe to compile - * @return if the recipe was successfully compiled - */ - public boolean addRecipe(GTRecipe recipe) { - if (recipe == null) { - return false; - } - - // Add combustion fuels to the Powerless Jetpack - if (recipe.getType() == GTRecipeTypes.COMBUSTION_GENERATOR_FUELS) { - Content content = recipe.getInputContents(FluidRecipeCapability.CAP).get(0); - FluidIngredient fluid = FluidRecipeCapability.CAP.of(content.content); - PowerlessJetpack.FUELS.putIfAbsent(fluid, recipe.duration); - } - List> items = fromRecipe(recipe); - if (recurseIngredientTreeAdd(recipe, items, lookup, 0, 0)) { - recipe.recipeCategory.addRecipe(recipe); - return true; - } - return false; - } - - /** - * Adds a recipe to the map. (recursive part) - * - * @param recipe the recipe to add. - * @param ingredients list of input ingredients representing the recipe. - * @param branchMap the current branch in the recursion. - * @param index where in the ingredients list we are. - * @param count how many branches were added already. - */ - private boolean recurseIngredientTreeAdd(@NotNull GTRecipe recipe, - @NotNull List> ingredients, - @NotNull Branch branchMap, int index, int count) { - if (count >= ingredients.size()) return true; - if (index >= ingredients.size()) { - throw new RuntimeException("Index out of bounds for recurseItemTreeAdd, should not happen"); - } - // Loop through NUMBER_OF_INGREDIENTS times. - - // the current contents to be added to a node in the branch - final List current = ingredients.get(index); - final Branch branchRight = new Branch(); - Either r; - - // for every ingredient, add it to a node - for (AbstractMapIngredient obj : current) { - // determine the root nodes - Map> targetMap = determineRootNodes(obj, branchMap); - - // Either add the recipe or create a branch. - r = targetMap.compute(obj, (k, v) -> { - if (count == ingredients.size() - 1) { - // handle very last ingredient - if (v != null) { - // handle the existing branch - if (v.left().isEmpty() || v.left().get() != recipe) { - // the recipe already there was not the one being added, so there is a conflict - if (ConfigHolder.INSTANCE.dev.debug || GTCEu.isDev()) { - GTCEu.LOGGER.warn( - "Recipe duplicate or conflict found in GTRecipeType {} and was not added. See next lines for details", - BuiltInRegistries.RECIPE_TYPE.getKey(this.recipeType)); - - GTCEu.LOGGER.warn("Attempted to add GTRecipe: {}", recipe.getId()); - - if (v.left().isPresent()) { - GTCEu.LOGGER.warn("Which conflicts with: {}", v.left().get().getId()); - } else { - GTCEu.LOGGER.warn("Could not find exact duplicate/conflict."); - } - } - } - // Return the existing recipe, even on conflicts. - // If there was no conflict but a recipe was still present, it was added on an earlier recurse, - // and this will carry the result further back in the call stack - return v; - } else { - // nothing exists for this path, so end with the recipe - return Either.left(recipe); - } - } else if (v == null) { - // no existing ingredient is present, so use the new one - return Either.right(branchRight); - } - // there is an existing ingredient here already, so use it - return v; - }); - - // left branches are always either empty or contain recipes. - // If there's a recipe present, the addition is finished for this ingredient - if (r.left().isPresent()) { - if (r.left().get() == recipe) { - // Cannot return here, since each ingredient to add is a separate path to the recipe - continue; - } else { - // exit if a different recipe is already present for this path - return false; - } - } - - // recursive part: apply the addition for the next ingredient in the list, for the right branch. - // the right branch only contains ingredients, or is empty when the left branch is present - boolean addedNextBranch = r.right() - .filter(m -> recurseIngredientTreeAdd(recipe, ingredients, m, (index + 1) % ingredients.size(), - count + 1)) - .isPresent(); - - if (!addedNextBranch) { - // failed to add the next branch, so undo any made changes - if (count == ingredients.size() - 1) { - // was the final ingredient, so the mapping of it to a recipe needs to be removed - targetMap.remove(obj); - } else { - // was a regular ingredient - if (targetMap.get(obj).right().isPresent()) { - // if something was put into the map - if (targetMap.get(obj).right().get().isEmptyBranch()) { - // if what was put was empty (invalid), remove it - targetMap.remove(obj); - } - } - } - // because a branch addition failure happened, fail the recipe addition for this step - return false; - } - } - // recipe addition was successful - return true; - } - - /** - * Determine the correct root nodes for an ingredient - * - * @param ingredient the ingredient to check - * @param branchMap the branch containing the nodes - * @return the correct nodes for the ingredient - */ - @NotNull - protected static Map> determineRootNodes(@NotNull AbstractMapIngredient ingredient, - @NotNull Branch branchMap) { - return ingredient.isSpecialIngredient() ? branchMap.getSpecialNodes() : branchMap.getNodes(); - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/MapIngredientPool.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/MapIngredientPool.java new file mode 100644 index 00000000000..dd4ce3bf12e --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/MapIngredientPool.java @@ -0,0 +1,50 @@ +package com.gregtechceu.gtceu.api.recipe.lookup; + +import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.AbstractMapIngredient; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Pool for {@link AbstractMapIngredient} to save memory + */ +@ApiStatus.Internal +public final class MapIngredientPool { + + private static final Map> pool = new WeakHashMap<>(); + + /** + * Replaces values in a list of ingredients with pooled versions, + * and pools the existing ingredients if not already pooled. + * + * @param list the list + */ + static void applyPooling(@NotNull List list) { + for (int i = 0; i < list.size(); i++) { + AbstractMapIngredient ingredient = list.get(i); + var pooledReference = pool.get(ingredient); + if (pooledReference == null) { + pool.put(ingredient, new WeakReference<>(ingredient)); + continue; + } + var pooled = pooledReference.get(); + if (pooled == null) { + pool.put(ingredient, new WeakReference<>(ingredient)); + } else { + list.set(i, pooled); + } + } + } + + /** + * Clear the ingredient pool + */ + public static void clear() { + pool.clear(); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeAdditionHandler.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeAdditionHandler.java new file mode 100644 index 00000000000..1e608bc6e16 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeAdditionHandler.java @@ -0,0 +1,59 @@ +package com.gregtechceu.gtceu.api.recipe.lookup; + +import com.gregtechceu.gtceu.api.recipe.GTRecipe; + +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Manages the recipe addition lifecycle as recipes are added to an {@link StagingRecipeDB} + * and later baked into a {@link RecipeDB} + */ +@ApiStatus.Internal +@RequiredArgsConstructor +public final class RecipeAdditionHandler { + + private final @NotNull StagingRecipeDB stagingDB = new StagingRecipeDB(); + private final @NotNull RecipeDB db; + + private boolean isStaging; + + /** + * Begin the staging process + */ + @ApiStatus.Internal + public void beginStaging() { + if (isStaging) { + throw new IllegalStateException("cannot begin staging while already in staging state"); + } + this.isStaging = true; + } + + /** + * Add a recipe to the staging DB + * + * @param recipe the recipe + */ + @ApiStatus.Internal + public void addStaging(@NotNull GTRecipe recipe) { + if (!isStaging) { + throw new IllegalStateException("cannot add a staging recipe while not in staging state"); + } + stagingDB.add(recipe); + } + + /** + * Complete the staging DB and bake it into an optimized storage + */ + @ApiStatus.Internal + public void completeStaging() { + if (!isStaging) { + throw new IllegalStateException("cannot complete staging while not in staging state"); + } + db.clear(); + stagingDB.populateDB(db); + stagingDB.clear(); + this.isStaging = false; + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeDB.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeDB.java new file mode 100644 index 00000000000..1d9850a7236 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeDB.java @@ -0,0 +1,395 @@ +package com.gregtechceu.gtceu.api.recipe.lookup; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.capability.recipe.FluidRecipeCapability; +import com.gregtechceu.gtceu.api.capability.recipe.IO; +import com.gregtechceu.gtceu.api.capability.recipe.IRecipeCapabilityHolder; +import com.gregtechceu.gtceu.api.capability.recipe.RecipeCapability; +import com.gregtechceu.gtceu.api.recipe.GTRecipe; +import com.gregtechceu.gtceu.api.recipe.RecipeHelper; +import com.gregtechceu.gtceu.api.recipe.content.Content; +import com.gregtechceu.gtceu.api.recipe.ingredient.FluidIngredient; +import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.AbstractMapIngredient; +import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.MapIngredientTypeManager; +import com.gregtechceu.gtceu.common.data.GTRecipeTypes; +import com.gregtechceu.gtceu.common.item.armor.PowerlessJetpack; +import com.gregtechceu.gtceu.config.ConfigHolder; + +import net.minecraftforge.registries.ForgeRegistries; + +import com.mojang.datafixers.util.Either; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.*; + +import java.util.*; +import java.util.function.Predicate; + +/** + * Data structure storing recipes by their input ingredients + */ +public final class RecipeDB { + + private final @NotNull Branch rootBranch = new Branch(); + + /** + * Clear the DB + */ + @ApiStatus.Internal + public void clear() { + rootBranch.clear(); + } + + /** + * Find a GT Recipe + * + * @param holder the holder to search + * @return the recipe + */ + public @Nullable GTRecipe find(@NotNull IRecipeCapabilityHolder holder) { + return find(holder, r -> RecipeHelper.matchRecipe(holder, r).isSuccess()); + } + + /** + * Find a GT Recipe + * + * @param holder the holder to search + * @param predicate the predicate to determine recipe validity + * @return the recipe + */ + public @Nullable GTRecipe find(@NotNull IRecipeCapabilityHolder holder, @NotNull Predicate predicate) { + List> list = fromHolder(holder); + if (list == null) { + return null; + } + return findRecursive(list, predicate); + } + + /** + * Find a GT Recipe + * + * @param list the ingredients to search + * @param predicate the predicate to determine recipe validity + * @return the recipe + */ + @ApiStatus.Internal + @VisibleForTesting + public @Nullable GTRecipe find(@NotNull List> list, + @NotNull Predicate predicate) { + return findRecursive(list, predicate); + } + + /** + * Find a GT Recipe + * + * @param inputs the input capabilities and their associated contents to search with + * @param predicate the predicate to determine recipe validity + * @return the recipe + */ + public @Nullable GTRecipe find(@NotNull Map, List> inputs, + @NotNull Predicate predicate) { + List> list = new ArrayList<>(); + inputs.forEach((cap, content) -> { + if (!cap.isRecipeSearchFilter()) { + return; + } + var compressed = cap.compressIngredients(content); + for (var ingredient : compressed) { + list.add(MapIngredientTypeManager.getFrom(ingredient, cap)); + } + }); + return findRecursive(list, predicate); + } + + /** + * Create an iterator for a search space + * + * @param holder the holder to search + * @param predicate the predicate to determine recipe validity + * @return an iterator + */ + public @Nullable RecipeDB.RecipeIterator iterator(@NotNull IRecipeCapabilityHolder holder, + @NotNull Predicate predicate) { + List> list = fromHolder(holder); + if (list == null) { + return null; + } + return new RecipeIterator(this, list, predicate); + } + + /** + * Recursively finds a recipe. + * + * @param ingredients the ingredients to search with + * @param predicate if the found recipe is valid + * @return the recipe + */ + private @Nullable GTRecipe findRecursive(@NotNull List> ingredients, + @NotNull Predicate predicate) { + // Try each ingredient as a starting point, adding it to the skip-list. + // The skip-list is a packed long, where each 1 bit represents an index to skip + for (int i = 0; i < ingredients.size(); i++) { + BitSet skipSet = new BitSet(ingredients.size()); + skipSet.set(i); + GTRecipe r = findRecursive(ingredients, rootBranch, predicate, i, 0, skipSet); + if (r != null) { + return r; + } + } + return null; + } + + /** + * Recursively finds a recipe by checking the current branch's nodes. + * + * @param ingredients the ingredients to search with + * @param branch the branch to search + * @param predicate if the found recipe is valid + * @param index the index of the ingredient list to check + * @param count how deep we are in recursion, < ingredients.length + * @param skip bitmask of ingredients already checked + * @return the recipe + */ + private @Nullable GTRecipe findRecursive(@NotNull List> ingredients, + @NotNull Branch branch, @NotNull Predicate predicate, + int index, int count, @NotNull BitSet skip) { + // exhausted all the ingredients, and didn't find anything + if (count == ingredients.size()) { + return null; + } + + // Iterate over current level of nodes. + for (AbstractMapIngredient obj : ingredients.get(index)) { + // determine the root nodes + var nodes = nodesForIngredient(obj, branch); + var result = nodes.get(obj); + if (result == null) { + continue; + } + // if there is a recipe (left mapping), return it immediately as found, if it can be handled + // Otherwise, recurse and go to the next branch. + GTRecipe recipe = result.map(r -> predicate.test(r) ? r : null, + b -> findRecursiveDive(ingredients, b, predicate, index, count, skip)); + if (recipe != null) { + return recipe; + } + } + return null; + } + + /** + * Recursively finds a recipe by diving deeper down a path. + * + * @param ingredients the ingredients to search with + * @param branch the branch to search + * @param predicate if the found recipe is valid + * @param index the index of the ingredient list to check + * @param count how deep we are in recursion, must be < ingredients.length + * @param skip bitmask of ingredients already checked + * @return the recipe + */ + private @Nullable GTRecipe findRecursiveDive(@NotNull List> ingredients, + @NotNull Branch branch, @NotNull Predicate predicate, + int index, int count, @NotNull BitSet skip) { + // loop through all ingredients, wrapping around the end until all are tried. + for (int i = (index + 1) % ingredients.size(); i != index; i = (i + 1) % ingredients.size()) { + if (skip.get(i)) { + continue; + } + // Recursive call + // Append the current index to the skip list + skip.set(i); + // Increase the count, so the recursion can terminate if needed (ingredients is exhausted) + GTRecipe r = findRecursive(ingredients, branch, predicate, i, count + 1, skip); + if (r != null) { + return r; + } + skip.clear(i); + } + return null; + } + + /** + * Converts a Recipe Capability holder's handlers into a list of {@link AbstractMapIngredient} + * + * @param holder the capability holder to query handlers from + * @return a list of all the AbstractMapIngredients in the handlers + */ + private @Nullable List> fromHolder(@NotNull IRecipeCapabilityHolder holder) { + var handlerMap = holder.getCapabilitiesFlat().getOrDefault(IO.IN, Collections.emptyMap()); + if (handlerMap.isEmpty()) { + return null; + } + + // the initial capacity is a "feel-good" value because it's faster to just grow the list + // than to calculate an accurate value. + List> list = new ObjectArrayList<>(handlerMap.size() * 8); + handlerMap.forEach((cap, handlers) -> { + if (!cap.isRecipeSearchFilter()) { + return; + } + for (var handler : handlers) { + var compressed = cap.compressIngredients(handler.getContents()); + for (var ingredient : compressed) { + list.add(MapIngredientTypeManager.getFrom(ingredient, cap)); + } + } + }); + if (list.isEmpty()) { + return null; + } + return list; + } + + /** + * Determine the correct root nodes for an ingredient. + * + * @param ingredient the ingredient to check + * @param branch the branch containing the nodes + * @return the nodes to search for the ingredient + */ + private static @NotNull Map> nodesForIngredient(@NotNull AbstractMapIngredient ingredient, + @NotNull Branch branch) { + if (ingredient.isSpecialIngredient()) { + return branch.getSpecialNodes(); + } + return branch.getNodes(); + } + + /** + * Add a recipe. + * + * @param recipe the recipe to add + * @param ingredients the ingredients in optimal order, comprising the recipe + * @return if successful + */ + boolean add(@NotNull GTRecipe recipe, @NotNull List<@Unmodifiable List> ingredients) { + // Add combustion fuels to the Powerless Jetpack + if (recipe.getType() == GTRecipeTypes.COMBUSTION_GENERATOR_FUELS) { + Content content = recipe.getInputContents(FluidRecipeCapability.CAP).get(0); + FluidIngredient fluid = FluidRecipeCapability.CAP.of(content.content); + PowerlessJetpack.FUELS.putIfAbsent(fluid, recipe.duration); + } + if (addRecursive(recipe, ingredients, rootBranch, 0)) { + recipe.recipeCategory.addRecipe(recipe); + return true; + } + return false; + } + + /** + * Recursively adds a recipe. + * + * @param recipe the recipe to add + * @param ingredients the ingredients to find the recipe with + * @param branch the branch to add ingredients to + * @param index the index of the ingredient list to check + * @return if successful + */ + private boolean addRecursive(@NotNull GTRecipe recipe, + @NotNull List<@Unmodifiable List> ingredients, + @NotNull Branch branch, int index) { + if (index >= ingredients.size()) { + return true; + } + boolean lastIngredient = index == ingredients.size() - 1; + var current = ingredients.get(index); + for (AbstractMapIngredient ingredient : current) { + var nodes = nodesForIngredient(ingredient, branch); + var either = nodes.compute(ingredient, (k, v) -> { + if (lastIngredient) { + // last ingredient + if (v == null) { + // no existing leaf, add the recipe + return Either.left(recipe); + } + if (v.left().isEmpty() || !v.left().get().equals(recipe)) { + // empty recipe or different recipe exists already, conflict + if (ConfigHolder.INSTANCE.dev.debug || GTCEu.isDev()) { + GTCEu.LOGGER.warn( + "Recipe duplicate or conflict found in GTRecipeType {} and was not added. See next lines for details", + ForgeRegistries.RECIPE_TYPES.getKey(recipe.getType())); + if (v.left().isPresent()) { + GTCEu.LOGGER.warn("Attempted to add GTRecipe: {}, which conflicts with {}", + recipe.getId(), v.left().get().getId()); + } else { + GTCEu.LOGGER.warn("Attempted to add GTRecipe: {}, without exact duplicate/conflict", + recipe.getId()); + } + } + } + // maintain existing recipe, even on conflicts + // if there was no conflict but a recipe was still present, it was added on an earlier recurse, + // and this will carry the result further back in the call stack + return v; + } + // if there is an existing ingredient, use it, otherwise create a new branch for the ingredient + return Objects.requireNonNullElseGet(v, () -> Either.right(new Branch())); + }); + if (either.left().isPresent()) { + if (either.left().get() == recipe) { + // recipe was successfully added, continue to add the other paths + continue; + } + // there was already a recipe here, fail on the conflict + return false; + } + boolean added = either.right() + .filter(b -> addRecursive(recipe, ingredients, b, index + 1)) + .isPresent(); + if (!added) { + if (lastIngredient) { + // remove the recipe + nodes.remove(ingredient); + } else { + var child = nodes.get(ingredient); + if (child != null && child.right().isPresent()) { + var childBranch = child.right().get(); + if (childBranch.isEmptyBranch()) { + // remove the branch if it was the only thing in it + nodes.remove(ingredient); + } + } + } + return false; + } + } + return true; + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public static class RecipeIterator implements Iterator { + + private final @NotNull RecipeDB db; + private final @NotNull List> ingredients; + private final @NotNull Predicate predicate; + private int index; + + @Override + public boolean hasNext() { + return index < ingredients.size(); + } + + @Override + public @Nullable GTRecipe next() { + while (index < ingredients.size()) { + BitSet skipSet = new BitSet(ingredients.size()); + skipSet.set(index); + GTRecipe r = db.findRecursive(ingredients, db.rootBranch, predicate, index, 0, skipSet); + index++; + if (r != null) { + return r; + } + } + return null; + } + + /** + * Reset the iterator + */ + public void reset() { + this.index = 0; + } + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeIterator.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeIterator.java deleted file mode 100644 index 55515d85f4d..00000000000 --- a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeIterator.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.gregtechceu.gtceu.api.recipe.lookup; - -import com.gregtechceu.gtceu.api.recipe.GTRecipe; -import com.gregtechceu.gtceu.api.recipe.GTRecipeType; -import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.AbstractMapIngredient; - -import org.jetbrains.annotations.NotNull; - -import java.util.BitSet; -import java.util.Iterator; -import java.util.List; -import java.util.function.Predicate; - -public class RecipeIterator implements Iterator { - - int index; - List> ingredients; - @NotNull - GTRecipeType recipeMap; - @NotNull - Predicate canHandle; - - RecipeIterator(@NotNull GTRecipeType recipeMap, List> ingredients, - @NotNull Predicate canHandle) { - this.ingredients = ingredients; - this.recipeMap = recipeMap; - this.canHandle = canHandle; - } - - // does not guarantee a next recipe, just the possibility of one - @Override - public boolean hasNext() { - return ingredients != null && this.index < this.ingredients.size(); - } - - @Override - public GTRecipe next() { - // couldn't build any inputs to use for search, so no recipe could be found - if (ingredients == null) return null; - // Try each ingredient as a starting point, save current index - GTRecipe r = null; - while (index < ingredients.size()) { - BitSet skipSet = new BitSet(); - skipSet.set(index); - r = recipeMap.getLookup().recurseIngredientTreeFindRecipe(ingredients, - recipeMap.getLookup().getLookup(), canHandle, - index, 0, skipSet); - ++index; - if (r != null) break; - } - return r; - } - - public void reset() { - index = 0; - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeManagerHandler.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeManagerHandler.java index b8e83add7c8..dc0391efadf 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeManagerHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/RecipeManagerHandler.java @@ -23,7 +23,7 @@ public final class RecipeManagerHandler { /** - * Adds proxy recipes to an {@link GTRecipeType}'s {@link GTRecipeLookup} and adds them to a list. + * Adds proxy recipes to an {@link GTRecipeType}'s {@link RecipeAdditionHandler} and adds them to a list. * * @param recipesByID the recipes stored by their ID * @param gtRecipeType the recipe type to add the recipes to, which owns the proxy recipes @@ -32,7 +32,7 @@ public final class RecipeManagerHandler { public static void addProxyRecipesToLookup(@NotNull Map> recipesByID, @NotNull GTRecipeType gtRecipeType, @NotNull RecipeType proxyType, @NotNull List proxyRecipes) { - var lookup = gtRecipeType.getLookup(); + var lookup = gtRecipeType.getAdditionHandler(); proxyRecipes.clear(); recipesByID.forEach((id, recipe) -> { if (recipe.getType() != proxyType) { @@ -41,26 +41,26 @@ public static void addProxyRecipesToLookup(@NotNull Map> recipesByID, @NotNull GTRecipeType gtRecipeType) { - var lookup = gtRecipeType.getLookup(); + var lookup = gtRecipeType.getAdditionHandler(); for (var r : recipesByID.values()) { if (r.getType() != gtRecipeType) { // do not add recipes of incompatible type continue; } if (r instanceof GTRecipe recipe) { - lookup.addRecipe(recipe); + lookup.addStaging(recipe); } } } diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/StagingRecipeDB.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/StagingRecipeDB.java new file mode 100644 index 00000000000..131a26e5b55 --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/StagingRecipeDB.java @@ -0,0 +1,135 @@ +package com.gregtechceu.gtceu.api.recipe.lookup; + +import com.gregtechceu.gtceu.GTCEu; +import com.gregtechceu.gtceu.api.capability.recipe.RecipeCapability; +import com.gregtechceu.gtceu.api.recipe.GTRecipe; +import com.gregtechceu.gtceu.api.recipe.content.Content; +import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.AbstractMapIngredient; +import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.MapIngredientTypeManager; + +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.objects.*; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +@ApiStatus.Internal +public final class StagingRecipeDB { + + private final @NotNull ObjectOpenHashSet recipes = new ObjectOpenHashSet<>(); + + /** + * Add a recipe to the DB + * + * @param recipe the recipe + * @return if successful + */ + public boolean add(@NotNull GTRecipe recipe) { + return recipes.add(recipe); + } + + /** + * Clear the DB + */ + public void clear() { + recipes.clear(); + recipes.trim(); + } + + /** + * Populate a DB with the contents of the staging DB + * + * @param db the db to populate + */ + public void populateDB(@NotNull RecipeDB db) { + var frequencies = inputFrequencies(); + for (GTRecipe recipe : recipes) { + List, Object>> flattedContent = flattenedContent(recipe); + flattedContent.sort(Comparator.comparingInt(entry -> frequencies.getInt(entry.right()))); + List> inputs = new ArrayList<>(flattedContent.size()); + for (var entry : flattedContent) { + var ingredients = MapIngredientTypeManager.getFrom(entry.right(), entry.left()); + MapIngredientPool.applyPooling(ingredients); + inputs.add(ingredients); + } + boolean result = db.add(recipe, inputs); + if (!result) { + GTCEu.LOGGER.warn("failed to add recipe from staging into lookup DB: {}", recipe.getId()); + } + } + } + + /** + * @return a map of the amount of times every input is used + */ + private @NotNull Object2IntMap inputFrequencies() { + var map = new Object2IntOpenHashMap<>(); + for (GTRecipe recipe : recipes) { + recipe.inputs.forEach((cap, list) -> { + for (var input : compressedContent(list, cap)) { + map.mergeInt(input, 1, Integer::sum); + } + }); + recipe.tickInputs.forEach((cap, list) -> { + for (var input : compressedContent(list, cap)) { + map.mergeInt(input, 1, Integer::sum); + } + }); + } + return map; + } + + /** + * @param list the list of content + * @param cap the RecipeCapability for the content + * @return the compressed ingredient form of the content + */ + private static @NotNull List compressedContent(@NotNull List list, + @NotNull RecipeCapability cap) { + var contentList = list.stream() + .map(Content::getContent) + .toList(); + return cap.compressIngredients(contentList); + } + + /** + * Returns the flattened content of a recipe + * + * @param recipe the recipe + * @return the flattened content + */ + private static @NotNull List, Object>> flattenedContent(@NotNull GTRecipe recipe) { + var map = new Object2ObjectOpenHashMap, List>(); + recipe.inputs.forEach((cap, list) -> buildInputsByCap(map, cap, list)); + recipe.tickInputs.forEach((cap, list) -> buildInputsByCap(map, cap, list)); + List, Object>> list = new ArrayList<>(); + map.forEach((k, v) -> { + for (var content : v) { + list.add(Pair.of(k, content.getContent())); + } + }); + return list; + } + + /** + * Builds a map of inputs by RecipeCapability + * + * @param map the map to populate + * @param cap the recipe capability for the list + * @param list the list of inputs + */ + private static void buildInputsByCap(@NotNull Map, List> map, + @NotNull RecipeCapability cap, @NotNull List list) { + if (!cap.isRecipeSearchFilter()) { + return; + } + map.compute(cap, (k, v) -> { + if (v == null) { + return new ArrayList<>(list); + } + v.addAll(list); + return v; + }); + } +} diff --git a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/ingredient/AbstractMapIngredient.java b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/ingredient/AbstractMapIngredient.java index 235b806595c..84e13c95527 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/ingredient/AbstractMapIngredient.java +++ b/src/main/java/com/gregtechceu/gtceu/api/recipe/lookup/ingredient/AbstractMapIngredient.java @@ -22,10 +22,6 @@ public final int hashCode() { return hash; } - protected final void invalidate() { - this.hashed = false; - } - @Override public boolean equals(Object obj) { if (this == obj) return true; diff --git a/src/main/java/com/gregtechceu/gtceu/core/mixins/RecipeManagerMixin.java b/src/main/java/com/gregtechceu/gtceu/core/mixins/RecipeManagerMixin.java index 7b87452a7ec..ca3c5615e00 100644 --- a/src/main/java/com/gregtechceu/gtceu/core/mixins/RecipeManagerMixin.java +++ b/src/main/java/com/gregtechceu/gtceu/core/mixins/RecipeManagerMixin.java @@ -1,6 +1,7 @@ package com.gregtechceu.gtceu.core.mixins; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; +import com.gregtechceu.gtceu.api.recipe.lookup.MapIngredientPool; import com.gregtechceu.gtceu.api.recipe.lookup.RecipeManagerHandler; import com.gregtechceu.gtceu.common.item.armor.PowerlessJetpack; @@ -39,7 +40,7 @@ public abstract class RecipeManagerMixin { if (!(recipeType instanceof GTRecipeType gtRecipeType)) { continue; } - gtRecipeType.getLookup().removeAllRecipes(); + gtRecipeType.getAdditionHandler().beginStaging(); gtRecipeType.getProxyRecipes().forEach((type, list) -> { var recipesByID = recipes.get(type); if (recipesByID == null) { @@ -52,6 +53,8 @@ public abstract class RecipeManagerMixin { continue; } RecipeManagerHandler.addRecipesToLookup(recipesByID, gtRecipeType); + gtRecipeType.getAdditionHandler().completeStaging(); } + MapIngredientPool.clear(); } } diff --git a/src/main/java/com/gregtechceu/gtceu/integration/kjs/GregTechKubeJSPlugin.java b/src/main/java/com/gregtechceu/gtceu/integration/kjs/GregTechKubeJSPlugin.java index 8e50ae7d7bb..f3e4248f772 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/kjs/GregTechKubeJSPlugin.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/kjs/GregTechKubeJSPlugin.java @@ -54,6 +54,7 @@ import com.gregtechceu.gtceu.api.recipe.chance.logic.ChanceLogic; import com.gregtechceu.gtceu.api.recipe.ingredient.EnergyStack; import com.gregtechceu.gtceu.api.recipe.ingredient.nbtpredicate.NBTPredicates; +import com.gregtechceu.gtceu.api.recipe.lookup.MapIngredientPool; import com.gregtechceu.gtceu.api.recipe.lookup.RecipeManagerHandler; import com.gregtechceu.gtceu.api.recipe.modifier.ModifierFunction; import com.gregtechceu.gtceu.api.registry.GTRegistries; @@ -527,12 +528,14 @@ public void injectRuntimeRecipes(RecipesEventJS event, RecipeManager manager, if (!(recipeType instanceof GTRecipeType gtRecipeType)) { continue; } - gtRecipeType.getLookup().removeAllRecipes(); + gtRecipeType.getAdditionHandler().beginStaging(); gtRecipeType.getProxyRecipes().forEach((type, list) -> { RecipeManagerHandler.addProxyRecipesToLookup(recipesByName, gtRecipeType, type, list); }); RecipeManagerHandler.addRecipesToLookup(recipesByName, gtRecipeType); + gtRecipeType.getAdditionHandler().completeStaging(); } + MapIngredientPool.clear(); } private static void handleGTRecipe(Map> recipesByName, diff --git a/src/test/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogicTest.java b/src/test/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogicTest.java index 6bcf3299606..140eab4cc95 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogicTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/machine/trait/RecipeLogicTest.java @@ -34,25 +34,29 @@ public static void prepare(ServerLevel level) { LCR_RECIPE_TYPE = TestUtils.createRecipeType("recipe_logic_test_lcr", GTRecipeTypes.LARGE_CHEMICAL_RECIPES); CR_RECIPE_TYPE = TestUtils.createRecipeType("recipe_logic_test_cr", GTRecipeTypes.CHEMICAL_RECIPES); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().beginStaging(); + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_multiblock_recipelogic")) .inputItems(new ItemStack(Blocks.COBBLESTONE)) .outputItems(new ItemStack(Blocks.STONE)) .EUt(GTValues.VA[GTValues.HV]).duration(1) .buildRawRecipe()); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_multiblock_recipelogic_16_items")) .inputItems(new ItemStack(Blocks.STONE, 16)) .outputItems(new ItemStack(Blocks.STONE)) .EUt(GTValues.VA[GTValues.HV]).duration(1) .buildRawRecipe()); + LCR_RECIPE_TYPE.getAdditionHandler().completeStaging(); - CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE + CR_RECIPE_TYPE.getAdditionHandler().beginStaging(); + CR_RECIPE_TYPE.getAdditionHandler().addStaging(CR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_singleblock_recipelogic")) .inputItems(new ItemStack(Blocks.COBBLESTONE)) .outputItems(new ItemStack(Blocks.STONE)) .EUt(GTValues.VA[GTValues.HV]).duration(1) .buildRawRecipe()); + CR_RECIPE_TYPE.getAdditionHandler().completeStaging(); } private record BusHolder(ItemBusPartMachine inputBus1, ItemBusPartMachine inputBus2, ItemBusPartMachine outputBus1, diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/InputSeparationTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/InputSeparationTest.java index a9e10bb1b75..b9a1ac76edf 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/InputSeparationTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/InputSeparationTest.java @@ -30,13 +30,15 @@ public class InputSeparationTest { public static void prepare(ServerLevel level) { LCR_RECIPE_TYPE = TestUtils.createRecipeType("input_separation_tests", 3, 3, 3, 3); // Force insert the recipe into the manager. - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().beginStaging(); + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_multiblock_input_separation")) .inputItems(new ItemStack(Blocks.COBBLESTONE), new ItemStack(Blocks.ACACIA_WOOD)) .outputItems(new ItemStack(Blocks.STONE)) .EUt(GTValues.VA[GTValues.HV]).duration(1) // NBT has a schematic in it with an HV energy input hatch .buildRawRecipe()); + LCR_RECIPE_TYPE.getAdditionHandler().completeStaging(); } private record BusHolder(ItemBusPartMachine inputBus1, ItemBusPartMachine inputBus2, ItemBusPartMachine outputBus1, diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/MultipleEnergyHatchTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/MultipleEnergyHatchTest.java index de4f0b46524..665ffb634d1 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/MultipleEnergyHatchTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/MultipleEnergyHatchTest.java @@ -46,7 +46,8 @@ public static void prepare(ServerLevel level) { LCR_RECIPE_TYPE = TestUtils.createRecipeType("multiple_energy_hatch_lcr_tests", GTRecipeTypes.LARGE_CHEMICAL_RECIPES); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().beginStaging(); + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_multiple_energy_hatch_ev")) .inputItems(new ItemStack(Items.CYAN_BED)) .outputItems(new ItemStack(Items.CYAN_BED)) @@ -54,7 +55,7 @@ public static void prepare(ServerLevel level) { .duration(16) .buildRawRecipe()); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_multiple_energy_hatch_iv")) .inputItems(new ItemStack(Items.BROWN_BED)) .outputItems(new ItemStack(Items.BROWN_BED)) @@ -62,13 +63,14 @@ public static void prepare(ServerLevel level) { .duration(16) .buildRawRecipe()); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_multiple_energy_hatch_iv")) .inputItems(new ItemStack(Items.GREEN_BED)) .outputItems(new ItemStack(Items.GREEN_BED)) .EUt(GTValues.V[GTValues.LuV]) .duration(16) .buildRawRecipe()); + LCR_RECIPE_TYPE.getAdditionHandler().completeStaging(); } private record BusHolder(ItemBusPartMachine inputBus, ItemBusPartMachine outputBus, diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/OverclockLogicTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/OverclockLogicTest.java index 432b88e9763..5d9322e0e2e 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/OverclockLogicTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/OverclockLogicTest.java @@ -41,7 +41,9 @@ public static void prepare(ServerLevel level) { LCR_RECIPE_TYPE = TestUtils.createRecipeType("overclock_logic_lcr_tests", GTRecipeTypes.LARGE_CHEMICAL_RECIPES); CR_RECIPE_TYPE = TestUtils.createRecipeType("overclock_logic_cr_tests", GTRecipeTypes.CHEMICAL_RECIPES); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().beginStaging(); + CR_RECIPE_TYPE.getAdditionHandler().beginStaging(); + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_overclock_logic")) .inputItems(new ItemStack(Items.RED_BED)) .outputItems(new ItemStack(Blocks.STONE)) @@ -49,7 +51,7 @@ public static void prepare(ServerLevel level) { .duration(20) // NBT has a schematic in it with an HV energy input hatch .buildRawRecipe()); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_overclock_logic_2")) .inputItems(new ItemStack(Items.STICK)) .outputItems(new ItemStack(Blocks.STONE)) @@ -57,7 +59,7 @@ public static void prepare(ServerLevel level) { .duration(1) // NBT has a schematic in it with an HV energy input hatch .buildRawRecipe()); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_overclock_logic_3")) .inputItems(new ItemStack(Items.BROWN_BED)) .outputItems(new ItemStack(Blocks.STONE)) @@ -65,7 +67,7 @@ public static void prepare(ServerLevel level) { .duration(1) // NBT has a schematic in it with an HV energy input hatch .buildRawRecipe()); - CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE + CR_RECIPE_TYPE.getAdditionHandler().addStaging(CR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_overclock_logic_4")) .inputItems(new ItemStack(Items.RED_BED)) .outputItems(new ItemStack(Blocks.STONE)) @@ -73,7 +75,7 @@ public static void prepare(ServerLevel level) { .duration(16) // NBT has a schematic in it with an HV charged singleblock CR in it .buildRawRecipe()); - CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE + CR_RECIPE_TYPE.getAdditionHandler().addStaging(CR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_overclock_logic_5")) .inputItems(new ItemStack(Items.BROWN_BED)) .outputItems(new ItemStack(Blocks.STONE)) @@ -81,6 +83,8 @@ public static void prepare(ServerLevel level) { .duration(16) // NBT has a schematic in it with an HV charged singleblock CR in it .buildRawRecipe()); + LCR_RECIPE_TYPE.getAdditionHandler().completeStaging(); + CR_RECIPE_TYPE.getAdditionHandler().completeStaging(); } private record BusHolder(ItemBusPartMachine inputBus1, ItemBusPartMachine inputBus2, ItemBusPartMachine outputBus1, diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredientTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredientTest.java index b9ea29bc8ce..36a06165618 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredientTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderFluidIngredientTest.java @@ -78,7 +78,14 @@ public static void prepare(ServerLevel level) { CENTRIFUGE_RECIPE_TYPE = TestUtils.createRecipeType("ranged_fluid_ingredient_centrifuge_tests", GTRecipeTypes.CENTRIFUGE_RECIPES); - CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE + var CRHandler = CR_RECIPE_TYPE.getAdditionHandler(); + CRHandler.beginStaging(); + var LCRHandler = LCR_RECIPE_TYPE.getAdditionHandler(); + LCRHandler.beginStaging(); + var centHandler = CENTRIFUGE_RECIPE_TYPE.getAdditionHandler(); + centHandler.beginStaging(); + + CRHandler.addStaging(CR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_input_fluid_cr")) .inputFluidsRanged(CR_IN, UniformInt.of(0, 9)) .inputItems(COBBLE) @@ -87,7 +94,7 @@ public static void prepare(ServerLevel level) { .duration(2) .buildRawRecipe()); - CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE + CRHandler.addStaging(CR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_output_fluid_cr")) .inputFluids(CR_OUT) .outputFluidsRanged(REDSTONE, UniformInt.of(0, 9)) @@ -95,7 +102,7 @@ public static void prepare(ServerLevel level) { .duration(2) .buildRawRecipe()); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCRHandler.addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_input_fluid_lcr")) .inputFluidsRanged(LCR_IN, UniformInt.of(0, 9)) .inputFluids(RUBBER) @@ -104,7 +111,7 @@ public static void prepare(ServerLevel level) { .duration(2) .buildRawRecipe()); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCRHandler.addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_output_fluid_lcr")) .inputFluids(LCR_OUT) .outputFluidsRanged(REDSTONE, UniformInt.of(0, 9)) @@ -112,7 +119,7 @@ public static void prepare(ServerLevel level) { .duration(2) .buildRawRecipe()); - CENTRIFUGE_RECIPE_TYPE.getLookup().addRecipe(CENTRIFUGE_RECIPE_TYPE + centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_input_fluid_cent")) .inputFluidsRanged(LCENT_IN, UniformInt.of(0, 40)) .inputItems(COBBLE) @@ -121,13 +128,17 @@ public static void prepare(ServerLevel level) { .duration(4) .buildRawRecipe()); - CENTRIFUGE_RECIPE_TYPE.getLookup().addRecipe(CENTRIFUGE_RECIPE_TYPE + centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_output_fluid_cent")) .inputFluids(LCENT_OUT) .outputFluidsRanged(REDSTONE, UniformInt.of(0, 40)) .EUt(GTValues.V[GTValues.IV]) .duration(4) .buildRawRecipe()); + + CRHandler.completeStaging(); + LCRHandler.completeStaging(); + centHandler.completeStaging(); } private static MetaMachine getMetaMachine(BlockEntity entity) { diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredientTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredientTest.java index c2264960355..51120392ffb 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredientTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/IntProviderIngredientTest.java @@ -72,7 +72,15 @@ public static void prepare(ServerLevel level) { GTRecipeTypes.LARGE_CHEMICAL_RECIPES); CENTRIFUGE_RECIPE_TYPE = TestUtils.createRecipeType("ranged_ingredient_centrifuge_tests", GTRecipeTypes.CENTRIFUGE_RECIPES); - CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE + + var CRHandler = CR_RECIPE_TYPE.getAdditionHandler(); + CRHandler.beginStaging(); + var LCRHandler = LCR_RECIPE_TYPE.getAdditionHandler(); + LCRHandler.beginStaging(); + var centHandler = CENTRIFUGE_RECIPE_TYPE.getAdditionHandler(); + centHandler.beginStaging(); + + CRHandler.addStaging(CR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_input_item_cr")) .inputItemsRanged(CR_IN, UniformInt.of(0, 9)) .inputItems(COBBLE) @@ -81,7 +89,7 @@ public static void prepare(ServerLevel level) { .duration(2) .buildRawRecipe()); - CR_RECIPE_TYPE.getLookup().addRecipe(CR_RECIPE_TYPE + CRHandler.addStaging(CR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_output_item_cr")) .inputItems(CR_OUT) .outputItemsRanged(STONE, UniformInt.of(0, 9)) @@ -89,7 +97,7 @@ public static void prepare(ServerLevel level) { .duration(2) .buildRawRecipe()); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCRHandler.addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_input_item_lcr")) .inputItemsRanged(LCR_IN, UniformInt.of(0, 9)) .inputItems(COBBLE) @@ -98,7 +106,7 @@ public static void prepare(ServerLevel level) { .duration(2) .buildRawRecipe()); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCRHandler.addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_output_item_lcr")) .inputItems(LCR_OUT) .outputItemsRanged(STONE, UniformInt.of(0, 9)) @@ -106,7 +114,7 @@ public static void prepare(ServerLevel level) { .duration(2) .buildRawRecipe()); - CENTRIFUGE_RECIPE_TYPE.getLookup().addRecipe(CENTRIFUGE_RECIPE_TYPE + centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_input_item_cent")) .inputItemsRanged(LCENT_IN, UniformInt.of(0, 4)) .inputItems(COBBLE) @@ -115,13 +123,17 @@ public static void prepare(ServerLevel level) { .duration(4) .buildRawRecipe()); - CENTRIFUGE_RECIPE_TYPE.getLookup().addRecipe(CENTRIFUGE_RECIPE_TYPE + centHandler.addStaging(CENTRIFUGE_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_ranged_output_item_cent")) .inputItems(LCENT_OUT) .outputItemsRanged(STONE, UniformInt.of(0, 4)) .EUt(GTValues.V[GTValues.IV]) .duration(4) .buildRawRecipe()); + + CRHandler.completeStaging(); + LCRHandler.completeStaging(); + centHandler.completeStaging(); } private static MetaMachine getMetaMachine(BlockEntity entity) { diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/NBTPredicateTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/NBTPredicateTest.java index 5356f73c001..3946b66fe8c 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/NBTPredicateTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/ingredient/NBTPredicateTest.java @@ -35,7 +35,10 @@ public class NBTPredicateTest { @BeforeBatch(batch = "NBTPredicateTest") public static void prepare(ServerLevel level) { CR_RECIPE_TYPE = TestUtils.createRecipeType("nbt_predicate_ingredient_cr_tests", CHEMICAL_RECIPES); - CR_RECIPE_TYPE.getLookup().addRecipe( + var CRHandler = CR_RECIPE_TYPE.getAdditionHandler(); + CRHandler.beginStaging(); + + CRHandler.addStaging( CR_RECIPE_TYPE.recipeBuilder("nbt_predicate_test") .inputItemNbtPredicate(new ItemStack(Items.FEATHER), eq("foo", "bar")) .outputItems(new ItemStack(Items.COAL)) @@ -43,7 +46,7 @@ public static void prepare(ServerLevel level) { .duration(5) .buildRawRecipe()); - CR_RECIPE_TYPE.getLookup().addRecipe( + CRHandler.addStaging( CR_RECIPE_TYPE.recipeBuilder("nbt_predicate_test_chanced") .chance(4000) .inputItemNbtPredicate(new ItemStack(Items.FEATHER), eq("bin", "bar")) @@ -53,7 +56,7 @@ public static void prepare(ServerLevel level) { .duration(4) .buildRawRecipe()); - CR_RECIPE_TYPE.getLookup().addRecipe( + CRHandler.addStaging( CR_RECIPE_TYPE.recipeBuilder("nbt_predicate_test_ranged") .inputItemRanged(new IntProviderIngredient(new NBTPredicateIngredient( new ItemStack(Items.FEATHER), eq("bash", "bar")), UniformInt.of(0, 4))) @@ -62,7 +65,7 @@ public static void prepare(ServerLevel level) { .duration(4) .buildRawRecipe()); - CR_RECIPE_TYPE.getLookup().addRecipe( + CRHandler.addStaging( CR_RECIPE_TYPE.recipeBuilder("nbt_predicate_test_chanced_ranged") .chance(4000) .inputItemRanged(new IntProviderIngredient(new NBTPredicateIngredient( @@ -72,6 +75,7 @@ public static void prepare(ServerLevel level) { .EUt(GTValues.V[GTValues.HV]) .duration(4) .buildRawRecipe()); + CRHandler.completeStaging(); } @GameTest(template = "empty", batch = "NBTPredicateTest") diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookupTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookupTest.java index 6c471a3d931..0cde5b0ac50 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookupTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/GTRecipeLookupTest.java @@ -31,48 +31,49 @@ @GameTestHolder(GTCEu.MOD_ID) public class GTRecipeLookupTest { - private static GTRecipeLookup LOOKUP; + private static RecipeDB DB; private static final Predicate ALWAYS_TRUE = gtRecipe -> true; private static final Predicate ALWAYS_FALSE = gtRecipe -> false; - private static GTRecipeType RECIPE_TYPE; private static GTRecipe SMELT_STONE, SMELT_ACACIA_WOOD, SMELT_BIRCH_WOOD, SMELT_CHERRY_WOOD; private static GTRecipe RANGED_INPUT_ITEM, RANGED_INPUT_FLUID, RANGED_INPUT_BOTH; @BeforeBatch(batch = "GTRecipeLookup") public static void prepare(ServerLevel level) { - RECIPE_TYPE = TestUtils.createRecipeType("recipe_lookup"); - LOOKUP = RECIPE_TYPE.getLookup(); + GTRecipeType recipeType = TestUtils.createRecipeType("recipe_lookup"); + RecipeAdditionHandler handler = recipeType.getAdditionHandler(); + DB = recipeType.db(); - SMELT_STONE = RECIPE_TYPE.recipeBuilder("smelt_stone") + SMELT_STONE = recipeType.recipeBuilder("smelt_stone") .inputItems(Items.COBBLESTONE, 1) .outputItems(Items.STONE, 1) .buildRawRecipe(); - SMELT_ACACIA_WOOD = RECIPE_TYPE.recipeBuilder("smelt_acacia_wood") + GTRecipe SMELT_ACACIA_WOOD = recipeType.recipeBuilder("smelt_acacia_wood") .inputItems(Items.ACACIA_WOOD, 1) .outputItems(Items.CHARCOAL, 1) .buildRawRecipe(); - SMELT_BIRCH_WOOD = RECIPE_TYPE.recipeBuilder("smelt_birch_wood") + SMELT_BIRCH_WOOD = recipeType.recipeBuilder("smelt_birch_wood") .inputItems(Items.BIRCH_WOOD, 1) .outputItems(Items.CHARCOAL, 1) .buildRawRecipe(); - SMELT_CHERRY_WOOD = RECIPE_TYPE.recipeBuilder("smelt_cherry_wood") + SMELT_CHERRY_WOOD = recipeType.recipeBuilder("smelt_cherry_wood") .inputItems(Items.CHERRY_WOOD, 16) .outputItems(Items.CHARCOAL, 1) .buildRawRecipe(); - RANGED_INPUT_ITEM = RECIPE_TYPE.recipeBuilder("ranged_input_item") + RANGED_INPUT_ITEM = recipeType.recipeBuilder("ranged_input_item") .inputItemsRanged(Items.RED_WOOL, UniformInt.of(0, 4)) .outputItems(Items.CHARCOAL, 1) .buildRawRecipe(); - RANGED_INPUT_FLUID = RECIPE_TYPE.recipeBuilder("ranged_input_fluid") + RANGED_INPUT_FLUID = recipeType.recipeBuilder("ranged_input_fluid") .inputFluidsRanged(GTMaterials.Helium.getFluid(1), UniformInt.of(0, 4)) .outputItems(Items.CHARCOAL, 1) .buildRawRecipe(); - RANGED_INPUT_BOTH = RECIPE_TYPE.recipeBuilder("ranged_input_both") + RANGED_INPUT_BOTH = recipeType.recipeBuilder("ranged_input_both") .inputItemsRanged(Items.BLUE_WOOL, UniformInt.of(0, 4)) .inputFluidsRanged(GTMaterials.Iron.getFluid(1), UniformInt.of(0, 4)) .outputItems(Items.CHARCOAL, 1) .buildRawRecipe(); + handler.beginStaging(); for (GTRecipe recipe : List.of(SMELT_STONE, SMELT_ACACIA_WOOD, SMELT_BIRCH_WOOD, @@ -80,8 +81,9 @@ public static void prepare(ServerLevel level) { RANGED_INPUT_ITEM, RANGED_INPUT_FLUID, RANGED_INPUT_BOTH)) { - LOOKUP.addRecipe(recipe); + handler.addStaging(recipe); } + handler.completeStaging(); } private static List> createIngredients(ItemStack... stacks) { @@ -106,7 +108,7 @@ private static List> createIngredients(List recipe.inputs .getOrDefault(ItemRecipeCapability.CAP, List.of()) .stream() @@ -177,7 +178,7 @@ public static void recipeLookupCustomCountCanHandleTest(GameTestHelper helper) { // Do a recipe check with a condition that requires at least 32 ingredients in the inputs // The recipe has 8, so this should fail - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe(ingredients, LOOKUP.getLookup(), recipe -> recipe.inputs + resultRecipe = DB.find(ingredients, recipe -> recipe.inputs .getOrDefault(ItemRecipeCapability.CAP, List.of()) .stream() .allMatch(content -> ((SizedIngredient) content.getContent()).getAmount() > 32)); @@ -192,8 +193,7 @@ public static void recipeLookupCustomCountCanHandleTest(GameTestHelper helper) { public static void recipeLookupSimpleRangedItemSuccessTest(GameTestHelper helper) { var ingredients = createIngredients(new ItemStack(Items.RED_WOOL, 4)); for (int i = 0; i < 100; i++) { - GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe(ingredients, LOOKUP.getLookup(), - ALWAYS_TRUE); + GTRecipe resultRecipe = DB.find(ingredients, ALWAYS_TRUE); helper.assertTrue(RANGED_INPUT_ITEM.equals(resultRecipe), "GT Recipe should be ranged_input_item, instead was " + resultRecipe + ". Failed on check " + i); } @@ -206,8 +206,7 @@ public static void recipeLookupSimpleRangedItemSuccessTest(GameTestHelper helper public static void recipeLookupSimpleRangedFluidSuccessTest(GameTestHelper helper) { var ingredients = createIngredients(GTMaterials.Helium.getFluid(4)); for (int i = 0; i < 100; i++) { - GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe(ingredients, LOOKUP.getLookup(), - ALWAYS_TRUE); + GTRecipe resultRecipe = DB.find(ingredients, ALWAYS_TRUE); helper.assertTrue(RANGED_INPUT_FLUID.equals(resultRecipe), "GT Recipe should be ranged_input_fluid, instead was " + resultRecipe + ". Failed on check " + i); } @@ -222,8 +221,7 @@ public static void recipeLookupSimpleRangedItemFluidSuccessTest(GameTestHelper h createIngredients(new ItemStack(Items.BLUE_WOOL, 4)), createIngredients(GTMaterials.Iron.getFluid(4))); for (int i = 0; i < 100; i++) { - GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe(ingredients, LOOKUP.getLookup(), - ALWAYS_TRUE); + GTRecipe resultRecipe = DB.find(ingredients, ALWAYS_TRUE); helper.assertTrue(RANGED_INPUT_BOTH.equals(resultRecipe), "GT Recipe should be raged_input_both, instead was " + resultRecipe + ". Failed on check " + i); } diff --git a/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/ingredient/item/NBTItemStackMapIngredientLookupTest.java b/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/ingredient/item/NBTItemStackMapIngredientLookupTest.java index fbcf29d8d45..7230e45b58b 100644 --- a/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/ingredient/item/NBTItemStackMapIngredientLookupTest.java +++ b/src/test/java/com/gregtechceu/gtceu/api/recipe/lookup/ingredient/item/NBTItemStackMapIngredientLookupTest.java @@ -3,7 +3,8 @@ import com.gregtechceu.gtceu.GTCEu; import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; -import com.gregtechceu.gtceu.api.recipe.lookup.GTRecipeLookup; +import com.gregtechceu.gtceu.api.recipe.lookup.RecipeAdditionHandler; +import com.gregtechceu.gtceu.api.recipe.lookup.RecipeDB; import com.gregtechceu.gtceu.gametest.util.TestUtils; import net.minecraft.gametest.framework.BeforeBatch; @@ -27,18 +28,18 @@ @GameTestHolder(GTCEu.MOD_ID) public class NBTItemStackMapIngredientLookupTest { - private static GTRecipeLookup LOOKUP; + private static RecipeDB DB; private static final Predicate ALWAYS_TRUE = gtRecipe -> true; private static final Predicate ALWAYS_FALSE = gtRecipe -> false; - private static GTRecipeType RECIPE_TYPE; private static GTRecipe PARTIAL_TAG_1, PARTIAL_TAG_2, STRICT_TAG_1, STRICT_TAG_2; private static CompoundTag tag1, tag2; @BeforeBatch(batch = "NBTItemStackMapIngredientLookup") public static void prepare(ServerLevel level) { - RECIPE_TYPE = TestUtils.createRecipeType("NBT_item_stack_map_ingredient_lookup"); - LOOKUP = RECIPE_TYPE.getLookup(); + GTRecipeType recipeType = TestUtils.createRecipeType("NBT_item_stack_map_ingredient_lookup"); + RecipeAdditionHandler handler = recipeType.getAdditionHandler(); + DB = recipeType.db(); tag1 = new CompoundTag(); tag1.putString("tag1", "tag1"); @@ -64,32 +65,34 @@ public static void prepare(ServerLevel level) { AbstractIngredient STRICT_TAG_1_INGREDIENT = createStrictTaggedIngredient(Items.GREEN_BED, tag1); AbstractIngredient STRICT_TAG_2_INGREDIENT = createStrictTaggedIngredient(Items.BLUE_BED, tag2); - PARTIAL_TAG_1 = RECIPE_TYPE.recipeBuilder("partial_match_NBT_1") + PARTIAL_TAG_1 = recipeType.recipeBuilder("partial_match_NBT_1") .inputItems(PARTIAL_TAG_1_INGREDIENT) .outputItems(Items.RED_BED, 1) .buildRawRecipe(); - PARTIAL_TAG_2 = RECIPE_TYPE.recipeBuilder("partial_match_NBT_2") + PARTIAL_TAG_2 = recipeType.recipeBuilder("partial_match_NBT_2") .inputItems(PARTIAL_TAG_2_INGREDIENT) .outputItems(Items.BROWN_BED, 1) .buildRawRecipe(); - STRICT_TAG_1 = RECIPE_TYPE.recipeBuilder("strict_match_NBT_1") + STRICT_TAG_1 = recipeType.recipeBuilder("strict_match_NBT_1") .inputItems(STRICT_TAG_1_INGREDIENT) .outputItems(Items.GREEN_BED, 1) .buildRawRecipe(); - STRICT_TAG_2 = RECIPE_TYPE.recipeBuilder("strict_match_NBT_2") + STRICT_TAG_2 = recipeType.recipeBuilder("strict_match_NBT_2") .inputItems(STRICT_TAG_2_INGREDIENT) .outputItems(Items.BLUE_BED, 1) .buildRawRecipe(); + handler.beginStaging(); for (GTRecipe recipe : List.of(PARTIAL_TAG_1, PARTIAL_TAG_2, STRICT_TAG_1, STRICT_TAG_2)) { - LOOKUP.addRecipe(recipe); + handler.addStaging(recipe); } + handler.completeStaging(); } private static StrictNBTIngredient createStrictTaggedIngredient(Item item, CompoundTag tag) { @@ -103,32 +106,28 @@ private static StrictNBTIngredient createStrictTaggedIngredient(Item item, Compo @GameTest(template = "empty", batch = "NBTItemStackMapIngredientLookup") public static void NBTItemStackMapIngredientMatchingPartialTag1Test(GameTestHelper helper) { // Partial tag 1 fits in Partial tag 1 - GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.RED_BED, tag1))), - LOOKUP.getLookup(), ALWAYS_TRUE); + GTRecipe resultRecipe = DB.find( + List.of(PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.RED_BED, tag1))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == PARTIAL_TAG_1, "GT Recipe should be PARTIAL_TAG_1, instead was " + resultRecipe); // Partial tag 2 fits in Partial tag 1 - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.RED_BED, tag2))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.RED_BED, tag2))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == PARTIAL_TAG_1, "GT Recipe should be PARTIAL_TAG_1, instead was " + resultRecipe); // Strict tag 1 and 2 should never fit in partial tag 1 - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.RED_BED, tag1))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.RED_BED, tag1))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.RED_BED, tag2))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.RED_BED, tag2))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); helper.succeed(); } @@ -136,31 +135,27 @@ public static void NBTItemStackMapIngredientMatchingPartialTag1Test(GameTestHelp @GameTest(template = "empty", batch = "NBTItemStackMapIngredientLookup") public static void NBTItemStackMapIngredientMatchingPartialTag2Test(GameTestHelper helper) { // Partial tag 1 should not fit in partial tag 2 - GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.BROWN_BED, tag1))), - LOOKUP.getLookup(), ALWAYS_TRUE); + GTRecipe resultRecipe = DB.find( + List.of(PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.BROWN_BED, tag1))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); // Partial tag 2 fits in Partial tag 2 - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.BROWN_BED, tag2))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.BROWN_BED, tag2))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == PARTIAL_TAG_2, "GT Recipe should be PARTIAL_TAG_2, instead was " + resultRecipe); // Strict tag 1 and 2 should never fit in partial tag 2 - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.BROWN_BED, tag1))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.BROWN_BED, tag1))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.BROWN_BED, tag2))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.BROWN_BED, tag2))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); helper.succeed(); } @@ -168,31 +163,27 @@ public static void NBTItemStackMapIngredientMatchingPartialTag2Test(GameTestHelp @GameTest(template = "empty", batch = "NBTItemStackMapIngredientLookup") public static void NBTItemStackMapIngredientMatchingStrictTag1Test(GameTestHelper helper) { // Partial tag 1 and 2 should not fit in strict tag 1 - GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.GREEN_BED, tag1))), - LOOKUP.getLookup(), ALWAYS_TRUE); + GTRecipe resultRecipe = DB.find( + List.of(PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.GREEN_BED, tag1))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.GREEN_BED, tag2))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.GREEN_BED, tag2))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); // Strict tag 1 should fit in strict tag 1 - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.GREEN_BED, tag1))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.GREEN_BED, tag1))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == STRICT_TAG_1, "GT Recipe should be STRICT_TAG_1, instead was " + resultRecipe); // Strict tag 2 should not fit in strict tag 1 - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.GREEN_BED, tag2))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.GREEN_BED, tag2))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); helper.succeed(); } @@ -200,30 +191,26 @@ public static void NBTItemStackMapIngredientMatchingStrictTag1Test(GameTestHelpe @GameTest(template = "empty", batch = "NBTItemStackMapIngredientLookup") public static void NBTItemStackMapIngredientMatchingStrictTag2Test(GameTestHelper helper) { // Partial tag 1 and 2 should not fit in strict tag 2 - GTRecipe resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.BLUE_BED, tag1))), - LOOKUP.getLookup(), ALWAYS_TRUE); + GTRecipe resultRecipe = DB.find( + List.of(PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.BLUE_BED, tag1))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.BLUE_BED, tag2))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(PartialNBTItemStackMapIngredient.from(PartialNBTIngredient.of(Items.BLUE_BED, tag2))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); // Strict tag 1 should not fit in strict tag 2 - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.BLUE_BED, tag1))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.BLUE_BED, tag1))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == null, "GT Recipe should be null, instead was " + resultRecipe); // Strict tag 2 should fit in strict tag 2 - resultRecipe = LOOKUP.recurseIngredientTreeFindRecipe( - List.of( - StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.BLUE_BED, tag2))), - LOOKUP.getLookup(), ALWAYS_TRUE); + resultRecipe = DB.find( + List.of(StrictNBTItemStackMapIngredient.from(createStrictTaggedIngredient(Items.BLUE_BED, tag2))), + ALWAYS_TRUE); helper.assertTrue(resultRecipe == STRICT_TAG_2, "GT Recipe should be STRICT_TAG_2, instead was " + resultRecipe); helper.succeed(); diff --git a/src/test/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/AssemblyLineTests.java b/src/test/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/AssemblyLineTests.java index 11a0b54e1e9..e8df6da7862 100644 --- a/src/test/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/AssemblyLineTests.java +++ b/src/test/java/com/gregtechceu/gtceu/common/machine/multiblock/electric/AssemblyLineTests.java @@ -33,7 +33,10 @@ public class AssemblyLineTests { @BeforeBatch(batch = "Assline") public static void prepare(ServerLevel level) { ASSLINE_RECIPE_TYPE = TestUtils.createRecipeType("assline_tests", ASSEMBLY_LINE_RECIPES); - ASSLINE_RECIPE_TYPE.getLookup().addRecipe(ASSLINE_RECIPE_TYPE + var assLineHandler = ASSLINE_RECIPE_TYPE.getAdditionHandler(); + assLineHandler.beginStaging(); + + assLineHandler.addStaging(ASSLINE_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_assline")) .inputItems(new ItemStack(Blocks.COBBLESTONE), new ItemStack(Blocks.ACACIA_WOOD)) .inputFluids(new FluidStack(Fluids.WATER, 1), new FluidStack(Fluids.LAVA, 1)) @@ -41,6 +44,7 @@ public static void prepare(ServerLevel level) { .EUt(GTValues.VA[GTValues.HV]).duration(1) // NBT has a schematic in it with an EV energy input hatch .buildRawRecipe()); + assLineHandler.completeStaging(); } private record BusHolder(ItemBusPartMachine inputBus1, ItemBusPartMachine inputBus2, diff --git a/src/test/java/com/gregtechceu/gtceu/common/recipe/condition/AdjacentFluidConditionTest.java b/src/test/java/com/gregtechceu/gtceu/common/recipe/condition/AdjacentFluidConditionTest.java index f72991eaae5..3a1adb5ac59 100644 --- a/src/test/java/com/gregtechceu/gtceu/common/recipe/condition/AdjacentFluidConditionTest.java +++ b/src/test/java/com/gregtechceu/gtceu/common/recipe/condition/AdjacentFluidConditionTest.java @@ -34,7 +34,8 @@ public class AdjacentFluidConditionTest { public static void prepare(ServerLevel level) { ROCK_BREAKER_RECIPE_TYPE = TestUtils.createRecipeType("adjacent_fluid_conditions_tests", GTRecipeTypes.ROCK_BREAKER_RECIPES); - ROCK_BREAKER_RECIPE_TYPE.getLookup().addRecipe(ROCK_BREAKER_RECIPE_TYPE + ROCK_BREAKER_RECIPE_TYPE.getAdditionHandler().beginStaging(); + ROCK_BREAKER_RECIPE_TYPE.getAdditionHandler().addStaging(ROCK_BREAKER_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_adjacent_fluid_conditions")) .inputItems(new ItemStack(Blocks.COBBLESTONE)) .outputItems(new ItemStack(Blocks.STONE)) @@ -43,7 +44,7 @@ public static void prepare(ServerLevel level) { .duration(8) .buildRawRecipe()); - ROCK_BREAKER_RECIPE_TYPE.getLookup().addRecipe(ROCK_BREAKER_RECIPE_TYPE + ROCK_BREAKER_RECIPE_TYPE.getAdditionHandler().addStaging(ROCK_BREAKER_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_adjacent_fluid_conditions_multiple_fluids")) .inputItems(new ItemStack(Blocks.OAK_WOOD)) .outputItems(new ItemStack(Items.CHARCOAL)) @@ -51,6 +52,7 @@ public static void prepare(ServerLevel level) { .EUt(GTValues.VA[GTValues.HV]) .duration(8) .buildRawRecipe()); + ROCK_BREAKER_RECIPE_TYPE.getAdditionHandler().completeStaging(); } // Test for checking if the rock breaker works when the condition is fulfilled diff --git a/src/test/java/com/gregtechceu/gtceu/gametest/util/TestUtils.java b/src/test/java/com/gregtechceu/gtceu/gametest/util/TestUtils.java index 58273e6a5ff..6cfee1c6fc8 100644 --- a/src/test/java/com/gregtechceu/gtceu/gametest/util/TestUtils.java +++ b/src/test/java/com/gregtechceu/gtceu/gametest/util/TestUtils.java @@ -163,7 +163,8 @@ public static void formMultiblock(MultiblockControllerMachine controller) { */ public static GTRecipeType createRecipeTypeAndInsertRecipe(String name, GTRecipeType original) { GTRecipeType type = createRecipeType(name, original); - type.getLookup().addRecipe(type + type.getAdditionHandler().beginStaging(); + type.getAdditionHandler().addStaging(type .recipeBuilder(GTCEu.id("test_recipe")) .inputItems(new ItemStack(Items.COBBLESTONE)) .outputItems(new ItemStack(Blocks.STONE)) diff --git a/src/test/java/com/gregtechceu/gtceu/integration/ae2/machine/PatternBufferTest.java b/src/test/java/com/gregtechceu/gtceu/integration/ae2/machine/PatternBufferTest.java index d1e7ca4a4f5..ce1ed6b1248 100644 --- a/src/test/java/com/gregtechceu/gtceu/integration/ae2/machine/PatternBufferTest.java +++ b/src/test/java/com/gregtechceu/gtceu/integration/ae2/machine/PatternBufferTest.java @@ -47,13 +47,14 @@ public static void prepare(ServerLevel level) { LCR_RECIPE_TYPE = TestUtils.createRecipeTypeAndInsertRecipe("pattern_buffer_tests", GTRecipeTypes.LARGE_CHEMICAL_RECIPES); - LCR_RECIPE_TYPE.getLookup().addRecipe(LCR_RECIPE_TYPE + LCR_RECIPE_TYPE.getAdditionHandler().addStaging(LCR_RECIPE_TYPE .recipeBuilder(GTCEu.id("test_recipe_pattern_buffer")) .id(GTCEu.id("test_recipe_pattern_buffer")) .inputItems(new ItemStack(Items.RED_BED)) .outputItems(new ItemStack(Blocks.BROWN_BED)) .EUt(GTValues.V[GTValues.EV]) .duration(1).buildRawRecipe()); + LCR_RECIPE_TYPE.getAdditionHandler().completeStaging(); } private record BusHolder(ItemBusPartMachine inputBus1, ItemBusPartMachine inputBus2, ItemBusPartMachine outputBus1,