From 64a365a9006a7c423f3c12cece103f3e10eb7461 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 1 May 2025 19:33:08 -0400 Subject: [PATCH] Merge remote-tracking branch 'origin/1.20' into 1.21.1 --- .../neoforge/init/ModernFixForge.java | 14 ++ .../faster_ingredients/IngredientMixin.java | 141 ++++++++++++++++++ .../model_optimizations/OBJLoaderMixin.java | 41 ----- .../ConnectionMixin.java | 34 +++++ .../IngredientMixin.java | 54 +++++++ .../packet/SmartIngredientSyncPayload.java | 21 +++ 6 files changed, 264 insertions(+), 41 deletions(-) create mode 100644 neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/faster_ingredients/IngredientMixin.java delete mode 100644 neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/model_optimizations/OBJLoaderMixin.java create mode 100644 neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java create mode 100644 neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/smart_ingredient_sync/IngredientMixin.java create mode 100644 neoforge/src/main/java/org/embeddedt/modernfix/neoforge/packet/SmartIngredientSyncPayload.java diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java index 2f552516..a8e49e13 100644 --- a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/init/ModernFixForge.java @@ -17,11 +17,14 @@ import net.neoforged.fml.loading.FMLLoader; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppedEvent; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import net.neoforged.neoforge.network.registration.PayloadRegistrar; import net.neoforged.neoforge.registries.RegisterEvent; import org.apache.commons.lang3.tuple.Pair; import org.embeddedt.modernfix.ModernFix; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.neoforge.ModernFixConfig; +import org.embeddedt.modernfix.neoforge.packet.SmartIngredientSyncPayload; import java.util.List; @@ -36,6 +39,7 @@ public class ModernFixForge { NeoForge.EVENT_BUS.register(this); modBus.addListener(this::commonSetup); modBus.addListener(this::registerItems); + modBus.addListener(this::registerNetworkChannel); if(FMLEnvironment.dist == Dist.CLIENT) { NeoForge.EVENT_BUS.register(new ModernFixClientForge(modContainer, modBus)); } @@ -74,6 +78,16 @@ public class ModernFixForge { } } + private void registerNetworkChannel(final RegisterPayloadHandlersEvent event) { + // Sets the current network version + final PayloadRegistrar registrar = event.registrar("1").optional(); + registrar.playToClient( + SmartIngredientSyncPayload.TYPE, + SmartIngredientSyncPayload.STREAM_CODEC, + (payload, ctx) -> {} + ); + } + @SubscribeEvent(priority = EventPriority.LOWEST) public void onServerDead(ServerStoppedEvent event) { commonMod.onServerDead(event.getServer()); diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/faster_ingredients/IngredientMixin.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/faster_ingredients/IngredientMixin.java new file mode 100644 index 00000000..7c3eb208 --- /dev/null +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/faster_ingredients/IngredientMixin.java @@ -0,0 +1,141 @@ +package org.embeddedt.modernfix.neoforge.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.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 { + @Shadow @Final + private Ingredient.Value[] values; + + @Shadow private @Nullable IntList stackingIds; + + @Shadow @Nullable private ItemStack[] itemStacks; + + @Shadow public abstract boolean isCustom(); + + @Unique + private boolean isVanilla() { + return !this.isCustom(); + } + + /** + * @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.isCustom() && this.values.length == 1 && this.values[0] instanceof Ingredient.TagValue tagValue) { + cir.setReturnValue(stack.getItemHolder().is(tagValue.tag())); + } + } + + /** + * @author embeddedt + * @reason exploding the stack list is unnecessary + */ + @Overwrite(remap = false) + public boolean hasNoItems() { + 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/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/model_optimizations/OBJLoaderMixin.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/model_optimizations/OBJLoaderMixin.java deleted file mode 100644 index bb4611df..00000000 --- a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/model_optimizations/OBJLoaderMixin.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.embeddedt.modernfix.neoforge.mixin.perf.model_optimizations; - -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.client.model.obj.ObjLoader; -import net.neoforged.neoforge.client.model.obj.ObjMaterialLibrary; -import net.neoforged.neoforge.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 { - /* TODO: Remove if unnecessary - @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/neoforged/neoforge/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/neoforged/neoforge/client/model/obj/ObjLoader;modelCache:Ljava/util/Map;", remap = false)) - private void useConcMap2(ObjLoader instance, Map value) { - this.modelCache = new ConcurrentHashMap<>(); - } - */ -} diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java new file mode 100644 index 00000000..6d5efff7 --- /dev/null +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/smart_ingredient_sync/ConnectionMixin.java @@ -0,0 +1,34 @@ +package org.embeddedt.modernfix.neoforge.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 net.neoforged.neoforge.network.registration.NetworkRegistry; +import org.embeddedt.modernfix.neoforge.packet.SmartIngredientSyncPayload; +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, PacketSendListener sendListener, boolean flush, Operation original) { + if (packet instanceof ClientboundUpdateRecipesPacket && NetworkRegistry.hasChannel((Connection)(Object)this, ConnectionProtocol.PLAY, SmartIngredientSyncPayload.TYPE.id())) { + SmartIngredientSyncPayload.CLIENT_HAS_SMART_INGREDIENT_SYNC.set(true); + try { + original.call(packet, sendListener, flush); + } finally { + SmartIngredientSyncPayload.CLIENT_HAS_SMART_INGREDIENT_SYNC.set(false); + } + } else { + original.call(packet, sendListener, flush); + } + } +} diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/smart_ingredient_sync/IngredientMixin.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/smart_ingredient_sync/IngredientMixin.java new file mode 100644 index 00000000..4952e075 --- /dev/null +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/smart_ingredient_sync/IngredientMixin.java @@ -0,0 +1,54 @@ +package org.embeddedt.modernfix.neoforge.mixin.perf.smart_ingredient_sync; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.crafting.Ingredient; +import org.embeddedt.modernfix.neoforge.packet.SmartIngredientSyncPayload; +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.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(targets = {"net/minecraft/world/item/crafting/Ingredient$1"}) +public abstract class IngredientMixin { + + @Inject(method = "encode(Lnet/minecraft/network/RegistryFriendlyByteBuf;Lnet/minecraft/world/item/crafting/Ingredient;)V", + at = @At(value = "FIELD", target = "Lnet/minecraft/world/item/ItemStack;LIST_STREAM_CODEC:Lnet/minecraft/network/codec/StreamCodec;"), + cancellable = true) + private void checkForVanillaTagIngredient(RegistryFriendlyByteBuf buf, Ingredient ingredient, CallbackInfo ci) { + if (!SmartIngredientSyncPayload.CLIENT_HAS_SMART_INGREDIENT_SYNC.get() || ingredient.isCustom()) { + return; + } + Ingredient.Value[] values = ingredient.getValues(); + 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(-2); + buf.writeResourceLocation(tagValue.tag().location()); + } + } + + @Inject(method = "decode(Lnet/minecraft/network/RegistryFriendlyByteBuf;)Lnet/minecraft/world/item/crafting/Ingredient;", + at = @At(value = "HEAD"), + cancellable = true, remap = false) + private void decodeSmartIngredient(RegistryFriendlyByteBuf buf, CallbackInfoReturnable cir) { + int readerIndex = buf.readerIndex(); + var sizeId = buf.readVarInt(); + if (sizeId == -2) { + // Probably our ingredient + var tagKey = TagKey.create(Registries.ITEM, buf.readResourceLocation()); + cir.setReturnValue(Ingredient.of(tagKey)); + } else { + buf.readerIndex(readerIndex); + } + } +} diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/packet/SmartIngredientSyncPayload.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/packet/SmartIngredientSyncPayload.java new file mode 100644 index 00000000..26d36914 --- /dev/null +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/packet/SmartIngredientSyncPayload.java @@ -0,0 +1,21 @@ +package org.embeddedt.modernfix.neoforge.packet; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.embeddedt.modernfix.ModernFix; + +public enum SmartIngredientSyncPayload implements CustomPacketPayload { + INSTANCE; + + public static final ThreadLocal CLIENT_HAS_SMART_INGREDIENT_SYNC = ThreadLocal.withInitial(() -> false); + + public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(ModernFix.MODID, "ingredient_sync")); + public static final StreamCodec STREAM_CODEC = StreamCodec.unit(INSTANCE); + + @Override + public Type type() { + return TYPE; + } +} \ No newline at end of file