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

[1.19.4] Add option for asynchronous JEI reloading #3152

Open
wants to merge 6 commits into
base: 1.19.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package mezz.jei.common.async;

final class JeiAsyncStartInterrupt extends Error {
public JeiAsyncStartInterrupt() {
}
}
46 changes: 46 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,46 @@
package mezz.jei.common.async;

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

public JeiStartTask(Runnable startTask) {
this.startTask = startTask;
this.setName("JEI Start");
}

public void interruptStart() {
isCancelled = true;
}

/**
* Check whether the startup should be interrupted. If this is not running on a JEI startup thread,
* false is returned.
*/
private static boolean isStartInterrupted() {
Thread t = Thread.currentThread();
if(t instanceof JeiStartTask) {
return ((JeiStartTask)t).isCancelled;
} else
return false;
}

private static final JeiAsyncStartInterrupt INTERRUPT_START = new JeiAsyncStartInterrupt();

public static void checkStartInterruption() {
if(isStartInterrupted())
forceInterrupt();
}

public static void forceInterrupt() {
throw INTERRUPT_START;
}

@Override
public void run() {
try {
startTask.run();
} catch(JeiAsyncStartInterrupt ignored) {
}
}
}
23 changes: 23 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 @@ -5,6 +5,7 @@
import mezz.jei.common.config.file.IConfigSchemaBuilder;
import mezz.jei.common.config.file.serializers.EnumSerializer;
import mezz.jei.common.config.file.serializers.ListSerializer;
import mezz.jei.common.config.file.serializers.StringSerializer;
import org.jetbrains.annotations.Nullable;

import java.util.List;
Expand All @@ -17,6 +18,8 @@ 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<List<String>> mainThreadPluginUids;
private final Supplier<Boolean> addBookmarksToFront;
private final Supplier<GiveMode> giveMode;
private final Supplier<Integer> maxRecipeGuiHeight;
Expand Down Expand Up @@ -58,6 +61,16 @@ public ClientConfig(IConfigSchemaBuilder schema) {
Integer.MAX_VALUE,
"Max. recipe gui height"
);
asyncLoadingEnabled = advanced.addBoolean(
"AsyncLoading",
false,
"Whether JEI should load asynchronously"
);
mainThreadPluginUids = advanced.addList("AsyncPluginCompat",
List.of("namespace:mod"),
new ListSerializer<>(new StringSerializer()),
"List of plugin UIDs that should be loaded on the main thread"
);

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

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

@Override
public List<String> getAsyncCompatPluginUids() {
return mainThreadPluginUids.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,9 @@ public interface IClientConfig {

boolean isCheatToHotbarUsingHotkeysEnabled();

boolean isAsyncLoadingEnabled();
List<String> getAsyncCompatPluginUids();

boolean isAddingBookmarksToFront();

GiveMode getGiveMode();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package mezz.jei.common.config.file.serializers;

import mezz.jei.api.runtime.config.IJeiConfigValueSerializer;

import java.util.Collection;
import java.util.Optional;

public class StringSerializer implements IJeiConfigValueSerializer<String> {
@Override
public String serialize(String value) {
return value;
}

@Override
public IDeserializeResult<String> deserialize(String string) {
string = string.trim();
if (string.startsWith("\"") && string.endsWith("\"")) {
string = string.substring(1, string.length() - 1);
};
return new DeserializeResult<>(string);
}

@Override
public boolean isValid(String value) {
return true;
}

@Override
public Optional<Collection<String>> getAllValidValues() {
return Optional.empty();
}

@Override
public String getValidValuesDescription() {
return "";
}
}
14 changes: 14 additions & 0 deletions CommonApi/src/main/java/mezz/jei/api/IModPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import mezz.jei.api.registration.IVanillaCategoryExtensionRegistration;
import mezz.jei.api.runtime.IJeiRuntime;

import java.util.EnumSet;

/**
* 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.
Expand Down Expand Up @@ -137,4 +139,16 @@ default void onRuntimeUnavailable() {
default void onConfigManagerAvailable(IJeiConfigManager configManager) {

}

/**
* Called to find out whether this plugin wants to load on the main thread (legacy behavior), instead of the async
* loading thread.
* <p></p>
* Most plugins should use Minecraft.getInstance().executeBlocking() for their purposes, as plugins loading on the
* main thread will cause lag spikes.
* @since TODO
*/
default boolean needsLoadingOnClientThread() {
return false;
}
}
6 changes: 6 additions & 0 deletions CommonApi/src/main/java/mezz/jei/api/helpers/IJeiHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.minecraft.resources.ResourceLocation;

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

/**
* {@link IJeiHelpers} provides helpers and tools for addon mods.
Expand Down Expand Up @@ -67,4 +68,9 @@ public interface IJeiHelpers {
* @since 11.5.0
*/
IIngredientManager getIngredientManager();

/**
* Get access to the client executor, which budgets running background tasks on the main thread.
*/
Executor getClientExecutor();
}
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 @@ -48,4 +48,11 @@ 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
Expand Up @@ -54,6 +54,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 @@ -50,7 +50,7 @@ public JustEnoughItemsClient(

JeiStarter jeiStarter = new JeiStarter(startData);

this.startEventObserver = new StartEventObserver(jeiStarter::start, jeiStarter::stop);
this.startEventObserver = new StartEventObserver(jeiStarter::start, jeiStarter::stop, jeiStarter::tick);
this.startEventObserver.register(subscriptions);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import net.minecraftforge.client.event.RecipesUpdatedEvent;
import net.minecraftforge.client.event.ScreenEvent;
import net.minecraftforge.event.TagsUpdatedEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.Event;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand Down Expand Up @@ -37,11 +38,13 @@ private enum State {
private final Set<Class<? extends Event>> observedEvents = new HashSet<>();
private final Runnable startRunnable;
private final Runnable stopRunnable;
private final Runnable tickRunnable;
private State state = State.DISABLED;

public StartEventObserver(Runnable startRunnable, Runnable stopRunnable) {
public StartEventObserver(Runnable startRunnable, Runnable stopRunnable, Runnable tickRunnable) {
this.startRunnable = startRunnable;
this.stopRunnable = stopRunnable;
this.tickRunnable = tickRunnable;
}

public void register(PermanentEventSubscriptions subscriptions) {
Expand Down Expand Up @@ -79,6 +82,12 @@ public void register(PermanentEventSubscriptions subscriptions) {
}
}
});

subscriptions.register(TickEvent.ClientTickEvent.class, event -> {
if(event.phase == TickEvent.Phase.END && this.state == State.JEI_STARTED) {
this.tickRunnable.run();
}
});
}

