Skip to content

Commit ade6faa

Browse files
committed
Improve performance of factory sorting and inserting by removing capturing lambdas from hot paths
1 parent 3ad418c commit ade6faa

10 files changed

+149
-82
lines changed

src/main/java/mekanism/common/base/MekFakePlayer.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,11 @@ public static <R> R withFakePlayer(ServerLevel world, Function<MekFakePlayer, R>
112112
* @return the return value of fakePlayerConsumer
113113
*/
114114
public static <R> R withFakePlayer(ServerLevel world, double x, double y, double z, Function<MekFakePlayer, R> fakePlayerConsumer) {
115-
return withFakePlayer(world, fakePlayer -> {
116-
fakePlayer.setPosRaw(x, y, z);
117-
return fakePlayerConsumer.apply(fakePlayer);
118-
});
115+
MekFakePlayer player = setupFakePlayer(world);
116+
player.setPosRaw(x, y, z);
117+
R result = fakePlayerConsumer.apply(player);
118+
player.cleanupFakePlayer(world);
119+
return result;
119120
}
120121

121122
public static void releaseInstance(ServerLevel world) {

src/main/java/mekanism/common/recipe/lookup/cache/DoubleInputRecipeCache.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import java.util.Set;
66
import java.util.function.BiPredicate;
77
import java.util.function.Function;
8-
import java.util.function.Predicate;
98
import java.util.function.Supplier;
109
import mekanism.api.recipes.MekanismRecipe;
1110
import mekanism.api.recipes.ingredients.InputIngredient;
@@ -178,31 +177,33 @@ private RECIPE findFirstRecipe(INPUT_A inputA, INPUT_B inputB, Iterable<RECIPE>
178177
* @apiNote This is mainly meant as a helper for factories so makes the assumption that if inputB is empty it doesn't factor it into the check at all.
179178
*/
180179
@Nullable
181-
public RECIPE findTypeBasedRecipe(@Nullable Level world, INPUT_A inputA, INPUT_B inputB, Predicate<RECIPE> matchCriteria) {
180+
public <DATA> RECIPE findTypeBasedRecipe(@Nullable Level world, INPUT_A inputA, INPUT_B inputB, DATA data, CheckRecipeType<INPUT_A, INPUT_B, RECIPE, DATA> matchCriteria) {
182181
if (cacheA.isEmpty(inputA)) {
183182
//Don't allow empty primary inputs
184183
return null;
185184
}
186185
initCacheIfNeeded(world);
187186
if (cacheB.isEmpty(inputB)) {
188187
//If b is empty, lookup by A and our match criteria
189-
RECIPE recipe = cacheA.findFirstRecipe(inputA, matchCriteria);
190-
if (recipe == null) {
191-
for (RECIPE complexRecipe : complexRecipes) {
192-
if (inputAExtractor.apply(complexRecipe).testType(inputA) && matchCriteria.test(complexRecipe)) {
193-
return complexRecipe;
194-
}
188+
for (RECIPE recipe : cacheA.getRecipes(inputA)) {
189+
if (matchCriteria.testType(recipe, inputA, inputB, data)) {
190+
return recipe;
191+
}
192+
}
193+
for (RECIPE complexRecipe : complexRecipes) {
194+
if (inputAExtractor.apply(complexRecipe).testType(inputA) && matchCriteria.testType(complexRecipe, inputA, inputB, data)) {
195+
return complexRecipe;
195196
}
196197
}
197198
} else {
198199
for (RECIPE recipe : cacheA.getRecipes(inputA)) {
199-
if (inputBExtractor.apply(recipe).testType(inputB) && matchCriteria.test(recipe)) {
200+
if (inputBExtractor.apply(recipe).testType(inputB) && matchCriteria.testType(recipe, inputA, inputB, data)) {
200201
return recipe;
201202
}
202203
}
203204
for (RECIPE complexRecipe : complexRecipes) {
204205
if (inputAExtractor.apply(complexRecipe).testType(inputA)) {
205-
if (inputBExtractor.apply(complexRecipe).testType(inputB) && matchCriteria.test(complexRecipe)) {
206+
if (inputBExtractor.apply(complexRecipe).testType(inputB) && matchCriteria.testType(complexRecipe, inputA, inputB, data)) {
206207
return complexRecipe;
207208
}
208209
}
@@ -240,4 +241,10 @@ protected DoubleSameInputRecipeCache(MekanismRecipeType<RECIPE, ?> recipeType, F
240241
super(recipeType, inputAExtractor, cacheSupplier.get(), inputBExtractor, cacheSupplier.get());
241242
}
242243
}
244+
245+
@FunctionalInterface
246+
public interface CheckRecipeType<INPUT_A, INPUT_B, RECIPE extends MekanismRecipe & BiPredicate<INPUT_A, INPUT_B>, DATA> {
247+
248+
boolean testType(RECIPE recipe, INPUT_A inputA, INPUT_B inputB, DATA data);
249+
}
243250
}

src/main/java/mekanism/common/recipe/lookup/cache/SingleInputRecipeCache.java

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import mekanism.common.recipe.lookup.cache.type.IInputCache;
1313
import net.minecraft.world.item.crafting.RecipeHolder;
1414
import net.minecraft.world.level.Level;
15+
import net.neoforged.neoforge.common.util.TriPredicate;
1516
import org.jetbrains.annotations.Nullable;
1617

1718
/**
@@ -88,7 +89,20 @@ private RECIPE findFirstRecipe(INPUT input, Iterable<RECIPE> recipes) {
8889
*/
8990
@Nullable
9091
public RECIPE findTypeBasedRecipe(@Nullable Level world, INPUT input) {
91-
return findTypeBasedRecipe(world, input, ConstantPredicates.alwaysTrue());
92+
if (cache.isEmpty(input)) {
93+
//Don't allow empty inputs
94+
return null;
95+
}
96+
initCacheIfNeeded(world);
97+
RECIPE recipe = cache.findFirstRecipe(input, ConstantPredicates.alwaysTrue());
98+
if (recipe == null) {
99+
for (RECIPE complexRecipe : complexRecipes) {
100+
if (inputExtractor.apply(complexRecipe).testType(input)) {
101+
return complexRecipe;
102+
}
103+
}
104+
}
105+
return recipe;
92106
}
93107

94108
/**
@@ -101,21 +115,52 @@ public RECIPE findTypeBasedRecipe(@Nullable Level world, INPUT input) {
101115
* @return Recipe matching the given input, or {@code null} if no recipe matches.
102116
*/
103117
@Nullable
104-
public RECIPE findTypeBasedRecipe(@Nullable Level world, INPUT input, Predicate<RECIPE> matchCriteria) {
118+
public <DATA> RECIPE findTypeBasedRecipe(@Nullable Level world, INPUT input, DATA data, TriPredicate<RECIPE, INPUT, DATA> matchCriteria) {
105119
if (cache.isEmpty(input)) {
106120
//Don't allow empty inputs
107121
return null;
108122
}
109123
initCacheIfNeeded(world);
110-
RECIPE recipe = cache.findFirstRecipe(input, matchCriteria);
111-
if (recipe == null) {
112-
for (RECIPE complexRecipe : complexRecipes) {
113-
if (inputExtractor.apply(complexRecipe).testType(input) && matchCriteria.test(complexRecipe)) {
114-
return complexRecipe;
115-
}
124+
for (RECIPE recipe : cache.getRecipes(input)) {
125+
if (matchCriteria.test(recipe, input, data)) {
126+
return recipe;
116127
}
117128
}
118-
return recipe;
129+
for (RECIPE complexRecipe : complexRecipes) {
130+
if (inputExtractor.apply(complexRecipe).testType(input) && matchCriteria.test(complexRecipe, input, data)) {
131+
return complexRecipe;
132+
}
133+
}
134+
return null;
135+
}
136+
137+
/**
138+
* Finds the first recipe that matches the given input type ignoring the size requirement and also matches the given recipe predicate.
139+
*
140+
* @param world World.
141+
* @param input Recipe input.
142+
* @param matchCriteria Extra validation criteria to check.
143+
*
144+
* @return Recipe matching the given input, or {@code null} if no recipe matches.
145+
*/
146+
@Nullable
147+
public <DATA_1, DATA_2> RECIPE findTypeBasedRecipe(@Nullable Level world, INPUT input, DATA_1 data1, DATA_2 data2, CheckRecipeType<INPUT, RECIPE, DATA_1, DATA_2> matchCriteria) {
148+
if (cache.isEmpty(input)) {
149+
//Don't allow empty inputs
150+
return null;
151+
}
152+
initCacheIfNeeded(world);
153+
for (RECIPE recipe : cache.getRecipes(input)) {
154+
if (matchCriteria.testType(recipe, input, data1, data2)) {
155+
return recipe;
156+
}
157+
}
158+
for (RECIPE complexRecipe : complexRecipes) {
159+
if (inputExtractor.apply(complexRecipe).testType(input) && matchCriteria.testType(complexRecipe, input, data1, data2)) {
160+
return complexRecipe;
161+
}
162+
}
163+
return null;
119164
}
120165

121166
@Override
@@ -127,4 +172,10 @@ protected void initCache(List<RecipeHolder<RECIPE>> recipes) {
127172
}
128173
}
129174
}
175+
176+
@FunctionalInterface
177+
public interface CheckRecipeType<INPUT, RECIPE extends MekanismRecipe & Predicate<INPUT>, DATA_1, DATA_2> {
178+
179+
boolean testType(RECIPE recipe, INPUT input, DATA_1 data1, DATA_2 data2);
180+
}
130181
}

src/main/java/mekanism/common/tile/factory/TileEntityCombiningFactory.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import mekanism.common.recipe.IMekanismRecipeTypeProvider;
2424
import mekanism.common.recipe.MekanismRecipeType;
2525
import mekanism.common.recipe.lookup.IDoubleRecipeLookupHandler.DoubleItemRecipeLookupHandler;
26+
import mekanism.common.recipe.lookup.cache.DoubleInputRecipeCache.CheckRecipeType;
2627
import mekanism.common.recipe.lookup.cache.InputRecipeCache.DoubleItem;
2728
import mekanism.common.upgrade.CombinerUpgradeData;
2829
import mekanism.common.upgrade.IUpgradeData;
@@ -35,6 +36,8 @@
3536

3637
public class TileEntityCombiningFactory extends TileEntityItemToItemFactory<CombinerRecipe> implements DoubleItemRecipeLookupHandler<CombinerRecipe> {
3738

39+
private static final CheckRecipeType<ItemStack, ItemStack, CombinerRecipe, ItemStack> OUTPUT_CHECK =
40+
(recipe, input, extra, output) -> InventoryUtils.areItemsStackable(recipe.getOutput(input, extra), output);
3841
private static final List<RecipeError> TRACKED_ERROR_TYPES = List.of(
3942
RecipeError.NOT_ENOUGH_ENERGY,
4043
RecipeError.NOT_ENOUGH_INPUT,
@@ -96,11 +99,8 @@ protected boolean isCachedRecipeValid(@Nullable CachedRecipe<CombinerRecipe> cac
9699

97100
@Override
98101
protected CombinerRecipe findRecipe(int process, @NotNull ItemStack fallbackInput, @NotNull IInventorySlot outputSlot, @Nullable IInventorySlot secondaryOutputSlot) {
99-
ItemStack extra = extraSlot.getStack();
100-
ItemStack output = outputSlot.getStack();
101102
//TODO: Give it something that is not empty when we don't have a stored secondary stack for getting the output?
102-
return getRecipeType().getInputCache().findTypeBasedRecipe(level, fallbackInput, extra,
103-
recipe -> InventoryUtils.areItemsStackable(recipe.getOutput(fallbackInput, extra), output));
103+
return getRecipeType().getInputCache().findTypeBasedRecipe(level, fallbackInput, extraSlot.getStack(), outputSlot.getStack(), OUTPUT_CHECK);
104104
}
105105

106106
@NotNull

src/main/java/mekanism/common/tile/factory/TileEntityFactory.java

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import java.util.Map.Entry;
1111
import java.util.Set;
1212
import java.util.function.BooleanSupplier;
13-
import java.util.function.IntSupplier;
13+
import java.util.function.ToIntBiFunction;
1414
import mekanism.api.Action;
1515
import mekanism.api.IContentsListener;
1616
import mekanism.api.NBTConstants;
@@ -69,6 +69,7 @@
6969
import net.minecraft.world.level.block.Block;
7070
import net.minecraft.world.level.block.state.BlockState;
7171
import net.neoforged.neoforge.attachment.AttachmentType;
72+
import org.jetbrains.annotations.Contract;
7273
import org.jetbrains.annotations.NotNull;
7374
import org.jetbrains.annotations.Nullable;
7475

@@ -279,6 +280,7 @@ public boolean inputProducesOutput(int process, @NotNull ItemStack fallbackInput
279280
return outputSlot.isEmpty() || getRecipeForInput(process, fallbackInput, outputSlot, secondaryOutputSlot, updateCache) != null;
280281
}
281282

283+
@Contract("null, _ -> false")
282284
protected abstract boolean isCachedRecipeValid(@Nullable CachedRecipe<RECIPE> cached, @NotNull ItemStack stack);
283285

284286
@Nullable
@@ -287,7 +289,7 @@ protected RECIPE getRecipeForInput(int process, @NotNull ItemStack fallbackInput
287289
if (!CommonWorldTickHandler.flushTagAndRecipeCaches) {
288290
//If our recipe caches are valid, grab our cached recipe and see if it is still valid
289291
CachedRecipe<RECIPE> cached = getCachedRecipe(process);
290-
if (cached != null && isCachedRecipeValid(cached, fallbackInput)) {
292+
if (isCachedRecipeValid(cached, fallbackInput)) {
291293
//Our input matches the recipe we have cached for this slot
292294
return cached.getRecipe();
293295
}
@@ -526,7 +528,7 @@ ItemStack getOutput(int process) throws ComputerException {
526528
//End methods IComputerTile
527529

528530
private void sortInventory() {
529-
Map<HashedItem, RecipeProcessInfo> processes = new HashMap<>();
531+
Map<HashedItem, RecipeProcessInfo<RECIPE>> processes = new HashMap<>();
530532
List<ProcessInfo> emptyProcesses = new ArrayList<>();
531533
for (ProcessInfo processInfo : processInfoSlots) {
532534
IInventorySlot inputSlot = processInfo.inputSlot();
@@ -535,18 +537,20 @@ private void sortInventory() {
535537
} else {
536538
ItemStack inputStack = inputSlot.getStack();
537539
HashedItem item = HashedItem.raw(inputStack);
538-
RecipeProcessInfo recipeProcessInfo = processes.computeIfAbsent(item, i -> new RecipeProcessInfo());
540+
RecipeProcessInfo<RECIPE> recipeProcessInfo = processes.computeIfAbsent(item, i -> new RecipeProcessInfo<>());
539541
recipeProcessInfo.processes.add(processInfo);
540542
recipeProcessInfo.totalCount += inputStack.getCount();
541543
if (recipeProcessInfo.lazyMinPerSlot == null && !CommonWorldTickHandler.flushTagAndRecipeCaches) {
542544
//If we don't have a lazily initialized min per slot calculation set for it yet
543545
// and our cache is not invalid/out of date due to a reload
544546
CachedRecipe<RECIPE> cachedRecipe = getCachedRecipe(processInfo.process());
545547
if (isCachedRecipeValid(cachedRecipe, inputStack)) {
548+
recipeProcessInfo.item = inputStack;
549+
recipeProcessInfo.recipe = cachedRecipe.getRecipe();
546550
// And our current process has a cached recipe then set the lazily initialized per slot value
547551
// Note: If something goes wrong, and we end up with zero as how much we need as an input
548552
// we just bump the value up to one to make sure we properly handle it
549-
recipeProcessInfo.lazyMinPerSlot = () -> Math.max(1, getNeededInput(cachedRecipe.getRecipe(), inputStack));
553+
recipeProcessInfo.lazyMinPerSlot = (info, factory) -> factory.getNeededInput(info.recipe, (ItemStack) info.item);
550554
}
551555
}
552556
}
@@ -555,23 +559,24 @@ private void sortInventory() {
555559
//If all input slots are empty, just exit
556560
return;
557561
}
558-
for (Entry<HashedItem, RecipeProcessInfo> entry : processes.entrySet()) {
559-
RecipeProcessInfo recipeProcessInfo = entry.getValue();
562+
for (Entry<HashedItem, RecipeProcessInfo<RECIPE>> entry : processes.entrySet()) {
563+
RecipeProcessInfo<RECIPE> recipeProcessInfo = entry.getValue();
560564
if (recipeProcessInfo.lazyMinPerSlot == null) {
565+
recipeProcessInfo.item = entry.getKey();
561566
//If we don't have a lazy initializer for our minPerSlot setup, that means that there is
562567
// no valid cached recipe for any of the slots of this type currently, so we want to try and
563568
// get the recipe we will have for the first slot, once we end up with more items in the stack
564-
recipeProcessInfo.lazyMinPerSlot = () -> {
569+
recipeProcessInfo.lazyMinPerSlot = (info, factory) -> {
565570
//Note: We put all of this logic in the lazy init, so that we don't actually call any of this
566571
// until it is needed. That way if we have no empty slots and all our input slots are filled
567572
// we don't do any extra processing here, and can properly short circuit
568-
HashedItem item = entry.getKey();
569-
ItemStack largerInput = item.createStack(Math.min(item.getMaxStackSize(), recipeProcessInfo.totalCount));
570-
ProcessInfo processInfo = recipeProcessInfo.processes.get(0);
573+
HashedItem item = (HashedItem) info.item;
574+
ItemStack largerInput = item.createStack(Math.min(item.getMaxStackSize(), info.totalCount));
575+
ProcessInfo processInfo = info.processes.get(0);
571576
//Try getting a recipe for our input with a larger size, and update the cache if we find one
572-
RECIPE recipe = getRecipeForInput(processInfo.process(), largerInput, processInfo.outputSlot(), processInfo.secondaryOutputSlot(), true);
573-
if (recipe != null) {
574-
return Math.max(1, getNeededInput(recipe, largerInput));
577+
info.recipe = factory.getRecipeForInput(processInfo.process(), largerInput, processInfo.outputSlot(), processInfo.secondaryOutputSlot(), true);
578+
if (info.recipe != null) {
579+
return factory.getNeededInput(info.recipe, largerInput);
575580
}
576581
return 1;
577582
};
@@ -587,10 +592,10 @@ private void sortInventory() {
587592
distributeItems(processes);
588593
}
589594

590-
private void addEmptySlotsAsTargets(Map<HashedItem, RecipeProcessInfo> processes, List<ProcessInfo> emptyProcesses) {
591-
for (Entry<HashedItem, RecipeProcessInfo> entry : processes.entrySet()) {
592-
RecipeProcessInfo recipeProcessInfo = entry.getValue();
593-
int minPerSlot = recipeProcessInfo.getMinPerSlot();
595+
private void addEmptySlotsAsTargets(Map<HashedItem, RecipeProcessInfo<RECIPE>> processes, List<ProcessInfo> emptyProcesses) {
596+
for (Entry<HashedItem, RecipeProcessInfo<RECIPE>> entry : processes.entrySet()) {
597+
RecipeProcessInfo<RECIPE> recipeProcessInfo = entry.getValue();
598+
int minPerSlot = recipeProcessInfo.getMinPerSlot(this);
594599
int maxSlots = recipeProcessInfo.totalCount / minPerSlot;
595600
if (maxSlots <= 1) {
596601
//If we don't have enough to even fill the input for a slot for a single recipe; skip
@@ -630,9 +635,9 @@ private void addEmptySlotsAsTargets(Map<HashedItem, RecipeProcessInfo> processes
630635
}
631636
}
632637

633-
private void distributeItems(Map<HashedItem, RecipeProcessInfo> processes) {
634-
for (Entry<HashedItem, RecipeProcessInfo> entry : processes.entrySet()) {
635-
RecipeProcessInfo recipeProcessInfo = entry.getValue();
638+
private void distributeItems(Map<HashedItem, RecipeProcessInfo<RECIPE>> processes) {
639+
for (Entry<HashedItem, RecipeProcessInfo<RECIPE>> entry : processes.entrySet()) {
640+
RecipeProcessInfo<RECIPE> recipeProcessInfo = entry.getValue();
636641
int processCount = recipeProcessInfo.processes.size();
637642
if (processCount == 1) {
638643
//If there is only one process with the item in it; short-circuit, no balancing is needed
@@ -647,7 +652,7 @@ private void distributeItems(Map<HashedItem, RecipeProcessInfo> processes) {
647652
continue;
648653
}
649654
int remainder = recipeProcessInfo.totalCount % processCount;
650-
int minPerSlot = recipeProcessInfo.getMinPerSlot();
655+
int minPerSlot = recipeProcessInfo.getMinPerSlot(this);
651656
if (minPerSlot > 1) {
652657
int perSlotRemainder = numberPerSlot % minPerSlot;
653658
if (perSlotRemainder > 0) {
@@ -732,18 +737,20 @@ public record ProcessInfo(int process, @NotNull FactoryInputInventorySlot inputS
732737
@Nullable IInventorySlot secondaryOutputSlot) {
733738
}
734739

735-
private static class RecipeProcessInfo {
740+
private static class RecipeProcessInfo<RECIPE extends MekanismRecipe> {
736741

737742
private final List<ProcessInfo> processes = new ArrayList<>();
738743
@Nullable
739-
private IntSupplier lazyMinPerSlot;
744+
private ToIntBiFunction<RecipeProcessInfo<RECIPE>, TileEntityFactory<RECIPE>> lazyMinPerSlot;
745+
private Object item;
746+
private RECIPE recipe;
740747
private int minPerSlot = 1;
741748
private int totalCount;
742749

743-
public int getMinPerSlot() {
750+
public int getMinPerSlot(TileEntityFactory<RECIPE> factory) {
744751
if (lazyMinPerSlot != null) {
745752
//Get the value lazily
746-
minPerSlot = lazyMinPerSlot.getAsInt();
753+
minPerSlot = Math.max(1, lazyMinPerSlot.applyAsInt(this, factory));
747754
lazyMinPerSlot = null;
748755
}
749756
return minPerSlot;

0 commit comments

Comments
 (0)