From 41da489c8559d37fad5dd4097d95512700380e49 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 5 Jan 2023 20:50:30 -0500 Subject: [PATCH] Add async JEI loading --- build.gradle | 21 ++-- gradle.properties | 3 +- .../core/config/ModernFixConfig.java | 1 + .../modernfix/jei/async/IAsyncJeiStarter.java | 8 ++ .../async/JEILoadingInterruptedException.java | 4 + .../ClientLifecycleHandlerMixin.java | 98 +++++++++++++++++++ .../IngredientListElementFactoryMixin.java | 20 ++++ .../mixin/perf/async_jei/JeiStarterMixin.java | 47 +++++++++ .../perf/async_jei/PluginCallerMixin.java | 21 ++++ .../async_jei/RecipeManagerInternalMixin.java | 18 ++++ src/main/resources/META-INF/mods.toml | 7 ++ src/main/resources/modernfix.mixins.json | 7 +- 12 files changed, 245 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/jei/async/IAsyncJeiStarter.java create mode 100644 src/main/java/org/embeddedt/modernfix/jei/async/JEILoadingInterruptedException.java create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/ClientLifecycleHandlerMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/IngredientListElementFactoryMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/JeiStarterMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/PluginCallerMixin.java create mode 100644 src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/RecipeManagerInternalMixin.java diff --git a/build.gradle b/build.gradle index 62ca5595..2f46bf90 100644 --- a/build.gradle +++ b/build.gradle @@ -121,14 +121,16 @@ repositories { includeGroup "curse.maven" } } - - // Put repositories for dependencies here - // ForgeGradle automatically adds the Forge maven and Maven Central for you - - // If you have mod jar dependencies in ./libs, you can declare them as a repository like so: - // flatDir { - // dir 'libs' - // } + maven { + // location of the maven that hosts JEI files + name = "Progwml6 maven" + url = "https://dvs1.progwml6.com/files/maven/" + } + maven { + // location of a maven mirror for JEI files, as a fallback + name = "ModMaven" + url = "https://modmaven.dev" + } } dependencies { @@ -148,6 +150,9 @@ dependencies { runtimeOnly fg.deobf("mekanism:Mekanism:${mekanism_version}")// core runtimeOnly fg.deobf("mekanism:Mekanism:${mekanism_version}:generators")// Mekanism: Generators runtimeOnly fg.deobf("mekanism:Mekanism:${mekanism_version}:tools")// Mekanism: Tools + + compileOnly fg.deobf("mezz.jei:jei-${minecraft_version}:${jei_version}") + runtimeOnly fg.deobf("mezz.jei:jei-${minecraft_version}:${jei_version}") } // Example for how to get properties into the manifest for reading at runtime. diff --git a/gradle.properties b/gradle.properties index e07e4dc6..a3f73b4e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,5 @@ minecraft_version=1.16.5 forge_version=36.2.39 lazydfu_version=3249059 mekanism_version=1.16.5-10.1.2.457 -parchment_version=2022.03.06 \ No newline at end of file +parchment_version=2022.03.06 +jei_version=7.7.1.153 \ No newline at end of file diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java index 792a2e49..325cbd70 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java @@ -28,6 +28,7 @@ public class ModernFixConfig { this.addMixinRule("perf.trim_model_caches", true); this.addMixinRule("bugfix.concurrency", true); this.addMixinRule("bugfix.edge_chunk_not_saved", true); + this.addMixinRule("perf.async_jei", true); } /** diff --git a/src/main/java/org/embeddedt/modernfix/jei/async/IAsyncJeiStarter.java b/src/main/java/org/embeddedt/modernfix/jei/async/IAsyncJeiStarter.java new file mode 100644 index 00000000..52ff2f04 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/jei/async/IAsyncJeiStarter.java @@ -0,0 +1,8 @@ +package org.embeddedt.modernfix.jei.async; + +public interface IAsyncJeiStarter { + static void checkForLoadInterruption() { + if(Thread.currentThread().isInterrupted()) + throw new JEILoadingInterruptedException(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/jei/async/JEILoadingInterruptedException.java b/src/main/java/org/embeddedt/modernfix/jei/async/JEILoadingInterruptedException.java new file mode 100644 index 00000000..5bba7273 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/jei/async/JEILoadingInterruptedException.java @@ -0,0 +1,4 @@ +package org.embeddedt.modernfix.jei.async; + +public class JEILoadingInterruptedException extends RuntimeException { +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/ClientLifecycleHandlerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/ClientLifecycleHandlerMixin.java new file mode 100644 index 00000000..819759fc --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/ClientLifecycleHandlerMixin.java @@ -0,0 +1,98 @@ +package org.embeddedt.modernfix.mixin.perf.async_jei; + +import mezz.jei.api.IModPlugin; +import mezz.jei.api.helpers.IModIdHelper; +import mezz.jei.config.*; +import mezz.jei.config.sorting.RecipeCategorySortingConfig; +import mezz.jei.events.EventBusHelper; +import mezz.jei.events.PlayerJoinedWorldEvent; +import mezz.jei.gui.textures.Textures; +import mezz.jei.ingredients.IIngredientSorter; +import mezz.jei.startup.ClientLifecycleHandler; +import mezz.jei.startup.JeiStarter; +import mezz.jei.startup.NetworkHandler; +import net.minecraft.client.Minecraft; +import net.minecraftforge.client.event.ClientPlayerNetworkEvent; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.jei.async.JEILoadingInterruptedException; +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.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(ClientLifecycleHandler.class) +public class ClientLifecycleHandlerMixin { + @Shadow @Final private JeiStarter starter; + @Shadow @Final private List plugins; + @Shadow @Final private Textures textures; + @Shadow @Final private IClientConfig clientConfig; + @Shadow @Final private IEditModeConfig editModeConfig; + @Shadow @Final private IngredientFilterConfig ingredientFilterConfig; + @Shadow @Final private WorldConfig worldConfig; + @Shadow @Final private BookmarkConfig bookmarkConfig; + @Shadow @Final private IModIdHelper modIdHelper; + @Shadow @Final private RecipeCategorySortingConfig recipeCategorySortingConfig; + @Shadow @Final private IIngredientSorter ingredientSorter; + private volatile Thread reloadThread = null; + @Inject(method = "setupJEI", at = @At(value = "INVOKE", target = "Lmezz/jei/startup/ClientLifecycleHandler;startJEI()V"), cancellable = true, remap = false) + private void startAsync(CallbackInfo ci) { + ci.cancel(); + startJEIAsync(() -> Minecraft.getInstance().execute(() -> EventBusHelper.post(new PlayerJoinedWorldEvent()))); + } + + /** + * @author embeddedt + * @reason force JEI starts to be asynchronous + */ + @Overwrite(remap = false) + public void startJEI() { + startJEIAsync(() -> {}); + } + + @Inject(method = "", at = @At("TAIL")) + private void setupCancellationHandler(NetworkHandler networkHandler, Textures textures, CallbackInfo ci) { + EventBusHelper.addListener(this, ClientPlayerNetworkEvent.LoggedOutEvent.class, event -> cancelPreviousStart()); + } + + private void cancelPreviousStart() { + Thread currentReloadThread = reloadThread; + if(currentReloadThread != null) { + currentReloadThread.interrupt(); + Minecraft.getInstance().managedBlock(currentReloadThread::isAlive); + reloadThread = null; + } + } + + private void startJEIAsync(Runnable whenFinishedCb) { + cancelPreviousStart(); + Thread newThread = new Thread(() -> { + try { + starter.start( + plugins, + textures, + clientConfig, + editModeConfig, + ingredientFilterConfig, + worldConfig, + bookmarkConfig, + modIdHelper, + recipeCategorySortingConfig, + ingredientSorter); + } catch(JEILoadingInterruptedException e) { + ModernFix.LOGGER.warn("JEI loading interrupted prematurely (this is normal)"); + } + whenFinishedCb.run(); + reloadThread = null; + }, "JEI Reload Thread"); + newThread.setPriority(Thread.MIN_PRIORITY); + reloadThread = newThread; + newThread.start(); + } + + +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/IngredientListElementFactoryMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/IngredientListElementFactoryMixin.java new file mode 100644 index 00000000..d75f1713 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/IngredientListElementFactoryMixin.java @@ -0,0 +1,20 @@ +package org.embeddedt.modernfix.mixin.perf.async_jei; + +import mezz.jei.api.ingredients.IIngredientType; +import mezz.jei.api.runtime.IIngredientManager; +import mezz.jei.gui.ingredients.IIngredientListElement; +import mezz.jei.ingredients.IngredientListElementFactory; +import net.minecraft.util.NonNullList; +import org.embeddedt.modernfix.jei.async.IAsyncJeiStarter; +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; + +@Mixin(IngredientListElementFactory.class) +public class IngredientListElementFactoryMixin { + @Inject(method = "addToBaseList", at = @At(value = "INVOKE", target = "Lmezz/jei/ingredients/IngredientOrderTracker;getOrderIndex(Ljava/lang/Object;Lmezz/jei/api/ingredients/IIngredientHelper;)I"), remap = false) + private static void checkForInterrupt(NonNullList> baseList, IIngredientManager ingredientManager, IIngredientType ingredientType, CallbackInfo ci) { + IAsyncJeiStarter.checkForLoadInterruption(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/JeiStarterMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/JeiStarterMixin.java new file mode 100644 index 00000000..4da9dec1 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/JeiStarterMixin.java @@ -0,0 +1,47 @@ +package org.embeddedt.modernfix.mixin.perf.async_jei; + +import mezz.jei.api.IModPlugin; +import mezz.jei.api.helpers.IModIdHelper; +import mezz.jei.config.*; +import mezz.jei.config.sorting.RecipeCategorySortingConfig; +import mezz.jei.gui.textures.Textures; +import mezz.jei.ingredients.IIngredientSorter; +import mezz.jei.load.PluginCaller; +import mezz.jei.startup.JeiStarter; +import net.minecraft.client.Minecraft; +import org.embeddedt.modernfix.jei.async.IAsyncJeiStarter; +import org.embeddedt.modernfix.jei.async.JEILoadingInterruptedException; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CancellationException; + +import java.util.List; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; + +@Mixin(JeiStarter.class) +public class JeiStarterMixin { + @Shadow private boolean started; + + @Inject(method = "start", at = @At(value = "INVOKE", target = "Lmezz/jei/util/ErrorUtil;checkNotEmpty(Ljava/util/Collection;Ljava/lang/String;)V", ordinal = 0, shift = At.Shift.AFTER), remap = false) + private void setStartedFlag(List plugins, Textures textures, IClientConfig clientConfig, IEditModeConfig editModeConfig, IIngredientFilterConfig ingredientFilterConfig, IWorldConfig worldConfig, BookmarkConfig bookmarkConfig, IModIdHelper modIdHelper, RecipeCategorySortingConfig recipeCategorySortingConfig, IIngredientSorter ingredientSorter, CallbackInfo ci) { + /* We need to set this ASAP so the reload system will restart the async load if needed */ + started = true; + } + + @Redirect(method = "start", at = @At(value = "INVOKE", target = "Lmezz/jei/load/PluginCaller;callOnPlugins(Ljava/lang/String;Ljava/util/List;Ljava/util/function/Consumer;)V"), remap = false) + private void callOnPluginsViaMainThread(String title, List plugins, Consumer func) { + PluginCaller.callOnPlugins(title, plugins, plugin -> { + try { + Minecraft.getInstance().executeBlocking(() -> func.accept(plugin)); + } catch(CancellationException | CompletionException e) { + Thread.currentThread().interrupt(); + } + }); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/PluginCallerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/PluginCallerMixin.java new file mode 100644 index 00000000..a6e21979 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/PluginCallerMixin.java @@ -0,0 +1,21 @@ +package org.embeddedt.modernfix.mixin.perf.async_jei; + +import mezz.jei.api.IModPlugin; +import mezz.jei.load.PluginCaller; +import org.embeddedt.modernfix.jei.async.IAsyncJeiStarter; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; +import java.util.function.Consumer; + +@Mixin(PluginCaller.class) +public class PluginCallerMixin { + @Inject(method = "callOnPlugins", at = @At(value = "INVOKE", target = "Ljava/util/Iterator;hasNext()Z"), remap = false) + private static void checkForInterrupt(String title, List plugins, Consumer func, CallbackInfo ci) { + IAsyncJeiStarter.checkForLoadInterruption(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/RecipeManagerInternalMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/RecipeManagerInternalMixin.java new file mode 100644 index 00000000..eb750559 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/async_jei/RecipeManagerInternalMixin.java @@ -0,0 +1,18 @@ +package org.embeddedt.modernfix.mixin.perf.async_jei; + +import com.google.common.collect.ImmutableListMultimap; +import mezz.jei.recipes.RecipeManagerInternal; +import net.minecraft.util.ResourceLocation; +import org.embeddedt.modernfix.jei.async.IAsyncJeiStarter; +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; + +@Mixin(RecipeManagerInternal.class) +public class RecipeManagerInternalMixin { + @Inject(method = "addRecipes", at = @At(value = "INVOKE", target = "Lmezz/jei/recipes/RecipeManagerInternal;addRecipeTyped(Ljava/lang/Object;Lnet/minecraft/util/ResourceLocation;)V"), remap = false) + private void checkForInterrupt(ImmutableListMultimap recipes, CallbackInfo ci) { + IAsyncJeiStarter.checkForLoadInterruption(); + } +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index f5220d47..0fbfd925 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -56,3 +56,10 @@ mandatory = true versionRange = "[1.16.5,1.17)" ordering = "NONE" side = "BOTH" +[[dependencies.modernfix]] +modId = "jei" +mandatory = false +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange = "[7.7.1.153,)" +ordering = "BEFORE" +side = "CLIENT" diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 8e3e41e8..9748ed78 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -22,7 +22,12 @@ "perf.skip_first_datapack_reload.MinecraftMixin", "bugfix.concurrency.RenderTypeMixin", "perf.parallelize_model_loading.ModelBakeryMixin", - "perf.trim_model_caches.ModelManagerMixin" + "perf.trim_model_caches.ModelManagerMixin", + "perf.async_jei.IngredientListElementFactoryMixin", + "perf.async_jei.ClientLifecycleHandlerMixin", + "perf.async_jei.JeiStarterMixin", + "perf.async_jei.PluginCallerMixin", + "perf.async_jei.RecipeManagerInternalMixin" ], "injectors": { "defaultRequire": 1