/**
Expand Down
4 changes: 3 additions & 1 deletion Forge/src/test/java/mezz/jei/test/IngredientFilterTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package mezz.jei.test;

import com.google.common.util.concurrent.MoreExecutors;
import mezz.jei.api.helpers.IColorHelper;
import mezz.jei.api.helpers.IModIdHelper;
import mezz.jei.api.ingredients.IIngredientRenderer;
Expand Down Expand Up @@ -95,7 +96,8 @@ public void setup() {
baseList,
modIdHelper,
ingredientVisibility,
colorHelper
colorHelper,
MoreExecutors.directExecutor()
);

this.ingredientManager.registerIngredientListener(ingredientFilter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Stream;

import mezz.jei.api.ingredients.ITypedIngredient;
Expand Down Expand Up @@ -38,4 +40,6 @@ public interface IListElementInfo<V> {

int getSortedIndex();

CompletableFuture<Void> cacheTooltips(IIngredientFilterConfig config, IIngredientManager ingredientManager, Executor clientExecutor);

}
24 changes: 21 additions & 3 deletions Gui/src/main/java/mezz/jei/gui/ingredients/IngredientFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import mezz.jei.api.ingredients.subtypes.UidContext;
import mezz.jei.api.runtime.IIngredientManager;
import mezz.jei.api.runtime.IIngredientVisibility;
import mezz.jei.common.async.JeiStartTask;
import mezz.jei.common.config.DebugConfig;
import mezz.jei.common.util.Translator;
import mezz.jei.common.config.IClientConfig;
Expand All @@ -34,9 +35,12 @@
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class IngredientFilter implements IIngredientGridSource, IIngredientManager.IIngredientListener {
Expand Down Expand Up @@ -67,7 +71,8 @@ public IngredientFilter(
NonNullList<IListElement<?>> ingredients,
IModIdHelper modIdHelper,
IIngredientVisibility ingredientVisibility,
IColorHelper colorHelper
IColorHelper colorHelper,
Executor clientExecutor
) {
this.filterTextSource = filterTextSource;
this.ingredientManager = ingredientManager;
Expand All @@ -83,10 +88,18 @@ public IngredientFilter(
}

LOGGER.info("Adding {} ingredients", ingredients.size());
ingredients.stream()
List<IListElementInfo<?>> elementInfos = ingredients.stream()
.map(i -> ListElementInfo.create(i, ingredientManager, modIdHelper))
.flatMap(Optional::stream)
.forEach(this::addIngredient);
.collect(Collectors.toList());
List<CompletableFuture<?>> futures = new ArrayList<>();
for(IListElementInfo<?> elementInfo : elementInfos) {
futures.add(elementInfo.cacheTooltips(config, ingredientManager, clientExecutor));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
for(IListElementInfo<?> elementInfo : elementInfos) {
this.addIngredient(elementInfo);
}
LOGGER.info("Added {} ingredients", ingredients.size());

this.filterTextSource.addListener(filterText -> {
Expand All @@ -95,7 +108,12 @@ public IngredientFilter(
});
}

/* used to check for interruption periodically */
private int ingredientNum = 0;

public <V> void addIngredient(IListElementInfo<V> info) {
if(((ingredientNum++) % 100) == 0)
JeiStartTask.checkStartInterruption();
IListElement<V> element = info.getElement();
updateHiddenState(element);

Expand Down
Loading