From 1b37c9da6674d5ff962d2d06b2a4dcf6ac81ad1d Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:33:50 -0400 Subject: [PATCH 1/4] Remove obsolete OBJLoader patch --- .../model_optimizations/OBJLoaderMixin.java | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/model_optimizations/OBJLoaderMixin.java diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/model_optimizations/OBJLoaderMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/model_optimizations/OBJLoaderMixin.java deleted file mode 100644 index 623ddb6f..00000000 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/model_optimizations/OBJLoaderMixin.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.embeddedt.modernfix.forge.mixin.perf.model_optimizations; - -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.client.model.obj.ObjLoader; -import net.minecraftforge.client.model.obj.ObjMaterialLibrary; -import net.minecraftforge.client.model.obj.ObjModel; -import org.embeddedt.modernfix.annotation.ClientOnlyMixin; -import org.objectweb.asm.Opcodes; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@Mixin(ObjLoader.class) -@ClientOnlyMixin -public class OBJLoaderMixin { - @Final - @Mutable - @Shadow(remap = false) private Map materialCache; - - @Final - @Mutable - @Shadow(remap = false) private Map modelCache; - - @Redirect(method = "", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraftforge/client/model/obj/ObjLoader;materialCache:Ljava/util/Map;", remap = false)) - private void useConcMap1(ObjLoader instance, Map value) { - this.materialCache = new ConcurrentHashMap<>(); - } - - @Redirect(method = "", at = @At(value = "FIELD", opcode = Opcodes.PUTFIELD, target = "Lnet/minecraftforge/client/model/obj/ObjLoader;modelCache:Ljava/util/Map;", remap = false)) - private void useConcMap2(ObjLoader instance, Map value) { - this.modelCache = new ConcurrentHashMap<>(); - } -} From 536eb03b50ee23e88dc203e4e270c3decb6d15b1 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 1 May 2025 15:18:15 -0400 Subject: [PATCH 2/4] Trigger classloading of Items when deferring blockstate cache rebuild --- .../perf/reduce_blockstate_cache_rebuilds/BlocksMixin.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlocksMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlocksMixin.java index fe28dff6..dac6f26d 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlocksMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlocksMixin.java @@ -1,5 +1,6 @@ package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds; +import net.minecraft.world.item.Items; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import org.embeddedt.modernfix.blockstate.BlockStateCacheHandler; @@ -22,6 +23,8 @@ public class BlocksMixin { // require = 0 due to Forge removing the BLOCK_STATE_REGISTRY init here @Redirect(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;initCache()V"), require = 0) private static void skipCacheInit(BlockState state) { + // Trigger classloading of Items in case a mod expects it. + Items.AIR.asItem(); // Mark the cache as invalid ((IBlockState)state).clearCache(); } From 01d2582c44c269dca24d168ea2b85895c1e18a8a Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 1 May 2025 16:32:21 -0400 Subject: [PATCH 3/4] Sync tag ingredients directly if ModernFix is installed on both sides --- .../main/resources/modernfix.accesswidener | 1 + .../ConnectionMixin.java | 33 ++++++++++ .../IngredientMixin.java | 63 +++++++++++++++++++ .../PlayerListMixin.java | 50 +++++++++++++++ .../modernfix/forge/packet/PacketHandler.java | 22 ++++--- 5 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/IngredientMixin.java create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/PlayerListMixin.java diff --git a/common/src/main/resources/modernfix.accesswidener b/common/src/main/resources/modernfix.accesswidener index f1291622..54f5368c 100644 --- a/common/src/main/resources/modernfix.accesswidener +++ b/common/src/main/resources/modernfix.accesswidener @@ -66,5 +66,6 @@ accessible field net/minecraft/server/packs/resources/ProfiledReloadInstance$Sta accessible field net/minecraft/server/packs/resources/ProfiledReloadInstance$State reloadNanos Ljava/util/concurrent/atomic/AtomicLong; accessible class net/minecraft/world/item/crafting/Ingredient$Value +accessible field net/minecraft/world/item/crafting/Ingredient$TagValue tag Lnet/minecraft/tags/TagKey; accessible class net/minecraft/world/item/crafting/Ingredient$ItemValue accessible class net/minecraft/client/searchtree/SearchRegistry$TreeEntry \ No newline at end of file diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java new file mode 100644 index 00000000..e9cbeeac --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java @@ -0,0 +1,33 @@ +package org.embeddedt.modernfix.forge.mixin.perf.smart_ingredient_sync; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.minecraft.network.Connection; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.PacketSendListener; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket; +import org.embeddedt.modernfix.forge.packet.PacketHandler; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Connection.class) +public class ConnectionMixin { + /** + * @author embeddedt + * @reason Provide context to the ingredient serializer about whether the enhanced sync protocol is supported. + */ + @WrapMethod(method = "doSendPacket") + private void modernfix$checkClientPresence(Packet packet, @Nullable PacketSendListener sendListener, ConnectionProtocol newProtocol, ConnectionProtocol currentProtocol, Operation original) { + if (packet instanceof ClientboundUpdateRecipesPacket && PacketHandler.INGREDIENT_SYNC.isRemotePresent((Connection)(Object)this)) { + PacketHandler.CLIENT_HAS_SMART_INGREDIENT_SYNC.set(true); + try { + original.call(packet, sendListener, newProtocol, currentProtocol); + } finally { + PacketHandler.CLIENT_HAS_SMART_INGREDIENT_SYNC.set(false); + } + } else { + original.call(packet, sendListener, newProtocol, currentProtocol); + } + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/IngredientMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/IngredientMixin.java new file mode 100644 index 00000000..771dbf0a --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/IngredientMixin.java @@ -0,0 +1,63 @@ +package org.embeddedt.modernfix.forge.mixin.perf.smart_ingredient_sync; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.crafting.Ingredient; +import org.embeddedt.modernfix.forge.packet.PacketHandler; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +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.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Ingredient.class) +public abstract class IngredientMixin { + @Shadow public abstract boolean isVanilla(); + + @Shadow @Final private Ingredient.Value[] values; + + @Unique + private static final ResourceLocation MODERNFIX_TAG_VALUE = new ResourceLocation("modernfix", "tag_value"); + + @Inject(method = "toNetwork", + at = @At("HEAD"), + cancellable = true) + private void checkForVanillaTagIngredient(FriendlyByteBuf buf, CallbackInfo ci) { + if (!PacketHandler.CLIENT_HAS_SMART_INGREDIENT_SYNC.get() || !this.isVanilla()) { + return; + } + Ingredient.Value[] values = this.values; + if (values.length == 1 && values[0] instanceof Ingredient.TagValue tagValue) { + var optionalHolderSet = BuiltInRegistries.ITEM.getTag(tagValue.tag); + if (optionalHolderSet.isEmpty()) { + // Use default serialization logic for tags that do not exist + return; + } + + // Encode this as our tag ingredient type instead of using vanilla's flattening logic. + ci.cancel(); + buf.writeVarInt(-1); + buf.writeResourceLocation(MODERNFIX_TAG_VALUE); + buf.writeResourceLocation(tagValue.tag.location()); + } + } + + @Inject(method = "fromNetwork", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/FriendlyByteBuf;readResourceLocation()Lnet/minecraft/resources/ResourceLocation;", ordinal = 0), cancellable = true) + private static void deserializeModernFixTagValue(FriendlyByteBuf buffer, CallbackInfoReturnable cir) { + int readerIndex = buffer.readerIndex(); + var type = buffer.readResourceLocation(); + if (!type.equals(MODERNFIX_TAG_VALUE)) { + // Allow Forge to read the original packet + buffer.readerIndex(readerIndex); + return; + } + var tag = buffer.readResourceLocation(); + cir.setReturnValue(Ingredient.of(TagKey.create(Registries.ITEM, tag))); + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/PlayerListMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/PlayerListMixin.java new file mode 100644 index 00000000..80e64379 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/smart_ingredient_sync/PlayerListMixin.java @@ -0,0 +1,50 @@ +package org.embeddedt.modernfix.forge.mixin.perf.smart_ingredient_sync; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket; +import net.minecraft.network.protocol.game.ClientboundUpdateTagsPacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.server.players.PlayerList; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.forge.packet.PacketHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; + +@Mixin(PlayerList.class) +public class PlayerListMixin { + /** + * @author embeddedt + * @reason Ensure the tag packet is always sent before the recipe packet (like it is after /reload). + */ + @Redirect(method = "placeNewPlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;)V"), + slice = @Slice( + from = @At(value = "NEW", target = "(Lnet/minecraft/server/players/PlayerList;Lnet/minecraft/server/level/ServerPlayer;)Lnet/minecraftforge/event/OnDatapackSyncEvent;"), + to = @At(value = "INVOKE", target = "Lnet/minecraft/server/players/PlayerList;sendPlayerPermissionLevel(Lnet/minecraft/server/level/ServerPlayer;)V") + ), expect = 2, allow = 2, require = 2) + private void modernfix$switchTagAndRecipeOrder(ServerGamePacketListenerImpl instance, Packet packet, @Local(ordinal = 0, argsOnly = true) Connection netManager, @Local(ordinal = 0, argsOnly = true) ServerPlayer player, @Share("deferred") LocalRef> pktRef) { + if (!(packet instanceof ClientboundUpdateRecipesPacket) && !(packet instanceof ClientboundUpdateTagsPacket)) { + throw new AssertionError("Mixin injected in wrong place"); + } + if (!PacketHandler.INGREDIENT_SYNC.isRemotePresent(netManager)) { + instance.send(packet); + return; + } + + if (packet instanceof ClientboundUpdateRecipesPacket) { + pktRef.set(packet); + } else { + ModernFix.LOGGER.info("Using enhanced recipe sync for player {}", player.getName().getString()); + // send tags + instance.send(packet); + // send recipes + instance.send(pktRef.get()); + } + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/packet/PacketHandler.java b/forge/src/main/java/org/embeddedt/modernfix/forge/packet/PacketHandler.java index e556618a..de7ad5a3 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/packet/PacketHandler.java +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/packet/PacketHandler.java @@ -13,17 +13,21 @@ import org.embeddedt.modernfix.packet.EntityIDSyncPacket; import java.util.function.Supplier; public class PacketHandler { - private static final String PROTOCOL_VERSION = "1"; - public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( - new ResourceLocation(ModernFix.MODID, "main"), - () -> PROTOCOL_VERSION, - NetworkRegistry.acceptMissingOr(PROTOCOL_VERSION), - NetworkRegistry.acceptMissingOr(PROTOCOL_VERSION) - ); + public static final SimpleChannel INSTANCE = buildChannel("main", "1"); + public static final SimpleChannel INGREDIENT_SYNC = buildChannel("ingredient_sync", "1"); + public static final ThreadLocal CLIENT_HAS_SMART_INGREDIENT_SYNC = ThreadLocal.withInitial(() -> false); + + private static SimpleChannel buildChannel(String name, String version) { + return NetworkRegistry.newSimpleChannel( + new ResourceLocation(ModernFix.MODID, name), + () -> version, + NetworkRegistry.acceptMissingOr(version), + NetworkRegistry.acceptMissingOr(version) + ); + } public static void register() { - int id = 1; - INSTANCE.registerMessage(id++, EntityIDSyncPacket.class, EntityIDSyncPacket::serialize, EntityIDSyncPacket::deserialize, PacketHandler::handleSyncPacket); + INSTANCE.registerMessage(1, EntityIDSyncPacket.class, EntityIDSyncPacket::serialize, EntityIDSyncPacket::deserialize, PacketHandler::handleSyncPacket); } private static void handleSyncPacket(EntityIDSyncPacket packet, Supplier contextSupplier) { From eed320b05588bcf3df39646701221242c5b5d9bc Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 1 May 2025 19:09:50 -0400 Subject: [PATCH 4/4] Optimize some methods in Ingredient and remove itemStacks caching --- .../main/resources/modernfix.accesswidener | 1 + .../faster_ingredients/ForgeHooksMixin.java | 23 +++ .../faster_ingredients/IngredientMixin.java | 134 ++++++++++++++++++ .../forge/recipe/ExtendedIngredient.java | 5 + 4 files changed, 163 insertions(+) create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/faster_ingredients/ForgeHooksMixin.java create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/faster_ingredients/IngredientMixin.java create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/recipe/ExtendedIngredient.java diff --git a/common/src/main/resources/modernfix.accesswidener b/common/src/main/resources/modernfix.accesswidener index 54f5368c..57322acc 100644 --- a/common/src/main/resources/modernfix.accesswidener +++ b/common/src/main/resources/modernfix.accesswidener @@ -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 \ No newline at end of file diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/faster_ingredients/ForgeHooksMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/faster_ingredients/ForgeHooksMixin.java new file mode 100644 index 00000000..a5cd1ca3 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/faster_ingredients/ForgeHooksMixin.java @@ -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 cir) { + if (ingredient.isVanilla()) { + cir.setReturnValue(((ExtendedIngredient)ingredient).mfix$hasNoElements()); + } + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/faster_ingredients/IngredientMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/faster_ingredients/IngredientMixin.java new file mode 100644 index 00000000..5fa2ab02 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/faster_ingredients/IngredientMixin.java @@ -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 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 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 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); + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/recipe/ExtendedIngredient.java b/forge/src/main/java/org/embeddedt/modernfix/forge/recipe/ExtendedIngredient.java new file mode 100644 index 00000000..db7014b4 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/recipe/ExtendedIngredient.java @@ -0,0 +1,5 @@ +package org.embeddedt.modernfix.forge.recipe; + +public interface ExtendedIngredient { + boolean mfix$hasNoElements(); +}