Optimize some methods in Ingredient and remove itemStacks caching

This commit is contained in:
embeddedt 2025-05-01 19:09:50 -04:00
parent 01d2582c44
commit eed320b055
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
4 changed files with 163 additions and 0 deletions

View File

@ -67,5 +67,6 @@ accessible field net/minecraft/server/packs/resources/ProfiledReloadInstance$Sta
accessible class net/minecraft/world/item/crafting/Ingredient$Value
accessible field net/minecraft/world/item/crafting/Ingredient$TagValue tag Lnet/minecraft/tags/TagKey;
accessible field net/minecraft/world/item/crafting/Ingredient$ItemValue item Lnet/minecraft/world/item/ItemStack;
accessible class net/minecraft/world/item/crafting/Ingredient$ItemValue
accessible class net/minecraft/client/searchtree/SearchRegistry$TreeEntry

View File

@ -0,0 +1,23 @@
package org.embeddedt.modernfix.forge.mixin.perf.faster_ingredients;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraftforge.common.ForgeHooks;
import org.embeddedt.modernfix.forge.recipe.ExtendedIngredient;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(value = ForgeHooks.class, priority = 900)
public class ForgeHooksMixin {
/**
* @author embeddedt
* @reason Exploding the stack list is entirely unnecessary to compute this
*/
@Inject(method = "hasNoElements", at = @At("HEAD"), cancellable = true, remap = false)
private static void modernfix$fastHasNoElements(Ingredient ingredient, CallbackInfoReturnable<Boolean> cir) {
if (ingredient.isVanilla()) {
cir.setReturnValue(((ExtendedIngredient)ingredient).mfix$hasNoElements());
}
}
}

View File

@ -0,0 +1,134 @@
package org.embeddedt.modernfix.forge.mixin.perf.faster_ingredients;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntComparators;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import org.embeddedt.modernfix.forge.recipe.ExtendedIngredient;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.ArrayList;
@Mixin(value = Ingredient.class, priority = 700)
public abstract class IngredientMixin implements ExtendedIngredient {
@Shadow
public abstract boolean isVanilla();
@Shadow @Final
private Ingredient.Value[] values;
@Shadow private @Nullable IntList stackingIds;
@Shadow @Nullable private ItemStack[] itemStacks;
/**
* @author embeddedt
* @reason tag ingredients can be tested without iterating over all items
*/
@Inject(method = "test(Lnet/minecraft/world/item/ItemStack;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/crafting/Ingredient;getItems()[Lnet/minecraft/world/item/ItemStack;"), cancellable = true)
private void modernfix$fasterTagIngredientTest(ItemStack stack, CallbackInfoReturnable<Boolean> cir) {
if (this.isVanilla() && this.values.length == 1 && this.values[0] instanceof Ingredient.TagValue tagValue) {
cir.setReturnValue(stack.getItemHolder().is(tagValue.tag));
}
}
@Override
public boolean mfix$hasNoElements() {
return !this.containsItems();
}
@Unique
private boolean isEmptyTagStack(ItemStack item) {
return item.getItem() == net.minecraft.world.item.Items.BARRIER && item.getHoverName() instanceof net.minecraft.network.chat.MutableComponent hoverName && hoverName.getString().startsWith("Empty Tag: ");
}
@Unique
private boolean containsItems() {
for (Ingredient.Value value : this.values) {
if (value instanceof Ingredient.ItemValue) {
return true;
} else if (value instanceof Ingredient.TagValue tagValue) {
var holderSetOpt = BuiltInRegistries.ITEM.getTag(tagValue.tag);
if (holderSetOpt.isPresent() && holderSetOpt.get().size() > 0) {
return true;
}
} else {
var items = value.getItems();
if (items.isEmpty() || isEmptyTagStack(items.iterator().next())) {
// Doesn't have items
continue;
}
return true;
}
}
return false;
}
/**
* @author embeddedt
* @reason tag ingredients can be converted to stacking IDs without expanding into stacks, since stacking only
* goes by item ID
*/
@Inject(method = "getStackingIds", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/crafting/Ingredient;getItems()[Lnet/minecraft/world/item/ItemStack;"), cancellable = true)
private void modernfix$fasterTagIngredientStacking(CallbackInfoReturnable<IntList> cir) {
if (this.isVanilla() && this.values.length == 1 && this.values[0] instanceof Ingredient.TagValue tagValue) {
var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag);
if (!tag.isPresent() || tag.get().size() == 0) {
return;
}
var list = new IntArrayList(tag.get().stream().mapToInt(h -> BuiltInRegistries.ITEM.getId(h.value())).toArray());
list.sort(IntComparators.NATURAL_COMPARATOR);
this.stackingIds = list;
cir.setReturnValue(list);
}
}
/**
* @author embeddedt
* @reason remove caching of the item stacks, it won't deduplicate anything with tags (since each Ingredient
* instance would make new item stacks anyway, and storing them permanently takes up a lot of memory).
* We implement an optimized version of some functions that avoids needing to call this entirely.
*/
@Overwrite
public ItemStack[] getItems() {
// For compatibility if mods explicitly force a set of item stacks to be used
if (this.itemStacks != null) {
return this.itemStacks;
}
// Fast path for case with one item
if (this.values.length == 1) {
if (this.values[0] instanceof Ingredient.ItemValue itemValue) {
return new ItemStack[] { itemValue.item };
} else if (this.values[0] instanceof Ingredient.TagValue tagValue) {
var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag);
if (tag.isPresent() && tag.get().size() > 0) {
var holderSet = tag.get();
ItemStack[] result = new ItemStack[holderSet.size()];
for (int i = 0; i < result.length; i++) {
result[i] = new ItemStack(holderSet.get(i));
}
return result;
}
}
}
ArrayList<ItemStack> itemList = new ArrayList<>(2);
for (var value : this.values) {
var collection = value.getItems();
itemList.ensureCapacity(collection.size() + itemList.size());
for (var item : collection) {
itemList.add(item);
}
}
return itemList.toArray(ItemStack[]::new);
}
}

View File

@ -0,0 +1,5 @@
package org.embeddedt.modernfix.forge.recipe;
public interface ExtendedIngredient {
boolean mfix$hasNoElements();
}