diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IRecipeHandler.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IRecipeHandler.java index 0d4cbdc1263..26dd25a5629 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IRecipeHandler.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/IRecipeHandler.java @@ -3,6 +3,7 @@ import com.gregtechceu.gtceu.api.recipe.GTRecipe; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import java.util.Comparator; @@ -25,6 +26,8 @@ public interface IRecipeHandler extends IFilteredHandler { /** * matching or handling the given recipe. + * The implementations must not produce any visible side effects when simulate == true. + * Multiple handleRecipeInner invocations with simulate == true can happen in parallel during recipe lookup. * * @param io the IO type of this recipe. always be one of the {@link IO#IN} or {@link IO#OUT} * @param recipe recipe. @@ -41,18 +44,37 @@ public interface IRecipeHandler extends IFilteredHandler { /** * container size, if it has one. otherwise -1. */ + @Contract(pure = true) default int getSize() { return -1; } + /** + * Returns a list of contents of this handler for the purposes of recipe searching. + * The implementations must be pure, e.g. should not introduce no visible side effects. + * Multiple getContents invocations can happen in parallel during recipe lookup. + * + * @return a list of ingredients for recipe lookup. + */ @NotNull + @Contract(pure = true) List getContents(); + /** + * Returns a total amount of contents (meaning of that is content type-specific) + * in this handler for purposes of recipe searching. + * The implementations must be pure, e.g. should not introduce no visible side effects. + * Multiple getContents invocations can happen in parallel during recipe lookup. + * + * @return a total amount of content within this handler + */ + @Contract(pure = true) double getTotalContentAmount(); /** * Whether the content of same capability can only be handled distinct. */ + @Contract(pure = true) default boolean isDistinct() { return false; } @@ -63,6 +85,7 @@ default boolean isDistinct() { * * @return {@code true} if this {@code IRecipeHandler} has content to be searched */ + @Contract(pure = true) default boolean shouldSearchContent() { return true; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/notifiable/NotifiableFluidTank.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/notifiable/NotifiableFluidTank.java index 6867a1ee7ee..ce98700006c 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/notifiable/NotifiableFluidTank.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/notifiable/NotifiableFluidTank.java @@ -113,10 +113,13 @@ public List handleRecipeInner(IO io, GTRecipe recipe, List {}); + Runnable[] listeners = null; + if (!simulate) { + listeners = new Runnable[storages.length]; + for (int i = 0; i < storages.length; i++) { + listeners[i] = storages[i].getOnContentsChanged(); + storages[i].setOnContentsChanged(() -> {}); + } } boolean changed = false; @@ -222,9 +225,11 @@ public List handleRecipeInner(IO io, GTRecipe recipe, List handleRecipe(IO io, GTRecipe recipe, List {}); + Runnable listener = null; + if (!simulate) { + listener = storage.getOnContentsChanged(); + storage.setOnContentsChanged(() -> {}); + } boolean changed = false; // Store the ItemStack in each slot after an operation @@ -170,8 +173,10 @@ public static List handleRecipe(IO io, GTRecipe recipe, List handlers = capabilityProxies.getOrDefault(io, Collections.emptyList()); // Only sort for non-tick outputs - if (!isTick && io.support(IO.OUT)) { + if (!isTick && io.support(IO.OUT) && !handlers.isEmpty()) { + // Copy before we sort, capabilityProxies is a live map which we should not modify during recipe search + handlers = new ArrayList<>(handlers); handlers.sort(RecipeHandlerList.COMPARATOR.reversed()); } diff --git a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferPartMachine.java b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferPartMachine.java index 73fac4dce43..51147a17358 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEPatternBufferPartMachine.java @@ -85,6 +85,7 @@ import it.unimi.dsi.fastutil.objects.*; import lombok.Getter; import lombok.Setter; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnmodifiableView; import org.jetbrains.annotations.VisibleForTesting; @@ -963,22 +964,24 @@ private void add(AEKey what, long amount) { } } + // This function is not technically pure, but it has no visible side effects, and is safe to execute in parallel + @Contract(pure = true) public List getItems() { if (itemStacks == null) { - itemStacks = new ArrayList<>(); - itemInventory.object2LongEntrySet().stream() - .map(e -> GTMath.splitStacks(e.getKey(), e.getLongValue())) - .forEach(itemStacks::addAll); + itemStacks = itemInventory.object2LongEntrySet().stream() + .flatMap(e -> GTMath.splitStacks(e.getKey(), e.getLongValue()).stream()) + .toList(); } return itemStacks; } + // This function is not technically pure, but it has no visible side effects, and is safe to execute in parallel + @Contract(pure = true) public List getFluids() { if (fluidStacks == null) { - fluidStacks = new ArrayList<>(); - fluidInventory.object2LongEntrySet().stream() - .map(e -> GTMath.splitFluidStacks(e.getKey(), e.getLongValue())) - .forEach(fluidStacks::addAll); + fluidStacks = fluidInventory.object2LongEntrySet().stream() + .flatMap(e -> GTMath.splitFluidStacks(e.getKey(), e.getLongValue()).stream()) + .toList(); } return fluidStacks; } @@ -1058,7 +1061,9 @@ public List handleItemInternal(List left, boolean simula var stack = entry.getKey(); var count = entry.getLongValue(); if (stack.isEmpty() || count == 0) { - it2.remove(); + if (!simulate) { + it2.remove(); + } continue; } if (!ingredient.test(stack)) continue; @@ -1110,7 +1115,9 @@ public List handleFluidInternal(List left, boo var stack = entry.getKey(); var count = entry.getLongValue(); if (stack.isEmpty() || count == 0) { - it2.remove(); + if (!simulate) { + it2.remove(); + } continue; } if (!ingredient.test(stack)) continue;