Skip to content

Commit

Permalink
Optimize list-based ingredient movement
Browse files Browse the repository at this point in the history
This makes importers and exporters configured with
a list aspect make better use of internal indexes
to significantly reduce server load.

Closes #286
  • Loading branch information
rubensworks committed Apr 23, 2024
1 parent d9da007 commit 7e71f9d
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 67 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package org.cyclops.integratedtunnels.core;

import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.nbt.Tag;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.InteractionHand;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.Material;
import net.minecraftforge.fluids.FluidStack;
import org.cyclops.commoncapabilities.api.capability.fluidhandler.FluidMatch;
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
Expand Down Expand Up @@ -38,7 +38,7 @@
*/
public class TunnelFluidHelpers {

public static final IngredientPredicate<FluidStack, Integer> MATCH_NONE = new IngredientPredicate<FluidStack, Integer>(IngredientComponent.FLUIDSTACK, null, FluidMatch.EXACT, false, true, 0, false) {
public static final IngredientPredicate<FluidStack, Integer> MATCH_NONE = new IngredientPredicate<FluidStack, Integer>(IngredientComponent.FLUIDSTACK, FluidStack.EMPTY, FluidMatch.EXACT, false, true, 0, false) {
@Override
public boolean test(FluidStack input) {
return false;
Expand All @@ -64,14 +64,18 @@ public boolean test(FluidStack input) {
};
}

public static IngredientPredicate<FluidStack, Integer> matchFluidStack(final FluidStack fluidStack, final boolean checkFluid,
final boolean checkAmount, final boolean checkNbt,
final boolean blacklist, final boolean exactAmount) {
protected static int getFluidStackMatchFlags(final boolean checkFluid, final boolean checkAmount, final boolean checkNbt) {
int matchFlags = FluidMatch.ANY;
if (checkFluid) matchFlags = matchFlags | FluidMatch.FLUID;
if (checkNbt) matchFlags = matchFlags | FluidMatch.TAG;
if (checkAmount) matchFlags = matchFlags | FluidMatch.AMOUNT;
return new IngredientPredicate<FluidStack, Integer>(IngredientComponent.FLUIDSTACK, fluidStack != null ? fluidStack.copy() : null, matchFlags, blacklist, fluidStack == null && !blacklist,
return matchFlags;
}

public static IngredientPredicate<FluidStack, Integer> matchFluidStack(final FluidStack fluidStack, final boolean checkFluid,
final boolean checkAmount, final boolean checkNbt,
final boolean blacklist, final boolean exactAmount) {
return new IngredientPredicate<FluidStack, Integer>(IngredientComponent.FLUIDSTACK, fluidStack != null ? fluidStack.copy() : null, getFluidStackMatchFlags(checkFluid, checkAmount, checkNbt), blacklist, fluidStack == null && !blacklist,
FluidHelpers.getAmount(fluidStack), exactAmount) {
@Override
public boolean test(@Nullable FluidStack input) {
Expand All @@ -87,7 +91,7 @@ public boolean test(@Nullable FluidStack input) {
public static IngredientPredicate<FluidStack, Integer> matchFluidStacks(final IValueTypeListProxy<ValueObjectTypeFluidStack, ValueObjectTypeFluidStack.ValueFluidStack> fluidStacks,
final boolean checkFluid, final boolean checkAmount, final boolean checkNbt,
final boolean blacklist, final int amount, final boolean exactAmount) {
return new IngredientPredicateFluidStackList(blacklist, amount, exactAmount, fluidStacks, checkFluid, checkAmount, checkNbt);
return new IngredientPredicateFluidStackList(blacklist, amount, exactAmount, fluidStacks, getFluidStackMatchFlags(checkFluid, checkAmount, checkNbt), checkFluid, checkAmount, checkNbt);
}

public static IngredientPredicate<FluidStack, Integer> matchPredicate(final PartTarget partTarget, final IOperator predicate,
Expand Down
75 changes: 44 additions & 31 deletions src/main/java/org/cyclops/integratedtunnels/core/TunnelHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,15 @@ public static <T, M> T moveSingle(IIngredientComponentStorage<T, M> source, int
try {
try {
if (ingredientPredicate.hasMatchFlags()) {
return IngredientStorageHelpers.moveIngredientsSlotted(source, sourceSlot, destination, destinationSlot,
ingredientPredicate.getInstance(), ingredientPredicate.getMatchFlags(), simulate);
IIngredientMatcher<T, M> matcher = ingredientPredicate.getIngredientComponent().getMatcher();
for (T instance : ingredientPredicate.getInstances()) {
T movedInstance = IngredientStorageHelpers.moveIngredientsSlotted(source, sourceSlot, destination, destinationSlot,
instance, ingredientPredicate.getMatchFlags(), simulate);
if (!matcher.isEmpty(movedInstance)) {
return movedInstance;
}
}
return matcher.getEmptyInstance();
} else {
return IngredientStorageHelpers.moveIngredientsSlotted(source, sourceSlot, destination, destinationSlot,
ingredientPredicate, ingredientPredicate.getMaxQuantity(), ingredientPredicate.isExactQuantity(), simulate);
Expand Down Expand Up @@ -124,9 +131,12 @@ public static <T, M> T moveSingleStateOptimized(INetwork network, IPositionedAdd
IIngredientMatcher<T, M> matcher = source.getComponent().getMatcher();

// Don't craft if we still have a running crafting job for the instance.
if (craftIfFailed && isCrafting(network, ingredientsNetwork, channel,
ingredientPredicate.getInstance(), ingredientPredicate.getMatchFlags())) {
return matcher.getEmptyInstance();
if (craftIfFailed) {
for (T instance : ingredientPredicate.getInstances()) {
if (isCrafting(network, ingredientsNetwork, channel, instance, ingredientPredicate.getMatchFlags())) {
return matcher.getEmptyInstance();
}
}
}

// Don't do any expensive transfers if the to-be-moved stack is empty
Expand All @@ -151,35 +161,38 @@ public static <T, M> T moveSingleStateOptimized(INetwork network, IPositionedAdd

// Craft if we moved nothing, and the flag is enabled.
if (craftIfFailed && matcher.isEmpty(moved)) {
// If we don't have to move exact instances,
// only request the crafting of 1.
T craftInstance = ingredientPredicate.getInstance();
if (!ingredientPredicate.isExactQuantity()) {
craftInstance = matcher.withQuantity(craftInstance, 1);
}
for (T instance : ingredientPredicate.getInstances()) {
// If we don't have to move exact instances,
// only request the crafting of 1.
T craftInstance = instance;
if (!ingredientPredicate.isExactQuantity()) {
craftInstance = matcher.withQuantity(craftInstance, 1);
}

// Don't allow crafting jobs to be started if we detect a case where movement failed,
// but the required ingredient is in fact present in the network.
// This is to avoid cases where crafting jobs would be started before a previous movement was observed,
// and the crafting job output thereby not being detected upon the next observement.
IIngredientPositionsIndex<T, M> index = ingredientsNetwork.getChannelIndex(channel);
if (index.getQuantity(ingredientPredicate.getInstance()) >= matcher.getQuantity(craftInstance)) {
return moved;
}
// Don't allow crafting jobs to be started if we detect a case where movement failed,
// but the required ingredient is in fact present in the network.
// This is to avoid cases where crafting jobs would be started before a previous movement was observed,
// and the crafting job output thereby not being detected upon the next observement.
IIngredientPositionsIndex<T, M> index = ingredientsNetwork.getChannelIndex(channel);
if (index.getQuantity(instance) >= matcher.getQuantity(craftInstance)) {
return moved;
}

// Only craft if the target accepts the crafting output completely
boolean targetAcceptsCraftingResult;
if (destinationSlot >= 0) {
targetAcceptsCraftingResult = destination instanceof IIngredientComponentStorageSlotted
&& matcher.isEmpty(((IIngredientComponentStorageSlotted<T, M>) destination)
.insert(destinationSlot, craftInstance, true));
} else {
targetAcceptsCraftingResult = matcher.isEmpty(destination.insert(craftInstance, true));
}
// Only craft if the target accepts the crafting output completely
boolean targetAcceptsCraftingResult;
if (destinationSlot >= 0) {
targetAcceptsCraftingResult = destination instanceof IIngredientComponentStorageSlotted
&& matcher.isEmpty(((IIngredientComponentStorageSlotted<T, M>) destination)
.insert(destinationSlot, craftInstance, true));
} else {
targetAcceptsCraftingResult = matcher.isEmpty(destination.insert(craftInstance, true));
}

if (targetAcceptsCraftingResult) {
requestCrafting(network, ingredientsNetwork, channel,
craftInstance, ingredientPredicate.getMatchFlags());
if (targetAcceptsCraftingResult) {
requestCrafting(network, ingredientsNetwork, channel,
craftInstance, ingredientPredicate.getMatchFlags());
break;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,19 @@ public boolean test(ItemStack input) {
};
}

public static IngredientPredicate<ItemStack, Integer> matchItemStack(final ItemStack itemStack, final boolean checkItem,
final boolean checkStackSize,
final boolean checkNbt, final boolean blacklist,
final boolean exactAmount) {
protected static int getItemStackMatchFlags(boolean checkItem, boolean checkStackSize, boolean checkNbt) {
int matchFlags = ItemMatch.ANY;
if (checkItem) matchFlags = matchFlags | ItemMatch.ITEM;
if (checkNbt) matchFlags = matchFlags | ItemMatch.TAG;
if (checkStackSize) matchFlags = matchFlags | ItemMatch.STACKSIZE;
return new IngredientPredicate<ItemStack, Integer>(IngredientComponent.ITEMSTACK, itemStack.copy(), matchFlags, blacklist, itemStack.isEmpty() && !blacklist,
return matchFlags;
}

public static IngredientPredicate<ItemStack, Integer> matchItemStack(final ItemStack itemStack, final boolean checkItem,
final boolean checkStackSize,
final boolean checkNbt, final boolean blacklist,
final boolean exactAmount) {
return new IngredientPredicate<ItemStack, Integer>(IngredientComponent.ITEMSTACK, itemStack.copy(), getItemStackMatchFlags(checkItem, checkStackSize, checkNbt), blacklist, itemStack.isEmpty() && !blacklist,
itemStack.getCount(), exactAmount) {
@Override
public boolean test(@Nullable ItemStack input) {
Expand All @@ -93,7 +97,7 @@ public static IngredientPredicate<ItemStack, Integer> matchItemStacks(final IVal
final boolean checkItem, final boolean checkStackSize,
final boolean checkNbt,
final boolean blacklist, final int amount, final boolean exactAmount) {
return new IngredientPredicateItemStackList(blacklist, amount, exactAmount, itemStacks, checkStackSize, checkItem, checkNbt);
return new IngredientPredicateItemStackList(blacklist, amount, exactAmount, itemStacks, getItemStackMatchFlags(checkItem, checkStackSize, checkNbt), checkStackSize, checkItem, checkNbt);
}

public static IngredientPredicate<ItemStack, Integer> matchPredicateItem(final PartTarget partTarget, final IOperator predicate,
Expand All @@ -105,7 +109,7 @@ public static IngredientPredicate<ItemStack, Integer> matchBlocks(final IValueTy
final boolean checkItem, final boolean checkStackSize,
final boolean checkNbt,
final boolean blacklist, final int amount, final boolean exactAmount) {
return new IngredientPredicateBlockList(blacklist, amount, exactAmount, blocks, checkStackSize, checkItem, checkNbt);
return new IngredientPredicateBlockList(blacklist, amount, exactAmount, blocks, getItemStackMatchFlags(checkItem, checkStackSize, checkNbt), checkStackSize, checkItem, checkNbt);
}

public static IngredientPredicate<ItemStack, Integer> matchPredicateBlock(final PartTarget partTarget, final IOperator predicate,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package org.cyclops.integratedtunnels.core.predicate;

import com.google.common.collect.Lists;
import org.cyclops.commoncapabilities.api.ingredient.IIngredientMatcher;
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
import org.cyclops.integratedtunnels.part.aspect.ITunnelTransfer;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.StreamSupport;

/**
* A predicate for matching ingredient components.
Expand All @@ -16,25 +21,31 @@
public abstract class IngredientPredicate<T, M> implements Predicate<T>, ITunnelTransfer {

private final IngredientComponent<T, M> ingredientComponent;
private final T instance;
private final Iterable<T> instances;
private final M matchFlags;
private final boolean blacklist;
private final boolean empty;
private final int maxQuantity;
private final boolean exactQuantity;

public IngredientPredicate(IngredientComponent<T, M> ingredientComponent,
T instance, M matchFlags, boolean blacklist, boolean empty,
Iterable<T> instances, M matchFlags, boolean blacklist, boolean empty,
int maxQuantity, boolean exactQuantity) {
this.ingredientComponent = ingredientComponent;
this.instance = instance;
this.instances = instances;
this.matchFlags = matchFlags;
this.blacklist = blacklist;
this.empty = empty;
this.maxQuantity = maxQuantity;
this.exactQuantity = exactQuantity;
}

public IngredientPredicate(IngredientComponent<T, M> ingredientComponent,
T instance, M matchFlags, boolean blacklist, boolean empty,
int maxQuantity, boolean exactQuantity) {
this(ingredientComponent, Collections.singletonList(instance), matchFlags, blacklist, empty, maxQuantity, exactQuantity);
}

// Note: implementors of this method *should* override equals and hashcode.
public IngredientPredicate(IngredientComponent<T, M> ingredientComponent,
boolean blacklist, boolean empty, int maxQuantity, boolean exactQuantity) {
Expand All @@ -47,8 +58,8 @@ public IngredientComponent<T, M> getIngredientComponent() {
}

@Nonnull
public T getInstance() {
return instance;
public Iterable<T> getInstances() {
return instances;
}

public M getMatchFlags() {
Expand Down Expand Up @@ -80,20 +91,38 @@ public boolean equals(Object obj) {
if (!(obj instanceof IngredientPredicate)) {
return false;
}

IngredientPredicate that = (IngredientPredicate) obj;
return this.ingredientComponent == that.ingredientComponent
&& this.ingredientComponent.getMatcher().matchesExactly(this.instance, (T) that.instance)
if (!(this.ingredientComponent == that.ingredientComponent
&& Objects.equals(this.matchFlags, that.matchFlags)
&& this.blacklist == that.blacklist
&& this.empty == that.empty
&& this.maxQuantity == that.maxQuantity
&& this.exactQuantity == that.exactQuantity;
&& this.exactQuantity == that.exactQuantity)) {
return false;
}

ArrayList<T> instances1 = Lists.newArrayList(this.instances);
ArrayList<T> instances2 = Lists.newArrayList(that.instances);
if (instances1.size() != instances2.size()) {
return false;
}
IIngredientMatcher<T, M> matcher = this.ingredientComponent.getMatcher();
for (int i = 0; i < instances1.size(); i++) {
if (!matcher.matchesExactly(instances1.get(i), instances2.get(i))) {
return false;
}
}

return true;
}

@Override
public int hashCode() {
return ingredientComponent.hashCode()
^ ingredientComponent.getMatcher().hash(instance)
^ StreamSupport.stream(instances.spliterator(), false)
.map(instance -> ingredientComponent.getMatcher().hash(instance))
.reduce(0, (a, b) -> a ^ b)
^ Objects.hashCode(matchFlags)
^ (blacklist ? 1 : 0)
^ (empty ? 2 : 4)
Expand Down
Loading

0 comments on commit 7e71f9d

Please sign in to comment.