Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP async loading #3219

Draft
wants to merge 23 commits into
base: 1.19.4
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8a443c8
Add config to reload JEI asynchronously
embeddedt Mar 12, 2023
4f57504
Add task executor system that runs on the end of a client tick
embeddedt Mar 20, 2023
f73ff52
Add executor to IRuntimeRegistration, and modify ingredient filter
embeddedt Mar 20, 2023
2e07a44
Add sanity check for tasks enqueued on main thread
embeddedt Mar 21, 2023
d4ce812
Add ability to configure specific JEI plugins to run on main thread
embeddedt Mar 21, 2023
d29f535
Move executor to IJeiHelpers
embeddedt Apr 14, 2023
4c85ffe
Merge remote-tracking branch 'origin/1.19.4' into embeddedt-async-reload
mezz May 14, 2023
6b33ae2
WIP async loading
mezz May 15, 2023
0f1c725
cleanup
mezz May 15, 2023
1cd6d35
fix calls to runtime plugins
mezz May 15, 2023
411316a
fix 'since' tags in IAsyncModPlugin and IRuntimePlugin
mezz May 15, 2023
a33ee6a
fix tests
mezz May 15, 2023
31a6863
Simplify use of JeiPlugin
mezz May 16, 2023
e96d726
remove useless interrupt check on client thread
mezz May 16, 2023
6f03577
Only allow one runtime plugin at a time. Use JeiPlugin annotation for…
mezz May 20, 2023
d598989
simplify JeiStartTask#interruptIfCanceled
mezz May 21, 2023
43d81e7
move configs to their own subcategory
mezz May 21, 2023
2ecf7e6
fix crash when plugin uid crashes
mezz May 21, 2023
442074d
remove parallel loading
mezz May 21, 2023
58f38d3
remove parallel loading
mezz May 22, 2023
4bfb889
fix config description
mezz May 22, 2023
89aa6da
fix fabric mod plugin declarations
mezz May 22, 2023
e81d6bf
clean up imports
mezz May 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions Common/src/main/java/mezz/jei/common/async/JeiStartTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package mezz.jei.common.async;

