diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/load/MinecraftServerReloadTracker.java b/forge/src/main/java/org/embeddedt/modernfix/forge/load/MinecraftServerReloadTracker.java new file mode 100644 index 00000000..7b8e06a9 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/load/MinecraftServerReloadTracker.java @@ -0,0 +1,9 @@ +package org.embeddedt.modernfix.forge.load; + +public class MinecraftServerReloadTracker { + public static int ACTIVE_RELOADS = 0; + + public static boolean isReloadActive() { + return ACTIVE_RELOADS > 0; + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/core/MinecraftServerMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/core/MinecraftServerMixin.java new file mode 100644 index 00000000..fa641559 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/core/MinecraftServerMixin.java @@ -0,0 +1,28 @@ +package org.embeddedt.modernfix.forge.mixin.core; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.server.MinecraftServer; +import org.embeddedt.modernfix.forge.load.MinecraftServerReloadTracker; +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; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@Mixin(MinecraftServer.class) +public class MinecraftServerMixin { + @Inject(method = "reloadResources", at = @At("HEAD")) + private void startReloadTrack(Collection selectedIds, CallbackInfoReturnable> cir) { + MinecraftServerReloadTracker.ACTIVE_RELOADS++; + } + + @ModifyExpressionValue(method = "reloadResources", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenAcceptAsync(Ljava/util/function/Consumer;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", ordinal = 0)) + private CompletableFuture mfix$endReloadTrack(CompletableFuture original) { + return original.thenAcceptAsync(val -> { + MinecraftServerReloadTracker.ACTIVE_RELOADS--; + }, (Executor)this); + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/core/WorldLoaderMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/core/WorldLoaderMixin.java new file mode 100644 index 00000000..bce85695 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/core/WorldLoaderMixin.java @@ -0,0 +1,29 @@ +package org.embeddedt.modernfix.forge.mixin.core; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.server.WorldLoader; +import org.embeddedt.modernfix.forge.load.MinecraftServerReloadTracker; +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; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@Mixin(WorldLoader.class) +public class WorldLoaderMixin { + @Inject(method = "load", at = @At("HEAD")) + private static void trackStartReload(CallbackInfoReturnable> cir) { + MinecraftServerReloadTracker.ACTIVE_RELOADS++; + } + + @ModifyReturnValue(method = "load", at = @At("RETURN")) + private static CompletableFuture trackEndReload(CompletableFuture original, @Local(ordinal = 1, argsOnly = true) Executor syncExecutor) { + return original.thenApplyAsync(val -> { + MinecraftServerReloadTracker.ACTIVE_RELOADS--; + return val; + }, syncExecutor); + } +} 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 index 5e9b0a15..e33ca3fd 100644 --- 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 @@ -6,6 +6,7 @@ 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.load.MinecraftServerReloadTracker; import org.embeddedt.modernfix.forge.recipe.ExtendedIngredient; import org.embeddedt.modernfix.forge.recipe.IngredientItemStacksSoftReference; import org.jetbrains.annotations.Nullable; @@ -34,13 +35,32 @@ public abstract class IngredientMixin implements ExtendedIngredient { private volatile IngredientItemStacksSoftReference mfix$cachedItemStacks; + /** + * Minecraft's server resource loading process has a design flaw in that tags are loaded, recipes are loaded, + * then tags are bound to the server registries. This results in recipe modification mods like KubeJS/CraftTweaker + * not being able to use Ingredient.test reliably as the TagValue will not find any contents for the given tag. + * To work around this issue these mods track tag context themselves and then patch TagValue.getItems to use it + * during the resource reload process. We often bypass Value.getItems, so we must disable that bypass + * whenever a server reload is in progress. + *

+ * An alternative fix would be to bind tags ourselves when the recipe manager reload begins, before control is + * handed to these mods. However, it's unclear if there would be any negative side effects from binding tags early + * like this. Moreover, this fix would only work if the mod-provided patches to getItems read exactly what the + * registry would normally contain, rather than a modified version. + *

+ * Note: this is a separate problem from the issue where clients may receive recipes before tags in 1.21. + */ + private boolean mfix$areTagsAvailable() { + return !MinecraftServerReloadTracker.isReloadActive(); + } + /** * @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) { + if (this.isVanilla() && this.values.length == 1 && this.values[0] instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) { cir.setReturnValue(stack.getItemHolder().is(tagValue.tag)); } } @@ -60,7 +80,7 @@ public abstract class IngredientMixin implements ExtendedIngredient { for (Ingredient.Value value : this.values) { if (value instanceof Ingredient.ItemValue) { return true; - } else if (value instanceof Ingredient.TagValue tagValue) { + } else if (value instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) { var holderSetOpt = BuiltInRegistries.ITEM.getTag(tagValue.tag); if (holderSetOpt.isPresent() && holderSetOpt.get().size() > 0) { return true; @@ -84,7 +104,7 @@ public abstract class IngredientMixin implements ExtendedIngredient { */ @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) { + if (this.isVanilla() && this.values.length == 1 && this.values[0] instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) { var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag); if (!tag.isPresent() || tag.get().size() == 0) { return; @@ -124,7 +144,7 @@ public abstract class IngredientMixin implements ExtendedIngredient { 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) { + } else if (this.values[0] instanceof Ingredient.TagValue tagValue && mfix$areTagsAvailable()) { var tag = BuiltInRegistries.ITEM.getTag(tagValue.tag); if (tag.isPresent() && tag.get().size() > 0) { var holderSet = tag.get();