Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +26,8 @@ public interface IRecipeHandler<K> extends IFilteredHandler<K> {

/**
* 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.
Expand All @@ -41,18 +44,37 @@ public interface IRecipeHandler<K> extends IFilteredHandler<K> {
/**
* 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<Object> 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;
}
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,13 @@ public List<FluidIngredient> handleRecipeInner(IO io, GTRecipe recipe, List<Flui
if (io != IO.IN && io != IO.OUT) return left;

// Temporarily remove listeners so that we can broadcast the entire set of transactions once
Runnable[] listeners = new Runnable[storages.length];
for (int i = 0; i < storages.length; i++) {
listeners[i] = storages[i].getOnContentsChanged();
storages[i].setOnContentsChanged(() -> {});
Runnable[] listeners = null;
if (!simulate) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly certain this should always be done regardless of simulated status? Don't quote me on that, though.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It being done means the state is modified, which makes it unsafe to call from multiple threads. Why should it be done? In a simulated path, no change listeners will fire since no contents will be changed, and as such, that logic is not necessary.

listeners = new Runnable[storages.length];
for (int i = 0; i < storages.length; i++) {
listeners[i] = storages[i].getOnContentsChanged();
storages[i].setOnContentsChanged(() -> {});
}
}
boolean changed = false;

Expand Down Expand Up @@ -222,9 +225,11 @@ public List<FluidIngredient> handleRecipeInner(IO io, GTRecipe recipe, List<Flui
}
}

for (int i = 0; i < storages.length; i++) {
storages[i].setOnContentsChanged(listeners[i]);
if (changed && action.execute()) listeners[i].run();
if (listeners != null && !simulate) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

for (int i = 0; i < storages.length; i++) {
storages[i].setOnContentsChanged(listeners[i]);
if (changed && action.execute()) listeners[i].run();
}
}

return left;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,11 @@ public static List<Ingredient> handleRecipe(IO io, GTRecipe recipe, List<Ingredi
if (io != IO.IN && io != IO.OUT) return left;

// Temporarily remove listener so that we can broadcast the entire set of transactions once
Runnable listener = storage.getOnContentsChanged();
storage.setOnContentsChanged(() -> {});
Runnable listener = null;
if (!simulate) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

listener = storage.getOnContentsChanged();
storage.setOnContentsChanged(() -> {});
}
boolean changed = false;

// Store the ItemStack in each slot after an operation
Expand Down Expand Up @@ -170,8 +173,10 @@ public static List<Ingredient> handleRecipe(IO io, GTRecipe recipe, List<Ingredi
}
}

storage.setOnContentsChanged(listener);
if (changed && !simulate) listener.run();
if (!simulate) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

storage.setOnContentsChanged(listener);
if (changed) listener.run();
}

return left;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ private ActionResult handleContents() {

List<RecipeHandlerList> 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());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ItemStack> 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<FluidStack> 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;
}
Expand Down Expand Up @@ -1058,7 +1061,9 @@ public List<Ingredient> handleItemInternal(List<Ingredient> 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;
Expand Down Expand Up @@ -1110,7 +1115,9 @@ public List<FluidIngredient> handleFluidInternal(List<FluidIngredient> 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;
Expand Down
Loading