public class JeiStartTask extends Thread {
private boolean isCancelled = false;

public JeiStartTask(Runnable target) {
super(target, "JEI Start");
setDaemon(true);
}

public void cancelStart() {
isCancelled = true;
}

public static void interruptIfCanceled() {
Thread t = Thread.currentThread();
if (t instanceof JeiStartTask startTask) {
if (startTask.isCancelled) {
throw new JeiAsyncStartInterrupt();
}
}
}

@Override
public void run() {
try {
super.run();
} catch (JeiAsyncStartInterrupt ignored) {

}
}

private static final class JeiAsyncStartInterrupt extends Error {
public JeiAsyncStartInterrupt() {
}
}
}
13 changes: 13 additions & 0 deletions Common/src/main/java/mezz/jei/common/config/ClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public final class ClientConfig implements IClientConfig {
private final Supplier<Boolean> centerSearchBarEnabled;
private final Supplier<Boolean> lowMemorySlowSearchEnabled;
private final Supplier<Boolean> cheatToHotbarUsingHotkeysEnabled;
private final Supplier<Boolean> asyncLoadingEnabled;
private final Supplier<Boolean> addBookmarksToFront;
private final Supplier<Boolean> lookupFluidContents;
private final Supplier<GiveMode> giveMode;
Expand Down Expand Up @@ -65,6 +66,13 @@ public ClientConfig(IConfigSchemaBuilder schema) {
"Max. recipe gui height"
);

IConfigCategoryBuilder loading = schema.addCategory("loading");
asyncLoadingEnabled = loading.addBoolean(
"asyncLoadingEnabled",
false,
"Whether JEI should load asynchronously, so that it finishes loading after world join."
);

IConfigCategoryBuilder sorting = schema.addCategory("sorting");
ingredientSorterStages = sorting.addList(
"IngredientSortStages",
Expand Down Expand Up @@ -98,6 +106,11 @@ public boolean isCheatToHotbarUsingHotkeysEnabled() {
return cheatToHotbarUsingHotkeysEnabled.get();
}

@Override
public boolean getAsyncLoadingEnabled() {
return asyncLoadingEnabled.get();
}

@Override
public boolean isAddingBookmarksToFront() {
return addBookmarksToFront.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public interface IClientConfig {

boolean isCheatToHotbarUsingHotkeysEnabled();

boolean getAsyncLoadingEnabled();

boolean isAddingBookmarksToFront();

boolean isLookupFluidContents();
Expand Down
14 changes: 0 additions & 14 deletions Common/src/main/java/mezz/jei/common/util/ErrorUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.client.Minecraft;
import net.minecraft.core.NonNullList;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
Expand Down Expand Up @@ -135,19 +134,6 @@ public static void checkNotNull(Collection<?> values, String name) {
}
}

@SuppressWarnings("ConstantConditions")
public static void assertMainThread() {
Minecraft minecraft = Minecraft.getInstance();
if (minecraft != null && !minecraft.isSameThread()) {
Thread currentThread = Thread.currentThread();
throw new IllegalStateException(
"A JEI API method is being called by another mod from the wrong thread:\n" +
currentThread + "\n" +
"It must be called on the main thread by using Minecraft.addScheduledTask."
);
}
}

public static <T> ReportedException createRenderIngredientException(Throwable throwable, final T ingredient, IIngredientManager ingredientManager) {
CrashReport crashreport = CrashReport.forThrowable(throwable, "Rendering ingredient");
CrashReportCategory ingredientCategory = crashreport.addCategory("Ingredient being rendered");
Expand Down
11 changes: 10 additions & 1 deletion CommonApi/src/main/java/mezz/jei/api/IModPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@
import mezz.jei.api.registration.IVanillaCategoryExtensionRegistration;
import mezz.jei.api.runtime.IJeiRuntime;


/**
* The main class to implement to create a JEI plugin. Everything communicated between a mod and JEI is through this class.
* IModPlugins must have the {@link JeiPlugin} annotation to get loaded by JEI.
*
* In a Forge environment, IModPlugins must have the {@link JeiPlugin} annotation to get loaded by JEI.
*
* In a Fabric environment, IModPlugins must be declared under `entrypoints.jei_mod_plugin` in `fabric.mod.json`.
* See <a href="https://fabricmc.net/wiki/documentation:entrypoint">the Fabric Wiki</a> for more information.
*/
public interface IModPlugin {

Expand Down Expand Up @@ -109,7 +114,11 @@ default void registerAdvanced(IAdvancedRegistration registration) {

/**
* Override the default JEI runtime.
*
* @since 12.0.2
* @deprecated this has moved to {@link IRuntimePlugin}
*/
@Deprecated(since = "13.2.0", forRemoval = true)
default void registerRuntime(IRuntimeRegistration registration) {

}
Expand Down
58 changes: 58 additions & 0 deletions CommonApi/src/main/java/mezz/jei/api/IRuntimePlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package mezz.jei.api;

import mezz.jei.api.registration.IRuntimeRegistration;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.resources.ResourceLocation;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

/**
* A runtime plugin is used to override the default JEI runtime.
* Only one runtime plugin will be used, so if you create one then JEI's will be deactivated.
* This is intended for mods that implement a GUI that completely replaces JEI's.
*
* In a Forge environment, IRuntimePlugins must have the {@link JeiPlugin} annotation to get loaded by JEI.
*
* In a Fabric environment, IModPlugins must be declared under `entrypoints.jei_runtime_plugin` in `fabric.mod.json`.
* See <a href="https://fabricmc.net/wiki/documentation:entrypoint">the Fabric Wiki</a> for more information.
*
* @since 13.2.0
*/
public interface IRuntimePlugin {

/**
* The unique ID for this mod plugin.
* The namespace should be your mod's modId.
*
* @since 13.2.0
*/
ResourceLocation getPluginUid();

/**
* Override the default JEI runtime.
*
* @since 13.2.0
*/
default CompletableFuture<Void> registerRuntime(IRuntimeRegistration registration, Executor clientExecutor) {
return CompletableFuture.completedFuture(null);
}

/**
* Called when JEI's runtime features are available, after all mods have registered.
*
* @since 13.2.0
*/
default CompletableFuture<Void> onRuntimeAvailable(IJeiRuntime jeiRuntime, Executor clientExecutor) {
return CompletableFuture.completedFuture(null);
}

/**
* Called when JEI's runtime features are no longer available, after a user quits or logs out of a world.
*
* @since 13.2.0
*/
default CompletableFuture<Void> onRuntimeUnavailable(Executor clientExecutor) {
return CompletableFuture.completedFuture(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ public class JeiLifecycleEvents {
}
});

public static final Event<Runnable> CLIENT_TICK_END =
EventFactory.createArrayBacked(Runnable.class, callbacks -> () -> {
for (Runnable callback : callbacks) {
callback.run();
}
});


@Environment(EnvType.CLIENT)
@FunctionalInterface
public interface RegisterResourceReloadListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import mezz.jei.api.runtime.IIngredientListOverlay;
import mezz.jei.api.runtime.IJeiRuntime;
import mezz.jei.fabric.plugins.fabric.FabricGuiPlugin;
import mezz.jei.fabric.plugins.fabric.FabricRuntimePlugin;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.gui.screens.inventory.EffectRenderingInventoryScreen;
import net.minecraft.network.chat.Component;
Expand All @@ -25,7 +25,7 @@ public EffectRenderingInventoryScreenMixin(AbstractContainerMenu menu, Inventory
at = @At("STORE")
)
public boolean modifyHasRoom(boolean bl) {
boolean ingredientListDisplayed = FabricGuiPlugin.getRuntime()
boolean ingredientListDisplayed = FabricRuntimePlugin.getRuntime()
.map(IJeiRuntime::getIngredientListOverlay)
.map(IIngredientListOverlay::isListDisplayed)
.orElse(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,12 @@ public void beforeInitialResourceReload(GameConfig gameConfig, CallbackInfo ci)
public void clearLevel(Screen screen, CallbackInfo ci) {
JeiLifecycleEvents.GAME_STOP.invoker().run();
}

@Inject(
method = "tick",
at = @At("TAIL")
)
private void jeiOnTickEnd(CallbackInfo ci) {
JeiLifecycleEvents.CLIENT_TICK_END.invoker().run();
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,51 @@
package mezz.jei.fabric.plugins.fabric;

import mezz.jei.api.IModPlugin;
import mezz.jei.api.IRuntimePlugin;
import mezz.jei.api.JeiPlugin;
import mezz.jei.api.constants.ModIds;
import mezz.jei.api.registration.IRuntimeRegistration;
import mezz.jei.api.runtime.IJeiRuntime;
import mezz.jei.fabric.startup.EventRegistration;
import mezz.jei.gui.startup.JeiEventHandlers;
import mezz.jei.gui.startup.JeiGuiStarter;
import net.minecraft.resources.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;

import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;

@JeiPlugin
public class FabricGuiPlugin implements IModPlugin {
public class FabricRuntimePlugin implements IRuntimePlugin {
private static final Logger LOGGER = LogManager.getLogger();
private static @Nullable IJeiRuntime runtime;

private final EventRegistration eventRegistration = new EventRegistration();

@Override
public ResourceLocation getPluginUid() {
return new ResourceLocation(ModIds.JEI_ID, "fabric_gui");
return new ResourceLocation(ModIds.JEI_ID, "fabric_runtime");
}

@Override
public void registerRuntime(IRuntimeRegistration registration) {
JeiEventHandlers eventHandlers = JeiGuiStarter.start(registration);
eventRegistration.setEventHandlers(eventHandlers);
public CompletableFuture<Void> registerRuntime(IRuntimeRegistration registration, Executor clientExecutor) {
return JeiGuiStarter.start(registration, clientExecutor)
.thenAccept(eventRegistration::setEventHandlers);
}

@Override
public void onRuntimeAvailable(IJeiRuntime jeiRuntime) {
public CompletableFuture<Void> onRuntimeAvailable(IJeiRuntime jeiRuntime, Executor clientExecutor) {
runtime = jeiRuntime;
return CompletableFuture.completedFuture(null);
}

@Override
public void onRuntimeUnavailable() {
public CompletableFuture<Void> onRuntimeUnavailable(Executor clientExecutor) {
runtime = null;
LOGGER.info("Stopping JEI GUI");
eventRegistration.clear();
return CompletableFuture.completedFuture(null);
}

public static Optional<IJeiRuntime> getRuntime() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package mezz.jei.fabric.startup;

import mezz.jei.api.IModPlugin;
import mezz.jei.common.Internal;
import mezz.jei.common.config.IServerConfig;
import mezz.jei.common.network.ClientPacketRouter;
Expand All @@ -16,8 +15,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.List;

public class ClientLifecycleHandler {
private static final Logger LOGGER = LogManager.getLogger();

Expand All @@ -34,9 +31,9 @@ public ClientLifecycleHandler(IServerConfig serverConfig) {
ClientPacketRouter packetRouter = new ClientPacketRouter(serverConnection, serverConfig);
ClientNetworkHandler.registerClientPacketHandler(packetRouter);

List<IModPlugin> plugins = FabricPluginFinder.getModPlugins();
StartData startData = new StartData(
plugins,
FabricPluginFinder pluginFinder = new FabricPluginFinder();
StartData startData = StartData.create(
pluginFinder,
serverConnection,
keyMappings
);
Expand All @@ -54,6 +51,7 @@ public void registerEvents() {
})
);
JeiLifecycleEvents.GAME_STOP.register(this::stopJei);
JeiLifecycleEvents.CLIENT_TICK_END.register(this.jeiStarter::tick);
}

public ResourceManagerReloadListener getReloadListener() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ private void registerEvents() {
ScreenEvents.BEFORE_INIT.register((client, screen, scaledWidth, scaledHeight) ->
registerScreenEvents(screen)
);
Screen currentScreen = Minecraft.getInstance().screen;
if (currentScreen != null) {
registerScreenEvents(currentScreen);
}
JeiCharTypedEvents.BEFORE_CHAR_TYPED.register(this::beforeCharTyped);
ScreenEvents.AFTER_INIT.register(this::afterInit);
JeiScreenEvents.AFTER_RENDER_BACKGROUND.register(this::afterRenderBackground);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
package mezz.jei.fabric.startup;

import mezz.jei.api.IModPlugin;
import mezz.jei.api.IRuntimePlugin;
import mezz.jei.library.startup.IPluginFinder;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public final class FabricPluginFinder {
private FabricPluginFinder() {
public final class FabricPluginFinder implements IPluginFinder {
private static final Map<Class<?>, String> entryPointKeys = Map.of(
IModPlugin.class, "jei_mod_plugin",
IRuntimePlugin.class, "jei_runtime_plugin"
);

}

public static List<IModPlugin> getModPlugins() {
return getInstances("jei_mod_plugin", IModPlugin.class);
}
@Override
public <T> List<T> getPlugins(Class<T> pluginClass) {
String entryPointKey = entryPointKeys.get(pluginClass);
if (entryPointKey == null) {
throw new IllegalArgumentException("FabricPluginFinder does not support " + pluginClass);
}

@SuppressWarnings("SameParameterValue")
private static <T> List<T> getInstances(String entrypointContainerKey, Class<T> instanceClass) {
FabricLoader fabricLoader = FabricLoader.getInstance();
List<EntrypointContainer<T>> pluginContainers = fabricLoader.getEntrypointContainers(entrypointContainerKey, instanceClass);
List<EntrypointContainer<T>> pluginContainers = fabricLoader.getEntrypointContainers(entryPointKey, pluginClass);
return pluginContainers.stream()
.map(EntrypointContainer::getEntrypoint)
.collect(Collectors.toList());
}

}
Loading